From baa0792b3cef826c96cf967d9d7722000b71f9c1 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 17 Nov 2022 17:10:06 -0500 Subject: [PATCH 001/592] set main back to dev mode (#1739) --- CHANGELOG.md | 2 ++ charts/consul/Chart.yaml | 6 +++--- charts/consul/values.yaml | 2 +- cli/version/version.go | 4 ++-- control-plane/version/version.go | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d7e3d6198..5040bdabf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## UNRELEASED + ## 1.0.0 (November 17, 2022) BREAKING CHANGES: diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index 2eabc1fc9f..a8e1505280 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: consul -version: 1.0.0 +version: 1.1.0-dev appVersion: 1.14.0 kubeVersion: ">=1.21.0-0" description: Official HashiCorp Consul Chart @@ -10,12 +10,12 @@ sources: - https://github.com/hashicorp/consul - https://github.com/hashicorp/consul-k8s annotations: - artifacthub.io/prerelease: false + artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul image: hashicorp/consul:1.14.0 - name: consul-k8s-control-plane - image: hashicorp/consul-k8s-control-plane:1.0.0 + image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.1.0-dev - name: consul-dataplane image: hashicorp/consul-dataplane:1.0.0 - name: envoy diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 1daec76b9f..bdf66a53a6 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -83,7 +83,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: hashicorp/consul-k8s-control-plane:1.0.0 + imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.1.0-dev # The name of the datacenter that the agents should # register as. This can't be changed once the Consul cluster is up and running diff --git a/cli/version/version.go b/cli/version/version.go index 591d8ccca0..933f072f35 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -14,12 +14,12 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.0.0" + Version = "1.1.0" // 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 = "" + VersionPrerelease = "dev" ) // GetHumanVersion composes the parts of the version in a way that's suitable diff --git a/control-plane/version/version.go b/control-plane/version/version.go index 591d8ccca0..933f072f35 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -14,12 +14,12 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.0.0" + Version = "1.1.0" // 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 = "" + VersionPrerelease = "dev" ) // GetHumanVersion composes the parts of the version in a way that's suitable From 831457765218143bad33d1a35542227fe65be4f6 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 18 Nov 2022 14:28:43 -0500 Subject: [PATCH 002/592] Add fix for api-gateway when using system-wide trusted CAs for external servers --- .../api-gateway-controller-deployment.yaml | 2 ++ .../api-gateway-controller-deployment.bats | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 1e12df90a4..52884f725b 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -57,9 +57,11 @@ spec: protocol: TCP env: {{- if .Values.global.tls.enabled }} + {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} - name: CONSUL_CACERT value: /consul/tls/ca/tls.crt {{- end }} + {{- end }} - name: HOST_IP valueFrom: fieldRef: diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index fe150fe158..b61486c2ed 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -1370,3 +1370,32 @@ load _helpers yq '.spec.template.spec.containers[0].env[3]' | tee /dev/stderr) [ "${actual}" = "null" ] } + +@test "apiGateway/Deployment: CONSUL_CACERT is set when using tls and clients even when useSystemRoots is true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + --set 'client.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[0].name == "CONSUL_CACERT"' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "apiGateway/Deployment: CONSUL_CACERT is not set when using tls and useSystemRoots" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[0].name == "CONSUL_CACERT"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} From eb085204ee13ed4d633eeb45a8c7883c12864092 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 18 Nov 2022 14:30:56 -0500 Subject: [PATCH 003/592] Add changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5040bdabf8..8565dd7e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## UNRELEASED +BUG FIXES: +* Helm: + * Don't pass in a CA file to the API Gateway controller when `externalServers.useSystemRoots` is `true`. [[GH-1743](https://github.com/hashicorp/consul-k8s/pull/1743)] + ## 1.0.0 (November 17, 2022) BREAKING CHANGES: From e0b6fa6e2d8d25fd54a6e78f7147bd59b6dbb1ee Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 18 Nov 2022 14:50:34 -0500 Subject: [PATCH 004/592] Save forgotten changes --- .../api-gateway-controller-deployment.bats | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index b61486c2ed..5f00cb65a0 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -1377,6 +1377,7 @@ load _helpers -s templates/api-gateway-controller-deployment.yaml \ --set 'apiGateway.enabled=true' \ --set 'apiGateway.image=bar' \ + --set 'global.tls.enabled=true' \ --set 'server.enabled=false' \ --set 'externalServers.hosts[0]=external-consul.host' \ --set 'externalServers.enabled=true' \ @@ -1384,7 +1385,20 @@ load _helpers --set 'client.enabled=true' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].env[0].name == "CONSUL_CACERT"' | tee /dev/stderr) - [ "${actual}" = "false" ] + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_CACERT is set when using tls and internal servers" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[0].name == "CONSUL_CACERT"' | tee /dev/stderr) + [ "${actual}" = "true" ] } @test "apiGateway/Deployment: CONSUL_CACERT is not set when using tls and useSystemRoots" { @@ -1395,7 +1409,10 @@ load _helpers --set 'apiGateway.image=bar' \ --set 'global.tls.enabled=true' \ --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].env[0].name == "CONSUL_CACERT"' | tee /dev/stderr) - [ "${actual}" = "true" ] + [ "${actual}" = "false" ] } From 47173e77a3815bde0d9d5d7f0dbc9493ce448dbf Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 18 Nov 2022 11:52:56 -0800 Subject: [PATCH 005/592] added fix for backport assistant on OSS repos with forks (#1744) --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index c1548332e0..9c241d9ada 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -2,7 +2,7 @@ name: Backport Assistant Runner on: - pull_request: + pull_request_target: types: - closed - labeled From a6c36a383ba537ac2e7c151d3b7bdc13b545de82 Mon Sep 17 00:00:00 2001 From: Kyle Schochenmaier Date: Fri, 18 Nov 2022 15:40:48 -0600 Subject: [PATCH 006/592] remove controller webhook cert roles from vault test and the values.yaml (#1740) * remove controller webhook cert roles from vault test and the values.yaml --- acceptance/tests/vault/vault_test.go | 17 ---------- charts/consul/templates/_helpers.tpl | 33 +++---------------- .../webhook-cert-manager-clusterrole.yaml | 2 +- ...bhook-cert-manager-clusterrolebinding.yaml | 2 +- .../webhook-cert-manager-configmap.yaml | 2 +- .../webhook-cert-manager-deployment.yaml | 2 +- ...ebhook-cert-manager-podsecuritypolicy.yaml | 2 +- .../webhook-cert-manager-serviceaccount.yaml | 2 +- .../test/unit/connect-inject-clusterrole.bats | 3 -- .../test/unit/connect-inject-deployment.bats | 24 ++------------ .../webhook-cert-manager-clusterrole.bats | 5 +-- ...bhook-cert-manager-clusterrolebinding.bats | 5 +-- .../unit/webhook-cert-manager-configmap.bats | 5 +-- .../unit/webhook-cert-manager-deployment.bats | 5 +-- ...ebhook-cert-manager-podsecuritypolicy.bats | 5 +-- .../webhook-cert-manager-serviceaccount.bats | 5 +-- charts/consul/values.yaml | 25 -------------- 17 files changed, 19 insertions(+), 125 deletions(-) diff --git a/acceptance/tests/vault/vault_test.go b/acceptance/tests/vault/vault_test.go index fdde364b5b..cf0c926b22 100644 --- a/acceptance/tests/vault/vault_test.go +++ b/acceptance/tests/vault/vault_test.go @@ -84,20 +84,6 @@ func TestVault(t *testing.T) { } serverPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) - // Configure controller webhook PKI - controllerWebhookPKIConfig := &vault.PKIAndAuthRoleConfiguration{ - BaseURL: "controller", - PolicyName: "controller-ca-policy", - RoleName: "controller-ca-role", - KubernetesNamespace: ns, - DataCenter: "dc1", - ServiceAccountName: fmt.Sprintf("%s-consul-%s", consulReleaseName, "controller"), - AllowedSubdomain: fmt.Sprintf("%s-consul-%s", consulReleaseName, "controller-webhook"), - MaxTTL: fmt.Sprintf("%ds", expirationInSeconds), - AuthMethodPath: KubernetesAuthMethodPath, - } - controllerWebhookPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) - // Configure connect injector webhook PKI connectInjectorWebhookPKIConfig := &vault.PKIAndAuthRoleConfiguration{ BaseURL: "connect", @@ -212,15 +198,12 @@ func TestVault(t *testing.T) { "connectInject.replicas": "1", "global.secretsBackend.vault.connectInject.tlsCert.secretName": connectInjectorWebhookPKIConfig.CertPath, "global.secretsBackend.vault.connectInject.caCert.secretName": connectInjectorWebhookPKIConfig.CAPath, - "global.secretsBackend.vault.controller.tlsCert.secretName": controllerWebhookPKIConfig.CertPath, - "global.secretsBackend.vault.controller.caCert.secretName": controllerWebhookPKIConfig.CAPath, "global.secretsBackend.vault.enabled": "true", "global.secretsBackend.vault.consulServerRole": consulServerRole, "global.secretsBackend.vault.consulClientRole": consulClientRole, "global.secretsBackend.vault.consulCARole": serverPKIConfig.RoleName, "global.secretsBackend.vault.connectInjectRole": connectInjectorWebhookPKIConfig.RoleName, - "global.secretsBackend.vault.controllerRole": controllerWebhookPKIConfig.RoleName, "global.secretsBackend.vault.manageSystemACLsRole": manageSystemACLsRole, "global.secretsBackend.vault.ca.secretName": vaultCASecret, diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index e2f735e690..3552c8c209 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -73,22 +73,6 @@ as well as the global.name setting. {{ "{{" }}- end -{{ "}}" }} {{- end -}} -{{- define "consul.controllerWebhookTLSCertTemplate" -}} - | - {{ "{{" }}- with secret "{{ .Values.global.secretsBackend.vault.controller.tlsCert.secretName }}" "{{- $name := include "consul.fullname" . -}}{{ printf "common_name=%s-controller-webhook" $name }}" - "alt_names={{ include "consul.controllerWebhookTLSAltNames" . }}" -{{ "}}" }} - {{ "{{" }}- .Data.certificate -{{ "}}" }} - {{ "{{" }}- end -{{ "}}" }} -{{- end -}} - -{{- define "consul.controllerWebhookTLSKeyTemplate" -}} - | - {{ "{{" }}- with secret "{{ .Values.global.secretsBackend.vault.controller.tlsCert.secretName }}" "{{- $name := include "consul.fullname" . -}}{{ printf "common_name=%s-controller-webhook" $name }}" - "alt_names={{ include "consul.controllerWebhookTLSAltNames" . }}" -{{ "}}" }} - {{ "{{" }}- .Data.private_key -{{ "}}" }} - {{ "{{" }}- end -{{ "}}" }} -{{- end -}} - {{- define "consul.serverTLSAltNames" -}} {{- $name := include "consul.fullname" . -}} {{- $ns := .Release.Namespace -}} @@ -109,12 +93,6 @@ as well as the global.name setting. {{ printf "%s-connect-injector,%s-connect-injector.%s,%s-connect-injector.%s.svc,%s-connect-injector.%s.svc.cluster.local" $name $name $ns $name $ns $name $ns}} {{- end -}} -{{- define "consul.controllerWebhookTLSAltNames" -}} -{{- $name := include "consul.fullname" . -}} -{{- $ns := .Release.Namespace -}} -{{ printf "%s-controller-webhook,%s-controller-webhook.%s,%s-controller-webhook.%s.svc,%s-controller-webhook.%s.svc.cluster.local" $name $name $ns $name $ns $name $ns}} -{{- end -}} - {{- define "consul.vaultReplicationTokenTemplate" -}} | {{ "{{" }}- with secret "{{ .Values.global.acls.replicationToken.secretName }}" -{{ "}}" }} @@ -285,20 +263,17 @@ Fails when at least one but not all of the following have been set: - global.secretsBackend.vault.connectInjectRole - global.secretsBackend.vault.connectInject.tlsCert.secretName - global.secretsBackend.vault.connectInject.caCert.secretName -- global.secretsBackend.vault.controllerRole -- global.secretsBackend.vault.controller.tlsCert.secretName -- global.secretsBackend.vault.controller.caCert.secretName The above values are needed in full to turn off web cert manager and allow -connect inject and controller to manage its own webhook certs. +connect inject to manage its own webhook certs. Usage: {{ template "consul.validateVaultWebhookCertConfiguration" . }} */}} {{- define "consul.validateVaultWebhookCertConfiguration" -}} -{{- if or .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName}} -{{- if or (not .Values.global.secretsBackend.vault.connectInjectRole) (not .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) (not .Values.global.secretsBackend.vault.connectInject.caCert.secretName) (not .Values.global.secretsBackend.vault.controllerRole) (not .Values.global.secretsBackend.vault.controller.tlsCert.secretName) (not .Values.global.secretsBackend.vault.controller.caCert.secretName) }} -{{fail "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName."}} +{{- if or .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName}} +{{- if or (not .Values.global.secretsBackend.vault.connectInjectRole) (not .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) (not .Values.global.secretsBackend.vault.connectInject.caCert.secretName) }} +{{fail "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName"}} {{ end }} {{ end }} {{- end -}} diff --git a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml index c2a2422d02..e13e2dc741 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml index ca2bb84bda..472ef4ee1d 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/webhook-cert-manager-configmap.yaml b/charts/consul/templates/webhook-cert-manager-configmap.yaml index 914d2b87dd..293dd32d9f 100644 --- a/charts/consul/templates/webhook-cert-manager-configmap.yaml +++ b/charts/consul/templates/webhook-cert-manager-configmap.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 kind: ConfigMap diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index 557cc0219b..be22d4da7a 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: apps/v1 kind: Deployment diff --git a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml index b67dbda510..4d685edc39 100644 --- a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml +++ b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled))) }} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: policy/v1beta1 diff --git a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml index fa4b24ef8b..68c54f3c27 100644 --- a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml +++ b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/test/unit/connect-inject-clusterrole.bats b/charts/consul/test/unit/connect-inject-clusterrole.bats index b1ad10a80c..4acdf211d2 100644 --- a/charts/consul/test/unit/connect-inject-clusterrole.bats +++ b/charts/consul/test/unit/connect-inject-clusterrole.bats @@ -193,9 +193,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index b851d863ed..4879049c49 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -1633,9 +1633,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=test' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=foo/tls' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].command | any(contains("-enable-webhook-ca-update"))' | tee /dev/stderr) [ "${actual}" = "true" ] @@ -1742,7 +1739,7 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=connectinjectcarole' \ --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . [ "$status" -eq 1 ] - [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName" ]] } @test "connectInject/Deployment: fails if vault is enabled and global.secretsBackend.vault.connectInject.tlsCert.secretName is set but global.secretsBackend.vault.connectInjectRole and global.secretsBackend.vault.connectInject.caCert.secretName are not" { @@ -1759,7 +1756,7 @@ load _helpers --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . [ "$status" -eq 1 ] - [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName" ]] } @test "connectInject/Deployment: fails if vault is enabled and global.secretsBackend.vault.connectInject.caCert.secretName is set but global.secretsBackend.vault.connectInjectRole and global.secretsBackend.vault.connectInject.tlsCert.secretName are not" { @@ -1776,7 +1773,7 @@ load _helpers --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . [ "$status" -eq 1 ] - [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName" ]] } @test "connectInject/Deployment: vault tls annotations are set when tls is enabled" { @@ -1794,9 +1791,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=test' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq -r '.spec.template.metadata' | tee /dev/stderr) @@ -1870,9 +1864,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ @@ -1895,9 +1886,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'server.serverCert.secretName=pki_int/issue/test' \ --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ . | tee /dev/stderr | @@ -1927,9 +1915,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq '.spec.template.spec.volumes[] | select(.name == "certs")' | tee /dev/stderr) [ "${actual}" == "" ] @@ -1949,9 +1934,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "certs")' | tee /dev/stderr) [ "${actual}" == "" ] diff --git a/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats b/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats index d58ce20928..4d1a4abdd2 100644 --- a/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats +++ b/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats @@ -130,7 +130,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/ClusterRole: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { +@test "webhookCertManager/ClusterRole: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-clusterrole.yaml \ @@ -142,9 +142,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats b/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats index 89a60a0ef3..2e507d279d 100644 --- a/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats +++ b/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats @@ -24,7 +24,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/ClusterRoleBinding: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { +@test "webhookCertManager/ClusterRoleBinding: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-clusterrolebinding.yaml \ @@ -36,9 +36,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-configmap.bats b/charts/consul/test/unit/webhook-cert-manager-configmap.bats index 774ada21d1..196da220d4 100644 --- a/charts/consul/test/unit/webhook-cert-manager-configmap.bats +++ b/charts/consul/test/unit/webhook-cert-manager-configmap.bats @@ -24,7 +24,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/Configmap: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { +@test "webhookCertManager/Configmap: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-configmap.yaml \ @@ -36,9 +36,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-deployment.bats b/charts/consul/test/unit/webhook-cert-manager-deployment.bats index 28771038dc..e6549200cf 100644 --- a/charts/consul/test/unit/webhook-cert-manager-deployment.bats +++ b/charts/consul/test/unit/webhook-cert-manager-deployment.bats @@ -66,7 +66,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/Deployment: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { +@test "webhookCertManager/Deployment: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-deployment.yaml \ @@ -78,9 +78,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats b/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats index 820be91404..d8e16f867c 100644 --- a/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats +++ b/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats @@ -35,7 +35,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/PodSecurityPolicy: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { +@test "webhookCertManager/PodSecurityPolicy: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-podsecuritypolicy.yaml \ @@ -48,9 +48,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats b/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats index 766d20fb66..f420e7319c 100644 --- a/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats +++ b/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats @@ -45,7 +45,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/ServiceAccount: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { +@test "webhookCertManager/ServiceAccount: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-serviceaccount.yaml \ @@ -57,9 +57,6 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ - --set 'global.secretsBackend.vault.controllerRole=test' \ - --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ - --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index bdf66a53a6..fde9ad9513 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -160,12 +160,6 @@ global: # and check the name of `metadata.name`. adminPartitionsRole: "" - # The Vault role to read Consul controller's webhook's - # CA and issue a certificate and private key. - # A Vault policy must be created which grants issue capabilities to - # `global.secretsBackend.vault.controller.tlsCert.secretName`. - controllerRole: "" - # The Vault role to read Consul connect-injector webhook's CA # and issue a certificate and private key. # A Vault policy must be created which grants issue capabilities to @@ -241,25 +235,6 @@ global: additionalConfig: | {} - controller: - # Configuration to the Vault Secret that Kubernetes will use on - # Kubernetes CRD creation, deletion, and update, to get TLS certificates - # used issued from vault to send webhooks to the controller. - tlsCert: - # The Vault secret path that issues TLS certificates for controller - # webhooks. - # @type: string - secretName: null - - # Configuration to the Vault Secret that Kubernetes will use on - # Kubernetes CRD creation, deletion, and update, to get CA certificates - # used issued from vault to send webhooks to the controller. - caCert: - # The Vault secret path that contains the CA certificate for controller - # webhooks. - # @type: string - secretName: null - connectInject: # Configuration to the Vault Secret that Kubernetes will use on # Kubernetes pod creation, deletion, and update, to get CA certificates From 94506bb87eb65b7bb6c95de9b05ff0f4312d6d6a Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Mon, 21 Nov 2022 12:49:07 -0500 Subject: [PATCH 007/592] Add discover binary to control-plane image (#1749) * add discover binary to control-plane image --- CHANGELOG.md | 2 ++ control-plane/Dockerfile | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8565dd7e95..4b10f3be33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## UNRELEASED BUG FIXES: +* Control Plane + * Add discover binary to control-plane image [[GH-1749](https://github.com/hashicorp/consul-k8s/pull/1749)] * Helm: * Don't pass in a CA file to the API Gateway controller when `externalServers.useSystemRoots` is `true`. [[GH-1743](https://github.com/hashicorp/consul-k8s/pull/1743)] diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index 9ac4188c22..3e31c92ef6 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -11,10 +11,14 @@ # # =================================== +# go-discover builds the discover binary (which we don't currently publish +# either). +FROM golang:1.19.2-alpine as go-discover +RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@49f60c093101c9c5f6b04d5b1c80164251a761a6 + # dev copies the binary from a local build # ----------------------------------- # BIN_NAME is a requirement in the hashicorp docker github action - FROM alpine:3.16 AS dev # NAME and VERSION are the name of the software in releases.hashicorp.com @@ -43,6 +47,7 @@ RUN apk add --no-cache ca-certificates gnupg libcap openssl su-exec iputils libc RUN addgroup ${BIN_NAME} && \ adduser -S -G ${BIN_NAME} 100 +COPY --from=go-discover /go/bin/discover /bin/ COPY pkg/bin/linux_${TARGETARCH}/${BIN_NAME} /bin COPY cni/pkg/bin/linux_${TARGETARCH}/${CNI_BIN_NAME} /bin @@ -94,6 +99,7 @@ ARG TARGETARCH RUN addgroup ${BIN_NAME} && \ adduser -S -G ${BIN_NAME} 100 +COPY --from=go-discover /go/bin/discover /bin/ COPY dist/${TARGETOS}/${TARGETARCH}/${BIN_NAME} /bin/ COPY dist/cni/${TARGETOS}/${TARGETARCH}/${CNI_BIN_NAME} /bin/ @@ -155,6 +161,7 @@ RUN groupadd --gid 1000 ${BIN_NAME} && \ adduser --uid 100 --system -g ${BIN_NAME} ${BIN_NAME} && \ usermod -a -G root ${BIN_NAME} +COPY --from=go-discover /go/bin/discover /bin/ COPY dist/${TARGETOS}/${TARGETARCH}/${BIN_NAME} /bin/ COPY dist/cni/${TARGETOS}/${TARGETARCH}/${CNI_BIN_NAME} /bin/ From ef421f968770e96ee2cbbb471423169b579aa1de Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:38:55 -0800 Subject: [PATCH 008/592] ran prepare-dev script (#1754) - fixed changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b10f3be33..ab1b80bdd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## UNRELEASED +## 1.0.1 (November 21, 2022) + BUG FIXES: * Control Plane * Add discover binary to control-plane image [[GH-1749](https://github.com/hashicorp/consul-k8s/pull/1749)] From 0a64d26c3889fcb19a2e4758c812c3b69b3b7568 Mon Sep 17 00:00:00 2001 From: Kyle Schochenmaier Date: Wed, 30 Nov 2022 13:45:39 -0600 Subject: [PATCH 009/592] Add support for setting the namespace that the CNI plugin is installed (#1756) * Add support for setting the namespace that the CNI plugin is installed into --- CHANGELOG.md | 4 ++ charts/consul/templates/cni-clusterrole.yaml | 2 +- .../templates/cni-clusterrolebinding.yaml | 2 +- charts/consul/templates/cni-daemonset.yaml | 2 +- .../cni-networkattachmentdefinition.yaml | 2 +- .../templates/cni-podsecuritypolicy.yaml | 2 +- .../consul/templates/cni-resourcequota.yaml | 2 +- .../cni-securitycontextconstraints.yaml | 2 +- .../consul/templates/cni-serviceaccount.yaml | 2 +- charts/consul/test/unit/cni-clusterrole.bats | 23 ++++++++++ .../test/unit/cni-clusterrolebinding.bats | 22 +++++++++ charts/consul/test/unit/cni-daemonset.bats | 45 +++++++++++++++++++ .../unit/cni-networkattachmentdefinition.bats | 24 ++++++++++ .../test/unit/cni-podsecuritypolicy.bats | 24 ++++++++++ .../consul/test/unit/cni-resourcequota.bats | 23 ++++++++++ .../unit/cni-securitycontextcontstraints.bats | 24 ++++++++++ .../consul/test/unit/cni-serviceaccount.bats | 23 ++++++++++ charts/consul/values.yaml | 5 +++ 18 files changed, 225 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1b80bdd2..5693dd1c28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## UNRELEASED +IMPROVEMENTS: +* Helm: + * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] + ## 1.0.1 (November 21, 2022) BUG FIXES: diff --git a/charts/consul/templates/cni-clusterrole.yaml b/charts/consul/templates/cni-clusterrole.yaml index 39dc5ead50..773942cca8 100644 --- a/charts/consul/templates/cni-clusterrole.yaml +++ b/charts/consul/templates/cni-clusterrole.yaml @@ -3,7 +3,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: {{ template "consul.fullname" . }}-cni - namespace: {{ .Release.Namespace }} + namespace: {{ default .Release.Namespace .Values.connectInject.cni.namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/cni-clusterrolebinding.yaml b/charts/consul/templates/cni-clusterrolebinding.yaml index 86c19d86aa..4b860388b6 100644 --- a/charts/consul/templates/cni-clusterrolebinding.yaml +++ b/charts/consul/templates/cni-clusterrolebinding.yaml @@ -16,5 +16,5 @@ roleRef: subjects: - kind: ServiceAccount name: {{ template "consul.fullname" . }}-cni - namespace: {{ .Release.Namespace }} + namespace: {{ default .Release.Namespace .Values.connectInject.cni.namespace }} {{- end }} diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index 7b9f90d939..e9a6807338 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -4,7 +4,7 @@ apiVersion: apps/v1 kind: DaemonSet metadata: name: {{ template "consul.fullname" . }}-cni - namespace: {{ .Release.Namespace }} + namespace: {{ default .Release.Namespace .Values.connectInject.cni.namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/cni-networkattachmentdefinition.yaml b/charts/consul/templates/cni-networkattachmentdefinition.yaml index d0feaf5cb1..80ef50bac6 100644 --- a/charts/consul/templates/cni-networkattachmentdefinition.yaml +++ b/charts/consul/templates/cni-networkattachmentdefinition.yaml @@ -3,7 +3,7 @@ apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: {{ template "consul.fullname" . }}-cni - namespace: {{ .Release.Namespace }} + namespace: {{ default .Release.Namespace .Values.connectInject.cni.namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/cni-podsecuritypolicy.yaml b/charts/consul/templates/cni-podsecuritypolicy.yaml index 15b96bc230..b600ed1b4b 100644 --- a/charts/consul/templates/cni-podsecuritypolicy.yaml +++ b/charts/consul/templates/cni-podsecuritypolicy.yaml @@ -3,7 +3,7 @@ apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: {{ template "consul.fullname" . }}-cni - namespace: {{ .Release.Namespace }} + namespace: {{ default .Release.Namespace .Values.connectInject.cni.namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/cni-resourcequota.yaml b/charts/consul/templates/cni-resourcequota.yaml index abfe5a8876..054c3061f5 100644 --- a/charts/consul/templates/cni-resourcequota.yaml +++ b/charts/consul/templates/cni-resourcequota.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: ResourceQuota metadata: name: {{ template "consul.fullname" . }}-cni - namespace: {{ .Release.Namespace }} + namespace: {{ default .Release.Namespace .Values.connectInject.cni.namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/cni-securitycontextconstraints.yaml b/charts/consul/templates/cni-securitycontextconstraints.yaml index 95cfc555e1..2c09dba9b8 100644 --- a/charts/consul/templates/cni-securitycontextconstraints.yaml +++ b/charts/consul/templates/cni-securitycontextconstraints.yaml @@ -3,7 +3,7 @@ apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints metadata: name: {{ template "consul.fullname" . }}-cni - namespace: {{ .Release.Namespace }} + namespace: {{ default .Release.Namespace .Values.connectInject.cni.namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/cni-serviceaccount.yaml b/charts/consul/templates/cni-serviceaccount.yaml index 6b2a7627f7..cf4250b696 100644 --- a/charts/consul/templates/cni-serviceaccount.yaml +++ b/charts/consul/templates/cni-serviceaccount.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ template "consul.fullname" . }}-cni - namespace: {{ .Release.Namespace }} + namespace: {{ default .Release.Namespace .Values.connectInject.cni.namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/test/unit/cni-clusterrole.bats b/charts/consul/test/unit/cni-clusterrole.bats index 02675ed882..4556d48f0d 100644 --- a/charts/consul/test/unit/cni-clusterrole.bats +++ b/charts/consul/test/unit/cni-clusterrole.bats @@ -20,6 +20,29 @@ load _helpers [[ "${actual}" == "true" ]] } +@test "cni/ClusterRole: cni namespace has a default when not set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-clusterrole.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "default" ]] +} + +@test "cni/ClusterRole: able to set cni namespace" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-clusterrole.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} + @test "cni/ClusterRole: disabled with connectInject.cni.enabled=false and connectInject.enabled=true" { cd `chart_dir` assert_empty helm template \ diff --git a/charts/consul/test/unit/cni-clusterrolebinding.bats b/charts/consul/test/unit/cni-clusterrolebinding.bats index ba217e7706..98cdb283c4 100644 --- a/charts/consul/test/unit/cni-clusterrolebinding.bats +++ b/charts/consul/test/unit/cni-clusterrolebinding.bats @@ -55,3 +55,25 @@ load _helpers [ "${actual}" = "foo" ] } +@test "cni/ClusterRoleBinding: subject namespace is correct when not set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-clusterrolebinding.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.subjects[0].namespace' | tee /dev/stderr) + [[ "${actual}" == "default" ]] +} + +@test "cni/ClusterRoleBinding: subject namespace can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-clusterrolebinding.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.subjects[0].namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} diff --git a/charts/consul/test/unit/cni-daemonset.bats b/charts/consul/test/unit/cni-daemonset.bats index 17c80d2da0..3b5e046a67 100644 --- a/charts/consul/test/unit/cni-daemonset.bats +++ b/charts/consul/test/unit/cni-daemonset.bats @@ -295,3 +295,48 @@ rollingUpdate: [ "${actual}" = '{"mountPath":"bar","name":"cni-net-dir"}' ] } +@test "cni/DaemonSet: cni namespace has a default when not set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-daemonset.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "default" ]] +} + +@test "cni/DaemonSet: able to set cni namespace" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-daemonset.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} + +@test "cni/DaemonSet: still uses cni.namespace when helm -n is used" { + cd `chart_dir` + local actual=$(helm template -n foo \ + -s templates/cni-daemonset.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} + +@test "cni/DaemonSet: default namespace can be overridden by helm -n" { + cd `chart_dir` + local actual=$(helm template -n foo \ + -s templates/cni-daemonset.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "foo" ]] +} diff --git a/charts/consul/test/unit/cni-networkattachmentdefinition.bats b/charts/consul/test/unit/cni-networkattachmentdefinition.bats index a7f0d1da03..65730079bb 100644 --- a/charts/consul/test/unit/cni-networkattachmentdefinition.bats +++ b/charts/consul/test/unit/cni-networkattachmentdefinition.bats @@ -59,3 +59,27 @@ load _helpers } +@test "cni/NetworkAttachmentDefinition: cni namespace has a default when not set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-networkattachmentdefinition.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.cni.multus=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "default" ]] +} + +@test "cni/NetworkAttachmentDefinition: able to set cni namespace" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-networkattachmentdefinition.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.cni.multus=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} diff --git a/charts/consul/test/unit/cni-podsecuritypolicy.bats b/charts/consul/test/unit/cni-podsecuritypolicy.bats index 37df761995..21af659cde 100644 --- a/charts/consul/test/unit/cni-podsecuritypolicy.bats +++ b/charts/consul/test/unit/cni-podsecuritypolicy.bats @@ -30,3 +30,27 @@ load _helpers [[ "${actual}" == "true" ]] } +@test "cni/PodSecurityPolicy: cni namespace has a default when not set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-podsecuritypolicy.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.enablePodSecurityPolicies=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "default" ]] +} + +@test "cni/PodSecurityPolicy: able to set cni namespace" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-podsecuritypolicy.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.enablePodSecurityPolicies=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} diff --git a/charts/consul/test/unit/cni-resourcequota.bats b/charts/consul/test/unit/cni-resourcequota.bats index 36c7a26b30..f7495d3565 100644 --- a/charts/consul/test/unit/cni-resourcequota.bats +++ b/charts/consul/test/unit/cni-resourcequota.bats @@ -29,6 +29,29 @@ load _helpers . } +@test "cni/ResourceQuota: cni namespace has a default when not set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-resourcequota.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "default" ]] +} + +@test "cni/ResourceQuota: able to set cni namespace" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-resourcequota.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} + #-------------------------------------------------------------------- # pods diff --git a/charts/consul/test/unit/cni-securitycontextcontstraints.bats b/charts/consul/test/unit/cni-securitycontextcontstraints.bats index 759979aee2..933282f0dc 100644 --- a/charts/consul/test/unit/cni-securitycontextcontstraints.bats +++ b/charts/consul/test/unit/cni-securitycontextcontstraints.bats @@ -31,3 +31,27 @@ load _helpers [ "${actual}" = "true" ] } +@test "cni/SecurityContextConstraints: cni namespace has a default when not set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-securitycontextconstraints.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.openshift.enabled=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "default" ]] +} + +@test "cni/SecurityContextConstraints: able to set cni namespace" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-securitycontextconstraints.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.openshift.enabled=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} diff --git a/charts/consul/test/unit/cni-serviceaccount.bats b/charts/consul/test/unit/cni-serviceaccount.bats index 4f2071f823..73146bd0d9 100644 --- a/charts/consul/test/unit/cni-serviceaccount.bats +++ b/charts/consul/test/unit/cni-serviceaccount.bats @@ -29,6 +29,29 @@ load _helpers . } +@test "cni/ServiceAccount: cni namespace has a default when not set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-serviceaccount.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "default" ]] +} + +@test "cni/ServiceAccount: able to set cni namespace" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-serviceaccount.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.cni.namespace=kube-system' \ + . | tee /dev/stderr | + yq -r -c '.metadata.namespace' | tee /dev/stderr) + [[ "${actual}" == "kube-system" ]] +} + #-------------------------------------------------------------------- # global.imagePullSecrets diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index fde9ad9513..9122d62d53 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -1920,6 +1920,11 @@ connectInject: # @type: string logLevel: null + # Set the namespace to install the CNI plugin into. Overrides global namespace settings for CNI resources. + # Ex: "kube-system" + # @type: string + namespace: null + # Location on the kubernetes node where the CNI plugin is installed. Shoud be the absolute path and start with a '/' # Example on GKE: # From 1feaa9fef89f5203e442578c1c053d9a24b6b9f8 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 30 Nov 2022 17:09:27 -0500 Subject: [PATCH 010/592] Fix TLS Cert when using `enableAutoEncrypt` (#1753) * Fix name of autoencrypt cert * Update BATS * Update CHANGELOG * Remove HOST_IP * Don't mount consul-ca-cert when using system roots and external servers * Remove timeout and partition flags' * Don't mount consul-ca-cert when using system roots and external servers on main container --- CHANGELOG.md | 4 + .../api-gateway-controller-deployment.yaml | 20 +- .../api-gateway-controller-deployment.bats | 173 ++++++++++++++---- 3 files changed, 151 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5693dd1c28..7cc3a1b74f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ IMPROVEMENTS: * Helm: * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] +BUG FIXES: +* Helm: + * Use the correct autogenerated cert for the API Gateway Controller when connecting to servers versus clients. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] + * Don't mount the CA cert when `externalServers.useSystemRoots` is `true`. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] ## 1.0.1 (November 21, 2022) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 52884f725b..808f93b421 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -56,8 +56,8 @@ spec: name: sds protocol: TCP env: - {{- if .Values.global.tls.enabled }} {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} + {{- if .Values.global.tls.enabled }} - name: CONSUL_CACERT value: /consul/tls/ca/tls.crt {{- end }} @@ -149,8 +149,9 @@ spec: - name: consul-bin mountPath: /consul-bin {{- end }} + {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} {{- if .Values.global.tls.enabled }} - {{- if .Values.global.tls.enableAutoEncrypt }} + {{- if and .Values.client.enabled .Values.global.tls.enableAutoEncrypt }} - name: consul-auto-encrypt-ca-cert {{- else }} - name: consul-ca-cert @@ -158,6 +159,7 @@ spec: mountPath: /consul/tls/ca readOnly: true {{- end }} + {{- end }} - mountPath: /consul/login name: consul-data readOnly: true @@ -222,10 +224,6 @@ spec: {{- if .Values.global.acls.manageSystemACLs }} - name: api-gateway-controller-acl-init env: - - name: HOST_IP - valueFrom: - fieldRef: - fieldPath: status.hostIP - name: NAMESPACE valueFrom: fieldRef: @@ -242,15 +240,13 @@ spec: - mountPath: /consul/login name: consul-data readOnly: false + {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} {{- if .Values.global.tls.enabled }} - {{- if .Values.global.tls.enableAutoEncrypt }} - - name: consul-auto-encrypt-ca-cert - {{- else }} - name: consul-ca-cert - {{- end }} mountPath: /consul/tls/ca readOnly: true {{- end }} + {{- end }} command: - "/bin/sh" - "-ec" @@ -262,10 +258,6 @@ spec: {{- else }} -auth-method-name={{ template "consul.fullname" . }}-k8s-component-auth-method \ {{- end }} - {{- if .Values.global.adminPartitions.enabled }} - -partition={{ .Values.global.adminPartitions.name }} \ - {{- end }} - -api-timeout={{ .Values.global.consulAPITimeout }} \ -log-level={{ default .Values.global.logLevel .Values.apiGateway.logLevel }} \ -log-json={{ .Values.global.logJSON }} resources: diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index 5f00cb65a0..e0d646dc9a 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -189,7 +189,7 @@ load _helpers [ "${actual}" = "true" ] } -@test "apiGateway/Deployment: consul-auto-encrypt-ca-cert volumeMount is added when TLS with auto-encrypt is enabled" { +@test "apiGateway/Deployment: consul-auto-encrypt-ca-cert volumeMount is added when TLS with auto-encrypt is enabled with clients" { cd `chart_dir` local actual=$(helm template \ -s templates/api-gateway-controller-deployment.yaml \ @@ -197,11 +197,26 @@ load _helpers --set 'apiGateway.image=foo' \ --set 'global.tls.enabled=true' \ --set 'global.tls.enableAutoEncrypt=true' \ + --set 'client.enabled=true' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-auto-encrypt-ca-cert") | length > 0' | tee /dev/stderr) [ "${actual}" = "true" ] } +@test "apiGateway/Deployment: consul-ca-cert volumeMount is added when TLS with auto-encrypt is enabled without clients" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert") | length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + @test "apiGateway/Deployment: get-auto-encrypt-client-ca init container is created when TLS with auto-encrypt is enabled" { cd `chart_dir` local actual=$(helm template \ @@ -315,27 +330,23 @@ load _helpers [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[0].name] | any(contains("HOST_IP"))' | tee /dev/stderr) - [ "${actual}" = "true" ] - - local actual=$(echo $object | - yq '[.env[1].name] | any(contains("NAMESPACE"))' | tee /dev/stderr) + yq '[.env[0].name] | any(contains("NAMESPACE"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[2].name] | any(contains("POD_NAME"))' | tee /dev/stderr) + yq '[.env[1].name] | any(contains("POD_NAME"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[3].name] | any(contains("CONSUL_LOGIN_META"))' | tee /dev/stderr) + yq '[.env[2].name] | any(contains("CONSUL_LOGIN_META"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[3].value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) + yq '[.env[2].value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq -r '.command | any(contains("-api-timeout=5s"))' | tee /dev/stderr) + yq -r '[.env[7].value] | any(contains("5s"))' | tee /dev/stderr) [ "${actual}" = "true" ] } @@ -356,31 +367,51 @@ load _helpers [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[0].name] | any(contains("HOST_IP"))' | tee /dev/stderr) + yq '.env[] | select(.name == "NAMESPACE") | [.valueFrom.fieldRef.fieldPath] | any(contains("metadata.namespace"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "POD_NAME") | [.valueFrom.fieldRef.fieldPath] | any(contains("metadata.name"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_LOGIN_META") | [.value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[1].name] | any(contains("NAMESPACE"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_ADDRESSES") | [.value] | any(contains("release-name-consul-server.default.svc"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[2].name] | any(contains("POD_NAME"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_GRPC_PORT") | [.value] | any(contains("8502"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[3].name] | any(contains("CONSUL_LOGIN_META"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_HTTP_PORT") | [.value] | any(contains("8501"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[3].value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_DATACENTER") | [.value] | any(contains("dc1"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '.volumeMounts[1] | any(contains("consul-ca-cert"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_API_TIMEOUT") | [.value] | any(contains("5s"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq -r '.command | any(contains("-api-timeout=5s"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_USE_TLS") | [.value] | any(contains("true"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_CACERT_FILE") | [.value] | any(contains("/consul/tls/ca/tls.crt"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.volumeMounts[] | select(.name == "consul-ca-cert") | [.mountPath] | any(contains("/consul/tls/ca"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.volumeMounts[] | select(.name == "consul-data") | [.mountPath] | any(contains("/consul/login"))' | tee /dev/stderr) [ "${actual}" = "true" ] } @@ -407,35 +438,59 @@ load _helpers [ "${actual}" = "true" ] local actual=$(echo $object | - yq -r '.command | any(contains("-partition=default"))' | tee /dev/stderr) + yq '.env[] | select(.name == "NAMESPACE") | [.valueFrom.fieldRef.fieldPath] | any(contains("metadata.namespace"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "POD_NAME") | [.valueFrom.fieldRef.fieldPath] | any(contains("metadata.name"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_LOGIN_META") | [.value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_ADDRESSES") | [.value] | any(contains("release-name-consul-server.default.svc"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_GRPC_PORT") | [.value] | any(contains("8502"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_HTTP_PORT") | [.value] | any(contains("8501"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_DATACENTER") | [.value] | any(contains("dc1"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[0].name] | any(contains("HOST_IP"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_API_TIMEOUT") | [.value] | any(contains("5s"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[1].name] | any(contains("NAMESPACE"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_PARTITION") | [.value] | any(contains("default"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[2].name] | any(contains("POD_NAME"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_LOGIN_PARTITION") | [.value] | any(contains("default"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[3].name] | any(contains("CONSUL_LOGIN_META"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_USE_TLS") | [.value] | any(contains("true"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[3].value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_CACERT_FILE") | [.value] | any(contains("/consul/tls/ca/tls.crt"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.volumeMounts[1].name] | any(contains("consul-ca-cert"))' | tee /dev/stderr) + yq '.volumeMounts[] | select(.name == "consul-ca-cert") | [.mountPath] | any(contains("/consul/tls/ca"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq -r '.command | any(contains("-api-timeout=5s"))' | tee /dev/stderr) + yq '.volumeMounts[] | select(.name == "consul-data") | [.mountPath] | any(contains("/consul/login"))' | tee /dev/stderr) [ "${actual}" = "true" ] } @@ -512,31 +567,51 @@ load _helpers [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[0].name] | any(contains("HOST_IP"))' | tee /dev/stderr) + yq '.env[] | select(.name == "NAMESPACE") | [.valueFrom.fieldRef.fieldPath] | any(contains("metadata.namespace"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "POD_NAME") | [.valueFrom.fieldRef.fieldPath] | any(contains("metadata.name"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_LOGIN_META") | [.value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_ADDRESSES") | [.value] | any(contains("release-name-consul-server.default.svc"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_GRPC_PORT") | [.value] | any(contains("8502"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_HTTP_PORT") | [.value] | any(contains("8501"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[1].name] | any(contains("NAMESPACE"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_DATACENTER") | [.value] | any(contains("dc1"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[2].name] | any(contains("POD_NAME"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_API_TIMEOUT") | [.value] | any(contains("5s"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[3].name] | any(contains("CONSUL_LOGIN_META"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_USE_TLS") | [.value] | any(contains("true"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.env[3].value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) + yq '.env[] | select(.name == "CONSUL_CACERT_FILE") | [.value] | any(contains("/consul/tls/ca/tls.crt"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq '[.volumeMounts[1].name] | any(contains("consul-auto-encrypt-ca-cert"))' | tee /dev/stderr) + yq '.volumeMounts[] | select(.name == "consul-ca-cert") | [.mountPath] | any(contains("/consul/tls/ca"))' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | - yq -r '.command | any(contains("-api-timeout=5s"))' | tee /dev/stderr) + yq '.volumeMounts[] | select(.name == "consul-data") | [.mountPath] | any(contains("/consul/login"))' | tee /dev/stderr) [ "${actual}" = "true" ] } @@ -1416,3 +1491,37 @@ load _helpers yq '.spec.template.spec.containers[0].env[0].name == "CONSUL_CACERT"' | tee /dev/stderr) [ "${actual}" = "false" ] } + +@test "apiGateway/Deployment: consul-ca-cert volume mount is not set when using externalServers and useSystemRoots" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + +@test "apiGateway/Deployment: consul-ca-cert volume mount is not set on acl-init when using externalServers and useSystemRoots" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[1].env[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} From daebf0b6b8cdec4e9ab1d0060d559f768bc04727 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 30 Nov 2022 18:11:35 -0500 Subject: [PATCH 011/592] Mount autoencrypt certs when using clients --- .../api-gateway-controller-deployment.yaml | 6 +-- .../api-gateway-controller-deployment.bats | 42 ++++++++++++++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 808f93b421..eeecf05133 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -149,7 +149,7 @@ spec: - name: consul-bin mountPath: /consul-bin {{- end }} - {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} + {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} {{- if .Values.global.tls.enabled }} {{- if and .Values.client.enabled .Values.global.tls.enableAutoEncrypt }} - name: consul-auto-encrypt-ca-cert @@ -179,7 +179,7 @@ spec: emptyDir: { } {{- end }} {{- if .Values.global.tls.enabled }} - {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} + {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} - name: consul-ca-cert secret: {{- if .Values.global.tls.caCert.secretName }} @@ -240,7 +240,7 @@ spec: - mountPath: /consul/login name: consul-data readOnly: false - {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} + {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} {{- if .Values.global.tls.enabled }} - name: consul-ca-cert mountPath: /consul/tls/ca diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index e0d646dc9a..8835844297 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -1505,7 +1505,7 @@ load _helpers --set 'externalServers.enabled=true' \ --set 'externalServers.useSystemRoots=true' \ . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].env[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) [ "${actual}" = "" ] } @@ -1522,6 +1522,44 @@ load _helpers --set 'externalServers.enabled=true' \ --set 'externalServers.useSystemRoots=true' \ . | tee /dev/stderr | - yq '.spec.template.spec.initContainers[1].env[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + yq '.spec.template.spec.initContainers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) [ "${actual}" = "" ] } + +@test "apiGateway/Deployment: consul-auto-encrypt-ca-cert volume mount is set when tls.enabled, client.enabled, externalServers, useSystemRoots, and autoencrypt" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'client.enabled=true' \ + --set 'server.enabled=false' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-auto-encrypt-ca-cert") | .mountPath' | tee /dev/stderr) + [ "${actual}" = '"/consul/tls/ca"' ] +} + +@test "apiGateway/Deployment: consul-ca-cert volume mount is set on acl-init container when tls.enabled, client.enabled, externalServers, useSystemRoots, and autoencrypt" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'client.enabled=true' \ + --set 'server.enabled=false' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[2].volumeMounts[] | select(.name == "consul-ca-cert") | .mountPath' | tee /dev/stderr) + [ "${actual}" = '"/consul/tls/ca"' ] +} From 4ad6d2a7df6731754ebb82ce093dc420871887ca Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 30 Nov 2022 18:12:46 -0500 Subject: [PATCH 012/592] Fix changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cc3a1b74f..01a82e871c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ IMPROVEMENTS: * Helm: * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] + BUG FIXES: * Helm: * Use the correct autogenerated cert for the API Gateway Controller when connecting to servers versus clients. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] From ddcddd95f1501df46ecdef3463602910190aa03e Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 30 Nov 2022 20:03:22 -0500 Subject: [PATCH 013/592] Remove clients check for acl-init container --- charts/consul/templates/api-gateway-controller-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index eeecf05133..2c93395a3b 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -240,7 +240,7 @@ spec: - mountPath: /consul/login name: consul-data readOnly: false - {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} + {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} {{- if .Values.global.tls.enabled }} - name: consul-ca-cert mountPath: /consul/tls/ca From fab3bcb55de61db70d039616cb39dcde6cb2bbeb Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Wed, 30 Nov 2022 21:01:43 -0500 Subject: [PATCH 014/592] only create the cert volume when not using system roots --- .../api-gateway-controller-deployment.yaml | 2 +- .../api-gateway-controller-deployment.bats | 21 +------------------ 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 2c93395a3b..c548b63e4d 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -179,7 +179,7 @@ spec: emptyDir: { } {{- end }} {{- if .Values.global.tls.enabled }} - {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} + {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} - name: consul-ca-cert secret: {{- if .Values.global.tls.caCert.secretName }} diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index 8835844297..3376dc63c7 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -1543,23 +1543,4 @@ load _helpers . | tee /dev/stderr | yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-auto-encrypt-ca-cert") | .mountPath' | tee /dev/stderr) [ "${actual}" = '"/consul/tls/ca"' ] -} - -@test "apiGateway/Deployment: consul-ca-cert volume mount is set on acl-init container when tls.enabled, client.enabled, externalServers, useSystemRoots, and autoencrypt" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/api-gateway-controller-deployment.yaml \ - --set 'apiGateway.enabled=true' \ - --set 'apiGateway.image=bar' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'client.enabled=true' \ - --set 'server.enabled=false' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.useSystemRoots=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.initContainers[2].volumeMounts[] | select(.name == "consul-ca-cert") | .mountPath' | tee /dev/stderr) - [ "${actual}" = '"/consul/tls/ca"' ] -} +} \ No newline at end of file From 7a79bfde848252395ea5411b7241693856d00680 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Wed, 30 Nov 2022 21:02:28 -0500 Subject: [PATCH 015/592] add newline --- charts/consul/test/unit/api-gateway-controller-deployment.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index 3376dc63c7..83d296b15d 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -1543,4 +1543,4 @@ load _helpers . | tee /dev/stderr | yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-auto-encrypt-ca-cert") | .mountPath' | tee /dev/stderr) [ "${actual}" = '"/consul/tls/ca"' ] -} \ No newline at end of file +} From 1788b54710dcb6388d190d7ebddaee376101ebb7 Mon Sep 17 00:00:00 2001 From: Ranjandas Date: Tue, 6 Dec 2022 12:24:26 +1100 Subject: [PATCH 016/592] Remove controller from preset values file (#1755) * Remove controller from preset values file in CLI --- cli/cmd/install/install_test.go | 4 ++-- cli/cmd/status/status_test.go | 4 ++-- cli/cmd/upgrade/upgrade_test.go | 4 ++-- cli/preset/demo.go | 2 -- cli/preset/quickstart.go | 2 -- cli/preset/secure.go | 2 -- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/cli/cmd/install/install_test.go b/cli/cmd/install/install_test.go index 04c250b1e4..07c04defef 100644 --- a/cli/cmd/install/install_test.go +++ b/cli/cmd/install/install_test.go @@ -558,7 +558,7 @@ func TestInstall(t *testing.T) { }, messages: []string{ "\n==> Checking if Consul can be installed\n ✓ No existing Consul installations found.\n ✓ No existing Consul persistent volume claims found\n ✓ No existing Consul secrets found.\n", - "\n==> Consul Installation Summary\n Name: consul\n Namespace: consul\n \n Helm value overrides\n --------------------\n connectInject:\n enabled: true\n metrics:\n defaultEnableMerging: true\n defaultEnabled: true\n enableGatewayMetrics: true\n controller:\n enabled: true\n global:\n metrics:\n enableAgentMetrics: true\n enabled: true\n name: consul\n prometheus:\n enabled: true\n server:\n replicas: 1\n ui:\n enabled: true\n service:\n enabled: true\n \n", + "\n==> Consul Installation Summary\n Name: consul\n Namespace: consul\n \n Helm value overrides\n --------------------\n connectInject:\n enabled: true\n metrics:\n defaultEnableMerging: true\n defaultEnabled: true\n enableGatewayMetrics: true\n global:\n metrics:\n enableAgentMetrics: true\n enabled: true\n name: consul\n prometheus:\n enabled: true\n server:\n replicas: 1\n ui:\n enabled: true\n service:\n enabled: true\n \n", "\n==> Installing Consul\n ✓ Downloaded charts.\n ✓ Consul installed in namespace \"consul\".\n", }, helmActionsRunner: &helm.MockActionRunner{}, @@ -574,7 +574,7 @@ func TestInstall(t *testing.T) { }, messages: []string{ "\n==> Checking if Consul can be installed\n ✓ No existing Consul installations found.\n ✓ No existing Consul persistent volume claims found\n ✓ No existing Consul secrets found.\n", - "\n==> Consul Installation Summary\n Name: consul\n Namespace: consul\n \n Helm value overrides\n --------------------\n connectInject:\n enabled: true\n controller:\n enabled: true\n global:\n acls:\n manageSystemACLs: true\n gossipEncryption:\n autoGenerate: true\n name: consul\n tls:\n enableAutoEncrypt: true\n enabled: true\n server:\n replicas: 1\n \n", + "\n==> Consul Installation Summary\n Name: consul\n Namespace: consul\n \n Helm value overrides\n --------------------\n connectInject:\n enabled: true\n global:\n acls:\n manageSystemACLs: true\n gossipEncryption:\n autoGenerate: true\n name: consul\n tls:\n enableAutoEncrypt: true\n enabled: true\n server:\n replicas: 1\n \n", "\n==> Installing Consul\n ✓ Downloaded charts.\n ✓ Consul installed in namespace \"consul\".\n", }, helmActionsRunner: &helm.MockActionRunner{}, diff --git a/cli/cmd/status/status_test.go b/cli/cmd/status/status_test.go index 8666fd8493..e227d4e3cf 100644 --- a/cli/cmd/status/status_test.go +++ b/cli/cmd/status/status_test.go @@ -77,7 +77,7 @@ func TestStatus(t *testing.T) { "status with servers returns success": { input: []string{}, messages: []string{ - fmt.Sprintf("\n==> Consul Status Summary\nName\tNamespace\tStatus\tChart Version\tAppVersion\tRevision\tLast Updated \n \t \tREADY \t1.0.0 \t \t0 \t%s\t\n", notImeStr), + fmt.Sprintf("\n==> Consul Status Summary\nName\tNamespace\tStatus\tChart Version\tAppVersion\tRevision\tLast Updated \n \t \tREADY \t1.0.0 \t \t0 \t%s\t\n", notImeStr), "\n==> Config:\n {}\n \nConsul servers healthy 3/3\n", }, preProcessingFunc: func(k8s kubernetes.Interface) error { @@ -102,7 +102,7 @@ func TestStatus(t *testing.T) { "status with pre-install and pre-upgrade hooks returns success and outputs hook status": { input: []string{}, messages: []string{ - fmt.Sprintf("\n==> Consul Status Summary\nName\tNamespace\tStatus\tChart Version\tAppVersion\tRevision\tLast Updated \n \t \tREADY \t1.0.0 \t \t0 \t%s\t\n", notImeStr), + fmt.Sprintf("\n==> Consul Status Summary\nName\tNamespace\tStatus\tChart Version\tAppVersion\tRevision\tLast Updated \n \t \tREADY \t1.0.0 \t \t0 \t%s\t\n", notImeStr), "\n==> Config:\n {}\n \n", "\n==> Status Of Helm Hooks:\npre-install-hook pre-install: Succeeded\npre-upgrade-hook pre-upgrade: Succeeded\nConsul servers healthy 3/3\n", }, diff --git a/cli/cmd/upgrade/upgrade_test.go b/cli/cmd/upgrade/upgrade_test.go index 809cac1034..d21b17febf 100644 --- a/cli/cmd/upgrade/upgrade_test.go +++ b/cli/cmd/upgrade/upgrade_test.go @@ -452,7 +452,7 @@ func TestUpgrade(t *testing.T) { messages: []string{ "\n==> Checking if Consul can be upgraded\n ✓ Existing Consul installation found to be upgraded.\n Name: consul\n Namespace: consul\n", "\n==> Checking if Consul demo application can be upgraded\n No existing Consul demo application installation found.\n", - "\n==> Consul Upgrade Summary\n ✓ Downloaded charts.\n \n Difference between user overrides for current and upgraded charts\n -----------------------------------------------------------------\n + connectInject:\n + enabled: true\n + metrics:\n + defaultEnableMerging: true\n + defaultEnabled: true\n + enableGatewayMetrics: true\n + controller:\n + enabled: true\n + global:\n + metrics:\n + enableAgentMetrics: true\n + enabled: true\n + name: consul\n + prometheus:\n + enabled: true\n + server:\n + replicas: 1\n + ui:\n + enabled: true\n + service:\n + enabled: true\n \n", + "\n==> Consul Upgrade Summary\n ✓ Downloaded charts.\n \n Difference between user overrides for current and upgraded charts\n -----------------------------------------------------------------\n + connectInject:\n + enabled: true\n + metrics:\n + defaultEnableMerging: true\n + defaultEnabled: true\n + enableGatewayMetrics: true\n + global:\n + metrics:\n + enableAgentMetrics: true\n + enabled: true\n + name: consul\n + prometheus:\n + enabled: true\n + server:\n + replicas: 1\n + ui:\n + enabled: true\n + service:\n + enabled: true\n \n", "\n==> Upgrading Consul\n ✓ Consul upgraded in namespace \"consul\".\n", }, helmActionsRunner: &helm.MockActionRunner{ @@ -477,7 +477,7 @@ func TestUpgrade(t *testing.T) { messages: []string{ "\n==> Checking if Consul can be upgraded\n ✓ Existing Consul installation found to be upgraded.\n Name: consul\n Namespace: consul\n", "\n==> Checking if Consul demo application can be upgraded\n No existing Consul demo application installation found.\n", - "\n==> Consul Upgrade Summary\n ✓ Downloaded charts.\n \n Difference between user overrides for current and upgraded charts\n -----------------------------------------------------------------\n + connectInject:\n + enabled: true\n + controller:\n + enabled: true\n + global:\n + acls:\n + manageSystemACLs: true\n + gossipEncryption:\n + autoGenerate: true\n + name: consul\n + tls:\n + enableAutoEncrypt: true\n + enabled: true\n + server:\n + replicas: 1\n \n", + "\n==> Consul Upgrade Summary\n ✓ Downloaded charts.\n \n Difference between user overrides for current and upgraded charts\n -----------------------------------------------------------------\n + connectInject:\n + enabled: true\n + global:\n + acls:\n + manageSystemACLs: true\n + gossipEncryption:\n + autoGenerate: true\n + name: consul\n + tls:\n + enableAutoEncrypt: true\n + enabled: true\n + server:\n + replicas: 1\n \n", "\n==> Upgrading Consul\n ✓ Consul upgraded in namespace \"consul\".\n", }, helmActionsRunner: &helm.MockActionRunner{ diff --git a/cli/preset/demo.go b/cli/preset/demo.go index bf6c0bb122..ee1b100114 100644 --- a/cli/preset/demo.go +++ b/cli/preset/demo.go @@ -29,8 +29,6 @@ connectInject: enableGatewayMetrics: true server: replicas: 1 -controller: - enabled: true ui: enabled: true service: diff --git a/cli/preset/quickstart.go b/cli/preset/quickstart.go index 52b3f000b1..823a60e312 100644 --- a/cli/preset/quickstart.go +++ b/cli/preset/quickstart.go @@ -29,8 +29,6 @@ connectInject: enableGatewayMetrics: true server: replicas: 1 -controller: - enabled: true ui: enabled: true service: diff --git a/cli/preset/secure.go b/cli/preset/secure.go index ded436804c..6fccc956a6 100644 --- a/cli/preset/secure.go +++ b/cli/preset/secure.go @@ -29,8 +29,6 @@ server: replicas: 1 connectInject: enabled: true -controller: - enabled: true ` return config.ConvertToMap(values), nil From c12be28f132bf1c4bbe6e89a7d0b508617ef446e Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 6 Dec 2022 16:49:15 -0500 Subject: [PATCH 017/592] Set consul api = v1.18.0, sdk = v0.13.0 and imageConsul = 1.14.2 (#1769) * Update to consuls latest versions * go mod tidy --- charts/consul/Chart.yaml | 4 ++-- charts/consul/values.yaml | 2 +- control-plane/cni/go.mod | 2 +- control-plane/go.mod | 4 ++-- control-plane/go.sum | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index a8e1505280..253291defc 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: consul version: 1.1.0-dev -appVersion: 1.14.0 +appVersion: 1.14.2 kubeVersion: ">=1.21.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -13,7 +13,7 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: hashicorp/consul:1.14.0 + image: hashicorp/consul:1.14.2 - name: consul-k8s-control-plane image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.1.0-dev - name: consul-dataplane diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 9122d62d53..d26316100e 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -63,7 +63,7 @@ global: # image: "hashicorp/consul-enterprise:1.10.0-ent" # ``` # @default: hashicorp/consul: - image: "hashicorp/consul:1.14.0" + image: "hashicorp/consul:1.14.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. diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index 5d3423eef7..2b43b784fd 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/consul-k8s/control-plane/cni require ( github.com/containernetworking/cni v1.1.1 github.com/containernetworking/plugins v1.1.1 - github.com/hashicorp/consul/sdk v0.12.0 + github.com/hashicorp/consul/sdk v0.13.0 github.com/hashicorp/go-hclog v0.16.1 github.com/stretchr/testify v1.7.1 k8s.io/api v0.22.2 diff --git a/control-plane/go.mod b/control-plane/go.mod index 7e1128121a..a20937c694 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,8 +10,8 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.16.0 - github.com/hashicorp/consul/sdk v0.12.0 + github.com/hashicorp/consul/api v1.18.0 + github.com/hashicorp/consul/sdk v0.13.0 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 github.com/hashicorp/go-multierror v1.1.1 diff --git a/control-plane/go.sum b/control-plane/go.sum index 0b746303bb..b5b77aa4b8 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -346,8 +346,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.16.0 h1:Vf/QVFIwz+PdHR4T4lSwYzLULtbHVq0BheXCUAKP50M= -github.com/hashicorp/consul/api v1.16.0/go.mod h1:GJI1Sif0Wc/iYyqg7EXHJV37IPush6eJTewvYdF9uO8= +github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g= +github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= From 62204bf56725d0c84a172d97803e784e793fed65 Mon Sep 17 00:00:00 2001 From: David Yu Date: Wed, 7 Dec 2022 08:31:52 -0800 Subject: [PATCH 018/592] CHANGELOG: Add 0.49.x series to release notes and 1.0.2 (#1772) --- CHANGELOG.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a82e871c..dccaa78fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## UNRELEASED +## 1.0.2 (December 1, 2022) + IMPROVEMENTS: * Helm: * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] @@ -9,6 +11,18 @@ BUG FIXES: * Use the correct autogenerated cert for the API Gateway Controller when connecting to servers versus clients. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] * Don't mount the CA cert when `externalServers.useSystemRoots` is `true`. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] +## 0.49.2 (December 1, 2022) + +IMPROVEMENTS: +* Control Plane + * Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.1`. [[GH-1725](https://github.com/hashicorp/consul-k8s/pull/1725)] +* Helm + * Add fields `localConnectTimeoutMs` and `localRequestTimeoutMs` to the `ServiceDefaults` CRD. [[GH-1647](https://github.com/hashicorp/consul-k8s/pull/1647)] + +BUG FIXES: +* Helm: + * Disable PodSecurityPolicies templating for `gossip-encryption-autogenerate` and `partition-init` when `global.enablePodSecurityPolicies` is `false`. [[GH-1693](https://github.com/hashicorp/consul-k8s/pull/1693)] + ## 1.0.1 (November 21, 2022) BUG FIXES: @@ -110,7 +124,6 @@ IMPROVEMENTS: * API Gateway: Allow controller to read MeshServices for use as a route backend. [[GH-1574](https://github.com/hashicorp/consul-k8s/pull/1574)] * API Gateway: Add support for using dynamic server discovery strings when running without agents. [[GH-1732](https://github.com/hashicorp/consul-k8s/pull/1732)] - BUG FIXES: * CLI * Allow optional environment variables for use in the cloud preset to the CLI for cluster bootstrapping. [[GH-1608](https://github.com/hashicorp/consul-k8s/pull/1608)] @@ -121,6 +134,23 @@ BUG FIXES: * Helm: * Disable PodSecurityPolicies in all templates when `global.enablePodSecurityPolicies` is `false`. [[GH-1693](https://github.com/hashicorp/consul-k8s/pull/1693)] +## 0.49.1 (November 14, 2022) +BREAKING CHANGES: +* Peering: + * Rename `PeerName` to `Peer` in ExportedServices CRD. [[GH-1596](https://github.com/hashicorp/consul-k8s/pull/1596)] + +FEATURES: +* Ingress Gateway + * Add support for MaxConnections, MaxConcurrentRequests, and MaxPendingRequests to Ingress Gateway CRD. [[GH-1691](https://github.com/hashicorp/consul-k8s/pull/1691)] + +IMPROVEMENTS: +* Helm: + * Add `tolerations` and `nodeSelector` to Server ACL init jobs and `nodeSelector` to Webhook cert manager. [[GH-1581](https://github.com/hashicorp/consul-k8s/pull/1581)] + * API Gateway: Allow controller to read MeshServices for use as a route backend. [[GH-1574](https://github.com/hashicorp/consul-k8s/pull/1574)] + * API Gateway: Add `tolerations` to `apiGateway.managedGatewayClass` and `apiGateway.controller` [[GH-1650](https://github.com/hashicorp/consul-k8s/pull/1650)] + * API Gateway: Create PodSecurityPolicy for controller when `global.enablePodSecurityPolicies=true`. [[GH-1656](https://github.com/hashicorp/consul-k8s/pull/1656)] + * API Gateway: Create PodSecurityPolicy and allow controller to bind it to ServiceAccounts that it creates for Gateway Deployments when `global.enablePodSecurityPolicies=true`. [[GH-1672](https://github.com/hashicorp/consul-k8s/pull/1672)] + ## 0.49.0 (September 29, 2022) FEATURES: From 4f4e81dfeb14db54826c36a1c507a7cf4a58917a Mon Sep 17 00:00:00 2001 From: Kyle Schochenmaier Date: Wed, 7 Dec 2022 13:11:34 -0600 Subject: [PATCH 019/592] fix failing cli test (#1774) --- cli/cmd/status/status_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/cmd/status/status_test.go b/cli/cmd/status/status_test.go index e227d4e3cf..8666fd8493 100644 --- a/cli/cmd/status/status_test.go +++ b/cli/cmd/status/status_test.go @@ -77,7 +77,7 @@ func TestStatus(t *testing.T) { "status with servers returns success": { input: []string{}, messages: []string{ - fmt.Sprintf("\n==> Consul Status Summary\nName\tNamespace\tStatus\tChart Version\tAppVersion\tRevision\tLast Updated \n \t \tREADY \t1.0.0 \t \t0 \t%s\t\n", notImeStr), + fmt.Sprintf("\n==> Consul Status Summary\nName\tNamespace\tStatus\tChart Version\tAppVersion\tRevision\tLast Updated \n \t \tREADY \t1.0.0 \t \t0 \t%s\t\n", notImeStr), "\n==> Config:\n {}\n \nConsul servers healthy 3/3\n", }, preProcessingFunc: func(k8s kubernetes.Interface) error { @@ -102,7 +102,7 @@ func TestStatus(t *testing.T) { "status with pre-install and pre-upgrade hooks returns success and outputs hook status": { input: []string{}, messages: []string{ - fmt.Sprintf("\n==> Consul Status Summary\nName\tNamespace\tStatus\tChart Version\tAppVersion\tRevision\tLast Updated \n \t \tREADY \t1.0.0 \t \t0 \t%s\t\n", notImeStr), + fmt.Sprintf("\n==> Consul Status Summary\nName\tNamespace\tStatus\tChart Version\tAppVersion\tRevision\tLast Updated \n \t \tREADY \t1.0.0 \t \t0 \t%s\t\n", notImeStr), "\n==> Config:\n {}\n \n", "\n==> Status Of Helm Hooks:\npre-install-hook pre-install: Succeeded\npre-upgrade-hook pre-upgrade: Succeeded\nConsul servers healthy 3/3\n", }, From 112ad18e12683559413c058d8e52f28d66597b07 Mon Sep 17 00:00:00 2001 From: Chris Bruce Date: Wed, 7 Dec 2022 12:33:43 -0800 Subject: [PATCH 020/592] Add global.extraLabels values.yaml setting (#1771) * Add global.extraLabels values.yaml setting This setting lets you apply a set of labels to all pods created by the consul-k8s helm chart. * Also apply global extra labels to deployments/daemonsets/statefulsets/jobs * Add global extraLabels to sync catalog deployment --- .../api-gateway-controller-deployment.yaml | 6 ++ charts/consul/templates/client-daemonset.yaml | 6 ++ charts/consul/templates/cni-daemonset.yaml | 6 ++ .../templates/connect-inject-deployment.yaml | 6 ++ .../create-federation-secret-job.yaml | 6 ++ .../templates/enterprise-license-job.yaml | 6 ++ .../gossip-encryption-autogenerate-job.yaml | 6 ++ .../ingress-gateways-deployment.yaml | 6 ++ .../templates/mesh-gateway-deployment.yaml | 6 ++ .../consul/templates/partition-init-job.yaml | 6 ++ .../server-acl-init-cleanup-job.yaml | 6 ++ .../consul/templates/server-acl-init-job.yaml | 6 ++ .../consul/templates/server-statefulset.yaml | 6 ++ .../templates/sync-catalog-deployment.yaml | 6 ++ .../terminating-gateways-deployment.yaml | 6 ++ .../templates/tls-init-cleanup-job.yaml | 6 ++ charts/consul/templates/tls-init-job.yaml | 6 ++ .../webhook-cert-manager-deployment.yaml | 6 ++ .../api-gateway-controller-deployment.bats | 47 +++++++++++++++ charts/consul/test/unit/client-daemonset.bats | 30 ++++++++++ charts/consul/test/unit/cni-daemonset.bats | 47 +++++++++++++++ .../test/unit/connect-inject-deployment.bats | 32 ++++++++++ .../unit/create-federation-secret-job.bats | 56 ++++++++++++++++++ .../test/unit/enterprise-license-job.bats | 50 ++++++++++++++++ .../gossip-encryption-autogenerate-job.bats | 44 ++++++++++++++ .../unit/ingress-gateways-deployment.bats | 47 +++++++++++++++ .../test/unit/mesh-gateway-deployment.bats | 47 +++++++++++++++ .../consul/test/unit/partition-init-job.bats | 59 +++++++++++++++++++ .../unit/server-acl-init-cleanup-job.bats | 44 ++++++++++++++ .../consul/test/unit/server-acl-init-job.bats | 44 ++++++++++++++ .../consul/test/unit/server-statefulset.bats | 29 +++++++++ .../test/unit/sync-catalog-deployment.bats | 32 ++++++++++ .../unit/terminating-gateways-deployment.bats | 46 +++++++++++++++ .../test/unit/tls-init-cleanup-job.bats | 44 ++++++++++++++ charts/consul/test/unit/tls-init-job.bats | 44 ++++++++++++++ .../unit/webhook-cert-manager-deployment.bats | 41 +++++++++++++ charts/consul/values.yaml | 13 ++++ 37 files changed, 904 insertions(+) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index c548b63e4d..ec64bc3631 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -15,6 +15,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: api-gateway-controller + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: replicas: {{ .Values.apiGateway.controller.replicas }} selector: @@ -46,6 +49,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: api-gateway-controller + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} spec: serviceAccountName: {{ template "consul.fullname" . }}-api-gateway-controller containers: diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 59252301f9..91af3821fc 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -24,6 +24,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: client + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: {{- if .Values.client.updateStrategy }} updateStrategy: @@ -47,6 +50,9 @@ spec: {{- if .Values.client.extraLabels }} {{- toYaml .Values.client.extraLabels | nindent 8 }} {{- end }} + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: {{- if .Values.global.secretsBackend.vault.enabled }} "vault.hashicorp.com/agent-inject": "true" diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index e9a6807338..ae04d9e657 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -11,6 +11,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: cni + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: {{- if .Values.connectInject.cni.updateStrategy }} updateStrategy: @@ -29,6 +32,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: cni + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: consul.hashicorp.com/connect-inject: "false" spec: diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index e6b4675876..2b52c1b81c 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -23,6 +23,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: connect-injector + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: replicas: {{ .Values.connectInject.replicas }} selector: @@ -41,6 +44,9 @@ spec: {{- if .Values.connectInject.extraLabels }} {{- toYaml .Values.connectInject.extraLabels | nindent 8 }} {{- end }} + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" {{- if .Values.connectInject.annotations }} diff --git a/charts/consul/templates/create-federation-secret-job.yaml b/charts/consul/templates/create-federation-secret-job.yaml index 40b81957d1..4f83a1f82a 100644 --- a/charts/consul/templates/create-federation-secret-job.yaml +++ b/charts/consul/templates/create-federation-secret-job.yaml @@ -15,6 +15,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: create-federation-secret + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} annotations: "helm.sh/hook": post-install,post-upgrade {{- /* Hook weight needs to be 1 so that the service account is provisioned first */}} @@ -29,6 +32,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: create-federation-secret + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" spec: diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index 02921db3b0..0122690104 100644 --- a/charts/consul/templates/enterprise-license-job.yaml +++ b/charts/consul/templates/enterprise-license-job.yaml @@ -15,6 +15,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: license + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} annotations: "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-weight": "100" @@ -31,6 +34,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: license + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" spec: diff --git a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml index e1a6e49823..9d296478a1 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml @@ -14,6 +14,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: gossip-encryption-autogenerate + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "1" @@ -27,6 +30,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: gossip-encryption-autogenerate + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" spec: diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index 139055b818..4f72031855 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -46,6 +46,9 @@ metadata: release: {{ $root.Release.Name }} component: ingress-gateway ingress-gateway-name: {{ template "consul.fullname" $root }}-{{ .name }} + {{- if $root.Values.global.extraLabels }} + {{- toYaml $root.Values.global.extraLabels | nindent 4 }} + {{- end }} spec: replicas: {{ default $defaults.replicas .replicas }} selector: @@ -66,6 +69,9 @@ spec: component: ingress-gateway ingress-gateway-name: {{ template "consul.fullname" $root }}-{{ .name }} consul.hashicorp.com/connect-inject-managed-by: consul-k8s-endpoints-controller + {{- if $root.Values.global.extraLabels }} + {{- toYaml $root.Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" "consul.hashicorp.com/gateway-kind": "ingress-gateway" diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index 0ba66dbdec..2b2bdc8c2a 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -19,6 +19,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: mesh-gateway + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: replicas: {{ .Values.meshGateway.replicas }} selector: @@ -35,6 +38,9 @@ spec: release: {{ .Release.Name }} component: mesh-gateway consul.hashicorp.com/connect-inject-managed-by: consul-k8s-endpoints-controller + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" "consul.hashicorp.com/gateway-kind": "mesh-gateway" diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 082c48447b..db73ef783b 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -15,6 +15,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: partition-init + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} annotations: "helm.sh/hook": pre-install "helm.sh/hook-weight": "2" @@ -28,6 +31,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: partition-init + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" {{- if (and .Values.global.secretsBackend.vault.enabled (or .Values.global.tls.enabled .Values.global.acls.manageSystemACLs)) }} diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 697427ab5f..35b0877ab4 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -23,6 +23,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: server-acl-init-cleanup + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} annotations: "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-weight": "0" @@ -39,6 +42,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: server-acl-init-cleanup + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" spec: diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index 88a16b0472..440ab8bee0 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -29,6 +29,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: server-acl-init + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: template: metadata: @@ -38,6 +41,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: server-acl-init + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" {{- if .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 23894c4a04..8b73306fd7 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -31,6 +31,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: server + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: serviceName: {{ template "consul.fullname" . }}-server podManagementPolicy: Parallel @@ -59,6 +62,9 @@ spec: {{- if .Values.server.extraLabels }} {{- toYaml .Values.server.extraLabels | nindent 8 }} {{- end }} + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: {{- if .Values.global.secretsBackend.vault.enabled }} "vault.hashicorp.com/agent-inject": "true" diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index 26de143065..f2815d9627 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -14,6 +14,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: sync-catalog + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: replicas: 1 selector: @@ -32,6 +35,9 @@ spec: {{- 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" {{- if .Values.syncCatalog.annotations }} diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index 3efa789527..2f2cb9a921 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -48,6 +48,9 @@ metadata: release: {{ $root.Release.Name }} component: terminating-gateway terminating-gateway-name: {{ template "consul.fullname" $root }}-{{ .name }} + {{- if $root.Values.global.extraLabels }} + {{- toYaml $root.Values.global.extraLabels | nindent 4 }} + {{- end }} spec: replicas: {{ default $defaults.replicas .replicas }} selector: @@ -68,6 +71,9 @@ spec: component: terminating-gateway terminating-gateway-name: {{ template "consul.fullname" $root }}-{{ .name }} consul.hashicorp.com/connect-inject-managed-by: consul-k8s-endpoints-controller + {{- if $root.Values.global.extraLabels }} + {{- toYaml $root.Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" "consul.hashicorp.com/gateway-kind": "terminating-gateway" diff --git a/charts/consul/templates/tls-init-cleanup-job.yaml b/charts/consul/templates/tls-init-cleanup-job.yaml index 9a8898cc10..ba29bb84ae 100644 --- a/charts/consul/templates/tls-init-cleanup-job.yaml +++ b/charts/consul/templates/tls-init-cleanup-job.yaml @@ -13,6 +13,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: tls-init-cleanup + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} annotations: "helm.sh/hook": pre-delete "helm.sh/hook-delete-policy": hook-succeeded @@ -27,6 +30,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: tls-init-cleanup + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" spec: diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index 47dd6462b0..d002ae7a75 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -14,6 +14,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: tls-init + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "1" @@ -27,6 +30,9 @@ spec: chart: {{ template "consul.chart" . }} release: {{ .Release.Name }} component: tls-init + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" spec: diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index be22d4da7a..dd93c039d2 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -11,6 +11,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: webhook-cert-manager + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} spec: replicas: 1 selector: @@ -28,6 +31,9 @@ spec: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: webhook-cert-manager + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/webhook-cert-manager-configmap.yaml") . | sha256sum }} diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index 83d296b15d..b71b51aee0 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -1544,3 +1544,50 @@ load _helpers yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-auto-encrypt-ca-cert") | .mountPath' | tee /dev/stderr) [ "${actual}" = '"/consul/tls/ca"' ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "apiGateway/Deployment: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "apiGateway/Deployment: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --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 "apiGateway/Deployment: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --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" ] +} diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index 0f417bf4ab..6e7a030cb1 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -490,6 +490,36 @@ load _helpers [ "${actualBaz}" = "qux" ] } +@test "client/DaemonSet: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'client.enabled=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 "client/DaemonSet: multiple extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'client.enabled=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 diff --git a/charts/consul/test/unit/cni-daemonset.bats b/charts/consul/test/unit/cni-daemonset.bats index 3b5e046a67..675d6b877f 100644 --- a/charts/consul/test/unit/cni-daemonset.bats +++ b/charts/consul/test/unit/cni-daemonset.bats @@ -340,3 +340,50 @@ rollingUpdate: yq -r -c '.metadata.namespace' | tee /dev/stderr) [[ "${actual}" == "foo" ]] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "cni/DaemonSet: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-daemonset.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "cni/DaemonSet: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-daemonset.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=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 "cni/DaemonSet: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/cni-daemonset.yaml \ + --set 'connectInject.cni.enabled=true' \ + --set 'connectInject.enabled=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" ] +} diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 4879049c49..9da24a7568 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -1272,6 +1272,38 @@ load _helpers [ "${actual}" = "bar" ] } +@test "connectInject/Deployment: can set extra global labels" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=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 "connectInject/Deployment: can set multiple extra global labels" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=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 diff --git a/charts/consul/test/unit/create-federation-secret-job.bats b/charts/consul/test/unit/create-federation-secret-job.bats index 41e401f485..e528f28f0e 100644 --- a/charts/consul/test/unit/create-federation-secret-job.bats +++ b/charts/consul/test/unit/create-federation-secret-job.bats @@ -362,3 +362,59 @@ load _helpers yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) [ "${actual}" = "testing" ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "createFederationSecret/Job: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/create-federation-secret-job.yaml \ + --set 'global.federation.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.federation.createFederationSecret=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "createFederationSecret/Job: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/create-federation-secret-job.yaml \ + --set 'global.federation.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.federation.createFederationSecret=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 "createFederationSecret/Job: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/create-federation-secret-job.yaml \ + --set 'global.federation.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.federation.createFederationSecret=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" ] +} diff --git a/charts/consul/test/unit/enterprise-license-job.bats b/charts/consul/test/unit/enterprise-license-job.bats index 5652419bb3..dcc844eb30 100644 --- a/charts/consul/test/unit/enterprise-license-job.bats +++ b/charts/consul/test/unit/enterprise-license-job.bats @@ -212,3 +212,53 @@ load _helpers actual=$(echo $ca_cert_volume | jq -r '.secret.items[0].key' | tee /dev/stderr) [ "${actual}" = "key" ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "enterpriseLicense/Job: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/enterprise-license-job.yaml \ + --set 'global.enterpriseLicense.secretName=foo' \ + --set 'global.enterpriseLicense.secretKey=bar' \ + --set 'global.enterpriseLicense.enableLicenseAutoload=false' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."app.kubernetes.io/managed-by") | del(."app.kubernetes.io/instance") | del(."helm.sh/chart")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "enterpriseLicense/Job: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/enterprise-license-job.yaml \ + --set 'global.enterpriseLicense.secretName=foo' \ + --set 'global.enterpriseLicense.secretKey=bar' \ + --set 'global.enterpriseLicense.enableLicenseAutoload=false' \ + --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 "enterpriseLicense/Job: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/enterprise-license-job.yaml \ + --set 'global.enterpriseLicense.secretName=foo' \ + --set 'global.enterpriseLicense.secretKey=bar' \ + --set 'global.enterpriseLicense.enableLicenseAutoload=false' \ + --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" ] +} diff --git a/charts/consul/test/unit/gossip-encryption-autogenerate-job.bats b/charts/consul/test/unit/gossip-encryption-autogenerate-job.bats index 87cc5a5990..662b523bc0 100644 --- a/charts/consul/test/unit/gossip-encryption-autogenerate-job.bats +++ b/charts/consul/test/unit/gossip-encryption-autogenerate-job.bats @@ -61,3 +61,47 @@ load _helpers yq -r '.spec.template.spec | has("securityContext")' | tee /dev/stderr) [ "${has_security_context}" = "false" ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "gossipEncryptionAutogenerate/Job: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/gossip-encryption-autogenerate-job.yaml \ + --set 'global.gossipEncryption.autoGenerate=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "gossipEncryptionAutogenerate/Job: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/gossip-encryption-autogenerate-job.yaml \ + --set 'global.gossipEncryption.autoGenerate=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 "gossipEncryptionAutogenerate/Job: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/gossip-encryption-autogenerate-job.yaml \ + --set 'global.gossipEncryption.autoGenerate=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" ] +} diff --git a/charts/consul/test/unit/ingress-gateways-deployment.bats b/charts/consul/test/unit/ingress-gateways-deployment.bats index 983a0b9edf..8ed76be13a 100644 --- a/charts/consul/test/unit/ingress-gateways-deployment.bats +++ b/charts/consul/test/unit/ingress-gateways-deployment.bats @@ -1457,3 +1457,50 @@ key2: value2' \ yq '.spec.template.spec.containers[0].args | any(contains("-tls-server-name=server.dc1.consul"))' | tee /dev/stderr) [ "${actual}" = "true" ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "ingressGateways/Deployment: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/ingress-gateways-deployment.yaml \ + --set 'ingressGateways.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."heritage") | del(."ingress-gateway-name") | del(."consul.hashicorp.com/connect-inject-managed-by")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "ingressGateways/Deployment: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/ingress-gateways-deployment.yaml \ + --set 'ingressGateways.enabled=true' \ + --set 'connectInject.enabled=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 "ingressGateways/Deployment: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/ingress-gateways-deployment.yaml \ + --set 'ingressGateways.enabled=true' \ + --set 'connectInject.enabled=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" ] +} diff --git a/charts/consul/test/unit/mesh-gateway-deployment.bats b/charts/consul/test/unit/mesh-gateway-deployment.bats index 6f326f05a3..588b026d40 100755 --- a/charts/consul/test/unit/mesh-gateway-deployment.bats +++ b/charts/consul/test/unit/mesh-gateway-deployment.bats @@ -1597,3 +1597,50 @@ key2: value2' \ yq '.spec.template.spec.containers[0].args | any(contains("-tls-server-name=server.dc1.consul"))' | tee /dev/stderr) [ "${actual}" = "true" ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "meshGateway/Deployment: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."consul.hashicorp.com/connect-inject-managed-by")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "meshGateway/Deployment: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=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 "meshGateway/Deployment: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=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" ] +} diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index 82ffc959fa..a3524090aa 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -879,3 +879,62 @@ reservedNameTest() { yq '.spec.template.spec.containers[0].command | any(contains("-tls-server-name=server.dc1.consul"))' | tee /dev/stderr) [ "${actual}" = "true" ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "partitionInit/Job: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'server.enabled=false' \ + --set 'global.adminPartitions.name=bar' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "partitionInit/Job: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'server.enabled=false' \ + --set 'global.adminPartitions.name=bar' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + --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 "partitionInit/Job: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'server.enabled=false' \ + --set 'global.adminPartitions.name=bar' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + --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" ] +} diff --git a/charts/consul/test/unit/server-acl-init-cleanup-job.bats b/charts/consul/test/unit/server-acl-init-cleanup-job.bats index cb57374116..947cfa9b42 100644 --- a/charts/consul/test/unit/server-acl-init-cleanup-job.bats +++ b/charts/consul/test/unit/server-acl-init-cleanup-job.bats @@ -115,3 +115,47 @@ load _helpers yq -r '.spec.template.spec.nodeSelector[0].key' | tee /dev/stderr) [ "${actual}" = "value" ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "serverACLInitCleanup/Job: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "serverACLInitCleanup/Job: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=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 "serverACLInitCleanup/Job: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=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" ] +} diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 43c9589747..63450aa4c2 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -2166,3 +2166,47 @@ load _helpers yq '[.env[9].value] | any(contains("server.dc1.consul"))' | tee /dev/stderr) [ "${actual}" = "true" ] } + +#-------------------------------------------------------------------- +# extraLabels + +@test "serverACLInit/Job: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "serverACLInit/Job: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=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 "serverACLInit/Job: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=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" ] +} diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 147e9e31b7..2d21cf7c1e 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -590,6 +590,35 @@ load _helpers [ "${actualBaz}" = "qux" ] } +@test "server/StatefulSet: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --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 "server/StatefulSet: multiple extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --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 diff --git a/charts/consul/test/unit/sync-catalog-deployment.bats b/charts/consul/test/unit/sync-catalog-deployment.bats index 2506d627f8..ae1fe1a854 100755 --- a/charts/consul/test/unit/sync-catalog-deployment.bats +++ b/charts/consul/test/unit/sync-catalog-deployment.bats @@ -895,6 +895,38 @@ load _helpers [ "${actual}" = "bar" ] } +@test "syncCatalog/Deployment: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.enabled=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 "syncCatalog/Deployment: multiple extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.enabled=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 diff --git a/charts/consul/test/unit/terminating-gateways-deployment.bats b/charts/consul/test/unit/terminating-gateways-deployment.bats index b7bbc0bf6d..523138a351 100644 --- a/charts/consul/test/unit/terminating-gateways-deployment.bats +++ b/charts/consul/test/unit/terminating-gateways-deployment.bats @@ -1458,3 +1458,49 @@ key2: value2' \ [ "${actual}" = "server.dc1.consul" ] } +#-------------------------------------------------------------------- +# extraLabels + +@test "terminatingGateways/Deployment: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/terminating-gateways-deployment.yaml \ + --set 'terminatingGateways.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."heritage") | del(."terminating-gateway-name") | del(."consul.hashicorp.com/connect-inject-managed-by")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "terminatingGateways/Deployment: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/terminating-gateways-deployment.yaml \ + --set 'terminatingGateways.enabled=true' \ + --set 'connectInject.enabled=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 "terminatingGateways/Deployment: multiple extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/terminating-gateways-deployment.yaml \ + --set 'terminatingGateways.enabled=true' \ + --set 'connectInject.enabled=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" ] +} diff --git a/charts/consul/test/unit/tls-init-cleanup-job.bats b/charts/consul/test/unit/tls-init-cleanup-job.bats index 76da65bfe5..04b4a2df31 100644 --- a/charts/consul/test/unit/tls-init-cleanup-job.bats +++ b/charts/consul/test/unit/tls-init-cleanup-job.bats @@ -75,3 +75,47 @@ load _helpers --set 'global.tls.enableAutoEncrypt=true' \ . } + +#-------------------------------------------------------------------- +# extraLabels + +@test "tlsInit/Job: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-cleanup-job.yaml \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "tlsInit/Job: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-cleanup-job.yaml \ + --set 'global.tls.enabled=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 "tlsInit/Job: multiple extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-cleanup-job.yaml \ + --set 'global.tls.enabled=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" ] +} diff --git a/charts/consul/test/unit/tls-init-job.bats b/charts/consul/test/unit/tls-init-job.bats index 6c148ed074..f9294915a5 100644 --- a/charts/consul/test/unit/tls-init-job.bats +++ b/charts/consul/test/unit/tls-init-job.bats @@ -163,3 +163,47 @@ load _helpers --set 'global.tls.enableAutoEncrypt=true' \ . } + +#-------------------------------------------------------------------- +# extraLabels + +@test "tlsInit/Job: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "tlsInit/Job: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=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 "tlsInit/Job: multiple extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=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" ] +} diff --git a/charts/consul/test/unit/webhook-cert-manager-deployment.bats b/charts/consul/test/unit/webhook-cert-manager-deployment.bats index e6549200cf..7d1a028d20 100644 --- a/charts/consul/test/unit/webhook-cert-manager-deployment.bats +++ b/charts/consul/test/unit/webhook-cert-manager-deployment.bats @@ -83,3 +83,44 @@ load _helpers --set 'global.secretsBackend.vault.consulCARole=test2' \ . } + +#-------------------------------------------------------------------- +# extraLabels + +@test "webhookCertManager/Deployment: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/webhook-cert-manager-deployment.yaml \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."heritage")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "webhookCertManager/Deployment: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/webhook-cert-manager-deployment.yaml \ + --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 "webhookCertManager/Deployment: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/webhook-cert-manager-deployment.yaml \ + --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" ] +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index d26316100e..7bd8c5e549 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -622,6 +622,19 @@ global: # @type: string secretKey: null + # Extra labels to attach to all pods, deployments, daemonsets, statefulsets, and jobs. This should be a YAML map. + # + # Example: + # + # ```yaml + # extraLabels: + # labelKey: label-value + # anotherLabelKey: another-label-value + # ``` + # + # @type: map + extraLabels: {} + # Server, when enabled, configures a server cluster to run. This should # be disabled if you plan on connecting to a Consul cluster external to # the Kube cluster. From 08c014783378f8d243cc9790f8ce42143f1036d9 Mon Sep 17 00:00:00 2001 From: Kyle Schochenmaier Date: Wed, 7 Dec 2022 15:05:36 -0600 Subject: [PATCH 021/592] add usage of acceptance test suite to contributing guide for debugging (#1775) * add usage of acceptance test suite to contributing guide for debugging --- CONTRIBUTING.md | 194 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 229f6068f5..327399b89c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,9 +22,9 @@ 1. [Running the tests](#running-the-tests) 1. [Writing Unit tests](#writing-unit-tests) 1. [Writing Acceptance tests](#writing-acceptance-tests) +1. [Using the Acceptance Test Framework to Debug](#using-acceptance-test-framework-to-debug) 1. [Helm Reference Docs](#helm-reference-docs) - ## Contributing 101 ### Building and running `consul-k8s-control-plane` @@ -940,6 +940,198 @@ Here are some things to consider before adding a test: --- +## Using Acceptance Test Framework to Debug +### Acceptance Tests + +The [consul-k8s](https://github.com/hashicorp/consul-k8s) repository has an extensive list of [acceptance](https://github.com/hashicorp/consul-k8s/tree/main/acceptance/tests) +tests that are used by CI to run per-PR and nightly acceptance tests. +It is built on its own framework that uses Helm and the consul-k8s CLI to deploy consul (and other tools) in various +configurations that provide test coverage for most features that exist and provides coverage for more advanced deployments +than are typically covered in guides. +Importantly, it is **automated**, so you are able to rapidly deploy known working +configurations in known working environments. +It can be very helpful for bootstrapping complex environments such as when using Vault as a CA for Consul or for federating test clusters. + +The tests are organized like this : +```shell +demo $ tree -L 1 -d acceptance/tests +acceptance/tests +├── basic +├── cli +├── config-entries +├── connect +├── consul-dns +├── example +├── fixtures +├── ingress-gateway +├── metrics +├── partitions +├── peering +├── snapshot-agent +├── sync +├── terminating-gateway +├── vault +└── wan-federation +``` + +### Basic Running of Tests +Any given test can be run either through GoLand or another IDE, or via command line using `go test -run`. + +To run all of the connect tests from command line: +```shell +$ cd acceptance/test +$ go test ./connect/... -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-enterprise -enable-multi-cluster -debug-directory=/tmp/debug -consul-k8s-image=kyleschochenmaier/consul-k8s-acls +``` + +When running from command line a few things are important: +* Some tests use Enterprise features, in which case you need: + * Set environment variables `CONSUL_ENT_LICENSE` and possibly `VAULT_LICENSE`. + * Use `-enable-enterprise` on command line when running the test. +* Multi-cluster tests require `-enable-multi-cluster` + `-kubecontext=` + `-secondary-kubecontext=` +* Using `.//...` is required as part of the command-line to pick up necessary environmental config. + +### Using the framework to debug in an environment +=> NOTE: It is helpful to tune the docker desktop resource settings so that docker has at least 4GB memory, plenty of cpu cores and 2GB of swap. + +* If using Kind, `-use-kind` should be added, and be sure you cluster is up and running: +```shell +$ kind create cluster --name=dc1 && kind create cluster --name=dc2 +``` +* Pick a test which replicates the environment you are wanting to work with. + Ex: pick a test from `partitions/` or `vault/` or `connect/`. +* If you need the environment to persist, add a `time.Sleep(1*time.Hour)` to the end of the test in the test file. +* Use the following flags if you need to use or test out a specific consul/k8s image: + `-consul-k8s-image=` && `-consul-image=` +* You can set custom helm flags by modifying the test file directly in the respective directory. + +Finally, run the test like shown above: +```shell +$ cd acceptance/tests +$ go test -run Vault_WANFederationViaGateways ./vault/... -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-multi-cluster -debug-directory=/tmp/debug +``` +You can interact with the running kubernetes clusters now using `kubectl [COMMAND] --context=` + +* `kind delete clusters --all` is helpful for cleanup! + +### Example Debugging session using the acceptance test framework to bootstrap and debug a Vault backed federated Consul installation: +This test utilizes the `consul-k8s` acceptance test framework, with a custom consul-k8s branch which: +* Modifies the acceptance test to use custom consul+consul-k8s images and sleeps at the end of the test to allow analysis. +* Modifies the helm chart to pass in `connect_ca.intermediate_cert_ttl` and `connect_ca.leaf_cert_ttl` in the `server-configmap` + +1. First clone the consul-k8s repo and then check out the branch locally: `git checkout origin/consul-vault-provider-wanfed-acceptance`. +2. Start the kind clusters: `kind create cluster --name=dc1 && kind create cluster --name=dc2` +3. run the `TestVault_WANFederationViaGateways` acceptance test in `acceptance/tests/vault/vault_wan_fed_test.go` - I use goland, but this command should get you most of the way: +```shell +$ cd acceptance/tests +$ go test -run Vault_WANFederationViaGateways ./vault/... -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-multi-cluster -debug-directory=/tmp/debug +``` +NOTE: This specific acceptance test is considered FLAKY with Kind, if things don't come up it's best to run against GKE/AKS/etc, in which case you just modify the `kubecontext` command parameters to point to your clusters. It is worth noting that you will need to setup any necessary networking for non-Kind clusters manually. + +NOTE: This test requires a VAULT_LICENSE set as an environment variable in the shell where you run `go test` + +4. Wait 10-20 minutes to allow the first intermediate ca renewal, this test is particularly resource intensive so it can take time for everything to come online on a laptop, use `kubectl get pods` to validate that `static-server` and `static-client` have been deployed and are online. + +You can validate the ICA rotation by doing: +```shell +# Fetch the vault root token: +$ kubectl get secrets -root-token -o json //----> b64 decode the `data.token` field. +$ kubectl exec -it -- sh +$ export VAULT_TOKEN= +$ export VAULT_ADDR=https://-vault:8200 + +# Fetch the consul bootstrap token +$ vault kv get consul/secret/bootstrap + +# Examine the vault issuers, there should be 2 by now if ICA renewal has occured: +# NOTE: for a federated setup the issuers url for dc2 is `vault list dc2/connect_inter/issuers`! +$ vault list dc1/connect_inter/issuers + +Keys +---- +29bdffbd-87ec-cfe0-fd05-b78f99eba243 +344eea3c-f085-943a-c3ff-66721ef408f4 + +# Now login to the consul-server +$ kubectl exec -it -- sh +$ export CONSUL_HTTP_TOKEN= +$ export CONSUL_HTTP_ADDR=https://localhost:8501 +$ export CONSUL_HTTP_SSL_VERIFY=false + +# Read the `connect/ca/roots` endpoint: +# It should change + rotate with the expiration of the ICA (defined by `intermediate_cert_ttl` which is `15m` in the branch for this gist. + +$ curl -k --header "X-Consul-Token: 1428da53-5e88-db1a-6ad5-e50212b011da" https://127.0.0.1:8501/v1/agent/connect/ca/roots | jq + . + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 3113 100 3113 0 0 6222 0 --:--:-- --:--:-- --:--:-- 7705 +{ + "ActiveRootID": "36:be:19:0e:56:d1:c2:1a:d8:54:22:97:88:3c:91:17:1d:d2:d3:e0", + "TrustDomain": "34a76791-b9b2-b93e-b0e4-1989ed11a28e.consul", + "Roots": [ + { + "ID": "36:be:19:0e:56:d1:c2:1a:d8:54:22:97:88:3c:91:17:1d:d2:d3:e0", + "Name": "Vault CA Primary Cert", + "SerialNumber": 15998414315735550000, + "SigningKeyID": "fe:b9:d6:0b:c6:ce:2c:25:4f:d8:59:cb:11:ea:a5:42:5f:8e:41:4b", + "ExternalTrustDomain": "34a76791-b9b2-b93e-b0e4-1989ed11a28e", + "NotBefore": "2022-11-16T20:16:15Z", + "NotAfter": "2032-11-13T20:16:45Z", + "RootCert": "-----BEGIN CERTIFICATE-----\nMIICLDCCAdKgAwIBAgIUKQ9BPHF9mtC7yFPC3gXJDpLxCHIwCgYIKoZIzj0EAwIw\nLzEtMCsGA1UEAxMkcHJpLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3Vs\nMB4XDTIyMTExNjIwMTYxNVoXDTMyMTExMzIwMTY0NVowLzEtMCsGA1UEAxMkcHJp\nLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3VsMFkwEwYHKoZIzj0CAQYI\nKoZIzj0DAQcDQgAETnpGixC1kW8ep2JcGjRR2jbdESvjlEm9nSIWVAcilemUGFwi\nJ0YW0XUmJeEzRyfwLXnOw6voPzXRf1zXKjdTD6OByzCByDAOBgNVHQ8BAf8EBAMC\nAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUtb6EjDxyI+myIjDc+7KbiN8u\n8XowHwYDVR0jBBgwFoAUtb6EjDxyI+myIjDc+7KbiN8u8XowZQYDVR0RBF4wXIIk\ncHJpLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3VshjRzcGlmZmU6Ly8z\nNGE3Njc5MS1iOWIyLWI5M2UtYjBlNC0xOTg5ZWQxMWEyOGUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCIHBezFSQAK5Nolf0rs3ErvlDcA8Z9esldh6gHupuGsNkAiEA\n9qL+P9PJAW4CrbTL0iF2yZUyJC2nwSSa2K0nYG8bXWQ=\n-----END CERTIFICATE-----\n", + "IntermediateCerts": [ + "-----BEGIN CERTIFICATE-----\nMIICLzCCAdSgAwIBAgIUbILCP3ODM4ScNBOm0jw59Fxju0swCgYIKoZIzj0EAwIw\nLzEtMCsGA1UEAxMkcHJpLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3Vs\nMB4XDTIyMTExNjIwMzIxNloXDTIyMTExNjIwNDc0NlowMDEuMCwGA1UEAxMlcHJp\nLTE4MThxNWlnLnZhdWx0LmNhLjM0YTc2NzkxLmNvbnN1bDBZMBMGByqGSM49AgEG\nCCqGSM49AwEHA0IABI30ikgrwTjbPaGgfNYkushvrEUUpxLzxMMEBlE82ilog1RW\nqwuEU29Qsa+N4SrfOf37xNv/Ey8SXPs5l2HmXJWjgcwwgckwDgYDVR0PAQH/BAQD\nAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCZpC/BTdaggL2kj6Dfyk3+a\nNqBvMB8GA1UdIwQYMBaAFLW+hIw8ciPpsiIw3Puym4jfLvF6MGYGA1UdEQRfMF2C\nJXByaS0xODE4cTVpZy52YXVsdC5jYS4zNGE3Njc5MS5jb25zdWyGNHNwaWZmZTov\nLzM0YTc2NzkxLWI5YjItYjkzZS1iMGU0LTE5ODllZDExYTI4ZS5jb25zdWwwCgYI\nKoZIzj0EAwIDSQAwRgIhAJ8RHgR5qkyW2q866vGYJy+7BJ4zUXs3OJ76QLmxxU3K\nAiEA70S7wBEm1ZduTAk1ZfZPJEUGxvAXAcgy7EWeO/6MJ5o=\n-----END CERTIFICATE-----\n", + "-----BEGIN CERTIFICATE-----\nMIICLTCCAdKgAwIBAgIUU3qwESuhh4PgW3/tnHDn3qnBMrAwCgYIKoZIzj0EAwIw\nLzEtMCsGA1UEAxMkcHJpLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3Vs\nMB4XDTIyMTExNjIwNDAxNloXDTIyMTExNjIwNTU0NlowLzEtMCsGA1UEAxMkcHJp\nLTFkY2hkbGkudmF1bHQuY2EuMzRhNzY3OTEuY29uc3VsMFkwEwYHKoZIzj0CAQYI\nKoZIzj0DAQcDQgAEpj0BWPkcH82su9XGOo9rN5Zr5+Jyp68LiHy+qlIgH3L+OAir\nYgmXmJfuNwI8S2BB8cu0Gk3w5cTF7O0p/qAghaOByzCByDAOBgNVHQ8BAf8EBAMC\nAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU/rnWC8bOLCVP2FnLEeqlQl+O\nQUswHwYDVR0jBBgwFoAUtb6EjDxyI+myIjDc+7KbiN8u8XowZQYDVR0RBF4wXIIk\ncHJpLTFkY2hkbGkudmF1bHQuY2EuMzRhNzY3OTEuY29uc3VshjRzcGlmZmU6Ly8z\nNGE3Njc5MS1iOWIyLWI5M2UtYjBlNC0xOTg5ZWQxMWEyOGUuY29uc3VsMAoGCCqG\nSM49BAMCA0kAMEYCIQCtq4LiZzkiIKUES9MrzUEflg7wcwQf7Km+8RcOGQbz9QIh\nANWHWt1fe8Hl1wQ55qxsV5lSfOpGAox5WHpgnsBC7cwU\n-----END CERTIFICATE-----\n" + ], + "Active": true, + "PrivateKeyType": "ec", + "PrivateKeyBits": 256, + "CreateIndex": 11, + "ModifyIndex": 797 + } + ] +} + +# You can x509 decode the ICA certs to verify they have been updated and have correct expiry: +$ openssl x509 -in cert.crt -text -noout +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 53:7a:b0:11:2b:a1:87:83:e0:5b:7f:ed:9c:70:e7:de:a9:c1:32:b0 + Signature Algorithm: ecdsa-with-SHA256 + Issuer: CN=pri-1092nu1.vault.ca.34a76791.consul + Validity + Not Before: Nov 16 20:40:16 2022 GMT + Not After : Nov 16 20:55:46 2022 GMT + Subject: CN=pri-1dchdli.vault.ca.34a76791.consul + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:a6:3d:01:58:f9:1c:1f:cd:ac:bb:d5:c6:3a:8f: + 6b:37:96:6b:e7:e2:72:a7:af:0b:88:7c:be:aa:52: + 20:1f:72:fe:38:08:ab:62:09:97:98:97:ee:37:02: + 3c:4b:60:41:f1:cb:b4:1a:4d:f0:e5:c4:c5:ec:ed: + 29:fe:a0:20:85 + ASN1 OID: prime256v1 + NIST CURVE: P-256 + X509v3 extensions: + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + FE:B9:D6:0B:C6:CE:2C:25:4F:D8:59:CB:11:EA:A5:42:5F:8E:41:4B + X509v3 Authority Key Identifier: + keyid:B5:BE:84:8C:3C:72:23:E9:B2:22:30:DC:FB:B2:9B:88:DF:2E:F1:7A + + X509v3 Subject Alternative Name: + DNS:pri-1dchdli.vault.ca.34a76791.consul, URI:spiffe://34a76791-b9b2-b93e-b0e4-1989ed11a28e.consul + +``` + +--- + ## Helm Reference Docs The Helm reference docs (https://www.consul.io/docs/k8s/helm) are automatically From d1129a5eb62b3cea00afbc7fc8ba907bc5980864 Mon Sep 17 00:00:00 2001 From: David Yu Date: Thu, 8 Dec 2022 12:58:34 -0800 Subject: [PATCH 022/592] Update CHANGELOG.md (#1779) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dccaa78fd5..1515b6d12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## UNRELEASED +IMPROVEMENTS: +* Helm: + * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] + ## 1.0.2 (December 1, 2022) IMPROVEMENTS: From d7c253f3da4c7a689d3b230ed60cdb85c9dac71c Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 13 Dec 2022 09:50:49 -0800 Subject: [PATCH 023/592] Create JIRA Github Sync (#1782) * Create JIRA Github Sync --- .github/workflows/jira.yaml | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 .github/workflows/jira.yaml diff --git a/.github/workflows/jira.yaml b/.github/workflows/jira.yaml new file mode 100644 index 0000000000..e86d54eb22 --- /dev/null +++ b/.github/workflows/jira.yaml @@ -0,0 +1,92 @@ +on: + issues: + types: [opened, closed, deleted, reopened] + pull_request_target: + types: [opened, closed, reopened] + issue_comment: + types: [created] + workflow_dispatch: + +name: Jira Sync + +jobs: + sync: + runs-on: ubuntu-latest + name: Jira sync + steps: + - name: Login + uses: atlassian/gajira-login@v3 + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Set ticket type + id: set-ticket-type + run: | + echo "::set-output name=type::GH Issue" + - name: Set ticket labels + if: github.event.action == 'opened' + id: set-ticket-labels + run: | + LABELS="[consul-k8s]" + echo "::set-output name=labels::${LABELS}" + + - name: Check if team member + if: github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'Task' + id: is-team-member + run: | + TEAM=consul-kubernetes + ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" + if [[ -n ${ROLE} ]]; then + echo "Actor ${{ github.actor }} is a ${TEAM} team member" + echo "::set-output name=message::true" + else + echo "Actor ${{ github.actor }} is NOT a ${TEAM} team member" + echo "::set-output name=message::false" + fi + env: + GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }} + + - name: Create ticket + if: ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type != 'Task' ) || ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'Task' && steps.is-team-member.outputs.message == 'false' ) + uses: tomhjp/gh-action-jira-create@v0.2.0 + with: + project: NET + issuetype: "${{ steps.set-ticket-type.outputs.type }}" + summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.type }} #${{ github.event.issue.number || github.event.pull_request.number }}]: ${{ github.event.issue.title || github.event.pull_request.title }}" + description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._" + # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) + extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}", + "customfield_10371": { "value": "GitHub" }, + "components": [{ "name": "${{ github.event.repository.name }}" }], + "labels": ${{ steps.set-ticket-labels.outputs.labels }} }' + + - name: Search + if: github.event.action != 'opened' + id: search + uses: tomhjp/gh-action-jira-search@v0.2.1 + with: + # cf[10089] is Issue Link (use JIRA API to retrieve) + jql: 'issuetype = "${{ steps.set-ticket-type.outputs.type }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' + + - name: Sync comment + if: github.event.action == 'created' && steps.search.outputs.issue + uses: tomhjp/gh-action-jira-comment@v0.1.0 + with: + issue: ${{ steps.search.outputs.issue }} + comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}" + + - name: Close ticket + if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue + uses: atlassian/gajira-transition@v2.0.1 + with: + issue: ${{ steps.search.outputs.issue }} + transition: "Closed" + + - name: Reopen ticket + if: github.event.action == 'reopened' && steps.search.outputs.issue + uses: atlassian/gajira-transition@v2.0.1 + with: + issue: ${{ steps.search.outputs.issue }} + transition: "To Do" From f3cb000e90cdf6b35e2bf707c1c602c00e4c7b4a Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 13 Dec 2022 11:11:26 -0800 Subject: [PATCH 024/592] JIRA sync: downgrade to tomhjp/gh-action-jira-create@v0.1.3 (#1784) * Update jira.yaml reset to 0.1.3 for create jira plugin and remove team member check --- .github/workflows/jira.yaml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/.github/workflows/jira.yaml b/.github/workflows/jira.yaml index e86d54eb22..3a0f2af541 100644 --- a/.github/workflows/jira.yaml +++ b/.github/workflows/jira.yaml @@ -31,26 +31,10 @@ jobs: run: | LABELS="[consul-k8s]" echo "::set-output name=labels::${LABELS}" - - - name: Check if team member - if: github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'Task' - id: is-team-member - run: | - TEAM=consul-kubernetes - ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" - if [[ -n ${ROLE} ]]; then - echo "Actor ${{ github.actor }} is a ${TEAM} team member" - echo "::set-output name=message::true" - else - echo "Actor ${{ github.actor }} is NOT a ${TEAM} team member" - echo "::set-output name=message::false" - fi - env: - GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }} - name: Create ticket - if: ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type != 'Task' ) || ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'Task' && steps.is-team-member.outputs.message == 'false' ) - uses: tomhjp/gh-action-jira-create@v0.2.0 + if: ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type != 'Task' ) || ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'Task' ) + uses: tomhjp/gh-action-jira-create@v0.1.3 with: project: NET issuetype: "${{ steps.set-ticket-type.outputs.type }}" From e4fde5364f2cf193cf717f7c94241c3ed257ab01 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 13 Dec 2022 12:33:42 -0800 Subject: [PATCH 025/592] JIRA: Use env when creating with custom fields (#1786) * Update jira.yaml --- .github/workflows/jira.yaml | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/jira.yaml b/.github/workflows/jira.yaml index 3a0f2af541..fdd07264c2 100644 --- a/.github/workflows/jira.yaml +++ b/.github/workflows/jira.yaml @@ -15,7 +15,7 @@ jobs: name: Jira sync steps: - name: Login - uses: atlassian/gajira-login@v3 + uses: atlassian/gajira-login@v2.0.0 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -24,17 +24,38 @@ jobs: - name: Set ticket type id: set-ticket-type run: | - echo "::set-output name=type::GH Issue" + if [[ "${{ github.event_name == 'pull_request' && github.event.action == 'opened' ]]; then + echo "::set-output name=type::PR" + else + echo "::set-output name=type::GH Issue" + fi + - name: Set ticket labels if: github.event.action == 'opened' id: set-ticket-labels run: | LABELS="[consul-k8s]" echo "::set-output name=labels::${LABELS}" + + - name: Check if team member + if: github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'Task' + id: is-team-member + run: | + TEAM=consul + ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" + if [[ -n ${ROLE} ]]; then + echo "Actor ${{ github.actor }} is a ${TEAM} team member" + echo "::set-output name=message::true" + else + echo "Actor ${{ github.actor }} is NOT a ${TEAM} team member" + echo "::set-output name=message::false" + fi + env: + GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }} - - name: Create ticket - if: ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type != 'Task' ) || ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'Task' ) - uses: tomhjp/gh-action-jira-create@v0.1.3 + - name: Create ticket if an issue is filed, or if PR not by a team member is opened + if: ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'GH Issue' ) || ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'PR' && steps.is-team-member.outputs.message == 'false' ) + uses: tomhjp/gh-action-jira-create@v0.2.0 with: project: NET issuetype: "${{ steps.set-ticket-type.outputs.type }}" @@ -45,6 +66,10 @@ jobs: "customfield_10371": { "value": "GitHub" }, "components": [{ "name": "${{ github.event.repository.name }}" }], "labels": ${{ steps.set-ticket-labels.outputs.labels }} }' + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - name: Search if: github.event.action != 'opened' From 904b5b9589caf923511fe52c83ec4613af5bf397 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 13 Dec 2022 12:40:42 -0800 Subject: [PATCH 026/592] Update jira.yaml (#1788) --- .github/workflows/jira.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jira.yaml b/.github/workflows/jira.yaml index fdd07264c2..02bb34b39c 100644 --- a/.github/workflows/jira.yaml +++ b/.github/workflows/jira.yaml @@ -24,7 +24,7 @@ jobs: - name: Set ticket type id: set-ticket-type run: | - if [[ "${{ github.event_name == 'pull_request' && github.event.action == 'opened' ]]; then + if [[ "${{ github.event_name == 'pull_request' && github.event.action == 'opened' }}" == "true" ]]; then echo "::set-output name=type::PR" else echo "::set-output name=type::GH Issue" From c577be1f4b4c6fbeee1a9b3cd2daafeb0036ae68 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 13 Dec 2022 13:03:37 -0800 Subject: [PATCH 027/592] missing quotes (#1790) --- .github/workflows/jira.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jira.yaml b/.github/workflows/jira.yaml index 02bb34b39c..ac1e12e3a9 100644 --- a/.github/workflows/jira.yaml +++ b/.github/workflows/jira.yaml @@ -15,7 +15,7 @@ jobs: name: Jira sync steps: - name: Login - uses: atlassian/gajira-login@v2.0.0 + uses: atlassian/gajira-login@v3.0.0 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -65,7 +65,7 @@ jobs: extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}", "customfield_10371": { "value": "GitHub" }, "components": [{ "name": "${{ github.event.repository.name }}" }], - "labels": ${{ steps.set-ticket-labels.outputs.labels }} }' + "labels": "${{ steps.set-ticket-labels.outputs.labels }}" }' env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} From 72e5aeb37ef113a82b8f539e1c1a1a77453f4425 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 13 Dec 2022 14:13:00 -0800 Subject: [PATCH 028/592] JIRA: Extra field for labels needs arrays (#1792) * Update jira.yaml --- .github/workflows/jira.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/jira.yaml b/.github/workflows/jira.yaml index ac1e12e3a9..f5c155604c 100644 --- a/.github/workflows/jira.yaml +++ b/.github/workflows/jira.yaml @@ -25,9 +25,9 @@ jobs: id: set-ticket-type run: | if [[ "${{ github.event_name == 'pull_request' && github.event.action == 'opened' }}" == "true" ]]; then - echo "::set-output name=type::PR" + echo "TYPE=PR" >> $GITHUB_OUTPUT else - echo "::set-output name=type::GH Issue" + echo "TYPE=GH Issue" >> $GITHUB_OUTPUT fi - name: Set ticket labels @@ -35,37 +35,37 @@ jobs: id: set-ticket-labels run: | LABELS="[consul-k8s]" - echo "::set-output name=labels::${LABELS}" + echo "LABELS=${LABELS}" >> $GITHUB_OUTPUT - name: Check if team member - if: github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'Task' + if: github.event.action == 'opened' && steps.set-ticket-type.outputs.TYPE == 'PR' id: is-team-member run: | TEAM=consul ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" if [[ -n ${ROLE} ]]; then echo "Actor ${{ github.actor }} is a ${TEAM} team member" - echo "::set-output name=message::true" + echo "MESSAGE=true" >> $GITHUB_OUTPUT else echo "Actor ${{ github.actor }} is NOT a ${TEAM} team member" - echo "::set-output name=message::false" + echo "MESSAGE=false" >> $GITHUB_OUTPUT fi env: GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }} - name: Create ticket if an issue is filed, or if PR not by a team member is opened - if: ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'GH Issue' ) || ( github.event.action == 'opened' && steps.set-ticket-type.outputs.type == 'PR' && steps.is-team-member.outputs.message == 'false' ) + if: ( github.event.action == 'opened' && steps.set-ticket-type.outputs.TYPE == 'GH Issue' ) || ( github.event.action == 'opened' && steps.is-team-member.outputs.MESSAGE == 'false' ) uses: tomhjp/gh-action-jira-create@v0.2.0 with: project: NET - issuetype: "${{ steps.set-ticket-type.outputs.type }}" - summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.type }} #${{ github.event.issue.number || github.event.pull_request.number }}]: ${{ github.event.issue.title || github.event.pull_request.title }}" + issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" + summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.TYPE }} #${{ github.event.issue.number || github.event.pull_request.number }}]: ${{ github.event.issue.title || github.event.pull_request.title }}" description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._" # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}", "customfield_10371": { "value": "GitHub" }, "components": [{ "name": "${{ github.event.repository.name }}" }], - "labels": "${{ steps.set-ticket-labels.outputs.labels }}" }' + "labels": [{ "${{ steps.set-ticket-labels.outputs.LABELS }}" }] }' env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -77,7 +77,7 @@ jobs: uses: tomhjp/gh-action-jira-search@v0.2.1 with: # cf[10089] is Issue Link (use JIRA API to retrieve) - jql: 'issuetype = "${{ steps.set-ticket-type.outputs.type }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' + jql: 'issuetype = "${{ steps.set-ticket-type.outputs.TYPE }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' - name: Sync comment if: github.event.action == 'created' && steps.search.outputs.issue From d673fb8afba03ddf4c4e0e961977ba6a48bca019 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 13 Dec 2022 15:24:25 -0800 Subject: [PATCH 029/592] JIRA: Re-use previous structure from labels (#1794) * Update jira.yaml re-using previous structure from labels --- .github/workflows/jira.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jira.yaml b/.github/workflows/jira.yaml index f5c155604c..4092a1898a 100644 --- a/.github/workflows/jira.yaml +++ b/.github/workflows/jira.yaml @@ -34,7 +34,10 @@ jobs: if: github.event.action == 'opened' id: set-ticket-labels run: | - LABELS="[consul-k8s]" + LABELS="[" + if [[ "${{ contains(github.event.issue.labels.*.name, 'type/bug') }}" == "true" ]]; then LABELS+="\"type/bug\", "; fi + if [[ "${{ contains(github.event.issue.labels.*.name, 'type/enhancement') }}" == "true" ]]; then LABELS+="\"type/enhancement\", "; fi + if [[ ${#LABELS} != 1 ]]; then LABELS=${LABELS::-2}"]"; else LABELS+="]"; fi echo "LABELS=${LABELS}" >> $GITHUB_OUTPUT - name: Check if team member @@ -65,7 +68,7 @@ jobs: extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}", "customfield_10371": { "value": "GitHub" }, "components": [{ "name": "${{ github.event.repository.name }}" }], - "labels": [{ "${{ steps.set-ticket-labels.outputs.LABELS }}" }] }' + "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} From 9069014e5ed947d7c79ad1d63f9ec4f9ee7ccd1e Mon Sep 17 00:00:00 2001 From: David Yu Date: Wed, 14 Dec 2022 08:39:02 -0800 Subject: [PATCH 030/592] JIRA: Add default work stream when syncing JIRA issues (#1796) * Update jira.yaml Split out issue vs pr workflow and fix some issues with work stream field --- .github/workflows/jira-issues.yaml | 83 +++++++++++++++++++ .github/workflows/{jira.yaml => jira-pr.yaml} | 20 ++--- 2 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/jira-issues.yaml rename .github/workflows/{jira.yaml => jira-pr.yaml} (82%) diff --git a/.github/workflows/jira-issues.yaml b/.github/workflows/jira-issues.yaml new file mode 100644 index 0000000000..18705db8e8 --- /dev/null +++ b/.github/workflows/jira-issues.yaml @@ -0,0 +1,83 @@ +on: + issues: + types: [opened, closed, deleted, reopened] + issue_comment: + types: [created] + workflow_dispatch: + +name: Jira Community Issue Sync + +jobs: + sync: + runs-on: ubuntu-latest + name: Jira Community Issue sync + steps: + - name: Login + uses: atlassian/gajira-login@v3.0.0 + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Set ticket type + id: set-ticket-type + run: | + echo "TYPE=GH Issue" >> $GITHUB_OUTPUT + + - name: Set ticket labels + if: github.event.action == 'opened' + id: set-ticket-labels + run: | + LABELS="[" + if [[ "${{ contains(github.event.issue.labels.*.name, 'type/bug') }}" == "true" ]]; then LABELS+="\"type/bug\", "; fi + if [[ "${{ contains(github.event.issue.labels.*.name, 'type/enhancement') }}" == "true" ]]; then LABELS+="\"type/enhancement\", "; fi + if [[ ${#LABELS} != 1 ]]; then LABELS=${LABELS::-2}"]"; else LABELS+="]"; fi + echo "LABELS=${LABELS}" >> $GITHUB_OUTPUT + + - name: Create ticket if an issue is filed, or if PR not by a team member is opened + if: github.event.action == 'opened' + uses: tomhjp/gh-action-jira-create@v0.2.0 + with: + project: NET + issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" + summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.TYPE }} #${{ github.event.issue.number }}]: ${{ github.event.issue.title }}" + description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._" + # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) + extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}", + "customfield_10371": { "value": "GitHub" }, + "customfield_10535": [{ "value": "Service Mesh" }], + "components": [{ "name": "${{ github.event.repository.name }}" }], + "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Search + if: github.event.action != 'opened' + id: search + uses: tomhjp/gh-action-jira-search@v0.2.1 + with: + # cf[10089] is Issue Link (use JIRA API to retrieve) + jql: 'issuetype = "${{ steps.set-ticket-type.outputs.TYPE }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' + + - name: Sync comment + if: github.event.action == 'created' && steps.search.outputs.issue + uses: tomhjp/gh-action-jira-comment@v0.1.0 + with: + issue: ${{ steps.search.outputs.issue }} + comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}" + + - name: Close ticket + if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue + uses: atlassian/gajira-transition@v2.0.1 + with: + issue: ${{ steps.search.outputs.issue }} + transition: "Closed" + + - name: Reopen ticket + if: github.event.action == 'reopened' && steps.search.outputs.issue + uses: atlassian/gajira-transition@v2.0.1 + with: + issue: ${{ steps.search.outputs.issue }} + transition: "To Do" diff --git a/.github/workflows/jira.yaml b/.github/workflows/jira-pr.yaml similarity index 82% rename from .github/workflows/jira.yaml rename to .github/workflows/jira-pr.yaml index 4092a1898a..5d0664fdcb 100644 --- a/.github/workflows/jira.yaml +++ b/.github/workflows/jira-pr.yaml @@ -1,13 +1,9 @@ on: - issues: - types: [opened, closed, deleted, reopened] pull_request_target: types: [opened, closed, reopened] - issue_comment: - types: [created] workflow_dispatch: -name: Jira Sync +name: Jira Community PR Sync jobs: sync: @@ -24,11 +20,7 @@ jobs: - name: Set ticket type id: set-ticket-type run: | - if [[ "${{ github.event_name == 'pull_request' && github.event.action == 'opened' }}" == "true" ]]; then - echo "TYPE=PR" >> $GITHUB_OUTPUT - else - echo "TYPE=GH Issue" >> $GITHUB_OUTPUT - fi + echo "TYPE=Github Issue" >> $GITHUB_OUTPUT - name: Set ticket labels if: github.event.action == 'opened' @@ -41,7 +33,7 @@ jobs: echo "LABELS=${LABELS}" >> $GITHUB_OUTPUT - name: Check if team member - if: github.event.action == 'opened' && steps.set-ticket-type.outputs.TYPE == 'PR' + if: github.event.action == 'opened' id: is-team-member run: | TEAM=consul @@ -57,15 +49,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }} - name: Create ticket if an issue is filed, or if PR not by a team member is opened - if: ( github.event.action == 'opened' && steps.set-ticket-type.outputs.TYPE == 'GH Issue' ) || ( github.event.action == 'opened' && steps.is-team-member.outputs.MESSAGE == 'false' ) + if: ( github.event.action == 'opened' && steps.is-team-member.outputs.MESSAGE == 'false' ) uses: tomhjp/gh-action-jira-create@v0.2.0 with: project: NET issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" - summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.TYPE }} #${{ github.event.issue.number || github.event.pull_request.number }}]: ${{ github.event.issue.title || github.event.pull_request.title }}" + summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.TYPE }} #${{ github.event.pull_request.number }}]: ${{ github.event.pull_request.title }}" description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._" # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) - extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}", + extraFields: '{ "customfield_10089": "${{ github.event.pull_request.html_url }}", "customfield_10371": { "value": "GitHub" }, "components": [{ "name": "${{ github.event.repository.name }}" }], "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' From a3decf08d4fa23983e025a1b4938b7a9455f5903 Mon Sep 17 00:00:00 2001 From: David Yu Date: Thu, 15 Dec 2022 13:21:17 -0800 Subject: [PATCH 031/592] JIRA fix for search issue (#1801) --- .github/workflows/jira-pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index 5d0664fdcb..0a2fc37a74 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -20,7 +20,7 @@ jobs: - name: Set ticket type id: set-ticket-type run: | - echo "TYPE=Github Issue" >> $GITHUB_OUTPUT + echo "TYPE=GH Issue" >> $GITHUB_OUTPUT - name: Set ticket labels if: github.event.action == 'opened' From c32b649dee05c36df29e919b6c1629033f9edfa2 Mon Sep 17 00:00:00 2001 From: David Yu Date: Mon, 19 Dec 2022 11:00:07 -0800 Subject: [PATCH 032/592] Add work stream to PR sync (#1803) --- .github/workflows/jira-pr.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index 0a2fc37a74..5c0ba71cd2 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -58,7 +58,8 @@ jobs: description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._" # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) extraFields: '{ "customfield_10089": "${{ github.event.pull_request.html_url }}", - "customfield_10371": { "value": "GitHub" }, + "customfield_10371": { "value": "GitHub" }, + "customfield_10535": [{ "value": "Service Mesh" }], "components": [{ "name": "${{ github.event.repository.name }}" }], "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' env: From a2ba8916201af55cf2f57c8ed84506dcb1345aa9 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Fri, 16 Dec 2022 14:42:02 -0800 Subject: [PATCH 033/592] ignore partition/namespace on SourceIntention to match top-level config entry --- control-plane/api/v1alpha1/serviceintentions_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index 6488a3615c..a0a240639a 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -239,7 +239,7 @@ func (in *ServiceIntentions) MatchesConsul(candidate api.ConfigEntry) bool { in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceIntentionsConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), - cmpopts.IgnoreFields(capi.SourceIntention{}, "LegacyID", "LegacyMeta", "LegacyCreateTime", "LegacyUpdateTime", "Precedence", "Type"), + cmpopts.IgnoreFields(capi.SourceIntention{}, "Partition", "Namespace", "LegacyID", "LegacyMeta", "LegacyCreateTime", "LegacyUpdateTime", "Precedence", "Type"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), // Consul will sort the sources by precedence when returning the resource From 730024577108cf318964ddfa1ebdb14a950d22b9 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Mon, 19 Dec 2022 11:26:31 -0800 Subject: [PATCH 034/592] Add changelog note --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1515b6d12f..330b1dc2c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ BUG FIXES: * Helm: * Use the correct autogenerated cert for the API Gateway Controller when connecting to servers versus clients. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] * Don't mount the CA cert when `externalServers.useSystemRoots` is `true`. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] +* Control Plane + * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] ## 0.49.2 (December 1, 2022) From 6ec4db232515017b160689f4fcf82e0346747230 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 20 Dec 2022 15:54:41 -0800 Subject: [PATCH 035/592] values.yaml - Fix helm docs for 1.0.x (#1810) * Fix helm docs for 1.0.x --- charts/consul/values.yaml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 7bd8c5e549..f2f909fa81 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -236,7 +236,7 @@ global: {} connectInject: - # Configuration to the Vault Secret that Kubernetes will use on + # Configuration to the Vault Secret that Kubernetes uses on # Kubernetes pod creation, deletion, and update, to get CA certificates # used issued from vault to send webhooks to the ConnectInject. caCert: @@ -245,7 +245,7 @@ global: # @type: string secretName: null - # Configuration to the Vault Secret that Kubernetes will use on + # Configuration to the Vault Secret that Kubernetes uses on # Kubernetes pod creation, deletion, and update, to get TLS certificates # used issued from vault to send webhooks to the ConnectInject. tlsCert: @@ -300,7 +300,7 @@ global: # If true, the Helm chart will enable TLS for Consul # servers and clients and all consul-k8s-control-plane components, as well as generate certificate # authority (optional) and server and client certificates. - # This setting is required for [Cluster Peering](/docs/connect/cluster-peering/k8s). + # This setting is required for [Cluster Peering](https://developer.hashicorp.com/consul/docs/connect/cluster-peering/k8s). enabled: false # If true, turns on the auto-encrypt feature on clients and servers. @@ -832,9 +832,9 @@ server: # This configures the PodDisruptionBudget (https://kubernetes.io/docs/tasks/run-application/configure-pdb/) # for the server cluster. disruptionBudget: - # This will enable/disable registering a PodDisruptionBudget for the server - # cluster. If this is enabled, it will only register the budget so long as - # the server cluster is enabled. + # Enables registering a PodDisruptionBudget for the server + # cluster. If enabled, it only registers the budget so long as + # the server cluster is enabled. To disable, set to `false`. enabled: true # The maximum number of unavailable pods. By default, this will be @@ -1924,7 +1924,7 @@ connectInject: # Configures consul-cni plugin for Consul Service mesh services cni: - # If true, then all traffic redirection setup will use the consul-cni plugin. + # If true, then all traffic redirection setup uses the consul-cni plugin. # Requires connectInject.enabled to also be true. # @type: boolean enabled: false @@ -2318,11 +2318,11 @@ connectInject: memory: "150Mi" cpu: "50m" -# [Mesh Gateways](/docs/connect/gateways/mesh-gateway) enable Consul Connect to work across Consul datacenters. +# [Mesh Gateways](https://developer.hashicorp.com/consul/docs/connect/gateways/mesh-gateway) enable Consul Connect to work across Consul datacenters. meshGateway: - # If [mesh gateways](/docs/connect/gateways/mesh-gateway) are enabled, a Deployment will be created that runs + # If [mesh gateways](https://developer.hashicorp.com/consul/docs/connect/gateways/mesh-gateway) are enabled, a Deployment will be created that runs # gateways and Consul Connect will be configured to use gateways. - # This setting is required for [Cluster Peering](/docs/connect/cluster-peering/k8s). + # This setting is required for [Cluster Peering](https://developer.hashicorp.com/consul/docs/connect/cluster-peering/k8s). # Requirements: consul 1.6.0+ if using `global.acls.manageSystemACLs``. enabled: false @@ -2871,9 +2871,9 @@ apiGateway: # @type: string nodeSelector: null - # This value defines the tolerations that will be assigned to a gateway pod. + # Toleration settings for gateway pods created with the managed gateway class. # This should be a multi-line string matching the - # Tolerations (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. # # @type: string tolerations: null From 21d3b04bfc2a4714173596bd859198e48f253ef8 Mon Sep 17 00:00:00 2001 From: DanStough Date: Wed, 21 Dec 2022 11:53:44 -0500 Subject: [PATCH 036/592] feat: access logs update for proxy-defaults CRD --- CHANGELOG.md | 1 + .../consul/templates/crd-proxydefaults.yaml | 32 ++++++ .../api/v1alpha1/proxydefaults_types.go | 92 ++++++++++++++++ .../api/v1alpha1/proxydefaults_types_test.go | 100 +++++++++++++++++- .../api/v1alpha1/zz_generated.deepcopy.go | 20 ++++ .../consul.hashicorp.com_proxydefaults.yaml | 32 ++++++ control-plane/go.mod | 2 +- control-plane/go.sum | 2 + 8 files changed, 279 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 330b1dc2c7..79cf1931b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ IMPROVEMENTS: * Helm: * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] + * Add the `accessLogs` field to the `ProxyDefaults` CRD. [[GH-1816](https://github.com/hashicorp/consul-k8s/pull/1816)] ## 1.0.2 (December 1, 2022) diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index e66543637f..1a63cfef36 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -57,6 +57,38 @@ spec: spec: description: ProxyDefaultsSpec defines the desired state of ProxyDefaults. properties: + accessLogs: + description: AccessLogs controls all envoy instances' access logging + configuration. + properties: + disableListenerLogs: + description: DisableListenerLogs turns off just listener logs + for connections rejected by Envoy because they don't have a + matching listener filter. + type: boolean + enabled: + description: Enabled turns on all access logging + type: boolean + jsonFormat: + description: 'JSONFormat is a JSON-formatted string of an Envoy + access log format dictionary. See for more info on formatting: + https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-dictionaries + Defining JSONFormat and TextFormat is invalid.' + type: string + path: + description: Path is the output file to write logs for file-type + logging + type: string + textFormat: + description: 'TextFormat is a representation of Envoy access logs + format. See for more info on formatting: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-strings + Defining JSONFormat and TextFormat is invalid.' + type: string + type: + description: Type selects the output for logs one of "file", "stderr". + "stdout" + type: string + type: object config: description: Config is an arbitrary map of configuration values used by Connect proxies. Any values that your proxy allows can be configured diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index 215ec708ff..82fcde3687 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -75,6 +75,8 @@ type ProxyDefaultsSpec struct { MeshGateway MeshGateway `json:"meshGateway,omitempty"` // Expose controls the default expose path configuration for Envoy. Expose Expose `json:"expose,omitempty"` + // AccessLogs controls all envoy instances' access logging configuration. + AccessLogs *AccessLogs `json:"accessLogs,omitempty"` } func (in *ProxyDefaults) GetObjectMeta() metav1.ObjectMeta { @@ -165,6 +167,7 @@ func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry { Expose: in.Spec.Expose.toConsul(), Config: consulConfig, TransparentProxy: in.Spec.TransparentProxy.toConsul(), + AccessLogs: in.Spec.AccessLogs.toConsul(), Meta: meta(datacenter), } } @@ -195,6 +198,9 @@ func (in *ProxyDefaults) Validate(_ common.ConsulMeta) error { if err := in.validateConfig(path.Child("config")); err != nil { allErrs = append(allErrs, err) } + if err := in.Spec.AccessLogs.validate(path.Child("accessLogs")); err != nil { + allErrs = append(allErrs, err) + } allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) if len(allErrs) > 0 { return apierrors.NewInvalid( @@ -237,3 +243,89 @@ func (in *ProxyDefaults) validateConfig(path *field.Path) *field.Error { } return nil } + +// LogSinkType represents the destination for Envoy access logs. +// One of "file", "stderr", or "stdout". +type LogSinkType string + +const ( + DefaultLogSinkType LogSinkType = "" + FileLogSinkType LogSinkType = "file" + StdErrLogSinkType LogSinkType = "stderr" + StdOutLogSinkType LogSinkType = "stdout" +) + +// AccessLogs describes the access logging configuration for all Envoy proxies in the mesh. +type AccessLogs struct { + // Enabled turns on all access logging + Enabled bool `json:"enabled,omitempty"` + + // DisableListenerLogs turns off just listener logs for connections rejected by Envoy because they don't + // have a matching listener filter. + DisableListenerLogs bool `json:"disableListenerLogs,omitempty"` + + // Type selects the output for logs + // one of "file", "stderr". "stdout" + Type LogSinkType `json:"type,omitempty"` + + // Path is the output file to write logs for file-type logging + Path string `json:"path,omitempty"` + + // JSONFormat is a JSON-formatted string of an Envoy access log format dictionary. + // See for more info on formatting: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-dictionaries + // Defining JSONFormat and TextFormat is invalid. + JSONFormat string `json:"jsonFormat,omitempty"` + + // TextFormat is a representation of Envoy access logs format. + // See for more info on formatting: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-strings + // Defining JSONFormat and TextFormat is invalid. + TextFormat string `json:"textFormat,omitempty"` +} + +func (in *AccessLogs) validate(path *field.Path) *field.Error { + if in == nil { + return nil + } + + switch in.Type { + case DefaultLogSinkType, StdErrLogSinkType, StdOutLogSinkType: + // OK + case FileLogSinkType: + if in.Path == "" { + return field.Invalid(path.Child("path"), in.Path, "path must be specified when using file type access logs") + } + default: + return field.Invalid(path.Child("type"), in.Type, "invalid access log type (must be one of \"stdout\", \"stderr\", \"file\"") + } + + if in.JSONFormat != "" && in.TextFormat != "" { + return field.Invalid(path.Child("textFormat"), in.TextFormat, "cannot specify both access log jsonFormat and textFormat") + } + + if in.Type != FileLogSinkType && in.Path != "" { + return field.Invalid(path.Child("path"), in.Path, "path is only valid for file type access logs") + } + + if in.JSONFormat != "" { + msg := json.RawMessage{} + if err := json.Unmarshal([]byte(in.JSONFormat), &msg); err != nil { + return field.Invalid(path.Child("jsonFormat"), in.JSONFormat, "invalid access log json") + } + } + + return nil +} + +func (in *AccessLogs) toConsul() *capi.AccessLogsConfig { + if in == nil { + return nil + } + return &capi.AccessLogsConfig{ + Enabled: in.Enabled, + DisableListenerLogs: in.DisableListenerLogs, + JSONFormat: in.JSONFormat, + Path: in.Path, + TextFormat: in.TextFormat, + Type: capi.LogSinkType(in.Type), + } +} diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 2950a3a36e..794906b05e 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -71,6 +71,13 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + AccessLogs: &AccessLogs{ + Enabled: true, + DisableListenerLogs: true, + Type: FileLogSinkType, + Path: "/var/log/envoy.logs", + TextFormat: "ITS WORKING %START_TIME%", + }, }, }, Theirs: &capi.ProxyConfigEntry{ @@ -103,6 +110,13 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + AccessLogs: &capi.AccessLogsConfig{ + Enabled: true, + DisableListenerLogs: true, + Type: capi.FileLogSinkType, + Path: "/var/log/envoy.logs", + TextFormat: "ITS WORKING %START_TIME%", + }, }, Matches: true, }, @@ -236,6 +250,13 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + AccessLogs: &AccessLogs{ + Enabled: true, + DisableListenerLogs: true, + Type: FileLogSinkType, + Path: "/var/log/envoy.logs", + TextFormat: "ITS WORKING %START_TIME%", + }, }, }, Exp: &capi.ProxyConfigEntry{ @@ -269,6 +290,13 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + AccessLogs: &capi.AccessLogsConfig{ + Enabled: true, + DisableListenerLogs: true, + Type: capi.FileLogSinkType, + Path: "/var/log/envoy.logs", + TextFormat: "ITS WORKING %START_TIME%", + }, Meta: map[string]string{ common.SourceKey: common.SourceValue, common.DatacenterKey: "datacenter", @@ -366,6 +394,72 @@ func TestProxyDefaults_Validate(t *testing.T) { }, "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode", }, + "accessLogs.type": { + &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + AccessLogs: &AccessLogs{ + Type: "foo", + }, + }, + }, + "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.type: Invalid value: \"foo\": invalid access log type (must be one of \"stdout\", \"stderr\", \"file\"", + }, + "accessLogs.path missing": { + &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + AccessLogs: &AccessLogs{ + Type: "file", + }, + }, + }, + "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.path: Invalid value: \"\": path must be specified when using file type access logs", + }, + "accessLogs.path for wrong type": { + &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + AccessLogs: &AccessLogs{ + Path: "/var/log/envoy.logs", + }, + }, + }, + "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.path: Invalid value: \"/var/log/envoy.logs\": path is only valid for file type access logs", + }, + "accessLogs.jsonFormat": { + &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + AccessLogs: &AccessLogs{ + JSONFormat: "{ \"start_time\": \"%START_TIME\"", // intentionally missing the closing brace + }, + }, + }, + "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.jsonFormat: Invalid value: \"{ \\\"start_time\\\": \\\"%START_TIME\\\"\": invalid access log json", + }, + "accessLogs.textFormat": { + &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + AccessLogs: &AccessLogs{ + JSONFormat: "{ \"start_time\": \"%START_TIME\" }", + TextFormat: "MY START TIME %START_TIME", + }, + }, + }, + "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.textFormat: Invalid value: \"MY START TIME %START_TIME\": cannot specify both access log jsonFormat and textFormat", + }, "multi-error": { &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ @@ -386,10 +480,14 @@ func TestProxyDefaults_Validate(t *testing.T) { TransparentProxy: &TransparentProxy{ OutboundListenerPort: 1000, }, + AccessLogs: &AccessLogs{ + JSONFormat: "{ \"start_time\": \"%START_TIME\" }", + TextFormat: "MY START TIME %START_TIME", + }, Mode: proxyModeRef("transparent"), }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: [spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]", + "proxydefaults.consul.hashicorp.com \"global\" is invalid: [spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.accessLogs.textFormat: Invalid value: \"MY START TIME %START_TIME\": cannot specify both access log jsonFormat and textFormat, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]", }, } for name, testCase := range cases { diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index a51bf63d0d..34c385329e 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -10,6 +10,21 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessLogs) DeepCopyInto(out *AccessLogs) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessLogs. +func (in *AccessLogs) DeepCopy() *AccessLogs { + if in == nil { + return nil + } + out := new(AccessLogs) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -1204,6 +1219,11 @@ func (in *ProxyDefaultsSpec) DeepCopyInto(out *ProxyDefaultsSpec) { } out.MeshGateway = in.MeshGateway in.Expose.DeepCopyInto(&out.Expose) + if in.AccessLogs != nil { + in, out := &in.AccessLogs, &out.AccessLogs + *out = new(AccessLogs) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsSpec. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 6b9628cd74..4a309ebbbc 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -50,6 +50,38 @@ spec: spec: description: ProxyDefaultsSpec defines the desired state of ProxyDefaults. properties: + accessLogs: + description: AccessLogs controls all envoy instances' access logging + configuration. + properties: + disableListenerLogs: + description: DisableListenerLogs turns off just listener logs + for connections rejected by Envoy because they don't have a + matching listener filter. + type: boolean + enabled: + description: Enabled turns on all access logging + type: boolean + jsonFormat: + description: 'JSONFormat is a JSON-formatted string of an Envoy + access log format dictionary. See for more info on formatting: + https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-dictionaries + Defining JSONFormat and TextFormat is invalid.' + type: string + path: + description: Path is the output file to write logs for file-type + logging + type: string + textFormat: + description: 'TextFormat is a representation of Envoy access logs + format. See for more info on formatting: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-strings + Defining JSONFormat and TextFormat is invalid.' + type: string + type: + description: Type selects the output for logs one of "file", "stderr". + "stdout" + type: string + type: object config: description: Config is an arbitrary map of configuration values used by Connect proxies. Any values that your proxy allows can be configured diff --git a/control-plane/go.mod b/control-plane/go.mod index a20937c694..6c3e48d2ba 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.18.0 + github.com/hashicorp/consul/api v1.10.1-0.20221220195433-629878a6879c github.com/hashicorp/consul/sdk v0.13.0 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 diff --git a/control-plane/go.sum b/control-plane/go.sum index b5b77aa4b8..ef065d85b3 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -346,6 +346,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.10.1-0.20221220195433-629878a6879c h1:dIy67NF/J5gm0wdGxTA8dCFeQDz8TatuuMEzC7n3aDk= +github.com/hashicorp/consul/api v1.10.1-0.20221220195433-629878a6879c/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g= github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= From 7fa7336b6bd002efed5fea88990c3f6d2f62d94f Mon Sep 17 00:00:00 2001 From: David Yu Date: Thu, 22 Dec 2022 10:58:30 -0800 Subject: [PATCH 037/592] move PR 1804 to unreleased (#1812) --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79cf1931b1..a6e1a511c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ IMPROVEMENTS: * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] * Add the `accessLogs` field to the `ProxyDefaults` CRD. [[GH-1816](https://github.com/hashicorp/consul-k8s/pull/1816)] +BUG FIXES: +* Control Plane + * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] + ## 1.0.2 (December 1, 2022) IMPROVEMENTS: @@ -15,8 +19,6 @@ BUG FIXES: * Helm: * Use the correct autogenerated cert for the API Gateway Controller when connecting to servers versus clients. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] * Don't mount the CA cert when `externalServers.useSystemRoots` is `true`. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] -* Control Plane - * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] ## 0.49.2 (December 1, 2022) From 3b7fa09f44ea229b44376e30811c59adbcebb229 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Thu, 12 Jan 2023 14:22:21 -0500 Subject: [PATCH 038/592] Create annotation to allows users to use proxy health check (#1824) --- CHANGELOG.md | 2 ++ .../connect-inject/constants/annotations_and_labels.go | 5 +++++ .../connect-inject/webhook/consul_dataplane_sidecar.go | 1 + 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e1a511c5..d0596484f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ IMPROVEMENTS: * Helm: * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] * Add the `accessLogs` field to the `ProxyDefaults` CRD. [[GH-1816](https://github.com/hashicorp/consul-k8s/pull/1816)] +* Control-Plane + * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)] BUG FIXES: * Control Plane diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index c3ba29ace2..637e028202 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -86,6 +86,11 @@ const ( // e.g. consul.hashicorp.com/service-meta-foo:bar. AnnotationMeta = "consul.hashicorp.com/service-meta-" + // AnnotationUseProxyHealthCheck creates a readiness listener on the sidecar proxy and + // queries this instead of the application health check for the status of the application. + // Enable this only if the application does not support health checks. + AnnotationUseProxyHealthCheck = "consul.hashicorp.com/use-proxy-health-check" + // annotations for sidecar proxy resource limits. AnnotationSidecarProxyCPULimit = "consul.hashicorp.com/sidecar-proxy-cpu-limit" AnnotationSidecarProxyCPURequest = "consul.hashicorp.com/sidecar-proxy-cpu-request" diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index 9d9c4af7d4..d0cd232c84 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -55,6 +55,7 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor }, InitialDelaySeconds: 1, } + container := corev1.Container{ Name: containerName, Image: w.ImageConsulDataplane, From a5cc951825295cbf285b4b0d1bf37490087c3bb3 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 12 Jan 2023 12:58:59 -0800 Subject: [PATCH 039/592] Add envoyExtensions field to serviceDefaults and proxyDefaults CRDs (#1823) * updated consul api to the latest * added new ServiceDefault fields to the serviceDefaults CRD - Added fields to support EnvoyExtensions - Added missing BalanceInboundConnections field * added EnvoyExtensions to proxyDefaults CRD - most of these changes are parallel to what was done in serviceDefaults - proxyDefaults makes use of the envoyExtension(s) definition in serviceDefaults and also makes use of the validation/toConsul logic defined there * added tests for new fields - Added tests for proxyDefault - Added tests for serviceDefault - EnvoyExtension test cases should be basically the same for both CRDs * generate the manifests and generate deepcopy * updated the changelog * added CI test to catch bad terraform formatting * formatted terraform files * update contributing doc --- .github/workflows/test.yml | 14 ++ CHANGELOG.md | 2 + CONTRIBUTING.md | 17 +- Makefile | 10 + .../consul/templates/crd-proxydefaults.yaml | 16 ++ .../consul/templates/crd-servicedefaults.yaml | 36 ++- charts/consul/test/terraform/aks/variables.tf | 2 +- charts/consul/test/terraform/eks/main.tf | 4 +- charts/consul/test/terraform/eks/variables.tf | 2 +- charts/consul/test/terraform/gke/variables.tf | 2 +- .../test/terraform/openshift/variables.tf | 2 +- .../api/v1alpha1/proxydefaults_types.go | 7 +- .../api/v1alpha1/proxydefaults_types_test.go | 189 ++++++++++++++-- .../v1alpha1/proxydefaults_webhook_test.go | 2 +- .../api/v1alpha1/servicedefaults_types.go | 43 ++-- .../v1alpha1/servicedefaults_types_test.go | 205 ++++++++++++++++++ control-plane/api/v1alpha1/shared_types.go | 66 ++++++ .../api/v1alpha1/zz_generated.deepcopy.go | 55 +++++ .../scripts/terraformfmtcheck.sh | 14 ++ .../consul.hashicorp.com_proxydefaults.yaml | 16 ++ .../consul.hashicorp.com_servicedefaults.yaml | 36 ++- control-plane/go.mod | 2 +- control-plane/go.sum | 2 + 23 files changed, 679 insertions(+), 65 deletions(-) create mode 100755 control-plane/build-support/scripts/terraformfmtcheck.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3bbaf5bfe7..3967d1c816 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,6 +10,20 @@ env: CONSUL_ENT_DOCKER_IMAGE: hashicorppreview/consul-enterprise:1.14-dev # Consul's enterprise version to use in tests jobs: + terraform-fmt-check: + name: "Terraform format check" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: TERRAFORM_VERSION + terraform_wrapper: false + - name: Run Terraform checks + run: | + make terraform-fmt-check TERRAFORM_DIR="${{ github.workspace }}" + get-go-version: name: "Determine Go toolchain version" runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index d0596484f3..3599155056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ IMPROVEMENTS: * Helm: * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] * Add the `accessLogs` field to the `ProxyDefaults` CRD. [[GH-1816](https://github.com/hashicorp/consul-k8s/pull/1816)] + * Add the `envoyExtensions` field to the `ProxyDefaults` and `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) + * Add the `balanceInboundConnections` field to the `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) * Control-Plane * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 327399b89c..61f65f21a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -424,20 +424,27 @@ rebase the branch on main, fixing any conflicts along the way before the code ca manage your resource type. ### Testing A New CRD -1. Build a Docker image for consul-k8s via `make dev-docker` and tagging your image appropriately. Remember to CD into the `control-plane` directory! +1. Build a Docker image for consul-k8s via `make control-plane-dev-docker` and push to a docker repository: + ``` + docker tag consul-k8s-control-plane-dev /consul-k8s-control-plane-dev: + docker push /consul-k8s-control-plane-dev: + ``` 1. Install using the updated Helm repository, with a values like: ```yaml global: - imageK8S: ghcr.io/lkysow/consul-k8s-dev:nov26 + imageK8S: lkysow/consul-k8s-control-plane-dev:nov26 name: consul server: replicas: 1 bootstrapExpect: 1 - controller: + ui: + enabled: true + connectInject: enabled: true ``` -1. `kubectl apply` your sample CRD. -1. Check its synced status: +1. Create a sample CRD +1. Run `kubectl apply -f ` to apply your sample CRD. +1. Check its synced status (for example CRD called ingressgateway): ```bash kubectl get ingressgateway NAME SYNCED AGE diff --git a/Makefile b/Makefile index 43a8e16b0d..6ad59a2a91 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,16 @@ kind-cni: kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc2 --image kindest/node:v1.23.6 make kind-cni-calico +# Perform a terraform fmt check but don't change anything +terraform-fmt-check: + @$(CURDIR)/control-plane/build-support/scripts/terraformfmtcheck.sh $(TERRAFORM_DIR) +.PHONY: terraform-fmt-check + +# Format all terraform files according to terraform fmt +terraform-fmt: + @terraform fmt -recursive +.PHONY: terraform-fmt + # ===========> CLI Targets diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 1a63cfef36..749f2e4257 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -95,6 +95,22 @@ spec: globally here. Supports JSON config values. See https://www.consul.io/docs/connect/proxies/envoy#configuration-formatting type: object x-kubernetes-preserve-unknown-fields: true + envoyExtensions: + description: EnvoyExtensions are a list of extensions to modify Envoy + proxy configuration. + items: + description: EnvoyExtension has configuration for an extension that + patches Envoy resources. + properties: + arguments: + type: object + x-kubernetes-preserve-unknown-fields: true + name: + type: string + required: + type: boolean + type: object + type: array expose: description: Expose controls the default expose path configuration for Envoy. diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index f1ebdc8d2c..128884c454 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -57,6 +57,12 @@ spec: spec: description: ServiceDefaultsSpec defines the desired state of ServiceDefaults. properties: + balanceInboundConnections: + description: BalanceInboundConnections sets the strategy for allocating + inbound connections to the service across proxy threads. The only + supported value is exact_balance. By default, no connection balancing + is used. Refer to the Envoy Connection Balance config for details. + type: string destination: description: Destination is an address(es)/port combination that represents an endpoint outside the mesh. This is only valid when the mesh is @@ -76,6 +82,22 @@ spec: format: int32 type: integer type: object + envoyExtensions: + description: EnvoyExtensions are a list of extensions to modify Envoy + proxy configuration. + items: + description: EnvoyExtension has configuration for an extension that + patches Envoy resources. + properties: + arguments: + type: object + x-kubernetes-preserve-unknown-fields: true + name: + type: string + required: + type: boolean + type: object + type: array expose: description: Expose controls the default expose path configuration for Envoy. @@ -114,15 +136,15 @@ spec: with an external system. type: string localConnectTimeoutMs: - description: The number of milliseconds allowed to make connections - to the local application instance before timing out. Defaults to - 5000. + description: LocalConnectTimeoutMs is the number of milliseconds allowed + to make connections to the local application instance before timing + out. Defaults to 5000. type: integer localRequestTimeoutMs: - description: In milliseconds, the timeout for HTTP requests to the - local application instance. Applies to HTTP-based protocols only. - If not specified, inherits the Envoy default for route timeouts - (15s). + description: LocalRequestTimeoutMs is the timeout for HTTP requests + to the local application instance in milliseconds. Applies to HTTP-based + protocols only. If not specified, inherits the Envoy default for + route timeouts (15s). type: integer maxInboundConnections: description: MaxInboundConnections is the maximum number of concurrent diff --git a/charts/consul/test/terraform/aks/variables.tf b/charts/consul/test/terraform/aks/variables.tf index 1651ce7b09..bb9dbef537 100644 --- a/charts/consul/test/terraform/aks/variables.tf +++ b/charts/consul/test/terraform/aks/variables.tf @@ -27,7 +27,7 @@ variable "cluster_count" { } variable "tags" { - type = map + type = map(any) default = {} description = "Tags to attach to the created resources." } diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index c466334315..9ccc2cdd2b 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -53,8 +53,8 @@ module "vpc" { module "eks" { count = var.cluster_count - source = "terraform-aws-modules/eks/aws" - version = "17.24.0" + source = "terraform-aws-modules/eks/aws" + version = "17.24.0" kubeconfig_api_version = "client.authentication.k8s.io/v1beta1" cluster_name = "consul-k8s-${random_id.suffix[count.index].dec}" diff --git a/charts/consul/test/terraform/eks/variables.tf b/charts/consul/test/terraform/eks/variables.tf index 361a5f5c45..05f383168b 100644 --- a/charts/consul/test/terraform/eks/variables.tf +++ b/charts/consul/test/terraform/eks/variables.tf @@ -21,7 +21,7 @@ variable "role_arn" { } variable "tags" { - type = map + type = map(any) default = {} description = "Tags to attach to the created resources." } diff --git a/charts/consul/test/terraform/gke/variables.tf b/charts/consul/test/terraform/gke/variables.tf index 04d214cedb..ef4a429116 100644 --- a/charts/consul/test/terraform/gke/variables.tf +++ b/charts/consul/test/terraform/gke/variables.tf @@ -30,7 +30,7 @@ variable "cluster_count" { } variable "labels" { - type = map + type = map(any) default = {} description = "Labels to attach to the created resources." } diff --git a/charts/consul/test/terraform/openshift/variables.tf b/charts/consul/test/terraform/openshift/variables.tf index f2479e3229..1df518f8ed 100644 --- a/charts/consul/test/terraform/openshift/variables.tf +++ b/charts/consul/test/terraform/openshift/variables.tf @@ -9,7 +9,7 @@ variable "cluster_count" { } variable "tags" { - type = map + type = map(any) default = {} description = "Tags to attach to the created resources." } diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index 82fcde3687..543d8f7ae4 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -77,6 +77,8 @@ type ProxyDefaultsSpec struct { Expose Expose `json:"expose,omitempty"` // AccessLogs controls all envoy instances' access logging configuration. AccessLogs *AccessLogs `json:"accessLogs,omitempty"` + // EnvoyExtensions are a list of extensions to modify Envoy proxy configuration. + EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` } func (in *ProxyDefaults) GetObjectMeta() metav1.ObjectMeta { @@ -168,6 +170,7 @@ func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry { Config: consulConfig, TransparentProxy: in.Spec.TransparentProxy.toConsul(), AccessLogs: in.Spec.AccessLogs.toConsul(), + EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), Meta: meta(datacenter), } } @@ -202,6 +205,8 @@ func (in *ProxyDefaults) Validate(_ common.ConsulMeta) error { allErrs = append(allErrs, err) } allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) + allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) + if len(allErrs) > 0 { return apierrors.NewInvalid( schema.GroupKind{Group: ConsulHashicorpGroup, Kind: ProxyDefaultsKubeKind}, @@ -239,7 +244,7 @@ func (in *ProxyDefaults) validateConfig(path *field.Path) *field.Error { } var outConfig map[string]interface{} if err := json.Unmarshal(in.Spec.Config, &outConfig); err != nil { - return field.Invalid(path, in.Spec.Config, fmt.Sprintf(`must be valid map value: %s`, err)) + return field.Invalid(path, string(in.Spec.Config), fmt.Sprintf(`must be valid map value: %s`, err)) } return nil } diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 794906b05e..225068c136 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -78,6 +78,18 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Path: "/var/log/envoy.logs", TextFormat: "ITS WORKING %START_TIME%", }, + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), + Required: true, + }, + }, }, }, Theirs: &capi.ProxyConfigEntry{ @@ -117,6 +129,25 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Path: "/var/log/envoy.logs", TextFormat: "ITS WORKING %START_TIME%", }, + EnvoyExtensions: []capi.EnvoyExtension{ + { + Name: "aws_request_signing", + Arguments: map[string]interface{}{ + "AWSServiceName": "s3", + "Region": "us-west-2", + }, + Required: false, + }, + { + Name: "zipkin", + Arguments: map[string]interface{}{ + "ClusterName": "zipkin_cluster", + "Port": "9411", + "CollectorEndpoint": "/api/v2/spans", + }, + Required: true, + }, + }, }, Matches: true, }, @@ -257,6 +288,18 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Path: "/var/log/envoy.logs", TextFormat: "ITS WORKING %START_TIME%", }, + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), + Required: true, + }, + }, }, }, Exp: &capi.ProxyConfigEntry{ @@ -297,6 +340,25 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Path: "/var/log/envoy.logs", TextFormat: "ITS WORKING %START_TIME%", }, + EnvoyExtensions: []capi.EnvoyExtension{ + { + Name: "aws_request_signing", + Arguments: map[string]interface{}{ + "AWSServiceName": "s3", + "Region": "us-west-2", + }, + Required: false, + }, + { + Name: "zipkin", + Arguments: map[string]interface{}{ + "ClusterName": "zipkin_cluster", + "Port": "9411", + "CollectorEndpoint": "/api/v2/spans", + }, + Required: true, + }, + }, Meta: map[string]string{ common.SourceKey: common.SourceValue, common.DatacenterKey: "datacenter", @@ -321,8 +383,30 @@ func TestProxyDefaults_Validate(t *testing.T) { input *ProxyDefaults expectedErrMsg string }{ + "valid envoyExtension": { + input: &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), + Required: true, + }, + }, + }, + }, + expectedErrMsg: "", + }, "meshgateway.mode": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -332,10 +416,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - `proxydefaults.consul.hashicorp.com "global" is invalid: spec.meshGateway.mode: Invalid value: "foobar": must be one of "remote", "local", "none", ""`, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.meshGateway.mode: Invalid value: "foobar": must be one of "remote", "local", "none", ""`, }, "expose.paths[].protocol": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -350,10 +434,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - `proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].protocol: Invalid value: "invalid-protocol": must be one of "http", "http2"`, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].protocol: Invalid value: "invalid-protocol": must be one of "http", "http2"`, }, "expose.paths[].path": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -368,10 +452,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - `proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].path: Invalid value: "invalid-path": must begin with a '/'`, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].path: Invalid value: "invalid-path": must begin with a '/'`, }, "transparentProxy.outboundListenerPort": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -381,10 +465,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port", + expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port", }, "mode": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -392,10 +476,10 @@ func TestProxyDefaults_Validate(t *testing.T) { Mode: proxyModeRef("transparent"), }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode", + expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode", }, "accessLogs.type": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -405,10 +489,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.type: Invalid value: \"foo\": invalid access log type (must be one of \"stdout\", \"stderr\", \"file\"", + expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.type: Invalid value: \"foo\": invalid access log type (must be one of \"stdout\", \"stderr\", \"file\"", }, "accessLogs.path missing": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -418,10 +502,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.path: Invalid value: \"\": path must be specified when using file type access logs", + expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.path: Invalid value: \"\": path must be specified when using file type access logs", }, "accessLogs.path for wrong type": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -431,10 +515,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.path: Invalid value: \"/var/log/envoy.logs\": path is only valid for file type access logs", + expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.path: Invalid value: \"/var/log/envoy.logs\": path is only valid for file type access logs", }, "accessLogs.jsonFormat": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -444,10 +528,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.jsonFormat: Invalid value: \"{ \\\"start_time\\\": \\\"%START_TIME\\\"\": invalid access log json", + expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.jsonFormat: Invalid value: \"{ \\\"start_time\\\": \\\"%START_TIME\\\"\": invalid access log json", }, "accessLogs.textFormat": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -458,10 +542,71 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.textFormat: Invalid value: \"MY START TIME %START_TIME\": cannot specify both access log jsonFormat and textFormat", + expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.textFormat: Invalid value: \"MY START TIME %START_TIME\": cannot specify both access log jsonFormat and textFormat", + }, + "envoyExtension.arguments single empty": { + input: &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: nil, + Required: true, + }, + }, + }, + }, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.envoyExtensions.envoyExtension[1].arguments: Required value: arguments must be defined`, + }, + "envoyExtension.arguments multi empty": { + input: &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: nil, + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: nil, + Required: true, + }, + }, + }, + }, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: [spec.envoyExtensions.envoyExtension[0].arguments: Required value: arguments must be defined, spec.envoyExtensions.envoyExtension[1].arguments: Required value: arguments must be defined]`, + }, + "envoyExtension.arguments invalid json": { + input: &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"SOME_INVALID_JSON"}`), + Required: false, + }, + }, + }, + }, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.envoyExtensions.envoyExtension[0].arguments: Invalid value: "{\"SOME_INVALID_JSON\"}": must be valid map value: invalid character '}' after object key`, }, "multi-error": { - &ProxyDefaults{ + input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -487,7 +632,7 @@ func TestProxyDefaults_Validate(t *testing.T) { Mode: proxyModeRef("transparent"), }, }, - "proxydefaults.consul.hashicorp.com \"global\" is invalid: [spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.accessLogs.textFormat: Invalid value: \"MY START TIME %START_TIME\": cannot specify both access log jsonFormat and textFormat, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]", + expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: [spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.accessLogs.textFormat: Invalid value: \"MY START TIME %START_TIME\": cannot specify both access log jsonFormat and textFormat, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]", }, } for name, testCase := range cases { diff --git a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go index cc36d994dd..3136540089 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go @@ -46,7 +46,7 @@ func TestValidateProxyDefault(t *testing.T) { }, expAllow: false, // This error message is because the value "1" is valid JSON but is an invalid map - expErrMessage: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.config: Invalid value: json.RawMessage{0x31}: must be valid map value: json: cannot unmarshal number into Go value of type map[string]interface {}", + expErrMessage: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.config: Invalid value: \"1\": must be valid map value: json: cannot unmarshal number into Go value of type map[string]interface {}", }, "proxy default exists": { existingResources: []runtime.Object{&ProxyDefaults{ diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 0a4020e010..2682f6a28a 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -88,13 +88,19 @@ type ServiceDefaultsSpec struct { // MaxInboundConnections is the maximum number of concurrent inbound connections to // each service instance. Defaults to 0 (using consul's default) if not set. MaxInboundConnections int `json:"maxInboundConnections,omitempty"` - // The number of milliseconds allowed to make connections to the local application + // LocalConnectTimeoutMs is the number of milliseconds allowed to make connections to the local application // instance before timing out. Defaults to 5000. LocalConnectTimeoutMs int `json:"localConnectTimeoutMs,omitempty"` - // In milliseconds, the timeout for HTTP requests to the local application instance. + // LocalRequestTimeoutMs is the timeout for HTTP requests to the local application instance in milliseconds. // Applies to HTTP-based protocols only. If not specified, inherits the Envoy default for // route timeouts (15s). LocalRequestTimeoutMs int `json:"localRequestTimeoutMs,omitempty"` + // BalanceInboundConnections sets the strategy for allocating inbound connections to the service across + // proxy threads. The only supported value is exact_balance. By default, no connection balancing is used. + // Refer to the Envoy Connection Balance config for details. + BalanceInboundConnections string `json:"balanceInboundConnections,omitempty"` + // EnvoyExtensions are a list of extensions to modify Envoy proxy configuration. + EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` } type Upstreams struct { @@ -260,19 +266,21 @@ func (in *ServiceDefaults) SyncedConditionStatus() corev1.ConditionStatus { // ToConsul converts the entry into it's Consul equivalent struct. func (in *ServiceDefaults) ToConsul(datacenter string) capi.ConfigEntry { return &capi.ServiceConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - Protocol: in.Spec.Protocol, - MeshGateway: in.Spec.MeshGateway.toConsul(), - Expose: in.Spec.Expose.toConsul(), - ExternalSNI: in.Spec.ExternalSNI, - TransparentProxy: in.Spec.TransparentProxy.toConsul(), - UpstreamConfig: in.Spec.UpstreamConfig.toConsul(), - Destination: in.Spec.Destination.toConsul(), - Meta: meta(datacenter), - MaxInboundConnections: in.Spec.MaxInboundConnections, - LocalConnectTimeoutMs: in.Spec.LocalConnectTimeoutMs, - LocalRequestTimeoutMs: in.Spec.LocalRequestTimeoutMs, + Kind: in.ConsulKind(), + Name: in.ConsulName(), + Protocol: in.Spec.Protocol, + MeshGateway: in.Spec.MeshGateway.toConsul(), + Expose: in.Spec.Expose.toConsul(), + ExternalSNI: in.Spec.ExternalSNI, + TransparentProxy: in.Spec.TransparentProxy.toConsul(), + UpstreamConfig: in.Spec.UpstreamConfig.toConsul(), + Destination: in.Spec.Destination.toConsul(), + Meta: meta(datacenter), + MaxInboundConnections: in.Spec.MaxInboundConnections, + LocalConnectTimeoutMs: in.Spec.LocalConnectTimeoutMs, + LocalRequestTimeoutMs: in.Spec.LocalRequestTimeoutMs, + BalanceInboundConnections: in.Spec.BalanceInboundConnections, + EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), } } @@ -311,8 +319,13 @@ func (in *ServiceDefaults) Validate(consulMeta common.ConsulMeta) error { allErrs = append(allErrs, field.Invalid(path.Child("localRequestTimeoutMs"), in.Spec.LocalRequestTimeoutMs, "LocalRequestTimeoutMs must be > 0")) } + if in.Spec.BalanceInboundConnections != "" && in.Spec.BalanceInboundConnections != "exact_balance" { + allErrs = append(allErrs, field.Invalid(path.Child("balanceInboundConnections"), in.Spec.BalanceInboundConnections, "BalanceInboundConnections must be an empty string or exact_balance")) + } + allErrs = append(allErrs, in.Spec.UpstreamConfig.validate(path.Child("upstreamConfig"), consulMeta.PartitionsEnabled)...) allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) + allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) if len(allErrs) > 0 { return apierrors.NewInvalid( diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index e7fdae2575..fb29cf15cc 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "encoding/json" "testing" "time" @@ -141,6 +142,19 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, }, }, + BalanceInboundConnections: "exact_balance", + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), + Required: true, + }, + }, Destination: &ServiceDefaultsDestination{ Addresses: []string{"api.google.com"}, Port: 443, @@ -249,6 +263,26 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, }, }, + BalanceInboundConnections: "exact_balance", + EnvoyExtensions: []capi.EnvoyExtension{ + { + Name: "aws_request_signing", + Arguments: map[string]interface{}{ + "AWSServiceName": "s3", + "Region": "us-west-2", + }, + Required: false, + }, + { + Name: "zipkin", + Arguments: map[string]interface{}{ + "ClusterName": "zipkin_cluster", + "Port": "9411", + "CollectorEndpoint": "/api/v2/spans", + }, + Required: true, + }, + }, Destination: &capi.DestinationConfig{ Addresses: []string{"api.google.com"}, Port: 443, @@ -402,6 +436,19 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, }, }, + BalanceInboundConnections: "exact_balance", + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), + Required: true, + }, + }, Destination: &ServiceDefaultsDestination{ Addresses: []string{"api.google.com"}, Port: 443, @@ -503,6 +550,26 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, }, }, + BalanceInboundConnections: "exact_balance", + EnvoyExtensions: []capi.EnvoyExtension{ + { + Name: "aws_request_signing", + Arguments: map[string]interface{}{ + "AWSServiceName": "s3", + "Region": "us-west-2", + }, + Required: false, + }, + { + Name: "zipkin", + Arguments: map[string]interface{}{ + "ClusterName": "zipkin_cluster", + "Port": "9411", + "CollectorEndpoint": "/api/v2/spans", + }, + Required: true, + }, + }, Destination: &capi.DestinationConfig{ Addresses: []string{"api.google.com"}, Port: 443, @@ -638,6 +705,39 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: "", }, + "valid - balanceInboundConnections": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + BalanceInboundConnections: "exact_balance", + }, + }, + expectedErrMsg: "", + }, + "valid - envoyExtension": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), + Required: true, + }, + }, + }, + }, + expectedErrMsg: "", + }, "protocol": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ @@ -915,6 +1015,111 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.destination.port: Invalid value: 0x0: invalid port number`, }, + "MaxInboundConnections (invalid value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + MaxInboundConnections: -1, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.maxinboundconnections: Invalid value: -1: MaxInboundConnections must be > 0`, + }, + "LocalConnectTimeoutMs (invalid value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + LocalConnectTimeoutMs: -1, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.localConnectTimeoutMs: Invalid value: -1: LocalConnectTimeoutMs must be > 0`, + }, + "LocalRequestTimeoutMs (invalid value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + LocalRequestTimeoutMs: -1, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.localRequestTimeoutMs: Invalid value: -1: LocalRequestTimeoutMs must be > 0`, + }, + "balanceInboundConnections (invalid value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + BalanceInboundConnections: "not_exact_balance", + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.balanceInboundConnections: Invalid value: "not_exact_balance": BalanceInboundConnections must be an empty string or exact_balance`, + }, + "envoyExtension.arguments (single empty)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), + Required: false, + }, + EnvoyExtension{ + Name: "zipkin", + Arguments: nil, + Required: true, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.envoyExtensions.envoyExtension[1].arguments: Required value: arguments must be defined`, + }, + "envoyExtension.arguments (multi empty)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: nil, + Required: false, + }, + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: nil, + Required: false, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.envoyExtensions.envoyExtension[0].arguments: Required value: arguments must be defined, spec.envoyExtensions.envoyExtension[1].arguments: Required value: arguments must be defined]`, + }, + "envoyExtension.arguments (invalid json)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + EnvoyExtensions: EnvoyExtensions{ + EnvoyExtension{ + Name: "aws_request_signing", + Arguments: json.RawMessage(`{"SOME_INVALID_JSON"}`), + Required: false, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.envoyExtensions.envoyExtension[0].arguments: Invalid value: "{\"SOME_INVALID_JSON\"}": must be valid map value: invalid character '}' after object key`, + }, } for name, testCase := range cases { diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 9b884cf476..edcaba5a46 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "encoding/json" "fmt" "strings" @@ -79,6 +80,19 @@ type HTTPHeaderModifiers struct { Remove []string `json:"remove,omitempty"` } +// EnvoyExtension has configuration for an extension that patches Envoy resources. +type EnvoyExtension struct { + Name string `json:"name,omitempty"` + Required bool `json:"required,omitempty"` + // +kubebuilder:validation:Type=object + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + Arguments json.RawMessage `json:"arguments,omitempty"` +} + +// EnvoyExtensions represents a list of the EnvoyExtension configuration. +type EnvoyExtensions []EnvoyExtension + func (in MeshGateway) toConsul() capi.MeshGatewayConfig { mode := capi.MeshGatewayMode(in.Mode) switch mode { @@ -176,6 +190,58 @@ func (in *HTTPHeaderModifiers) toConsul() *capi.HTTPHeaderModifiers { } } +func (in EnvoyExtensions) toConsul() []capi.EnvoyExtension { + if in == nil { + return nil + } + + outConfig := make([]capi.EnvoyExtension, 0) + + for _, e := range in { + consulExtension := capi.EnvoyExtension{ + Name: e.Name, + Required: e.Required, + } + + // We already validate that arguments is present + var args map[string]interface{} + _ = json.Unmarshal(e.Arguments, &args) + consulExtension.Arguments = args + outConfig = append(outConfig, consulExtension) + } + + return outConfig +} + +func (in EnvoyExtensions) validate(path *field.Path) field.ErrorList { + if len(in) == 0 { + return nil + } + + var errs field.ErrorList + for i, e := range in { + if err := e.validate(path.Child("envoyExtension").Index(i)); err != nil { + errs = append(errs, err) + } + } + + return errs +} + +func (in EnvoyExtension) validate(path *field.Path) *field.Error { + // Validate that the arguments are not nil + if in.Arguments == nil { + err := field.Required(path.Child("arguments"), "arguments must be defined") + return err + } + // Validate that the arguments are valid json + var outConfig map[string]interface{} + if err := json.Unmarshal(in.Arguments, &outConfig); err != nil { + return field.Invalid(path.Child("arguments"), string(in.Arguments), fmt.Sprintf(`must be valid map value: %s`, err)) + } + return nil +} + func notInSliceMessage(slice []string) string { return fmt.Sprintf(`must be one of "%s"`, strings.Join(slice, `", "`)) } diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 34c385329e..d12db29d14 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -78,6 +78,47 @@ func (in *CookieConfig) DeepCopy() *CookieConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvoyExtension) DeepCopyInto(out *EnvoyExtension) { + *out = *in + if in.Arguments != nil { + in, out := &in.Arguments, &out.Arguments + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyExtension. +func (in *EnvoyExtension) DeepCopy() *EnvoyExtension { + if in == nil { + return nil + } + out := new(EnvoyExtension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in EnvoyExtensions) DeepCopyInto(out *EnvoyExtensions) { + { + in := &in + *out = make(EnvoyExtensions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyExtensions. +func (in EnvoyExtensions) DeepCopy() EnvoyExtensions { + if in == nil { + return nil + } + out := new(EnvoyExtensions) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExportedService) DeepCopyInto(out *ExportedService) { *out = *in @@ -1224,6 +1265,13 @@ func (in *ProxyDefaultsSpec) DeepCopyInto(out *ProxyDefaultsSpec) { *out = new(AccessLogs) **out = **in } + if in.EnvoyExtensions != nil { + in, out := &in.EnvoyExtensions, &out.EnvoyExtensions + *out = make(EnvoyExtensions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsSpec. @@ -1401,6 +1449,13 @@ func (in *ServiceDefaultsSpec) DeepCopyInto(out *ServiceDefaultsSpec) { *out = new(ServiceDefaultsDestination) (*in).DeepCopyInto(*out) } + if in.EnvoyExtensions != nil { + in, out := &in.EnvoyExtensions, &out.EnvoyExtensions + *out = make(EnvoyExtensions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceDefaultsSpec. diff --git a/control-plane/build-support/scripts/terraformfmtcheck.sh b/control-plane/build-support/scripts/terraformfmtcheck.sh new file mode 100755 index 0000000000..0f962a1c1b --- /dev/null +++ b/control-plane/build-support/scripts/terraformfmtcheck.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Check terraform fmt +echo "==> Checking that code complies with terraform fmt requirements..." +tffmt_files=$(terraform fmt -check -recursive "$1") +if [[ -n ${tffmt_files} ]]; then + echo 'terraform fmt needs to be run on the following files:' + echo "${tffmt_files}" + echo "You can use the command: \`make terraform-fmt\` to reformat all terraform code." + exit 1 +fi + +echo "==> Check code compile completed successfully" +exit 0 \ No newline at end of file diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 4a309ebbbc..2563cbcf77 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -88,6 +88,22 @@ spec: globally here. Supports JSON config values. See https://www.consul.io/docs/connect/proxies/envoy#configuration-formatting type: object x-kubernetes-preserve-unknown-fields: true + envoyExtensions: + description: EnvoyExtensions are a list of extensions to modify Envoy + proxy configuration. + items: + description: EnvoyExtension has configuration for an extension that + patches Envoy resources. + properties: + arguments: + type: object + x-kubernetes-preserve-unknown-fields: true + name: + type: string + required: + type: boolean + type: object + type: array expose: description: Expose controls the default expose path configuration for Envoy. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 944f494f98..8b05eeb025 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -50,6 +50,12 @@ spec: spec: description: ServiceDefaultsSpec defines the desired state of ServiceDefaults. properties: + balanceInboundConnections: + description: BalanceInboundConnections sets the strategy for allocating + inbound connections to the service across proxy threads. The only + supported value is exact_balance. By default, no connection balancing + is used. Refer to the Envoy Connection Balance config for details. + type: string destination: description: Destination is an address(es)/port combination that represents an endpoint outside the mesh. This is only valid when the mesh is @@ -69,6 +75,22 @@ spec: format: int32 type: integer type: object + envoyExtensions: + description: EnvoyExtensions are a list of extensions to modify Envoy + proxy configuration. + items: + description: EnvoyExtension has configuration for an extension that + patches Envoy resources. + properties: + arguments: + type: object + x-kubernetes-preserve-unknown-fields: true + name: + type: string + required: + type: boolean + type: object + type: array expose: description: Expose controls the default expose path configuration for Envoy. @@ -107,15 +129,15 @@ spec: with an external system. type: string localConnectTimeoutMs: - description: The number of milliseconds allowed to make connections - to the local application instance before timing out. Defaults to - 5000. + description: LocalConnectTimeoutMs is the number of milliseconds allowed + to make connections to the local application instance before timing + out. Defaults to 5000. type: integer localRequestTimeoutMs: - description: In milliseconds, the timeout for HTTP requests to the - local application instance. Applies to HTTP-based protocols only. - If not specified, inherits the Envoy default for route timeouts - (15s). + description: LocalRequestTimeoutMs is the timeout for HTTP requests + to the local application instance in milliseconds. Applies to HTTP-based + protocols only. If not specified, inherits the Envoy default for + route timeouts (15s). type: integer maxInboundConnections: description: MaxInboundConnections is the maximum number of concurrent diff --git a/control-plane/go.mod b/control-plane/go.mod index 6c3e48d2ba..2cd0300557 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.10.1-0.20221220195433-629878a6879c + github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919 github.com/hashicorp/consul/sdk v0.13.0 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 diff --git a/control-plane/go.sum b/control-plane/go.sum index ef065d85b3..098d32835d 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -348,6 +348,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1-0.20221220195433-629878a6879c h1:dIy67NF/J5gm0wdGxTA8dCFeQDz8TatuuMEzC7n3aDk= github.com/hashicorp/consul/api v1.10.1-0.20221220195433-629878a6879c/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= +github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919 h1:8aVegJMSv7PIAAa1zqQQ0CT4TKv+Nf7I4rhE6+uDa1U= +github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g= github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= From 3ccee4c4093d4aa28001a8fcb6a3862f56e50357 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 13 Jan 2023 09:05:32 -0800 Subject: [PATCH 040/592] updated tests to use 1.15-dev (#1831) * updated tests to use 1.15-dev - 1.15-dev is currently broken, so tagging it to a working sha for the moment * add EnvoyExtensions to proxy and service defaults - Also include balanceInboundConnections in serviceDefaults --- .circleci/config.yml | 20 +++++++++---------- .../bases/crds-oss/proxydefaults.yaml | 11 ++++++++++ .../bases/crds-oss/servicedefaults.yaml | 14 ++++++++++++- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 34bbc520a5..df5dd49573 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -589,7 +589,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results - store_artifacts: @@ -622,7 +622,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results - store_artifacts: @@ -655,7 +655,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -enable-cni -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -enable-cni -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results - store_artifacts: @@ -773,7 +773,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-pod-security-policies -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-pod-security-policies -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results @@ -842,7 +842,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -use-gke -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-pod-security-policies -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -use-gke -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-pod-security-policies -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results @@ -899,7 +899,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results @@ -956,7 +956,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results @@ -1018,7 +1018,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results @@ -1081,7 +1081,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results @@ -1135,7 +1135,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-openshift -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.14-dev + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-openshift -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results diff --git a/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml index 040da247f8..878b4c37e8 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml @@ -19,3 +19,14 @@ spec: - path: /health listenerPort: 22000 localPathPort: 8080 + envoyExtensions: + - name: builtin/aws/lambda + required: false + arguments: + payloadPassthrough: false + region: us-west-2 + - name: builtin/aws/lambda + required: false + arguments: + payloadPassthrough: false + region: us-east-1 diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index 7b5f23eff5..33bc0e5096 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -24,4 +24,16 @@ spec: maxConnections: 5 passiveHealthCheck: interval: 10s - maxFailures: 2 \ No newline at end of file + maxFailures: 2 + balanceInboundConnections: "exact_balance" + envoyExtensions: + - name: builtin/aws/lambda + required: false + arguments: + payloadPassthrough: false + region: us-west-2 + - name: builtin/aws/lambda + required: false + arguments: + payloadPassthrough: false + region: us-east-1 \ No newline at end of file From 9ab7bea6a52043eb25b0673bb77562daa418251c Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Tue, 17 Jan 2023 14:33:42 -0500 Subject: [PATCH 041/592] Add health checks for services that are synced (#1821) * Add health checks for services that are synced - When the type of the service is ClusterIP, a health check will be added to the catalog registration with the health info of the service based on the state of the readiness probe of the pod associated with the service. - Replace `apiv1` with `corev1` to be consistent across the project. - Run `go mod tidy`. --- CHANGELOG.md | 1 + acceptance/tests/sync/sync_catalog_test.go | 8 ++- control-plane/catalog/to-consul/resource.go | 67 ++++++++++++------- .../catalog/to-consul/resource_test.go | 38 +++++++++++ control-plane/go.sum | 9 --- 5 files changed, 90 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3599155056..9b7c7dd852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ IMPROVEMENTS: * Add the `balanceInboundConnections` field to the `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) * Control-Plane * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)] + * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] BUG FIXES: * Control Plane diff --git a/acceptance/tests/sync/sync_catalog_test.go b/acceptance/tests/sync/sync_catalog_test.go index 92b006cac6..c4f873fcbd 100644 --- a/acceptance/tests/sync/sync_catalog_test.go +++ b/acceptance/tests/sync/sync_catalog_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" ) @@ -65,8 +66,13 @@ func TestSyncCatalog(t *testing.T) { service, _, err := consulClient.Catalog().Service(syncedServiceName, "", nil) require.NoError(t, err) - require.Equal(t, 1, len(service)) + require.Len(t, service, 1) require.Equal(t, []string{"k8s"}, service[0].ServiceTags) + filter := fmt.Sprintf("ServiceID == %q", service[0].ServiceID) + healthChecks, _, err := consulClient.Health().Checks(syncedServiceName, &api.QueryOptions{Filter: filter}) + require.NoError(t, err) + require.Len(t, healthChecks, 1) + require.Equal(t, api.HealthPassing, healthChecks[0].Status) }) } } diff --git a/control-plane/catalog/to-consul/resource.go b/control-plane/catalog/to-consul/resource.go index 96538510d1..09d8aa6c5d 100644 --- a/control-plane/catalog/to-consul/resource.go +++ b/control-plane/catalog/to-consul/resource.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/namespaces" consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" - apiv1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" @@ -33,6 +33,12 @@ const ( ConsulK8SRefKind = "external-k8s-ref-kind" ConsulK8SRefValue = "external-k8s-ref-name" ConsulK8SNodeName = "external-k8s-node-name" + + // consulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. + consulKubernetesCheckType = "kubernetes-readiness" + // consulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. + consulKubernetesCheckName = "Kubernetes Readiness Check" + kubernetesSuccessReasonMsg = "Kubernetes health checks passing" ) type NodePortSyncType string @@ -131,11 +137,11 @@ type ServiceResource struct { // serviceMap holds services we should sync to Consul. Keys are the // in the form /. - serviceMap map[string]*apiv1.Service + serviceMap map[string]*corev1.Service // endpointsMap uses the same keys as serviceMap but maps to the endpoints // of each service. - endpointsMap map[string]*apiv1.Endpoints + endpointsMap map[string]*corev1.Endpoints // consulMap holds the services in Consul that we've registered from kube. // It's populated via Consul's API and lets us diff what is actually in @@ -157,7 +163,7 @@ func (t *ServiceResource) Informer() cache.SharedIndexInformer { return t.Client.CoreV1().Services(metav1.NamespaceAll).Watch(t.Ctx, options) }, }, - &apiv1.Service{}, + &corev1.Service{}, 0, cache.Indexers{}, ) @@ -166,7 +172,7 @@ func (t *ServiceResource) Informer() cache.SharedIndexInformer { // Upsert implements the controller.Resource interface. func (t *ServiceResource) Upsert(key string, raw interface{}) error { // We expect a Service. If it isn't a service then just ignore it. - service, ok := raw.(*apiv1.Service) + service, ok := raw.(*corev1.Service) if !ok { t.Log.Warn("upsert got invalid type", "raw", raw) return nil @@ -176,7 +182,7 @@ func (t *ServiceResource) Upsert(key string, raw interface{}) error { defer t.serviceLock.Unlock() if t.serviceMap == nil { - t.serviceMap = make(map[string]*apiv1.Service) + t.serviceMap = make(map[string]*corev1.Service) } if !t.shouldSync(service) { @@ -205,7 +211,7 @@ func (t *ServiceResource) Upsert(key string, raw interface{}) error { "err", err) } else { if t.endpointsMap == nil { - t.endpointsMap = make(map[string]*apiv1.Endpoints) + t.endpointsMap = make(map[string]*corev1.Endpoints) } t.endpointsMap[key] = endpoints t.Log.Debug("[ServiceResource.Upsert] adding service's endpoints to endpointsMap", "key", key, "service", service, "endpoints", endpoints) @@ -254,7 +260,7 @@ func (t *ServiceResource) Run(ch <-chan struct{}) { } // shouldSync returns true if resyncing should be enabled for the given service. -func (t *ServiceResource) shouldSync(svc *apiv1.Service) bool { +func (t *ServiceResource) shouldSync(svc *corev1.Service) bool { // Namespace logic // If in deny list, don't sync if t.DenyK8sNamespacesSet.Contains(svc.Namespace) { @@ -269,7 +275,7 @@ func (t *ServiceResource) shouldSync(svc *apiv1.Service) bool { } // Ignore ClusterIP services if ClusterIP sync is disabled - if svc.Spec.Type == apiv1.ServiceTypeClusterIP && !t.ClusterIPSync { + if svc.Spec.Type == corev1.ServiceTypeClusterIP && !t.ClusterIPSync { t.Log.Debug("[shouldSync] ignoring clusterip service", "svc.Namespace", svc.Namespace, "service", svc) return false } @@ -310,9 +316,9 @@ func (t *ServiceResource) shouldTrackEndpoints(key string) bool { return false } - return svc.Spec.Type == apiv1.ServiceTypeNodePort || - svc.Spec.Type == apiv1.ServiceTypeClusterIP || - (t.LoadBalancerEndpointsSync && svc.Spec.Type == apiv1.ServiceTypeLoadBalancer) + return svc.Spec.Type == corev1.ServiceTypeNodePort || + svc.Spec.Type == corev1.ServiceTypeClusterIP || + (t.LoadBalancerEndpointsSync && svc.Spec.Type == corev1.ServiceTypeLoadBalancer) } // generateRegistrations generates the necessary Consul registrations for @@ -380,7 +386,7 @@ func (t *ServiceResource) generateRegistrations(key string) { var overridePortNumber int if len(svc.Spec.Ports) > 0 { var port int - isNodePort := svc.Spec.Type == apiv1.ServiceTypeNodePort + isNodePort := svc.Spec.Type == corev1.ServiceTypeNodePort // If a specific port is specified, then use that port value portAnnotation, ok := svc.Annotations[annotationServicePort] @@ -479,7 +485,7 @@ func (t *ServiceResource) generateRegistrations(key string) { // each LoadBalancer entry. We only support entries that have an IP // address assigned (not hostnames). // If LoadBalancerEndpointsSync is true sync LB endpoints instead of loadbalancer ingress. - case apiv1.ServiceTypeLoadBalancer: + case corev1.ServiceTypeLoadBalancer: if t.LoadBalancerEndpointsSync { t.registerServiceInstance(baseNode, baseService, key, overridePortName, overridePortNumber, false) } else { @@ -512,7 +518,7 @@ func (t *ServiceResource) generateRegistrations(key string) { // endpoint of the service, which corresponds to the nodes the service's // pods are running on. This way we don't register _every_ K8S // node as part of the service. - case apiv1.ServiceTypeNodePort: + case corev1.ServiceTypeNodePort: if t.endpointsMap == nil { return } @@ -538,11 +544,11 @@ func (t *ServiceResource) generateRegistrations(key string) { } // Set the expected node address type - var expectedType apiv1.NodeAddressType + var expectedType corev1.NodeAddressType if t.NodePortSync == InternalOnly { - expectedType = apiv1.NodeInternalIP + expectedType = corev1.NodeInternalIP } else { - expectedType = apiv1.NodeExternalIP + expectedType = corev1.NodeExternalIP } // Find the ip address for the node and @@ -571,7 +577,7 @@ func (t *ServiceResource) generateRegistrations(key string) { // use an InternalIP if t.NodePortSync == ExternalFirst && !found { for _, address := range node.Status.Addresses { - if address.Type == apiv1.NodeInternalIP { + if address.Type == corev1.NodeInternalIP { r := baseNode rs := baseService r.Service = &rs @@ -593,7 +599,7 @@ func (t *ServiceResource) generateRegistrations(key string) { // For ClusterIP services, we register a service instance // for each endpoint. - case apiv1.ServiceTypeClusterIP: + case corev1.ServiceTypeClusterIP: t.registerServiceInstance(baseNode, baseService, key, overridePortName, overridePortNumber, true) } } @@ -674,6 +680,16 @@ func (t *ServiceResource) registerServiceInstance( r.Service.Meta[ConsulK8SNodeName] = *subsetAddr.NodeName } + r.Check = &consulapi.AgentCheck{ + CheckID: consulHealthCheckID(endpoints.Namespace, serviceID(r.Service.Service, addr)), + Name: consulKubernetesCheckName, + Namespace: baseService.Namespace, + Type: consulKubernetesCheckType, + Status: consulapi.HealthPassing, + ServiceID: serviceID(r.Service.Service, addr), + Output: kubernetesSuccessReasonMsg, + } + t.consulMap[key] = append(t.consulMap[key], &r) } } @@ -723,7 +739,7 @@ func (t *serviceEndpointsResource) Informer() cache.SharedIndexInformer { Watch(t.Ctx, options) }, }, - &apiv1.Endpoints{}, + &corev1.Endpoints{}, 0, cache.Indexers{}, ) @@ -731,7 +747,7 @@ func (t *serviceEndpointsResource) Informer() cache.SharedIndexInformer { func (t *serviceEndpointsResource) Upsert(key string, raw interface{}) error { svc := t.Service - endpoints, ok := raw.(*apiv1.Endpoints) + endpoints, ok := raw.(*corev1.Endpoints) if !ok { svc.Log.Warn("upsert got invalid type", "raw", raw) return nil @@ -747,7 +763,7 @@ func (t *serviceEndpointsResource) Upsert(key string, raw interface{}) error { // We are tracking this service so let's keep track of the endpoints if svc.endpointsMap == nil { - svc.endpointsMap = make(map[string]*apiv1.Endpoints) + svc.endpointsMap = make(map[string]*corev1.Endpoints) } svc.endpointsMap[key] = endpoints @@ -788,3 +804,8 @@ func (t *ServiceResource) addPrefixAndK8SNamespace(name, namespace string) strin return name } + +// consulHealthCheckID deterministically generates a health check ID based on service ID and Kubernetes namespace. +func consulHealthCheckID(k8sNS string, serviceID string) string { + return fmt.Sprintf("%s/%s", k8sNS, serviceID) +} diff --git a/control-plane/catalog/to-consul/resource_test.go b/control-plane/catalog/to-consul/resource_test.go index 28335dea27..9ba94123ef 100644 --- a/control-plane/catalog/to-consul/resource_test.go +++ b/control-plane/catalog/to-consul/resource_test.go @@ -6,6 +6,7 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/hashicorp/consul-k8s/control-plane/helper/controller" + consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" @@ -1005,6 +1006,43 @@ func TestServiceResource_clusterIP(t *testing.T) { }) } +// Test that the proper registrations with health checks are generated for a ClusterIP type. +func TestServiceResource_clusterIP_healthCheck(t *testing.T) { + t.Parallel() + client := fake.NewSimpleClientset() + syncer := newTestSyncer() + serviceResource := defaultServiceResource(client, syncer) + serviceResource.ClusterIPSync = true + + // Start the controller + closer := controller.TestControllerRun(&serviceResource) + defer closer() + + // Insert the service + svc := clusterIPService("foo", metav1.NamespaceDefault) + _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) + require.NoError(t, err) + + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) + + // Verify what we got + retry.Run(t, func(r *retry.R) { + syncer.Lock() + defer syncer.Unlock() + actual := syncer.Registrations + require.Len(r, actual, 2) + require.Equal(r, consulKubernetesCheckName, actual[0].Check.Name) + require.Equal(r, consulapi.HealthPassing, actual[0].Check.Status) + require.Equal(r, kubernetesSuccessReasonMsg, actual[0].Check.Output) + require.Equal(r, consulKubernetesCheckType, actual[0].Check.Type) + require.Equal(r, consulKubernetesCheckName, actual[1].Check.Name) + require.Equal(r, consulapi.HealthPassing, actual[1].Check.Status) + require.Equal(r, kubernetesSuccessReasonMsg, actual[1].Check.Output) + require.Equal(r, consulKubernetesCheckType, actual[1].Check.Type) + }) +} + // Test clusterIP with prefix. func TestServiceResource_clusterIPPrefix(t *testing.T) { t.Parallel() diff --git a/control-plane/go.sum b/control-plane/go.sum index 098d32835d..7542ac12d9 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -346,12 +346,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20221220195433-629878a6879c h1:dIy67NF/J5gm0wdGxTA8dCFeQDz8TatuuMEzC7n3aDk= -github.com/hashicorp/consul/api v1.10.1-0.20221220195433-629878a6879c/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919 h1:8aVegJMSv7PIAAa1zqQQ0CT4TKv+Nf7I4rhE6+uDa1U= github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= -github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g= -github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= @@ -384,7 +380,6 @@ github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5O github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -454,7 +449,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -495,7 +489,6 @@ github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXx github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -595,7 +588,6 @@ github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKk github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= @@ -804,7 +796,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= From 7d8d4fdc0b59205d9ce687c3b378450db39ef57b Mon Sep 17 00:00:00 2001 From: Jose Ignacio Lorenzo Date: Wed, 18 Jan 2023 10:28:15 -0300 Subject: [PATCH 042/592] [CONSUL-620] Refactor Package Import --- .../subcommand/inject-connect/command.go | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 5cbd2abdee..93566387cd 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -35,7 +35,6 @@ import ( "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" - ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook" ) const WebhookCAFilename = "ca.crt" @@ -645,61 +644,61 @@ func (c *Command) Run(args []string) int { // Note: The path here should be identical to the one on the kubebuilder // annotation in each webhook file. mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", - &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceDefaults), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", - &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceResolver), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", - &ctrlwebhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ProxyDefaults), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-mesh", - &ctrlwebhook.Admission{Handler: &v1alpha1.MeshWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.MeshWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.Mesh), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-exportedservices", - &ctrlwebhook.Admission{Handler: &v1alpha1.ExportedServicesWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ExportedServicesWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ExportedServices), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", - &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceRouter), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", - &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceSplitter), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceintentions", - &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceIntentionsWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceIntentionsWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceIntentions), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-ingressgateway", - &ctrlwebhook.Admission{Handler: &v1alpha1.IngressGatewayWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.IngressGatewayWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.IngressGateway), ConsulMeta: consulMeta, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-terminatinggateway", - &ctrlwebhook.Admission{Handler: &v1alpha1.TerminatingGatewayWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.TerminatingGatewayWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), ConsulMeta: consulMeta, From 1821f08477724394b9819de04b5a43ade5b996b7 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Thu, 19 Jan 2023 11:21:44 -0500 Subject: [PATCH 043/592] When configured, use the health check of the proxy (#1841) * When a service is configured with the correct annotation, a readiness endpoint with be configured in Consul dataplane and the readiness probe of the sidecar will be configured to use that endpoint to determine the health of the system. Additionally, when t-proxy is enabled, that port shall be in the ExcludeList for inbound connections. --- CHANGELOG.md | 2 +- .../connect-inject/constants/constants.go | 3 + .../webhook/consul_dataplane_sidecar.go | 61 ++++++- .../webhook/consul_dataplane_sidecar_test.go | 154 +++++++++++++++++- .../webhook/redirect_traffic.go | 6 + .../webhook/redirect_traffic_test.go | 33 ++++ 6 files changed, 249 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b7c7dd852..d4cf05b4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ IMPROVEMENTS: * Add the `envoyExtensions` field to the `ProxyDefaults` and `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) * Add the `balanceInboundConnections` field to the `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) * Control-Plane - * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)] + * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1824)] * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] BUG FIXES: diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 62f21740c4..e371677629 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -7,6 +7,9 @@ const ( // ProxyDefaultInboundPort is the default inbound port for the proxy. ProxyDefaultInboundPort = 20000 + // ProxyDefaultHealthPort is the default HTTP health check port for the proxy. + ProxyDefaultHealthPort = 21000 + // MetaKeyKubeNS is the meta key name for Kubernetes namespace used for the Consul services. MetaKeyKubeNS = "k8s-namespace" diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index d0cd232c84..ad3333ba1b 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -47,13 +47,28 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor containerName = fmt.Sprintf("%s-%s", sidecarContainer, mpi.serviceName) } - probe := &corev1.Probe{ - Handler: corev1.Handler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(constants.ProxyDefaultInboundPort + mpi.serviceIndex), + var probe *corev1.Probe + if useProxyHealthCheck(pod) { + // If using the proxy health check for a service, configure an HTTP handler + // that queries the '/ready' endpoint of the proxy. + probe = &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(constants.ProxyDefaultHealthPort + mpi.serviceIndex), + Path: "/ready", + }, }, - }, - InitialDelaySeconds: 1, + InitialDelaySeconds: 1, + } + } else { + probe = &corev1.Probe{ + Handler: corev1.Handler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(constants.ProxyDefaultInboundPort + mpi.serviceIndex), + }, + }, + InitialDelaySeconds: 1, + } } container := corev1.Container{ @@ -89,13 +104,27 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor }, Args: args, ReadinessProbe: probe, - LivenessProbe: probe, } if w.AuthMethod != "" { container.VolumeMounts = append(container.VolumeMounts, saTokenVolumeMount) } + if useProxyHealthCheck(pod) { + // Configure the Readiness Address for the proxy's health check to be the Pod IP. + container.Env = append(container.Env, corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + }) + // Configure the port on which the readiness probe will query the proxy for its health. + container.Ports = append(container.Ports, corev1.ContainerPort{ + Name: fmt.Sprintf("%s-%d", "proxy-health", mpi.serviceIndex), + ContainerPort: int32(constants.ProxyDefaultHealthPort + mpi.serviceIndex), + }) + } + // Add any extra VolumeMounts. if userVolMount, ok := pod.Annotations[constants.AnnotationConsulSidecarUserVolumeMount]; ok { var volumeMounts []corev1.VolumeMount @@ -206,6 +235,11 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, mpi mu args = append(args, "-tls-disabled") } + // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. + if useProxyHealthCheck(pod) { + args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort+mpi.serviceIndex)) + } + if mpi.serviceName != "" { args = append(args, fmt.Sprintf("-envoy-admin-bind-port=%d", 19000+mpi.serviceIndex)) } @@ -383,3 +417,16 @@ func (w *MeshWebhook) sidecarResources(pod corev1.Pod) (corev1.ResourceRequireme return resources, nil } + +// useProxyHealthCheck returns true if the pod has the annotation 'consul.hashicorp.com/use-proxy-health-check' +// set to truthy values. +func useProxyHealthCheck(pod corev1.Pod) bool { + if v, ok := pod.Annotations[constants.AnnotationUseProxyHealthCheck]; ok { + useProxyHealthCheck, err := strconv.ParseBool(v) + if err != nil { + return false + } + return useProxyHealthCheck + } + return false +} diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index f6916bc3e2..37aa1619bf 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -214,7 +214,6 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { InitialDelaySeconds: 1, } require.Equal(t, expectedProbe, container.ReadinessProbe) - require.Equal(t, expectedProbe, container.LivenessProbe) require.Nil(t, container.StartupProbe) require.Len(t, container.Env, 3) require.Equal(t, container.Env[0].Name, "TMPDIR") @@ -308,6 +307,158 @@ func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { require.Contains(t, container.Args, "-consul-dns-bind-port=8600") } +func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck(t *testing.T) { + h := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + ConsulAddress: "1.1.1.1", + LogLevel: "info", + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationUseProxyHealthCheck: "true", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + container, err := h.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) + expectedProbe := &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + } + require.NoError(t, err) + require.Contains(t, container.Args, "-envoy-ready-bind-port=21000") + require.Equal(t, expectedProbe, container.ReadinessProbe) + require.Contains(t, container.Env, corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + }) + require.Contains(t, container.Ports, corev1.ContainerPort{ + Name: "proxy-health-0", + ContainerPort: 21000, + }) +} + +func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck_Multiport(t *testing.T) { + h := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + ConsulAddress: "1.1.1.1", + LogLevel: "info", + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Annotations: map[string]string{ + constants.AnnotationService: "web,web-admin", + constants.AnnotationUseProxyHealthCheck: "true", + }, + }, + + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "web-admin-service-account", + }, + }, + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web-side", + }, + { + Name: "web-admin", + }, + { + Name: "web-admin-side", + }, + { + Name: "auth-method-secret", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "service-account-secret", + MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + }, + }, + }, + }, + ServiceAccountName: "web", + }, + } + multiPortInfos := []multiPortInfo{ + { + serviceIndex: 0, + serviceName: "web", + }, + { + serviceIndex: 1, + serviceName: "web-admin", + }, + } + expectedArgs := []string{ + "-envoy-ready-bind-port=21000", + "-envoy-ready-bind-port=21001", + } + expectedProbe := []*corev1.Probe{ + { + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + }, + { + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21001), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + }, + } + expectedPort := []corev1.ContainerPort{ + { + Name: "proxy-health-0", + ContainerPort: 21000, + }, + { + Name: "proxy-health-1", + ContainerPort: 21001, + }, + } + expectedEnvVar := corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + } + for i, info := range multiPortInfos { + container, err := h.consulDataplaneSidecar(testNS, pod, info) + require.NoError(t, err) + require.Contains(t, container.Args, expectedArgs[i]) + require.Equal(t, expectedProbe[i], container.ReadinessProbe) + require.Contains(t, container.Ports, expectedPort[i]) + require.Contains(t, container.Env, expectedEnvVar) + } +} + func TestHandlerConsulDataplaneSidecar_Multiport(t *testing.T) { for _, aclsEnabled := range []bool{false, true} { name := fmt.Sprintf("acls enabled: %t", aclsEnabled) @@ -430,7 +581,6 @@ func TestHandlerConsulDataplaneSidecar_Multiport(t *testing.T) { InitialDelaySeconds: 1, } require.Equal(t, expectedProbe, container.ReadinessProbe) - require.Equal(t, expectedProbe, container.LivenessProbe) require.Nil(t, container.StartupProbe) } }) diff --git a/control-plane/connect-inject/webhook/redirect_traffic.go b/control-plane/connect-inject/webhook/redirect_traffic.go index 73040b39e7..eab23a2b91 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic.go +++ b/control-plane/connect-inject/webhook/redirect_traffic.go @@ -52,6 +52,12 @@ func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (s return "", err } + // Exclude the port on which the proxy health check port will be configured if + // using the proxy health check for a service. + if useProxyHealthCheck(pod) { + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(constants.ProxyDefaultHealthPort)) + } + if overwriteProbes { for i, container := range pod.Spec.Containers { // skip the "envoy-sidecar" container from having its probes overridden diff --git a/control-plane/connect-inject/webhook/redirect_traffic_test.go b/control-plane/connect-inject/webhook/redirect_traffic_test.go index 5ed660d96d..2ad9940fbe 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic_test.go +++ b/control-plane/connect-inject/webhook/redirect_traffic_test.go @@ -72,6 +72,39 @@ func TestAddRedirectTrafficConfig(t *testing.T) { ExcludeUIDs: []string{"5996"}, }, }, + { + name: "proxy health checks enabled", + webhook: MeshWebhook{ + Log: logrtest.TestLogger{T: t}, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationUseProxyHealthCheck: "true", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"5996"}, + ExcludeInboundPorts: []string{"21000"}, + }, + }, { name: "metrics enabled", webhook: MeshWebhook{ From dc0f645f92f915ef16b1ac6120f0b5a42148aa08 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Thu, 19 Jan 2023 13:11:03 -0500 Subject: [PATCH 044/592] Added first test for log command --- cli/cmd/proxy/log/command.go | 1 + cli/cmd/proxy/log/command_test.go | 51 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 cli/cmd/proxy/log/command.go create mode 100644 cli/cmd/proxy/log/command_test.go diff --git a/cli/cmd/proxy/log/command.go b/cli/cmd/proxy/log/command.go new file mode 100644 index 0000000000..7330d54052 --- /dev/null +++ b/cli/cmd/proxy/log/command.go @@ -0,0 +1 @@ +package log diff --git a/cli/cmd/proxy/log/command_test.go b/cli/cmd/proxy/log/command_test.go new file mode 100644 index 0000000000..f2970feaf8 --- /dev/null +++ b/cli/cmd/proxy/log/command_test.go @@ -0,0 +1,51 @@ +package log + +import ( + "bytes" + "context" + "io" + "os" + "testing" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" +) + +func TestFlagParsing(t *testing.T) { + testCases := map[string]struct { + args []string + out int + }{ + "No args": { + args: []string{}, + out: 1, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + c := setupCommand(bytes.NewBuffer([]byte{})) + out := c.Run(tc.args) + require.Equal(t, tc.out, out) + }) + } +} + +func setupCommand(buf io.Writer) *LogCommand { + log := hclog.New(&hclog.LoggerOptions{ + Name: "test", + Level: hclog.Debug, + Output: os.Stdout, + }) + + command := &LogCommand{ + BaseCommand: &common.BaseCommand{ + Log: log, + UI: terminal.NewUI(context.Background(), buf), + }, + } + command.init() + return command +} From 0543b9394aa7a8b8af7ebca61eccd363c2eac1de Mon Sep 17 00:00:00 2001 From: jm96441n Date: Thu, 19 Jan 2023 13:25:26 -0500 Subject: [PATCH 045/592] Added initial flag parsing --- cli/cmd/proxy/log/command.go | 62 +++++++++++++++++++++++++++++++ cli/cmd/proxy/log/command_test.go | 4 ++ 2 files changed, 66 insertions(+) diff --git a/cli/cmd/proxy/log/command.go b/cli/cmd/proxy/log/command.go index 7330d54052..4959c47eb2 100644 --- a/cli/cmd/proxy/log/command.go +++ b/cli/cmd/proxy/log/command.go @@ -1 +1,63 @@ package log + +import ( + "errors" + "strings" + "sync" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" +) + +type LogCommand struct { + *common.BaseCommand + set *flag.Sets + + // Command Flags + podName string + + once sync.Once + help string +} + +var ErrMissingPodName = errors.New("Exactly one positional argument is requied: ") + +func (l *LogCommand) init() { + l.set = flag.NewSets() +} + +func (l *LogCommand) Run(args []string) int { + l.once.Do(l.init) + l.Log.ResetNamed("log") + defer common.CloseWithError(l.BaseCommand) + + err := l.parseFlags(args) + if err != nil { + return 1 + } + return 0 +} + +func (l *LogCommand) parseFlags(args []string) error { + positional := []string{} + // Separate positional args from keyed args + for _, arg := range args { + if strings.HasPrefix(arg, "-") { + break + } + positional = append(positional, arg) + } + keyed := args[len(positional):] + + if len(positional) != 1 { + return ErrMissingPodName + } + + l.podName = positional[0] + + err := l.set.Parse(keyed) + if err != nil { + return err + } + return nil +} diff --git a/cli/cmd/proxy/log/command_test.go b/cli/cmd/proxy/log/command_test.go index f2970feaf8..fb6b57a8cb 100644 --- a/cli/cmd/proxy/log/command_test.go +++ b/cli/cmd/proxy/log/command_test.go @@ -22,6 +22,10 @@ func TestFlagParsing(t *testing.T) { args: []string{}, out: 1, }, + "With pod name": { + args: []string{"now-this-is-pod-racing"}, + out: 0, + }, } for name, tc := range testCases { From 68ac06ef6dc96a6503d95d37e27b3806f4f27215 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Thu, 19 Jan 2023 16:38:28 -0500 Subject: [PATCH 046/592] able to print and format output for getting current log levels for a proxy in a pod --- cli/cmd/proxy/log/command.go | 63 ------- cli/cmd/proxy/log/command_test.go | 55 ------ cli/cmd/proxy/loglevel/command.go | 223 +++++++++++++++++++++++++ cli/cmd/proxy/loglevel/command_test.go | 164 ++++++++++++++++++ cli/commands.go | 7 +- 5 files changed, 393 insertions(+), 119 deletions(-) delete mode 100644 cli/cmd/proxy/log/command.go delete mode 100644 cli/cmd/proxy/log/command_test.go create mode 100644 cli/cmd/proxy/loglevel/command.go create mode 100644 cli/cmd/proxy/loglevel/command_test.go diff --git a/cli/cmd/proxy/log/command.go b/cli/cmd/proxy/log/command.go deleted file mode 100644 index 4959c47eb2..0000000000 --- a/cli/cmd/proxy/log/command.go +++ /dev/null @@ -1,63 +0,0 @@ -package log - -import ( - "errors" - "strings" - "sync" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/flag" -) - -type LogCommand struct { - *common.BaseCommand - set *flag.Sets - - // Command Flags - podName string - - once sync.Once - help string -} - -var ErrMissingPodName = errors.New("Exactly one positional argument is requied: ") - -func (l *LogCommand) init() { - l.set = flag.NewSets() -} - -func (l *LogCommand) Run(args []string) int { - l.once.Do(l.init) - l.Log.ResetNamed("log") - defer common.CloseWithError(l.BaseCommand) - - err := l.parseFlags(args) - if err != nil { - return 1 - } - return 0 -} - -func (l *LogCommand) parseFlags(args []string) error { - positional := []string{} - // Separate positional args from keyed args - for _, arg := range args { - if strings.HasPrefix(arg, "-") { - break - } - positional = append(positional, arg) - } - keyed := args[len(positional):] - - if len(positional) != 1 { - return ErrMissingPodName - } - - l.podName = positional[0] - - err := l.set.Parse(keyed) - if err != nil { - return err - } - return nil -} diff --git a/cli/cmd/proxy/log/command_test.go b/cli/cmd/proxy/log/command_test.go deleted file mode 100644 index fb6b57a8cb..0000000000 --- a/cli/cmd/proxy/log/command_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package log - -import ( - "bytes" - "context" - "io" - "os" - "testing" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" -) - -func TestFlagParsing(t *testing.T) { - testCases := map[string]struct { - args []string - out int - }{ - "No args": { - args: []string{}, - out: 1, - }, - "With pod name": { - args: []string{"now-this-is-pod-racing"}, - out: 0, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - c := setupCommand(bytes.NewBuffer([]byte{})) - out := c.Run(tc.args) - require.Equal(t, tc.out, out) - }) - } -} - -func setupCommand(buf io.Writer) *LogCommand { - log := hclog.New(&hclog.LoggerOptions{ - Name: "test", - Level: hclog.Debug, - Output: os.Stdout, - }) - - command := &LogCommand{ - BaseCommand: &common.BaseCommand{ - Log: log, - UI: terminal.NewUI(context.Background(), buf), - }, - } - command.init() - return command -} diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go new file mode 100644 index 0000000000..83504e018c --- /dev/null +++ b/cli/cmd/proxy/loglevel/command.go @@ -0,0 +1,223 @@ +package loglevel + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + "strings" + "sync" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + helmCLI "helm.sh/helm/v3/pkg/cli" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +const defaultAdminPort int = 19000 + +type LoggerConfig map[string]string + +type LogCommand struct { + *common.BaseCommand + + kubernetes kubernetes.Interface + set *flag.Sets + + // Command Flags + podName string + + once sync.Once + help string + restConfig *rest.Config + logLevelFetcher func(context.Context, common.PortForwarder) (LoggerConfig, error) +} + +var ErrMissingPodName = errors.New("Exactly one positional argument is required: ") + +func (l *LogCommand) init() { + l.set = flag.NewSets() + l.help = l.set.Help() +} + +func (l *LogCommand) Run(args []string) int { + l.once.Do(l.init) + l.Log.ResetNamed("loglevel") + defer common.CloseWithError(l.BaseCommand) + + err := l.parseFlags(args) + if err != nil { + fmt.Println(err) + return 1 + } + + if l.logLevelFetcher == nil { + l.logLevelFetcher = FetchLogLevel + } + + err = l.initKubernetes() + if err != nil { + fmt.Println(err) + return 1 + } + + adminPorts, err := l.fetchAdminPorts() + if err != nil { + fmt.Println(err) + return 1 + } + + logLevels, err := l.fetchLogLevels(adminPorts) + if err != nil { + fmt.Println(err) + return 1 + } + l.outputLevels(logLevels) + return 0 +} + +func (l *LogCommand) parseFlags(args []string) error { + positional := []string{} + // Separate positional args from keyed args + for _, arg := range args { + if strings.HasPrefix(arg, "-") { + break + } + positional = append(positional, arg) + } + keyed := args[len(positional):] + + if len(positional) != 1 { + return ErrMissingPodName + } + + l.podName = positional[0] + + err := l.set.Parse(keyed) + if err != nil { + return err + } + return nil +} + +func (l *LogCommand) initKubernetes() error { + settings := helmCLI.New() + var err error + + if l.restConfig == nil { + l.restConfig, err = settings.RESTClientGetter().ToRESTConfig() + if err != nil { + return fmt.Errorf("error creating Kubernetes REST config %v", err) + } + + } + + if l.kubernetes == nil { + l.kubernetes, err = kubernetes.NewForConfig(l.restConfig) + if err != nil { + return fmt.Errorf("error creating Kubernetes client %v", err) + } + } + return nil +} + +func (l *LogCommand) fetchAdminPorts() (map[string]int, error) { + adminPorts := make(map[string]int, 0) + // TODO: support different namespaces + pod, err := l.kubernetes.CoreV1().Pods("default").Get(l.Ctx, l.podName, metav1.GetOptions{}) + if err != nil { + return adminPorts, err + } + + connectService, isMultiport := pod.Annotations["consul.hashicorp.com/connect-service"] + + if !isMultiport { + // Return the default port configuration. + adminPorts[l.podName] = defaultAdminPort + return adminPorts, nil + } + + for idx, svc := range strings.Split(connectService, ",") { + adminPorts[svc] = defaultAdminPort + idx + } + + return adminPorts, nil +} + +func (l *LogCommand) fetchLogLevels(adminPorts map[string]int) (map[string]LoggerConfig, error) { + loggers := make(map[string]LoggerConfig, 0) + + for name, port := range adminPorts { + pf := common.PortForward{ + Namespace: "default", // TODO: change this to use the configurable namespace + PodName: l.podName, + RemotePort: port, + KubeClient: l.kubernetes, + RestConfig: l.restConfig, + } + + logLevels, err := l.logLevelFetcher(l.Ctx, &pf) + if err != nil { + return loggers, err + } + loggers[name] = logLevels + } + return loggers, nil +} + +func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (LoggerConfig, error) { + endpoint, err := portForward.Open(ctx) + if err != nil { + return nil, err + } + + defer portForward.Close() + + response, err := http.Post(fmt.Sprintf("http://%s/logging", endpoint), "application/json", bytes.NewBuffer([]byte{})) + if err != nil { + return nil, err + } + body, err := io.ReadAll(response.Body) + loggers := strings.Split(string(body), "\n") + logLevels := make(map[string]string) + var name string + var level string + + // the first line here is just a header + for _, logger := range loggers[1:] { + if len(logger) == 0 { + continue + } + fmt.Sscanf(logger, "%s %s", &name, &level) + name = strings.TrimRight(name, ":") + logLevels[name] = level + } + return logLevels, nil +} + +func (l *LogCommand) Help() string { + l.once.Do(l.init) + return fmt.Sprintf("%s\n\nUsage: consul-k8s proxy log [flags]\n\n%s", l.Synopsis(), l.help) +} + +func (l *LogCommand) Synopsis() string { + return "Inspect and Modify the Envoy Log configuration for a given Pod." +} + +func (l *LogCommand) outputLevels(logLevels map[string]LoggerConfig) { + l.UI.Output(fmt.Sprintf("Envoy log configuration for %s in namespace default:", l.podName)) + for n, levels := range logLevels { + l.UI.Output(fmt.Sprintf("Log Levels for %s", n), terminal.WithHeaderStyle()) + table := terminal.NewTable("Name", "Level") + for name, level := range levels { + table.AddRow([]string{name, level}, []string{}) + } + l.UI.Table(table) + l.UI.Output("") + } +} diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go new file mode 100644 index 0000000000..1cb575ba7c --- /dev/null +++ b/cli/cmd/proxy/loglevel/command_test.go @@ -0,0 +1,164 @@ +package loglevel + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "regexp" + "testing" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestFlagParsing(t *testing.T) { + t.Parallel() + testCases := map[string]struct { + args []string + out int + }{ + "No args": { + args: []string{}, + out: 1, + }, + "With pod name": { + args: []string{"now-this-is-pod-racing"}, + out: 0, + }, + } + podName := "now-this-is-pod-racing" + fakePod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: "default", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + c := setupCommand(bytes.NewBuffer([]byte{})) + c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) + c.logLevelFetcher = func(context.Context, common.PortForwarder) (LoggerConfig, error) { + return testLogConfig, nil + } + + out := c.Run(tc.args) + require.Equal(t, tc.out, out) + }) + } +} + +func TestOutputForGettingLogLevel(t *testing.T) { + t.Parallel() + podName := "now-this-is-pod-racing" + expectedHeader := fmt.Sprintf("Envoy log configuration for %s in namespace default:", podName) + fakePod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: "default", + }, + } + + buf := bytes.NewBuffer([]byte{}) + c := setupCommand(buf) + c.logLevelFetcher = func(context.Context, common.PortForwarder) (LoggerConfig, error) { + return testLogConfig, nil + } + c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) + + args := []string{podName} + out := c.Run(args) + require.Equal(t, 0, out) + + actual := buf.String() + + require.Regexp(t, expectedHeader, actual) + require.Regexp(t, "Log Levels for now-this-is-pod-racing", actual) + for logger, level := range testLogConfig { + require.Regexp(t, regexp.MustCompile(logger+`\s*`+level), actual) + } +} + +var testLogConfig = LoggerConfig{ + "admin": "debug", + "alternate_protocols_cache": "debug", + "aws": "debug", + "assert": "debug", + "backtrace": "debug", + "cache_filter": "debug", + "client": "debug", + "config": "debug", + "connection": "debug", + "conn_handler": "debug", + "decompression": "debug", + "dns": "debug", + "dubbo": "debug", + "envoy_bug": "debug", + "ext_authz": "debug", + "ext_proc": "debug", + "rocketmq": "debug", + "file": "debug", + "filter": "debug", + "forward_proxy": "debug", + "grpc": "debug", + "happy_eyeballs": "debug", + "hc": "debug", + "health_checker": "debug", + "http": "debug", + "http2": "debug", + "hystrix": "debug", + "init": "debug", + "io": "debug", + "jwt": "debug", + "kafka": "debug", + "key_value_store": "debug", + "lua": "debug", + "main": "debug", + "matcher": "debug", + "misc": "debug", + "mongo": "debug", + "multi_connection": "debug", + "oauth2": "debug", + "quic": "debug", + "quic_stream": "debug", + "pool": "debug", + "rbac": "debug", + "rds": "debug", + "redis": "debug", + "router": "debug", + "runtime": "debug", + "stats": "debug", + "secret": "debug", + "tap": "debug", + "testing": "debug", + "thrift": "debug", + "tracing": "debug", + "upstream": "debug", + "udp": "debug", + "wasm": "debug", + "websocket": "debug", +} + +func setupCommand(buf io.Writer) *LogCommand { + log := hclog.New(&hclog.LoggerOptions{ + Name: "test", + Level: hclog.Debug, + Output: os.Stdout, + }) + + command := &LogCommand{ + BaseCommand: &common.BaseCommand{ + Log: log, + UI: terminal.NewUI(context.Background(), buf), + }, + } + command.init() + return command +} diff --git a/cli/commands.go b/cli/commands.go index d29b4a8ad7..dc132eef9a 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -6,6 +6,7 @@ import ( "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/status" "github.com/hashicorp/consul-k8s/cli/cmd/uninstall" @@ -19,7 +20,6 @@ import ( ) func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseCommand, map[string]cli.CommandFactory) { - baseCommand := &common.BaseCommand{ Ctx: ctx, Log: log, @@ -68,6 +68,11 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm BaseCommand: baseCommand, }, nil }, + "proxy log": func() (cli.Command, error) { + return &loglevel.LogCommand{ + BaseCommand: baseCommand, + }, nil + }, } return baseCommand, commands From 27d148893d63d6be436453850c4ea684feef8ac4 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Thu, 19 Jan 2023 17:05:37 -0500 Subject: [PATCH 047/592] Add test for FetchLogLevel --- cli/cmd/proxy/loglevel/command_test.go | 39 ++++++++++++ .../loglevel/testdata/fetch_debug_levels.txt | 59 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 cli/cmd/proxy/loglevel/testdata/fetch_debug_levels.txt diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go index 1cb575ba7c..10f444f74e 100644 --- a/cli/cmd/proxy/loglevel/command_test.go +++ b/cli/cmd/proxy/loglevel/command_test.go @@ -5,8 +5,11 @@ import ( "context" "fmt" "io" + "net/http" + "net/http/httptest" "os" "regexp" + "strings" "testing" "github.com/hashicorp/consul-k8s/cli/common" @@ -86,6 +89,42 @@ func TestOutputForGettingLogLevel(t *testing.T) { } } +func TestHelp(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + c := setupCommand(buf) + expectedSynposis := "Inspect and Modify the Envoy Log configuration for a given Pod." + expectedUsage := `Usage: consul-k8s proxy log \[flags\]` + actual := c.Help() + require.Regexp(t, expectedSynposis, actual) + require.Regexp(t, expectedUsage, actual) +} + +func TestFetchLogLevel(t *testing.T) { + rawLogLevels, err := os.ReadFile("testdata/fetch_debug_levels.txt") + require.NoError(t, err) + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(rawLogLevels) + })) + + defer mockServer.Close() + + mpf := &mockPortForwarder{ + openBehavior: func(ctx context.Context) (string, error) { + return strings.Replace(mockServer.URL, "http://", "", 1), nil + }, + } + logLevels, err := FetchLogLevel(context.Background(), mpf) + require.NoError(t, err) + require.Equal(t, testLogConfig, logLevels) +} + +type mockPortForwarder struct { + openBehavior func(context.Context) (string, error) +} + +func (m *mockPortForwarder) Open(ctx context.Context) (string, error) { return m.openBehavior(ctx) } +func (m *mockPortForwarder) Close() {} + var testLogConfig = LoggerConfig{ "admin": "debug", "alternate_protocols_cache": "debug", diff --git a/cli/cmd/proxy/loglevel/testdata/fetch_debug_levels.txt b/cli/cmd/proxy/loglevel/testdata/fetch_debug_levels.txt new file mode 100644 index 0000000000..6b059dc1aa --- /dev/null +++ b/cli/cmd/proxy/loglevel/testdata/fetch_debug_levels.txt @@ -0,0 +1,59 @@ +active loggers: + admin: debug + alternate_protocols_cache: debug + aws: debug + assert: debug + backtrace: debug + cache_filter: debug + client: debug + config: debug + connection: debug + conn_handler: debug + decompression: debug + dns: debug + dubbo: debug + envoy_bug: debug + ext_authz: debug + ext_proc: debug + rocketmq: debug + file: debug + filter: debug + forward_proxy: debug + grpc: debug + happy_eyeballs: debug + hc: debug + health_checker: debug + http: debug + http2: debug + hystrix: debug + init: debug + io: debug + jwt: debug + kafka: debug + key_value_store: debug + lua: debug + main: debug + matcher: debug + misc: debug + mongo: debug + multi_connection: debug + oauth2: debug + quic: debug + quic_stream: debug + pool: debug + rbac: debug + rds: debug + redis: debug + router: debug + runtime: debug + stats: debug + secret: debug + tap: debug + testing: debug + thrift: debug + tracing: debug + upstream: debug + udp: debug + wasm: debug + websocket: debug + From 98cde9b6c805f7d4795058f1bf34862244ae4b09 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Fri, 20 Jan 2023 10:55:58 -0500 Subject: [PATCH 048/592] Add color to log level output to help differentiate levels --- cli/cmd/proxy/loglevel/command.go | 16 +++++++++++++--- cli/cmd/proxy/loglevel/command_test.go | 2 +- cli/common/terminal/table.go | 18 ++++++++++++------ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go index 83504e018c..f80eff08cc 100644 --- a/cli/cmd/proxy/loglevel/command.go +++ b/cli/cmd/proxy/loglevel/command.go @@ -23,6 +23,18 @@ const defaultAdminPort int = 19000 type LoggerConfig map[string]string +var ErrMissingPodName = errors.New("Exactly one positional argument is required: ") + +var levelToColor = map[string]string{ + "trace": terminal.Green, + "debug": terminal.HiWhite, + "info": terminal.Blue, + "warning": terminal.Yellow, + "error": terminal.Red, + "critical": terminal.Magenta, + "off": "", +} + type LogCommand struct { *common.BaseCommand @@ -38,8 +50,6 @@ type LogCommand struct { logLevelFetcher func(context.Context, common.PortForwarder) (LoggerConfig, error) } -var ErrMissingPodName = errors.New("Exactly one positional argument is required: ") - func (l *LogCommand) init() { l.set = flag.NewSets() l.help = l.set.Help() @@ -215,7 +225,7 @@ func (l *LogCommand) outputLevels(logLevels map[string]LoggerConfig) { l.UI.Output(fmt.Sprintf("Log Levels for %s", n), terminal.WithHeaderStyle()) table := terminal.NewTable("Name", "Level") for name, level := range levels { - table.AddRow([]string{name, level}, []string{}) + table.AddRow([]string{name, level}, []string{"", levelToColor[level]}) } l.UI.Table(table) l.UI.Output("") diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go index 10f444f74e..f35ec6b715 100644 --- a/cli/cmd/proxy/loglevel/command_test.go +++ b/cli/cmd/proxy/loglevel/command_test.go @@ -85,7 +85,7 @@ func TestOutputForGettingLogLevel(t *testing.T) { require.Regexp(t, expectedHeader, actual) require.Regexp(t, "Log Levels for now-this-is-pod-racing", actual) for logger, level := range testLogConfig { - require.Regexp(t, regexp.MustCompile(logger+`\s*`+level), actual) + require.Regexp(t, regexp.MustCompile(logger+`.*`+level), actual) } } diff --git a/cli/common/terminal/table.go b/cli/common/terminal/table.go index 67278931a4..ac9a762609 100644 --- a/cli/common/terminal/table.go +++ b/cli/common/terminal/table.go @@ -5,15 +5,21 @@ import ( ) const ( - Yellow = "yellow" - Green = "green" - Red = "red" + Yellow = "yellow" + Green = "green" + Red = "red" + Blue = "blue" + Magenta = "magenta" + HiWhite = "hiwhite" ) var colorMapping = map[string]int{ - Green: tablewriter.FgGreenColor, - Yellow: tablewriter.FgYellowColor, - Red: tablewriter.FgRedColor, + Green: tablewriter.FgGreenColor, + Yellow: tablewriter.FgYellowColor, + Red: tablewriter.FgRedColor, + Blue: tablewriter.FgBlueColor, + Magenta: tablewriter.FgMagentaColor, + HiWhite: tablewriter.FgHiWhiteColor, } // Passed to UI.Table to provide a nicely formatted table. From 2dfc6c9ff54d9777f6d5a6f2a84b3bedbfa81f11 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Fri, 20 Jan 2023 11:45:03 -0500 Subject: [PATCH 049/592] Added args for kubeconfig, kubecontext, and namespace, added comments, changed command name to `LogLevelCommand` --- cli/cmd/proxy/loglevel/command.go | 136 ++++++++++++++++++++----- cli/cmd/proxy/loglevel/command_test.go | 57 ++++++++++- cli/commands.go | 2 +- 3 files changed, 163 insertions(+), 32 deletions(-) diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go index f80eff08cc..c622955bb4 100644 --- a/cli/cmd/proxy/loglevel/command.go +++ b/cli/cmd/proxy/loglevel/command.go @@ -13,13 +13,20 @@ import ( "github.com/hashicorp/consul-k8s/cli/common" "github.com/hashicorp/consul-k8s/cli/common/flag" "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/posener/complete" helmCLI "helm.sh/helm/v3/pkg/cli" + "k8s.io/apimachinery/pkg/api/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) -const defaultAdminPort int = 19000 +const ( + defaultAdminPort int = 19000 + flagNameNamespace = "namespace" + flagNameKubeConfig = "kubeconfig" + flagNameKubeContext = "context" +) type LoggerConfig map[string]string @@ -35,14 +42,17 @@ var levelToColor = map[string]string{ "off": "", } -type LogCommand struct { +type LogLevelCommand struct { *common.BaseCommand kubernetes kubernetes.Interface set *flag.Sets // Command Flags - podName string + podName string + namespace string + kubeConfig string + kubeContext string once sync.Once help string @@ -50,19 +60,48 @@ type LogCommand struct { logLevelFetcher func(context.Context, common.PortForwarder) (LoggerConfig, error) } -func (l *LogCommand) init() { +func (l *LogLevelCommand) init() { l.set = flag.NewSets() + f := l.set.NewSet("Command Options") + f.StringVar(&flag.StringVar{ + Name: flagNameNamespace, + Target: &l.namespace, + Usage: "The namespace where the target Pod can be found.", + Aliases: []string{"n"}, + }) + + f = l.set.NewSet("Global Options") + f.StringVar(&flag.StringVar{ + Name: flagNameKubeConfig, + Aliases: []string{"c"}, + Target: &l.kubeConfig, + Usage: "Set the path to kubeconfig file.", + }) + f.StringVar(&flag.StringVar{ + Name: flagNameKubeContext, + Target: &l.kubeContext, + Usage: "Set the Kubernetes context to use.", + }) + l.help = l.set.Help() } -func (l *LogCommand) Run(args []string) int { +func (l *LogLevelCommand) Run(args []string) int { l.once.Do(l.init) l.Log.ResetNamed("loglevel") defer common.CloseWithError(l.BaseCommand) err := l.parseFlags(args) if err != nil { - fmt.Println(err) + l.UI.Output(err.Error(), terminal.WithErrorStyle()) + l.UI.Output(fmt.Sprintf("\n%s", l.Help())) + return 1 + } + + err = l.validateFlags() + if err != nil { + l.UI.Output(err.Error(), terminal.WithErrorStyle()) + l.UI.Output(fmt.Sprintf("\n%s", l.Help())) return 1 } @@ -72,26 +111,29 @@ func (l *LogCommand) Run(args []string) int { err = l.initKubernetes() if err != nil { - fmt.Println(err) + l.UI.Output(err.Error(), terminal.WithErrorStyle()) + l.UI.Output(fmt.Sprintf("\n%s", l.Help())) return 1 } adminPorts, err := l.fetchAdminPorts() if err != nil { - fmt.Println(err) + l.UI.Output(err.Error(), terminal.WithErrorStyle()) + l.UI.Output(fmt.Sprintf("\n%s", l.Help())) return 1 } logLevels, err := l.fetchLogLevels(adminPorts) if err != nil { - fmt.Println(err) + l.UI.Output(err.Error(), terminal.WithErrorStyle()) + l.UI.Output(fmt.Sprintf("\n%s", l.Help())) return 1 } l.outputLevels(logLevels) return 0 } -func (l *LogCommand) parseFlags(args []string) error { +func (l *LogLevelCommand) parseFlags(args []string) error { positional := []string{} // Separate positional args from keyed args for _, arg := range args { @@ -115,10 +157,25 @@ func (l *LogCommand) parseFlags(args []string) error { return nil } -func (l *LogCommand) initKubernetes() error { +func (l *LogLevelCommand) validateFlags() error { + if errs := validation.ValidateNamespaceName(l.namespace, false); l.namespace != "" && len(errs) > 0 { + return fmt.Errorf("invalid namespace name passed for -namespace/-n: %v", strings.Join(errs, "; ")) + } + return nil +} + +func (l *LogLevelCommand) initKubernetes() error { settings := helmCLI.New() var err error + if l.kubeConfig != "" { + settings.KubeConfig = l.kubeConfig + } + + if l.kubeContext != "" { + settings.KubeContext = l.kubeContext + } + if l.restConfig == nil { l.restConfig, err = settings.RESTClientGetter().ToRESTConfig() if err != nil { @@ -133,13 +190,17 @@ func (l *LogCommand) initKubernetes() error { return fmt.Errorf("error creating Kubernetes client %v", err) } } + if l.namespace == "" { + l.namespace = settings.Namespace() + } + return nil } -func (l *LogCommand) fetchAdminPorts() (map[string]int, error) { +// fetchAdminPorts retrieves all admin ports for Envoy Proxies running in a pod given namespace +func (l *LogLevelCommand) fetchAdminPorts() (map[string]int, error) { adminPorts := make(map[string]int, 0) - // TODO: support different namespaces - pod, err := l.kubernetes.CoreV1().Pods("default").Get(l.Ctx, l.podName, metav1.GetOptions{}) + pod, err := l.kubernetes.CoreV1().Pods(l.namespace).Get(l.Ctx, l.podName, metav1.GetOptions{}) if err != nil { return adminPorts, err } @@ -159,12 +220,12 @@ func (l *LogCommand) fetchAdminPorts() (map[string]int, error) { return adminPorts, nil } -func (l *LogCommand) fetchLogLevels(adminPorts map[string]int) (map[string]LoggerConfig, error) { +func (l *LogLevelCommand) fetchLogLevels(adminPorts map[string]int) (map[string]LoggerConfig, error) { loggers := make(map[string]LoggerConfig, 0) for name, port := range adminPorts { pf := common.PortForward{ - Namespace: "default", // TODO: change this to use the configurable namespace + Namespace: l.namespace, PodName: l.podName, RemotePort: port, KubeClient: l.kubernetes, @@ -180,6 +241,8 @@ func (l *LogCommand) fetchLogLevels(adminPorts map[string]int) (map[string]Logge return loggers, nil } +// FetchLogLevel requests the logging endpoint from Envoy Admin Interface for a given port +// more can be read about that endpoint https://www.envoyproxy.io/docs/envoy/latest/operations/admin#post--logging func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (LoggerConfig, error) { endpoint, err := portForward.Open(ctx) if err != nil { @@ -188,7 +251,8 @@ func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (Logge defer portForward.Close() - response, err := http.Post(fmt.Sprintf("http://%s/logging", endpoint), "application/json", bytes.NewBuffer([]byte{})) + // this endpoint does not support returning json, so we've gotta parse the plain text + response, err := http.Post(fmt.Sprintf("http://%s/logging", endpoint), "text/plain", bytes.NewBuffer([]byte{})) if err != nil { return nil, err } @@ -210,16 +274,7 @@ func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (Logge return logLevels, nil } -func (l *LogCommand) Help() string { - l.once.Do(l.init) - return fmt.Sprintf("%s\n\nUsage: consul-k8s proxy log [flags]\n\n%s", l.Synopsis(), l.help) -} - -func (l *LogCommand) Synopsis() string { - return "Inspect and Modify the Envoy Log configuration for a given Pod." -} - -func (l *LogCommand) outputLevels(logLevels map[string]LoggerConfig) { +func (l *LogLevelCommand) outputLevels(logLevels map[string]LoggerConfig) { l.UI.Output(fmt.Sprintf("Envoy log configuration for %s in namespace default:", l.podName)) for n, levels := range logLevels { l.UI.Output(fmt.Sprintf("Log Levels for %s", n), terminal.WithHeaderStyle()) @@ -231,3 +286,30 @@ func (l *LogCommand) outputLevels(logLevels map[string]LoggerConfig) { l.UI.Output("") } } + +func (l *LogLevelCommand) Help() string { + l.once.Do(l.init) + return fmt.Sprintf("%s\n\nUsage: consul-k8s proxy log [flags]\n\n%s", l.Synopsis(), l.help) +} + +func (l *LogLevelCommand) Synopsis() string { + return "Inspect and Modify the Envoy Log configuration for a given Pod." +} + +// AutocompleteFlags returns a mapping of supported flags and autocomplete +// options for this command. The map key for the Flags map should be the +// complete flag such as "-foo" or "--foo". +func (l *LogLevelCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + fmt.Sprintf("-%s", flagNameNamespace): complete.PredictNothing, + fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), + fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, + } +} + +// AutocompleteArgs returns the argument predictor for this command. +// Since argument completion is not supported, this will return +// complete.PredictNothing. +func (l *LogLevelCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go index f35ec6b715..9206ea1ba6 100644 --- a/cli/cmd/proxy/loglevel/command_test.go +++ b/cli/cmd/proxy/loglevel/command_test.go @@ -21,7 +21,7 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -func TestFlagParsing(t *testing.T) { +func TestFlagParsingFails(t *testing.T) { t.Parallel() testCases := map[string]struct { args []string @@ -31,9 +31,17 @@ func TestFlagParsing(t *testing.T) { args: []string{}, out: 1, }, - "With pod name": { - args: []string{"now-this-is-pod-racing"}, - out: 0, + "Multiple podnames passed": { + args: []string{"podname", "podname2"}, + out: 1, + }, + "Nonexistent flag passed, -foo bar": { + args: []string{"podName", "-foo", "bar"}, + out: 1, + }, + "Invalid argument passed, -namespace YOLO": { + args: []string{"podName", "-namespace", "YOLO"}, + out: 1, }, } podName := "now-this-is-pod-racing" @@ -58,6 +66,47 @@ func TestFlagParsing(t *testing.T) { } } +func TestFlagParsingSucceeds(t *testing.T) { + t.Parallel() + testCases := map[string]struct { + args []string + podNamespace string + out int + }{ + "With single pod name": { + args: []string{"now-this-is-pod-racing"}, + podNamespace: "default", + out: 0, + }, + "With single pod name and namespace": { + args: []string{"now-this-is-pod-racing", "-n", "another"}, + podNamespace: "another", + out: 0, + }, + } + podName := "now-this-is-pod-racing" + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + fakePod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: tc.podNamespace, + }, + } + + c := setupCommand(bytes.NewBuffer([]byte{})) + c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) + c.logLevelFetcher = func(context.Context, common.PortForwarder) (LoggerConfig, error) { + return testLogConfig, nil + } + + out := c.Run(tc.args) + require.Equal(t, tc.out, out) + }) + } +} + func TestOutputForGettingLogLevel(t *testing.T) { t.Parallel() podName := "now-this-is-pod-racing" diff --git a/cli/commands.go b/cli/commands.go index dc132eef9a..2946873e51 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -69,7 +69,7 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm }, nil }, "proxy log": func() (cli.Command, error) { - return &loglevel.LogCommand{ + return &loglevel.LogLevelCommand{ BaseCommand: baseCommand, }, nil }, From bcedf88b368d79df7d2daff8433a9b03382f6e01 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Fri, 20 Jan 2023 11:52:37 -0500 Subject: [PATCH 050/592] Clean up, ran linter --- cli/cmd/proxy/loglevel/command.go | 5 ++++- cli/cmd/proxy/loglevel/command_test.go | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go index c622955bb4..4b4ea2d347 100644 --- a/cli/cmd/proxy/loglevel/command.go +++ b/cli/cmd/proxy/loglevel/command.go @@ -197,7 +197,7 @@ func (l *LogLevelCommand) initKubernetes() error { return nil } -// fetchAdminPorts retrieves all admin ports for Envoy Proxies running in a pod given namespace +// fetchAdminPorts retrieves all admin ports for Envoy Proxies running in a pod given namespace. func (l *LogLevelCommand) fetchAdminPorts() (map[string]int, error) { adminPorts := make(map[string]int, 0) pod, err := l.kubernetes.CoreV1().Pods(l.namespace).Get(l.Ctx, l.podName, metav1.GetOptions{}) @@ -257,6 +257,9 @@ func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (Logge return nil, err } body, err := io.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("failed to reach envoy: %v", err) + } loggers := strings.Split(string(body), "\n") logLevels := make(map[string]string) var name string diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go index 9206ea1ba6..083416d836 100644 --- a/cli/cmd/proxy/loglevel/command_test.go +++ b/cli/cmd/proxy/loglevel/command_test.go @@ -234,14 +234,14 @@ var testLogConfig = LoggerConfig{ "websocket": "debug", } -func setupCommand(buf io.Writer) *LogCommand { +func setupCommand(buf io.Writer) *LogLevelCommand { log := hclog.New(&hclog.LoggerOptions{ Name: "test", Level: hclog.Debug, Output: os.Stdout, }) - command := &LogCommand{ + command := &LogLevelCommand{ BaseCommand: &common.BaseCommand{ Log: log, UI: terminal.NewUI(context.Background(), buf), From 0706360c1f23e02ad17799b9252a3bf2902b7788 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Fri, 20 Jan 2023 12:03:26 -0500 Subject: [PATCH 051/592] Clean up variable usage in test --- cli/cmd/proxy/loglevel/command_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go index 083416d836..c79eba9726 100644 --- a/cli/cmd/proxy/loglevel/command_test.go +++ b/cli/cmd/proxy/loglevel/command_test.go @@ -68,23 +68,23 @@ func TestFlagParsingFails(t *testing.T) { func TestFlagParsingSucceeds(t *testing.T) { t.Parallel() + podName := "now-this-is-pod-racing" testCases := map[string]struct { args []string podNamespace string out int }{ "With single pod name": { - args: []string{"now-this-is-pod-racing"}, + args: []string{podName}, podNamespace: "default", out: 0, }, "With single pod name and namespace": { - args: []string{"now-this-is-pod-racing", "-n", "another"}, + args: []string{podName, "-n", "another"}, podNamespace: "another", out: 0, }, } - podName := "now-this-is-pod-racing" for name, tc := range testCases { t.Run(name, func(t *testing.T) { From 1f877511b1338c462eae9fd549a18d446df74b3f Mon Sep 17 00:00:00 2001 From: jm96441n Date: Fri, 20 Jan 2023 13:05:23 -0500 Subject: [PATCH 052/592] Removed unnecessary variable type --- cli/cmd/proxy/loglevel/command.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go index 4b4ea2d347..0b4e7fb5df 100644 --- a/cli/cmd/proxy/loglevel/command.go +++ b/cli/cmd/proxy/loglevel/command.go @@ -22,10 +22,10 @@ import ( ) const ( - defaultAdminPort int = 19000 - flagNameNamespace = "namespace" - flagNameKubeConfig = "kubeconfig" - flagNameKubeContext = "context" + defaultAdminPort = 19000 + flagNameNamespace = "namespace" + flagNameKubeConfig = "kubeconfig" + flagNameKubeContext = "context" ) type LoggerConfig map[string]string From ee55c0167e11ae957e54c42df1798f4da4f7b7ac Mon Sep 17 00:00:00 2001 From: jm96441n Date: Fri, 20 Jan 2023 17:24:37 -0500 Subject: [PATCH 053/592] PR feedback, DRY-ing up some error handling logic, cleaning up validation, check bounds of returned slice for envoy parsing, check status code of response from envoy --- cli/cmd/proxy/loglevel/command.go | 57 +++++++++++++++++--------- cli/cmd/proxy/loglevel/command_test.go | 2 + 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go index 0b4e7fb5df..aa47d6c391 100644 --- a/cli/cmd/proxy/loglevel/command.go +++ b/cli/cmd/proxy/loglevel/command.go @@ -30,7 +30,10 @@ const ( type LoggerConfig map[string]string -var ErrMissingPodName = errors.New("Exactly one positional argument is required: ") +var ( + ErrIncorrectArgFormat = errors.New("Exactly one positional argument is required: ") + ErrNoLoggersReturned = errors.New("No loggers were returned from Envoy") +) var levelToColor = map[string]string{ "trace": terminal.Green, @@ -93,16 +96,11 @@ func (l *LogLevelCommand) Run(args []string) int { err := l.parseFlags(args) if err != nil { - l.UI.Output(err.Error(), terminal.WithErrorStyle()) - l.UI.Output(fmt.Sprintf("\n%s", l.Help())) - return 1 + return l.logOutputAndDie(err) } - err = l.validateFlags() if err != nil { - l.UI.Output(err.Error(), terminal.WithErrorStyle()) - l.UI.Output(fmt.Sprintf("\n%s", l.Help())) - return 1 + return l.logOutputAndDie(err) } if l.logLevelFetcher == nil { @@ -111,29 +109,27 @@ func (l *LogLevelCommand) Run(args []string) int { err = l.initKubernetes() if err != nil { - l.UI.Output(err.Error(), terminal.WithErrorStyle()) - l.UI.Output(fmt.Sprintf("\n%s", l.Help())) - return 1 + return l.logOutputAndDie(err) } adminPorts, err := l.fetchAdminPorts() if err != nil { - l.UI.Output(err.Error(), terminal.WithErrorStyle()) - l.UI.Output(fmt.Sprintf("\n%s", l.Help())) - return 1 + return l.logOutputAndDie(err) } logLevels, err := l.fetchLogLevels(adminPorts) if err != nil { - l.UI.Output(err.Error(), terminal.WithErrorStyle()) - l.UI.Output(fmt.Sprintf("\n%s", l.Help())) - return 1 + return l.logOutputAndDie(err) } l.outputLevels(logLevels) return 0 } func (l *LogLevelCommand) parseFlags(args []string) error { + if len(args) == 0 { + return ErrIncorrectArgFormat + } + positional := []string{} // Separate positional args from keyed args for _, arg := range args { @@ -145,7 +141,7 @@ func (l *LogLevelCommand) parseFlags(args []string) error { keyed := args[len(positional):] if len(positional) != 1 { - return ErrMissingPodName + return ErrIncorrectArgFormat } l.podName = positional[0] @@ -158,9 +154,15 @@ func (l *LogLevelCommand) parseFlags(args []string) error { } func (l *LogLevelCommand) validateFlags() error { - if errs := validation.ValidateNamespaceName(l.namespace, false); l.namespace != "" && len(errs) > 0 { + if l.namespace == "" { + return nil + } + + errs := validation.ValidateNamespaceName(l.namespace, false) + if len(errs) > 0 { return fmt.Errorf("invalid namespace name passed for -namespace/-n: %v", strings.Join(errs, "; ")) } + return nil } @@ -256,11 +258,21 @@ func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (Logge if err != nil { return nil, err } + body, err := io.ReadAll(response.Body) if err != nil { return nil, fmt.Errorf("failed to reach envoy: %v", err) } + + if response.StatusCode >= 400 { + return nil, fmt.Errorf("call to envoy failed with status code: %d, and message: %s", response.StatusCode, body) + } + loggers := strings.Split(string(body), "\n") + if len(loggers) == 0 { + return nil, ErrNoLoggersReturned + } + logLevels := make(map[string]string) var name string var level string @@ -274,6 +286,7 @@ func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (Logge name = strings.TrimRight(name, ":") logLevels[name] = level } + return logLevels, nil } @@ -316,3 +329,9 @@ func (l *LogLevelCommand) AutocompleteFlags() complete.Flags { func (l *LogLevelCommand) AutocompleteArgs() complete.Predictor { return complete.PredictNothing } + +func (l *LogLevelCommand) logOutputAndDie(err error) int { + l.UI.Output(err.Error(), terminal.WithErrorStyle()) + l.UI.Output(fmt.Sprintf("\n%s", l.Help())) + return 1 +} diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go index c79eba9726..6207270c20 100644 --- a/cli/cmd/proxy/loglevel/command_test.go +++ b/cli/cmd/proxy/loglevel/command_test.go @@ -139,6 +139,7 @@ func TestOutputForGettingLogLevel(t *testing.T) { } func TestHelp(t *testing.T) { + t.Parallel() buf := bytes.NewBuffer([]byte{}) c := setupCommand(buf) expectedSynposis := "Inspect and Modify the Envoy Log configuration for a given Pod." @@ -149,6 +150,7 @@ func TestHelp(t *testing.T) { } func TestFetchLogLevel(t *testing.T) { + t.Parallel() rawLogLevels, err := os.ReadFile("testdata/fetch_debug_levels.txt") require.NoError(t, err) mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { From 7425f125b475543321fceb864d86b5dcce0a0d1c Mon Sep 17 00:00:00 2001 From: jm96441n Date: Fri, 20 Jan 2023 17:27:15 -0500 Subject: [PATCH 054/592] Move log name setting to the init function --- cli/cmd/proxy/loglevel/command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go index aa47d6c391..80444d7339 100644 --- a/cli/cmd/proxy/loglevel/command.go +++ b/cli/cmd/proxy/loglevel/command.go @@ -64,6 +64,7 @@ type LogLevelCommand struct { } func (l *LogLevelCommand) init() { + l.Log.ResetNamed("loglevel") l.set = flag.NewSets() f := l.set.NewSet("Command Options") f.StringVar(&flag.StringVar{ @@ -91,7 +92,6 @@ func (l *LogLevelCommand) init() { func (l *LogLevelCommand) Run(args []string) int { l.once.Do(l.init) - l.Log.ResetNamed("loglevel") defer common.CloseWithError(l.BaseCommand) err := l.parseFlags(args) From d47cfd3b9739d1210d00501784ff4276cfee46b5 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Fri, 27 Jan 2023 11:41:18 -0500 Subject: [PATCH 055/592] Bump Kubernetes versions for clouds for acceptance tests (#1852) --- .circleci/config.yml | 57 ++++++++++++------------ CHANGELOG.md | 3 +- README.md | 2 +- charts/consul/README.md | 2 +- charts/consul/test/terraform/aks/main.tf | 19 ++++---- charts/consul/test/terraform/eks/main.tf | 48 ++++++++++++++++++-- charts/consul/test/terraform/gke/main.tf | 13 ++---- 7 files changed, 90 insertions(+), 54 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index df5dd49573..f55e540f13 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -573,7 +573,7 @@ jobs: - checkout - install-prereqs - create-kind-clusters: - version: "v1.25.3" + version: "v1.26.0" - restore_cache: keys: - consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} @@ -606,7 +606,7 @@ jobs: - checkout - install-prereqs - create-kind-clusters: - version: "v1.25.3" + version: "v1.26.0" - restore_cache: keys: - consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} @@ -723,7 +723,7 @@ jobs: ############################# # CLOUD ACCEPTANCE TEST JOBS ############################# - acceptance-gke-1-23: + acceptance-gke-1-25: parallelism: 2 environment: - TEST_RESULTS: /tmp/test-results @@ -773,7 +773,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-pod-security-policies -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results @@ -792,7 +792,7 @@ jobs: fail_only: true failure_message: "GKE acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - acceptance-gke-cni-1-23: + acceptance-gke-cni-1-25: parallelism: 2 environment: - TEST_RESULTS: /tmp/test-results @@ -842,7 +842,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -use-gke -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-pod-security-policies -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -use-gke -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 - store_test_results: path: /tmp/test-results @@ -861,7 +861,7 @@ jobs: fail_only: true failure_message: "GKE CNI acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - acceptance-aks-1-22: + acceptance-aks-1-24: parallelism: 3 environment: - TEST_RESULTS: /tmp/test-results @@ -918,7 +918,7 @@ jobs: fail_only: true failure_message: "AKS acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - acceptance-aks-cni-1-22: + acceptance-aks-cni-1-24: parallelism: 3 environment: - TEST_RESULTS: /tmp/test-results @@ -974,7 +974,7 @@ jobs: fail_only: true failure_message: "AKS CNI acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - acceptance-eks-1-21: + acceptance-eks-1-23: parallelism: 3 environment: - TEST_RESULTS: /tmp/test-results @@ -1037,7 +1037,7 @@ jobs: fail_only: true failure_message: "EKS acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - acceptance-eks-cni-1-21: + acceptance-eks-cni-1-23: parallelism: 3 environment: - TEST_RESULTS: /tmp/test-results @@ -1193,7 +1193,7 @@ jobs: - slack/status: channel: *slack-channel fail_only: true - failure_message: "Acceptance tests against Kind with Kubernetes v1.23 with Consul 1.12 nightly failed. Check the logs at: ${CIRCLE_BUILD_URL}" + failure_message: "Acceptance tests against Kind with Kubernetes v1.25 with Consul 1.12 nightly failed. Check the logs at: ${CIRCLE_BUILD_URL}" acceptance-kind-1-23-consul-compat-nightly-1-13: environment: @@ -1234,7 +1234,7 @@ jobs: - slack/status: channel: *slack-channel fail_only: true - failure_message: "Acceptance tests against Kind with Kubernetes v1.23 with Consul 1.13 nightly failed. Check the logs at: ${CIRCLE_BUILD_URL}" + failure_message: "Acceptance tests against Kind with Kubernetes v1.25 with Consul 1.13 nightly failed. Check the logs at: ${CIRCLE_BUILD_URL}" ######################## # WORKFLOWS @@ -1264,16 +1264,17 @@ workflows: - acceptance: context: consul-ci requires: - - dev-upload-docker + - dev-upload-docker - acceptance-tproxy-cni: context: consul-ci requires: - - dev-upload-docker + - dev-upload-docker - acceptance-tproxy: context: consul-ci requires: - dev-upload-docker + nightly-cleanup: triggers: - schedule: @@ -1310,15 +1311,15 @@ workflows: - build-distros-linux # Disable until we can use UBI images. # - acceptance-openshift - - acceptance-gke-1-23: + - acceptance-gke-1-25: requires: - - dev-upload-docker - - acceptance-gke-cni-1-23: + - dev-upload-docker + - acceptance-gke-cni-1-25: requires: - - acceptance-gke-1-23 + - acceptance-gke-1-25 - acceptance-tproxy: requires: - - dev-upload-docker + - dev-upload-docker nightly-acceptance-tests-main: description: | @@ -1342,24 +1343,24 @@ workflows: - build-distros-linux # Disable until we can use UBI images. # - acceptance-openshift - - acceptance-gke-1-23: + - acceptance-gke-1-25: requires: - dev-upload-docker - - acceptance-gke-cni-1-23: + - acceptance-gke-cni-1-25: requires: - - acceptance-gke-1-23 - - acceptance-eks-1-21: + - acceptance-gke-1-25 + - acceptance-eks-1-23: requires: - dev-upload-docker - - acceptance-eks-cni-1-21: + - acceptance-eks-cni-1-23: requires: - - acceptance-eks-1-21 - - acceptance-aks-1-22: + - acceptance-eks-1-23 + - acceptance-aks-1-24: requires: - dev-upload-docker - - acceptance-aks-cni-1-22: + - acceptance-aks-cni-1-24: requires: - - acceptance-aks-1-22 + - acceptance-aks-1-24 - acceptance-tproxy: requires: - dev-upload-docker diff --git a/CHANGELOG.md b/CHANGELOG.md index d4cf05b4f6..bf1197c5ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,13 @@ IMPROVEMENTS: * Helm: + * Kubernetes v1.26 is now supported. Minimum tested version of Kubernetes is now v1.23. [[GH-1852](https://github.com/hashicorp/consul-k8s/pull/1852)] * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] * Add the `accessLogs` field to the `ProxyDefaults` CRD. [[GH-1816](https://github.com/hashicorp/consul-k8s/pull/1816)] * Add the `envoyExtensions` field to the `ProxyDefaults` and `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) * Add the `balanceInboundConnections` field to the `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) * Control-Plane - * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1824)] + * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] BUG FIXES: diff --git a/README.md b/README.md index aafddfbc29..1d3a3733ab 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). The following pre-requisites must be met before installing Consul on Kubernetes. - * **Kubernetes 1.22.x - 1.25.x** - This represents the earliest versions of Kubernetes tested. + * **Kubernetes 1.23.x - 1.26.x** - This represents the earliest versions of Kubernetes tested. It is possible that this chart works with earlier versions, but it is untested. * Helm install diff --git a/charts/consul/README.md b/charts/consul/README.md index 79b3fc4a68..e7d7fd9285 100644 --- a/charts/consul/README.md +++ b/charts/consul/README.md @@ -42,7 +42,7 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). The following pre-requisites must be met before installing Consul on Kubernetes. - * **Kubernetes 1.22.x - 1.25.x** - This represents the earliest versions of Kubernetes tested. + * **Kubernetes 1.23.x - 1.26.x** - This represents the earliest versions of Kubernetes tested. It is possible that this chart works with earlier versions, but it is untested. * Helm install diff --git a/charts/consul/test/terraform/aks/main.tf b/charts/consul/test/terraform/aks/main.tf index 784a60d9ef..1db5145531 100644 --- a/charts/consul/test/terraform/aks/main.tf +++ b/charts/consul/test/terraform/aks/main.tf @@ -1,5 +1,5 @@ provider "azurerm" { - version = "2.90.0" + version = "3.40.0" features {} } @@ -40,12 +40,13 @@ resource "azurerm_virtual_network_peering" "default" { } resource "azurerm_kubernetes_cluster" "default" { - count = var.cluster_count - name = "consul-k8s-${random_id.suffix[count.index].dec}" - location = azurerm_resource_group.default[count.index].location - resource_group_name = azurerm_resource_group.default[count.index].name - dns_prefix = "consul-k8s-${random_id.suffix[count.index].dec}" - kubernetes_version = "1.22.11" + count = var.cluster_count + name = "consul-k8s-${random_id.suffix[count.index].dec}" + location = azurerm_resource_group.default[count.index].location + resource_group_name = azurerm_resource_group.default[count.index].name + dns_prefix = "consul-k8s-${random_id.suffix[count.index].dec}" + kubernetes_version = "1.24.6" + role_based_access_control_enabled = true // We're setting the network plugin and other network properties explicitly // here even though they are the same as defaults to ensure that none of these CIDRs @@ -77,10 +78,6 @@ resource "azurerm_kubernetes_cluster" "default" { client_secret = var.client_secret } - role_based_access_control { - enabled = true - } - tags = var.tags } diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index 9ccc2cdd2b..ca48a5a8fe 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -3,8 +3,8 @@ provider "aws" { region = var.region assume_role { - role_arn = var.role_arn - duration_seconds = 2700 + role_arn = var.role_arn + duration = "2700s" } } @@ -58,8 +58,9 @@ module "eks" { kubeconfig_api_version = "client.authentication.k8s.io/v1beta1" cluster_name = "consul-k8s-${random_id.suffix[count.index].dec}" - cluster_version = "1.21" + cluster_version = "1.23" subnets = module.vpc[count.index].private_subnets + enable_irsa = true vpc_id = module.vpc[count.index].vpc_id @@ -80,6 +81,47 @@ module "eks" { tags = var.tags } +resource "aws_iam_role" "csi-driver-role" { + count = var.cluster_count + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = "sts:AssumeRoleWithWebIdentity", + Principal = { + Federated = module.eks[count.index].oidc_provider_arn + }, + Condition = { + StringEquals = { + join(":", [trimprefix(module.eks[count.index].cluster_oidc_issuer_url, "https://"), "aud"]) = ["sts.amazonaws.com"], + join(":", [trimprefix(module.eks[count.index].cluster_oidc_issuer_url, "https://"), "sub"]) = ["system:serviceaccount:kube-system:ebs-csi-controller-sa"], + } + } + } + ] + }) +} + +data "aws_iam_policy" "csi-driver-policy" { + name = "AmazonEBSCSIDriverPolicy" +} + +resource "aws_iam_role_policy_attachment" "csi" { + count = var.cluster_count + role = aws_iam_role.csi-driver-role[count.index].name + policy_arn = data.aws_iam_policy.csi-driver-policy.arn +} + +resource "aws_eks_addon" "csi-driver" { + count = var.cluster_count + cluster_name = module.eks[count.index].cluster_id + addon_name = "aws-ebs-csi-driver" + addon_version = "v1.15.0-eksbuild.1" + service_account_role_arn = aws_iam_role.csi-driver-role[count.index].arn + resolve_conflicts = "OVERWRITE" +} + data "aws_eks_cluster" "cluster" { count = var.cluster_count name = module.eks[count.index].cluster_id diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index 1574df36b3..1bd574ce2c 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -1,4 +1,4 @@ -provider "google-beta" { +provider "google" { project = var.project version = "~> 3.49.0" } @@ -10,13 +10,12 @@ resource "random_id" "suffix" { data "google_container_engine_versions" "main" { location = var.zone - version_prefix = "1.23." + version_prefix = "1.25." } resource "google_container_cluster" "cluster" { - provider = "google-beta" - - count = var.cluster_count + provider = "google" + count = var.cluster_count name = "consul-k8s-${random_id.suffix[count.index].dec}" project = var.project @@ -28,10 +27,6 @@ resource "google_container_cluster" "cluster" { tags = ["consul-k8s-${random_id.suffix[count.index].dec}"] machine_type = "e2-standard-4" } - pod_security_policy_config { - enabled = true - } - resource_labels = var.labels } From d531159748727cd6cb956f938ef3387ecf5fc126 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Wed, 1 Feb 2023 14:42:17 -0500 Subject: [PATCH 056/592] Update main to latest consul & consul-dataplane versions (late Jan 2023) (#1865) --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ charts/consul/Chart.yaml | 6 +++--- charts/consul/values.yaml | 4 ++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf1197c5ae..db8162167e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,32 @@ BUG FIXES: * Control Plane * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] +## 1.0.3 (January 30, 2023) + +IMPROVEMENTS: +* Helm: + * Kubernetes v1.26 is now supported. Minimum tested version of Kubernetes is now v1.23. [[GH-1852](https://github.com/hashicorp/consul-k8s/pull/1852)] + * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] +* Control-Plane + * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] + * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] + +BUG FIXES: +* Control Plane + +## 0.49.3 (January 30, 2023) + +IMPROVEMENTS: +* Helm: + * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] +* Control-Plane + * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1843](https://github.com/hashicorp/consul-k8s/pull/1843)] + * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] + +BUG FIXES: +* Control Plane + * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] + ## 1.0.2 (December 1, 2022) IMPROVEMENTS: diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index 253291defc..41b95611e6 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: consul version: 1.1.0-dev -appVersion: 1.14.2 +appVersion: 1.14.4 kubeVersion: ">=1.21.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -13,11 +13,11 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: hashicorp/consul:1.14.2 + image: hashicorp/consul:1.14.4 - name: consul-k8s-control-plane image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.1.0-dev - name: consul-dataplane - image: hashicorp/consul-dataplane:1.0.0 + image: hashicorp/consul-dataplane:1.0.1 - name: envoy image: envoyproxy/envoy:v1.23.1 artifacthub.io/license: MPL-2.0 diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index f2f909fa81..ed1ed4cccb 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -63,7 +63,7 @@ global: # image: "hashicorp/consul-enterprise:1.10.0-ent" # ``` # @default: hashicorp/consul: - image: "hashicorp/consul:1.14.2" + image: "hashicorp/consul:1.14.4" # 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. @@ -542,7 +542,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: "hashicorp/consul-dataplane:1.0.0" + imageConsulDataplane: "hashicorp/consul-dataplane:1.0.1" # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. From eea6c5b5eeb1eed14bb86cb2daa9cb5f03c1a7f7 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 2 Feb 2023 13:01:54 -0500 Subject: [PATCH 057/592] missing a line in the changelog (#1868) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db8162167e..3dc03f3479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ IMPROVEMENTS: BUG FIXES: * Control Plane + * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] ## 0.49.3 (January 30, 2023) From 02896eb2e1db7ddd394f2251bc74ede52cd85f1c Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Thu, 2 Feb 2023 11:38:24 -0800 Subject: [PATCH 058/592] Exclude openebs namespace from injection. (#1869) * Exclude openebs namespace from injection. OpenEBS is a Kubernetes storage solution. When you spin up a PVC, under the hood OpenEBS creates a pod to handle the necessary storage operations. If the openebs namespace is not excluded from injection, that pod can't start because our mutatingwebhook config requires all pod scheduling requests make it to our webhook and our webhook isn't running yet because the consul servers aren't running. This is a breaking change but I think it's worth it because it's very unlikely anyone is using the openebs namespace for anything other than openebs. * Changelog --- CHANGELOG.md | 15 +++++++++++++++ charts/consul/values.yaml | 8 ++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc03f3479..9d1aafcc01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ ## UNRELEASED +BREAKING CHANGES: +* Helm: + * Change defaults to exclude the `openebs` namespace from sidecar injection. If you previously had pods in that namespace + that you wanted to be injected, you must now set `namespaceSelector` as follows: + + ```yaml + connectInject: + namespaceSelector: | + matchExpressions: + - key: "kubernetes.io/metadata.name" + operator: "NotIn" + values: ["kube-system","local-path-storage"] + ``` + [[GH-1869](https://github.com/hashicorp/consul-k8s/pull/1869)] + IMPROVEMENTS: * Helm: * Kubernetes v1.26 is now supported. Minimum tested version of Kubernetes is now v1.23. [[GH-1852](https://github.com/hashicorp/consul-k8s/pull/1852)] diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index ed1ed4cccb..63c2a63cbf 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2140,9 +2140,9 @@ connectInject: # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector # for more details. # - # By default, we exclude the kube-system namespace since usually users won't - # want those pods injected and also the local-path-storage namespace so that - # Kind (Kubernetes In Docker) can provision Pods used to create PVCs. + # 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. # Note that this exclusion is only supported in Kubernetes v1.21.1+. # # Example: @@ -2157,7 +2157,7 @@ connectInject: matchExpressions: - key: "kubernetes.io/metadata.name" operator: "NotIn" - values: ["kube-system","local-path-storage"] + values: ["kube-system","local-path-storage","openebs"] # List of k8s namespaces to allow Connect sidecar # injection in. If a k8s namespace is not included or is listed in `k8sDenyNamespaces`, From d08dd73cab4884a362b4bb554476c5ce44ed1c93 Mon Sep 17 00:00:00 2001 From: DanStough Date: Thu, 26 Jan 2023 18:11:03 -0500 Subject: [PATCH 059/592] feat: add peer to service-defaults overrides --- CHANGELOG.md | 1 + .../consul/templates/crd-servicedefaults.yaml | 20 +++-- .../api/v1alpha1/servicedefaults_types.go | 27 ++++++- .../v1alpha1/servicedefaults_types_test.go | 73 ++++++++++++++++++- .../consul.hashicorp.com_servicedefaults.yaml | 20 +++-- control-plane/go.mod | 2 +- control-plane/go.sum | 4 + 7 files changed, 128 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1aafcc01..98335b1481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ IMPROVEMENTS: * Add the `accessLogs` field to the `ProxyDefaults` CRD. [[GH-1816](https://github.com/hashicorp/consul-k8s/pull/1816)] * Add the `envoyExtensions` field to the `ProxyDefaults` and `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) * Add the `balanceInboundConnections` field to the `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) + * Add the `upstreamConfig.overrides[].peer` field to the `ServiceDefaults` CRD. [[GH-1853]](https://github.com/hashicorp/consul-k8s/pull/1853) * Control-Plane * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 128884c454..5c6ecc7476 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -258,15 +258,15 @@ spec: type: string type: object name: - description: Name is only accepted within a service-defaults + description: Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string namespace: - description: Namespace is only accepted within a service-defaults + description: Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string partition: - description: Partition is only accepted within a service-defaults + description: Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string passiveHealthCheck: @@ -291,6 +291,10 @@ spec: format: int32 type: integer type: object + peer: + description: Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + config entry. + type: string protocol: description: Protocol describes the upstream's service protocol. Valid values are "tcp", "http" and "grpc". Anything else @@ -357,15 +361,15 @@ spec: type: string type: object name: - description: Name is only accepted within a service-defaults + description: Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string namespace: - description: Namespace is only accepted within a service-defaults + description: Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string partition: - description: Partition is only accepted within a service-defaults + description: Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string passiveHealthCheck: @@ -392,6 +396,10 @@ spec: format: int32 type: integer type: object + peer: + description: Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + config entry. + type: string protocol: description: Protocol describes the upstream's service protocol. Valid values are "tcp", "http" and "grpc". Anything else diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 2682f6a28a..06da8b1d2c 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -7,7 +7,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/miekg/dns" corev1 "k8s.io/api/core/v1" @@ -15,6 +14,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ( @@ -113,12 +114,14 @@ type Upstreams struct { } type Upstream struct { - // Name is only accepted within a service-defaults config entry. + // Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. Name string `json:"name,omitempty"` - // Namespace is only accepted within a service-defaults config entry. + // Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. Namespace string `json:"namespace,omitempty"` - // Partition is only accepted within a service-defaults config entry. + // Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. Partition string `json:"partition,omitempty"` + // Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. + Peer string `json:"peer,omitempty"` // EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's // listener. // Note: This escape hatch is NOT compatible with the discovery chain and @@ -374,10 +377,25 @@ func (in *Upstream) validate(path *field.Path, kind string, partitionsEnabled bo if in.Name != "" { errs = append(errs, field.Invalid(path.Child("name"), in.Name, "upstream.name for a default upstream must be \"\"")) } + if in.Namespace != "" { + errs = append(errs, field.Invalid(path.Child("namespace"), in.Namespace, "upstream.namespace for a default upstream must be \"\"")) + } + if in.Partition != "" { + errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, "upstream.partition for a default upstream must be \"\"")) + } + if in.Peer != "" { + errs = append(errs, field.Invalid(path.Child("peer"), in.Peer, "upstream.peer for a default upstream must be \"\"")) + } } else if kind == overrideUpstream { if in.Name == "" { errs = append(errs, field.Invalid(path.Child("name"), in.Name, "upstream.name for an override upstream cannot be \"\"")) } + if in.Namespace != "" && in.Peer != "" { + errs = append(errs, field.Invalid(path, in, "both namespace and peer cannot be specified.")) + } + if in.Partition != "" && in.Peer != "" { + errs = append(errs, field.Invalid(path, in, "both partition and peer cannot be specified.")) + } } if !partitionsEnabled && in.Partition != "" { errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, "Consul Enterprise Admin Partitions must be enabled to set upstream.partition")) @@ -396,6 +414,7 @@ func (in *Upstream) toConsul() *capi.UpstreamConfig { Name: in.Name, Namespace: in.Namespace, Partition: in.Partition, + Peer: in.Peer, EnvoyListenerJSON: in.EnvoyListenerJSON, EnvoyClusterJSON: in.EnvoyClusterJSON, Protocol: in.Protocol, diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index fb29cf15cc..33ec6d2f40 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -5,12 +5,13 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func TestServiceDefaults_ToConsul(t *testing.T) { @@ -854,6 +855,21 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.defaults.name: Invalid value: "foobar": upstream.name for a default upstream must be ""`, }, + "upstreamConfig.defaults.namespace": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + UpstreamConfig: &Upstreams{ + Defaults: &Upstream{ + Namespace: "foobar", + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.defaults.namespace: Invalid value: "foobar": upstream.namespace for a default upstream must be ""`, + }, "upstreamConfig.defaults.partition": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ @@ -868,7 +884,22 @@ func TestServiceDefaults_Validate(t *testing.T) { }, }, partitionsEnabled: false, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.defaults.partition: Invalid value: "upstream": Consul Enterprise Admin Partitions must be enabled to set upstream.partition`, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.upstreamConfig.defaults.partition: Invalid value: "upstream": upstream.partition for a default upstream must be "", spec.upstreamConfig.defaults.partition: Invalid value: "upstream": Consul Enterprise Admin Partitions must be enabled to set upstream.partition]`, + }, + "upstreamConfig.defaults.peer": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + UpstreamConfig: &Upstreams{ + Defaults: &Upstream{ + Peer: "foobar", + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.defaults.peer: Invalid value: "foobar": upstream.peer for a default upstream must be ""`, }, "upstreamConfig.overrides.meshGateway": { input: &ServiceDefaults{ @@ -925,6 +956,44 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.overrides[0].partition: Invalid value: "upstream": Consul Enterprise Admin Partitions must be enabled to set upstream.partition`, }, + "upstreamConfig.overrides.partition and namespace": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + UpstreamConfig: &Upstreams{ + Overrides: []*Upstream{ + { + Name: "service", + Namespace: "namespace", + Peer: "peer", + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.overrides[0]: Invalid value: v1alpha1.Upstream{Name:"service", Namespace:"namespace", Partition:"", Peer:"peer", EnvoyListenerJSON:"", EnvoyClusterJSON:"", Protocol:"", ConnectTimeoutMs:0, Limits:(*v1alpha1.UpstreamLimits)(nil), PassiveHealthCheck:(*v1alpha1.PassiveHealthCheck)(nil), MeshGateway:v1alpha1.MeshGateway{Mode:""}}: both namespace and peer cannot be specified.`, + }, + "upstreamConfig.overrides.partition and peer": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + UpstreamConfig: &Upstreams{ + Overrides: []*Upstream{ + { + Name: "service", + Partition: "upstream", + Peer: "peer", + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.upstreamConfig.overrides[0]: Invalid value: v1alpha1.Upstream{Name:"service", Namespace:"", Partition:"upstream", Peer:"peer", EnvoyListenerJSON:"", EnvoyClusterJSON:"", Protocol:"", ConnectTimeoutMs:0, Limits:(*v1alpha1.UpstreamLimits)(nil), PassiveHealthCheck:(*v1alpha1.PassiveHealthCheck)(nil), MeshGateway:v1alpha1.MeshGateway{Mode:""}}: both partition and peer cannot be specified., spec.upstreamConfig.overrides[0].partition: Invalid value: "upstream": Consul Enterprise Admin Partitions must be enabled to set upstream.partition]`, + }, "multi-error": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 8b05eeb025..4f335a923d 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -251,15 +251,15 @@ spec: type: string type: object name: - description: Name is only accepted within a service-defaults + description: Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string namespace: - description: Namespace is only accepted within a service-defaults + description: Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string partition: - description: Partition is only accepted within a service-defaults + description: Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string passiveHealthCheck: @@ -284,6 +284,10 @@ spec: format: int32 type: integer type: object + peer: + description: Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + config entry. + type: string protocol: description: Protocol describes the upstream's service protocol. Valid values are "tcp", "http" and "grpc". Anything else @@ -350,15 +354,15 @@ spec: type: string type: object name: - description: Name is only accepted within a service-defaults + description: Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string namespace: - description: Namespace is only accepted within a service-defaults + description: Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string partition: - description: Partition is only accepted within a service-defaults + description: Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. type: string passiveHealthCheck: @@ -385,6 +389,10 @@ spec: format: int32 type: integer type: object + peer: + description: Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + config entry. + type: string protocol: description: Protocol describes the upstream's service protocol. Valid values are "tcp", "http" and "grpc". Anything else diff --git a/control-plane/go.mod b/control-plane/go.mod index 2cd0300557..d5805fe558 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919 + github.com/hashicorp/consul/api v1.10.1-0.20230203155153-2f149d60ccbf github.com/hashicorp/consul/sdk v0.13.0 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 diff --git a/control-plane/go.sum b/control-plane/go.sum index 7542ac12d9..d1473ae24f 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -348,6 +348,10 @@ github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919 h1:8aVegJMSv7PIAAa1zqQQ0CT4TKv+Nf7I4rhE6+uDa1U= github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= +github.com/hashicorp/consul/api v1.10.1-0.20230126204442-a43eadd1225b h1:dNIQYhru10Hg+E1oEL8f9CX6MC+8CW5JuQ4jk3g70LA= +github.com/hashicorp/consul/api v1.10.1-0.20230126204442-a43eadd1225b/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= +github.com/hashicorp/consul/api v1.10.1-0.20230203155153-2f149d60ccbf h1:vvsHghmX3LyNUaDe7onYKHyDiny+ystdHKIEujbNj4Q= +github.com/hashicorp/consul/api v1.10.1-0.20230203155153-2f149d60ccbf/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= From f562a092ea6dcc1ec8243930a0c9dc62af44971a Mon Sep 17 00:00:00 2001 From: DanStough Date: Thu, 26 Jan 2023 18:11:44 -0500 Subject: [PATCH 060/592] chore: fix generation for peering CRDs --- hack/copy-crds-to-chart/main.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/hack/copy-crds-to-chart/main.go b/hack/copy-crds-to-chart/main.go index 7276c468f8..7085bdb9e6 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -9,6 +9,14 @@ import ( "strings" ) +var ( + // HACK IT! + requiresPeering = map[string]struct{}{ + "consul.hashicorp.com_peeringacceptors.yaml": {}, + "consul.hashicorp.com_peeringdialers.yaml": {}, + } +) + func main() { if len(os.Args) != 1 { fmt.Println("Usage: go run ./...") @@ -43,8 +51,13 @@ func realMain(helmPath string) error { // Strip leading newline. contents = strings.TrimPrefix(contents, "\n") - // Add {{- if .Values.connectInject.enabled }} {{- end }} wrapper. - contents = fmt.Sprintf("{{- if .Values.connectInject.enabled }}\n%s{{- end }}\n", contents) + if _, ok := requiresPeering[info.Name()]; ok { + // Add {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} {{- end }} wrapper. + contents = fmt.Sprintf("{{- if and .Values.connectInject.enabled .Values.global.peering.enabled }}\n%s{{- end }}\n", contents) + } else { + // Add {{- if .Values.connectInject.enabled }} {{- end }} wrapper. + contents = fmt.Sprintf("{{- if .Values.connectInject.enabled }}\n%s{{- end }}\n", contents) + } // Add labels, this is hacky because we're relying on the line number // but it means we don't need to regex or yaml parse. From 10e188c2db299e871510bd051637512e68021a69 Mon Sep 17 00:00:00 2001 From: Jared Kirschner <85913323+jkirschner-hashicorp@users.noreply.github.com> Date: Sun, 5 Feb 2023 21:03:39 -0500 Subject: [PATCH 061/592] Refine server TLS Vault PKI role config (#1877) The generate_lease=true configuration is unnecessary and generates a note about performance implications in Vault logs. Remove this configuration so that the default value of generate_lease=false is used instead. --- acceptance/framework/vault/helpers.go | 1 - 1 file changed, 1 deletion(-) diff --git a/acceptance/framework/vault/helpers.go b/acceptance/framework/vault/helpers.go index 6ebfb5cf62..4726e246ae 100644 --- a/acceptance/framework/vault/helpers.go +++ b/acceptance/framework/vault/helpers.go @@ -40,7 +40,6 @@ func ConfigurePKICerts(t *testing.T, "allow_bare_domains": "true", "allow_localhost": "true", "allow_subdomains": "true", - "generate_lease": "true", "max_ttl": maxTTL, } From c61785fe37338c4860f2714f876c897850e3a9f3 Mon Sep 17 00:00:00 2001 From: David Yu Date: Mon, 6 Feb 2023 18:00:55 -0800 Subject: [PATCH 062/592] Dockerfile: Remove gnupg from Consul k8s container (#1882) * Remove gnupg * Update CHANGELOG.md --- CHANGELOG.md | 1 + control-plane/Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98335b1481..277535e2fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ IMPROVEMENTS: * Control-Plane * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] + * Remove extraneous `gnupg` depdency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] BUG FIXES: * Control Plane diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index 3e31c92ef6..ee20f8bb61 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -89,7 +89,7 @@ LABEL name=${BIN_NAME} \ ENV BIN_NAME=${BIN_NAME} ENV VERSION=${PRODUCT_VERSION} -RUN apk add --no-cache ca-certificates gnupg libcap openssl su-exec iputils libc6-compat iptables +RUN apk add --no-cache ca-certificates libcap openssl su-exec iputils libc6-compat iptables # TARGETOS and TARGETARCH are set automatically when --platform is provided. ARG TARGETOS @@ -152,7 +152,7 @@ ARG TARGETARCH # Copy license for Red Hat certification. COPY LICENSE /licenses/mozilla.txt -RUN microdnf install -y ca-certificates gnupg libcap openssl shadow-utils iptables +RUN microdnf install -y ca-certificates libcap openssl shadow-utils iptables # Create a non-root user to run the software. On OpenShift, this # will not matter since the container is run as a random user and group From 05ffee3896d890186bc8cc160ad1617c9baa53ec Mon Sep 17 00:00:00 2001 From: David Yu Date: Mon, 6 Feb 2023 20:02:14 -0800 Subject: [PATCH 063/592] Dockerfile: remove `gnupg` from dev image (#1885) * Dockerfile: remove `gnupg` from dev image * Update CHANGELOG.md --- CHANGELOG.md | 2 +- control-plane/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 277535e2fc..4aecdf96d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ IMPROVEMENTS: * Control-Plane * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] - * Remove extraneous `gnupg` depdency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] + * Remove extraneous `gnupg` dependency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] BUG FIXES: * Control Plane diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index ee20f8bb61..2989712a5f 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -41,7 +41,7 @@ LABEL name=${BIN_NAME} \ ENV BIN_NAME=${BIN_NAME} ENV VERSION=${VERSION} -RUN apk add --no-cache ca-certificates gnupg libcap openssl su-exec iputils libc6-compat iptables +RUN apk add --no-cache ca-certificates libcap openssl su-exec iputils libc6-compat iptables # Create a non-root user to run the software. RUN addgroup ${BIN_NAME} && \ From 013d7bd1893e2b94d82461b1b28a1a7b9152f95f Mon Sep 17 00:00:00 2001 From: Tu Nguyen Date: Tue, 7 Feb 2023 13:10:55 -0800 Subject: [PATCH 064/592] Update links to support devdot --- charts/consul/values.yaml | 86 +++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 63c2a63cbf..7bd4d27e99 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -25,7 +25,7 @@ global: name: null # The domain Consul will answer DNS queries for - # (see `-domain` (https://www.consul.io/docs/agent/config/cli-flags#_domain)) and the domain services synced from + # (Refer to [`-domain`](https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_domain)) and the domain services synced from # Consul into Kubernetes will have, e.g. `service-name.service.consul`. domain: consul @@ -67,7 +67,7 @@ global: # 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. - # See https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry for reference. + # Refer to https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry. # # Example: # @@ -92,13 +92,13 @@ global: datacenter: dc1 # Controls whether pod security policies are created for the Consul components - # created by this chart. See https://kubernetes.io/docs/concepts/policy/pod-security-policy/. + # created by this chart. Refer to https://kubernetes.io/docs/concepts/policy/pod-security-policy/. enablePodSecurityPolicies: false # secretsBackend is used to configure Vault as the secrets backend for the Consul on Kubernetes installation. # The Vault cluster needs to have the Kubernetes Auth Method, KV2 and PKI secrets engines enabled # and have necessary secrets, policies and roles created prior to installing Consul. - # See https://www.consul.io/docs/k8s/installation/vault for full instructions. + # Refer to https://developer.hashicorp.com/consul/docs/k8s/deployment-configurations/vault for full instructions. # # The Vault cluster _must_ not have the Consul cluster installed by this Helm chart as its storage backend # as that would cause a circular dependency. @@ -198,7 +198,7 @@ global: # The provider will be configured to use the Vault Kubernetes auth method # and therefore requires the role provided by `global.secretsBackend.vault.consulServerRole` # to have permissions to the root and intermediate PKI paths. - # Please see https://www.consul.io/docs/connect/ca/vault#vault-acl-policies + # Please refer to https://developer.hashicorp.com/consul/docs/connect/ca/vault#vault-acl-policies # for information on how to configure the Vault policies. connectCA: # The address of the Vault server. @@ -208,15 +208,15 @@ global: authMethodPath: "kubernetes" # The path to a PKI secrets engine for the root certificate. - # For more details, please refer to [Vault Connect CA configuration](https://www.consul.io/docs/connect/ca/vault#rootpkipath). + # For more details, please refer to [Vault Connect CA configuration](https://developer.hashicorp.com/consul/docs/connect/ca/vault#rootpkipath). rootPKIPath: "" # The path to a PKI secrets engine for the generated intermediate certificate. - # For more details, please refer to [Vault Connect CA configuration](https://www.consul.io/docs/connect/ca/vault#intermediatepkipath). + # For more details, please refer to [Vault Connect CA configuration](https://developer.hashicorp.com/consul/docs/connect/ca/vault#intermediatepkipath). intermediatePKIPath: "" # Additional Connect CA configuration in JSON format. - # Please refer to [Vault Connect CA configuration](https://www.consul.io/docs/connect/ca/vault#configuration) + # Please refer to [Vault Connect CA configuration](https://developer.hashicorp.com/consul/docs/connect/ca/vault#configuration) # for all configuration options available for that provider. # # Example: @@ -255,7 +255,7 @@ global: secretName: null # Configures Consul's gossip encryption key. - # (see `-encrypt` (https://www.consul.io/docs/agent/config/cli-flags#_encrypt)). + # (Refer to [`-encrypt`](https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_encrypt)). # By default, gossip encryption is not enabled. The gossip encryption key may be set automatically or manually. # The recommended method is to automatically generate the key. # To automatically generate and set a gossip encryption key, set autoGenerate to true. @@ -263,7 +263,7 @@ global: # To manually generate a gossip encryption key, set secretName and secretKey and use Consul to generate # a key, saving this as a Kubernetes secret or Vault secret path and key. # If `global.secretsBackend.vault.enabled=true`, be sure to add the "data" component of the secretName path as required by - # the Vault KV-2 secrets engine [see example]. + # the Vault KV-2 secrets engine [refer to example]. # # ```shell-session # $ kubectl create secret generic consul-gossip-encryption-key --from-literal=key=$(consul keygen) @@ -288,12 +288,12 @@ global: # A list of addresses of upstream DNS servers that are used to recursively resolve DNS queries. # These values are given as `-recursor` flags to Consul servers and clients. - # See https://www.consul.io/docs/agent/config/cli-flags#_recursor for more details. + # Refer to https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_recursor for more details. # If this is an empty array (the default), then Consul DNS will only resolve queries for the Consul top level domain (by default `.consul`). # @type: array recursors: [ ] - # Enables TLS (https://learn.hashicorp.com/tutorials/consul/tls-encryption-secure) + # Enables TLS (https://developer.hashicorp.com/consul/tutorials/security/tls-encryption-secure) # across the cluster to verify authenticity of the Consul servers and clients. # Requires Consul v1.4.1+. tls: @@ -323,7 +323,7 @@ global: # If true, `verify_outgoing`, `verify_server_hostname`, # and `verify_incoming` for internal RPC communication will be set to `true` for Consul servers and clients. # Set this to false to incrementally roll out TLS on an existing Consul cluster. - # Please see https://consul.io/docs/k8s/operations/tls-on-existing-cluster + # Please refer to https://developer.hashicorp.com/consul/docs/k8s/operations/tls-on-existing-cluster # for more details. verify: true @@ -501,7 +501,7 @@ global: # This address must be reachable from the Consul servers in the primary datacenter. # This auth method will be used to provision ACL tokens for Consul components and is different # from the one used by the Consul Service Mesh. - # Please see the [Kubernetes Auth Method documentation](https://consul.io/docs/acl/auth-methods/kubernetes). + # Please refer to the [Kubernetes Auth Method documentation](https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes). # # You can retrieve this value from your `kubeconfig` by running: # @@ -653,7 +653,7 @@ server: image: null # The number of server agents to run. This determines the fault tolerance of - # the cluster. Please see the deployment table (https://consul.io/docs/internals/consensus#deployment-table) + # the cluster. Please refer to the deployment table (https://developer.hashicorp.com/consul/docs/architecture/consensus#deployment-table) # for more information. replicas: 1 @@ -695,8 +695,8 @@ server: # Vault Secrets backend: # If you are using Vault as a secrets backend, a Vault Policy must be created which allows `["create", "update"]` # capabilities on the PKI issuing endpoint, which is usually of the form `pki/issue/consul-server`. - # Please see the following guide for steps to generate a compatible certificate: - # https://learn.hashicorp.com/tutorials/consul/vault-pki-consul-secure-tls + # Please refer to the following guide for steps to generate a compatible certificate: + # https://developer.hashicorp.com/consul/tutorials/vault-secure/vault-pki-consul-secure-tls # Note: when using TLS, both the `server.serverCert` and `global.tls.caCert` which points to the CA endpoint of this PKI engine # must be provided. serverCert: @@ -739,17 +739,17 @@ server: # storage classes, the PersistentVolumeClaims would need to be manually created. # A `null` value will use the Kubernetes cluster's default StorageClass. If a default # StorageClass does not exist, you will need to create one. - # Refer to the [Read/Write Tuning](https://www.consul.io/docs/install/performance#read-write-tuning) + # Refer to the [Read/Write Tuning](https://developer.hashicorp.com/consul/docs/install/performance#read-write-tuning) # section of the Server Performance Requirements documentation for considerations # around choosing a performant storage class. # - # ~> **Note:** The [Reference Architecture](https://learn.hashicorp.com/tutorials/consul/reference-architecture#hardware-sizing-for-consul-servers) + # ~> **Note:** The [Reference Architecture](https://developer.hashicorp.com/consul/tutorials/production-deploy/reference-architecture#hardware-sizing-for-consul-servers) # contains best practices and recommendations for selecting suitable # hardware sizes for your Consul servers. # @type: string storageClass: null - # This will enable/disable Connect (https://consul.io/docs/connect). Setting this to true + # This will enable/disable Connect (https://developer.hashicorp.com/consul/docs/connect). Setting this to true # _will not_ automatically secure pod communication, this # setting will only enable usage of the feature. Consul will automatically initialize # a new CA and set of certificates. Additional Connect settings can be configured @@ -826,7 +826,7 @@ server: # control a rolling update of Consul server agents. This value specifies the # partition (https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions) # for performing a rolling update. Please read the linked Kubernetes documentation - # and https://www.consul.io/docs/k8s/upgrade#upgrading-consul-servers for more information. + # and https://developer.hashicorp.com/consul/docs/k8s/upgrade#upgrading-consul-servers for more information. updatePartition: 0 # This configures the PodDisruptionBudget (https://kubernetes.io/docs/tasks/run-application/configure-pdb/) @@ -845,7 +845,7 @@ server: # @type: integer maxUnavailable: null - # A raw string of extra JSON configuration (https://consul.io/docs/agent/options) for Consul + # A raw string of extra JSON configuration (https://developer.hashicorp.com/consul/docs/agent/config/config-files) for Consul # servers. This will be saved as-is into a ConfigMap that is read by the Consul # server agents. This can be used to add additional configuration that # isn't directly exposed by the chart. @@ -1063,21 +1063,21 @@ server: extraEnvironmentVars: { } # [Enterprise Only] Values for setting up and running snapshot agents - # (https://consul.io/commands/snapshot/agent) + # (https://developer.hashicorp.com/consul/commands/snapshot/agent) # within the Consul clusters. They run as a sidecar with Consul servers. snapshotAgent: # If true, the chart will install resources necessary to run the snapshot agent. enabled: false # Interval at which to perform snapshots. - # See https://www.consul.io/commands/snapshot/agent#interval + # Refer to https://developer.hashicorp.com/consul/commands/snapshot/agent#interval # @type: string interval: 1h # A Kubernetes or Vault secret that should be manually created to contain the entire # config to be used on the snapshot agent. # This is the preferred method of configuration since there are usually storage - # credentials present. Please see Snapshot agent config (https://consul.io/commands/snapshot/agent#config-file-options) + # credentials present. Please refer to the [Snapshot agent config](https://developer.hashicorp.com/consul/commands/snapshot/agent#config-file-options) # for details. configSecret: # The name of the Kubernetes secret or Vault secret path that holds the snapshot agent config. @@ -1155,7 +1155,7 @@ externalServers: # If you are setting `global.acls.manageSystemACLs` and # `connectInject.enabled` to true, set `k8sAuthMethodHost` to the address of the Kubernetes API server. # This address must be reachable from the Consul servers. - # Please see the Kubernetes Auth Method documentation (https://consul.io/docs/acl/auth-methods/kubernetes). + # Please refer to the [Kubernetes Auth Method documentation](https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes). # # You could retrieve this value from your `kubeconfig` by running: # @@ -1184,7 +1184,7 @@ client: # @type: string image: null - # A list of valid `-retry-join` values (https://www.consul.io/docs/agent/config/cli-flags#_retry_join). + # A list of valid `-retry-join` values (https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_retry_join). # If this is `null` (default), then the clients will attempt to automatically # join the server cluster running within Kubernetes. # This means that with `server.enabled` set to true, clients will automatically @@ -1210,7 +1210,7 @@ client: grpc: true # nodeMeta specifies an arbitrary metadata key/value pair to associate with the node - # (see https://www.consul.io/docs/agent/config/cli-flags#_node_meta) + # (refer to https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_node_meta) nodeMeta: pod-name: ${HOSTNAME} host-ip: ${HOST_IP} @@ -1281,7 +1281,7 @@ client: # @recurse: false tlsInit: null - # A raw string of extra JSON configuration (https://consul.io/docs/agent/options) for Consul + # A raw string of extra JSON configuration (https://developer.hashicorp.com/consul/docs/agent/config/config-files) for Consul # clients. This will be saved as-is into a ConfigMap that is read by the Consul # client agents. This can be used to add additional configuration that # isn't directly exposed by the chart. @@ -1435,7 +1435,7 @@ client: hostNetwork: false # updateStrategy for the DaemonSet. - # See https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy. + # Refer to https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy. # This should be a multi-line string mapping directly to the updateStrategy # # Example: @@ -1563,7 +1563,7 @@ ui: # Optionally set the ingressClassName. ingressClassName: "" - # pathType override - see: https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types + # pathType override - refer to: https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types pathType: Prefix # hosts is a list of host name to create Ingress rules. @@ -1609,8 +1609,8 @@ ui: # @type: boolean # @default: global.metrics.enabled enabled: "-" - # Provider for metrics. See - # https://www.consul.io/docs/agent/options#ui_config_metrics_provider + # Provider for metrics. Refer to + # https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_metrics_provider # This value is only used if `ui.enabled` is set to true. # @type: string provider: "prometheus" @@ -1620,9 +1620,9 @@ ui: # @type: string baseURL: http://prometheus-server - # Corresponds to https://www.consul.io/docs/agent/options#ui_config_dashboard_url_templates configuration. + # Corresponds to https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_dashboard_url_templates configuration. dashboardURLTemplates: - # Sets https://www.consul.io/docs/agent/options#ui_config_dashboard_url_templates_service. + # Sets https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_dashboard_url_templates_service. service: "" # Configure the catalog sync process to sync K8S with Consul @@ -1645,7 +1645,7 @@ syncCatalog: # If true, all valid services in K8S are # synced by default. If false, the service must be annotated - # (https://consul.io/docs/k8s/service-sync#sync-enable-disable) properly to sync. + # (https://developer.hashicorp.com/consul/docs/k8s/service-sync#sync-enable-disable) properly to sync. # In either case an annotation can override the default. default: true @@ -1880,7 +1880,7 @@ connectInject: # If true, the injector will inject the # Connect sidecar into all pods by default. Otherwise, pods must specify the - # injection annotation (https://consul.io/docs/k8s/connect#consul-hashicorp-com-connect-inject) + # injection annotation (https://developer.hashicorp.com/consul/docs/k8s/connect#consul-hashicorp-com-connect-inject) # to opt-in to Connect injection. If this is true, pods can use the same annotation # to explicitly opt-out of injection. default: false @@ -1992,7 +1992,7 @@ connectInject: runAsUser: 0 # updateStrategy for the CNI installer DaemonSet. - # See https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy. + # Refer to https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy. # This should be a multi-line string mapping directly to the updateStrategy # # Example: @@ -2137,7 +2137,7 @@ connectInject: # Selector for restricting the webhook to only specific namespaces. # Use with `connectInject.default: true` to automatically inject all pods in namespaces that match the selector. This should be set to a multiline string. - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector + # Refer to https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector # for more details. # # By default, we exclude kube-system since usually users won't @@ -2243,8 +2243,8 @@ connectInject: # If set to an empty string all service accounts can log in. # This only has effect if ACLs are enabled. # - # See https://www.consul.io/docs/acl/acl-auth-methods.html#binding-rules - # and https://www.consul.io/docs/acl/auth-methods/kubernetes.html#trusted-identity-attributes + # Refer to https://developer.hashicorp.com/consul/docs/security/acl/auth-methods#binding-rules + # and https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes#trusted-identity-attributes # for more details. # Requires Consul >= v1.5. aclBindingRuleSelector: "serviceaccount.name!=default" @@ -2274,7 +2274,7 @@ connectInject: # leads to unnecessary thread and memory usage and leaves unnecessary idle connections open. It is # advised to keep this number low for sidecars and high for edge proxies. # This will control the `--concurrency` flag to Envoy. - # For additional information see also: https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310 + # For additional information, refer to https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310 # # This setting can be overridden on a per-pod basis via this annotation: # - `consul.hashicorp.com/consul-envoy-proxy-concurrency` @@ -2359,7 +2359,7 @@ meshGateway: # Port that gets registered for WAN traffic. # If source is set to "Service" then this setting will have no effect. - # See the documentation for source as to which port will be used in that + # Refer to the documentation for source as to which port will be used in that # case. port: 443 From ebee53dfce2d8a697bdca3fa4fff58631e1fafe9 Mon Sep 17 00:00:00 2001 From: Tu Nguyen Date: Tue, 7 Feb 2023 21:47:50 -0800 Subject: [PATCH 065/592] opportunistic updates - add markdown links --- charts/consul/values.yaml | 130 ++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 60 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 7bd4d27e99..1bf3ce122c 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -98,7 +98,8 @@ global: # secretsBackend is used to configure Vault as the secrets backend for the Consul on Kubernetes installation. # The Vault cluster needs to have the Kubernetes Auth Method, KV2 and PKI secrets engines enabled # and have necessary secrets, policies and roles created prior to installing Consul. - # Refer to https://developer.hashicorp.com/consul/docs/k8s/deployment-configurations/vault for full instructions. + # Refer to [Vault as the Secrets Backend](https://developer.hashicorp.com/consul/docs/k8s/deployment-configurations/vault) + # documentation for full instructions. # # The Vault cluster _must_ not have the Consul cluster installed by this Helm chart as its storage backend # as that would cause a circular dependency. @@ -198,8 +199,8 @@ global: # The provider will be configured to use the Vault Kubernetes auth method # and therefore requires the role provided by `global.secretsBackend.vault.consulServerRole` # to have permissions to the root and intermediate PKI paths. - # Please refer to https://developer.hashicorp.com/consul/docs/connect/ca/vault#vault-acl-policies - # for information on how to configure the Vault policies. + # Please refer to [Vault ACL policies](https://developer.hashicorp.com/consul/docs/connect/ca/vault#vault-acl-policies) + # documentation for information on how to configure the Vault policies. connectCA: # The address of the Vault server. address: "" @@ -288,12 +289,12 @@ global: # A list of addresses of upstream DNS servers that are used to recursively resolve DNS queries. # These values are given as `-recursor` flags to Consul servers and clients. - # Refer to https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_recursor for more details. + # Refer to [`-recursor`](https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_recursor) for more details. # If this is an empty array (the default), then Consul DNS will only resolve queries for the Consul top level domain (by default `.consul`). # @type: array recursors: [ ] - # Enables TLS (https://developer.hashicorp.com/consul/tutorials/security/tls-encryption-secure) + # Enables [TLS](https://developer.hashicorp.com/consul/tutorials/security/tls-encryption-secure) # across the cluster to verify authenticity of the Consul servers and clients. # Requires Consul v1.4.1+. tls: @@ -323,7 +324,7 @@ global: # If true, `verify_outgoing`, `verify_server_hostname`, # and `verify_incoming` for internal RPC communication will be set to `true` for Consul servers and clients. # Set this to false to incrementally roll out TLS on an existing Consul cluster. - # Please refer to https://developer.hashicorp.com/consul/docs/k8s/operations/tls-on-existing-cluster + # Please refer to [TLS on existing clusters](https://developer.hashicorp.com/consul/docs/k8s/operations/tls-on-existing-cluster) # for more details. verify: true @@ -436,10 +437,10 @@ global: # tolerations configures the taints and tolerations for the server-acl-init # and server-acl-init-cleanup jobs. This should be a multi-line string matching the - # Tolerations (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. tolerations: "" - # This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) # labels for the server-acl-init and server-acl-init-cleanup jobs pod assignment, formatted as a multi-line string. # # Example: @@ -653,7 +654,7 @@ server: image: null # The number of server agents to run. This determines the fault tolerance of - # the cluster. Please refer to the deployment table (https://developer.hashicorp.com/consul/docs/architecture/consensus#deployment-table) + # the cluster. Please refer to the [deployment table](https://developer.hashicorp.com/consul/docs/architecture/consensus#deployment-table) # for more information. replicas: 1 @@ -695,8 +696,8 @@ server: # Vault Secrets backend: # If you are using Vault as a secrets backend, a Vault Policy must be created which allows `["create", "update"]` # capabilities on the PKI issuing endpoint, which is usually of the form `pki/issue/consul-server`. - # Please refer to the following guide for steps to generate a compatible certificate: - # https://developer.hashicorp.com/consul/tutorials/vault-secure/vault-pki-consul-secure-tls + # Complete [this tutorial](https://developer.hashicorp.com/consul/tutorials/vault-secure/vault-pki-consul-secure-tls) + # to learn how to generate a compatible certificate. # Note: when using TLS, both the `server.serverCert` and `global.tls.caCert` which points to the CA endpoint of this PKI engine # must be provided. serverCert: @@ -749,7 +750,7 @@ server: # @type: string storageClass: null - # This will enable/disable Connect (https://developer.hashicorp.com/consul/docs/connect). Setting this to true + # This will enable/disable [Connect](https://developer.hashicorp.com/consul/docs/connect). Setting this to true # _will not_ automatically secure pod communication, this # setting will only enable usage of the feature. Consul will automatically initialize # a new CA and set of certificates. Additional Connect settings can be configured @@ -771,7 +772,7 @@ server: # The resource requests (CPU, memory, etc.) # for each of the server agents. This should be a YAML map corresponding to a Kubernetes - # ResourceRequirements (https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#resourcerequirements-v1-core) + # [`ResourceRequirements``](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#resourcerequirements-v1-core) # object. NOTE: The use of a YAML string is deprecated. # # Example: @@ -824,12 +825,13 @@ server: # This value is used to carefully # control a rolling update of Consul server agents. This value specifies the - # partition (https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions) - # for performing a rolling update. Please read the linked Kubernetes documentation - # and https://developer.hashicorp.com/consul/docs/k8s/upgrade#upgrading-consul-servers for more information. + # [partition](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions) + # for performing a rolling update. Please read the linked Kubernetes + # and [Upgrade Consul](https://developer.hashicorp.com/consul/docs/k8s/upgrade#upgrading-consul-servers) + # documentation for more information. updatePartition: 0 - # This configures the PodDisruptionBudget (https://kubernetes.io/docs/tasks/run-application/configure-pdb/) + # This configures the [`PodDisruptionBudget`](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) # for the server cluster. disruptionBudget: # Enables registering a PodDisruptionBudget for the server @@ -845,7 +847,7 @@ server: # @type: integer maxUnavailable: null - # A raw string of extra JSON configuration (https://developer.hashicorp.com/consul/docs/agent/config/config-files) for Consul + # A raw string of extra [JSON configuration](https://developer.hashicorp.com/consul/docs/agent/config/config-files) for Consul # servers. This will be saved as-is into a ConfigMap that is read by the Consul # server agents. This can be used to add additional configuration that # isn't directly exposed by the chart. @@ -908,7 +910,7 @@ server: # @type: array extraContainers: [ ] - # This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + # This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) # for server pods. It defaults to allowing only a single server pod on each node, which # minimizes risk of the cluster becoming unusable if a node is lost. If you need # to run more pods per node (for example, testing on Minikube), set this value @@ -938,13 +940,15 @@ server: topologyKey: kubernetes.io/hostname # Toleration settings for server pods. This - # should be a multi-line string matching the Tolerations - # (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + # should be a multi-line string matching the + # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) + # array in a Pod spec. tolerations: "" # Pod topology spread constraints for server pods. - # This should be a multi-line YAML string matching the `topologySpreadConstraints` array - # (https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) in a Pod Spec. + # This should be a multi-line YAML string matching the + # [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + # array in a Pod Spec. # # This requires K8S >= 1.18 (beta) or 1.19 (stable). # @@ -963,7 +967,7 @@ server: # ``` topologySpreadConstraints: "" - # This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) # labels for server pod assignment, formatted as a multi-line string. # # Example: @@ -977,7 +981,7 @@ server: nodeSelector: null # This value references an existing - # Kubernetes `priorityClassName` (https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) + # Kubernetes [`priorityClassName`](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) # that can be assigned to server pods. priorityClassName: "" @@ -1062,15 +1066,15 @@ server: # @type: map extraEnvironmentVars: { } - # [Enterprise Only] Values for setting up and running snapshot agents - # (https://developer.hashicorp.com/consul/commands/snapshot/agent) + # [Enterprise Only] Values for setting up and running + # [snapshot agents](https://developer.hashicorp.com/consul/commands/snapshot/agent) # within the Consul clusters. They run as a sidecar with Consul servers. snapshotAgent: # If true, the chart will install resources necessary to run the snapshot agent. enabled: false # Interval at which to perform snapshots. - # Refer to https://developer.hashicorp.com/consul/commands/snapshot/agent#interval + # Refer to [`interval`](https://developer.hashicorp.com/consul/commands/snapshot/agent#interval) # @type: string interval: 1h @@ -1184,7 +1188,7 @@ client: # @type: string image: null - # A list of valid `-retry-join` values (https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_retry_join). + # A list of valid [`-retry-join` values](https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_retry_join). # If this is `null` (default), then the clients will attempt to automatically # join the server cluster running within Kubernetes. # This means that with `server.enabled` set to true, clients will automatically @@ -1210,7 +1214,7 @@ client: grpc: true # nodeMeta specifies an arbitrary metadata key/value pair to associate with the node - # (refer to https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_node_meta) + # (refer to [`-node-meta`](https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_node_meta)) nodeMeta: pod-name: ${HOSTNAME} host-ip: ${HOST_IP} @@ -1281,7 +1285,7 @@ client: # @recurse: false tlsInit: null - # A raw string of extra JSON configuration (https://developer.hashicorp.com/consul/docs/agent/config/config-files) for Consul + # A raw string of extra [JSON configuration](https://developer.hashicorp.com/consul/docs/agent/config/config-files) for Consul # clients. This will be saved as-is into a ConfigMap that is read by the Consul # client agents. This can be used to add additional configuration that # isn't directly exposed by the chart. @@ -1386,7 +1390,7 @@ client: affinity: null # This value references an existing - # Kubernetes `priorityClassName` (https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) + # Kubernetes [`priorityClassName`](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) # that can be assigned to client pods. priorityClassName: "" @@ -1422,7 +1426,7 @@ client: # @type: map extraEnvironmentVars: { } - # This value defines the Pod DNS policy (https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy) + # This value defines the [Pod DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy) # for client pods to use. # @type: string dnsPolicy: null @@ -1435,7 +1439,8 @@ client: hostNetwork: false # updateStrategy for the DaemonSet. - # Refer to https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy. + # Refer to the Kubernetes [Daemonset upgrade strategy](https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy) + # documentation. # This should be a multi-line string mapping directly to the updateStrategy # # Example: @@ -1610,7 +1615,7 @@ ui: # @default: global.metrics.enabled enabled: "-" # Provider for metrics. Refer to - # https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_metrics_provider + # [`metrics_provider`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_metrics_provider) # This value is only used if `ui.enabled` is set to true. # @type: string provider: "prometheus" @@ -1620,9 +1625,10 @@ ui: # @type: string baseURL: http://prometheus-server - # Corresponds to https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_dashboard_url_templates configuration. + # Corresponds to [`dashboard_url_templates`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_dashboard_url_templates) + # configuration. dashboardURLTemplates: - # Sets https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_dashboard_url_templates_service. + # Sets [`dashboardURLTemplates.service`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#ui_config_dashboard_url_templates_service). service: "" # Configure the catalog sync process to sync K8S with Consul @@ -1644,8 +1650,8 @@ syncCatalog: image: null # If true, all valid services in K8S are - # synced by default. If false, the service must be annotated - # (https://developer.hashicorp.com/consul/docs/k8s/service-sync#sync-enable-disable) properly to sync. + # synced by default. If false, the service must be [annotated](https://developer.hashicorp.com/consul/docs/k8s/service-sync#enable-and-disable-sync) + # properly to sync. # In either case an annotation can override the default. default: true @@ -1784,7 +1790,7 @@ syncCatalog: # @type: string secretKey: null - # This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) # labels for catalog sync pod assignment, formatted as a multi-line string. # # Example: @@ -1880,7 +1886,7 @@ connectInject: # If true, the injector will inject the # Connect sidecar into all pods by default. Otherwise, pods must specify the - # injection annotation (https://developer.hashicorp.com/consul/docs/k8s/connect#consul-hashicorp-com-connect-inject) + # [injection annotation](https://developer.hashicorp.com/consul/docs/k8s/connect#consul-hashicorp-com-connect-inject) # to opt-in to Connect injection. If this is true, pods can use the same annotation # to explicitly opt-out of injection. default: false @@ -1901,7 +1907,7 @@ connectInject: # Note: This value has no effect if transparent proxy is disabled on the pod. defaultOverwriteProbes: true - # This configures the PodDisruptionBudget (https://kubernetes.io/docs/tasks/run-application/configure-pdb/) + # This configures the [`PodDisruptionBudget`](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) # for the service mesh sidecar injector. disruptionBudget: # This will enable/disable registering a PodDisruptionBudget for the @@ -1992,7 +1998,8 @@ connectInject: runAsUser: 0 # updateStrategy for the CNI installer DaemonSet. - # Refer to https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy. + # Refer to the Kubernetes [Daemonset upgrade strategy](https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy) + # documentation. # This should be a multi-line string mapping directly to the updateStrategy # # Example: @@ -2142,7 +2149,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. + # Kind (Kubernetes In Docker) and [OpenEBS](https://openebs.io/) respectively can provision Pods used to create PVCs. # Note that this exclusion is only supported in Kubernetes v1.21.1+. # # Example: @@ -2243,8 +2250,8 @@ connectInject: # If set to an empty string all service accounts can log in. # This only has effect if ACLs are enabled. # - # Refer to https://developer.hashicorp.com/consul/docs/security/acl/auth-methods#binding-rules - # and https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes#trusted-identity-attributes + # Refer to Auth methods [Binding rules](https://developer.hashicorp.com/consul/docs/security/acl/auth-methods#binding-rules) + # and [Trusted identiy attributes](https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes#trusted-identity-attributes) # for more details. # Requires Consul >= v1.5. aclBindingRuleSelector: "serviceaccount.name!=default" @@ -2462,7 +2469,7 @@ meshGateway: memory: "50Mi" cpu: "50m" - # This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + # This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) # for mesh gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer # a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value # to the value in the example below. @@ -2488,8 +2495,9 @@ meshGateway: tolerations: null # Pod topology spread constraints for mesh gateway pods. - # This should be a multi-line YAML string matching the `topologySpreadConstraints` array - # (https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) in a Pod Spec. + # This should be a multi-line YAML string matching the + # [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + # array in a Pod Spec. # # This requires K8S >= 1.18 (beta) or 1.19 (stable). # @@ -2609,7 +2617,7 @@ ingressGateways: memory: "100Mi" cpu: "100m" - # This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + # This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) # for ingress gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer # a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value # to the value in the example below. @@ -2635,8 +2643,9 @@ ingressGateways: tolerations: null # Pod topology spread constraints for ingress gateway pods. - # This should be a multi-line YAML string matching the `topologySpreadConstraints` array - # (https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) in a Pod Spec. + # This should be a multi-line YAML string matching the + # [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + # array in a Pod Spec. # # This requires K8S >= 1.18 (beta) or 1.19 (stable). # @@ -2739,7 +2748,7 @@ terminatingGateways: memory: "100Mi" cpu: "100m" - # This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + # This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) # for terminating gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer # a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value # to the value in the example below. @@ -2765,8 +2774,9 @@ terminatingGateways: tolerations: null # Pod topology spread constraints for terminating gateway pods. - # This should be a multi-line YAML string matching the `topologySpreadConstraints` array - # (https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) in a Pod Spec. + # This should be a multi-line YAML string matching the + # [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + # array in a Pod Spec. # # This requires K8S >= 1.18 (beta) or 1.19 (stable). # @@ -2858,7 +2868,7 @@ apiGateway: # When true a GatewayClass is configured to automatically work with Consul as installed by helm. enabled: true - # This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) # labels for gateway pod assignment, formatted as a multi-line string. # # Example: @@ -2943,11 +2953,11 @@ apiGateway: annotations: null # This value references an existing - # Kubernetes `priorityClassName` (https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) + # Kubernetes [`priorityClassName`](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#pod-priority) # that can be assigned to api-gateway-controller pods. priorityClassName: "" - # This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) # labels for api-gateway-controller pod assignment, formatted as a multi-line string. # # Example: @@ -2961,7 +2971,7 @@ apiGateway: nodeSelector: null # This value defines the tolerations for api-gateway-controller pod, this should be a multi-line string matching the - # Tolerations (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. # # @type: string tolerations: null @@ -3011,7 +3021,7 @@ webhookCertManager: # @type: string tolerations: null - # This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) # labels for the webhook-cert-manager pod assignment, formatted as a multi-line string. # # Example: From 0ce9fb4f2d3450e4709800c33283d31b4f5bd35c Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Wed, 8 Feb 2023 15:41:31 -0500 Subject: [PATCH 066/592] NET-1750 Fixes ACL init command Consul Login Datacenter mixup (#1881) --- .../api-gateway-controller-deployment.yaml | 8 ++++- charts/consul/templates/client-daemonset.yaml | 6 ++++ .../api-gateway-controller-deployment.bats | 32 +++++++++++++++++-- charts/consul/test/unit/client-daemonset.bats | 23 +++++++++++++ control-plane/subcommand/acl-init/command.go | 2 +- 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index ec64bc3631..a9f1806cc8 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -75,6 +75,7 @@ spec: {{- if .Values.global.acls.manageSystemACLs }} - name: CONSUL_HTTP_TOKEN_FILE value: "/consul/login/acl-token" + # CONSUL_LOGIN_DATACENTER is passed to the gateway that gets created. The controller does not use this to log in - name: CONSUL_LOGIN_DATACENTER value: {{ .Values.global.datacenter }} {{- end }} @@ -240,6 +241,12 @@ spec: fieldPath: metadata.name - name: CONSUL_LOGIN_META value: "component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)" + - name: CONSUL_LOGIN_DATACENTER + {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter }} + value: {{ .Values.global.federation.primaryDatacenter }} + {{- else }} + value: {{ .Values.global.datacenter }} + {{- end}} {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} image: {{ .Values.global.imageK8S }} volumeMounts: @@ -260,7 +267,6 @@ spec: consul-k8s-control-plane acl-init \ {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter }} -auth-method-name={{ template "consul.fullname" . }}-k8s-component-auth-method-{{ .Values.global.datacenter }} \ - -datacenter={{ .Values.global.federation.primaryDatacenter }} \ {{- else }} -auth-method-name={{ template "consul.fullname" . }}-k8s-component-auth-method \ {{- end }} diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 91af3821fc..09a70b394e 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -509,6 +509,12 @@ spec: - name: CONSUL_LOGIN_META value: "component=client,pod=$(NAMESPACE)/$(POD_NAME)" {{- end }} + - name: CONSUL_LOGIN_DATACENTER + {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter }} + value: {{ .Values.global.federation.primaryDatacenter }} + {{- else }} + value: {{ .Values.global.datacenter }} + {{- end}} command: - "/bin/sh" - "-ec" diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index b71b51aee0..2dbcb9e0f1 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -346,7 +346,11 @@ load _helpers [ "${actual}" = "true" ] local actual=$(echo $object | - yq -r '[.env[7].value] | any(contains("5s"))' | tee /dev/stderr) + yq '[.env[3].name] | any(contains("CONSUL_LOGIN_DATACENTER"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '[.env[8].value] | any(contains("5s"))' | tee /dev/stderr) [ "${actual}" = "true" ] } @@ -494,6 +498,30 @@ load _helpers [ "${actual}" = "true" ] } +@test "apiGateway/Deployment: consul login datacenter is set to primary when when federation enabled in non-primary datacenter" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'meshGateway.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.datacenter=dc1' \ + --set 'global.federation.enabled=true' \ + --set 'global.federation.primaryDatacenter=dc2' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[1]' | tee /dev/stderr) + + local actual=$(echo $object | + yq '[.env[3].name] | any(contains("CONSUL_LOGIN_DATACENTER"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '[.env[3].value] | any(contains("dc2"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + @test "apiGateway/Deployment: primary-datacenter flag provided when federation enabled in non-primary datacenter" { cd `chart_dir` local object=$(helm template \ @@ -546,7 +574,7 @@ load _helpers [ "${actual}" = "true" ] local actual=$(echo $object | - yq -r '.command | any(contains("-datacenter=dc1"))' | tee /dev/stderr) + yq '[.env[3].value] | any(contains("dc1"))' | tee /dev/stderr) [ "${actual}" = "true" ] } diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index 6e7a030cb1..4c38207635 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -2127,6 +2127,29 @@ rollingUpdate: [[ "$output" =~ "If global.federation.enabled is true, global.adminPartitions.enabled must be false because they are mutually exclusive" ]] } +@test "client/DaemonSet: consul login datacenter is set to primary when when federation enabled in non-primary datacenter" { + cd `chart_dir` + local object=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'client.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.datacenter=dc1' \ + --set 'global.federation.enabled=true' \ + --set 'global.federation.primaryDatacenter=dc2' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "client-acl-init")' | tee /dev/stderr) + + local actual=$(echo $object | + yq '[.env[11].name] | any(contains("CONSUL_LOGIN_DATACENTER"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '[.env[11].value] | any(contains("dc2"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # extraContainers diff --git a/control-plane/subcommand/acl-init/command.go b/control-plane/subcommand/acl-init/command.go index 2745470da8..af85128ea8 100644 --- a/control-plane/subcommand/acl-init/command.go +++ b/control-plane/subcommand/acl-init/command.go @@ -167,7 +167,7 @@ func (c *Command) Run(args []string) int { loginParams := common.LoginParams{ AuthMethod: c.consul.ConsulLogin.AuthMethod, - Datacenter: c.consul.Datacenter, + Datacenter: c.consul.ConsulLogin.Datacenter, BearerTokenFile: c.consul.ConsulLogin.BearerTokenFile, TokenSinkFile: c.flagTokenSinkFile, Meta: c.consul.ConsulLogin.Meta, From 69ae0ca3351d16fd56aef20436321bf097978a14 Mon Sep 17 00:00:00 2001 From: Semir Patel Date: Thu, 9 Feb 2023 09:16:21 -0600 Subject: [PATCH 067/592] Minor fixes to docs for acceptance tests (#1895) --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61f65f21a3..510d4c3b3f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -986,15 +986,15 @@ Any given test can be run either through GoLand or another IDE, or via command l To run all of the connect tests from command line: ```shell -$ cd acceptance/test -$ go test ./connect/... -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-enterprise -enable-multi-cluster -debug-directory=/tmp/debug -consul-k8s-image=kyleschochenmaier/consul-k8s-acls +$ cd acceptance/tests +$ go test ./connect/... -v -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-enterprise -enable-multi-cluster -debug-directory=/tmp/debug -consul-k8s-image=kyleschochenmaier/consul-k8s-acls ``` When running from command line a few things are important: * Some tests use Enterprise features, in which case you need: * Set environment variables `CONSUL_ENT_LICENSE` and possibly `VAULT_LICENSE`. * Use `-enable-enterprise` on command line when running the test. -* Multi-cluster tests require `-enable-multi-cluster` + `-kubecontext=` + `-secondary-kubecontext=` +* Multi-cluster tests require `-enable-multi-cluster -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2` * Using `.//...` is required as part of the command-line to pick up necessary environmental config. ### Using the framework to debug in an environment From e71a71cbe7426d04d2410bad7205b79788cf418d Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Sun, 12 Feb 2023 22:06:35 -0500 Subject: [PATCH 068/592] Service to service troubleshooting (#1851) - Add troubleshooting commands for 'upstream' and 'proxy' to allow troubleshooting of envoy config. --- acceptance/tests/cli/cli_install_test.go | 42 ++- cli/cmd/troubleshoot/command.go | 26 ++ cli/cmd/troubleshoot/proxy/proxy.go | 282 +++++++++++++++ cli/cmd/troubleshoot/proxy/proxy_test.go | 72 ++++ cli/cmd/troubleshoot/upstreams/upstreams.go | 265 ++++++++++++++ .../troubleshoot/upstreams/upstreams_test.go | 127 +++++++ cli/commands.go | 22 +- cli/go.mod | 85 +++-- cli/go.sum | 342 +++++++++--------- 9 files changed, 1053 insertions(+), 210 deletions(-) create mode 100644 cli/cmd/troubleshoot/command.go create mode 100644 cli/cmd/troubleshoot/proxy/proxy.go create mode 100644 cli/cmd/troubleshoot/proxy/proxy_test.go create mode 100644 cli/cmd/troubleshoot/upstreams/upstreams.go create mode 100644 cli/cmd/troubleshoot/upstreams/upstreams_test.go diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index f8c40d3194..d45093dd59 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -1,6 +1,7 @@ package cli import ( + "context" "fmt" "strings" "testing" @@ -12,6 +13,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ipv4RegEx = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" @@ -21,9 +23,11 @@ const ipv4RegEx = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9] func TestInstall(t *testing.T) { cases := map[string]struct { secure bool + tproxy bool }{ - "not-secure": {secure: false}, - "secure": {secure: true}, + "not-secure": {secure: false, tproxy: false}, + "secure": {secure: true, tproxy: false}, + "not-secure-tproxy": {secure: false, tproxy: true}, } for name, c := range cases { @@ -32,6 +36,7 @@ func TestInstall(t *testing.T) { require.NoError(t, err) cfg := suite.Config() + cfg.EnableTransparentProxy = c.tproxy ctx := suite.Environment().DefaultContext(t) connHelper := connhelper.ConnectHelper{ @@ -83,6 +88,39 @@ func TestInstall(t *testing.T) { } }) + // Troubleshoot: Get the client pod so we can portForward to it and get the 'troubleshoot upstreams' output + clientPod, err := connHelper.Ctx.KubernetesClient(t).CoreV1().Pods(connHelper.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: "app=static-client", + }) + require.NoError(t, err) + + clientPodName := clientPod.Items[0].Name + upstreamsOut, err := cli.Run(t, ctx.KubectlOptions(t), "troubleshoot", "upstreams", "-pod", clientPodName) + logger.Log(t, string(upstreamsOut)) + require.NoError(t, err) + + if c.tproxy { + // If tproxy is enabled we are looking for the upstream ip which is the ClusterIP of the Kubernetes Service + serverService, err := connHelper.Ctx.KubernetesClient(t).CoreV1().Services(connHelper.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ + FieldSelector: "metadata.name=static-server", + }) + require.NoError(t, err) + serverIP := serverService.Items[0].Spec.ClusterIP + + proxyOut, err := cli.Run(t, ctx.KubectlOptions(t), "troubleshoot", "proxy", "-pod", clientPodName, "-upstream-ip", serverIP) + require.NoError(t, err) + require.Regexp(t, "upstream resources are valid", string(proxyOut)) + logger.Log(t, string(proxyOut)) + } else { + // With tproxy disabled and explicit upstreams we need the envoy-id of the server + require.Regexp(t, "static-server", string(upstreamsOut)) + + proxyOut, err := cli.Run(t, ctx.KubectlOptions(t), "troubleshoot", "proxy", "-pod", clientPodName, "-upstream-envoy-id", "static-server") + require.NoError(t, err) + require.Regexp(t, "upstream resources are valid", string(proxyOut)) + logger.Log(t, string(proxyOut)) + } + connHelper.TestConnectionSuccess(t) connHelper.TestConnectionFailureWhenUnhealthy(t) }) diff --git a/cli/cmd/troubleshoot/command.go b/cli/cmd/troubleshoot/command.go new file mode 100644 index 0000000000..d37c66e998 --- /dev/null +++ b/cli/cmd/troubleshoot/command.go @@ -0,0 +1,26 @@ +package troubleshoot + +import ( + "fmt" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/mitchellh/cli" +) + +// TroubleshootCommand provides a synopsis for the troubleshoot subcommands (e.g. proxy, upstreams). +type TroubleshootCommand struct { + *common.BaseCommand +} + +// Run prints out information about the subcommands. +func (c *TroubleshootCommand) Run([]string) int { + return cli.RunResultHelp +} + +func (c *TroubleshootCommand) Help() string { + return fmt.Sprintf("%s\n\nUsage: consul-k8s troubleshoot ", c.Synopsis()) +} + +func (c *TroubleshootCommand) Synopsis() string { + return "Troubleshoot network and security configurations." +} diff --git a/cli/cmd/troubleshoot/proxy/proxy.go b/cli/cmd/troubleshoot/proxy/proxy.go new file mode 100644 index 0000000000..d17c491f5d --- /dev/null +++ b/cli/cmd/troubleshoot/proxy/proxy.go @@ -0,0 +1,282 @@ +package proxy + +import ( + "fmt" + "net" + "strings" + "sync" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + troubleshoot "github.com/hashicorp/consul/troubleshoot/proxy" + "github.com/posener/complete" + helmCLI "helm.sh/helm/v3/pkg/cli" + "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +const ( + defaultAdminPort int = 19000 + flagNameKubeConfig = "kubeconfig" + flagNameKubeContext = "context" + flagNameNamespace = "namespace" + flagNamePod = "pod" + flagNameUpstreamEnvoyID = "upstream-envoy-id" + flagNameUpstreamIP = "upstream-ip" + DebugColor = "\033[0;36m%s\033[0m" +) + +type ProxyCommand struct { + *common.BaseCommand + + kubernetes kubernetes.Interface + + set *flag.Sets + + flagKubeConfig string + flagKubeContext string + flagNamespace string + + flagPod string + flagUpstreamEnvoyID string + flagUpstreamIP string + + restConfig *rest.Config + + once sync.Once + help string +} + +// init sets up flags and help text for the command. +func (c *ProxyCommand) init() { + c.set = flag.NewSets() + f := c.set.NewSet("Command Options") + + f.StringVar(&flag.StringVar{ + Name: flagNamePod, + Target: &c.flagPod, + Usage: "The pod to port-forward to.", + Aliases: []string{"p"}, + }) + + f.StringVar(&flag.StringVar{ + Name: flagNameUpstreamEnvoyID, + Target: &c.flagUpstreamEnvoyID, + Usage: "The envoy identifier of the upstream service that receives the communication. (explicit upstreams only)", + Aliases: []string{"id"}, + }) + + f.StringVar(&flag.StringVar{ + Name: flagNameUpstreamIP, + Target: &c.flagUpstreamIP, + Usage: "The IP address of the upstream service that receives the communication. (transparent proxy only)", + Aliases: []string{"ip"}, + }) + + f = c.set.NewSet("Global Options") + f.StringVar(&flag.StringVar{ + Name: flagNameKubeConfig, + Aliases: []string{"c"}, + Target: &c.flagKubeConfig, + Default: "", + Usage: "Set the path to kubeconfig file.", + }) + f.StringVar(&flag.StringVar{ + Name: flagNameKubeContext, + Target: &c.flagKubeContext, + Default: "", + Usage: "Set the Kubernetes context to use.", + }) + + f.StringVar(&flag.StringVar{ + Name: flagNameNamespace, + Target: &c.flagNamespace, + Usage: "The namespace the pod is in.", + Aliases: []string{"n"}, + }) + + c.help = c.set.Help() +} + +// Run executes the list command. +func (c *ProxyCommand) Run(args []string) int { + c.once.Do(c.init) + c.Log.ResetNamed("list") + defer common.CloseWithError(c.BaseCommand) + + // Parse the command line flags. + if err := c.set.Parse(args); err != nil { + c.UI.Output("Error parsing arguments: %v", err.Error(), terminal.WithErrorStyle()) + return 1 + } + + // Validate the command line flags. + if err := c.validateFlags(); err != nil { + c.UI.Output("Invalid argument: %v", err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if c.kubernetes == nil { + if err := c.initKubernetes(); err != nil { + c.UI.Output("Error initializing Kubernetes client: %v", err.Error(), terminal.WithErrorStyle()) + return 1 + } + } + + if err := c.Troubleshoot(); err != nil { + c.UI.Output("Error running troubleshoot: %v", err.Error(), terminal.WithErrorStyle()) + return 1 + } + + return 0 +} + +// validateFlags ensures that the flags passed in by the can be used. +func (c *ProxyCommand) validateFlags() error { + + if (c.flagUpstreamEnvoyID == "" && c.flagUpstreamIP == "") || (c.flagUpstreamEnvoyID != "" && c.flagUpstreamIP != "") { + return fmt.Errorf("-upstream-envoy-id OR -upstream-ip is required.\n Please run `consul troubleshoot upstreams` to find the corresponding upstream.") + } + + if c.flagPod == "" { + return fmt.Errorf("-pod flag is required") + } + + if errs := validation.ValidateNamespaceName(c.flagNamespace, false); c.flagNamespace != "" && len(errs) > 0 { + return fmt.Errorf("invalid namespace name passed for -namespace/-n: %v", strings.Join(errs, "; ")) + } + + return nil +} + +// initKubernetes initializes the Kubernetes client. +func (c *ProxyCommand) initKubernetes() (err error) { + settings := helmCLI.New() + + if c.flagKubeConfig != "" { + settings.KubeConfig = c.flagKubeConfig + } + + if c.flagKubeContext != "" { + settings.KubeContext = c.flagKubeContext + } + + if c.restConfig == nil { + if c.restConfig, err = settings.RESTClientGetter().ToRESTConfig(); err != nil { + return fmt.Errorf("error creating Kubernetes REST config %v", err) + } + } + + if c.kubernetes == nil { + if c.kubernetes, err = kubernetes.NewForConfig(c.restConfig); err != nil { + return fmt.Errorf("error creating Kubernetes client %v", err) + } + } + + if c.flagNamespace == "" { + c.flagNamespace = settings.Namespace() + } + + return nil +} + +func (c *ProxyCommand) Troubleshoot() error { + pf := common.PortForward{ + Namespace: c.flagNamespace, + PodName: c.flagPod, + RemotePort: defaultAdminPort, + KubeClient: c.kubernetes, + RestConfig: c.restConfig, + } + + endpoint, err := pf.Open(c.Ctx) + if err != nil { + return err + } + defer pf.Close() + + adminAddr, adminPort, err := net.SplitHostPort(endpoint) + if err != nil { + return err + } + + adminAddrIP, err := net.ResolveIPAddr("ip", adminAddr) + if err != nil { + return err + } + + t, err := troubleshoot.NewTroubleshoot(adminAddrIP, adminPort) + if err != nil { + return err + } + + // err = t.GetEnvoyConfigDump() + // if err != nil { + // return err + // } + + messages, err := t.RunAllTests(c.flagUpstreamEnvoyID, c.flagUpstreamIP) + if err != nil { + return err + } + + c.UI.Output("Validation", terminal.WithHeaderStyle()) + for _, o := range messages { + if o.Success { + c.UI.Output(o.Message, terminal.WithSuccessStyle()) + } else { + c.UI.Output(o.Message, terminal.WithErrorStyle()) + if o.PossibleActions != "" { + c.UI.Output(fmt.Sprintf("possible actions: %v", o.PossibleActions), terminal.WithInfoStyle()) + } + } + } + + return nil +} + +// AutocompleteFlags returns a mapping of supported flags and autocomplete +// options for this command. The map key for the Flags map should be the +// complete flag such as "-foo" or "--foo". +func (c *ProxyCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + fmt.Sprintf("-%s", flagNameNamespace): complete.PredictNothing, + fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), + fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, + } +} + +// AutocompleteArgs returns the argument predictor for this command. +// Since argument completion is not supported, this will return +// complete.PredictNothing. +func (c *ProxyCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *ProxyCommand) Synopsis() string { + return synopsis +} + +func (c *ProxyCommand) Help() string { + return help +} + +const ( + synopsis = "Troubleshoots service mesh issues." + help = ` +Usage: consul-k8s troubleshoot proxy [options] + + Connect to a pod with a proxy and troubleshoots service mesh communication issues. + + Requires a pod and upstream service SNI. + + Examples: + $ consul-k8s troubleshoot proxy -pod pod1 -upstream foo + + where 'pod1' is the pod running a consul proxy and 'foo' is the upstream envoy ID which + can be obtained by running: + $ consul-k8s troubleshoot upstreams [options] +` +) diff --git a/cli/cmd/troubleshoot/proxy/proxy_test.go b/cli/cmd/troubleshoot/proxy/proxy_test.go new file mode 100644 index 0000000000..784cc7a136 --- /dev/null +++ b/cli/cmd/troubleshoot/proxy/proxy_test.go @@ -0,0 +1,72 @@ +package proxy + +import ( + "bytes" + "context" + "io" + "os" + "testing" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "k8s.io/client-go/kubernetes/fake" +) + +func TestFlagParsing(t *testing.T) { + cases := map[string]struct { + args []string + out int + }{ + "No args, should fail": { + args: []string{}, + out: 1, + }, + "Nonexistent flag passed, -foo bar, should fail": { + args: []string{"-foo", "bar"}, + out: 1, + }, + "Invalid argument passed, -namespace notaname, should fail": { + args: []string{"-namespace", "notaname"}, + out: 1, + }, + "Cannot pass both -upstream-envoy-id and -upstream-ip flags, should fail": { + args: []string{"-upstream-envoy-id", "1234", "-upstream-ip", "127.0.0.1"}, + out: 1, + }, + "Cannot pass empty -upstream-envoy-id and -upstream-ip flags, should fail": { + args: []string{"-upstream-envoy-id", "-upstream-ip"}, + out: 1, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := setupCommand(new(bytes.Buffer)) + c.kubernetes = fake.NewSimpleClientset() + out := c.Run(tc.args) + require.Equal(t, tc.out, out) + }) + } +} + +func setupCommand(buf io.Writer) *ProxyCommand { + // 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 := &ProxyCommand{ + BaseCommand: &common.BaseCommand{ + Log: log, + UI: terminal.NewUI(context.Background(), buf), + }, + } + command.init() + + return command +} diff --git a/cli/cmd/troubleshoot/upstreams/upstreams.go b/cli/cmd/troubleshoot/upstreams/upstreams.go new file mode 100644 index 0000000000..8b20928c7a --- /dev/null +++ b/cli/cmd/troubleshoot/upstreams/upstreams.go @@ -0,0 +1,265 @@ +package upstreams + +import ( + "fmt" + "net" + "strconv" + "strings" + "sync" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + troubleshoot "github.com/hashicorp/consul/troubleshoot/proxy" + "github.com/posener/complete" + helmCLI "helm.sh/helm/v3/pkg/cli" + "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +const ( + defaultAdminPort int = 19000 + flagNameKubeConfig = "kubeconfig" + flagNameKubeContext = "context" + flagNameNamespace = "namespace" + flagNamePod = "pod" +) + +type UpstreamsCommand struct { + *common.BaseCommand + + kubernetes kubernetes.Interface + + set *flag.Sets + + flagKubeConfig string + flagKubeContext string + flagNamespace string + + flagPod string + + restConfig *rest.Config + + once sync.Once + help string +} + +// init sets up flags and help text for the command. +func (c *UpstreamsCommand) init() { + c.set = flag.NewSets() + f := c.set.NewSet("Command Options") + + f.StringVar(&flag.StringVar{ + Name: flagNamePod, + Target: &c.flagPod, + Usage: "The pod to port-forward to.", + Aliases: []string{"p"}, + }) + + f = c.set.NewSet("Global Options") + f.StringVar(&flag.StringVar{ + Name: flagNameKubeConfig, + Aliases: []string{"c"}, + Target: &c.flagKubeConfig, + Default: "", + Usage: "Set the path to kubeconfig file.", + }) + f.StringVar(&flag.StringVar{ + Name: flagNameKubeContext, + Target: &c.flagKubeContext, + Default: "", + Usage: "Set the Kubernetes context to use.", + }) + + f.StringVar(&flag.StringVar{ + Name: flagNameNamespace, + Target: &c.flagNamespace, + Usage: "The namespace the pod is in.", + Aliases: []string{"n"}, + }) + + c.help = c.set.Help() +} + +// Run executes the list command. +func (c *UpstreamsCommand) Run(args []string) int { + c.once.Do(c.init) + c.Log.ResetNamed("list") + defer common.CloseWithError(c.BaseCommand) + + // Parse the command line flags. + if err := c.set.Parse(args); err != nil { + c.UI.Output("Error parsing arguments: %v", err.Error(), terminal.WithErrorStyle()) + return 1 + } + + // Validate the command line flags. + if err := c.validateFlags(); err != nil { + c.UI.Output("Invalid argument: %v", err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if c.kubernetes == nil { + if err := c.initKubernetes(); err != nil { + c.UI.Output("Error initializing Kubernetes client: %v", err.Error(), terminal.WithErrorStyle()) + return 1 + } + } + + if err := c.Troubleshoot(); err != nil { + c.UI.Output("Error running troubleshoot: %v", err.Error(), terminal.WithErrorStyle()) + return 1 + } + + return 0 +} + +// validateFlags ensures that the flags passed in by the can be used. +func (c *UpstreamsCommand) validateFlags() error { + + if c.flagPod == "" { + return fmt.Errorf("-pod flag is required") + } + + if errs := validation.ValidateNamespaceName(c.flagNamespace, false); c.flagNamespace != "" && len(errs) > 0 { + return fmt.Errorf("invalid namespace name passed for -namespace/-n: %v", strings.Join(errs, "; ")) + } + + return nil +} + +// initKubernetes initializes the Kubernetes client. +func (c *UpstreamsCommand) initKubernetes() (err error) { + settings := helmCLI.New() + + if c.flagKubeConfig != "" { + settings.KubeConfig = c.flagKubeConfig + } + + if c.flagKubeContext != "" { + settings.KubeContext = c.flagKubeContext + } + + if c.restConfig == nil { + if c.restConfig, err = settings.RESTClientGetter().ToRESTConfig(); err != nil { + return fmt.Errorf("error creating Kubernetes REST config %v", err) + } + } + + if c.kubernetes == nil { + if c.kubernetes, err = kubernetes.NewForConfig(c.restConfig); err != nil { + return fmt.Errorf("error creating Kubernetes client %v", err) + } + } + + if c.flagNamespace == "" { + c.flagNamespace = settings.Namespace() + } + + return nil +} + +func (c *UpstreamsCommand) Troubleshoot() error { + pf := common.PortForward{ + Namespace: c.flagNamespace, + PodName: c.flagPod, + RemotePort: defaultAdminPort, + KubeClient: c.kubernetes, + RestConfig: c.restConfig, + } + + endpoint, err := pf.Open(c.Ctx) + if err != nil { + return fmt.Errorf("error opening endpoint: %v", err) + } + defer pf.Close() + + adminAddr, adminPort, err := net.SplitHostPort(endpoint) + if err != nil { + return fmt.Errorf("error splitting hostport: %v", err) + } + + adminAddrIP, err := net.ResolveIPAddr("ip", adminAddr) + if err != nil { + return fmt.Errorf("error resolving ip address: %v", err) + } + + t, err := troubleshoot.NewTroubleshoot(adminAddrIP, adminPort) + if err != nil { + return fmt.Errorf("error creating new troubleshoot: %v", err) + } + + envoyIDs, upstreamIPs, err := t.GetUpstreams() + if err != nil { + return fmt.Errorf("error getting upstreams: %v", err) + } + + c.UI.Output(fmt.Sprintf("Envoy Identifiers (explicit upstreams only) (%v)", len(envoyIDs)), terminal.WithHeaderStyle()) + for _, e := range envoyIDs { + c.UI.Output(e) + } + + c.UI.Output(fmt.Sprintf("Upstream IPs (transparent proxy only) (%v)", len(upstreamIPs)), terminal.WithHeaderStyle()) + table := terminal.NewTable("IPs ", "Virtual ", "Cluster Names") + for _, u := range upstreamIPs { + table.AddRow([]string{formatIPs(u.IPs), strconv.FormatBool(u.IsVirtual), formatClusterNames(u.ClusterNames)}, []string{}) + } + c.UI.Table(table) + + return nil +} + +// AutocompleteFlags returns a mapping of supported flags and autocomplete +// options for this command. The map key for the Flags map should be the +// complete flag such as "-foo" or "--foo". +func (c *UpstreamsCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + fmt.Sprintf("-%s", flagNameNamespace): complete.PredictNothing, + fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), + fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, + } +} + +// AutocompleteArgs returns the argument predictor for this command. +// Since argument completion is not supported, this will return +// complete.PredictNothing. +func (c *UpstreamsCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *UpstreamsCommand) Synopsis() string { + return synopsis +} + +func (c *UpstreamsCommand) Help() string { + return help +} + +func formatIPs(ips []string) string { + return strings.Join(ips, ", ") +} + +func formatClusterNames(names map[string]struct{}) string { + var out []string + for k := range names { + out = append(out, k) + } + return strings.Join(out, ", ") +} + +const ( + synopsis = "Connect to a pod with a proxy and gather upstream services." + help = ` +Usage: consul-k8s troubleshoot upstreams [options] + + Connect to a pod with a proxy and gather upstream services. + + Requires a pod. + + Examples: + $ consul-k8s troubleshoot upstreams -pod pod1 + + where 'pod1' is the pod running a consul proxy +` +) diff --git a/cli/cmd/troubleshoot/upstreams/upstreams_test.go b/cli/cmd/troubleshoot/upstreams/upstreams_test.go new file mode 100644 index 0000000000..f5ddefbd28 --- /dev/null +++ b/cli/cmd/troubleshoot/upstreams/upstreams_test.go @@ -0,0 +1,127 @@ +package upstreams + +import ( + "bytes" + "context" + "io" + "os" + "testing" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "k8s.io/client-go/kubernetes/fake" +) + +func TestFlagParsing(t *testing.T) { + cases := map[string]struct { + args []string + out int + }{ + "No args, should fail": { + args: []string{}, + out: 1, + }, + "Nonexistent flag passed, -foo bar, should fail": { + args: []string{"-foo", "bar"}, + out: 1, + }, + "Invalid argument passed, -namespace notaname, should fail": { + args: []string{"-namespace", "notaname"}, + out: 1, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := setupCommand(new(bytes.Buffer)) + c.kubernetes = fake.NewSimpleClientset() + out := c.Run(tc.args) + require.Equal(t, tc.out, out) + }) + } +} + +func TestFormatIPs(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + actual []string + expected string + }{ + { + name: "single IPs", + actual: []string{"1.1.1.1"}, + expected: "1.1.1.1", + }, + + { + name: "several IPs", + actual: []string{"1.1.1.1", "2.2.2.2"}, + expected: "1.1.1.1, 2.2.2.2", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := formatIPs(c.actual) + if c.expected != got { + t.Errorf("expected %v, got %v", c.expected, got) + } + }) + } +} + +func TestFormatClusterNames(t *testing.T) { + cases := []struct { + name string + actual map[string]struct{} + expected string + }{ + { + name: "single cluster", + actual: map[string]struct{}{ + "cluster1": {}, + }, + expected: "cluster1", + }, + { + name: "several clusters", + actual: map[string]struct{}{ + "cluster1": {}, + "cluster2": {}, + "cluster3": {}, + }, + expected: "cluster1, cluster2, cluster3", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := formatClusterNames(c.actual) + if c.expected != got { + t.Errorf("expected %v, got %v", c.expected, got) + } + }) + } +} + +func setupCommand(buf io.Writer) *UpstreamsCommand { + // 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 := &UpstreamsCommand{ + BaseCommand: &common.BaseCommand{ + Log: log, + UI: terminal.NewUI(context.Background(), buf), + }, + } + command.init() + + return command +} diff --git a/cli/commands.go b/cli/commands.go index 2946873e51..fe4c47400e 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -9,6 +9,9 @@ import ( "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/status" + "github.com/hashicorp/consul-k8s/cli/cmd/troubleshoot" + troubleshoot_proxy "github.com/hashicorp/consul-k8s/cli/cmd/troubleshoot/proxy" + "github.com/hashicorp/consul-k8s/cli/cmd/troubleshoot/upstreams" "github.com/hashicorp/consul-k8s/cli/cmd/uninstall" "github.com/hashicorp/consul-k8s/cli/cmd/upgrade" cmdversion "github.com/hashicorp/consul-k8s/cli/cmd/version" @@ -63,13 +66,28 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm BaseCommand: baseCommand, }, nil }, + "proxy log": func() (cli.Command, error) { + return &loglevel.LogLevelCommand{ + BaseCommand: baseCommand, + }, nil + }, "proxy read": func() (cli.Command, error) { return &read.ReadCommand{ BaseCommand: baseCommand, }, nil }, - "proxy log": func() (cli.Command, error) { - return &loglevel.LogLevelCommand{ + "troubleshoot": func() (cli.Command, error) { + return &troubleshoot.TroubleshootCommand{ + BaseCommand: baseCommand, + }, nil + }, + "troubleshoot proxy": func() (cli.Command, error) { + return &troubleshoot_proxy.ProxyCommand{ + BaseCommand: baseCommand, + }, nil + }, + "troubleshoot upstreams": func() (cli.Command, error) { + return &upstreams.UpstreamsCommand{ BaseCommand: baseCommand, }, nil }, diff --git a/cli/go.mod b/cli/go.mod index 2fbe57f6c5..7c1a5af4b8 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -8,15 +8,16 @@ require ( github.com/fatih/color v1.13.0 github.com/google/go-cmp v0.5.8 github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 - github.com/hashicorp/go-hclog v0.16.2 + github.com/hashicorp/consul/troubleshoot v0.0.0-20230210154717-4f2ce606547b + github.com/hashicorp/go-hclog v1.2.1 github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc github.com/kr/text v0.2.0 - github.com/mattn/go-isatty v0.0.14 + github.com/mattn/go-isatty v0.0.16 github.com/mitchellh/cli v1.1.2 github.com/olekukonko/tablewriter v0.0.5 github.com/posener/complete v1.2.3 - github.com/stretchr/testify v1.7.2 - golang.org/x/text v0.3.7 + github.com/stretchr/testify v1.8.0 + golang.org/x/text v0.5.0 helm.sh/helm/v3 v3.9.4 k8s.io/api v0.25.0 k8s.io/apiextensions-apiserver v0.25.0 @@ -27,6 +28,8 @@ require ( sigs.k8s.io/yaml v1.3.0 ) +require go.opentelemetry.io/proto/otlp v0.11.0 // indirect + require ( cloud.google.com/go v0.99.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect @@ -46,14 +49,17 @@ require ( github.com/Masterminds/squirrel v1.5.3 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/armon/go-metrics v0.3.10 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect + 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.3.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect + github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc // indirect github.com/containerd/containerd v1.6.6 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/cli v20.10.17+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/docker v20.10.17+incompatible // indirect @@ -62,24 +68,25 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect + github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-errors/errors v1.0.1 // indirect - github.com/go-gorp/gorp/v3 v3.0.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.2.3 // indirect - github.com/go-openapi/analysis v0.20.0 // indirect + github.com/go-openapi/analysis v0.21.2 // indirect github.com/go-openapi/errors v0.20.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/loads v0.20.2 // indirect - github.com/go-openapi/runtime v0.19.24 // indirect - github.com/go-openapi/spec v0.20.3 // indirect - github.com/go-openapi/strfmt v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect - github.com/go-openapi/validate v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/loads v0.21.1 // indirect + github.com/go-openapi/runtime v0.24.1 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/strfmt v0.21.3 // indirect + github.com/go-openapi/swag v0.21.1 // indirect + github.com/go-openapi/validate v0.21.0 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect - github.com/go-stack/stack v1.8.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect @@ -88,15 +95,22 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // 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.10.1-0.20230209203402-db2bd404bf72 // indirect + github.com/hashicorp/consul/envoyextensions v0.0.0-20230210154717-4f2ce606547b // indirect github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.1 // 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/golang-lru v0.5.4 // indirect + github.com/hashicorp/serf v0.10.1 // indirect github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -104,16 +118,17 @@ require ( github.com/klauspost/compress v1.13.6 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lib/pq v1.10.6 // indirect + github.com/lib/pq v1.10.7 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.6 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect + 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.2-0.20181231171920-c182affec369 // 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 github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect + 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 @@ -123,12 +138,14 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oklog/ulid v1.3.1 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -145,19 +162,19 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect - go.mongodb.org/mongo-driver v1.4.6 // indirect - go.starlark.net v0.0.0-20200707032745-474f21a9602d // indirect - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + go.mongodb.org/mongo-driver v1.11.1 // indirect + go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/net v0.4.0 // indirect + golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect - google.golang.org/grpc v1.47.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect + google.golang.org/grpc v1.49.0 // indirect + google.golang.org/protobuf v1.28.1 // 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 diff --git a/cli/go.sum b/cli/go.sum index bc940a4844..d736c0176a 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -71,6 +71,7 @@ github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -90,34 +91,30 @@ github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6 github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -135,6 +132,8 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -144,15 +143,20 @@ github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +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/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc h1:PYXxkRUBGUMa5xgMVMDl62vEklZvKpVaxQeN9ie7Hfk= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= @@ -167,8 +171,9 @@ github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= @@ -185,7 +190,6 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= @@ -203,8 +207,12 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 h1:xvqufLtNVwAhN8NMyWklVgxnWohi+wtMGQMhtxexlm0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -212,6 +220,7 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= @@ -225,15 +234,14 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -246,104 +254,45 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= -github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk= -github.com/go-openapi/analysis v0.20.0 h1:UN09o0kNhleunxW7LR+KnltD0YrJ8FF03pSqvAN3Vro= -github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU= +github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= -github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= -github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= -github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= -github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4= -github.com/go-openapi/loads v0.20.2 h1:z5p5Xf5wujMxS1y8aP+vxwW5qYT2zdJBbXKmQUG3lcc= -github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= -github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= -github.com/go-openapi/runtime v0.19.24 h1:TqagMVlRAOTwllE/7hNKx6rQ10O6T8ZzeJdMjSTKaD4= -github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= -github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= -github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ= -github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ= -github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= -github.com/go-openapi/strfmt v0.20.0 h1:l2omNtmNbMc39IGptl9BuXBEKcZfS8zjrTsPKTiJiDM= -github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0= +github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= +github.com/go-openapi/runtime v0.24.1 h1:Sml5cgQKGYQHF+M7yYSHaH1eOjvTykrddTE/KtQVjqo= +github.com/go-openapi/runtime v0.24.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= +github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= +github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= +github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= +github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M= -github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= -github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= -github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= -github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= -github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= -github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts= -github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI= +github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= @@ -468,11 +417,10 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -489,34 +437,61 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWet github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.10.1-0.20230209203402-db2bd404bf72 h1:O+z5m5kNtu6NHBMwMsRb1S0P7giqNu5vBBeCzgiAesg= +github.com/hashicorp/consul/api v1.10.1-0.20230209203402-db2bd404bf72/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= +github.com/hashicorp/consul/envoyextensions v0.0.0-20230210154717-4f2ce606547b h1:T+El0UxZP7h2mGL+EPBJejS4gKM/w0KAYOSpTs7hrbY= +github.com/hashicorp/consul/envoyextensions v0.0.0-20230210154717-4f2ce606547b/go.mod h1:oJKG0zAMtq6ZmZNYQyeKh6kIJmi01rZSZDSgnjzZ15w= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU= +github.com/hashicorp/consul/troubleshoot v0.0.0-20230210154717-4f2ce606547b h1:I5zDW3o7KwW4cX5kkerhm7bZOEknlSjdnIgtxnhBxOk= +github.com/hashicorp/consul/troubleshoot v0.0.0-20230210154717-4f2ce606547b/go.mod h1:rskvju2tK8XvHYTAILHjO7lpV1/uViHs3Q3mg9Rkwlg= 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= -github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= +github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +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.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc h1:on26TCKYnX7JzZCtwkR/LWHSqMu40PoZ6h/0e6Pq8ug= github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc/go.mod h1:/9UoDY2FYYA8lFaKBb2HmM/jKYZGANmf65q9QRc/cVw= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= @@ -525,12 +500,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -540,6 +513,7 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -553,10 +527,8 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -567,10 +539,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -580,20 +551,18 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= @@ -603,27 +572,34 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= @@ -632,6 +608,8 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -640,11 +618,11 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -677,6 +655,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +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.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= @@ -695,9 +675,11 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -709,16 +691,18 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 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.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= @@ -731,6 +715,7 @@ github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= @@ -738,6 +723,7 @@ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ 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.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= @@ -746,6 +732,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L 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.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY= github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= @@ -754,8 +741,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= @@ -791,22 +778,28 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -815,6 +808,7 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -825,19 +819,16 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= -go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= -go.mongodb.org/mongo-driver v1.4.6 h1:rh7GdYmDrb8AQSkF8yteAus8qYOgOASWDOv1BWqBXkU= -go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= +go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= +go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= +go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= +go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8= +go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -857,9 +848,11 @@ go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU= +go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.starlark.net v0.0.0-20200707032745-474f21a9602d h1:uFqwFYlX7d5ZSp+IqhXxct0SybXrTzEBDvb2CkEhPBs= -go.starlark.net v0.0.0-20200707032745-474f21a9602d/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= +go.starlark.net v0.0.0-20230128213706-3f75dec8e403 h1:jPeC7Exc+m8OBJUlWbBLh0O5UZPM7yU5W4adnhhbG4U= +go.starlark.net v0.0.0-20230128213706-3f75dec8e403/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -868,25 +861,24 @@ go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -926,14 +918,12 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -943,6 +933,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -955,28 +946,27 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -993,8 +983,9 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= 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= @@ -1018,7 +1009,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1027,15 +1017,15 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1067,11 +1057,13 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1091,11 +1083,18 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 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.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1104,8 +1103,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1113,9 +1113,7 @@ golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1129,12 +1127,11 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1284,8 +1281,8 @@ google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ= +google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1311,9 +1308,9 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -1328,8 +1325,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -1357,6 +1354,7 @@ 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.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= From de88a4186d2b307605f1e1549aeef297b678ff37 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Mon, 23 Jan 2023 15:09:22 -0500 Subject: [PATCH 069/592] Added set command for log levels Move format parsing into envoy package Move enovy to common package, move param parsing to calling package use a LoggerParams struct for handling a format for log changes to envoy refactor to use logger params and methods to set and validate logger and log levels before calling envoy linting changes clean up from rebase Improve comment on envoy logging endpoint function, switched to using '-update-level' for updating envoy log level flag for better usability --- cli/cmd/proxy/loglevel/command.go | 116 ++++++------ cli/cmd/proxy/loglevel/command_test.go | 141 ++++++++------ cli/cmd/proxy/read/command.go | 42 +++-- cli/cmd/proxy/read/command_test.go | 114 ++++++++++-- cli/cmd/proxy/read/filters.go | 14 +- cli/cmd/proxy/read/filters_test.go | 46 ++--- cli/cmd/proxy/read/format.go | 17 +- cli/cmd/proxy/read/format_test.go | 18 +- .../read/config.go => common/envoy/http.go} | 60 +++++- .../envoy/http_test.go} | 57 ++++-- cli/common/envoy/logger_params.go | 166 +++++++++++++++++ cli/common/envoy/logger_params_test.go | 172 ++++++++++++++++++ .../envoy}/testdata/fetch_debug_levels.txt | 0 .../envoy/testdata}/test_clusters.json | 0 .../envoy/testdata}/test_config_dump.json | 0 .../envoy_types.go => common/envoy/types.go} | 2 +- 16 files changed, 753 insertions(+), 212 deletions(-) rename cli/{cmd/proxy/read/config.go => common/envoy/http.go} (91%) rename cli/{cmd/proxy/read/config_test.go => common/envoy/http_test.go} (93%) create mode 100644 cli/common/envoy/logger_params.go create mode 100644 cli/common/envoy/logger_params_test.go rename cli/{cmd/proxy/loglevel => common/envoy}/testdata/fetch_debug_levels.txt (100%) rename cli/{cmd/proxy/read => common/envoy/testdata}/test_clusters.json (100%) rename cli/{cmd/proxy/read => common/envoy/testdata}/test_config_dump.json (100%) rename cli/{cmd/proxy/read/envoy_types.go => common/envoy/types.go} (99%) diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go index 80444d7339..85dc91bfa2 100644 --- a/cli/cmd/proxy/loglevel/command.go +++ b/cli/cmd/proxy/loglevel/command.go @@ -1,39 +1,36 @@ package loglevel import ( - "bytes" "context" "errors" "fmt" - "io" - "net/http" "strings" "sync" - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" "github.com/posener/complete" helmCLI "helm.sh/helm/v3/pkg/cli" "k8s.io/apimachinery/pkg/api/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/envoy" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" ) const ( defaultAdminPort = 19000 flagNameNamespace = "namespace" + flagNameUpdateLevel = "update-level" flagNameKubeConfig = "kubeconfig" flagNameKubeContext = "context" ) -type LoggerConfig map[string]string +var ErrIncorrectArgFormat = errors.New("Exactly one positional argument is required: ") -var ( - ErrIncorrectArgFormat = errors.New("Exactly one positional argument is required: ") - ErrNoLoggersReturned = errors.New("No loggers were returned from Envoy") -) +type LoggerConfig map[string]string var levelToColor = map[string]string{ "trace": terminal.Green, @@ -54,13 +51,14 @@ type LogLevelCommand struct { // Command Flags podName string namespace string + level string kubeConfig string kubeContext string - once sync.Once - help string - restConfig *rest.Config - logLevelFetcher func(context.Context, common.PortForwarder) (LoggerConfig, error) + once sync.Once + help string + restConfig *rest.Config + envoyLoggingCaller func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) } func (l *LogLevelCommand) init() { @@ -74,6 +72,13 @@ func (l *LogLevelCommand) init() { Aliases: []string{"n"}, }) + f.StringVar(&flag.StringVar{ + Name: flagNameUpdateLevel, + Target: &l.level, + Usage: "Update the level for the logger. Can be either `-update-level warning` to change all loggers to warning, or a comma delineated list of loggers with level can be passed like `-update-level grpc:warning,http:info` to only modify specific loggers.", + Aliases: []string{"u"}, + }) + f = l.set.NewSet("Global Options") f.StringVar(&flag.StringVar{ Name: flagNameKubeConfig, @@ -103,8 +108,8 @@ func (l *LogLevelCommand) Run(args []string) int { return l.logOutputAndDie(err) } - if l.logLevelFetcher == nil { - l.logLevelFetcher = FetchLogLevel + if l.envoyLoggingCaller == nil { + l.envoyLoggingCaller = envoy.CallLoggingEndpoint } err = l.initKubernetes() @@ -117,11 +122,11 @@ func (l *LogLevelCommand) Run(args []string) int { return l.logOutputAndDie(err) } - logLevels, err := l.fetchLogLevels(adminPorts) + err = l.fetchOrSetLogLevels(adminPorts) if err != nil { return l.logOutputAndDie(err) } - l.outputLevels(logLevels) + return 0 } @@ -150,6 +155,7 @@ func (l *LogLevelCommand) parseFlags(args []string) error { if err != nil { return err } + return nil } @@ -222,7 +228,7 @@ func (l *LogLevelCommand) fetchAdminPorts() (map[string]int, error) { return adminPorts, nil } -func (l *LogLevelCommand) fetchLogLevels(adminPorts map[string]int) (map[string]LoggerConfig, error) { +func (l *LogLevelCommand) fetchOrSetLogLevels(adminPorts map[string]int) error { loggers := make(map[string]LoggerConfig, 0) for name, port := range adminPorts { @@ -233,61 +239,47 @@ func (l *LogLevelCommand) fetchLogLevels(adminPorts map[string]int) (map[string] KubeClient: l.kubernetes, RestConfig: l.restConfig, } - - logLevels, err := l.logLevelFetcher(l.Ctx, &pf) + params, err := parseParams(l.level) + if err != nil { + return err + } + logLevels, err := l.envoyLoggingCaller(l.Ctx, &pf, params) if err != nil { - return loggers, err + return err } loggers[name] = logLevels } - return loggers, nil -} - -// FetchLogLevel requests the logging endpoint from Envoy Admin Interface for a given port -// more can be read about that endpoint https://www.envoyproxy.io/docs/envoy/latest/operations/admin#post--logging -func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (LoggerConfig, error) { - endpoint, err := portForward.Open(ctx) - if err != nil { - return nil, err - } - - defer portForward.Close() - - // this endpoint does not support returning json, so we've gotta parse the plain text - response, err := http.Post(fmt.Sprintf("http://%s/logging", endpoint), "text/plain", bytes.NewBuffer([]byte{})) - if err != nil { - return nil, err - } - body, err := io.ReadAll(response.Body) - if err != nil { - return nil, fmt.Errorf("failed to reach envoy: %v", err) - } + l.outputLevels(loggers) + return nil +} - if response.StatusCode >= 400 { - return nil, fmt.Errorf("call to envoy failed with status code: %d, and message: %s", response.StatusCode, body) +func parseParams(params string) (*envoy.LoggerParams, error) { + loggerParams := envoy.NewLoggerParams() + if len(params) == 0 { + return loggerParams, nil } - loggers := strings.Split(string(body), "\n") - if len(loggers) == 0 { - return nil, ErrNoLoggersReturned + // contains global log level change + if !strings.Contains(params, ":") { + err := loggerParams.SetGlobalLoggerLevel(params) + if err != nil { + return nil, err + } + return loggerParams, nil } - logLevels := make(map[string]string) - var name string - var level string + // contains changes to at least 1 specific log level + loggerChanges := strings.Split(params, ",") - // the first line here is just a header - for _, logger := range loggers[1:] { - if len(logger) == 0 { - continue + for _, logger := range loggerChanges { + levelValues := strings.Split(logger, ":") + err := loggerParams.SetLoggerLevel(levelValues[0], levelValues[1]) + if err != nil { + return nil, err } - fmt.Sscanf(logger, "%s %s", &name, &level) - name = strings.TrimRight(name, ":") - logLevels[name] = level } - - return logLevels, nil + return loggerParams, nil } func (l *LogLevelCommand) outputLevels(logLevels map[string]LoggerConfig) { diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go index 6207270c20..1c5387fd5f 100644 --- a/cli/cmd/proxy/loglevel/command_test.go +++ b/cli/cmd/proxy/loglevel/command_test.go @@ -5,20 +5,19 @@ import ( "context" "fmt" "io" - "net/http" - "net/http/httptest" "os" "regexp" - "strings" "testing" - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" 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" + "github.com/hashicorp/consul-k8s/cli/common/envoy" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/go-hclog" ) func TestFlagParsingFails(t *testing.T) { @@ -56,7 +55,7 @@ func TestFlagParsingFails(t *testing.T) { t.Run(name, func(t *testing.T) { c := setupCommand(bytes.NewBuffer([]byte{})) c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - c.logLevelFetcher = func(context.Context, common.PortForwarder) (LoggerConfig, error) { + c.envoyLoggingCaller = func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) { return testLogConfig, nil } @@ -84,6 +83,36 @@ func TestFlagParsingSucceeds(t *testing.T) { podNamespace: "another", out: 0, }, + "With single pod name and blanket level": { + args: []string{podName, "-u", "warning"}, + podNamespace: "default", + out: 0, + }, + "With single pod name and single level": { + args: []string{podName, "-u", "grpc:warning"}, + podNamespace: "default", + out: 0, + }, + "With single pod name and multiple levels": { + args: []string{podName, "-u", "grpc:warning,http:info"}, + podNamespace: "default", + out: 0, + }, + "With single pod name and blanket level full flag": { + args: []string{podName, "-update-level", "warning"}, + podNamespace: "default", + out: 0, + }, + "With single pod name and single level full flag": { + args: []string{podName, "-update-level", "grpc:warning"}, + podNamespace: "default", + out: 0, + }, + "With single pod name and multiple levels full flag": { + args: []string{podName, "-update-level", "grpc:warning,http:info"}, + podNamespace: "default", + out: 0, + }, } for name, tc := range testCases { @@ -97,7 +126,7 @@ func TestFlagParsingSucceeds(t *testing.T) { c := setupCommand(bytes.NewBuffer([]byte{})) c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - c.logLevelFetcher = func(context.Context, common.PortForwarder) (LoggerConfig, error) { + c.envoyLoggingCaller = func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) { return testLogConfig, nil } @@ -107,7 +136,7 @@ func TestFlagParsingSucceeds(t *testing.T) { } } -func TestOutputForGettingLogLevel(t *testing.T) { +func TestOutputForGettingLogLevels(t *testing.T) { t.Parallel() podName := "now-this-is-pod-racing" expectedHeader := fmt.Sprintf("Envoy log configuration for %s in namespace default:", podName) @@ -120,12 +149,49 @@ func TestOutputForGettingLogLevel(t *testing.T) { buf := bytes.NewBuffer([]byte{}) c := setupCommand(buf) - c.logLevelFetcher = func(context.Context, common.PortForwarder) (LoggerConfig, error) { + newLogLevel := "warning" + config := make(map[string]string, len(testLogConfig)) + for logger := range testLogConfig { + config[logger] = newLogLevel + } + + c.envoyLoggingCaller = func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) { + return config, nil + } + c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) + + args := []string{podName, "-u", newLogLevel} + out := c.Run(args) + require.Equal(t, 0, out) + + actual := buf.String() + + require.Regexp(t, expectedHeader, actual) + require.Regexp(t, "Log Levels for now-this-is-pod-racing", actual) + for logger, level := range config { + require.Regexp(t, regexp.MustCompile(logger+`.*`+level), actual) + } +} + +func TestOutputForSettingLogLevels(t *testing.T) { + t.Parallel() + podName := "now-this-is-pod-racing" + expectedHeader := fmt.Sprintf("Envoy log configuration for %s in namespace default:", podName) + fakePod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: "default", + }, + } + + buf := bytes.NewBuffer([]byte{}) + c := setupCommand(buf) + c.envoyLoggingCaller = func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) { return testLogConfig, nil } c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - args := []string{podName} + args := []string{podName, "-u", "warning"} out := c.Run(args) require.Equal(t, 0, out) @@ -149,34 +215,24 @@ func TestHelp(t *testing.T) { require.Regexp(t, expectedUsage, actual) } -func TestFetchLogLevel(t *testing.T) { - t.Parallel() - rawLogLevels, err := os.ReadFile("testdata/fetch_debug_levels.txt") - require.NoError(t, err) - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(rawLogLevels) - })) - - defer mockServer.Close() +func setupCommand(buf io.Writer) *LogLevelCommand { + log := hclog.New(&hclog.LoggerOptions{ + Name: "test", + Level: hclog.Debug, + Output: os.Stdout, + }) - mpf := &mockPortForwarder{ - openBehavior: func(ctx context.Context) (string, error) { - return strings.Replace(mockServer.URL, "http://", "", 1), nil + command := &LogLevelCommand{ + BaseCommand: &common.BaseCommand{ + Log: log, + UI: terminal.NewUI(context.Background(), buf), }, } - logLevels, err := FetchLogLevel(context.Background(), mpf) - require.NoError(t, err) - require.Equal(t, testLogConfig, logLevels) -} - -type mockPortForwarder struct { - openBehavior func(context.Context) (string, error) + command.init() + return command } -func (m *mockPortForwarder) Open(ctx context.Context) (string, error) { return m.openBehavior(ctx) } -func (m *mockPortForwarder) Close() {} - -var testLogConfig = LoggerConfig{ +var testLogConfig = map[string]string{ "admin": "debug", "alternate_protocols_cache": "debug", "aws": "debug", @@ -235,20 +291,3 @@ var testLogConfig = LoggerConfig{ "wasm": "debug", "websocket": "debug", } - -func setupCommand(buf io.Writer) *LogLevelCommand { - log := hclog.New(&hclog.LoggerOptions{ - Name: "test", - Level: hclog.Debug, - Output: os.Stdout, - }) - - command := &LogLevelCommand{ - BaseCommand: &common.BaseCommand{ - Log: log, - UI: terminal.NewUI(context.Background(), buf), - }, - } - command.init() - return command -} diff --git a/cli/cmd/proxy/read/command.go b/cli/cmd/proxy/read/command.go index ad2bb96303..e85d7166e9 100644 --- a/cli/cmd/proxy/read/command.go +++ b/cli/cmd/proxy/read/command.go @@ -7,9 +7,6 @@ import ( "strings" "sync" - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" "github.com/posener/complete" helmCLI "helm.sh/helm/v3/pkg/cli" "k8s.io/apimachinery/pkg/api/validation" @@ -17,6 +14,11 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/utils/strings/slices" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/envoy" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" ) // defaultAdminPort is the port where the Envoy admin API is exposed. @@ -67,7 +69,7 @@ type ReadCommand struct { flagKubeConfig string flagKubeContext string - fetchConfig func(context.Context, common.PortForwarder) (*EnvoyConfig, error) + fetchConfig func(context.Context, common.PortForwarder) (*envoy.EnvoyConfig, error) restConfig *rest.Config @@ -77,7 +79,7 @@ type ReadCommand struct { func (c *ReadCommand) init() { if c.fetchConfig == nil { - c.fetchConfig = FetchConfig + c.fetchConfig = envoy.FetchConfig } c.set = flag.NewSets() @@ -320,8 +322,8 @@ func (c *ReadCommand) fetchAdminPorts() (map[string]int, error) { return adminPorts, nil } -func (c *ReadCommand) fetchConfigs(adminPorts map[string]int) (map[string]*EnvoyConfig, error) { - configs := make(map[string]*EnvoyConfig, 0) +func (c *ReadCommand) fetchConfigs(adminPorts map[string]int) (map[string]*envoy.EnvoyConfig, error) { + configs := make(map[string]*envoy.EnvoyConfig, 0) for name, adminPort := range adminPorts { pf := common.PortForward{ @@ -343,7 +345,7 @@ func (c *ReadCommand) fetchConfigs(adminPorts map[string]int) (map[string]*Envoy return configs, nil } -func (c *ReadCommand) outputConfigs(configs map[string]*EnvoyConfig) error { +func (c *ReadCommand) outputConfigs(configs map[string]*envoy.EnvoyConfig) error { switch c.flagOutput { case Table: return c.outputTables(configs) @@ -396,7 +398,7 @@ func (c *ReadCommand) filterWarnings() []string { return warnings } -func (c *ReadCommand) outputTables(configs map[string]*EnvoyConfig) error { +func (c *ReadCommand) outputTables(configs map[string]*envoy.EnvoyConfig) error { if c.flagFQDN != "" || c.flagAddress != "" || c.flagPort != -1 { c.UI.Output("Filters applied", terminal.WithHeaderStyle()) @@ -431,7 +433,7 @@ func (c *ReadCommand) outputTables(configs map[string]*EnvoyConfig) error { return nil } -func (c *ReadCommand) outputJSON(configs map[string]*EnvoyConfig) error { +func (c *ReadCommand) outputJSON(configs map[string]*envoy.EnvoyConfig) error { cfgs := make(map[string]interface{}) for name, config := range configs { cfg := make(map[string]interface{}) @@ -467,11 +469,11 @@ func (c *ReadCommand) outputJSON(configs map[string]*EnvoyConfig) error { return nil } -func (c *ReadCommand) outputRaw(configs map[string]*EnvoyConfig) error { +func (c *ReadCommand) outputRaw(configs map[string]*envoy.EnvoyConfig) error { cfgs := make(map[string]interface{}, 0) for name, config := range configs { var cfg interface{} - if err := json.Unmarshal(config.rawCfg, &cfg); err != nil { + if err := json.Unmarshal(config.RawCfg, &cfg); err != nil { return err } @@ -488,7 +490,7 @@ func (c *ReadCommand) outputRaw(configs map[string]*EnvoyConfig) error { return nil } -func (c *ReadCommand) outputClustersTable(clusters []Cluster) { +func (c *ReadCommand) outputClustersTable(clusters []envoy.Cluster) { if !c.shouldPrintTable(c.flagClusters) { return } @@ -496,14 +498,16 @@ func (c *ReadCommand) outputClustersTable(clusters []Cluster) { c.UI.Output(fmt.Sprintf("Clusters (%d)", len(clusters)), terminal.WithHeaderStyle()) table := terminal.NewTable("Name", "FQDN", "Endpoints", "Type", "Last Updated") for _, cluster := range clusters { - table.AddRow([]string{cluster.Name, cluster.FullyQualifiedDomainName, strings.Join(cluster.Endpoints, ", "), - cluster.Type, cluster.LastUpdated}, []string{}) + table.AddRow([]string{ + cluster.Name, cluster.FullyQualifiedDomainName, strings.Join(cluster.Endpoints, ", "), + cluster.Type, cluster.LastUpdated, + }, []string{}) } c.UI.Table(table) c.UI.Output("") } -func (c *ReadCommand) outputEndpointsTable(endpoints []Endpoint) { +func (c *ReadCommand) outputEndpointsTable(endpoints []envoy.Endpoint) { if !c.shouldPrintTable(c.flagEndpoints) { return } @@ -512,7 +516,7 @@ func (c *ReadCommand) outputEndpointsTable(endpoints []Endpoint) { c.UI.Table(formatEndpoints(endpoints)) } -func (c *ReadCommand) outputListenersTable(listeners []Listener) { +func (c *ReadCommand) outputListenersTable(listeners []envoy.Listener) { if !c.shouldPrintTable(c.flagListeners) { return } @@ -521,7 +525,7 @@ func (c *ReadCommand) outputListenersTable(listeners []Listener) { c.UI.Table(formatListeners(listeners)) } -func (c *ReadCommand) outputRoutesTable(routes []Route) { +func (c *ReadCommand) outputRoutesTable(routes []envoy.Route) { if !c.shouldPrintTable(c.flagRoutes) { return } @@ -530,7 +534,7 @@ func (c *ReadCommand) outputRoutesTable(routes []Route) { c.UI.Table(formatRoutes(routes)) } -func (c *ReadCommand) outputSecretsTable(secrets []Secret) { +func (c *ReadCommand) outputSecretsTable(secrets []envoy.Secret) { if !c.shouldPrintTable(c.flagSecrets) { return } diff --git a/cli/cmd/proxy/read/command_test.go b/cli/cmd/proxy/read/command_test.go index 27f19e7370..1e439bce91 100644 --- a/cli/cmd/proxy/read/command_test.go +++ b/cli/cmd/proxy/read/command_test.go @@ -9,16 +9,18 @@ import ( "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" "github.com/stretchr/testify/require" 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" + "github.com/hashicorp/consul-k8s/cli/common/envoy" + cmnFlag "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/go-hclog" ) func TestFlagParsing(t *testing.T) { @@ -65,38 +67,48 @@ func TestReadCommandOutput(t *testing.T) { // These regular expressions must be present in the output. expectedHeader := fmt.Sprintf("Envoy configuration for %s in namespace default:", podName) expected := map[string][]string{ - "-clusters": {"==> Clusters \\(5\\)", + "-clusters": { + "==> Clusters \\(5\\)", "Name.*FQDN.*Endpoints.*Type.*Last Updated", "local_agent.*192\\.168\\.79\\.187:8502.*STATIC.*2022-05-13T04:22:39\\.553Z", "local_app.*127\\.0\\.0\\.1:8080.*STATIC.*2022-05-13T04:22:39\\.655Z", "client.*client\\.default\\.dc1\\.internal\\.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00\\.consul.*EDS", "frontend.*frontend\\.default\\.dc1\\.internal\\.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00\\.consul", - "original-destination.*ORIGINAL_DST"}, + "original-destination.*ORIGINAL_DST", + }, - "-endpoints": {"==> Endpoints \\(6\\)", + "-endpoints": { + "==> Endpoints \\(6\\)", "Address:Port.*Cluster.*Weight.*Status", "192.168.79.187:8502.*local_agent.*1.00.*HEALTHY", "127.0.0.1:8080.*local_app.*1.00.*HEALTHY", "192.168.18.110:20000.*client.*1.00.*HEALTHY", "192.168.52.101:20000.*client.*1.00.*HEALTHY", "192.168.65.131:20000.*client.*1.00.*HEALTHY", - "192.168.63.120:20000.*frontend.*1.00.*HEALTHY"}, + "192.168.63.120:20000.*frontend.*1.00.*HEALTHY", + }, - "-listeners": {"==> Listeners \\(2\\)", + "-listeners": { + "==> Listeners \\(2\\)", "Name.*Address:Port.*Direction.*Filter Chain Match.*Filters.*Last Updated", "public_listener.*192\\.168\\.69\\.179:20000.*INBOUND.*Any.*\\* -> local_app/", "outbound_listener.*127.0.0.1:15001.*OUTBOUND.*10\\.100\\.134\\.173/32, 240\\.0\\.0\\.3/32.*TCP: -> client", "10\\.100\\.31\\.2/32, 240\\.0\\.0\\.5/32.*TCP: -> frontend", - "Any.*TCP: -> original-destination"}, + "Any.*TCP: -> original-destination", + }, - "-routes": {"==> Routes \\(1\\)", + "-routes": { + "==> Routes \\(1\\)", "Name.*Destination Cluster.*Last Updated", - "public_listener.*local_app/"}, + "public_listener.*local_app/", + }, - "-secrets": {"==> Secrets \\(2\\)", + "-secrets": { + "==> Secrets \\(2\\)", "Name.*Type.*Last Updated", "default.*Dynamic Active", - "ROOTCA.*Dynamic Warming"}, + "ROOTCA.*Dynamic Warming", + }, } cases := map[string][]string{ @@ -122,7 +134,7 @@ func TestReadCommandOutput(t *testing.T) { c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) // A fetchConfig function that just returns the test Envoy config. - c.fetchConfig = func(context.Context, common.PortForwarder) (*EnvoyConfig, error) { + c.fetchConfig = func(context.Context, common.PortForwarder) (*envoy.EnvoyConfig, error) { return testEnvoyConfig, nil } @@ -230,7 +242,7 @@ func TestFilterWarnings(t *testing.T) { buf := new(bytes.Buffer) c := setupCommand(buf) c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - c.fetchConfig = func(context.Context, common.PortForwarder) (*EnvoyConfig, error) { + c.fetchConfig = func(context.Context, common.PortForwarder) (*envoy.EnvoyConfig, error) { return testEnvoyConfig, nil } @@ -295,3 +307,73 @@ func TestTaskCreateCommand_AutocompleteArgs(t *testing.T) { c := cmd.AutocompleteArgs() assert.Equal(t, complete.PredictNothing, c) } + +// testEnvoyConfig is what we expect the config at `test_config_dump.json` to be. + +var testEnvoyConfig = &envoy.EnvoyConfig{ + Clusters: []envoy.Cluster{ + {Name: "local_agent", FullyQualifiedDomainName: "local_agent", Endpoints: []string{"192.168.79.187:8502"}, Type: "STATIC", LastUpdated: "2022-05-13T04:22:39.553Z"}, + + {Name: "client", FullyQualifiedDomainName: "client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", Endpoints: []string{"192.168.18.110:20000", "192.168.52.101:20000", "192.168.65.131:20000"}, Type: "EDS", LastUpdated: "2022-08-10T12:30:32.326Z"}, + + {Name: "frontend", FullyQualifiedDomainName: "frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", Endpoints: []string{"192.168.63.120:20000"}, Type: "EDS", LastUpdated: "2022-08-10T12:30:32.233Z"}, + + {Name: "local_app", FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, Type: "STATIC", LastUpdated: "2022-05-13T04:22:39.655Z"}, + + {Name: "original-destination", FullyQualifiedDomainName: "original-destination", Endpoints: []string{}, Type: "ORIGINAL_DST", LastUpdated: "2022-05-13T04:22:39.743Z"}, + }, + + Endpoints: []envoy.Endpoint{ + {Address: "192.168.79.187:8502", Cluster: "local_agent", Weight: 1, Status: "HEALTHY"}, + + {Address: "192.168.18.110:20000", Cluster: "client", Weight: 1, Status: "HEALTHY"}, + + {Address: "192.168.52.101:20000", Cluster: "client", Weight: 1, Status: "HEALTHY"}, + + {Address: "192.168.65.131:20000", Cluster: "client", Weight: 1, Status: "HEALTHY"}, + + {Address: "192.168.63.120:20000", Cluster: "frontend", Weight: 1, Status: "HEALTHY"}, + + {Address: "127.0.0.1:8080", Cluster: "local_app", Weight: 1, Status: "HEALTHY"}, + }, + + Listeners: []envoy.Listener{ + {Name: "public_listener", Address: "192.168.69.179:20000", FilterChain: []envoy.FilterChain{{Filters: []string{"HTTP: * -> local_app/"}, FilterChainMatch: "Any"}}, Direction: "INBOUND", LastUpdated: "2022-08-10T12:30:47.142Z"}, + + {Name: "outbound_listener", Address: "127.0.0.1:15001", FilterChain: []envoy.FilterChain{ + {Filters: []string{"TCP: -> client"}, FilterChainMatch: "10.100.134.173/32, 240.0.0.3/32"}, + + {Filters: []string{"TCP: -> frontend"}, FilterChainMatch: "10.100.31.2/32, 240.0.0.5/32"}, + + {Filters: []string{"TCP: -> original-destination"}, FilterChainMatch: "Any"}, + }, Direction: "OUTBOUND", LastUpdated: "2022-07-18T15:31:03.246Z"}, + }, + + Routes: []envoy.Route{ + { + Name: "public_listener", + + DestinationCluster: "local_app/", + + LastUpdated: "2022-08-10T12:30:47.141Z", + }, + }, + + Secrets: []envoy.Secret{ + { + Name: "default", + + Type: "Dynamic Active", + + LastUpdated: "2022-05-24T17:41:59.078Z", + }, + + { + Name: "ROOTCA", + + Type: "Dynamic Warming", + + LastUpdated: "2022-03-15T05:14:22.868Z", + }, + }, +} diff --git a/cli/cmd/proxy/read/filters.go b/cli/cmd/proxy/read/filters.go index dc65172f32..ab3e21b4d8 100644 --- a/cli/cmd/proxy/read/filters.go +++ b/cli/cmd/proxy/read/filters.go @@ -3,6 +3,8 @@ package read import ( "strconv" "strings" + + "github.com/hashicorp/consul-k8s/cli/common/envoy" ) // FilterClusters takes a slice of clusters along with parameters for filtering @@ -17,7 +19,7 @@ import ( // // The filters are applied in combination such that a cluster must adhere to // all of the filtering values which are passed in. -func FilterClusters(clusters []Cluster, fqdn, address string, port int) []Cluster { +func FilterClusters(clusters []envoy.Cluster, fqdn, address string, port int) []envoy.Cluster { // No filtering no-op. if fqdn == "" && address == "" && port == -1 { return clusters @@ -25,7 +27,7 @@ func FilterClusters(clusters []Cluster, fqdn, address string, port int) []Cluste portStr := ":" + strconv.Itoa(port) - filtered := make([]Cluster, 0) + filtered := make([]envoy.Cluster, 0) for _, cluster := range clusters { if !strings.Contains(cluster.FullyQualifiedDomainName, fqdn) { continue @@ -58,14 +60,14 @@ func FilterClusters(clusters []Cluster, fqdn, address string, port int) []Cluste // // The filters are applied in combination such that an endpoint must adhere to // all of the filtering values which are passed in. -func FilterEndpoints(endpoints []Endpoint, address string, port int) []Endpoint { +func FilterEndpoints(endpoints []envoy.Endpoint, address string, port int) []envoy.Endpoint { if address == "" && port == -1 { return endpoints } portStr := ":" + strconv.Itoa(port) - filtered := make([]Endpoint, 0) + filtered := make([]envoy.Endpoint, 0) for _, endpoint := range endpoints { if strings.Contains(endpoint.Address, address) && (port == -1 || strings.Contains(endpoint.Address, portStr)) { filtered = append(filtered, endpoint) @@ -85,14 +87,14 @@ func FilterEndpoints(endpoints []Endpoint, address string, port int) []Endpoint // // The filters are applied in combination such that an listener must adhere to // all of the filtering values which are passed in. -func FilterListeners(listeners []Listener, address string, port int) []Listener { +func FilterListeners(listeners []envoy.Listener, address string, port int) []envoy.Listener { if address == "" && port == -1 { return listeners } portStr := ":" + strconv.Itoa(port) - filtered := make([]Listener, 0) + filtered := make([]envoy.Listener, 0) for _, listener := range listeners { if strings.Contains(listener.Address, address) && (port == -1 || strings.Contains(listener.Address, portStr)) { filtered = append(filtered, listener) diff --git a/cli/cmd/proxy/read/filters_test.go b/cli/cmd/proxy/read/filters_test.go index 48ff3a97da..7340b6ae0d 100644 --- a/cli/cmd/proxy/read/filters_test.go +++ b/cli/cmd/proxy/read/filters_test.go @@ -4,10 +4,12 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/cli/common/envoy" ) func TestFilterClusters(t *testing.T) { - given := []Cluster{ + given := []envoy.Cluster{ { FullyQualifiedDomainName: "local_agent", Endpoints: []string{"192.168.79.187:8502"}, @@ -42,13 +44,13 @@ func TestFilterClusters(t *testing.T) { fqdn string address string port int - expected []Cluster + expected []envoy.Cluster }{ "No filter": { fqdn: "", address: "", port: -1, - expected: []Cluster{ + expected: []envoy.Cluster{ { FullyQualifiedDomainName: "local_agent", Endpoints: []string{"192.168.79.187:8502"}, @@ -83,7 +85,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "default", address: "", port: -1, - expected: []Cluster{ + expected: []envoy.Cluster{ { FullyQualifiedDomainName: "client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", Endpoints: []string{}, @@ -102,7 +104,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "", address: "127.0.", port: -1, - expected: []Cluster{ + expected: []envoy.Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -117,7 +119,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "", address: "", port: 8080, - expected: []Cluster{ + expected: []envoy.Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -132,7 +134,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "local", address: "127.0.0.1", port: -1, - expected: []Cluster{ + expected: []envoy.Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -147,7 +149,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "local", address: "", port: 8080, - expected: []Cluster{ + expected: []envoy.Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -158,7 +160,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "", address: "127.0.0.1", port: 8080, - expected: []Cluster{ + expected: []envoy.Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -169,7 +171,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "local", address: "192.168.79.187", port: 8502, - expected: []Cluster{ + expected: []envoy.Cluster{ { FullyQualifiedDomainName: "local_agent", Endpoints: []string{"192.168.79.187:8502"}, @@ -187,7 +189,7 @@ func TestFilterClusters(t *testing.T) { } func TestFilterEndpoints(t *testing.T) { - given := []Endpoint{ + given := []envoy.Endpoint{ { Address: "192.168.79.187:8502", }, @@ -208,12 +210,12 @@ func TestFilterEndpoints(t *testing.T) { cases := map[string]struct { address string port int - expected []Endpoint + expected []envoy.Endpoint }{ "No filter": { address: "", port: -1, - expected: []Endpoint{ + expected: []envoy.Endpoint{ { Address: "192.168.79.187:8502", }, @@ -234,7 +236,7 @@ func TestFilterEndpoints(t *testing.T) { "Filter address": { address: "127.0.0.1", port: -1, - expected: []Endpoint{ + expected: []envoy.Endpoint{ { Address: "127.0.0.1:8080", }, @@ -243,7 +245,7 @@ func TestFilterEndpoints(t *testing.T) { "Filter port": { address: "", port: 20000, - expected: []Endpoint{ + expected: []envoy.Endpoint{ { Address: "192.168.31.201:20000", }, @@ -258,7 +260,7 @@ func TestFilterEndpoints(t *testing.T) { "Filter address and port": { address: "235", port: 20000, - expected: []Endpoint{ + expected: []envoy.Endpoint{ { Address: "192.168.47.235:20000", }, @@ -275,7 +277,7 @@ func TestFilterEndpoints(t *testing.T) { } func TestFilterListeners(t *testing.T) { - given := []Listener{ + given := []envoy.Listener{ { Address: "192.168.69.179:20000", }, @@ -287,12 +289,12 @@ func TestFilterListeners(t *testing.T) { cases := map[string]struct { address string port int - expected []Listener + expected []envoy.Listener }{ "No filter": { address: "", port: -1, - expected: []Listener{ + expected: []envoy.Listener{ { Address: "192.168.69.179:20000", }, @@ -304,7 +306,7 @@ func TestFilterListeners(t *testing.T) { "Filter address": { address: "127.0.0.1", port: -1, - expected: []Listener{ + expected: []envoy.Listener{ { Address: "127.0.0.1:15001", }, @@ -313,7 +315,7 @@ func TestFilterListeners(t *testing.T) { "Filter port": { address: "", port: 20000, - expected: []Listener{ + expected: []envoy.Listener{ { Address: "192.168.69.179:20000", }, @@ -322,7 +324,7 @@ func TestFilterListeners(t *testing.T) { "Filter address and port": { address: "192.168.69.179", port: 20000, - expected: []Listener{ + expected: []envoy.Listener{ { Address: "192.168.69.179:20000", }, diff --git a/cli/cmd/proxy/read/format.go b/cli/cmd/proxy/read/format.go index 97d5ada86a..90137db6a5 100644 --- a/cli/cmd/proxy/read/format.go +++ b/cli/cmd/proxy/read/format.go @@ -4,20 +4,23 @@ import ( "fmt" "strings" + "github.com/hashicorp/consul-k8s/cli/common/envoy" "github.com/hashicorp/consul-k8s/cli/common/terminal" ) -func formatClusters(clusters []Cluster) *terminal.Table { +func formatClusters(clusters []envoy.Cluster) *terminal.Table { table := terminal.NewTable("Name", "FQDN", "Endpoints", "Type", "Last Updated") for _, cluster := range clusters { - table.AddRow([]string{cluster.Name, cluster.FullyQualifiedDomainName, strings.Join(cluster.Endpoints, ", "), - cluster.Type, cluster.LastUpdated}, []string{}) + table.AddRow([]string{ + cluster.Name, cluster.FullyQualifiedDomainName, strings.Join(cluster.Endpoints, ", "), + cluster.Type, cluster.LastUpdated, + }, []string{}) } return table } -func formatEndpoints(endpoints []Endpoint) *terminal.Table { +func formatEndpoints(endpoints []envoy.Endpoint) *terminal.Table { table := terminal.NewTable("Address:Port", "Cluster", "Weight", "Status") for _, endpoint := range endpoints { var statusColor string @@ -35,7 +38,7 @@ func formatEndpoints(endpoints []Endpoint) *terminal.Table { return table } -func formatListeners(listeners []Listener) *terminal.Table { +func formatListeners(listeners []envoy.Listener) *terminal.Table { table := terminal.NewTable("Name", "Address:Port", "Direction", "Filter Chain Match", "Filters", "Last Updated") for _, listener := range listeners { for index, filter := range listener.FilterChain { @@ -57,7 +60,7 @@ func formatListeners(listeners []Listener) *terminal.Table { return table } -func formatRoutes(routes []Route) *terminal.Table { +func formatRoutes(routes []envoy.Route) *terminal.Table { table := terminal.NewTable("Name", "Destination Cluster", "Last Updated") for _, route := range routes { table.AddRow([]string{route.Name, route.DestinationCluster, route.LastUpdated}, []string{}) @@ -66,7 +69,7 @@ func formatRoutes(routes []Route) *terminal.Table { return table } -func formatSecrets(secrets []Secret) *terminal.Table { +func formatSecrets(secrets []envoy.Secret) *terminal.Table { table := terminal.NewTable("Name", "Type", "Last Updated") for _, secret := range secrets { table.AddRow([]string{secret.Name, secret.Type, secret.LastUpdated}, []string{}) diff --git a/cli/cmd/proxy/read/format_test.go b/cli/cmd/proxy/read/format_test.go index 7d6f975d39..95c562a0dd 100644 --- a/cli/cmd/proxy/read/format_test.go +++ b/cli/cmd/proxy/read/format_test.go @@ -5,8 +5,10 @@ import ( "context" "testing" - "github.com/hashicorp/consul-k8s/cli/common/terminal" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/cli/common/envoy" + "github.com/hashicorp/consul-k8s/cli/common/terminal" ) func TestFormatClusters(t *testing.T) { @@ -21,7 +23,7 @@ func TestFormatClusters(t *testing.T) { "server.*server.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul.*EDS.*2022-06-09T00:39:12\\.754Z", } - given := []Cluster{ + given := []envoy.Cluster{ { Name: "local_agent", FullyQualifiedDomainName: "local_agent", @@ -97,7 +99,7 @@ func TestFormatEndpoints(t *testing.T) { "192.168.65.131:20000.*1.00.*HEALTHY", } - given := []Endpoint{ + given := []envoy.Endpoint{ { Address: "192.168.79.187:8502", Cluster: "local_agent", @@ -174,11 +176,11 @@ func TestFormatListeners(t *testing.T) { "Any.*-> original-destination", } - given := []Listener{ + given := []envoy.Listener{ { Name: "public_listener", Address: "192.168.69.179:20000", - FilterChain: []FilterChain{ + FilterChain: []envoy.FilterChain{ { FilterChainMatch: "Any", Filters: []string{"* -> local_app/"}, @@ -190,7 +192,7 @@ func TestFormatListeners(t *testing.T) { { Name: "outbound_listener", Address: "127.0.0.1:15001", - FilterChain: []FilterChain{ + FilterChain: []envoy.FilterChain{ { FilterChainMatch: "10.100.134.173/32, 240.0.0.3/32", Filters: []string{"-> client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul"}, @@ -245,7 +247,7 @@ func TestFormatRoutes(t *testing.T) { "server.*server\\.default\\.dc1\\.internal\\.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00\\.consul/.*2022-05-24T17:41:59\\.078Z", } - given := []Route{ + given := []envoy.Route{ { Name: "public_listener", DestinationCluster: "local_app/", @@ -282,7 +284,7 @@ func TestFormatSecrets(t *testing.T) { "ROOTCA.*Dynamic Warming.*2022-03-15T05:14:22.868Z", } - given := []Secret{ + given := []envoy.Secret{ { Name: "default", Type: "Dynamic Active", diff --git a/cli/cmd/proxy/read/config.go b/cli/common/envoy/http.go similarity index 91% rename from cli/cmd/proxy/read/config.go rename to cli/common/envoy/http.go index e7e6bcad34..88299d6724 100644 --- a/cli/cmd/proxy/read/config.go +++ b/cli/common/envoy/http.go @@ -1,8 +1,10 @@ -package read +package envoy import ( + "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net" @@ -12,11 +14,13 @@ import ( "github.com/hashicorp/consul-k8s/cli/common" ) +var ErrNoLoggersReturned = errors.New("No loggers were returned from Envoy") + // EnvoyConfig represents the configuration retrieved from a config dump at the // admin endpoint. It wraps the Envoy ConfigDump struct to give us convenient // access to the different sections of the config. type EnvoyConfig struct { - rawCfg []byte + RawCfg []byte Clusters []Cluster Endpoints []Endpoint Listeners []Listener @@ -69,6 +73,54 @@ type Secret struct { LastUpdated string } +// CallLoggingEndpoint requests the logging endpoint from Envoy Admin Interface for a given port +// This is used to both read and update the logging levels (the envoy admin interface uses the same endpoint for both) +// more can be read about that endpoint https://www.envoyproxy.io/docs/envoy/latest/operations/admin#post--logging +func CallLoggingEndpoint(ctx context.Context, portForward common.PortForwarder, params *LoggerParams) (map[string]string, error) { + endpoint, err := portForward.Open(ctx) + if err != nil { + return nil, err + } + + defer portForward.Close() + + // this endpoint does not support returning json, so we've gotta parse the plain text + response, err := http.Post(fmt.Sprintf("http://%s/logging%s", endpoint, params), "text/plain", bytes.NewBuffer([]byte{})) + if err != nil { + return nil, err + } + + body, err := io.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("failed to reach envoy: %v", err) + } + + if response.StatusCode >= 400 { + return nil, fmt.Errorf("call to envoy failed with status code: %d, and message: %s", response.StatusCode, body) + } + + loggers := strings.Split(string(body), "\n") + if len(loggers) == 0 { + return nil, ErrNoLoggersReturned + } + + logLevels := make(map[string]string) + var name string + var level string + + // the first line here is just a header + for _, logger := range loggers[1:] { + if len(logger) == 0 { + continue + } + fmt.Sscanf(logger, "%s %s", &name, &level) + name = strings.TrimRight(name, ":") + logLevels[name] = level + } + + return logLevels, nil +} + // FetchConfig opens a port forward to the Envoy admin API and fetches the // configuration from the config dump endpoint. func FetchConfig(ctx context.Context, portForward common.PortForwarder) (*EnvoyConfig, error) { @@ -117,7 +169,7 @@ func FetchConfig(ctx context.Context, portForward common.PortForwarder) (*EnvoyC // JSON returns the original JSON Envoy config dump data which was used to create // the Config object. func (c *EnvoyConfig) JSON() []byte { - return c.rawCfg + return c.RawCfg } // UnmarshalJSON implements the json.Unmarshaler interface to unmarshal the raw @@ -126,7 +178,7 @@ func (c *EnvoyConfig) JSON() []byte { func (c *EnvoyConfig) UnmarshalJSON(b []byte) error { // Save the original config dump bytes for marshalling. We should treat this // struct as immutable so this should be safe. - c.rawCfg = b + c.RawCfg = b var root root err := json.Unmarshal(b, &root) diff --git a/cli/cmd/proxy/read/config_test.go b/cli/common/envoy/http_test.go similarity index 93% rename from cli/cmd/proxy/read/config_test.go rename to cli/common/envoy/http_test.go index 6b0e425794..2f10e10a2f 100644 --- a/cli/cmd/proxy/read/config_test.go +++ b/cli/common/envoy/http_test.go @@ -1,21 +1,38 @@ -package read +package envoy import ( "bytes" "context" - "embed" "encoding/json" "fmt" "net/http" "net/http/httptest" + "os" "strings" "testing" "github.com/stretchr/testify/require" ) -//go:embed test_config_dump.json test_clusters.json -var fs embed.FS +func TestCallLoggingEndpoint(t *testing.T) { + t.Parallel() + rawLogLevels, err := os.ReadFile("testdata/fetch_debug_levels.txt") + require.NoError(t, err) + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(rawLogLevels) + })) + + defer mockServer.Close() + + mpf := &mockPortForwarder{ + openBehavior: func(ctx context.Context) (string, error) { + return strings.Replace(mockServer.URL, "http://", "", 1), nil + }, + } + logLevels, err := CallLoggingEndpoint(context.Background(), mpf, NewLoggerParams()) + require.NoError(t, err) + require.Equal(t, testLogConfig(), logLevels) +} const ( testConfigDump = "test_config_dump.json" @@ -35,7 +52,7 @@ func TestUnmarshaling(t *testing.T) { } func TestJSON(t *testing.T) { - raw, err := fs.ReadFile(testConfigDump) + raw, err := os.ReadFile(fmt.Sprintf("testdata/%s", testConfigDump)) require.NoError(t, err) expected := bytes.TrimSpace(raw) @@ -49,10 +66,10 @@ func TestJSON(t *testing.T) { } func TestFetchConfig(t *testing.T) { - configDump, err := fs.ReadFile(testConfigDump) + configDump, err := os.ReadFile(fmt.Sprintf("testdata/%s", testConfigDump)) require.NoError(t, err) - clusters, err := fs.ReadFile(testClusters) + clusters, err := os.ReadFile(fmt.Sprintf("testdata/%s", testClusters)) require.NoError(t, err) mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -446,18 +463,11 @@ func TestClusterParsingEndpoints(t *testing.T) { require.Equal(t, expected, actual) } -type mockPortForwarder struct { - openBehavior func(context.Context) (string, error) -} - -func (m *mockPortForwarder) Open(ctx context.Context) (string, error) { return m.openBehavior(ctx) } -func (m *mockPortForwarder) Close() {} - func rawEnvoyConfig(t *testing.T) []byte { - configDump, err := fs.ReadFile(testConfigDump) + configDump, err := os.ReadFile(fmt.Sprintf("testdata/%s", testConfigDump)) require.NoError(t, err) - clusters, err := fs.ReadFile(testClusters) + clusters, err := os.ReadFile(fmt.Sprintf("testdata/%s", testClusters)) require.NoError(t, err) return []byte(fmt.Sprintf("{\n\"config_dump\":%s,\n\"clusters\":%s}", string(configDump), string(clusters))) @@ -508,3 +518,18 @@ var testEnvoyConfig = &EnvoyConfig{ }, }, } + +type mockPortForwarder struct { + openBehavior func(context.Context) (string, error) +} + +func (m *mockPortForwarder) Open(ctx context.Context) (string, error) { return m.openBehavior(ctx) } +func (m *mockPortForwarder) Close() {} + +func testLogConfig() map[string]string { + cfg := make(map[string]string, len(envoyLoggers)) + for k := range envoyLoggers { + cfg[k] = "debug" + } + return cfg +} diff --git a/cli/common/envoy/logger_params.go b/cli/common/envoy/logger_params.go new file mode 100644 index 0000000000..346ac19478 --- /dev/null +++ b/cli/common/envoy/logger_params.go @@ -0,0 +1,166 @@ +package envoy + +import ( + "fmt" + "strings" +) + +type logLevel struct { + name string + level string +} + +type LoggerParams struct { + globalLevel string + individualLevels []logLevel +} + +func NewLoggerParams() *LoggerParams { + return &LoggerParams{ + individualLevels: make([]logLevel, 0), + } +} + +func (l *LoggerParams) SetLoggerLevel(name, level string) error { + err := validateLoggerName(name) + if err != nil { + return err + } + err = validateLogLevel(level) + if err != nil { + return err + } + + l.individualLevels = append(l.individualLevels, logLevel{name: name, level: level}) + return nil +} + +func (l *LoggerParams) SetGlobalLoggerLevel(level string) error { + err := validateLogLevel(level) + if err != nil { + return err + } + l.globalLevel = level + return nil +} + +func validateLogLevel(level string) error { + if _, ok := envoyLevels[level]; !ok { + logLevels := []string{} + for levelName := range envoyLevels { + logLevels = append(logLevels, levelName) + } + return fmt.Errorf("Unknown log level %s, available log levels are %q", level, strings.Join(logLevels, ", ")) + } + return nil +} + +func validateLoggerName(name string) error { + if _, ok := envoyLoggers[name]; !ok { + loggers := []string{} + for loggerName := range envoyLevels { + loggers = append(loggers, loggerName) + } + return fmt.Errorf("Unknown logger %s, available loggers are %q", name, strings.Join(loggers, ", ")) + + } + return nil +} + +func (l *LoggerParams) String() string { + switch { + // Global log level change is set + case l.globalLevel != "": + return fmt.Sprintf("?level=%s", l.globalLevel) + + // only one specific logger is changed + case len(l.individualLevels) == 1: + params := fmt.Sprintf("?%s=%s", l.individualLevels[0].name, l.individualLevels[0].level) + return params + + // multiple specific loggers are changed + case len(l.individualLevels) > 1: + logParams := make([]string, 0, len(l.individualLevels)) + for _, logger := range l.individualLevels { + logParams = append(logParams, fmt.Sprintf("%s:%s", logger.name, logger.level)) + } + + params := fmt.Sprintf("?paths=%s", strings.Join(logParams, ",")) + return params + default: + + // default path, this is hit if there are no params + return "" + } +} + +// trace debug info warning error critical off. +var envoyLevels = map[string]struct{}{ + "trace": {}, + "debug": {}, + "info": {}, + "warning": {}, + "error": {}, + "critical": {}, + "off": {}, +} + +var envoyLoggers = map[string]struct{}{ + "admin": {}, + "alternate_protocols_cache": {}, + "aws": {}, + "assert": {}, + "backtrace": {}, + "cache_filter": {}, + "client": {}, + "config": {}, + "connection": {}, + "conn_handler": {}, + "decompression": {}, + "dns": {}, + "dubbo": {}, + "envoy_bug": {}, + "ext_authz": {}, + "ext_proc": {}, + "rocketmq": {}, + "file": {}, + "filter": {}, + "forward_proxy": {}, + "grpc": {}, + "happy_eyeballs": {}, + "hc": {}, + "health_checker": {}, + "http": {}, + "http2": {}, + "hystrix": {}, + "init": {}, + "io": {}, + "jwt": {}, + "kafka": {}, + "key_value_store": {}, + "lua": {}, + "main": {}, + "matcher": {}, + "misc": {}, + "mongo": {}, + "multi_connection": {}, + "oauth2": {}, + "quic": {}, + "quic_stream": {}, + "pool": {}, + "rbac": {}, + "rds": {}, + "redis": {}, + "router": {}, + "runtime": {}, + "stats": {}, + "secret": {}, + "tap": {}, + "testing": {}, + "thrift": {}, + "tracing": {}, + "upstream": {}, + "udp": {}, + "wasm": {}, + "websocket": {}, +} diff --git a/cli/common/envoy/logger_params_test.go b/cli/common/envoy/logger_params_test.go new file mode 100644 index 0000000000..26be9f69a6 --- /dev/null +++ b/cli/common/envoy/logger_params_test.go @@ -0,0 +1,172 @@ +package envoy + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSetLoggerLevelSucceeds(t *testing.T) { + t.Parallel() + testCases := map[string]struct { + levelsToSet [][]string + expectedIndividualLevels []logLevel + }{ + "single log level change trace": { + levelsToSet: [][]string{ + {"admin", "trace"}, + }, + expectedIndividualLevels: []logLevel{ + {name: "admin", level: "trace"}, + }, + }, + "single log level change debug": { + levelsToSet: [][]string{ + {"admin", "debug"}, + }, + expectedIndividualLevels: []logLevel{ + {name: "admin", level: "debug"}, + }, + }, + "single log level change info": { + levelsToSet: [][]string{ + {"admin", "info"}, + }, + expectedIndividualLevels: []logLevel{ + {name: "admin", level: "info"}, + }, + }, + "single log level change warning": { + levelsToSet: [][]string{ + {"admin", "warning"}, + }, + expectedIndividualLevels: []logLevel{ + {name: "admin", level: "warning"}, + }, + }, + "single log level change error": { + levelsToSet: [][]string{ + {"admin", "error"}, + }, + expectedIndividualLevels: []logLevel{ + {name: "admin", level: "error"}, + }, + }, + "single log level change critical": { + levelsToSet: [][]string{ + {"admin", "critical"}, + }, + expectedIndividualLevels: []logLevel{ + {name: "admin", level: "critical"}, + }, + }, + "single log level change off": { + levelsToSet: [][]string{ + {"admin", "off"}, + }, + expectedIndividualLevels: []logLevel{ + {name: "admin", level: "off"}, + }, + }, + "multiple log level change": { + levelsToSet: [][]string{ + {"admin", "info"}, + {"grpc", "debug"}, + }, + expectedIndividualLevels: []logLevel{ + {name: "admin", level: "info"}, + {name: "grpc", level: "debug"}, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + loggerParams := NewLoggerParams() + for _, loggerLevel := range tc.levelsToSet { + logger, level := loggerLevel[0], loggerLevel[1] + err := loggerParams.SetLoggerLevel(logger, level) + require.NoError(t, err) + } + require.Equal(t, loggerParams.individualLevels, tc.expectedIndividualLevels) + }) + } +} + +func TestSetLoggerLevelFails(t *testing.T) { + t.Parallel() + testCases := map[string]struct { + loggerName string + loggerLevel string + }{ + "invalid logger name": { + loggerName: "this is not the logger you're looking for", + loggerLevel: "info", + }, + "invalid logger level": { + loggerName: "grpc", + loggerLevel: "this is also incorrect", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + loggerParams := NewLoggerParams() + err := loggerParams.SetLoggerLevel(tc.loggerName, tc.loggerLevel) + require.Error(t, err) + }) + } +} + +func TestSetGlobalLoggerLevel(t *testing.T) { + t.Parallel() + for level := range envoyLevels { + loggerParams := NewLoggerParams() + err := loggerParams.SetGlobalLoggerLevel(level) + require.NoError(t, err) + } +} + +func TestSetGlobalLoggerLevelFails(t *testing.T) { + t.Parallel() + loggerParams := NewLoggerParams() + err := loggerParams.SetGlobalLoggerLevel("not a valid level") + require.Error(t, err) +} + +func TestString(t *testing.T) { + t.Parallel() + testCases := map[string]struct { + subject *LoggerParams + expectedOutput string + }{ + "when global level is set": { + subject: &LoggerParams{globalLevel: "warn"}, + expectedOutput: "?level=warn", + }, + "when one specific log level is set": { + subject: &LoggerParams{ + individualLevels: []logLevel{ + {name: "grpc", level: "warn"}, + }, + }, + expectedOutput: "?grpc=warn", + }, + "when multiple specific log levels are set": { + subject: &LoggerParams{ + individualLevels: []logLevel{ + {name: "grpc", level: "warn"}, + {name: "http", level: "info"}, + }, + }, + expectedOutput: "?paths=grpc:warn,http:info", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + actual := tc.subject.String() + require.Equal(t, actual, tc.expectedOutput) + }) + } +} diff --git a/cli/cmd/proxy/loglevel/testdata/fetch_debug_levels.txt b/cli/common/envoy/testdata/fetch_debug_levels.txt similarity index 100% rename from cli/cmd/proxy/loglevel/testdata/fetch_debug_levels.txt rename to cli/common/envoy/testdata/fetch_debug_levels.txt diff --git a/cli/cmd/proxy/read/test_clusters.json b/cli/common/envoy/testdata/test_clusters.json similarity index 100% rename from cli/cmd/proxy/read/test_clusters.json rename to cli/common/envoy/testdata/test_clusters.json diff --git a/cli/cmd/proxy/read/test_config_dump.json b/cli/common/envoy/testdata/test_config_dump.json similarity index 100% rename from cli/cmd/proxy/read/test_config_dump.json rename to cli/common/envoy/testdata/test_config_dump.json diff --git a/cli/cmd/proxy/read/envoy_types.go b/cli/common/envoy/types.go similarity index 99% rename from cli/cmd/proxy/read/envoy_types.go rename to cli/common/envoy/types.go index cc1ffcf7e2..505248e983 100644 --- a/cli/cmd/proxy/read/envoy_types.go +++ b/cli/common/envoy/types.go @@ -1,4 +1,4 @@ -package read +package envoy /* Envoy Types These types are based on the JSON returned from the Envoy Config Dump API on the From a7f4f914eba5094085a406b663c61859b211c8b4 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:40:20 -0800 Subject: [PATCH 070/592] pinned consul test image to latest dev nightly (#1901) * pinned consul test image to latest dev nightly - created a new variable for setting the consul test image used for the majority of tests - fixed some incorrect spacing * updated builtin lambda envoy extensions in tests --- .circleci/config.yml | 45 ++++++++++++------- .../bases/crds-oss/proxydefaults.yaml | 4 +- .../bases/crds-oss/servicedefaults.yaml | 4 +- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f55e540f13..c371b63bdf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,6 +23,7 @@ aks-terraform-path: &aks-terraform-path charts/consul/test/terraform/aks openshift-terraform-path: &openshift-terraform-path charts/consul/test/terraform/openshift # This image is built from test/docker/Test.dockerfile consul-helm-test-image: &consul-helm-test-image docker.mirror.hashicorp.services/hashicorpdev/consul-helm-test:0.15.0 +consul-test-image: &consul-test-image hashicorppreview/consul-enterprise:1.15-dev ######################## # COMMANDS @@ -564,7 +565,8 @@ jobs: ########################### acceptance: environment: - - TEST_RESULTS: /tmp/test-results + - TEST_RESULTS: /tmp/test-results] + - CONSUL_TEST_IMAGE: *consul-test-image machine: image: ubuntu-2004:202010-01 resource_class: xlarge @@ -589,7 +591,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results - store_artifacts: @@ -598,6 +600,7 @@ jobs: acceptance-tproxy: environment: - TEST_RESULTS: /tmp/test-results + - CONSUL_TEST_IMAGE: *consul-test-image machine: image: ubuntu-2004:202010-01 resource_class: xlarge @@ -622,7 +625,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results - store_artifacts: @@ -631,6 +634,7 @@ jobs: acceptance-tproxy-cni: environment: - TEST_RESULTS: /tmp/test-results + - CONSUL_TEST_IMAGE: *consul-test-image machine: image: ubuntu-2004:202010-01 resource_class: xlarge @@ -655,7 +659,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -enable-cni -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -enable-cni -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results - store_artifacts: @@ -728,6 +732,7 @@ jobs: environment: - TEST_RESULTS: /tmp/test-results - USE_GKE_GCLOUD_AUTH_PLUGIN: true + - CONSUL_TEST_IMAGE: *consul-test-image docker: - image: *consul-helm-test-image @@ -773,7 +778,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results @@ -797,6 +802,7 @@ jobs: environment: - TEST_RESULTS: /tmp/test-results - USE_GKE_GCLOUD_AUTH_PLUGIN: true + - CONSUL_TEST_IMAGE: *consul-test-image docker: - image: *consul-helm-test-image @@ -842,7 +848,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -use-gke -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -use-gke -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results @@ -865,6 +871,7 @@ jobs: parallelism: 3 environment: - TEST_RESULTS: /tmp/test-results + - CONSUL_TEST_IMAGE: *consul-test-image docker: - image: *consul-helm-test-image @@ -899,7 +906,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results @@ -922,6 +929,7 @@ jobs: parallelism: 3 environment: - TEST_RESULTS: /tmp/test-results + - CONSUL_TEST_IMAGE: *consul-test-image docker: - image: *consul-helm-test-image @@ -956,7 +964,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results @@ -978,6 +986,7 @@ jobs: parallelism: 3 environment: - TEST_RESULTS: /tmp/test-results + - CONSUL_TEST_IMAGE: *consul-test-image docker: - image: *consul-helm-test-image @@ -1018,7 +1027,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results @@ -1041,6 +1050,7 @@ jobs: parallelism: 3 environment: - TEST_RESULTS: /tmp/test-results + - CONSUL_TEST_IMAGE: *consul-test-image docker: - image: *consul-helm-test-image @@ -1081,7 +1091,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results @@ -1103,6 +1113,7 @@ jobs: acceptance-openshift: environment: TEST_RESULTS: /tmp/test-results + CONSUL_TEST_IMAGE: *consul-test-image parallelism: 1 docker: - image: *consul-helm-test-image @@ -1135,7 +1146,7 @@ jobs: - run: mkdir -p $TEST_RESULTS - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-openshift -enable-transparent-proxy -consul-image=hashicorppreview/consul-enterprise:1.15-dev-23aaa4f83845d0e2eced9ea69f731d7eedf840d1 + additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-openshift -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - store_test_results: path: /tmp/test-results @@ -1264,15 +1275,15 @@ workflows: - acceptance: context: consul-ci requires: - - dev-upload-docker + - dev-upload-docker - acceptance-tproxy-cni: context: consul-ci requires: - - dev-upload-docker + - dev-upload-docker - acceptance-tproxy: context: consul-ci requires: - - dev-upload-docker + - dev-upload-docker nightly-cleanup: @@ -1313,13 +1324,13 @@ workflows: # - acceptance-openshift - acceptance-gke-1-25: requires: - - dev-upload-docker + - dev-upload-docker - acceptance-gke-cni-1-25: requires: - - acceptance-gke-1-25 + - acceptance-gke-1-25 - acceptance-tproxy: requires: - - dev-upload-docker + - dev-upload-docker nightly-acceptance-tests-main: description: | diff --git a/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml index 878b4c37e8..6a90f29506 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml @@ -24,9 +24,9 @@ spec: required: false arguments: payloadPassthrough: false - region: us-west-2 + arn: arn:aws:lambda:us-west-2:111111111111:function:lambda-1234 - name: builtin/aws/lambda required: false arguments: payloadPassthrough: false - region: us-east-1 + arn: arn:aws:lambda:us-east-1:111111111111:function:lambda-1234 diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index 33bc0e5096..413eb68d22 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -31,9 +31,9 @@ spec: required: false arguments: payloadPassthrough: false - region: us-west-2 + arn: arn:aws:lambda:us-west-2:111111111111:function:lambda-1234 - name: builtin/aws/lambda required: false arguments: payloadPassthrough: false - region: us-east-1 \ No newline at end of file + arn: arn:aws:lambda:us-east-1:111111111111:function:lambda-1234 \ No newline at end of file From 22bb4e25994da2b3b8ac01bfef15181d0ee7f43a Mon Sep 17 00:00:00 2001 From: Kyle Schochenmaier Date: Tue, 14 Feb 2023 10:21:10 -0600 Subject: [PATCH 071/592] Update server-acl-init to always check for the deployed serviceAccountToken secret (#1770) --- CHANGELOG.md | 2 ++ .../server-acl-init/connect_inject.go | 17 +++++++---------- .../server-acl-init/connect_inject_test.go | 18 ++++++++++++++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aecdf96d0..b704a1ec09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,8 @@ BUG FIXES: IMPROVEMENTS: * Helm: * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] +* Control Plane: + * Server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/pull/1770)] BUG FIXES: * Helm: diff --git a/control-plane/subcommand/server-acl-init/connect_inject.go b/control-plane/subcommand/server-acl-init/connect_inject.go index 0160efd0a1..e732dae452 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject.go +++ b/control-plane/subcommand/server-acl-init/connect_inject.go @@ -96,16 +96,13 @@ func (c *Command) createAuthMethodTmpl(authMethodName string, useNS bool) (api.A var saSecret *apiv1.Secret var secretNames []string - if len(authMethodServiceAccount.Secrets) == 0 { - // In Kube 1.24+ there is no automatically generated long term JWT token for a ServiceAccount. - // Furthermore, there is no reference to a Secret in the ServiceAccount. Instead we have deployed - // a Secret in Helm which references the ServiceAccount and contains a permanent JWT token. - secretNames = append(secretNames, c.withPrefix("auth-method")) - } else { - // ServiceAccounts always have a SecretRef in Kubernetes < 1.24. The Secret contains the JWT token. - for _, secretRef := range authMethodServiceAccount.Secrets { - secretNames = append(secretNames, secretRef.Name) - } + // In Kube 1.24+ there is no automatically generated long term JWT token for a ServiceAccount. + // Furthermore, there is no reference to a Secret in the ServiceAccount. Instead we have deployed + // a Secret in Helm which references the ServiceAccount and contains a permanent JWT token. + secretNames = append(secretNames, c.withPrefix("auth-method")) + // ServiceAccounts always have a SecretRef in Kubernetes < 1.24. The Secret contains the JWT token. + for _, secretRef := range authMethodServiceAccount.Secrets { + secretNames = append(secretNames, secretRef.Name) } // Because there could be multiple secrets attached to the service account, // we need pick the first one of type corev1.SecretTypeServiceAccountToken. diff --git a/control-plane/subcommand/server-acl-init/connect_inject_test.go b/control-plane/subcommand/server-acl-init/connect_inject_test.go index e3166442af..e7144146b7 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject_test.go +++ b/control-plane/subcommand/server-acl-init/connect_inject_test.go @@ -30,6 +30,20 @@ func TestCommand_createAuthMethodTmpl_SecretNotFound(t *testing.T) { ctx: ctx, } + // create the auth method secret since it is always deployed by helm chart. + authMethodSecretName := resourcePrefix + "-auth-method" + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: authMethodSecretName, + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{}, + // Make it not a service-account-token so the test can pass through to checking the other secrets. + Type: v1.SecretTypeOpaque, + } + _, err := k8s.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{}) + require.NoError(t, err) + serviceAccountName := resourcePrefix + "-auth-method" secretName := resourcePrefix + "-connect-injector" @@ -53,7 +67,7 @@ func TestCommand_createAuthMethodTmpl_SecretNotFound(t *testing.T) { } // Create a secret of non service-account-token type (we're using the opaque type). - secret := &v1.Secret{ + secret = &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: secretName, Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, @@ -61,7 +75,7 @@ func TestCommand_createAuthMethodTmpl_SecretNotFound(t *testing.T) { Data: map[string][]byte{}, Type: v1.SecretTypeOpaque, } - _, err := k8s.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{}) + _, err = k8s.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{}) require.NoError(t, err) _, err = cmd.createAuthMethodTmpl("test", true) From 3dc73a6c9898683f4150fb0417b0e217035463a2 Mon Sep 17 00:00:00 2001 From: jm96441n Date: Mon, 23 Jan 2023 15:24:33 -0500 Subject: [PATCH 072/592] Add reset command for envoy proxy log command Move format parsing into envoy package Move enovy to common package, move param parsing to calling package Added changelog for proxy log command Cleaning up usage message and error message from pr review clean up issue with exported constant used in test, fmt'd/linted export loggers undo autoformatting last fix to changelog to clean up from auto format set level on reset for envoy log level to info Fix spelling mistake in changelog --- CHANGELOG.md | 3 +++ cli/cmd/proxy/loglevel/command.go | 17 +++++++++++++++++ cli/common/envoy/http_test.go | 4 ++-- cli/common/envoy/logger_params.go | 4 ++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aecdf96d0..31014df601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ IMPROVEMENTS: * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] * Remove extraneous `gnupg` dependency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] +* CLI: + * Add `consul-k8s proxy log podname` command for displaying and modifying Envoy log levels for a given Pod. [GH-1844](https://github.com/hashicorp/consul-k8s/pull/1844), [GH-1849](https://github.com/hashicorp/consul-k8s/pull/1849), [GH-1864](https://github.com/hashicorp/consul-k8s/pull/1864) + BUG FIXES: * Control Plane diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go index 85dc91bfa2..4355997ed3 100644 --- a/cli/cmd/proxy/loglevel/command.go +++ b/cli/cmd/proxy/loglevel/command.go @@ -24,6 +24,7 @@ const ( defaultAdminPort = 19000 flagNameNamespace = "namespace" flagNameUpdateLevel = "update-level" + flagNameReset = "reset" flagNameKubeConfig = "kubeconfig" flagNameKubeContext = "context" ) @@ -52,6 +53,7 @@ type LogLevelCommand struct { podName string namespace string level string + reset bool kubeConfig string kubeContext string @@ -79,6 +81,13 @@ func (l *LogLevelCommand) init() { Aliases: []string{"u"}, }) + f.BoolVar(&flag.BoolVar{ + Name: flagNameReset, + Target: &l.reset, + Usage: "Reset the log level for all loggers in a pod to the Envoy default (info).", + Aliases: []string{"r"}, + }) + f = l.set.NewSet("Global Options") f.StringVar(&flag.StringVar{ Name: flagNameKubeConfig, @@ -108,6 +117,11 @@ func (l *LogLevelCommand) Run(args []string) int { return l.logOutputAndDie(err) } + // if we're resetting the default log level for envoy is info: https://www.envoyproxy.io/docs/envoy/latest/start/quick-start/run-envoy#debugging-envoy + if l.reset { + l.level = "info" + } + if l.envoyLoggingCaller == nil { l.envoyLoggingCaller = envoy.CallLoggingEndpoint } @@ -160,6 +174,9 @@ func (l *LogLevelCommand) parseFlags(args []string) error { } func (l *LogLevelCommand) validateFlags() error { + if l.level != "" && l.reset { + return fmt.Errorf("cannot set log level to %q and reset to 'info' at the same time", l.level) + } if l.namespace == "" { return nil } diff --git a/cli/common/envoy/http_test.go b/cli/common/envoy/http_test.go index 2f10e10a2f..291eb2c22b 100644 --- a/cli/common/envoy/http_test.go +++ b/cli/common/envoy/http_test.go @@ -527,8 +527,8 @@ func (m *mockPortForwarder) Open(ctx context.Context) (string, error) { return m func (m *mockPortForwarder) Close() {} func testLogConfig() map[string]string { - cfg := make(map[string]string, len(envoyLoggers)) - for k := range envoyLoggers { + cfg := make(map[string]string, len(EnvoyLoggers)) + for k := range EnvoyLoggers { cfg[k] = "debug" } return cfg diff --git a/cli/common/envoy/logger_params.go b/cli/common/envoy/logger_params.go index 346ac19478..b2eff623c0 100644 --- a/cli/common/envoy/logger_params.go +++ b/cli/common/envoy/logger_params.go @@ -56,7 +56,7 @@ func validateLogLevel(level string) error { } func validateLoggerName(name string) error { - if _, ok := envoyLoggers[name]; !ok { + if _, ok := EnvoyLoggers[name]; !ok { loggers := []string{} for loggerName := range envoyLevels { loggers = append(loggers, loggerName) @@ -105,7 +105,7 @@ var envoyLevels = map[string]struct{}{ "off": {}, } -var envoyLoggers = map[string]struct{}{ +var EnvoyLoggers = map[string]struct{}{ "admin": {}, "alternate_protocols_cache": {}, "aws": {}, From ad8b239ea2139217e60531b39ba37d8e397bf598 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 14 Feb 2023 14:55:32 -0800 Subject: [PATCH 073/592] updated golangci-lint-action to latest (#1912) --- .github/workflows/reusable-golangci-lint.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reusable-golangci-lint.yml b/.github/workflows/reusable-golangci-lint.yml index c8c3793b03..30b6a0a3b3 100644 --- a/.github/workflows/reusable-golangci-lint.yml +++ b/.github/workflows/reusable-golangci-lint.yml @@ -22,13 +22,13 @@ jobs: uses: actions/checkout@v2 - name: Setup go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ inputs.go-version }} - name: golangci-lint-${{inputs.directory}} - uses: golangci/golangci-lint-action@v3.2.0 + uses: golangci/golangci-lint-action@v3.4.0 with: - version: v1.46.2 + version: v1.51 working-directory: ${{inputs.directory}} args: ${{inputs.args}} From 6655508821ff18b5d176037bcbab8a7fe86eaeb6 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 14 Feb 2023 16:01:19 -0800 Subject: [PATCH 074/592] move GH-1770 to unreleased (#1909) --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbbb5b86ce..6a625430c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ IMPROVEMENTS: * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] * Remove extraneous `gnupg` dependency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] + * Server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/pull/1770)] * CLI: * Add `consul-k8s proxy log podname` command for displaying and modifying Envoy log levels for a given Pod. [GH-1844](https://github.com/hashicorp/consul-k8s/pull/1844), [GH-1849](https://github.com/hashicorp/consul-k8s/pull/1849), [GH-1864](https://github.com/hashicorp/consul-k8s/pull/1864) @@ -67,8 +68,6 @@ BUG FIXES: IMPROVEMENTS: * Helm: * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] -* Control Plane: - * Server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/pull/1770)] BUG FIXES: * Helm: From 77658e3e70f820e3f335deb6a1c2555f804d439e Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 16 Feb 2023 00:30:31 -0800 Subject: [PATCH 075/592] GitHub actions now uses branch local reusable tests (#1915) --- .github/workflows/test.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3967d1c816..79eefd17f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -82,7 +82,7 @@ jobs: golangci-lint-helm-gen: needs: - get-go-version - uses: hashicorp/consul-k8s/.github/workflows/reusable-golangci-lint.yml@main + uses: ./.github/workflows/reusable-golangci-lint.yml with: directory: hack/helm-reference-gen go-version: ${{ needs.get-go-version.outputs.go-version }} @@ -91,7 +91,7 @@ jobs: unit-helm-gen: needs: [get-go-version, golangci-lint-helm-gen, validate-helm-gen] - uses: hashicorp/consul-k8s/.github/workflows/reusable-unit.yml@main + uses: ./.github/workflows/reusable-unit.yml with: directory: hack/helm-reference-gen go-version: ${{ needs.get-go-version.outputs.go-version }} @@ -133,7 +133,7 @@ jobs: golangci-lint-control-plane: needs: - get-go-version - uses: hashicorp/consul-k8s/.github/workflows/reusable-golangci-lint.yml@main + uses: ./.github/workflows/reusable-golangci-lint.yml with: directory: control-plane go-version: ${{ needs.get-go-version.outputs.go-version }} @@ -280,14 +280,14 @@ jobs: golangci-lint-acceptance: needs: - get-go-version - uses: hashicorp/consul-k8s/.github/workflows/reusable-golangci-lint.yml@main + uses: ./.github/workflows/reusable-golangci-lint.yml with: directory: acceptance go-version: ${{ needs.get-go-version.outputs.go-version }} unit-acceptance-framework: needs: [get-go-version, golangci-lint-acceptance] - uses: hashicorp/consul-k8s/.github/workflows/reusable-unit.yml@main + uses: ./.github/workflows/reusable-unit.yml with: directory: acceptance/framework go-version: ${{ needs.get-go-version.outputs.go-version }} @@ -295,14 +295,14 @@ jobs: golangci-lint-cli: needs: - get-go-version - uses: hashicorp/consul-k8s/.github/workflows/reusable-golangci-lint.yml@main + uses: ./.github/workflows/reusable-golangci-lint.yml with: directory: cli go-version: ${{ needs.get-go-version.outputs.go-version }} unit-cli: needs: [get-go-version, golangci-lint-cli] - uses: hashicorp/consul-k8s/.github/workflows/reusable-unit.yml@main + uses: ./.github/workflows/reusable-unit.yml with: directory: cli go-version: ${{ needs.get-go-version.outputs.go-version }} @@ -359,7 +359,7 @@ jobs: # Disable GHA acceptance tests until GHA formally supported # acceptance: # needs: [ get-product-version, dev-upload-docker, get-go-version ] -# uses: hashicorp/consul-k8s/.github/workflows/reusable-acceptance.yml@main +# uses: ./.github/workflows/reusable-acceptance.yml # with: # name: acceptance # directory: acceptance/tests @@ -372,7 +372,7 @@ jobs: # # acceptance-tproxy: # needs: [ get-product-version, dev-upload-docker, get-go-version ] -# uses: hashicorp/consul-k8s/.github/workflows/reusable-acceptance.yml@main +# uses: ./.github/workflows/reusable-acceptance.yml # with: # name: acceptance-tproxy # directory: acceptance/tests @@ -385,7 +385,7 @@ jobs: # # acceptance-cni: # needs: [ get-product-version, dev-upload-docker, get-go-version ] -# uses: hashicorp/consul-k8s/.github/workflows/reusable-acceptance.yml@main +# uses: ./.github/workflows/reusable-acceptance.yml # with: # name: acceptance # directory: acceptance/tests From 5d263901656c138d2ac30f4ce6ef7150b95f2e11 Mon Sep 17 00:00:00 2001 From: Iryna Shustava Date: Fri, 17 Feb 2023 08:31:44 -0700 Subject: [PATCH 076/592] Fix flakey test in troubleshoot/upstreams_test.go (#1919) When using maps as input, the order is not guaranteed in go, and so our tests are flakey because we're asserting on specific order in the output. This changes the formatClusterNames function to sort the cluster names array first. --- cli/cmd/troubleshoot/upstreams/upstreams.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/cmd/troubleshoot/upstreams/upstreams.go b/cli/cmd/troubleshoot/upstreams/upstreams.go index 8b20928c7a..c11a82ac6e 100644 --- a/cli/cmd/troubleshoot/upstreams/upstreams.go +++ b/cli/cmd/troubleshoot/upstreams/upstreams.go @@ -3,6 +3,7 @@ package upstreams import ( "fmt" "net" + "sort" "strconv" "strings" "sync" @@ -245,6 +246,7 @@ func formatClusterNames(names map[string]struct{}) string { for k := range names { out = append(out, k) } + sort.Strings(out) return strings.Join(out, ", ") } From 89494fd35afc41833c80f685428c1c7b2bfa3552 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 17 Feb 2023 11:29:46 -0800 Subject: [PATCH 077/592] Pipeline Updates (#1908) * updated to go 1.20 - ran go mod tidy on all go mod files * updated go version to 1.20.1 - removed compatibility tests, those won't be needed now that we have paired Consul/Consul-K8s versions * addressed linter error - updated tests to remove require.New(t) and instead pass t directly when required * update to use gotestsum testname for better debugging * add 1.1.x to the nightly release testing * added changelog updates --- .circleci/config.yml | 41 ++++++------------- .github/workflows/reusable-acceptance.yml | 4 +- .github/workflows/reusable-unit.yml | 2 +- .github/workflows/test.yml | 4 +- .go-version | 2 +- CHANGELOG.md | 5 ++- acceptance/go.mod | 2 +- charts/go.mod | 2 +- cli/go.mod | 2 +- control-plane/cni/go.mod | 2 +- control-plane/go.mod | 2 +- control-plane/go.sum | 4 -- .../subcommand/common/common_test.go | 19 +++------ hack/aws-acceptance-test-cleanup/go.mod | 2 +- hack/copy-crds-to-chart/go.mod | 2 +- hack/helm-reference-gen/go.mod | 2 +- 16 files changed, 37 insertions(+), 60 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c371b63bdf..7ab9c55f01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ orbs: executors: go: docker: - - image: docker.mirror.hashicorp.services/cimg/go:1.19.2 + - image: docker.mirror.hashicorp.services/cimg/go:1.20.1 environment: TEST_RESULTS: /tmp/test-results # path to where test results are saved @@ -35,9 +35,9 @@ commands: - run: name: Install go, gotestsum, kind, kubectl, and helm command: | - wget https://golang.org/dl/go1.19.2.linux-amd64.tar.gz - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.19.2.linux-amd64.tar.gz - rm go1.19.2.linux-amd64.tar.gz + wget https://golang.org/dl/go1.20.1.linux-amd64.tar.gz + sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.20.1.linux-amd64.tar.gz + rm go1.20.1.linux-amd64.tar.gz echo 'export PATH=$PATH:/usr/local/go/bin' >> $BASH_ENV wget https://github.com/gotestyourself/gotestsum/releases/download/v1.8.2/gotestsum_1.8.2_linux_amd64.tar.gz @@ -169,7 +169,7 @@ commands: echo $pkgs for pkg in $pkgs do - if ! gotestsum --no-summary=all --jsonfile=jsonfile-${pkg////-} -- $pkg -p 1 -timeout 2h -failfast \ + if ! gotestsum --format=testname --no-summary=all --jsonfile=jsonfile-${pkg////-} -- $pkg -p 1 -timeout 2h -failfast \ << parameters.additional-flags >> \ -enable-multi-cluster \ ${ENABLE_ENTERPRISE:+-enable-enterprise} \ @@ -181,7 +181,7 @@ commands: break fi done - gotestsum --raw-command --junitfile "$TEST_RESULTS/gotestsum-report.xml" -- cat jsonfile* + gotestsum --format=testname --raw-command --junitfile "$TEST_RESULTS/gotestsum-report.xml" -- cat jsonfile* exit $exit_code - unless: @@ -200,7 +200,7 @@ commands: pkgs=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname) echo "Running $pkgs" - gotestsum --junitfile "$TEST_RESULTS/gotestsum-report.xml" -- $pkgs -p 1 -timeout 2h -failfast \ + gotestsum --format testname --junitfile "$TEST_RESULTS/gotestsum-report.xml" -- $pkgs -p 1 -timeout 2h -failfast \ << parameters.additional-flags >> \ ${ENABLE_ENTERPRISE:+-enable-enterprise} \ -enable-multi-cluster \ @@ -281,7 +281,7 @@ jobs: unzip consul_"${CONSUL_VERSION}"_linux_amd64.zip -d /home/circleci/bin && rm consul_"${CONSUL_VERSION}"_linux_amd64.zip PACKAGE_NAMES=$(go list ./...) - gotestsum --junitfile $TEST_RESULTS/gotestsum-report.xml -- -p 4 $PACKAGE_NAMES + gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml -- -p 4 $PACKAGE_NAMES - store_test_results: path: /tmp/test-results @@ -312,7 +312,7 @@ jobs: unzip consul_"${CONSUL_ENT_VERSION}"_linux_amd64.zip -d /home/circleci/bin && rm consul_"${CONSUL_ENT_VERSION}"_linux_amd64.zip PACKAGE_NAMES=$(go list ./...) - gotestsum --junitfile $TEST_RESULTS/gotestsum-report.xml -- -tags=enterprise -p 4 $PACKAGE_NAMES + gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml -- -tags=enterprise -p 4 $PACKAGE_NAMES - store_test_results: path: /tmp/test-results @@ -401,7 +401,7 @@ jobs: name: Run tests working_directory: *cli-path command: | - gotestsum --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 + gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 - store_test_results: path: /tmp/test-results @@ -500,7 +500,7 @@ jobs: name: Run tests working_directory: *acceptance-framework-path command: | - gotestsum --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 + gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 - store_test_results: path: /tmp/test-results @@ -523,7 +523,7 @@ jobs: name: Run tests working_directory: *helm-gen-path command: | - gotestsum --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 + gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 - store_test_results: path: /tmp/test-results @@ -1312,6 +1312,7 @@ workflows: only: - release/0.49.x - release/1.0.x + - release/1.1.x jobs: - build-distro: OS: "linux" @@ -1375,19 +1376,3 @@ workflows: - acceptance-tproxy: requires: - dev-upload-docker - - nightly-kind-acceptance-tests-consul-compatability: - description: | - Acceptance tests which run nightly to verify the compatibility between - a consul-k8s binary and it's consul version pair. Tests will be conducted - for up to n-2 previous Consul-k8s releases. - triggers: - - schedule: - cron: "0 0 * * *" # Run at 12 am UTC (5 pm PST) - filters: - branches: - only: - - main - jobs: - - acceptance-kind-1-23-consul-compat-nightly-1-12 - - acceptance-kind-1-23-consul-compat-nightly-1-13 diff --git a/.github/workflows/reusable-acceptance.yml b/.github/workflows/reusable-acceptance.yml index 142754e41a..e4401932d3 100644 --- a/.github/workflows/reusable-acceptance.yml +++ b/.github/workflows/reusable-acceptance.yml @@ -127,7 +127,7 @@ jobs: do fullpkg="github.com/hashicorp/consul-k8s/${{ inputs.directory }}/${pkg}" echo "Testing package: ${fullpkg}" - if ! gotestsum --jsonfile=jsonfile-${pkg////-} -- ${fullpkg} -p 1 -timeout 2h -failfast \ + if ! gotestsum --format=testname --jsonfile=jsonfile-${pkg////-} -- ${fullpkg} -p 1 -timeout 2h -failfast \ ${{ inputs.additional-flags }} \ -enable-enterprise \ -enable-multi-cluster \ @@ -139,7 +139,7 @@ jobs: break fi done - gotestsum --raw-command --junitfile "${{ env.TEST_RESULTS }}/gotestsum-report.xml" -- cat jsonfile* + gotestsum --format=testname --raw-command --junitfile "${{ env.TEST_RESULTS }}/gotestsum-report.xml" -- cat jsonfile* exit $exit_code - name: Upload tests diff --git a/.github/workflows/reusable-unit.yml b/.github/workflows/reusable-unit.yml index fa2b4130f5..8ba09377cc 100644 --- a/.github/workflows/reusable-unit.yml +++ b/.github/workflows/reusable-unit.yml @@ -53,5 +53,5 @@ jobs: - name: Run tests working-directory: ${{inputs.directory}} run: | - gotestsum --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml ./... -- -p 4 + gotestsum --format=testname --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml ./... -- -p 4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79eefd17f3..803aed69a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -180,7 +180,7 @@ jobs: working-directory: control-plane run: | PACKAGE_NAMES=$(go list ./...) - gotestsum --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml -- -p 4 $PACKAGE_NAMES + gotestsum --format=testname --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml -- -p 4 $PACKAGE_NAMES test-enterprise-control-plane: if: github.repository_owner == 'hashicorp' # Do not run on forks as this requires secrets @@ -228,7 +228,7 @@ jobs: working-directory: control-plane run: | PACKAGE_NAMES=$(go list ./...) - gotestsum --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml -- -tags=enterprise -p 4 $PACKAGE_NAMES + gotestsum --format=testname --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml -- -tags=enterprise -p 4 $PACKAGE_NAMES build-distros: needs: [get-go-version, get-product-version] diff --git a/.go-version b/.go-version index 836ae4eda2..0044d6cb96 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.19.2 +1.20.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a625430c0..dac76400c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,17 +24,20 @@ IMPROVEMENTS: * Add the `balanceInboundConnections` field to the `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) * Add the `upstreamConfig.overrides[].peer` field to the `ServiceDefaults` CRD. [[GH-1853]](https://github.com/hashicorp/consul-k8s/pull/1853) * Control-Plane + * Update minimum go version for project to 1.20 [[GH-1908](https://github.com/hashicorp/consul-k8s/pull/1908)] * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] * Remove extraneous `gnupg` dependency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] * Server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/pull/1770)] * CLI: + * Update minimum go version for project to 1.20 [[GH-1908](https://github.com/hashicorp/consul-k8s/pull/1908)] * Add `consul-k8s proxy log podname` command for displaying and modifying Envoy log levels for a given Pod. [GH-1844](https://github.com/hashicorp/consul-k8s/pull/1844), [GH-1849](https://github.com/hashicorp/consul-k8s/pull/1849), [GH-1864](https://github.com/hashicorp/consul-k8s/pull/1864) - BUG FIXES: * Control Plane * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] +* Security: + * Upgrade to use Go 1.20.1 This resolves vulnerabilities [CVE-2022-41724](https://go.dev/issue/58001) in `crypto/tls` and [CVE-2022-41723](https://go.dev/issue/57855) in `net/http`. [[GH-1908](https://github.com/hashicorp/consul-k8s/pull/1908)] ## 1.0.3 (January 30, 2023) diff --git a/acceptance/go.mod b/acceptance/go.mod index 55acec04ba..f81eff4066 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul-k8s/acceptance -go 1.19 +go 1.20 require ( github.com/gruntwork-io/terratest v0.31.2 diff --git a/charts/go.mod b/charts/go.mod index cdb23e46b0..f76282d756 100644 --- a/charts/go.mod +++ b/charts/go.mod @@ -1,3 +1,3 @@ module github.com/hashicorp/consul-k8s/charts -go 1.19 +go 1.20 diff --git a/cli/go.mod b/cli/go.mod index 7c1a5af4b8..4563450755 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul-k8s/cli -go 1.19 +go 1.20 require ( github.com/bgentry/speakeasy v0.1.0 diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index 2b43b784fd..c7820438cb 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -50,4 +50,4 @@ require ( replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 -go 1.19 +go 1.20 diff --git a/control-plane/go.mod b/control-plane/go.mod index d5805fe558..51e4ad39a0 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -136,4 +136,4 @@ require ( replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 -go 1.19 +go 1.20 diff --git a/control-plane/go.sum b/control-plane/go.sum index d1473ae24f..1fbb30b7ff 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -346,10 +346,6 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919 h1:8aVegJMSv7PIAAa1zqQQ0CT4TKv+Nf7I4rhE6+uDa1U= -github.com/hashicorp/consul/api v1.10.1-0.20230106171340-8d923c178919/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= -github.com/hashicorp/consul/api v1.10.1-0.20230126204442-a43eadd1225b h1:dNIQYhru10Hg+E1oEL8f9CX6MC+8CW5JuQ4jk3g70LA= -github.com/hashicorp/consul/api v1.10.1-0.20230126204442-a43eadd1225b/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= github.com/hashicorp/consul/api v1.10.1-0.20230203155153-2f149d60ccbf h1:vvsHghmX3LyNUaDe7onYKHyDiny+ystdHKIEujbNj4Q= github.com/hashicorp/consul/api v1.10.1-0.20230203155153-2f149d60ccbf/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index 521831473a..6021bd0b49 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -8,7 +8,6 @@ import ( "net/url" "os" "testing" - "time" "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover/mocks" "github.com/hashicorp/consul/api" @@ -163,36 +162,33 @@ func TestConsulLogin_TokenNotReplicated(t *testing.T) { func TestConsulLogin_EmptyBearerTokenFile(t *testing.T) { t.Parallel() - require := require.New(t) bearerTokenFile := WriteTempFile(t, "") params := LoginParams{ BearerTokenFile: bearerTokenFile, } _, err := ConsulLogin(nil, params, hclog.NewNullLogger()) - require.EqualError(err, fmt.Sprintf("no bearer token found in %q", bearerTokenFile)) + require.EqualError(t, err, fmt.Sprintf("no bearer token found in %q", bearerTokenFile)) } func TestConsulLogin_BearerTokenFileDoesNotExist(t *testing.T) { t.Parallel() - require := require.New(t) randFileName := fmt.Sprintf("/foo/%d/%d", rand.Int(), rand.Int()) params := LoginParams{ BearerTokenFile: randFileName, } _, err := ConsulLogin(nil, params, hclog.NewNullLogger()) - require.Error(err) - require.Contains(err.Error(), "unable to read bearer token file") + require.Error(t, err) + require.Contains(t, err.Error(), "unable to read bearer token file") } func TestConsulLogin_TokenFileUnwritable(t *testing.T) { t.Parallel() - require := require.New(t) bearerTokenFile := WriteTempFile(t, "foo") client := startMockServer(t) // This is a common.Logger. log, err := Logger("INFO", false) - require.NoError(err) + require.NoError(t, err) randFileName := fmt.Sprintf("/foo/%d/%d", rand.Int(), rand.Int()) params := LoginParams{ AuthMethod: testAuthMethod, @@ -201,13 +197,12 @@ func TestConsulLogin_TokenFileUnwritable(t *testing.T) { NumRetries: 2, } _, err = ConsulLogin(client, params, log) - require.Error(err) - require.Contains(err.Error(), "error writing token to file sink") + require.Error(t, err) + require.Contains(t, err.Error(), "error writing token to file sink") } func TestWriteFileWithPerms_InvalidOutputFile(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano()) randFileName := fmt.Sprintf("/tmp/tmp/tmp/%d", rand.Int()) t.Cleanup(func() { os.RemoveAll(randFileName) @@ -218,7 +213,6 @@ func TestWriteFileWithPerms_InvalidOutputFile(t *testing.T) { func TestWriteFileWithPerms_OutputFileExists(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano()) randFileName := fmt.Sprintf("/tmp/%d", rand.Int()) err := os.WriteFile(randFileName, []byte("foo"), os.FileMode(0444)) require.NoError(t, err) @@ -236,7 +230,6 @@ func TestWriteFileWithPerms_OutputFileExists(t *testing.T) { func TestWriteFileWithPerms(t *testing.T) { t.Parallel() payload := "foo-foo-foo-foo" - rand.Seed(time.Now().UnixNano()) randFileName := fmt.Sprintf("/tmp/%d", rand.Int()) t.Cleanup(func() { os.RemoveAll(randFileName) diff --git a/hack/aws-acceptance-test-cleanup/go.mod b/hack/aws-acceptance-test-cleanup/go.mod index 13e8f48909..ac4f7b0d1a 100644 --- a/hack/aws-acceptance-test-cleanup/go.mod +++ b/hack/aws-acceptance-test-cleanup/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul-helm/hack/aws-acceptance-test-cleanup -go 1.19 +go 1.20 require ( github.com/aws/aws-sdk-go v1.38.63 diff --git a/hack/copy-crds-to-chart/go.mod b/hack/copy-crds-to-chart/go.mod index 73b1f10306..c224f8f244 100644 --- a/hack/copy-crds-to-chart/go.mod +++ b/hack/copy-crds-to-chart/go.mod @@ -1,3 +1,3 @@ module github.com/hashicorp/consul-k8s/hack/copy-crds-to-chart -go 1.19 +go 1.20 diff --git a/hack/helm-reference-gen/go.mod b/hack/helm-reference-gen/go.mod index 7e41675f18..36e1ff3a8d 100644 --- a/hack/helm-reference-gen/go.mod +++ b/hack/helm-reference-gen/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul-k8s/hack/helm-reference-gen -go 1.19 +go 1.20 require ( github.com/stretchr/testify v1.6.1 From 60d1023bc4610e5f09d66ff368dfe22af4fb8263 Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Tue, 21 Feb 2023 08:44:09 -0600 Subject: [PATCH 078/592] update troubleshoot module and some messages for consistency across consul and consul-k8s (#1922) - update troubleshoot module and some messages for consistency across consul and consul-k8s --- acceptance/tests/cli/cli_install_test.go | 4 ++-- cli/cmd/troubleshoot/proxy/proxy.go | 4 ++-- cli/cmd/troubleshoot/upstreams/upstreams.go | 9 ++++++++- cli/go.mod | 2 +- cli/go.sum | 4 ++-- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index d45093dd59..b6c52e9fc4 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -109,7 +109,7 @@ func TestInstall(t *testing.T) { proxyOut, err := cli.Run(t, ctx.KubectlOptions(t), "troubleshoot", "proxy", "-pod", clientPodName, "-upstream-ip", serverIP) require.NoError(t, err) - require.Regexp(t, "upstream resources are valid", string(proxyOut)) + require.Regexp(t, "Upstream resources are valid", string(proxyOut)) logger.Log(t, string(proxyOut)) } else { // With tproxy disabled and explicit upstreams we need the envoy-id of the server @@ -117,7 +117,7 @@ func TestInstall(t *testing.T) { proxyOut, err := cli.Run(t, ctx.KubectlOptions(t), "troubleshoot", "proxy", "-pod", clientPodName, "-upstream-envoy-id", "static-server") require.NoError(t, err) - require.Regexp(t, "upstream resources are valid", string(proxyOut)) + require.Regexp(t, "Upstream resources are valid", string(proxyOut)) logger.Log(t, string(proxyOut)) } diff --git a/cli/cmd/troubleshoot/proxy/proxy.go b/cli/cmd/troubleshoot/proxy/proxy.go index d17c491f5d..624757fad3 100644 --- a/cli/cmd/troubleshoot/proxy/proxy.go +++ b/cli/cmd/troubleshoot/proxy/proxy.go @@ -228,8 +228,8 @@ func (c *ProxyCommand) Troubleshoot() error { c.UI.Output(o.Message, terminal.WithSuccessStyle()) } else { c.UI.Output(o.Message, terminal.WithErrorStyle()) - if o.PossibleActions != "" { - c.UI.Output(fmt.Sprintf("possible actions: %v", o.PossibleActions), terminal.WithInfoStyle()) + for _, action := range o.PossibleActions { + c.UI.Output(fmt.Sprintf("-> %s", action), terminal.WithInfoStyle()) } } } diff --git a/cli/cmd/troubleshoot/upstreams/upstreams.go b/cli/cmd/troubleshoot/upstreams/upstreams.go index c11a82ac6e..7765b170a7 100644 --- a/cli/cmd/troubleshoot/upstreams/upstreams.go +++ b/cli/cmd/troubleshoot/upstreams/upstreams.go @@ -196,7 +196,7 @@ func (c *UpstreamsCommand) Troubleshoot() error { return fmt.Errorf("error getting upstreams: %v", err) } - c.UI.Output(fmt.Sprintf("Envoy Identifiers (explicit upstreams only) (%v)", len(envoyIDs)), terminal.WithHeaderStyle()) + c.UI.Output(fmt.Sprintf("Upstreams (explicit upstreams only) (%v)", len(envoyIDs)), terminal.WithHeaderStyle()) for _, e := range envoyIDs { c.UI.Output(e) } @@ -208,6 +208,13 @@ func (c *UpstreamsCommand) Troubleshoot() error { } c.UI.Table(table) + c.UI.Output("\nIf you cannot find the upstream address or cluster for a transparent proxy upstream:", terminal.WithInfoStyle()) + c.UI.Output("-> Check intentions: Transparent proxy upstreams are configured based on intentions. Make sure you "+ + "have configured intentions to allow traffic to your upstream.", terminal.WithInfoStyle()) + c.UI.Output("-> To check that the right cluster is being dialed, run a DNS lookup "+ + "for the upstream you are dialing. For example, run `dig backend.svc.consul` to return the IP address for the `backend` service. If the address you get from that is missing "+ + "from the upstream IPs, it means that your proxy may be misconfigured.", terminal.WithInfoStyle()) + return nil } diff --git a/cli/go.mod b/cli/go.mod index 4563450755..ae69aa5a64 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -8,7 +8,7 @@ require ( github.com/fatih/color v1.13.0 github.com/google/go-cmp v0.5.8 github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 - github.com/hashicorp/consul/troubleshoot v0.0.0-20230210154717-4f2ce606547b + github.com/hashicorp/consul/troubleshoot v0.0.0-20230217154305-8dab825c3640 github.com/hashicorp/go-hclog v1.2.1 github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc github.com/kr/text v0.2.0 diff --git a/cli/go.sum b/cli/go.sum index d736c0176a..6582dc76ac 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -443,8 +443,8 @@ github.com/hashicorp/consul/envoyextensions v0.0.0-20230210154717-4f2ce606547b h github.com/hashicorp/consul/envoyextensions v0.0.0-20230210154717-4f2ce606547b/go.mod h1:oJKG0zAMtq6ZmZNYQyeKh6kIJmi01rZSZDSgnjzZ15w= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU= -github.com/hashicorp/consul/troubleshoot v0.0.0-20230210154717-4f2ce606547b h1:I5zDW3o7KwW4cX5kkerhm7bZOEknlSjdnIgtxnhBxOk= -github.com/hashicorp/consul/troubleshoot v0.0.0-20230210154717-4f2ce606547b/go.mod h1:rskvju2tK8XvHYTAILHjO7lpV1/uViHs3Q3mg9Rkwlg= +github.com/hashicorp/consul/troubleshoot v0.0.0-20230217154305-8dab825c3640 h1:P81kThpSzUW2oERDMrLsiZE3OuilLo3/EQhtVQW5M+8= +github.com/hashicorp/consul/troubleshoot v0.0.0-20230217154305-8dab825c3640/go.mod h1:rskvju2tK8XvHYTAILHjO7lpV1/uViHs3Q3mg9Rkwlg= 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= From 74af05612c0f53c292d3aa1727106d4e8ab2cce3 Mon Sep 17 00:00:00 2001 From: Iryna Shustava Date: Tue, 21 Feb 2023 12:31:46 -0700 Subject: [PATCH 079/592] Fix a typo in circleci config (#1931) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7ab9c55f01..9c84f112cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -565,7 +565,7 @@ jobs: ########################### acceptance: environment: - - TEST_RESULTS: /tmp/test-results] + - TEST_RESULTS: /tmp/test-results - CONSUL_TEST_IMAGE: *consul-test-image machine: image: ubuntu-2004:202010-01 From 0cb71f2890a97bf559585ccdbbef32aa6c588a68 Mon Sep 17 00:00:00 2001 From: David Yu Date: Wed, 22 Feb 2023 08:25:59 -0800 Subject: [PATCH 080/592] Dockerfile: bump Alpine base image to 3.17 (#1934) * Update Dockerfile --- CHANGELOG.md | 1 + control-plane/Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dac76400c8..222af34026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ IMPROVEMENTS: * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] * Remove extraneous `gnupg` dependency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] * Server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/pull/1770)] + * Update alpine to 3.17 in the Docker image. [[GH-1934](https://github.com/hashicorp/consul-k8s/pull/1934)] * CLI: * Update minimum go version for project to 1.20 [[GH-1908](https://github.com/hashicorp/consul-k8s/pull/1908)] * Add `consul-k8s proxy log podname` command for displaying and modifying Envoy log levels for a given Pod. [GH-1844](https://github.com/hashicorp/consul-k8s/pull/1844), [GH-1849](https://github.com/hashicorp/consul-k8s/pull/1849), [GH-1864](https://github.com/hashicorp/consul-k8s/pull/1864) diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index 2989712a5f..850cdd42c6 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -19,7 +19,7 @@ RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@49f60 # dev copies the binary from a local build # ----------------------------------- # BIN_NAME is a requirement in the hashicorp docker github action -FROM alpine:3.16 AS dev +FROM alpine:3.17 AS dev # NAME and VERSION are the name of the software in releases.hashicorp.com # and the version to download. Example: NAME=consul VERSION=1.2.3. @@ -71,7 +71,7 @@ CMD /bin/${BIN_NAME} # We don't rebuild the software because we want the exact checksums and # binary signatures to match the software and our builds aren't fully # reproducible currently. -FROM alpine:3.16 AS release-default +FROM alpine:3.17 AS release-default ARG BIN_NAME=consul-k8s-control-plane ARG CNI_BIN_NAME=consul-cni From bd7a7526c8ee9266883c8acca8afeefa815e1d05 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Wed, 22 Feb 2023 08:54:03 -0800 Subject: [PATCH 081/592] Include allowed presets in err msg (#1933) * Include allowed presets in err msg --- cli/cmd/install/install.go | 2 +- cli/cmd/install/install_test.go | 14 ++++++++++---- cli/cmd/upgrade/upgrade.go | 3 +-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cli/cmd/install/install.go b/cli/cmd/install/install.go index 7b5d5bb31c..cdfef4f670 100644 --- a/cli/cmd/install/install.go +++ b/cli/cmd/install/install.go @@ -589,7 +589,7 @@ func (c *Command) validateFlags(args []string) error { return fmt.Errorf("cannot set both -%s and -%s", flagNameConfigFile, flagNamePreset) } if ok := slices.Contains(preset.Presets, c.flagPreset); c.flagPreset != defaultPreset && !ok { - return fmt.Errorf("'%s' is not a valid preset", c.flagPreset) + return fmt.Errorf("'%s' is not a valid preset (valid presets: %s)", c.flagPreset, strings.Join(preset.Presets, ", ")) } if !common.IsValidLabel(c.flagNamespace) { return fmt.Errorf("'%s' is an invalid namespace. Namespaces follow the RFC 1123 label convention and must "+ diff --git a/cli/cmd/install/install_test.go b/cli/cmd/install/install_test.go index 07c04defef..da77b740f8 100644 --- a/cli/cmd/install/install_test.go +++ b/cli/cmd/install/install_test.go @@ -162,39 +162,45 @@ func TestValidateFlags(t *testing.T) { testCases := []struct { description string input []string + expErr string }{ { "Should disallow non-flag arguments.", []string{"foo", "-auto-approve"}, + "should have no non-flag arguments", }, { "Should disallow specifying both values file AND presets.", []string{"-f='f.txt'", "-preset=demo"}, + "cannot set both -config-file and -preset", }, { "Should error on invalid presets.", []string{"-preset=foo"}, + "'foo' is not a valid preset (valid presets: cloud, quickstart, secure)", }, { "Should error on invalid timeout.", []string{"-timeout=invalid-timeout"}, + "unable to parse -timeout: time: invalid duration \"invalid-timeout\"", }, { "Should error on an invalid namespace. If this failed, TestValidLabel() probably did too.", []string{"-namespace=\" nsWithSpace\""}, + "'\" nsWithSpace\"' is an invalid namespace. Namespaces follow the RFC 1123 label convention and must consist of a lower case alphanumeric character or '-' and must start/end with an alphanumeric character", }, { - "Should have errored on a non-existant file.", + "Should have errored on a non-existent file.", []string{"-f=\"does_not_exist.txt\""}, + "file '\"does_not_exist.txt\"' does not exist", }, } for _, testCase := range testCases { c := getInitializedCommand(t, nil) t.Run(testCase.description, func(t *testing.T) { - if err := c.validateFlags(testCase.input); err == nil { - t.Errorf("Test case should have failed.") - } + err := c.validateFlags(testCase.input) + require.EqualError(t, err, testCase.expErr) }) } } diff --git a/cli/cmd/upgrade/upgrade.go b/cli/cmd/upgrade/upgrade.go index 4c962c47b5..2672a9f989 100644 --- a/cli/cmd/upgrade/upgrade.go +++ b/cli/cmd/upgrade/upgrade.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/consul-k8s/cli/helm" "github.com/hashicorp/consul-k8s/cli/preset" "github.com/posener/complete" - helmCLI "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/getter" @@ -425,7 +424,7 @@ func (c *Command) validateFlags(args []string) error { return fmt.Errorf("cannot set both -%s and -%s", flagNameConfigFile, flagNamePreset) } if ok := slices.Contains(preset.Presets, c.flagPreset); c.flagPreset != defaultPreset && !ok { - return fmt.Errorf("'%s' is not a valid preset", c.flagPreset) + return fmt.Errorf("'%s' is not a valid preset (valid presets: %s)", c.flagPreset, strings.Join(preset.Presets, ", ")) } if _, err := time.ParseDuration(c.flagTimeout); err != nil { return fmt.Errorf("unable to parse -%s: %s", flagNameTimeout, err) From 3550b61f63557aed7e8d30a833c632a943ba0a04 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:17:01 -0800 Subject: [PATCH 082/592] Update docs for global.tls.caKey (#1900) It's no longer required since https://github.com/hashicorp/consul-helm/pull/1046 --- charts/consul/values.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 63c2a63cbf..dfe98f6bab 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -364,8 +364,9 @@ global: # # Note that we need the CA key so that we can generate server and client certificates. # It is particularly important for the client certificates since they need to have host IPs - # as Subject Alternative Names. In the future, we may support bringing your own server - # certificates. + # as Subject Alternative Names. If you are setting server certs yourself via `server.serverCert` + # and you are not enabling clients (or clients are enabled with autoEncrypt) then you do not + # need to provide the CA key. caKey: # The name of the Kubernetes or Vault secret that holds the CA key. # @type: string From a263119c2287ea487197cbd711e2195ab499f5df Mon Sep 17 00:00:00 2001 From: David Yu Date: Wed, 22 Feb 2023 10:26:17 -0800 Subject: [PATCH 083/592] Removing docs directory since no longer relevant (#1942) --- docs/admin-partitions-with-acls.md | 98 ------------------------------ 1 file changed, 98 deletions(-) delete mode 100644 docs/admin-partitions-with-acls.md diff --git a/docs/admin-partitions-with-acls.md b/docs/admin-partitions-with-acls.md deleted file mode 100644 index fb282fa38d..0000000000 --- a/docs/admin-partitions-with-acls.md +++ /dev/null @@ -1,98 +0,0 @@ -## Installing Admin Partitions with ACLs enabled - -To enable ACLs on the server cluster use the following config: -```yaml -global: - enableConsulNamespaces: true - tls: - enabled: true - image: hashicorp/consul-enterprise:1.11.1 - adminPartitions: - enabled: true - acls: - manageSystemACLs: true -server: - exposeGossipAndRPCPorts: true - enterpriseLicense: - secretName: license - secretKey: key - replicas: 1 -connectInject: - enabled: true - transparentProxy: - defaultEnabled: false - consulNamespaces: - mirroringK8S: true -controller: - enabled: true -meshGateway: - enabled: true -``` - -Identify the LoadBalancer External IP of the `partition-service` -```bash -kubectl get svc consul-consul-partition-service -o json | jq -r '.status.loadBalancer.ingress[0].ip' -``` - -Migrate the TLS CA credentials from the server cluster to the workload clusters -```bash -kubectl get secret consul-consul-ca-key --context "server-context" -o json | kubectl apply --context "workload-context" -f - -kubectl get secret consul-consul-ca-cert --context "server-context" -o json | kubectl apply --context "workload-context" -f - -``` - -Migrate the Partition token from the server cluster to the workload clusters -```bash -kubectl get secret consul-consul-partitions-acl-token --context "server-context" -o json | kubectl apply --context "workload-context" -f - -``` - -Identify the Kubernetes AuthMethod URL of the workload cluster to use as the `k8sAuthMethodHost`: -```bash -kubectl config view -o "jsonpath={.clusters[?(@.name=='workload-cluster-name')].cluster.server}" -``` - -Configure the workload cluster using the following: - -```yaml -global: - enabled: false - enableConsulNamespaces: true - image: hashicorp/consul-enterprise:1.11.1 - adminPartitions: - enabled: true - name: "partition-name" - tls: - enabled: true - caCert: - secretName: consul-consul-ca-cert - secretKey: tls.crt - caKey: - secretName: consul-consul-ca-key - secretKey: tls.key - acls: - manageSystemACLs: true - bootstrapToken: - secretName: consul-consul-partitions-acl-token - secretKey: token -server: - enterpriseLicense: - secretName: license - secretKey: key -externalServers: - enabled: true - hosts: [ "loadbalancer IP" ] - tlsServerName: server.dc1.consul - k8sAuthMethodHost: "authmethod-host IP" -client: - enabled: true - exposeGossipPorts: true - join: [ "loadbalancer IP" ] -connectInject: - enabled: true - consulNamespaces: - mirroringK8S: true -controller: - enabled: true -meshGateway: - enabled: true -``` -This should create clusters that have Admin Partitions deployed on them with ACLs enabled. From d6789a395dbb454d5ea1caeceb4e74e2acd78156 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 28 Feb 2023 14:22:01 -0500 Subject: [PATCH 084/592] Update main to the latest versions post 1.1.0 (#1954) * update to the latest versions --- CHANGELOG.md | 9 ++++++++- charts/consul/Chart.yaml | 14 +++++++------- charts/consul/values.yaml | 8 ++++---- cli/version/version.go | 2 +- control-plane/version/version.go | 2 +- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 222af34026..81edcde7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## UNRELEASED +## 1.1.0 (February 27, 2023) + BREAKING CHANGES: * Helm: * Change defaults to exclude the `openebs` namespace from sidecar injection. If you previously had pods in that namespace @@ -17,6 +19,7 @@ BREAKING CHANGES: IMPROVEMENTS: * Helm: + * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] * Kubernetes v1.26 is now supported. Minimum tested version of Kubernetes is now v1.23. [[GH-1852](https://github.com/hashicorp/consul-k8s/pull/1852)] * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] * Add the `accessLogs` field to the `ProxyDefaults` CRD. [[GH-1816](https://github.com/hashicorp/consul-k8s/pull/1816)] @@ -36,7 +39,11 @@ IMPROVEMENTS: BUG FIXES: * Control Plane - * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] + * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] + * Add discover binary to control-plane image [[GH-1749](https://github.com/hashicorp/consul-k8s/pull/1749)] +* Helm: + * Don't pass in a CA file to the API Gateway controller when `externalServers.useSystemRoots` is `true`. [[GH-1743](https://github.com/hashicorp/consul-k8s/pull/1743)] + * Use the correct autogenerated cert for the API Gateway Controller when connecting to servers versus clients. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] * Security: * Upgrade to use Go 1.20.1 This resolves vulnerabilities [CVE-2022-41724](https://go.dev/issue/58001) in `crypto/tls` and [CVE-2022-41723](https://go.dev/issue/57855) in `net/http`. [[GH-1908](https://github.com/hashicorp/consul-k8s/pull/1908)] diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index 41b95611e6..58ca134137 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 name: consul -version: 1.1.0-dev -appVersion: 1.14.4 -kubeVersion: ">=1.21.0-0" +version: 1.2.0-dev +appVersion: 1.15.0 +kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io icon: https://raw.githubusercontent.com/hashicorp/consul-k8s/main/assets/icon.png @@ -13,13 +13,13 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: hashicorp/consul:1.14.4 + image: hashicorp/consul:1.15.0 - name: consul-k8s-control-plane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.1.0-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.0-dev - name: consul-dataplane - image: hashicorp/consul-dataplane:1.0.1 + image: hashicorp/consul-dataplane:1.1.0 - name: envoy - image: envoyproxy/envoy:v1.23.1 + image: envoyproxy/envoy:v1.25.1 artifacthub.io/license: MPL-2.0 artifacthub.io/links: | - name: Documentation diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 8b5055572a..dbc5935211 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -63,7 +63,7 @@ global: # image: "hashicorp/consul-enterprise:1.10.0-ent" # ``` # @default: hashicorp/consul: - image: "hashicorp/consul:1.14.4" + image: "hashicorp/consul:1.15.0" # 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. @@ -83,7 +83,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.1.0-dev + imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.0-dev # The name of the datacenter that the agents should # register as. This can't be changed once the Consul cluster is up and running @@ -544,7 +544,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: "hashicorp/consul-dataplane:1.0.1" + imageConsulDataplane: "hashicorp/consul-dataplane:1.1.0" # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. @@ -2858,7 +2858,7 @@ apiGateway: # The name (and tag) of the Envoy Docker image used for the # apiGateway. For other Consul compoenents, imageEnvoy has been replaced with Consul Dataplane. # @default: envoyproxy/envoy: - imageEnvoy: "envoyproxy/envoy:v1.23.1" + imageEnvoy: "envoyproxy/envoy:v1.25.1" # Override global log verbosity level for api-gateway-controller pods. One of "debug", "info", "warn", or "error". # @type: string diff --git a/cli/version/version.go b/cli/version/version.go index 933f072f35..8ae06829bb 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -14,7 +14,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.1.0" + Version = "1.2.0" // 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 diff --git a/control-plane/version/version.go b/control-plane/version/version.go index 933f072f35..8ae06829bb 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -14,7 +14,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.1.0" + Version = "1.2.0" // 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 From 1ab2cacc52851c9a7bc5e71a824367de9d541b53 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 28 Feb 2023 11:26:36 -0800 Subject: [PATCH 085/592] Added support for go-changelog automations (#1947) * Added some go-changelog automations - added templates for go-changelog, pretty much copied from Consul - added a checker for missing changelog entries * added contributing doc information on new changelog * update prepare release to update the changelog - Prepare release now requires an additional LAST_RELEASE_GIT_TAG environment variable required by go-changelog - removed adding the unreleased tag to the Changelog as we will no longer be doing that. All changelog entries will be added at the time of release by the go-changelog tool --- .changelog/changelog.tmpl | 57 ++++++ .changelog/note.tmpl | 3 + .github/workflows/changelog-checker.yml | 46 +++++ CONTRIBUTING.md | 41 ++++ Makefile | 4 +- .../build-support/functions/10-util.sh | 177 +++++------------- 6 files changed, 200 insertions(+), 128 deletions(-) create mode 100644 .changelog/changelog.tmpl create mode 100644 .changelog/note.tmpl create mode 100644 .github/workflows/changelog-checker.yml diff --git a/.changelog/changelog.tmpl b/.changelog/changelog.tmpl new file mode 100644 index 0000000000..c1de4293b9 --- /dev/null +++ b/.changelog/changelog.tmpl @@ -0,0 +1,57 @@ +{{- if index .NotesByType "breaking-change" -}} +BREAKING CHANGES: + +{{range index .NotesByType "breaking-change" -}} +* {{ template "note" . }} +{{ end -}} +{{- end -}} + +{{- if .NotesByType.security }} +SECURITY: + +{{range .NotesByType.security -}} +* {{ template "note" . }} +{{ end -}} +{{- end -}} + +{{- if .NotesByType.feature }} +FEATURES: + +{{range .NotesByType.feature -}} +* {{ template "note" . }} +{{ end -}} +{{- end -}} + +{{- $improvements := combineTypes .NotesByType.improvement .NotesByType.enhancement -}} +{{- if $improvements }} +IMPROVEMENTS: + +{{range $improvements | sort -}} +* {{ template "note" . }} +{{ end -}} +{{- end -}} + +{{- if .NotesByType.deprecation }} +DEPRECATIONS: + +{{range .NotesByType.deprecation -}} +* {{ template "note" . }} +{{ end -}} +{{- end -}} + +{{- if .NotesByType.bug }} +BUG FIXES: + +{{range .NotesByType.bug -}} +* {{ template "note" . }} +{{ end -}} +{{- end -}} + +{{- if .NotesByType.note }} +NOTES: + +{{range .NotesByType.note -}} +* {{ template "note" . }} +{{ end -}} +{{- end -}} + diff --git a/.changelog/note.tmpl b/.changelog/note.tmpl new file mode 100644 index 0000000000..7588c65fd4 --- /dev/null +++ b/.changelog/note.tmpl @@ -0,0 +1,3 @@ +{{- define "note" -}} +{{.Body}}{{if not (stringHasPrefix .Issue "_")}} [[GH-{{- .Issue -}}](https://github.com/hashicorp/consul-k8s/issues/{{- .Issue -}})]{{end}} +{{- end -}} diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml new file mode 100644 index 0000000000..bb13255d29 --- /dev/null +++ b/.github/workflows/changelog-checker.yml @@ -0,0 +1,46 @@ +# This workflow checks that there is either a 'pr/no-changelog' label applied to a PR +# or there is a .changelog/.txt file associated with a PR for a changelog entry + +name: Changelog Checker + +on: + pull_request: + types: [opened, synchronize, labeled] + # Runs on PRs to main and all release branches + branches: + - main + - release/* + +jobs: + # checks that a .changelog entry is present for a PR + changelog-check: + # If there a `pr/no-changelog` label we ignore this check. Also, we ignore PRs created by the bot assigned to `backport-assistant` + if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/no-changelog') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 # by default the checkout action doesn't checkout all branches + - name: Check for changelog entry in diff + run: | + # check if there is a diff in the .changelog directory + # for PRs against the main branch, the changelog file name should match the PR number + if [ "${{ github.event.pull_request.base.ref }}" = "${{ github.event.repository.default_branch }}" ]; then + enforce_matching_pull_request_number="matching this PR number " + changelog_file_path=".changelog/(_)?${{ github.event.pull_request.number }}.txt" + else + changelog_file_path=".changelog/[_0-9]*.txt" + fi + + changelog_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/main")" | egrep ${changelog_file_path}) + + # If we do not find a file in .changelog/, we fail the check + if [ -z "$changelog_files" ]; then + # Fail status check when no .changelog entry was found on the PR + echo "Did not find a .changelog entry ${enforce_matching_pull_request_number}and the 'pr/no-changelog' label was not applied. Reference - https://github.com/hashicorp/consul/pull/8387" + exit 1 + else + echo "Found .changelog entry in PR!" + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 510d4c3b3f..8e634e323e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,7 @@ 1. [Writing Acceptance tests](#writing-acceptance-tests) 1. [Using the Acceptance Test Framework to Debug](#using-acceptance-test-framework-to-debug) 1. [Helm Reference Docs](#helm-reference-docs) +1. [Adding a Changelog Entry](#adding-a-changelog-entry) ## Contributing 101 @@ -1214,3 +1215,43 @@ So that the documentation can look like: ```markdown - `ports` ((#v-ingressgateways-defaults-service-ports)) (`array: [{port: 8080, port: 8443}]`) - Port docs ``` + +## Adding a Changelog Entry + +Any change that a Consul-K8s user might need to know about should have a changelog entry. + +What doesn't need a changelog entry? +- Typos/fixes, unless they are in a public-facing API +- Code changes we are certain no Consul-K8s users will need to know about + +To include a [changelog entry](../.changelog) in a PR, commit a text file +named `.changelog/.txt`, where `` is the number associated with the open +PR in GitHub. The text file should describe the changes in the following format: + +```` +```release-note: +: +``` +```` + +Valid values for `` include: +- `feature`: for the addition of a new feature +- `improvement`: for an improvement (not a bug fix) to an existing feature +- `bug`: for a bug fix +- `security`: for any Common Vulnerabilities and Exposures (CVE) resolutions +- `breaking-change`: for any change that is not fully backwards-compatible +- `deprecation`: for functionality which is now marked for removal in a future release + +`` is meant to categorize the functionality affected by the change. +Some common values are: +- `cli`: related to the command-line interface and its commands +- `control-plane`: related to control-plane functionality +- `helm`: related to the charts module and any files, yaml, go, etc. therein + +There may be cases where a `code area` doesn't make sense (i.e. addressing a Go CVE). In these +cases it is okay not to provide a `code area`. + +For more examples, look in the [`.changelog/`](../.changelog) folder for existing changelog entries. + +If a PR deserves multiple changelog entries, just add multiple entries separated by a newline +in the format described above to the `.changelog/.txt` file. diff --git a/Makefile b/Makefile index 6ad59a2a91..daee6b693b 100644 --- a/Makefile +++ b/Makefile @@ -156,7 +156,7 @@ endif ifndef RELEASE_DATE $(error RELEASE_DATE is required, use format , (ex. October 4, 2022)) endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" $(PRERELEASE_VERSION) + source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" $(LAST_RELEASE_GIT_TAG) $(PRERELEASE_VERSION) prepare-dev: ifndef RELEASE_VERSION @@ -168,7 +168,7 @@ endif ifndef NEXT_RELEASE_VERSION $(error NEXT_RELEASE_VERSION is required) endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" $(NEXT_RELEASE_VERSION) $(PRERELEASE_VERSION) + source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" $(NEXT_RELEASE_VERSION) # ===========> Makefile config diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index b807d35397..8367c22743 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -684,94 +684,6 @@ function update_version_helm { return $? } -function set_changelog_version { - # Arguments: - # $1 - Path to top level Consul source - # $2 - Version to put into the Changelog - # $3 - Release Date - # - # Returns: - # 0 - success - # * - error - - local changelog="${1}/CHANGELOG.md" - local version="$2" - local rel_date="$3" - - if ! test -f "${changelog}" - then - err "ERROR: File not found: ${changelog}" - return 1 - fi - - if test -z "${version}" - then - err "ERROR: Must specify a version to put into the changelog" - return 1 - fi - - if test -z "${rel_date}" - then - rel_date=$(date +"%B %d, %Y") - fi - - sed_i ${SED_EXT} -e "s/## UNRELEASED/## ${version} (${rel_date})/" "${changelog}" - return $? -} - -function unset_changelog_version { - # Arguments: - # $1 - Path to top level Consul source - # - # Returns: - # 0 - success - # * - error - - local changelog="${1}/CHANGELOG.md" - - if ! test -f "${changelog}" - then - err "ERROR: File not found: ${changelog}" - return 1 - fi - - sed_i ${SED_EXT} -e "1 s/^## [0-9]+\.[0-9]+\.[0-9]+ \([^)]*\)/## UNRELEASED/" "${changelog}" - return $? -} - -function add_unreleased_to_changelog { - # Arguments: - # $1 - Path to top level Consul source - # - # Returns: - # 0 - success - # * - error - - local changelog="${1}/CHANGELOG.md" - - if ! test -f "${changelog}" - then - err "ERROR: File not found: ${changelog}" - return 1 - fi - - # Check if we are already in unreleased mode - if head -n 1 "${changelog}" | grep -q -c UNRELEASED - then - return 0 - fi - - local tfile="$(mktemp) -t "CHANGELOG.md_")" - ( - echo -e "## UNRELEASED\n" > "${tfile}" && - cat "${changelog}" >> "${tfile}" && - cp "${tfile}" "${changelog}" - ) - local ret=$? - rm "${tfile}" - return $ret -} - function set_version { # Arguments: # $1 - Path to top level Consul source @@ -803,21 +715,18 @@ function set_version { status_stage "==> Updating control-plane version/version.go with version info: ${vers} "$4"" if ! update_version "${sdir}/control-plane/version/version.go" "${vers}" "$4" then - unset_changelog_version "${sdir}" return 1 fi status_stage "==> Updating cli version/version.go with version info: ${vers} "$4"" if ! update_version "${sdir}/cli/version/version.go" "${vers}" "$4" then - unset_changelog_version "${sdir}" return 1 fi status_stage "==> Updating Helm chart versions with version info: ${vers} "$4"" if ! update_version_helm "${sdir}/charts/consul" "${vers}" "$4" "$5" then - unset_changelog_version "${sdir}" return 1 fi @@ -825,31 +734,52 @@ function set_version { } function set_changelog { - # Arguments: - # $1 - Path to top level Consul source - # $2 - The version of the release - # $3 - The release date - # $4 - The pre-release version - # - # - # Returns: - # 0 - success - # * - error - local sdir="$1" - local vers="$2" - local rel_date="$(date +"%B %d, %Y")" - if test -n "$3" - then - rel_date="$3" - fi + # Arguments: + # $1 - Path to top level Consul source + # $2 - Version + # $3 - Release Date + # $4 - The last git release tag + # + # + # Returns: + # 0 - success + # * - error - local changelog_vers="${vers}" - if test -n "$4" - then - changelog_vers="${vers}-$4" - fi - status_stage "==> Updating CHANGELOG.md with release info: ${changelog_vers} (${rel_date})" - set_changelog_version "${sdir}" "${changelog_vers}" "${rel_date}" || return 1 + # Check if changelog-build is installed + if ! command -v changelog-build &> /dev/null; then + echo "Error: changelog-build is not installed. Please install it and try again." + exit 1 + fi + + local curdir="$1" + local version="$2" + local rel_date="$(date +"%B %d, %Y")" + if test -n "$3" + then + rel_date="$3" + fi + local last_release_date_git_tag=$4 + + if test -z "${version}" + then + err "ERROR: Must specify a version to put into the changelog" + return 1 + fi + + if [ -z "$LAST_RELEASE_GIT_TAG" ]; then + echo "Error: LAST_RELEASE_GIT_TAG not specified." + exit 1 + fi + +cat < tmp && mv tmp "${curdir}"/CHANGELOG.MD +## ${version} (${rel_date}) +$(changelog-build -last-release ${LAST_RELEASE_GIT_TAG} \ + -entries-dir .changelog/ \ + -changelog-template .changelog/changelog.tmpl \ + -note-template .changelog/note.tmpl \ + -this-release $(git rev-parse HEAD)) + +EOT } function prepare_release { @@ -857,14 +787,16 @@ function prepare_release { # $1 - Path to top level Consul source # $2 - The version of the release # $3 - The release date - # $4 - The pre-release version + # $4 - The last release git tag for this branch (eg. v1.1.0) + # $5 - The pre-release version # # # Returns: # 0 - success # * - error - echo "release version: " $1 $2 $3 $4 - set_version "$1" "$2" "$3" "$4" "hashicorp\/consul-k8s-control-plane:" + + echo "release version: " "$1" "$2" "$3" "$4" + set_version "$1" "$2" "$3" "$5" "hashicorp\/consul-k8s-control-plane:" set_changelog "$1" "$2" "$3" "$4" } @@ -874,22 +806,15 @@ function prepare_dev { # $2 - The version of the release # $3 - The release date # $4 - The version of the next release - # $5 - The pre-release version (for setting beta in changelog) + # $5 - The last release git tag for this branch (eg. v1.1.0) # # Returns: # 0 - success # * - error echo "dev version: " $1 $4 $3 "dev" - - local sdir="$1" - - set_changelog "$1" "$2" "$3" "$5" set_version "$1" "$4" "$3" "dev" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" - status_stage "==> Adding new UNRELEASED label in CHANGELOG.md" - add_unreleased_to_changelog "${sdir}" || return 1 - return 0 } From 2e8eeb2f58793a2fb99f82335dd4fe3577af7f3a Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Wed, 1 Mar 2023 15:23:58 -0800 Subject: [PATCH 086/592] updated security scan to have parity with consul-dataplane (#1965) --- .release/security-scan.hcl | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.release/security-scan.hcl b/.release/security-scan.hcl index 994e169dd9..516ae4f5e6 100644 --- a/.release/security-scan.hcl +++ b/.release/security-scan.hcl @@ -1,14 +1,13 @@ container { - dependencies = false - alpine_secdb = false - secrets = false + dependencies = true + alpine_secdb = true + secrets = true } binary { - secrets = false - go_modules = false - osv = false + secrets = true + go_modules = true + osv = true oss_index = false nvd = false -} - +} \ No newline at end of file From 968eb429c993b099cb28c652703ab22a9f32c415 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 2 Mar 2023 08:29:28 -0800 Subject: [PATCH 087/592] add 365 days instead of 1 year to account for leap years (#1969) --- control-plane/subcommand/tls-init/command_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/control-plane/subcommand/tls-init/command_test.go b/control-plane/subcommand/tls-init/command_test.go index 33493bba16..ae3cbd8982 100644 --- a/control-plane/subcommand/tls-init/command_test.go +++ b/control-plane/subcommand/tls-init/command_test.go @@ -395,7 +395,9 @@ func TestRun_CreatesServerCertificatesWithExpiryWithinSpecifiedDays(t *testing.T certBlock, _ := pem.Decode(newServerCert) certificate, err := x509.ParseCertificate(certBlock.Bytes) require.NoError(t, err) - require.Equal(t, time.Now().AddDate(1, 0, 0).Unix(), certificate.NotAfter.Unix()) + + // Add 365 days instead of 1 year to account for leap years + require.Equal(t, time.Now().AddDate(0, 0, 365).Unix(), certificate.NotAfter.Unix()) } func TestRun_CreatesServerCertificatesWithProvidedHosts(t *testing.T) { From 1ea918e0965f73c4560764bc4dbcea3ebf7e80c8 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 2 Mar 2023 12:02:57 -0800 Subject: [PATCH 088/592] disable go mod scanning (#1974) --- .release/security-scan.hcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.release/security-scan.hcl b/.release/security-scan.hcl index 516ae4f5e6..42576d29b2 100644 --- a/.release/security-scan.hcl +++ b/.release/security-scan.hcl @@ -6,7 +6,7 @@ container { binary { secrets = true - go_modules = true + go_modules = false osv = true oss_index = false nvd = false From c0e5c691082126160ce4b41bd55d49f0af8568c8 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 3 Mar 2023 13:02:59 -0800 Subject: [PATCH 089/592] Mw/add backport checker (#1982) * fix grammar in changelog checker * add backport checker --- .github/workflows/backport-checker.yml | 32 +++++++++++++++++++++++++ .github/workflows/changelog-checker.yml | 4 ++-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/backport-checker.yml diff --git a/.github/workflows/backport-checker.yml b/.github/workflows/backport-checker.yml new file mode 100644 index 0000000000..5bcac5a38e --- /dev/null +++ b/.github/workflows/backport-checker.yml @@ -0,0 +1,32 @@ +# This workflow checks that there is either a 'pr/no-backport' label applied to a PR +# or there is a backport/.txt file associated with a PR for a backport label + +name: Backport Checker + +on: + pull_request: + types: [opened, synchronize, labeled] + # Runs on PRs to main and all release branches + branches: + - main + - release/* + +jobs: + # checks that a backport label is present for a PR + backport-check: + # If there's a `pr/no-backport` label we ignore this check. Also, we ignore PRs created by the bot assigned to `backport-assistant` + if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/no-backport') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )" + runs-on: ubuntu-latest + + steps: + - name: Check for Backport Label + run: | + labels="${{join(github.event.pull_request.labels.*.name, ', ') }}" + if [[ "$labels" =~ .*"backport/".* ]]; then + echo "Found backport label!" + exit 0 + fi + # Fail status check when no backport label was found on the PR + echo "Did not find a backport label matching the pattern 'backport/*' and the 'pr/no-backport' label was not applied. Reference - https://github.com/hashicorp/consul-k8s/pull/1982" + exit 1 + diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index bb13255d29..ae2e88170b 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -14,7 +14,7 @@ on: jobs: # checks that a .changelog entry is present for a PR changelog-check: - # If there a `pr/no-changelog` label we ignore this check. Also, we ignore PRs created by the bot assigned to `backport-assistant` + # If there's a `pr/no-changelog` label we ignore this check. Also, we ignore PRs created by the bot assigned to `backport-assistant` if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/no-changelog') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )" runs-on: ubuntu-latest @@ -39,7 +39,7 @@ jobs: # If we do not find a file in .changelog/, we fail the check if [ -z "$changelog_files" ]; then # Fail status check when no .changelog entry was found on the PR - echo "Did not find a .changelog entry ${enforce_matching_pull_request_number}and the 'pr/no-changelog' label was not applied. Reference - https://github.com/hashicorp/consul/pull/8387" + echo "Did not find a .changelog entry ${enforce_matching_pull_request_number}and the 'pr/no-changelog' label was not applied. Reference - https://github.com/hashicorp/consul-k8s/pull/1947" exit 1 else echo "Found .changelog entry in PR!" From 7079fc8ad02f48bb7ca571233546e26864c76d1e Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Mon, 6 Mar 2023 13:11:11 -0600 Subject: [PATCH 090/592] Automatic ACL bootstrap with Vault secrets backend (#1920) Support automatic ACL bootstrapping with the Vault secrets backend With the Vault secrets backend, server-acl-init now: * Runs the Vault agent as a sidecar * Bootstraps ACLs if the Vault bootstrap token is empty or not found, and writes the bootstrap token back to Vault via the Vault agent The Kubernetes backend will write the bootstrap token to the user-provided secret if that secret is empty. The Vault behavior is the same. The Vault backend writes to a default secret name if the secretName and secretKey are not set in the helm chart values. server-acl-init reads the secret directly from k8s or Vault. * Remove -bootstrap-token-file flag from server-acl-init and remove the * Remove the volume/mount for bootstrap token --------- Co-authored-by: Chris Thain <32781396+cthain@users.noreply.github.com> --- .changelog/1920.txt | 3 + acceptance/framework/vault/helpers.go | 14 + acceptance/tests/vault/vault_test.go | 60 ++- .../consul/templates/server-acl-init-job.yaml | 55 ++- .../consul/test/unit/server-acl-init-job.bats | 148 +++--- charts/consul/values.yaml | 14 +- control-plane/go.mod | 29 +- control-plane/go.sum | 64 ++- .../subcommand/server-acl-init/command.go | 150 ++++-- .../server-acl-init/command_ent_test.go | 3 +- .../server-acl-init/command_test.go | 457 ++++++++---------- .../server-acl-init/k8s_secrets_backend.go | 62 +++ .../server-acl-init/secrets_backend.go | 18 + .../subcommand/server-acl-init/servers.go | 54 +-- .../test_fake_secrets_backend.go | 20 + .../server-acl-init/vault_secrets_backend.go | 66 +++ 16 files changed, 751 insertions(+), 466 deletions(-) create mode 100644 .changelog/1920.txt create mode 100644 control-plane/subcommand/server-acl-init/k8s_secrets_backend.go create mode 100644 control-plane/subcommand/server-acl-init/secrets_backend.go create mode 100644 control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go create mode 100644 control-plane/subcommand/server-acl-init/vault_secrets_backend.go diff --git a/.changelog/1920.txt b/.changelog/1920.txt new file mode 100644 index 0000000000..4b1f151fe4 --- /dev/null +++ b/.changelog/1920.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: When the `global.acls.bootstrapToken` field is set and the content of the secret is empty, the bootstrap ACL token is written to that secret after bootstrapping ACLs. This applies to both the Vault and Consul secrets backends. +``` diff --git a/acceptance/framework/vault/helpers.go b/acceptance/framework/vault/helpers.go index 4726e246ae..2280aee8b2 100644 --- a/acceptance/framework/vault/helpers.go +++ b/acceptance/framework/vault/helpers.go @@ -167,6 +167,20 @@ func (config *KV2Secret) SaveSecretAndAddReadPolicy(t *testing.T, vaultClient *v path "%s" { capabilities = ["read"] }`, config.Path) + config.saveSecretAndAddPolicy(t, vaultClient, policy) +} + +// SaveSecretAndAddUpdatePolicy will create an update policy for the PolicyName +// on the KV2Secret and then will save the secret in the KV2 store. +func (config *KV2Secret) SaveSecretAndAddUpdatePolicy(t *testing.T, vaultClient *vapi.Client) { + policy := fmt.Sprintf(` + path "%s" { + capabilities = ["read", "update"] + }`, config.Path) + config.saveSecretAndAddPolicy(t, vaultClient, policy) +} + +func (config *KV2Secret) saveSecretAndAddPolicy(t *testing.T, vaultClient *vapi.Client, policy string) { // Create the Vault Policy for the secret. logger.Log(t, "Creating policy") err := vaultClient.Sys().PutPolicy(config.PolicyName, policy) diff --git a/acceptance/tests/vault/vault_test.go b/acceptance/tests/vault/vault_test.go index cf0c926b22..4d43d8bb5b 100644 --- a/acceptance/tests/vault/vault_test.go +++ b/acceptance/tests/vault/vault_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/portforward" "github.com/hashicorp/consul-k8s/acceptance/framework/vault" + "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-uuid" "github.com/hashicorp/go-version" "github.com/stretchr/testify/require" @@ -31,6 +32,29 @@ const ( // TestVault installs Vault, bootstraps it with secrets, policies, and Kube Auth Method. // It then configures Consul to use vault as the backend and checks that it works. func TestVault(t *testing.T) { + cases := map[string]struct { + autoBootstrap bool + }{ + "manual ACL bootstrap": {}, + "automatic ACL bootstrap": { + autoBootstrap: true, + }, + } + for name, c := range cases { + c := c + t.Run(name, func(t *testing.T) { + testVault(t, c.autoBootstrap) + }) + } +} + +// testVault is the implementation for TestVault: +// +// - testAutoBootstrap = false. Test when ACL bootstrapping has already occurred. +// The test pre-populates a Vault secret with the bootstrap token. +// - testAutoBootstrap = true. Test that server-acl-init automatically ACL bootstraps +// consul and writes the bootstrap token to Vault. +func testVault(t *testing.T, testAutoBootstrap bool) { cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) kubectlOptions := ctx.KubectlOptions(t) @@ -123,16 +147,22 @@ func TestVault(t *testing.T) { licenseSecret.SaveSecretAndAddReadPolicy(t, vaultClient) } - // Bootstrap Token - bootstrapToken, err := uuid.GenerateUUID() - require.NoError(t, err) bootstrapTokenSecret := &vault.KV2Secret{ Path: "consul/data/secret/bootstrap", Key: "token", - Value: bootstrapToken, + Value: "", PolicyName: "bootstrap", } - bootstrapTokenSecret.SaveSecretAndAddReadPolicy(t, vaultClient) + if testAutoBootstrap { + bootstrapTokenSecret.SaveSecretAndAddUpdatePolicy(t, vaultClient) + } else { + id, err := uuid.GenerateUUID() + require.NoError(t, err) + bootstrapTokenSecret.Value = id + bootstrapTokenSecret.SaveSecretAndAddReadPolicy(t, vaultClient) + } + + bootstrapToken := bootstrapTokenSecret.Value // ------------------------- // Additional Auth Roles @@ -265,6 +295,26 @@ func TestVault(t *testing.T) { logger.Logf(t, "Wait %d seconds for certificates to rotate....", expirationInSeconds) time.Sleep(time.Duration(expirationInSeconds) * time.Second) + if testAutoBootstrap { + logger.Logf(t, "Validating the ACL bootstrap token was stored in Vault.") + timer := &retry.Timer{Timeout: 10 * time.Second, Wait: 1 * time.Second} + retry.RunWith(timer, t, func(r *retry.R) { + secret, err := vaultClient.Logical().Read("consul/data/secret/bootstrap") + require.NoError(r, err) + + data, ok := secret.Data["data"].(map[string]interface{}) + require.True(r, ok) + require.NotNil(r, data) + + tok, ok := data["token"].(string) + require.True(r, ok) + require.NotEmpty(r, tok) + + // Set bootstrapToken for subsequent validations. + bootstrapToken = tok + }) + } + // Validate that the gossip encryption key is set correctly. logger.Log(t, "Validating the gossip key has been set correctly.") consulCluster.ACLToken = bootstrapToken diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index 440ab8bee0..e62db41ec2 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -47,14 +47,23 @@ spec: annotations: "consul.hashicorp.com/connect-inject": "false" {{- if .Values.global.secretsBackend.vault.enabled }} - "vault.hashicorp.com/agent-pre-populate-only": "true" + + {{- /* Run the Vault agent as both an init container and sidecar. + The Vault agent sidecar is needed when server-acl-init bootstraps ACLs + and writes the bootstrap token back to Vault. + * agent-pre-populate: true - Run the Vault agent init container. + * agent-pre-populate-only: false - Also, run the Vault agent sidecar. + * agent-cache-enable: true - Enable the Agent cache listener. + * agent-cache-listener-port: 8200 - (optional) Listen on 127.0.0.1:8200. + * agent-enable-quit: true - Enable a "quit" endpoint. server-acl-init + tells the Vault agent to stop (without this the Job will not complete). + */}} + "vault.hashicorp.com/agent-pre-populate": "true" + "vault.hashicorp.com/agent-pre-populate-only": "false" + "vault.hashicorp.com/agent-cache-enable": "true" + "vault.hashicorp.com/agent-cache-listener-port": "8200" + "vault.hashicorp.com/agent-enable-quit": "true" "vault.hashicorp.com/agent-inject": "true" - {{- if .Values.global.acls.bootstrapToken.secretName }} - {{- with .Values.global.acls.bootstrapToken }} - "vault.hashicorp.com/agent-inject-secret-bootstrap-token": "{{ .secretName }}" - "vault.hashicorp.com/agent-inject-template-bootstrap-token": {{ template "consul.vaultSecretTemplate" . }} - {{- end }} - {{- end }} {{- if .Values.global.acls.partitionToken.secretName }} {{- with .Values.global.acls.partitionToken }} "vault.hashicorp.com/agent-inject-secret-partition-token": "{{ .secretName }}" @@ -101,14 +110,7 @@ spec: path: tls.crt {{- end }} {{- end }} - {{- if (and .Values.global.acls.bootstrapToken.secretName (not .Values.global.secretsBackend.vault.enabled)) }} - - name: bootstrap-token - secret: - secretName: {{ .Values.global.acls.bootstrapToken.secretName }} - items: - - key: {{ .Values.global.acls.bootstrapToken.secretKey }} - path: bootstrap-token - {{- else if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} + {{- if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} - name: acl-replication-token secret: secretName: {{ .Values.global.acls.replicationToken.secretName }} @@ -129,6 +131,13 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name + # Extract the Vault namespace from the Vault agent annotations. + {{- if .Values.global.secretsBackend.vault.enabled }} + {{- if .Values.global.secretsBackend.vault.agentAnnotations }} + - name: VAULT_NAMESPACE + value: {{ get (tpl .Values.global.secretsBackend.vault.agentAnnotations . | fromYaml) "vault.hashicorp.com/namespace" }} + {{- end }} + {{- end }} {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} {{- if (or .Values.global.tls.enabled .Values.global.acls.replicationToken.secretName .Values.global.acls.bootstrapToken.secretName) }} volumeMounts: @@ -139,11 +148,7 @@ spec: readOnly: true {{- end }} {{- end }} - {{- if (and .Values.global.acls.bootstrapToken.secretName (not .Values.global.secretsBackend.vault.enabled)) }} - - name: bootstrap-token - mountPath: /consul/acl/tokens - readOnly: true - {{- else if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} + {{- if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} - name: acl-replication-token mountPath: /consul/acl/tokens readOnly: true @@ -161,13 +166,15 @@ spec: -resource-prefix=${CONSUL_FULLNAME} \ -k8s-namespace={{ .Release.Namespace }} \ -set-server-tokens={{ $serverEnabled }} \ - - {{- if .Values.global.acls.bootstrapToken.secretName }} {{- if .Values.global.secretsBackend.vault.enabled }} - -bootstrap-token-file=/vault/secrets/bootstrap-token \ + -secrets-backend=vault \ {{- else }} - -bootstrap-token-file=/consul/acl/tokens/bootstrap-token \ + -secrets-backend=kubernetes \ {{- end }} + + {{- if .Values.global.acls.bootstrapToken.secretName }} + -bootstrap-token-secret-name={{ .Values.global.acls.bootstrapToken.secretName }} \ + -bootstrap-token-secret-key={{ .Values.global.acls.bootstrapToken.secretKey }} \ {{- end }} {{- if .Values.syncCatalog.enabled }} diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 63450aa4c2..81064c95eb 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -634,7 +634,19 @@ load _helpers yq -r '.spec.template' | tee /dev/stderr) # Check annotations + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) + [ "${actual}" = "false" ] + + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-enable"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-listener-port"]' | tee /dev/stderr) + [ "${actual}" = "8200" ] + + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-enable-quit"]' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) @@ -643,15 +655,13 @@ load _helpers local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) [ "${actual}" = "aclrole" ] - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-bootstrap-token"]' | tee /dev/stderr) - [ "${actual}" = "foo" ] + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-secrets-backend=vault"))') + [ "${actual}" = "true" ] - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-bootstrap-token"]' | tee /dev/stderr) - local expected=$'{{- with secret \"foo\" -}}\n{{- .Data.data.bar -}}\n{{- end -}}' - [ "${actual}" = "${expected}" ] + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=foo"))') + [ "${actual}" = "true" ] - # Check that the bootstrap token flag is set to the path of the Vault secret. - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-file=/vault/secrets/bootstrap-token"))') + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=bar"))') [ "${actual}" = "true" ] # Check that no (secret) volumes are not attached @@ -682,20 +692,31 @@ load _helpers yq -r '.spec.template' | tee /dev/stderr) # Check annotations - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate"]' | tee /dev/stderr) [ "${actual}" = "true" ] - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) + [ "${actual}" = "false" ] + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-enable"]' | tee /dev/stderr) [ "${actual}" = "true" ] - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-listener-port"]' | tee /dev/stderr) + [ "${actual}" = "8200" ] + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-enable-quit"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) [ "${actual}" = "aclrole" ] - local actual - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr) + + local 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) + + local 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 -}}' ] # Check that the consul-ca-cert volume is not attached @@ -881,12 +902,14 @@ load _helpers local expected=$'{{- with secret \"/vault/replication\" -}}\n{{- .Data.data.token -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] - local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-inject-secret-bootstrap-token"') - [ "${actual}" = "/vault/bootstrap" ] + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-secrets-backend=vault"))') + [ "${actual}" = "true" ] - local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-inject-template-bootstrap-token"') - local expected=$'{{- with secret \"/vault/bootstrap\" -}}\n{{- .Data.data.token -}}\n{{- end -}}' - [ "${actual}" = "${expected}" ] + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=/vault/bootstrap"))') + [ "${actual}" = "true" ] + + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=token"))') + [ "${actual}" = "true" ] # Check that replication token Kubernetes secret volumes and volumeMounts are not attached. local actual=$(echo $object | jq -r '.spec.volumes') @@ -895,12 +918,9 @@ load _helpers local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").volumeMounts') [ "${actual}" = "null" ] - # Check that the replication and bootstrap token flags are set to the path of the Vault secret. + # Replication token file is passed. local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-acl-replication-token-file=/vault/secrets/replication-token"))') [ "${actual}" = "true" ] - - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-file=/vault/secrets/bootstrap-token"))') - [ "${actual}" = "true" ] } #-------------------------------------------------------------------- @@ -953,7 +973,7 @@ load _helpers #-------------------------------------------------------------------- # Vault agent annotations -@test "serverACLInit/Job: no vault agent annotations defined by default" { +@test "serverACLInit/Job: default vault agent annotations" { cd `chart_dir` local actual=$(helm template \ -s templates/server-acl-init-job.yaml \ @@ -967,8 +987,21 @@ load _helpers --set 'global.secretsBackend.vault.consulCARole=carole' \ --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/agent-pre-populate-only") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-inject-secret-bootstrap-token") | del(."vault.hashicorp.com/agent-inject-template-bootstrap-token")' | tee /dev/stderr) - [ "${actual}" = "{}" ] + yq -r .spec.template.metadata.annotations | tee /dev/stderr) + + local expected=$(echo '{ + "consul.hashicorp.com/connect-inject": "false", + "vault.hashicorp.com/agent-inject": "true", + "vault.hashicorp.com/agent-pre-populate": "true", + "vault.hashicorp.com/agent-pre-populate-only": "false", + "vault.hashicorp.com/agent-cache-enable": "true", + "vault.hashicorp.com/agent-cache-listener-port": "8200", + "vault.hashicorp.com/agent-enable-quit": "true", + "vault.hashicorp.com/role": "aclrole" + }' | tee /dev/stderr) + + local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') + [ "$equal" = "true" ] } @test "serverACLInit/Job: vault agent annotations can be set" { @@ -1807,55 +1840,23 @@ load _helpers [[ "$output" =~ "both global.acls.bootstrapToken.secretKey and global.acls.bootstrapToken.secretName must be set if one of them is provided" ]] } -@test "serverACLInit/Job: -bootstrap-token-file is not set by default" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-acl-init-job.yaml \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr) - - # Test the flag is not set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) - [ "${actual}" = "false" ] - - # Test the volume doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "serverACLInit/Job: -bootstrap-token-file is set when acls.bootstrapToken.secretKey and secretName are set" { +@test "serverACLInit/Job: bootstrap token secret is passed when acls.bootstrapToken.secretKey and secretName are set" { cd `chart_dir` local object=$(helm template \ -s templates/server-acl-init-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ --set 'global.acls.bootstrapToken.secretName=name' \ --set 'global.acls.bootstrapToken.secretKey=key' \ - . | tee /dev/stderr) + . | yq .spec.template | tee /dev/stderr) - # Test the -bootstrap-token-file flag is set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file=/consul/acl/tokens/bootstrap-token"))' | tee /dev/stderr) + local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=name"))') [ "${actual}" = "true" ] - # Test the volume exists - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount exists - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) + local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=key"))') [ "${actual}" = "true" ] } -@test "serverACLInit/Job: -bootstrap-token-file is preferred when both acls.bootstrapToken and acls.replicationToken are set" { +@test "serverACLInit/Job: bootstrap token secret is passed when both acl.bootstrapToken and acls.replicationToken are set" { cd `chart_dir` local object=$(helm template \ -s templates/server-acl-init-job.yaml \ @@ -1864,21 +1865,12 @@ load _helpers --set 'global.acls.bootstrapToken.secretKey=key' \ --set 'global.acls.replicationToken.secretName=replication' \ --set 'global.acls.replicationToken.secretKey=token' \ - . | tee /dev/stderr) + . | yq .spec.template | tee /dev/stderr) - # Test the -bootstrap-token-file flag is set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file=/consul/acl/tokens/bootstrap-token"))' | tee /dev/stderr) + local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=name"))') [ "${actual}" = "true" ] - # Test the volume exists - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount exists - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) + local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=key"))') [ "${actual}" = "true" ] } diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index dbc5935211..009e851e1b 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -392,14 +392,20 @@ global: # This requires Consul >= 1.4. manageSystemACLs: false - # A Kubernetes or Vault secret containing the bootstrap token to use for - # creating policies and tokens for all Consul and consul-k8s-control-plane components. - # If set, we will skip ACL bootstrapping of the servers and will only - # initialize ACLs for the Consul clients and consul-k8s-control-plane system components. + # A Kubernetes or Vault secret containing the bootstrap token to use for creating policies and + # tokens for all Consul and consul-k8s-control-plane components. If `secretName` and `secretKey` + # are unset, a default secret name and secret key are used. If the secret is populated, then + # we will skip ACL bootstrapping of the servers and will only initialize ACLs for the Consul + # clients and consul-k8s-control-plane system components. + # If the secret is empty, then we will bootstrap ACLs on the Consul servers, and write the + # bootstrap token to this secret. If ACLs are already bootstrapped on the servers, then the + # secret must contain the bootstrap token. bootstrapToken: # The name of the Kubernetes or Vault secret that holds the bootstrap token. + # If unset, this defaults to `{{ global.name }}-bootstrap-acl-token`. secretName: null # The key within the Kubernetes or Vault secret that holds the bootstrap token. + # If unset, this defaults to `token`. secretKey: null # If true, an ACL token will be created that can be used in secondary diff --git a/control-plane/go.mod b/control-plane/go.mod index 51e4ad39a0..a60079cc53 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -19,14 +19,15 @@ require ( github.com/hashicorp/go-rootcerts v1.0.2 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/serf v0.10.1 + github.com/hashicorp/vault/api v1.8.3 github.com/kr/text v0.2.0 github.com/miekg/dns v1.1.41 github.com/mitchellh/cli v1.1.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/mapstructure v1.4.1 + github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.7.2 go.uber.org/zap v1.19.0 - golang.org/x/text v0.3.7 + golang.org/x/text v0.3.8 golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 gomodules.xyz/jsonpatch/v2 v2.2.0 k8s.io/api v0.22.2 @@ -55,6 +56,7 @@ require ( github.com/aws/aws-sdk-go v1.25.41 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -68,6 +70,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.1.2 // indirect @@ -75,13 +78,22 @@ require ( github.com/googleapis/gnostic v0.5.5 // indirect github.com/gophercloud/gophercloud v0.1.0 // indirect github.com/hashicorp/consul/proto-public v0.1.0 // indirect - github.com/hashicorp/errwrap v1.0.0 // 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.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-plugin v1.4.5 // indirect + github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/mdns v1.0.4 // indirect + github.com/hashicorp/vault/sdk v0.7.0 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f // indirect @@ -90,10 +102,15 @@ require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect + github.com/oklog/run v1.0.0 // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect + github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect @@ -102,6 +119,7 @@ require ( github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -109,7 +127,7 @@ require ( github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible // indirect github.com/vmware/govmomi v0.18.0 // indirect go.opencensus.io v0.23.0 // indirect - go.uber.org/atomic v1.7.0 // indirect + go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect @@ -125,6 +143,7 @@ require ( google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect + gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.22.2 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 1fbb30b7ff..b4ed16e150 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -117,6 +117,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -197,10 +199,12 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -235,6 +239,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -276,6 +281,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -352,20 +359,22 @@ github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3us github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 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= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f h1:7WFMVeuJQp6BkzuTv9O52pzwtEFVUJubKYN+zez8eTI= github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f/go.mod h1:D4eo8/CN92vm9/9UDG+ldX1/fMFa4kpl8qzyTolus8o= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -374,12 +383,24 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-netaddrs v0.1.0 h1:TnlYvODD4C/wO+j7cX1z69kV5gOzI87u3OcUinANaW8= github.com/hashicorp/go-netaddrs v0.1.0/go.mod h1:33+a/emi5R5dqRspOuZKO0E+Tuz5WV1F84eRWALkedA= +github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= +github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -392,6 +413,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -404,8 +426,14 @@ github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o= +github.com/hashicorp/vault/api v1.8.3/go.mod h1:4g/9lj9lmuJQMtT6CmVMHC5FW1yENaVv+Nv4ZfG8fAg= +github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg= +github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -419,6 +447,7 @@ github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGk github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -449,6 +478,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -486,15 +516,23 @@ github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJys github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -516,6 +554,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -542,6 +582,8 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -588,6 +630,9 @@ github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKk github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= @@ -683,8 +728,9 @@ go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4 go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -915,8 +961,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -982,8 +1028,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= 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= @@ -1121,6 +1167,8 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index 698da2a25c..a444f65aaa 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -22,12 +22,11 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-netaddrs" + vaultApi "github.com/hashicorp/vault/api" "github.com/mitchellh/cli" "github.com/mitchellh/mapstructure" "golang.org/x/text/cases" "golang.org/x/text/language" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) @@ -86,8 +85,10 @@ type Command struct { flagEnableInjectK8SNSMirroring bool // Enables mirroring of k8s namespaces into Consul for Connect inject flagInjectK8SNSMirroringPrefix string // Prefix added to Consul namespaces created when mirroring injected services - // Flag to support a custom bootstrap token. - flagBootstrapTokenFile string + // Flags for the secrets backend. + flagSecretsBackend SecretsBackendType + flagBootstrapTokenSecretName string + flagBootstrapTokenSecretKey string flagLogLevel string flagLogJSON bool @@ -98,7 +99,9 @@ type Command struct { // flagFederation indicates if federation has been enabled in the cluster. flagFederation bool - clientset kubernetes.Interface + backend SecretsBackend // for unit testing. + clientset kubernetes.Interface + vaultClient *vaultApi.Client watcher consul.ServerConnectionManager @@ -194,9 +197,14 @@ func (c *Command) init() { c.flags.BoolVar(&c.flagFederation, "federation", false, "Toggle for when federation has been enabled.") - c.flags.StringVar(&c.flagBootstrapTokenFile, "bootstrap-token-file", "", - "Path to file containing ACL token for creating policies and tokens. This token must have 'acl:write' permissions."+ - "When provided, servers will not be bootstrapped and their policies and tokens will not be updated.") + c.flags.StringVar((*string)(&c.flagSecretsBackend), "secrets-backend", "kubernetes", + `The secrets backend to use. Either "vault" or "kubernetes". Defaults to "kubernetes"`) + c.flags.StringVar(&c.flagBootstrapTokenSecretName, "bootstrap-token-secret-name", "", + "The name of the Vault or Kuberenetes secret for the bootstrap token. This token must have `ac::write` permission "+ + "in order to create policies and tokens. If not provided or if the secret is empty, then this command will "+ + "bootstrap ACLs and write the bootstrap token to this secret.") + c.flags.StringVar(&c.flagBootstrapTokenSecretKey, "bootstrap-token-secret-key", "", + "The key within the Vault or Kuberenetes secret containing the bootstrap token.") c.flags.DurationVar(&c.flagTimeout, "timeout", 10*time.Minute, "How long we'll try to bootstrap ACLs for before timing out, e.g. 1ms, 2s, 3m") @@ -231,6 +239,7 @@ func (c *Command) Help() string { // The function will retry its tasks indefinitely until they are complete. func (c *Command) Run(args []string) int { c.once.Do(c.init) + defer c.quitVaultAgent() if err := c.flags.Parse(args); err != nil { return 1 } @@ -264,16 +273,6 @@ func (c *Command) Run(args []string) int { } } - var providedBootstrapToken string - if c.flagBootstrapTokenFile != "" { - var err error - providedBootstrapToken, err = loadTokenFromFile(c.flagBootstrapTokenFile) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - var cancel context.CancelFunc c.ctx, cancel = context.WithTimeout(context.Background(), c.flagTimeout) // The context will only ever be intentionally ended by the timeout. @@ -306,8 +305,12 @@ func (c *Command) Run(args []string) int { c.UI.Error(err.Error()) } - var bootstrapToken string + if err := c.configureSecretsBackend(); err != nil { + c.log.Error(err.Error()) + return 1 + } + var bootstrapToken string if c.flagACLReplicationTokenFile != "" && !c.flagCreateACLReplicationToken { // If ACL replication is enabled, we don't need to ACL bootstrap the servers // since they will be performing replication. @@ -316,21 +319,7 @@ func (c *Command) Run(args []string) int { c.log.Info("ACL replication is enabled so skipping Consul server ACL bootstrapping") bootstrapToken = aclReplicationToken } else { - // Check if we've already been bootstrapped. - var bootTokenSecretName string - if providedBootstrapToken != "" { - c.log.Info("Using provided bootstrap token") - bootstrapToken = providedBootstrapToken - } else { - bootTokenSecretName = c.withPrefix("bootstrap-acl-token") - bootstrapToken, err = c.getBootstrapToken(bootTokenSecretName) - if err != nil { - c.log.Error(fmt.Sprintf("Unexpected error looking for preexisting bootstrap Secret: %s", err)) - return 1 - } - } - - bootstrapToken, err = c.bootstrapServers(ipAddrs, bootstrapToken, bootTokenSecretName) + bootstrapToken, err = c.bootstrapServers(ipAddrs, c.backend) if err != nil { c.log.Error(err.Error()) return 1 @@ -806,24 +795,6 @@ func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, consulC return nil } -// getBootstrapToken returns the existing bootstrap token if there is one by -// reading the Kubernetes Secret with name secretName. -// If there is no bootstrap token yet, then it returns an empty string (not an error). -func (c *Command) getBootstrapToken(secretName string) (string, error) { - secret, err := c.clientset.CoreV1().Secrets(c.flagK8sNamespace).Get(c.ctx, secretName, metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - return "", nil - } - return "", err - } - token, ok := secret.Data[common.ACLTokenSecretKey] - if !ok { - return "", fmt.Errorf("secret %q does not have data key 'token'", secretName) - } - return string(token), nil -} - func (c *Command) configureKubeClient() error { config, err := subcommand.K8SConfig(c.k8s.KubeConfig()) if err != nil { @@ -836,6 +807,55 @@ func (c *Command) configureKubeClient() error { return nil } +// configureSecretsBackend configures either the Kubernetes or Vault +// secrets backend based on flags. +func (c *Command) configureSecretsBackend() error { + if c.backend != nil { + // support a fake backend in unit tests + return nil + } + secretName := c.flagBootstrapTokenSecretName + if secretName == "" { + secretName = c.withPrefix("bootstrap-acl-token") + } + + secretKey := c.flagBootstrapTokenSecretKey + if secretKey == "" { + secretKey = common.ACLTokenSecretKey + } + + switch c.flagSecretsBackend { + case SecretsBackendTypeKubernetes: + c.backend = &KubernetesSecretsBackend{ + ctx: c.ctx, + clientset: c.clientset, + k8sNamespace: c.flagK8sNamespace, + secretName: secretName, + secretKey: secretKey, + } + return nil + case SecretsBackendTypeVault: + cfg := vaultApi.DefaultConfig() + cfg.Address = "" + cfg.AgentAddress = "http://127.0.0.1:8200" + vaultClient, err := vaultApi.NewClient(cfg) + if err != nil { + return fmt.Errorf("Error initializing Vault client: %w", err) + } + + c.vaultClient = vaultClient // must set this for c.quitVaultAgent. + c.backend = &VaultSecretsBackend{ + vaultClient: c.vaultClient, + secretName: secretName, + secretKey: secretKey, + } + return nil + default: + validValues := []SecretsBackendType{SecretsBackendTypeKubernetes, SecretsBackendTypeVault} + return fmt.Errorf("Invalid value for -secrets-backend: %q. Valid values are %v.", c.flagSecretsBackend, validValues) + } +} + // untilSucceeds runs op until it returns a nil error. // If c.cmdTimeout is cancelled it will exit. func (c *Command) untilSucceeds(opName string, op func() error) error { @@ -962,6 +982,10 @@ func (c *Command) validateFlags() error { return errors.New("-consul-api-timeout must be set to a value greater than 0") } + //if c.flagVaultNamespace != "" && c.flagSecretsBackend != SecretsBackendTypeVault { + // return fmt.Errorf("-vault-namespace not supported for -secrets-backend=%q", c.flagSecretsBackend) + //} + return nil } @@ -977,6 +1001,28 @@ func loadTokenFromFile(tokenFile string) (string, error) { return strings.TrimSpace(string(tokenBytes)), nil } +func (c *Command) quitVaultAgent() { + if c.vaultClient == nil { + return + } + + // Tell the Vault agent sidecar to quit. Without this, the Job does not + // complete because the Vault agent does not stop. This retries because it + // does not know exactly when the Vault agent sidecar will start. + err := c.untilSucceeds("tell Vault agent to quit", func() error { + // TODO: RawRequest is deprecated, but there is also not a high level + // method for this in the Vault client. + // nolint:staticcheck // SA1004 ignore + _, err := c.vaultClient.RawRequest( + c.vaultClient.NewRequest("POST", "/agent/v1/quit"), + ) + return err + }) + if err != nil { + c.log.Error("Error telling Vault agent to quit", "error", err) + } +} + const ( consulDefaultNamespace = "default" consulDefaultPartition = "default" diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index e31b787e4b..fa56f3bb3a 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -222,7 +222,6 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { // a non-default partition. func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - tokenFile := common.WriteTempFile(t, bootToken) server := partitionedSetup(t, bootToken, "test") k8s := fake.NewSimpleClientset() setUpK8sServiceAccount(t, k8s, ns) @@ -231,6 +230,7 @@ func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, + backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmd.init() args := []string{ @@ -239,7 +239,6 @@ func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], "-resource-prefix=" + resourcePrefix, "-k8s-namespace=" + ns, - "-bootstrap-token-file", tokenFile, "-allow-dns", "-partition=test", "-enable-namespaces", diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index 3111f58820..ffe60af593 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -60,13 +60,6 @@ func TestRun_FlagValidation(t *testing.T) { "-resource-prefix=prefix"}, ExpErr: "unable to read token from file \"/notexist\": open /notexist: no such file or directory", }, - { - Flags: []string{ - "-bootstrap-token-file=/notexist", - "-addresses=localhost", - "-resource-prefix=prefix"}, - ExpErr: "unable to read token from file \"/notexist\": open /notexist: no such file or directory", - }, { Flags: []string{ "-addresses=localhost", @@ -407,7 +400,6 @@ func TestRun_TokensWithProvidedBootstrapToken(t *testing.T) { for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - tokenFile := common.WriteTempFile(t, bootToken) k8s, testAgent := completeBootstrappedSetup(t, bootToken) setUpK8sServiceAccount(t, k8s, ns) @@ -417,11 +409,11 @@ func TestRun_TokensWithProvidedBootstrapToken(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, + backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmdArgs := append([]string{ "-timeout=1m", "-k8s-namespace", ns, - "-bootstrap-token-file", tokenFile, "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], @@ -915,7 +907,6 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { // Create Consul with ACLs already bootstrapped so that we can // then seed it with our manually created policy. bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - tokenFile := common.WriteTempFile(t, bootToken) k8s, testAgent := completeBootstrappedSetup(t, bootToken) setUpK8sServiceAccount(t, k8s, ns) @@ -938,11 +929,11 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, + backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmdArgs := []string{ "-timeout=1s", "-k8s-namespace", ns, - "-bootstrap-token-file", tokenFile, "-resource-prefix=" + resourcePrefix, "-k8s-namespace=" + ns, "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], @@ -1453,272 +1444,223 @@ func TestRun_ClientPolicyAndBindingRuleRetry(t *testing.T) { // Test if there is an old bootstrap Secret we still try to create and set // server tokens. func TestRun_AlreadyBootstrapped(t *testing.T) { - t.Parallel() - cases := map[string]bool{ - "token saved in k8s secret": true, - "token provided via file": false, - } - - for name, tokenFromK8sSecret := range cases { - t.Run(name, func(t *testing.T) { - k8s := fake.NewSimpleClientset() - - type APICall struct { - Method string - Path string - } - var consulAPICalls []APICall - - // Start the Consul server. - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Record all the API calls made. - consulAPICalls = append(consulAPICalls, APICall{ - Method: r.Method, - Path: r.URL.Path, - }) - switch r.URL.Path { - case "/v1/agent/self": - fmt.Fprintln(w, `{"Config": {"Datacenter": "dc1", "PrimaryDatacenter": "dc1"}}`) - case "/v1/acl/tokens": - fmt.Fprintln(w, `[]`) - case "/v1/acl/token": - fmt.Fprintln(w, `{}`) - case "/v1/acl/policy": - fmt.Fprintln(w, `{}`) - case "/v1/agent/token/acl_agent_token": - fmt.Fprintln(w, `{}`) - case "/v1/acl/auth-method": - fmt.Fprintln(w, `{}`) - case "/v1/acl/role/name/release-name-consul-client-acl-role": - w.WriteHeader(404) - case "/v1/acl/role": - fmt.Fprintln(w, `{}`) - case "/v1/acl/binding-rules": - fmt.Fprintln(w, `[]`) - case "/v1/acl/binding-rule": - fmt.Fprintln(w, `{}`) - default: - w.WriteHeader(500) - fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) - } - })) - defer consulServer.Close() + k8s := fake.NewSimpleClientset() - serverURL, err := url.Parse(consulServer.URL) - require.NoError(t, err) - port, err := strconv.Atoi(serverURL.Port()) - require.NoError(t, err) - setUpK8sServiceAccount(t, k8s, ns) + type APICall struct { + Method string + Path string + } + var consulAPICalls []APICall - cmdArgs := []string{ - "-timeout=500ms", - "-resource-prefix=" + resourcePrefix, - "-k8s-namespace=" + ns, - "-addresses=" + serverURL.Hostname(), - "-http-port=" + serverURL.Port(), - } + // Start the Consul server. + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Record all the API calls made. + consulAPICalls = append(consulAPICalls, APICall{ + Method: r.Method, + Path: r.URL.Path, + }) + switch r.URL.Path { + case "/v1/agent/self": + fmt.Fprintln(w, `{"Config": {"Datacenter": "dc1", "PrimaryDatacenter": "dc1"}}`) + case "/v1/acl/tokens": + fmt.Fprintln(w, `[]`) + case "/v1/acl/token": + fmt.Fprintln(w, `{}`) + case "/v1/acl/policy": + fmt.Fprintln(w, `{}`) + case "/v1/agent/token/acl_agent_token": + fmt.Fprintln(w, `{}`) + case "/v1/acl/auth-method": + fmt.Fprintln(w, `{}`) + case "/v1/acl/role/name/release-name-consul-client-acl-role": + w.WriteHeader(404) + case "/v1/acl/role": + fmt.Fprintln(w, `{}`) + case "/v1/acl/binding-rules": + fmt.Fprintln(w, `[]`) + case "/v1/acl/binding-rule": + fmt.Fprintln(w, `{}`) + default: + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) + } + })) + defer consulServer.Close() - // Create the bootstrap secret. - if tokenFromK8sSecret { - _, err = k8s.CoreV1().Secrets(ns).Create( - context.Background(), - &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourcePrefix + "-bootstrap-acl-token", - Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, - }, - Data: map[string][]byte{ - "token": []byte("old-token"), - }, - }, - metav1.CreateOptions{}) - require.NoError(t, err) - } else { - // Write token to a file. - bootTokenFile, err := os.CreateTemp("", "") - require.NoError(t, err) - defer os.RemoveAll(bootTokenFile.Name()) + serverURL, err := url.Parse(consulServer.URL) + require.NoError(t, err) + port, err := strconv.Atoi(serverURL.Port()) + require.NoError(t, err) + setUpK8sServiceAccount(t, k8s, ns) - _, err = bootTokenFile.WriteString("old-token") - require.NoError(t, err) + cmdArgs := []string{ + "-timeout=500ms", + "-resource-prefix=" + resourcePrefix, + "-k8s-namespace=" + ns, + "-addresses=" + serverURL.Hostname(), + "-http-port=" + serverURL.Port(), + } - require.NoError(t, err) - cmdArgs = append(cmdArgs, "-bootstrap-token-file", bootTokenFile.Name()) - } + // Create the bootstrap secret. + _, err = k8s.CoreV1().Secrets(ns).Create( + context.Background(), + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourcePrefix + "-bootstrap-acl-token", + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{ + "token": []byte("old-token"), + }, + }, + metav1.CreateOptions{}) + require.NoError(t, err) - // Run the command. - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), - } + // Run the command. + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + } - responseCode := cmd.Run(cmdArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + responseCode := cmd.Run(cmdArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - // Test that the Secret is the same. - if tokenFromK8sSecret { - secret, err := k8s.CoreV1().Secrets(ns).Get(context.Background(), resourcePrefix+"-bootstrap-acl-token", metav1.GetOptions{}) - require.NoError(t, err) - require.Contains(t, secret.Data, "token") - require.Equal(t, "old-token", string(secret.Data["token"])) - } + // Test that the Secret is the same. + secret, err := k8s.CoreV1().Secrets(ns).Get(context.Background(), resourcePrefix+"-bootstrap-acl-token", metav1.GetOptions{}) + require.NoError(t, err) + require.Contains(t, secret.Data, "token") + require.Equal(t, "old-token", string(secret.Data["token"])) - // Test that the expected API calls were made. - require.Equal(t, []APICall{ - // We expect calls for updating the server policy, setting server tokens, - // and updating client policy. - { - "PUT", - "/v1/acl/policy", - }, - { - "GET", - "/v1/acl/tokens", - }, - { - "PUT", - "/v1/acl/token", - }, - { - "PUT", - "/v1/agent/token/agent", - }, - { - "PUT", - "/v1/agent/token/acl_agent_token", - }, - { - "GET", - "/v1/agent/self", - }, - { - "PUT", - "/v1/acl/auth-method", - }, - { - "PUT", - "/v1/acl/policy", - }, - { - "GET", - "/v1/acl/role/name/release-name-consul-client-acl-role", - }, - { - "PUT", - "/v1/acl/role", - }, - { - "GET", - "/v1/acl/binding-rules", - }, - { - "PUT", - "/v1/acl/binding-rule", - }, - }, consulAPICalls) - }) - } + // Test that the expected API calls were made. + require.Equal(t, []APICall{ + // We expect calls for updating the server policy, setting server tokens, + // and updating client policy. + { + "PUT", + "/v1/acl/policy", + }, + { + "GET", + "/v1/acl/tokens", + }, + { + "PUT", + "/v1/acl/token", + }, + { + "PUT", + "/v1/agent/token/agent", + }, + { + "PUT", + "/v1/agent/token/acl_agent_token", + }, + { + "GET", + "/v1/agent/self", + }, + { + "PUT", + "/v1/acl/auth-method", + }, + { + "PUT", + "/v1/acl/policy", + }, + { + "GET", + "/v1/acl/role/name/release-name-consul-client-acl-role", + }, + { + "PUT", + "/v1/acl/role", + }, + { + "GET", + "/v1/acl/binding-rules", + }, + { + "PUT", + "/v1/acl/binding-rule", + }, + }, consulAPICalls) } // Test if there is an old bootstrap Secret and the server token exists // that we don't try and recreate the token. func TestRun_AlreadyBootstrapped_ServerTokenExists(t *testing.T) { - t.Parallel() - cases := map[string]bool{ - "token saved in k8s secret": true, - "token provided via file": false, - } - - for name, tokenInK8sSecret := range cases { - t.Run(name, func(t *testing.T) { - - // First set everything up with ACLs bootstrapped. - bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - k8s, testAgent := completeBootstrappedSetup(t, bootToken) - setUpK8sServiceAccount(t, k8s, ns) - - cmdArgs := []string{ - "-timeout=1m", - "-k8s-namespace", ns, - "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], - "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], - "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], - "-resource-prefix", resourcePrefix, - } - - if tokenInK8sSecret { - _, err := k8s.CoreV1().Secrets(ns).Create(context.Background(), &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourcePrefix + "-bootstrap-acl-token", - }, - Data: map[string][]byte{ - "token": []byte(bootToken), - }, - }, metav1.CreateOptions{}) - require.NoError(t, err) - } else { - // Write token to a file. - bootTokenFile, err := os.CreateTemp("", "") - require.NoError(t, err) - defer os.RemoveAll(bootTokenFile.Name()) + // First set everything up with ACLs bootstrapped. + bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + k8s, testAgent := completeBootstrappedSetup(t, bootToken) + setUpK8sServiceAccount(t, k8s, ns) - _, err = bootTokenFile.WriteString(bootToken) - require.NoError(t, err) + cmdArgs := []string{ + "-timeout=1m", + "-k8s-namespace", ns, + "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], + "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], + "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], + "-resource-prefix", resourcePrefix, + } - require.NoError(t, err) - cmdArgs = append(cmdArgs, "-bootstrap-token-file", bootTokenFile.Name()) - } + _, err := k8s.CoreV1().Secrets(ns).Create(context.Background(), &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourcePrefix + "-bootstrap-acl-token", + }, + Data: map[string][]byte{ + "token": []byte(bootToken), + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) - consulClient, err := api.NewClient(&api.Config{ - Address: testAgent.TestServer.HTTPAddr, - Token: bootToken, - }) - require.NoError(t, err) - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - } + consulClient, err := api.NewClient(&api.Config{ + Address: testAgent.TestServer.HTTPAddr, + Token: bootToken, + }) + require.NoError(t, err) + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + } - cmd.init() - // Create the server policy and token _before_ we run the command. - agentPolicyRules, err := cmd.agentRules() - require.NoError(t, err) - policy, _, err := consulClient.ACL().PolicyCreate(&api.ACLPolicy{ - Name: "agent-token", - Description: "Agent Token Policy", - Rules: agentPolicyRules, - }, nil) - require.NoError(t, err) - _, _, err = consulClient.ACL().TokenCreate(&api.ACLToken{ - Description: fmt.Sprintf("Server Token for %s", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0]), - Policies: []*api.ACLTokenPolicyLink{ - { - Name: policy.Name, - }, - }, - }, nil) - require.NoError(t, err) + cmd.init() + // Create the server policy and token _before_ we run the command. + agentPolicyRules, err := cmd.agentRules() + require.NoError(t, err) + policy, _, err := consulClient.ACL().PolicyCreate(&api.ACLPolicy{ + Name: "agent-token", + Description: "Agent Token Policy", + Rules: agentPolicyRules, + }, nil) + require.NoError(t, err) + _, _, err = consulClient.ACL().TokenCreate(&api.ACLToken{ + Description: fmt.Sprintf("Server Token for %s", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0]), + Policies: []*api.ACLTokenPolicyLink{ + { + Name: policy.Name, + }, + }, + }, nil) + require.NoError(t, err) - // Run the command. - responseCode := cmd.Run(cmdArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + // Run the command. + responseCode := cmd.Run(cmdArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - // Check that only one server token exists, i.e. it didn't create an - // extra token. - tokens, _, err := consulClient.ACL().TokenList(nil) - require.NoError(t, err) - count := 0 - for _, token := range tokens { - if len(token.Policies) == 1 && token.Policies[0].Name == policy.Name { - count++ - } - } - require.Equal(t, 1, count) - }) + // Check that only one server token exists, i.e. it didn't create an + // extra token. + tokens, _, err := consulClient.ACL().TokenList(nil) + require.NoError(t, err) + count := 0 + for _, token := range tokens { + if len(token.Policies) == 1 && token.Policies[0].Name == policy.Name { + count++ + } } + require.Equal(t, 1, count) } // Test if -set-server-tokens is false (i.e. servers are disabled), we skip bootstrapping of the servers @@ -1728,7 +1670,6 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { k8s := fake.NewSimpleClientset() bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - tokenFile := common.WriteTempFile(t, bootToken) type APICall struct { Method string @@ -1766,6 +1707,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { UI: ui, clientset: k8s, watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } responseCode := cmd.Run([]string{ "-timeout=500ms", @@ -1773,7 +1715,6 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { "-k8s-namespace=" + ns, "-addresses=" + serverURL.Hostname(), "-http-port=" + serverURL.Port(), - "-bootstrap-token-file=" + tokenFile, "-set-server-tokens=false", "-client=false", // disable client token, so there are fewer calls }) diff --git a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go new file mode 100644 index 0000000000..4e3efcf65b --- /dev/null +++ b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go @@ -0,0 +1,62 @@ +package serveraclinit + +import ( + "context" + "fmt" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + apiv1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const SecretsBackendTypeKubernetes SecretsBackendType = "kubernetes" + +type KubernetesSecretsBackend struct { + ctx context.Context + clientset kubernetes.Interface + k8sNamespace string + secretName string + secretKey string +} + +var _ SecretsBackend = (*KubernetesSecretsBackend)(nil) + +// BootstrapToken returns the existing bootstrap token if there is one by +// reading the Kubernetes Secret. If there is no bootstrap token yet, then +// it returns an empty string (not an error). +func (b *KubernetesSecretsBackend) BootstrapToken() (string, error) { + secret, err := b.clientset.CoreV1().Secrets(b.k8sNamespace).Get(b.ctx, b.secretName, metav1.GetOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return "", nil + } + return "", err + } + token, ok := secret.Data[b.secretKey] + if !ok { + return "", fmt.Errorf("secret %q does not have data key %q", b.secretName, b.secretKey) + } + return string(token), nil + +} + +// WriteBootstrapToken writes the given bootstrap token to the Kubernetes Secret. +func (b *KubernetesSecretsBackend) WriteBootstrapToken(bootstrapToken string) error { + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.secretName, + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{ + b.secretKey: []byte(bootstrapToken), + }, + } + _, err := b.clientset.CoreV1().Secrets(b.k8sNamespace).Create(b.ctx, secret, metav1.CreateOptions{}) + return err +} + +func (b *KubernetesSecretsBackend) BootstrapTokenSecretName() string { + return b.secretName +} diff --git a/control-plane/subcommand/server-acl-init/secrets_backend.go b/control-plane/subcommand/server-acl-init/secrets_backend.go new file mode 100644 index 0000000000..4b4d5c2fc4 --- /dev/null +++ b/control-plane/subcommand/server-acl-init/secrets_backend.go @@ -0,0 +1,18 @@ +package serveraclinit + +type SecretsBackendType string + +type SecretsBackend interface { + // BootstrapToken fetches the bootstrap token from the backend. If the + // token is not found or empty, implementations should return an empty + // string (not an error). + BootstrapToken() (string, error) + + // WriteBootstrapToken writes the given bootstrap token to the backend. + // Implementations of this method do not need to retry the write until + // successful. + WriteBootstrapToken(string) error + + // BootstrapTokenSecretName returns the name of the bootstrap token secret. + BootstrapTokenSecretName() string +} diff --git a/control-plane/subcommand/server-acl-init/servers.go b/control-plane/subcommand/server-acl-init/servers.go index 2dc8f8ab67..01e9a58145 100644 --- a/control-plane/subcommand/server-acl-init/servers.go +++ b/control-plane/subcommand/server-acl-init/servers.go @@ -1,7 +1,6 @@ package serveraclinit import ( - "errors" "fmt" "net" "net/http" @@ -9,29 +8,30 @@ import ( "time" "github.com/hashicorp/consul/api" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // bootstrapServers bootstraps ACLs and ensures each server has an ACL token. -// If bootstrapToken is not empty then ACLs are already bootstrapped. -func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, bootstrapToken, bootTokenSecretName string) (string, error) { +// If a bootstrap is found in the secrets backend, then skip ACL bootstrapping. +// Otherwise, bootstrap ACLs and write the bootstrap token to the secrets backend. +func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, backend SecretsBackend) (string, error) { // Pick the first server address to connect to for bootstrapping and set up connection. firstServerAddr := fmt.Sprintf("%s:%d", serverAddresses[0].IP.String(), c.consulFlags.HTTPPort) - if bootstrapToken == "" { - c.log.Info("No bootstrap token from previous installation found, continuing on to bootstrapping") + bootstrapToken, err := backend.BootstrapToken() + if err != nil { + return "", fmt.Errorf("Unexpected error fetching bootstrap token secret: %w", err) + } - var err error - bootstrapToken, err = c.bootstrapACLs(firstServerAddr, bootTokenSecretName) + if bootstrapToken != "" { + c.log.Info("Found bootstrap token in secrets backend", "secret", backend.BootstrapTokenSecretName()) + } else { + c.log.Info("No bootstrap token found in secrets backend, continuing to ACL bootstrapping", "secret", backend.BootstrapTokenSecretName()) + bootstrapToken, err = c.bootstrapACLs(firstServerAddr, backend) if err != nil { return "", err } - } else { - c.log.Info(fmt.Sprintf("ACLs already bootstrapped - retrieved bootstrap token from Secret %q", bootTokenSecretName)) } // We should only create and set server tokens when servers are running within this cluster. @@ -47,7 +47,7 @@ func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, bootstrapToken, // bootstrapACLs makes the ACL bootstrap API call and writes the bootstrap token // to a kube secret. -func (c *Command) bootstrapACLs(firstServerAddr, bootTokenSecretName string) (string, error) { +func (c *Command) bootstrapACLs(firstServerAddr string, backend SecretsBackend) (string, error) { config := c.consulFlags.ConsulClientConfig().APIClientConfig config.Address = firstServerAddr // Exempting this particular use of the http client from using global.consulAPITimeout @@ -78,9 +78,12 @@ func (c *Command) bootstrapACLs(firstServerAddr, bootTokenSecretName string) (st // Check if already bootstrapped. if strings.Contains(err.Error(), "Unexpected response code: 403") { - unrecoverableErr = errors.New("ACLs already bootstrapped but the ACL token was not written to a Kubernetes secret." + - " We can't proceed because the bootstrap token is lost." + - " You must reset ACLs.") + unrecoverableErr = fmt.Errorf( + "ACLs already bootstrapped but unable to find the bootstrap token in the secrets backend."+ + " We can't proceed without a bootstrap token."+ + " Store a token with `acl:write` permission in the secret %q.", + backend.BootstrapTokenSecretName(), + ) return nil } @@ -98,21 +101,12 @@ func (c *Command) bootstrapACLs(firstServerAddr, bootTokenSecretName string) (st return "", err } - // Write bootstrap token to a Kubernetes secret. - err = c.untilSucceeds(fmt.Sprintf("writing bootstrap Secret %q", bootTokenSecretName), + // Write bootstrap token to the secrets backend. + err = c.untilSucceeds(fmt.Sprintf("writing bootstrap Secret %q", backend.BootstrapTokenSecretName()), func() error { - secret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: bootTokenSecretName, - Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, - }, - Data: map[string][]byte{ - common.ACLTokenSecretKey: []byte(bootstrapToken), - }, - } - _, err := c.clientset.CoreV1().Secrets(c.flagK8sNamespace).Create(c.ctx, secret, metav1.CreateOptions{}) - return err - }) + return backend.WriteBootstrapToken(bootstrapToken) + }, + ) return bootstrapToken, err } diff --git a/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go b/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go new file mode 100644 index 0000000000..5c9a63f1a5 --- /dev/null +++ b/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go @@ -0,0 +1,20 @@ +package serveraclinit + +type FakeSecretsBackend struct { + bootstrapToken string +} + +func (b *FakeSecretsBackend) BootstrapToken() (string, error) { + return b.bootstrapToken, nil +} + +func (*FakeSecretsBackend) BootstrapTokenSecretName() string { + return "fake-bootstrap-token" +} + +func (b *FakeSecretsBackend) WriteBootstrapToken(token string) error { + b.bootstrapToken = token + return nil +} + +var _ SecretsBackend = (*FakeSecretsBackend)(nil) diff --git a/control-plane/subcommand/server-acl-init/vault_secrets_backend.go b/control-plane/subcommand/server-acl-init/vault_secrets_backend.go new file mode 100644 index 0000000000..2706567878 --- /dev/null +++ b/control-plane/subcommand/server-acl-init/vault_secrets_backend.go @@ -0,0 +1,66 @@ +package serveraclinit + +import ( + "fmt" + + "github.com/hashicorp/vault/api" +) + +const SecretsBackendTypeVault SecretsBackendType = "vault" + +type VaultSecretsBackend struct { + vaultClient *api.Client + secretName string + secretKey string +} + +var _ SecretsBackend = (*VaultSecretsBackend)(nil) + +// BootstrapToken returns the bootstrap token stored in Vault. +// If not found this returns an empty string (not an error). +func (b *VaultSecretsBackend) BootstrapToken() (string, error) { + secret, err := b.vaultClient.Logical().Read(b.secretName) + if err != nil { + return "", err + } + if secret == nil || secret.Data == nil { + // secret not found or empty. + return "", nil + } + // Grab secret.Data["data"][secretKey]. + dataRaw, found := secret.Data["data"] + if !found { + return "", nil + } + data, ok := dataRaw.(map[string]interface{}) + if !ok { + return "", nil + } + tokRaw, found := data[b.secretKey] + if !found { + return "", nil + } + if tok, ok := tokRaw.(string); ok { + return tok, nil + } + return "", fmt.Errorf("Unexpected data. To resolve this, "+ + "`vault kv put %[1]s=` if Consul is already ACL bootstrapped. "+ + "If not ACL bootstrapped, `vault kv put %[1]s=\"\"`", b.secretKey, b.secretKey) +} + +// BootstrapTokenSecretName returns the name of the bootstrap token secret. +func (b *VaultSecretsBackend) BootstrapTokenSecretName() string { + return b.secretName +} + +// WriteBootstrapToken writes the bootstrap token to Vault. +func (b *VaultSecretsBackend) WriteBootstrapToken(bootstrapToken string) error { + _, err := b.vaultClient.Logical().Write(b.secretName, + map[string]interface{}{ + "data": map[string]interface{}{ + b.secretKey: bootstrapToken, + }, + }, + ) + return err +} From 86454d27581b16730db0f2d6a85c1b5ddbb2f95d Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 7 Mar 2023 19:43:00 -0800 Subject: [PATCH 091/592] Mw/update to 1.15.1 (#1990) * update charts to point to 1.15.1 * updated consul libraries to the latest --- acceptance/go.mod | 4 ++-- acceptance/go.sum | 10 ++++------ charts/consul/Chart.yaml | 4 ++-- charts/consul/values.yaml | 2 +- cli/go.mod | 6 +++--- cli/go.sum | 12 +++++++----- control-plane/cni/go.mod | 20 ++++++++++---------- control-plane/cni/go.sum | 37 ++++++++++++++++++++++--------------- control-plane/go.mod | 6 ++---- control-plane/go.sum | 12 +++++------- 10 files changed, 58 insertions(+), 55 deletions(-) diff --git a/acceptance/go.mod b/acceptance/go.mod index f81eff4066..66fa6c4b44 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -5,8 +5,8 @@ go 1.20 require ( github.com/gruntwork-io/terratest v0.31.2 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3 - github.com/hashicorp/consul/api v1.16.0 - github.com/hashicorp/consul/sdk v0.12.0 + github.com/hashicorp/consul/api v1.20.0 + github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/vault/api v1.2.0 diff --git a/acceptance/go.sum b/acceptance/go.sum index 2b154c10f7..44969f22ae 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -364,10 +364,10 @@ github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+Xb github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3 h1:4wROIZB8Y4cN/wPILChc2zQ/q00z1VyJitdgyLbITdU= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3/go.mod h1:j9Db/whkzvNC+KP2GftY0HxxleLm9swxXjlu3tYaOAw= -github.com/hashicorp/consul/api v1.16.0 h1:Vf/QVFIwz+PdHR4T4lSwYzLULtbHVq0BheXCUAKP50M= -github.com/hashicorp/consul/api v1.16.0/go.mod h1:GJI1Sif0Wc/iYyqg7EXHJV37IPush6eJTewvYdF9uO8= -github.com/hashicorp/consul/sdk v0.12.0 h1:qsNQToBEs9v5MUWOv/JhiOu4wPeq9VdK7Jcgf7shOrU= -github.com/hashicorp/consul/sdk v0.12.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= +github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= +github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= 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= @@ -803,7 +803,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -902,7 +901,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index 58ca134137..ffd7961302 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: consul version: 1.2.0-dev -appVersion: 1.15.0 +appVersion: 1.15.1 kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -13,7 +13,7 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: hashicorp/consul:1.15.0 + image: hashicorp/consul:1.15.1 - name: consul-k8s-control-plane image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.0-dev - name: consul-dataplane diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 009e851e1b..6fca91244b 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -63,7 +63,7 @@ global: # image: "hashicorp/consul-enterprise:1.10.0-ent" # ``` # @default: hashicorp/consul: - image: "hashicorp/consul:1.15.0" + image: "hashicorp/consul:1.15.1" # 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. diff --git a/cli/go.mod b/cli/go.mod index ae69aa5a64..adc0309466 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -8,7 +8,7 @@ require ( github.com/fatih/color v1.13.0 github.com/google/go-cmp v0.5.8 github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 - github.com/hashicorp/consul/troubleshoot v0.0.0-20230217154305-8dab825c3640 + github.com/hashicorp/consul/troubleshoot v0.1.2 github.com/hashicorp/go-hclog v1.2.1 github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc github.com/kr/text v0.2.0 @@ -99,8 +99,8 @@ require ( github.com/gorilla/mux v1.8.0 // 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.10.1-0.20230209203402-db2bd404bf72 // indirect - github.com/hashicorp/consul/envoyextensions v0.0.0-20230210154717-4f2ce606547b // indirect + github.com/hashicorp/consul/api v1.20.0 // indirect + github.com/hashicorp/consul/envoyextensions v0.1.2 // 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 diff --git a/cli/go.sum b/cli/go.sum index 6582dc76ac..6ba77d4281 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -437,14 +437,16 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWet github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20230209203402-db2bd404bf72 h1:O+z5m5kNtu6NHBMwMsRb1S0P7giqNu5vBBeCzgiAesg= -github.com/hashicorp/consul/api v1.10.1-0.20230209203402-db2bd404bf72/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= -github.com/hashicorp/consul/envoyextensions v0.0.0-20230210154717-4f2ce606547b h1:T+El0UxZP7h2mGL+EPBJejS4gKM/w0KAYOSpTs7hrbY= -github.com/hashicorp/consul/envoyextensions v0.0.0-20230210154717-4f2ce606547b/go.mod h1:oJKG0zAMtq6ZmZNYQyeKh6kIJmi01rZSZDSgnjzZ15w= +github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/consul/envoyextensions v0.1.2 h1:PvPqJ/td3UpOeIKQl5ycFPUy46XZP9awfhAUCduDeI4= +github.com/hashicorp/consul/envoyextensions v0.1.2/go.mod h1:N94DQQkgITiA40zuTQ/UdPOLAAWobgHfVT5u7wxE/aU= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU= +github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= github.com/hashicorp/consul/troubleshoot v0.0.0-20230217154305-8dab825c3640 h1:P81kThpSzUW2oERDMrLsiZE3OuilLo3/EQhtVQW5M+8= github.com/hashicorp/consul/troubleshoot v0.0.0-20230217154305-8dab825c3640/go.mod h1:rskvju2tK8XvHYTAILHjO7lpV1/uViHs3Q3mg9Rkwlg= +github.com/hashicorp/consul/troubleshoot v0.1.2 h1:c6uMTSt/qTMhK3e18nl4xW4j7JcANdQNHOEYhoXH1P8= +github.com/hashicorp/consul/troubleshoot v0.1.2/go.mod h1:q35QOtN7K5kFLPm2SXHBDD+PzsuBekcqTZuuoOTzbWA= 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= diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index c7820438cb..aabc1ceaf0 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -3,9 +3,9 @@ module github.com/hashicorp/consul-k8s/control-plane/cni require ( github.com/containernetworking/cni v1.1.1 github.com/containernetworking/plugins v1.1.1 - github.com/hashicorp/consul/sdk v0.13.0 - github.com/hashicorp/go-hclog v0.16.1 - github.com/stretchr/testify v1.7.1 + github.com/hashicorp/consul/sdk v0.13.1 + github.com/hashicorp/go-hclog v1.2.1 + github.com/stretchr/testify v1.7.2 k8s.io/api v0.22.2 k8s.io/apimachinery v0.22.2 k8s.io/client-go v0.22.2 @@ -14,7 +14,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/fatih/color v1.12.0 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/go-logr/logr v0.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -23,24 +23,24 @@ require ( github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/json-iterator/go v1.1.11 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect + golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.26.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.9.0 // indirect k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index 1188cc5dd4..ec1e322cf3 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -57,8 +57,8 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= -github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -135,8 +135,8 @@ github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmN github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= -github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= +github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -162,14 +162,15 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -217,8 +218,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -286,8 +287,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ 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.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -334,8 +335,12 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= @@ -445,8 +450,9 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -467,8 +473,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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 h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/control-plane/go.mod b/control-plane/go.mod index a60079cc53..b0d67085ec 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,8 +10,8 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.10.1-0.20230203155153-2f149d60ccbf - github.com/hashicorp/consul/sdk v0.13.0 + github.com/hashicorp/consul/api v1.20.0 + github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 github.com/hashicorp/go-multierror v1.1.1 @@ -153,6 +153,4 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) -replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 - go 1.20 diff --git a/control-plane/go.sum b/control-plane/go.sum index b4ed16e150..06dfbb8b10 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -353,12 +353,13 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20230203155153-2f149d60ccbf h1:vvsHghmX3LyNUaDe7onYKHyDiny+ystdHKIEujbNj4Q= -github.com/hashicorp/consul/api v1.10.1-0.20230203155153-2f149d60ccbf/go.mod h1:c1u8FzGHcavbEtRW/p1YditvfMgn4QsKNgz2rnCDF7c= +github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= -github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= -github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= +github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= 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= @@ -369,7 +370,6 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f h1:7WFMVeuJQp6BkzuTv9O52pzwtEFVUJubKYN+zez8eTI= github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f/go.mod h1:D4eo8/CN92vm9/9UDG+ldX1/fMFa4kpl8qzyTolus8o= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -500,7 +500,6 @@ github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZb github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -895,7 +894,6 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From a46657e2725977dc6ee64bb015ebc6288b1cd6cc Mon Sep 17 00:00:00 2001 From: Michael Wilkerson Date: Thu, 9 Mar 2023 11:25:38 -0800 Subject: [PATCH 092/592] added changelog files from release branches --- .changelog/1770.txt | 3 +++ .changelog/1914.txt | 3 +++ .changelog/1934.txt | 3 +++ .changelog/1953.txt | 3 +++ .changelog/1975.txt | 11 +++++++++++ .changelog/1976.txt | 3 +++ 6 files changed, 26 insertions(+) create mode 100644 .changelog/1770.txt create mode 100644 .changelog/1914.txt create mode 100644 .changelog/1934.txt create mode 100644 .changelog/1953.txt create mode 100644 .changelog/1975.txt create mode 100644 .changelog/1976.txt diff --git a/.changelog/1770.txt b/.changelog/1770.txt new file mode 100644 index 0000000000..f8b1c570da --- /dev/null +++ b/.changelog/1770.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. +``` \ No newline at end of file diff --git a/.changelog/1914.txt b/.changelog/1914.txt new file mode 100644 index 0000000000..3179f3b0b3 --- /dev/null +++ b/.changelog/1914.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: fix issue where consul-connect-injector acl token was unintentionally being deleted and not recreated when a container was restarted due to a livenessProbe failure. +``` \ No newline at end of file diff --git a/.changelog/1934.txt b/.changelog/1934.txt new file mode 100644 index 0000000000..a8bc41fd50 --- /dev/null +++ b/.changelog/1934.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: update alpine to 3.17 in the Docker image. +``` \ No newline at end of file diff --git a/.changelog/1953.txt b/.changelog/1953.txt new file mode 100644 index 0000000000..3185330864 --- /dev/null +++ b/.changelog/1953.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.1.0`. +``` \ No newline at end of file diff --git a/.changelog/1975.txt b/.changelog/1975.txt new file mode 100644 index 0000000000..ba26b1ab1e --- /dev/null +++ b/.changelog/1975.txt @@ -0,0 +1,11 @@ +```release-note:security +upgrade to use Go 1.19.6. This resolves vulnerabilities CVE-2022-41724 in crypto/tls and CVE-2022-41723 in net/http. +``` + +```release-note:improvement +cli: update minimum go version for project to 1.19. +``` + +```release-note:improvement +control-plane: update minimum go version for project to 1.19. +``` \ No newline at end of file diff --git a/.changelog/1976.txt b/.changelog/1976.txt new file mode 100644 index 0000000000..65024aa6f9 --- /dev/null +++ b/.changelog/1976.txt @@ -0,0 +1,3 @@ +```release-note:security +upgrade to use Go 1.19.6. This resolves vulnerabilities CVE-2022-41724 in crypto/tls and CVE-2022-41723 in net/http. +``` \ No newline at end of file From d13953b2a2873b38ee3f64d38387a354707e8a5a Mon Sep 17 00:00:00 2001 From: Michael Wilkerson Date: Thu, 9 Mar 2023 11:25:49 -0800 Subject: [PATCH 093/592] copied release notes from release branches --- CHANGELOG.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81edcde7c2..e0df700efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,31 @@ -## UNRELEASED +## 1.0.5 (March 9, 2023) + +SECURITY: + +* upgrade to use Go 1.19.6. This resolves vulnerabilities CVE-2022-41724 in crypto/tls and CVE-2022-41723 in net/http. [[GH-1976](https://github.com/hashicorp/consul-k8s/issues/1976)] + +IMPROVEMENTS: + +* control-plane: server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/issues/1770)] +* control-plane: update alpine to 3.17 in the Docker image. [[GH-1934](https://github.com/hashicorp/consul-k8s/issues/1934)] +* helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.1.0`. [[GH-1953](https://github.com/hashicorp/consul-k8s/issues/1953)] + +## 0.49.5 (March 9, 2023) + +SECURITY: + +* upgrade to use Go 1.19.6. This resolves vulnerabilities CVE-2022-41724 in crypto/tls and CVE-2022-41723 in net/http. [[GH-1975](https://github.com/hashicorp/consul-k8s/issues/1975)] + +IMPROVEMENTS: + +* cli: update minimum go version for project to 1.19. [[GH-1975](https://github.com/hashicorp/consul-k8s/issues/1975)] +* control-plane: server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/issues/1770)] +* control-plane: update alpine to 3.17 in the Docker image. [[GH-1934](https://github.com/hashicorp/consul-k8s/issues/1934)] +* control-plane: update minimum go version for project to 1.19. [[GH-1975](https://github.com/hashicorp/consul-k8s/issues/1975)] + +BUG FIXES: + +* control-plane: fix issue where consul-connect-injector acl token was unintentionally being deleted and not recreated when a container was restarted due to a livenessProbe failure. [[GH-1914](https://github.com/hashicorp/consul-k8s/issues/1914)] ## 1.1.0 (February 27, 2023) From 6fbb20ff22906cd36a158f69eb8ea7b377660707 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Mon, 13 Mar 2023 10:03:38 -0700 Subject: [PATCH 094/592] added more retries for tests (#2006) --- acceptance/framework/consul/cli_cluster.go | 2 +- acceptance/framework/consul/helm_cluster.go | 75 +++++++++++-------- acceptance/framework/helpers/helpers.go | 2 +- .../framework/portforward/port_forward.go | 2 +- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/acceptance/framework/consul/cli_cluster.go b/acceptance/framework/consul/cli_cluster.go index 271f8f2eae..0a3e688951 100644 --- a/acceptance/framework/consul/cli_cluster.go +++ b/acceptance/framework/consul/cli_cluster.go @@ -252,7 +252,7 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool) (*api.Client, c.logger) // Retry creating the port forward since it can fail occasionally. - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 3}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 3 * time.Second, Count: 60}, t, func(r *retry.R) { // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry // because we're using ForwardPortE (not ForwardPort) so the `t` won't // get used to fail the test, just for logging. diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index eab1ba2904..3f4d31173d 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -142,8 +142,11 @@ func (h *HelmCluster) Destroy(t *testing.T) { h.helmOptions.ExtraArgs = map[string][]string{ "--wait": nil, } - err := helm.DeleteE(t, h.helmOptions, h.releaseName, false) - require.NoError(t, err) + + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { + err := helm.DeleteE(t, h.helmOptions, h.releaseName, false) + require.NoError(r, err) + }) // Retry because sometimes certain resources (like PVC) take time to delete // in cloud providers. @@ -324,22 +327,25 @@ func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool) (client *api. if h.ACLToken != "" { config.Token = h.ACLToken } else { - // Get the ACL token. First, attempt to read it from the bootstrap token (this will be true in primary Consul servers). - // If the bootstrap token doesn't exist, it means we are running against a secondary cluster - // and will try to read the replication token from the federation secret. - // In secondary servers, we don't create a bootstrap token since ACLs are only bootstrapped in the primary. - // Instead, we provide a replication token that serves the role of the bootstrap token. - aclSecret, err := h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), h.releaseName+"-consul-bootstrap-acl-token", metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - federationSecret := fmt.Sprintf("%s-consul-federation", h.releaseName) - aclSecret, err = h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), federationSecret, metav1.GetOptions{}) - require.NoError(t, err) - config.Token = string(aclSecret.Data["replicationToken"]) - } else if err == nil { - config.Token = string(aclSecret.Data["token"]) - } else { - require.NoError(t, err) - } + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 600}, t, func(r *retry.R) { + // Get the ACL token. First, attempt to read it from the bootstrap token (this will be true in primary Consul servers). + // If the bootstrap token doesn't exist, it means we are running against a secondary cluster + // and will try to read the replication token from the federation secret. + // In secondary servers, we don't create a bootstrap token since ACLs are only bootstrapped in the primary. + // Instead, we provide a replication token that serves the role of the bootstrap token. + aclSecret, err := h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), h.releaseName+"-consul-bootstrap-acl-token", metav1.GetOptions{}) + if err != nil && errors.IsNotFound(err) { + federationSecret := fmt.Sprintf("%s-consul-federation", h.releaseName) + aclSecret, err = h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), federationSecret, metav1.GetOptions{}) + require.NoError(r, err) + config.Token = string(aclSecret.Data["replicationToken"]) + } else if err == nil { + config.Token = string(aclSecret.Data["token"]) + } else { + require.NoError(r, err) + } + }) + } } @@ -524,21 +530,24 @@ func defaultValues() map[string]string { } func CreateK8sSecret(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace, secretName, secretKey, secret string) { - _, err := client.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) - if errors.IsNotFound(err) { - _, err := client.CoreV1().Secrets(namespace).Create(context.Background(), &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - }, - StringData: map[string]string{ - secretKey: secret, - }, - Type: corev1.SecretTypeOpaque, - }, metav1.CreateOptions{}) - require.NoError(t, err) - } else { - require.NoError(t, err) - } + + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { + _, err := client.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) + if errors.IsNotFound(err) { + _, err := client.CoreV1().Secrets(namespace).Create(context.Background(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + }, + StringData: map[string]string{ + secretKey: secret, + }, + Type: corev1.SecretTypeOpaque, + }, metav1.CreateOptions{}) + require.NoError(r, err) + } else { + require.NoError(r, err) + } + }) helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { _ = client.CoreV1().Secrets(namespace).Delete(context.Background(), secretName, metav1.DeleteOptions{}) diff --git a/acceptance/framework/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index 2c1a6ebf47..5f99a682ae 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -35,7 +35,7 @@ func CheckForPriorInstallations(t *testing.T, client kubernetes.Interface, optio // Check if there's an existing cluster and fail if there is one. // We may need to retry since this is the first command run once the Kube // cluster is created and sometimes the API server returns errors. - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 3}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { var err error // NOTE: It's okay to pass in `t` to RunHelmCommandAndGetOutputE despite being in a retry // because we're using RunHelmCommandAndGetOutputE (not RunHelmCommandAndGetOutput) so the `t` won't diff --git a/acceptance/framework/portforward/port_forward.go b/acceptance/framework/portforward/port_forward.go index 97bf3f7856..30f4aed157 100644 --- a/acceptance/framework/portforward/port_forward.go +++ b/acceptance/framework/portforward/port_forward.go @@ -24,7 +24,7 @@ func CreateTunnelToResourcePort(t *testing.T, resourceName string, remotePort in logger) // Retry creating the port forward since it can fail occasionally. - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 3}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 3 * time.Second, Count: 60}, t, func(r *retry.R) { // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry // because we're using ForwardPortE (not ForwardPort) so the `t` won't // get used to fail the test, just for logging. From e23dbb65afe8226d756de4510016ac4a7a6f469f Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 14 Mar 2023 10:28:07 -0400 Subject: [PATCH 095/592] Add SNI skip for client node configuration --- .../api-gateway-controller-deployment.yaml | 2 +- .../api-gateway-controller-deployment.bats | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index a9f1806cc8..86517d7140 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -112,7 +112,7 @@ spec: {{- end }} - name: CONSUL_HTTP_SSL value: "{{ .Values.global.tls.enabled }}" - {{- if and .Values.externalServers.enabled .Values.externalServers.tlsServerName }} + {{- if and (not .Values.client.enabled) .Values.externalServers.enabled .Values.externalServers.tlsServerName }} - name: CONSUL_TLS_SERVER_NAME value: {{ .Values.externalServers.tlsServerName }} {{- end }} diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index 2dbcb9e0f1..d4db4b373b 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -2,6 +2,16 @@ load _helpers +testOnly() { + if [ "$BATS_TEST_DESCRIPTION" != "$1" ]; then + skip + fi +} + +setup() { + testOnly "apiGateway/Deployment: CONSUL_TLS_SERVER_NAME will not be set for when clients are used" +} + @test "apiGateway/Deployment: disabled by default" { cd `chart_dir` assert_empty helm template \ @@ -1418,6 +1428,24 @@ load _helpers [ "${actual}" = "true" ] } +@test "apiGateway/Deployment: CONSUL_TLS_SERVER_NAME will not be set for when clients are used" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.httpsPort=8501' \ + --set 'externalServers.tlsServerName=hashi' \ + --set 'client.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[] | select (.name == "api-gateway-controller") | .env[] | select(.name == "CONSUL_TLS_SERVER_NAME")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + #-------------------------------------------------------------------- # Admin Partitions From 730ab263c8d791011901208c36b8d467ce382135 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 14 Mar 2023 10:34:56 -0400 Subject: [PATCH 096/592] Add changelog --- .changelog/2013.txt | 3 +++ .../test/unit/api-gateway-controller-deployment.bats | 10 ---------- 2 files changed, 3 insertions(+), 10 deletions(-) create mode 100644 .changelog/2013.txt diff --git a/.changelog/2013.txt b/.changelog/2013.txt new file mode 100644 index 0000000000..056253a5d2 --- /dev/null +++ b/.changelog/2013.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: fix issue where specifying an external server SNI name while using client nodes resulted in a TLS verification error. +``` \ No newline at end of file diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index d4db4b373b..880586ab43 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -2,16 +2,6 @@ load _helpers -testOnly() { - if [ "$BATS_TEST_DESCRIPTION" != "$1" ]; then - skip - fi -} - -setup() { - testOnly "apiGateway/Deployment: CONSUL_TLS_SERVER_NAME will not be set for when clients are used" -} - @test "apiGateway/Deployment: disabled by default" { cd `chart_dir` assert_empty helm template \ From 1483c1775a0dcf56f61023e14329aecf432c2686 Mon Sep 17 00:00:00 2001 From: David Yu Date: Wed, 15 Mar 2023 13:10:25 -0700 Subject: [PATCH 097/592] values.yaml - set default connect inject init cpu resource limits to `null` to increase service registration times (#2008) * Update values.yaml --- .changelog/2008.txt | 3 ++ .../test/unit/connect-inject-deployment.bats | 5 +-- charts/consul/values.yaml | 34 ++++++++++++++----- 3 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 .changelog/2008.txt diff --git a/.changelog/2008.txt b/.changelog/2008.txt new file mode 100644 index 0000000000..ba8bb5fa25 --- /dev/null +++ b/.changelog/2008.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: Set default `limits.cpu` resource setting to `null` for `consul-connect-inject-init` container to speed up registration times when onboarding services onto the mesh during the init container lifecycle. +``` diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 9da24a7568..90166daca2 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -986,10 +986,7 @@ load _helpers local actual=$(echo "$cmd" | yq 'any(contains("-init-container-memory-limit=150Mi"))' | tee /dev/stderr) [ "${actual}" = "true" ] - - local actual=$(echo "$cmd" | - yq 'any(contains("-init-container-cpu-limit=50m"))' | tee /dev/stderr) - [ "${actual}" = "true" ] + } @test "connectInject/Deployment: can set init container resources" { diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 6fca91244b..db63e04804 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2130,15 +2130,22 @@ connectInject: # @type: string annotations: null - # The resource settings for connect inject pods. - # @recurse: false + # The resource settings for connect inject pods. The defaults, are optimized for getting started worklows on developer deployments. The settings should be tweaked for production deployments. # @type: map resources: requests: + # Recommended production default: 500Mi + # @type: string memory: "50Mi" + # Recommended production default: 250m + # @type: string cpu: "50m" limits: + # Recommended production default: 500Mi + # @type: string memory: "50Mi" + # Recommended production default: 250m + # @type: string cpu: "50m" # Sets the failurePolicy for the mutating webhook. By default this will cause pods not part of the consul installation to fail scheduling while the webhook @@ -2306,31 +2313,40 @@ connectInject: # @type: map resources: requests: - # Recommended default: 100Mi + # Recommended production default: 100Mi # @type: string memory: null - # Recommended default: 100m + # Recommended production default: 100m # @type: string cpu: null limits: - # Recommended default: 100Mi + # Recommended production default: 100Mi # @type: string memory: null - # Recommended default: 100m + # Recommended production default: 100m # @type: string cpu: null - # The resource settings for the Connect injected init container. - # @recurse: false + # The resource settings for the Connect injected init container. If null, the resources + # won't be set for the initContainer. The defaults are optimized for developer instances of + # Kubernetes, however they should be tweaked with the recommended defaults as shown below to speed up service registration times. # @type: map initContainer: resources: requests: + # Recommended production default: 150Mi + # @type: string memory: "25Mi" + # Recommended production default: 250m + # @type: string cpu: "50m" limits: + # Recommended production default: 150Mi + # @type: string memory: "150Mi" - cpu: "50m" + # Recommended production default: 500m + # @type: string + cpu: null # [Mesh Gateways](https://developer.hashicorp.com/consul/docs/connect/gateways/mesh-gateway) enable Consul Connect to work across Consul datacenters. meshGateway: From 9de3ff9354cd7f0578554412d037dcad551732e4 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Mon, 20 Mar 2023 14:47:44 -0700 Subject: [PATCH 098/592] Remove client.enabled requirement in docs (#2027) Clients are not required for ingress/terminating gateways. --- charts/consul/values.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index db63e04804..3ccac77c22 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2565,8 +2565,7 @@ meshGateway: # for a specific gateway. # Requirements: consul >= 1.8.0 ingressGateways: - # Enable ingress gateway deployment. Requires `connectInject.enabled=true` - # and `client.enabled=true`. + # Enable ingress gateway deployment. Requires `connectInject.enabled=true`. enabled: false # Defaults sets default values for all gateway fields. With the exception @@ -2732,8 +2731,7 @@ ingressGateways: # for a specific gateway. # Requirements: consul >= 1.8.0 terminatingGateways: - # Enable terminating gateway deployment. Requires `connectInject.enabled=true` - # and `client.enabled=true`. + # Enable terminating gateway deployment. Requires `connectInject.enabled=true`. enabled: false # Defaults sets default values for all gateway fields. With the exception From 7d098bdd2936995be5b7920114726e5b039e6b7d Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Tue, 21 Mar 2023 11:19:39 -0700 Subject: [PATCH 099/592] Remove website prefix from generated docs (#2028) Website has linting that errors when links have the developer.hashicorp.com prefix. --- hack/helm-reference-gen/main.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hack/helm-reference-gen/main.go b/hack/helm-reference-gen/main.go index 5f23143caa..ae9b17610e 100644 --- a/hack/helm-reference-gen/main.go +++ b/hack/helm-reference-gen/main.go @@ -159,11 +159,14 @@ func GenerateDocs(yamlStr string) (string, error) { return "", err } - enterpriseSubst := strings.ReplaceAll(strings.Join(children, "\n\n"), "[Enterprise Only]", "") + docsStr := strings.Join(children, "\n\n") + docsStr = strings.ReplaceAll(docsStr, "[Enterprise Only]", "") + // Remove https://developer.hashicorp.com prefix from links because docs linting requires it. + docsStr = strings.ReplaceAll(docsStr, "https://developer.hashicorp.com/", "/") // Add table of contents. toc := generateTOC(node) - return toc + "\n\n" + enterpriseSubst + "\n", nil + return toc + "\n\n" + docsStr + "\n", nil } // Parse parses yamlStr into a tree of DocNode's. From 63440712a8c06436f4ba52fb3e5d98a693b199ab Mon Sep 17 00:00:00 2001 From: Sarah Alsmiller Date: Fri, 24 Mar 2023 11:12:15 -0500 Subject: [PATCH 100/592] update ACLs, add operator.write permission --- charts/consul/templates/api-gateway-controller-clusterrole.yaml | 1 + control-plane/subcommand/server-acl-init/rules.go | 1 + 2 files changed, 2 insertions(+) diff --git a/charts/consul/templates/api-gateway-controller-clusterrole.yaml b/charts/consul/templates/api-gateway-controller-clusterrole.yaml index eac2bd1f69..2307d6d113 100644 --- a/charts/consul/templates/api-gateway-controller-clusterrole.yaml +++ b/charts/consul/templates/api-gateway-controller-clusterrole.yaml @@ -85,6 +85,7 @@ rules: resources: - namespaces verbs: + - create - get - list - watch diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index ee6ae41e40..39efe3ee6b 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -146,6 +146,7 @@ func (c *Command) apiGatewayControllerRules() (string, error) { apiGatewayRulesTpl := `{{- if .EnablePartitions }} partition "{{ .PartitionName }}" { mesh = "write" + operator = "write" acl = "write" {{- else }} operator = "write" From 3783178a8da65b6d2686acc44bdc0921afb1d683 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Fri, 24 Mar 2023 18:14:27 -0500 Subject: [PATCH 101/592] Update api-gateway-controller-clusterrole.yaml --- charts/consul/templates/api-gateway-controller-clusterrole.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/charts/consul/templates/api-gateway-controller-clusterrole.yaml b/charts/consul/templates/api-gateway-controller-clusterrole.yaml index 2307d6d113..eac2bd1f69 100644 --- a/charts/consul/templates/api-gateway-controller-clusterrole.yaml +++ b/charts/consul/templates/api-gateway-controller-clusterrole.yaml @@ -85,7 +85,6 @@ rules: resources: - namespaces verbs: - - create - get - list - watch From 7ec6ec259c72bb579f2e0a6a973673d7d740573b Mon Sep 17 00:00:00 2001 From: Sarah Alsmiller Date: Sat, 25 Mar 2023 09:28:44 -0500 Subject: [PATCH 102/592] update unit tests --- .../subcommand/server-acl-init/rules.go | 6 +++-- .../subcommand/server-acl-init/rules_test.go | 27 ++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index 39efe3ee6b..84e49801da 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -146,14 +146,16 @@ func (c *Command) apiGatewayControllerRules() (string, error) { apiGatewayRulesTpl := `{{- if .EnablePartitions }} partition "{{ .PartitionName }}" { mesh = "write" - operator = "write" acl = "write" + operator = "write" {{- else }} operator = "write" acl = "write" {{- end }} + {{- if .EnableNamespaces }} namespace_prefix "" { + policy = "write" {{- end }} service_prefix "" { policy = "write" @@ -168,7 +170,7 @@ namespace_prefix "" { {{- if .EnablePartitions }} } {{- end }} - ` +` return c.renderRules(apiGatewayRulesTpl) } diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index 22e63ed0ce..5452712936 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -143,6 +143,7 @@ func TestAPIGatewayControllerRules(t *testing.T) { cases := []struct { Name string EnableNamespaces bool + Partition string Expected string }{ { @@ -165,6 +166,7 @@ acl = "write" operator = "write" acl = "write" namespace_prefix "" { + policy = "write" service_prefix "" { policy = "write" intentions = "write" @@ -172,6 +174,27 @@ namespace_prefix "" { node_prefix "" { policy = "read" } +}`, + }, + { + Name: "Namespaces are enabled, partitions enabled", + EnableNamespaces: true, + Partition: "Default", + Expected: ` +partition "Default" { + mesh = "write" + acl = "write" + operator = "write" +namespace_prefix "" { + policy = "write" + service_prefix "" { + policy = "write" + intentions = "write" + } + node_prefix "" { + policy = "read" + } +} }`, }, } @@ -180,7 +203,9 @@ namespace_prefix "" { t.Run(tt.Name, func(t *testing.T) { cmd := Command{ flagEnableNamespaces: tt.EnableNamespaces, - consulFlags: &flags.ConsulFlags{}, + consulFlags: &flags.ConsulFlags{ + Partition: tt.Partition, + }, } meshGatewayRules, err := cmd.apiGatewayControllerRules() From d7188836e41c58ec7c8037f51853ced83fe56e33 Mon Sep 17 00:00:00 2001 From: Sarah Alsmiller Date: Sat, 25 Mar 2023 13:06:01 -0500 Subject: [PATCH 103/592] added changelog --- .changelog/2029.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/2029.txt diff --git a/.changelog/2029.txt b/.changelog/2029.txt new file mode 100644 index 0000000000..663b7c8142 --- /dev/null +++ b/.changelog/2029.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: fix issue where when adminPartitions and ACLs are enabled, API Gateway Controller is unable to create a new namespace in Consul +``` \ No newline at end of file From 5d0a6adffff2408f2f05f0b35e0004b938df1c92 Mon Sep 17 00:00:00 2001 From: Sarah Alsmiller Date: Sat, 25 Mar 2023 13:06:19 -0500 Subject: [PATCH 104/592] add changelog --- .changelog/2029.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/2029.txt b/.changelog/2029.txt index 663b7c8142..c864419eba 100644 --- a/.changelog/2029.txt +++ b/.changelog/2029.txt @@ -1,3 +1,3 @@ ```release-note:bug -api-gateway: fix issue where when adminPartitions and ACLs are enabled, API Gateway Controller is unable to create a new namespace in Consul +api-gateway: fix ACL issue where when adminPartitions and ACLs are enabled, API Gateway Controller is unable to create a new namespace in Consul ``` \ No newline at end of file From 74291afc9a3ca28533e566eb76dc003ac76ee727 Mon Sep 17 00:00:00 2001 From: Sarah Alsmiller Date: Mon, 27 Mar 2023 16:40:57 -0500 Subject: [PATCH 105/592] removed operator:write after proving namespace:write sufficently solved the bug --- control-plane/subcommand/server-acl-init/rules.go | 1 - control-plane/subcommand/server-acl-init/rules_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index 84e49801da..02831dc4de 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -147,7 +147,6 @@ func (c *Command) apiGatewayControllerRules() (string, error) { partition "{{ .PartitionName }}" { mesh = "write" acl = "write" - operator = "write" {{- else }} operator = "write" acl = "write" diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index 5452712936..622ed341a8 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -184,7 +184,6 @@ namespace_prefix "" { partition "Default" { mesh = "write" acl = "write" - operator = "write" namespace_prefix "" { policy = "write" service_prefix "" { From f76c1dcb31031b86e5b353de87710b739c1a8dbb Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Wed, 15 Mar 2023 08:41:26 -0400 Subject: [PATCH 106/592] add copyright headers to file + config file --- .circleci/config.yml | 3 +++ .copywrite.hcl | 14 ++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 3 +++ .golangci.yml | 3 +++ .release/ci.hcl | 3 +++ .release/release-metadata.hcl | 3 +++ .release/security-scan.hcl | 3 +++ acceptance/framework/cli/cli.go | 3 +++ acceptance/framework/config/config.go | 3 +++ acceptance/framework/config/config_test.go | 3 +++ acceptance/framework/connhelper/connect_helper.go | 3 +++ acceptance/framework/consul/cli_cluster.go | 3 +++ acceptance/framework/consul/cluster.go | 3 +++ acceptance/framework/consul/helm_cluster.go | 3 +++ acceptance/framework/consul/helm_cluster_test.go | 3 +++ .../environment/cni-kind/custom-resources.yaml | 3 +++ .../environment/cni-kind/tigera-operator.yaml | 3 +++ acceptance/framework/environment/environment.go | 3 +++ acceptance/framework/flags/flags.go | 3 +++ acceptance/framework/flags/flags_test.go | 3 +++ acceptance/framework/helpers/helpers.go | 3 +++ acceptance/framework/helpers/helpers_test.go | 3 +++ acceptance/framework/k8s/debug.go | 3 +++ acceptance/framework/k8s/deploy.go | 3 +++ acceptance/framework/k8s/helpers.go | 3 +++ acceptance/framework/k8s/kubectl.go | 3 +++ acceptance/framework/logger/logger.go | 3 +++ acceptance/framework/portforward/port_forward.go | 3 +++ acceptance/framework/suite/suite.go | 3 +++ acceptance/framework/vault/helpers.go | 3 +++ acceptance/framework/vault/vault_cluster.go | 3 +++ acceptance/tests/basic/basic_test.go | 3 +++ acceptance/tests/basic/main_test.go | 3 +++ acceptance/tests/cli/cli_install_test.go | 3 +++ acceptance/tests/cli/cli_upgrade_test.go | 3 +++ acceptance/tests/cli/main_test.go | 3 +++ .../config_entries_namespaces_test.go | 3 +++ .../tests/config-entries/config_entries_test.go | 3 +++ acceptance/tests/config-entries/main_test.go | 3 +++ .../tests/connect/connect_external_servers_test.go | 3 +++ .../connect/connect_inject_namespaces_test.go | 3 +++ acceptance/tests/connect/connect_inject_test.go | 3 +++ acceptance/tests/connect/main_test.go | 3 +++ acceptance/tests/consul-dns/consul_dns_test.go | 3 +++ acceptance/tests/consul-dns/main_test.go | 3 +++ acceptance/tests/example/example_test.go | 3 +++ acceptance/tests/example/main_test.go | 3 +++ .../fixtures/bases/crds-oss/ingressgateway.yaml | 3 +++ .../fixtures/bases/crds-oss/kustomization.yaml | 3 +++ acceptance/tests/fixtures/bases/crds-oss/mesh.yaml | 3 +++ .../fixtures/bases/crds-oss/proxydefaults.yaml | 3 +++ .../fixtures/bases/crds-oss/servicedefaults.yaml | 3 +++ .../fixtures/bases/crds-oss/serviceexports.yaml | 3 +++ .../fixtures/bases/crds-oss/serviceintentions.yaml | 3 +++ .../fixtures/bases/crds-oss/serviceresolver.yaml | 3 +++ .../fixtures/bases/crds-oss/servicerouter.yaml | 3 +++ .../fixtures/bases/crds-oss/servicesplitter.yaml | 3 +++ .../bases/crds-oss/terminatinggateway.yaml | 3 +++ .../exportedservices-default.yaml | 3 +++ .../exportedservices-default/kustomization.yaml | 3 +++ .../exportedservices-secondary.yaml | 3 +++ .../exportedservices-secondary/kustomization.yaml | 3 +++ .../tests/fixtures/bases/intention/intention.yaml | 3 +++ .../fixtures/bases/intention/kustomization.yaml | 3 +++ .../fixtures/bases/mesh-gateway/kustomization.yaml | 3 +++ .../fixtures/bases/mesh-gateway/proxydefaults.yaml | 3 +++ .../fixtures/bases/mesh-peering/kustomization.yaml | 3 +++ .../fixtures/bases/mesh-peering/meshpeering.yaml | 3 +++ .../multiport-app/anyuid-scc-rolebinding.yaml | 3 +++ .../fixtures/bases/multiport-app/deployment.yaml | 3 +++ .../bases/multiport-app/kustomization.yaml | 3 +++ .../multiport-app/privileged-scc-rolebinding.yaml | 3 +++ .../bases/multiport-app/psp-rolebinding.yaml | 3 +++ .../tests/fixtures/bases/multiport-app/secret.yaml | 3 +++ .../fixtures/bases/multiport-app/service.yaml | 3 +++ .../bases/multiport-app/serviceaccount.yaml | 3 +++ .../fixtures/bases/peering/peering-acceptor.yaml | 3 +++ .../fixtures/bases/peering/peering-dialer.yaml | 3 +++ .../static-client/anyuid-scc-rolebinding.yaml | 3 +++ .../fixtures/bases/static-client/deployment.yaml | 3 +++ .../bases/static-client/kustomization.yaml | 3 +++ .../static-client/privileged-scc-rolebinding.yaml | 3 +++ .../bases/static-client/psp-rolebinding.yaml | 3 +++ .../fixtures/bases/static-client/service.yaml | 3 +++ .../bases/static-client/serviceaccount.yaml | 3 +++ .../bases/static-metrics-app/deployment.yaml | 3 +++ .../bases/static-metrics-app/kustomization.yaml | 3 +++ .../fixtures/bases/static-metrics-app/service.yaml | 3 +++ .../anyuid-scc-rolebinding.yaml | 3 +++ .../bases/static-server-https/configmap.yaml | 3 +++ .../bases/static-server-https/deployment.yaml | 3 +++ .../bases/static-server-https/kustomization.yaml | 3 +++ .../privileged-scc-rolebinding.yaml | 3 +++ .../bases/static-server-https/psp-rolebinding.yaml | 3 +++ .../bases/static-server-https/service.yaml | 3 +++ .../bases/static-server-https/serviceaccount.yaml | 3 +++ .../static-server/anyuid-scc-rolebinding.yaml | 3 +++ .../fixtures/bases/static-server/deployment.yaml | 3 +++ .../bases/static-server/kustomization.yaml | 3 +++ .../static-server/privileged-scc-rolebinding.yaml | 3 +++ .../bases/static-server/psp-rolebinding.yaml | 3 +++ .../fixtures/bases/static-server/service.yaml | 3 +++ .../bases/static-server/serviceaccount.yaml | 3 +++ .../default-partition-default/kustomization.yaml | 3 +++ .../default-partition-default/patch.yaml | 3 +++ .../default-partition-ns1/kustomization.yaml | 3 +++ .../default-partition-ns1/patch.yaml | 3 +++ .../secondary-partition-default/kustomization.yaml | 3 +++ .../secondary-partition-default/patch.yaml | 3 +++ .../secondary-partition-ns1/kustomization.yaml | 3 +++ .../secondary-partition-ns1/patch.yaml | 3 +++ .../crd-peers/default-namespace/kustomization.yaml | 3 +++ .../cases/crd-peers/default-namespace/patch.yaml | 3 +++ .../cases/crd-peers/default/kustomization.yaml | 3 +++ .../fixtures/cases/crd-peers/default/patch.yaml | 3 +++ .../non-default-namespace/kustomization.yaml | 3 +++ .../crd-peers/non-default-namespace/patch.yaml | 3 +++ .../fixtures/cases/crds-ent/exportedservices.yaml | 3 +++ .../fixtures/cases/crds-ent/kustomization.yaml | 3 +++ .../kustomization.yaml | 3 +++ .../static-client-inject-multiport/patch.yaml | 3 +++ .../cases/static-client-inject/kustomization.yaml | 3 +++ .../fixtures/cases/static-client-inject/patch.yaml | 3 +++ .../static-client-multi-dc/kustomization.yaml | 3 +++ .../cases/static-client-multi-dc/patch.yaml | 3 +++ .../static-client-namespaces/kustomization.yaml | 3 +++ .../cases/static-client-namespaces/patch.yaml | 3 +++ .../kustomization.yaml | 3 +++ .../default-ns-default-partition/patch.yaml | 3 +++ .../default-ns-partition/kustomization.yaml | 3 +++ .../default-ns-partition/patch.yaml | 3 +++ .../ns-default-partition/kustomization.yaml | 3 +++ .../ns-default-partition/patch.yaml | 3 +++ .../ns-partition/kustomization.yaml | 3 +++ .../ns-partition/patch.yaml | 3 +++ .../default-namespace/kustomization.yaml | 3 +++ .../default-namespace/patch.yaml | 3 +++ .../static-client-peers/default/kustomization.yaml | 3 +++ .../cases/static-client-peers/default/patch.yaml | 3 +++ .../non-default-namespace/kustomization.yaml | 3 +++ .../non-default-namespace/patch.yaml | 3 +++ .../cases/static-client-tproxy/kustomization.yaml | 3 +++ .../fixtures/cases/static-client-tproxy/patch.yaml | 3 +++ .../cases/static-server-inject/kustomization.yaml | 3 +++ .../fixtures/cases/static-server-inject/patch.yaml | 3 +++ .../ingress_gateway_namespaces_test.go | 3 +++ .../tests/ingress-gateway/ingress_gateway_test.go | 3 +++ acceptance/tests/ingress-gateway/main_test.go | 3 +++ acceptance/tests/metrics/main_test.go | 3 +++ acceptance/tests/metrics/metrics_test.go | 3 +++ acceptance/tests/partitions/main_test.go | 3 +++ .../tests/partitions/partitions_connect_test.go | 3 +++ .../tests/partitions/partitions_sync_test.go | 3 +++ acceptance/tests/peering/main_test.go | 3 +++ .../peering/peering_connect_namespaces_test.go | 3 +++ acceptance/tests/peering/peering_connect_test.go | 3 +++ acceptance/tests/snapshot-agent/main_test.go | 3 +++ .../snapshot_agent_k8s_secret_test.go | 3 +++ .../snapshot-agent/snapshot_agent_vault_test.go | 3 +++ acceptance/tests/sync/main_test.go | 3 +++ .../tests/sync/sync_catalog_namespaces_test.go | 3 +++ acceptance/tests/sync/sync_catalog_test.go | 3 +++ acceptance/tests/terminating-gateway/common.go | 3 +++ acceptance/tests/terminating-gateway/main_test.go | 3 +++ .../terminating_gateway_destinations_test.go | 3 +++ .../terminating_gateway_namespaces_test.go | 3 +++ .../terminating_gateway_test.go | 3 +++ acceptance/tests/vault/main_test.go | 3 +++ acceptance/tests/vault/vault_namespaces_test.go | 3 +++ acceptance/tests/vault/vault_partitions_test.go | 3 +++ acceptance/tests/vault/vault_test.go | 3 +++ .../tests/vault/vault_tls_auto_reload_test.go | 3 +++ acceptance/tests/vault/vault_wan_fed_test.go | 3 +++ acceptance/tests/wan-federation/main_test.go | 3 +++ .../tests/wan-federation/wan_federation_test.go | 3 +++ charts/consul/Chart.yaml | 3 +++ charts/consul/addons/gen.sh | 3 +++ charts/consul/addons/values/prometheus.yaml | 3 +++ .../api-gateway-controller-clusterrole.yaml | 3 +++ .../api-gateway-controller-clusterrolebinding.yaml | 3 +++ .../api-gateway-controller-deployment.yaml | 3 +++ .../api-gateway-controller-podsecuritypolicy.yaml | 3 +++ .../templates/api-gateway-controller-service.yaml | 3 +++ .../api-gateway-controller-serviceaccount.yaml | 3 +++ .../consul/templates/api-gateway-gatewayclass.yaml | 3 +++ .../templates/api-gateway-gatewayclassconfig.yaml | 3 +++ .../templates/api-gateway-podsecuritypolicy.yaml | 3 +++ .../consul/templates/auth-method-clusterrole.yaml | 3 +++ .../templates/auth-method-clusterrolebinding.yaml | 3 +++ charts/consul/templates/auth-method-secret.yaml | 3 +++ .../templates/auth-method-serviceaccount.yaml | 3 +++ .../consul/templates/client-config-configmap.yaml | 3 +++ charts/consul/templates/client-daemonset.yaml | 3 +++ .../consul/templates/client-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/client-role.yaml | 3 +++ charts/consul/templates/client-rolebinding.yaml | 3 +++ .../client-securitycontextconstraints.yaml | 3 +++ charts/consul/templates/client-serviceaccount.yaml | 3 +++ charts/consul/templates/cni-clusterrole.yaml | 3 +++ .../consul/templates/cni-clusterrolebinding.yaml | 3 +++ charts/consul/templates/cni-daemonset.yaml | 3 +++ .../templates/cni-networkattachmentdefinition.yaml | 3 +++ charts/consul/templates/cni-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/cni-resourcequota.yaml | 3 +++ .../templates/cni-securitycontextconstraints.yaml | 3 +++ charts/consul/templates/cni-serviceaccount.yaml | 3 +++ .../templates/connect-inject-clusterrole.yaml | 3 +++ .../connect-inject-clusterrolebinding.yaml | 3 +++ .../templates/connect-inject-deployment.yaml | 3 +++ .../connect-inject-leader-election-role.yaml | 3 +++ ...connect-inject-leader-election-rolebinding.yaml | 3 +++ ...onnect-inject-mutatingwebhookconfiguration.yaml | 3 +++ .../connect-inject-podsecuritypolicy.yaml | 3 +++ .../consul/templates/connect-inject-service.yaml | 3 +++ .../templates/connect-inject-serviceaccount.yaml | 3 +++ .../connect-injector-disruptionbudget.yaml | 3 +++ charts/consul/templates/crd-exportedservices.yaml | 3 +++ charts/consul/templates/crd-ingressgateways.yaml | 3 +++ charts/consul/templates/crd-meshes.yaml | 3 +++ charts/consul/templates/crd-peeringacceptors.yaml | 3 +++ charts/consul/templates/crd-peeringdialers.yaml | 3 +++ charts/consul/templates/crd-proxydefaults.yaml | 3 +++ charts/consul/templates/crd-servicedefaults.yaml | 3 +++ charts/consul/templates/crd-serviceintentions.yaml | 3 +++ charts/consul/templates/crd-serviceresolvers.yaml | 3 +++ charts/consul/templates/crd-servicerouters.yaml | 3 +++ charts/consul/templates/crd-servicesplitters.yaml | 3 +++ .../consul/templates/crd-terminatinggateways.yaml | 3 +++ .../templates/create-federation-secret-job.yaml | 3 +++ ...create-federation-secret-podsecuritypolicy.yaml | 3 +++ .../templates/create-federation-secret-role.yaml | 3 +++ .../create-federation-secret-rolebinding.yaml | 3 +++ .../create-federation-secret-serviceaccount.yaml | 3 +++ charts/consul/templates/dns-service.yaml | 3 +++ .../consul/templates/enterprise-license-job.yaml | 3 +++ .../enterprise-license-podsecuritypolicy.yaml | 3 +++ .../consul/templates/enterprise-license-role.yaml | 3 +++ .../templates/enterprise-license-rolebinding.yaml | 3 +++ .../enterprise-license-serviceaccount.yaml | 3 +++ .../consul/templates/expose-servers-service.yaml | 3 +++ .../gossip-encryption-autogenerate-job.yaml | 3 +++ ...-encryption-autogenerate-podsecuritypolicy.yaml | 3 +++ .../gossip-encryption-autogenerate-role.yaml | 3 +++ ...gossip-encryption-autogenerate-rolebinding.yaml | 3 +++ ...sip-encryption-autogenerate-serviceaccount.yaml | 3 +++ .../templates/ingress-gateways-deployment.yaml | 3 +++ .../ingress-gateways-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/ingress-gateways-role.yaml | 3 +++ .../templates/ingress-gateways-rolebinding.yaml | 3 +++ .../consul/templates/ingress-gateways-service.yaml | 3 +++ .../templates/ingress-gateways-serviceaccount.yaml | 3 +++ .../consul/templates/mesh-gateway-clusterrole.yaml | 3 +++ .../templates/mesh-gateway-clusterrolebinding.yaml | 3 +++ .../consul/templates/mesh-gateway-deployment.yaml | 3 +++ .../templates/mesh-gateway-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/mesh-gateway-service.yaml | 3 +++ .../templates/mesh-gateway-serviceaccount.yaml | 3 +++ charts/consul/templates/partition-init-job.yaml | 3 +++ .../partition-init-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/partition-init-role.yaml | 3 +++ .../templates/partition-init-rolebinding.yaml | 3 +++ .../templates/partition-init-serviceaccount.yaml | 3 +++ .../consul/templates/partition-name-configmap.yaml | 3 +++ charts/consul/templates/prometheus.yaml | 3 +++ .../templates/server-acl-init-cleanup-job.yaml | 3 +++ .../server-acl-init-cleanup-podsecuritypolicy.yaml | 3 +++ .../templates/server-acl-init-cleanup-role.yaml | 3 +++ .../server-acl-init-cleanup-rolebinding.yaml | 3 +++ .../server-acl-init-cleanup-serviceaccount.yaml | 3 +++ charts/consul/templates/server-acl-init-job.yaml | 3 +++ .../server-acl-init-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/server-acl-init-role.yaml | 3 +++ .../templates/server-acl-init-rolebinding.yaml | 3 +++ .../templates/server-acl-init-serviceaccount.yaml | 3 +++ .../consul/templates/server-config-configmap.yaml | 3 +++ .../consul/templates/server-disruptionbudget.yaml | 3 +++ .../consul/templates/server-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/server-role.yaml | 3 +++ charts/consul/templates/server-rolebinding.yaml | 3 +++ .../server-securitycontextconstraints.yaml | 3 +++ charts/consul/templates/server-service.yaml | 3 +++ charts/consul/templates/server-serviceaccount.yaml | 3 +++ .../templates/server-snapshot-agent-configmap.yaml | 3 +++ charts/consul/templates/server-statefulset.yaml | 3 +++ .../consul/templates/sync-catalog-clusterrole.yaml | 3 +++ .../templates/sync-catalog-clusterrolebinding.yaml | 3 +++ .../consul/templates/sync-catalog-deployment.yaml | 3 +++ .../templates/sync-catalog-podsecuritypolicy.yaml | 3 +++ .../templates/sync-catalog-serviceaccount.yaml | 3 +++ .../templates/terminating-gateways-deployment.yaml | 3 +++ .../terminating-gateways-podsecuritypolicy.yaml | 3 +++ .../templates/terminating-gateways-role.yaml | 3 +++ .../terminating-gateways-rolebinding.yaml | 3 +++ .../templates/terminating-gateways-service.yaml | 3 +++ .../terminating-gateways-serviceaccount.yaml | 3 +++ charts/consul/templates/tests/test-runner.yaml | 3 +++ charts/consul/templates/tls-init-cleanup-job.yaml | 3 +++ .../tls-init-cleanup-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/tls-init-cleanup-role.yaml | 3 +++ .../templates/tls-init-cleanup-rolebinding.yaml | 3 +++ .../templates/tls-init-cleanup-serviceaccount.yaml | 3 +++ charts/consul/templates/tls-init-job.yaml | 3 +++ .../templates/tls-init-podsecuritypolicy.yaml | 3 +++ charts/consul/templates/tls-init-role.yaml | 3 +++ charts/consul/templates/tls-init-rolebinding.yaml | 3 +++ .../consul/templates/tls-init-serviceaccount.yaml | 3 +++ charts/consul/templates/ui-ingress.yaml | 3 +++ charts/consul/templates/ui-service.yaml | 3 +++ .../webhook-cert-manager-clusterrole.yaml | 3 +++ .../webhook-cert-manager-clusterrolebinding.yaml | 3 +++ .../templates/webhook-cert-manager-configmap.yaml | 3 +++ .../templates/webhook-cert-manager-deployment.yaml | 3 +++ .../webhook-cert-manager-podsecuritypolicy.yaml | 3 +++ .../webhook-cert-manager-serviceaccount.yaml | 3 +++ charts/consul/test/docker/Test.dockerfile | 3 +++ charts/consul/test/terraform/aks/main.tf | 3 +++ charts/consul/test/terraform/aks/outputs.tf | 3 +++ charts/consul/test/terraform/aks/variables.tf | 3 +++ charts/consul/test/terraform/eks/main.tf | 3 +++ charts/consul/test/terraform/eks/outputs.tf | 3 +++ charts/consul/test/terraform/eks/variables.tf | 3 +++ charts/consul/test/terraform/gke/main.tf | 3 +++ charts/consul/test/terraform/gke/outputs.tf | 3 +++ charts/consul/test/terraform/gke/variables.tf | 3 +++ charts/consul/test/terraform/openshift/main.tf | 3 +++ charts/consul/test/terraform/openshift/oc-login.sh | 3 +++ charts/consul/test/terraform/openshift/outputs.tf | 3 +++ .../consul/test/terraform/openshift/variables.tf | 3 +++ charts/consul/test/unit/_helpers.bash | 3 +++ charts/consul/values.yaml | 3 +++ charts/demo/Chart.yaml | 3 +++ charts/demo/templates/frontend.yaml | 3 +++ charts/demo/templates/intentions.yaml | 3 +++ charts/demo/templates/nginx.yaml | 3 +++ charts/demo/templates/payments.yaml | 3 +++ charts/demo/templates/postgres.yaml | 3 +++ charts/demo/templates/products-api.yaml | 3 +++ charts/demo/templates/public-api.yaml | 3 +++ charts/demo/values.yaml | 3 +++ charts/embed_chart.go | 3 +++ cli/cmd/install/install.go | 3 +++ cli/cmd/install/install_test.go | 3 +++ cli/cmd/proxy/command.go | 3 +++ cli/cmd/proxy/list/command.go | 3 +++ cli/cmd/proxy/list/command_test.go | 3 +++ cli/cmd/proxy/loglevel/command.go | 3 +++ cli/cmd/proxy/loglevel/command_test.go | 3 +++ cli/cmd/proxy/read/command.go | 3 +++ cli/cmd/proxy/read/command_test.go | 3 +++ cli/cmd/proxy/read/filters.go | 3 +++ cli/cmd/proxy/read/filters_test.go | 3 +++ cli/cmd/proxy/read/format.go | 3 +++ cli/cmd/proxy/read/format_test.go | 3 +++ cli/cmd/status/status.go | 3 +++ cli/cmd/status/status_test.go | 3 +++ cli/cmd/troubleshoot/command.go | 3 +++ cli/cmd/troubleshoot/proxy/proxy.go | 3 +++ cli/cmd/troubleshoot/proxy/proxy_test.go | 3 +++ cli/cmd/troubleshoot/upstreams/upstreams.go | 3 +++ cli/cmd/troubleshoot/upstreams/upstreams_test.go | 3 +++ cli/cmd/uninstall/uninstall.go | 3 +++ cli/cmd/uninstall/uninstall_test.go | 3 +++ cli/cmd/upgrade/upgrade.go | 3 +++ cli/cmd/upgrade/upgrade_test.go | 3 +++ cli/cmd/version/version.go | 3 +++ cli/commands.go | 3 +++ cli/common/base.go | 3 +++ cli/common/diff.go | 3 +++ cli/common/diff_test.go | 3 +++ cli/common/envoy/http.go | 3 +++ cli/common/envoy/http_test.go | 3 +++ cli/common/envoy/logger_params.go | 3 +++ cli/common/envoy/logger_params_test.go | 3 +++ cli/common/envoy/types.go | 3 +++ cli/common/error.go | 3 +++ cli/common/flag/doc.go | 3 +++ cli/common/flag/flag.go | 3 +++ cli/common/flag/flag_bool.go | 3 +++ cli/common/flag/flag_enum.go | 3 +++ cli/common/flag/flag_enum_single.go | 3 +++ cli/common/flag/flag_float.go | 3 +++ cli/common/flag/flag_int.go | 3 +++ cli/common/flag/flag_string.go | 3 +++ cli/common/flag/flag_string_map.go | 3 +++ cli/common/flag/flag_string_slice.go | 3 +++ cli/common/flag/flag_string_slice_test.go | 3 +++ cli/common/flag/flag_time.go | 3 +++ cli/common/flag/flag_var.go | 3 +++ cli/common/flag/set.go | 3 +++ cli/common/flag/set_test.go | 3 +++ cli/common/portforward.go | 3 +++ cli/common/portforward_test.go | 3 +++ cli/common/terminal/basic.go | 3 +++ cli/common/terminal/doc.go | 3 +++ cli/common/terminal/table.go | 3 +++ cli/common/terminal/ui.go | 3 +++ cli/common/usage.go | 3 +++ cli/common/utils.go | 3 +++ cli/common/utils_test.go | 3 +++ cli/config/config.go | 3 +++ cli/helm/action.go | 3 +++ cli/helm/chart.go | 3 +++ cli/helm/chart_test.go | 3 +++ cli/helm/install.go | 3 +++ cli/helm/install_test.go | 3 +++ cli/helm/mock.go | 3 +++ cli/helm/test_fixtures/consul/Chart.yaml | 3 +++ cli/helm/test_fixtures/consul/templates/foo.yaml | 3 +++ cli/helm/test_fixtures/consul/values.yaml | 3 +++ cli/helm/upgrade.go | 3 +++ cli/helm/upgrade_test.go | 3 +++ cli/helm/values.go | 3 +++ cli/main.go | 3 +++ cli/preset/cloud_preset.go | 3 +++ cli/preset/cloud_preset_test.go | 3 +++ cli/preset/demo.go | 3 +++ cli/preset/preset.go | 3 +++ cli/preset/preset_test.go | 3 +++ cli/preset/quickstart.go | 3 +++ cli/preset/secure.go | 3 +++ cli/release/release.go | 3 +++ cli/release/release_test.go | 3 +++ cli/validation/kubernetes.go | 3 +++ cli/validation/kubernetes_test.go | 3 +++ cli/version/version.go | 3 +++ control-plane/Dockerfile | 3 +++ control-plane/api/common/common.go | 3 +++ control-plane/api/common/configentry.go | 3 +++ control-plane/api/common/configentry_webhook.go | 3 +++ .../api/common/configentry_webhook_test.go | 3 +++ .../api/v1alpha1/exportedservices_types.go | 3 +++ .../api/v1alpha1/exportedservices_types_test.go | 3 +++ .../api/v1alpha1/exportedservices_webhook.go | 3 +++ .../api/v1alpha1/exportedservices_webhook_test.go | 3 +++ control-plane/api/v1alpha1/groupversion_info.go | 3 +++ control-plane/api/v1alpha1/ingressgateway_types.go | 3 +++ .../api/v1alpha1/ingressgateway_types_test.go | 3 +++ .../api/v1alpha1/ingressgateway_webhook.go | 3 +++ control-plane/api/v1alpha1/mesh_types.go | 3 +++ control-plane/api/v1alpha1/mesh_types_test.go | 3 +++ control-plane/api/v1alpha1/mesh_webhook.go | 3 +++ control-plane/api/v1alpha1/mesh_webhook_test.go | 3 +++ .../api/v1alpha1/peeringacceptor_types.go | 3 +++ .../api/v1alpha1/peeringacceptor_types_test.go | 3 +++ .../api/v1alpha1/peeringacceptor_webhook.go | 3 +++ .../api/v1alpha1/peeringacceptor_webhook_test.go | 3 +++ control-plane/api/v1alpha1/peeringdialer_types.go | 3 +++ .../api/v1alpha1/peeringdialer_types_test.go | 3 +++ .../api/v1alpha1/peeringdialer_webhook.go | 3 +++ .../api/v1alpha1/peeringdialer_webhook_test.go | 3 +++ control-plane/api/v1alpha1/proxydefaults_types.go | 3 +++ .../api/v1alpha1/proxydefaults_types_test.go | 3 +++ .../api/v1alpha1/proxydefaults_webhook.go | 3 +++ .../api/v1alpha1/proxydefaults_webhook_test.go | 3 +++ .../api/v1alpha1/servicedefaults_types.go | 3 +++ .../api/v1alpha1/servicedefaults_types_test.go | 3 +++ .../api/v1alpha1/servicedefaults_webhook.go | 3 +++ .../api/v1alpha1/serviceintentions_types.go | 3 +++ .../api/v1alpha1/serviceintentions_types_test.go | 3 +++ .../api/v1alpha1/serviceintentions_webhook.go | 3 +++ .../api/v1alpha1/serviceintentions_webhook_test.go | 3 +++ .../api/v1alpha1/serviceresolver_types.go | 3 +++ .../api/v1alpha1/serviceresolver_types_test.go | 3 +++ .../api/v1alpha1/serviceresolver_webhook.go | 3 +++ control-plane/api/v1alpha1/servicerouter_types.go | 3 +++ .../api/v1alpha1/servicerouter_types_test.go | 3 +++ .../api/v1alpha1/servicerouter_webhook.go | 3 +++ .../api/v1alpha1/servicesplitter_types.go | 3 +++ .../api/v1alpha1/servicesplitter_types_test.go | 3 +++ .../api/v1alpha1/servicesplitter_webhook.go | 3 +++ control-plane/api/v1alpha1/shared_types.go | 3 +++ control-plane/api/v1alpha1/status.go | 3 +++ .../api/v1alpha1/terminatinggateway_types.go | 3 +++ .../api/v1alpha1/terminatinggateway_types_test.go | 3 +++ .../api/v1alpha1/terminatinggateway_webhook.go | 3 +++ control-plane/build-support/functions/00-vars.sh | 3 +++ control-plane/build-support/functions/10-util.sh | 3 +++ control-plane/build-support/functions/20-build.sh | 3 +++ .../build-support/functions/40-publish.sh | 3 +++ control-plane/build-support/scripts/build-local.sh | 3 +++ control-plane/build-support/scripts/functions.sh | 3 +++ .../build-support/scripts/terraformfmtcheck.sh | 3 +++ control-plane/build-support/scripts/version.sh | 3 +++ control-plane/catalog/to-consul/annotation.go | 3 +++ control-plane/catalog/to-consul/resource.go | 3 +++ control-plane/catalog/to-consul/resource_test.go | 3 +++ control-plane/catalog/to-consul/service_id.go | 3 +++ control-plane/catalog/to-consul/syncer.go | 3 +++ control-plane/catalog/to-consul/syncer_ent_test.go | 3 +++ control-plane/catalog/to-consul/syncer_test.go | 3 +++ control-plane/catalog/to-consul/testing.go | 3 +++ control-plane/catalog/to-k8s/sink.go | 3 +++ control-plane/catalog/to-k8s/sink_test.go | 3 +++ control-plane/catalog/to-k8s/source.go | 3 +++ control-plane/catalog/to-k8s/source_test.go | 3 +++ control-plane/catalog/to-k8s/testing.go | 3 +++ control-plane/cni/config/config.go | 3 +++ control-plane/cni/main.go | 3 +++ control-plane/cni/main_test.go | 3 +++ control-plane/commands.go | 3 +++ .../consul.hashicorp.com_exportedservices.yaml | 3 +++ .../consul.hashicorp.com_ingressgateways.yaml | 3 +++ .../crd/bases/consul.hashicorp.com_meshes.yaml | 3 +++ .../consul.hashicorp.com_peeringacceptors.yaml | 3 +++ .../bases/consul.hashicorp.com_peeringdialers.yaml | 3 +++ .../bases/consul.hashicorp.com_proxydefaults.yaml | 3 +++ .../consul.hashicorp.com_servicedefaults.yaml | 3 +++ .../consul.hashicorp.com_serviceintentions.yaml | 3 +++ .../consul.hashicorp.com_serviceresolvers.yaml | 3 +++ .../bases/consul.hashicorp.com_servicerouters.yaml | 3 +++ .../consul.hashicorp.com_servicesplitters.yaml | 3 +++ .../consul.hashicorp.com_terminatinggateways.yaml | 3 +++ control-plane/config/rbac/role.yaml | 3 +++ control-plane/config/webhook/manifests.yaml | 3 +++ control-plane/connect-inject/common/common.go | 3 +++ control-plane/connect-inject/common/common_test.go | 3 +++ .../constants/annotations_and_labels.go | 3 +++ .../connect-inject/constants/constants.go | 3 +++ .../endpoints/consul_client_health_checks.go | 3 +++ .../endpoints/consul_client_health_checks_test.go | 3 +++ .../controllers/endpoints/endpoints_controller.go | 3 +++ .../endpoints/endpoints_controller_ent_test.go | 3 +++ .../endpoints/endpoints_controller_test.go | 3 +++ .../peering/peering_acceptor_controller.go | 3 +++ .../peering/peering_acceptor_controller_test.go | 3 +++ .../peering/peering_dialer_controller.go | 3 +++ .../peering/peering_dialer_controller_test.go | 3 +++ .../metrics/metrics_configuration.go | 3 +++ .../metrics/metrics_configuration_test.go | 3 +++ .../webhook/consul_dataplane_sidecar.go | 3 +++ .../webhook/consul_dataplane_sidecar_test.go | 3 +++ .../connect-inject/webhook/container_env.go | 3 +++ .../connect-inject/webhook/container_env_test.go | 3 +++ .../connect-inject/webhook/container_init.go | 3 +++ .../connect-inject/webhook/container_init_test.go | 3 +++ .../connect-inject/webhook/container_volume.go | 3 +++ control-plane/connect-inject/webhook/dns.go | 3 +++ control-plane/connect-inject/webhook/dns_test.go | 3 +++ .../connect-inject/webhook/health_checks_test.go | 3 +++ .../connect-inject/webhook/heath_checks.go | 3 +++ .../connect-inject/webhook/mesh_webhook.go | 3 +++ .../webhook/mesh_webhook_ent_test.go | 3 +++ .../connect-inject/webhook/mesh_webhook_test.go | 3 +++ .../connect-inject/webhook/redirect_traffic.go | 3 +++ .../webhook/redirect_traffic_test.go | 3 +++ control-plane/consul/consul.go | 3 +++ control-plane/consul/consul_test.go | 3 +++ control-plane/controller/configentry_controller.go | 3 +++ .../controller/configentry_controller_ent_test.go | 3 +++ .../controller/configentry_controller_test.go | 3 +++ .../controller/exportedservices_controller.go | 3 +++ .../exportedservices_controller_ent_test.go | 3 +++ .../controller/ingressgateway_controller.go | 3 +++ control-plane/controller/mesh_controller.go | 3 +++ .../controller/proxydefaults_controller.go | 3 +++ .../controller/servicedefaults_controller.go | 3 +++ .../controller/serviceintentions_controller.go | 3 +++ .../controller/serviceresolver_controller.go | 3 +++ .../controller/servicerouter_controller.go | 3 +++ .../controller/servicesplitter_controller.go | 3 +++ .../controller/terminatinggateway_controller.go | 3 +++ control-plane/hack/lint-api-new-client/main.go | 3 +++ control-plane/helper/cert/notify.go | 3 +++ control-plane/helper/cert/notify_test.go | 3 +++ control-plane/helper/cert/source.go | 3 +++ control-plane/helper/cert/source_gen.go | 3 +++ control-plane/helper/cert/source_gen_test.go | 3 +++ control-plane/helper/cert/tls_util.go | 3 +++ control-plane/helper/coalesce/coalesce.go | 3 +++ control-plane/helper/coalesce/coalesce_test.go | 3 +++ control-plane/helper/controller/controller.go | 3 +++ control-plane/helper/controller/controller_test.go | 3 +++ control-plane/helper/controller/resource.go | 3 +++ control-plane/helper/controller/testing.go | 3 +++ control-plane/helper/go-discover/discover.go | 3 +++ control-plane/helper/go-discover/discover_test.go | 3 +++ .../helper/go-discover/mocks/mock_provider.go | 3 +++ .../mutating_webhook_configuration.go | 3 +++ .../mutating_webhook_configuration_test.go | 3 +++ control-plane/helper/parsetags/parsetags.go | 3 +++ control-plane/helper/parsetags/parsetags_test.go | 3 +++ control-plane/helper/test/test_util.go | 3 +++ control-plane/main.go | 3 +++ control-plane/namespaces/namespaces.go | 3 +++ control-plane/namespaces/namespaces_test.go | 3 +++ control-plane/subcommand/acl-init/command.go | 3 +++ control-plane/subcommand/acl-init/command_test.go | 3 +++ control-plane/subcommand/auth.go | 3 +++ control-plane/subcommand/common/common.go | 3 +++ control-plane/subcommand/common/common_test.go | 3 +++ control-plane/subcommand/common/test_util.go | 3 +++ control-plane/subcommand/connect-init/command.go | 3 +++ .../subcommand/connect-init/command_ent_test.go | 3 +++ .../subcommand/connect-init/command_test.go | 3 +++ control-plane/subcommand/consul-logout/command.go | 3 +++ .../subcommand/consul-logout/command_test.go | 3 +++ .../subcommand/create-federation-secret/command.go | 3 +++ .../create-federation-secret/command_test.go | 3 +++ .../subcommand/delete-completed-job/command.go | 3 +++ .../delete-completed-job/command_test.go | 3 +++ control-plane/subcommand/flags/consul.go | 3 +++ control-plane/subcommand/flags/consul_test.go | 3 +++ control-plane/subcommand/flags/flag_map_value.go | 3 +++ .../subcommand/flags/flag_map_value_test.go | 3 +++ control-plane/subcommand/flags/flag_slice_value.go | 3 +++ .../subcommand/flags/flag_slice_value_test.go | 3 +++ control-plane/subcommand/flags/flags.go | 3 +++ control-plane/subcommand/flags/http.go | 3 +++ control-plane/subcommand/flags/http_test.go | 3 +++ control-plane/subcommand/flags/k8s.go | 3 +++ control-plane/subcommand/flags/mapset.go | 3 +++ control-plane/subcommand/flags/usage.go | 3 +++ control-plane/subcommand/flags/usage_test.go | 3 +++ .../subcommand/get-consul-client-ca/command.go | 3 +++ .../get-consul-client-ca/command_test.go | 3 +++ .../gossip-encryption-autogenerate/command.go | 3 +++ .../gossip-encryption-autogenerate/command_test.go | 3 +++ control-plane/subcommand/inject-connect/command.go | 3 +++ .../subcommand/inject-connect/command_test.go | 3 +++ control-plane/subcommand/install-cni/binary.go | 3 +++ .../subcommand/install-cni/binary_test.go | 3 +++ control-plane/subcommand/install-cni/cniconfig.go | 3 +++ .../subcommand/install-cni/cniconfig_test.go | 3 +++ control-plane/subcommand/install-cni/command.go | 3 +++ .../subcommand/install-cni/command_test.go | 3 +++ control-plane/subcommand/install-cni/kubeconfig.go | 3 +++ .../subcommand/install-cni/kubeconfig_test.go | 3 +++ control-plane/subcommand/partition-init/command.go | 3 +++ .../subcommand/partition-init/command_ent_test.go | 3 +++ .../subcommand/server-acl-init/anonymous_token.go | 3 +++ .../subcommand/server-acl-init/command.go | 3 +++ .../subcommand/server-acl-init/command_ent_test.go | 3 +++ .../subcommand/server-acl-init/command_test.go | 3 +++ .../subcommand/server-acl-init/connect_inject.go | 3 +++ .../server-acl-init/connect_inject_test.go | 3 +++ .../subcommand/server-acl-init/create_or_update.go | 3 +++ .../server-acl-init/create_or_update_test.go | 3 +++ .../server-acl-init/k8s_secrets_backend.go | 3 +++ control-plane/subcommand/server-acl-init/rules.go | 3 +++ .../subcommand/server-acl-init/rules_test.go | 3 +++ .../subcommand/server-acl-init/secrets_backend.go | 3 +++ .../subcommand/server-acl-init/servers.go | 3 +++ .../server-acl-init/test_fake_secrets_backend.go | 3 +++ .../server-acl-init/vault_secrets_backend.go | 3 +++ control-plane/subcommand/sync-catalog/command.go | 3 +++ .../subcommand/sync-catalog/command_ent_test.go | 3 +++ .../subcommand/sync-catalog/command_test.go | 3 +++ control-plane/subcommand/tls-init/command.go | 3 +++ control-plane/subcommand/tls-init/command_test.go | 3 +++ control-plane/subcommand/version/command.go | 3 +++ .../subcommand/webhook-cert-manager/command.go | 3 +++ .../webhook-cert-manager/command_test.go | 3 +++ .../subcommand/webhook-cert-manager/mocks/mocks.go | 3 +++ control-plane/version/version.go | 3 +++ hack/aws-acceptance-test-cleanup/main.go | 3 +++ hack/copy-crds-to-chart/main.go | 3 +++ hack/helm-reference-gen/doc_node.go | 3 +++ hack/helm-reference-gen/fixtures/full-values.yaml | 3 +++ hack/helm-reference-gen/main.go | 3 +++ hack/helm-reference-gen/main_test.go | 3 +++ hack/helm-reference-gen/parse_error.go | 3 +++ 661 files changed, 1994 insertions(+) create mode 100644 .copywrite.hcl diff --git a/.circleci/config.yml b/.circleci/config.yml index 9c84f112cc..e0e59a56d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # Originally from consul-k8s version: 2.1 orbs: diff --git a/.copywrite.hcl b/.copywrite.hcl new file mode 100644 index 0000000000..6a47e1061d --- /dev/null +++ b/.copywrite.hcl @@ -0,0 +1,14 @@ +schema_version = 1 + +project { + license = "MPL-2.0" + copyright_year = 2018 + + # (OPTIONAL) A list of globs that should not have copyright/license headers. + # Supports doublestar glob patterns for more flexibility in defining which + # files or folders should be ignored + header_ignore = [ + # "vendors/**", + # "**autogen**", + ] +} diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index e22e28a48a..eb998e2cd9 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + blank_issues_enabled: false contact_links: - name: Consul Community Support diff --git a/.golangci.yml b/.golangci.yml index 143e0297e4..142f5c2722 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + linters: # enables all defaults + the below, `golangci-lint linters` to see the list of active linters. enable: diff --git a/.release/ci.hcl b/.release/ci.hcl index ffc2fd60d5..5c781045e7 100644 --- a/.release/ci.hcl +++ b/.release/ci.hcl @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + schema = "1" project "consul-k8s" { diff --git a/.release/release-metadata.hcl b/.release/release-metadata.hcl index c053fdac2f..10741e9c36 100644 --- a/.release/release-metadata.hcl +++ b/.release/release-metadata.hcl @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + url_docker_registry_dockerhub = "https://hub.docker.com/r/hashicorp/consul-k8s-control-plane" url_license = "https://github.com/hashicorp/consul-k8s/blob/main/LICENSE" url_project_website = "https://www.consul.io/docs/k8s" diff --git a/.release/security-scan.hcl b/.release/security-scan.hcl index 42576d29b2..692fea1578 100644 --- a/.release/security-scan.hcl +++ b/.release/security-scan.hcl @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + container { dependencies = true alpine_secdb = true diff --git a/acceptance/framework/cli/cli.go b/acceptance/framework/cli/cli.go index 5297382d94..11a158269f 100644 --- a/acceptance/framework/cli/cli.go +++ b/acceptance/framework/cli/cli.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cli import ( diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index 8e131c9c59..310955e8f8 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package config import ( diff --git a/acceptance/framework/config/config_test.go b/acceptance/framework/config/config_test.go index 7733d815db..f5992cdd99 100644 --- a/acceptance/framework/config/config_test.go +++ b/acceptance/framework/config/config_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package config import ( diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index c5d677ba6f..8a7f4d3d53 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connhelper import ( diff --git a/acceptance/framework/consul/cli_cluster.go b/acceptance/framework/consul/cli_cluster.go index 0a3e688951..a9b1dbe74f 100644 --- a/acceptance/framework/consul/cli_cluster.go +++ b/acceptance/framework/consul/cli_cluster.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/acceptance/framework/consul/cluster.go b/acceptance/framework/consul/cluster.go index 41dbec86f8..734f07f36e 100644 --- a/acceptance/framework/consul/cluster.go +++ b/acceptance/framework/consul/cluster.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 3f4d31173d..f34aab6455 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/acceptance/framework/consul/helm_cluster_test.go b/acceptance/framework/consul/helm_cluster_test.go index af70812f9a..435d9f4d7f 100644 --- a/acceptance/framework/consul/helm_cluster_test.go +++ b/acceptance/framework/consul/helm_cluster_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/acceptance/framework/environment/cni-kind/custom-resources.yaml b/acceptance/framework/environment/cni-kind/custom-resources.yaml index 1d2e08da56..9a87195041 100644 --- a/acceptance/framework/environment/cni-kind/custom-resources.yaml +++ b/acceptance/framework/environment/cni-kind/custom-resources.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # This section includes base Calico installation configuration. # For more information, see: https://projectcalico.docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Installation apiVersion: operator.tigera.io/v1 diff --git a/acceptance/framework/environment/cni-kind/tigera-operator.yaml b/acceptance/framework/environment/cni-kind/tigera-operator.yaml index f13e65ceb0..8114b6f596 100644 --- a/acceptance/framework/environment/cni-kind/tigera-operator.yaml +++ b/acceptance/framework/environment/cni-kind/tigera-operator.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: Namespace metadata: diff --git a/acceptance/framework/environment/environment.go b/acceptance/framework/environment/environment.go index 15121b97e3..2c86b43707 100644 --- a/acceptance/framework/environment/environment.go +++ b/acceptance/framework/environment/environment.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package environment import ( diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index 5d90d74f9e..b8c315044b 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/acceptance/framework/flags/flags_test.go b/acceptance/framework/flags/flags_test.go index f66317a048..7546ae911c 100644 --- a/acceptance/framework/flags/flags_test.go +++ b/acceptance/framework/flags/flags_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/acceptance/framework/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index 5f99a682ae..544079b8fb 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package helpers import ( diff --git a/acceptance/framework/helpers/helpers_test.go b/acceptance/framework/helpers/helpers_test.go index c1b4a916e2..2a994d7f9f 100644 --- a/acceptance/framework/helpers/helpers_test.go +++ b/acceptance/framework/helpers/helpers_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package helpers import ( diff --git a/acceptance/framework/k8s/debug.go b/acceptance/framework/k8s/debug.go index 5bf588f959..773769fced 100644 --- a/acceptance/framework/k8s/debug.go +++ b/acceptance/framework/k8s/debug.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package k8s import ( diff --git a/acceptance/framework/k8s/deploy.go b/acceptance/framework/k8s/deploy.go index 869ebdd804..771aab3fe1 100644 --- a/acceptance/framework/k8s/deploy.go +++ b/acceptance/framework/k8s/deploy.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package k8s import ( diff --git a/acceptance/framework/k8s/helpers.go b/acceptance/framework/k8s/helpers.go index 8d3da64612..efac5b35db 100644 --- a/acceptance/framework/k8s/helpers.go +++ b/acceptance/framework/k8s/helpers.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package k8s import ( diff --git a/acceptance/framework/k8s/kubectl.go b/acceptance/framework/k8s/kubectl.go index e766395f47..4416c7e6a9 100644 --- a/acceptance/framework/k8s/kubectl.go +++ b/acceptance/framework/k8s/kubectl.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package k8s import ( diff --git a/acceptance/framework/logger/logger.go b/acceptance/framework/logger/logger.go index c186e77436..26777c4e22 100644 --- a/acceptance/framework/logger/logger.go +++ b/acceptance/framework/logger/logger.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package logger import ( diff --git a/acceptance/framework/portforward/port_forward.go b/acceptance/framework/portforward/port_forward.go index 30f4aed157..0a48e8d300 100644 --- a/acceptance/framework/portforward/port_forward.go +++ b/acceptance/framework/portforward/port_forward.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package portforward import ( diff --git a/acceptance/framework/suite/suite.go b/acceptance/framework/suite/suite.go index 67dff4f587..5d93d2c23f 100644 --- a/acceptance/framework/suite/suite.go +++ b/acceptance/framework/suite/suite.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package suite import ( diff --git a/acceptance/framework/vault/helpers.go b/acceptance/framework/vault/helpers.go index 2280aee8b2..d086e10391 100644 --- a/acceptance/framework/vault/helpers.go +++ b/acceptance/framework/vault/helpers.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package vault import ( diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index c8105fa806..d09b8ed75e 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package vault import ( diff --git a/acceptance/tests/basic/basic_test.go b/acceptance/tests/basic/basic_test.go index 91047711a1..4727b53495 100644 --- a/acceptance/tests/basic/basic_test.go +++ b/acceptance/tests/basic/basic_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package basic import ( diff --git a/acceptance/tests/basic/main_test.go b/acceptance/tests/basic/main_test.go index fa858ee4ae..0e400ef870 100644 --- a/acceptance/tests/basic/main_test.go +++ b/acceptance/tests/basic/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package basic import ( diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index b6c52e9fc4..009d3140fc 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cli import ( diff --git a/acceptance/tests/cli/cli_upgrade_test.go b/acceptance/tests/cli/cli_upgrade_test.go index 6fcf82f738..8c8970d88a 100644 --- a/acceptance/tests/cli/cli_upgrade_test.go +++ b/acceptance/tests/cli/cli_upgrade_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cli import ( diff --git a/acceptance/tests/cli/main_test.go b/acceptance/tests/cli/main_test.go index 85cef25abe..2e66d89543 100644 --- a/acceptance/tests/cli/main_test.go +++ b/acceptance/tests/cli/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cli import ( diff --git a/acceptance/tests/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index a2580069a8..05ea9e5235 100644 --- a/acceptance/tests/config-entries/config_entries_namespaces_test.go +++ b/acceptance/tests/config-entries/config_entries_namespaces_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package config_entries import ( diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index 035edf6a8f..d3c0d410a0 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package config_entries import ( diff --git a/acceptance/tests/config-entries/main_test.go b/acceptance/tests/config-entries/main_test.go index 64034f7663..805045dcef 100644 --- a/acceptance/tests/config-entries/main_test.go +++ b/acceptance/tests/config-entries/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package config_entries import ( diff --git a/acceptance/tests/connect/connect_external_servers_test.go b/acceptance/tests/connect/connect_external_servers_test.go index 5132b66e7a..a7b0f656bf 100644 --- a/acceptance/tests/connect/connect_external_servers_test.go +++ b/acceptance/tests/connect/connect_external_servers_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connect import ( diff --git a/acceptance/tests/connect/connect_inject_namespaces_test.go b/acceptance/tests/connect/connect_inject_namespaces_test.go index db48465bda..f848594cd2 100644 --- a/acceptance/tests/connect/connect_inject_namespaces_test.go +++ b/acceptance/tests/connect/connect_inject_namespaces_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connect import ( diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index 2e75846884..dd3865ae46 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connect import ( diff --git a/acceptance/tests/connect/main_test.go b/acceptance/tests/connect/main_test.go index a2b5925bed..e44c75e519 100644 --- a/acceptance/tests/connect/main_test.go +++ b/acceptance/tests/connect/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connect import ( diff --git a/acceptance/tests/consul-dns/consul_dns_test.go b/acceptance/tests/consul-dns/consul_dns_test.go index 47cfb4af07..15b7d580be 100644 --- a/acceptance/tests/consul-dns/consul_dns_test.go +++ b/acceptance/tests/consul-dns/consul_dns_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consuldns import ( diff --git a/acceptance/tests/consul-dns/main_test.go b/acceptance/tests/consul-dns/main_test.go index 848f30ad8f..1a17337d0a 100644 --- a/acceptance/tests/consul-dns/main_test.go +++ b/acceptance/tests/consul-dns/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consuldns import ( diff --git a/acceptance/tests/example/example_test.go b/acceptance/tests/example/example_test.go index 9c6457f906..b324ac31fe 100644 --- a/acceptance/tests/example/example_test.go +++ b/acceptance/tests/example/example_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Rename package to your test package. package example diff --git a/acceptance/tests/example/main_test.go b/acceptance/tests/example/main_test.go index 323f421d32..f92fff8a59 100644 --- a/acceptance/tests/example/main_test.go +++ b/acceptance/tests/example/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Rename package to your test package. package example diff --git a/acceptance/tests/fixtures/bases/crds-oss/ingressgateway.yaml b/acceptance/tests/fixtures/bases/crds-oss/ingressgateway.yaml index d613dc217c..79b5b194bc 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/ingressgateway.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/ingressgateway.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: IngressGateway metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml index 040b64f155..1217d857c8 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ingressgateway.yaml - mesh.yaml diff --git a/acceptance/tests/fixtures/bases/crds-oss/mesh.yaml b/acceptance/tests/fixtures/bases/crds-oss/mesh.yaml index 85474c1db6..9af8106b27 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/mesh.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/mesh.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: Mesh metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml index 6a90f29506..af4d7c30c2 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ProxyDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index 413eb68d22..d930439c67 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml b/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml index 8ae095cd8e..704ea9ee19 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceExports metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/serviceintentions.yaml b/acceptance/tests/fixtures/bases/crds-oss/serviceintentions.yaml index 6b86312a78..fe3408cbd5 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/serviceintentions.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/serviceintentions.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml b/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml index 05dee04184..a71da92e35 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceResolver metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicerouter.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicerouter.yaml index 8526f1202d..0f04cd4cd4 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicerouter.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicerouter.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicesplitter.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicesplitter.yaml index f0e8d74fe4..2eb9c3fccc 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicesplitter.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicesplitter.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/terminatinggateway.yaml b/acceptance/tests/fixtures/bases/crds-oss/terminatinggateway.yaml index 77333b2011..23daa6da20 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/terminatinggateway.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/terminatinggateway.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: TerminatingGateway metadata: diff --git a/acceptance/tests/fixtures/bases/exportedservices-default/exportedservices-default.yaml b/acceptance/tests/fixtures/bases/exportedservices-default/exportedservices-default.yaml index a260602109..0e523bdd7e 100644 --- a/acceptance/tests/fixtures/bases/exportedservices-default/exportedservices-default.yaml +++ b/acceptance/tests/fixtures/bases/exportedservices-default/exportedservices-default.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/bases/exportedservices-default/kustomization.yaml b/acceptance/tests/fixtures/bases/exportedservices-default/kustomization.yaml index e540a4def1..59527a69fb 100644 --- a/acceptance/tests/fixtures/bases/exportedservices-default/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/exportedservices-default/kustomization.yaml @@ -1,2 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - exportedservices-default.yaml diff --git a/acceptance/tests/fixtures/bases/exportedservices-secondary/exportedservices-secondary.yaml b/acceptance/tests/fixtures/bases/exportedservices-secondary/exportedservices-secondary.yaml index a514ed50d9..69151577f9 100644 --- a/acceptance/tests/fixtures/bases/exportedservices-secondary/exportedservices-secondary.yaml +++ b/acceptance/tests/fixtures/bases/exportedservices-secondary/exportedservices-secondary.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/bases/exportedservices-secondary/kustomization.yaml b/acceptance/tests/fixtures/bases/exportedservices-secondary/kustomization.yaml index 10af8e20c5..e1781d47c1 100644 --- a/acceptance/tests/fixtures/bases/exportedservices-secondary/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/exportedservices-secondary/kustomization.yaml @@ -1,2 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - exportedservices-secondary.yaml diff --git a/acceptance/tests/fixtures/bases/intention/intention.yaml b/acceptance/tests/fixtures/bases/intention/intention.yaml index c7bf26dac2..973866be03 100644 --- a/acceptance/tests/fixtures/bases/intention/intention.yaml +++ b/acceptance/tests/fixtures/bases/intention/intention.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceIntentions metadata: diff --git a/acceptance/tests/fixtures/bases/intention/kustomization.yaml b/acceptance/tests/fixtures/bases/intention/kustomization.yaml index 8d15c05511..572e7727a4 100644 --- a/acceptance/tests/fixtures/bases/intention/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/intention/kustomization.yaml @@ -1,2 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - intention.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/mesh-gateway/kustomization.yaml b/acceptance/tests/fixtures/bases/mesh-gateway/kustomization.yaml index 6a913f2c44..c271e6af8b 100644 --- a/acceptance/tests/fixtures/bases/mesh-gateway/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/mesh-gateway/kustomization.yaml @@ -1,2 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - proxydefaults.yaml diff --git a/acceptance/tests/fixtures/bases/mesh-gateway/proxydefaults.yaml b/acceptance/tests/fixtures/bases/mesh-gateway/proxydefaults.yaml index 2d28036fe5..1560f6c640 100644 --- a/acceptance/tests/fixtures/bases/mesh-gateway/proxydefaults.yaml +++ b/acceptance/tests/fixtures/bases/mesh-gateway/proxydefaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ProxyDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/mesh-peering/kustomization.yaml b/acceptance/tests/fixtures/bases/mesh-peering/kustomization.yaml index b48237763e..e964c5ab05 100644 --- a/acceptance/tests/fixtures/bases/mesh-peering/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/mesh-peering/kustomization.yaml @@ -1,2 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - meshpeering.yaml diff --git a/acceptance/tests/fixtures/bases/mesh-peering/meshpeering.yaml b/acceptance/tests/fixtures/bases/mesh-peering/meshpeering.yaml index de84382d3e..2fb6a04bb6 100644 --- a/acceptance/tests/fixtures/bases/mesh-peering/meshpeering.yaml +++ b/acceptance/tests/fixtures/bases/mesh-peering/meshpeering.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: Mesh metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml index f80bd41d81..5c2e0dcfa2 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/deployment.yaml b/acceptance/tests/fixtures/bases/multiport-app/deployment.yaml index a99d415d80..f345b27c5e 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/deployment.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml b/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml index ead8b3afe5..fb792d63a7 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - deployment.yaml - service.yaml diff --git a/acceptance/tests/fixtures/bases/multiport-app/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/multiport-app/privileged-scc-rolebinding.yaml index f909785b36..d0910816ed 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/privileged-scc-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/multiport-app/psp-rolebinding.yaml index fce63f0076..f321858164 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/psp-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/secret.yaml b/acceptance/tests/fixtures/bases/multiport-app/secret.yaml index cb3fa957e2..bc8d931f1b 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/secret.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/secret.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: Secret metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/service.yaml b/acceptance/tests/fixtures/bases/multiport-app/service.yaml index d18da258a3..8684c75f21 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/service.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/serviceaccount.yaml b/acceptance/tests/fixtures/bases/multiport-app/serviceaccount.yaml index 2293c2e173..87b33476f4 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/bases/peering/peering-acceptor.yaml b/acceptance/tests/fixtures/bases/peering/peering-acceptor.yaml index 3eff952833..1b3ea3956b 100644 --- a/acceptance/tests/fixtures/bases/peering/peering-acceptor.yaml +++ b/acceptance/tests/fixtures/bases/peering/peering-acceptor.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: PeeringAcceptor metadata: diff --git a/acceptance/tests/fixtures/bases/peering/peering-dialer.yaml b/acceptance/tests/fixtures/bases/peering/peering-dialer.yaml index ec125d7bb6..9bf9f03819 100644 --- a/acceptance/tests/fixtures/bases/peering/peering-dialer.yaml +++ b/acceptance/tests/fixtures/bases/peering/peering-dialer.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: PeeringDialer metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml index be44c13e0a..b80bc5c562 100644 --- a/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/deployment.yaml b/acceptance/tests/fixtures/bases/static-client/deployment.yaml index 66bb771f6f..ff29f1b03a 100644 --- a/acceptance/tests/fixtures/bases/static-client/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-client/deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/kustomization.yaml b/acceptance/tests/fixtures/bases/static-client/kustomization.yaml index b9d8e11f73..9aa0009dc4 100644 --- a/acceptance/tests/fixtures/bases/static-client/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-client/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - deployment.yaml - service.yaml diff --git a/acceptance/tests/fixtures/bases/static-client/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-client/privileged-scc-rolebinding.yaml index b8e097c3d8..36f6652e65 100644 --- a/acceptance/tests/fixtures/bases/static-client/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-client/privileged-scc-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-client/psp-rolebinding.yaml index a377c8038d..e516e57f7c 100644 --- a/acceptance/tests/fixtures/bases/static-client/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-client/psp-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/service.yaml b/acceptance/tests/fixtures/bases/static-client/service.yaml index 16fbee0463..abd6ee4faf 100644 --- a/acceptance/tests/fixtures/bases/static-client/service.yaml +++ b/acceptance/tests/fixtures/bases/static-client/service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/serviceaccount.yaml b/acceptance/tests/fixtures/bases/static-client/serviceaccount.yaml index b9bf136862..23578e5ead 100644 --- a/acceptance/tests/fixtures/bases/static-client/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/static-client/serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/bases/static-metrics-app/deployment.yaml b/acceptance/tests/fixtures/bases/static-metrics-app/deployment.yaml index a3020ddb47..21852487e3 100644 --- a/acceptance/tests/fixtures/bases/static-metrics-app/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-metrics-app/deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/static-metrics-app/kustomization.yaml b/acceptance/tests/fixtures/bases/static-metrics-app/kustomization.yaml index 6d1374a18e..ccc066e0a8 100644 --- a/acceptance/tests/fixtures/bases/static-metrics-app/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-metrics-app/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - deployment.yaml - service.yaml diff --git a/acceptance/tests/fixtures/bases/static-metrics-app/service.yaml b/acceptance/tests/fixtures/bases/static-metrics-app/service.yaml index a37db26875..2e553f7e83 100644 --- a/acceptance/tests/fixtures/bases/static-metrics-app/service.yaml +++ b/acceptance/tests/fixtures/bases/static-metrics-app/service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml index d224a082da..2be7cf13db 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/configmap.yaml b/acceptance/tests/fixtures/bases/static-server-https/configmap.yaml index dcb975978e..5a037ecdc6 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/configmap.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/configmap.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: ConfigMap metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/deployment.yaml b/acceptance/tests/fixtures/bases/static-server-https/deployment.yaml index 3071162c15..c95bdb4289 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml b/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml index 78508b7938..da166af201 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - deployment.yaml - configmap.yaml diff --git a/acceptance/tests/fixtures/bases/static-server-https/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-https/privileged-scc-rolebinding.yaml index 1b9fe13789..bed477ed26 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/privileged-scc-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-https/psp-rolebinding.yaml index acccd2fcec..3c6cfad8f1 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/psp-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/service.yaml b/acceptance/tests/fixtures/bases/static-server-https/service.yaml index 168c90d3d0..7f4f930c0a 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/service.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/serviceaccount.yaml b/acceptance/tests/fixtures/bases/static-server-https/serviceaccount.yaml index f4267a573e..ced9002d6b 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml index d224a082da..2be7cf13db 100644 --- a/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/deployment.yaml b/acceptance/tests/fixtures/bases/static-server/deployment.yaml index 1c724b0d37..f61371a880 100644 --- a/acceptance/tests/fixtures/bases/static-server/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-server/deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/kustomization.yaml b/acceptance/tests/fixtures/bases/static-server/kustomization.yaml index b9d8e11f73..9aa0009dc4 100644 --- a/acceptance/tests/fixtures/bases/static-server/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-server/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - deployment.yaml - service.yaml diff --git a/acceptance/tests/fixtures/bases/static-server/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server/privileged-scc-rolebinding.yaml index 1b9fe13789..bed477ed26 100644 --- a/acceptance/tests/fixtures/bases/static-server/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server/privileged-scc-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server/psp-rolebinding.yaml index acccd2fcec..3c6cfad8f1 100644 --- a/acceptance/tests/fixtures/bases/static-server/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server/psp-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/service.yaml b/acceptance/tests/fixtures/bases/static-server/service.yaml index 4776c07239..6d8d54acc2 100644 --- a/acceptance/tests/fixtures/bases/static-server/service.yaml +++ b/acceptance/tests/fixtures/bases/static-server/service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/serviceaccount.yaml b/acceptance/tests/fixtures/bases/static-server/serviceaccount.yaml index f4267a573e..ced9002d6b 100644 --- a/acceptance/tests/fixtures/bases/static-server/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/static-server/serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml index 499fdc5bc1..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/exportedservices-default diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/patch.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/patch.yaml index c98ecb6f48..a06855e317 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml index 499fdc5bc1..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/exportedservices-default diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/patch.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/patch.yaml index f826174aec..3e86df911d 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml index 5a9c8412aa..bb16f51e64 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/exportedservices-secondary diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/patch.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/patch.yaml index d2fc1ab914..141a080903 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml index 5a9c8412aa..bb16f51e64 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/exportedservices-secondary diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/patch.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/patch.yaml index 4165f2d21a..3b8c2a4068 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml index 499fdc5bc1..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/exportedservices-default diff --git a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/patch.yaml index eed6bee4cc..0031c7f601 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml index 499fdc5bc1..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/exportedservices-default diff --git a/acceptance/tests/fixtures/cases/crd-peers/default/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/default/patch.yaml index b0dcd89ebb..e632512ff1 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml index 499fdc5bc1..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/exportedservices-default diff --git a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/patch.yaml index 4162a0f27b..300fbcf3d6 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml b/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml index 4703d23493..f9f8aad4bf 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml index cdc3e60cb1..14f6c765d8 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../bases/crds-oss - exportedservices.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml index 9834f91903..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-inject-multiport/patch.yaml b/acceptance/tests/fixtures/cases/static-client-inject-multiport/patch.yaml index c38ce8e448..c8b0ae8412 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject-multiport/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject-multiport/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml index 9834f91903..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-inject/patch.yaml b/acceptance/tests/fixtures/cases/static-client-inject/patch.yaml index 2f8b32ec0a..aea6e8c778 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml index 9834f91903..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-multi-dc/patch.yaml b/acceptance/tests/fixtures/cases/static-client-multi-dc/patch.yaml index 7b9b095073..17e635b1d5 100644 --- a/acceptance/tests/fixtures/cases/static-client-multi-dc/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-multi-dc/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml index 4c5d895a98..97a00c6466 100644 --- a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-namespaces/patch.yaml b/acceptance/tests/fixtures/cases/static-client-namespaces/patch.yaml index b41a27df3f..2ebdbf6d9e 100644 --- a/acceptance/tests/fixtures/cases/static-client-namespaces/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-namespaces/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml index 7191edfb80..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/patch.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/patch.yaml index 43507364f8..4044d78bdf 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml index 7191edfb80..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/patch.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/patch.yaml index 42857c3d2b..f6a3c50f74 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml index 7191edfb80..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/patch.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/patch.yaml index 2fa8ec7e27..720252e833 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml index 7191edfb80..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/patch.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/patch.yaml index f0ceb634bd..df04ceccda 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml index 7191edfb80..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/patch.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/patch.yaml index 02ea8993ec..075cb6ecd4 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml index 7191edfb80..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default/patch.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default/patch.yaml index 715485e0f8..a6a2f72b37 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml index 7191edfb80..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/patch.yaml b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/patch.yaml index fd622759a4..5a7ce34e1b 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml index 9834f91903..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../bases/static-client diff --git a/acceptance/tests/fixtures/cases/static-client-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/static-client-tproxy/patch.yaml index 573bf93b01..8540fc4721 100644 --- a/acceptance/tests/fixtures/cases/static-client-tproxy/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-tproxy/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml index bac892baa0..bc50c78adf 100644 --- a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../bases/static-server diff --git a/acceptance/tests/fixtures/cases/static-server-inject/patch.yaml b/acceptance/tests/fixtures/cases/static-server-inject/patch.yaml index f2bf0d6539..56ef015d8f 100644 --- a/acceptance/tests/fixtures/cases/static-server-inject/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-server-inject/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go b/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go index b713620f1e..ec4878df04 100644 --- a/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go +++ b/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ingressgateway import ( diff --git a/acceptance/tests/ingress-gateway/ingress_gateway_test.go b/acceptance/tests/ingress-gateway/ingress_gateway_test.go index b6535439c1..b5df6287b6 100644 --- a/acceptance/tests/ingress-gateway/ingress_gateway_test.go +++ b/acceptance/tests/ingress-gateway/ingress_gateway_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ingressgateway import ( diff --git a/acceptance/tests/ingress-gateway/main_test.go b/acceptance/tests/ingress-gateway/main_test.go index bd927f96cb..4a74d2f361 100644 --- a/acceptance/tests/ingress-gateway/main_test.go +++ b/acceptance/tests/ingress-gateway/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ingressgateway import ( diff --git a/acceptance/tests/metrics/main_test.go b/acceptance/tests/metrics/main_test.go index 8717c7c4b5..d3c15b811b 100644 --- a/acceptance/tests/metrics/main_test.go +++ b/acceptance/tests/metrics/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package metrics import ( diff --git a/acceptance/tests/metrics/metrics_test.go b/acceptance/tests/metrics/metrics_test.go index 5d05e45aa4..acfe465b00 100644 --- a/acceptance/tests/metrics/metrics_test.go +++ b/acceptance/tests/metrics/metrics_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package metrics import ( diff --git a/acceptance/tests/partitions/main_test.go b/acceptance/tests/partitions/main_test.go index b2758a572c..9368c12f00 100644 --- a/acceptance/tests/partitions/main_test.go +++ b/acceptance/tests/partitions/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package partitions import ( diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index f205ac67e2..b14f079a68 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package partitions import ( diff --git a/acceptance/tests/partitions/partitions_sync_test.go b/acceptance/tests/partitions/partitions_sync_test.go index e29ef18c78..344d64a780 100644 --- a/acceptance/tests/partitions/partitions_sync_test.go +++ b/acceptance/tests/partitions/partitions_sync_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package partitions import ( diff --git a/acceptance/tests/peering/main_test.go b/acceptance/tests/peering/main_test.go index 12bb35afd5..64c5f8ed03 100644 --- a/acceptance/tests/peering/main_test.go +++ b/acceptance/tests/peering/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package peering import ( diff --git a/acceptance/tests/peering/peering_connect_namespaces_test.go b/acceptance/tests/peering/peering_connect_namespaces_test.go index 3e243b4781..9276582db3 100644 --- a/acceptance/tests/peering/peering_connect_namespaces_test.go +++ b/acceptance/tests/peering/peering_connect_namespaces_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package peering import ( diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index 0761854497..ad62ca6926 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package peering import ( diff --git a/acceptance/tests/snapshot-agent/main_test.go b/acceptance/tests/snapshot-agent/main_test.go index daa389d4c4..3d70823059 100644 --- a/acceptance/tests/snapshot-agent/main_test.go +++ b/acceptance/tests/snapshot-agent/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package snapshotagent import ( diff --git a/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go b/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go index a4c1ef7494..b5613fe76d 100644 --- a/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go +++ b/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package snapshotagent import ( diff --git a/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go b/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go index ee20ffd9d7..10cceb5952 100644 --- a/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go +++ b/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package snapshotagent import ( diff --git a/acceptance/tests/sync/main_test.go b/acceptance/tests/sync/main_test.go index 80c394e561..a26ecc9670 100644 --- a/acceptance/tests/sync/main_test.go +++ b/acceptance/tests/sync/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package sync import ( diff --git a/acceptance/tests/sync/sync_catalog_namespaces_test.go b/acceptance/tests/sync/sync_catalog_namespaces_test.go index 79f592033c..7634220b6b 100644 --- a/acceptance/tests/sync/sync_catalog_namespaces_test.go +++ b/acceptance/tests/sync/sync_catalog_namespaces_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package sync import ( diff --git a/acceptance/tests/sync/sync_catalog_test.go b/acceptance/tests/sync/sync_catalog_test.go index c4f873fcbd..7407126580 100644 --- a/acceptance/tests/sync/sync_catalog_test.go +++ b/acceptance/tests/sync/sync_catalog_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package sync import ( diff --git a/acceptance/tests/terminating-gateway/common.go b/acceptance/tests/terminating-gateway/common.go index b94dd8a897..908e6cbec1 100644 --- a/acceptance/tests/terminating-gateway/common.go +++ b/acceptance/tests/terminating-gateway/common.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package terminatinggateway import ( diff --git a/acceptance/tests/terminating-gateway/main_test.go b/acceptance/tests/terminating-gateway/main_test.go index 477e125f94..0c8127623d 100644 --- a/acceptance/tests/terminating-gateway/main_test.go +++ b/acceptance/tests/terminating-gateway/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package terminatinggateway import ( diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go index 4ff4ae7bd4..109975d731 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package terminatinggateway import ( diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go index 8c4435ae75..7ad9f7da10 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package terminatinggateway import ( diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_test.go index 16809de5e2..25792e9abb 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package terminatinggateway import ( diff --git a/acceptance/tests/vault/main_test.go b/acceptance/tests/vault/main_test.go index 1d3a5a5842..e20892bf1c 100644 --- a/acceptance/tests/vault/main_test.go +++ b/acceptance/tests/vault/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package vault import ( diff --git a/acceptance/tests/vault/vault_namespaces_test.go b/acceptance/tests/vault/vault_namespaces_test.go index 8d0beefdc0..68fa819a84 100644 --- a/acceptance/tests/vault/vault_namespaces_test.go +++ b/acceptance/tests/vault/vault_namespaces_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package vault import ( diff --git a/acceptance/tests/vault/vault_partitions_test.go b/acceptance/tests/vault/vault_partitions_test.go index 0fff6726dc..53bdc23e97 100644 --- a/acceptance/tests/vault/vault_partitions_test.go +++ b/acceptance/tests/vault/vault_partitions_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package vault import ( diff --git a/acceptance/tests/vault/vault_test.go b/acceptance/tests/vault/vault_test.go index 4d43d8bb5b..47d58b68c5 100644 --- a/acceptance/tests/vault/vault_test.go +++ b/acceptance/tests/vault/vault_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package vault import ( diff --git a/acceptance/tests/vault/vault_tls_auto_reload_test.go b/acceptance/tests/vault/vault_tls_auto_reload_test.go index f079ee7492..c3a3ae4034 100644 --- a/acceptance/tests/vault/vault_tls_auto_reload_test.go +++ b/acceptance/tests/vault/vault_tls_auto_reload_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package vault import ( diff --git a/acceptance/tests/vault/vault_wan_fed_test.go b/acceptance/tests/vault/vault_wan_fed_test.go index 0a08810463..21a86937f4 100644 --- a/acceptance/tests/vault/vault_wan_fed_test.go +++ b/acceptance/tests/vault/vault_wan_fed_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package vault import ( diff --git a/acceptance/tests/wan-federation/main_test.go b/acceptance/tests/wan-federation/main_test.go index 197a3181e8..ced18d5cc7 100644 --- a/acceptance/tests/wan-federation/main_test.go +++ b/acceptance/tests/wan-federation/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package wanfederation import ( diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index 1b23633077..ced126af42 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package wanfederation import ( diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index ffd7961302..d0216aa36e 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v2 name: consul version: 1.2.0-dev diff --git a/charts/consul/addons/gen.sh b/charts/consul/addons/gen.sh index 967b368c63..1d03390bed 100755 --- a/charts/consul/addons/gen.sh +++ b/charts/consul/addons/gen.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + WD=$(dirname "$0") WD=$(cd "$WD"; pwd) diff --git a/charts/consul/addons/values/prometheus.yaml b/charts/consul/addons/values/prometheus.yaml index 9ffe9af5ae..1f90636f2e 100644 --- a/charts/consul/addons/values/prometheus.yaml +++ b/charts/consul/addons/values/prometheus.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # Disable non-essential components alertmanager: enabled: false diff --git a/charts/consul/templates/api-gateway-controller-clusterrole.yaml b/charts/consul/templates/api-gateway-controller-clusterrole.yaml index eac2bd1f69..5d2d45987b 100644 --- a/charts/consul/templates/api-gateway-controller-clusterrole.yaml +++ b/charts/consul/templates/api-gateway-controller-clusterrole.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.apiGateway.enabled }} # The ClusterRole to enable the API Gateway controller to access required api endpoints. apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml b/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml index d083a08129..c874f5c7af 100644 --- a/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml +++ b/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.apiGateway.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 86517d7140..4d757cfa2c 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.apiGateway.enabled }} {{- if not .Values.client.grpc }}{{ fail "client.grpc must be true for api gateway" }}{{ end }} {{- if not .Values.apiGateway.image}}{{ fail "apiGateway.image must be set to enable api gateway" }}{{ end }} diff --git a/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml b/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml index 390d084303..3ac3a05ba3 100644 --- a/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml +++ b/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if and .Values.apiGateway.enabled .Values.global.enablePodSecurityPolicies }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/api-gateway-controller-service.yaml b/charts/consul/templates/api-gateway-controller-service.yaml index aa79ff9fc3..c2ed00cd88 100644 --- a/charts/consul/templates/api-gateway-controller-service.yaml +++ b/charts/consul/templates/api-gateway-controller-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.apiGateway.enabled }} apiVersion: v1 kind: Service diff --git a/charts/consul/templates/api-gateway-controller-serviceaccount.yaml b/charts/consul/templates/api-gateway-controller-serviceaccount.yaml index 98292a8dbe..0ee711f164 100644 --- a/charts/consul/templates/api-gateway-controller-serviceaccount.yaml +++ b/charts/consul/templates/api-gateway-controller-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.apiGateway.enabled }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/api-gateway-gatewayclass.yaml b/charts/consul/templates/api-gateway-gatewayclass.yaml index d9ba85e633..4a3a59f849 100644 --- a/charts/consul/templates/api-gateway-gatewayclass.yaml +++ b/charts/consul/templates/api-gateway-gatewayclass.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.apiGateway.enabled .Values.apiGateway.managedGatewayClass.enabled) }} apiVersion: gateway.networking.k8s.io/v1alpha2 kind: GatewayClass diff --git a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml index ba0e6c63db..feed4e158b 100644 --- a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml +++ b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.apiGateway.enabled .Values.apiGateway.managedGatewayClass.enabled) }} apiVersion: api-gateway.consul.hashicorp.com/v1alpha1 kind: GatewayClassConfig diff --git a/charts/consul/templates/api-gateway-podsecuritypolicy.yaml b/charts/consul/templates/api-gateway-podsecuritypolicy.yaml index 48f826f995..f445cb77bf 100644 --- a/charts/consul/templates/api-gateway-podsecuritypolicy.yaml +++ b/charts/consul/templates/api-gateway-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if and .Values.apiGateway.enabled .Values.global.enablePodSecurityPolicies }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/auth-method-clusterrole.yaml b/charts/consul/templates/auth-method-clusterrole.yaml index 6b8f2c5451..bd696f479f 100644 --- a/charts/consul/templates/auth-method-clusterrole.yaml +++ b/charts/consul/templates/auth-method-clusterrole.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.acls.manageSystemACLs }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/charts/consul/templates/auth-method-clusterrolebinding.yaml b/charts/consul/templates/auth-method-clusterrolebinding.yaml index 9bd6c64113..9c6b7cef31 100644 --- a/charts/consul/templates/auth-method-clusterrolebinding.yaml +++ b/charts/consul/templates/auth-method-clusterrolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.acls.manageSystemACLs }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/auth-method-secret.yaml b/charts/consul/templates/auth-method-secret.yaml index af0aeb4e1b..04fb0c164d 100644 --- a/charts/consul/templates/auth-method-secret.yaml +++ b/charts/consul/templates/auth-method-secret.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.acls.manageSystemACLs }} apiVersion: v1 kind: Secret diff --git a/charts/consul/templates/auth-method-serviceaccount.yaml b/charts/consul/templates/auth-method-serviceaccount.yaml index 098339b8c8..453063ecd3 100644 --- a/charts/consul/templates/auth-method-serviceaccount.yaml +++ b/charts/consul/templates/auth-method-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.acls.manageSystemACLs }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/client-config-configmap.yaml b/charts/consul/templates/client-config-configmap.yaml index f9650a100b..9a7122aa02 100644 --- a/charts/consul/templates/client-config-configmap.yaml +++ b/charts/consul/templates/client-config-configmap.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} # ConfigMap with extra configuration specified directly to the chart # for client agents only. diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 09a70b394e..91401eebdf 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.imageK8s }}{{ fail "global.imageK8s is not a valid key, use global.imageK8S (note the capital 'S')" }}{{ end -}} {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} diff --git a/charts/consul/templates/client-podsecuritypolicy.yaml b/charts/consul/templates/client-podsecuritypolicy.yaml index 0121bdf586..db87e29f8d 100644 --- a/charts/consul/templates/client-podsecuritypolicy.yaml +++ b/charts/consul/templates/client-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/client-role.yaml b/charts/consul/templates/client-role.yaml index 7f05b82e6b..aebe8e1a52 100644 --- a/charts/consul/templates/client-role.yaml +++ b/charts/consul/templates/client-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/client-rolebinding.yaml b/charts/consul/templates/client-rolebinding.yaml index b034c70e55..4281a7869e 100644 --- a/charts/consul/templates/client-rolebinding.yaml +++ b/charts/consul/templates/client-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/client-securitycontextconstraints.yaml b/charts/consul/templates/client-securitycontextconstraints.yaml index 07e7711384..7ae5b7ba2a 100644 --- a/charts/consul/templates/client-securitycontextconstraints.yaml +++ b/charts/consul/templates/client-securitycontextconstraints.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.global.openshift.enabled (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints diff --git a/charts/consul/templates/client-serviceaccount.yaml b/charts/consul/templates/client-serviceaccount.yaml index addd757b84..44b74a5c53 100644 --- a/charts/consul/templates/client-serviceaccount.yaml +++ b/charts/consul/templates/client-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/cni-clusterrole.yaml b/charts/consul/templates/cni-clusterrole.yaml index 773942cca8..a1cda2b7c5 100644 --- a/charts/consul/templates/cni-clusterrole.yaml +++ b/charts/consul/templates/cni-clusterrole.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.cni.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/charts/consul/templates/cni-clusterrolebinding.yaml b/charts/consul/templates/cni-clusterrolebinding.yaml index 4b860388b6..f86391c259 100644 --- a/charts/consul/templates/cni-clusterrolebinding.yaml +++ b/charts/consul/templates/cni-clusterrolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.cni.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index ae04d9e657..1eda31998c 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and (.Values.connectInject.cni.enabled) (not .Values.connectInject.enabled)) }}{{ fail "connectInject.enabled must be true if connectInject.cni.enabled is true" }}{{ end -}} {{- if .Values.connectInject.cni.enabled }} apiVersion: apps/v1 diff --git a/charts/consul/templates/cni-networkattachmentdefinition.yaml b/charts/consul/templates/cni-networkattachmentdefinition.yaml index 80ef50bac6..056b74fa71 100644 --- a/charts/consul/templates/cni-networkattachmentdefinition.yaml +++ b/charts/consul/templates/cni-networkattachmentdefinition.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and (.Values.connectInject.cni.enabled) (.Values.connectInject.cni.multus)) }} apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition diff --git a/charts/consul/templates/cni-podsecuritypolicy.yaml b/charts/consul/templates/cni-podsecuritypolicy.yaml index b600ed1b4b..8747387850 100644 --- a/charts/consul/templates/cni-podsecuritypolicy.yaml +++ b/charts/consul/templates/cni-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.connectInject.cni.enabled .Values.global.enablePodSecurityPolicies) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/cni-resourcequota.yaml b/charts/consul/templates/cni-resourcequota.yaml index 054c3061f5..1b8a8c7332 100644 --- a/charts/consul/templates/cni-resourcequota.yaml +++ b/charts/consul/templates/cni-resourcequota.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.cni.enabled }} apiVersion: v1 kind: ResourceQuota diff --git a/charts/consul/templates/cni-securitycontextconstraints.yaml b/charts/consul/templates/cni-securitycontextconstraints.yaml index 2c09dba9b8..d408617925 100644 --- a/charts/consul/templates/cni-securitycontextconstraints.yaml +++ b/charts/consul/templates/cni-securitycontextconstraints.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and (.Values.connectInject.cni.enabled) (.Values.global.openshift.enabled)) }} apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints diff --git a/charts/consul/templates/cni-serviceaccount.yaml b/charts/consul/templates/cni-serviceaccount.yaml index cf4250b696..b3740d9f4a 100644 --- a/charts/consul/templates/cni-serviceaccount.yaml +++ b/charts/consul/templates/cni-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.cni.enabled }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index f2e12f0ad9..307a9e3aed 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} # The ClusterRole to enable the Connect injector to get, list, watch and patch MutatingWebhookConfiguration. apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/connect-inject-clusterrolebinding.yaml b/charts/consul/templates/connect-inject-clusterrolebinding.yaml index c380adb311..de969246a7 100644 --- a/charts/consul/templates/connect-inject-clusterrolebinding.yaml +++ b/charts/consul/templates/connect-inject-clusterrolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 2b52c1b81c..529cc98787 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if and .Values.global.peering.enabled (not .Values.connectInject.enabled) }}{{ fail "setting global.peering.enabled to true requires connectInject.enabled to be true" }}{{ end }} {{- if and .Values.global.peering.enabled (not .Values.global.tls.enabled) }}{{ fail "setting global.peering.enabled to true requires global.tls.enabled to be true" }}{{ end }} {{- if and .Values.global.peering.enabled (not .Values.meshGateway.enabled) }}{{ fail "setting global.peering.enabled to true requires meshGateway.enabled to be true" }}{{ end }} diff --git a/charts/consul/templates/connect-inject-leader-election-role.yaml b/charts/consul/templates/connect-inject-leader-election-role.yaml index 703aaffaac..2efaf64ec0 100644 --- a/charts/consul/templates/connect-inject-leader-election-role.yaml +++ b/charts/consul/templates/connect-inject-leader-election-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/connect-inject-leader-election-rolebinding.yaml b/charts/consul/templates/connect-inject-leader-election-rolebinding.yaml index 9a27d3c868..28c4c9a7de 100644 --- a/charts/consul/templates/connect-inject-leader-election-rolebinding.yaml +++ b/charts/consul/templates/connect-inject-leader-election-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml index afcfd3800f..c01ffc333b 100644 --- a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml +++ b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} # The MutatingWebhookConfiguration to enable the Connect injector. apiVersion: admissionregistration.k8s.io/v1 diff --git a/charts/consul/templates/connect-inject-podsecuritypolicy.yaml b/charts/consul/templates/connect-inject-podsecuritypolicy.yaml index 0fafef7c40..5f449eba4d 100644 --- a/charts/consul/templates/connect-inject-podsecuritypolicy.yaml +++ b/charts/consul/templates/connect-inject-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/connect-inject-service.yaml b/charts/consul/templates/connect-inject-service.yaml index b0284af74d..39ca054b30 100644 --- a/charts/consul/templates/connect-inject-service.yaml +++ b/charts/consul/templates/connect-inject-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} # The service for the Connect sidecar injector apiVersion: v1 diff --git a/charts/consul/templates/connect-inject-serviceaccount.yaml b/charts/consul/templates/connect-inject-serviceaccount.yaml index ea2352c7ac..3ec08e7ffb 100644 --- a/charts/consul/templates/connect-inject-serviceaccount.yaml +++ b/charts/consul/templates/connect-inject-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/connect-injector-disruptionbudget.yaml b/charts/consul/templates/connect-injector-disruptionbudget.yaml index 9b9cf2e39e..4c7753a375 100644 --- a/charts/consul/templates/connect-injector-disruptionbudget.yaml +++ b/charts/consul/templates/connect-injector-disruptionbudget.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.connectInject.disruptionBudget.enabled (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled))) }} # PodDisruptionBudget to prevent degrading the connectInject cluster through # voluntary cluster changes. diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 007990372c..5ff9450307 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index a01fafd8dd..d7bd768593 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index 2e33eb9653..2e238c318e 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index e06e830f04..e74bb2d733 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index e24401e761..aab95bb61a 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 749f2e4257..6c34618cb3 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 5c6ecc7476..0cb99dc97b 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index cdbb5413b0..18ab5b9d97 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index e058052e97..f8989a8cc6 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index 5052facc06..c927745f45 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index a2af050c3d..4c28e5a506 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 583c218be8..2ed7143f8b 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/create-federation-secret-job.yaml b/charts/consul/templates/create-federation-secret-job.yaml index 4f83a1f82a..a90b2610bf 100644 --- a/charts/consul/templates/create-federation-secret-job.yaml +++ b/charts/consul/templates/create-federation-secret-job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.federation.createFederationSecret }} {{- if not .Values.global.federation.enabled }}{{ fail "global.federation.enabled must be true when global.federation.createFederationSecret is true" }}{{ end }} {{- if and (not .Values.global.acls.createReplicationToken) .Values.global.acls.manageSystemACLs }}{{ fail "global.acls.createReplicationToken must be true when global.acls.manageSystemACLs is true because the federation secret must include the replication token" }}{{ end }} diff --git a/charts/consul/templates/create-federation-secret-podsecuritypolicy.yaml b/charts/consul/templates/create-federation-secret-podsecuritypolicy.yaml index 8217311992..e05659807d 100644 --- a/charts/consul/templates/create-federation-secret-podsecuritypolicy.yaml +++ b/charts/consul/templates/create-federation-secret-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.enablePodSecurityPolicies }} {{- if .Values.global.federation.createFederationSecret }} apiVersion: policy/v1beta1 diff --git a/charts/consul/templates/create-federation-secret-role.yaml b/charts/consul/templates/create-federation-secret-role.yaml index 086932a831..6d285b968a 100644 --- a/charts/consul/templates/create-federation-secret-role.yaml +++ b/charts/consul/templates/create-federation-secret-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.federation.createFederationSecret }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/create-federation-secret-rolebinding.yaml b/charts/consul/templates/create-federation-secret-rolebinding.yaml index 3db8e7cb06..e9c326448f 100644 --- a/charts/consul/templates/create-federation-secret-rolebinding.yaml +++ b/charts/consul/templates/create-federation-secret-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.federation.createFederationSecret }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/create-federation-secret-serviceaccount.yaml b/charts/consul/templates/create-federation-secret-serviceaccount.yaml index e398ec69c4..843e75b61b 100644 --- a/charts/consul/templates/create-federation-secret-serviceaccount.yaml +++ b/charts/consul/templates/create-federation-secret-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.federation.createFederationSecret }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/dns-service.yaml b/charts/consul/templates/dns-service.yaml index 5bb446bc19..03b3a4cc9b 100644 --- a/charts/consul/templates/dns-service.yaml +++ b/charts/consul/templates/dns-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.dns.enabled | toString) "-") .Values.dns.enabled) (and (eq (.Values.dns.enabled | toString) "-") .Values.connectInject.transparentProxy.defaultEnabled)) }} # Service for Consul DNS. apiVersion: v1 diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index 0122690104..e6be55878a 100644 --- a/charts/consul/templates/enterprise-license-job.yaml +++ b/charts/consul/templates/enterprise-license-job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.server.enterpriseLicense }}{{ fail "server.enterpriseLicense has been moved to global.enterpriseLicense" }}{{ end -}} {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} diff --git a/charts/consul/templates/enterprise-license-podsecuritypolicy.yaml b/charts/consul/templates/enterprise-license-podsecuritypolicy.yaml index cf96367473..24adf32765 100644 --- a/charts/consul/templates/enterprise-license-podsecuritypolicy.yaml +++ b/charts/consul/templates/enterprise-license-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} {{- if .Values.global.enablePodSecurityPolicies }} diff --git a/charts/consul/templates/enterprise-license-role.yaml b/charts/consul/templates/enterprise-license-role.yaml index 6a1b7fdffa..7536d51c60 100644 --- a/charts/consul/templates/enterprise-license-role.yaml +++ b/charts/consul/templates/enterprise-license-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/enterprise-license-rolebinding.yaml b/charts/consul/templates/enterprise-license-rolebinding.yaml index a21118b431..5b479613db 100644 --- a/charts/consul/templates/enterprise-license-rolebinding.yaml +++ b/charts/consul/templates/enterprise-license-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/enterprise-license-serviceaccount.yaml b/charts/consul/templates/enterprise-license-serviceaccount.yaml index 31c9da841e..6286281350 100644 --- a/charts/consul/templates/enterprise-license-serviceaccount.yaml +++ b/charts/consul/templates/enterprise-license-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} apiVersion: v1 diff --git a/charts/consul/templates/expose-servers-service.yaml b/charts/consul/templates/expose-servers-service.yaml index d86cec9042..56743bbee6 100644 --- a/charts/consul/templates/expose-servers-service.yaml +++ b/charts/consul/templates/expose-servers-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- $serverExposeServiceEnabled := (or (and (ne (.Values.server.exposeService.enabled | toString) "-") .Values.server.exposeService.enabled) (and (eq (.Values.server.exposeService.enabled | toString) "-") .Values.global.adminPartitions.enabled)) -}} {{- if (and $serverEnabled $serverExposeServiceEnabled) }} diff --git a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml index 9d296478a1..fc7548366a 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.gossipEncryption.autoGenerate }} {{- if (or .Values.global.gossipEncryption.secretName .Values.global.gossipEncryption.secretKey) }} {{ fail "If global.gossipEncryption.autoGenerate is true, global.gossipEncryption.secretName and global.gossipEncryption.secretKey must not be set." }} diff --git a/charts/consul/templates/gossip-encryption-autogenerate-podsecuritypolicy.yaml b/charts/consul/templates/gossip-encryption-autogenerate-podsecuritypolicy.yaml index 209b3aa343..54095be3a0 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-podsecuritypolicy.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if and .Values.global.gossipEncryption.autoGenerate .Values.global.enablePodSecurityPolicies }} --- apiVersion: policy/v1beta1 diff --git a/charts/consul/templates/gossip-encryption-autogenerate-role.yaml b/charts/consul/templates/gossip-encryption-autogenerate-role.yaml index 8c51c96ffe..70b6058bda 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-role.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.gossipEncryption.autoGenerate }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/gossip-encryption-autogenerate-rolebinding.yaml b/charts/consul/templates/gossip-encryption-autogenerate-rolebinding.yaml index 7118475f64..961d14e240 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-rolebinding.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.gossipEncryption.autoGenerate }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/gossip-encryption-autogenerate-serviceaccount.yaml b/charts/consul/templates/gossip-encryption-autogenerate-serviceaccount.yaml index 1fd620237f..4ea1c6bb39 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-serviceaccount.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.global.gossipEncryption.autoGenerate }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index 4f72031855..8a0f35328c 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.ingressGateways.enabled }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} diff --git a/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml b/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml index f7354da2b3..abcf971598 100644 --- a/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml +++ b/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.global.enablePodSecurityPolicies .Values.ingressGateways.enabled) }} {{- $root := . }} {{- range .Values.ingressGateways.gateways }} diff --git a/charts/consul/templates/ingress-gateways-role.yaml b/charts/consul/templates/ingress-gateways-role.yaml index 49e8486e58..ea4567163b 100644 --- a/charts/consul/templates/ingress-gateways-role.yaml +++ b/charts/consul/templates/ingress-gateways-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.ingressGateways.enabled }} {{- $root := . }} diff --git a/charts/consul/templates/ingress-gateways-rolebinding.yaml b/charts/consul/templates/ingress-gateways-rolebinding.yaml index 601de775f4..015395ec16 100644 --- a/charts/consul/templates/ingress-gateways-rolebinding.yaml +++ b/charts/consul/templates/ingress-gateways-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.ingressGateways.enabled }} {{- $root := . }} {{- range .Values.ingressGateways.gateways }} diff --git a/charts/consul/templates/ingress-gateways-service.yaml b/charts/consul/templates/ingress-gateways-service.yaml index cf54a740fe..5f0ca22c39 100644 --- a/charts/consul/templates/ingress-gateways-service.yaml +++ b/charts/consul/templates/ingress-gateways-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.ingressGateways.enabled }} {{- $root := . }} diff --git a/charts/consul/templates/ingress-gateways-serviceaccount.yaml b/charts/consul/templates/ingress-gateways-serviceaccount.yaml index cea6cafc21..0dc1e4564a 100644 --- a/charts/consul/templates/ingress-gateways-serviceaccount.yaml +++ b/charts/consul/templates/ingress-gateways-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.ingressGateways.enabled }} {{- $root := . }} {{- $defaults := .Values.ingressGateways.defaults }} diff --git a/charts/consul/templates/mesh-gateway-clusterrole.yaml b/charts/consul/templates/mesh-gateway-clusterrole.yaml index b951418b26..fe4c7ca348 100644 --- a/charts/consul/templates/mesh-gateway-clusterrole.yaml +++ b/charts/consul/templates/mesh-gateway-clusterrole.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.meshGateway.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml b/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml index f8150ebb53..028dea8a34 100644 --- a/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml +++ b/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.meshGateway.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index 2b2bdc8c2a..8e2c0cc791 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.meshGateway.enabled }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.acls.manageSystemACLs (ne .Values.meshGateway.consulServiceName "") (ne .Values.meshGateway.consulServiceName "mesh-gateway") }}{{ fail "if global.acls.manageSystemACLs is true, meshGateway.consulServiceName cannot be set" }}{{ end -}} diff --git a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml index b5bbb2fa03..712f58c0cf 100644 --- a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml +++ b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if and .Values.global.enablePodSecurityPolicies .Values.meshGateway.enabled }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/mesh-gateway-service.yaml b/charts/consul/templates/mesh-gateway-service.yaml index 5fdceca8df..3dbfb0486a 100644 --- a/charts/consul/templates/mesh-gateway-service.yaml +++ b/charts/consul/templates/mesh-gateway-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if and .Values.meshGateway.enabled }} apiVersion: v1 kind: Service diff --git a/charts/consul/templates/mesh-gateway-serviceaccount.yaml b/charts/consul/templates/mesh-gateway-serviceaccount.yaml index 8c2da5ae06..4ba2b6788d 100644 --- a/charts/consul/templates/mesh-gateway-serviceaccount.yaml +++ b/charts/consul/templates/mesh-gateway-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.meshGateway.enabled }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index db73ef783b..00a500f519 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled) (ne .Values.global.adminPartitions.name "default")) }} {{- template "consul.reservedNamesFailer" (list .Values.global.adminPartitions.name "global.adminPartitions.name") }} diff --git a/charts/consul/templates/partition-init-podsecuritypolicy.yaml b/charts/consul/templates/partition-init-podsecuritypolicy.yaml index 2bc6782394..4cf3f3e038 100644 --- a/charts/consul/templates/partition-init-podsecuritypolicy.yaml +++ b/charts/consul/templates/partition-init-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled .Values.global.enablePodSecurityPolicies (not $serverEnabled)) }} apiVersion: policy/v1beta1 diff --git a/charts/consul/templates/partition-init-role.yaml b/charts/consul/templates/partition-init-role.yaml index c13a5378eb..592c10e413 100644 --- a/charts/consul/templates/partition-init-role.yaml +++ b/charts/consul/templates/partition-init-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/partition-init-rolebinding.yaml b/charts/consul/templates/partition-init-rolebinding.yaml index 432d6df6ec..e70b8d63ba 100644 --- a/charts/consul/templates/partition-init-rolebinding.yaml +++ b/charts/consul/templates/partition-init-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/partition-init-serviceaccount.yaml b/charts/consul/templates/partition-init-serviceaccount.yaml index 65fcf43b08..5352b5aea6 100644 --- a/charts/consul/templates/partition-init-serviceaccount.yaml +++ b/charts/consul/templates/partition-init-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} apiVersion: v1 diff --git a/charts/consul/templates/partition-name-configmap.yaml b/charts/consul/templates/partition-name-configmap.yaml index ee330b0f46..bc3e4566c3 100644 --- a/charts/consul/templates/partition-name-configmap.yaml +++ b/charts/consul/templates/partition-name-configmap.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} # Immutable ConfigMap which saves the partition name. Attempting to update this configmap diff --git a/charts/consul/templates/prometheus.yaml b/charts/consul/templates/prometheus.yaml index 4dcede1745..670fd76946 100644 --- a/charts/consul/templates/prometheus.yaml +++ b/charts/consul/templates/prometheus.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.prometheus.enabled }} # This file is auto-generated, see addons/gen.sh --- diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 35b0877ab4..63c509d883 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/server-acl-init-cleanup-podsecuritypolicy.yaml index dd5dad24df..1ea8ce28b4 100644 --- a/charts/consul/templates/server-acl-init-cleanup-podsecuritypolicy.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-cleanup-role.yaml b/charts/consul/templates/server-acl-init-cleanup-role.yaml index 0a2f296a60..e18200adfe 100644 --- a/charts/consul/templates/server-acl-init-cleanup-role.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-cleanup-rolebinding.yaml b/charts/consul/templates/server-acl-init-cleanup-rolebinding.yaml index 268eaa5677..c2365d63bd 100644 --- a/charts/consul/templates/server-acl-init-cleanup-rolebinding.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-cleanup-serviceaccount.yaml b/charts/consul/templates/server-acl-init-cleanup-serviceaccount.yaml index 604e6d784c..030bd38516 100644 --- a/charts/consul/templates/server-acl-init-cleanup-serviceaccount.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index e62db41ec2..3e42ce553b 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and $serverEnabled .Values.externalServers.enabled) }}{{ fail "only one of server.enabled or externalServers.enabled can be set" }}{{ end -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} diff --git a/charts/consul/templates/server-acl-init-podsecuritypolicy.yaml b/charts/consul/templates/server-acl-init-podsecuritypolicy.yaml index 9bf93e2551..aa35d9ba07 100644 --- a/charts/consul/templates/server-acl-init-podsecuritypolicy.yaml +++ b/charts/consul/templates/server-acl-init-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-role.yaml b/charts/consul/templates/server-acl-init-role.yaml index eb7b6a928e..d165c34492 100644 --- a/charts/consul/templates/server-acl-init-role.yaml +++ b/charts/consul/templates/server-acl-init-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-rolebinding.yaml b/charts/consul/templates/server-acl-init-rolebinding.yaml index fda4726d9f..ff125f2c5e 100644 --- a/charts/consul/templates/server-acl-init-rolebinding.yaml +++ b/charts/consul/templates/server-acl-init-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-serviceaccount.yaml b/charts/consul/templates/server-acl-init-serviceaccount.yaml index c0e257de96..1b0129d168 100644 --- a/charts/consul/templates/server-acl-init-serviceaccount.yaml +++ b/charts/consul/templates/server-acl-init-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index f7dd85f166..6ae8037606 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} # StatefulSet to run the actual Consul server cluster. apiVersion: v1 diff --git a/charts/consul/templates/server-disruptionbudget.yaml b/charts/consul/templates/server-disruptionbudget.yaml index edf9c1c57f..e4806c21f9 100644 --- a/charts/consul/templates/server-disruptionbudget.yaml +++ b/charts/consul/templates/server-disruptionbudget.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.server.disruptionBudget.enabled (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled))) }} # PodDisruptionBudget to prevent degrading the server cluster through # voluntary cluster changes. diff --git a/charts/consul/templates/server-podsecuritypolicy.yaml b/charts/consul/templates/server-podsecuritypolicy.yaml index 09e8d75bd1..4049679043 100644 --- a/charts/consul/templates/server-podsecuritypolicy.yaml +++ b/charts/consul/templates/server-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/server-role.yaml b/charts/consul/templates/server-role.yaml index 202518bf67..a3d432c299 100644 --- a/charts/consul/templates/server-role.yaml +++ b/charts/consul/templates/server-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/server-rolebinding.yaml b/charts/consul/templates/server-rolebinding.yaml index 8ab705ddbc..c317cab99d 100644 --- a/charts/consul/templates/server-rolebinding.yaml +++ b/charts/consul/templates/server-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/server-securitycontextconstraints.yaml b/charts/consul/templates/server-securitycontextconstraints.yaml index 8edd784ea7..593a842ec2 100644 --- a/charts/consul/templates/server-securitycontextconstraints.yaml +++ b/charts/consul/templates/server-securitycontextconstraints.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.global.openshift.enabled .Values.server.exposeGossipAndRPCPorts (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints diff --git a/charts/consul/templates/server-service.yaml b/charts/consul/templates/server-service.yaml index a392f0e76b..e3ae931a3a 100644 --- a/charts/consul/templates/server-service.yaml +++ b/charts/consul/templates/server-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} # Headless service for Consul server DNS entries. This service should only # point to Consul servers. For access to an agent, one should assume that diff --git a/charts/consul/templates/server-serviceaccount.yaml b/charts/consul/templates/server-serviceaccount.yaml index a1617975ae..ff8cc2022a 100644 --- a/charts/consul/templates/server-serviceaccount.yaml +++ b/charts/consul/templates/server-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/server-snapshot-agent-configmap.yaml b/charts/consul/templates/server-snapshot-agent-configmap.yaml index da68d1509c..cff5e0d840 100644 --- a/charts/consul/templates/server-snapshot-agent-configmap.yaml +++ b/charts/consul/templates/server-snapshot-agent-configmap.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.server.snapshotAgent.enabled }} apiVersion: v1 kind: ConfigMap diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 8b73306fd7..e389509d88 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if and .Values.global.federation.enabled .Values.global.adminPartitions.enabled }}{{ fail "If global.federation.enabled is true, global.adminPartitions.enabled must be false because they are mutually exclusive" }}{{ end }} {{- if and .Values.global.federation.enabled (not .Values.global.tls.enabled) }}{{ fail "If global.federation.enabled is true, global.tls.enabled must be true because federation is only supported with TLS enabled" }}{{ end }} diff --git a/charts/consul/templates/sync-catalog-clusterrole.yaml b/charts/consul/templates/sync-catalog-clusterrole.yaml index 0b0837c0df..5055530f6e 100644 --- a/charts/consul/templates/sync-catalog-clusterrole.yaml +++ b/charts/consul/templates/sync-catalog-clusterrole.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $syncEnabled := (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} {{- if $syncEnabled }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/sync-catalog-clusterrolebinding.yaml b/charts/consul/templates/sync-catalog-clusterrolebinding.yaml index 818823cca3..f5e3e0a98a 100644 --- a/charts/consul/templates/sync-catalog-clusterrolebinding.yaml +++ b/charts/consul/templates/sync-catalog-clusterrolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $syncEnabled := (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} {{- if $syncEnabled }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index f2815d9627..94d97fcfc1 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} {{- template "consul.reservedNamesFailer" (list .Values.syncCatalog.consulNamespaces.consulDestinationNamespace "syncCatalog.consulNamespaces.consulDestinationNamespace") }} {{ template "consul.validateRequiredCloudSecretsExist" . }} diff --git a/charts/consul/templates/sync-catalog-podsecuritypolicy.yaml b/charts/consul/templates/sync-catalog-podsecuritypolicy.yaml index cc70feaab1..0737265591 100644 --- a/charts/consul/templates/sync-catalog-podsecuritypolicy.yaml +++ b/charts/consul/templates/sync-catalog-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/sync-catalog-serviceaccount.yaml b/charts/consul/templates/sync-catalog-serviceaccount.yaml index deab1ad075..a5b6023956 100644 --- a/charts/consul/templates/sync-catalog-serviceaccount.yaml +++ b/charts/consul/templates/sync-catalog-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- $syncEnabled := (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} {{- if $syncEnabled }} apiVersion: v1 diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index 2f2cb9a921..2fcc27448f 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.terminatingGateways.enabled }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} diff --git a/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml b/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml index 97ad2af961..a529f902bd 100644 --- a/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml +++ b/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and .Values.global.enablePodSecurityPolicies .Values.terminatingGateways.enabled) }} {{- $root := . }} {{- range .Values.terminatingGateways.gateways }} diff --git a/charts/consul/templates/terminating-gateways-role.yaml b/charts/consul/templates/terminating-gateways-role.yaml index 4ae280ca81..71e3cd0e1e 100644 --- a/charts/consul/templates/terminating-gateways-role.yaml +++ b/charts/consul/templates/terminating-gateways-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.terminatingGateways.enabled }} {{- $root := . }} diff --git a/charts/consul/templates/terminating-gateways-rolebinding.yaml b/charts/consul/templates/terminating-gateways-rolebinding.yaml index 4271f8f59c..cf6d533e91 100644 --- a/charts/consul/templates/terminating-gateways-rolebinding.yaml +++ b/charts/consul/templates/terminating-gateways-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.terminatingGateways.enabled }} {{- $root := . }} {{- range .Values.terminatingGateways.gateways }} diff --git a/charts/consul/templates/terminating-gateways-service.yaml b/charts/consul/templates/terminating-gateways-service.yaml index 124900e727..8515897037 100644 --- a/charts/consul/templates/terminating-gateways-service.yaml +++ b/charts/consul/templates/terminating-gateways-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.terminatingGateways.enabled }} {{- $root := . }} diff --git a/charts/consul/templates/terminating-gateways-serviceaccount.yaml b/charts/consul/templates/terminating-gateways-serviceaccount.yaml index 211fb5c72f..47f65fb937 100644 --- a/charts/consul/templates/terminating-gateways-serviceaccount.yaml +++ b/charts/consul/templates/terminating-gateways-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.terminatingGateways.enabled }} {{- $root := . }} {{- $defaults := .Values.terminatingGateways.defaults }} diff --git a/charts/consul/templates/tests/test-runner.yaml b/charts/consul/templates/tests/test-runner.yaml index b8b078003b..985605c2e5 100644 --- a/charts/consul/templates/tests/test-runner.yaml +++ b/charts/consul/templates/tests/test-runner.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if .Values.tests.enabled }} apiVersion: v1 kind: Pod diff --git a/charts/consul/templates/tls-init-cleanup-job.yaml b/charts/consul/templates/tls-init-cleanup-job.yaml index ba29bb84ae..cfc389ca44 100644 --- a/charts/consul/templates/tls-init-cleanup-job.yaml +++ b/charts/consul/templates/tls-init-cleanup-job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/tls-init-cleanup-podsecuritypolicy.yaml index ed99d5f297..db28affcc2 100644 --- a/charts/consul/templates/tls-init-cleanup-podsecuritypolicy.yaml +++ b/charts/consul/templates/tls-init-cleanup-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and (and .Values.global.tls.enabled .Values.global.enablePodSecurityPolicies) (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-cleanup-role.yaml b/charts/consul/templates/tls-init-cleanup-role.yaml index aa66e3edc4..00a70aee17 100644 --- a/charts/consul/templates/tls-init-cleanup-role.yaml +++ b/charts/consul/templates/tls-init-cleanup-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-cleanup-rolebinding.yaml b/charts/consul/templates/tls-init-cleanup-rolebinding.yaml index 0d3bfe38e7..a2abcc4b71 100644 --- a/charts/consul/templates/tls-init-cleanup-rolebinding.yaml +++ b/charts/consul/templates/tls-init-cleanup-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-cleanup-serviceaccount.yaml b/charts/consul/templates/tls-init-cleanup-serviceaccount.yaml index 57e40dd3af..60f1e7402f 100644 --- a/charts/consul/templates/tls-init-cleanup-serviceaccount.yaml +++ b/charts/consul/templates/tls-init-cleanup-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index d002ae7a75..d27e31d44b 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-podsecuritypolicy.yaml b/charts/consul/templates/tls-init-podsecuritypolicy.yaml index 5d2a393955..936097cee9 100644 --- a/charts/consul/templates/tls-init-podsecuritypolicy.yaml +++ b/charts/consul/templates/tls-init-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and (and .Values.global.tls.enabled .Values.global.enablePodSecurityPolicies) (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-role.yaml b/charts/consul/templates/tls-init-role.yaml index 216602ee9f..49a15ad479 100644 --- a/charts/consul/templates/tls-init-role.yaml +++ b/charts/consul/templates/tls-init-role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-rolebinding.yaml b/charts/consul/templates/tls-init-rolebinding.yaml index 9b68d97d8c..3217a80909 100644 --- a/charts/consul/templates/tls-init-rolebinding.yaml +++ b/charts/consul/templates/tls-init-rolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-serviceaccount.yaml b/charts/consul/templates/tls-init-serviceaccount.yaml index f8504da94c..6f3b9407b1 100644 --- a/charts/consul/templates/tls-init-serviceaccount.yaml +++ b/charts/consul/templates/tls-init-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/ui-ingress.yaml b/charts/consul/templates/ui-ingress.yaml index 0414a7cc2d..b1b5dbc8fa 100644 --- a/charts/consul/templates/ui-ingress.yaml +++ b/charts/consul/templates/ui-ingress.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) (or (and (ne (.Values.ui.enabled | toString) "-") .Values.ui.enabled) (and (eq (.Values.ui.enabled | toString) "-") .Values.global.enabled)) (or (and (ne (.Values.ui.service.enabled | toString) "-") .Values.ui.service.enabled) (and (eq (.Values.ui.service.enabled | toString) "-") .Values.global.enabled))) }} {{- if (and (ne (.Values.ui.ingress.enabled | toString) "-") .Values.ui.ingress.enabled) }} {{- $serviceName := printf "%s-%s" (include "consul.fullname" .) "ui" -}} diff --git a/charts/consul/templates/ui-service.yaml b/charts/consul/templates/ui-service.yaml index dc2abf4fc5..6a8045082e 100644 --- a/charts/consul/templates/ui-service.yaml +++ b/charts/consul/templates/ui-service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{- if (and (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) (or (and (ne (.Values.ui.enabled | toString) "-") .Values.ui.enabled) (and (eq (.Values.ui.enabled | toString) "-") .Values.global.enabled)) (or (and (ne (.Values.ui.service.enabled | toString) "-") .Values.ui.service.enabled) (and (eq (.Values.ui.service.enabled | toString) "-") .Values.global.enabled))) }} # UI Service for Consul Server apiVersion: v1 diff --git a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml index e13e2dc741..d7b89db85e 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml index 472ef4ee1d..fa9b6f46c5 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/webhook-cert-manager-configmap.yaml b/charts/consul/templates/webhook-cert-manager-configmap.yaml index 293dd32d9f..c4b747a795 100644 --- a/charts/consul/templates/webhook-cert-manager-configmap.yaml +++ b/charts/consul/templates/webhook-cert-manager-configmap.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index dd93c039d2..558a5d43d5 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: apps/v1 diff --git a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml index 4d685edc39..d4e5413d52 100644 --- a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml +++ b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled))) }} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} diff --git a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml index 68c54f3c27..c4f594945f 100644 --- a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml +++ b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 diff --git a/charts/consul/test/docker/Test.dockerfile b/charts/consul/test/docker/Test.dockerfile index 85f3a607e3..e6a4caa6e0 100644 --- a/charts/consul/test/docker/Test.dockerfile +++ b/charts/consul/test/docker/Test.dockerfile @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # This Dockerfile installs all the dependencies necessary to run the unit and # acceptance tests. This image also contains gcloud so you can run tests # against a GKE cluster easily. diff --git a/charts/consul/test/terraform/aks/main.tf b/charts/consul/test/terraform/aks/main.tf index 1db5145531..d077471ec9 100644 --- a/charts/consul/test/terraform/aks/main.tf +++ b/charts/consul/test/terraform/aks/main.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + provider "azurerm" { version = "3.40.0" features {} diff --git a/charts/consul/test/terraform/aks/outputs.tf b/charts/consul/test/terraform/aks/outputs.tf index 9ba75d10f8..2fc01b8a1d 100644 --- a/charts/consul/test/terraform/aks/outputs.tf +++ b/charts/consul/test/terraform/aks/outputs.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + output "kubeconfigs" { value = local_file.kubeconfigs.*.filename } \ No newline at end of file diff --git a/charts/consul/test/terraform/aks/variables.tf b/charts/consul/test/terraform/aks/variables.tf index bb9dbef537..554d1b0965 100644 --- a/charts/consul/test/terraform/aks/variables.tf +++ b/charts/consul/test/terraform/aks/variables.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + variable "location" { default = "West US 2" description = "The location to launch this AKS cluster in." diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index ca48a5a8fe..07c58a2705 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + provider "aws" { version = ">= 2.28.1" region = var.region diff --git a/charts/consul/test/terraform/eks/outputs.tf b/charts/consul/test/terraform/eks/outputs.tf index 7e88046b1b..1d971bf0b5 100644 --- a/charts/consul/test/terraform/eks/outputs.tf +++ b/charts/consul/test/terraform/eks/outputs.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + output "kubeconfigs" { value = [for cl in module.eks : pathexpand(format("~/.kube/%s", cl.cluster_id))] } diff --git a/charts/consul/test/terraform/eks/variables.tf b/charts/consul/test/terraform/eks/variables.tf index 05f383168b..548cddeb33 100644 --- a/charts/consul/test/terraform/eks/variables.tf +++ b/charts/consul/test/terraform/eks/variables.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + variable "region" { default = "us-west-2" description = "AWS region" diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index 1bd574ce2c..f8ff19b912 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + provider "google" { project = var.project version = "~> 3.49.0" diff --git a/charts/consul/test/terraform/gke/outputs.tf b/charts/consul/test/terraform/gke/outputs.tf index 95b68d0296..a0ffac907f 100644 --- a/charts/consul/test/terraform/gke/outputs.tf +++ b/charts/consul/test/terraform/gke/outputs.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + output "cluster_ids" { value = google_container_cluster.cluster.*.id } diff --git a/charts/consul/test/terraform/gke/variables.tf b/charts/consul/test/terraform/gke/variables.tf index ef4a429116..1eebe64145 100644 --- a/charts/consul/test/terraform/gke/variables.tf +++ b/charts/consul/test/terraform/gke/variables.tf @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + variable "project" { description = < .' to build one. # diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 7c761b6477..2c579ba715 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package common holds code that isn't tied to a particular CRD version or type. package common diff --git a/control-plane/api/common/configentry.go b/control-plane/api/common/configentry.go index 2d83ce05b0..3559d8a6bc 100644 --- a/control-plane/api/common/configentry.go +++ b/control-plane/api/common/configentry.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import ( diff --git a/control-plane/api/common/configentry_webhook.go b/control-plane/api/common/configentry_webhook.go index 4028a5e3cb..4b8a482d6c 100644 --- a/control-plane/api/common/configentry_webhook.go +++ b/control-plane/api/common/configentry_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import ( diff --git a/control-plane/api/common/configentry_webhook_test.go b/control-plane/api/common/configentry_webhook_test.go index cf79efea85..07f24cff20 100644 --- a/control-plane/api/common/configentry_webhook_test.go +++ b/control-plane/api/common/configentry_webhook_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import ( diff --git a/control-plane/api/v1alpha1/exportedservices_types.go b/control-plane/api/v1alpha1/exportedservices_types.go index e05f17a177..e4df1cee0d 100644 --- a/control-plane/api/v1alpha1/exportedservices_types.go +++ b/control-plane/api/v1alpha1/exportedservices_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/exportedservices_types_test.go b/control-plane/api/v1alpha1/exportedservices_types_test.go index 8826166a76..e94eb36cbe 100644 --- a/control-plane/api/v1alpha1/exportedservices_types_test.go +++ b/control-plane/api/v1alpha1/exportedservices_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/exportedservices_webhook.go b/control-plane/api/v1alpha1/exportedservices_webhook.go index 5a3d2cb2f1..6c870427ac 100644 --- a/control-plane/api/v1alpha1/exportedservices_webhook.go +++ b/control-plane/api/v1alpha1/exportedservices_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/exportedservices_webhook_test.go b/control-plane/api/v1alpha1/exportedservices_webhook_test.go index 6548c131f7..41476bd822 100644 --- a/control-plane/api/v1alpha1/exportedservices_webhook_test.go +++ b/control-plane/api/v1alpha1/exportedservices_webhook_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/groupversion_info.go b/control-plane/api/v1alpha1/groupversion_info.go index cdbe085af4..3657e9b048 100644 --- a/control-plane/api/v1alpha1/groupversion_info.go +++ b/control-plane/api/v1alpha1/groupversion_info.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package v1alpha1 contains API Schema definitions for the consul.hashicorp.com v1alpha1 API group // +kubebuilder:object:generate=true // +groupName=consul.hashicorp.com diff --git a/control-plane/api/v1alpha1/ingressgateway_types.go b/control-plane/api/v1alpha1/ingressgateway_types.go index c94b6e1458..64e024fbd5 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types.go +++ b/control-plane/api/v1alpha1/ingressgateway_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/ingressgateway_types_test.go b/control-plane/api/v1alpha1/ingressgateway_types_test.go index 4942d38e11..dd1c3835e0 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types_test.go +++ b/control-plane/api/v1alpha1/ingressgateway_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/ingressgateway_webhook.go b/control-plane/api/v1alpha1/ingressgateway_webhook.go index 7f8ba37558..04e31a0a3e 100644 --- a/control-plane/api/v1alpha1/ingressgateway_webhook.go +++ b/control-plane/api/v1alpha1/ingressgateway_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/mesh_types.go b/control-plane/api/v1alpha1/mesh_types.go index 502e567829..9a2df631f2 100644 --- a/control-plane/api/v1alpha1/mesh_types.go +++ b/control-plane/api/v1alpha1/mesh_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/mesh_types_test.go b/control-plane/api/v1alpha1/mesh_types_test.go index 392c38d354..e20ce19d47 100644 --- a/control-plane/api/v1alpha1/mesh_types_test.go +++ b/control-plane/api/v1alpha1/mesh_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/mesh_webhook.go b/control-plane/api/v1alpha1/mesh_webhook.go index 5c714c4e5f..1c0ea3088e 100644 --- a/control-plane/api/v1alpha1/mesh_webhook.go +++ b/control-plane/api/v1alpha1/mesh_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/mesh_webhook_test.go b/control-plane/api/v1alpha1/mesh_webhook_test.go index 55b0c3a77d..def1ebf54f 100644 --- a/control-plane/api/v1alpha1/mesh_webhook_test.go +++ b/control-plane/api/v1alpha1/mesh_webhook_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringacceptor_types.go b/control-plane/api/v1alpha1/peeringacceptor_types.go index 032870cb80..e1ca013475 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_types.go +++ b/control-plane/api/v1alpha1/peeringacceptor_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringacceptor_types_test.go b/control-plane/api/v1alpha1/peeringacceptor_types_test.go index 33d437f46a..d7c05e7e2c 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_types_test.go +++ b/control-plane/api/v1alpha1/peeringacceptor_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringacceptor_webhook.go b/control-plane/api/v1alpha1/peeringacceptor_webhook.go index 60367c1384..2bb48b3580 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_webhook.go +++ b/control-plane/api/v1alpha1/peeringacceptor_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go index a65966881a..b97f7cf97c 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringdialer_types.go b/control-plane/api/v1alpha1/peeringdialer_types.go index 4ddde3fe2f..89c16bf3ad 100644 --- a/control-plane/api/v1alpha1/peeringdialer_types.go +++ b/control-plane/api/v1alpha1/peeringdialer_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringdialer_types_test.go b/control-plane/api/v1alpha1/peeringdialer_types_test.go index 7e358facb8..69c3569acd 100644 --- a/control-plane/api/v1alpha1/peeringdialer_types_test.go +++ b/control-plane/api/v1alpha1/peeringdialer_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringdialer_webhook.go b/control-plane/api/v1alpha1/peeringdialer_webhook.go index fc0b1c38f6..76c2011e4c 100644 --- a/control-plane/api/v1alpha1/peeringdialer_webhook.go +++ b/control-plane/api/v1alpha1/peeringdialer_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go index e8b206e3e6..62b941d6a1 100644 --- a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index 543d8f7ae4..8a211aed56 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 225068c136..011ab7d724 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/proxydefaults_webhook.go b/control-plane/api/v1alpha1/proxydefaults_webhook.go index 3873516074..ced4853b12 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go index 3136540089..9ba6241e57 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 06da8b1d2c..304bce2db6 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 33ec6d2f40..9f9e7ebbda 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicedefaults_webhook.go b/control-plane/api/v1alpha1/servicedefaults_webhook.go index f79e68bcde..124aeff5f6 100644 --- a/control-plane/api/v1alpha1/servicedefaults_webhook.go +++ b/control-plane/api/v1alpha1/servicedefaults_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index a0a240639a..82a5dcfdc8 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceintentions_types_test.go b/control-plane/api/v1alpha1/serviceintentions_types_test.go index f9be4120f2..99b391039c 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceintentions_webhook.go b/control-plane/api/v1alpha1/serviceintentions_webhook.go index ddc6488690..fef2401fb3 100644 --- a/control-plane/api/v1alpha1/serviceintentions_webhook.go +++ b/control-plane/api/v1alpha1/serviceintentions_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go index e6095e8351..c6934557c8 100644 --- a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 4fc637b35f..58711dfde6 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index fd4fc25a60..b3c552172f 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceresolver_webhook.go b/control-plane/api/v1alpha1/serviceresolver_webhook.go index ca5f9d9482..88996e2f8d 100644 --- a/control-plane/api/v1alpha1/serviceresolver_webhook.go +++ b/control-plane/api/v1alpha1/serviceresolver_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicerouter_types.go b/control-plane/api/v1alpha1/servicerouter_types.go index 9f8f7fc3fd..d5baf37368 100644 --- a/control-plane/api/v1alpha1/servicerouter_types.go +++ b/control-plane/api/v1alpha1/servicerouter_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicerouter_types_test.go b/control-plane/api/v1alpha1/servicerouter_types_test.go index eb0568db81..d0919871ce 100644 --- a/control-plane/api/v1alpha1/servicerouter_types_test.go +++ b/control-plane/api/v1alpha1/servicerouter_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicerouter_webhook.go b/control-plane/api/v1alpha1/servicerouter_webhook.go index f6837fcf7b..cdcc3ba439 100644 --- a/control-plane/api/v1alpha1/servicerouter_webhook.go +++ b/control-plane/api/v1alpha1/servicerouter_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicesplitter_types.go b/control-plane/api/v1alpha1/servicesplitter_types.go index b61b1a320b..d94dbb7120 100644 --- a/control-plane/api/v1alpha1/servicesplitter_types.go +++ b/control-plane/api/v1alpha1/servicesplitter_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicesplitter_types_test.go b/control-plane/api/v1alpha1/servicesplitter_types_test.go index 48e9eeac54..e2ade99f1c 100644 --- a/control-plane/api/v1alpha1/servicesplitter_types_test.go +++ b/control-plane/api/v1alpha1/servicesplitter_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicesplitter_webhook.go b/control-plane/api/v1alpha1/servicesplitter_webhook.go index c0020c88b8..42dbbbf54a 100644 --- a/control-plane/api/v1alpha1/servicesplitter_webhook.go +++ b/control-plane/api/v1alpha1/servicesplitter_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index edcaba5a46..28d53b8926 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/status.go b/control-plane/api/v1alpha1/status.go index d7cd0e0b78..0e11bf930b 100644 --- a/control-plane/api/v1alpha1/status.go +++ b/control-plane/api/v1alpha1/status.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/terminatinggateway_types.go b/control-plane/api/v1alpha1/terminatinggateway_types.go index 6e708b5d44..cf453160ff 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_types.go +++ b/control-plane/api/v1alpha1/terminatinggateway_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/terminatinggateway_types_test.go b/control-plane/api/v1alpha1/terminatinggateway_types_test.go index 9d8ba9948d..2daf93c6a4 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_types_test.go +++ b/control-plane/api/v1alpha1/terminatinggateway_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/terminatinggateway_webhook.go b/control-plane/api/v1alpha1/terminatinggateway_webhook.go index b0427b87ca..481a1a1f15 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_webhook.go +++ b/control-plane/api/v1alpha1/terminatinggateway_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/build-support/functions/00-vars.sh b/control-plane/build-support/functions/00-vars.sh index 1f03013c32..484344703c 100644 --- a/control-plane/build-support/functions/00-vars.sh +++ b/control-plane/build-support/functions/00-vars.sh @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # GPG Key ID to use for publically released builds HASHICORP_GPG_KEY="348FFC4C" diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index 8367c22743..26c43cb610 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + function err { if test "${COLORIZE}" -eq 1 then diff --git a/control-plane/build-support/functions/20-build.sh b/control-plane/build-support/functions/20-build.sh index ddde7b6acf..a4f36ee3e4 100644 --- a/control-plane/build-support/functions/20-build.sh +++ b/control-plane/build-support/functions/20-build.sh @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + function refresh_docker_images { # Arguments: # $1 - Path to top level Consul source diff --git a/control-plane/build-support/functions/40-publish.sh b/control-plane/build-support/functions/40-publish.sh index 975c835bc0..aae9a5f719 100644 --- a/control-plane/build-support/functions/40-publish.sh +++ b/control-plane/build-support/functions/40-publish.sh @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + function hashicorp_release { # Arguments: # $1 - Path to directory containing all of the release artifacts diff --git a/control-plane/build-support/scripts/build-local.sh b/control-plane/build-support/scripts/build-local.sh index 95d18e0ba6..453310b0b7 100755 --- a/control-plane/build-support/scripts/build-local.sh +++ b/control-plane/build-support/scripts/build-local.sh @@ -1,4 +1,7 @@ #!/bin/bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + SCRIPT_NAME="$(basename ${BASH_SOURCE[0]})" pushd $(dirname ${BASH_SOURCE[0]}) > /dev/null SCRIPT_DIR=$(pwd) diff --git a/control-plane/build-support/scripts/functions.sh b/control-plane/build-support/scripts/functions.sh index 0301c0d3a1..590666eb7d 100644 --- a/control-plane/build-support/scripts/functions.sh +++ b/control-plane/build-support/scripts/functions.sh @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # # NOTE: This file is meant to be sourced from other bash scripts/shells # diff --git a/control-plane/build-support/scripts/terraformfmtcheck.sh b/control-plane/build-support/scripts/terraformfmtcheck.sh index 0f962a1c1b..8608a88e30 100755 --- a/control-plane/build-support/scripts/terraformfmtcheck.sh +++ b/control-plane/build-support/scripts/terraformfmtcheck.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # Check terraform fmt echo "==> Checking that code complies with terraform fmt requirements..." diff --git a/control-plane/build-support/scripts/version.sh b/control-plane/build-support/scripts/version.sh index f91b0c3917..fce325e03c 100755 --- a/control-plane/build-support/scripts/version.sh +++ b/control-plane/build-support/scripts/version.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + version_file=$1 version=$(awk '$1 == "Version" && $2 == "=" { gsub(/"/, "", $3); print $3 }' < "${version_file}") diff --git a/control-plane/catalog/to-consul/annotation.go b/control-plane/catalog/to-consul/annotation.go index 5cd6023181..27e37ca217 100644 --- a/control-plane/catalog/to-consul/annotation.go +++ b/control-plane/catalog/to-consul/annotation.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog const ( diff --git a/control-plane/catalog/to-consul/resource.go b/control-plane/catalog/to-consul/resource.go index 09d8aa6c5d..e25257c8f8 100644 --- a/control-plane/catalog/to-consul/resource.go +++ b/control-plane/catalog/to-consul/resource.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-consul/resource_test.go b/control-plane/catalog/to-consul/resource_test.go index 9ba94123ef..d75f03d692 100644 --- a/control-plane/catalog/to-consul/resource_test.go +++ b/control-plane/catalog/to-consul/resource_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-consul/service_id.go b/control-plane/catalog/to-consul/service_id.go index 1aa3071497..8300871b73 100644 --- a/control-plane/catalog/to-consul/service_id.go +++ b/control-plane/catalog/to-consul/service_id.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-consul/syncer.go b/control-plane/catalog/to-consul/syncer.go index 19e0aaca6f..9f1df18ba6 100644 --- a/control-plane/catalog/to-consul/syncer.go +++ b/control-plane/catalog/to-consul/syncer.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-consul/syncer_ent_test.go b/control-plane/catalog/to-consul/syncer_ent_test.go index fbe2cbd494..5dfb158d23 100644 --- a/control-plane/catalog/to-consul/syncer_ent_test.go +++ b/control-plane/catalog/to-consul/syncer_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package catalog diff --git a/control-plane/catalog/to-consul/syncer_test.go b/control-plane/catalog/to-consul/syncer_test.go index d8d9b0f402..3fae7a3d16 100644 --- a/control-plane/catalog/to-consul/syncer_test.go +++ b/control-plane/catalog/to-consul/syncer_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-consul/testing.go b/control-plane/catalog/to-consul/testing.go index e6541c6ba1..5f19017cbe 100644 --- a/control-plane/catalog/to-consul/testing.go +++ b/control-plane/catalog/to-consul/testing.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-k8s/sink.go b/control-plane/catalog/to-k8s/sink.go index fa8821989e..6e201253df 100644 --- a/control-plane/catalog/to-k8s/sink.go +++ b/control-plane/catalog/to-k8s/sink.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-k8s/sink_test.go b/control-plane/catalog/to-k8s/sink_test.go index fbce7bbaaf..cfba502268 100644 --- a/control-plane/catalog/to-k8s/sink_test.go +++ b/control-plane/catalog/to-k8s/sink_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-k8s/source.go b/control-plane/catalog/to-k8s/source.go index 5a384e760a..ab34089d8e 100644 --- a/control-plane/catalog/to-k8s/source.go +++ b/control-plane/catalog/to-k8s/source.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-k8s/source_test.go b/control-plane/catalog/to-k8s/source_test.go index ca00a1e954..66afb4b608 100644 --- a/control-plane/catalog/to-k8s/source_test.go +++ b/control-plane/catalog/to-k8s/source_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/catalog/to-k8s/testing.go b/control-plane/catalog/to-k8s/testing.go index d7181bdbea..1eb731a17f 100644 --- a/control-plane/catalog/to-k8s/testing.go +++ b/control-plane/catalog/to-k8s/testing.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package catalog import ( diff --git a/control-plane/cni/config/config.go b/control-plane/cni/config/config.go index f22d3ff79b..a9dafd0ab7 100644 --- a/control-plane/cni/config/config.go +++ b/control-plane/cni/config/config.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package config const ( diff --git a/control-plane/cni/main.go b/control-plane/cni/main.go index 7b05b5c6cd..e35f5ad811 100644 --- a/control-plane/cni/main.go +++ b/control-plane/cni/main.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main import ( diff --git a/control-plane/cni/main_test.go b/control-plane/cni/main_test.go index 740e15c646..7c289a9825 100644 --- a/control-plane/cni/main_test.go +++ b/control-plane/cni/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main import ( diff --git a/control-plane/commands.go b/control-plane/commands.go index ec3b7ca612..4b7cbed362 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main import ( diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index da1a66fd74..6352ac3af1 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index 16ac322090..fd8ebc86ff 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition 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 7ad173afbf..4850ad152e 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index e782ef472f..50df179f04 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index d5103252a5..01e4363f14 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 2563cbcf77..5c8d4a5082 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 4f335a923d..7744a8fe7a 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition 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 a0cc7a6343..8e186af1a7 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index a84fc0bd88..9bd5096376 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index de071bd0ef..5b9c9d3c1f 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index df8bbbfbdf..aa2b592c94 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index 8e6c449ef8..b465cd9494 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 8daf050ec0..245f09568f 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index c1ffb5e0fe..d064b50acb 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 9611797c9f..67182e6d0a 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import ( diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index e43ccf8255..3f995e2874 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import ( diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 637e028202..fa5c7da26c 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package constants const ( diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index e371677629..30b7cddac0 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package constants const ( diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go index f54fb71d11..c71ee9ba55 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package endpoints import ( diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go index 189587106d..8c74b4f718 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package endpoints import ( diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 800df9cc24..f408a21ea1 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package endpoints import ( diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 4303bafbc8..5c87c2442d 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package endpoints diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 63cde6404b..19c78925a6 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package endpoints import ( diff --git a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go index 044de55998..3a1251aae6 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package peering import ( diff --git a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go index 15a3740816..f3b3e6a844 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package peering import ( diff --git a/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go b/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go index 98646b1654..37de792cb6 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package peering import ( diff --git a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go index ba33afd765..e211fe856e 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package peering import ( diff --git a/control-plane/connect-inject/metrics/metrics_configuration.go b/control-plane/connect-inject/metrics/metrics_configuration.go index 651fb87184..f5b819af3d 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration.go +++ b/control-plane/connect-inject/metrics/metrics_configuration.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package metrics import ( diff --git a/control-plane/connect-inject/metrics/metrics_configuration_test.go b/control-plane/connect-inject/metrics/metrics_configuration_test.go index 2f41b05744..ec19d4f55a 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration_test.go +++ b/control-plane/connect-inject/metrics/metrics_configuration_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package metrics import ( diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index ad3333ba1b..70f3be82da 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index 37aa1619bf..f7cb7bc594 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/container_env.go b/control-plane/connect-inject/webhook/container_env.go index 7c65dcd77c..2b2d7f5471 100644 --- a/control-plane/connect-inject/webhook/container_env.go +++ b/control-plane/connect-inject/webhook/container_env.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/container_env_test.go b/control-plane/connect-inject/webhook/container_env_test.go index 62200e7bbd..50d38832c0 100644 --- a/control-plane/connect-inject/webhook/container_env_test.go +++ b/control-plane/connect-inject/webhook/container_env_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/container_init.go b/control-plane/connect-inject/webhook/container_init.go index 328882bc04..b33c8f4d3e 100644 --- a/control-plane/connect-inject/webhook/container_init.go +++ b/control-plane/connect-inject/webhook/container_init.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/container_init_test.go b/control-plane/connect-inject/webhook/container_init_test.go index 8e0b551b24..0e2de79bb1 100644 --- a/control-plane/connect-inject/webhook/container_init_test.go +++ b/control-plane/connect-inject/webhook/container_init_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/container_volume.go b/control-plane/connect-inject/webhook/container_volume.go index b33567469b..76ba461c7b 100644 --- a/control-plane/connect-inject/webhook/container_volume.go +++ b/control-plane/connect-inject/webhook/container_volume.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/dns.go b/control-plane/connect-inject/webhook/dns.go index ed4e95703b..3f73928ece 100644 --- a/control-plane/connect-inject/webhook/dns.go +++ b/control-plane/connect-inject/webhook/dns.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/dns_test.go b/control-plane/connect-inject/webhook/dns_test.go index d6392c5317..e8d718557e 100644 --- a/control-plane/connect-inject/webhook/dns_test.go +++ b/control-plane/connect-inject/webhook/dns_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/health_checks_test.go b/control-plane/connect-inject/webhook/health_checks_test.go index 9279d8f140..53e2781509 100644 --- a/control-plane/connect-inject/webhook/health_checks_test.go +++ b/control-plane/connect-inject/webhook/health_checks_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/heath_checks.go b/control-plane/connect-inject/webhook/heath_checks.go index 42d6da08e1..8986f9e985 100644 --- a/control-plane/connect-inject/webhook/heath_checks.go +++ b/control-plane/connect-inject/webhook/heath_checks.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 503d3182b4..7105e19e67 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go b/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go index 34071a686c..e6da9b1f49 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package webhook diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index 602ba63239..9b93d8d984 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/redirect_traffic.go b/control-plane/connect-inject/webhook/redirect_traffic.go index eab23a2b91..7066929dae 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic.go +++ b/control-plane/connect-inject/webhook/redirect_traffic.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/connect-inject/webhook/redirect_traffic_test.go b/control-plane/connect-inject/webhook/redirect_traffic_test.go index 2ad9940fbe..f94871fa96 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic_test.go +++ b/control-plane/connect-inject/webhook/redirect_traffic_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhook import ( diff --git a/control-plane/consul/consul.go b/control-plane/consul/consul.go index bb46308ff8..3cf916ffbf 100644 --- a/control-plane/consul/consul.go +++ b/control-plane/consul/consul.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/control-plane/consul/consul_test.go b/control-plane/consul/consul_test.go index 02430b0f48..04afe08033 100644 --- a/control-plane/consul/consul_test.go +++ b/control-plane/consul/consul_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/control-plane/controller/configentry_controller.go b/control-plane/controller/configentry_controller.go index 8ae90a56a6..593fb1514f 100644 --- a/control-plane/controller/configentry_controller.go +++ b/control-plane/controller/configentry_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/configentry_controller_ent_test.go b/control-plane/controller/configentry_controller_ent_test.go index 61a6aef947..cfe9985e56 100644 --- a/control-plane/controller/configentry_controller_ent_test.go +++ b/control-plane/controller/configentry_controller_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package controller_test diff --git a/control-plane/controller/configentry_controller_test.go b/control-plane/controller/configentry_controller_test.go index 83b9e3eecf..47f28f02d1 100644 --- a/control-plane/controller/configentry_controller_test.go +++ b/control-plane/controller/configentry_controller_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/exportedservices_controller.go b/control-plane/controller/exportedservices_controller.go index 84e767d6dc..90d7261d9a 100644 --- a/control-plane/controller/exportedservices_controller.go +++ b/control-plane/controller/exportedservices_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/exportedservices_controller_ent_test.go b/control-plane/controller/exportedservices_controller_ent_test.go index dd91c49b57..aba193bdd9 100644 --- a/control-plane/controller/exportedservices_controller_ent_test.go +++ b/control-plane/controller/exportedservices_controller_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package controller_test diff --git a/control-plane/controller/ingressgateway_controller.go b/control-plane/controller/ingressgateway_controller.go index 7e656b3d29..fffc3c5a06 100644 --- a/control-plane/controller/ingressgateway_controller.go +++ b/control-plane/controller/ingressgateway_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/mesh_controller.go b/control-plane/controller/mesh_controller.go index e15f49fca0..9f7d8cd7c8 100644 --- a/control-plane/controller/mesh_controller.go +++ b/control-plane/controller/mesh_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/proxydefaults_controller.go b/control-plane/controller/proxydefaults_controller.go index a63e121522..7499928ea4 100644 --- a/control-plane/controller/proxydefaults_controller.go +++ b/control-plane/controller/proxydefaults_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/servicedefaults_controller.go b/control-plane/controller/servicedefaults_controller.go index 7dd73914e4..b96ff7e566 100644 --- a/control-plane/controller/servicedefaults_controller.go +++ b/control-plane/controller/servicedefaults_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/serviceintentions_controller.go b/control-plane/controller/serviceintentions_controller.go index 3b70447517..43298a23f7 100644 --- a/control-plane/controller/serviceintentions_controller.go +++ b/control-plane/controller/serviceintentions_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/serviceresolver_controller.go b/control-plane/controller/serviceresolver_controller.go index 3e01e680ea..cca014ab50 100644 --- a/control-plane/controller/serviceresolver_controller.go +++ b/control-plane/controller/serviceresolver_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/servicerouter_controller.go b/control-plane/controller/servicerouter_controller.go index 7db983dec2..6ed1e52fad 100644 --- a/control-plane/controller/servicerouter_controller.go +++ b/control-plane/controller/servicerouter_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/servicesplitter_controller.go b/control-plane/controller/servicesplitter_controller.go index 9d07845dbb..dc5e2a8dd3 100644 --- a/control-plane/controller/servicesplitter_controller.go +++ b/control-plane/controller/servicesplitter_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/controller/terminatinggateway_controller.go b/control-plane/controller/terminatinggateway_controller.go index a8db2d851e..10af041c39 100644 --- a/control-plane/controller/terminatinggateway_controller.go +++ b/control-plane/controller/terminatinggateway_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/hack/lint-api-new-client/main.go b/control-plane/hack/lint-api-new-client/main.go index c4c6d896b1..6297f0abb1 100644 --- a/control-plane/hack/lint-api-new-client/main.go +++ b/control-plane/hack/lint-api-new-client/main.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Parses golang code looking for github.com/hashicorp/consul/api.NewClient() // being used in non-test code. If it finds this, it will error. // The purpose of this lint is that we actually want to use our internal diff --git a/control-plane/helper/cert/notify.go b/control-plane/helper/cert/notify.go index 201ca34dd5..076c923119 100644 --- a/control-plane/helper/cert/notify.go +++ b/control-plane/helper/cert/notify.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cert import ( diff --git a/control-plane/helper/cert/notify_test.go b/control-plane/helper/cert/notify_test.go index 8813816f86..7e7cbf6d68 100644 --- a/control-plane/helper/cert/notify_test.go +++ b/control-plane/helper/cert/notify_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cert import ( diff --git a/control-plane/helper/cert/source.go b/control-plane/helper/cert/source.go index dcc4e3640c..5a85cb271d 100644 --- a/control-plane/helper/cert/source.go +++ b/control-plane/helper/cert/source.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cert import ( diff --git a/control-plane/helper/cert/source_gen.go b/control-plane/helper/cert/source_gen.go index e9c79ed390..9d54f2836c 100644 --- a/control-plane/helper/cert/source_gen.go +++ b/control-plane/helper/cert/source_gen.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cert import ( diff --git a/control-plane/helper/cert/source_gen_test.go b/control-plane/helper/cert/source_gen_test.go index b68ffc7e13..fd31de654e 100644 --- a/control-plane/helper/cert/source_gen_test.go +++ b/control-plane/helper/cert/source_gen_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cert import ( diff --git a/control-plane/helper/cert/tls_util.go b/control-plane/helper/cert/tls_util.go index 37e2f4ea97..d5552d60f3 100644 --- a/control-plane/helper/cert/tls_util.go +++ b/control-plane/helper/cert/tls_util.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cert import ( diff --git a/control-plane/helper/coalesce/coalesce.go b/control-plane/helper/coalesce/coalesce.go index d3e96e6be8..bfe341d1d6 100644 --- a/control-plane/helper/coalesce/coalesce.go +++ b/control-plane/helper/coalesce/coalesce.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package coalesce import ( diff --git a/control-plane/helper/coalesce/coalesce_test.go b/control-plane/helper/coalesce/coalesce_test.go index 8489fed8b4..d2c37135c7 100644 --- a/control-plane/helper/coalesce/coalesce_test.go +++ b/control-plane/helper/coalesce/coalesce_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package coalesce import ( diff --git a/control-plane/helper/controller/controller.go b/control-plane/helper/controller/controller.go index 6edfb9d89c..87cdde1a6f 100644 --- a/control-plane/helper/controller/controller.go +++ b/control-plane/helper/controller/controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package controller contains a reusable abstraction for efficiently // watching for changes in resources in a Kubernetes cluster. package controller diff --git a/control-plane/helper/controller/controller_test.go b/control-plane/helper/controller/controller_test.go index 43fe677280..a7e960a6aa 100644 --- a/control-plane/helper/controller/controller_test.go +++ b/control-plane/helper/controller/controller_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/helper/controller/resource.go b/control-plane/helper/controller/resource.go index 959d101488..9634db51ae 100644 --- a/control-plane/helper/controller/resource.go +++ b/control-plane/helper/controller/resource.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/helper/controller/testing.go b/control-plane/helper/controller/testing.go index 7575276bf5..e4809c70bd 100644 --- a/control-plane/helper/controller/testing.go +++ b/control-plane/helper/controller/testing.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controller import ( diff --git a/control-plane/helper/go-discover/discover.go b/control-plane/helper/go-discover/discover.go index 845a7b144b..140586634a 100644 --- a/control-plane/helper/go-discover/discover.go +++ b/control-plane/helper/go-discover/discover.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package godiscover import ( diff --git a/control-plane/helper/go-discover/discover_test.go b/control-plane/helper/go-discover/discover_test.go index b655dc388e..5c25261516 100644 --- a/control-plane/helper/go-discover/discover_test.go +++ b/control-plane/helper/go-discover/discover_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package godiscover import ( diff --git a/control-plane/helper/go-discover/mocks/mock_provider.go b/control-plane/helper/go-discover/mocks/mock_provider.go index 51afb86a84..dfdab445da 100644 --- a/control-plane/helper/go-discover/mocks/mock_provider.go +++ b/control-plane/helper/go-discover/mocks/mock_provider.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package mocks import ( diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go index c3c93b5204..093b1ef908 100644 --- a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package mutatingwebhookconfiguration import ( diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go index e247c71d14..be1a3b5c64 100644 --- a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package mutatingwebhookconfiguration import ( diff --git a/control-plane/helper/parsetags/parsetags.go b/control-plane/helper/parsetags/parsetags.go index e9d9625338..caec75bb1d 100644 --- a/control-plane/helper/parsetags/parsetags.go +++ b/control-plane/helper/parsetags/parsetags.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package parsetags import ( diff --git a/control-plane/helper/parsetags/parsetags_test.go b/control-plane/helper/parsetags/parsetags_test.go index f403a2f711..2a6b9ad47f 100644 --- a/control-plane/helper/parsetags/parsetags_test.go +++ b/control-plane/helper/parsetags/parsetags_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package parsetags import ( diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index 0ad4601fde..e29e44de59 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package test import ( diff --git a/control-plane/main.go b/control-plane/main.go index 7ec1340290..a4ccc9630c 100644 --- a/control-plane/main.go +++ b/control-plane/main.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main import ( diff --git a/control-plane/namespaces/namespaces.go b/control-plane/namespaces/namespaces.go index c17aa1f788..84b8c15ee4 100644 --- a/control-plane/namespaces/namespaces.go +++ b/control-plane/namespaces/namespaces.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package namespaces handles interaction with Consul namespaces needed across // commands. package namespaces diff --git a/control-plane/namespaces/namespaces_test.go b/control-plane/namespaces/namespaces_test.go index 7b6a061a65..a2c9989ae8 100644 --- a/control-plane/namespaces/namespaces_test.go +++ b/control-plane/namespaces/namespaces_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package namespaces diff --git a/control-plane/subcommand/acl-init/command.go b/control-plane/subcommand/acl-init/command.go index af85128ea8..77e8f87d87 100644 --- a/control-plane/subcommand/acl-init/command.go +++ b/control-plane/subcommand/acl-init/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package aclinit import ( diff --git a/control-plane/subcommand/acl-init/command_test.go b/control-plane/subcommand/acl-init/command_test.go index c9f5703459..acdafc939f 100644 --- a/control-plane/subcommand/acl-init/command_test.go +++ b/control-plane/subcommand/acl-init/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package aclinit import ( diff --git a/control-plane/subcommand/auth.go b/control-plane/subcommand/auth.go index 58a9b801e8..6389e7d6f7 100644 --- a/control-plane/subcommand/auth.go +++ b/control-plane/subcommand/auth.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package subcommand import ( diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index e3a569ddf6..b7927e4490 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package common holds code needed by multiple commands. package common diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index 6021bd0b49..9e50302b17 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import ( diff --git a/control-plane/subcommand/common/test_util.go b/control-plane/subcommand/common/test_util.go index 13d9017fe4..3399b40e2b 100644 --- a/control-plane/subcommand/common/test_util.go +++ b/control-plane/subcommand/common/test_util.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import ( diff --git a/control-plane/subcommand/connect-init/command.go b/control-plane/subcommand/connect-init/command.go index 4750e9455c..a5fbe9066c 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connectinit import ( diff --git a/control-plane/subcommand/connect-init/command_ent_test.go b/control-plane/subcommand/connect-init/command_ent_test.go index ecdc34122e..b3ef7109e0 100644 --- a/control-plane/subcommand/connect-init/command_ent_test.go +++ b/control-plane/subcommand/connect-init/command_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package connectinit diff --git a/control-plane/subcommand/connect-init/command_test.go b/control-plane/subcommand/connect-init/command_test.go index 14bdc5280c..69abc8f1ad 100644 --- a/control-plane/subcommand/connect-init/command_test.go +++ b/control-plane/subcommand/connect-init/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connectinit import ( diff --git a/control-plane/subcommand/consul-logout/command.go b/control-plane/subcommand/consul-logout/command.go index b0836b71f0..a6b599dccd 100644 --- a/control-plane/subcommand/consul-logout/command.go +++ b/control-plane/subcommand/consul-logout/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consullogout import ( diff --git a/control-plane/subcommand/consul-logout/command_test.go b/control-plane/subcommand/consul-logout/command_test.go index 22412ea752..e7e3a00f38 100644 --- a/control-plane/subcommand/consul-logout/command_test.go +++ b/control-plane/subcommand/consul-logout/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consullogout import ( diff --git a/control-plane/subcommand/create-federation-secret/command.go b/control-plane/subcommand/create-federation-secret/command.go index 3a96e18134..52600aedda 100644 --- a/control-plane/subcommand/create-federation-secret/command.go +++ b/control-plane/subcommand/create-federation-secret/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package createfederationsecret import ( diff --git a/control-plane/subcommand/create-federation-secret/command_test.go b/control-plane/subcommand/create-federation-secret/command_test.go index d0f85fa686..a90ff692e2 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package createfederationsecret import ( diff --git a/control-plane/subcommand/delete-completed-job/command.go b/control-plane/subcommand/delete-completed-job/command.go index 273e621a2a..f6f594393d 100644 --- a/control-plane/subcommand/delete-completed-job/command.go +++ b/control-plane/subcommand/delete-completed-job/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package deletecompletedjob import ( diff --git a/control-plane/subcommand/delete-completed-job/command_test.go b/control-plane/subcommand/delete-completed-job/command_test.go index 0da056fc88..abfbb0e788 100644 --- a/control-plane/subcommand/delete-completed-job/command_test.go +++ b/control-plane/subcommand/delete-completed-job/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package deletecompletedjob import ( diff --git a/control-plane/subcommand/flags/consul.go b/control-plane/subcommand/flags/consul.go index 1294729a6b..9368b95b3d 100644 --- a/control-plane/subcommand/flags/consul.go +++ b/control-plane/subcommand/flags/consul.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/consul_test.go b/control-plane/subcommand/flags/consul_test.go index b53025daa6..7f35dc8575 100644 --- a/control-plane/subcommand/flags/consul_test.go +++ b/control-plane/subcommand/flags/consul_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/flag_map_value.go b/control-plane/subcommand/flags/flag_map_value.go index 9ddb4dad08..c647f57bf5 100644 --- a/control-plane/subcommand/flags/flag_map_value.go +++ b/control-plane/subcommand/flags/flag_map_value.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/flag_map_value_test.go b/control-plane/subcommand/flags/flag_map_value_test.go index a3b7659cc4..ea68b6b312 100644 --- a/control-plane/subcommand/flags/flag_map_value_test.go +++ b/control-plane/subcommand/flags/flag_map_value_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/flag_slice_value.go b/control-plane/subcommand/flags/flag_slice_value.go index 11e838e683..42c45562b2 100644 --- a/control-plane/subcommand/flags/flag_slice_value.go +++ b/control-plane/subcommand/flags/flag_slice_value.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import "strings" diff --git a/control-plane/subcommand/flags/flag_slice_value_test.go b/control-plane/subcommand/flags/flag_slice_value_test.go index e361c12b10..24cf4df695 100644 --- a/control-plane/subcommand/flags/flag_slice_value_test.go +++ b/control-plane/subcommand/flags/flag_slice_value_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/flags.go b/control-plane/subcommand/flags/flags.go index e290e876d7..6739684bf0 100644 --- a/control-plane/subcommand/flags/flags.go +++ b/control-plane/subcommand/flags/flags.go @@ -1,2 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package flags holds common flags that are shared between our commands. package flags diff --git a/control-plane/subcommand/flags/http.go b/control-plane/subcommand/flags/http.go index 74db3c26dc..21ccb45df1 100644 --- a/control-plane/subcommand/flags/http.go +++ b/control-plane/subcommand/flags/http.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/http_test.go b/control-plane/subcommand/flags/http_test.go index a44f79931c..3292661933 100644 --- a/control-plane/subcommand/flags/http_test.go +++ b/control-plane/subcommand/flags/http_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/k8s.go b/control-plane/subcommand/flags/k8s.go index 31a2284f65..15f1a996b9 100644 --- a/control-plane/subcommand/flags/k8s.go +++ b/control-plane/subcommand/flags/k8s.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/mapset.go b/control-plane/subcommand/flags/mapset.go index c58cc9a3a2..d97419a827 100644 --- a/control-plane/subcommand/flags/mapset.go +++ b/control-plane/subcommand/flags/mapset.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import "github.com/deckarep/golang-set" diff --git a/control-plane/subcommand/flags/usage.go b/control-plane/subcommand/flags/usage.go index 960ce85926..df63d9c8a6 100644 --- a/control-plane/subcommand/flags/usage.go +++ b/control-plane/subcommand/flags/usage.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/flags/usage_test.go b/control-plane/subcommand/flags/usage_test.go index 801888e549..7e27ad77fb 100644 --- a/control-plane/subcommand/flags/usage_test.go +++ b/control-plane/subcommand/flags/usage_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package flags import ( diff --git a/control-plane/subcommand/get-consul-client-ca/command.go b/control-plane/subcommand/get-consul-client-ca/command.go index a154d778e9..619f08625d 100644 --- a/control-plane/subcommand/get-consul-client-ca/command.go +++ b/control-plane/subcommand/get-consul-client-ca/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package getconsulclientca import ( diff --git a/control-plane/subcommand/get-consul-client-ca/command_test.go b/control-plane/subcommand/get-consul-client-ca/command_test.go index 9c48e63712..466e11a11e 100644 --- a/control-plane/subcommand/get-consul-client-ca/command_test.go +++ b/control-plane/subcommand/get-consul-client-ca/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package getconsulclientca import ( diff --git a/control-plane/subcommand/gossip-encryption-autogenerate/command.go b/control-plane/subcommand/gossip-encryption-autogenerate/command.go index 3668a20c35..cf871eca69 100644 --- a/control-plane/subcommand/gossip-encryption-autogenerate/command.go +++ b/control-plane/subcommand/gossip-encryption-autogenerate/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gossipencryptionautogenerate import ( diff --git a/control-plane/subcommand/gossip-encryption-autogenerate/command_test.go b/control-plane/subcommand/gossip-encryption-autogenerate/command_test.go index 91d7101232..55b32bdb06 100644 --- a/control-plane/subcommand/gossip-encryption-autogenerate/command_test.go +++ b/control-plane/subcommand/gossip-encryption-autogenerate/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gossipencryptionautogenerate import ( diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 93566387cd..e570832ee9 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connectinject import ( diff --git a/control-plane/subcommand/inject-connect/command_test.go b/control-plane/subcommand/inject-connect/command_test.go index 5f067cf7c2..9c64020376 100644 --- a/control-plane/subcommand/inject-connect/command_test.go +++ b/control-plane/subcommand/inject-connect/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connectinject import ( diff --git a/control-plane/subcommand/install-cni/binary.go b/control-plane/subcommand/install-cni/binary.go index 2429770109..5bf25ab607 100644 --- a/control-plane/subcommand/install-cni/binary.go +++ b/control-plane/subcommand/install-cni/binary.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package installcni import ( diff --git a/control-plane/subcommand/install-cni/binary_test.go b/control-plane/subcommand/install-cni/binary_test.go index e65f61c63b..397751d42c 100644 --- a/control-plane/subcommand/install-cni/binary_test.go +++ b/control-plane/subcommand/install-cni/binary_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package installcni import ( diff --git a/control-plane/subcommand/install-cni/cniconfig.go b/control-plane/subcommand/install-cni/cniconfig.go index 922d7283dd..448c9efa92 100644 --- a/control-plane/subcommand/install-cni/cniconfig.go +++ b/control-plane/subcommand/install-cni/cniconfig.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package installcni import ( diff --git a/control-plane/subcommand/install-cni/cniconfig_test.go b/control-plane/subcommand/install-cni/cniconfig_test.go index b6e2154adb..06fa848074 100644 --- a/control-plane/subcommand/install-cni/cniconfig_test.go +++ b/control-plane/subcommand/install-cni/cniconfig_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package installcni import ( diff --git a/control-plane/subcommand/install-cni/command.go b/control-plane/subcommand/install-cni/command.go index 7c481a0800..53abe7cda1 100644 --- a/control-plane/subcommand/install-cni/command.go +++ b/control-plane/subcommand/install-cni/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package installcni import ( diff --git a/control-plane/subcommand/install-cni/command_test.go b/control-plane/subcommand/install-cni/command_test.go index 5cb9bea91e..d5ee65f928 100644 --- a/control-plane/subcommand/install-cni/command_test.go +++ b/control-plane/subcommand/install-cni/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package installcni import ( diff --git a/control-plane/subcommand/install-cni/kubeconfig.go b/control-plane/subcommand/install-cni/kubeconfig.go index ca93759578..467130dea9 100644 --- a/control-plane/subcommand/install-cni/kubeconfig.go +++ b/control-plane/subcommand/install-cni/kubeconfig.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package installcni import ( diff --git a/control-plane/subcommand/install-cni/kubeconfig_test.go b/control-plane/subcommand/install-cni/kubeconfig_test.go index 899ad3f600..8197115db3 100644 --- a/control-plane/subcommand/install-cni/kubeconfig_test.go +++ b/control-plane/subcommand/install-cni/kubeconfig_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package installcni import ( diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go index 7ca70b50a7..72c4ceeff0 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package partition_init import ( diff --git a/control-plane/subcommand/partition-init/command_ent_test.go b/control-plane/subcommand/partition-init/command_ent_test.go index 5bb1868b39..182412c8aa 100644 --- a/control-plane/subcommand/partition-init/command_ent_test.go +++ b/control-plane/subcommand/partition-init/command_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package partition_init diff --git a/control-plane/subcommand/server-acl-init/anonymous_token.go b/control-plane/subcommand/server-acl-init/anonymous_token.go index 3423ee78da..32c19ec208 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index a444f65aaa..7ff0ae2268 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index fa56f3bb3a..a67a2d0e41 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package serveraclinit diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index ffe60af593..68ce4c1e02 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/connect_inject.go b/control-plane/subcommand/server-acl-init/connect_inject.go index e732dae452..58a36b988f 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject.go +++ b/control-plane/subcommand/server-acl-init/connect_inject.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/connect_inject_test.go b/control-plane/subcommand/server-acl-init/connect_inject_test.go index e7144146b7..03e47c8ba6 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject_test.go +++ b/control-plane/subcommand/server-acl-init/connect_inject_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/create_or_update.go b/control-plane/subcommand/server-acl-init/create_or_update.go index 833b923b90..d14fbc845c 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update.go +++ b/control-plane/subcommand/server-acl-init/create_or_update.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/create_or_update_test.go b/control-plane/subcommand/server-acl-init/create_or_update_test.go index 259707f85d..6aff677dda 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update_test.go +++ b/control-plane/subcommand/server-acl-init/create_or_update_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go index 4e3efcf65b..93d9d0d2d8 100644 --- a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go +++ b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index ee6ae41e40..440327ad53 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index 22e63ed0ce..fd4f1ce929 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/secrets_backend.go b/control-plane/subcommand/server-acl-init/secrets_backend.go index 4b4d5c2fc4..e0d74462cf 100644 --- a/control-plane/subcommand/server-acl-init/secrets_backend.go +++ b/control-plane/subcommand/server-acl-init/secrets_backend.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit type SecretsBackendType string diff --git a/control-plane/subcommand/server-acl-init/servers.go b/control-plane/subcommand/server-acl-init/servers.go index 01e9a58145..c530f648e5 100644 --- a/control-plane/subcommand/server-acl-init/servers.go +++ b/control-plane/subcommand/server-acl-init/servers.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go b/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go index 5c9a63f1a5..87826f6c60 100644 --- a/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go +++ b/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit type FakeSecretsBackend struct { diff --git a/control-plane/subcommand/server-acl-init/vault_secrets_backend.go b/control-plane/subcommand/server-acl-init/vault_secrets_backend.go index 2706567878..5a9097cca3 100644 --- a/control-plane/subcommand/server-acl-init/vault_secrets_backend.go +++ b/control-plane/subcommand/server-acl-init/vault_secrets_backend.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/sync-catalog/command.go b/control-plane/subcommand/sync-catalog/command.go index f890b44f34..1530cf9d53 100644 --- a/control-plane/subcommand/sync-catalog/command.go +++ b/control-plane/subcommand/sync-catalog/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package synccatalog import ( diff --git a/control-plane/subcommand/sync-catalog/command_ent_test.go b/control-plane/subcommand/sync-catalog/command_ent_test.go index fac330c557..fb6c6c4347 100644 --- a/control-plane/subcommand/sync-catalog/command_ent_test.go +++ b/control-plane/subcommand/sync-catalog/command_ent_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build enterprise package synccatalog diff --git a/control-plane/subcommand/sync-catalog/command_test.go b/control-plane/subcommand/sync-catalog/command_test.go index 8228986d00..0223931cc1 100644 --- a/control-plane/subcommand/sync-catalog/command_test.go +++ b/control-plane/subcommand/sync-catalog/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package synccatalog import ( diff --git a/control-plane/subcommand/tls-init/command.go b/control-plane/subcommand/tls-init/command.go index 354a26fdc5..c2498a3125 100644 --- a/control-plane/subcommand/tls-init/command.go +++ b/control-plane/subcommand/tls-init/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tls_init import ( diff --git a/control-plane/subcommand/tls-init/command_test.go b/control-plane/subcommand/tls-init/command_test.go index ae3cbd8982..81f5893543 100644 --- a/control-plane/subcommand/tls-init/command_test.go +++ b/control-plane/subcommand/tls-init/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tls_init import ( diff --git a/control-plane/subcommand/version/command.go b/control-plane/subcommand/version/command.go index 58768a1f92..1f6b2aed01 100644 --- a/control-plane/subcommand/version/command.go +++ b/control-plane/subcommand/version/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package version import ( diff --git a/control-plane/subcommand/webhook-cert-manager/command.go b/control-plane/subcommand/webhook-cert-manager/command.go index 214a1d41e2..4d85565b62 100644 --- a/control-plane/subcommand/webhook-cert-manager/command.go +++ b/control-plane/subcommand/webhook-cert-manager/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhookcertmanager import ( diff --git a/control-plane/subcommand/webhook-cert-manager/command_test.go b/control-plane/subcommand/webhook-cert-manager/command_test.go index 7e302d5261..31c98b0ebe 100644 --- a/control-plane/subcommand/webhook-cert-manager/command_test.go +++ b/control-plane/subcommand/webhook-cert-manager/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package webhookcertmanager import ( diff --git a/control-plane/subcommand/webhook-cert-manager/mocks/mocks.go b/control-plane/subcommand/webhook-cert-manager/mocks/mocks.go index d700b192ac..5efa09eb29 100644 --- a/control-plane/subcommand/webhook-cert-manager/mocks/mocks.go +++ b/control-plane/subcommand/webhook-cert-manager/mocks/mocks.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package mocks import ( diff --git a/control-plane/version/version.go b/control-plane/version/version.go index 8ae06829bb..81433c0a5f 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package version import ( diff --git a/hack/aws-acceptance-test-cleanup/main.go b/hack/aws-acceptance-test-cleanup/main.go index b72b88c12f..d62e5e4405 100644 --- a/hack/aws-acceptance-test-cleanup/main.go +++ b/hack/aws-acceptance-test-cleanup/main.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main // This script deletes AWS resources created for acceptance tests that have diff --git a/hack/copy-crds-to-chart/main.go b/hack/copy-crds-to-chart/main.go index 7085bdb9e6..0f94a77b5d 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Script to copy generated CRD yaml into chart directory and modify it to match // the expected chart format (e.g. formatted YAML). package main diff --git a/hack/helm-reference-gen/doc_node.go b/hack/helm-reference-gen/doc_node.go index a183ead969..cf28cf9374 100644 --- a/hack/helm-reference-gen/doc_node.go +++ b/hack/helm-reference-gen/doc_node.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main import ( diff --git a/hack/helm-reference-gen/fixtures/full-values.yaml b/hack/helm-reference-gen/fixtures/full-values.yaml index b0eec07666..a79fbc6944 100644 --- a/hack/helm-reference-gen/fixtures/full-values.yaml +++ b/hack/helm-reference-gen/fixtures/full-values.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # Available parameters and their default values for the Consul chart. # Holds values that affect multiple components of the chart. diff --git a/hack/helm-reference-gen/main.go b/hack/helm-reference-gen/main.go index ae9b17610e..0bc623cff0 100644 --- a/hack/helm-reference-gen/main.go +++ b/hack/helm-reference-gen/main.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main // This script generates markdown documentation out of the values.yaml file diff --git a/hack/helm-reference-gen/main_test.go b/hack/helm-reference-gen/main_test.go index 03e8648218..fc91032203 100644 --- a/hack/helm-reference-gen/main_test.go +++ b/hack/helm-reference-gen/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main import ( diff --git a/hack/helm-reference-gen/parse_error.go b/hack/helm-reference-gen/parse_error.go index f474664f1f..914737db5f 100644 --- a/hack/helm-reference-gen/parse_error.go +++ b/hack/helm-reference-gen/parse_error.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main import "fmt" From ed2c3d6a77361140d9c35a0999cd9284d5723e84 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Wed, 22 Mar 2023 10:46:09 -0400 Subject: [PATCH 107/592] fix tests --- .github/workflows/backport-checker.yml | 2 ++ .github/workflows/backport.yml | 2 ++ .github/workflows/build.yml | 2 ++ .github/workflows/changelog-checker.yml | 2 ++ .github/workflows/jira-issues.yaml | 2 ++ .github/workflows/jira-pr.yaml | 2 ++ .github/workflows/reusable-acceptance.yml | 2 ++ .github/workflows/reusable-golangci-lint.yml | 2 ++ .github/workflows/reusable-unit.yml | 2 ++ .github/workflows/test.yml | 2 ++ cli/helm/chart_test.go | 6 +++--- 11 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backport-checker.yml b/.github/workflows/backport-checker.yml index 5bcac5a38e..a70790c0c0 100644 --- a/.github/workflows/backport-checker.yml +++ b/.github/workflows/backport-checker.yml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + # This workflow checks that there is either a 'pr/no-backport' label applied to a PR # or there is a backport/.txt file associated with a PR for a backport label diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 9c241d9ada..0283034a66 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + --- name: Backport Assistant Runner diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ce46f1704..de0d18d648 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + name: build on: workflow_dispatch: diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index ae2e88170b..3595781825 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + # This workflow checks that there is either a 'pr/no-changelog' label applied to a PR # or there is a .changelog/.txt file associated with a PR for a changelog entry diff --git a/.github/workflows/jira-issues.yaml b/.github/workflows/jira-issues.yaml index 18705db8e8..dc743e9328 100644 --- a/.github/workflows/jira-issues.yaml +++ b/.github/workflows/jira-issues.yaml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + on: issues: types: [opened, closed, deleted, reopened] diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index 5c0ba71cd2..c07a92ee77 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + on: pull_request_target: types: [opened, closed, reopened] diff --git a/.github/workflows/reusable-acceptance.yml b/.github/workflows/reusable-acceptance.yml index e4401932d3..cf1e11e3f5 100644 --- a/.github/workflows/reusable-acceptance.yml +++ b/.github/workflows/reusable-acceptance.yml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + name: reusable-acceptance on: diff --git a/.github/workflows/reusable-golangci-lint.yml b/.github/workflows/reusable-golangci-lint.yml index 30b6a0a3b3..e8fcd79440 100644 --- a/.github/workflows/reusable-golangci-lint.yml +++ b/.github/workflows/reusable-golangci-lint.yml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + name: golangci-lint on: diff --git a/.github/workflows/reusable-unit.yml b/.github/workflows/reusable-unit.yml index 8ba09377cc..dd2dd4e4a0 100644 --- a/.github/workflows/reusable-unit.yml +++ b/.github/workflows/reusable-unit.yml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + name: reusable-unit on: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 803aed69a4..6cb3ab1937 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. + name: test on: push: diff --git a/cli/helm/chart_test.go b/cli/helm/chart_test.go index 10917ad739..f0e33e092b 100644 --- a/cli/helm/chart_test.go +++ b/cli/helm/chart_test.go @@ -38,10 +38,10 @@ func TestLoadChart(t *testing.T) { func TestReadChartFiles(t *testing.T) { directory := "test_fixtures/consul" expectedFiles := map[string]string{ - "Chart.yaml": "# This is a mock Helm Chart.yaml file used for testing.\napiVersion: v2\nname: Foo\nversion: 0.1.0\ndescription: Mock Helm Chart for testing.", - "values.yaml": "# This is a mock Helm values.yaml file used for testing.\nkey: value", + "Chart.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\n# This is a mock Helm Chart.yaml file used for testing.\napiVersion: v2\nname: Foo\nversion: 0.1.0\ndescription: Mock Helm Chart for testing.", + "values.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\n# This is a mock Helm values.yaml file used for testing.\nkey: value", "templates/_helpers.tpl": "helpers", - "templates/foo.yaml": "foo: bar\n", + "templates/foo.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nfoo: bar\n", } files, err := readChartFiles(testChartFiles, directory) From 4f00647c984ed921460d96f6783e2a1348542af3 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 28 Mar 2023 09:37:26 -0400 Subject: [PATCH 108/592] fix charts --- .../consul/templates/api-gateway-controller-clusterrole.yaml | 3 --- .../templates/api-gateway-controller-clusterrolebinding.yaml | 3 --- charts/consul/templates/api-gateway-controller-deployment.yaml | 3 --- .../templates/api-gateway-controller-podsecuritypolicy.yaml | 3 --- charts/consul/templates/api-gateway-controller-service.yaml | 3 --- .../templates/api-gateway-controller-serviceaccount.yaml | 3 --- charts/consul/templates/api-gateway-gatewayclass.yaml | 3 --- charts/consul/templates/api-gateway-gatewayclassconfig.yaml | 3 --- charts/consul/templates/api-gateway-podsecuritypolicy.yaml | 3 --- charts/consul/templates/auth-method-clusterrole.yaml | 3 --- charts/consul/templates/auth-method-clusterrolebinding.yaml | 3 --- charts/consul/templates/auth-method-secret.yaml | 3 --- charts/consul/templates/auth-method-serviceaccount.yaml | 3 --- charts/consul/templates/client-config-configmap.yaml | 3 --- charts/consul/templates/client-daemonset.yaml | 3 --- charts/consul/templates/client-podsecuritypolicy.yaml | 3 --- charts/consul/templates/client-role.yaml | 3 --- charts/consul/templates/client-rolebinding.yaml | 3 --- charts/consul/templates/client-securitycontextconstraints.yaml | 3 --- charts/consul/templates/client-serviceaccount.yaml | 3 --- charts/consul/templates/cni-clusterrole.yaml | 3 --- charts/consul/templates/cni-clusterrolebinding.yaml | 3 --- charts/consul/templates/cni-daemonset.yaml | 3 --- charts/consul/templates/cni-networkattachmentdefinition.yaml | 3 --- charts/consul/templates/cni-podsecuritypolicy.yaml | 3 --- charts/consul/templates/cni-resourcequota.yaml | 3 --- charts/consul/templates/cni-securitycontextconstraints.yaml | 3 --- charts/consul/templates/cni-serviceaccount.yaml | 3 --- charts/consul/templates/connect-inject-clusterrole.yaml | 3 --- charts/consul/templates/connect-inject-clusterrolebinding.yaml | 3 --- charts/consul/templates/connect-inject-deployment.yaml | 3 --- .../consul/templates/connect-inject-leader-election-role.yaml | 3 --- .../templates/connect-inject-leader-election-rolebinding.yaml | 3 --- .../templates/connect-inject-mutatingwebhookconfiguration.yaml | 3 --- charts/consul/templates/connect-inject-podsecuritypolicy.yaml | 3 --- charts/consul/templates/connect-inject-service.yaml | 3 --- charts/consul/templates/connect-inject-serviceaccount.yaml | 3 --- charts/consul/templates/connect-injector-disruptionbudget.yaml | 3 --- charts/consul/templates/crd-exportedservices.yaml | 3 --- charts/consul/templates/crd-ingressgateways.yaml | 3 --- charts/consul/templates/crd-meshes.yaml | 3 --- charts/consul/templates/crd-peeringacceptors.yaml | 3 --- charts/consul/templates/crd-peeringdialers.yaml | 3 --- charts/consul/templates/crd-proxydefaults.yaml | 3 --- charts/consul/templates/crd-servicedefaults.yaml | 3 --- charts/consul/templates/crd-serviceintentions.yaml | 3 --- charts/consul/templates/crd-serviceresolvers.yaml | 3 --- charts/consul/templates/crd-servicerouters.yaml | 3 --- charts/consul/templates/crd-servicesplitters.yaml | 3 --- charts/consul/templates/crd-terminatinggateways.yaml | 3 --- charts/consul/templates/create-federation-secret-job.yaml | 3 --- .../templates/create-federation-secret-podsecuritypolicy.yaml | 3 --- charts/consul/templates/create-federation-secret-role.yaml | 3 --- .../consul/templates/create-federation-secret-rolebinding.yaml | 3 --- .../templates/create-federation-secret-serviceaccount.yaml | 3 --- charts/consul/templates/dns-service.yaml | 3 --- charts/consul/templates/enterprise-license-job.yaml | 3 --- .../consul/templates/enterprise-license-podsecuritypolicy.yaml | 3 --- charts/consul/templates/enterprise-license-role.yaml | 3 --- charts/consul/templates/enterprise-license-rolebinding.yaml | 3 --- charts/consul/templates/enterprise-license-serviceaccount.yaml | 3 --- charts/consul/templates/expose-servers-service.yaml | 3 --- .../consul/templates/gossip-encryption-autogenerate-job.yaml | 3 --- .../gossip-encryption-autogenerate-podsecuritypolicy.yaml | 3 --- .../consul/templates/gossip-encryption-autogenerate-role.yaml | 3 --- .../templates/gossip-encryption-autogenerate-rolebinding.yaml | 3 --- .../gossip-encryption-autogenerate-serviceaccount.yaml | 3 --- charts/consul/templates/ingress-gateways-deployment.yaml | 3 --- .../consul/templates/ingress-gateways-podsecuritypolicy.yaml | 3 --- charts/consul/templates/ingress-gateways-role.yaml | 3 --- charts/consul/templates/ingress-gateways-rolebinding.yaml | 3 --- charts/consul/templates/ingress-gateways-service.yaml | 3 --- charts/consul/templates/ingress-gateways-serviceaccount.yaml | 3 --- charts/consul/templates/mesh-gateway-clusterrole.yaml | 3 --- charts/consul/templates/mesh-gateway-clusterrolebinding.yaml | 3 --- charts/consul/templates/mesh-gateway-deployment.yaml | 3 --- charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml | 3 --- charts/consul/templates/mesh-gateway-service.yaml | 3 --- charts/consul/templates/mesh-gateway-serviceaccount.yaml | 3 --- charts/consul/templates/partition-init-job.yaml | 3 --- charts/consul/templates/partition-init-podsecuritypolicy.yaml | 3 --- charts/consul/templates/partition-init-role.yaml | 3 --- charts/consul/templates/partition-init-rolebinding.yaml | 3 --- charts/consul/templates/partition-init-serviceaccount.yaml | 3 --- charts/consul/templates/partition-name-configmap.yaml | 3 --- charts/consul/templates/prometheus.yaml | 3 --- charts/consul/templates/server-acl-init-cleanup-job.yaml | 3 --- .../templates/server-acl-init-cleanup-podsecuritypolicy.yaml | 3 --- charts/consul/templates/server-acl-init-cleanup-role.yaml | 3 --- .../consul/templates/server-acl-init-cleanup-rolebinding.yaml | 3 --- .../templates/server-acl-init-cleanup-serviceaccount.yaml | 3 --- charts/consul/templates/server-acl-init-job.yaml | 3 --- charts/consul/templates/server-acl-init-podsecuritypolicy.yaml | 3 --- charts/consul/templates/server-acl-init-role.yaml | 3 --- charts/consul/templates/server-acl-init-rolebinding.yaml | 3 --- charts/consul/templates/server-acl-init-serviceaccount.yaml | 3 --- charts/consul/templates/server-config-configmap.yaml | 3 --- charts/consul/templates/server-disruptionbudget.yaml | 3 --- charts/consul/templates/server-podsecuritypolicy.yaml | 3 --- charts/consul/templates/server-role.yaml | 3 --- charts/consul/templates/server-rolebinding.yaml | 3 --- charts/consul/templates/server-securitycontextconstraints.yaml | 3 --- charts/consul/templates/server-service.yaml | 3 --- charts/consul/templates/server-serviceaccount.yaml | 3 --- charts/consul/templates/server-snapshot-agent-configmap.yaml | 3 --- charts/consul/templates/server-statefulset.yaml | 3 --- charts/consul/templates/sync-catalog-clusterrole.yaml | 3 --- charts/consul/templates/sync-catalog-clusterrolebinding.yaml | 3 --- charts/consul/templates/sync-catalog-deployment.yaml | 3 --- charts/consul/templates/sync-catalog-podsecuritypolicy.yaml | 3 --- charts/consul/templates/sync-catalog-serviceaccount.yaml | 3 --- charts/consul/templates/terminating-gateways-deployment.yaml | 3 --- .../templates/terminating-gateways-podsecuritypolicy.yaml | 3 --- charts/consul/templates/terminating-gateways-role.yaml | 3 --- charts/consul/templates/terminating-gateways-rolebinding.yaml | 3 --- charts/consul/templates/terminating-gateways-service.yaml | 3 --- .../consul/templates/terminating-gateways-serviceaccount.yaml | 3 --- charts/consul/templates/tests/test-runner.yaml | 3 --- charts/consul/templates/tls-init-cleanup-job.yaml | 3 --- .../consul/templates/tls-init-cleanup-podsecuritypolicy.yaml | 3 --- charts/consul/templates/tls-init-cleanup-role.yaml | 3 --- charts/consul/templates/tls-init-cleanup-rolebinding.yaml | 3 --- charts/consul/templates/tls-init-cleanup-serviceaccount.yaml | 3 --- charts/consul/templates/tls-init-job.yaml | 3 --- charts/consul/templates/tls-init-podsecuritypolicy.yaml | 3 --- charts/consul/templates/tls-init-role.yaml | 3 --- charts/consul/templates/tls-init-rolebinding.yaml | 3 --- charts/consul/templates/tls-init-serviceaccount.yaml | 3 --- charts/consul/templates/ui-ingress.yaml | 3 --- charts/consul/templates/ui-service.yaml | 3 --- charts/consul/templates/webhook-cert-manager-clusterrole.yaml | 3 --- .../templates/webhook-cert-manager-clusterrolebinding.yaml | 3 --- charts/consul/templates/webhook-cert-manager-configmap.yaml | 3 --- charts/consul/templates/webhook-cert-manager-deployment.yaml | 3 --- .../templates/webhook-cert-manager-podsecuritypolicy.yaml | 3 --- .../consul/templates/webhook-cert-manager-serviceaccount.yaml | 3 --- 136 files changed, 408 deletions(-) diff --git a/charts/consul/templates/api-gateway-controller-clusterrole.yaml b/charts/consul/templates/api-gateway-controller-clusterrole.yaml index 5d2d45987b..eac2bd1f69 100644 --- a/charts/consul/templates/api-gateway-controller-clusterrole.yaml +++ b/charts/consul/templates/api-gateway-controller-clusterrole.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.apiGateway.enabled }} # The ClusterRole to enable the API Gateway controller to access required api endpoints. apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml b/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml index c874f5c7af..d083a08129 100644 --- a/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml +++ b/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.apiGateway.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 4d757cfa2c..86517d7140 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.apiGateway.enabled }} {{- if not .Values.client.grpc }}{{ fail "client.grpc must be true for api gateway" }}{{ end }} {{- if not .Values.apiGateway.image}}{{ fail "apiGateway.image must be set to enable api gateway" }}{{ end }} diff --git a/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml b/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml index 3ac3a05ba3..390d084303 100644 --- a/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml +++ b/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if and .Values.apiGateway.enabled .Values.global.enablePodSecurityPolicies }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/api-gateway-controller-service.yaml b/charts/consul/templates/api-gateway-controller-service.yaml index c2ed00cd88..aa79ff9fc3 100644 --- a/charts/consul/templates/api-gateway-controller-service.yaml +++ b/charts/consul/templates/api-gateway-controller-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.apiGateway.enabled }} apiVersion: v1 kind: Service diff --git a/charts/consul/templates/api-gateway-controller-serviceaccount.yaml b/charts/consul/templates/api-gateway-controller-serviceaccount.yaml index 0ee711f164..98292a8dbe 100644 --- a/charts/consul/templates/api-gateway-controller-serviceaccount.yaml +++ b/charts/consul/templates/api-gateway-controller-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.apiGateway.enabled }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/api-gateway-gatewayclass.yaml b/charts/consul/templates/api-gateway-gatewayclass.yaml index 4a3a59f849..d9ba85e633 100644 --- a/charts/consul/templates/api-gateway-gatewayclass.yaml +++ b/charts/consul/templates/api-gateway-gatewayclass.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.apiGateway.enabled .Values.apiGateway.managedGatewayClass.enabled) }} apiVersion: gateway.networking.k8s.io/v1alpha2 kind: GatewayClass diff --git a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml index feed4e158b..ba0e6c63db 100644 --- a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml +++ b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.apiGateway.enabled .Values.apiGateway.managedGatewayClass.enabled) }} apiVersion: api-gateway.consul.hashicorp.com/v1alpha1 kind: GatewayClassConfig diff --git a/charts/consul/templates/api-gateway-podsecuritypolicy.yaml b/charts/consul/templates/api-gateway-podsecuritypolicy.yaml index f445cb77bf..48f826f995 100644 --- a/charts/consul/templates/api-gateway-podsecuritypolicy.yaml +++ b/charts/consul/templates/api-gateway-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if and .Values.apiGateway.enabled .Values.global.enablePodSecurityPolicies }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/auth-method-clusterrole.yaml b/charts/consul/templates/auth-method-clusterrole.yaml index bd696f479f..6b8f2c5451 100644 --- a/charts/consul/templates/auth-method-clusterrole.yaml +++ b/charts/consul/templates/auth-method-clusterrole.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.acls.manageSystemACLs }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/charts/consul/templates/auth-method-clusterrolebinding.yaml b/charts/consul/templates/auth-method-clusterrolebinding.yaml index 9c6b7cef31..9bd6c64113 100644 --- a/charts/consul/templates/auth-method-clusterrolebinding.yaml +++ b/charts/consul/templates/auth-method-clusterrolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.acls.manageSystemACLs }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/auth-method-secret.yaml b/charts/consul/templates/auth-method-secret.yaml index 04fb0c164d..af0aeb4e1b 100644 --- a/charts/consul/templates/auth-method-secret.yaml +++ b/charts/consul/templates/auth-method-secret.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.acls.manageSystemACLs }} apiVersion: v1 kind: Secret diff --git a/charts/consul/templates/auth-method-serviceaccount.yaml b/charts/consul/templates/auth-method-serviceaccount.yaml index 453063ecd3..098339b8c8 100644 --- a/charts/consul/templates/auth-method-serviceaccount.yaml +++ b/charts/consul/templates/auth-method-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.acls.manageSystemACLs }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/client-config-configmap.yaml b/charts/consul/templates/client-config-configmap.yaml index 9a7122aa02..f9650a100b 100644 --- a/charts/consul/templates/client-config-configmap.yaml +++ b/charts/consul/templates/client-config-configmap.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} # ConfigMap with extra configuration specified directly to the chart # for client agents only. diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 91401eebdf..09a70b394e 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.imageK8s }}{{ fail "global.imageK8s is not a valid key, use global.imageK8S (note the capital 'S')" }}{{ end -}} {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} diff --git a/charts/consul/templates/client-podsecuritypolicy.yaml b/charts/consul/templates/client-podsecuritypolicy.yaml index db87e29f8d..0121bdf586 100644 --- a/charts/consul/templates/client-podsecuritypolicy.yaml +++ b/charts/consul/templates/client-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/client-role.yaml b/charts/consul/templates/client-role.yaml index aebe8e1a52..7f05b82e6b 100644 --- a/charts/consul/templates/client-role.yaml +++ b/charts/consul/templates/client-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/client-rolebinding.yaml b/charts/consul/templates/client-rolebinding.yaml index 4281a7869e..b034c70e55 100644 --- a/charts/consul/templates/client-rolebinding.yaml +++ b/charts/consul/templates/client-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/client-securitycontextconstraints.yaml b/charts/consul/templates/client-securitycontextconstraints.yaml index 7ae5b7ba2a..07e7711384 100644 --- a/charts/consul/templates/client-securitycontextconstraints.yaml +++ b/charts/consul/templates/client-securitycontextconstraints.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.global.openshift.enabled (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints diff --git a/charts/consul/templates/client-serviceaccount.yaml b/charts/consul/templates/client-serviceaccount.yaml index 44b74a5c53..addd757b84 100644 --- a/charts/consul/templates/client-serviceaccount.yaml +++ b/charts/consul/templates/client-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/cni-clusterrole.yaml b/charts/consul/templates/cni-clusterrole.yaml index a1cda2b7c5..773942cca8 100644 --- a/charts/consul/templates/cni-clusterrole.yaml +++ b/charts/consul/templates/cni-clusterrole.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.cni.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/charts/consul/templates/cni-clusterrolebinding.yaml b/charts/consul/templates/cni-clusterrolebinding.yaml index f86391c259..4b860388b6 100644 --- a/charts/consul/templates/cni-clusterrolebinding.yaml +++ b/charts/consul/templates/cni-clusterrolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.cni.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index 1eda31998c..ae04d9e657 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and (.Values.connectInject.cni.enabled) (not .Values.connectInject.enabled)) }}{{ fail "connectInject.enabled must be true if connectInject.cni.enabled is true" }}{{ end -}} {{- if .Values.connectInject.cni.enabled }} apiVersion: apps/v1 diff --git a/charts/consul/templates/cni-networkattachmentdefinition.yaml b/charts/consul/templates/cni-networkattachmentdefinition.yaml index 056b74fa71..80ef50bac6 100644 --- a/charts/consul/templates/cni-networkattachmentdefinition.yaml +++ b/charts/consul/templates/cni-networkattachmentdefinition.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and (.Values.connectInject.cni.enabled) (.Values.connectInject.cni.multus)) }} apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition diff --git a/charts/consul/templates/cni-podsecuritypolicy.yaml b/charts/consul/templates/cni-podsecuritypolicy.yaml index 8747387850..b600ed1b4b 100644 --- a/charts/consul/templates/cni-podsecuritypolicy.yaml +++ b/charts/consul/templates/cni-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.connectInject.cni.enabled .Values.global.enablePodSecurityPolicies) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/cni-resourcequota.yaml b/charts/consul/templates/cni-resourcequota.yaml index 1b8a8c7332..054c3061f5 100644 --- a/charts/consul/templates/cni-resourcequota.yaml +++ b/charts/consul/templates/cni-resourcequota.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.cni.enabled }} apiVersion: v1 kind: ResourceQuota diff --git a/charts/consul/templates/cni-securitycontextconstraints.yaml b/charts/consul/templates/cni-securitycontextconstraints.yaml index d408617925..2c09dba9b8 100644 --- a/charts/consul/templates/cni-securitycontextconstraints.yaml +++ b/charts/consul/templates/cni-securitycontextconstraints.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and (.Values.connectInject.cni.enabled) (.Values.global.openshift.enabled)) }} apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints diff --git a/charts/consul/templates/cni-serviceaccount.yaml b/charts/consul/templates/cni-serviceaccount.yaml index b3740d9f4a..cf4250b696 100644 --- a/charts/consul/templates/cni-serviceaccount.yaml +++ b/charts/consul/templates/cni-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.cni.enabled }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 307a9e3aed..f2e12f0ad9 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} # The ClusterRole to enable the Connect injector to get, list, watch and patch MutatingWebhookConfiguration. apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/connect-inject-clusterrolebinding.yaml b/charts/consul/templates/connect-inject-clusterrolebinding.yaml index de969246a7..c380adb311 100644 --- a/charts/consul/templates/connect-inject-clusterrolebinding.yaml +++ b/charts/consul/templates/connect-inject-clusterrolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 529cc98787..2b52c1b81c 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if and .Values.global.peering.enabled (not .Values.connectInject.enabled) }}{{ fail "setting global.peering.enabled to true requires connectInject.enabled to be true" }}{{ end }} {{- if and .Values.global.peering.enabled (not .Values.global.tls.enabled) }}{{ fail "setting global.peering.enabled to true requires global.tls.enabled to be true" }}{{ end }} {{- if and .Values.global.peering.enabled (not .Values.meshGateway.enabled) }}{{ fail "setting global.peering.enabled to true requires meshGateway.enabled to be true" }}{{ end }} diff --git a/charts/consul/templates/connect-inject-leader-election-role.yaml b/charts/consul/templates/connect-inject-leader-election-role.yaml index 2efaf64ec0..703aaffaac 100644 --- a/charts/consul/templates/connect-inject-leader-election-role.yaml +++ b/charts/consul/templates/connect-inject-leader-election-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/connect-inject-leader-election-rolebinding.yaml b/charts/consul/templates/connect-inject-leader-election-rolebinding.yaml index 28c4c9a7de..9a27d3c868 100644 --- a/charts/consul/templates/connect-inject-leader-election-rolebinding.yaml +++ b/charts/consul/templates/connect-inject-leader-election-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml index c01ffc333b..afcfd3800f 100644 --- a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml +++ b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} # The MutatingWebhookConfiguration to enable the Connect injector. apiVersion: admissionregistration.k8s.io/v1 diff --git a/charts/consul/templates/connect-inject-podsecuritypolicy.yaml b/charts/consul/templates/connect-inject-podsecuritypolicy.yaml index 5f449eba4d..0fafef7c40 100644 --- a/charts/consul/templates/connect-inject-podsecuritypolicy.yaml +++ b/charts/consul/templates/connect-inject-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/connect-inject-service.yaml b/charts/consul/templates/connect-inject-service.yaml index 39ca054b30..b0284af74d 100644 --- a/charts/consul/templates/connect-inject-service.yaml +++ b/charts/consul/templates/connect-inject-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} # The service for the Connect sidecar injector apiVersion: v1 diff --git a/charts/consul/templates/connect-inject-serviceaccount.yaml b/charts/consul/templates/connect-inject-serviceaccount.yaml index 3ec08e7ffb..ea2352c7ac 100644 --- a/charts/consul/templates/connect-inject-serviceaccount.yaml +++ b/charts/consul/templates/connect-inject-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled) }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/connect-injector-disruptionbudget.yaml b/charts/consul/templates/connect-injector-disruptionbudget.yaml index 4c7753a375..9b9cf2e39e 100644 --- a/charts/consul/templates/connect-injector-disruptionbudget.yaml +++ b/charts/consul/templates/connect-injector-disruptionbudget.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.connectInject.disruptionBudget.enabled (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled))) }} # PodDisruptionBudget to prevent degrading the connectInject cluster through # voluntary cluster changes. diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 5ff9450307..007990372c 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index d7bd768593..a01fafd8dd 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index 2e238c318e..2e33eb9653 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index e74bb2d733..e06e830f04 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index aab95bb61a..e24401e761 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 6c34618cb3..749f2e4257 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 0cb99dc97b..5c6ecc7476 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 18ab5b9d97..cdbb5413b0 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index f8989a8cc6..e058052e97 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index c927745f45..5052facc06 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index 4c28e5a506..a2af050c3d 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 2ed7143f8b..583c218be8 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 diff --git a/charts/consul/templates/create-federation-secret-job.yaml b/charts/consul/templates/create-federation-secret-job.yaml index a90b2610bf..4f83a1f82a 100644 --- a/charts/consul/templates/create-federation-secret-job.yaml +++ b/charts/consul/templates/create-federation-secret-job.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.federation.createFederationSecret }} {{- if not .Values.global.federation.enabled }}{{ fail "global.federation.enabled must be true when global.federation.createFederationSecret is true" }}{{ end }} {{- if and (not .Values.global.acls.createReplicationToken) .Values.global.acls.manageSystemACLs }}{{ fail "global.acls.createReplicationToken must be true when global.acls.manageSystemACLs is true because the federation secret must include the replication token" }}{{ end }} diff --git a/charts/consul/templates/create-federation-secret-podsecuritypolicy.yaml b/charts/consul/templates/create-federation-secret-podsecuritypolicy.yaml index e05659807d..8217311992 100644 --- a/charts/consul/templates/create-federation-secret-podsecuritypolicy.yaml +++ b/charts/consul/templates/create-federation-secret-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.enablePodSecurityPolicies }} {{- if .Values.global.federation.createFederationSecret }} apiVersion: policy/v1beta1 diff --git a/charts/consul/templates/create-federation-secret-role.yaml b/charts/consul/templates/create-federation-secret-role.yaml index 6d285b968a..086932a831 100644 --- a/charts/consul/templates/create-federation-secret-role.yaml +++ b/charts/consul/templates/create-federation-secret-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.federation.createFederationSecret }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/create-federation-secret-rolebinding.yaml b/charts/consul/templates/create-federation-secret-rolebinding.yaml index e9c326448f..3db8e7cb06 100644 --- a/charts/consul/templates/create-federation-secret-rolebinding.yaml +++ b/charts/consul/templates/create-federation-secret-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.federation.createFederationSecret }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/create-federation-secret-serviceaccount.yaml b/charts/consul/templates/create-federation-secret-serviceaccount.yaml index 843e75b61b..e398ec69c4 100644 --- a/charts/consul/templates/create-federation-secret-serviceaccount.yaml +++ b/charts/consul/templates/create-federation-secret-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.federation.createFederationSecret }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/dns-service.yaml b/charts/consul/templates/dns-service.yaml index 03b3a4cc9b..5bb446bc19 100644 --- a/charts/consul/templates/dns-service.yaml +++ b/charts/consul/templates/dns-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.dns.enabled | toString) "-") .Values.dns.enabled) (and (eq (.Values.dns.enabled | toString) "-") .Values.connectInject.transparentProxy.defaultEnabled)) }} # Service for Consul DNS. apiVersion: v1 diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index e6be55878a..0122690104 100644 --- a/charts/consul/templates/enterprise-license-job.yaml +++ b/charts/consul/templates/enterprise-license-job.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.server.enterpriseLicense }}{{ fail "server.enterpriseLicense has been moved to global.enterpriseLicense" }}{{ end -}} {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} diff --git a/charts/consul/templates/enterprise-license-podsecuritypolicy.yaml b/charts/consul/templates/enterprise-license-podsecuritypolicy.yaml index 24adf32765..cf96367473 100644 --- a/charts/consul/templates/enterprise-license-podsecuritypolicy.yaml +++ b/charts/consul/templates/enterprise-license-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} {{- if .Values.global.enablePodSecurityPolicies }} diff --git a/charts/consul/templates/enterprise-license-role.yaml b/charts/consul/templates/enterprise-license-role.yaml index 7536d51c60..6a1b7fdffa 100644 --- a/charts/consul/templates/enterprise-license-role.yaml +++ b/charts/consul/templates/enterprise-license-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/enterprise-license-rolebinding.yaml b/charts/consul/templates/enterprise-license-rolebinding.yaml index 5b479613db..a21118b431 100644 --- a/charts/consul/templates/enterprise-license-rolebinding.yaml +++ b/charts/consul/templates/enterprise-license-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/enterprise-license-serviceaccount.yaml b/charts/consul/templates/enterprise-license-serviceaccount.yaml index 6286281350..31c9da841e 100644 --- a/charts/consul/templates/enterprise-license-serviceaccount.yaml +++ b/charts/consul/templates/enterprise-license-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.enterpriseLicense.secretName .Values.global.enterpriseLicense.secretKey (not .Values.global.enterpriseLicense.enableLicenseAutoload)) }} apiVersion: v1 diff --git a/charts/consul/templates/expose-servers-service.yaml b/charts/consul/templates/expose-servers-service.yaml index 56743bbee6..d86cec9042 100644 --- a/charts/consul/templates/expose-servers-service.yaml +++ b/charts/consul/templates/expose-servers-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- $serverExposeServiceEnabled := (or (and (ne (.Values.server.exposeService.enabled | toString) "-") .Values.server.exposeService.enabled) (and (eq (.Values.server.exposeService.enabled | toString) "-") .Values.global.adminPartitions.enabled)) -}} {{- if (and $serverEnabled $serverExposeServiceEnabled) }} diff --git a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml index fc7548366a..9d296478a1 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.gossipEncryption.autoGenerate }} {{- if (or .Values.global.gossipEncryption.secretName .Values.global.gossipEncryption.secretKey) }} {{ fail "If global.gossipEncryption.autoGenerate is true, global.gossipEncryption.secretName and global.gossipEncryption.secretKey must not be set." }} diff --git a/charts/consul/templates/gossip-encryption-autogenerate-podsecuritypolicy.yaml b/charts/consul/templates/gossip-encryption-autogenerate-podsecuritypolicy.yaml index 54095be3a0..209b3aa343 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-podsecuritypolicy.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if and .Values.global.gossipEncryption.autoGenerate .Values.global.enablePodSecurityPolicies }} --- apiVersion: policy/v1beta1 diff --git a/charts/consul/templates/gossip-encryption-autogenerate-role.yaml b/charts/consul/templates/gossip-encryption-autogenerate-role.yaml index 70b6058bda..8c51c96ffe 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-role.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.gossipEncryption.autoGenerate }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/gossip-encryption-autogenerate-rolebinding.yaml b/charts/consul/templates/gossip-encryption-autogenerate-rolebinding.yaml index 961d14e240..7118475f64 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-rolebinding.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.gossipEncryption.autoGenerate }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/gossip-encryption-autogenerate-serviceaccount.yaml b/charts/consul/templates/gossip-encryption-autogenerate-serviceaccount.yaml index 4ea1c6bb39..1fd620237f 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-serviceaccount.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.global.gossipEncryption.autoGenerate }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index 8a0f35328c..4f72031855 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.ingressGateways.enabled }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} diff --git a/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml b/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml index abcf971598..f7354da2b3 100644 --- a/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml +++ b/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.global.enablePodSecurityPolicies .Values.ingressGateways.enabled) }} {{- $root := . }} {{- range .Values.ingressGateways.gateways }} diff --git a/charts/consul/templates/ingress-gateways-role.yaml b/charts/consul/templates/ingress-gateways-role.yaml index ea4567163b..49e8486e58 100644 --- a/charts/consul/templates/ingress-gateways-role.yaml +++ b/charts/consul/templates/ingress-gateways-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.ingressGateways.enabled }} {{- $root := . }} diff --git a/charts/consul/templates/ingress-gateways-rolebinding.yaml b/charts/consul/templates/ingress-gateways-rolebinding.yaml index 015395ec16..601de775f4 100644 --- a/charts/consul/templates/ingress-gateways-rolebinding.yaml +++ b/charts/consul/templates/ingress-gateways-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.ingressGateways.enabled }} {{- $root := . }} {{- range .Values.ingressGateways.gateways }} diff --git a/charts/consul/templates/ingress-gateways-service.yaml b/charts/consul/templates/ingress-gateways-service.yaml index 5f0ca22c39..cf54a740fe 100644 --- a/charts/consul/templates/ingress-gateways-service.yaml +++ b/charts/consul/templates/ingress-gateways-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.ingressGateways.enabled }} {{- $root := . }} diff --git a/charts/consul/templates/ingress-gateways-serviceaccount.yaml b/charts/consul/templates/ingress-gateways-serviceaccount.yaml index 0dc1e4564a..cea6cafc21 100644 --- a/charts/consul/templates/ingress-gateways-serviceaccount.yaml +++ b/charts/consul/templates/ingress-gateways-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.ingressGateways.enabled }} {{- $root := . }} {{- $defaults := .Values.ingressGateways.defaults }} diff --git a/charts/consul/templates/mesh-gateway-clusterrole.yaml b/charts/consul/templates/mesh-gateway-clusterrole.yaml index fe4c7ca348..b951418b26 100644 --- a/charts/consul/templates/mesh-gateway-clusterrole.yaml +++ b/charts/consul/templates/mesh-gateway-clusterrole.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.meshGateway.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml b/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml index 028dea8a34..f8150ebb53 100644 --- a/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml +++ b/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.meshGateway.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index 8e2c0cc791..2b2bdc8c2a 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.meshGateway.enabled }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.acls.manageSystemACLs (ne .Values.meshGateway.consulServiceName "") (ne .Values.meshGateway.consulServiceName "mesh-gateway") }}{{ fail "if global.acls.manageSystemACLs is true, meshGateway.consulServiceName cannot be set" }}{{ end -}} diff --git a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml index 712f58c0cf..b5bbb2fa03 100644 --- a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml +++ b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if and .Values.global.enablePodSecurityPolicies .Values.meshGateway.enabled }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/mesh-gateway-service.yaml b/charts/consul/templates/mesh-gateway-service.yaml index 3dbfb0486a..5fdceca8df 100644 --- a/charts/consul/templates/mesh-gateway-service.yaml +++ b/charts/consul/templates/mesh-gateway-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if and .Values.meshGateway.enabled }} apiVersion: v1 kind: Service diff --git a/charts/consul/templates/mesh-gateway-serviceaccount.yaml b/charts/consul/templates/mesh-gateway-serviceaccount.yaml index 4ba2b6788d..8c2da5ae06 100644 --- a/charts/consul/templates/mesh-gateway-serviceaccount.yaml +++ b/charts/consul/templates/mesh-gateway-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.meshGateway.enabled }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 00a500f519..db73ef783b 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled) (ne .Values.global.adminPartitions.name "default")) }} {{- template "consul.reservedNamesFailer" (list .Values.global.adminPartitions.name "global.adminPartitions.name") }} diff --git a/charts/consul/templates/partition-init-podsecuritypolicy.yaml b/charts/consul/templates/partition-init-podsecuritypolicy.yaml index 4cf3f3e038..2bc6782394 100644 --- a/charts/consul/templates/partition-init-podsecuritypolicy.yaml +++ b/charts/consul/templates/partition-init-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled .Values.global.enablePodSecurityPolicies (not $serverEnabled)) }} apiVersion: policy/v1beta1 diff --git a/charts/consul/templates/partition-init-role.yaml b/charts/consul/templates/partition-init-role.yaml index 592c10e413..c13a5378eb 100644 --- a/charts/consul/templates/partition-init-role.yaml +++ b/charts/consul/templates/partition-init-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/partition-init-rolebinding.yaml b/charts/consul/templates/partition-init-rolebinding.yaml index e70b8d63ba..432d6df6ec 100644 --- a/charts/consul/templates/partition-init-rolebinding.yaml +++ b/charts/consul/templates/partition-init-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/partition-init-serviceaccount.yaml b/charts/consul/templates/partition-init-serviceaccount.yaml index 5352b5aea6..65fcf43b08 100644 --- a/charts/consul/templates/partition-init-serviceaccount.yaml +++ b/charts/consul/templates/partition-init-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} apiVersion: v1 diff --git a/charts/consul/templates/partition-name-configmap.yaml b/charts/consul/templates/partition-name-configmap.yaml index bc3e4566c3..ee330b0f46 100644 --- a/charts/consul/templates/partition-name-configmap.yaml +++ b/charts/consul/templates/partition-name-configmap.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} # Immutable ConfigMap which saves the partition name. Attempting to update this configmap diff --git a/charts/consul/templates/prometheus.yaml b/charts/consul/templates/prometheus.yaml index 670fd76946..4dcede1745 100644 --- a/charts/consul/templates/prometheus.yaml +++ b/charts/consul/templates/prometheus.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.prometheus.enabled }} # This file is auto-generated, see addons/gen.sh --- diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 63c509d883..35b0877ab4 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/server-acl-init-cleanup-podsecuritypolicy.yaml index 1ea8ce28b4..dd5dad24df 100644 --- a/charts/consul/templates/server-acl-init-cleanup-podsecuritypolicy.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-cleanup-role.yaml b/charts/consul/templates/server-acl-init-cleanup-role.yaml index e18200adfe..0a2f296a60 100644 --- a/charts/consul/templates/server-acl-init-cleanup-role.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-cleanup-rolebinding.yaml b/charts/consul/templates/server-acl-init-cleanup-rolebinding.yaml index c2365d63bd..268eaa5677 100644 --- a/charts/consul/templates/server-acl-init-cleanup-rolebinding.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-cleanup-serviceaccount.yaml b/charts/consul/templates/server-acl-init-cleanup-serviceaccount.yaml index 030bd38516..604e6d784c 100644 --- a/charts/consul/templates/server-acl-init-cleanup-serviceaccount.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index 3e42ce553b..e62db41ec2 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (and $serverEnabled .Values.externalServers.enabled) }}{{ fail "only one of server.enabled or externalServers.enabled can be set" }}{{ end -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} diff --git a/charts/consul/templates/server-acl-init-podsecuritypolicy.yaml b/charts/consul/templates/server-acl-init-podsecuritypolicy.yaml index aa35d9ba07..9bf93e2551 100644 --- a/charts/consul/templates/server-acl-init-podsecuritypolicy.yaml +++ b/charts/consul/templates/server-acl-init-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-role.yaml b/charts/consul/templates/server-acl-init-role.yaml index d165c34492..eb7b6a928e 100644 --- a/charts/consul/templates/server-acl-init-role.yaml +++ b/charts/consul/templates/server-acl-init-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-rolebinding.yaml b/charts/consul/templates/server-acl-init-rolebinding.yaml index ff125f2c5e..fda4726d9f 100644 --- a/charts/consul/templates/server-acl-init-rolebinding.yaml +++ b/charts/consul/templates/server-acl-init-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-acl-init-serviceaccount.yaml b/charts/consul/templates/server-acl-init-serviceaccount.yaml index 1b0129d168..c0e257de96 100644 --- a/charts/consul/templates/server-acl-init-serviceaccount.yaml +++ b/charts/consul/templates/server-acl-init-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} {{- if (or $serverEnabled .Values.externalServers.enabled) }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 6ae8037606..f7dd85f166 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} # StatefulSet to run the actual Consul server cluster. apiVersion: v1 diff --git a/charts/consul/templates/server-disruptionbudget.yaml b/charts/consul/templates/server-disruptionbudget.yaml index e4806c21f9..edf9c1c57f 100644 --- a/charts/consul/templates/server-disruptionbudget.yaml +++ b/charts/consul/templates/server-disruptionbudget.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.server.disruptionBudget.enabled (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled))) }} # PodDisruptionBudget to prevent degrading the server cluster through # voluntary cluster changes. diff --git a/charts/consul/templates/server-podsecuritypolicy.yaml b/charts/consul/templates/server-podsecuritypolicy.yaml index 4049679043..09e8d75bd1 100644 --- a/charts/consul/templates/server-podsecuritypolicy.yaml +++ b/charts/consul/templates/server-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/server-role.yaml b/charts/consul/templates/server-role.yaml index a3d432c299..202518bf67 100644 --- a/charts/consul/templates/server-role.yaml +++ b/charts/consul/templates/server-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/charts/consul/templates/server-rolebinding.yaml b/charts/consul/templates/server-rolebinding.yaml index c317cab99d..8ab705ddbc 100644 --- a/charts/consul/templates/server-rolebinding.yaml +++ b/charts/consul/templates/server-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/consul/templates/server-securitycontextconstraints.yaml b/charts/consul/templates/server-securitycontextconstraints.yaml index 593a842ec2..8edd784ea7 100644 --- a/charts/consul/templates/server-securitycontextconstraints.yaml +++ b/charts/consul/templates/server-securitycontextconstraints.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.global.openshift.enabled .Values.server.exposeGossipAndRPCPorts (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints diff --git a/charts/consul/templates/server-service.yaml b/charts/consul/templates/server-service.yaml index e3ae931a3a..a392f0e76b 100644 --- a/charts/consul/templates/server-service.yaml +++ b/charts/consul/templates/server-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} # Headless service for Consul server DNS entries. This service should only # point to Consul servers. For access to an agent, one should assume that diff --git a/charts/consul/templates/server-serviceaccount.yaml b/charts/consul/templates/server-serviceaccount.yaml index ff8cc2022a..a1617975ae 100644 --- a/charts/consul/templates/server-serviceaccount.yaml +++ b/charts/consul/templates/server-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/templates/server-snapshot-agent-configmap.yaml b/charts/consul/templates/server-snapshot-agent-configmap.yaml index cff5e0d840..da68d1509c 100644 --- a/charts/consul/templates/server-snapshot-agent-configmap.yaml +++ b/charts/consul/templates/server-snapshot-agent-configmap.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.server.snapshotAgent.enabled }} apiVersion: v1 kind: ConfigMap diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index e389509d88..8b73306fd7 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if and .Values.global.federation.enabled .Values.global.adminPartitions.enabled }}{{ fail "If global.federation.enabled is true, global.adminPartitions.enabled must be false because they are mutually exclusive" }}{{ end }} {{- if and .Values.global.federation.enabled (not .Values.global.tls.enabled) }}{{ fail "If global.federation.enabled is true, global.tls.enabled must be true because federation is only supported with TLS enabled" }}{{ end }} diff --git a/charts/consul/templates/sync-catalog-clusterrole.yaml b/charts/consul/templates/sync-catalog-clusterrole.yaml index 5055530f6e..0b0837c0df 100644 --- a/charts/consul/templates/sync-catalog-clusterrole.yaml +++ b/charts/consul/templates/sync-catalog-clusterrole.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $syncEnabled := (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} {{- if $syncEnabled }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/sync-catalog-clusterrolebinding.yaml b/charts/consul/templates/sync-catalog-clusterrolebinding.yaml index f5e3e0a98a..818823cca3 100644 --- a/charts/consul/templates/sync-catalog-clusterrolebinding.yaml +++ b/charts/consul/templates/sync-catalog-clusterrolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $syncEnabled := (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} {{- if $syncEnabled }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index 94d97fcfc1..f2815d9627 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} {{- template "consul.reservedNamesFailer" (list .Values.syncCatalog.consulNamespaces.consulDestinationNamespace "syncCatalog.consulNamespaces.consulDestinationNamespace") }} {{ template "consul.validateRequiredCloudSecretsExist" . }} diff --git a/charts/consul/templates/sync-catalog-podsecuritypolicy.yaml b/charts/consul/templates/sync-catalog-podsecuritypolicy.yaml index 0737265591..cc70feaab1 100644 --- a/charts/consul/templates/sync-catalog-podsecuritypolicy.yaml +++ b/charts/consul/templates/sync-catalog-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled))) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy diff --git a/charts/consul/templates/sync-catalog-serviceaccount.yaml b/charts/consul/templates/sync-catalog-serviceaccount.yaml index a5b6023956..deab1ad075 100644 --- a/charts/consul/templates/sync-catalog-serviceaccount.yaml +++ b/charts/consul/templates/sync-catalog-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- $syncEnabled := (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} {{- if $syncEnabled }} apiVersion: v1 diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index 2fcc27448f..2f2cb9a921 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.terminatingGateways.enabled }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} diff --git a/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml b/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml index a529f902bd..97ad2af961 100644 --- a/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml +++ b/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and .Values.global.enablePodSecurityPolicies .Values.terminatingGateways.enabled) }} {{- $root := . }} {{- range .Values.terminatingGateways.gateways }} diff --git a/charts/consul/templates/terminating-gateways-role.yaml b/charts/consul/templates/terminating-gateways-role.yaml index 71e3cd0e1e..4ae280ca81 100644 --- a/charts/consul/templates/terminating-gateways-role.yaml +++ b/charts/consul/templates/terminating-gateways-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.terminatingGateways.enabled }} {{- $root := . }} diff --git a/charts/consul/templates/terminating-gateways-rolebinding.yaml b/charts/consul/templates/terminating-gateways-rolebinding.yaml index cf6d533e91..4271f8f59c 100644 --- a/charts/consul/templates/terminating-gateways-rolebinding.yaml +++ b/charts/consul/templates/terminating-gateways-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.terminatingGateways.enabled }} {{- $root := . }} {{- range .Values.terminatingGateways.gateways }} diff --git a/charts/consul/templates/terminating-gateways-service.yaml b/charts/consul/templates/terminating-gateways-service.yaml index 8515897037..124900e727 100644 --- a/charts/consul/templates/terminating-gateways-service.yaml +++ b/charts/consul/templates/terminating-gateways-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.terminatingGateways.enabled }} {{- $root := . }} diff --git a/charts/consul/templates/terminating-gateways-serviceaccount.yaml b/charts/consul/templates/terminating-gateways-serviceaccount.yaml index 47f65fb937..211fb5c72f 100644 --- a/charts/consul/templates/terminating-gateways-serviceaccount.yaml +++ b/charts/consul/templates/terminating-gateways-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.terminatingGateways.enabled }} {{- $root := . }} {{- $defaults := .Values.terminatingGateways.defaults }} diff --git a/charts/consul/templates/tests/test-runner.yaml b/charts/consul/templates/tests/test-runner.yaml index 985605c2e5..b8b078003b 100644 --- a/charts/consul/templates/tests/test-runner.yaml +++ b/charts/consul/templates/tests/test-runner.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if .Values.tests.enabled }} apiVersion: v1 kind: Pod diff --git a/charts/consul/templates/tls-init-cleanup-job.yaml b/charts/consul/templates/tls-init-cleanup-job.yaml index cfc389ca44..ba29bb84ae 100644 --- a/charts/consul/templates/tls-init-cleanup-job.yaml +++ b/charts/consul/templates/tls-init-cleanup-job.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/tls-init-cleanup-podsecuritypolicy.yaml index db28affcc2..ed99d5f297 100644 --- a/charts/consul/templates/tls-init-cleanup-podsecuritypolicy.yaml +++ b/charts/consul/templates/tls-init-cleanup-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and (and .Values.global.tls.enabled .Values.global.enablePodSecurityPolicies) (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-cleanup-role.yaml b/charts/consul/templates/tls-init-cleanup-role.yaml index 00a70aee17..aa66e3edc4 100644 --- a/charts/consul/templates/tls-init-cleanup-role.yaml +++ b/charts/consul/templates/tls-init-cleanup-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-cleanup-rolebinding.yaml b/charts/consul/templates/tls-init-cleanup-rolebinding.yaml index a2abcc4b71..0d3bfe38e7 100644 --- a/charts/consul/templates/tls-init-cleanup-rolebinding.yaml +++ b/charts/consul/templates/tls-init-cleanup-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-cleanup-serviceaccount.yaml b/charts/consul/templates/tls-init-cleanup-serviceaccount.yaml index 60f1e7402f..57e40dd3af 100644 --- a/charts/consul/templates/tls-init-cleanup-serviceaccount.yaml +++ b/charts/consul/templates/tls-init-cleanup-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index d27e31d44b..d002ae7a75 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-podsecuritypolicy.yaml b/charts/consul/templates/tls-init-podsecuritypolicy.yaml index 936097cee9..5d2a393955 100644 --- a/charts/consul/templates/tls-init-podsecuritypolicy.yaml +++ b/charts/consul/templates/tls-init-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and (and .Values.global.tls.enabled .Values.global.enablePodSecurityPolicies) (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-role.yaml b/charts/consul/templates/tls-init-role.yaml index 49a15ad479..216602ee9f 100644 --- a/charts/consul/templates/tls-init-role.yaml +++ b/charts/consul/templates/tls-init-role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-rolebinding.yaml b/charts/consul/templates/tls-init-rolebinding.yaml index 3217a80909..9b68d97d8c 100644 --- a/charts/consul/templates/tls-init-rolebinding.yaml +++ b/charts/consul/templates/tls-init-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/tls-init-serviceaccount.yaml b/charts/consul/templates/tls-init-serviceaccount.yaml index 6f3b9407b1..f8504da94c 100644 --- a/charts/consul/templates/tls-init-serviceaccount.yaml +++ b/charts/consul/templates/tls-init-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (and .Values.global.tls.enabled (not .Values.server.serverCert.secretName)) }} {{- if not .Values.global.secretsBackend.vault.enabled }} diff --git a/charts/consul/templates/ui-ingress.yaml b/charts/consul/templates/ui-ingress.yaml index b1b5dbc8fa..0414a7cc2d 100644 --- a/charts/consul/templates/ui-ingress.yaml +++ b/charts/consul/templates/ui-ingress.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) (or (and (ne (.Values.ui.enabled | toString) "-") .Values.ui.enabled) (and (eq (.Values.ui.enabled | toString) "-") .Values.global.enabled)) (or (and (ne (.Values.ui.service.enabled | toString) "-") .Values.ui.service.enabled) (and (eq (.Values.ui.service.enabled | toString) "-") .Values.global.enabled))) }} {{- if (and (ne (.Values.ui.ingress.enabled | toString) "-") .Values.ui.ingress.enabled) }} {{- $serviceName := printf "%s-%s" (include "consul.fullname" .) "ui" -}} diff --git a/charts/consul/templates/ui-service.yaml b/charts/consul/templates/ui-service.yaml index 6a8045082e..dc2abf4fc5 100644 --- a/charts/consul/templates/ui-service.yaml +++ b/charts/consul/templates/ui-service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{- if (and (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) (or (and (ne (.Values.ui.enabled | toString) "-") .Values.ui.enabled) (and (eq (.Values.ui.enabled | toString) "-") .Values.global.enabled)) (or (and (ne (.Values.ui.service.enabled | toString) "-") .Values.ui.service.enabled) (and (eq (.Values.ui.service.enabled | toString) "-") .Values.global.enabled))) }} # UI Service for Consul Server apiVersion: v1 diff --git a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml index d7b89db85e..e13e2dc741 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml index fa9b6f46c5..472ef4ee1d 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/consul/templates/webhook-cert-manager-configmap.yaml b/charts/consul/templates/webhook-cert-manager-configmap.yaml index c4b747a795..293dd32d9f 100644 --- a/charts/consul/templates/webhook-cert-manager-configmap.yaml +++ b/charts/consul/templates/webhook-cert-manager-configmap.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index 558a5d43d5..dd93c039d2 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: apps/v1 diff --git a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml index d4e5413d52..4d685edc39 100644 --- a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml +++ b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled))) }} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} diff --git a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml index c4f594945f..68c54f3c27 100644 --- a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml +++ b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - {{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 From c6068ca1efa659c30beb8fc2301a197100374507 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 28 Mar 2023 09:47:28 -0400 Subject: [PATCH 109/592] fix copywrite ignore file --- .copywrite.hcl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.copywrite.hcl b/.copywrite.hcl index 6a47e1061d..9c2e0c0310 100644 --- a/.copywrite.hcl +++ b/.copywrite.hcl @@ -8,7 +8,9 @@ project { # Supports doublestar glob patterns for more flexibility in defining which # files or folders should be ignored header_ignore = [ - # "vendors/**", - # "**autogen**", + + # ignoring charts templates as adding copyright headers breaks all tests + "charts/consul/templates/**", + ] } From faf41e865262f34f9be6d5be95e9ec7a5180c2c9 Mon Sep 17 00:00:00 2001 From: Maliz Date: Fri, 24 Mar 2023 13:23:26 -0700 Subject: [PATCH 110/592] add failover policy to service resolver annd proxy default --- .../consul/templates/crd-proxydefaults.yaml | 8 +++++ .../templates/crd-serviceresolvers.yaml | 8 +++++ .../api/v1alpha1/proxydefaults_types.go | 4 +++ .../api/v1alpha1/proxydefaults_types_test.go | 12 +++++++ .../api/v1alpha1/serviceresolver_types.go | 9 +++-- .../v1alpha1/serviceresolver_types_test.go | 36 +++++++++++++++++++ control-plane/api/v1alpha1/shared_types.go | 29 +++++++++++++++ .../consul.hashicorp.com_proxydefaults.yaml | 8 +++++ ...consul.hashicorp.com_serviceresolvers.yaml | 8 +++++ control-plane/go.mod | 2 +- control-plane/go.sum | 6 ++++ 11 files changed, 127 insertions(+), 3 deletions(-) diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 749f2e4257..1d78d687a7 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -143,6 +143,14 @@ spec: type: object type: array type: object + failoverPolicy: + description: FailoverPolicy specifies the exact mechanism used for failover. + properties: + mode: + description: Mode specifies the type of failover that will be performed. + Valid values are "default", "" (equivalent to "default") and "order-by-locality". + type: string + type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index e058052e97..16d46b9615 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -79,6 +79,14 @@ spec: service from to form the failover group of instances. If empty the current namespace is used. type: string + policy: + description: FailoverPolicy specifies the exact mechanism used for failover. + properties: + mode: + description: Mode specifies the type of failover that will be performed. + Valid values are "default", "" (equivalent to "default") and "order-by-locality". + type: string + type: object service: description: Service is the service to resolve instead of the default as the failover group of instances during failover. diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index 8a211aed56..f83b12ecfe 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -82,6 +82,8 @@ type ProxyDefaultsSpec struct { AccessLogs *AccessLogs `json:"accessLogs,omitempty"` // EnvoyExtensions are a list of extensions to modify Envoy proxy configuration. EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` + // FailoverPolicy specifies the exact mechanism used for failover. + FailoverPolicy *FailoverPolicy `json:"failoverPolicy,omitempty"` } func (in *ProxyDefaults) GetObjectMeta() metav1.ObjectMeta { @@ -174,6 +176,7 @@ func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry { TransparentProxy: in.Spec.TransparentProxy.toConsul(), AccessLogs: in.Spec.AccessLogs.toConsul(), EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), + FailoverPolicy: in.Spec.FailoverPolicy.toConsul(), Meta: meta(datacenter), } } @@ -209,6 +212,7 @@ func (in *ProxyDefaults) Validate(_ common.ConsulMeta) error { } allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) + allErrs = append(allErrs, in.Spec.FailoverPolicy.validate(path.Child("failoverPolicy"))...) if len(allErrs) > 0 { return apierrors.NewInvalid( diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 011ab7d724..c194db2b9a 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -93,6 +93,9 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Required: true, }, }, + FailoverPolicy: &FailoverPolicy{ + Mode: "default", + }, }, }, Theirs: &capi.ProxyConfigEntry{ @@ -151,6 +154,9 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Required: true, }, }, + FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ + Mode: "default", + }, }, Matches: true, }, @@ -303,6 +309,9 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Required: true, }, }, + FailoverPolicy: &FailoverPolicy{ + Mode: "default", + }, }, }, Exp: &capi.ProxyConfigEntry{ @@ -362,6 +371,9 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Required: true, }, }, + FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ + Mode: "default", + }, Meta: map[string]string{ common.SourceKey: common.SourceValue, common.DatacenterKey: "datacenter", diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 58711dfde6..4bf6f5f4ad 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -131,6 +131,8 @@ type ServiceResolverFailover struct { Datacenters []string `json:"datacenters,omitempty"` // Targets specifies a fixed list of failover targets to try during failover. Targets []ServiceResolverFailoverTarget `json:"targets,omitempty"` + // Policy specifies the exact mechanism used for failover. + Policy *FailoverPolicy `json:"policy,omitempty"` } type ServiceResolverFailoverTarget struct { @@ -398,6 +400,9 @@ func (in ServiceResolverFailover) toConsul() capi.ServiceResolverFailover { Namespace: in.Namespace, Datacenters: in.Datacenters, Targets: targets, + Policy: &capi.ServiceResolverFailoverPolicy { + Mode: in.Policy.Mode, + }, } } @@ -507,7 +512,7 @@ func (in *ServiceResolver) validateEnterprise(consulMeta common.ConsulMeta) fiel } func (in *ServiceResolverFailover) isEmpty() bool { - return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 + return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 && in.Policy == nil } func (in *ServiceResolverFailover) validate(path *field.Path) *field.Error { @@ -515,7 +520,7 @@ func (in *ServiceResolverFailover) validate(path *field.Path) *field.Error { // NOTE: We're passing "{}" here as our value because we know that the // error is we have an empty object. return field.Invalid(path, "{}", - "service, serviceSubset, namespace, datacenters, and targets cannot all be empty at once") + "service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once") } return nil } diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index b3c552172f..6ac9e71999 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -70,18 +70,27 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { ServiceSubset: "failover_subset1", Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, + Policy: &FailoverPolicy{ + Mode: "default", + }, }, "failover2": { Service: "failover2", ServiceSubset: "failover_subset2", Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, + Policy: &FailoverPolicy{ + Mode: "", + }, }, "failover3": { Targets: []ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, + Policy: &FailoverPolicy{ + Mode: "order-by-locality", + }, }, }, ConnectTimeout: metav1.Duration{Duration: 1 * time.Second}, @@ -137,18 +146,27 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { ServiceSubset: "failover_subset1", Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, + Policy: &capi.ServiceResolverFailoverPolicy{ + Mode: "default", + }, }, "failover2": { Service: "failover2", ServiceSubset: "failover_subset2", Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, + Policy: &capi.ServiceResolverFailoverPolicy{ + Mode: "", + }, }, "failover3": { Targets: []capi.ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, + Policy: &capi.ServiceResolverFailoverPolicy{ + Mode: "order-by-locality", + }, }, }, ConnectTimeout: 1 * time.Second, @@ -253,18 +271,27 @@ func TestServiceResolver_ToConsul(t *testing.T) { ServiceSubset: "failover_subset1", Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, + Policy: &FailoverPolicy{ + Mode: "default", + }, }, "failover2": { Service: "failover2", ServiceSubset: "failover_subset2", Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, + Policy: &FailoverPolicy{ + Mode: "", + }, }, "failover3": { Targets: []ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, + Policy: &FailoverPolicy{ + Mode: "order-by-locality", + }, }, }, ConnectTimeout: metav1.Duration{Duration: 1 * time.Second}, @@ -320,18 +347,27 @@ func TestServiceResolver_ToConsul(t *testing.T) { ServiceSubset: "failover_subset1", Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, + Policy: &capi.ServiceResolverFailoverPolicy{ + Mode: "default", + }, }, "failover2": { Service: "failover2", ServiceSubset: "failover_subset2", Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, + Policy: &capi.ServiceResolverFailoverPolicy{ + Mode: "", + }, }, "failover3": { Targets: []capi.ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, + Policy: &capi.ServiceResolverFailoverPolicy{ + Mode: "order-by-locality", + }, }, }, ConnectTimeout: 1 * time.Second, diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 28d53b8926..3e98a41b89 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -245,6 +245,35 @@ func (in EnvoyExtension) validate(path *field.Path) *field.Error { return nil } +// FailoverPolicy specifies the exact mechanism used for failover. +type FailoverPolicy struct { + // Mode specifies the type of failover that will be performed. Valid values are + // "default", "" (equivalent to "default") and "order-by-locality". + Mode string `json:",omitempty"` +} + +func (in *FailoverPolicy) toConsul() *capi.ServiceResolverFailoverPolicy { + if in == nil { + return nil + } + + return &capi.ServiceResolverFailoverPolicy{ + Mode: in.Mode, + } +} + +func (in *FailoverPolicy) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if in == nil { + return nil + } + modes := []string{"default", "order-by-locality"} + if !sliceContains(modes, in.Mode) { + errs = append(errs, field.Invalid(path.Child("mode"), in.Mode, notInSliceMessage(modes))) + } + return errs +} + func notInSliceMessage(slice []string) string { return fmt.Sprintf(`must be one of "%s"`, strings.Join(slice, `", "`)) } diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 5c8d4a5082..a2cd1e539d 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -139,6 +139,14 @@ spec: type: object type: array type: object + failoverPolicy: + description: FailoverPolicy specifies the exact mechanism used for failover. + properties: + mode: + description: Mode specifies the type of failover that will be performed. + Valid values are "default", "" (equivalent to "default") and "order-by-locality". + type: string + type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index 9bd5096376..da2f665fcc 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -75,6 +75,14 @@ spec: service from to form the failover group of instances. If empty the current namespace is used. type: string + policy: + description: FailoverPolicy specifies the exact mechanism used for failover. + properties: + mode: + description: Mode specifies the type of failover that will be performed. + Valid values are "default", "" (equivalent to "default") and "order-by-locality". + type: string + type: object service: description: Service is the service to resolve instead of the default as the failover group of instances during failover. diff --git a/control-plane/go.mod b/control-plane/go.mod index b0d67085ec..d5cc893d58 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.20.0 + github.com/hashicorp/consul/api v1.10.1-0.20230324190300-a168d0e667ac github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 diff --git a/control-plane/go.sum b/control-plane/go.sum index 06dfbb8b10..94e3b6f51c 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -353,6 +353,12 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.10.1-0.20230315182124-152c75349ea9 h1:tWnkjV/2K5i6nbY68pNniulRLMwE3U7rwr+O3zLN7xg= +github.com/hashicorp/consul/api v1.10.1-0.20230315182124-152c75349ea9/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= +github.com/hashicorp/consul/api v1.10.1-0.20230317144806-eaa39f4ef59a h1:eXpcnIZKlLaSM+aCp6cvEbNDRao7mqqM9DkdCvIFVN8= +github.com/hashicorp/consul/api v1.10.1-0.20230317144806-eaa39f4ef59a/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= +github.com/hashicorp/consul/api v1.10.1-0.20230324190300-a168d0e667ac h1:jl7+FYcrlMS/m9SLCz4Ehm2qh/vueJstPEXkIjQj6ug= +github.com/hashicorp/consul/api v1.10.1-0.20230324190300-a168d0e667ac/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= From fd2b2655aa313947f554364a4c87bf44642a6f01 Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 28 Mar 2023 09:14:12 -0700 Subject: [PATCH 111/592] change default mode to sequential --- charts/consul/templates/crd-proxydefaults.yaml | 2 +- charts/consul/templates/crd-serviceresolvers.yaml | 2 +- control-plane/api/v1alpha1/proxydefaults_types_test.go | 8 ++++---- control-plane/api/v1alpha1/serviceresolver_types.go | 2 +- control-plane/api/v1alpha1/serviceresolver_types_test.go | 8 ++++---- control-plane/api/v1alpha1/shared_types.go | 2 +- .../crd/bases/consul.hashicorp.com_proxydefaults.yaml | 2 +- .../crd/bases/consul.hashicorp.com_serviceresolvers.yaml | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 1d78d687a7..550943762b 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -148,7 +148,7 @@ spec: properties: mode: description: Mode specifies the type of failover that will be performed. - Valid values are "default", "" (equivalent to "default") and "order-by-locality". + Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". type: string type: object meshGateway: diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 16d46b9615..12d6070e51 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -84,7 +84,7 @@ spec: properties: mode: description: Mode specifies the type of failover that will be performed. - Valid values are "default", "" (equivalent to "default") and "order-by-locality". + Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". type: string type: object service: diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index c194db2b9a..19371dae1c 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -94,7 +94,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { }, }, FailoverPolicy: &FailoverPolicy{ - Mode: "default", + Mode: "sequential", }, }, }, @@ -155,7 +155,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { }, }, FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ - Mode: "default", + Mode: "sequential", }, }, Matches: true, @@ -310,7 +310,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) { }, }, FailoverPolicy: &FailoverPolicy{ - Mode: "default", + Mode: "sequential", }, }, }, @@ -372,7 +372,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) { }, }, FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ - Mode: "default", + Mode: "sequential", }, Meta: map[string]string{ common.SourceKey: common.SourceValue, diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 4bf6f5f4ad..576be448fb 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -400,7 +400,7 @@ func (in ServiceResolverFailover) toConsul() capi.ServiceResolverFailover { Namespace: in.Namespace, Datacenters: in.Datacenters, Targets: targets, - Policy: &capi.ServiceResolverFailoverPolicy { + Policy: &capi.ServiceResolverFailoverPolicy{ Mode: in.Policy.Mode, }, } diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index 6ac9e71999..fbf1dd9cf2 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -71,7 +71,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, Policy: &FailoverPolicy{ - Mode: "default", + Mode: "sequential", }, }, "failover2": { @@ -147,7 +147,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "default", + Mode: "sequential", }, }, "failover2": { @@ -272,7 +272,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, Policy: &FailoverPolicy{ - Mode: "default", + Mode: "sequential", }, }, "failover2": { @@ -348,7 +348,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "default", + Mode: "sequential", }, }, "failover2": { diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 3e98a41b89..8f000f4087 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -267,7 +267,7 @@ func (in *FailoverPolicy) validate(path *field.Path) field.ErrorList { if in == nil { return nil } - modes := []string{"default", "order-by-locality"} + modes := []string{"sequential", "order-by-locality"} if !sliceContains(modes, in.Mode) { errs = append(errs, field.Invalid(path.Child("mode"), in.Mode, notInSliceMessage(modes))) } diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index a2cd1e539d..a277d6b9a9 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -144,7 +144,7 @@ spec: properties: mode: description: Mode specifies the type of failover that will be performed. - Valid values are "default", "" (equivalent to "default") and "order-by-locality". + Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". type: string type: object meshGateway: diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index da2f665fcc..508e754c58 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -80,7 +80,7 @@ spec: properties: mode: description: Mode specifies the type of failover that will be performed. - Valid values are "default", "" (equivalent to "default") and "order-by-locality". + Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". type: string type: object service: From ec4165a8499ecbe0bd2d47764c21abdfa5f4590a Mon Sep 17 00:00:00 2001 From: Maliz Date: Wed, 29 Mar 2023 11:49:22 -0700 Subject: [PATCH 112/592] add changelog --- .changelog/2030.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/2030.txt diff --git a/.changelog/2030.txt b/.changelog/2030.txt new file mode 100644 index 0000000000..46516d9513 --- /dev/null +++ b/.changelog/2030.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: add failover policy field to service resolver and proxy default CRDs +``` \ No newline at end of file From 6c82e99a31ae056e8b948e1fee4d0c6c2f53049d Mon Sep 17 00:00:00 2001 From: Maliz Date: Fri, 31 Mar 2023 12:57:51 -0700 Subject: [PATCH 113/592] add region field to failover policy --- .../consul/templates/crd-proxydefaults.yaml | 6 ++++ .../templates/crd-serviceresolvers.yaml | 6 ++++ .../api/v1alpha1/proxydefaults_types_test.go | 12 ++++--- .../api/v1alpha1/serviceresolver_types.go | 3 +- .../v1alpha1/serviceresolver_types_test.go | 36 ++++++++++++------- control-plane/api/v1alpha1/shared_types.go | 6 ++-- .../consul.hashicorp.com_proxydefaults.yaml | 6 ++++ ...consul.hashicorp.com_serviceresolvers.yaml | 6 ++++ control-plane/go.mod | 2 +- control-plane/go.sum | 4 +++ 10 files changed, 67 insertions(+), 20 deletions(-) diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 550943762b..aaaa3228a1 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -150,6 +150,12 @@ spec: description: Mode specifies the type of failover that will be performed. Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". type: string + regions: + description: The ordered list of the regions of the failover targets. + Valid values can be "us-west-1", "us-west-2", and so on. + items: + type: string + type: array type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 12d6070e51..842506d642 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -86,6 +86,12 @@ spec: description: Mode specifies the type of failover that will be performed. Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". type: string + regions: + description: The ordered list of the regions of the failover targets. + Valid values can be "us-west-1", "us-west-2", and so on. + items: + type: string + type: array type: object service: description: Service is the service to resolve instead of the diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 19371dae1c..f056281a34 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -94,7 +94,8 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { }, }, FailoverPolicy: &FailoverPolicy{ - Mode: "sequential", + Mode: "sequential", + Regions: []string{"us-west-1"}, }, }, }, @@ -155,7 +156,8 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { }, }, FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", + Mode: "sequential", + Regions: []string{"us-west-1"}, }, }, Matches: true, @@ -310,7 +312,8 @@ func TestProxyDefaults_ToConsul(t *testing.T) { }, }, FailoverPolicy: &FailoverPolicy{ - Mode: "sequential", + Mode: "sequential", + Regions: []string{"us-west-1"}, }, }, }, @@ -372,7 +375,8 @@ func TestProxyDefaults_ToConsul(t *testing.T) { }, }, FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", + Mode: "sequential", + Regions: []string{"us-west-1"}, }, Meta: map[string]string{ common.SourceKey: common.SourceValue, diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 576be448fb..48483550ba 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -401,7 +401,8 @@ func (in ServiceResolverFailover) toConsul() capi.ServiceResolverFailover { Datacenters: in.Datacenters, Targets: targets, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: in.Policy.Mode, + Mode: in.Policy.Mode, + Regions: in.Policy.Regions, }, } } diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index fbf1dd9cf2..ed92fa9f60 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -71,7 +71,8 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, Policy: &FailoverPolicy{ - Mode: "sequential", + Mode: "sequential", + Regions: []string{"us-west-2"}, }, }, "failover2": { @@ -80,7 +81,8 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, Policy: &FailoverPolicy{ - Mode: "", + Mode: "", + Regions: []string{"us-west-1"}, }, }, "failover3": { @@ -89,7 +91,8 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, Policy: &FailoverPolicy{ - Mode: "order-by-locality", + Mode: "order-by-locality", + Regions: []string{"us-east-1"}, }, }, }, @@ -147,7 +150,8 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", + Mode: "sequential", + Regions: []string{"us-west-2"}, }, }, "failover2": { @@ -156,7 +160,8 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "", + Mode: "", + Regions: []string{"us-west-1"}, }, }, "failover3": { @@ -165,7 +170,8 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "order-by-locality", + Mode: "order-by-locality", + Regions: []string{"us-east-1"}, }, }, }, @@ -272,7 +278,8 @@ func TestServiceResolver_ToConsul(t *testing.T) { Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, Policy: &FailoverPolicy{ - Mode: "sequential", + Mode: "sequential", + Regions: []string{"us-west-2"}, }, }, "failover2": { @@ -281,7 +288,8 @@ func TestServiceResolver_ToConsul(t *testing.T) { Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, Policy: &FailoverPolicy{ - Mode: "", + Mode: "", + Regions: []string{"us-west-1"}, }, }, "failover3": { @@ -290,7 +298,8 @@ func TestServiceResolver_ToConsul(t *testing.T) { {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, Policy: &FailoverPolicy{ - Mode: "order-by-locality", + Mode: "order-by-locality", + Regions: []string{"us-east-1"}, }, }, }, @@ -348,7 +357,8 @@ func TestServiceResolver_ToConsul(t *testing.T) { Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", + Mode: "sequential", + Regions: []string{"us-west-2"}, }, }, "failover2": { @@ -357,7 +367,8 @@ func TestServiceResolver_ToConsul(t *testing.T) { Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "", + Mode: "", + Regions: []string{"us-west-1"}, }, }, "failover3": { @@ -366,7 +377,8 @@ func TestServiceResolver_ToConsul(t *testing.T) { {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "order-by-locality", + Mode: "order-by-locality", + Regions: []string{"us-east-1"}, }, }, }, diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 8f000f4087..c0c59963c6 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -249,7 +249,8 @@ func (in EnvoyExtension) validate(path *field.Path) *field.Error { type FailoverPolicy struct { // Mode specifies the type of failover that will be performed. Valid values are // "default", "" (equivalent to "default") and "order-by-locality". - Mode string `json:",omitempty"` + Mode string `json:",omitempty"` + Regions []string `json:",omitempty"` } func (in *FailoverPolicy) toConsul() *capi.ServiceResolverFailoverPolicy { @@ -258,7 +259,8 @@ func (in *FailoverPolicy) toConsul() *capi.ServiceResolverFailoverPolicy { } return &capi.ServiceResolverFailoverPolicy{ - Mode: in.Mode, + Mode: in.Mode, + Regions: in.Regions, } } diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index a277d6b9a9..0f90e95e16 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -146,6 +146,12 @@ spec: description: Mode specifies the type of failover that will be performed. Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". type: string + regions: + description: The ordered list of the regions of the failover targets. + Valid values can be "us-west-1", "us-west-2", and so on. + items: + type: string + type: array type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index 508e754c58..a36784cc77 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -82,6 +82,12 @@ spec: description: Mode specifies the type of failover that will be performed. Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". type: string + regions: + description: The ordered list of the regions of the failover targets. + Valid values can be "us-west-1", "us-west-2", and so on. + items: + type: string + type: array type: object service: description: Service is the service to resolve instead of the diff --git a/control-plane/go.mod b/control-plane/go.mod index d5cc893d58..b139e1cd3f 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.10.1-0.20230324190300-a168d0e667ac + github.com/hashicorp/consul/api v1.10.1-0.20230331190547-fc64a702f43c github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 diff --git a/control-plane/go.sum b/control-plane/go.sum index 94e3b6f51c..e108ac64b0 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -359,6 +359,10 @@ github.com/hashicorp/consul/api v1.10.1-0.20230317144806-eaa39f4ef59a h1:eXpcnIZ github.com/hashicorp/consul/api v1.10.1-0.20230317144806-eaa39f4ef59a/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= github.com/hashicorp/consul/api v1.10.1-0.20230324190300-a168d0e667ac h1:jl7+FYcrlMS/m9SLCz4Ehm2qh/vueJstPEXkIjQj6ug= github.com/hashicorp/consul/api v1.10.1-0.20230324190300-a168d0e667ac/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= +github.com/hashicorp/consul/api v1.10.1-0.20230329135459-3e69ff1cd299 h1:JAevohOFaTL9XDpzYwN/2mkGPBm2QxTNcN41lys13Yc= +github.com/hashicorp/consul/api v1.10.1-0.20230329135459-3e69ff1cd299/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= +github.com/hashicorp/consul/api v1.10.1-0.20230331190547-fc64a702f43c h1:MfDuWW38RozPodXT2aGc5jakoYo9EjgYpZl+vR3//Wg= +github.com/hashicorp/consul/api v1.10.1-0.20230331190547-fc64a702f43c/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= From b4e313e26a4c423925cd0585879bc2712ce47b73 Mon Sep 17 00:00:00 2001 From: Maliz Date: Fri, 31 Mar 2023 13:26:48 -0700 Subject: [PATCH 114/592] go mod tidy --- control-plane/go.sum | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/control-plane/go.sum b/control-plane/go.sum index e108ac64b0..d794bad47f 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -353,18 +353,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20230315182124-152c75349ea9 h1:tWnkjV/2K5i6nbY68pNniulRLMwE3U7rwr+O3zLN7xg= -github.com/hashicorp/consul/api v1.10.1-0.20230315182124-152c75349ea9/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= -github.com/hashicorp/consul/api v1.10.1-0.20230317144806-eaa39f4ef59a h1:eXpcnIZKlLaSM+aCp6cvEbNDRao7mqqM9DkdCvIFVN8= -github.com/hashicorp/consul/api v1.10.1-0.20230317144806-eaa39f4ef59a/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= -github.com/hashicorp/consul/api v1.10.1-0.20230324190300-a168d0e667ac h1:jl7+FYcrlMS/m9SLCz4Ehm2qh/vueJstPEXkIjQj6ug= -github.com/hashicorp/consul/api v1.10.1-0.20230324190300-a168d0e667ac/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= -github.com/hashicorp/consul/api v1.10.1-0.20230329135459-3e69ff1cd299 h1:JAevohOFaTL9XDpzYwN/2mkGPBm2QxTNcN41lys13Yc= -github.com/hashicorp/consul/api v1.10.1-0.20230329135459-3e69ff1cd299/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= github.com/hashicorp/consul/api v1.10.1-0.20230331190547-fc64a702f43c h1:MfDuWW38RozPodXT2aGc5jakoYo9EjgYpZl+vR3//Wg= github.com/hashicorp/consul/api v1.10.1-0.20230331190547-fc64a702f43c/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= -github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= -github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= From adbca8052c1a651a014f29d7562f6058c60e27ad Mon Sep 17 00:00:00 2001 From: Maliz Date: Fri, 31 Mar 2023 14:17:44 -0700 Subject: [PATCH 115/592] add test for validate function --- .../api/v1alpha1/proxydefaults_types_test.go | 13 +++++++++++++ control-plane/api/v1alpha1/shared_types.go | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index f056281a34..16642ad277 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -624,6 +624,19 @@ func TestProxyDefaults_Validate(t *testing.T) { }, expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.envoyExtensions.envoyExtension[0].arguments: Invalid value: "{\"SOME_INVALID_JSON\"}": must be valid map value: invalid character '}' after object key`, }, + "failoverPolicy.mode invalid": { + input: &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + FailoverPolicy: &FailoverPolicy{ + Mode: "wrong-mode", + }, + }, + }, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.failoverPolicy.mode: Invalid value: "wrong-mode": must be one of "", "sequential", "order-by-locality"`, + }, "multi-error": { input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index c0c59963c6..fdac9a3fea 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -269,7 +269,7 @@ func (in *FailoverPolicy) validate(path *field.Path) field.ErrorList { if in == nil { return nil } - modes := []string{"sequential", "order-by-locality"} + modes := []string{"", "sequential", "order-by-locality"} if !sliceContains(modes, in.Mode) { errs = append(errs, field.Invalid(path.Child("mode"), in.Mode, notInSliceMessage(modes))) } From 5ec0a7f51c2235ea23404c45c572a7a96b912843 Mon Sep 17 00:00:00 2001 From: Maliz Date: Fri, 31 Mar 2023 14:54:13 -0700 Subject: [PATCH 116/592] update test for validate --- control-plane/api/v1alpha1/serviceresolver_types_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index ed92fa9f60..c82394784d 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -646,8 +646,8 @@ func TestServiceResolver_Validate(t *testing.T) { }, namespacesEnabled: false, expectedErrMsgs: []string{ - "spec.failover[failA]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, and targets cannot all be empty at once", - "spec.failover[failB]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, and targets cannot all be empty at once", + "spec.failover[failA]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", + "spec.failover[failB]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", }, }, "hashPolicy.field invalid": { From 36e3d5cdb1e8e6b1a137aa4d7e62b916698ad336 Mon Sep 17 00:00:00 2001 From: Maliz Date: Mon, 3 Apr 2023 15:10:27 -0700 Subject: [PATCH 117/592] update circle ci config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e0e59a56d9..cda1e54ba8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,7 +26,7 @@ aks-terraform-path: &aks-terraform-path charts/consul/test/terraform/aks openshift-terraform-path: &openshift-terraform-path charts/consul/test/terraform/openshift # This image is built from test/docker/Test.dockerfile consul-helm-test-image: &consul-helm-test-image docker.mirror.hashicorp.services/hashicorpdev/consul-helm-test:0.15.0 -consul-test-image: &consul-test-image hashicorppreview/consul-enterprise:1.15-dev +consul-test-image: &consul-test-image hashicorppreview/consul-enterprise:1.16-dev ######################## # COMMANDS From 3a18ca1674826b1ea80df0c8a7807abc87e3d9bc Mon Sep 17 00:00:00 2001 From: Maliz Date: Mon, 3 Apr 2023 15:12:34 -0700 Subject: [PATCH 118/592] update gha config --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6cb3ab1937..6e6212e0d8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,8 +8,8 @@ env: TEST_RESULTS: /tmp/test-results # path to where test results are saved GOTESTSUM_VERSION: 1.8.2 # You cannot use environment variables with workflows. The gotestsum version is hardcoded in the reusable workflows too. # We use docker images to copy the consul binary for unit tests. - CONSUL_OSS_DOCKER_IMAGE: hashicorppreview/consul:1.14-dev # Consul's OSS version to use in tests - CONSUL_ENT_DOCKER_IMAGE: hashicorppreview/consul-enterprise:1.14-dev # Consul's enterprise version to use in tests + CONSUL_OSS_DOCKER_IMAGE: hashicorppreview/consul:1.16-dev # Consul's OSS version to use in tests + CONSUL_ENT_DOCKER_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests jobs: terraform-fmt-check: From 58a1434ed923c4e29c16ac25935dcf6dbaccd3fa Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 11:06:10 -0700 Subject: [PATCH 119/592] update acl test error message --- .../controllers/endpoints/endpoints_controller_ent_test.go | 6 +++--- .../controllers/endpoints/endpoints_controller_test.go | 4 ++-- control-plane/subcommand/common/common.go | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 5c87c2442d..2a2eda3a3a 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1564,7 +1564,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(serviceID) { - require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+serviceID) require.NotNil(t, token) @@ -1822,7 +1822,7 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") } }) } @@ -2107,7 +2107,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") } }) } diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 19c78925a6..9de882ff89 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -3442,7 +3442,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(sID) { - require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+sID) require.NotNil(t, token) @@ -4040,7 +4040,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") } }) } diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index b7927e4490..b2ccc4be16 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -166,9 +166,9 @@ func ConsulLogin(client *api.Client, params LoginParams, log hclog.Logger) (stri // with that token. This is because clients talk to servers in the stale consistency mode // to decrease the load on the servers (see https://www.consul.io/docs/architecture/consensus#stale). // In that case, it's possible that the token isn't replicated - // to that server instance yet. The client will then get an "ACL not found" error + // to that server instance yet. The client will then get an "token does not exist: ACL not found" error // and subsequently cache this not found response. Then on any API call with the token, - // we will keep hitting the same "ACL not found" error + // we will keep hitting the same "token does not exist: ACL not found" error // until the cache entry expires (determined by the `acl_token_ttl` which defaults to 30 seconds). // This is not great because it will delay app start up time by 30 seconds in most cases // (if you are running 3 servers, then the probability of ending up on a follower is close to 2/3). @@ -182,7 +182,7 @@ func ConsulLogin(client *api.Client, params LoginParams, log hclog.Logger) (stri // for this call and the next call to reach different servers and those servers to have different // states from each other. // For example, this call can reach a leader and succeed, while the next call can go to a follower - // that is still behind the leader and get an "ACL not found" error. + // that is still behind the leader and get an "token does not exist: ACL not found" error. // However, this is a pretty unlikely case because // clients have sticky connections to a server, and those connections get rebalanced only every 2-3min. // And so, this workaround should work in a vast majority of cases. From 22211654e52c64403cc5a04f7b8128541ef2f607 Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 11:21:42 -0700 Subject: [PATCH 120/592] update acl test error message --- .../controllers/endpoints/endpoints_controller_ent_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 2a2eda3a3a..428d52039e 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1564,7 +1564,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(serviceID) { - require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+serviceID) require.NotNil(t, token) From 446e8c11a150edfc8b77c37ae86aeeeb989061ab Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 11:42:01 -0700 Subject: [PATCH 121/592] update acl test error message --- .../controllers/endpoints/endpoints_controller_ent_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 428d52039e..17609eba4f 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1564,7 +1564,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(serviceID) { - require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token not found in namespace kube: ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+serviceID) require.NotNil(t, token) From ac3289721890ba156a15dd4f62de27c74036a896 Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 12:00:00 -0700 Subject: [PATCH 122/592] update acl test --- .../controllers/endpoints/endpoints_controller_ent_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 17609eba4f..223fda9d8d 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1564,7 +1564,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(serviceID) { - require.EqualError(t, err, "Unexpected response code: 403 (token not found in namespace kube: ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token not found in namespace "+ns+": ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+serviceID) require.NotNil(t, token) From 7cebf6e7309be545403aa3f034f0f886fcf5c5cb Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 12:13:50 -0700 Subject: [PATCH 123/592] update acl test --- .../controllers/endpoints/endpoints_controller_ent_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 223fda9d8d..c902c9619e 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1564,7 +1564,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(serviceID) { - require.EqualError(t, err, "Unexpected response code: 403 (token not found in namespace "+ns+": ACL not found)") + require.EqualError(t, err, + fmt.Sprintf("Unexpected response code: 403 (token not found in namespace %s: ACL not found)", + ts.SourceKubeNS)) } else { require.NoError(t, err, "token should exist for service instance: "+serviceID) require.NotNil(t, token) From 064453be83435df9840973d56d328212b5d9a6a9 Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 13:33:03 -0700 Subject: [PATCH 124/592] update acl test --- .../controllers/endpoints/endpoints_controller_ent_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index c902c9619e..16c4ba4763 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1566,7 +1566,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { if deregisteredServices.Contains(serviceID) { require.EqualError(t, err, fmt.Sprintf("Unexpected response code: 403 (token not found in namespace %s: ACL not found)", - ts.SourceKubeNS)) + ts.ExpConsulNS)) } else { require.NoError(t, err, "token should exist for service instance: "+serviceID) require.NotNil(t, token) From e51fddf2e0e1ec946b152530a7c005323a10f25a Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 14:07:59 -0700 Subject: [PATCH 125/592] update acl test --- .../controllers/endpoints/endpoints_controller_ent_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 16c4ba4763..65f9874c97 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1824,7 +1824,9 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") + require.EqualError(t, err, + fmt.Sprintf("Unexpected response code: 403 (token not found in namespace %s: ACL not found)", + ts.ExpConsulNS)) } }) } @@ -2109,7 +2111,8 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") + require.EqualError(t, err, + "Unexpected response code: 403 (token not found in namespace default: ACL not found)") } }) } From 610c946bf3a25eb1f5fd85bd9cae443e59405c13 Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 14:24:04 -0700 Subject: [PATCH 126/592] update acl test --- .../controllers/endpoints/endpoints_controller_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 9de882ff89..4f4b289dd8 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -3442,7 +3442,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(sID) { - require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token not found in namespace default: ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+sID) require.NotNil(t, token) @@ -4040,7 +4040,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token not found in namespace default: ACL not found)") } }) } From 9a10ce1882edb51eeeae0aa8c91dc44b7b21be0a Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 15:06:29 -0700 Subject: [PATCH 127/592] update acl test in oss --- .../controllers/endpoints/endpoints_controller_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 4f4b289dd8..9de882ff89 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -3442,7 +3442,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(sID) { - require.EqualError(t, err, "Unexpected response code: 403 (token not found in namespace default: ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+sID) require.NotNil(t, token) @@ -4040,7 +4040,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, "Unexpected response code: 403 (token not found in namespace default: ACL not found)") + require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") } }) } From b10598edfeae4ac944eb524714ebefe0283404a4 Mon Sep 17 00:00:00 2001 From: Maliz Date: Tue, 4 Apr 2023 15:41:17 -0700 Subject: [PATCH 128/592] update acl test to pass in oss and ent --- .../controllers/endpoints/endpoints_controller_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 9de882ff89..c7751f20a8 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -3442,7 +3442,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(sID) { - require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") + require.Contains(t, err.Error(), "Unexpected response code: 403") } else { require.NoError(t, err, "token should exist for service instance: "+sID) require.NotNil(t, token) @@ -4040,7 +4040,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, "Unexpected response code: 403 (token does not exist: ACL not found)") + require.Contains(t, err.Error(), "Unexpected response code: 403") } }) } From 2cff14e07bd734dbd187a5bb9939aeb36282bb59 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Tue, 4 Apr 2023 23:34:25 -0400 Subject: [PATCH 129/592] Fix the indentation of the copyAnnotations example (#2037) --- charts/consul/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 42f08af255..73b805680b 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2927,7 +2927,7 @@ apiGateway: # ```yaml # service: # annotations: | - # - external-dns.alpha.kubernetes.io/hostname + # - external-dns.alpha.kubernetes.io/hostname # ``` # # @type: string From ed8325b514c08f9772987a4b5e4b4be0de24a8f0 Mon Sep 17 00:00:00 2001 From: Maliz Date: Wed, 5 Apr 2023 10:26:01 -0700 Subject: [PATCH 130/592] make all acl error comparisons consistent in tests --- .../endpoints/endpoints_controller_ent_test.go | 11 +++-------- .../endpoints/endpoints_controller_test.go | 4 ++-- control-plane/subcommand/common/common.go | 6 +++--- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 65f9874c97..abb84c7116 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1564,9 +1564,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(serviceID) { - require.EqualError(t, err, - fmt.Sprintf("Unexpected response code: 403 (token not found in namespace %s: ACL not found)", - ts.ExpConsulNS)) + require.Contains(t, err.Error(), "ACL not found") } else { require.NoError(t, err, "token should exist for service instance: "+serviceID) require.NotNil(t, token) @@ -1824,9 +1822,7 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, - fmt.Sprintf("Unexpected response code: 403 (token not found in namespace %s: ACL not found)", - ts.ExpConsulNS)) + require.Contains(t, err.Error(), "ACL not found") } }) } @@ -2111,8 +2107,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.EqualError(t, err, - "Unexpected response code: 403 (token not found in namespace default: ACL not found)") + require.Contains(t, err.Error(), "ACL not found") } }) } diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index c7751f20a8..7ea04a4e52 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -3442,7 +3442,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(sID) { - require.Contains(t, err.Error(), "Unexpected response code: 403") + require.Contains(t, err.Error(), "ACL not found") } else { require.NoError(t, err, "token should exist for service instance: "+sID) require.NotNil(t, token) @@ -4040,7 +4040,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.Contains(t, err.Error(), "Unexpected response code: 403") + require.Contains(t, err.Error(), "ACL not found") } }) } diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index b2ccc4be16..b7927e4490 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -166,9 +166,9 @@ func ConsulLogin(client *api.Client, params LoginParams, log hclog.Logger) (stri // with that token. This is because clients talk to servers in the stale consistency mode // to decrease the load on the servers (see https://www.consul.io/docs/architecture/consensus#stale). // In that case, it's possible that the token isn't replicated - // to that server instance yet. The client will then get an "token does not exist: ACL not found" error + // to that server instance yet. The client will then get an "ACL not found" error // and subsequently cache this not found response. Then on any API call with the token, - // we will keep hitting the same "token does not exist: ACL not found" error + // we will keep hitting the same "ACL not found" error // until the cache entry expires (determined by the `acl_token_ttl` which defaults to 30 seconds). // This is not great because it will delay app start up time by 30 seconds in most cases // (if you are running 3 servers, then the probability of ending up on a follower is close to 2/3). @@ -182,7 +182,7 @@ func ConsulLogin(client *api.Client, params LoginParams, log hclog.Logger) (stri // for this call and the next call to reach different servers and those servers to have different // states from each other. // For example, this call can reach a leader and succeed, while the next call can go to a follower - // that is still behind the leader and get an "token does not exist: ACL not found" error. + // that is still behind the leader and get an "ACL not found" error. // However, this is a pretty unlikely case because // clients have sticky connections to a server, and those connections get rebalanced only every 2-3min. // And so, this workaround should work in a vast majority of cases. From 2fb794450c8d64a502ebdb296f6836de7be06d59 Mon Sep 17 00:00:00 2001 From: John Murret Date: Mon, 10 Apr 2023 12:16:46 -0600 Subject: [PATCH 131/592] test image form consul-enterprise --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index cda1e54ba8..8ef947c103 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -177,6 +177,7 @@ commands: -enable-multi-cluster \ ${ENABLE_ENTERPRISE:+-enable-enterprise} \ -debug-directory="$TEST_RESULTS/debug" \ + -consul-image=hashicorpdev/consul:1bd3c09 \ -consul-k8s-image=<< parameters.consul-k8s-image >> then echo "Tests in ${pkg} failed, aborting early" @@ -208,6 +209,7 @@ commands: ${ENABLE_ENTERPRISE:+-enable-enterprise} \ -enable-multi-cluster \ -debug-directory="$TEST_RESULTS/debug" \ + -consul-image=hashicorpdev/consul:1bd3c09 \ -consul-k8s-image=<< parameters.consul-k8s-image >> ######################## From 0e771fcd49be3af400caec8da0f46e52ccd29b88 Mon Sep 17 00:00:00 2001 From: John Murret Date: Mon, 10 Apr 2023 12:17:35 -0600 Subject: [PATCH 132/592] Revert "test image form consul-enterprise" This reverts commit 2fb794450c8d64a502ebdb296f6836de7be06d59. --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8ef947c103..cda1e54ba8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -177,7 +177,6 @@ commands: -enable-multi-cluster \ ${ENABLE_ENTERPRISE:+-enable-enterprise} \ -debug-directory="$TEST_RESULTS/debug" \ - -consul-image=hashicorpdev/consul:1bd3c09 \ -consul-k8s-image=<< parameters.consul-k8s-image >> then echo "Tests in ${pkg} failed, aborting early" @@ -209,7 +208,6 @@ commands: ${ENABLE_ENTERPRISE:+-enable-enterprise} \ -enable-multi-cluster \ -debug-directory="$TEST_RESULTS/debug" \ - -consul-image=hashicorpdev/consul:1bd3c09 \ -consul-k8s-image=<< parameters.consul-k8s-image >> ######################## From 10c7a56500c0a750a1568c1a4055c44f8677fc37 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 13 Apr 2023 17:27:57 -0400 Subject: [PATCH 133/592] Convert acceptance to use github actions (#2046) * Terraform: increase node sizes * update GKE to use already created subnets * Dispatch: dispatch to consul-k8s-workflows --- .github/workflows/nightly-acceptance.yml | 27 ++ .github/workflows/pr.yml | 31 ++ .github/workflows/reusable-acceptance.yml | 159 ------- .github/workflows/reusable-golangci-lint.yml | 36 -- .github/workflows/reusable-unit.yml | 59 --- .github/workflows/test.yml | 401 ------------------ .../workflows/weekly-acceptance-0-49-x.yml | 29 ++ .github/workflows/weekly-acceptance-1-0-x.yml | 30 ++ .github/workflows/weekly-acceptance-1-1-x.yml | 30 ++ acceptance/framework/consul/helm_cluster.go | 8 +- acceptance/framework/helpers/helpers.go | 4 +- acceptance/framework/k8s/helpers.go | 6 +- acceptance/framework/k8s/kubectl.go | 2 +- .../framework/portforward/port_forward.go | 2 +- acceptance/framework/vault/vault_cluster.go | 2 +- .../config_entries_namespaces_test.go | 6 +- .../config-entries/config_entries_test.go | 2 +- .../tests/consul-dns/consul_dns_test.go | 2 +- acceptance/tests/metrics/metrics_test.go | 2 +- .../tests/partitions/partitions_sync_test.go | 2 +- charts/consul/test/terraform/aks/main.tf | 2 +- charts/consul/test/terraform/eks/main.tf | 2 +- charts/consul/test/terraform/gke/main.tf | 11 +- charts/consul/test/terraform/gke/variables.tf | 6 + .../subcommand/connect-init/command_test.go | 4 +- .../subcommand/consul-logout/command_test.go | 6 +- .../create-federation-secret/command_test.go | 32 +- .../get-consul-client-ca/command_test.go | 10 +- 28 files changed, 209 insertions(+), 704 deletions(-) create mode 100644 .github/workflows/nightly-acceptance.yml create mode 100644 .github/workflows/pr.yml delete mode 100644 .github/workflows/reusable-acceptance.yml delete mode 100644 .github/workflows/reusable-golangci-lint.yml delete mode 100644 .github/workflows/reusable-unit.yml delete mode 100644 .github/workflows/test.yml create mode 100644 .github/workflows/weekly-acceptance-0-49-x.yml create mode 100644 .github/workflows/weekly-acceptance-1-0-x.yml create mode 100644 .github/workflows/weekly-acceptance-1-1-x.yml diff --git a/.github/workflows/nightly-acceptance.yml b/.github/workflows/nightly-acceptance.yml new file mode 100644 index 0000000000..b8b7f50798 --- /dev/null +++ b/.github/workflows/nightly-acceptance.yml @@ -0,0 +1,27 @@ +# Dispatch to the consul-k8s-workflows with a nightly cron +name: nightly-acceptance +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run nightly at 12AM UTC/8PM EST/5PM PST + - cron: '0 0 * * *' + +# these should be the only settings that you will ever need to change +env: + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests + BRANCH: ${{ github.ref_name }} + CONTEXT: "nightly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@v1.2.2 + 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000000..d006c2514c --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,31 @@ +# Dispatch to the consul-k8s-workflows when a PR is created and on merges to main/release* +name: pr +on: + pull_request: + push: + # Sequence of patterns matched against refs/heads + branches: + # Push events on main branch + - main + # Push events to branches matching refs/heads/release/** + - "release/**" + +# these should be the only settings that you will ever need to change +env: + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests. We use this consul image on release branches too + BRANCH: ${{ github.head_ref || github.ref_name }} + CONTEXT: "${{ github.actor }}" + +jobs: + test: + name: test + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@v1.2.2 + name: test + with: + workflow: test.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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' diff --git a/.github/workflows/reusable-acceptance.yml b/.github/workflows/reusable-acceptance.yml deleted file mode 100644 index cf1e11e3f5..0000000000 --- a/.github/workflows/reusable-acceptance.yml +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (c) HashiCorp, Inc. - -name: reusable-acceptance - -on: - workflow_call: - inputs: - name: - required: true - type: string - additional-flags: - required: false - type: string - default: "" - consul-k8s-image: - required: false - type: string - directory: - required: true - type: string - go-version: - required: true - type: string - gotestsum-version: - required: true - type: string - kind-version: - required: false - type: string - default: "v1.24.6" - checkout-ref: - required: false - type: string - default: ${{ github.sha }} - secrets: - CONSUL_ENT_LICENSE: - required: true - VAULT_LICENSE: - required: true - -# Environment variables can only be used at the step level -env: - TEST_RESULTS: /tmp/test-results # path to where test results are saved - CONSUL_ENT_LICENSE: ${{ secrets.CONSUL_ENT_LICENSE }} - VAULT_LICENSE: ${{ secrets.VAULT_LICENSE }} - CONSUL_K8S_IMAGE: ${{ inputs.consul-k8s-image }} - -jobs: - job: - runs-on: [custom, linux, xl] - strategy: - matrix: - include: - - {runner: "0", test-packages: "basic consul-dns metrics"} - - {runner: "1", test-packages: "connect"} - - {runner: "2", test-packages: "controller example"} - - {runner: "3", test-packages: "ingress-gateway"} - - {runner: "4", test-packages: "partitions"} - - {runner: "5", test-packages: "peering"} - - {runner: "6", test-packages: "snapshot-agent vault wan-federation"} - - {runner: "7", test-packages: "cli sync terminating-gateway"} - - fail-fast: false - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - ref: ${{ inputs.checkout-ref }} - - - name: Setup go - uses: actions/setup-go@v3 - with: - go-version: ${{ inputs.go-version }} - - - name: Setup go mod cache - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Install pre-requisites # Install gotestsum, kind, kubectl, and helm - run: | - wget https://github.com/gotestyourself/gotestsum/releases/download/v1.6.4/gotestsum_1.6.4_linux_amd64.tar.gz - sudo tar -C /usr/local/bin -xzf gotestsum_1.6.4_linux_amd64.tar.gz - rm gotestsum_1.6.4_linux_amd64.tar.gz - - curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.15.0/kind-linux-amd64 - chmod +x ./kind - sudo mv ./kind /usr/local/bin/kind - - 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 - sudo mv ./kubectl /usr/local/bin/kubectl - - wget https://get.helm.sh/helm-v3.9.4-linux-amd64.tar.gz - tar -zxvf helm-v3.9.4-linux-amd64.tar.gz - sudo mv linux-amd64/helm /usr/local/bin/helm - - - run: mkdir -p ${{ env.TEST_RESULTS }} - - - name: go mod download - working-directory: ${{ inputs.directory }} - run: go mod download - - - name: Create kind clusters - run: | - kind create cluster --name dc1 --image kindest/node:${{ inputs.kind-version }} - kind create cluster --name dc2 --image kindest/node:${{ inputs.kind-version }} - - - name: Build CLI - run: | - sudo make cli-dev - consul-k8s version - - # We have to run the tests for each package separately so that we can - # exit early if any test fails (-failfast only works within a single - # package). - - name: Run acceptance tests ${{ matrix.runner }} - working-directory: ${{ inputs.directory }} - if: github.repository_owner == 'hashicorp' # This prevents running on forks - run: | - exit_code=0 - echo "Running packages: ${{ matrix.test-packages }}" - for pkg in $(echo ${{ matrix.test-packages }}) - do - fullpkg="github.com/hashicorp/consul-k8s/${{ inputs.directory }}/${pkg}" - echo "Testing package: ${fullpkg}" - if ! gotestsum --format=testname --jsonfile=jsonfile-${pkg////-} -- ${fullpkg} -p 1 -timeout 2h -failfast \ - ${{ inputs.additional-flags }} \ - -enable-enterprise \ - -enable-multi-cluster \ - -debug-directory=${{ env.TEST_RESULTS }}/debug \ - -consul-k8s-image=${{ inputs.consul-k8s-image }} - then - echo "Tests in ${pkg} failed, aborting early" - exit_code=1 - break - fi - done - gotestsum --format=testname --raw-command --junitfile "${{ env.TEST_RESULTS }}/gotestsum-report.xml" -- cat jsonfile* - exit $exit_code - - - name: Upload tests - if: always() - uses: actions/upload-artifact@v3 - with: - name: ${{ inputs.name }}-${{ matrix.test-packages }}-gotestsum-report.xml - path: ${{ env.TEST_RESULTS }}/gotestsum-report.xml - - - name: Upload debug (on failure) - if: failure() - uses: actions/upload-artifact@v3 - with: - name: ${{ inputs.name }}-${{ matrix.test-packages }}-debug-info - path: ${{ env.TEST_RESULTS }}/debug diff --git a/.github/workflows/reusable-golangci-lint.yml b/.github/workflows/reusable-golangci-lint.yml deleted file mode 100644 index e8fcd79440..0000000000 --- a/.github/workflows/reusable-golangci-lint.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) HashiCorp, Inc. - -name: golangci-lint - -on: - workflow_call: - inputs: - directory: - required: true - type: string - go-version: - required: true - type: string - args: - required: false - type: string - -jobs: - job: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup go - uses: actions/setup-go@v3 - with: - go-version: ${{ inputs.go-version }} - - - name: golangci-lint-${{inputs.directory}} - uses: golangci/golangci-lint-action@v3.4.0 - with: - version: v1.51 - working-directory: ${{inputs.directory}} - args: ${{inputs.args}} diff --git a/.github/workflows/reusable-unit.yml b/.github/workflows/reusable-unit.yml deleted file mode 100644 index dd2dd4e4a0..0000000000 --- a/.github/workflows/reusable-unit.yml +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) HashiCorp, Inc. - -name: reusable-unit - -on: - workflow_call: - inputs: - directory: - required: true - type: string - go-version: - required: true - type: string - -# Environment variables can only be used at the step level -env: - TEST_RESULTS: /tmp/test-results # path to where test results are saved - GOTESTSUM_VERSION: 1.8.2 - -jobs: - job: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup go - uses: actions/setup-go@v3 - with: - go-version: ${{inputs.go-version}} - - - name: Setup go mod cache - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Install gotestsum - run: | - wget https://github.com/gotestyourself/gotestsum/releases/download/v${{env.GOTESTSUM_VERSION}}/gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - sudo tar -C /usr/local/bin -xzf gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - rm gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - - - run: mkdir -p ${{env.TEST_RESULTS}} - - - name: go mod download - working-directory: ${{inputs.directory}} - run: go mod download - - - name: Run tests - working-directory: ${{inputs.directory}} - run: | - gotestsum --format=testname --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml ./... -- -p 4 - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 6e6212e0d8..0000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,401 +0,0 @@ -# Copyright (c) HashiCorp, Inc. - -name: test -on: - push: - -env: - TEST_RESULTS: /tmp/test-results # path to where test results are saved - GOTESTSUM_VERSION: 1.8.2 # You cannot use environment variables with workflows. The gotestsum version is hardcoded in the reusable workflows too. - # We use docker images to copy the consul binary for unit tests. - CONSUL_OSS_DOCKER_IMAGE: hashicorppreview/consul:1.16-dev # Consul's OSS version to use in tests - CONSUL_ENT_DOCKER_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests - -jobs: - terraform-fmt-check: - name: "Terraform format check" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Setup Terraform - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: TERRAFORM_VERSION - terraform_wrapper: false - - name: Run Terraform checks - run: | - make terraform-fmt-check TERRAFORM_DIR="${{ github.workspace }}" - - get-go-version: - name: "Determine Go toolchain version" - runs-on: ubuntu-latest - outputs: - go-version: ${{ steps.get-go-version.outputs.go-version }} - steps: - - uses: actions/checkout@v3 - - name: Determine Go version - id: get-go-version - # We use .go-version as our source of truth for current Go - # version, because "goenv" can react to it automatically. - run: | - echo "Building with Go $(cat .go-version)" - echo "::set-output name=go-version::$(cat .go-version)" - - get-product-version: - runs-on: ubuntu-latest - outputs: - product-version: ${{ steps.get-product-version.outputs.product-version }} - steps: - - uses: actions/checkout@v3 - - name: get product version - id: get-product-version - run: | - make version - echo "::set-output name=product-version::$(make version)" - - validate-helm-gen: - needs: - - get-go-version - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup go - uses: actions/setup-go@v3 - with: - go-version: ${{ needs.get-go-version.outputs.go-version }} - - - name: Setup go mod cache - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Validate helm gen - working-directory: hack/helm-reference-gen - run: | - go run ./... -validate - - golangci-lint-helm-gen: - needs: - - get-go-version - uses: ./.github/workflows/reusable-golangci-lint.yml - with: - directory: hack/helm-reference-gen - go-version: ${{ needs.get-go-version.outputs.go-version }} - #TODO: This is a workaround in order to get pipelines working. godot and staticcheck fail for helm-reference-gen - args: "--no-config --disable-all --enable gofmt,govet" - - unit-helm-gen: - needs: [get-go-version, golangci-lint-helm-gen, validate-helm-gen] - uses: ./.github/workflows/reusable-unit.yml - with: - directory: hack/helm-reference-gen - go-version: ${{ needs.get-go-version.outputs.go-version }} - - unit-test-helm-templates: - needs: - - unit-helm-gen - runs-on: ubuntu-latest - container: - image: docker.mirror.hashicorp.services/hashicorpdev/consul-helm-test:0.15.0 - options: --user 1001 - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Run Unit Tests - working-directory: charts/consul - run: bats --jobs 4 ./test/unit - - lint-control-plane: - needs: - - get-go-version - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup go - uses: actions/setup-go@v3 - with: - go-version: ${{ needs.get-go-version.outputs.go-version }} - - - run: go install github.com/hashicorp/lint-consul-retry@master && lint-consul-retry - - - name: Run lint - working-directory: control-plane - run: go run hack/lint-api-new-client/main.go - - golangci-lint-control-plane: - needs: - - get-go-version - uses: ./.github/workflows/reusable-golangci-lint.yml - with: - directory: control-plane - go-version: ${{ needs.get-go-version.outputs.go-version }} - - test-control-plane: - needs: [get-go-version, lint-control-plane, golangci-lint-control-plane] - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup go - uses: actions/setup-go@v3 - with: - go-version: ${{ needs.get-go-version.outputs.go-version }} - - - name: Setup go mod cache - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Install gotestsum - run: | - wget https://github.com/gotestyourself/gotestsum/releases/download/v${{env.GOTESTSUM_VERSION}}/gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - sudo tar -C /usr/local/bin -xzf gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - rm gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - - - run: mkdir -p ${{env.TEST_RESULTS}} - - run: echo "$HOME/bin" >> $GITHUB_PATH - - - name: Download consul - working-directory: control-plane - run: | - mkdir -p $HOME/bin - container_id=$(docker create ${{env.CONSUL_OSS_DOCKER_IMAGE}}) - docker cp "$container_id:/bin/consul" $HOME/bin/consul - docker rm "$container_id" - - name: Run go tests - working-directory: control-plane - run: | - PACKAGE_NAMES=$(go list ./...) - gotestsum --format=testname --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml -- -p 4 $PACKAGE_NAMES - - test-enterprise-control-plane: - if: github.repository_owner == 'hashicorp' # Do not run on forks as this requires secrets - needs: [get-go-version, lint-control-plane, golangci-lint-control-plane] - runs-on: ubuntu-latest - env: - CONSUL_LICENSE: ${{secrets.CONSUL_LICENSE}} - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup go - uses: actions/setup-go@v3 - with: - go-version: ${{ needs.get-go-version.outputs.go-version }} - - - name: Setup go mod cache - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Install gotestsum - run: | - wget https://github.com/gotestyourself/gotestsum/releases/download/v${{env.GOTESTSUM_VERSION}}/gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - sudo tar -C /usr/local/bin -xzf gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - rm gotestsum_${{env.GOTESTSUM_VERSION}}_linux_amd64.tar.gz - - - run: mkdir -p ${{env.TEST_RESULTS}} - - run: echo "$HOME/bin" >> $GITHUB_PATH - - - name: Download consul - working-directory: control-plane - run: | - mkdir -p $HOME/bin - container_id=$(docker create ${{env.CONSUL_ENT_DOCKER_IMAGE}}) - docker cp "$container_id:/bin/consul" $HOME/bin/consul - docker rm "$container_id" - - - name: Run go tests - working-directory: control-plane - run: | - PACKAGE_NAMES=$(go list ./...) - gotestsum --format=testname --junitfile ${{env.TEST_RESULTS}}/gotestsum-report.xml -- -tags=enterprise -p 4 $PACKAGE_NAMES - - build-distros: - needs: [get-go-version, get-product-version] - runs-on: ubuntu-latest - strategy: - matrix: - include: - # cli - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - # control-plane - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - # consul-cni - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - - fail-fast: true - - name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} build - steps: - - uses: actions/checkout@v3 - - - name: Setup go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go }} - - - name: Build - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: 0 - working-directory: ${{ matrix.component }} - run: | - mkdir -p dist out - - export GIT_COMMIT=$(git rev-parse --short HEAD) - export GIT_DIRTY=$(test -n "$(git status --porcelain)" && echo "+CHANGES") - export GIT_IMPORT=github.com/hashicorp/consul-k8s/${{ matrix.component }}/version - export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X ${GIT_IMPORT}.GitDescribe=${{ needs.get-product-version.outputs.product-version }}" - - CGO_ENABLED=0 go build -o dist/${{ matrix.bin_name }} -ldflags "${GOLDFLAGS}" . - zip -r -j out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ - - - name: Upload built binaries - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip - path: ${{ matrix.component}}/out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip - - golangci-lint-acceptance: - needs: - - get-go-version - uses: ./.github/workflows/reusable-golangci-lint.yml - with: - directory: acceptance - go-version: ${{ needs.get-go-version.outputs.go-version }} - - unit-acceptance-framework: - needs: [get-go-version, golangci-lint-acceptance] - uses: ./.github/workflows/reusable-unit.yml - with: - directory: acceptance/framework - go-version: ${{ needs.get-go-version.outputs.go-version }} - - golangci-lint-cli: - needs: - - get-go-version - uses: ./.github/workflows/reusable-golangci-lint.yml - with: - directory: cli - go-version: ${{ needs.get-go-version.outputs.go-version }} - - unit-cli: - needs: [get-go-version, golangci-lint-cli] - uses: ./.github/workflows/reusable-unit.yml - with: - directory: cli - go-version: ${{ needs.get-go-version.outputs.go-version }} - - # upload dev docker image - dev-upload-docker: - if: github.repository_owner == 'hashicorp' # Do not run on forks as this requires secrets - needs: [ get-product-version, build-distros ] - runs-on: ubuntu-latest - strategy: - matrix: - arch: ["amd64"] - env: - repo: ${{ github.event.repository.name }} - version: ${{ needs.get-product-version.outputs.product-version }} - steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 - with: - name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip - path: control-plane/dist/cni/linux/${{ matrix.arch }} - - uses: actions/download-artifact@v3 - with: - name: consul-k8s-control-plane_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip - path: control-plane/dist/linux/${{ matrix.arch }} - - name: extract consul-cni zip - env: - ZIP_LOCATION: control-plane/dist/cni/linux/${{ matrix.arch }} - run: | - cd "${ZIP_LOCATION}" - unzip -j *.zip - - name: extract control-plane zip - env: - ZIP_LOCATION: control-plane/dist/linux/${{ matrix.arch }} - run: | - cd "${ZIP_LOCATION}" - unzip -j *.zip - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} - - name: Docker Build (Action) - uses: docker/build-push-action@v3 - with: - push: true - context: control-plane - platforms: ${{ matrix.arch }} - target: release-default - tags: docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-pr-${{ github.sha }} - -# Disable GHA acceptance tests until GHA formally supported -# acceptance: -# needs: [ get-product-version, dev-upload-docker, get-go-version ] -# uses: ./.github/workflows/reusable-acceptance.yml -# with: -# name: acceptance -# directory: acceptance/tests -# go-version: ${{ needs.get-go-version.outputs.go-version }} -# additional-flags: "-use-kind -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.14-dev" -# gotestsum-version: 1.8.2 -# consul-k8s-image: docker.io/hashicorppreview/${{ github.event.repository.name }}-control-plane:${{ needs.get-product-version.outputs.product-version }}-pr-${{ github.sha }} -# secrets: -# CONSUL_ENT_LICENSE: ${{ secrets.CONSUL_ENT_LICENSE }} -# -# acceptance-tproxy: -# needs: [ get-product-version, dev-upload-docker, get-go-version ] -# uses: ./.github/workflows/reusable-acceptance.yml -# with: -# name: acceptance-tproxy -# directory: acceptance/tests -# go-version: ${{ needs.get-go-version.outputs.go-version }} -# additional-flags: "-use-kind -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-transparent-proxy -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.14-dev" -# gotestsum-version: 1.8.2 -# consul-k8s-image: docker.io/hashicorppreview/${{ github.event.repository.name }}-control-plane:${{ needs.get-product-version.outputs.product-version }}-pr-${{ github.sha }} -# secrets: -# CONSUL_ENT_LICENSE: ${{ secrets.CONSUL_ENT_LICENSE }} -# -# acceptance-cni: -# needs: [ get-product-version, dev-upload-docker, get-go-version ] -# uses: ./.github/workflows/reusable-acceptance.yml -# with: -# name: acceptance -# directory: acceptance/tests -# go-version: ${{ needs.get-go-version.outputs.go-version }} -# additional-flags: "-use-kind -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-transparent-proxy -enable-cni -consul-image=docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.14-dev" -# gotestsum-version: 1.8.2 -# consul-k8s-image: docker.io/hashicorppreview/${{ github.event.repository.name }}-control-plane:${{ needs.get-product-version.outputs.product-version }}-pr-${{ github.sha }} -# secrets: -# CONSUL_ENT_LICENSE: ${{ secrets.CONSUL_ENT_LICENSE }} - - diff --git a/.github/workflows/weekly-acceptance-0-49-x.yml b/.github/workflows/weekly-acceptance-0-49-x.yml new file mode 100644 index 0000000000..7025bcb241 --- /dev/null +++ b/.github/workflows/weekly-acceptance-0-49-x.yml @@ -0,0 +1,29 @@ +# 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-0-49-x +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run weekly on Monday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 1' + +# these should be the only settings that you will ever need to change +env: + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.13-dev # Consul's enterprise version to use in tests + BRANCH: "release/0.49.x" + CONTEXT: "weekly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@v1.2.2 + 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' diff --git a/.github/workflows/weekly-acceptance-1-0-x.yml b/.github/workflows/weekly-acceptance-1-0-x.yml new file mode 100644 index 0000000000..4aa49594f3 --- /dev/null +++ b/.github/workflows/weekly-acceptance-1-0-x.yml @@ -0,0 +1,30 @@ +# 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-0-x +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run weekly on Tuesday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 2' + + +# these should be the only settings that you will ever need to change +env: + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.14-dev # Consul's enterprise version to use in tests + BRANCH: "release/1.0.x" + CONTEXT: "weekly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@v1.2.2 + 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' diff --git a/.github/workflows/weekly-acceptance-1-1-x.yml b/.github/workflows/weekly-acceptance-1-1-x.yml new file mode 100644 index 0000000000..1ffc6f8684 --- /dev/null +++ b/.github/workflows/weekly-acceptance-1-1-x.yml @@ -0,0 +1,30 @@ +# 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-1-x +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 3' + + +# these should be the only settings that you will ever need to change +env: + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.15-dev # Consul's enterprise version to use in tests + BRANCH: "release/1.1.x" + CONTEXT: "weekly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@v1.2.2 + 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index f34aab6455..e03e3615fc 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -146,14 +146,14 @@ func (h *HelmCluster) Destroy(t *testing.T) { "--wait": nil, } - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 30}, t, func(r *retry.R) { err := helm.DeleteE(t, h.helmOptions, h.releaseName, false) require.NoError(r, err) }) // Retry because sometimes certain resources (like PVC) take time to delete // in cloud providers. - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 600}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { // Force delete any pods that have h.releaseName in their name because sometimes // graceful termination takes a long time and since this is an uninstall // we don't care that they're stopped gracefully. @@ -330,7 +330,7 @@ func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool) (client *api. if h.ACLToken != "" { config.Token = h.ACLToken } else { - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 600}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { // Get the ACL token. First, attempt to read it from the bootstrap token (this will be true in primary Consul servers). // If the bootstrap token doesn't exist, it means we are running against a secondary cluster // and will try to read the replication token from the federation secret. @@ -534,7 +534,7 @@ func defaultValues() map[string]string { func CreateK8sSecret(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace, secretName, secretKey, secret string) { - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 15}, t, func(r *retry.R) { _, err := client.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) if errors.IsNotFound(err) { _, err := client.CoreV1().Secrets(namespace).Create(context.Background(), &corev1.Secret{ diff --git a/acceptance/framework/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index 544079b8fb..3e6ef039b2 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -38,7 +38,7 @@ func CheckForPriorInstallations(t *testing.T, client kubernetes.Interface, optio // Check if there's an existing cluster and fail if there is one. // We may need to retry since this is the first command run once the Kube // cluster is created and sometimes the API server returns errors. - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 15}, t, func(r *retry.R) { var err error // NOTE: It's okay to pass in `t` to RunHelmCommandAndGetOutputE despite being in a retry // because we're using RunHelmCommandAndGetOutputE (not RunHelmCommandAndGetOutput) so the `t` won't @@ -58,7 +58,7 @@ func CheckForPriorInstallations(t *testing.T, client kubernetes.Interface, optio // Wait for all pods in the "default" namespace to exit. A previous // release may not be listed by Helm but its pods may still be terminating. - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 60}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 60}, t, func(r *retry.R) { pods, err := client.CoreV1().Pods(options.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector}) require.NoError(r, err) if len(pods.Items) > 0 { diff --git a/acceptance/framework/k8s/helpers.go b/acceptance/framework/k8s/helpers.go index efac5b35db..235d26d061 100644 --- a/acceptance/framework/k8s/helpers.go +++ b/acceptance/framework/k8s/helpers.go @@ -43,10 +43,10 @@ func WaitForAllPodsToBeReady(t *testing.T, client kubernetes.Interface, namespac logger.Logf(t, "Waiting for pods with label %q to be ready.", podLabelSelector) - // Wait up to 11m. + // Wait up to 20m. // On Azure, volume provisioning can sometimes take close to 5 min, // so we need to give a bit more time for pods to become healthy. - counter := &retry.Counter{Count: 11 * 60, Wait: 1 * time.Second} + counter := &retry.Counter{Count: 20 * 60, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { pods, err := client.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: podLabelSelector}) require.NoError(r, err) @@ -113,7 +113,7 @@ func ServiceHost(t *testing.T, cfg *config.TestConfig, ctx environment.TestConte var host string // It can take some time for the load balancers to be ready and have an IP/Hostname. // Wait for 5 minutes before failing. - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 600}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { svc, err := ctx.KubernetesClient(t).CoreV1().Services(ctx.KubectlOptions(t).Namespace).Get(context.Background(), serviceName, metav1.GetOptions{}) require.NoError(t, err) require.NotEmpty(r, svc.Status.LoadBalancer.Ingress) diff --git a/acceptance/framework/k8s/kubectl.go b/acceptance/framework/k8s/kubectl.go index 4416c7e6a9..ea90212e04 100644 --- a/acceptance/framework/k8s/kubectl.go +++ b/acceptance/framework/k8s/kubectl.go @@ -56,7 +56,7 @@ func RunKubectlAndGetOutputWithLoggerE(t *testing.T, options *k8s.KubectlOptions } counter := &retry.Counter{ - Count: 3, + Count: 10, Wait: 1 * time.Second, } var output string diff --git a/acceptance/framework/portforward/port_forward.go b/acceptance/framework/portforward/port_forward.go index 0a48e8d300..3242541cc5 100644 --- a/acceptance/framework/portforward/port_forward.go +++ b/acceptance/framework/portforward/port_forward.go @@ -27,7 +27,7 @@ func CreateTunnelToResourcePort(t *testing.T, resourceName string, remotePort in logger) // Retry creating the port forward since it can fail occasionally. - retry.RunWith(&retry.Counter{Wait: 3 * time.Second, Count: 60}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry // because we're using ForwardPortE (not ForwardPort) so the `t` won't // get used to fail the test, just for logging. diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index d09b8ed75e..26da56b646 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -113,7 +113,7 @@ func (v *VaultCluster) SetupVaultClient(t *testing.T) *vapi.Client { v.logger) // Retry creating the port forward since it can fail occasionally. - retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 60}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry // because we're using ForwardPortE (not ForwardPort) so the `t` won't // get used to fail the test, just for logging. diff --git a/acceptance/tests/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index 05ea9e5235..c15277a6ee 100644 --- a/acceptance/tests/config-entries/config_entries_namespaces_test.go +++ b/acceptance/tests/config-entries/config_entries_namespaces_test.go @@ -136,7 +136,7 @@ func TestControllerNamespaces(t *testing.T) { // On startup, the controller can take upwards of 1m to perform // leader election so we may need to wait a long time for // the reconcile loop to run (hence the 1m timeout here). - counter := &retry.Counter{Count: 60, Wait: 1 * time.Second} + counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults entry, _, err := consulClient.ConfigEntries().Get(api.ServiceDefaults, "defaults", queryOpts) @@ -258,7 +258,7 @@ func TestControllerNamespaces(t *testing.T) { patchSNI := "patch-sni" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "terminatinggateway", "terminating-gateway", "-p", fmt.Sprintf(`{"spec": {"services": [{"name":"name","caFile":"caFile","certFile":"certFile","keyFile":"keyFile","sni":"%s"}]}}`, patchSNI), "--type=merge") - counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} + counter := &retry.Counter{Count: 20, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults entry, _, err := consulClient.ConfigEntries().Get(api.ServiceDefaults, "defaults", queryOpts) @@ -366,7 +366,7 @@ func TestControllerNamespaces(t *testing.T) { logger.Log(t, "deleting terminating-gateway custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "terminatinggateway", "terminating-gateway") - counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} + counter := &retry.Counter{Count: 20, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults _, _, err := consulClient.ConfigEntries().Get(api.ServiceDefaults, "defaults", queryOpts) diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index d3c0d410a0..4aa7cf11bd 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -96,7 +96,7 @@ func TestController(t *testing.T) { // On startup, the controller can take upwards of 1m to perform // leader election so we may need to wait a long time for // the reconcile loop to run (hence the 1m timeout here). - counter := &retry.Counter{Count: 60, Wait: 1 * time.Second} + counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults entry, _, err := consulClient.ConfigEntries().Get(api.ServiceDefaults, "defaults", nil) diff --git a/acceptance/tests/consul-dns/consul_dns_test.go b/acceptance/tests/consul-dns/consul_dns_test.go index 15b7d580be..c33de7747d 100644 --- a/acceptance/tests/consul-dns/consul_dns_test.go +++ b/acceptance/tests/consul-dns/consul_dns_test.go @@ -62,7 +62,7 @@ func TestConsulDNS(t *testing.T) { dnsPodName := fmt.Sprintf("%s-dns-pod", releaseName) dnsTestPodArgs := []string{ - "run", "-i", dnsPodName, "--restart", "Never", "--image", "anubhavmishra/tiny-tools", "--", "dig", fmt.Sprintf("@%s-consul-dns", releaseName), "consul.service.consul", + "run", "-it", dnsPodName, "--restart", "Never", "--image", "anubhavmishra/tiny-tools", "--", "dig", fmt.Sprintf("@%s-consul-dns", releaseName), "consul.service.consul", } helpers.Cleanup(t, suite.Config().NoCleanupOnFailure, func() { diff --git a/acceptance/tests/metrics/metrics_test.go b/acceptance/tests/metrics/metrics_test.go index acfe465b00..2a130f38db 100644 --- a/acceptance/tests/metrics/metrics_test.go +++ b/acceptance/tests/metrics/metrics_test.go @@ -132,7 +132,7 @@ func TestAppMetrics(t *testing.T) { // Retry because sometimes the merged metrics server takes a couple hundred milliseconds // to start. - retry.RunWith(&retry.Counter{Count: 3, Wait: 1 * time.Second}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Count: 20, Wait: 2 * time.Second}, t, func(r *retry.R) { metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) require.NoError(r, err) // This assertion represents the metrics from the envoy sidecar. diff --git a/acceptance/tests/partitions/partitions_sync_test.go b/acceptance/tests/partitions/partitions_sync_test.go index 344d64a780..cf32c97ae3 100644 --- a/acceptance/tests/partitions/partitions_sync_test.go +++ b/acceptance/tests/partitions/partitions_sync_test.go @@ -248,7 +248,7 @@ func TestPartitions_Sync(t *testing.T) { logger.Log(t, "checking that the service has been synced to Consul") var services map[string][]string - counter := &retry.Counter{Count: 20, Wait: 30 * time.Second} + counter := &retry.Counter{Count: 30, Wait: 30 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { var err error // list services in default partition catalog. diff --git a/charts/consul/test/terraform/aks/main.tf b/charts/consul/test/terraform/aks/main.tf index d077471ec9..8cf72a142c 100644 --- a/charts/consul/test/terraform/aks/main.tf +++ b/charts/consul/test/terraform/aks/main.tf @@ -71,7 +71,7 @@ resource "azurerm_kubernetes_cluster" "default" { default_node_pool { name = "default" node_count = 3 - vm_size = "Standard_D2_v2" + vm_size = "Standard_D3_v2" os_disk_size_gb = 30 vnet_subnet_id = azurerm_virtual_network.default[count.index].subnet.*.id[0] } diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index 07c58a2705..6301202161 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -73,7 +73,7 @@ module "eks" { max_capacity = 3 min_capacity = 3 - instance_type = "m5.large" + instance_type = "m5.xlarge" } } diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index f8ff19b912..98a063d450 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -3,7 +3,8 @@ provider "google" { project = var.project - version = "~> 3.49.0" + version = "~> 4.58.0" + zone = var.zone } resource "random_id" "suffix" { @@ -16,6 +17,11 @@ data "google_container_engine_versions" "main" { version_prefix = "1.25." } +# We assume that the subnets are already created to save time. +data "google_compute_subnetwork" "subnet" { + name = var.subnet +} + resource "google_container_cluster" "cluster" { provider = "google" count = var.cluster_count @@ -28,8 +34,9 @@ resource "google_container_cluster" "cluster" { node_version = data.google_container_engine_versions.main.latest_master_version node_config { tags = ["consul-k8s-${random_id.suffix[count.index].dec}"] - machine_type = "e2-standard-4" + machine_type = "e2-standard-8" } + subnetwork = data.google_compute_subnetwork.subnet.name resource_labels = var.labels } diff --git a/charts/consul/test/terraform/gke/variables.tf b/charts/consul/test/terraform/gke/variables.tf index 1eebe64145..f33952850a 100644 --- a/charts/consul/test/terraform/gke/variables.tf +++ b/charts/consul/test/terraform/gke/variables.tf @@ -37,3 +37,9 @@ variable "labels" { default = {} description = "Labels to attach to the created resources." } + +variable "subnet" { + type = string + default = "default" + description = "Subnet to create the cluster in. Currently all clusters use the default subnet and we are running out of IPs" +} diff --git a/control-plane/subcommand/connect-init/command_test.go b/control-plane/subcommand/connect-init/command_test.go index 69abc8f1ad..cb1d32d03b 100644 --- a/control-plane/subcommand/connect-init/command_test.go +++ b/control-plane/subcommand/connect-init/command_test.go @@ -615,7 +615,7 @@ func TestRun_Gateways_Errors(t *testing.T) { "-pod-name", testPodName, "-pod-namespace", testPodNamespace, "-proxy-id-file", proxyFile, - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", "-consul-node-name", nodeName, } @@ -729,7 +729,7 @@ func TestRun_InvalidProxyFile(t *testing.T) { "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), "-proxy-id-file", randFileName, - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", } code := cmd.Run(flags) require.Equal(t, 1, code) diff --git a/control-plane/subcommand/consul-logout/command_test.go b/control-plane/subcommand/consul-logout/command_test.go index e7e3a00f38..1a0744996a 100644 --- a/control-plane/subcommand/consul-logout/command_test.go +++ b/control-plane/subcommand/consul-logout/command_test.go @@ -54,7 +54,7 @@ func TestRun_InvalidSinkFile(t *testing.T) { } code := cmd.Run([]string{ "-token-file", randFileName, - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, code) } @@ -107,7 +107,7 @@ func Test_UnableToLogoutDueToInvalidToken(t *testing.T) { code := cmd.Run([]string{ "-http-addr", fmt.Sprintf("%s://%s", cfg.Scheme, cfg.Address), "-token-file", tokenFile, - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, code, ui.ErrorWriter.String()) require.Contains(t, "Unexpected response code: 403 (ACL not found)", ui.ErrorWriter.String()) @@ -172,7 +172,7 @@ func Test_RunUsingLogin(t *testing.T) { code := cmd.Run([]string{ "-http-addr", fmt.Sprintf("%s://%s", cfg.Scheme, cfg.Address), "-token-file", tokenFile, - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, code, ui.ErrorWriter.String()) diff --git a/control-plane/subcommand/create-federation-secret/command_test.go b/control-plane/subcommand/create-federation-secret/command_test.go index a90ff692e2..c401f1fc7e 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -80,7 +80,7 @@ func TestRun_FlagValidation(t *testing.T) { "-server-ca-key-file=file", "-ca-file", f.Name(), "-mesh-gateway-service-name=name", - "-consul-api-timeout=5s", + "-consul-api-timeout=10s", "-log-level=invalid", }, expErr: "unknown log level: invalid", @@ -117,7 +117,7 @@ func TestRun_CAFileMissing(t *testing.T) { "-server-ca-cert-file", f.Name(), "-server-ca-key-file", f.Name(), "-ca-file=/this/does/not/exist", - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "error reading CA file") @@ -140,7 +140,7 @@ func TestRun_ServerCACertFileMissing(t *testing.T) { "-ca-file", f.Name(), "-server-ca-cert-file=/this/does/not/exist", "-server-ca-key-file", f.Name(), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "Error reading server CA cert file") @@ -163,7 +163,7 @@ func TestRun_ServerCAKeyFileMissing(t *testing.T) { "-ca-file", f.Name(), "-server-ca-cert-file", f.Name(), "-server-ca-key-file=/this/does/not/exist", - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "Error reading server CA key file") @@ -187,7 +187,7 @@ func TestRun_GossipEncryptionKeyFileMissing(t *testing.T) { "-server-ca-cert-file", f.Name(), "-server-ca-key-file", f.Name(), "-gossip-key-file=/this/does/not/exist", - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "Error reading gossip encryption key file") @@ -211,7 +211,7 @@ func TestRun_GossipEncryptionKeyFileEmpty(t *testing.T) { "-server-ca-cert-file", f.Name(), "-server-ca-key-file", f.Name(), "-gossip-key-file", f.Name(), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), fmt.Sprintf("gossip key file %q was empty", f.Name())) @@ -249,7 +249,7 @@ func TestRun_ReplicationTokenMissingExpectedKey(t *testing.T) { "-server-ca-cert-file", f.Name(), "-server-ca-key-file", f.Name(), "-export-replication-token", - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) } @@ -448,7 +448,7 @@ func TestRun_ACLs_K8SNamespaces_ResourcePrefixes(tt *testing.T) { "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", } if c.aclsEnabled { flags = append(flags, "-export-replication-token") @@ -555,7 +555,7 @@ func TestRun_WaitsForMeshGatewayInstances(t *testing.T) { "-server-ca-cert-file", certFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -609,7 +609,7 @@ func TestRun_MeshGatewayNoWANAddr(t *testing.T) { "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) } @@ -695,7 +695,7 @@ func TestRun_MeshGatewayUniqueAddrs(tt *testing.T) { "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -838,7 +838,7 @@ func TestRun_ReplicationSecretDelay(t *testing.T) { "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), "-export-replication-token", - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", } exitCode := cmd.Run(flags) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -908,7 +908,7 @@ func TestRun_UpdatesSecret(t *testing.T) { "-server-ca-cert-file", certFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -950,7 +950,7 @@ func TestRun_UpdatesSecret(t *testing.T) { "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -1044,7 +1044,7 @@ func TestRun_ConsulClientDelay(t *testing.T) { "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://127.0.0.1:%d", randomPorts[2]), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", } exitCode := cmd.Run(flags) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -1112,7 +1112,7 @@ func TestRun_Autoencrypt(t *testing.T) { "-server-ca-cert-file", keyFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) diff --git a/control-plane/subcommand/get-consul-client-ca/command_test.go b/control-plane/subcommand/get-consul-client-ca/command_test.go index 466e11a11e..95ff4dbfbb 100644 --- a/control-plane/subcommand/get-consul-client-ca/command_test.go +++ b/control-plane/subcommand/get-consul-client-ca/command_test.go @@ -51,7 +51,7 @@ func TestRun_FlagsValidation(t *testing.T) { flags: []string{ "-output-file=output.pem", "-server-addr=foo.com", - "-consul-api-timeout=5s", + "-consul-api-timeout=10s", "-log-level=invalid-log-level", }, expErr: "unknown log level: invalid-log-level", @@ -106,7 +106,7 @@ func TestRun(t *testing.T) { "-server-port", strings.Split(a.HTTPSAddr, ":")[1], "-ca-file", caFile, "-output-file", outputFile.Name(), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -189,7 +189,7 @@ func TestRun_ConsulServerAvailableLater(t *testing.T) { "-server-port", fmt.Sprintf("%d", randomPorts[2]), "-ca-file", caFile, "-output-file", outputFile.Name(), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter) @@ -280,7 +280,7 @@ func TestRun_GetsOnlyActiveRoot(t *testing.T) { "-server-port", strings.Split(a.HTTPSAddr, ":")[1], "-ca-file", caFile, "-output-file", outputFile.Name(), - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode) @@ -348,7 +348,7 @@ func TestRun_WithProvider(t *testing.T) { "-server-port", strings.Split(a.HTTPSAddr, ":")[1], "-output-file", outputFile.Name(), "-ca-file", caFile, - "-consul-api-timeout", "5s", + "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) From 3b2d5e870c64345e99ec394e584470bcdbedd79c Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 14 Apr 2023 10:25:24 -0400 Subject: [PATCH 134/592] Remove CircleCI (#2050) --- .circleci/config.yml | 1381 ------------------------------------------ 1 file changed, 1381 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index cda1e54ba8..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,1381 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -# Originally from consul-k8s -version: 2.1 -orbs: - slack: circleci/slack@3.4.2 -# reusable 'executor' object for jobs -executors: - go: - docker: - - image: docker.mirror.hashicorp.services/cimg/go:1.20.1 - environment: - TEST_RESULTS: /tmp/test-results # path to where test results are saved - -slack-channel: &slack-channel C0421KHNAV9 #feed-consul-k8s-ci channel ID -control-plane-path: &control-plane-path control-plane -cli-path: &cli-path cli -acceptance-mod-path: &acceptance-mod-path acceptance -acceptance-test-path: &acceptance-test-path acceptance/tests -acceptance-framework-path: &acceptance-framework-path acceptance/framework -helm-gen-path: &helm-gen-path hack/helm-reference-gen -gke-terraform-path: &gke-terraform-path charts/consul/test/terraform/gke -eks-terraform-path: &eks-terraform-path charts/consul/test/terraform/eks -aks-terraform-path: &aks-terraform-path charts/consul/test/terraform/aks -openshift-terraform-path: &openshift-terraform-path charts/consul/test/terraform/openshift -# This image is built from test/docker/Test.dockerfile -consul-helm-test-image: &consul-helm-test-image docker.mirror.hashicorp.services/hashicorpdev/consul-helm-test:0.15.0 -consul-test-image: &consul-test-image hashicorppreview/consul-enterprise:1.16-dev - -######################## -# COMMANDS -######################## -# Commands define a sequence of steps as a map to be executed and reused in jobs -commands: - install-prereqs: - steps: - - run: - name: Install go, gotestsum, kind, kubectl, and helm - command: | - wget https://golang.org/dl/go1.20.1.linux-amd64.tar.gz - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.20.1.linux-amd64.tar.gz - rm go1.20.1.linux-amd64.tar.gz - echo 'export PATH=$PATH:/usr/local/go/bin' >> $BASH_ENV - - wget https://github.com/gotestyourself/gotestsum/releases/download/v1.8.2/gotestsum_1.8.2_linux_amd64.tar.gz - sudo tar -C /usr/local/bin -xzf gotestsum_1.8.2_linux_amd64.tar.gz - rm gotestsum_1.8.2_linux_amd64.tar.gz - - curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-linux-amd64 - chmod +x ./kind - sudo mv ./kind /usr/local/bin/kind - - 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 - sudo mv ./kubectl /usr/local/bin/kubectl - - wget https://get.helm.sh/helm-v3.9.4-linux-amd64.tar.gz - tar -zxvf helm-v3.9.4-linux-amd64.tar.gz - sudo mv linux-amd64/helm /usr/local/bin/helm - custom-checkout: - description: | - custom-checkout will perform a custom checkout procedure if provided with an alternative git reference, - otherwise it will run the CircleCI defined checkout command. This is needed as the CircleCI defined checkout - command does not support subsequent git actions after being called. - parameters: - git-ref: - type: string - default: "" - steps: - - when: - condition: << parameters.git-ref >> - steps: - - run: - name: Checkout code - command: | - ssh-keyscan github.com >> ~/.ssh/known_hosts - if [ -e '/home/circleci/project/.git' ] ; then - echo 'Fetching into existing repository' - existing_repo='true' - cd '/home/circleci/project' - git remote set-url origin "$CIRCLE_REPOSITORY_URL" || true - else - echo 'Cloning git repository' - existing_repo='false' - mkdir -p '/home/circleci/project' - cd '/home/circleci/project' - git clone --no-checkout "$CIRCLE_REPOSITORY_URL" . - fi - - if [ "$existing_repo" = 'true' ] || [ 'false' = 'true' ]; then - echo 'Fetching from remote repository' - git fetch --force origin - git fetch --force --tags origin - fi - - echo 'Checking out branch/tag' - git checkout --force "<< parameters.git-ref >>" - - unless: - condition: << parameters.git-ref >> - steps: - - checkout - create-kind-clusters: - parameters: - version: - type: string - steps: - - run: - name: Create kind clusters - command: | - kind create cluster --name dc1 --image kindest/node:<< parameters.version >> - kind create cluster --name dc2 --image kindest/node:<< parameters.version >> - create-kind-cni-clusters: - parameters: - version: - type: string - steps: - - run: - name: Create CNI kind clusters - command: | - kind create cluster --config=acceptance/framework/environment/cni-kind/kind.config --name dc1 --image kindest/node:<< parameters.version >> - make kind-cni-calico - kind create cluster --config=acceptance/framework/environment/cni-kind/kind.config --name dc2 --image kindest/node:<< parameters.version >> - make kind-cni-calico - build-cli: - steps: - - run: - name: Build consul-k8s CLI - working_directory: *cli-path - command: | - go build -o ./bin/consul-k8s - sudo cp ./bin/consul-k8s /usr/local/go/bin/ - consul-k8s version - - run-acceptance-tests: - description: | - Runs the Kind acceptance tests using a provided consul-k8s image, or else attempts to use the image referenced by the - branch name and git reference of the current git commit - parameters: - failfast: - type: boolean - default: false - additional-flags: - type: string - consul-k8s-image: - type: string - default: "docker.mirror.hashicorp.services/hashicorpdev/consul-k8s-control-plane:$(git rev-parse --short HEAD)" - go-path: - type: string - default: "/home/circleci/.go_workspace" - steps: - - when: - condition: << parameters.failfast >> - steps: - - run: - name: Run acceptance tests - working_directory: *acceptance-test-path - no_output_timeout: 2h - command: | - # Enterprise tests can't run on fork PRs because they require - # a secret. - if [ -z "$CIRCLE_PR_NUMBER" ]; then - ENABLE_ENTERPRISE=true - fi - - # We have to run the tests for each package separately so that we can - # exit early if any test fails (-failfast only works within a single - # package). - exit_code=0 - pkgs=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname) - echo "Running $(echo $pkgs | wc -w) packages:" - echo $pkgs - for pkg in $pkgs - do - if ! gotestsum --format=testname --no-summary=all --jsonfile=jsonfile-${pkg////-} -- $pkg -p 1 -timeout 2h -failfast \ - << parameters.additional-flags >> \ - -enable-multi-cluster \ - ${ENABLE_ENTERPRISE:+-enable-enterprise} \ - -debug-directory="$TEST_RESULTS/debug" \ - -consul-k8s-image=<< parameters.consul-k8s-image >> - then - echo "Tests in ${pkg} failed, aborting early" - exit_code=1 - break - fi - done - gotestsum --format=testname --raw-command --junitfile "$TEST_RESULTS/gotestsum-report.xml" -- cat jsonfile* - exit $exit_code - - - unless: - condition: << parameters.failfast >> - steps: - - run: - name: Run acceptance tests - working_directory: *acceptance-test-path - no_output_timeout: 2h - command: | - # Enterprise tests can't run on fork PRs because they require - # a secret. - if [ -z "$CIRCLE_PR_NUMBER" ]; then - ENABLE_ENTERPRISE=true - fi - - pkgs=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname) - echo "Running $pkgs" - gotestsum --format testname --junitfile "$TEST_RESULTS/gotestsum-report.xml" -- $pkgs -p 1 -timeout 2h -failfast \ - << parameters.additional-flags >> \ - ${ENABLE_ENTERPRISE:+-enable-enterprise} \ - -enable-multi-cluster \ - -debug-directory="$TEST_RESULTS/debug" \ - -consul-k8s-image=<< parameters.consul-k8s-image >> - -######################## -# JOBS -######################## -# Jobs are a collection of steps. These are used in the workflows to define -# what gets run in the pipeline -jobs: - go-fmt-and-vet-control-plane: - executor: go - steps: - - checkout - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-k8s-modcache-v2-{{ checksum "control-plane/go.mod" }} - - - run: - name: go mod download - working_directory: *control-plane-path - command: go mod download - - # Save go module cache if the go.mod file has changed - - save_cache: - key: consul-k8s-modcache-v2-{{ checksum "control-plane/go.mod" }} - paths: - - "/home/circleci/go/pkg/mod" - - # check go fmt output because it does not report non-zero when there are fmt changes - - run: - name: check go fmt - working_directory: *control-plane-path - command: | - files=$(go fmt ./...) - if [ -n "$files" ]; then - echo "The following file(s) do not conform to go fmt:" - echo "$files" - exit 1 - fi - - run: cd control-plane && go vet ./... - - lint-control-plane: - executor: go - steps: - - checkout - - run: go get -u github.com/hashicorp/lint-consul-retry && lint-consul-retry - - run: - name: run lint - working_directory: *control-plane-path - command: go run hack/lint-api-new-client/main.go - - test-control-plane: - executor: go - environment: - TEST_RESULTS: /tmp/test-results - parallelism: 1 - steps: - - checkout - - run: mkdir -p $TEST_RESULTS - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-k8s-modcache-v2-{{ checksum "control-plane/go.mod" }} - - # run go tests with gotestsum - - run: - name: run go tests - working_directory: *control-plane-path - command: | - # download and install the consul binary - wget https://releases.hashicorp.com/consul/"${CONSUL_VERSION}"/consul_"${CONSUL_VERSION}"_linux_amd64.zip && \ - unzip consul_"${CONSUL_VERSION}"_linux_amd64.zip -d /home/circleci/bin && - rm consul_"${CONSUL_VERSION}"_linux_amd64.zip - PACKAGE_NAMES=$(go list ./...) - gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml -- -p 4 $PACKAGE_NAMES - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - test-enterprise-control-plane: - executor: go - environment: - TEST_RESULTS: /tmp/test-results - parallelism: 1 - steps: - - checkout - - run: mkdir -p $TEST_RESULTS - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-k8s-modcache-v2-{{ checksum "control-plane/go.mod" }} - - # run go tests with gotestsum - - run: - name: run enterprise go tests - working_directory: *control-plane-path - command: | - # download and install the consul binary - wget https://releases.hashicorp.com/consul/"${CONSUL_ENT_VERSION}"/consul_"${CONSUL_ENT_VERSION}"_linux_amd64.zip && \ - unzip consul_"${CONSUL_ENT_VERSION}"_linux_amd64.zip -d /home/circleci/bin && - rm consul_"${CONSUL_ENT_VERSION}"_linux_amd64.zip - PACKAGE_NAMES=$(go list ./...) - gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml -- -tags=enterprise -p 4 $PACKAGE_NAMES - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - build-distro: # defines a parameterized job - description: A job that will build the os/arch distro set by XC_OS and XC_ARCH - parameters: - OS: - description: What OSes to build - default: "" - type: string - ARCH: - description: What architectures to build - default: "" - type: string - executor: go - environment: - GOXPARALLEL: 2 # CircleCI containers are 2 CPU x 4GB RAM - steps: - - checkout - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-k8s-modcache-v2-{{ checksum "control-plane/go.mod" }} - - run: - name: build local - working_directory: *control-plane-path - command: XC_OS="<< parameters.OS >>" XC_ARCH="<< parameters.ARCH >>" ./build-support/scripts/build-local.sh - # persist to downstream job - - persist_to_workspace: - root: . - paths: - - control-plane/pkg/bin - - control-plane/cni/pkg/bin - # save dev build to CircleCI - - store_artifacts: - path: ./control-plane/pkg/bin - - store_artifacts: - path: ./control-plane/cni/pkg/bin - - # upload dev docker image - dev-upload-docker: - machine: - image: ubuntu-2004:202010-01 - environment: - DOCKER_CLI_EXPERIMENTAL: enabled - steps: - - checkout - # get consul-k8s binary - - attach_workspace: - at: . - - run: sudo apt-get update - - run: sudo apt-get install -y qemu-user-static - - run: docker buildx create --use - - run: - name: make ci.dev-docker - working_directory: *control-plane-path - command: make ci.dev-docker - - unit-cli: - executor: go - steps: - - checkout - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-k8s-cli-modcache-v2-{{ checksum "cli/go.mod" }} - - - run: - name: go mod download - working_directory: *cli-path - command: go mod download - - # Save go module cache if the go.mod file has changed - - save_cache: - key: consul-k8s-cli-modcache-v2-{{ checksum "cli/go.mod" }} - paths: - - "/home/circleci/go/pkg/mod" - - - run: mkdir -p $TEST_RESULTS - - - run: - name: Run tests - working_directory: *cli-path - command: | - gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - go-fmt-and-vet-acceptance: - executor: go - steps: - - checkout - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: - name: go mod download - working_directory: *acceptance-mod-path - command: go mod download - - # Save go module cache if the go.mod file has changed - - save_cache: - key: consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - paths: - - "/home/circleci/go/pkg/mod" - - # check go fmt output because it does not report non-zero when there are fmt changes - - run: - name: check go fmt - working_directory: *acceptance-mod-path - command: | - files=$(go fmt ./...) - if [ -n "$files" ]; then - echo "The following file(s) do not conform to go fmt:" - echo "$files" - exit 1 - fi - - - run: - name: go vet - working_directory: *acceptance-mod-path - command: go vet ./... - - go-fmt-and-vet-helm-gen: - executor: go - steps: - - checkout - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-helm-gen-modcache-v2-{{ checksum "charts/consul/hack/helm-reference-gen/go.mod" }} - - - run: - name: go mod download - working_directory: *helm-gen-path - command: go mod download - - # Save go module cache if the go.mod file has changed - - save_cache: - key: consul-helm-helm-gen-modcache-v2-{{ checksum "charts/consul/hack/helm-reference-gen/go.mod" }} - paths: - - "/home/circleci/go/pkg/mod" - - # check go fmt output because it does not report non-zero when there are fmt changes - - run: - name: check go fmt - working_directory: *helm-gen-path - command: | - files=$(go fmt ./...) - if [ -n "$files" ]; then - echo "The following file(s) do not conform to go fmt:" - echo "$files" - exit 1 - fi - - - run: - name: go vet - working_directory: *helm-gen-path - command: go vet ./... - - unit-acceptance-framework: - executor: go - steps: - - checkout - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run: - name: Run tests - working_directory: *acceptance-framework-path - command: | - gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - unit-helm-gen: - executor: go - steps: - - checkout - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-helm-gen-modcache-v2-{{ checksum "charts/consul/hack/helm-reference-gen/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run: - name: Run tests - working_directory: *helm-gen-path - command: | - gotestsum --format testname --junitfile $TEST_RESULTS/gotestsum-report.xml ./... -- -p 4 - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - validate-helm-gen: - executor: go - steps: - - checkout - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-helm-gen-modcache-v2-{{ checksum "charts/consul/hack/helm-reference-gen/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run: - name: Validate helm gen - working_directory: *helm-gen-path - command: | - go run ./... -validate - - unit-test-helm-templates: - docker: - - image: *consul-helm-test-image - - steps: - - checkout - - - run: - name: Run Unit Tests - working_directory: charts/consul - command: bats --jobs 4 ./test/unit - - ########################### - # KIND ACCEPTANCE TEST JOBS - ########################### - acceptance: - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_TEST_IMAGE: *consul-test-image - machine: - image: ubuntu-2004:202010-01 - resource_class: xlarge - parallelism: 6 - steps: - - checkout - - install-prereqs - - create-kind-clusters: - version: "v1.26.0" - - restore_cache: - keys: - - consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - - run: - name: go mod download - working_directory: *acceptance-mod-path - command: go mod download - - save_cache: - key: consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - paths: - - ~/.go_workspace/pkg/mod - - build-cli - - run: mkdir -p $TEST_RESULTS - - run-acceptance-tests: - failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -consul-image=$CONSUL_TEST_IMAGE - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - acceptance-tproxy: - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_TEST_IMAGE: *consul-test-image - machine: - image: ubuntu-2004:202010-01 - resource_class: xlarge - parallelism: 6 - steps: - - checkout - - install-prereqs - - create-kind-clusters: - version: "v1.26.0" - - restore_cache: - keys: - - consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - - run: - name: go mod download - working_directory: *acceptance-mod-path - command: go mod download - - save_cache: - key: consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - paths: - - ~/.go_workspace/pkg/mod - - build-cli - - run: mkdir -p $TEST_RESULTS - - run-acceptance-tests: - failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - acceptance-tproxy-cni: - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_TEST_IMAGE: *consul-test-image - machine: - image: ubuntu-2004:202010-01 - resource_class: xlarge - parallelism: 3 - steps: - - checkout - - install-prereqs - - create-kind-cni-clusters: - version: "v1.25.3" - - restore_cache: - keys: - - consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - - run: - name: go mod download - working_directory: *acceptance-mod-path - command: go mod download - - save_cache: - key: consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - paths: - - ~/.go_workspace/pkg/mod - - build-cli - - run: mkdir -p $TEST_RESULTS - - run-acceptance-tests: - failfast: true - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -enable-transparent-proxy -enable-cni -consul-image=$CONSUL_TEST_IMAGE - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - ############################## - # CLEANUP CLOUD RESOURCES JOBS - ############################## - cleanup-gcp-resources: - docker: - - image: *consul-helm-test-image - steps: - - run: - name: cleanup leftover resources - command: | - echo "${GOOGLE_CREDENTIALS}" | gcloud auth activate-service-account --key-file=- - clusters=$(gcloud container clusters list --zone us-central1-a --project ${CLOUDSDK_CORE_PROJECT} --format json | jq -r '.[] | select(.name | test("^consul-k8s-\\d+$")) | .name') - for cluster in $clusters; do - echo "Deleting $cluster GKE cluster" - gcloud container clusters delete $cluster --zone us-central1-a --project ${CLOUDSDK_CORE_PROJECT} --quiet - done - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "GKE cleanup failed" - - cleanup-azure-resources: - docker: - - image: *consul-helm-test-image - steps: - - run: - name: cleanup leftover resources - command: | - az login --service-principal -u "$ARM_CLIENT_ID" -p "$ARM_CLIENT_SECRET" --tenant "$ARM_TENANT_ID" > /dev/null - resource_groups=$(az group list -o json | jq -r '.[] | select(.name | test("^consul-k8s-\\d+$")) | .name') - for group in $resource_groups; do - echo "Deleting $group resource group" - az group delete -n $group --yes - done - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "AKS cleanup failed" - - cleanup-eks-resources: - docker: - - image: *consul-helm-test-image - steps: - - checkout - - run: - name: cleanup eks resources - command: | - # Assume the role and set environment variables. - aws sts assume-role --role-arn "$AWS_ROLE_ARN" --role-session-name "consul-helm-$CIRCLE_BUILD_NUM" --duration-seconds 10800 > assume-role.json - export AWS_ACCESS_KEY_ID="$(jq -r .Credentials.AccessKeyId assume-role.json)" - export AWS_SECRET_ACCESS_KEY="$(jq -r .Credentials.SecretAccessKey assume-role.json)" - export AWS_SESSION_TOKEN="$(jq -r .Credentials.SessionToken assume-role.json)" - - make ci.aws-acceptance-test-cleanup - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "EKS cleanup failed" - - ############################# - # CLOUD ACCEPTANCE TEST JOBS - ############################# - acceptance-gke-1-25: - parallelism: 2 - environment: - - TEST_RESULTS: /tmp/test-results - - USE_GKE_GCLOUD_AUTH_PLUGIN: true - - CONSUL_TEST_IMAGE: *consul-test-image - docker: - - image: *consul-helm-test-image - - steps: - - run: - name: Exit if forked PR - command: | - if [ -n "$CIRCLE_PR_NUMBER" ]; then - echo "Skipping acceptance tests for forked PRs; marking step successful." - circleci step halt - fi - - - checkout - - - build-cli - - run: - name: terraform init & apply - working_directory: *gke-terraform-path - command: | - terraform init - echo "${GOOGLE_CREDENTIALS}" | gcloud auth activate-service-account --key-file=- - - # On GKE, we're setting the build number instead of build URL because label values - # cannot contain '/'. - terraform apply \ - -var project=${CLOUDSDK_CORE_PROJECT} \ - -var init_cli=true \ - -var cluster_count=2 \ - -var labels="{\"build_number\": \"$CIRCLE_BUILD_NUM\"}" \ - -auto-approve - - primary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[0]) - secondary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[1]) - - echo "export primary_kubeconfig=$primary_kubeconfig" >> $BASH_ENV - echo "export secondary_kubeconfig=$secondary_kubeconfig" >> $BASH_ENV - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - - run: - name: terraform destroy - working_directory: *gke-terraform-path - command: | - terraform destroy -var project=${CLOUDSDK_CORE_PROJECT} -auto-approve - when: always - - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "GKE acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - - acceptance-gke-cni-1-25: - parallelism: 2 - environment: - - TEST_RESULTS: /tmp/test-results - - USE_GKE_GCLOUD_AUTH_PLUGIN: true - - CONSUL_TEST_IMAGE: *consul-test-image - docker: - - image: *consul-helm-test-image - - steps: - - run: - name: Exit if forked PR - command: | - if [ -n "$CIRCLE_PR_NUMBER" ]; then - echo "Skipping acceptance tests for forked PRs; marking step successful." - circleci step halt - fi - - - checkout - - - build-cli - - run: - name: terraform init & apply - working_directory: *gke-terraform-path - command: | - terraform init - echo "${GOOGLE_CREDENTIALS}" | gcloud auth activate-service-account --key-file=- - - # On GKE, we're setting the build number instead of build URL because label values - # cannot contain '/'. - terraform apply \ - -var project=${CLOUDSDK_CORE_PROJECT} \ - -var init_cli=true \ - -var cluster_count=2 \ - -var labels="{\"build_number\": \"$CIRCLE_BUILD_NUM\"}" \ - -auto-approve - - primary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[0]) - secondary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[1]) - - echo "export primary_kubeconfig=$primary_kubeconfig" >> $BASH_ENV - echo "export secondary_kubeconfig=$secondary_kubeconfig" >> $BASH_ENV - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run-acceptance-tests: - additional-flags: -use-gke -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=$CONSUL_TEST_IMAGE - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - - run: - name: terraform destroy - working_directory: *gke-terraform-path - command: | - terraform destroy -var project=${CLOUDSDK_CORE_PROJECT} -auto-approve - when: always - - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "GKE CNI acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - - acceptance-aks-1-24: - parallelism: 3 - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_TEST_IMAGE: *consul-test-image - docker: - - image: *consul-helm-test-image - - steps: - - checkout - - - build-cli - - run: - name: terraform init & apply - working_directory: *aks-terraform-path - command: | - terraform init - - terraform apply \ - -var client_id="$ARM_CLIENT_ID" \ - -var client_secret="$ARM_CLIENT_SECRET" \ - -var cluster_count=2 \ - -var tags="{\"build_url\": \"$CIRCLE_BUILD_URL\"}" \ - -auto-approve - - primary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[0]) - secondary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[1]) - - echo "export primary_kubeconfig=$primary_kubeconfig" >> $BASH_ENV - echo "export secondary_kubeconfig=$secondary_kubeconfig" >> $BASH_ENV - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run-acceptance-tests: - additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - - run: - name: terraform destroy - working_directory: *aks-terraform-path - command: | - terraform destroy -auto-approve - when: always - - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "AKS acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - - acceptance-aks-cni-1-24: - parallelism: 3 - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_TEST_IMAGE: *consul-test-image - docker: - - image: *consul-helm-test-image - - steps: - - checkout - - - build-cli - - run: - name: terraform init & apply - working_directory: *aks-terraform-path - command: | - terraform init - - terraform apply \ - -var client_id="$ARM_CLIENT_ID" \ - -var client_secret="$ARM_CLIENT_SECRET" \ - -var cluster_count=2 \ - -var tags="{\"build_url\": \"$CIRCLE_BUILD_URL\"}" \ - -auto-approve - - primary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[0]) - secondary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[1]) - - echo "export primary_kubeconfig=$primary_kubeconfig" >> $BASH_ENV - echo "export secondary_kubeconfig=$secondary_kubeconfig" >> $BASH_ENV - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run-acceptance-tests: - additional-flags: -use-aks -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=$CONSUL_TEST_IMAGE - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - - run: - name: terraform destroy - working_directory: *aks-terraform-path - command: | - terraform destroy -auto-approve - when: always - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "AKS CNI acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - - acceptance-eks-1-23: - parallelism: 3 - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_TEST_IMAGE: *consul-test-image - docker: - - image: *consul-helm-test-image - - steps: - - checkout - - - build-cli - - run: - name: configure aws - command: | - aws configure --profile helm_user set aws_access_key_id "$AWS_ACCESS_KEY_ID" - aws configure --profile helm_user set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY" - aws configure set role_arn "$AWS_ROLE_ARN" - aws configure set source_profile helm_user - - echo "unset AWS_ACCESS_KEY_ID" >> $BASH_ENV - echo "unset AWS_SECRET_ACCESS_KEY" >> $BASH_ENV - - - run: - name: terraform init & apply - working_directory: *eks-terraform-path - command: | - terraform init - - terraform apply -var cluster_count=2 -var tags="{\"build_url\": \"$CIRCLE_BUILD_URL\"}" -auto-approve - - primary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[0]) - secondary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[1]) - - echo "export primary_kubeconfig=$primary_kubeconfig" >> $BASH_ENV - echo "export secondary_kubeconfig=$secondary_kubeconfig" >> $BASH_ENV - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - - run: - name: terraform destroy - working_directory: *eks-terraform-path - command: | - terraform destroy -var cluster_count=2 -auto-approve - when: always - - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "EKS acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - - acceptance-eks-cni-1-23: - parallelism: 3 - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_TEST_IMAGE: *consul-test-image - docker: - - image: *consul-helm-test-image - - steps: - - checkout - - - build-cli - - run: - name: configure aws - command: | - aws configure --profile helm_user set aws_access_key_id "$AWS_ACCESS_KEY_ID" - aws configure --profile helm_user set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY" - aws configure set role_arn "$AWS_ROLE_ARN" - aws configure set source_profile helm_user - - echo "unset AWS_ACCESS_KEY_ID" >> $BASH_ENV - echo "unset AWS_SECRET_ACCESS_KEY" >> $BASH_ENV - - - run: - name: terraform init & apply - working_directory: *eks-terraform-path - command: | - terraform init - - terraform apply -var cluster_count=2 -var tags="{\"build_url\": \"$CIRCLE_BUILD_URL\"}" -auto-approve - - primary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[0]) - secondary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[1]) - - echo "export primary_kubeconfig=$primary_kubeconfig" >> $BASH_ENV - echo "export secondary_kubeconfig=$secondary_kubeconfig" >> $BASH_ENV - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-transparent-proxy -enable-cni -consul-image=$CONSUL_TEST_IMAGE - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - - run: - name: terraform destroy - working_directory: *eks-terraform-path - command: | - terraform destroy -var cluster_count=2 -auto-approve - when: always - - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "EKS CNI acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - - acceptance-openshift: - environment: - TEST_RESULTS: /tmp/test-results - CONSUL_TEST_IMAGE: *consul-test-image - parallelism: 1 - docker: - - image: *consul-helm-test-image - - steps: - - checkout - - build-cli - - run: - name: terraform init & apply - working_directory: *openshift-terraform-path - command: | - terraform init - az login --service-principal -u "$ARM_CLIENT_ID" -p "$ARM_CLIENT_SECRET" --tenant "$ARM_TENANT_ID" > /dev/null - terraform apply \ - -var cluster_count=2 \ - -var tags="{\"build_url\": \"$CIRCLE_BUILD_URL\"}" \ - -auto-approve - - primary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[0]) - secondary_kubeconfig=$(terraform output -json | jq -r .kubeconfigs.value[1]) - - echo "export primary_kubeconfig=$primary_kubeconfig" >> $BASH_ENV - echo "export secondary_kubeconfig=$secondary_kubeconfig" >> $BASH_ENV - - # Restore go module cache if there is one - - restore_cache: - keys: - - consul-helm-acceptance-modcache-v2-{{ checksum "acceptance/go.mod" }} - - - run: mkdir -p $TEST_RESULTS - - - run-acceptance-tests: - additional-flags: -kubeconfig="$primary_kubeconfig" -secondary-kubeconfig="$secondary_kubeconfig" -enable-openshift -enable-transparent-proxy -consul-image=$CONSUL_TEST_IMAGE - - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - - run: - name: terraform destroy - working_directory: *openshift-terraform-path - command: | - terraform destroy -auto-approve - when: always - - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "OpenShift acceptance tests failed. Check the logs at: ${CIRCLE_BUILD_URL}" - - acceptance-kind-1-23-consul-compat-nightly-1-12: - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_IMAGE: "docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.12-dev" - - ENVOY_IMAGE: "envoyproxy/envoy:v1.22.2" - - HELM_CHART_VERSION: "0.49.0" - - CONSUL_K8S_IMAGE: "docker.mirror.hashicorp.services/hashicorp/consul-k8s-control-plane:0.49.0" - machine: - image: ubuntu-2004:202010-01 - resource_class: xlarge - steps: - - custom-checkout: - git-ref: "v$HELM_CHART_VERSION" - - install-prereqs - - create-kind-clusters: - version: "v1.23.0" - - restore_cache: - keys: - - consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - - run: - name: go mod download - working_directory: *acceptance-mod-path - command: go mod download - - save_cache: - key: consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - paths: - - ~/.go_workspace/pkg/mod - - build-cli - - run: mkdir -p $TEST_RESULTS - - run-acceptance-tests: - consul-k8s-image: $CONSUL_K8S_IMAGE - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -consul-image=$CONSUL_IMAGE -consul-version="1.12" -envoy-image=$ENVOY_IMAGE -helm-chart-version=$HELM_CHART_VERSION -enable-transparent-proxy - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "Acceptance tests against Kind with Kubernetes v1.25 with Consul 1.12 nightly failed. Check the logs at: ${CIRCLE_BUILD_URL}" - - acceptance-kind-1-23-consul-compat-nightly-1-13: - environment: - - TEST_RESULTS: /tmp/test-results - - CONSUL_IMAGE: "docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.13-dev" - - ENVOY_IMAGE: "envoyproxy/envoy:v1.23.1" - - CONSUL_K8S_IMAGE: "docker.mirror.hashicorp.services/hashicorp/consul-k8s-control-plane:0.49.0" - - HELM_CHART_VERSION: "0.49.0" - machine: - image: ubuntu-2004:202010-01 - resource_class: xlarge - steps: - - custom-checkout: - git-ref: "v$HELM_CHART_VERSION" - - install-prereqs - - create-kind-clusters: - version: "v1.23.0" - - restore_cache: - keys: - - consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - - run: - name: go mod download - working_directory: *acceptance-mod-path - command: go mod download - - save_cache: - key: consul-helm-modcache-v2-{{ checksum "acceptance/go.mod" }} - paths: - - ~/.go_workspace/pkg/mod - - build-cli - - run: mkdir -p $TEST_RESULTS - - run-acceptance-tests: - consul-k8s-image: $CONSUL_K8S_IMAGE - additional-flags: -use-kind -kubecontext="kind-dc1" -secondary-kubecontext="kind-dc2" -consul-image=$CONSUL_IMAGE -consul-version="1.13" -envoy-image=$ENVOY_IMAGE -helm-chart-version=$HELM_CHART_VERSION -enable-transparent-proxy - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/test-results - - slack/status: - channel: *slack-channel - fail_only: true - failure_message: "Acceptance tests against Kind with Kubernetes v1.25 with Consul 1.13 nightly failed. Check the logs at: ${CIRCLE_BUILD_URL}" - -######################## -# WORKFLOWS -######################## -# Workflows are a set of rules for defining a collection of jobs and their run order. -# This is where the pipeline tests and builds are constructed and triggers for running these tests -# are defined -workflows: - version: 2 - test-and-build: - jobs: - # Build this one control-plane binary so that acceptance and acceptance-tproxy will run - # The rest of these CircleCI jobs have been migrated to GitHub Actions. We need to wait until - # there is support for a larger pool of runners before the acceptance tests can - # be moved - # Run acceptance tests using the docker image built for the control plane for this particular - # branch - # This is run on every PR - - build-distro: - OS: "linux" - ARCH: "amd64 arm64" - name: build-distros-linux - - dev-upload-docker: - context: consul-ci - requires: - - build-distros-linux - - acceptance: - context: consul-ci - requires: - - dev-upload-docker - - acceptance-tproxy-cni: - context: consul-ci - requires: - - dev-upload-docker - - acceptance-tproxy: - context: consul-ci - requires: - - dev-upload-docker - - - nightly-cleanup: - triggers: - - schedule: - cron: "0 12 * * *" # Run at 12 pm UTC (5 am PST) - filters: - branches: - only: - - main - jobs: - - cleanup-gcp-resources - - cleanup-azure-resources - - cleanup-eks-resources - - nightly-acceptance-tests-release: - description: | - Tests which run on a release branch nightly. These exist separate from the main - acceptance tests so that they can run at their own cadence, but - contains the same sequence of jobs. - triggers: - - schedule: - cron: "0 0 * * *" # Run at 12 am UTC (5 pm PST) - filters: - branches: - only: - - release/0.49.x - - release/1.0.x - - release/1.1.x - jobs: - - build-distro: - OS: "linux" - ARCH: "amd64 arm64" - name: build-distros-linux - - dev-upload-docker: - requires: - - build-distros-linux - # Disable until we can use UBI images. - # - acceptance-openshift - - acceptance-gke-1-25: - requires: - - dev-upload-docker - - acceptance-gke-cni-1-25: - requires: - - acceptance-gke-1-25 - - acceptance-tproxy: - requires: - - dev-upload-docker - - nightly-acceptance-tests-main: - description: | - Tests which run on the main branch nightly. These exist separate from the release - acceptance tests so that they can run at their own cadence, but - contains the same sequence of jobs. - triggers: - - schedule: - cron: "0 0 * * *" # Run at 12 am UTC (5 pm PST) - filters: - branches: - only: - - main - jobs: - - build-distro: - OS: "linux" - ARCH: "amd64 arm64" - name: build-distros-linux - - dev-upload-docker: - requires: - - build-distros-linux - # Disable until we can use UBI images. - # - acceptance-openshift - - acceptance-gke-1-25: - requires: - - dev-upload-docker - - acceptance-gke-cni-1-25: - requires: - - acceptance-gke-1-25 - - acceptance-eks-1-23: - requires: - - dev-upload-docker - - acceptance-eks-cni-1-23: - requires: - - acceptance-eks-1-23 - - acceptance-aks-1-24: - requires: - - dev-upload-docker - - acceptance-aks-cni-1-24: - requires: - - acceptance-aks-1-24 - - acceptance-tproxy: - requires: - - dev-upload-docker From 5817c28423f2ddc404454443f1ecb4685c7b92a0 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 14 Apr 2023 13:45:59 -0400 Subject: [PATCH 135/592] Update status on PRs (#2054) * Update status on PRs * Split pr and push into 2 different files so that context can be passed through --- .github/workflows/merge.yml | 31 +++++++++++++++++++++++++++++++ .github/workflows/pr.yml | 14 ++++---------- 2 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/merge.yml diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml new file mode 100644 index 0000000000..be1b392f4a --- /dev/null +++ b/.github/workflows/merge.yml @@ -0,0 +1,31 @@ +# Dispatch to the consul-k8s-workflows when a PR is created and on merges to main/release* +name: merge +on: + push: + # Sequence of patterns matched against refs/heads + branches: + # Push events on main branch + - main + # Push events to branches matching refs/heads/release/** + - "release/**" + +# these should be the only settings that you will ever need to change +env: + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests. We use this consul image on release branches too + BRANCH: ${{ github.head_ref || github.ref_name }} + CONTEXT: "merge" + SHA: ${{ github.event.pull_request.head.sha || github.sha }} + +jobs: + test: + name: test + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@v1.2.2 + name: test + with: + workflow: test.yml + repo: hashicorp/consul-k8s-workflows + ref: main + token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d006c2514c..32baf472fb 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -2,19 +2,13 @@ name: pr on: pull_request: - push: - # Sequence of patterns matched against refs/heads - branches: - # Push events on main branch - - main - # Push events to branches matching refs/heads/release/** - - "release/**" # these should be the only settings that you will ever need to change env: CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests. We use this consul image on release branches too - BRANCH: ${{ github.head_ref || github.ref_name }} - CONTEXT: "${{ github.actor }}" + BRANCH: ${{ github.head_ref || github.ref_name }} + CONTEXT: "pr" + SHA: ${{ github.event.pull_request.head.sha || github.sha }} jobs: test: @@ -28,4 +22,4 @@ jobs: 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' From 8d2c1931f838f0f1b09fed894926a897713e8f8e Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Mon, 17 Apr 2023 14:53:10 -0400 Subject: [PATCH 136/592] Update backport assistant to support -gh-automerge (#2047) --- .github/workflows/backport.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 0283034a66..ad9b77b0d1 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -13,10 +13,10 @@ jobs: backport: if: github.event.pull_request.merged runs-on: ubuntu-latest - container: hashicorpdev/backport-assistant:0.2.5 + container: hashicorpdev/backport-assistant:0.3.0 steps: - name: Run Backport Assistant - run: backport-assistant backport -automerge + run: backport-assistant backport -merge-method=squash -gh-automerge env: BACKPORT_LABEL_REGEXP: "backport/(?P\\d+\\.\\d+\\.x)" BACKPORT_TARGET_TEMPLATE: "release/{{.target}}" From 34fb4a2a1e2047cf254174e048126e75e75c1a34 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Mon, 17 Apr 2023 20:09:52 -0400 Subject: [PATCH 137/592] Add a cleanup cron job (#2059) * Add a cleanup cron job --- .github/workflows/nightly-cleanup.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/nightly-cleanup.yml diff --git a/.github/workflows/nightly-cleanup.yml b/.github/workflows/nightly-cleanup.yml new file mode 100644 index 0000000000..79360e497d --- /dev/null +++ b/.github/workflows/nightly-cleanup.yml @@ -0,0 +1,27 @@ +# Dispatch to the consul-k8s-workflows with a nightly cron +name: nightly-cleanup +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run nightly at 12PM UTC/8AM EST/5AM PST + - cron: '0 12 * * *' + +# these should be the only settings that you will ever need to change +env: + CONSUL_IMAGE: "not used" + BRANCH: ${{ github.ref_name }} + CONTEXT: "nightly" + +jobs: + cleanup: + name: cleanup + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@v1.2.2 + name: cleanup + with: + workflow: cleanup.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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' From 11f842cbe94b5df544c03e2441a1820397f92b9b Mon Sep 17 00:00:00 2001 From: malizz Date: Tue, 18 Apr 2023 16:12:58 -0700 Subject: [PATCH 138/592] add sameness group CRD (#2048) * draft of adding sameness group CRD * move sameness group tests to ent test file * update tests * fix lint issues * generate yaml and update helm charts * update field descriptions and validation and its test * remove unwanted files, add license comments back * rename samenessgroups to samenessgroup * fix resource names * update failing unit test --- CONTRIBUTING.md | 10 +- .../templates/connect-inject-clusterrole.yaml | 2 + ...t-inject-mutatingwebhookconfiguration.yaml | 21 + .../consul/templates/crd-proxydefaults.yaml | 14 +- .../consul/templates/crd-samenessgroups.yaml | 126 ++++++ .../templates/crd-serviceresolvers.yaml | 9 +- control-plane/PROJECT | 13 + control-plane/api/common/common.go | 1 + .../api/v1alpha1/samenessgroup_types.go | 262 ++++++++++++ .../api/v1alpha1/samenessgroup_types_test.go | 390 ++++++++++++++++++ .../api/v1alpha1/samenessgroup_webhook.go | 61 +++ .../v1alpha1/servicedefaults_types_test.go | 2 +- control-plane/api/v1alpha1/shared_types.go | 8 +- .../api/v1alpha1/zz_generated.deepcopy.go | 124 ++++++ .../consul.hashicorp.com_proxydefaults.yaml | 12 +- .../consul.hashicorp.com_samenessgroups.yaml | 121 ++++++ ...consul.hashicorp.com_serviceresolvers.yaml | 9 +- control-plane/config/rbac/role.yaml | 23 +- control-plane/config/webhook/manifests.yaml | 24 +- .../configentry_controller.go | 2 +- .../configentry_controller_ent_test.go | 382 +++++++++++++++-- .../configentry_controller_test.go | 2 +- .../exportedservices_controller.go | 2 +- .../exportedservices_controller_ent_test.go | 20 +- .../ingressgateway_controller.go | 2 +- .../mesh_controller.go | 2 +- .../proxydefaults_controller.go | 2 +- .../controllers/samenessgroups_controller.go | 41 ++ .../servicedefaults_controller.go | 2 +- .../serviceintentions_controller.go | 2 +- .../serviceresolver_controller.go | 2 +- .../servicerouter_controller.go | 2 +- .../servicesplitter_controller.go | 2 +- .../terminatinggateway_controller.go | 2 +- control-plane/go.mod | 4 + control-plane/main.go | 3 +- .../subcommand/inject-connect/command.go | 39 +- 37 files changed, 1640 insertions(+), 105 deletions(-) create mode 100644 charts/consul/templates/crd-samenessgroups.yaml create mode 100644 control-plane/api/v1alpha1/samenessgroup_types.go create mode 100644 control-plane/api/v1alpha1/samenessgroup_types_test.go create mode 100644 control-plane/api/v1alpha1/samenessgroup_webhook.go create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml rename control-plane/{controller => controllers}/configentry_controller.go (99%) rename control-plane/{controller => controllers}/configentry_controller_ent_test.go (64%) rename control-plane/{controller => controllers}/configentry_controller_test.go (99%) rename control-plane/{controller => controllers}/exportedservices_controller.go (98%) rename control-plane/{controller => controllers}/exportedservices_controller_ent_test.go (95%) rename control-plane/{controller => controllers}/ingressgateway_controller.go (98%) rename control-plane/{controller => controllers}/mesh_controller.go (98%) rename control-plane/{controller => controllers}/proxydefaults_controller.go (98%) create mode 100644 control-plane/controllers/samenessgroups_controller.go rename control-plane/{controller => controllers}/servicedefaults_controller.go (98%) rename control-plane/{controller => controllers}/serviceintentions_controller.go (98%) rename control-plane/{controller => controllers}/serviceresolver_controller.go (98%) rename control-plane/{controller => controllers}/servicerouter_controller.go (98%) rename control-plane/{controller => controllers}/servicesplitter_controller.go (98%) rename control-plane/{controller => controllers}/terminatinggateway_controller.go (98%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e634e323e..3745c80bac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -167,7 +167,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ```bash operator-sdk create api --group consul --version v1alpha1 --kind IngressGateway --controller --namespaced=true --make=false --resource=true ``` -1. Re-order the file so it looks like: +1. Re-order the generated ingressgateway_types.go file, so it looks like: ```go func init() { SchemeBuilder.Register(&IngressGateway{}, &IngressGatewayList{}) @@ -320,8 +320,6 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ### Controller 1. Delete the file `control-plane/controllers/suite_test.go`. We don't write suite tests, just unit tests. -1. Move `control-plane/controllers/ingressgateway_controller.go` to `control-plane/controller` directory. -1. Delete the `control-plane/controllers` directory. 1. Rename `Reconciler` to `Controller`, e.g. `IngressGatewayReconciler` => `IngressGatewayController` 1. Use the existing controller files as a guide and make this file match. 1. Add your controller as a case in the tests in `configentry_controller_test.go`: @@ -395,13 +393,13 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ``` ### Updating Helm chart -1. Update `charts/consul/templates/controller-mutatingwebhookconfiguration` with the webhook for this resource +1. Update `charts/consul/templates/connect-inject-mutatingwebhookconfiguration` with the webhook for this resource using the updated `control-plane/config/webhook/manifests.v1beta1.yaml` and replacing `clientConfig.service.name/namespace` with the templated strings shown below to match the other webhooks.: ```yaml - clientConfig: service: - name: {{ template "consul.fullname" . }}-controller-webhook + name: {{ template "consul.fullname" . }}-connect-injector namespace: {{ .Release.Namespace }} path: /mutate-v1alpha1-ingressgateway failurePolicy: Fail @@ -421,7 +419,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca - ingressgateways sideEffects: None ``` -1. Update `charts/consul/templates/controller-clusterrole.yaml` to allow the controller to +1. Update `charts/consul/templates/connect-inject-clusterrole.yaml` to allow the controller to manage your resource type. ### Testing A New CRD diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index f2e12f0ad9..e383e5ce28 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -24,6 +24,7 @@ rules: - serviceintentions - ingressgateways - terminatinggateways + - samenessgroups {{- if .Values.global.peering.enabled }} - peeringacceptors - peeringdialers @@ -49,6 +50,7 @@ rules: - serviceintentions/status - ingressgateways/status - terminatinggateways/status + - samenessgroups/status {{- if .Values.global.peering.enabled }} - peeringacceptors/status - peeringdialers/status diff --git a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml index afcfd3800f..b68efdb9f8 100644 --- a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml +++ b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml @@ -291,5 +291,26 @@ webhooks: admissionReviewVersions: - "v1beta1" - "v1" +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: {{ template "consul.fullname" . }}-connect-injector + namespace: {{ .Release.Namespace }} + path: /mutate-v1alpha1-samenessgroup + failurePolicy: Fail + name: mutate-samenessgroup.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - samenessgroups + sideEffects: None {{- end }} {{- end }} diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index aaaa3228a1..6bb1234066 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -143,16 +143,18 @@ spec: type: object type: array type: object - failoverPolicy: - description: FailoverPolicy specifies the exact mechanism used for failover. + failoverPolicy: + description: FailoverPolicy specifies the exact mechanism used for + failover. properties: mode: - description: Mode specifies the type of failover that will be performed. - Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". + description: Mode specifies the type of failover that will be + performed. Valid values are "sequential", "" (equivalent to + "sequential") and "order-by-locality". type: string - regions: + regions: description: The ordered list of the regions of the failover targets. - Valid values can be "us-west-1", "us-west-2", and so on. + Valid values can be "us-west-1", "us-west-2", and so on. items: type: string type: array diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml new file mode 100644 index 0000000000..e541539481 --- /dev/null +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -0,0 +1,126 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: samenessgroups.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: consul.hashicorp.com + names: + kind: SamenessGroup + listKind: SamenessGroupList + plural: samenessgroups + shortNames: + - sameness-group + singular: samenessgroup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: SamenessGroup is the Schema for the samenessgroups API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SamenessGroupSpec defines the desired state of SamenessGroup. + properties: + defaultForFailover: + description: 'DefaultForFailover indicates that upstream requests to members of the given sameness group will implicitly failover between members of this sameness group.' + type: boolean + includeLocal: + description: 'IncludeLocal is used to include the local partition as the first member of the sameness group.' + type: boolean + members: + description: 'Members are the partitions and peers that are part of the sameness group.' + items: + properties: + partition: + type: string + peer: + type: string + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 842506d642..1942b79b8f 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -80,13 +80,14 @@ spec: the current namespace is used. type: string policy: - description: FailoverPolicy specifies the exact mechanism used for failover. + description: Policy specifies the exact mechanism used for failover. properties: mode: - description: Mode specifies the type of failover that will be performed. - Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". + description: Mode specifies the type of failover that will + be performed. Valid values are "sequential", "" (equivalent + to "sequential") and "order-by-locality". type: string - regions: + regions: description: The ordered list of the regions of the failover targets. Valid values can be "us-west-1", "us-west-2", and so on. items: diff --git a/control-plane/PROJECT b/control-plane/PROJECT index c11e857849..eb653ad34a 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -1,3 +1,7 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html domain: hashicorp.com layout: - go.kubebuilder.io/v2 @@ -77,4 +81,13 @@ resources: kind: PeeringDialer path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: hashicorp.com + group: consul + kind: SamenessGroup + path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 2c579ba715..4faeccada1 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -14,6 +14,7 @@ const ( ExportedServices string = "exportedservices" IngressGateway string = "ingressgateway" TerminatingGateway string = "terminatinggateway" + SamenessGroup string = "samenessgroup" Global string = "global" Mesh string = "mesh" diff --git a/control-plane/api/v1alpha1/samenessgroup_types.go b/control-plane/api/v1alpha1/samenessgroup_types.go new file mode 100644 index 0000000000..7f5f194150 --- /dev/null +++ b/control-plane/api/v1alpha1/samenessgroup_types.go @@ -0,0 +1,262 @@ +package v1alpha1 + +import ( + "encoding/json" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul/api" + capi "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +const ( + SamenessGroupKubeKind string = "samenessgroup" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +func init() { + SchemeBuilder.Register(&SamenessGroup{}, &SamenessGroupList{}) +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// SamenessGroup is the Schema for the samenessgroups API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="sameness-group" +type SamenessGroup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec SamenessGroupSpec `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// SamenessGroupList contains a list of SamenessGroup. +type SamenessGroupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SamenessGroup `json:"items"` +} + +// SamenessGroupSpec defines the desired state of SamenessGroup. +type SamenessGroupSpec struct { + // DefaultForFailover indicates that upstream requests to members of the given sameness group will implicitly failover between members of this sameness group. + // When DefaultForFailover is true, the local partition must be a member of the sameness group or IncludeLocal must be set to true. + DefaultForFailover bool `json:"defaultForFailover,omitempty"` + // IncludeLocal is used to include the local partition as the first member of the sameness group. + // The local partition can only be a member of a single sameness group. + IncludeLocal bool `json:"includeLocal,omitempty"` + // Members are the partitions and peers that are part of the sameness group. + // If a member of a sameness group does not exist, it will be ignored. + Members []SamenessGroupMember `json:"members,omitempty"` +} + +type SamenessGroupMember struct { + // The partitions and peers that are part of the sameness group. + // A sameness group member cannot define both peer and partition at the same time. + Partition string `json:"partition,omitempty"` + Peer string `json:"peer,omitempty"` +} + +func (in *SamenessGroup) GetObjectMeta() metav1.ObjectMeta { + return in.ObjectMeta +} + +func (in *SamenessGroup) AddFinalizer(name string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), name) +} + +func (in *SamenessGroup) RemoveFinalizer(name string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != name { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *SamenessGroup) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *SamenessGroup) ConsulKind() string { + return capi.SamenessGroup +} + +func (in *SamenessGroup) ConsulGlobalResource() bool { + return false +} + +func (in *SamenessGroup) ConsulMirroringNS() string { + return common.DefaultConsulNamespace +} + +func (in *SamenessGroup) KubeKind() string { + return SamenessGroupKubeKind +} + +func (in *SamenessGroup) ConsulName() string { + return in.ObjectMeta.Name +} + +func (in *SamenessGroup) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *SamenessGroup) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *SamenessGroup) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *SamenessGroup) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *SamenessGroup) SyncedConditionStatus() corev1.ConditionStatus { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown + } + return cond.Status +} + +func (in *SamenessGroup) ToConsul(datacenter string) api.ConfigEntry { + return &capi.SamenessGroupConfigEntry{ + Kind: in.ConsulKind(), + Name: in.ConsulName(), + DefaultForFailover: in.Spec.DefaultForFailover, + IncludeLocal: in.Spec.IncludeLocal, + Members: SamenessGroupMembers(in.Spec.Members).toConsul(), + Meta: meta(datacenter), + } +} + +func (in *SamenessGroup) MatchesConsul(candidate api.ConfigEntry) bool { + configEntry, ok := candidate.(*capi.SamenessGroupConfigEntry) + if !ok { + return false + } + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.SamenessGroupConfigEntry{}, "Partition", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), + cmp.Comparer(transparentProxyConfigComparer)) +} + +func (in *SamenessGroup) Validate(consulMeta common.ConsulMeta) error { + var allErrs field.ErrorList + path := field.NewPath("spec") + + if in == nil { + return nil + } + if in.Name == "" { + allErrs = append(allErrs, field.Invalid(path.Child("name"), in.Name, "sameness groups must have a name defined")) + } + + partition := consulMeta.Partition + includesLocal := in.Spec.IncludeLocal + + if in.ObjectMeta.Namespace != "default" && in.ObjectMeta.Namespace != "" { + allErrs = append(allErrs, field.Invalid(path.Child("name"), consulMeta.DestinationNamespace, "sameness groups must reside in the default namespace")) + } + + if len(in.Spec.Members) == 0 { + asJSON, _ := json.Marshal(in.Spec.Members) + allErrs = append(allErrs, field.Invalid(path.Child("members"), string(asJSON), "sameness groups must have at least one member")) + } + + seenMembers := make(map[SamenessGroupMember]struct{}) + for i, m := range in.Spec.Members { + if partition == m.Partition { + includesLocal = true + } + if err := m.validate(path.Child("members").Index(i)); err != nil { + allErrs = append(allErrs, err) + } + if _, ok := seenMembers[m]; ok { + asJSON, _ := json.Marshal(m) + allErrs = append(allErrs, field.Invalid(path.Child("members").Index(i), string(asJSON), "sameness group members must be unique")) + } + seenMembers[m] = struct{}{} + + } + + if !includesLocal { + allErrs = append(allErrs, field.Invalid(path.Child("members"), in.Spec.IncludeLocal, "the local partition must be a member of sameness groups")) + } + + if len(allErrs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: ConsulHashicorpGroup, Kind: SamenessGroupKubeKind}, + in.KubernetesName(), allErrs) + } + + return nil +} + +// DefaultNamespaceFields has no behaviour here as sameness-groups have no namespace specific fields. +func (in *SamenessGroup) DefaultNamespaceFields(_ common.ConsulMeta) { +} + +type SamenessGroupMembers []SamenessGroupMember + +func (in SamenessGroupMembers) toConsul() []capi.SamenessGroupMember { + if in == nil { + return nil + } + + outMembers := make([]capi.SamenessGroupMember, 0, len(in)) + for _, e := range in { + consulMember := capi.SamenessGroupMember{ + Peer: e.Peer, + Partition: e.Partition, + } + outMembers = append(outMembers, consulMember) + } + return outMembers +} + +func (in *SamenessGroupMember) validate(path *field.Path) *field.Error { + asJSON, _ := json.Marshal(in) + + if in == nil { + return field.Invalid(path, string(asJSON), "sameness group member is nil") + } + if in.isEmpty() { + return field.Invalid(path, string(asJSON), "sameness group members must specify either partition or peer") + } + // We do not allow referencing peer connections in other partitions. + if in.Peer != "" && in.Partition != "" { + return field.Invalid(path, string(asJSON), "sameness group members cannot specify both partition and peer in the same entry") + } + return nil +} + +func (in *SamenessGroupMember) isEmpty() bool { + return in.Peer == "" && in.Partition == "" +} diff --git a/control-plane/api/v1alpha1/samenessgroup_types_test.go b/control-plane/api/v1alpha1/samenessgroup_types_test.go new file mode 100644 index 0000000000..0f461701cc --- /dev/null +++ b/control-plane/api/v1alpha1/samenessgroup_types_test.go @@ -0,0 +1,390 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "github.com/hashicorp/consul-k8s/control-plane/api/common" + capi "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" + "time" +) + +func TestSamenessGroups_ToConsul(t *testing.T) { + cases := map[string]struct { + input *SamenessGroup + expected *capi.SamenessGroupConfigEntry + }{ + "empty fields": { + &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: SamenessGroupSpec{}, + }, + &capi.SamenessGroupConfigEntry{ + Name: "foo", + Kind: capi.SamenessGroup, + Meta: map[string]string{ + common.SourceKey: common.SourceValue, + common.DatacenterKey: "datacenter", + }, + }, + }, + "every field set": { + &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []SamenessGroupMember{ + { + Peer: "peer2", + }, + { + Partition: "p2", + }, + }, + }, + }, + &capi.SamenessGroupConfigEntry{ + Name: "foo", + Kind: capi.SamenessGroup, + Meta: map[string]string{ + common.SourceKey: common.SourceValue, + common.DatacenterKey: "datacenter", + }, + DefaultForFailover: true, + IncludeLocal: true, + Members: []capi.SamenessGroupMember{ + { + Peer: "peer2", + }, + { + Partition: "p2", + }, + }, + }, + }, + } + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + output := testCase.input.ToConsul("datacenter") + require.Equal(t, testCase.expected, output) + }) + } +} + +func TestSamenessGroups_MatchesConsul(t *testing.T) { + cases := map[string]struct { + internal *SamenessGroup + consul capi.ConfigEntry + matches bool + }{ + "empty fields matches": { + &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-test-sameness-group", + }, + Spec: SamenessGroupSpec{}, + }, + &capi.SamenessGroupConfigEntry{ + Kind: capi.SamenessGroup, + Name: "my-test-sameness-group", + CreateIndex: 1, + ModifyIndex: 2, + Meta: map[string]string{ + common.SourceKey: common.SourceValue, + common.DatacenterKey: "datacenter", + }, + }, + true, + }, + "all fields populated matches": { + &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-test-sameness-group", + }, + Spec: SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []SamenessGroupMember{ + { + Peer: "peer2", + }, + { + Partition: "p2", + }, + }, + }, + }, + &capi.SamenessGroupConfigEntry{ + Kind: capi.SamenessGroup, + Name: "my-test-sameness-group", + Meta: map[string]string{ + common.SourceKey: common.SourceValue, + common.DatacenterKey: "datacenter", + }, + DefaultForFailover: true, + IncludeLocal: true, + Members: []capi.SamenessGroupMember{ + { + Peer: "peer2", + }, + { + Partition: "p2", + }, + }, + }, + true, + }, + } + + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + require.Equal(t, testCase.matches, testCase.internal.MatchesConsul(testCase.consul)) + }) + } +} + +func TestSamenessGroups_Validate(t *testing.T) { + cases := map[string]struct { + input *SamenessGroup + partitionsEnabled bool + expectedErrMsg string + }{ + "valid": { + input: &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-sameness-group", + }, + Spec: SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []SamenessGroupMember{ + { + Peer: "peer2", + Partition: "", + }, + { + Peer: "", + Partition: "p2", + }, + }, + }, + }, + partitionsEnabled: true, + expectedErrMsg: "", + }, + "invalid - with peer and partition both": { + input: &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-sameness-group", + }, + Spec: SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []SamenessGroupMember{ + { + Peer: "peer2", + Partition: "p2", + }, + }, + }, + }, + partitionsEnabled: true, + expectedErrMsg: "sameness group members cannot specify both partition and peer in the same entry", + }, + "invalid - no name": { + input: &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []SamenessGroupMember{ + { + Peer: "peer2", + }, + { + Partition: "p2", + }, + }, + }, + }, + partitionsEnabled: true, + expectedErrMsg: "sameness groups must have a name defined", + }, + "invalid - empty members": { + input: &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-sameness-group", + }, + Spec: SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []SamenessGroupMember{}, + }, + }, + partitionsEnabled: true, + expectedErrMsg: "sameness groups must have at least one member", + }, + "invalid - not unique members": { + input: &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-sameness-group", + }, + Spec: SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []SamenessGroupMember{ + { + Peer: "peer2", + }, + { + Peer: "peer2", + }, + }, + }, + }, + partitionsEnabled: true, + expectedErrMsg: "sameness group members must be unique", + }, + "invalid - not in default namespace": { + input: &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-sameness-group", + Namespace: "non-default", + }, + Spec: SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []SamenessGroupMember{ + { + Peer: "peer2", + }, + }, + }, + }, + partitionsEnabled: true, + expectedErrMsg: "sameness groups must reside in the default namespace", + }, + } + + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + err := testCase.input.Validate(common.ConsulMeta{}) + if testCase.expectedErrMsg != "" { + require.ErrorContains(t, err, testCase.expectedErrMsg) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestSamenessGroups_GetObjectMeta(t *testing.T) { + meta := metav1.ObjectMeta{ + Name: "name", + } + samenessGroups := &SamenessGroup{ + ObjectMeta: meta, + } + require.Equal(t, meta, samenessGroups.GetObjectMeta()) +} + +func TestSamenessGroups_AddFinalizer(t *testing.T) { + samenessGroups := &SamenessGroup{} + samenessGroups.AddFinalizer("finalizer") + require.Equal(t, []string{"finalizer"}, samenessGroups.ObjectMeta.Finalizers) +} + +func TestSamenessGroups_RemoveFinalizer(t *testing.T) { + samenessGroups := &SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{"f1", "f2"}, + }, + } + samenessGroups.RemoveFinalizer("f1") + require.Equal(t, []string{"f2"}, samenessGroups.ObjectMeta.Finalizers) +} + +func TestSamenessGroups_ConsulKind(t *testing.T) { + require.Equal(t, capi.SamenessGroup, (&SamenessGroup{}).ConsulKind()) +} + +func TestSamenessGroups_ConsulGlobalResource(t *testing.T) { + require.False(t, (&SamenessGroup{}).ConsulGlobalResource()) +} + +func TestSamenessGroups_ConsulMirroringNS(t *testing.T) { + +} + +func TestSamenessGroups_KubeKind(t *testing.T) { + require.Equal(t, "samenessgroup", (&SamenessGroup{}).KubeKind()) +} + +func TestSamenessGroups_ConsulName(t *testing.T) { + require.Equal(t, "foo", (&SamenessGroup{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).ConsulName()) +} + +func TestSamenessGroups_KubernetesName(t *testing.T) { + require.Equal(t, "foo", (&SamenessGroup{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).KubernetesName()) +} + +func TestSamenessGroups_SetSyncedCondition(t *testing.T) { + samenessGroups := &SamenessGroup{} + samenessGroups.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") + + require.Equal(t, corev1.ConditionTrue, samenessGroups.Status.Conditions[0].Status) + require.Equal(t, "reason", samenessGroups.Status.Conditions[0].Reason) + require.Equal(t, "message", samenessGroups.Status.Conditions[0].Message) + now := metav1.Now() + require.True(t, samenessGroups.Status.Conditions[0].LastTransitionTime.Before(&now)) +} + +func TestSamenessGroups_SetLastSyncedTime(t *testing.T) { + samenessGroups := &SamenessGroup{} + syncedTime := metav1.NewTime(time.Now()) + samenessGroups.SetLastSyncedTime(&syncedTime) + + require.Equal(t, &syncedTime, samenessGroups.Status.LastSyncedTime) +} + +func TestSamenessGroups_GetSyncedConditionStatus(t *testing.T) { + cases := []corev1.ConditionStatus{ + corev1.ConditionUnknown, + corev1.ConditionFalse, + corev1.ConditionTrue, + } + for _, status := range cases { + t.Run(string(status), func(t *testing.T) { + samenessGroups := &SamenessGroup{ + Status: Status{ + Conditions: []Condition{{ + Type: ConditionSynced, + Status: status, + }}, + }, + } + + require.Equal(t, status, samenessGroups.SyncedConditionStatus()) + }) + } +} + +func TestSamenessGroups_SyncedConditionStatusWhenStatusNil(t *testing.T) { + require.Equal(t, corev1.ConditionUnknown, (&SamenessGroup{}).SyncedConditionStatus()) +} + +func TestSamenessGroups_SyncedConditionWhenStatusNil(t *testing.T) { + status, reason, message := (&SamenessGroup{}).SyncedCondition() + require.Equal(t, corev1.ConditionUnknown, status) + require.Equal(t, "", reason) + require.Equal(t, "", message) +} diff --git a/control-plane/api/v1alpha1/samenessgroup_webhook.go b/control-plane/api/v1alpha1/samenessgroup_webhook.go new file mode 100644 index 0000000000..6c1da5cba2 --- /dev/null +++ b/control-plane/api/v1alpha1/samenessgroup_webhook.go @@ -0,0 +1,61 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "context" + "net/http" + + "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// +kubebuilder:object:generate=false + +type SamenessGroupWebhook struct { + Logger logr.Logger + + // ConsulMeta contains metadata specific to the Consul installation. + ConsulMeta common.ConsulMeta + + decoder *admission.Decoder + client.Client +} + +// NOTE: The path value in the below line is the path to the webhook. +// If it is updated, run code-gen, update subcommand/controller/command.go +// and the consul-helm value for the path to the webhook. +// +// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. +// +// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-samenessgroups,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=samenessgroups,versions=v1alpha1,name=mutate-samenessgroup.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *SamenessGroupWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var resource SamenessGroup + err := v.decoder.Decode(req, &resource) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + return common.ValidateConfigEntry(ctx, req, v.Logger, v, &resource, v.ConsulMeta) +} + +func (v *SamenessGroupWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { + var resourceList SamenessGroupList + if err := v.Client.List(ctx, &resourceList); err != nil { + return nil, err + } + var entries []common.ConfigEntryResource + for _, item := range resourceList.Items { + entries = append(entries, common.ConfigEntryResource(&item)) + } + return entries, nil +} + +func (v *SamenessGroupWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 9f9e7ebbda..69b749decd 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -1296,7 +1296,7 @@ func TestServiceDefaults_ConsulName(t *testing.T) { } func TestServiceDefaults_KubernetesName(t *testing.T) { - require.Equal(t, "foo", (&ServiceDefaults{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).ConsulName()) + require.Equal(t, "foo", (&ServiceDefaults{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).KubernetesName()) } func TestServiceDefaults_ConsulNamespace(t *testing.T) { diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index fdac9a3fea..98eb3f1b20 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -248,9 +248,11 @@ func (in EnvoyExtension) validate(path *field.Path) *field.Error { // FailoverPolicy specifies the exact mechanism used for failover. type FailoverPolicy struct { // Mode specifies the type of failover that will be performed. Valid values are - // "default", "" (equivalent to "default") and "order-by-locality". - Mode string `json:",omitempty"` - Regions []string `json:",omitempty"` + // "sequential", "" (equivalent to "sequential") and "order-by-locality". + Mode string `json:"mode,omitempty"` + // Regions is the ordered list of the regions of the failover targets. + // Valid values can be "us-west-1", "us-west-2", and so on. + Regions []string `json:"regions,omitempty"` } func (in *FailoverPolicy) toConsul() *capi.ServiceResolverFailoverPolicy { diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index d12db29d14..f485b5c5f1 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -255,6 +255,26 @@ func (in *ExposePath) DeepCopy() *ExposePath { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FailoverPolicy) DeepCopyInto(out *FailoverPolicy) { + *out = *in + if in.Regions != nil { + in, out := &in.Regions, &out.Regions + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailoverPolicy. +func (in *FailoverPolicy) DeepCopy() *FailoverPolicy { + if in == nil { + return nil + } + out := new(FailoverPolicy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayServiceTLSConfig) DeepCopyInto(out *GatewayServiceTLSConfig) { *out = *in @@ -1272,6 +1292,11 @@ func (in *ProxyDefaultsSpec) DeepCopyInto(out *ProxyDefaultsSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.FailoverPolicy != nil { + in, out := &in.FailoverPolicy, &out.FailoverPolicy + *out = new(FailoverPolicy) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsSpec. @@ -1299,6 +1324,100 @@ func (in *RingHashConfig) DeepCopy() *RingHashConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SamenessGroupMember) DeepCopyInto(out *SamenessGroupMember) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupMember. +func (in *SamenessGroupMember) DeepCopy() *SamenessGroupMember { + if in == nil { + return nil + } + out := new(SamenessGroupMember) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SamenessGroup) DeepCopyInto(out *SamenessGroup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroups. +func (in *SamenessGroup) DeepCopy() *SamenessGroup { + if in == nil { + return nil + } + out := new(SamenessGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SamenessGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SamenessGroupList) DeepCopyInto(out *SamenessGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SamenessGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupList. +func (in *SamenessGroupList) DeepCopy() *SamenessGroupList { + if in == nil { + return nil + } + out := new(SamenessGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SamenessGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SamenessGroupSpec) DeepCopyInto(out *SamenessGroupSpec) { + *out = *in + if in.Members != nil { + in, out := &in.Members, &out.Members + *out = make([]SamenessGroupMember, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupSpec. +func (in *SamenessGroupSpec) DeepCopy() *SamenessGroupSpec { + if in == nil { + return nil + } + out := new(SamenessGroupSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Secret) DeepCopyInto(out *Secret) { *out = *in @@ -1594,6 +1713,11 @@ func (in *ServiceResolverFailover) DeepCopyInto(out *ServiceResolverFailover) { *out = make([]ServiceResolverFailoverTarget, len(*in)) copy(*out, *in) } + if in.Policy != nil { + in, out := &in.Policy, &out.Policy + *out = new(FailoverPolicy) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceResolverFailover. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 0f90e95e16..c66b5fdd0f 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -139,14 +139,16 @@ spec: type: object type: array type: object - failoverPolicy: - description: FailoverPolicy specifies the exact mechanism used for failover. + failoverPolicy: + description: FailoverPolicy specifies the exact mechanism used for + failover. properties: mode: - description: Mode specifies the type of failover that will be performed. - Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". + description: Mode specifies the type of failover that will be + performed. Valid values are "sequential", "" (equivalent to + "sequential") and "order-by-locality". type: string - regions: + regions: description: The ordered list of the regions of the failover targets. Valid values can be "us-west-1", "us-west-2", and so on. items: diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml new file mode 100644 index 0000000000..5efda1ffa2 --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -0,0 +1,121 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: samenessgroups.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: SamenessGroup + listKind: SamenessGroupList + plural: samenessgroups + shortNames: + - sameness-group + singular: samenessgroup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: SamenessGroup is the Schema for the samenessgroups API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SamenessGroupSpec defines the desired state of SamenessGroup. + properties: + defaultForFailover: + description: 'DefaultForFailover indicates that upstream requests to members of the given sameness group will implicitly failover between members of this sameness group.' + type: boolean + includeLocal: + description: 'IncludeLocal is used to include the local partition as the first member of the sameness group.' + type: boolean + members: + description: 'Members are the partitions and peers that are part of the sameness group.' + items: + properties: + partition: + type: string + peer: + type: string + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index a36784cc77..56b5e72014 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -76,13 +76,14 @@ spec: the current namespace is used. type: string policy: - description: FailoverPolicy specifies the exact mechanism used for failover. + description: Policy specifies the exact mechanism used for failover. properties: mode: - description: Mode specifies the type of failover that will be performed. - Valid values are "sequential", "" (equivalent to "sequential") and "order-by-locality". + description: Mode specifies the type of failover that will + be performed. Valid values are "sequential", "" (equivalent + to "sequential") and "order-by-locality". type: string - regions: + regions: description: The ordered list of the regions of the failover targets. Valid values can be "us-west-1", "us-west-2", and so on. items: diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 245f09568f..562eb5f9f9 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -146,6 +143,26 @@ rules: - get - patch - update +- apiGroups: + - consul.hashicorp.com + resources: + - samenessgroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - consul.hashicorp.com + resources: + - samenessgroups/status + verbs: + - get + - patch + - update - apiGroups: - consul.hashicorp.com resources: diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index d064b50acb..f96d669544 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration @@ -134,6 +131,27 @@ webhooks: resources: - proxydefaults sideEffects: None +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v1alpha1-samenessgroup + failurePolicy: Fail + name: mutate-samenessgroup.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - samenessgroups + sideEffects: None - admissionReviewVersions: - v1beta1 - v1 diff --git a/control-plane/controller/configentry_controller.go b/control-plane/controllers/configentry_controller.go similarity index 99% rename from control-plane/controller/configentry_controller.go rename to control-plane/controllers/configentry_controller.go index 593fb1514f..c2c6da9071 100644 --- a/control-plane/controller/configentry_controller.go +++ b/control-plane/controllers/configentry_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/configentry_controller_ent_test.go b/control-plane/controllers/configentry_controller_ent_test.go similarity index 64% rename from control-plane/controller/configentry_controller_ent_test.go rename to control-plane/controllers/configentry_controller_ent_test.go index cfe9985e56..ef2aa6b7a4 100644 --- a/control-plane/controller/configentry_controller_ent_test.go +++ b/control-plane/controllers/configentry_controller_ent_test.go @@ -3,7 +3,7 @@ //go:build enterprise -package controller_test +package controllers import ( "context" @@ -15,7 +15,7 @@ import ( logrtest "github.com/go-logr/logr/testing" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/controller" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" @@ -29,11 +29,323 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -// NOTE: We're not testing each controller type here because that's done in +// NOTE: We're not testing each controller type here because that's mostly done in // the OSS tests and it would result in too many permutations. Instead -// we're only testing with the ServiceDefaults and ProxyDefaults controller which will exercise -// all the namespaces code for config entries that are namespaced and those that +// we're only testing with the ServiceDefaults and ProxyDefaults controllers which +// will exercise all the namespaces code for config entries that are namespaced and those that // exist in the global namespace. +// We also test Enterprise only features like SamenessGroups. + +func TestConfigEntryController_createsEntConfigEntry(t *testing.T) { + t.Parallel() + kubeNS := "default" + + cases := []struct { + kubeKind string + consulKind string + consulPrereqs []capi.ConfigEntry + configEntryResource common.ConfigEntryResource + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler + compare func(t *testing.T, consul capi.ConfigEntry) + }{ + { + kubeKind: "SamenessGroup", + consulKind: capi.SamenessGroup, + configEntryResource: &v1alpha1.SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: v1alpha1.SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []v1alpha1.SamenessGroupMember{ + { + Peer: "dc1", + Partition: "", + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &SamenessGroupController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + resource, ok := consulEntry.(*capi.SamenessGroupConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, true, resource.DefaultForFailover) + require.Equal(t, true, resource.IncludeLocal) + require.Equal(t, "dc1", resource.Members[0].Peer) + require.Equal(t, "", resource.Members[0].Partition) + }, + }, + } + + for _, c := range cases { + t.Run(c.kubeKind, func(t *testing.T) { + req := require.New(t) + ctx := context.Background() + + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResource) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResource).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + testClient.TestServer.WaitForServiceIntentions(t) + consulClient := testClient.APIClient + + for _, configEntry := range c.consulPrereqs { + written, _, err := consulClient.ConfigEntries().Set(configEntry, nil) + req.NoError(err) + req.True(written) + } + + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + namespacedName := types.NamespacedName{ + Namespace: kubeNS, + Name: c.configEntryResource.KubernetesName(), + } + resp, err := r.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + req.NoError(err) + req.False(resp.Requeue) + + cfg, _, err := consulClient.ConfigEntries().Get(c.consulKind, c.configEntryResource.ConsulName(), nil) + req.NoError(err) + req.Equal(c.configEntryResource.ConsulName(), cfg.GetName()) + c.compare(t, cfg) + + // Check that the status is "synced". + err = fakeClient.Get(ctx, namespacedName, c.configEntryResource) + req.NoError(err) + req.Equal(corev1.ConditionTrue, c.configEntryResource.SyncedConditionStatus()) + + // Check that the finalizer is added. + req.Contains(c.configEntryResource.Finalizers(), FinalizerName) + }) + } +} + +func TestConfigEntryController_updatesEntConfigEntry(t *testing.T) { + t.Parallel() + kubeNS := "default" + + cases := []struct { + kubeKind string + consulKind string + consulPrereqs []capi.ConfigEntry + configEntryResource common.ConfigEntryResource + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler + updateF func(common.ConfigEntryResource) + compare func(t *testing.T, consul capi.ConfigEntry) + }{ + { + kubeKind: "SamenessGroup", + consulKind: capi.SamenessGroup, + configEntryResource: &v1alpha1.SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: v1alpha1.SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []v1alpha1.SamenessGroupMember{ + { + Peer: "dc1", + Partition: "", + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &SamenessGroupController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + updateF: func(resource common.ConfigEntryResource) { + sg := resource.(*v1alpha1.SamenessGroup) + sg.Spec.IncludeLocal = false + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + resource, ok := consulEntry.(*capi.SamenessGroupConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, true, resource.DefaultForFailover) + require.Equal(t, false, resource.IncludeLocal) + require.Equal(t, "dc1", resource.Members[0].Peer) + require.Equal(t, "", resource.Members[0].Partition) + }, + }, + } + + for _, c := range cases { + t.Run(c.kubeKind, func(t *testing.T) { + req := require.New(t) + ctx := context.Background() + + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResource) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResource).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + testClient.TestServer.WaitForServiceIntentions(t) + consulClient := testClient.APIClient + + // Create any prereqs. + for _, configEntry := range c.consulPrereqs { + written, _, err := consulClient.ConfigEntries().Set(configEntry, nil) + req.NoError(err) + req.True(written) + } + + // We haven't run reconcile yet so we must create the config entry + // in Consul ourselves. + { + written, _, err := consulClient.ConfigEntries().Set(c.configEntryResource.ToConsul(datacenterName), nil) + req.NoError(err) + req.True(written) + } + + // Now run reconcile which should update the entry in Consul. + { + namespacedName := types.NamespacedName{ + Namespace: kubeNS, + Name: c.configEntryResource.KubernetesName(), + } + // First get it so we have the latest revision number. + err := fakeClient.Get(ctx, namespacedName, c.configEntryResource) + req.NoError(err) + + // Update the entry in Kube and run reconcile. + c.updateF(c.configEntryResource) + err = fakeClient.Update(ctx, c.configEntryResource) + req.NoError(err) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + resp, err := r.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + req.NoError(err) + req.False(resp.Requeue) + + // Now check that the object in Consul is as expected. + cfg, _, err := consulClient.ConfigEntries().Get(c.consulKind, c.configEntryResource.ConsulName(), nil) + req.NoError(err) + req.Equal(c.configEntryResource.ConsulName(), cfg.GetName()) + c.compare(t, cfg) + } + }) + } +} + +func TestConfigEntryController_deletesEntConfigEntry(t *testing.T) { + t.Parallel() + kubeNS := "default" + + cases := []struct { + kubeKind string + consulKind string + consulPrereq []capi.ConfigEntry + configEntryResourceWithDeletion common.ConfigEntryResource + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler + }{ + { + kubeKind: "SamenessGroup", + consulKind: capi.SamenessGroup, + configEntryResourceWithDeletion: &v1alpha1.SamenessGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + Finalizers: []string{FinalizerName}, + }, + Spec: v1alpha1.SamenessGroupSpec{ + DefaultForFailover: true, + IncludeLocal: true, + Members: []v1alpha1.SamenessGroupMember{ + { + Peer: "dc1", + Partition: "", + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &SamenessGroupController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + }, + } + + for _, c := range cases { + t.Run(c.kubeKind, func(t *testing.T) { + req := require.New(t) + + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResourceWithDeletion) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResourceWithDeletion).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + testClient.TestServer.WaitForServiceIntentions(t) + consulClient := testClient.APIClient + + // Create any prereqs. + for _, configEntry := range c.consulPrereq { + written, _, err := consulClient.ConfigEntries().Set(configEntry, nil) + req.NoError(err) + req.True(written) + } + + // We haven't run reconcile yet so we must create the config entry + // in Consul ourselves. + { + written, _, err := consulClient.ConfigEntries().Set(c.configEntryResourceWithDeletion.ToConsul(datacenterName), nil) + req.NoError(err) + req.True(written) + } + + // Now run reconcile. It's marked for deletion so this should delete it. + { + namespacedName := types.NamespacedName{ + Namespace: kubeNS, + Name: c.configEntryResourceWithDeletion.KubernetesName(), + } + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + resp, err := r.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: namespacedName, + }) + req.NoError(err) + req.False(resp.Requeue) + + _, _, err = consulClient.ConfigEntries().Get(c.consulKind, c.configEntryResourceWithDeletion.ConsulName(), nil) + req.EqualError(err, + fmt.Sprintf("Unexpected response code: 404 (Config entry not found for %q / %q)", + c.consulKind, c.configEntryResourceWithDeletion.ConsulName())) + } + }) + } +} func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T) { tt.Parallel() @@ -88,7 +400,7 @@ func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T ConsulKind string ConsulNamespace string KubeResource common.ConfigEntryResource - GetController func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler + GetController func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler AssertValidConfig func(entry capi.ConfigEntry) bool }{ "namespaced": { @@ -102,8 +414,8 @@ func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T Protocol: "http", }, }, - GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ServiceDefaultsController{ + GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ServiceDefaultsController{ Client: client, Log: logger, Scheme: scheme, @@ -132,8 +444,8 @@ func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T }, }, }, - GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ProxyDefaultsController{ + GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ProxyDefaultsController{ Client: client, Log: logger, Scheme: scheme, @@ -170,8 +482,8 @@ func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T }, }, }, - GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ServiceIntentionsController{ + GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ServiceIntentionsController{ Client: client, Log: logger, Scheme: scheme, @@ -206,7 +518,7 @@ func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T fakeClient, logrtest.TestLogger{T: t}, s, - &controller.ConfigEntryController{ + &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, @@ -299,7 +611,7 @@ func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T ConsulKind string ConsulNamespace string KubeResource common.ConfigEntryResource - GetControllerFunc func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler + GetControllerFunc func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler AssertValidConfigFunc func(entry capi.ConfigEntry) bool WriteConfigEntryFunc func(consulClient *capi.Client, namespace string) error UpdateResourceFunc func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error @@ -310,15 +622,15 @@ func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: c.SourceKubeNS, - Finalizers: []string{controller.FinalizerName}, + Finalizers: []string{FinalizerName}, }, Spec: v1alpha1.ServiceDefaultsSpec{ Protocol: "http", }, }, ConsulNamespace: c.ExpConsulNS, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ServiceDefaultsController{ + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ServiceDefaultsController{ Client: client, Log: logger, Scheme: scheme, @@ -352,7 +664,7 @@ func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T ObjectMeta: metav1.ObjectMeta{ Name: common.Global, Namespace: c.SourceKubeNS, - Finalizers: []string{controller.FinalizerName}, + Finalizers: []string{FinalizerName}, }, Spec: v1alpha1.ProxyDefaultsSpec{ MeshGateway: v1alpha1.MeshGateway{ @@ -361,8 +673,8 @@ func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T }, }, ConsulNamespace: common.DefaultConsulNamespace, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ProxyDefaultsController{ + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ProxyDefaultsController{ Client: client, Log: logger, Scheme: scheme, @@ -398,7 +710,7 @@ func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: c.SourceKubeNS, - Finalizers: []string{controller.FinalizerName}, + Finalizers: []string{FinalizerName}, }, Spec: v1alpha1.ServiceIntentionsSpec{ Destination: v1alpha1.IntentionDestination{ @@ -415,8 +727,8 @@ func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T }, }, ConsulNamespace: c.ExpConsulNS, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ServiceIntentionsController{ + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ServiceIntentionsController{ Client: client, Log: logger, Scheme: scheme, @@ -468,7 +780,7 @@ func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T fakeClient, logrtest.TestLogger{T: t}, s, - &controller.ConfigEntryController{ + &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, @@ -577,7 +889,7 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T ConsulKind string ConsulNamespace string KubeResource common.ConfigEntryResource - GetControllerFunc func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler + GetControllerFunc func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler WriteConfigEntryFunc func(consulClient *capi.Client, namespace string) error }{ "namespaced": { @@ -588,7 +900,7 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: c.SourceKubeNS, - Finalizers: []string{controller.FinalizerName}, + Finalizers: []string{FinalizerName}, DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: v1alpha1.ServiceDefaultsSpec{ @@ -596,8 +908,8 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T }, }, ConsulNamespace: c.ExpConsulNS, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ServiceDefaultsController{ + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ServiceDefaultsController{ Client: client, Log: logger, Scheme: scheme, @@ -621,7 +933,7 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T ObjectMeta: metav1.ObjectMeta{ Name: common.Global, Namespace: c.SourceKubeNS, - Finalizers: []string{controller.FinalizerName}, + Finalizers: []string{FinalizerName}, DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: v1alpha1.ProxyDefaultsSpec{ @@ -631,8 +943,8 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T }, }, ConsulNamespace: common.DefaultConsulNamespace, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ProxyDefaultsController{ + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ProxyDefaultsController{ Client: client, Log: logger, Scheme: scheme, @@ -658,7 +970,7 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: c.SourceKubeNS, - Finalizers: []string{controller.FinalizerName}, + Finalizers: []string{FinalizerName}, DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: v1alpha1.ServiceIntentionsSpec{ @@ -676,8 +988,8 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T }, }, ConsulNamespace: c.ExpConsulNS, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { - return &controller.ServiceIntentionsController{ + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { + return &ServiceIntentionsController{ Client: client, Log: logger, Scheme: scheme, @@ -717,7 +1029,7 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T fakeClient, logrtest.TestLogger{T: t}, s, - &controller.ConfigEntryController{ + &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, diff --git a/control-plane/controller/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go similarity index 99% rename from control-plane/controller/configentry_controller_test.go rename to control-plane/controllers/configentry_controller_test.go index 47f28f02d1..494715cf4f 100644 --- a/control-plane/controller/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/exportedservices_controller.go b/control-plane/controllers/exportedservices_controller.go similarity index 98% rename from control-plane/controller/exportedservices_controller.go rename to control-plane/controllers/exportedservices_controller.go index 90d7261d9a..e72b743a1f 100644 --- a/control-plane/controller/exportedservices_controller.go +++ b/control-plane/controllers/exportedservices_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/exportedservices_controller_ent_test.go b/control-plane/controllers/exportedservices_controller_ent_test.go similarity index 95% rename from control-plane/controller/exportedservices_controller_ent_test.go rename to control-plane/controllers/exportedservices_controller_ent_test.go index aba193bdd9..40ab5d1e1e 100644 --- a/control-plane/controller/exportedservices_controller_ent_test.go +++ b/control-plane/controllers/exportedservices_controller_ent_test.go @@ -3,7 +3,7 @@ //go:build enterprise -package controller_test +package controllers_test import ( "context" @@ -14,7 +14,7 @@ import ( logrtest "github.com/go-logr/logr/testing" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/controller" + "github.com/hashicorp/consul-k8s/control-plane/controllers" "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" @@ -103,11 +103,11 @@ func TestExportedServicesController_createsExportedServices(tt *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &controller.ExportedServicesController{ + controller := &controllers.ExportedServicesController{ Client: fakeClient, Log: logrtest.TestLogger{T: t}, Scheme: s, - ConfigEntryController: &controller.ConfigEntryController{ + ConfigEntryController: &controllers.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, @@ -195,7 +195,7 @@ func TestExportedServicesController_updatesExportedServices(tt *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "default", Namespace: c.SourceKubeNS, - Finalizers: []string{controller.FinalizerName}, + Finalizers: []string{controllers.FinalizerName}, }, Spec: v1alpha1.ExportedServicesSpec{ Services: []v1alpha1.ExportedService{ @@ -218,11 +218,11 @@ func TestExportedServicesController_updatesExportedServices(tt *testing.T) { consulClient := testClient.APIClient fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &controller.ExportedServicesController{ + controller := &controllers.ExportedServicesController{ Client: fakeClient, Log: logrtest.TestLogger{T: t}, Scheme: s, - ConfigEntryController: &controller.ConfigEntryController{ + ConfigEntryController: &controllers.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, @@ -332,7 +332,7 @@ func TestExportedServicesController_deletesExportedServices(tt *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "default", Namespace: c.SourceKubeNS, - Finalizers: []string{controller.FinalizerName}, + Finalizers: []string{controllers.FinalizerName}, DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: v1alpha1.ExportedServicesSpec{ @@ -356,11 +356,11 @@ func TestExportedServicesController_deletesExportedServices(tt *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &controller.ExportedServicesController{ + controller := &controllers.ExportedServicesController{ Client: fakeClient, Log: logrtest.TestLogger{T: t}, Scheme: s, - ConfigEntryController: &controller.ConfigEntryController{ + ConfigEntryController: &controllers.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, diff --git a/control-plane/controller/ingressgateway_controller.go b/control-plane/controllers/ingressgateway_controller.go similarity index 98% rename from control-plane/controller/ingressgateway_controller.go rename to control-plane/controllers/ingressgateway_controller.go index fffc3c5a06..faa728dd4f 100644 --- a/control-plane/controller/ingressgateway_controller.go +++ b/control-plane/controllers/ingressgateway_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/mesh_controller.go b/control-plane/controllers/mesh_controller.go similarity index 98% rename from control-plane/controller/mesh_controller.go rename to control-plane/controllers/mesh_controller.go index 9f7d8cd7c8..92839d0104 100644 --- a/control-plane/controller/mesh_controller.go +++ b/control-plane/controllers/mesh_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/proxydefaults_controller.go b/control-plane/controllers/proxydefaults_controller.go similarity index 98% rename from control-plane/controller/proxydefaults_controller.go rename to control-plane/controllers/proxydefaults_controller.go index 7499928ea4..1415da8688 100644 --- a/control-plane/controller/proxydefaults_controller.go +++ b/control-plane/controllers/proxydefaults_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controllers/samenessgroups_controller.go b/control-plane/controllers/samenessgroups_controller.go new file mode 100644 index 0000000000..afc243f267 --- /dev/null +++ b/control-plane/controllers/samenessgroups_controller.go @@ -0,0 +1,41 @@ +package controllers + +import ( + "context" + "k8s.io/apimachinery/pkg/types" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +// SamenessGroupController reconciles a SamenessGroups object. +type SamenessGroupController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + ConfigEntryController *ConfigEntryController +} + +//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=samenessgroups,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=samenessgroups/status,verbs=get;update;patch + +func (r *SamenessGroupController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.ConfigEntryController.ReconcileEntry(ctx, r, req, &consulv1alpha1.SamenessGroup{}) +} + +func (r *SamenessGroupController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *SamenessGroupController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *SamenessGroupController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &consulv1alpha1.SamenessGroup{}, r) +} diff --git a/control-plane/controller/servicedefaults_controller.go b/control-plane/controllers/servicedefaults_controller.go similarity index 98% rename from control-plane/controller/servicedefaults_controller.go rename to control-plane/controllers/servicedefaults_controller.go index b96ff7e566..9c2dbe683d 100644 --- a/control-plane/controller/servicedefaults_controller.go +++ b/control-plane/controllers/servicedefaults_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/serviceintentions_controller.go b/control-plane/controllers/serviceintentions_controller.go similarity index 98% rename from control-plane/controller/serviceintentions_controller.go rename to control-plane/controllers/serviceintentions_controller.go index 43298a23f7..30dcb63f81 100644 --- a/control-plane/controller/serviceintentions_controller.go +++ b/control-plane/controllers/serviceintentions_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/serviceresolver_controller.go b/control-plane/controllers/serviceresolver_controller.go similarity index 98% rename from control-plane/controller/serviceresolver_controller.go rename to control-plane/controllers/serviceresolver_controller.go index cca014ab50..f82c4d42a2 100644 --- a/control-plane/controller/serviceresolver_controller.go +++ b/control-plane/controllers/serviceresolver_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/servicerouter_controller.go b/control-plane/controllers/servicerouter_controller.go similarity index 98% rename from control-plane/controller/servicerouter_controller.go rename to control-plane/controllers/servicerouter_controller.go index 6ed1e52fad..831179eee9 100644 --- a/control-plane/controller/servicerouter_controller.go +++ b/control-plane/controllers/servicerouter_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/servicesplitter_controller.go b/control-plane/controllers/servicesplitter_controller.go similarity index 98% rename from control-plane/controller/servicesplitter_controller.go rename to control-plane/controllers/servicesplitter_controller.go index dc5e2a8dd3..1dd89dc278 100644 --- a/control-plane/controller/servicesplitter_controller.go +++ b/control-plane/controllers/servicesplitter_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/controller/terminatinggateway_controller.go b/control-plane/controllers/terminatinggateway_controller.go similarity index 98% rename from control-plane/controller/terminatinggateway_controller.go rename to control-plane/controllers/terminatinggateway_controller.go index 10af041c39..9550a2d04f 100644 --- a/control-plane/controller/terminatinggateway_controller.go +++ b/control-plane/controllers/terminatinggateway_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controller +package controllers import ( "context" diff --git a/control-plane/go.mod b/control-plane/go.mod index b139e1cd3f..23900deb46 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -25,6 +25,8 @@ require ( github.com/mitchellh/cli v1.1.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 + github.com/onsi/ginkgo v1.16.4 + github.com/onsi/gomega v1.17.0 github.com/stretchr/testify v1.7.2 go.uber.org/zap v1.19.0 golang.org/x/text v0.3.8 @@ -108,6 +110,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect + github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.0.0 // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect @@ -144,6 +147,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.22.2 // indirect diff --git a/control-plane/main.go b/control-plane/main.go index a4ccc9630c..64ccd5d43a 100644 --- a/control-plane/main.go +++ b/control-plane/main.go @@ -7,8 +7,9 @@ import ( "log" "os" - "github.com/hashicorp/consul-k8s/control-plane/version" "github.com/mitchellh/cli" + + "github.com/hashicorp/consul-k8s/control-plane/version" ) func main() { diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index e570832ee9..05937dfe90 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -21,7 +21,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - "github.com/hashicorp/consul-k8s/control-plane/controller" + "github.com/hashicorp/consul-k8s/control-plane/controllers" mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" @@ -456,7 +456,7 @@ func (c *Command) Run(args []string) int { Prefix: c.flagK8SNSMirroringPrefix, } - configEntryReconciler := &controller.ConfigEntryController{ + configEntryReconciler := &controllers.ConfigEntryController{ ConsulClientConfig: c.consul.ConsulClientConfig(), ConsulServerConnMgr: watcher, DatacenterName: c.consul.Datacenter, @@ -466,7 +466,7 @@ func (c *Command) Run(args []string) int { NSMirroringPrefix: c.flagK8SNSMirroringPrefix, CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, } - if err = (&controller.ServiceDefaultsController{ + if err = (&controllers.ServiceDefaultsController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceDefaults), @@ -475,7 +475,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceDefaults) return 1 } - if err = (&controller.ServiceResolverController{ + if err = (&controllers.ServiceResolverController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceResolver), @@ -484,7 +484,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceResolver) return 1 } - if err = (&controller.ProxyDefaultsController{ + if err = (&controllers.ProxyDefaultsController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.ProxyDefaults), @@ -493,7 +493,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.ProxyDefaults) return 1 } - if err = (&controller.MeshController{ + if err = (&controllers.MeshController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.Mesh), @@ -502,7 +502,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.Mesh) return 1 } - if err = (&controller.ExportedServicesController{ + if err = (&controllers.ExportedServicesController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.ExportedServices), @@ -511,7 +511,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.ExportedServices) return 1 } - if err = (&controller.ServiceRouterController{ + if err = (&controllers.ServiceRouterController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceRouter), @@ -520,7 +520,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceRouter) return 1 } - if err = (&controller.ServiceSplitterController{ + if err = (&controllers.ServiceSplitterController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceSplitter), @@ -529,7 +529,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceSplitter) return 1 } - if err = (&controller.ServiceIntentionsController{ + if err = (&controllers.ServiceIntentionsController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceIntentions), @@ -538,7 +538,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceIntentions) return 1 } - if err = (&controller.IngressGatewayController{ + if err = (&controllers.IngressGatewayController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.IngressGateway), @@ -547,7 +547,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.IngressGateway) return 1 } - if err = (&controller.TerminatingGatewayController{ + if err = (&controllers.TerminatingGatewayController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(apicommon.TerminatingGateway), @@ -556,6 +556,15 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.TerminatingGateway) return 1 } + if err = (&controllers.SamenessGroupController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.SamenessGroup), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.SamenessGroup) + return 1 + } if err = mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) @@ -706,6 +715,12 @@ func (c *Command) Run(args []string) int { Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), ConsulMeta: consulMeta, }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-samenessgroup", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.SamenessGroupWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.SamenessGroup), + ConsulMeta: consulMeta, + }}) if c.flagEnableWebhookCAUpdate { err = c.updateWebhookCABundle(ctx) From 7145b025b8e83fca69c44d4cc77e1ad374bf5a45 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 20 Apr 2023 14:29:23 -0400 Subject: [PATCH 139/592] Supply chain updates (#2072) --- .github/workflows/build.yml | 34 +++++++++---------- .github/workflows/changelog-checker.yml | 2 +- .github/workflows/jira-issues.yaml | 12 +++---- .github/workflows/merge.yml | 2 +- .github/workflows/nightly-acceptance.yml | 2 +- .github/workflows/nightly-cleanup.yml | 2 +- .github/workflows/pr.yml | 2 +- .../workflows/weekly-acceptance-0-49-x.yml | 2 +- .github/workflows/weekly-acceptance-1-0-x.yml | 2 +- .github/workflows/weekly-acceptance-1-1-x.yml | 2 +- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index de0d18d648..a00629bde2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: outputs: go-version: ${{ steps.get-go-version.outputs.go-version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Determine Go version id: get-go-version # We use .go-version as our source of truth for current Go @@ -35,7 +35,7 @@ jobs: outputs: product-version: ${{ steps.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: get product version id: get-product-version run: | @@ -49,7 +49,7 @@ jobs: filepath: ${{ steps.generate-metadata-file.outputs.filepath }} steps: - name: "Checkout directory" - uses: actions/checkout@v3 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Generate metadata file id: generate-metadata-file uses: hashicorp/actions-generate-metadata@v1 @@ -57,7 +57,7 @@ jobs: version: ${{ needs.get-product-version.outputs.product-version }} product: ${{ env.PKG_NAME }} repositoryOwner: "hashicorp" - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: name: metadata.json path: ${{ steps.generate-metadata-file.outputs.filepath }} @@ -109,10 +109,10 @@ jobs: name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} build steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Setup go - uses: actions/setup-go@v3 + uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 with: go-version: ${{ matrix.go }} @@ -134,7 +134,7 @@ jobs: zip -r -j out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ - name: Upload built binaries - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: name: ${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip path: ${{ matrix.component}}/out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip @@ -162,7 +162,7 @@ jobs: - name: Test rpm package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: registry.access.redhat.com/ubi8/ubi:latest options: -v ${{ github.workspace }}:/work @@ -179,7 +179,7 @@ jobs: echo "Test PASSED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" - name: Upload rpm package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} with: name: ${{ env.RPM_PACKAGE }} @@ -187,7 +187,7 @@ jobs: - name: Test debian package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: ubuntu:latest options: -v ${{ github.workspace }}:/work @@ -204,7 +204,7 @@ jobs: echo "Test PASSED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" - name: Upload debian packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} with: name: ${{ env.DEB_PACKAGE }} @@ -221,8 +221,8 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip path: control-plane/dist/cni/linux/${{ matrix.arch }} @@ -265,8 +265,8 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip path: control-plane/dist/cni/linux/${{ matrix.arch }} @@ -307,8 +307,8 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip path: control-plane/dist/cni/linux/${{ matrix.arch }} diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 3595781825..1c41634fd3 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 # by default the checkout action doesn't checkout all branches diff --git a/.github/workflows/jira-issues.yaml b/.github/workflows/jira-issues.yaml index dc743e9328..bddc69c83f 100644 --- a/.github/workflows/jira-issues.yaml +++ b/.github/workflows/jira-issues.yaml @@ -15,7 +15,7 @@ jobs: name: Jira Community Issue sync steps: - name: Login - uses: atlassian/gajira-login@v3.0.0 + uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -38,7 +38,7 @@ jobs: - name: Create ticket if an issue is filed, or if PR not by a team member is opened if: github.event.action == 'opened' - uses: tomhjp/gh-action-jira-create@v0.2.0 + uses: tomhjp/gh-action-jira-create@3ed1789cad3521292e591a7cfa703215ec1348bf # v0.2.1 with: project: NET issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" @@ -58,28 +58,28 @@ jobs: - name: Search if: github.event.action != 'opened' id: search - uses: tomhjp/gh-action-jira-search@v0.2.1 + uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2 with: # cf[10089] is Issue Link (use JIRA API to retrieve) jql: 'issuetype = "${{ steps.set-ticket-type.outputs.TYPE }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' - name: Sync comment if: github.event.action == 'created' && steps.search.outputs.issue - uses: tomhjp/gh-action-jira-comment@v0.1.0 + uses: tomhjp/gh-action-jira-comment@6eb6b9ead70221916b6badd118c24535ed220bd9 # v0.2.0 with: issue: ${{ steps.search.outputs.issue }} comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}" - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@v2.0.1 + uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@v2.0.1 + uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index be1b392f4a..b6037e0af3 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -21,7 +21,7 @@ jobs: name: test runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@v1.2.2 + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 name: test with: workflow: test.yml diff --git a/.github/workflows/nightly-acceptance.yml b/.github/workflows/nightly-acceptance.yml index b8b7f50798..6414d6a611 100644 --- a/.github/workflows/nightly-acceptance.yml +++ b/.github/workflows/nightly-acceptance.yml @@ -17,7 +17,7 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@v1.2.2 + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 name: cloud with: workflow: cloud.yml diff --git a/.github/workflows/nightly-cleanup.yml b/.github/workflows/nightly-cleanup.yml index 79360e497d..4a304549df 100644 --- a/.github/workflows/nightly-cleanup.yml +++ b/.github/workflows/nightly-cleanup.yml @@ -17,7 +17,7 @@ jobs: name: cleanup runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@v1.2.2 + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 name: cleanup with: workflow: cleanup.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 32baf472fb..b4b431693a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -15,7 +15,7 @@ jobs: name: test runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@v1.2.2 + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 name: test with: workflow: test.yml diff --git a/.github/workflows/weekly-acceptance-0-49-x.yml b/.github/workflows/weekly-acceptance-0-49-x.yml index 7025bcb241..adba13846a 100644 --- a/.github/workflows/weekly-acceptance-0-49-x.yml +++ b/.github/workflows/weekly-acceptance-0-49-x.yml @@ -19,7 +19,7 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@v1.2.2 + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 name: cloud with: workflow: cloud.yml diff --git a/.github/workflows/weekly-acceptance-1-0-x.yml b/.github/workflows/weekly-acceptance-1-0-x.yml index 4aa49594f3..72769f0ca1 100644 --- a/.github/workflows/weekly-acceptance-1-0-x.yml +++ b/.github/workflows/weekly-acceptance-1-0-x.yml @@ -20,7 +20,7 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@v1.2.2 + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 name: cloud with: workflow: cloud.yml diff --git a/.github/workflows/weekly-acceptance-1-1-x.yml b/.github/workflows/weekly-acceptance-1-1-x.yml index 1ffc6f8684..b77da7eff0 100644 --- a/.github/workflows/weekly-acceptance-1-1-x.yml +++ b/.github/workflows/weekly-acceptance-1-1-x.yml @@ -20,7 +20,7 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@v1.2.2 + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 name: cloud with: workflow: cloud.yml From e0df67985209482af3552ab25a63238753a71f21 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Fri, 21 Apr 2023 13:42:35 -0500 Subject: [PATCH 140/592] Fix Sync Catalog ACL Token Environment Var Name (#2068) * Fix Sync Catalog ACL Token Environment Var Name * Update ACL variable name in tests --- charts/consul/templates/sync-catalog-deployment.yaml | 2 +- charts/consul/test/unit/sync-catalog-deployment.bats | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index f2815d9627..ec1abbfc84 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -100,7 +100,7 @@ spec: fieldRef: fieldPath: metadata.namespace {{- if (and .Values.syncCatalog.aclSyncToken.secretName .Values.syncCatalog.aclSyncToken.secretKey) }} - - name: CONSUL_HTTP_TOKEN + - name: CONSUL_ACL_TOKEN valueFrom: secretKeyRef: name: {{ .Values.syncCatalog.aclSyncToken.secretName }} diff --git a/charts/consul/test/unit/sync-catalog-deployment.bats b/charts/consul/test/unit/sync-catalog-deployment.bats index ae1fe1a854..0b397a55d0 100755 --- a/charts/consul/test/unit/sync-catalog-deployment.bats +++ b/charts/consul/test/unit/sync-catalog-deployment.bats @@ -338,7 +338,7 @@ load _helpers --set 'syncCatalog.enabled=true' \ --set 'syncCatalog.aclSyncToken.secretKey=bar' \ . | tee /dev/stderr | - yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_HTTP_TOKEN"))' | tee /dev/stderr) + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) [ "${actual}" = "false" ] } @@ -349,7 +349,7 @@ load _helpers --set 'syncCatalog.enabled=true' \ --set 'syncCatalog.aclSyncToken.secretName=foo' \ . | tee /dev/stderr | - yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_HTTP_TOKEN"))' | tee /dev/stderr) + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) [ "${actual}" = "false" ] } @@ -361,7 +361,7 @@ load _helpers --set 'syncCatalog.aclSyncToken.secretName=foo' \ --set 'syncCatalog.aclSyncToken.secretKey=bar' \ . | tee /dev/stderr | - yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_HTTP_TOKEN"))' | tee /dev/stderr) + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) [ "${actual}" = "true" ] } From 568ab03f14fdba410e8b1306105ed001b2c2c3b7 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 24 Apr 2023 13:51:34 -0400 Subject: [PATCH 141/592] Add changelog for NET 2422 (#2080) --- .changelog/2068.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/2068.txt diff --git a/.changelog/2068.txt b/.changelog/2068.txt new file mode 100644 index 0000000000..bc17061f51 --- /dev/null +++ b/.changelog/2068.txt @@ -0,0 +1,3 @@ +```release-note:bug +sync-catalog: fix issue where the sync-catalog ACL token were set with an incorrect ENV VAR. +``` \ No newline at end of file From f8eb931d53b693311da3710c142079fbcdc7cd56 Mon Sep 17 00:00:00 2001 From: malizz Date: Mon, 24 Apr 2023 11:27:03 -0700 Subject: [PATCH 142/592] add sameness group to exported services (#2075) * add sameness group to exported services * update CRDs * update deep copy * re add license line * check if sameness group is wildcard * remove experimental tag on peering fields * update error message case * update error message case in webhook test --- .../templates/crd-exportedservices.yaml | 7 +- .../consul/templates/crd-proxydefaults.yaml | 5 +- .../consul/templates/crd-samenessgroups.yaml | 17 ++- .../templates/crd-serviceintentions.yaml | 3 +- .../templates/crd-serviceresolvers.yaml | 5 +- .../api/v1alpha1/exportedservices_types.go | 40 ++++-- .../v1alpha1/exportedservices_types_test.go | 119 +++++++++++++++++- .../v1alpha1/exportedservices_webhook_test.go | 2 +- .../api/v1alpha1/serviceintentions_types.go | 2 +- .../api/v1alpha1/zz_generated.deepcopy.go | 51 +++++--- ...consul.hashicorp.com_exportedservices.yaml | 7 +- .../consul.hashicorp.com_proxydefaults.yaml | 5 +- .../consul.hashicorp.com_samenessgroups.yaml | 17 ++- ...onsul.hashicorp.com_serviceintentions.yaml | 3 +- ...consul.hashicorp.com_serviceresolvers.yaml | 5 +- control-plane/config/webhook/manifests.yaml | 2 +- control-plane/go.mod | 6 +- control-plane/go.sum | 4 +- 18 files changed, 238 insertions(+), 62 deletions(-) diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 007990372c..073e081d0c 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -76,8 +76,11 @@ spec: the service to. type: string peer: - description: '[Experimental] Peer is the name of the peer - to export the service to.' + description: Peer is the name of the peer to export the service to. + type: string + samenessGroup: + description: SamenessGroup is the name of the sameness + group to export the service to. type: string type: object type: array diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 6bb1234066..f72d1c7cea 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -153,8 +153,9 @@ spec: "sequential") and "order-by-locality". type: string regions: - description: The ordered list of the regions of the failover targets. - Valid values can be "us-west-1", "us-west-2", and so on. + description: Regions is the ordered list of the regions of the + failover targets. Valid values can be "us-west-1", "us-west-2", + and so on. items: type: string type: array diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index e541539481..60beb5662c 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -58,16 +58,27 @@ spec: description: SamenessGroupSpec defines the desired state of SamenessGroup. properties: defaultForFailover: - description: 'DefaultForFailover indicates that upstream requests to members of the given sameness group will implicitly failover between members of this sameness group.' + description: DefaultForFailover indicates that upstream requests to + members of the given sameness group will implicitly failover between + members of this sameness group. When DefaultForFailover is true, + the local partition must be a member of the sameness group or IncludeLocal + must be set to true. type: boolean includeLocal: - description: 'IncludeLocal is used to include the local partition as the first member of the sameness group.' + description: IncludeLocal is used to include the local partition as + the first member of the sameness group. The local partition can + only be a member of a single sameness group. type: boolean members: - description: 'Members are the partitions and peers that are part of the sameness group.' + description: Members are the partitions and peers that are part of + the sameness group. If a member of a sameness group does not exist, + it will be ignored. items: properties: partition: + description: The partitions and peers that are part of the sameness + group. A sameness group member cannot define both peer and + partition at the same time. type: string peer: type: string diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index cdbb5413b0..ede8ae09b0 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -102,8 +102,7 @@ spec: description: Partition is the Admin Partition for the Name parameter. type: string peer: - description: '[Experimental] Peer is the peer name for the Name - parameter.' + description: Peer is the peer name for the Name parameter. type: string permissions: description: Permissions is the list of all additional L7 attributes diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 1942b79b8f..04d6dd9754 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -88,8 +88,9 @@ spec: to "sequential") and "order-by-locality". type: string regions: - description: The ordered list of the regions of the failover targets. - Valid values can be "us-west-1", "us-west-2", and so on. + description: Regions is the ordered list of the regions + of the failover targets. Valid values can be "us-west-1", + "us-west-2", and so on. items: type: string type: array diff --git a/control-plane/api/v1alpha1/exportedservices_types.go b/control-plane/api/v1alpha1/exportedservices_types.go index e4df1cee0d..06d6ce30cf 100644 --- a/control-plane/api/v1alpha1/exportedservices_types.go +++ b/control-plane/api/v1alpha1/exportedservices_types.go @@ -19,6 +19,7 @@ import ( ) const ExportedServicesKubeKind = "exportedservices" +const WildcardSpecifier = "*" func init() { SchemeBuilder.Register(&ExportedServices{}, &ExportedServicesList{}) @@ -71,8 +72,10 @@ type ExportedService struct { type ServiceConsumer struct { // Partition is the admin partition to export the service to. Partition string `json:"partition,omitempty"` - // [Experimental] Peer is the name of the peer to export the service to. + // Peer is the name of the peer to export the service to. Peer string `json:"peer,omitempty"` + // SamenessGroup is the name of the sameness group to export the service to. + SamenessGroup string `json:"samenessGroup,omitempty"` } func (in *ExportedServices) GetObjectMeta() metav1.ObjectMeta { @@ -169,8 +172,9 @@ func (in *ExportedService) toConsul() capi.ExportedService { var consumers []capi.ServiceConsumer for _, consumer := range in.Consumers { consumers = append(consumers, capi.ServiceConsumer{ - Partition: consumer.Partition, - Peer: consumer.Peer, + Partition: consumer.Partition, + Peer: consumer.Peer, + SamenessGroup: consumer.SamenessGroup, }) } return capi.ExportedService{ @@ -230,14 +234,34 @@ func (in *ExportedService) validate(path *field.Path, consulMeta common.ConsulMe } func (in *ServiceConsumer) validate(path *field.Path, consulMeta common.ConsulMeta) *field.Error { - if in.Partition != "" && in.Peer != "" { - return field.Invalid(path, *in, "both partition and peer cannot be specified.") + count := 0 + + if in.Partition != "" { + count++ + } + if in.Peer != "" { + count++ + } + if in.SamenessGroup != "" { + count++ + } + if count > 1 { + return field.Invalid(path, *in, "service consumer must define at most one of Peer, Partition, or SamenessGroup") } - if in.Partition == "" && in.Peer == "" { - return field.Invalid(path, *in, "either partition or peer must be specified.") + if count == 0 { + return field.Invalid(path, *in, "service consumer must define at least one of Peer, Partition, or SamenessGroup") } if !consulMeta.PartitionsEnabled && in.Partition != "" { - return field.Invalid(path.Child("partitions"), in.Partition, "Consul Admin Partitions need to be enabled to specify partition.") + return field.Invalid(path.Child("partition"), in.Partition, "Consul Admin Partitions need to be enabled to specify partition.") + } + if in.Partition == WildcardSpecifier { + return field.Invalid(path.Child("partition"), "", "exporting to all partitions (wildcard) is not supported") + } + if in.Peer == WildcardSpecifier { + return field.Invalid(path.Child("peer"), "", "exporting to all peers (wildcard) is not supported") + } + if in.SamenessGroup == WildcardSpecifier { + return field.Invalid(path.Child("samenessgroup"), "", "exporting to all sameness groups (wildcard) is not supported") } return nil } diff --git a/control-plane/api/v1alpha1/exportedservices_types_test.go b/control-plane/api/v1alpha1/exportedservices_types_test.go index e94eb36cbe..d759a6f270 100644 --- a/control-plane/api/v1alpha1/exportedservices_types_test.go +++ b/control-plane/api/v1alpha1/exportedservices_types_test.go @@ -59,6 +59,9 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "second-peer", }, + { + SamenessGroup: "sg1", + }, }, }, { @@ -74,6 +77,9 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "third-peer", }, + { + SamenessGroup: "sg2", + }, }, }, }, @@ -95,6 +101,9 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "second-peer", }, + { + SamenessGroup: "sg1", + }, }, }, { @@ -110,6 +119,9 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "third-peer", }, + { + SamenessGroup: "sg2", + }, }, }, }, @@ -183,6 +195,9 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "second-peer", }, + { + SamenessGroup: "sg2", + }, }, }, { @@ -198,6 +213,9 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "third-peer", }, + { + SamenessGroup: "sg3", + }, }, }, }, @@ -219,6 +237,9 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "second-peer", }, + { + SamenessGroup: "sg2", + }, }, }, { @@ -234,6 +255,9 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "third-peer", }, + { + SamenessGroup: "sg3", + }, }, }, }, @@ -278,6 +302,9 @@ func TestExportedServices_Validate(t *testing.T) { { Peer: "second-peer", }, + { + SamenessGroup: "sg2", + }, }, }, }, @@ -331,10 +358,10 @@ func TestExportedServices_Validate(t *testing.T) { namespaceEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `spec.services[0].consumers[0]: Invalid value: v1alpha1.ServiceConsumer{Partition:"second", Peer:"second-peer"}: both partition and peer cannot be specified.`, + `service consumer must define at most one of Peer, Partition, or SamenessGroup`, }, }, - "neither partition nor peer name specified": { + "none of peer, partition, or sameness group defined": { input: &ExportedServices{ ObjectMeta: metav1.ObjectMeta{ Name: common.DefaultConsulPartition, @@ -354,7 +381,7 @@ func TestExportedServices_Validate(t *testing.T) { namespaceEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `spec.services[0].consumers[0]: Invalid value: v1alpha1.ServiceConsumer{Partition:"", Peer:""}: either partition or peer must be specified.`, + `service consumer must define at least one of Peer, Partition, or SamenessGroup`, }, }, "partition provided when partitions are disabled": { @@ -379,7 +406,7 @@ func TestExportedServices_Validate(t *testing.T) { namespaceEnabled: true, partitionsEnabled: false, expectedErrMsgs: []string{ - `spec.services[0].consumers[0].partitions: Invalid value: "test-partition": Consul Admin Partitions need to be enabled to specify partition.`, + `spec.services[0].consumers[0].partition: Invalid value: "test-partition": Consul Admin Partitions need to be enabled to specify partition.`, }, }, "namespace provided when namespaces are disabled": { @@ -407,6 +434,81 @@ func TestExportedServices_Validate(t *testing.T) { `spec.services[0]: Invalid value: "frontend": Consul Namespaces must be enabled to specify service namespace.`, }, }, + "exporting to all partitions is not supported": { + input: &ExportedServices{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.DefaultConsulPartition, + }, + Spec: ExportedServicesSpec{ + Services: []ExportedService{ + { + Name: "service-frontend", + Namespace: "frontend", + Consumers: []ServiceConsumer{ + { + Partition: "*", + }, + }, + }, + }, + }, + }, + namespaceEnabled: true, + partitionsEnabled: true, + expectedErrMsgs: []string{ + `exporting to all partitions (wildcard) is not supported`, + }, + }, + "exporting to all peers (wildcard) is not supported": { + input: &ExportedServices{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.DefaultConsulPartition, + }, + Spec: ExportedServicesSpec{ + Services: []ExportedService{ + { + Name: "service-frontend", + Namespace: "frontend", + Consumers: []ServiceConsumer{ + { + Peer: "*", + }, + }, + }, + }, + }, + }, + namespaceEnabled: true, + partitionsEnabled: true, + expectedErrMsgs: []string{ + `exporting to all peers (wildcard) is not supported`, + }, + }, + "exporting to all sameness groups (wildcard) is not supported": { + input: &ExportedServices{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.DefaultConsulPartition, + }, + Spec: ExportedServicesSpec{ + Services: []ExportedService{ + { + Name: "service-frontend", + Namespace: "frontend", + Consumers: []ServiceConsumer{ + { + SamenessGroup: "*", + }, + }, + }, + }, + }, + }, + namespaceEnabled: true, + partitionsEnabled: true, + expectedErrMsgs: []string{ + `exporting to all sameness groups (wildcard) is not supported`, + }, + }, "multiple errors": { input: &ExportedServices{ ObjectMeta: metav1.ObjectMeta{ @@ -423,6 +525,10 @@ func TestExportedServices_Validate(t *testing.T) { Peer: "second-peer", }, {}, + { + SamenessGroup: "sg2", + Partition: "partition2", + }, }, }, }, @@ -431,8 +537,9 @@ func TestExportedServices_Validate(t *testing.T) { namespaceEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `spec.services[0].consumers[0]: Invalid value: v1alpha1.ServiceConsumer{Partition:"second", Peer:"second-peer"}: both partition and peer cannot be specified.`, - `spec.services[0].consumers[1]: Invalid value: v1alpha1.ServiceConsumer{Partition:"", Peer:""}: either partition or peer must be specified.`, + `spec.services[0].consumers[0]: Invalid value: v1alpha1.ServiceConsumer{Partition:"second", Peer:"second-peer", SamenessGroup:""}: service consumer must define at most one of Peer, Partition, or SamenessGroup`, + `spec.services[0].consumers[1]: Invalid value: v1alpha1.ServiceConsumer{Partition:"", Peer:"", SamenessGroup:""}: service consumer must define at least one of Peer, Partition, or SamenessGroup`, + `spec.services[0].consumers[2]: Invalid value: v1alpha1.ServiceConsumer{Partition:"partition2", Peer:"", SamenessGroup:"sg2"}: service consumer must define at most one of Peer, Partition, or SamenessGroup`, }, }, } diff --git a/control-plane/api/v1alpha1/exportedservices_webhook_test.go b/control-plane/api/v1alpha1/exportedservices_webhook_test.go index 41476bd822..735f938825 100644 --- a/control-plane/api/v1alpha1/exportedservices_webhook_test.go +++ b/control-plane/api/v1alpha1/exportedservices_webhook_test.go @@ -123,7 +123,7 @@ func TestValidateExportedServices(t *testing.T) { Partition: "", }, expAllow: false, - expErrMessage: "exportedservices.consul.hashicorp.com \"default\" is invalid: spec.services[0].consumers[0].partitions: Invalid value: \"other\": Consul Admin Partitions need to be enabled to specify partition.", + expErrMessage: "exportedservices.consul.hashicorp.com \"default\" is invalid: spec.services[0].consumers[0].partition: Invalid value: \"other\": Consul Admin Partitions need to be enabled to specify partition.", }, "no services": { existingResources: []runtime.Object{}, diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index 82a5dcfdc8..a066b4e84f 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -81,7 +81,7 @@ type SourceIntention struct { Name string `json:"name,omitempty"` // Namespace is the namespace for the Name parameter. Namespace string `json:"namespace,omitempty"` - // [Experimental] Peer is the peer name for the Name parameter. + // Peer is the peer name for the Name parameter. Peer string `json:"peer,omitempty"` // Partition is the Admin Partition for the Name parameter. Partition string `json:"partition,omitempty"` diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index f485b5c5f1..87d4bd9594 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -1324,21 +1324,6 @@ func (in *RingHashConfig) DeepCopy() *RingHashConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SamenessGroupMember) DeepCopyInto(out *SamenessGroupMember) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupMember. -func (in *SamenessGroupMember) DeepCopy() *SamenessGroupMember { - if in == nil { - return nil - } - out := new(SamenessGroupMember) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SamenessGroup) DeepCopyInto(out *SamenessGroup) { *out = *in @@ -1348,7 +1333,7 @@ func (in *SamenessGroup) DeepCopyInto(out *SamenessGroup) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroups. +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroup. func (in *SamenessGroup) DeepCopy() *SamenessGroup { if in == nil { return nil @@ -1398,6 +1383,40 @@ func (in *SamenessGroupList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SamenessGroupMember) DeepCopyInto(out *SamenessGroupMember) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupMember. +func (in *SamenessGroupMember) DeepCopy() *SamenessGroupMember { + if in == nil { + return nil + } + out := new(SamenessGroupMember) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in SamenessGroupMembers) DeepCopyInto(out *SamenessGroupMembers) { + { + in := &in + *out = make(SamenessGroupMembers, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupMembers. +func (in SamenessGroupMembers) DeepCopy() SamenessGroupMembers { + if in == nil { + return nil + } + out := new(SamenessGroupMembers) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SamenessGroupSpec) DeepCopyInto(out *SamenessGroupSpec) { *out = *in diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index 6352ac3af1..84e523f50c 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -72,8 +72,11 @@ spec: the service to. type: string peer: - description: '[Experimental] Peer is the name of the peer - to export the service to.' + description: Peer is the name of the peer to export the service to. + type: string + samenessGroup: + description: SamenessGroup is the name of the sameness + group to export the service to. type: string type: object type: array diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index c66b5fdd0f..86409d2de0 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -149,8 +149,9 @@ spec: "sequential") and "order-by-locality". type: string regions: - description: The ordered list of the regions of the failover targets. - Valid values can be "us-west-1", "us-west-2", and so on. + description: Regions is the ordered list of the regions of the + failover targets. Valid values can be "us-west-1", "us-west-2", + and so on. items: type: string type: array diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml index 5efda1ffa2..c71a211f63 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -54,16 +54,27 @@ spec: description: SamenessGroupSpec defines the desired state of SamenessGroup. properties: defaultForFailover: - description: 'DefaultForFailover indicates that upstream requests to members of the given sameness group will implicitly failover between members of this sameness group.' + description: DefaultForFailover indicates that upstream requests to + members of the given sameness group will implicitly failover between + members of this sameness group. When DefaultForFailover is true, + the local partition must be a member of the sameness group or IncludeLocal + must be set to true. type: boolean includeLocal: - description: 'IncludeLocal is used to include the local partition as the first member of the sameness group.' + description: IncludeLocal is used to include the local partition as + the first member of the sameness group. The local partition can + only be a member of a single sameness group. type: boolean members: - description: 'Members are the partitions and peers that are part of the sameness group.' + description: Members are the partitions and peers that are part of + the sameness group. If a member of a sameness group does not exist, + it will be ignored. items: properties: partition: + description: The partitions and peers that are part of the sameness + group. A sameness group member cannot define both peer and + partition at the same time. type: string peer: type: string 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 8e186af1a7..3c80ea8933 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -98,8 +98,7 @@ spec: description: Partition is the Admin Partition for the Name parameter. type: string peer: - description: '[Experimental] Peer is the peer name for the Name - parameter.' + description: Peer is the peer name for the Name parameter. type: string permissions: description: Permissions is the list of all additional L7 attributes diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index 56b5e72014..bae83ac7b9 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -84,8 +84,9 @@ spec: to "sequential") and "order-by-locality". type: string regions: - description: The ordered list of the regions of the failover targets. - Valid values can be "us-west-1", "us-west-2", and so on. + description: Regions is the ordered list of the regions + of the failover targets. Valid values can be "us-west-1", + "us-west-2", and so on. items: type: string type: array diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index f96d669544..663ace9d70 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -138,7 +138,7 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-samenessgroup + path: /mutate-v1alpha1-samenessgroups failurePolicy: Fail name: mutate-samenessgroup.consul.hashicorp.com rules: diff --git a/control-plane/go.mod b/control-plane/go.mod index 23900deb46..5307056ce3 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.10.1-0.20230331190547-fc64a702f43c + github.com/hashicorp/consul/api v1.10.1-0.20230418163148-eb9f671eafae github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 @@ -25,8 +25,6 @@ require ( github.com/mitchellh/cli v1.1.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/onsi/ginkgo v1.16.4 - github.com/onsi/gomega v1.17.0 github.com/stretchr/testify v1.7.2 go.uber.org/zap v1.19.0 golang.org/x/text v0.3.8 @@ -110,7 +108,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect - github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.0.0 // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect @@ -147,7 +144,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.22.2 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index d794bad47f..c045aba7bd 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -353,8 +353,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20230331190547-fc64a702f43c h1:MfDuWW38RozPodXT2aGc5jakoYo9EjgYpZl+vR3//Wg= -github.com/hashicorp/consul/api v1.10.1-0.20230331190547-fc64a702f43c/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= +github.com/hashicorp/consul/api v1.10.1-0.20230418163148-eb9f671eafae h1:lYnO52QxlfATRZ1Vo8tQV+lFns7rZ4iAbbi3JN4ZAQw= +github.com/hashicorp/consul/api v1.10.1-0.20230418163148-eb9f671eafae/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= From 969b6f91c446b6d9c1de65b56552fd0a1b1fdd19 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Thu, 27 Apr 2023 15:47:08 -0400 Subject: [PATCH 143/592] Adjust API gateway controller deployment appropriately when Vault configured as secrets backend (#2083) * Adjust mount based on whether Vault is enabled as secrets backend * Add changelog entry * Improve wording of changelog entry * Use Vault serverca for CONSUL_CACERT when secrets backend enabled * Add comment to Helm template explaining logic * Add unit test for CONSUL_CACERT with Vault secret path * Add unit tests for removing mounts when Vault is secrets backend --- .changelog/2083.txt | 3 ++ .../api-gateway-controller-deployment.yaml | 13 +++-- .../api-gateway-controller-deployment.bats | 47 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 .changelog/2083.txt diff --git a/.changelog/2083.txt b/.changelog/2083.txt new file mode 100644 index 0000000000..23c0c77592 --- /dev/null +++ b/.changelog/2083.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: fix issue where the API Gateway controller is unable to start up successfully when Vault is configured as the secrets backend +``` diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 86517d7140..8c5c2fa73e 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -65,7 +65,14 @@ spec: {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} {{- if .Values.global.tls.enabled }} - name: CONSUL_CACERT + {{- /* When Vault is being used as a secrets backend, auto-encrypt must be enabled. Since clients use a separate + root CA from servers when auto-encrypt is enabled, and our controller communicates with the agent when clients are + enabled, we only use the Vault server CA if clients are disabled and our controller will be communicating w/ the server. */}} + {{- if and (not .Values.client.enabled) .Values.global.secretsBackend.vault.enabled }} + value: /vault/secrets/serverca.crt + {{- else }} value: /consul/tls/ca/tls.crt + {{- end }} {{- end }} {{- end }} - name: HOST_IP @@ -156,7 +163,7 @@ spec: - name: consul-bin mountPath: /consul-bin {{- end }} - {{- if or (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) .Values.client.enabled }} + {{- if or (not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled)) .Values.client.enabled }} {{- if .Values.global.tls.enabled }} {{- if and .Values.client.enabled .Values.global.tls.enableAutoEncrypt }} - name: consul-auto-encrypt-ca-cert @@ -186,7 +193,7 @@ spec: emptyDir: { } {{- end }} {{- if .Values.global.tls.enabled }} - {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} + {{- 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 }} @@ -253,7 +260,7 @@ spec: - mountPath: /consul/login name: consul-data readOnly: false - {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} + {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} {{- if .Values.global.tls.enabled }} - name: consul-ca-cert mountPath: /consul/tls/ca diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index 880586ab43..327802af07 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -1522,6 +1522,23 @@ load _helpers [ "${actual}" = "true" ] } +@test "apiGateway/Deployment: CONSUL_CACERT has correct path with Vault as secrets backend and client disabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'server.enabled=true' \ + --set 'client.enabled=false' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + . | tee /dev/stderr| + yq '.spec.template.spec.containers[0].env[0].value == "/vault/secrets/serverca.crt"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + @test "apiGateway/Deployment: CONSUL_CACERT is not set when using tls and useSystemRoots" { cd `chart_dir` local actual=$(helm template \ @@ -1555,6 +1572,21 @@ load _helpers [ "${actual}" = "" ] } +@test "apiGateway/Deployment: consul-ca-cert volume mount is not set when using Vault as a secrets backend" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + @test "apiGateway/Deployment: consul-ca-cert volume mount is not set on acl-init when using externalServers and useSystemRoots" { cd `chart_dir` local actual=$(helm template \ @@ -1572,6 +1604,21 @@ load _helpers [ "${actual}" = "" ] } +@test "apiGateway/Deployment: consul-ca-cert volume mount is not set on acl-init when using Vault as secrets backend" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + @test "apiGateway/Deployment: consul-auto-encrypt-ca-cert volume mount is set when tls.enabled, client.enabled, externalServers, useSystemRoots, and autoencrypt" { cd `chart_dir` local actual=$(helm template \ From 21731124eb8fe9a7b1a8be8e6715a3b98bfeb10c Mon Sep 17 00:00:00 2001 From: "hashicorp-tsccr[bot]" <129506189+hashicorp-tsccr[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 17:00:42 -0400 Subject: [PATCH 144/592] Result of tsccr-helper -pin-all-workflows . (#2089) Co-authored-by: hashicorp-tsccr[bot] --- .github/workflows/build.yml | 4 ++-- .github/workflows/jira-issues.yaml | 6 +++--- .github/workflows/jira-pr.yaml | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a00629bde2..43194ea7bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -162,7 +162,7 @@ jobs: - name: Test rpm package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: registry.access.redhat.com/ubi8/ubi:latest options: -v ${{ github.workspace }}:/work @@ -187,7 +187,7 @@ jobs: - name: Test debian package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: ubuntu:latest options: -v ${{ github.workspace }}:/work diff --git a/.github/workflows/jira-issues.yaml b/.github/workflows/jira-issues.yaml index bddc69c83f..700803a45f 100644 --- a/.github/workflows/jira-issues.yaml +++ b/.github/workflows/jira-issues.yaml @@ -15,7 +15,7 @@ jobs: name: Jira Community Issue sync steps: - name: Login - uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 + uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -72,14 +72,14 @@ jobs: - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index c07a92ee77..a285c4c176 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -13,7 +13,7 @@ jobs: name: Jira sync steps: - name: Login - uses: atlassian/gajira-login@v3.0.0 + uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -52,7 +52,7 @@ jobs: - name: Create ticket if an issue is filed, or if PR not by a team member is opened if: ( github.event.action == 'opened' && steps.is-team-member.outputs.MESSAGE == 'false' ) - uses: tomhjp/gh-action-jira-create@v0.2.0 + uses: tomhjp/gh-action-jira-create@3ed1789cad3521292e591a7cfa703215ec1348bf # v0.2.1 with: project: NET issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" @@ -72,28 +72,28 @@ jobs: - name: Search if: github.event.action != 'opened' id: search - uses: tomhjp/gh-action-jira-search@v0.2.1 + uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2 with: # cf[10089] is Issue Link (use JIRA API to retrieve) jql: 'issuetype = "${{ steps.set-ticket-type.outputs.TYPE }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' - name: Sync comment if: github.event.action == 'created' && steps.search.outputs.issue - uses: tomhjp/gh-action-jira-comment@v0.1.0 + uses: tomhjp/gh-action-jira-comment@6eb6b9ead70221916b6badd118c24535ed220bd9 # v0.2.0 with: issue: ${{ steps.search.outputs.issue }} comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}" - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@v2.0.1 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@v2.0.1 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" From 7a006b5cfb7b8c0808e9adec472dbe76684f8199 Mon Sep 17 00:00:00 2001 From: Eric Haberkorn Date: Fri, 28 Apr 2023 09:13:51 -0400 Subject: [PATCH 145/592] set consul server locality from k8s node labels (#2093) --- .changelog/2093.txt | 3 + charts/consul/templates/_helpers.tpl | 4 +- .../consul/templates/server-clusterrole.yaml | 16 ++ .../templates/server-clusterrolebinding.yaml | 18 ++ .../consul/templates/server-statefulset.yaml | 27 ++- control-plane/commands.go | 4 + .../subcommand/fetch-server-region/command.go | 158 ++++++++++++++++++ .../fetch-server-region/command_test.go | 114 +++++++++++++ 8 files changed, 337 insertions(+), 7 deletions(-) create mode 100644 .changelog/2093.txt create mode 100644 charts/consul/templates/server-clusterrole.yaml create mode 100644 charts/consul/templates/server-clusterrolebinding.yaml create mode 100644 control-plane/subcommand/fetch-server-region/command.go create mode 100644 control-plane/subcommand/fetch-server-region/command_test.go diff --git a/.changelog/2093.txt b/.changelog/2093.txt new file mode 100644 index 0000000000..20c657e566 --- /dev/null +++ b/.changelog/2093.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: set agent localities on Consul servers to the server node's `topology.kubernetes.io/region` label. +``` diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 3552c8c209..b1feb0dbd6 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -335,7 +335,7 @@ Consul server environment variables for consul-k8s commands. {{- end }} {{- if and .Values.externalServers.enabled .Values.externalServers.skipServerWatch }} - name: CONSUL_SKIP_SERVER_WATCH - value: "true" + value: "true" {{- end }} {{- end -}} @@ -366,7 +366,7 @@ Usage: {{ template "consul.validateCloudSecretKeys" . }} */}} {{- define "consul.validateCloudSecretKeys" -}} -{{- if and .Values.global.cloud.enabled }} +{{- if and .Values.global.cloud.enabled }} {{- if or (and .Values.global.cloud.resourceId.secretName (not .Values.global.cloud.resourceId.secretKey)) (and .Values.global.cloud.resourceId.secretKey (not .Values.global.cloud.resourceId.secretName)) }} {{fail "When either global.cloud.resourceId.secretName or global.cloud.resourceId.secretKey is defined, both must be set."}} {{- end }} diff --git a/charts/consul/templates/server-clusterrole.yaml b/charts/consul/templates/server-clusterrole.yaml new file mode 100644 index 0000000000..c22f562264 --- /dev/null +++ b/charts/consul/templates/server-clusterrole.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "consul.fullname" . }}-server + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: server +rules: +- apiGroups: [""] + resources: ["nodes"] + verbs: + - get diff --git a/charts/consul/templates/server-clusterrolebinding.yaml b/charts/consul/templates/server-clusterrolebinding.yaml new file mode 100644 index 0000000000..854fda870e --- /dev/null +++ b/charts/consul/templates/server-clusterrolebinding.yaml @@ -0,0 +1,18 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "consul.fullname" . }}-server + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: server +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "consul.fullname" . }}-server +subjects: +- kind: ServiceAccount + name: {{ template "consul.fullname" . }}-server + namespace: {{ .Release.Namespace }} diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 8b73306fd7..aa9198f127 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -217,6 +217,22 @@ spec: {{- if .Values.server.priorityClassName }} priorityClassName: {{ .Values.server.priorityClassName | quote }} {{- end }} + initContainers: + - name: locality-init + image: {{ .Values.global.imageK8S }} + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + command: + - "/bin/sh" + - "-ec" + - | + consul-k8s-control-plane fetch-server-region -node-name "$NODE_NAME" -output-file /consul/extra-config/locality.json + volumeMounts: + - name: extra-config + mountPath: /consul/extra-config containers: - name: consul image: "{{ default .Values.global.image .Values.server.image }}" @@ -291,9 +307,9 @@ spec: {{- end }} {{- if .Values.global.cloud.enabled}} # These are mounted as secrets so that the consul server agent can use them. - # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, + # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. - # - HCP_RESOURCE_ID is created for use in the + # - HCP_RESOURCE_ID is created for use in the # `-hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }"` logic in the command below. {{- if .Values.global.cloud.clientId.secretName }} - name: HCP_CLIENT_ID @@ -328,7 +344,7 @@ spec: valueFrom: secretKeyRef: name: {{ .Values.global.cloud.apiHost.secretName }} - key: {{ .Values.global.cloud.apiHost.secretKey }} + key: {{ .Values.global.cloud.apiHost.secretKey }} {{- end}} {{- if .Values.global.cloud.scadaAddress.secretName }} - name: HCP_SCADA_ADDRESS @@ -336,7 +352,7 @@ spec: secretKeyRef: name: {{ .Values.global.cloud.scadaAddress.secretName }} key: {{ .Values.global.cloud.scadaAddress.secretKey }} - {{- end}} + {{- end}} {{- end }} {{- include "consul.extraEnvironmentVars" .Values.server | nindent 12 }} command: @@ -375,7 +391,8 @@ spec: -config-dir=/consul/userconfig/{{ .name }} \ {{- end }} {{- end }} - -config-file=/consul/extra-config/extra-from-values.json + -config-file=/consul/extra-config/extra-from-values.json \ + -config-file=/consul/extra-config/locality.json {{- if and .Values.global.cloud.enabled .Values.global.cloud.resourceId.secretName }} -hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }" {{- end }} diff --git a/control-plane/commands.go b/control-plane/commands.go index 4b7cbed362..0da29c938b 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -11,6 +11,7 @@ import ( cmdConsulLogout "github.com/hashicorp/consul-k8s/control-plane/subcommand/consul-logout" cmdCreateFederationSecret "github.com/hashicorp/consul-k8s/control-plane/subcommand/create-federation-secret" cmdDeleteCompletedJob "github.com/hashicorp/consul-k8s/control-plane/subcommand/delete-completed-job" + cmdFetchServerRegion "github.com/hashicorp/consul-k8s/control-plane/subcommand/fetch-server-region" cmdGetConsulClientCA "github.com/hashicorp/consul-k8s/control-plane/subcommand/get-consul-client-ca" cmdGossipEncryptionAutogenerate "github.com/hashicorp/consul-k8s/control-plane/subcommand/gossip-encryption-autogenerate" cmdInjectConnect "github.com/hashicorp/consul-k8s/control-plane/subcommand/inject-connect" @@ -90,6 +91,9 @@ func init() { "install-cni": func() (cli.Command, error) { return &cmdInstallCNI.Command{UI: ui}, nil }, + "fetch-server-region": func() (cli.Command, error) { + return &cmdFetchServerRegion.Command{UI: ui}, nil + }, } } diff --git a/control-plane/subcommand/fetch-server-region/command.go b/control-plane/subcommand/fetch-server-region/command.go new file mode 100644 index 0000000000..248ce971e7 --- /dev/null +++ b/control-plane/subcommand/fetch-server-region/command.go @@ -0,0 +1,158 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fetchserverregion + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "sync" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +// The consul-logout command issues a Consul logout API request to delete an ACL token. +type Command struct { + UI cli.Ui + + flagLogLevel string + flagLogJSON bool + flagNodeName string + flagOutputFile string + + flagSet *flag.FlagSet + k8s *flags.K8SFlags + + once sync.Once + help string + logger hclog.Logger + + // for testing + clientset kubernetes.Interface +} + +type Locality struct { + Region string `json:"region"` +} + +type Config struct { + Locality Locality `json:"locality"` +} + +func (c *Command) init() { + c.flagSet = flag.NewFlagSet("", flag.ContinueOnError) + c.flagSet.StringVar(&c.flagLogLevel, "log-level", "info", + "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ + "\"debug\", \"info\", \"warn\", and \"error\".") + c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, + "Enable or disable JSON output format for logging.") + c.flagSet.StringVar(&c.flagNodeName, "node-name", "", + "Specifies the node name that will be used.") + c.flagSet.StringVar(&c.flagOutputFile, "output-file", "", + "The file path for writing the locality portion of a Consul agent configuration to.") + + c.k8s = &flags.K8SFlags{} + flags.Merge(c.flagSet, c.k8s.Flags()) + + c.help = flags.Usage(help, c.flagSet) + +} + +func (c *Command) Run(args []string) int { + var err error + c.once.Do(c.init) + + if err := c.flagSet.Parse(args); err != nil { + return 1 + } + + if c.logger == nil { + c.logger, err = common.Logger(c.flagLogLevel, c.flagLogJSON) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + } + + if c.flagNodeName == "" { + c.UI.Error("-node-name is required") + return 1 + } + + if c.flagOutputFile == "" { + c.UI.Error("-output-file is required") + return 1 + } + + if c.clientset == nil { + config, err := rest.InClusterConfig() + if err != nil { + // This just allows us to test it locally. + kubeconfig := clientcmd.RecommendedHomeFile + config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + } + + c.clientset, err = kubernetes.NewForConfig(config) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + } + + config := c.fetchLocalityConfig() + + jsonData, err := json.Marshal(config) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + + err = os.WriteFile(c.flagOutputFile, jsonData, 0644) + if err != nil { + c.UI.Error(fmt.Sprintf("Error writing locality file: %s", err)) + return 1 + } + + return 0 +} + +func (c *Command) fetchLocalityConfig() Config { + var cfg Config + node, err := c.clientset.CoreV1().Nodes().Get(context.Background(), c.flagNodeName, metav1.GetOptions{}) + if err != nil { + return cfg + } + + cfg.Locality.Region = node.Labels[corev1.LabelTopologyRegion] + + return cfg +} + +func (c *Command) Synopsis() string { return synopsis } +func (c *Command) Help() string { + c.once.Do(c.init) + return c.help +} + +const synopsis = "Fetch the cloud region for a Consul server from the Kubernetes node's region label." +const help = ` +Usage: consul-k8s-control-plane fetch-server-region [options] + + Fetch the region for a Consul server. + Not intended for stand-alone use. +` diff --git a/control-plane/subcommand/fetch-server-region/command_test.go b/control-plane/subcommand/fetch-server-region/command_test.go new file mode 100644 index 0000000000..a64dc9be95 --- /dev/null +++ b/control-plane/subcommand/fetch-server-region/command_test.go @@ -0,0 +1,114 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fetchserverregion + +import ( + "os" + "testing" + + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" +) + +func TestRun_FlagValidation(t *testing.T) { + t.Parallel() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + + cases := map[string]struct { + args []string + err string + }{ + "missing node name": { + args: []string{}, + err: "-node-name is required", + }, + "missing output-file": { + args: []string{"-node-name", "n1"}, + err: "-output-file is required", + }, + } + + for n, c := range cases { + c := c + t.Run(n, func(t *testing.T) { + responseCode := cmd.Run(c.args) + require.Equal(t, 1, responseCode, ui.ErrorWriter.String()) + require.Contains(t, ui.ErrorWriter.String(), c.err) + }) + } +} + +func TestRun(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + region string + expected string + missingNode bool + }{ + "no region": { + expected: `{"locality":{"region":""}}`, + }, + "region": { + region: "us-east-1", + expected: `{"locality":{"region":"us-east-1"}}`, + }, + "missing node": { + region: "us-east-1", + missingNode: true, + expected: `{"locality":{"region":""}}`, + }, + } + + for n, c := range cases { + c := c + t.Run(n, func(t *testing.T) { + outputFile, err := os.CreateTemp("", "ca") + require.NoError(t, err) + t.Cleanup(func() { + os.RemoveAll(outputFile.Name()) + }) + + var objs []runtime.Object + if !c.missingNode { + objs = append(objs, &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node", + Labels: map[string]string{ + corev1.LabelTopologyRegion: c.region, + }, + }, + }) + } + + k8s := fake.NewSimpleClientset(objs...) + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + } + + responseCode := cmd.Run([]string{ + "-node-name", + "my-node", + "-output-file", + outputFile.Name(), + }) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + require.NoError(t, err) + cfg, err := os.ReadFile(outputFile.Name()) + require.NoError(t, err) + require.Equal(t, c.expected, string(cfg)) + }) + } +} From 28f396ac12fe04131e4380d4d968a688ef8b3494 Mon Sep 17 00:00:00 2001 From: malizz Date: Fri, 28 Apr 2023 11:44:16 -0700 Subject: [PATCH 146/592] add sameness group to service resolver, update manifests (#2086) * add sameness group to service resolver, update manifests * get the latest api and update acceptance tests * get the latest api in acceptanc tests * update validation code, remove dynamic validations, update tests * check nil pointer * go get latest api * revert acceptance changes --- acceptance/go.mod | 2 +- acceptance/go.sum | 2 +- .../bases/crds-oss/serviceresolver.yaml | 2 +- .../templates/crd-serviceresolvers.yaml | 8 + .../api/v1alpha1/serviceresolver_types.go | 213 ++++++- .../v1alpha1/serviceresolver_types_test.go | 585 +++++++++++++++++- ...consul.hashicorp.com_serviceresolvers.yaml | 8 + control-plane/go.mod | 10 +- control-plane/go.sum | 22 +- 9 files changed, 792 insertions(+), 60 deletions(-) diff --git a/acceptance/go.mod b/acceptance/go.mod index 66fa6c4b44..503ca5333c 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -101,4 +101,4 @@ require ( k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect sigs.k8s.io/yaml v1.2.0 // indirect -) +) \ No newline at end of file diff --git a/acceptance/go.sum b/acceptance/go.sum index 44969f22ae..3301c3a7da 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -1213,4 +1213,4 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3 sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml b/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml index a71da92e35..fc236966d6 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml @@ -7,4 +7,4 @@ metadata: name: resolver spec: redirect: - service: bar + service: bar \ No newline at end of file diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 04d6dd9754..2a6f7923b8 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -95,6 +95,10 @@ spec: type: string type: array type: object + samenessGroup: + description: SamenessGroup is the name of the sameness group + to try during failover. + type: string service: description: Service is the service to resolve instead of the default as the failover group of instances during failover. @@ -248,6 +252,10 @@ spec: description: Peer is the name of the cluster peer to resolve the service from instead of the current one. type: string + samenessGroup: + description: SamenessGroup is the name of the sameness group to + resolve the service from instead of the current one. + type: string service: description: Service is a service to resolve instead of the current service. diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 48483550ba..75aa44f6b9 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -5,16 +5,19 @@ package v1alpha1 import ( "encoding/json" + "regexp" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + capi "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-bexpr" ) const ServiceResolverKubeKind string = "serviceresolver" @@ -97,6 +100,8 @@ type ServiceResolverRedirect struct { // Peer is the name of the cluster peer to resolve the service from instead // of the current one. Peer string `json:"peer,omitempty"` + // SamenessGroup is the name of the sameness group to resolve the service from instead of the current one. + SamenessGroup string `json:"samenessGroup,omitempty"` } type ServiceResolverSubsetMap map[string]ServiceResolverSubset @@ -133,6 +138,8 @@ type ServiceResolverFailover struct { Targets []ServiceResolverFailoverTarget `json:"targets,omitempty"` // Policy specifies the exact mechanism used for failover. Policy *FailoverPolicy `json:"policy,omitempty"` + // SamenessGroup is the name of the sameness group to try during failover. + SamenessGroup string `json:"samenessGroup,omitempty"` } type ServiceResolverFailoverTarget struct { @@ -322,14 +329,17 @@ func (in *ServiceResolver) Validate(consulMeta common.ConsulMeta) error { var errs field.ErrorList path := field.NewPath("spec") - for k, v := range in.Spec.Failover { - if err := v.validate(path.Child("failover").Key(k)); err != nil { - errs = append(errs, err) - } + for subset, f := range in.Spec.Failover { + errs = append(errs, f.validate(path.Child("failover").Key(subset), consulMeta)...) + } + if len(in.Spec.Failover) > 0 && in.Spec.Redirect != nil { + asJSON, _ := json.Marshal(in) + errs = append(errs, field.Invalid(path, string(asJSON), "service resolver redirect and failover cannot both be set")) } + errs = append(errs, in.Spec.Redirect.validate(path.Child("redirect"), consulMeta)...) + errs = append(errs, in.Spec.Subsets.validate(path.Child("subsets"))...) errs = append(errs, in.Spec.LoadBalancer.validate(path.Child("loadBalancer"))...) - errs = append(errs, in.validateEnterprise(consulMeta)...) if len(errs) > 0 { @@ -356,6 +366,31 @@ func (in ServiceResolverSubsetMap) toConsul() map[string]capi.ServiceResolverSub return m } +func (in ServiceResolverSubsetMap) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if len(in) == 0 { + return nil + } + validServiceSubset := regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`) + + for name, subset := range in { + indexPath := path.Key(name) + + if name == "" { + errs = append(errs, field.Invalid(indexPath, name, "subset defined with empty name")) + } + if !validServiceSubset.MatchString(name) { + errs = append(errs, field.Invalid(indexPath, name, "subset name must begin or end with lower case alphanumeric characters, and contain lower case alphanumeric characters or '-' in between")) + } + if subset.Filter != "" { + if _, err := bexpr.CreateEvaluator(subset.Filter, nil); err != nil { + errs = append(errs, field.Invalid(indexPath.Child("filter"), subset.Filter, "filter for subset is not a valid expression")) + } + } + } + return errs +} + func (in ServiceResolverSubset) toConsul() capi.ServiceResolverSubset { return capi.ServiceResolverSubset{ Filter: in.Filter, @@ -374,7 +409,74 @@ func (in *ServiceResolverRedirect) toConsul() *capi.ServiceResolverRedirect { Datacenter: in.Datacenter, Partition: in.Partition, Peer: in.Peer, + SamenessGroup: in.SamenessGroup, + } +} + +func (in *ServiceResolverRedirect) validate(path *field.Path, consulMeta common.ConsulMeta) field.ErrorList { + var errs field.ErrorList + if in == nil { + return nil + } + + asJSON, _ := json.Marshal(in) + if in.isEmpty() { + errs = append(errs, field.Invalid(path, "{}", + "service resolver redirect cannot be empty")) + } + + if consulMeta.Partition != "default" && in.Datacenter != "" { + errs = append(errs, field.Invalid(path.Child("datacenter"), in.Datacenter, + "cross-datacenter redirect is only supported in the default partition")) + } + if consulMeta.Partition != in.Partition && in.Datacenter != "" { + errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, + "cross-datacenter and cross-partition redirect is not supported")) + } + + switch { + case in.SamenessGroup != "" && in.ServiceSubset != "": + errs = append(errs, field.Invalid(path, string(asJSON), + "samenessGroup cannot be set with serviceSubset")) + case in.SamenessGroup != "" && in.Partition != "": + errs = append(errs, field.Invalid(path, string(asJSON), + "partition cannot be set with samenessGroup")) + case in.SamenessGroup != "" && in.Datacenter != "": + errs = append(errs, field.Invalid(path, string(asJSON), + "samenessGroup cannot be set with datacenter")) + case in.Peer != "" && in.ServiceSubset != "": + errs = append(errs, field.Invalid(path, string(asJSON), + "peer cannot be set with serviceSubset")) + case in.Peer != "" && in.Partition != "": + errs = append(errs, field.Invalid(path, string(asJSON), + "partition cannot be set with peer")) + case in.Peer != "" && in.Datacenter != "": + errs = append(errs, field.Invalid(path, string(asJSON), + "peer cannot be set with datacenter")) + case in.Service == "": + if in.ServiceSubset != "" { + errs = append(errs, field.Invalid(path, string(asJSON), + "serviceSubset defined without service")) + } + if in.Namespace != "" { + errs = append(errs, field.Invalid(path, string(asJSON), + "namespace defined without service")) + } + if in.Partition != "" { + errs = append(errs, field.Invalid(path, string(asJSON), + "partition defined without service")) + } + if in.Peer != "" { + errs = append(errs, field.Invalid(path, string(asJSON), + "peer defined without service")) + } } + + return errs +} + +func (in *ServiceResolverRedirect) isEmpty() bool { + return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && in.Partition == "" && in.Datacenter == "" && in.Peer == "" && in.SamenessGroup == "" } func (in ServiceResolverFailoverMap) toConsul() map[string]capi.ServiceResolverFailover { @@ -383,27 +485,38 @@ func (in ServiceResolverFailoverMap) toConsul() map[string]capi.ServiceResolverF } m := make(map[string]capi.ServiceResolverFailover) for k, v := range in { - m[k] = v.toConsul() + if f := v.toConsul(); f != nil { + m[k] = *f + } } return m } -func (in ServiceResolverFailover) toConsul() capi.ServiceResolverFailover { +func (in *ServiceResolverFailover) toConsul() *capi.ServiceResolverFailover { + if in == nil { + return nil + } var targets []capi.ServiceResolverFailoverTarget for _, target := range in.Targets { targets = append(targets, target.toConsul()) } - return capi.ServiceResolverFailover{ + var policy *capi.ServiceResolverFailoverPolicy + if in.Policy != nil { + policy = &capi.ServiceResolverFailoverPolicy{ + Mode: in.Policy.Mode, + Regions: in.Policy.Regions, + } + } + + return &capi.ServiceResolverFailover{ Service: in.Service, ServiceSubset: in.ServiceSubset, Namespace: in.Namespace, Datacenters: in.Datacenters, Targets: targets, - Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: in.Policy.Mode, - Regions: in.Policy.Regions, - }, + Policy: policy, + SamenessGroup: in.SamenessGroup, } } @@ -513,17 +626,79 @@ func (in *ServiceResolver) validateEnterprise(consulMeta common.ConsulMeta) fiel } func (in *ServiceResolverFailover) isEmpty() bool { - return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 && in.Policy == nil + return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 && in.Policy == nil && in.SamenessGroup == "" } -func (in *ServiceResolverFailover) validate(path *field.Path) *field.Error { +func (in *ServiceResolverFailover) validate(path *field.Path, consulMeta common.ConsulMeta) field.ErrorList { + var errs field.ErrorList if in.isEmpty() { // NOTE: We're passing "{}" here as our value because we know that the // error is we have an empty object. - return field.Invalid(path, "{}", - "service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once") + errs = append(errs, field.Invalid(path, "{}", + "service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once")) } - return nil + + if consulMeta.Partition != "default" && len(in.Datacenters) != 0 { + errs = append(errs, field.Invalid(path.Child("datacenters"), in.Datacenters, + "cross-datacenter failover is only supported in the default partition")) + } + + errs = append(errs, in.Policy.validate(path.Child("policy"))...) + + asJSON, _ := json.Marshal(in) + if in.SamenessGroup != "" { + switch { + case len(in.Datacenters) > 0: + errs = append(errs, field.Invalid(path, string(asJSON), + "samenessGroup cannot be set with datacenters")) + case in.ServiceSubset != "": + errs = append(errs, field.Invalid(path, string(asJSON), + "samenessGroup cannot be set with serviceSubset")) + case len(in.Targets) > 0: + errs = append(errs, field.Invalid(path, string(asJSON), + "samenessGroup cannot be set with targets")) + } + } + + if len(in.Datacenters) != 0 && len(in.Targets) != 0 { + errs = append(errs, field.Invalid(path, string(asJSON), + "targets cannot be set with datacenters")) + } + + if in.ServiceSubset != "" && len(in.Targets) != 0 { + errs = append(errs, field.Invalid(path, string(asJSON), + "targets cannot be set with serviceSubset")) + } + + if in.Service != "" && len(in.Targets) != 0 { + errs = append(errs, field.Invalid(path, string(asJSON), + "targets cannot be set with service")) + } + + for i, target := range in.Targets { + asJSON, _ := json.Marshal(target) + switch { + case target.Peer != "" && target.ServiceSubset != "": + errs = append(errs, field.Invalid(path.Child("targets").Index(i), string(asJSON), + "target.peer cannot be set with target.serviceSubset")) + case target.Peer != "" && target.Partition != "": + errs = append(errs, field.Invalid(path.Child("targets").Index(i), string(asJSON), + "target.partition cannot be set with target.peer")) + case target.Peer != "" && target.Datacenter != "": + errs = append(errs, field.Invalid(path.Child("targets").Index(i), string(asJSON), + "target.peer cannot be set with target.datacenter")) + case target.Partition != "" && target.Datacenter != "": + errs = append(errs, field.Invalid(path.Child("targets").Index(i), string(asJSON), + "target.partition cannot be set with target.datacenter")) + } + } + + for i, dc := range in.Datacenters { + if dc == "" { + errs = append(errs, field.Invalid(path.Child("datacenters").Index(i), "", "found empty datacenter")) + } + } + return errs } func (in *LoadBalancer) validate(path *field.Path) field.ErrorList { diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index c82394784d..d09f0809c8 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -4,6 +4,7 @@ package v1alpha1 import ( + "strings" "testing" "time" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" ) func TestServiceResolver_MatchesConsul(t *testing.T) { @@ -74,6 +76,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-2"}, }, + SamenessGroup: "sg2", }, "failover2": { Service: "failover2", @@ -84,6 +87,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Mode: "", Regions: []string{"us-west-1"}, }, + SamenessGroup: "sg3", }, "failover3": { Targets: []ServiceResolverFailoverTarget{ @@ -153,6 +157,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-2"}, }, + SamenessGroup: "sg2", }, "failover2": { Service: "failover2", @@ -163,6 +168,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Mode: "", Regions: []string{"us-west-1"}, }, + SamenessGroup: "sg3", }, "failover3": { Targets: []capi.ServiceResolverFailoverTarget{ @@ -281,6 +287,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-2"}, }, + SamenessGroup: "sg2", }, "failover2": { Service: "failover2", @@ -291,6 +298,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { Mode: "", Regions: []string{"us-west-1"}, }, + SamenessGroup: "sg3", }, "failover3": { Targets: []ServiceResolverFailoverTarget{ @@ -360,6 +368,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-2"}, }, + SamenessGroup: "sg2", }, "failover2": { Service: "failover2", @@ -370,6 +379,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { Mode: "", Regions: []string{"us-west-1"}, }, + SamenessGroup: "sg3", }, "failover3": { Targets: []capi.ServiceResolverFailoverTarget{ @@ -543,16 +553,15 @@ func TestServiceResolver_Validate(t *testing.T) { Name: "foo", }, Spec: ServiceResolverSpec{ - Redirect: &ServiceResolverRedirect{ - Service: "bar", - Namespace: "namespace-a", - }, Failover: map[string]ServiceResolverFailover{ - "failA": { + "v1": { Service: "baz", Namespace: "namespace-b", }, }, + Subsets: map[string]ServiceResolverSubset{ + "v1": {Filter: "Service.Meta.version == v1"}, + }, }, }, namespacesEnabled: true, @@ -568,10 +577,8 @@ func TestServiceResolver_Validate(t *testing.T) { Redirect: &ServiceResolverRedirect{ Service: "bar", }, - Failover: map[string]ServiceResolverFailover{ - "failA": { - Service: "baz", - }, + Subsets: map[string]ServiceResolverSubset{ + "v1": {Filter: "Service.Meta.version == v1"}, }, }, }, @@ -585,17 +592,15 @@ func TestServiceResolver_Validate(t *testing.T) { Name: "foo", }, Spec: ServiceResolverSpec{ - Redirect: &ServiceResolverRedirect{ - Service: "bar", - Namespace: "namespace-a", - Partition: "other", - }, Failover: map[string]ServiceResolverFailover{ - "failA": { + "v1": { Service: "baz", Namespace: "namespace-b", }, }, + Subsets: map[string]ServiceResolverSubset{ + "v1": {Filter: "Service.Meta.version == v1"}, + }, }, }, namespacesEnabled: true, @@ -611,11 +616,6 @@ func TestServiceResolver_Validate(t *testing.T) { Redirect: &ServiceResolverRedirect{ Service: "bar", }, - Failover: map[string]ServiceResolverFailover{ - "failA": { - Service: "baz", - }, - }, }, }, namespacesEnabled: false, @@ -629,13 +629,13 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Failover: map[string]ServiceResolverFailover{ - "failA": { + "v1": { Service: "", ServiceSubset: "", Namespace: "", Datacenters: nil, }, - "failB": { + "v2": { Service: "", ServiceSubset: "", Namespace: "", @@ -646,10 +646,32 @@ func TestServiceResolver_Validate(t *testing.T) { }, namespacesEnabled: false, expectedErrMsgs: []string{ - "spec.failover[failA]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", - "spec.failover[failB]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", + "spec.failover[v1]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", + "spec.failover[v2]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", }, }, + "service resolver redirect and failover cannot both be set": { + input: &ServiceResolver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: ServiceResolverSpec{ + Redirect: &ServiceResolverRedirect{ + Service: "bar", + Namespace: "namespace-a", + }, + Failover: map[string]ServiceResolverFailover{ + "failA": { + Service: "baz", + Namespace: "namespace-b", + }, + }, + }, + }, + namespacesEnabled: true, + partitionsEnabled: false, + expectedErrMsgs: []string{"service resolver redirect and failover cannot both be set"}, + }, "hashPolicy.field invalid": { input: &ServiceResolver{ ObjectMeta: metav1.ObjectMeta{ @@ -723,11 +745,19 @@ func TestServiceResolver_Validate(t *testing.T) { }, }, }, + Subsets: map[string]ServiceResolverSubset{ + "": { + Filter: "random string", + }, + }, }, }, namespacesEnabled: false, expectedErrMsgs: []string{ - `serviceresolver.consul.hashicorp.com "foo" is invalid: spec.loadBalancer.hashPolicies[0]: Invalid value: "{\"field\":\"header\",\"sourceIP\":true}": cannot set both field and sourceIP`, + `spec.loadBalancer.hashPolicies[0]: Invalid value: "{\"field\":\"header\",\"sourceIP\":true}": cannot set both field and sourceIP`, + `subset defined with empty name`, + `subset name must begin or end with lower case alphanumeric characters, and contain lower case alphanumeric characters or '-' in between`, + `filter for subset is not a valid expression`, }, }, "hashPolicy nothing set is valid": { @@ -778,6 +808,7 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Redirect: &ServiceResolverRedirect{ + Service: "bar", Namespace: "namespace-a", }, }, @@ -794,6 +825,7 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Redirect: &ServiceResolverRedirect{ + Service: "bar", Namespace: "namespace-a", Partition: "other", }, @@ -812,14 +844,19 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Failover: map[string]ServiceResolverFailover{ - "failA": { + "v1": { Namespace: "namespace-a", }, }, + Subsets: map[string]ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == v1", + }, + }, }, }, expectedErrMsgs: []string{ - "serviceresolver.consul.hashicorp.com \"foo\" is invalid: spec.failover[failA].namespace: Invalid value: \"namespace-a\": Consul Enterprise namespaces must be enabled to set failover.namespace", + "serviceresolver.consul.hashicorp.com \"foo\" is invalid: spec.failover[v1].namespace: Invalid value: \"namespace-a\": Consul Enterprise namespaces must be enabled to set failover.namespace", }, namespacesEnabled: false, }, @@ -860,3 +897,497 @@ func TestServiceResolver_Validate(t *testing.T) { }) } } + +func TestServiceResolverRedirect_ToConsul(t *testing.T) { + cases := map[string]struct { + Ours *ServiceResolverRedirect + Exp *capi.ServiceResolverRedirect + }{ + "nil": { + Ours: nil, + Exp: nil, + }, + "empty fields": { + Ours: &ServiceResolverRedirect{}, + Exp: &capi.ServiceResolverRedirect{}, + }, + "every field set": { + Ours: &ServiceResolverRedirect{ + Service: "foo", + ServiceSubset: "v1", + Namespace: "ns1", + Datacenter: "dc1", + Partition: "default", + Peer: "peer1", + SamenessGroup: "sg1", + }, + Exp: &capi.ServiceResolverRedirect{ + Service: "foo", + ServiceSubset: "v1", + Namespace: "ns1", + Datacenter: "dc1", + Partition: "default", + Peer: "peer1", + SamenessGroup: "sg1", + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual := c.Ours.toConsul() + require.Equal(t, c.Exp, actual) + }) + } +} + +func TestServiceResolverRedirect_Validate(t *testing.T) { + cases := map[string]struct { + input *ServiceResolverRedirect + consulMeta common.ConsulMeta + expectedErrMsgs []string + }{ + "empty redirect": { + input: &ServiceResolverRedirect{}, + consulMeta: common.ConsulMeta{}, + expectedErrMsgs: []string{ + "service resolver redirect cannot be empty", + }, + }, + "cross-datacenter redirect is only supported in the default partition": { + input: &ServiceResolverRedirect{ + Datacenter: "dc2", + Partition: "p2", + Service: "foo", + }, + consulMeta: common.ConsulMeta{ + Partition: "p2", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "cross-datacenter redirect is only supported in the default partition", + }, + }, + "cross-datacenter and cross-partition redirect is not supported": { + input: &ServiceResolverRedirect{ + Partition: "p1", + Datacenter: "dc2", + Service: "foo", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "cross-datacenter and cross-partition redirect is not supported", + }, + }, + "samenessGroup cannot be set with serviceSubset": { + input: &ServiceResolverRedirect{ + Service: "foo", + ServiceSubset: "v1", + SamenessGroup: "sg2", + }, + expectedErrMsgs: []string{ + "samenessGroup cannot be set with serviceSubset", + }, + }, + "samenessGroup cannot be set with partition": { + input: &ServiceResolverRedirect{ + Partition: "default", + Service: "foo", + SamenessGroup: "sg2", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "partition cannot be set with samenessGroup", + }, + }, + "samenessGroup cannot be set with datacenter": { + input: &ServiceResolverRedirect{ + Datacenter: "dc2", + Service: "foo", + SamenessGroup: "sg2", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "cross-datacenter and cross-partition redirect is not supported", + "samenessGroup cannot be set with datacenter", + }, + }, + "peer cannot be set with serviceSubset": { + input: &ServiceResolverRedirect{ + Peer: "p2", + Service: "foo", + ServiceSubset: "v1", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "peer cannot be set with serviceSubset", + }, + }, + "partition cannot be set with peer": { + input: &ServiceResolverRedirect{ + Partition: "default", + Peer: "p2", + Service: "foo", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "partition cannot be set with peer", + }, + }, + "peer cannot be set with datacenter": { + input: &ServiceResolverRedirect{ + Peer: "p2", + Service: "foo", + Datacenter: "dc2", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "peer cannot be set with datacenter", + "cross-datacenter and cross-partition redirect is not supported", + }, + }, + "serviceSubset defined without service": { + input: &ServiceResolverRedirect{ + ServiceSubset: "v1", + }, + consulMeta: common.ConsulMeta{ + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "serviceSubset defined without service", + }, + }, + "namespace defined without service": { + input: &ServiceResolverRedirect{ + Namespace: "ns1", + }, + consulMeta: common.ConsulMeta{ + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "namespace defined without service", + }, + }, + "partition defined without service": { + input: &ServiceResolverRedirect{ + Partition: "default", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "partition defined without service", + }, + }, + "peer defined without service": { + input: &ServiceResolverRedirect{ + Peer: "p2", + }, + consulMeta: common.ConsulMeta{ + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "peer defined without service", + }, + }, + } + + path := field.NewPath("spec.redirect") + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + errList := testCase.input.validate(path, testCase.consulMeta) + compareErrorLists(t, testCase.expectedErrMsgs, errList) + }) + } +} + +func compareErrorLists(t *testing.T, expectedErrMsgs []string, errList field.ErrorList) { + if len(expectedErrMsgs) != 0 { + require.Equal(t, len(expectedErrMsgs), len(errList)) + for _, m := range expectedErrMsgs { + found := false + for _, e := range errList { + errMsg := e.ErrorBody() + if strings.Contains(errMsg, m) { + found = true + break + } + } + require.Equal(t, true, found) + } + } else { + require.Equal(t, 0, len(errList)) + } +} + +func TestServiceResolverFailover_ToConsul(t *testing.T) { + cases := map[string]struct { + Ours *ServiceResolverFailover + Exp *capi.ServiceResolverFailover + }{ + "nil": { + Ours: nil, + Exp: nil, + }, + "empty fields": { + Ours: &ServiceResolverFailover{}, + Exp: &capi.ServiceResolverFailover{}, + }, + "every field set": { + Ours: &ServiceResolverFailover{ + Service: "foo", + ServiceSubset: "v1", + Namespace: "ns1", + Datacenters: []string{"dc1"}, + Targets: []ServiceResolverFailoverTarget{ + { + Peer: "p2", + }, + }, + Policy: &FailoverPolicy{ + Mode: "sequential", + Regions: []string{"us-west-2"}, + }, + SamenessGroup: "sg1", + }, + Exp: &capi.ServiceResolverFailover{ + Service: "foo", + ServiceSubset: "v1", + Namespace: "ns1", + Datacenters: []string{"dc1"}, + Targets: []capi.ServiceResolverFailoverTarget{ + { + Peer: "p2", + }, + }, + Policy: &capi.ServiceResolverFailoverPolicy{ + Mode: "sequential", + Regions: []string{"us-west-2"}, + }, + SamenessGroup: "sg1", + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual := c.Ours.toConsul() + require.Equal(t, c.Exp, actual) + }) + } +} + +func TestServiceResolverFailover_Validate(t *testing.T) { + cases := map[string]struct { + input *ServiceResolverFailover + consulMeta common.ConsulMeta + expectedErrMsgs []string + }{ + "empty failover": { + input: &ServiceResolverFailover{}, + consulMeta: common.ConsulMeta{}, + expectedErrMsgs: []string{ + "service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", + }, + }, + "cross-datacenter failover is only supported in the default partition": { + input: &ServiceResolverFailover{ + Datacenters: []string{"dc2"}, + Service: "foo", + }, + consulMeta: common.ConsulMeta{ + Partition: "p2", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "cross-datacenter failover is only supported in the default partition", + }, + }, + "samenessGroup cannot be set with datacenters": { + input: &ServiceResolverFailover{ + Service: "foo", + Datacenters: []string{"dc2"}, + SamenessGroup: "sg2", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "samenessGroup cannot be set with datacenters", + }, + }, + "samenessGroup cannot be set with serviceSubset": { + input: &ServiceResolverFailover{ + ServiceSubset: "v1", + Service: "foo", + SamenessGroup: "sg2", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "samenessGroup cannot be set with serviceSubset", + }, + }, + "samenessGroup cannot be set with targets": { + input: &ServiceResolverFailover{ + Targets: []ServiceResolverFailoverTarget{ + { + Peer: "p2", + }, + }, + SamenessGroup: "sg2", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "samenessGroup cannot be set with targets", + }, + }, + "targets cannot be set with datacenters": { + input: &ServiceResolverFailover{ + Targets: []ServiceResolverFailoverTarget{ + { + Peer: "p2", + }, + }, + Datacenters: []string{"dc1"}, + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "targets cannot be set with datacenters", + }, + }, + "targets cannot be set with serviceSubset or service": { + input: &ServiceResolverFailover{ + Targets: []ServiceResolverFailoverTarget{ + { + Peer: "p2", + }, + }, + ServiceSubset: "v1", + Service: "foo", + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "targets cannot be set with serviceSubset", + "targets cannot be set with service", + }, + }, + "target.peer cannot be set with target.serviceSubset": { + input: &ServiceResolverFailover{ + Targets: []ServiceResolverFailoverTarget{ + { + Peer: "p2", + ServiceSubset: "v1", + }, + }, + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "target.peer cannot be set with target.serviceSubset", + }, + }, + "target.partition cannot be set with target.peer": { + input: &ServiceResolverFailover{ + Targets: []ServiceResolverFailoverTarget{ + { + Peer: "p2", + Partition: "partition2", + }, + }, + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "target.partition cannot be set with target.peer", + }, + }, + "target.peer cannot be set with target.datacenter": { + input: &ServiceResolverFailover{ + Targets: []ServiceResolverFailoverTarget{ + { + Peer: "p2", + Datacenter: "dc2", + }, + }, + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "target.peer cannot be set with target.datacenter", + }, + }, + "target.partition cannot be set with target.datacenter": { + input: &ServiceResolverFailover{ + Targets: []ServiceResolverFailoverTarget{ + { + Partition: "p2", + Datacenter: "dc2", + }, + }, + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "target.partition cannot be set with target.datacenter", + }, + }, + "found empty datacenter": { + input: &ServiceResolverFailover{ + Datacenters: []string{""}, + }, + consulMeta: common.ConsulMeta{ + Partition: "default", + PartitionsEnabled: true, + }, + expectedErrMsgs: []string{ + "found empty datacenter", + }, + }, + } + + path := field.NewPath("spec.redirect") + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + errList := testCase.input.validate(path, testCase.consulMeta) + compareErrorLists(t, testCase.expectedErrMsgs, errList) + }) + } +} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index bae83ac7b9..69084724a9 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -91,6 +91,10 @@ spec: type: string type: array type: object + samenessGroup: + description: SamenessGroup is the name of the sameness group + to try during failover. + type: string service: description: Service is the service to resolve instead of the default as the failover group of instances during failover. @@ -244,6 +248,10 @@ spec: description: Peer is the name of the cluster peer to resolve the service from instead of the current one. type: string + samenessGroup: + description: SamenessGroup is the name of the sameness group to + resolve the service from instead of the current one. + type: string service: description: Service is a service to resolve instead of the current service. diff --git a/control-plane/go.mod b/control-plane/go.mod index 5307056ce3..06ac31d7eb 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -6,12 +6,13 @@ require ( github.com/deckarep/golang-set v1.7.1 github.com/fsnotify/fsnotify v1.5.4 github.com/go-logr/logr v0.4.0 - github.com/google/go-cmp v0.5.7 + github.com/google/go-cmp v0.5.8 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.10.1-0.20230418163148-eb9f671eafae + github.com/hashicorp/consul/api v1.10.1-0.20230427155444-391ed069c461 github.com/hashicorp/consul/sdk v0.13.1 + github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f github.com/hashicorp/go-hclog v1.2.2 github.com/hashicorp/go-multierror v1.1.1 @@ -104,6 +105,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -130,12 +132,12 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/api v0.43.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index c045aba7bd..2ad5716991 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -299,8 +299,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -353,8 +353,10 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20230418163148-eb9f671eafae h1:lYnO52QxlfATRZ1Vo8tQV+lFns7rZ4iAbbi3JN4ZAQw= -github.com/hashicorp/consul/api v1.10.1-0.20230418163148-eb9f671eafae/go.mod h1:f8zVJwBcLdr1IQnfdfszjUM0xzp31Zl3bpws3pL9uFM= +github.com/hashicorp/consul/api v1.10.1-0.20230424202255-e47f3216e51b h1:6JQAVFzqHzB51SP55BOLoDsw6sVD2WGkNSOoxI1hVZ4= +github.com/hashicorp/consul/api v1.10.1-0.20230424202255-e47f3216e51b/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= +github.com/hashicorp/consul/api v1.10.1-0.20230427155444-391ed069c461 h1:cbsTR88ShbvcRMqLU8K0atm4GmRr8UH4x4jX4e12RYE= +github.com/hashicorp/consul/api v1.10.1-0.20230427155444-391ed069c461/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -363,6 +365,8 @@ github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXE 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= +github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY= +github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -530,6 +534,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= +github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= @@ -765,6 +771,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -943,8 +951,9 @@ golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1027,11 +1036,10 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= 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= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= From 8c22f833001f2067376fbc65202cac3f74b6391d Mon Sep 17 00:00:00 2001 From: malizz Date: Fri, 28 Apr 2023 14:55:58 -0700 Subject: [PATCH 147/592] add sameness group to source intention (#2097) * add sameness group to source intention * add more test coverage * add comment on metaValueMaxLength variable * fix comment lint issue --- .../templates/crd-exportedservices.yaml | 3 +- .../templates/crd-serviceintentions.yaml | 4 + .../api/v1alpha1/serviceintentions_types.go | 75 +++++--- .../v1alpha1/serviceintentions_types_test.go | 175 +++++++++++++++++- control-plane/api/v1alpha1/shared_types.go | 3 + ...consul.hashicorp.com_exportedservices.yaml | 3 +- ...onsul.hashicorp.com_serviceintentions.yaml | 4 + control-plane/go.sum | 2 - 8 files changed, 239 insertions(+), 30 deletions(-) diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 073e081d0c..591500cb12 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -76,7 +76,8 @@ spec: the service to. type: string peer: - description: Peer is the name of the peer to export the service to. + description: Peer is the name of the peer to export the + service to. type: string samenessGroup: description: SamenessGroup is the name of the sameness diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index ede8ae09b0..67998f776c 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -185,6 +185,10 @@ spec: type: object type: object type: array + samenessGroup: + description: SamenessGroup is the name of the sameness group, + if applicable. + type: string type: object type: array type: object diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index a066b4e84f..fd18ecd3fe 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -5,6 +5,7 @@ package v1alpha1 import ( "encoding/json" + "fmt" "net/http" "strings" @@ -85,6 +86,8 @@ type SourceIntention struct { Peer string `json:"peer,omitempty"` // Partition is the Admin Partition for the Name parameter. Partition string `json:"partition,omitempty"` + // SamenessGroup is the name of the sameness group, if applicable. + SamenessGroup string `json:"samenessGroup,omitempty"` // Action is required for an L4 intention, and should be set to one of // "allow" or "deny" for the action that should be taken if this intention matches a request. Action IntentionAction `json:"action,omitempty"` @@ -272,10 +275,10 @@ func (in *ServiceIntentions) Validate(consulMeta common.ConsulMeta) error { } else { errs = append(errs, source.Permissions.validate(path.Child("sources").Index(i))...) } + errs = append(errs, source.validate(path.Child("sources").Index(i), consulMeta.PartitionsEnabled)...) } errs = append(errs, in.validateNamespaces(consulMeta.NamespacesEnabled)...) - errs = append(errs, in.validateSourcePeerAndPartitions(consulMeta.PartitionsEnabled)...) if len(errs) > 0 { return apierrors.NewInvalid( @@ -285,6 +288,46 @@ func (in *ServiceIntentions) Validate(consulMeta common.ConsulMeta) error { return nil } +func (in *SourceIntention) validate(path *field.Path, partitionsEnabled bool) field.ErrorList { + var errs field.ErrorList + + if in.Name == "" { + errs = append(errs, field.Required(path.Child("name"), "name is required.")) + } + + if strings.Contains(in.Partition, WildcardSpecifier) { + errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, "partition cannot use or contain wildcard '*'")) + } + if strings.Contains(in.Peer, WildcardSpecifier) { + errs = append(errs, field.Invalid(path.Child("peer"), in.Peer, "peer cannot use or contain wildcard '*'")) + } + if strings.Contains(in.SamenessGroup, WildcardSpecifier) { + errs = append(errs, field.Invalid(path.Child("samenessgroup"), in.SamenessGroup, "samenessgroup cannot use or contain wildcard '*'")) + } + + if in.Partition != "" && !partitionsEnabled { + errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, `Consul Enterprise Admin Partitions must be enabled to set source.partition`)) + } + + if in.Peer != "" && in.Partition != "" { + errs = append(errs, field.Invalid(path, *in, "cannot set peer and partition at the same time.")) + } + + if in.SamenessGroup != "" && in.Partition != "" { + errs = append(errs, field.Invalid(path, *in, "cannot set samenessgroup and partition at the same time.")) + } + + if in.SamenessGroup != "" && in.Peer != "" { + errs = append(errs, field.Invalid(path, *in, "cannot set samenessgroup and peer at the same time.")) + } + + if len(in.Description) > metaValueMaxLength { + errs = append(errs, field.Invalid(path, "", fmt.Sprintf("description exceeds maximum length %d", metaValueMaxLength))) + } + + return errs +} + // DefaultNamespaceFields sets the namespace field on spec.destination to their default values if namespaces are enabled. func (in *ServiceIntentions) DefaultNamespaceFields(consulMeta common.ConsulMeta) { // If namespaces are enabled we want to set the destination namespace field to it's @@ -313,13 +356,14 @@ func (in *SourceIntention) toConsul() *capi.SourceIntention { return nil } return &capi.SourceIntention{ - Name: in.Name, - Namespace: in.Namespace, - Partition: in.Partition, - Peer: in.Peer, - Action: in.Action.toConsul(), - Permissions: in.Permissions.toConsul(), - Description: in.Description, + Name: in.Name, + Namespace: in.Namespace, + Partition: in.Partition, + Peer: in.Peer, + SamenessGroup: in.SamenessGroup, + Action: in.Action.toConsul(), + Permissions: in.Permissions.toConsul(), + Description: in.Description, } } @@ -461,21 +505,6 @@ func (in *ServiceIntentions) validateNamespaces(namespacesEnabled bool) field.Er return errs } -func (in *ServiceIntentions) validateSourcePeerAndPartitions(partitionsEnabled bool) field.ErrorList { - var errs field.ErrorList - path := field.NewPath("spec") - for i, source := range in.Spec.Sources { - if source.Partition != "" && !partitionsEnabled { - errs = append(errs, field.Invalid(path.Child("sources").Index(i).Child("partition"), source.Partition, `Consul Enterprise Admin Partitions must be enabled to set source.partition`)) - } - - if source.Peer != "" && source.Partition != "" { - errs = append(errs, field.Invalid(path.Child("sources").Index(i), source, `Both source.peer and source.partition cannot be set.`)) - } - } - return errs -} - func (in IntentionAction) validate(path *field.Path) *field.Error { actions := []string{"allow", "deny"} if !sliceContains(actions, string(in)) { diff --git a/control-plane/api/v1alpha1/serviceintentions_types_test.go b/control-plane/api/v1alpha1/serviceintentions_types_test.go index 99b391039c..df0100e75a 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_types_test.go @@ -4,6 +4,7 @@ package v1alpha1 import ( + "strings" "testing" "time" @@ -269,6 +270,13 @@ func TestServiceIntentions_ToConsul(t *testing.T) { Action: "deny", Description: "disallow access from namespace not-test", }, + { + Name: "*", + Namespace: "ns1", + SamenessGroup: "sg2", + Action: "deny", + Description: "disallow access from namespace ns1", + }, { Name: "svc-2", Namespace: "bar", @@ -322,6 +330,13 @@ func TestServiceIntentions_ToConsul(t *testing.T) { Action: "deny", Description: "disallow access from namespace not-test", }, + { + Name: "*", + Namespace: "ns1", + SamenessGroup: "sg2", + Action: "deny", + Description: "disallow access from namespace ns1", + }, { Name: "svc-2", Namespace: "bar", @@ -602,6 +617,8 @@ func TestServiceIntentions_DefaultNamespaceFields(t *testing.T) { } func TestServiceIntentions_Validate(t *testing.T) { + longDescription := strings.Repeat("x", metaValueMaxLength+1) + cases := map[string]struct { input *ServiceIntentions namespacesEnabled bool @@ -1124,6 +1141,54 @@ func TestServiceIntentions_Validate(t *testing.T) { `serviceintentions.consul.hashicorp.com "does-not-matter" is invalid: spec.sources[0]: Invalid value: "{\"name\":\"svc-2\",\"namespace\":\"bar\",\"action\":\"deny\",\"permissions\":[{\"action\":\"allow\",\"http\":{\"pathExact\":\"/bar\"}}]}": action and permissions are mutually exclusive and only one of them can be specified`, }, }, + "name not specified": { + input: &ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "dest-service", + Namespace: "namespace", + }, + Sources: SourceIntentions{ + { + Namespace: "bar", + Action: "deny", + }, + }, + }, + }, + namespacesEnabled: true, + expectedErrMsgs: []string{ + `serviceintentions.consul.hashicorp.com "does-not-matter" is invalid: spec.sources[0].name: Required value: name is required.`, + }, + }, + "description is too long": { + input: &ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "dest-service", + Namespace: "namespace", + }, + Sources: SourceIntentions{ + { + Name: "foo", + Namespace: "bar", + Action: "deny", + Description: longDescription, + }, + }, + }, + }, + namespacesEnabled: true, + expectedErrMsgs: []string{ + `serviceintentions.consul.hashicorp.com "does-not-matter" is invalid: spec.sources[0]: Invalid value: "": description exceeds maximum length 512`, + }, + }, "namespaces disabled: destination namespace specified": { input: &ServiceIntentions{ ObjectMeta: metav1.ObjectMeta{ @@ -1343,7 +1408,71 @@ func TestServiceIntentions_Validate(t *testing.T) { namespacesEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `spec.sources[0]: Invalid value: v1alpha1.SourceIntention{Name:"web", Namespace:"namespace-b", Peer:"peer-other", Partition:"partition-other", Action:"allow", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: Both source.peer and source.partition cannot be set.`, + `cannot set peer and partition at the same time.`, + }, + }, + "single source samenessgroup and partition specified": { + input: &ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "dest-service", + Namespace: "namespace-a", + }, + Sources: SourceIntentions{ + { + Name: "web", + Action: "allow", + Namespace: "namespace-b", + Partition: "partition-other", + SamenessGroup: "sg2", + }, + { + Name: "db", + Action: "deny", + Namespace: "namespace-c", + }, + }, + }, + }, + namespacesEnabled: true, + partitionsEnabled: true, + expectedErrMsgs: []string{ + `cannot set samenessgroup and partition at the same time.`, + }, + }, + "single source samenessgroup and peer specified": { + input: &ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "dest-service", + Namespace: "namespace-a", + }, + Sources: SourceIntentions{ + { + Name: "web", + Action: "allow", + Namespace: "namespace-b", + Peer: "p2", + SamenessGroup: "sg2", + }, + { + Name: "db", + Action: "deny", + Namespace: "namespace-c", + }, + }, + }, + }, + namespacesEnabled: true, + partitionsEnabled: true, + expectedErrMsgs: []string{ + `cannot set samenessgroup and peer at the same time.`, }, }, "multiple source peer and partition specified": { @@ -1377,8 +1506,48 @@ func TestServiceIntentions_Validate(t *testing.T) { namespacesEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `spec.sources[0]: Invalid value: v1alpha1.SourceIntention{Name:"web", Namespace:"namespace-b", Peer:"peer-other", Partition:"partition-other", Action:"allow", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: Both source.peer and source.partition cannot be set.`, - `spec.sources[1]: Invalid value: v1alpha1.SourceIntention{Name:"db", Namespace:"namespace-c", Peer:"peer-2", Partition:"partition-2", Action:"deny", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: Both source.peer and source.partition cannot be set.`, + `spec.sources[0]: Invalid value: v1alpha1.SourceIntention{Name:"web", Namespace:"namespace-b", Peer:"peer-other", Partition:"partition-other", SamenessGroup:"", Action:"allow", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: cannot set peer and partition at the same time.`, + `spec.sources[1]: Invalid value: v1alpha1.SourceIntention{Name:"db", Namespace:"namespace-c", Peer:"peer-2", Partition:"partition-2", SamenessGroup:"", Action:"deny", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: cannot set peer and partition at the same time.`, + }, + }, + "multiple errors: wildcard peer and partition and samenessgroup specified": { + input: &ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "dest-service", + Namespace: "namespace-a", + }, + Sources: SourceIntentions{ + { + Name: "web", + Action: "allow", + Namespace: "namespace-b", + Partition: "*", + }, + { + Name: "db", + Action: "deny", + Namespace: "namespace-c", + Peer: "*", + }, + { + Name: "db2", + Action: "deny", + Namespace: "namespace-d", + SamenessGroup: "*", + }, + }, + }, + }, + namespacesEnabled: true, + partitionsEnabled: true, + expectedErrMsgs: []string{ + `partition cannot use or contain wildcard '*'`, + `peer cannot use or contain wildcard '*'`, + `samenessgroup cannot use or contain wildcard '*'`, }, }, } diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 98eb3f1b20..9aa5e519d4 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -16,6 +16,9 @@ import ( // This file contains structs that are shared between multiple config entries. +// metaValueMaxLength is the maximum allowed string length of a metadata value. +const metaValueMaxLength = 512 + type MeshGatewayMode string // Expose describes HTTP paths to expose through Envoy outside of Connect. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index 84e523f50c..0b6b969856 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -72,7 +72,8 @@ spec: the service to. type: string peer: - description: Peer is the name of the peer to export the service to. + description: Peer is the name of the peer to export the + service to. type: string samenessGroup: description: SamenessGroup is the name of the sameness 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 3c80ea8933..35930a0e8a 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -181,6 +181,10 @@ spec: type: object type: object type: array + samenessGroup: + description: SamenessGroup is the name of the sameness group, + if applicable. + type: string type: object type: array type: object diff --git a/control-plane/go.sum b/control-plane/go.sum index 2ad5716991..66d96bdb84 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -353,8 +353,6 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af6526 github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20230424202255-e47f3216e51b h1:6JQAVFzqHzB51SP55BOLoDsw6sVD2WGkNSOoxI1hVZ4= -github.com/hashicorp/consul/api v1.10.1-0.20230424202255-e47f3216e51b/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= github.com/hashicorp/consul/api v1.10.1-0.20230427155444-391ed069c461 h1:cbsTR88ShbvcRMqLU8K0atm4GmRr8UH4x4jX4e12RYE= github.com/hashicorp/consul/api v1.10.1-0.20230427155444-391ed069c461/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= From bc693e6ca6b731e5b80fc888c642805bd4eba6ec Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 4 May 2023 14:55:14 -0400 Subject: [PATCH 148/592] security: update Go version to 1.20.4 (#2102) --- .changelog/2102.txt | 12 ++++++++++++ .go-version | 2 +- acceptance/go.mod | 10 +++++----- acceptance/go.sum | 17 +++++++++-------- cli/go.mod | 8 ++++---- cli/go.sum | 18 ++++++++---------- control-plane/cni/go.mod | 8 ++++---- control-plane/cni/go.sum | 15 ++++++++------- control-plane/go.mod | 8 ++++---- control-plane/go.sum | 16 ++++++++-------- 10 files changed, 63 insertions(+), 51 deletions(-) create mode 100644 .changelog/2102.txt diff --git a/.changelog/2102.txt b/.changelog/2102.txt new file mode 100644 index 0000000000..59d120f747 --- /dev/null +++ b/.changelog/2102.txt @@ -0,0 +1,12 @@ +```release-note:security +Upgrade to use Go 1.20.4. +This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), +[CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), +[CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and +[CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). +Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41721 +](https://github.com/advisories/GHSA-fxg5-wq6x-vr4w +), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 +](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h +.) +``` diff --git a/.go-version b/.go-version index 0044d6cb96..0bd54efd31 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.20.1 +1.20.4 diff --git a/acceptance/go.mod b/acceptance/go.mod index 503ca5333c..0693467102 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -83,11 +83,11 @@ require ( github.com/urfave/cli v1.22.2 // indirect go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect @@ -101,4 +101,4 @@ require ( k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect sigs.k8s.io/yaml v1.2.0 // indirect -) \ No newline at end of file +) diff --git a/acceptance/go.sum b/acceptance/go.sum index 3301c3a7da..a2c392ed4e 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -803,8 +803,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -902,13 +902,14 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -918,8 +919,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1213,4 +1214,4 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3 sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= \ No newline at end of file +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/cli/go.mod b/cli/go.mod index adc0309466..9633fd5dd2 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -17,7 +17,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.8.0 - golang.org/x/text v0.5.0 + golang.org/x/text v0.7.0 helm.sh/helm/v3 v3.9.4 k8s.io/api v0.25.0 k8s.io/apiextensions-apiserver v0.25.0 @@ -165,11 +165,11 @@ require ( go.mongodb.org/mongo-driver v1.11.1 // indirect go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect - golang.org/x/net v0.4.0 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/term v0.3.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect diff --git a/cli/go.sum b/cli/go.sum index 6ba77d4281..b5ee614f52 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -443,8 +443,6 @@ github.com/hashicorp/consul/envoyextensions v0.1.2 h1:PvPqJ/td3UpOeIKQl5ycFPUy46 github.com/hashicorp/consul/envoyextensions v0.1.2/go.mod h1:N94DQQkgITiA40zuTQ/UdPOLAAWobgHfVT5u7wxE/aU= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= -github.com/hashicorp/consul/troubleshoot v0.0.0-20230217154305-8dab825c3640 h1:P81kThpSzUW2oERDMrLsiZE3OuilLo3/EQhtVQW5M+8= -github.com/hashicorp/consul/troubleshoot v0.0.0-20230217154305-8dab825c3640/go.mod h1:rskvju2tK8XvHYTAILHjO7lpV1/uViHs3Q3mg9Rkwlg= github.com/hashicorp/consul/troubleshoot v0.1.2 h1:c6uMTSt/qTMhK3e18nl4xW4j7JcANdQNHOEYhoXH1P8= github.com/hashicorp/consul/troubleshoot v0.1.2/go.mod h1:q35QOtN7K5kFLPm2SXHBDD+PzsuBekcqTZuuoOTzbWA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -967,8 +965,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1089,14 +1087,14 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1106,8 +1104,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index aabc1ceaf0..b594015392 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -30,11 +30,11 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index ec1e322cf3..845baf6231 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -287,8 +287,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ 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.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -339,20 +339,21 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/control-plane/go.mod b/control-plane/go.mod index 06ac31d7eb..4cf30f5816 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -28,7 +28,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.7.2 go.uber.org/zap v1.19.0 - golang.org/x/text v0.3.8 + golang.org/x/text v0.7.0 golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 gomodules.xyz/jsonpatch/v2 v2.2.0 k8s.io/api v0.22.2 @@ -133,11 +133,11 @@ require ( go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect google.golang.org/api v0.43.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 66d96bdb84..f49bc4b2d5 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -847,8 +847,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -950,13 +950,13 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -966,8 +966,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From c86f3d5e3738a8f38e4a5c0c6577a1aa61e34b04 Mon Sep 17 00:00:00 2001 From: malizz Date: Thu, 4 May 2023 14:33:01 -0700 Subject: [PATCH 149/592] Spatel/net 1646 add max ejection percent and base ejection time (#2064) * Add MaxEjectionPercent and BaseEjectionTime to servicedefaults * test with sister branch in consul repo * missed one * fix tag names * fix json tags and duration type * update test * generate yaml files and fix imports --------- Co-authored-by: Semir Patel --- .../bases/crds-oss/servicedefaults.yaml | 2 ++ .../consul/templates/crd-servicedefaults.yaml | 28 +++++++++++++++ .../api/v1alpha1/servicedefaults_types.go | 11 +++++- .../v1alpha1/servicedefaults_types_test.go | 36 +++++++++++++++++++ .../api/v1alpha1/zz_generated.deepcopy.go | 11 ++++++ .../consul.hashicorp.com_servicedefaults.yaml | 28 +++++++++++++++ 6 files changed, 115 insertions(+), 1 deletion(-) diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index d930439c67..1a6818f7fe 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -22,6 +22,8 @@ spec: interval: 1s maxFailures: 10 enforcing_consecutive_5xx: 60 + maxEjectionPercent: 100 + baseEjectionTime: 20s - name: "bar" limits: maxConnections: 5 diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 5c6ecc7476..320ef31508 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -274,6 +274,13 @@ spec: upstream proxy instances will be monitored for removal from the load balancing pool. properties: + baseEjectionTime: + description: The base time that a host is ejected for. + The real time is equal to the base time multiplied by + the number of times the host has been ejected and is + capped by max_ejection_time (Default 300s). Defaults + to 30000ms or 30s. + type: string enforcing_consecutive_5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status @@ -285,6 +292,13 @@ spec: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts to the pool. type: string + maxEjectionPercent: + description: The maximum % of an upstream cluster that + can be ejected due to outlier detection. Defaults to + 10% but will eject at least one host regardless of the + value. + format: int32 + type: integer maxFailures: description: MaxFailures is the count of consecutive failures that results in a host being removed from the pool. @@ -377,6 +391,13 @@ spec: how upstream proxy instances will be monitored for removal from the load balancing pool. properties: + baseEjectionTime: + description: The base time that a host is ejected for. + The real time is equal to the base time multiplied + by the number of times the host has been ejected and + is capped by max_ejection_time (Default 300s). Defaults + to 30000ms or 30s. + type: string enforcing_consecutive_5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier @@ -389,6 +410,13 @@ spec: sweeps. Each sweep may remove hosts or return hosts to the pool. type: string + maxEjectionPercent: + description: The maximum % of an upstream cluster that + can be ejected due to outlier detection. Defaults + to 10% but will eject at least one host regardless + of the value. + format: int32 + type: integer maxFailures: description: MaxFailures is the count of consecutive failures that results in a host being removed from diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 304bce2db6..13455c4c8f 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -10,7 +10,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - capi "github.com/hashicorp/consul/api" "github.com/miekg/dns" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -19,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "github.com/hashicorp/consul-k8s/control-plane/api/common" + capi "github.com/hashicorp/consul/api" ) const ( @@ -184,6 +184,13 @@ type PassiveHealthCheck struct { // when an outlier status is detected through consecutive 5xx. // This setting can be used to disable ejection or to ramp it up slowly. EnforcingConsecutive5xx *uint32 `json:"enforcing_consecutive_5xx,omitempty"` + // The maximum % of an upstream cluster that can be ejected due to outlier detection. + // Defaults to 10% but will eject at least one host regardless of the value. + MaxEjectionPercent *uint32 `json:"maxEjectionPercent,omitempty"` + // The base time that a host is ejected for. The real time is equal to the base time + // multiplied by the number of times the host has been ejected and is capped by + // max_ejection_time (Default 300s). Defaults to 30000ms or 30s. + BaseEjectionTime *metav1.Duration `json:"baseEjectionTime,omitempty"` } type ServiceDefaultsDestination struct { @@ -448,6 +455,8 @@ func (in *PassiveHealthCheck) toConsul() *capi.PassiveHealthCheck { Interval: in.Interval.Duration, MaxFailures: in.MaxFailures, EnforcingConsecutive5xx: in.EnforcingConsecutive5xx, + MaxEjectionPercent: in.MaxEjectionPercent, + BaseEjectionTime: &in.BaseEjectionTime.Duration, } } diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 69b749decd..3201529e4e 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -90,6 +90,10 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, MaxFailures: uint32(20), EnforcingConsecutive5xx: pointer.Uint32(100), + MaxEjectionPercent: pointer.Uint32(10), + BaseEjectionTime: &metav1.Duration{ + Duration: 10 * time.Second, + }, }, MeshGateway: MeshGateway{ Mode: "local", @@ -115,6 +119,10 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, MaxFailures: uint32(10), EnforcingConsecutive5xx: pointer.Uint32(60), + MaxEjectionPercent: pointer.Uint32(20), + BaseEjectionTime: &metav1.Duration{ + Duration: 20 * time.Second, + }, }, MeshGateway: MeshGateway{ Mode: "remote", @@ -139,6 +147,10 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, MaxFailures: uint32(10), EnforcingConsecutive5xx: pointer.Uint32(60), + MaxEjectionPercent: pointer.Uint32(30), + BaseEjectionTime: &metav1.Duration{ + Duration: 30 * time.Second, + }, }, MeshGateway: MeshGateway{ Mode: "remote", @@ -215,6 +227,8 @@ func TestServiceDefaults_ToConsul(t *testing.T) { Interval: 2 * time.Second, MaxFailures: uint32(20), EnforcingConsecutive5xx: pointer.Uint32(100), + MaxEjectionPercent: pointer.Uint32(10), + BaseEjectionTime: pointer.Duration(10 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "local", @@ -238,6 +252,8 @@ func TestServiceDefaults_ToConsul(t *testing.T) { Interval: 2 * time.Second, MaxFailures: uint32(10), EnforcingConsecutive5xx: pointer.Uint32(60), + MaxEjectionPercent: pointer.Uint32(20), + BaseEjectionTime: pointer.Duration(20 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "remote", @@ -260,6 +276,8 @@ func TestServiceDefaults_ToConsul(t *testing.T) { Interval: 2 * time.Second, MaxFailures: uint32(10), EnforcingConsecutive5xx: pointer.Uint32(60), + MaxEjectionPercent: pointer.Uint32(30), + BaseEjectionTime: pointer.Duration(30 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "remote", @@ -386,6 +404,10 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, MaxFailures: uint32(20), EnforcingConsecutive5xx: pointer.Uint32(100), + MaxEjectionPercent: pointer.Uint32(10), + BaseEjectionTime: &metav1.Duration{ + Duration: 10 * time.Second, + }, }, MeshGateway: MeshGateway{ Mode: "local", @@ -410,6 +432,10 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, MaxFailures: uint32(10), EnforcingConsecutive5xx: pointer.Uint32(60), + MaxEjectionPercent: pointer.Uint32(20), + BaseEjectionTime: &metav1.Duration{ + Duration: 20 * time.Second, + }, }, MeshGateway: MeshGateway{ Mode: "remote", @@ -433,6 +459,10 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, MaxFailures: uint32(10), EnforcingConsecutive5xx: pointer.Uint32(60), + MaxEjectionPercent: pointer.Uint32(30), + BaseEjectionTime: &metav1.Duration{ + Duration: 30 * time.Second, + }, }, MeshGateway: MeshGateway{ Mode: "remote", @@ -504,6 +534,8 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { Interval: 2 * time.Second, MaxFailures: uint32(20), EnforcingConsecutive5xx: pointer.Uint32(100), + MaxEjectionPercent: pointer.Uint32(10), + BaseEjectionTime: pointer.Duration(10 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "local", @@ -526,6 +558,8 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { Interval: 2 * time.Second, MaxFailures: uint32(10), EnforcingConsecutive5xx: pointer.Uint32(60), + MaxEjectionPercent: pointer.Uint32(20), + BaseEjectionTime: pointer.Duration(20 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "remote", @@ -547,6 +581,8 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { Interval: 2 * time.Second, MaxFailures: uint32(10), EnforcingConsecutive5xx: pointer.Uint32(60), + MaxEjectionPercent: pointer.Uint32(30), + BaseEjectionTime: pointer.Duration(30 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "remote", diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 87d4bd9594..cedd78a1e5 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -7,6 +7,7 @@ package v1alpha1 import ( "encoding/json" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -924,6 +925,16 @@ func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) { *out = new(uint32) **out = **in } + if in.MaxEjectionPercent != nil { + in, out := &in.MaxEjectionPercent, &out.MaxEjectionPercent + *out = new(uint32) + **out = **in + } + if in.BaseEjectionTime != nil { + in, out := &in.BaseEjectionTime, &out.BaseEjectionTime + *out = new(v1.Duration) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PassiveHealthCheck. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 7744a8fe7a..8009b3816b 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -270,6 +270,13 @@ spec: upstream proxy instances will be monitored for removal from the load balancing pool. properties: + baseEjectionTime: + description: The base time that a host is ejected for. + The real time is equal to the base time multiplied by + the number of times the host has been ejected and is + capped by max_ejection_time (Default 300s). Defaults + to 30000ms or 30s. + type: string enforcing_consecutive_5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status @@ -281,6 +288,13 @@ spec: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts to the pool. type: string + maxEjectionPercent: + description: The maximum % of an upstream cluster that + can be ejected due to outlier detection. Defaults to + 10% but will eject at least one host regardless of the + value. + format: int32 + type: integer maxFailures: description: MaxFailures is the count of consecutive failures that results in a host being removed from the pool. @@ -373,6 +387,13 @@ spec: how upstream proxy instances will be monitored for removal from the load balancing pool. properties: + baseEjectionTime: + description: The base time that a host is ejected for. + The real time is equal to the base time multiplied + by the number of times the host has been ejected and + is capped by max_ejection_time (Default 300s). Defaults + to 30000ms or 30s. + type: string enforcing_consecutive_5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier @@ -385,6 +406,13 @@ spec: sweeps. Each sweep may remove hosts or return hosts to the pool. type: string + maxEjectionPercent: + description: The maximum % of an upstream cluster that + can be ejected due to outlier detection. Defaults + to 10% but will eject at least one host regardless + of the value. + format: int32 + type: integer maxFailures: description: MaxFailures is the count of consecutive failures that results in a host being removed from From d7bf922f53af63071e946c77537b1122e5cace49 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 8 May 2023 10:14:04 -0400 Subject: [PATCH 150/592] chore(ci): fix changelog action for non-main base branches (#2105) --- .github/workflows/changelog-checker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 1c41634fd3..ab76073047 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -36,7 +36,7 @@ jobs: changelog_file_path=".changelog/[_0-9]*.txt" fi - changelog_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/main")" | egrep ${changelog_file_path}) + changelog_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/${{ github.event.pull_request.base.ref }}")" | egrep -e "${changelog_file_path}")) # If we do not find a file in .changelog/, we fail the check if [ -z "$changelog_files" ]; then From cb7fd51e95520d65d9a6f3da61a90130cddbdb55 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 8 May 2023 10:59:57 -0400 Subject: [PATCH 151/592] chore(ci): fix backport assistant not finding new branches (#2113) --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index ad9b77b0d1..7f39b4e5a0 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -13,7 +13,7 @@ jobs: backport: if: github.event.pull_request.merged runs-on: ubuntu-latest - container: hashicorpdev/backport-assistant:0.3.0 + container: hashicorpdev/backport-assistant:0.3.3 steps: - name: Run Backport Assistant run: backport-assistant backport -merge-method=squash -gh-automerge From 4a2bd440ac99a8597e90e74a84986b9a6daabd3f Mon Sep 17 00:00:00 2001 From: Sujata Roy <61177855+20sr20@users.noreply.github.com> Date: Wed, 10 May 2023 11:52:40 -0700 Subject: [PATCH 152/592] Customizing Vault Version for WanFed Test (#2043) * Customizing Vault Version for WanFed Test * Modified * Changed according to the review comments * Removed the commented line * Vault server version type changed to String * changed back to VaultServerVersion type * Changing "VaultServerVersion" to type "String" --- acceptance/framework/config/config.go | 3 +++ acceptance/framework/flags/flags.go | 27 +++++++++++++-------- acceptance/framework/vault/vault_cluster.go | 9 ++++++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index 310955e8f8..c771e49653 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -52,6 +52,9 @@ type TestConfig struct { ConsulVersion *version.Version EnvoyImage string + VaultHelmChartVersion string + VaultServerVersion string + NoCleanupOnFailure bool DebugDirectory string diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index b8c315044b..ea58fda058 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -34,11 +34,13 @@ type TestFlags struct { flagEnableTransparentProxy bool - flagHelmChartVersion string - flagConsulImage string - flagConsulK8sImage string - flagConsulVersion string - flagEnvoyImage string + flagHelmChartVersion string + flagConsulImage string + flagConsulK8sImage string + flagConsulVersion string + flagEnvoyImage string + flagVaultHelmChartVersion string + flagVaultServerVersion string flagNoCleanupOnFailure bool @@ -72,6 +74,8 @@ func (t *TestFlags) init() { flag.StringVar(&t.flagConsulVersion, "consul-version", "", "The consul version used for all tests.") flag.StringVar(&t.flagHelmChartVersion, "helm-chart-version", config.HelmChartPath, "The helm chart used for all tests.") flag.StringVar(&t.flagEnvoyImage, "envoy-image", "", "The Envoy image to use for all tests.") + flag.StringVar(&t.flagVaultServerVersion, "vault-server-version", "", "The vault serverversion used for all tests.") + flag.StringVar(&t.flagVaultHelmChartVersion, "vault-helm-chart-version", "", "The Vault helm chart used for all tests.") flag.BoolVar(&t.flagEnableMultiCluster, "enable-multi-cluster", false, "If true, the tests that require multiple Kubernetes clusters will be run. "+ @@ -142,6 +146,7 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { // if the Version is empty consulVersion will be nil consulVersion, _ := version.NewVersion(t.flagConsulVersion) + //vaultserverVersion, _ := version.NewVersion(t.flagVaultServerVersion) return &config.TestConfig{ Kubeconfig: t.flagKubeconfig, @@ -166,11 +171,13 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { DisablePeering: t.flagDisablePeering, - HelmChartVersion: t.flagHelmChartVersion, - ConsulImage: t.flagConsulImage, - ConsulK8SImage: t.flagConsulK8sImage, - ConsulVersion: consulVersion, - EnvoyImage: t.flagEnvoyImage, + HelmChartVersion: t.flagHelmChartVersion, + ConsulImage: t.flagConsulImage, + ConsulK8SImage: t.flagConsulK8sImage, + ConsulVersion: consulVersion, + EnvoyImage: t.flagEnvoyImage, + VaultHelmChartVersion: t.flagVaultHelmChartVersion, + VaultServerVersion: t.flagVaultServerVersion, NoCleanupOnFailure: t.flagNoCleanupOnFailure, DebugDirectory: tempDir, diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index 26da56b646..e0030490c9 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -59,13 +59,20 @@ func NewVaultCluster(t *testing.T, ctx environment.TestContext, cfg *config.Test if cfg.EnablePodSecurityPolicies { values["global.psp.enable"] = "true" } + if cfg.VaultServerVersion != "" { + values["server.image.tag"] = cfg.VaultServerVersion + } + vaultHelmChartVersion := defaultVaultHelmChartVersion + if cfg.VaultHelmChartVersion != "" { + vaultHelmChartVersion = cfg.VaultHelmChartVersion + } helpers.MergeMaps(values, helmValues) vaultHelmOpts := &helm.Options{ SetValues: values, KubectlOptions: kopts, Logger: logger, - Version: defaultVaultHelmChartVersion, + Version: vaultHelmChartVersion, } helm.AddRepo(t, vaultHelmOpts, "hashicorp", "https://helm.releases.hashicorp.com") From ccb51c163bf7834fdac3c61e153b9f6ad481ab27 Mon Sep 17 00:00:00 2001 From: Hans Hasselberg Date: Thu, 11 May 2023 11:57:09 +0200 Subject: [PATCH 153/592] add config read command (#2078) * add config read command * add tests * lint * update docs * add changelog * fix linting errors * PR feedback --- .changelog/2078.txt | 3 + cli/cmd/config/command.go | 26 ++++ cli/cmd/config/read/command.go | 199 ++++++++++++++++++++++++++++ cli/cmd/config/read/command_test.go | 149 +++++++++++++++++++++ cli/commands.go | 12 ++ 5 files changed, 389 insertions(+) create mode 100644 .changelog/2078.txt create mode 100644 cli/cmd/config/command.go create mode 100644 cli/cmd/config/read/command.go create mode 100644 cli/cmd/config/read/command_test.go diff --git a/.changelog/2078.txt b/.changelog/2078.txt new file mode 100644 index 0000000000..2206de1128 --- /dev/null +++ b/.changelog/2078.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Add `consul-k8s config read` command that returns the helm configuration in yaml format. +``` diff --git a/cli/cmd/config/command.go b/cli/cmd/config/command.go new file mode 100644 index 0000000000..5e44677ff6 --- /dev/null +++ b/cli/cmd/config/command.go @@ -0,0 +1,26 @@ +package config + +import ( + "fmt" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/mitchellh/cli" +) + +// ConfigCommand provides a synopsis for the config subcommands (e.g. read). +type ConfigCommand struct { + *common.BaseCommand +} + +// Run prints out information about the subcommands. +func (c *ConfigCommand) Run([]string) int { + return cli.RunResultHelp +} + +func (c *ConfigCommand) Help() string { + return fmt.Sprintf("%s\n\nUsage: consul-k8s config ", c.Synopsis()) +} + +func (c *ConfigCommand) Synopsis() string { + return "Operate on configuration" +} diff --git a/cli/cmd/config/read/command.go b/cli/cmd/config/read/command.go new file mode 100644 index 0000000000..e2258bd013 --- /dev/null +++ b/cli/cmd/config/read/command.go @@ -0,0 +1,199 @@ +package read + +import ( + "errors" + "fmt" + "sync" + + "github.com/posener/complete" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/consul-k8s/cli/helm" + "helm.sh/helm/v3/pkg/action" + helmCLI "helm.sh/helm/v3/pkg/cli" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/yaml" +) + +const ( + flagNameKubeConfig = "kubeconfig" + flagNameKubeContext = "context" +) + +type ReadCommand struct { + *common.BaseCommand + + helmActionsRunner helm.HelmActionsRunner + + kubernetes kubernetes.Interface + + set *flag.Sets + + flagKubeConfig string + flagKubeContext string + + once sync.Once + help string +} + +func (c *ReadCommand) init() { + c.set = flag.NewSets() + + f := c.set.NewSet("Global Options") + f.StringVar(&flag.StringVar{ + Name: "kubeconfig", + Aliases: []string{"c"}, + Target: &c.flagKubeConfig, + Default: "", + Usage: "Path to kubeconfig file.", + }) + f.StringVar(&flag.StringVar{ + Name: "context", + Target: &c.flagKubeContext, + Default: "", + Usage: "Kubernetes context to use.", + }) + + c.help = c.set.Help() +} + +// Run checks the status of a Consul installation on Kubernetes. +func (c *ReadCommand) Run(args []string) int { + c.once.Do(c.init) + if c.helmActionsRunner == nil { + c.helmActionsRunner = &helm.ActionRunner{} + } + + c.Log.ResetNamed("config read") + defer common.CloseWithError(c.BaseCommand) + + if err := c.set.Parse(args); err != nil { + c.UI.Output(err.Error()) + return 1 + } + + if err := c.validateFlags(); err != nil { + c.UI.Output(err.Error()) + return 1 + } + + // helmCLI.New() will create a settings object which is used by the Helm Go SDK calls. + settings := helmCLI.New() + if c.flagKubeConfig != "" { + settings.KubeConfig = c.flagKubeConfig + } + if c.flagKubeContext != "" { + settings.KubeContext = c.flagKubeContext + } + + if err := c.setupKubeClient(settings); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + // Setup logger to stream Helm library logs. + var uiLogger = func(s string, args ...interface{}) { + logMsg := fmt.Sprintf(s, args...) + c.UI.Output(logMsg, terminal.WithLibraryStyle()) + } + + _, releaseName, namespace, err := c.helmActionsRunner.CheckForInstallations(&helm.CheckForInstallationsOptions{ + Settings: settings, + ReleaseName: common.DefaultReleaseName, + DebugLog: uiLogger, + }) + if err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if err := c.checkHelmInstallation(settings, uiLogger, releaseName, namespace); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + return 0 +} + +// validateFlags checks the command line flags and values for errors. +func (c *ReadCommand) validateFlags() error { + if len(c.set.Args()) > 0 { + return errors.New("should have no non-flag arguments") + } + return nil +} + +// AutocompleteFlags returns a mapping of supported flags and autocomplete +// options for this command. The map key for the Flags map should be the +// complete flag such as "-foo" or "--foo". +func (c *ReadCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), + fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, + } +} + +// AutocompleteArgs returns the argument predictor for this command. +// Since argument completion is not supported, this will return +// complete.PredictNothing. +func (c *ReadCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +// checkHelmInstallation uses the helm Go SDK to depict the status of a named release. This function then prints +// the version of the release, it's status (unknown, deployed, uninstalled, ...), and the overwritten values. +func (c *ReadCommand) checkHelmInstallation(settings *helmCLI.EnvSettings, uiLogger action.DebugLog, releaseName, namespace string) error { + // Need a specific action config to call helm status, where namespace comes from the previous call to list. + statusConfig := new(action.Configuration) + statusConfig, err := helm.InitActionConfig(statusConfig, namespace, settings, uiLogger) + if err != nil { + return err + } + + statuser := action.NewStatus(statusConfig) + rel, err := c.helmActionsRunner.GetStatus(statuser, releaseName) + if err != nil { + return fmt.Errorf("couldn't check for installations: %s", err) + } + + valuesYaml, err := yaml.Marshal(rel.Config) + if err != nil { + return err + } + c.UI.Output(string(valuesYaml)) + + return nil +} + +// setupKubeClient to use for non Helm SDK calls to the Kubernetes API The Helm SDK will use +// settings.RESTClientGetter for its calls as well, so this will use a consistent method to +// target the right cluster for both Helm SDK and non Helm SDK calls. +func (c *ReadCommand) setupKubeClient(settings *helmCLI.EnvSettings) error { + if c.kubernetes == nil { + restConfig, err := settings.RESTClientGetter().ToRESTConfig() + if err != nil { + c.UI.Output("Error retrieving Kubernetes authentication: %v", err, terminal.WithErrorStyle()) + return err + } + c.kubernetes, err = kubernetes.NewForConfig(restConfig) + if err != nil { + c.UI.Output("Error initializing Kubernetes client: %v", err, terminal.WithErrorStyle()) + return err + } + } + + return nil +} + +// Help returns a description of the command and how it is used. +func (c *ReadCommand) Help() string { + c.once.Do(c.init) + return c.Synopsis() + "\n\nUsage: consul-k8s config read [flags]\n\n" + c.help +} + +// Synopsis returns a one-line command summary. +func (c *ReadCommand) Synopsis() string { + return "Returns the helm config of a Consul installation on Kubernetes." +} diff --git a/cli/cmd/config/read/command_test.go b/cli/cmd/config/read/command_test.go new file mode 100644 index 0000000000..a3716cf3c1 --- /dev/null +++ b/cli/cmd/config/read/command_test.go @@ -0,0 +1,149 @@ +package read + +import ( + "bytes" + "context" + "errors" + "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/consul-k8s/cli/helm" + "github.com/hashicorp/go-hclog" + "github.com/posener/complete" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + helmRelease "helm.sh/helm/v3/pkg/release" + helmTime "helm.sh/helm/v3/pkg/time" + "k8s.io/client-go/kubernetes/fake" +) + +func TestConfigRead(t *testing.T) { + nowTime := helmTime.Now() + cases := map[string]struct { + messages []string + helmActionsRunner *helm.MockActionRunner + expectedReturnCode int + }{ + "empty config": { + messages: []string{"\n"}, + + helmActionsRunner: &helm.MockActionRunner{ + GetStatusFunc: func(status *action.Status, name string) (*helmRelease.Release, error) { + return &helmRelease.Release{ + Name: "consul", Namespace: "consul", + Info: &helmRelease.Info{LastDeployed: nowTime, Status: "READY"}, + Chart: &chart.Chart{Metadata: &chart.Metadata{Version: "1.0.0"}}, + Config: make(map[string]interface{})}, nil + }, + }, + expectedReturnCode: 0, + }, + "error": { + messages: []string{"error", "\n"}, + + helmActionsRunner: &helm.MockActionRunner{ + GetStatusFunc: func(status *action.Status, name string) (*helmRelease.Release, error) { + return nil, errors.New("error") + }, + }, + expectedReturnCode: 1, + }, + "some config": { + messages: []string{"global: \"true\"", "\n"}, + + helmActionsRunner: &helm.MockActionRunner{ + GetStatusFunc: func(status *action.Status, name string) (*helmRelease.Release, error) { + return &helmRelease.Release{ + Name: "consul", Namespace: "consul", + Info: &helmRelease.Info{LastDeployed: nowTime, Status: "READY"}, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Version: "1.0.0", + }, + }, + Config: map[string]interface{}{"global": "true"}, + }, nil + }, + }, + expectedReturnCode: 0, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + buf := new(bytes.Buffer) + c := getInitializedCommand(t, buf) + c.kubernetes = fake.NewSimpleClientset() + c.helmActionsRunner = tc.helmActionsRunner + returnCode := c.Run([]string{}) + require.Equal(t, tc.expectedReturnCode, returnCode) + output := buf.String() + for _, msg := range tc.messages { + require.Contains(t, output, msg) + } + }) + } +} + +func TestTaskCreateCommand_AutocompleteFlags(t *testing.T) { + t.Parallel() + cmd := getInitializedCommand(t, nil) + + predictor := cmd.AutocompleteFlags() + + // Test that we get the expected number of predictions + args := complete.Args{Last: "-"} + res := predictor.Predict(args) + + // Grab the list of flags from the Flag object + flags := make([]string, 0) + cmd.set.VisitSets(func(name string, set *cmnFlag.Set) { + set.VisitAll(func(flag *flag.Flag) { + flags = append(flags, fmt.Sprintf("-%s", flag.Name)) + }) + }) + + // Verify that there is a prediction for each flag associated with the command + assert.Equal(t, len(flags), len(res)) + assert.ElementsMatch(t, flags, res, "flags and predictions didn't match, make sure to add "+ + "new flags to the command AutoCompleteFlags function") +} + +func TestTaskCreateCommand_AutocompleteArgs(t *testing.T) { + cmd := getInitializedCommand(t, nil) + c := cmd.AutocompleteArgs() + assert.Equal(t, complete.PredictNothing, c) +} + +// getInitializedCommand sets up a command struct for tests. +func getInitializedCommand(t *testing.T, buf io.Writer) *ReadCommand { + t.Helper() + log := hclog.New(&hclog.LoggerOptions{ + Name: "cli", + Level: hclog.Info, + Output: os.Stdout, + }) + var ui terminal.UI + if buf != nil { + ui = terminal.NewUI(context.Background(), buf) + } else { + ui = terminal.NewBasicUI(context.Background()) + } + baseCommand := &common.BaseCommand{ + Log: log, + UI: ui, + } + + c := &ReadCommand{ + BaseCommand: baseCommand, + } + c.init() + return c +} diff --git a/cli/commands.go b/cli/commands.go index 1f62d4eafd..1c58e75cf8 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -6,6 +6,8 @@ package main import ( "context" + "github.com/hashicorp/consul-k8s/cli/cmd/config" + config_read "github.com/hashicorp/consul-k8s/cli/cmd/config/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" @@ -79,6 +81,16 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm BaseCommand: baseCommand, }, nil }, + "config": func() (cli.Command, error) { + return &config.ConfigCommand{ + BaseCommand: baseCommand, + }, nil + }, + "config read": func() (cli.Command, error) { + return &config_read.ReadCommand{ + BaseCommand: baseCommand, + }, nil + }, "troubleshoot": func() (cli.Command, error) { return &troubleshoot.TroubleshootCommand{ BaseCommand: baseCommand, From bd16ab83383dbec5f8db680f39d85edd66b25b62 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Thu, 11 May 2023 09:39:18 -0500 Subject: [PATCH 154/592] Update CRDs for Permissive mTLS (#2100) * Add mutualTLSMode to service-defaults and proxy-defaults * Add allowEnablingPermisiveMutualTLS to mesh config entry --- .changelog/2100.txt | 3 ++ charts/consul/templates/crd-meshes.yaml | 5 ++++ .../consul/templates/crd-proxydefaults.yaml | 12 ++++++++ .../consul/templates/crd-servicedefaults.yaml | 12 ++++++++ control-plane/api/v1alpha1/mesh_types.go | 14 +++++---- control-plane/api/v1alpha1/mesh_types_test.go | 4 +++ .../api/v1alpha1/proxydefaults_types.go | 15 ++++++++++ .../api/v1alpha1/proxydefaults_types_test.go | 15 ++++++++++ .../api/v1alpha1/servicedefaults_types.go | 15 ++++++++++ .../v1alpha1/servicedefaults_types_test.go | 16 ++++++++++ control-plane/api/v1alpha1/shared_types.go | 29 +++++++++++++++++++ .../bases/consul.hashicorp.com_meshes.yaml | 5 ++++ .../consul.hashicorp.com_proxydefaults.yaml | 12 ++++++++ .../consul.hashicorp.com_servicedefaults.yaml | 12 ++++++++ 14 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 .changelog/2100.txt diff --git a/.changelog/2100.txt b/.changelog/2100.txt new file mode 100644 index 0000000000..4fece0991c --- /dev/null +++ b/.changelog/2100.txt @@ -0,0 +1,3 @@ +```release-note:feature +crd: Add `mutualTLSMode` to the ProxyDefaults and ServiceDefaults CRDs and `allowEnablingPermissiveMutualTLS` to the Mesh CRD to support configuring permissive mutual TLS. +``` diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index 2e33eb9653..0710d41280 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -55,6 +55,11 @@ spec: spec: description: MeshSpec defines the desired state of Mesh. properties: + allowEnablingPermissiveMutualTLS: + description: AllowEnablingPermissiveMutualTLS must be true in order + to allow setting MutualTLSMode=permissive in either service-defaults + or proxy-defaults. + type: boolean http: description: HTTP defines the HTTP configuration for the service mesh. properties: diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index f72d1c7cea..362672c1c1 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -180,6 +180,18 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string + mutualTLSMode: + description: 'MutualTLSMode controls whether mutual TLS is required + for all incoming connections when transparent proxy is enabled. + This can be set to "permissive" or "strict". "strict" is the default + which requires mutual TLS for incoming connections. In the insecure + "permissive" mode, connections to the sidecar proxy public listener + port require mutual TLS, but connections to the service port do + not require mutual TLS and are proxied to the application unmodified. + Note: Intentions are not enforced for non-mTLS connections. To keep + your services secure, we recommend using "strict" mode whenever + possible and enabling "permissive" mode only when necessary.' + type: string transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 320ef31508..36900d1bda 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -171,6 +171,18 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string + mutualTLSMode: + description: 'MutualTLSMode controls whether mutual TLS is required + for all incoming connections when transparent proxy is enabled. + This can be set to "permissive" or "strict". "strict" is the default + which requires mutual TLS for incoming connections. In the insecure + "permissive" mode, connections to the sidecar proxy public listener + port require mutual TLS, but connections to the service port do + not require mutual TLS and are proxied to the application unmodified. + Note: Intentions are not enforced for non-mTLS connections. To keep + your services secure, we recommend using "strict" mode whenever + possible and enabling "permissive" mode only when necessary.' + type: string protocol: description: Protocol sets the protocol of the service. This is used by Connect proxies for things like observability features and to diff --git a/control-plane/api/v1alpha1/mesh_types.go b/control-plane/api/v1alpha1/mesh_types.go index 9a2df631f2..162132a47a 100644 --- a/control-plane/api/v1alpha1/mesh_types.go +++ b/control-plane/api/v1alpha1/mesh_types.go @@ -51,6 +51,9 @@ type MeshList struct { type MeshSpec struct { // TransparentProxy controls the configuration specific to proxies in "transparent" mode. Added in v1.10.0. TransparentProxy TransparentProxyMeshConfig `json:"transparentProxy,omitempty"` + // AllowEnablingPermissiveMutualTLS must be true in order to allow setting + // MutualTLSMode=permissive in either service-defaults or proxy-defaults. + AllowEnablingPermissiveMutualTLS bool `json:"allowEnablingPermissiveMutualTLS,omitempty"` // TLS defines the TLS configuration for the service mesh. TLS *MeshTLSConfig `json:"tls,omitempty"` // HTTP defines the HTTP configuration for the service mesh. @@ -192,11 +195,12 @@ func (in *Mesh) SetLastSyncedTime(time *metav1.Time) { func (in *Mesh) ToConsul(datacenter string) capi.ConfigEntry { return &capi.MeshConfigEntry{ - TransparentProxy: in.Spec.TransparentProxy.toConsul(), - TLS: in.Spec.TLS.toConsul(), - HTTP: in.Spec.HTTP.toConsul(), - Peering: in.Spec.Peering.toConsul(), - Meta: meta(datacenter), + TransparentProxy: in.Spec.TransparentProxy.toConsul(), + AllowEnablingPermissiveMutualTLS: in.Spec.AllowEnablingPermissiveMutualTLS, + TLS: in.Spec.TLS.toConsul(), + HTTP: in.Spec.HTTP.toConsul(), + Peering: in.Spec.Peering.toConsul(), + Meta: meta(datacenter), } } diff --git a/control-plane/api/v1alpha1/mesh_types_test.go b/control-plane/api/v1alpha1/mesh_types_test.go index e20ce19d47..f2ea714f60 100644 --- a/control-plane/api/v1alpha1/mesh_types_test.go +++ b/control-plane/api/v1alpha1/mesh_types_test.go @@ -48,6 +48,7 @@ func TestMesh_MatchesConsul(t *testing.T) { TransparentProxy: TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + AllowEnablingPermissiveMutualTLS: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -72,6 +73,7 @@ func TestMesh_MatchesConsul(t *testing.T) { TransparentProxy: capi.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + AllowEnablingPermissiveMutualTLS: true, TLS: &capi.MeshTLSConfig{ Incoming: &capi.MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -148,6 +150,7 @@ func TestMesh_ToConsul(t *testing.T) { TransparentProxy: TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + AllowEnablingPermissiveMutualTLS: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -172,6 +175,7 @@ func TestMesh_ToConsul(t *testing.T) { TransparentProxy: capi.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + AllowEnablingPermissiveMutualTLS: true, TLS: &capi.MeshTLSConfig{ Incoming: &capi.MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index f83b12ecfe..1100cd107a 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -67,6 +67,17 @@ type ProxyDefaultsSpec struct { // Note: This cannot be set using the CRD and should be set using annotations on the // services that are part of the mesh. TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"` + // MutualTLSMode controls whether mutual TLS is required for all incoming + // connections when transparent proxy is enabled. This can be set to + // "permissive" or "strict". "strict" is the default which requires mutual + // TLS for incoming connections. In the insecure "permissive" mode, + // connections to the sidecar proxy public listener port require mutual + // TLS, but connections to the service port do not require mutual TLS and + // are proxied to the application unmodified. Note: Intentions are not + // enforced for non-mTLS connections. To keep your services secure, we + // recommend using "strict" mode whenever possible and enabling + // "permissive" mode only when necessary. + MutualTLSMode MutualTLSMode `json:"mutualTLSMode,omitempty"` // Config is an arbitrary map of configuration values used by Connect proxies. // Any values that your proxy allows can be configured globally here. // Supports JSON config values. See https://www.consul.io/docs/connect/proxies/envoy#configuration-formatting @@ -174,6 +185,7 @@ func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry { Expose: in.Spec.Expose.toConsul(), Config: consulConfig, TransparentProxy: in.Spec.TransparentProxy.toConsul(), + MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), AccessLogs: in.Spec.AccessLogs.toConsul(), EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), FailoverPolicy: in.Spec.FailoverPolicy.toConsul(), @@ -201,6 +213,9 @@ func (in *ProxyDefaults) Validate(_ common.ConsulMeta) error { if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil { allErrs = append(allErrs, err) } + if err := in.Spec.MutualTLSMode.validate(); err != nil { + allErrs = append(allErrs, field.Invalid(path.Child("mutualTLSMode"), in.Spec.MutualTLSMode, err.Error())) + } if err := in.Spec.Mode.validate(path.Child("mode")); err != nil { allErrs = append(allErrs, err) } diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 16642ad277..07f894f322 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -74,6 +74,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: MutualTLSModePermissive, AccessLogs: &AccessLogs{ Enabled: true, DisableListenerLogs: true, @@ -129,6 +130,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: capi.MutualTLSModePermissive, AccessLogs: &capi.AccessLogsConfig{ Enabled: true, DisableListenerLogs: true, @@ -292,6 +294,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: MutualTLSModeStrict, AccessLogs: &AccessLogs{ Enabled: true, DisableListenerLogs: true, @@ -348,6 +351,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: capi.MutualTLSModeStrict, AccessLogs: &capi.AccessLogsConfig{ Enabled: true, DisableListenerLogs: true, @@ -497,6 +501,17 @@ func TestProxyDefaults_Validate(t *testing.T) { }, expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode", }, + "mutualTLSMode": { + input: &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + MutualTLSMode: MutualTLSMode("asdf"), + }, + }, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.mutualTLSMode: Invalid value: "asdf": Must be one of "", "strict", or "permissive".`, + }, "accessLogs.type": { input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 13455c4c8f..cfb8865bdf 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -73,6 +73,17 @@ type ServiceDefaultsSpec struct { // Note: This cannot be set using the CRD and should be set using annotations on the // services that are part of the mesh. TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"` + // MutualTLSMode controls whether mutual TLS is required for all incoming + // connections when transparent proxy is enabled. This can be set to + // "permissive" or "strict". "strict" is the default which requires mutual + // TLS for incoming connections. In the insecure "permissive" mode, + // connections to the sidecar proxy public listener port require mutual + // TLS, but connections to the service port do not require mutual TLS and + // are proxied to the application unmodified. Note: Intentions are not + // enforced for non-mTLS connections. To keep your services secure, we + // recommend using "strict" mode whenever possible and enabling + // "permissive" mode only when necessary. + MutualTLSMode MutualTLSMode `json:"mutualTLSMode,omitempty"` // MeshGateway controls the default mesh gateway configuration for this service. MeshGateway MeshGateway `json:"meshGateway,omitempty"` // Expose controls the default expose path configuration for Envoy. @@ -286,6 +297,7 @@ func (in *ServiceDefaults) ToConsul(datacenter string) capi.ConfigEntry { Expose: in.Spec.Expose.toConsul(), ExternalSNI: in.Spec.ExternalSNI, TransparentProxy: in.Spec.TransparentProxy.toConsul(), + MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), UpstreamConfig: in.Spec.UpstreamConfig.toConsul(), Destination: in.Spec.Destination.toConsul(), Meta: meta(datacenter), @@ -313,6 +325,9 @@ func (in *ServiceDefaults) Validate(consulMeta common.ConsulMeta) error { if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil { allErrs = append(allErrs, err) } + if err := in.Spec.MutualTLSMode.validate(); err != nil { + allErrs = append(allErrs, field.Invalid(path.Child("mutualTLSMode"), in.Spec.MutualTLSMode, err.Error())) + } if err := in.Spec.Mode.validate(path.Child("mode")); err != nil { allErrs = append(allErrs, err) } diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 3201529e4e..0a5a59e4d1 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -70,6 +70,7 @@ func TestServiceDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: MutualTLSModePermissive, UpstreamConfig: &Upstreams{ Defaults: &Upstream{ Name: "upstream-default", @@ -209,6 +210,7 @@ func TestServiceDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: capi.MutualTLSModePermissive, UpstreamConfig: &capi.UpstreamConfiguration{ Defaults: &capi.UpstreamConfig{ Name: "upstream-default", @@ -385,6 +387,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: MutualTLSModeStrict, UpstreamConfig: &Upstreams{ Defaults: &Upstream{ Name: "upstream-default", @@ -517,6 +520,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, + MutualTLSMode: capi.MutualTLSModeStrict, UpstreamConfig: &capi.UpstreamConfiguration{ Defaults: &capi.UpstreamConfig{ Name: "upstream-default", @@ -716,6 +720,7 @@ func TestServiceDefaults_Validate(t *testing.T) { MeshGateway: MeshGateway{ Mode: "remote", }, + MutualTLSMode: MutualTLSModePermissive, Expose: Expose{ Checks: false, Paths: []ExposePath{ @@ -851,6 +856,17 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: "servicedefaults.consul.hashicorp.com \"my-service\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port", }, + "mutualTLSMode": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + MutualTLSMode: MutualTLSMode("asdf"), + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.mutualTLSMode: Invalid value: "asdf": Must be one of "", "strict", or "permissive".`, + }, "mode": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 9aa5e519d4..aa19c339da 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -58,6 +58,35 @@ type TransparentProxy struct { DialedDirectly bool `json:"dialedDirectly,omitempty"` } +type MutualTLSMode string + +const ( + // MutualTLSModeDefault represents no specific mode and should + // be used to indicate that a different layer of the configuration + // chain should take precedence. + MutualTLSModeDefault MutualTLSMode = "" + + // MutualTLSModeStrict requires mTLS for incoming traffic. + MutualTLSModeStrict MutualTLSMode = "strict" + + // MutualTLSModePermissive allows incoming non-mTLS traffic. + MutualTLSModePermissive MutualTLSMode = "permissive" +) + +func (m MutualTLSMode) validate() error { + switch m { + case MutualTLSModeDefault, MutualTLSModeStrict, MutualTLSModePermissive: + return nil + } + return fmt.Errorf("Must be one of %q, %q, or %q.", + MutualTLSModeDefault, MutualTLSModeStrict, MutualTLSModePermissive, + ) +} + +func (m MutualTLSMode) toConsul() capi.MutualTLSMode { + return capi.MutualTLSMode(m) +} + // MeshGateway controls how Mesh Gateways are used for upstream Connect // services. type MeshGateway struct { 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 4850ad152e..adbb12bba6 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -51,6 +51,11 @@ spec: spec: description: MeshSpec defines the desired state of Mesh. properties: + allowEnablingPermissiveMutualTLS: + description: AllowEnablingPermissiveMutualTLS must be true in order + to allow setting MutualTLSMode=permissive in either service-defaults + or proxy-defaults. + type: boolean http: description: HTTP defines the HTTP configuration for the service mesh. properties: diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 86409d2de0..7084980bf0 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -176,6 +176,18 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string + mutualTLSMode: + description: 'MutualTLSMode controls whether mutual TLS is required + for all incoming connections when transparent proxy is enabled. + This can be set to "permissive" or "strict". "strict" is the default + which requires mutual TLS for incoming connections. In the insecure + "permissive" mode, connections to the sidecar proxy public listener + port require mutual TLS, but connections to the service port do + not require mutual TLS and are proxied to the application unmodified. + Note: Intentions are not enforced for non-mTLS connections. To keep + your services secure, we recommend using "strict" mode whenever + possible and enabling "permissive" mode only when necessary.' + type: string transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 8009b3816b..ed4dbff857 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -167,6 +167,18 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string + mutualTLSMode: + description: 'MutualTLSMode controls whether mutual TLS is required + for all incoming connections when transparent proxy is enabled. + This can be set to "permissive" or "strict". "strict" is the default + which requires mutual TLS for incoming connections. In the insecure + "permissive" mode, connections to the sidecar proxy public listener + port require mutual TLS, but connections to the service port do + not require mutual TLS and are proxied to the application unmodified. + Note: Intentions are not enforced for non-mTLS connections. To keep + your services secure, we recommend using "strict" mode whenever + possible and enabling "permissive" mode only when necessary.' + type: string protocol: description: Protocol sets the protocol of the service. This is used by Connect proxies for things like observability features and to From d159fc546a684e38e2b15c52177909bb816f991d Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 12 May 2023 12:43:50 -0700 Subject: [PATCH 155/592] helm: add HOST_IP to mesh-gateway (#1808) * add HOST_IP to mesh-gateway --- .changelog/1808.txt | 3 +++ charts/consul/templates/mesh-gateway-deployment.yaml | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 .changelog/1808.txt diff --git a/.changelog/1808.txt b/.changelog/1808.txt new file mode 100644 index 0000000000..a860c635d9 --- /dev/null +++ b/.changelog/1808.txt @@ -0,0 +1,3 @@ +```release-note:bug +helm: add missing `$HOST_IP` environment variable to to mesh gateway deployments. +``` diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index 2b2bdc8c2a..449d6ae492 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -203,6 +203,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: HOST_IP + valueFrom: + fieldRef: + fieldPath: status.hostIP - name: POD_NAME valueFrom: fieldRef: From 8a10701ab26c0b26b580b7772b1938ab315d03c4 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Fri, 12 May 2023 15:51:02 -0400 Subject: [PATCH 156/592] chore(ci): fix typo in changelog checker (#2127) --- .github/workflows/changelog-checker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index ab76073047..1eea4196e7 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -36,7 +36,7 @@ jobs: changelog_file_path=".changelog/[_0-9]*.txt" fi - changelog_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/${{ github.event.pull_request.base.ref }}")" | egrep -e "${changelog_file_path}")) + changelog_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/${{ github.event.pull_request.base.ref }}")" | egrep -e "${changelog_file_path}") # If we do not find a file in .changelog/, we fail the check if [ -z "$changelog_files" ]; then From eeab0760f829bb5facb752119a428b73867764e9 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 15 May 2023 15:31:41 -0400 Subject: [PATCH 157/592] Add support for syncing Ingress hostname to the Consul Catalog (#2098) * Add support for syncing Ingress hostname to the Consul Catalog * fix changelog-checker syntax error --- .changelog/2098.txt | 3 + .../templates/sync-catalog-clusterrole.yaml | 55 ++- .../templates/sync-catalog-deployment.yaml | 264 +++++------ .../test/unit/sync-catalog-deployment.bats | 48 ++ charts/consul/values.yaml | 13 + control-plane/catalog/to-consul/resource.go | 207 ++++++++- .../catalog/to-consul/resource_test.go | 412 +++++++++++++++--- .../subcommand/sync-catalog/command.go | 11 + 8 files changed, 795 insertions(+), 218 deletions(-) create mode 100644 .changelog/2098.txt diff --git a/.changelog/2098.txt b/.changelog/2098.txt new file mode 100644 index 0000000000..f7c56e65a3 --- /dev/null +++ b/.changelog/2098.txt @@ -0,0 +1,3 @@ +```release-note:feature +sync-catalog: add ability to sync hostname from a Kubernetes Ingress resource to the Consul Catalog during service registration. +``` \ No newline at end of file diff --git a/charts/consul/templates/sync-catalog-clusterrole.yaml b/charts/consul/templates/sync-catalog-clusterrole.yaml index 0b0837c0df..585b5ad171 100644 --- a/charts/consul/templates/sync-catalog-clusterrole.yaml +++ b/charts/consul/templates/sync-catalog-clusterrole.yaml @@ -11,31 +11,38 @@ metadata: release: {{ .Release.Name }} component: sync-catalog rules: - - apiGroups: [""] - resources: - - services - - endpoints - verbs: - - get - - list - - watch +- apiGroups: [ "" ] + resources: + - services + - endpoints + verbs: + - get + - list + - watch {{- if .Values.syncCatalog.toK8S }} - - update - - patch - - delete - - create + - update + - patch + - delete + - create {{- end }} - - apiGroups: [""] - resources: - - nodes - verbs: - - get +- apiGroups: [ "" ] + resources: + - nodes + verbs: + - get {{- if .Values.global.enablePodSecurityPolicies }} - - apiGroups: ["policy"] - resources: ["podsecuritypolicies"] - verbs: - - use - resourceNames: - - {{ template "consul.fullname" . }}-sync-catalog -{{- end }} +- apiGroups: [ "policy" ] + resources: [ "podsecuritypolicies" ] + verbs: + - use + resourceNames: + - {{ template "consul.fullname" . }}-sync-catalog {{- end }} +- apiGroups: [ "networking.k8s.io" ] + resources: + - ingresses + verbs: + - get + - list + - watch +{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index ec1abbfc84..e88adea533 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -75,136 +75,142 @@ spec: {{- end }} {{- end }} containers: - - name: sync-catalog - image: "{{ default .Values.global.imageK8S .Values.syncCatalog.image }}" - env: - {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 12 }} - {{- 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" - - | - 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 (not .Values.syncCatalog.toConsul) }} - -to-consul=false \ - {{- end }} - {{- if (not .Values.syncCatalog.toK8S) }} - -to-k8s=false \ - {{- end }} - -consul-domain={{ .Values.global.domain }} \ - {{- if .Values.syncCatalog.k8sPrefix }} - -k8s-service-prefix="{{ .Values.syncCatalog.k8sPrefix}}" \ - {{- end }} - {{- if .Values.syncCatalog.k8sSourceNamespace }} - -k8s-source-namespace="{{ .Values.syncCatalog.k8sSourceNamespace}}" \ - {{- end }} - {{- range $value := .Values.syncCatalog.k8sAllowNamespaces }} - -allow-k8s-namespace="{{ $value }}" \ - {{- end }} - {{- range $value := .Values.syncCatalog.k8sDenyNamespaces }} - -deny-k8s-namespace="{{ $value }}" \ - {{- end }} - -k8s-write-namespace=${NAMESPACE} \ - {{- if (not .Values.syncCatalog.syncClusterIPServices) }} - -sync-clusterip-services=false \ - {{- end }} - {{- if .Values.syncCatalog.nodePortSyncType }} - -node-port-sync-type={{ .Values.syncCatalog.nodePortSyncType }} \ - {{- end }} - {{- if .Values.syncCatalog.consulWriteInterval }} - -consul-write-interval={{ .Values.syncCatalog.consulWriteInterval }} \ - {{- end }} - {{- if .Values.syncCatalog.k8sTag }} - -consul-k8s-tag={{ .Values.syncCatalog.k8sTag }} \ - {{- end }} - {{- if .Values.syncCatalog.consulNodeName }} - -consul-node-name={{ .Values.syncCatalog.consulNodeName }} \ - {{- end }} - {{- if .Values.global.adminPartitions.enabled }} - -partition={{ .Values.global.adminPartitions.name }} \ - {{- end }} - {{- if .Values.syncCatalog.consulPrefix}} - -consul-service-prefix="{{ .Values.syncCatalog.consulPrefix}}" \ - {{- end}} - {{- if .Values.syncCatalog.addK8SNamespaceSuffix}} - -add-k8s-namespace-suffix \ - {{- end}} - {{- if .Values.global.enableConsulNamespaces }} - -enable-namespaces=true \ - {{- if .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - -consul-destination-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} \ - {{- end }} - {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} - -enable-k8s-namespace-mirroring=true \ - {{- if .Values.syncCatalog.consulNamespaces.mirroringK8SPrefix }} - -k8s-namespace-mirroring-prefix={{ .Values.syncCatalog.consulNamespaces.mirroringK8SPrefix }} \ - {{- end }} - {{- end }} - {{- if .Values.global.acls.manageSystemACLs }} - -consul-cross-namespace-acl-policy=cross-namespace-policy \ - {{- end }} - {{- end }} - livenessProbe: - httpGet: - path: /health/ready - port: 8080 - scheme: HTTP - failureThreshold: 3 - initialDelaySeconds: 30 - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 5 - readinessProbe: - httpGet: - path: /health/ready - port: 8080 - scheme: HTTP - failureThreshold: 5 - initialDelaySeconds: 10 - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 5 - {{- with .Values.syncCatalog.resources }} - resources: - {{- toYaml . | nindent 12 }} + - name: sync-catalog + image: "{{ default .Values.global.imageK8S .Values.syncCatalog.image }}" + 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" + - | + 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 (not .Values.syncCatalog.toConsul) }} + -to-consul=false \ + {{- end }} + {{- if (not .Values.syncCatalog.toK8S) }} + -to-k8s=false \ + {{- end }} + -consul-domain={{ .Values.global.domain }} \ + {{- if .Values.syncCatalog.k8sPrefix }} + -k8s-service-prefix="{{ .Values.syncCatalog.k8sPrefix}}" \ + {{- end }} + {{- if .Values.syncCatalog.k8sSourceNamespace }} + -k8s-source-namespace="{{ .Values.syncCatalog.k8sSourceNamespace}}" \ + {{- end }} + {{- range $value := .Values.syncCatalog.k8sAllowNamespaces }} + -allow-k8s-namespace="{{ $value }}" \ + {{- end }} + {{- range $value := .Values.syncCatalog.k8sDenyNamespaces }} + -deny-k8s-namespace="{{ $value }}" \ + {{- end }} + -k8s-write-namespace=${NAMESPACE} \ + {{- if (not .Values.syncCatalog.syncClusterIPServices) }} + -sync-clusterip-services=false \ + {{- end }} + {{- if .Values.syncCatalog.nodePortSyncType }} + -node-port-sync-type={{ .Values.syncCatalog.nodePortSyncType }} \ + {{- end }} + {{- if .Values.syncCatalog.consulWriteInterval }} + -consul-write-interval={{ .Values.syncCatalog.consulWriteInterval }} \ + {{- end }} + {{- if .Values.syncCatalog.k8sTag }} + -consul-k8s-tag={{ .Values.syncCatalog.k8sTag }} \ + {{- end }} + {{- if .Values.syncCatalog.consulNodeName }} + -consul-node-name={{ .Values.syncCatalog.consulNodeName }} \ + {{- end }} + {{- if .Values.global.adminPartitions.enabled }} + -partition={{ .Values.global.adminPartitions.name }} \ + {{- end }} + {{- if .Values.syncCatalog.consulPrefix}} + -consul-service-prefix="{{ .Values.syncCatalog.consulPrefix}}" \ + {{- end}} + {{- if .Values.syncCatalog.addK8SNamespaceSuffix}} + -add-k8s-namespace-suffix \ + {{- end}} + {{- if .Values.global.enableConsulNamespaces }} + -enable-namespaces=true \ + {{- if .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + -consul-destination-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} \ + {{- end }} + {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + -enable-k8s-namespace-mirroring=true \ + {{- if .Values.syncCatalog.consulNamespaces.mirroringK8SPrefix }} + -k8s-namespace-mirroring-prefix={{ .Values.syncCatalog.consulNamespaces.mirroringK8SPrefix }} \ + {{- end }} + {{- end }} + {{- if .Values.global.acls.manageSystemACLs }} + -consul-cross-namespace-acl-policy=cross-namespace-policy \ + {{- end }} + {{- end }} + {{- if .Values.syncCatalog.ingress.enabled }} + -enable-ingress=true \ + {{- if .Values.syncCatalog.ingress.loadBalancerIPs }} + -loadBalancer-ips=true \ + {{- end }} + {{- end }} + livenessProbe: + httpGet: + path: /health/ready + port: 8080 + scheme: HTTP + failureThreshold: 3 + initialDelaySeconds: 30 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /health/ready + port: 8080 + scheme: HTTP + failureThreshold: 5 + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 5 + {{- with .Values.syncCatalog.resources }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} {{- if .Values.syncCatalog.priorityClassName }} priorityClassName: {{ .Values.syncCatalog.priorityClassName | quote }} {{- end }} diff --git a/charts/consul/test/unit/sync-catalog-deployment.bats b/charts/consul/test/unit/sync-catalog-deployment.bats index 0b397a55d0..318b4d3d3c 100755 --- a/charts/consul/test/unit/sync-catalog-deployment.bats +++ b/charts/consul/test/unit/sync-catalog-deployment.bats @@ -365,6 +365,54 @@ load _helpers [ "${actual}" = "true" ] } +#-------------------------------------------------------------------- +# sync ingress + +@test "syncCatalog/Deployment: enable ingress sync flag not passed when disabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.ingress.enabled=false' \ + --set 'syncCatalog.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-ingress=true"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} +@test "syncCatalog/Deployment: enable ingress sync flag passed when enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.ingress.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-ingress=true"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalog/Deployment: enable loadbalancer IP sync flag not passed when syncIngress disabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.ingress.enabled=false' \ + --set 'syncCatalog.ingress.loadBalancerIPs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-loadBalancer-ips=true"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "syncCatalog/Deployment: enable loadbalancer IP sync flag passed when enabled with ingress sync" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.ingress.enabled=true' \ + --set 'syncCatalog.ingress.loadBalancerIPs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-loadBalancer-ips=true"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # affinity diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 73b805680b..9aac754ed4 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -1780,6 +1780,19 @@ syncCatalog: # Set this to false to skip syncing ClusterIP services. syncClusterIPServices: true + ingress: + # Syncs the hostname from a Kubernetes Ingress resource to service registrations + # when a rule matched a service. Currently only supports host based routing and + # not path based routing. The only supported path on an ingress rule is "/". + # Set this to false to skip syncing Ingress services. + # + # Currently, port 80 is synced if there is not TLS entry for the hostname. Syncs the port + # 443 if there is a TLS entry that matches the hostname. + enabled: false + # Requires syncIngress to be `true`. syncs the LoadBalancer IP from a Kubernetes Ingress + # resource instead of the hostname to service registrations when a rule matched a service. + loadBalancerIPs: false + # Configures the type of syncing that happens for NodePort # services. The valid options are: ExternalOnly, InternalOnly, ExternalFirst. # diff --git a/control-plane/catalog/to-consul/resource.go b/control-plane/catalog/to-consul/resource.go index e25257c8f8..6a4e913d80 100644 --- a/control-plane/catalog/to-consul/resource.go +++ b/control-plane/catalog/to-consul/resource.go @@ -17,6 +17,7 @@ import ( consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" @@ -146,12 +147,33 @@ type ServiceResource struct { // of each service. endpointsMap map[string]*corev1.Endpoints + // EnableIngress enables syncing of the hostname from an Ingress resource + // to the service registration if an Ingress rule matches the service. + EnableIngress bool + + // SyncLoadBalancerIPs enables syncing the IP of the Ingress LoadBalancer + // if we do not want to sync the hostname from the Ingress resource. + SyncLoadBalancerIPs bool + + // ingressServiceMap uses the same keys as serviceMap but maps to the ingress + // of each service if it exists. + ingressServiceMap map[string]map[string]string + + // serviceHostnameMap maps the name of a service to the hostName and port that + // is provided by the Ingress resource for the service. + serviceHostnameMap map[string]serviceAddress + // consulMap holds the services in Consul that we've registered from kube. // It's populated via Consul's API and lets us diff what is actually in // Consul vs. what we expect to be there. consulMap map[string][]*consulapi.CatalogRegistration } +type serviceAddress struct { + hostName string + port int32 +} + // Informer implements the controller.Resource interface. func (t *ServiceResource) Informer() cache.SharedIndexInformer { // Watch all k8s namespaces. Events will be filtered out as appropriate @@ -256,9 +278,21 @@ func (t *ServiceResource) doDelete(key string) { // Run implements the controller.Backgrounder interface. func (t *ServiceResource) Run(ch <-chan struct{}) { t.Log.Info("starting runner for endpoints") + // Register a controller for Endpoints which subsequently registers a + // controller for the Ingress resource. (&controller.Controller{ - Log: t.Log.Named("controller/endpoints"), - Resource: &serviceEndpointsResource{Service: t, Ctx: t.Ctx}, + Resource: &serviceEndpointsResource{ + Service: t, + Ctx: t.Ctx, + Log: t.Log.Named("controller/endpoints"), + Resource: &serviceIngressResource{ + Service: t, + Ctx: t.Ctx, + SyncLoadBalancerIPs: t.SyncLoadBalancerIPs, + EnableIngress: t.EnableIngress, + }, + }, + Log: t.Log.Named("controller/service"), }).Run(ch) } @@ -647,12 +681,21 @@ func (t *ServiceResource) registerServiceInstance( } } for _, subsetAddr := range subset.Addresses { - addr := subsetAddr.IP - if addr == "" && useHostname { - addr = subsetAddr.Hostname - } - if addr == "" { - continue + var addr string + // Use the address and port from the Ingress resource if + // ingress-sync is enabled and the service has an ingress + // resource that references it. + if t.EnableIngress && t.isIngressService(key) { + addr = t.serviceHostnameMap[key].hostName + epPort = int(t.serviceHostnameMap[key].port) + } else { + addr = subsetAddr.IP + if addr == "" && useHostname { + addr = subsetAddr.Hostname + } + if addr == "" { + continue + } } // Its not clear whether K8S guarantees ready addresses to @@ -719,8 +762,19 @@ func (t *ServiceResource) sync() { // a background watcher on endpoints that is used by the ServiceResource // to keep track of changing endpoints for registered services. type serviceEndpointsResource struct { - Service *ServiceResource - Ctx context.Context + Service *ServiceResource + Ctx context.Context + Log hclog.Logger + Resource controller.Resource +} + +// Run implements the controller.Backgrounder interface. +func (t *serviceEndpointsResource) Run(ch <-chan struct{}) { + t.Log.Info("starting runner for ingress") + (&controller.Controller{ + Log: t.Log.Named("controller/ingress"), + Resource: t.Resource, + }).Run(ch) } func (t *serviceEndpointsResource) Informer() cache.SharedIndexInformer { @@ -796,6 +850,134 @@ func (t *serviceEndpointsResource) Delete(key string, _ interface{}) error { return nil } +// serviceIngressResource implements controller.Resource and starts +// a background watcher on ingress resources that is used by the ServiceResource +// to keep track of changing ingress for registered services. +type serviceIngressResource struct { + Service *ServiceResource + Resource controller.Resource + Ctx context.Context + EnableIngress bool + SyncLoadBalancerIPs bool +} + +func (t *serviceIngressResource) Informer() cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return t.Service.Client.NetworkingV1(). + Ingresses(metav1.NamespaceAll). + List(t.Ctx, options) + }, + + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return t.Service.Client.NetworkingV1(). + Ingresses(metav1.NamespaceAll). + Watch(t.Ctx, options) + }, + }, + &networkingv1.Ingress{}, + 0, + cache.Indexers{}, + ) +} + +func (t *serviceIngressResource) Upsert(key string, raw interface{}) error { + if !t.EnableIngress { + return nil + } + svc := t.Service + ingress, ok := raw.(*networkingv1.Ingress) + if !ok { + svc.Log.Warn("upsert got invalid type", "raw", raw) + return nil + } + + svc.serviceLock.Lock() + defer svc.serviceLock.Unlock() + + for _, rule := range ingress.Spec.Rules { + var svcName string + var hostName string + var svcPort int32 + for _, path := range rule.HTTP.Paths { + if path.Path == "/" { + svcName = path.Backend.Service.Name + svcPort = 80 + } else { + continue + } + } + if svcName == "" { + continue + } + if t.SyncLoadBalancerIPs { + if ingress.Status.LoadBalancer.Ingress[0].IP == "" { + continue + } + hostName = ingress.Status.LoadBalancer.Ingress[0].IP + } else { + hostName = rule.Host + } + for _, ingressTLS := range ingress.Spec.TLS { + for _, host := range ingressTLS.Hosts { + if rule.Host == host { + svcPort = 443 + } + } + } + + if svc.serviceHostnameMap == nil { + svc.serviceHostnameMap = make(map[string]serviceAddress) + } + // Maintain a list of the service name to the hostname from the Ingress resource. + svc.serviceHostnameMap[fmt.Sprintf("%s/%s", ingress.Namespace, svcName)] = serviceAddress{ + hostName: hostName, + port: svcPort, + } + if svc.ingressServiceMap == nil { + svc.ingressServiceMap = make(map[string]map[string]string) + } + if svc.ingressServiceMap[key] == nil { + svc.ingressServiceMap[key] = make(map[string]string) + } + // Maintain a list of all the service names that map to an Ingress resource. + svc.ingressServiceMap[key][fmt.Sprintf("%s/%s", ingress.Namespace, svcName)] = "" + } + + // Update the registration for each matched service and trigger a sync + for svcName := range svc.ingressServiceMap[key] { + svc.Log.Info(fmt.Sprintf("generating registrations for %s", svcName)) + svc.generateRegistrations(svcName) + } + svc.sync() + svc.Log.Info("upsert ingress", "key", key) + + return nil +} + +func (t *serviceIngressResource) Delete(key string, _ interface{}) error { + if !t.EnableIngress { + return nil + } + t.Service.serviceLock.Lock() + defer t.Service.serviceLock.Unlock() + + // This is a bit of an optimization. We only want to force a resync + // if we were tracking this ingress to begin with and that ingress + // had associated registrations. + if _, ok := t.Service.ingressServiceMap[key]; ok { + for svcName := range t.Service.ingressServiceMap[key] { + delete(t.Service.serviceHostnameMap, svcName) + } + delete(t.Service.ingressServiceMap, key) + t.Service.sync() + } + + t.Service.Log.Info("delete ingress", "key", key) + return nil +} + func (t *ServiceResource) addPrefixAndK8SNamespace(name, namespace string) string { if t.ConsulServicePrefix != "" { name = fmt.Sprintf("%s%s", t.ConsulServicePrefix, name) @@ -808,6 +990,11 @@ func (t *ServiceResource) addPrefixAndK8SNamespace(name, namespace string) strin return name } +// isIngressService return if a service has an Ingress resource that references it. +func (t *ServiceResource) isIngressService(key string) bool { + return t.serviceHostnameMap != nil && t.serviceHostnameMap[key].hostName != "" +} + // consulHealthCheckID deterministically generates a health check ID based on service ID and Kubernetes namespace. func consulHealthCheckID(k8sNS string, serviceID string) string { return fmt.Sprintf("%s/%s", k8sNS, serviceID) diff --git a/control-plane/catalog/to-consul/resource_test.go b/control-plane/catalog/to-consul/resource_test.go index d75f03d692..3475729950 100644 --- a/control-plane/catalog/to-consul/resource_test.go +++ b/control-plane/catalog/to-consul/resource_test.go @@ -13,7 +13,8 @@ import ( "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" - apiv1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" @@ -445,7 +446,7 @@ func TestServiceResource_lbMultiEndpoint(t *testing.T) { svc := lbService("foo", metav1.NamespaceDefault, "1.2.3.4") svc.Status.LoadBalancer.Ingress = append( svc.Status.LoadBalancer.Ingress, - apiv1.LoadBalancerIngress{IP: "2.3.4.5"}, + corev1.LoadBalancerIngress{IP: "2.3.4.5"}, ) _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) @@ -504,7 +505,7 @@ func TestServiceResource_lbPort(t *testing.T) { // Insert an LB service svc := lbService("foo", metav1.NamespaceDefault, "1.2.3.4") - svc.Spec.Ports = []apiv1.ServicePort{ + svc.Spec.Ports = []corev1.ServicePort{ {Name: "http", Port: 80, TargetPort: intstr.FromInt(8080)}, {Name: "rpc", Port: 8500, TargetPort: intstr.FromInt(2000)}, } @@ -537,7 +538,7 @@ func TestServiceResource_lbAnnotatedPort(t *testing.T) { // Insert an LB service svc := lbService("foo", metav1.NamespaceDefault, "1.2.3.4") svc.Annotations[annotationServicePort] = "rpc" - svc.Spec.Ports = []apiv1.ServicePort{ + svc.Spec.Ports = []corev1.ServicePort{ {Name: "http", Port: 80, TargetPort: intstr.FromInt(8080)}, {Name: "rpc", Port: 8500, TargetPort: intstr.FromInt(2000)}, } @@ -628,17 +629,17 @@ func TestServiceResource_lbRegisterEndpoints(t *testing.T) { // Insert the endpoints _, err := client.CoreV1().Endpoints(metav1.NamespaceDefault).Create( context.Background(), - &apiv1.Endpoints{ + &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Subsets: []apiv1.EndpointSubset{ + Subsets: []corev1.EndpointSubset{ { - Addresses: []apiv1.EndpointAddress{ + Addresses: []corev1.EndpointAddress{ {NodeName: &node1.Name, IP: "8.8.8.8"}, }, - Ports: []apiv1.EndpointPort{ + Ports: []corev1.EndpointPort{ {Name: "http", Port: 8080}, {Name: "rpc", Port: 2000}, }, @@ -763,17 +764,17 @@ func TestServiceResource_nodePort_singleEndpoint(t *testing.T) { // Insert the endpoints _, err := client.CoreV1().Endpoints(metav1.NamespaceDefault).Create( context.Background(), - &apiv1.Endpoints{ + &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Subsets: []apiv1.EndpointSubset{ + Subsets: []corev1.EndpointSubset{ { - Addresses: []apiv1.EndpointAddress{ + Addresses: []corev1.EndpointAddress{ {NodeName: &node1.Name, IP: "1.2.3.4"}, }, - Ports: []apiv1.EndpointPort{ + Ports: []corev1.EndpointPort{ {Name: "http", Port: 8080}, {Name: "rpc", Port: 2000}, }, @@ -860,7 +861,7 @@ func TestServiceResource_nodePortUnnamedPort(t *testing.T) { // Insert the service svc := nodePortService("foo", metav1.NamespaceDefault) // Override service ports - svc.Spec.Ports = []apiv1.ServicePort{ + svc.Spec.Ports = []corev1.ServicePort{ {Port: 80, TargetPort: intstr.FromInt(8080), NodePort: 30000}, {Port: 8500, TargetPort: intstr.FromInt(2000), NodePort: 30001}, } @@ -940,9 +941,9 @@ func TestServiceResource_nodePort_externalFirstSync(t *testing.T) { node1, _ := createNodes(t, client) - node1.Status = apiv1.NodeStatus{ - Addresses: []apiv1.NodeAddress{ - {Type: apiv1.NodeInternalIP, Address: "4.5.6.7"}, + node1.Status = corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeInternalIP, Address: "4.5.6.7"}, }, } _, err := client.CoreV1().Nodes().UpdateStatus(context.Background(), node1, metav1.UpdateOptions{}) @@ -1173,7 +1174,7 @@ func TestServiceResource_clusterIPUnnamedPorts(t *testing.T) { // Insert the service svc := clusterIPService("foo", metav1.NamespaceDefault) - svc.Spec.Ports = []apiv1.ServicePort{ + svc.Spec.Ports = []corev1.ServicePort{ {Port: 80, TargetPort: intstr.FromInt(8080)}, {Port: 8500, TargetPort: intstr.FromInt(2000)}, } @@ -1281,7 +1282,7 @@ func TestServiceResource_clusterIPTargetPortNamed(t *testing.T) { // Insert the service svc := clusterIPService("foo", metav1.NamespaceDefault) svc.Annotations[annotationServicePort] = "rpc" - svc.Spec.Ports = []apiv1.ServicePort{ + svc.Spec.Ports = []corev1.ServicePort{ {Port: 80, TargetPort: intstr.FromString("httpPort"), Name: "http"}, {Port: 8500, TargetPort: intstr.FromString("rpcPort"), Name: "rpc"}, } @@ -1531,22 +1532,323 @@ func TestServiceResource_MirroredPrefixNamespace(t *testing.T) { }) } +// Test k8s namespace suffix is not appended +// when the service name annotation is provided. +func TestServiceResource_addIngress(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + enableIngress bool + syncIngressIP bool + ingress *networkingv1.Ingress + expectIngressSync bool + expectedAddress string + expectedPort int + }{ + "enable ingress on port 80": { + enableIngress: true, + ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"test.other.consul"}, + SecretName: "test-other-tls-secret", + }, + }, + Rules: []networkingv1.IngressRule{ + { + Host: "test.host.consul", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "test-service", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectIngressSync: true, + expectedAddress: "test.host.consul", + expectedPort: 80, + }, + "enable ingress on port 443": { + enableIngress: true, + ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"test.host.consul"}, + SecretName: "test-tls-secret", + }, + }, + Rules: []networkingv1.IngressRule{ + { + Host: "test.host.consul", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "test-service", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectIngressSync: true, + expectedAddress: "test.host.consul", + expectedPort: 443, + }, + "enable ingress on port 80 with loadbalancer IP": { + enableIngress: true, + syncIngressIP: true, + ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"test.other.consul"}, + SecretName: "test-other-tls-secret", + }, + }, + Rules: []networkingv1.IngressRule{ + { + Host: "test.host.consul", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "test-service", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Status: networkingv1.IngressStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{{IP: "1.2.3.4"}}, + }, + }, + }, + expectIngressSync: true, + expectedAddress: "1.2.3.4", + expectedPort: 80, + }, + "enable ingress on port 443 with loadbalancer IP": { + enableIngress: true, + syncIngressIP: true, + ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"test.host.consul"}, + SecretName: "test-tls-secret", + }, + }, + Rules: []networkingv1.IngressRule{ + { + Host: "test.host.consul", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "test-service", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Status: networkingv1.IngressStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{{IP: "1.2.3.4"}}, + }, + }, + }, + expectIngressSync: true, + expectedAddress: "1.2.3.4", + expectedPort: 443, + }, + "ingress disabled": { + enableIngress: false, + ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "test.host.consul", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "test-service", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectIngressSync: false, + expectedAddress: "1.1.1.1", + expectedPort: 8080, + }, + "ignores ingress if host != /": { + enableIngress: true, + ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "test.host.consul", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/foo", + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "test-service", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectIngressSync: false, + expectedAddress: "1.1.1.1", + expectedPort: 8080, + }, + } + + for name, test := range cases { + t.Run(name, func(t *testing.T) { + client := fake.NewSimpleClientset() + syncer := newTestSyncer() + serviceResource := defaultServiceResource(client, syncer) + serviceResource.ClusterIPSync = true + serviceResource.EnableIngress = test.enableIngress + serviceResource.SyncLoadBalancerIPs = test.syncIngressIP + + // Start the controller + closer := controller.TestControllerRun(&serviceResource) + defer closer() + + // Create the service + _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), clusterIPService("test-service", metav1.NamespaceDefault), metav1.CreateOptions{}) + require.NoError(t, err) + // Create the ingress + _, err = client.NetworkingV1().Ingresses(metav1.NamespaceDefault).Create(context.Background(), test.ingress, metav1.CreateOptions{}) + require.NoError(t, err) + createEndpoints(t, client, "test-service", metav1.NamespaceDefault) + // Verify that the service name annotation is preferred + retry.Run(t, func(r *retry.R) { + syncer.Lock() + defer syncer.Unlock() + actual := syncer.Registrations + if test.expectIngressSync { + require.Len(r, actual, 1) + require.Equal(r, test.expectedAddress, actual[0].Service.Address) + require.Equal(r, test.expectedPort, actual[0].Service.Port) + } else { + require.Len(r, actual, 2) + require.Equal(r, test.expectedAddress, actual[0].Service.Address) + require.Equal(r, test.expectedPort, actual[0].Service.Port) + } + + }) + }) + } +} + // lbService returns a Kubernetes service of type LoadBalancer. -func lbService(name, namespace, lbIP string) *apiv1.Service { - return &apiv1.Service{ +func lbService(name, namespace, lbIP string) *corev1.Service { + return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, Annotations: map[string]string{}, }, - Spec: apiv1.ServiceSpec{ - Type: apiv1.ServiceTypeLoadBalancer, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, }, - Status: apiv1.ServiceStatus{ - LoadBalancer: apiv1.LoadBalancerStatus{ - Ingress: []apiv1.LoadBalancerIngress{ + Status: corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ { IP: lbIP, }, @@ -1557,16 +1859,16 @@ func lbService(name, namespace, lbIP string) *apiv1.Service { } // nodePortService returns a Kubernetes service of type NodePort. -func nodePortService(name, namespace string) *apiv1.Service { - return &apiv1.Service{ +func nodePortService(name, namespace string) *corev1.Service { + return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, - Spec: apiv1.ServiceSpec{ - Type: apiv1.ServiceTypeNodePort, - Ports: []apiv1.ServicePort{ + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ {Name: "http", Port: 80, TargetPort: intstr.FromInt(8080), NodePort: 30000}, {Name: "rpc", Port: 8500, TargetPort: intstr.FromInt(2000), NodePort: 30001}, }, @@ -1575,17 +1877,17 @@ func nodePortService(name, namespace string) *apiv1.Service { } // clusterIPService returns a Kubernetes service of type ClusterIP. -func clusterIPService(name, namespace string) *apiv1.Service { - return &apiv1.Service{ +func clusterIPService(name, namespace string) *corev1.Service { + return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, Annotations: map[string]string{}, }, - Spec: apiv1.ServiceSpec{ - Type: apiv1.ServiceTypeClusterIP, - Ports: []apiv1.ServicePort{ + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Ports: []corev1.ServicePort{ {Name: "http", Port: 80, TargetPort: intstr.FromInt(8080)}, {Name: "rpc", Port: 8500, TargetPort: intstr.FromInt(2000)}, }, @@ -1594,34 +1896,34 @@ func clusterIPService(name, namespace string) *apiv1.Service { } // createNodes calls the fake k8s client to create two Kubernetes nodes and returns them. -func createNodes(t *testing.T, client *fake.Clientset) (*apiv1.Node, *apiv1.Node) { +func createNodes(t *testing.T, client *fake.Clientset) (*corev1.Node, *corev1.Node) { // Insert the nodes - node1 := &apiv1.Node{ + node1 := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName1, }, - Status: apiv1.NodeStatus{ - Addresses: []apiv1.NodeAddress{ - {Type: apiv1.NodeExternalIP, Address: "1.2.3.4"}, - {Type: apiv1.NodeInternalIP, Address: "4.5.6.7"}, - {Type: apiv1.NodeInternalIP, Address: "7.8.9.10"}, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeExternalIP, Address: "1.2.3.4"}, + {Type: corev1.NodeInternalIP, Address: "4.5.6.7"}, + {Type: corev1.NodeInternalIP, Address: "7.8.9.10"}, }, }, } _, err := client.CoreV1().Nodes().Create(context.Background(), node1, metav1.CreateOptions{}) require.NoError(t, err) - node2 := &apiv1.Node{ + node2 := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName2, }, - Status: apiv1.NodeStatus{ - Addresses: []apiv1.NodeAddress{ - {Type: apiv1.NodeExternalIP, Address: "2.3.4.5"}, - {Type: apiv1.NodeInternalIP, Address: "3.4.5.6"}, - {Type: apiv1.NodeInternalIP, Address: "6.7.8.9"}, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeExternalIP, Address: "2.3.4.5"}, + {Type: corev1.NodeInternalIP, Address: "3.4.5.6"}, + {Type: corev1.NodeInternalIP, Address: "6.7.8.9"}, }, }, } @@ -1635,31 +1937,31 @@ func createNodes(t *testing.T, client *fake.Clientset) (*apiv1.Node, *apiv1.Node func createEndpoints(t *testing.T, client *fake.Clientset, serviceName string, namespace string) { node1 := nodeName1 node2 := nodeName2 - targetRef := apiv1.ObjectReference{Kind: "pod", Name: "foobar"} + targetRef := corev1.ObjectReference{Kind: "pod", Name: "foobar"} _, err := client.CoreV1().Endpoints(namespace).Create( context.Background(), - &apiv1.Endpoints{ + &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, }, - Subsets: []apiv1.EndpointSubset{ + Subsets: []corev1.EndpointSubset{ { - Addresses: []apiv1.EndpointAddress{ + Addresses: []corev1.EndpointAddress{ {NodeName: &node1, IP: "1.1.1.1", TargetRef: &targetRef}, }, - Ports: []apiv1.EndpointPort{ + Ports: []corev1.EndpointPort{ {Name: "http", Port: 8080}, {Name: "rpc", Port: 2000}, }, }, { - Addresses: []apiv1.EndpointAddress{ + Addresses: []corev1.EndpointAddress{ {NodeName: &node2, IP: "2.2.2.2"}, }, - Ports: []apiv1.EndpointPort{ + Ports: []corev1.EndpointPort{ {Name: "http", Port: 8080}, {Name: "rpc", Port: 2000}, }, diff --git a/control-plane/subcommand/sync-catalog/command.go b/control-plane/subcommand/sync-catalog/command.go index 1530cf9d53..2dadf6e039 100644 --- a/control-plane/subcommand/sync-catalog/command.go +++ b/control-plane/subcommand/sync-catalog/command.go @@ -67,6 +67,10 @@ type Command struct { flagK8SNSMirroringPrefix string // Prefix added to Consul namespaces created when mirroring flagCrossNamespaceACLPolicy string // The name of the ACL policy to add to every created namespace if ACLs are enabled + // Flags to support Kubernetes Ingress resources + flagEnableIngress bool // Register services using the hostname from an ingress resource + flagLoadBalancerIPs bool // Use the load balancer IP of an ingress resource instead of the hostname + clientset kubernetes.Interface // ready indicates whether this controller is ready to sync services. This will be changed to true once the @@ -151,6 +155,11 @@ func (c *Command) init() { "[Enterprise Only] Name of the ACL policy to attach to all created Consul namespaces to allow service "+ "discovery across Consul namespaces. Only necessary if ACLs are enabled.") + c.flags.BoolVar(&c.flagEnableIngress, "enable-ingress", false, + "[Enterprise Only] Enables namespaces, in either a single Consul namespace or mirrored.") + c.flags.BoolVar(&c.flagLoadBalancerIPs, "loadBalancer-ips", false, + "[Enterprise Only] Enables namespaces, in either a single Consul namespace or mirrored.") + c.consul = &flags.ConsulFlags{} c.k8s = &flags.K8SFlags{} flags.Merge(c.flags, c.consul.Flags()) @@ -294,6 +303,8 @@ func (c *Command) Run(args []string) int { EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, ConsulNodeName: c.flagConsulNodeName, + EnableIngress: c.flagEnableIngress, + SyncLoadBalancerIPs: c.flagLoadBalancerIPs, }, } From 02cab6ccbd2a9a18f8b83f939c03f8a6c0b5df11 Mon Sep 17 00:00:00 2001 From: Connor Date: Tue, 16 May 2023 15:46:34 -0500 Subject: [PATCH 158/592] Add telemetry collector deployment to consul-k8s (#2134) * Create values.yaml section for telemetry-collector * Initial telemetry-collector validation and bats test * Add nodeSelector * Add connect-init initContainer * Add consul-dataplane container * Conditionally add ca-cert volume * Include vault annotations * Prune tests to pertinent test cases * Move consul server env vars * Check ca mount for dataplane container * Check correct env var * Set default resources * Set initContainer and tolerations * Support priorityClassName * Support setting initContainer resources * Fix replicas unit test * Turn off tproxy and remove unneeded security context * Set -tls-disabled if global.tls.enabled=false * Set -ca-certs correct if tls is enabled * Set external server args * Set partition flag tests * Label bats tests, remove duplicate flags * Bats tests for service, add metricsserver port * Support annotations and imagePullSecret on serviceAccount * Create configmap for custom configuration * Add configmap to deployment * Fix test names * Remove unneeded cloud validation. fixup comment * Comment values.yaml changes * Switch from sidecar auth method to component auth method * changelog * Add PodSecurityPolicy for consul-telemetry-collector * Rename init container + add comment * Remove logLevel bats tests as it is unsupported right now * Remove auth-method special cases * Replace LOGIN_DATACENTER login with LOGIN_NAMESPACE * Remove unneeded LOGIN_DATACENTER test --- .changelog/2134.txt | 3 + charts/consul/templates/_helpers.tpl | 36 + .../telemetry-collector-configmap.yaml | 18 + .../telemetry-collector-deployment.yaml | 367 +++++++ ...telemetry-collector-podsecuritypolicy.yaml | 40 + .../templates/telemetry-collector-role.yaml | 21 + .../telemetry-collector-rolebinding.yaml | 21 + .../telemetry-collector-service.yaml | 24 + .../telemetry-collector-serviceaccount.yaml | 23 + .../unit/telemetry-collector-configmap.bats | 29 + .../unit/telemetry-collector-deployment.bats | 979 ++++++++++++++++++ ...telemetry-collector-podsecuritypolicy.bats | 21 + .../test/unit/telemetry-collector-role.bats | 21 + .../unit/telemetry-collector-rolebinding.bats | 21 + .../unit/telemetry-collector-service.bats | 72 ++ .../telemetry-collector-serviceaccount.bats | 69 ++ charts/consul/values.yaml | 112 +- 17 files changed, 1865 insertions(+), 12 deletions(-) create mode 100644 .changelog/2134.txt create mode 100644 charts/consul/templates/telemetry-collector-configmap.yaml create mode 100644 charts/consul/templates/telemetry-collector-deployment.yaml create mode 100644 charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml create mode 100644 charts/consul/templates/telemetry-collector-role.yaml create mode 100644 charts/consul/templates/telemetry-collector-rolebinding.yaml create mode 100644 charts/consul/templates/telemetry-collector-service.yaml create mode 100644 charts/consul/templates/telemetry-collector-serviceaccount.yaml create mode 100644 charts/consul/test/unit/telemetry-collector-configmap.bats create mode 100755 charts/consul/test/unit/telemetry-collector-deployment.bats create mode 100644 charts/consul/test/unit/telemetry-collector-podsecuritypolicy.bats create mode 100644 charts/consul/test/unit/telemetry-collector-role.bats create mode 100644 charts/consul/test/unit/telemetry-collector-rolebinding.bats create mode 100755 charts/consul/test/unit/telemetry-collector-service.bats create mode 100644 charts/consul/test/unit/telemetry-collector-serviceaccount.bats diff --git a/.changelog/2134.txt b/.changelog/2134.txt new file mode 100644 index 0000000000..980e7ba666 --- /dev/null +++ b/.changelog/2134.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add support for consul-telemetry-collector to forward envoy metrics to an otelhttp compatible receiver or HCP +``` \ No newline at end of file diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index b1feb0dbd6..1b866888c0 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -387,3 +387,39 @@ Usage: {{ template "consul.validateCloudSecretKeys" . }} {{- end }} {{- end }} {{- end -}} + + +{{/* +Fails if temeletryCollector.clientId or telemetryCollector.clientSecret exist and one of other secrets is nil or empty. +- telemetryCollector.cloud.clientId.secretName +- telemetryCollector.cloud.clientSecret.secretName +- global.cloud.resourceId.secretName + +Usage: {{ template "consul.validateTelemetryCollectorCloud" . }} + +*/}} +{{- define "consul.validateTelemetryCollectorCloud" -}} +{{- if (and .Values.telemetryCollector.cloud.clientId.secretName (or (not .Values.global.cloud.resourceId.secretName) (not .Values.telemetryCollector.cloud.clientSecret.secretName))) }} +{{fail "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set."}} +{{- end }} +{{- if (and .Values.telemetryCollector.cloud.clientSecret.secretName (or (not .Values.global.cloud.resourceId.secretName) (not .Values.telemetryCollector.cloud.clientSecret.secretName))) }} +{{fail "When telemetryCollector.cloud.clientSecret.secretName is set, global.cloud.resourceId.secretName,telemetryCollector.cloud.clientId.secretName must also be set."}} +{{- end }} +{{- end }} + +{{/**/}} + +{{- define "consul.validateTelemetryCollectorCloudSecretKeys" -}} +{{- if or (and .Values.telemetryCollector.cloud.clientId.secretName (not .Values.telemetryCollector.cloud.clientId.secretKey)) (and .Values.telemetryCollector.cloud.clientId.secretKey (not .Values.telemetryCollector.cloud.clientId.secretName)) }} +{{fail "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set."}} +{{- end }} +{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName (not .Values.telemetryCollector.cloud.clientSecret.secretKey)) (and .Values.telemetryCollector.cloud.clientSecret.secretKey (not .Values.telemetryCollector.cloud.clientSecret.secretName)) }} +{{fail "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set."}} +{{- end }} +{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not .Values.global.cloud.resourceId.secretName)) }} +{{fail "When telemetryCollector has clientId and clientSecret global.cloud.resourceId.secretName must be set"}} +{{- end }} +{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not .Values.global.cloud.resourceId.secretKey)) }} +{{fail "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set"}} +{{- end }} +{{- end -}} \ No newline at end of file diff --git a/charts/consul/templates/telemetry-collector-configmap.yaml b/charts/consul/templates/telemetry-collector-configmap.yaml new file mode 100644 index 0000000000..0bf5b8753c --- /dev/null +++ b/charts/consul/templates/telemetry-collector-configmap.yaml @@ -0,0 +1,18 @@ +{{- if (and .Values.telemetryCollector.enabled .Values.telemetryCollector.customExporterConfig) }} +# Immutable ConfigMap which saves the partition name. Attempting to update this configmap +# with a new Admin Partition name will cause the helm upgrade to fail +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "consul.fullname" . }}-telemetry-collector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: consul-telemetry-collector +data: + config.json: |- + {{ tpl .Values.telemetryCollector.customExporterConfig . | trimAll "\"" | indent 4 }} +{{- end }} diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml new file mode 100644 index 0000000000..a073128f95 --- /dev/null +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -0,0 +1,367 @@ +{{- if .Values.telemetryCollector.enabled }} +{{- if not .Values.telemetryCollector.image}}{{ fail "telemetryCollector.image must be set to enable consul-telemetry-collector" }}{{ end }} +{{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} +{{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} +{{ template "consul.validateCloudSecretKeys" . }} +{{ template "consul.validateTelemetryCollectorCloud" . }} +{{ template "consul.validateTelemetryCollectorCloudSecretKeys" . }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "consul.fullname" . }}-telemetry-collector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: consul-telemetry-collector + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.telemetryCollector.replicas }} + selector: + matchLabels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: consul-telemetry-collector + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "false" + # This annotation tells the endpoints controller that this pod was injected even though it wasn't. The + # endpoints controller would then sync the endpoint into Consul + "consul.hashicorp.com/connect-inject-status": "injected" + # We aren't using tproxy and we don't have an original pod. This would be simpler if we made a path similar + # to gateways + "consul.hashicorp.com/transparent-proxy": "false" + "consul.hashicorp.com/transparent-proxy-overwrite-probes": "false" + "consul.hashicorp.com/connect-k8s-version": {{ $.Chart.Version }} + {{- if .Values.telemetryCollector.customExporterConfig }} + # configmap checksum + "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/telemetry-collector-configmap.yaml") . | sha256sum }} + {{- end }} + # vault annotations + {{- 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 }} + {{- end }} + + labels: + consul.hashicorp.com/connect-inject-managed-by: consul-k8s-endpoints-controller + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: consul-telemetry-collector + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "consul.fullname" . }}-telemetry-collector + initContainers: + # We're manually managing this init container instead of using the connect injector so that we don't run into + # any race conditions on the connect-injector deployment or upgrade + - name: consul-connect-init + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + {{- if .Values.global.acls.manageSystemACLs }} + - name: CONSUL_LOGIN_AUTH_METHOD + value: {{ template "consul.fullname" . }}-k8s-auth-method + - name: CONSUL_LOGIN_META + value: "component=consul-telemetry-collector,pod=$(NAMESPACE)/$(POD_NAME)" + {{- end }} + - name: CONSUL_NODE_NAME + value: $(NODE_NAME)-virtual + {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} + {{- if .Values.global.enableConsulNamespaces }} + - name: CONSUL_NAMESPACE + value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + - name: CONSUL_LOGIN_NAMESPACE + value: "default" + {{- else }} + - name: CONSUL_LOGIN_NAMESPACE + value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- end }} + command: + - /bin/sh + - -ec + - |- + consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level={{ default .Values.global.logLevel }} \ + -log-json={{ .Values.global.logJSON }} \ + -service-account-name="consul-telemetry-collector" \ + -service-name="" \ + -proxy-id-file="/consul/connect-inject/proxyid" + + image: {{ .Values.global.imageK8S }} + imagePullPolicy: IfNotPresent + {{- if .Values.telemetryCollector.initContainer.resources }} + resources: + {{- toYaml .Values.telemetryCollector.initContainer.resources | nindent 12 }} + {{- else }} + resources: + limits: + cpu: 50m + memory: 150Mi + requests: + cpu: 50m + memory: 25Mi + {{- end }} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /consul/connect-inject + name: consul-connect-inject-data + {{- 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 }} + containers: + - name: consul-telemetry-collector + image: {{ .Values.telemetryCollector.image }} + imagePullPolicy: Always + ports: + - containerPort: 9090 + name: metrics + protocol: TCP + - containerPort: 9356 + name: metricsserver + protocol: TCP + env: + # These are mounted as secrets so that the telemetry-collector can use them when cloud is enabled. + # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, + # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. + # - HCP_RESOURCE_ID is created for use in the global cloud section but we will share it here + {{- if .Values.telemetryCollector.cloud.clientId.secretName }} + - name: HCP_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ .Values.telemetryCollector.cloud.clientId.secretName }} + key: {{ .Values.telemetryCollector.cloud.clientId.secretKey }} + {{- end }} + {{- if .Values.telemetryCollector.cloud.clientSecret.secretName }} + - name: HCP_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.telemetryCollector.cloud.clientSecret.secretName }} + key: {{ .Values.telemetryCollector.cloud.clientSecret.secretKey }} + {{- end}} + {{- if .Values.global.cloud.resourceId.secretName }} + - name: HCP_RESOURCE_ID + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.resourceId.secretName }} + key: {{ .Values.global.cloud.resourceId.secretKey }} + {{- end }} + {{- if .Values.global.cloud.authUrl.secretName }} + - name: HCP_AUTH_URL + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.authUrl.secretName }} + key: {{ .Values.global.cloud.authUrl.secretKey }} + {{- end}} + {{- if .Values.global.cloud.apiHost.secretName }} + - name: HCP_API_HOST + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.apiHost.secretName }} + key: {{ .Values.global.cloud.apiHost.secretKey }} + {{- end}} + {{- if .Values.global.cloud.scadaAddress.secretName }} + - name: HCP_SCADA_ADDRESS + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.scadaAddress.secretName }} + key: {{ .Values.global.cloud.scadaAddress.secretKey }} + {{- end}} + + command: + - "/bin/sh" + - "-ec" + - | + consul-telemetry-collector agent + {{- if .Values.telemetryCollector.customExporterConfig }} + args: + - -config-file-path /consul/config/config.json + {{ end }} + {{- if .Values.telemetryCollector.customExporterConfig }} + volumeMounts: + - name: config + mountPath: /consul/config + {{- end }} + resources: + {{- if .Values.telemetryCollector.resources }} + {{- toYaml .Values.telemetryCollector.resources | nindent 12 }} + {{- end }} + # consul-dataplane container + - name: consul-dataplane + image: "{{ .Values.global.imageConsulDataplane }}" + imagePullPolicy: IfNotPresent + command: + - consul-dataplane + args: + # addresses + {{- if .Values.externalServers.enabled }} + - -addresses={{ .Values.externalServers.hosts | first }} + {{- else }} + - -addresses={{ template "consul.fullname" . }}-server.{{ .Release.Namespace }}.svc + {{- end }} + # grpc + {{- if .Values.externalServers.enabled }} + - -grpc-port={{ .Values.externalServers.grpcPort }} + {{- else }} + - -grpc-port=8502 + {{- end }} + - -proxy-service-id-path=/consul/connect-inject/proxyid + # tls + {{- if .Values.global.tls.enabled }} + {{- if (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) }} + {{- if .Values.global.secretsBackend.vault.enabled }} + - -ca-certs=/vault/secrets/serverca.crt + {{- else }} + - -ca-certs=/consul/tls/ca/tls.crt + {{- end }} + {{- end }} + {{- if and .Values.externalServers.enabled .Values.externalServers.tlsServerName }} + - -tls-server-name={{.Values.externalServers.tlsServerName }} + {{- else if .Values.global.cloud.enabled }} + - -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} + {{- end }} + {{- else }} + - -tls-disabled + {{- end }} + # credentials + {{- if .Values.global.acls.manageSystemACLs }} + - -credential-type=login + - -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token + - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method + {{- if .Values.global.enableConsulNamespaces }} + {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + - -login-namespace="default" + {{- else }} + - -login-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- end }} + {{- if .Values.global.adminPartitions.enabled }} + - foo + - -login-partition={{ .Values.global.adminPartitions.name }} + {{- end }} + {{- end }} + {{- if .Values.global.enableConsulNamespaces }} + - -service-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- if .Values.global.adminPartitions.enabled }} + - -service-partition={{ .Values.global.adminPartitions.name }} + {{- end }} + {{- if .Values.global.metrics.enabled }} + - -telemetry-prom-scrape-path=/metrics + {{- end }} + - -log-level={{ default .Values.global.logLevel }} + - -log-json={{ .Values.global.logJSON }} + - -envoy-concurrency=2 + {{- if and .Values.externalServers.enabled .Values.externalServers.skipServerWatch }} + - -server-watch-disabled=true + {{- end }} + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: DP_CREDENTIAL_LOGIN_META1 + value: pod=$(NAMESPACE)/$(POD_NAME) + - name: DP_CREDENTIAL_LOGIN_META2 + value: component=consul-telemetry-collector + - name: DP_SERVICE_NODE_NAME + value: $(NODE_NAME)-virtual + - name: TMPDIR + value: /consul/connect-inject + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 1 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 20000 + timeoutSeconds: 1 + securityContext: + readOnlyRootFilesystem: true + runAsGroup: 5995 + runAsNonRoot: true + runAsUser: 5995 + # dataplane volume mounts + volumeMounts: + - mountPath: /consul/connect-inject + name: consul-connect-inject-data + {{- 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 }} + + {{- if .Values.telemetryCollector.nodeSelector }} + nodeSelector: + {{ tpl .Values.telemetryCollector.nodeSelector . | indent 8 | trim }} + {{- end }} + {{- if .Values.telemetryCollector.priorityClassName }} + priorityClassName: {{ .Values.telemetryCollector.priorityClassName }} + {{- end }} + volumes: + - emptyDir: + medium: Memory + name: consul-connect-inject-data + {{- 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 }} + - name: config + configMap: + name: {{ template "consul.fullname" . }}-telemetry-collector +{{- end }} diff --git a/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml b/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml new file mode 100644 index 0000000000..286a92d0bd --- /dev/null +++ b/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml @@ -0,0 +1,40 @@ +{{- if and .Values.global.enablePodSecurityPolicies .Values.telemetryCollector.enabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "consul.fullname" . }}-telemetry-collector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: telemetry-collector +spec: + privileged: false + # Required to prevent escalations to root. + allowPrivilegeEscalation: false + # This is redundant with non-root + disallow privilege escalation, + # but we can provide it for defense in depth. + requiredDropCapabilities: + - ALL + # Allow core volume types. + volumes: + - 'configMap' + - 'emptyDir' + - 'projected' + - 'secret' + - 'downwardAPI' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/consul/templates/telemetry-collector-role.yaml b/charts/consul/templates/telemetry-collector-role.yaml new file mode 100644 index 0000000000..f89373252d --- /dev/null +++ b/charts/consul/templates/telemetry-collector-role.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.global.enablePodSecurityPolicies .Values.telemetryCollector.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "consul.fullname" . }}-telemetry-collector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: consul-telemetry-collector +rules: + - apiGroups: ["policy"] + resources: ["podsecuritypolicies"] + resourceNames: + - {{ template "consul.fullname" . }}-telemetry-collector + verbs: + - use +{{- end }} + diff --git a/charts/consul/templates/telemetry-collector-rolebinding.yaml b/charts/consul/templates/telemetry-collector-rolebinding.yaml new file mode 100644 index 0000000000..1f9a896997 --- /dev/null +++ b/charts/consul/templates/telemetry-collector-rolebinding.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.global.enablePodSecurityPolicies .Values.telemetryCollector.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "consul.fullname" . }}-telemetry-collector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: consul-telemetry-collector +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "consul.fullname" . }}-telemetry-collector +subjects: + - kind: ServiceAccount + name: {{ template "consul.fullname" . }}-telemetry-collector +{{- end }} + diff --git a/charts/consul/templates/telemetry-collector-service.yaml b/charts/consul/templates/telemetry-collector-service.yaml new file mode 100644 index 0000000000..f7fc3f09d9 --- /dev/null +++ b/charts/consul/templates/telemetry-collector-service.yaml @@ -0,0 +1,24 @@ +{{- if .Values.telemetryCollector.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "consul.fullname" . }}-telemetry-collector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: consul-telemetry-collector + {{ if .Values.telemetryCollector.service.annotations }} + annotations: + {{ tpl .Values.telemetryCollector.service.annotations . | nindent 4 | trim }} + {{- end }} +spec: + type: ClusterIP + ports: + - port: 9356 + targetPort: 9356 + selector: + app: consul + component: consul-telemetry-collector +{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/telemetry-collector-serviceaccount.yaml b/charts/consul/templates/telemetry-collector-serviceaccount.yaml new file mode 100644 index 0000000000..f2b6e88171 --- /dev/null +++ b/charts/consul/templates/telemetry-collector-serviceaccount.yaml @@ -0,0 +1,23 @@ +{{- if .Values.telemetryCollector.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "consul.fullname" . }}-telemetry-collector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: consul-telemetry-collector + {{- if .Values.telemetryCollector.serviceAccount.annotations }} + annotations: + {{ tpl .Values.telemetryCollector.serviceAccount.annotations . | nindent 4 | trim }} + {{- end }} +automountServiceAccountToken: true +{{- with .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range . }} + - name: {{ .name }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-configmap.bats b/charts/consul/test/unit/telemetry-collector-configmap.bats new file mode 100644 index 0000000000..c866f0d3e1 --- /dev/null +++ b/charts/consul/test/unit/telemetry-collector-configmap.bats @@ -0,0 +1,29 @@ +#!/usr/bin/env bats + +load _helpers + +@test "telemetryCollector/ConfigMap: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-configmap.yaml \ + . +} + +@test "telemetryCollector/ConfigMap: enabled with telemetryCollector enabled and config has content" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-configmap.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.customExporterConfig="{}"' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/ConfigMap: disabled with telemetryCollector enabled and config is empty content" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-configmap.yaml \ + --set 'telemetryCollector.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats new file mode 100755 index 0000000000..8161e90e5d --- /dev/null +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -0,0 +1,979 @@ +#!/usr/bin/env bats + +load _helpers + +@test "telemetryCollector/Deployment: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-deployment.yaml \ + . +} + +@test "telemetryCollector/Deployment: fails if no image is set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=null' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "telemetryCollector.image must be set to enable consul-telemetry-collector" ]] +} + +@test "telemetryCollector/Deployment: disable with telemetry-collector.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=false' \ + . +} + +@test "telemetryCollector/Deployment: disable with global.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'global.enabled=false' \ + . +} + +@test "telemetryCollector/Deployment: container image overrides" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].image' | tee /dev/stderr) + [ "${actual}" = "\"bar\"" ] +} + +#-------------------------------------------------------------------- +# nodeSelector + +@test "telemetryCollector/Deployment: nodeSelector is not set by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + . | tee /dev/stderr | + yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "telemetryCollector/Deployment: specified nodeSelector" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.nodeSelector=testing' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "testing" ] +} + +#-------------------------------------------------------------------- +# global.tls.enabled + +@test "telemetryCollector/Deployment: Adds tls-ca-cert volume when global.tls.enabled is true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" != "" ] +} + +@test "telemetryCollector/Deployment: Adds tls-ca-cert volumeMounts when global.tls.enabled is true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" != "" ] +} + +@test "telemetryCollector/Deployment: can overwrite CA secret with the provided one" { + cd `chart_dir` + local ca_cert_volume=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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" ] +} + +#-------------------------------------------------------------------- +# global.tls.enableAutoEncrypt + +@test "telemetryCollector/Deployment: consul-ca-cert volumeMount is added when TLS with auto-encrypt is enabled without clients" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert") | length > 0' | tee + /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment: consul-ca-cert volume is not added if externalServers.enabled=true and externalServers.useSystemRoots=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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 "telemetryCollector/Deployment: resources has default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].resources' | tee /dev/stderr) + + [ $(echo "${actual}" | yq -r '.requests.memory') = "512Mi" ] + [ $(echo "${actual}" | yq -r '.requests.cpu') = "1000m" ] + [ $(echo "${actual}" | yq -r '.limits.memory') = "512Mi" ] + [ $(echo "${actual}" | yq -r '.limits.cpu') = "1000m" ] +} + +@test "telemetryCollector/Deployment: resources can be overridden" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'telemetryCollector.resources.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +#-------------------------------------------------------------------- +# init container resources + +@test "telemetryCollector/Deployment: init container has default resources" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) + + [ $(echo "${actual}" | yq -r '.requests.memory') = "25Mi" ] + [ $(echo "${actual}" | yq -r '.requests.cpu') = "50m" ] + [ $(echo "${actual}" | yq -r '.limits.memory') = "150Mi" ] + [ $(echo "${actual}" | yq -r '.limits.cpu') = "50m" ] +} + +@test "telemetryCollector/Deployment: init container resources can be set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'telemetryCollector.initContainer.resources.requests.memory=memory' \ + --set 'telemetryCollector.initContainer.resources.requests.cpu=cpu' \ + --set 'telemetryCollector.initContainer.resources.limits.memory=memory2' \ + --set 'telemetryCollector.initContainer.resources.limits.cpu=cpu2' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.requests.memory' | tee /dev/stderr) + [ "${actual}" = "memory" ] + + local actual=$(echo $object | yq -r '.requests.cpu' | tee /dev/stderr) + [ "${actual}" = "cpu" ] + + local actual=$(echo $object | yq -r '.limits.memory' | tee /dev/stderr) + [ "${actual}" = "memory2" ] + + local actual=$(echo $object | yq -r '.limits.cpu' | tee /dev/stderr) + [ "${actual}" = "cpu2" ] +} + +#-------------------------------------------------------------------- +# priorityClassName + +@test "telemetryCollector/Deployment: no priorityClassName by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) + + [ "${actual}" = "null" ] +} + +@test "telemetryCollector/Deployment: can set a priorityClassName" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'telemetryCollector.priorityClassName=name' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) + + [ "${actual}" = "name" ] +} + +#-------------------------------------------------------------------- +# replicas + +@test "telemetryCollector/Deployment: replicas defaults to 1" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + . | tee /dev/stderr | + yq '.spec.replicas' | tee /dev/stderr) + + [ "${actual}" = "1" ] +} + +@test "telemetryCollector/Deployment: replicas can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'telemetryCollector.replicas=3' \ + . | tee /dev/stderr | + yq '.spec.replicas' | tee /dev/stderr) + + [ "${actual}" = "3" ] +} + +#-------------------------------------------------------------------- +# Vault + +@test "telemetryCollector/Deployment: vault CA is not configured by default" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + . | 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 "telemetryCollector/Deployment: vault CA is not configured when secretName is set but secretKey is not" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + --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 "telemetryCollector/Deployment: vault CA is not configured when secretKey is set but secretName is not" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + --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 "telemetryCollector/Deployment: vault CA is configured when both secretName and secretKey are set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + --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" ] +} + +@test "telemetryCollector/Deployment: vault tls annotations are set when tls is enabled" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'server.serverCert.secretName=pki_int/issue/test' \ + --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"pki_int/cert/ca\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr)" + [ "${actual}" = "pki_int/cert/ca" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr)" + [ "${actual}" = "true" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr)" + [ "${actual}" = "true" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" + [ "${actual}" = "test" ] +} + +@test "telemetryCollector/Deployment: vault agent annotations can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +#-------------------------------------------------------------------- +# telemetryCollector.cloud + +@test "telemetryCollector/Deployment: success with all cloud bits set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientSecret.secretName=client-secret-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-key' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ + . +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId is set and global.cloud.resourceId is not set or global.cloud.clientSecret.secretName is not set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientSecret.secretName=client-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-id-key' \ + --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.enabled is true and global.cloud.clientSecret.secretName is not set but global.cloud.clientId.secretName and global.cloud.resourceId.secretName is set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.enabled is true and global.cloud.resourceId.secretName is not set but global.cloud.clientId.secretName and global.cloud.clientSecret.secretName is set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.resourceId.secretName is set but global.cloud.resourceId.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When either global.cloud.resourceId.secretName or global.cloud.resourceId.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.authURL.secretName is set but global.cloud.authURL.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.authUrl.secretName=auth-url-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.authURL.secretKey is set but global.cloud.authURL.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.authUrl.secretKey=auth-url-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.apiHost.secretName is set but global.cloud.apiHost.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.apiHost.secretName=auth-url-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.apiHost.secretKey is set but global.cloud.apiHost.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.apiHost.secretKey=auth-url-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.scadaAddress.secretName is set but global.cloud.scadaAddress.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.scadaAddress.secretName=scada-address-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when global.cloud.scadaAddress.secretKey is set but global.cloud.scadaAddress.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.scadaAddress.secretKey=scada-address-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId.secretName is set but telemetryCollector.cloud.clientId.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ + --set 'telemetryCollector.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.resourceId.secretName=resource-id-name' \ + --set 'global.resourceId.secretKey=resource-id-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId.secretKey is set but telemetryCollector.cloud.clientId.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ + --set 'global.resourceId.secretName=resource-id-name' \ + --set 'global.resourceId.secretKey=resource-id-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetryCollector.cloud.clientId.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ + --set 'telemetryCollector.clientSecret.secretKey=client-secret-key-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId.secretName is set but telemetry.cloud.clientId.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetry.cloud.clientSecret.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId and telemetryCollector.cloud.clientSecret is set but global.cloud.resourceId.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set" ]] +} + +#-------------------------------------------------------------------- +# global.tls.enabled + +@test "telemetryCollector/Deployment: sets -tls-disabled args when when not using TLS." { + cd `chart_dir` + + local flags=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=false' \ + . | yq -r .spec.template.spec.containers[1].args) + + local actual=$(echo $flags | yq -r '. | any(contains("-tls-disabled"))') + [ "${actual}" = 'true' ] + +} + +@test "telemetryCollector/Deployment: -ca-certs set correctly when using TLS." { + cd `chart_dir` + local flags=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] +} + +#-------------------------------------------------------------------- +# External Server + +@test "telemetryCollector/Deployment: sets external server args when global.tls.enabled and externalServers.enabled" { + cd `chart_dir` + local flags=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.httpsPort=8501' \ + --set 'externalServers.tlsServerName=foo.tls.server' \ + --set 'externalServers.useSystemRoots=true' \ + --set 'server.enabled=false' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) + [ "${actual}" = 'false' ] + + local actual=$(echo $flags | yq -r '. | any(contains("-tls-server-name=foo.tls.server"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] + + local actual=$(echo $flags | jq -r '. | any(contains("-addresses=external-consul.host"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] +} + +#-------------------------------------------------------------------- +# Admin Partitions + +@test "telemetryCollector/Deployment: partition flags are set when using admin partitions" { + cd `chart_dir` + local flags=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.adminPartitions.name=hashi' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo $flags | jq -r '. | any(contains("-login-partition=hashi"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] + + local actual=$(echo $flags | jq -r '. | any(contains("-service-partition=hashi"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment: consul-ca-cert volume mount is not set when using externalServers and useSystemRoots" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + +@test "telemetryCollector/Deployment: config volume mount is set when config exists" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.customExporterConfig="foo"' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "config") | .name' | tee /dev/stderr) + [ "${actual}" = "config" ] +} + +@test "telemetryCollector/Deployment: config flag is set when config exists" { + cd `chart_dir` + local flags=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.customExporterConfig="foo"' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].args') + + local actual=$(echo $flags | yq -r '. | any(contains("-config-file-path /consul/config/config.json"))') + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment: consul-ca-cert volume mount is not set on acl-init when using externalServers and useSystemRoots" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + +#-------------------------------------------------------------------- +# extraLabels + +@test "telemetryCollector/Deployment: no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."consul.hashicorp.com/connect-inject-managed-by")' \ + | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "telemetryCollector/Deployment: extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --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 "telemetryCollector/Deployment: multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --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" ] +} diff --git a/charts/consul/test/unit/telemetry-collector-podsecuritypolicy.bats b/charts/consul/test/unit/telemetry-collector-podsecuritypolicy.bats new file mode 100644 index 0000000000..90c494eca5 --- /dev/null +++ b/charts/consul/test/unit/telemetry-collector-podsecuritypolicy.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load _helpers + +@test "telemetryCollector/PodSecurityPolicy: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-podsecuritypolicy.yaml \ + . +} + +@test "telemetryCollector/PodSecurityPolicy: enabled with telemetryCollector and global.enablePodSecurityPolicies=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-podsecuritypolicy.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.enablePodSecurityPolicies=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-role.bats b/charts/consul/test/unit/telemetry-collector-role.bats new file mode 100644 index 0000000000..8fa209ef4b --- /dev/null +++ b/charts/consul/test/unit/telemetry-collector-role.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load _helpers + +@test "telemetryCollector/Role: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-role.yaml \ + . +} + +@test "telemetryCollector/Role: enabled with telemetryCollector and global.enablePodSecurityPolicies=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-role.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.enablePodSecurityPolicies=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-rolebinding.bats b/charts/consul/test/unit/telemetry-collector-rolebinding.bats new file mode 100644 index 0000000000..454c2569fb --- /dev/null +++ b/charts/consul/test/unit/telemetry-collector-rolebinding.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load _helpers + +@test "telemetryCollector/RoleBinding: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-rolebinding.yaml \ + . +} + +@test "telemetryCollector/RoleBinding: enabled with telemetryCollector and global.enablePodSecurityPolicies=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-rolebinding.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.enablePodSecurityPolicies=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-service.bats b/charts/consul/test/unit/telemetry-collector-service.bats new file mode 100755 index 0000000000..0ee6512c05 --- /dev/null +++ b/charts/consul/test/unit/telemetry-collector-service.bats @@ -0,0 +1,72 @@ +#!/usr/bin/env bats + +load _helpers + +@test "telemetryCollector/Service: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-service.yaml \ + . +} + +@test "telemetryCollector/Service: enabled by default with telemetryCollector, connectInject enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-service.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Service: enabled with telemetryCollector.enabled=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-service.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# annotations + +@test "telemetryCollector/Service: no annotations by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-service.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.metadata.annotations' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "telemetryCollector/Service: can set annotations" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-service.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'telemetryCollector.service.annotations=key: value' \ + . | tee /dev/stderr | + yq -r '.metadata.annotations.key' | tee /dev/stderr) + [ "${actual}" = "value" ] +} + +#-------------------------------------------------------------------- +# port + +@test "telemetryCollector/Service: has default port" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-service.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.ports[0].port' | tee /dev/stderr) + [ "${actual}" = "9356" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-serviceaccount.bats b/charts/consul/test/unit/telemetry-collector-serviceaccount.bats new file mode 100644 index 0000000000..211238f72f --- /dev/null +++ b/charts/consul/test/unit/telemetry-collector-serviceaccount.bats @@ -0,0 +1,69 @@ +#!/usr/bin/env bats + +load _helpers + +@test "telemetryCollector/ServiceAccount: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-serviceaccount.yaml \ + . +} + +@test "telemetryCollector/ServiceAccount: enabled with telemetryCollector, connectInject enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-serviceaccount.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# global.imagePullSecrets + +@test "telemetryCollector/ServiceAccount: can set image pull secrets" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-serviceaccount.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=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" ] +} + +#-------------------------------------------------------------------- +# telemetryCollector.serviceAccount.annotations + +@test "telemetryCollector/ServiceAccount: no annotations by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-serviceaccount.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.metadata.annotations | length > 0' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "telemetryCollector/ServiceAccount: annotations when enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-serviceaccount.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set "telemetryCollector.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 9aac754ed4..edf36a41f0 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -550,6 +550,11 @@ global: # @type: boolean enableGatewayMetrics: true + # Configures the Helm chart’s components to forward envoy metrics for the Consul service mesh to the + # consul-telemetry-collector. This includes gateway metrics and sidecar metrics. + # @type: boolean + enableTelemetryCollector: true + # 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: @@ -950,14 +955,14 @@ server: topologyKey: kubernetes.io/hostname # Toleration settings for server pods. This - # should be a multi-line string matching the - # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) + # should be a multi-line string matching the + # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) # array in a Pod spec. tolerations: "" # Pod topology spread constraints for server pods. - # This should be a multi-line YAML string matching the - # [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + # This should be a multi-line YAML string matching the + # [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) # array in a Pod Spec. # # This requires K8S >= 1.18 (beta) or 1.19 (stable). @@ -1076,7 +1081,7 @@ server: # @type: map extraEnvironmentVars: { } - # [Enterprise Only] Values for setting up and running + # [Enterprise Only] Values for setting up and running # [snapshot agents](https://developer.hashicorp.com/consul/commands/snapshot/agent) # within the Consul clusters. They run as a sidecar with Consul servers. snapshotAgent: @@ -1963,7 +1968,7 @@ connectInject: logLevel: null # Set the namespace to install the CNI plugin into. Overrides global namespace settings for CNI resources. - # Ex: "kube-system" + # Ex: "kube-system" # @type: string namespace: null @@ -2146,7 +2151,7 @@ connectInject: # @type: string annotations: null - # The resource settings for connect inject pods. The defaults, are optimized for getting started worklows on developer deployments. The settings should be tweaked for production deployments. + # The resource settings for connect inject pods. The defaults, are optimized for getting started worklows on developer deployments. The settings should be tweaked for production deployments. # @type: map resources: requests: @@ -2344,8 +2349,8 @@ connectInject: cpu: null # The resource settings for the Connect injected init container. If null, the resources - # won't be set for the initContainer. The defaults are optimized for developer instances of - # Kubernetes, however they should be tweaked with the recommended defaults as shown below to speed up service registration times. + # won't be set for the initContainer. The defaults are optimized for developer instances of + # Kubernetes, however they should be tweaked with the recommended defaults as shown below to speed up service registration times. # @type: map initContainer: resources: @@ -2534,7 +2539,7 @@ meshGateway: tolerations: null # Pod topology spread constraints for mesh gateway pods. - # This should be a multi-line YAML string matching the + # This should be a multi-line YAML string matching the # [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) # array in a Pod Spec. # @@ -2681,7 +2686,7 @@ ingressGateways: tolerations: null # Pod topology spread constraints for ingress gateway pods. - # This should be a multi-line YAML string matching the + # This should be a multi-line YAML string matching the # [`topologySpreadConstraints`](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) # array in a Pod Spec. # @@ -2918,7 +2923,7 @@ apiGateway: # @type: string nodeSelector: null - # Toleration settings for gateway pods created with the managed gateway class. + # Toleration settings for gateway pods created with the managed gateway class. # This should be a multi-line string matching the # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. # @@ -3082,3 +3087,86 @@ prometheus: # is only useful when running helm template. tests: enabled: true + +telemetryCollector: + # Enables the consul-telemetry-collector deployment + # @type: boolean + enabled: false + + # The name of the Docker image (including any tag) for the containers running + # the consul-telemetry-collector + # @type: string + image: "hashicorp/consul-telemetry-collector:0.0.1" + + # The resource settings for consul-telemetry-collector pods. + # @recurse: false + # @type: map + resources: + requests: + memory: "512Mi" + cpu: "1000m" + limits: + memory: "512Mi" + cpu: "1000m" + + # This value sets the number of consul-telemetry-collector replicas to deploy. + replicas: 1 + + # This value defines additional configuration for the telemetry collector. It should be formatted as a multi-line + # json blob string + # + # ```yaml + # customExporterConfig: | + # {"http_collector_endpoint": "other-otel-collector"} + # ``` + # + # @type: string + customExporterConfig: null + + service: + # This value defines additional annotations for the server service account. This should be formatted as a multi-line + # string. + # + # ```yaml + # annotations: | + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # ``` + # + # @type: string + annotations: null + + serviceAccount: + # This value defines additional annotations for the telemetry-collector's service account. This should be formatted + # as a multi-line string. + # + # ```yaml + # annotations: | + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # ``` + # + # @type: string + annotations: null + + cloud: + clientId: + secretName: null + secretKey: null + clientSecret: + secretName: null + secretKey: null + + initContainer: + # The resource settings for consul-telemetry-collector initContainer. + # @recurse: false + # @type: map + resources: {} + + # Optional YAML string to specify a nodeSelector config. + # @type: string + nodeSelector: null + + # Optional priorityClassName. + # @type: string + priorityClassName: "" From 5bd6c607b8ce23caea0782345b66668f1846a9fb Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Wed, 17 May 2023 09:50:41 -0400 Subject: [PATCH 159/592] NET-2619 - save ClusterIPs to manual vips table (#2124) --- .changelog/2124.txt | 3 + .../endpoints/endpoints_controller.go | 29 ++- .../endpoints/endpoints_controller_test.go | 51 ++++ .../controllers/configentry_controller.go | 61 +++++ .../configentry_controller_test.go | 233 ++++++++++++++++++ control-plane/go.mod | 6 +- control-plane/go.sum | 12 +- 7 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 .changelog/2124.txt diff --git a/.changelog/2124.txt b/.changelog/2124.txt new file mode 100644 index 0000000000..b65c23db2e --- /dev/null +++ b/.changelog/2124.txt @@ -0,0 +1,3 @@ +``release-note:improvement +control-plane: Transparent proxy enhancements for failover and virtual Services +``` diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index f408a21ea1..13f75f1156 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -1,6 +1,5 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 - package endpoints import ( @@ -295,6 +294,14 @@ func (r *Controller) registerServicesAndHealthCheck(apiClient *api.Client, pod c return err } + // Add manual ip to the VIP table + r.Log.Info("adding manual ip to virtual ip table in Consul", "name", serviceRegistration.Service.Service, + "id", serviceRegistration.ID) + err = assignServiceVirtualIP(r.Context, apiClient, serviceRegistration.Service) + if err != nil { + r.Log.Error(err, "failed to add ip to virtual ip table", "name", serviceRegistration.Service.Service) + } + // Register the proxy service instance with Consul. r.Log.Info("registering proxy service with Consul", "name", proxyServiceRegistration.Service.Service) _, err = apiClient.Catalog().Register(proxyServiceRegistration, nil) @@ -1258,6 +1265,26 @@ func (r *Controller) appendNodeMeta(registration *api.CatalogRegistration) { } } +// assignServiceVirtualIPs manually assigns the ClusterIP to the virtual IP table so that transparent proxy routing works. +func assignServiceVirtualIP(ctx context.Context, apiClient *api.Client, svc *api.AgentService) error { + ip := svc.TaggedAddresses[clusterIPTaggedAddressName].Address + if ip == "" { + return nil + } + + _, _, err := apiClient.Internal().AssignServiceVirtualIP(ctx, svc.Service, []string{ip}, &api.WriteOptions{Namespace: svc.Namespace, Partition: svc.Partition}) + if err != nil { + // Maintain backwards compatibility with older versions of Consul that do not support the VIP improvements. Tproxy + // will not work 100% correctly but the mesh will still work + if strings.Contains(err.Error(), "404") { + return fmt.Errorf("failed to add ip for service %s to virtual ip table. Please upgrade Consul to version 1.16 or higher", svc.Service) + } else { + return err + } + } + return nil +} + // hasBeenInjected checks the value of the status annotation and returns true if the Pod has been injected. func hasBeenInjected(pod corev1.Pod) bool { if anno, ok := pod.Annotations[constants.KeyInjectStatus]; ok && anno == constants.Injected { diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 7ea04a4e52..e9ae243a12 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -6414,3 +6414,54 @@ func createGatewayPod(name, ip string, annotations map[string]string) *corev1.Po } return pod } + +func TestReconcileAssignServiceVirtualIP(t *testing.T) { + t.Parallel() + ctx := context.Background() + cases := []struct { + name string + service *api.AgentService + expectErr bool + }{ + { + name: "valid service", + service: &api.AgentService{ + ID: "", + Service: "foo", + Port: 80, + Address: "1.2.3.4", + TaggedAddresses: map[string]api.ServiceAddress{ + "virtual": { + Address: "1.2.3.4", + Port: 80, + }, + }, + Meta: map[string]string{constants.MetaKeyKubeNS: "default"}, + }, + expectErr: false, + }, + { + name: "service missing IP should not error", + service: &api.AgentService{ + ID: "", + Service: "bar", + Meta: map[string]string{constants.MetaKeyKubeNS: "default"}, + }, + expectErr: false, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + apiClient := testClient.APIClient + err := assignServiceVirtualIP(ctx, apiClient, c.service) + if err != nil { + require.True(t, c.expectErr) + } else { + require.False(t, c.expectErr) + } + }) + } +} diff --git a/control-plane/controllers/configentry_controller.go b/control-plane/controllers/configentry_controller.go index c2c6da9071..f35d2e88e7 100644 --- a/control-plane/controllers/configentry_controller.go +++ b/control-plane/controllers/configentry_controller.go @@ -199,6 +199,7 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont return r.syncFailed(ctx, logger, crdCtrl, configEntry, ConsulAgentError, fmt.Errorf("writing config entry to consul: %w", err)) } + logger.Info("config entry created", "request-time", writeMeta.RequestTime) return r.syncSuccessful(ctx, crdCtrl, configEntry) } @@ -267,6 +268,15 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont return r.syncSuccessful(ctx, crdCtrl, configEntry) } + // For resolvers and splitters, we need to set the ClusterIP of the matching service to Consul so that transparent + // proxy works correctly. Do not fail the reconcile if assigning the virtual IP returns an error. + if needsVirtualIPAssignment(configEntry) { + err = assignServiceVirtualIP(ctx, logger, consulClient, crdCtrl, req.NamespacedName, configEntry, r.DatacenterName) + if err != nil { + logger.Error(err, "failed assigning service virtual ip") + } + } + return ctrl.Result{}, nil } @@ -381,6 +391,57 @@ func (r *ConfigEntryController) nonMatchingMigrationError(kubeEntry common.Confi return fmt.Errorf("migration failed: Kubernetes resource does not match existing Consul config entry: consul=%s, kube=%s", consulJSON, kubeJSON) } +// needsVirtualIPAssignment checks to see if a configEntry type needs to be assigned a virtual IP. +func needsVirtualIPAssignment(configEntry common.ConfigEntryResource) bool { + kubeKind := configEntry.KubeKind() + if kubeKind == common.ServiceResolver || kubeKind == common.ServiceRouter || kubeKind == common.ServiceSplitter { + return true + } + return false +} + +// assignServiceVirtualIPs manually sends the ClusterIP for a matching service for ServiceRouter or ServiceSplitter +// CRDs to Consul so that it can be added to the virtual IP table. The assignment is skipped if the matching service +// does not exist or if an older version of Consul is being used. Endpoints Controller, on service registration, also +// manually sends a ClusterIP when a service is created. This increases the chance of a real IP ending up in the +// discovery chain. +func assignServiceVirtualIP(ctx context.Context, logger logr.Logger, consulClient *capi.Client, crdCtrl Controller, namespacedName types.NamespacedName, configEntry common.ConfigEntryResource, datacenter string) error { + service := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: configEntry.KubernetesName(), + Namespace: namespacedName.Namespace, + }, + } + if err := crdCtrl.Get(ctx, namespacedName, &service); err != nil { + // It is non-fatal if the service does not exist. The ClusterIP will get added when the service is registered in + // the endpoints controller + if k8serr.IsNotFound(err) { + return nil + } + // Something is really wrong with the service + return err + } + + wo := &capi.WriteOptions{ + Namespace: configEntry.ToConsul(datacenter).GetNamespace(), + Partition: configEntry.ToConsul(datacenter).GetPartition(), + } + + logger.Info("adding manual ip to virtual ip table in Consul", "name", service.Name) + _, _, err := consulClient.Internal().AssignServiceVirtualIP(ctx, configEntry.KubernetesName(), []string{service.Spec.ClusterIP}, wo) + if err != nil { + // Maintain backwards compatibility with older versions of Consul that do not support the manual VIP improvements. With the older version, the mesh + // will still work. + if isNotFoundErr(err) { + logger.Error(err, "failed to add ip to virtual ip table. Please upgrade Consul to version 1.16 or higher", "name", service.Name) + return nil + } else { + return err + } + } + return nil +} + func isNotFoundErr(err error) bool { return err != nil && strings.Contains(err.Error(), "404") } diff --git a/control-plane/controllers/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go index 494715cf4f..d548f78069 100644 --- a/control-plane/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -22,6 +22,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -1891,3 +1892,235 @@ func TestConfigEntryController_Migration(t *testing.T) { }) } } + +func TestConfigEntryControllers_assignServiceVirtualIP(t *testing.T) { + t.Parallel() + kubeNS := "default" + + cases := []struct { + name string + kubeKind string + consulKind string + consulPrereqs []capi.ConfigEntry + configEntryResource common.ConfigEntryResource + service corev1.Service + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) Controller + expectErr bool + }{ + { + name: "ServiceResolver no error and vip should be assigned", + kubeKind: "ServiceResolver", + consulKind: capi.ServiceRouter, + configEntryResource: &v1alpha1.ServiceRouter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: v1alpha1.ServiceRouterSpec{ + Routes: []v1alpha1.ServiceRoute{ + { + Match: &v1alpha1.ServiceRouteMatch{ + HTTP: &v1alpha1.ServiceRouteHTTPMatch{ + PathPrefix: "/admin", + }, + }, + }, + }, + }, + }, + service: corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "10.0.0.1", + Ports: []corev1.ServicePort{ + { + Port: 8081, + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) Controller { + return &ServiceRouterController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + expectErr: false, + }, + { + name: "ServiceRouter no error and vip should be assigned", + kubeKind: "ServiceRouter", + consulKind: capi.ServiceRouter, + consulPrereqs: []capi.ConfigEntry{ + &capi.ServiceConfigEntry{ + Kind: capi.ServiceDefaults, + Name: "bar", + Protocol: "http", + }, + }, + configEntryResource: &v1alpha1.ServiceRouter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: kubeNS, + }, + Spec: v1alpha1.ServiceRouterSpec{ + Routes: []v1alpha1.ServiceRoute{ + { + Match: &v1alpha1.ServiceRouteMatch{ + HTTP: &v1alpha1.ServiceRouteHTTPMatch{ + PathPrefix: "/admin", + }, + }, + }, + }, + }, + }, + service: corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: kubeNS, + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "10.0.0.2", + Ports: []corev1.ServicePort{ + { + Port: 8081, + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) Controller { + return &ServiceRouterController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + expectErr: false, + }, + { + name: "ServiceRouter should fail because service does not have a valid IP address", + kubeKind: "ServiceRouter", + consulKind: capi.ServiceRouter, + consulPrereqs: []capi.ConfigEntry{ + &capi.ServiceConfigEntry{ + Kind: capi.ServiceDefaults, + Name: "bar", + Protocol: "http", + }, + }, + configEntryResource: &v1alpha1.ServiceRouter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: kubeNS, + }, + Spec: v1alpha1.ServiceRouterSpec{ + Routes: []v1alpha1.ServiceRoute{ + { + Match: &v1alpha1.ServiceRouteMatch{ + HTTP: &v1alpha1.ServiceRouteHTTPMatch{ + PathPrefix: "/admin", + }, + }, + }, + }, + }, + }, + service: corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: kubeNS, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) Controller { + return &ServiceRouterController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + expectErr: true, + }, + { + name: "ServiceSplitter no error because a matching service does not exist", + kubeKind: "ServiceSplitter", + consulKind: capi.ServiceSplitter, + consulPrereqs: []capi.ConfigEntry{ + &capi.ServiceConfigEntry{ + Kind: capi.ServiceDefaults, + Name: "foo", + Protocol: "http", + }, + }, + configEntryResource: &v1alpha1.ServiceSplitter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: v1alpha1.ServiceSplitterSpec{ + Splits: []v1alpha1.ServiceSplit{ + { + Weight: 100, + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) Controller { + return &ServiceSplitterController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + expectErr: false, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + ctx := context.Background() + + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResource) + s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Service{}) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(&c.service, c.configEntryResource).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + testClient.TestServer.WaitForLeader(t) + consulClient := testClient.APIClient + + ctrl := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + namespacedName := types.NamespacedName{ + Namespace: kubeNS, + Name: c.configEntryResource.KubernetesName(), + } + + err := assignServiceVirtualIP(ctx, ctrl.Logger(namespacedName), consulClient, ctrl, namespacedName, c.configEntryResource, "dc1") + if err != nil { + require.True(t, c.expectErr) + } else { + require.False(t, c.expectErr) + } + }) + } +} diff --git a/control-plane/go.mod b/control-plane/go.mod index 4cf30f5816..6a565a080d 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -8,9 +8,9 @@ require ( github.com/go-logr/logr v0.4.0 github.com/google/go-cmp v0.5.8 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 - github.com/hashicorp/consul-server-connection-manager v0.1.0 - github.com/hashicorp/consul/api v1.10.1-0.20230427155444-391ed069c461 + github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d + github.com/hashicorp/consul-server-connection-manager v0.1.2 + github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f diff --git a/control-plane/go.sum b/control-plane/go.sum index f49bc4b2d5..9d5bf5e90c 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -348,13 +348,13 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8 h1:TQY0oKtLV15UNYWeSkTxi4McBIyLecsEtbc/VfxvbYA= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20220831174802-b8af65262de8/go.mod h1:aw35GB76URgbtxaSSMxbOetbG7YEHHPkIX3/SkTBaWc= -github.com/hashicorp/consul-server-connection-manager v0.1.0 h1:XCweGvMHzra88rYv2zxwwuUOjBUdcQmNKVrnQmt/muo= -github.com/hashicorp/consul-server-connection-manager v0.1.0/go.mod h1:XVVlO+Yk7aiRpspiHZkrrFVn9BJIiOPnQIzqytPxGaU= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d h1:RJ1MZ8JKnfgKQ1kR3IBQAMpOpzXrdseZAYN/QR//MFM= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= +github.com/hashicorp/consul-server-connection-manager v0.1.2 h1:tNVQHUPuMbd+cMdD8kd+qkZUYpmLmrHMAV/49f4L53I= +github.com/hashicorp/consul-server-connection-manager v0.1.2/go.mod h1:NzQoVi1KcxGI2SangsDue8+ZPuXZWs+6BKAKrDNyg+w= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1-0.20230427155444-391ed069c461 h1:cbsTR88ShbvcRMqLU8K0atm4GmRr8UH4x4jX4e12RYE= -github.com/hashicorp/consul/api v1.10.1-0.20230427155444-391ed069c461/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= +github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca h1:5UPVYOlJg/HBEJ2q82rkkQ3ZLzeMnF5MOpGcw2kh+XU= +github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= From 7b09da1fb76e428745682ea31c3d914516e44c16 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Wed, 17 May 2023 18:26:56 -0400 Subject: [PATCH 160/592] Get the consul version from values.yaml (#2146) --- Makefile | 5 +++++ control-plane/build-support/scripts/consul-version.sh | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100755 control-plane/build-support/scripts/consul-version.sh diff --git a/Makefile b/Makefile index daee6b693b..ea94b303e9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ VERSION = $(shell ./control-plane/build-support/scripts/version.sh control-plane/version/version.go) +CONSUL_VERSION = $(shell ./control-plane/build-support/scripts/consul-version.sh charts/consul/values.yaml) # ===========> Helm Targets @@ -147,6 +148,10 @@ ci.aws-acceptance-test-cleanup: ## Deletes AWS resources left behind after faile version: @echo $(VERSION) +consul-version: + @echo $(CONSUL_VERSION) + + # ===========> Release Targets prepare-release: ## Sets the versions, updates changelog to prepare this repository to release diff --git a/control-plane/build-support/scripts/consul-version.sh b/control-plane/build-support/scripts/consul-version.sh new file mode 100755 index 0000000000..634b5db07d --- /dev/null +++ b/control-plane/build-support/scripts/consul-version.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +FILE=$1 +VERSION=$(yq .global.image $FILE) + +# echo full string "hashicorp/consul:1.15.1" | remove first and last characters | cut everything before ':' +echo "${VERSION}" | sed 's/^.//;s/.$//' | cut -d ':' -f2- From 85c28bd4d76c6f8539060fdd8e1d8a90716becb4 Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 10:28:38 -0400 Subject: [PATCH 161/592] [COMPLIANCE] Add Copyright and License Headers (#2079) Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- cli/cmd/config/command.go | 3 +++ cli/cmd/config/read/command.go | 3 +++ cli/cmd/config/read/command_test.go | 3 +++ control-plane/api/v1alpha1/samenessgroup_types.go | 3 +++ control-plane/config/rbac/role.yaml | 3 +++ control-plane/config/webhook/manifests.yaml | 3 +++ control-plane/controllers/samenessgroups_controller.go | 3 +++ 7 files changed, 21 insertions(+) diff --git a/cli/cmd/config/command.go b/cli/cmd/config/command.go index 5e44677ff6..302b054bd9 100644 --- a/cli/cmd/config/command.go +++ b/cli/cmd/config/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package config import ( diff --git a/cli/cmd/config/read/command.go b/cli/cmd/config/read/command.go index e2258bd013..0565294bd0 100644 --- a/cli/cmd/config/read/command.go +++ b/cli/cmd/config/read/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package read import ( diff --git a/cli/cmd/config/read/command_test.go b/cli/cmd/config/read/command_test.go index a3716cf3c1..07275f872a 100644 --- a/cli/cmd/config/read/command_test.go +++ b/cli/cmd/config/read/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package read import ( diff --git a/control-plane/api/v1alpha1/samenessgroup_types.go b/control-plane/api/v1alpha1/samenessgroup_types.go index 7f5f194150..86b02445f6 100644 --- a/control-plane/api/v1alpha1/samenessgroup_types.go +++ b/control-plane/api/v1alpha1/samenessgroup_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 562eb5f9f9..bd2ce10a25 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index 663ace9d70..305e3bbe67 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration diff --git a/control-plane/controllers/samenessgroups_controller.go b/control-plane/controllers/samenessgroups_controller.go index afc243f267..1d768cc968 100644 --- a/control-plane/controllers/samenessgroups_controller.go +++ b/control-plane/controllers/samenessgroups_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controllers import ( From 889b689a8a4f3e8d0e01eb809b535d4a8b00bc5e Mon Sep 17 00:00:00 2001 From: John Maguire Date: Tue, 23 May 2023 15:36:39 -0400 Subject: [PATCH 162/592] Update go-discover (#2157) * update go-discove so we're not pulling in a version of tencent cloud that no longer exists * Update go discover to latest --- control-plane/go.mod | 19 ++++---- control-plane/go.sum | 108 +++++++++++++++++-------------------------- 2 files changed, 53 insertions(+), 74 deletions(-) diff --git a/control-plane/go.mod b/control-plane/go.mod index 6a565a080d..da581c221f 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-bexpr v0.1.11 - github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f + github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 github.com/hashicorp/go-hclog v1.2.2 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-netaddrs v0.1.0 @@ -22,7 +22,7 @@ require ( github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.8.3 github.com/kr/text v0.2.0 - github.com/miekg/dns v1.1.41 + github.com/miekg/dns v1.1.50 github.com/mitchellh/cli v1.1.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 @@ -54,7 +54,7 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/aws/aws-sdk-go v1.25.41 // indirect + github.com/aws/aws-sdk-go v1.44.262 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect @@ -96,12 +96,12 @@ require ( github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/linode/linodego v0.7.1 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect @@ -126,18 +126,21 @@ require ( github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.2.0 // indirect - github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658 // indirect github.com/vmware/govmomi v0.18.0 // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.5.0 // indirect + golang.org/x/tools v0.3.0 // indirect google.golang.org/api v0.43.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 9d5bf5e90c..8dd50a5843 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -44,11 +44,9 @@ github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= @@ -56,11 +54,8 @@ github.com/Azure/go-autorest/autorest/azure/auth v0.5.0 h1:nSMjYIe24eBYasAIxt859 github.com/Azure/go-autorest/autorest/azure/auth v0.5.0/go.mod h1:QRTvSZQpxqm8mSErhnbI+tANIBAKP7B+UIE2z4ypUO0= github.com/Azure/go-autorest/autorest/azure/cli v0.4.0 h1:Ml+UCrnlKD+cJmSzrZ/RDcDw86NjkRUpnFh7V5JUhzU= github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -68,11 +63,9 @@ github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+X github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.3.0 h1:3I9AAI63HfcLtphd9g39ruUwRI+Ca+z/f36KHPFRUss= github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -81,9 +74,7 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= @@ -102,8 +93,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.25.41 h1:/hj7nZ0586wFqpwjNpzWiUTwtaMgxAZNZKHay80MdXw= -github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= +github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -169,14 +160,11 @@ github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72 github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.7.5/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/digitalocean/godo v1.10.0 h1:uW1/FcvZE/hoixnJcnlmIUvTVNdZCLjRLzmDtRi1xXY= github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= @@ -191,7 +179,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -210,7 +197,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -227,14 +213,10 @@ github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 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/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -247,7 +229,6 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -262,7 +243,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -301,7 +281,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -331,9 +310,6 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= @@ -369,8 +345,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f h1:7WFMVeuJQp6BkzuTv9O52pzwtEFVUJubKYN+zez8eTI= -github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f/go.mod h1:D4eo8/CN92vm9/9UDG+ldX1/fMFa4kpl8qzyTolus8o= +github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 h1:WUwSDou+memX/pb6xnjA0PfAqEEJtdWSrK00kl8ySK8= +github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530/go.mod h1:RH2Jr1/cCsZ1nRLmAOC65hp/gRehf55SsUIYV2+NAxI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= @@ -419,7 +395,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= @@ -447,20 +422,19 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= -github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -490,7 +464,6 @@ github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -498,22 +471,25 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 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.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= @@ -563,7 +539,6 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= @@ -571,7 +546,6 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= @@ -643,7 +617,6 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -685,8 +658,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible h1:8uRvJleFpqLsO77WaAh2UrasMOzd8MxXrNj20e7El+Q= -github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658 h1:q208plt7F8Pj3b1w8D3XDb/vTgHsn/JlEwDCSe+lvyo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658 h1:wF/3PTojsx9/J8CaeiTy0zXxvwrcuu282R4g7fDlgCQ= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658/go.mod h1:LLex9maWMIQzOFF/mYz5rEsTUwKKcmAbGRehSNRWqgQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -701,6 +676,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= @@ -753,10 +729,10 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -797,7 +773,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -819,7 +797,6 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -847,6 +824,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -872,9 +852,10 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -900,7 +881,6 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -943,21 +923,27 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/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 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -966,6 +952,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -977,7 +965,6 @@ golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0k golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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= @@ -1034,7 +1021,10 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= 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= @@ -1154,7 +1144,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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= @@ -1163,7 +1152,6 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1199,35 +1187,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4= k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA= -k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI= -k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M= k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 h1:XmRqFcQlCy/lKRZ39j+RVpokYNroHPqV3mcBRfnhT5o= k8s.io/utils v0.0.0-20220812165043-ad590609e2e5/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -1237,11 +1216,8 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.10.2 h1:jW8qiY+yMnnPx6O9hu63tgcwaKzd1yLYui+mpvClOOc= sigs.k8s.io/controller-runtime v0.10.2/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From d5b460195b900572aa8e359fecfc8873a8421500 Mon Sep 17 00:00:00 2001 From: John Murret Date: Tue, 23 May 2023 16:11:31 -0600 Subject: [PATCH 163/592] add helm chart values to configure global server side rate limiting (#2170) * add helm chart values to configure global server side rate limiting * add changelog. * update server checksum for configmap * fix the other 2 checksums --- .changelog/2170.txt | 2 + .../templates/server-config-configmap.yaml | 9 ++ .../test/unit/server-config-configmap.bats | 92 +++++++++++++++++++ .../consul/test/unit/server-statefulset.bats | 8 +- charts/consul/values.yaml | 37 ++++++++ 5 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 .changelog/2170.txt diff --git a/.changelog/2170.txt b/.changelog/2170.txt new file mode 100644 index 0000000000..6d10ae1097 --- /dev/null +++ b/.changelog/2170.txt @@ -0,0 +1,2 @@ +```release-note:feature +Add support for configuring global level server rate limiting. diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index f7dd85f166..1ad04a42b5 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -1,4 +1,6 @@ {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} +{{- if (not (or (eq .Values.server.limits.requestLimits.mode "disabled") (eq .Values.server.limits.requestLimits.mode "permissive") (eq .Values.server.limits.requestLimits.mode "enforce"))) }}{{fail "server.limits.requestLimits.mode must be one of the following values: disabled, permissive, and enforce." }}{{ end -}} + # StatefulSet to run the actual Consul server cluster. apiVersion: v1 kind: ConfigMap @@ -26,6 +28,13 @@ data: "datacenter": "{{ .Values.global.datacenter }}", "data_dir": "/consul/data", "domain": "{{ .Values.global.domain }}", + "limits": { + "request_limits": { + "mode": "{{ .Values.server.limits.requestLimits.mode }}", + "read_rate": {{ .Values.server.limits.requestLimits.readRate }}, + "write_rate": {{ .Values.server.limits.requestLimits.writeRate }} + } + }, "ports": { {{- if not .Values.global.tls.enabled }} "grpc": 8502, diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 5e849d5a4f..2c8a83f4ca 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -965,3 +965,95 @@ load _helpers [ "${actual}" = "true" ] } + +#-------------------------------------------------------------------- +# server.limits.requestLimits + +@test "server/ConfigMap: server.limits.requestLimits.mode is disabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .limits.request_limits.mode | tee /dev/stderr) + + [ "${actual}" = "disabled" ] +} + +@test "server/ConfigMap: server.limits.requestLimits.mode accepts disabled, permissive, and enforce" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.limits.requestLimits.mode=disabled' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .limits.request_limits.mode | tee /dev/stderr) + + [ "${actual}" = "disabled" ] + + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.limits.requestLimits.mode=permissive' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .limits.request_limits.mode | tee /dev/stderr) + + [ "${actual}" = "permissive" ] + + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.limits.requestLimits.mode=enforce' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .limits.request_limits.mode | tee /dev/stderr) + + [ "${actual}" = "enforce" ] +} + +@test "server/ConfigMap: server.limits.requestLimits.mode errors with value other than disabled, permissive, and enforce" { + cd `chart_dir` + run helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.limits.requestLimits.mode=notvalid' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "server.limits.requestLimits.mode must be one of the following values: disabled, permissive, and enforce" ]] +} + +@test "server/ConfigMap: server.limits.request_limits.read_rate is -1 by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .limits.request_limits.read_rate | tee /dev/stderr) + + [ "${actual}" = "-1" ] +} + +@test "server/ConfigMap: server.limits.request_limits.read_rate is set properly when specified " { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.limits.requestLimits.readRate=100' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .limits.request_limits.read_rate | tee /dev/stderr) + + [ "${actual}" = "100" ] +} + +@test "server/ConfigMap: server.limits.request_limits.write_rate is -1 by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .limits.request_limits.write_rate | tee /dev/stderr) + + [ "${actual}" = "-1" ] +} + +@test "server/ConfigMap: server.limits.request_limits.write_rate is set properly when specified " { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.limits.requestLimits.writeRate=100' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .limits.request_limits.write_rate | tee /dev/stderr) + + [ "${actual}" = "100" ] +} diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 2d21cf7c1e..f0a8b0112b 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -686,7 +686,7 @@ load _helpers -s templates/server-statefulset.yaml \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 251dd23c6cc44bf8362acddc24c78440b6a65c4618785d027fae526958af5dde ] + [ "${actual}" = 3714bf1fbca840dcd10b5aeb40c6ef35e349bd534727abe80b831c77f88da7da ] } @test "server/StatefulSet: adds config-checksum annotation when extraConfig is provided" { @@ -696,7 +696,7 @@ load _helpers --set 'server.extraConfig="{\"hello\": \"world\"}"' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 473d54d05b794be1526d42ef04fdc049f4f979a75d3394c897eef149d399207d ] + [ "${actual}" = 87126fac3c7704fba1d4265201ad0345f4d972d2123df6e6fb416b40c5823d80 ] } @test "server/StatefulSet: adds config-checksum annotation when config is updated" { @@ -706,7 +706,7 @@ load _helpers --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 6acd3761c0981d4d6194b3375b0f7a291e3927602ce7857344c26010381d3a61 ] + [ "${actual}" = d98ff135c5dee661058f33e29970675761ecf235676db0d4d24f354908eee425 ] } #-------------------------------------------------------------------- @@ -2773,4 +2773,4 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ . | tee /dev/stderr | yq -r '.spec.template.spec.containers[1].command[2] | contains("-interval=10h34m5s")' | tee /dev/stderr) [ "${actual}" = "true" ] -} \ No newline at end of file +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index edf36a41f0..a0660c5020 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -1131,6 +1131,43 @@ server: # @type: string caCert: null + # Settings for potentially limiting timeouts, rate limiting on clients as well + # as servers, and other settings to limit exposure too many requests, requests + # waiting for too long, and other runtime considerations. + limits: + # This object specifies configurations that limit the rate of RPC and gRPC + # requests on the Consul server. Limiting the rate of gRPC and RPC requests + # also limits HTTP requests to the Consul server. + # https://developer.hashicorp.com/consul/docs/agent/config/config-files#request_limits + requestLimits: + # Setting for disabling or enabling rate limiting. If not disabled, it + # enforces the action that will occur when RequestLimitsReadRate + # or RequestLimitsWriteRate is exceeded. The default value of "disabled" will + # prevent any rate limiting from occuring. A value of "enforce" will block + # the request from processings by returning an error. A value of + # "permissive" will not block the request and will allow the request to + # continue processing. + # @type: string + mode: "disabled" + + # Setting that controls how frequently RPC, gRPC, and HTTP + # queries are allowed to happen. In any large enough time interval, rate + # limiter limits the rate to RequestLimitsReadRate tokens per second. + # + # See https://en.wikipedia.org/wiki/Token_bucket for more about token + # buckets. + # @type: integer + readRate: -1 + + # Setting that controls how frequently RPC, gRPC, and HTTP + # writes are allowed to happen. In any large enough time interval, rate + # limiter limits the rate to RequestLimitsWriteRate tokens per second. + # + # See https://en.wikipedia.org/wiki/Token_bucket for more about token + # buckets. + # @type: integer + writeRate: -1 + # Configuration for Consul servers when the servers are running outside of Kubernetes. # When running external servers, configuring these values is recommended # if setting `global.tls.enableAutoEncrypt` to true From 13c166f2657bfbeb1ca4e80684e2b3f172872169 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Thu, 25 May 2023 10:05:39 -0700 Subject: [PATCH 164/592] Disable DNS redirection when tproxy is disabled (#2176) * Disable DNS redirection when tproxy is disabled DNS redirection and the various settings that make that possible (like the dataplane binding to a port for DNS) is only useful if tproxy is enabled. Most of the code checked if tproxy was enabled but there was one location where we didn't check. This resulted in a bug with our multiport support where even though tproxy is disabled, we tried to setup the dataplane to proxy DNS. This meant each dataplane tried to bind to 8600 but because there are >1 dataplanes with multiport, there was a port conflict. This PR fixes the location where we didn't check if tproxy was enabled and as a result fixes the multiport issue. --- .changelog/2176.txt | 3 + .../tests/connect/connect_inject_test.go | 7 +- .../webhook/consul_dataplane_sidecar.go | 6 +- .../webhook/consul_dataplane_sidecar_test.go | 124 ++++++++++++++-- .../connect-inject/webhook/container_init.go | 14 +- .../webhook/container_init_test.go | 3 +- .../connect-inject/webhook/mesh_webhook.go | 10 +- .../webhook/mesh_webhook_test.go | 139 ++++++++++++++++++ .../webhook/redirect_traffic.go | 2 +- 9 files changed, 281 insertions(+), 27 deletions(-) create mode 100644 .changelog/2176.txt diff --git a/.changelog/2176.txt b/.changelog/2176.txt new file mode 100644 index 0000000000..0aee796433 --- /dev/null +++ b/.changelog/2176.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: fix issue with multiport pods crashlooping due to dataplane port conflicts by ensuring dns redirection is disabled for non-tproxy pods +``` diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index dd3865ae46..3f1660319f 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -136,13 +136,10 @@ func TestConnectInject_MultiportServices(t *testing.T) { cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) - // Multi port apps don't work with transparent proxy. - if cfg.EnableTransparentProxy { - t.Skipf("skipping this test because transparent proxy is enabled") - } - helmValues := map[string]string{ "connectInject.enabled": "true", + // Enable DNS so we can test that DNS redirection _isn't_ set in the pod. + "dns.enabled": "true", "global.tls.enabled": strconv.FormatBool(secure), "global.acls.manageSystemACLs": strconv.FormatBool(secure), diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index 70f3be82da..f5134f208f 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -314,7 +314,11 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, mpi mu // If Consul DNS is enabled, we want to configure consul-dataplane to be the DNS proxy // for Consul DNS in the pod. - if w.EnableConsulDNS { + dnsEnabled, err := consulDNSEnabled(namespace, pod, w.EnableConsulDNS, w.EnableTransparentProxy) + if err != nil { + return nil, err + } + if dnsEnabled { args = append(args, "-consul-dns-bind-port="+strconv.Itoa(consulDataplaneDNSBindPort)) } diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index f7cb7bc594..e759b627fe 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -290,24 +290,115 @@ func TestHandlerConsulDataplaneSidecar_Concurrency(t *testing.T) { } } +// Test that we pass the dns proxy flag to dataplane correctly. func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - EnableConsulDNS: true, + + // We only want the flag passed when DNS and tproxy are both enabled. DNS/tproxy can + // both be enabled/disabled with annotations/labels on the pod and namespace and then globally + // through the helm chart. To test this we use an outer loop with the possible DNS settings and then + // and inner loop with possible tproxy settings. + dnsCases := []struct { + GlobalConsulDNS bool + NamespaceDNS *bool + PodDNS *bool + ExpEnabled bool + }{ + { + GlobalConsulDNS: false, + ExpEnabled: false, + }, + { + GlobalConsulDNS: true, + ExpEnabled: true, + }, + { + GlobalConsulDNS: false, + NamespaceDNS: boolPtr(true), + ExpEnabled: true, + }, + { + GlobalConsulDNS: false, + PodDNS: boolPtr(true), + ExpEnabled: true, + }, } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, + tproxyCases := []struct { + GlobalTProxy bool + NamespaceTProxy *bool + PodTProxy *bool + ExpEnabled bool + }{ + { + GlobalTProxy: false, + ExpEnabled: false, + }, + { + GlobalTProxy: true, + ExpEnabled: true, + }, + { + GlobalTProxy: false, + NamespaceTProxy: boolPtr(true), + ExpEnabled: true, + }, + { + GlobalTProxy: false, + PodTProxy: boolPtr(true), + ExpEnabled: true, }, } - container, err := h.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) - require.NoError(t, err) - require.Contains(t, container.Args, "-consul-dns-bind-port=8600") + + // Outer loop is permutations of dns being enabled. Inner loop is permutations of tproxy being enabled. + // Both must be enabled for dns to be enabled. + for i, dnsCase := range dnsCases { + for j, tproxyCase := range tproxyCases { + t.Run(fmt.Sprintf("dns=%d,tproxy=%d", i, j), func(t *testing.T) { + + // Test setup. + h := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + EnableTransparentProxy: tproxyCase.GlobalTProxy, + EnableConsulDNS: dnsCase.GlobalConsulDNS, + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + if dnsCase.PodDNS != nil { + pod.Annotations[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.PodDNS) + } + if tproxyCase.PodTProxy != nil { + pod.Annotations[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.PodTProxy) + } + + ns := testNS + if dnsCase.NamespaceDNS != nil { + ns.Labels[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.NamespaceDNS) + } + if tproxyCase.NamespaceTProxy != nil { + ns.Labels[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.NamespaceTProxy) + } + + // Actual test here. + container, err := h.consulDataplaneSidecar(ns, pod, multiPortInfo{}) + require.NoError(t, err) + // Flag should only be passed if both tproxy and dns are enabled. + if tproxyCase.ExpEnabled && dnsCase.ExpEnabled { + require.Contains(t, container.Args, "-consul-dns-bind-port=8600") + } else { + require.NotContains(t, container.Args, "-consul-dns-bind-port=8600") + } + }) + } + } } func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck(t *testing.T) { @@ -1202,3 +1293,8 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { }) } } + +// boolPtr returns pointer to b. +func boolPtr(b bool) *bool { + return &b +} diff --git a/control-plane/connect-inject/webhook/container_init.go b/control-plane/connect-inject/webhook/container_init.go index b33c8f4d3e..f180de88a3 100644 --- a/control-plane/connect-inject/webhook/container_init.go +++ b/control-plane/connect-inject/webhook/container_init.go @@ -267,7 +267,17 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, // consulDNSEnabled returns true if Consul DNS should be enabled for this pod. // It returns an error when the annotation value cannot be parsed by strconv.ParseBool or if we are unable // to read the pod's namespace label when it exists. -func consulDNSEnabled(namespace corev1.Namespace, pod corev1.Pod, globalEnabled bool) (bool, error) { +func consulDNSEnabled(namespace corev1.Namespace, pod corev1.Pod, globalDNSEnabled bool, globalTProxyEnabled bool) (bool, error) { + // DNS is only possible when tproxy is also enabled because it relies + // on traffic being redirected. + tproxy, err := common.TransparentProxyEnabled(namespace, pod, globalTProxyEnabled) + if err != nil { + return false, err + } + if !tproxy { + return false, nil + } + // First check to see if the pod annotation exists to override the namespace or global settings. if raw, ok := pod.Annotations[constants.KeyConsulDNS]; ok { return strconv.ParseBool(raw) @@ -277,7 +287,7 @@ func consulDNSEnabled(namespace corev1.Namespace, pod corev1.Pod, globalEnabled return strconv.ParseBool(raw) } // Else fall back to the global default. - return globalEnabled, nil + return globalDNSEnabled, nil } // splitCommaSeparatedItemsFromAnnotation takes an annotation and a pod diff --git a/control-plane/connect-inject/webhook/container_init_test.go b/control-plane/connect-inject/webhook/container_init_test.go index 0e2de79bb1..fd89d7eba6 100644 --- a/control-plane/connect-inject/webhook/container_init_test.go +++ b/control-plane/connect-inject/webhook/container_init_test.go @@ -937,7 +937,8 @@ func TestHandlerContainerInit_Resources(t *testing.T) { var testNS = corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ - Name: k8sNamespace, + Name: k8sNamespace, + Labels: map[string]string{}, }, } diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 7105e19e67..96c73d93d4 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -396,13 +396,17 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi pod.Annotations[constants.KeyTransparentProxyStatus] = constants.Enabled } - // If tproxy with DNS redirection is enabled, we want to configure dns on the pod. - if tproxyEnabled && w.EnableConsulDNS { + // If DNS redirection is enabled, we want to configure dns on the pod. + dnsEnabled, err := consulDNSEnabled(*ns, pod, w.EnableConsulDNS, w.EnableTransparentProxy) + if err != nil { + w.Log.Error(err, "error determining if dns redirection is enabled", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error determining if dns redirection is enabled: %s", err)) + } + if dnsEnabled { if err = w.configureDNS(&pod, req.Namespace); err != nil { w.Log.Error(err, "error configuring DNS on the pod", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring DNS on the pod: %s", err)) } - } // Add annotations for metrics. diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index 9b93d8d984..20c66bd57a 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -811,6 +811,145 @@ func TestHandlerHandle(t *testing.T) { }, }, }, + { + "dns redirection enabled", + MeshWebhook{ + Log: logrtest.TestLogger{T: t}, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableTransparentProxy: true, + TProxyOverwriteProbes: true, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + Annotations: map[string]string{constants.KeyConsulDNS: "true"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), + }, + + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/spec/dnsPolicy", + }, + { + Operation: "add", + Path: "/spec/dnsConfig", + }, + }, + }, + { + "dns redirection only enabled if tproxy enabled", + MeshWebhook{ + Log: logrtest.TestLogger{T: t}, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableTransparentProxy: true, + TProxyOverwriteProbes: true, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + Annotations: map[string]string{ + constants.KeyConsulDNS: "true", + constants.KeyTransparentProxy: "false", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + // Note: no DNS policy/config additions. + }, + }, } for _, tt := range cases { diff --git a/control-plane/connect-inject/webhook/redirect_traffic.go b/control-plane/connect-inject/webhook/redirect_traffic.go index 7066929dae..b0cbefeeaa 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic.go +++ b/control-plane/connect-inject/webhook/redirect_traffic.go @@ -98,7 +98,7 @@ func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (s // Add init container user ID to exclude from traffic redirection. cfg.ExcludeUIDs = append(cfg.ExcludeUIDs, strconv.Itoa(initContainersUserAndGroupID)) - dnsEnabled, err := consulDNSEnabled(ns, pod, w.EnableConsulDNS) + dnsEnabled, err := consulDNSEnabled(ns, pod, w.EnableConsulDNS, w.EnableTransparentProxy) if err != nil { return "", err } From eac1df848f551e42e8b7421f881ac94c86eef771 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Thu, 25 May 2023 13:28:27 -0700 Subject: [PATCH 165/592] Fix tests (#2181) --- .../webhook/consul_dataplane_sidecar_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index e759b627fe..e127915218 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -379,7 +379,12 @@ func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { pod.Annotations[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.PodTProxy) } - ns := testNS + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: k8sNamespace, + Labels: map[string]string{}, + }, + } if dnsCase.NamespaceDNS != nil { ns.Labels[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.NamespaceDNS) } From 1aa138ae4d84fc74adaca2c5eefd2d62090a6cb0 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 26 May 2023 09:42:09 -0400 Subject: [PATCH 166/592] [API Gateway] Add stub acceptance test (#2185) --- acceptance/tests/api-gateway/example_test.go | 64 ++++++++++++++++++++ acceptance/tests/api-gateway/main_test.go | 37 +++++++++++ 2 files changed, 101 insertions(+) create mode 100644 acceptance/tests/api-gateway/example_test.go create mode 100644 acceptance/tests/api-gateway/main_test.go diff --git a/acceptance/tests/api-gateway/example_test.go b/acceptance/tests/api-gateway/example_test.go new file mode 100644 index 0000000000..b324ac31fe --- /dev/null +++ b/acceptance/tests/api-gateway/example_test.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Rename package to your test package. +package example + +import ( + "context" + "testing" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestExample(t *testing.T) { + // Get test configuration. + cfg := suite.Config() + + // Get the default context. + ctx := suite.Environment().DefaultContext(t) + + // Create Helm values for the Helm install. + helmValues := map[string]string{ + "exampleFeature.enabled": "true", + } + + // Generate a random name for this test. + releaseName := helpers.RandomName() + + // Create a new Consul cluster object. + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + + // Create the Consul cluster with Helm. + consulCluster.Create(t) + + // Make test assertions. + + // To run kubectl commands, you need to get KubectlOptions from the test context. + // There are a number of kubectl commands available in the helpers/kubectl.go file. + // For example, to call 'kubectl apply' from the test write the following: + k8s.KubectlApply(t, ctx.KubectlOptions(t), "path/to/config") + + // Clean up any Kubernetes resources you have created + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDelete(t, ctx.KubectlOptions(t), "path/to/config") + }) + + // Similarly, you can obtain Kubernetes client from your test context. + // You can use it to, for example, read all services in a namespace: + k8sClient := ctx.KubernetesClient(t) + services, err := k8sClient.CoreV1().Services(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + require.NotNil(t, services.Items) + + // To make Consul API calls, you can get the Consul client from the consulCluster object, + // indicating whether the client needs to be secure or not (i.e. whether TLS and ACLs are enabled on the Consul cluster): + consulClient, _ := consulCluster.SetupConsulClient(t, true) + consulServices, _, err := consulClient.Catalog().Services(nil) + require.NoError(t, err) + require.NotNil(t, consulServices) +} diff --git a/acceptance/tests/api-gateway/main_test.go b/acceptance/tests/api-gateway/main_test.go new file mode 100644 index 0000000000..f92fff8a59 --- /dev/null +++ b/acceptance/tests/api-gateway/main_test.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Rename package to your test package. +package example + +import ( + "testing" + + testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" +) + +var suite testsuite.Suite + +func TestMain(m *testing.M) { + // First, uncomment the line below to create a new suite so that all flags are parsed. + /* + suite = framework.NewSuite(m) + */ + + // If the test suite needs to run only when certain test flags are passed, + // you need to handle that in the TestMain function. + // Uncomment and modify example code below if that is the case. + /* + if suite.Config().EnableExampleFeature { + os.Exit(suite.Run()) + } else { + fmt.Println("Skipping example feature tests because -enable-example-feature is not set") + os.Exit(0) + } + */ + + // If the test suite should run in every case, uncomment the line below. + /* + os.Exit(suite.Run()) + */ +} From 37dd92944b515fd81272c31f5ddd27b88a95c5e1 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 26 May 2023 10:42:56 -0400 Subject: [PATCH 167/592] Update consul image so that acceptance tests run (#2189) --- charts/consul/Chart.yaml | 4 ++-- charts/consul/values.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index d0216aa36e..c55c6be6a2 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -4,7 +4,7 @@ apiVersion: v2 name: consul version: 1.2.0-dev -appVersion: 1.15.1 +appVersion: 1.16-dev kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -16,7 +16,7 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: hashicorp/consul:1.15.1 + image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.16-dev - name: consul-k8s-control-plane image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.0-dev - name: consul-dataplane diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index a0660c5020..83b18ae044 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: "hashicorp/consul:1.15.1" + image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.16-dev # 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. From 8bfcfcf2f8091772e0dd5911aea3ff2984c61909 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Fri, 26 May 2023 11:52:15 -0400 Subject: [PATCH 168/592] API Gateways for Consul on Kubernetes `BETA` (#2152) * Add API Gateway subcommand to Control Plane. Co-authored-by: Thomas Eckert * Add GatewayClassConfig CRD (#2036) * Update dependencies so that CRDs can be added * Generate CRD for GatewayClassConfig * Return empty logger instead of nil due to dependency update * Update sidecar webhook to use ProbeHandler instead of Handler * Update controller sub resources to use sub resource update options * Re-add copyright header that got removed on generation * Use NewTestLogger and ProbeHandler in tests * Add api_gateway_types_test * Remove boilerplate from ctrl-generate as it is no longer required * Add app-copyright-header to Makefile * Clarify GatewayClassConfig description * Remove unneeded fields from GatewayClassConfig * Fix lint issues * Fix TestLogger in enterprise tests * Add Changelog * Fix TestLogger in enterprise test in one more place * Remove the helpers * Remove unused consts * Adds API Gateway Class Config controller * Add Hack for Generating CRDs from external sources (#2060) * Add generate-external-crds to Makefile * Add contributing docs * Add comment about Helm ignoring kustomization.yaml * Update Makefile Co-authored-by: Luke Kysow <1034429+lkysow@users.noreply.github.com> * Update CONTRIBUTING.md Co-authored-by: Nathan Coleman --------- Co-authored-by: Luke Kysow <1034429+lkysow@users.noreply.github.com> Co-authored-by: Nathan Coleman * Remove the api-gateway subcommand we decided not to use (#2062) * APIGW Resource Translation (#2070) * WIP: api-gateway resource conversion * convert meta for apigw from k8s * Added tests and updated config entry translation for APIGW * Fix linting issue, move translation code to correct location * Updates from PR comments * Update config entry translation to use k8s type NamedNamespace, updated tests * switch to standard import rename for consul api * Add GatewayClass Controller (#2055) * Add permissions to connect-inject clusterrole * Add gateway api crd deps * Stub out the gatewayclass controller * Add finalizer functions * Use finalizer functions * Add tests for GatewayClass Controller * Change the controller name * Only register gwv1beta1 * Run tests in parallel * Remove RBAC comments * Remove perms from resources not yet implemented * shouldUpdate -> expectedDidUpdate * Don't requeue if in use * Address PR feedback * Apply suggestions from code review Co-authored-by: Andrew Stucki * Make gatewayClassFinalizer private * Separate out indexers * Move validation of parametersRef to a helper func * Add reason to ensureStatus * Rename GatewayClassReconciler -> GatewayClassController * Add perms to list gateways * Clean up status conditions * Clean up indexes * Set conditions properly and test them * Test incorrect parametersRef * Fix comments on indexer funcs * Fix lint issues * Set conditions without unnecessary updates * Set ObservedGeneration from parent object * Fix infinite loop issue with invalid config * Fix update issue * Return error if the GatewayClass cannot be reached --------- Co-authored-by: Andrew Stucki * Updates GatewayClassConfig Controller to use common finalizer methods * APIGW4CONK8S: HTTP Route/TCPRoute/Secrets Translation (#2088) * Add http route translation * Added copywrite headers * Add namespace translation for service * handle potential nil pointer on section name, check if parent ref if an api gateway, fix comment from PR Review * Added TCPRoute Translation * Fix potential nil pointer deref in tcp service namespace, update tcproute tests * Add inline certs translation, clean up some potential nil pointer derefs * Clean up comments * Linting * Switch out env var usage for field on translator * rename api-gateway/consul package to api-gateway/translation * Adds stub for Gateway Controller * Use the non-deprecated logr test (#2125) * APIGW4CONK8s: Add Consul Cache (#2118) * Added basic cache functionality with most tests, todo: add get method for cache and expand tests * Updated tests for Cache.Run function, removed tests of unexported methods called by Run function * Moved translation function def to translation package, added translate apigw config entry * Add translation for consul config entries to k8s namespaced name meta * Added Get method to cache * Add watch for contoller and setup in inject command * Updated comments, renamed TranslateConsulInlineSecret method to TranslateConsulInlineCertificate * Updates from PR review * Parallelize tests * Bump consul api version * Set api timeout for cache calls * Revert "Bump consul api version" This reverts commit c074b0f749d891f78ddff86b3a7eb62ba1e52a17. * Linting fun * Add Gatekeeper for managing gateway deployment resources (#2117) * Stub out the gatewayclass controller * Change the controller name * Only register gwv1beta1 * Address PR feedback * Adds stub of Gateway Controller * cannot understand why the indexes are not working * some updates, want to do cleanup * rebase and cleanup * Start adding deployer * Flesh out tests * Refactor into a "gatekeeper" * Integrate the gatekeeper into the gateway controller * Simplify the api * Remove the creation of helm config until later * Remove use and rename package to gatekeeper * Add labels to apigateway * Manage ServiceAccount * Manage Deployment * Add more to deployment * Update Helm Values * WIP fleshing out the gateway deployment upsert behavior * Update role and service * Fix merge conflicts * Round out tests * Add test for respecting replicas * Change the Gatekeeper New API and add comments for Upsert and Delete * implement joinResources * accept suggestions from @jm96441n * Use pointer receivers * Separate out mutator * Update deployment correctly * Update Role and ServiceAccount * Fix that silly linting error * Comments on HelmConfig * Add Image to deployment * Merge api-gateway into branch --------- Co-authored-by: Melisa Griffin * Net 3490/reference grants (#2122) * Adds reference grant validation * Adds all necessary methods and tests * lint * some cleanup, fix copypasta test errors * lint * more linting * PR updates, fix capitalization * Add a bunch of TODOs for teamwork * Split out cleanup func and clear up todos * APIGW4CONK8S: Serialize the GatewayClassConfig onto the Gateway for easier retrieval (#2126) * Add serialization of gateway class config * Parallelize tests * Remove prints, fix cache tests * Add outer managed check to ensure we don't fetch config if we don't need to * Stub out where the openshift role info will go (#2145) * APIGW4CONK8S: Function to get all refs for a gateway (#2139) * Added function to get all refs for a gateway * Use k8s objects for references rather than consul objects * Fix comment * [API Gateway] API Gateway Binding Logic (#2142) * initial commit * Add additional TODO * Add some basic lifecycle unit tests * split up implementation * Add more tests and fix some bugs * remove one parallel call in a loop * Fix binding * Add resolvedRefs statuses for routes * Fix issue with empty parent ref that k8s doesn't like * Fix up updates/status ordering * Add basic gateway status setting * Finish up first pass on gateway statuses * Re-organize and begin adding comments * More comments * More comments * More comments * More comments * More comments * Add file that wasn't saved * Add utils unit tests * Add more tests * Final tests * Fix tests * Fix up gateway annotation with binding logic * Update doc comments for linter * Add forgotten file * Fix block in tests due to buffered channel size and better handle context cancelation * Add basic acceptance tests for route binding behavior (#2161) * Configure Gateway Controller with Helm values (#2158) * Stub out the gatewayclass controller * Change the controller name * Only register gwv1beta1 * Address PR feedback * Adds stub of Gateway Controller * cannot understand why the indexes are not working * some updates, want to do cleanup * rebase and cleanup * Start adding deployer * Flesh out tests * Refactor into a "gatekeeper" * Integrate the gatekeeper into the gateway controller * Simplify the api * Remove the creation of helm config until later * Remove use and rename package to gatekeeper * Add labels to apigateway * Manage ServiceAccount * Manage Deployment * Add more to deployment * Update Helm Values * WIP fleshing out the gateway deployment upsert behavior * Update role and service * Fix merge conflicts * Round out tests * Add test for respecting replicas * Change the Gatekeeper New API and add comments for Upsert and Delete * implement joinResources * accept suggestions from @jm96441n * Use pointer receivers * Separate out mutator * Update deployment correctly * Update Role and ServiceAccount * Fix that silly linting error * Comments on HelmConfig * Add Image to deployment * Add Gateway flags to inject-connect * Pass through env vars * Add environment variables to the deployment template * Add conditional injection of environment variables * Add env vars back in * Fix up issues from merge * Test default env vars * Test all of the env vars * Fix up more issues from merge * Pass in values to HelmConfig then to Controller * Just pass config in as a struct * Add gateway-gatewayclass * Add gateway-gatewayclassconfig * Add DeploymentSpec to GatewayClassConfig * Remove deployment configuration settings from HelmConfig * Remove BATs on deployment configuration * Expand gatewayclassconfig * Set deployment replicas in test * Place GatewayClassConfig in the crds/ dir * Update control-plane/api-gateway/gatekeeper/gatekeeper_test.go Co-authored-by: Andrew Stucki --------- Co-authored-by: Melisa Griffin Co-authored-by: Andrew Stucki * Net 4124/handle syncing consul lifecycle events (#2173) * with type switch * latest changes * remove debugging panic * Updated error in test * Fix bug with capacity v length in the cache list and type that is being subscribed to * Fix linting issues/naming from PR review * Added tests for delete function * Plumbing for gatekeeper with snapshot * [API Gateway] Hooking up API Gateways End-to-End (#2175) * updated gatekeeper, added update call, still needs work * still has some print statements, seeing issues with updates * some linting * run ctrl-manifests and generate * get the whole gamut finally working in a minimum configuration * Fix up tests * Add some tests * Move cache package * Fix up tests after other fixes * Fix up test lifecycle * Fix up linter issues * Remove unnecessary test that panics * Add MeshService CRD * fix bats tests * bats bats bats * baaaatttss * Fix up acceptance test cleanup by introducing uninstall hook to cleanup managed GatewayClass and GatewayClassConfig resources * Add test for deletion failures due to finalizers * reorder commands --------- Co-authored-by: Melisa Griffin * Fix crd loading (#2179) * Fix CRD loading for CLI * Adds crds directory to install with consul-k8s cli * fix tests * testing * fix bats tests --------- Co-authored-by: Thomas Eckert Co-authored-by: Andrew Stucki * Add Changelog * Fix up issues after merge back * Fix wildcard usage on enterprise * Don't subscribe to peerings when not enabled * Remove additional changelog entries since we're only going to use 1 --------- Co-authored-by: Melisa Griffin Co-authored-by: Luke Kysow <1034429+lkysow@users.noreply.github.com> Co-authored-by: Nathan Coleman Co-authored-by: John Maguire Co-authored-by: Andrew Stucki Co-authored-by: Melisa Griffin --- .changelog/2152.txt | 3 + CONTRIBUTING.md | 21 + Makefile | 17 +- .../framework/consul/helm_cluster_test.go | 5 + .../framework/environment/environment.go | 36 +- acceptance/framework/k8s/deploy.go | 9 + acceptance/go.mod | 68 +- acceptance/go.sum | 291 +- .../tests/api-gateway/api_gateway_test.go | 248 ++ acceptance/tests/api-gateway/example_test.go | 64 - acceptance/tests/api-gateway/main_test.go | 27 +- .../bases/api-gateway/apigateway.yaml | 16 + .../bases/api-gateway/gatewayclass.yaml | 13 + .../bases/api-gateway/gatewayclassconfig.yaml | 7 + .../fixtures/bases/api-gateway/httproute.yaml | 10 + .../bases/api-gateway/kustomization.yaml | 8 + charts/consul/.helmignore | 1 + ...ewayclassconfigs.consul.hashicorp.com.yaml | 140 + ...ewayclasses.gateway.networking.k8s.io.yaml | 323 ++ ...classes.gateway.networking.k8s.io.yaml.yml | 323 ++ .../gateways.gateway.networking.k8s.io.yaml | 877 ++++++ ...ateways.gateway.networking.k8s.io.yaml.yml | 877 ++++++ .../grpcroutes.gateway.networking.k8s.io.yaml | 761 +++++ ...croutes.gateway.networking.k8s.io.yaml.yml | 761 +++++ .../httproutes.gateway.networking.k8s.io.yaml | 1909 ++++++++++++ ...proutes.gateway.networking.k8s.io.yaml.yml | 1909 ++++++++++++ charts/consul/crds/kustomization.yaml | 10 + ...rencegrants.gateway.networking.k8s.io.yaml | 203 ++ ...egrants.gateway.networking.k8s.io.yaml.yml | 203 ++ .../tcproutes.gateway.networking.k8s.io.yaml | 276 ++ ...proutes.gateway.networking.k8s.io.yaml.yml | 276 ++ .../tlsroutes.gateway.networking.k8s.io.yaml | 286 ++ ...sroutes.gateway.networking.k8s.io.yaml.yml | 286 ++ .../udproutes.gateway.networking.k8s.io.yaml | 276 ++ ...proutes.gateway.networking.k8s.io.yaml.yml | 276 ++ .../templates/connect-inject-clusterrole.yaml | 87 +- .../templates/crd-exportedservices.yaml | 8 +- .../consul/templates/crd-ingressgateways.yaml | 8 +- charts/consul/templates/crd-meshes.yaml | 8 +- charts/consul/templates/crd-meshservices.yaml | 58 + .../templates/crd-peeringacceptors.yaml | 8 +- .../consul/templates/crd-peeringdialers.yaml | 8 +- .../consul/templates/crd-proxydefaults.yaml | 8 +- .../consul/templates/crd-samenessgroups.yaml | 8 +- .../consul/templates/crd-servicedefaults.yaml | 8 +- .../templates/crd-serviceintentions.yaml | 8 +- .../templates/crd-serviceresolvers.yaml | 8 +- .../consul/templates/crd-servicerouters.yaml | 8 +- .../templates/crd-servicesplitters.yaml | 8 +- .../templates/crd-terminatinggateways.yaml | 8 +- .../gateway-cleanup-clusterrole.yaml | 35 + .../gateway-cleanup-clusterrolebinding.yaml | 20 + .../consul/templates/gateway-cleanup-job.yaml | 61 + .../gateway-cleanup-podsecuritypolicy.yaml | 34 + .../gateway-cleanup-serviceaccount.yaml | 13 + .../templates/gateway-gatewayclass.yaml | 18 + .../templates/gateway-gatewayclassconfig.yaml | 74 + .../test/unit/connect-inject-clusterrole.bats | 10 +- .../test/unit/connect-inject-deployment.bats | 1 + .../test/unit/gateway-gatewayclass.bats | 21 + .../test/unit/gateway-gatewayclassconfig.bats | 117 + charts/consul/test/unit/helpers.bats | 2 +- charts/consul/values.yaml | 74 + charts/embed_chart.go | 3 +- cli/helm/chart.go | 24 +- cli/helm/chart_test.go | 1 + cli/helm/test_fixtures/consul/crds/foo.yaml | 4 + .../api-gateway/binding/annotations.go | 41 + .../api-gateway/binding/annotations_test.go | 206 ++ control-plane/api-gateway/binding/binder.go | 433 +++ .../api-gateway/binding/binder_test.go | 2683 +++++++++++++++++ .../api-gateway/binding/references.go | 135 + .../api-gateway/binding/registration.go | 92 + .../api-gateway/binding/registration_test.go | 83 + control-plane/api-gateway/binding/result.go | 471 +++ .../api-gateway/binding/route_binding.go | 489 +++ control-plane/api-gateway/binding/setter.go | 180 ++ .../api-gateway/binding/setter_test.go | 42 + control-plane/api-gateway/binding/snapshot.go | 56 + control-plane/api-gateway/binding/utils.go | 105 + .../api-gateway/binding/utils_test.go | 239 ++ .../api-gateway/binding/validation.go | 332 ++ .../api-gateway/binding/validation_test.go | 672 +++++ control-plane/api-gateway/cache/consul.go | 897 ++++++ .../api-gateway/cache/consul_test.go | 2028 +++++++++++++ .../api-gateway/controllers/finalizer.go | 44 + .../api-gateway/controllers/finalizer_test.go | 84 + .../gateway_class_config_controller.go | 130 + .../gateway_class_config_controller_test.go | 123 + .../controllers/gateway_controller.go | 779 +++++ .../controllers/gateway_controller_test.go | 464 +++ .../controllers/gatewayclass_controller.go | 259 ++ .../gatewayclass_controller_test.go | 275 ++ .../api-gateway/controllers/index.go | 262 ++ .../api-gateway/controllers/index_test.go | 13 + .../controllers/reference_validator.go | 177 ++ .../controllers/reference_validator_test.go | 649 ++++ .../api-gateway/gatekeeper/dataplane.go | 172 ++ .../api-gateway/gatekeeper/deployment.go | 234 ++ .../api-gateway/gatekeeper/gatekeeper.go | 91 + .../api-gateway/gatekeeper/gatekeeper_test.go | 896 ++++++ control-plane/api-gateway/gatekeeper/init.go | 199 ++ control-plane/api-gateway/gatekeeper/role.go | 90 + .../api-gateway/gatekeeper/service.go | 143 + .../api-gateway/gatekeeper/serviceaccount.go | 80 + control-plane/api-gateway/helm_config.go | 35 + control-plane/api-gateway/labels.go | 27 + .../translation/config_entry_translation.go | 516 ++++ .../config_entry_translation_test.go | 1636 ++++++++++ .../translation/k8s_cache_translation.go | 134 + .../translation/k8s_cache_translation_test.go | 460 +++ .../api/common/configentry_webhook_test.go | 2 +- .../api/v1alpha1/api_gateway_types.go | 135 + .../api/v1alpha1/api_gateway_types_test.go | 48 + .../v1alpha1/exportedservices_webhook_test.go | 2 +- .../api/v1alpha1/mesh_webhook_test.go | 2 +- .../v1alpha1/peeringacceptor_webhook_test.go | 2 +- .../v1alpha1/peeringdialer_webhook_test.go | 2 +- .../v1alpha1/proxydefaults_webhook_test.go | 2 +- .../serviceintentions_webhook_test.go | 6 +- .../api/v1alpha1/zz_generated.deepcopy.go | 227 +- .../build-support/controller/README.md | 5 - .../controller/boilerplate.go.txt | 0 .../catalog/to-consul/resource_test.go | 8 +- control-plane/commands.go | 5 + ...consul.hashicorp.com_exportedservices.yaml | 8 +- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 140 + .../consul.hashicorp.com_ingressgateways.yaml | 8 +- .../bases/consul.hashicorp.com_meshes.yaml | 8 +- .../consul.hashicorp.com_meshservices.yaml | 53 + ...consul.hashicorp.com_peeringacceptors.yaml | 8 +- .../consul.hashicorp.com_peeringdialers.yaml | 8 +- .../consul.hashicorp.com_proxydefaults.yaml | 8 +- .../consul.hashicorp.com_samenessgroups.yaml | 8 +- .../consul.hashicorp.com_servicedefaults.yaml | 8 +- ...onsul.hashicorp.com_serviceintentions.yaml | 8 +- ...consul.hashicorp.com_serviceresolvers.yaml | 8 +- .../consul.hashicorp.com_servicerouters.yaml | 8 +- ...consul.hashicorp.com_servicesplitters.yaml | 8 +- ...sul.hashicorp.com_terminatinggateways.yaml | 8 +- .../connect-inject/constants/constants.go | 3 + .../consul_client_health_checks_test.go | 2 +- .../endpoints_controller_ent_test.go | 10 +- .../endpoints/endpoints_controller_test.go | 54 +- .../peering_acceptor_controller_test.go | 12 +- .../peering/peering_dialer_controller_test.go | 12 +- .../webhook/consul_dataplane_sidecar.go | 4 +- .../webhook/consul_dataplane_sidecar_test.go | 10 +- .../webhook/mesh_webhook_ent_test.go | 34 +- .../webhook/mesh_webhook_test.go | 66 +- .../webhook/redirect_traffic_test.go | 22 +- .../controllers/configentry_controller.go | 4 +- .../configentry_controller_ent_test.go | 12 +- .../configentry_controller_test.go | 20 +- .../exportedservices_controller.go | 2 +- .../exportedservices_controller_ent_test.go | 6 +- .../controllers/ingressgateway_controller.go | 2 +- control-plane/controllers/mesh_controller.go | 2 +- .../controllers/proxydefaults_controller.go | 2 +- .../controllers/samenessgroups_controller.go | 3 +- .../controllers/servicedefaults_controller.go | 2 +- .../serviceintentions_controller.go | 2 +- .../controllers/serviceresolver_controller.go | 2 +- .../controllers/servicerouter_controller.go | 2 +- .../controllers/servicesplitter_controller.go | 2 +- .../terminatinggateway_controller.go | 2 +- control-plane/go.mod | 92 +- control-plane/go.sum | 476 +-- control-plane/helper/controller/controller.go | 5 +- control-plane/subcommand/common/common.go | 2 +- .../subcommand/connect-init/command.go | 2 +- .../subcommand/gateway-cleanup/command.go | 193 ++ .../gateway-cleanup/command_test.go | 83 + .../subcommand/inject-connect/command.go | 88 +- hack/copy-crds-to-chart/main.go | 43 +- 175 files changed, 29689 insertions(+), 1065 deletions(-) create mode 100644 .changelog/2152.txt create mode 100644 acceptance/tests/api-gateway/api_gateway_test.go delete mode 100644 acceptance/tests/api-gateway/example_test.go create mode 100644 acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml create mode 100644 acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml create mode 100644 acceptance/tests/fixtures/bases/api-gateway/gatewayclassconfig.yaml create mode 100644 acceptance/tests/fixtures/bases/api-gateway/httproute.yaml create mode 100644 acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml create mode 100644 charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml create mode 100644 charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml create mode 100644 charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml.yml create mode 100644 charts/consul/crds/gateways.gateway.networking.k8s.io.yaml create mode 100644 charts/consul/crds/gateways.gateway.networking.k8s.io.yaml.yml create mode 100644 charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml create mode 100644 charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml.yml create mode 100644 charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml create mode 100644 charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml.yml create mode 100644 charts/consul/crds/kustomization.yaml create mode 100644 charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml create mode 100644 charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml.yml create mode 100644 charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml create mode 100644 charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml.yml create mode 100644 charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml create mode 100644 charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml.yml create mode 100644 charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml create mode 100644 charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml.yml create mode 100644 charts/consul/templates/crd-meshservices.yaml create mode 100644 charts/consul/templates/gateway-cleanup-clusterrole.yaml create mode 100644 charts/consul/templates/gateway-cleanup-clusterrolebinding.yaml create mode 100644 charts/consul/templates/gateway-cleanup-job.yaml create mode 100644 charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml create mode 100644 charts/consul/templates/gateway-cleanup-serviceaccount.yaml create mode 100644 charts/consul/templates/gateway-gatewayclass.yaml create mode 100644 charts/consul/templates/gateway-gatewayclassconfig.yaml create mode 100755 charts/consul/test/unit/gateway-gatewayclass.bats create mode 100644 charts/consul/test/unit/gateway-gatewayclassconfig.bats create mode 100644 cli/helm/test_fixtures/consul/crds/foo.yaml create mode 100644 control-plane/api-gateway/binding/annotations.go create mode 100644 control-plane/api-gateway/binding/annotations_test.go create mode 100644 control-plane/api-gateway/binding/binder.go create mode 100644 control-plane/api-gateway/binding/binder_test.go create mode 100644 control-plane/api-gateway/binding/references.go create mode 100644 control-plane/api-gateway/binding/registration.go create mode 100644 control-plane/api-gateway/binding/registration_test.go create mode 100644 control-plane/api-gateway/binding/result.go create mode 100644 control-plane/api-gateway/binding/route_binding.go create mode 100644 control-plane/api-gateway/binding/setter.go create mode 100644 control-plane/api-gateway/binding/setter_test.go create mode 100644 control-plane/api-gateway/binding/snapshot.go create mode 100644 control-plane/api-gateway/binding/utils.go create mode 100644 control-plane/api-gateway/binding/utils_test.go create mode 100644 control-plane/api-gateway/binding/validation.go create mode 100644 control-plane/api-gateway/binding/validation_test.go create mode 100644 control-plane/api-gateway/cache/consul.go create mode 100644 control-plane/api-gateway/cache/consul_test.go create mode 100644 control-plane/api-gateway/controllers/finalizer.go create mode 100644 control-plane/api-gateway/controllers/finalizer_test.go create mode 100644 control-plane/api-gateway/controllers/gateway_class_config_controller.go create mode 100644 control-plane/api-gateway/controllers/gateway_class_config_controller_test.go create mode 100644 control-plane/api-gateway/controllers/gateway_controller.go create mode 100644 control-plane/api-gateway/controllers/gateway_controller_test.go create mode 100644 control-plane/api-gateway/controllers/gatewayclass_controller.go create mode 100644 control-plane/api-gateway/controllers/gatewayclass_controller_test.go create mode 100644 control-plane/api-gateway/controllers/index.go create mode 100644 control-plane/api-gateway/controllers/index_test.go create mode 100644 control-plane/api-gateway/controllers/reference_validator.go create mode 100644 control-plane/api-gateway/controllers/reference_validator_test.go create mode 100644 control-plane/api-gateway/gatekeeper/dataplane.go create mode 100644 control-plane/api-gateway/gatekeeper/deployment.go create mode 100644 control-plane/api-gateway/gatekeeper/gatekeeper.go create mode 100644 control-plane/api-gateway/gatekeeper/gatekeeper_test.go create mode 100644 control-plane/api-gateway/gatekeeper/init.go create mode 100644 control-plane/api-gateway/gatekeeper/role.go create mode 100644 control-plane/api-gateway/gatekeeper/service.go create mode 100644 control-plane/api-gateway/gatekeeper/serviceaccount.go create mode 100644 control-plane/api-gateway/helm_config.go create mode 100644 control-plane/api-gateway/labels.go create mode 100644 control-plane/api-gateway/translation/config_entry_translation.go create mode 100644 control-plane/api-gateway/translation/config_entry_translation_test.go create mode 100644 control-plane/api-gateway/translation/k8s_cache_translation.go create mode 100644 control-plane/api-gateway/translation/k8s_cache_translation_test.go create mode 100644 control-plane/api/v1alpha1/api_gateway_types.go create mode 100644 control-plane/api/v1alpha1/api_gateway_types_test.go delete mode 100644 control-plane/build-support/controller/README.md delete mode 100644 control-plane/build-support/controller/boilerplate.go.txt create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml create mode 100644 control-plane/subcommand/gateway-cleanup/command.go create mode 100644 control-plane/subcommand/gateway-cleanup/command_test.go diff --git a/.changelog/2152.txt b/.changelog/2152.txt new file mode 100644 index 0000000000..2f0743a9d8 --- /dev/null +++ b/.changelog/2152.txt @@ -0,0 +1,3 @@ +```release-note:feature +api-gateway: Add API Gateway for Consul on Kubernetes leveraging Consul native API Gateway configuration. +``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3745c80bac..f427421294 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,7 @@ 1. [Writing Acceptance tests](#writing-acceptance-tests) 1. [Using the Acceptance Test Framework to Debug](#using-acceptance-test-framework-to-debug) 1. [Helm Reference Docs](#helm-reference-docs) +1. [Managing External CRD Dependencies](#managing-external-crd-dependencies) 1. [Adding a Changelog Entry](#adding-a-changelog-entry) ## Contributing 101 @@ -1214,6 +1215,26 @@ So that the documentation can look like: - `ports` ((#v-ingressgateways-defaults-service-ports)) (`array: [{port: 8080, port: 8443}]`) - Port docs ``` +## Managing External CRD Dependencies + +Some of the features of Consul on Kubernetes make use of CustomResourceDefinitions (CRDs) that we don't directly +manage. One such example is the Gateway API CRDs which we use to configure API Gateways, but are managed by SIG +Networking. + +To pull external CRDs into our Helm chart and make sure they get installed, we generate their configuration using +[Kustomize](https://kustomize.io/) which can pull in Kubernetes config from external sources. We split these +generated CRDs into individual files and store them in the +[Helm `/crds` directory](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/). + +If you need to update the external CRDs we depend on, or add to them, you can do this by editing the +[crds/kustomization.yaml](/charts/consul/crds/kustomization.yaml) file. Once modified, running + +```bash +make generate-external-crds +``` + +will update the CRDs in the `/crds` directory. + ## Adding a Changelog Entry Any change that a Consul-K8s user might need to know about should have a changelog entry. diff --git a/Makefile b/Makefile index ea94b303e9..6fa53d77c3 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,10 @@ gen-helm-docs: ## Generate Helm reference docs from values.yaml and update Consu copy-crds-to-chart: ## Copy generated CRD YAML into charts/consul. Usage: make copy-crds-to-chart @cd hack/copy-crds-to-chart; go run ./... +generate-external-crds: ## Generate CRDs for externally defined CRDs and copy them to charts/consul. Usage: make generate-external-crds + @cd ./charts/consul/crds/; \ + kustomize build | yq --split-exp '.metadata.name + ".yaml"' --no-doc + bats-tests: ## Run Helm chart bats tests. bats --jobs 4 charts/consul/test/unit @@ -65,7 +69,7 @@ cni-plugin-lint: cd control-plane/cni; golangci-lint run -c ../../.golangci.yml ctrl-generate: get-controller-gen ## Run CRD code generation. - cd control-plane; $(CONTROLLER_GEN) object:headerFile="build-support/controller/boilerplate.go.txt" paths="./..." + cd control-plane; $(CONTROLLER_GEN) object paths="./..." # Helper target for doing local cni acceptance testing kind-cni: @@ -124,6 +128,8 @@ lint: cni-plugin-lint ## Run linter in the control-plane, cli, and acceptance di ctrl-manifests: get-controller-gen ## Generate CRD manifests. cd control-plane; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases make copy-crds-to-chart + make generate-external-crds + make add-copyright-header get-controller-gen: ## Download controller-gen program needed for operator SDK. ifeq (, $(shell which controller-gen)) @@ -140,6 +146,13 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif +add-copyright-header: ## Add copyright header to all files in the project +ifeq (, $(shell which copywrite)) + @echo "Installing copywrite" + @go install github.com/hashicorp/copywrite@latest +endif + @copywrite headers --spdx "MPL-2.0" + # ===========> CI Targets ci.aws-acceptance-test-cleanup: ## Deletes AWS resources left behind after failed acceptance tests. @@ -178,7 +191,7 @@ endif # ===========> Makefile config .DEFAULT_GOAL := help -.PHONY: gen-helm-docs copy-crds-to-chart bats-tests help ci.aws-acceptance-test-cleanup version cli-dev prepare-dev prepare-release +.PHONY: gen-helm-docs copy-crds-to-chart generate-external-crds bats-tests help ci.aws-acceptance-test-cleanup version cli-dev prepare-dev prepare-release SHELL = bash GOOS?=$(shell go env GOOS) GOARCH?=$(shell go env GOARCH) diff --git a/acceptance/framework/consul/helm_cluster_test.go b/acceptance/framework/consul/helm_cluster_test.go index 435d9f4d7f..9c44006d43 100644 --- a/acceptance/framework/consul/helm_cluster_test.go +++ b/acceptance/framework/consul/helm_cluster_test.go @@ -11,6 +11,8 @@ import ( "github.com/stretchr/testify/require" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/controller-runtime/pkg/client" + runtimefake "sigs.k8s.io/controller-runtime/pkg/client/fake" ) // Test that if TestConfig has values that need to be provided @@ -81,3 +83,6 @@ func (c *ctx) KubectlOptions(_ *testing.T) *k8s.KubectlOptions { func (c *ctx) KubernetesClient(_ *testing.T) kubernetes.Interface { return fake.NewSimpleClientset() } +func (c *ctx) ControllerRuntimeClient(_ *testing.T) client.Client { + return runtimefake.NewClientBuilder().Build() +} diff --git a/acceptance/framework/environment/environment.go b/acceptance/framework/environment/environment.go index 2c86b43707..58e4e83a5b 100644 --- a/acceptance/framework/environment/environment.go +++ b/acceptance/framework/environment/environment.go @@ -9,9 +9,15 @@ import ( "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/config" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) const ( @@ -31,6 +37,7 @@ type TestEnvironment interface { type TestContext interface { KubectlOptions(t *testing.T) *k8s.KubectlOptions KubernetesClient(t *testing.T) kubernetes.Interface + ControllerRuntimeClient(t *testing.T) client.Client } type KubernetesEnvironment struct { @@ -85,7 +92,9 @@ type kubernetesContext struct { kubeContextName string namespace string - client kubernetes.Interface + client kubernetes.Interface + runtimeClient client.Client + options *k8s.KubectlOptions } @@ -164,6 +173,31 @@ func (k kubernetesContext) KubernetesClient(t *testing.T) kubernetes.Interface { return k.client } +func (k kubernetesContext) ControllerRuntimeClient(t *testing.T) client.Client { + if k.runtimeClient != nil { + return k.runtimeClient + } + + options := k.KubectlOptions(t) + configPath, err := options.GetConfigPath(t) + require.NoError(t, err) + config, err := k8s.LoadApiClientConfigE(configPath, options.ContextName) + require.NoError(t, err) + + s := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(s)) + require.NoError(t, gwv1alpha2.Install(s)) + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + client, err := client.New(config, client.Options{Scheme: s}) + require.NoError(t, err) + + k.runtimeClient = client + + return k.runtimeClient +} + func NewContext(namespace, pathToKubeConfig, kubeContextName string) *kubernetesContext { return &kubernetesContext{ namespace: namespace, diff --git a/acceptance/framework/k8s/deploy.go b/acceptance/framework/k8s/deploy.go index 771aab3fe1..6834284c33 100644 --- a/acceptance/framework/k8s/deploy.go +++ b/acceptance/framework/k8s/deploy.go @@ -153,6 +153,15 @@ func CheckStaticServerConnectionFailing(t *testing.T, options *k8s.KubectlOption }, "", curlArgs...) } +// CheckStaticServerHTTPConnectionFailing is just like CheckStaticServerConnectionFailing +// except with HTTP-based intentions. +func CheckStaticServerHTTPConnectionFailing(t *testing.T, options *k8s.KubectlOptions, sourceApp string, curlArgs ...string) { + t.Helper() + CheckStaticServerConnection(t, options, sourceApp, false, []string{ + "curl: (22) The requested URL returned error: 403", + }, "", curlArgs...) +} + // labelMapToString takes a label map[string]string // and returns the string-ified version of, e.g app=foo,env=dev. func labelMapToString(labelMap map[string]string) string { diff --git a/acceptance/go.mod b/acceptance/go.mod index 0693467102..fb2cb62725 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -10,35 +10,43 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/vault/api v1.2.0 - github.com/stretchr/testify v1.7.2 + github.com/stretchr/testify v1.8.2 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.22.2 - k8s.io/apimachinery v0.22.2 - k8s.io/client-go v0.22.2 + k8s.io/api v0.26.3 + k8s.io/apimachinery v0.26.3 + k8s.io/client-go v0.26.3 + sigs.k8s.io/controller-runtime v0.14.6 + sigs.k8s.io/gateway-api v0.7.0 ) require ( - cloud.google.com/go v0.81.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/aws/aws-sdk-go v1.30.27 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect - github.com/go-logr/logr v0.4.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.1 // indirect - github.com/google/go-cmp v0.5.7 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.2 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -58,9 +66,13 @@ require ( github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/miekg/dns v1.1.41 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect @@ -69,26 +81,29 @@ require ( github.com/moby/spdystream v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.0.0 // indirect - github.com/onsi/ginkgo v1.16.4 // indirect - github.com/onsi/gomega v1.15.0 // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/otp v1.2.0 // indirect - github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/urfave/cli v1.22.2 // indirect go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/time v0.3.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect google.golang.org/grpc v1.48.0 // indirect @@ -96,9 +111,10 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.9.0 // indirect - k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect - k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/acceptance/go.sum b/acceptance/go.sum index a2c392ed4e..811c11c123 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -15,12 +15,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -49,14 +43,12 @@ github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8 github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.5/go.mod h1:foo3aIXRQ90zFve3r0QiDsrjGDUwWhKl0ZOQy1CT14k= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/azure/auth v0.5.1/go.mod h1:ea90/jvmnAwDrSooLH4sRIehEPtG/EPUXavDh31MnA4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= @@ -73,7 +65,6 @@ github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQ github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -92,6 +83,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -103,7 +95,7 @@ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -112,6 +104,7 @@ github.com/aws/aws-sdk-go v1.30.27 h1:9gPjZWVDSoQrBO2AvqrWObS6KAZByfEJxQoCYo4ZfK github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -121,6 +114,8 @@ github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3 github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -128,7 +123,6 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D 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/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -189,69 +183,82 @@ github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/El github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU= github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/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/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +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-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -267,7 +274,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -275,7 +282,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -292,15 +298,16 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -308,13 +315,11 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3/go.mod h1:2wIuQute9+hhWqvL3vEI7YB0EKluF4WcPzI1eAliazk= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -322,7 +327,6 @@ github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -330,23 +334,17 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -442,18 +440,21 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -466,12 +467,14 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -487,6 +490,8 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -505,6 +510,8 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= +github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= @@ -541,14 +548,11 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -556,18 +560,13 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -601,18 +600,35 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 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.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -620,8 +636,9 @@ github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYe github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -630,12 +647,12 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -654,17 +671,22 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +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/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -689,15 +711,15 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -713,8 +735,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -741,7 +761,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -751,8 +770,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -786,38 +803,28 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 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= @@ -829,6 +836,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -854,7 +862,6 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -863,9 +870,9 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -878,57 +885,52 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -983,20 +985,14 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= @@ -1017,11 +1013,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1060,17 +1051,7 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -1089,13 +1070,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= @@ -1119,8 +1095,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -1134,7 +1110,6 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1149,7 +1124,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= @@ -1163,20 +1137,22 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= -k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= -k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= +k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= +k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= +k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= -k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= +k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM= -k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= -k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= +k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= +k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= +k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -1186,18 +1162,17 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 h1:XmRqFcQlCy/lKRZ39j+RVpokYNroHPqV3mcBRfnhT5o= -k8s.io/utils v0.0.0-20220812165043-ad590609e2e5/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= +k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= @@ -1206,12 +1181,18 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= +sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/gateway-api v0.7.0 h1:/mG8yyJNBifqvuVLW5gwlI4CQs0NR/5q4BKUlf1bVdY= +sigs.k8s.io/gateway-api v0.7.0/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/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go new file mode 100644 index 0000000000..251d966803 --- /dev/null +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -0,0 +1,248 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package apigateway + +import ( + "context" + "fmt" + "strconv" + "testing" + "time" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +const ( + StaticClientName = "static-client" + gatewayClassControllerName = "hashicorp.com/consul-api-gateway-controller" + gatewayClassFinalizer = "gateway-exists-finalizer.consul.hashicorp.com" + gatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" +) + +// Test that api gateway basic functionality works in a default installation and a secure installation. +func TestAPIGateway(t *testing.T) { + cases := []struct { + secure bool + }{ + { + secure: false, + }, + { + secure: true, + }, + } + for _, c := range cases { + name := fmt.Sprintf("secure: %t", c.secure) + t.Run(name, func(t *testing.T) { + ctx := suite.Environment().DefaultContext(t) + cfg := suite.Config() + helmValues := map[string]string{ + "connectInject.enabled": "true", + "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), + "global.tls.enabled": strconv.FormatBool(c.secure), + "global.logLevel": "trace", + } + + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + + consulCluster.Create(t) + + // Override the default proxy config settings for this test + consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) + _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + }, nil) + require.NoError(t, err) + + logger.Log(t, "creating api-gateway resources") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") + }) + + logger.Log(t, "creating target server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + logger.Log(t, "patching route to target server") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"name":"static-server","port":80}]}]}}`, "--type=merge") + + // We use the static-client pod so that we can make calls to the api gateway + // via kubectl exec without needing a route into the cluster from the test machine. + logger.Log(t, "creating static-client pod") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + + // Grab a kubernetes client so that we can verify binding + // behavior prior to issuing requests through the gateway. + k8sClient := ctx.ControllerRuntimeClient(t) + + // On startup, the controller can take upwards of 1m to perform + // leader election so we may need to wait a long time for + // the reconcile loop to run (hence the 1m timeout here). + var gatewayAddress string + counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} + retry.RunWith(counter, t, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) + require.NoError(r, err) + + // check our finalizers + require.Len(r, gateway.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, gateway.Finalizers[0]) + + // check our statuses + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) + require.Len(r, gateway.Status.Listeners, 2) + require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + require.EqualValues(r, 0, gateway.Status.Listeners[1].AttachedRoutes) + checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) + checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + + // check that we have an address to use + require.Len(r, gateway.Status.Addresses, 1) + // now we know we have an address, set it so we can use it + gatewayAddress = gateway.Status.Addresses[0].Value + }) + + // now that we've satisfied those assertions, we know reconciliation is done + // so we can run assertions on the routes and the other objects + + // gateway class checks + var gatewayClass gwv1beta1.GatewayClass + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway-class"}, &gatewayClass) + require.NoError(t, err) + + // check our finalizers + require.Len(t, gatewayClass.Finalizers, 1) + require.EqualValues(t, gatewayClassFinalizer, gatewayClass.Finalizers[0]) + + // http route checks + var httproute gwv1beta1.HTTPRoute + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route", Namespace: "default"}, &httproute) + require.NoError(t, err) + + // check our finalizers + require.Len(t, httproute.Finalizers, 1) + require.EqualValues(t, gatewayFinalizer, httproute.Finalizers[0]) + + // check parent status + require.Len(t, httproute.Status.Parents, 1) + require.EqualValues(t, gatewayClassControllerName, httproute.Status.Parents[0].ControllerName) + require.EqualValues(t, "gateway", httproute.Status.Parents[0].ParentRef.Name) + checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + + // check that the Consul entries were created + entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) + require.NoError(t, err) + gateway := entry.(*api.APIGatewayConfigEntry) + + entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) + require.NoError(t, err) + route := entry.(*api.HTTPRouteConfigEntry) + + // now check the gateway status conditions + checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) + + // and the route status conditions + checkConsulStatusCondition(t, route.Status.Conditions, trueConsulCondition("Bound", "Bound")) + + // finally we check that we can actually route to the service via the gateway + k8sOptions := ctx.KubectlOptions(t) + targetAddress := fmt.Sprintf("http://%s:8080/", gatewayAddress) + + if c.secure { + // check that intentions keep our connection from happening + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetAddress) + + // Now we create the allow intention. + _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: "static-server", + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Action: api.IntentionActionAllow, + }, + }, + }, nil) + require.NoError(t, err) + } + + // Test that we can make a call to the api gateway + // via the static-client pod. It should route to the static-server pod. + logger.Log(t, "trying calls to api gateway") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetAddress) + }) + } +} + +func checkStatusCondition(t require.TestingT, conditions []metav1.Condition, toCheck metav1.Condition) { + for _, c := range conditions { + if c.Type == toCheck.Type { + require.EqualValues(t, toCheck.Reason, c.Reason) + require.EqualValues(t, toCheck.Status, c.Status) + return + } + } + + t.Errorf("expected condition not found: %s", toCheck.Type) +} + +func trueCondition(conditionType, reason string) metav1.Condition { + return metav1.Condition{ + Type: conditionType, + Reason: reason, + Status: metav1.ConditionTrue, + } +} + +func falseCondition(conditionType, reason string) metav1.Condition { + return metav1.Condition{ + Type: conditionType, + Reason: reason, + Status: metav1.ConditionFalse, + } +} + +func checkConsulStatusCondition(t require.TestingT, conditions []api.Condition, toCheck api.Condition) { + for _, c := range conditions { + if c.Type == toCheck.Type { + require.EqualValues(t, toCheck.Reason, c.Reason) + require.EqualValues(t, toCheck.Status, c.Status) + return + } + } + + t.Errorf("expected condition not found: %s", toCheck.Type) +} + +func trueConsulCondition(conditionType, reason string) api.Condition { + return api.Condition{ + Type: conditionType, + Reason: reason, + Status: "True", + } +} diff --git a/acceptance/tests/api-gateway/example_test.go b/acceptance/tests/api-gateway/example_test.go deleted file mode 100644 index b324ac31fe..0000000000 --- a/acceptance/tests/api-gateway/example_test.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Rename package to your test package. -package example - -import ( - "context" - "testing" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestExample(t *testing.T) { - // Get test configuration. - cfg := suite.Config() - - // Get the default context. - ctx := suite.Environment().DefaultContext(t) - - // Create Helm values for the Helm install. - helmValues := map[string]string{ - "exampleFeature.enabled": "true", - } - - // Generate a random name for this test. - releaseName := helpers.RandomName() - - // Create a new Consul cluster object. - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - - // Create the Consul cluster with Helm. - consulCluster.Create(t) - - // Make test assertions. - - // To run kubectl commands, you need to get KubectlOptions from the test context. - // There are a number of kubectl commands available in the helpers/kubectl.go file. - // For example, to call 'kubectl apply' from the test write the following: - k8s.KubectlApply(t, ctx.KubectlOptions(t), "path/to/config") - - // Clean up any Kubernetes resources you have created - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { - k8s.KubectlDelete(t, ctx.KubectlOptions(t), "path/to/config") - }) - - // Similarly, you can obtain Kubernetes client from your test context. - // You can use it to, for example, read all services in a namespace: - k8sClient := ctx.KubernetesClient(t) - services, err := k8sClient.CoreV1().Services(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, services.Items) - - // To make Consul API calls, you can get the Consul client from the consulCluster object, - // indicating whether the client needs to be secure or not (i.e. whether TLS and ACLs are enabled on the Consul cluster): - consulClient, _ := consulCluster.SetupConsulClient(t, true) - consulServices, _, err := consulClient.Catalog().Services(nil) - require.NoError(t, err) - require.NotNil(t, consulServices) -} diff --git a/acceptance/tests/api-gateway/main_test.go b/acceptance/tests/api-gateway/main_test.go index f92fff8a59..f408845b3e 100644 --- a/acceptance/tests/api-gateway/main_test.go +++ b/acceptance/tests/api-gateway/main_test.go @@ -1,10 +1,10 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -// Rename package to your test package. -package example +package apigateway import ( + "os" "testing" testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" @@ -13,25 +13,6 @@ import ( var suite testsuite.Suite func TestMain(m *testing.M) { - // First, uncomment the line below to create a new suite so that all flags are parsed. - /* - suite = framework.NewSuite(m) - */ - - // If the test suite needs to run only when certain test flags are passed, - // you need to handle that in the TestMain function. - // Uncomment and modify example code below if that is the case. - /* - if suite.Config().EnableExampleFeature { - os.Exit(suite.Run()) - } else { - fmt.Println("Skipping example feature tests because -enable-example-feature is not set") - os.Exit(0) - } - */ - - // If the test suite should run in every case, uncomment the line below. - /* - os.Exit(suite.Run()) - */ + suite = testsuite.NewSuite(m) + os.Exit(suite.Run()) } diff --git a/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml b/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml new file mode 100644 index 0000000000..de7ac7b5de --- /dev/null +++ b/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml @@ -0,0 +1,16 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: gateway +spec: + gatewayClassName: gateway-class + listeners: + - protocol: HTTP + port: 8080 + name: http + - protocol: TCP + port: 8081 + name: tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml b/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml new file mode 100644 index 0000000000..872faeb78c --- /dev/null +++ b/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: GatewayClass +metadata: + name: gateway-class +spec: + controllerName: "hashicorp.com/consul-api-gateway-controller" + parametersRef: + group: consul.hashicorp.com + kind: GatewayClassConfig + name: gateway-class-config diff --git a/acceptance/tests/fixtures/bases/api-gateway/gatewayclassconfig.yaml b/acceptance/tests/fixtures/bases/api-gateway/gatewayclassconfig.yaml new file mode 100644 index 0000000000..b8dfae7aa5 --- /dev/null +++ b/acceptance/tests/fixtures/bases/api-gateway/gatewayclassconfig.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayClassConfig +metadata: + name: gateway-class-config \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway/httproute.yaml b/acceptance/tests/fixtures/bases/api-gateway/httproute.yaml new file mode 100644 index 0000000000..d59c4e067e --- /dev/null +++ b/acceptance/tests/fixtures/bases/api-gateway/httproute.yaml @@ -0,0 +1,10 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route +spec: + parentRefs: + - name: gateway \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml b/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml new file mode 100644 index 0000000000..2049f1af0b --- /dev/null +++ b/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - gatewayclassconfig.yaml + - gatewayclass.yaml + - apigateway.yaml + - httproute.yaml \ No newline at end of file diff --git a/charts/consul/.helmignore b/charts/consul/.helmignore index d1180d2fb7..3fa2f24edf 100644 --- a/charts/consul/.helmignore +++ b/charts/consul/.helmignore @@ -2,3 +2,4 @@ .terraform/ bin/ test/ +crds/kustomization.yaml diff --git a/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml b/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml new file mode 100644 index 0000000000..a8393cd8fd --- /dev/null +++ b/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml @@ -0,0 +1,140 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: gatewayclassconfigs.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: GatewayClassConfig + listKind: GatewayClassConfigList + plural: gatewayclassconfigs + singular: gatewayclassconfig + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GatewayClassConfig defines the values that may be set on a GatewayClass + for Consul API Gateway. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClassConfig. + properties: + copyAnnotations: + description: Annotation Information to copy to services or deployments + properties: + service: + description: List of annotations to copy to the gateway service. + items: + type: string + type: array + type: object + deployment: + description: Deployment defines the deployment configuration for the + gateway. + properties: + defaultInstances: + default: 1 + description: Number of gateway instances that should be deployed + by default + format: int32 + maximum: 8 + minimum: 1 + type: integer + maxInstances: + default: 8 + description: Max allowed number of gateway instances + format: int32 + maximum: 8 + minimum: 1 + type: integer + minInstances: + default: 1 + description: Minimum allowed number of gateway instances + format: int32 + maximum: 8 + minimum: 1 + type: integer + type: object + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for the + pod to fit on a node. Selector which must match a node''s labels + for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + podSecurityPolicy: + description: The name of an existing Kubernetes PodSecurityPolicy + to bind to the managed ServiceAccount if ACLs are managed. + type: string + serviceType: + description: Service Type string describes ingress methods for a service + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + tolerations: + description: 'Tolerations allow the scheduler to schedule nodes with + matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + served: true + storage: true diff --git a/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml b/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml new file mode 100644 index 0000000000..044c7af939 --- /dev/null +++ b/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml @@ -0,0 +1,323 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + deprecated: true + deprecationWarning: The v1alpha2 version of GatewayClass has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: Status defines the current state of GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: Status defines the current state of GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml.yml b/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml.yml new file mode 100644 index 0000000000..044c7af939 --- /dev/null +++ b/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml.yml @@ -0,0 +1,323 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + deprecated: true + deprecationWarning: The v1alpha2 version of GatewayClass has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: Status defines the current state of GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: Status defines the current state of GatewayClass. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml b/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml new file mode 100644 index 0000000000..b7a7c8a7d1 --- /dev/null +++ b/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml @@ -0,0 +1,877 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of Gateway has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n The .listener.hostname field is used to route traffic that has already arrived at the Gateway to the correct in-cluster destination. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended" + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" + items: + description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Accepted + description: Status defines the current state of Gateway. + properties: + addresses: + description: Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this listener. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n The .listener.hostname field is used to route traffic that has already arrived at the Gateway to the correct in-cluster destination. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended" + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" + items: + description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Accepted + description: Status defines the current state of Gateway. + properties: + addresses: + description: Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this listener. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml.yml b/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml.yml new file mode 100644 index 0000000000..b7a7c8a7d1 --- /dev/null +++ b/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml.yml @@ -0,0 +1,877 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of Gateway has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n The .listener.hostname field is used to route traffic that has already arrived at the Gateway to the correct in-cluster destination. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended" + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" + items: + description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Accepted + description: Status defines the current state of Gateway. + properties: + addresses: + description: Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this listener. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n The .listener.hostname field is used to route traffic that has already arrived at the Gateway to the correct in-cluster destination. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended" + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" + items: + description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: NotReconciled + status: Unknown + type: Accepted + description: Status defines the current state of Gateway. + properties: + addresses: + description: Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this listener. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml b/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml new file mode 100644 index 0000000000..8f3ab6d385 --- /dev/null +++ b/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml @@ -0,0 +1,761 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge. \n Support: Extended" + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - method: + type: Exact + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: GRPCRouteRule defines the semantics for matching an gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + properties: + filters: + description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. Support: Core" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - method: + type: Exact + description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." + items: + description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. + items: + description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. + properties: + name: + description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + default: + type: Exact + description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. + properties: + method: + description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Method must be a valid Protobuf Method (https://protobuf.com/docs/language-spec#methods)." + maxLength: 1024 + pattern: ^[A-Za-z_][A-Za-z_0-9]*$ + type: string + service: + description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Service must be a valid Protobuf Type Name (https://protobuf.com/docs/language-spec#type-references)." + maxLength: 1024 + pattern: ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$ + type: string + type: + default: Exact + description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - RegularExpression + type: string + type: object + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml.yml new file mode 100644 index 0000000000..8f3ab6d385 --- /dev/null +++ b/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml.yml @@ -0,0 +1,761 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge. \n Support: Extended" + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - method: + type: Exact + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: GRPCRouteRule defines the semantics for matching an gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + properties: + filters: + description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. Support: Core" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - method: + type: Exact + description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." + items: + description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. + items: + description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. + properties: + name: + description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + default: + type: Exact + description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. + properties: + method: + description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Method must be a valid Protobuf Method (https://protobuf.com/docs/language-spec#methods)." + maxLength: 1024 + pattern: ^[A-Za-z_][A-Za-z_0-9]*$ + type: string + service: + description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Service must be a valid Protobuf Type Name (https://protobuf.com/docs/language-spec#type-references)." + maxLength: 1024 + pattern: ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$ + type: string + type: + default: Exact + description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - RegularExpression + type: string + type: object + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml b/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml new file mode 100644 index 0000000000..b455d788de --- /dev/null +++ b/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml @@ -0,0 +1,1909 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of HTTPRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml.yml new file mode 100644 index 0000000000..b455d788de --- /dev/null +++ b/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml.yml @@ -0,0 +1,1909 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of HTTPRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/kustomization.yaml b/charts/consul/crds/kustomization.yaml new file mode 100644 index 0000000000..a1a0e349ff --- /dev/null +++ b/charts/consul/crds/kustomization.yaml @@ -0,0 +1,10 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# This file is Helm ignored. It is only used for the `make generate-external-crds` command. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v0.6.2 diff --git a/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml b/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml new file mode 100644 index 0000000000..cd39b9c12a --- /dev/null +++ b/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml @@ -0,0 +1,203 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed. \n Support: Core" + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and kinds. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed. \n Support: Core" + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and kinds. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: false + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml.yml b/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml.yml new file mode 100644 index 0000000000..cd39b9c12a --- /dev/null +++ b/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml.yml @@ -0,0 +1,203 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed. \n Support: Core" + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and kinds. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed. \n Support: Core" + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and kinds. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: false + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml b/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml new file mode 100644 index 0000000000..906b442d31 --- /dev/null +++ b/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml @@ -0,0 +1,276 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml.yml new file mode 100644 index 0000000000..906b442d31 --- /dev/null +++ b/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml.yml @@ -0,0 +1,276 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml b/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml new file mode 100644 index 0000000000..2e22b04ef0 --- /dev/null +++ b/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml @@ -0,0 +1,286 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tlsroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "The TLSRoute resource is similar to TCPRoute, but can be configured to match against TLS-specific metadata. This allows more flexibility in matching streams for a given TLS listener. \n If you need to forward traffic to a single target for a TLS listener, you could choose to use a TCPRoute with a TLS listener." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: "Hostnames defines a set of SNI names that should match against the SNI attribute of TLS ClientHello message in TLS handshake. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and TLSRoute, there must be at least one intersecting hostname for the TLSRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n If both the Listener and TLSRoute have specified hostnames, any TLSRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the TLSRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and TLSRoute have specified hostnames, and none match with the criteria above, then the TLSRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this backend, by rejecting the connection or returning a 500 status code. Request rejections must respect weight; if an invalid backend is requested to have 80% of requests, then 80% of requests must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml.yml new file mode 100644 index 0000000000..2e22b04ef0 --- /dev/null +++ b/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml.yml @@ -0,0 +1,286 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tlsroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "The TLSRoute resource is similar to TCPRoute, but can be configured to match against TLS-specific metadata. This allows more flexibility in matching streams for a given TLS listener. \n If you need to forward traffic to a single target for a TLS listener, you could choose to use a TCPRoute with a TLS listener." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: "Hostnames defines a set of SNI names that should match against the SNI attribute of TLS ClientHello message in TLS handshake. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and TLSRoute, there must be at least one intersecting hostname for the TLSRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n If both the Listener and TLSRoute have specified hostnames, any TLSRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the TLSRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and TLSRoute have specified hostnames, and none match with the criteria above, then the TLSRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this backend, by rejecting the connection or returning a 500 status code. Request rejections must respect weight; if an invalid backend is requested to have 80% of requests, then 80% of requests must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml b/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml new file mode 100644 index 0000000000..19b03dcd0b --- /dev/null +++ b/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml @@ -0,0 +1,276 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: UDPRoute provides a way to route UDP traffic. When combined with a Gateway listener, it can be used to forward traffic on the port specified by the listener to a set of backends specified by the UDPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Packet drops must respect weight; if an invalid backend is requested to have 80% of the packets, then 80% of packets must be dropped instead. \n Support: Core for Kubernetes Service Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml.yml new file mode 100644 index 0000000000..19b03dcd0b --- /dev/null +++ b/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml.yml @@ -0,0 +1,276 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: UDPRoute provides a way to route UDP traffic. When combined with a Gateway listener, it can be used to forward traffic on the port specified by the listener to a set of backends specified by the UDPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Packet drops must respect weight; if an invalid backend is requested to have 80% of the packets, then 80% of packets must be dropped instead. \n Support: Core for Kubernetes Service Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index e383e5ce28..e152511469 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -24,6 +24,8 @@ rules: - serviceintentions - ingressgateways - terminatinggateways + - gatewayclassconfigs + - meshservices - samenessgroups {{- if .Values.global.peering.enabled }} - peeringacceptors @@ -59,18 +61,23 @@ rules: - get - patch - update -{{- if .Values.global.acls.manageSystemACLs }} - apiGroups: [ "" ] - resources: [ "serviceaccounts", "secrets" ] + resources: [ "secrets", "serviceaccounts", "endpoints", "services", "namespaces", "nodes" ] verbs: + - create - get -{{- end }} -- apiGroups: [ "" ] - resources: [ "endpoints", "services", "namespaces", "nodes" ] + - list + - watch + - delete +- apiGroups: [ "rbac.authorization.k8s.io" ] + resources: [ "roles" ] verbs: - - "get" - - "list" - - "watch" + - get + - list + - watch + - delete + - create + - update - apiGroups: [ "" ] resources: - pods @@ -110,12 +117,68 @@ rules: - "update" - "delete" {{- end }} -{{- if .Values.global.enablePodSecurityPolicies }} - apiGroups: [ "policy" ] resources: [ "podsecuritypolicies" ] - resourceNames: - - {{ template "consul.fullname" . }}-connect-injector verbs: - use -{{- end }} +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + - gateways + - httproutes + - tcproutes + - referencegrants + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses/finalizers + - gateways/finalizers + - httproutes/finalizers + - tcproutes/finalizers + verbs: + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses/status + - gateways/status + - httproutes/status + - tcproutes/status + verbs: + - get + - patch + - update +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - get + - list + - update + - watch + - delete +- apiGroups: + - core + resources: + - services + verbs: + - watch + - list +- apiGroups: [ "" ] + resources: [ "secrets" ] + verbs: + - "get" + - "list" + - "watch" {{- end }} diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 591500cb12..7ffddf7537 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: exportedservices.consul.hashicorp.com labels: @@ -138,10 +138,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index a01fafd8dd..ef33890461 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: ingressgateways.consul.hashicorp.com labels: @@ -368,10 +368,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index 0710d41280..cdc11b6ed9 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: meshes.consul.hashicorp.com labels: @@ -206,10 +206,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml new file mode 100644 index 0000000000..859c8683ee --- /dev/null +++ b/charts/consul/templates/crd-meshservices.yaml @@ -0,0 +1,58 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: meshservices.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: consul.hashicorp.com + names: + kind: MeshService + listKind: MeshServiceList + plural: meshservices + singular: meshservice + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: MeshService holds a reference to an externally managed Consul + Service Mesh service. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of MeshService. + properties: + name: + description: Name holds the service name for a Consul service. + type: string + peer: + description: Peer optionally specifies the name of the peer exporting + the Consul service. If not specified, the Consul service is assumed + to be in the local datacenter. + type: string + type: object + type: object + served: true + storage: true +{{- end }} diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index e06e830f04..3822f3bdfe 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: peeringacceptors.consul.hashicorp.com labels: @@ -145,10 +145,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index e24401e761..405361c486 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: peeringdialers.consul.hashicorp.com labels: @@ -145,10 +145,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 362672c1c1..30dd25f674 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: proxydefaults.consul.hashicorp.com labels: @@ -254,10 +254,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index 60beb5662c..c1d1c85a8e 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: samenessgroups.consul.hashicorp.com labels: @@ -128,10 +128,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 36900d1bda..2562c53320 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: servicedefaults.consul.hashicorp.com labels: @@ -494,10 +494,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 67998f776c..f16d0a0d8a 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: serviceintentions.consul.hashicorp.com labels: @@ -235,10 +235,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 2a6f7923b8..ed95c15846 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: serviceresolvers.consul.hashicorp.com labels: @@ -333,10 +333,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index 5052facc06..e7a3239e75 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: servicerouters.consul.hashicorp.com labels: @@ -307,10 +307,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index a2af050c3d..18fb10341e 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: servicesplitters.consul.hashicorp.com labels: @@ -185,10 +185,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 583c218be8..955496aeee 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: terminatinggateways.consul.hashicorp.com labels: @@ -136,10 +136,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/gateway-cleanup-clusterrole.yaml b/charts/consul/templates/gateway-cleanup-clusterrole.yaml new file mode 100644 index 0000000000..c533a882f5 --- /dev/null +++ b/charts/consul/templates/gateway-cleanup-clusterrole.yaml @@ -0,0 +1,35 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "consul.fullname" . }}-gateway-cleanup + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-cleanup +rules: + - apiGroups: + - consul.hashicorp.com + resources: + - gatewayclassconfigs + verbs: + - get + - delete + - apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + verbs: + - get + - delete +{{- if .Values.global.enablePodSecurityPolicies }} + - apiGroups: ["policy"] + resources: ["podsecuritypolicies"] + resourceNames: + - {{ template "consul.fullname" . }}-gateway-cleanup + verbs: + - use +{{- end }} +{{- end }} diff --git a/charts/consul/templates/gateway-cleanup-clusterrolebinding.yaml b/charts/consul/templates/gateway-cleanup-clusterrolebinding.yaml new file mode 100644 index 0000000000..9235f32101 --- /dev/null +++ b/charts/consul/templates/gateway-cleanup-clusterrolebinding.yaml @@ -0,0 +1,20 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "consul.fullname" . }}-gateway-cleanup + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-cleanup +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "consul.fullname" . }}-gateway-cleanup +subjects: + - kind: ServiceAccount + name: {{ template "consul.fullname" . }}-gateway-cleanup + namespace: {{ .Release.Namespace }} +{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml new file mode 100644 index 0000000000..ff6f295357 --- /dev/null +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -0,0 +1,61 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "consul.fullname" . }}-gateway-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-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" . }}-gateway-cleanup + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: gateway-cleanup + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} + annotations: + "consul.hashicorp.com/connect-inject": "false" + spec: + restartPolicy: Never + serviceAccountName: {{ template "consul.fullname" . }}-gateway-cleanup + containers: + - name: gateway-cleanup + image: {{ .Values.global.imageK8S }} + command: + - consul-k8s-control-plane + args: + - gateway-cleanup + - -gateway-class-name=consul-api-gateway + - -gateway-class-config-name=consul-api-gateway + resources: + requests: + memory: "50Mi" + cpu: "50m" + limits: + memory: "50Mi" + cpu: "50m" + {{- if .Values.global.acls.tolerations }} + tolerations: + {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} + {{- end }} + {{- if .Values.global.acls.nodeSelector }} + nodeSelector: + {{ tpl .Values.global.acls.nodeSelector . | indent 8 | trim }} + {{- end }} +{{- end }} diff --git a/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml new file mode 100644 index 0000000000..c4d4e8acca --- /dev/null +++ b/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml @@ -0,0 +1,34 @@ +{{- if .Values.global.enablePodSecurityPolicies }} +{{- if .Values.connectInject.enabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "consul.fullname" . }}-gateway-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-cleanup +spec: + privileged: false + allowPrivilegeEscalation: false + # This is redundant with non-root + disallow privilege escalation, + # but we can provide it for defense in depth. + requiredDropCapabilities: + - ALL + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false +{{- end }} +{{- end }} diff --git a/charts/consul/templates/gateway-cleanup-serviceaccount.yaml b/charts/consul/templates/gateway-cleanup-serviceaccount.yaml new file mode 100644 index 0000000000..f50eb72d97 --- /dev/null +++ b/charts/consul/templates/gateway-cleanup-serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "consul.fullname" . }}-gateway-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-cleanup +{{- end }} diff --git a/charts/consul/templates/gateway-gatewayclass.yaml b/charts/consul/templates/gateway-gatewayclass.yaml new file mode 100644 index 0000000000..612627fbda --- /dev/null +++ b/charts/consul/templates/gateway-gatewayclass.yaml @@ -0,0 +1,18 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: GatewayClass +metadata: + name: consul-api-gateway + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: api-gateway-controller +spec: + controllerName: consul.hashicorp.com/consul-api-gateway-controller + parametersRef: + group: consul.hashicorp.com + kind: GatewayClassConfig + name: consul-api-gateway +{{- end }} diff --git a/charts/consul/templates/gateway-gatewayclassconfig.yaml b/charts/consul/templates/gateway-gatewayclassconfig.yaml new file mode 100644 index 0000000000..b812ebd814 --- /dev/null +++ b/charts/consul/templates/gateway-gatewayclassconfig.yaml @@ -0,0 +1,74 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayClassConfig +metadata: + name: consul-api-gateway + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: api-gateway +spec: + {{- if .Values.apiGateway.enabled }} # Overide values from the old stanza. To be removed in 1.17 (t-eckert 2023-05-19) + + {{- if .Values.apiGateway.managedGatewayClass.deployment }} + deployment: + {{- if .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} + defaultInstances: {{ .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} + {{- end}} + {{- if .Values.apiGateway.managedGatewayClass.deployment.maxInstances }} + maxInstances: {{ .Values.apiGateway.managedGatewayClass.deployment.maxInstances }} + {{- end}} + {{- if .Values.apiGateway.managedGatewayClass.deployment.minInstances }} + minInstances: {{ .Values.apiGateway.managedGatewayClass.deployment.minInstances }} + {{- end}} + {{- end}} + {{- if .Values.apiGateway.managedGatewayClass.nodeSelector }} + nodeSelector: + {{ tpl .Values.apiGateway.managedGatewayClass.nodeSelector . | indent 4 | trim }} + {{- end }} + {{- if .Values.apiGateway.managedGatewayClass.tolerations }} + tolerations: + {{ tpl .Values.apiGateway.managedGatewayClass.tolerations . | indent 4 | trim }} + {{- end }} + {{- if .Values.apiGateway.managedGatewayClass.copyAnnotations.service }} + copyAnnotations: + service: + {{ tpl .Values.apiGateway.managedGatewayClass.copyAnnotations.service.annotations . | nindent 6 | trim }} + {{- end }} + serviceType: {{ .Values.apiGateway.managedGatewayClass.serviceType }} + + {{- else }} + + {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment }} + deployment: + {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} + defaultInstances: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} + {{- end}} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} + maxInstances: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} + {{- end}} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} + minInstances: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} + {{- end}} + {{- end}} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector }} + nodeSelector: + {{ tpl .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector . | indent 4 | trim }} + {{- end }} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} + tolerations: + {{ tpl .Values.connectInject.apiGateway.managedGatewayClass.tolerations . | indent 4 | trim }} + {{- end }} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service }} + copyAnnotations: + service: + {{ tpl .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations . | nindent 6 | trim }} + {{- end }} + serviceType: {{ .Values.connectInject.apiGateway.managedGatewayClass.serviceType }} + + {{- end }} + +{{- end }} diff --git a/charts/consul/test/unit/connect-inject-clusterrole.bats b/charts/consul/test/unit/connect-inject-clusterrole.bats index 4acdf211d2..ace8c18d4a 100644 --- a/charts/consul/test/unit/connect-inject-clusterrole.bats +++ b/charts/consul/test/unit/connect-inject-clusterrole.bats @@ -77,7 +77,7 @@ load _helpers --set 'client.enabled=true' \ --set 'connectInject.enabled=true' \ . | tee /dev/stderr | - yq -r '.rules[3]' | tee /dev/stderr) + yq -r '.rules[4]' | tee /dev/stderr) local actual=$(echo $object | yq -r '.resources[| index("pods")' | tee /dev/stderr) [ "${actual}" != null ] @@ -106,7 +106,7 @@ load _helpers --set 'client.enabled=true' \ --set 'connectInject.enabled=true' \ . | tee /dev/stderr | - yq -r '.rules[4]' | tee /dev/stderr) + yq -r '.rules[5]' | tee /dev/stderr) local actual=$(echo $object | yq -r '.resources[| index("leases")' | tee /dev/stderr) [ "${actual}" != null ] @@ -154,7 +154,7 @@ load _helpers #-------------------------------------------------------------------- # global.enablePodSecurityPolicies -@test "connectInject/ClusterRole: no podsecuritypolicies access with global.enablePodSecurityPolicies=false" { +@test "connectInject/ClusterRole: allows podsecuritypolicies access with global.enablePodSecurityPolicies=false" { cd `chart_dir` local actual=$(helm template \ -s templates/connect-inject-clusterrole.yaml \ @@ -162,7 +162,7 @@ load _helpers --set 'global.enablePodSecurityPolicies=false' \ . | tee /dev/stderr | yq -r '.rules | map(select(.resources[0] == "podsecuritypolicies")) | length' | tee /dev/stderr) - [ "${actual}" = "0" ] + [ "${actual}" = "1" ] } @test "connectInject/ClusterRole: allows podsecuritypolicies access with global.enablePodSecurityPolicies=true" { @@ -197,7 +197,7 @@ load _helpers --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ . | tee /dev/stderr | - yq -r '.rules[5]' | tee /dev/stderr) + yq -r '.rules[6]' | tee /dev/stderr) local actual=$(echo $object | yq -r '.resources[0]' | tee /dev/stderr) [ "${actual}" = "mutatingwebhookconfigurations" ] diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 90166daca2..c60ea14f1f 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -2436,3 +2436,4 @@ reservedNameTest() { jq -r '. | select( .name == "CONSUL_TLS_SERVER_NAME").value' | tee /dev/stderr) [ "${actual}" = "server.dc1.consul" ] } + diff --git a/charts/consul/test/unit/gateway-gatewayclass.bats b/charts/consul/test/unit/gateway-gatewayclass.bats new file mode 100755 index 0000000000..ac8a53aed9 --- /dev/null +++ b/charts/consul/test/unit/gateway-gatewayclass.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/GatewayClass: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/gateway-gatewayclass.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClass: disabled with connectInject.enabled false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/gateway-gatewayclass.yaml \ + --set 'connectInject.enabled=false' \ + . +} + diff --git a/charts/consul/test/unit/gateway-gatewayclassconfig.bats b/charts/consul/test/unit/gateway-gatewayclassconfig.bats new file mode 100644 index 0000000000..30aa972cbe --- /dev/null +++ b/charts/consul/test/unit/gateway-gatewayclassconfig.bats @@ -0,0 +1,117 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/GatewayClassConfig: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/gateway-gatewayclassconfig.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: disabled with connectInject.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/gateway-gatewayclassconfig.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +#-------------------------------------------------------------------- +# fallback configuration +# to be removed in 1.17 (t-eckert 2023-05-23) + +@test "apiGateway/GatewayClassConfig: fallback configuration is used when apiGateway.enabled is true" { + cd `chart_dir` + local spec=$(helm template \ + -s templates/gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=testing' \ + --set 'apiGateway.managedGatewayClass.nodeSelector=foo: bar' \ + --set 'apiGateway.managedGatewayClass.tolerations=- key: bar' \ + --set 'apiGateway.managedGatewayClass.copyAnnotations.service.annotations=- bingo' \ + --set 'apiGateway.managedGatewayClass.serviceType=LoadBalancer' \ + . | tee /dev/stderr | + yq '.spec' | tee /dev/stderr) + + local actual=$(echo "$spec" | + jq -r '.nodeSelector.foo') + [ "${actual}" = "bar" ] + + local actual=$(echo "$spec" | + jq -r '.tolerations[0].key') + [ "${actual}" = "bar" ] + + local actual=$(echo "$spec" | + jq -r '.copyAnnotations.service[0]') + [ "${actual}" = "bingo" ] + + local actual=$(echo "$spec" | + jq -r '.serviceType') + [ "${actual}" = "LoadBalancer" ] +} + +#-------------------------------------------------------------------- +# configuration + +@test "apiGateway/GatewayClassConfig: default configuration" { + cd `chart_dir` + local spec=$(helm template \ + -s templates/gateway-gatewayclassconfig.yaml \ + . | tee /dev/stderr | + yq '.spec' | tee /dev/stderr) + + local actual=$(echo "$spec" | + jq -r '.deployment.defaultInstances') + [ "${actual}" = 1 ] + + local actual=$(echo "$spec" | + jq -r '.deployment.maxInstances') + [ "${actual}" = 1 ] + + local actual=$(echo "$spec" | + jq -r '.deployment.minInstances') + [ "${actual}" = 1 ] +} + +@test "apigateway/gatewayclassconfig: custom configuration" { + cd `chart_dir` + local spec=$(helm template \ + -s templates/gateway-gatewayclassconfig.yaml \ + --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=LoadBalancer' \ + . | tee /dev/stderr | + yq '.spec' | tee /dev/stderr) + + local actual=$(echo "$spec" | + jq -r '.deployment.defaultInstances') + [ "${actual}" = "1" ] + + local actual=$(echo "$spec" | + jq -r '.deployment.maxInstances') + [ "${actual}" = "1" ] + + local actual=$(echo "$spec" | + jq -r '.deployment.minInstances') + [ "${actual}" = "1" ] + + local actual=$(echo "$spec" | + jq -r '.nodeSelector.foo') + [ "${actual}" = "bar" ] + + local actual=$(echo "$spec" | + jq -r '.tolerations[0].key') + [ "${actual}" = "bar" ] + + local actual=$(echo "$spec" | + jq -r '.copyAnnotations.service[0]') + [ "${actual}" = "bingo" ] + + local actual=$(echo "$spec" | + jq -r '.serviceType') + [ "${actual}" = "LoadBalancer" ] +} diff --git a/charts/consul/test/unit/helpers.bats b/charts/consul/test/unit/helpers.bats index 4245b519c4..efcbc116b5 100644 --- a/charts/consul/test/unit/helpers.bats +++ b/charts/consul/test/unit/helpers.bats @@ -115,7 +115,7 @@ load _helpers @test "helper/namespace: used everywhere" { cd `chart_dir` # Grep for files that don't have 'namespace: ' in them - local actual=$(grep -L 'namespace: ' templates/*.yaml | grep -v 'crd' | grep -v 'clusterrole' | grep -v 'api-gateway-gateway' | tee /dev/stderr ) + local actual=$(grep -L 'namespace: ' templates/*.yaml | grep -v 'crd' | grep -v 'clusterrole' | grep -v 'gateway-gateway' | tee /dev/stderr ) [ "${actual}" = '' ] } diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 83b18ae044..e71282c520 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -1993,6 +1993,79 @@ connectInject: # @type: integer minAvailable: null + # Configuration settings for the Consul API Gateway integration. + apiGateway: + # Configuration settings for the GatewayClass installed by Consul on Kubernetes. + managedGatewayClass: + # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + # labels for gateway pod assignment, formatted as a multi-line string. + # + # Example: + # + # ```yaml + # nodeSelector: | + # beta.kubernetes.io/arch: amd64 + # ``` + # + # @type: string + nodeSelector: null + + # Toleration settings for gateway pods created with the managed gateway class. + # This should be a multi-line string matching the + # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + # + # @type: string + tolerations: null + + # This value defines the type of Service created for gateways (e.g. LoadBalancer, ClusterIP) + serviceType: LoadBalancer + + # Configuration settings for annotations to be copied from the Gateway to other child resources. + copyAnnotations: + # This value defines a list of annotations to be copied from the Gateway to the Service created, formatted as a multi-line string. + # + # Example: + # + # ```yaml + # service: + # annotations: | + # - external-dns.alpha.kubernetes.io/hostname + # ``` + # + # @type: string + service: null + + # This value defines the number of pods to deploy for each Gateway as well as a min and max number of pods for all Gateways + deployment: + defaultInstances: 1 + maxInstances: 1 + minInstances: 1 + + # Configuration for the ServiceAccount created for the api-gateway component + serviceAccount: + # This value defines additional annotations for the client service account. This should be formatted as a multi-line + # string. + # + # ```yaml + # annotations: | + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # ``` + # + # @type: string + annotations: null + + # The resource settings for Pods handling traffic for Gateway API. + # @recurse: false + # @type: map + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "100Mi" + cpu: "100m" + # Configures consul-cni plugin for Consul Service mesh services cni: # If true, then all traffic redirection setup uses the consul-cni plugin. @@ -2922,6 +2995,7 @@ terminatingGateways: gateways: - name: terminating-gateway +# [DEPRECATED] Use connectInject.apiGateway instead. This stanza will be removed with the release of Consul 1.17 # Configuration settings for the Consul API Gateway integration apiGateway: # When true the helm chart will install the Consul API Gateway controller diff --git a/charts/embed_chart.go b/charts/embed_chart.go index ed416f04a5..bb3898a421 100644 --- a/charts/embed_chart.go +++ b/charts/embed_chart.go @@ -15,7 +15,8 @@ import "embed" // // The embed directive does not include files with underscores unless explicitly listed, which is why _helpers.tpl is // explicitly embedded. -//go:embed consul/Chart.yaml consul/values.yaml consul/templates consul/templates/_helpers.tpl +// +//go:embed consul/Chart.yaml consul/values.yaml consul/crds consul/templates consul/templates/_helpers.tpl var ConsulHelmChart embed.FS //go:embed demo/Chart.yaml demo/values.yaml demo/templates diff --git a/cli/helm/chart.go b/cli/helm/chart.go index 6fc033e1f4..3bb9484487 100644 --- a/cli/helm/chart.go +++ b/cli/helm/chart.go @@ -18,6 +18,7 @@ const ( chartFileName = "Chart.yaml" valuesFileName = "values.yaml" templatesDirName = "templates" + crdDirName = "crds" ) // LoadChart will attempt to load a Helm chart from the embedded file system. @@ -65,7 +66,7 @@ func readChartFiles(chart embed.FS, chartDirName string) ([]*loader.BufferedFile // filepath.* functions, then Go on Windows will try to use `\` delimiters to access // the embedded filesystem, which will then fail. - // Load Chart.yaml and values.yaml first. + // Load Chart.yaml and values.yaml. for _, f := range []string{chartFileName, valuesFileName} { file, err := readFile(chart, path.Join(chartDirName, f), chartDirName) if err != nil { @@ -74,7 +75,7 @@ func readChartFiles(chart embed.FS, chartDirName string) ([]*loader.BufferedFile chartFiles = append(chartFiles, file) } - // Now load everything under templates/. + // Load everything under templates/. dirs, err := chart.ReadDir(path.Join(chartDirName, templatesDirName)) if err != nil { return nil, err @@ -93,6 +94,25 @@ func readChartFiles(chart embed.FS, chartDirName string) ([]*loader.BufferedFile chartFiles = append(chartFiles, file) } + // Load everything under crds/. + dirs, err = chart.ReadDir(path.Join(chartDirName, crdDirName)) + if err != nil { + return nil, err + } + + for _, f := range dirs { + if f.IsDir() || f.Name() == "kustomization.yaml" { + // We only need to include files in the crds directory. + continue + } + + file, err := readFile(chart, path.Join(chartDirName, crdDirName, f.Name()), chartDirName) + if err != nil { + return nil, err + } + chartFiles = append(chartFiles, file) + } + return chartFiles, nil } diff --git a/cli/helm/chart_test.go b/cli/helm/chart_test.go index f0e33e092b..7fe6cf50a2 100644 --- a/cli/helm/chart_test.go +++ b/cli/helm/chart_test.go @@ -42,6 +42,7 @@ func TestReadChartFiles(t *testing.T) { "values.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\n# This is a mock Helm values.yaml file used for testing.\nkey: value", "templates/_helpers.tpl": "helpers", "templates/foo.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nfoo: bar\n", + "crds/foo.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nfoo: bar\n", } files, err := readChartFiles(testChartFiles, directory) diff --git a/cli/helm/test_fixtures/consul/crds/foo.yaml b/cli/helm/test_fixtures/consul/crds/foo.yaml new file mode 100644 index 0000000000..b17972509f --- /dev/null +++ b/cli/helm/test_fixtures/consul/crds/foo.yaml @@ -0,0 +1,4 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +foo: bar diff --git a/control-plane/api-gateway/binding/annotations.go b/control-plane/api-gateway/binding/annotations.go new file mode 100644 index 0000000000..2cb8f41792 --- /dev/null +++ b/control-plane/api-gateway/binding/annotations.go @@ -0,0 +1,41 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "encoding/json" + + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +const ( + group = "api-gateway.consul.hashicorp.com" + annotationConfigKey = "api-gateway.consul.hashicorp.com/config" +) + +func serializeGatewayClassConfig(gw *gwv1beta1.Gateway, gwcc *v1alpha1.GatewayClassConfig) (*v1alpha1.GatewayClassConfig, bool) { + if gwcc == nil { + return nil, false + } + + if gw.Annotations == nil { + gw.Annotations = make(map[string]string) + } + + if annotatedConfig, ok := gw.Annotations[annotationConfigKey]; ok { + var config v1alpha1.GatewayClassConfig + if err := json.Unmarshal([]byte(annotatedConfig), &config.Spec); err == nil { + // if we can unmarshal the gateway, return it + return &config, false + } + } + + // otherwise if we failed to unmarshal or there was no annotation, marshal it onto + // the gateway + marshaled, _ := json.Marshal(gwcc.Spec) + gw.Annotations[annotationConfigKey] = string(marshaled) + return gwcc, true +} diff --git a/control-plane/api-gateway/binding/annotations_test.go b/control-plane/api-gateway/binding/annotations_test.go new file mode 100644 index 0000000000..1886ba80f5 --- /dev/null +++ b/control-plane/api-gateway/binding/annotations_test.go @@ -0,0 +1,206 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "encoding/json" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { + t.Parallel() + + type args struct { + gw *gwv1beta1.Gateway + gwcc *v1alpha1.GatewayClassConfig + } + tests := []struct { + name string + args args + expectedDidUpdate bool + }{ + { + name: "when gateway has not been annotated yet and annotations are nil", + args: args{ + gw: &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gw", + }, + Spec: gwv1beta1.GatewaySpec{}, + Status: gwv1beta1.GatewayStatus{}, + }, + gwcc: &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "the config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + ServiceType: pointerTo(corev1.ServiceType("serviceType")), + NodeSelector: map[string]string{ + "selector": "of node", + }, + Tolerations: []v1.Toleration{ + { + Key: "key", + Operator: "op", + Value: "120", + Effect: "to the moon", + TolerationSeconds: new(int64), + }, + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ + Service: []string{"service"}, + }, + }, + }, + }, + expectedDidUpdate: true, + }, + { + name: "when gateway has not been annotated yet but annotations are empty", + args: args{ + gw: &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gw", + Annotations: make(map[string]string), + }, + Spec: gwv1beta1.GatewaySpec{}, + Status: gwv1beta1.GatewayStatus{}, + }, + gwcc: &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "the config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + ServiceType: pointerTo(corev1.ServiceType("serviceType")), + NodeSelector: map[string]string{ + "selector": "of node", + }, + Tolerations: []v1.Toleration{ + { + Key: "key", + Operator: "op", + Value: "120", + Effect: "to the moon", + TolerationSeconds: new(int64), + }, + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ + Service: []string{"service"}, + }, + }, + }, + }, + expectedDidUpdate: true, + }, + { + name: "when gateway has been annotated", + args: args{ + gw: &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gw", + Annotations: map[string]string{ + annotationConfigKey: `{"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`, + }, + }, + Spec: gwv1beta1.GatewaySpec{}, + Status: gwv1beta1.GatewayStatus{}, + }, + gwcc: &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "the config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + ServiceType: pointerTo(corev1.ServiceType("serviceType")), + NodeSelector: map[string]string{ + "selector": "of node", + }, + Tolerations: []v1.Toleration{ + { + Key: "key", + Operator: "op", + Value: "120", + Effect: "to the moon", + TolerationSeconds: new(int64), + }, + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ + Service: []string{"service"}, + }, + }, + }, + }, + expectedDidUpdate: false, + }, + { + name: "when gateway has been annotated but the serialization was invalid", + args: args{ + gw: &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gw", + Annotations: map[string]string{ + // we remove the opening brace to make unmarshalling fail + annotationConfigKey: `"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`, + }, + }, + Spec: gwv1beta1.GatewaySpec{}, + Status: gwv1beta1.GatewayStatus{}, + }, + gwcc: &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "the config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + ServiceType: pointerTo(corev1.ServiceType("serviceType")), + NodeSelector: map[string]string{ + "selector": "of node", + }, + Tolerations: []v1.Toleration{ + { + Key: "key", + Operator: "op", + Value: "120", + Effect: "to the moon", + TolerationSeconds: new(int64), + }, + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ + Service: []string{"service"}, + }, + }, + }, + }, + expectedDidUpdate: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, actualDidUpdate := serializeGatewayClassConfig(tt.args.gw, tt.args.gwcc) + + if actualDidUpdate != tt.expectedDidUpdate { + t.Errorf("SerializeGatewayClassConfig() = %v, want %v", actualDidUpdate, tt.expectedDidUpdate) + } + + var config v1alpha1.GatewayClassConfig + err := json.Unmarshal([]byte(tt.args.gw.Annotations[annotationConfigKey]), &config.Spec) + require.NoError(t, err) + + if diff := cmp.Diff(config.Spec, tt.args.gwcc.Spec); diff != "" { + t.Errorf("Expected gwconfig spec to match serialized version (-want,+got):\n%s", diff) + } + }) + } +} diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go new file mode 100644 index 0000000000..00e928db0a --- /dev/null +++ b/control-plane/api-gateway/binding/binder.go @@ -0,0 +1,433 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +const ( + // gatewayFinalizer is the finalizer we add to any gateway object. + gatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" + + // namespaceNameLabel represents that label added automatically to namespaces in newer Kubernetes clusters. + namespaceNameLabel = "kubernetes.io/metadata.name" +) + +var ( + // constants extracted for ease of use. + kindGateway = "Gateway" + kindSecret = "Secret" + betaGroup = gwv1beta1.GroupVersion.Group + + // the list of kinds we can support by listener protocol. + supportedKindsForProtocol = map[gwv1beta1.ProtocolType][]gwv1beta1.RouteGroupKind{ + gwv1beta1.HTTPProtocolType: {{ + Group: (*gwv1beta1.Group)(&gwv1beta1.GroupVersion.Group), + Kind: "HTTPRoute", + }}, + gwv1beta1.HTTPSProtocolType: {{ + Group: (*gwv1beta1.Group)(&gwv1beta1.GroupVersion.Group), + Kind: "HTTPRoute", + }}, + gwv1beta1.TCPProtocolType: {{ + Group: (*gwv1alpha2.Group)(&gwv1alpha2.GroupVersion.Group), + Kind: "TCPRoute", + }}, + } +) + +// BinderConfig configures a binder instance with all of the information +// that it needs to know to generate a snapshot of bound state. +type BinderConfig struct { + // Translator instance initialized with proper name/namespace translation + // configuration from helm. + Translator translation.K8sToConsulTranslator + // ControllerName is the name of the controller used in determining which + // gateways we control, also leveraged for setting route statuses. + ControllerName string + + // GatewayClassConfig is the configuration corresponding to the given + // GatewayClass -- if it is nil we should treat the gateway as deleted + // since the gateway is now pointing to an invalid gateway class + GatewayClassConfig *v1alpha1.GatewayClassConfig + // GatewayClass is the GatewayClass corresponding to the Gateway we want to + // bind routes to. It is passed as a pointer because it could be nil. If no + // GatewayClass corresponds to a Gateway, we ought to clean up any sort of + // state that we may have set on the Gateway, its corresponding Routes or in + // Consul, because we should no longer be managing the Gateway (its association + // to our controller is through a parameter on the GatewayClass). + GatewayClass *gwv1beta1.GatewayClass + // Gateway is the Gateway being reconciled that we want to bind routes to. + Gateway gwv1beta1.Gateway + // HTTPRoutes is a list of HTTPRoute objects that ought to be bound to the Gateway. + HTTPRoutes []gwv1beta1.HTTPRoute + // TCPRoutes is a list of TCPRoute objects that ought to be bound to the Gateway. + TCPRoutes []gwv1alpha2.TCPRoute + // Secrets is a list of Secret objects that a Gateway references. + Secrets []corev1.Secret + // Pods are any pods that are part of the Gateway deployment + Pods []corev1.Pod + // MeshServices are all the MeshService objects that can be used for service lookup + MeshServices []v1alpha1.MeshService + // Service is the service associated with a Gateway deployment + Service *corev1.Service + + // TODO: Do we need to pass in Routes that have references to a Gateway in their statuses + // for cleanup purposes or is the below enough for record keeping? + + // ConsulGateway is the config entry we've created in Consul. + ConsulGateway *api.APIGatewayConfigEntry + // ConsulHTTPRoutes are a list of HTTPRouteConfigEntry objects that currently reference the + // Gateway we've created in Consul. + ConsulHTTPRoutes []api.HTTPRouteConfigEntry + // ConsulTCPRoutes are a list of TCPRouteConfigEntry objects that currently reference the + // Gateway we've created in Consul. + ConsulTCPRoutes []api.TCPRouteConfigEntry + // ConsulInlineCertificates is a list of certificates that have been created in Consul. + ConsulInlineCertificates []api.InlineCertificateConfigEntry + // ConnectInjectedServices is a list of all services that have been injected by our connect-injector + // and that we can, therefore reference on the mesh. + ConnectInjectedServices []api.CatalogService + // GatewayServices are the consul services for a given gateway + GatewayServices []api.CatalogService + + // Namespaces is a map of all namespaces in Kubernetes indexed by their names for looking up labels + // for AllowedRoutes matching purposes. + Namespaces map[string]corev1.Namespace + // ControlledGateways is a map of all Gateway objects that we currently should be interested in. This + // is used to determine whether we should garbage collect Certificate or Route objects when they become + // disassociated with a particular Gateway. + ControlledGateways map[types.NamespacedName]gwv1beta1.Gateway +} + +// Binder is used for generating a Snapshot of all operations that should occur both +// in Kubernetes and Consul as a result of binding routes to a Gateway. +type Binder struct { + statusSetter *setter + config BinderConfig +} + +// NewBinder creates a Binder object with the given configuration. +func NewBinder(config BinderConfig) *Binder { + return &Binder{config: config, statusSetter: newSetter(config.ControllerName)} +} + +// gatewayRef returns a Consul-based reference for the given Kubernetes gateway to +// be used for marking a deletion that is needed in Consul. +func (b *Binder) gatewayRef() api.ResourceReference { + return b.config.Translator.ReferenceForGateway(&b.config.Gateway) +} + +// isGatewayDeleted returns whether we should treat the given gateway as a deleted object. +// This is true if the gateway has a deleted timestamp, if its GatewayClass does not match +// our controller name, or if the GatewayClass it references doesn't exist. +func (b *Binder) isGatewayDeleted() bool { + gatewayClassMismatch := b.config.GatewayClass == nil || b.config.ControllerName != string(b.config.GatewayClass.Spec.ControllerName) + isGatewayDeleted := isDeleted(&b.config.Gateway) || gatewayClassMismatch || b.config.GatewayClassConfig == nil + return isGatewayDeleted +} + +// Snapshot generates a snapshot of operations that need to occur in Kubernetes and Consul +// in order for a Gateway to be reconciled. +func (b *Binder) Snapshot() Snapshot { + // at this point we assume all tcp routes and http routes + // actually reference this gateway + tracker := b.references() + meshServiceMap := meshServiceMap(b.config.MeshServices) + serviceMap := serviceMap(b.config.ConnectInjectedServices) + seenRoutes := map[api.ResourceReference]struct{}{} + snapshot := Snapshot{} + gwcc := b.config.GatewayClassConfig + + isGatewayDeleted := b.isGatewayDeleted() + if !isGatewayDeleted { + var updated bool + gwcc, updated = serializeGatewayClassConfig(&b.config.Gateway, gwcc) + + // we don't have a deletion but if we add a finalizer for the gateway, then just add it and return + // otherwise try and resolve as much as possible + if ensureFinalizer(&b.config.Gateway) || updated { + // if we've added the finalizer or serialized the class config, then update + snapshot.Kubernetes.Updates = append(snapshot.Kubernetes.Updates, &b.config.Gateway) + return snapshot + } + } + + httpRouteBinder := b.newHTTPRouteBinder(tracker, serviceMap, meshServiceMap) + tcpRouteBinder := b.newTCPRouteBinder(tracker, serviceMap, meshServiceMap) + + // used for tracking how many routes have successfully bound to which listeners + // on a gateway for reporting the number of bound routes in a gateway listener's + // status + boundCounts := make(map[gwv1beta1.SectionName]int) + + // attempt to bind all routes + + for _, r := range b.config.HTTPRoutes { + snapshot = httpRouteBinder.bind(pointerTo(r), boundCounts, seenRoutes, snapshot) + } + + for _, r := range b.config.TCPRoutes { + snapshot = tcpRouteBinder.bind(pointerTo(r), boundCounts, seenRoutes, snapshot) + } + + // now cleanup any routes that we haven't already processed + + for _, r := range b.config.ConsulHTTPRoutes { + snapshot = b.cleanHTTPRoute(pointerTo(r), seenRoutes, snapshot) + } + + for _, r := range b.config.ConsulTCPRoutes { + snapshot = b.cleanTCPRoute(pointerTo(r), seenRoutes, snapshot) + } + + // process certificates + + seenCerts := make(map[types.NamespacedName]api.ResourceReference) + for _, secret := range b.config.Secrets { + if isGatewayDeleted { + // we bypass the secret creation since we want to be able to GC if necessary + continue + } + + certificate := b.config.Translator.SecretToInlineCertificate(secret) + certificateRef := translation.EntryToReference(&certificate) + + // mark the certificate as processed + seenCerts[objectToMeta(&secret)] = certificateRef + // add the certificate to the set of upsert operations needed in Consul + snapshot.Consul.Updates = append(snapshot.Consul.Updates, &certificate) + } + + // clean up any inline certs that are now stale and can be GC'd + for _, cert := range b.config.ConsulInlineCertificates { + certRef := translation.EntryToNamespacedName(&cert) + if _, ok := seenCerts[certRef]; !ok { + // check to see if nothing is now referencing the certificate + if tracker.canGCSecret(certRef) { + ref := translation.EntryToReference(&cert) + // we can GC this now since it's not referenced by any Gateway + snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, ref) + } + } + } + + // we only want to upsert the gateway into Consul or update its status + // if the gateway hasn't been marked for deletion + if !isGatewayDeleted { + snapshot.GatewayClassConfig = gwcc + snapshot.UpsertGatewayDeployment = true + + entry := b.config.Translator.GatewayToAPIGateway(b.config.Gateway, seenCerts) + snapshot.Consul.Updates = append(snapshot.Consul.Updates, &entry) + + registrationPods := []corev1.Pod{} + // filter out any pod that is being deleted + for _, pod := range b.config.Pods { + if !isDeleted(&pod) { + registrationPods = append(registrationPods, pod) + } + } + + registrations := registrationsForPods(entry.Namespace, b.config.Gateway, registrationPods) + snapshot.Consul.Registrations = registrations + + // deregister any not explicitly registered service + for _, service := range b.config.GatewayServices { + found := false + for _, registration := range registrations { + if service.ServiceID == registration.Service.ID { + found = true + } + } + if !found { + // we didn't register the service instance, so drop it + snapshot.Consul.Deregistrations = append(snapshot.Consul.Deregistrations, api.CatalogDeregistration{ + Node: service.Node, + ServiceID: service.ServiceID, + Namespace: service.Namespace, + }) + } + } + + // calculate the status for the gateway + var status gwv1beta1.GatewayStatus + gatewayValidation := validateGateway(b.config.Gateway, registrationPods, b.config.ConsulGateway) + listenerValidation := validateListeners(b.config.Gateway.Namespace, b.config.Gateway.Spec.Listeners, b.config.Secrets) + for i, listener := range b.config.Gateway.Spec.Listeners { + status.Listeners = append(status.Listeners, gwv1beta1.ListenerStatus{ + Name: listener.Name, + SupportedKinds: supportedKindsForProtocol[listener.Protocol], + AttachedRoutes: int32(boundCounts[listener.Name]), + Conditions: listenerValidation.Conditions(b.config.Gateway.Generation, i), + }) + } + status.Conditions = gatewayValidation.Conditions(b.config.Gateway.Generation, listenerValidation.Invalid()) + status.Addresses = addressesForGateway(b.config.Service, registrationPods) + + // only mark the gateway as needing a status update if there's a diff with its old + // status, this keeps the controller from infinitely reconciling + if !cmp.Equal(status, b.config.Gateway.Status, cmp.FilterPath(func(p cmp.Path) bool { + path := p.String() + return path == "Listeners.Conditions.LastTransitionTime" || path == "Conditions.LastTransitionTime" + }, cmp.Ignore())) { + b.config.Gateway.Status = status + snapshot.Kubernetes.StatusUpdates = append(snapshot.Kubernetes.StatusUpdates, &b.config.Gateway) + } + } else { + // if the gateway has been deleted, unset whatever we've set on it + ref := b.gatewayRef() + snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, ref) + for _, service := range b.config.GatewayServices { + // deregister all gateways + snapshot.Consul.Deregistrations = append(snapshot.Consul.Deregistrations, api.CatalogDeregistration{ + Node: service.Node, + ServiceID: service.ServiceID, + Namespace: service.Namespace, + }) + } + if removeFinalizer(&b.config.Gateway) { + snapshot.Kubernetes.Updates = append(snapshot.Kubernetes.Updates, &b.config.Gateway) + } + } + + return snapshot +} + +// serviceMap constructs a map of services indexed by their Kubernetes namespace and name +// from the annotations that are set on the service. +func serviceMap(services []api.CatalogService) map[types.NamespacedName]api.CatalogService { + smap := make(map[types.NamespacedName]api.CatalogService) + for _, service := range services { + smap[serviceToNamespacedName(&service)] = service + } + return smap +} + +// meshServiceMap constructs a map of services indexed by their Kubernetes namespace and name. +func meshServiceMap(services []v1alpha1.MeshService) map[types.NamespacedName]v1alpha1.MeshService { + smap := make(map[types.NamespacedName]v1alpha1.MeshService) + for _, service := range services { + smap[client.ObjectKeyFromObject(&service)] = service + } + return smap +} + +// serviceToNamespacedName returns the Kubernetes namespace and name of a Consul catalog service +// based on the Metadata annotations written on the service. +func serviceToNamespacedName(s *api.CatalogService) types.NamespacedName { + var ( + metaKeyKubeNS = "k8s-namespace" + metaKeyKubeServiceName = "k8s-service-name" + ) + return types.NamespacedName{ + Namespace: s.ServiceMeta[metaKeyKubeNS], + Name: s.ServiceMeta[metaKeyKubeServiceName], + } +} + +func addressesForGateway(service *corev1.Service, pods []corev1.Pod) []gwv1beta1.GatewayAddress { + if service == nil { + return addressesFromPods(pods) + } + + switch service.Spec.Type { + case corev1.ServiceTypeLoadBalancer: + return addressesFromLoadBalancer(service) + case corev1.ServiceTypeClusterIP: + return addressesFromClusterIP(service) + case corev1.ServiceTypeNodePort: + /* For serviceType: NodePort, there isn't a consistent way to guarantee access to the + * service from outside the k8s cluster. For now, we're putting the IP address of the + * nodes that the gateway pods are running on. + * The practitioner will have to understand that they may need to port forward into the + * cluster (in the case of Kind) or open firewall rules (in the case of GKE) in order to + * access the gateway from outside the cluster. + */ + return addressesFromPodHosts(pods) + } + + return []gwv1beta1.GatewayAddress{} +} + +func addressesFromLoadBalancer(service *corev1.Service) []gwv1beta1.GatewayAddress { + addresses := []gwv1beta1.GatewayAddress{} + + for _, ingress := range service.Status.LoadBalancer.Ingress { + if ingress.IP != "" { + addresses = append(addresses, gwv1beta1.GatewayAddress{ + Type: pointerTo(gwv1beta1.IPAddressType), + Value: ingress.IP, + }) + } + if ingress.Hostname != "" { + addresses = append(addresses, gwv1beta1.GatewayAddress{ + Type: pointerTo(gwv1beta1.HostnameAddressType), + Value: ingress.Hostname, + }) + } + } + + return addresses +} + +func addressesFromClusterIP(service *corev1.Service) []gwv1beta1.GatewayAddress { + addresses := []gwv1beta1.GatewayAddress{} + + if service.Spec.ClusterIP != "" { + addresses = append(addresses, gwv1beta1.GatewayAddress{ + Type: pointerTo(gwv1beta1.IPAddressType), + Value: service.Spec.ClusterIP, + }) + } + + return addresses +} + +func addressesFromPods(pods []corev1.Pod) []gwv1beta1.GatewayAddress { + addresses := []gwv1beta1.GatewayAddress{} + seenIPs := make(map[string]struct{}) + + for _, pod := range pods { + if pod.Status.PodIP != "" { + if _, found := seenIPs[pod.Status.PodIP]; !found { + addresses = append(addresses, gwv1beta1.GatewayAddress{ + Type: pointerTo(gwv1beta1.IPAddressType), + Value: pod.Status.PodIP, + }) + seenIPs[pod.Status.PodIP] = struct{}{} + } + } + } + + return addresses +} + +func addressesFromPodHosts(pods []corev1.Pod) []gwv1beta1.GatewayAddress { + addresses := []gwv1beta1.GatewayAddress{} + seenIPs := make(map[string]struct{}) + + for _, pod := range pods { + if pod.Status.HostIP != "" { + if _, found := seenIPs[pod.Status.HostIP]; !found { + addresses = append(addresses, gwv1beta1.GatewayAddress{ + Type: pointerTo(gwv1beta1.IPAddressType), + Value: pod.Status.HostIP, + }) + seenIPs[pod.Status.HostIP] = struct{}{} + } + } + } + + return addresses +} diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go new file mode 100644 index 0000000000..6078944004 --- /dev/null +++ b/control-plane/api-gateway/binding/binder_test.go @@ -0,0 +1,2683 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestBinder_Lifecycle(t *testing.T) { + t.Parallel() + + className := "gateway-class" + gatewayClassName := gwv1beta1.ObjectName(className) + controllerName := "test-controller" + deletionTimestamp := pointerTo(metav1.Now()) + gatewayClass := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: className, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: gwv1beta1.GatewayController(controllerName), + }, + } + + for name, tt := range map[string]struct { + config BinderConfig + expected Snapshot + }{ + "no gateway class and empty routes": { + config: BinderConfig{ + Gateway: gwv1beta1.Gateway{}, + }, + expected: Snapshot{ + Consul: ConsulSnapshot{ + Deletions: []api.ResourceReference{{ + Kind: api.APIGateway, + }}, + }, + }, + }, + "no gateway class and empty routes remove finalizer": { + config: BinderConfig{ + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{gatewayFinalizer}, + }, + }, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + Updates: []client.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{}, + }, + }, + }, + }, + Consul: ConsulSnapshot{ + Deletions: []api.ResourceReference{{ + Kind: api.APIGateway, + }}, + }, + }, + }, + "deleting gateway empty routes": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + Updates: []client.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + }, + }, + Consul: ConsulSnapshot{ + Deletions: []api.ResourceReference{{ + Kind: api.APIGateway, + }}, + }, + }, + }, + "basic gateway no finalizer": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + Updates: []client.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + }, + }, + Consul: ConsulSnapshot{}, + }, + }, + "basic gateway": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + Listeners: []gwv1beta1.Listener{{ + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }}, + }, + }}, + }, + }, + Secrets: []corev1.Secret{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-one", + }, + }}, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + StatusUpdates: []client.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + Listeners: []gwv1beta1.Listener{{ + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }}, + }, + }}, + }, + Status: gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "ListenersNotValid", + Message: "one or more listeners are invalid", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + Listeners: []gwv1beta1.ListenerStatus{{ + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "UnsupportedProtocol", + Message: "listener protocol is unsupported", + }, { + Type: "Conflicted", + Status: metav1.ConditionFalse, + Reason: "NoConflicts", + Message: "listener has no conflicts", + }, { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved certificate references", + }}, + }}, + }, + }, + }, + }, + Consul: ConsulSnapshot{ + Updates: []api.ConfigEntry{ + &api.InlineCertificateConfigEntry{ + Kind: api.InlineCertificate, + Name: "secret-one", + Meta: map[string]string{ + "k8s-name": "secret-one", + "k8s-namespace": "", + "k8s-service-name": "secret-one", + "managed-by": "consul-k8s-gateway-controller", + }, + }, + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Meta: map[string]string{ + "k8s-name": "", + "k8s-namespace": "", + "k8s-service-name": "", + "managed-by": "consul-k8s-gateway-controller", + }, + Listeners: []api.APIGatewayListener{{ + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{{ + Kind: api.InlineCertificate, + Name: "secret-one", + }}, + }, + }}, + }, + }, + Registrations: []api.CatalogRegistration{}, + }, + }, + }, + "gateway http route no finalizer": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + HTTPRoutes: []gwv1beta1.HTTPRoute{{ + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }}, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + Updates: []client.Object{ + &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, + }, + StatusUpdates: []client.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + Status: gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + }, + }, + }, + }, + Consul: ConsulSnapshot{ + Updates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "", + "k8s-service-name": "gateway", + "managed-by": "consul-k8s-gateway-controller", + }, + Listeners: []api.APIGatewayListener{}, + }, + }, + Registrations: []api.CatalogRegistration{}, + }, + }, + }, + "gateway http route deleting": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + HTTPRoutes: []gwv1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }}, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + Updates: []client.Object{ + &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, + }, + StatusUpdates: []client.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + Status: gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + }, + }, + }, + }, + Consul: ConsulSnapshot{ + Updates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "", + "k8s-service-name": "gateway", + "managed-by": "consul-k8s-gateway-controller", + }, + Listeners: []api.APIGatewayListener{}, + }, + }, + Deletions: []api.ResourceReference{{ + Kind: api.HTTPRoute, + }}, + Registrations: []api.CatalogRegistration{}, + }, + }, + }, + "gateway tcp route no finalizer": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + TCPRoutes: []gwv1alpha2.TCPRoute{{ + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }}, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + Updates: []client.Object{ + &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, + }, + StatusUpdates: []client.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + Status: gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + }, + }, + }, + }, + Consul: ConsulSnapshot{ + Updates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "", + "k8s-service-name": "gateway", + "managed-by": "consul-k8s-gateway-controller", + }, + Listeners: []api.APIGatewayListener{}, + }, + }, + Registrations: []api.CatalogRegistration{}, + }, + }, + }, + "gateway tcp route deleting": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + TCPRoutes: []gwv1alpha2.TCPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }}, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + Updates: []client.Object{ + &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, + }, + StatusUpdates: []client.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + Status: gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + }, + }, + }, + }, + Consul: ConsulSnapshot{ + Updates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "", + "k8s-service-name": "gateway", + "managed-by": "consul-k8s-gateway-controller", + }, + Listeners: []api.APIGatewayListener{}, + }, + }, + Deletions: []api.ResourceReference{{ + Kind: api.TCPRoute, + }}, + Registrations: []api.CatalogRegistration{}, + }, + }, + }, + "gateway deletion routes and secrets": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + Listeners: []gwv1beta1.Listener{{ + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }, { + Name: "secret-two", + }}, + }, + }}, + }, + }, + ControlledGateways: map[types.NamespacedName]gwv1beta1.Gateway{ + {Name: "gateway"}: { + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + Listeners: []gwv1beta1.Listener{{ + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }, { + Name: "secret-two", + }}, + }, + }}, + }, + }, + {Name: "gateway-two"}: { + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-two", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + Listeners: []gwv1beta1.Listener{{ + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }, { + Name: "secret-three", + }}, + }, + }}, + }, + }, + }, + Secrets: []corev1.Secret{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-one", + }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-two", + }, + }}, + HTTPRoutes: []gwv1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route-one", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route-two", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }, { + Name: "gateway-two", + }}, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gwv1beta1.GatewayController(controllerName), + ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + }}, + }, { + ControllerName: gwv1beta1.GatewayController(controllerName), + ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + }}, + }}, + }, + }, + }}, + TCPRoutes: []gwv1alpha2.TCPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route-one", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route-two", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }, { + Name: "gateway-two", + }}, + }, + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gwv1beta1.GatewayController(controllerName), + ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + }}, + }, { + ControllerName: gwv1beta1.GatewayController(controllerName), + ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + }}, + }}, + }, + }, + }}, + ConsulHTTPRoutes: []api.HTTPRouteConfigEntry{{ + Kind: api.HTTPRoute, + Name: "http-route-two", + Meta: map[string]string{ + "k8s-name": "http-route-two", + "k8s-namespace": "", + "k8s-service-name": "http-route-two", + "managed-by": "consul-k8s-gateway-controller", + }, + Parents: []api.ResourceReference{{ + Kind: api.APIGateway, + Name: "gateway", + }, { + Kind: api.APIGateway, + Name: "gateway-two", + }}, + }}, + ConsulTCPRoutes: []api.TCPRouteConfigEntry{{ + Kind: api.TCPRoute, + Name: "tcp-route-two", + Meta: map[string]string{ + "k8s-name": "tcp-route-two", + "k8s-namespace": "", + "k8s-service-name": "tcp-route-two", + "managed-by": "consul-k8s-gateway-controller", + }, + Parents: []api.ResourceReference{{ + Kind: api.APIGateway, + Name: "gateway", + }, { + Kind: api.APIGateway, + Name: "gateway-two", + }}, + }}, + ConsulInlineCertificates: []api.InlineCertificateConfigEntry{{ + Kind: api.InlineCertificate, + Name: "secret-one", + Meta: map[string]string{ + "k8s-name": "secret-one", + "k8s-namespace": "", + "k8s-service-name": "secret-one", + "managed-by": "consul-k8s-gateway-controller", + }, + }, { + Kind: api.InlineCertificate, + Name: "secret-two", + Meta: map[string]string{ + "k8s-name": "secret-two", + "k8s-namespace": "", + "k8s-service-name": "secret-two", + "managed-by": "consul-k8s-gateway-controller", + }, + }}, + }, + expected: Snapshot{ + Kubernetes: KubernetesSnapshot{ + StatusUpdates: []client.Object{ + &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route-two", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }, { + Name: "gateway-two", + }}, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + // removed gateway status + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gwv1beta1.GatewayController(controllerName), + ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + }}, + }}, + }, + }, + }, + &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route-two", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }, { + Name: "gateway-two", + }}, + }, + }, + // removed gateway status + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gwv1beta1.GatewayController(controllerName), + ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + }}, + }}, + }, + }, + }, + }, + Updates: []client.Object{ + &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route-one", + Finalizers: []string{}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, + &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route-one", + Finalizers: []string{}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + Listeners: []gwv1beta1.Listener{{ + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }, { + Name: "secret-two", + }}, + }, + }}, + }, + }, + }, + }, + Consul: ConsulSnapshot{ + Updates: []api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "http-route-two", + Meta: map[string]string{ + "k8s-name": "http-route-two", + "k8s-namespace": "", + "k8s-service-name": "http-route-two", + "managed-by": "consul-k8s-gateway-controller", + }, + // dropped ref to gateway + Parents: []api.ResourceReference{{ + Kind: api.APIGateway, + Name: "gateway-two", + }}, + }, + &api.TCPRouteConfigEntry{ + Kind: api.TCPRoute, + Name: "tcp-route-two", + Meta: map[string]string{ + "k8s-name": "tcp-route-two", + "k8s-namespace": "", + "k8s-service-name": "tcp-route-two", + "managed-by": "consul-k8s-gateway-controller", + }, + // dropped ref to gateway + Parents: []api.ResourceReference{{ + Kind: api.APIGateway, + Name: "gateway-two", + }}, + }, + }, + Deletions: []api.ResourceReference{{ + Kind: api.HTTPRoute, + Name: "http-route-one", + }, { + Kind: api.TCPRoute, + Name: "tcp-route-one", + }, { + Kind: api.InlineCertificate, + Name: "secret-two", + }, { + Kind: api.APIGateway, + Name: "gateway", + }}, + }, + }, + }, + } { + t.Run(name, func(t *testing.T) { + tt.config.ControllerName = controllerName + tt.config.GatewayClassConfig = &v1alpha1.GatewayClassConfig{} + serializeGatewayClassConfig(&tt.config.Gateway, tt.config.GatewayClassConfig) + + binder := NewBinder(tt.config) + actual := binder.Snapshot() + + diff := cmp.Diff(tt.expected, actual, cmp.FilterPath(func(p cmp.Path) bool { + return p.String() == "GatewayClassConfig" || strings.HasSuffix(p.String(), "LastTransitionTime") || strings.HasSuffix(p.String(), "Annotations") || strings.HasSuffix(p.String(), "UpsertGatewayDeployment") + }, cmp.Ignore())) + if diff != "" { + t.Error("undexpected diff", diff) + } + }) + } +} + +func TestBinder_Registrations(t *testing.T) { + t.Parallel() + + className := "gateway-class" + gatewayClassName := gwv1beta1.ObjectName(className) + controllerName := "test-controller" + deletionTimestamp := pointerTo(metav1.Now()) + gatewayClass := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: className, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: gwv1beta1.GatewayController(controllerName), + }, + } + gatewayName := "gateway" + + for name, tt := range map[string]struct { + config BinderConfig + expectedRegistrations []string + expectedDeregistrations []api.CatalogDeregistration + }{ + "deleting gateway with consul services": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gatewayName, + Finalizers: []string{gatewayFinalizer}, + DeletionTimestamp: deletionTimestamp, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + GatewayServices: []api.CatalogService{ + {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, + {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, + {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, + }, + }, + expectedDeregistrations: []api.CatalogDeregistration{ + {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, + {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, + {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, + }, + }, + "gateway with consul services and mixed pods": { + config: BinderConfig{ + ControllerName: controllerName, + GatewayClass: gatewayClass, + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gatewayName, + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + }, + }, + Pods: []corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "namespace1"}, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pod3", Namespace: "namespace1"}, + Status: corev1.PodStatus{ + Phase: corev1.PodFailed, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pod4", Namespace: "namespace1"}, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, + }, + }, + }, + GatewayServices: []api.CatalogService{ + {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, + {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, + {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, + }, + }, + expectedRegistrations: []string{"pod1", "pod3", "pod4"}, + expectedDeregistrations: []api.CatalogDeregistration{ + {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, + }, + }, + } { + t.Run(name, func(t *testing.T) { + tt.config.ControllerName = controllerName + tt.config.GatewayClassConfig = &v1alpha1.GatewayClassConfig{} + serializeGatewayClassConfig(&tt.config.Gateway, tt.config.GatewayClassConfig) + + binder := NewBinder(tt.config) + actual := binder.Snapshot() + + require.Len(t, actual.Consul.Registrations, len(tt.expectedRegistrations)) + for i := range actual.Consul.Registrations { + registration := actual.Consul.Registrations[i] + expected := tt.expectedRegistrations[i] + + require.EqualValues(t, expected, registration.Service.ID) + require.EqualValues(t, gatewayName, registration.Service.Service) + } + + require.EqualValues(t, tt.expectedDeregistrations, actual.Consul.Deregistrations) + }) + } +} + +func TestBinder_BindingRulesKitchenSink(t *testing.T) { + t.Parallel() + + className := "gateway-class" + gatewayClassName := gwv1beta1.ObjectName(className) + controllerName := "test-controller" + gatewayClass := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: className, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: gwv1beta1.GatewayController(controllerName), + }, + } + + gateway := gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gatewayClassName, + Listeners: []gwv1beta1.Listener{{ + Name: "http-listener-default-same", + Protocol: gwv1beta1.HTTPProtocolType, + }, { + Name: "http-listener-hostname", + Protocol: gwv1beta1.HTTPProtocolType, + Hostname: pointerTo[gwv1beta1.Hostname]("host.name"), + }, { + Name: "http-listener-mismatched-kind-allowed", + Protocol: gwv1beta1.HTTPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Kinds: []gwv1beta1.RouteGroupKind{{ + Kind: "Foo", + }}, + }, + }, { + Name: "http-listener-explicit-all-allowed", + Protocol: gwv1beta1.HTTPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromAll), + }, + }, + }, { + Name: "http-listener-explicit-allowed-same", + Protocol: gwv1beta1.HTTPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromSame), + }, + }, + }, { + Name: "http-listener-allowed-selector", + Protocol: gwv1beta1.HTTPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "foo", + }, + }, + }, + }, + }, { + Name: "http-listener-tls", + Protocol: gwv1beta1.HTTPSProtocolType, + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }}, + }, + }, { + Name: "tcp-listener-default-same", + Protocol: gwv1beta1.TCPProtocolType, + }, { + Name: "tcp-listener-mismatched-kind-allowed", + Protocol: gwv1beta1.TCPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Kinds: []gwv1beta1.RouteGroupKind{{ + Kind: "Foo", + }}, + }, + }, { + Name: "tcp-listener-explicit-all-allowed", + Protocol: gwv1beta1.TCPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromAll), + }, + }, + }, { + Name: "tcp-listener-explicit-allowed-same", + Protocol: gwv1beta1.TCPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromSame), + }, + }, + }, { + Name: "tcp-listener-allowed-selector", + Protocol: gwv1beta1.TCPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "foo", + }, + }, + }, + }, + }, { + Name: "tcp-listener-tls", + Protocol: gwv1beta1.TCPProtocolType, + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }}, + }, + }}, + }, + } + + namespaces := map[string]corev1.Namespace{ + "": { + ObjectMeta: metav1.ObjectMeta{ + Name: "", + }, + }, + "test": { + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Labels: map[string]string{ + "test": "foo", + }, + }, + }, + } + + defaultNamespacePointer := pointerTo[gwv1beta1.Namespace]("") + + httpTypeMeta := metav1.TypeMeta{} + httpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("HTTPRoute")) + tcpTypeMeta := metav1.TypeMeta{} + tcpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("TCPRoute")) + + for name, tt := range map[string]struct { + httpRoute *gwv1beta1.HTTPRoute + expectedHTTPRouteUpdate *gwv1beta1.HTTPRoute + expectedHTTPRouteUpdateStatus *gwv1beta1.HTTPRoute + expectedHTTPConsulRouteUpdate *api.HTTPRouteConfigEntry + expectedHTTPConsulRouteDelete *api.ResourceReference + + tcpRoute *gwv1alpha2.TCPRoute + expectedTCPRouteUpdate *gwv1alpha2.TCPRoute + expectedTCPRouteUpdateStatus *gwv1alpha2.TCPRoute + expectedTCPConsulRouteUpdate *api.TCPRouteConfigEntry + expectedTCPConsulRouteDelete *api.ResourceReference + }{ + "untargeted http route same namespace": { + httpRoute: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, + expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }}, + }, + }, + }, + }, + "untargeted http route same namespace missing backend": { + httpRoute: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + Rules: []gwv1beta1.HTTPRouteRule{{ + BackendRefs: []gwv1beta1.HTTPBackendRef{{ + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName("backend"), + }, + }, + }}, + }}, + }, + }, + expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + Rules: []gwv1beta1.HTTPRouteRule{{ + BackendRefs: []gwv1beta1.HTTPBackendRef{{ + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName("backend"), + }, + }, + }}, + }}, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "BackendNotFound", + Message: "/backend: backend not found", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }}, + }, + }, + }, + }, + "untargeted http route same namespace invalid backend type": { + httpRoute: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + Rules: []gwv1beta1.HTTPRouteRule{{ + BackendRefs: []gwv1beta1.HTTPBackendRef{{ + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName("backend"), + Group: pointerTo[gwv1beta1.Group]("invalid.foo.com"), + }, + }, + }}, + }}, + }, + }, + expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + Rules: []gwv1beta1.HTTPRouteRule{{ + BackendRefs: []gwv1beta1.HTTPBackendRef{{ + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName("backend"), + Group: pointerTo[gwv1beta1.Group]("invalid.foo.com"), + }, + }, + }}, + }}, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidKind", + Message: "/backend [Service.invalid.foo.com]: invalid backend kind", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }}, + }, + }, + }, + }, + "untargeted http route different namespace": { + httpRoute: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Namespace: "test", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }}, + }, + }, + }, + expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Namespace: "test", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }}, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }}, + }, + }, + }, + }, + "targeted http route same namespace": { + httpRoute: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }}, + }, + }, + }, + expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }}, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-allowed-selector: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-explicit-all-allowed: listener does not support route protocol", + }}, + }}, + }, + }, + }, + }, + "targeted http route different namespace": { + httpRoute: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Namespace: "test", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }}, + }, + }, + }, + expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Namespace: "test", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }}, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-default-same: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-hostname: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-explicit-allowed-same: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-tls: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-explicit-all-allowed: listener does not support route protocol", + }}, + }}, + }, + }, + }, + }, + "untargeted tcp route same namespace": { + tcpRoute: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + }, + expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }}, + }, + }, + }, + }, + "untargeted tcp route same namespace missing backend": { + tcpRoute: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + Rules: []gwv1alpha2.TCPRouteRule{{ + BackendRefs: []gwv1beta1.BackendRef{{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName("backend"), + }, + }}, + }}, + }, + }, + expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + Rules: []gwv1alpha2.TCPRouteRule{{ + BackendRefs: []gwv1beta1.BackendRef{{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName("backend"), + }, + }}, + }}, + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "BackendNotFound", + Message: "/backend: backend not found", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }}, + }, + }, + }, + }, + "untargeted tcp route same namespace invalid backend type": { + tcpRoute: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + Rules: []gwv1alpha2.TCPRouteRule{{ + BackendRefs: []gwv1beta1.BackendRef{{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName("backend"), + Group: pointerTo[gwv1beta1.Group]("invalid.foo.com"), + }, + }}, + }}, + }, + }, + expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, + }, + Rules: []gwv1alpha2.TCPRouteRule{{ + BackendRefs: []gwv1beta1.BackendRef{{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName("backend"), + Group: pointerTo[gwv1beta1.Group]("invalid.foo.com"), + }, + }}, + }}, + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidKind", + Message: "/backend [Service.invalid.foo.com]: invalid backend kind", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }}, + }, + }, + }, + }, + "untargeted tcp route different namespace": { + tcpRoute: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Namespace: "test", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }}, + }, + }, + }, + expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Namespace: "test", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }}, + }, + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }}, + }, + }, + }, + }, + "targeted tcp route same namespace": { + tcpRoute: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }}, + }, + }, + }, + expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), + }, { + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }}, + }, + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-mismatched-kind-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-allowed-selector: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-explicit-all-allowed: listener does not support route protocol", + }}, + }}, + }, + }, + }, + }, + "targeted tcp route different namespace": { + tcpRoute: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Namespace: "test", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }}, + }, + }, + }, + expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + Namespace: "test", + Finalizers: []string{gatewayFinalizer}, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }}, + }, + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{{ + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-default-same: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-mismatched-kind-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-explicit-allowed-same: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-tls: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: gatewayClass.Spec.ControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-explicit-all-allowed: listener does not support route protocol", + }}, + }}, + }, + }, + }, + }, + } { + t.Run(name, func(t *testing.T) { + config := BinderConfig{ + ControllerName: controllerName, + GatewayClassConfig: &v1alpha1.GatewayClassConfig{}, + GatewayClass: gatewayClass, + Gateway: gateway, + Namespaces: namespaces, + ControlledGateways: map[types.NamespacedName]gwv1beta1.Gateway{ + {Name: "gateway"}: gateway, + }, + Secrets: []corev1.Secret{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-one", + }, + }}, + } + serializeGatewayClassConfig(&config.Gateway, config.GatewayClassConfig) + + if tt.httpRoute != nil { + config.HTTPRoutes = append(config.HTTPRoutes, *tt.httpRoute) + } + if tt.tcpRoute != nil { + config.TCPRoutes = append(config.TCPRoutes, *tt.tcpRoute) + } + + binder := NewBinder(config) + actual := binder.Snapshot() + + compareUpdates(t, tt.expectedHTTPRouteUpdate, actual.Kubernetes.Updates) + compareUpdates(t, tt.expectedTCPRouteUpdate, actual.Kubernetes.Updates) + compareUpdates(t, tt.expectedHTTPRouteUpdateStatus, actual.Kubernetes.StatusUpdates) + compareUpdates(t, tt.expectedTCPRouteUpdateStatus, actual.Kubernetes.StatusUpdates) + }) + } +} + +func compareUpdates[T client.Object](t *testing.T, expected T, updates []client.Object) { + t.Helper() + + if isNil(expected) { + for _, update := range updates { + if u, ok := update.(T); ok { + t.Error("found unexpected update", u) + } + } + } else { + found := false + for _, update := range updates { + if u, ok := update.(T); ok { + diff := cmp.Diff(expected, u, cmp.FilterPath(func(p cmp.Path) bool { + return p.String() == "Status.RouteStatus.Parents.Conditions.LastTransitionTime" + }, cmp.Ignore())) + if diff != "" { + t.Error("diff between actual and expected", diff) + } + found = true + } + } + if !found { + t.Error("expected route update not found in", updates) + } + } +} diff --git a/control-plane/api-gateway/binding/references.go b/control-plane/api-gateway/binding/references.go new file mode 100644 index 0000000000..ec598c6364 --- /dev/null +++ b/control-plane/api-gateway/binding/references.go @@ -0,0 +1,135 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// referenceTracker acts as a reference counting object for: +// 1. the number of controlled gateways that are referenced by an HTTPRoute +// 2. the number of controlled gateways that are referenced by a TCPRoute +// 3. the number of gateways that reference a certificate Secret +// +// These are used for determining when dissasociating from a gateway +// should cause us to cleanup a route or certificate both in Consul and +// whatever state we have set on the object in Kubernetes. +type referenceTracker struct { + httpRouteReferencesGateways map[types.NamespacedName]int + tcpRouteReferencesGateways map[types.NamespacedName]int + certificatesReferencedByGateways map[types.NamespacedName]int +} + +// isLastReference checks if the given gateway is the last controlled gateway +// that a route references. If it is and the gateway has been deleted, we +// should clean up all state created for the route. +func (r referenceTracker) isLastReference(object client.Object) bool { + key := types.NamespacedName{ + Namespace: object.GetNamespace(), + Name: object.GetName(), + } + + switch object.(type) { + case *gwv1alpha2.TCPRoute: + return r.tcpRouteReferencesGateways[key] == 1 + case *gwv1beta1.HTTPRoute: + return r.httpRouteReferencesGateways[key] == 1 + default: + return false + } +} + +// canGCSecret checks if we can garbage collect a secret that has +// not been upserted. +func (r referenceTracker) canGCSecret(key types.NamespacedName) bool { + // should this be 1 or 0? + return r.certificatesReferencedByGateways[key] == 1 +} + +// references initializes a referenceTracker based on the HTTPRoutes, TCPRoutes, +// and ControlledGateways associated with this Binder. +func (b *Binder) references() referenceTracker { + tracker := referenceTracker{ + httpRouteReferencesGateways: make(map[types.NamespacedName]int), + tcpRouteReferencesGateways: make(map[types.NamespacedName]int), + certificatesReferencedByGateways: make(map[types.NamespacedName]int), + } + + for _, route := range b.config.HTTPRoutes { + references := map[types.NamespacedName]struct{}{} + for _, ref := range route.Spec.ParentRefs { + for _, gateway := range b.config.ControlledGateways { + parentName := string(ref.Name) + parentNamespace := valueOr(ref.Namespace, route.Namespace) + if nilOrEqual(ref.Group, betaGroup) && + nilOrEqual(ref.Kind, kindGateway) && + gateway.Namespace == parentNamespace && + gateway.Name == parentName { + // the route references a gateway we control, store the ref to this gateway + references[types.NamespacedName{ + Namespace: parentNamespace, + Name: parentName, + }] = struct{}{} + } + } + } + tracker.httpRouteReferencesGateways[types.NamespacedName{ + Namespace: route.Namespace, + Name: route.Name, + }] = len(references) + } + + for _, route := range b.config.TCPRoutes { + references := map[types.NamespacedName]struct{}{} + for _, ref := range route.Spec.ParentRefs { + for _, gateway := range b.config.ControlledGateways { + parentName := string(ref.Name) + parentNamespace := valueOr(ref.Namespace, route.Namespace) + if nilOrEqual(ref.Group, betaGroup) && + nilOrEqual(ref.Kind, kindGateway) && + gateway.Namespace == parentNamespace && + gateway.Name == parentName { + // the route references a gateway we control, store the ref to this gateway + references[types.NamespacedName{ + Namespace: parentNamespace, + Name: parentName, + }] = struct{}{} + } + } + } + tracker.tcpRouteReferencesGateways[types.NamespacedName{ + Namespace: route.Namespace, + Name: route.Name, + }] = len(references) + } + + for _, gateway := range b.config.ControlledGateways { + references := map[types.NamespacedName]struct{}{} + for _, listener := range gateway.Spec.Listeners { + if listener.TLS == nil { + continue + } + for _, ref := range listener.TLS.CertificateRefs { + if nilOrEqual(ref.Group, "") && + nilOrEqual(ref.Kind, kindSecret) { + // the gateway references a secret, store it + references[types.NamespacedName{ + Namespace: valueOr(ref.Namespace, gateway.Namespace), + Name: string(ref.Name), + }] = struct{}{} + } + } + } + + for ref := range references { + count := tracker.certificatesReferencedByGateways[ref] + tracker.certificatesReferencedByGateways[ref] = count + 1 + } + } + + return tracker +} diff --git a/control-plane/api-gateway/binding/registration.go b/control-plane/api-gateway/binding/registration.go new file mode 100644 index 0000000000..ae26ab51f6 --- /dev/null +++ b/control-plane/api-gateway/binding/registration.go @@ -0,0 +1,92 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "fmt" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +const ( + metaKeySyntheticNode = "synthetic-node" + kubernetesSuccessReasonMsg = "Kubernetes health checks passing" + + // consulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. + consulKubernetesCheckType = "kubernetes-readiness" + + // consulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. + consulKubernetesCheckName = "Kubernetes Readiness Check" +) + +func registrationsForPods(namespace string, gateway gwv1beta1.Gateway, pods []corev1.Pod) []api.CatalogRegistration { + registrations := []api.CatalogRegistration{} + for _, pod := range pods { + registrations = append(registrations, registrationForPod(namespace, gateway, pod)) + } + return registrations +} + +func registrationForPod(namespace string, gateway gwv1beta1.Gateway, pod corev1.Pod) api.CatalogRegistration { + healthStatus := api.HealthCritical + if isPodReady(pod) { + healthStatus = api.HealthPassing + } + + return api.CatalogRegistration{ + Node: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), + Address: pod.Status.HostIP, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, + Service: &api.AgentService{ + Kind: api.ServiceKindAPIGateway, + ID: pod.Name, + Service: gateway.Name, + Address: pod.Status.PodIP, + Namespace: namespace, + Meta: map[string]string{ + constants.MetaKeyPodName: pod.Name, + constants.MetaKeyKubeNS: pod.Namespace, + constants.MetaKeyKubeServiceName: gateway.Name, + "external-source": "consul-api-gateway", + }, + }, + Check: &api.AgentCheck{ + CheckID: fmt.Sprintf("%s/%s", pod.Namespace, pod.Name), + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, + Status: healthStatus, + ServiceID: pod.Name, + Output: getHealthCheckStatusReason(healthStatus, pod.Name, pod.Namespace), + Namespace: namespace, + }, + SkipNodeUpdate: true, + } +} + +func getHealthCheckStatusReason(healthCheckStatus, podName, podNamespace string) string { + if healthCheckStatus == api.HealthPassing { + return kubernetesSuccessReasonMsg + } + + return fmt.Sprintf("Pod \"%s/%s\" is not ready", podNamespace, podName) +} + +func isPodReady(pod corev1.Pod) bool { + if corev1.PodRunning != pod.Status.Phase { + return false + } + + for _, condition := range pod.Status.Conditions { + if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue { + return true + } + } + return false +} diff --git a/control-plane/api-gateway/binding/registration_test.go b/control-plane/api-gateway/binding/registration_test.go new file mode 100644 index 0000000000..356915f9f7 --- /dev/null +++ b/control-plane/api-gateway/binding/registration_test.go @@ -0,0 +1,83 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "testing" + + "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestRegistrationsForPods_Health(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + consulNamespace string + gateway gwv1beta1.Gateway + pods []corev1.Pod + expected []string + }{ + "empty": { + consulNamespace: "", + gateway: gwv1beta1.Gateway{}, + pods: []corev1.Pod{}, + expected: []string{}, + }, + "mix": { + consulNamespace: "", + gateway: gwv1beta1.Gateway{}, + pods: []corev1.Pod{ + // Pods without a running status + {Status: corev1.PodStatus{Phase: corev1.PodFailed}}, + {Status: corev1.PodStatus{Phase: corev1.PodPending}}, + {Status: corev1.PodStatus{Phase: corev1.PodSucceeded}}, + {Status: corev1.PodStatus{Phase: corev1.PodUnknown}}, + // Running statuses that don't show readiness + {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ + {Type: corev1.PodScheduled, Status: corev1.ConditionTrue}, + }}}, + {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ + {Type: corev1.PodInitialized, Status: corev1.ConditionTrue}, + }}}, + {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ + {Type: corev1.DisruptionTarget, Status: corev1.ConditionTrue}, + }}}, + {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ + {Type: corev1.ContainersReady, Status: corev1.ConditionTrue}, + }}}, + // And finally, the successful check + {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ + {Type: corev1.PodReady, Status: corev1.ConditionTrue}, + }}}, + }, + expected: []string{ + api.HealthCritical, + api.HealthCritical, + api.HealthCritical, + api.HealthCritical, + api.HealthCritical, + api.HealthCritical, + api.HealthCritical, + api.HealthCritical, + api.HealthPassing, + }, + }, + } { + t.Run(name, func(t *testing.T) { + registrations := registrationsForPods(tt.consulNamespace, tt.gateway, tt.pods) + require.Len(t, registrations, len(tt.expected)) + + for i := range registrations { + registration := registrations[i] + expected := tt.expected[i] + + require.EqualValues(t, "Kubernetes Readiness Check", registration.Check.Name) + require.EqualValues(t, expected, registration.Check.Status) + } + }) + } +} diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go new file mode 100644 index 0000000000..0fd291b0ef --- /dev/null +++ b/control-plane/api-gateway/binding/result.go @@ -0,0 +1,471 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "errors" + "fmt" + "sort" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +var ( + // Each of the below are specified in the Gateway spec under RouteConditionReason + // the general usage is that each error is specified as errRoute* where * corresponds + // to the RouteConditionReason given in the spec. If a reason is overloaded and can + // be used with two different types of things (i.e. something is not found or it's not supported) + // then we distinguish those two usages with errRoute*_Usage. + errRouteNotAllowedByListeners_Namespace = errors.New("listener does not allow binding routes from the given namespace") + errRouteNotAllowedByListeners_Protocol = errors.New("listener does not support route protocol") + errRouteNoMatchingListenerHostname = errors.New("listener cannot bind route with a non-aligned hostname") + errRouteInvalidKind = errors.New("invalid backend kind") + errRouteBackendNotFound = errors.New("backend not found") + errRouteRefNotPermitted = errors.New("reference not permitted due to lack of ReferenceGrant") +) + +// routeValidationResult holds the result of validating a route globally, in other +// words, for a particular backend reference without consideration to its particular +// gateway. Unfortunately, due to the fact that the spec requires a route status be +// associated with a parent reference, what it means is that anything that is global +// in nature, like this status will need to be duplicated for every parent reference +// on a given route status. +type routeValidationResult struct { + namespace string + backend gwv1beta1.BackendRef + err error +} + +// Type is used for error printing a backend reference type that we don't support on +// a validation error. +func (v routeValidationResult) Type() string { + return (&metav1.GroupKind{ + Group: valueOr(v.backend.Group, ""), + Kind: valueOr(v.backend.Kind, "Service"), + }).String() +} + +// String is the namespace/name of the reference that has an error. +func (v routeValidationResult) String() string { + return (types.NamespacedName{Namespace: v.namespace, Name: string(v.backend.Name)}).String() +} + +// routeValidationResults contains a list of validation results for the backend references +// on a route. +type routeValidationResults []routeValidationResult + +// Condition returns the ResolvedRefs condition that gets duplicated across every relevant +// parent on a route's status. +func (e routeValidationResults) Condition() metav1.Condition { + // we only use the first error due to the way the spec is structured + // where you can only have a single condition + for _, v := range e { + err := v.err + if err != nil { + switch err { + case errRouteInvalidKind: + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidKind", + Message: fmt.Sprintf("%s [%s]: %s", v.String(), v.Type(), err.Error()), + } + case errRouteBackendNotFound: + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "BackendNotFound", + Message: fmt.Sprintf("%s: %s", v.String(), err.Error()), + } + case errRouteRefNotPermitted: + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "RefNotPermitted", + Message: fmt.Sprintf("%s: %s", v.String(), err.Error()), + } + default: + // this should never happen + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "UnhandledValidationError", + Message: err.Error(), + } + } + } + } + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + } +} + +// bindResult holds the result of attempting to bind a route to a particular gateway listener +// an error value here means that the route did not bind successfully, no error means that +// the route should be considered bound. +type bindResult struct { + section gwv1beta1.SectionName + err error +} + +// bindResults holds the results of attempting to bind a route to a gateway, having a separate +// bindResult for each listener on the gateway. +type bindResults []bindResult + +// Error constructs a human readable error for bindResults, containing any errors that a route +// had in binding to a gateway, note that this is only used if a route failed to bind to every +// listener it attempted to bind to. +func (b bindResults) Error() string { + messages := []string{} + for _, result := range b { + if result.err != nil { + messages = append(messages, fmt.Sprintf("%s: %s", result.section, result.err.Error())) + } + } + + sort.Strings(messages) + return strings.Join(messages, "; ") +} + +// DidBind returns whether a route successfully bound to any listener on a gateway. +func (b bindResults) DidBind() bool { + for _, result := range b { + if result.err == nil { + return true + } + } + return false +} + +// Condition constructs an Accepted condition for a route that will be scoped +// to the particular parent reference it's using to attempt binding. +func (b bindResults) Condition() metav1.Condition { + // if we bound to any listeners, say we're accepted + if b.DidBind() { + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + } + } + + // default to the most generic reason in the spec "NotAllowedByListeners" + reason := "NotAllowedByListeners" + + // if we only have a single binding error, we can get more specific + if len(b) == 1 { + for _, result := range b { + // if we have a hostname mismatch error, then use the more specific reason + if result.err == errRouteNoMatchingListenerHostname { + reason = "NoMatchingListenerHostname" + } + } + } + + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: reason, + Message: b.Error(), + } +} + +// parentBindResult associates a binding result with the given parent reference. +type parentBindResult struct { + parent gwv1beta1.ParentReference + results bindResults +} + +// parentBindResults contains the list of all results that occurred when this route +// attempted to bind to a gateway using its parent references. +type parentBindResults []parentBindResult + +var ( + // Each of the below are specified in the Gateway spec under ListenerConditionReason + // the general usage is that each error is specified as errListener* where * corresponds + // to the ListenerConditionReason given in the spec. If a reason is overloaded and can + // be used with two different types of things (i.e. something is not found or it's not supported) + // then we distinguish those two usages with errListener*_Usage. + errListenerUnsupportedProtocol = errors.New("listener protocol is unsupported") + errListenerPortUnavailable = errors.New("listener port is unavailable") + errListenerHostnameConflict = errors.New("listener hostname conflicts with another listener") + errListenerProtocolConflict = errors.New("listener protocol conflicts with another listener") + errListenerInvalidCertificateRef_NotFound = errors.New("certificate not found") + errListenerInvalidCertificateRef_NotSupported = errors.New("certificate type is not supported") + + // Below is where any custom generic listener validation errors should go. + // We map anything under here to a custom ListenerConditionReason of Invalid on + // an Accepted status type. + errListenerNoTLSPassthrough = errors.New("TLS passthrough is not supported") +) + +// listenerValidationResult contains the result of internally validating a single listener +// as well as the result of validating it in relation to all its peers (via conflictedErr). +// an error set on any of its members corresponds to an error condition on the corresponding +// status type. +type listenerValidationResult struct { + // status type: Accepted + acceptedErr error + // status type: Conflicted + conflictedErr error + // status type: ResolvedRefs + refErr error + // TODO: programmed +} + +// acceptedCondition constructs the condition for the Accepted status type. +func (l listenerValidationResult) acceptedCondition(generation int64) metav1.Condition { + now := metav1.Now() + switch l.acceptedErr { + case errListenerPortUnavailable: + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "PortUnavailable", + ObservedGeneration: generation, + Message: l.acceptedErr.Error(), + LastTransitionTime: now, + } + case errListenerUnsupportedProtocol: + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "UnsupportedProtocol", + ObservedGeneration: generation, + Message: l.acceptedErr.Error(), + LastTransitionTime: now, + } + case nil: + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + ObservedGeneration: generation, + Message: "listener accepted", + LastTransitionTime: now, + } + default: + // falback to invalid + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "Invalid", + ObservedGeneration: generation, + Message: l.acceptedErr.Error(), + LastTransitionTime: now, + } + } +} + +// conflictedCondition constructs the condition for the Conflicted status type. +func (l listenerValidationResult) conflictedCondition(generation int64) metav1.Condition { + now := metav1.Now() + + switch l.conflictedErr { + case errListenerProtocolConflict: + return metav1.Condition{ + Type: "Conflicted", + Status: metav1.ConditionTrue, + Reason: "ProtocolConflict", + ObservedGeneration: generation, + Message: l.conflictedErr.Error(), + LastTransitionTime: now, + } + case errListenerHostnameConflict: + return metav1.Condition{ + Type: "Conflicted", + Status: metav1.ConditionTrue, + Reason: "HostnameConflict", + ObservedGeneration: generation, + Message: l.conflictedErr.Error(), + LastTransitionTime: now, + } + default: + return metav1.Condition{ + Type: "Conflicted", + Status: metav1.ConditionFalse, + Reason: "NoConflicts", + ObservedGeneration: generation, + Message: "listener has no conflicts", + LastTransitionTime: now, + } + } +} + +// acceptedCondition constructs the condition for the ResolvedRefs status type. +func (l listenerValidationResult) resolvedRefsCondition(generation int64) metav1.Condition { + now := metav1.Now() + + switch l.refErr { + case errListenerInvalidCertificateRef_NotFound: + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidCertificateRef", + ObservedGeneration: generation, + Message: l.refErr.Error(), + LastTransitionTime: now, + } + case errListenerInvalidCertificateRef_NotSupported: + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidCertificateRef", + ObservedGeneration: generation, + Message: l.refErr.Error(), + LastTransitionTime: now, + } + default: + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + ObservedGeneration: generation, + Message: "resolved certificate references", + LastTransitionTime: now, + } + } +} + +// Conditions constructs the entire set of conditions for a given gateway listener. +func (l listenerValidationResult) Conditions(generation int64) []metav1.Condition { + return []metav1.Condition{ + l.acceptedCondition(generation), + l.conflictedCondition(generation), + l.resolvedRefsCondition(generation), + } +} + +// listenerValidationResults holds all of the results for a gateway's listeners +// the index of each result needs to correspond exactly to the index of the listener +// on the gateway spec for which it is describing. +type listenerValidationResults []listenerValidationResult + +// Invalid returns whether or not there is any listener that is not "Accepted" +// this is used in constructing a gateway's status where the Accepted status +// at the top-level can have a GatewayConditionReason of ListenersNotValid. +func (l listenerValidationResults) Invalid() bool { + for _, r := range l { + if r.acceptedErr != nil { + return true + } + } + return false +} + +// Conditions returns the listener conditions at a given index. +func (l listenerValidationResults) Conditions(generation int64, index int) []metav1.Condition { + result := l[index] + return result.Conditions(generation) +} + +var ( + // Each of the below are specified in the Gateway spec under GatewayConditionReason + // the general usage is that each error is specified as errGateway* where * corresponds + // to the GatewayConditionReason given in the spec. + errGatewayUnsupportedAddress = errors.New("gateway does not support specifying addresses") + errGatewayListenersNotValid = errors.New("one or more listeners are invalid") + errGatewayPending_Pods = errors.New("gateway pods are still being scheduled") + errGatewayPending_Consul = errors.New("gateway configuration is not yet synced to Consul") +) + +// gatewayValidationResult contains the result of internally validating a gateway. +// An error set on any of its members corresponds to an error condition on the corresponding +// status type. +type gatewayValidationResult struct { + acceptedErr error + programmedErr error +} + +// programmedCondition returns a condition for the Programmed status type. +func (l gatewayValidationResult) programmedCondition(generation int64) metav1.Condition { + now := metav1.Now() + + switch l.programmedErr { + case errGatewayPending_Pods, errGatewayPending_Consul: + return metav1.Condition{ + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + ObservedGeneration: generation, + Message: l.programmedErr.Error(), + LastTransitionTime: now, + } + default: + return metav1.Condition{ + Type: "Programmed", + Status: metav1.ConditionTrue, + Reason: "Programmed", + ObservedGeneration: generation, + Message: "gateway programmed", + LastTransitionTime: now, + } + } +} + +// acceptedCondition returns a condition for the Accepted status type. It takes a boolean argument +// for whether or not any of the gateway's listeners are invalid, if they are, it overrides whatever +// Reason is set as an error on the result and instead uses the ListenersNotValid reason. +func (l gatewayValidationResult) acceptedCondition(generation int64, listenersInvalid bool) metav1.Condition { + now := metav1.Now() + + if l.acceptedErr == nil { + if listenersInvalid { + return metav1.Condition{ + Type: "Accepted", + // should one invalid listener cause the entire gateway to become invalid? + Status: metav1.ConditionFalse, + Reason: "ListenersNotValid", + ObservedGeneration: generation, + Message: errGatewayListenersNotValid.Error(), + LastTransitionTime: now, + } + } + + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + ObservedGeneration: generation, + Message: "gateway accepted", + LastTransitionTime: now, + } + } + + if l.acceptedErr == errGatewayUnsupportedAddress { + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "UnsupportedAddress", + ObservedGeneration: generation, + Message: l.acceptedErr.Error(), + LastTransitionTime: now, + } + } + + // fallback to Invalid reason + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "Invalid", + ObservedGeneration: generation, + Message: l.acceptedErr.Error(), + LastTransitionTime: now, + } +} + +// Conditions constructs the gateway conditions given whether its listeners are valid. +func (l gatewayValidationResult) Conditions(generation int64, listenersInvalid bool) []metav1.Condition { + return []metav1.Condition{ + l.acceptedCondition(generation, listenersInvalid), + l.programmedCondition(generation), + } +} diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go new file mode 100644 index 0000000000..5175c8f647 --- /dev/null +++ b/control-plane/api-gateway/binding/route_binding.go @@ -0,0 +1,489 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// consulHTTPRouteFor returns the Consul HTTPRouteConfigEntry for the given reference. +func (b *Binder) consulHTTPRouteFor(ref api.ResourceReference) *api.HTTPRouteConfigEntry { + for _, route := range b.config.ConsulHTTPRoutes { + if route.Namespace == ref.Namespace && route.Partition == ref.Partition && route.Name == ref.Name { + return &route + } + } + return nil +} + +// consulTCPRouteFor returns the Consul TCPRouteConfigEntry for the given reference. +func (b *Binder) consulTCPRouteFor(ref api.ResourceReference) *api.TCPRouteConfigEntry { + for _, route := range b.config.ConsulTCPRoutes { + if route.Namespace == ref.Namespace && route.Partition == ref.Partition && route.Name == ref.Name { + return &route + } + } + return nil +} + +// routeBinder encapsulates the binding logic for binding a route to the given Gateway. +// The logic for route binding is almost identical between different route types, but +// due to the strong typing in the Spec and Go's inability to deal with fields via generics +// we have to pull in a bunch of accessors (which ideally should be in the upstream spec) +// for each route type. +// +// From the generic signature -- T: the type of Kubernetes route, U: the type of Consul config entry +// +// TODO: consider moving the function closures to something like an interface that we can +// implement the accessors on for each route type. +type routeBinder[T client.Object, U api.ConfigEntry] struct { + // isGatewayDeleted is used to determine whether we should just ignore + // attempting to bind the route (since we no longer know whether we + // should manage the route we only want to remove any state we've + // set on it). + isGatewayDeleted bool + // gateway is the gateway that we want to use for binding + gateway *gwv1beta1.Gateway + // gatewayRef is a Consul reference used to prune no-longer bound + // parents from a Consul resource we've created. + gatewayRef api.ResourceReference + // tracker is the referenceTracker used to determine when we want to cleanup + // routes based on a deleted gateway. + tracker referenceTracker + // namespaces is the set of namespaces in Consul that use for determining + // whether a route in a given namespace can bind to a gateway with AllowedRoutes set + namespaces map[string]corev1.Namespace + // services is a catalog of all connect-injected services to check a route against + // for resolving its backend refs + services map[types.NamespacedName]api.CatalogService + // meshServices is a catalog of all mesh service objects to check a route against + // for resolving its backend refs + meshServices map[types.NamespacedName]v1alpha1.MeshService + + // translationReferenceFunc is a function used to translate a Kubernetes object into + // a Consul object reference + translationReferenceFunc func(route T) api.ResourceReference + // lookupFunc is a function used for finding an existing Consul object based on + // its object reference + lookupFunc func(api.ResourceReference) U + // getParentsFunc is a function used for getting the parent references of a Consul route object + getParentsFunc func(U) []api.ResourceReference + // setParentsFunc is a function used for setting the parent references of a route object + setParentsFunc func(U, []api.ResourceReference) + // removeStatusRefsFunc is a function used for removing the statuses for the given parent + // references from a route + removeStatusRefsFunc func(T, []gwv1beta1.ParentReference) bool + // getHostnamesFunc is a function used for getting the hostnames associated with a route + getHostnamesFunc func(T) []gwv1beta1.Hostname + // getParentRefsFunc is used for getting the parent references of a Kubernetes route object + getParentRefsFunc func(T) []gwv1beta1.ParentReference + // translationFunc is used for translating a Kubernetes route into the corresponding Consul config entry + translationFunc func(T, map[types.NamespacedName]api.ResourceReference, map[types.NamespacedName]api.CatalogService, map[types.NamespacedName]v1alpha1.MeshService) U + // setRouteConditionFunc is used for adding or overwriting a condition on a route at the given + // parent + setRouteConditionFunc func(T, *gwv1beta1.ParentReference, metav1.Condition) bool + // getBackendRefsFunc returns a list of all backend references that we need to validate against the + // list of known connect-injected services + getBackendRefsFunc func(T) []gwv1beta1.BackendRef + // removeControllerStatusFunc is used to remove all of the statuses set by our controller when GC'ing + // a route + removeControllerStatusFunc func(T) bool +} + +// newRouteBinder creates a new route binder for the given Kubernetes and Consul route types +// generally this is lightly wrapped by other constructors that pass in the various closures +// needed for accessing fields on the objects. +func newRouteBinder[T client.Object, U api.ConfigEntry]( + isGatewayDeleted bool, + gateway *gwv1beta1.Gateway, + gatewayRef api.ResourceReference, + namespaces map[string]corev1.Namespace, + services map[types.NamespacedName]api.CatalogService, + meshServices map[types.NamespacedName]v1alpha1.MeshService, + tracker referenceTracker, + translationReferenceFunc func(route T) api.ResourceReference, + lookupFunc func(api.ResourceReference) U, + getParentsFunc func(U) []api.ResourceReference, + setParentsFunc func(U, []api.ResourceReference), + removeStatusRefsFunc func(T, []gwv1beta1.ParentReference) bool, + getHostnamesFunc func(T) []gwv1beta1.Hostname, + getParentRefsFunc func(T) []gwv1beta1.ParentReference, + translationFunc func(T, map[types.NamespacedName]api.ResourceReference, map[types.NamespacedName]api.CatalogService, map[types.NamespacedName]v1alpha1.MeshService) U, + setRouteConditionFunc func(T, *gwv1beta1.ParentReference, metav1.Condition) bool, + getBackendRefsFunc func(T) []gwv1beta1.BackendRef, + removeControllerStatusFunc func(T) bool, +) *routeBinder[T, U] { + return &routeBinder[T, U]{ + isGatewayDeleted: isGatewayDeleted, + gateway: gateway, + gatewayRef: gatewayRef, + namespaces: namespaces, + services: services, + meshServices: meshServices, + tracker: tracker, + translationReferenceFunc: translationReferenceFunc, + lookupFunc: lookupFunc, + getParentsFunc: getParentsFunc, + setParentsFunc: setParentsFunc, + removeStatusRefsFunc: removeStatusRefsFunc, + getHostnamesFunc: getHostnamesFunc, + getParentRefsFunc: getParentRefsFunc, + translationFunc: translationFunc, + setRouteConditionFunc: setRouteConditionFunc, + getBackendRefsFunc: getBackendRefsFunc, + removeControllerStatusFunc: removeControllerStatusFunc, + } +} + +// bind contains the main logic for binding a route to a given gateway. +func (r *routeBinder[T, U]) bind(route T, boundCount map[gwv1beta1.SectionName]int, seenRoutes map[api.ResourceReference]struct{}, snapshot Snapshot) (updatedSnapshot Snapshot) { + routeRef := r.translationReferenceFunc(route) + existing := r.lookupFunc(routeRef) + gatewayRefs := filterParentRefs(objectToMeta(r.gateway), route.GetNamespace(), r.getParentRefsFunc(route)) + + // mark this route as having been processed + seenRoutes[routeRef] = struct{}{} + + // flags to mark that some operation needs to occur + consulNeedsDelete := false + kubernetesNeedsUpdate := false + kubernetesNeedsStatusUpdate := false + // Since the update can either be for an existing resource (in the case + // of a deleted gateway) or for a resource generated by translating a + // bound gateway we just set the resource that we want to push out an + // update for. If this is not nil, we push it into the snapshot. + var consulUpdate U + + // we do this in a closure at the end to make sure we don't accidentally + // add something multiple times into the list of update/delete operations + // instead we just set a flag indicating that an update is needed and then + // append to the snapshot right before returning + defer func() { + if !isNil(consulUpdate) { + snapshot.Consul.Updates = append(snapshot.Consul.Updates, consulUpdate) + } + if consulNeedsDelete { + snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, routeRef) + } + if kubernetesNeedsUpdate { + snapshot.Kubernetes.Updates = append(snapshot.Kubernetes.Updates, route) + } + if kubernetesNeedsStatusUpdate { + snapshot.Kubernetes.StatusUpdates = append(snapshot.Kubernetes.StatusUpdates, route) + } + + updatedSnapshot = snapshot + }() + + if isDeleted(route) { + // mark the route as needing to get cleaned up if we detect that it's being deleted + consulNeedsDelete = true + if removeFinalizer(route) { + kubernetesNeedsUpdate = true + } + return + } + + if r.isGatewayDeleted { + // first check if this is our only ref for the route + if r.tracker.isLastReference(route) { + // if it is, then mark everything for deletion + consulNeedsDelete = true + if r.removeControllerStatusFunc(route) { + kubernetesNeedsStatusUpdate = true + } + if removeFinalizer(route) { + kubernetesNeedsUpdate = true + } + return + } + + // otherwise remove the condition since we no longer know if we should + // control the route and drop any references for the Consul route + if !isNil(existing) { + // this drops all the parent refs + r.setParentsFunc(existing, parentsForRoute(r.gatewayRef, r.getParentsFunc(existing), nil)) + // and then we mark the route as needing updated + consulUpdate = existing + // drop the status conditions + if r.removeStatusRefsFunc(route, gatewayRefs) { + kubernetesNeedsStatusUpdate = true + } + } + return + } + + if ensureFinalizer(route) { + kubernetesNeedsUpdate = true + return + } + + // TODO: scrub route refs from statuses that no longer exist + + validation := validateRefs(route.GetNamespace(), r.getBackendRefsFunc(route), r.services, r.meshServices) + // the spec is dumb and makes you set a parent for any status, even when the + // status is not with respect to a parent, as is the case of resolved refs + // so we need to set the status on all parents + for _, ref := range gatewayRefs { + if r.setRouteConditionFunc(route, &ref, validation.Condition()) { + kubernetesNeedsStatusUpdate = true + } + } + + results := make(parentBindResults, 0) + namespace := r.namespaces[route.GetNamespace()] + gk := route.GetObjectKind().GroupVersionKind().GroupKind() + for _, ref := range gatewayRefs { + result := make(bindResults, 0) + for _, listener := range listenersFor(r.gateway, ref.SectionName) { + if !routeKindIsAllowedForListener(supportedKindsForProtocol[listener.Protocol], gk) { + result = append(result, bindResult{ + section: listener.Name, + err: errRouteNotAllowedByListeners_Protocol, + }) + continue + } + + if !routeKindIsAllowedForListenerExplicit(listener.AllowedRoutes, gk) { + result = append(result, bindResult{ + section: listener.Name, + err: errRouteNotAllowedByListeners_Protocol, + }) + continue + } + + if !routeAllowedForListenerNamespaces(r.gateway.Namespace, listener.AllowedRoutes, namespace) { + result = append(result, bindResult{ + section: listener.Name, + err: errRouteNotAllowedByListeners_Namespace, + }) + continue + } + + if !routeAllowedForListenerHostname(listener.Hostname, r.getHostnamesFunc(route)) { + result = append(result, bindResult{ + section: listener.Name, + err: errRouteNoMatchingListenerHostname, + }) + continue + } + + result = append(result, bindResult{ + section: listener.Name, + }) + + boundCount[listener.Name] = boundCount[listener.Name] + 1 + } + + results = append(results, parentBindResult{ + parent: ref, + results: result, + }) + } + + updated := false + for _, result := range results { + if r.setRouteConditionFunc(route, &result.parent, result.results.Condition()) { + updated = true + } + } + + if updated { + kubernetesNeedsStatusUpdate = true + } + + entry := r.translationFunc(route, nil, r.services, r.meshServices) + // make all parent refs explicit based on what actually bound + if isNil(existing) { + r.setParentsFunc(entry, parentsForRoute(r.gatewayRef, nil, results)) + } else { + r.setParentsFunc(entry, parentsForRoute(r.gatewayRef, r.getParentsFunc(existing), results)) + } + consulUpdate = entry + + return +} + +// newTCPRouteBinder wraps newRouteBinder with the proper closures needed for accessing TCPRoutes and their config entries. +func (b *Binder) newTCPRouteBinder(tracker referenceTracker, services map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) *routeBinder[*gwv1alpha2.TCPRoute, *api.TCPRouteConfigEntry] { + return newRouteBinder( + b.isGatewayDeleted(), + &b.config.Gateway, + b.gatewayRef(), + b.config.Namespaces, + services, + meshServices, + tracker, + b.config.Translator.ReferenceForTCPRoute, + b.consulTCPRouteFor, + func(t *api.TCPRouteConfigEntry) []api.ResourceReference { return t.Parents }, + func(t *api.TCPRouteConfigEntry, parents []api.ResourceReference) { t.Parents = parents }, + b.statusSetter.removeTCPRouteReferences, + func(t *gwv1alpha2.TCPRoute) []gwv1beta1.Hostname { return nil }, + func(t *gwv1alpha2.TCPRoute) []gwv1beta1.ParentReference { return t.Spec.ParentRefs }, + b.config.Translator.TCPRouteToTCPRoute, + b.statusSetter.setTCPRouteCondition, + func(t *gwv1alpha2.TCPRoute) []gwv1beta1.BackendRef { + refs := []gwv1beta1.BackendRef{} + for _, rule := range t.Spec.Rules { + refs = append(refs, rule.BackendRefs...) + } + return refs + }, + b.statusSetter.removeTCPStatuses, + ) +} + +// newHTTPRouteBinder wraps newRouteBinder with the proper closures needed for accessing HTTPRoutes and their config entries. +func (b *Binder) newHTTPRouteBinder(tracker referenceTracker, services map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) *routeBinder[*gwv1beta1.HTTPRoute, *api.HTTPRouteConfigEntry] { + return newRouteBinder( + b.isGatewayDeleted(), + &b.config.Gateway, + b.gatewayRef(), + b.config.Namespaces, + services, + meshServices, + tracker, + b.config.Translator.ReferenceForHTTPRoute, + b.consulHTTPRouteFor, + func(t *api.HTTPRouteConfigEntry) []api.ResourceReference { return t.Parents }, + func(t *api.HTTPRouteConfigEntry, parents []api.ResourceReference) { t.Parents = parents }, + b.statusSetter.removeHTTPRouteReferences, + func(t *gwv1beta1.HTTPRoute) []gwv1beta1.Hostname { return t.Spec.Hostnames }, + func(t *gwv1beta1.HTTPRoute) []gwv1beta1.ParentReference { return t.Spec.ParentRefs }, + b.config.Translator.HTTPRouteToHTTPRoute, + b.statusSetter.setHTTPRouteCondition, + func(t *gwv1beta1.HTTPRoute) []gwv1beta1.BackendRef { + refs := []gwv1beta1.BackendRef{} + for _, rule := range t.Spec.Rules { + for _, ref := range rule.BackendRefs { + refs = append(refs, ref.BackendRef) + } + } + return refs + }, + b.statusSetter.removeHTTPStatuses, + ) +} + +// cleanRoute removes a gateway reference from the given route config entry +// and marks adds it to the snapshot if its mutated the entry at all. +func cleanRoute[T api.ConfigEntry]( + route T, + seenRoutes map[api.ResourceReference]struct{}, + snapshot Snapshot, + gatewayRef api.ResourceReference, + getParentsFunc func(T) []api.ResourceReference, + setParentsFunc func(T, []api.ResourceReference), +) Snapshot { + routeRef := translation.EntryToReference(route) + if _, ok := seenRoutes[routeRef]; !ok { + existingParents := getParentsFunc(route) + parents := parentsForRoute(gatewayRef, existingParents, nil) + if len(parents) == 0 { + // we can GC this now since we've dropped all refs from it + snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, routeRef) + } else if len(existingParents) != len(parents) { + // we've mutated the length, which means this route needs an update + setParentsFunc(route, parents) + snapshot.Consul.Updates = append(snapshot.Consul.Updates, route) + } + } + return snapshot +} + +// cleanHTTPRoute wraps cleanRoute with the proper closures for HTTPRoute config entries. +func (b *Binder) cleanHTTPRoute(route *api.HTTPRouteConfigEntry, seenRoutes map[api.ResourceReference]struct{}, snapshot Snapshot) Snapshot { + return cleanRoute(route, seenRoutes, snapshot, b.gatewayRef(), + func(route *api.HTTPRouteConfigEntry) []api.ResourceReference { return route.Parents }, + func(route *api.HTTPRouteConfigEntry, parents []api.ResourceReference) { route.Parents = parents }, + ) +} + +// cleanTCPRoute wraps cleanRoute with the proper closures for TCPRoute config entries. +func (b *Binder) cleanTCPRoute(route *api.TCPRouteConfigEntry, seenRoutes map[api.ResourceReference]struct{}, snapshot Snapshot) Snapshot { + return cleanRoute(route, seenRoutes, snapshot, b.gatewayRef(), + func(route *api.TCPRouteConfigEntry) []api.ResourceReference { return route.Parents }, + func(route *api.TCPRouteConfigEntry, parents []api.ResourceReference) { route.Parents = parents }, + ) +} + +// parentsForRoute constructs a list of Consul route parent references based on what parents actually bound +// on a given route. This is necessary due to the fact that some additional validation in Kubernetes might +// require a route not to actually be accepted by a gateway, whereas we may have laxer logic inside of Consul +// itself. In these cases we want to just drop the parent reference in the Consul config entry we are going +// to write in order for it not to succeed in binding where Kubernetes failed to bind. +func parentsForRoute(ref api.ResourceReference, existing []api.ResourceReference, results parentBindResults) []api.ResourceReference { + // store all section names that bound + parentSet := map[string]struct{}{} + for _, result := range results { + for _, r := range result.results { + if r.err == nil { + parentSet[string(r.section)] = struct{}{} + } + } + } + + // first, filter out all of the parent refs that don't correspond to this gateway + parents := []api.ResourceReference{} + for _, parent := range existing { + if parent.Kind == api.APIGateway && + parent.Name == ref.Name && + parent.Namespace == ref.Namespace { + continue + } + parents = append(parents, parent) + } + + // now construct the bound set + for parent := range parentSet { + parents = append(parents, api.ResourceReference{ + Kind: api.APIGateway, + Name: ref.Name, + Namespace: ref.Namespace, + SectionName: parent, + }) + } + return parents +} + +// filterParentRefs returns the subset of parent references on a route that point to the given gateway. +func filterParentRefs(gateway types.NamespacedName, namespace string, refs []gwv1beta1.ParentReference) []gwv1beta1.ParentReference { + references := []gwv1beta1.ParentReference{} + for _, ref := range refs { + if nilOrEqual(ref.Group, betaGroup) && + nilOrEqual(ref.Kind, kindGateway) && + gateway.Namespace == valueOr(ref.Namespace, namespace) && + gateway.Name == string(ref.Name) { + references = append(references, ref) + } + } + + return references +} + +// listenersFor returns the listeners corresponding the given section name. If the section +// name is actually specified, the returned set should just have one listener, if it is +// unspecified, the all gatweway listeners should be returned. +func listenersFor(gateway *gwv1beta1.Gateway, name *gwv1beta1.SectionName) []gwv1beta1.Listener { + listeners := []gwv1beta1.Listener{} + for _, listener := range gateway.Spec.Listeners { + if name == nil { + listeners = append(listeners, listener) + continue + } + if listener.Name == *name { + listeners = append(listeners, listener) + } + } + return listeners +} diff --git a/control-plane/api-gateway/binding/setter.go b/control-plane/api-gateway/binding/setter.go new file mode 100644 index 0000000000..93727b37b2 --- /dev/null +++ b/control-plane/api-gateway/binding/setter.go @@ -0,0 +1,180 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// setter wraps the status setting logic for routes. +type setter struct { + controllerName string +} + +// newSetter constructs a status setter with the given controller name. +func newSetter(controllerName string) *setter { + return &setter{controllerName: controllerName} +} + +// setHTTPRouteCondition sets an HTTPRoute condition on its status with the given parent. +func (s *setter) setHTTPRouteCondition(route *gwv1beta1.HTTPRoute, parent *gwv1beta1.ParentReference, condition metav1.Condition) bool { + condition.LastTransitionTime = metav1.Now() + condition.ObservedGeneration = route.Generation + + status := s.getParentStatus(route.Status.Parents, parent) + conditions, modified := setCondition(status.Conditions, condition) + if modified { + status.Conditions = conditions + route.Status.Parents = s.setParentStatus(route.Status.Parents, status) + } + return modified +} + +// removeHTTPRouteReferences removes the given parent reference sections from an HTTPRoute's status. +func (s *setter) removeHTTPRouteReferences(route *gwv1beta1.HTTPRoute, refs []gwv1beta1.ParentReference) bool { + modified := false + for _, parent := range refs { + parents, removed := s.removeParentStatus(route.Status.Parents, parent) + route.Status.Parents = parents + if removed { + modified = true + } + } + return modified +} + +// setTCPRouteCondition sets a TCPRoute condition on its status with the given parent. +func (s *setter) setTCPRouteCondition(route *gwv1alpha2.TCPRoute, parent *gwv1beta1.ParentReference, condition metav1.Condition) bool { + condition.LastTransitionTime = metav1.Now() + condition.ObservedGeneration = route.Generation + + status := s.getParentStatus(route.Status.Parents, parent) + conditions, modified := setCondition(status.Conditions, condition) + if modified { + status.Conditions = conditions + route.Status.Parents = s.setParentStatus(route.Status.Parents, status) + } + return modified +} + +// removeTCPRouteReferences removes the given parent reference sections from a TCPRoute's status. +func (s *setter) removeTCPRouteReferences(route *gwv1alpha2.TCPRoute, refs []gwv1beta1.ParentReference) bool { + modified := false + for _, parent := range refs { + parents, removed := s.removeParentStatus(route.Status.Parents, parent) + route.Status.Parents = parents + if removed { + modified = true + } + } + return modified +} + +// removeHTTPStatuses removes all statuses set by the given controller from an HTTPRoute's status. +func (s *setter) removeHTTPStatuses(route *gwv1beta1.HTTPRoute) bool { + modified := false + filtered := []gwv1beta1.RouteParentStatus{} + for _, status := range route.Status.Parents { + if string(status.ControllerName) == s.controllerName { + modified = true + continue + } + filtered = append(filtered, status) + } + + if modified { + route.Status.Parents = filtered + } + return modified +} + +// removeTCPStatuses removes all statuses set by the given controller from a TCPRoute's status. +func (s *setter) removeTCPStatuses(route *gwv1alpha2.TCPRoute) bool { + modified := false + filtered := []gwv1beta1.RouteParentStatus{} + for _, status := range route.Status.Parents { + if string(status.ControllerName) == s.controllerName { + modified = true + continue + } + filtered = append(filtered, status) + } + + if modified { + route.Status.Parents = filtered + } + return modified +} + +// getParentStatus returns the section of a status referenced by the given parent reference. +func (s *setter) getParentStatus(statuses []gwv1beta1.RouteParentStatus, parent *gwv1beta1.ParentReference) gwv1beta1.RouteParentStatus { + var parentRef gwv1beta1.ParentReference + if parent != nil { + parentRef = *parent + } + + for _, status := range statuses { + if parentsEqual(status.ParentRef, parentRef) && string(status.ControllerName) == s.controllerName { + return status + } + } + return gwv1beta1.RouteParentStatus{ + ParentRef: parentRef, + ControllerName: gwv1beta1.GatewayController(s.controllerName), + } +} + +// removeParentStatus removes the section of a status referenced by the given parent reference. +func (s *setter) removeParentStatus(statuses []gwv1beta1.RouteParentStatus, parent gwv1beta1.ParentReference) ([]gwv1beta1.RouteParentStatus, bool) { + found := false + filtered := []gwv1beta1.RouteParentStatus{} + for _, status := range statuses { + if parentsEqual(status.ParentRef, parent) && string(status.ControllerName) == s.controllerName { + found = true + continue + } + filtered = append(filtered, status) + } + return filtered, found +} + +// setCondition overrides or appends a condition to the list of conditions, returning if a modification +// to the condition set was made or not. Modifications only occur if a field other than the observation +// timestamp is modified. +func setCondition(conditions []metav1.Condition, condition metav1.Condition) ([]metav1.Condition, bool) { + for i, existing := range conditions { + if existing.Type == condition.Type { + // no-op if we have the exact same thing + if condition.Reason == existing.Reason && condition.Message == existing.Message && condition.ObservedGeneration == existing.ObservedGeneration { + return conditions, false + } + + conditions[i] = condition + return conditions, true + } + } + return append(conditions, condition), true +} + +// setParentStatus updates or inserts the set of parent statuses with the newly modified parent. +func (s *setter) setParentStatus(statuses []gwv1beta1.RouteParentStatus, parent gwv1beta1.RouteParentStatus) []gwv1beta1.RouteParentStatus { + for i, status := range statuses { + if parentsEqual(status.ParentRef, parent.ParentRef) && status.ControllerName == parent.ControllerName { + statuses[i] = parent + return statuses + } + } + return append(statuses, parent) +} + +// parentsEqual checks for equality between two parent references. +func parentsEqual(one, two gwv1beta1.ParentReference) bool { + return bothNilOrEqual(one.Group, two.Group) && + bothNilOrEqual(one.Kind, two.Kind) && + bothNilOrEqual(one.SectionName, two.SectionName) && + bothNilOrEqual(one.Port, two.Port) && + one.Name == two.Name +} diff --git a/control-plane/api-gateway/binding/setter_test.go b/control-plane/api-gateway/binding/setter_test.go new file mode 100644 index 0000000000..a8eabfb55d --- /dev/null +++ b/control-plane/api-gateway/binding/setter_test.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestSetter(t *testing.T) { + setter := newSetter("test") + parentRef := gwv1beta1.ParentReference{ + Name: "test", + } + parentRefDup := gwv1beta1.ParentReference{ + Name: "test", + } + condition := metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + } + route := &gwv1beta1.HTTPRoute{ + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{parentRef}, + }, + }, + } + require.True(t, setter.setHTTPRouteCondition(route, &parentRef, condition)) + require.False(t, setter.setHTTPRouteCondition(route, &parentRefDup, condition)) + require.False(t, setter.setHTTPRouteCondition(route, &parentRefDup, condition)) + require.False(t, setter.setHTTPRouteCondition(route, &parentRefDup, condition)) + + require.Len(t, route.Status.Parents, 1) + require.Len(t, route.Status.Parents[0].Conditions, 1) +} diff --git a/control-plane/api-gateway/binding/snapshot.go b/control-plane/api-gateway/binding/snapshot.go new file mode 100644 index 0000000000..5eaf48abb3 --- /dev/null +++ b/control-plane/api-gateway/binding/snapshot.go @@ -0,0 +1,56 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// KubernetesSnapshot contains all the operations +// required in Kubernetes to complete reconciliation. +type KubernetesSnapshot struct { + // Updates is the list of objects that need to have + // aspects of their metadata or spec updated in Kubernetes + // (i.e. for finalizers or annotations) + Updates []client.Object + // StatusUpdates is the list of objects that need + // to have their statuses updated in Kubernetes + StatusUpdates []client.Object +} + +// ConsulSnapshot contains all the operations required +// in Consul to complete reconciliation. +type ConsulSnapshot struct { + // Updates is the list of ConfigEntry objects that should + // either be updated or created in Consul + Updates []api.ConfigEntry + // Deletions is a list of references that ought to be + // deleted in Consul + Deletions []api.ResourceReference + // Registrations is a list of Consul services to make sure + // are registered in Consul + Registrations []api.CatalogRegistration + // Deregistrations is a list of Consul services to make sure + // are no longer registered in Consul + Deregistrations []api.CatalogDeregistration +} + +// Snapshot contains all Kubernetes and Consul operations +// needed to complete reconciliation. +type Snapshot struct { + // Kubernetes holds the snapshot of required Kubernetes operations + Kubernetes KubernetesSnapshot + // Consul holds the snapshot of required Consul operations + Consul ConsulSnapshot + // GatewayClassConfig is the configuration to use for determining + // a Gateway deployment, if it is not set, a deployment should be + // deleted instead of updated + GatewayClassConfig *v1alpha1.GatewayClassConfig + + // UpsertGatewayDeployment determines whether the gateway deployment + // objects should be updated, i.e. deployments, roles, services + UpsertGatewayDeployment bool +} diff --git a/control-plane/api-gateway/binding/utils.go b/control-plane/api-gateway/binding/utils.go new file mode 100644 index 0000000000..2ba2d01ce5 --- /dev/null +++ b/control-plane/api-gateway/binding/utils.go @@ -0,0 +1,105 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "reflect" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// pointerTo is a convenience method for taking a pointer +// of an object without having to declare an intermediate variable. +// It's also useful for making sure we don't accidentally take +// the pointer of a range variable directly. +func pointerTo[T any](v T) *T { + return &v +} + +// isNil checks if the argument is nil. It's mainly used to +// check if a generic conforming to a nullable interface is +// actually nil. +func isNil(arg interface{}) bool { + return arg == nil || reflect.ValueOf(arg).IsNil() +} + +// bothNilOrEqual is used to determine if two pointers to comparable +// object are either nil or both point to the same value. +func bothNilOrEqual[T comparable](one, two *T) bool { + if one == nil && two == nil { + return true + } + if one == nil { + return false + } + if two == nil { + return false + } + return *one == *two +} + +// valueOr checks if a string-like pointer is nil, and if it is, +// returns the given value instead. +func valueOr[T ~string](v *T, fallback string) string { + if v == nil { + return fallback + } + return string(*v) +} + +// nilOrEqual checks if a string-like pointer is nil or if it is +// equal to the value provided. +func nilOrEqual[T ~string](v *T, check string) bool { + return v == nil || string(*v) == check +} + +// objectToMeta returns the NamespacedName for the given object. +func objectToMeta[T metav1.Object](object T) types.NamespacedName { + return types.NamespacedName{ + Namespace: object.GetNamespace(), + Name: object.GetName(), + } +} + +// isDeleted checks if the deletion timestamp is set for an object. +func isDeleted(object client.Object) bool { + return !object.GetDeletionTimestamp().IsZero() +} + +// ensureFinalizer ensures that our finalizer is set on an object +// returning whether or not it modified the object. +func ensureFinalizer(object client.Object) bool { + if !object.GetDeletionTimestamp().IsZero() { + return false + } + + finalizers := object.GetFinalizers() + for _, f := range finalizers { + if f == gatewayFinalizer { + return false + } + } + + object.SetFinalizers(append(finalizers, gatewayFinalizer)) + return true +} + +// removeFinalizer ensures that our finalizer is absent from an object +// returning whether or not it modified the object. +func removeFinalizer(object client.Object) bool { + found := false + filtered := []string{} + for _, f := range object.GetFinalizers() { + if f == gatewayFinalizer { + found = true + continue + } + filtered = append(filtered, f) + } + + object.SetFinalizers(filtered) + return found +} diff --git a/control-plane/api-gateway/binding/utils_test.go b/control-plane/api-gateway/binding/utils_test.go new file mode 100644 index 0000000000..a7393e6839 --- /dev/null +++ b/control-plane/api-gateway/binding/utils_test.go @@ -0,0 +1,239 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestIsNil(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + value interface{} + expected bool + }{ + "nil pointer": { + value: (*string)(nil), + expected: true, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, isNil(tt.value)) + }) + } +} + +func TestBothNilOrEqual(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + first *string + second *string + expected bool + }{ + "both nil": { + first: nil, + second: nil, + expected: true, + }, + "second nil": { + first: pointerTo(""), + second: nil, + expected: false, + }, + "first nil": { + first: nil, + second: pointerTo(""), + expected: false, + }, + "both equal": { + first: pointerTo(""), + second: pointerTo(""), + expected: true, + }, + "both not equal": { + first: pointerTo("1"), + second: pointerTo("2"), + expected: false, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, bothNilOrEqual(tt.first, tt.second)) + }) + } +} + +func TestValueOr(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + value *string + or string + expected string + }{ + "nil value": { + value: nil, + or: "test", + expected: "test", + }, + "set value": { + value: pointerTo("value"), + or: "test", + expected: "value", + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, valueOr(tt.value, tt.or)) + }) + } +} + +func TestNilOrEqual(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + value *string + check string + expected bool + }{ + "nil value": { + value: nil, + check: "test", + expected: true, + }, + "equal values": { + value: pointerTo("test"), + check: "test", + expected: true, + }, + "unequal values": { + value: pointerTo("value"), + check: "test", + expected: false, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, nilOrEqual(tt.value, tt.check)) + }) + } +} + +func TestObjectToMeta(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + object metav1.Object + expected types.NamespacedName + }{ + "gateway": { + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "test"}}, + expected: types.NamespacedName{Namespace: "test", Name: "test"}, + }, + "secret": { + object: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Namespace: "secret", Name: "secret"}}, + expected: types.NamespacedName{Namespace: "secret", Name: "secret"}, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, objectToMeta(tt.object)) + }) + } +} + +func TestIsDeleted(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + object client.Object + expected bool + }{ + "deleted gateway": { + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: pointerTo(metav1.Now())}}, + expected: true, + }, + "non-deleted http route": { + object: &gwv1beta1.HTTPRoute{}, + expected: false, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, isDeleted(tt.object)) + }) + } +} + +func TestEnsureFinalizer(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + object client.Object + expected bool + finalizers []string + }{ + "gateway no finalizer": { + object: &gwv1beta1.Gateway{}, + expected: true, + finalizers: []string{gatewayFinalizer}, + }, + "gateway other finalizer": { + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other"}}}, + expected: true, + finalizers: []string{"other", gatewayFinalizer}, + }, + "gateway already has finalizer": { + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{gatewayFinalizer}}}, + expected: false, + finalizers: []string{gatewayFinalizer}, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, ensureFinalizer(tt.object)) + require.Equal(t, tt.finalizers, tt.object.GetFinalizers()) + }) + } +} + +func TestRemoveFinalizer(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + object client.Object + expected bool + finalizers []string + }{ + "gateway no finalizer": { + object: &gwv1beta1.Gateway{}, + expected: false, + finalizers: []string{}, + }, + "gateway other finalizer": { + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other"}}}, + expected: false, + finalizers: []string{"other"}, + }, + "gateway multiple finalizers": { + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{gatewayFinalizer, gatewayFinalizer}}}, + expected: true, + finalizers: []string{}, + }, + "gateway mixed finalizers": { + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other", gatewayFinalizer}}}, + expected: true, + finalizers: []string{"other"}, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, removeFinalizer(tt.object)) + require.Equal(t, tt.finalizers, tt.object.GetFinalizers()) + }) + } +} diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go new file mode 100644 index 0000000000..c22726f2da --- /dev/null +++ b/control-plane/api-gateway/binding/validation.go @@ -0,0 +1,332 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "strings" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + klabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// validateRefs validates backend references for a route, determining whether or +// not they were found in the list of known connect-injected services. +func validateRefs(namespace string, refs []gwv1beta1.BackendRef, services map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) routeValidationResults { + var result routeValidationResults + for _, ref := range refs { + nsn := types.NamespacedName{ + Name: string(ref.BackendObjectReference.Name), + Namespace: valueOr(ref.BackendObjectReference.Namespace, namespace), + } + + // TODO: check reference grants + + backendRef := ref.BackendObjectReference + + isServiceRef := nilOrEqual(backendRef.Group, "") && nilOrEqual(backendRef.Kind, "Service") + isMeshServiceRef := derefEqual(backendRef.Group, v1alpha1.ConsulHashicorpGroup) && derefEqual(backendRef.Kind, v1alpha1.MeshServiceKind) + + if !isServiceRef && !isMeshServiceRef { + result = append(result, routeValidationResult{ + namespace: nsn.Namespace, + backend: ref, + err: errRouteInvalidKind, + }) + continue + } + + if isServiceRef { + if _, found := services[nsn]; !found { + result = append(result, routeValidationResult{ + namespace: nsn.Namespace, + backend: ref, + err: errRouteBackendNotFound, + }) + continue + } + } + + if isMeshServiceRef { + if _, found := meshServices[nsn]; !found { + result = append(result, routeValidationResult{ + namespace: nsn.Namespace, + backend: ref, + err: errRouteBackendNotFound, + }) + continue + } + } + + result = append(result, routeValidationResult{ + namespace: nsn.Namespace, + backend: ref, + }) + } + return result +} + +// validateGateway validates that a gateway is semantically valid given +// the set of features that we support. +func validateGateway(gateway gwv1beta1.Gateway, pods []corev1.Pod, consulGateway *api.APIGatewayConfigEntry) gatewayValidationResult { + var result gatewayValidationResult + + if len(gateway.Spec.Addresses) > 0 { + result.acceptedErr = errGatewayUnsupportedAddress + } + + if len(pods) == 0 { + result.programmedErr = errGatewayPending_Pods + } else if consulGateway == nil { + result.programmedErr = errGatewayPending_Consul + } + + return result +} + +// mergedListener associates a listener with its indexed position +// in the gateway spec, it's used to re-associate a status with +// a listener after we merge compatible listeners together and then +// validate their conflicts. +type mergedListener struct { + index int + listener gwv1beta1.Listener +} + +// mergedListeners is a set of a listeners that are considered "merged" +// due to referencing the same listener port. +type mergedListeners []mergedListener + +// validateProtocol validates that the protocols used across all merged +// listeners are compatible. +func (m mergedListeners) validateProtocol() error { + var protocol *gwv1beta1.ProtocolType + for _, l := range m { + if protocol == nil { + protocol = pointerTo(l.listener.Protocol) + } + if *protocol != l.listener.Protocol { + return errListenerProtocolConflict + } + } + return nil +} + +// validateHostname validates that the merged listeners don't use the same +// hostnames as per the spec. +func (m mergedListeners) validateHostname(index int, listener gwv1beta1.Listener) error { + for _, l := range m { + if l.index == index { + continue + } + if bothNilOrEqual(listener.Hostname, l.listener.Hostname) { + return errListenerHostnameConflict + } + } + return nil +} + +// validateTLS validates that the TLS configuration for a given listener is valid and that +// the certificates that it references exist. +func validateTLS(namespace string, tls *gwv1beta1.GatewayTLSConfig, certificates []corev1.Secret) (error, error) { + if tls == nil { + return nil, nil + } + + // TODO: Resource Grants + + var err error +MAIN_LOOP: + for _, ref := range tls.CertificateRefs { + // break on the first error + if !nilOrEqual(ref.Group, "") || !nilOrEqual(ref.Kind, "Secret") { + err = errListenerInvalidCertificateRef_NotSupported + break MAIN_LOOP + } + ns := valueOr(ref.Namespace, namespace) + + for _, secret := range certificates { + if secret.Namespace == ns && secret.Name == string(ref.Name) { + continue MAIN_LOOP + } + } + + // not found, set error + err = errListenerInvalidCertificateRef_NotFound + break MAIN_LOOP + } + + if tls.Mode != nil && *tls.Mode == gwv1beta1.TLSModePassthrough { + return errListenerNoTLSPassthrough, err + } + + // TODO: validate tls options + return nil, err +} + +// validateListeners validates the given listeners both internally and with respect to each +// other for purposes of setting "Conflicted" status conditions. +func validateListeners(namespace string, listeners []gwv1beta1.Listener, secrets []corev1.Secret) listenerValidationResults { + var results listenerValidationResults + merged := make(map[gwv1beta1.PortNumber]mergedListeners) + for i, listener := range listeners { + merged[listener.Port] = append(merged[listener.Port], mergedListener{ + index: i, + listener: listener, + }) + } + + for i, listener := range listeners { + var result listenerValidationResult + + err, refErr := validateTLS(namespace, listener.TLS, secrets) + result.refErr = refErr + if err != nil { + result.acceptedErr = err + } else { + _, supported := supportedKindsForProtocol[listener.Protocol] + if !supported { + result.acceptedErr = errListenerUnsupportedProtocol + } else if listener.Port == 20000 { //admin port + result.acceptedErr = errListenerPortUnavailable + } + } + + if err := merged[listener.Port].validateProtocol(); err != nil { + result.conflictedErr = err + } else { + result.conflictedErr = merged[listener.Port].validateHostname(i, listener) + } + + results = append(results, result) + } + return results +} + +// routeAllowedForListenerNamespaces determines whether the route is allowed +// to bind to the Gateway based on the AllowedRoutes namespace selectors. +func routeAllowedForListenerNamespaces(gatewayNamespace string, allowedRoutes *gwv1beta1.AllowedRoutes, namespace corev1.Namespace) bool { + var namespaceSelector *gwv1beta1.RouteNamespaces + if allowedRoutes != nil { + // check gateway namespace + namespaceSelector = allowedRoutes.Namespaces + } + + // set default if namespace selector is nil + from := gwv1beta1.NamespacesFromSame + if namespaceSelector != nil && namespaceSelector.From != nil && *namespaceSelector.From != "" { + from = *namespaceSelector.From + } + + switch from { + case gwv1beta1.NamespacesFromAll: + return true + case gwv1beta1.NamespacesFromSame: + return gatewayNamespace == namespace.Name + case gwv1beta1.NamespacesFromSelector: + namespaceSelector, err := metav1.LabelSelectorAsSelector(namespaceSelector.Selector) + if err != nil { + // log the error here, the label selector is invalid + return false + } + + return namespaceSelector.Matches(toNamespaceSet(namespace.GetName(), namespace.GetLabels())) + default: + return false + } +} + +// routeAllowedForListenerHostname checks that a hostname specified on a route and the hostname specified +// on the gateway listener are compatible. +func routeAllowedForListenerHostname(hostname *gwv1beta1.Hostname, hostnames []gwv1beta1.Hostname) bool { + if hostname == nil || len(hostnames) == 0 { + return true + } + + for _, name := range hostnames { + if hostnamesMatch(name, *hostname) { + return true + } + } + return false +} + +// hostnameMatch checks that an individual hostname matches another hostname for +// compatibility. +func hostnamesMatch(a gwv1alpha2.Hostname, b gwv1beta1.Hostname) bool { + if a == "" || a == "*" || b == "" || b == "*" { + // any wildcard always matches + return true + } + + if strings.HasPrefix(string(a), "*.") || strings.HasPrefix(string(b), "*.") { + aLabels, bLabels := strings.Split(string(a), "."), strings.Split(string(b), ".") + if len(aLabels) != len(bLabels) { + return false + } + + for i := 1; i < len(aLabels); i++ { + if !strings.EqualFold(aLabels[i], bLabels[i]) { + return false + } + } + return true + } + + return string(a) == string(b) +} + +// routeKindIsAllowedForListener checks that the given route kind is present in the allowed set. +func routeKindIsAllowedForListener(kinds []gwv1beta1.RouteGroupKind, gk schema.GroupKind) bool { + if kinds == nil { + return true + } + + for _, kind := range kinds { + if string(kind.Kind) == gk.Kind && nilOrEqual(kind.Group, gk.Group) { + return true + } + } + + return false +} + +// routeKindIsAllowedForListenerExplicit checks that a route is allowed by the kinds specified explicitly +// on the listener. +func routeKindIsAllowedForListenerExplicit(allowedRoutes *gwv1alpha2.AllowedRoutes, gk schema.GroupKind) bool { + if allowedRoutes == nil { + return true + } + + return routeKindIsAllowedForListener(allowedRoutes.Kinds, gk) +} + +// toNamespaceSet constructs a list of labels used to match a Namespace. +func toNamespaceSet(name string, labels map[string]string) klabels.Labels { + // If namespace label is not set, implicitly insert it to support older Kubernetes versions + if labels[namespaceNameLabel] == name { + // Already set, avoid copies + return klabels.Set(labels) + } + // First we need a copy to not modify the underlying object + ret := make(map[string]string, len(labels)+1) + for k, v := range labels { + ret[k] = v + } + ret[namespaceNameLabel] = name + return klabels.Set(ret) +} + +func derefEqual[T ~string](v *T, check string) bool { + if v == nil { + return false + } + return string(*v) == check +} diff --git a/control-plane/api-gateway/binding/validation_test.go b/control-plane/api-gateway/binding/validation_test.go new file mode 100644 index 0000000000..eaf4388413 --- /dev/null +++ b/control-plane/api-gateway/binding/validation_test.go @@ -0,0 +1,672 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package binding + +import ( + "testing" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestValidateRefs(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + namespace string + refs []gwv1beta1.BackendObjectReference + services map[types.NamespacedName]api.CatalogService + meshServices map[types.NamespacedName]v1alpha1.MeshService + expectedErrors []error + }{ + "all pass no namespaces": { + namespace: "test", + refs: []gwv1beta1.BackendObjectReference{{Name: "1"}, {Name: "2"}}, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "1", Namespace: "test"}: {}, + {Name: "2", Namespace: "test"}: {}, + {Name: "3", Namespace: "test"}: {}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + expectedErrors: []error{nil, nil}, + }, + "all pass namespaces": { + namespace: "test", + refs: []gwv1beta1.BackendObjectReference{ + {Name: "1", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + {Name: "2", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "1", Namespace: "other"}: {}, + {Name: "2", Namespace: "other"}: {}, + {Name: "3", Namespace: "other"}: {}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + expectedErrors: []error{nil, nil}, + }, + "all pass mixed": { + namespace: "test", + refs: []gwv1beta1.BackendObjectReference{ + {Name: "1", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + {Name: "2"}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "1", Namespace: "other"}: {}, + {Name: "2", Namespace: "test"}: {}, + {Name: "3", Namespace: "other"}: {}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + expectedErrors: []error{nil, nil}, + }, + "all fail mixed": { + namespace: "test", + refs: []gwv1beta1.BackendObjectReference{ + {Name: "1"}, + {Name: "2", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "1", Namespace: "other"}: {}, + {Name: "2", Namespace: "test"}: {}, + {Name: "3", Namespace: "other"}: {}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, + }, + "all fail no namespaces": { + namespace: "test", + refs: []gwv1beta1.BackendObjectReference{ + {Name: "1"}, + {Name: "2"}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "1", Namespace: "other"}: {}, + {Name: "2", Namespace: "other"}: {}, + {Name: "3", Namespace: "other"}: {}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, + }, + "all fail namespaces": { + namespace: "test", + refs: []gwv1beta1.BackendObjectReference{ + {Name: "1", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + {Name: "2", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "1", Namespace: "test"}: {}, + {Name: "2", Namespace: "test"}: {}, + {Name: "3", Namespace: "test"}: {}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, + }, + "type failures": { + namespace: "test", + refs: []gwv1beta1.BackendObjectReference{ + {Name: "1", Group: pointerTo[gwv1beta1.Group]("test")}, + {Name: "2"}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "1", Namespace: "test"}: {}, + {Name: "2", Namespace: "test"}: {}, + {Name: "3", Namespace: "test"}: {}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + expectedErrors: []error{errRouteInvalidKind, nil}, + }, + "mesh services": { + namespace: "test", + refs: []gwv1beta1.BackendObjectReference{ + { + Name: "1", + Group: pointerTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), + Kind: pointerTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), + }, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{ + {Name: "1", Namespace: "test"}: {}, + {Name: "2", Namespace: "test"}: {}, + {Name: "3", Namespace: "test"}: {}, + }, + expectedErrors: []error{nil}, + }, + } { + t.Run(name, func(t *testing.T) { + refs := make([]gwv1beta1.BackendRef, len(tt.refs)) + for i, ref := range tt.refs { + refs[i] = gwv1beta1.BackendRef{BackendObjectReference: ref} + } + + actual := validateRefs(tt.namespace, refs, tt.services, tt.meshServices) + require.Equal(t, len(actual), len(tt.refs)) + require.Equal(t, len(actual), len(tt.expectedErrors)) + for i, err := range tt.expectedErrors { + require.Equal(t, err, actual[i].err) + } + }) + } +} + +func TestValidateGateway(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + object gwv1beta1.Gateway + expected error + }{ + "valid": { + object: gwv1beta1.Gateway{}, + expected: nil, + }, + "invalid": { + object: gwv1beta1.Gateway{Spec: gwv1beta1.GatewaySpec{Addresses: []gwv1beta1.GatewayAddress{ + {Value: "1"}, + }}}, + expected: errGatewayUnsupportedAddress, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, validateGateway(tt.object, nil, nil).acceptedErr) + }) + } +} + +func TestMergedListeners_ValidateProtocol(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + mergedListeners mergedListeners + expected error + }{ + "valid": { + mergedListeners: []mergedListener{ + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + }, + expected: nil, + }, + "invalid": { + mergedListeners: []mergedListener{ + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.TCPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + }, + expected: errListenerProtocolConflict, + }, + "big list": { + mergedListeners: []mergedListener{ + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPSProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, + }, + expected: errListenerProtocolConflict, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, tt.mergedListeners.validateProtocol()) + }) + } +} + +func TestMergedListeners_ValidateHostname(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + mergedListeners mergedListeners + expected error + }{ + "valid": { + mergedListeners: []mergedListener{ + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("1")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("2")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("3")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("4")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("5")}}, + {}, + }, + expected: nil, + }, + "invalid nil": { + mergedListeners: []mergedListener{ + {}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("1")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("2")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("3")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("4")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("5")}}, + {}, + }, + expected: errListenerHostnameConflict, + }, + "invalid set": { + mergedListeners: []mergedListener{ + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("1")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("2")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("3")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("4")}}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("5")}}, + {}, + {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("1")}}, + }, + expected: errListenerHostnameConflict, + }, + } { + t.Run(name, func(t *testing.T) { + for i, l := range tt.mergedListeners { + l.index = i + tt.mergedListeners[i] = l + } + + require.Equal(t, tt.expected, tt.mergedListeners.validateHostname(0, tt.mergedListeners[0].listener)) + }) + } +} + +func TestValidateTLS(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + namespace string + tls *gwv1beta1.GatewayTLSConfig + certificates []corev1.Secret + expectedResolvedRefsErr error + expectedAcceptedErr error + }{ + "no tls": { + namespace: "test", + tls: nil, + certificates: nil, + expectedResolvedRefsErr: nil, + expectedAcceptedErr: nil, + }, + "not supported certificate": { + namespace: "test", + tls: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "foo", Namespace: pointerTo[gwv1beta1.Namespace]("other"), Group: pointerTo[gwv1beta1.Group]("test")}, + }, + }, + certificates: []corev1.Secret{ + {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "other"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "other"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "other"}}, + }, + expectedResolvedRefsErr: errListenerInvalidCertificateRef_NotSupported, + expectedAcceptedErr: nil, + }, + "not found certificate": { + namespace: "test", + tls: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "zoiks", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + }, + }, + certificates: []corev1.Secret{ + {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "other"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "other"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "other"}}, + }, + expectedResolvedRefsErr: errListenerInvalidCertificateRef_NotFound, + expectedAcceptedErr: nil, + }, + "not found certificate mismatched namespace": { + namespace: "test", + tls: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "foo", Namespace: pointerTo[gwv1beta1.Namespace]("1")}, + }, + }, + certificates: []corev1.Secret{ + {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "other"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "other"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "other"}}, + }, + expectedResolvedRefsErr: errListenerInvalidCertificateRef_NotFound, + expectedAcceptedErr: nil, + }, + "passthrough mode": { + namespace: "test", + tls: &gwv1beta1.GatewayTLSConfig{ + Mode: pointerTo(gwv1beta1.TLSModePassthrough), + }, + certificates: nil, + expectedResolvedRefsErr: nil, + expectedAcceptedErr: errListenerNoTLSPassthrough, + }, + "valid targeted namespace": { + namespace: "test", + tls: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "foo", Namespace: pointerTo[gwv1beta1.Namespace]("1")}, + {Name: "bar", Namespace: pointerTo[gwv1beta1.Namespace]("2")}, + {Name: "baz", Namespace: pointerTo[gwv1beta1.Namespace]("3")}, + }, + }, + certificates: []corev1.Secret{ + {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "2"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "3"}}, + }, + expectedResolvedRefsErr: nil, + expectedAcceptedErr: nil, + }, + "valid same namespace": { + namespace: "test", + tls: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "foo"}, + {Name: "bar"}, + {Name: "baz"}, + }, + }, + certificates: []corev1.Secret{ + {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test"}}, + }, + expectedResolvedRefsErr: nil, + expectedAcceptedErr: nil, + }, + "valid empty certs": { + namespace: "test", + tls: &gwv1beta1.GatewayTLSConfig{}, + certificates: nil, + expectedResolvedRefsErr: nil, + expectedAcceptedErr: nil, + }, + } { + t.Run(name, func(t *testing.T) { + actualAcceptedError, actualResolvedRefsError := validateTLS(tt.namespace, tt.tls, tt.certificates) + require.Equal(t, tt.expectedResolvedRefsErr, actualResolvedRefsError) + require.Equal(t, tt.expectedAcceptedErr, actualAcceptedError) + }) + } +} + +func TestValidateListeners(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + listeners []gwv1beta1.Listener + expectedAcceptedErr error + }{ + "valid protocol HTTP": { + listeners: []gwv1beta1.Listener{ + {Protocol: gwv1beta1.HTTPProtocolType}, + }, + expectedAcceptedErr: nil, + }, + "valid protocol HTTPS": { + listeners: []gwv1beta1.Listener{ + {Protocol: gwv1beta1.HTTPSProtocolType}, + }, + expectedAcceptedErr: nil, + }, + "valid protocol TCP": { + listeners: []gwv1beta1.Listener{ + {Protocol: gwv1beta1.TCPProtocolType}, + }, + expectedAcceptedErr: nil, + }, + "invalid protocol UDP": { + listeners: []gwv1beta1.Listener{ + {Protocol: gwv1beta1.UDPProtocolType}, + }, + expectedAcceptedErr: errListenerUnsupportedProtocol, + }, + "invalid port": { + listeners: []gwv1beta1.Listener{ + {Protocol: gwv1beta1.TCPProtocolType, Port: 20000}, + }, + expectedAcceptedErr: errListenerPortUnavailable, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expectedAcceptedErr, validateListeners("", tt.listeners, nil)[0].acceptedErr) + }) + } +} + +func TestRouteAllowedForListenerNamespaces(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + allowedRoutes *gwv1beta1.AllowedRoutes + gatewayNamespace string + routeNamespace corev1.Namespace + expected bool + }{ + "default same namespace allowed": { + allowedRoutes: nil, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, + expected: true, + }, + "default same namespace not allowed": { + allowedRoutes: nil, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other"}}, + expected: false, + }, + "explicit same namespace allowed": { + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: pointerTo(gwv1beta1.NamespacesFromSame)}}, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, + expected: true, + }, + "explicit same namespace not allowed": { + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: pointerTo(gwv1beta1.NamespacesFromSame)}}, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other"}}, + expected: false, + }, + "all namespace allowed": { + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: pointerTo(gwv1beta1.NamespacesFromAll)}}, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other"}}, + expected: true, + }, + "invalid namespace from not allowed": { + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: pointerTo[gwv1beta1.FromNamespaces]("other")}}, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, + expected: false, + }, + "labeled namespace allowed": { + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }}, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other", Labels: map[string]string{ + "foo": "bar", + }}}, + expected: true, + }, + "labeled namespace not allowed": { + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }}, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other", Labels: map[string]string{ + "foo": "baz", + }}}, + expected: false, + }, + "invalid labeled namespace": { + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ + From: pointerTo(gwv1beta1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{ + {Key: "foo", Operator: "junk", Values: []string{"1"}}, + }}, + }}, + gatewayNamespace: "test", + routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other", Labels: map[string]string{ + "foo": "bar", + }}}, + expected: false, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, routeAllowedForListenerNamespaces(tt.gatewayNamespace, tt.allowedRoutes, tt.routeNamespace)) + }) + } +} + +func TestRouteAllowedForListenerHostname(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + hostname *gwv1beta1.Hostname + hostnames []gwv1beta1.Hostname + expected bool + }{ + "empty hostnames": { + hostname: nil, + hostnames: []gwv1beta1.Hostname{"foo", "bar"}, + expected: true, + }, + "empty hostname": { + hostname: pointerTo[gwv1beta1.Hostname]("foo"), + hostnames: nil, + expected: true, + }, + "any hostname match": { + hostname: pointerTo[gwv1beta1.Hostname]("foo"), + hostnames: []gwv1beta1.Hostname{"foo", "bar"}, + expected: true, + }, + "no match": { + hostname: pointerTo[gwv1beta1.Hostname]("foo"), + hostnames: []gwv1beta1.Hostname{"bar"}, + expected: false, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, routeAllowedForListenerHostname(tt.hostname, tt.hostnames)) + }) + } +} + +func TestHostnamesMatch(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + one gwv1beta1.Hostname + two gwv1beta1.Hostname + expected bool + }{ + "wildcard one": { + one: "*", + two: "foo", + expected: true, + }, + "wildcard two": { + one: "foo", + two: "*", + expected: true, + }, + "empty one": { + one: "", + two: "foo", + expected: true, + }, + "empty two": { + one: "foo", + two: "", + expected: true, + }, + "subdomain one": { + one: "*.foo", + two: "sub.foo", + expected: true, + }, + "subdomain two": { + one: "sub.foo", + two: "*.foo", + expected: true, + }, + "exact match": { + one: "foo", + two: "foo", + expected: true, + }, + "no match": { + one: "foo", + two: "bar", + expected: false, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, hostnamesMatch(tt.one, tt.two)) + }) + } +} + +func TestRouteKindIsAllowedForListener(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + kinds []gwv1beta1.RouteGroupKind + gk schema.GroupKind + expected bool + }{ + "empty kinds": { + kinds: nil, + gk: schema.GroupKind{Group: "a", Kind: "b"}, + expected: true, + }, + "group specified": { + kinds: []gwv1beta1.RouteGroupKind{ + {Group: pointerTo[gwv1beta1.Group]("a"), Kind: "b"}, + }, + gk: schema.GroupKind{Group: "a", Kind: "b"}, + expected: true, + }, + "group unspecified": { + kinds: []gwv1beta1.RouteGroupKind{ + {Kind: "b"}, + }, + gk: schema.GroupKind{Group: "a", Kind: "b"}, + expected: true, + }, + "kind mismatch": { + kinds: []gwv1beta1.RouteGroupKind{ + {Kind: "b"}, + }, + gk: schema.GroupKind{Group: "a", Kind: "c"}, + expected: false, + }, + "group mismatch": { + kinds: []gwv1beta1.RouteGroupKind{ + {Group: pointerTo[gwv1beta1.Group]("a"), Kind: "b"}, + }, + gk: schema.GroupKind{Group: "d", Kind: "b"}, + expected: false, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tt.expected, routeKindIsAllowedForListener(tt.kinds, tt.gk)) + }) + } +} diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go new file mode 100644 index 0000000000..7b66067123 --- /dev/null +++ b/control-plane/api-gateway/cache/consul.go @@ -0,0 +1,897 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cache + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "github.com/go-logr/logr" + "github.com/google/go-cmp/cmp" + "golang.org/x/exp/slices" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul/api" +) + +const ( + namespaceWildcard = "*" + apiTimeout = 5 * time.Minute +) + +var Kinds = []string{api.APIGateway, api.HTTPRoute, api.TCPRoute, api.InlineCertificate} + +type Config struct { + ConsulClientConfig *consul.Config + ConsulServerConnMgr consul.ServerConnectionManager + NamespacesEnabled bool + PeeringEnabled bool + Logger logr.Logger +} + +type resourceCache map[api.ResourceReference]api.ConfigEntry + +func (oldCache resourceCache) diff(newCache resourceCache) []api.ConfigEntry { + diffs := make([]api.ConfigEntry, 0) + + for ref, entry := range newCache { + oldRef, ok := oldCache[ref] + // ref from the new cache doesn't exist in the old one + // this means a resource was added + if !ok { + diffs = append(diffs, entry) + continue + } + + // the entry in the old cache has an older modify index than the ref + // from the new cache + if oldRef.GetModifyIndex() < entry.GetModifyIndex() { + diffs = append(diffs, entry) + } + } + + // get all deleted entries, these are entries present in the old cache + // that are not present in the new + for ref, entry := range oldCache { + if _, ok := newCache[ref]; !ok { + diffs = append(diffs, entry) + } + } + + return diffs +} + +type serviceCache map[api.ResourceReference]*api.CatalogService + +func (oldCache serviceCache) diff(newCache serviceCache) []*api.CatalogService { + diffs := make([]*api.CatalogService, 0) + + for ref, entry := range newCache { + oldRef, ok := oldCache[ref] + // ref from the new cache doesn't exist in the old one + // this means a resource was added + if !ok { + diffs = append(diffs, entry) + continue + } + + // the entry in the old cache has an older modify index than the ref + // from the new cache + if oldRef.ModifyIndex < entry.ModifyIndex { + diffs = append(diffs, entry) + } + } + + // get all deleted entries, these are entries present in the old cache + // that are not present in the new + for ref, entry := range oldCache { + if _, ok := newCache[ref]; !ok { + diffs = append(diffs, entry) + } + } + return diffs +} + +type peeringCache map[api.ResourceReference]*api.Peering + +func (oldCache peeringCache) diff(newCache peeringCache) []*api.Peering { + diffs := make([]*api.Peering, 0) + + for ref, entry := range newCache { + oldRef, ok := oldCache[ref] + // ref from the new cache doesn't exist in the old one + // this means a resource was added + if !ok { + diffs = append(diffs, entry) + continue + } + + // the entry in the old cache has an older modify index than the ref + // from the new cache + if oldRef.ModifyIndex < entry.ModifyIndex { + diffs = append(diffs, entry) + } + } + + // get all deleted entries, these are entries present in the old cache + // that are not present in the new + for ref, entry := range oldCache { + if _, ok := newCache[ref]; !ok { + diffs = append(diffs, entry) + } + } + return diffs +} + +// configEntryObject is used for generic k8s events so we maintain the consul name/namespace. +type configEntryObject struct { + client.Object // embed so we fufill the object interface + + Namespace string + Name string +} + +func (c *configEntryObject) GetNamespace() string { + return c.Namespace +} + +func (c *configEntryObject) GetName() string { + return c.Name +} + +func newConfigEntryObject(namespacedName types.NamespacedName) *configEntryObject { + return &configEntryObject{ + Namespace: namespacedName.Namespace, + Name: namespacedName.Name, + } +} + +// Subscription represents a watcher for events on a specific kind. +type Subscription struct { + translator translation.TranslatorFn + ctx context.Context + cancelCtx context.CancelFunc + events chan event.GenericEvent +} + +func (s *Subscription) Cancel() { + s.cancelCtx() +} + +func (s *Subscription) Events() chan event.GenericEvent { + return s.events +} + +type ServiceTranslatorFn func(*api.CatalogService) []types.NamespacedName + +// ServiceSubscription represents a watcher for events on a specific kind. +type ServiceSubscription struct { + translator ServiceTranslatorFn + ctx context.Context + cancelCtx context.CancelFunc + events chan event.GenericEvent +} + +func (s *ServiceSubscription) Cancel() { + s.cancelCtx() +} + +func (s *ServiceSubscription) Events() chan event.GenericEvent { + return s.events +} + +type PeeringTranslatorFn func(*api.Peering) []types.NamespacedName + +// PeeringsSubscription represents a watcher for events on a specific kind. +type PeeringsSubscription struct { + translator PeeringTranslatorFn + ctx context.Context + cancelCtx context.CancelFunc + events chan event.GenericEvent +} + +func (s *PeeringsSubscription) Cancel() { + s.cancelCtx() +} + +func (s *PeeringsSubscription) Events() chan event.GenericEvent { + return s.events +} + +// Cache subscribes to and caches Consul objects, it also responsible for mainting subscriptions to +// resources that it caches. +type Cache struct { + config *consul.Config + serverMgr consul.ServerConnectionManager + logger logr.Logger + + cache map[string]resourceCache + serviceCache serviceCache + peeringCache peeringCache + cacheMutex *sync.Mutex + + subscribers map[string][]*Subscription + serviceSubscribers []*ServiceSubscription + peeringsSubscribers []*PeeringsSubscription + subscriberMutex *sync.Mutex + + namespacesEnabled bool + peeringsEnabled bool + + synced chan struct{} + + kinds []string +} + +func New(config Config) *Cache { + cache := make(map[string]resourceCache, len(Kinds)) + for _, kind := range Kinds { + cache[kind] = make(resourceCache) + } + config.ConsulClientConfig.APITimeout = apiTimeout + + return &Cache{ + config: config.ConsulClientConfig, + serverMgr: config.ConsulServerConnMgr, + namespacesEnabled: config.NamespacesEnabled, + peeringsEnabled: config.PeeringEnabled, + cache: cache, + serviceCache: make(serviceCache), + peeringCache: make(peeringCache), + cacheMutex: &sync.Mutex{}, + subscribers: make(map[string][]*Subscription), + serviceSubscribers: make([]*ServiceSubscription, 0), + peeringsSubscribers: make([]*PeeringsSubscription, 0), + subscriberMutex: &sync.Mutex{}, + kinds: Kinds, + // we make a buffered channel that is the length of the kinds which + // are subscribed to + 2 for services + peerings + synced: make(chan struct{}, len(Kinds)+2), + logger: config.Logger, + } +} + +// WaitSynced is used to coordinate with the caller when the cache is initially filled. +func (c *Cache) WaitSynced(ctx context.Context) { + for range c.kinds { + select { + case <-c.synced: + case <-ctx.Done(): + return + } + } + // one more for service subscribers + select { + case <-c.synced: + case <-ctx.Done(): + return + } + if c.peeringsEnabled { + // and one more for peerings subscribers + select { + case <-c.synced: + case <-ctx.Done(): + return + } + } +} + +// Subscribe handles adding a new subscription for resources of a given kind. +func (c *Cache) Subscribe(ctx context.Context, kind string, translator translation.TranslatorFn) *Subscription { + c.subscriberMutex.Lock() + defer c.subscriberMutex.Unlock() + + // check that kind is registered with cache + if !slices.Contains(c.kinds, kind) { + return &Subscription{} + } + + subscribers, ok := c.subscribers[kind] + if !ok { + subscribers = []*Subscription{} + } + + ctx, cancel := context.WithCancel(ctx) + events := make(chan event.GenericEvent) + sub := &Subscription{ + translator: translator, + ctx: ctx, + cancelCtx: cancel, + events: events, + } + + subscribers = append(subscribers, sub) + + c.subscribers[kind] = subscribers + + return sub +} + +// SubscribeServices handles adding a new subscription for resources of a given kind. +func (c *Cache) SubscribeServices(ctx context.Context, translator ServiceTranslatorFn) *ServiceSubscription { + c.subscriberMutex.Lock() + defer c.subscriberMutex.Unlock() + + ctx, cancel := context.WithCancel(ctx) + events := make(chan event.GenericEvent) + sub := &ServiceSubscription{ + translator: translator, + ctx: ctx, + cancelCtx: cancel, + events: events, + } + + c.serviceSubscribers = append(c.serviceSubscribers, sub) + return sub +} + +// SubscribeServices handles adding a new subscription for resources of a given kind. +func (c *Cache) SubscribePeerings(ctx context.Context, translator PeeringTranslatorFn) *PeeringsSubscription { + c.subscriberMutex.Lock() + defer c.subscriberMutex.Unlock() + + ctx, cancel := context.WithCancel(ctx) + events := make(chan event.GenericEvent) + sub := &PeeringsSubscription{ + translator: translator, + ctx: ctx, + cancelCtx: cancel, + events: events, + } + + c.peeringsSubscribers = append(c.peeringsSubscribers, sub) + return sub +} + +// Run starts the cache watch cycle, on the first call it will fill the cache with existing resources. +func (c *Cache) Run(ctx context.Context) { + wg := &sync.WaitGroup{} + + for i := range c.kinds { + kind := c.kinds[i] + + wg.Add(1) + go func() { + defer wg.Done() + c.subscribeToConsul(ctx, kind) + }() + } + + wg.Add(1) + go func() { + defer wg.Done() + c.subscribeToConsulServices(ctx) + }() + + if c.peeringsEnabled { + wg.Add(1) + go func() { + defer wg.Done() + c.subscribeToConsulPeerings(ctx) + }() + } + + wg.Wait() +} + +func (c *Cache) subscribeToConsul(ctx context.Context, kind string) { + once := &sync.Once{} + + opts := &api.QueryOptions{} + if c.namespacesEnabled { + opts.Namespace = namespaceWildcard + } + + for { + select { + case <-ctx.Done(): + return + default: + } + + client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) + if err != nil { + c.logger.Error(err, "error initializing consul client") + continue + } + + entries, meta, err := client.ConfigEntries().List(kind, opts.WithContext(ctx)) + if err != nil { + c.logger.Error(err, fmt.Sprintf("error fetching config entries for kind: %s", kind)) + continue + } + + opts.WaitIndex = meta.LastIndex + + c.updateAndNotify(ctx, once, kind, entries) + + select { + case <-ctx.Done(): + return + default: + continue + } + } +} + +func (c *Cache) subscribeToConsulServices(ctx context.Context) { + once := &sync.Once{} + + opts := &api.QueryOptions{Connect: true} + + // we need a second set of opts to make sure we don't + // block on the secondary list operations + serviceListOpts := &api.QueryOptions{Connect: true} + +MAIN_LOOP: + for { + select { + case <-ctx.Done(): + return + default: + } + + client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) + if err != nil { + c.logger.Error(err, "error initializing consul client") + continue + } + + services, meta, err := client.Catalog().Services(opts.WithContext(ctx)) + if err != nil { + c.logger.Error(err, "error fetching services") + continue + } + + flattened := []*api.CatalogService{} + for service := range services { + serviceList, _, err := client.Catalog().Service(service, "", serviceListOpts.WithContext(ctx)) + if err != nil { + c.logger.Error(err, fmt.Sprintf("error fetching service: %s", service)) + continue MAIN_LOOP + } + flattened = append(flattened, serviceList...) + } + + opts.WaitIndex = meta.LastIndex + c.updateAndNotifyServices(ctx, once, flattened) + + select { + case <-ctx.Done(): + return + default: + continue + } + } +} + +func (c *Cache) subscribeToConsulPeerings(ctx context.Context) { + once := &sync.Once{} + + opts := &api.QueryOptions{Connect: true} + if c.namespacesEnabled { + opts.Namespace = namespaceWildcard + } + + for { + select { + case <-ctx.Done(): + return + default: + } + + client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) + if err != nil { + c.logger.Error(err, "error initializing consul client") + continue + } + + peerings, meta, err := client.Peerings().List(ctx, opts.WithContext(ctx)) + if err != nil { + c.logger.Error(err, "error fetching services") + continue + } + + opts.WaitIndex = meta.LastIndex + c.updateAndNotifyPeerings(ctx, once, peerings) + + select { + case <-ctx.Done(): + return + default: + continue + } + } +} + +func (c *Cache) updateAndNotify(ctx context.Context, once *sync.Once, kind string, entries []api.ConfigEntry) { + c.cacheMutex.Lock() + + cache := make(resourceCache) + + for _, entry := range entries { + cache[translation.EntryToReference(entry)] = entry + } + + diffs := c.cache[kind].diff(cache) + + c.cache[kind] = cache + + // we run this the first time the cache is filled to notify the waiter + once.Do(func() { + c.logger.Info("sync mark for " + kind) + c.synced <- struct{}{} + }) + + c.cacheMutex.Unlock() + + // now notify all subscribers + c.notifySubscribers(ctx, kind, diffs) +} + +func (c *Cache) updateAndNotifyServices(ctx context.Context, once *sync.Once, services []*api.CatalogService) { + c.cacheMutex.Lock() + + cache := make(serviceCache) + + for _, service := range services { + cache[api.ResourceReference{Name: service.ServiceName, Namespace: service.Namespace, Partition: service.Partition}] = service + } + + diffs := c.serviceCache.diff(cache) + + c.serviceCache = cache + + // we run this the first time the cache is filled to notify the waiter + once.Do(func() { + c.logger.Info("sync mark for services") + c.synced <- struct{}{} + }) + + c.cacheMutex.Unlock() + + // now notify all subscribers + c.notifyServiceSubscribers(ctx, diffs) +} + +func (c *Cache) updateAndNotifyPeerings(ctx context.Context, once *sync.Once, peerings []*api.Peering) { + c.cacheMutex.Lock() + + cache := make(peeringCache) + + for _, peering := range peerings { + cache[api.ResourceReference{Name: peering.Name, Namespace: peering.ID, Partition: peering.Partition}] = peering + } + + diffs := c.peeringCache.diff(cache) + + c.peeringCache = cache + + // we run this the first time the cache is filled to notify the waiter + once.Do(func() { + c.logger.Info("sync mark for peerings") + c.synced <- struct{}{} + }) + + c.cacheMutex.Unlock() + + // now notify all peering subscribers + c.notifyPeeringSubscribers(ctx, diffs) +} + +// notifyServiceSubscribers notifies each subscriber for a given kind on changes to a config entry of that kind. It also +// handles removing any subscribers that have marked themselves as done. +func (c *Cache) notifyServiceSubscribers(ctx context.Context, services []*api.CatalogService) { + c.subscriberMutex.Lock() + defer c.subscriberMutex.Unlock() + + for _, service := range services { + // this will hold the new list of current subscribers after we finish notifying + subscribers := make([]*ServiceSubscription, 0, len(c.serviceSubscribers)) + for _, subscriber := range c.serviceSubscribers { + addSubscriber := false + + for _, namespaceName := range subscriber.translator(service) { + event := event.GenericEvent{ + Object: newConfigEntryObject(namespaceName), + } + + select { + case <-ctx.Done(): + return + case <-subscriber.ctx.Done(): + // don't add this subscriber to current list because it is done + addSubscriber = false + case subscriber.events <- event: + // keep this one since we can send events to it + addSubscriber = true + } + } + + if addSubscriber { + subscribers = append(subscribers, subscriber) + } + } + c.serviceSubscribers = subscribers + } +} + +// notifyPeeringSubscribers notifies each subscriber for a given kind on changes to a config entry of that kind. It also +// handles removing any subscribers that have marked themselves as done. +func (c *Cache) notifyPeeringSubscribers(ctx context.Context, peerings []*api.Peering) { + c.subscriberMutex.Lock() + defer c.subscriberMutex.Unlock() + + for _, peering := range peerings { + // this will hold the new list of current subscribers after we finish notifying + subscribers := make([]*PeeringsSubscription, 0, len(c.peeringsSubscribers)) + for _, subscriber := range c.peeringsSubscribers { + addSubscriber := false + + for _, namespaceName := range subscriber.translator(peering) { + event := event.GenericEvent{ + Object: newConfigEntryObject(namespaceName), + } + + select { + case <-ctx.Done(): + return + case <-subscriber.ctx.Done(): + // don't add this subscriber to current list because it is done + addSubscriber = false + case subscriber.events <- event: + // keep this one since we can send events to it + addSubscriber = true + } + } + + if addSubscriber { + subscribers = append(subscribers, subscriber) + } + } + c.peeringsSubscribers = subscribers + } +} + +// notifySubscribers notifies each subscriber for a given kind on changes to a config entry of that kind. It also +// handles removing any subscribers that have marked themselves as done. +func (c *Cache) notifySubscribers(ctx context.Context, kind string, entries []api.ConfigEntry) { + c.subscriberMutex.Lock() + defer c.subscriberMutex.Unlock() + + for _, entry := range entries { + // this will hold the new list of current subscribers after we finish notifying + subscribers := make([]*Subscription, 0, len(c.subscribers[kind])) + for _, subscriber := range c.subscribers[kind] { + addSubscriber := false + + for _, namespaceName := range subscriber.translator(entry) { + event := event.GenericEvent{ + Object: newConfigEntryObject(namespaceName), + } + + select { + case <-ctx.Done(): + return + case <-subscriber.ctx.Done(): + // don't add this subscriber to current list because it is done + addSubscriber = false + case subscriber.events <- event: + // keep this one since we can send events to it + addSubscriber = true + } + } + + if addSubscriber { + subscribers = append(subscribers, subscriber) + } + } + c.subscribers[kind] = subscribers + } +} + +// Write handles writing the config entry back to Consul, if the current reference of the +// config entry is stale then it returns an error. +func (c *Cache) Write(ctx context.Context, entry api.ConfigEntry) error { + c.cacheMutex.Lock() + defer c.cacheMutex.Unlock() + + entryMap, ok := c.cache[entry.GetKind()] + if !ok { + return nil + } + + ref := translation.EntryToReference(entry) + + old, ok := entryMap[ref] + if ok { + if cmp.Equal(old, entry, cmp.FilterPath(func(p cmp.Path) bool { + path := p.String() + return strings.HasSuffix(path, "Status") || strings.HasSuffix(path, "ModifyIndex") || strings.HasSuffix(path, "CreateIndex") + }, cmp.Ignore())) { + return nil + } + } + + client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) + if err != nil { + return err + } + + options := &api.WriteOptions{} + + _, _, err = client.ConfigEntries().Set(entry, options.WithContext(ctx)) + if err != nil { + return err + } + + return nil +} + +// Get returns a config entry from the cache that corresponds to the given resource reference. +func (c *Cache) Get(ref api.ResourceReference) api.ConfigEntry { + c.cacheMutex.Lock() + defer c.cacheMutex.Unlock() + + entryMap, ok := c.cache[ref.Kind] + if !ok { + return nil + } + + entry, ok := entryMap[ref] + if !ok { + return nil + } + + return entry +} + +// Delete handles deleting the config entry from consul, if the current reference of the config entry is stale then +// it returns an error. +func (c *Cache) Delete(ctx context.Context, ref api.ResourceReference) error { + c.cacheMutex.Lock() + defer c.cacheMutex.Unlock() + + entryMap, ok := c.cache[ref.Kind] + if !ok { + return nil + } + _, ok = entryMap[ref] + if !ok { + c.logger.Info("cached object not found, not deleting") + return nil + } + + client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) + if err != nil { + return err + } + + options := &api.WriteOptions{} + + _, err = client.ConfigEntries().Delete(ref.Kind, ref.Name, options.WithContext(ctx)) + return err +} + +// List returns a list of config entries from the cache that corresponds to the given kind. +func (c *Cache) List(kind string) []api.ConfigEntry { + c.cacheMutex.Lock() + defer c.cacheMutex.Unlock() + + entryMap, ok := c.cache[kind] + if !ok { + return nil + } + entries := make([]api.ConfigEntry, 0, len(entryMap)) + for _, entry := range entryMap { + entries = append(entries, entry) + } + + return entries +} + +// ListServices returns a list of services from the cache that corresponds to the given kind. +func (c *Cache) ListServices() []api.CatalogService { + c.cacheMutex.Lock() + defer c.cacheMutex.Unlock() + + entries := make([]api.CatalogService, 0, len(c.serviceCache)) + for _, service := range c.serviceCache { + entries = append(entries, *service) + } + + return entries +} + +// LinkPolicy links a mesh write policy to a token associated with the service. +func (c *Cache) LinkPolicy(ctx context.Context, name, namespace string) error { + client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) + if err != nil { + return err + } + + options := &api.QueryOptions{} + + if c.namespacesEnabled { + options.Namespace = namespace + } + + policies, _, err := client.ACL().PolicyList(options.WithContext(ctx)) + if err != nil { + return ignoreACLsDisabled(err) + } + + links := []*api.ACLLink{} + for _, policy := range policies { + if strings.HasPrefix(policy.Name, "connect-inject-policy") { + links = append(links, &api.ACLLink{ + Name: policy.Name, + }) + } + } + + tokens, _, err := client.ACL().TokenList(options.WithContext(ctx)) + if err != nil { + return ignoreACLsDisabled(err) + } + + for _, token := range tokens { + for _, identity := range token.ServiceIdentities { + if identity.ServiceName == name { + token, _, err := client.ACL().TokenRead(token.AccessorID, options.WithContext(ctx)) + if err != nil { + return ignoreACLsDisabled(err) + } + token.Policies = links + + _, _, err = client.ACL().TokenUpdate(token, &api.WriteOptions{}) + if err != nil { + return ignoreACLsDisabled(err) + } + } + } + } + + return nil +} + +// Register registers a service in Consul. +func (c *Cache) Register(ctx context.Context, registration api.CatalogRegistration) error { + client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) + if err != nil { + return err + } + + options := &api.WriteOptions{} + + _, err = client.Catalog().Register(®istration, options.WithContext(ctx)) + return err +} + +// Deregister deregisters a service in Consul. +func (c *Cache) Deregister(ctx context.Context, deregistration api.CatalogDeregistration) error { + client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) + if err != nil { + return err + } + + options := &api.WriteOptions{} + + _, err = client.Catalog().Deregister(&deregistration, options.WithContext(ctx)) + return err +} + +func ignoreACLsDisabled(err error) error { + if err.Error() == "Unexpected response code: 401 (ACL support disabled)" { + return nil + } + return err +} diff --git a/control-plane/api-gateway/cache/consul_test.go b/control-plane/api-gateway/cache/consul_test.go new file mode 100644 index 0000000000..256aa6b31d --- /dev/null +++ b/control-plane/api-gateway/cache/consul_test.go @@ -0,0 +1,2028 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cache + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/go-logr/logr" + logrtest "github.com/go-logr/logr/testing" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/event" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul/api" +) + +func Test_resourceCache_diff(t *testing.T) { + t.Parallel() + type args struct { + newCache resourceCache + } + tests := []struct { + name string + oldCache resourceCache + args args + want []api.ConfigEntry + }{ + { + name: "no difference", + oldCache: resourceCache{ + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + args: args{ + newCache: resourceCache{ + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + }, + want: []api.ConfigEntry{}, + }, + { + name: "resource exists in old cache but not new one", + oldCache: resourceCache{ + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route 2", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route 2", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-2", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + args: args{ + newCache: resourceCache{ + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + }, + want: []api.ConfigEntry{ + api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route 2", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-2", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + }, + { + name: "resource exists in new cache but not old one", + oldCache: resourceCache{ + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + args: args{ + newCache: resourceCache{ + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route 2", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route 2", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-2", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + }, + want: []api.ConfigEntry{ + api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route 2", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-2", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + }, + { + name: "same ref new cache has a greater modify index", + oldCache: resourceCache{ + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + ModifyIndex: 1, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + args: args{ + newCache: resourceCache{ + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "my route", + }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + ModifyIndex: 10, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + }, + want: []api.ConfigEntry{ + api.ConfigEntry(&api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + ModifyIndex: 10, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + }), + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := tt.oldCache.diff(tt.args.newCache) + if diff := cmp.Diff(got, tt.want); diff != "" { + t.Errorf("resourceCache.diff mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestCache_Subscribe(t *testing.T) { + t.Parallel() + type args struct { + ctx context.Context + kind string + translator translation.TranslatorFn + } + tests := []struct { + name string + args args + subscribers map[string][]*Subscription + subscriberChange int + }{ + { + name: "new subscription added when there are no other subscribers of the same kind", + args: args{ + ctx: context.Background(), + kind: api.HTTPRoute, + translator: func(api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{} + }, + }, + subscriberChange: 1, + }, + { + name: "new subscription added when there are existing subscribers of the same kind", + args: args{ + ctx: context.Background(), + kind: api.HTTPRoute, + translator: func(api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{} + }, + }, + subscribers: map[string][]*Subscription{ + api.HTTPRoute: { + { + translator: func(api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{} + }, + ctx: context.Background(), + cancelCtx: func() { + }, + events: make(chan event.GenericEvent), + }, + }, + }, + subscriberChange: 1, + }, + { + name: "subscription for kind that does not exist does not change any subscriber counts", + args: args{ + ctx: context.Background(), + kind: "UnknownKind", + translator: func(api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{} + }, + }, + subscriberChange: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New(Config{ + ConsulClientConfig: &consul.Config{ + APIClientConfig: &api.Config{}, + HTTPPort: 0, + GRPCPort: 0, + APITimeout: 0, + }, + ConsulServerConnMgr: consul.NewMockServerConnectionManager(t), + NamespacesEnabled: false, + Logger: logr.Logger{}, + }) + + if len(tt.subscribers) > 0 { + c.subscribers = tt.subscribers + } + + kindSubscriberCounts := make(map[string]int) + for kind, subscribers := range c.subscribers { + kindSubscriberCounts[kind] = len(subscribers) + } + + c.Subscribe(tt.args.ctx, tt.args.kind, tt.args.translator) + + for kind, subscribers := range c.subscribers { + expectedSubscriberCount := kindSubscriberCounts[kind] + if kind == tt.args.kind { + expectedSubscriberCount += tt.subscriberChange + } + actualSubscriberCount := len(subscribers) + + if expectedSubscriberCount != actualSubscriberCount { + t.Errorf("Expected there to be %d subscribers, there were %d", expectedSubscriberCount, actualSubscriberCount) + } + } + }) + } +} + +func TestCache_Write(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + responseFn func(w http.ResponseWriter) + expectedErr error + }{ + { + name: "write is successful", + responseFn: func(w http.ResponseWriter) { + w.WriteHeader(200) + fmt.Fprintln(w, `{updated: true}`) + }, + expectedErr: nil, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v1/config": + tt.responseFn(w) + case "/v1/catalog/services": + fmt.Fprintln(w, `{}`) + default: + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) + } + })) + defer consulServer.Close() + + serverURL, err := url.Parse(consulServer.URL) + require.NoError(t, err) + + port, err := strconv.Atoi(serverURL.Port()) + require.NoError(t, err) + + c := New(Config{ + ConsulClientConfig: &consul.Config{ + APIClientConfig: &api.Config{}, + HTTPPort: port, + GRPCPort: port, + APITimeout: 0, + }, + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + NamespacesEnabled: false, + Logger: logr.Logger{}, + }) + + entry := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{}, + Status: api.ConfigEntryStatus{}, + } + + err = c.Write(context.Background(), entry) + require.Equal(t, err, tt.expectedErr) + }) + } +} + +func TestCache_Get(t *testing.T) { + t.Parallel() + type args struct { + ref api.ResourceReference + } + tests := []struct { + name string + args args + want api.ConfigEntry + cache map[string]resourceCache + }{ + { + name: "entry exists", + args: args{ + ref: api.ResourceReference{ + Kind: api.APIGateway, + Name: "api-gw", + }, + }, + want: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{}, + }, + cache: map[string]resourceCache{ + api.APIGateway: { + api.ResourceReference{ + Kind: api.APIGateway, + Name: "api-gw", + }: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{}, + }, + api.ResourceReference{ + Kind: api.APIGateway, + Name: "api-gw-2", + }: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-2", + Meta: map[string]string{}, + }, + }, + }, + }, + { + name: "entry does not exist", + args: args{ + ref: api.ResourceReference{ + Kind: api.APIGateway, + Name: "api-gw-4", + }, + }, + want: nil, + cache: map[string]resourceCache{ + api.APIGateway: { + api.ResourceReference{ + Kind: api.APIGateway, + Name: "api-gw", + }: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{}, + }, + api.ResourceReference{ + Kind: api.APIGateway, + Name: "api-gw-2", + }: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-2", + Meta: map[string]string{}, + }, + }, + }, + }, + { + name: "kind key does not exist", + args: args{ + ref: api.ResourceReference{ + Kind: api.APIGateway, + Name: "api-gw-4", + }, + }, + want: nil, + cache: map[string]resourceCache{ + api.HTTPRoute: { + api.ResourceReference{ + Kind: api.HTTPRoute, + Name: "api-gw", + }: &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "route", + Meta: map[string]string{}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New(Config{ + ConsulClientConfig: &consul.Config{ + APIClientConfig: &api.Config{}, + }, + }) + c.cache = tt.cache + + got := c.Get(tt.args.ref) + + if diff := cmp.Diff(got, tt.want); diff != "" { + t.Errorf("Cache.Get mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func Test_Run(t *testing.T) { + t.Parallel() + // setup httproutes + httpRouteOne, httpRouteTwo := setupHTTPRoutes() + httpRoutes := []*api.HTTPRouteConfigEntry{httpRouteOne, httpRouteTwo} + + // setup gateway + gw := setupGateway() + gateways := []*api.APIGatewayConfigEntry{gw} + + // setup TCPRoutes + tcpRoute := setupTCPRoute() + tcpRoutes := []*api.TCPRouteConfigEntry{tcpRoute} + + // setup inline certs + inlineCert := setupInlineCertificate() + certs := []*api.InlineCertificateConfigEntry{inlineCert} + + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v1/config/http-route": + val, err := json.Marshal(httpRoutes) + if err != nil { + w.WriteHeader(500) + fmt.Fprintln(w, err) + return + } + fmt.Fprintln(w, string(val)) + case "/v1/config/api-gateway": + val, err := json.Marshal(gateways) + if err != nil { + w.WriteHeader(500) + fmt.Fprintln(w, err) + return + } + fmt.Fprintln(w, string(val)) + case "/v1/config/tcp-route": + val, err := json.Marshal(tcpRoutes) + if err != nil { + w.WriteHeader(500) + fmt.Fprintln(w, err) + return + } + fmt.Fprintln(w, string(val)) + case "/v1/config/inline-certificate": + val, err := json.Marshal(certs) + if err != nil { + w.WriteHeader(500) + fmt.Fprintln(w, err) + return + } + fmt.Fprintln(w, string(val)) + case "/v1/catalog/services": + fmt.Fprintln(w, `{}`) + case "/v1/peerings": + fmt.Fprintln(w, `[]`) + default: + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) + } + })) + defer consulServer.Close() + + serverURL, err := url.Parse(consulServer.URL) + require.NoError(t, err) + + port, err := strconv.Atoi(serverURL.Port()) + require.NoError(t, err) + + c := New(Config{ + ConsulClientConfig: &consul.Config{ + APIClientConfig: &api.Config{}, + HTTPPort: port, + GRPCPort: port, + APITimeout: 0, + }, + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + NamespacesEnabled: false, + Logger: logrtest.NewTestLogger(t), + }) + prevCache := make(map[string]resourceCache) + for kind, cache := range c.cache { + resCache := make(resourceCache) + for resourceRef, entry := range cache { + resCache[resourceRef] = entry + } + prevCache[kind] = resCache + } + + expectedCache := map[string]resourceCache{ + api.APIGateway: { + {Kind: api.APIGateway, Name: gw.Name}: gw, + }, + api.TCPRoute: { + {Kind: api.TCPRoute, Name: tcpRoute.Name}: tcpRoute, + }, + api.HTTPRoute: { + {Kind: api.HTTPRoute, Name: httpRouteOne.Name}: httpRouteOne, + {Kind: api.HTTPRoute, Name: httpRouteTwo.Name}: httpRouteTwo, + }, + api.InlineCertificate: { + {Kind: api.InlineCertificate, Name: inlineCert.Name}: inlineCert, + }, + } + + ctx, cancelFn := context.WithCancel(context.Background()) + + httpRouteOneNsn := types.NamespacedName{ + Name: httpRouteOne.Name, + Namespace: httpRouteOne.Namespace, + } + + httpRouteTwoNsn := types.NamespacedName{ + Name: httpRouteTwo.Name, + Namespace: httpRouteTwo.Namespace, + } + + httpRouteSubscriber := c.Subscribe(ctx, api.HTTPRoute, func(cfe api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{ + {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, + } + }) + + canceledSub := c.Subscribe(ctx, api.HTTPRoute, func(cfe api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{ + {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, + } + }) + + gwNsn := types.NamespacedName{ + Name: gw.Name, + Namespace: gw.Namespace, + } + + gwSubscriber := c.Subscribe(ctx, api.APIGateway, func(cfe api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{ + {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, + } + }) + + tcpRouteNsn := types.NamespacedName{ + Name: tcpRoute.Name, + Namespace: tcpRoute.Namespace, + } + + tcpRouteSubscriber := c.Subscribe(ctx, api.TCPRoute, func(cfe api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{ + {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, + } + }) + + certNsn := types.NamespacedName{ + Name: inlineCert.Name, + Namespace: inlineCert.Namespace, + } + + certSubscriber := c.Subscribe(ctx, api.InlineCertificate, func(cfe api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{ + {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, + } + }) + + c.SubscribeServices(ctx, func(cs *api.CatalogService) []types.NamespacedName { return nil }).Cancel() + c.SubscribePeerings(ctx, func(cs *api.Peering) []types.NamespacedName { return nil }).Cancel() + + // mark this subscription as ended + canceledSub.Cancel() + + go c.Run(ctx) + + // Check subscribers + httpRouteExpectedEvents := []event.GenericEvent{{Object: newConfigEntryObject(httpRouteOneNsn)}, {Object: newConfigEntryObject(httpRouteTwoNsn)}} + gwExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(gwNsn)} + tcpExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(tcpRouteNsn)} + certExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(certNsn)} + + // 2 http routes + 1 gw + 1 tcp route + 1 cert = 5 + i := 5 + for { + if i == 0 { + break + } + select { + case actualHTTPRouteEvent := <-httpRouteSubscriber.Events(): + require.Contains(t, httpRouteExpectedEvents, actualHTTPRouteEvent) + case actualGWEvent := <-gwSubscriber.Events(): + require.Equal(t, gwExpectedEvent, actualGWEvent) + case actualTCPRouteEvent := <-tcpRouteSubscriber.Events(): + require.Equal(t, tcpExpectedEvent, actualTCPRouteEvent) + case actualCertExpectedEvent := <-certSubscriber.Events(): + require.Equal(t, certExpectedEvent, actualCertExpectedEvent) + } + i -= 1 + } + + // the canceled Subscription should not receive any events + require.Zero(t, len(canceledSub.Events())) + c.WaitSynced(ctx) + + // cancel the context so the Run function exits + cancelFn() + + // Check cache + // expect the cache to have changed + if diff := cmp.Diff(prevCache, c.cache); diff == "" { + t.Error("Expect cache to have changed but it did not") + } + + if diff := cmp.Diff(expectedCache, c.cache); diff != "" { + t.Errorf("Cache.cache mismatch (-want +got):\n%s", diff) + } +} + +func setupHTTPRoutes() (*api.HTTPRouteConfigEntry, *api.HTTPRouteConfigEntry) { + routeOne := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{ + "metaKey": "metaVal", + }, + Status: api.ConfigEntryStatus{}, + } + routeTwo := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "my route 2", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener-2", + Namespace: "ns", + }, + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{Path: "v1"}, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &api.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{"hostname.com"}, + Meta: map[string]string{ + "metakey": "meta val", + }, + } + return routeOne, routeTwo +} + +func setupGateway() *api.APIGatewayConfigEntry { + return &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{ + "metakey": "meta val", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "listener one", + Hostname: "hostname.com", + Port: 3350, + Protocol: "https", + TLS: api.APIGatewayTLSConfiguration{}, + }, + }, + } +} + +func setupTCPRoute() *api.TCPRouteConfigEntry { + return &api.TCPRouteConfigEntry{ + Kind: api.TCPRoute, + Name: "tcp route", + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw", + SectionName: "listener two", + }, + }, + Services: []api.TCPService{ + { + Name: "tcp service", + }, + }, + Meta: map[string]string{ + "metakey": "meta val", + }, + Status: api.ConfigEntryStatus{}, + } +} + +func setupInlineCertificate() *api.InlineCertificateConfigEntry { + return &api.InlineCertificateConfigEntry{ + Kind: api.InlineCertificate, + Name: "inline-cert", + Certificate: "cert", + PrivateKey: "super secret", + Meta: map[string]string{ + "metaKey": "meta val", + }, + } +} + +func TestCache_Delete(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + responseFn func(w http.ResponseWriter) + expectedErr error + }{ + { + name: "delete is successful", + responseFn: func(w http.ResponseWriter) { + w.WriteHeader(200) + fmt.Fprintln(w, `{deleted: true}`) + }, + expectedErr: nil, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + ref := api.ResourceReference{ + Name: "my-route", + Kind: api.HTTPRoute, + } + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case fmt.Sprintf("/v1/config/%s/%s", ref.Kind, ref.Name): + tt.responseFn(w) + default: + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) + } + })) + defer consulServer.Close() + + serverURL, err := url.Parse(consulServer.URL) + require.NoError(t, err) + + port, err := strconv.Atoi(serverURL.Port()) + require.NoError(t, err) + + c := New(Config{ + ConsulClientConfig: &consul.Config{ + APIClientConfig: &api.Config{}, + HTTPPort: port, + GRPCPort: port, + APITimeout: 0, + }, + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + NamespacesEnabled: false, + Logger: logrtest.NewTestLogger(t), + }) + + err = c.Delete(context.Background(), ref) + require.ErrorIs(t, err, tt.expectedErr) + }) + } +} diff --git a/control-plane/api-gateway/controllers/finalizer.go b/control-plane/api-gateway/controllers/finalizer.go new file mode 100644 index 0000000000..c12f5f29e7 --- /dev/null +++ b/control-plane/api-gateway/controllers/finalizer.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// EnsureFinalizer ensures that the given object has the given finalizer. +func EnsureFinalizer(ctx context.Context, client client.Client, object client.Object, finalizer string) (didUpdate bool, err error) { + finalizers := object.GetFinalizers() + for _, f := range finalizers { + if f == finalizer { + return false, nil + } + } + object.SetFinalizers(append(finalizers, finalizer)) + if err := client.Update(ctx, object); err != nil { + return false, err + } + + return true, nil +} + +// RemoveFinalizer removes the given finalizer from the given object. +func RemoveFinalizer(ctx context.Context, client client.Client, object client.Object, finalizer string) (didUpdate bool, err error) { + finalizers := object.GetFinalizers() + + for i, f := range finalizers { + if f == finalizer { + finalizers = append(finalizers[:i], finalizers[i+1:]...) + object.SetFinalizers(finalizers) + if err := client.Update(ctx, object); err != nil { + return false, err + } + return true, nil + } + } + + return false, nil +} diff --git a/control-plane/api-gateway/controllers/finalizer_test.go b/control-plane/api-gateway/controllers/finalizer_test.go new file mode 100644 index 0000000000..dc265ef6ca --- /dev/null +++ b/control-plane/api-gateway/controllers/finalizer_test.go @@ -0,0 +1,84 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestEnsureFinalizer(t *testing.T) { + t.Parallel() + + finalizer := "test-finalizer" + + cases := map[string]struct { + initialFinalizers []string + finalizerToAdd string + expectedDidUpdate bool + }{ + "should update": {[]string{}, finalizer, true}, + "should not update": {[]string{finalizer}, finalizer, false}, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + // It doesn't matter what the object is, as long as it implements client.Object. + // A Pod was as good as any other object here. + testObj := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-obj", + Finalizers: tc.initialFinalizers, + }, + } + + client := fake.NewClientBuilder().WithObjects(testObj).Build() + + didUpdate, err := EnsureFinalizer(context.Background(), client, testObj, tc.finalizerToAdd) + + require.NoError(t, err) + require.Equal(t, tc.expectedDidUpdate, didUpdate) + }) + } +} + +func TestRemoveFinalizer(t *testing.T) { + t.Parallel() + + finalizer := "test-finalizer" + + cases := map[string]struct { + initialFinalizers []string + finalizerToRemove string + expectedDidUpdate bool + }{ + "should update": {[]string{finalizer}, finalizer, true}, + "should not update": {[]string{}, finalizer, false}, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + // It doesn't matter what the object is, as long as it implements client.Object. + // A Pod was as good as any other object here. + testObj := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-obj", + Finalizers: tc.initialFinalizers, + }, + } + + client := fake.NewClientBuilder().WithObjects(testObj).Build() + + didUpdate, err := RemoveFinalizer(context.Background(), client, testObj, tc.finalizerToRemove) + + require.NoError(t, err) + require.Equal(t, tc.expectedDidUpdate, didUpdate) + }) + } +} diff --git a/control-plane/api-gateway/controllers/gateway_class_config_controller.go b/control-plane/api-gateway/controllers/gateway_class_config_controller.go new file mode 100644 index 0000000000..3889778348 --- /dev/null +++ b/control-plane/api-gateway/controllers/gateway_class_config_controller.go @@ -0,0 +1,130 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "time" + + "github.com/go-logr/logr" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +const ( + gatewayClassConfigFinalizer = "gateway-class-exists-finalizer.consul.hashicorp.com" +) + +// The GatewayClassConfigController manages the state of GatewayClassConfigs. +type GatewayClassConfigController struct { + client.Client + + Log logr.Logger +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile +func (r *GatewayClassConfigController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("gatewayClassConfig", req.NamespacedName.Name) + log.Info("Reconciling GatewayClassConfig ") + + gcc := &v1alpha1.GatewayClassConfig{} + if err := r.Client.Get(ctx, req.NamespacedName, gcc); err != nil { + if k8serrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + r.Log.Error(err, "failed to get gateway class config") + return ctrl.Result{}, err + } + + if !gcc.ObjectMeta.DeletionTimestamp.IsZero() { + // We have a deletion, ensure we're not in use. + used, err := gatewayClassConfigInUse(ctx, r.Client, gcc) + if err != nil { + r.Log.Error(err, "failed to check if the gateway class config is still in use") + return ctrl.Result{}, err + } + if used { + r.Log.Info("gateway class config still in use") + // Requeue as to not block the reconciliation loop. + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + // gcc is no longer in use. + if _, err := RemoveFinalizer(ctx, r.Client, gcc, gatewayClassConfigFinalizer); err != nil { + r.Log.Error(err, "error removing gateway class config finalizer") + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + + if _, err := EnsureFinalizer(ctx, r.Client, gcc, gatewayClassConfigFinalizer); err != nil { + r.Log.Error(err, "error adding gateway class config finalizer") + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// gatewayClassUsesConfig determines whether a given GatewayClass references a +// given GatewayClassConfig. Since these resources are scoped to the cluster, +// namespace is not considered. +func gatewayClassUsesConfig(gc gwv1beta1.GatewayClass, gcc *v1alpha1.GatewayClassConfig) bool { + parameterRef := gc.Spec.ParametersRef + return parameterRef != nil && + string(parameterRef.Group) == v1alpha1.ConsulHashicorpGroup && + parameterRef.Kind == v1alpha1.GatewayClassConfigKind && + parameterRef.Name == gcc.Name +} + +// GatewayClassConfigInUse determines whether any GatewayClass in the cluster +// references the provided GatewayClassConfig. +func gatewayClassConfigInUse(ctx context.Context, k8sClient client.Client, gcc *v1alpha1.GatewayClassConfig) (bool, error) { + list := &gwv1beta1.GatewayClassList{} + if err := k8sClient.List(ctx, list); err != nil { + return false, err + } + + for _, gc := range list.Items { + if gatewayClassUsesConfig(gc, gcc) { + return true, nil + } + } + + return false, nil +} + +func (r *GatewayClassConfigController) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.GatewayClassConfig{}). + // Watch for changes to GatewayClass objects associated with this config for purposes of finalizer removal. + Watches(source.NewKindWithCache(&gwv1beta1.GatewayClass{}, mgr.GetCache()), r.transformGatewayClassToGatewayClassConfig(ctx)). + Complete(r) +} + +func (r *GatewayClassConfigController) transformGatewayClassToGatewayClassConfig(ctx context.Context) handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { + gc := o.(*gwv1beta1.GatewayClass) + + pr := gc.Spec.ParametersRef + if pr != nil && pr.Kind == v1alpha1.GatewayClassConfigKind { + return []reconcile.Request{{ + NamespacedName: types.NamespacedName{ + Name: pr.Name, + }, + }} + } + + return nil + }) +} diff --git a/control-plane/api-gateway/controllers/gateway_class_config_controller_test.go b/control-plane/api-gateway/controllers/gateway_class_config_controller_test.go new file mode 100644 index 0000000000..40023d498f --- /dev/null +++ b/control-plane/api-gateway/controllers/gateway_class_config_controller_test.go @@ -0,0 +1,123 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "testing" + "time" + + logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/stretchr/testify/require" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestGatewayClassConfigReconcile(t *testing.T) { + t.Parallel() + deletionTimestamp := meta.Now() + cases := []struct { + name string + k8sObjects func() []runtime.Object + expErr string + requeue bool + requeueAfter time.Duration + }{ + { + name: "Successfully reconcile without any changes", + k8sObjects: func() []runtime.Object { + gatewayClassConfig := v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-api-gateway", + }, + } + return []runtime.Object{&gatewayClassConfig} + }, + }, + { + name: "GatewayClassConfig Does Not Exist", + k8sObjects: func() []runtime.Object { + return []runtime.Object{} + }, + }, + { + name: "Remove not-in-use GatewayClassConfig", + k8sObjects: func() []runtime.Object { + gatewayClassConfig := v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-api-gateway", + DeletionTimestamp: &deletionTimestamp, + }, + } + return []runtime.Object{&gatewayClassConfig} + }, + }, + { + name: "Try to remove in-use GatewayClassConfig", + k8sObjects: func() []runtime.Object { + gatewayClassConfig := v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-api-gateway", + DeletionTimestamp: &deletionTimestamp, + }, + } + gatewayClass := gwv1beta1.GatewayClass{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-api-gateway-class", + }, + Spec: gwv1beta1.GatewayClassSpec{ + ParametersRef: &gwv1beta1.ParametersReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: v1alpha1.GatewayClassConfigKind, + Name: gatewayClassConfig.ObjectMeta.Name, + Namespace: nil, + }, + }, + Status: gwv1beta1.GatewayClassStatus{}, + } + return []runtime.Object{&gatewayClassConfig, &gatewayClass} + }, + requeueAfter: time.Second * 10, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(s)) + require.NoError(t, gwv1alpha2.Install(s)) + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.k8sObjects()...).Build() + + // Create the gateway class config controller. + gcc := &GatewayClassConfigController{ + Client: fakeClient, + Log: logrtest.New(t), + } + + resp, err := gcc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "", + Name: "consul-api-gateway", + }, + }) + if tt.expErr != "" { + require.EqualError(t, err, tt.expErr) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.requeue, resp.Requeue) + }) + } +} diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go new file mode 100644 index 0000000000..e29e40b28e --- /dev/null +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -0,0 +1,779 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "reflect" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + + "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" + 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/fields" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/binding" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/gatekeeper" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul/api" +) + +const ( + gatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" + + kindGateway = "Gateway" +) + +// GatewayControllerConfig holds the values necessary for configuring the GatewayController. +type GatewayControllerConfig struct { + HelmConfig apigateway.HelmConfig + ConsulClientConfig *consul.Config + ConsulServerConnMgr consul.ServerConnectionManager + NamespacesEnabled bool + Partition string +} + +// GatewayController reconciles a Gateway object. +// The Gateway is responsible for defining the behavior of API gateways. +type GatewayController struct { + HelmConfig apigateway.HelmConfig + Log logr.Logger + Translator translation.K8sToConsulTranslator + cache *cache.Cache + client.Client +} + +// Reconcile handles the reconciliation loop for Gateway objects. +func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("gateway", req.NamespacedName) + log.Info("Reconciling Gateway") + + // If gateway does not exist, log an error. + var gw gwv1beta1.Gateway + err := r.Client.Get(ctx, req.NamespacedName, &gw) + if err != nil { + if k8serrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + log.Error(err, "unable to get Gateway") + return ctrl.Result{}, err + } + + // If gateway class on the gateway does not exist, log an error. + gwc := &gwv1beta1.GatewayClass{} + err = r.Client.Get(ctx, types.NamespacedName{Name: string(gw.Spec.GatewayClassName)}, gwc) + if err != nil { + if !k8serrors.IsNotFound(err) { + log.Error(err, "unable to get GatewayClass") + return ctrl.Result{}, err + } + gwc = nil + } + + gwcc, err := getConfigForGatewayClass(ctx, r.Client, gwc) + if err != nil { + log.Error(err, "error fetching the gateway class config") + return ctrl.Result{}, err + } + + // fetch all namespaces + namespaceList := &corev1.NamespaceList{} + if err := r.Client.List(ctx, namespaceList); err != nil { + log.Error(err, "unable to list Namespaces") + return ctrl.Result{}, err + } + namespaces := map[string]corev1.Namespace{} + for _, namespace := range namespaceList.Items { + namespaces[namespace.Name] = namespace + } + + // fetch all gateways we control for reference counting + gwcList := &gwv1beta1.GatewayClassList{} + if err := r.Client.List(ctx, gwcList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(GatewayClass_ControllerNameIndex, GatewayClassControllerName), + }); err != nil { + log.Error(err, "unable to list GatewayClasses") + return ctrl.Result{}, err + } + + // fetch related gateway pods + labels := apigateway.LabelsForGateway(&gw) + podList := &corev1.PodList{} + if err := r.Client.List(ctx, podList, client.MatchingLabels(labels)); err != nil { + log.Error(err, "unable to list Pods for Gateway") + return ctrl.Result{}, err + } + + // fetch related gateway services + service := &corev1.Service{} + // we use the implicit association of a service name/namespace with a corresponding + // gateway + if err := r.Client.Get(ctx, req.NamespacedName, service); err != nil { + if !k8serrors.IsNotFound(err) { + log.Error(err, "unable to fetch service for Gateway") + return ctrl.Result{}, err + } + // if we got a 404, then nil out the service + service = nil + } + + gwList := &gwv1beta1.GatewayList{} + if err := r.Client.List(ctx, gwList); err != nil { + log.Error(err, "unable to list Gateways") + return ctrl.Result{}, err + } + + controlled := map[types.NamespacedName]gwv1beta1.Gateway{} + for _, gwc := range gwcList.Items { + for _, gw := range gwList.Items { + if string(gw.Spec.GatewayClassName) == gwc.Name { + controlled[types.NamespacedName{Namespace: gw.Namespace, Name: gw.Name}] = gw + } + } + } + + // fetch all MeshServices + meshServiceList := &v1alpha1.MeshServiceList{} + if err := r.Client.List(ctx, meshServiceList); err != nil { + log.Error(err, "unable to list MeshServices") + return ctrl.Result{}, err + } + + // fetch all secrets referenced by this gateway + secretList := &corev1.SecretList{} + if err := r.Client.List(ctx, secretList); err != nil { + log.Error(err, "unable to list Secrets") + return ctrl.Result{}, err + } + + listenerCerts := make(map[types.NamespacedName]struct{}) + for _, listener := range gw.Spec.Listeners { + if listener.TLS != nil { + for _, ref := range listener.TLS.CertificateRefs { + if nilOrEqual(ref.Group, "") && nilOrEqual(ref.Kind, "Secret") { + listenerCerts[indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, gw.Namespace)] = struct{}{} + } + } + } + } + + filteredSecrets := []corev1.Secret{} + for _, secret := range secretList.Items { + namespacedName := types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name} + if _, ok := listenerCerts[namespacedName]; ok { + filteredSecrets = append(filteredSecrets, secret) + } + } + + // fetch all http routes referencing this gateway + httpRouteList := &gwv1beta1.HTTPRouteList{} + if err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(HTTPRoute_GatewayIndex, req.String()), + }); err != nil { + log.Error(err, "unable to list HTTPRoutes") + return ctrl.Result{}, err + } + + // fetch all tcp routes referencing this gateway + tcpRouteList := &gwv1alpha2.TCPRouteList{} + if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(TCPRoute_GatewayIndex, req.String()), + }); err != nil { + log.Error(err, "unable to list TCPRoutes") + return ctrl.Result{}, err + } + + configEntry := r.cache.Get(r.Translator.ReferenceForGateway(&gw)) + + var consulGateway *api.APIGatewayConfigEntry + if configEntry != nil { + consulGateway = configEntry.(*api.APIGatewayConfigEntry) + } + httpRoutes := r.cache.List(api.HTTPRoute) + tcpRoutes := r.cache.List(api.TCPRoute) + inlineCertificates := r.cache.List(api.InlineCertificate) + services := r.cache.ListServices() + + binder := binding.NewBinder(binding.BinderConfig{ + Translator: r.Translator, + ControllerName: GatewayClassControllerName, + GatewayClassConfig: gwcc, + GatewayClass: gwc, + Gateway: gw, + Pods: podList.Items, + Service: service, + HTTPRoutes: httpRouteList.Items, + TCPRoutes: tcpRouteList.Items, + MeshServices: meshServiceList.Items, + Secrets: filteredSecrets, + ConsulGateway: consulGateway, + ConsulHTTPRoutes: derefAll(configEntriesTo[*api.HTTPRouteConfigEntry](httpRoutes)), + ConsulTCPRoutes: derefAll(configEntriesTo[*api.TCPRouteConfigEntry](tcpRoutes)), + ConsulInlineCertificates: derefAll(configEntriesTo[*api.InlineCertificateConfigEntry](inlineCertificates)), + ConnectInjectedServices: services, + GatewayServices: consulServicesForGateway(gw, services), + Namespaces: namespaces, + ControlledGateways: controlled, + }) + + updates := binder.Snapshot() + + if updates.UpsertGatewayDeployment { + log.Info("updating gatekeeper") + err := r.updateGatekeeperResources(ctx, log, &gw, gwcc) + if err != nil { + log.Error(err, "unable to update gateway resources") + return ctrl.Result{}, err + } + } else { + log.Info("deleting gatekeeper") + err := r.deleteGatekeeperResources(ctx, log, &gw) + if err != nil { + log.Error(err, "unable to delete gateway resources") + return ctrl.Result{}, err + } + } + + for _, deletion := range updates.Consul.Deletions { + log.Info("deleting from Consul", "kind", deletion.Kind, "namespace", deletion.Namespace, "name", deletion.Name) + if err := r.cache.Delete(ctx, deletion); err != nil { + log.Error(err, "error deleting config entry") + return ctrl.Result{}, err + } + } + + for _, update := range updates.Consul.Updates { + log.Info("updating in Consul", "kind", update.GetKind(), "namespace", update.GetNamespace(), "name", update.GetName()) + if err := r.cache.Write(ctx, update); err != nil { + log.Error(err, "error updating config entry") + return ctrl.Result{}, err + } + } + + for _, registration := range updates.Consul.Registrations { + log.Info("registering service in Consul", "service", registration.Service.Service, "id", registration.Service.ID) + if err := r.cache.Register(ctx, registration); err != nil { + log.Error(err, "error registering service") + return ctrl.Result{}, err + } + } + + for _, deregistration := range updates.Consul.Deregistrations { + log.Info("deregistering service in Consul", "id", deregistration.ServiceID) + if err := r.cache.Deregister(ctx, deregistration); err != nil { + log.Error(err, "error deregistering service") + return ctrl.Result{}, err + } + } + + for _, update := range updates.Kubernetes.Updates { + log.Info("update in Kubernetes", "kind", update.GetObjectKind().GroupVersionKind().Kind, "namespace", update.GetNamespace(), "name", update.GetName()) + if err := r.updateAndResetStatus(ctx, update); err != nil { + log.Error(err, "error updating object") + return ctrl.Result{}, err + } + } + + for _, update := range updates.Kubernetes.StatusUpdates { + log.Info("update status in Kubernetes", "kind", update.GetObjectKind().GroupVersionKind().Kind, "namespace", update.GetNamespace(), "name", update.GetName()) + if err := r.Client.Status().Update(ctx, update); err != nil { + log.Error(err, "error updating status") + return ctrl.Result{}, err + } + } + + // link up policy - TODO: this is really a nasty hack to inject a known policy with + // mesh == read on the provisioned gateway token if needed, figure out some other + // way of handling it. + if updates.UpsertGatewayDeployment { + reference := r.Translator.ReferenceForGateway(&gw) + if err := r.cache.LinkPolicy(ctx, reference.Name, reference.Namespace); err != nil { + log.Error(err, "error linking token policy") + return ctrl.Result{}, err + } + } + + /* TODO: + 1.ReferenceGrants + */ + + return ctrl.Result{}, nil +} + +func (r *GatewayController) updateAndResetStatus(ctx context.Context, o client.Object) error { + // we create a copy so that we can re-update its status if need be + status := reflect.ValueOf(o.DeepCopyObject()).Elem().FieldByName("Status") + if err := r.Client.Update(ctx, o); err != nil { + return err + } + // reset the status in case it needs to be updated below + reflect.ValueOf(o).Elem().FieldByName("Status").Set(status) + return nil +} + +func derefAll[T any](vs []*T) []T { + e := make([]T, len(vs)) + for _, v := range vs { + e = append(e, *v) + } + return e +} + +func configEntriesTo[T api.ConfigEntry](entries []api.ConfigEntry) []T { + es := []T{} + for _, e := range entries { + es = append(es, e.(T)) + } + return es +} + +func (r *GatewayController) deleteGatekeeperResources(ctx context.Context, log logr.Logger, gw *gwv1beta1.Gateway) error { + gk := gatekeeper.New(log, r.Client) + err := gk.Delete(ctx, types.NamespacedName{ + Namespace: gw.Namespace, + Name: gw.Name, + }) + if err != nil { + return err + } + + return nil +} + +func (r *GatewayController) updateGatekeeperResources(ctx context.Context, log logr.Logger, gw *gwv1beta1.Gateway, gwcc *v1alpha1.GatewayClassConfig) error { + gk := gatekeeper.New(log, r.Client) + err := gk.Upsert(ctx, *gw, *gwcc, r.HelmConfig) + if err != nil { + return err + } + + return nil +} + +// SetupWithGatewayControllerManager registers the controller with the given manager. +func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, config GatewayControllerConfig) (*cache.Cache, error) { + c := cache.New(cache.Config{ + ConsulClientConfig: config.ConsulClientConfig, + ConsulServerConnMgr: config.ConsulServerConnMgr, + NamespacesEnabled: config.NamespacesEnabled, + PeeringEnabled: config.HelmConfig.PeeringEnabled, + Logger: mgr.GetLogger(), + }) + + translator := translation.NewConsulToNamespaceNameTranslator(c) + + r := &GatewayController{ + Client: mgr.GetClient(), + Log: mgr.GetLogger(), + HelmConfig: config.HelmConfig, + cache: c, + } + + return c, ctrl.NewControllerManagedBy(mgr). + For(&gwv1beta1.Gateway{}). + Owns(&appsv1.Deployment{}). + Owns(&corev1.Service{}). + Owns(&corev1.Pod{}). + Watches( + source.NewKindWithCache(&gwv1beta1.GatewayClass{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformGatewayClass(ctx)), + ). + Watches( + source.NewKindWithCache(&gwv1beta1.HTTPRoute{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformHTTPRoute(ctx)), + ). + Watches( + source.NewKindWithCache(&gwv1alpha2.TCPRoute{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformTCPRoute(ctx)), + ). + Watches( + source.NewKindWithCache(&corev1.Secret{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformSecret(ctx)), + ). + Watches( + source.NewKindWithCache(&gwv1beta1.ReferenceGrant{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformReferenceGrant(ctx)), + ). + Watches( + source.NewKindWithCache(&v1alpha1.MeshService{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformMeshService(ctx)), + ). + Watches( + // Subscribe to changes from Consul Connect Services + &source.Channel{Source: c.SubscribeServices(ctx, r.transformConsulService(ctx)).Events()}, + &handler.EnqueueRequestForObject{}, + ). + Watches( + // Subscribe to changes from Consul Peering Services + &source.Channel{Source: c.SubscribePeerings(ctx, r.transformConsulPeering(ctx)).Events()}, + &handler.EnqueueRequestForObject{}, + ). + Watches( + // Subscribe to changes from Consul for APIGateways + &source.Channel{Source: c.Subscribe(ctx, api.APIGateway, translator.BuildConsulGatewayTranslator(ctx)).Events()}, + &handler.EnqueueRequestForObject{}, + ). + Watches( + // Subscribe to changes from Consul for HTTPRoutes + &source.Channel{Source: c.Subscribe(ctx, api.HTTPRoute, translator.BuildConsulHTTPRouteTranslator(ctx)).Events()}, + &handler.EnqueueRequestForObject{}, + ). + Watches( + // Subscribe to changes from Consul for TCPRoutes + &source.Channel{Source: c.Subscribe(ctx, api.TCPRoute, translator.BuildConsulTCPRouteTranslator(ctx)).Events()}, + &handler.EnqueueRequestForObject{}, + ). + Watches( + // Subscribe to changes from Consul for InlineCertificates + &source.Channel{Source: c.Subscribe(ctx, api.InlineCertificate, translator.BuildConsulInlineCertificateTranslator(ctx, r.transformSecret)).Events()}, + &handler.EnqueueRequestForObject{}, + ).Complete(r) +} + +func serviceToNamespacedName(s *api.CatalogService) types.NamespacedName { + return types.NamespacedName{ + Namespace: s.ServiceMeta[constants.MetaKeyKubeNS], + Name: s.ServiceMeta[constants.MetaKeyKubeServiceName], + } +} + +// transformGatewayClass will check the list of GatewayClass objects for a matching +// class, then return a list of reconcile Requests for it. +func (r *GatewayController) transformGatewayClass(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + gatewayClass := o.(*gwv1beta1.GatewayClass) + gatewayList := &gwv1beta1.GatewayList{} + if err := r.Client.List(ctx, gatewayList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(Gateway_GatewayClassIndex, gatewayClass.Name), + }); err != nil { + return nil + } + return objectsToRequests(pointersOf(gatewayList.Items)) + } +} + +// transformHTTPRoute will check the HTTPRoute object for a matching +// class, then return a list of reconcile Requests for Gateways referring to it. +func (r *GatewayController) transformHTTPRoute(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + route := o.(*gwv1beta1.HTTPRoute) + return refsToRequests(parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs)) + } +} + +// transformTCPRoute will check the TCPRoute object for a matching +// class, then return a list of reconcile Requests for Gateways referring to it. +func (r *GatewayController) transformTCPRoute(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + route := o.(*gwv1alpha2.TCPRoute) + return refsToRequests(parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs)) + } +} + +// transformSecret will check the Secret object for a matching +// class, then return a list of reconcile Requests for Gateways referring to it. +func (r *GatewayController) transformSecret(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + secret := o.(*corev1.Secret) + gatewayList := &gwv1beta1.GatewayList{} + if err := r.Client.List(ctx, gatewayList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(Secret_GatewayIndex, secret.Name), + }); err != nil { + return nil + } + return objectsToRequests(pointersOf(gatewayList.Items)) + } +} + +// transformReferenceGrant will check the ReferenceGrant object for a matching +// class, then return a list of reconcile Requests for Gateways referring to it. +func (r *GatewayController) transformReferenceGrant(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + // just reconcile all gateways within the namespace + grant := o.(*gwv1beta1.ReferenceGrant) + gatewayList := &gwv1beta1.GatewayList{} + if err := r.Client.List(ctx, gatewayList, &client.ListOptions{ + Namespace: grant.Namespace, + }); err != nil { + return nil + } + return objectsToRequests(pointersOf(gatewayList.Items)) + } +} + +// transformConsulService will return a list of gateways that are referenced +// by a TCPRoute or HTTPRoute that references the Consul service. +func (r *GatewayController) transformConsulService(ctx context.Context) func(service *api.CatalogService) []types.NamespacedName { + return func(service *api.CatalogService) []types.NamespacedName { + nsn := serviceToNamespacedName(service) + + if nsn.Namespace != "" && nsn.Name != "" { + key := nsn.String() + + requestSet := make(map[types.NamespacedName]struct{}) + tcpRouteList := &gwv1alpha2.TCPRouteList{} + if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(TCPRoute_ServiceIndex, key), + }); err != nil { + r.Log.Error(err, "unable to list TCPRoutes") + } + for _, route := range tcpRouteList.Items { + for _, ref := range parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs) { + requestSet[ref] = struct{}{} + } + } + + httpRouteList := &gwv1alpha2.HTTPRouteList{} + if err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(HTTPRoute_ServiceIndex, key), + }); err != nil { + r.Log.Error(err, "unable to list HTTPRoutes") + } + for _, route := range httpRouteList.Items { + for _, ref := range parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs) { + requestSet[ref] = struct{}{} + } + } + + requests := []types.NamespacedName{} + for request := range requestSet { + requests = append(requests, request) + } + return requests + } + + return nil + } +} + +// transformConsulPeering will return a list of gateways that are referenced +// by a TCPRoute or HTTPRoute that references the Consul peering. +func (r *GatewayController) transformConsulPeering(ctx context.Context) func(service *api.Peering) []types.NamespacedName { + return func(peering *api.Peering) []types.NamespacedName { + meshServiceList := &v1alpha1.MeshServiceList{} + + if err := r.Client.List(ctx, meshServiceList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(MeshService_PeerIndex, peering.Name), + }); err != nil { + r.Log.Error(err, "unable to list TCPRoutes") + } + + flattened := []types.NamespacedName{} + for _, meshService := range meshServiceList.Items { + for _, request := range r.transformMeshService(ctx)(&meshService) { + flattened = append(flattened, request.NamespacedName) + } + } + + return flattened + } +} + +// transformMeshService will return a list of gateways that are referenced +// by a TCPRoute or HTTPRoute that references the mesh service. +func (r *GatewayController) transformMeshService(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + service := o.(*v1alpha1.MeshService) + key := client.ObjectKeyFromObject(service).String() + + requestSet := make(map[types.NamespacedName]struct{}) + + tcpRouteList := &gwv1alpha2.TCPRouteList{} + if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(TCPRoute_MeshServiceIndex, key), + }); err != nil { + r.Log.Error(err, "unable to list TCPRoutes") + } + for _, route := range tcpRouteList.Items { + for _, ref := range parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs) { + requestSet[ref] = struct{}{} + } + } + + httpRouteList := &gwv1beta1.HTTPRouteList{} + if err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(HTTPRoute_MeshServiceIndex, key), + }); err != nil { + r.Log.Error(err, "unable to list HTTPRoutes") + } + for _, route := range httpRouteList.Items { + for _, ref := range parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs) { + requestSet[ref] = struct{}{} + } + } + + requests := []reconcile.Request{} + for request := range requestSet { + requests = append(requests, reconcile.Request{NamespacedName: request}) + } + return requests + } +} + +// objectsToRequests takes a list of objects and returns a list of +// reconcile Requests. +func objectsToRequests[T metav1.Object](objects []T) []reconcile.Request { + requests := make([]reconcile.Request, 0, len(objects)) + + // TODO: is it possible to receive empty objects? + for _, object := range objects { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: object.GetNamespace(), + Name: object.GetName(), + }, + }) + } + return requests +} + +// pointersOf returns a list of pointers to the list of objects passed in. +func pointersOf[T any](objects []T) []*T { + pointers := make([]*T, 0, len(objects)) + for _, object := range objects { + pointers = append(pointers, pointerTo(object)) + } + return pointers +} + +// pointerTo returns a pointer to the object type passed in. +func pointerTo[T any](v T) *T { + return &v +} + +// refsToRequests takes a list of NamespacedName objects and returns a list of +// reconcile Requests. +func refsToRequests(objects []types.NamespacedName) []reconcile.Request { + requests := make([]reconcile.Request, 0, len(objects)) + for _, object := range objects { + requests = append(requests, reconcile.Request{ + NamespacedName: object, + }) + } + return requests +} + +// parentRefs takes a list of ParentReference objects and returns a list of NamespacedName objects. +func parentRefs(group, kind, namespace string, refs []gwv1beta1.ParentReference) []types.NamespacedName { + indexed := make([]types.NamespacedName, 0, len(refs)) + for _, parent := range refs { + if nilOrEqual(parent.Group, group) && nilOrEqual(parent.Kind, kind) { + indexed = append(indexed, indexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace)) + } + } + return indexed +} + +func nilOrEqual[T ~string](v *T, check string) bool { + return v == nil || string(*v) == check +} + +func indexedNamespacedNameWithDefault[T ~string, U ~string, V ~string](t T, u *U, v V) types.NamespacedName { + return types.NamespacedName{ + Namespace: derefStringOr(u, v), + Name: string(t), + } +} + +func derefStringOr[T ~string, U ~string](v *T, val U) string { + if v == nil { + return string(val) + } + return string(*v) +} + +func (r *GatewayController) getAllRefsForGateway(ctx context.Context, gw *gwv1beta1.Gateway) ([]metav1.Object, error) { + objs := make([]metav1.Object, 0) + + // handle http routes + httpRouteList := &gwv1beta1.HTTPRouteList{} + err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(HTTPRoute_GatewayIndex, types.NamespacedName{Name: gw.Name, Namespace: gw.Namespace}.String()), + }) + if err != nil { + return nil, err + } + for _, route := range httpRouteList.Items { + objs = append(objs, &route) + } + // handle tcp routes + tcpRouteList := &v1alpha2.TCPRouteList{} + err = r.Client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(TCPRoute_GatewayIndex, types.NamespacedName{Name: gw.Name, Namespace: gw.Namespace}.String()), + }) + if err != nil { + return nil, err + } + for _, route := range tcpRouteList.Items { + objs = append(objs, &route) + } + + // handle secrets + for _, listener := range gw.Spec.Listeners { + for _, secret := range listener.TLS.CertificateRefs { + secretObj := &corev1.Secret{} + err = r.Client.Get(ctx, indexedNamespacedNameWithDefault(secret.Name, secret.Namespace, gw.Namespace), secretObj) + if err != nil { + continue + } + objs = append(objs, secretObj) + } + } + + return objs, nil +} + +// getConfigForGatewayClass returns the relevant GatewayClassConfig for the GatewayClass. +func getConfigForGatewayClass(ctx context.Context, client client.Client, gwc *gwv1beta1.GatewayClass) (*v1alpha1.GatewayClassConfig, error) { + if gwc == nil { + // if we don't have a gateway class we can't fetch the corresponding config + return nil, nil + } + + config := &v1alpha1.GatewayClassConfig{} + if ref := gwc.Spec.ParametersRef; ref != nil { + if string(ref.Group) != v1alpha1.GroupVersion.Group || + ref.Kind != v1alpha1.GatewayClassConfigKind || + gwc.Spec.ControllerName != GatewayClassControllerName { + // we don't have supported params, so return nil + return nil, nil + } + + err := client.Get(ctx, types.NamespacedName{Name: ref.Name}, config) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil, nil + } + return nil, err + } + } + return config, nil +} + +func consulServicesForGateway(gateway gwv1beta1.Gateway, services []api.CatalogService) []api.CatalogService { + filtered := []api.CatalogService{} + for _, service := range services { + kubeService := serviceToNamespacedName(&service) + if gateway.Name == kubeService.Name && gateway.Namespace == kubeService.Namespace { + filtered = append(filtered, service) + } + } + return filtered +} diff --git a/control-plane/api-gateway/controllers/gateway_controller_test.go b/control-plane/api-gateway/controllers/gateway_controller_test.go new file mode 100644 index 0000000000..d04a0b2e44 --- /dev/null +++ b/control-plane/api-gateway/controllers/gateway_controller_test.go @@ -0,0 +1,464 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + appsv1 "k8s.io/api/apps/v1" + rbac "k8s.io/api/rbac/v1" + + logrtest "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul/api" +) + +const ( + TestGatewayClassConfigName = "test-gateway-class-config" + TestAnnotationConfigKey = "api-gateway.consul.hashicorp.com/config" + TestGatewayClassName = "test-gateway-class" + TestGatewayName = "test-gateway" + TestNamespace = "test-namespace" +) + +func stubConsulCache(t *testing.T) *cache.Cache { + t.Helper() + + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v1/acl/policies": + fmt.Fprintln(w, `[]`) + case "/v1/acl/tokens": + fmt.Fprintln(w, `[]`) + case "/v1/config": + fmt.Fprintln(w, `[]`) + case "/v1/catalog/services": + fmt.Fprintln(w, `{}`) + default: + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) + } + })) + t.Cleanup(consulServer.Close) + + serverURL, err := url.Parse(consulServer.URL) + require.NoError(t, err) + + port, err := strconv.Atoi(serverURL.Port()) + require.NoError(t, err) + + return cache.New(cache.Config{ + ConsulClientConfig: &consul.Config{ + APIClientConfig: &api.Config{}, + HTTPPort: port, + GRPCPort: port, + APITimeout: 0, + }, + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + NamespacesEnabled: false, + Logger: logrtest.New(t), + }) +} + +func TestGatewayReconcileGatekeeperUpdates(t *testing.T) { + t.Parallel() + + namespace := "test-namespace" + name := "test-gateway" + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: namespace, + Name: name, + }, + } + + basicGatewayClass, basicGatewayClassConfig := getBasicGatewayClassAndConfig() + + cases := map[string]struct { + gateway *gwv1beta1.Gateway + k8sObjects []runtime.Object + expectedError error + }{ + "successful update of gateway": { + gateway: &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{gatewayFinalizer}, + Annotations: map[string]string{ + TestAnnotationConfigKey: `{"serviceType":"serviceType"}`, + }, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: TestGatewayClassName, + }, + }, + k8sObjects: []runtime.Object{ + &basicGatewayClass, + &basicGatewayClassConfig, + }, + expectedError: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + require.NoError(t, gwv1alpha2.AddToScheme(s)) + require.NoError(t, rbac.AddToScheme(s)) + require.NoError(t, corev1.AddToScheme(s)) + require.NoError(t, appsv1.AddToScheme(s)) + + objs := tc.k8sObjects + if tc.gateway != nil { + objs = append(objs, tc.gateway) + } + + fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...)).Build() + + r := &GatewayController{ + cache: stubConsulCache(t), + Client: fakeClient, + Log: logrtest.New(t), + } + + _, err := r.Reconcile(context.Background(), req) + + require.Equal(t, tc.expectedError, err) + deployment := appsv1.Deployment{} + r.Client.Get(context.TODO(), types.NamespacedName{ + Namespace: TestNamespace, + Name: TestGatewayName, + }, &deployment) + require.NotEmpty(t, deployment) + require.Equal(t, TestGatewayName, deployment.ObjectMeta.Name) + }) + } +} + +func TestGatewayReconcileGatekeeperDeletes(t *testing.T) { + t.Parallel() + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: TestNamespace, + Name: TestGatewayName, + }, + } + + basicGatewayClass, basicGatewayClassConfig := getBasicGatewayClassAndConfig() + cases := map[string]struct { + gateway *gwv1beta1.Gateway + k8sObjects []runtime.Object + expectedError error + }{ + "successful change of gatewayclass on gateway": { + gateway: &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: TestNamespace, + Name: TestGatewayName, + Finalizers: []string{gatewayFinalizer}, + Annotations: map[string]string{ + TestAnnotationConfigKey: `{"serviceType":"serviceType"}`, + }, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: TestGatewayClassName, + }, + }, + k8sObjects: []runtime.Object{ + &basicGatewayClass, + &basicGatewayClassConfig, + }, + expectedError: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + require.NoError(t, gwv1alpha2.AddToScheme(s)) + require.NoError(t, rbac.AddToScheme(s)) + require.NoError(t, corev1.AddToScheme(s)) + require.NoError(t, appsv1.AddToScheme(s)) + + objs := tc.k8sObjects + if tc.gateway != nil { + objs = append(objs, tc.gateway) + } + + fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...)).Build() + + r := &GatewayController{ + cache: stubConsulCache(t), + Client: fakeClient, + Log: logrtest.New(t), + } + + _, err := r.Reconcile(context.Background(), req) + + require.Equal(t, tc.expectedError, err) + deployment := appsv1.Deployment{} + r.Client.Get(context.TODO(), types.NamespacedName{ + Namespace: TestNamespace, + Name: TestGatewayName, + }, &deployment) + require.NotEmpty(t, deployment) + require.Equal(t, TestGatewayName, deployment.ObjectMeta.Name) + }) + } +} + +func TestObjectsToRequests(t *testing.T) { + t.Parallel() + + name := "test-gatewayclass" + + namespacedName := types.NamespacedName{ + Namespace: TestNamespace, + Name: name, + } + + cases := map[string]struct { + objects []metav1.Object + expectedResult []reconcile.Request + }{ + "successful conversion of gateway to request": { + objects: []metav1.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: TestNamespace, + Name: name, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: GatewayClassControllerName, + }, + }, + }, + expectedResult: []reconcile.Request{ + { + NamespacedName: namespacedName, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + requests := objectsToRequests(tc.objects) + + require.Equal(t, tc.expectedResult, requests) + }) + } +} + +func TestGatewayController_getAllRefsForGateway(t *testing.T) { + t.Parallel() + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, gwv1alpha2.Install(s)) + require.NoError(t, corev1.AddToScheme(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + secret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "secret squirrel", + }, + } + gw := &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gw", + Annotations: map[string]string{}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: "", + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + { + Kind: pointerTo(gwv1beta1.Kind("Secret")), + Name: "secret squirrel", + }, + }, + Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{}, + }, + AllowedRoutes: &gwv1beta1.AllowedRoutes{}, + }, + }, + Addresses: []gwv1beta1.GatewayAddress{}, + }, + Status: gwv1beta1.GatewayStatus{}, + } + gwc := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gw-class", + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "", + ParametersRef: &gwv1beta1.ParametersReference{ + Group: Group, + Kind: v1alpha1.GatewayClassConfigKind, + Name: "the config", + }, + Description: new(string), + }, + } + gwcConfig := &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "the config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + ServiceType: pointerTo(corev1.ServiceType("serviceType")), + NodeSelector: map[string]string{ + "selector": "of node", + }, + Tolerations: []v1.Toleration{ + { + Key: "key", + Operator: "op", + Value: "120", + Effect: "to the moon", + TolerationSeconds: new(int64), + }, + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ + Service: []string{"service"}, + }, + }, + } + + httpRouteOnGateway := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "route 1", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Name: gwv1beta1.ObjectName(gw.Name), + }, + }, + }, + }, + } + + httpRouteNotOnGateway := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "route not on gateway", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Name: gwv1beta1.ObjectName("not on the gateway"), + }, + }, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{}, + } + + tcpRoute := &gwv1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp route", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Name: gwv1beta1.ObjectName(gw.Name), + }, + }, + }, + }, + } + + objs := []runtime.Object{gw, gwc, gwcConfig, httpRouteOnGateway, httpRouteNotOnGateway, tcpRoute, secret} + + fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...)).Build() + controller := GatewayController{ + Client: fakeClient, + } + + ctx := context.Background() + + actual, err := controller.getAllRefsForGateway(ctx, gw) + + require.NoError(t, err) + expectedEntries := []metav1.Object{httpRouteOnGateway, tcpRoute, secret} + + require.ElementsMatch(t, expectedEntries, actual) +} + +func getBasicGatewayClassAndConfig() (gwv1beta1.GatewayClass, v1alpha1.GatewayClassConfig) { + serviceType := corev1.ServiceType("NodePort") + basicGatewayClass := gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: TestGatewayClassName, + Finalizers: []string{ + gatewayClassFinalizer, + }, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: GatewayClassControllerName, + ParametersRef: &gwv1beta1.ParametersReference{ + Group: "consul.hashicorp.com", + Kind: "GatewayClassConfig", + Name: TestGatewayClassConfigName, + Namespace: nil, + }, + }, + } + + basicGatewayClassConfig := v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestGatewayClassConfigName, + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + ServiceType: &serviceType, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ + Service: []string{"serviceType"}, + }, + }, + } + + return basicGatewayClass, basicGatewayClassConfig +} diff --git a/control-plane/api-gateway/controllers/gatewayclass_controller.go b/control-plane/api-gateway/controllers/gatewayclass_controller.go new file mode 100644 index 0000000000..4180157616 --- /dev/null +++ b/control-plane/api-gateway/controllers/gatewayclass_controller.go @@ -0,0 +1,259 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +const ( + GatewayClassControllerName = "hashicorp.com/consul-api-gateway-controller" + + gatewayClassFinalizer = "gateway-exists-finalizer.consul.hashicorp.com" + + // GatewayClass status fields. + accepted = "Accepted" + invalidParameters = "InvalidParameters" +) + +// GatewayClassController reconciles a GatewayClass object. +// The GatewayClass is responsible for defining the behavior of API gateways +// which reference the given class. +type GatewayClassController struct { + ControllerName string + Log logr.Logger + + client.Client +} + +// Reconcile handles the reconciliation loop for GatewayClass objects. +func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("gatewayClass", req.NamespacedName.Name) + log.Info("Reconciling GatewayClass") + + gc := &gwv1beta1.GatewayClass{} + + err := r.Client.Get(ctx, req.NamespacedName, gc) + if err != nil { + if k8serrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + log.Error(err, "unable to get GatewayClass") + return ctrl.Result{}, err + } + + if string(gc.Spec.ControllerName) != r.ControllerName { + // This GatewayClass is not for this controller. + _, err := RemoveFinalizer(ctx, r.Client, gc, gatewayClassFinalizer) + if err != nil { + log.Error(err, "unable to remove finalizer") + } + + return ctrl.Result{}, err + } + + if !gc.ObjectMeta.DeletionTimestamp.IsZero() { + // We have a deletion request. Ensure we are not in use. + used, err := r.isGatewayClassInUse(ctx, gc) + if err != nil { + log.Error(err, "unable to check if GatewayClass is in use") + return ctrl.Result{}, err + } + if used { + log.Info("GatewayClass is in use, cannot delete") + return ctrl.Result{}, nil + } + // Remove our finalizer. + if _, err := RemoveFinalizer(ctx, r.Client, gc, gatewayClassFinalizer); err != nil { + log.Error(err, "unable to remove finalizer") + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + + // We are creating or updating the GatewayClass. + didUpdate, err := EnsureFinalizer(ctx, r.Client, gc, gatewayClassFinalizer) + if err != nil { + log.Error(err, "unable to add finalizer") + return ctrl.Result{}, err + } + if didUpdate { + // We updated the GatewayClass, requeue to avoid another update. + return ctrl.Result{}, nil + } + + didUpdate, err = r.validateParametersRef(ctx, gc, log) + if didUpdate { + if err := r.Client.Status().Update(ctx, gc); err != nil { + log.Error(err, "unable to update GatewayClass") + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + if err != nil { + log.Error(err, "unable to validate ParametersRef") + } + + return ctrl.Result{}, err +} + +// SetupWithManager registers the controller with the given manager. +func (r *GatewayClassController) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&gwv1beta1.GatewayClass{}). + // Watch for changes to GatewayClassConfig objects. + Watches(source.NewKindWithCache(&v1alpha1.GatewayClassConfig{}, mgr.GetCache()), r.gatewayClassConfigFieldIndexEventHandler(ctx)). + // Watch for changes to Gateway objects that reference this GatewayClass. + Watches(source.NewKindWithCache(&gwv1beta1.Gateway{}, mgr.GetCache()), r.gatewayFieldIndexEventHandler(ctx)). + Complete(r) +} + +// isGatewayClassInUse returns true if the given GatewayClass is referenced by any Gateway objects. +func (r *GatewayClassController) isGatewayClassInUse(ctx context.Context, gc *gwv1beta1.GatewayClass) (bool, error) { + list := &gwv1beta1.GatewayList{} + if err := r.Client.List(ctx, list, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(Gateway_GatewayClassIndex, gc.Name), + }); err != nil { + return false, err + } + + return len(list.Items) != 0, nil +} + +// validateParametersRef validates the ParametersRef field of the given GatewayClass +// if it is set, ensuring that the referenced object is a GatewayClassConfig that exists. +func (r *GatewayClassController) validateParametersRef(ctx context.Context, gc *gwv1beta1.GatewayClass, log logr.Logger) (didUpdate bool, err error) { + parametersRef := gc.Spec.ParametersRef + if parametersRef != nil { + if parametersRef.Kind != v1alpha1.GatewayClassConfigKind { + didUpdate = r.setCondition(gc, metav1.Condition{ + Type: accepted, + Status: metav1.ConditionFalse, + Reason: invalidParameters, + Message: fmt.Sprintf("Incorrect type for parametersRef. Expected GatewayClassConfig, got %q.", parametersRef.Kind), + }) + return didUpdate, nil + } + + err = r.Client.Get(ctx, types.NamespacedName{Name: parametersRef.Name}, &v1alpha1.GatewayClassConfig{}) + if k8serrors.IsNotFound(err) { + didUpdate := r.setCondition(gc, metav1.Condition{ + Type: accepted, + Status: metav1.ConditionFalse, + Reason: invalidParameters, + Message: fmt.Sprintf("GatewayClassConfig not found %q.", parametersRef.Name), + }) + return didUpdate, nil + } + if err != nil { + log.Error(err, "unable to fetch GatewayClassConfig") + return false, err + } + } + + didUpdate = r.setCondition(gc, metav1.Condition{ + Type: accepted, + Status: metav1.ConditionTrue, + Reason: accepted, + Message: "GatewayClass Accepted", + }) + + return didUpdate, err +} + +// setCondition sets the given condition on the given GatewayClass. +func (r *GatewayClassController) setCondition(gc *gwv1beta1.GatewayClass, condition metav1.Condition) (didUpdate bool) { + condition.LastTransitionTime = metav1.Now() + condition.ObservedGeneration = gc.GetGeneration() + + // Set the condition if it already exists. + for i, c := range gc.Status.Conditions { + if c.Type == condition.Type { + // The condition already exists and is up to date. + if equalConditions(condition, c) { + return false + } + + gc.Status.Conditions[i] = condition + + return true + } + } + + // Append the condition if it does not exist. + gc.Status.Conditions = append(gc.Status.Conditions, condition) + + return true +} + +// gatewayClassConfigFieldIndexEventHandler returns an EventHandler that will enqueue +// reconcile.Requests for GatewayClass objects that reference the GatewayClassConfig +// object that triggered the event. +func (r *GatewayClassController) gatewayClassConfigFieldIndexEventHandler(ctx context.Context) handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + // Get all GatewayClass objects from the field index of the GatewayClassConfig which triggered the event. + var gcList gwv1beta1.GatewayClassList + err := r.Client.List(ctx, &gcList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(GatewayClass_GatewayClassConfigIndex, o.GetName()), + }) + if err != nil { + r.Log.Error(err, "unable to list gateway classes") + } + + // Create a reconcile request for each GatewayClass. + for _, gc := range gcList.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: gc.Name, + }, + }) + } + + return requests + }) +} + +// gatewayFieldIndexEventHandler returns an EventHandler that will enqueue +// reconcile.Requests for GatewayClass objects from Gateways which reference the GatewayClass +// when those Gateways are updated. +func (r *GatewayClassController) gatewayFieldIndexEventHandler(ctx context.Context) handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { + // Get the Gateway object that triggered the event. + g := o.(*gwv1beta1.Gateway) + + // Return a slice with the single reconcile.Request for the GatewayClass + // that the Gateway references. + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: string(g.Spec.GatewayClassName), + }, + }, + } + }) +} + +func equalConditions(a, b metav1.Condition) bool { + return a.Type == b.Type && + a.Status == b.Status && + a.Reason == b.Reason && + a.Message == b.Message && + a.ObservedGeneration == b.ObservedGeneration +} diff --git a/control-plane/api-gateway/controllers/gatewayclass_controller_test.go b/control-plane/api-gateway/controllers/gatewayclass_controller_test.go new file mode 100644 index 0000000000..ac5be25205 --- /dev/null +++ b/control-plane/api-gateway/controllers/gatewayclass_controller_test.go @@ -0,0 +1,275 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "fmt" + "testing" + + logrtest "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/apis/v1beta1" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +func TestGatewayClassReconciler(t *testing.T) { + t.Parallel() + + namespace := "" // GatewayClass is cluster-scoped. + name := "test-gatewayclass" + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: namespace, + Name: name, + }, + } + + deletionTimestamp := metav1.Now() + + cases := map[string]struct { + gatewayClass *gwv1beta1.GatewayClass + k8sObjects []runtime.Object + expectedResult ctrl.Result + expectedError error + expectedFinalizers []string + expectedIsDeleted bool + expectedConditions []metav1.Condition + }{ + "successful reconcile with no change": { + gatewayClass: &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{gatewayClassFinalizer}, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: GatewayClassControllerName, + }, + }, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedFinalizers: []string{gatewayClassFinalizer}, + expectedIsDeleted: false, + expectedConditions: []metav1.Condition{ + { + Type: accepted, + Status: metav1.ConditionTrue, + Reason: accepted, + Message: "GatewayClass Accepted", + }, + }, + }, + "successful reconcile that adds finalizer": { + gatewayClass: &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{}, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: GatewayClassControllerName, + }, + }, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedFinalizers: []string{gatewayClassFinalizer}, + expectedConditions: []metav1.Condition{}, + }, + "attempt to reconcile a GatewayClass with a different controller name": { + gatewayClass: &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{}, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "foo", + }, + }, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedConditions: []metav1.Condition{}, + }, + "attempt to reconcile a GatewayClass with a different controller name removing our finalizer": { + gatewayClass: &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{gatewayClassFinalizer}, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "foo", + }, + }, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedConditions: []metav1.Condition{}, + }, + "attempt to reconcile a GatewayClass with an incorrect parametersRef type": { + gatewayClass: &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{gatewayClassFinalizer}, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: GatewayClassControllerName, + ParametersRef: &gwv1beta1.ParametersReference{ + Kind: "some-nonsense", + }, + }, + }, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedFinalizers: []string{gatewayClassFinalizer}, + expectedConditions: []metav1.Condition{ + { + Type: accepted, + Status: metav1.ConditionFalse, + Reason: invalidParameters, + Message: fmt.Sprintf("Incorrect type for parametersRef. Expected GatewayClassConfig, got %q.", "some-nonsense"), + }, + }, + }, + "attempt to reconcile a GatewayClass with a GatewayClassConfig that does not exist": { + gatewayClass: &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{gatewayClassFinalizer}, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: GatewayClassControllerName, + ParametersRef: &gwv1beta1.ParametersReference{ + Kind: v1alpha1.GatewayClassConfigKind, + Name: "does-not-exist", + }, + }, + }, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedFinalizers: []string{gatewayClassFinalizer}, + expectedConditions: []metav1.Condition{ + { + Type: accepted, + Status: metav1.ConditionFalse, + Reason: invalidParameters, + Message: fmt.Sprintf("GatewayClassConfig not found %q.", "does-not-exist"), + }, + }, + }, + "attempt to reconcile a non-existent object": { + k8sObjects: []runtime.Object{}, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedConditions: []metav1.Condition{}, + }, + "attempt to remove a GatewayClass that is not in use": { + gatewayClass: &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{ + gatewayClassFinalizer, + }, + DeletionTimestamp: &deletionTimestamp, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: GatewayClassControllerName, + }, + }, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedFinalizers: []string{}, + expectedIsDeleted: true, + }, + "attempt to remove a GatewayClass that is in use": { + gatewayClass: &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Finalizers: []string{ + gatewayClassFinalizer, + }, + DeletionTimestamp: &deletionTimestamp, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: GatewayClassControllerName, + }, + }, + k8sObjects: []runtime.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test-gateway", + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: v1beta1.ObjectName(name), + }, + }, + }, + expectedResult: ctrl.Result{}, + expectedError: nil, + expectedFinalizers: []string{gatewayClassFinalizer}, + }, + // */ + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(s)) + require.NoError(t, gwv1alpha2.Install(s)) + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + objs := tc.k8sObjects + if tc.gatewayClass != nil { + objs = append(objs, tc.gatewayClass) + } + + fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...)).Build() + + r := &GatewayClassController{ + Client: fakeClient, + ControllerName: GatewayClassControllerName, + Log: logrtest.New(t), + } + result, err := r.Reconcile(context.Background(), req) + + require.Equal(t, tc.expectedResult, result) + require.Equal(t, tc.expectedError, err) + + // Check the GatewayClass after reconciliation. + gc := &gwv1beta1.GatewayClass{} + err = r.Client.Get(context.Background(), req.NamespacedName, gc) + + if tc.gatewayClass == nil || tc.expectedIsDeleted { + // There shouldn't be a GatewayClass to check. + require.True(t, apierrors.IsNotFound(err)) + return + } + + require.NoError(t, client.IgnoreNotFound(err)) + require.Equal(t, tc.expectedFinalizers, gc.ObjectMeta.Finalizers) + require.Equal(t, len(tc.expectedConditions), len(gc.Status.Conditions), "expected %+v, got %+v", tc.expectedConditions, gc.Status.Conditions) + for i, expectedCondition := range tc.expectedConditions { + require.True(t, equalConditions(expectedCondition, gc.Status.Conditions[i]), "expected %+v, got %+v", expectedCondition, gc.Status.Conditions[i]) + } + }) + } +} diff --git a/control-plane/api-gateway/controllers/index.go b/control-plane/api-gateway/controllers/index.go new file mode 100644 index 0000000000..3ef0e65c81 --- /dev/null +++ b/control-plane/api-gateway/controllers/index.go @@ -0,0 +1,262 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +const ( + // Naming convention: TARGET_REFERENCE. + GatewayClass_GatewayClassConfigIndex = "__gatewayclass_referencing_gatewayclassconfig" + GatewayClass_ControllerNameIndex = "__gatewayclass_controller_name" + Gateway_GatewayClassIndex = "__gateway_referencing_gatewayclass" + HTTPRoute_GatewayIndex = "__httproute_referencing_gateway" + HTTPRoute_ServiceIndex = "__httproute_referencing_service" + HTTPRoute_MeshServiceIndex = "__httproute_referencing_mesh_service" + TCPRoute_GatewayIndex = "__tcproute_referencing_gateway" + TCPRoute_ServiceIndex = "__tcproute_referencing_service" + TCPRoute_MeshServiceIndex = "__tcproute_referencing_mesh_service" + MeshService_PeerIndex = "__meshservice_referencing_peer" + Secret_GatewayIndex = "__secret_referencing_gateway" +) + +// RegisterFieldIndexes registers all of the field indexes for the API gateway controllers. +// These indexes are similar to indexes used in databases to speed up queries. +// They allow us to quickly find objects based on a field value. +func RegisterFieldIndexes(ctx context.Context, mgr ctrl.Manager) error { + for _, index := range indexes { + if err := mgr.GetFieldIndexer().IndexField(ctx, index.target, index.name, index.indexerFunc); err != nil { + return err + } + } + return nil +} + +type index struct { + name string + target client.Object + indexerFunc client.IndexerFunc +} + +var indexes = []index{ + { + name: GatewayClass_GatewayClassConfigIndex, + target: &gwv1beta1.GatewayClass{}, + indexerFunc: gatewayClassConfigForGatewayClass, + }, + { + name: GatewayClass_ControllerNameIndex, + target: &gwv1beta1.GatewayClass{}, + indexerFunc: gatewayClassControllerName, + }, + { + name: Gateway_GatewayClassIndex, + target: &gwv1beta1.Gateway{}, + indexerFunc: gatewayClassForGateway, + }, + { + name: Secret_GatewayIndex, + target: &gwv1beta1.Gateway{}, + indexerFunc: gatewayForSecret, + }, + { + name: HTTPRoute_GatewayIndex, + target: &gwv1beta1.HTTPRoute{}, + indexerFunc: gatewaysForHTTPRoute, + }, + { + name: HTTPRoute_ServiceIndex, + target: &gwv1beta1.HTTPRoute{}, + indexerFunc: servicesForHTTPRoute, + }, + { + name: HTTPRoute_MeshServiceIndex, + target: &gwv1beta1.HTTPRoute{}, + indexerFunc: meshServicesForHTTPRoute, + }, + { + name: TCPRoute_GatewayIndex, + target: &gwv1alpha2.TCPRoute{}, + indexerFunc: gatewaysForTCPRoute, + }, + { + name: TCPRoute_ServiceIndex, + target: &gwv1alpha2.TCPRoute{}, + indexerFunc: servicesForTCPRoute, + }, + { + name: TCPRoute_MeshServiceIndex, + target: &gwv1alpha2.TCPRoute{}, + indexerFunc: meshServicesForTCPRoute, + }, + { + name: MeshService_PeerIndex, + target: &v1alpha1.MeshService{}, + indexerFunc: peersForMeshService, + }, +} + +// gatewayClassConfigForGatewayClass creates an index of every GatewayClassConfig referenced by a GatewayClass. +func gatewayClassConfigForGatewayClass(o client.Object) []string { + gc := o.(*gwv1beta1.GatewayClass) + + pr := gc.Spec.ParametersRef + if pr != nil && pr.Kind == v1alpha1.GatewayClassConfigKind { + return []string{pr.Name} + } + + return []string{} +} + +func gatewayClassControllerName(o client.Object) []string { + gc := o.(*gwv1beta1.GatewayClass) + + if gc.Spec.ControllerName != "" { + return []string{string(gc.Spec.ControllerName)} + } + + return []string{} +} + +// gatewayClassForGateway creates an index of every GatewayClass referenced by a Gateway. +func gatewayClassForGateway(o client.Object) []string { + g := o.(*gwv1beta1.Gateway) + return []string{string(g.Spec.GatewayClassName)} +} + +func peersForMeshService(o client.Object) []string { + m := o.(*v1alpha1.MeshService) + if m.Spec.Peer != nil { + return []string{string(*m.Spec.Peer)} + } + return nil +} + +func gatewayForSecret(o client.Object) []string { + gateway := o.(*gwv1beta1.Gateway) + var secretReferences []string + for _, listener := range gateway.Spec.Listeners { + if listener.TLS == nil || *listener.TLS.Mode != gwv1beta1.TLSModeTerminate { + continue + } + for _, cert := range listener.TLS.CertificateRefs { + if nilOrEqual(cert.Group, "") && nilOrEqual(cert.Kind, "Secret") { + // If an explicit Secret namespace is not provided, use the Gateway namespace to lookup the provided Secret Name. + secretReferences = append(secretReferences, indexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace).String()) + } + } + } + return secretReferences +} + +func gatewaysForHTTPRoute(o client.Object) []string { + route := o.(*gwv1beta1.HTTPRoute) + return gatewaysForRoute(route.Namespace, route.Spec.ParentRefs) +} + +func gatewaysForTCPRoute(o client.Object) []string { + route := o.(*gwv1alpha2.TCPRoute) + return gatewaysForRoute(route.Namespace, route.Spec.ParentRefs) +} + +func servicesForHTTPRoute(o client.Object) []string { + route := o.(*gwv1beta1.HTTPRoute) + refs := []string{} + for _, rule := range route.Spec.Rules { + BACKEND_LOOP: + for _, ref := range rule.BackendRefs { + if nilOrEqual(ref.Group, "") && nilOrEqual(ref.Kind, "Service") { + backendRef := indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() + for _, member := range refs { + if member == backendRef { + continue BACKEND_LOOP + } + } + refs = append(refs, backendRef) + } + } + } + return refs +} + +func meshServicesForHTTPRoute(o client.Object) []string { + route := o.(*gwv1beta1.HTTPRoute) + refs := []string{} + for _, rule := range route.Spec.Rules { + BACKEND_LOOP: + for _, ref := range rule.BackendRefs { + if ref.Group != nil && string(*ref.Group) == v1alpha1.ConsulHashicorpGroup && + ref.Kind != nil && string(*ref.Kind) == v1alpha1.MeshServiceKind { + backendRef := indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() + for _, member := range refs { + if member == backendRef { + continue BACKEND_LOOP + } + } + refs = append(refs, backendRef) + } + } + } + return refs +} + +func servicesForTCPRoute(o client.Object) []string { + route := o.(*gwv1alpha2.TCPRoute) + refs := []string{} + for _, rule := range route.Spec.Rules { + BACKEND_LOOP: + for _, ref := range rule.BackendRefs { + if nilOrEqual(ref.Group, "") && nilOrEqual(ref.Kind, "Service") { + backendRef := indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() + for _, member := range refs { + if member == backendRef { + continue BACKEND_LOOP + } + } + refs = append(refs, backendRef) + } + } + } + return refs +} + +func meshServicesForTCPRoute(o client.Object) []string { + route := o.(*gwv1alpha2.TCPRoute) + refs := []string{} + for _, rule := range route.Spec.Rules { + BACKEND_LOOP: + for _, ref := range rule.BackendRefs { + if ref.Group != nil && string(*ref.Group) == v1alpha1.ConsulHashicorpGroup && + ref.Kind != nil && string(*ref.Kind) == v1alpha1.MeshServiceKind { + backendRef := indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() + for _, member := range refs { + if member == backendRef { + continue BACKEND_LOOP + } + } + refs = append(refs, backendRef) + } + } + } + return refs +} + +func gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference) []string { + var references []string + for _, parent := range refs { + if nilOrEqual(parent.Group, gwv1beta1.GroupVersion.Group) && nilOrEqual(parent.Kind, "Gateway") { + // If an explicit Gateway namespace is not provided, use the Route namespace to lookup the provided Gateway Namespace. + references = append(references, indexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace).String()) + } + } + return references +} diff --git a/control-plane/api-gateway/controllers/index_test.go b/control-plane/api-gateway/controllers/index_test.go new file mode 100644 index 0000000000..5655a3c3da --- /dev/null +++ b/control-plane/api-gateway/controllers/index_test.go @@ -0,0 +1,13 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import "sigs.k8s.io/controller-runtime/pkg/client/fake" + +func registerFieldIndexersForTest(clientBuilder *fake.ClientBuilder) *fake.ClientBuilder { + for _, index := range indexes { + clientBuilder = clientBuilder.WithIndex(index.target, index.name, index.indexerFunc) + } + return clientBuilder +} diff --git a/control-plane/api-gateway/controllers/reference_validator.go b/control-plane/api-gateway/controllers/reference_validator.go new file mode 100644 index 0000000000..91b4c0ea51 --- /dev/null +++ b/control-plane/api-gateway/controllers/reference_validator.go @@ -0,0 +1,177 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +type ReferenceValidator struct { + client.Client +} + +func NewReferenceValidator(client client.Client) *ReferenceValidator { + return &ReferenceValidator{ + client, + } +} + +func (rv *ReferenceValidator) GatewayCanReferenceSecret(ctx context.Context, gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) (bool, error) { + fromNS := gateway.GetNamespace() + fromGK := metav1.GroupKind{ + Group: gateway.GroupVersionKind().Group, + Kind: gateway.GroupVersionKind().Kind, + } + + // Kind should default to Secret if not set + // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#LL59C21-L59C21 + toNS, toGK := createValuesFromRef(secretRef.Namespace, secretRef.Group, secretRef.Kind, "Secret") + + return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(secretRef.Name), rv.Client) +} + +func (rv *ReferenceValidator) HTTPRouteCanReferenceGateway(ctx context.Context, httproute gwv1beta1.HTTPRoute, parentRef gwv1beta1.ParentReference) (bool, error) { + fromNS := httproute.GetNamespace() + fromGK := metav1.GroupKind{ + Group: httproute.GroupVersionKind().Group, + Kind: httproute.GroupVersionKind().Kind, + } + + // Kind should default to Gateway if not set + // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/shared_types.go#L48 + toNS, toGK := createValuesFromRef(parentRef.Namespace, parentRef.Group, parentRef.Kind, "Gateway") + + return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(parentRef.Name), rv.Client) +} + +func (rv *ReferenceValidator) HTTPRouteCanReferenceBackend(ctx context.Context, httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) (bool, error) { + fromNS := httproute.GetNamespace() + fromGK := metav1.GroupKind{ + Group: httproute.GroupVersionKind().Group, + Kind: httproute.GroupVersionKind().Kind, + } + + // Kind should default to Service if not set + // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#L106 + toNS, toGK := createValuesFromRef(backendRef.Namespace, backendRef.Group, backendRef.Kind, "Service") + + return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(backendRef.Name), rv.Client) + +} + +func (rv *ReferenceValidator) TCPRouteCanReferenceGateway(ctx context.Context, tcpRoute gwv1alpha2.TCPRoute, parentRef gwv1beta1.ParentReference) (bool, error) { + fromNS := tcpRoute.GetNamespace() + fromGK := metav1.GroupKind{ + Group: tcpRoute.GroupVersionKind().Group, + Kind: tcpRoute.GroupVersionKind().Kind, + } + + // Kind should default to Gateway if not set + // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/shared_types.go#L48 + toNS, toGK := createValuesFromRef(parentRef.Namespace, parentRef.Group, parentRef.Kind, "Gateway") + + return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(parentRef.Name), rv.Client) +} + +func (rv *ReferenceValidator) TCPRouteCanReferenceBackend(ctx context.Context, tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) (bool, error) { + fromNS := tcpRoute.GetNamespace() + fromGK := metav1.GroupKind{ + Group: tcpRoute.GroupVersionKind().Group, + Kind: tcpRoute.GroupVersionKind().Kind, + } + + // Kind should default to Service if not set + // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#L106 + toNS, toGK := createValuesFromRef(backendRef.Namespace, backendRef.Group, backendRef.Kind, "Service") + + return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(backendRef.Name), rv.Client) + +} + +func createValuesFromRef(ns *gwv1beta1.Namespace, group *gwv1beta1.Group, kind *gwv1beta1.Kind, defaultKind string) (string, metav1.GroupKind) { + toNS := "" + if ns != nil { + toNS = string(*ns) + } + + gk := metav1.GroupKind{ + Kind: defaultKind, + } + if group != nil { + gk.Group = string(*group) + } + if kind != nil { + gk.Kind = string(*kind) + } + + return toNS, gk +} + +// referenceAllowed checks to see if a reference between resources is allowed. +// In particular, references from one namespace to a resource in a different namespace +// require an applicable ReferenceGrant be found in the namespace containing the resource +// being referred to. +// +// For example, a Gateway in namespace "foo" may only reference a Secret in namespace "bar" +// if a ReferenceGrant in namespace "bar" allows references from namespace "foo". +func referenceAllowed(ctx context.Context, fromGK metav1.GroupKind, fromNamespace string, toGK metav1.GroupKind, toNamespace, toName string, c client.Client) (bool, error) { + // Reference does not cross namespaces + if toNamespace == "" || toNamespace == fromNamespace { + return true, nil + } + + // Fetch all ReferenceGrants in the referenced namespace + refGrants, err := getReferenceGrantsInNamespace(ctx, toNamespace, c) + if err != nil || len(refGrants) == 0 { + return false, err + } + + for _, refGrant := range refGrants { + // Check for a From that applies + fromMatch := false + for _, from := range refGrant.Spec.From { + if fromGK.Group == string(from.Group) && fromGK.Kind == string(from.Kind) && fromNamespace == string(from.Namespace) { + fromMatch = true + break + } + } + + if !fromMatch { + continue + } + + // Check for a To that applies + for _, to := range refGrant.Spec.To { + if toGK.Group == string(to.Group) && toGK.Kind == string(to.Kind) { + if to.Name == nil || *to.Name == "" { + // No name specified is treated as a wildcard within the namespace + return true, nil + } + + if gwv1beta1.ObjectName(toName) == *to.Name { + // The ReferenceGrant specifically targets this object + return true, nil + } + } + } + } + + // No ReferenceGrant was found which allows this cross-namespace reference + return false, nil +} + +// This function will get all reference grants in the given namespace. +func getReferenceGrantsInNamespace(ctx context.Context, namespace string, c client.Client) ([]gwv1beta1.ReferenceGrant, error) { + refGrantList := &gwv1beta1.ReferenceGrantList{} + if err := c.List(ctx, refGrantList, client.InNamespace(namespace)); err != nil { + return nil, err + } + refGrants := refGrantList.Items + + return refGrants, nil +} diff --git a/control-plane/api-gateway/controllers/reference_validator_test.go b/control-plane/api-gateway/controllers/reference_validator_test.go new file mode 100644 index 0000000000..e507568ae1 --- /dev/null +++ b/control-plane/api-gateway/controllers/reference_validator_test.go @@ -0,0 +1,649 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "k8s.io/apimachinery/pkg/runtime" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const ( + ToNamespace = "toNamespace" + FromNamespace = "fromNamespace" + InvalidNamespace = "invalidNamespace" + Group = "gateway.networking.k8s.io" + V1Beta1 = "/v1beta1" + V1Alpha2 = "/v1alpha2" + HTTPRouteKind = "HTTPRoute" + TCPRouteKind = "TCPRoute" + GatewayKind = "Gateway" + BackendRefKind = "Service" + SecretKind = "Secret" +) + +func TestGatewayCanReferenceSecret(t *testing.T) { + t.Parallel() + + objName := gwv1beta1.ObjectName("mysecret") + + basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ToNamespace, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + { + Group: Group, + Kind: GatewayKind, + Namespace: FromNamespace, + }, + }, + To: []gwv1beta1.ReferenceGrantTo{ + { + Group: Group, + Kind: SecretKind, + Name: &objName, + }, + }, + }, + } + + secretRefGroup := gwv1beta1.Group(Group) + secretRefKind := gwv1beta1.Kind(SecretKind) + secretRefNamespace := gwv1beta1.Namespace(ToNamespace) + + cases := map[string]struct { + canReference bool + err error + ctx context.Context + gateway gwv1beta1.Gateway + secret gwv1beta1.SecretObjectReference + k8sReferenceGrants []runtime.Object + }{ + "gateway allowed to secret": { + canReference: true, + err: nil, + ctx: context.TODO(), + gateway: gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: GatewayKind, + APIVersion: Group + V1Beta1, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: FromNamespace, + }, + Spec: gwv1beta1.GatewaySpec{}, + Status: gwv1beta1.GatewayStatus{}, + }, + secret: gwv1beta1.SecretObjectReference{ + Group: &secretRefGroup, + Kind: &secretRefKind, + Namespace: &secretRefNamespace, + Name: objName, + }, + k8sReferenceGrants: []runtime.Object{ + basicValidReferenceGrant, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, gwv1beta1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() + rv := NewReferenceValidator(client) + canReference, err := rv.GatewayCanReferenceSecret(tc.ctx, tc.gateway, tc.secret) + + require.Equal(t, tc.err, err) + require.Equal(t, tc.canReference, canReference) + }) + } +} + +func TestHTTPRouteCanReferenceGateway(t *testing.T) { + t.Parallel() + + objName := gwv1beta1.ObjectName("mygateway") + + basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ToNamespace, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + { + Group: Group, + Kind: HTTPRouteKind, + Namespace: FromNamespace, + }, + }, + To: []gwv1beta1.ReferenceGrantTo{ + { + Group: Group, + Kind: GatewayKind, + Name: &objName, + }, + }, + }, + } + + gatewayRefGroup := gwv1beta1.Group(Group) + gatewayRefKind := gwv1beta1.Kind(GatewayKind) + gatewayRefNamespace := gwv1beta1.Namespace(ToNamespace) + + cases := map[string]struct { + canReference bool + err error + ctx context.Context + httpRoute gwv1beta1.HTTPRoute + gatewayRef gwv1beta1.ParentReference + k8sReferenceGrants []runtime.Object + }{ + "httproute allowed to gateway": { + canReference: true, + err: nil, + ctx: context.TODO(), + httpRoute: gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: HTTPRouteKind, + APIVersion: Group + V1Beta1, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: FromNamespace, + }, + Spec: gwv1beta1.HTTPRouteSpec{}, + Status: gwv1beta1.HTTPRouteStatus{}, + }, + gatewayRef: gwv1beta1.ParentReference{ + Group: &gatewayRefGroup, + Kind: &gatewayRefKind, + Namespace: &gatewayRefNamespace, + Name: objName, + SectionName: nil, + Port: nil, + }, + k8sReferenceGrants: []runtime.Object{ + basicValidReferenceGrant, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, gwv1beta1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() + rv := NewReferenceValidator(client) + canReference, err := rv.HTTPRouteCanReferenceGateway(tc.ctx, tc.httpRoute, tc.gatewayRef) + + require.Equal(t, tc.err, err) + require.Equal(t, tc.canReference, canReference) + }) + } +} + +func TestHTTPRouteCanReferenceBackend(t *testing.T) { + t.Parallel() + + objName := gwv1beta1.ObjectName("myBackendRef") + + basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ToNamespace, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + { + Group: Group, + Kind: HTTPRouteKind, + Namespace: FromNamespace, + }, + }, + To: []gwv1beta1.ReferenceGrantTo{ + { + Group: Group, + Kind: BackendRefKind, + Name: &objName, + }, + }, + }, + } + + backendRefGroup := gwv1beta1.Group(Group) + backendRefKind := gwv1beta1.Kind(BackendRefKind) + backendRefNamespace := gwv1beta1.Namespace(ToNamespace) + + cases := map[string]struct { + canReference bool + err error + ctx context.Context + httpRoute gwv1beta1.HTTPRoute + backendRef gwv1beta1.BackendRef + k8sReferenceGrants []runtime.Object + }{ + "httproute allowed to gateway": { + canReference: true, + err: nil, + ctx: context.TODO(), + httpRoute: gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: HTTPRouteKind, + APIVersion: Group + V1Beta1, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: FromNamespace, + }, + Spec: gwv1beta1.HTTPRouteSpec{}, + Status: gwv1beta1.HTTPRouteStatus{}, + }, + backendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Group: &backendRefGroup, + Kind: &backendRefKind, + Name: objName, + Namespace: &backendRefNamespace, + Port: nil, + }, + Weight: nil, + }, + k8sReferenceGrants: []runtime.Object{ + basicValidReferenceGrant, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, gwv1beta1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() + rv := NewReferenceValidator(client) + canReference, err := rv.HTTPRouteCanReferenceBackend(tc.ctx, tc.httpRoute, tc.backendRef) + + require.Equal(t, tc.err, err) + require.Equal(t, tc.canReference, canReference) + }) + } +} + +func TestTCPRouteCanReferenceGateway(t *testing.T) { + t.Parallel() + + objName := gwv1beta1.ObjectName("mygateway") + + basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ToNamespace, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + { + Group: Group, + Kind: TCPRouteKind, + Namespace: FromNamespace, + }, + }, + To: []gwv1beta1.ReferenceGrantTo{ + { + Group: Group, + Kind: GatewayKind, + Name: &objName, + }, + }, + }, + } + + gatewayRefGroup := gwv1beta1.Group(Group) + gatewayRefKind := gwv1beta1.Kind(GatewayKind) + gatewayRefNamespace := gwv1beta1.Namespace(ToNamespace) + + cases := map[string]struct { + canReference bool + err error + ctx context.Context + tcpRoute gwv1alpha2.TCPRoute + gatewayRef gwv1beta1.ParentReference + k8sReferenceGrants []runtime.Object + }{ + "tcpRoute allowed to gateway": { + canReference: true, + err: nil, + ctx: context.TODO(), + tcpRoute: gwv1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: TCPRouteKind, + APIVersion: Group + V1Alpha2, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: FromNamespace, + }, + Spec: gwv1alpha2.TCPRouteSpec{}, + Status: gwv1alpha2.TCPRouteStatus{}, + }, + gatewayRef: gwv1beta1.ParentReference{ + Group: &gatewayRefGroup, + Kind: &gatewayRefKind, + Namespace: &gatewayRefNamespace, + Name: objName, + SectionName: nil, + Port: nil, + }, + k8sReferenceGrants: []runtime.Object{ + basicValidReferenceGrant, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, gwv1beta1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() + rv := NewReferenceValidator(client) + canReference, err := rv.TCPRouteCanReferenceGateway(tc.ctx, tc.tcpRoute, tc.gatewayRef) + + require.Equal(t, tc.err, err) + require.Equal(t, tc.canReference, canReference) + }) + } +} + +func TestTCPRouteCanReferenceBackend(t *testing.T) { + t.Parallel() + + objName := gwv1beta1.ObjectName("myBackendRef") + + basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ToNamespace, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + { + Group: Group, + Kind: TCPRouteKind, + Namespace: FromNamespace, + }, + }, + To: []gwv1beta1.ReferenceGrantTo{ + { + Group: Group, + Kind: BackendRefKind, + Name: &objName, + }, + }, + }, + } + + backendRefGroup := gwv1beta1.Group(Group) + backendRefKind := gwv1beta1.Kind(BackendRefKind) + backendRefNamespace := gwv1beta1.Namespace(ToNamespace) + + cases := map[string]struct { + canReference bool + err error + ctx context.Context + tcpRoute gwv1alpha2.TCPRoute + backendRef gwv1beta1.BackendRef + k8sReferenceGrants []runtime.Object + }{ + "tcpRoute allowed to gateway": { + canReference: true, + err: nil, + ctx: context.TODO(), + tcpRoute: gwv1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: TCPRouteKind, + APIVersion: Group + V1Alpha2, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: FromNamespace, + }, + Spec: gwv1alpha2.TCPRouteSpec{}, + Status: gwv1alpha2.TCPRouteStatus{}, + }, + backendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Group: &backendRefGroup, + Kind: &backendRefKind, + Name: objName, + Namespace: &backendRefNamespace, + Port: nil, + }, + Weight: nil, + }, + k8sReferenceGrants: []runtime.Object{ + basicValidReferenceGrant, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, gwv1beta1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() + rv := NewReferenceValidator(client) + canReference, err := rv.TCPRouteCanReferenceBackend(tc.ctx, tc.tcpRoute, tc.backendRef) + + require.Equal(t, tc.err, err) + require.Equal(t, tc.canReference, canReference) + }) + } +} + +func TestReferenceAllowed(t *testing.T) { + t.Parallel() + + objName := gwv1beta1.ObjectName("myObject") + + basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ToNamespace, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + { + Group: Group, + Kind: HTTPRouteKind, + Namespace: FromNamespace, + }, + }, + To: []gwv1beta1.ReferenceGrantTo{ + { + Group: Group, + Kind: GatewayKind, + Name: &objName, + }, + }, + }, + } + + cases := map[string]struct { + refAllowed bool + err error + ctx context.Context + fromGK metav1.GroupKind + fromNamespace string + toGK metav1.GroupKind + toNamespace string + toName string + k8sReferenceGrants []runtime.Object + }{ + "same namespace": { + refAllowed: true, + err: nil, + ctx: context.TODO(), + fromGK: metav1.GroupKind{ + Group: Group, + Kind: HTTPRouteKind, + }, + fromNamespace: FromNamespace, + toGK: metav1.GroupKind{ + Group: Group, + Kind: GatewayKind, + }, + toNamespace: FromNamespace, + toName: string(objName), + k8sReferenceGrants: []runtime.Object{ + &gwv1beta1.ReferenceGrant{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: FromNamespace, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + { + Group: Group, + Kind: HTTPRouteKind, + Namespace: FromNamespace, + }, + }, + To: []gwv1beta1.ReferenceGrantTo{ + { + Group: Group, + Kind: GatewayKind, + Name: &objName, + }, + }, + }, + }, + }, + }, + "reference allowed": { + refAllowed: true, + err: nil, + ctx: context.TODO(), + fromGK: metav1.GroupKind{ + Group: Group, + Kind: HTTPRouteKind, + }, + fromNamespace: FromNamespace, + toGK: metav1.GroupKind{ + Group: Group, + Kind: GatewayKind, + }, + toNamespace: ToNamespace, + toName: string(objName), + k8sReferenceGrants: []runtime.Object{ + basicValidReferenceGrant, + }, + }, + "reference not allowed": { + refAllowed: false, + err: nil, + ctx: context.TODO(), + fromGK: metav1.GroupKind{ + Group: Group, + Kind: HTTPRouteKind, + }, + fromNamespace: InvalidNamespace, + toGK: metav1.GroupKind{ + Group: Group, + Kind: GatewayKind, + }, + toNamespace: ToNamespace, + toName: string(objName), + k8sReferenceGrants: []runtime.Object{ + basicValidReferenceGrant, + }, + }, + "no reference grant defined in namespace": { + refAllowed: false, + err: nil, + ctx: context.TODO(), + fromGK: metav1.GroupKind{ + Group: Group, + Kind: HTTPRouteKind, + }, + fromNamespace: FromNamespace, + toGK: metav1.GroupKind{ + Group: Group, + Kind: GatewayKind, + }, + toNamespace: ToNamespace, + toName: string(objName), + k8sReferenceGrants: nil, + }, + "reference allowed to all objects in namespace": { + refAllowed: true, + err: nil, + ctx: context.TODO(), + fromGK: metav1.GroupKind{ + Group: Group, + Kind: HTTPRouteKind, + }, + fromNamespace: FromNamespace, + toGK: metav1.GroupKind{ + Group: Group, + Kind: GatewayKind, + }, + toNamespace: ToNamespace, + toName: string(objName), + k8sReferenceGrants: []runtime.Object{ + &gwv1beta1.ReferenceGrant{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ToNamespace, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + { + Group: Group, + Kind: HTTPRouteKind, + Namespace: FromNamespace, + }, + }, + To: []gwv1beta1.ReferenceGrantTo{ + { + Group: Group, + Kind: GatewayKind, + Name: nil, + }, + }, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, gwv1beta1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() + + refAllowed, err := referenceAllowed(tc.ctx, tc.fromGK, tc.fromNamespace, tc.toGK, tc.toNamespace, tc.toName, client) + + require.Equal(t, tc.err, err) + require.Equal(t, tc.refAllowed, refAllowed) + }) + } +} diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go new file mode 100644 index 0000000000..6ef7cc04ec --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/dataplane.go @@ -0,0 +1,172 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "fmt" + "strconv" + + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" + + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "k8s.io/apimachinery/pkg/util/intstr" +) + +const ( + consulDataplaneDNSBindHost = "127.0.0.1" + consulDataplaneDNSBindPort = 8600 + sidecarUserAndGroupID = 5995 + defaultPrometheusScrapePath = "/metrics" + defaultEnvoyProxyConcurrency = 1 + volumeName = "consul-connect-inject-data" +) + +func consulDataplaneContainer(config apigateway.HelmConfig, name, namespace string) (corev1.Container, error) { + // Extract the service account token's volume mount. + var ( + err error + bearerTokenFile string + ) + + if config.AuthMethod != "" { + bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" + } + + args, err := getDataplaneArgs(namespace, config, bearerTokenFile, name) + if err != nil { + return corev1.Container{}, err + } + + probe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(constants.ProxyDefaultHealthPort), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + } + + container := corev1.Container{ + Name: name, + Image: config.ImageDataplane, + + // We need to set tmp dir to an ephemeral volume that we're mounting so that + // consul-dataplane can write files to it. Otherwise, it wouldn't be able to + // because we set file system to be read-only. + Env: []corev1.EnvVar{ + { + Name: "TMPDIR", + Value: "/consul/connect-inject", + }, + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "DP_SERVICE_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/consul/connect-inject", + }, + }, + Args: args, + ReadinessProbe: probe, + } + + // Configure the Readiness Address for the proxy's health check to be the Pod IP. + container.Env = append(container.Env, corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + }) + // Configure the port on which the readiness probe will query the proxy for its health. + container.Ports = append(container.Ports, corev1.ContainerPort{ + Name: "proxy-health", + ContainerPort: int32(constants.ProxyDefaultHealthPort), + }) + + // If not running in an OpenShift environment, + // skip setting the security context and let OpenShift set it for us. + if !config.EnableOpenShift { + container.SecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + } + } + + return container, nil +} + +func getDataplaneArgs(namespace string, config apigateway.HelmConfig, bearerTokenFile string, name string) ([]string, error) { + proxyIDFileName := "/consul/connect-inject/proxyid" + envoyConcurrency := defaultEnvoyProxyConcurrency + + args := []string{ + "-addresses", config.ConsulConfig.Address, + "-grpc-port=" + strconv.Itoa(config.ConsulConfig.GRPCPort), + "-proxy-service-id-path=" + proxyIDFileName, + "-log-level=" + config.LogLevel, + "-log-json=" + strconv.FormatBool(config.LogJSON), + "-envoy-concurrency=" + strconv.Itoa(envoyConcurrency), + } + + consulNamespace := namespaces.ConsulNamespace(namespace, config.EnableNamespaces, config.ConsulDestinationNamespace, config.EnableNamespaceMirroring, config.NamespaceMirroringPrefix) + + if config.AuthMethod != "" { + args = append(args, + "-credential-type=login", + "-login-auth-method="+config.AuthMethod, + "-login-bearer-token-path="+bearerTokenFile, + "-login-meta="+fmt.Sprintf("gateway=%s/%s", namespace, name), + ) + if config.EnableNamespaces { + args = append(args, "-login-namespace="+consulNamespace) + } + if config.ConsulPartition != "" { + args = append(args, "-login-partition="+config.ConsulPartition) + } + } + if config.EnableNamespaces { + args = append(args, "-service-namespace="+consulNamespace) + } + if config.ConsulPartition != "" { + args = append(args, "-service-partition="+config.ConsulPartition) + } + if config.TLSEnabled { + if config.ConsulTLSServerName != "" { + args = append(args, "-tls-server-name="+config.ConsulTLSServerName) + } + if config.ConsulCACert != "" { + args = append(args, "-ca-certs="+constants.ConsulCAFile) + } + } else { + args = append(args, "-tls-disabled") + } + + // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. + args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) + + args = append(args, fmt.Sprintf("-envoy-admin-bind-port=%d", 19000)) + + // Set a default scrape path that can be overwritten by the annotation. + prometheusScrapePath := defaultPrometheusScrapePath + args = append(args, "-telemetry-prom-scrape-path="+prometheusScrapePath) + + return args, nil +} diff --git a/control-plane/api-gateway/gatekeeper/deployment.go b/control-plane/api-gateway/gatekeeper/deployment.go new file mode 100644 index 0000000000..0c46dbb0a5 --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/deployment.go @@ -0,0 +1,234 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "context" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "k8s.io/apimachinery/pkg/types" + + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + appsv1 "k8s.io/api/apps/v1" + 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/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +const ( + defaultInstances int32 = 1 +) + +func (g *Gatekeeper) upsertDeployment(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig) error { + // Get Deployment if it exists. + existingDeployment := &appsv1.Deployment{} + exists := false + + err := g.Client.Get(ctx, g.namespacedName(gateway), existingDeployment) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } else if k8serrors.IsNotFound(err) { + exists = false + } else { + exists = true + } + + var currentReplicas *int32 + if exists { + currentReplicas = existingDeployment.Spec.Replicas + } + + deployment, err := g.deployment(gateway, gcc, config, currentReplicas) + if err != nil { + return err + } + + if exists { + g.Log.Info("Existing Gateway Deployment found.") + + // If the user has set the number of replicas, let's respect that. + deployment.Spec.Replicas = existingDeployment.Spec.Replicas + } + + mutated := deployment.DeepCopy() + mutator := newDeploymentMutator(deployment, mutated, gcc, gateway, g.Client.Scheme()) + + result, err := controllerutil.CreateOrUpdate(ctx, g.Client, mutated, mutator) + if err != nil { + return err + } + + switch result { + case controllerutil.OperationResultCreated: + g.Log.Info("Created Deployment") + case controllerutil.OperationResultUpdated: + g.Log.Info("Updated Deployment") + case controllerutil.OperationResultNone: + g.Log.Info("No change to deployment") + } + + return nil +} + +func (g *Gatekeeper) deleteDeployment(ctx context.Context, nsname types.NamespacedName) error { + err := g.Client.Delete(ctx, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: nsname.Name, Namespace: nsname.Namespace}}) + if k8serrors.IsNotFound(err) { + return nil + } + + return err +} + +func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig, currentReplicas *int32) (*appsv1.Deployment, error) { + initContainer, err := initContainer(config, gateway.Name, gateway.Namespace) + if err != nil { + return nil, err + } + + container, err := consulDataplaneContainer(config, gateway.Name, gateway.Namespace) + if err != nil { + return nil, err + } + + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: gateway.Name, + Namespace: gateway.Namespace, + Labels: apigateway.LabelsForGateway(&gateway), + }, + Spec: appsv1.DeploymentSpec{ + Replicas: deploymentReplicas(gcc, currentReplicas), + Selector: &metav1.LabelSelector{ + MatchLabels: apigateway.LabelsForGateway(&gateway), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: apigateway.LabelsForGateway(&gateway), + Annotations: map[string]string{ + "consul.hashicorp.com/connect-inject": "false", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, + }, + }, + }, + InitContainers: []corev1.Container{ + initContainer, + }, + Containers: []corev1.Container{ + container, + }, + Affinity: &corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 1, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: apigateway.LabelsForGateway(&gateway), + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, + }, + NodeSelector: gcc.Spec.NodeSelector, + Tolerations: gcc.Spec.Tolerations, + ServiceAccountName: g.serviceAccountName(gateway, config), + }, + }, + }, + }, nil +} + +func mergeDeployments(gcc v1alpha1.GatewayClassConfig, a, b *appsv1.Deployment) *appsv1.Deployment { + if !compareDeployments(a, b) { + b.Spec.Template = a.Spec.Template + b.Spec.Replicas = deploymentReplicas(gcc, a.Spec.Replicas) + } + + return b +} + +func compareDeployments(a, b *appsv1.Deployment) bool { + // since K8s adds a bunch of defaults when we create a deployment, check that + // they don't differ by the things that we may actually change, namely container + // ports + if len(b.Spec.Template.Spec.Containers) != len(a.Spec.Template.Spec.Containers) { + return false + } + for i, container := range a.Spec.Template.Spec.Containers { + otherPorts := b.Spec.Template.Spec.Containers[i].Ports + if len(container.Ports) != len(otherPorts) { + return false + } + for j, port := range container.Ports { + otherPort := otherPorts[j] + if port.ContainerPort != otherPort.ContainerPort { + return false + } + if port.Protocol != otherPort.Protocol { + return false + } + } + } + + if b.Spec.Replicas == nil && a.Spec.Replicas == nil { + return true + } else if b.Spec.Replicas == nil { + return false + } else if a.Spec.Replicas == nil { + return false + } + + return *b.Spec.Replicas == *a.Spec.Replicas +} + +func newDeploymentMutator(deployment, mutated *appsv1.Deployment, gcc v1alpha1.GatewayClassConfig, gateway gwv1beta1.Gateway, scheme *runtime.Scheme) resourceMutator { + return func() error { + mutated = mergeDeployments(gcc, deployment, mutated) + return ctrl.SetControllerReference(&gateway, mutated, scheme) + } +} + +func deploymentReplicas(gcc v1alpha1.GatewayClassConfig, currentReplicas *int32) *int32 { + instanceValue := defaultInstances + + //if currentReplicas is not nil use current value when building deployment + if currentReplicas != nil { + instanceValue = *currentReplicas + } else if gcc.Spec.DeploymentSpec.DefaultInstances != nil { + // otherwise use the default value on the GatewayClassConfig if set + instanceValue = *gcc.Spec.DeploymentSpec.DefaultInstances + } + + if gcc.Spec.DeploymentSpec.MaxInstances != nil { + + //check if over maximum and lower to maximum + maxValue := *gcc.Spec.DeploymentSpec.MaxInstances + if instanceValue > maxValue { + instanceValue = maxValue + } + } + + if gcc.Spec.DeploymentSpec.MinInstances != nil { + //check if less than minimum and raise to minimum + minValue := *gcc.Spec.DeploymentSpec.MinInstances + if instanceValue < minValue { + instanceValue = minValue + } + + } + return &instanceValue +} diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper.go b/control-plane/api-gateway/gatekeeper/gatekeeper.go new file mode 100644 index 0000000000..e333b6a8ea --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/gatekeeper.go @@ -0,0 +1,91 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// Gatekeeper is used to manage the lifecycle of Gateway deployments and services. +type Gatekeeper struct { + Log logr.Logger + Client client.Client +} + +// New creates a new Gatekeeper from the Config. +func New(log logr.Logger, client client.Client) *Gatekeeper { + return &Gatekeeper{ + Log: log, + Client: client, + } +} + +// Upsert creates or updates the resources for handling routing of network traffic. +func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig) error { + g.Log.Info(fmt.Sprintf("Upsert Gateway Deployment %s/%s", gateway.Namespace, gateway.Name)) + + if err := g.upsertRole(ctx, gateway, gcc, config); err != nil { + return err + } + + if err := g.upsertServiceAccount(ctx, gateway, config); err != nil { + return err + } + + if err := g.upsertService(ctx, gateway, gcc, config); err != nil { + return err + } + + if err := g.upsertDeployment(ctx, gateway, gcc, config); err != nil { + return err + } + + return nil +} + +// Delete removes the resources for handling routing of network traffic. +func (g *Gatekeeper) Delete(ctx context.Context, nsname types.NamespacedName) error { + if err := g.deleteRole(ctx, nsname); err != nil { + return err + } + + if err := g.deleteServiceAccount(ctx, nsname); err != nil { + return err + } + + if err := g.deleteService(ctx, nsname); err != nil { + return err + } + + if err := g.deleteDeployment(ctx, nsname); err != nil { + return err + } + + return nil +} + +// resourceMutator is passed to create or update functions to mutate Kubernetes resources. +type resourceMutator = func() error + +func (g Gatekeeper) namespacedName(gateway gwv1beta1.Gateway) types.NamespacedName { + return types.NamespacedName{ + Namespace: gateway.Namespace, + Name: gateway.Name, + } +} + +func (g Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config apigateway.HelmConfig) string { + if config.AuthMethod == "" { + return "" + } + return gateway.Name +} diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go new file mode 100644 index 0000000000..92a92d66f2 --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -0,0 +1,896 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "context" + "fmt" + "testing" + + logrtest "github.com/go-logr/logr/testr" + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbac "k8s.io/api/rbac/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +var ( + createdAtLabelKey = "gateway.consul.hashicorp.com/created" + createdAtLabelValue = "101010" + name = "test" + namespace = "default" + labels = map[string]string{ + "gateway.consul.hashicorp.com/name": name, + "gateway.consul.hashicorp.com/namespace": namespace, + createdAtLabelKey: createdAtLabelValue, + "gateway.consul.hashicorp.com/managed": "true", + } + listeners = []gwv1beta1.Listener{ + { + Name: "Listener 1", + Port: 8080, + Protocol: "TCP", + }, + { + Name: "Listener 2", + Port: 8081, + Protocol: "UDP", + }, + } +) + +type testCase struct { + gateway gwv1beta1.Gateway + gatewayClassConfig v1alpha1.GatewayClassConfig + helmConfig apigateway.HelmConfig + + initialResources resources + finalResources resources +} + +type resources struct { + deployments []*appsv1.Deployment + roles []*rbac.Role + services []*corev1.Service + serviceAccounts []*corev1.ServiceAccount +} + +func TestUpsert(t *testing.T) { + t.Parallel() + + cases := map[string]testCase{ + "create a new gateway deployment with only Deployment": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(3)), + MaxInstances: ptrTo(int32(3)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{}, + initialResources: resources{}, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + "create a new gateway deployment with managed Service": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(3)), + MaxInstances: ptrTo(int32(3)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{}, + initialResources: resources{}, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + }, + { + Name: "Listener 2", + Protocol: "UDP", + Port: 8081, + }, + }, "1"), + }, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + "create a new gateway deployment with managed Service and ACLs": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(3)), + MaxInstances: ptrTo(int32(3)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{ + AuthMethod: "method", + }, + initialResources: resources{}, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{ + configureRole(name, namespace, labels, "1"), + }, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + }, + { + Name: "Listener 2", + Protocol: "UDP", + Port: 8081, + }, + }, "1"), + }, + serviceAccounts: []*corev1.ServiceAccount{ + configureServiceAccount(name, namespace, labels, "1"), + }, + }, + }, + "update a gateway, adding a listener to a service": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(3)), + MaxInstances: ptrTo(int32(3)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{ + AuthMethod: "method", + }, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{ + configureRole(name, namespace, labels, "1"), + }, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + }, + }, "1"), + }, + serviceAccounts: []*corev1.ServiceAccount{ + configureServiceAccount(name, namespace, labels, "1"), + }, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"), + }, + roles: []*rbac.Role{ + configureRole(name, namespace, labels, "1"), + }, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + }, + { + Name: "Listener 2", + Protocol: "UDP", + Port: 8081, + }, + }, "2"), + }, + serviceAccounts: []*corev1.ServiceAccount{ + configureServiceAccount(name, namespace, labels, "1"), + }, + }, + }, + "update a gateway, removing a listener from a service": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + listeners[0], + }, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(3)), + MaxInstances: ptrTo(int32(3)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{ + AuthMethod: "method", + }, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{ + configureRole(name, namespace, labels, "1"), + }, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + }, + { + Name: "Listener 2", + Protocol: "UDP", + Port: 8081, + }, + }, "1"), + }, + serviceAccounts: []*corev1.ServiceAccount{ + configureServiceAccount(name, namespace, labels, "1"), + }, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"), + }, + roles: []*rbac.Role{ + configureRole(name, namespace, labels, "1"), + }, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + }, + }, "2"), + }, + serviceAccounts: []*corev1.ServiceAccount{ + configureServiceAccount(name, namespace, labels, "1"), + }, + }, + }, + "updating a gateway deployment respects the number of replicas a user has set": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(5)), + MaxInstances: ptrTo(int32(7)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{}, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), + }, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + require.NoError(t, rbac.AddToScheme(s)) + require.NoError(t, corev1.AddToScheme(s)) + require.NoError(t, appsv1.AddToScheme(s)) + + log := logrtest.New(t) + + objs := append(joinResources(tc.initialResources), &tc.gateway, &tc.gatewayClassConfig) + client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() + + gatekeeper := New(log, client) + + err := gatekeeper.Upsert(context.Background(), tc.gateway, tc.gatewayClassConfig, tc.helmConfig) + require.NoError(t, err) + require.NoError(t, validateResourcesExist(t, client, tc.finalResources)) + }) + } +} + +func TestDelete(t *testing.T) { + t.Parallel() + + cases := map[string]testCase{ + "delete a gateway deployment with only Deployment": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(3)), + MaxInstances: ptrTo(int32(3)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{}, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{}, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + "delete a gateway deployment with a managed Service": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(3)), + MaxInstances: ptrTo(int32(3)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{}, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + }, + { + Name: "Listener 2", + Protocol: "UDP", + Port: 8081, + }, + }, "1"), + }, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{}, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + "delete a gateway deployment with managed Service and ACLs": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: ptrTo(int32(3)), + MaxInstances: ptrTo(int32(3)), + MinInstances: ptrTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + }, + }, + helmConfig: apigateway.HelmConfig{ + AuthMethod: "method", + }, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{ + configureRole(name, namespace, labels, "1"), + }, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + }, + { + Name: "Listener 2", + Protocol: "UDP", + Port: 8081, + }, + }, "1"), + }, + serviceAccounts: []*corev1.ServiceAccount{ + configureServiceAccount(name, namespace, labels, "1"), + }, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{}, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + require.NoError(t, rbac.AddToScheme(s)) + require.NoError(t, corev1.AddToScheme(s)) + require.NoError(t, appsv1.AddToScheme(s)) + + log := logrtest.New(t) + + objs := append(joinResources(tc.initialResources), &tc.gateway, &tc.gatewayClassConfig) + client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() + + gatekeeper := New(log, client) + + err := gatekeeper.Delete(context.Background(), types.NamespacedName{ + Namespace: tc.gateway.Namespace, + Name: tc.gateway.Name, + }) + require.NoError(t, err) + require.NoError(t, validateResourcesExist(t, client, tc.finalResources)) + require.NoError(t, validateResourcesAreDeleted(t, client, tc.initialResources)) + }) + } +} + +func joinResources(resources resources) (objs []client.Object) { + for _, deployment := range resources.deployments { + objs = append(objs, deployment) + } + + for _, role := range resources.roles { + objs = append(objs, role) + } + + for _, service := range resources.services { + objs = append(objs, service) + } + + for _, serviceAccount := range resources.serviceAccounts { + objs = append(objs, serviceAccount) + } + + return objs +} + +func validateResourcesExist(t *testing.T, client client.Client, resources resources) error { + t.Helper() + + for _, expected := range resources.deployments { + actual := &appsv1.Deployment{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if err != nil { + return err + } + + // Patch the createdAt label + actual.Labels[createdAtLabelKey] = createdAtLabelValue + actual.Spec.Selector.MatchLabels[createdAtLabelKey] = createdAtLabelValue + actual.Spec.Template.ObjectMeta.Labels[createdAtLabelKey] = createdAtLabelValue + + require.Equal(t, expected.Name, actual.Name) + require.Equal(t, expected.Namespace, actual.Namespace) + require.Equal(t, expected.APIVersion, actual.APIVersion) + require.Equal(t, expected.Labels, actual.Labels) + if expected.Spec.Replicas != nil { + require.NotNil(t, actual.Spec.Replicas) + require.EqualValues(t, *expected.Spec.Replicas, *actual.Spec.Replicas) + } + } + + for _, expected := range resources.roles { + actual := &rbac.Role{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if err != nil { + return err + } + + // Patch the createdAt label + actual.Labels[createdAtLabelKey] = createdAtLabelValue + + require.Equal(t, expected, actual) + } + + for _, expected := range resources.services { + actual := &corev1.Service{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if err != nil { + return err + } + + // Patch the createdAt label + actual.Labels[createdAtLabelKey] = createdAtLabelValue + actual.Spec.Selector[createdAtLabelKey] = createdAtLabelValue + + require.Equal(t, expected, actual) + } + + for _, expected := range resources.serviceAccounts { + actual := &corev1.ServiceAccount{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if err != nil { + return err + } + + // Patch the createdAt label + actual.Labels[createdAtLabelKey] = createdAtLabelValue + + require.Equal(t, expected, actual) + } + + return nil +} + +func validateResourcesAreDeleted(t *testing.T, client client.Client, resources resources) error { + t.Helper() + + for _, expected := range resources.deployments { + actual := &appsv1.Deployment{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if !k8serrors.IsNotFound(err) { + return fmt.Errorf("expected deployment %s to be deleted", expected.Name) + } + require.Error(t, err) + } + + for _, expected := range resources.roles { + actual := &rbac.Role{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if !k8serrors.IsNotFound(err) { + return fmt.Errorf("expected role %s to be deleted", expected.Name) + } + require.Error(t, err) + } + + for _, expected := range resources.services { + actual := &corev1.Service{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if !k8serrors.IsNotFound(err) { + return fmt.Errorf("expected service %s to be deleted", expected.Name) + } + require.Error(t, err) + } + + for _, expected := range resources.serviceAccounts { + actual := &corev1.ServiceAccount{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if !k8serrors.IsNotFound(err) { + return fmt.Errorf("expected service account %s to be deleted", expected.Name) + } + require.Error(t, err) + } + + 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 { + return &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + ResourceVersion: resourceVersion, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "gateway.networking.k8s.io/v1beta1", + Kind: "Gateway", + Name: name, + Controller: ptrTo(true), + BlockOwnerDeletion: ptrTo(true), + }, + }, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + Annotations: map[string]string{ + "consul.hashicorp.com/connect-inject": "false", + }, + }, + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 1, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, + }, + NodeSelector: nodeSelector, + Tolerations: tolerations, + ServiceAccountName: serviceAccoutName, + }, + }, + }, + } +} + +func configureRole(name, namespace string, labels map[string]string, resourceVersion string) *rbac.Role { + return &rbac.Role{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "Role", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + ResourceVersion: resourceVersion, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "gateway.networking.k8s.io/v1beta1", + Kind: "Gateway", + Name: name, + Controller: ptrTo(true), + BlockOwnerDeletion: ptrTo(true), + }, + }, + }, + Rules: []rbac.PolicyRule{}, + } +} + +func configureService(name, namespace string, labels, annotations map[string]string, serviceType corev1.ServiceType, ports []corev1.ServicePort, resourceVersion string) *corev1.Service { + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + Annotations: annotations, + ResourceVersion: resourceVersion, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "gateway.networking.k8s.io/v1beta1", + Kind: "Gateway", + Name: name, + Controller: ptrTo(true), + BlockOwnerDeletion: ptrTo(true), + }, + }, + }, + Spec: corev1.ServiceSpec{ + Selector: labels, + Type: serviceType, + Ports: ports, + }, + } +} + +func configureServiceAccount(name, namespace string, labels map[string]string, resourceVersion string) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + ResourceVersion: resourceVersion, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "gateway.networking.k8s.io/v1beta1", + Kind: "Gateway", + Name: name, + Controller: ptrTo(true), + BlockOwnerDeletion: ptrTo(true), + }, + }, + }, + } +} + +func ptrTo[T any](t T) *T { + return &t +} diff --git a/control-plane/api-gateway/gatekeeper/init.go b/control-plane/api-gateway/gatekeeper/init.go new file mode 100644 index 0000000000..4f53769c11 --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/init.go @@ -0,0 +1,199 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "bytes" + "strconv" + "strings" + "text/template" + + corev1 "k8s.io/api/core/v1" + + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "k8s.io/utils/pointer" +) + +const ( + injectInitContainerName = "consul-connect-inject-init" + initContainersUserAndGroupID = 5996 +) + +type initContainerCommandData struct { + ServiceName string + ServiceAccountName string + AuthMethod string + + // Log settings for the connect-init command. + LogLevel string + LogJSON bool +} + +// containerInit 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 initContainer(config apigateway.HelmConfig, name, namespace string) (corev1.Container, error) { + data := initContainerCommandData{ + AuthMethod: config.AuthMethod, + LogLevel: config.LogLevel, + LogJSON: config.LogJSON, + ServiceName: name, + ServiceAccountName: name, + } + + // Create expected volume mounts + volMounts := []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/consul/connect-inject", + }, + } + + var bearerTokenFile string + if config.AuthMethod != "" { + bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" + } + + // Render the command + var buf bytes.Buffer + tpl := template.Must(template.New("root").Parse(strings.TrimSpace(initContainerCommandTpl))) + + if err := tpl.Execute(&buf, &data); err != nil { + return corev1.Container{}, err + } + + consulNamespace := namespaces.ConsulNamespace(namespace, config.EnableNamespaces, config.ConsulDestinationNamespace, config.EnableNamespaceMirroring, config.NamespaceMirroringPrefix) + + initContainerName := injectInitContainerName + container := corev1.Container{ + Name: initContainerName, + Image: config.ImageConsulK8S, + + Env: []corev1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "CONSUL_ADDRESSES", + Value: config.ConsulConfig.Address, + }, + { + Name: "CONSUL_GRPC_PORT", + Value: strconv.Itoa(config.ConsulConfig.GRPCPort), + }, + { + Name: "CONSUL_HTTP_PORT", + Value: strconv.Itoa(config.ConsulConfig.HTTPPort), + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: config.ConsulConfig.APITimeout.String(), + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + }, + VolumeMounts: volMounts, + Command: []string{"/bin/sh", "-ec", buf.String()}, + } + + if config.TLSEnabled { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_USE_TLS", + Value: "true", + }, + corev1.EnvVar{ + Name: "CONSUL_CACERT_PEM", + Value: config.ConsulCACert, + }, + corev1.EnvVar{ + Name: "CONSUL_TLS_SERVER_NAME", + Value: config.ConsulTLSServerName, + }) + } + + if config.AuthMethod != "" { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_AUTH_METHOD", + Value: config.AuthMethod, + }, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", + Value: bearerTokenFile, + }, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }) + + container.Env = append(container.Env, corev1.EnvVar{ + Name: "CONSUL_LOGIN_NAMESPACE", + Value: consulNamespace, + }) + + if config.ConsulPartition != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "CONSUL_LOGIN_PARTITION", + Value: config.ConsulPartition, + }) + } + } + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_NAMESPACE", + Value: consulNamespace, + }) + + if config.ConsulPartition != "" { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_PARTITION", + Value: config.ConsulPartition, + }) + } + + container.SecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(initContainersUserAndGroupID), + RunAsGroup: pointer.Int64(initContainersUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + Privileged: pointer.Bool(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + } + + return container, nil +} + +// initContainerCommandTpl is the template for the command executed by +// the init container. +const initContainerCommandTpl = ` +consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -gateway-kind="api-gateway" \ + -log-json={{ .LogJSON }} \ + {{- if .AuthMethod }} + -service-account-name="{{ .ServiceAccountName }}" \ + {{- end }} + -service-name="{{ .ServiceName }}" +` diff --git a/control-plane/api-gateway/gatekeeper/role.go b/control-plane/api-gateway/gatekeeper/role.go new file mode 100644 index 0000000000..604c503be5 --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/role.go @@ -0,0 +1,90 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "context" + "errors" + + "k8s.io/apimachinery/pkg/types" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "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" +) + +func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig) error { + if config.AuthMethod == "" { + return g.deleteRole(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) + } + + role := &rbac.Role{} + exists := false + + // Get ServiceAccount + err := g.Client.Get(ctx, g.namespacedName(gateway), role) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } else if k8serrors.IsNotFound(err) { + exists = false + } else { + exists = true + } + + if exists { + // Ensure we own the Role. + for _, ref := range role.GetOwnerReferences() { + if ref.UID == gateway.GetUID() && ref.Name == gateway.GetName() { + // We found ourselves! + return nil + } + } + return errors.New("Role not owned by controller") + } + + role = g.role(gateway, gcc) + if err := ctrl.SetControllerReference(&gateway, role, g.Client.Scheme()); err != nil { + return err + } + if err := g.Client.Create(ctx, role); err != nil { + return err + } + + return nil +} + +func (g *Gatekeeper) deleteRole(ctx context.Context, nsname types.NamespacedName) error { + if err := g.Client.Delete(ctx, &rbac.Role{ObjectMeta: metav1.ObjectMeta{Name: nsname.Name, Namespace: nsname.Namespace}}); err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + return nil +} + +func (g *Gatekeeper) role(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig) *rbac.Role { + role := &rbac.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: gateway.Name, + Namespace: gateway.Namespace, + Labels: apigateway.LabelsForGateway(&gateway), + }, + Rules: []rbac.PolicyRule{}, + } + if gcc.Spec.PodSecurityPolicy != "" { + role.Rules = append(role.Rules, rbac.PolicyRule{ + APIGroups: []string{"policy"}, + Resources: []string{"podsecuritypolicies"}, + ResourceNames: []string{gcc.Spec.PodSecurityPolicy}, + Verbs: []string{"use"}, + }) + } + return role +} diff --git a/control-plane/api-gateway/gatekeeper/service.go b/control-plane/api-gateway/gatekeeper/service.go new file mode 100644 index 0000000000..1191879390 --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/service.go @@ -0,0 +1,143 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "context" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "k8s.io/apimachinery/pkg/types" + + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +var ( + defaultServiceAnnotations = []string{ + "external-dns.alpha.kubernetes.io/hostname", + } +) + +func (g *Gatekeeper) upsertService(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig) error { + if gcc.Spec.ServiceType == nil { + return g.deleteService(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) + } + + service := g.service(gateway, gcc) + + mutated := service.DeepCopy() + mutator := newServiceMutator(service, mutated, gateway, g.Client.Scheme()) + + result, err := controllerutil.CreateOrUpdate(ctx, g.Client, mutated, mutator) + if err != nil { + return err + } + + switch result { + case controllerutil.OperationResultCreated: + g.Log.Info("Created Service") + case controllerutil.OperationResultUpdated: + g.Log.Info("Updated Service") + case controllerutil.OperationResultNone: + g.Log.Info("No change to service") + } + + return nil +} + +func (g *Gatekeeper) deleteService(ctx context.Context, nsname types.NamespacedName) error { + if err := g.Client.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: nsname.Name, Namespace: nsname.Namespace}}); err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + return nil +} + +func (g *Gatekeeper) service(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig) *corev1.Service { + ports := []corev1.ServicePort{} + for _, listener := range gateway.Spec.Listeners { + ports = append(ports, corev1.ServicePort{ + Name: string(listener.Name), + Protocol: corev1.Protocol(listener.Protocol), + Port: int32(listener.Port), + }) + } + + // Copy annotations from the Gateway, filtered by those allowed by the GatewayClassConfig. + allowedAnnotations := gcc.Spec.CopyAnnotations.Service + if allowedAnnotations == nil { + allowedAnnotations = defaultServiceAnnotations + } + annotations := make(map[string]string) + for _, allowedAnnotation := range allowedAnnotations { + if value, found := gateway.Annotations[allowedAnnotation]; found { + annotations[allowedAnnotation] = value + } + } + + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: gateway.Name, + Namespace: gateway.Namespace, + Labels: apigateway.LabelsForGateway(&gateway), + Annotations: annotations, + }, + Spec: corev1.ServiceSpec{ + Selector: apigateway.LabelsForGateway(&gateway), + Type: *gcc.Spec.ServiceType, + Ports: ports, + }, + } +} + +// mergeService is used to keep annotations and ports from the `from` Service +// to the `to` service. This prevents an infinite reconciliation loop when +// Kubernetes adds this configuration back in. +func mergeService(from, to *corev1.Service) *corev1.Service { + if areServicesEqual(from, to) { + return to + } + + to.Annotations = from.Annotations + to.Spec.Ports = from.Spec.Ports + + return to +} + +func areServicesEqual(a, b *corev1.Service) bool { + if !equality.Semantic.DeepEqual(a.Annotations, b.Annotations) { + return false + } + if len(b.Spec.Ports) != len(a.Spec.Ports) { + return false + } + + for i, port := range a.Spec.Ports { + otherPort := b.Spec.Ports[i] + if port.Port != otherPort.Port { + return false + } + if port.Protocol != otherPort.Protocol { + return false + } + } + return true +} + +func newServiceMutator(service, mutated *corev1.Service, gateway gwv1beta1.Gateway, scheme *runtime.Scheme) resourceMutator { + return func() error { + mutated = mergeService(service, mutated) + return ctrl.SetControllerReference(&gateway, mutated, scheme) + } +} diff --git a/control-plane/api-gateway/gatekeeper/serviceaccount.go b/control-plane/api-gateway/gatekeeper/serviceaccount.go new file mode 100644 index 0000000000..207f4485c4 --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/serviceaccount.go @@ -0,0 +1,80 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "context" + "errors" + + "k8s.io/apimachinery/pkg/types" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" +) + +func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1.Gateway, config apigateway.HelmConfig) error { + if config.AuthMethod == "" { + return g.deleteServiceAccount(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) + } + + serviceAccount := &corev1.ServiceAccount{} + exists := false + + // Get ServiceAccount if it exists. + err := g.Client.Get(ctx, g.namespacedName(gateway), serviceAccount) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } else if k8serrors.IsNotFound(err) { + exists = false + } else { + exists = true + } + + if exists { + // Ensure we own the ServiceAccount. + for _, ref := range serviceAccount.GetOwnerReferences() { + if ref.UID == gateway.GetUID() && ref.Name == gateway.GetName() { + // We found ourselves! + return nil + } + } + return errors.New("ServiceAccount not owned by controller") + } + + // Create the ServiceAccount. + serviceAccount = g.serviceAccount(gateway) + 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 +} + +func (g *Gatekeeper) deleteServiceAccount(ctx context.Context, nsname types.NamespacedName) error { + if err := g.Client.Delete(ctx, &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: nsname.Name, Namespace: nsname.Namespace}}); err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + return nil +} + +func (g *Gatekeeper) serviceAccount(gateway gwv1beta1.Gateway) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: gateway.Name, + Namespace: gateway.Namespace, + Labels: apigateway.LabelsForGateway(&gateway), + }, + } +} diff --git a/control-plane/api-gateway/helm_config.go b/control-plane/api-gateway/helm_config.go new file mode 100644 index 0000000000..ef7ab22df3 --- /dev/null +++ b/control-plane/api-gateway/helm_config.go @@ -0,0 +1,35 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package apigateway + +import "time" + +// HelmConfig is the configuration of gateways that comes in from the user's Helm values. +type HelmConfig struct { + // ImageDataplane is the Consul Dataplane image to use in gateway deployments. + ImageDataplane string + ImageConsulK8S string + ConsulDestinationNamespace string + NamespaceMirroringPrefix string + EnableNamespaces bool + EnableOpenShift bool + EnableNamespaceMirroring bool + AuthMethod string + // LogLevel is the logging level of the deployed Consul Dataplanes. + LogLevel string + ConsulPartition string + LogJSON bool + TLSEnabled bool + PeeringEnabled bool + ConsulTLSServerName string + ConsulCACert string + ConsulConfig ConsulConfig +} + +type ConsulConfig struct { + Address string + GRPCPort int + HTTPPort int + APITimeout time.Duration +} diff --git a/control-plane/api-gateway/labels.go b/control-plane/api-gateway/labels.go new file mode 100644 index 0000000000..3b4f7c5f1f --- /dev/null +++ b/control-plane/api-gateway/labels.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package apigateway + +import ( + "fmt" + + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +const ( + nameLabel = "gateway.consul.hashicorp.com/name" + namespaceLabel = "gateway.consul.hashicorp.com/namespace" + createdAtLabel = "gateway.consul.hashicorp.com/created" + managedLabel = "gateway.consul.hashicorp.com/managed" +) + +// LabelsForGateway formats the default labels that appear on objects managed by the controllers. +func LabelsForGateway(gateway *gwv1beta1.Gateway) map[string]string { + return map[string]string{ + nameLabel: gateway.Name, + namespaceLabel: gateway.Namespace, + createdAtLabel: fmt.Sprintf("%d", gateway.CreationTimestamp.Unix()), + managedLabel: "true", + } +} diff --git a/control-plane/api-gateway/translation/config_entry_translation.go b/control-plane/api-gateway/translation/config_entry_translation.go new file mode 100644 index 0000000000..9501859f8b --- /dev/null +++ b/control-plane/api-gateway/translation/config_entry_translation.go @@ -0,0 +1,516 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package translation handles translating resources between different types +package translation + +import ( + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul/api" + capi "github.com/hashicorp/consul/api" +) + +const ( + metaKeyManagedBy = "managed-by" + metaValueManagedBy = "consul-k8s-gateway-controller" + metaKeyKubeNS = "k8s-namespace" + metaKeyKubeServiceName = "k8s-service-name" + metaKeyKubeName = "k8s-name" + + // AnnotationGateway is the annotation used to override the gateway name. + AnnotationGateway = "consul.hashicorp.com/gateway" + // AnnotationHTTPRoute is the annotation used to override the http route name. + AnnotationHTTPRoute = "consul.hashicorp.com/http-route" + // AnnotationTCPRoute is the annotation used to override the tcp route name. + AnnotationTCPRoute = "consul.hashicorp.com/tcp-route" + // AnnotationInlineCertificate is the annotation used to override the inline certificate name. + AnnotationInlineCertificate = "consul.hashicorp.com/inline-certificate" +) + +func translateListenerProtocol[T ~string](protocol T) string { + return strings.ToLower(string(protocol)) +} + +// K8sToConsulTranslator handles translating K8s resources into Consul config entries. +type K8sToConsulTranslator struct { + EnableConsulNamespaces bool + ConsulDestNamespace string + EnableK8sMirroring bool + MirroringPrefix string + ConsulPartition string +} + +// GatewayToAPIGateway translates a kuberenetes API gateway into a Consul APIGateway Config Entry. +func (t K8sToConsulTranslator) GatewayToAPIGateway(k8sGW gwv1beta1.Gateway, certs map[types.NamespacedName]api.ResourceReference) capi.APIGatewayConfigEntry { + listeners := make([]capi.APIGatewayListener, 0, len(k8sGW.Spec.Listeners)) + for _, listener := range k8sGW.Spec.Listeners { + var certificates []capi.ResourceReference + if listener.TLS != nil { + certificates = make([]capi.ResourceReference, 0, len(listener.TLS.CertificateRefs)) + for _, certificate := range listener.TLS.CertificateRefs { + k8sNS := "" + if certificate.Namespace != nil { + k8sNS = string(*certificate.Namespace) + } + nsn := types.NamespacedName{Name: string(certificate.Name), Namespace: k8sNS} + certRef, ok := certs[nsn] + if !ok { + // we don't have a ref for this certificate in consul + // drop the ref from the created gateway + continue + } + c := capi.ResourceReference{ + Kind: capi.InlineCertificate, + Name: certRef.Name, + Partition: certRef.Partition, + Namespace: certRef.Namespace, + } + certificates = append(certificates, c) + } + } + hostname := "" + if listener.Hostname != nil { + hostname = string(*listener.Hostname) + } + l := capi.APIGatewayListener{ + Name: string(listener.Name), + Hostname: hostname, + Port: int(listener.Port), + Protocol: translateListenerProtocol(listener.Protocol), + TLS: capi.APIGatewayTLSConfiguration{ + Certificates: certificates, + }, + } + + listeners = append(listeners, l) + } + gwName := k8sGW.Name + + if gwNameFromAnnotation, ok := k8sGW.Annotations[AnnotationGateway]; ok && gwNameFromAnnotation != "" && !strings.Contains(gwNameFromAnnotation, ",") { + gwName = gwNameFromAnnotation + } + + return capi.APIGatewayConfigEntry{ + Kind: capi.APIGateway, + Name: gwName, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: k8sGW.GetObjectMeta().GetNamespace(), + metaKeyKubeServiceName: k8sGW.GetObjectMeta().GetName(), + metaKeyKubeName: k8sGW.GetObjectMeta().GetName(), + }, + Listeners: listeners, + Partition: t.ConsulPartition, + Namespace: t.getConsulNamespace(k8sGW.GetObjectMeta().GetNamespace()), + } +} + +func (t K8sToConsulTranslator) ReferenceForGateway(k8sGW *gwv1beta1.Gateway) api.ResourceReference { + gwName := k8sGW.Name + if gwNameFromAnnotation, ok := k8sGW.Annotations[AnnotationGateway]; ok && gwNameFromAnnotation != "" && !strings.Contains(gwNameFromAnnotation, ",") { + gwName = gwNameFromAnnotation + } + return api.ResourceReference{ + Kind: api.APIGateway, + Name: gwName, + Namespace: t.getConsulNamespace(k8sGW.GetObjectMeta().GetNamespace()), + } +} + +// HTTPRouteToHTTPRoute translates a k8s HTTPRoute into a Consul HTTPRoute Config Entry. +func (t K8sToConsulTranslator) HTTPRouteToHTTPRoute(k8sHTTPRoute *gwv1beta1.HTTPRoute, parentRefs map[types.NamespacedName]api.ResourceReference, k8sServices map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) *capi.HTTPRouteConfigEntry { + routeName := k8sHTTPRoute.Name + if routeNameFromAnnotation, ok := k8sHTTPRoute.Annotations[AnnotationHTTPRoute]; ok && routeNameFromAnnotation != "" && !strings.Contains(routeNameFromAnnotation, ",") { + routeName = routeNameFromAnnotation + } + + consulHTTPRoute := &capi.HTTPRouteConfigEntry{ + Kind: capi.HTTPRoute, + Name: routeName, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: k8sHTTPRoute.GetObjectMeta().GetNamespace(), + metaKeyKubeServiceName: k8sHTTPRoute.GetObjectMeta().GetName(), + metaKeyKubeName: k8sHTTPRoute.GetObjectMeta().GetName(), + }, + Partition: t.ConsulPartition, + + Namespace: t.getConsulNamespace(k8sHTTPRoute.GetObjectMeta().GetNamespace()), + } + + // translate hostnames + hostnames := make([]string, 0, len(k8sHTTPRoute.Spec.Hostnames)) + for _, k8Hostname := range k8sHTTPRoute.Spec.Hostnames { + hostnames = append(hostnames, string(k8Hostname)) + } + consulHTTPRoute.Hostnames = hostnames + + // translate parent refs + consulHTTPRoute.Parents = translateRouteParentRefs(k8sHTTPRoute.Spec.CommonRouteSpec.ParentRefs, parentRefs) + + // translate rules + consulHTTPRoute.Rules = t.translateHTTPRouteRules(k8sHTTPRoute.Namespace, k8sHTTPRoute.Spec.Rules, k8sServices, meshServices) + + return consulHTTPRoute +} + +func (t K8sToConsulTranslator) ReferenceForHTTPRoute(k8sHTTPRoute *gwv1beta1.HTTPRoute) api.ResourceReference { + routeName := k8sHTTPRoute.Name + if routeNameFromAnnotation, ok := k8sHTTPRoute.Annotations[AnnotationHTTPRoute]; ok && routeNameFromAnnotation != "" && !strings.Contains(routeNameFromAnnotation, ",") { + routeName = routeNameFromAnnotation + } + return api.ResourceReference{ + Kind: api.HTTPRoute, + Name: routeName, + Namespace: t.getConsulNamespace(k8sHTTPRoute.GetObjectMeta().GetNamespace()), + } +} + +// translates parent refs for Routes into Consul Resource References. +func translateRouteParentRefs(k8sParentRefs []gwv1beta1.ParentReference, parentRefs map[types.NamespacedName]api.ResourceReference) []capi.ResourceReference { + parents := make([]capi.ResourceReference, 0, len(k8sParentRefs)) + for _, k8sParentRef := range k8sParentRefs { + namespace := "" + if k8sParentRef.Namespace != nil { + namespace = string(*k8sParentRef.Namespace) + } + parentRef, ok := parentRefs[types.NamespacedName{Name: string(k8sParentRef.Name), Namespace: namespace}] + if !(ok && isRefAPIGateway(k8sParentRef)) { + // we drop any parent refs that consul does not know about + continue + } + sectionName := "" + if k8sParentRef.SectionName != nil { + sectionName = string(*k8sParentRef.SectionName) + } + ref := capi.ResourceReference{ + Kind: capi.APIGateway, // Will this ever not be a gateway? is that something we need to handle? + Name: parentRef.Name, + SectionName: sectionName, + Partition: parentRef.Partition, + Namespace: parentRef.Namespace, + } + parents = append(parents, ref) + } + return parents +} + +// isRefAPIGateway checks if the parent resource is an APIGateway. +func isRefAPIGateway(ref gwv1beta1.ParentReference) bool { + return ref.Kind != nil && *ref.Kind == gwv1beta1.Kind("Gateway") || ref.Group != nil && string(*ref.Group) == gwv1beta1.GroupName +} + +// translate the rules portion of a HTTPRoute. +func (t K8sToConsulTranslator) translateHTTPRouteRules(namespace string, k8sRules []gwv1beta1.HTTPRouteRule, k8sServices map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) []capi.HTTPRouteRule { + rules := make([]capi.HTTPRouteRule, 0, len(k8sRules)) + for _, k8sRule := range k8sRules { + rule := capi.HTTPRouteRule{} + // translate matches + rule.Matches = translateHTTPMatches(k8sRule.Matches) + + // translate filters + rule.Filters = translateHTTPFilters(k8sRule.Filters) + + // translate services + rule.Services = t.translateHTTPServices(namespace, k8sRule.BackendRefs, k8sServices, meshServices) + + rules = append(rules, rule) + } + return rules +} + +var headerMatchTypeTranslation = map[gwv1beta1.HeaderMatchType]capi.HTTPHeaderMatchType{ + gwv1beta1.HeaderMatchExact: capi.HTTPHeaderMatchExact, + gwv1beta1.HeaderMatchRegularExpression: capi.HTTPHeaderMatchRegularExpression, +} + +var headerPathMatchTypeTranslation = map[gwv1beta1.PathMatchType]capi.HTTPPathMatchType{ + gwv1beta1.PathMatchExact: capi.HTTPPathMatchExact, + gwv1beta1.PathMatchPathPrefix: capi.HTTPPathMatchPrefix, + gwv1beta1.PathMatchRegularExpression: capi.HTTPPathMatchRegularExpression, +} + +var queryMatchTypeTranslation = map[gwv1beta1.QueryParamMatchType]capi.HTTPQueryMatchType{ + gwv1beta1.QueryParamMatchExact: capi.HTTPQueryMatchExact, + gwv1beta1.QueryParamMatchRegularExpression: capi.HTTPQueryMatchRegularExpression, +} + +// translate the http matches section. +func translateHTTPMatches(k8sMatches []gwv1beta1.HTTPRouteMatch) []capi.HTTPMatch { + matches := make([]capi.HTTPMatch, 0, len(k8sMatches)) + for _, k8sMatch := range k8sMatches { + // translate header matches + headers := make([]capi.HTTPHeaderMatch, 0, len(k8sMatch.Headers)) + for _, k8sHeader := range k8sMatch.Headers { + header := capi.HTTPHeaderMatch{ + Name: string(k8sHeader.Name), + Value: k8sHeader.Value, + } + if k8sHeader.Type != nil { + header.Match = headerMatchTypeTranslation[*k8sHeader.Type] + } + headers = append(headers, header) + } + + // translate query matches + queries := make([]capi.HTTPQueryMatch, 0, len(k8sMatch.QueryParams)) + for _, k8sQuery := range k8sMatch.QueryParams { + query := capi.HTTPQueryMatch{ + Name: k8sQuery.Name, + Value: k8sQuery.Value, + } + if k8sQuery.Type != nil { + query.Match = queryMatchTypeTranslation[*k8sQuery.Type] + } + queries = append(queries, query) + } + + match := capi.HTTPMatch{ + Headers: headers, + Query: queries, + } + if k8sMatch.Method != nil { + match.Method = capi.HTTPMatchMethod(*k8sMatch.Method) + } + if k8sMatch.Path != nil { + if k8sMatch.Path.Type != nil { + match.Path.Match = headerPathMatchTypeTranslation[*k8sMatch.Path.Type] + } + if k8sMatch.Path.Value != nil { + match.Path.Value = string(*k8sMatch.Path.Value) + } + } + matches = append(matches, match) + } + return matches +} + +// translate the http filters section. +func translateHTTPFilters(k8sFilters []gwv1beta1.HTTPRouteFilter) capi.HTTPFilters { + add := make(map[string]string) + set := make(map[string]string) + remove := make([]string, 0) + var urlRewrite *capi.URLRewrite + for _, k8sFilter := range k8sFilters { + for _, adder := range k8sFilter.RequestHeaderModifier.Add { + add[string(adder.Name)] = adder.Value + } + + for _, setter := range k8sFilter.RequestHeaderModifier.Set { + set[string(setter.Name)] = setter.Value + } + + remove = append(remove, k8sFilter.RequestHeaderModifier.Remove...) + + // we drop any path rewrites that are not prefix matches as we don't support those + if k8sFilter.URLRewrite != nil && k8sFilter.URLRewrite.Path.Type == gwv1beta1.PrefixMatchHTTPPathModifier { + urlRewrite = &capi.URLRewrite{Path: *k8sFilter.URLRewrite.Path.ReplacePrefixMatch} + } + + } + filter := capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: add, + Remove: remove, + Set: set, + }, + }, + URLRewrite: urlRewrite, + } + + return filter +} + +// translate the backendrefs into services. +func (t K8sToConsulTranslator) translateHTTPServices(namespace string, k8sBackendRefs []gwv1beta1.HTTPBackendRef, k8sServices map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) []capi.HTTPService { + services := make([]capi.HTTPService, 0, len(k8sBackendRefs)) + + for _, k8sRef := range k8sBackendRefs { + backendRef := k8sRef.BackendObjectReference + + nsn := types.NamespacedName{ + Name: string(backendRef.Name), + Namespace: valueOr(backendRef.Namespace, namespace), + } + + isServiceRef := nilOrEqual(backendRef.Group, "") && nilOrEqual(backendRef.Kind, "Service") + isMeshServiceRef := derefEqual(backendRef.Group, v1alpha1.ConsulHashicorpGroup) && derefEqual(backendRef.Kind, v1alpha1.MeshServiceKind) + + k8sService, k8sServiceFound := k8sServices[nsn] + meshService, meshServiceFound := meshServices[nsn] + + if isServiceRef && k8sServiceFound { + service := capi.HTTPService{ + Name: strings.TrimSuffix(k8sService.ServiceName, "-sidecar-proxy"), + Namespace: t.getConsulNamespace(k8sService.Namespace), + Filters: translateHTTPFilters(k8sRef.Filters), + } + if k8sRef.Weight != nil { + service.Weight = int(*k8sRef.Weight) + } + services = append(services, service) + } else if isMeshServiceRef && meshServiceFound { + service := capi.HTTPService{ + Name: meshService.Spec.Name, + Namespace: t.getConsulNamespace(meshService.Namespace), + Filters: translateHTTPFilters(k8sRef.Filters), + } + if k8sRef.Weight != nil { + service.Weight = int(*k8sRef.Weight) + } + services = append(services, service) + } + } + + return services +} + +// TCPRouteToTCPRoute translates a Kuberenetes TCPRoute into a Consul TCPRoute Config Entry. +func (t K8sToConsulTranslator) TCPRouteToTCPRoute(k8sRoute *gwv1alpha2.TCPRoute, parentRefs map[types.NamespacedName]api.ResourceReference, k8sServices map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) *capi.TCPRouteConfigEntry { + routeName := k8sRoute.Name + if routeNameFromAnnotation, ok := k8sRoute.Annotations[AnnotationTCPRoute]; ok && routeNameFromAnnotation != "" && !strings.Contains(routeNameFromAnnotation, ",") { + routeName = routeNameFromAnnotation + } + + consulRoute := &capi.TCPRouteConfigEntry{ + Kind: capi.TCPRoute, + Name: routeName, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: k8sRoute.GetObjectMeta().GetNamespace(), + metaKeyKubeServiceName: k8sRoute.GetObjectMeta().GetName(), + metaKeyKubeName: k8sRoute.GetObjectMeta().GetName(), + }, + Partition: t.ConsulPartition, + + Namespace: t.getConsulNamespace(k8sRoute.GetObjectMeta().GetNamespace()), + } + + // translate parent refs + consulRoute.Parents = translateRouteParentRefs(k8sRoute.Spec.CommonRouteSpec.ParentRefs, parentRefs) + + // translate the services + consulRoute.Services = make([]capi.TCPService, 0) + for _, rule := range k8sRoute.Spec.Rules { + for _, k8sref := range rule.BackendRefs { + backendRef := k8sref.BackendObjectReference + + nsn := types.NamespacedName{ + Name: string(backendRef.Name), + Namespace: valueOr(backendRef.Namespace, k8sRoute.Namespace), + } + + isServiceRef := nilOrEqual(backendRef.Group, "") && nilOrEqual(backendRef.Kind, "Service") + isMeshServiceRef := derefEqual(backendRef.Group, v1alpha1.ConsulHashicorpGroup) && derefEqual(backendRef.Kind, v1alpha1.MeshServiceKind) + + k8sService, k8sServiceFound := k8sServices[nsn] + meshService, meshServiceFound := meshServices[nsn] + + if isServiceRef && k8sServiceFound { + service := capi.TCPService{ + Name: strings.TrimSuffix(k8sService.ServiceName, "-sidecar-proxy"), + Namespace: t.getConsulNamespace(k8sService.Namespace), + } + consulRoute.Services = append(consulRoute.Services, service) + } else if isMeshServiceRef && meshServiceFound { + service := capi.TCPService{ + Name: meshService.Spec.Name, + Namespace: t.getConsulNamespace(meshService.Namespace), + } + consulRoute.Services = append(consulRoute.Services, service) + } + } + } + + return consulRoute +} + +func (t K8sToConsulTranslator) ReferenceForTCPRoute(k8sTCPRoute *gwv1alpha2.TCPRoute) api.ResourceReference { + routeName := k8sTCPRoute.Name + if routeNameFromAnnotation, ok := k8sTCPRoute.Annotations[AnnotationTCPRoute]; ok && routeNameFromAnnotation != "" && !strings.Contains(routeNameFromAnnotation, ",") { + routeName = routeNameFromAnnotation + } + return api.ResourceReference{ + Kind: api.TCPRoute, + Name: routeName, + Namespace: t.getConsulNamespace(k8sTCPRoute.GetObjectMeta().GetNamespace()), + } +} + +// SecretToInlineCertificate translates a Kuberenetes Secret into a Consul Inline Certificate Config Entry. +func (t K8sToConsulTranslator) SecretToInlineCertificate(k8sSecret corev1.Secret) capi.InlineCertificateConfigEntry { + namespace := t.getConsulNamespace(k8sSecret.GetObjectMeta().GetNamespace()) + return capi.InlineCertificateConfigEntry{ + Kind: capi.InlineCertificate, + Namespace: namespace, + Name: k8sSecret.Name, + Certificate: k8sSecret.StringData[corev1.TLSCertKey], + PrivateKey: k8sSecret.StringData[corev1.TLSPrivateKeyKey], + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: namespace, + metaKeyKubeServiceName: string(k8sSecret.Name), + metaKeyKubeName: string(k8sSecret.Name), + }, + } +} + +func (t K8sToConsulTranslator) ReferenceForSecret(k8sSecret corev1.Secret) api.ResourceReference { + return api.ResourceReference{ + Kind: api.InlineCertificate, + Name: k8sSecret.Name, + Namespace: t.getConsulNamespace(k8sSecret.GetObjectMeta().GetNamespace()), + } +} + +func EntryToNamespacedName(entry capi.ConfigEntry) types.NamespacedName { + meta := entry.GetMeta() + return types.NamespacedName{ + Name: meta[metaKeyKubeName], + Namespace: meta[metaKeyKubeNS], + } +} + +func (t K8sToConsulTranslator) getConsulNamespace(k8sNS string) string { + return namespaces.ConsulNamespace(k8sNS, t.EnableK8sMirroring, t.ConsulDestNamespace, t.EnableK8sMirroring, t.MirroringPrefix) +} + +func EntryToReference(entry capi.ConfigEntry) capi.ResourceReference { + return capi.ResourceReference{ + Kind: entry.GetKind(), + Name: entry.GetName(), + Partition: entry.GetPartition(), + Namespace: entry.GetNamespace(), + } +} + +func ptrTo[T any](v T) *T { + return &v +} + +func derefEqual[T ~string](v *T, check string) bool { + if v == nil { + return false + } + return string(*v) == check +} + +func nilOrEqual[T ~string](v *T, check string) bool { + return v == nil || string(*v) == check +} + +func valueOr[T ~string](v *T, fallback string) string { + if v == nil { + return fallback + } + return string(*v) +} diff --git a/control-plane/api-gateway/translation/config_entry_translation_test.go b/control-plane/api-gateway/translation/config_entry_translation_test.go new file mode 100644 index 0000000000..39abe4b613 --- /dev/null +++ b/control-plane/api-gateway/translation/config_entry_translation_test.go @@ -0,0 +1,1636 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package translation + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + capi "github.com/hashicorp/consul/api" +) + +func TestTranslator_GatewayToAPIGateway(t *testing.T) { + t.Parallel() + k8sObjectName := "my-k8s-gw" + k8sNamespace := "my-k8s-namespace" + + // gw status + gwLastTransmissionTime := time.Now() + + // listener one configuration + listenerOneName := "listener-one" + listenerOneHostname := "*.consul.io" + listenerOnePort := 3366 + listenerOneProtocol := "https" + + // listener one tls config + listenerOneCertName := "one-cert" + listenerOneCertK8sNamespace := "one-cert-k8s-ns" + listenerOneCertConsulNamespace := "one-cert-consul-ns" + + // listener one status + listenerOneLastTransmissionTime := time.Now() + + // listener two configuration + listenerTwoName := "listener-two" + listenerTwoHostname := "*.consul.io" + listenerTwoPort := 5432 + listenerTwoProtocol := "https" + + // listener one tls config + listenerTwoCertName := "two-cert" + listenerTwoCertK8sNamespace := "two-cert-k8s-ns" + listenerTwoCertConsulNamespace := "two-cert-consul-ns" + + // listener two status + listenerTwoLastTransmissionTime := time.Now() + + testCases := map[string]struct { + annotations map[string]string + expectedGWName string + listenerOneK8sCertRefs []gwv1beta1.SecretObjectReference + }{ + "when gw name is not overriden by annotations": { + annotations: make(map[string]string), + expectedGWName: k8sObjectName, + listenerOneK8sCertRefs: []gwv1beta1.SecretObjectReference{ + { + Name: gwv1beta1.ObjectName(listenerOneCertName), + Namespace: ptrTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), + }, + }, + }, + "when gw name is overriden by annotations": { + annotations: map[string]string{AnnotationGateway: "my-new-gw-name"}, + expectedGWName: "my-new-gw-name", + listenerOneK8sCertRefs: []gwv1beta1.SecretObjectReference{ + { + Name: gwv1beta1.ObjectName(listenerOneCertName), + Namespace: ptrTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), + }, + }, + }, + "when k8s has certs that are not referenced in consul": { + annotations: make(map[string]string), + expectedGWName: k8sObjectName, + listenerOneK8sCertRefs: []gwv1beta1.SecretObjectReference{ + { + Name: gwv1beta1.ObjectName(listenerOneCertName), + Namespace: ptrTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), + }, + { + Name: gwv1beta1.ObjectName("cert that won't exist in the translated type"), + Namespace: ptrTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), + }, + }, + }, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + input := gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: k8sObjectName, + Namespace: k8sNamespace, + Annotations: tc.annotations, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: gwv1beta1.SectionName(listenerOneName), + Hostname: ptrTo(gwv1beta1.Hostname(listenerOneHostname)), + Port: gwv1beta1.PortNumber(listenerOnePort), + Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: tc.listenerOneK8sCertRefs, + }, + }, + { + Name: gwv1beta1.SectionName(listenerTwoName), + Hostname: ptrTo(gwv1beta1.Hostname(listenerTwoHostname)), + Port: gwv1beta1.PortNumber(listenerTwoPort), + Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + { + Name: gwv1beta1.ObjectName(listenerTwoCertName), + Namespace: ptrTo(gwv1beta1.Namespace(listenerTwoCertK8sNamespace)), + }, + }, + }, + }, + }, + }, + Status: gwv1beta1.GatewayStatus{ + Conditions: []metav1.Condition{ + { + Type: string(gwv1beta1.GatewayConditionAccepted), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: gwLastTransmissionTime}, + Reason: string(gwv1beta1.GatewayReasonAccepted), + Message: "I'm accepted", + }, + }, + Listeners: []gwv1beta1.ListenerStatus{ + { + Name: gwv1beta1.SectionName(listenerOneName), + AttachedRoutes: 5, + Conditions: []metav1.Condition{ + { + Type: string(gwv1beta1.GatewayConditionReady), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: listenerOneLastTransmissionTime}, + Reason: string(gwv1beta1.GatewayConditionReady), + Message: "I'm ready", + }, + }, + }, + + { + Name: gwv1beta1.SectionName(listenerTwoName), + AttachedRoutes: 3, + Conditions: []metav1.Condition{ + { + Type: string(gwv1beta1.GatewayConditionReady), + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: listenerTwoLastTransmissionTime}, + Reason: string(gwv1beta1.GatewayConditionReady), + Message: "I'm also ready", + }, + }, + }, + }, + }, + } + + expectedConfigEntry := capi.APIGatewayConfigEntry{ + Kind: capi.APIGateway, + Name: tc.expectedGWName, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: k8sNamespace, + metaKeyKubeServiceName: k8sObjectName, + metaKeyKubeName: k8sObjectName, + }, + Listeners: []capi.APIGatewayListener{ + { + Name: listenerOneName, + Hostname: listenerOneHostname, + Port: listenerOnePort, + Protocol: listenerOneProtocol, + TLS: capi.APIGatewayTLSConfiguration{ + Certificates: []capi.ResourceReference{ + { + Kind: capi.InlineCertificate, + Name: listenerOneCertName, + Namespace: listenerOneCertConsulNamespace, + }, + }, + }, + }, + { + Name: listenerTwoName, + Hostname: listenerTwoHostname, + Port: listenerTwoPort, + Protocol: listenerTwoProtocol, + TLS: capi.APIGatewayTLSConfiguration{ + Certificates: []capi.ResourceReference{ + { + Kind: capi.InlineCertificate, + Name: listenerTwoCertName, + Namespace: listenerTwoCertConsulNamespace, + }, + }, + }, + }, + }, + Status: capi.ConfigEntryStatus{}, + Namespace: k8sNamespace, + } + translator := K8sToConsulTranslator{ + EnableConsulNamespaces: true, + ConsulDestNamespace: "", + EnableK8sMirroring: true, + MirroringPrefix: "", + } + + certs := map[types.NamespacedName]api.ResourceReference{ + {Name: listenerOneCertName, Namespace: listenerOneCertK8sNamespace}: { + Name: listenerOneCertName, + Namespace: listenerOneCertConsulNamespace, + }, + {Name: listenerTwoCertName, Namespace: listenerTwoCertK8sNamespace}: { + Name: listenerTwoCertName, + Namespace: listenerTwoCertConsulNamespace, + }, + } + + actualConfigEntry := translator.GatewayToAPIGateway(input, certs) + + if diff := cmp.Diff(expectedConfigEntry, actualConfigEntry); diff != "" { + t.Errorf("Translator.GatewayToAPIGateway() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { + t.Parallel() + type args struct { + k8sHTTPRoute gwv1beta1.HTTPRoute + parentRefs map[types.NamespacedName]api.ResourceReference + services map[types.NamespacedName]api.CatalogService + meshServices map[types.NamespacedName]v1alpha1.MeshService + } + tests := map[string]struct { + args args + want capi.HTTPRouteConfigEntry + }{ + "base test": { + args: args{ + k8sHTTPRoute: gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s-http-route", + Namespace: "k8s-ns", + Annotations: map[string]string{}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Name: gwv1beta1.ObjectName("api-gw"), + Kind: ptrTo(gwv1beta1.Kind("Gateway")), + SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{ + "host-name.example.com", + "consul.io", + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: ptrTo(gwv1beta1.PathMatchPathPrefix), + Value: ptrTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: ptrTo(gwv1beta1.HeaderMatchExact), + Name: "my header match", + Value: "the value", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "term", + }, + }, + Method: ptrTo(gwv1beta1.HTTPMethodGet), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "Magic", + Value: "v2", + }, + { + Name: "Another One", + Value: "dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "add it on", + Value: "the value", + }, + }, + Remove: []string{"time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("v1"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "service one", + Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + }, + Weight: ptrTo(int32(45)), + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "svc - Magic", + Value: "svc - v2", + }, + { + Name: "svc - Another One", + Value: "svc - dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "svc - add it on", + Value: "svc - the value", + }, + }, + Remove: []string{"svc - time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("path"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentRefs: map[types.NamespacedName]api.ResourceReference{ + {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "other"}, + }, + }, + want: capi.HTTPRouteConfigEntry{ + Kind: capi.HTTPRoute, + Name: "k8s-http-route", + Parents: []capi.ResourceReference{ + { + Kind: capi.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Partition: "part-1", + Namespace: "ns", + }, + }, + Rules: []capi.HTTPRouteRule{ + { + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{Path: "v1"}, + }, + Matches: []capi.HTTPMatch{ + { + Headers: []capi.HTTPHeaderMatch{ + { + Match: capi.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: capi.HTTPMatchMethodGet, + Path: capi.HTTPPathMatch{ + Match: capi.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []capi.HTTPQueryMatch{ + { + Match: capi.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []capi.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{ + Path: "path", + }, + }, + Namespace: "other", + }, + }, + }, + }, + Hostnames: []string{ + "host-name.example.com", + "consul.io", + }, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: "k8s-ns", + metaKeyKubeServiceName: "k8s-http-route", + metaKeyKubeName: "k8s-http-route", + }, + Namespace: "k8s-ns", + }, + }, + "with httproute name override": { + args: args{ + k8sHTTPRoute: gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s-http-route", + Namespace: "k8s-ns", + Annotations: map[string]string{ + AnnotationHTTPRoute: "overrrrride", + }, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Name: gwv1beta1.ObjectName("api-gw"), + Kind: ptrTo(gwv1beta1.Kind("Gateway")), + SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{ + "host-name.example.com", + "consul.io", + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: ptrTo(gwv1beta1.PathMatchPathPrefix), + Value: ptrTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: ptrTo(gwv1beta1.HeaderMatchExact), + Name: "my header match", + Value: "the value", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "term", + }, + }, + Method: ptrTo(gwv1beta1.HTTPMethodGet), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "Magic", + Value: "v2", + }, + { + Name: "Another One", + Value: "dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "add it on", + Value: "the value", + }, + }, + Remove: []string{"time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("v1"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "service one", + Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + }, + Weight: ptrTo(int32(45)), + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "svc - Magic", + Value: "svc - v2", + }, + { + Name: "svc - Another One", + Value: "svc - dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "svc - add it on", + Value: "svc - the value", + }, + }, + Remove: []string{"svc - time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("path"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentRefs: map[types.NamespacedName]api.ResourceReference{ + {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "some ns"}, + }, + }, + want: capi.HTTPRouteConfigEntry{ + Kind: capi.HTTPRoute, + Name: "overrrrride", + Parents: []capi.ResourceReference{ + { + Kind: capi.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Partition: "part-1", + Namespace: "ns", + }, + }, + Rules: []capi.HTTPRouteRule{ + { + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{Path: "v1"}, + }, + Matches: []capi.HTTPMatch{ + { + Headers: []capi.HTTPHeaderMatch{ + { + Match: capi.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: capi.HTTPMatchMethodGet, + Path: capi.HTTPPathMatch{ + Match: capi.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []capi.HTTPQueryMatch{ + { + Match: capi.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []capi.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{ + "host-name.example.com", + "consul.io", + }, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: "k8s-ns", + metaKeyKubeServiceName: "k8s-http-route", + metaKeyKubeName: "k8s-http-route", + }, + Namespace: "k8s-ns", + }, + }, + "dropping path rewrites that are not prefix match": { + args: args{ + k8sHTTPRoute: gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s-http-route", + Namespace: "k8s-ns", + Annotations: map[string]string{ + AnnotationHTTPRoute: "overrrrride", + }, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Name: gwv1beta1.ObjectName("api-gw"), + SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), + Kind: ptrTo(gwv1beta1.Kind("Gateway")), + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{ + "host-name.example.com", + "consul.io", + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: ptrTo(gwv1beta1.PathMatchPathPrefix), + Value: ptrTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: ptrTo(gwv1beta1.HeaderMatchExact), + Name: "my header match", + Value: "the value", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "term", + }, + }, + Method: ptrTo(gwv1beta1.HTTPMethodGet), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "Magic", + Value: "v2", + }, + { + Name: "Another One", + Value: "dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "add it on", + Value: "the value", + }, + }, + Remove: []string{"time to go"}, + }, + // THIS IS THE CHANGE + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.FullPathHTTPPathModifier, + ReplaceFullPath: ptrTo("v1"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "service one", + Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + }, + Weight: ptrTo(int32(45)), + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "svc - Magic", + Value: "svc - v2", + }, + { + Name: "svc - Another One", + Value: "svc - dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "svc - add it on", + Value: "svc - the value", + }, + }, + Remove: []string{"svc - time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("path"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentRefs: map[types.NamespacedName]api.ResourceReference{ + {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "some ns"}, + }, + }, + want: capi.HTTPRouteConfigEntry{ + Kind: capi.HTTPRoute, + Name: "overrrrride", + Parents: []capi.ResourceReference{ + { + Kind: capi.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Partition: "part-1", + Namespace: "ns", + }, + }, + Rules: []capi.HTTPRouteRule{ + { + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + }, + Matches: []capi.HTTPMatch{ + { + Headers: []capi.HTTPHeaderMatch{ + { + Match: capi.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: capi.HTTPMatchMethodGet, + Path: capi.HTTPPathMatch{ + Match: capi.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []capi.HTTPQueryMatch{ + { + Match: capi.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []capi.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{ + "host-name.example.com", + "consul.io", + }, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: "k8s-ns", + metaKeyKubeServiceName: "k8s-http-route", + metaKeyKubeName: "k8s-http-route", + }, + Namespace: "k8s-ns", + }, + }, + + "parent ref that is not registered with consul is dropped": { + args: args{ + k8sHTTPRoute: gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s-http-route", + Namespace: "k8s-ns", + Annotations: map[string]string{}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Name: gwv1beta1.ObjectName("api-gw"), + Kind: ptrTo(gwv1beta1.Kind("Gateway")), + SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), + }, + + { + Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Name: gwv1beta1.ObjectName("consul don't know about me"), + Kind: ptrTo(gwv1beta1.Kind("Gateway")), + SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{ + "host-name.example.com", + "consul.io", + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: ptrTo(gwv1beta1.PathMatchPathPrefix), + Value: ptrTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: ptrTo(gwv1beta1.HeaderMatchExact), + Name: "my header match", + Value: "the value", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "term", + }, + }, + Method: ptrTo(gwv1beta1.HTTPMethodGet), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "Magic", + Value: "v2", + }, + { + Name: "Another One", + Value: "dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "add it on", + Value: "the value", + }, + }, + Remove: []string{"time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("v1"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "service one", + Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + }, + Weight: ptrTo(int32(45)), + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "svc - Magic", + Value: "svc - v2", + }, + { + Name: "svc - Another One", + Value: "svc - dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "svc - add it on", + Value: "svc - the value", + }, + }, + Remove: []string{"svc - time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("path"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentRefs: map[types.NamespacedName]api.ResourceReference{ + {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "some ns"}, + }, + }, + want: capi.HTTPRouteConfigEntry{ + Kind: capi.HTTPRoute, + Name: "k8s-http-route", + Parents: []capi.ResourceReference{ + { + Kind: capi.APIGateway, + Name: "api-gw", + SectionName: "listener-1", + Partition: "part-1", + Namespace: "ns", + }, + }, + Rules: []capi.HTTPRouteRule{ + { + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{Path: "v1"}, + }, + Matches: []capi.HTTPMatch{ + { + Headers: []capi.HTTPHeaderMatch{ + { + Match: capi.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: capi.HTTPMatchMethodGet, + Path: capi.HTTPPathMatch{ + Match: capi.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []capi.HTTPQueryMatch{ + { + Match: capi.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []capi.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{ + "host-name.example.com", + "consul.io", + }, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: "k8s-ns", + metaKeyKubeServiceName: "k8s-http-route", + metaKeyKubeName: "k8s-http-route", + }, + Namespace: "k8s-ns", + }, + }, + "when section name on apigw is not supplied": { + args: args{ + k8sHTTPRoute: gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s-http-route", + Namespace: "k8s-ns", + Annotations: map[string]string{}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Name: gwv1beta1.ObjectName("api-gw"), + Kind: ptrTo(gwv1beta1.Kind("Gateway")), + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{ + "host-name.example.com", + "consul.io", + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: ptrTo(gwv1beta1.PathMatchPathPrefix), + Value: ptrTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: ptrTo(gwv1beta1.HeaderMatchExact), + Name: "my header match", + Value: "the value", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "term", + }, + }, + Method: ptrTo(gwv1beta1.HTTPMethodGet), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "Magic", + Value: "v2", + }, + { + Name: "Another One", + Value: "dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "add it on", + Value: "the value", + }, + }, + Remove: []string{"time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("v1"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + // this ref should get dropped + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "service two", + Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + }, + }, + }, + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "some-service-part-three", + Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), + Group: ptrTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), + Kind: ptrTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), + }, + }, + }, + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "service one", + Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + }, + Weight: ptrTo(int32(45)), + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "svc - Magic", + Value: "svc - v2", + }, + { + Name: "svc - Another One", + Value: "svc - dj khaled", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "svc - add it on", + Value: "svc - the value", + }, + }, + Remove: []string{"svc - time to go"}, + }, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: ptrTo("path"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentRefs: map[types.NamespacedName]api.ResourceReference{ + {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "some ns"}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{ + {Name: "some-service-part-three", Namespace: "svc-ns"}: {Spec: v1alpha1.MeshServiceSpec{Name: "some-service-part-three"}}, + }, + }, + want: capi.HTTPRouteConfigEntry{ + Kind: capi.HTTPRoute, + Name: "k8s-http-route", + Parents: []capi.ResourceReference{ + { + Kind: capi.APIGateway, + Name: "api-gw", + SectionName: "", + Partition: "part-1", + Namespace: "ns", + }, + }, + Rules: []capi.HTTPRouteRule{ + { + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "add it on": "the value", + }, + Remove: []string{"time to go"}, + Set: map[string]string{ + "Magic": "v2", + "Another One": "dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{Path: "v1"}, + }, + Matches: []capi.HTTPMatch{ + { + Headers: []capi.HTTPHeaderMatch{ + { + Match: capi.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: capi.HTTPMatchMethodGet, + Path: capi.HTTPPathMatch{ + Match: capi.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []capi.HTTPQueryMatch{ + { + Match: capi.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []capi.HTTPService{ + {Name: "some-service-part-three", Filters: capi.HTTPFilters{Headers: []capi.HTTPHeaderFilter{{Add: make(map[string]string), Remove: make([]string, 0), Set: make(map[string]string)}}}}, + { + Name: "service one", + Weight: 45, + Filters: capi.HTTPFilters{ + Headers: []capi.HTTPHeaderFilter{ + { + Add: map[string]string{ + "svc - add it on": "svc - the value", + }, + Remove: []string{"svc - time to go"}, + Set: map[string]string{ + "svc - Magic": "svc - v2", + "svc - Another One": "svc - dj khaled", + }, + }, + }, + URLRewrite: &capi.URLRewrite{ + Path: "path", + }, + }, + Namespace: "some ns", + }, + }, + }, + }, + Hostnames: []string{ + "host-name.example.com", + "consul.io", + }, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: "k8s-ns", + metaKeyKubeServiceName: "k8s-http-route", + metaKeyKubeName: "k8s-http-route", + }, + Namespace: "k8s-ns", + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tr := K8sToConsulTranslator{ + EnableConsulNamespaces: true, + EnableK8sMirroring: true, + } + got := tr.HTTPRouteToHTTPRoute(&tc.args.k8sHTTPRoute, tc.args.parentRefs, tc.args.services, tc.args.meshServices) + if diff := cmp.Diff(&tc.want, got); diff != "" { + t.Errorf("Translator.HTTPRouteToHTTPRoute() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestTranslator_TCPRouteToTCPRoute(t *testing.T) { + t.Parallel() + type args struct { + k8sRoute gwv1alpha2.TCPRoute + parentRefs map[types.NamespacedName]api.ResourceReference + services map[types.NamespacedName]api.CatalogService + meshServices map[types.NamespacedName]v1alpha1.MeshService + } + tests := map[string]struct { + args args + want capi.TCPRouteConfigEntry + }{ + "base test": { + args: args{ + k8sRoute: gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route", + Namespace: "k8s-ns", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: ptrTo(gwv1beta1.Namespace("another-ns")), + Name: "mygw", + SectionName: ptrTo(gwv1beta1.SectionName("listener-one")), + Kind: ptrTo(gwv1beta1.Kind("Gateway")), + }, + }, + }, + Rules: []gwv1alpha2.TCPRouteRule{ + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "some-service", + Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), + }, + Weight: new(int32), + }, + }, + }, + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "some-service-part-two", + Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), + }, + Weight: new(int32), + }, + }, + }, + }, + }, + }, + parentRefs: map[types.NamespacedName]api.ResourceReference{ + { + Namespace: "another-ns", + Name: "mygw", + }: { + Name: "mygw", + Namespace: "another-ns", + Partition: "", + }, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "some-service", Namespace: "svc-ns"}: {ServiceName: "some-service", Namespace: "svc-ns"}, + {Name: "some-service-part-two", Namespace: "svc-ns"}: {ServiceName: "some-service-part-two", Namespace: "svc-ns"}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{ + {Name: "some-service-part-three", Namespace: "svc-ns"}: {Spec: v1alpha1.MeshServiceSpec{Name: "some-service-part-three"}}, + }, + }, + want: capi.TCPRouteConfigEntry{ + Kind: capi.TCPRoute, + Name: "tcp-route", + Namespace: "k8s-ns", + Parents: []capi.ResourceReference{ + { + Kind: capi.APIGateway, + Name: "mygw", + SectionName: "listener-one", + Partition: "", + Namespace: "another-ns", + }, + }, + Services: []capi.TCPService{ + { + Name: "some-service", + Partition: "", + Namespace: "svc-ns", + }, + { + Name: "some-service-part-two", + Partition: "", + Namespace: "svc-ns", + }, + }, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: "k8s-ns", + metaKeyKubeServiceName: "tcp-route", + metaKeyKubeName: "tcp-route", + }, + }, + }, + + "overwrite the route name via annotaions": { + args: args{ + k8sRoute: gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route", + Namespace: "k8s-ns", + Annotations: map[string]string{ + AnnotationTCPRoute: "replaced-name", + }, + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: ptrTo(gwv1beta1.Namespace("another-ns")), + Name: "mygw", + SectionName: ptrTo(gwv1beta1.SectionName("listener-one")), + Kind: ptrTo(gwv1beta1.Kind("Gateway")), + }, + }, + }, + Rules: []gwv1alpha2.TCPRouteRule{ + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "some-service", + Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), + }, + Weight: new(int32), + }, + }, + }, + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "some-service-part-two", + Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), + }, + Weight: new(int32), + }, + }, + }, + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "some-service-part-three", + Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), + Group: ptrTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), + Kind: ptrTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), + }, + Weight: new(int32), + }, + }, + }, + }, + }, + }, + parentRefs: map[types.NamespacedName]api.ResourceReference{ + { + Namespace: "another-ns", + Name: "mygw", + }: { + Name: "mygw", + Namespace: "another-ns", + Partition: "", + }, + }, + services: map[types.NamespacedName]api.CatalogService{ + {Name: "some-service", Namespace: "svc-ns"}: {ServiceName: "some-service", Namespace: "other"}, + }, + meshServices: map[types.NamespacedName]v1alpha1.MeshService{ + {Name: "some-service-part-three", Namespace: "svc-ns"}: {Spec: v1alpha1.MeshServiceSpec{Name: "some-service-part-three"}}, + }, + }, + want: capi.TCPRouteConfigEntry{ + Kind: capi.TCPRoute, + Name: "replaced-name", + Namespace: "k8s-ns", + Parents: []capi.ResourceReference{ + { + Kind: capi.APIGateway, + Name: "mygw", + SectionName: "listener-one", + Partition: "", + Namespace: "another-ns", + }, + }, + Services: []capi.TCPService{ + { + Name: "some-service", + Partition: "", + Namespace: "other", + }, + {Name: "some-service-part-three"}, + }, + Meta: map[string]string{ + metaKeyManagedBy: metaValueManagedBy, + metaKeyKubeNS: "k8s-ns", + metaKeyKubeServiceName: "tcp-route", + metaKeyKubeName: "tcp-route", + }, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + tr := K8sToConsulTranslator{ + EnableConsulNamespaces: true, + EnableK8sMirroring: true, + } + + got := tr.TCPRouteToTCPRoute(&tt.args.k8sRoute, tt.args.parentRefs, tt.args.services, tt.args.meshServices) + if diff := cmp.Diff(&tt.want, got); diff != "" { + t.Errorf("Translator.TCPRouteToTCPRoute() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/control-plane/api-gateway/translation/k8s_cache_translation.go b/control-plane/api-gateway/translation/k8s_cache_translation.go new file mode 100644 index 0000000000..88c0eca3b9 --- /dev/null +++ b/control-plane/api-gateway/translation/k8s_cache_translation.go @@ -0,0 +1,134 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package translation + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/hashicorp/consul/api" +) + +type TranslatorFn func(api.ConfigEntry) []types.NamespacedName + +type secretTransfomer func(context.Context) func(client.Object) []reconcile.Request + +type resourceGetter interface { + Get(api.ResourceReference) api.ConfigEntry +} + +// ConsulToNamespaceNameTranslator handles translating consul config entries to k8s namespaced names. +type ConsulToNamespaceNameTranslator struct { + cache resourceGetter +} + +// NewConsulToNamespaceNameTranslator creates an instance of the ConsulToNSNTranslator. +func NewConsulToNamespaceNameTranslator(cache resourceGetter) ConsulToNamespaceNameTranslator { + return ConsulToNamespaceNameTranslator{cache: cache} +} + +// BuildConsulGatewayTranslator creates a slice k8s types.NamespacedName from the meta fields of the api gateway config entry. +func (c ConsulToNamespaceNameTranslator) BuildConsulGatewayTranslator(ctx context.Context) TranslatorFn { + return func(config api.ConfigEntry) []types.NamespacedName { + meta, ok := metaToK8sNamespacedName(config) + if !ok { + return nil + } + + return []types.NamespacedName{meta} + } +} + +// BuildConsulHTTPRouteTranslator creates a slice of k8s types.NamespacedName from the meta fields of the http route parent refs. +func (c ConsulToNamespaceNameTranslator) BuildConsulHTTPRouteTranslator(ctx context.Context) TranslatorFn { + return func(config api.ConfigEntry) []types.NamespacedName { + route, ok := config.(*api.HTTPRouteConfigEntry) + if !ok { + return nil + } + + return consulRefsToNSN(c.cache, route.Parents) + } +} + +// BuildConsulTCPRouteTranslator creates a slice of k8s types.NamespacedName from the meta fields of the tcp route parent refs. +func (c ConsulToNamespaceNameTranslator) BuildConsulTCPRouteTranslator(ctx context.Context) TranslatorFn { + return func(config api.ConfigEntry) []types.NamespacedName { + route, ok := config.(*api.TCPRouteConfigEntry) + if !ok { + return nil + } + + return consulRefsToNSN(c.cache, route.Parents) + } +} + +// BuildConsulInlineCertificateTranslator creates a slice of k8s types.NamespacedName from the meta fields of the secret. It does this +// by using a secret transformer function to get a list of reconcile requests from k8s for the given secret and then converts +// those requests to the slice of NamespaceName. +func (c ConsulToNamespaceNameTranslator) BuildConsulInlineCertificateTranslator(ctx context.Context, secretTransformer secretTransfomer) TranslatorFn { + return func(config api.ConfigEntry) []types.NamespacedName { + meta, ok := metaToK8sNamespacedName(config) + if !ok { + return nil + } + + return requestsToRefs(secretTransformer(ctx)(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: meta.Name, + Namespace: meta.Namespace, + }, + })) + } +} + +func metaToK8sNamespacedName(config api.ConfigEntry) (types.NamespacedName, bool) { + meta := config.GetMeta() + + namespace, ok := meta[metaKeyKubeNS] + if !ok { + return types.NamespacedName{}, false + } + + name, ok := meta[metaKeyKubeName] + if !ok { + return types.NamespacedName{}, false + } + + return types.NamespacedName{ + Namespace: namespace, + Name: name, + }, true +} + +func consulRefsToNSN(cache resourceGetter, refs []api.ResourceReference) []types.NamespacedName { + nsnSet := make(map[types.NamespacedName]struct{}) + + for _, ref := range refs { + if parent := cache.Get(ref); parent != nil { + if k8sNSN, ok := metaToK8sNamespacedName(parent); ok { + nsnSet[k8sNSN] = struct{}{} + } + } + } + nsns := make([]types.NamespacedName, 0, len(nsnSet)) + + for nsn := range nsnSet { + nsns = append(nsns, nsn) + } + return nsns +} + +func requestsToRefs(objects []reconcile.Request) []types.NamespacedName { + var refs []types.NamespacedName + for _, object := range objects { + refs = append(refs, object.NamespacedName) + } + return refs +} diff --git a/control-plane/api-gateway/translation/k8s_cache_translation_test.go b/control-plane/api-gateway/translation/k8s_cache_translation_test.go new file mode 100644 index 0000000000..52f2ca0eb3 --- /dev/null +++ b/control-plane/api-gateway/translation/k8s_cache_translation_test.go @@ -0,0 +1,460 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package translation + +import ( + "context" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/hashicorp/consul/api" +) + +func Test_ConsulToNamespaceNameTranslator_TranslateConsulGateway(t *testing.T) { + t.Parallel() + type args struct { + config *api.APIGatewayConfigEntry + } + tests := []struct { + name string + args args + want []types.NamespacedName + }{ + { + name: "when name and namespace are set", + args: args{ + config: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{ + metaKeyKubeNS: "my-ns", + metaKeyKubeName: "api-gw-name", + }, + }, + }, + want: []types.NamespacedName{ + { + Namespace: "my-ns", + Name: "api-gw-name", + }, + }, + }, + { + name: "when name is not set and namespace is set", + args: args{ + config: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{ + metaKeyKubeNS: "my-ns", + }, + }, + }, + want: nil, + }, + { + name: "when name is set and namespace is not set", + args: args{ + config: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{ + metaKeyKubeName: "api-gw-name", + }, + }, + }, + want: nil, + }, + { + name: "when both name and namespace are not set", + args: args{ + config: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{}, + }, + }, + want: nil, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + translator := ConsulToNamespaceNameTranslator{} + fn := translator.BuildConsulGatewayTranslator(context.Background()) + got := fn(tt.args.config) + if diff := cmp.Diff(got, tt.want, sortTransformer()); diff != "" { + t.Errorf("ConsulToNSNTranslator.TranslateConsulGateway() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestConsulToNamespaceNameTranslator_TranslateConsulHTTPRoute(t *testing.T) { + t.Parallel() + type fields struct { + cache resourceGetter + } + tests := []struct { + name string + fields fields + parentRefs []api.ResourceReference + want []types.NamespacedName + }{ + { + name: "all refs in cache", + fields: fields{ + cache: buildMockCache(map[api.ResourceReference]api.ConfigEntry{ + { + Kind: api.APIGateway, + Name: "api-gw-1", + Namespace: "ns", + }: api.ConfigEntry(&api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-1", + Meta: map[string]string{ + metaKeyKubeNS: "ns", + metaKeyKubeName: "api-gw-1", + }, + Namespace: "ns", + }), + { + Kind: api.APIGateway, + Name: "api-gw-2", + Namespace: "ns", + }: api.ConfigEntry(&api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-2", + Meta: map[string]string{ + metaKeyKubeNS: "ns", + metaKeyKubeName: "api-gw-2", + }, + Namespace: "ns", + }), + }), + }, + parentRefs: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw-1", + Namespace: "ns", + }, + + { + Kind: api.APIGateway, + Name: "api-gw-2", + Namespace: "ns", + }, + }, + want: []types.NamespacedName{ + { + Namespace: "ns", + Name: "api-gw-1", + }, + { + Namespace: "ns", + Name: "api-gw-2", + }, + }, + }, + { + name: "some refs not in cache", + fields: fields{ + cache: buildMockCache(map[api.ResourceReference]api.ConfigEntry{ + { + Kind: api.APIGateway, + Name: "api-gw-1", + Namespace: "ns", + }: api.ConfigEntry(&api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-1", + Meta: map[string]string{ + metaKeyKubeNS: "ns", + metaKeyKubeName: "api-gw-1", + }, + Namespace: "ns", + }), + }), + }, + parentRefs: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw-1", + Namespace: "ns", + }, + { + Kind: api.APIGateway, + Name: "api-gw-2", + Namespace: "ns", + }, + }, + want: []types.NamespacedName{ + { + Namespace: "ns", + Name: "api-gw-1", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := ConsulToNamespaceNameTranslator{ + cache: tt.fields.cache, + } + config := &api.HTTPRouteConfigEntry{ + Parents: tt.parentRefs, + } + got := c.BuildConsulHTTPRouteTranslator(context.Background())(config) + if diff := cmp.Diff(got, tt.want, sortTransformer()); diff != "" { + t.Errorf("ConsulToNSNTranslator.TranslateConsulHTTPRoute() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestConsulToNamespaceNameTranslator_TranslateConsulTCPRoute(t *testing.T) { + t.Parallel() + type fields struct { + cache resourceGetter + } + tests := []struct { + name string + fields fields + parentRefs []api.ResourceReference + want []types.NamespacedName + }{ + { + name: "all refs in cache", + fields: fields{ + cache: buildMockCache(map[api.ResourceReference]api.ConfigEntry{ + { + Kind: api.APIGateway, + Name: "api-gw-1", + Namespace: "ns", + }: api.ConfigEntry(&api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-1", + Meta: map[string]string{ + metaKeyKubeNS: "ns", + metaKeyKubeName: "api-gw-1", + }, + Namespace: "ns", + }), + { + Kind: api.APIGateway, + Name: "api-gw-2", + Namespace: "ns", + }: api.ConfigEntry(&api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-2", + Meta: map[string]string{ + metaKeyKubeNS: "ns", + metaKeyKubeName: "api-gw-2", + }, + Namespace: "ns", + }), + }), + }, + parentRefs: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw-1", + Namespace: "ns", + }, + + { + Kind: api.APIGateway, + Name: "api-gw-2", + Namespace: "ns", + }, + }, + want: []types.NamespacedName{ + { + Namespace: "ns", + Name: "api-gw-1", + }, + { + Namespace: "ns", + Name: "api-gw-2", + }, + }, + }, + { + name: "some refs not in cache", + fields: fields{ + cache: buildMockCache(map[api.ResourceReference]api.ConfigEntry{ + { + Kind: api.APIGateway, + Name: "api-gw-1", + Namespace: "ns", + }: api.ConfigEntry(&api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-1", + Meta: map[string]string{ + metaKeyKubeNS: "ns", + metaKeyKubeName: "api-gw-1", + }, + Namespace: "ns", + }), + }), + }, + parentRefs: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: "api-gw-1", + Namespace: "ns", + }, + { + Kind: api.APIGateway, + Name: "api-gw-2", + Namespace: "ns", + }, + }, + want: []types.NamespacedName{ + { + Namespace: "ns", + Name: "api-gw-1", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := ConsulToNamespaceNameTranslator{ + cache: tt.fields.cache, + } + config := &api.TCPRouteConfigEntry{ + Parents: tt.parentRefs, + } + got := c.BuildConsulTCPRouteTranslator(context.Background())(config) + if diff := cmp.Diff(got, tt.want, sortTransformer()); diff != "" { + t.Errorf("ConsulToNSNTranslator.TranslateConsulTCPRoute() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func Test_ConsulToNamespaceNameTranslator_TranslateInlineCertificate(t *testing.T) { + t.Parallel() + type args struct { + config *api.InlineCertificateConfigEntry + } + tests := []struct { + name string + args args + want []types.NamespacedName + }{ + { + name: "when name and namespace are set", + args: args{ + config: &api.InlineCertificateConfigEntry{ + Kind: api.InlineCertificate, + Name: "secret", + Meta: map[string]string{ + metaKeyKubeNS: "my-ns", + metaKeyKubeName: "secret", + }, + }, + }, + want: []types.NamespacedName{ + { + Namespace: "my-ns", + Name: "secret", + }, + }, + }, + { + name: "when name is not set and namespace is set", + args: args{ + config: &api.InlineCertificateConfigEntry{ + Kind: api.InlineCertificate, + Name: "secret", + Meta: map[string]string{ + metaKeyKubeNS: "my-ns", + }, + }, + }, + want: nil, + }, + { + name: "when name is set and namespace is not set", + args: args{ + config: &api.InlineCertificateConfigEntry{ + Kind: api.APIGateway, + Name: "secret", + Meta: map[string]string{ + metaKeyKubeName: "secret", + }, + }, + }, + want: nil, + }, + { + name: "when both name and namespace are not set", + args: args{ + config: &api.InlineCertificateConfigEntry{ + Kind: api.InlineCertificate, + Name: "secret", + Meta: map[string]string{}, + }, + }, + want: nil, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + transformer := func(ctx context.Context) func(client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{Name: o.GetName(), Namespace: o.GetNamespace()}, + }, + } + } + } + + translator := ConsulToNamespaceNameTranslator{} + fn := translator.BuildConsulInlineCertificateTranslator(context.Background(), transformer) + got := fn(tt.args.config) + if diff := cmp.Diff(got, tt.want, sortTransformer()); diff != "" { + t.Errorf("ConsulToNSNTranslator.TranslateConsulInlineCertificate() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func sortTransformer() cmp.Option { + return cmp.Transformer("Sort", func(in []types.NamespacedName) []types.NamespacedName { + sort.Slice(in, func(i int, j int) bool { + return in[i].Name < in[j].Name + }) + return in + }) +} + +type mockCache struct { + c map[api.ResourceReference]api.ConfigEntry +} + +func (m mockCache) Get(ref api.ResourceReference) api.ConfigEntry { + val, ok := m.c[ref] + if !ok { + return nil + } + return val +} + +func buildMockCache(c map[api.ResourceReference]api.ConfigEntry) mockCache { + return mockCache{c: c} +} diff --git a/control-plane/api/common/configentry_webhook_test.go b/control-plane/api/common/configentry_webhook_test.go index 07f24cff20..7d7139089d 100644 --- a/control-plane/api/common/configentry_webhook_test.go +++ b/control-plane/api/common/configentry_webhook_test.go @@ -115,7 +115,7 @@ func TestValidateConfigEntry(t *testing.T) { }, }, }, - logrtest.TestLogger{T: t}, + logrtest.NewTestLogger(t), lister, c.newResource, ConsulMeta{ diff --git a/control-plane/api/v1alpha1/api_gateway_types.go b/control-plane/api/v1alpha1/api_gateway_types.go new file mode 100644 index 0000000000..f9be4bdb47 --- /dev/null +++ b/control-plane/api/v1alpha1/api_gateway_types.go @@ -0,0 +1,135 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + GatewayClassConfigKind = "GatewayClassConfig" + MeshServiceKind = "MeshService" +) + +func init() { + SchemeBuilder.Register(&GatewayClassConfig{}, &GatewayClassConfigList{}) + SchemeBuilder.Register(&MeshService{}, &MeshServiceList{}) +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster + +// GatewayClassConfig defines the values that may be set on a GatewayClass for Consul API Gateway. +type GatewayClassConfig struct { + // Standard Kubernetes resource metadata. + metav1.TypeMeta `json:",inline"` + + // Standard object's metadata. + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of GatewayClassConfig. + Spec GatewayClassConfigSpec `json:"spec,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// GatewayClassConfigSpec specifies the desired state of the Config CRD. +type GatewayClassConfigSpec struct { + + // +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer + ServiceType *corev1.ServiceType `json:"serviceType,omitempty"` + + // NodeSelector is a selector which must be true for the pod to fit on a node. + // Selector which must match a node's labels for the pod to be scheduled on that node. + // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + + // Tolerations allow the scheduler to schedule nodes with matching taints. + // More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + + // Deployment defines the deployment configuration for the gateway. + DeploymentSpec DeploymentSpec `json:"deployment,omitempty"` + + // Annotation Information to copy to services or deployments + CopyAnnotations CopyAnnotationsSpec `json:"copyAnnotations,omitempty"` + + // The name of an existing Kubernetes PodSecurityPolicy to bind to the managed ServiceAccount if ACLs are managed. + PodSecurityPolicy string `json:"podSecurityPolicy,omitempty"` +} + +// +k8s:deepcopy-gen=true + +type DeploymentSpec struct { + // +kubebuilder:default:=1 + // +kubebuilder:validation:Maximum=8 + // +kubebuilder:validation:Minimum=1 + // Number of gateway instances that should be deployed by default + DefaultInstances *int32 `json:"defaultInstances,omitempty"` + // +kubebuilder:default:=8 + // +kubebuilder:validation:Maximum=8 + // +kubebuilder:validation:Minimum=1 + // Max allowed number of gateway instances + MaxInstances *int32 `json:"maxInstances,omitempty"` + // +kubebuilder:default:=1 + // +kubebuilder:validation:Maximum=8 + // +kubebuilder:validation:Minimum=1 + // Minimum allowed number of gateway instances + MinInstances *int32 `json:"minInstances,omitempty"` +} + +//+kubebuilder:object:generate=true + +// CopyAnnotationsSpec defines the annotations that should be copied to the gateway service. +type CopyAnnotationsSpec struct { + // List of annotations to copy to the gateway service. + Service []string `json:"service,omitempty"` +} + +// +kubebuilder:object:root=true + +// GatewayClassConfigList is a list of Config resources. +type GatewayClassConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + // Items is the list of Configs. + Items []GatewayClassConfig `json:"items"` +} + +// +genclient +// +kubebuilder:object:root=true + +// MeshService holds a reference to an externally managed Consul Service Mesh service. +type MeshService struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of MeshService. + Spec MeshServiceSpec `json:"spec,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// MeshServiceSpec specifies the 'spec' of the MeshService CRD. +type MeshServiceSpec struct { + // Name holds the service name for a Consul service. + Name string `json:"name,omitempty"` + // Peer optionally specifies the name of the peer exporting the Consul service. + // If not specified, the Consul service is assumed to be in the local datacenter. + Peer *string `json:"peer,omitempty"` +} + +// +kubebuilder:object:root=true + +// MeshServiceList is a list of MeshService resources. +type MeshServiceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []MeshService `json:"items"` +} diff --git a/control-plane/api/v1alpha1/api_gateway_types_test.go b/control-plane/api/v1alpha1/api_gateway_types_test.go new file mode 100644 index 0000000000..1f9d8ebef0 --- /dev/null +++ b/control-plane/api/v1alpha1/api_gateway_types_test.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "testing" + + "github.com/stretchr/testify/require" + core "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGatewayClassConfigDeepCopy(t *testing.T) { + var nilConfig *GatewayClassConfig + require.Nil(t, nilConfig.DeepCopy()) + require.Nil(t, nilConfig.DeepCopyObject()) + lbType := core.ServiceTypeLoadBalancer + spec := GatewayClassConfigSpec{ + ServiceType: &lbType, + NodeSelector: map[string]string{ + "test": "test", + }, + } + config := &GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: spec, + } + copy := config.DeepCopy() + copyObject := config.DeepCopyObject() + require.Equal(t, copy, copyObject) + + var nilSpec *GatewayClassConfigSpec + require.Nil(t, nilSpec.DeepCopy()) + specCopy := (&spec).DeepCopy() + require.Equal(t, spec.NodeSelector, specCopy.NodeSelector) + + var nilConfigList *GatewayClassConfigList + require.Nil(t, nilConfigList.DeepCopyObject()) + configList := &GatewayClassConfigList{ + Items: []GatewayClassConfig{*config}, + } + copyConfigList := configList.DeepCopy() + copyConfigListObject := configList.DeepCopyObject() + require.Equal(t, copyConfigList, copyConfigListObject) +} diff --git a/control-plane/api/v1alpha1/exportedservices_webhook_test.go b/control-plane/api/v1alpha1/exportedservices_webhook_test.go index 735f938825..19c2b040bb 100644 --- a/control-plane/api/v1alpha1/exportedservices_webhook_test.go +++ b/control-plane/api/v1alpha1/exportedservices_webhook_test.go @@ -180,7 +180,7 @@ func TestValidateExportedServices(t *testing.T) { validator := &ExportedServicesWebhook{ Client: client, - Logger: logrtest.TestLogger{T: t}, + Logger: logrtest.NewTestLogger(t), decoder: decoder, ConsulMeta: c.consulMeta, } diff --git a/control-plane/api/v1alpha1/mesh_webhook_test.go b/control-plane/api/v1alpha1/mesh_webhook_test.go index def1ebf54f..bd22b02475 100644 --- a/control-plane/api/v1alpha1/mesh_webhook_test.go +++ b/control-plane/api/v1alpha1/mesh_webhook_test.go @@ -97,7 +97,7 @@ func TestValidateMesh(t *testing.T) { validator := &MeshWebhook{ Client: client, - Logger: logrtest.TestLogger{T: t}, + Logger: logrtest.NewTestLogger(t), decoder: decoder, } response := validator.Handle(ctx, admission.Request{ diff --git a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go index b97f7cf97c..d0cd39d2d0 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go @@ -138,7 +138,7 @@ func TestValidatePeeringAcceptor(t *testing.T) { validator := &PeeringAcceptorWebhook{ Client: client, - Logger: logrtest.TestLogger{T: t}, + Logger: logrtest.NewTestLogger(t), decoder: decoder, } response := validator.Handle(ctx, admission.Request{ diff --git a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go index 62b941d6a1..a6fcb79f9e 100644 --- a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go @@ -138,7 +138,7 @@ func TestValidatePeeringDialer(t *testing.T) { validator := &PeeringDialerWebhook{ Client: client, - Logger: logrtest.TestLogger{T: t}, + Logger: logrtest.NewTestLogger(t), decoder: decoder, } response := validator.Handle(ctx, admission.Request{ diff --git a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go index 9ba6241e57..90e59f0bf8 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go @@ -122,7 +122,7 @@ func TestValidateProxyDefault(t *testing.T) { validator := &ProxyDefaultsWebhook{ Client: client, - Logger: logrtest.TestLogger{T: t}, + Logger: logrtest.NewTestLogger(t), decoder: decoder, } response := validator.Handle(ctx, admission.Request{ diff --git a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go index c6934557c8..507d831e68 100644 --- a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go @@ -253,7 +253,7 @@ func TestHandle_ServiceIntentions_Create(t *testing.T) { validator := &ServiceIntentionsWebhook{ Client: client, - Logger: logrtest.TestLogger{T: t}, + Logger: logrtest.NewTestLogger(t), decoder: decoder, ConsulMeta: common.ConsulMeta{ NamespacesEnabled: true, @@ -442,7 +442,7 @@ func TestHandle_ServiceIntentions_Update(t *testing.T) { validator := &ServiceIntentionsWebhook{ Client: client, - Logger: logrtest.TestLogger{T: t}, + Logger: logrtest.NewTestLogger(t), decoder: decoder, ConsulMeta: common.ConsulMeta{ NamespacesEnabled: true, @@ -602,7 +602,7 @@ func TestHandle_ServiceIntentions_Patches(t *testing.T) { validator := &ServiceIntentionsWebhook{ Client: client, - Logger: logrtest.TestLogger{T: t}, + Logger: logrtest.NewTestLogger(t), decoder: decoder, ConsulMeta: common.ConsulMeta{ NamespacesEnabled: namespacesEnabled, diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index cedd78a1e5..a2e83491ab 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -7,7 +7,8 @@ package v1alpha1 import ( "encoding/json" - "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -79,6 +80,56 @@ func (in *CookieConfig) DeepCopy() *CookieConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CopyAnnotationsSpec) DeepCopyInto(out *CopyAnnotationsSpec) { + *out = *in + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CopyAnnotationsSpec. +func (in *CopyAnnotationsSpec) DeepCopy() *CopyAnnotationsSpec { + if in == nil { + return nil + } + out := new(CopyAnnotationsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { + *out = *in + if in.DefaultInstances != nil { + in, out := &in.DefaultInstances, &out.DefaultInstances + *out = new(int32) + **out = **in + } + if in.MaxInstances != nil { + in, out := &in.MaxInstances, &out.MaxInstances + *out = new(int32) + **out = **in + } + if in.MinInstances != nil { + in, out := &in.MinInstances, &out.MinInstances + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec. +func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { + if in == nil { + return nil + } + out := new(DeploymentSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvoyExtension) DeepCopyInto(out *EnvoyExtension) { *out = *in @@ -276,6 +327,100 @@ func (in *FailoverPolicy) DeepCopy() *FailoverPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassConfig) DeepCopyInto(out *GatewayClassConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfig. +func (in *GatewayClassConfig) DeepCopy() *GatewayClassConfig { + if in == nil { + return nil + } + out := new(GatewayClassConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayClassConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassConfigList) DeepCopyInto(out *GatewayClassConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GatewayClassConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigList. +func (in *GatewayClassConfigList) DeepCopy() *GatewayClassConfigList { + if in == nil { + return nil + } + out := new(GatewayClassConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayClassConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassConfigSpec) DeepCopyInto(out *GatewayClassConfigSpec) { + *out = *in + if in.ServiceType != nil { + in, out := &in.ServiceType, &out.ServiceType + *out = new(v1.ServiceType) + **out = **in + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.DeploymentSpec.DeepCopyInto(&out.DeploymentSpec) + in.CopyAnnotations.DeepCopyInto(&out.CopyAnnotations) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigSpec. +func (in *GatewayClassConfigSpec) DeepCopy() *GatewayClassConfigSpec { + if in == nil { + return nil + } + out := new(GatewayClassConfigSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayServiceTLSConfig) DeepCopyInto(out *GatewayServiceTLSConfig) { *out = *in @@ -860,6 +1005,84 @@ func (in *MeshList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MeshService) DeepCopyInto(out *MeshService) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshService. +func (in *MeshService) DeepCopy() *MeshService { + if in == nil { + return nil + } + out := new(MeshService) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MeshService) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MeshServiceList) DeepCopyInto(out *MeshServiceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]MeshService, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshServiceList. +func (in *MeshServiceList) DeepCopy() *MeshServiceList { + if in == nil { + return nil + } + out := new(MeshServiceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MeshServiceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MeshServiceSpec) DeepCopyInto(out *MeshServiceSpec) { + *out = *in + if in.Peer != nil { + in, out := &in.Peer, &out.Peer + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshServiceSpec. +func (in *MeshServiceSpec) DeepCopy() *MeshServiceSpec { + if in == nil { + return nil + } + out := new(MeshServiceSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MeshSpec) DeepCopyInto(out *MeshSpec) { *out = *in @@ -932,7 +1155,7 @@ func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) { } if in.BaseEjectionTime != nil { in, out := &in.BaseEjectionTime, &out.BaseEjectionTime - *out = new(v1.Duration) + *out = new(metav1.Duration) **out = **in } } diff --git a/control-plane/build-support/controller/README.md b/control-plane/build-support/controller/README.md deleted file mode 100644 index 0d24937531..0000000000 --- a/control-plane/build-support/controller/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## Overview - -`boilerplate.go.txt` is a file required by `operator-sdk` when it performs code-generation. - -It's contents provide the headers to the generated files but as we do not require headers for the files we generate, it has been left intentionally blank. diff --git a/control-plane/build-support/controller/boilerplate.go.txt b/control-plane/build-support/controller/boilerplate.go.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/control-plane/catalog/to-consul/resource_test.go b/control-plane/catalog/to-consul/resource_test.go index 3475729950..3c01088c0d 100644 --- a/control-plane/catalog/to-consul/resource_test.go +++ b/control-plane/catalog/to-consul/resource_test.go @@ -1665,8 +1665,8 @@ func TestServiceResource_addIngress(t *testing.T) { }, }, Status: networkingv1.IngressStatus{ - LoadBalancer: corev1.LoadBalancerStatus{ - Ingress: []corev1.LoadBalancerIngress{{IP: "1.2.3.4"}}, + LoadBalancer: networkingv1.IngressLoadBalancerStatus{ + Ingress: []networkingv1.IngressLoadBalancerIngress{{IP: "1.2.3.4"}}, }, }, }, @@ -1712,8 +1712,8 @@ func TestServiceResource_addIngress(t *testing.T) { }, }, Status: networkingv1.IngressStatus{ - LoadBalancer: corev1.LoadBalancerStatus{ - Ingress: []corev1.LoadBalancerIngress{{IP: "1.2.3.4"}}, + LoadBalancer: networkingv1.IngressLoadBalancerStatus{ + Ingress: []networkingv1.IngressLoadBalancerIngress{{IP: "1.2.3.4"}}, }, }, }, diff --git a/control-plane/commands.go b/control-plane/commands.go index 0da29c938b..4e76abf00d 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -12,6 +12,7 @@ import ( cmdCreateFederationSecret "github.com/hashicorp/consul-k8s/control-plane/subcommand/create-federation-secret" cmdDeleteCompletedJob "github.com/hashicorp/consul-k8s/control-plane/subcommand/delete-completed-job" cmdFetchServerRegion "github.com/hashicorp/consul-k8s/control-plane/subcommand/fetch-server-region" + cmdGatewayCleanup "github.com/hashicorp/consul-k8s/control-plane/subcommand/gateway-cleanup" cmdGetConsulClientCA "github.com/hashicorp/consul-k8s/control-plane/subcommand/get-consul-client-ca" cmdGossipEncryptionAutogenerate "github.com/hashicorp/consul-k8s/control-plane/subcommand/gossip-encryption-autogenerate" cmdInjectConnect "github.com/hashicorp/consul-k8s/control-plane/subcommand/inject-connect" @@ -49,6 +50,10 @@ func init() { return &cmdConsulLogout.Command{UI: ui}, nil }, + "gateway-cleanup": func() (cli.Command, error) { + return &cmdGatewayCleanup.Command{UI: ui}, nil + }, + "server-acl-init": func() (cli.Command, error) { return &cmdServerACLInit.Command{UI: ui}, nil }, diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index 0b6b969856..f066c90612 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: exportedservices.consul.hashicorp.com spec: @@ -134,9 +134,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml new file mode 100644 index 0000000000..a8393cd8fd --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml @@ -0,0 +1,140 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: gatewayclassconfigs.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: GatewayClassConfig + listKind: GatewayClassConfigList + plural: gatewayclassconfigs + singular: gatewayclassconfig + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GatewayClassConfig defines the values that may be set on a GatewayClass + for Consul API Gateway. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClassConfig. + properties: + copyAnnotations: + description: Annotation Information to copy to services or deployments + properties: + service: + description: List of annotations to copy to the gateway service. + items: + type: string + type: array + type: object + deployment: + description: Deployment defines the deployment configuration for the + gateway. + properties: + defaultInstances: + default: 1 + description: Number of gateway instances that should be deployed + by default + format: int32 + maximum: 8 + minimum: 1 + type: integer + maxInstances: + default: 8 + description: Max allowed number of gateway instances + format: int32 + maximum: 8 + minimum: 1 + type: integer + minInstances: + default: 1 + description: Minimum allowed number of gateway instances + format: int32 + maximum: 8 + minimum: 1 + type: integer + type: object + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for the + pod to fit on a node. Selector which must match a node''s labels + for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + podSecurityPolicy: + description: The name of an existing Kubernetes PodSecurityPolicy + to bind to the managed ServiceAccount if ACLs are managed. + type: string + serviceType: + description: Service Type string describes ingress methods for a service + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + tolerations: + description: 'Tolerations allow the scheduler to schedule nodes with + matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + served: true + storage: true diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index fd8ebc86ff..f7ccf205d9 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: ingressgateways.consul.hashicorp.com spec: @@ -364,9 +364,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] 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 adbb12bba6..bc46b6ab37 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: meshes.consul.hashicorp.com spec: @@ -202,9 +202,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml new file mode 100644 index 0000000000..0871fc32e5 --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml @@ -0,0 +1,53 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: meshservices.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: MeshService + listKind: MeshServiceList + plural: meshservices + singular: meshservice + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: MeshService holds a reference to an externally managed Consul + Service Mesh service. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of MeshService. + properties: + name: + description: Name holds the service name for a Consul service. + type: string + peer: + description: Peer optionally specifies the name of the peer exporting + the Consul service. If not specified, the Consul service is assumed + to be in the local datacenter. + type: string + type: object + type: object + served: true + storage: true diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index 50df179f04..f6f9eda72b 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: peeringacceptors.consul.hashicorp.com spec: @@ -141,9 +141,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index 01e4363f14..7e0927c169 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: peeringdialers.consul.hashicorp.com spec: @@ -141,9 +141,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 7084980bf0..7396816f7e 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: proxydefaults.consul.hashicorp.com spec: @@ -250,9 +250,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml index c71a211f63..23de092485 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: samenessgroups.consul.hashicorp.com spec: @@ -124,9 +124,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index ed4dbff857..0890c6323b 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: servicedefaults.consul.hashicorp.com spec: @@ -490,9 +490,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] 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 35930a0e8a..fac2b31b97 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: serviceintentions.consul.hashicorp.com spec: @@ -231,9 +231,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index 69084724a9..3cd3b37324 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: serviceresolvers.consul.hashicorp.com spec: @@ -329,9 +329,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 5b9c9d3c1f..25055b0951 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: servicerouters.consul.hashicorp.com spec: @@ -303,9 +303,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index aa2b592c94..d5848ed6ec 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: servicesplitters.consul.hashicorp.com spec: @@ -181,9 +181,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index b465cd9494..4910e42829 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: terminatinggateways.consul.hashicorp.com spec: @@ -132,9 +132,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 30b7cddac0..48596f9d4b 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -16,6 +16,9 @@ const ( // MetaKeyKubeNS is the meta key name for Kubernetes namespace used for the Consul services. MetaKeyKubeNS = "k8s-namespace" + // MetaKeyKubeServiceName is the meta key name for Kubernetes service name used for the Consul services. + MetaKeyKubeServiceName = "k8s-service-name" + // MetaKeyPodName is the meta key name for Kubernetes pod name used for the Consul services. MetaKeyPodName = "pod-name" ) diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go index 8c74b4f718..23bd39c1ed 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go @@ -241,7 +241,7 @@ func TestUpdateHealthCheckOnConsulClient(t *testing.T) { ctrl := Controller{ ConsulClientConfig: testClient.Cfg, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), } err := ctrl.updateHealthCheckOnConsulClient(testClient.Cfg.APIClientConfig, pod, endpoints, c.updateToStatus) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index abb84c7116..e0c7f034b1 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -226,7 +226,7 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -487,7 +487,7 @@ func TestReconcileCreateGatewayWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -1494,7 +1494,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -1780,7 +1780,7 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -2074,7 +2074,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index e9ae243a12..871081073d 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -603,7 +603,7 @@ func TestProcessUpstreams(t *testing.T) { for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { ep := &Controller{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), EnableConsulNamespaces: tt.consulNamespacesEnabled, @@ -902,7 +902,7 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { // Create the endpoints controller ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -2057,7 +2057,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -3377,7 +3377,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -3627,7 +3627,7 @@ func TestReconcileUpdateEndpoint_LegacyService(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -4001,7 +4001,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { // Create the endpoints controller ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -4146,7 +4146,7 @@ func TestReconcileIgnoresServiceIgnoreLabel(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -4232,7 +4232,7 @@ func TestReconcile_podSpecifiesExplicitService(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -5061,7 +5061,7 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20300), }, @@ -5119,7 +5119,7 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20300), }, @@ -5176,7 +5176,7 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20300), }, @@ -5222,7 +5222,7 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20400), }, @@ -5279,7 +5279,7 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, StartupProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20500), }, @@ -5336,21 +5336,21 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20300), }, }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20400), }, }, }, StartupProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20500), }, @@ -5415,21 +5415,21 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20300), }, }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20400), }, }, }, StartupProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20500), }, @@ -5449,21 +5449,21 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20300 + 1), }, }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20400 + 1), }, }, }, StartupProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20500 + 1), }, @@ -5549,7 +5549,7 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ TCPSocket: &corev1.TCPSocketAction{ Port: intstr.FromInt(8080), }, @@ -5601,21 +5601,21 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20300), }, }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20400), }, }, }, StartupProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(20500), }, @@ -5711,7 +5711,7 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { Client: fakeClient, EnableTransparentProxy: c.tproxyGlobalEnabled, TProxyOverwriteProbes: c.overwriteProbes, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), } serviceRegistration, proxyServiceRegistration, err := epCtrl.createServiceRegistrations(*pod, *endpoints, api.HealthPassing) diff --git a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go index f3b3e6a844..af4d3b0f0a 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go @@ -520,7 +520,7 @@ func TestReconcile_CreateUpdatePeeringAcceptor(t *testing.T) { Client: fakeClient, ExposeServersServiceName: "test-expose-servers", ReleaseNamespace: "default", - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -638,7 +638,7 @@ func TestReconcile_DeletePeeringAcceptor(t *testing.T) { // Create the peering acceptor controller. controller := &AcceptorController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -782,7 +782,7 @@ func TestReconcile_VersionAnnotation(t *testing.T) { // Create the peering acceptor controller controller := &AcceptorController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -1086,7 +1086,7 @@ func TestAcceptorUpdateStatus(t *testing.T) { // Create the peering acceptor controller. pac := &AcceptorController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), Scheme: s, } @@ -1198,7 +1198,7 @@ func TestAcceptorUpdateStatusError(t *testing.T) { // Create the peering acceptor controller. controller := &AcceptorController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), Scheme: s, } @@ -1481,7 +1481,7 @@ func TestAcceptor_RequestsForPeeringTokens(t *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.secret, &tt.acceptors).Build() controller := AcceptorController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), } result := controller.requestsForPeeringTokens(tt.secret) diff --git a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go index e211fe856e..d1b9cba696 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go @@ -321,7 +321,7 @@ func TestReconcile_CreateUpdatePeeringDialer(t *testing.T) { // Create the peering dialer controller controller := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -531,7 +531,7 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { // Create the peering dialer controller controller := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: consulConfig, ConsulServerConnMgr: watcher, Scheme: s, @@ -755,7 +755,7 @@ func TestReconcileDeletePeeringDialer(t *testing.T) { // Create the peering dialer controller. pdc := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -887,7 +887,7 @@ func TestDialerUpdateStatus(t *testing.T) { // Create the peering dialer controller. controller := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), Scheme: s, } @@ -999,7 +999,7 @@ func TestDialerUpdateStatusError(t *testing.T) { // Create the peering dialer controller. controller := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), Scheme: s, } @@ -1282,7 +1282,7 @@ func TestDialer_RequestsForPeeringTokens(t *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.secret, &tt.dialers).Build() controller := PeeringDialerController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), } result := controller.requestsForPeeringTokens(tt.secret) diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index f5134f208f..fe37720b7d 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -55,7 +55,7 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor // If using the proxy health check for a service, configure an HTTP handler // that queries the '/ready' endpoint of the proxy. probe = &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(constants.ProxyDefaultHealthPort + mpi.serviceIndex), Path: "/ready", @@ -65,7 +65,7 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor } } else { probe = &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ TCPSocket: &corev1.TCPSocketAction{ Port: intstr.FromInt(constants.ProxyDefaultInboundPort + mpi.serviceIndex), }, diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index e127915218..0860293352 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -209,7 +209,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { } expectedProbe := &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ TCPSocket: &corev1.TCPSocketAction{ Port: intstr.FromInt(constants.ProxyDefaultInboundPort), }, @@ -428,7 +428,7 @@ func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck(t *testing.T) { } container, err := h.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) expectedProbe := &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(21000), Path: "/ready", @@ -514,7 +514,7 @@ func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck_Multiport(t *testing.T) } expectedProbe := []*corev1.Probe{ { - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(21000), Path: "/ready", @@ -523,7 +523,7 @@ func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck_Multiport(t *testing.T) InitialDelaySeconds: 1, }, { - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(21001), Path: "/ready", @@ -672,7 +672,7 @@ func TestHandlerConsulDataplaneSidecar_Multiport(t *testing.T) { port := constants.ProxyDefaultInboundPort + i expectedProbe := &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ TCPSocket: &corev1.TCPSocketAction{ Port: intstr.FromInt(port), }, diff --git a/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go b/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go index e6da9b1f49..860694dcef 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go @@ -52,7 +52,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "single destination namespace 'default' from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -74,7 +74,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "single destination namespace 'default' from k8s 'non-default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -96,7 +96,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "single destination namespace 'dest' from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -118,7 +118,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "single destination namespace 'dest' from k8s 'non-default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -140,7 +140,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "mirroring from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -163,7 +163,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "mirroring from k8s 'dest'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -186,7 +186,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "mirroring with prefix from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -210,7 +210,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "mirroring with prefix from k8s 'dest'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -298,7 +298,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + single destination namespace 'default' from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -321,7 +321,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + single destination namespace 'default' from k8s 'non-default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -344,7 +344,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + single destination namespace 'dest' from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -367,7 +367,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + single destination namespace 'dest' from k8s 'non-default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -390,7 +390,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + mirroring from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -414,7 +414,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + mirroring from k8s 'dest'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -438,7 +438,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + mirroring with prefix from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -463,7 +463,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + mirroring with prefix from k8s 'dest'", Webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -603,7 +603,7 @@ func TestHandler_MutateWithNamespaces_Annotation(t *testing.T) { require.NoError(t, err) webhook := MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index 20c66bd57a..ddc03eaecc 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -56,7 +56,7 @@ func TestHandlerHandle(t *testing.T) { { "kube-system namespace", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -76,7 +76,7 @@ func TestHandlerHandle(t *testing.T) { { "already injected", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -100,7 +100,7 @@ func TestHandlerHandle(t *testing.T) { { "empty pod basic", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -142,7 +142,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with upstreams specified", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -201,7 +201,7 @@ func TestHandlerHandle(t *testing.T) { { "empty pod with injection disabled", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -227,7 +227,7 @@ func TestHandlerHandle(t *testing.T) { { "empty pod with injection truthy", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -282,7 +282,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with empty volume mount annotation", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -336,7 +336,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with volume mount annotation", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -410,7 +410,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with sidecar volume mount annotation", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -470,7 +470,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with sidecar invalid volume mount annotation", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -501,7 +501,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with service annotation", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -556,7 +556,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with existing label", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -606,7 +606,7 @@ func TestHandlerHandle(t *testing.T) { { "tproxy with overwriteProbes is enabled", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableTransparentProxy: true, @@ -629,14 +629,14 @@ func TestHandlerHandle(t *testing.T) { { Name: "web", LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8080), }, }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8081), }, @@ -696,7 +696,7 @@ func TestHandlerHandle(t *testing.T) { { "multiport pod kube < 1.24 with AuthMethod, serviceaccount has secret ref", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -755,7 +755,7 @@ func TestHandlerHandle(t *testing.T) { { "multiport pod kube 1.24 with AuthMethod, serviceaccount does not have secret ref", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -814,7 +814,7 @@ func TestHandlerHandle(t *testing.T) { { "dns redirection enabled", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableTransparentProxy: true, @@ -888,7 +888,7 @@ func TestHandlerHandle(t *testing.T) { { "dns redirection only enabled if tproxy enabled", MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableTransparentProxy: true, @@ -1602,7 +1602,7 @@ func TestOverwriteProbes(t *testing.T) { }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8080), }, @@ -1628,7 +1628,7 @@ func TestOverwriteProbes(t *testing.T) { }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8080), }, @@ -1655,7 +1655,7 @@ func TestOverwriteProbes(t *testing.T) { }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8080), }, @@ -1682,7 +1682,7 @@ func TestOverwriteProbes(t *testing.T) { }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8080), }, @@ -1709,7 +1709,7 @@ func TestOverwriteProbes(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8081), }, @@ -1745,21 +1745,21 @@ func TestOverwriteProbes(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8081), }, }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8080), }, }, }, StartupProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8082), }, @@ -1788,21 +1788,21 @@ func TestOverwriteProbes(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8081), }, }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8080), }, }, }, StartupProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8080), }, @@ -1822,21 +1822,21 @@ func TestOverwriteProbes(t *testing.T) { }, }, LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8083), }, }, }, ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8082), }, }, }, StartupProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(8082), }, diff --git a/control-plane/connect-inject/webhook/redirect_traffic_test.go b/control-plane/connect-inject/webhook/redirect_traffic_test.go index f94871fa96..df6b7a8559 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic_test.go +++ b/control-plane/connect-inject/webhook/redirect_traffic_test.go @@ -48,7 +48,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "basic bare minimum pod", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -78,7 +78,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "proxy health checks enabled", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -111,7 +111,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "metrics enabled", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -145,7 +145,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "metrics enabled with incorrect annotation", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -180,7 +180,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "overwrite probes, transparent proxy annotation set", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -199,7 +199,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { Name: "test", LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ + ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(exposedPathsLivenessPortsRangeStart), }, @@ -221,7 +221,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude inbound ports", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -254,7 +254,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude outbound ports", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -287,7 +287,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude outbound CIDRs", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -320,7 +320,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude UIDs", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -352,7 +352,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude inbound ports, outbound ports, outbound CIDRs, and UIDs", webhook: MeshWebhook{ - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, diff --git a/control-plane/controllers/configentry_controller.go b/control-plane/controllers/configentry_controller.go index f35d2e88e7..374af7451e 100644 --- a/control-plane/controllers/configentry_controller.go +++ b/control-plane/controllers/configentry_controller.go @@ -40,11 +40,11 @@ type Controller interface { // Update updates the state of the whole object. Update(context.Context, client.Object, ...client.UpdateOption) error // UpdateStatus updates the state of just the object's status. - UpdateStatus(context.Context, client.Object, ...client.UpdateOption) error + UpdateStatus(context.Context, client.Object, ...client.SubResourceUpdateOption) error // Get retrieves an obj for the given object key from the Kubernetes Cluster. // obj must be a struct pointer so that obj can be updated with the response // returned by the Server. - Get(ctx context.Context, key client.ObjectKey, obj client.Object) error + Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error // Logger returns a logger with values added for the specific controller // and request name. Logger(types.NamespacedName) logr.Logger diff --git a/control-plane/controllers/configentry_controller_ent_test.go b/control-plane/controllers/configentry_controller_ent_test.go index ef2aa6b7a4..ab6c70b9ad 100644 --- a/control-plane/controllers/configentry_controller_ent_test.go +++ b/control-plane/controllers/configentry_controller_ent_test.go @@ -108,7 +108,7 @@ func TestConfigEntryController_createsEntConfigEntry(t *testing.T) { req.True(written) } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) namespacedName := types.NamespacedName{ Namespace: kubeNS, Name: c.configEntryResource.KubernetesName(), @@ -235,7 +235,7 @@ func TestConfigEntryController_updatesEntConfigEntry(t *testing.T) { c.updateF(c.configEntryResource) err = fakeClient.Update(ctx, c.configEntryResource) req.NoError(err) - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) resp, err := r.Reconcile(ctx, ctrl.Request{ NamespacedName: namespacedName, }) @@ -331,7 +331,7 @@ func TestConfigEntryController_deletesEntConfigEntry(t *testing.T) { Namespace: kubeNS, Name: c.configEntryResourceWithDeletion.KubernetesName(), } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) resp, err := r.Reconcile(context.Background(), ctrl.Request{ NamespacedName: namespacedName, }) @@ -516,7 +516,7 @@ func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T r := in.GetController( fakeClient, - logrtest.TestLogger{T: t}, + logrtest.NewTestLogger(t), s, &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, @@ -778,7 +778,7 @@ func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T r := in.GetControllerFunc( fakeClient, - logrtest.TestLogger{T: t}, + logrtest.NewTestLogger(t), s, &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, @@ -1027,7 +1027,7 @@ func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T r := in.GetControllerFunc( fakeClient, - logrtest.TestLogger{T: t}, + logrtest.NewTestLogger(t), s, &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, diff --git a/control-plane/controllers/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go index d548f78069..2404b59365 100644 --- a/control-plane/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -453,7 +453,7 @@ func TestConfigEntryControllers_createsConfigEntry(t *testing.T) { req.True(written) } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) namespacedName := types.NamespacedName{ Namespace: kubeNS, Name: c.configEntryResource.KubernetesName(), @@ -953,7 +953,7 @@ func TestConfigEntryControllers_updatesConfigEntry(t *testing.T) { c.updateF(c.configEntryResource) err = fakeClient.Update(ctx, c.configEntryResource) req.NoError(err) - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) resp, err := r.Reconcile(ctx, ctrl.Request{ NamespacedName: namespacedName, }) @@ -1345,7 +1345,7 @@ func TestConfigEntryControllers_deletesConfigEntry(t *testing.T) { Namespace: kubeNS, Name: c.configEntryResourceWithDeletion.KubernetesName(), } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) resp, err := r.Reconcile(context.Background(), ctrl.Request{ NamespacedName: namespacedName, }) @@ -1392,7 +1392,7 @@ func TestConfigEntryControllers_errorUpdatesSyncStatus(t *testing.T) { reconciler := &ServiceDefaultsController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConfigEntryController: &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1459,7 +1459,7 @@ func TestConfigEntryControllers_setsSyncedToTrue(t *testing.T) { consulClient := testClient.APIClient reconciler := &ServiceDefaultsController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConfigEntryController: &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1551,7 +1551,7 @@ func TestConfigEntryControllers_doesNotCreateUnownedConfigEntry(t *testing.T) { // Attempt to create the entry in Kube and run reconcile. reconciler := ServiceDefaultsController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConfigEntryController: &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1615,7 +1615,7 @@ func TestConfigEntryControllers_doesNotDeleteUnownedConfig(t *testing.T) { consulClient := testClient.APIClient reconciler := &ServiceDefaultsController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), ConfigEntryController: &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1695,7 +1695,7 @@ func TestConfigEntryControllers_updatesStatusWhenDeleteFails(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) - logger := logrtest.TestLogger{T: t} + logger := logrtest.NewTestLogger(t) svcDefaultsReconciler := ServiceDefaultsController{ Client: fakeClient, @@ -1833,7 +1833,7 @@ func TestConfigEntryController_Migration(t *testing.T) { require.True(t, success, "config entry was not created") // Set up the reconciler. - logger := logrtest.TestLogger{T: t} + logger := logrtest.NewTestLogger(t) svcDefaultsReconciler := ServiceDefaultsController{ Client: fakeClient, Log: logger, @@ -2109,7 +2109,7 @@ func TestConfigEntryControllers_assignServiceVirtualIP(t *testing.T) { testClient.TestServer.WaitForLeader(t) consulClient := testClient.APIClient - ctrl := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.TestLogger{T: t}) + ctrl := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) namespacedName := types.NamespacedName{ Namespace: kubeNS, Name: c.configEntryResource.KubernetesName(), diff --git a/control-plane/controllers/exportedservices_controller.go b/control-plane/controllers/exportedservices_controller.go index e72b743a1f..2e82ed0eae 100644 --- a/control-plane/controllers/exportedservices_controller.go +++ b/control-plane/controllers/exportedservices_controller.go @@ -34,7 +34,7 @@ func (r *ExportedServicesController) Logger(name types.NamespacedName) logr.Logg return r.Log.WithValues("request", name) } -func (r *ExportedServicesController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *ExportedServicesController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/exportedservices_controller_ent_test.go b/control-plane/controllers/exportedservices_controller_ent_test.go index 40ab5d1e1e..94a605eab4 100644 --- a/control-plane/controllers/exportedservices_controller_ent_test.go +++ b/control-plane/controllers/exportedservices_controller_ent_test.go @@ -105,7 +105,7 @@ func TestExportedServicesController_createsExportedServices(tt *testing.T) { controller := &controllers.ExportedServicesController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), Scheme: s, ConfigEntryController: &controllers.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, @@ -220,7 +220,7 @@ func TestExportedServicesController_updatesExportedServices(tt *testing.T) { controller := &controllers.ExportedServicesController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), Scheme: s, ConfigEntryController: &controllers.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, @@ -358,7 +358,7 @@ func TestExportedServicesController_deletesExportedServices(tt *testing.T) { controller := &controllers.ExportedServicesController{ Client: fakeClient, - Log: logrtest.TestLogger{T: t}, + Log: logrtest.NewTestLogger(t), Scheme: s, ConfigEntryController: &controllers.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, diff --git a/control-plane/controllers/ingressgateway_controller.go b/control-plane/controllers/ingressgateway_controller.go index faa728dd4f..5bcb39bc2a 100644 --- a/control-plane/controllers/ingressgateway_controller.go +++ b/control-plane/controllers/ingressgateway_controller.go @@ -34,7 +34,7 @@ func (r *IngressGatewayController) Logger(name types.NamespacedName) logr.Logger return r.Log.WithValues("request", name) } -func (r *IngressGatewayController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *IngressGatewayController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/mesh_controller.go b/control-plane/controllers/mesh_controller.go index 92839d0104..ba78f0c144 100644 --- a/control-plane/controllers/mesh_controller.go +++ b/control-plane/controllers/mesh_controller.go @@ -34,7 +34,7 @@ func (r *MeshController) Logger(name types.NamespacedName) logr.Logger { return r.Log.WithValues("request", name) } -func (r *MeshController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *MeshController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/proxydefaults_controller.go b/control-plane/controllers/proxydefaults_controller.go index 1415da8688..882843bf9e 100644 --- a/control-plane/controllers/proxydefaults_controller.go +++ b/control-plane/controllers/proxydefaults_controller.go @@ -34,7 +34,7 @@ func (r *ProxyDefaultsController) Logger(name types.NamespacedName) logr.Logger return r.Log.WithValues("request", name) } -func (r *ProxyDefaultsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *ProxyDefaultsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/samenessgroups_controller.go b/control-plane/controllers/samenessgroups_controller.go index 1d768cc968..815b7e8ab8 100644 --- a/control-plane/controllers/samenessgroups_controller.go +++ b/control-plane/controllers/samenessgroups_controller.go @@ -5,6 +5,7 @@ package controllers import ( "context" + "k8s.io/apimachinery/pkg/types" "github.com/go-logr/logr" @@ -34,7 +35,7 @@ func (r *SamenessGroupController) Logger(name types.NamespacedName) logr.Logger return r.Log.WithValues("request", name) } -func (r *SamenessGroupController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *SamenessGroupController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/servicedefaults_controller.go b/control-plane/controllers/servicedefaults_controller.go index 9c2dbe683d..0496788bb3 100644 --- a/control-plane/controllers/servicedefaults_controller.go +++ b/control-plane/controllers/servicedefaults_controller.go @@ -34,7 +34,7 @@ func (r *ServiceDefaultsController) Logger(name types.NamespacedName) logr.Logge return r.Log.WithValues("request", name) } -func (r *ServiceDefaultsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *ServiceDefaultsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/serviceintentions_controller.go b/control-plane/controllers/serviceintentions_controller.go index 30dcb63f81..4461a82dd6 100644 --- a/control-plane/controllers/serviceintentions_controller.go +++ b/control-plane/controllers/serviceintentions_controller.go @@ -34,7 +34,7 @@ func (r *ServiceIntentionsController) Logger(name types.NamespacedName) logr.Log return r.Log.WithValues("request", name) } -func (r *ServiceIntentionsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *ServiceIntentionsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/serviceresolver_controller.go b/control-plane/controllers/serviceresolver_controller.go index f82c4d42a2..cfc5c31ca3 100644 --- a/control-plane/controllers/serviceresolver_controller.go +++ b/control-plane/controllers/serviceresolver_controller.go @@ -34,7 +34,7 @@ func (r *ServiceResolverController) Logger(name types.NamespacedName) logr.Logge return r.Log.WithValues("request", name) } -func (r *ServiceResolverController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *ServiceResolverController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/servicerouter_controller.go b/control-plane/controllers/servicerouter_controller.go index 831179eee9..a0b3bc0581 100644 --- a/control-plane/controllers/servicerouter_controller.go +++ b/control-plane/controllers/servicerouter_controller.go @@ -34,7 +34,7 @@ func (r *ServiceRouterController) Logger(name types.NamespacedName) logr.Logger return r.Log.WithValues("request", name) } -func (r *ServiceRouterController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *ServiceRouterController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/servicesplitter_controller.go b/control-plane/controllers/servicesplitter_controller.go index 1dd89dc278..b733fb9cb5 100644 --- a/control-plane/controllers/servicesplitter_controller.go +++ b/control-plane/controllers/servicesplitter_controller.go @@ -34,7 +34,7 @@ func (r *ServiceSplitterController) Logger(name types.NamespacedName) logr.Logge return r.Log.WithValues("request", name) } -func (r *ServiceSplitterController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *ServiceSplitterController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/controllers/terminatinggateway_controller.go b/control-plane/controllers/terminatinggateway_controller.go index 9550a2d04f..d73d4e043c 100644 --- a/control-plane/controllers/terminatinggateway_controller.go +++ b/control-plane/controllers/terminatinggateway_controller.go @@ -34,7 +34,7 @@ func (r *TerminatingGatewayController) Logger(name types.NamespacedName) logr.Lo return r.Log.WithValues("request", name) } -func (r *TerminatingGatewayController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (r *TerminatingGatewayController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { return r.Status().Update(ctx, obj, opts...) } diff --git a/control-plane/go.mod b/control-plane/go.mod index da581c221f..9f81da1907 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -4,9 +4,9 @@ require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 github.com/deckarep/golang-set v1.7.1 - github.com/fsnotify/fsnotify v1.5.4 - github.com/go-logr/logr v0.4.0 - github.com/google/go-cmp v0.5.8 + github.com/fsnotify/fsnotify v1.6.0 + github.com/go-logr/logr v1.2.3 + github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d github.com/hashicorp/consul-server-connection-manager v0.1.2 @@ -26,21 +26,23 @@ require ( github.com/mitchellh/cli v1.1.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/stretchr/testify v1.7.2 - go.uber.org/zap v1.19.0 + github.com/stretchr/testify v1.8.1 + go.uber.org/zap v1.24.0 + golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 golang.org/x/text v0.7.0 - golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 + golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.2.0 - k8s.io/api v0.22.2 - k8s.io/apimachinery v0.22.2 - k8s.io/client-go v0.22.2 - k8s.io/klog/v2 v2.9.0 - k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 - sigs.k8s.io/controller-runtime v0.10.2 + k8s.io/api v0.26.1 + k8s.io/apimachinery v0.26.1 + k8s.io/client-go v0.26.1 + k8s.io/klog/v2 v2.80.1 + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 + sigs.k8s.io/controller-runtime v0.14.6 + sigs.k8s.io/gateway-api v0.6.2 ) require ( - cloud.google.com/go v0.81.0 // indirect + cloud.google.com/go v0.65.0 // indirect github.com/Azure/azure-sdk-for-go v44.0.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.18 // indirect @@ -59,24 +61,29 @@ require ( github.com/bgentry/speakeasy v0.1.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 // indirect - github.com/digitalocean/godo v1.10.0 // indirect + github.com/digitalocean/godo v1.7.5 // indirect github.com/dimchansky/utfbom v1.1.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect - github.com/go-logr/zapr v0.4.0 // indirect + github.com/go-logr/zapr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-querystring v1.0.0 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect github.com/gophercloud/gophercloud v0.1.0 // indirect github.com/hashicorp/consul/proto-public v0.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -97,18 +104,21 @@ require ( github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/linode/linodego v0.7.1 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect github.com/oklog/run v1.0.0 // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect @@ -116,46 +126,46 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.26.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.2.0 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480 // indirect github.com/vmware/govmomi v0.18.0 // indirect - go.opencensus.io v0.23.0 // indirect + go.opencensus.io v0.22.4 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.5.0 // indirect golang.org/x/tools v0.3.0 // indirect - google.golang.org/api v0.43.0 // indirect + google.golang.org/api v0.30.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect - google.golang.org/grpc v1.48.0 // indirect + google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect + google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.22.2 // indirect - k8s.io/component-base v0.22.2 // indirect - k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + k8s.io/apiextensions-apiserver v0.26.1 // indirect + k8s.io/component-base v0.26.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) go 1.20 diff --git a/control-plane/go.sum b/control-plane/go.sum index 8dd50a5843..b915ac004b 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -12,13 +12,8 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -27,7 +22,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -40,8 +34,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v44.0.0+incompatible h1:e82Yv2HNpS0kuyeCrV29OPKvEiqfs2/uJHic3/3iKdg= github.com/Azure/azure-sdk-for-go v44.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= @@ -71,13 +63,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= -github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -85,17 +71,14 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -104,8 +87,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= @@ -113,11 +94,9 @@ github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4r github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -125,32 +104,14 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D 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/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containernetworking/cni v1.1.1 h1:ky20T7c0MvKvbMOwS/FrlbNwjEoqJEUUYfsL4b0mc4k= github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -159,44 +120,38 @@ github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14y github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72MbxIxFUzKmcMCdt9Oi8RzpAxzTNQHD7o= github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.10.0 h1:uW1/FcvZE/hoixnJcnlmIUvTVNdZCLjRLzmDtRi1xXY= -github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/digitalocean/godo v1.7.5 h1:JOQbAO6QT1GGjor0doT0mXefX2FgUDPOpYh2RaXA+ko= +github.com/digitalocean/godo v1.7.5/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -204,32 +159,32 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -242,7 +197,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -258,7 +212,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -266,7 +219,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -274,21 +228,18 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -296,44 +247,26 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 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 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d h1:RJ1MZ8JKnfgKQ1kR3IBQAMpOpzXrdseZAYN/QR//MFM= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= github.com/hashicorp/consul-server-connection-manager v0.1.2 h1:tNVQHUPuMbd+cMdD8kd+qkZUYpmLmrHMAV/49f4L53I= github.com/hashicorp/consul-server-connection-manager v0.1.2/go.mod h1:NzQoVi1KcxGI2SangsDue8+ZPuXZWs+6BKAKrDNyg+w= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca h1:5UPVYOlJg/HBEJ2q82rkkQ3ZLzeMnF5MOpGcw2kh+XU= github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -366,7 +299,6 @@ github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHG github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= @@ -386,7 +318,6 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 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/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -394,13 +325,10 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o= @@ -414,13 +342,8 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= @@ -428,11 +351,10 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= -github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= +github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 h1:JHCT6xuyPUrbbgAPE/3dqlvUKzRHMNuTBKKUb6OeR/k= +github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -442,11 +364,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -459,33 +378,27 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= +github.com/matttproud/golang_protobuf_extensions v1.0.2/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/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= @@ -495,16 +408,12 @@ github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -512,8 +421,6 @@ github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQh github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -521,44 +428,33 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -570,171 +466,113 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 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 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= -github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQvfjNUeRqaY/uT0tFuvuFY0ulgnczuR684Xic= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +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/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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658 h1:q208plt7F8Pj3b1w8D3XDb/vTgHsn/JlEwDCSe+lvyo= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658 h1:wF/3PTojsx9/J8CaeiTy0zXxvwrcuu282R4g7fDlgCQ= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658/go.mod h1:LLex9maWMIQzOFF/mYz5rEsTUwKKcmAbGRehSNRWqgQ= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480 h1:Dwnfdrk3KXpYRH9Kwrk9sHpZSOmrE7P9LBoNsYUJKR4= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480 h1:YEDZmv2ABU8QvwXEVTOQgVEQzDOByhz73vdjL6sERkE= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480/go.mod h1:zaBIuDDs+rC74X8Aog+LSu91GFtHYRYDC196RGTm2jk= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -745,8 +583,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -759,9 +597,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -770,8 +605,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= @@ -779,9 +612,7 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -789,13 +620,11 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -813,18 +642,14 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= @@ -834,14 +659,9 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 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= @@ -860,10 +680,7 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -872,7 +689,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -898,48 +714,34 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/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 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= @@ -949,7 +751,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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= @@ -959,32 +760,25 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1004,7 +798,6 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1012,15 +805,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= @@ -1046,13 +832,8 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0 h1:4sAyIHT6ZohtAQDoxws+ez7bROYmUlOVvsUscYCDTqA= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1082,7 +863,6 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1092,22 +872,11 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1118,17 +887,11 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= 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= @@ -1142,6 +905,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -1154,16 +918,11 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1178,8 +937,6 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1187,37 +944,32 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= -k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= -k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4= -k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA= -k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= -k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI= -k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= -k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= -k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= -k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M= -k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 h1:XmRqFcQlCy/lKRZ39j+RVpokYNroHPqV3mcBRfnhT5o= -k8s.io/utils v0.0.0-20220812165043-ad590609e2e5/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= +k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= +k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= +k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= +k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= +k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= +k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.10.2 h1:jW8qiY+yMnnPx6O9hu63tgcwaKzd1yLYui+mpvClOOc= -sigs.k8s.io/controller-runtime v0.10.2/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= +sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/gateway-api v0.6.2 h1:583XHiX2M2bKEA0SAdkoxL1nY73W1+/M+IAm8LJvbEA= +sigs.k8s.io/gateway-api v0.6.2/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/control-plane/helper/controller/controller.go b/control-plane/helper/controller/controller.go index 87cdde1a6f..db6de05b07 100644 --- a/control-plane/helper/controller/controller.go +++ b/control-plane/helper/controller/controller.go @@ -62,7 +62,7 @@ func (c *Controller) Run(stopCh <-chan struct{}) { // Add an event handler when data is received from the informer. The // event handlers here will block the informer so we just offload them // immediately into a workqueue. - informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { // convert the resource object into a key (in this case // we are just doing it in the format of 'namespace/name') @@ -81,6 +81,9 @@ func (c *Controller) Run(stopCh <-chan struct{}) { }, DeleteFunc: c.informerDeleteHandler(queue), }) + if err != nil { + c.Log.Error("error adding informer event handlers", err) + } // If the type is a background syncer, then we startup the background // process. diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index b7927e4490..598ba66ea5 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -70,7 +70,7 @@ func ZapLogger(level string, jsonLogging bool) (logr.Logger, error) { level = "debug" } if err := zapLevel.UnmarshalText([]byte(level)); err != nil { - return nil, fmt.Errorf("unknown log level %q: %s", level, err.Error()) + return logr.Logger{}, fmt.Errorf("unknown log level %q: %s", level, err.Error()) } if jsonLogging { return zap.New(zap.UseDevMode(false), zap.Level(zapLevel), zap.JSONEncoder()), nil diff --git a/control-plane/subcommand/connect-init/command.go b/control-plane/subcommand/connect-init/command.go index a5fbe9066c..72090d299b 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -316,7 +316,7 @@ func (c *Command) getGatewayRegistration(client *api.Client) backoff.Operation { } for _, gateway := range gatewayList.Services { switch gateway.Kind { - case api.ServiceKindMeshGateway, api.ServiceKindIngressGateway, api.ServiceKindTerminatingGateway: + case api.ServiceKindAPIGateway, api.ServiceKindMeshGateway, api.ServiceKindIngressGateway, api.ServiceKindTerminatingGateway: proxyID = gateway.ID } } diff --git a/control-plane/subcommand/gateway-cleanup/command.go b/control-plane/subcommand/gateway-cleanup/command.go new file mode 100644 index 0000000000..cecb225290 --- /dev/null +++ b/control-plane/subcommand/gateway-cleanup/command.go @@ -0,0 +1,193 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatewaycleanup + +import ( + "context" + "errors" + "flag" + "fmt" + "sync" + "time" + + "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/mitchellh/cli" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +type Command struct { + UI cli.Ui + + flags *flag.FlagSet + k8s *flags.K8SFlags + + flagGatewayClassName string + flagGatewayClassConfigName string + + k8sClient client.Client + + once sync.Once + help string + + ctx context.Context +} + +func (c *Command) init() { + c.flags = flag.NewFlagSet("", flag.ContinueOnError) + + c.flags.StringVar(&c.flagGatewayClassName, "gateway-class-name", "", + "Name of Kubernetes GatewayClass to delete.") + c.flags.StringVar(&c.flagGatewayClassConfigName, "gateway-class-config-name", "", + "Name of Kubernetes GatewayClassConfig to delete.") + + c.k8s = &flags.K8SFlags{} + flags.Merge(c.flags, c.k8s.Flags()) + c.help = flags.Usage(help, c.flags) +} + +func (c *Command) Run(args []string) int { + var err error + c.once.Do(c.init) + if err = c.flags.Parse(args); err != nil { + return 1 + } + // Validate flags + if err := c.validateFlags(); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if c.ctx == nil { + c.ctx = context.Background() + } + + // Create the Kubernetes clientset + if c.k8sClient == nil { + config, err := subcommand.K8SConfig(c.k8s.KubeConfig()) + if err != nil { + c.UI.Error(fmt.Sprintf("Error retrieving Kubernetes auth: %s", err)) + return 1 + } + + s := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add client-go schema: %s", err)) + return 1 + } + if err := gwv1beta1.Install(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add api-gateway schema: %s", err)) + return 1 + } + if err := v1alpha1.AddToScheme(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add consul-k8s schema: %s", err)) + return 1 + } + + c.k8sClient, err = client.New(config, client.Options{Scheme: s}) + if err != nil { + c.UI.Error(fmt.Sprintf("Error initializing Kubernetes client: %s", err)) + return 1 + } + } + + // do the cleanup + + // find the class config and mark it for deletion first so that we + // can do an early return if the gateway class isn't found + config := &v1alpha1.GatewayClassConfig{} + err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassConfigName}, config) + if err != nil { + if k8serrors.IsNotFound(err) { + // no gateway class config, just ignore and return + return 0 + } + c.UI.Error(err.Error()) + return 1 + } + + // ignore any returned errors + _ = c.k8sClient.Delete(context.Background(), config) + + // find the gateway class + gatewayClass := &gwv1beta1.GatewayClass{} + err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassName}, gatewayClass) + if err != nil { + if k8serrors.IsNotFound(err) { + // no gateway class, just ignore and return + return 0 + } + c.UI.Error(err.Error()) + return 1 + } + + // ignore any returned errors + _ = c.k8sClient.Delete(context.Background(), gatewayClass) + + // make sure they're gone + if err := backoff.Retry(func() error { + err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassConfigName}, config) + if err == nil || !k8serrors.IsNotFound(err) { + return errors.New("gateway class config still exists") + } + + err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassName}, gatewayClass) + if err == nil || !k8serrors.IsNotFound(err) { + return errors.New("gateway class still exists") + } + + return nil + }, exponentialBackoffWithMaxIntervalAndTime()); err != nil { + c.UI.Error(err.Error()) + // if we failed, return 0 anyway after logging the error + // since we don't want to block someone from uninstallation + } + + return 0 +} + +func (c *Command) validateFlags() error { + if c.flagGatewayClassConfigName == "" { + return errors.New("-gateway-class-config-name must be set") + } + if c.flagGatewayClassName == "" { + return errors.New("-gateway-class-name must be set") + } + + return nil +} + +func (c *Command) Synopsis() string { return synopsis } +func (c *Command) Help() string { + c.once.Do(c.init) + return c.help +} + +const synopsis = "Clean up global gateway resources prior to uninstall." +const help = ` +Usage: consul-k8s-control-plane gateay-cleanup [options] + + Deletes installed gateway class and gateway class config objects + prior to helm uninstallation. This is required due to finalizers + existing on the GatewayClassConfig that will leave around a dangling + object without deleting these prior to their controllers being deleted. + The job is best effort, so if it fails to successfully delete the + objects, it will allow the uninstallation to continue. + +` + +func exponentialBackoffWithMaxIntervalAndTime() *backoff.ExponentialBackOff { + backoff := backoff.NewExponentialBackOff() + backoff.MaxElapsedTime = 10 * time.Second + backoff.MaxInterval = 1 * time.Second + backoff.Reset() + return backoff +} diff --git a/control-plane/subcommand/gateway-cleanup/command_test.go b/control-plane/subcommand/gateway-cleanup/command_test.go new file mode 100644 index 0000000000..56b7651270 --- /dev/null +++ b/control-plane/subcommand/gateway-cleanup/command_test.go @@ -0,0 +1,83 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatewaycleanup + +import ( + "testing" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestRun(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + gatewayClassConfig *v1alpha1.GatewayClassConfig + gatewayClass *gwv1beta1.GatewayClass + }{ + "both exist": { + gatewayClassConfig: &v1alpha1.GatewayClassConfig{}, + gatewayClass: &gwv1beta1.GatewayClass{}, + }, + "gateway class config doesn't exist": { + gatewayClass: &gwv1beta1.GatewayClass{}, + }, + "gateway class doesn't exist": { + gatewayClassConfig: &v1alpha1.GatewayClassConfig{}, + }, + "neither exist": {}, + "finalizers on gatewayclass blocking deletion": { + gatewayClassConfig: &v1alpha1.GatewayClassConfig{}, + gatewayClass: &gwv1beta1.GatewayClass{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"finalizer"}}}, + }, + "finalizers on gatewayclassconfig blocking deletion": { + gatewayClassConfig: &v1alpha1.GatewayClassConfig{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"finalizer"}}}, + gatewayClass: &gwv1beta1.GatewayClass{}, + }, + } { + t.Run(name, func(t *testing.T) { + tt := tt + + t.Parallel() + + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + objs := []client.Object{} + if tt.gatewayClass != nil { + tt.gatewayClass.Name = "gateway-class" + objs = append(objs, tt.gatewayClass) + } + if tt.gatewayClassConfig != nil { + tt.gatewayClassConfig.Name = "gateway-class-config" + objs = append(objs, tt.gatewayClassConfig) + } + + client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + flagGatewayClassName: "gateway-class", + flagGatewayClassConfigName: "gateway-class-config", + } + + code := cmd.Run([]string{ + "-gateway-class-config-name", "gateway-class-config", + "-gateway-class-name", "gateway-class", + }) + + require.Equal(t, 0, code) + }) + } +} diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 05937dfe90..c7c04dd481 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -15,6 +15,8 @@ import ( "sync" "syscall" + apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" @@ -38,9 +40,13 @@ import ( "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -const WebhookCAFilename = "ca.crt" +const ( + WebhookCAFilename = "ca.crt" +) type Command struct { UI cli.Ui @@ -136,6 +142,8 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) // We need v1alpha1 here to add the peering api to the scheme utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(gwv1beta1.AddToScheme(scheme)) + utilruntime.Must(gwv1alpha2.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -447,15 +455,70 @@ func (c *Command) Run(args []string) int { return 1 } - consulMeta := apicommon.ConsulMeta{ - PartitionsEnabled: c.flagEnablePartitions, - Partition: c.consul.Partition, - NamespacesEnabled: c.flagEnableNamespaces, - DestinationNamespace: c.flagConsulDestinationNamespace, - Mirroring: c.flagEnableK8SNSMirroring, - Prefix: c.flagK8SNSMirroringPrefix, + // API Gateway Controllers + if err := gatewaycontrollers.RegisterFieldIndexes(ctx, mgr); err != nil { + setupLog.Error(err, "unable to register field indexes") + return 1 + } + + if err = (&gatewaycontrollers.GatewayClassConfigController{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName("gateways"), + }).SetupWithManager(ctx, mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", gatewaycontrollers.GatewayClassConfigController{}) + return 1 } + if err := (&gatewaycontrollers.GatewayClassController{ + ControllerName: gatewaycontrollers.GatewayClassControllerName, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("GatewayClass"), + }).SetupWithManager(ctx, mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "GatewayClass") + return 1 + } + + cache, err := gatewaycontrollers.SetupGatewayControllerWithManager(ctx, mgr, gatewaycontrollers.GatewayControllerConfig{ + HelmConfig: apigateway.HelmConfig{ + ConsulConfig: apigateway.ConsulConfig{ + Address: c.consul.Addresses, + GRPCPort: consulConfig.GRPCPort, + HTTPPort: consulConfig.HTTPPort, + APITimeout: consulConfig.APITimeout, + }, + ImageDataplane: c.flagConsulDataplaneImage, + ImageConsulK8S: c.flagConsulK8sImage, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + NamespaceMirroringPrefix: c.flagK8SNSMirroringPrefix, + EnableNamespaces: c.flagEnableNamespaces, + PeeringEnabled: c.flagEnablePeering, + EnableOpenShift: c.flagEnableOpenShift, + EnableNamespaceMirroring: c.flagEnableK8SNSMirroring, + AuthMethod: c.flagACLAuthMethod, + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, + TLSEnabled: c.consul.UseTLS, + ConsulTLSServerName: c.consul.TLSServerName, + ConsulPartition: c.consul.Partition, + ConsulCACert: string(caCertPem), + }, + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + NamespacesEnabled: c.flagEnableNamespaces, + Partition: c.consul.Partition, + }) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Gateway") + return 1 + } + + go cache.Run(ctx) + + // wait for the cache to fill + setupLog.Info("waiting for Consul cache sync") + cache.WaitSynced(ctx) + setupLog.Info("Consul cache synced") + configEntryReconciler := &controllers.ConfigEntryController{ ConsulClientConfig: c.consul.ConsulClientConfig(), ConsulServerConnMgr: watcher, @@ -653,6 +716,15 @@ func (c *Command) Run(args []string) int { LogJSON: c.flagLogJSON, }}) + consulMeta := apicommon.ConsulMeta{ + PartitionsEnabled: c.flagEnablePartitions, + Partition: c.consul.Partition, + NamespacesEnabled: c.flagEnableNamespaces, + DestinationNamespace: c.flagConsulDestinationNamespace, + Mirroring: c.flagEnableK8SNSMirroring, + Prefix: c.flagK8SNSMirroringPrefix, + } + // Note: The path here should be identical to the one on the kubebuilder // annotation in each webhook file. mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", diff --git a/hack/copy-crds-to-chart/main.go b/hack/copy-crds-to-chart/main.go index 0f94a77b5d..b5eef3e1aa 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -18,6 +18,12 @@ var ( "consul.hashicorp.com_peeringacceptors.yaml": {}, "consul.hashicorp.com_peeringdialers.yaml": {}, } + // HACK IT (again)! These CRDs need to go in the Helm chart's crds directory which means they + // cannot have any templating in them. They need to be in the CRD directory because we install + // resources that reference them in the main installation sequence. + toCRDDir = map[string]struct{}{ + "consul.hashicorp.com_gatewayclassconfigs.yaml": {}, + } ) func main() { @@ -57,29 +63,36 @@ func realMain(helmPath string) error { if _, ok := requiresPeering[info.Name()]; ok { // Add {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} {{- end }} wrapper. contents = fmt.Sprintf("{{- if and .Values.connectInject.enabled .Values.global.peering.enabled }}\n%s{{- end }}\n", contents) + } else if _, ok := toCRDDir[info.Name()]; ok { + // No-op (we don't want templating). } else { // Add {{- if .Values.connectInject.enabled }} {{- end }} wrapper. contents = fmt.Sprintf("{{- if .Values.connectInject.enabled }}\n%s{{- end }}\n", contents) } - // Add labels, this is hacky because we're relying on the line number - // but it means we don't need to regex or yaml parse. - splitOnNewlines := strings.Split(contents, "\n") - labelLines := []string{ - ` labels:`, - ` app: {{ template "consul.name" . }}`, - ` chart: {{ template "consul.chart" . }}`, - ` heritage: {{ .Release.Service }}`, - ` release: {{ .Release.Name }}`, - ` component: crd`, + if _, ok := toCRDDir[info.Name()]; !ok { + // Add labels, this is hacky because we're relying on the line number + // but it means we don't need to regex or yaml parse. + splitOnNewlines := strings.Split(contents, "\n") + labelLines := []string{ + ` labels:`, + ` app: {{ template "consul.name" . }}`, + ` chart: {{ template "consul.chart" . }}`, + ` heritage: {{ .Release.Service }}`, + ` release: {{ .Release.Name }}`, + ` component: crd`, + } + withLabels := append(splitOnNewlines[0:9], append(labelLines, splitOnNewlines[9:]...)...) + contents = strings.Join(withLabels, "\n") } - withLabels := append(splitOnNewlines[0:9], append(labelLines, splitOnNewlines[9:]...)...) - contents = strings.Join(withLabels, "\n") // Construct the destination filename. filenameSplit := strings.Split(info.Name(), "_") crdName := filenameSplit[1] destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName)) + if _, ok := toCRDDir[info.Name()]; ok { + destinationPath = filepath.Join(helmPath, "crds", formatCRDName(info.Name())) + } // Write it. printf("writing to %s", destinationPath) @@ -90,3 +103,9 @@ func realMain(helmPath string) error { func printf(format string, args ...interface{}) { fmt.Println(fmt.Sprintf(format, args...)) } + +func formatCRDName(name string) string { + name = strings.TrimSuffix(name, ".yaml") + segments := strings.Split(name, "_") + return fmt.Sprintf("%s.%s.yaml", segments[1], segments[0]) +} From e7d528ac2fd550b427db8b37de4c6d9c3c1d1405 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 26 May 2023 12:19:22 -0400 Subject: [PATCH 169/592] Update consul image on prepare-dev and prepare-release (#2180) Update consul image on prepare-dev and prepare-release --- .github/workflows/pr.yml | 3 +- Makefile | 18 +- charts/consul/Chart.yaml | 4 +- charts/consul/values.yaml | 70 +- .../build-support/functions/10-util.sh | 1689 ++++++++--------- .../build-support/scripts/consul-version.sh | 3 +- 6 files changed, 867 insertions(+), 920 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b4b431693a..2959554e21 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,7 +5,6 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests. We use this consul image on release branches too BRANCH: ${{ github.head_ref || github.ref_name }} CONTEXT: "pr" SHA: ${{ github.event.pull_request.head.sha || github.sha }} @@ -22,4 +21,4 @@ jobs: repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/Makefile b/Makefile index 6fa53d77c3..f687c308c5 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ VERSION = $(shell ./control-plane/build-support/scripts/version.sh control-plane/version/version.go) -CONSUL_VERSION = $(shell ./control-plane/build-support/scripts/consul-version.sh charts/consul/values.yaml) +CONSUL_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-version.sh charts/consul/values.yaml) # ===========> Helm Targets @@ -162,8 +162,7 @@ version: @echo $(VERSION) consul-version: - @echo $(CONSUL_VERSION) - + @echo $(CONSUL_IMAGE_VERSION) # ===========> Release Targets @@ -174,7 +173,13 @@ endif ifndef RELEASE_DATE $(error RELEASE_DATE is required, use format , (ex. October 4, 2022)) endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" $(LAST_RELEASE_GIT_TAG) $(PRERELEASE_VERSION) +ifndef LAST_RELEASE_GIT_TAG + $(error LAST_RELEASE_GIT_TAG is required) +endif +ifndef CONSUL_VERSION + $(error CONSUL_VERSION is required) +endif + source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" $(LAST_RELEASE_GIT_TAG) $(CONSUL_VERSION) $(PRERELEASE_VERSION) prepare-dev: ifndef RELEASE_VERSION @@ -186,7 +191,10 @@ endif ifndef NEXT_RELEASE_VERSION $(error NEXT_RELEASE_VERSION is required) endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" $(NEXT_RELEASE_VERSION) +ifndef NEXT_CONSUL_VERSION + $(error NEXT_CONSUL_VERSION is required) +endif + source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" "" $(NEXT_RELEASE_VERSION) $(NEXT_CONSUL_VERSION) # ===========> Makefile config diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index c55c6be6a2..d0216aa36e 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -4,7 +4,7 @@ apiVersion: v2 name: consul version: 1.2.0-dev -appVersion: 1.16-dev +appVersion: 1.15.1 kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -16,7 +16,7 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.16-dev + image: hashicorp/consul:1.15.1 - name: consul-k8s-control-plane image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.0-dev - name: consul-dataplane diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index e71282c520..5bc0dd33bf 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-enterprise:1.16-dev + image: "hashicorp/consul:1.15.1" # 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. @@ -80,7 +80,7 @@ global: # - name: pull-secret-name-2 # ``` # @type: array - imagePullSecrets: [ ] + imagePullSecrets: [] # The name (and tag) of the consul-k8s-control-plane Docker # image that is used for functionality such as catalog sync. @@ -295,7 +295,7 @@ global: # Refer to [`-recursor`](https://developer.hashicorp.com/consul/docs/agent/config/cli-flags#_recursor) for more details. # If this is an empty array (the default), then Consul DNS will only resolve queries for the Consul top level domain (by default `.consul`). # @type: array - recursors: [ ] + recursors: [] # Enables [TLS](https://developer.hashicorp.com/consul/tutorials/security/tls-encryption-secure) # across the cluster to verify authenticity of the Consul servers and clients. @@ -316,13 +316,13 @@ global: # in the server certificate. This is useful when you need to access the # Consul server(s) externally, for example, if you're using the UI. # @type: array - serverAdditionalDNSSANs: [ ] + serverAdditionalDNSSANs: [] # A list of additional IP addresses to set as Subject Alternative Names (SANs) # in the server certificate. This is useful when you need to access the # Consul server(s) externally, for example, if you're using the UI. # @type: array - serverAdditionalIPSANs: [ ] + serverAdditionalIPSANs: [] # If true, `verify_outgoing`, `verify_server_hostname`, # and `verify_incoming` for internal RPC communication will be set to `true` for Consul servers and clients. @@ -389,7 +389,6 @@ global: # Configure ACLs. acls: - # If true, the Helm chart will automatically manage ACL tokens and policies # for all Consul and consul-k8s-control-plane components. # This requires Consul >= 1.4. @@ -505,7 +504,7 @@ global: # A list of addresses of the primary mesh gateways in the form `:`. # (e.g. ["1.1.1.1:443", "2.3.4.5:443"] # @type: array - primaryGateways: [ ] + primaryGateways: [] # If you are setting `global.federation.enabled` to true and are in a secondary datacenter, # set `k8sAuthMethodHost` to the address of the Kubernetes API server of the secondary datacenter. @@ -655,7 +654,6 @@ global: # be disabled if you plan on connecting to a Consul cluster external to # the Kube cluster. server: - # If true, the chart will install all the resources necessary for a # Consul server cluster. If you're running Consul externally and want agents # within Kubernetes to join that cluster, this should probably be false. @@ -910,7 +908,7 @@ server: # with `-config-dir`. This defaults to false. # # @type: array - extraVolumes: [ ] + extraVolumes: [] # A list of sidecar containers. # Example: @@ -923,7 +921,7 @@ server: # - ... # ``` # @type: array - extraContainers: [ ] + extraContainers: [] # This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) # for server pods. It defaults to allowing only a single server pod on each node, which @@ -1079,7 +1077,7 @@ server: # feature, in case kubernetes cluster is behind egress http proxies. Additionally, # it could be used to configure custom consul parameters. # @type: map - extraEnvironmentVars: { } + extraEnvironmentVars: {} # [Enterprise Only] Values for setting up and running # [snapshot agents](https://developer.hashicorp.com/consul/commands/snapshot/agent) @@ -1135,21 +1133,21 @@ server: # as servers, and other settings to limit exposure too many requests, requests # waiting for too long, and other runtime considerations. limits: - # This object specifies configurations that limit the rate of RPC and gRPC + # This object specifies configurations that limit the rate of RPC and gRPC # requests on the Consul server. Limiting the rate of gRPC and RPC requests - # also limits HTTP requests to the Consul server. + # also limits HTTP requests to the Consul server. # https://developer.hashicorp.com/consul/docs/agent/config/config-files#request_limits - requestLimits: + requestLimits: # Setting for disabling or enabling rate limiting. If not disabled, it # enforces the action that will occur when RequestLimitsReadRate # or RequestLimitsWriteRate is exceeded. The default value of "disabled" will # prevent any rate limiting from occuring. A value of "enforce" will block # the request from processings by returning an error. A value of - # "permissive" will not block the request and will allow the request to + # "permissive" will not block the request and will allow the request to # continue processing. # @type: string mode: "disabled" - + # Setting that controls how frequently RPC, gRPC, and HTTP # queries are allowed to happen. In any large enough time interval, rate # limiter limits the rate to RequestLimitsReadRate tokens per second. @@ -1158,7 +1156,7 @@ server: # buckets. # @type: integer readRate: -1 - + # Setting that controls how frequently RPC, gRPC, and HTTP # writes are allowed to happen. In any large enough time interval, rate # limiter limits the rate to RequestLimitsWriteRate tokens per second. @@ -1187,7 +1185,7 @@ externalServers: # should be the same, however, they may be different if you # wish to use separate hosts for the HTTPS connections. # @type: array - hosts: [ ] + hosts: [] # The HTTPS port of the Consul servers. httpsPort: 8501 @@ -1385,7 +1383,7 @@ client: # with `-config-dir`. This defaults to false. # # @type: array - extraVolumes: [ ] + extraVolumes: [] # A list of sidecar containers. # Example: @@ -1398,7 +1396,7 @@ client: # - ... # ``` # @type: array - extraContainers: [ ] + extraContainers: [] # Toleration Settings for Client pods # This should be a multi-line string matching the Toleration array @@ -1476,7 +1474,7 @@ client: # feature, in case kubernetes cluster is behind egress http proxies. Additionally, # it could be used to configure custom consul parameters. # @type: map - extraEnvironmentVars: { } + extraEnvironmentVars: {} # This value defines the [Pod DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy) # for client pods to use. @@ -1570,7 +1568,6 @@ ui: # Set the port value of the UI service. port: - # HTTP port. http: 80 @@ -1581,7 +1578,6 @@ ui: # If not set and using a NodePort service, Kubernetes will automatically assign # a port. nodePort: - # HTTP node port # @type: integer http: null @@ -1634,7 +1630,7 @@ ui: # ``` # # @type: array - hosts: [ ] + hosts: [] # tls is a list of hosts and secret name in an Ingress # which tells the Ingress controller to secure the channel. @@ -1646,7 +1642,7 @@ ui: # secretName: testsecret-tls # ``` # @type: array - tls: [ ] + tls: [] # Annotations to apply to the UI ingress. # @@ -1737,7 +1733,7 @@ syncCatalog: # # Note: `k8sDenyNamespaces` takes precedence over values defined here. # @type: array - k8sAllowNamespaces: [ "*" ] + k8sAllowNamespaces: ["*"] # List of k8s namespaces that should not have their # services synced. This list takes precedence over `k8sAllowNamespaces`. @@ -1747,7 +1743,7 @@ syncCatalog: # `["namespace1", "namespace2"]`, then all k8s namespaces besides `namespace1` # and `namespace2` will be synced. # @type: array - k8sDenyNamespaces: [ "kube-system", "kube-public" ] + k8sDenyNamespaces: ["kube-system", "kube-public"] # [DEPRECATED] Use k8sAllowNamespaces and k8sDenyNamespaces instead. For # backwards compatibility, if both this and the allow/deny lists are set, @@ -2166,7 +2162,6 @@ connectInject: # @type: map meta: null - # Configures metrics for Consul Connect services. All values are overridable # via annotations on a per-pod basis. metrics: @@ -2326,7 +2321,7 @@ connectInject: # `namespaceSelector` takes precedence over both since it is applied first. # `kube-system` and `kube-public` are never injected, even if included here. # @type: array - k8sAllowNamespaces: [ "*" ] + k8sAllowNamespaces: ["*"] # List of k8s namespaces that should not allow Connect # sidecar injection. This list takes precedence over `k8sAllowNamespaces`. @@ -2339,7 +2334,7 @@ connectInject: # Note: `namespaceSelector` takes precedence over this since it is applied first. # `kube-system` and `kube-public` are never injected. # @type: array - k8sDenyNamespaces: [ ] + k8sDenyNamespaces: [] # [Enterprise Only] These settings manage the connect injector's interaction with # Consul namespaces (requires consul-ent v1.7+). @@ -2724,10 +2719,10 @@ ingressGateways: # @default: [{port: 8080, port: 8443}] # @recurse: false ports: - - port: 8080 - nodePort: null - - port: 8443 - nodePort: null + - port: 8080 + nodePort: null + - port: 8443 + nodePort: null # Annotations to apply to the ingress gateway service. Annotations defined # here will be applied to all ingress gateway services in addition to any @@ -2852,7 +2847,7 @@ ingressGateways: # case of annotations where both will be applied. # @type: array gateways: - - name: ingress-gateway + - name: ingress-gateway # Configuration options for terminating gateways. Default values for all # terminating gateways are defined in `terminatingGateways.defaults`. Any of @@ -2887,7 +2882,7 @@ terminatingGateways: # path: path # secret will now mount to /consul/userconfig/my-secret/path # ``` # @type: array - extraVolumes: [ ] + extraVolumes: [] # Resource limits for all terminating gateway pods # @recurse: false @@ -2993,7 +2988,7 @@ terminatingGateways: # case of annotations where both will be applied. # @type: array gateways: - - name: terminating-gateway + - name: terminating-gateway # [DEPRECATED] Use connectInject.apiGateway instead. This stanza will be removed with the release of Consul 1.17 # Configuration settings for the Consul API Gateway integration @@ -3167,7 +3162,6 @@ apiGateway: # Configuration settings for the webhook-cert-manager # `webhook-cert-manager` ensures that cert bundles are up to date for the mutating webhook. webhookCertManager: - # Toleration Settings # This should be a multi-line string matching the Toleration array # in a PodSpec. diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index 26c43cb610..9ba6c26da9 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -2,984 +2,931 @@ # SPDX-License-Identifier: MPL-2.0 function err { - if test "${COLORIZE}" -eq 1 - then - tput bold - tput setaf 1 - fi + if test "${COLORIZE}" -eq 1; then + tput bold + tput setaf 1 + fi - echo "$@" 1>&2 + echo "$@" 1>&2 - if test "${COLORIZE}" -eq 1 - then - tput sgr0 - fi + if test "${COLORIZE}" -eq 1; then + tput sgr0 + fi } function status { - if test "${COLORIZE}" -eq 1 - then - tput bold - tput setaf 4 - fi + if test "${COLORIZE}" -eq 1; then + tput bold + tput setaf 4 + fi - echo "$@" + echo "$@" - if test "${COLORIZE}" -eq 1 - then - tput sgr0 - fi + if test "${COLORIZE}" -eq 1; then + tput sgr0 + fi } function status_stage { - if test "${COLORIZE}" -eq 1 - then - tput bold - tput setaf 2 - fi + if test "${COLORIZE}" -eq 1; then + tput bold + tput setaf 2 + fi - echo "$@" + echo "$@" - if test "${COLORIZE}" -eq 1 - then - tput sgr0 - fi + if test "${COLORIZE}" -eq 1; then + tput sgr0 + fi } function debug { - if is_set "${BUILD_DEBUG}" - then - if test "${COLORIZE}" -eq 1 - then - tput setaf 6 - fi - echo "$@" - if test "${COLORIZE}" -eq 1 - then - tput sgr0 - fi - fi + if is_set "${BUILD_DEBUG}"; then + if test "${COLORIZE}" -eq 1; then + tput setaf 6 + fi + echo "$@" + if test "${COLORIZE}" -eq 1; then + tput sgr0 + fi + fi } function sed_i { - if test "$(uname)" == "Darwin" - then - sed -i '' "$@" - return $? - else - sed -i "$@" - return $? - fi + if test "$(uname)" == "Darwin"; then + sed -i '' "$@" + return $? + else + sed -i "$@" + return $? + fi } function is_set { - # Arguments: - # $1 - string value to check its truthiness - # - # Return: - # 0 - is truthy (backwards I know but allows syntax like `if is_set ` to work) - # 1 - is not truthy - - local val=$(tr '[:upper:]' '[:lower:]' <<< "$1") - case $val in - 1 | t | true | y | yes) - return 0 - ;; - *) - return 1 - ;; - esac + # Arguments: + # $1 - string value to check its truthiness + # + # Return: + # 0 - is truthy (backwards I know but allows syntax like `if is_set ` to work) + # 1 - is not truthy + + local val=$(tr '[:upper:]' '[:lower:]' <<<"$1") + case $val in + 1 | t | true | y | yes) + return 0 + ;; + *) + return 1 + ;; + esac } function have_gpg_key { - # Arguments: - # $1 - GPG Key id to check if we have installed - # - # Return: - # 0 - success (we can use this key for signing) - # * - failure (key cannot be used) - - gpg --list-secret-keys $1 > /dev/null 2>&1 - return $? + # Arguments: + # $1 - GPG Key id to check if we have installed + # + # Return: + # 0 - success (we can use this key for signing) + # * - failure (key cannot be used) + + gpg --list-secret-keys $1 >/dev/null 2>&1 + return $? } function parse_version { - # Arguments: - # $1 - Path to the top level Consul source - # $2 - boolean value for whether the release version should be parsed from the source - # $3 - boolean whether to use GIT_DESCRIBE and GIT_COMMIT environment variables - # $4 - boolean whether to omit the version part of the version string. (optional) - # - # Return: - # 0 - success (will write the version to stdout) - # * - error (no version output) - # - # Notes: - # If the GOTAGS environment variable is present then it is used to determine which - # version file to use for parsing. - - local vfile="${1}/version/version.go" - - # ensure the version file exists - if ! test -f "${vfile}" - then - err "Error - File not found: ${vfile}" - return 1 - fi - - local include_release="$2" - local use_git_env="$3" - local omit_version="$4" - - local git_version="" - local git_commit="" - - if test -z "${include_release}" - then - include_release=true - fi - - if test -z "${use_git_env}" - then - use_git_env=true - fi - - if is_set "${use_git_env}" - then - git_version="${GIT_DESCRIBE}" - git_commit="${GIT_COMMIT}" - fi - - # Get the main version out of the source file - version_main=$(awk '$1 == "Version" && $2 == "=" { gsub(/"/, "", $3); print $3 }' < ${vfile}) - release_main=$(awk '$1 == "VersionPrerelease" && $2 == "=" { gsub(/"/, "", $3); print $3 }' < ${vfile}) - - - # try to determine the version if we have build tags - for tag in "$GOTAGS" - do - for vfile in $(find "${1}/version" -name "version_*.go" 2> /dev/null| sort) - do - if grep -q "// +build $tag" "${vfile}" - then - version_main=$(awk '$1 == "Version" && $2 == "=" { gsub(/"/, "", $3); print $3 }' < ${vfile}) - release_main=$(awk '$1 == "VersionPrerelease" && $2 == "=" { gsub(/"/, "", $3); print $3 }' < ${vfile}) - fi - done - done - - local version="${version_main}" - # override the version from source with the value of the GIT_DESCRIBE env var if present - if test -n "${git_version}" - then - version="${git_version}" - fi - - local rel_ver="" - if is_set "${include_release}" - then - # Default to pre-release from the source - rel_ver="${release_main}" - - # When no GIT_DESCRIBE env var is present and no release is in the source then we - # are definitely in dev mode - if test -z "${git_version}" -a -z "${rel_ver}" && is_set "${use_git_env}" - then - rel_ver="dev" - fi - - # Add the release to the version - if test -n "${rel_ver}" -a -n "${git_commit}" - then - rel_ver="${rel_ver} (${git_commit})" - fi - fi - - if test -n "${rel_ver}" - then - if is_set "${omit_version}" - then - echo "${rel_ver}" | tr -d "'" - else - echo "${version}-${rel_ver}" | tr -d "'" - fi - return 0 - elif ! is_set "${omit_version}" - then - echo "${version}" | tr -d "'" - return 0 - else - return 1 - fi + # Arguments: + # $1 - Path to the top level Consul source + # $2 - boolean value for whether the release version should be parsed from the source + # $3 - boolean whether to use GIT_DESCRIBE and GIT_COMMIT environment variables + # $4 - boolean whether to omit the version part of the version string. (optional) + # + # Return: + # 0 - success (will write the version to stdout) + # * - error (no version output) + # + # Notes: + # If the GOTAGS environment variable is present then it is used to determine which + # version file to use for parsing. + + local vfile="${1}/version/version.go" + + # ensure the version file exists + if ! test -f "${vfile}"; then + err "Error - File not found: ${vfile}" + return 1 + fi + + local include_release="$2" + local use_git_env="$3" + local omit_version="$4" + + local git_version="" + local git_commit="" + + if test -z "${include_release}"; then + include_release=true + fi + + if test -z "${use_git_env}"; then + use_git_env=true + fi + + if is_set "${use_git_env}"; then + git_version="${GIT_DESCRIBE}" + git_commit="${GIT_COMMIT}" + fi + + # Get the main version out of the source file + version_main=$(awk '$1 == "Version" && $2 == "=" { gsub(/"/, "", $3); print $3 }' <${vfile}) + release_main=$(awk '$1 == "VersionPrerelease" && $2 == "=" { gsub(/"/, "", $3); print $3 }' <${vfile}) + + # try to determine the version if we have build tags + for tag in "$GOTAGS"; do + for vfile in $(find "${1}/version" -name "version_*.go" 2>/dev/null | sort); do + if grep -q "// +build $tag" "${vfile}"; then + version_main=$(awk '$1 == "Version" && $2 == "=" { gsub(/"/, "", $3); print $3 }' <${vfile}) + release_main=$(awk '$1 == "VersionPrerelease" && $2 == "=" { gsub(/"/, "", $3); print $3 }' <${vfile}) + fi + done + done + + local version="${version_main}" + # override the version from source with the value of the GIT_DESCRIBE env var if present + if test -n "${git_version}"; then + version="${git_version}" + fi + + local rel_ver="" + if is_set "${include_release}"; then + # Default to pre-release from the source + rel_ver="${release_main}" + + # When no GIT_DESCRIBE env var is present and no release is in the source then we + # are definitely in dev mode + if test -z "${git_version}" -a -z "${rel_ver}" && is_set "${use_git_env}"; then + rel_ver="dev" + fi + + # Add the release to the version + if test -n "${rel_ver}" -a -n "${git_commit}"; then + rel_ver="${rel_ver} (${git_commit})" + fi + fi + + if test -n "${rel_ver}"; then + if is_set "${omit_version}"; then + echo "${rel_ver}" | tr -d "'" + else + echo "${version}-${rel_ver}" | tr -d "'" + fi + return 0 + elif ! is_set "${omit_version}"; then + echo "${version}" | tr -d "'" + return 0 + else + return 1 + fi } function get_version { - # Arguments: - # $1 - Path to the top level Consul source - # $2 - Whether the release version should be parsed from source (optional) - # $3 - Whether to use GIT_DESCRIBE and GIT_COMMIT environment variables - # - # Returns: - # 0 - success (the version is also echoed to stdout) - # 1 - error - # - # Notes: - # If a VERSION environment variable is present it will override any parsing of the version from the source - # In addition to processing the main version.go, version_*.go files will be processed if they have - # a Go build tag that matches the one in the GOTAGS environment variable. This tag processing is - # primitive though and will not match complex build tags in the files with negation etc. - - local vers="$VERSION" - if test -z "$vers" - then - # parse the OSS version from version.go - vers="$(parse_version ${1} ${2} ${3})" - fi - - if test -z "$vers" - then - return 1 - else - echo $vers - return 0 - fi + # Arguments: + # $1 - Path to the top level Consul source + # $2 - Whether the release version should be parsed from source (optional) + # $3 - Whether to use GIT_DESCRIBE and GIT_COMMIT environment variables + # + # Returns: + # 0 - success (the version is also echoed to stdout) + # 1 - error + # + # Notes: + # If a VERSION environment variable is present it will override any parsing of the version from the source + # In addition to processing the main version.go, version_*.go files will be processed if they have + # a Go build tag that matches the one in the GOTAGS environment variable. This tag processing is + # primitive though and will not match complex build tags in the files with negation etc. + + local vers="$VERSION" + if test -z "$vers"; then + # parse the OSS version from version.go + vers="$(parse_version ${1} ${2} ${3})" + fi + + if test -z "$vers"; then + return 1 + else + echo $vers + return 0 + fi } function git_branch { - # Arguments: - # $1 - Path to the git repo (optional - assumes pwd is git repo otherwise) - # - # Returns: - # 0 - success - # * - failure - # - # Notes: - # Echos the current branch to stdout when successful - - local gdir="$(pwd)" - if test -d "$1" - then - gdir="$1" - fi - - pushd "${gdir}" > /dev/null - - local ret=0 - local head="$(git status -b --porcelain=v2 | awk '{if ($1 == "#" && $2 =="branch.head") { print $3 }}')" || ret=1 - - popd > /dev/null - - test ${ret} -eq 0 && echo "$head" - return ${ret} + # Arguments: + # $1 - Path to the git repo (optional - assumes pwd is git repo otherwise) + # + # Returns: + # 0 - success + # * - failure + # + # Notes: + # Echos the current branch to stdout when successful + + local gdir="$(pwd)" + if test -d "$1"; then + gdir="$1" + fi + + pushd "${gdir}" >/dev/null + + local ret=0 + local head="$(git status -b --porcelain=v2 | awk '{if ($1 == "#" && $2 =="branch.head") { print $3 }}')" || ret=1 + + popd >/dev/null + + test ${ret} -eq 0 && echo "$head" + return ${ret} } function git_upstream { - # Arguments: - # $1 - Path to the git repo (optional - assumes pwd is git repo otherwise) - # - # Returns: - # 0 - success - # * - failure - # - # Notes: - # Echos the current upstream branch to stdout when successful - - local gdir="$(pwd)" - if test -d "$1" - then - gdir="$1" - fi - - pushd "${gdir}" > /dev/null - - local ret=0 - local head="$(git status -b --porcelain=v2 | awk '{if ($1 == "#" && $2 =="branch.upstream") { print $3 }}')" || ret=1 - - popd > /dev/null - - test ${ret} -eq 0 && echo "$head" - return ${ret} + # Arguments: + # $1 - Path to the git repo (optional - assumes pwd is git repo otherwise) + # + # Returns: + # 0 - success + # * - failure + # + # Notes: + # Echos the current upstream branch to stdout when successful + + local gdir="$(pwd)" + if test -d "$1"; then + gdir="$1" + fi + + pushd "${gdir}" >/dev/null + + local ret=0 + local head="$(git status -b --porcelain=v2 | awk '{if ($1 == "#" && $2 =="branch.upstream") { print $3 }}')" || ret=1 + + popd >/dev/null + + test ${ret} -eq 0 && echo "$head" + return ${ret} } function git_log_summary { - # Arguments: - # $1 - Path to the git repo (optional - assumes pwd is git repo otherwise) - # - # Returns: - # 0 - success - # * - failure - # + # Arguments: + # $1 - Path to the git repo (optional - assumes pwd is git repo otherwise) + # + # Returns: + # 0 - success + # * - failure + # - local gdir="$(pwd)" - if test -d "$1" - then - gdir="$1" - fi + local gdir="$(pwd)" + if test -d "$1"; then + gdir="$1" + fi - pushd "${gdir}" > /dev/null + pushd "${gdir}" >/dev/null - local ret=0 + local ret=0 - local head=$(git_branch) || ret=1 - local upstream=$(git_upstream) || ret=1 - local rev_range="${head}...${upstream}" + local head=$(git_branch) || ret=1 + local upstream=$(git_upstream) || ret=1 + local rev_range="${head}...${upstream}" - if test ${ret} -eq 0 - then - status "Git Changes:" - git log --pretty=oneline ${rev_range} || ret=1 + if test ${ret} -eq 0; then + status "Git Changes:" + git log --pretty=oneline ${rev_range} || ret=1 - fi - return $ret + fi + return $ret } function git_diff { - # Arguments: - # $1 - Path to the git repo (optional - assumes pwd is git repo otherwise) - # $2 .. $N - Optional path specification - # - # Returns: - # 0 - success - # * - failure - # - - local gdir="$(pwd)" - if test -d "$1" - then - gdir="$1" - fi - - shift - - pushd "${gdir}" > /dev/null - - local ret=0 - - local head=$(git_branch) || ret=1 - local upstream=$(git_upstream) || ret=1 - - if test ${ret} -eq 0 - then - status "Git Diff - Paths: $@" - git diff ${HEAD} ${upstream} -- "$@" || ret=1 - fi - return $ret + # Arguments: + # $1 - Path to the git repo (optional - assumes pwd is git repo otherwise) + # $2 .. $N - Optional path specification + # + # Returns: + # 0 - success + # * - failure + # + + local gdir="$(pwd)" + if test -d "$1"; then + gdir="$1" + fi + + shift + + pushd "${gdir}" >/dev/null + + local ret=0 + + local head=$(git_branch) || ret=1 + local upstream=$(git_upstream) || ret=1 + + if test ${ret} -eq 0; then + status "Git Diff - Paths: $@" + git diff ${HEAD} ${upstream} -- "$@" || ret=1 + fi + return $ret } function normalize_git_url { - url="${1#https://}" - url="${url#git@}" - url="${url%.git}" - url="$(sed ${SED_EXT} -e 's/([^\/:]*)[:\/](.*)/\1:\2/' <<< "${url}")" - echo "$url" - return 0 + url="${1#https://}" + url="${url#git@}" + url="${url%.git}" + url="$(sed ${SED_EXT} -e 's/([^\/:]*)[:\/](.*)/\1:\2/' <<<"${url}")" + echo "$url" + return 0 } function git_remote_url { - # Arguments: - # $1 - Path to the top level Consul source - # $2 - Remote name - # - # Returns: - # 0 - success - # * - error - # - # Note: - # The push url for the git remote will be echoed to stdout - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. git_remote_url must be called with the path to the top level source as the first argument'" - return 1 - fi - - if test -z "$2" - then - err "ERROR: git_remote_url must be called with a second argument that is the name of the remote" - return 1 - fi - - local ret=0 - - pushd "$1" > /dev/null - - local url=$(git remote get-url --push $2 2>&1) || ret=1 - - popd > /dev/null - - if test "${ret}" -eq 0 - then - echo "${url}" - return 0 - fi + # Arguments: + # $1 - Path to the top level Consul source + # $2 - Remote name + # + # Returns: + # 0 - success + # * - error + # + # Note: + # The push url for the git remote will be echoed to stdout + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. git_remote_url must be called with the path to the top level source as the first argument'" + return 1 + fi + + if test -z "$2"; then + err "ERROR: git_remote_url must be called with a second argument that is the name of the remote" + return 1 + fi + + local ret=0 + + pushd "$1" >/dev/null + + local url=$(git remote get-url --push $2 2>&1) || ret=1 + + popd >/dev/null + + if test "${ret}" -eq 0; then + echo "${url}" + return 0 + fi } function find_git_remote { - # Arguments: - # $1 - Path to the top level Consul source - # - # Returns: - # 0 - success - # * - error - # - # Note: - # The remote name to use for publishing will be echoed to stdout upon success - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. find_git_remote must be called with the path to the top level source as the first argument'" - return 1 - fi - - need_url=$(normalize_git_url "${PUBLISH_GIT_HOST}:${PUBLISH_GIT_REPO}") - debug "Required normalized remote: ${need_url}" - - pushd "$1" > /dev/null - - local ret=1 - for remote in $(git remote) - do - url=$(git remote get-url --push ${remote}) || continue - url=$(normalize_git_url "${url}") - - debug "Testing Remote: ${remote}: ${url}" - if test "${url}" == "${need_url}" - then - echo "${remote}" - ret=0 - break - fi - done - - popd > /dev/null - return ${ret} + # Arguments: + # $1 - Path to the top level Consul source + # + # Returns: + # 0 - success + # * - error + # + # Note: + # The remote name to use for publishing will be echoed to stdout upon success + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. find_git_remote must be called with the path to the top level source as the first argument'" + return 1 + fi + + need_url=$(normalize_git_url "${PUBLISH_GIT_HOST}:${PUBLISH_GIT_REPO}") + debug "Required normalized remote: ${need_url}" + + pushd "$1" >/dev/null + + local ret=1 + for remote in $(git remote); do + url=$(git remote get-url --push ${remote}) || continue + url=$(normalize_git_url "${url}") + + debug "Testing Remote: ${remote}: ${url}" + if test "${url}" == "${need_url}"; then + echo "${remote}" + ret=0 + break + fi + done + + popd >/dev/null + return ${ret} } function git_remote_not_blacklisted { - # Arguments: - # $1 - path to the repo - # $2 - the remote name - # - # Returns: - # 0 - not blacklisted - # * - blacklisted - return 0 + # Arguments: + # $1 - path to the repo + # $2 - the remote name + # + # Returns: + # 0 - not blacklisted + # * - blacklisted + return 0 } function is_git_clean { - # Arguments: - # $1 - Path to git repo - # $2 - boolean whether the git status should be output when not clean - # - # Returns: - # 0 - success - # * - error - # - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. is_git_clean must be called with the path to a git repo as the first argument'" - return 1 - fi - - local output_status="$2" - - pushd "${1}" > /dev/null - - local ret=0 - test -z "$(git status --porcelain=v2 2> /dev/null)" || ret=1 - - if is_set "${output_status}" && test "$ret" -ne 0 - then - err "Git repo is not clean" - # --porcelain=v1 is the same as --short except uncolorized - git status --porcelain=v1 - fi - popd > /dev/null - return ${ret} + # Arguments: + # $1 - Path to git repo + # $2 - boolean whether the git status should be output when not clean + # + # Returns: + # 0 - success + # * - error + # + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. is_git_clean must be called with the path to a git repo as the first argument'" + return 1 + fi + + local output_status="$2" + + pushd "${1}" >/dev/null + + local ret=0 + test -z "$(git status --porcelain=v2 2>/dev/null)" || ret=1 + + if is_set "${output_status}" && test "$ret" -ne 0; then + err "Git repo is not clean" + # --porcelain=v1 is the same as --short except uncolorized + git status --porcelain=v1 + fi + popd >/dev/null + return ${ret} } function update_git_env { - # Arguments: - # $1 - Path to git repo - # - # Returns: - # 0 - success - # * - error - # - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. is_git_clean must be called with the path to a git repo as the first argument'" - return 1 - fi - - export GIT_COMMIT=$(git rev-parse --short HEAD) - export GIT_DIRTY=$(test -n "$(git status --porcelain)" && echo "+CHANGES") - export GIT_DESCRIBE=$(git describe --tags --always) - export GIT_IMPORT=github.com/hashicorp/consul-k8s/version - export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X ${GIT_IMPORT}.GitDescribe=${GIT_DESCRIBE}" - return 0 + # Arguments: + # $1 - Path to git repo + # + # Returns: + # 0 - success + # * - error + # + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. is_git_clean must be called with the path to a git repo as the first argument'" + return 1 + fi + + export GIT_COMMIT=$(git rev-parse --short HEAD) + export GIT_DIRTY=$(test -n "$(git status --porcelain)" && echo "+CHANGES") + export GIT_DESCRIBE=$(git describe --tags --always) + export GIT_IMPORT=github.com/hashicorp/consul-k8s/version + export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X ${GIT_IMPORT}.GitDescribe=${GIT_DESCRIBE}" + return 0 } function git_push_ref { - # Arguments: - # $1 - Path to the top level Consul source - # $2 - Git ref (optional) - # $3 - remote (optional - if not specified we will try to determine it) - # - # Returns: - # 0 - success - # * - error - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. push_git_release must be called with the path to the top level source as the first argument'" - return 1 - fi - - local sdir="$1" - local ret=0 - local remote="$3" - - # find the correct remote corresponding to the desired repo (basically prevent pushing enterprise to oss or oss to enterprise) - if test -z "${remote}" - then - local remote=$(find_git_remote "${sdir}") || return 1 - status "Using git remote: ${remote}" - fi - - local ref="" - - pushd "${sdir}" > /dev/null - - if test -z "$2" - then - # If no git ref was provided we lookup the current local branch and its tracking branch - # It must have a tracking upstream and it must be tracking the sanctioned git remote - local head=$(git_branch "${sdir}") || return 1 - local upstream=$(git_upstream "${sdir}") || return 1 - - # upstream branch for this branch does not track the remote we need to push to - # basically this checks that the upstream (could be something like origin/main) references the correct remote - # if it doesn't then the string modification wont apply and the var will reamin unchanged and equal to itself. - if test "${upstream#${remote}/}" == "${upstream}" - then - err "ERROR: Upstream branch '${upstream}' does not track the correct remote '${remote}' - cannot push" - ret=1 - fi - ref="refs/heads/${head}" - else - # A git ref was provided - get the full ref and make sure it isn't ambiguous and also to - # be able to determine whether its a branch or tag we are pushing - ref_out=$(git rev-parse --symbolic-full-name "$2" --) - - # -ne 2 because it should have the ref on one line followed by a line with '--' - if test "$(wc -l <<< "${ref_out}")" -ne 2 - then - err "ERROR: Git ref '$2' is ambiguous" - debug "${ref_out}" - ret=1 - else - ref=$(head -n 1 <<< "${ref_out}") - fi - fi - - if test ${ret} -eq 0 - then - case "${ref}" in - refs/tags/*) - status "Pushing tag ${ref#refs/tags/} to ${remote}" - ;; - refs/heads/*) - status "Pushing local branch ${ref#refs/tags/} to ${remote}" - ;; - *) - err "ERROR: git_push_ref func is refusing to push ref that isn't a branch or tag" - return 1 - esac - - if ! git push "${remote}" "${ref}" - then - err "ERROR: Failed to push ${ref} to remote: ${remote}" - ret=1 - fi - fi - - popd > /dev/null - - return $ret + # Arguments: + # $1 - Path to the top level Consul source + # $2 - Git ref (optional) + # $3 - remote (optional - if not specified we will try to determine it) + # + # Returns: + # 0 - success + # * - error + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. push_git_release must be called with the path to the top level source as the first argument'" + return 1 + fi + + local sdir="$1" + local ret=0 + local remote="$3" + + # find the correct remote corresponding to the desired repo (basically prevent pushing enterprise to oss or oss to enterprise) + if test -z "${remote}"; then + local remote=$(find_git_remote "${sdir}") || return 1 + status "Using git remote: ${remote}" + fi + + local ref="" + + pushd "${sdir}" >/dev/null + + if test -z "$2"; then + # If no git ref was provided we lookup the current local branch and its tracking branch + # It must have a tracking upstream and it must be tracking the sanctioned git remote + local head=$(git_branch "${sdir}") || return 1 + local upstream=$(git_upstream "${sdir}") || return 1 + + # upstream branch for this branch does not track the remote we need to push to + # basically this checks that the upstream (could be something like origin/main) references the correct remote + # if it doesn't then the string modification wont apply and the var will reamin unchanged and equal to itself. + if test "${upstream#${remote}/}" == "${upstream}"; then + err "ERROR: Upstream branch '${upstream}' does not track the correct remote '${remote}' - cannot push" + ret=1 + fi + ref="refs/heads/${head}" + else + # A git ref was provided - get the full ref and make sure it isn't ambiguous and also to + # be able to determine whether its a branch or tag we are pushing + ref_out=$(git rev-parse --symbolic-full-name "$2" --) + + # -ne 2 because it should have the ref on one line followed by a line with '--' + if test "$(wc -l <<<"${ref_out}")" -ne 2; then + err "ERROR: Git ref '$2' is ambiguous" + debug "${ref_out}" + ret=1 + else + ref=$(head -n 1 <<<"${ref_out}") + fi + fi + + if test ${ret} -eq 0; then + case "${ref}" in + refs/tags/*) + status "Pushing tag ${ref#refs/tags/} to ${remote}" + ;; + refs/heads/*) + status "Pushing local branch ${ref#refs/tags/} to ${remote}" + ;; + *) + err "ERROR: git_push_ref func is refusing to push ref that isn't a branch or tag" + return 1 + ;; + esac + + if ! git push "${remote}" "${ref}"; then + err "ERROR: Failed to push ${ref} to remote: ${remote}" + ret=1 + fi + fi + + popd >/dev/null + + return $ret } function update_version { - # Arguments: - # $1 - Path to the version file - # $2 - Version string - # $3 - PreRelease version (if unset will become an empty string) - # - # Returns: - # 0 - success - # * - error - - if ! test -f "$1" - then - err "ERROR: '$1' is not a regular file. update_version must be called with the path to a go version file" - return 1 - fi - - if test -z "$2" - then - err "ERROR: The version specified was empty" - return 1 - fi - - local vfile="$1" - local version="$2" - local prerelease="$3" - - sed_i ${SED_EXT} -e "s/(Version[[:space:]]*=[[:space:]]*)\"[^\"]*\"/\1\"${version}\"/g" -e "s/(VersionPrerelease[[:space:]]*=[[:space:]]*)\"[^\"]*\"/\1\"${prerelease}\"/g" "${vfile}" - return $? + # Arguments: + # $1 - Path to the version file + # $2 - Version string + # $3 - PreRelease version (if unset will become an empty string) + # + # Returns: + # 0 - success + # * - error + + if ! test -f "$1"; then + err "ERROR: '$1' is not a regular file. update_version must be called with the path to a go version file" + return 1 + fi + + if test -z "$2"; then + err "ERROR: The version specified was empty" + return 1 + fi + + local vfile="$1" + local version="$2" + local prerelease="$3" + + sed_i ${SED_EXT} -e "s/(Version[[:space:]]*=[[:space:]]*)\"[^\"]*\"/\1\"${version}\"/g" -e "s/(VersionPrerelease[[:space:]]*=[[:space:]]*)\"[^\"]*\"/\1\"${prerelease}\"/g" "${vfile}" + return $? } function update_version_helm { - # Arguments: - # $1 - Path to the directory where the root of the Helm chart is - # $2 - Version string - # $3 - PreRelease version (if unset will become an empty string) - # $4 - Image base path - # - # Returns: - # 0 - success - # * - error - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. update_version_helm must be called with the path to the Helm chart" - return 1 - fi - - if test -z "$2" - then - err "ERROR: The version specified was empty" - return 1 - fi - - local vfile="$1/values.yaml" - local cfile="$1/Chart.yaml" - local version="$2" - local prerelease="$3" - local full_version="$2" - if ! test -z "$3" - then - full_version="$2-$3" - fi - - sed_i ${SED_EXT} -e "s/(imageK8S:.*\/consul-k8s-control-plane:)[^\"]*/imageK8S: $4${full_version}/g" "${vfile}" - sed_i ${SED_EXT} -e "s/(version:[[:space:]]*)[^\"]*/\1${full_version}/g" "${cfile}" - sed_i ${SED_EXT} -e "s/(image:.*\/consul-k8s-control-plane:)[^\"]*/image: $4${full_version}/g" "${cfile}" - - if test -z "$3" - then - sed_i ${SED_EXT} -e "s/(artifacthub.io\/prerelease:[[:space:]]*)[^\"]*/\1false/g" "${cfile}" - else - sed_i ${SED_EXT} -e "s/(artifacthub.io\/prerelease:[[:space:]]*)[^\"]*/\1true/g" "${cfile}" - fi - return $? + # Arguments: + # $1 - Path to the directory where the root of the Helm chart is + # $2 - Version string + # $3 - Release version (if unset will become an empty string) + # $4 - Image base path + # $5 - Consul version string + # $6 - Consul image base path + # + # Returns: + # 0 - success + # * - error + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. update_version_helm must be called with the path to the Helm chart" + return 1 + fi + + if test -z "$2"; then + err "ERROR: The version specified was empty" + return 1 + fi + + local vfile="$1/values.yaml" + local cfile="$1/Chart.yaml" + local version="$2" + local prerelease="$3" + local full_version="$2" + local full_consul_version="$5" + if ! test -z "$3"; then + full_version="$2-$3" + # strip off the last minor patch version so that the consul image can be set to something like 1.16-dev. The image + # is produced by Consul every night + full_consul_version="${5%.*}-$3" + fi + + sed_i ${SED_EXT} -e "s/(imageK8S:.*\/consul-k8s-control-plane:)[^\"]*/imageK8S: $4${full_version}/g" "${vfile}" + sed_i ${SED_EXT} -e "s/(version:[[:space:]]*)[^\"]*/\1${full_version}/g" "${cfile}" + sed_i ${SED_EXT} -e "s/(appVersion:[[:space:]]*)[^\"]*/\1${full_consul_version}/g" "${cfile}" + sed_i ${SED_EXT} -e "s/(image:.*\/consul-k8s-control-plane:)[^\"]*/image: $4${full_version}/g" "${cfile}" + if ! test -z "$3"; then + sed_i ${SED_EXT} -e "s/(image:.*\/consul:)[^\"]*/image: $6:${full_consul_version}/g" "${cfile}" + sed_i ${SED_EXT} -e "s/(image:.*\/consul:)[^\"]*/image: $6:${full_consul_version}/g" "${vfile}" + else + sed_i ${SED_EXT} -e "s/(image:.*\/consul-enterprise:)[^\"]*/image: $6:${full_consul_version}/g" "${cfile}" + sed_i ${SED_EXT} -e "s/(image:.*\/consul-enterprise:)[^\"]*/image: $6:${full_consul_version}/g" "${vfile}" + fi + + if test -z "$3"; then + sed_i ${SED_EXT} -e "s/(artifacthub.io\/prerelease:[[:space:]]*)[^\"]*/\1false/g" "${cfile}" + else + sed_i ${SED_EXT} -e "s/(artifacthub.io\/prerelease:[[:space:]]*)[^\"]*/\1true/g" "${cfile}" + fi + return $? } function set_version { - # Arguments: - # $1 - Path to top level Consul source - # $2 - The version of the release - # $3 - The release date - # $4 - The pre-release version - # $5 - The helm docker image base path - # - # - # Returns: - # 0 - success - # * - error - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. prepare_release must be called with the path to a git repo as the first argument" - return 1 - fi - - if test -z "$2" - then - err "ERROR: The version specified was empty" - return 1 - fi - - local sdir="$1" - local vers="$2" - - status_stage "==> Updating control-plane version/version.go with version info: ${vers} "$4"" - if ! update_version "${sdir}/control-plane/version/version.go" "${vers}" "$4" - then - return 1 - fi - - status_stage "==> Updating cli version/version.go with version info: ${vers} "$4"" - if ! update_version "${sdir}/cli/version/version.go" "${vers}" "$4" - then - return 1 - fi - - status_stage "==> Updating Helm chart versions with version info: ${vers} "$4"" - if ! update_version_helm "${sdir}/charts/consul" "${vers}" "$4" "$5" - then - return 1 - fi - - return 0 + # Arguments: + # $1 - Path to top level Consul source + # $2 - The version of the release + # $3 - The release date + # $4 - The pre-release version + # $5 - The consul-k8s helm docker image base path + # $6 - The consul version + # $7 - The consul helm docker image base path + # + # + # Returns: + # 0 - success + # * - error + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. prepare_release must be called with the path to a git repo as the first argument" + return 1 + fi + + if test -z "$2"; then + err "ERROR: The version specified was empty" + return 1 + fi + + local sdir="$1" + local vers="$2" + local consul_vers="$6" + + status_stage "==> Updating control-plane version/version.go with version info: ${vers} "$4"" + if ! update_version "${sdir}/control-plane/version/version.go" "${vers}" "$4"; then + return 1 + fi + + status_stage "==> Updating cli version/version.go with version info: ${vers} "$4"" + if ! update_version "${sdir}/cli/version/version.go" "${vers}" "$4"; then + return 1 + fi + + status_stage "==> Updating Helm chart version, consul-k8s: ${vers} "$4" consul: ${consul_vers} "$4"" + if ! update_version_helm "${sdir}/charts/consul" "${vers}" "$4" "$5" "${consul_vers}" "$7"; then + return 1 + fi + + return 0 } function set_changelog { - # Arguments: - # $1 - Path to top level Consul source - # $2 - Version - # $3 - Release Date - # $4 - The last git release tag - # - # - # Returns: - # 0 - success - # * - error - - # Check if changelog-build is installed - if ! command -v changelog-build &> /dev/null; then - echo "Error: changelog-build is not installed. Please install it and try again." - exit 1 - fi - - local curdir="$1" - local version="$2" - local rel_date="$(date +"%B %d, %Y")" - if test -n "$3" - then - rel_date="$3" - fi - local last_release_date_git_tag=$4 - - if test -z "${version}" - then - err "ERROR: Must specify a version to put into the changelog" - return 1 - fi - - if [ -z "$LAST_RELEASE_GIT_TAG" ]; then - echo "Error: LAST_RELEASE_GIT_TAG not specified." - exit 1 - fi - -cat < tmp && mv tmp "${curdir}"/CHANGELOG.MD + # Arguments: + # $1 - Path to top level Consul source + # $2 - Version + # $3 - Release Date + # $4 - The last git release tag + # + # + # Returns: + # 0 - success + # * - error + + # Check if changelog-build is installed + if ! command -v changelog-build &>/dev/null; then + echo "Error: changelog-build is not installed. Please install it and try again." + exit 1 + fi + + local curdir="$1" + local version="$2" + local rel_date="$(date +"%B %d, %Y")" + if test -n "$3"; then + rel_date="$3" + fi + local last_release_date_git_tag=$4 + + if test -z "${version}"; then + err "ERROR: Must specify a version to put into the changelog" + return 1 + fi + + if [ -z "$LAST_RELEASE_GIT_TAG" ]; then + echo "Error: LAST_RELEASE_GIT_TAG not specified." + exit 1 + fi + + cat <tmp && mv tmp "${curdir}"/CHANGELOG.MD ## ${version} (${rel_date}) $(changelog-build -last-release ${LAST_RELEASE_GIT_TAG} \ - -entries-dir .changelog/ \ - -changelog-template .changelog/changelog.tmpl \ - -note-template .changelog/note.tmpl \ - -this-release $(git rev-parse HEAD)) + -entries-dir .changelog/ \ + -changelog-template .changelog/changelog.tmpl \ + -note-template .changelog/note.tmpl \ + -this-release $(git rev-parse HEAD)) EOT } function prepare_release { - # Arguments: - # $1 - Path to top level Consul source - # $2 - The version of the release - # $3 - The release date - # $4 - The last release git tag for this branch (eg. v1.1.0) - # $5 - The pre-release version - # - # - # Returns: - # 0 - success - # * - error - - echo "release version: " "$1" "$2" "$3" "$4" - set_version "$1" "$2" "$3" "$5" "hashicorp\/consul-k8s-control-plane:" - set_changelog "$1" "$2" "$3" "$4" + # Arguments: + # $1 - Path to top level Consul source + # $2 - The version of the release + # $3 - The release date + # $4 - The last release git tag for this branch (eg. v1.1.0) + # $5 - The pre-release version + # $6 - The consul version + # + # + # Returns: + # 0 - success + # * - error + + echo "prepare_release: dir:$1 consul-k8s:$2 consul:$5 date:"$3" git tag:$4" + set_version "$1" "$2" "$3" "$6" "hashicorp\/consul-k8s-control-plane:" "$5" "hashicorp\/consul" + set_changelog "$1" "$2" "$3" "$4" } function prepare_dev { - # Arguments: - # $1 - Path to top level Consul source - # $2 - The version of the release - # $3 - The release date - # $4 - The version of the next release - # $5 - The last release git tag for this branch (eg. v1.1.0) - # - # Returns: - # 0 - success - # * - error - - echo "dev version: " $1 $4 $3 "dev" - set_version "$1" "$4" "$3" "dev" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" - - return 0 + # Arguments: + # $1 - Path to top level Consul source + # $2 - The version of the release + # $3 - The release date + # $4 - The last release git tag for this branch (eg. v1.1.0) (Unused) + # $5 - The version of the next release + # $6 - The version of the next consul release + # + # Returns: + # 0 - success + # * - error + + echo "prepare_dev: dir:$1 consul-k8s:$5 consul:$6 date:"$3" mode:dev" + set_version "$1" "$5" "$3" "dev" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" "$6" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-enterprise" + + return 0 } function git_staging_empty { - # Arguments: - # $1 - Path to git repo - # - # Returns: - # 0 - success (nothing staged) - # * - error (staged files) - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. commit_dev_mode must be called with the path to a git repo as the first argument'" - return 1 - fi - - pushd "$1" > /dev/null - - declare -i ret=0 - - for status in $(git status --porcelain=v2 | awk '{print $2}' | cut -b 1) - do - if test "${status}" != "." - then - ret=1 - break - fi - done - - popd > /dev/null - return ${ret} + # Arguments: + # $1 - Path to git repo + # + # Returns: + # 0 - success (nothing staged) + # * - error (staged files) + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. commit_dev_mode must be called with the path to a git repo as the first argument'" + return 1 + fi + + pushd "$1" >/dev/null + + declare -i ret=0 + + for status in $(git status --porcelain=v2 | awk '{print $2}' | cut -b 1); do + if test "${status}" != "."; then + ret=1 + break + fi + done + + popd >/dev/null + return ${ret} } function commit_dev_mode { - # Arguments: - # $1 - Path to top level Consul source - # - # Returns: - # 0 - success - # * - error + # Arguments: + # $1 - Path to top level Consul source + # + # Returns: + # 0 - success + # * - error - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory. commit_dev_mode must be called with the path to a git repo as the first argument'" - return 1 - fi + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory. commit_dev_mode must be called with the path to a git repo as the first argument'" + return 1 + fi - status "Checking for previously staged files" - git_staging_empty "$1" || return 1 + status "Checking for previously staged files" + git_staging_empty "$1" || return 1 - declare -i ret=0 + declare -i ret=0 - pushd "$1" > /dev/null + pushd "$1" >/dev/null - status "Staging CHANGELOG.md and version_*.go files" - git add CHANGELOG.md && git add */version/version*.go - ret=$? + status "Staging CHANGELOG.md and version_*.go files" + git add CHANGELOG.md && git add */version/version*.go + ret=$? - if test ${ret} -eq 0 - then - status "Adding Commit" - git commit -m "Putting source back into Dev Mode" - ret=$? - fi + if test ${ret} -eq 0; then + status "Adding Commit" + git commit -m "Putting source back into Dev Mode" + ret=$? + fi - popd >/dev/null - return ${ret} + popd >/dev/null + return ${ret} } function gpg_detach_sign { - # Arguments: - # $1 - File to sign - # $2 - Alternative GPG key to use for signing - # - # Returns: - # 0 - success - # * - failure - - # determine whether the gpg key to use is being overridden - local gpg_key=${HASHICORP_GPG_KEY} - if test -n "$2" - then - gpg_key=$2 - fi - - gpg --default-key "${gpg_key}" --detach-sig --yes -v "$1" - return $? + # Arguments: + # $1 - File to sign + # $2 - Alternative GPG key to use for signing + # + # Returns: + # 0 - success + # * - failure + + # determine whether the gpg key to use is being overridden + local gpg_key=${HASHICORP_GPG_KEY} + if test -n "$2"; then + gpg_key=$2 + fi + + gpg --default-key "${gpg_key}" --detach-sig --yes -v "$1" + return $? } function shasum_directory { - # Arguments: - # $1 - Path to directory containing the files to shasum - # $2 - File to output sha sums to - # - # Returns: - # 0 - success - # * - failure - - if ! test -d "$1" - then - err "ERROR: '$1' is not a directory and shasum_release requires passing a directory as the first argument" - return 1 - fi - - if test -z "$2" - then - err "ERROR: shasum_release requires a second argument to be the filename to output the shasums to but none was given" - return 1 - fi - - pushd $1 > /dev/null - shasum -a256 * > "$2" - ret=$? - popd >/dev/null - - return $ret -} - - function ui_version { - # Arguments: - # $1 - path to index.html - # - # Returns: - # 0 - success - # * -failure - # - # Notes: echoes the version to stdout upon success - if ! test -f "$1" - then - err "ERROR: No such file: '$1'" - return 1 - fi - - local ui_version=$(sed -n ${SED_EXT} -e 's/.*CONSUL_VERSION%22%3A%22([^%]*)%22%2C%22.*/\1/p' < "$1") || return 1 - echo "$ui_version" - return 0 - } - function ui_logo_type { - # Arguments: - # $1 - path to index.html - # - # Returns: - # 0 - success - # * -failure - # - # Notes: echoes the 'logo type' to stdout upon success - # the 'logo' can be one of 'enterprise' or 'oss' - # and doesn't necessarily correspond to the binary type of consul - # the logo is 'enterprise' if the binary type is anything but 'oss' - if ! test -f "$1" - then - err "ERROR: No such file: '$1'" - return 1 - fi - grep -q "data-enterprise-logo" < "$1" - - if test $? -eq 0 - then - echo "enterprise" - else - echo "oss" - fi - return 0 - } + # Arguments: + # $1 - Path to directory containing the files to shasum + # $2 - File to output sha sums to + # + # Returns: + # 0 - success + # * - failure + + if ! test -d "$1"; then + err "ERROR: '$1' is not a directory and shasum_release requires passing a directory as the first argument" + return 1 + fi + + if test -z "$2"; then + err "ERROR: shasum_release requires a second argument to be the filename to output the shasums to but none was given" + return 1 + fi + + pushd $1 >/dev/null + shasum -a256 * >"$2" + ret=$? + popd >/dev/null + + return $ret +} + +function ui_version { + # Arguments: + # $1 - path to index.html + # + # Returns: + # 0 - success + # * -failure + # + # Notes: echoes the version to stdout upon success + if ! test -f "$1"; then + err "ERROR: No such file: '$1'" + return 1 + fi + + local ui_version=$(sed -n ${SED_EXT} -e 's/.*CONSUL_VERSION%22%3A%22([^%]*)%22%2C%22.*/\1/p' <"$1") || return 1 + echo "$ui_version" + return 0 +} +function ui_logo_type { + # Arguments: + # $1 - path to index.html + # + # Returns: + # 0 - success + # * -failure + # + # Notes: echoes the 'logo type' to stdout upon success + # the 'logo' can be one of 'enterprise' or 'oss' + # and doesn't necessarily correspond to the binary type of consul + # the logo is 'enterprise' if the binary type is anything but 'oss' + if ! test -f "$1"; then + err "ERROR: No such file: '$1'" + return 1 + fi + grep -q "data-enterprise-logo" <"$1" + + if test $? -eq 0; then + echo "enterprise" + else + echo "oss" + fi + return 0 +} diff --git a/control-plane/build-support/scripts/consul-version.sh b/control-plane/build-support/scripts/consul-version.sh index 634b5db07d..e245e2a239 100755 --- a/control-plane/build-support/scripts/consul-version.sh +++ b/control-plane/build-support/scripts/consul-version.sh @@ -4,5 +4,4 @@ FILE=$1 VERSION=$(yq .global.image $FILE) -# echo full string "hashicorp/consul:1.15.1" | remove first and last characters | cut everything before ':' -echo "${VERSION}" | sed 's/^.//;s/.$//' | cut -d ':' -f2- +echo "${VERSION}" From f44d888b4690ace2f01d704427943b93b32911fa Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 26 May 2023 13:09:04 -0400 Subject: [PATCH 170/592] Fix dev mode on main (#2193) --- charts/consul/Chart.yaml | 4 ++-- charts/consul/values.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index d0216aa36e..c55c6be6a2 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -4,7 +4,7 @@ apiVersion: v2 name: consul version: 1.2.0-dev -appVersion: 1.15.1 +appVersion: 1.16-dev kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -16,7 +16,7 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: hashicorp/consul:1.15.1 + image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.16-dev - name: consul-k8s-control-plane image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.0-dev - name: consul-dataplane diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 5bc0dd33bf..85e82f0fd2 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: "hashicorp/consul:1.15.1" + image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.16-dev # 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. From 725e78d54c1e4d89193ccb2d476599d326ab8489 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 29 May 2023 10:33:41 -0400 Subject: [PATCH 171/592] Fix CVEs by updating controller-runtime (#2183) * Bump version of controller runtime * Use SubResourceUpdateOption * Fix test loggr * Fix ProbeHandler * Set runtime to 0.14.6 * Add Changelog * Fix up a few more breaking change issues --- .changelog/2183.txt | 3 + .../api/common/configentry_webhook_test.go | 4 +- .../v1alpha1/exportedservices_webhook_test.go | 4 +- .../api/v1alpha1/mesh_webhook_test.go | 4 +- .../v1alpha1/peeringacceptor_webhook_test.go | 4 +- .../v1alpha1/peeringdialer_webhook_test.go | 4 +- .../v1alpha1/proxydefaults_webhook_test.go | 4 +- .../serviceintentions_webhook_test.go | 8 +- .../consul_client_health_checks_test.go | 4 +- .../endpoints/endpoints_controller_test.go | 20 ++--- .../peering_acceptor_controller_test.go | 14 ++-- .../peering/peering_dialer_controller_test.go | 14 ++-- .../webhook/mesh_webhook_test.go | 36 ++++----- .../webhook/redirect_traffic_test.go | 22 +++--- .../configentry_controller_test.go | 22 +++--- control-plane/go.mod | 34 ++++---- control-plane/go.sum | 79 +++++++++---------- 17 files changed, 139 insertions(+), 141 deletions(-) create mode 100644 .changelog/2183.txt diff --git a/.changelog/2183.txt b/.changelog/2183.txt new file mode 100644 index 0000000000..d54983a8f4 --- /dev/null +++ b/.changelog/2183.txt @@ -0,0 +1,3 @@ +```release-note:security +Fix Prometheus CVEs by bumping controller-runtime. +``` diff --git a/control-plane/api/common/configentry_webhook_test.go b/control-plane/api/common/configentry_webhook_test.go index 7d7139089d..2760ff15ff 100644 --- a/control-plane/api/common/configentry_webhook_test.go +++ b/control-plane/api/common/configentry_webhook_test.go @@ -9,7 +9,7 @@ import ( "errors" "testing" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" "gomodules.xyz/jsonpatch/v2" @@ -115,7 +115,7 @@ func TestValidateConfigEntry(t *testing.T) { }, }, }, - logrtest.NewTestLogger(t), + logrtest.New(t), lister, c.newResource, ConsulMeta{ diff --git a/control-plane/api/v1alpha1/exportedservices_webhook_test.go b/control-plane/api/v1alpha1/exportedservices_webhook_test.go index 19c2b040bb..7cca7ff915 100644 --- a/control-plane/api/v1alpha1/exportedservices_webhook_test.go +++ b/control-plane/api/v1alpha1/exportedservices_webhook_test.go @@ -8,7 +8,7 @@ import ( "encoding/json" "testing" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" @@ -180,7 +180,7 @@ func TestValidateExportedServices(t *testing.T) { validator := &ExportedServicesWebhook{ Client: client, - Logger: logrtest.NewTestLogger(t), + Logger: logrtest.New(t), decoder: decoder, ConsulMeta: c.consulMeta, } diff --git a/control-plane/api/v1alpha1/mesh_webhook_test.go b/control-plane/api/v1alpha1/mesh_webhook_test.go index bd22b02475..2266a6b77e 100644 --- a/control-plane/api/v1alpha1/mesh_webhook_test.go +++ b/control-plane/api/v1alpha1/mesh_webhook_test.go @@ -8,7 +8,7 @@ import ( "encoding/json" "testing" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" @@ -97,7 +97,7 @@ func TestValidateMesh(t *testing.T) { validator := &MeshWebhook{ Client: client, - Logger: logrtest.NewTestLogger(t), + Logger: logrtest.New(t), decoder: decoder, } response := validator.Handle(ctx, admission.Request{ diff --git a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go index d0cd39d2d0..251ab87c8e 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go @@ -8,7 +8,7 @@ import ( "encoding/json" "testing" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -138,7 +138,7 @@ func TestValidatePeeringAcceptor(t *testing.T) { validator := &PeeringAcceptorWebhook{ Client: client, - Logger: logrtest.NewTestLogger(t), + Logger: logrtest.New(t), decoder: decoder, } response := validator.Handle(ctx, admission.Request{ diff --git a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go index a6fcb79f9e..42372451d1 100644 --- a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go @@ -8,7 +8,7 @@ import ( "encoding/json" "testing" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -138,7 +138,7 @@ func TestValidatePeeringDialer(t *testing.T) { validator := &PeeringDialerWebhook{ Client: client, - Logger: logrtest.NewTestLogger(t), + Logger: logrtest.New(t), decoder: decoder, } response := validator.Handle(ctx, admission.Request{ diff --git a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go index 90e59f0bf8..ade806b7e3 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go @@ -8,7 +8,7 @@ import ( "encoding/json" "testing" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" @@ -122,7 +122,7 @@ func TestValidateProxyDefault(t *testing.T) { validator := &ProxyDefaultsWebhook{ Client: client, - Logger: logrtest.NewTestLogger(t), + Logger: logrtest.New(t), decoder: decoder, } response := validator.Handle(ctx, admission.Request{ diff --git a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go index 507d831e68..238ff7f33e 100644 --- a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/stretchr/testify/require" "gomodules.xyz/jsonpatch/v2" @@ -253,7 +253,7 @@ func TestHandle_ServiceIntentions_Create(t *testing.T) { validator := &ServiceIntentionsWebhook{ Client: client, - Logger: logrtest.NewTestLogger(t), + Logger: logrtest.New(t), decoder: decoder, ConsulMeta: common.ConsulMeta{ NamespacesEnabled: true, @@ -442,7 +442,7 @@ func TestHandle_ServiceIntentions_Update(t *testing.T) { validator := &ServiceIntentionsWebhook{ Client: client, - Logger: logrtest.NewTestLogger(t), + Logger: logrtest.New(t), decoder: decoder, ConsulMeta: common.ConsulMeta{ NamespacesEnabled: true, @@ -602,7 +602,7 @@ func TestHandle_ServiceIntentions_Patches(t *testing.T) { validator := &ServiceIntentionsWebhook{ Client: client, - Logger: logrtest.NewTestLogger(t), + Logger: logrtest.New(t), decoder: decoder, ConsulMeta: common.ConsulMeta{ NamespacesEnabled: namespacesEnabled, diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go index 23bd39c1ed..36ad222d68 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go @@ -6,7 +6,7 @@ package endpoints import ( "testing" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-server-connection-manager/discovery" @@ -241,7 +241,7 @@ func TestUpdateHealthCheckOnConsulClient(t *testing.T) { ctrl := Controller{ ConsulClientConfig: testClient.Cfg, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), } err := ctrl.updateHealthCheckOnConsulClient(testClient.Cfg.APIClientConfig, pod, endpoints, c.updateToStatus) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 871081073d..aad7694b2e 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -10,7 +10,7 @@ import ( "testing" mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" @@ -603,7 +603,7 @@ func TestProcessUpstreams(t *testing.T) { for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { ep := &Controller{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), EnableConsulNamespaces: tt.consulNamespacesEnabled, @@ -902,7 +902,7 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { // Create the endpoints controller ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -2057,7 +2057,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -3377,7 +3377,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -3627,7 +3627,7 @@ func TestReconcileUpdateEndpoint_LegacyService(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -4001,7 +4001,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { // Create the endpoints controller ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -4146,7 +4146,7 @@ func TestReconcileIgnoresServiceIgnoreLabel(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -4232,7 +4232,7 @@ func TestReconcile_podSpecifiesExplicitService(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -5711,7 +5711,7 @@ func TestCreateServiceRegistrations_withTransparentProxy(t *testing.T) { Client: fakeClient, EnableTransparentProxy: c.tproxyGlobalEnabled, TProxyOverwriteProbes: c.overwriteProbes, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), } serviceRegistration, proxyServiceRegistration, err := epCtrl.createServiceRegistrations(*pod, *endpoints, api.HealthPassing) diff --git a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go index af4d3b0f0a..5a0d37135a 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/helper/test" @@ -520,7 +520,7 @@ func TestReconcile_CreateUpdatePeeringAcceptor(t *testing.T) { Client: fakeClient, ExposeServersServiceName: "test-expose-servers", ReleaseNamespace: "default", - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -638,7 +638,7 @@ func TestReconcile_DeletePeeringAcceptor(t *testing.T) { // Create the peering acceptor controller. controller := &AcceptorController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -782,7 +782,7 @@ func TestReconcile_VersionAnnotation(t *testing.T) { // Create the peering acceptor controller controller := &AcceptorController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -1086,7 +1086,7 @@ func TestAcceptorUpdateStatus(t *testing.T) { // Create the peering acceptor controller. pac := &AcceptorController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), Scheme: s, } @@ -1198,7 +1198,7 @@ func TestAcceptorUpdateStatusError(t *testing.T) { // Create the peering acceptor controller. controller := &AcceptorController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), Scheme: s, } @@ -1481,7 +1481,7 @@ func TestAcceptor_RequestsForPeeringTokens(t *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.secret, &tt.acceptors).Build() controller := AcceptorController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), } result := controller.requestsForPeeringTokens(tt.secret) diff --git a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go index d1b9cba696..8d999cbf2a 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" @@ -321,7 +321,7 @@ func TestReconcile_CreateUpdatePeeringDialer(t *testing.T) { // Create the peering dialer controller controller := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -531,7 +531,7 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { // Create the peering dialer controller controller := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: consulConfig, ConsulServerConnMgr: watcher, Scheme: s, @@ -755,7 +755,7 @@ func TestReconcileDeletePeeringDialer(t *testing.T) { // Create the peering dialer controller. pdc := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, Scheme: s, @@ -887,7 +887,7 @@ func TestDialerUpdateStatus(t *testing.T) { // Create the peering dialer controller. controller := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), Scheme: s, } @@ -999,7 +999,7 @@ func TestDialerUpdateStatusError(t *testing.T) { // Create the peering dialer controller. controller := &PeeringDialerController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), Scheme: s, } @@ -1282,7 +1282,7 @@ func TestDialer_RequestsForPeeringTokens(t *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.secret, &tt.dialers).Build() controller := PeeringDialerController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), } result := controller.requestsForPeeringTokens(tt.secret) diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index ddc03eaecc..946933f7d7 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -10,7 +10,7 @@ import ( "testing" mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/consul" @@ -56,7 +56,7 @@ func TestHandlerHandle(t *testing.T) { { "kube-system namespace", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -76,7 +76,7 @@ func TestHandlerHandle(t *testing.T) { { "already injected", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -100,7 +100,7 @@ func TestHandlerHandle(t *testing.T) { { "empty pod basic", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -142,7 +142,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with upstreams specified", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -201,7 +201,7 @@ func TestHandlerHandle(t *testing.T) { { "empty pod with injection disabled", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -227,7 +227,7 @@ func TestHandlerHandle(t *testing.T) { { "empty pod with injection truthy", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -282,7 +282,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with empty volume mount annotation", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -336,7 +336,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with volume mount annotation", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -410,7 +410,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with sidecar volume mount annotation", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -470,7 +470,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with sidecar invalid volume mount annotation", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -501,7 +501,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with service annotation", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -556,7 +556,7 @@ func TestHandlerHandle(t *testing.T) { { "pod with existing label", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -606,7 +606,7 @@ func TestHandlerHandle(t *testing.T) { { "tproxy with overwriteProbes is enabled", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableTransparentProxy: true, @@ -696,7 +696,7 @@ func TestHandlerHandle(t *testing.T) { { "multiport pod kube < 1.24 with AuthMethod, serviceaccount has secret ref", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -755,7 +755,7 @@ func TestHandlerHandle(t *testing.T) { { "multiport pod kube 1.24 with AuthMethod, serviceaccount does not have secret ref", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -814,7 +814,7 @@ func TestHandlerHandle(t *testing.T) { { "dns redirection enabled", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableTransparentProxy: true, @@ -888,7 +888,7 @@ func TestHandlerHandle(t *testing.T) { { "dns redirection only enabled if tproxy enabled", MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableTransparentProxy: true, diff --git a/control-plane/connect-inject/webhook/redirect_traffic_test.go b/control-plane/connect-inject/webhook/redirect_traffic_test.go index df6b7a8559..bfa8ad8f2a 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic_test.go +++ b/control-plane/connect-inject/webhook/redirect_traffic_test.go @@ -10,7 +10,7 @@ import ( "testing" mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/sdk/iptables" @@ -48,7 +48,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "basic bare minimum pod", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -78,7 +78,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "proxy health checks enabled", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -111,7 +111,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "metrics enabled", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -145,7 +145,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "metrics enabled with incorrect annotation", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -180,7 +180,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "overwrite probes, transparent proxy annotation set", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -221,7 +221,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude inbound ports", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -254,7 +254,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude outbound ports", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -287,7 +287,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude outbound CIDRs", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -320,7 +320,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude UIDs", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, @@ -352,7 +352,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { { name: "exclude inbound ports, outbound ports, outbound CIDRs, and UIDs", webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSet(), decoder: decoder, diff --git a/control-plane/controllers/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go index 2404b59365..10af0f7716 100644 --- a/control-plane/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -10,7 +10,7 @@ import ( "time" "github.com/go-logr/logr" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/consul-k8s/control-plane/api/common" @@ -453,7 +453,7 @@ func TestConfigEntryControllers_createsConfigEntry(t *testing.T) { req.True(written) } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) namespacedName := types.NamespacedName{ Namespace: kubeNS, Name: c.configEntryResource.KubernetesName(), @@ -953,7 +953,7 @@ func TestConfigEntryControllers_updatesConfigEntry(t *testing.T) { c.updateF(c.configEntryResource) err = fakeClient.Update(ctx, c.configEntryResource) req.NoError(err) - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) resp, err := r.Reconcile(ctx, ctrl.Request{ NamespacedName: namespacedName, }) @@ -1345,7 +1345,7 @@ func TestConfigEntryControllers_deletesConfigEntry(t *testing.T) { Namespace: kubeNS, Name: c.configEntryResourceWithDeletion.KubernetesName(), } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) resp, err := r.Reconcile(context.Background(), ctrl.Request{ NamespacedName: namespacedName, }) @@ -1392,7 +1392,7 @@ func TestConfigEntryControllers_errorUpdatesSyncStatus(t *testing.T) { reconciler := &ServiceDefaultsController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConfigEntryController: &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1459,7 +1459,7 @@ func TestConfigEntryControllers_setsSyncedToTrue(t *testing.T) { consulClient := testClient.APIClient reconciler := &ServiceDefaultsController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConfigEntryController: &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1551,7 +1551,7 @@ func TestConfigEntryControllers_doesNotCreateUnownedConfigEntry(t *testing.T) { // Attempt to create the entry in Kube and run reconcile. reconciler := ServiceDefaultsController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConfigEntryController: &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1615,7 +1615,7 @@ func TestConfigEntryControllers_doesNotDeleteUnownedConfig(t *testing.T) { consulClient := testClient.APIClient reconciler := &ServiceDefaultsController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConfigEntryController: &ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1695,7 +1695,7 @@ func TestConfigEntryControllers_updatesStatusWhenDeleteFails(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) - logger := logrtest.NewTestLogger(t) + logger := logrtest.New(t) svcDefaultsReconciler := ServiceDefaultsController{ Client: fakeClient, @@ -1833,7 +1833,7 @@ func TestConfigEntryController_Migration(t *testing.T) { require.True(t, success, "config entry was not created") // Set up the reconciler. - logger := logrtest.NewTestLogger(t) + logger := logrtest.New(t) svcDefaultsReconciler := ServiceDefaultsController{ Client: fakeClient, Log: logger, @@ -2109,7 +2109,7 @@ func TestConfigEntryControllers_assignServiceVirtualIP(t *testing.T) { testClient.TestServer.WaitForLeader(t) consulClient := testClient.APIClient - ctrl := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) + ctrl := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) namespacedName := types.NamespacedName{ Namespace: kubeNS, Name: c.configEntryResource.KubernetesName(), diff --git a/control-plane/go.mod b/control-plane/go.mod index 9f81da1907..9a65b3dd11 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -29,14 +29,14 @@ require ( github.com/stretchr/testify v1.8.1 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 - golang.org/x/text v0.7.0 + golang.org/x/text v0.9.0 golang.org/x/time v0.3.0 - gomodules.xyz/jsonpatch/v2 v2.2.0 + gomodules.xyz/jsonpatch/v2 v2.3.0 k8s.io/api v0.26.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 - k8s.io/klog/v2 v2.80.1 - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 + k8s.io/klog/v2 v2.90.1 + k8s.io/utils v0.0.0-20230209194617-a36077c30491 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/gateway-api v0.6.2 ) @@ -72,17 +72,17 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-logr/zapr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.2 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/gophercloud/gophercloud v0.1.0 // indirect github.com/hashicorp/consul/proto-public v0.1.0 // indirect @@ -108,7 +108,7 @@ require ( github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/linode/linodego v0.7.1 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect @@ -143,13 +143,13 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/mod v0.9.0 // indirect + golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/tools v0.3.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/tools v0.7.0 // indirect google.golang.org/api v0.30.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect @@ -162,8 +162,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect k8s.io/component-base v0.26.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/control-plane/go.sum b/control-plane/go.sum index b915ac004b..1876d81578 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -134,7 +134,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= @@ -170,14 +169,12 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +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/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= @@ -212,8 +209,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -251,8 +249,9 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -374,16 +373,15 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -434,8 +432,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= @@ -524,7 +520,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= @@ -607,8 +602,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -652,8 +647,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -739,13 +734,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -755,8 +750,8 @@ 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -809,14 +804,14 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= +gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -912,8 +907,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -954,12 +949,12 @@ k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= +k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= @@ -967,8 +962,8 @@ sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92 sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/gateway-api v0.6.2 h1:583XHiX2M2bKEA0SAdkoxL1nY73W1+/M+IAm8LJvbEA= sigs.k8s.io/gateway-api v0.6.2/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +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/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= From 1734d986739822dbaa32d7d203831027ee054ff7 Mon Sep 17 00:00:00 2001 From: Kaustubh Phatak Date: Mon, 29 May 2023 12:51:09 -0700 Subject: [PATCH 172/592] Adding support for idleTimeout in Service Router spec (#2156) * Adding support for idleTimeout in Service Router spec --- charts/consul/templates/crd-servicerouters.yaml | 4 ++++ control-plane/api/v1alpha1/servicerouter_types.go | 4 ++++ control-plane/api/v1alpha1/servicerouter_types_test.go | 6 +++++- control-plane/api/v1alpha1/zz_generated.deepcopy.go | 1 + .../crd/bases/consul.hashicorp.com_servicerouters.yaml | 4 ++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index e7a3239e75..0157f646b4 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -68,6 +68,10 @@ spec: description: Destination controls how to proxy the matching request(s) to a service. properties: + idleTimeout: + description: IdleTimeout is total amount of time permitted + for the request stream to be idle. + type: string namespace: description: Namespace is the Consul namespace to resolve the service from instead of the current namespace. If diff --git a/control-plane/api/v1alpha1/servicerouter_types.go b/control-plane/api/v1alpha1/servicerouter_types.go index d5baf37368..43e7353bf5 100644 --- a/control-plane/api/v1alpha1/servicerouter_types.go +++ b/control-plane/api/v1alpha1/servicerouter_types.go @@ -140,6 +140,9 @@ type ServiceRouteDestination struct { // This requires that either match.http.pathPrefix or match.http.pathExact // be configured on this route. PrefixRewrite string `json:"prefixRewrite,omitempty"` + // IdleTimeout is total amount of time permitted + // for the request stream to be idle. + IdleTimeout metav1.Duration `json:"idleTimeout,omitempty"` // RequestTimeout is the total amount of time permitted for the entire // downstream request (and retries) to be processed. RequestTimeout metav1.Duration `json:"requestTimeout,omitempty"` @@ -337,6 +340,7 @@ func (in *ServiceRouteDestination) toConsul() *capi.ServiceRouteDestination { Namespace: in.Namespace, Partition: in.Partition, PrefixRewrite: in.PrefixRewrite, + IdleTimeout: in.IdleTimeout.Duration, RequestTimeout: in.RequestTimeout.Duration, NumRetries: in.NumRetries, RetryOnConnectFailure: in.RetryOnConnectFailure, diff --git a/control-plane/api/v1alpha1/servicerouter_types_test.go b/control-plane/api/v1alpha1/servicerouter_types_test.go index d0919871ce..653bdc26c1 100644 --- a/control-plane/api/v1alpha1/servicerouter_types_test.go +++ b/control-plane/api/v1alpha1/servicerouter_types_test.go @@ -82,6 +82,7 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { ServiceSubset: "serviceSubset", Namespace: "namespace", PrefixRewrite: "prefixRewrite", + IdleTimeout: metav1.Duration{Duration: 1 * time.Second}, RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, NumRetries: 1, RetryOnConnectFailure: true, @@ -158,6 +159,7 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { ServiceSubset: "serviceSubset", Namespace: "namespace", PrefixRewrite: "prefixRewrite", + IdleTimeout: 1 * time.Second, RequestTimeout: 1 * time.Second, NumRetries: 1, RetryOnConnectFailure: true, @@ -283,6 +285,7 @@ func TestServiceRouter_ToConsul(t *testing.T) { ServiceSubset: "serviceSubset", Namespace: "namespace", PrefixRewrite: "prefixRewrite", + IdleTimeout: metav1.Duration{Duration: 1 * time.Second}, RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, NumRetries: 1, RetryOnConnectFailure: true, @@ -359,6 +362,7 @@ func TestServiceRouter_ToConsul(t *testing.T) { ServiceSubset: "serviceSubset", Namespace: "namespace", PrefixRewrite: "prefixRewrite", + IdleTimeout: 1 * time.Second, RequestTimeout: 1 * time.Second, NumRetries: 1, RetryOnConnectFailure: true, @@ -717,7 +721,7 @@ func TestServiceRouter_Validate(t *testing.T) { }, namespacesEnabled: false, expectedErrMsgs: []string{ - `servicerouter.consul.hashicorp.com "foo" is invalid: spec.routes[0]: Invalid value: "{\"match\":{\"http\":{}},\"destination\":{\"prefixRewrite\":\"prefixRewrite\",\"requestTimeout\":\"0s\"}}": destination.prefixRewrite requires that either match.http.pathPrefix or match.http.pathExact be configured on this route`, + `servicerouter.consul.hashicorp.com "foo" is invalid: spec.routes[0]: Invalid value: "{\"match\":{\"http\":{}},\"destination\":{\"prefixRewrite\":\"prefixRewrite\",\"idleTimeout\":\"0s\",\"requestTimeout\":\"0s\"}}": destination.prefixRewrite requires that either match.http.pathPrefix or match.http.pathExact be configured on this route`, }, }, "namespaces disabled: single destination namespace specified": { diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index a2e83491ab..f1f5ffb150 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -2170,6 +2170,7 @@ func (in *ServiceRoute) DeepCopy() *ServiceRoute { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceRouteDestination) DeepCopyInto(out *ServiceRouteDestination) { *out = *in + out.IdleTimeout = in.IdleTimeout out.RequestTimeout = in.RequestTimeout if in.RetryOnStatusCodes != nil { in, out := &in.RetryOnStatusCodes, &out.RetryOnStatusCodes diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 25055b0951..5919e23005 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -64,6 +64,10 @@ spec: description: Destination controls how to proxy the matching request(s) to a service. properties: + idleTimeout: + description: IdleTimeout is total amount of time permitted + for the request stream to be idle. + type: string namespace: description: Namespace is the Consul namespace to resolve the service from instead of the current namespace. If From 370976fce900509712ef3fca865ce3650e969f0e Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 30 May 2023 10:02:18 -0400 Subject: [PATCH 173/592] Changelog: add support for idleTimeout in Service Router config (#2200) * add changelog --- .changelog/2156.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/2156.txt diff --git a/.changelog/2156.txt b/.changelog/2156.txt new file mode 100644 index 0000000000..cb812b451a --- /dev/null +++ b/.changelog/2156.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: add support for idleTimeout in the Service Router config +``` From fa3e146b3518e509fd731abcbc428508fa53beaa Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Tue, 30 May 2023 14:06:40 -0400 Subject: [PATCH 174/592] build(deps): update controller UBI base to 9.2 (#2204) --- .changelog/2204.txt | 3 +++ .github/workflows/build.yml | 2 +- control-plane/Dockerfile | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .changelog/2204.txt diff --git a/.changelog/2204.txt b/.changelog/2204.txt new file mode 100644 index 0000000000..6962f485a0 --- /dev/null +++ b/.changelog/2204.txt @@ -0,0 +1,3 @@ +```release-note:security +Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.2`. +``` diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43194ea7bf..52f94ec71c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -164,7 +164,7 @@ jobs: if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: - image: registry.access.redhat.com/ubi8/ubi:latest + image: registry.access.redhat.com/ubi9/ubi:latest options: -v ${{ github.workspace }}:/work run: | dnf install -qy openssl diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index f9c4936b29..a870e20e2b 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -120,7 +120,7 @@ CMD /bin/${BIN_NAME} # We don't rebuild the software because we want the exact checksums and # binary signatures to match the software and our builds aren't fully # reproducible currently. -FROM registry.access.redhat.com/ubi9-minimal:9.1.0 as ubi +FROM registry.access.redhat.com/ubi9-minimal:9.2 as ubi ARG PRODUCT_NAME ARG PRODUCT_VERSION From ea41d4dbb8caaa98031a4e1360242c0fe75addd1 Mon Sep 17 00:00:00 2001 From: Nick Ethier Date: Tue, 30 May 2023 16:00:53 -0400 Subject: [PATCH 175/592] inject envoy_telemetry_bind_socket_dir proxy config when telemetry collector is enabled (#2143) * inject envoy_telemetry_bind_socket_dir proxy config when telemetry collector is enabled * use metrics.enableTelemetryCollector value to gate controller logic * add changelog entry and unit test --- .changelog/2143.txt | 4 ++++ .../templates/connect-inject-deployment.yaml | 1 + .../test/unit/connect-inject-deployment.bats | 13 +++++++++++++ .../endpoints/endpoints_controller.go | 19 ++++++++++++++++--- .../subcommand/inject-connect/command.go | 6 ++++++ 5 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 .changelog/2143.txt diff --git a/.changelog/2143.txt b/.changelog/2143.txt new file mode 100644 index 0000000000..8f58328f3d --- /dev/null +++ b/.changelog/2143.txt @@ -0,0 +1,4 @@ + +```release-note:feature +consul-telemetry-collector: Configure envoy proxy config during registration when consul-telemetry-collector is enabled. +``` diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 2b52c1b81c..479e05b25a 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -257,6 +257,7 @@ spec: {{- if and .Values.global.tls.enabled .Values.global.tls.enableAutoEncrypt }} -enable-auto-encrypt \ {{- end }} + -enable-telemetry-collector={{ .Values.global.metrics.enableTelemetryCollector}} \ startupProbe: httpGet: path: /readyz/ready diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index c60ea14f1f..e7d5b3bf48 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -211,6 +211,19 @@ load _helpers [ "${actual}" = "true" ] } +@test "connectInject/Deployment: metrics.enableTelemetryCollector can be configured" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.metrics.enableTelemetryCollector=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-enable-telemetry-collector=true"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} #-------------------------------------------------------------------- # consul and consul-dataplane images diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 13f75f1156..584abd48c6 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -44,9 +44,10 @@ const ( terminatingGateway = "terminating-gateway" ingressGateway = "ingress-gateway" - kubernetesSuccessReasonMsg = "Kubernetes health checks passing" - envoyPrometheusBindAddr = "envoy_prometheus_bind_addr" - defaultNS = "default" + kubernetesSuccessReasonMsg = "Kubernetes health checks passing" + envoyPrometheusBindAddr = "envoy_prometheus_bind_addr" + envoyTelemetryCollectorBindSocketDir = "envoy_telemetry_collector_bind_socket_dir" + defaultNS = "default" // clusterIPTaggedAddressName is the key for the tagged address to store the service's cluster IP and service port // in Consul. Note: This value should not be changed without a corresponding change in Consul. @@ -119,6 +120,10 @@ type Controller struct { // to Consul client agents. EnableAutoEncrypt bool + // EnableTelemetryCollector controls whether the proxy service should be registered + // with config to enable telemetry forwarding. + EnableTelemetryCollector bool + MetricsConfig metrics.Config Log logr.Logger @@ -482,6 +487,10 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints proxyConfig.Config[envoyPrometheusBindAddr] = prometheusScrapeListener } + if r.EnableTelemetryCollector { + proxyConfig.Config[envoyTelemetryCollectorBindSocketDir] = "/consul/connect-inject" + } + if consulServicePort > 0 { proxyConfig.LocalServiceAddress = "127.0.0.1" proxyConfig.LocalServicePort = consulServicePort @@ -761,6 +770,10 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints } } + if r.EnableTelemetryCollector { + service.Proxy.Config[envoyTelemetryCollectorBindSocketDir] = "/consul/service" + } + serviceRegistration := &api.CatalogRegistration{ Node: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), Address: pod.Status.HostIP, diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index c7c04dd481..671a15f7cd 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -118,6 +118,9 @@ type Command struct { flagEnableAutoEncrypt bool + // Consul telemetry collector + flagEnableTelemetryCollector bool + // Consul DNS flags. flagEnableConsulDNS bool flagResourcePrefix string @@ -203,6 +206,8 @@ func (c *Command) init() { "Enables updating the CABundle on the webhook within this controller rather than using the web cert manager.") c.flagSet.BoolVar(&c.flagEnableAutoEncrypt, "enable-auto-encrypt", false, "Indicates whether TLS with auto-encrypt should be used when talking to Consul clients.") + c.flagSet.BoolVar(&c.flagEnableTelemetryCollector, "enable-telemetry-collector", false, + "Indicates whether proxies should be registered with configuration to enable forwarding metrics to consul-telemetry-collector") c.flagSet.StringVar(&c.flagLogLevel, "log-level", zapcore.InfoLevel.String(), fmt.Sprintf("Log verbosity level. Supported values (in order of detail) are "+ "%q, %q, %q, and %q.", zapcore.DebugLevel.String(), zapcore.InfoLevel.String(), zapcore.WarnLevel.String(), zapcore.ErrorLevel.String())) @@ -449,6 +454,7 @@ func (c *Command) Run(args []string) int { ReleaseName: c.flagReleaseName, ReleaseNamespace: c.flagReleaseNamespace, EnableAutoEncrypt: c.flagEnableAutoEncrypt, + EnableTelemetryCollector: c.flagEnableTelemetryCollector, Context: ctx, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", endpoints.Controller{}) From cb67e6ac296510377b6a870ad8737497cc7b56e8 Mon Sep 17 00:00:00 2001 From: Nick Ethier Date: Tue, 30 May 2023 20:47:23 -0400 Subject: [PATCH 176/592] update cloud preset to enable telemetry collector (#2205) --- .changelog/2205.txt | 3 +++ charts/consul/values.yaml | 2 +- cli/preset/cloud_preset.go | 13 +++++++++++++ cli/preset/cloud_preset_test.go | 22 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .changelog/2205.txt diff --git a/.changelog/2205.txt b/.changelog/2205.txt new file mode 100644 index 0000000000..6a66970cfc --- /dev/null +++ b/.changelog/2205.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: update cloud preset to enable telemetry collector +``` \ No newline at end of file diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 85e82f0fd2..cf6a8e5ab4 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -552,7 +552,7 @@ global: # Configures the Helm chart’s components to forward envoy metrics for the Consul service mesh to the # consul-telemetry-collector. This includes gateway metrics and sidecar metrics. # @type: boolean - enableTelemetryCollector: true + enableTelemetryCollector: false # The name (and tag) of the consul-dataplane Docker image used for the # connect-injected sidecar proxies and mesh, terminating, and ingress gateways. diff --git a/cli/preset/cloud_preset.go b/cli/preset/cloud_preset.go index 5e5c6eccc2..732bad1b14 100644 --- a/cli/preset/cloud_preset.go +++ b/cli/preset/cloud_preset.go @@ -201,6 +201,8 @@ global: bootstrapToken: secretName: %s secretKey: %s + metrics: + enableTelemetryCollector: true cloud: enabled: true resourceId: @@ -215,6 +217,15 @@ global: %s %s %s +telemetryCollector: + enabled: true + cloud: + clientId: + secretName: %s + secretKey: %s + clientSecret: + secretName: %s + secretKey: %s server: replicas: %d affinity: null @@ -231,6 +242,8 @@ controller: secretNameHCPClientID, secretKeyHCPClientID, secretNameHCPClientSecret, secretKeyHCPClientSecret, apiHostCfg, authURLCfg, scadaAddressCfg, + secretNameHCPClientID, secretKeyHCPClientID, + secretNameHCPClientSecret, secretKeyHCPClientSecret, cfg.BootstrapResponse.Cluster.BootstrapExpect, secretNameServerCert) valuesMap := config.ConvertToMap(values) return valuesMap diff --git a/cli/preset/cloud_preset_test.go b/cli/preset/cloud_preset_test.go index 5ade3146ca..770b47ba5c 100644 --- a/cli/preset/cloud_preset_test.go +++ b/cli/preset/cloud_preset_test.go @@ -483,6 +483,8 @@ global: gossipEncryption: secretKey: key secretName: consul-gossip-key + metrics: + enableTelemetryCollector: true tls: caCert: secretKey: tls.crt @@ -494,6 +496,15 @@ server: replicas: 3 serverCert: secretName: consul-server-cert +telemetryCollector: + cloud: + clientId: + secretKey: client-id + secretName: consul-hcp-client-id + clientSecret: + secretKey: client-secret + secretName: consul-hcp-client-secret + enabled: true ` const expectedWithoutOptional = `connectInject: @@ -521,6 +532,8 @@ global: gossipEncryption: secretKey: key secretName: consul-gossip-key + metrics: + enableTelemetryCollector: true tls: caCert: secretKey: tls.crt @@ -532,6 +545,15 @@ server: replicas: 3 serverCert: secretName: consul-server-cert +telemetryCollector: + cloud: + clientId: + secretKey: client-id + secretName: consul-hcp-client-id + clientSecret: + secretKey: client-secret + secretName: consul-hcp-client-secret + enabled: true ` cloudPreset := &CloudPreset{} From f132cdd36a5db6c5ddb51b513397f01e79ecb38f Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Tue, 30 May 2023 18:49:57 -0700 Subject: [PATCH 177/592] Consul Telemetry acceptance test (#2195) --- .changelog/2195.txt | 3 + acceptance/framework/config/config.go | 13 +- acceptance/framework/flags/flags.go | 9 + acceptance/tests/cloud/basic_test.go | 233 ++++++++++++++++++ acceptance/tests/cloud/main_test.go | 18 ++ .../bases/cloud/hcp-mock/deployment.yaml | 29 +++ .../bases/cloud/hcp-mock/kustomization.yaml | 10 + .../bases/cloud/hcp-mock/service.yaml | 17 ++ .../bases/cloud/hcp-mock/serviceaccount.yaml | 7 + .../bases/static-server/deployment.yaml | 4 +- .../consul/templates/server-statefulset.yaml | 22 ++ .../telemetry-collector-deployment.yaml | 33 ++- .../telemetry-collector-service.yaml | 2 +- .../telemetry-collector-serviceaccount.yaml | 2 +- .../consul/test/unit/server-statefulset.bats | 58 +++++ .../unit/telemetry-collector-deployment.bats | 97 ++++++++ .../unit/telemetry-collector-service.bats | 14 ++ .../telemetry-collector-serviceaccount.bats | 15 ++ charts/consul/values.yaml | 24 +- 19 files changed, 597 insertions(+), 13 deletions(-) create mode 100644 .changelog/2195.txt create mode 100644 acceptance/tests/cloud/basic_test.go create mode 100644 acceptance/tests/cloud/main_test.go create mode 100644 acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml create mode 100644 acceptance/tests/fixtures/bases/cloud/hcp-mock/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/cloud/hcp-mock/service.yaml create mode 100644 acceptance/tests/fixtures/bases/cloud/hcp-mock/serviceaccount.yaml diff --git a/.changelog/2195.txt b/.changelog/2195.txt new file mode 100644 index 0000000000..1b450eb40d --- /dev/null +++ b/.changelog/2195.txt @@ -0,0 +1,3 @@ +```release-note:imrpovement +consul-telemetry-collector: add acceptance tests for consul telemetry collector component. +``` \ No newline at end of file diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index c771e49653..8a5ba7893e 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -46,11 +46,14 @@ type TestConfig struct { DisablePeering bool - HelmChartVersion string - ConsulImage string - ConsulK8SImage string - ConsulVersion *version.Version - EnvoyImage string + HelmChartVersion string + ConsulImage string + ConsulK8SImage string + ConsulVersion *version.Version + EnvoyImage string + ConsulCollectorImage string + + HCPResourceID string VaultHelmChartVersion string VaultServerVersion string diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index ea58fda058..3b542c5294 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -39,9 +39,12 @@ type TestFlags struct { flagConsulK8sImage string flagConsulVersion string flagEnvoyImage string + flagConsulCollectorImage string flagVaultHelmChartVersion string flagVaultServerVersion string + flagHCPResourceID string + flagNoCleanupOnFailure bool flagDebugDirectory string @@ -74,9 +77,12 @@ func (t *TestFlags) init() { flag.StringVar(&t.flagConsulVersion, "consul-version", "", "The consul version used for all tests.") flag.StringVar(&t.flagHelmChartVersion, "helm-chart-version", config.HelmChartPath, "The helm chart used for all tests.") flag.StringVar(&t.flagEnvoyImage, "envoy-image", "", "The Envoy image to use for all tests.") + flag.StringVar(&t.flagConsulCollectorImage, "consul-collector-image", "", "The consul collector image to use for all tests.") flag.StringVar(&t.flagVaultServerVersion, "vault-server-version", "", "The vault serverversion used for all tests.") flag.StringVar(&t.flagVaultHelmChartVersion, "vault-helm-chart-version", "", "The Vault helm chart used for all tests.") + flag.StringVar(&t.flagHCPResourceID, "hcp-resource-id", "", "The hcp resource id to use for all tests.") + flag.BoolVar(&t.flagEnableMultiCluster, "enable-multi-cluster", false, "If true, the tests that require multiple Kubernetes clusters will be run. "+ "At least one of -secondary-kubeconfig or -secondary-kubecontext is required when this flag is used.") @@ -176,9 +182,12 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { ConsulK8SImage: t.flagConsulK8sImage, ConsulVersion: consulVersion, EnvoyImage: t.flagEnvoyImage, + ConsulCollectorImage: t.flagConsulCollectorImage, VaultHelmChartVersion: t.flagVaultHelmChartVersion, VaultServerVersion: t.flagVaultServerVersion, + HCPResourceID: t.flagHCPResourceID, + NoCleanupOnFailure: t.flagNoCleanupOnFailure, DebugDirectory: tempDir, UseAKS: t.flagUseAKS, diff --git a/acceptance/tests/cloud/basic_test.go b/acceptance/tests/cloud/basic_test.go new file mode 100644 index 0000000000..8278309ff3 --- /dev/null +++ b/acceptance/tests/cloud/basic_test.go @@ -0,0 +1,233 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloud + +import ( + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/serf/testutil/retry" + "github.com/stretchr/testify/require" +) + +type TokenResponse struct { + Token string `json:"token"` +} + +var ( + resourceSecretName = "resource-sec-name" + resourceSecretKey = "resource-sec-key" + resourceSecretKeyValue = "organization/11eb1a35-aac0-f7c7-8fe1-0242ac110008/project/11eb1a35-ab64-d576-8fe1-0242ac110008/hashicorp.consul.global-network-manager.cluster/TEST" + + clientIDSecretName = "clientid-sec-name" + clientIDSecretKey = "clientid-sec-key" + clientIDSecretKeyValue = "clientid" + + clientSecretName = "client-sec-name" + clientSecretKey = "client-sec-key" + clientSecretKeyValue = "client-secret" + + apiHostSecretName = "apihost-sec-name" + apiHostSecretKey = "apihost-sec-key" + apiHostSecretKeyValue = "fake-server:443" + + authUrlSecretName = "authurl-sec-name" + authUrlSecretKey = "authurl-sec-key" + authUrlSecretKeyValue = "https://fake-server:443" + + scadaAddressSecretName = "scadaaddress-sec-name" + scadaAddressSecretKey = "scadaaddress-sec-key" + scadaAddressSecretKeyValue = "fake-server:443" +) + +// The fake-server has a requestToken endpoint to retrieve the token. +func requestToken(endpoint string) (string, error) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + client := &http.Client{Transport: tr} + url := fmt.Sprintf("https://%s/token", endpoint) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Println("Error creating request:", err) + return "", errors.New("error creating request") + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error sending request:", err) + return "", errors.New("error making request") + } + defer resp.Body.Close() + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response:", err) + return "", errors.New("error reading body") + } + + var tokenResponse TokenResponse + err = json.Unmarshal(body, &tokenResponse) + if err != nil { + fmt.Println("Error parsing response:", err) + return "", errors.New("error parsing body") + } + + return tokenResponse.Token, nil + +} + +func TestBasicCloud(t *testing.T) { + ctx := suite.Environment().DefaultContext(t) + + kubectlOptions := ctx.KubectlOptions(t) + ns := kubectlOptions.Namespace + k8sClient := environment.KubernetesClientFromOptions(t, kubectlOptions) + + cfg := suite.Config() + + if cfg.HCPResourceID != "" { + resourceSecretKeyValue = cfg.HCPResourceID + } + consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) + + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/cloud/hcp-mock") + podName, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "get", "pod", "-l", "app=fake-server", "-o", `jsonpath="{.items[0].metadata.name}"`) + podName = strings.ReplaceAll(podName, "\"", "") + if err != nil { + logger.Log(t, "error finding pod name") + return + } + logger.Log(t, "fake-server pod name:"+podName) + localPort := terratestk8s.GetAvailablePort(t) + tunnel := terratestk8s.NewTunnelWithLogger( + ctx.KubectlOptions(t), + terratestk8s.ResourceTypePod, + podName, + localPort, + 443, + logger.TestLogger{}) + + // Retry creating the port forward since it can fail occasionally. + retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { + // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry + // because we're using ForwardPortE (not ForwardPort) so the `t` won't + // get used to fail the test, just for logging. + require.NoError(r, tunnel.ForwardPortE(t)) + }) + + logger.Log(t, "fake-server addr:"+tunnel.Endpoint()) + consulToken, err := requestToken(tunnel.Endpoint()) + if err != nil { + logger.Log(t, "error finding consul token") + return + } + tunnel.Close() + logger.Log(t, "consul test token :"+consulToken) + + releaseName := helpers.RandomName() + + helmValues := map[string]string{ + "global.cloud.enabled": "true", + "global.cloud.resourceId.secretName": resourceSecretName, + "global.cloud.resourceId.secretKey": resourceSecretKey, + + "global.cloud.clientId.secretName": clientIDSecretName, + "global.cloud.clientId.secretKey": clientIDSecretKey, + + "global.cloud.clientSecret.secretName": clientSecretName, + "global.cloud.clientSecret.secretKey": clientSecretKey, + + "global.cloud.apiHost.secretName": apiHostSecretName, + "global.cloud.apiHost.secretKey": apiHostSecretKey, + + "global.cloud.authUrl.secretName": authUrlSecretName, + "global.cloud.authUrl.secretKey": authUrlSecretKey, + + "global.cloud.scadaAddress.secretName": scadaAddressSecretName, + "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, + "connectInject.default": "true", + + // TODO: Follow up with this bug + "global.acls.manageSystemACLs": "false", + "global.gossipEncryption.autoGenerate": "false", + "global.tls.enabled": "true", + "global.tls.enableAutoEncrypt": "true", + // TODO: Take this out + + "telemetryCollector.enabled": "true", + "telemetryCollector.image": cfg.ConsulCollectorImage, + "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, + "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, + + "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, + "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, + // Either we set the global.trustedCAs (make sure it's idented exactly) or we + // set TLS to insecure + + "telemetryCollector.extraEnvironmentVars.HCP_API_TLS": "insecure", + "telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", + "telemetryCollector.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", + "telemetryCollector.extraEnvironmentVars.OTLP_EXPORTER_TLS": "insecure", + + "server.extraEnvironmentVars.HCP_API_TLS": "insecure", + "server.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", + "server.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", + + // This is pregenerated CA used for testing. It can be replaced at any time and isn't + // meant for anything other than testing + // "global.trustedCAs[0]": `-----BEGIN CERTIFICATE----- + // MIICrjCCAZYCCQD5LxMcnMY8rDANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5m + // YWtlLXNlcnZlci1jYTAeFw0yMzA1MTkxMjIwMzhaFw0zMzA1MTYxMjIwMzhaMBkx + // FzAVBgNVBAMMDmZha2Utc2VydmVyLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A + // MIIBCgKCAQEAwhbiII7sMultedFzQVhVZz5Ti+9lWrpZb8y0ZR6NaNvoxDPX151t + // Adh5NegSeH/+351iDBGZHhmKECtBuk8FJgk88O7y8A7Yg+/lyeZd0SJTEeiYUe7d + // sSaBTYSmixyn6s15Y5MVp9gM7t2YXrocRkFxDtdhLMWf0zwzJEwDouFMMiFZw5II + // yDbI6UfwKyB8C8ln10+TcczbheaOMQ1jGn35YWAG/LEdutU6DO2Y/GZYQ41nyLF1 + // klqh34USQPVQSQW7R7GiDxyhh1fGaDF6RAzH4RerzQSNvvTHmBXIGurB/Hnu1n3p + // CwWeatWMU5POy1es73S/EPM0NpWD5RabSwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB + // AQBayoTltSW55PvKVp9cmqGOBMlkIMKPd6Ny4bCb/3UF+3bzQmIblh3O3kEt7WoY + // fA9vp+6cSRGVqgBfR2bi40RrerLNA79yywIZjfBMteNuRoul5VeD+mLyFCo4197r + // Atl2TEx2kl2V8rjCsEBcTqKqetVOMLYEZ2tbCeUt1A/K7OzaJfHgelEYcsVt68Q9 + // /BLoo2UXfOpRrcsx7u7s5HPVbG3bx+1MvGJZ2C3i0B6agnkGDzEpoM4KZGxEefB9 + // DOHIJfie9d9BQD52nZh3SGHz0b3vfJ430XrQmaNZ26fuIEyIYrpvyAhBXckj2iTD + // 1TXpqr/1D7EUbddktyhXTK9e + // -----END CERTIFICATE-----`, + } + if cfg.ConsulImage != "" { + helmValues["global.image"] = cfg.ConsulImage + } + if cfg.ConsulCollectorImage != "" { + helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage + } + + consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), suite.Config(), releaseName) + consulCluster.Create(t) + + logger.Log(t, "creating static-server deployment") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + // time.Sleep(1 * time.Hour) + // TODO: add in test assertions here +} diff --git a/acceptance/tests/cloud/main_test.go b/acceptance/tests/cloud/main_test.go new file mode 100644 index 0000000000..85d1867933 --- /dev/null +++ b/acceptance/tests/cloud/main_test.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloud + +import ( + "os" + "testing" + + testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" +) + +var suite testsuite.Suite + +func TestMain(m *testing.M) { + suite = testsuite.NewSuite(m) + os.Exit(suite.Run()) +} diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml new file mode 100644 index 0000000000..7278557cdb --- /dev/null +++ b/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml @@ -0,0 +1,29 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fake-server +spec: + replicas: 1 + selector: + matchLabels: + app: fake-server + template: + metadata: + name: fake-server + labels: + app: fake-server + spec: + containers: + - name: fake-server + # TODO: move this to a hashicorp mirror + image: docker.io/chaapppie/fakeserver:latest + ports: + - containerPort: 443 + name: https + - containerPort: 8080 + name: http + serviceAccountName: fake-server + terminationGracePeriodSeconds: 0 # so deletion is quick diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/kustomization.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/kustomization.yaml new file mode 100644 index 0000000000..dc9c951ab2 --- /dev/null +++ b/acceptance/tests/fixtures/bases/cloud/hcp-mock/kustomization.yaml @@ -0,0 +1,10 @@ + + +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - deployment.yaml + - service.yaml + - serviceaccount.yaml + diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/service.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/service.yaml new file mode 100644 index 0000000000..0cc6f1b9ce --- /dev/null +++ b/acceptance/tests/fixtures/bases/cloud/hcp-mock/service.yaml @@ -0,0 +1,17 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: Service +metadata: + name: fake-server +spec: + selector: + app: fake-server + ports: + - name: https + port: 443 + targetPort: 443 + - name: http + port: 8080 + targetPort: 8080 diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/serviceaccount.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/serviceaccount.yaml new file mode 100644 index 0000000000..f52d9640cd --- /dev/null +++ b/acceptance/tests/fixtures/bases/cloud/hcp-mock/serviceaccount.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: fake-server diff --git a/acceptance/tests/fixtures/bases/static-server/deployment.yaml b/acceptance/tests/fixtures/bases/static-server/deployment.yaml index f61371a880..9f5776c9ca 100644 --- a/acceptance/tests/fixtures/bases/static-server/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-server/deployment.yaml @@ -18,7 +18,9 @@ spec: spec: containers: - name: static-server - image: docker.mirror.hashicorp.services/hashicorp/http-echo:latest + # Using alpine vs latest as there is a build issue with M1s. Also other tests in multiport-app reference + # alpine so standardizing this. + image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine args: - -text="hello world" - -listen=:8080 diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index aa9198f127..0bde9b881a 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -198,6 +198,11 @@ spec: medium: "Memory" {{- end }} {{- end }} + {{- if .Values.global.trustedCAs }} + - name: trusted-cas + emptyDir: + medium: "Memory" + {{- end }} {{- range .Values.server.extraVolumes }} - name: userconfig-{{ .name }} {{ .type }}: @@ -354,11 +359,23 @@ spec: key: {{ .Values.global.cloud.scadaAddress.secretKey }} {{- end}} {{- end }} + {{- if .Values.global.trustedCAs }} + - name: SSL_CERT_DIR + value: "/etc/ssl/certs:/trusted-cas" + {{- end }} {{- include "consul.extraEnvironmentVars" .Values.server | nindent 12 }} command: - "/bin/sh" - "-ec" - | + {{- if .Values.global.trustedCAs }} + {{- range $i, $cert := .Values.global.trustedCAs }} + cat < /trusted-cas/custom-ca-{{$i}}.pem + {{- $cert | nindent 14 }} + EOF + {{- end }} + {{- end }} + {{- if and .Values.global.secretsBackend.vault.enabled .Values.global.gossipEncryption.secretName }} GOSSIP_KEY=`cat /vault/secrets/gossip.txt` {{- end }} @@ -426,6 +443,11 @@ spec: mountPath: /consul/vault-ca/ readOnly: true {{- end }} + {{- if .Values.global.trustedCAs }} + - name: trusted-cas + mountPath: /trusted-cas + readOnly: false + {{- end }} ports: {{- if (or (not .Values.global.tls.enabled) (not .Values.global.tls.httpsOnly)) }} - name: http diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index a073128f95..cb0cb67852 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -36,6 +36,7 @@ spec: "consul.hashicorp.com/connect-inject-status": "injected" # We aren't using tproxy and we don't have an original pod. This would be simpler if we made a path similar # to gateways + "consul.hashicorp.com/connect-service-port": "metricsserver" "consul.hashicorp.com/transparent-proxy": "false" "consul.hashicorp.com/transparent-proxy-overwrite-probes": "false" "consul.hashicorp.com/connect-k8s-version": {{ $.Chart.Version }} @@ -69,7 +70,9 @@ spec: {{- toYaml .Values.global.extraLabels | nindent 8 }} {{- end }} spec: - serviceAccountName: {{ template "consul.fullname" . }}-telemetry-collector + # This needs to explicitly be consul-telemetry-collector because we look this up from each service consul-dataplane + # to forward metrics to it. + serviceAccountName: consul-telemetry-collector initContainers: # We're manually managing this init container instead of using the connect injector so that we don't run into # any race conditions on the connect-injector deployment or upgrade @@ -202,21 +205,38 @@ spec: name: {{ .Values.global.cloud.scadaAddress.secretName }} key: {{ .Values.global.cloud.scadaAddress.secretKey }} {{- end}} - + {{- if .Values.global.trustedCAs }} + - name: SSL_CERT_DIR + value: "/etc/ssl/certs:/trusted-cas" + {{- end }} + {{- include "consul.extraEnvironmentVars" .Values.telemetryCollector | nindent 12 }} command: - "/bin/sh" - "-ec" - | + {{- if .Values.global.trustedCAs }} + {{- range $i, $cert := .Values.global.trustedCAs }} + cat < /trusted-cas/custom-ca-{{$i}}.pem + {{- $cert | nindent 10 }} + EOF + {{- end }} + {{- end }} + consul-telemetry-collector agent {{- if .Values.telemetryCollector.customExporterConfig }} args: - -config-file-path /consul/config/config.json {{ end }} - {{- if .Values.telemetryCollector.customExporterConfig }} volumeMounts: + {{- if .Values.telemetryCollector.customExporterConfig }} - name: config mountPath: /consul/config - {{- end }} + {{- end }} + {{- if .Values.global.trustedCAs }} + - name: trusted-cas + mountPath: /trusted-cas + readOnly: false + {{- end }} resources: {{- if .Values.telemetryCollector.resources }} {{- toYaml .Values.telemetryCollector.resources | nindent 12 }} @@ -347,6 +367,11 @@ spec: - emptyDir: medium: Memory name: consul-connect-inject-data + {{- if .Values.global.trustedCAs }} + - name: trusted-cas + emptyDir: + medium: "Memory" + {{- end }} {{- if .Values.global.tls.enabled }} {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} - name: consul-ca-cert diff --git a/charts/consul/templates/telemetry-collector-service.yaml b/charts/consul/templates/telemetry-collector-service.yaml index f7fc3f09d9..266c80b4bf 100644 --- a/charts/consul/templates/telemetry-collector-service.yaml +++ b/charts/consul/templates/telemetry-collector-service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector + name: consul-telemetry-collector namespace: {{ .Release.Namespace }} labels: app: {{ template "consul.name" . }} diff --git a/charts/consul/templates/telemetry-collector-serviceaccount.yaml b/charts/consul/templates/telemetry-collector-serviceaccount.yaml index f2b6e88171..fca58eede9 100644 --- a/charts/consul/templates/telemetry-collector-serviceaccount.yaml +++ b/charts/consul/templates/telemetry-collector-serviceaccount.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector + name: consul-telemetry-collector namespace: {{ .Release.Namespace }} labels: app: {{ template "consul.name" . }} diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index f0a8b0112b..29621187ab 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -2586,6 +2586,64 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ } +#-------------------------------------------------------------------- +# global.trustedCAs + +@test "server/StatefulSet: trustedCAs: if trustedCAs is set command is modified correctly" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "server/StatefulSet: trustedCAs: if tustedCAs multiple are set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + --set 'global.trustedCAs[1]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0]' | tee /dev/stderr) + + + local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-1.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +# global.trustedCAs +@test "server/StatefulSet: trustedCAs: if trustedCAs is set /trusted-cas volumeMount is added" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) + local actual=$(echo $object | jq -r '.volumes[] | select(.name == "trusted-cas") | .name' | tee /dev/stderr) + [ "${actual}" = "trusted-cas" ] +} + +@test "server/StatefulSet: trustedCAs: if trustedCAs is set SSL_CERT_DIR env var is set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | yq -r '.spec.template.spec.containers[0].env[] | select(.name == "SSL_CERT_DIR")' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.name' | tee /dev/stderr) + [ "${actual}" = "SSL_CERT_DIR" ] + local actual=$(echo $object | jq -r '.value' | tee /dev/stderr) + [ "${actual}" = "/etc/ssl/certs:/trusted-cas" ] +} + #-------------------------------------------------------------------- # snapshotAgent license-autoload diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index 8161e90e5d..1e2758e638 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -73,6 +73,20 @@ load _helpers [ "${actual}" = "testing" ] } +#-------------------------------------------------------------------- +# consul.name + +@test "telemetryCollector/Deployment: name is constant regardless of consul name" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'consul.name=foobar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].name' | tee /dev/stderr) + [ "${actual}" = "consul-telemetry-collector" ] +} + #-------------------------------------------------------------------- # global.tls.enabled @@ -929,6 +943,67 @@ load _helpers yq '.spec.template.spec.initContainers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) [ "${actual}" = "" ] } +#-------------------------------------------------------------------- +# trustedCAs + +@test "telemetryCollector/Deployment: trustedCAs: if trustedCAs is set command is modified correctly" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment: trustedCAs: if multiple Trusted cas were set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + --set 'global.trustedCAs[1]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0]' | tee /dev/stderr) + + + local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-1.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment: trustedCAs: if trustedCAs is set /trusted-cas volumeMount is added" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) + local actual=$(echo $object | jq -r '.volumes[] | select(.name == "trusted-cas") | .name' | tee /dev/stderr) + [ "${actual}" = "trusted-cas" ] +} + + +@test "telemetryCollector/Deployment: trustedCAs: if trustedCAs is set SSL_CERT_DIR env var is set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | yq -r '.spec.template.spec.containers[0].env[] | select(.name == "SSL_CERT_DIR")' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.name' | tee /dev/stderr) + [ "${actual}" = "SSL_CERT_DIR" ] + local actual=$(echo $object | jq -r '.value' | tee /dev/stderr) + [ "${actual}" = "/etc/ssl/certs:/trusted-cas" ] +} #-------------------------------------------------------------------- # extraLabels @@ -977,3 +1052,25 @@ load _helpers [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# extraEnvironmentVariables + +@test "telemetryCollector/Deployment: extra environment variables" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS=insecure' \ + --set 'telemetryCollector.extraEnvironmentVars.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_AUTH_TLS")) | .[0].value' | tee /dev/stderr) + [ "${actual}" = "insecure" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "foo")) | .[0].value' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-service.bats b/charts/consul/test/unit/telemetry-collector-service.bats index 0ee6512c05..c5406b8124 100755 --- a/charts/consul/test/unit/telemetry-collector-service.bats +++ b/charts/consul/test/unit/telemetry-collector-service.bats @@ -31,6 +31,20 @@ load _helpers [ "${actual}" = "true" ] } +#-------------------------------------------------------------------- +# consul.name + +@test "telemetryCollector/Service: name is constant regardless of consul name" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-service.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'consul.name=foobar' \ + . | tee /dev/stderr | + yq -r '.metadata.name' | tee /dev/stderr) + [ "${actual}" = "consul-telemetry-collector" ] +} + #-------------------------------------------------------------------- # annotations diff --git a/charts/consul/test/unit/telemetry-collector-serviceaccount.bats b/charts/consul/test/unit/telemetry-collector-serviceaccount.bats index 211238f72f..589632d5b5 100644 --- a/charts/consul/test/unit/telemetry-collector-serviceaccount.bats +++ b/charts/consul/test/unit/telemetry-collector-serviceaccount.bats @@ -67,3 +67,18 @@ load _helpers yq -r '.metadata.annotations.foo' | tee /dev/stderr) [ "${actual}" = "bar" ] } + + +#-------------------------------------------------------------------- +# consul.name + +@test "telemetryCollector/ServiceAccount: name is constant regardless of consul name" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-serviceaccount.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'consul.name=foobar' \ + . | tee /dev/stderr | + yq -r '.metadata.name' | tee /dev/stderr) + [ "${actual}" = "consul-telemetry-collector" ] +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index cf6a8e5ab4..9f004cab30 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -557,7 +557,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: "hashicorp/consul-dataplane:1.1.0" + imageConsulDataplane: "hashicorppreview/consul-dataplane:1.1-dev" # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. @@ -650,6 +650,21 @@ global: # @type: map extraLabels: {} + # Optional PEM-encoded CA certificates that will be added to trusted system CAs. + # + # Example: + # + # ```yaml + # trustedCAs: [ + # | + # -----BEGIN CERTIFICATE----- + # MIIC7jCCApSgAwIBAgIRAIq2zQEVexqxvtxP6J0bXAwwCgYIKoZIzj0EAwIwgbkx + # ... + # ] + # ``` + # @type: array + trustedCAs: [ ] + # Server, when enabled, configures a server cluster to run. This should # be disabled if you plan on connecting to a Consul cluster external to # the Kube cluster. @@ -3275,3 +3290,10 @@ telemetryCollector: # Optional priorityClassName. # @type: string priorityClassName: "" + + # A list of extra environment variables to set within the stateful set. + # These could be used to include proxy settings required for cloud auto-join + # feature, in case kubernetes cluster is behind egress http proxies. Additionally, + # it could be used to configure custom consul parameters. + # @type: map + extraEnvironmentVars: { } From aaee9a71d321d40574f03c0b8aabd9f5acbfe184 Mon Sep 17 00:00:00 2001 From: Eric Haberkorn Date: Wed, 31 May 2023 09:24:22 -0400 Subject: [PATCH 178/592] Fix bug on service intention CRDs causing source partitions and namespaces not to be compared. (#2194) This bug means that swapping partitions and namespaces on sources wouldn't get reflected in Consul. --- .changelog/2194.txt | 3 + .../api/v1alpha1/serviceintentions_types.go | 19 ++++- .../v1alpha1/serviceintentions_types_test.go | 72 +++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 .changelog/2194.txt diff --git a/.changelog/2194.txt b/.changelog/2194.txt new file mode 100644 index 0000000000..997326218b --- /dev/null +++ b/.changelog/2194.txt @@ -0,0 +1,3 @@ +```release-note: +crd: fix bug on service intentions CRD causing some updates to be ignored. +``` diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index fd18ecd3fe..74e02eb617 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -234,18 +234,34 @@ func (in *ServiceIntentions) ConsulGlobalResource() bool { return false } +func normalizeEmptyToDefault(value string) string { + if value == "" { + return "default" + } + return value +} + func (in *ServiceIntentions) MatchesConsul(candidate api.ConfigEntry) bool { configEntry, ok := candidate.(*capi.ServiceIntentionsConfigEntry) if !ok { return false } + specialEquality := cmp.Options{ + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Sources.Namespace" + }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Sources.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. return cmp.Equal( in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceIntentionsConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), - cmpopts.IgnoreFields(capi.SourceIntention{}, "Partition", "Namespace", "LegacyID", "LegacyMeta", "LegacyCreateTime", "LegacyUpdateTime", "Precedence", "Type"), + cmpopts.IgnoreFields(capi.SourceIntention{}, "LegacyID", "LegacyMeta", "LegacyCreateTime", "LegacyUpdateTime", "Precedence", "Type"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), // Consul will sort the sources by precedence when returning the resource @@ -255,6 +271,7 @@ func (in *ServiceIntentions) MatchesConsul(candidate api.ConfigEntry) bool { // piggyback on strings.Compare that returns -1 if a < b. return strings.Compare(sourceIntentionSortKey(a), sourceIntentionSortKey(b)) == -1 }), + specialEquality, ) } diff --git a/control-plane/api/v1alpha1/serviceintentions_types_test.go b/control-plane/api/v1alpha1/serviceintentions_types_test.go index df0100e75a..f445dfb246 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_types_test.go @@ -41,6 +41,78 @@ func TestServiceIntentions_MatchesConsul(t *testing.T) { }, Matches: true, }, + "namespaces and partitions equate `default` and empty strings": { + Ours: ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "svc-name", + Namespace: "ns1", + }, + Sources: []*SourceIntention{ + { + Name: "svc1", + Namespace: "", + Partition: "default", + Action: "allow", + }, + }, + }, + }, + Theirs: &capi.ServiceIntentionsConfigEntry{ + Kind: capi.ServiceIntentions, + Name: "svc-name", + Namespace: "ns1", + Sources: []*capi.SourceIntention{ + { + Name: "svc1", + Namespace: "default", + Partition: "", + Action: "allow", + Precedence: 0, + }, + }, + }, + Matches: true, + }, + "source namespaces and partitions are compared": { + Ours: ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "svc-name", + Namespace: "test", + }, + Sources: []*SourceIntention{ + { + Name: "svc1", + Namespace: "test", + Partition: "test", + Action: "allow", + }, + }, + }, + }, + Theirs: &capi.ServiceIntentionsConfigEntry{ + Kind: capi.ServiceIntentions, + Name: "svc-name", + Namespace: "test", + Sources: []*capi.SourceIntention{ + { + Name: "svc1", + Namespace: "not-test", + Partition: "not-test", + Action: "allow", + Precedence: 0, + }, + }, + }, + Matches: false, + }, "all fields set matches": { Ours: ServiceIntentions{ ObjectMeta: metav1.ObjectMeta{ From 3a66856c057ceca6a22fa1c39f6f99c4037b3be8 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Wed, 31 May 2023 09:15:47 -0500 Subject: [PATCH 179/592] Add CRD for jwt-provider config entry (#2209) * Add CRD for jwt-provider config entry * Pin consul/api to versions containing the jwt-provider config entry * Update Makefile to use v0.10.0 of sigs.k8s.io/controller-tools/cmd/controller-gen --- .changelog/2209.txt | 3 + CONTRIBUTING.md | 3 +- Makefile | 2 +- acceptance/go.mod | 3 +- acceptance/go.sum | 4 + .../config-entries/config_entries_test.go | 16 + .../fixtures/bases/crds-oss/jwtprovider.yaml | 34 + .../templates/connect-inject-clusterrole.yaml | 2 + ...t-inject-mutatingwebhookconfiguration.yaml | 21 + charts/consul/templates/crd-jwtproviders.yaml | 259 +++++++ control-plane/PROJECT | 9 + control-plane/api/common/common.go | 1 + .../api/v1alpha1/jwtprovider_types.go | 663 ++++++++++++++++ .../api/v1alpha1/jwtprovider_types_test.go | 730 ++++++++++++++++++ .../api/v1alpha1/jwtprovider_webhook.go | 61 ++ .../api/v1alpha1/zz_generated.deepcopy.go | 305 ++++++++ .../consul.hashicorp.com_jwtproviders.yaml | 254 ++++++ control-plane/config/rbac/role.yaml | 20 + control-plane/config/webhook/manifests.yaml | 21 + .../configentry_controller_test.go | 126 +++ .../controllers/jwtprovider_controller.go | 43 ++ control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- .../subcommand/inject-connect/command.go | 15 + 24 files changed, 2595 insertions(+), 6 deletions(-) create mode 100644 .changelog/2209.txt create mode 100644 acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml create mode 100644 charts/consul/templates/crd-jwtproviders.yaml create mode 100644 control-plane/api/v1alpha1/jwtprovider_types.go create mode 100644 control-plane/api/v1alpha1/jwtprovider_types_test.go create mode 100644 control-plane/api/v1alpha1/jwtprovider_webhook.go create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml create mode 100644 control-plane/controllers/jwtprovider_controller.go diff --git a/.changelog/2209.txt b/.changelog/2209.txt new file mode 100644 index 0000000000..72a59064e4 --- /dev/null +++ b/.changelog/2209.txt @@ -0,0 +1,3 @@ +```release-note:feature +helm: Add `JWTProvider` CRD for configuring the `jwt-provider` config entry. +``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f427421294..63ae3b2323 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -214,6 +214,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ```go // ServiceRouter is the Schema for the servicerouters API // +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" + // +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" type ServiceRouter struct { ``` @@ -232,7 +233,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ``` 1. Go to the Consul `api` package for the config entry, e.g. https://github.com/hashicorp/consul/blob/main/api/config_entry_gateways.go 1. Copy the top-level fields over into the `Spec` struct except for - `Kind`, `Name`, `Namespace`, `Meta`, `CreateIndex` and `ModifyIndex`. In this + `Kind`, `Name`, `Namespace`, `Partition`, `Meta`, `CreateIndex` and `ModifyIndex`. In this example, the top-level fields remaining are `TLS` and `Listeners`: ```go diff --git a/Makefile b/Makefile index f687c308c5..02527e4d76 100644 --- a/Makefile +++ b/Makefile @@ -138,7 +138,7 @@ ifeq (, $(shell which controller-gen)) CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ cd $$CONTROLLER_GEN_TMP_DIR ;\ go mod init tmp ;\ - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 ;\ + go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.10.0 ;\ rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ } CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen diff --git a/acceptance/go.mod b/acceptance/go.mod index fb2cb62725..c53caf4952 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/gruntwork-io/terratest v0.31.2 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3 - github.com/hashicorp/consul/api v1.20.0 + github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 @@ -97,6 +97,7 @@ require ( github.com/urfave/cli v1.22.2 // indirect go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.6.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 811c11c123..82037c23a0 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -362,6 +362,8 @@ github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+Xb github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3 h1:4wROIZB8Y4cN/wPILChc2zQ/q00z1VyJitdgyLbITdU= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3/go.mod h1:j9Db/whkzvNC+KP2GftY0HxxleLm9swxXjlu3tYaOAw= +github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 h1:6kUTk+YBgA5X5b3gNAoI18WEK4/t75LcWSimEgmpFdg= +github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= @@ -749,6 +751,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index 4aa7cf11bd..a31f4762fd 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -178,6 +178,22 @@ func TestController(t *testing.T) { require.Equal(r, "certFile", terminatingGatewayEntry.Services[0].CertFile) require.Equal(r, "keyFile", terminatingGatewayEntry.Services[0].KeyFile) require.Equal(r, "sni", terminatingGatewayEntry.Services[0].SNI) + + // jwt-provider + entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "test-jwt-provider", nil) + require.NoError(r, err) + jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) + require.True(r, ok, "could not cast to JWTProviderConfigEntry") + require.Equal(r, "jwks.txt", jwtProviderConfigEntry.JSONWebKeySet.Local.Filename) + require.Equal(r, "test-issuer", jwtProviderConfigEntry.Issuer) + require.Equal(r, []string{"aud1", "aud2"}, jwtProviderConfigEntry.Issuer) + require.Equal(r, "x-jwt-header", jwtProviderConfigEntry.Locations[0].Header.Name) + require.Equal(r, "x-query-param", jwtProviderConfigEntry.Locations[1].QueryParam.Name) + require.Equal(r, "session-id", jwtProviderConfigEntry.Locations[2].Cookie.Name) + require.Equal(r, "x-forwarded-jwt", jwtProviderConfigEntry.Forwarding.HeaderName) + require.True(r, jwtProviderConfigEntry.Forwarding.PadForwardPayloadHeader) + require.Equal(r, 45, jwtProviderConfigEntry.ClockSkewSeconds) + require.Equal(r, 15, jwtProviderConfigEntry.CacheConfig) }) } diff --git a/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml b/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml new file mode 100644 index 0000000000..d28139dc40 --- /dev/null +++ b/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml @@ -0,0 +1,34 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: JWTProvider +metadata: + name: test-jwt-provider +spec: + jsonWebKeySet: + local: + filename: "jwks.txt" + issuer: "test-issuer" + audiences: + - "aud1" + - "aud2" + locations: + - header: + name: "x-jwt-header" + valuePrefix: "bearer" + forward: true + - queryParam: + name: "x-query-param" + - cookie: + name: "session-id" + forwarding: + headerName: "x-forwarded-jwt" + padForwardPayloadHeader: true + clockSkewSeconds: 45 + cacheConfig: + size: 15 + + + + diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index e152511469..d4b2464dec 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -31,6 +31,7 @@ rules: - peeringacceptors - peeringdialers {{- end }} + - jwtproviders verbs: - create - delete @@ -57,6 +58,7 @@ rules: - peeringacceptors/status - peeringdialers/status {{- end }} + - jwtproviders/status verbs: - get - patch diff --git a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml index b68efdb9f8..5f26807e0a 100644 --- a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml +++ b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml @@ -313,4 +313,25 @@ webhooks: - samenessgroups sideEffects: None {{- end }} +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: {{ template "consul.fullname" . }}-connect-injector + namespace: {{ .Release.Namespace }} + path: /mutate-v1alpha1-jwtprovider + failurePolicy: Fail + name: mutate-jwtprovider.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - jwtproviders + sideEffects: None {{- end }} diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml new file mode 100644 index 0000000000..c7d20883e8 --- /dev/null +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -0,0 +1,259 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: jwtproviders.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: consul.hashicorp.com + names: + kind: JWTProvider + listKind: JWTProviderList + plural: jwtproviders + singular: jwtprovider + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: JWTProvider is the Schema for the jwtproviders API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: JWTProviderSpec defines the desired state of JWTProvider + properties: + audiences: + description: Audiences is the set of audiences the JWT is allowed + to access. If specified, all JWTs verified with this provider must + address at least one of these to be considered valid. + items: + type: string + type: array + cacheConfig: + description: CacheConfig defines configuration for caching the validation + result for previously seen JWTs. Caching results can speed up verification + when individual tokens are expected to be handled multiple times. + properties: + size: + description: "Size specifies the maximum number of JWT verification + results to cache. \n Defaults to 0, meaning that JWT caching + is disabled." + type: integer + type: object + clockSkewSeconds: + description: "ClockSkewSeconds specifies the maximum allowable time + difference from clock skew when validating the \"exp\" (Expiration) + and \"nbf\" (Not Before) claims. \n Default value is 30 seconds." + type: integer + forwarding: + description: Forwarding defines rules for forwarding verified JWTs + to the backend. + properties: + headerName: + description: "HeaderName is a header name to use when forwarding + a verified JWT to the backend. The verified JWT could have been + extracted from any location (query param, header, or cookie). + \n The header value will be base64-URL-encoded, and will not + be padded unless PadForwardPayloadHeader is true." + type: string + padForwardPayloadHeader: + description: "PadForwardPayloadHeader determines whether padding + should be added to the base64 encoded token forwarded with ForwardPayloadHeader. + \n Default value is false." + type: boolean + type: object + issuer: + description: Issuer is the entity that must have issued the JWT. This + value must match the "iss" claim of the token. + type: string + jsonWebKeySet: + description: JSONWebKeySet defines a JSON Web Key Set, its location + on disk, or the means with which to fetch a key set from a remote + server. + properties: + local: + description: Local specifies a local source for the key set. + properties: + filename: + description: Filename configures a location on disk where + the JWKS can be found. If specified, the file must be present + on the disk of ALL proxies with intentions referencing this + provider. + type: string + jwks: + description: JWKS contains a base64 encoded JWKS. + type: string + type: object + remote: + description: Remote specifies how to fetch a key set from a remote + server. + properties: + cacheDuration: + description: "CacheDuration is the duration after which cached + keys should be expired. \n Default value is 5 minutes." + format: int64 + type: integer + fetchAsynchronously: + description: "FetchAsynchronously indicates that the JWKS + should be fetched when a client request arrives. Client + requests will be paused until the JWKS is fetched. If false, + the proxy listener will wait for the JWKS to be fetched + before being activated. \n Default value is false." + type: boolean + requestTimeoutMs: + description: RequestTimeoutMs is the number of milliseconds + to time out when making a request for the JWKS. + type: integer + retryPolicy: + description: "RetryPolicy defines a retry policy for fetching + JWKS. \n There is no retry by default." + properties: + numRetries: + description: "NumRetries is the number of times to retry + fetching the JWKS. The retry strategy uses jittered + exponential backoff with a base interval of 1s and max + of 10s. \n Default value is 0." + type: integer + retryPolicyBackOff: + description: "Backoff policy \n Defaults to Envoy's backoff + policy" + properties: + baseInterval: + description: "BaseInterval to be used for the next + back off computation \n The default value from envoy + is 1s" + format: int64 + type: integer + maxInterval: + description: "MaxInternal to be used to specify the + maximum interval between retries. Optional but should + be greater or equal to BaseInterval. \n Defaults + to 10 times BaseInterval" + format: int64 + type: integer + type: object + type: object + uri: + description: URI is the URI of the server to query for the + JWKS. + type: string + type: object + type: object + locations: + description: 'Locations where the JWT will be present in requests. + Envoy will check all of these locations to extract a JWT. If no + locations are specified Envoy will default to: 1. Authorization + header with Bearer schema: "Authorization: Bearer " 2. accessToken + query parameter.' + items: + description: "JWTLocation is a location where the JWT could be present + in requests. \n Only one of Header, QueryParam, or Cookie can + be specified." + properties: + cookie: + description: Cookie defines how to extract a JWT from an HTTP + request cookie. + properties: + name: + description: Name is the name of the cookie containing the + token. + type: string + type: object + header: + description: Header defines how to extract a JWT from an HTTP + request header. + properties: + forward: + description: "Forward defines whether the header with the + JWT should be forwarded after the token has been verified. + If false, the header will not be forwarded to the backend. + \n Default value is false." + type: boolean + name: + description: Name is the name of the header containing the + token. + type: string + valuePrefix: + description: 'ValuePrefix is an optional prefix that precedes + the token in the header value. For example, "Bearer " + is a standard value prefix for a header named "Authorization", + but the prefix is not part of the token itself: "Authorization: + Bearer "' + type: string + type: object + queryParam: + description: QueryParam defines how to extract a JWT from an + HTTP request query parameter. + properties: + name: + description: Name is the name of the query param containing + the token. + type: string + type: object + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/control-plane/PROJECT b/control-plane/PROJECT index eb653ad34a..beec0e6504 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -90,4 +90,13 @@ resources: kind: SamenessGroup path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: hashicorp.com + group: consul + kind: JWTProvider + path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 4faeccada1..779ece9218 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -15,6 +15,7 @@ const ( IngressGateway string = "ingressgateway" TerminatingGateway string = "terminatinggateway" SamenessGroup string = "samenessgroup" + JWTProvider string = "jwtprovider" Global string = "global" Mesh string = "mesh" diff --git a/control-plane/api/v1alpha1/jwtprovider_types.go b/control-plane/api/v1alpha1/jwtprovider_types.go new file mode 100644 index 0000000000..fee0ef9a78 --- /dev/null +++ b/control-plane/api/v1alpha1/jwtprovider_types.go @@ -0,0 +1,663 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "encoding/base64" + "encoding/json" + "net/url" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul/api" + capi "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +const ( + JWTProviderKubeKind string = "jwtprovider" +) + +func init() { + SchemeBuilder.Register(&JWTProvider{}, &JWTProviderList{}) +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// JWTProvider is the Schema for the jwtproviders API. +type JWTProvider struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec JWTProviderSpec `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// JWTProviderList contains a list of JWTProvider. +type JWTProviderList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []JWTProvider `json:"items"` +} + +// JWTProviderSpec defines the desired state of JWTProvider +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +type JWTProviderSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // JSONWebKeySet defines a JSON Web Key Set, its location on disk, or the + // means with which to fetch a key set from a remote server. + JSONWebKeySet *JSONWebKeySet `json:"jsonWebKeySet,omitempty"` + + // Issuer is the entity that must have issued the JWT. + // This value must match the "iss" claim of the token. + Issuer string `json:"issuer,omitempty"` + + // Audiences is the set of audiences the JWT is allowed to access. + // If specified, all JWTs verified with this provider must address + // at least one of these to be considered valid. + Audiences []string `json:"audiences,omitempty"` + + // Locations where the JWT will be present in requests. + // Envoy will check all of these locations to extract a JWT. + // If no locations are specified Envoy will default to: + // 1. Authorization header with Bearer schema: + // "Authorization: Bearer " + // 2. accessToken query parameter. + Locations []*JWTLocation `json:"locations,omitempty"` + + // Forwarding defines rules for forwarding verified JWTs to the backend. + Forwarding *JWTForwardingConfig `json:"forwarding,omitempty"` + + // ClockSkewSeconds specifies the maximum allowable time difference + // from clock skew when validating the "exp" (Expiration) and "nbf" + // (Not Before) claims. + // + // Default value is 30 seconds. + ClockSkewSeconds int `json:"clockSkewSeconds,omitempty"` + + // CacheConfig defines configuration for caching the validation + // result for previously seen JWTs. Caching results can speed up + // verification when individual tokens are expected to be handled + // multiple times. + CacheConfig *JWTCacheConfig `json:"cacheConfig,omitempty"` +} + +type JWTLocations []*JWTLocation + +func (j JWTLocations) toConsul() []*capi.JWTLocation { + if j == nil { + return nil + } + result := make([]*capi.JWTLocation, 0, len(j)) + for _, loc := range j { + result = append(result, loc.toConsul()) + } + return result +} + +func (j JWTLocations) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + for i, loc := range j { + errs = append(errs, loc.validate(path.Index(i))...) + } + return errs +} + +// JWTLocation is a location where the JWT could be present in requests. +// +// Only one of Header, QueryParam, or Cookie can be specified. +type JWTLocation struct { + // Header defines how to extract a JWT from an HTTP request header. + Header *JWTLocationHeader `json:"header,omitempty"` + + // QueryParam defines how to extract a JWT from an HTTP request + // query parameter. + QueryParam *JWTLocationQueryParam `json:"queryParam,omitempty"` + + // Cookie defines how to extract a JWT from an HTTP request cookie. + Cookie *JWTLocationCookie `json:"cookie,omitempty"` +} + +func (j *JWTLocation) toConsul() *capi.JWTLocation { + if j == nil { + return nil + } + return &capi.JWTLocation{ + Header: j.Header.toConsul(), + QueryParam: j.QueryParam.toConsul(), + Cookie: j.Cookie.toConsul(), + } +} + +func (j *JWTLocation) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if j == nil { + return append(errs, field.Invalid(path, j, "location must not be nil")) + } + + if 1 != countTrue( + j.Header != nil, + j.QueryParam != nil, + j.Cookie != nil, + ) { + asJSON, _ := json.Marshal(j) + return append(errs, field.Invalid(path, string(asJSON), "exactly one of 'header', 'queryParam', or 'cookie' is required")) + } + + errs = append(errs, j.Header.validate(path.Child("header"))...) + errs = append(errs, j.QueryParam.validate(path.Child("queryParam"))...) + errs = append(errs, j.Cookie.validate(path.Child("cookie"))...) + return errs +} + +// JWTLocationHeader defines how to extract a JWT from an HTTP +// request header. +type JWTLocationHeader struct { + // Name is the name of the header containing the token. + Name string `json:"name,omitempty"` + + // ValuePrefix is an optional prefix that precedes the token in the + // header value. + // For example, "Bearer " is a standard value prefix for a header named + // "Authorization", but the prefix is not part of the token itself: + // "Authorization: Bearer " + ValuePrefix string `json:"valuePrefix,omitempty"` + + // Forward defines whether the header with the JWT should be + // forwarded after the token has been verified. If false, the + // header will not be forwarded to the backend. + // + // Default value is false. + Forward bool `json:"forward,omitempty"` +} + +func (j *JWTLocationHeader) toConsul() *capi.JWTLocationHeader { + if j == nil { + return nil + } + return &capi.JWTLocationHeader{ + Name: j.Name, + ValuePrefix: j.ValuePrefix, + Forward: j.Forward, + } +} + +func (j *JWTLocationHeader) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if j == nil { + return errs + } + + if j.Name == "" { + errs = append(errs, field.Invalid(path.Child("name"), j.Name, "JWT location header name is required")) + } + return errs +} + +// JWTLocationQueryParam defines how to extract a JWT from an HTTP request query parameter. +type JWTLocationQueryParam struct { + // Name is the name of the query param containing the token. + Name string `json:"name,omitempty"` +} + +func (j *JWTLocationQueryParam) toConsul() *capi.JWTLocationQueryParam { + if j == nil { + return nil + } + return &capi.JWTLocationQueryParam{ + Name: j.Name, + } +} + +func (j *JWTLocationQueryParam) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if j == nil { + return nil + } + if j.Name == "" { + errs = append(errs, field.Invalid(path.Child("name"), j.Name, "JWT location query parameter name is required")) + } + return errs +} + +// JWTLocationCookie defines how to extract a JWT from an HTTP request cookie. +type JWTLocationCookie struct { + // Name is the name of the cookie containing the token. + Name string `json:"name,omitempty"` +} + +func (j *JWTLocationCookie) toConsul() *capi.JWTLocationCookie { + if j == nil { + return nil + } + return &capi.JWTLocationCookie{ + Name: j.Name, + } +} + +func (j *JWTLocationCookie) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if j == nil { + return nil + } + if j.Name == "" { + errs = append(errs, field.Invalid(path.Child("name"), j.Name, "JWT location cookie name is required")) + } + return errs +} + +type JWTForwardingConfig struct { + // HeaderName is a header name to use when forwarding a verified + // JWT to the backend. The verified JWT could have been extracted + // from any location (query param, header, or cookie). + // + // The header value will be base64-URL-encoded, and will not be + // padded unless PadForwardPayloadHeader is true. + HeaderName string `json:"headerName,omitempty"` + + // PadForwardPayloadHeader determines whether padding should be added + // to the base64 encoded token forwarded with ForwardPayloadHeader. + // + // Default value is false. + PadForwardPayloadHeader bool `json:"padForwardPayloadHeader,omitempty"` +} + +func (j *JWTForwardingConfig) toConsul() *capi.JWTForwardingConfig { + if j == nil { + return nil + } + return &capi.JWTForwardingConfig{ + HeaderName: j.HeaderName, + PadForwardPayloadHeader: j.PadForwardPayloadHeader, + } +} + +func (j *JWTForwardingConfig) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if j == nil { + return nil + } + + if j.HeaderName == "" { + errs = append(errs, field.Invalid(path.Child("HeaderName"), j.HeaderName, "JWT forwarding header name is required")) + } + return errs +} + +// JSONWebKeySet defines a key set, its location on disk, or the +// means with which to fetch a key set from a remote server. +// +// Exactly one of Local or Remote must be specified. +type JSONWebKeySet struct { + // Local specifies a local source for the key set. + Local *LocalJWKS `json:"local,omitempty"` + + // Remote specifies how to fetch a key set from a remote server. + Remote *RemoteJWKS `json:"remote,omitempty"` +} + +func (j *JSONWebKeySet) toConsul() *capi.JSONWebKeySet { + if j == nil { + return nil + } + + return &capi.JSONWebKeySet{ + Local: j.Local.toConsul(), + Remote: j.Remote.toConsul(), + } +} + +func (j *JSONWebKeySet) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if j == nil { + return append(errs, field.Invalid(path, j, "jsonWebKeySet is required")) + } + + if countTrue(j.Local != nil, j.Remote != nil) != 1 { + asJSON, _ := json.Marshal(j) + return append(errs, field.Invalid(path, string(asJSON), "exactly one of 'local' or 'remote' is required")) + } + errs = append(errs, j.Local.validate(path.Child("local"))...) + errs = append(errs, j.Remote.validate(path.Child("remote"))...) + return errs +} + +// LocalJWKS specifies a location for a local JWKS. +// +// Only one of String and Filename can be specified. +type LocalJWKS struct { + // JWKS contains a base64 encoded JWKS. + JWKS string `json:"jwks,omitempty"` + + // Filename configures a location on disk where the JWKS can be + // found. If specified, the file must be present on the disk of ALL + // proxies with intentions referencing this provider. + Filename string `json:"filename,omitempty"` +} + +func (l *LocalJWKS) toConsul() *capi.LocalJWKS { + if l == nil { + return nil + } + return &capi.LocalJWKS{ + JWKS: l.JWKS, + Filename: l.Filename, + } +} + +func (l *LocalJWKS) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if l == nil { + return errs + } + + if countTrue(l.JWKS != "", l.Filename != "") != 1 { + asJSON, _ := json.Marshal(l) + return append(errs, field.Invalid(path, string(asJSON), "Exactly one of 'jwks' or 'filename' is required")) + } + if l.JWKS != "" { + if _, err := base64.StdEncoding.DecodeString(l.JWKS); err != nil { + return append(errs, field.Invalid(path.Child("jwks"), l.JWKS, "JWKS must be a valid base64-encoded string")) + } + } + return errs +} + +// RemoteJWKS specifies how to fetch a JWKS from a remote server. +type RemoteJWKS struct { + // URI is the URI of the server to query for the JWKS. + URI string `json:"uri,omitempty"` + + // RequestTimeoutMs is the number of milliseconds to + // time out when making a request for the JWKS. + RequestTimeoutMs int `json:"requestTimeoutMs,omitempty"` + + // CacheDuration is the duration after which cached keys + // should be expired. + // + // Default value is 5 minutes. + CacheDuration time.Duration `json:"cacheDuration,omitempty"` + + // FetchAsynchronously indicates that the JWKS should be fetched + // when a client request arrives. Client requests will be paused + // until the JWKS is fetched. + // If false, the proxy listener will wait for the JWKS to be + // fetched before being activated. + // + // Default value is false. + FetchAsynchronously bool `json:"fetchAsynchronously,omitempty"` + + // RetryPolicy defines a retry policy for fetching JWKS. + // + // There is no retry by default. + RetryPolicy *JWKSRetryPolicy `json:"retryPolicy,omitempty"` +} + +func (r *RemoteJWKS) toConsul() *capi.RemoteJWKS { + if r == nil { + return nil + } + return &capi.RemoteJWKS{ + URI: r.URI, + RequestTimeoutMs: r.RequestTimeoutMs, + CacheDuration: r.CacheDuration, + FetchAsynchronously: r.FetchAsynchronously, + RetryPolicy: r.RetryPolicy.toConsul(), + } +} + +func (r *RemoteJWKS) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if r == nil { + return errs + } + + if r.URI == "" { + errs = append(errs, field.Invalid(path.Child("uri"), r.URI, "remote JWKS URI is required")) + } else if _, err := url.ParseRequestURI(r.URI); err != nil { + errs = append(errs, field.Invalid(path.Child("uri"), r.URI, "remote JWKS URI is invalid")) + } + + errs = append(errs, r.RetryPolicy.validate(path.Child("retryPolicy"))...) + return errs +} + +type JWKSRetryPolicy struct { + // NumRetries is the number of times to retry fetching the JWKS. + // The retry strategy uses jittered exponential backoff with + // a base interval of 1s and max of 10s. + // + // Default value is 0. + NumRetries int `json:"numRetries,omitempty"` + + // Backoff policy + // + // Defaults to Envoy's backoff policy + RetryPolicyBackOff *RetryPolicyBackOff `json:"retryPolicyBackOff,omitempty"` +} + +func (j *JWKSRetryPolicy) toConsul() *capi.JWKSRetryPolicy { + if j == nil { + return nil + } + return &capi.JWKSRetryPolicy{ + NumRetries: j.NumRetries, + RetryPolicyBackOff: j.RetryPolicyBackOff.toConsul(), + } +} + +func (j *JWKSRetryPolicy) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if j == nil { + return errs + } + + return append(errs, j.RetryPolicyBackOff.validate(path.Child("retryPolicyBackOff"))...) +} + +type RetryPolicyBackOff struct { + // BaseInterval to be used for the next back off computation + // + // The default value from envoy is 1s + BaseInterval time.Duration `json:"baseInterval,omitempty"` + + // MaxInternal to be used to specify the maximum interval between retries. + // Optional but should be greater or equal to BaseInterval. + // + // Defaults to 10 times BaseInterval + MaxInterval time.Duration `json:"maxInterval,omitempty"` +} + +func (r *RetryPolicyBackOff) toConsul() *capi.RetryPolicyBackOff { + if r == nil { + return nil + } + return &capi.RetryPolicyBackOff{ + BaseInterval: r.BaseInterval, + MaxInterval: r.MaxInterval, + } +} + +func (r *RetryPolicyBackOff) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if r == nil { + return errs + } + + if (r.MaxInterval != 0) && (r.BaseInterval > r.MaxInterval) { + asJSON, _ := json.Marshal(r) + errs = append(errs, field.Invalid(path, string(asJSON), "maxInterval should be greater or equal to baseInterval")) + } + return errs +} + +type JWTCacheConfig struct { + // Size specifies the maximum number of JWT verification + // results to cache. + // + // Defaults to 0, meaning that JWT caching is disabled. + Size int `json:"size,omitempty"` +} + +func (j *JWTCacheConfig) toConsul() *capi.JWTCacheConfig { + if j == nil { + return nil + } + return &capi.JWTCacheConfig{ + Size: j.Size, + } +} + +func (j *JWTProvider) GetObjectMeta() metav1.ObjectMeta { + return j.ObjectMeta +} + +func (j *JWTProvider) AddFinalizer(name string) { + j.ObjectMeta.Finalizers = append(j.Finalizers(), name) +} + +func (j *JWTProvider) RemoveFinalizer(name string) { + var newFinalizers []string + for _, oldF := range j.Finalizers() { + if oldF != name { + newFinalizers = append(newFinalizers, oldF) + } + } + j.ObjectMeta.Finalizers = newFinalizers +} + +func (j *JWTProvider) Finalizers() []string { + return j.ObjectMeta.Finalizers +} + +func (j *JWTProvider) ConsulKind() string { + return capi.JWTProvider +} + +func (j *JWTProvider) ConsulGlobalResource() bool { + return true +} + +func (j *JWTProvider) ConsulMirroringNS() string { + return common.DefaultConsulNamespace +} + +func (j *JWTProvider) KubeKind() string { + return JWTProviderKubeKind +} + +func (j *JWTProvider) ConsulName() string { + return j.ObjectMeta.Name +} + +func (j *JWTProvider) KubernetesName() string { + return j.ObjectMeta.Name +} + +func (j *JWTProvider) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + j.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (j *JWTProvider) SetLastSyncedTime(time *metav1.Time) { + j.Status.LastSyncedTime = time +} + +// SyncedCondition gets the synced condition. +func (j *JWTProvider) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := j.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +// SyncedConditionStatus returns the status of the synced condition. +func (j *JWTProvider) SyncedConditionStatus() corev1.ConditionStatus { + cond := j.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown + } + return cond.Status +} + +// ToConsul converts the resource to the corresponding Consul API definition. +// Its return type is the generic ConfigEntry but a specific config entry +// type should be constructed e.g. ServiceConfigEntry. +func (j *JWTProvider) ToConsul(datacenter string) api.ConfigEntry { + return &capi.JWTProviderConfigEntry{ + Kind: j.ConsulKind(), + Name: j.ConsulName(), + JSONWebKeySet: j.Spec.JSONWebKeySet.toConsul(), + Issuer: j.Spec.Issuer, + Audiences: j.Spec.Audiences, + Locations: JWTLocations(j.Spec.Locations).toConsul(), + Forwarding: j.Spec.Forwarding.toConsul(), + ClockSkewSeconds: j.Spec.ClockSkewSeconds, + CacheConfig: j.Spec.CacheConfig.toConsul(), + Meta: meta(datacenter), + } +} + +// MatchesConsul returns true if the resource has the same fields as the Consul +// config entry. +func (j *JWTProvider) MatchesConsul(candidate api.ConfigEntry) bool { + configEntry, ok := candidate.(*capi.JWTProviderConfigEntry) + if !ok { + return false + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. + return cmp.Equal(j.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.JWTProviderConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) +} + +// Validate returns an error if the resource is invalid. +func (j *JWTProvider) Validate(consulMeta common.ConsulMeta) error { + var errs field.ErrorList + path := field.NewPath("spec") + + errs = append(errs, j.Spec.JSONWebKeySet.validate(path.Child("jsonWebKeySet"))...) + errs = append(errs, JWTLocations(j.Spec.Locations).validate(path.Child("locations"))...) + errs = append(errs, j.Spec.Forwarding.validate(path.Child("forwarding"))...) + if len(errs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: ConsulHashicorpGroup, Kind: JWTProviderKubeKind}, + j.KubernetesName(), errs) + } + return nil +} + +// DefaultNamespaceFields sets Consul namespace fields on the config entry +// spec to their default values if namespaces are enabled. +func (j *JWTProvider) DefaultNamespaceFields(_ common.ConsulMeta) {} + +func countTrue(vals ...bool) int { + var result int + for _, v := range vals { + if v { + result++ + } + } + return result +} + +var _ common.ConfigEntryResource = (*JWTProvider)(nil) diff --git a/control-plane/api/v1alpha1/jwtprovider_types_test.go b/control-plane/api/v1alpha1/jwtprovider_types_test.go new file mode 100644 index 0000000000..15a3e7a5d6 --- /dev/null +++ b/control-plane/api/v1alpha1/jwtprovider_types_test.go @@ -0,0 +1,730 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "testing" + "time" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + capi "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Test MatchesConsul for cases that should return true. +func TestJWTProvider_MatchesConsul(t *testing.T) { + cases := map[string]struct { + Ours JWTProvider + Theirs capi.ConfigEntry + Matches bool + }{ + "empty fields matches": { + Ours: JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-okta", + }, + Spec: JWTProviderSpec{}, + }, + Theirs: &capi.JWTProviderConfigEntry{ + Kind: capi.JWTProvider, + Name: "test-okta", + Namespace: "default", + CreateIndex: 1, + ModifyIndex: 2, + Meta: map[string]string{ + common.SourceKey: common.SourceValue, + common.DatacenterKey: "datacenter", + }, + }, + Matches: true, + }, + "all fields set matches": { + Ours: JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-okta2", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Local: &LocalJWKS{ + JWKS: "jwks-string", + Filename: "jwks-file", + }, + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + RequestTimeoutMs: 567, + CacheDuration: 890, + FetchAsynchronously: true, + RetryPolicy: &JWKSRetryPolicy{ + NumRetries: 1, + RetryPolicyBackOff: &RetryPolicyBackOff{ + BaseInterval: 23, + MaxInterval: 456, + }, + }, + }, + }, + Issuer: "test-issuer", + Audiences: []string{"aud1", "aud2"}, + Locations: []*JWTLocation{ + { + Header: &JWTLocationHeader{ + Name: "jwt-header", + ValuePrefix: "my-bearer", + Forward: true, + }, + }, + { + QueryParam: &JWTLocationQueryParam{ + Name: "jwt-query-param", + }, + }, + { + Cookie: &JWTLocationCookie{ + Name: "jwt-cookie", + }, + }, + }, + Forwarding: &JWTForwardingConfig{ + HeaderName: "jwt-forward-header", + PadForwardPayloadHeader: true, + }, + ClockSkewSeconds: 357, + CacheConfig: &JWTCacheConfig{ + Size: 468, + }, + }, + }, + Theirs: &capi.JWTProviderConfigEntry{ + Kind: capi.JWTProvider, + Name: "test-okta2", + Namespace: "default", + JSONWebKeySet: &capi.JSONWebKeySet{ + Local: &capi.LocalJWKS{ + JWKS: "jwks-string", + Filename: "jwks-file", + }, + Remote: &capi.RemoteJWKS{ + URI: "https://jwks.example.com", + RequestTimeoutMs: 567, + CacheDuration: 890, + FetchAsynchronously: true, + RetryPolicy: &capi.JWKSRetryPolicy{ + NumRetries: 1, + RetryPolicyBackOff: &capi.RetryPolicyBackOff{ + BaseInterval: 23, + MaxInterval: 456, + }, + }, + }, + }, + Issuer: "test-issuer", + Audiences: []string{"aud1", "aud2"}, + Locations: []*capi.JWTLocation{ + { + Header: &capi.JWTLocationHeader{ + Name: "jwt-header", + ValuePrefix: "my-bearer", + Forward: true, + }, + }, + { + QueryParam: &capi.JWTLocationQueryParam{ + Name: "jwt-query-param", + }, + }, + { + Cookie: &capi.JWTLocationCookie{ + Name: "jwt-cookie", + }, + }, + }, + Forwarding: &capi.JWTForwardingConfig{ + HeaderName: "jwt-forward-header", + PadForwardPayloadHeader: true, + }, + ClockSkewSeconds: 357, + CacheConfig: &capi.JWTCacheConfig{ + Size: 468, + }, + }, + Matches: true, + }, + "mismatched types does not match": { + Ours: JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-okta3", + }, + Spec: JWTProviderSpec{}, + }, + Theirs: &capi.JWTProviderConfigEntry{}, + Matches: false, + }, + } + for name, c := range cases { + c := c + t.Run(name, func(t *testing.T) { + require.Equal(t, c.Matches, c.Ours.MatchesConsul(c.Theirs)) + }) + } +} + +func TestJWTProvider_ToConsul(t *testing.T) { + cases := map[string]struct { + Ours JWTProvider + Exp *capi.JWTProviderConfigEntry + }{ + "empty fields": { + Ours: JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-okta1", + }, + Spec: JWTProviderSpec{}, + }, + Exp: &capi.JWTProviderConfigEntry{ + Kind: capi.JWTProvider, + Name: "test-okta1", + Meta: map[string]string{ + common.SourceKey: common.SourceValue, + common.DatacenterKey: "datacenter", + }, + }, + }, + "every field set": { + Ours: JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-okta2", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Local: &LocalJWKS{ + JWKS: "jwks-string", + Filename: "jwks-file", + }, + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + RequestTimeoutMs: 567, + CacheDuration: 890, + FetchAsynchronously: true, + RetryPolicy: &JWKSRetryPolicy{ + NumRetries: 1, + RetryPolicyBackOff: &RetryPolicyBackOff{ + BaseInterval: 23, + MaxInterval: 456, + }, + }, + }, + }, + Issuer: "test-issuer", + Audiences: []string{"aud1", "aud2"}, + Locations: []*JWTLocation{ + { + Header: &JWTLocationHeader{ + Name: "jwt-header", + ValuePrefix: "my-bearer", + Forward: true, + }, + }, + { + QueryParam: &JWTLocationQueryParam{ + Name: "jwt-query-param", + }, + }, + { + Cookie: &JWTLocationCookie{ + Name: "jwt-cookie", + }, + }, + }, + Forwarding: &JWTForwardingConfig{ + HeaderName: "jwt-forward-header", + PadForwardPayloadHeader: true, + }, + ClockSkewSeconds: 357, + CacheConfig: &JWTCacheConfig{ + Size: 468, + }, + }, + }, + Exp: &capi.JWTProviderConfigEntry{ + Kind: capi.JWTProvider, + Name: "test-okta2", + JSONWebKeySet: &capi.JSONWebKeySet{ + Local: &capi.LocalJWKS{ + JWKS: "jwks-string", + Filename: "jwks-file", + }, + Remote: &capi.RemoteJWKS{ + URI: "https://jwks.example.com", + RequestTimeoutMs: 567, + CacheDuration: 890, + FetchAsynchronously: true, + RetryPolicy: &capi.JWKSRetryPolicy{ + NumRetries: 1, + RetryPolicyBackOff: &capi.RetryPolicyBackOff{ + BaseInterval: 23, + MaxInterval: 456, + }, + }, + }, + }, + Issuer: "test-issuer", + Audiences: []string{"aud1", "aud2"}, + Locations: []*capi.JWTLocation{ + { + Header: &capi.JWTLocationHeader{ + Name: "jwt-header", + ValuePrefix: "my-bearer", + Forward: true, + }, + }, + { + QueryParam: &capi.JWTLocationQueryParam{ + Name: "jwt-query-param", + }, + }, + { + Cookie: &capi.JWTLocationCookie{ + Name: "jwt-cookie", + }, + }, + }, + Forwarding: &capi.JWTForwardingConfig{ + HeaderName: "jwt-forward-header", + PadForwardPayloadHeader: true, + }, + ClockSkewSeconds: 357, + CacheConfig: &capi.JWTCacheConfig{ + Size: 468, + }, + Meta: map[string]string{ + common.SourceKey: common.SourceValue, + common.DatacenterKey: "datacenter", + }, + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + act := c.Ours.ToConsul("datacenter") + mesh, ok := act.(*capi.JWTProviderConfigEntry) + require.True(t, ok, "could not cast") + require.Equal(t, c.Exp, mesh) + }) + } +} + +func TestJWTProvider_Validate(t *testing.T) { + cases := map[string]struct { + input *JWTProvider + expectedErrMsgs []string + consulMeta common.ConsulMeta + }{ + "valid - local jwks": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-okta1", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Local: &LocalJWKS{ + Filename: "jwks.txt", + }, + }, + }, + Status: Status{}, + }, + expectedErrMsgs: nil, + }, + + "valid - remote jwks": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwt-provider", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + FetchAsynchronously: true, + }, + }, + Locations: []*JWTLocation{ + { + Header: &JWTLocationHeader{ + Name: "Authorization", + }, + }, + }, + Forwarding: &JWTForwardingConfig{ + HeaderName: "jwt-forward-header", + }, + }, + }, + expectedErrMsgs: nil, + }, + + "valid - remote jwks with all fields": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwt-provider", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + RequestTimeoutMs: 5000, + CacheDuration: 10 * time.Second, + FetchAsynchronously: true, + RetryPolicy: &JWKSRetryPolicy{ + NumRetries: 3, + RetryPolicyBackOff: &RetryPolicyBackOff{ + BaseInterval: 5 * time.Second, + MaxInterval: 20 * time.Second, + }, + }, + }, + }, + Issuer: "test-issuer", + Audiences: []string{"aud1", "aud2"}, + Locations: []*JWTLocation{ + { + Header: &JWTLocationHeader{ + Name: "Authorization", + ValuePrefix: "Bearer", + Forward: true, + }, + }, + { + QueryParam: &JWTLocationQueryParam{ + Name: "access-token", + }, + }, + { + Cookie: &JWTLocationCookie{ + Name: "session-id", + }, + }, + }, + Forwarding: &JWTForwardingConfig{ + HeaderName: "jwt-forward-header", + PadForwardPayloadHeader: true, + }, + ClockSkewSeconds: 20, + CacheConfig: &JWTCacheConfig{ + Size: 30, + }, + }, + }, + expectedErrMsgs: nil, + }, + + "invalid - nil jwks": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-no-jwks", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: nil, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-no-jwks" is invalid: spec.jsonWebKeySet: Invalid value: "null": jsonWebKeySet is required`, + }, + }, + + "invalid - empty jwks": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-no-jwks", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{}, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-no-jwks" is invalid: spec.jsonWebKeySet: Invalid value: "{}": exactly one of 'local' or 'remote' is required`, + }, + }, + + "invalid - local jwks with non-base64 string": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-base64", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Local: &LocalJWKS{ + JWKS: "not base64 encoded", + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-base64" is invalid: spec.jsonWebKeySet.local.jwks: Invalid value: "not base64 encoded": JWKS must be a valid base64-encoded string`, + }, + }, + + "invalid - both local and remote jwks set": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-local-and-remote", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Local: &LocalJWKS{Filename: "jwks.txt"}, + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-local-and-remote" is invalid: spec.jsonWebKeySet: Invalid value: "{\"local\":{\"filename\":\"jwks.txt\"},\"remote\":{\"uri\":\"https://jwks.example.com\"}}": exactly one of 'local' or 'remote' is required`, + }, + }, + + "invalid - remote jwks missing uri": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-missing-uri", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + FetchAsynchronously: true, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-missing-uri" is invalid: spec.jsonWebKeySet.remote.uri: Invalid value: "": remote JWKS URI is required`, + }, + }, + + "invalid - remote jwks invalid uri": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-invalid-uri", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "invalid-uri", + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.uri: Invalid value: "invalid-uri": remote JWKS URI is invalid`, + }, + }, + + "invalid - JWT location with all fields": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-all-locations", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + }, + }, + Locations: []*JWTLocation{ + { + Header: &JWTLocationHeader{ + Name: "jwt-header", + }, + QueryParam: &JWTLocationQueryParam{ + Name: "jwt-query-param", + }, + Cookie: &JWTLocationCookie{ + Name: "jwt-cookie", + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-all-locations" is invalid: spec.locations[0]: Invalid value: "{\"header\":{\"name\":\"jwt-header\"},\"queryParam\":{\"name\":\"jwt-query-param\"},\"cookie\":{\"name\":\"jwt-cookie\"}}": exactly one of 'header', 'queryParam', or 'cookie' is required`, + }, + }, + + "invalid - JWT location with two fields": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-two-locations", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + }, + }, + Locations: []*JWTLocation{ + { + Header: &JWTLocationHeader{ + Name: "jwt-header", + }, + Cookie: &JWTLocationCookie{ + Name: "jwt-cookie", + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-two-locations" is invalid: spec.locations[0]: Invalid value: "{\"header\":{\"name\":\"jwt-header\"},\"cookie\":{\"name\":\"jwt-cookie\"}}": exactly one of 'header', 'queryParam', or 'cookie' is required`, + }, + }, + + "invalid - remote jwks retry policy maxInterval < baseInterval": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-retry-intervals", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + RetryPolicy: &JWKSRetryPolicy{ + NumRetries: 0, + RetryPolicyBackOff: &RetryPolicyBackOff{ + BaseInterval: 100 * time.Second, + MaxInterval: 10 * time.Second, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-retry-intervals" is invalid: spec.jsonWebKeySet.remote.retryPolicy.retryPolicyBackOff: Invalid value: "{\"baseInterval\":100000000000,\"maxInterval\":10000000000}": maxInterval should be greater or equal to baseInterval`, + }, + }, + } + + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + err := testCase.input.Validate(testCase.consulMeta) + if len(testCase.expectedErrMsgs) != 0 { + require.Error(t, err) + for _, s := range testCase.expectedErrMsgs { + require.Contains(t, err.Error(), s) + } + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestJWTProvider_AddFinalizer(t *testing.T) { + jwt := &JWTProvider{} + jwt.AddFinalizer("finalizer") + require.Equal(t, []string{"finalizer"}, jwt.ObjectMeta.Finalizers) +} + +func TestJWTProvider_RemoveFinalizer(t *testing.T) { + jwt := &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{"f1", "f2"}, + }, + } + jwt.RemoveFinalizer("f1") + require.Equal(t, []string{"f2"}, jwt.ObjectMeta.Finalizers) +} + +func TestJWTProvider_SetSyncedCondition(t *testing.T) { + jwt := &JWTProvider{} + jwt.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") + + require.Equal(t, corev1.ConditionTrue, jwt.Status.Conditions[0].Status) + require.Equal(t, "reason", jwt.Status.Conditions[0].Reason) + require.Equal(t, "message", jwt.Status.Conditions[0].Message) + now := metav1.Now() + require.True(t, jwt.Status.Conditions[0].LastTransitionTime.Before(&now)) +} + +func TestJWTProvider_SetLastSyncedTime(t *testing.T) { + jwt := &JWTProvider{} + syncedTime := metav1.NewTime(time.Now()) + jwt.SetLastSyncedTime(&syncedTime) + require.Equal(t, &syncedTime, jwt.Status.LastSyncedTime) +} + +func TestJWTProvider_GetSyncedConditionStatus(t *testing.T) { + cases := []corev1.ConditionStatus{ + corev1.ConditionUnknown, + corev1.ConditionFalse, + corev1.ConditionTrue, + } + for _, status := range cases { + t.Run(string(status), func(t *testing.T) { + jwt := &JWTProvider{ + Status: Status{ + Conditions: []Condition{{ + Type: ConditionSynced, + Status: status, + }}, + }, + } + + require.Equal(t, status, jwt.SyncedConditionStatus()) + }) + } +} + +func TestJWTProvider_GetConditionWhenStatusNil(t *testing.T) { + require.Nil(t, (&JWTProvider{}).GetCondition(ConditionSynced)) +} + +func TestJWTProvider_SyncedConditionStatusWhenStatusNil(t *testing.T) { + require.Equal(t, corev1.ConditionUnknown, (&JWTProvider{}).SyncedConditionStatus()) +} + +func TestJWTProvider_SyncedConditionWhenStatusNil(t *testing.T) { + status, reason, message := (&JWTProvider{}).SyncedCondition() + require.Equal(t, corev1.ConditionUnknown, status) + require.Equal(t, "", reason) + require.Equal(t, "", message) +} + +func TestJWTProvider_ConsulKind(t *testing.T) { + require.Equal(t, capi.JWTProvider, (&JWTProvider{}).ConsulKind()) +} + +func TestJWTProvider_KubeKind(t *testing.T) { + require.Equal(t, "jwtprovider", (&JWTProvider{}).KubeKind()) +} + +func TestJWTProvider_ConsulName(t *testing.T) { + require.Equal(t, "foo", (&JWTProvider{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).ConsulName()) +} + +func TestJWTProvider_KubernetesName(t *testing.T) { + require.Equal(t, "foo", (&JWTProvider{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).KubernetesName()) +} + +func TestJWTProvider_ConsulNamespace(t *testing.T) { + require.Equal(t, common.DefaultConsulNamespace, (&JWTProvider{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}}).ConsulMirroringNS()) +} + +func TestJWTProvider_ConsulGlobalResource(t *testing.T) { + require.True(t, (&JWTProvider{}).ConsulGlobalResource()) +} + +func TestJWTProvider_ObjectMeta(t *testing.T) { + meta := metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + } + jwt := &JWTProvider{ + ObjectMeta: meta, + } + require.Equal(t, meta, jwt.GetObjectMeta()) +} diff --git a/control-plane/api/v1alpha1/jwtprovider_webhook.go b/control-plane/api/v1alpha1/jwtprovider_webhook.go new file mode 100644 index 0000000000..c434c83c01 --- /dev/null +++ b/control-plane/api/v1alpha1/jwtprovider_webhook.go @@ -0,0 +1,61 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "context" + "net/http" + + "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// +kubebuilder:object:generate=false + +type JWTProviderWebhook struct { + Logger logr.Logger + + // ConsulMeta contains metadata specific to the Consul installation. + ConsulMeta common.ConsulMeta + + decoder *admission.Decoder + client.Client +} + +// NOTE: The path value in the below line is the path to the webhook. +// If it is updated, run code-gen, update subcommand/controller/command.go +// and the consul-helm value for the path to the webhook. +// +// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. +// +// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-jwtprovider,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=jwtproviders,versions=v1alpha1,name=mutate-jwtprovider.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *JWTProviderWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var resource JWTProvider + err := v.decoder.Decode(req, &resource) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + return common.ValidateConfigEntry(ctx, req, v.Logger, v, &resource, v.ConsulMeta) +} + +func (v *JWTProviderWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { + var resourceList JWTProviderList + if err := v.Client.List(ctx, &resourceList); err != nil { + return nil, err + } + var entries []common.ConfigEntryResource + for _, item := range resourceList.Items { + entries = append(entries, common.ConfigEntryResource(&item)) + } + return entries, nil +} + +func (v *JWTProviderWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index f1f5ffb150..e8bff89986 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -834,6 +834,261 @@ func (in IntentionPermissions) DeepCopy() IntentionPermissions { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JSONWebKeySet) DeepCopyInto(out *JSONWebKeySet) { + *out = *in + if in.Local != nil { + in, out := &in.Local, &out.Local + *out = new(LocalJWKS) + **out = **in + } + if in.Remote != nil { + in, out := &in.Remote, &out.Remote + *out = new(RemoteJWKS) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONWebKeySet. +func (in *JSONWebKeySet) DeepCopy() *JSONWebKeySet { + if in == nil { + return nil + } + out := new(JSONWebKeySet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWKSRetryPolicy) DeepCopyInto(out *JWKSRetryPolicy) { + *out = *in + if in.RetryPolicyBackOff != nil { + in, out := &in.RetryPolicyBackOff, &out.RetryPolicyBackOff + *out = new(RetryPolicyBackOff) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSRetryPolicy. +func (in *JWKSRetryPolicy) DeepCopy() *JWKSRetryPolicy { + if in == nil { + return nil + } + out := new(JWKSRetryPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTCacheConfig) DeepCopyInto(out *JWTCacheConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTCacheConfig. +func (in *JWTCacheConfig) DeepCopy() *JWTCacheConfig { + if in == nil { + return nil + } + out := new(JWTCacheConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTForwardingConfig) DeepCopyInto(out *JWTForwardingConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTForwardingConfig. +func (in *JWTForwardingConfig) DeepCopy() *JWTForwardingConfig { + if in == nil { + return nil + } + out := new(JWTForwardingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTLocation) DeepCopyInto(out *JWTLocation) { + *out = *in + if in.Header != nil { + in, out := &in.Header, &out.Header + *out = new(JWTLocationHeader) + **out = **in + } + if in.QueryParam != nil { + in, out := &in.QueryParam, &out.QueryParam + *out = new(JWTLocationQueryParam) + **out = **in + } + if in.Cookie != nil { + in, out := &in.Cookie, &out.Cookie + *out = new(JWTLocationCookie) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocation. +func (in *JWTLocation) DeepCopy() *JWTLocation { + if in == nil { + return nil + } + out := new(JWTLocation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTLocationCookie) DeepCopyInto(out *JWTLocationCookie) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocationCookie. +func (in *JWTLocationCookie) DeepCopy() *JWTLocationCookie { + if in == nil { + return nil + } + out := new(JWTLocationCookie) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTLocationHeader) DeepCopyInto(out *JWTLocationHeader) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocationHeader. +func (in *JWTLocationHeader) DeepCopy() *JWTLocationHeader { + if in == nil { + return nil + } + out := new(JWTLocationHeader) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTLocationQueryParam) DeepCopyInto(out *JWTLocationQueryParam) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocationQueryParam. +func (in *JWTLocationQueryParam) DeepCopy() *JWTLocationQueryParam { + if in == nil { + return nil + } + out := new(JWTLocationQueryParam) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTProvider) DeepCopyInto(out *JWTProvider) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTProvider. +func (in *JWTProvider) DeepCopy() *JWTProvider { + if in == nil { + return nil + } + out := new(JWTProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JWTProvider) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTProviderList) DeepCopyInto(out *JWTProviderList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]JWTProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTProviderList. +func (in *JWTProviderList) DeepCopy() *JWTProviderList { + if in == nil { + return nil + } + out := new(JWTProviderList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JWTProviderList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTProviderSpec) DeepCopyInto(out *JWTProviderSpec) { + *out = *in + if in.JSONWebKeySet != nil { + in, out := &in.JSONWebKeySet, &out.JSONWebKeySet + *out = new(JSONWebKeySet) + (*in).DeepCopyInto(*out) + } + if in.Audiences != nil { + in, out := &in.Audiences, &out.Audiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Locations != nil { + in, out := &in.Locations, &out.Locations + *out = make([]*JWTLocation, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(JWTLocation) + (*in).DeepCopyInto(*out) + } + } + } + if in.Forwarding != nil { + in, out := &in.Forwarding, &out.Forwarding + *out = new(JWTForwardingConfig) + **out = **in + } + if in.CacheConfig != nil { + in, out := &in.CacheConfig, &out.CacheConfig + *out = new(JWTCacheConfig) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTProviderSpec. +func (in *JWTProviderSpec) DeepCopy() *JWTProviderSpec { + if in == nil { + return nil + } + out := new(JWTProviderSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LeastRequestConfig) DeepCopyInto(out *LeastRequestConfig) { *out = *in @@ -896,6 +1151,21 @@ func (in *LoadBalancer) DeepCopy() *LoadBalancer { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalJWKS) DeepCopyInto(out *LocalJWKS) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalJWKS. +func (in *LocalJWKS) DeepCopy() *LocalJWKS { + if in == nil { + return nil + } + out := new(LocalJWKS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Mesh) DeepCopyInto(out *Mesh) { *out = *in @@ -1543,6 +1813,41 @@ func (in *ProxyDefaultsSpec) DeepCopy() *ProxyDefaultsSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RemoteJWKS) DeepCopyInto(out *RemoteJWKS) { + *out = *in + if in.RetryPolicy != nil { + in, out := &in.RetryPolicy, &out.RetryPolicy + *out = new(JWKSRetryPolicy) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteJWKS. +func (in *RemoteJWKS) DeepCopy() *RemoteJWKS { + if in == nil { + return nil + } + out := new(RemoteJWKS) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RetryPolicyBackOff) DeepCopyInto(out *RetryPolicyBackOff) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryPolicyBackOff. +func (in *RetryPolicyBackOff) DeepCopy() *RetryPolicyBackOff { + if in == nil { + return nil + } + out := new(RetryPolicyBackOff) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RingHashConfig) DeepCopyInto(out *RingHashConfig) { *out = *in diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml new file mode 100644 index 0000000000..8ca1ec0748 --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -0,0 +1,254 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: jwtproviders.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: JWTProvider + listKind: JWTProviderList + plural: jwtproviders + singular: jwtprovider + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: JWTProvider is the Schema for the jwtproviders API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: JWTProviderSpec defines the desired state of JWTProvider + properties: + audiences: + description: Audiences is the set of audiences the JWT is allowed + to access. If specified, all JWTs verified with this provider must + address at least one of these to be considered valid. + items: + type: string + type: array + cacheConfig: + description: CacheConfig defines configuration for caching the validation + result for previously seen JWTs. Caching results can speed up verification + when individual tokens are expected to be handled multiple times. + properties: + size: + description: "Size specifies the maximum number of JWT verification + results to cache. \n Defaults to 0, meaning that JWT caching + is disabled." + type: integer + type: object + clockSkewSeconds: + description: "ClockSkewSeconds specifies the maximum allowable time + difference from clock skew when validating the \"exp\" (Expiration) + and \"nbf\" (Not Before) claims. \n Default value is 30 seconds." + type: integer + forwarding: + description: Forwarding defines rules for forwarding verified JWTs + to the backend. + properties: + headerName: + description: "HeaderName is a header name to use when forwarding + a verified JWT to the backend. The verified JWT could have been + extracted from any location (query param, header, or cookie). + \n The header value will be base64-URL-encoded, and will not + be padded unless PadForwardPayloadHeader is true." + type: string + padForwardPayloadHeader: + description: "PadForwardPayloadHeader determines whether padding + should be added to the base64 encoded token forwarded with ForwardPayloadHeader. + \n Default value is false." + type: boolean + type: object + issuer: + description: Issuer is the entity that must have issued the JWT. This + value must match the "iss" claim of the token. + type: string + jsonWebKeySet: + description: JSONWebKeySet defines a JSON Web Key Set, its location + on disk, or the means with which to fetch a key set from a remote + server. + properties: + local: + description: Local specifies a local source for the key set. + properties: + filename: + description: Filename configures a location on disk where + the JWKS can be found. If specified, the file must be present + on the disk of ALL proxies with intentions referencing this + provider. + type: string + jwks: + description: JWKS contains a base64 encoded JWKS. + type: string + type: object + remote: + description: Remote specifies how to fetch a key set from a remote + server. + properties: + cacheDuration: + description: "CacheDuration is the duration after which cached + keys should be expired. \n Default value is 5 minutes." + format: int64 + type: integer + fetchAsynchronously: + description: "FetchAsynchronously indicates that the JWKS + should be fetched when a client request arrives. Client + requests will be paused until the JWKS is fetched. If false, + the proxy listener will wait for the JWKS to be fetched + before being activated. \n Default value is false." + type: boolean + requestTimeoutMs: + description: RequestTimeoutMs is the number of milliseconds + to time out when making a request for the JWKS. + type: integer + retryPolicy: + description: "RetryPolicy defines a retry policy for fetching + JWKS. \n There is no retry by default." + properties: + numRetries: + description: "NumRetries is the number of times to retry + fetching the JWKS. The retry strategy uses jittered + exponential backoff with a base interval of 1s and max + of 10s. \n Default value is 0." + type: integer + retryPolicyBackOff: + description: "Backoff policy \n Defaults to Envoy's backoff + policy" + properties: + baseInterval: + description: "BaseInterval to be used for the next + back off computation \n The default value from envoy + is 1s" + format: int64 + type: integer + maxInterval: + description: "MaxInternal to be used to specify the + maximum interval between retries. Optional but should + be greater or equal to BaseInterval. \n Defaults + to 10 times BaseInterval" + format: int64 + type: integer + type: object + type: object + uri: + description: URI is the URI of the server to query for the + JWKS. + type: string + type: object + type: object + locations: + description: 'Locations where the JWT will be present in requests. + Envoy will check all of these locations to extract a JWT. If no + locations are specified Envoy will default to: 1. Authorization + header with Bearer schema: "Authorization: Bearer " 2. accessToken + query parameter.' + items: + description: "JWTLocation is a location where the JWT could be present + in requests. \n Only one of Header, QueryParam, or Cookie can + be specified." + properties: + cookie: + description: Cookie defines how to extract a JWT from an HTTP + request cookie. + properties: + name: + description: Name is the name of the cookie containing the + token. + type: string + type: object + header: + description: Header defines how to extract a JWT from an HTTP + request header. + properties: + forward: + description: "Forward defines whether the header with the + JWT should be forwarded after the token has been verified. + If false, the header will not be forwarded to the backend. + \n Default value is false." + type: boolean + name: + description: Name is the name of the header containing the + token. + type: string + valuePrefix: + description: 'ValuePrefix is an optional prefix that precedes + the token in the header value. For example, "Bearer " + is a standard value prefix for a header named "Authorization", + but the prefix is not part of the token itself: "Authorization: + Bearer "' + type: string + type: object + queryParam: + description: QueryParam defines how to extract a JWT from an + HTTP request query parameter. + properties: + name: + description: Name is the name of the query param containing + the token. + type: string + type: object + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index bd2ce10a25..28ddd913b5 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -66,6 +66,26 @@ rules: - get - patch - update +- apiGroups: + - consul.hashicorp.com + resources: + - jwtproviders + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - consul.hashicorp.com + resources: + - jwtproviders/status + verbs: + - get + - patch + - update - apiGroups: - consul.hashicorp.com resources: diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index 305e3bbe67..3a65e10d1b 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -50,6 +50,27 @@ webhooks: resources: - ingressgateways sideEffects: None +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v1alpha1-jwtprovider + failurePolicy: Fail + name: mutate-jwtprovider.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - jwtproviders + sideEffects: None - admissionReviewVersions: - v1beta1 - v1 diff --git a/control-plane/controllers/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go index 10af0f7716..38a02732d0 100644 --- a/control-plane/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -432,6 +432,50 @@ func TestConfigEntryControllers_createsConfigEntry(t *testing.T) { require.Equal(t, "sni", resource.Services[0].SNI) }, }, + { + kubeKind: "JWTProvider", + consulKind: capi.JWTProvider, + configEntryResource: &v1alpha1.JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwt-provider", + Namespace: kubeNS, + }, + Spec: v1alpha1.JWTProviderSpec{ + JSONWebKeySet: &v1alpha1.JSONWebKeySet{ + Local: &v1alpha1.LocalJWKS{ + Filename: "jwks.txt", + }, + }, + Issuer: "test-issuer", + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &JWTProviderController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + jwt, ok := consulEntry.(*capi.JWTProviderConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, capi.JWTProvider, jwt.Kind) + require.Equal(t, "test-jwt-provider", jwt.Name) + require.Equal(t, + &capi.JSONWebKeySet{ + Local: &capi.LocalJWKS{ + Filename: "jwks.txt", + }, + }, + jwt.JSONWebKeySet, + ) + require.Equal(t, "test-issuer", jwt.Issuer) + }, + }, } for _, c := range cases { @@ -909,6 +953,57 @@ func TestConfigEntryControllers_updatesConfigEntry(t *testing.T) { require.Equal(t, "new-sni", resource.Services[0].SNI) }, }, + + { + kubeKind: "JWTProvider", + consulKind: capi.JWTProvider, + configEntryResource: &v1alpha1.JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwt-provider", + Namespace: kubeNS, + }, + Spec: v1alpha1.JWTProviderSpec{ + JSONWebKeySet: &v1alpha1.JSONWebKeySet{ + Local: &v1alpha1.LocalJWKS{ + Filename: "jwks.txt", + }, + }, + Issuer: "test-issuer", + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &JWTProviderController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + updateF: func(resource common.ConfigEntryResource) { + jwt := resource.(*v1alpha1.JWTProvider) + jwt.Spec.Issuer = "test-updated-issuer" + jwt.Spec.Audiences = []string{"aud1"} + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + jwt, ok := consulEntry.(*capi.JWTProviderConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, capi.JWTProvider, jwt.Kind) + require.Equal(t, "test-jwt-provider", jwt.Name) + require.Equal(t, + &capi.JSONWebKeySet{ + Local: &capi.LocalJWKS{ + Filename: "jwks.txt", + }, + }, + jwt.JSONWebKeySet, + ) + require.Equal(t, "test-updated-issuer", jwt.Issuer) + require.Equal(t, []string{"aud1"}, jwt.Audiences) + }, + }, } for _, c := range cases { @@ -1310,6 +1405,37 @@ func TestConfigEntryControllers_deletesConfigEntry(t *testing.T) { } }, }, + { + kubeKind: "JWTProvider", + consulKind: capi.JWTProvider, + configEntryResourceWithDeletion: &v1alpha1.JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.Global, + Namespace: kubeNS, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + Finalizers: []string{FinalizerName}, + }, + Spec: v1alpha1.JWTProviderSpec{ + JSONWebKeySet: &v1alpha1.JSONWebKeySet{ + Local: &v1alpha1.LocalJWKS{ + Filename: "jwks.txt", + }, + }, + Issuer: "test-issuer", + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &JWTProviderController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + }, } for _, c := range cases { diff --git a/control-plane/controllers/jwtprovider_controller.go b/control-plane/controllers/jwtprovider_controller.go new file mode 100644 index 0000000000..157f4bc855 --- /dev/null +++ b/control-plane/controllers/jwtprovider_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +// JWTProviderController reconciles a JWTProvider object. +type JWTProviderController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + ConfigEntryController *ConfigEntryController +} + +//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=jwtproviders,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=jwtproviders/status,verbs=get;update;patch + +func (r *JWTProviderController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.ConfigEntryController.ReconcileEntry(ctx, r, req, &consulv1alpha1.JWTProvider{}) +} + +func (r *JWTProviderController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *JWTProviderController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *JWTProviderController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &consulv1alpha1.JWTProvider{}, r) +} diff --git a/control-plane/go.mod b/control-plane/go.mod index 9a65b3dd11..96f1d91fed 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d github.com/hashicorp/consul-server-connection-manager v0.1.2 - github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca + github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 diff --git a/control-plane/go.sum b/control-plane/go.sum index 1876d81578..3248aba347 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -262,8 +262,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= github.com/hashicorp/consul-server-connection-manager v0.1.2 h1:tNVQHUPuMbd+cMdD8kd+qkZUYpmLmrHMAV/49f4L53I= github.com/hashicorp/consul-server-connection-manager v0.1.2/go.mod h1:NzQoVi1KcxGI2SangsDue8+ZPuXZWs+6BKAKrDNyg+w= -github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca h1:5UPVYOlJg/HBEJ2q82rkkQ3ZLzeMnF5MOpGcw2kh+XU= -github.com/hashicorp/consul/api v1.10.1-0.20230512003852-bd0eb07ed3ca/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= +github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 h1:6kUTk+YBgA5X5b3gNAoI18WEK4/t75LcWSimEgmpFdg= +github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 671a15f7cd..676681baea 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -634,6 +634,15 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.SamenessGroup) return 1 } + if err = (&controllers.JWTProviderController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.JWTProvider), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.JWTProvider) + return 1 + } if err = mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) @@ -799,6 +808,12 @@ func (c *Command) Run(args []string) int { Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.SamenessGroup), ConsulMeta: consulMeta, }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-jwtprovider", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.JWTProviderWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.JWTProvider), + ConsulMeta: consulMeta, + }}) if c.flagEnableWebhookCAUpdate { err = c.updateWebhookCABundle(ctx) From 5f3f26d8d633739426247c71ac8fe51eb6a7fe97 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Wed, 31 May 2023 11:07:49 -0400 Subject: [PATCH 180/592] API Gateway tenancy tests + fixes (#2201) * Initial scaffolding * Fix up some infinite reconciliation issues and initial other bugs * overhaul * get basic e2e working again * Add resource ref validation * Fix up namespace/reference grants * fix binding * clean up logging * cleanup * Get some binder unit tests working again * log guard * Fix unit test * Fix up more binder tests * get more binder tests working * finish binder tests * fix setter test * light touches and un-bak passing tests * Remove controller test as the wiring of deployments is predominantly tests via acceptance tests * Update reference grant tests * fix linter issues * fix acceptance test linters * Fix validation tests * Fix up consul cache tests * fixing up a few more tests * Finish up translation test work * Fix last bit of tests --- .../api-gateway/api_gateway_tenancy_test.go | 427 ++ .../tests/api-gateway/api_gateway_test.go | 8 +- .../bases/api-gateway/apigateway.yaml | 17 +- .../bases/api-gateway/certificate.yaml | 11 + .../api-gateways/certificate/certificate.yaml | 11 + .../certificate/kustomization.yaml | 5 + .../cases/api-gateways/gateway/gateway.yaml | 20 + .../api-gateways/gateway/kustomization.yaml | 5 + .../api-gateways/httproute/kustomization.yaml | 5 + .../cases/api-gateways/httproute/route.yaml | 8 + .../templates/connect-inject-clusterrole.yaml | 1 + .../templates/gateway-gatewayclass.yaml | 2 +- .../api-gateway/binding/annotations.go | 10 +- .../api-gateway/binding/annotations_test.go | 15 +- control-plane/api-gateway/binding/binder.go | 290 +- .../api-gateway/binding/binder_test.go | 4130 ++++++++--------- .../reference_grant.go} | 94 +- .../reference_grant_test.go} | 113 +- .../api-gateway/binding/references.go | 135 - control-plane/api-gateway/binding/result.go | 52 +- .../api-gateway/binding/route_binding.go | 651 ++- control-plane/api-gateway/binding/setter.go | 112 +- .../api-gateway/binding/setter_test.go | 8 +- control-plane/api-gateway/binding/snapshot.go | 22 +- control-plane/api-gateway/binding/utils.go | 105 - .../api-gateway/binding/validation.go | 139 +- .../api-gateway/binding/validation_test.go | 342 +- control-plane/api-gateway/cache/consul.go | 570 +-- .../api-gateway/cache/consul_test.go | 265 +- control-plane/api-gateway/cache/gateway.go | 136 + control-plane/api-gateway/cache/kubernetes.go | 32 + .../api-gateway/cache/subscription.go | 30 + control-plane/api-gateway/common/constants.go | 8 + control-plane/api-gateway/common/diff.go | 263 ++ .../api-gateway/common/finalizers.go | 60 + .../api-gateway/{ => common}/helm_config.go | 2 +- control-plane/api-gateway/common/helpers.go | 218 + .../utils_test.go => common/helpers_test.go} | 106 +- .../api-gateway/{ => common}/labels.go | 2 +- control-plane/api-gateway/common/reference.go | 184 + control-plane/api-gateway/common/resources.go | 574 +++ control-plane/api-gateway/common/secrets.go | 68 + .../api-gateway/common/translation.go | 363 ++ .../translation_test.go} | 948 ++-- .../controllers/gateway_controller.go | 930 ++-- .../controllers/gateway_controller_test.go | 464 -- .../api-gateway/controllers/index.go | 27 +- .../api-gateway/gatekeeper/dataplane.go | 6 +- .../api-gateway/gatekeeper/deployment.go | 14 +- .../api-gateway/gatekeeper/gatekeeper.go | 6 +- .../api-gateway/gatekeeper/gatekeeper_test.go | 128 +- control-plane/api-gateway/gatekeeper/init.go | 4 +- control-plane/api-gateway/gatekeeper/role.go | 6 +- .../api-gateway/gatekeeper/service.go | 13 +- .../api-gateway/gatekeeper/serviceaccount.go | 6 +- .../translation/config_entry_translation.go | 516 -- .../translation/k8s_cache_translation.go | 134 - .../translation/k8s_cache_translation_test.go | 460 -- .../connect-inject/constants/constants.go | 3 + .../subcommand/inject-connect/command.go | 17 +- 60 files changed, 6448 insertions(+), 6853 deletions(-) create mode 100644 acceptance/tests/api-gateway/api_gateway_tenancy_test.go create mode 100644 acceptance/tests/fixtures/bases/api-gateway/certificate.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/certificate/certificate.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/certificate/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/gateway/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/httproute/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/httproute/route.yaml rename control-plane/api-gateway/{controllers/reference_validator.go => binding/reference_grant.go} (58%) rename control-plane/api-gateway/{controllers/reference_validator_test.go => binding/reference_grant_test.go} (79%) delete mode 100644 control-plane/api-gateway/binding/references.go delete mode 100644 control-plane/api-gateway/binding/utils.go create mode 100644 control-plane/api-gateway/cache/gateway.go create mode 100644 control-plane/api-gateway/cache/kubernetes.go create mode 100644 control-plane/api-gateway/cache/subscription.go create mode 100644 control-plane/api-gateway/common/constants.go create mode 100644 control-plane/api-gateway/common/diff.go create mode 100644 control-plane/api-gateway/common/finalizers.go rename control-plane/api-gateway/{ => common}/helm_config.go (98%) create mode 100644 control-plane/api-gateway/common/helpers.go rename control-plane/api-gateway/{binding/utils_test.go => common/helpers_test.go} (56%) rename control-plane/api-gateway/{ => common}/labels.go (97%) create mode 100644 control-plane/api-gateway/common/reference.go create mode 100644 control-plane/api-gateway/common/resources.go create mode 100644 control-plane/api-gateway/common/secrets.go create mode 100644 control-plane/api-gateway/common/translation.go rename control-plane/api-gateway/{translation/config_entry_translation_test.go => common/translation_test.go} (51%) delete mode 100644 control-plane/api-gateway/controllers/gateway_controller_test.go delete mode 100644 control-plane/api-gateway/translation/config_entry_translation.go delete mode 100644 control-plane/api-gateway/translation/k8s_cache_translation.go delete mode 100644 control-plane/api-gateway/translation/k8s_cache_translation_test.go diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go new file mode 100644 index 0000000000..3455b69f58 --- /dev/null +++ b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go @@ -0,0 +1,427 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package apigateway + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/pem" + "fmt" + "math/big" + "path" + "strconv" + "testing" + "time" + + terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/config" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +var ( + gatewayGroup = gwv1beta1.Group(gwv1beta1.GroupVersion.Group) + consulGroup = gwv1beta1.Group(v1alpha1.GroupVersion.Group) + gatewayKind = gwv1beta1.Kind("Gateway") + serviceKind = gwv1beta1.Kind("Service") + secretKind = gwv1beta1.Kind("Secret") + meshServiceKind = gwv1beta1.Kind("MeshService") + httpRouteKind = gwv1beta1.Kind("HTTPRoute") + tcpRouteKind = gwv1beta1.Kind("TCPRoute") +) + +func TestAPIGateway_Tenancy(t *testing.T) { + cases := []struct { + secure bool + namespaceMirroring bool + }{ + { + secure: false, + namespaceMirroring: false, + }, + { + secure: true, + namespaceMirroring: false, + }, + { + secure: false, + namespaceMirroring: true, + }, + { + secure: true, + namespaceMirroring: true, + }, + } + for _, c := range cases { + name := fmt.Sprintf("secure: %t, namespaces: %t", c.secure, c.namespaceMirroring) + t.Run(name, func(t *testing.T) { + cfg := suite.Config() + + if !cfg.EnableEnterprise && c.namespaceMirroring { + t.Skipf("skipping this test because -enable-enterprise is not set") + } + + ctx := suite.Environment().DefaultContext(t) + + helmValues := map[string]string{ + "global.enableConsulNamespaces": strconv.FormatBool(c.namespaceMirroring), + "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), + "global.tls.enabled": strconv.FormatBool(c.secure), + "global.logLevel": "trace", + "connectInject.enabled": "true", + "connectInject.consulNamespaces.mirroringK8S": strconv.FormatBool(c.namespaceMirroring), + } + + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + + consulCluster.Create(t) + + serviceNamespace, serviceK8SOptions := createNamespace(t, ctx, cfg) + certificateNamespace, certificateK8SOptions := createNamespace(t, ctx, cfg) + gatewayNamespace, gatewayK8SOptions := createNamespace(t, ctx, cfg) + routeNamespace, routeK8SOptions := createNamespace(t, ctx, cfg) + + logger.Logf(t, "creating target server in %s namespace", serviceNamespace) + k8s.DeployKustomize(t, serviceK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + logger.Logf(t, "creating certificate resources in %s namespace", certificateNamespace) + applyFixture(t, cfg, certificateK8SOptions, "cases/api-gateways/certificate") + + logger.Logf(t, "creating gateway in %s namespace", gatewayNamespace) + applyFixture(t, cfg, gatewayK8SOptions, "cases/api-gateways/gateway") + + logger.Logf(t, "creating route resources in %s namespace", routeNamespace) + applyFixture(t, cfg, routeK8SOptions, "cases/api-gateways/httproute") + + // patch certificate with data + logger.Log(t, "patching certificate with generated data") + certificate := generateCertificate(t, nil, "gateway.test.local") + k8s.RunKubectl(t, certificateK8SOptions, "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") + + // patch the resources to reference each other + logger.Log(t, "patching gateway to certificate") + k8s.RunKubectl(t, gatewayK8SOptions, "patch", "gateway", "gateway", "-p", fmt.Sprintf(`{"spec":{"listeners":[{"protocol":"HTTPS","port":8082,"name":"https","tls":{"certificateRefs":[{"name":"certificate","namespace":"%s"}]},"allowedRoutes":{"namespaces":{"from":"All"}}}]}}`, certificateNamespace), "--type=merge") + + logger.Log(t, "patching route to target server") + k8s.RunKubectl(t, routeK8SOptions, "patch", "httproute", "route", "-p", fmt.Sprintf(`{"spec":{"rules":[{"backendRefs":[{"name":"static-server","namespace":"%s","port":80}]}]}}`, serviceNamespace), "--type=merge") + + logger.Log(t, "patching route to gateway") + k8s.RunKubectl(t, routeK8SOptions, "patch", "httproute", "route", "-p", fmt.Sprintf(`{"spec":{"parentRefs":[{"name":"gateway","namespace":"%s"}]}}`, gatewayNamespace), "--type=merge") + + // Grab a kubernetes and consul client so that we can verify binding + // behavior prior to issuing requests through the gateway. + k8sClient := ctx.ControllerRuntimeClient(t) + consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) + + retryCheck(t, 60, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: gatewayNamespace}, &gateway) + require.NoError(r, err) + + // check our statuses + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Conditions, falseCondition("Programmed", "Pending")) + // we expect a sync error here since dropping the listener means the gateway is now invalid + checkStatusCondition(r, gateway.Status.Conditions, falseCondition("Synced", "SyncError")) + + require.Len(r, gateway.Status.Listeners, 1) + require.EqualValues(r, 0, gateway.Status.Listeners[0].AttachedRoutes) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("ResolvedRefs", "RefNotPermitted")) + }) + + retryCheck(t, 10, func(r *retry.R) { + // since the sync operation should fail above, check that we don't have the entry in Consul. + _, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", &api.QueryOptions{ + Namespace: namespaceForConsul(c.namespaceMirroring, gatewayNamespace), + }) + require.Error(r, err) + require.EqualError(r, err, `Unexpected response code: 404 (Config entry not found for "api-gateway" / "gateway")`) + }) + + // route failure + retryCheck(t, 10, func(r *retry.R) { + var httproute gwv1beta1.HTTPRoute + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "route", Namespace: routeNamespace}, &httproute) + require.NoError(r, err) + + require.Len(r, httproute.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httproute.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httproute.Status.Parents[0].ParentRef.Name) + require.NotNil(r, httproute.Status.Parents[0].ParentRef.Namespace) + require.EqualValues(r, gatewayNamespace, *httproute.Status.Parents[0].ParentRef.Namespace) + checkStatusCondition(r, httproute.Status.Parents[0].Conditions, falseCondition("Accepted", "RefNotPermitted")) + checkStatusCondition(r, httproute.Status.Parents[0].Conditions, falseCondition("ResolvedRefs", "RefNotPermitted")) + // the route itself actually gets synced to Consul + checkStatusCondition(r, httproute.Status.Parents[0].Conditions, trueCondition("Synced", "Synced")) + }) + + retryCheck(t, 10, func(r *retry.R) { + // since we're not bound, check to make sure that the route doesn't target the gateway in Consul. + entry, _, err := consulClient.ConfigEntries().Get(api.HTTPRoute, "route", &api.QueryOptions{ + Namespace: namespaceForConsul(c.namespaceMirroring, routeNamespace), + }) + require.NoError(r, err) + route := entry.(*api.HTTPRouteConfigEntry) + + require.EqualValues(r, "route", route.Meta["k8s-name"]) + require.EqualValues(r, routeNamespace, route.Meta["k8s-namespace"]) + require.Len(r, route.Parents, 0) + }) + + retryCheck(t, 10, func(r *retry.R) { + // we only sync validly referenced certificates over, so check to make sure it is not created. + _, _, err := consulClient.ConfigEntries().Get(api.InlineCertificate, "certificate", &api.QueryOptions{ + Namespace: namespaceForConsul(c.namespaceMirroring, certificateNamespace), + }) + require.Error(r, err) + require.EqualError(r, err, `Unexpected response code: 404 (Config entry not found for "inline-certificate" / "certificate")`) + }) + + // now create reference grants + createReferenceGrant(t, k8sClient, "gateway-certificate", gatewayNamespace, certificateNamespace) + createReferenceGrant(t, k8sClient, "route-gateway", routeNamespace, gatewayNamespace) + createReferenceGrant(t, k8sClient, "route-service", routeNamespace, serviceNamespace) + + // gateway updated with references allowed + retryCheck(t, 10, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: gatewayNamespace}, &gateway) + require.NoError(r, err) + + // check our statuses + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Programmed", "Programmed")) + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Synced", "Synced")) + require.Len(r, gateway.Status.Listeners, 1) + require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + }) + + // check the Consul gateway is updated, with the listener. + retryCheck(t, 10, func(r *retry.R) { + entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", &api.QueryOptions{ + Namespace: namespaceForConsul(c.namespaceMirroring, gatewayNamespace), + }) + require.NoError(r, err) + gateway := entry.(*api.APIGatewayConfigEntry) + + require.EqualValues(r, "gateway", gateway.Meta["k8s-name"]) + require.EqualValues(r, gatewayNamespace, gateway.Meta["k8s-namespace"]) + require.Len(r, gateway.Listeners, 1) + checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) + checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("ResolvedRefs", "ResolvedRefs")) + }) + + // route updated with gateway and services allowed + retryCheck(t, 10, func(r *retry.R) { + var httproute gwv1beta1.HTTPRoute + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "route", Namespace: routeNamespace}, &httproute) + require.NoError(r, err) + + require.Len(r, httproute.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httproute.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httproute.Status.Parents[0].ParentRef.Name) + require.NotNil(r, httproute.Status.Parents[0].ParentRef.Namespace) + require.EqualValues(r, gatewayNamespace, *httproute.Status.Parents[0].ParentRef.Namespace) + checkStatusCondition(r, httproute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, httproute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + }) + + // now check to make sure that the route is updated and valid + retryCheck(t, 10, func(r *retry.R) { + // since we're not bound, check to make sure that the route doesn't target the gateway in Consul. + entry, _, err := consulClient.ConfigEntries().Get(api.HTTPRoute, "route", &api.QueryOptions{ + Namespace: namespaceForConsul(c.namespaceMirroring, routeNamespace), + }) + require.NoError(r, err) + route := entry.(*api.HTTPRouteConfigEntry) + + require.EqualValues(r, "route", route.Meta["k8s-name"]) + require.EqualValues(r, routeNamespace, route.Meta["k8s-namespace"]) + require.Len(r, route.Parents, 1) + }) + + // and check to make sure that the certificate exists + retryCheck(t, 10, func(r *retry.R) { + entry, _, err := consulClient.ConfigEntries().Get(api.InlineCertificate, "certificate", &api.QueryOptions{ + Namespace: namespaceForConsul(c.namespaceMirroring, certificateNamespace), + }) + require.NoError(r, err) + certificate := entry.(*api.InlineCertificateConfigEntry) + + require.EqualValues(r, "certificate", certificate.Meta["k8s-name"]) + require.EqualValues(r, certificateNamespace, certificate.Meta["k8s-namespace"]) + }) + }) + } +} + +func applyFixture(t *testing.T, cfg *config.TestConfig, k8sOptions *terratestk8s.KubectlOptions, fixture string) { + t.Helper() + + out, err := k8s.RunKubectlAndGetOutputE(t, k8sOptions, "apply", "-k", path.Join("../fixtures", fixture)) + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.RunKubectlAndGetOutputE(t, k8sOptions, "delete", "-k", path.Join("../fixtures", fixture)) + }) +} + +func createNamespace(t *testing.T, ctx environment.TestContext, cfg *config.TestConfig) (string, *terratestk8s.KubectlOptions) { + t.Helper() + + namespace := helpers.RandomName() + + logger.Logf(t, "creating Kubernetes namespace %s", namespace) + k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", namespace) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", namespace) + }) + + return namespace, &terratestk8s.KubectlOptions{ + ContextName: ctx.KubectlOptions(t).ContextName, + ConfigPath: ctx.KubectlOptions(t).ConfigPath, + Namespace: namespace, + } +} + +type certificateInfo struct { + Cert *x509.Certificate + PrivateKey *rsa.PrivateKey + CertPEM []byte + PrivateKeyPEM []byte +} + +func generateCertificate(t *testing.T, ca *certificateInfo, commonName string) *certificateInfo { + t.Helper() + + bits := 1024 + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + require.NoError(t, err) + + usage := x509.KeyUsageDigitalSignature + if ca == nil { + usage = x509.KeyUsageCertSign + } + + expiration := time.Now().AddDate(10, 0, 0) + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Testing, INC."}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Fake Street"}, + PostalCode: []string{"11111"}, + CommonName: commonName, + }, + IsCA: ca == nil, + NotBefore: time.Now().Add(-10 * time.Minute), + NotAfter: expiration, + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: usage, + BasicConstraintsValid: true, + } + caCert := cert + if ca != nil { + caCert = ca.Cert + } + caPrivateKey := privateKey + if ca != nil { + caPrivateKey = ca.PrivateKey + } + data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) + require.NoError(t, err) + + certBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: data, + }) + + privateKeyBytes := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + + return &certificateInfo{ + Cert: cert, + CertPEM: certBytes, + PrivateKey: privateKey, + PrivateKeyPEM: privateKeyBytes, + } +} + +func retryCheck(t *testing.T, count int, fn func(r *retry.R)) { + t.Helper() + + counter := &retry.Counter{Count: count, Wait: 2 * time.Second} + retry.RunWith(counter, t, fn) +} + +func createReferenceGrant(t *testing.T, client client.Client, name, from, to string) { + t.Helper() + + // we just create a reference grant for all combinations in the given namespaces + + require.NoError(t, client.Create(context.Background(), &gwv1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: to, + }, + Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{{ + Group: gatewayGroup, + Kind: gatewayKind, + Namespace: gwv1beta1.Namespace(from), + }, { + Group: gatewayGroup, + Kind: httpRouteKind, + Namespace: gwv1beta1.Namespace(from), + }, { + Group: gatewayGroup, + Kind: tcpRouteKind, + Namespace: gwv1beta1.Namespace(from), + }}, + To: []gwv1beta1.ReferenceGrantTo{{ + Group: gatewayGroup, + Kind: gatewayKind, + }, { + Kind: serviceKind, + }, { + Group: consulGroup, + Kind: meshServiceKind, + }, { + Kind: secretKind, + }}, + }, + })) +} + +func namespaceForConsul(namespaceMirroringEnabled bool, namespace string) string { + if namespaceMirroringEnabled { + return namespace + } + return "" +} diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index 251d966803..006f1456d3 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -31,7 +31,7 @@ const ( ) // Test that api gateway basic functionality works in a default installation and a secure installation. -func TestAPIGateway(t *testing.T) { +func TestAPIGateway_Basic(t *testing.T) { cases := []struct { secure bool }{ @@ -110,7 +110,7 @@ func TestAPIGateway(t *testing.T) { // check our statuses checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) - require.Len(r, gateway.Status.Listeners, 2) + require.Len(r, gateway.Status.Listeners, 3) require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) @@ -119,6 +119,10 @@ func TestAPIGateway(t *testing.T) { checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + require.EqualValues(r, 1, gateway.Status.Listeners[2].AttachedRoutes) + checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, falseCondition("Conflicted", "NoConflicts")) + checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, falseCondition("ResolvedRefs", "InvalidCertificateRef")) // check that we have an address to use require.Len(r, gateway.Status.Addresses, 1) diff --git a/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml b/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml index de7ac7b5de..f0e4eddc03 100644 --- a/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml +++ b/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml @@ -11,6 +11,21 @@ spec: - protocol: HTTP port: 8080 name: http + allowedRoutes: + namespaces: + from: "All" - protocol: TCP port: 8081 - name: tcp \ No newline at end of file + name: tcp + allowedRoutes: + namespaces: + from: "All" + - protocol: HTTPS + port: 8082 + name: https + tls: + certificateRefs: + - name: "certificate" + allowedRoutes: + namespaces: + from: "All" diff --git a/acceptance/tests/fixtures/bases/api-gateway/certificate.yaml b/acceptance/tests/fixtures/bases/api-gateway/certificate.yaml new file mode 100644 index 0000000000..d35dc559e2 --- /dev/null +++ b/acceptance/tests/fixtures/bases/api-gateway/certificate.yaml @@ -0,0 +1,11 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: Secret +metadata: + name: certificate +type: kubernetes.io/tls +data: + tls.crt: "" + tls.key: "" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/certificate/certificate.yaml b/acceptance/tests/fixtures/cases/api-gateways/certificate/certificate.yaml new file mode 100644 index 0000000000..d35dc559e2 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/certificate/certificate.yaml @@ -0,0 +1,11 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: Secret +metadata: + name: certificate +type: kubernetes.io/tls +data: + tls.crt: "" + tls.key: "" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/certificate/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/certificate/kustomization.yaml new file mode 100644 index 0000000000..42b7526335 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/certificate/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - certificate.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml new file mode 100644 index 0000000000..14c39978b7 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml @@ -0,0 +1,20 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: gateway +spec: + gatewayClassName: consul-api-gateway + listeners: + - protocol: HTTPS + port: 8080 + name: https + tls: + certificateRefs: + - name: "certificate" + namespace: "default" + allowedRoutes: + namespaces: + from: "All" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/gateway/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/gateway/kustomization.yaml new file mode 100644 index 0000000000..8efac31693 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/gateway/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - gateway.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/httproute/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/httproute/kustomization.yaml new file mode 100644 index 0000000000..7a6713835c --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/httproute/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - route.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/httproute/route.yaml b/acceptance/tests/fixtures/cases/api-gateways/httproute/route.yaml new file mode 100644 index 0000000000..9f7f66b433 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/httproute/route.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: route +spec: {} \ No newline at end of file diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index d4b2464dec..e476949997 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -71,6 +71,7 @@ rules: - list - watch - delete + - update - apiGroups: [ "rbac.authorization.k8s.io" ] resources: [ "roles" ] verbs: diff --git a/charts/consul/templates/gateway-gatewayclass.yaml b/charts/consul/templates/gateway-gatewayclass.yaml index 612627fbda..a6856620d2 100644 --- a/charts/consul/templates/gateway-gatewayclass.yaml +++ b/charts/consul/templates/gateway-gatewayclass.yaml @@ -10,7 +10,7 @@ metadata: release: {{ .Release.Name }} component: api-gateway-controller spec: - controllerName: consul.hashicorp.com/consul-api-gateway-controller + controllerName: "hashicorp.com/consul-api-gateway-controller" parametersRef: group: consul.hashicorp.com kind: GatewayClassConfig diff --git a/control-plane/api-gateway/binding/annotations.go b/control-plane/api-gateway/binding/annotations.go index 2cb8f41792..2bd4d0db15 100644 --- a/control-plane/api-gateway/binding/annotations.go +++ b/control-plane/api-gateway/binding/annotations.go @@ -8,14 +8,10 @@ import ( 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" ) -const ( - group = "api-gateway.consul.hashicorp.com" - annotationConfigKey = "api-gateway.consul.hashicorp.com/config" -) - func serializeGatewayClassConfig(gw *gwv1beta1.Gateway, gwcc *v1alpha1.GatewayClassConfig) (*v1alpha1.GatewayClassConfig, bool) { if gwcc == nil { return nil, false @@ -25,7 +21,7 @@ func serializeGatewayClassConfig(gw *gwv1beta1.Gateway, gwcc *v1alpha1.GatewayCl gw.Annotations = make(map[string]string) } - if annotatedConfig, ok := gw.Annotations[annotationConfigKey]; ok { + if annotatedConfig, ok := gw.Annotations[common.AnnotationGatewayClassConfig]; ok { var config v1alpha1.GatewayClassConfig if err := json.Unmarshal([]byte(annotatedConfig), &config.Spec); err == nil { // if we can unmarshal the gateway, return it @@ -36,6 +32,6 @@ func serializeGatewayClassConfig(gw *gwv1beta1.Gateway, gwcc *v1alpha1.GatewayCl // otherwise if we failed to unmarshal or there was no annotation, marshal it onto // the gateway marshaled, _ := json.Marshal(gwcc.Spec) - gw.Annotations[annotationConfigKey] = string(marshaled) + gw.Annotations[common.AnnotationGatewayClassConfig] = string(marshaled) return gwcc, true } diff --git a/control-plane/api-gateway/binding/annotations_test.go b/control-plane/api-gateway/binding/annotations_test.go index 1886ba80f5..edb44ccfb4 100644 --- a/control-plane/api-gateway/binding/annotations_test.go +++ b/control-plane/api-gateway/binding/annotations_test.go @@ -14,6 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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" ) @@ -45,7 +46,7 @@ func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { Name: "the config", }, Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: pointerTo(corev1.ServiceType("serviceType")), + ServiceType: common.PointerTo(corev1.ServiceType("serviceType")), NodeSelector: map[string]string{ "selector": "of node", }, @@ -83,7 +84,7 @@ func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { Name: "the config", }, Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: pointerTo(corev1.ServiceType("serviceType")), + ServiceType: common.PointerTo(corev1.ServiceType("serviceType")), NodeSelector: map[string]string{ "selector": "of node", }, @@ -111,7 +112,7 @@ func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "my-gw", Annotations: map[string]string{ - annotationConfigKey: `{"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`, + common.AnnotationGatewayClassConfig: `{"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`, }, }, Spec: gwv1beta1.GatewaySpec{}, @@ -123,7 +124,7 @@ func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { Name: "the config", }, Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: pointerTo(corev1.ServiceType("serviceType")), + ServiceType: common.PointerTo(corev1.ServiceType("serviceType")), NodeSelector: map[string]string{ "selector": "of node", }, @@ -152,7 +153,7 @@ func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { Name: "my-gw", Annotations: map[string]string{ // we remove the opening brace to make unmarshalling fail - annotationConfigKey: `"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`, + common.AnnotationGatewayClassConfig: `"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`, }, }, Spec: gwv1beta1.GatewaySpec{}, @@ -164,7 +165,7 @@ func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { Name: "the config", }, Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: pointerTo(corev1.ServiceType("serviceType")), + ServiceType: common.PointerTo(corev1.ServiceType("serviceType")), NodeSelector: map[string]string{ "selector": "of node", }, @@ -195,7 +196,7 @@ func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { } var config v1alpha1.GatewayClassConfig - err := json.Unmarshal([]byte(tt.args.gw.Annotations[annotationConfigKey]), &config.Spec) + err := json.Unmarshal([]byte(tt.args.gw.Annotations[common.AnnotationGatewayClassConfig]), &config.Spec) require.NoError(t, err) if diff := cmp.Diff(config.Spec, tt.args.gwcc.Spec); diff != "" { diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index 00e928db0a..391c083724 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -4,8 +4,9 @@ package binding import ( - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" + mapset "github.com/deckarep/golang-set" + "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" @@ -15,47 +16,21 @@ import ( gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -const ( - // gatewayFinalizer is the finalizer we add to any gateway object. - gatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" - - // namespaceNameLabel represents that label added automatically to namespaces in newer Kubernetes clusters. - namespaceNameLabel = "kubernetes.io/metadata.name" -) - -var ( - // constants extracted for ease of use. - kindGateway = "Gateway" - kindSecret = "Secret" - betaGroup = gwv1beta1.GroupVersion.Group - - // the list of kinds we can support by listener protocol. - supportedKindsForProtocol = map[gwv1beta1.ProtocolType][]gwv1beta1.RouteGroupKind{ - gwv1beta1.HTTPProtocolType: {{ - Group: (*gwv1beta1.Group)(&gwv1beta1.GroupVersion.Group), - Kind: "HTTPRoute", - }}, - gwv1beta1.HTTPSProtocolType: {{ - Group: (*gwv1beta1.Group)(&gwv1beta1.GroupVersion.Group), - Kind: "HTTPRoute", - }}, - gwv1beta1.TCPProtocolType: {{ - Group: (*gwv1alpha2.Group)(&gwv1alpha2.GroupVersion.Group), - Kind: "TCPRoute", - }}, - } -) - // BinderConfig configures a binder instance with all of the information // that it needs to know to generate a snapshot of bound state. type BinderConfig struct { + // Logger for any internal logs + Logger logr.Logger // Translator instance initialized with proper name/namespace translation // configuration from helm. - Translator translation.K8sToConsulTranslator + Translator common.ResourceTranslator // ControllerName is the name of the controller used in determining which // gateways we control, also leveraged for setting route statuses. ControllerName string + // Namespaces is a map of all namespaces in Kubernetes indexed by their names for looking up labels + // for AllowedRoutes matching purposes. + Namespaces map[string]corev1.Namespace // GatewayClassConfig is the configuration corresponding to the given // GatewayClass -- if it is nil we should treat the gateway as deleted // since the gateway is now pointing to an invalid gateway class @@ -73,13 +48,9 @@ type BinderConfig struct { HTTPRoutes []gwv1beta1.HTTPRoute // TCPRoutes is a list of TCPRoute objects that ought to be bound to the Gateway. TCPRoutes []gwv1alpha2.TCPRoute - // Secrets is a list of Secret objects that a Gateway references. - Secrets []corev1.Secret - // Pods are any pods that are part of the Gateway deployment + // Pods are any pods that are part of the Gateway deployment. Pods []corev1.Pod - // MeshServices are all the MeshService objects that can be used for service lookup - MeshServices []v1alpha1.MeshService - // Service is the service associated with a Gateway deployment + // Service is the deployed service associated with the Gateway deployment. Service *corev1.Service // TODO: Do we need to pass in Routes that have references to a Gateway in their statuses @@ -95,37 +66,35 @@ type BinderConfig struct { ConsulTCPRoutes []api.TCPRouteConfigEntry // ConsulInlineCertificates is a list of certificates that have been created in Consul. ConsulInlineCertificates []api.InlineCertificateConfigEntry - // ConnectInjectedServices is a list of all services that have been injected by our connect-injector - // and that we can, therefore reference on the mesh. - ConnectInjectedServices []api.CatalogService - // GatewayServices are the consul services for a given gateway - GatewayServices []api.CatalogService + // GatewayServices are the services associated with the Gateway + ConsulGatewayServices []api.CatalogService - // Namespaces is a map of all namespaces in Kubernetes indexed by their names for looking up labels - // for AllowedRoutes matching purposes. - Namespaces map[string]corev1.Namespace - // ControlledGateways is a map of all Gateway objects that we currently should be interested in. This - // is used to determine whether we should garbage collect Certificate or Route objects when they become - // disassociated with a particular Gateway. - ControlledGateways map[types.NamespacedName]gwv1beta1.Gateway + // Resources is a map containing all service targets to verify + // against the routing backends. + Resources *common.ResourceMap } // Binder is used for generating a Snapshot of all operations that should occur both // in Kubernetes and Consul as a result of binding routes to a Gateway. type Binder struct { - statusSetter *setter - config BinderConfig + statusSetter *setter + key types.NamespacedName + nonNormalizedConsulKey api.ResourceReference + normalizedConsulKey api.ResourceReference + config BinderConfig } // NewBinder creates a Binder object with the given configuration. func NewBinder(config BinderConfig) *Binder { - return &Binder{config: config, statusSetter: newSetter(config.ControllerName)} -} - -// gatewayRef returns a Consul-based reference for the given Kubernetes gateway to -// be used for marking a deletion that is needed in Consul. -func (b *Binder) gatewayRef() api.ResourceReference { - return b.config.Translator.ReferenceForGateway(&b.config.Gateway) + id := client.ObjectKeyFromObject(&config.Gateway) + + return &Binder{ + config: config, + statusSetter: newSetter(config.ControllerName), + key: id, + nonNormalizedConsulKey: config.Translator.NonNormalizedConfigEntryReference(api.APIGateway, id), + normalizedConsulKey: config.Translator.ConfigEntryReference(api.APIGateway, id), + } } // isGatewayDeleted returns whether we should treat the given gateway as a deleted object. @@ -139,32 +108,43 @@ func (b *Binder) isGatewayDeleted() bool { // Snapshot generates a snapshot of operations that need to occur in Kubernetes and Consul // in order for a Gateway to be reconciled. -func (b *Binder) Snapshot() Snapshot { +func (b *Binder) Snapshot() *Snapshot { // at this point we assume all tcp routes and http routes // actually reference this gateway - tracker := b.references() - meshServiceMap := meshServiceMap(b.config.MeshServices) - serviceMap := serviceMap(b.config.ConnectInjectedServices) - seenRoutes := map[api.ResourceReference]struct{}{} - snapshot := Snapshot{} - gwcc := b.config.GatewayClassConfig + snapshot := NewSnapshot() + + registrationPods := []corev1.Pod{} + // filter out any pod that is being deleted + for _, pod := range b.config.Pods { + if !isDeleted(&pod) { + registrationPods = append(registrationPods, pod) + } + } + + gatewayClassConfig := b.config.GatewayClassConfig isGatewayDeleted := b.isGatewayDeleted() + + var gatewayValidation gatewayValidationResult + var listenerValidation listenerValidationResults + if !isGatewayDeleted { var updated bool - gwcc, updated = serializeGatewayClassConfig(&b.config.Gateway, gwcc) + + gatewayClassConfig, updated = serializeGatewayClassConfig(&b.config.Gateway, gatewayClassConfig) // we don't have a deletion but if we add a finalizer for the gateway, then just add it and return // otherwise try and resolve as much as possible - if ensureFinalizer(&b.config.Gateway) || updated { + if common.EnsureFinalizer(&b.config.Gateway) || updated { // if we've added the finalizer or serialized the class config, then update - snapshot.Kubernetes.Updates = append(snapshot.Kubernetes.Updates, &b.config.Gateway) + snapshot.Kubernetes.Updates.Add(&b.config.Gateway) return snapshot } - } - httpRouteBinder := b.newHTTPRouteBinder(tracker, serviceMap, meshServiceMap) - tcpRouteBinder := b.newTCPRouteBinder(tracker, serviceMap, meshServiceMap) + // calculate the status for the gateway + gatewayValidation = validateGateway(b.config.Gateway, registrationPods, b.config.ConsulGateway) + listenerValidation = validateListeners(b.config.Gateway, b.config.Gateway.Spec.Listeners, b.config.Resources) + } // used for tracking how many routes have successfully bound to which listeners // on a gateway for reporting the number of bound routes in a gateway listener's @@ -174,76 +154,54 @@ func (b *Binder) Snapshot() Snapshot { // attempt to bind all routes for _, r := range b.config.HTTPRoutes { - snapshot = httpRouteBinder.bind(pointerTo(r), boundCounts, seenRoutes, snapshot) + b.bindRoute(common.PointerTo(r), boundCounts, snapshot) } for _, r := range b.config.TCPRoutes { - snapshot = tcpRouteBinder.bind(pointerTo(r), boundCounts, seenRoutes, snapshot) + b.bindRoute(common.PointerTo(r), boundCounts, snapshot) } - // now cleanup any routes that we haven't already processed - - for _, r := range b.config.ConsulHTTPRoutes { - snapshot = b.cleanHTTPRoute(pointerTo(r), seenRoutes, snapshot) - } - - for _, r := range b.config.ConsulTCPRoutes { - snapshot = b.cleanTCPRoute(pointerTo(r), seenRoutes, snapshot) - } - - // process certificates - - seenCerts := make(map[types.NamespacedName]api.ResourceReference) - for _, secret := range b.config.Secrets { - if isGatewayDeleted { - // we bypass the secret creation since we want to be able to GC if necessary - continue + // process secrets + gatewaySecrets := secretsForGateway(b.config.Gateway, b.config.Resources) + certs := mapset.NewSet() + if !isGatewayDeleted { + // we only do this if the gateway isn't going to be deleted so that the + // resources can get GC'd + for secret := range gatewaySecrets.Iter() { + // ignore the error if the certificate cannot be processed and just don't add it into the final + // sync set + if err := b.config.Resources.TranslateInlineCertificate(secret.(types.NamespacedName)); err != nil { + b.config.Logger.Error(err, "error parsing referenced secret, ignoring") + continue + } + certs.Add(secret) } + } - certificate := b.config.Translator.SecretToInlineCertificate(secret) - certificateRef := translation.EntryToReference(&certificate) + // now cleanup any routes or certificates that we haven't already processed - // mark the certificate as processed - seenCerts[objectToMeta(&secret)] = certificateRef - // add the certificate to the set of upsert operations needed in Consul - snapshot.Consul.Updates = append(snapshot.Consul.Updates, &certificate) - } + snapshot.Consul.Deletions = b.config.Resources.ResourcesToGC(b.key) + snapshot.Consul.Updates = b.config.Resources.Mutations() - // clean up any inline certs that are now stale and can be GC'd - for _, cert := range b.config.ConsulInlineCertificates { - certRef := translation.EntryToNamespacedName(&cert) - if _, ok := seenCerts[certRef]; !ok { - // check to see if nothing is now referencing the certificate - if tracker.canGCSecret(certRef) { - ref := translation.EntryToReference(&cert) - // we can GC this now since it's not referenced by any Gateway - snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, ref) - } - } - } + // finally, handle the gateway itself // we only want to upsert the gateway into Consul or update its status // if the gateway hasn't been marked for deletion if !isGatewayDeleted { - snapshot.GatewayClassConfig = gwcc + snapshot.GatewayClassConfig = gatewayClassConfig snapshot.UpsertGatewayDeployment = true - entry := b.config.Translator.GatewayToAPIGateway(b.config.Gateway, seenCerts) - snapshot.Consul.Updates = append(snapshot.Consul.Updates, &entry) - - registrationPods := []corev1.Pod{} - // filter out any pod that is being deleted - for _, pod := range b.config.Pods { - if !isDeleted(&pod) { - registrationPods = append(registrationPods, pod) - } - } + entry := b.config.Translator.ToAPIGateway(b.config.Gateway, b.config.Resources) + snapshot.Consul.Updates = append(snapshot.Consul.Updates, &common.ConsulUpdateOperation{ + Entry: entry, + OnUpdate: b.handleGatewaySyncStatus(snapshot, &b.config.Gateway), + }) registrations := registrationsForPods(entry.Namespace, b.config.Gateway, registrationPods) snapshot.Consul.Registrations = registrations // deregister any not explicitly registered service - for _, service := range b.config.GatewayServices { + for _, service := range b.config.ConsulGatewayServices { found := false for _, registration := range registrations { if service.ServiceID == registration.Service.ID { @@ -262,8 +220,6 @@ func (b *Binder) Snapshot() Snapshot { // calculate the status for the gateway var status gwv1beta1.GatewayStatus - gatewayValidation := validateGateway(b.config.Gateway, registrationPods, b.config.ConsulGateway) - listenerValidation := validateListeners(b.config.Gateway.Namespace, b.config.Gateway.Spec.Listeners, b.config.Secrets) for i, listener := range b.config.Gateway.Spec.Listeners { status.Listeners = append(status.Listeners, gwv1beta1.ListenerStatus{ Name: listener.Name, @@ -272,23 +228,25 @@ func (b *Binder) Snapshot() Snapshot { Conditions: listenerValidation.Conditions(b.config.Gateway.Generation, i), }) } - status.Conditions = gatewayValidation.Conditions(b.config.Gateway.Generation, listenerValidation.Invalid()) + status.Conditions = b.config.Gateway.Status.Conditions + + // we do this loop to not accidentally override any additional statuses that + // have been set anywhere outside of validation. + for _, condition := range gatewayValidation.Conditions(b.config.Gateway.Generation, listenerValidation.Invalid()) { + status.Conditions, _ = setCondition(status.Conditions, condition) + } status.Addresses = addressesForGateway(b.config.Service, registrationPods) // only mark the gateway as needing a status update if there's a diff with its old // status, this keeps the controller from infinitely reconciling - if !cmp.Equal(status, b.config.Gateway.Status, cmp.FilterPath(func(p cmp.Path) bool { - path := p.String() - return path == "Listeners.Conditions.LastTransitionTime" || path == "Conditions.LastTransitionTime" - }, cmp.Ignore())) { + if !common.GatewayStatusesEqual(status, b.config.Gateway.Status) { b.config.Gateway.Status = status - snapshot.Kubernetes.StatusUpdates = append(snapshot.Kubernetes.StatusUpdates, &b.config.Gateway) + snapshot.Kubernetes.StatusUpdates.Add(&b.config.Gateway) } } else { // if the gateway has been deleted, unset whatever we've set on it - ref := b.gatewayRef() - snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, ref) - for _, service := range b.config.GatewayServices { + snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, b.nonNormalizedConsulKey) + for _, service := range b.config.ConsulGatewayServices { // deregister all gateways snapshot.Consul.Deregistrations = append(snapshot.Consul.Deregistrations, api.CatalogDeregistration{ Node: service.Node, @@ -296,44 +254,33 @@ func (b *Binder) Snapshot() Snapshot { Namespace: service.Namespace, }) } - if removeFinalizer(&b.config.Gateway) { - snapshot.Kubernetes.Updates = append(snapshot.Kubernetes.Updates, &b.config.Gateway) + if common.RemoveFinalizer(&b.config.Gateway) { + snapshot.Kubernetes.Updates.Add(&b.config.Gateway) } } return snapshot } -// serviceMap constructs a map of services indexed by their Kubernetes namespace and name -// from the annotations that are set on the service. -func serviceMap(services []api.CatalogService) map[types.NamespacedName]api.CatalogService { - smap := make(map[types.NamespacedName]api.CatalogService) - for _, service := range services { - smap[serviceToNamespacedName(&service)] = service - } - return smap -} +func secretsForGateway(gateway gwv1beta1.Gateway, resources *common.ResourceMap) mapset.Set { + set := mapset.NewSet() -// meshServiceMap constructs a map of services indexed by their Kubernetes namespace and name. -func meshServiceMap(services []v1alpha1.MeshService) map[types.NamespacedName]v1alpha1.MeshService { - smap := make(map[types.NamespacedName]v1alpha1.MeshService) - for _, service := range services { - smap[client.ObjectKeyFromObject(&service)] = service - } - return smap -} + for _, listener := range gateway.Spec.Listeners { + if listener.TLS == nil { + continue + } -// serviceToNamespacedName returns the Kubernetes namespace and name of a Consul catalog service -// based on the Metadata annotations written on the service. -func serviceToNamespacedName(s *api.CatalogService) types.NamespacedName { - var ( - metaKeyKubeNS = "k8s-namespace" - metaKeyKubeServiceName = "k8s-service-name" - ) - return types.NamespacedName{ - Namespace: s.ServiceMeta[metaKeyKubeNS], - Name: s.ServiceMeta[metaKeyKubeServiceName], + for _, cert := range listener.TLS.CertificateRefs { + if resources.GatewayCanReferenceSecret(gateway, cert) { + if common.NilOrEqual(cert.Group, "") && common.NilOrEqual(cert.Kind, common.KindSecret) { + key := common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace) + set.Add(key) + } + } + } } + + return set } func addressesForGateway(service *corev1.Service, pods []corev1.Pod) []gwv1beta1.GatewayAddress { @@ -366,13 +313,13 @@ func addressesFromLoadBalancer(service *corev1.Service) []gwv1beta1.GatewayAddre for _, ingress := range service.Status.LoadBalancer.Ingress { if ingress.IP != "" { addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: pointerTo(gwv1beta1.IPAddressType), + Type: common.PointerTo(gwv1beta1.IPAddressType), Value: ingress.IP, }) } if ingress.Hostname != "" { addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: pointerTo(gwv1beta1.HostnameAddressType), + Type: common.PointerTo(gwv1beta1.HostnameAddressType), Value: ingress.Hostname, }) } @@ -386,7 +333,7 @@ func addressesFromClusterIP(service *corev1.Service) []gwv1beta1.GatewayAddress if service.Spec.ClusterIP != "" { addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: pointerTo(gwv1beta1.IPAddressType), + Type: common.PointerTo(gwv1beta1.IPAddressType), Value: service.Spec.ClusterIP, }) } @@ -402,7 +349,7 @@ func addressesFromPods(pods []corev1.Pod) []gwv1beta1.GatewayAddress { if pod.Status.PodIP != "" { if _, found := seenIPs[pod.Status.PodIP]; !found { addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: pointerTo(gwv1beta1.IPAddressType), + Type: common.PointerTo(gwv1beta1.IPAddressType), Value: pod.Status.PodIP, }) seenIPs[pod.Status.PodIP] = struct{}{} @@ -421,7 +368,7 @@ func addressesFromPodHosts(pods []corev1.Pod) []gwv1beta1.GatewayAddress { if pod.Status.HostIP != "" { if _, found := seenIPs[pod.Status.HostIP]; !found { addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: pointerTo(gwv1beta1.IPAddressType), + Type: common.PointerTo(gwv1beta1.IPAddressType), Value: pod.Status.HostIP, }) seenIPs[pod.Status.HostIP] = struct{}{} @@ -431,3 +378,8 @@ func addressesFromPodHosts(pods []corev1.Pod) []gwv1beta1.GatewayAddress { return addresses } + +// isDeleted checks if the deletion timestamp is set for an object. +func isDeleted(object client.Object) bool { + return !object.GetDeletionTimestamp().IsZero() +} diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index 6078944004..484b6d1a60 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -4,10 +4,18 @@ package binding import ( - "strings" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" "testing" + "time" + logrtest "github.com/go-logr/logr/testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" @@ -19,335 +27,309 @@ import ( gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -func TestBinder_Lifecycle(t *testing.T) { - t.Parallel() +func init() { + timeFunc = func() metav1.Time { + return metav1.Time{} + } +} - className := "gateway-class" - gatewayClassName := gwv1beta1.ObjectName(className) - controllerName := "test-controller" - deletionTimestamp := pointerTo(metav1.Now()) - gatewayClass := &gwv1beta1.GatewayClass{ +const ( + testGatewayClassName = "gateway-class" + testControllerName = "test-controller" + routeListenerReferenceGrantErrorMessage = `http-listener-allowed-selector: reference not permitted due to lack of ReferenceGrant; http-listener-default-same: reference not permitted due to lack of ReferenceGrant; http-listener-explicit-all-allowed: reference not permitted due to lack of ReferenceGrant; http-listener-explicit-allowed-same: reference not permitted due to lack of ReferenceGrant; http-listener-hostname: reference not permitted due to lack of ReferenceGrant; http-listener-mismatched-kind-allowed: reference not permitted due to lack of ReferenceGrant; http-listener-tls: reference not permitted due to lack of ReferenceGrant; tcp-listener-allowed-selector: reference not permitted due to lack of ReferenceGrant; tcp-listener-default-same: reference not permitted due to lack of ReferenceGrant; tcp-listener-explicit-all-allowed: reference not permitted due to lack of ReferenceGrant; tcp-listener-explicit-allowed-same: reference not permitted due to lack of ReferenceGrant; tcp-listener-mismatched-kind-allowed: reference not permitted due to lack of ReferenceGrant; tcp-listener-tls: reference not permitted due to lack of ReferenceGrant` +) + +var ( + testGatewayClassObjectName = gwv1beta1.ObjectName(testGatewayClassName) + deletionTimestamp = common.PointerTo(metav1.Now()) + + testGatewayClass = &gwv1beta1.GatewayClass{ ObjectMeta: metav1.ObjectMeta{ - Name: className, + Name: testGatewayClassName, }, Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: gwv1beta1.GatewayController(controllerName), + ControllerName: gwv1beta1.GatewayController(testControllerName), }, } +) + +type resourceMapResources struct { + grants []gwv1beta1.ReferenceGrant + secrets []corev1.Secret + gateways []gwv1beta1.Gateway + httpRoutes []gwv1beta1.HTTPRoute + tcpRoutes []gwv1alpha2.TCPRoute + meshServices []v1alpha1.MeshService + services []types.NamespacedName + consulHTTPRoutes []api.HTTPRouteConfigEntry + consulTCPRoutes []api.TCPRouteConfigEntry +} + +func newTestResourceMap(t *testing.T, resources resourceMapResources) *common.ResourceMap { + resourceMap := common.NewResourceMap(common.ResourceTranslator{}, NewReferenceValidator(resources.grants), logrtest.NewTestLogger(t)) + + for _, s := range resources.services { + resourceMap.AddService(s, s.Name) + } + for _, s := range resources.meshServices { + resourceMap.AddMeshService(s) + } + for _, s := range resources.secrets { + resourceMap.ReferenceCountCertificate(s) + } + for _, g := range resources.gateways { + resourceMap.ReferenceCountGateway(g) + } + for _, r := range resources.httpRoutes { + resourceMap.ReferenceCountHTTPRoute(r) + } + for _, r := range resources.tcpRoutes { + resourceMap.ReferenceCountTCPRoute(r) + } + for _, r := range resources.consulHTTPRoutes { + resourceMap.ReferenceCountConsulHTTPRoute(r) + } + for _, r := range resources.consulTCPRoutes { + resourceMap.ReferenceCountConsulTCPRoute(r) + } + return resourceMap +} + +func TestBinder_Lifecycle(t *testing.T) { + t.Parallel() + + certificateOne, secretOne := generateTestCertificate(t, "default", "secret-one") + certificateTwo, secretTwo := generateTestCertificate(t, "default", "secret-two") for name, tt := range map[string]struct { - config BinderConfig - expected Snapshot + resources resourceMapResources + config BinderConfig + expectedStatusUpdates []client.Object + expectedUpdates []client.Object + expectedConsulDeletions []api.ResourceReference + expectedConsulUpdates []api.ConfigEntry }{ "no gateway class and empty routes": { config: BinderConfig{ Gateway: gwv1beta1.Gateway{}, }, - expected: Snapshot{ - Consul: ConsulSnapshot{ - Deletions: []api.ResourceReference{{ - Kind: api.APIGateway, - }}, - }, - }, + expectedConsulDeletions: []api.ResourceReference{{ + Kind: api.APIGateway, + }}, }, "no gateway class and empty routes remove finalizer": { config: BinderConfig{ Gateway: gwv1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{gatewayFinalizer}, + Finalizers: []string{common.GatewayFinalizer}, }, }, }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - Updates: []client.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{}, - }, - }, - }, - }, - Consul: ConsulSnapshot{ - Deletions: []api.ResourceReference{{ - Kind: api.APIGateway, - }}, - }, + expectedUpdates: []client.Object{ + addClassConfig(gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{}}}), + }, + expectedConsulDeletions: []api.ResourceReference{ + {Kind: api.APIGateway}, }, }, "deleting gateway empty routes": { config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, + ControllerName: testControllerName, + GatewayClass: testGatewayClass, Gateway: gwv1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ DeletionTimestamp: deletionTimestamp, - Finalizers: []string{gatewayFinalizer}, + Finalizers: []string{common.GatewayFinalizer}, }, Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, + GatewayClassName: testGatewayClassObjectName, }, }, }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - Updates: []client.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - }, + expectedUpdates: []client.Object{ + addClassConfig(gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: deletionTimestamp, Finalizers: []string{}}, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: testGatewayClassObjectName, }, - }, - Consul: ConsulSnapshot{ - Deletions: []api.ResourceReference{{ - Kind: api.APIGateway, - }}, - }, + }), + }, + expectedConsulDeletions: []api.ResourceReference{ + {Kind: api.APIGateway}, }, }, "basic gateway no finalizer": { config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, + ControllerName: testControllerName, + GatewayClass: testGatewayClass, Gateway: gwv1beta1.Gateway{ Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, + GatewayClassName: testGatewayClassObjectName, }, }, }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - Updates: []client.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - }, + expectedUpdates: []client.Object{ + addClassConfig(gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{Finalizers: []string{common.GatewayFinalizer}}, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: testGatewayClassObjectName, }, - }, - Consul: ConsulSnapshot{}, + }), }, }, "basic gateway": { - config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, + config: controlledBinder(BinderConfig{ + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{{ + Protocol: gwv1beta1.HTTPSProtocolType, + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "secret-one"}, + }, + Mode: common.PointerTo(gwv1beta1.TLSModeTerminate), + }, + }}, + }), + }), + resources: resourceMapResources{ + secrets: []corev1.Secret{ + secretOne, + }, + }, + expectedStatusUpdates: []client.Object{ + addClassConfig(gatewayWithFinalizerStatus( + gwv1beta1.GatewaySpec{ Listeners: []gwv1beta1.Listener{{ + Protocol: gwv1beta1.HTTPSProtocolType, TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }}, + Mode: common.PointerTo(gwv1beta1.TLSModeTerminate), + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "secret-one"}, + }, }, }}, }, - }, - Secrets: []corev1.Secret{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-one", - }, - }}, - }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - StatusUpdates: []client.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - Listeners: []gwv1beta1.Listener{{ - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }}, - }, - }}, + gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", }, - Status: gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ + }, + Listeners: []gwv1beta1.ListenerStatus{{ + SupportedKinds: supportedKindsForProtocol[gwv1beta1.HTTPSProtocolType], + Conditions: []metav1.Condition{ + { Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "ListenersNotValid", - Message: "one or more listeners are invalid", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "listener accepted", }, { - Type: "Programmed", + Type: "Conflicted", Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - Listeners: []gwv1beta1.ListenerStatus{{ - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "UnsupportedProtocol", - Message: "listener protocol is unsupported", - }, { - Type: "Conflicted", - Status: metav1.ConditionFalse, - Reason: "NoConflicts", - Message: "listener has no conflicts", - }, { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved certificate references", - }}, - }}, - }, - }, - }, - }, - Consul: ConsulSnapshot{ - Updates: []api.ConfigEntry{ - &api.InlineCertificateConfigEntry{ - Kind: api.InlineCertificate, - Name: "secret-one", - Meta: map[string]string{ - "k8s-name": "secret-one", - "k8s-namespace": "", - "k8s-service-name": "secret-one", - "managed-by": "consul-k8s-gateway-controller", - }, - }, - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Meta: map[string]string{ - "k8s-name": "", - "k8s-namespace": "", - "k8s-service-name": "", - "managed-by": "consul-k8s-gateway-controller", - }, - Listeners: []api.APIGatewayListener{{ - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{{ - Kind: api.InlineCertificate, - Name: "secret-one", - }}, + Reason: "NoConflicts", + Message: "listener has no conflicts", + }, { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved certificate references", }, + }, + }}, + }), + ), + }, + expectedConsulUpdates: []api.ConfigEntry{ + certificateOne, + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "default", + }, + Listeners: []api.APIGatewayListener{{ + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{{ + Kind: api.InlineCertificate, + Name: "secret-one", }}, }, - }, - Registrations: []api.CatalogRegistration{}, + }}, }, }, }, "gateway http route no finalizer": { - config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - }, - HTTPRoutes: []gwv1beta1.HTTPRoute{{ - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, + config: controlledBinder(BinderConfig{ + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + HTTPRoutes: []gwv1beta1.HTTPRoute{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", }, - }, - }}, - }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - Updates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", }, - }, - StatusUpdates: []client.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - Status: gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", }}, }, }, }, }, - Consul: ConsulSnapshot{ - Updates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "", - "k8s-service-name": "gateway", - "managed-by": "consul-k8s-gateway-controller", - }, - Listeners: []api.APIGatewayListener{}, - }, + }), + expectedUpdates: []client.Object{ + common.PointerTo(testHTTPRoute("route", []string{"gateway"}, nil)), + }, + expectedStatusUpdates: []client.Object{ + addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{}, gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + })), + }, + expectedConsulUpdates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "default", }, - Registrations: []api.CatalogRegistration{}, + Listeners: []api.APIGatewayListener{}, }, }, }, "gateway http route deleting": { - config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - }, + config: controlledBinder(BinderConfig{ + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), HTTPRoutes: []gwv1beta1.HTTPRoute{{ ObjectMeta: metav1.ObjectMeta{ + Name: "route", DeletionTimestamp: deletionTimestamp, - Finalizers: []string{gatewayFinalizer}, + Finalizers: []string{common.GatewayFinalizer}, }, Spec: gwv1beta1.HTTPRouteSpec{ CommonRouteSpec: gwv1beta1.CommonRouteSpec{ @@ -357,171 +339,122 @@ func TestBinder_Lifecycle(t *testing.T) { }, }, }}, - }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - Updates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - }, - StatusUpdates: []client.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - Status: gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - }, - }, - }, - }, - Consul: ConsulSnapshot{ - Updates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "", - "k8s-service-name": "gateway", - "managed-by": "consul-k8s-gateway-controller", - }, - Listeners: []api.APIGatewayListener{}, - }, + ConsulHTTPRoutes: []api.HTTPRouteConfigEntry{{ + Kind: api.HTTPRoute, + Name: "route", + Parents: []api.ResourceReference{ + {Kind: api.APIGateway, Name: "gateway"}, }, - Deletions: []api.ResourceReference{{ - Kind: api.HTTPRoute, - }}, - Registrations: []api.CatalogRegistration{}, - }, - }, - }, - "gateway tcp route no finalizer": { - config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, - Gateway: gwv1beta1.Gateway{ + }}, + }), + expectedUpdates: []client.Object{ + &gwv1beta1.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, + Name: "route", + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{}, }, - }, - TCPRoutes: []gwv1alpha2.TCPRoute{{ - Spec: gwv1alpha2.TCPRouteSpec{ + Spec: gwv1beta1.HTTPRouteSpec{ CommonRouteSpec: gwv1beta1.CommonRouteSpec{ ParentRefs: []gwv1beta1.ParentReference{{ Name: "gateway", }}, }, }, - }}, + }, }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - Updates: []client.Object{ - &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, + expectedStatusUpdates: []client.Object{ + addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{}, gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + })), + }, + expectedConsulUpdates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "default", }, - StatusUpdates: []client.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - Status: gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", + Listeners: []api.APIGatewayListener{}, + }, + }, + expectedConsulDeletions: []api.ResourceReference{ + {Kind: api.HTTPRoute, Name: "route"}, + }, + }, + "gateway tcp route no finalizer": { + config: controlledBinder(BinderConfig{ + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + TCPRoutes: []gwv1alpha2.TCPRoute{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "TCPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", }}, }, }, }, }, - Consul: ConsulSnapshot{ - Updates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "", - "k8s-service-name": "gateway", - "managed-by": "consul-k8s-gateway-controller", - }, - Listeners: []api.APIGatewayListener{}, - }, + }), + expectedUpdates: []client.Object{ + common.PointerTo(testTCPRoute("route", []string{"gateway"}, nil)), + }, + expectedStatusUpdates: []client.Object{ + addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{}, gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + })), + }, + expectedConsulUpdates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "default", }, - Registrations: []api.CatalogRegistration{}, + Listeners: []api.APIGatewayListener{}, }, }, }, "gateway tcp route deleting": { - config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - }, + config: controlledBinder(BinderConfig{ + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), TCPRoutes: []gwv1alpha2.TCPRoute{{ ObjectMeta: metav1.ObjectMeta{ + Name: "route", DeletionTimestamp: deletionTimestamp, - Finalizers: []string{gatewayFinalizer}, + Finalizers: []string{common.GatewayFinalizer}, }, Spec: gwv1alpha2.TCPRouteSpec{ CommonRouteSpec: gwv1beta1.CommonRouteSpec{ @@ -531,460 +464,317 @@ func TestBinder_Lifecycle(t *testing.T) { }, }, }}, - }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - Updates: []client.Object{ - &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, + ConsulTCPRoutes: []api.TCPRouteConfigEntry{{ + Kind: api.TCPRoute, + Name: "route", + Parents: []api.ResourceReference{ + {Kind: api.APIGateway, Name: "gateway"}, }, - StatusUpdates: []client.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - Status: gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - }, - }, + }}, + }), + expectedUpdates: []client.Object{ + &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "route", + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{}, }, - }, - Consul: ConsulSnapshot{ - Updates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "", - "k8s-service-name": "gateway", - "managed-by": "consul-k8s-gateway-controller", - }, - Listeners: []api.APIGatewayListener{}, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{{ + Name: "gateway", + }}, }, }, - Deletions: []api.ResourceReference{{ - Kind: api.TCPRoute, + }, + }, + expectedStatusUpdates: []client.Object{ + addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{}, gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", }}, - Registrations: []api.CatalogRegistration{}, + })), + }, + expectedConsulUpdates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "gateway", + Meta: map[string]string{ + "k8s-name": "gateway", + "k8s-namespace": "default", + }, + Listeners: []api.APIGatewayListener{}, }, }, + expectedConsulDeletions: []api.ResourceReference{ + {Kind: api.TCPRoute, Name: "route"}, + }, }, "gateway deletion routes and secrets": { - config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, + config: controlledBinder(BinderConfig{ Gateway: gwv1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", + Name: "gateway-deleted", DeletionTimestamp: deletionTimestamp, - Finalizers: []string{gatewayFinalizer}, + Finalizers: []string{common.GatewayFinalizer}, }, Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, + GatewayClassName: testGatewayClassName, Listeners: []gwv1beta1.Listener{{ TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }, { - Name: "secret-two", - }}, + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "secret-one"}, + {Name: "secret-two"}, + }, }, }}, }, }, - ControlledGateways: map[types.NamespacedName]gwv1beta1.Gateway{ - {Name: "gateway"}: { - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - Listeners: []gwv1beta1.Listener{{ - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }, { - Name: "secret-two", - }}, - }, - }}, + HTTPRoutes: []gwv1beta1.HTTPRoute{ + testHTTPRoute("http-route-one", []string{"gateway-deleted"}, nil), + testHTTPRouteStatus("http-route-two", nil, []gwv1alpha2.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway-deleted"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + }, + }}, + {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + }, + }}, + }), + }, + TCPRoutes: []gwv1alpha2.TCPRoute{ + testTCPRoute("tcp-route-one", []string{"gateway-deleted"}, nil), + testTCPRouteStatus("tcp-route-two", nil, []gwv1alpha2.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway-deleted"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + }, + }}, + {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + }, + }}, + }), + }, + ConsulHTTPRoutes: []api.HTTPRouteConfigEntry{ + { + Kind: api.HTTPRoute, Name: "http-route-two", Meta: map[string]string{ + "k8s-name": "http-route-two", + "k8s-namespace": "", + }, + Parents: []api.ResourceReference{ + {Kind: api.APIGateway, Name: "gateway-deleted"}, + {Kind: api.APIGateway, Name: "gateway"}, }, }, - {Name: "gateway-two"}: { - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-two", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - Listeners: []gwv1beta1.Listener{{ - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }, { - Name: "secret-three", - }}, - }, - }}, + { + Kind: api.HTTPRoute, Name: "http-route-one", Meta: map[string]string{ + "k8s-name": "http-route-one", + "k8s-namespace": "", + }, + Parents: []api.ResourceReference{ + {Kind: api.APIGateway, Name: "gateway-deleted"}, }, }, }, - Secrets: []corev1.Secret{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-one", - }, - }, { - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-two", + ConsulTCPRoutes: []api.TCPRouteConfigEntry{ + { + Kind: api.TCPRoute, Name: "tcp-route-two", + Meta: map[string]string{ + "k8s-name": "tcp-route-two", + "k8s-namespace": "", + }, + Parents: []api.ResourceReference{ + {Kind: api.APIGateway, Name: "gateway-deleted"}, + {Kind: api.APIGateway, Name: "gateway"}, + }, }, - }}, - HTTPRoutes: []gwv1beta1.HTTPRoute{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route-one", - Finalizers: []string{gatewayFinalizer}, + { + Kind: api.TCPRoute, Name: "tcp-route-one", + Meta: map[string]string{ + "k8s-name": "tcp-route-one", + "k8s-namespace": "", + }, + Parents: []api.ResourceReference{ + {Kind: api.APIGateway, Name: "gateway-deleted"}, + }, }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, + }, + ConsulInlineCertificates: []api.InlineCertificateConfigEntry{ + *certificateOne, + *certificateTwo, + }, + }), + resources: resourceMapResources{ + secrets: []corev1.Secret{ + secretOne, + secretTwo, + }, + gateways: []gwv1beta1.Gateway{ + gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{{ + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "secret-one"}, + {Name: "secret-three"}, + }, + }, + }}, + }), + }, + }, + expectedStatusUpdates: []client.Object{ + common.PointerTo(testHTTPRouteStatus("http-route-two", nil, []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + }, + }}, + }, "gateway-deleted")), + common.PointerTo(testTCPRouteStatus("tcp-route-two", nil, []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, }, + }}, + }, "gateway-deleted")), + }, + expectedUpdates: []client.Object{ + &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", }, - }, { ObjectMeta: metav1.ObjectMeta{ - Name: "http-route-two", - Finalizers: []string{gatewayFinalizer}, + Name: "http-route-one", + // removing a finalizer + Finalizers: []string{}, }, Spec: gwv1beta1.HTTPRouteSpec{ CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }, { - Name: "gateway-two", - }}, + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway-deleted"}, + }, }, }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gwv1beta1.GatewayController(controllerName), - ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - }}, - }, { - ControllerName: gwv1beta1.GatewayController(controllerName), - ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - }}, - }}, - }, + Status: gwv1beta1.HTTPRouteStatus{RouteStatus: gwv1beta1.RouteStatus{Parents: []gwv1alpha2.RouteParentStatus{}}}, + }, + &gwv1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "TCPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", }, - }}, - TCPRoutes: []gwv1alpha2.TCPRoute{{ ObjectMeta: metav1.ObjectMeta{ Name: "tcp-route-one", - Finalizers: []string{gatewayFinalizer}, + Finalizers: []string{}, }, Spec: gwv1alpha2.TCPRouteSpec{ CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway-deleted"}, + }, }, }, - }, { + Status: gwv1alpha2.TCPRouteStatus{RouteStatus: gwv1beta1.RouteStatus{Parents: []gwv1alpha2.RouteParentStatus{}}}, + }, + addClassConfig(gwv1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route-two", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }, { - Name: "gateway-two", - }}, - }, + Name: "gateway-deleted", + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{}, }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gwv1beta1.GatewayController(controllerName), - ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - }}, - }, { - ControllerName: gwv1beta1.GatewayController(controllerName), - ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - }}, - }}, - }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: testGatewayClassName, + Listeners: []gwv1beta1.Listener{{ + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "secret-one"}, + {Name: "secret-two"}, + }, + }, + }}, }, - }}, - ConsulHTTPRoutes: []api.HTTPRouteConfigEntry{{ + }), + }, + expectedConsulUpdates: []api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "http-route-two", Meta: map[string]string{ - "k8s-name": "http-route-two", - "k8s-namespace": "", - "k8s-service-name": "http-route-two", - "managed-by": "consul-k8s-gateway-controller", + "k8s-name": "http-route-two", + "k8s-namespace": "", }, + // dropped ref to gateway Parents: []api.ResourceReference{{ Kind: api.APIGateway, Name: "gateway", - }, { - Kind: api.APIGateway, - Name: "gateway-two", }}, - }}, - ConsulTCPRoutes: []api.TCPRouteConfigEntry{{ + }, + &api.TCPRouteConfigEntry{ Kind: api.TCPRoute, Name: "tcp-route-two", Meta: map[string]string{ - "k8s-name": "tcp-route-two", - "k8s-namespace": "", - "k8s-service-name": "tcp-route-two", - "managed-by": "consul-k8s-gateway-controller", + "k8s-name": "tcp-route-two", + "k8s-namespace": "", }, + // dropped ref to gateway Parents: []api.ResourceReference{{ Kind: api.APIGateway, Name: "gateway", - }, { - Kind: api.APIGateway, - Name: "gateway-two", - }}, - }}, - ConsulInlineCertificates: []api.InlineCertificateConfigEntry{{ - Kind: api.InlineCertificate, - Name: "secret-one", - Meta: map[string]string{ - "k8s-name": "secret-one", - "k8s-namespace": "", - "k8s-service-name": "secret-one", - "managed-by": "consul-k8s-gateway-controller", - }, - }, { - Kind: api.InlineCertificate, - Name: "secret-two", - Meta: map[string]string{ - "k8s-name": "secret-two", - "k8s-namespace": "", - "k8s-service-name": "secret-two", - "managed-by": "consul-k8s-gateway-controller", - }, - }}, - }, - expected: Snapshot{ - Kubernetes: KubernetesSnapshot{ - StatusUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route-two", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }, { - Name: "gateway-two", - }}, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - // removed gateway status - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gwv1beta1.GatewayController(controllerName), - ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - }}, - }}, - }, - }, - }, - &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route-two", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }, { - Name: "gateway-two", - }}, - }, - }, - // removed gateway status - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gwv1beta1.GatewayController(controllerName), - ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - }}, - }}, - }, - }, - }, - }, - Updates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route-one", - Finalizers: []string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route-one", - Finalizers: []string{}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - Listeners: []gwv1beta1.Listener{{ - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }, { - Name: "secret-two", - }}, - }, - }}, - }, - }, - }, - }, - Consul: ConsulSnapshot{ - Updates: []api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "http-route-two", - Meta: map[string]string{ - "k8s-name": "http-route-two", - "k8s-namespace": "", - "k8s-service-name": "http-route-two", - "managed-by": "consul-k8s-gateway-controller", - }, - // dropped ref to gateway - Parents: []api.ResourceReference{{ - Kind: api.APIGateway, - Name: "gateway-two", - }}, - }, - &api.TCPRouteConfigEntry{ - Kind: api.TCPRoute, - Name: "tcp-route-two", - Meta: map[string]string{ - "k8s-name": "tcp-route-two", - "k8s-namespace": "", - "k8s-service-name": "tcp-route-two", - "managed-by": "consul-k8s-gateway-controller", - }, - // dropped ref to gateway - Parents: []api.ResourceReference{{ - Kind: api.APIGateway, - Name: "gateway-two", - }}, - }, - }, - Deletions: []api.ResourceReference{{ - Kind: api.HTTPRoute, - Name: "http-route-one", - }, { - Kind: api.TCPRoute, - Name: "tcp-route-one", - }, { - Kind: api.InlineCertificate, - Name: "secret-two", - }, { - Kind: api.APIGateway, - Name: "gateway", }}, }, }, + expectedConsulDeletions: []api.ResourceReference{ + {Kind: api.HTTPRoute, Name: "http-route-one"}, + {Kind: api.TCPRoute, Name: "tcp-route-one"}, + {Kind: api.InlineCertificate, Name: "secret-two"}, + {Kind: api.APIGateway, Name: "gateway-deleted"}, + }, }, } { t.Run(name, func(t *testing.T) { - tt.config.ControllerName = controllerName + tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) + tt.resources.httpRoutes = append(tt.resources.httpRoutes, tt.config.HTTPRoutes...) + tt.resources.tcpRoutes = append(tt.resources.tcpRoutes, tt.config.TCPRoutes...) + tt.resources.consulHTTPRoutes = append(tt.resources.consulHTTPRoutes, tt.config.ConsulHTTPRoutes...) + tt.resources.consulTCPRoutes = append(tt.resources.consulTCPRoutes, tt.config.ConsulTCPRoutes...) + + tt.config.Resources = newTestResourceMap(t, tt.resources) + tt.config.ControllerName = testControllerName + tt.config.Logger = logrtest.NewTestLogger(t) tt.config.GatewayClassConfig = &v1alpha1.GatewayClassConfig{} serializeGatewayClassConfig(&tt.config.Gateway, tt.config.GatewayClassConfig) binder := NewBinder(tt.config) actual := binder.Snapshot() - diff := cmp.Diff(tt.expected, actual, cmp.FilterPath(func(p cmp.Path) bool { - return p.String() == "GatewayClassConfig" || strings.HasSuffix(p.String(), "LastTransitionTime") || strings.HasSuffix(p.String(), "Annotations") || strings.HasSuffix(p.String(), "UpsertGatewayDeployment") - }, cmp.Ignore())) - if diff != "" { - t.Error("undexpected diff", diff) - } + actualConsulUpdates := common.ConvertSliceFunc(actual.Consul.Updates, func(op *common.ConsulUpdateOperation) api.ConfigEntry { + return op.Entry + }) + + require.ElementsMatch(t, tt.expectedConsulUpdates, actualConsulUpdates, "consul updates differ", cmp.Diff(tt.expectedConsulUpdates, actualConsulUpdates)) + require.ElementsMatch(t, tt.expectedConsulDeletions, actual.Consul.Deletions, "consul deletions differ") + require.ElementsMatch(t, tt.expectedStatusUpdates, actual.Kubernetes.StatusUpdates.Operations(), "kubernetes statuses differ", cmp.Diff(tt.expectedStatusUpdates, actual.Kubernetes.StatusUpdates.Operations())) + require.ElementsMatch(t, tt.expectedUpdates, actual.Kubernetes.Updates.Operations(), "kubernetes updates differ", cmp.Diff(tt.expectedUpdates, actual.Kubernetes.Updates.Operations())) }) } } @@ -992,45 +782,26 @@ func TestBinder_Lifecycle(t *testing.T) { func TestBinder_Registrations(t *testing.T) { t.Parallel() - className := "gateway-class" - gatewayClassName := gwv1beta1.ObjectName(className) - controllerName := "test-controller" - deletionTimestamp := pointerTo(metav1.Now()) - gatewayClass := &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: className, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: gwv1beta1.GatewayController(controllerName), - }, + setDeleted := func(gateway gwv1beta1.Gateway) gwv1beta1.Gateway { + gateway.DeletionTimestamp = deletionTimestamp + return gateway } - gatewayName := "gateway" for name, tt := range map[string]struct { config BinderConfig + resources resourceMapResources expectedRegistrations []string expectedDeregistrations []api.CatalogDeregistration }{ "deleting gateway with consul services": { - config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: gatewayName, - Finalizers: []string{gatewayFinalizer}, - DeletionTimestamp: deletionTimestamp, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - }, - GatewayServices: []api.CatalogService{ + config: controlledBinder(BinderConfig{ + Gateway: setDeleted(gatewayWithFinalizer(gwv1beta1.GatewaySpec{})), + ConsulGatewayServices: []api.CatalogService{ {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, }, - }, + }), expectedDeregistrations: []api.CatalogDeregistration{ {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, @@ -1038,18 +809,8 @@ func TestBinder_Registrations(t *testing.T) { }, }, "gateway with consul services and mixed pods": { - config: BinderConfig{ - ControllerName: controllerName, - GatewayClass: gatewayClass, - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: gatewayName, - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - }, - }, + config: controlledBinder(BinderConfig{ + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), Pods: []corev1.Pod{ { ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "namespace1"}, @@ -1072,12 +833,12 @@ func TestBinder_Registrations(t *testing.T) { }, }, }, - GatewayServices: []api.CatalogService{ + ConsulGatewayServices: []api.CatalogService{ {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, }, - }, + }), expectedRegistrations: []string{"pod1", "pod3", "pod4"}, expectedDeregistrations: []api.CatalogDeregistration{ {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, @@ -1085,7 +846,15 @@ func TestBinder_Registrations(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - tt.config.ControllerName = controllerName + tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) + tt.resources.httpRoutes = append(tt.resources.httpRoutes, tt.config.HTTPRoutes...) + tt.resources.tcpRoutes = append(tt.resources.tcpRoutes, tt.config.TCPRoutes...) + tt.resources.consulHTTPRoutes = append(tt.resources.consulHTTPRoutes, tt.config.ConsulHTTPRoutes...) + tt.resources.consulTCPRoutes = append(tt.resources.consulTCPRoutes, tt.config.ConsulTCPRoutes...) + + tt.config.Resources = newTestResourceMap(t, tt.resources) + tt.config.ControllerName = testControllerName + tt.config.Logger = logrtest.NewTestLogger(t) tt.config.GatewayClassConfig = &v1alpha1.GatewayClassConfig{} serializeGatewayClassConfig(&tt.config.Gateway, tt.config.GatewayClassConfig) @@ -1098,7 +867,7 @@ func TestBinder_Registrations(t *testing.T) { expected := tt.expectedRegistrations[i] require.EqualValues(t, expected, registration.Service.ID) - require.EqualValues(t, gatewayName, registration.Service.Service) + require.EqualValues(t, "gateway", registration.Service.Service) } require.EqualValues(t, tt.expectedDeregistrations, actual.Consul.Deregistrations) @@ -1109,133 +878,114 @@ func TestBinder_Registrations(t *testing.T) { func TestBinder_BindingRulesKitchenSink(t *testing.T) { t.Parallel() - className := "gateway-class" - gatewayClassName := gwv1beta1.ObjectName(className) - controllerName := "test-controller" - gatewayClass := &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: className, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: gwv1beta1.GatewayController(controllerName), - }, - } - - gateway := gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gatewayClassName, - Listeners: []gwv1beta1.Listener{{ - Name: "http-listener-default-same", - Protocol: gwv1beta1.HTTPProtocolType, - }, { - Name: "http-listener-hostname", - Protocol: gwv1beta1.HTTPProtocolType, - Hostname: pointerTo[gwv1beta1.Hostname]("host.name"), - }, { - Name: "http-listener-mismatched-kind-allowed", - Protocol: gwv1beta1.HTTPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Kinds: []gwv1beta1.RouteGroupKind{{ - Kind: "Foo", - }}, - }, - }, { - Name: "http-listener-explicit-all-allowed", - Protocol: gwv1beta1.HTTPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromAll), - }, + gateway := gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{{ + Name: "http-listener-default-same", + Protocol: gwv1beta1.HTTPProtocolType, + }, { + Name: "http-listener-hostname", + Protocol: gwv1beta1.HTTPProtocolType, + Hostname: common.PointerTo[gwv1beta1.Hostname]("host.name"), + }, { + Name: "http-listener-mismatched-kind-allowed", + Protocol: gwv1beta1.HTTPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Kinds: []gwv1beta1.RouteGroupKind{{ + Kind: "Foo", + }}, + }, + }, { + Name: "http-listener-explicit-all-allowed", + Protocol: gwv1beta1.HTTPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.NamespacesFromAll), }, - }, { - Name: "http-listener-explicit-allowed-same", - Protocol: gwv1beta1.HTTPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromSame), - }, + }, + }, { + Name: "http-listener-explicit-allowed-same", + Protocol: gwv1beta1.HTTPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.NamespacesFromSame), }, - }, { - Name: "http-listener-allowed-selector", - Protocol: gwv1beta1.HTTPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "foo", - }, + }, + }, { + Name: "http-listener-allowed-selector", + Protocol: gwv1beta1.HTTPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "foo", }, }, }, - }, { - Name: "http-listener-tls", - Protocol: gwv1beta1.HTTPSProtocolType, - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }}, - }, - }, { - Name: "tcp-listener-default-same", - Protocol: gwv1beta1.TCPProtocolType, - }, { - Name: "tcp-listener-mismatched-kind-allowed", - Protocol: gwv1beta1.TCPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Kinds: []gwv1beta1.RouteGroupKind{{ - Kind: "Foo", - }}, - }, - }, { - Name: "tcp-listener-explicit-all-allowed", - Protocol: gwv1beta1.TCPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromAll), - }, + }, + }, { + Name: "http-listener-tls", + Protocol: gwv1beta1.HTTPSProtocolType, + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }}, + }, + }, { + Name: "tcp-listener-default-same", + Protocol: gwv1beta1.TCPProtocolType, + }, { + Name: "tcp-listener-mismatched-kind-allowed", + Protocol: gwv1beta1.TCPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Kinds: []gwv1beta1.RouteGroupKind{{ + Kind: "Foo", + }}, + }, + }, { + Name: "tcp-listener-explicit-all-allowed", + Protocol: gwv1beta1.TCPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.NamespacesFromAll), }, - }, { - Name: "tcp-listener-explicit-allowed-same", - Protocol: gwv1beta1.TCPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromSame), - }, + }, + }, { + Name: "tcp-listener-explicit-allowed-same", + Protocol: gwv1beta1.TCPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.NamespacesFromSame), }, - }, { - Name: "tcp-listener-allowed-selector", - Protocol: gwv1beta1.TCPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "foo", - }, + }, + }, { + Name: "tcp-listener-allowed-selector", + Protocol: gwv1beta1.TCPProtocolType, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "foo", }, }, }, - }, { - Name: "tcp-listener-tls", - Protocol: gwv1beta1.TCPProtocolType, - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }}, - }, - }}, - }, - } + }, + }, { + Name: "tcp-listener-tls", + Protocol: gwv1beta1.TCPProtocolType, + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: "secret-one", + }}, + }, + }}, + }) namespaces := map[string]corev1.Namespace{ - "": { + "default": { ObjectMeta: metav1.ObjectMeta{ - Name: "", + Name: "default", }, }, "test": { @@ -1248,1436 +998,1358 @@ func TestBinder_BindingRulesKitchenSink(t *testing.T) { }, } - defaultNamespacePointer := pointerTo[gwv1beta1.Namespace]("") + _, secretOne := generateTestCertificate(t, "", "secret-one") - httpTypeMeta := metav1.TypeMeta{} - httpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("HTTPRoute")) - tcpTypeMeta := metav1.TypeMeta{} - tcpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("TCPRoute")) + gateway.Namespace = "default" + defaultNamespacePointer := common.PointerTo[gwv1beta1.Namespace]("default") for name, tt := range map[string]struct { - httpRoute *gwv1beta1.HTTPRoute - expectedHTTPRouteUpdate *gwv1beta1.HTTPRoute - expectedHTTPRouteUpdateStatus *gwv1beta1.HTTPRoute - expectedHTTPConsulRouteUpdate *api.HTTPRouteConfigEntry - expectedHTTPConsulRouteDelete *api.ResourceReference - - tcpRoute *gwv1alpha2.TCPRoute - expectedTCPRouteUpdate *gwv1alpha2.TCPRoute - expectedTCPRouteUpdateStatus *gwv1alpha2.TCPRoute - expectedTCPConsulRouteUpdate *api.TCPRouteConfigEntry - expectedTCPConsulRouteDelete *api.ResourceReference + httpRoute *gwv1beta1.HTTPRoute + tcpRoute *gwv1alpha2.TCPRoute + referenceGrants []gwv1beta1.ReferenceGrant + expectedStatusUpdates []client.Object }{ "untargeted http route same namespace": { - httpRoute: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }}, - }, - }, + httpRoute: testHTTPRouteBackends("route", "default", nil, []gwv1beta1.ParentReference{ + {Name: "gateway"}, + }), + expectedStatusUpdates: []client.Object{ + testHTTPRouteStatusBackends("route", "default", nil, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }, + }}, + }), }, }, "untargeted http route same namespace missing backend": { - httpRoute: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - Rules: []gwv1beta1.HTTPRouteRule{{ - BackendRefs: []gwv1beta1.HTTPBackendRef{{ - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName("backend"), - }, - }, - }}, + httpRoute: testHTTPRouteBackends("route", "default", []gwv1beta1.BackendObjectReference{ + {Name: gwv1beta1.ObjectName("backend")}, + }, []gwv1beta1.ParentReference{ + {Name: "gateway"}, + }), + expectedStatusUpdates: []client.Object{ + testHTTPRouteStatusBackends("route", "default", []gwv1beta1.BackendObjectReference{ + {Name: gwv1beta1.ObjectName("backend")}, + }, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "BackendNotFound", + Message: "default/backend: backend not found", + }, + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }, }}, - }, - }, - expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - Rules: []gwv1beta1.HTTPRouteRule{{ - BackendRefs: []gwv1beta1.HTTPBackendRef{{ - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName("backend"), - }, - }, - }}, - }}, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "BackendNotFound", - Message: "/backend: backend not found", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }}, - }, - }, + }), }, }, "untargeted http route same namespace invalid backend type": { - httpRoute: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - Rules: []gwv1beta1.HTTPRouteRule{{ - BackendRefs: []gwv1beta1.HTTPBackendRef{{ - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName("backend"), - Group: pointerTo[gwv1beta1.Group]("invalid.foo.com"), - }, - }, - }}, - }}, - }, - }, - expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - Rules: []gwv1beta1.HTTPRouteRule{{ - BackendRefs: []gwv1beta1.HTTPBackendRef{{ - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName("backend"), - Group: pointerTo[gwv1beta1.Group]("invalid.foo.com"), - }, - }, - }}, + httpRoute: testHTTPRouteBackends("route", "default", []gwv1beta1.BackendObjectReference{ + { + Name: gwv1beta1.ObjectName("backend"), + Group: common.PointerTo[gwv1beta1.Group]("invalid.foo.com"), + }, + }, []gwv1beta1.ParentReference{ + {Name: "gateway"}, + }), + expectedStatusUpdates: []client.Object{ + testHTTPRouteStatusBackends("route", "default", []gwv1beta1.BackendObjectReference{ + { + Name: gwv1beta1.ObjectName("backend"), + Group: common.PointerTo[gwv1beta1.Group]("invalid.foo.com"), + }, + }, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidKind", + Message: "default/backend [Service.invalid.foo.com]: invalid backend kind", + }, + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }, }}, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidKind", - Message: "/backend [Service.invalid.foo.com]: invalid backend kind", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }}, - }, - }, + }), }, }, "untargeted http route different namespace": { - httpRoute: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Namespace: "test", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }}, - }, - }, + httpRoute: testHTTPRouteBackends("route", "other", nil, []gwv1beta1.ParentReference{ + { + Name: "gateway", + Namespace: defaultNamespacePointer, + }, + }), + expectedStatusUpdates: []client.Object{ + testHTTPRouteStatusBackends("route", "other", nil, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: routeListenerReferenceGrantErrorMessage, + }, + }}, + }), }, - expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Namespace: "test", - Finalizers: []string{gatewayFinalizer}, + }, + "untargeted http route different namespace and reference grants": { + httpRoute: testHTTPRouteBackends("route", "other", nil, []gwv1beta1.ParentReference{ + { + Name: "gateway", + Namespace: defaultNamespacePointer, }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }}, + }), + referenceGrants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("other")}, }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }}, + To: []gwv1beta1.ReferenceGrantTo{ + {Group: gwv1beta1.GroupName, Kind: "Gateway"}, }, - }, + }}, + }, + expectedStatusUpdates: []client.Object{ + testHTTPRouteStatusBackends("route", "other", nil, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }, + }}, + }), }, }, "targeted http route same namespace": { - httpRoute: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, + httpRoute: testHTTPRouteBackends("route", "default", nil, []gwv1beta1.ParentReference{ + { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, { + }), + expectedStatusUpdates: []client.Object{ + testHTTPRouteStatusBackends("route", "default", nil, []gwv1beta1.RouteParentStatus{ + { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", }}, - }, - }, - }, - expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, { + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-allowed-selector: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", }}, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-allowed-selector: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-explicit-all-allowed: listener does not support route protocol", - }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-explicit-all-allowed: listener does not support route protocol", }}, }, - }, + }), }, }, "targeted http route different namespace": { - httpRoute: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Namespace: "test", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }}, + referenceGrants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("test")}, }, - }, + To: []gwv1beta1.ReferenceGrantTo{ + {Group: gwv1beta1.GroupName, Kind: "Gateway"}, + }, + }}, }, - expectedHTTPRouteUpdateStatus: &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Namespace: "test", - Finalizers: []string{gatewayFinalizer}, + httpRoute: testHTTPRouteBackends("route", "test", nil, []gwv1beta1.ParentReference{ + { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ + }), + expectedStatusUpdates: []client.Object{ + testHTTPRouteStatusBackends("route", "test", nil, []gwv1beta1.RouteParentStatus{ + { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-default-same: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-hostname: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-explicit-allowed-same: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-tls: listener does not allow binding routes from the given namespace", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "tcp-listener-explicit-all-allowed: listener does not support route protocol", }}, }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-default-same: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-hostname: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-explicit-allowed-same: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-tls: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-explicit-all-allowed: listener does not support route protocol", - }}, - }}, - }, - }, + }), }, }, "untargeted tcp route same namespace": { - tcpRoute: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }}, - }, - }, + tcpRoute: testTCPRouteBackends("route", "default", nil, []gwv1beta1.ParentReference{ + {Name: "gateway"}, + }), + expectedStatusUpdates: []client.Object{ + testTCPRouteStatusBackends("route", "default", nil, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }, + }}, + }), }, }, "untargeted tcp route same namespace missing backend": { - tcpRoute: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - Rules: []gwv1alpha2.TCPRouteRule{{ - BackendRefs: []gwv1beta1.BackendRef{{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName("backend"), - }, - }}, + tcpRoute: testTCPRouteBackends("route", "default", []gwv1beta1.BackendObjectReference{ + {Name: gwv1beta1.ObjectName("backend")}, + }, []gwv1beta1.ParentReference{ + {Name: "gateway"}, + }), + expectedStatusUpdates: []client.Object{ + testTCPRouteStatusBackends("route", "default", []gwv1beta1.BackendObjectReference{ + {Name: gwv1beta1.ObjectName("backend")}, + }, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "BackendNotFound", + Message: "default/backend: backend not found", + }, + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }, }}, - }, - }, - expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - Rules: []gwv1alpha2.TCPRouteRule{{ - BackendRefs: []gwv1beta1.BackendRef{{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName("backend"), - }, - }}, - }}, - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "BackendNotFound", - Message: "/backend: backend not found", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }}, - }, - }, + }), }, }, "untargeted tcp route same namespace invalid backend type": { - tcpRoute: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - Rules: []gwv1alpha2.TCPRouteRule{{ - BackendRefs: []gwv1beta1.BackendRef{{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName("backend"), - Group: pointerTo[gwv1beta1.Group]("invalid.foo.com"), - }, - }}, - }}, - }, - }, - expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - Rules: []gwv1alpha2.TCPRouteRule{{ - BackendRefs: []gwv1beta1.BackendRef{{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName("backend"), - Group: pointerTo[gwv1beta1.Group]("invalid.foo.com"), - }, - }}, + tcpRoute: testTCPRouteBackends("route", "default", []gwv1beta1.BackendObjectReference{ + { + Name: gwv1beta1.ObjectName("backend"), + Group: common.PointerTo[gwv1beta1.Group]("invalid.foo.com"), + }, + }, []gwv1beta1.ParentReference{ + {Name: "gateway"}, + }), + expectedStatusUpdates: []client.Object{ + testTCPRouteStatusBackends("route", "default", []gwv1beta1.BackendObjectReference{ + { + Name: gwv1beta1.ObjectName("backend"), + Group: common.PointerTo[gwv1beta1.Group]("invalid.foo.com"), + }, + }, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidKind", + Message: "default/backend [Service.invalid.foo.com]: invalid backend kind", + }, + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }, }}, - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidKind", - Message: "/backend [Service.invalid.foo.com]: invalid backend kind", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }}, - }, - }, + }), }, }, "untargeted tcp route different namespace": { - tcpRoute: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Namespace: "test", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }}, - }, - }, + tcpRoute: testTCPRouteBackends("route", "other", nil, []gwv1beta1.ParentReference{ + { + Name: "gateway", + Namespace: defaultNamespacePointer, + }, + }), + expectedStatusUpdates: []client.Object{ + testTCPRouteStatusBackends("route", "other", nil, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: routeListenerReferenceGrantErrorMessage, + }, + }}, + }), }, - expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Namespace: "test", - Finalizers: []string{gatewayFinalizer}, + }, + "untargeted tcp route different namespace and reference grants": { + tcpRoute: testTCPRouteBackends("route", "other", nil, []gwv1beta1.ParentReference{ + { + Name: "gateway", + Namespace: defaultNamespacePointer, }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }}, + }), + referenceGrants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "TCPRoute", Namespace: gwv1beta1.Namespace("other")}, }, - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }}, + To: []gwv1beta1.ReferenceGrantTo{ + {Group: gwv1beta1.GroupName, Kind: "Gateway"}, }, - }, + }}, + }, + expectedStatusUpdates: []client.Object{ + testTCPRouteStatusBackends("route", "other", nil, []gwv1beta1.RouteParentStatus{ + {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + }, Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", + }, + }}, + }), }, }, "targeted tcp route same namespace": { - tcpRoute: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }}, - }, - }, - }, - expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Finalizers: []string{gatewayFinalizer}, + tcpRoute: testTCPRouteBackends("route", "default", nil, []gwv1beta1.ParentReference{ + { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, { + Name: "gateway", + SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), - }, { - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), - }, { + }), + expectedStatusUpdates: []client.Object{ + testTCPRouteStatusBackends("route", "default", nil, []gwv1beta1.RouteParentStatus{ + { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }}, - }, - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-mismatched-kind-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-allowed-selector: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-explicit-all-allowed: listener does not support route protocol", - }}, - }}, - }, - }, - }, - }, - "targeted tcp route different namespace": { - tcpRoute: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Namespace: "test", - Finalizers: []string{gatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-default-same: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-hostname: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-explicit-all-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-explicit-allowed-same: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-allowed-selector: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-tls: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", }}, }, - }, + }), + }, + }, + "targeted tcp route different namespace": { + referenceGrants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "TCPRoute", Namespace: gwv1beta1.Namespace("test")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Group: gwv1beta1.GroupName, Kind: "Gateway"}, + }, + }}, }, - expectedTCPRouteUpdateStatus: &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - Namespace: "test", - Finalizers: []string{gatewayFinalizer}, + tcpRoute: testTCPRouteBackends("route", "test", nil, []gwv1beta1.ParentReference{ + { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, { + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ + }), + expectedStatusUpdates: []client.Object{ + testTCPRouteStatusBackends("route", "test", nil, []gwv1beta1.RouteParentStatus{ + { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-default-same: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-hostname: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-explicit-all-allowed: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-explicit-allowed-same: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), - }, { + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-allowed-selector: listener does not support route protocol", + }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ Name: "gateway", Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), + SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "NotAllowedByListeners", + Message: "http-listener-tls: listener does not support route protocol", }}, - }, - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{{ - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-default-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-default-same: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-mismatched-kind-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-mismatched-kind-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-allowed-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-explicit-allowed-same: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-allowed-selector"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("tcp-listener-tls"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-tls: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: gatewayClass.Spec.ControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: pointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-explicit-all-allowed: listener does not support route protocol", - }}, + }, { + ControllerName: testControllerName, + ParentRef: gwv1beta1.ParentReference{ + Name: "gateway", + Namespace: defaultNamespacePointer, + SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), + }, + Conditions: []metav1.Condition{{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", }}, }, - }, + }), }, }, } { t.Run(name, func(t *testing.T) { - config := BinderConfig{ - ControllerName: controllerName, - GatewayClassConfig: &v1alpha1.GatewayClassConfig{}, - GatewayClass: gatewayClass, - Gateway: gateway, - Namespaces: namespaces, - ControlledGateways: map[types.NamespacedName]gwv1beta1.Gateway{ - {Name: "gateway"}: gateway, + g := *addClassConfig(gateway) + + resources := resourceMapResources{ + gateways: []gwv1beta1.Gateway{g}, + secrets: []corev1.Secret{ + secretOne, }, - Secrets: []corev1.Secret{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secret-one", - }, - }}, + grants: tt.referenceGrants, } - serializeGatewayClassConfig(&config.Gateway, config.GatewayClassConfig) if tt.httpRoute != nil { - config.HTTPRoutes = append(config.HTTPRoutes, *tt.httpRoute) + resources.httpRoutes = append(resources.httpRoutes, *tt.httpRoute) } if tt.tcpRoute != nil { - config.TCPRoutes = append(config.TCPRoutes, *tt.tcpRoute) + resources.tcpRoutes = append(resources.tcpRoutes, *tt.tcpRoute) } + config := controlledBinder(BinderConfig{ + Gateway: g, + GatewayClassConfig: &v1alpha1.GatewayClassConfig{}, + Namespaces: namespaces, + Resources: newTestResourceMap(t, resources), + HTTPRoutes: resources.httpRoutes, + TCPRoutes: resources.tcpRoutes, + }) + binder := NewBinder(config) actual := binder.Snapshot() - compareUpdates(t, tt.expectedHTTPRouteUpdate, actual.Kubernetes.Updates) - compareUpdates(t, tt.expectedTCPRouteUpdate, actual.Kubernetes.Updates) - compareUpdates(t, tt.expectedHTTPRouteUpdateStatus, actual.Kubernetes.StatusUpdates) - compareUpdates(t, tt.expectedTCPRouteUpdateStatus, actual.Kubernetes.StatusUpdates) + compareUpdates(t, tt.expectedStatusUpdates, actual.Kubernetes.StatusUpdates.Operations()) }) } } -func compareUpdates[T client.Object](t *testing.T, expected T, updates []client.Object) { +func compareUpdates(t *testing.T, expected []client.Object, actual []client.Object) { t.Helper() - if isNil(expected) { - for _, update := range updates { - if u, ok := update.(T); ok { - t.Error("found unexpected update", u) - } - } - } else { - found := false - for _, update := range updates { - if u, ok := update.(T); ok { - diff := cmp.Diff(expected, u, cmp.FilterPath(func(p cmp.Path) bool { - return p.String() == "Status.RouteStatus.Parents.Conditions.LastTransitionTime" - }, cmp.Ignore())) - if diff != "" { - t.Error("diff between actual and expected", diff) - } - found = true - } + filtered := common.Filter(actual, func(o client.Object) bool { + if _, ok := o.(*gwv1beta1.HTTPRoute); ok { + return false } - if !found { - t.Error("expected route update not found in", updates) + if _, ok := o.(*gwv1alpha2.TCPRoute); ok { + return false } + return true + }) + + require.ElementsMatch(t, expected, filtered, "statuses don't match", cmp.Diff(expected, filtered)) +} + +func addClassConfig(g gwv1beta1.Gateway) *gwv1beta1.Gateway { + serializeGatewayClassConfig(&g, &v1alpha1.GatewayClassConfig{}) + return &g +} + +func gatewayWithFinalizer(spec gwv1beta1.GatewaySpec) gwv1beta1.Gateway { + spec.GatewayClassName = testGatewayClassObjectName + + typeMeta := metav1.TypeMeta{} + typeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("Gateway")) + + return gwv1beta1.Gateway{ + TypeMeta: typeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Namespace: "default", + Finalizers: []string{common.GatewayFinalizer}, + }, + Spec: spec, + } +} + +func gatewayWithFinalizerStatus(spec gwv1beta1.GatewaySpec, status gwv1beta1.GatewayStatus) gwv1beta1.Gateway { + g := gatewayWithFinalizer(spec) + g.Status = status + return g +} + +func testHTTPRoute(name string, parents []string, services []string) gwv1beta1.HTTPRoute { + var parentRefs []gwv1beta1.ParentReference + var rules []gwv1beta1.HTTPRouteRule + + for _, parent := range parents { + parentRefs = append(parentRefs, gwv1beta1.ParentReference{Name: gwv1beta1.ObjectName(parent)}) + } + + for _, service := range services { + rules = append(rules, gwv1beta1.HTTPRouteRule{ + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName(service), + }, + }, + }, + }, + }) + } + + httpTypeMeta := metav1.TypeMeta{} + httpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("HTTPRoute")) + + return gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{Name: name, Finalizers: []string{common.GatewayFinalizer}}, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: parentRefs, + }, + Rules: rules, + }, + } +} + +func testHTTPRouteBackends(name, namespace string, services []gwv1beta1.BackendObjectReference, parents []gwv1beta1.ParentReference) *gwv1beta1.HTTPRoute { + var rules []gwv1beta1.HTTPRouteRule + for _, service := range services { + rules = append(rules, gwv1beta1.HTTPRouteRule{ + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: service, + }, + }, + }, + }) + } + + httpTypeMeta := metav1.TypeMeta{} + httpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("HTTPRoute")) + + return &gwv1beta1.HTTPRoute{ + TypeMeta: httpTypeMeta, + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, Finalizers: []string{common.GatewayFinalizer}}, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: parents, + }, + Rules: rules, + }, + } +} + +func testHTTPRouteStatusBackends(name, namespace string, services []gwv1beta1.BackendObjectReference, parentStatuses []gwv1beta1.RouteParentStatus) *gwv1beta1.HTTPRoute { + var parentRefs []gwv1beta1.ParentReference + + for _, parent := range parentStatuses { + parentRefs = append(parentRefs, parent.ParentRef) + } + + route := testHTTPRouteBackends(name, namespace, services, parentRefs) + route.Status.RouteStatus.Parents = parentStatuses + return route +} + +func testHTTPRouteStatus(name string, services []string, parentStatuses []gwv1beta1.RouteParentStatus, extraParents ...string) gwv1beta1.HTTPRoute { + parentRefs := extraParents + + for _, parent := range parentStatuses { + parentRefs = append(parentRefs, string(parent.ParentRef.Name)) + } + + route := testHTTPRoute(name, parentRefs, services) + route.Status.RouteStatus.Parents = parentStatuses + + return route +} + +func testTCPRoute(name string, parents []string, services []string) gwv1alpha2.TCPRoute { + var parentRefs []gwv1beta1.ParentReference + var rules []gwv1alpha2.TCPRouteRule + + for _, parent := range parents { + parentRefs = append(parentRefs, gwv1beta1.ParentReference{Name: gwv1beta1.ObjectName(parent)}) + } + + for _, service := range services { + rules = append(rules, gwv1alpha2.TCPRouteRule{ + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: gwv1beta1.ObjectName(service), + }, + }, + }, + }) + } + + tcpTypeMeta := metav1.TypeMeta{} + tcpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("TCPRoute")) + + return gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{Name: name, Finalizers: []string{common.GatewayFinalizer}}, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: parentRefs, + }, + Rules: rules, + }, + } +} + +func testTCPRouteBackends(name, namespace string, services []gwv1beta1.BackendObjectReference, parents []gwv1beta1.ParentReference) *gwv1alpha2.TCPRoute { + var rules []gwv1alpha2.TCPRouteRule + for _, service := range services { + rules = append(rules, gwv1alpha2.TCPRouteRule{ + BackendRefs: []gwv1beta1.BackendRef{ + {BackendObjectReference: service}, + }, + }) + } + + tcpTypeMeta := metav1.TypeMeta{} + tcpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("TCPRoute")) + + return &gwv1alpha2.TCPRoute{ + TypeMeta: tcpTypeMeta, + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, Finalizers: []string{common.GatewayFinalizer}}, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: parents, + }, + Rules: rules, + }, + } +} + +func testTCPRouteStatusBackends(name, namespace string, services []gwv1beta1.BackendObjectReference, parentStatuses []gwv1beta1.RouteParentStatus) *gwv1alpha2.TCPRoute { + var parentRefs []gwv1beta1.ParentReference + + for _, parent := range parentStatuses { + parentRefs = append(parentRefs, parent.ParentRef) + } + + route := testTCPRouteBackends(name, namespace, services, parentRefs) + route.Status.RouteStatus.Parents = parentStatuses + return route +} + +func testTCPRouteStatus(name string, services []string, parentStatuses []gwv1beta1.RouteParentStatus, extraParents ...string) gwv1alpha2.TCPRoute { + parentRefs := extraParents + + for _, parent := range parentStatuses { + parentRefs = append(parentRefs, string(parent.ParentRef.Name)) + } + + route := testTCPRoute(name, parentRefs, services) + route.Status.RouteStatus.Parents = parentStatuses + + return route +} + +func controlledBinder(config BinderConfig) BinderConfig { + config.ControllerName = testControllerName + config.GatewayClass = testGatewayClass + return config +} + +func generateTestCertificate(t *testing.T, namespace, name string) (*api.InlineCertificateConfigEntry, corev1.Secret) { + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(t, err) + + usage := x509.KeyUsageCertSign + expiration := time.Now().AddDate(10, 0, 0) + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "consul.test", + }, + IsCA: true, + NotBefore: time.Now().Add(-10 * time.Minute), + NotAfter: expiration, + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: usage, + BasicConstraintsValid: true, + } + caCert := cert + caPrivateKey := privateKey + + data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) + require.NoError(t, err) + + certBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: data, + }) + + privateKeyBytes := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Data: map[string][]byte{ + corev1.TLSCertKey: certBytes, + corev1.TLSPrivateKeyKey: privateKeyBytes, + }, } + + certificate, err := (common.ResourceTranslator{}).ToInlineCertificate(secret) + require.NoError(t, err) + + return certificate, secret } diff --git a/control-plane/api-gateway/controllers/reference_validator.go b/control-plane/api-gateway/binding/reference_grant.go similarity index 58% rename from control-plane/api-gateway/controllers/reference_validator.go rename to control-plane/api-gateway/binding/reference_grant.go index 91b4c0ea51..12c0f3b048 100644 --- a/control-plane/api-gateway/controllers/reference_validator.go +++ b/control-plane/api-gateway/binding/reference_grant.go @@ -1,27 +1,37 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package binding import ( - "context" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -type ReferenceValidator struct { - client.Client +type referenceValidator struct { + grants map[string]map[types.NamespacedName]gwv1beta1.ReferenceGrant } -func NewReferenceValidator(client client.Client) *ReferenceValidator { - return &ReferenceValidator{ - client, +func NewReferenceValidator(grants []gwv1beta1.ReferenceGrant) common.ReferenceValidator { + byNamespace := make(map[string]map[types.NamespacedName]gwv1beta1.ReferenceGrant) + for _, grant := range grants { + grantsForNamespace, ok := byNamespace[grant.Namespace] + if !ok { + grantsForNamespace = make(map[types.NamespacedName]gwv1beta1.ReferenceGrant) + } + grantsForNamespace[client.ObjectKeyFromObject(&grant)] = grant + byNamespace[grant.Namespace] = grantsForNamespace + } + return &referenceValidator{ + grants: byNamespace, } } -func (rv *ReferenceValidator) GatewayCanReferenceSecret(ctx context.Context, gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) (bool, error) { +func (rv *referenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool { fromNS := gateway.GetNamespace() fromGK := metav1.GroupKind{ Group: gateway.GroupVersionKind().Group, @@ -30,12 +40,12 @@ func (rv *ReferenceValidator) GatewayCanReferenceSecret(ctx context.Context, gat // Kind should default to Secret if not set // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#LL59C21-L59C21 - toNS, toGK := createValuesFromRef(secretRef.Namespace, secretRef.Group, secretRef.Kind, "Secret") + toNS, toGK := createValuesFromRef(secretRef.Namespace, secretRef.Group, secretRef.Kind, "", common.KindSecret) - return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(secretRef.Name), rv.Client) + return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(secretRef.Name)) } -func (rv *ReferenceValidator) HTTPRouteCanReferenceGateway(ctx context.Context, httproute gwv1beta1.HTTPRoute, parentRef gwv1beta1.ParentReference) (bool, error) { +func (rv *referenceValidator) HTTPRouteCanReferenceGateway(httproute gwv1beta1.HTTPRoute, parentRef gwv1beta1.ParentReference) bool { fromNS := httproute.GetNamespace() fromGK := metav1.GroupKind{ Group: httproute.GroupVersionKind().Group, @@ -44,12 +54,12 @@ func (rv *ReferenceValidator) HTTPRouteCanReferenceGateway(ctx context.Context, // Kind should default to Gateway if not set // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/shared_types.go#L48 - toNS, toGK := createValuesFromRef(parentRef.Namespace, parentRef.Group, parentRef.Kind, "Gateway") + toNS, toGK := createValuesFromRef(parentRef.Namespace, parentRef.Group, parentRef.Kind, common.BetaGroup, common.KindGateway) - return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(parentRef.Name), rv.Client) + return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(parentRef.Name)) } -func (rv *ReferenceValidator) HTTPRouteCanReferenceBackend(ctx context.Context, httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) (bool, error) { +func (rv *referenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { fromNS := httproute.GetNamespace() fromGK := metav1.GroupKind{ Group: httproute.GroupVersionKind().Group, @@ -58,13 +68,12 @@ func (rv *ReferenceValidator) HTTPRouteCanReferenceBackend(ctx context.Context, // Kind should default to Service if not set // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#L106 - toNS, toGK := createValuesFromRef(backendRef.Namespace, backendRef.Group, backendRef.Kind, "Service") - - return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(backendRef.Name), rv.Client) + toNS, toGK := createValuesFromRef(backendRef.Namespace, backendRef.Group, backendRef.Kind, "", common.KindService) + return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(backendRef.Name)) } -func (rv *ReferenceValidator) TCPRouteCanReferenceGateway(ctx context.Context, tcpRoute gwv1alpha2.TCPRoute, parentRef gwv1beta1.ParentReference) (bool, error) { +func (rv *referenceValidator) TCPRouteCanReferenceGateway(tcpRoute gwv1alpha2.TCPRoute, parentRef gwv1beta1.ParentReference) bool { fromNS := tcpRoute.GetNamespace() fromGK := metav1.GroupKind{ Group: tcpRoute.GroupVersionKind().Group, @@ -73,12 +82,12 @@ func (rv *ReferenceValidator) TCPRouteCanReferenceGateway(ctx context.Context, t // Kind should default to Gateway if not set // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/shared_types.go#L48 - toNS, toGK := createValuesFromRef(parentRef.Namespace, parentRef.Group, parentRef.Kind, "Gateway") + toNS, toGK := createValuesFromRef(parentRef.Namespace, parentRef.Group, parentRef.Kind, common.BetaGroup, common.KindGateway) - return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(parentRef.Name), rv.Client) + return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(parentRef.Name)) } -func (rv *ReferenceValidator) TCPRouteCanReferenceBackend(ctx context.Context, tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) (bool, error) { +func (rv *referenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { fromNS := tcpRoute.GetNamespace() fromGK := metav1.GroupKind{ Group: tcpRoute.GroupVersionKind().Group, @@ -87,20 +96,20 @@ func (rv *ReferenceValidator) TCPRouteCanReferenceBackend(ctx context.Context, t // Kind should default to Service if not set // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#L106 - toNS, toGK := createValuesFromRef(backendRef.Namespace, backendRef.Group, backendRef.Kind, "Service") - - return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, string(backendRef.Name), rv.Client) + toNS, toGK := createValuesFromRef(backendRef.Namespace, backendRef.Group, backendRef.Kind, common.BetaGroup, common.KindService) + return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(backendRef.Name)) } -func createValuesFromRef(ns *gwv1beta1.Namespace, group *gwv1beta1.Group, kind *gwv1beta1.Kind, defaultKind string) (string, metav1.GroupKind) { +func createValuesFromRef(ns *gwv1beta1.Namespace, group *gwv1beta1.Group, kind *gwv1beta1.Kind, defaultGroup, defaultKind string) (string, metav1.GroupKind) { toNS := "" if ns != nil { toNS = string(*ns) } gk := metav1.GroupKind{ - Kind: defaultKind, + Kind: defaultKind, + Group: defaultGroup, } if group != nil { gk.Group = string(*group) @@ -119,22 +128,22 @@ func createValuesFromRef(ns *gwv1beta1.Namespace, group *gwv1beta1.Group, kind * // // For example, a Gateway in namespace "foo" may only reference a Secret in namespace "bar" // if a ReferenceGrant in namespace "bar" allows references from namespace "foo". -func referenceAllowed(ctx context.Context, fromGK metav1.GroupKind, fromNamespace string, toGK metav1.GroupKind, toNamespace, toName string, c client.Client) (bool, error) { +func (rv *referenceValidator) referenceAllowed(fromGK metav1.GroupKind, fromNamespace string, toGK metav1.GroupKind, toNamespace, toName string) bool { // Reference does not cross namespaces if toNamespace == "" || toNamespace == fromNamespace { - return true, nil + return true } // Fetch all ReferenceGrants in the referenced namespace - refGrants, err := getReferenceGrantsInNamespace(ctx, toNamespace, c) - if err != nil || len(refGrants) == 0 { - return false, err + grants, ok := rv.grants[toNamespace] + if !ok { + return false } - for _, refGrant := range refGrants { + for _, grant := range grants { // Check for a From that applies fromMatch := false - for _, from := range refGrant.Spec.From { + for _, from := range grant.Spec.From { if fromGK.Group == string(from.Group) && fromGK.Kind == string(from.Kind) && fromNamespace == string(from.Namespace) { fromMatch = true break @@ -146,32 +155,21 @@ func referenceAllowed(ctx context.Context, fromGK metav1.GroupKind, fromNamespac } // Check for a To that applies - for _, to := range refGrant.Spec.To { + for _, to := range grant.Spec.To { if toGK.Group == string(to.Group) && toGK.Kind == string(to.Kind) { if to.Name == nil || *to.Name == "" { // No name specified is treated as a wildcard within the namespace - return true, nil + return true } if gwv1beta1.ObjectName(toName) == *to.Name { // The ReferenceGrant specifically targets this object - return true, nil + return true } } } } // No ReferenceGrant was found which allows this cross-namespace reference - return false, nil -} - -// This function will get all reference grants in the given namespace. -func getReferenceGrantsInNamespace(ctx context.Context, namespace string, c client.Client) ([]gwv1beta1.ReferenceGrant, error) { - refGrantList := &gwv1beta1.ReferenceGrantList{} - if err := c.List(ctx, refGrantList, client.InNamespace(namespace)); err != nil { - return nil, err - } - refGrants := refGrantList.Items - - return refGrants, nil + return false } diff --git a/control-plane/api-gateway/controllers/reference_validator_test.go b/control-plane/api-gateway/binding/reference_grant_test.go similarity index 79% rename from control-plane/api-gateway/controllers/reference_validator_test.go rename to control-plane/api-gateway/binding/reference_grant_test.go index e507568ae1..a325a2e927 100644 --- a/control-plane/api-gateway/controllers/reference_validator_test.go +++ b/control-plane/api-gateway/binding/reference_grant_test.go @@ -1,18 +1,17 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package binding import ( "context" - "k8s.io/apimachinery/pkg/runtime" + "testing" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "testing" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) const ( @@ -34,7 +33,7 @@ func TestGatewayCanReferenceSecret(t *testing.T) { objName := gwv1beta1.ObjectName("mysecret") - basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Namespace: ToNamespace, @@ -67,7 +66,7 @@ func TestGatewayCanReferenceSecret(t *testing.T) { ctx context.Context gateway gwv1beta1.Gateway secret gwv1beta1.SecretObjectReference - k8sReferenceGrants []runtime.Object + k8sReferenceGrants []gwv1beta1.ReferenceGrant }{ "gateway allowed to secret": { canReference: true, @@ -90,7 +89,7 @@ func TestGatewayCanReferenceSecret(t *testing.T) { Namespace: &secretRefNamespace, Name: objName, }, - k8sReferenceGrants: []runtime.Object{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ basicValidReferenceGrant, }, }, @@ -98,15 +97,9 @@ func TestGatewayCanReferenceSecret(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, gwv1beta1.AddToScheme(s)) + rv := NewReferenceValidator(tc.k8sReferenceGrants) + canReference := rv.GatewayCanReferenceSecret(tc.gateway, tc.secret) - client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() - rv := NewReferenceValidator(client) - canReference, err := rv.GatewayCanReferenceSecret(tc.ctx, tc.gateway, tc.secret) - - require.Equal(t, tc.err, err) require.Equal(t, tc.canReference, canReference) }) } @@ -117,7 +110,7 @@ func TestHTTPRouteCanReferenceGateway(t *testing.T) { objName := gwv1beta1.ObjectName("mygateway") - basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Namespace: ToNamespace, @@ -150,7 +143,7 @@ func TestHTTPRouteCanReferenceGateway(t *testing.T) { ctx context.Context httpRoute gwv1beta1.HTTPRoute gatewayRef gwv1beta1.ParentReference - k8sReferenceGrants []runtime.Object + k8sReferenceGrants []gwv1beta1.ReferenceGrant }{ "httproute allowed to gateway": { canReference: true, @@ -175,7 +168,7 @@ func TestHTTPRouteCanReferenceGateway(t *testing.T) { SectionName: nil, Port: nil, }, - k8sReferenceGrants: []runtime.Object{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ basicValidReferenceGrant, }, }, @@ -183,15 +176,9 @@ func TestHTTPRouteCanReferenceGateway(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, gwv1beta1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() - rv := NewReferenceValidator(client) - canReference, err := rv.HTTPRouteCanReferenceGateway(tc.ctx, tc.httpRoute, tc.gatewayRef) + rv := NewReferenceValidator(tc.k8sReferenceGrants) + canReference := rv.HTTPRouteCanReferenceGateway(tc.httpRoute, tc.gatewayRef) - require.Equal(t, tc.err, err) require.Equal(t, tc.canReference, canReference) }) } @@ -202,7 +189,7 @@ func TestHTTPRouteCanReferenceBackend(t *testing.T) { objName := gwv1beta1.ObjectName("myBackendRef") - basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Namespace: ToNamespace, @@ -235,7 +222,7 @@ func TestHTTPRouteCanReferenceBackend(t *testing.T) { ctx context.Context httpRoute gwv1beta1.HTTPRoute backendRef gwv1beta1.BackendRef - k8sReferenceGrants []runtime.Object + k8sReferenceGrants []gwv1beta1.ReferenceGrant }{ "httproute allowed to gateway": { canReference: true, @@ -262,7 +249,7 @@ func TestHTTPRouteCanReferenceBackend(t *testing.T) { }, Weight: nil, }, - k8sReferenceGrants: []runtime.Object{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ basicValidReferenceGrant, }, }, @@ -270,15 +257,9 @@ func TestHTTPRouteCanReferenceBackend(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, gwv1beta1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() - rv := NewReferenceValidator(client) - canReference, err := rv.HTTPRouteCanReferenceBackend(tc.ctx, tc.httpRoute, tc.backendRef) + rv := NewReferenceValidator(tc.k8sReferenceGrants) + canReference := rv.HTTPRouteCanReferenceBackend(tc.httpRoute, tc.backendRef) - require.Equal(t, tc.err, err) require.Equal(t, tc.canReference, canReference) }) } @@ -289,7 +270,7 @@ func TestTCPRouteCanReferenceGateway(t *testing.T) { objName := gwv1beta1.ObjectName("mygateway") - basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Namespace: ToNamespace, @@ -322,7 +303,7 @@ func TestTCPRouteCanReferenceGateway(t *testing.T) { ctx context.Context tcpRoute gwv1alpha2.TCPRoute gatewayRef gwv1beta1.ParentReference - k8sReferenceGrants []runtime.Object + k8sReferenceGrants []gwv1beta1.ReferenceGrant }{ "tcpRoute allowed to gateway": { canReference: true, @@ -347,7 +328,7 @@ func TestTCPRouteCanReferenceGateway(t *testing.T) { SectionName: nil, Port: nil, }, - k8sReferenceGrants: []runtime.Object{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ basicValidReferenceGrant, }, }, @@ -355,15 +336,9 @@ func TestTCPRouteCanReferenceGateway(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, gwv1beta1.AddToScheme(s)) + rv := NewReferenceValidator(tc.k8sReferenceGrants) + canReference := rv.TCPRouteCanReferenceGateway(tc.tcpRoute, tc.gatewayRef) - client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() - rv := NewReferenceValidator(client) - canReference, err := rv.TCPRouteCanReferenceGateway(tc.ctx, tc.tcpRoute, tc.gatewayRef) - - require.Equal(t, tc.err, err) require.Equal(t, tc.canReference, canReference) }) } @@ -374,7 +349,7 @@ func TestTCPRouteCanReferenceBackend(t *testing.T) { objName := gwv1beta1.ObjectName("myBackendRef") - basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Namespace: ToNamespace, @@ -407,7 +382,7 @@ func TestTCPRouteCanReferenceBackend(t *testing.T) { ctx context.Context tcpRoute gwv1alpha2.TCPRoute backendRef gwv1beta1.BackendRef - k8sReferenceGrants []runtime.Object + k8sReferenceGrants []gwv1beta1.ReferenceGrant }{ "tcpRoute allowed to gateway": { canReference: true, @@ -434,7 +409,7 @@ func TestTCPRouteCanReferenceBackend(t *testing.T) { }, Weight: nil, }, - k8sReferenceGrants: []runtime.Object{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ basicValidReferenceGrant, }, }, @@ -442,15 +417,9 @@ func TestTCPRouteCanReferenceBackend(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, gwv1beta1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() - rv := NewReferenceValidator(client) - canReference, err := rv.TCPRouteCanReferenceBackend(tc.ctx, tc.tcpRoute, tc.backendRef) + rv := NewReferenceValidator(tc.k8sReferenceGrants) + canReference := rv.TCPRouteCanReferenceBackend(tc.tcpRoute, tc.backendRef) - require.Equal(t, tc.err, err) require.Equal(t, tc.canReference, canReference) }) } @@ -461,7 +430,7 @@ func TestReferenceAllowed(t *testing.T) { objName := gwv1beta1.ObjectName("myObject") - basicValidReferenceGrant := &gwv1beta1.ReferenceGrant{ + basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Namespace: ToNamespace, @@ -493,7 +462,7 @@ func TestReferenceAllowed(t *testing.T) { toGK metav1.GroupKind toNamespace string toName string - k8sReferenceGrants []runtime.Object + k8sReferenceGrants []gwv1beta1.ReferenceGrant }{ "same namespace": { refAllowed: true, @@ -510,8 +479,8 @@ func TestReferenceAllowed(t *testing.T) { }, toNamespace: FromNamespace, toName: string(objName), - k8sReferenceGrants: []runtime.Object{ - &gwv1beta1.ReferenceGrant{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ + { TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Namespace: FromNamespace, @@ -550,7 +519,7 @@ func TestReferenceAllowed(t *testing.T) { }, toNamespace: ToNamespace, toName: string(objName), - k8sReferenceGrants: []runtime.Object{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ basicValidReferenceGrant, }, }, @@ -569,7 +538,7 @@ func TestReferenceAllowed(t *testing.T) { }, toNamespace: ToNamespace, toName: string(objName), - k8sReferenceGrants: []runtime.Object{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ basicValidReferenceGrant, }, }, @@ -605,8 +574,8 @@ func TestReferenceAllowed(t *testing.T) { }, toNamespace: ToNamespace, toName: string(objName), - k8sReferenceGrants: []runtime.Object{ - &gwv1beta1.ReferenceGrant{ + k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ + { TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Namespace: ToNamespace, @@ -634,15 +603,9 @@ func TestReferenceAllowed(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, gwv1beta1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.k8sReferenceGrants...).Build() - - refAllowed, err := referenceAllowed(tc.ctx, tc.fromGK, tc.fromNamespace, tc.toGK, tc.toNamespace, tc.toName, client) + rv := NewReferenceValidator(tc.k8sReferenceGrants).(*referenceValidator) + refAllowed := rv.referenceAllowed(tc.fromGK, tc.fromNamespace, tc.toGK, tc.toNamespace, tc.toName) - require.Equal(t, tc.err, err) require.Equal(t, tc.refAllowed, refAllowed) }) } diff --git a/control-plane/api-gateway/binding/references.go b/control-plane/api-gateway/binding/references.go deleted file mode 100644 index ec598c6364..0000000000 --- a/control-plane/api-gateway/binding/references.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// referenceTracker acts as a reference counting object for: -// 1. the number of controlled gateways that are referenced by an HTTPRoute -// 2. the number of controlled gateways that are referenced by a TCPRoute -// 3. the number of gateways that reference a certificate Secret -// -// These are used for determining when dissasociating from a gateway -// should cause us to cleanup a route or certificate both in Consul and -// whatever state we have set on the object in Kubernetes. -type referenceTracker struct { - httpRouteReferencesGateways map[types.NamespacedName]int - tcpRouteReferencesGateways map[types.NamespacedName]int - certificatesReferencedByGateways map[types.NamespacedName]int -} - -// isLastReference checks if the given gateway is the last controlled gateway -// that a route references. If it is and the gateway has been deleted, we -// should clean up all state created for the route. -func (r referenceTracker) isLastReference(object client.Object) bool { - key := types.NamespacedName{ - Namespace: object.GetNamespace(), - Name: object.GetName(), - } - - switch object.(type) { - case *gwv1alpha2.TCPRoute: - return r.tcpRouteReferencesGateways[key] == 1 - case *gwv1beta1.HTTPRoute: - return r.httpRouteReferencesGateways[key] == 1 - default: - return false - } -} - -// canGCSecret checks if we can garbage collect a secret that has -// not been upserted. -func (r referenceTracker) canGCSecret(key types.NamespacedName) bool { - // should this be 1 or 0? - return r.certificatesReferencedByGateways[key] == 1 -} - -// references initializes a referenceTracker based on the HTTPRoutes, TCPRoutes, -// and ControlledGateways associated with this Binder. -func (b *Binder) references() referenceTracker { - tracker := referenceTracker{ - httpRouteReferencesGateways: make(map[types.NamespacedName]int), - tcpRouteReferencesGateways: make(map[types.NamespacedName]int), - certificatesReferencedByGateways: make(map[types.NamespacedName]int), - } - - for _, route := range b.config.HTTPRoutes { - references := map[types.NamespacedName]struct{}{} - for _, ref := range route.Spec.ParentRefs { - for _, gateway := range b.config.ControlledGateways { - parentName := string(ref.Name) - parentNamespace := valueOr(ref.Namespace, route.Namespace) - if nilOrEqual(ref.Group, betaGroup) && - nilOrEqual(ref.Kind, kindGateway) && - gateway.Namespace == parentNamespace && - gateway.Name == parentName { - // the route references a gateway we control, store the ref to this gateway - references[types.NamespacedName{ - Namespace: parentNamespace, - Name: parentName, - }] = struct{}{} - } - } - } - tracker.httpRouteReferencesGateways[types.NamespacedName{ - Namespace: route.Namespace, - Name: route.Name, - }] = len(references) - } - - for _, route := range b.config.TCPRoutes { - references := map[types.NamespacedName]struct{}{} - for _, ref := range route.Spec.ParentRefs { - for _, gateway := range b.config.ControlledGateways { - parentName := string(ref.Name) - parentNamespace := valueOr(ref.Namespace, route.Namespace) - if nilOrEqual(ref.Group, betaGroup) && - nilOrEqual(ref.Kind, kindGateway) && - gateway.Namespace == parentNamespace && - gateway.Name == parentName { - // the route references a gateway we control, store the ref to this gateway - references[types.NamespacedName{ - Namespace: parentNamespace, - Name: parentName, - }] = struct{}{} - } - } - } - tracker.tcpRouteReferencesGateways[types.NamespacedName{ - Namespace: route.Namespace, - Name: route.Name, - }] = len(references) - } - - for _, gateway := range b.config.ControlledGateways { - references := map[types.NamespacedName]struct{}{} - for _, listener := range gateway.Spec.Listeners { - if listener.TLS == nil { - continue - } - for _, ref := range listener.TLS.CertificateRefs { - if nilOrEqual(ref.Group, "") && - nilOrEqual(ref.Kind, kindSecret) { - // the gateway references a secret, store it - references[types.NamespacedName{ - Namespace: valueOr(ref.Namespace, gateway.Namespace), - Name: string(ref.Name), - }] = struct{}{} - } - } - } - - for ref := range references { - count := tracker.certificatesReferencedByGateways[ref] - tracker.certificatesReferencedByGateways[ref] = count + 1 - } - } - - return tracker -} diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index 0fd291b0ef..65198eeaf4 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -9,11 +9,23 @@ import ( "sort" "strings" + mapset "github.com/deckarep/golang-set" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) +var ( + // override function for tests. + timeFunc = metav1.Now +) + +var ( + // This is used for any error related to a lack of proper reference grant creation. + errRefNotPermitted = errors.New("reference not permitted due to lack of ReferenceGrant") +) + var ( // Each of the below are specified in the Gateway spec under RouteConditionReason // the general usage is that each error is specified as errRoute* where * corresponds @@ -25,7 +37,6 @@ var ( errRouteNoMatchingListenerHostname = errors.New("listener cannot bind route with a non-aligned hostname") errRouteInvalidKind = errors.New("invalid backend kind") errRouteBackendNotFound = errors.New("backend not found") - errRouteRefNotPermitted = errors.New("reference not permitted due to lack of ReferenceGrant") ) // routeValidationResult holds the result of validating a route globally, in other @@ -44,8 +55,8 @@ type routeValidationResult struct { // a validation error. func (v routeValidationResult) Type() string { return (&metav1.GroupKind{ - Group: valueOr(v.backend.Group, ""), - Kind: valueOr(v.backend.Kind, "Service"), + Group: common.ValueOr(v.backend.Group, ""), + Kind: common.ValueOr(v.backend.Kind, common.KindService), }).String() } @@ -81,7 +92,7 @@ func (e routeValidationResults) Condition() metav1.Condition { Reason: "BackendNotFound", Message: fmt.Sprintf("%s: %s", v.String(), err.Error()), } - case errRouteRefNotPermitted: + case errRefNotPermitted: return metav1.Condition{ Type: "ResolvedRefs", Status: metav1.ConditionFalse, @@ -167,6 +178,10 @@ func (b bindResults) Condition() metav1.Condition { if result.err == errRouteNoMatchingListenerHostname { reason = "NoMatchingListenerHostname" } + // or if we have a ref not permitted, then use that + if result.err == errRefNotPermitted { + reason = "RefNotPermitted" + } } } @@ -188,6 +203,18 @@ type parentBindResult struct { // attempted to bind to a gateway using its parent references. type parentBindResults []parentBindResult +func (p parentBindResults) boundSections() mapset.Set { + set := mapset.NewSet() + for _, result := range p { + for _, r := range result.results { + if r.err == nil { + set.Add(string(r.section)) + } + } + } + return set +} + var ( // Each of the below are specified in the Gateway spec under ListenerConditionReason // the general usage is that each error is specified as errListener* where * corresponds @@ -200,6 +227,7 @@ var ( errListenerProtocolConflict = errors.New("listener protocol conflicts with another listener") errListenerInvalidCertificateRef_NotFound = errors.New("certificate not found") errListenerInvalidCertificateRef_NotSupported = errors.New("certificate type is not supported") + errListenerInvalidCertificateRef_InvalidData = errors.New("certificate is invalid or does not contain a supported server name") // Below is where any custom generic listener validation errors should go. // We map anything under here to a custom ListenerConditionReason of Invalid on @@ -223,7 +251,7 @@ type listenerValidationResult struct { // acceptedCondition constructs the condition for the Accepted status type. func (l listenerValidationResult) acceptedCondition(generation int64) metav1.Condition { - now := metav1.Now() + now := timeFunc() switch l.acceptedErr { case errListenerPortUnavailable: return metav1.Condition{ @@ -267,7 +295,7 @@ func (l listenerValidationResult) acceptedCondition(generation int64) metav1.Con // conflictedCondition constructs the condition for the Conflicted status type. func (l listenerValidationResult) conflictedCondition(generation int64) metav1.Condition { - now := metav1.Now() + now := timeFunc() switch l.conflictedErr { case errListenerProtocolConflict: @@ -302,10 +330,10 @@ func (l listenerValidationResult) conflictedCondition(generation int64) metav1.C // acceptedCondition constructs the condition for the ResolvedRefs status type. func (l listenerValidationResult) resolvedRefsCondition(generation int64) metav1.Condition { - now := metav1.Now() + now := timeFunc() switch l.refErr { - case errListenerInvalidCertificateRef_NotFound: + case errListenerInvalidCertificateRef_NotFound, errListenerInvalidCertificateRef_NotSupported, errListenerInvalidCertificateRef_InvalidData: return metav1.Condition{ Type: "ResolvedRefs", Status: metav1.ConditionFalse, @@ -314,11 +342,11 @@ func (l listenerValidationResult) resolvedRefsCondition(generation int64) metav1 Message: l.refErr.Error(), LastTransitionTime: now, } - case errListenerInvalidCertificateRef_NotSupported: + case errRefNotPermitted: return metav1.Condition{ Type: "ResolvedRefs", Status: metav1.ConditionFalse, - Reason: "InvalidCertificateRef", + Reason: "RefNotPermitted", ObservedGeneration: generation, Message: l.refErr.Error(), LastTransitionTime: now, @@ -387,7 +415,7 @@ type gatewayValidationResult struct { // programmedCondition returns a condition for the Programmed status type. func (l gatewayValidationResult) programmedCondition(generation int64) metav1.Condition { - now := metav1.Now() + now := timeFunc() switch l.programmedErr { case errGatewayPending_Pods, errGatewayPending_Consul: @@ -415,7 +443,7 @@ func (l gatewayValidationResult) programmedCondition(generation int64) metav1.Co // for whether or not any of the gateway's listeners are invalid, if they are, it overrides whatever // Reason is set as an error on the result and instead uses the ListenersNotValid reason. func (l gatewayValidationResult) acceptedCondition(generation int64, listenersInvalid bool) metav1.Condition { - now := metav1.Now() + now := timeFunc() if l.acceptedErr == nil { if listenersInvalid { diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index 5175c8f647..187d579852 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -4,10 +4,9 @@ package binding import ( - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + mapset "github.com/deckarep/golang-set" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul/api" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -15,237 +14,91 @@ import ( gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -// consulHTTPRouteFor returns the Consul HTTPRouteConfigEntry for the given reference. -func (b *Binder) consulHTTPRouteFor(ref api.ResourceReference) *api.HTTPRouteConfigEntry { - for _, route := range b.config.ConsulHTTPRoutes { - if route.Namespace == ref.Namespace && route.Partition == ref.Partition && route.Name == ref.Name { - return &route - } - } - return nil -} - -// consulTCPRouteFor returns the Consul TCPRouteConfigEntry for the given reference. -func (b *Binder) consulTCPRouteFor(ref api.ResourceReference) *api.TCPRouteConfigEntry { - for _, route := range b.config.ConsulTCPRoutes { - if route.Namespace == ref.Namespace && route.Partition == ref.Partition && route.Name == ref.Name { - return &route - } - } - return nil -} - -// routeBinder encapsulates the binding logic for binding a route to the given Gateway. -// The logic for route binding is almost identical between different route types, but -// due to the strong typing in the Spec and Go's inability to deal with fields via generics -// we have to pull in a bunch of accessors (which ideally should be in the upstream spec) -// for each route type. -// -// From the generic signature -- T: the type of Kubernetes route, U: the type of Consul config entry -// -// TODO: consider moving the function closures to something like an interface that we can -// implement the accessors on for each route type. -type routeBinder[T client.Object, U api.ConfigEntry] struct { - // isGatewayDeleted is used to determine whether we should just ignore - // attempting to bind the route (since we no longer know whether we - // should manage the route we only want to remove any state we've - // set on it). - isGatewayDeleted bool - // gateway is the gateway that we want to use for binding - gateway *gwv1beta1.Gateway - // gatewayRef is a Consul reference used to prune no-longer bound - // parents from a Consul resource we've created. - gatewayRef api.ResourceReference - // tracker is the referenceTracker used to determine when we want to cleanup - // routes based on a deleted gateway. - tracker referenceTracker - // namespaces is the set of namespaces in Consul that use for determining - // whether a route in a given namespace can bind to a gateway with AllowedRoutes set - namespaces map[string]corev1.Namespace - // services is a catalog of all connect-injected services to check a route against - // for resolving its backend refs - services map[types.NamespacedName]api.CatalogService - // meshServices is a catalog of all mesh service objects to check a route against - // for resolving its backend refs - meshServices map[types.NamespacedName]v1alpha1.MeshService - - // translationReferenceFunc is a function used to translate a Kubernetes object into - // a Consul object reference - translationReferenceFunc func(route T) api.ResourceReference - // lookupFunc is a function used for finding an existing Consul object based on - // its object reference - lookupFunc func(api.ResourceReference) U - // getParentsFunc is a function used for getting the parent references of a Consul route object - getParentsFunc func(U) []api.ResourceReference - // setParentsFunc is a function used for setting the parent references of a route object - setParentsFunc func(U, []api.ResourceReference) - // removeStatusRefsFunc is a function used for removing the statuses for the given parent - // references from a route - removeStatusRefsFunc func(T, []gwv1beta1.ParentReference) bool - // getHostnamesFunc is a function used for getting the hostnames associated with a route - getHostnamesFunc func(T) []gwv1beta1.Hostname - // getParentRefsFunc is used for getting the parent references of a Kubernetes route object - getParentRefsFunc func(T) []gwv1beta1.ParentReference - // translationFunc is used for translating a Kubernetes route into the corresponding Consul config entry - translationFunc func(T, map[types.NamespacedName]api.ResourceReference, map[types.NamespacedName]api.CatalogService, map[types.NamespacedName]v1alpha1.MeshService) U - // setRouteConditionFunc is used for adding or overwriting a condition on a route at the given - // parent - setRouteConditionFunc func(T, *gwv1beta1.ParentReference, metav1.Condition) bool - // getBackendRefsFunc returns a list of all backend references that we need to validate against the - // list of known connect-injected services - getBackendRefsFunc func(T) []gwv1beta1.BackendRef - // removeControllerStatusFunc is used to remove all of the statuses set by our controller when GC'ing - // a route - removeControllerStatusFunc func(T) bool -} - -// newRouteBinder creates a new route binder for the given Kubernetes and Consul route types -// generally this is lightly wrapped by other constructors that pass in the various closures -// needed for accessing fields on the objects. -func newRouteBinder[T client.Object, U api.ConfigEntry]( - isGatewayDeleted bool, - gateway *gwv1beta1.Gateway, - gatewayRef api.ResourceReference, - namespaces map[string]corev1.Namespace, - services map[types.NamespacedName]api.CatalogService, - meshServices map[types.NamespacedName]v1alpha1.MeshService, - tracker referenceTracker, - translationReferenceFunc func(route T) api.ResourceReference, - lookupFunc func(api.ResourceReference) U, - getParentsFunc func(U) []api.ResourceReference, - setParentsFunc func(U, []api.ResourceReference), - removeStatusRefsFunc func(T, []gwv1beta1.ParentReference) bool, - getHostnamesFunc func(T) []gwv1beta1.Hostname, - getParentRefsFunc func(T) []gwv1beta1.ParentReference, - translationFunc func(T, map[types.NamespacedName]api.ResourceReference, map[types.NamespacedName]api.CatalogService, map[types.NamespacedName]v1alpha1.MeshService) U, - setRouteConditionFunc func(T, *gwv1beta1.ParentReference, metav1.Condition) bool, - getBackendRefsFunc func(T) []gwv1beta1.BackendRef, - removeControllerStatusFunc func(T) bool, -) *routeBinder[T, U] { - return &routeBinder[T, U]{ - isGatewayDeleted: isGatewayDeleted, - gateway: gateway, - gatewayRef: gatewayRef, - namespaces: namespaces, - services: services, - meshServices: meshServices, - tracker: tracker, - translationReferenceFunc: translationReferenceFunc, - lookupFunc: lookupFunc, - getParentsFunc: getParentsFunc, - setParentsFunc: setParentsFunc, - removeStatusRefsFunc: removeStatusRefsFunc, - getHostnamesFunc: getHostnamesFunc, - getParentRefsFunc: getParentRefsFunc, - translationFunc: translationFunc, - setRouteConditionFunc: setRouteConditionFunc, - getBackendRefsFunc: getBackendRefsFunc, - removeControllerStatusFunc: removeControllerStatusFunc, - } -} - -// bind contains the main logic for binding a route to a given gateway. -func (r *routeBinder[T, U]) bind(route T, boundCount map[gwv1beta1.SectionName]int, seenRoutes map[api.ResourceReference]struct{}, snapshot Snapshot) (updatedSnapshot Snapshot) { - routeRef := r.translationReferenceFunc(route) - existing := r.lookupFunc(routeRef) - gatewayRefs := filterParentRefs(objectToMeta(r.gateway), route.GetNamespace(), r.getParentRefsFunc(route)) - - // mark this route as having been processed - seenRoutes[routeRef] = struct{}{} +// bindRoute contains the main logic for binding a route to a given gateway. +func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.SectionName]int, snapshot *Snapshot) { + // use the non-normalized key since we can't write back enterprise metadata + // on non-enterprise installations + routeConsulKey := r.config.Translator.NonNormalizedConfigEntryReference(entryKind(route), client.ObjectKeyFromObject(route)) + filteredParents := filterParentRefs(r.key, route.GetNamespace(), getRouteParents(route)) // flags to mark that some operation needs to occur - consulNeedsDelete := false kubernetesNeedsUpdate := false kubernetesNeedsStatusUpdate := false - // Since the update can either be for an existing resource (in the case - // of a deleted gateway) or for a resource generated by translating a - // bound gateway we just set the resource that we want to push out an - // update for. If this is not nil, we push it into the snapshot. - var consulUpdate U // we do this in a closure at the end to make sure we don't accidentally // add something multiple times into the list of update/delete operations // instead we just set a flag indicating that an update is needed and then // append to the snapshot right before returning defer func() { - if !isNil(consulUpdate) { - snapshot.Consul.Updates = append(snapshot.Consul.Updates, consulUpdate) - } - if consulNeedsDelete { - snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, routeRef) - } if kubernetesNeedsUpdate { - snapshot.Kubernetes.Updates = append(snapshot.Kubernetes.Updates, route) + snapshot.Kubernetes.Updates.Add(route) } if kubernetesNeedsStatusUpdate { - snapshot.Kubernetes.StatusUpdates = append(snapshot.Kubernetes.StatusUpdates, route) + snapshot.Kubernetes.StatusUpdates.Add(route) } - - updatedSnapshot = snapshot }() if isDeleted(route) { // mark the route as needing to get cleaned up if we detect that it's being deleted - consulNeedsDelete = true - if removeFinalizer(route) { + if common.RemoveFinalizer(route) { kubernetesNeedsUpdate = true } return } - if r.isGatewayDeleted { - // first check if this is our only ref for the route - if r.tracker.isLastReference(route) { - // if it is, then mark everything for deletion - consulNeedsDelete = true - if r.removeControllerStatusFunc(route) { - kubernetesNeedsStatusUpdate = true - } - if removeFinalizer(route) { - kubernetesNeedsUpdate = true - } - return + if r.isGatewayDeleted() { + if canGCOnUnbind(routeConsulKey, r.config.Resources) && common.RemoveFinalizer(route) { + kubernetesNeedsUpdate = true + } else { + // Remove the condition since we no longer know if we should + // control the route and drop any references for the Consul route. + // This only gets run if we can't GC the route at the end of this + // loop. + r.dropConsulRouteParent(snapshot, route, r.nonNormalizedConsulKey, r.config.Resources) } - // otherwise remove the condition since we no longer know if we should - // control the route and drop any references for the Consul route - if !isNil(existing) { - // this drops all the parent refs - r.setParentsFunc(existing, parentsForRoute(r.gatewayRef, r.getParentsFunc(existing), nil)) - // and then we mark the route as needing updated - consulUpdate = existing - // drop the status conditions - if r.removeStatusRefsFunc(route, gatewayRefs) { - kubernetesNeedsStatusUpdate = true - } + // drop the status conditions + if r.statusSetter.removeRouteReferences(route, filteredParents) { + kubernetesNeedsStatusUpdate = true } return } - if ensureFinalizer(route) { + if common.EnsureFinalizer(route) { kubernetesNeedsUpdate = true return } // TODO: scrub route refs from statuses that no longer exist - validation := validateRefs(route.GetNamespace(), r.getBackendRefsFunc(route), r.services, r.meshServices) + validation := validateRefs(route, getRouteBackends(route), r.config.Resources) // the spec is dumb and makes you set a parent for any status, even when the // status is not with respect to a parent, as is the case of resolved refs // so we need to set the status on all parents - for _, ref := range gatewayRefs { - if r.setRouteConditionFunc(route, &ref, validation.Condition()) { + for _, parent := range filteredParents { + if r.statusSetter.setRouteCondition(route, &parent, validation.Condition()) { kubernetesNeedsStatusUpdate = true } } - results := make(parentBindResults, 0) - namespace := r.namespaces[route.GetNamespace()] - gk := route.GetObjectKind().GroupVersionKind().GroupKind() - for _, ref := range gatewayRefs { - result := make(bindResults, 0) - for _, listener := range listenersFor(r.gateway, ref.SectionName) { - if !routeKindIsAllowedForListener(supportedKindsForProtocol[listener.Protocol], gk) { + namespace := r.config.Namespaces[route.GetNamespace()] + groupKind := route.GetObjectKind().GroupVersionKind().GroupKind() + + var results parentBindResults + + for _, ref := range filteredParents { + var result bindResults + + for _, listener := range listenersFor(&r.config.Gateway, ref.SectionName) { + if !canReferenceGateway(route, ref, r.config.Resources) { + result = append(result, bindResult{ + section: listener.Name, + err: errRefNotPermitted, + }) + continue + } + + if !routeKindIsAllowedForListener(supportedKindsForProtocol[listener.Protocol], groupKind) { result = append(result, bindResult{ section: listener.Name, err: errRouteNotAllowedByListeners_Protocol, @@ -253,7 +106,7 @@ func (r *routeBinder[T, U]) bind(route T, boundCount map[gwv1beta1.SectionName]i continue } - if !routeKindIsAllowedForListenerExplicit(listener.AllowedRoutes, gk) { + if !routeKindIsAllowedForListenerExplicit(listener.AllowedRoutes, groupKind) { result = append(result, bindResult{ section: listener.Name, err: errRouteNotAllowedByListeners_Protocol, @@ -261,7 +114,7 @@ func (r *routeBinder[T, U]) bind(route T, boundCount map[gwv1beta1.SectionName]i continue } - if !routeAllowedForListenerNamespaces(r.gateway.Namespace, listener.AllowedRoutes, namespace) { + if !routeAllowedForListenerNamespaces(r.config.Gateway.Namespace, listener.AllowedRoutes, namespace) { result = append(result, bindResult{ section: listener.Name, err: errRouteNotAllowedByListeners_Namespace, @@ -269,7 +122,7 @@ func (r *routeBinder[T, U]) bind(route T, boundCount map[gwv1beta1.SectionName]i continue } - if !routeAllowedForListenerHostname(listener.Hostname, r.getHostnamesFunc(route)) { + if !routeAllowedForListenerHostname(listener.Hostname, getRouteHostnames(route)) { result = append(result, bindResult{ section: listener.Name, err: errRouteNoMatchingListenerHostname, @@ -281,7 +134,7 @@ func (r *routeBinder[T, U]) bind(route T, boundCount map[gwv1beta1.SectionName]i section: listener.Name, }) - boundCount[listener.Name] = boundCount[listener.Name] + 1 + boundCount[listener.Name]++ } results = append(results, parentBindResult{ @@ -292,7 +145,7 @@ func (r *routeBinder[T, U]) bind(route T, boundCount map[gwv1beta1.SectionName]i updated := false for _, result := range results { - if r.setRouteConditionFunc(route, &result.parent, result.results.Condition()) { + if r.statusSetter.setRouteCondition(route, &result.parent, result.results.Condition()) { updated = true } } @@ -301,189 +154,285 @@ func (r *routeBinder[T, U]) bind(route T, boundCount map[gwv1beta1.SectionName]i kubernetesNeedsStatusUpdate = true } - entry := r.translationFunc(route, nil, r.services, r.meshServices) - // make all parent refs explicit based on what actually bound - if isNil(existing) { - r.setParentsFunc(entry, parentsForRoute(r.gatewayRef, nil, results)) - } else { - r.setParentsFunc(entry, parentsForRoute(r.gatewayRef, r.getParentsFunc(existing), results)) + r.mutateRouteWithBindingResults(snapshot, route, r.nonNormalizedConsulKey, r.config.Resources, results) +} + +// filterParentRefs returns the subset of parent references on a route that point to the given gateway. +func filterParentRefs(gateway types.NamespacedName, namespace string, refs []gwv1beta1.ParentReference) []gwv1beta1.ParentReference { + references := []gwv1beta1.ParentReference{} + for _, ref := range refs { + if common.NilOrEqual(ref.Group, common.BetaGroup) && + common.NilOrEqual(ref.Kind, common.KindGateway) && + gateway.Namespace == common.ValueOr(ref.Namespace, namespace) && + gateway.Name == string(ref.Name) { + references = append(references, ref) + } } - consulUpdate = entry - return + return references } -// newTCPRouteBinder wraps newRouteBinder with the proper closures needed for accessing TCPRoutes and their config entries. -func (b *Binder) newTCPRouteBinder(tracker referenceTracker, services map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) *routeBinder[*gwv1alpha2.TCPRoute, *api.TCPRouteConfigEntry] { - return newRouteBinder( - b.isGatewayDeleted(), - &b.config.Gateway, - b.gatewayRef(), - b.config.Namespaces, - services, - meshServices, - tracker, - b.config.Translator.ReferenceForTCPRoute, - b.consulTCPRouteFor, - func(t *api.TCPRouteConfigEntry) []api.ResourceReference { return t.Parents }, - func(t *api.TCPRouteConfigEntry, parents []api.ResourceReference) { t.Parents = parents }, - b.statusSetter.removeTCPRouteReferences, - func(t *gwv1alpha2.TCPRoute) []gwv1beta1.Hostname { return nil }, - func(t *gwv1alpha2.TCPRoute) []gwv1beta1.ParentReference { return t.Spec.ParentRefs }, - b.config.Translator.TCPRouteToTCPRoute, - b.statusSetter.setTCPRouteCondition, - func(t *gwv1alpha2.TCPRoute) []gwv1beta1.BackendRef { - refs := []gwv1beta1.BackendRef{} - for _, rule := range t.Spec.Rules { - refs = append(refs, rule.BackendRefs...) - } - return refs - }, - b.statusSetter.removeTCPStatuses, - ) +// listenersFor returns the listeners corresponding the given section name. If the section +// name is actually specified, the returned set should just have one listener, if it is +// unspecified, the all gatweway listeners should be returned. +func listenersFor(gateway *gwv1beta1.Gateway, name *gwv1beta1.SectionName) []gwv1beta1.Listener { + listeners := []gwv1beta1.Listener{} + for _, listener := range gateway.Spec.Listeners { + if name == nil { + listeners = append(listeners, listener) + continue + } + if listener.Name == *name { + listeners = append(listeners, listener) + } + } + return listeners } -// newHTTPRouteBinder wraps newRouteBinder with the proper closures needed for accessing HTTPRoutes and their config entries. -func (b *Binder) newHTTPRouteBinder(tracker referenceTracker, services map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) *routeBinder[*gwv1beta1.HTTPRoute, *api.HTTPRouteConfigEntry] { - return newRouteBinder( - b.isGatewayDeleted(), - &b.config.Gateway, - b.gatewayRef(), - b.config.Namespaces, - services, - meshServices, - tracker, - b.config.Translator.ReferenceForHTTPRoute, - b.consulHTTPRouteFor, - func(t *api.HTTPRouteConfigEntry) []api.ResourceReference { return t.Parents }, - func(t *api.HTTPRouteConfigEntry, parents []api.ResourceReference) { t.Parents = parents }, - b.statusSetter.removeHTTPRouteReferences, - func(t *gwv1beta1.HTTPRoute) []gwv1beta1.Hostname { return t.Spec.Hostnames }, - func(t *gwv1beta1.HTTPRoute) []gwv1beta1.ParentReference { return t.Spec.ParentRefs }, - b.config.Translator.HTTPRouteToHTTPRoute, - b.statusSetter.setHTTPRouteCondition, - func(t *gwv1beta1.HTTPRoute) []gwv1beta1.BackendRef { - refs := []gwv1beta1.BackendRef{} - for _, rule := range t.Spec.Rules { - for _, ref := range rule.BackendRefs { - refs = append(refs, ref.BackendRef) +func consulParentMatches(namespace string, gatewayKey api.ResourceReference, parent api.ResourceReference) bool { + gatewayKey = common.NormalizeMeta(gatewayKey) + + if parent.Namespace == "" { + parent.Namespace = namespace + } + if parent.Kind == "" { + parent.Kind = api.APIGateway + } + + parent = common.NormalizeMeta(parent) + + return parent.Kind == api.APIGateway && + parent.Name == gatewayKey.Name && + parent.Namespace == gatewayKey.Namespace && + parent.Partition == gatewayKey.Partition +} + +func (r *Binder) dropConsulRouteParent(snapshot *Snapshot, object client.Object, gateway api.ResourceReference, resources *common.ResourceMap) { + switch object.(type) { + case *gwv1beta1.HTTPRoute: + resources.MutateHTTPRoute(client.ObjectKeyFromObject(object), r.handleRouteSyncStatus(snapshot, object), func(entry api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry { + entry.Parents = common.Filter(entry.Parents, func(parent api.ResourceReference) bool { + return consulParentMatches(entry.Namespace, gateway, parent) + }) + return entry + }) + case *gwv1alpha2.TCPRoute: + resources.MutateTCPRoute(client.ObjectKeyFromObject(object), r.handleRouteSyncStatus(snapshot, object), func(entry api.TCPRouteConfigEntry) api.TCPRouteConfigEntry { + entry.Parents = common.Filter(entry.Parents, func(parent api.ResourceReference) bool { + return consulParentMatches(entry.Namespace, gateway, parent) + }) + return entry + }) + } +} + +func (r *Binder) mutateRouteWithBindingResults(snapshot *Snapshot, object client.Object, gatewayConsulKey api.ResourceReference, resources *common.ResourceMap, results parentBindResults) { + key := client.ObjectKeyFromObject(object) + + parents := mapset.NewSet() + // the normalized set keeps us from accidentally adding the same thing + // twice due to the Consul server normalizing our refs. + normalized := make(map[api.ResourceReference]api.ResourceReference) + for section := range results.boundSections().Iter() { + ref := api.ResourceReference{ + Kind: api.APIGateway, + Name: gatewayConsulKey.Name, + SectionName: section.(string), + Namespace: gatewayConsulKey.Namespace, + Partition: gatewayConsulKey.Partition, + } + parents.Add(ref) + normalized[common.NormalizeMeta(ref)] = ref + } + + switch object.(type) { + case *gwv1beta1.HTTPRoute: + resources.TranslateAndMutateHTTPRoute(key, r.handleRouteSyncStatus(snapshot, object), func(old *api.HTTPRouteConfigEntry, new api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry { + if old != nil { + for _, parent := range old.Parents { + // drop any references that already exist + if parents.Contains(parent) { + parents.Remove(parent) + } + if id, ok := normalized[parent]; ok { + parents.Remove(id) + } + } + + // set the old parent states + new.Parents = old.Parents + } + // and now add what is left + for parent := range parents.Iter() { + new.Parents = append(new.Parents, parent.(api.ResourceReference)) + } + return new + }) + case *gwv1alpha2.TCPRoute: + resources.TranslateAndMutateTCPRoute(key, r.handleRouteSyncStatus(snapshot, object), func(old *api.TCPRouteConfigEntry, new api.TCPRouteConfigEntry) api.TCPRouteConfigEntry { + if old != nil { + for _, parent := range old.Parents { + // drop any references that already exist + if parents.Contains(parent) { + parents.Remove(parent) + } } + + // set the old parent states + new.Parents = old.Parents } - return refs - }, - b.statusSetter.removeHTTPStatuses, - ) + // and now add what is left + for parent := range parents.Iter() { + new.Parents = append(new.Parents, parent.(api.ResourceReference)) + } + return new + }) + } } -// cleanRoute removes a gateway reference from the given route config entry -// and marks adds it to the snapshot if its mutated the entry at all. -func cleanRoute[T api.ConfigEntry]( - route T, - seenRoutes map[api.ResourceReference]struct{}, - snapshot Snapshot, - gatewayRef api.ResourceReference, - getParentsFunc func(T) []api.ResourceReference, - setParentsFunc func(T, []api.ResourceReference), -) Snapshot { - routeRef := translation.EntryToReference(route) - if _, ok := seenRoutes[routeRef]; !ok { - existingParents := getParentsFunc(route) - parents := parentsForRoute(gatewayRef, existingParents, nil) - if len(parents) == 0 { - // we can GC this now since we've dropped all refs from it - snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, routeRef) - } else if len(existingParents) != len(parents) { - // we've mutated the length, which means this route needs an update - setParentsFunc(route, parents) - snapshot.Consul.Updates = append(snapshot.Consul.Updates, route) - } +func entryKind(object client.Object) string { + switch object.(type) { + case *gwv1beta1.HTTPRoute: + return api.HTTPRoute + case *gwv1alpha2.TCPRoute: + return api.TCPRoute } - return snapshot + return "" } -// cleanHTTPRoute wraps cleanRoute with the proper closures for HTTPRoute config entries. -func (b *Binder) cleanHTTPRoute(route *api.HTTPRouteConfigEntry, seenRoutes map[api.ResourceReference]struct{}, snapshot Snapshot) Snapshot { - return cleanRoute(route, seenRoutes, snapshot, b.gatewayRef(), - func(route *api.HTTPRouteConfigEntry) []api.ResourceReference { return route.Parents }, - func(route *api.HTTPRouteConfigEntry, parents []api.ResourceReference) { route.Parents = parents }, - ) +func canGCOnUnbind(id api.ResourceReference, resources *common.ResourceMap) bool { + switch id.Kind { + case api.HTTPRoute: + return resources.CanGCHTTPRouteOnUnbind(id) + case api.TCPRoute: + return resources.CanGCTCPRouteOnUnbind(id) + } + return true } -// cleanTCPRoute wraps cleanRoute with the proper closures for TCPRoute config entries. -func (b *Binder) cleanTCPRoute(route *api.TCPRouteConfigEntry, seenRoutes map[api.ResourceReference]struct{}, snapshot Snapshot) Snapshot { - return cleanRoute(route, seenRoutes, snapshot, b.gatewayRef(), - func(route *api.TCPRouteConfigEntry) []api.ResourceReference { return route.Parents }, - func(route *api.TCPRouteConfigEntry, parents []api.ResourceReference) { route.Parents = parents }, - ) +func getRouteHostnames(object client.Object) []gwv1beta1.Hostname { + switch v := object.(type) { + case *gwv1beta1.HTTPRoute: + return v.Spec.Hostnames + } + return nil } -// parentsForRoute constructs a list of Consul route parent references based on what parents actually bound -// on a given route. This is necessary due to the fact that some additional validation in Kubernetes might -// require a route not to actually be accepted by a gateway, whereas we may have laxer logic inside of Consul -// itself. In these cases we want to just drop the parent reference in the Consul config entry we are going -// to write in order for it not to succeed in binding where Kubernetes failed to bind. -func parentsForRoute(ref api.ResourceReference, existing []api.ResourceReference, results parentBindResults) []api.ResourceReference { - // store all section names that bound - parentSet := map[string]struct{}{} - for _, result := range results { - for _, r := range result.results { - if r.err == nil { - parentSet[string(r.section)] = struct{}{} - } - } +func getRouteParents(object client.Object) []gwv1beta1.ParentReference { + switch v := object.(type) { + case *gwv1beta1.HTTPRoute: + return v.Spec.ParentRefs + case *gwv1alpha2.TCPRoute: + return v.Spec.ParentRefs } + return nil +} - // first, filter out all of the parent refs that don't correspond to this gateway - parents := []api.ResourceReference{} - for _, parent := range existing { - if parent.Kind == api.APIGateway && - parent.Name == ref.Name && - parent.Namespace == ref.Namespace { - continue - } - parents = append(parents, parent) +func getRouteParentsStatus(object client.Object) []gwv1beta1.RouteParentStatus { + switch v := object.(type) { + case *gwv1beta1.HTTPRoute: + return v.Status.RouteStatus.Parents + case *gwv1alpha2.TCPRoute: + return v.Status.RouteStatus.Parents } + return nil +} - // now construct the bound set - for parent := range parentSet { - parents = append(parents, api.ResourceReference{ - Kind: api.APIGateway, - Name: ref.Name, - Namespace: ref.Namespace, - SectionName: parent, - }) +func setRouteParentsStatus(object client.Object, parents []gwv1beta1.RouteParentStatus) { + switch v := object.(type) { + case *gwv1beta1.HTTPRoute: + v.Status.RouteStatus.Parents = parents + case *gwv1alpha2.TCPRoute: + v.Status.RouteStatus.Parents = parents } - return parents } -// filterParentRefs returns the subset of parent references on a route that point to the given gateway. -func filterParentRefs(gateway types.NamespacedName, namespace string, refs []gwv1beta1.ParentReference) []gwv1beta1.ParentReference { - references := []gwv1beta1.ParentReference{} - for _, ref := range refs { - if nilOrEqual(ref.Group, betaGroup) && - nilOrEqual(ref.Kind, kindGateway) && - gateway.Namespace == valueOr(ref.Namespace, namespace) && - gateway.Name == string(ref.Name) { - references = append(references, ref) - } +func getRouteBackends(object client.Object) []gwv1beta1.BackendRef { + switch v := object.(type) { + case *gwv1beta1.HTTPRoute: + return common.Flatten(common.ConvertSliceFunc(v.Spec.Rules, func(rule gwv1beta1.HTTPRouteRule) []gwv1beta1.BackendRef { + return common.ConvertSliceFunc(rule.BackendRefs, func(rule gwv1beta1.HTTPBackendRef) gwv1beta1.BackendRef { + return rule.BackendRef + }) + })) + case *gwv1alpha2.TCPRoute: + return common.Flatten(common.ConvertSliceFunc(v.Spec.Rules, func(rule gwv1alpha2.TCPRouteRule) []gwv1beta1.BackendRef { + return rule.BackendRefs + })) } + return nil +} - return references +func canReferenceGateway(object client.Object, ref gwv1beta1.ParentReference, resources *common.ResourceMap) bool { + switch v := object.(type) { + case *gwv1beta1.HTTPRoute: + return resources.HTTPRouteCanReferenceGateway(*v, ref) + case *gwv1alpha2.TCPRoute: + return resources.TCPRouteCanReferenceGateway(*v, ref) + } + return false } -// listenersFor returns the listeners corresponding the given section name. If the section -// name is actually specified, the returned set should just have one listener, if it is -// unspecified, the all gatweway listeners should be returned. -func listenersFor(gateway *gwv1beta1.Gateway, name *gwv1beta1.SectionName) []gwv1beta1.Listener { - listeners := []gwv1beta1.Listener{} - for _, listener := range gateway.Spec.Listeners { - if name == nil { - listeners = append(listeners, listener) - continue +func canReferenceBackend(object client.Object, ref gwv1beta1.BackendRef, resources *common.ResourceMap) bool { + switch v := object.(type) { + case *gwv1beta1.HTTPRoute: + return resources.HTTPRouteCanReferenceBackend(*v, ref) + case *gwv1alpha2.TCPRoute: + return resources.TCPRouteCanReferenceBackend(*v, ref) + } + return false +} + +func (r *Binder) handleRouteSyncStatus(snapshot *Snapshot, object client.Object) func(error) { + return func(err error) { + condition := metav1.Condition{ + Type: "Synced", + Status: metav1.ConditionTrue, + ObservedGeneration: object.GetGeneration(), + LastTransitionTime: timeFunc(), + Reason: "Synced", + Message: "route synced to Consul", } - if listener.Name == *name { - listeners = append(listeners, listener) + if err != nil { + condition = metav1.Condition{ + Type: "Synced", + Status: metav1.ConditionFalse, + ObservedGeneration: object.GetGeneration(), + LastTransitionTime: timeFunc(), + Reason: "SyncError", + Message: err.Error(), + } + } + if r.statusSetter.setRouteConditionOnAllRefs(object, condition) { + snapshot.Kubernetes.StatusUpdates.Add(object) + } + } +} + +func (r *Binder) handleGatewaySyncStatus(snapshot *Snapshot, gateway *gwv1beta1.Gateway) func(error) { + return func(err error) { + condition := metav1.Condition{ + Type: "Synced", + Status: metav1.ConditionTrue, + ObservedGeneration: gateway.Generation, + LastTransitionTime: timeFunc(), + Reason: "Synced", + Message: "gateway synced to Consul", + } + if err != nil { + condition = metav1.Condition{ + Type: "Synced", + Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, + LastTransitionTime: timeFunc(), + Reason: "SyncError", + Message: err.Error(), + } + } + + if conditions, updated := setCondition(gateway.Status.Conditions, condition); updated { + gateway.Status.Conditions = conditions + snapshot.Kubernetes.StatusUpdates.Add(gateway) } } - return listeners } diff --git a/control-plane/api-gateway/binding/setter.go b/control-plane/api-gateway/binding/setter.go index 93727b37b2..5b3a9096d6 100644 --- a/control-plane/api-gateway/binding/setter.go +++ b/control-plane/api-gateway/binding/setter.go @@ -4,8 +4,9 @@ package binding import ( + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -19,26 +20,12 @@ func newSetter(controllerName string) *setter { return &setter{controllerName: controllerName} } -// setHTTPRouteCondition sets an HTTPRoute condition on its status with the given parent. -func (s *setter) setHTTPRouteCondition(route *gwv1beta1.HTTPRoute, parent *gwv1beta1.ParentReference, condition metav1.Condition) bool { - condition.LastTransitionTime = metav1.Now() - condition.ObservedGeneration = route.Generation - - status := s.getParentStatus(route.Status.Parents, parent) - conditions, modified := setCondition(status.Conditions, condition) - if modified { - status.Conditions = conditions - route.Status.Parents = s.setParentStatus(route.Status.Parents, status) - } - return modified -} - -// removeHTTPRouteReferences removes the given parent reference sections from an HTTPRoute's status. -func (s *setter) removeHTTPRouteReferences(route *gwv1beta1.HTTPRoute, refs []gwv1beta1.ParentReference) bool { +// removeRouteReferences removes the given parent reference sections from a routes's status. +func (s *setter) removeRouteReferences(route client.Object, refs []gwv1beta1.ParentReference) bool { modified := false for _, parent := range refs { - parents, removed := s.removeParentStatus(route.Status.Parents, parent) - route.Status.Parents = parents + parents, removed := s.removeParentStatus(getRouteParentsStatus(route), parent) + setRouteParentsStatus(route, parents) if removed { modified = true } @@ -46,67 +33,41 @@ func (s *setter) removeHTTPRouteReferences(route *gwv1beta1.HTTPRoute, refs []gw return modified } -// setTCPRouteCondition sets a TCPRoute condition on its status with the given parent. -func (s *setter) setTCPRouteCondition(route *gwv1alpha2.TCPRoute, parent *gwv1beta1.ParentReference, condition metav1.Condition) bool { - condition.LastTransitionTime = metav1.Now() - condition.ObservedGeneration = route.Generation +// setRouteCondition sets an route condition on its status with the given parent. +func (s *setter) setRouteCondition(route client.Object, parent *gwv1beta1.ParentReference, condition metav1.Condition) bool { + condition.LastTransitionTime = timeFunc() + condition.ObservedGeneration = route.GetGeneration() - status := s.getParentStatus(route.Status.Parents, parent) + parents := getRouteParentsStatus(route) + status := s.getParentStatus(parents, parent) conditions, modified := setCondition(status.Conditions, condition) if modified { status.Conditions = conditions - route.Status.Parents = s.setParentStatus(route.Status.Parents, status) - } - return modified -} - -// removeTCPRouteReferences removes the given parent reference sections from a TCPRoute's status. -func (s *setter) removeTCPRouteReferences(route *gwv1alpha2.TCPRoute, refs []gwv1beta1.ParentReference) bool { - modified := false - for _, parent := range refs { - parents, removed := s.removeParentStatus(route.Status.Parents, parent) - route.Status.Parents = parents - if removed { - modified = true - } + setRouteParentsStatus(route, s.setParentStatus(parents, status)) } return modified } -// removeHTTPStatuses removes all statuses set by the given controller from an HTTPRoute's status. -func (s *setter) removeHTTPStatuses(route *gwv1beta1.HTTPRoute) bool { - modified := false - filtered := []gwv1beta1.RouteParentStatus{} - for _, status := range route.Status.Parents { - if string(status.ControllerName) == s.controllerName { - modified = true - continue - } - filtered = append(filtered, status) - } +// setRouteConditionOnAllRefs sets an route condition and its status on all parents. +func (s *setter) setRouteConditionOnAllRefs(route client.Object, condition metav1.Condition) bool { + condition.LastTransitionTime = timeFunc() + condition.ObservedGeneration = route.GetGeneration() - if modified { - route.Status.Parents = filtered - } - return modified -} + parents := getRouteParentsStatus(route) + statuses := common.Filter(getRouteParentsStatus(route), func(status gwv1beta1.RouteParentStatus) bool { + return string(status.ControllerName) != s.controllerName + }) -// removeTCPStatuses removes all statuses set by the given controller from a TCPRoute's status. -func (s *setter) removeTCPStatuses(route *gwv1alpha2.TCPRoute) bool { - modified := false - filtered := []gwv1beta1.RouteParentStatus{} - for _, status := range route.Status.Parents { - if string(status.ControllerName) == s.controllerName { - modified = true - continue + updated := false + for _, status := range statuses { + conditions, modified := setCondition(status.Conditions, condition) + if modified { + updated = true + status.Conditions = conditions + setRouteParentsStatus(route, s.setParentStatus(parents, status)) } - filtered = append(filtered, status) - } - - if modified { - route.Status.Parents = filtered } - return modified + return updated } // getParentStatus returns the section of a status referenced by the given parent reference. @@ -117,7 +78,7 @@ func (s *setter) getParentStatus(statuses []gwv1beta1.RouteParentStatus, parent } for _, status := range statuses { - if parentsEqual(status.ParentRef, parentRef) && string(status.ControllerName) == s.controllerName { + if common.ParentsEqual(status.ParentRef, parentRef) && string(status.ControllerName) == s.controllerName { return status } } @@ -132,7 +93,7 @@ func (s *setter) removeParentStatus(statuses []gwv1beta1.RouteParentStatus, pare found := false filtered := []gwv1beta1.RouteParentStatus{} for _, status := range statuses { - if parentsEqual(status.ParentRef, parent) && string(status.ControllerName) == s.controllerName { + if common.ParentsEqual(status.ParentRef, parent) && string(status.ControllerName) == s.controllerName { found = true continue } @@ -162,19 +123,10 @@ func setCondition(conditions []metav1.Condition, condition metav1.Condition) ([] // setParentStatus updates or inserts the set of parent statuses with the newly modified parent. func (s *setter) setParentStatus(statuses []gwv1beta1.RouteParentStatus, parent gwv1beta1.RouteParentStatus) []gwv1beta1.RouteParentStatus { for i, status := range statuses { - if parentsEqual(status.ParentRef, parent.ParentRef) && status.ControllerName == parent.ControllerName { + if common.ParentsEqual(status.ParentRef, parent.ParentRef) && status.ControllerName == parent.ControllerName { statuses[i] = parent return statuses } } return append(statuses, parent) } - -// parentsEqual checks for equality between two parent references. -func parentsEqual(one, two gwv1beta1.ParentReference) bool { - return bothNilOrEqual(one.Group, two.Group) && - bothNilOrEqual(one.Kind, two.Kind) && - bothNilOrEqual(one.SectionName, two.SectionName) && - bothNilOrEqual(one.Port, two.Port) && - one.Name == two.Name -} diff --git a/control-plane/api-gateway/binding/setter_test.go b/control-plane/api-gateway/binding/setter_test.go index a8eabfb55d..84d3ecc7d5 100644 --- a/control-plane/api-gateway/binding/setter_test.go +++ b/control-plane/api-gateway/binding/setter_test.go @@ -32,10 +32,10 @@ func TestSetter(t *testing.T) { }, }, } - require.True(t, setter.setHTTPRouteCondition(route, &parentRef, condition)) - require.False(t, setter.setHTTPRouteCondition(route, &parentRefDup, condition)) - require.False(t, setter.setHTTPRouteCondition(route, &parentRefDup, condition)) - require.False(t, setter.setHTTPRouteCondition(route, &parentRefDup, condition)) + require.True(t, setter.setRouteCondition(route, &parentRef, condition)) + require.False(t, setter.setRouteCondition(route, &parentRefDup, condition)) + require.False(t, setter.setRouteCondition(route, &parentRefDup, condition)) + require.False(t, setter.setRouteCondition(route, &parentRefDup, condition)) require.Len(t, route.Status.Parents, 1) require.Len(t, route.Status.Parents[0].Conditions, 1) diff --git a/control-plane/api-gateway/binding/snapshot.go b/control-plane/api-gateway/binding/snapshot.go index 5eaf48abb3..18888a6d46 100644 --- a/control-plane/api-gateway/binding/snapshot.go +++ b/control-plane/api-gateway/binding/snapshot.go @@ -4,9 +4,9 @@ package binding import ( + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" - "sigs.k8s.io/controller-runtime/pkg/client" ) // KubernetesSnapshot contains all the operations @@ -15,10 +15,10 @@ type KubernetesSnapshot struct { // Updates is the list of objects that need to have // aspects of their metadata or spec updated in Kubernetes // (i.e. for finalizers or annotations) - Updates []client.Object + Updates *common.KubernetesUpdates // StatusUpdates is the list of objects that need // to have their statuses updated in Kubernetes - StatusUpdates []client.Object + StatusUpdates *common.KubernetesUpdates } // ConsulSnapshot contains all the operations required @@ -26,7 +26,7 @@ type KubernetesSnapshot struct { type ConsulSnapshot struct { // Updates is the list of ConfigEntry objects that should // either be updated or created in Consul - Updates []api.ConfigEntry + Updates []*common.ConsulUpdateOperation // Deletions is a list of references that ought to be // deleted in Consul Deletions []api.ResourceReference @@ -42,9 +42,9 @@ type ConsulSnapshot struct { // needed to complete reconciliation. type Snapshot struct { // Kubernetes holds the snapshot of required Kubernetes operations - Kubernetes KubernetesSnapshot + Kubernetes *KubernetesSnapshot // Consul holds the snapshot of required Consul operations - Consul ConsulSnapshot + Consul *ConsulSnapshot // GatewayClassConfig is the configuration to use for determining // a Gateway deployment, if it is not set, a deployment should be // deleted instead of updated @@ -54,3 +54,13 @@ type Snapshot struct { // objects should be updated, i.e. deployments, roles, services UpsertGatewayDeployment bool } + +func NewSnapshot() *Snapshot { + return &Snapshot{ + Kubernetes: &KubernetesSnapshot{ + Updates: common.NewKubernetesUpdates(), + StatusUpdates: common.NewKubernetesUpdates(), + }, + Consul: &ConsulSnapshot{}, + } +} diff --git a/control-plane/api-gateway/binding/utils.go b/control-plane/api-gateway/binding/utils.go deleted file mode 100644 index 2ba2d01ce5..0000000000 --- a/control-plane/api-gateway/binding/utils.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "reflect" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// pointerTo is a convenience method for taking a pointer -// of an object without having to declare an intermediate variable. -// It's also useful for making sure we don't accidentally take -// the pointer of a range variable directly. -func pointerTo[T any](v T) *T { - return &v -} - -// isNil checks if the argument is nil. It's mainly used to -// check if a generic conforming to a nullable interface is -// actually nil. -func isNil(arg interface{}) bool { - return arg == nil || reflect.ValueOf(arg).IsNil() -} - -// bothNilOrEqual is used to determine if two pointers to comparable -// object are either nil or both point to the same value. -func bothNilOrEqual[T comparable](one, two *T) bool { - if one == nil && two == nil { - return true - } - if one == nil { - return false - } - if two == nil { - return false - } - return *one == *two -} - -// valueOr checks if a string-like pointer is nil, and if it is, -// returns the given value instead. -func valueOr[T ~string](v *T, fallback string) string { - if v == nil { - return fallback - } - return string(*v) -} - -// nilOrEqual checks if a string-like pointer is nil or if it is -// equal to the value provided. -func nilOrEqual[T ~string](v *T, check string) bool { - return v == nil || string(*v) == check -} - -// objectToMeta returns the NamespacedName for the given object. -func objectToMeta[T metav1.Object](object T) types.NamespacedName { - return types.NamespacedName{ - Namespace: object.GetNamespace(), - Name: object.GetName(), - } -} - -// isDeleted checks if the deletion timestamp is set for an object. -func isDeleted(object client.Object) bool { - return !object.GetDeletionTimestamp().IsZero() -} - -// ensureFinalizer ensures that our finalizer is set on an object -// returning whether or not it modified the object. -func ensureFinalizer(object client.Object) bool { - if !object.GetDeletionTimestamp().IsZero() { - return false - } - - finalizers := object.GetFinalizers() - for _, f := range finalizers { - if f == gatewayFinalizer { - return false - } - } - - object.SetFinalizers(append(finalizers, gatewayFinalizer)) - return true -} - -// removeFinalizer ensures that our finalizer is absent from an object -// returning whether or not it modified the object. -func removeFinalizer(object client.Object) bool { - found := false - filtered := []string{} - for _, f := range object.GetFinalizers() { - if f == gatewayFinalizer { - found = true - continue - } - filtered = append(filtered, f) - } - - object.SetFinalizers(filtered) - return found -} diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index c22726f2da..41c9484483 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -6,6 +6,7 @@ package binding import ( "strings" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" @@ -13,26 +14,45 @@ import ( klabels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) +var ( + // the list of kinds we can support by listener protocol. + supportedKindsForProtocol = map[gwv1beta1.ProtocolType][]gwv1beta1.RouteGroupKind{ + gwv1beta1.HTTPProtocolType: {{ + Group: (*gwv1beta1.Group)(&gwv1beta1.GroupVersion.Group), + Kind: "HTTPRoute", + }}, + gwv1beta1.HTTPSProtocolType: {{ + Group: (*gwv1beta1.Group)(&gwv1beta1.GroupVersion.Group), + Kind: "HTTPRoute", + }}, + gwv1beta1.TCPProtocolType: {{ + Group: (*gwv1alpha2.Group)(&gwv1alpha2.GroupVersion.Group), + Kind: "TCPRoute", + }}, + } +) + // validateRefs validates backend references for a route, determining whether or // not they were found in the list of known connect-injected services. -func validateRefs(namespace string, refs []gwv1beta1.BackendRef, services map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) routeValidationResults { +func validateRefs(route client.Object, refs []gwv1beta1.BackendRef, resources *common.ResourceMap) routeValidationResults { + namespace := route.GetNamespace() + var result routeValidationResults for _, ref := range refs { + backendRef := ref.BackendObjectReference + nsn := types.NamespacedName{ - Name: string(ref.BackendObjectReference.Name), - Namespace: valueOr(ref.BackendObjectReference.Namespace, namespace), + Name: string(backendRef.Name), + Namespace: common.ValueOr(backendRef.Namespace, namespace), } - // TODO: check reference grants - - backendRef := ref.BackendObjectReference - - isServiceRef := nilOrEqual(backendRef.Group, "") && nilOrEqual(backendRef.Kind, "Service") - isMeshServiceRef := derefEqual(backendRef.Group, v1alpha1.ConsulHashicorpGroup) && derefEqual(backendRef.Kind, v1alpha1.MeshServiceKind) + isServiceRef := common.NilOrEqual(backendRef.Group, "") && common.NilOrEqual(backendRef.Kind, common.KindService) + isMeshServiceRef := common.DerefEqual(backendRef.Group, v1alpha1.ConsulHashicorpGroup) && common.DerefEqual(backendRef.Kind, v1alpha1.MeshServiceKind) if !isServiceRef && !isMeshServiceRef { result = append(result, routeValidationResult{ @@ -43,26 +63,31 @@ func validateRefs(namespace string, refs []gwv1beta1.BackendRef, services map[ty continue } - if isServiceRef { - if _, found := services[nsn]; !found { - result = append(result, routeValidationResult{ - namespace: nsn.Namespace, - backend: ref, - err: errRouteBackendNotFound, - }) - continue - } + if isServiceRef && !resources.HasService(nsn) { + result = append(result, routeValidationResult{ + namespace: nsn.Namespace, + backend: ref, + err: errRouteBackendNotFound, + }) + continue } - if isMeshServiceRef { - if _, found := meshServices[nsn]; !found { - result = append(result, routeValidationResult{ - namespace: nsn.Namespace, - backend: ref, - err: errRouteBackendNotFound, - }) - continue - } + if isMeshServiceRef && !resources.HasMeshService(nsn) { + result = append(result, routeValidationResult{ + namespace: nsn.Namespace, + backend: ref, + err: errRouteBackendNotFound, + }) + continue + } + + if !canReferenceBackend(route, ref, resources) { + result = append(result, routeValidationResult{ + namespace: nsn.Namespace, + backend: ref, + err: errRefNotPermitted, + }) + continue } result = append(result, routeValidationResult{ @@ -110,7 +135,7 @@ func (m mergedListeners) validateProtocol() error { var protocol *gwv1beta1.ProtocolType for _, l := range m { if protocol == nil { - protocol = pointerTo(l.listener.Protocol) + protocol = common.PointerTo(l.listener.Protocol) } if *protocol != l.listener.Protocol { return errListenerProtocolConflict @@ -126,7 +151,7 @@ func (m mergedListeners) validateHostname(index int, listener gwv1beta1.Listener if l.index == index { continue } - if bothNilOrEqual(listener.Hostname, l.listener.Hostname) { + if common.BothNilOrEqual(listener.Hostname, l.listener.Hostname) { return errListenerHostnameConflict } } @@ -135,32 +160,36 @@ func (m mergedListeners) validateHostname(index int, listener gwv1beta1.Listener // validateTLS validates that the TLS configuration for a given listener is valid and that // the certificates that it references exist. -func validateTLS(namespace string, tls *gwv1beta1.GatewayTLSConfig, certificates []corev1.Secret) (error, error) { +func validateTLS(gateway gwv1beta1.Gateway, tls *gwv1beta1.GatewayTLSConfig, resources *common.ResourceMap) (error, error) { + namespace := gateway.Namespace + if tls == nil { return nil, nil } - // TODO: Resource Grants - var err error -MAIN_LOOP: - for _, ref := range tls.CertificateRefs { + + for _, cert := range tls.CertificateRefs { // break on the first error - if !nilOrEqual(ref.Group, "") || !nilOrEqual(ref.Kind, "Secret") { + if !common.NilOrEqual(cert.Group, "") || !common.NilOrEqual(cert.Kind, common.KindSecret) { err = errListenerInvalidCertificateRef_NotSupported - break MAIN_LOOP + break } - ns := valueOr(ref.Namespace, namespace) - for _, secret := range certificates { - if secret.Namespace == ns && secret.Name == string(ref.Name) { - continue MAIN_LOOP - } + if !resources.GatewayCanReferenceSecret(gateway, cert) { + err = errRefNotPermitted + break } - // not found, set error - err = errListenerInvalidCertificateRef_NotFound - break MAIN_LOOP + key := common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, namespace) + secret := resources.Certificate(key) + + if secret == nil { + err = errListenerInvalidCertificateRef_NotFound + break + } + + err = validateCertificateData(*secret) } if tls.Mode != nil && *tls.Mode == gwv1beta1.TLSModePassthrough { @@ -171,9 +200,14 @@ MAIN_LOOP: return nil, err } +func validateCertificateData(secret corev1.Secret) error { + _, _, err := common.ParseCertificateData(secret) + return err +} + // validateListeners validates the given listeners both internally and with respect to each // other for purposes of setting "Conflicted" status conditions. -func validateListeners(namespace string, listeners []gwv1beta1.Listener, secrets []corev1.Secret) listenerValidationResults { +func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener, resources *common.ResourceMap) listenerValidationResults { var results listenerValidationResults merged := make(map[gwv1beta1.PortNumber]mergedListeners) for i, listener := range listeners { @@ -186,7 +220,7 @@ func validateListeners(namespace string, listeners []gwv1beta1.Listener, secrets for i, listener := range listeners { var result listenerValidationResult - err, refErr := validateTLS(namespace, listener.TLS, secrets) + err, refErr := validateTLS(gateway, listener.TLS, resources) result.refErr = refErr if err != nil { result.acceptedErr = err @@ -290,7 +324,7 @@ func routeKindIsAllowedForListener(kinds []gwv1beta1.RouteGroupKind, gk schema.G } for _, kind := range kinds { - if string(kind.Kind) == gk.Kind && nilOrEqual(kind.Group, gk.Group) { + if string(kind.Kind) == gk.Kind && common.NilOrEqual(kind.Group, gk.Group) { return true } } @@ -311,7 +345,7 @@ func routeKindIsAllowedForListenerExplicit(allowedRoutes *gwv1alpha2.AllowedRout // toNamespaceSet constructs a list of labels used to match a Namespace. func toNamespaceSet(name string, labels map[string]string) klabels.Labels { // If namespace label is not set, implicitly insert it to support older Kubernetes versions - if labels[namespaceNameLabel] == name { + if labels[common.NamespaceNameLabel] == name { // Already set, avoid copies return klabels.Set(labels) } @@ -320,13 +354,6 @@ func toNamespaceSet(name string, labels map[string]string) klabels.Labels { for k, v := range labels { ret[k] = v } - ret[namespaceNameLabel] = name + ret[common.NamespaceNameLabel] = name return klabels.Set(ret) } - -func derefEqual[T ~string](v *T, check string) bool { - if v == nil { - return false - } - return string(*v) == check -} diff --git a/control-plane/api-gateway/binding/validation_test.go b/control-plane/api-gateway/binding/validation_test.go index eaf4388413..da0ed83f95 100644 --- a/control-plane/api-gateway/binding/validation_test.go +++ b/control-plane/api-gateway/binding/validation_test.go @@ -6,13 +6,15 @@ package binding import ( "testing" + logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -20,132 +22,183 @@ func TestValidateRefs(t *testing.T) { t.Parallel() for name, tt := range map[string]struct { - namespace string - refs []gwv1beta1.BackendObjectReference - services map[types.NamespacedName]api.CatalogService - meshServices map[types.NamespacedName]v1alpha1.MeshService - expectedErrors []error + route client.Object + services map[types.NamespacedName]corev1.Service + referenceGrants []gwv1beta1.ReferenceGrant + meshServices []v1alpha1.MeshService + expectedErrors []error }{ "all pass no namespaces": { - namespace: "test", - refs: []gwv1beta1.BackendObjectReference{{Name: "1"}, {Name: "2"}}, - services: map[types.NamespacedName]api.CatalogService{ + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{{Name: "1"}, {Name: "2"}}, nil), + services: map[types.NamespacedName]corev1.Service{ {Name: "1", Namespace: "test"}: {}, {Name: "2", Namespace: "test"}: {}, {Name: "3", Namespace: "test"}: {}, }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + meshServices: []v1alpha1.MeshService{}, expectedErrors: []error{nil, nil}, }, + "all fails namespaces no reference grants": { + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ + {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + {Name: "2", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + }, nil), + services: map[types.NamespacedName]corev1.Service{ + {Name: "1", Namespace: "other"}: {}, + {Name: "2", Namespace: "other"}: {}, + {Name: "3", Namespace: "other"}: {}, + }, + meshServices: []v1alpha1.MeshService{}, + expectedErrors: []error{errRefNotPermitted, errRefNotPermitted}, + }, "all pass namespaces": { - namespace: "test", - refs: []gwv1beta1.BackendObjectReference{ - {Name: "1", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, - {Name: "2", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + referenceGrants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("test")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Kind: "Service"}, + }, + }}, }, - services: map[types.NamespacedName]api.CatalogService{ + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ + {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + {Name: "2", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + }, nil), + services: map[types.NamespacedName]corev1.Service{ {Name: "1", Namespace: "other"}: {}, {Name: "2", Namespace: "other"}: {}, {Name: "3", Namespace: "other"}: {}, }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + meshServices: []v1alpha1.MeshService{}, expectedErrors: []error{nil, nil}, }, - "all pass mixed": { - namespace: "test", - refs: []gwv1beta1.BackendObjectReference{ - {Name: "1", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + "some pass mixed missing reference grants": { + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ + {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, {Name: "2"}, + }, nil), + services: map[types.NamespacedName]corev1.Service{ + {Name: "1", Namespace: "other"}: {}, + {Name: "2", Namespace: "test"}: {}, + {Name: "3", Namespace: "other"}: {}, + }, + meshServices: []v1alpha1.MeshService{}, + expectedErrors: []error{errRefNotPermitted, nil}, + }, + "all pass mixed": { + referenceGrants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("test")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Kind: "Service"}, + }, + }}, }, - services: map[types.NamespacedName]api.CatalogService{ + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ + {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + {Name: "2"}, + }, nil), + services: map[types.NamespacedName]corev1.Service{ {Name: "1", Namespace: "other"}: {}, {Name: "2", Namespace: "test"}: {}, {Name: "3", Namespace: "other"}: {}, }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + meshServices: []v1alpha1.MeshService{}, expectedErrors: []error{nil, nil}, }, "all fail mixed": { - namespace: "test", - refs: []gwv1beta1.BackendObjectReference{ - {Name: "1"}, - {Name: "2", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + referenceGrants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("test")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Kind: "Service"}, + }, + }}, }, - services: map[types.NamespacedName]api.CatalogService{ + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ + {Name: "1"}, + {Name: "2", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + }, nil), + services: map[types.NamespacedName]corev1.Service{ {Name: "1", Namespace: "other"}: {}, {Name: "2", Namespace: "test"}: {}, {Name: "3", Namespace: "other"}: {}, }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + meshServices: []v1alpha1.MeshService{}, expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, }, "all fail no namespaces": { - namespace: "test", - refs: []gwv1beta1.BackendObjectReference{ + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ {Name: "1"}, {Name: "2"}, - }, - services: map[types.NamespacedName]api.CatalogService{ + }, nil), + services: map[types.NamespacedName]corev1.Service{ {Name: "1", Namespace: "other"}: {}, {Name: "2", Namespace: "other"}: {}, {Name: "3", Namespace: "other"}: {}, }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + meshServices: []v1alpha1.MeshService{}, expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, }, "all fail namespaces": { - namespace: "test", - refs: []gwv1beta1.BackendObjectReference{ - {Name: "1", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, - {Name: "2", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, - }, - services: map[types.NamespacedName]api.CatalogService{ + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ + {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + {Name: "2", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + }, nil), + services: map[types.NamespacedName]corev1.Service{ {Name: "1", Namespace: "test"}: {}, {Name: "2", Namespace: "test"}: {}, {Name: "3", Namespace: "test"}: {}, }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + meshServices: []v1alpha1.MeshService{}, expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, }, "type failures": { - namespace: "test", - refs: []gwv1beta1.BackendObjectReference{ - {Name: "1", Group: pointerTo[gwv1beta1.Group]("test")}, + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ + {Name: "1", Group: common.PointerTo[gwv1beta1.Group]("test")}, {Name: "2"}, - }, - services: map[types.NamespacedName]api.CatalogService{ + }, nil), + services: map[types.NamespacedName]corev1.Service{ {Name: "1", Namespace: "test"}: {}, {Name: "2", Namespace: "test"}: {}, {Name: "3", Namespace: "test"}: {}, }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{}, + meshServices: []v1alpha1.MeshService{}, expectedErrors: []error{errRouteInvalidKind, nil}, }, "mesh services": { - namespace: "test", - refs: []gwv1beta1.BackendObjectReference{ + route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ { Name: "1", - Group: pointerTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), - Kind: pointerTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), + Group: common.PointerTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), + Kind: common.PointerTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), }, - }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{ - {Name: "1", Namespace: "test"}: {}, - {Name: "2", Namespace: "test"}: {}, - {Name: "3", Namespace: "test"}: {}, + }, nil), + meshServices: []v1alpha1.MeshService{ + {ObjectMeta: metav1.ObjectMeta{Name: "1", Namespace: "test"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "2", Namespace: "test"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "3", Namespace: "test"}}, }, expectedErrors: []error{nil}, }, } { t.Run(name, func(t *testing.T) { - refs := make([]gwv1beta1.BackendRef, len(tt.refs)) - for i, ref := range tt.refs { - refs[i] = gwv1beta1.BackendRef{BackendObjectReference: ref} + refs := getRouteBackends(tt.route) + resources := common.NewResourceMap(common.ResourceTranslator{}, NewReferenceValidator(tt.referenceGrants), logrtest.NewTestLogger(t)) + for _, service := range tt.meshServices { + resources.AddMeshService(service) + } + for id := range tt.services { + resources.AddService(id, id.Name) } - actual := validateRefs(tt.namespace, refs, tt.services, tt.meshServices) - require.Equal(t, len(actual), len(tt.refs)) + actual := validateRefs(tt.route, refs, resources) require.Equal(t, len(actual), len(tt.expectedErrors)) for i, err := range tt.expectedErrors { require.Equal(t, err, actual[i].err) @@ -236,11 +289,11 @@ func TestMergedListeners_ValidateHostname(t *testing.T) { }{ "valid": { mergedListeners: []mergedListener{ - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("1")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("2")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("3")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("4")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("5")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("1")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("2")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("3")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("4")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("5")}}, {}, }, expected: nil, @@ -248,24 +301,24 @@ func TestMergedListeners_ValidateHostname(t *testing.T) { "invalid nil": { mergedListeners: []mergedListener{ {}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("1")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("2")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("3")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("4")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("5")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("1")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("2")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("3")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("4")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("5")}}, {}, }, expected: errListenerHostnameConflict, }, "invalid set": { mergedListeners: []mergedListener{ - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("1")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("2")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("3")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("4")}}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("5")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("1")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("2")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("3")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("4")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("5")}}, {}, - {listener: gwv1beta1.Listener{Hostname: pointerTo[gwv1beta1.Hostname]("1")}}, + {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("1")}}, }, expected: errListenerHostnameConflict, }, @@ -284,25 +337,28 @@ func TestMergedListeners_ValidateHostname(t *testing.T) { func TestValidateTLS(t *testing.T) { t.Parallel() + _, secret := generateTestCertificate(t, "", "") + for name, tt := range map[string]struct { - namespace string + gateway gwv1beta1.Gateway + grants []gwv1beta1.ReferenceGrant tls *gwv1beta1.GatewayTLSConfig certificates []corev1.Secret expectedResolvedRefsErr error expectedAcceptedErr error }{ "no tls": { - namespace: "test", + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tls: nil, certificates: nil, expectedResolvedRefsErr: nil, expectedAcceptedErr: nil, }, "not supported certificate": { - namespace: "test", + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tls: &gwv1beta1.GatewayTLSConfig{ CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "foo", Namespace: pointerTo[gwv1beta1.Namespace]("other"), Group: pointerTo[gwv1beta1.Group]("test")}, + {Name: "foo", Namespace: common.PointerTo[gwv1beta1.Namespace]("other"), Group: common.PointerTo[gwv1beta1.Group]("test")}, }, }, certificates: []corev1.Secret{ @@ -313,11 +369,36 @@ func TestValidateTLS(t *testing.T) { expectedResolvedRefsErr: errListenerInvalidCertificateRef_NotSupported, expectedAcceptedErr: nil, }, + "not allowed certificate": { + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + tls: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "foo", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, + }, + }, + certificates: []corev1.Secret{ + {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "other"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "other"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "other"}}, + }, + expectedResolvedRefsErr: errRefNotPermitted, + expectedAcceptedErr: nil, + }, "not found certificate": { - namespace: "test", + grants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Kind: "Secret"}, + }, + }}, + }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tls: &gwv1beta1.GatewayTLSConfig{ CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "zoiks", Namespace: pointerTo[gwv1beta1.Namespace]("other")}, + {Name: "zoiks", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, }, }, certificates: []corev1.Secret{ @@ -329,10 +410,20 @@ func TestValidateTLS(t *testing.T) { expectedAcceptedErr: nil, }, "not found certificate mismatched namespace": { - namespace: "test", + grants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Kind: "Secret"}, + }, + }}, + }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tls: &gwv1beta1.GatewayTLSConfig{ CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "foo", Namespace: pointerTo[gwv1beta1.Namespace]("1")}, + {Name: "foo", Namespace: common.PointerTo[gwv1beta1.Namespace]("foo")}, }, }, certificates: []corev1.Secret{ @@ -344,21 +435,47 @@ func TestValidateTLS(t *testing.T) { expectedAcceptedErr: nil, }, "passthrough mode": { - namespace: "test", + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tls: &gwv1beta1.GatewayTLSConfig{ - Mode: pointerTo(gwv1beta1.TLSModePassthrough), + Mode: common.PointerTo(gwv1beta1.TLSModePassthrough), }, certificates: nil, expectedResolvedRefsErr: nil, expectedAcceptedErr: errListenerNoTLSPassthrough, }, "valid targeted namespace": { - namespace: "test", + grants: []gwv1beta1.ReferenceGrant{ + {ObjectMeta: metav1.ObjectMeta{Namespace: "1", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Kind: "Secret"}, + }, + }}, + {ObjectMeta: metav1.ObjectMeta{Namespace: "2", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Kind: "Secret"}, + }, + }}, + {ObjectMeta: metav1.ObjectMeta{Namespace: "3", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ + From: []gwv1beta1.ReferenceGrantFrom{ + {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, + }, + To: []gwv1beta1.ReferenceGrantTo{ + {Kind: "Secret"}, + }, + }}, + }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tls: &gwv1beta1.GatewayTLSConfig{ CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "foo", Namespace: pointerTo[gwv1beta1.Namespace]("1")}, - {Name: "bar", Namespace: pointerTo[gwv1beta1.Namespace]("2")}, - {Name: "baz", Namespace: pointerTo[gwv1beta1.Namespace]("3")}, + {Name: "foo", Namespace: common.PointerTo[gwv1beta1.Namespace]("1")}, + {Name: "bar", Namespace: common.PointerTo[gwv1beta1.Namespace]("2")}, + {Name: "baz", Namespace: common.PointerTo[gwv1beta1.Namespace]("3")}, }, }, certificates: []corev1.Secret{ @@ -370,7 +487,7 @@ func TestValidateTLS(t *testing.T) { expectedAcceptedErr: nil, }, "valid same namespace": { - namespace: "test", + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tls: &gwv1beta1.GatewayTLSConfig{ CertificateRefs: []gwv1beta1.SecretObjectReference{ {Name: "foo"}, @@ -379,15 +496,15 @@ func TestValidateTLS(t *testing.T) { }, }, certificates: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "default"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "default"}}, }, expectedResolvedRefsErr: nil, expectedAcceptedErr: nil, }, "valid empty certs": { - namespace: "test", + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tls: &gwv1beta1.GatewayTLSConfig{}, certificates: nil, expectedResolvedRefsErr: nil, @@ -395,7 +512,14 @@ func TestValidateTLS(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - actualAcceptedError, actualResolvedRefsError := validateTLS(tt.namespace, tt.tls, tt.certificates) + resources := common.NewResourceMap(common.ResourceTranslator{}, NewReferenceValidator(tt.grants), logrtest.NewTestLogger(t)) + for _, certificate := range tt.certificates { + // make the data valid + certificate.Data = secret.Data + resources.ReferenceCountCertificate(certificate) + } + + actualAcceptedError, actualResolvedRefsError := validateTLS(tt.gateway, tt.tls, resources) require.Equal(t, tt.expectedResolvedRefsErr, actualResolvedRefsError) require.Equal(t, tt.expectedAcceptedErr, actualAcceptedError) }) @@ -441,7 +565,7 @@ func TestValidateListeners(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expectedAcceptedErr, validateListeners("", tt.listeners, nil)[0].acceptedErr) + require.Equal(t, tt.expectedAcceptedErr, validateListeners(gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tt.listeners, nil)[0].acceptedErr) }) } } @@ -468,32 +592,32 @@ func TestRouteAllowedForListenerNamespaces(t *testing.T) { expected: false, }, "explicit same namespace allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: pointerTo(gwv1beta1.NamespacesFromSame)}}, + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: common.PointerTo(gwv1beta1.NamespacesFromSame)}}, gatewayNamespace: "test", routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, expected: true, }, "explicit same namespace not allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: pointerTo(gwv1beta1.NamespacesFromSame)}}, + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: common.PointerTo(gwv1beta1.NamespacesFromSame)}}, gatewayNamespace: "test", routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other"}}, expected: false, }, "all namespace allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: pointerTo(gwv1beta1.NamespacesFromAll)}}, + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: common.PointerTo(gwv1beta1.NamespacesFromAll)}}, gatewayNamespace: "test", routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other"}}, expected: true, }, "invalid namespace from not allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: pointerTo[gwv1beta1.FromNamespaces]("other")}}, + allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: common.PointerTo[gwv1beta1.FromNamespaces]("other")}}, gatewayNamespace: "test", routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, expected: false, }, "labeled namespace allowed": { allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromSelector), + From: common.PointerTo(gwv1beta1.NamespacesFromSelector), Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }}, gatewayNamespace: "test", @@ -504,7 +628,7 @@ func TestRouteAllowedForListenerNamespaces(t *testing.T) { }, "labeled namespace not allowed": { allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromSelector), + From: common.PointerTo(gwv1beta1.NamespacesFromSelector), Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }}, gatewayNamespace: "test", @@ -515,7 +639,7 @@ func TestRouteAllowedForListenerNamespaces(t *testing.T) { }, "invalid labeled namespace": { allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ - From: pointerTo(gwv1beta1.NamespacesFromSelector), + From: common.PointerTo(gwv1beta1.NamespacesFromSelector), Selector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{ {Key: "foo", Operator: "junk", Values: []string{"1"}}, }}, @@ -547,17 +671,17 @@ func TestRouteAllowedForListenerHostname(t *testing.T) { expected: true, }, "empty hostname": { - hostname: pointerTo[gwv1beta1.Hostname]("foo"), + hostname: common.PointerTo[gwv1beta1.Hostname]("foo"), hostnames: nil, expected: true, }, "any hostname match": { - hostname: pointerTo[gwv1beta1.Hostname]("foo"), + hostname: common.PointerTo[gwv1beta1.Hostname]("foo"), hostnames: []gwv1beta1.Hostname{"foo", "bar"}, expected: true, }, "no match": { - hostname: pointerTo[gwv1beta1.Hostname]("foo"), + hostname: common.PointerTo[gwv1beta1.Hostname]("foo"), hostnames: []gwv1beta1.Hostname{"bar"}, expected: false, }, @@ -638,7 +762,7 @@ func TestRouteKindIsAllowedForListener(t *testing.T) { }, "group specified": { kinds: []gwv1beta1.RouteGroupKind{ - {Group: pointerTo[gwv1beta1.Group]("a"), Kind: "b"}, + {Group: common.PointerTo[gwv1beta1.Group]("a"), Kind: "b"}, }, gk: schema.GroupKind{Group: "a", Kind: "b"}, expected: true, @@ -659,7 +783,7 @@ func TestRouteKindIsAllowedForListener(t *testing.T) { }, "group mismatch": { kinds: []gwv1beta1.RouteGroupKind{ - {Group: pointerTo[gwv1beta1.Group]("a"), Kind: "b"}, + {Group: common.PointerTo[gwv1beta1.Group]("a"), Kind: "b"}, }, gk: schema.GroupKind{Group: "d", Kind: "b"}, expected: false, diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index 7b66067123..5c4125a040 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -11,14 +11,12 @@ import ( "time" "github.com/go-logr/logr" - "github.com/google/go-cmp/cmp" "golang.org/x/exp/slices" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" ) @@ -30,180 +28,11 @@ const ( var Kinds = []string{api.APIGateway, api.HTTPRoute, api.TCPRoute, api.InlineCertificate} type Config struct { - ConsulClientConfig *consul.Config - ConsulServerConnMgr consul.ServerConnectionManager - NamespacesEnabled bool - PeeringEnabled bool - Logger logr.Logger -} - -type resourceCache map[api.ResourceReference]api.ConfigEntry - -func (oldCache resourceCache) diff(newCache resourceCache) []api.ConfigEntry { - diffs := make([]api.ConfigEntry, 0) - - for ref, entry := range newCache { - oldRef, ok := oldCache[ref] - // ref from the new cache doesn't exist in the old one - // this means a resource was added - if !ok { - diffs = append(diffs, entry) - continue - } - - // the entry in the old cache has an older modify index than the ref - // from the new cache - if oldRef.GetModifyIndex() < entry.GetModifyIndex() { - diffs = append(diffs, entry) - } - } - - // get all deleted entries, these are entries present in the old cache - // that are not present in the new - for ref, entry := range oldCache { - if _, ok := newCache[ref]; !ok { - diffs = append(diffs, entry) - } - } - - return diffs -} - -type serviceCache map[api.ResourceReference]*api.CatalogService - -func (oldCache serviceCache) diff(newCache serviceCache) []*api.CatalogService { - diffs := make([]*api.CatalogService, 0) - - for ref, entry := range newCache { - oldRef, ok := oldCache[ref] - // ref from the new cache doesn't exist in the old one - // this means a resource was added - if !ok { - diffs = append(diffs, entry) - continue - } - - // the entry in the old cache has an older modify index than the ref - // from the new cache - if oldRef.ModifyIndex < entry.ModifyIndex { - diffs = append(diffs, entry) - } - } - - // get all deleted entries, these are entries present in the old cache - // that are not present in the new - for ref, entry := range oldCache { - if _, ok := newCache[ref]; !ok { - diffs = append(diffs, entry) - } - } - return diffs -} - -type peeringCache map[api.ResourceReference]*api.Peering - -func (oldCache peeringCache) diff(newCache peeringCache) []*api.Peering { - diffs := make([]*api.Peering, 0) - - for ref, entry := range newCache { - oldRef, ok := oldCache[ref] - // ref from the new cache doesn't exist in the old one - // this means a resource was added - if !ok { - diffs = append(diffs, entry) - continue - } - - // the entry in the old cache has an older modify index than the ref - // from the new cache - if oldRef.ModifyIndex < entry.ModifyIndex { - diffs = append(diffs, entry) - } - } - - // get all deleted entries, these are entries present in the old cache - // that are not present in the new - for ref, entry := range oldCache { - if _, ok := newCache[ref]; !ok { - diffs = append(diffs, entry) - } - } - return diffs -} - -// configEntryObject is used for generic k8s events so we maintain the consul name/namespace. -type configEntryObject struct { - client.Object // embed so we fufill the object interface - - Namespace string - Name string -} - -func (c *configEntryObject) GetNamespace() string { - return c.Namespace -} - -func (c *configEntryObject) GetName() string { - return c.Name -} - -func newConfigEntryObject(namespacedName types.NamespacedName) *configEntryObject { - return &configEntryObject{ - Namespace: namespacedName.Namespace, - Name: namespacedName.Name, - } -} - -// Subscription represents a watcher for events on a specific kind. -type Subscription struct { - translator translation.TranslatorFn - ctx context.Context - cancelCtx context.CancelFunc - events chan event.GenericEvent -} - -func (s *Subscription) Cancel() { - s.cancelCtx() -} - -func (s *Subscription) Events() chan event.GenericEvent { - return s.events -} - -type ServiceTranslatorFn func(*api.CatalogService) []types.NamespacedName - -// ServiceSubscription represents a watcher for events on a specific kind. -type ServiceSubscription struct { - translator ServiceTranslatorFn - ctx context.Context - cancelCtx context.CancelFunc - events chan event.GenericEvent -} - -func (s *ServiceSubscription) Cancel() { - s.cancelCtx() -} - -func (s *ServiceSubscription) Events() chan event.GenericEvent { - return s.events -} - -type PeeringTranslatorFn func(*api.Peering) []types.NamespacedName - -// PeeringsSubscription represents a watcher for events on a specific kind. -type PeeringsSubscription struct { - translator PeeringTranslatorFn - ctx context.Context - cancelCtx context.CancelFunc - events chan event.GenericEvent -} - -func (s *PeeringsSubscription) Cancel() { - s.cancelCtx() -} - -func (s *PeeringsSubscription) Events() chan event.GenericEvent { - return s.events + ConsulClientConfig *consul.Config + ConsulServerConnMgr consul.ServerConnectionManager + NamespacesEnabled bool + CrossNamespaceACLPolicy string + Logger logr.Logger } // Cache subscribes to and caches Consul objects, it also responsible for mainting subscriptions to @@ -213,18 +42,14 @@ type Cache struct { serverMgr consul.ServerConnectionManager logger logr.Logger - cache map[string]resourceCache - serviceCache serviceCache - peeringCache peeringCache - cacheMutex *sync.Mutex + cache map[string]*common.ReferenceMap + cacheMutex *sync.Mutex - subscribers map[string][]*Subscription - serviceSubscribers []*ServiceSubscription - peeringsSubscribers []*PeeringsSubscription - subscriberMutex *sync.Mutex + subscribers map[string][]*Subscription + subscriberMutex *sync.Mutex - namespacesEnabled bool - peeringsEnabled bool + namespacesEnabled bool + crossNamespaceACLPolicy string synced chan struct{} @@ -232,30 +57,24 @@ type Cache struct { } func New(config Config) *Cache { - cache := make(map[string]resourceCache, len(Kinds)) + cache := make(map[string]*common.ReferenceMap, len(Kinds)) for _, kind := range Kinds { - cache[kind] = make(resourceCache) + cache[kind] = common.NewReferenceMap() } config.ConsulClientConfig.APITimeout = apiTimeout return &Cache{ - config: config.ConsulClientConfig, - serverMgr: config.ConsulServerConnMgr, - namespacesEnabled: config.NamespacesEnabled, - peeringsEnabled: config.PeeringEnabled, - cache: cache, - serviceCache: make(serviceCache), - peeringCache: make(peeringCache), - cacheMutex: &sync.Mutex{}, - subscribers: make(map[string][]*Subscription), - serviceSubscribers: make([]*ServiceSubscription, 0), - peeringsSubscribers: make([]*PeeringsSubscription, 0), - subscriberMutex: &sync.Mutex{}, - kinds: Kinds, - // we make a buffered channel that is the length of the kinds which - // are subscribed to + 2 for services + peerings - synced: make(chan struct{}, len(Kinds)+2), - logger: config.Logger, + config: config.ConsulClientConfig, + serverMgr: config.ConsulServerConnMgr, + namespacesEnabled: config.NamespacesEnabled, + cache: cache, + cacheMutex: &sync.Mutex{}, + subscribers: make(map[string][]*Subscription), + subscriberMutex: &sync.Mutex{}, + kinds: Kinds, + synced: make(chan struct{}, len(Kinds)), + logger: config.Logger, + crossNamespaceACLPolicy: config.CrossNamespaceACLPolicy, } } @@ -268,24 +87,10 @@ func (c *Cache) WaitSynced(ctx context.Context) { return } } - // one more for service subscribers - select { - case <-c.synced: - case <-ctx.Done(): - return - } - if c.peeringsEnabled { - // and one more for peerings subscribers - select { - case <-c.synced: - case <-ctx.Done(): - return - } - } } // Subscribe handles adding a new subscription for resources of a given kind. -func (c *Cache) Subscribe(ctx context.Context, kind string, translator translation.TranslatorFn) *Subscription { +func (c *Cache) Subscribe(ctx context.Context, kind string, translator TranslatorFn) *Subscription { c.subscriberMutex.Lock() defer c.subscriberMutex.Unlock() @@ -315,42 +120,6 @@ func (c *Cache) Subscribe(ctx context.Context, kind string, translator translati return sub } -// SubscribeServices handles adding a new subscription for resources of a given kind. -func (c *Cache) SubscribeServices(ctx context.Context, translator ServiceTranslatorFn) *ServiceSubscription { - c.subscriberMutex.Lock() - defer c.subscriberMutex.Unlock() - - ctx, cancel := context.WithCancel(ctx) - events := make(chan event.GenericEvent) - sub := &ServiceSubscription{ - translator: translator, - ctx: ctx, - cancelCtx: cancel, - events: events, - } - - c.serviceSubscribers = append(c.serviceSubscribers, sub) - return sub -} - -// SubscribeServices handles adding a new subscription for resources of a given kind. -func (c *Cache) SubscribePeerings(ctx context.Context, translator PeeringTranslatorFn) *PeeringsSubscription { - c.subscriberMutex.Lock() - defer c.subscriberMutex.Unlock() - - ctx, cancel := context.WithCancel(ctx) - events := make(chan event.GenericEvent) - sub := &PeeringsSubscription{ - translator: translator, - ctx: ctx, - cancelCtx: cancel, - events: events, - } - - c.peeringsSubscribers = append(c.peeringsSubscribers, sub) - return sub -} - // Run starts the cache watch cycle, on the first call it will fill the cache with existing resources. func (c *Cache) Run(ctx context.Context) { wg := &sync.WaitGroup{} @@ -365,20 +134,6 @@ func (c *Cache) Run(ctx context.Context) { }() } - wg.Add(1) - go func() { - defer wg.Done() - c.subscribeToConsulServices(ctx) - }() - - if c.peeringsEnabled { - wg.Add(1) - go func() { - defer wg.Done() - c.subscribeToConsulPeerings(ctx) - }() - } - wg.Wait() } @@ -422,106 +177,16 @@ func (c *Cache) subscribeToConsul(ctx context.Context, kind string) { } } -func (c *Cache) subscribeToConsulServices(ctx context.Context) { - once := &sync.Once{} - - opts := &api.QueryOptions{Connect: true} - - // we need a second set of opts to make sure we don't - // block on the secondary list operations - serviceListOpts := &api.QueryOptions{Connect: true} - -MAIN_LOOP: - for { - select { - case <-ctx.Done(): - return - default: - } - - client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) - if err != nil { - c.logger.Error(err, "error initializing consul client") - continue - } - - services, meta, err := client.Catalog().Services(opts.WithContext(ctx)) - if err != nil { - c.logger.Error(err, "error fetching services") - continue - } - - flattened := []*api.CatalogService{} - for service := range services { - serviceList, _, err := client.Catalog().Service(service, "", serviceListOpts.WithContext(ctx)) - if err != nil { - c.logger.Error(err, fmt.Sprintf("error fetching service: %s", service)) - continue MAIN_LOOP - } - flattened = append(flattened, serviceList...) - } - - opts.WaitIndex = meta.LastIndex - c.updateAndNotifyServices(ctx, once, flattened) - - select { - case <-ctx.Done(): - return - default: - continue - } - } -} - -func (c *Cache) subscribeToConsulPeerings(ctx context.Context) { - once := &sync.Once{} - - opts := &api.QueryOptions{Connect: true} - if c.namespacesEnabled { - opts.Namespace = namespaceWildcard - } - - for { - select { - case <-ctx.Done(): - return - default: - } - - client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) - if err != nil { - c.logger.Error(err, "error initializing consul client") - continue - } - - peerings, meta, err := client.Peerings().List(ctx, opts.WithContext(ctx)) - if err != nil { - c.logger.Error(err, "error fetching services") - continue - } - - opts.WaitIndex = meta.LastIndex - c.updateAndNotifyPeerings(ctx, once, peerings) - - select { - case <-ctx.Done(): - return - default: - continue - } - } -} - func (c *Cache) updateAndNotify(ctx context.Context, once *sync.Once, kind string, entries []api.ConfigEntry) { c.cacheMutex.Lock() - cache := make(resourceCache) + cache := common.NewReferenceMap() for _, entry := range entries { - cache[translation.EntryToReference(entry)] = entry + cache.Set(common.EntryToReference(entry), entry) } - diffs := c.cache[kind].diff(cache) + diffs := c.cache[kind].Diff(cache) c.cache[kind] = cache @@ -537,130 +202,6 @@ func (c *Cache) updateAndNotify(ctx context.Context, once *sync.Once, kind strin c.notifySubscribers(ctx, kind, diffs) } -func (c *Cache) updateAndNotifyServices(ctx context.Context, once *sync.Once, services []*api.CatalogService) { - c.cacheMutex.Lock() - - cache := make(serviceCache) - - for _, service := range services { - cache[api.ResourceReference{Name: service.ServiceName, Namespace: service.Namespace, Partition: service.Partition}] = service - } - - diffs := c.serviceCache.diff(cache) - - c.serviceCache = cache - - // we run this the first time the cache is filled to notify the waiter - once.Do(func() { - c.logger.Info("sync mark for services") - c.synced <- struct{}{} - }) - - c.cacheMutex.Unlock() - - // now notify all subscribers - c.notifyServiceSubscribers(ctx, diffs) -} - -func (c *Cache) updateAndNotifyPeerings(ctx context.Context, once *sync.Once, peerings []*api.Peering) { - c.cacheMutex.Lock() - - cache := make(peeringCache) - - for _, peering := range peerings { - cache[api.ResourceReference{Name: peering.Name, Namespace: peering.ID, Partition: peering.Partition}] = peering - } - - diffs := c.peeringCache.diff(cache) - - c.peeringCache = cache - - // we run this the first time the cache is filled to notify the waiter - once.Do(func() { - c.logger.Info("sync mark for peerings") - c.synced <- struct{}{} - }) - - c.cacheMutex.Unlock() - - // now notify all peering subscribers - c.notifyPeeringSubscribers(ctx, diffs) -} - -// notifyServiceSubscribers notifies each subscriber for a given kind on changes to a config entry of that kind. It also -// handles removing any subscribers that have marked themselves as done. -func (c *Cache) notifyServiceSubscribers(ctx context.Context, services []*api.CatalogService) { - c.subscriberMutex.Lock() - defer c.subscriberMutex.Unlock() - - for _, service := range services { - // this will hold the new list of current subscribers after we finish notifying - subscribers := make([]*ServiceSubscription, 0, len(c.serviceSubscribers)) - for _, subscriber := range c.serviceSubscribers { - addSubscriber := false - - for _, namespaceName := range subscriber.translator(service) { - event := event.GenericEvent{ - Object: newConfigEntryObject(namespaceName), - } - - select { - case <-ctx.Done(): - return - case <-subscriber.ctx.Done(): - // don't add this subscriber to current list because it is done - addSubscriber = false - case subscriber.events <- event: - // keep this one since we can send events to it - addSubscriber = true - } - } - - if addSubscriber { - subscribers = append(subscribers, subscriber) - } - } - c.serviceSubscribers = subscribers - } -} - -// notifyPeeringSubscribers notifies each subscriber for a given kind on changes to a config entry of that kind. It also -// handles removing any subscribers that have marked themselves as done. -func (c *Cache) notifyPeeringSubscribers(ctx context.Context, peerings []*api.Peering) { - c.subscriberMutex.Lock() - defer c.subscriberMutex.Unlock() - - for _, peering := range peerings { - // this will hold the new list of current subscribers after we finish notifying - subscribers := make([]*PeeringsSubscription, 0, len(c.peeringsSubscribers)) - for _, subscriber := range c.peeringsSubscribers { - addSubscriber := false - - for _, namespaceName := range subscriber.translator(peering) { - event := event.GenericEvent{ - Object: newConfigEntryObject(namespaceName), - } - - select { - case <-ctx.Done(): - return - case <-subscriber.ctx.Done(): - // don't add this subscriber to current list because it is done - addSubscriber = false - case subscriber.events <- event: - // keep this one since we can send events to it - addSubscriber = true - } - } - - if addSubscriber { - subscribers = append(subscribers, subscriber) - } - } - c.peeringsSubscribers = subscribers - } -} - // notifySubscribers notifies each subscriber for a given kind on changes to a config entry of that kind. It also // handles removing any subscribers that have marked themselves as done. func (c *Cache) notifySubscribers(ctx context.Context, kind string, entries []api.ConfigEntry) { @@ -709,16 +250,11 @@ func (c *Cache) Write(ctx context.Context, entry api.ConfigEntry) error { return nil } - ref := translation.EntryToReference(entry) + ref := common.EntryToReference(entry) - old, ok := entryMap[ref] - if ok { - if cmp.Equal(old, entry, cmp.FilterPath(func(p cmp.Path) bool { - path := p.String() - return strings.HasSuffix(path, "Status") || strings.HasSuffix(path, "ModifyIndex") || strings.HasSuffix(path, "CreateIndex") - }, cmp.Ignore())) { - return nil - } + old := entryMap.Get(ref) + if old != nil && common.EntriesEqual(old, entry) { + return nil } client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) @@ -726,6 +262,12 @@ func (c *Cache) Write(ctx context.Context, entry api.ConfigEntry) error { return err } + if c.namespacesEnabled { + if _, err := namespaces.EnsureExists(client, entry.GetNamespace(), c.crossNamespaceACLPolicy); err != nil { + return err + } + } + options := &api.WriteOptions{} _, _, err = client.ConfigEntries().Set(entry, options.WithContext(ctx)) @@ -746,12 +288,7 @@ func (c *Cache) Get(ref api.ResourceReference) api.ConfigEntry { return nil } - entry, ok := entryMap[ref] - if !ok { - return nil - } - - return entry + return entryMap.Get(ref) } // Delete handles deleting the config entry from consul, if the current reference of the config entry is stale then @@ -764,8 +301,8 @@ func (c *Cache) Delete(ctx context.Context, ref api.ResourceReference) error { if !ok { return nil } - _, ok = entryMap[ref] - if !ok { + + if entryMap.Get(ref) == nil { c.logger.Info("cached object not found, not deleting") return nil } @@ -786,29 +323,12 @@ func (c *Cache) List(kind string) []api.ConfigEntry { c.cacheMutex.Lock() defer c.cacheMutex.Unlock() - entryMap, ok := c.cache[kind] + refMap, ok := c.cache[kind] if !ok { return nil } - entries := make([]api.ConfigEntry, 0, len(entryMap)) - for _, entry := range entryMap { - entries = append(entries, entry) - } - - return entries -} - -// ListServices returns a list of services from the cache that corresponds to the given kind. -func (c *Cache) ListServices() []api.CatalogService { - c.cacheMutex.Lock() - defer c.cacheMutex.Unlock() - - entries := make([]api.CatalogService, 0, len(c.serviceCache)) - for _, service := range c.serviceCache { - entries = append(entries, *service) - } - return entries + return refMap.Entries() } // LinkPolicy links a mesh write policy to a token associated with the service. diff --git a/control-plane/api-gateway/cache/consul_test.go b/control-plane/api-gateway/cache/consul_test.go index 256aa6b31d..555e13b6c2 100644 --- a/control-plane/api-gateway/cache/consul_test.go +++ b/control-plane/api-gateway/cache/consul_test.go @@ -16,11 +16,12 @@ import ( "github.com/go-logr/logr" logrtest "github.com/go-logr/logr/testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/event" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" @@ -29,21 +30,18 @@ import ( func Test_resourceCache_diff(t *testing.T) { t.Parallel() type args struct { - newCache resourceCache + newCache *common.ReferenceMap } tests := []struct { name string - oldCache resourceCache + oldCache *common.ReferenceMap args args want []api.ConfigEntry }{ { name: "no difference", - oldCache: resourceCache{ - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + oldCache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", Parents: []api.ResourceReference{ @@ -123,14 +121,11 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - }, + }, + })[api.HTTPRoute], args: args{ - newCache: resourceCache{ - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + newCache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", Parents: []api.ResourceReference{ @@ -210,18 +205,15 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - }, + }, + })[api.HTTPRoute], }, want: []api.ConfigEntry{}, }, { name: "resource exists in old cache but not new one", - oldCache: resourceCache{ - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + oldCache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", Parents: []api.ResourceReference{ @@ -301,11 +293,8 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route 2", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + }, + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route 2", Parents: []api.ResourceReference{ @@ -385,14 +374,11 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - }, + }, + })[api.HTTPRoute], args: args{ - newCache: resourceCache{ - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + newCache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", Parents: []api.ResourceReference{ @@ -472,11 +458,11 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - }, + }, + })[api.HTTPRoute], }, want: []api.ConfigEntry{ - api.ConfigEntry(&api.HTTPRouteConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route 2", Parents: []api.ResourceReference{ @@ -556,16 +542,13 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), + }, }, }, { name: "resource exists in new cache but not old one", - oldCache: resourceCache{ - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + oldCache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", Parents: []api.ResourceReference{ @@ -645,14 +628,11 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - }, + }, + })[api.HTTPRoute], args: args{ - newCache: resourceCache{ - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + newCache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", Parents: []api.ResourceReference{ @@ -732,12 +712,8 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route 2", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + }, + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route 2", Parents: []api.ResourceReference{ @@ -817,11 +793,11 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - }, + }, + })[api.HTTPRoute], }, want: []api.ConfigEntry{ - api.ConfigEntry(&api.HTTPRouteConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route 2", Parents: []api.ResourceReference{ @@ -901,16 +877,13 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), + }, }, }, { name: "same ref new cache has a greater modify index", - oldCache: resourceCache{ - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + oldCache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", ModifyIndex: 1, @@ -991,14 +964,11 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - }, + }, + })[api.HTTPRoute], args: args{ - newCache: resourceCache{ - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "my route", - }: api.ConfigEntry(&api.HTTPRouteConfigEntry{ + newCache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", ModifyIndex: 10, @@ -1079,11 +1049,11 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), - }, + }, + })[api.HTTPRoute], }, want: []api.ConfigEntry{ - api.ConfigEntry(&api.HTTPRouteConfigEntry{ + &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "my route", ModifyIndex: 10, @@ -1164,7 +1134,7 @@ func Test_resourceCache_diff(t *testing.T) { Hostnames: []string{"hostname.com"}, Meta: map[string]string{}, Status: api.ConfigEntryStatus{}, - }), + }, }, }, } @@ -1172,7 +1142,7 @@ func Test_resourceCache_diff(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - got := tt.oldCache.diff(tt.args.newCache) + got := tt.oldCache.Diff(tt.args.newCache) if diff := cmp.Diff(got, tt.want); diff != "" { t.Errorf("resourceCache.diff mismatch (-want +got):\n%s", diff) } @@ -1185,7 +1155,7 @@ func TestCache_Subscribe(t *testing.T) { type args struct { ctx context.Context kind string - translator translation.TranslatorFn + translator TranslatorFn } tests := []struct { name string @@ -1327,7 +1297,7 @@ func TestCache_Write(t *testing.T) { }, ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), NamespacesEnabled: false, - Logger: logr.Logger{}, + Logger: logrtest.NewTestLogger(t), }) entry := &api.HTTPRouteConfigEntry{ @@ -1427,7 +1397,7 @@ func TestCache_Get(t *testing.T) { name string args args want api.ConfigEntry - cache map[string]resourceCache + cache map[string]*common.ReferenceMap }{ { name: "entry exists", @@ -1442,26 +1412,18 @@ func TestCache_Get(t *testing.T) { Name: "api-gw", Meta: map[string]string{}, }, - cache: map[string]resourceCache{ - api.APIGateway: { - api.ResourceReference{ - Kind: api.APIGateway, - Name: "api-gw", - }: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{}, - }, - api.ResourceReference{ - Kind: api.APIGateway, - Name: "api-gw-2", - }: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-2", - Meta: map[string]string{}, - }, + cache: loadedReferenceMaps([]api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{}, }, - }, + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-2", + Meta: map[string]string{}, + }, + }), }, { name: "entry does not exist", @@ -1472,26 +1434,18 @@ func TestCache_Get(t *testing.T) { }, }, want: nil, - cache: map[string]resourceCache{ - api.APIGateway: { - api.ResourceReference{ - Kind: api.APIGateway, - Name: "api-gw", - }: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{}, - }, - api.ResourceReference{ - Kind: api.APIGateway, - Name: "api-gw-2", - }: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-2", - Meta: map[string]string{}, - }, + cache: loadedReferenceMaps([]api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw", + Meta: map[string]string{}, }, - }, + &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gw-2", + Meta: map[string]string{}, + }, + }), }, { name: "kind key does not exist", @@ -1502,18 +1456,13 @@ func TestCache_Get(t *testing.T) { }, }, want: nil, - cache: map[string]resourceCache{ - api.HTTPRoute: { - api.ResourceReference{ - Kind: api.HTTPRoute, - Name: "api-gw", - }: &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "route", - Meta: map[string]string{}, - }, + cache: loadedReferenceMaps([]api.ConfigEntry{ + &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "route", + Meta: map[string]string{}, }, - }, + }), }, } for _, tt := range tests { @@ -1614,30 +1563,18 @@ func Test_Run(t *testing.T) { NamespacesEnabled: false, Logger: logrtest.NewTestLogger(t), }) - prevCache := make(map[string]resourceCache) + prevCache := make(map[string]*common.ReferenceMap) for kind, cache := range c.cache { - resCache := make(resourceCache) - for resourceRef, entry := range cache { - resCache[resourceRef] = entry + resCache := common.NewReferenceMap() + for _, entry := range cache.Entries() { + resCache.Set(common.EntryToReference(entry), entry) } prevCache[kind] = resCache } - expectedCache := map[string]resourceCache{ - api.APIGateway: { - {Kind: api.APIGateway, Name: gw.Name}: gw, - }, - api.TCPRoute: { - {Kind: api.TCPRoute, Name: tcpRoute.Name}: tcpRoute, - }, - api.HTTPRoute: { - {Kind: api.HTTPRoute, Name: httpRouteOne.Name}: httpRouteOne, - {Kind: api.HTTPRoute, Name: httpRouteTwo.Name}: httpRouteTwo, - }, - api.InlineCertificate: { - {Kind: api.InlineCertificate, Name: inlineCert.Name}: inlineCert, - }, - } + expectedCache := loadedReferenceMaps([]api.ConfigEntry{ + gw, tcpRoute, httpRouteOne, httpRouteTwo, inlineCert, + }) ctx, cancelFn := context.WithCancel(context.Background()) @@ -1696,9 +1633,6 @@ func Test_Run(t *testing.T) { } }) - c.SubscribeServices(ctx, func(cs *api.CatalogService) []types.NamespacedName { return nil }).Cancel() - c.SubscribePeerings(ctx, func(cs *api.Peering) []types.NamespacedName { return nil }).Cancel() - // mark this subscription as ended canceledSub.Cancel() @@ -1736,14 +1670,19 @@ func Test_Run(t *testing.T) { // cancel the context so the Run function exits cancelFn() + sorter := func(x, y api.ConfigEntry) bool { + return x.GetName() < y.GetName() + } // Check cache // expect the cache to have changed - if diff := cmp.Diff(prevCache, c.cache); diff == "" { - t.Error("Expect cache to have changed but it did not") - } + for _, kind := range Kinds { + if diff := cmp.Diff(prevCache[kind].Entries(), c.cache[kind].Entries(), cmpopts.SortSlices(sorter)); diff == "" { + t.Error("Expect cache to have changed but it did not") + } - if diff := cmp.Diff(expectedCache, c.cache); diff != "" { - t.Errorf("Cache.cache mismatch (-want +got):\n%s", diff) + if diff := cmp.Diff(expectedCache[kind].Entries(), c.cache[kind].Entries(), cmpopts.SortSlices(sorter)); diff != "" { + t.Errorf("Cache.cache mismatch (-want +got):\n%s", diff) + } } } @@ -2026,3 +1965,17 @@ func TestCache_Delete(t *testing.T) { }) } } + +func loadedReferenceMaps(entries []api.ConfigEntry) map[string]*common.ReferenceMap { + refs := make(map[string]*common.ReferenceMap) + + for _, entry := range entries { + refMap, ok := refs[entry.GetKind()] + if !ok { + refMap = common.NewReferenceMap() + } + refMap.Set(common.EntryToReference(entry), entry) + refs[entry.GetKind()] = refMap + } + return refs +} diff --git a/control-plane/api-gateway/cache/gateway.go b/control-plane/api-gateway/cache/gateway.go new file mode 100644 index 0000000000..d6d0c27ed2 --- /dev/null +++ b/control-plane/api-gateway/cache/gateway.go @@ -0,0 +1,136 @@ +package cache + +import ( + "context" + "fmt" + "sync" + + "github.com/cenkalti/backoff" + "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul/api" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/event" +) + +type GatewayCache struct { + config *consul.Config + serverMgr consul.ServerConnectionManager + logger logr.Logger + + events chan event.GenericEvent + + data map[api.ResourceReference][]api.CatalogService + dataMutex sync.RWMutex + + subscribedGateways map[api.ResourceReference]context.CancelFunc + mutex sync.RWMutex + + ctx context.Context +} + +func NewGatewayCache(ctx context.Context, config Config) *GatewayCache { + return &GatewayCache{ + config: config.ConsulClientConfig, + serverMgr: config.ConsulServerConnMgr, + logger: config.Logger, + events: make(chan event.GenericEvent), + data: make(map[api.ResourceReference][]api.CatalogService), + subscribedGateways: make(map[api.ResourceReference]context.CancelFunc), + ctx: ctx, + } +} + +func (r *GatewayCache) ServicesFor(ref api.ResourceReference) []api.CatalogService { + r.dataMutex.RLock() + defer r.dataMutex.RUnlock() + + return r.data[common.NormalizeMeta(ref)] +} + +func (r *GatewayCache) EnsureSubscribed(ref api.ResourceReference, resource types.NamespacedName) { + r.mutex.Lock() + defer r.mutex.Unlock() + + if _, exists := r.subscribedGateways[common.NormalizeMeta(ref)]; exists { + return + } + + ctx, cancel := context.WithCancel(r.ctx) + r.subscribedGateways[common.NormalizeMeta(ref)] = cancel + go r.subscribeToGateway(ctx, ref, resource) +} + +func (r *GatewayCache) RemoveSubscription(ref api.ResourceReference) { + r.mutex.Lock() + defer r.mutex.Unlock() + + if cancel, exists := r.subscribedGateways[common.NormalizeMeta(ref)]; exists { + cancel() + delete(r.subscribedGateways, common.NormalizeMeta(ref)) + } +} + +func (r *GatewayCache) subscribeToGateway(ctx context.Context, ref api.ResourceReference, resource types.NamespacedName) { + opts := &api.QueryOptions{} + if ref.Namespace != "" { + opts.Namespace = ref.Namespace + } + + var ( + services []*api.CatalogService + meta *api.QueryMeta + ) + + for { + select { + case <-ctx.Done(): + r.dataMutex.Lock() + delete(r.data, ref) + r.dataMutex.Unlock() + return + default: + } + + retryBackoff := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 10) + + if err := backoff.Retry(func() error { + client, err := consul.NewClientFromConnMgr(r.config, r.serverMgr) + if err != nil { + return err + } + + services, meta, err = client.Catalog().Service(ref.Name, "", opts.WithContext(ctx)) + if err != nil { + return err + } + + return nil + }, backoff.WithContext(retryBackoff, ctx)); err != nil { + r.logger.Error(err, fmt.Sprintf("unable to fetch config entry for gateway: %s/%s", ref.Namespace, ref.Name)) + continue + } + + opts.WaitIndex = meta.LastIndex + + derefed := common.DerefAll(services) + + r.dataMutex.Lock() + r.data[common.NormalizeMeta(ref)] = derefed + r.dataMutex.Unlock() + + event := event.GenericEvent{ + Object: newConfigEntryObject(resource), + } + + select { + case <-ctx.Done(): + r.dataMutex.Lock() + delete(r.data, ref) + r.dataMutex.Unlock() + return + case r.events <- event: + } + } +} diff --git a/control-plane/api-gateway/cache/kubernetes.go b/control-plane/api-gateway/cache/kubernetes.go new file mode 100644 index 0000000000..642a6935fb --- /dev/null +++ b/control-plane/api-gateway/cache/kubernetes.go @@ -0,0 +1,32 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cache + +import ( + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// configEntryObject is used for generic k8s events so we maintain the consul name/namespace. +type configEntryObject struct { + client.Object // embed so we fufill the object interface + + Namespace string + Name string +} + +func (c *configEntryObject) GetNamespace() string { + return c.Namespace +} + +func (c *configEntryObject) GetName() string { + return c.Name +} + +func newConfigEntryObject(namespacedName types.NamespacedName) *configEntryObject { + return &configEntryObject{ + Namespace: namespacedName.Namespace, + Name: namespacedName.Name, + } +} diff --git a/control-plane/api-gateway/cache/subscription.go b/control-plane/api-gateway/cache/subscription.go new file mode 100644 index 0000000000..8605c95926 --- /dev/null +++ b/control-plane/api-gateway/cache/subscription.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cache + +import ( + "context" + + "github.com/hashicorp/consul/api" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/event" +) + +type TranslatorFn func(api.ConfigEntry) []types.NamespacedName + +// Subscription represents a watcher for events on a specific kind. +type Subscription struct { + translator TranslatorFn + ctx context.Context + cancelCtx context.CancelFunc + events chan event.GenericEvent +} + +func (s *Subscription) Cancel() { + s.cancelCtx() +} + +func (s *Subscription) Events() chan event.GenericEvent { + return s.events +} diff --git a/control-plane/api-gateway/common/constants.go b/control-plane/api-gateway/common/constants.go new file mode 100644 index 0000000000..68abfc96b1 --- /dev/null +++ b/control-plane/api-gateway/common/constants.go @@ -0,0 +1,8 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +const ( + AnnotationGatewayClassConfig = "consul.hashicorp.com/gateway-class-config" +) diff --git a/control-plane/api-gateway/common/diff.go b/control-plane/api-gateway/common/diff.go new file mode 100644 index 0000000000..b58bf23901 --- /dev/null +++ b/control-plane/api-gateway/common/diff.go @@ -0,0 +1,263 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "strings" + + "github.com/hashicorp/consul/api" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func GatewayStatusesEqual(a, b gwv1beta1.GatewayStatus) bool { + return slices.EqualFunc(a.Addresses, b.Addresses, gatewayStatusesAddressesEqual) && + slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) && + slices.EqualFunc(a.Listeners, b.Listeners, gatewayStatusesListenersEqual) +} + +func gatewayStatusesAddressesEqual(a, b gwv1beta1.GatewayAddress) bool { + return BothNilOrEqual(a.Type, b.Type) && + a.Value == b.Value +} + +func gatewayStatusesListenersEqual(a, b gwv1beta1.ListenerStatus) bool { + return a.AttachedRoutes == b.AttachedRoutes && + a.Name == b.Name && + slices.EqualFunc(a.SupportedKinds, b.SupportedKinds, routeGroupKindsEqual) && + slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) +} + +func routeGroupKindsEqual(a, b gwv1beta1.RouteGroupKind) bool { + return BothNilOrEqual(a.Group, b.Group) && + a.Kind == b.Kind +} + +// this intentionally ignores the last set time so we don't +// always fail a conditional check per-reconciliation. +func conditionsEqual(a, b metav1.Condition) bool { + return a.Type == b.Type && + a.Status == b.Status && + a.Reason == b.Reason && + a.Message == b.Message && + a.ObservedGeneration == b.ObservedGeneration +} + +func EntriesEqual(a, b api.ConfigEntry) bool { + switch aCast := a.(type) { + case *api.APIGatewayConfigEntry: + if bCast, ok := b.(*api.APIGatewayConfigEntry); ok { + return apiGatewaysEqual(aCast, bCast) + } + case *api.HTTPRouteConfigEntry: + if bCast, ok := b.(*api.HTTPRouteConfigEntry); ok { + return httpRoutesEqual(aCast, bCast) + } + case *api.TCPRouteConfigEntry: + if bCast, ok := b.(*api.TCPRouteConfigEntry); ok { + return tcpRoutesEqual(aCast, bCast) + } + case *api.InlineCertificateConfigEntry: + if bCast, ok := b.(*api.InlineCertificateConfigEntry); ok { + return certificatesEqual(aCast, bCast) + } + } + return false +} + +type entryComparator struct { + namespaceA string + partitionA string + namespaceB string + partitionB string +} + +func apiGatewaysEqual(a, b *api.APIGatewayConfigEntry) bool { + if a == nil || b == nil { + return false + } + + return (entryComparator{ + namespaceA: NormalizeEmptyMetadataString(a.Namespace), + partitionA: NormalizeEmptyMetadataString(a.Partition), + namespaceB: NormalizeEmptyMetadataString(b.Namespace), + partitionB: NormalizeEmptyMetadataString(b.Partition), + }).apiGatewaysEqual(*a, *b) +} + +func (e entryComparator) apiGatewaysEqual(a, b api.APIGatewayConfigEntry) bool { + return a.Kind == b.Kind && + a.Name == b.Name && + e.namespaceA == e.namespaceB && + e.partitionA == e.partitionB && + maps.Equal(a.Meta, b.Meta) && + slices.EqualFunc(a.Listeners, b.Listeners, e.apiGatewayListenersEqual) +} + +func (e entryComparator) apiGatewayListenersEqual(a, b api.APIGatewayListener) bool { + return a.Hostname == b.Hostname && + a.Name == b.Name && + a.Port == b.Port && + // normalize the protocol name + strings.EqualFold(a.Protocol, b.Protocol) && + e.apiGatewayListenerTLSConfigurationsEqual(a.TLS, b.TLS) +} + +func (e entryComparator) apiGatewayListenerTLSConfigurationsEqual(a, b api.APIGatewayTLSConfiguration) bool { + return a.MaxVersion == b.MaxVersion && + a.MinVersion == b.MinVersion && + slices.Equal(a.CipherSuites, b.CipherSuites) && + slices.EqualFunc(a.Certificates, b.Certificates, e.resourceReferencesEqual) +} + +func (e entryComparator) resourceReferencesEqual(a, b api.ResourceReference) bool { + return a.Kind == b.Kind && + a.Name == b.Name && + a.SectionName == b.SectionName && + orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) && + orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) +} + +func httpRoutesEqual(a, b *api.HTTPRouteConfigEntry) bool { + if a == nil || b == nil { + return false + } + + return (entryComparator{ + namespaceA: NormalizeEmptyMetadataString(a.Namespace), + partitionA: NormalizeEmptyMetadataString(a.Partition), + namespaceB: NormalizeEmptyMetadataString(b.Namespace), + partitionB: NormalizeEmptyMetadataString(b.Partition), + }).httpRoutesEqual(*a, *b) +} + +func (e entryComparator) httpRoutesEqual(a, b api.HTTPRouteConfigEntry) bool { + return a.Kind == b.Kind && + a.Name == b.Name && + e.namespaceA == e.namespaceB && + e.partitionA == e.partitionB && + maps.Equal(a.Meta, b.Meta) && + slices.Equal(a.Hostnames, b.Hostnames) && + slices.EqualFunc(a.Parents, b.Parents, e.resourceReferencesEqual) && + slices.EqualFunc(a.Rules, b.Rules, e.httpRouteRulesEqual) +} + +func (e entryComparator) httpRouteRulesEqual(a, b api.HTTPRouteRule) bool { + return slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && + bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) && + slices.EqualFunc(a.Matches, b.Matches, e.httpMatchesEqual) && + slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) +} + +func (e entryComparator) httpServicesEqual(a, b api.HTTPService) bool { + return a.Name == b.Name && + a.Weight == b.Weight && + orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) && + orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) && + slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && + bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) +} + +func (e entryComparator) httpMatchesEqual(a, b api.HTTPMatch) bool { + return a.Method == b.Method && + slices.EqualFunc(a.Headers, b.Headers, e.httpHeaderMatchesEqual) && + slices.EqualFunc(a.Query, b.Query, e.httpQueryMatchesEqual) && + e.httpPathMatchesEqual(a.Path, b.Path) +} + +func (e entryComparator) httpPathMatchesEqual(a, b api.HTTPPathMatch) bool { + return a.Match == b.Match && a.Value == b.Value +} + +func (e entryComparator) httpHeaderMatchesEqual(a, b api.HTTPHeaderMatch) bool { + return a.Match == b.Match && a.Name == b.Name && a.Value == b.Value +} + +func (e entryComparator) httpQueryMatchesEqual(a, b api.HTTPQueryMatch) bool { + return a.Match == b.Match && a.Name == b.Name && a.Value == b.Value +} + +func (e entryComparator) httpHeaderFiltersEqual(a, b api.HTTPHeaderFilter) bool { + return maps.Equal(a.Add, b.Add) && + maps.Equal(a.Set, b.Set) && + slices.Equal(a.Remove, b.Remove) +} + +func (e entryComparator) urlRewritesEqual(a, b api.URLRewrite) bool { + return a.Path == b.Path +} + +func tcpRoutesEqual(a, b *api.TCPRouteConfigEntry) bool { + if a == nil || b == nil { + return false + } + + return (entryComparator{ + namespaceA: NormalizeEmptyMetadataString(a.Namespace), + partitionA: NormalizeEmptyMetadataString(a.Partition), + namespaceB: NormalizeEmptyMetadataString(b.Namespace), + partitionB: NormalizeEmptyMetadataString(b.Partition), + }).tcpRoutesEqual(*a, *b) +} + +func (e entryComparator) tcpRoutesEqual(a, b api.TCPRouteConfigEntry) bool { + return a.Kind == b.Kind && + a.Name == b.Name && + e.namespaceA == e.namespaceB && + e.partitionA == e.partitionB && + maps.Equal(a.Meta, b.Meta) && + slices.EqualFunc(a.Parents, b.Parents, e.resourceReferencesEqual) && + slices.EqualFunc(a.Services, b.Services, e.tcpRouteServicesEqual) +} + +func (e entryComparator) tcpRouteServicesEqual(a, b api.TCPService) bool { + return a.Name == b.Name && + orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) && + orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) +} + +func certificatesEqual(a, b *api.InlineCertificateConfigEntry) bool { + if a == nil || b == nil { + return false + } + + return (entryComparator{ + namespaceA: NormalizeEmptyMetadataString(a.Namespace), + partitionA: NormalizeEmptyMetadataString(a.Partition), + namespaceB: NormalizeEmptyMetadataString(b.Namespace), + partitionB: NormalizeEmptyMetadataString(b.Partition), + }).certificatesEqual(*a, *b) +} + +func (e entryComparator) certificatesEqual(a, b api.InlineCertificateConfigEntry) bool { + return a.Kind == b.Kind && + a.Name == b.Name && + e.namespaceA == e.namespaceB && + e.partitionA == e.partitionB && + maps.Equal(a.Meta, b.Meta) && + a.Certificate == b.Certificate && + a.PrivateKey == b.PrivateKey +} + +func bothNilOrEqualFunc[T any](one, two *T, fn func(T, T) bool) bool { + if one == nil && two == nil { + return true + } + if one == nil { + return false + } + if two == nil { + return false + } + return fn(*one, *two) +} + +func orDefault[T ~string](v T, fallback string) string { + if v == "" { + return fallback + } + return string(v) +} diff --git a/control-plane/api-gateway/common/finalizers.go b/control-plane/api-gateway/common/finalizers.go new file mode 100644 index 0000000000..e1fe84bdac --- /dev/null +++ b/control-plane/api-gateway/common/finalizers.go @@ -0,0 +1,60 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +const ( + // GatewayFinalizer is the finalizer we add to any gateway object. + GatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" + + // NamespaceNameLabel represents that label added automatically to namespaces in newer Kubernetes clusters. + NamespaceNameLabel = "kubernetes.io/metadata.name" +) + +var ( + // constants extracted for ease of use. + KindGateway = "Gateway" + KindSecret = "Secret" + KindService = "Service" + BetaGroup = gwv1beta1.GroupVersion.Group +) + +// EnsureFinalizer ensures that our finalizer is set on an object +// returning whether or not it modified the object. +func EnsureFinalizer(object client.Object) bool { + if !object.GetDeletionTimestamp().IsZero() { + return false + } + + finalizers := object.GetFinalizers() + for _, f := range finalizers { + if f == GatewayFinalizer { + return false + } + } + + object.SetFinalizers(append(finalizers, GatewayFinalizer)) + return true +} + +// RemoveFinalizer ensures that our finalizer is absent from an object +// returning whether or not it modified the object. +func RemoveFinalizer(object client.Object) bool { + found := false + filtered := []string{} + for _, f := range object.GetFinalizers() { + if f == GatewayFinalizer { + found = true + continue + } + filtered = append(filtered, f) + } + + object.SetFinalizers(filtered) + return found +} diff --git a/control-plane/api-gateway/helm_config.go b/control-plane/api-gateway/common/helm_config.go similarity index 98% rename from control-plane/api-gateway/helm_config.go rename to control-plane/api-gateway/common/helm_config.go index ef7ab22df3..2a6cc8211b 100644 --- a/control-plane/api-gateway/helm_config.go +++ b/control-plane/api-gateway/common/helm_config.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package apigateway +package common import "time" diff --git a/control-plane/api-gateway/common/helpers.go b/control-plane/api-gateway/common/helpers.go new file mode 100644 index 0000000000..b0eeb46510 --- /dev/null +++ b/control-plane/api-gateway/common/helpers.go @@ -0,0 +1,218 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "github.com/hashicorp/consul/api" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func DerefAll[T any](vs []*T) []T { + e := make([]T, 0, len(vs)) + for _, v := range vs { + e = append(e, *v) + } + return e +} + +func EmptyOrEqual(v, check string) bool { + return v == "" || v == check +} + +func NilOrEqual[T ~string](v *T, check string) bool { + return v == nil || string(*v) == check +} + +func IndexedNamespacedNameWithDefault[T ~string, U ~string, V ~string](t T, u *U, v V) types.NamespacedName { + return types.NamespacedName{ + Namespace: DerefStringOr(u, v), + Name: string(t), + } +} + +func ResourceReferenceWithDefault[T ~string, U ~string, V ~string](kind string, name T, section string, u *U, v V, partition string) api.ResourceReference { + return api.ResourceReference{ + Kind: kind, + Name: string(name), + SectionName: section, + Namespace: DerefStringOr(u, v), + Partition: partition, + } +} + +func DerefStringOr[T ~string, U ~string](v *T, val U) string { + if v == nil { + return string(val) + } + return string(*v) +} + +func DerefLookup[T comparable, U any](v *T, lookup map[T]U) U { + var zero U + if v == nil { + return zero + } + return lookup[*v] +} + +func DerefConvertFunc[T any, U any](v *T, fn func(T) U) U { + var zero U + if v == nil { + return zero + } + return fn(*v) +} + +func DerefEqual[T ~string](v *T, check string) bool { + if v == nil { + return false + } + return string(*v) == check +} + +func DerefIntOr[T ~int | ~int32, U ~int](v *T, val U) int { + if v == nil { + return int(val) + } + return int(*v) +} + +func StringLikeSlice[T ~string](vs []T) []string { + converted := []string{} + for _, v := range vs { + converted = append(converted, string(v)) + } + return converted +} + +func ConvertMapValuesToSlice[T comparable, U any](vs map[T]U) []U { + converted := []U{} + for _, v := range vs { + converted = append(converted, v) + } + return converted +} + +func ConvertSliceFunc[T any, U any](vs []T, fn func(T) U) []U { + converted := []U{} + for _, v := range vs { + converted = append(converted, fn(v)) + } + return converted +} + +func ConvertSliceFuncIf[T any, U any](vs []T, fn func(T) (U, bool)) []U { + converted := []U{} + for _, v := range vs { + if c, ok := fn(v); ok { + converted = append(converted, c) + } + } + return converted +} + +func Flatten[T any](vs [][]T) []T { + flattened := []T{} + for _, v := range vs { + flattened = append(flattened, v...) + } + return flattened +} + +func Filter[T any](vs []T, filterFn func(T) bool) []T { + filtered := []T{} + for _, v := range vs { + if !filterFn(v) { + filtered = append(filtered, v) + } + } + return filtered +} + +func DefaultOrEqual(v, fallback, check string) bool { + if v == "" { + return fallback == check + } + return v == check +} + +// ObjectsToReconcileRequests takes a list of objects and returns a list of +// reconcile Requests. +func ObjectsToReconcileRequests[T metav1.Object](objects []T) []reconcile.Request { + requests := make([]reconcile.Request, 0, len(objects)) + + for _, object := range objects { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: object.GetNamespace(), + Name: object.GetName(), + }, + }) + } + return requests +} + +// ParentRefs takes a list of ParentReference objects and returns a list of NamespacedName objects. +func ParentRefs(group, kind, namespace string, refs []gwv1beta1.ParentReference) []types.NamespacedName { + indexed := make([]types.NamespacedName, 0, len(refs)) + for _, parent := range refs { + if NilOrEqual(parent.Group, group) && NilOrEqual(parent.Kind, kind) { + indexed = append(indexed, IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace)) + } + } + return indexed +} + +// BothNilOrEqual is used to determine if two pointers to comparable +// object are either nil or both point to the same value. +func BothNilOrEqual[T comparable](one, two *T) bool { + if one == nil && two == nil { + return true + } + if one == nil { + return false + } + if two == nil { + return false + } + return *one == *two +} + +// ValueOr checks if a string-like pointer is nil, and if it is, +// returns the given value instead. +func ValueOr[T ~string](v *T, fallback string) string { + if v == nil { + return fallback + } + return string(*v) +} + +// PointerTo is a convenience method for taking a pointer +// of an object without having to declare an intermediate variable. +// It's also useful for making sure we don't accidentally take +// the pointer of a range variable directly. +func PointerTo[T any](v T) *T { + return &v +} + +// ParentsEqual checks for equality between two parent references. +func ParentsEqual(one, two gwv1beta1.ParentReference) bool { + return BothNilOrEqual(one.Group, two.Group) && + BothNilOrEqual(one.Kind, two.Kind) && + BothNilOrEqual(one.SectionName, two.SectionName) && + BothNilOrEqual(one.Port, two.Port) && + one.Name == two.Name +} + +func EntryToReference(entry api.ConfigEntry) api.ResourceReference { + return api.ResourceReference{ + Kind: entry.GetKind(), + Name: entry.GetName(), + Partition: entry.GetPartition(), + Namespace: entry.GetNamespace(), + } +} diff --git a/control-plane/api-gateway/binding/utils_test.go b/control-plane/api-gateway/common/helpers_test.go similarity index 56% rename from control-plane/api-gateway/binding/utils_test.go rename to control-plane/api-gateway/common/helpers_test.go index a7393e6839..62070b434c 100644 --- a/control-plane/api-gateway/binding/utils_test.go +++ b/control-plane/api-gateway/common/helpers_test.go @@ -1,37 +1,17 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package binding +package common import ( "testing" "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -func TestIsNil(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - value interface{} - expected bool - }{ - "nil pointer": { - value: (*string)(nil), - expected: true, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, isNil(tt.value)) - }) - } -} - func TestBothNilOrEqual(t *testing.T) { t.Parallel() @@ -46,28 +26,28 @@ func TestBothNilOrEqual(t *testing.T) { expected: true, }, "second nil": { - first: pointerTo(""), + first: PointerTo(""), second: nil, expected: false, }, "first nil": { first: nil, - second: pointerTo(""), + second: PointerTo(""), expected: false, }, "both equal": { - first: pointerTo(""), - second: pointerTo(""), + first: PointerTo(""), + second: PointerTo(""), expected: true, }, "both not equal": { - first: pointerTo("1"), - second: pointerTo("2"), + first: PointerTo("1"), + second: PointerTo("2"), expected: false, }, } { t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, bothNilOrEqual(tt.first, tt.second)) + require.Equal(t, tt.expected, BothNilOrEqual(tt.first, tt.second)) }) } } @@ -86,13 +66,13 @@ func TestValueOr(t *testing.T) { expected: "test", }, "set value": { - value: pointerTo("value"), + value: PointerTo("value"), or: "test", expected: "value", }, } { t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, valueOr(tt.value, tt.or)) + require.Equal(t, tt.expected, ValueOr(tt.value, tt.or)) }) } } @@ -111,62 +91,18 @@ func TestNilOrEqual(t *testing.T) { expected: true, }, "equal values": { - value: pointerTo("test"), + value: PointerTo("test"), check: "test", expected: true, }, "unequal values": { - value: pointerTo("value"), + value: PointerTo("value"), check: "test", expected: false, }, } { t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, nilOrEqual(tt.value, tt.check)) - }) - } -} - -func TestObjectToMeta(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - object metav1.Object - expected types.NamespacedName - }{ - "gateway": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "test"}}, - expected: types.NamespacedName{Namespace: "test", Name: "test"}, - }, - "secret": { - object: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Namespace: "secret", Name: "secret"}}, - expected: types.NamespacedName{Namespace: "secret", Name: "secret"}, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, objectToMeta(tt.object)) - }) - } -} - -func TestIsDeleted(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - object client.Object - expected bool - }{ - "deleted gateway": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: pointerTo(metav1.Now())}}, - expected: true, - }, - "non-deleted http route": { - object: &gwv1beta1.HTTPRoute{}, - expected: false, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, isDeleted(tt.object)) + require.Equal(t, tt.expected, NilOrEqual(tt.value, tt.check)) }) } } @@ -182,21 +118,21 @@ func TestEnsureFinalizer(t *testing.T) { "gateway no finalizer": { object: &gwv1beta1.Gateway{}, expected: true, - finalizers: []string{gatewayFinalizer}, + finalizers: []string{GatewayFinalizer}, }, "gateway other finalizer": { object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other"}}}, expected: true, - finalizers: []string{"other", gatewayFinalizer}, + finalizers: []string{"other", GatewayFinalizer}, }, "gateway already has finalizer": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{gatewayFinalizer}}}, + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{GatewayFinalizer}}}, expected: false, - finalizers: []string{gatewayFinalizer}, + finalizers: []string{GatewayFinalizer}, }, } { t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, ensureFinalizer(tt.object)) + require.Equal(t, tt.expected, EnsureFinalizer(tt.object)) require.Equal(t, tt.finalizers, tt.object.GetFinalizers()) }) } @@ -221,18 +157,18 @@ func TestRemoveFinalizer(t *testing.T) { finalizers: []string{"other"}, }, "gateway multiple finalizers": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{gatewayFinalizer, gatewayFinalizer}}}, + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{GatewayFinalizer, GatewayFinalizer}}}, expected: true, finalizers: []string{}, }, "gateway mixed finalizers": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other", gatewayFinalizer}}}, + object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other", GatewayFinalizer}}}, expected: true, finalizers: []string{"other"}, }, } { t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, removeFinalizer(tt.object)) + require.Equal(t, tt.expected, RemoveFinalizer(tt.object)) require.Equal(t, tt.finalizers, tt.object.GetFinalizers()) }) } diff --git a/control-plane/api-gateway/labels.go b/control-plane/api-gateway/common/labels.go similarity index 97% rename from control-plane/api-gateway/labels.go rename to control-plane/api-gateway/common/labels.go index 3b4f7c5f1f..3ab7eaf164 100644 --- a/control-plane/api-gateway/labels.go +++ b/control-plane/api-gateway/common/labels.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package apigateway +package common import ( "fmt" diff --git a/control-plane/api-gateway/common/reference.go b/control-plane/api-gateway/common/reference.go new file mode 100644 index 0000000000..78935c11e1 --- /dev/null +++ b/control-plane/api-gateway/common/reference.go @@ -0,0 +1,184 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "sync" + + "github.com/hashicorp/consul/api" +) + +// ReferenceMap is contains a map of config entries stored +// by their normalized resource references (with empty string +// for namespaces and partitions stored as "default"). +type ReferenceMap struct { + data map[api.ResourceReference]api.ConfigEntry + ids map[api.ResourceReference]struct{} + mutex sync.RWMutex +} + +// NewReferenceMap constructs a reference map. +func NewReferenceMap() *ReferenceMap { + return &ReferenceMap{ + data: make(map[api.ResourceReference]api.ConfigEntry), + ids: make(map[api.ResourceReference]struct{}), + } +} + +func (r *ReferenceMap) IDs() []api.ResourceReference { + r.mutex.RLock() + defer r.mutex.RUnlock() + + var ids []api.ResourceReference + for id := range r.ids { + ids = append(ids, id) + } + return ids +} + +// Set adds an entry to the reference map. +func (r *ReferenceMap) Set(ref api.ResourceReference, v api.ConfigEntry) { + r.mutex.Lock() + defer r.mutex.Unlock() + + r.ids[ref] = struct{}{} + r.data[NormalizeMeta(ref)] = v +} + +// Get returns an entry from the reference map. +func (r *ReferenceMap) Get(ref api.ResourceReference) api.ConfigEntry { + r.mutex.RLock() + defer r.mutex.RUnlock() + + v, ok := r.data[NormalizeMeta(ref)] + if !ok { + return nil + } + return v +} + +// Entries returns a list of entries stored in the reference map. +func (r *ReferenceMap) Entries() []api.ConfigEntry { + r.mutex.RLock() + defer r.mutex.RUnlock() + + entries := make([]api.ConfigEntry, 0, len(r.data)) + for _, entry := range r.data { + entries = append(entries, entry) + } + return entries +} + +// Delete deletes an entry stored in the reference map. +func (r *ReferenceMap) Delete(ref api.ResourceReference) { + r.mutex.Lock() + defer r.mutex.Unlock() + + delete(r.ids, ref) + delete(r.data, NormalizeMeta(ref)) +} + +// Diff calculates the difference between the stored entries in two reference maps. +func (r *ReferenceMap) Diff(other *ReferenceMap) []api.ConfigEntry { + r.mutex.RLock() + defer r.mutex.RUnlock() + + other.mutex.RLock() + defer other.mutex.RUnlock() + + diffs := make([]api.ConfigEntry, 0) + + for ref, entry := range other.data { + oldRef := r.Get(ref) + // ref from the new cache doesn't exist in the old one + // this means a resource was added + if oldRef == nil { + diffs = append(diffs, entry) + continue + } + + // the entry in the old cache has an older modify index than the ref + // from the new cache + if oldRef.GetModifyIndex() < entry.GetModifyIndex() { + diffs = append(diffs, entry) + } + } + + // get all deleted entries, these are entries present in the old cache + // that are not present in the new + for ref, entry := range r.data { + if other.Get(ref) == nil { + diffs = append(diffs, entry) + } + } + + return diffs +} + +// ReferenceSet is a set of stored references. +type ReferenceSet struct { + data map[api.ResourceReference]struct{} + ids map[api.ResourceReference]struct{} + + mutex sync.RWMutex +} + +// NewReferenceSet constructs a new reference set. +func NewReferenceSet() *ReferenceSet { + return &ReferenceSet{ + data: make(map[api.ResourceReference]struct{}), + ids: make(map[api.ResourceReference]struct{}), + } +} + +// Mark adds a reference to the reference set. +func (r *ReferenceSet) Mark(ref api.ResourceReference) { + r.mutex.Lock() + defer r.mutex.Unlock() + + r.ids[ref] = struct{}{} + r.data[NormalizeMeta(ref)] = struct{}{} +} + +// Contains checks for the inclusion of a reference in the set. +func (r *ReferenceSet) Contains(ref api.ResourceReference) bool { + r.mutex.RLock() + defer r.mutex.RUnlock() + + _, ok := r.data[NormalizeMeta(ref)] + return ok +} + +// Remove drops a reference from the set. +func (r *ReferenceSet) Remove(ref api.ResourceReference) { + r.mutex.Lock() + defer r.mutex.Unlock() + + delete(r.ids, ref) + delete(r.data, NormalizeMeta(ref)) +} + +func (r *ReferenceSet) IDs() []api.ResourceReference { + r.mutex.RLock() + defer r.mutex.RUnlock() + + var ids []api.ResourceReference + for id := range r.ids { + ids = append(ids, id) + } + return ids +} + +func NormalizeMeta(ref api.ResourceReference) api.ResourceReference { + ref.Namespace = NormalizeEmptyMetadataString(ref.Namespace) + ref.Partition = NormalizeEmptyMetadataString(ref.Partition) + return ref +} + +func NormalizeEmptyMetadataString(metaString string) string { + if metaString == "" { + return "default" + } + return metaString +} diff --git a/control-plane/api-gateway/common/resources.go b/control-plane/api-gateway/common/resources.go new file mode 100644 index 0000000000..8696dcacb7 --- /dev/null +++ b/control-plane/api-gateway/common/resources.go @@ -0,0 +1,574 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + mapset "github.com/deckarep/golang-set" + "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// ConsulUpdateOperation is an operation representing an +// update in Consul. +type ConsulUpdateOperation struct { + // Entry is the ConfigEntry to write to Consul. + Entry api.ConfigEntry + // OnUpdate is an optional callback to fire after running + // the Consul update operation. If specified, then no more + // error handling occurs after the function is called, otherwise + // normal error handling logic applies. + OnUpdate func(err error) +} + +type gvkNamespacedName struct { + gvk string + nsn types.NamespacedName +} + +// KubernetesUpdates holds all update operations (including status) +// that need to be synced to Kubernetes. So long as you're +// modifying the same pointer object passed in to its Add +// function, this de-duplicates any calls to Add, in order +// for us to Add any previously unseen entires, but ignore +// them if they've already been added. +type KubernetesUpdates struct { + operations map[gvkNamespacedName]client.Object +} + +func NewKubernetesUpdates() *KubernetesUpdates { + return &KubernetesUpdates{ + operations: make(map[gvkNamespacedName]client.Object), + } +} + +func (k *KubernetesUpdates) Add(object client.Object) { + k.operations[gvkNamespacedName{ + gvk: object.GetObjectKind().GroupVersionKind().String(), + nsn: client.ObjectKeyFromObject(object), + }] = object +} + +func (k *KubernetesUpdates) Operations() []client.Object { + return ConvertMapValuesToSlice(k.operations) +} + +type ReferenceValidator interface { + GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool + HTTPRouteCanReferenceGateway(httproute gwv1beta1.HTTPRoute, parentRef gwv1beta1.ParentReference) bool + HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool + TCPRouteCanReferenceGateway(tcpRoute gwv1alpha2.TCPRoute, parentRef gwv1beta1.ParentReference) bool + TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool +} + +type certificate struct { + secret corev1.Secret + gateways mapset.Set +} + +type httpRoute struct { + route gwv1beta1.HTTPRoute + gateways mapset.Set +} + +type tcpRoute struct { + route gwv1alpha2.TCPRoute + gateways mapset.Set +} + +type consulHTTPRoute struct { + route api.HTTPRouteConfigEntry + gateways mapset.Set +} + +type consulTCPRoute struct { + route api.TCPRouteConfigEntry + gateways mapset.Set +} + +type resourceSet struct { + httpRoutes mapset.Set + tcpRoutes mapset.Set + certificates mapset.Set + + consulObjects *ReferenceSet +} + +type ResourceMap struct { + translator ResourceTranslator + referenceValidator ReferenceValidator + logger logr.Logger + + services map[types.NamespacedName]api.ResourceReference + meshServices map[types.NamespacedName]api.ResourceReference + certificates mapset.Set + + // this acts a a secondary store of what has not yet + // been processed for the sake of garbage collection. + processedCertificates mapset.Set + certificateGateways map[api.ResourceReference]*certificate + tcpRouteGateways map[api.ResourceReference]*tcpRoute + httpRouteGateways map[api.ResourceReference]*httpRoute + gatewayResources map[api.ResourceReference]*resourceSet + + // consul resources for a gateway + consulTCPRoutes map[api.ResourceReference]*consulTCPRoute + consulHTTPRoutes map[api.ResourceReference]*consulHTTPRoute + consulInlineCertificates map[api.ResourceReference]mapset.Set + + // mutations + consulMutations []*ConsulUpdateOperation +} + +func NewResourceMap(translator ResourceTranslator, validator ReferenceValidator, logger logr.Logger) *ResourceMap { + return &ResourceMap{ + translator: translator, + referenceValidator: validator, + logger: logger, + processedCertificates: mapset.NewSet(), + services: make(map[types.NamespacedName]api.ResourceReference), + meshServices: make(map[types.NamespacedName]api.ResourceReference), + certificates: mapset.NewSet(), + consulTCPRoutes: make(map[api.ResourceReference]*consulTCPRoute), + consulHTTPRoutes: make(map[api.ResourceReference]*consulHTTPRoute), + consulInlineCertificates: make(map[api.ResourceReference]mapset.Set), + certificateGateways: make(map[api.ResourceReference]*certificate), + tcpRouteGateways: make(map[api.ResourceReference]*tcpRoute), + httpRouteGateways: make(map[api.ResourceReference]*httpRoute), + gatewayResources: make(map[api.ResourceReference]*resourceSet), + } +} + +func (s *ResourceMap) AddService(id types.NamespacedName, name string) { + // this needs to be not-normalized since it gets written straight + // to Consul's configuration, including in non-enterprise builds. + s.services[id] = api.ResourceReference{ + Name: name, + Namespace: s.translator.Namespace(id.Namespace), + Partition: s.translator.ConsulPartition, + } +} + +func (s *ResourceMap) Service(id types.NamespacedName) api.ResourceReference { + return s.services[id] +} + +func (s *ResourceMap) HasService(id types.NamespacedName) bool { + _, ok := s.services[id] + return ok +} + +func (s *ResourceMap) AddMeshService(service v1alpha1.MeshService) { + // this needs to be not-normalized since it gets written straight + // to Consul's configuration, including in non-enterprise builds. + key := client.ObjectKeyFromObject(&service) + s.meshServices[key] = api.ResourceReference{ + Name: service.Spec.Name, + Namespace: s.translator.Namespace(service.Namespace), + Partition: s.translator.ConsulPartition, + } +} + +func (s *ResourceMap) MeshService(id types.NamespacedName) api.ResourceReference { + return s.meshServices[id] +} + +func (s *ResourceMap) HasMeshService(id types.NamespacedName) bool { + _, ok := s.meshServices[id] + return ok +} + +func (s *ResourceMap) Certificate(key types.NamespacedName) *corev1.Secret { + if !s.certificates.Contains(key) { + return nil + } + consulKey := NormalizeMeta(s.toConsulReference(api.InlineCertificate, key)) + if secret, ok := s.certificateGateways[consulKey]; ok { + return &secret.secret + } + return nil +} + +func (s *ResourceMap) ReferenceCountCertificate(secret corev1.Secret) { + key := client.ObjectKeyFromObject(&secret) + s.certificates.Add(key) + consulKey := NormalizeMeta(s.toConsulReference(api.InlineCertificate, key)) + if _, ok := s.certificateGateways[consulKey]; !ok { + s.certificateGateways[consulKey] = &certificate{ + secret: secret, + gateways: mapset.NewSet(), + } + } +} + +func (s *ResourceMap) ReferenceCountGateway(gateway gwv1beta1.Gateway) { + key := client.ObjectKeyFromObject(&gateway) + consulKey := NormalizeMeta(s.toConsulReference(api.APIGateway, key)) + + set := &resourceSet{ + httpRoutes: mapset.NewSet(), + tcpRoutes: mapset.NewSet(), + certificates: mapset.NewSet(), + consulObjects: NewReferenceSet(), + } + + for _, listener := range gateway.Spec.Listeners { + if listener.TLS == nil || (listener.TLS.Mode != nil && *listener.TLS.Mode != gwv1beta1.TLSModeTerminate) { + continue + } + for _, cert := range listener.TLS.CertificateRefs { + if NilOrEqual(cert.Group, "") && NilOrEqual(cert.Kind, "Secret") { + certificateKey := IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace) + + set.certificates.Add(certificateKey) + + consulCertificateKey := s.toConsulReference(api.InlineCertificate, certificateKey) + certificate, ok := s.certificateGateways[NormalizeMeta(consulCertificateKey)] + if ok { + certificate.gateways.Add(key) + set.consulObjects.Mark(consulCertificateKey) + } + } + } + } + + s.gatewayResources[consulKey] = set +} + +func (s *ResourceMap) ResourcesToGC(key types.NamespacedName) []api.ResourceReference { + consulKey := NormalizeMeta(s.toConsulReference(api.APIGateway, key)) + + resources, ok := s.gatewayResources[consulKey] + if !ok { + return nil + } + + var toGC []api.ResourceReference + + for _, id := range resources.consulObjects.IDs() { + // if any of these objects exist in the below maps + // it means we haven't "popped" it to be created + switch id.Kind { + case api.HTTPRoute: + if route, ok := s.consulHTTPRoutes[NormalizeMeta(id)]; ok && route.gateways.Cardinality() <= 1 { + // we only have a single reference, which will be this gateway, so drop + // the route altogether + toGC = append(toGC, id) + } + case api.TCPRoute: + if route, ok := s.consulTCPRoutes[NormalizeMeta(id)]; ok && route.gateways.Cardinality() <= 1 { + // we only have a single reference, which will be this gateway, so drop + // the route altogether + toGC = append(toGC, id) + } + case api.InlineCertificate: + if s.processedCertificates.Contains(id) { + continue + } + if route, ok := s.certificateGateways[NormalizeMeta(id)]; ok && route.gateways.Cardinality() <= 1 { + // we only have a single reference, which will be this gateway, so drop + // the route altogether + toGC = append(toGC, id) + } + } + } + + return toGC +} + +func (s *ResourceMap) ReferenceCountConsulHTTPRoute(route api.HTTPRouteConfigEntry) { + key := s.objectReference(&route) + + set := &consulHTTPRoute{ + route: route, + gateways: mapset.NewSet(), + } + + for gatewayKey := range s.consulGatewaysForRoute(route.Namespace, route.Parents).Iter() { + if gateway, ok := s.gatewayResources[gatewayKey.(api.ResourceReference)]; ok { + gateway.consulObjects.Mark(key) + } + + set.gateways.Add(gatewayKey) + } + + s.consulHTTPRoutes[NormalizeMeta(key)] = set +} + +func (s *ResourceMap) ReferenceCountConsulTCPRoute(route api.TCPRouteConfigEntry) { + key := s.objectReference(&route) + + set := &consulTCPRoute{ + route: route, + gateways: mapset.NewSet(), + } + + for gatewayKey := range s.consulGatewaysForRoute(route.Namespace, route.Parents).Iter() { + if gateway, ok := s.gatewayResources[gatewayKey.(api.ResourceReference)]; ok { + gateway.consulObjects.Mark(key) + } + + set.gateways.Add(gatewayKey) + } + + s.consulTCPRoutes[NormalizeMeta(key)] = set +} + +func (s *ResourceMap) consulGatewaysForRoute(namespace string, refs []api.ResourceReference) mapset.Set { + gateways := mapset.NewSet() + + for _, parent := range refs { + if EmptyOrEqual(parent.Kind, api.APIGateway) { + key := s.sectionlessParentReference(api.APIGateway, namespace, parent) + gateways.Add(key) + } + } + + return gateways +} + +func (s *ResourceMap) ReferenceCountHTTPRoute(route gwv1beta1.HTTPRoute) { + key := client.ObjectKeyFromObject(&route) + consulKey := NormalizeMeta(s.toConsulReference(api.HTTPRoute, key)) + + set := &httpRoute{ + route: route, + gateways: mapset.NewSet(), + } + + for gatewayKey := range s.gatewaysForRoute(route.Namespace, route.Spec.ParentRefs).Iter() { + set.gateways.Add(gatewayKey.(api.ResourceReference)) + + gateway := s.gatewayResources[gatewayKey.(api.ResourceReference)] + gateway.httpRoutes.Add(consulKey) + } + + s.httpRouteGateways[consulKey] = set +} + +func (s *ResourceMap) ReferenceCountTCPRoute(route gwv1alpha2.TCPRoute) { + key := client.ObjectKeyFromObject(&route) + consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) + + set := &tcpRoute{ + route: route, + gateways: mapset.NewSet(), + } + + for gatewayKey := range s.gatewaysForRoute(route.Namespace, route.Spec.ParentRefs).Iter() { + set.gateways.Add(gatewayKey.(api.ResourceReference)) + + gateway := s.gatewayResources[gatewayKey.(api.ResourceReference)] + gateway.tcpRoutes.Add(consulKey) + } + + s.tcpRouteGateways[consulKey] = set +} + +func (s *ResourceMap) gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference) mapset.Set { + gateways := mapset.NewSet() + + for _, parent := range refs { + if NilOrEqual(parent.Group, gwv1beta1.GroupVersion.Group) && NilOrEqual(parent.Kind, "Gateway") { + key := IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace) + consulKey := NormalizeMeta(s.toConsulReference(api.APIGateway, key)) + + if _, ok := s.gatewayResources[consulKey]; ok { + gateways.Add(consulKey) + } + } + } + + return gateways +} + +func (s *ResourceMap) TranslateAndMutateHTTPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(old *api.HTTPRouteConfigEntry, new api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { + consulKey := NormalizeMeta(s.toConsulReference(api.HTTPRoute, key)) + + route, ok := s.httpRouteGateways[consulKey] + if !ok { + return + } + + translated := s.translator.ToHTTPRoute(route.route, s) + + consulRoute, ok := s.consulHTTPRoutes[consulKey] + if ok { + // remove from the consulHTTPRoutes map since we don't want to + // GC it in the end + delete(s.consulHTTPRoutes, consulKey) + mutated := mutateFn(&consulRoute.route, *translated) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) + return + } + mutated := mutateFn(nil, *translated) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) +} + +func (s *ResourceMap) MutateHTTPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { + consulKey := NormalizeMeta(s.toConsulReference(api.HTTPRoute, key)) + + consulRoute, ok := s.consulHTTPRoutes[consulKey] + if ok { + // remove from the consulHTTPRoutes map since we don't want to + // GC it in the end + delete(s.consulHTTPRoutes, consulKey) + mutated := mutateFn(consulRoute.route) + // add it to the mutation set + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) + } +} + +func (s *ResourceMap) CanGCHTTPRouteOnUnbind(id api.ResourceReference) bool { + if set := s.httpRouteGateways[NormalizeMeta(id)]; set != nil { + return set.gateways.Cardinality() <= 1 + } + return true +} + +func (s *ResourceMap) TranslateAndMutateTCPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(*api.TCPRouteConfigEntry, api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { + consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) + + route, ok := s.tcpRouteGateways[consulKey] + if !ok { + + return + } + + translated := s.translator.ToTCPRoute(route.route, s) + + consulRoute, ok := s.consulTCPRoutes[consulKey] + if ok { + // remove from the consulTCPRoutes map since we don't want to + // GC it in the end + mutated := mutateFn(&consulRoute.route, *translated) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) + return + } + mutated := mutateFn(nil, *translated) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) +} + +func (s *ResourceMap) MutateTCPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { + consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) + + consulRoute, ok := s.consulTCPRoutes[consulKey] + if ok { + // remove from the consulTCPRoutes map since we don't want to + // GC it in the end + delete(s.consulTCPRoutes, consulKey) + mutated := mutateFn(consulRoute.route) + // add it to the mutation set + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) + } +} + +func (s *ResourceMap) CanGCTCPRouteOnUnbind(id api.ResourceReference) bool { + if set := s.tcpRouteGateways[NormalizeMeta(id)]; set != nil { + return set.gateways.Cardinality() <= 1 + } + return true +} + +func (s *ResourceMap) TranslateInlineCertificate(key types.NamespacedName) error { + consulKey := s.toConsulReference(api.InlineCertificate, key) + + certificate, ok := s.certificateGateways[NormalizeMeta(consulKey)] + if !ok { + return nil + } + + consulCertificate, err := s.translator.ToInlineCertificate(certificate.secret) + if err != nil { + return err + } + + // add to the processed set so we don't GC it. + s.processedCertificates.Add(consulKey) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: consulCertificate, + // just swallow the error and log it since we can't propagate status back on a certificate. + OnUpdate: func(error) { + if err != nil { + s.logger.Error(err, "error syncing certificate to Consul") + } + }, + }) + + return nil +} + +func (s *ResourceMap) Mutations() []*ConsulUpdateOperation { + return s.consulMutations +} + +func (s *ResourceMap) objectReference(o api.ConfigEntry) api.ResourceReference { + return api.ResourceReference{ + Kind: o.GetKind(), + Name: o.GetName(), + Namespace: o.GetNamespace(), + Partition: s.translator.ConsulPartition, + } +} + +func (s *ResourceMap) sectionlessParentReference(kind, namespace string, parent api.ResourceReference) api.ResourceReference { + return NormalizeMeta(api.ResourceReference{ + Kind: kind, + Name: parent.Name, + Namespace: orDefault(parent.Namespace, namespace), + Partition: s.translator.ConsulPartition, + }) +} + +func (s *ResourceMap) toConsulReference(kind string, key types.NamespacedName) api.ResourceReference { + return api.ResourceReference{ + Kind: kind, + Name: key.Name, + Namespace: s.translator.Namespace(key.Namespace), + Partition: s.translator.ConsulPartition, + } +} + +func (s *ResourceMap) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, ref gwv1beta1.SecretObjectReference) bool { + return s.referenceValidator.GatewayCanReferenceSecret(gateway, ref) +} + +func (s *ResourceMap) HTTPRouteCanReferenceBackend(route gwv1beta1.HTTPRoute, ref gwv1beta1.BackendRef) bool { + return s.referenceValidator.HTTPRouteCanReferenceBackend(route, ref) +} + +func (s *ResourceMap) HTTPRouteCanReferenceGateway(route gwv1beta1.HTTPRoute, ref gwv1beta1.ParentReference) bool { + return s.referenceValidator.HTTPRouteCanReferenceGateway(route, ref) +} + +func (s *ResourceMap) TCPRouteCanReferenceBackend(route gwv1alpha2.TCPRoute, ref gwv1beta1.BackendRef) bool { + return s.referenceValidator.TCPRouteCanReferenceBackend(route, ref) +} + +func (s *ResourceMap) TCPRouteCanReferenceGateway(route gwv1alpha2.TCPRoute, ref gwv1beta1.ParentReference) bool { + return s.referenceValidator.TCPRouteCanReferenceGateway(route, ref) +} diff --git a/control-plane/api-gateway/common/secrets.go b/control-plane/api-gateway/common/secrets.go new file mode 100644 index 0000000000..f7e6064d9f --- /dev/null +++ b/control-plane/api-gateway/common/secrets.go @@ -0,0 +1,68 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + + "github.com/miekg/dns" + corev1 "k8s.io/api/core/v1" +) + +func ParseCertificateData(secret corev1.Secret) (cert string, privateKey string, err error) { + decodedPrivateKey := secret.Data[corev1.TLSPrivateKeyKey] + decodedCertificate := secret.Data[corev1.TLSCertKey] + + privateKeyBlock, _ := pem.Decode(decodedPrivateKey) + if privateKeyBlock == nil { + return "", "", errors.New("failed to parse private key PEM") + } + + certificateBlock, _ := pem.Decode(decodedCertificate) + if certificateBlock == nil { + return "", "", errors.New("failed to parse certificate PEM") + } + + // make sure we have a valid x509 certificate + certificate, err := x509.ParseCertificate(certificateBlock.Bytes) + if err != nil { + return "", "", err + } + + // validate that the cert was generated with the given private key + _, err = tls.X509KeyPair(decodedCertificate, decodedPrivateKey) + if err != nil { + return "", "", err + } + + // validate that each host referenced in the CN, DNSSans, and IPSans + // are valid hostnames + if err := validateCertificateHosts(certificate); err != nil { + return "", "", err + } + + return string(decodedCertificate), string(decodedPrivateKey), nil +} + +func validateCertificateHosts(certificate *x509.Certificate) error { + hosts := []string{certificate.Subject.CommonName} + + hosts = append(hosts, certificate.DNSNames...) + + for _, ip := range certificate.IPAddresses { + hosts = append(hosts, ip.String()) + } + + for _, host := range hosts { + if _, ok := dns.IsDomainName(host); !ok { + return fmt.Errorf("host %q must be a valid DNS hostname", host) + } + } + + return nil +} diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go new file mode 100644 index 0000000000..5e577470d6 --- /dev/null +++ b/control-plane/api-gateway/common/translation.go @@ -0,0 +1,363 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "strings" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// ResourceTranslator handles translating K8s resources into Consul config entries. +type ResourceTranslator struct { + EnableConsulNamespaces bool + ConsulDestNamespace string + EnableK8sMirroring bool + MirroringPrefix string + ConsulPartition string +} + +func (t ResourceTranslator) NonNormalizedConfigEntryReference(kind string, id types.NamespacedName) api.ResourceReference { + return api.ResourceReference{ + Kind: kind, + Name: id.Name, + Namespace: t.Namespace(id.Namespace), + Partition: t.ConsulPartition, + } +} + +func (t ResourceTranslator) ConfigEntryReference(kind string, id types.NamespacedName) api.ResourceReference { + return NormalizeMeta(t.NonNormalizedConfigEntryReference(kind, id)) +} + +func (t ResourceTranslator) NormalizedResourceReference(kind, namespace string, ref api.ResourceReference) api.ResourceReference { + return NormalizeMeta(api.ResourceReference{ + Kind: kind, + Name: ref.Name, + SectionName: ref.SectionName, + Namespace: t.Namespace(namespace), + Partition: t.ConsulPartition, + }) +} + +func (t ResourceTranslator) Namespace(namespace string) string { + return namespaces.ConsulNamespace(namespace, t.EnableK8sMirroring, t.ConsulDestNamespace, t.EnableK8sMirroring, t.MirroringPrefix) +} + +// ToAPIGateway translates a kuberenetes API gateway into a Consul APIGateway Config Entry. +func (t ResourceTranslator) ToAPIGateway(gateway gwv1beta1.Gateway, resources *ResourceMap) *api.APIGatewayConfigEntry { + namespace := t.Namespace(gateway.Namespace) + + listeners := ConvertSliceFuncIf(gateway.Spec.Listeners, func(listener gwv1beta1.Listener) (api.APIGatewayListener, bool) { + return t.toAPIGatewayListener(gateway, listener, resources) + }) + + return &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: gateway.Name, + Namespace: namespace, + Partition: t.ConsulPartition, + Meta: map[string]string{ + constants.MetaKeyKubeNS: gateway.Namespace, + constants.MetaKeyKubeName: gateway.Name, + }, + Listeners: listeners, + } +} + +var listenerProtocolMap = map[string]string{ + "https": "http", + "http": "http", + "tcp": "tcp", +} + +func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, listener gwv1beta1.Listener, resources *ResourceMap) (api.APIGatewayListener, bool) { + namespace := gateway.Namespace + + var certificates []api.ResourceReference + + if listener.TLS != nil { + for _, ref := range listener.TLS.CertificateRefs { + if !resources.GatewayCanReferenceSecret(gateway, ref) { + return api.APIGatewayListener{}, false + } + + if !NilOrEqual(ref.Group, "") || !NilOrEqual(ref.Kind, "Secret") { + // only translate the valid types we support + continue + } + + ref := IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, namespace) + if resources.Certificate(ref) != nil { + certificates = append(certificates, t.NonNormalizedConfigEntryReference(api.InlineCertificate, ref)) + } + } + } + + return api.APIGatewayListener{ + Name: string(listener.Name), + Hostname: DerefStringOr(listener.Hostname, ""), + Port: int(listener.Port), + Protocol: listenerProtocolMap[strings.ToLower(string(listener.Protocol))], + TLS: api.APIGatewayTLSConfiguration{ + Certificates: certificates, + }, + }, true +} + +func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *ResourceMap) *api.HTTPRouteConfigEntry { + namespace := t.Namespace(route.Namespace) + + // we don't translate parent refs + + hostnames := StringLikeSlice(route.Spec.Hostnames) + rules := ConvertSliceFuncIf(route.Spec.Rules, func(rule gwv1beta1.HTTPRouteRule) (api.HTTPRouteRule, bool) { + return t.translateHTTPRouteRule(route, rule, resources) + }) + + return &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: route.Name, + Namespace: namespace, + Partition: t.ConsulPartition, + Meta: map[string]string{ + constants.MetaKeyKubeNS: route.Namespace, + constants.MetaKeyKubeName: route.Name, + }, + Hostnames: hostnames, + Rules: rules, + } +} + +func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, rule gwv1beta1.HTTPRouteRule, resources *ResourceMap) (api.HTTPRouteRule, bool) { + services := ConvertSliceFuncIf(rule.BackendRefs, func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) { + return t.translateHTTPBackendRef(route, ref, resources) + }) + + if len(services) == 0 { + return api.HTTPRouteRule{}, false + } + + matches := ConvertSliceFunc(rule.Matches, t.translateHTTPMatch) + filters := t.translateHTTPFilters(rule.Filters) + + return api.HTTPRouteRule{ + Services: services, + Matches: matches, + Filters: filters, + }, true +} + +func (t ResourceTranslator) translateHTTPBackendRef(route gwv1beta1.HTTPRoute, ref gwv1beta1.HTTPBackendRef, resources *ResourceMap) (api.HTTPService, bool) { + id := types.NamespacedName{ + Name: string(ref.Name), + Namespace: DerefStringOr(ref.Namespace, route.Namespace), + } + + isServiceRef := NilOrEqual(ref.Group, "") && NilOrEqual(ref.Kind, "Service") + + if isServiceRef && resources.HasService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { + filters := t.translateHTTPFilters(ref.Filters) + service := resources.Service(id) + + return api.HTTPService{ + Name: service.Name, + Namespace: service.Namespace, + Partition: t.ConsulPartition, + Filters: filters, + Weight: DerefIntOr(ref.Weight, 1), + }, true + } + + isMeshServiceRef := DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) + if isMeshServiceRef && resources.HasMeshService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { + filters := t.translateHTTPFilters(ref.Filters) + service := resources.MeshService(id) + + return api.HTTPService{ + Name: service.Name, + Namespace: service.Namespace, + Partition: t.ConsulPartition, + Filters: filters, + Weight: DerefIntOr(ref.Weight, 1), + }, true + } + + return api.HTTPService{}, false +} + +var headerMatchTypeTranslation = map[gwv1beta1.HeaderMatchType]api.HTTPHeaderMatchType{ + gwv1beta1.HeaderMatchExact: api.HTTPHeaderMatchExact, + gwv1beta1.HeaderMatchRegularExpression: api.HTTPHeaderMatchRegularExpression, +} + +var headerPathMatchTypeTranslation = map[gwv1beta1.PathMatchType]api.HTTPPathMatchType{ + gwv1beta1.PathMatchExact: api.HTTPPathMatchExact, + gwv1beta1.PathMatchPathPrefix: api.HTTPPathMatchPrefix, + gwv1beta1.PathMatchRegularExpression: api.HTTPPathMatchRegularExpression, +} + +var queryMatchTypeTranslation = map[gwv1beta1.QueryParamMatchType]api.HTTPQueryMatchType{ + gwv1beta1.QueryParamMatchExact: api.HTTPQueryMatchExact, + gwv1beta1.QueryParamMatchRegularExpression: api.HTTPQueryMatchRegularExpression, +} + +func (t ResourceTranslator) translateHTTPMatch(match gwv1beta1.HTTPRouteMatch) api.HTTPMatch { + headers := ConvertSliceFunc(match.Headers, t.translateHTTPHeaderMatch) + queries := ConvertSliceFunc(match.QueryParams, t.translateHTTPQueryMatch) + + return api.HTTPMatch{ + Headers: headers, + Query: queries, + Path: DerefConvertFunc(match.Path, t.translateHTTPPathMatch), + Method: api.HTTPMatchMethod(DerefStringOr(match.Method, "")), + } +} + +func (t ResourceTranslator) translateHTTPPathMatch(match gwv1beta1.HTTPPathMatch) api.HTTPPathMatch { + return api.HTTPPathMatch{ + Match: DerefLookup(match.Type, headerPathMatchTypeTranslation), + Value: DerefStringOr(match.Value, ""), + } +} + +func (t ResourceTranslator) translateHTTPHeaderMatch(match gwv1beta1.HTTPHeaderMatch) api.HTTPHeaderMatch { + return api.HTTPHeaderMatch{ + Name: string(match.Name), + Value: match.Value, + Match: DerefLookup(match.Type, headerMatchTypeTranslation), + } +} + +func (t ResourceTranslator) translateHTTPQueryMatch(match gwv1beta1.HTTPQueryParamMatch) api.HTTPQueryMatch { + return api.HTTPQueryMatch{ + Name: string(match.Name), + Value: match.Value, + Match: DerefLookup(match.Type, queryMatchTypeTranslation), + } +} + +func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter) api.HTTPFilters { + var urlRewrite *api.URLRewrite + consulFilter := api.HTTPHeaderFilter{ + Add: make(map[string]string), + Set: make(map[string]string), + } + + for _, filter := range filters { + consulFilter.Remove = append(consulFilter.Remove, filter.RequestHeaderModifier.Remove...) + + for _, toAdd := range filter.RequestHeaderModifier.Add { + consulFilter.Add[string(toAdd.Name)] = toAdd.Value + } + + for _, toSet := range filter.RequestHeaderModifier.Set { + consulFilter.Set[string(toSet.Name)] = toSet.Value + } + + // we drop any path rewrites that are not prefix matches as we don't support those + if filter.URLRewrite != nil && + filter.URLRewrite.Path != nil && + filter.URLRewrite.Path.Type == gwv1beta1.PrefixMatchHTTPPathModifier { + urlRewrite = &api.URLRewrite{Path: DerefStringOr(filter.URLRewrite.Path.ReplacePrefixMatch, "")} + } + } + return api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{consulFilter}, + URLRewrite: urlRewrite, + } +} + +func (t ResourceTranslator) ToTCPRoute(route gwv1alpha2.TCPRoute, resources *ResourceMap) *api.TCPRouteConfigEntry { + namespace := t.Namespace(route.Namespace) + + // we don't translate parent refs + + backendRefs := ConvertSliceFunc(route.Spec.Rules, func(rule gwv1alpha2.TCPRouteRule) []gwv1beta1.BackendRef { return rule.BackendRefs }) + flattenedRefs := Flatten(backendRefs) + services := ConvertSliceFuncIf(flattenedRefs, func(ref gwv1beta1.BackendRef) (api.TCPService, bool) { + return t.translateTCPRouteRule(route, ref, resources) + }) + + return &api.TCPRouteConfigEntry{ + Kind: api.TCPRoute, + Name: route.Name, + Namespace: namespace, + Partition: t.ConsulPartition, + Meta: map[string]string{ + constants.MetaKeyKubeNS: route.Namespace, + constants.MetaKeyKubeName: route.Name, + }, + Services: services, + } +} + +func (t ResourceTranslator) translateTCPRouteRule(route gwv1alpha2.TCPRoute, ref gwv1beta1.BackendRef, resources *ResourceMap) (api.TCPService, bool) { + // we ignore weight for now + + id := types.NamespacedName{ + Name: string(ref.Name), + Namespace: DerefStringOr(ref.Namespace, route.Namespace), + } + + isServiceRef := NilOrEqual(ref.Group, "") && NilOrEqual(ref.Kind, "Service") + if isServiceRef && resources.HasService(id) && resources.TCPRouteCanReferenceBackend(route, ref) { + service := resources.Service(id) + + return api.TCPService{ + Name: service.Name, + Namespace: service.Namespace, + }, true + } + + isMeshServiceRef := DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) + if isMeshServiceRef && resources.HasMeshService(id) && resources.TCPRouteCanReferenceBackend(route, ref) { + service := resources.MeshService(id) + + return api.TCPService{ + Name: service.Name, + Namespace: service.Namespace, + }, true + } + + return api.TCPService{}, false +} + +func (t ResourceTranslator) ToInlineCertificate(secret corev1.Secret) (*api.InlineCertificateConfigEntry, error) { + certificate, privateKey, err := ParseCertificateData(secret) + if err != nil { + return nil, err + } + + namespace := t.Namespace(secret.Namespace) + + return &api.InlineCertificateConfigEntry{ + Kind: api.InlineCertificate, + Name: secret.Name, + Namespace: namespace, + Partition: t.ConsulPartition, + Certificate: strings.TrimSpace(certificate), + PrivateKey: strings.TrimSpace(privateKey), + Meta: map[string]string{ + constants.MetaKeyKubeNS: secret.Namespace, + constants.MetaKeyKubeName: secret.Name, + }, + }, nil +} + +func EntryToNamespacedName(entry api.ConfigEntry) types.NamespacedName { + meta := entry.GetMeta() + + return types.NamespacedName{ + Namespace: meta[constants.MetaKeyKubeNS], + Name: meta[constants.MetaKeyKubeName], + } +} diff --git a/control-plane/api-gateway/translation/config_entry_translation_test.go b/control-plane/api-gateway/common/translation_test.go similarity index 51% rename from control-plane/api-gateway/translation/config_entry_translation_test.go rename to control-plane/api-gateway/common/translation_test.go index 39abe4b613..029e4affa7 100644 --- a/control-plane/api-gateway/translation/config_entry_translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -1,24 +1,51 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package translation +package common import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" "testing" "time" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + logrtest "github.com/go-logr/logr/testing" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul/api" - capi "github.com/hashicorp/consul/api" ) -func TestTranslator_GatewayToAPIGateway(t *testing.T) { +type fakeReferenceValidator struct{} + +func (v fakeReferenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool { + return true +} +func (v fakeReferenceValidator) HTTPRouteCanReferenceGateway(httproute gwv1beta1.HTTPRoute, parentRef gwv1beta1.ParentReference) bool { + return true +} +func (v fakeReferenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { + return true +} +func (v fakeReferenceValidator) TCPRouteCanReferenceGateway(tcpRoute gwv1alpha2.TCPRoute, parentRef gwv1beta1.ParentReference) bool { + return true +} +func (v fakeReferenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { + return true +} + +func TestTranslator_ToAPIGateway(t *testing.T) { t.Parallel() k8sObjectName := "my-k8s-gw" k8sNamespace := "my-k8s-namespace" @@ -30,12 +57,13 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { listenerOneName := "listener-one" listenerOneHostname := "*.consul.io" listenerOnePort := 3366 - listenerOneProtocol := "https" + listenerOneProtocol := "http" // listener one tls config listenerOneCertName := "one-cert" - listenerOneCertK8sNamespace := "one-cert-k8s-ns" - listenerOneCertConsulNamespace := "one-cert-consul-ns" + listenerOneCertK8sNamespace := "one-cert-ns" + listenerOneCertConsulNamespace := "one-cert-ns" + listenerOneCert := generateTestCertificate(t, "one-cert-ns", "one-cert") // listener one status listenerOneLastTransmissionTime := time.Now() @@ -44,12 +72,13 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { listenerTwoName := "listener-two" listenerTwoHostname := "*.consul.io" listenerTwoPort := 5432 - listenerTwoProtocol := "https" + listenerTwoProtocol := "http" // listener one tls config listenerTwoCertName := "two-cert" - listenerTwoCertK8sNamespace := "two-cert-k8s-ns" - listenerTwoCertConsulNamespace := "two-cert-consul-ns" + listenerTwoCertK8sNamespace := "two-cert-ns" + listenerTwoCertConsulNamespace := "two-cert-ns" + listenerTwoCert := generateTestCertificate(t, "two-cert-ns", "two-cert") // listener two status listenerTwoLastTransmissionTime := time.Now() @@ -59,23 +88,13 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { expectedGWName string listenerOneK8sCertRefs []gwv1beta1.SecretObjectReference }{ - "when gw name is not overriden by annotations": { + "gw name": { annotations: make(map[string]string), expectedGWName: k8sObjectName, listenerOneK8sCertRefs: []gwv1beta1.SecretObjectReference{ { Name: gwv1beta1.ObjectName(listenerOneCertName), - Namespace: ptrTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), - }, - }, - }, - "when gw name is overriden by annotations": { - annotations: map[string]string{AnnotationGateway: "my-new-gw-name"}, - expectedGWName: "my-new-gw-name", - listenerOneK8sCertRefs: []gwv1beta1.SecretObjectReference{ - { - Name: gwv1beta1.ObjectName(listenerOneCertName), - Namespace: ptrTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), + Namespace: PointerTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), }, }, }, @@ -85,11 +104,11 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { listenerOneK8sCertRefs: []gwv1beta1.SecretObjectReference{ { Name: gwv1beta1.ObjectName(listenerOneCertName), - Namespace: ptrTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), + Namespace: PointerTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), }, { Name: gwv1beta1.ObjectName("cert that won't exist in the translated type"), - Namespace: ptrTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), + Namespace: PointerTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), }, }, }, @@ -113,7 +132,7 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { Listeners: []gwv1beta1.Listener{ { Name: gwv1beta1.SectionName(listenerOneName), - Hostname: ptrTo(gwv1beta1.Hostname(listenerOneHostname)), + Hostname: PointerTo(gwv1beta1.Hostname(listenerOneHostname)), Port: gwv1beta1.PortNumber(listenerOnePort), Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), TLS: &gwv1beta1.GatewayTLSConfig{ @@ -122,14 +141,14 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { }, { Name: gwv1beta1.SectionName(listenerTwoName), - Hostname: ptrTo(gwv1beta1.Hostname(listenerTwoHostname)), + Hostname: PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), Port: gwv1beta1.PortNumber(listenerTwoPort), Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), TLS: &gwv1beta1.GatewayTLSConfig{ CertificateRefs: []gwv1beta1.SecretObjectReference{ { Name: gwv1beta1.ObjectName(listenerTwoCertName), - Namespace: ptrTo(gwv1beta1.Namespace(listenerTwoCertK8sNamespace)), + Namespace: PointerTo(gwv1beta1.Namespace(listenerTwoCertK8sNamespace)), }, }, }, @@ -178,25 +197,23 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { }, } - expectedConfigEntry := capi.APIGatewayConfigEntry{ - Kind: capi.APIGateway, + expectedConfigEntry := &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, Name: tc.expectedGWName, Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: k8sNamespace, - metaKeyKubeServiceName: k8sObjectName, - metaKeyKubeName: k8sObjectName, + constants.MetaKeyKubeNS: k8sNamespace, + constants.MetaKeyKubeName: k8sObjectName, }, - Listeners: []capi.APIGatewayListener{ + Listeners: []api.APIGatewayListener{ { Name: listenerOneName, Hostname: listenerOneHostname, Port: listenerOnePort, Protocol: listenerOneProtocol, - TLS: capi.APIGatewayTLSConfiguration{ - Certificates: []capi.ResourceReference{ + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ { - Kind: capi.InlineCertificate, + Kind: api.InlineCertificate, Name: listenerOneCertName, Namespace: listenerOneCertConsulNamespace, }, @@ -208,10 +225,10 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { Hostname: listenerTwoHostname, Port: listenerTwoPort, Protocol: listenerTwoProtocol, - TLS: capi.APIGatewayTLSConfiguration{ - Certificates: []capi.ResourceReference{ + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ { - Kind: capi.InlineCertificate, + Kind: api.InlineCertificate, Name: listenerTwoCertName, Namespace: listenerTwoCertConsulNamespace, }, @@ -219,28 +236,21 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { }, }, }, - Status: capi.ConfigEntryStatus{}, + Status: api.ConfigEntryStatus{}, Namespace: k8sNamespace, } - translator := K8sToConsulTranslator{ + translator := ResourceTranslator{ EnableConsulNamespaces: true, ConsulDestNamespace: "", EnableK8sMirroring: true, MirroringPrefix: "", } - certs := map[types.NamespacedName]api.ResourceReference{ - {Name: listenerOneCertName, Namespace: listenerOneCertK8sNamespace}: { - Name: listenerOneCertName, - Namespace: listenerOneCertConsulNamespace, - }, - {Name: listenerTwoCertName, Namespace: listenerTwoCertK8sNamespace}: { - Name: listenerTwoCertName, - Namespace: listenerTwoCertConsulNamespace, - }, - } + resources := NewResourceMap(translator, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) + resources.ReferenceCountCertificate(listenerOneCert) + resources.ReferenceCountCertificate(listenerTwoCert) - actualConfigEntry := translator.GatewayToAPIGateway(input, certs) + actualConfigEntry := translator.ToAPIGateway(input, resources) if diff := cmp.Diff(expectedConfigEntry, actualConfigEntry); diff != "" { t.Errorf("Translator.GatewayToAPIGateway() mismatch (-want +got):\n%s", diff) @@ -249,17 +259,16 @@ func TestTranslator_GatewayToAPIGateway(t *testing.T) { } } -func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { +func TestTranslator_ToHTTPRoute(t *testing.T) { t.Parallel() type args struct { k8sHTTPRoute gwv1beta1.HTTPRoute - parentRefs map[types.NamespacedName]api.ResourceReference - services map[types.NamespacedName]api.CatalogService - meshServices map[types.NamespacedName]v1alpha1.MeshService + services []types.NamespacedName + meshServices []v1alpha1.MeshService } tests := map[string]struct { args args - want capi.HTTPRouteConfigEntry + want api.HTTPRouteConfigEntry }{ "base test": { args: args{ @@ -273,10 +282,10 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { CommonRouteSpec: gwv1beta1.CommonRouteSpec{ ParentRefs: []gwv1beta1.ParentReference{ { - Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), Name: gwv1beta1.ObjectName("api-gw"), - Kind: ptrTo(gwv1beta1.Kind("Gateway")), - SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), + Kind: PointerTo(gwv1beta1.Kind("Gateway")), + SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), }, }, }, @@ -289,24 +298,24 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { Matches: []gwv1beta1.HTTPRouteMatch{ { Path: &gwv1beta1.HTTPPathMatch{ - Type: ptrTo(gwv1beta1.PathMatchPathPrefix), - Value: ptrTo("/v1"), + Type: PointerTo(gwv1beta1.PathMatchPathPrefix), + Value: PointerTo("/v1"), }, Headers: []gwv1beta1.HTTPHeaderMatch{ { - Type: ptrTo(gwv1beta1.HeaderMatchExact), + Type: PointerTo(gwv1beta1.HeaderMatchExact), Name: "my header match", Value: "the value", }, }, QueryParams: []gwv1beta1.HTTPQueryParamMatch{ { - Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Type: PointerTo(gwv1beta1.QueryParamMatchExact), Name: "search", Value: "term", }, }, - Method: ptrTo(gwv1beta1.HTTPMethodGet), + Method: PointerTo(gwv1beta1.HTTPMethodGet), }, }, Filters: []gwv1beta1.HTTPRouteFilter{ @@ -333,7 +342,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ Path: &gwv1beta1.HTTPPathModifier{ Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("v1"), + ReplacePrefixMatch: PointerTo("v1"), }, }, }, @@ -343,9 +352,9 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { BackendRef: gwv1beta1.BackendRef{ BackendObjectReference: gwv1beta1.BackendObjectReference{ Name: "service one", - Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + Namespace: PointerTo(gwv1beta1.Namespace("other")), }, - Weight: ptrTo(int32(45)), + Weight: PointerTo(int32(45)), }, Filters: []gwv1beta1.HTTPRouteFilter{ { @@ -371,7 +380,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ Path: &gwv1beta1.HTTPPathModifier{ Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("path"), + ReplacePrefixMatch: PointerTo("path"), }, }, }, @@ -382,29 +391,17 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - parentRefs: map[types.NamespacedName]api.ResourceReference{ - {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, - }, - services: map[types.NamespacedName]api.CatalogService{ - {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "other"}, + services: []types.NamespacedName{ + {Name: "service one", Namespace: "other"}, }, }, - want: capi.HTTPRouteConfigEntry{ - Kind: capi.HTTPRoute, + want: api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, Name: "k8s-http-route", - Parents: []capi.ResourceReference{ - { - Kind: capi.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Partition: "part-1", - Namespace: "ns", - }, - }, - Rules: []capi.HTTPRouteRule{ + Rules: []api.HTTPRouteRule{ { - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ { Add: map[string]string{ "add it on": "the value", @@ -416,37 +413,37 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - URLRewrite: &capi.URLRewrite{Path: "v1"}, + URLRewrite: &api.URLRewrite{Path: "v1"}, }, - Matches: []capi.HTTPMatch{ + Matches: []api.HTTPMatch{ { - Headers: []capi.HTTPHeaderMatch{ + Headers: []api.HTTPHeaderMatch{ { - Match: capi.HTTPHeaderMatchExact, + Match: api.HTTPHeaderMatchExact, Name: "my header match", Value: "the value", }, }, - Method: capi.HTTPMatchMethodGet, - Path: capi.HTTPPathMatch{ - Match: capi.HTTPPathMatchPrefix, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, Value: "/v1", }, - Query: []capi.HTTPQueryMatch{ + Query: []api.HTTPQueryMatch{ { - Match: capi.HTTPQueryMatchExact, + Match: api.HTTPQueryMatchExact, Name: "search", Value: "term", }, }, }, }, - Services: []capi.HTTPService{ + Services: []api.HTTPService{ { Name: "service one", Weight: 45, - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ { Add: map[string]string{ "svc - add it on": "svc - the value", @@ -458,7 +455,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - URLRewrite: &capi.URLRewrite{ + URLRewrite: &api.URLRewrite{ Path: "path", }, }, @@ -472,231 +469,8 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { "consul.io", }, Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: "k8s-ns", - metaKeyKubeServiceName: "k8s-http-route", - metaKeyKubeName: "k8s-http-route", - }, - Namespace: "k8s-ns", - }, - }, - "with httproute name override": { - args: args{ - k8sHTTPRoute: gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s-http-route", - Namespace: "k8s-ns", - Annotations: map[string]string{ - AnnotationHTTPRoute: "overrrrride", - }, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), - Name: gwv1beta1.ObjectName("api-gw"), - Kind: ptrTo(gwv1beta1.Kind("Gateway")), - SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{ - "host-name.example.com", - "consul.io", - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: ptrTo(gwv1beta1.PathMatchPathPrefix), - Value: ptrTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: ptrTo(gwv1beta1.HeaderMatchExact), - Name: "my header match", - Value: "the value", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: ptrTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "term", - }, - }, - Method: ptrTo(gwv1beta1.HTTPMethodGet), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "Magic", - Value: "v2", - }, - { - Name: "Another One", - Value: "dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "add it on", - Value: "the value", - }, - }, - Remove: []string{"time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("v1"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "service one", - Namespace: ptrTo(gwv1beta1.Namespace("some ns")), - }, - Weight: ptrTo(int32(45)), - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "svc - Magic", - Value: "svc - v2", - }, - { - Name: "svc - Another One", - Value: "svc - dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "svc - add it on", - Value: "svc - the value", - }, - }, - Remove: []string{"svc - time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("path"), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - parentRefs: map[types.NamespacedName]api.ResourceReference{ - {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, - }, - services: map[types.NamespacedName]api.CatalogService{ - {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "some ns"}, - }, - }, - want: capi.HTTPRouteConfigEntry{ - Kind: capi.HTTPRoute, - Name: "overrrrride", - Parents: []capi.ResourceReference{ - { - Kind: capi.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Partition: "part-1", - Namespace: "ns", - }, - }, - Rules: []capi.HTTPRouteRule{ - { - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &capi.URLRewrite{Path: "v1"}, - }, - Matches: []capi.HTTPMatch{ - { - Headers: []capi.HTTPHeaderMatch{ - { - Match: capi.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: capi.HTTPMatchMethodGet, - Path: capi.HTTPPathMatch{ - Match: capi.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []capi.HTTPQueryMatch{ - { - Match: capi.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []capi.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &capi.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{ - "host-name.example.com", - "consul.io", - }, - Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: "k8s-ns", - metaKeyKubeServiceName: "k8s-http-route", - metaKeyKubeName: "k8s-http-route", + constants.MetaKeyKubeNS: "k8s-ns", + constants.MetaKeyKubeName: "k8s-http-route", }, Namespace: "k8s-ns", }, @@ -707,18 +481,15 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "k8s-http-route", Namespace: "k8s-ns", - Annotations: map[string]string{ - AnnotationHTTPRoute: "overrrrride", - }, }, Spec: gwv1beta1.HTTPRouteSpec{ CommonRouteSpec: gwv1beta1.CommonRouteSpec{ ParentRefs: []gwv1beta1.ParentReference{ { - Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), Name: gwv1beta1.ObjectName("api-gw"), - SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), - Kind: ptrTo(gwv1beta1.Kind("Gateway")), + SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), + Kind: PointerTo(gwv1beta1.Kind("Gateway")), }, }, }, @@ -731,24 +502,24 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { Matches: []gwv1beta1.HTTPRouteMatch{ { Path: &gwv1beta1.HTTPPathMatch{ - Type: ptrTo(gwv1beta1.PathMatchPathPrefix), - Value: ptrTo("/v1"), + Type: PointerTo(gwv1beta1.PathMatchPathPrefix), + Value: PointerTo("/v1"), }, Headers: []gwv1beta1.HTTPHeaderMatch{ { - Type: ptrTo(gwv1beta1.HeaderMatchExact), + Type: PointerTo(gwv1beta1.HeaderMatchExact), Name: "my header match", Value: "the value", }, }, QueryParams: []gwv1beta1.HTTPQueryParamMatch{ { - Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Type: PointerTo(gwv1beta1.QueryParamMatchExact), Name: "search", Value: "term", }, }, - Method: ptrTo(gwv1beta1.HTTPMethodGet), + Method: PointerTo(gwv1beta1.HTTPMethodGet), }, }, Filters: []gwv1beta1.HTTPRouteFilter{ @@ -776,7 +547,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ Path: &gwv1beta1.HTTPPathModifier{ Type: gwv1beta1.FullPathHTTPPathModifier, - ReplaceFullPath: ptrTo("v1"), + ReplaceFullPath: PointerTo("v1"), }, }, }, @@ -786,9 +557,9 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { BackendRef: gwv1beta1.BackendRef{ BackendObjectReference: gwv1beta1.BackendObjectReference{ Name: "service one", - Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + Namespace: PointerTo(gwv1beta1.Namespace("some ns")), }, - Weight: ptrTo(int32(45)), + Weight: PointerTo(int32(45)), }, Filters: []gwv1beta1.HTTPRouteFilter{ { @@ -814,7 +585,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ Path: &gwv1beta1.HTTPPathModifier{ Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("path"), + ReplacePrefixMatch: PointerTo("path"), }, }, }, @@ -825,29 +596,17 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - parentRefs: map[types.NamespacedName]api.ResourceReference{ - {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, - }, - services: map[types.NamespacedName]api.CatalogService{ - {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "some ns"}, + services: []types.NamespacedName{ + {Name: "service one", Namespace: "some ns"}, }, }, - want: capi.HTTPRouteConfigEntry{ - Kind: capi.HTTPRoute, - Name: "overrrrride", - Parents: []capi.ResourceReference{ - { - Kind: capi.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Partition: "part-1", - Namespace: "ns", - }, - }, - Rules: []capi.HTTPRouteRule{ + want: api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "k8s-http-route", + Rules: []api.HTTPRouteRule{ { - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ { Add: map[string]string{ "add it on": "the value", @@ -860,35 +619,35 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - Matches: []capi.HTTPMatch{ + Matches: []api.HTTPMatch{ { - Headers: []capi.HTTPHeaderMatch{ + Headers: []api.HTTPHeaderMatch{ { - Match: capi.HTTPHeaderMatchExact, + Match: api.HTTPHeaderMatchExact, Name: "my header match", Value: "the value", }, }, - Method: capi.HTTPMatchMethodGet, - Path: capi.HTTPPathMatch{ - Match: capi.HTTPPathMatchPrefix, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, Value: "/v1", }, - Query: []capi.HTTPQueryMatch{ + Query: []api.HTTPQueryMatch{ { - Match: capi.HTTPQueryMatchExact, + Match: api.HTTPQueryMatchExact, Name: "search", Value: "term", }, }, }, }, - Services: []capi.HTTPService{ + Services: []api.HTTPService{ { Name: "service one", Weight: 45, - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ { Add: map[string]string{ "svc - add it on": "svc - the value", @@ -900,7 +659,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - URLRewrite: &capi.URLRewrite{ + URLRewrite: &api.URLRewrite{ Path: "path", }, }, @@ -914,15 +673,12 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { "consul.io", }, Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: "k8s-ns", - metaKeyKubeServiceName: "k8s-http-route", - metaKeyKubeName: "k8s-http-route", + constants.MetaKeyKubeNS: "k8s-ns", + constants.MetaKeyKubeName: "k8s-http-route", }, Namespace: "k8s-ns", }, }, - "parent ref that is not registered with consul is dropped": { args: args{ k8sHTTPRoute: gwv1beta1.HTTPRoute{ @@ -935,17 +691,17 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { CommonRouteSpec: gwv1beta1.CommonRouteSpec{ ParentRefs: []gwv1beta1.ParentReference{ { - Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), Name: gwv1beta1.ObjectName("api-gw"), - Kind: ptrTo(gwv1beta1.Kind("Gateway")), - SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), + Kind: PointerTo(gwv1beta1.Kind("Gateway")), + SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), }, { - Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), Name: gwv1beta1.ObjectName("consul don't know about me"), - Kind: ptrTo(gwv1beta1.Kind("Gateway")), - SectionName: ptrTo(gwv1beta1.SectionName("listener-1")), + Kind: PointerTo(gwv1beta1.Kind("Gateway")), + SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), }, }, }, @@ -958,24 +714,24 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { Matches: []gwv1beta1.HTTPRouteMatch{ { Path: &gwv1beta1.HTTPPathMatch{ - Type: ptrTo(gwv1beta1.PathMatchPathPrefix), - Value: ptrTo("/v1"), + Type: PointerTo(gwv1beta1.PathMatchPathPrefix), + Value: PointerTo("/v1"), }, Headers: []gwv1beta1.HTTPHeaderMatch{ { - Type: ptrTo(gwv1beta1.HeaderMatchExact), + Type: PointerTo(gwv1beta1.HeaderMatchExact), Name: "my header match", Value: "the value", }, }, QueryParams: []gwv1beta1.HTTPQueryParamMatch{ { - Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Type: PointerTo(gwv1beta1.QueryParamMatchExact), Name: "search", Value: "term", }, }, - Method: ptrTo(gwv1beta1.HTTPMethodGet), + Method: PointerTo(gwv1beta1.HTTPMethodGet), }, }, Filters: []gwv1beta1.HTTPRouteFilter{ @@ -1002,7 +758,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ Path: &gwv1beta1.HTTPPathModifier{ Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("v1"), + ReplacePrefixMatch: PointerTo("v1"), }, }, }, @@ -1012,9 +768,9 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { BackendRef: gwv1beta1.BackendRef{ BackendObjectReference: gwv1beta1.BackendObjectReference{ Name: "service one", - Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + Namespace: PointerTo(gwv1beta1.Namespace("some ns")), }, - Weight: ptrTo(int32(45)), + Weight: PointerTo(int32(45)), }, Filters: []gwv1beta1.HTTPRouteFilter{ { @@ -1040,7 +796,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ Path: &gwv1beta1.HTTPPathModifier{ Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("path"), + ReplacePrefixMatch: PointerTo("path"), }, }, }, @@ -1051,29 +807,17 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - parentRefs: map[types.NamespacedName]api.ResourceReference{ - {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, - }, - services: map[types.NamespacedName]api.CatalogService{ - {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "some ns"}, + services: []types.NamespacedName{ + {Name: "service one", Namespace: "some ns"}, }, }, - want: capi.HTTPRouteConfigEntry{ - Kind: capi.HTTPRoute, + want: api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, Name: "k8s-http-route", - Parents: []capi.ResourceReference{ - { - Kind: capi.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Partition: "part-1", - Namespace: "ns", - }, - }, - Rules: []capi.HTTPRouteRule{ + Rules: []api.HTTPRouteRule{ { - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ { Add: map[string]string{ "add it on": "the value", @@ -1085,37 +829,37 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - URLRewrite: &capi.URLRewrite{Path: "v1"}, + URLRewrite: &api.URLRewrite{Path: "v1"}, }, - Matches: []capi.HTTPMatch{ + Matches: []api.HTTPMatch{ { - Headers: []capi.HTTPHeaderMatch{ + Headers: []api.HTTPHeaderMatch{ { - Match: capi.HTTPHeaderMatchExact, + Match: api.HTTPHeaderMatchExact, Name: "my header match", Value: "the value", }, }, - Method: capi.HTTPMatchMethodGet, - Path: capi.HTTPPathMatch{ - Match: capi.HTTPPathMatchPrefix, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, Value: "/v1", }, - Query: []capi.HTTPQueryMatch{ + Query: []api.HTTPQueryMatch{ { - Match: capi.HTTPQueryMatchExact, + Match: api.HTTPQueryMatchExact, Name: "search", Value: "term", }, }, }, }, - Services: []capi.HTTPService{ + Services: []api.HTTPService{ { Name: "service one", Weight: 45, - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ { Add: map[string]string{ "svc - add it on": "svc - the value", @@ -1127,7 +871,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - URLRewrite: &capi.URLRewrite{ + URLRewrite: &api.URLRewrite{ Path: "path", }, }, @@ -1141,10 +885,8 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { "consul.io", }, Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: "k8s-ns", - metaKeyKubeServiceName: "k8s-http-route", - metaKeyKubeName: "k8s-http-route", + constants.MetaKeyKubeNS: "k8s-ns", + constants.MetaKeyKubeName: "k8s-http-route", }, Namespace: "k8s-ns", }, @@ -1161,9 +903,9 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { CommonRouteSpec: gwv1beta1.CommonRouteSpec{ ParentRefs: []gwv1beta1.ParentReference{ { - Namespace: ptrTo(gwv1beta1.Namespace("k8s-gw-ns")), + Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), Name: gwv1beta1.ObjectName("api-gw"), - Kind: ptrTo(gwv1beta1.Kind("Gateway")), + Kind: PointerTo(gwv1beta1.Kind("Gateway")), }, }, }, @@ -1176,24 +918,24 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { Matches: []gwv1beta1.HTTPRouteMatch{ { Path: &gwv1beta1.HTTPPathMatch{ - Type: ptrTo(gwv1beta1.PathMatchPathPrefix), - Value: ptrTo("/v1"), + Type: PointerTo(gwv1beta1.PathMatchPathPrefix), + Value: PointerTo("/v1"), }, Headers: []gwv1beta1.HTTPHeaderMatch{ { - Type: ptrTo(gwv1beta1.HeaderMatchExact), + Type: PointerTo(gwv1beta1.HeaderMatchExact), Name: "my header match", Value: "the value", }, }, QueryParams: []gwv1beta1.HTTPQueryParamMatch{ { - Type: ptrTo(gwv1beta1.QueryParamMatchExact), + Type: PointerTo(gwv1beta1.QueryParamMatchExact), Name: "search", Value: "term", }, }, - Method: ptrTo(gwv1beta1.HTTPMethodGet), + Method: PointerTo(gwv1beta1.HTTPMethodGet), }, }, Filters: []gwv1beta1.HTTPRouteFilter{ @@ -1220,7 +962,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ Path: &gwv1beta1.HTTPPathModifier{ Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("v1"), + ReplacePrefixMatch: PointerTo("v1"), }, }, }, @@ -1231,7 +973,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { BackendRef: gwv1beta1.BackendRef{ BackendObjectReference: gwv1beta1.BackendObjectReference{ Name: "service two", - Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + Namespace: PointerTo(gwv1beta1.Namespace("some ns")), }, }, }, @@ -1239,9 +981,9 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { BackendRef: gwv1beta1.BackendRef{ BackendObjectReference: gwv1beta1.BackendObjectReference{ Name: "some-service-part-three", - Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), - Group: ptrTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), - Kind: ptrTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), + Namespace: PointerTo(gwv1beta1.Namespace("svc-ns")), + Group: PointerTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), + Kind: PointerTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), }, }, }, @@ -1249,9 +991,9 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { BackendRef: gwv1beta1.BackendRef{ BackendObjectReference: gwv1beta1.BackendObjectReference{ Name: "service one", - Namespace: ptrTo(gwv1beta1.Namespace("some ns")), + Namespace: PointerTo(gwv1beta1.Namespace("some ns")), }, - Weight: ptrTo(int32(45)), + Weight: PointerTo(int32(45)), }, Filters: []gwv1beta1.HTTPRouteFilter{ { @@ -1277,7 +1019,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ Path: &gwv1beta1.HTTPPathModifier{ Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: ptrTo("path"), + ReplacePrefixMatch: PointerTo("path"), }, }, }, @@ -1288,32 +1030,20 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - parentRefs: map[types.NamespacedName]api.ResourceReference{ - {Name: "api-gw", Namespace: "k8s-gw-ns"}: {Name: "api-gw", Partition: "part-1", Namespace: "ns"}, - }, - services: map[types.NamespacedName]api.CatalogService{ - {Name: "service one", Namespace: "some ns"}: {ServiceName: "service one", Namespace: "some ns"}, + services: []types.NamespacedName{ + {Name: "service one", Namespace: "some ns"}, }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{ - {Name: "some-service-part-three", Namespace: "svc-ns"}: {Spec: v1alpha1.MeshServiceSpec{Name: "some-service-part-three"}}, + meshServices: []v1alpha1.MeshService{ + {ObjectMeta: metav1.ObjectMeta{Name: "some-service-part-three", Namespace: "svc-ns"}, Spec: v1alpha1.MeshServiceSpec{Name: "some-override"}}, }, }, - want: capi.HTTPRouteConfigEntry{ - Kind: capi.HTTPRoute, + want: api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, Name: "k8s-http-route", - Parents: []capi.ResourceReference{ - { - Kind: capi.APIGateway, - Name: "api-gw", - SectionName: "", - Partition: "part-1", - Namespace: "ns", - }, - }, - Rules: []capi.HTTPRouteRule{ + Rules: []api.HTTPRouteRule{ { - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ { Add: map[string]string{ "add it on": "the value", @@ -1325,38 +1055,38 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - URLRewrite: &capi.URLRewrite{Path: "v1"}, + URLRewrite: &api.URLRewrite{Path: "v1"}, }, - Matches: []capi.HTTPMatch{ + Matches: []api.HTTPMatch{ { - Headers: []capi.HTTPHeaderMatch{ + Headers: []api.HTTPHeaderMatch{ { - Match: capi.HTTPHeaderMatchExact, + Match: api.HTTPHeaderMatchExact, Name: "my header match", Value: "the value", }, }, - Method: capi.HTTPMatchMethodGet, - Path: capi.HTTPPathMatch{ - Match: capi.HTTPPathMatchPrefix, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, Value: "/v1", }, - Query: []capi.HTTPQueryMatch{ + Query: []api.HTTPQueryMatch{ { - Match: capi.HTTPQueryMatchExact, + Match: api.HTTPQueryMatchExact, Name: "search", Value: "term", }, }, }, }, - Services: []capi.HTTPService{ - {Name: "some-service-part-three", Filters: capi.HTTPFilters{Headers: []capi.HTTPHeaderFilter{{Add: make(map[string]string), Remove: make([]string, 0), Set: make(map[string]string)}}}}, + Services: []api.HTTPService{ + {Name: "some-override", Namespace: "svc-ns", Weight: 1, Filters: api.HTTPFilters{Headers: []api.HTTPHeaderFilter{{Add: make(map[string]string), Set: make(map[string]string)}}}}, { Name: "service one", Weight: 45, - Filters: capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ { Add: map[string]string{ "svc - add it on": "svc - the value", @@ -1368,7 +1098,7 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { }, }, }, - URLRewrite: &capi.URLRewrite{ + URLRewrite: &api.URLRewrite{ Path: "path", }, }, @@ -1382,10 +1112,8 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { "consul.io", }, Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: "k8s-ns", - metaKeyKubeServiceName: "k8s-http-route", - metaKeyKubeName: "k8s-http-route", + constants.MetaKeyKubeNS: "k8s-ns", + constants.MetaKeyKubeName: "k8s-http-route", }, Namespace: "k8s-ns", }, @@ -1393,29 +1121,37 @@ func TestTranslator_HTTPRouteToHTTPRoute(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - tr := K8sToConsulTranslator{ + tr := ResourceTranslator{ EnableConsulNamespaces: true, EnableK8sMirroring: true, } - got := tr.HTTPRouteToHTTPRoute(&tc.args.k8sHTTPRoute, tc.args.parentRefs, tc.args.services, tc.args.meshServices) + + resources := NewResourceMap(tr, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) + for _, service := range tc.args.services { + resources.AddService(service, service.Name) + } + for _, service := range tc.args.meshServices { + resources.AddMeshService(service) + } + + got := tr.ToHTTPRoute(tc.args.k8sHTTPRoute, resources) if diff := cmp.Diff(&tc.want, got); diff != "" { - t.Errorf("Translator.HTTPRouteToHTTPRoute() mismatch (-want +got):\n%s", diff) + t.Errorf("Translator.ToHTTPRoute() mismatch (-want +got):\n%s", diff) } }) } } -func TestTranslator_TCPRouteToTCPRoute(t *testing.T) { +func TestTranslator_ToTCPRoute(t *testing.T) { t.Parallel() type args struct { k8sRoute gwv1alpha2.TCPRoute - parentRefs map[types.NamespacedName]api.ResourceReference - services map[types.NamespacedName]api.CatalogService - meshServices map[types.NamespacedName]v1alpha1.MeshService + services []types.NamespacedName + meshServices []v1alpha1.MeshService } tests := map[string]struct { args args - want capi.TCPRouteConfigEntry + want api.TCPRouteConfigEntry }{ "base test": { args: args{ @@ -1425,23 +1161,13 @@ func TestTranslator_TCPRouteToTCPRoute(t *testing.T) { Namespace: "k8s-ns", }, Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: ptrTo(gwv1beta1.Namespace("another-ns")), - Name: "mygw", - SectionName: ptrTo(gwv1beta1.SectionName("listener-one")), - Kind: ptrTo(gwv1beta1.Kind("Gateway")), - }, - }, - }, Rules: []gwv1alpha2.TCPRouteRule{ { BackendRefs: []gwv1beta1.BackendRef{ { BackendObjectReference: gwv1beta1.BackendObjectReference{ Name: "some-service", - Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), + Namespace: PointerTo(gwv1beta1.Namespace("svc-ns")), }, Weight: new(int32), }, @@ -1452,7 +1178,16 @@ func TestTranslator_TCPRouteToTCPRoute(t *testing.T) { { BackendObjectReference: gwv1beta1.BackendObjectReference{ Name: "some-service-part-two", - Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), + Namespace: PointerTo(gwv1beta1.Namespace("svc-ns")), + }, + Weight: new(int32), + }, + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Group: PointerTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), + Kind: PointerTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), + Name: "some-service-part-three", + Namespace: PointerTo(gwv1beta1.Namespace("svc-ns")), }, Weight: new(int32), }, @@ -1461,38 +1196,19 @@ func TestTranslator_TCPRouteToTCPRoute(t *testing.T) { }, }, }, - parentRefs: map[types.NamespacedName]api.ResourceReference{ - { - Namespace: "another-ns", - Name: "mygw", - }: { - Name: "mygw", - Namespace: "another-ns", - Partition: "", - }, + services: []types.NamespacedName{ + {Name: "some-service", Namespace: "svc-ns"}, + {Name: "some-service-part-two", Namespace: "svc-ns"}, }, - services: map[types.NamespacedName]api.CatalogService{ - {Name: "some-service", Namespace: "svc-ns"}: {ServiceName: "some-service", Namespace: "svc-ns"}, - {Name: "some-service-part-two", Namespace: "svc-ns"}: {ServiceName: "some-service-part-two", Namespace: "svc-ns"}, - }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{ - {Name: "some-service-part-three", Namespace: "svc-ns"}: {Spec: v1alpha1.MeshServiceSpec{Name: "some-service-part-three"}}, + meshServices: []v1alpha1.MeshService{ + {ObjectMeta: metav1.ObjectMeta{Name: "some-service-part-three", Namespace: "svc-ns"}, Spec: v1alpha1.MeshServiceSpec{Name: "some-override"}}, }, }, - want: capi.TCPRouteConfigEntry{ - Kind: capi.TCPRoute, + want: api.TCPRouteConfigEntry{ + Kind: api.TCPRoute, Name: "tcp-route", Namespace: "k8s-ns", - Parents: []capi.ResourceReference{ - { - Kind: capi.APIGateway, - Name: "mygw", - SectionName: "listener-one", - Partition: "", - Namespace: "another-ns", - }, - }, - Services: []capi.TCPService{ + Services: []api.TCPService{ { Name: "some-service", Partition: "", @@ -1503,134 +1219,86 @@ func TestTranslator_TCPRouteToTCPRoute(t *testing.T) { Partition: "", Namespace: "svc-ns", }, - }, - Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: "k8s-ns", - metaKeyKubeServiceName: "tcp-route", - metaKeyKubeName: "tcp-route", - }, - }, - }, - - "overwrite the route name via annotaions": { - args: args{ - k8sRoute: gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route", - Namespace: "k8s-ns", - Annotations: map[string]string{ - AnnotationTCPRoute: "replaced-name", - }, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: ptrTo(gwv1beta1.Namespace("another-ns")), - Name: "mygw", - SectionName: ptrTo(gwv1beta1.SectionName("listener-one")), - Kind: ptrTo(gwv1beta1.Kind("Gateway")), - }, - }, - }, - Rules: []gwv1alpha2.TCPRouteRule{ - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "some-service", - Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), - }, - Weight: new(int32), - }, - }, - }, - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "some-service-part-two", - Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), - }, - Weight: new(int32), - }, - }, - }, - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "some-service-part-three", - Namespace: ptrTo(gwv1beta1.Namespace("svc-ns")), - Group: ptrTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), - Kind: ptrTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), - }, - Weight: new(int32), - }, - }, - }, - }, - }, - }, - parentRefs: map[types.NamespacedName]api.ResourceReference{ { - Namespace: "another-ns", - Name: "mygw", - }: { - Name: "mygw", - Namespace: "another-ns", + Name: "some-override", Partition: "", + Namespace: "svc-ns", }, }, - services: map[types.NamespacedName]api.CatalogService{ - {Name: "some-service", Namespace: "svc-ns"}: {ServiceName: "some-service", Namespace: "other"}, - }, - meshServices: map[types.NamespacedName]v1alpha1.MeshService{ - {Name: "some-service-part-three", Namespace: "svc-ns"}: {Spec: v1alpha1.MeshServiceSpec{Name: "some-service-part-three"}}, - }, - }, - want: capi.TCPRouteConfigEntry{ - Kind: capi.TCPRoute, - Name: "replaced-name", - Namespace: "k8s-ns", - Parents: []capi.ResourceReference{ - { - Kind: capi.APIGateway, - Name: "mygw", - SectionName: "listener-one", - Partition: "", - Namespace: "another-ns", - }, - }, - Services: []capi.TCPService{ - { - Name: "some-service", - Partition: "", - Namespace: "other", - }, - {Name: "some-service-part-three"}, - }, Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: "k8s-ns", - metaKeyKubeServiceName: "tcp-route", - metaKeyKubeName: "tcp-route", + constants.MetaKeyKubeNS: "k8s-ns", + constants.MetaKeyKubeName: "tcp-route", }, }, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { - tr := K8sToConsulTranslator{ + tr := ResourceTranslator{ EnableConsulNamespaces: true, EnableK8sMirroring: true, } - got := tr.TCPRouteToTCPRoute(&tt.args.k8sRoute, tt.args.parentRefs, tt.args.services, tt.args.meshServices) + resources := NewResourceMap(tr, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) + for _, service := range tt.args.services { + resources.AddService(service, service.Name) + } + for _, service := range tt.args.meshServices { + resources.AddMeshService(service) + } + + got := tr.ToTCPRoute(tt.args.k8sRoute, resources) if diff := cmp.Diff(&tt.want, got); diff != "" { t.Errorf("Translator.TCPRouteToTCPRoute() mismatch (-want +got):\n%s", diff) } }) } } + +func generateTestCertificate(t *testing.T, namespace, name string) corev1.Secret { + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(t, err) + + usage := x509.KeyUsageCertSign + expiration := time.Now().AddDate(10, 0, 0) + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "consul.test", + }, + IsCA: true, + NotBefore: time.Now().Add(-10 * time.Minute), + NotAfter: expiration, + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: usage, + BasicConstraintsValid: true, + } + caCert := cert + caPrivateKey := privateKey + + data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) + require.NoError(t, err) + + certBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: data, + }) + + privateKeyBytes := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + + return corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Data: map[string][]byte{ + corev1.TLSCertKey: certBytes, + corev1.TLSPrivateKeyKey: privateKeyBytes, + }, + } +} diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index e29e40b28e..691c25bf72 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -6,7 +6,10 @@ package controllers import ( "context" "reflect" + "strconv" + "strings" + mapset "github.com/deckarep/golang-set" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/go-logr/logr" @@ -21,234 +24,178 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/binding" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/gatekeeper" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/translation" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" ) -const ( - gatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" - - kindGateway = "Gateway" -) - // GatewayControllerConfig holds the values necessary for configuring the GatewayController. type GatewayControllerConfig struct { - HelmConfig apigateway.HelmConfig - ConsulClientConfig *consul.Config - ConsulServerConnMgr consul.ServerConnectionManager - NamespacesEnabled bool - Partition string + HelmConfig common.HelmConfig + ConsulClientConfig *consul.Config + ConsulServerConnMgr consul.ServerConnectionManager + NamespacesEnabled bool + CrossNamespaceACLPolicy string + Partition string + AllowK8sNamespacesSet mapset.Set + DenyK8sNamespacesSet mapset.Set } // GatewayController reconciles a Gateway object. // The Gateway is responsible for defining the behavior of API gateways. type GatewayController struct { - HelmConfig apigateway.HelmConfig - Log logr.Logger - Translator translation.K8sToConsulTranslator - cache *cache.Cache + HelmConfig common.HelmConfig + Log logr.Logger + Translator common.ResourceTranslator + cache *cache.Cache + gatewayCache *cache.GatewayCache + allowK8sNamespacesSet mapset.Set + denyK8sNamespacesSet mapset.Set client.Client } // Reconcile handles the reconciliation loop for Gateway objects. func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + consulKey := r.Translator.ConfigEntryReference(api.APIGateway, req.NamespacedName) + nonNormalizedConsulKey := r.Translator.NonNormalizedConfigEntryReference(api.APIGateway, req.NamespacedName) + + var gateway gwv1beta1.Gateway + log := r.Log.WithValues("gateway", req.NamespacedName) log.Info("Reconciling Gateway") - // If gateway does not exist, log an error. - var gw gwv1beta1.Gateway - err := r.Client.Get(ctx, req.NamespacedName, &gw) - if err != nil { - if k8serrors.IsNotFound(err) { - return ctrl.Result{}, nil + // get the gateway + if err := r.Client.Get(ctx, req.NamespacedName, &gateway); err != nil { + if !k8serrors.IsNotFound(err) { + log.Error(err, "unable to get Gateway") } - log.Error(err, "unable to get Gateway") - return ctrl.Result{}, err + return ctrl.Result{}, client.IgnoreNotFound(err) } - // If gateway class on the gateway does not exist, log an error. - gwc := &gwv1beta1.GatewayClass{} - err = r.Client.Get(ctx, types.NamespacedName{Name: string(gw.Spec.GatewayClassName)}, gwc) + // get the gateway class + gatewayClass, err := r.getGatewayClassForGateway(ctx, gateway) if err != nil { - if !k8serrors.IsNotFound(err) { - log.Error(err, "unable to get GatewayClass") - return ctrl.Result{}, err - } - gwc = nil + log.Error(err, "unable to get GatewayClass") + return ctrl.Result{}, err } - gwcc, err := getConfigForGatewayClass(ctx, r.Client, gwc) + // get the gateway class config + gatewayClassConfig, err := r.getConfigForGatewayClass(ctx, gatewayClass) if err != nil { log.Error(err, "error fetching the gateway class config") return ctrl.Result{}, err } - // fetch all namespaces - namespaceList := &corev1.NamespaceList{} - if err := r.Client.List(ctx, namespaceList); err != nil { + // get all namespaces + namespaces, err := r.getNamespaces(ctx) + if err != nil { log.Error(err, "unable to list Namespaces") return ctrl.Result{}, err } - namespaces := map[string]corev1.Namespace{} - for _, namespace := range namespaceList.Items { - namespaces[namespace.Name] = namespace - } - - // fetch all gateways we control for reference counting - gwcList := &gwv1beta1.GatewayClassList{} - if err := r.Client.List(ctx, gwcList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(GatewayClass_ControllerNameIndex, GatewayClassControllerName), - }); err != nil { - log.Error(err, "unable to list GatewayClasses") - return ctrl.Result{}, err - } - // fetch related gateway pods - labels := apigateway.LabelsForGateway(&gw) - podList := &corev1.PodList{} - if err := r.Client.List(ctx, podList, client.MatchingLabels(labels)); err != nil { - log.Error(err, "unable to list Pods for Gateway") + // get all reference grants + grants, err := r.getReferenceGrants(ctx) + if err != nil { + log.Error(err, "unable to list ReferenceGrants") return ctrl.Result{}, err } - // fetch related gateway services - service := &corev1.Service{} - // we use the implicit association of a service name/namespace with a corresponding - // gateway - if err := r.Client.Get(ctx, req.NamespacedName, service); err != nil { - if !k8serrors.IsNotFound(err) { - log.Error(err, "unable to fetch service for Gateway") - return ctrl.Result{}, err - } - // if we got a 404, then nil out the service - service = nil + // get related gateway service + service, err := r.getDeployedGatewayService(ctx, req.NamespacedName) + if err != nil { + log.Error(err, "unable to fetch service for Gateway") } - gwList := &gwv1beta1.GatewayList{} - if err := r.Client.List(ctx, gwList); err != nil { - log.Error(err, "unable to list Gateways") + // get related gateway pods + pods, err := r.getDeployedGatewayPods(ctx, gateway) + if err != nil { + log.Error(err, "unable to list Pods for Gateway") return ctrl.Result{}, err } - controlled := map[types.NamespacedName]gwv1beta1.Gateway{} - for _, gwc := range gwcList.Items { - for _, gw := range gwList.Items { - if string(gw.Spec.GatewayClassName) == gwc.Name { - controlled[types.NamespacedName{Namespace: gw.Namespace, Name: gw.Name}] = gw - } - } - } + // construct our resource map + referenceValidator := binding.NewReferenceValidator(grants) + resources := common.NewResourceMap(r.Translator, referenceValidator, log) - // fetch all MeshServices - meshServiceList := &v1alpha1.MeshServiceList{} - if err := r.Client.List(ctx, meshServiceList); err != nil { - log.Error(err, "unable to list MeshServices") + if err := r.fetchCertificatesForGateway(ctx, resources, gateway); err != nil { + log.Error(err, "unable to fetch certificates for gateway") return ctrl.Result{}, err } - // fetch all secrets referenced by this gateway - secretList := &corev1.SecretList{} - if err := r.Client.List(ctx, secretList); err != nil { - log.Error(err, "unable to list Secrets") + if err := r.fetchControlledGateways(ctx, resources); err != nil { + log.Error(err, "unable to fetch controlled gateways") return ctrl.Result{}, err } - listenerCerts := make(map[types.NamespacedName]struct{}) - for _, listener := range gw.Spec.Listeners { - if listener.TLS != nil { - for _, ref := range listener.TLS.CertificateRefs { - if nilOrEqual(ref.Group, "") && nilOrEqual(ref.Kind, "Secret") { - listenerCerts[indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, gw.Namespace)] = struct{}{} - } - } - } - } - - filteredSecrets := []corev1.Secret{} - for _, secret := range secretList.Items { - namespacedName := types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name} - if _, ok := listenerCerts[namespacedName]; ok { - filteredSecrets = append(filteredSecrets, secret) - } - } - - // fetch all http routes referencing this gateway - httpRouteList := &gwv1beta1.HTTPRouteList{} - if err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(HTTPRoute_GatewayIndex, req.String()), - }); err != nil { + // get all http routes referencing this gateway + httpRoutes, err := r.getRelatedHTTPRoutes(ctx, req.NamespacedName, resources) + if err != nil { log.Error(err, "unable to list HTTPRoutes") return ctrl.Result{}, err } - // fetch all tcp routes referencing this gateway - tcpRouteList := &gwv1alpha2.TCPRouteList{} - if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(TCPRoute_GatewayIndex, req.String()), - }); err != nil { + // get all tcp routes referencing this gateway + tcpRoutes, err := r.getRelatedTCPRoutes(ctx, req.NamespacedName, resources) + if err != nil { log.Error(err, "unable to list TCPRoutes") return ctrl.Result{}, err } - configEntry := r.cache.Get(r.Translator.ReferenceForGateway(&gw)) - - var consulGateway *api.APIGatewayConfigEntry - if configEntry != nil { - consulGateway = configEntry.(*api.APIGatewayConfigEntry) + if err := r.fetchServicesForRoutes(ctx, resources, tcpRoutes, httpRoutes); err != nil { + log.Error(err, "unable to fetch services for routes") + return ctrl.Result{}, err } - httpRoutes := r.cache.List(api.HTTPRoute) - tcpRoutes := r.cache.List(api.TCPRoute) - inlineCertificates := r.cache.List(api.InlineCertificate) - services := r.cache.ListServices() + + // fetch all consul objects from cache + consulServices := r.getConsulServices(consulKey) + consulGateway := r.getConsulGateway(consulKey) + consulHTTPRoutes := r.getConsulHTTPRoutes(consulKey, resources) + consulTCPRoutes := r.getConsulTCPRoutes(consulKey, resources) + consulInlineCertificates := r.getConsulInlineCertificates() binder := binding.NewBinder(binding.BinderConfig{ + Logger: log, Translator: r.Translator, ControllerName: GatewayClassControllerName, - GatewayClassConfig: gwcc, - GatewayClass: gwc, - Gateway: gw, - Pods: podList.Items, + Namespaces: namespaces, + GatewayClassConfig: gatewayClassConfig, + GatewayClass: gatewayClass, + Gateway: gateway, + Pods: pods, Service: service, - HTTPRoutes: httpRouteList.Items, - TCPRoutes: tcpRouteList.Items, - MeshServices: meshServiceList.Items, - Secrets: filteredSecrets, + HTTPRoutes: httpRoutes, + TCPRoutes: tcpRoutes, + Resources: resources, ConsulGateway: consulGateway, - ConsulHTTPRoutes: derefAll(configEntriesTo[*api.HTTPRouteConfigEntry](httpRoutes)), - ConsulTCPRoutes: derefAll(configEntriesTo[*api.TCPRouteConfigEntry](tcpRoutes)), - ConsulInlineCertificates: derefAll(configEntriesTo[*api.InlineCertificateConfigEntry](inlineCertificates)), - ConnectInjectedServices: services, - GatewayServices: consulServicesForGateway(gw, services), - Namespaces: namespaces, - ControlledGateways: controlled, + ConsulHTTPRoutes: consulHTTPRoutes, + ConsulTCPRoutes: consulTCPRoutes, + ConsulInlineCertificates: consulInlineCertificates, + ConsulGatewayServices: consulServices, }) updates := binder.Snapshot() if updates.UpsertGatewayDeployment { - log.Info("updating gatekeeper") - err := r.updateGatekeeperResources(ctx, log, &gw, gwcc) + err := r.updateGatekeeperResources(ctx, log, &gateway, updates.GatewayClassConfig) if err != nil { log.Error(err, "unable to update gateway resources") return ctrl.Result{}, err } + r.gatewayCache.EnsureSubscribed(nonNormalizedConsulKey, req.NamespacedName) } else { - log.Info("deleting gatekeeper") - err := r.deleteGatekeeperResources(ctx, log, &gw) + err := r.deleteGatekeeperResources(ctx, log, &gateway) if err != nil { log.Error(err, "unable to delete gateway resources") return ctrl.Result{}, err } + r.gatewayCache.RemoveSubscription(nonNormalizedConsulKey) } for _, deletion := range updates.Consul.Deletions { @@ -260,8 +207,16 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct } for _, update := range updates.Consul.Updates { - log.Info("updating in Consul", "kind", update.GetKind(), "namespace", update.GetNamespace(), "name", update.GetName()) - if err := r.cache.Write(ctx, update); err != nil { + entry := update.Entry + log.Info("updating in Consul", "kind", entry.GetKind(), "namespace", entry.GetNamespace(), "name", entry.GetName()) + err := r.cache.Write(ctx, entry) + if update.OnUpdate != nil { + // swallow any potential error with our handler if one is provided + update.OnUpdate(err) + continue + } + + if err != nil { log.Error(err, "error updating config entry") return ctrl.Result{}, err } @@ -283,7 +238,7 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct } } - for _, update := range updates.Kubernetes.Updates { + for _, update := range updates.Kubernetes.Updates.Operations() { log.Info("update in Kubernetes", "kind", update.GetObjectKind().GroupVersionKind().Kind, "namespace", update.GetNamespace(), "name", update.GetName()) if err := r.updateAndResetStatus(ctx, update); err != nil { log.Error(err, "error updating object") @@ -291,7 +246,7 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct } } - for _, update := range updates.Kubernetes.StatusUpdates { + for _, update := range updates.Kubernetes.StatusUpdates.Operations() { log.Info("update status in Kubernetes", "kind", update.GetObjectKind().GroupVersionKind().Kind, "namespace", update.GetNamespace(), "name", update.GetName()) if err := r.Client.Status().Update(ctx, update); err != nil { log.Error(err, "error updating status") @@ -303,17 +258,12 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct // mesh == read on the provisioned gateway token if needed, figure out some other // way of handling it. if updates.UpsertGatewayDeployment { - reference := r.Translator.ReferenceForGateway(&gw) - if err := r.cache.LinkPolicy(ctx, reference.Name, reference.Namespace); err != nil { + if err := r.cache.LinkPolicy(ctx, nonNormalizedConsulKey.Name, nonNormalizedConsulKey.Namespace); err != nil { log.Error(err, "error linking token policy") return ctrl.Result{}, err } } - /* TODO: - 1.ReferenceGrants - */ - return ctrl.Result{}, nil } @@ -328,14 +278,6 @@ func (r *GatewayController) updateAndResetStatus(ctx context.Context, o client.O return nil } -func derefAll[T any](vs []*T) []T { - e := make([]T, len(vs)) - for _, v := range vs { - e = append(e, *v) - } - return e -} - func configEntriesTo[T api.ConfigEntry](entries []api.ConfigEntry) []T { es := []T{} for _, e := range entries { @@ -369,21 +311,31 @@ func (r *GatewayController) updateGatekeeperResources(ctx context.Context, log l // SetupWithGatewayControllerManager registers the controller with the given manager. func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, config GatewayControllerConfig) (*cache.Cache, error) { - c := cache.New(cache.Config{ - ConsulClientConfig: config.ConsulClientConfig, - ConsulServerConnMgr: config.ConsulServerConnMgr, - NamespacesEnabled: config.NamespacesEnabled, - PeeringEnabled: config.HelmConfig.PeeringEnabled, - Logger: mgr.GetLogger(), - }) - - translator := translation.NewConsulToNamespaceNameTranslator(c) + cacheConfig := cache.Config{ + ConsulClientConfig: config.ConsulClientConfig, + ConsulServerConnMgr: config.ConsulServerConnMgr, + NamespacesEnabled: config.NamespacesEnabled, + CrossNamespaceACLPolicy: config.CrossNamespaceACLPolicy, + Logger: mgr.GetLogger(), + } + c := cache.New(cacheConfig) + gwc := cache.NewGatewayCache(ctx, cacheConfig) r := &GatewayController{ Client: mgr.GetClient(), Log: mgr.GetLogger(), HelmConfig: config.HelmConfig, - cache: c, + Translator: common.ResourceTranslator{ + EnableConsulNamespaces: config.HelmConfig.EnableNamespaces, + ConsulDestNamespace: config.HelmConfig.ConsulDestinationNamespace, + EnableK8sMirroring: config.HelmConfig.EnableNamespaceMirroring, + MirroringPrefix: config.HelmConfig.NamespaceMirroringPrefix, + ConsulPartition: config.HelmConfig.ConsulPartition, + }, + denyK8sNamespacesSet: config.DenyK8sNamespacesSet, + allowK8sNamespacesSet: config.AllowK8sNamespacesSet, + cache: c, + gatewayCache: gwc, } return c, ctrl.NewControllerManagedBy(mgr). @@ -391,6 +343,10 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co Owns(&appsv1.Deployment{}). Owns(&corev1.Service{}). Owns(&corev1.Pod{}). + Watches( + source.NewKindWithCache(&gwv1beta1.ReferenceGrant{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformReferenceGrant(ctx)), + ). Watches( source.NewKindWithCache(&gwv1beta1.GatewayClass{}, mgr.GetCache()), handler.EnqueueRequestsFromMapFunc(r.transformGatewayClass(ctx)), @@ -416,44 +372,31 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co handler.EnqueueRequestsFromMapFunc(r.transformMeshService(ctx)), ). Watches( - // Subscribe to changes from Consul Connect Services - &source.Channel{Source: c.SubscribeServices(ctx, r.transformConsulService(ctx)).Events()}, - &handler.EnqueueRequestForObject{}, - ). - Watches( - // Subscribe to changes from Consul Peering Services - &source.Channel{Source: c.SubscribePeerings(ctx, r.transformConsulPeering(ctx)).Events()}, - &handler.EnqueueRequestForObject{}, + source.NewKindWithCache(&corev1.Endpoints{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformEndpoints(ctx)), ). Watches( // Subscribe to changes from Consul for APIGateways - &source.Channel{Source: c.Subscribe(ctx, api.APIGateway, translator.BuildConsulGatewayTranslator(ctx)).Events()}, + &source.Channel{Source: c.Subscribe(ctx, api.APIGateway, r.transformConsulGateway).Events()}, &handler.EnqueueRequestForObject{}, ). Watches( // Subscribe to changes from Consul for HTTPRoutes - &source.Channel{Source: c.Subscribe(ctx, api.HTTPRoute, translator.BuildConsulHTTPRouteTranslator(ctx)).Events()}, + &source.Channel{Source: c.Subscribe(ctx, api.HTTPRoute, r.transformConsulHTTPRoute(ctx)).Events()}, &handler.EnqueueRequestForObject{}, ). Watches( // Subscribe to changes from Consul for TCPRoutes - &source.Channel{Source: c.Subscribe(ctx, api.TCPRoute, translator.BuildConsulTCPRouteTranslator(ctx)).Events()}, + &source.Channel{Source: c.Subscribe(ctx, api.TCPRoute, r.transformConsulTCPRoute(ctx)).Events()}, &handler.EnqueueRequestForObject{}, ). Watches( // Subscribe to changes from Consul for InlineCertificates - &source.Channel{Source: c.Subscribe(ctx, api.InlineCertificate, translator.BuildConsulInlineCertificateTranslator(ctx, r.transformSecret)).Events()}, + &source.Channel{Source: c.Subscribe(ctx, api.InlineCertificate, r.transformConsulInlineCertificate(ctx)).Events()}, &handler.EnqueueRequestForObject{}, ).Complete(r) } -func serviceToNamespacedName(s *api.CatalogService) types.NamespacedName { - return types.NamespacedName{ - Namespace: s.ServiceMeta[constants.MetaKeyKubeNS], - Name: s.ServiceMeta[constants.MetaKeyKubeServiceName], - } -} - // transformGatewayClass will check the list of GatewayClass objects for a matching // class, then return a list of reconcile Requests for it. func (r *GatewayController) transformGatewayClass(ctx context.Context) func(o client.Object) []reconcile.Request { @@ -465,7 +408,7 @@ func (r *GatewayController) transformGatewayClass(ctx context.Context) func(o cl }); err != nil { return nil } - return objectsToRequests(pointersOf(gatewayList.Items)) + return common.ObjectsToReconcileRequests(pointersOf(gatewayList.Items)) } } @@ -474,7 +417,7 @@ func (r *GatewayController) transformGatewayClass(ctx context.Context) func(o cl func (r *GatewayController) transformHTTPRoute(ctx context.Context) func(o client.Object) []reconcile.Request { return func(o client.Object) []reconcile.Request { route := o.(*gwv1beta1.HTTPRoute) - return refsToRequests(parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs)) + return refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs)) } } @@ -483,7 +426,7 @@ func (r *GatewayController) transformHTTPRoute(ctx context.Context) func(o clien func (r *GatewayController) transformTCPRoute(ctx context.Context) func(o client.Object) []reconcile.Request { return func(o client.Object) []reconcile.Request { route := o.(*gwv1alpha2.TCPRoute) - return refsToRequests(parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs)) + return refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs)) } } @@ -498,7 +441,7 @@ func (r *GatewayController) transformSecret(ctx context.Context) func(o client.O }); err != nil { return nil } - return objectsToRequests(pointersOf(gatewayList.Items)) + return common.ObjectsToReconcileRequests(pointersOf(gatewayList.Items)) } } @@ -506,140 +449,153 @@ func (r *GatewayController) transformSecret(ctx context.Context) func(o client.O // class, then return a list of reconcile Requests for Gateways referring to it. func (r *GatewayController) transformReferenceGrant(ctx context.Context) func(o client.Object) []reconcile.Request { return func(o client.Object) []reconcile.Request { - // just reconcile all gateways within the namespace - grant := o.(*gwv1beta1.ReferenceGrant) + // just re-reconcile all gateways for now ideally this will filter down to gateways + // affected, but technically the blast radius is gateways in the namespace + referencing + // the namespace + the routes that bind to them. gatewayList := &gwv1beta1.GatewayList{} - if err := r.Client.List(ctx, gatewayList, &client.ListOptions{ - Namespace: grant.Namespace, - }); err != nil { + if err := r.Client.List(ctx, gatewayList); err != nil { return nil } - return objectsToRequests(pointersOf(gatewayList.Items)) + + return common.ObjectsToReconcileRequests(pointersOf(gatewayList.Items)) } } -// transformConsulService will return a list of gateways that are referenced -// by a TCPRoute or HTTPRoute that references the Consul service. -func (r *GatewayController) transformConsulService(ctx context.Context) func(service *api.CatalogService) []types.NamespacedName { - return func(service *api.CatalogService) []types.NamespacedName { - nsn := serviceToNamespacedName(service) +// transformMeshService will return a list of gateways that are referenced +// by a TCPRoute or HTTPRoute that references the mesh service. +func (r *GatewayController) transformMeshService(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + service := o.(*v1alpha1.MeshService) + key := client.ObjectKeyFromObject(service).String() - if nsn.Namespace != "" && nsn.Name != "" { - key := nsn.String() + return r.gatewaysForRoutesReferencing(ctx, TCPRoute_MeshServiceIndex, HTTPRoute_MeshServiceIndex, key) + } +} - requestSet := make(map[types.NamespacedName]struct{}) - tcpRouteList := &gwv1alpha2.TCPRouteList{} - if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(TCPRoute_ServiceIndex, key), - }); err != nil { - r.Log.Error(err, "unable to list TCPRoutes") - } - for _, route := range tcpRouteList.Items { - for _, ref := range parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs) { - requestSet[ref] = struct{}{} - } - } +// transformConsulGateway will return a list of gateways that this corresponds to. +func (r *GatewayController) transformConsulGateway(entry api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{common.EntryToNamespacedName(entry)} +} - httpRouteList := &gwv1alpha2.HTTPRouteList{} - if err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(HTTPRoute_ServiceIndex, key), - }); err != nil { - r.Log.Error(err, "unable to list HTTPRoutes") - } - for _, route := range httpRouteList.Items { - for _, ref := range parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs) { - requestSet[ref] = struct{}{} - } - } +// transformConsulHTTPRoute will return a list of gateways that need to be reconciled. +func (r *GatewayController) transformConsulHTTPRoute(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { + return func(entry api.ConfigEntry) []types.NamespacedName { + parents := mapset.NewSet() + for _, parent := range entry.(*api.HTTPRouteConfigEntry).Parents { + parents.Add(api.ResourceReference{ + Kind: parent.Kind, + Name: parent.Name, + Namespace: parent.Namespace, + Partition: parent.Partition, + }) + } - requests := []types.NamespacedName{} - for request := range requestSet { - requests = append(requests, request) + var gateways []types.NamespacedName + for parent := range parents.Iter() { + if gateway := r.cache.Get(parent.(api.ResourceReference)); gateway != nil { + gateways = append(gateways, common.EntryToNamespacedName(gateway)) } - return requests } - - return nil + return gateways } } -// transformConsulPeering will return a list of gateways that are referenced -// by a TCPRoute or HTTPRoute that references the Consul peering. -func (r *GatewayController) transformConsulPeering(ctx context.Context) func(service *api.Peering) []types.NamespacedName { - return func(peering *api.Peering) []types.NamespacedName { - meshServiceList := &v1alpha1.MeshServiceList{} - - if err := r.Client.List(ctx, meshServiceList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(MeshService_PeerIndex, peering.Name), - }); err != nil { - r.Log.Error(err, "unable to list TCPRoutes") +func (r *GatewayController) transformConsulTCPRoute(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { + return func(entry api.ConfigEntry) []types.NamespacedName { + parents := mapset.NewSet() + for _, parent := range entry.(*api.TCPRouteConfigEntry).Parents { + parents.Add(api.ResourceReference{ + Kind: parent.Kind, + Name: parent.Name, + Namespace: parent.Namespace, + Partition: parent.Partition, + }) } - flattened := []types.NamespacedName{} - for _, meshService := range meshServiceList.Items { - for _, request := range r.transformMeshService(ctx)(&meshService) { - flattened = append(flattened, request.NamespacedName) + var gateways []types.NamespacedName + for parent := range parents.Iter() { + if gateway := r.cache.Get(parent.(api.ResourceReference)); gateway != nil { + gateways = append(gateways, common.EntryToNamespacedName(gateway)) } } - - return flattened + return gateways } } -// transformMeshService will return a list of gateways that are referenced -// by a TCPRoute or HTTPRoute that references the mesh service. -func (r *GatewayController) transformMeshService(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - service := o.(*v1alpha1.MeshService) - key := client.ObjectKeyFromObject(service).String() - - requestSet := make(map[types.NamespacedName]struct{}) - - tcpRouteList := &gwv1alpha2.TCPRouteList{} - if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(TCPRoute_MeshServiceIndex, key), - }); err != nil { - r.Log.Error(err, "unable to list TCPRoutes") +func (r *GatewayController) transformConsulInlineCertificate(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { + return func(entry api.ConfigEntry) []types.NamespacedName { + certificateKey := api.ResourceReference{ + Kind: entry.GetKind(), + Name: entry.GetName(), + Namespace: entry.GetNamespace(), + Partition: entry.GetPartition(), } - for _, route := range tcpRouteList.Items { - for _, ref := range parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs) { - requestSet[ref] = struct{}{} + + var gateways []types.NamespacedName + for _, entry := range r.cache.List(api.APIGateway) { + gateway := entry.(*api.APIGatewayConfigEntry) + if gatewayReferencesCertificate(certificateKey, gateway) { + gateways = append(gateways, common.EntryToNamespacedName(gateway)) } } - httpRouteList := &gwv1beta1.HTTPRouteList{} - if err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(HTTPRoute_MeshServiceIndex, key), - }); err != nil { - r.Log.Error(err, "unable to list HTTPRoutes") - } - for _, route := range httpRouteList.Items { - for _, ref := range parentRefs(gwv1beta1.GroupVersion.Group, kindGateway, route.Namespace, route.Spec.ParentRefs) { - requestSet[ref] = struct{}{} + return gateways + } +} + +func gatewayReferencesCertificate(certificateKey api.ResourceReference, gateway *api.APIGatewayConfigEntry) bool { + for _, listener := range gateway.Listeners { + for _, cert := range listener.TLS.Certificates { + if cert == certificateKey { + return true } } + } + return false +} - requests := []reconcile.Request{} - for request := range requestSet { - requests = append(requests, reconcile.Request{NamespacedName: request}) - } - return requests +// transformEndpoints will return a list of gateways that are referenced +// by a TCPRoute or HTTPRoute that references the service. +func (r *GatewayController) transformEndpoints(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + key := client.ObjectKeyFromObject(o).String() + + return r.gatewaysForRoutesReferencing(ctx, TCPRoute_ServiceIndex, HTTPRoute_ServiceIndex, key) } } -// objectsToRequests takes a list of objects and returns a list of -// reconcile Requests. -func objectsToRequests[T metav1.Object](objects []T) []reconcile.Request { - requests := make([]reconcile.Request, 0, len(objects)) +// gatewaysForRoutesReferencing returns a mapping of all gateways that are referenced by routes that +// have a backend associated with the given key and index. +func (r *GatewayController) gatewaysForRoutesReferencing(ctx context.Context, tcpIndex, httpIndex, key string) []reconcile.Request { + requestSet := make(map[types.NamespacedName]struct{}) - // TODO: is it possible to receive empty objects? - for _, object := range objects { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: object.GetNamespace(), - Name: object.GetName(), - }, - }) + tcpRouteList := &gwv1alpha2.TCPRouteList{} + if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(tcpIndex, key), + }); err != nil { + r.Log.Error(err, "unable to list TCPRoutes") + } + for _, route := range tcpRouteList.Items { + for _, ref := range common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs) { + requestSet[ref] = struct{}{} + } + } + + httpRouteList := &gwv1beta1.HTTPRouteList{} + if err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(httpIndex, key), + }); err != nil { + r.Log.Error(err, "unable to list HTTPRoutes") + } + for _, route := range httpRouteList.Items { + for _, ref := range common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs) { + requestSet[ref] = struct{}{} + } + } + + requests := []reconcile.Request{} + for request := range requestSet { + requests = append(requests, reconcile.Request{NamespacedName: request}) } return requests } @@ -670,110 +626,364 @@ func refsToRequests(objects []types.NamespacedName) []reconcile.Request { return requests } -// parentRefs takes a list of ParentReference objects and returns a list of NamespacedName objects. -func parentRefs(group, kind, namespace string, refs []gwv1beta1.ParentReference) []types.NamespacedName { - indexed := make([]types.NamespacedName, 0, len(refs)) - for _, parent := range refs { - if nilOrEqual(parent.Group, group) && nilOrEqual(parent.Kind, kind) { - indexed = append(indexed, indexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace)) - } +// kubernetes helpers + +func (c *GatewayController) getNamespaces(ctx context.Context) (map[string]corev1.Namespace, error) { + var list corev1.NamespaceList + + if err := c.Client.List(ctx, &list); err != nil { + return nil, err } - return indexed + namespaces := map[string]corev1.Namespace{} + for _, namespace := range list.Items { + namespaces[namespace.Name] = namespace + } + + return namespaces, nil } -func nilOrEqual[T ~string](v *T, check string) bool { - return v == nil || string(*v) == check +func (c *GatewayController) getReferenceGrants(ctx context.Context) ([]gwv1beta1.ReferenceGrant, error) { + var list gwv1beta1.ReferenceGrantList + + if err := c.Client.List(ctx, &list); err != nil { + return nil, err + } + + return list.Items, nil } -func indexedNamespacedNameWithDefault[T ~string, U ~string, V ~string](t T, u *U, v V) types.NamespacedName { - return types.NamespacedName{ - Namespace: derefStringOr(u, v), - Name: string(t), +func (c *GatewayController) getDeployedGatewayService(ctx context.Context, gateway types.NamespacedName) (*corev1.Service, error) { + service := &corev1.Service{} + + // we use the implicit association of a service name/namespace with a corresponding gateway + if err := c.Client.Get(ctx, gateway, service); err != nil { + return nil, client.IgnoreNotFound(err) } + + return service, nil } -func derefStringOr[T ~string, U ~string](v *T, val U) string { - if v == nil { - return string(val) +func (c *GatewayController) getDeployedGatewayPods(ctx context.Context, gateway gwv1beta1.Gateway) ([]corev1.Pod, error) { + labels := common.LabelsForGateway(&gateway) + + var list corev1.PodList + + if err := c.Client.List(ctx, &list, client.MatchingLabels(labels)); err != nil { + return nil, err } - return string(*v) + + return list.Items, nil } -func (r *GatewayController) getAllRefsForGateway(ctx context.Context, gw *gwv1beta1.Gateway) ([]metav1.Object, error) { - objs := make([]metav1.Object, 0) +func (c *GatewayController) getRelatedHTTPRoutes(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]gwv1beta1.HTTPRoute, error) { + var list gwv1beta1.HTTPRouteList - // handle http routes - httpRouteList := &gwv1beta1.HTTPRouteList{} - err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(HTTPRoute_GatewayIndex, types.NamespacedName{Name: gw.Name, Namespace: gw.Namespace}.String()), - }) - if err != nil { + if err := c.Client.List(ctx, &list, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(HTTPRoute_GatewayIndex, gateway.String()), + }); err != nil { return nil, err } - for _, route := range httpRouteList.Items { - objs = append(objs, &route) + + for _, route := range list.Items { + resources.ReferenceCountHTTPRoute(route) } - // handle tcp routes - tcpRouteList := &v1alpha2.TCPRouteList{} - err = r.Client.List(ctx, tcpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(TCPRoute_GatewayIndex, types.NamespacedName{Name: gw.Name, Namespace: gw.Namespace}.String()), - }) - if err != nil { + + return list.Items, nil +} + +func (c *GatewayController) getRelatedTCPRoutes(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]gwv1alpha2.TCPRoute, error) { + var list gwv1alpha2.TCPRouteList + + if err := c.Client.List(ctx, &list, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(TCPRoute_GatewayIndex, gateway.String()), + }); err != nil { return nil, err } - for _, route := range tcpRouteList.Items { - objs = append(objs, &route) - } - // handle secrets - for _, listener := range gw.Spec.Listeners { - for _, secret := range listener.TLS.CertificateRefs { - secretObj := &corev1.Secret{} - err = r.Client.Get(ctx, indexedNamespacedNameWithDefault(secret.Name, secret.Namespace, gw.Namespace), secretObj) - if err != nil { - continue - } - objs = append(objs, secretObj) - } + for _, route := range list.Items { + resources.ReferenceCountTCPRoute(route) } - return objs, nil + return list.Items, nil } -// getConfigForGatewayClass returns the relevant GatewayClassConfig for the GatewayClass. -func getConfigForGatewayClass(ctx context.Context, client client.Client, gwc *gwv1beta1.GatewayClass) (*v1alpha1.GatewayClassConfig, error) { - if gwc == nil { +func (c *GatewayController) getConfigForGatewayClass(ctx context.Context, gatewayClassConfig *gwv1beta1.GatewayClass) (*v1alpha1.GatewayClassConfig, error) { + if gatewayClassConfig == nil { // if we don't have a gateway class we can't fetch the corresponding config return nil, nil } config := &v1alpha1.GatewayClassConfig{} - if ref := gwc.Spec.ParametersRef; ref != nil { + if ref := gatewayClassConfig.Spec.ParametersRef; ref != nil { if string(ref.Group) != v1alpha1.GroupVersion.Group || ref.Kind != v1alpha1.GatewayClassConfigKind || - gwc.Spec.ControllerName != GatewayClassControllerName { + gatewayClassConfig.Spec.ControllerName != GatewayClassControllerName { // we don't have supported params, so return nil return nil, nil } - err := client.Get(ctx, types.NamespacedName{Name: ref.Name}, config) - if err != nil { - if k8serrors.IsNotFound(err) { - return nil, nil - } - return nil, err + if err := c.Client.Get(ctx, types.NamespacedName{Name: ref.Name}, config); err != nil { + return nil, client.IgnoreNotFound(err) } } return config, nil } -func consulServicesForGateway(gateway gwv1beta1.Gateway, services []api.CatalogService) []api.CatalogService { - filtered := []api.CatalogService{} - for _, service := range services { - kubeService := serviceToNamespacedName(&service) - if gateway.Name == kubeService.Name && gateway.Namespace == kubeService.Namespace { - filtered = append(filtered, service) +func (c *GatewayController) getGatewayClassForGateway(ctx context.Context, gateway gwv1beta1.Gateway) (*gwv1beta1.GatewayClass, error) { + var gatewayClass gwv1beta1.GatewayClass + if err := c.Client.Get(ctx, types.NamespacedName{Name: string(gateway.Spec.GatewayClassName)}, &gatewayClass); err != nil { + return nil, client.IgnoreNotFound(err) + } + return &gatewayClass, nil +} + +// resource map construction routines + +func (c *GatewayController) fetchControlledGateways(ctx context.Context, resources *common.ResourceMap) error { + set := mapset.NewSet() + + list := gwv1beta1.GatewayClassList{} + if err := c.Client.List(ctx, &list, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(GatewayClass_ControllerNameIndex, GatewayClassControllerName), + }); err != nil { + return err + } + for _, gatewayClass := range list.Items { + set.Add(gatewayClass.Name) + } + + gateways := &gwv1beta1.GatewayList{} + if err := c.Client.List(ctx, gateways); err != nil { + return err + } + + for _, gateway := range gateways.Items { + if set.Contains(string(gateway.Spec.GatewayClassName)) { + resources.ReferenceCountGateway(gateway) + } + } + return nil +} + +func (c *GatewayController) fetchCertificatesForGateway(ctx context.Context, resources *common.ResourceMap, gateway gwv1beta1.Gateway) error { + certificates := mapset.NewSet() + + for _, listener := range gateway.Spec.Listeners { + if listener.TLS != nil { + for _, cert := range listener.TLS.CertificateRefs { + if common.NilOrEqual(cert.Group, "") && common.NilOrEqual(cert.Kind, common.KindSecret) { + certificates.Add(common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace)) + } + } + } + } + + for key := range certificates.Iter() { + if err := c.fetchSecret(ctx, resources, key.(types.NamespacedName)); err != nil { + return err + } + } + + return nil +} + +func (c *GatewayController) fetchSecret(ctx context.Context, resources *common.ResourceMap, key types.NamespacedName) error { + var secret corev1.Secret + if err := c.Client.Get(ctx, key, &secret); err != nil { + return client.IgnoreNotFound(err) + } + + resources.ReferenceCountCertificate(secret) + + return nil +} + +func (c *GatewayController) fetchServicesForRoutes(ctx context.Context, resources *common.ResourceMap, tcpRoutes []gwv1alpha2.TCPRoute, httpRoutes []gwv1beta1.HTTPRoute) error { + serviceBackends := mapset.NewSet() + meshServiceBackends := mapset.NewSet() + + for _, route := range httpRoutes { + for _, rule := range route.Spec.Rules { + for _, backend := range rule.BackendRefs { + if common.DerefEqual(backend.Group, v1alpha1.ConsulHashicorpGroup) && + common.DerefEqual(backend.Kind, v1alpha1.MeshServiceKind) { + meshServiceBackends.Add(common.IndexedNamespacedNameWithDefault(backend.Name, backend.Namespace, route.Namespace)) + } else if common.NilOrEqual(backend.Group, "") && common.NilOrEqual(backend.Kind, "Service") { + serviceBackends.Add(common.IndexedNamespacedNameWithDefault(backend.Name, backend.Namespace, route.Namespace)) + } + } + } + } + + for _, route := range tcpRoutes { + for _, rule := range route.Spec.Rules { + for _, backend := range rule.BackendRefs { + if common.DerefEqual(backend.Group, v1alpha1.ConsulHashicorpGroup) && + common.DerefEqual(backend.Kind, v1alpha1.MeshServiceKind) { + meshServiceBackends.Add(common.IndexedNamespacedNameWithDefault(backend.Name, backend.Namespace, route.Namespace)) + } else if common.NilOrEqual(backend.Group, "") && common.NilOrEqual(backend.Kind, "Service") { + serviceBackends.Add(common.IndexedNamespacedNameWithDefault(backend.Name, backend.Namespace, route.Namespace)) + } + } + } + } + + for key := range meshServiceBackends.Iter() { + if err := c.fetchMeshService(ctx, resources, key.(types.NamespacedName)); err != nil { + return err + } + } + + for key := range serviceBackends.Iter() { + if err := c.fetchServicesForEndpoints(ctx, resources, key.(types.NamespacedName)); err != nil { + return err + } + } + return nil +} + +func (c *GatewayController) fetchMeshService(ctx context.Context, resources *common.ResourceMap, key types.NamespacedName) error { + var service v1alpha1.MeshService + if err := c.Client.Get(ctx, key, &service); err != nil { + return client.IgnoreNotFound(err) + } + + resources.AddMeshService(service) + + return nil +} + +func (c *GatewayController) fetchServicesForEndpoints(ctx context.Context, resources *common.ResourceMap, key types.NamespacedName) error { + if shouldIgnore(key.Namespace, c.denyK8sNamespacesSet, c.allowK8sNamespacesSet) { + return nil + } + + var endpoints corev1.Endpoints + if err := c.Client.Get(ctx, key, &endpoints); err != nil { + return client.IgnoreNotFound(err) + } + + if isLabeledIgnore(endpoints.Labels) { + return nil + } + + for _, subset := range endpoints.Subsets { + for _, address := range subset.Addresses { + if address.TargetRef != nil && address.TargetRef.Kind == "Pod" { + objectKey := types.NamespacedName{Name: address.TargetRef.Name, Namespace: address.TargetRef.Namespace} + + var pod corev1.Pod + if err := c.Client.Get(ctx, objectKey, &pod); err != nil { + if k8serrors.IsNotFound(err) { + continue + } + return err + } + + resources.AddService(key, serviceName(pod, endpoints)) + } + } + } + + return nil +} + +// cache routines + +func (c *GatewayController) getConsulServices(ref api.ResourceReference) []api.CatalogService { + return c.gatewayCache.ServicesFor(ref) +} + +func (c *GatewayController) getConsulGateway(ref api.ResourceReference) *api.APIGatewayConfigEntry { + if entry := c.cache.Get(ref); entry != nil { + return entry.(*api.APIGatewayConfigEntry) + } + return nil +} + +func (c *GatewayController) getConsulHTTPRoutes(ref api.ResourceReference, resources *common.ResourceMap) []api.HTTPRouteConfigEntry { + var filtered []api.HTTPRouteConfigEntry + + for _, route := range configEntriesTo[*api.HTTPRouteConfigEntry](c.cache.List(api.HTTPRoute)) { + if routeReferencesGateway(route.Namespace, ref, route.Parents) { + filtered = append(filtered, *route) + resources.ReferenceCountConsulHTTPRoute(*route) + } + } + return filtered +} + +func (c *GatewayController) getConsulTCPRoutes(ref api.ResourceReference, resources *common.ResourceMap) []api.TCPRouteConfigEntry { + var filtered []api.TCPRouteConfigEntry + + for _, route := range configEntriesTo[*api.TCPRouteConfigEntry](c.cache.List(api.TCPRoute)) { + if routeReferencesGateway(route.Namespace, ref, route.Parents) { + filtered = append(filtered, *route) + resources.ReferenceCountConsulTCPRoute(*route) } } return filtered } + +func (c *GatewayController) getConsulInlineCertificates() []api.InlineCertificateConfigEntry { + var filtered []api.InlineCertificateConfigEntry + + for _, cert := range configEntriesTo[*api.InlineCertificateConfigEntry](c.cache.List(api.InlineCertificate)) { + filtered = append(filtered, *cert) + } + return filtered +} + +func routeReferencesGateway(namespace string, ref api.ResourceReference, refs []api.ResourceReference) bool { + // we don't need to check partition here since they're all in the same partition + if namespace == "" { + namespace = "default" + } + + for _, parent := range refs { + if common.EmptyOrEqual(parent.Kind, api.APIGateway) { + if common.DefaultOrEqual(parent.Namespace, namespace, ref.Namespace) && + parent.Name == ref.Name { + return true + } + } + } + + return false +} + +func serviceName(pod corev1.Pod, serviceEndpoints corev1.Endpoints) string { + svcName := serviceEndpoints.Name + // If the annotation has a comma, it is a multi port Pod. In that case we always use the name of the endpoint. + if serviceNameFromAnnotation, ok := pod.Annotations[constants.AnnotationService]; ok && serviceNameFromAnnotation != "" && !strings.Contains(serviceNameFromAnnotation, ",") { + svcName = serviceNameFromAnnotation + } + return svcName +} + +func isLabeledIgnore(labels map[string]string) bool { + value, labelExists := labels[constants.LabelServiceIgnore] + shouldIgnore, err := strconv.ParseBool(value) + + return shouldIgnore && labelExists && err == nil +} + +// shouldIgnore ignores namespaces where we don't connect-inject. +func shouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { + // Ignores system namespaces. + if namespace == metav1.NamespaceSystem || namespace == metav1.NamespacePublic || namespace == "local-path-storage" { + return true + } + + // Ignores deny list. + if denySet.Contains(namespace) { + return true + } + + // Ignores if not in allow list or allow list is not *. + if !allowSet.Contains("*") && !allowSet.Contains(namespace) { + return true + } + + return false +} diff --git a/control-plane/api-gateway/controllers/gateway_controller_test.go b/control-plane/api-gateway/controllers/gateway_controller_test.go deleted file mode 100644 index d04a0b2e44..0000000000 --- a/control-plane/api-gateway/controllers/gateway_controller_test.go +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - - appsv1 "k8s.io/api/apps/v1" - rbac "k8s.io/api/rbac/v1" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul/api" -) - -const ( - TestGatewayClassConfigName = "test-gateway-class-config" - TestAnnotationConfigKey = "api-gateway.consul.hashicorp.com/config" - TestGatewayClassName = "test-gateway-class" - TestGatewayName = "test-gateway" - TestNamespace = "test-namespace" -) - -func stubConsulCache(t *testing.T) *cache.Cache { - t.Helper() - - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/v1/acl/policies": - fmt.Fprintln(w, `[]`) - case "/v1/acl/tokens": - fmt.Fprintln(w, `[]`) - case "/v1/config": - fmt.Fprintln(w, `[]`) - case "/v1/catalog/services": - fmt.Fprintln(w, `{}`) - default: - w.WriteHeader(500) - fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) - } - })) - t.Cleanup(consulServer.Close) - - serverURL, err := url.Parse(consulServer.URL) - require.NoError(t, err) - - port, err := strconv.Atoi(serverURL.Port()) - require.NoError(t, err) - - return cache.New(cache.Config{ - ConsulClientConfig: &consul.Config{ - APIClientConfig: &api.Config{}, - HTTPPort: port, - GRPCPort: port, - APITimeout: 0, - }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), - NamespacesEnabled: false, - Logger: logrtest.New(t), - }) -} - -func TestGatewayReconcileGatekeeperUpdates(t *testing.T) { - t.Parallel() - - namespace := "test-namespace" - name := "test-gateway" - - req := ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: namespace, - Name: name, - }, - } - - basicGatewayClass, basicGatewayClassConfig := getBasicGatewayClassAndConfig() - - cases := map[string]struct { - gateway *gwv1beta1.Gateway - k8sObjects []runtime.Object - expectedError error - }{ - "successful update of gateway": { - gateway: &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{gatewayFinalizer}, - Annotations: map[string]string{ - TestAnnotationConfigKey: `{"serviceType":"serviceType"}`, - }, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: TestGatewayClassName, - }, - }, - k8sObjects: []runtime.Object{ - &basicGatewayClass, - &basicGatewayClassConfig, - }, - expectedError: nil, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - require.NoError(t, gwv1alpha2.AddToScheme(s)) - require.NoError(t, rbac.AddToScheme(s)) - require.NoError(t, corev1.AddToScheme(s)) - require.NoError(t, appsv1.AddToScheme(s)) - - objs := tc.k8sObjects - if tc.gateway != nil { - objs = append(objs, tc.gateway) - } - - fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...)).Build() - - r := &GatewayController{ - cache: stubConsulCache(t), - Client: fakeClient, - Log: logrtest.New(t), - } - - _, err := r.Reconcile(context.Background(), req) - - require.Equal(t, tc.expectedError, err) - deployment := appsv1.Deployment{} - r.Client.Get(context.TODO(), types.NamespacedName{ - Namespace: TestNamespace, - Name: TestGatewayName, - }, &deployment) - require.NotEmpty(t, deployment) - require.Equal(t, TestGatewayName, deployment.ObjectMeta.Name) - }) - } -} - -func TestGatewayReconcileGatekeeperDeletes(t *testing.T) { - t.Parallel() - - req := ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: TestNamespace, - Name: TestGatewayName, - }, - } - - basicGatewayClass, basicGatewayClassConfig := getBasicGatewayClassAndConfig() - cases := map[string]struct { - gateway *gwv1beta1.Gateway - k8sObjects []runtime.Object - expectedError error - }{ - "successful change of gatewayclass on gateway": { - gateway: &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: TestNamespace, - Name: TestGatewayName, - Finalizers: []string{gatewayFinalizer}, - Annotations: map[string]string{ - TestAnnotationConfigKey: `{"serviceType":"serviceType"}`, - }, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: TestGatewayClassName, - }, - }, - k8sObjects: []runtime.Object{ - &basicGatewayClass, - &basicGatewayClassConfig, - }, - expectedError: nil, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - require.NoError(t, gwv1alpha2.AddToScheme(s)) - require.NoError(t, rbac.AddToScheme(s)) - require.NoError(t, corev1.AddToScheme(s)) - require.NoError(t, appsv1.AddToScheme(s)) - - objs := tc.k8sObjects - if tc.gateway != nil { - objs = append(objs, tc.gateway) - } - - fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...)).Build() - - r := &GatewayController{ - cache: stubConsulCache(t), - Client: fakeClient, - Log: logrtest.New(t), - } - - _, err := r.Reconcile(context.Background(), req) - - require.Equal(t, tc.expectedError, err) - deployment := appsv1.Deployment{} - r.Client.Get(context.TODO(), types.NamespacedName{ - Namespace: TestNamespace, - Name: TestGatewayName, - }, &deployment) - require.NotEmpty(t, deployment) - require.Equal(t, TestGatewayName, deployment.ObjectMeta.Name) - }) - } -} - -func TestObjectsToRequests(t *testing.T) { - t.Parallel() - - name := "test-gatewayclass" - - namespacedName := types.NamespacedName{ - Namespace: TestNamespace, - Name: name, - } - - cases := map[string]struct { - objects []metav1.Object - expectedResult []reconcile.Request - }{ - "successful conversion of gateway to request": { - objects: []metav1.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: TestNamespace, - Name: name, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: GatewayClassControllerName, - }, - }, - }, - expectedResult: []reconcile.Request{ - { - NamespacedName: namespacedName, - }, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - requests := objectsToRequests(tc.objects) - - require.Equal(t, tc.expectedResult, requests) - }) - } -} - -func TestGatewayController_getAllRefsForGateway(t *testing.T) { - t.Parallel() - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, gwv1alpha2.Install(s)) - require.NoError(t, corev1.AddToScheme(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - secret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "secret squirrel", - }, - } - gw := &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gw", - Annotations: map[string]string{}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: "", - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - { - Kind: pointerTo(gwv1beta1.Kind("Secret")), - Name: "secret squirrel", - }, - }, - Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{}, - }, - AllowedRoutes: &gwv1beta1.AllowedRoutes{}, - }, - }, - Addresses: []gwv1beta1.GatewayAddress{}, - }, - Status: gwv1beta1.GatewayStatus{}, - } - gwc := &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gw-class", - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "", - ParametersRef: &gwv1beta1.ParametersReference{ - Group: Group, - Kind: v1alpha1.GatewayClassConfigKind, - Name: "the config", - }, - Description: new(string), - }, - } - gwcConfig := &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "the config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: pointerTo(corev1.ServiceType("serviceType")), - NodeSelector: map[string]string{ - "selector": "of node", - }, - Tolerations: []v1.Toleration{ - { - Key: "key", - Operator: "op", - Value: "120", - Effect: "to the moon", - TolerationSeconds: new(int64), - }, - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ - Service: []string{"service"}, - }, - }, - } - - httpRouteOnGateway := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "route 1", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Name: gwv1beta1.ObjectName(gw.Name), - }, - }, - }, - }, - } - - httpRouteNotOnGateway := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "route not on gateway", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Name: gwv1beta1.ObjectName("not on the gateway"), - }, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{}, - } - - tcpRoute := &gwv1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp route", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Name: gwv1beta1.ObjectName(gw.Name), - }, - }, - }, - }, - } - - objs := []runtime.Object{gw, gwc, gwcConfig, httpRouteOnGateway, httpRouteNotOnGateway, tcpRoute, secret} - - fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...)).Build() - controller := GatewayController{ - Client: fakeClient, - } - - ctx := context.Background() - - actual, err := controller.getAllRefsForGateway(ctx, gw) - - require.NoError(t, err) - expectedEntries := []metav1.Object{httpRouteOnGateway, tcpRoute, secret} - - require.ElementsMatch(t, expectedEntries, actual) -} - -func getBasicGatewayClassAndConfig() (gwv1beta1.GatewayClass, v1alpha1.GatewayClassConfig) { - serviceType := corev1.ServiceType("NodePort") - basicGatewayClass := gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: TestGatewayClassName, - Finalizers: []string{ - gatewayClassFinalizer, - }, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: GatewayClassControllerName, - ParametersRef: &gwv1beta1.ParametersReference{ - Group: "consul.hashicorp.com", - Kind: "GatewayClassConfig", - Name: TestGatewayClassConfigName, - Namespace: nil, - }, - }, - } - - basicGatewayClassConfig := v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: TestGatewayClassConfigName, - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: &serviceType, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ - Service: []string{"serviceType"}, - }, - }, - } - - return basicGatewayClass, basicGatewayClassConfig -} diff --git a/control-plane/api-gateway/controllers/index.go b/control-plane/api-gateway/controllers/index.go index 3ef0e65c81..cff6dfac12 100644 --- a/control-plane/api-gateway/controllers/index.go +++ b/control-plane/api-gateway/controllers/index.go @@ -11,6 +11,7 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" 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" ) @@ -149,9 +150,9 @@ func gatewayForSecret(o client.Object) []string { continue } for _, cert := range listener.TLS.CertificateRefs { - if nilOrEqual(cert.Group, "") && nilOrEqual(cert.Kind, "Secret") { + if common.NilOrEqual(cert.Group, "") && common.NilOrEqual(cert.Kind, "Secret") { // If an explicit Secret namespace is not provided, use the Gateway namespace to lookup the provided Secret Name. - secretReferences = append(secretReferences, indexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace).String()) + secretReferences = append(secretReferences, common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace).String()) } } } @@ -174,8 +175,8 @@ func servicesForHTTPRoute(o client.Object) []string { for _, rule := range route.Spec.Rules { BACKEND_LOOP: for _, ref := range rule.BackendRefs { - if nilOrEqual(ref.Group, "") && nilOrEqual(ref.Kind, "Service") { - backendRef := indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() + if common.NilOrEqual(ref.Group, "") && common.NilOrEqual(ref.Kind, "Service") { + backendRef := common.IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() for _, member := range refs { if member == backendRef { continue BACKEND_LOOP @@ -194,9 +195,8 @@ func meshServicesForHTTPRoute(o client.Object) []string { for _, rule := range route.Spec.Rules { BACKEND_LOOP: for _, ref := range rule.BackendRefs { - if ref.Group != nil && string(*ref.Group) == v1alpha1.ConsulHashicorpGroup && - ref.Kind != nil && string(*ref.Kind) == v1alpha1.MeshServiceKind { - backendRef := indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() + if common.DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && common.DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) { + backendRef := common.IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() for _, member := range refs { if member == backendRef { continue BACKEND_LOOP @@ -215,8 +215,8 @@ func servicesForTCPRoute(o client.Object) []string { for _, rule := range route.Spec.Rules { BACKEND_LOOP: for _, ref := range rule.BackendRefs { - if nilOrEqual(ref.Group, "") && nilOrEqual(ref.Kind, "Service") { - backendRef := indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() + if common.NilOrEqual(ref.Group, "") && common.NilOrEqual(ref.Kind, common.KindService) { + backendRef := common.IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() for _, member := range refs { if member == backendRef { continue BACKEND_LOOP @@ -235,9 +235,8 @@ func meshServicesForTCPRoute(o client.Object) []string { for _, rule := range route.Spec.Rules { BACKEND_LOOP: for _, ref := range rule.BackendRefs { - if ref.Group != nil && string(*ref.Group) == v1alpha1.ConsulHashicorpGroup && - ref.Kind != nil && string(*ref.Kind) == v1alpha1.MeshServiceKind { - backendRef := indexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() + if common.DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && common.DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) { + backendRef := common.IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() for _, member := range refs { if member == backendRef { continue BACKEND_LOOP @@ -253,9 +252,9 @@ func meshServicesForTCPRoute(o client.Object) []string { func gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference) []string { var references []string for _, parent := range refs { - if nilOrEqual(parent.Group, gwv1beta1.GroupVersion.Group) && nilOrEqual(parent.Kind, "Gateway") { + if common.NilOrEqual(parent.Group, common.BetaGroup) && common.NilOrEqual(parent.Kind, common.KindGateway) { // If an explicit Gateway namespace is not provided, use the Route namespace to lookup the provided Gateway Namespace. - references = append(references, indexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace).String()) + references = append(references, common.IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace).String()) } } return references diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go index 6ef7cc04ec..90cd186743 100644 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ b/control-plane/api-gateway/gatekeeper/dataplane.go @@ -10,7 +10,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/utils/pointer" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" "k8s.io/apimachinery/pkg/util/intstr" @@ -25,7 +25,7 @@ const ( volumeName = "consul-connect-inject-data" ) -func consulDataplaneContainer(config apigateway.HelmConfig, name, namespace string) (corev1.Container, error) { +func consulDataplaneContainer(config common.HelmConfig, name, namespace string) (corev1.Container, error) { // Extract the service account token's volume mount. var ( err error @@ -113,7 +113,7 @@ func consulDataplaneContainer(config apigateway.HelmConfig, name, namespace stri return container, nil } -func getDataplaneArgs(namespace string, config apigateway.HelmConfig, bearerTokenFile string, name string) ([]string, error) { +func getDataplaneArgs(namespace string, config common.HelmConfig, bearerTokenFile string, name string) ([]string, error) { proxyIDFileName := "/consul/connect-inject/proxyid" envoyConcurrency := defaultEnvoyProxyConcurrency diff --git a/control-plane/api-gateway/gatekeeper/deployment.go b/control-plane/api-gateway/gatekeeper/deployment.go index 0c46dbb0a5..eecfa31349 100644 --- a/control-plane/api-gateway/gatekeeper/deployment.go +++ b/control-plane/api-gateway/gatekeeper/deployment.go @@ -6,10 +6,10 @@ package gatekeeper import ( "context" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "k8s.io/apimachinery/pkg/types" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -24,7 +24,7 @@ const ( defaultInstances int32 = 1 ) -func (g *Gatekeeper) upsertDeployment(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig) error { +func (g *Gatekeeper) upsertDeployment(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { // Get Deployment if it exists. existingDeployment := &appsv1.Deployment{} exists := false @@ -84,7 +84,7 @@ func (g *Gatekeeper) deleteDeployment(ctx context.Context, nsname types.Namespac return err } -func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig, currentReplicas *int32) (*appsv1.Deployment, error) { +func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig, currentReplicas *int32) (*appsv1.Deployment, error) { initContainer, err := initContainer(config, gateway.Name, gateway.Namespace) if err != nil { return nil, err @@ -99,16 +99,16 @@ func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayC ObjectMeta: metav1.ObjectMeta{ Name: gateway.Name, Namespace: gateway.Namespace, - Labels: apigateway.LabelsForGateway(&gateway), + Labels: common.LabelsForGateway(&gateway), }, Spec: appsv1.DeploymentSpec{ Replicas: deploymentReplicas(gcc, currentReplicas), Selector: &metav1.LabelSelector{ - MatchLabels: apigateway.LabelsForGateway(&gateway), + MatchLabels: common.LabelsForGateway(&gateway), }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: apigateway.LabelsForGateway(&gateway), + Labels: common.LabelsForGateway(&gateway), Annotations: map[string]string{ "consul.hashicorp.com/connect-inject": "false", }, @@ -135,7 +135,7 @@ func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayC Weight: 1, PodAffinityTerm: corev1.PodAffinityTerm{ LabelSelector: &metav1.LabelSelector{ - MatchLabels: apigateway.LabelsForGateway(&gateway), + MatchLabels: common.LabelsForGateway(&gateway), }, TopologyKey: "kubernetes.io/hostname", }, diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper.go b/control-plane/api-gateway/gatekeeper/gatekeeper.go index e333b6a8ea..23f96acb3d 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper.go @@ -8,7 +8,7 @@ import ( "fmt" "github.com/go-logr/logr" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -30,7 +30,7 @@ func New(log logr.Logger, client client.Client) *Gatekeeper { } // Upsert creates or updates the resources for handling routing of network traffic. -func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig) error { +func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { g.Log.Info(fmt.Sprintf("Upsert Gateway Deployment %s/%s", gateway.Namespace, gateway.Name)) if err := g.upsertRole(ctx, gateway, gcc, config); err != nil { @@ -83,7 +83,7 @@ func (g Gatekeeper) namespacedName(gateway gwv1beta1.Gateway) types.NamespacedNa } } -func (g Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config apigateway.HelmConfig) string { +func (g Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config common.HelmConfig) string { if config.AuthMethod == "" { return "" } diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go index 92a92d66f2..cc19f68d47 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -9,7 +9,7 @@ import ( "testing" logrtest "github.com/go-logr/logr/testr" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + common "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" @@ -44,7 +44,7 @@ var ( { Name: "Listener 2", Port: 8081, - Protocol: "UDP", + Protocol: "TCP", }, } ) @@ -52,7 +52,7 @@ var ( type testCase struct { gateway gwv1beta1.Gateway gatewayClassConfig v1alpha1.GatewayClassConfig - helmConfig apigateway.HelmConfig + helmConfig common.HelmConfig initialResources resources finalResources resources @@ -85,15 +85,15 @@ func TestUpsert(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(3)), - MaxInstances: ptrTo(int32(3)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{}, + helmConfig: common.HelmConfig{}, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ @@ -120,15 +120,15 @@ func TestUpsert(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(3)), - MaxInstances: ptrTo(int32(3)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{}, + helmConfig: common.HelmConfig{}, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ @@ -144,7 +144,7 @@ func TestUpsert(t *testing.T) { }, { Name: "Listener 2", - Protocol: "UDP", + Protocol: "TCP", Port: 8081, }, }, "1"), @@ -168,15 +168,15 @@ func TestUpsert(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(3)), - MaxInstances: ptrTo(int32(3)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{ + helmConfig: common.HelmConfig{ AuthMethod: "method", }, initialResources: resources{}, @@ -196,7 +196,7 @@ func TestUpsert(t *testing.T) { }, { Name: "Listener 2", - Protocol: "UDP", + Protocol: "TCP", Port: 8081, }, }, "1"), @@ -222,15 +222,15 @@ func TestUpsert(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(3)), - MaxInstances: ptrTo(int32(3)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{ + helmConfig: common.HelmConfig{ AuthMethod: "method", }, initialResources: resources{ @@ -269,7 +269,7 @@ func TestUpsert(t *testing.T) { }, { Name: "Listener 2", - Protocol: "UDP", + Protocol: "TCP", Port: 8081, }, }, "2"), @@ -297,15 +297,15 @@ func TestUpsert(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(3)), - MaxInstances: ptrTo(int32(3)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{ + helmConfig: common.HelmConfig{ AuthMethod: "method", }, initialResources: resources{ @@ -324,7 +324,7 @@ func TestUpsert(t *testing.T) { }, { Name: "Listener 2", - Protocol: "UDP", + Protocol: "TCP", Port: 8081, }, }, "1"), @@ -370,15 +370,15 @@ func TestUpsert(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(5)), - MaxInstances: ptrTo(int32(7)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(5)), + MaxInstances: common.PointerTo(int32(7)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{}, + helmConfig: common.HelmConfig{}, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), @@ -438,15 +438,15 @@ func TestDelete(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(3)), - MaxInstances: ptrTo(int32(3)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{}, + helmConfig: common.HelmConfig{}, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), @@ -475,15 +475,15 @@ func TestDelete(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(3)), - MaxInstances: ptrTo(int32(3)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{}, + helmConfig: common.HelmConfig{}, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), @@ -498,7 +498,7 @@ func TestDelete(t *testing.T) { }, { Name: "Listener 2", - Protocol: "UDP", + Protocol: "TCP", Port: 8081, }, }, "1"), @@ -528,15 +528,15 @@ func TestDelete(t *testing.T) { }, Spec: v1alpha1.GatewayClassConfigSpec{ DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: ptrTo(int32(3)), - MaxInstances: ptrTo(int32(3)), - MinInstances: ptrTo(int32(1)), + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), }, CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(ptrTo("NodePort")), + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: apigateway.HelmConfig{ + helmConfig: common.HelmConfig{ AuthMethod: "method", }, initialResources: resources{ @@ -555,7 +555,7 @@ func TestDelete(t *testing.T) { }, { Name: "Listener 2", - Protocol: "UDP", + Protocol: "TCP", Port: 8081, }, }, "1"), @@ -770,8 +770,8 @@ func configureDeployment(name, namespace string, labels map[string]string, repli APIVersion: "gateway.networking.k8s.io/v1beta1", Kind: "Gateway", Name: name, - Controller: ptrTo(true), - BlockOwnerDeletion: ptrTo(true), + Controller: common.PointerTo(true), + BlockOwnerDeletion: common.PointerTo(true), }, }, }, @@ -828,8 +828,8 @@ func configureRole(name, namespace string, labels map[string]string, resourceVer APIVersion: "gateway.networking.k8s.io/v1beta1", Kind: "Gateway", Name: name, - Controller: ptrTo(true), - BlockOwnerDeletion: ptrTo(true), + Controller: common.PointerTo(true), + BlockOwnerDeletion: common.PointerTo(true), }, }, }, @@ -854,8 +854,8 @@ func configureService(name, namespace string, labels, annotations map[string]str APIVersion: "gateway.networking.k8s.io/v1beta1", Kind: "Gateway", Name: name, - Controller: ptrTo(true), - BlockOwnerDeletion: ptrTo(true), + Controller: common.PointerTo(true), + BlockOwnerDeletion: common.PointerTo(true), }, }, }, @@ -883,14 +883,10 @@ func configureServiceAccount(name, namespace string, labels map[string]string, r APIVersion: "gateway.networking.k8s.io/v1beta1", Kind: "Gateway", Name: name, - Controller: ptrTo(true), - BlockOwnerDeletion: ptrTo(true), + Controller: common.PointerTo(true), + BlockOwnerDeletion: common.PointerTo(true), }, }, }, } } - -func ptrTo[T any](t T) *T { - return &t -} diff --git a/control-plane/api-gateway/gatekeeper/init.go b/control-plane/api-gateway/gatekeeper/init.go index 4f53769c11..831380cb52 100644 --- a/control-plane/api-gateway/gatekeeper/init.go +++ b/control-plane/api-gateway/gatekeeper/init.go @@ -11,7 +11,7 @@ import ( corev1 "k8s.io/api/core/v1" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/namespaces" "k8s.io/utils/pointer" ) @@ -33,7 +33,7 @@ type initContainerCommandData struct { // containerInit 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 initContainer(config apigateway.HelmConfig, name, namespace string) (corev1.Container, error) { +func 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/role.go b/control-plane/api-gateway/gatekeeper/role.go index 604c503be5..688c425f5f 100644 --- a/control-plane/api-gateway/gatekeeper/role.go +++ b/control-plane/api-gateway/gatekeeper/role.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/types" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + "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" @@ -18,7 +18,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ) -func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig) error { +func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { if config.AuthMethod == "" { return g.deleteRole(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) } @@ -74,7 +74,7 @@ func (g *Gatekeeper) role(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassCo ObjectMeta: metav1.ObjectMeta{ Name: gateway.Name, Namespace: gateway.Namespace, - Labels: apigateway.LabelsForGateway(&gateway), + Labels: common.LabelsForGateway(&gateway), }, Rules: []rbac.PolicyRule{}, } diff --git a/control-plane/api-gateway/gatekeeper/service.go b/control-plane/api-gateway/gatekeeper/service.go index 1191879390..149dc067e3 100644 --- a/control-plane/api-gateway/gatekeeper/service.go +++ b/control-plane/api-gateway/gatekeeper/service.go @@ -6,10 +6,10 @@ package gatekeeper import ( "context" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "k8s.io/apimachinery/pkg/types" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -26,7 +26,7 @@ var ( } ) -func (g *Gatekeeper) upsertService(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config apigateway.HelmConfig) error { +func (g *Gatekeeper) upsertService(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { if gcc.Spec.ServiceType == nil { return g.deleteService(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) } @@ -68,8 +68,9 @@ func (g *Gatekeeper) service(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClas ports := []corev1.ServicePort{} for _, listener := range gateway.Spec.Listeners { ports = append(ports, corev1.ServicePort{ - Name: string(listener.Name), - Protocol: corev1.Protocol(listener.Protocol), + Name: string(listener.Name), + // only TCP-based services are supported for now + Protocol: corev1.ProtocolTCP, Port: int32(listener.Port), }) } @@ -90,11 +91,11 @@ func (g *Gatekeeper) service(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClas ObjectMeta: metav1.ObjectMeta{ Name: gateway.Name, Namespace: gateway.Namespace, - Labels: apigateway.LabelsForGateway(&gateway), + Labels: common.LabelsForGateway(&gateway), Annotations: annotations, }, Spec: corev1.ServiceSpec{ - Selector: apigateway.LabelsForGateway(&gateway), + Selector: common.LabelsForGateway(&gateway), Type: *gcc.Spec.ServiceType, Ports: ports, }, diff --git a/control-plane/api-gateway/gatekeeper/serviceaccount.go b/control-plane/api-gateway/gatekeeper/serviceaccount.go index 207f4485c4..864047fb1b 100644 --- a/control-plane/api-gateway/gatekeeper/serviceaccount.go +++ b/control-plane/api-gateway/gatekeeper/serviceaccount.go @@ -7,17 +7,17 @@ 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" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" ) -func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1.Gateway, config apigateway.HelmConfig) error { +func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1.Gateway, config common.HelmConfig) error { if config.AuthMethod == "" { return g.deleteServiceAccount(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) } @@ -74,7 +74,7 @@ func (g *Gatekeeper) serviceAccount(gateway gwv1beta1.Gateway) *corev1.ServiceAc ObjectMeta: metav1.ObjectMeta{ Name: gateway.Name, Namespace: gateway.Namespace, - Labels: apigateway.LabelsForGateway(&gateway), + Labels: common.LabelsForGateway(&gateway), }, } } diff --git a/control-plane/api-gateway/translation/config_entry_translation.go b/control-plane/api-gateway/translation/config_entry_translation.go deleted file mode 100644 index 9501859f8b..0000000000 --- a/control-plane/api-gateway/translation/config_entry_translation.go +++ /dev/null @@ -1,516 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Package translation handles translating resources between different types -package translation - -import ( - "strings" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul/api" - capi "github.com/hashicorp/consul/api" -) - -const ( - metaKeyManagedBy = "managed-by" - metaValueManagedBy = "consul-k8s-gateway-controller" - metaKeyKubeNS = "k8s-namespace" - metaKeyKubeServiceName = "k8s-service-name" - metaKeyKubeName = "k8s-name" - - // AnnotationGateway is the annotation used to override the gateway name. - AnnotationGateway = "consul.hashicorp.com/gateway" - // AnnotationHTTPRoute is the annotation used to override the http route name. - AnnotationHTTPRoute = "consul.hashicorp.com/http-route" - // AnnotationTCPRoute is the annotation used to override the tcp route name. - AnnotationTCPRoute = "consul.hashicorp.com/tcp-route" - // AnnotationInlineCertificate is the annotation used to override the inline certificate name. - AnnotationInlineCertificate = "consul.hashicorp.com/inline-certificate" -) - -func translateListenerProtocol[T ~string](protocol T) string { - return strings.ToLower(string(protocol)) -} - -// K8sToConsulTranslator handles translating K8s resources into Consul config entries. -type K8sToConsulTranslator struct { - EnableConsulNamespaces bool - ConsulDestNamespace string - EnableK8sMirroring bool - MirroringPrefix string - ConsulPartition string -} - -// GatewayToAPIGateway translates a kuberenetes API gateway into a Consul APIGateway Config Entry. -func (t K8sToConsulTranslator) GatewayToAPIGateway(k8sGW gwv1beta1.Gateway, certs map[types.NamespacedName]api.ResourceReference) capi.APIGatewayConfigEntry { - listeners := make([]capi.APIGatewayListener, 0, len(k8sGW.Spec.Listeners)) - for _, listener := range k8sGW.Spec.Listeners { - var certificates []capi.ResourceReference - if listener.TLS != nil { - certificates = make([]capi.ResourceReference, 0, len(listener.TLS.CertificateRefs)) - for _, certificate := range listener.TLS.CertificateRefs { - k8sNS := "" - if certificate.Namespace != nil { - k8sNS = string(*certificate.Namespace) - } - nsn := types.NamespacedName{Name: string(certificate.Name), Namespace: k8sNS} - certRef, ok := certs[nsn] - if !ok { - // we don't have a ref for this certificate in consul - // drop the ref from the created gateway - continue - } - c := capi.ResourceReference{ - Kind: capi.InlineCertificate, - Name: certRef.Name, - Partition: certRef.Partition, - Namespace: certRef.Namespace, - } - certificates = append(certificates, c) - } - } - hostname := "" - if listener.Hostname != nil { - hostname = string(*listener.Hostname) - } - l := capi.APIGatewayListener{ - Name: string(listener.Name), - Hostname: hostname, - Port: int(listener.Port), - Protocol: translateListenerProtocol(listener.Protocol), - TLS: capi.APIGatewayTLSConfiguration{ - Certificates: certificates, - }, - } - - listeners = append(listeners, l) - } - gwName := k8sGW.Name - - if gwNameFromAnnotation, ok := k8sGW.Annotations[AnnotationGateway]; ok && gwNameFromAnnotation != "" && !strings.Contains(gwNameFromAnnotation, ",") { - gwName = gwNameFromAnnotation - } - - return capi.APIGatewayConfigEntry{ - Kind: capi.APIGateway, - Name: gwName, - Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: k8sGW.GetObjectMeta().GetNamespace(), - metaKeyKubeServiceName: k8sGW.GetObjectMeta().GetName(), - metaKeyKubeName: k8sGW.GetObjectMeta().GetName(), - }, - Listeners: listeners, - Partition: t.ConsulPartition, - Namespace: t.getConsulNamespace(k8sGW.GetObjectMeta().GetNamespace()), - } -} - -func (t K8sToConsulTranslator) ReferenceForGateway(k8sGW *gwv1beta1.Gateway) api.ResourceReference { - gwName := k8sGW.Name - if gwNameFromAnnotation, ok := k8sGW.Annotations[AnnotationGateway]; ok && gwNameFromAnnotation != "" && !strings.Contains(gwNameFromAnnotation, ",") { - gwName = gwNameFromAnnotation - } - return api.ResourceReference{ - Kind: api.APIGateway, - Name: gwName, - Namespace: t.getConsulNamespace(k8sGW.GetObjectMeta().GetNamespace()), - } -} - -// HTTPRouteToHTTPRoute translates a k8s HTTPRoute into a Consul HTTPRoute Config Entry. -func (t K8sToConsulTranslator) HTTPRouteToHTTPRoute(k8sHTTPRoute *gwv1beta1.HTTPRoute, parentRefs map[types.NamespacedName]api.ResourceReference, k8sServices map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) *capi.HTTPRouteConfigEntry { - routeName := k8sHTTPRoute.Name - if routeNameFromAnnotation, ok := k8sHTTPRoute.Annotations[AnnotationHTTPRoute]; ok && routeNameFromAnnotation != "" && !strings.Contains(routeNameFromAnnotation, ",") { - routeName = routeNameFromAnnotation - } - - consulHTTPRoute := &capi.HTTPRouteConfigEntry{ - Kind: capi.HTTPRoute, - Name: routeName, - Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: k8sHTTPRoute.GetObjectMeta().GetNamespace(), - metaKeyKubeServiceName: k8sHTTPRoute.GetObjectMeta().GetName(), - metaKeyKubeName: k8sHTTPRoute.GetObjectMeta().GetName(), - }, - Partition: t.ConsulPartition, - - Namespace: t.getConsulNamespace(k8sHTTPRoute.GetObjectMeta().GetNamespace()), - } - - // translate hostnames - hostnames := make([]string, 0, len(k8sHTTPRoute.Spec.Hostnames)) - for _, k8Hostname := range k8sHTTPRoute.Spec.Hostnames { - hostnames = append(hostnames, string(k8Hostname)) - } - consulHTTPRoute.Hostnames = hostnames - - // translate parent refs - consulHTTPRoute.Parents = translateRouteParentRefs(k8sHTTPRoute.Spec.CommonRouteSpec.ParentRefs, parentRefs) - - // translate rules - consulHTTPRoute.Rules = t.translateHTTPRouteRules(k8sHTTPRoute.Namespace, k8sHTTPRoute.Spec.Rules, k8sServices, meshServices) - - return consulHTTPRoute -} - -func (t K8sToConsulTranslator) ReferenceForHTTPRoute(k8sHTTPRoute *gwv1beta1.HTTPRoute) api.ResourceReference { - routeName := k8sHTTPRoute.Name - if routeNameFromAnnotation, ok := k8sHTTPRoute.Annotations[AnnotationHTTPRoute]; ok && routeNameFromAnnotation != "" && !strings.Contains(routeNameFromAnnotation, ",") { - routeName = routeNameFromAnnotation - } - return api.ResourceReference{ - Kind: api.HTTPRoute, - Name: routeName, - Namespace: t.getConsulNamespace(k8sHTTPRoute.GetObjectMeta().GetNamespace()), - } -} - -// translates parent refs for Routes into Consul Resource References. -func translateRouteParentRefs(k8sParentRefs []gwv1beta1.ParentReference, parentRefs map[types.NamespacedName]api.ResourceReference) []capi.ResourceReference { - parents := make([]capi.ResourceReference, 0, len(k8sParentRefs)) - for _, k8sParentRef := range k8sParentRefs { - namespace := "" - if k8sParentRef.Namespace != nil { - namespace = string(*k8sParentRef.Namespace) - } - parentRef, ok := parentRefs[types.NamespacedName{Name: string(k8sParentRef.Name), Namespace: namespace}] - if !(ok && isRefAPIGateway(k8sParentRef)) { - // we drop any parent refs that consul does not know about - continue - } - sectionName := "" - if k8sParentRef.SectionName != nil { - sectionName = string(*k8sParentRef.SectionName) - } - ref := capi.ResourceReference{ - Kind: capi.APIGateway, // Will this ever not be a gateway? is that something we need to handle? - Name: parentRef.Name, - SectionName: sectionName, - Partition: parentRef.Partition, - Namespace: parentRef.Namespace, - } - parents = append(parents, ref) - } - return parents -} - -// isRefAPIGateway checks if the parent resource is an APIGateway. -func isRefAPIGateway(ref gwv1beta1.ParentReference) bool { - return ref.Kind != nil && *ref.Kind == gwv1beta1.Kind("Gateway") || ref.Group != nil && string(*ref.Group) == gwv1beta1.GroupName -} - -// translate the rules portion of a HTTPRoute. -func (t K8sToConsulTranslator) translateHTTPRouteRules(namespace string, k8sRules []gwv1beta1.HTTPRouteRule, k8sServices map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) []capi.HTTPRouteRule { - rules := make([]capi.HTTPRouteRule, 0, len(k8sRules)) - for _, k8sRule := range k8sRules { - rule := capi.HTTPRouteRule{} - // translate matches - rule.Matches = translateHTTPMatches(k8sRule.Matches) - - // translate filters - rule.Filters = translateHTTPFilters(k8sRule.Filters) - - // translate services - rule.Services = t.translateHTTPServices(namespace, k8sRule.BackendRefs, k8sServices, meshServices) - - rules = append(rules, rule) - } - return rules -} - -var headerMatchTypeTranslation = map[gwv1beta1.HeaderMatchType]capi.HTTPHeaderMatchType{ - gwv1beta1.HeaderMatchExact: capi.HTTPHeaderMatchExact, - gwv1beta1.HeaderMatchRegularExpression: capi.HTTPHeaderMatchRegularExpression, -} - -var headerPathMatchTypeTranslation = map[gwv1beta1.PathMatchType]capi.HTTPPathMatchType{ - gwv1beta1.PathMatchExact: capi.HTTPPathMatchExact, - gwv1beta1.PathMatchPathPrefix: capi.HTTPPathMatchPrefix, - gwv1beta1.PathMatchRegularExpression: capi.HTTPPathMatchRegularExpression, -} - -var queryMatchTypeTranslation = map[gwv1beta1.QueryParamMatchType]capi.HTTPQueryMatchType{ - gwv1beta1.QueryParamMatchExact: capi.HTTPQueryMatchExact, - gwv1beta1.QueryParamMatchRegularExpression: capi.HTTPQueryMatchRegularExpression, -} - -// translate the http matches section. -func translateHTTPMatches(k8sMatches []gwv1beta1.HTTPRouteMatch) []capi.HTTPMatch { - matches := make([]capi.HTTPMatch, 0, len(k8sMatches)) - for _, k8sMatch := range k8sMatches { - // translate header matches - headers := make([]capi.HTTPHeaderMatch, 0, len(k8sMatch.Headers)) - for _, k8sHeader := range k8sMatch.Headers { - header := capi.HTTPHeaderMatch{ - Name: string(k8sHeader.Name), - Value: k8sHeader.Value, - } - if k8sHeader.Type != nil { - header.Match = headerMatchTypeTranslation[*k8sHeader.Type] - } - headers = append(headers, header) - } - - // translate query matches - queries := make([]capi.HTTPQueryMatch, 0, len(k8sMatch.QueryParams)) - for _, k8sQuery := range k8sMatch.QueryParams { - query := capi.HTTPQueryMatch{ - Name: k8sQuery.Name, - Value: k8sQuery.Value, - } - if k8sQuery.Type != nil { - query.Match = queryMatchTypeTranslation[*k8sQuery.Type] - } - queries = append(queries, query) - } - - match := capi.HTTPMatch{ - Headers: headers, - Query: queries, - } - if k8sMatch.Method != nil { - match.Method = capi.HTTPMatchMethod(*k8sMatch.Method) - } - if k8sMatch.Path != nil { - if k8sMatch.Path.Type != nil { - match.Path.Match = headerPathMatchTypeTranslation[*k8sMatch.Path.Type] - } - if k8sMatch.Path.Value != nil { - match.Path.Value = string(*k8sMatch.Path.Value) - } - } - matches = append(matches, match) - } - return matches -} - -// translate the http filters section. -func translateHTTPFilters(k8sFilters []gwv1beta1.HTTPRouteFilter) capi.HTTPFilters { - add := make(map[string]string) - set := make(map[string]string) - remove := make([]string, 0) - var urlRewrite *capi.URLRewrite - for _, k8sFilter := range k8sFilters { - for _, adder := range k8sFilter.RequestHeaderModifier.Add { - add[string(adder.Name)] = adder.Value - } - - for _, setter := range k8sFilter.RequestHeaderModifier.Set { - set[string(setter.Name)] = setter.Value - } - - remove = append(remove, k8sFilter.RequestHeaderModifier.Remove...) - - // we drop any path rewrites that are not prefix matches as we don't support those - if k8sFilter.URLRewrite != nil && k8sFilter.URLRewrite.Path.Type == gwv1beta1.PrefixMatchHTTPPathModifier { - urlRewrite = &capi.URLRewrite{Path: *k8sFilter.URLRewrite.Path.ReplacePrefixMatch} - } - - } - filter := capi.HTTPFilters{ - Headers: []capi.HTTPHeaderFilter{ - { - Add: add, - Remove: remove, - Set: set, - }, - }, - URLRewrite: urlRewrite, - } - - return filter -} - -// translate the backendrefs into services. -func (t K8sToConsulTranslator) translateHTTPServices(namespace string, k8sBackendRefs []gwv1beta1.HTTPBackendRef, k8sServices map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) []capi.HTTPService { - services := make([]capi.HTTPService, 0, len(k8sBackendRefs)) - - for _, k8sRef := range k8sBackendRefs { - backendRef := k8sRef.BackendObjectReference - - nsn := types.NamespacedName{ - Name: string(backendRef.Name), - Namespace: valueOr(backendRef.Namespace, namespace), - } - - isServiceRef := nilOrEqual(backendRef.Group, "") && nilOrEqual(backendRef.Kind, "Service") - isMeshServiceRef := derefEqual(backendRef.Group, v1alpha1.ConsulHashicorpGroup) && derefEqual(backendRef.Kind, v1alpha1.MeshServiceKind) - - k8sService, k8sServiceFound := k8sServices[nsn] - meshService, meshServiceFound := meshServices[nsn] - - if isServiceRef && k8sServiceFound { - service := capi.HTTPService{ - Name: strings.TrimSuffix(k8sService.ServiceName, "-sidecar-proxy"), - Namespace: t.getConsulNamespace(k8sService.Namespace), - Filters: translateHTTPFilters(k8sRef.Filters), - } - if k8sRef.Weight != nil { - service.Weight = int(*k8sRef.Weight) - } - services = append(services, service) - } else if isMeshServiceRef && meshServiceFound { - service := capi.HTTPService{ - Name: meshService.Spec.Name, - Namespace: t.getConsulNamespace(meshService.Namespace), - Filters: translateHTTPFilters(k8sRef.Filters), - } - if k8sRef.Weight != nil { - service.Weight = int(*k8sRef.Weight) - } - services = append(services, service) - } - } - - return services -} - -// TCPRouteToTCPRoute translates a Kuberenetes TCPRoute into a Consul TCPRoute Config Entry. -func (t K8sToConsulTranslator) TCPRouteToTCPRoute(k8sRoute *gwv1alpha2.TCPRoute, parentRefs map[types.NamespacedName]api.ResourceReference, k8sServices map[types.NamespacedName]api.CatalogService, meshServices map[types.NamespacedName]v1alpha1.MeshService) *capi.TCPRouteConfigEntry { - routeName := k8sRoute.Name - if routeNameFromAnnotation, ok := k8sRoute.Annotations[AnnotationTCPRoute]; ok && routeNameFromAnnotation != "" && !strings.Contains(routeNameFromAnnotation, ",") { - routeName = routeNameFromAnnotation - } - - consulRoute := &capi.TCPRouteConfigEntry{ - Kind: capi.TCPRoute, - Name: routeName, - Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: k8sRoute.GetObjectMeta().GetNamespace(), - metaKeyKubeServiceName: k8sRoute.GetObjectMeta().GetName(), - metaKeyKubeName: k8sRoute.GetObjectMeta().GetName(), - }, - Partition: t.ConsulPartition, - - Namespace: t.getConsulNamespace(k8sRoute.GetObjectMeta().GetNamespace()), - } - - // translate parent refs - consulRoute.Parents = translateRouteParentRefs(k8sRoute.Spec.CommonRouteSpec.ParentRefs, parentRefs) - - // translate the services - consulRoute.Services = make([]capi.TCPService, 0) - for _, rule := range k8sRoute.Spec.Rules { - for _, k8sref := range rule.BackendRefs { - backendRef := k8sref.BackendObjectReference - - nsn := types.NamespacedName{ - Name: string(backendRef.Name), - Namespace: valueOr(backendRef.Namespace, k8sRoute.Namespace), - } - - isServiceRef := nilOrEqual(backendRef.Group, "") && nilOrEqual(backendRef.Kind, "Service") - isMeshServiceRef := derefEqual(backendRef.Group, v1alpha1.ConsulHashicorpGroup) && derefEqual(backendRef.Kind, v1alpha1.MeshServiceKind) - - k8sService, k8sServiceFound := k8sServices[nsn] - meshService, meshServiceFound := meshServices[nsn] - - if isServiceRef && k8sServiceFound { - service := capi.TCPService{ - Name: strings.TrimSuffix(k8sService.ServiceName, "-sidecar-proxy"), - Namespace: t.getConsulNamespace(k8sService.Namespace), - } - consulRoute.Services = append(consulRoute.Services, service) - } else if isMeshServiceRef && meshServiceFound { - service := capi.TCPService{ - Name: meshService.Spec.Name, - Namespace: t.getConsulNamespace(meshService.Namespace), - } - consulRoute.Services = append(consulRoute.Services, service) - } - } - } - - return consulRoute -} - -func (t K8sToConsulTranslator) ReferenceForTCPRoute(k8sTCPRoute *gwv1alpha2.TCPRoute) api.ResourceReference { - routeName := k8sTCPRoute.Name - if routeNameFromAnnotation, ok := k8sTCPRoute.Annotations[AnnotationTCPRoute]; ok && routeNameFromAnnotation != "" && !strings.Contains(routeNameFromAnnotation, ",") { - routeName = routeNameFromAnnotation - } - return api.ResourceReference{ - Kind: api.TCPRoute, - Name: routeName, - Namespace: t.getConsulNamespace(k8sTCPRoute.GetObjectMeta().GetNamespace()), - } -} - -// SecretToInlineCertificate translates a Kuberenetes Secret into a Consul Inline Certificate Config Entry. -func (t K8sToConsulTranslator) SecretToInlineCertificate(k8sSecret corev1.Secret) capi.InlineCertificateConfigEntry { - namespace := t.getConsulNamespace(k8sSecret.GetObjectMeta().GetNamespace()) - return capi.InlineCertificateConfigEntry{ - Kind: capi.InlineCertificate, - Namespace: namespace, - Name: k8sSecret.Name, - Certificate: k8sSecret.StringData[corev1.TLSCertKey], - PrivateKey: k8sSecret.StringData[corev1.TLSPrivateKeyKey], - Meta: map[string]string{ - metaKeyManagedBy: metaValueManagedBy, - metaKeyKubeNS: namespace, - metaKeyKubeServiceName: string(k8sSecret.Name), - metaKeyKubeName: string(k8sSecret.Name), - }, - } -} - -func (t K8sToConsulTranslator) ReferenceForSecret(k8sSecret corev1.Secret) api.ResourceReference { - return api.ResourceReference{ - Kind: api.InlineCertificate, - Name: k8sSecret.Name, - Namespace: t.getConsulNamespace(k8sSecret.GetObjectMeta().GetNamespace()), - } -} - -func EntryToNamespacedName(entry capi.ConfigEntry) types.NamespacedName { - meta := entry.GetMeta() - return types.NamespacedName{ - Name: meta[metaKeyKubeName], - Namespace: meta[metaKeyKubeNS], - } -} - -func (t K8sToConsulTranslator) getConsulNamespace(k8sNS string) string { - return namespaces.ConsulNamespace(k8sNS, t.EnableK8sMirroring, t.ConsulDestNamespace, t.EnableK8sMirroring, t.MirroringPrefix) -} - -func EntryToReference(entry capi.ConfigEntry) capi.ResourceReference { - return capi.ResourceReference{ - Kind: entry.GetKind(), - Name: entry.GetName(), - Partition: entry.GetPartition(), - Namespace: entry.GetNamespace(), - } -} - -func ptrTo[T any](v T) *T { - return &v -} - -func derefEqual[T ~string](v *T, check string) bool { - if v == nil { - return false - } - return string(*v) == check -} - -func nilOrEqual[T ~string](v *T, check string) bool { - return v == nil || string(*v) == check -} - -func valueOr[T ~string](v *T, fallback string) string { - if v == nil { - return fallback - } - return string(*v) -} diff --git a/control-plane/api-gateway/translation/k8s_cache_translation.go b/control-plane/api-gateway/translation/k8s_cache_translation.go deleted file mode 100644 index 88c0eca3b9..0000000000 --- a/control-plane/api-gateway/translation/k8s_cache_translation.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package translation - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul/api" -) - -type TranslatorFn func(api.ConfigEntry) []types.NamespacedName - -type secretTransfomer func(context.Context) func(client.Object) []reconcile.Request - -type resourceGetter interface { - Get(api.ResourceReference) api.ConfigEntry -} - -// ConsulToNamespaceNameTranslator handles translating consul config entries to k8s namespaced names. -type ConsulToNamespaceNameTranslator struct { - cache resourceGetter -} - -// NewConsulToNamespaceNameTranslator creates an instance of the ConsulToNSNTranslator. -func NewConsulToNamespaceNameTranslator(cache resourceGetter) ConsulToNamespaceNameTranslator { - return ConsulToNamespaceNameTranslator{cache: cache} -} - -// BuildConsulGatewayTranslator creates a slice k8s types.NamespacedName from the meta fields of the api gateway config entry. -func (c ConsulToNamespaceNameTranslator) BuildConsulGatewayTranslator(ctx context.Context) TranslatorFn { - return func(config api.ConfigEntry) []types.NamespacedName { - meta, ok := metaToK8sNamespacedName(config) - if !ok { - return nil - } - - return []types.NamespacedName{meta} - } -} - -// BuildConsulHTTPRouteTranslator creates a slice of k8s types.NamespacedName from the meta fields of the http route parent refs. -func (c ConsulToNamespaceNameTranslator) BuildConsulHTTPRouteTranslator(ctx context.Context) TranslatorFn { - return func(config api.ConfigEntry) []types.NamespacedName { - route, ok := config.(*api.HTTPRouteConfigEntry) - if !ok { - return nil - } - - return consulRefsToNSN(c.cache, route.Parents) - } -} - -// BuildConsulTCPRouteTranslator creates a slice of k8s types.NamespacedName from the meta fields of the tcp route parent refs. -func (c ConsulToNamespaceNameTranslator) BuildConsulTCPRouteTranslator(ctx context.Context) TranslatorFn { - return func(config api.ConfigEntry) []types.NamespacedName { - route, ok := config.(*api.TCPRouteConfigEntry) - if !ok { - return nil - } - - return consulRefsToNSN(c.cache, route.Parents) - } -} - -// BuildConsulInlineCertificateTranslator creates a slice of k8s types.NamespacedName from the meta fields of the secret. It does this -// by using a secret transformer function to get a list of reconcile requests from k8s for the given secret and then converts -// those requests to the slice of NamespaceName. -func (c ConsulToNamespaceNameTranslator) BuildConsulInlineCertificateTranslator(ctx context.Context, secretTransformer secretTransfomer) TranslatorFn { - return func(config api.ConfigEntry) []types.NamespacedName { - meta, ok := metaToK8sNamespacedName(config) - if !ok { - return nil - } - - return requestsToRefs(secretTransformer(ctx)(&corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: meta.Name, - Namespace: meta.Namespace, - }, - })) - } -} - -func metaToK8sNamespacedName(config api.ConfigEntry) (types.NamespacedName, bool) { - meta := config.GetMeta() - - namespace, ok := meta[metaKeyKubeNS] - if !ok { - return types.NamespacedName{}, false - } - - name, ok := meta[metaKeyKubeName] - if !ok { - return types.NamespacedName{}, false - } - - return types.NamespacedName{ - Namespace: namespace, - Name: name, - }, true -} - -func consulRefsToNSN(cache resourceGetter, refs []api.ResourceReference) []types.NamespacedName { - nsnSet := make(map[types.NamespacedName]struct{}) - - for _, ref := range refs { - if parent := cache.Get(ref); parent != nil { - if k8sNSN, ok := metaToK8sNamespacedName(parent); ok { - nsnSet[k8sNSN] = struct{}{} - } - } - } - nsns := make([]types.NamespacedName, 0, len(nsnSet)) - - for nsn := range nsnSet { - nsns = append(nsns, nsn) - } - return nsns -} - -func requestsToRefs(objects []reconcile.Request) []types.NamespacedName { - var refs []types.NamespacedName - for _, object := range objects { - refs = append(refs, object.NamespacedName) - } - return refs -} diff --git a/control-plane/api-gateway/translation/k8s_cache_translation_test.go b/control-plane/api-gateway/translation/k8s_cache_translation_test.go deleted file mode 100644 index 52f2ca0eb3..0000000000 --- a/control-plane/api-gateway/translation/k8s_cache_translation_test.go +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package translation - -import ( - "context" - "sort" - "testing" - - "github.com/google/go-cmp/cmp" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul/api" -) - -func Test_ConsulToNamespaceNameTranslator_TranslateConsulGateway(t *testing.T) { - t.Parallel() - type args struct { - config *api.APIGatewayConfigEntry - } - tests := []struct { - name string - args args - want []types.NamespacedName - }{ - { - name: "when name and namespace are set", - args: args{ - config: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{ - metaKeyKubeNS: "my-ns", - metaKeyKubeName: "api-gw-name", - }, - }, - }, - want: []types.NamespacedName{ - { - Namespace: "my-ns", - Name: "api-gw-name", - }, - }, - }, - { - name: "when name is not set and namespace is set", - args: args{ - config: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{ - metaKeyKubeNS: "my-ns", - }, - }, - }, - want: nil, - }, - { - name: "when name is set and namespace is not set", - args: args{ - config: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{ - metaKeyKubeName: "api-gw-name", - }, - }, - }, - want: nil, - }, - { - name: "when both name and namespace are not set", - args: args{ - config: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{}, - }, - }, - want: nil, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - translator := ConsulToNamespaceNameTranslator{} - fn := translator.BuildConsulGatewayTranslator(context.Background()) - got := fn(tt.args.config) - if diff := cmp.Diff(got, tt.want, sortTransformer()); diff != "" { - t.Errorf("ConsulToNSNTranslator.TranslateConsulGateway() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestConsulToNamespaceNameTranslator_TranslateConsulHTTPRoute(t *testing.T) { - t.Parallel() - type fields struct { - cache resourceGetter - } - tests := []struct { - name string - fields fields - parentRefs []api.ResourceReference - want []types.NamespacedName - }{ - { - name: "all refs in cache", - fields: fields{ - cache: buildMockCache(map[api.ResourceReference]api.ConfigEntry{ - { - Kind: api.APIGateway, - Name: "api-gw-1", - Namespace: "ns", - }: api.ConfigEntry(&api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-1", - Meta: map[string]string{ - metaKeyKubeNS: "ns", - metaKeyKubeName: "api-gw-1", - }, - Namespace: "ns", - }), - { - Kind: api.APIGateway, - Name: "api-gw-2", - Namespace: "ns", - }: api.ConfigEntry(&api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-2", - Meta: map[string]string{ - metaKeyKubeNS: "ns", - metaKeyKubeName: "api-gw-2", - }, - Namespace: "ns", - }), - }), - }, - parentRefs: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw-1", - Namespace: "ns", - }, - - { - Kind: api.APIGateway, - Name: "api-gw-2", - Namespace: "ns", - }, - }, - want: []types.NamespacedName{ - { - Namespace: "ns", - Name: "api-gw-1", - }, - { - Namespace: "ns", - Name: "api-gw-2", - }, - }, - }, - { - name: "some refs not in cache", - fields: fields{ - cache: buildMockCache(map[api.ResourceReference]api.ConfigEntry{ - { - Kind: api.APIGateway, - Name: "api-gw-1", - Namespace: "ns", - }: api.ConfigEntry(&api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-1", - Meta: map[string]string{ - metaKeyKubeNS: "ns", - metaKeyKubeName: "api-gw-1", - }, - Namespace: "ns", - }), - }), - }, - parentRefs: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw-1", - Namespace: "ns", - }, - { - Kind: api.APIGateway, - Name: "api-gw-2", - Namespace: "ns", - }, - }, - want: []types.NamespacedName{ - { - Namespace: "ns", - Name: "api-gw-1", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := ConsulToNamespaceNameTranslator{ - cache: tt.fields.cache, - } - config := &api.HTTPRouteConfigEntry{ - Parents: tt.parentRefs, - } - got := c.BuildConsulHTTPRouteTranslator(context.Background())(config) - if diff := cmp.Diff(got, tt.want, sortTransformer()); diff != "" { - t.Errorf("ConsulToNSNTranslator.TranslateConsulHTTPRoute() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestConsulToNamespaceNameTranslator_TranslateConsulTCPRoute(t *testing.T) { - t.Parallel() - type fields struct { - cache resourceGetter - } - tests := []struct { - name string - fields fields - parentRefs []api.ResourceReference - want []types.NamespacedName - }{ - { - name: "all refs in cache", - fields: fields{ - cache: buildMockCache(map[api.ResourceReference]api.ConfigEntry{ - { - Kind: api.APIGateway, - Name: "api-gw-1", - Namespace: "ns", - }: api.ConfigEntry(&api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-1", - Meta: map[string]string{ - metaKeyKubeNS: "ns", - metaKeyKubeName: "api-gw-1", - }, - Namespace: "ns", - }), - { - Kind: api.APIGateway, - Name: "api-gw-2", - Namespace: "ns", - }: api.ConfigEntry(&api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-2", - Meta: map[string]string{ - metaKeyKubeNS: "ns", - metaKeyKubeName: "api-gw-2", - }, - Namespace: "ns", - }), - }), - }, - parentRefs: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw-1", - Namespace: "ns", - }, - - { - Kind: api.APIGateway, - Name: "api-gw-2", - Namespace: "ns", - }, - }, - want: []types.NamespacedName{ - { - Namespace: "ns", - Name: "api-gw-1", - }, - { - Namespace: "ns", - Name: "api-gw-2", - }, - }, - }, - { - name: "some refs not in cache", - fields: fields{ - cache: buildMockCache(map[api.ResourceReference]api.ConfigEntry{ - { - Kind: api.APIGateway, - Name: "api-gw-1", - Namespace: "ns", - }: api.ConfigEntry(&api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-1", - Meta: map[string]string{ - metaKeyKubeNS: "ns", - metaKeyKubeName: "api-gw-1", - }, - Namespace: "ns", - }), - }), - }, - parentRefs: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw-1", - Namespace: "ns", - }, - { - Kind: api.APIGateway, - Name: "api-gw-2", - Namespace: "ns", - }, - }, - want: []types.NamespacedName{ - { - Namespace: "ns", - Name: "api-gw-1", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := ConsulToNamespaceNameTranslator{ - cache: tt.fields.cache, - } - config := &api.TCPRouteConfigEntry{ - Parents: tt.parentRefs, - } - got := c.BuildConsulTCPRouteTranslator(context.Background())(config) - if diff := cmp.Diff(got, tt.want, sortTransformer()); diff != "" { - t.Errorf("ConsulToNSNTranslator.TranslateConsulTCPRoute() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func Test_ConsulToNamespaceNameTranslator_TranslateInlineCertificate(t *testing.T) { - t.Parallel() - type args struct { - config *api.InlineCertificateConfigEntry - } - tests := []struct { - name string - args args - want []types.NamespacedName - }{ - { - name: "when name and namespace are set", - args: args{ - config: &api.InlineCertificateConfigEntry{ - Kind: api.InlineCertificate, - Name: "secret", - Meta: map[string]string{ - metaKeyKubeNS: "my-ns", - metaKeyKubeName: "secret", - }, - }, - }, - want: []types.NamespacedName{ - { - Namespace: "my-ns", - Name: "secret", - }, - }, - }, - { - name: "when name is not set and namespace is set", - args: args{ - config: &api.InlineCertificateConfigEntry{ - Kind: api.InlineCertificate, - Name: "secret", - Meta: map[string]string{ - metaKeyKubeNS: "my-ns", - }, - }, - }, - want: nil, - }, - { - name: "when name is set and namespace is not set", - args: args{ - config: &api.InlineCertificateConfigEntry{ - Kind: api.APIGateway, - Name: "secret", - Meta: map[string]string{ - metaKeyKubeName: "secret", - }, - }, - }, - want: nil, - }, - { - name: "when both name and namespace are not set", - args: args{ - config: &api.InlineCertificateConfigEntry{ - Kind: api.InlineCertificate, - Name: "secret", - Meta: map[string]string{}, - }, - }, - want: nil, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - transformer := func(ctx context.Context) func(client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - return []reconcile.Request{ - { - NamespacedName: types.NamespacedName{Name: o.GetName(), Namespace: o.GetNamespace()}, - }, - } - } - } - - translator := ConsulToNamespaceNameTranslator{} - fn := translator.BuildConsulInlineCertificateTranslator(context.Background(), transformer) - got := fn(tt.args.config) - if diff := cmp.Diff(got, tt.want, sortTransformer()); diff != "" { - t.Errorf("ConsulToNSNTranslator.TranslateConsulInlineCertificate() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func sortTransformer() cmp.Option { - return cmp.Transformer("Sort", func(in []types.NamespacedName) []types.NamespacedName { - sort.Slice(in, func(i int, j int) bool { - return in[i].Name < in[j].Name - }) - return in - }) -} - -type mockCache struct { - c map[api.ResourceReference]api.ConfigEntry -} - -func (m mockCache) Get(ref api.ResourceReference) api.ConfigEntry { - val, ok := m.c[ref] - if !ok { - return nil - } - return val -} - -func buildMockCache(c map[api.ResourceReference]api.ConfigEntry) mockCache { - return mockCache{c: c} -} diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 48596f9d4b..8987a9f5e8 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -16,6 +16,9 @@ const ( // MetaKeyKubeNS is the meta key name for Kubernetes namespace used for the Consul services. MetaKeyKubeNS = "k8s-namespace" + // MetaKeyKubeName is the meta key name for Kubernetes object name used for a Consul object. + MetaKeyKubeName = "k8s-name" + // MetaKeyKubeServiceName is the meta key name for Kubernetes service name used for the Consul services. MetaKeyKubeServiceName = "k8s-service-name" diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 676681baea..c969b3058b 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -15,7 +15,7 @@ import ( "sync" "syscall" - apigateway "github.com/hashicorp/consul-k8s/control-plane/api-gateway" + gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" @@ -485,8 +485,8 @@ func (c *Command) Run(args []string) int { } cache, err := gatewaycontrollers.SetupGatewayControllerWithManager(ctx, mgr, gatewaycontrollers.GatewayControllerConfig{ - HelmConfig: apigateway.HelmConfig{ - ConsulConfig: apigateway.ConsulConfig{ + HelmConfig: gatewaycommon.HelmConfig{ + ConsulConfig: gatewaycommon.ConsulConfig{ Address: c.consul.Addresses, GRPCPort: consulConfig.GRPCPort, HTTPPort: consulConfig.HTTPPort, @@ -508,10 +508,13 @@ func (c *Command) Run(args []string) int { ConsulPartition: c.consul.Partition, ConsulCACert: string(caCertPem), }, - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - NamespacesEnabled: c.flagEnableNamespaces, - Partition: c.consul.Partition, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + NamespacesEnabled: c.flagEnableNamespaces, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + Partition: c.consul.Partition, }) if err != nil { setupLog.Error(err, "unable to create controller", "controller", "Gateway") From 18f2cd53700734fafb7575bd85d30639384b04b2 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Wed, 31 May 2023 12:15:17 -0500 Subject: [PATCH 181/592] Update ServiceIntentions CRD for JWT auth (#2213) --- .changelog/2213.txt | 3 + .../templates/crd-serviceintentions.yaml | 75 +++++++ .../api/v1alpha1/serviceintentions_types.go | 96 ++++++++- .../v1alpha1/serviceintentions_types_test.go | 190 ++++++++++++++++++ ...onsul.hashicorp.com_serviceintentions.yaml | 75 +++++++ 5 files changed, 437 insertions(+), 2 deletions(-) create mode 100644 .changelog/2213.txt diff --git a/.changelog/2213.txt b/.changelog/2213.txt new file mode 100644 index 0000000000..c09c2e0397 --- /dev/null +++ b/.changelog/2213.txt @@ -0,0 +1,3 @@ +```release-note:feature +helm: Update the ServiceIntentions CRD to support `JWT` fields. +``` diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index f16d0a0d8a..335d2eff7a 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -74,6 +74,43 @@ spec: have intentions defined. type: string type: object + jwt: + description: JWT specifies the configuration to validate a JSON Web + Token for all incoming requests. + properties: + providers: + description: Providers is a list of providers to consider when + verifying a JWT. + items: + properties: + name: + description: Name is the name of the JWT provider. There + MUST be a corresponding "jwt-provider" config entry with + this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional claims + to verify in a JWT's payload. + items: + properties: + path: + description: Path is the path to the claim in the + token JSON. + items: + type: string + type: array + value: + description: Value is the expected value at the given + path. If the type at the path is a list then we + verify that this value is contained in the list. + If the type at the path is a string then we verify + that this value matches. + type: string + type: object + type: array + type: object + type: array + type: object sources: description: Sources is the list of all intention sources and the authorization granted to those sources. The order of this list does @@ -183,6 +220,44 @@ spec: match on the HTTP request path. type: string type: object + jwt: + description: JWT specifies configuration to validate a + JSON Web Token for incoming requests. + properties: + providers: + description: Providers is a list of providers to consider + when verifying a JWT. + items: + properties: + name: + description: Name is the name of the JWT provider. + There MUST be a corresponding "jwt-provider" + config entry with this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional + claims to verify in a JWT's payload. + items: + properties: + path: + description: Path is the path to the claim + in the token JSON. + items: + type: string + type: array + value: + description: Value is the expected value + at the given path. If the type at the + path is a list then we verify that this + value is contained in the list. If the + type at the path is a string then we + verify that this value matches. + type: string + type: object + type: array + type: object + type: array + type: object type: object type: array samenessGroup: diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index 74e02eb617..d393f72a2d 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -59,6 +59,8 @@ type ServiceIntentionsSpec struct { // The order of this list does not matter, but out of convenience Consul will always store this // reverse sorted by intention precedence, as that is the order that they will be evaluated at enforcement time. Sources SourceIntentions `json:"sources,omitempty"` + // JWT specifies the configuration to validate a JSON Web Token for all incoming requests. + JWT *IntentionJWTRequirement `json:"jwt,omitempty"` } type IntentionDestination struct { @@ -108,6 +110,8 @@ type IntentionPermission struct { Action IntentionAction `json:"action,omitempty"` // HTTP is a set of HTTP-specific authorization criteria. HTTP *IntentionHTTPPermission `json:"http,omitempty"` + // JWT specifies configuration to validate a JSON Web Token for incoming requests. + JWT *IntentionJWTRequirement `json:"jwt,omitempty"` } type IntentionHTTPPermission struct { @@ -142,6 +146,30 @@ type IntentionHTTPHeaderPermission struct { Invert bool `json:"invert,omitempty"` } +type IntentionJWTRequirement struct { + // Providers is a list of providers to consider when verifying a JWT. + Providers []*IntentionJWTProvider `json:"providers,omitempty"` +} + +type IntentionJWTProvider struct { + // Name is the name of the JWT provider. There MUST be a corresponding + // "jwt-provider" config entry with this name. + Name string `json:"name,omitempty"` + + // VerifyClaims is a list of additional claims to verify in a JWT's payload. + VerifyClaims []*IntentionJWTClaimVerification `json:"verifyClaims,omitempty"` +} + +type IntentionJWTClaimVerification struct { + // Path is the path to the claim in the token JSON. + Path []string `json:"path,omitempty"` + + // Value is the expected value at the given path. If the type at the path + // is a list then we verify that this value is contained in the list. If + // the type at the path is a string then we verify that this value matches. + Value string `json:"value,omitempty"` +} + // IntentionAction is the action that the intention represents. This // can be "allow" or "deny" to allowlist or denylist intentions. type IntentionAction string @@ -226,6 +254,7 @@ func (in *ServiceIntentions) ToConsul(datacenter string) api.ConfigEntry { Name: in.Spec.Destination.Name, Namespace: in.Spec.Destination.Namespace, Sources: in.Spec.Sources.toConsul(), + JWT: in.Spec.JWT.toConsul(), Meta: meta(datacenter), } } @@ -297,6 +326,8 @@ func (in *ServiceIntentions) Validate(consulMeta common.ConsulMeta) error { errs = append(errs, in.validateNamespaces(consulMeta.NamespacesEnabled)...) + errs = append(errs, in.Spec.JWT.validate(path.Child("jwt"))...) + if len(errs) > 0 { return apierrors.NewInvalid( schema.GroupKind{Group: ConsulHashicorpGroup, Kind: common.ServiceIntentions}, @@ -394,6 +425,7 @@ func (in IntentionPermissions) toConsul() []*capi.IntentionPermission { consulIntentionPermissions = append(consulIntentionPermissions, &capi.IntentionPermission{ Action: permission.Action.toConsul(), HTTP: permission.HTTP.toConsul(), + JWT: permission.JWT.toConsul(), }) } return consulIntentionPermissions @@ -429,15 +461,54 @@ func (in IntentionHTTPHeaderPermissions) toConsul() []capi.IntentionHTTPHeaderPe return headerPermissions } +func (in *IntentionJWTRequirement) toConsul() *capi.IntentionJWTRequirement { + if in == nil { + return nil + } + var providers []*capi.IntentionJWTProvider + for _, p := range in.Providers { + providers = append(providers, p.toConsul()) + } + return &capi.IntentionJWTRequirement{ + Providers: providers, + } +} + +func (in *IntentionJWTProvider) toConsul() *capi.IntentionJWTProvider { + if in == nil { + return nil + } + var claims []*capi.IntentionJWTClaimVerification + for _, c := range in.VerifyClaims { + claims = append(claims, c.toConsul()) + } + return &capi.IntentionJWTProvider{ + Name: in.Name, + VerifyClaims: claims, + } +} + +func (in *IntentionJWTClaimVerification) toConsul() *capi.IntentionJWTClaimVerification { + if in == nil { + return nil + } + return &capi.IntentionJWTClaimVerification{ + Path: in.Path, + Value: in.Value, + } +} + func (in IntentionPermissions) validate(path *field.Path) field.ErrorList { var errs field.ErrorList for i, permission := range in { - if err := permission.Action.validate(path.Child("permissions").Index(i)); err != nil { + permPath := path.Child("permissions").Index(i) + if err := permission.Action.validate(permPath); err != nil { errs = append(errs, err) } if permission.HTTP != nil { - errs = append(errs, permission.HTTP.validate(path.Child("permissions").Index(i))...) + errs = append(errs, permission.HTTP.validate(permPath)...) } + errs = append(errs, permission.JWT.validate(permPath.Child("jwt"))...) } return errs } @@ -540,6 +611,27 @@ func numNotEmpty(ss ...string) int { return count } +func (in *IntentionJWTRequirement) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if in == nil { + return errs + } + + for i, p := range in.Providers { + if err := p.validate(path.Child("providers").Index(i)); err != nil { + errs = append(errs, err) + } + } + return errs +} + +func (in *IntentionJWTProvider) validate(path *field.Path) *field.Error { + if in != nil && in.Name == "" { + return field.Invalid(path.Child("name"), in.Name, "JWT provider name is required") + } + return nil +} + // sourceIntentionSortKey returns a string that can be used to sort intention // sources. func sourceIntentionSortKey(ixn *capi.SourceIntention) string { diff --git a/control-plane/api/v1alpha1/serviceintentions_types_test.go b/control-plane/api/v1alpha1/serviceintentions_types_test.go index f445dfb246..8d0a6d907a 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_types_test.go @@ -165,11 +165,37 @@ func TestServiceIntentions_MatchesConsul(t *testing.T) { "PUT", }, }, + JWT: &IntentionJWTRequirement{ + Providers: []*IntentionJWTProvider{ + { + Name: "okta-nested", + VerifyClaims: []*IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin-nested", + }, + }, + }, + }, + }, }, }, Description: "an L7 config", }, }, + JWT: &IntentionJWTRequirement{ + Providers: []*IntentionJWTProvider{ + { + Name: "okta", + VerifyClaims: []*IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin", + }, + }, + }, + }, + }, }, }, Theirs: &capi.ServiceIntentionsConfigEntry{ @@ -220,11 +246,37 @@ func TestServiceIntentions_MatchesConsul(t *testing.T) { "PUT", }, }, + JWT: &capi.IntentionJWTRequirement{ + Providers: []*capi.IntentionJWTProvider{ + { + Name: "okta-nested", + VerifyClaims: []*capi.IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin-nested", + }, + }, + }, + }, + }, }, }, Description: "an L7 config", }, }, + JWT: &capi.IntentionJWTRequirement{ + Providers: []*capi.IntentionJWTProvider{ + { + Name: "okta", + VerifyClaims: []*capi.IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin", + }, + }, + }, + }, + }, Meta: nil, }, Matches: true, @@ -376,11 +428,37 @@ func TestServiceIntentions_ToConsul(t *testing.T) { "PUT", }, }, + JWT: &IntentionJWTRequirement{ + Providers: []*IntentionJWTProvider{ + { + Name: "okta-nested", + VerifyClaims: []*IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin-nested", + }, + }, + }, + }, + }, }, }, Description: "an L7 config", }, }, + JWT: &IntentionJWTRequirement{ + Providers: []*IntentionJWTProvider{ + { + Name: "okta", + VerifyClaims: []*IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin", + }, + }, + }, + }, + }, }, }, Exp: &capi.ServiceIntentionsConfigEntry{ @@ -436,11 +514,37 @@ func TestServiceIntentions_ToConsul(t *testing.T) { "PUT", }, }, + JWT: &capi.IntentionJWTRequirement{ + Providers: []*capi.IntentionJWTProvider{ + { + Name: "okta-nested", + VerifyClaims: []*capi.IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin-nested", + }, + }, + }, + }, + }, }, }, Description: "an L7 config", }, }, + JWT: &capi.IntentionJWTRequirement{ + Providers: []*capi.IntentionJWTProvider{ + { + Name: "okta", + VerifyClaims: []*capi.IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin", + }, + }, + }, + }, + }, Meta: map[string]string{ common.SourceKey: common.SourceValue, common.DatacenterKey: "datacenter", @@ -741,11 +845,37 @@ func TestServiceIntentions_Validate(t *testing.T) { "PUT", }, }, + JWT: &IntentionJWTRequirement{ + Providers: []*IntentionJWTProvider{ + { + Name: "okta-nested", + VerifyClaims: []*IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin-nested", + }, + }, + }, + }, + }, }, }, Description: "an L7 config", }, }, + JWT: &IntentionJWTRequirement{ + Providers: []*IntentionJWTProvider{ + { + Name: "okta", + VerifyClaims: []*IntentionJWTClaimVerification{ + { + Path: []string{"perms", "role"}, + Value: "admin", + }, + }, + }, + }, + }, }, }, namespacesEnabled: true, @@ -1622,6 +1752,66 @@ func TestServiceIntentions_Validate(t *testing.T) { `samenessgroup cannot use or contain wildcard '*'`, }, }, + "invalid empty jwt provider name at top-level": { + input: &ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "dest-service", + }, + Sources: SourceIntentions{ + { + Name: "bar", + Action: "allow", + }, + }, + JWT: &IntentionJWTRequirement{ + Providers: []*IntentionJWTProvider{ + { + Name: "", + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.jwt.providers[0].name: Invalid value: "": JWT provider name is required`, + }, + }, + "invalid empty jwt provider name in permissions": { + input: &ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + }, + Spec: ServiceIntentionsSpec{ + Destination: IntentionDestination{ + Name: "dest-service", + }, + Sources: SourceIntentions{ + { + Name: "bar", + Permissions: IntentionPermissions{ + { + Action: "allow", + JWT: &IntentionJWTRequirement{ + Providers: []*IntentionJWTProvider{ + { + Name: "", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.sources[0].permissions[0].jwt.providers[0].name: Invalid value: "": JWT provider name is required`, + }, + }, } for name, testCase := range cases { t.Run(name, func(t *testing.T) { 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 fac2b31b97..cd28173ba8 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -70,6 +70,43 @@ spec: have intentions defined. type: string type: object + jwt: + description: JWT specifies the configuration to validate a JSON Web + Token for all incoming requests. + properties: + providers: + description: Providers is a list of providers to consider when + verifying a JWT. + items: + properties: + name: + description: Name is the name of the JWT provider. There + MUST be a corresponding "jwt-provider" config entry with + this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional claims + to verify in a JWT's payload. + items: + properties: + path: + description: Path is the path to the claim in the + token JSON. + items: + type: string + type: array + value: + description: Value is the expected value at the given + path. If the type at the path is a list then we + verify that this value is contained in the list. + If the type at the path is a string then we verify + that this value matches. + type: string + type: object + type: array + type: object + type: array + type: object sources: description: Sources is the list of all intention sources and the authorization granted to those sources. The order of this list does @@ -179,6 +216,44 @@ spec: match on the HTTP request path. type: string type: object + jwt: + description: JWT specifies configuration to validate a + JSON Web Token for incoming requests. + properties: + providers: + description: Providers is a list of providers to consider + when verifying a JWT. + items: + properties: + name: + description: Name is the name of the JWT provider. + There MUST be a corresponding "jwt-provider" + config entry with this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional + claims to verify in a JWT's payload. + items: + properties: + path: + description: Path is the path to the claim + in the token JSON. + items: + type: string + type: array + value: + description: Value is the expected value + at the given path. If the type at the + path is a list then we verify that this + value is contained in the list. If the + type at the path is a string then we + verify that this value matches. + type: string + type: object + type: array + type: object + type: array + type: object type: object type: array samenessGroup: From aaaed6780ad223327c1997fb5e5be5f3a2b9345f Mon Sep 17 00:00:00 2001 From: Connor Date: Wed, 31 May 2023 15:59:02 -0500 Subject: [PATCH 182/592] Fix setting args for the telemetry-collector (#2224) * Fix setting args for the telemetry-collector Either the docker container or the execution method for the telemetry-collector is making the args not get included on the process. Switch to putting it directly in the command so we can ensure this works as expected * Fix bats test --- .../consul/templates/telemetry-collector-deployment.yaml | 9 ++++----- charts/consul/test/unit/connect-inject-deployment.bats | 2 +- .../consul/test/unit/telemetry-collector-deployment.bats | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index cb0cb67852..62b8868f1f 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -222,11 +222,10 @@ spec: {{- end }} {{- end }} - consul-telemetry-collector agent - {{- if .Values.telemetryCollector.customExporterConfig }} - args: - - -config-file-path /consul/config/config.json - {{ end }} + consul-telemetry-collector agent \ + {{- if .Values.telemetryCollector.customExporterConfig }} + -config-file-path /consul/config/config.json \ + {{ end }} volumeMounts: {{- if .Values.telemetryCollector.customExporterConfig }} - name: config diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index e7d5b3bf48..26e3038759 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -216,7 +216,7 @@ load _helpers local cmd=$(helm template \ -s templates/connect-inject-deployment.yaml \ --set 'connectInject.enabled=true' \ - --set 'connectInject.metrics.enableTelemetryCollector=true' \ + --set 'global.metrics.enableTelemetryCollector=true' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index 1e2758e638..705447621e 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -921,7 +921,7 @@ load _helpers --set 'telemetryCollector.image=bar' \ --set 'telemetryCollector.customExporterConfig="foo"' \ . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].args') + yq '.spec.template.spec.containers[0].command') local actual=$(echo $flags | yq -r '. | any(contains("-config-file-path /consul/config/config.json"))') [ "${actual}" = "true" ] @@ -1073,4 +1073,4 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ local actual=$(echo $object | yq -r 'map(select(.name == "foo")) | .[0].value' | tee /dev/stderr) [ "${actual}" = "bar" ] -} \ No newline at end of file +} From 0c28b9b000cb5dced3011ae10f5cd858f2754026 Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Wed, 31 May 2023 20:42:56 -0700 Subject: [PATCH 183/592] Fix telemetry collector issue and fix for bat test (#2223) --- .../endpoints/endpoints_controller.go | 7 +- .../endpoints/endpoints_controller_test.go | 104 +++++++++++++++++- 2 files changed, 103 insertions(+), 8 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 584abd48c6..eeaeeab485 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -487,7 +487,7 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints proxyConfig.Config[envoyPrometheusBindAddr] = prometheusScrapeListener } - if r.EnableTelemetryCollector { + if r.EnableTelemetryCollector && proxyConfig.Config != nil { proxyConfig.Config[envoyTelemetryCollectorBindSocketDir] = "/consul/connect-inject" } @@ -671,6 +671,9 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints ID: pod.Name, Address: pod.Status.PodIP, Meta: meta, + Proxy: &api.AgentServiceConnectProxyConfig{ + Config: map[string]interface{}{}, + }, } gatewayServiceName, ok := pod.Annotations[constants.AnnotationGatewayConsulServiceName] @@ -770,7 +773,7 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints } } - if r.EnableTelemetryCollector { + if r.EnableTelemetryCollector && service.Proxy != nil && service.Proxy.Config != nil { service.Proxy.Config[envoyTelemetryCollectorBindSocketDir] = "/consul/service" } diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index aad7694b2e..acf62b2b0e 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -996,6 +996,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { expectedProxySvcInstances []*api.CatalogService expectedHealthChecks []*api.HealthCheck metricsEnabled bool + telemetryCollectorDisabled bool nodeMeta map[string]string expErr string }{ @@ -1078,6 +1079,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, + Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, ServiceTags: []string{}, @@ -1163,7 +1165,9 @@ func TestReconcileCreateEndpoint(t *testing.T) { Port: 443, }, }, - ServiceProxy: &api.AgentServiceConnectProxyConfig{}, + ServiceProxy: &api.AgentServiceConnectProxyConfig{ + Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/service")}, + }, NodeMeta: map[string]string{ "synthetic-node": "true", "test-node": "true", @@ -1216,6 +1220,80 @@ func TestReconcileCreateEndpoint(t *testing.T) { } return []runtime.Object{gateway, endpoint} }, + expectedConsulSvcInstances: []*api.CatalogService{ + { + ServiceID: "mesh-gateway", + ServiceName: "mesh-gateway", + ServiceAddress: "1.2.3.4", + ServicePort: 8443, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "mesh-gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceTags: []string{}, + ServiceTaggedAddresses: map[string]api.ServiceAddress{ + "lan": { + Address: "1.2.3.4", + Port: 8443, + }, + "wan": { + Address: "2.3.4.5", + Port: 443, + }, + }, + ServiceProxy: &api.AgentServiceConnectProxyConfig{ + Config: map[string]interface{}{ + "envoy_prometheus_bind_addr": "1.2.3.4:20200", + "envoy_telemetry_collector_bind_socket_dir": "/consul/service", + }, + }, + }, + }, + expectedHealthChecks: []*api.HealthCheck{ + { + CheckID: "default/mesh-gateway", + ServiceName: "mesh-gateway", + ServiceID: "mesh-gateway", + Name: consulKubernetesCheckName, + Status: api.HealthPassing, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, + }, + }, + metricsEnabled: true, + }, + { + name: "Mesh_Gateway_with_Metrics_enabled_and_telemetry_collector_disabled", + svcName: "mesh-gateway", + consulSvcName: "mesh-gateway", + telemetryCollectorDisabled: true, + k8sObjects: func() []runtime.Object { + gateway := createGatewayPod("mesh-gateway", "1.2.3.4", map[string]string{ + constants.AnnotationGatewayConsulServiceName: "mesh-gateway", + constants.AnnotationGatewayWANSource: "Static", + constants.AnnotationGatewayWANAddress: "2.3.4.5", + constants.AnnotationGatewayWANPort: "443", + constants.AnnotationMeshGatewayContainerPort: "8443", + constants.AnnotationGatewayKind: meshGateway}) + endpoint := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mesh-gateway", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + { + IP: "1.2.3.4", + TargetRef: &corev1.ObjectReference{ + Kind: "Pod", + Name: "mesh-gateway", + Namespace: "default", + }, + }, + }, + }, + }, + } + return []runtime.Object{gateway, endpoint} + }, expectedConsulSvcInstances: []*api.CatalogService{ { ServiceID: "mesh-gateway", @@ -1298,8 +1376,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", }, - ServiceTags: []string{}, - ServiceProxy: &api.AgentServiceConnectProxyConfig{}, + ServiceTags: []string{}, + ServiceProxy: &api.AgentServiceConnectProxyConfig{ + Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/service")}, + }, }, }, expectedHealthChecks: []*api.HealthCheck{ @@ -1362,7 +1442,8 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{ Config: map[string]interface{}{ - "envoy_prometheus_bind_addr": "1.2.3.4:20200", + "envoy_prometheus_bind_addr": "1.2.3.4:20200", + "envoy_telemetry_collector_bind_socket_dir": "/consul/service", }, }, }, @@ -1462,6 +1543,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { "address": "0.0.0.0", }, }, + "envoy_telemetry_collector_bind_socket_dir": "/consul/service", }, }, }, @@ -1562,7 +1644,8 @@ func TestReconcileCreateEndpoint(t *testing.T) { "address": "0.0.0.0", }, }, - "envoy_prometheus_bind_addr": "1.2.3.4:20200", + "envoy_prometheus_bind_addr": "1.2.3.4:20200", + "envoy_telemetry_collector_bind_socket_dir": "/consul/service", }, }, }, @@ -1647,6 +1730,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, + Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, ServiceTags: []string{}, @@ -1661,6 +1745,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod2-service-created", LocalServiceAddress: "", LocalServicePort: 0, + Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, ServiceTags: []string{}, @@ -1786,6 +1871,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, + Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, ServiceTags: []string{}, @@ -1800,6 +1886,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod2-service-created", LocalServiceAddress: "", LocalServicePort: 0, + Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, ServiceTags: []string{}, @@ -1912,7 +1999,8 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, }, Config: map[string]interface{}{ - "envoy_prometheus_bind_addr": "0.0.0.0:12345", + "envoy_prometheus_bind_addr": "0.0.0.0:12345", + "envoy_telemetry_collector_bind_socket_dir": "/consul/connect-inject", }, }, ServiceMeta: map[string]string{ @@ -2013,6 +2101,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, + Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, ServiceTags: []string{}, @@ -2072,6 +2161,9 @@ func TestReconcileCreateEndpoint(t *testing.T) { EnableGatewayMetrics: true, } } + + ep.EnableTelemetryCollector = !tt.telemetryCollectorDisabled + namespacedName := types.NamespacedName{ Namespace: "default", Name: tt.svcName, From 8d51935f399cd9ffb1e456872c4366a32342e053 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 1 Jun 2023 10:34:33 -0400 Subject: [PATCH 184/592] Get consul-dataplane image from helm chart (#2232) --- Makefile | 5 +++++ .../build-support/scripts/consul-dataplane-version.sh | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100755 control-plane/build-support/scripts/consul-dataplane-version.sh diff --git a/Makefile b/Makefile index 02527e4d76..151f7868de 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ VERSION = $(shell ./control-plane/build-support/scripts/version.sh control-plane/version/version.go) CONSUL_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-version.sh charts/consul/values.yaml) +CONSUL_DATAPLANE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-dataplane-version.sh charts/consul/values.yaml) # ===========> Helm Targets @@ -164,6 +165,10 @@ version: consul-version: @echo $(CONSUL_IMAGE_VERSION) +consul-dataplane-version: + @echo $(CONSUL_DATAPLANE_IMAGE_VERSION) + + # ===========> Release Targets prepare-release: ## Sets the versions, updates changelog to prepare this repository to release diff --git a/control-plane/build-support/scripts/consul-dataplane-version.sh b/control-plane/build-support/scripts/consul-dataplane-version.sh new file mode 100755 index 0000000000..906ee54a1f --- /dev/null +++ b/control-plane/build-support/scripts/consul-dataplane-version.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +FILE=$1 +VERSION=$(yq .global.imageConsulDataplane $FILE) + +echo "${VERSION}" From 9dfc3d0f6867128b879d9dbf752c557ea97a2516 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Thu, 1 Jun 2023 12:04:56 -0400 Subject: [PATCH 185/592] Add acceptance test cleanup for API Gateway resources (#2237) --- acceptance/framework/consul/helm_cluster.go | 56 ++++++ acceptance/go.mod | 45 +++-- acceptance/go.sum | 192 +++++++------------- 3 files changed, 149 insertions(+), 144 deletions(-) diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index e03e3615fc..613b69da91 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/portforward" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" @@ -26,7 +27,11 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) // HelmCluster implements Cluster and uses Helm @@ -44,6 +49,7 @@ type HelmCluster struct { ctx environment.TestContext helmOptions *helm.Options releaseName string + runtimeClient client.Client kubernetesClient kubernetes.Interface noCleanupOnFailure bool debugDirectory string @@ -98,6 +104,7 @@ func NewHelmCluster( ctx: ctx, helmOptions: opts, releaseName: releaseName, + runtimeClient: ctx.ControllerRuntimeClient(t), kubernetesClient: ctx.KubernetesClient(t), noCleanupOnFailure: cfg.NoCleanupOnFailure, debugDirectory: cfg.DebugDirectory, @@ -154,6 +161,9 @@ func (h *HelmCluster) Destroy(t *testing.T) { // Retry because sometimes certain resources (like PVC) take time to delete // in cloud providers. retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { + requirement, err := labels.NewRequirement("release", selection.Equals, []string{h.releaseName}) + require.NoError(r, err) + // Force delete any pods that have h.releaseName in their name because sometimes // graceful termination takes a long time and since this is an uninstall // we don't care that they're stopped gracefully. @@ -233,6 +243,34 @@ func (h *HelmCluster) Destroy(t *testing.T) { } } + // Forcibly delete all gateway classes and remove their finalizers. + err = h.runtimeClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}, client.HasLabels{"release=" + h.releaseName}) + require.NoError(r, err) + + var gatewayClassList gwv1beta1.GatewayClassList + err = h.runtimeClient.List(context.Background(), &gatewayClassList, &client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*requirement), + }) + require.NoError(r, err) + for _, item := range gatewayClassList.Items { + item.SetFinalizers([]string{}) + require.NoError(r, h.runtimeClient.Update(context.Background(), &item)) + } + + // Forcibly delete all gateway class configs and remove their finalizers. + err = h.runtimeClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}, client.HasLabels{"release=" + h.releaseName}) + require.NoError(r, err) + + var gatewayClassConfigList v1alpha1.GatewayClassConfigList + err = h.runtimeClient.List(context.Background(), &gatewayClassConfigList, &client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*requirement), + }) + require.NoError(r, err) + for _, item := range gatewayClassConfigList.Items { + item.SetFinalizers([]string{}) + require.NoError(r, h.runtimeClient.Update(context.Background(), &item)) + } + // Verify all Consul Pods are deleted. pods, err = h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) require.NoError(r, err) @@ -291,6 +329,24 @@ func (h *HelmCluster) Destroy(t *testing.T) { r.Errorf("Found job which should have been deleted: %s", job.Name) } } + + // Verify all Gateway Classes are deleted. + err = h.runtimeClient.List(context.Background(), &gatewayClassList, &client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*requirement), + }) + require.NoError(r, err) + for _, gatewayClass := range gatewayClassList.Items { + r.Errorf("Found gateway class which should have been deleted: %s", gatewayClass.Name) + } + + // Verify all Gateway Class Configs are deleted. + err = h.runtimeClient.List(context.Background(), &gatewayClassConfigList, &client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*requirement), + }) + require.NoError(r, err) + for _, gatewayClassConfig := range gatewayClassConfigList.Items { + r.Errorf("Found gateway class config which should have been deleted: %s", gatewayClassConfig.Name) + } }) } diff --git a/acceptance/go.mod b/acceptance/go.mod index c53caf4952..88507a2d54 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -4,12 +4,13 @@ go 1.20 require ( github.com/gruntwork-io/terratest v0.31.2 - github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3 + github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/vault/api v1.2.0 + github.com/hashicorp/serf v0.10.1 + github.com/hashicorp/vault/api v1.8.3 github.com/stretchr/testify v1.8.2 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 @@ -22,7 +23,7 @@ require ( require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/aws/aws-sdk-go v1.30.27 // indirect + github.com/aws/aws-sdk-go v1.44.262 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect @@ -42,41 +43,43 @@ require ( github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golang/snappy v0.0.1 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.2.2 // indirect - github.com/hashicorp/go-immutable-radix v1.3.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.0.1 // indirect + github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-retryablehttp v0.6.6 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect - github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/serf v0.10.1 // indirect - github.com/hashicorp/vault/sdk v0.2.1 // indirect + github.com/hashicorp/vault/sdk v0.7.0 // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/jmespath/go-jmespath v0.3.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect - github.com/miekg/dns v1.1.41 // indirect + github.com/miekg/dns v1.1.50 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect - github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -95,19 +98,21 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/urfave/cli v1.22.2 // indirect - go.uber.org/atomic v1.7.0 // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + go.uber.org/atomic v1.9.0 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect + golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + golang.org/x/tools v0.7.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect - google.golang.org/grpc v1.48.0 // indirect + google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect + google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 82037c23a0..c0a0a6d870 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -1,4 +1,3 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -72,8 +71,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -88,8 +85,6 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= -github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -98,10 +93,9 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.30.27 h1:9gPjZWVDSoQrBO2AvqrWObS6KAZByfEJxQoCYo4ZfK0= -github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= +github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -129,17 +123,7 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= @@ -167,7 +151,6 @@ github.com/docker/cli v0.0.0-20200109221225-a4f60165b7a3/go.mod h1:JLrzqnKDaYBop github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -189,10 +172,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF 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.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= @@ -204,17 +185,13 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= -github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -226,8 +203,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= -github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -259,10 +234,7 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -301,8 +273,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -347,7 +319,6 @@ github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -360,63 +331,55 @@ github.com/gruntwork-io/gruntwork-cli v0.7.0 h1:YgSAmfCj9c61H+zuvHwKfYUwlMhu5arn github.com/gruntwork-io/gruntwork-cli v0.7.0/go.mod h1:jp6Z7NcLF2avpY8v71fBx6hds9eOFPELSuD/VPv7w00= github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+XbVVtytUHg= github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3 h1:4wROIZB8Y4cN/wPILChc2zQ/q00z1VyJitdgyLbITdU= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3/go.mod h1:j9Db/whkzvNC+KP2GftY0HxxleLm9swxXjlu3tYaOAw= +github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb h1:9GUvDoKVoV3IW78QyfoNY4bRcKxcn26wTGLoBrz92N4= +github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb/go.mod h1:jKzTEgDc/np2gX/KPdfdm1mEUfZLrU8gc71XN3B15VI= github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 h1:6kUTk+YBgA5X5b3gNAoI18WEK4/t75LcWSimEgmpFdg= github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= -github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= -github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= 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= +github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY= +github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= +github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2/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.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.0/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= @@ -432,12 +395,10 @@ github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= -github.com/hashicorp/vault/api v1.2.0 h1:ysGFc6XRGbv05NsWPzuO5VTv68Lj8jtwATxRLFOpP9s= -github.com/hashicorp/vault/api v1.2.0/go.mod h1:dAjw0T5shMnrfH7Q/Mst+LrcTKvStZBVs1PICEDpUqY= -github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= -github.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M= -github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= +github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o= +github.com/hashicorp/vault/api v1.8.3/go.mod h1:4g/9lj9lmuJQMtT6CmVMHC5FW1yENaVv+Nv4ZfG8fAg= +github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg= +github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -448,10 +409,13 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -503,7 +467,6 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -517,25 +480,25 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfr github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -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.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= +github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= @@ -569,24 +532,17 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/oracle/oci-go-sdk v7.1.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -599,7 +555,6 @@ github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prY github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -613,7 +568,6 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= @@ -621,9 +575,7 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -650,7 +602,6 @@ github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -659,12 +610,10 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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= @@ -692,7 +641,6 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= @@ -706,6 +654,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -715,19 +664,16 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/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-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -735,10 +681,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -751,8 +697,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -765,7 +711,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -775,12 +720,14 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -808,7 +755,6 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -817,8 +763,11 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -840,7 +789,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -849,29 +799,24 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -890,7 +835,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -910,11 +854,15 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -926,13 +874,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -951,7 +899,6 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -959,7 +906,6 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -990,13 +936,16 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= +gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= @@ -1056,13 +1005,11 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= @@ -1076,9 +1023,9 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= 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= @@ -1092,9 +1039,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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= @@ -1105,13 +1052,11 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -1131,7 +1076,6 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C 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 v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 494837ef1b15320cf2d5ff11dbf5b5bb582fa205 Mon Sep 17 00:00:00 2001 From: malizz Date: Thu, 1 Jun 2023 10:37:29 -0700 Subject: [PATCH 186/592] improve code readability and fix flaky tests re acl token generation (#2210) --- .../create-federation-secret/command_test.go | 148 +++++++++--------- 1 file changed, 78 insertions(+), 70 deletions(-) diff --git a/control-plane/subcommand/create-federation-secret/command_test.go b/control-plane/subcommand/create-federation-secret/command_test.go index c401f1fc7e..16939a2868 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -323,7 +323,7 @@ func TestRun_ACLs_K8SNamespaces_ResourcePrefixes(tt *testing.T) { // Set up Consul server with TLS. caFile, certFile, keyFile := test.GenerateServerCerts(t) - a, err := testutil.NewTestServerConfigT(t, func(cfg *testutil.TestServerConfig) { + testserver, err := testutil.NewTestServerConfigT(t, func(cfg *testutil.TestServerConfig) { cfg.CAFile = caFile cfg.CertFile = certFile cfg.KeyFile = keyFile @@ -333,11 +333,11 @@ func TestRun_ACLs_K8SNamespaces_ResourcePrefixes(tt *testing.T) { } }) require.NoError(t, err) - defer a.Stop() + defer testserver.Stop() // Construct Consul client. client, err := api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -362,7 +362,7 @@ func TestRun_ACLs_K8SNamespaces_ResourcePrefixes(tt *testing.T) { // Redefine the client with the bootstrap token set so // subsequent calls will succeed. client, err = api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -447,7 +447,7 @@ func TestRun_ACLs_K8SNamespaces_ResourcePrefixes(tt *testing.T) { "-ca-file", caFile, "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, - "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), + "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-consul-api-timeout", "10s", } if c.aclsEnabled { @@ -506,27 +506,31 @@ func TestRun_WaitsForMeshGatewayInstances(t *testing.T) { // Set up Consul server with TLS. caFile, certFile, keyFile := test.GenerateServerCerts(t) - a, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + testserver, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { c.CAFile = caFile c.CertFile = certFile c.KeyFile = keyFile }) require.NoError(t, err) - defer a.Stop() + defer testserver.Stop() // Create a mesh gateway instance after a delay. meshGWIP := "192.168.0.1" meshGWPort := 443 go func() { - time.Sleep(500 * time.Millisecond) - client, err := api.NewClient(&api.Config{ - Address: a.HTTPSAddr, - Scheme: "https", - TLSConfig: api.TLSConfig{ - CAFile: caFile, - }, + var client *api.Client + timer := &retry.Timer{Timeout: 10 * time.Second, Wait: 500 * time.Millisecond} + retry.RunWith(timer, t, func(r *retry.R) { + client, err = api.NewClient(&api.Config{ + Address: testserver.HTTPSAddr, + Scheme: "https", + TLSConfig: api.TLSConfig{ + CAFile: caFile, + }, + }) + require.NoError(t, err) }) - require.NoError(t, err) + err = client.Agent().ServiceRegister(&api.AgentServiceRegistration{ Name: "mesh-gateway", TaggedAddresses: map[string]api.ServiceAddress{ @@ -554,7 +558,7 @@ func TestRun_WaitsForMeshGatewayInstances(t *testing.T) { "-ca-file", caFile, "-server-ca-cert-file", certFile, "-server-ca-key-file", keyFile, - "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), + "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -575,15 +579,15 @@ func TestRun_MeshGatewayNoWANAddr(t *testing.T) { // Set up Consul server with TLS. caFile, certFile, keyFile := test.GenerateServerCerts(t) - a, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + testserver, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { c.CAFile = caFile c.CertFile = certFile c.KeyFile = keyFile }) require.NoError(t, err) - defer a.Stop() + defer testserver.Stop() client, err := api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -608,7 +612,7 @@ func TestRun_MeshGatewayNoWANAddr(t *testing.T) { "-ca-file", caFile, "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, - "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), + "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-consul-api-timeout", "10s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) @@ -646,17 +650,17 @@ func TestRun_MeshGatewayUniqueAddrs(tt *testing.T) { // Set up Consul server with TLS. caFile, certFile, keyFile := test.GenerateServerCerts(t) - a, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + testserver, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { c.CAFile = caFile c.CertFile = certFile c.KeyFile = keyFile }) require.NoError(t, err) - defer a.Stop() + defer testserver.Stop() // Create mesh gateway instances. client, err := api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -694,7 +698,7 @@ func TestRun_MeshGatewayUniqueAddrs(tt *testing.T) { "-ca-file", caFile, "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, - "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), + "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -725,7 +729,7 @@ func TestRun_ReplicationSecretDelay(t *testing.T) { // Set up Consul server with TLS. caFile, certFile, keyFile := test.GenerateServerCerts(t) - a, err := testutil.NewTestServerConfigT(t, func(cfg *testutil.TestServerConfig) { + testserver, err := testutil.NewTestServerConfigT(t, func(cfg *testutil.TestServerConfig) { cfg.CAFile = caFile cfg.CertFile = certFile cfg.KeyFile = keyFile @@ -733,11 +737,11 @@ func TestRun_ReplicationSecretDelay(t *testing.T) { cfg.ACL.DefaultPolicy = "deny" }) require.NoError(t, err) - defer a.Stop() + defer testserver.Stop() // Construct Consul client. client, err := api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -762,7 +766,7 @@ func TestRun_ReplicationSecretDelay(t *testing.T) { // Redefine the client with the bootstrap token set so // subsequent calls will succeed. client, err = api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -807,20 +811,22 @@ func TestRun_ReplicationSecretDelay(t *testing.T) { // Create replication token secret after a delay. go func() { - time.Sleep(400 * time.Millisecond) - _, err := k8s.CoreV1().Secrets("default").Create( - context.Background(), - &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "prefix-" + common.ACLReplicationTokenName + "-acl-token", - Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, - }, - Data: map[string][]byte{ - common.ACLTokenSecretKey: []byte(replicationToken), + timer := &retry.Timer{Timeout: 6 * time.Second, Wait: 400 * time.Millisecond} + retry.RunWith(timer, t, func(r *retry.R) { + _, err := k8s.CoreV1().Secrets("default").Create( + context.Background(), + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "prefix-" + common.ACLReplicationTokenName + "-acl-token", + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{ + common.ACLTokenSecretKey: []byte(replicationToken), + }, }, - }, - metav1.CreateOptions{}) - require.NoError(t, err) + metav1.CreateOptions{}) + require.NoError(t, err) + }) }() // Run the command. @@ -836,7 +842,7 @@ func TestRun_ReplicationSecretDelay(t *testing.T) { "-ca-file", caFile, "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, - "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), + "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-export-replication-token", "-consul-api-timeout", "10s", } @@ -860,17 +866,17 @@ func TestRun_UpdatesSecret(t *testing.T) { // Set up Consul server with TLS. caFile, certFile, keyFile := test.GenerateServerCerts(t) - a, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + testserver, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { c.CAFile = caFile c.CertFile = certFile c.KeyFile = keyFile }) require.NoError(t, err) - defer a.Stop() + defer testserver.Stop() // Create a mesh gateway instance. client, err := api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -907,7 +913,7 @@ func TestRun_UpdatesSecret(t *testing.T) { "-ca-file", caFile, "-server-ca-cert-file", certFile, "-server-ca-key-file", keyFile, - "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), + "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -949,7 +955,7 @@ func TestRun_UpdatesSecret(t *testing.T) { "-ca-file", caFile, "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, - "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), + "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -978,31 +984,33 @@ func TestRun_ConsulClientDelay(t *testing.T) { k8s := fake.NewSimpleClientset() // Set up Consul server with TLS. Start after a 500ms delay. - var a *testutil.TestServer + var testserver *testutil.TestServer wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() - time.Sleep(500 * time.Millisecond) - var err error - a, err = testutil.NewTestServerConfigT(t, func(cfg *testutil.TestServerConfig) { - cfg.CAFile = caFile - cfg.CertFile = certFile - cfg.KeyFile = keyFile - cfg.Ports = &testutil.TestPortConfig{ - DNS: randomPorts[0], - HTTP: randomPorts[1], - HTTPS: randomPorts[2], - SerfLan: randomPorts[3], - SerfWan: randomPorts[4], - Server: randomPorts[5], - } + timer := &retry.Timer{Timeout: 10 * time.Second, Wait: 500 * time.Millisecond} + retry.RunWith(timer, t, func(r *retry.R) { + var err error + testserver, err = testutil.NewTestServerConfigT(t, func(cfg *testutil.TestServerConfig) { + cfg.CAFile = caFile + cfg.CertFile = certFile + cfg.KeyFile = keyFile + cfg.Ports = &testutil.TestPortConfig{ + DNS: randomPorts[0], + HTTP: randomPorts[1], + HTTPS: randomPorts[2], + SerfLan: randomPorts[3], + SerfWan: randomPorts[4], + Server: randomPorts[5], + } + }) + require.NoError(t, err) }) - require.NoError(t, err) // Construct Consul client. client, err := api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -1025,8 +1033,8 @@ func TestRun_ConsulClientDelay(t *testing.T) { require.NoError(t, err) }() defer func() { - if a != nil { - a.Stop() + if testserver != nil { + testserver.Stop() } }() @@ -1065,17 +1073,17 @@ func TestRun_Autoencrypt(t *testing.T) { // Set up Consul server with TLS. caFile, certFile, keyFile := test.GenerateServerCerts(t) - a, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + testserver, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { c.CAFile = caFile c.CertFile = certFile c.KeyFile = keyFile }) require.NoError(t, err) - defer a.Stop() + defer testserver.Stop() // Create a mesh gateway instance. client, err := api.NewClient(&api.Config{ - Address: a.HTTPSAddr, + Address: testserver.HTTPSAddr, Scheme: "https", TLSConfig: api.TLSConfig{ CAFile: caFile, @@ -1111,7 +1119,7 @@ func TestRun_Autoencrypt(t *testing.T) { // was being used as the CA (since it's not a CA). "-server-ca-cert-file", keyFile, "-server-ca-key-file", keyFile, - "-http-addr", fmt.Sprintf("https://%s", a.HTTPSAddr), + "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-consul-api-timeout", "10s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) From 10c582f1f3ed0e6fb8271a6d3aaa67140a1ab095 Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:52:25 -0700 Subject: [PATCH 187/592] Increase timeout and backoff for retry on flaky test (#2242) --- control-plane/subcommand/get-consul-client-ca/command_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/control-plane/subcommand/get-consul-client-ca/command_test.go b/control-plane/subcommand/get-consul-client-ca/command_test.go index 95ff4dbfbb..68bdd918b1 100644 --- a/control-plane/subcommand/get-consul-client-ca/command_test.go +++ b/control-plane/subcommand/get-consul-client-ca/command_test.go @@ -203,9 +203,10 @@ func TestRun_ConsulServerAvailableLater(t *testing.T) { }) require.NoError(t, err) + retrier := &retry.Timer{Timeout: 20 * time.Second, Wait: 1 * time.Second} // get the actual ca cert from consul var expectedCARoot string - retry.Run(t, func(r *retry.R) { + retry.RunWith(retrier, t, func(r *retry.R) { roots, _, err := client.Agent().ConnectCARoots(nil) require.NoError(r, err) require.NotNil(r, roots) From 46055a324f8b1fcfb6e6debf95f5d059d7b50fe4 Mon Sep 17 00:00:00 2001 From: Joshua Timmons Date: Thu, 1 Jun 2023 19:22:48 -0400 Subject: [PATCH 188/592] Add fake demo/crds to get around that expectation in chart install (#2245) --- charts/demo/crds/blank | 4 ++++ charts/demo/templates/intentions.yaml | 11 +++++++++++ charts/embed_chart.go | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 charts/demo/crds/blank diff --git a/charts/demo/crds/blank b/charts/demo/crds/blank new file mode 100644 index 0000000000..a6560dc716 --- /dev/null +++ b/charts/demo/crds/blank @@ -0,0 +1,4 @@ +This is here purely so we can embed it so the HelmDeployment that references does not fail. Otherwise, at the time of writing, we get: + +==> Installing Consul demo application + ! open demo/crds: file does not exist \ No newline at end of file diff --git a/charts/demo/templates/intentions.yaml b/charts/demo/templates/intentions.yaml index a268e4fa4b..ef36025b16 100644 --- a/charts/demo/templates/intentions.yaml +++ b/charts/demo/templates/intentions.yaml @@ -58,6 +58,17 @@ spec: --- apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceIntentions +metadata: + name: consul-telemetry-collector +spec: + destination: + name: 'consul-telemetry-collector' + sources: + - name: '*' + action: allow +--- +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions metadata: name: deny-all spec: diff --git a/charts/embed_chart.go b/charts/embed_chart.go index bb3898a421..72650e8819 100644 --- a/charts/embed_chart.go +++ b/charts/embed_chart.go @@ -15,9 +15,9 @@ import "embed" // // The embed directive does not include files with underscores unless explicitly listed, which is why _helpers.tpl is // explicitly embedded. -// + //go:embed consul/Chart.yaml consul/values.yaml consul/crds consul/templates consul/templates/_helpers.tpl var ConsulHelmChart embed.FS -//go:embed demo/Chart.yaml demo/values.yaml demo/templates +//go:embed demo/Chart.yaml demo/values.yaml demo/crds/blank demo/templates var DemoHelmChart embed.FS From d4b8c733f639f58c7bed43755d880d4fc6c26f22 Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Thu, 1 Jun 2023 20:08:04 -0700 Subject: [PATCH 189/592] NET-4285 add check for pointer (#2246) --- .../api/v1alpha1/servicedefaults_types.go | 10 ++- .../v1alpha1/servicedefaults_types_test.go | 85 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index cfb8865bdf..2f3b95a297 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "strings" + "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -465,13 +466,20 @@ func (in *PassiveHealthCheck) toConsul() *capi.PassiveHealthCheck { if in == nil { return nil } + var baseEjectiontime *time.Duration + if in.BaseEjectionTime == nil { + dur := time.Second * 30 + baseEjectiontime = &dur + } else { + baseEjectiontime = &in.BaseEjectionTime.Duration + } return &capi.PassiveHealthCheck{ Interval: in.Interval.Duration, MaxFailures: in.MaxFailures, EnforcingConsecutive5xx: in.EnforcingConsecutive5xx, MaxEjectionPercent: in.MaxEjectionPercent, - BaseEjectionTime: &in.BaseEjectionTime.Duration, + BaseEjectionTime: baseEjectiontime, } } diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 0a5a59e4d1..31a41f3f06 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -330,6 +330,91 @@ func TestServiceDefaults_ToConsul(t *testing.T) { } } +func TestPasstiveHealthCheckConsul(t *testing.T) { + baseDur := time.Second * 30 + baseEjection := time.Second * 60 + baseInt := uint32(1) + for name, tc := range map[string]struct { + input *PassiveHealthCheck + output *capi.PassiveHealthCheck + }{ + "basenil": {}, + "base": { + input: &PassiveHealthCheck{}, + output: &capi.PassiveHealthCheck{BaseEjectionTime: &baseDur}, + }, + "with_interval": { + input: &PassiveHealthCheck{ + Interval: metav1.Duration{Duration: baseDur}, + }, + output: &capi.PassiveHealthCheck{ + Interval: time.Second * 30, + BaseEjectionTime: &baseDur, + }, + }, + "with_interval_maxfailures": { + input: &PassiveHealthCheck{ + Interval: metav1.Duration{Duration: baseDur}, + MaxFailures: 100, + }, + output: &capi.PassiveHealthCheck{ + MaxFailures: 100, + Interval: time.Second * 30, + BaseEjectionTime: &baseDur, + }, + }, + "with_interval_maxfailures_enforcing": { + input: &PassiveHealthCheck{ + Interval: metav1.Duration{Duration: baseDur}, + MaxFailures: 100, + EnforcingConsecutive5xx: &baseInt, + }, + output: &capi.PassiveHealthCheck{ + MaxFailures: 100, + Interval: time.Second * 30, + BaseEjectionTime: &baseDur, + EnforcingConsecutive5xx: &baseInt, + }, + }, + "with_interval_maxfailures_enforcing_maxejection": { + input: &PassiveHealthCheck{ + Interval: metav1.Duration{Duration: baseDur}, + MaxFailures: 100, + EnforcingConsecutive5xx: &baseInt, + MaxEjectionPercent: &baseInt, + }, + output: &capi.PassiveHealthCheck{ + MaxFailures: 100, + Interval: time.Second * 30, + BaseEjectionTime: &baseDur, + EnforcingConsecutive5xx: &baseInt, + MaxEjectionPercent: &baseInt, + }, + }, + "with_interval_maxfailures_enforcing_maxejection_baseejection": { + input: &PassiveHealthCheck{ + Interval: metav1.Duration{Duration: baseDur}, + MaxFailures: 100, + EnforcingConsecutive5xx: &baseInt, + MaxEjectionPercent: &baseInt, + BaseEjectionTime: &metav1.Duration{Duration: baseEjection}, + }, + output: &capi.PassiveHealthCheck{ + MaxFailures: 100, + Interval: time.Second * 30, + BaseEjectionTime: &baseEjection, + EnforcingConsecutive5xx: &baseInt, + MaxEjectionPercent: &baseInt, + }, + }, + } { + t.Run(name, func(t *testing.T) { + output := tc.input.toConsul() + require.Equal(t, tc.output, output) + }) + } +} + func TestServiceDefaults_MatchesConsul(t *testing.T) { cases := map[string]struct { internal *ServiceDefaults From 7b6e5eba6e49c97abb11d9832d2f7f6a6cab6d6d Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:11:34 -0500 Subject: [PATCH 190/592] Persist virtual-ips for intentions / service-defaults. (#2222) --- .../controllers/configentry_controller.go | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/control-plane/controllers/configentry_controller.go b/control-plane/controllers/configentry_controller.go index 374af7451e..133afc0a1c 100644 --- a/control-plane/controllers/configentry_controller.go +++ b/control-plane/controllers/configentry_controller.go @@ -270,7 +270,7 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont // For resolvers and splitters, we need to set the ClusterIP of the matching service to Consul so that transparent // proxy works correctly. Do not fail the reconcile if assigning the virtual IP returns an error. - if needsVirtualIPAssignment(configEntry) { + if needsVirtualIPAssignment(r.DatacenterName, configEntry) { err = assignServiceVirtualIP(ctx, logger, consulClient, crdCtrl, req.NamespacedName, configEntry, r.DatacenterName) if err != nil { logger.Error(err, "failed assigning service virtual ip") @@ -392,10 +392,27 @@ func (r *ConfigEntryController) nonMatchingMigrationError(kubeEntry common.Confi } // needsVirtualIPAssignment checks to see if a configEntry type needs to be assigned a virtual IP. -func needsVirtualIPAssignment(configEntry common.ConfigEntryResource) bool { - kubeKind := configEntry.KubeKind() - if kubeKind == common.ServiceResolver || kubeKind == common.ServiceRouter || kubeKind == common.ServiceSplitter { +func needsVirtualIPAssignment(datacenterName string, configEntry common.ConfigEntryResource) bool { + switch configEntry.KubeKind() { + case common.ServiceResolver: return true + case common.ServiceRouter: + return true + case common.ServiceSplitter: + return true + case common.ServiceDefaults: + return true + case common.ServiceIntentions: + entry := configEntry.ToConsul(datacenterName) + intention, ok := entry.(*capi.ServiceIntentionsConfigEntry) + if !ok { + return false + } + // We should not persist virtual ips if the destination is a wildcard + // in any form, since that would target multiple services. + return !strings.Contains(intention.Name, "*") && + !strings.Contains(intention.Namespace, "*") && + !strings.Contains(intention.Partition, "*") } return false } @@ -422,13 +439,14 @@ func assignServiceVirtualIP(ctx context.Context, logger logr.Logger, consulClien return err } + consulType := configEntry.ToConsul(datacenter) wo := &capi.WriteOptions{ - Namespace: configEntry.ToConsul(datacenter).GetNamespace(), - Partition: configEntry.ToConsul(datacenter).GetPartition(), + Namespace: consulType.GetNamespace(), + Partition: consulType.GetPartition(), } logger.Info("adding manual ip to virtual ip table in Consul", "name", service.Name) - _, _, err := consulClient.Internal().AssignServiceVirtualIP(ctx, configEntry.KubernetesName(), []string{service.Spec.ClusterIP}, wo) + _, _, err := consulClient.Internal().AssignServiceVirtualIP(ctx, consulType.GetName(), []string{service.Spec.ClusterIP}, wo) if err != nil { // Maintain backwards compatibility with older versions of Consul that do not support the manual VIP improvements. With the older version, the mesh // will still work. From b922ef2d3760744143f8cf606891bd091b46091a Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 2 Jun 2023 11:28:18 -0400 Subject: [PATCH 191/592] Allow API Gateways to bind to privileged ports (#2253) --- acceptance/tests/api-gateway/api_gateway_test.go | 2 +- .../fixtures/bases/api-gateway/apigateway.yaml | 6 +++--- control-plane/api-gateway/gatekeeper/dataplane.go | 15 +++++++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index 006f1456d3..94a91e79c2 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -175,7 +175,7 @@ func TestAPIGateway_Basic(t *testing.T) { // finally we check that we can actually route to the service via the gateway k8sOptions := ctx.KubectlOptions(t) - targetAddress := fmt.Sprintf("http://%s:8080/", gatewayAddress) + targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) if c.secure { // check that intentions keep our connection from happening diff --git a/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml b/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml index f0e4eddc03..2a355e1b2f 100644 --- a/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml +++ b/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml @@ -9,19 +9,19 @@ spec: gatewayClassName: gateway-class listeners: - protocol: HTTP - port: 8080 + port: 80 name: http allowedRoutes: namespaces: from: "All" - protocol: TCP - port: 8081 + port: 81 name: tcp allowedRoutes: namespaces: from: "All" - protocol: HTTPS - port: 8082 + port: 443 name: https tls: certificateRefs: diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go index 90cd186743..36829e2b7c 100644 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ b/control-plane/api-gateway/gatekeeper/dataplane.go @@ -17,9 +17,10 @@ import ( ) const ( + allCapabilities = "all" + netBindCapability = "NET_BIND_SERVICE" consulDataplaneDNSBindHost = "127.0.0.1" consulDataplaneDNSBindPort = 8600 - sidecarUserAndGroupID = 5995 defaultPrometheusScrapePath = "/metrics" defaultEnvoyProxyConcurrency = 1 volumeName = "consul-connect-inject-data" @@ -103,10 +104,16 @@ func consulDataplaneContainer(config common.HelmConfig, name, namespace string) // skip setting the security context and let OpenShift set it for us. if !config.EnableOpenShift { container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), ReadOnlyRootFilesystem: pointer.Bool(true), + // We have to run as root if we want to bind to any + // sort of privileged ports. The drop "all" is intended + // to drop any Linux capabilities you'd get as root + // other than NET_BIND_SERVICE. + RunAsUser: pointer.Int64(0), + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{netBindCapability}, + Drop: []corev1.Capability{allCapabilities}, + }, } } From f9ad99446a23222f732b3f9858bcdb8f3e47f842 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 2 Jun 2023 11:46:43 -0400 Subject: [PATCH 192/592] API Gateway lifecycle acceptance tests (#2248) * initial test * More lifecycle work * functional lifecycle tests --- acceptance/go.mod | 11 + acceptance/go.sum | 20 +- .../api-gateway/api_gateway_lifecycle_test.go | 444 ++++++++++++++++++ .../api-gateway/api_gateway_tenancy_test.go | 36 +- control-plane/api-gateway/binding/binder.go | 11 - .../api-gateway/binding/binder_test.go | 45 +- .../api-gateway/binding/route_binding.go | 23 +- control-plane/api-gateway/common/resources.go | 142 +++--- .../controllers/gateway_controller.go | 80 ++-- .../controllers/gateway_controller_test.go | 297 ++++++++++++ .../api-gateway/controllers/index.go | 18 +- 11 files changed, 966 insertions(+), 161 deletions(-) create mode 100644 acceptance/tests/api-gateway/api_gateway_lifecycle_test.go create mode 100644 control-plane/api-gateway/controllers/gateway_controller_test.go diff --git a/acceptance/go.mod b/acceptance/go.mod index 88507a2d54..a63e1187fe 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -26,14 +26,18 @@ require ( github.com/aws/aws-sdk-go v1.44.262 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/deckarep/golang-set v1.7.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.2.3 // indirect @@ -42,6 +46,7 @@ require ( github.com/go-openapi/swag v0.22.3 // indirect github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect @@ -49,12 +54,15 @@ require ( github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect + github.com/hashicorp/consul-server-connection-manager v0.1.2 // indirect + github.com/hashicorp/consul/proto-public v0.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.2.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-netaddrs v0.1.0 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-retryablehttp v0.6.6 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect @@ -97,6 +105,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/urfave/cli v1.22.2 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.1.0 // indirect @@ -117,6 +126,8 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.26.3 // indirect + k8s.io/component-base v0.26.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index c0a0a6d870..1c9bd2ad25 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -104,8 +104,12 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -143,6 +147,8 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= +github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= @@ -189,6 +195,7 @@ github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -247,6 +254,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -333,8 +341,12 @@ github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+Xb github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb h1:9GUvDoKVoV3IW78QyfoNY4bRcKxcn26wTGLoBrz92N4= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb/go.mod h1:jKzTEgDc/np2gX/KPdfdm1mEUfZLrU8gc71XN3B15VI= +github.com/hashicorp/consul-server-connection-manager v0.1.2 h1:tNVQHUPuMbd+cMdD8kd+qkZUYpmLmrHMAV/49f4L53I= +github.com/hashicorp/consul-server-connection-manager v0.1.2/go.mod h1:NzQoVi1KcxGI2SangsDue8+ZPuXZWs+6BKAKrDNyg+w= github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 h1:6kUTk+YBgA5X5b3gNAoI18WEK4/t75LcWSimEgmpFdg= github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= +github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= +github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -358,6 +370,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-netaddrs v0.1.0 h1:TnlYvODD4C/wO+j7cX1z69kV5gOzI87u3OcUinANaW8= +github.com/hashicorp/go-netaddrs v0.1.0/go.mod h1:33+a/emi5R5dqRspOuZKO0E+Tuz5WV1F84eRWALkedA= github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= @@ -666,6 +680,7 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -857,6 +872,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1088,6 +1104,7 @@ k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= +k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= @@ -1100,7 +1117,8 @@ k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= -k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= +k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= diff --git a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go new file mode 100644 index 0000000000..c07075a709 --- /dev/null +++ b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go @@ -0,0 +1,444 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package apigateway + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestAPIGateway_Lifecycle(t *testing.T) { + ctx := suite.Environment().DefaultContext(t) + cfg := suite.Config() + helmValues := map[string]string{ + "global.logLevel": "trace", + "connectInject.enabled": "true", + } + + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + + consulCluster.Create(t) + + k8sClient := ctx.ControllerRuntimeClient(t) + consulClient, _ := consulCluster.SetupConsulClient(t, false) + + defaultNamespace := "default" + + // create a service to target + targetName := "static-server" + logger.Log(t, "creating target server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + // create a basic GatewayClassConfig + gatewayClassConfigName := "controlled-gateway-class-config" + gatewayClassConfig := &v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: gatewayClassConfigName, + }, + } + logger.Log(t, "creating gateway class config") + err := k8sClient.Create(context.Background(), gatewayClassConfig) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + logger.Log(t, "deleting all gateway class configs") + k8sClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}) + }) + + gatewayParametersRef := &gwv1beta1.ParametersReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: gwv1beta1.Kind(v1alpha1.GatewayClassConfigKind), + Name: gatewayClassConfigName, + } + + // create three gateway classes, two we control, one we don't + controlledGatewayClassOneName := "controlled-gateway-class-one" + logger.Log(t, "creating controlled gateway class one") + createGatewayClass(t, k8sClient, controlledGatewayClassOneName, gatewayClassControllerName, gatewayParametersRef) + + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + logger.Log(t, "deleting all gateway classes") + k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}) + }) + + controlledGatewayClassTwoName := "controlled-gateway-class-two" + logger.Log(t, "creating controlled gateway class two") + createGatewayClass(t, k8sClient, controlledGatewayClassTwoName, gatewayClassControllerName, gatewayParametersRef) + + uncontrolledGatewayClassName := "uncontrolled-gateway-class" + logger.Log(t, "creating uncontrolled gateway class") + createGatewayClass(t, k8sClient, uncontrolledGatewayClassName, "example.com/some-controller", nil) + + // Create a certificate to reference in listeners + certificateInfo := generateCertificate(t, nil, "certificate.consul.local") + certificateName := "certificate" + certificate := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: certificateName, + Namespace: defaultNamespace, + Labels: map[string]string{ + "test-certificate": "true", + }, + }, + Type: corev1.SecretTypeTLS, + Data: map[string][]byte{ + corev1.TLSCertKey: certificateInfo.CertPEM, + corev1.TLSPrivateKeyKey: certificateInfo.PrivateKeyPEM, + }, + } + logger.Log(t, "creating certificate") + err = k8sClient.Create(context.Background(), certificate) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8sClient.Delete(context.Background(), certificate) + }) + + // Create three gateways with a basic HTTPS listener to correspond to the three classes + controlledGatewayOneName := "controlled-gateway-one" + logger.Log(t, "creating controlled gateway one") + controlledGatewayOne := createGateway(t, k8sClient, controlledGatewayOneName, defaultNamespace, controlledGatewayClassOneName, certificateName) + + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + logger.Log(t, "deleting all gateways") + k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(defaultNamespace)) + }) + + controlledGatewayTwoName := "controlled-gateway-two" + logger.Log(t, "creating controlled gateway two") + controlledGatewayTwo := createGateway(t, k8sClient, controlledGatewayTwoName, defaultNamespace, controlledGatewayClassTwoName, certificateName) + + uncontrolledGatewayName := "uncontrolled-gateway" + logger.Log(t, "creating uncontrolled gateway") + _ = createGateway(t, k8sClient, uncontrolledGatewayName, defaultNamespace, uncontrolledGatewayClassName, certificateName) + + // create two http routes associated with the first controlled gateway + routeOneName := "route-one" + logger.Log(t, "creating route one") + routeOne := createRoute(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayOneName, targetName) + + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + logger.Log(t, "deleting all http routes") + k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.HTTPRoute{}, client.InNamespace(defaultNamespace)) + }) + + routeTwoName := "route-two" + logger.Log(t, "creating route two") + routeTwo := createRoute(t, k8sClient, routeTwoName, defaultNamespace, controlledGatewayTwoName, targetName) + + // Scenario: Swapping a route to another controlled gateway should clean up the old parent statuses and references on Consul resources + + // check that the route is bound properly and objects are reflected in Consul + logger.Log(t, "checking that http route one is bound to gateway one") + checkRouteBound(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayOneName) + + logger.Log(t, "checking that http route one is synchronized to Consul") + checkConsulRouteParent(t, consulClient, routeOneName, controlledGatewayOneName) + + // update the route to point to the other controlled gateway + logger.Log(t, "updating route one to be bound to gateway two") + updateKubernetes(t, k8sClient, routeOne, func(r *gwv1beta1.HTTPRoute) { + r.Spec.ParentRefs[0].Name = gwv1beta1.ObjectName(controlledGatewayTwoName) + }) + + // check that the route is bound properly and objects are reflected in Consul + logger.Log(t, "checking that http route one is bound to gateway two") + checkRouteBound(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayTwoName) + + logger.Log(t, "checking that http route one is synchronized to Consul") + checkConsulRouteParent(t, consulClient, routeOneName, controlledGatewayTwoName) + + // Scenario: Binding a route to a controlled gateway and then associating it with another gateway we don’t control should clean up Consul resources, route statuses, and finalizers + // check that the route is bound properly and objects are reflected in Consul + + // check that our second http route is bound properly + logger.Log(t, "checking that http route two is bound to gateway two") + checkRouteBound(t, k8sClient, routeTwoName, defaultNamespace, controlledGatewayTwoName) + + logger.Log(t, "checking that http route two is synchronized to Consul") + checkConsulRouteParent(t, consulClient, routeTwoName, controlledGatewayTwoName) + + // update the route to point to the uncontrolled gateway + logger.Log(t, "updating route two to be bound to an uncontrolled gateway") + updateKubernetes(t, k8sClient, routeTwo, func(r *gwv1beta1.HTTPRoute) { + r.Spec.ParentRefs[0].Name = gwv1beta1.ObjectName(uncontrolledGatewayName) + }) + + // check that the route is unbound and all Consul objects and Kubernetes statuses are cleaned up + logger.Log(t, "checking that http route two is cleaned up because we no longer control it") + checkEmptyRoute(t, k8sClient, routeTwoName, defaultNamespace) + + logger.Log(t, "checking that http route two is deleted from Consul") + checkConsulNotExists(t, consulClient, api.HTTPRoute, routeTwoName) + + // Scenario: Switching a controlled gateway’s protocol that causes a route to unbind should cause the route to drop the parent ref in Consul and result in proper statuses set in Kubernetes + + // swap the gateway's protocol and see the route unbind + logger.Log(t, "marking gateway two as using TCP") + updateKubernetes(t, k8sClient, controlledGatewayTwo, func(g *gwv1beta1.Gateway) { + g.Spec.Listeners[0].Protocol = gwv1beta1.TCPProtocolType + }) + + // check that the route is unbound and all Consul objects and Kubernetes statuses are cleaned up + logger.Log(t, "checking that http route one is not bound to gateway two") + retryCheck(t, 10, func(r *retry.R) { + var route gwv1beta1.HTTPRoute + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: routeOneName, Namespace: defaultNamespace}, &route) + require.NoError(r, err) + + require.Len(r, route.Status.Parents, 1) + require.EqualValues(r, controlledGatewayTwoName, route.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, route.Status.Parents[0].Conditions, falseCondition("Accepted", "NotAllowedByListeners")) + }) + + logger.Log(t, "checking that route one is deleted from Consul") + checkConsulNotExists(t, consulClient, api.HTTPRoute, routeOneName) + + // Scenario: Deleting a gateway should result in routes only referencing it to get cleaned up from Consul and their statuses/finalizers cleared, but routes referencing another controlled gateway should still exist in Consul and only have their statuses cleaned up from referencing the gateway we previously controlled. Any referenced certificates should also get cleaned up. + + // delete gateway two + logger.Log(t, "deleting gateway two in Kubernetes") + err = k8sClient.Delete(context.Background(), controlledGatewayTwo) + require.NoError(t, err) + + // check that the gateway is deleted from Consul + logger.Log(t, "checking that gateway two is deleted from Consul") + checkConsulNotExists(t, consulClient, api.APIGateway, controlledGatewayTwoName) + + // check that the Kubernetes route is cleaned up and the entries deleted from Consul + logger.Log(t, "checking that http route one is cleaned up in Kubernetes") + checkEmptyRoute(t, k8sClient, routeOneName, defaultNamespace) + + // Scenario: Changing a gateway class name on a gateway to something we don’t control should have the same affect as deleting it with the addition of cleaning up our finalizer from the gateway. + + // reset route one to point to our first gateway and check that it's bound properly + logger.Log(t, "remarking route one as bound to gateway one") + updateKubernetes(t, k8sClient, routeOne, func(r *gwv1beta1.HTTPRoute) { + r.Spec.ParentRefs[0].Name = gwv1beta1.ObjectName(controlledGatewayOneName) + }) + + logger.Log(t, "checking that http route one is bound to gateway one") + checkRouteBound(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayOneName) + + logger.Log(t, "checking that http route one is synchronized to Consul") + checkConsulRouteParent(t, consulClient, routeOneName, controlledGatewayOneName) + + // make the gateway uncontrolled by pointing to a non-existent gateway class + logger.Log(t, "marking gateway one as not controlled by our controller") + updateKubernetes(t, k8sClient, controlledGatewayOne, func(g *gwv1beta1.Gateway) { + g.Spec.GatewayClassName = "non-existent" + }) + + // check that the Kubernetes gateway is cleaned up + logger.Log(t, "checking that gateway one is cleaned up in Kubernetes") + retryCheck(t, 10, func(r *retry.R) { + var route gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: controlledGatewayOneName, Namespace: defaultNamespace}, &route) + require.NoError(r, err) + + require.Len(r, route.Finalizers, 0) + }) + + // check that the gateway is deleted from Consul + logger.Log(t, "checking that gateway one is deleted from Consul") + checkConsulNotExists(t, consulClient, api.APIGateway, controlledGatewayOneName) + + // check that the Kubernetes route is cleaned up and the entries deleted from Consul + logger.Log(t, "checking that http route one is cleaned up in Kubernetes") + checkEmptyRoute(t, k8sClient, routeOneName, defaultNamespace) + + logger.Log(t, "checking that http route one is deleted from Consul") + checkConsulNotExists(t, consulClient, api.HTTPRoute, routeOneName) + + // Scenario: Deleting a certificate referenced by a gateway’s listener should make the listener invalid and drop it from Consul. + + // reset the gateway + logger.Log(t, "remarking gateway one as controlled by our controller") + updateKubernetes(t, k8sClient, controlledGatewayOne, func(g *gwv1beta1.Gateway) { + g.Spec.GatewayClassName = gwv1beta1.ObjectName(controlledGatewayClassOneName) + }) + + // make sure it exists + logger.Log(t, "checking that gateway one is synchronized to Consul") + checkConsulExists(t, consulClient, api.APIGateway, controlledGatewayOneName) + + // make sure our certificate exists + logger.Log(t, "checking that the certificate is synchronized to Consul") + checkConsulExists(t, consulClient, api.InlineCertificate, certificateName) + + // delete the certificate in Kubernetes + logger.Log(t, "deleting the certificate in Kubernetes") + err = k8sClient.Delete(context.Background(), certificate) + require.NoError(t, err) + + // make sure the certificate no longer exists in Consul + logger.Log(t, "checking that the certificate is deleted from Consul") + checkConsulNotExists(t, consulClient, api.InlineCertificate, certificateName) +} + +func checkConsulNotExists(t *testing.T, client *api.Client, kind, name string, namespace ...string) { + t.Helper() + + opts := &api.QueryOptions{} + if len(namespace) != 0 { + opts.Namespace = namespace[0] + } + + retryCheck(t, 10, func(r *retry.R) { + _, _, err := client.ConfigEntries().Get(kind, name, opts) + require.Error(r, err) + require.EqualError(r, err, fmt.Sprintf("Unexpected response code: 404 (Config entry not found for %q / %q)", kind, name)) + }) +} + +func checkConsulExists(t *testing.T, client *api.Client, kind, name string) { + t.Helper() + + retryCheck(t, 10, func(r *retry.R) { + _, _, err := client.ConfigEntries().Get(kind, name, nil) + require.NoError(r, err) + }) +} + +func checkConsulRouteParent(t *testing.T, client *api.Client, name, parent string) { + t.Helper() + + retryCheck(t, 10, func(r *retry.R) { + entry, _, err := client.ConfigEntries().Get(api.HTTPRoute, name, nil) + require.NoError(r, err) + route := entry.(*api.HTTPRouteConfigEntry) + + require.Len(r, route.Parents, 1) + require.Equal(r, parent, route.Parents[0].Name) + }) +} + +func checkEmptyRoute(t *testing.T, client client.Client, name, namespace string) { + t.Helper() + + retryCheck(t, 10, func(r *retry.R) { + var route gwv1beta1.HTTPRoute + err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &route) + require.NoError(r, err) + + require.Len(r, route.Status.Parents, 0) + require.Len(r, route.Finalizers, 0) + }) +} + +func checkRouteBound(t *testing.T, client client.Client, name, namespace, parent string) { + t.Helper() + + retryCheck(t, 10, func(r *retry.R) { + var route gwv1beta1.HTTPRoute + err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &route) + require.NoError(r, err) + + require.Len(r, route.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, route.Status.Parents[0].ControllerName) + require.EqualValues(r, parent, route.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, route.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, route.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, route.Status.Parents[0].Conditions, trueCondition("Synced", "Synced")) + }) +} + +func updateKubernetes[T client.Object](t *testing.T, k8sClient client.Client, o T, fn func(o T)) { + t.Helper() + + err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(o), o) + require.NoError(t, err) + fn(o) + err = k8sClient.Update(context.Background(), o) + require.NoError(t, err) +} + +func createRoute(t *testing.T, client client.Client, name, namespace, parent, target string) *gwv1beta1.HTTPRoute { + t.Helper() + + route := &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: gwv1beta1.ObjectName(parent)}, + }, + }, + Rules: []gwv1beta1.HTTPRouteRule{ + {BackendRefs: []gwv1beta1.HTTPBackendRef{ + {BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{Name: gwv1beta1.ObjectName(target)}, + }}, + }}, + }, + }, + } + + err := client.Create(context.Background(), route) + require.NoError(t, err) + return route +} + +func createGateway(t *testing.T, client client.Client, name, namespace, gatewayClass, certificate string) *gwv1beta1.Gateway { + t.Helper() + + gateway := &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gatewayClass), + Listeners: []gwv1beta1.Listener{{ + Name: gwv1beta1.SectionName("listener"), + Protocol: gwv1beta1.HTTPSProtocolType, + Port: 8443, + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{{ + Name: gwv1beta1.ObjectName(certificate), + }}, + }, + }}, + }, + } + + err := client.Create(context.Background(), gateway) + require.NoError(t, err) + + return gateway +} + +func createGatewayClass(t *testing.T, client client.Client, name, controllerName string, parameters *gwv1beta1.ParametersReference) { + t.Helper() + + gatewayClass := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: gwv1beta1.GatewayController(controllerName), + ParametersRef: parameters, + }, + } + + err := client.Create(context.Background(), gatewayClass) + require.NoError(t, err) +} diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go index 3455b69f58..d5a0845810 100644 --- a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go +++ b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go @@ -148,14 +148,8 @@ func TestAPIGateway_Tenancy(t *testing.T) { checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("ResolvedRefs", "RefNotPermitted")) }) - retryCheck(t, 10, func(r *retry.R) { - // since the sync operation should fail above, check that we don't have the entry in Consul. - _, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", &api.QueryOptions{ - Namespace: namespaceForConsul(c.namespaceMirroring, gatewayNamespace), - }) - require.Error(r, err) - require.EqualError(r, err, `Unexpected response code: 404 (Config entry not found for "api-gateway" / "gateway")`) - }) + // since the sync operation should fail above, check that we don't have the entry in Consul. + checkConsulNotExists(t, consulClient, api.APIGateway, "gateway", namespaceForConsul(c.namespaceMirroring, gatewayNamespace)) // route failure retryCheck(t, 10, func(r *retry.R) { @@ -170,31 +164,13 @@ func TestAPIGateway_Tenancy(t *testing.T) { require.EqualValues(r, gatewayNamespace, *httproute.Status.Parents[0].ParentRef.Namespace) checkStatusCondition(r, httproute.Status.Parents[0].Conditions, falseCondition("Accepted", "RefNotPermitted")) checkStatusCondition(r, httproute.Status.Parents[0].Conditions, falseCondition("ResolvedRefs", "RefNotPermitted")) - // the route itself actually gets synced to Consul - checkStatusCondition(r, httproute.Status.Parents[0].Conditions, trueCondition("Synced", "Synced")) }) - retryCheck(t, 10, func(r *retry.R) { - // since we're not bound, check to make sure that the route doesn't target the gateway in Consul. - entry, _, err := consulClient.ConfigEntries().Get(api.HTTPRoute, "route", &api.QueryOptions{ - Namespace: namespaceForConsul(c.namespaceMirroring, routeNamespace), - }) - require.NoError(r, err) - route := entry.(*api.HTTPRouteConfigEntry) - - require.EqualValues(r, "route", route.Meta["k8s-name"]) - require.EqualValues(r, routeNamespace, route.Meta["k8s-namespace"]) - require.Len(r, route.Parents, 0) - }) + // since we're not bound to anything, check to make sure that the route doesn't get created in Consul. + checkConsulNotExists(t, consulClient, api.HTTPRoute, "route", namespaceForConsul(c.namespaceMirroring, routeNamespace)) - retryCheck(t, 10, func(r *retry.R) { - // we only sync validly referenced certificates over, so check to make sure it is not created. - _, _, err := consulClient.ConfigEntries().Get(api.InlineCertificate, "certificate", &api.QueryOptions{ - Namespace: namespaceForConsul(c.namespaceMirroring, certificateNamespace), - }) - require.Error(r, err) - require.EqualError(r, err, `Unexpected response code: 404 (Config entry not found for "inline-certificate" / "certificate")`) - }) + // we only sync validly referenced certificates over, so check to make sure it is not created. + checkConsulNotExists(t, consulClient, api.InlineCertificate, "certificate", namespaceForConsul(c.namespaceMirroring, certificateNamespace)) // now create reference grants createReferenceGrant(t, k8sClient, "gateway-certificate", gatewayNamespace, certificateNamespace) diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index 391c083724..96056920c3 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -53,19 +53,8 @@ type BinderConfig struct { // Service is the deployed service associated with the Gateway deployment. Service *corev1.Service - // TODO: Do we need to pass in Routes that have references to a Gateway in their statuses - // for cleanup purposes or is the below enough for record keeping? - // ConsulGateway is the config entry we've created in Consul. ConsulGateway *api.APIGatewayConfigEntry - // ConsulHTTPRoutes are a list of HTTPRouteConfigEntry objects that currently reference the - // Gateway we've created in Consul. - ConsulHTTPRoutes []api.HTTPRouteConfigEntry - // ConsulTCPRoutes are a list of TCPRouteConfigEntry objects that currently reference the - // Gateway we've created in Consul. - ConsulTCPRoutes []api.TCPRouteConfigEntry - // ConsulInlineCertificates is a list of certificates that have been created in Consul. - ConsulInlineCertificates []api.InlineCertificateConfigEntry // GatewayServices are the services associated with the Gateway ConsulGatewayServices []api.CatalogService diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index 484b6d1a60..065290f56a 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -54,15 +54,16 @@ var ( ) type resourceMapResources struct { - grants []gwv1beta1.ReferenceGrant - secrets []corev1.Secret - gateways []gwv1beta1.Gateway - httpRoutes []gwv1beta1.HTTPRoute - tcpRoutes []gwv1alpha2.TCPRoute - meshServices []v1alpha1.MeshService - services []types.NamespacedName - consulHTTPRoutes []api.HTTPRouteConfigEntry - consulTCPRoutes []api.TCPRouteConfigEntry + grants []gwv1beta1.ReferenceGrant + secrets []corev1.Secret + gateways []gwv1beta1.Gateway + httpRoutes []gwv1beta1.HTTPRoute + tcpRoutes []gwv1alpha2.TCPRoute + meshServices []v1alpha1.MeshService + services []types.NamespacedName + consulInlineCertificates []api.InlineCertificateConfigEntry + consulHTTPRoutes []api.HTTPRouteConfigEntry + consulTCPRoutes []api.TCPRouteConfigEntry } func newTestResourceMap(t *testing.T, resources resourceMapResources) *common.ResourceMap { @@ -339,14 +340,16 @@ func TestBinder_Lifecycle(t *testing.T) { }, }, }}, - ConsulHTTPRoutes: []api.HTTPRouteConfigEntry{{ + }), + resources: resourceMapResources{ + consulHTTPRoutes: []api.HTTPRouteConfigEntry{{ Kind: api.HTTPRoute, Name: "route", Parents: []api.ResourceReference{ {Kind: api.APIGateway, Name: "gateway"}, }, }}, - }), + }, expectedUpdates: []client.Object{ &gwv1beta1.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ @@ -464,14 +467,16 @@ func TestBinder_Lifecycle(t *testing.T) { }, }, }}, - ConsulTCPRoutes: []api.TCPRouteConfigEntry{{ + }), + resources: resourceMapResources{ + consulTCPRoutes: []api.TCPRouteConfigEntry{{ Kind: api.TCPRoute, Name: "route", Parents: []api.ResourceReference{ {Kind: api.APIGateway, Name: "gateway"}, }, }}, - }), + }, expectedUpdates: []client.Object{ &gwv1alpha2.TCPRoute{ ObjectMeta: metav1.ObjectMeta{ @@ -573,7 +578,9 @@ func TestBinder_Lifecycle(t *testing.T) { }}, }), }, - ConsulHTTPRoutes: []api.HTTPRouteConfigEntry{ + }), + resources: resourceMapResources{ + consulHTTPRoutes: []api.HTTPRouteConfigEntry{ { Kind: api.HTTPRoute, Name: "http-route-two", Meta: map[string]string{ "k8s-name": "http-route-two", @@ -594,7 +601,7 @@ func TestBinder_Lifecycle(t *testing.T) { }, }, }, - ConsulTCPRoutes: []api.TCPRouteConfigEntry{ + consulTCPRoutes: []api.TCPRouteConfigEntry{ { Kind: api.TCPRoute, Name: "tcp-route-two", Meta: map[string]string{ @@ -617,12 +624,10 @@ func TestBinder_Lifecycle(t *testing.T) { }, }, }, - ConsulInlineCertificates: []api.InlineCertificateConfigEntry{ + consulInlineCertificates: []api.InlineCertificateConfigEntry{ *certificateOne, *certificateTwo, }, - }), - resources: resourceMapResources{ secrets: []corev1.Secret{ secretOne, secretTwo, @@ -755,8 +760,6 @@ func TestBinder_Lifecycle(t *testing.T) { tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) tt.resources.httpRoutes = append(tt.resources.httpRoutes, tt.config.HTTPRoutes...) tt.resources.tcpRoutes = append(tt.resources.tcpRoutes, tt.config.TCPRoutes...) - tt.resources.consulHTTPRoutes = append(tt.resources.consulHTTPRoutes, tt.config.ConsulHTTPRoutes...) - tt.resources.consulTCPRoutes = append(tt.resources.consulTCPRoutes, tt.config.ConsulTCPRoutes...) tt.config.Resources = newTestResourceMap(t, tt.resources) tt.config.ControllerName = testControllerName @@ -849,8 +852,6 @@ func TestBinder_Registrations(t *testing.T) { tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) tt.resources.httpRoutes = append(tt.resources.httpRoutes, tt.config.HTTPRoutes...) tt.resources.tcpRoutes = append(tt.resources.tcpRoutes, tt.config.TCPRoutes...) - tt.resources.consulHTTPRoutes = append(tt.resources.consulHTTPRoutes, tt.config.ConsulHTTPRoutes...) - tt.resources.consulTCPRoutes = append(tt.resources.consulTCPRoutes, tt.config.ConsulTCPRoutes...) tt.config.Resources = newTestResourceMap(t, tt.resources) tt.config.ControllerName = testControllerName diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index 187d579852..2a3be4884b 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -20,6 +20,11 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section // on non-enterprise installations routeConsulKey := r.config.Translator.NonNormalizedConfigEntryReference(entryKind(route), client.ObjectKeyFromObject(route)) filteredParents := filterParentRefs(r.key, route.GetNamespace(), getRouteParents(route)) + filteredParentStatuses := filterParentRefs(r.key, route.GetNamespace(), + common.ConvertSliceFunc(getRouteParentsStatus(route), func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { + return parentStatus.ParentRef + }), + ) // flags to mark that some operation needs to occur kubernetesNeedsUpdate := false @@ -58,6 +63,9 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section } // drop the status conditions + if r.statusSetter.removeRouteReferences(route, filteredParentStatuses) { + kubernetesNeedsStatusUpdate = true + } if r.statusSetter.removeRouteReferences(route, filteredParents) { kubernetesNeedsStatusUpdate = true } @@ -69,8 +77,6 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section return } - // TODO: scrub route refs from statuses that no longer exist - validation := validateRefs(route, getRouteBackends(route), r.config.Resources) // the spec is dumb and makes you set a parent for any status, even when the // status is not with respect to a parent, as is the case of resolved refs @@ -80,6 +86,14 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section kubernetesNeedsStatusUpdate = true } } + // if we're orphaned from this gateway we'll + // always need a status update. + if len(filteredParents) == 0 { + // we already checked that these refs existed, so no need to check + // the return value here. + _ = r.statusSetter.removeRouteReferences(route, filteredParentStatuses) + kubernetesNeedsStatusUpdate = true + } namespace := r.config.Namespaces[route.GetNamespace()] groupKind := route.GetObjectKind().GroupVersionKind().GroupKind() @@ -227,6 +241,11 @@ func (r *Binder) dropConsulRouteParent(snapshot *Snapshot, object client.Object, } func (r *Binder) mutateRouteWithBindingResults(snapshot *Snapshot, object client.Object, gatewayConsulKey api.ResourceReference, resources *common.ResourceMap, results parentBindResults) { + if results.boundSections().Cardinality() == 0 { + r.dropConsulRouteParent(snapshot, object, r.nonNormalizedConsulKey, r.config.Resources) + return + } + key := client.ObjectKeyFromObject(object) parents := mapset.NewSet() diff --git a/control-plane/api-gateway/common/resources.go b/control-plane/api-gateway/common/resources.go index 8696dcacb7..4cd3bfc3d2 100644 --- a/control-plane/api-gateway/common/resources.go +++ b/control-plane/api-gateway/common/resources.go @@ -68,7 +68,7 @@ type ReferenceValidator interface { } type certificate struct { - secret corev1.Secret + secret *corev1.Secret gateways mapset.Set } @@ -118,9 +118,8 @@ type ResourceMap struct { gatewayResources map[api.ResourceReference]*resourceSet // consul resources for a gateway - consulTCPRoutes map[api.ResourceReference]*consulTCPRoute - consulHTTPRoutes map[api.ResourceReference]*consulHTTPRoute - consulInlineCertificates map[api.ResourceReference]mapset.Set + consulTCPRoutes map[api.ResourceReference]*consulTCPRoute + consulHTTPRoutes map[api.ResourceReference]*consulHTTPRoute // mutations consulMutations []*ConsulUpdateOperation @@ -128,20 +127,19 @@ type ResourceMap struct { func NewResourceMap(translator ResourceTranslator, validator ReferenceValidator, logger logr.Logger) *ResourceMap { return &ResourceMap{ - translator: translator, - referenceValidator: validator, - logger: logger, - processedCertificates: mapset.NewSet(), - services: make(map[types.NamespacedName]api.ResourceReference), - meshServices: make(map[types.NamespacedName]api.ResourceReference), - certificates: mapset.NewSet(), - consulTCPRoutes: make(map[api.ResourceReference]*consulTCPRoute), - consulHTTPRoutes: make(map[api.ResourceReference]*consulHTTPRoute), - consulInlineCertificates: make(map[api.ResourceReference]mapset.Set), - certificateGateways: make(map[api.ResourceReference]*certificate), - tcpRouteGateways: make(map[api.ResourceReference]*tcpRoute), - httpRouteGateways: make(map[api.ResourceReference]*httpRoute), - gatewayResources: make(map[api.ResourceReference]*resourceSet), + translator: translator, + referenceValidator: validator, + logger: logger, + processedCertificates: mapset.NewSet(), + services: make(map[types.NamespacedName]api.ResourceReference), + meshServices: make(map[types.NamespacedName]api.ResourceReference), + certificates: mapset.NewSet(), + consulTCPRoutes: make(map[api.ResourceReference]*consulTCPRoute), + consulHTTPRoutes: make(map[api.ResourceReference]*consulHTTPRoute), + certificateGateways: make(map[api.ResourceReference]*certificate), + tcpRouteGateways: make(map[api.ResourceReference]*tcpRoute), + httpRouteGateways: make(map[api.ResourceReference]*httpRoute), + gatewayResources: make(map[api.ResourceReference]*resourceSet), } } @@ -190,7 +188,7 @@ func (s *ResourceMap) Certificate(key types.NamespacedName) *corev1.Secret { } consulKey := NormalizeMeta(s.toConsulReference(api.InlineCertificate, key)) if secret, ok := s.certificateGateways[consulKey]; ok { - return &secret.secret + return secret.secret } return nil } @@ -201,7 +199,7 @@ func (s *ResourceMap) ReferenceCountCertificate(secret corev1.Secret) { consulKey := NormalizeMeta(s.toConsulReference(api.InlineCertificate, key)) if _, ok := s.certificateGateways[consulKey]; !ok { s.certificateGateways[consulKey] = &certificate{ - secret: secret, + secret: &secret, gateways: mapset.NewSet(), } } @@ -320,6 +318,21 @@ func (s *ResourceMap) ReferenceCountConsulTCPRoute(route api.TCPRouteConfigEntry s.consulTCPRoutes[NormalizeMeta(key)] = set } +func (s *ResourceMap) ReferenceCountConsulCertificate(cert api.InlineCertificateConfigEntry) { + key := s.objectReference(&cert) + + var referenced *certificate + if existing, ok := s.certificateGateways[NormalizeMeta(key)]; ok { + referenced = existing + } else { + referenced = &certificate{ + gateways: mapset.NewSet(), + } + } + + s.certificateGateways[NormalizeMeta(key)] = referenced +} + func (s *ResourceMap) consulGatewaysForRoute(namespace string, refs []api.ResourceReference) mapset.Set { gateways := mapset.NewSet() @@ -400,21 +413,28 @@ func (s *ResourceMap) TranslateAndMutateHTTPRoute(key types.NamespacedName, onUp consulRoute, ok := s.consulHTTPRoutes[consulKey] if ok { - // remove from the consulHTTPRoutes map since we don't want to - // GC it in the end - delete(s.consulHTTPRoutes, consulKey) mutated := mutateFn(&consulRoute.route, *translated) + if len(mutated.Parents) != 0 { + // if we don't have any parents set, we keep this around to allow the route + // to be GC'd. + delete(s.consulHTTPRoutes, consulKey) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) + } + return + } + mutated := mutateFn(nil, *translated) + if len(mutated.Parents) != 0 { + // if we don't have any parents set, we keep this around to allow the route + // to be GC'd. + delete(s.consulHTTPRoutes, consulKey) s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ Entry: &mutated, OnUpdate: onUpdate, }) - return } - mutated := mutateFn(nil, *translated) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, - }) } func (s *ResourceMap) MutateHTTPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { @@ -422,15 +442,16 @@ func (s *ResourceMap) MutateHTTPRoute(key types.NamespacedName, onUpdate func(er consulRoute, ok := s.consulHTTPRoutes[consulKey] if ok { - // remove from the consulHTTPRoutes map since we don't want to - // GC it in the end - delete(s.consulHTTPRoutes, consulKey) mutated := mutateFn(consulRoute.route) - // add it to the mutation set - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, - }) + if len(mutated.Parents) != 0 { + // if we don't have any parents set, we keep this around to allow the route + // to be GC'd. + delete(s.consulHTTPRoutes, consulKey) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) + } } } @@ -454,20 +475,28 @@ func (s *ResourceMap) TranslateAndMutateTCPRoute(key types.NamespacedName, onUpd consulRoute, ok := s.consulTCPRoutes[consulKey] if ok { - // remove from the consulTCPRoutes map since we don't want to - // GC it in the end mutated := mutateFn(&consulRoute.route, *translated) + if len(mutated.Parents) != 0 { + // if we don't have any parents set, we keep this around to allow the route + // to be GC'd. + delete(s.consulTCPRoutes, consulKey) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) + } + return + } + mutated := mutateFn(nil, *translated) + if len(mutated.Parents) != 0 { + // if we don't have any parents set, we keep this around to allow the route + // to be GC'd. + delete(s.consulTCPRoutes, consulKey) s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ Entry: &mutated, OnUpdate: onUpdate, }) - return } - mutated := mutateFn(nil, *translated) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, - }) } func (s *ResourceMap) MutateTCPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { @@ -475,15 +504,16 @@ func (s *ResourceMap) MutateTCPRoute(key types.NamespacedName, onUpdate func(err consulRoute, ok := s.consulTCPRoutes[consulKey] if ok { - // remove from the consulTCPRoutes map since we don't want to - // GC it in the end - delete(s.consulTCPRoutes, consulKey) mutated := mutateFn(consulRoute.route) - // add it to the mutation set - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, - }) + if len(mutated.Parents) != 0 { + // if we don't have any parents set, we keep this around to allow the route + // to be GC'd. + delete(s.consulTCPRoutes, consulKey) + s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ + Entry: &mutated, + OnUpdate: onUpdate, + }) + } } } @@ -502,7 +532,11 @@ func (s *ResourceMap) TranslateInlineCertificate(key types.NamespacedName) error return nil } - consulCertificate, err := s.translator.ToInlineCertificate(certificate.secret) + if certificate.secret == nil { + return nil + } + + consulCertificate, err := s.translator.ToInlineCertificate(*certificate.secret) if err != nil { return err } diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index 691c25bf72..8baa27cca1 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -129,6 +129,15 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } + // fetch our inline certificates from cache, this needs to happen + // here since the certificates need to be reference counted before + // the gateways. + r.fetchConsulInlineCertificates(resources) + + // add our current gateway even if it's not controlled by us so we + // can garbage collect any resources for it. + resources.ReferenceCountGateway(gateway) + if err := r.fetchControlledGateways(ctx, resources); err != nil { log.Error(err, "unable to fetch controlled gateways") return ctrl.Result{}, err @@ -153,31 +162,27 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - // fetch all consul objects from cache + // fetch the rest of the consul objects from cache consulServices := r.getConsulServices(consulKey) consulGateway := r.getConsulGateway(consulKey) - consulHTTPRoutes := r.getConsulHTTPRoutes(consulKey, resources) - consulTCPRoutes := r.getConsulTCPRoutes(consulKey, resources) - consulInlineCertificates := r.getConsulInlineCertificates() + r.fetchConsulHTTPRoutes(consulKey, resources) + r.fetchConsulTCPRoutes(consulKey, resources) binder := binding.NewBinder(binding.BinderConfig{ - Logger: log, - Translator: r.Translator, - ControllerName: GatewayClassControllerName, - Namespaces: namespaces, - GatewayClassConfig: gatewayClassConfig, - GatewayClass: gatewayClass, - Gateway: gateway, - Pods: pods, - Service: service, - HTTPRoutes: httpRoutes, - TCPRoutes: tcpRoutes, - Resources: resources, - ConsulGateway: consulGateway, - ConsulHTTPRoutes: consulHTTPRoutes, - ConsulTCPRoutes: consulTCPRoutes, - ConsulInlineCertificates: consulInlineCertificates, - ConsulGatewayServices: consulServices, + Logger: log, + Translator: r.Translator, + ControllerName: GatewayClassControllerName, + Namespaces: namespaces, + GatewayClassConfig: gatewayClassConfig, + GatewayClass: gatewayClass, + Gateway: gateway, + Pods: pods, + Service: service, + HTTPRoutes: httpRoutes, + TCPRoutes: tcpRoutes, + Resources: resources, + ConsulGateway: consulGateway, + ConsulGatewayServices: consulServices, }) updates := binder.Snapshot() @@ -417,7 +422,12 @@ func (r *GatewayController) transformGatewayClass(ctx context.Context) func(o cl func (r *GatewayController) transformHTTPRoute(ctx context.Context) func(o client.Object) []reconcile.Request { return func(o client.Object) []reconcile.Request { route := o.(*gwv1beta1.HTTPRoute) - return refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs)) + + refs := refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs)) + statusRefs := refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, common.ConvertSliceFunc(route.Status.Parents, func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { + return parentStatus.ParentRef + }))) + return append(refs, statusRefs...) } } @@ -426,7 +436,12 @@ func (r *GatewayController) transformHTTPRoute(ctx context.Context) func(o clien func (r *GatewayController) transformTCPRoute(ctx context.Context) func(o client.Object) []reconcile.Request { return func(o client.Object) []reconcile.Request { route := o.(*gwv1alpha2.TCPRoute) - return refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs)) + + refs := refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs)) + statusRefs := refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, common.ConvertSliceFunc(route.Status.Parents, func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { + return parentStatus.ParentRef + }))) + return append(refs, statusRefs...) } } @@ -901,37 +916,26 @@ func (c *GatewayController) getConsulGateway(ref api.ResourceReference) *api.API return nil } -func (c *GatewayController) getConsulHTTPRoutes(ref api.ResourceReference, resources *common.ResourceMap) []api.HTTPRouteConfigEntry { - var filtered []api.HTTPRouteConfigEntry - +func (c *GatewayController) fetchConsulHTTPRoutes(ref api.ResourceReference, resources *common.ResourceMap) { for _, route := range configEntriesTo[*api.HTTPRouteConfigEntry](c.cache.List(api.HTTPRoute)) { if routeReferencesGateway(route.Namespace, ref, route.Parents) { - filtered = append(filtered, *route) resources.ReferenceCountConsulHTTPRoute(*route) } } - return filtered } -func (c *GatewayController) getConsulTCPRoutes(ref api.ResourceReference, resources *common.ResourceMap) []api.TCPRouteConfigEntry { - var filtered []api.TCPRouteConfigEntry - +func (c *GatewayController) fetchConsulTCPRoutes(ref api.ResourceReference, resources *common.ResourceMap) { for _, route := range configEntriesTo[*api.TCPRouteConfigEntry](c.cache.List(api.TCPRoute)) { if routeReferencesGateway(route.Namespace, ref, route.Parents) { - filtered = append(filtered, *route) resources.ReferenceCountConsulTCPRoute(*route) } } - return filtered } -func (c *GatewayController) getConsulInlineCertificates() []api.InlineCertificateConfigEntry { - var filtered []api.InlineCertificateConfigEntry - +func (c *GatewayController) fetchConsulInlineCertificates(resources *common.ResourceMap) { for _, cert := range configEntriesTo[*api.InlineCertificateConfigEntry](c.cache.List(api.InlineCertificate)) { - filtered = append(filtered, *cert) + resources.ReferenceCountConsulCertificate(*cert) } - return filtered } func routeReferencesGateway(namespace string, ref api.ResourceReference, refs []api.ResourceReference) bool { diff --git a/control-plane/api-gateway/controllers/gateway_controller_test.go b/control-plane/api-gateway/controllers/gateway_controller_test.go new file mode 100644 index 0000000000..b36f256121 --- /dev/null +++ b/control-plane/api-gateway/controllers/gateway_controller_test.go @@ -0,0 +1,297 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllers + +import ( + "context" + "testing" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestTransformHTTPRoute(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + route *gwv1beta1.HTTPRoute + expected []reconcile.Request + }{ + "route with parent empty namespace": { + route: &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway"}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "default"}}, + }, + }, + "route with parent with namespace": { + route: &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "other"}}, + }, + }, + "route with non gateway parent with namespace": { + route: &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway", Group: common.PointerTo(gwv1beta1.Group("group"))}, + }, + }, + }, + }, + expected: []reconcile.Request{}, + }, + "route with parent in status and no namespace": { + route: &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "default"}}, + }, + }, + "route with parent in status and namespace": { + route: &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "other"}}, + }, + }, + "route with non gateway parent in status": { + route: &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway", Group: common.PointerTo(gwv1beta1.Group("group"))}}, + }, + }, + }, + }, + expected: []reconcile.Request{}, + }, + "route parent in spec and in status": { + route: &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway-one"}, + }, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway-one", Namespace: "default"}}, + {NamespacedName: types.NamespacedName{Name: "gateway-two", Namespace: "default"}}, + }, + }, + } { + t.Run(name, func(t *testing.T) { + controller := GatewayController{} + + fn := controller.transformHTTPRoute(context.Background()) + require.ElementsMatch(t, tt.expected, fn(tt.route)) + }) + } +} + +func TestTransformTCPRoute(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + route *gwv1alpha2.TCPRoute + expected []reconcile.Request + }{ + "route with parent empty namespace": { + route: &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway"}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "default"}}, + }, + }, + "route with parent with namespace": { + route: &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "other"}}, + }, + }, + "route with non gateway parent with namespace": { + route: &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway", Group: common.PointerTo(gwv1beta1.Group("group"))}, + }, + }, + }, + }, + expected: []reconcile.Request{}, + }, + "route with parent in status and no namespace": { + route: &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "default"}}, + }, + }, + "route with parent in status and namespace": { + route: &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "other"}}, + }, + }, + "route with non gateway parent in status": { + route: &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway", Group: common.PointerTo(gwv1beta1.Group("group"))}}, + }, + }, + }, + }, + expected: []reconcile.Request{}, + }, + "route parent in spec and in status": { + route: &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "gateway-one"}, + }, + }, + }, + Status: gwv1alpha2.TCPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + {ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}}, + }, + }, + }, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway-one", Namespace: "default"}}, + {NamespacedName: types.NamespacedName{Name: "gateway-two", Namespace: "default"}}, + }, + }, + } { + t.Run(name, func(t *testing.T) { + controller := GatewayController{} + + fn := controller.transformTCPRoute(context.Background()) + require.ElementsMatch(t, tt.expected, fn(tt.route)) + }) + } +} diff --git a/control-plane/api-gateway/controllers/index.go b/control-plane/api-gateway/controllers/index.go index cff6dfac12..7fd13de1de 100644 --- a/control-plane/api-gateway/controllers/index.go +++ b/control-plane/api-gateway/controllers/index.go @@ -161,12 +161,18 @@ func gatewayForSecret(o client.Object) []string { func gatewaysForHTTPRoute(o client.Object) []string { route := o.(*gwv1beta1.HTTPRoute) - return gatewaysForRoute(route.Namespace, route.Spec.ParentRefs) + statusRefs := common.ConvertSliceFunc(route.Status.Parents, func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { + return parentStatus.ParentRef + }) + return gatewaysForRoute(route.Namespace, route.Spec.ParentRefs, statusRefs) } func gatewaysForTCPRoute(o client.Object) []string { route := o.(*gwv1alpha2.TCPRoute) - return gatewaysForRoute(route.Namespace, route.Spec.ParentRefs) + statusRefs := common.ConvertSliceFunc(route.Status.Parents, func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { + return parentStatus.ParentRef + }) + return gatewaysForRoute(route.Namespace, route.Spec.ParentRefs, statusRefs) } func servicesForHTTPRoute(o client.Object) []string { @@ -249,7 +255,7 @@ func meshServicesForTCPRoute(o client.Object) []string { return refs } -func gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference) []string { +func gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference, statusRefs []gwv1beta1.ParentReference) []string { var references []string for _, parent := range refs { if common.NilOrEqual(parent.Group, common.BetaGroup) && common.NilOrEqual(parent.Kind, common.KindGateway) { @@ -257,5 +263,11 @@ func gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference) []stri references = append(references, common.IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace).String()) } } + for _, parent := range statusRefs { + if common.NilOrEqual(parent.Group, common.BetaGroup) && common.NilOrEqual(parent.Kind, common.KindGateway) { + // If an explicit Gateway namespace is not provided, use the Route namespace to lookup the provided Gateway Namespace. + references = append(references, common.IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace).String()) + } + } return references } From 05acb5f5b6c8a888c0ef1891b80bcc41866c8a7a Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 2 Jun 2023 15:43:45 -0400 Subject: [PATCH 193/592] accepance: extend api gateway lifecycle test retryCheck timeouts (#2256) To reduce the likelihood of flakes. --- .../api-gateway/api_gateway_lifecycle_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go index c07075a709..08712815cc 100644 --- a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go +++ b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go @@ -196,7 +196,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { // check that the route is unbound and all Consul objects and Kubernetes statuses are cleaned up logger.Log(t, "checking that http route one is not bound to gateway two") - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { var route gwv1beta1.HTTPRoute err := k8sClient.Get(context.Background(), types.NamespacedName{Name: routeOneName, Namespace: defaultNamespace}, &route) require.NoError(r, err) @@ -246,7 +246,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { // check that the Kubernetes gateway is cleaned up logger.Log(t, "checking that gateway one is cleaned up in Kubernetes") - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { var route gwv1beta1.Gateway err := k8sClient.Get(context.Background(), types.NamespacedName{Name: controlledGatewayOneName, Namespace: defaultNamespace}, &route) require.NoError(r, err) @@ -299,7 +299,7 @@ func checkConsulNotExists(t *testing.T, client *api.Client, kind, name string, n opts.Namespace = namespace[0] } - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { _, _, err := client.ConfigEntries().Get(kind, name, opts) require.Error(r, err) require.EqualError(r, err, fmt.Sprintf("Unexpected response code: 404 (Config entry not found for %q / %q)", kind, name)) @@ -309,7 +309,7 @@ func checkConsulNotExists(t *testing.T, client *api.Client, kind, name string, n func checkConsulExists(t *testing.T, client *api.Client, kind, name string) { t.Helper() - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { _, _, err := client.ConfigEntries().Get(kind, name, nil) require.NoError(r, err) }) @@ -318,7 +318,7 @@ func checkConsulExists(t *testing.T, client *api.Client, kind, name string) { func checkConsulRouteParent(t *testing.T, client *api.Client, name, parent string) { t.Helper() - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { entry, _, err := client.ConfigEntries().Get(api.HTTPRoute, name, nil) require.NoError(r, err) route := entry.(*api.HTTPRouteConfigEntry) @@ -331,7 +331,7 @@ func checkConsulRouteParent(t *testing.T, client *api.Client, name, parent strin func checkEmptyRoute(t *testing.T, client client.Client, name, namespace string) { t.Helper() - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { var route gwv1beta1.HTTPRoute err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &route) require.NoError(r, err) @@ -344,7 +344,7 @@ func checkEmptyRoute(t *testing.T, client client.Client, name, namespace string) func checkRouteBound(t *testing.T, client client.Client, name, namespace, parent string) { t.Helper() - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { var route gwv1beta1.HTTPRoute err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &route) require.NoError(r, err) From 6662c780d57df8aa8dae1b4825cd80a9a81cc823 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Fri, 2 Jun 2023 15:55:57 -0400 Subject: [PATCH 194/592] api-gateway: create RoleBinding attaching Role to ServiceAccount (#2252) * Create RoleBinding attaching Role to ServiceAccount * Update ClusterRole for controller to allow management of RoleBindings * Separate logic for RoleBinding management from logic for Role * Use pointer receiver for all functions on Gatekeeper struct * Use more descriptive name for NamespacedName arg on delete * Clean up missed code in cherrypick * Remove out-of-scope TODO * Make Upsert docstring more robust, explaining dependency ordering * Add RoleBindings to unit tests for Gatekeeper --- .../templates/connect-inject-clusterrole.yaml | 2 +- .../api-gateway/gatekeeper/deployment.go | 4 +- .../api-gateway/gatekeeper/gatekeeper.go | 26 +++-- .../api-gateway/gatekeeper/gatekeeper_test.go | 97 ++++++++++++++++++- control-plane/api-gateway/gatekeeper/role.go | 19 ++-- .../api-gateway/gatekeeper/rolebinding.go | 90 +++++++++++++++++ .../api-gateway/gatekeeper/service.go | 4 +- .../api-gateway/gatekeeper/serviceaccount.go | 4 +- 8 files changed, 215 insertions(+), 31 deletions(-) create mode 100644 control-plane/api-gateway/gatekeeper/rolebinding.go diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index e476949997..730720d460 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -73,7 +73,7 @@ rules: - delete - update - apiGroups: [ "rbac.authorization.k8s.io" ] - resources: [ "roles" ] + resources: [ "roles", "rolebindings" ] verbs: - get - list diff --git a/control-plane/api-gateway/gatekeeper/deployment.go b/control-plane/api-gateway/gatekeeper/deployment.go index eecfa31349..cc08e1bbef 100644 --- a/control-plane/api-gateway/gatekeeper/deployment.go +++ b/control-plane/api-gateway/gatekeeper/deployment.go @@ -75,8 +75,8 @@ func (g *Gatekeeper) upsertDeployment(ctx context.Context, gateway gwv1beta1.Gat return nil } -func (g *Gatekeeper) deleteDeployment(ctx context.Context, nsname types.NamespacedName) error { - err := g.Client.Delete(ctx, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: nsname.Name, Namespace: nsname.Namespace}}) +func (g *Gatekeeper) deleteDeployment(ctx context.Context, gwName types.NamespacedName) error { + err := g.Client.Delete(ctx, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}) if k8serrors.IsNotFound(err) { return nil } diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper.go b/control-plane/api-gateway/gatekeeper/gatekeeper.go index 23f96acb3d..46243ff9a1 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper.go @@ -30,6 +30,7 @@ func New(log logr.Logger, client client.Client) *Gatekeeper { } // Upsert creates or updates the resources for handling routing of network traffic. +// This is done in order based on dependencies between resources. func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { g.Log.Info(fmt.Sprintf("Upsert Gateway Deployment %s/%s", gateway.Namespace, gateway.Name)) @@ -41,6 +42,10 @@ func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc return err } + if err := g.upsertRoleBinding(ctx, gateway, gcc, config); err != nil { + return err + } + if err := g.upsertService(ctx, gateway, gcc, config); err != nil { return err } @@ -53,20 +58,27 @@ func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc } // Delete removes the resources for handling routing of network traffic. -func (g *Gatekeeper) Delete(ctx context.Context, nsname types.NamespacedName) error { - if err := g.deleteRole(ctx, nsname); err != nil { +// This is done in the reverse order of Upsert due to dependencies between resources. +func (g *Gatekeeper) Delete(ctx context.Context, gatewayName types.NamespacedName) error { + g.Log.Info(fmt.Sprintf("Delete Gateway Deployment %s/%s", gatewayName.Namespace, gatewayName.Name)) + + if err := g.deleteDeployment(ctx, gatewayName); err != nil { + return err + } + + if err := g.deleteService(ctx, gatewayName); err != nil { return err } - if err := g.deleteServiceAccount(ctx, nsname); err != nil { + if err := g.deleteRoleBinding(ctx, gatewayName); err != nil { return err } - if err := g.deleteService(ctx, nsname); err != nil { + if err := g.deleteServiceAccount(ctx, gatewayName); err != nil { return err } - if err := g.deleteDeployment(ctx, nsname); err != nil { + if err := g.deleteRole(ctx, gatewayName); err != nil { return err } @@ -76,14 +88,14 @@ func (g *Gatekeeper) Delete(ctx context.Context, nsname types.NamespacedName) er // resourceMutator is passed to create or update functions to mutate Kubernetes resources. type resourceMutator = func() error -func (g Gatekeeper) namespacedName(gateway gwv1beta1.Gateway) types.NamespacedName { +func (g *Gatekeeper) namespacedName(gateway gwv1beta1.Gateway) types.NamespacedName { return types.NamespacedName{ Namespace: gateway.Namespace, Name: gateway.Name, } } -func (g Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config common.HelmConfig) string { +func (g *Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config common.HelmConfig) string { if config.AuthMethod == "" { return "" } diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go index cc19f68d47..e2da61177f 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -61,6 +61,7 @@ type testCase struct { type resources struct { deployments []*appsv1.Deployment roles []*rbac.Role + roleBindings []*rbac.RoleBinding services []*corev1.Service serviceAccounts []*corev1.ServiceAccount } @@ -187,6 +188,9 @@ func TestUpsert(t *testing.T) { roles: []*rbac.Role{ configureRole(name, namespace, labels, "1"), }, + roleBindings: []*rbac.RoleBinding{ + configureRoleBinding(name, namespace, labels, "1"), + }, services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { @@ -240,6 +244,9 @@ func TestUpsert(t *testing.T) { roles: []*rbac.Role{ configureRole(name, namespace, labels, "1"), }, + roleBindings: []*rbac.RoleBinding{ + configureRoleBinding(name, namespace, labels, "1"), + }, services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { @@ -260,6 +267,9 @@ func TestUpsert(t *testing.T) { roles: []*rbac.Role{ configureRole(name, namespace, labels, "1"), }, + roleBindings: []*rbac.RoleBinding{ + configureRoleBinding(name, namespace, labels, "1"), + }, services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { @@ -315,6 +325,9 @@ func TestUpsert(t *testing.T) { roles: []*rbac.Role{ configureRole(name, namespace, labels, "1"), }, + roleBindings: []*rbac.RoleBinding{ + configureRoleBinding(name, namespace, labels, "1"), + }, services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { @@ -340,6 +353,9 @@ func TestUpsert(t *testing.T) { roles: []*rbac.Role{ configureRole(name, namespace, labels, "1"), }, + roleBindings: []*rbac.RoleBinding{ + configureRoleBinding(name, namespace, labels, "1"), + }, services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { @@ -546,6 +562,9 @@ func TestDelete(t *testing.T) { roles: []*rbac.Role{ configureRole(name, namespace, labels, "1"), }, + roleBindings: []*rbac.RoleBinding{ + configureRoleBinding(name, namespace, labels, "1"), + }, services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { @@ -609,6 +628,10 @@ func joinResources(resources resources) (objs []client.Object) { objs = append(objs, role) } + for _, roleBinding := range resources.roleBindings { + objs = append(objs, roleBinding) + } + for _, service := range resources.services { objs = append(objs, service) } @@ -664,6 +687,22 @@ func validateResourcesExist(t *testing.T, client client.Client, resources resour require.Equal(t, expected, actual) } + for _, expected := range resources.roleBindings { + actual := &rbac.RoleBinding{} + err := client.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if err != nil { + return err + } + + // Patch the createdAt label + actual.Labels[createdAtLabelKey] = createdAtLabelValue + + require.Equal(t, expected, actual) + } + for _, expected := range resources.services { actual := &corev1.Service{} err := client.Get(context.Background(), types.NamespacedName{ @@ -700,12 +739,12 @@ func validateResourcesExist(t *testing.T, client client.Client, resources resour return nil } -func validateResourcesAreDeleted(t *testing.T, client client.Client, resources resources) error { +func validateResourcesAreDeleted(t *testing.T, k8sClient client.Client, resources resources) error { t.Helper() for _, expected := range resources.deployments { actual := &appsv1.Deployment{} - err := client.Get(context.Background(), types.NamespacedName{ + err := k8sClient.Get(context.Background(), types.NamespacedName{ Name: expected.Name, Namespace: expected.Namespace, }, actual) @@ -717,7 +756,7 @@ func validateResourcesAreDeleted(t *testing.T, client client.Client, resources r for _, expected := range resources.roles { actual := &rbac.Role{} - err := client.Get(context.Background(), types.NamespacedName{ + err := k8sClient.Get(context.Background(), types.NamespacedName{ Name: expected.Name, Namespace: expected.Namespace, }, actual) @@ -727,9 +766,21 @@ func validateResourcesAreDeleted(t *testing.T, client client.Client, resources r require.Error(t, err) } + for _, expected := range resources.roleBindings { + actual := &rbac.RoleBinding{} + err := k8sClient.Get(context.Background(), types.NamespacedName{ + Name: expected.Name, + Namespace: expected.Namespace, + }, actual) + if !k8serrors.IsNotFound(err) { + return fmt.Errorf("expected rolebinding %s to be deleted", expected.Name) + } + require.Error(t, err) + } + for _, expected := range resources.services { actual := &corev1.Service{} - err := client.Get(context.Background(), types.NamespacedName{ + err := k8sClient.Get(context.Background(), types.NamespacedName{ Name: expected.Name, Namespace: expected.Namespace, }, actual) @@ -741,7 +792,7 @@ func validateResourcesAreDeleted(t *testing.T, client client.Client, resources r for _, expected := range resources.serviceAccounts { actual := &corev1.ServiceAccount{} - err := client.Get(context.Background(), types.NamespacedName{ + err := k8sClient.Get(context.Background(), types.NamespacedName{ Name: expected.Name, Namespace: expected.Namespace, }, actual) @@ -837,6 +888,42 @@ func configureRole(name, namespace string, labels map[string]string, resourceVer } } +func configureRoleBinding(name, namespace string, labels map[string]string, resourceVersion string) *rbac.RoleBinding { + return &rbac.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "RoleBinding", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + ResourceVersion: resourceVersion, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "gateway.networking.k8s.io/v1beta1", + Kind: "Gateway", + Name: name, + Controller: common.PointerTo(true), + BlockOwnerDeletion: common.PointerTo(true), + }, + }, + }, + RoleRef: rbac.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: name, + }, + Subjects: []rbac.Subject{ + { + Kind: "ServiceAccount", + Name: name, + Namespace: namespace, + }, + }, + } +} + func configureService(name, namespace string, labels, annotations map[string]string, serviceType corev1.ServiceType, ports []corev1.ServicePort, resourceVersion string) *corev1.Service { return &corev1.Service{ TypeMeta: metav1.TypeMeta{ diff --git a/control-plane/api-gateway/gatekeeper/role.go b/control-plane/api-gateway/gatekeeper/role.go index 688c425f5f..eb8431075a 100644 --- a/control-plane/api-gateway/gatekeeper/role.go +++ b/control-plane/api-gateway/gatekeeper/role.go @@ -24,19 +24,12 @@ func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, } role := &rbac.Role{} - exists := false - // Get ServiceAccount + // If the Role already exists, ensure that we own the Role err := g.Client.Get(ctx, g.namespacedName(gateway), role) if err != nil && !k8serrors.IsNotFound(err) { return err - } else if k8serrors.IsNotFound(err) { - exists = false - } else { - exists = true - } - - if exists { + } else if !k8serrors.IsNotFound(err) { // Ensure we own the Role. for _, ref := range role.GetOwnerReferences() { if ref.UID == gateway.GetUID() && ref.Name == gateway.GetName() { @@ -44,7 +37,7 @@ func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, return nil } } - return errors.New("Role not owned by controller") + return errors.New("role not owned by controller") } role = g.role(gateway, gcc) @@ -58,8 +51,8 @@ func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, return nil } -func (g *Gatekeeper) deleteRole(ctx context.Context, nsname types.NamespacedName) error { - if err := g.Client.Delete(ctx, &rbac.Role{ObjectMeta: metav1.ObjectMeta{Name: nsname.Name, Namespace: nsname.Namespace}}); err != nil { +func (g *Gatekeeper) deleteRole(ctx context.Context, gwName types.NamespacedName) error { + if err := g.Client.Delete(ctx, &rbac.Role{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}); err != nil { if k8serrors.IsNotFound(err) { return nil } @@ -78,6 +71,7 @@ func (g *Gatekeeper) role(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassCo }, Rules: []rbac.PolicyRule{}, } + if gcc.Spec.PodSecurityPolicy != "" { role.Rules = append(role.Rules, rbac.PolicyRule{ APIGroups: []string{"policy"}, @@ -86,5 +80,6 @@ func (g *Gatekeeper) role(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassCo Verbs: []string{"use"}, }) } + return role } diff --git a/control-plane/api-gateway/gatekeeper/rolebinding.go b/control-plane/api-gateway/gatekeeper/rolebinding.go new file mode 100644 index 0000000000..8891a754e6 --- /dev/null +++ b/control-plane/api-gateway/gatekeeper/rolebinding.go @@ -0,0 +1,90 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatekeeper + +import ( + "context" + "errors" + + "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" +) + +func (g *Gatekeeper) upsertRoleBinding(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { + if config.AuthMethod == "" { + return g.deleteRole(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) + } + + roleBinding := &rbac.RoleBinding{} + + // If the RoleBinding already exists, ensure that we own the RoleBinding + err := g.Client.Get(ctx, g.namespacedName(gateway), roleBinding) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } else if !k8serrors.IsNotFound(err) { + // Ensure we own the Role. + for _, ref := range roleBinding.GetOwnerReferences() { + if ref.UID == gateway.GetUID() && ref.Name == gateway.GetName() { + // We found ourselves! + return nil + } + } + return errors.New("role not owned by controller") + } + + // Create or update the RoleBinding + roleBinding = g.roleBinding(gateway, gcc, config) + if err := ctrl.SetControllerReference(&gateway, roleBinding, g.Client.Scheme()); err != nil { + return err + } + if err := g.Client.Create(ctx, roleBinding); err != nil { + return err + } + + return nil +} + +func (g *Gatekeeper) deleteRoleBinding(ctx context.Context, gwName types.NamespacedName) error { + if err := g.Client.Delete(ctx, &rbac.RoleBinding{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}); err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + return nil +} + +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) + role := g.role(gateway, gcc) + + return &rbac.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: gateway.Name, + Namespace: gateway.Namespace, + Labels: common.LabelsForGateway(&gateway), + }, + RoleRef: rbac.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: role.Name, + }, + Subjects: []rbac.Subject{ + { + Kind: "ServiceAccount", + Name: serviceAccount.Name, + Namespace: serviceAccount.Namespace, + }, + }, + } +} diff --git a/control-plane/api-gateway/gatekeeper/service.go b/control-plane/api-gateway/gatekeeper/service.go index 149dc067e3..80272b7495 100644 --- a/control-plane/api-gateway/gatekeeper/service.go +++ b/control-plane/api-gateway/gatekeeper/service.go @@ -53,8 +53,8 @@ func (g *Gatekeeper) upsertService(ctx context.Context, gateway gwv1beta1.Gatewa return nil } -func (g *Gatekeeper) deleteService(ctx context.Context, nsname types.NamespacedName) error { - if err := g.Client.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: nsname.Name, Namespace: nsname.Namespace}}); err != nil { +func (g *Gatekeeper) deleteService(ctx context.Context, gwName types.NamespacedName) error { + if err := g.Client.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}); err != nil { if k8serrors.IsNotFound(err) { return nil } diff --git a/control-plane/api-gateway/gatekeeper/serviceaccount.go b/control-plane/api-gateway/gatekeeper/serviceaccount.go index 864047fb1b..47336867aa 100644 --- a/control-plane/api-gateway/gatekeeper/serviceaccount.go +++ b/control-plane/api-gateway/gatekeeper/serviceaccount.go @@ -58,8 +58,8 @@ func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1 return nil } -func (g *Gatekeeper) deleteServiceAccount(ctx context.Context, nsname types.NamespacedName) error { - if err := g.Client.Delete(ctx, &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: nsname.Name, Namespace: nsname.Namespace}}); err != nil { +func (g *Gatekeeper) deleteServiceAccount(ctx context.Context, gwName types.NamespacedName) error { + if err := g.Client.Delete(ctx, &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}); err != nil { if k8serrors.IsNotFound(err) { return nil } From 3f346768b03c0180e49801c86310d04e024889e8 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Fri, 2 Jun 2023 19:09:05 -0400 Subject: [PATCH 195/592] Add missing resources to kustomization.yaml (#2255) * Add missing JWT provider resource to kustomization.yaml - Add missing assertions for JWT provider too. * Add OSS tests for exported-services --- .../config_entries_namespaces_test.go | 91 +++++++++++++------ .../config-entries/config_entries_test.go | 57 +++++++++++- ...viceexports.yaml => exportedservices.yaml} | 8 +- .../fixtures/bases/crds-oss/jwtprovider.yaml | 28 +++--- .../bases/crds-oss/kustomization.yaml | 20 ++-- .../cases/crds-ent/exportedservices.yaml | 4 +- .../cases/crds-ent/kustomization.yaml | 5 +- 7 files changed, 150 insertions(+), 63 deletions(-) rename acceptance/tests/fixtures/bases/crds-oss/{serviceexports.yaml => exportedservices.yaml} (65%) diff --git a/acceptance/tests/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index c15277a6ee..507b1a07af 100644 --- a/acceptance/tests/config-entries/config_entries_namespaces_test.go +++ b/acceptance/tests/config-entries/config_entries_namespaces_test.go @@ -159,13 +159,6 @@ func TestControllerNamespaces(t *testing.T) { require.True(r, ok, "could not cast to ProxyConfigEntry") require.Equal(r, api.MeshGatewayModeLocal, proxyDefaultEntry.MeshGateway.Mode) - // exported-services - entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) - require.NoError(r, err) - exportedServicesEntry, ok := entry.(*api.ExportedServicesConfigEntry) - require.True(r, ok, "could not cast to ExportedServicesConfigEntry") - require.Equal(r, "frontend", exportedServicesEntry.Services[0].Name) - // mesh entry, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", defaultOpts) require.NoError(r, err) @@ -216,6 +209,33 @@ func TestControllerNamespaces(t *testing.T) { require.Equal(r, "certFile", terminatingGatewayEntry.Services[0].CertFile) require.Equal(r, "keyFile", terminatingGatewayEntry.Services[0].KeyFile) require.Equal(r, "sni", terminatingGatewayEntry.Services[0].SNI) + + // jwt-provider + entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) + require.NoError(r, err) + jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) + require.True(r, ok, "could not cast to JWTProviderConfigEntry") + require.Equal(r, "jwks.txt", jwtProviderConfigEntry.JSONWebKeySet.Local.Filename) + require.Equal(r, "test-issuer", jwtProviderConfigEntry.Issuer) + require.ElementsMatch(r, []string{"aud1", "aud2"}, jwtProviderConfigEntry.Audiences) + require.Equal(r, "x-jwt-header", jwtProviderConfigEntry.Locations[0].Header.Name) + require.Equal(r, "x-query-param", jwtProviderConfigEntry.Locations[1].QueryParam.Name) + require.Equal(r, "session-id", jwtProviderConfigEntry.Locations[2].Cookie.Name) + require.Equal(r, "x-forwarded-jwt", jwtProviderConfigEntry.Forwarding.HeaderName) + require.True(r, jwtProviderConfigEntry.Forwarding.PadForwardPayloadHeader) + require.Equal(r, 45, jwtProviderConfigEntry.ClockSkewSeconds) + require.Equal(r, 15, jwtProviderConfigEntry.CacheConfig.Size) + + // exported-services + entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) + require.NoError(r, err) + exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) + require.True(r, ok, "could not cast to ExportedServicesConfigEntry") + require.Equal(r, "frontend", exportedServicesConfigEntry.Services[0].Name) + require.Equal(r, "frontend", exportedServicesConfigEntry.Services[0].Namespace) + require.Equal(r, "partitionName", exportedServicesConfigEntry.Services[0].Consumers[0].Partition) + require.Equal(r, "peerName", exportedServicesConfigEntry.Services[0].Consumers[1].Peer) + require.Equal(r, "groupName", exportedServicesConfigEntry.Services[0].Consumers[2].SamenessGroup) }) } @@ -233,10 +253,6 @@ func TestControllerNamespaces(t *testing.T) { patchMeshGatewayMode := "remote" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "proxydefaults", "global", "-p", fmt.Sprintf(`{"spec":{"meshGateway":{"mode": "%s"}}}`, patchMeshGatewayMode), "--type=merge") - logger.Log(t, "patching partition-exports custom resource") - patchServiceName := "backend" - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "exportedservices", "default", "-p", fmt.Sprintf(`{"spec":{"services":[{"name": "%s", "namespace": "front", "consumers":[{"partition": "foo"}]}]}}`, patchServiceName), "--type=merge") - logger.Log(t, "patching mesh custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "mesh", "mesh", "-p", fmt.Sprintf(`{"spec":{"transparentProxy":{"meshDestinationsOnly": %t}}}`, false), "--type=merge") @@ -258,6 +274,14 @@ func TestControllerNamespaces(t *testing.T) { patchSNI := "patch-sni" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "terminatinggateway", "terminating-gateway", "-p", fmt.Sprintf(`{"spec": {"services": [{"name":"name","caFile":"caFile","certFile":"certFile","keyFile":"keyFile","sni":"%s"}]}}`, patchSNI), "--type=merge") + logger.Log(t, "patching jwt-provider custom resource") + patchIssuer := "other-issuer" + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "jwtprovider", "jwt-provider", "-p", fmt.Sprintf(`{"spec": {"issuer": "%s"}}`, patchIssuer), "--type=merge") + + logger.Log(t, "patching exported-services custom resource") + patchPartition := "destination" + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "exportedservices", "default", "-p", fmt.Sprintf(`{"spec": {"services": [{"name": "frontend", "namespace": "frontend", "consumers": [{"partition": "%s"}, {"peer": "peerName"}, {"samenessGroup": "groupName"}]}]}}`, patchPartition), "--type=merge") + counter := &retry.Counter{Count: 20, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -281,13 +305,6 @@ func TestControllerNamespaces(t *testing.T) { require.True(r, ok, "could not cast to ProxyConfigEntry") require.Equal(r, api.MeshGatewayModeRemote, proxyDefaultsEntry.MeshGateway.Mode) - // partition-exports - entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) - require.NoError(r, err) - exportedServicesEntry, ok := entry.(*api.ExportedServicesConfigEntry) - require.True(r, ok, "could not cast to ExportedServicesConfigEntry") - require.Equal(r, "backend", exportedServicesEntry.Services[0].Name) - // mesh entry, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", defaultOpts) require.NoError(r, err) @@ -331,6 +348,20 @@ func TestControllerNamespaces(t *testing.T) { terminatingGatewayEntry, ok := entry.(*api.TerminatingGatewayConfigEntry) require.True(r, ok, "could not cast to TerminatingGatewayConfigEntry") require.Equal(r, patchSNI, terminatingGatewayEntry.Services[0].SNI) + + // jwt-Provider + entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) + require.NoError(r, err) + jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) + require.True(r, ok, "could not cast to JWTProviderConfigEntry") + require.Equal(r, patchIssuer, jwtProviderConfigEntry.Issuer) + + // exported-services + entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) + require.NoError(r, err) + exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) + require.True(r, ok, "could not cast to ExportedServicesConfigEntry") + require.Equal(r, patchPartition, exportedServicesConfigEntry.Services[0].Consumers[0].Partition) }) } @@ -345,9 +376,6 @@ func TestControllerNamespaces(t *testing.T) { logger.Log(t, "deleting proxy-defaults custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "proxydefaults", "global") - logger.Log(t, "deleting partition-exports custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "exportedservices", "default") - logger.Log(t, "deleting mesh custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "mesh", "mesh") @@ -366,6 +394,12 @@ func TestControllerNamespaces(t *testing.T) { logger.Log(t, "deleting terminating-gateway custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "terminatinggateway", "terminating-gateway") + logger.Log(t, "deleting jwt-provider custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "jwtprovider", "jwt-provider") + + logger.Log(t, "deleting exported-services custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "exportedservices", "default") + counter := &retry.Counter{Count: 20, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -383,11 +417,6 @@ func TestControllerNamespaces(t *testing.T) { require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") - // partition-exports - _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) - require.Error(r, err) - require.Contains(r, err.Error(), "404 (Config entry not found") - // mesh _, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", defaultOpts) require.Error(r, err) @@ -417,6 +446,16 @@ func TestControllerNamespaces(t *testing.T) { _, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "terminating-gateway", queryOpts) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") + + // jwt-provider + _, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") + + // exported-services + _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") }) } }) diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index a31f4762fd..5f88594468 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -95,7 +95,7 @@ func TestController(t *testing.T) { // On startup, the controller can take upwards of 1m to perform // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 1m timeout here). + // the reconcile loop to run (hence the 2m timeout here). counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -180,20 +180,29 @@ func TestController(t *testing.T) { require.Equal(r, "sni", terminatingGatewayEntry.Services[0].SNI) // jwt-provider - entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "test-jwt-provider", nil) + entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) require.NoError(r, err) jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) require.True(r, ok, "could not cast to JWTProviderConfigEntry") require.Equal(r, "jwks.txt", jwtProviderConfigEntry.JSONWebKeySet.Local.Filename) require.Equal(r, "test-issuer", jwtProviderConfigEntry.Issuer) - require.Equal(r, []string{"aud1", "aud2"}, jwtProviderConfigEntry.Issuer) + require.ElementsMatch(r, []string{"aud1", "aud2"}, jwtProviderConfigEntry.Audiences) require.Equal(r, "x-jwt-header", jwtProviderConfigEntry.Locations[0].Header.Name) require.Equal(r, "x-query-param", jwtProviderConfigEntry.Locations[1].QueryParam.Name) require.Equal(r, "session-id", jwtProviderConfigEntry.Locations[2].Cookie.Name) require.Equal(r, "x-forwarded-jwt", jwtProviderConfigEntry.Forwarding.HeaderName) require.True(r, jwtProviderConfigEntry.Forwarding.PadForwardPayloadHeader) require.Equal(r, 45, jwtProviderConfigEntry.ClockSkewSeconds) - require.Equal(r, 15, jwtProviderConfigEntry.CacheConfig) + require.Equal(r, 15, jwtProviderConfigEntry.CacheConfig.Size) + + // exported-services + entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) + require.NoError(r, err) + exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) + require.True(r, ok, "could not cast to ExportedServicesConfigEntry") + require.Equal(r, "frontend", exportedServicesConfigEntry.Services[0].Name) + require.Equal(r, "peerName", exportedServicesConfigEntry.Services[0].Consumers[0].Peer) + require.Equal(r, "groupName", exportedServicesConfigEntry.Services[0].Consumers[1].SamenessGroup) }) } @@ -232,6 +241,14 @@ func TestController(t *testing.T) { patchSNI := "patch-sni" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "terminatinggateway", "terminating-gateway", "-p", fmt.Sprintf(`{"spec": {"services": [{"name":"name","caFile":"caFile","certFile":"certFile","keyFile":"keyFile","sni":"%s"}]}}`, patchSNI), "--type=merge") + logger.Log(t, "patching JWTProvider custom resource") + patchIssuer := "other-issuer" + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "jwtprovider", "jwt-provider", "-p", fmt.Sprintf(`{"spec": {"issuer": "%s"}}`, patchIssuer), "--type=merge") + + logger.Log(t, "patching ExportedServices custom resource") + patchPeer := "destination" + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "exportedservices", "default", "-p", fmt.Sprintf(`{"spec": {"services": [{"name": "frontend", "consumers": [{"peer": "%s"}, {"samenessGroup": "groupName"}]}]}}`, patchPeer), "--type=merge") + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -299,6 +316,20 @@ func TestController(t *testing.T) { terminatingGatewayEntry, ok := entry.(*api.TerminatingGatewayConfigEntry) require.True(r, ok, "could not cast to TerminatingGatewayConfigEntry") require.Equal(r, patchSNI, terminatingGatewayEntry.Services[0].SNI) + + // jwt-provider + entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) + require.NoError(r, err) + jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) + require.True(r, ok, "could not cast to JWTProviderConfigEntry") + require.Equal(r, patchIssuer, jwtProviderConfigEntry.Issuer) + + // exported-services + entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) + require.NoError(r, err) + exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) + require.True(r, ok, "could not cast to ExportedServicesConfigEntry") + require.Equal(r, patchPeer, exportedServicesConfigEntry.Services[0].Consumers[0].Peer) }) } @@ -331,6 +362,12 @@ func TestController(t *testing.T) { logger.Log(t, "deleting terminating-gateway custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "terminatinggateway", "terminating-gateway") + logger.Log(t, "deleting jwt-provider custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "jwtprovider", "jwt-provider") + + logger.Log(t, "deleting exported-services custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "exportedservices", "default") + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -374,7 +411,17 @@ func TestController(t *testing.T) { require.Contains(r, err.Error(), "404 (Config entry not found") // terminating-gateway - _, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "terminating-gateway", nil) + _, _, err = consulClient.ConfigEntries().Get(api.TerminatingGateway, "terminating-gateway", nil) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") + + // jwt-provider + _, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") + + // exported-services + _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") }) diff --git a/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml b/acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml similarity index 65% rename from acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml rename to acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml index 704ea9ee19..51d69ae709 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml @@ -2,12 +2,12 @@ # SPDX-License-Identifier: MPL-2.0 apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceExports +kind: ExportedServices metadata: - name: exports + name: default spec: services: - name: frontend - namespace: frontend consumers: - - partition: other \ No newline at end of file + - peer: peerName + - samenessGroup: groupName \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml b/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml index d28139dc40..d35e532bf2 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml @@ -4,31 +4,27 @@ apiVersion: consul.hashicorp.com/v1alpha1 kind: JWTProvider metadata: - name: test-jwt-provider + name: jwt-provider spec: jsonWebKeySet: local: filename: "jwks.txt" issuer: "test-issuer" audiences: - - "aud1" - - "aud2" + - "aud1" + - "aud2" locations: - - header: - name: "x-jwt-header" - valuePrefix: "bearer" - forward: true - - queryParam: - name: "x-query-param" - - cookie: - name: "session-id" + - header: + name: "x-jwt-header" + valuePrefix: "bearer" + forward: true + - queryParam: + name: "x-query-param" + - cookie: + name: "session-id" forwarding: headerName: "x-forwarded-jwt" padForwardPayloadHeader: true clockSkewSeconds: 45 cacheConfig: - size: 15 - - - - + size: 15 \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml index 1217d857c8..de02de61e9 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml @@ -2,12 +2,14 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ingressgateway.yaml - - mesh.yaml - - proxydefaults.yaml - - servicedefaults.yaml - - serviceintentions.yaml - - serviceresolver.yaml - - servicerouter.yaml - - servicesplitter.yaml - - terminatinggateway.yaml +- ingressgateway.yaml +- mesh.yaml +- proxydefaults.yaml +- servicedefaults.yaml +- serviceintentions.yaml +- serviceresolver.yaml +- servicerouter.yaml +- servicesplitter.yaml +- terminatinggateway.yaml +- jwtprovider.yaml +- exportedservices.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml b/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml index f9f8aad4bf..dd39ab3626 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml @@ -10,4 +10,6 @@ spec: - name: frontend namespace: frontend consumers: - - partition: other \ No newline at end of file + - partition: partitionName + - peer: peerName + - samenessGroup: groupName \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml index 14f6c765d8..69d972b417 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml @@ -2,5 +2,6 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ../../bases/crds-oss - - exportedservices.yaml +- ../../bases/crds-oss +patchesStrategicMerge: +- exportedservices.yaml From 89666515f5f0aa7d426eca95b9df6dbc3a4d240f Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 2 Jun 2023 22:39:35 -0400 Subject: [PATCH 196/592] Fix Gateway trigger for when secret is modified (#2261) * Fix Gateway trigger for when secret is modified * Add some simple unit tests * up some testing timeouts for acceptance tests --- .../api-gateway/api_gateway_tenancy_test.go | 12 +-- .../controllers/gateway_controller.go | 2 +- .../controllers/gateway_controller_test.go | 91 +++++++++++++++++++ 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go index d5a0845810..f2e6899094 100644 --- a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go +++ b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go @@ -152,7 +152,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { checkConsulNotExists(t, consulClient, api.APIGateway, "gateway", namespaceForConsul(c.namespaceMirroring, gatewayNamespace)) // route failure - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { var httproute gwv1beta1.HTTPRoute err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "route", Namespace: routeNamespace}, &httproute) require.NoError(r, err) @@ -178,7 +178,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { createReferenceGrant(t, k8sClient, "route-service", routeNamespace, serviceNamespace) // gateway updated with references allowed - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { var gateway gwv1beta1.Gateway err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: gatewayNamespace}, &gateway) require.NoError(r, err) @@ -195,7 +195,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { }) // check the Consul gateway is updated, with the listener. - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", &api.QueryOptions{ Namespace: namespaceForConsul(c.namespaceMirroring, gatewayNamespace), }) @@ -210,7 +210,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { }) // route updated with gateway and services allowed - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { var httproute gwv1beta1.HTTPRoute err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "route", Namespace: routeNamespace}, &httproute) require.NoError(r, err) @@ -225,7 +225,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { }) // now check to make sure that the route is updated and valid - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { // since we're not bound, check to make sure that the route doesn't target the gateway in Consul. entry, _, err := consulClient.ConfigEntries().Get(api.HTTPRoute, "route", &api.QueryOptions{ Namespace: namespaceForConsul(c.namespaceMirroring, routeNamespace), @@ -239,7 +239,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { }) // and check to make sure that the certificate exists - retryCheck(t, 10, func(r *retry.R) { + retryCheck(t, 30, func(r *retry.R) { entry, _, err := consulClient.ConfigEntries().Get(api.InlineCertificate, "certificate", &api.QueryOptions{ Namespace: namespaceForConsul(c.namespaceMirroring, certificateNamespace), }) diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index 8baa27cca1..cc90ad6f17 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -452,7 +452,7 @@ func (r *GatewayController) transformSecret(ctx context.Context) func(o client.O secret := o.(*corev1.Secret) gatewayList := &gwv1beta1.GatewayList{} if err := r.Client.List(ctx, gatewayList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Secret_GatewayIndex, secret.Name), + FieldSelector: fields.OneTermEqualSelector(Secret_GatewayIndex, client.ObjectKeyFromObject(secret).String()), }); err != nil { return nil } diff --git a/control-plane/api-gateway/controllers/gateway_controller_test.go b/control-plane/api-gateway/controllers/gateway_controller_test.go index b36f256121..d1028eb945 100644 --- a/control-plane/api-gateway/controllers/gateway_controller_test.go +++ b/control-plane/api-gateway/controllers/gateway_controller_test.go @@ -8,9 +8,14 @@ import ( "testing" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -295,3 +300,89 @@ func TestTransformTCPRoute(t *testing.T) { }) } } + +func TestTransformSecret(t *testing.T) { + t.Parallel() + + gateway := &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Namespace: "test", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + {Name: "terminate", TLS: &gwv1beta1.GatewayTLSConfig{ + Mode: common.PointerTo(gwv1beta1.TLSModeTerminate), + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "secret-no-namespace"}, + {Name: "secret-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, + }, + }}, + {Name: "passthrough", TLS: &gwv1beta1.GatewayTLSConfig{ + Mode: common.PointerTo(gwv1beta1.TLSModePassthrough), + CertificateRefs: []gwv1beta1.SecretObjectReference{ + {Name: "passthrough", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, + }, + }}, + }, + }, + } + + for name, tt := range map[string]struct { + secret *corev1.Secret + expected []reconcile.Request + }{ + "explicit namespace from parent": { + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "secret-namespace", Namespace: "other"}, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "test"}}, + }, + }, + "implicit namespace from parent": { + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "secret-no-namespace", Namespace: "test"}, + }, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "test"}}, + }, + }, + "mismatched namespace": { + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "secret-no-namespace", Namespace: "other"}, + }, + }, + "mismatched names": { + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test"}, + }, + }, + "passthrough ignored": { + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "passthrough", Namespace: "other"}, + }, + }, + } { + t.Run(name, func(t *testing.T) { + tt := tt + + t.Parallel() + + s := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(s)) + require.NoError(t, gwv1alpha2.Install(s)) + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s)).WithRuntimeObjects(gateway).Build() + + controller := GatewayController{ + Client: fakeClient, + } + + fn := controller.transformSecret(context.Background()) + require.ElementsMatch(t, tt.expected, fn(tt.secret)) + }) + } +} From 3cecd2e5d82c5b22e0fa741dc3df313609228cfe Mon Sep 17 00:00:00 2001 From: Dan Bond Date: Sun, 4 Jun 2023 17:36:35 -0700 Subject: [PATCH 197/592] Add CRD for ControlPlane RequestLimits (#2166) --- .changelog/2166.txt | 3 + Makefile | 2 +- .../config_entries_namespaces_test.go | 65 +- .../config-entries/config_entries_test.go | 54 ++ .../crds-oss/controlplanerequestlimit.yaml | 50 ++ .../bases/crds-oss/kustomization.yaml | 3 +- ...ewayclassconfigs.consul.hashicorp.com.yaml | 3 +- .../templates/connect-inject-clusterrole.yaml | 2 + ...t-inject-mutatingwebhookconfiguration.yaml | 21 + .../crd-controlplanerequestlimits.yaml | 196 ++++++ .../templates/crd-exportedservices.yaml | 5 +- .../consul/templates/crd-ingressgateways.yaml | 5 +- charts/consul/templates/crd-jwtproviders.yaml | 5 +- charts/consul/templates/crd-meshes.yaml | 5 +- charts/consul/templates/crd-meshservices.yaml | 5 +- .../templates/crd-peeringacceptors.yaml | 5 +- .../consul/templates/crd-peeringdialers.yaml | 5 +- .../consul/templates/crd-proxydefaults.yaml | 5 +- .../consul/templates/crd-samenessgroups.yaml | 5 +- .../consul/templates/crd-servicedefaults.yaml | 5 +- .../templates/crd-serviceintentions.yaml | 5 +- .../templates/crd-serviceresolvers.yaml | 5 +- .../consul/templates/crd-servicerouters.yaml | 5 +- .../templates/crd-servicesplitters.yaml | 5 +- .../templates/crd-terminatinggateways.yaml | 5 +- ...t-inject-mutatingwebhookconfiguration.bats | 4 +- .../unit/crd-controlplanerequestlimits.bats | 26 + control-plane/PROJECT | 9 + control-plane/api/common/common.go | 23 +- .../controlplanerequestlimit_types.go | 268 +++++++++ .../controlplanerequestlimit_types_test.go | 566 ++++++++++++++++++ .../controlplanerequestlimit_webhook.go | 83 +++ .../controlplanerequestlimit_webhook_test.go | 145 +++++ .../api/v1alpha1/zz_generated.deepcopy.go | 153 +++++ ...shicorp.com_controlplanerequestlimits.yaml | 191 ++++++ ...consul.hashicorp.com_exportedservices.yaml | 3 +- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 3 +- .../consul.hashicorp.com_ingressgateways.yaml | 3 +- .../consul.hashicorp.com_jwtproviders.yaml | 3 +- .../bases/consul.hashicorp.com_meshes.yaml | 3 +- .../consul.hashicorp.com_meshservices.yaml | 3 +- ...consul.hashicorp.com_peeringacceptors.yaml | 3 +- .../consul.hashicorp.com_peeringdialers.yaml | 3 +- .../consul.hashicorp.com_proxydefaults.yaml | 3 +- .../consul.hashicorp.com_samenessgroups.yaml | 3 +- .../consul.hashicorp.com_servicedefaults.yaml | 3 +- ...onsul.hashicorp.com_serviceintentions.yaml | 3 +- ...consul.hashicorp.com_serviceresolvers.yaml | 3 +- .../consul.hashicorp.com_servicerouters.yaml | 3 +- ...consul.hashicorp.com_servicesplitters.yaml | 3 +- ...sul.hashicorp.com_terminatinggateways.yaml | 3 +- control-plane/config/crd/kustomization.yaml | 21 + control-plane/config/crd/kustomizeconfig.yaml | 17 + control-plane/config/rbac/role.yaml | 21 +- control-plane/config/webhook/manifests.yaml | 22 +- .../configentry_controller_test.go | 314 +++++++++- .../controlplanerequestlimit_controller.go | 40 ++ .../subcommand/inject-connect/command.go | 15 + 58 files changed, 2337 insertions(+), 103 deletions(-) create mode 100644 .changelog/2166.txt create mode 100644 acceptance/tests/fixtures/bases/crds-oss/controlplanerequestlimit.yaml create mode 100644 charts/consul/templates/crd-controlplanerequestlimits.yaml create mode 100644 charts/consul/test/unit/crd-controlplanerequestlimits.bats create mode 100644 control-plane/api/v1alpha1/controlplanerequestlimit_types.go create mode 100644 control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go create mode 100644 control-plane/api/v1alpha1/controlplanerequestlimit_webhook.go create mode 100644 control-plane/api/v1alpha1/controlplanerequestlimit_webhook_test.go create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml create mode 100644 control-plane/config/crd/kustomization.yaml create mode 100644 control-plane/config/crd/kustomizeconfig.yaml create mode 100644 control-plane/controllers/controlplanerequestlimit_controller.go diff --git a/.changelog/2166.txt b/.changelog/2166.txt new file mode 100644 index 0000000000..b2392bd7d5 --- /dev/null +++ b/.changelog/2166.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add support for configuring Consul server-side rate limiting +``` diff --git a/Makefile b/Makefile index 151f7868de..437357898a 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,7 @@ ifeq (, $(shell which controller-gen)) CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ cd $$CONTROLLER_GEN_TMP_DIR ;\ go mod init tmp ;\ - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.10.0 ;\ + go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.12.0 ;\ rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ } CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen diff --git a/acceptance/tests/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index 507b1a07af..ced7cc8236 100644 --- a/acceptance/tests/config-entries/config_entries_namespaces_test.go +++ b/acceptance/tests/config-entries/config_entries_namespaces_test.go @@ -211,7 +211,7 @@ func TestControllerNamespaces(t *testing.T) { require.Equal(r, "sni", terminatingGatewayEntry.Services[0].SNI) // jwt-provider - entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) + entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", defaultOpts) require.NoError(r, err) jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) require.True(r, ok, "could not cast to JWTProviderConfigEntry") @@ -227,7 +227,7 @@ func TestControllerNamespaces(t *testing.T) { require.Equal(r, 15, jwtProviderConfigEntry.CacheConfig.Size) // exported-services - entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) + entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) require.NoError(r, err) exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) require.True(r, ok, "could not cast to ExportedServicesConfigEntry") @@ -236,6 +236,41 @@ func TestControllerNamespaces(t *testing.T) { require.Equal(r, "partitionName", exportedServicesConfigEntry.Services[0].Consumers[0].Partition) require.Equal(r, "peerName", exportedServicesConfigEntry.Services[0].Consumers[1].Peer) require.Equal(r, "groupName", exportedServicesConfigEntry.Services[0].Consumers[2].SamenessGroup) + + // control-plane-request-limit + entry, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", defaultOpts) + require.NoError(r, err) + rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) + require.True(r, ok, "could not cast to RateLimitIPConfigEntry") + require.Equal(t, "permissive", rateLimitIPConfigEntry.Mode) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ACL.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ACL.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Catalog.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Catalog.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ConfigEntry.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ConfigEntry.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ConnectCA.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ConnectCA.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Coordinate.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Coordinate.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.DiscoveryChain.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.DiscoveryChain.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Health.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Health.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Intention.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Intention.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.KV.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.KV.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) + //require.Equal(t, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) + //require.Equal(t, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Session.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Session.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Txn.WriteRate) }) } @@ -282,6 +317,9 @@ func TestControllerNamespaces(t *testing.T) { patchPartition := "destination" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "exportedservices", "default", "-p", fmt.Sprintf(`{"spec": {"services": [{"name": "frontend", "namespace": "frontend", "consumers": [{"partition": "%s"}, {"peer": "peerName"}, {"samenessGroup": "groupName"}]}]}}`, patchPartition), "--type=merge") + logger.Log(t, "patching control-plane-request-limit custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "controlplanerequestlimit", "controlplanerequestlimit", "-p", `{"spec": {"mode": "disabled"}}`, "--type=merge") + counter := &retry.Counter{Count: 20, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -350,18 +388,25 @@ func TestControllerNamespaces(t *testing.T) { require.Equal(r, patchSNI, terminatingGatewayEntry.Services[0].SNI) // jwt-Provider - entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) + entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", defaultOpts) require.NoError(r, err) jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) require.True(r, ok, "could not cast to JWTProviderConfigEntry") require.Equal(r, patchIssuer, jwtProviderConfigEntry.Issuer) // exported-services - entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) + entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) require.NoError(r, err) exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) require.True(r, ok, "could not cast to ExportedServicesConfigEntry") require.Equal(r, patchPartition, exportedServicesConfigEntry.Services[0].Consumers[0].Partition) + + // control-plane-request-limit + entry, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", defaultOpts) + require.NoError(r, err) + rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) + require.True(r, ok, "could not cast to RateLimitIPConfigEntry") + require.Equal(r, rateLimitIPConfigEntry.Mode, "disabled") }) } @@ -400,6 +445,9 @@ func TestControllerNamespaces(t *testing.T) { logger.Log(t, "deleting exported-services custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "exportedservices", "default") + logger.Log(t, "deleting control-plane-request-limit custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "controlplanerequestlimit", "controlplanerequestlimit") + counter := &retry.Counter{Count: 20, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -448,12 +496,17 @@ func TestControllerNamespaces(t *testing.T) { require.Contains(r, err.Error(), "404 (Config entry not found") // jwt-provider - _, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) + _, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", defaultOpts) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") // exported-services - _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) + _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") + + // control-plane-request-limit + _, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", defaultOpts) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") }) diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index 5f88594468..089f96767f 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -203,6 +203,42 @@ func TestController(t *testing.T) { require.Equal(r, "frontend", exportedServicesConfigEntry.Services[0].Name) require.Equal(r, "peerName", exportedServicesConfigEntry.Services[0].Consumers[0].Peer) require.Equal(r, "groupName", exportedServicesConfigEntry.Services[0].Consumers[1].SamenessGroup) + + // control-plane-request-limit + entry, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", nil) + require.NoError(r, err) + rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) + require.True(r, ok, "could not cast to RateLimitIPConfigEntry") + require.Equal(t, "permissive", rateLimitIPConfigEntry.Mode) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ACL.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ACL.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Catalog.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Catalog.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ConfigEntry.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ConfigEntry.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ConnectCA.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.ConnectCA.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Coordinate.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Coordinate.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.DiscoveryChain.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.DiscoveryChain.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Health.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Health.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Intention.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Intention.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.KV.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.KV.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) + //require.Equal(t, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) + //require.Equal(t, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Session.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Session.WriteRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) + require.Equal(t, 100.0, rateLimitIPConfigEntry.Txn.WriteRate, 100.0) + }) } @@ -249,6 +285,9 @@ func TestController(t *testing.T) { patchPeer := "destination" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "exportedservices", "default", "-p", fmt.Sprintf(`{"spec": {"services": [{"name": "frontend", "consumers": [{"peer": "%s"}, {"samenessGroup": "groupName"}]}]}}`, patchPeer), "--type=merge") + logger.Log(t, "patching control-plane-request-limit custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "controlplanerequestlimit", "controlplanerequestlimit", "-p", `{"spec": {"mode": "disabled"}}`, "--type=merge") + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -330,6 +369,13 @@ func TestController(t *testing.T) { exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) require.True(r, ok, "could not cast to ExportedServicesConfigEntry") require.Equal(r, patchPeer, exportedServicesConfigEntry.Services[0].Consumers[0].Peer) + + // control-plane-request-limit + entry, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", nil) + require.NoError(r, err) + rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) + require.True(r, ok, "could not cast to RateLimitIPConfigEntry") + require.Equal(r, rateLimitIPConfigEntry.Mode, "disabled") }) } @@ -368,6 +414,9 @@ func TestController(t *testing.T) { logger.Log(t, "deleting exported-services custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "exportedservices", "default") + logger.Log(t, "deleting control-plane-request-limit custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "controlplanerequestlimit", "controlplanerequestlimit") + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -424,6 +473,11 @@ func TestController(t *testing.T) { _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") + + // control-plane-request-limit + _, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", nil) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") }) } }) diff --git a/acceptance/tests/fixtures/bases/crds-oss/controlplanerequestlimit.yaml b/acceptance/tests/fixtures/bases/crds-oss/controlplanerequestlimit.yaml new file mode 100644 index 0000000000..5e8e32dbb5 --- /dev/null +++ b/acceptance/tests/fixtures/bases/crds-oss/controlplanerequestlimit.yaml @@ -0,0 +1,50 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ControlPlaneRequestLimit +metadata: + name: controlplanerequestlimit +spec: + mode: "permissive" + readRate: 100.0 + writeRate: 100.0 + acl: + readRate: 100.0 + writeRate: 100.0 + catalog: + readRate: 100.0 + writeRate: 100.0 + configEntry: + readRate: 100.0 + writeRate: 100.0 + connectCA: + readRate: 100.0 + writeRate: 100.0 + coordinate: + readRate: 100.0 + writeRate: 100.0 + discoveryChain: + readRate: 100.0 + writeRate: 100.0 + health: + readRate: 100.0 + writeRate: 100.0 + intention: + readRate: 100.0 + writeRate: 100.0 + kv: + readRate: 100.0 + writeRate: 100.0 + tenancy: + readRate: 100.0 + writeRate: 100.0 +# preparedQuery: +# readRate: 100.0 +# writeRate: 100.0 + session: + readRate: 100.0 + writeRate: 100.0 + txn: + readRate: 100.0 + writeRate: 100.0 diff --git a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml index de02de61e9..77afbc9522 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml @@ -12,4 +12,5 @@ resources: - servicesplitter.yaml - terminatinggateway.yaml - jwtprovider.yaml -- exportedservices.yaml \ No newline at end of file +- exportedservices.yaml +- controlplanerequestlimit.yaml \ No newline at end of file diff --git a/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml b/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml index a8393cd8fd..44eff52492 100644 --- a/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml +++ b/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 730720d460..8c0bbe9bf7 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -27,6 +27,7 @@ rules: - gatewayclassconfigs - meshservices - samenessgroups + - controlplanerequestlimits {{- if .Values.global.peering.enabled }} - peeringacceptors - peeringdialers @@ -54,6 +55,7 @@ rules: - ingressgateways/status - terminatinggateways/status - samenessgroups/status + - controlplanerequestlimits/status {{- if .Values.global.peering.enabled }} - peeringacceptors/status - peeringdialers/status diff --git a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml index 5f26807e0a..e4fe79f621 100644 --- a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml +++ b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml @@ -222,6 +222,27 @@ webhooks: resources: - exportedservices sideEffects: None +- clientConfig: + service: + name: {{ template "consul.fullname" . }}-connect-injector + namespace: {{ .Release.Namespace }} + path: /mutate-v1alpha1-controlplanerequestlimits + failurePolicy: Fail + admissionReviewVersions: + - "v1beta1" + - "v1" + name: mutate-controlplanerequestlimit.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - controlplanerequestlimits + sideEffects: None - name: {{ template "consul.fullname" . }}-connect-injector.consul.hashicorp.com # The webhook will fail scheduling all pods that are not part of consul if all replicas of the webhook are unhealthy. objectSelector: diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml new file mode 100644 index 0000000000..67ff258eb8 --- /dev/null +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -0,0 +1,196 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: controlplanerequestlimits.consul.hashicorp.com +spec: + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + group: consul.hashicorp.com + names: + kind: ControlPlaneRequestLimit + listKind: ControlPlaneRequestLimitList + plural: controlplanerequestlimits + singular: controlplanerequestlimit + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: ControlPlaneRequestLimit is the Schema for the controlplanerequestlimits + API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ControlPlaneRequestLimitSpec defines the desired state of + ControlPlaneRequestLimit. + properties: + acl: + properties: + readRate: + type: number + writeRate: + type: number + type: object + catalog: + properties: + readRate: + type: number + writeRate: + type: number + type: object + configEntry: + properties: + readRate: + type: number + writeRate: + type: number + type: object + connectCA: + properties: + readRate: + type: number + writeRate: + type: number + type: object + coordinate: + properties: + readRate: + type: number + writeRate: + type: number + type: object + discoveryChain: + properties: + readRate: + type: number + writeRate: + type: number + type: object + health: + properties: + readRate: + type: number + writeRate: + type: number + type: object + intention: + properties: + readRate: + type: number + writeRate: + type: number + type: object + kv: + properties: + readRate: + type: number + writeRate: + type: number + type: object + mode: + type: string + perparedQuery: + properties: + readRate: + type: number + writeRate: + type: number + type: object + readRate: + type: number + session: + properties: + readRate: + type: number + writeRate: + type: number + type: object + tenancy: + properties: + readRate: + type: number + writeRate: + type: number + type: object + txn: + properties: + readRate: + type: number + writeRate: + type: number + type: object + writeRate: + type: number + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 7ffddf7537..8581ac4e88 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: exportedservices.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ExportedServices diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index ef33890461..eff7ef61a9 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: ingressgateways.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: IngressGateway diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index c7d20883e8..fa87f37489 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: jwtproviders.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: JWTProvider diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index cdc11b6ed9..f2549b5111 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: meshes.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: Mesh diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml index 859c8683ee..aa808113a2 100644 --- a/charts/consul/templates/crd-meshservices.yaml +++ b/charts/consul/templates/crd-meshservices.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: meshservices.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: MeshService diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index 3822f3bdfe..40f7f1d4d6 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: peeringacceptors.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: PeeringAcceptor diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index 405361c486..bfe4778d0c 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: peeringdialers.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: PeeringDialer diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 30dd25f674..a224effc12 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: proxydefaults.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ProxyDefaults diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index c1d1c85a8e..7cc3b71ae1 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: samenessgroups.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: SamenessGroup diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 2562c53320..8f284782e9 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicedefaults.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceDefaults diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 335d2eff7a..5f849f65ba 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: serviceintentions.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceIntentions diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index ed95c15846..ef380f77b5 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: serviceresolvers.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceResolver diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index 0157f646b4..c5ba99466c 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicerouters.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceRouter diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index 18fb10341e..abe3ac85cc 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicesplitters.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceSplitter diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 955496aeee..cd58d1679c 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: terminatinggateways.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: TerminatingGateway diff --git a/charts/consul/test/unit/connect-inject-mutatingwebhookconfiguration.bats b/charts/consul/test/unit/connect-inject-mutatingwebhookconfiguration.bats index 81eda87875..bc0876586c 100755 --- a/charts/consul/test/unit/connect-inject-mutatingwebhookconfiguration.bats +++ b/charts/consul/test/unit/connect-inject-mutatingwebhookconfiguration.bats @@ -60,7 +60,7 @@ load _helpers --set 'meshGateway.enabled=true' \ --set 'global.peering.enabled=true' \ . | tee /dev/stderr | - yq '.webhooks[11].name | contains("peeringacceptors.consul.hashicorp.com")' | tee /dev/stderr) + yq '.webhooks[12].name | contains("peeringacceptors.consul.hashicorp.com")' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(helm template \ -s templates/connect-inject-mutatingwebhookconfiguration.yaml \ @@ -69,6 +69,6 @@ load _helpers --set 'meshGateway.enabled=true' \ --set 'global.peering.enabled=true' \ . | tee /dev/stderr | - yq '.webhooks[12].name | contains("peeringdialers.consul.hashicorp.com")' | tee /dev/stderr) + yq '.webhooks[13].name | contains("peeringdialers.consul.hashicorp.com")' | tee /dev/stderr) [ "${actual}" = "true" ] } diff --git a/charts/consul/test/unit/crd-controlplanerequestlimits.bats b/charts/consul/test/unit/crd-controlplanerequestlimits.bats new file mode 100644 index 0000000000..ed98fc539f --- /dev/null +++ b/charts/consul/test/unit/crd-controlplanerequestlimits.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bats + +load _helpers + +@test "controlPlaneRequestLimit/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-controlplanerequestlimits.yaml \ + . | tee /dev/stderr | + yq -s 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "controlPlaneRequestLimit/CustomResourceDefinition: enabled with connectInject.enabled=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-controlplanerequestlimits.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + # The generated CRDs have "---" at the top which results in two objects + # being detected by yq, the first of which is null. We must therefore use + # yq -s so that length operates on both objects at once rather than + # individually, which would output false\ntrue and fail the test. + yq -s 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/control-plane/PROJECT b/control-plane/PROJECT index beec0e6504..5338cff047 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -99,4 +99,13 @@ resources: kind: JWTProvider path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: hashicorp.com + group: consul + kind: ControlPlaneRequestLimit + path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 779ece9218..a4063d6147 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -5,17 +5,18 @@ package common const ( - ServiceDefaults string = "servicedefaults" - ProxyDefaults string = "proxydefaults" - ServiceResolver string = "serviceresolver" - ServiceRouter string = "servicerouter" - ServiceSplitter string = "servicesplitter" - ServiceIntentions string = "serviceintentions" - ExportedServices string = "exportedservices" - IngressGateway string = "ingressgateway" - TerminatingGateway string = "terminatinggateway" - SamenessGroup string = "samenessgroup" - JWTProvider string = "jwtprovider" + ServiceDefaults string = "servicedefaults" + ProxyDefaults string = "proxydefaults" + ServiceResolver string = "serviceresolver" + ServiceRouter string = "servicerouter" + ServiceSplitter string = "servicesplitter" + ServiceIntentions string = "serviceintentions" + ExportedServices string = "exportedservices" + IngressGateway string = "ingressgateway" + TerminatingGateway string = "terminatinggateway" + SamenessGroup string = "samenessgroup" + JWTProvider string = "jwtprovider" + ControlPlaneRequestLimit string = "controlplanerequestlimit" Global string = "global" Mesh string = "mesh" diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_types.go b/control-plane/api/v1alpha1/controlplanerequestlimit_types.go new file mode 100644 index 0000000000..6b469696d1 --- /dev/null +++ b/control-plane/api/v1alpha1/controlplanerequestlimit_types.go @@ -0,0 +1,268 @@ +package v1alpha1 + +import ( + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + consul "github.com/hashicorp/consul/api" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +const ( + ControlPlaneRequestLimitKubeKind = "controlplanerequestlimit" +) + +func init() { + SchemeBuilder.Register(&ControlPlaneRequestLimit{}, &ControlPlaneRequestLimitList{}) +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// ControlPlaneRequestLimit is the Schema for the controlplanerequestlimits API. +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +type ControlPlaneRequestLimit struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ControlPlaneRequestLimitSpec `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ControlPlaneRequestLimitList contains a list of ControlPlaneRequestLimit. +type ControlPlaneRequestLimitList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ControlPlaneRequestLimit `json:"items"` +} + +type ReadWriteRatesConfig struct { + ReadRate float64 `json:"readRate,omitempty"` + WriteRate float64 `json:"writeRate,omitempty"` +} + +func (c *ReadWriteRatesConfig) toConsul() *consul.ReadWriteRatesConfig { + if c == nil { + return nil + } + return &consul.ReadWriteRatesConfig{ + ReadRate: c.ReadRate, + WriteRate: c.WriteRate, + } +} + +func (c *ReadWriteRatesConfig) validate(path *field.Path) field.ErrorList { + if c == nil { + return nil + } + + var errs field.ErrorList + + if c.ReadRate < 0 { + errs = append(errs, field.Invalid(path.Child("readRate"), c.ReadRate, "readRate must be >= 0")) + } + + if c.WriteRate <= 0 { + errs = append(errs, field.Invalid(path.Child("writeRate"), c.WriteRate, "writeRate must be > 0")) + } + return errs +} + +// ControlPlaneRequestLimitSpec defines the desired state of ControlPlaneRequestLimit. +type ControlPlaneRequestLimitSpec struct { + Mode string `json:"mode,omitempty"` + ReadWriteRatesConfig `json:",inline"` + ACL *ReadWriteRatesConfig `json:"acl,omitempty"` + Catalog *ReadWriteRatesConfig `json:"catalog,omitempty"` + ConfigEntry *ReadWriteRatesConfig `json:"configEntry,omitempty"` + ConnectCA *ReadWriteRatesConfig `json:"connectCA,omitempty"` + Coordinate *ReadWriteRatesConfig `json:"coordinate,omitempty"` + DiscoveryChain *ReadWriteRatesConfig `json:"discoveryChain,omitempty"` + Health *ReadWriteRatesConfig `json:"health,omitempty"` + Intention *ReadWriteRatesConfig `json:"intention,omitempty"` + KV *ReadWriteRatesConfig `json:"kv,omitempty"` + Tenancy *ReadWriteRatesConfig `json:"tenancy,omitempty"` + PreparedQuery *ReadWriteRatesConfig `json:"perparedQuery,omitempty"` + Session *ReadWriteRatesConfig `json:"session,omitempty"` + Txn *ReadWriteRatesConfig `json:"txn,omitempty"` +} + +// GetObjectMeta returns object meta. +func (c *ControlPlaneRequestLimit) GetObjectMeta() metav1.ObjectMeta { + return c.ObjectMeta +} + +// AddFinalizer adds a finalizer to the list of finalizers. +func (c *ControlPlaneRequestLimit) AddFinalizer(name string) { + c.ObjectMeta.Finalizers = append(c.ObjectMeta.Finalizers, name) +} + +// RemoveFinalizer removes this finalizer from the list. +func (c *ControlPlaneRequestLimit) RemoveFinalizer(name string) { + for i, n := range c.ObjectMeta.Finalizers { + if n == name { + c.ObjectMeta.Finalizers = append(c.ObjectMeta.Finalizers[:i], c.ObjectMeta.Finalizers[i+1:]...) + return + } + } +} + +// Finalizers returns the list of finalizers for this object. +func (c *ControlPlaneRequestLimit) Finalizers() []string { + return c.ObjectMeta.Finalizers +} + +// ConsulKind returns the Consul config entry kind, i.e. service-defaults, not +// servicedefaults. +func (c *ControlPlaneRequestLimit) ConsulKind() string { + return consul.RateLimitIPConfig +} + +// ConsulGlobalResource returns if the resource exists in the default +// Consul namespace only. +func (c *ControlPlaneRequestLimit) ConsulGlobalResource() bool { + return true +} + +// ConsulMirroringNS returns the Consul namespace that the config entry should +// be created in if namespaces and mirroring are enabled. +func (c *ControlPlaneRequestLimit) ConsulMirroringNS() string { + return common.DefaultConsulNamespace +} + +// KubeKind returns the Kube config entry kind, i.e. servicedefaults, not +// service-defaults. +func (c *ControlPlaneRequestLimit) KubeKind() string { + return ControlPlaneRequestLimitKubeKind +} + +// ConsulName returns the name of the config entry as saved in Consul. +// This may be different than KubernetesName() in the case of a ServiceIntentions +// config entry. +func (c *ControlPlaneRequestLimit) ConsulName() string { + return c.ObjectMeta.Name +} + +// KubernetesName returns the name of the Kubernetes resource. +func (c *ControlPlaneRequestLimit) KubernetesName() string { + return c.ObjectMeta.Name +} + +// SetSyncedCondition updates the synced condition. +func (c *ControlPlaneRequestLimit) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + c.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +// SetLastSyncedTime updates the last synced time. +func (c *ControlPlaneRequestLimit) SetLastSyncedTime(time *metav1.Time) { + c.Status.LastSyncedTime = time +} + +// SyncedCondition gets the synced condition. +func (c *ControlPlaneRequestLimit) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := c.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +// SyncedConditionStatus returns the status of the synced condition. +func (c *ControlPlaneRequestLimit) SyncedConditionStatus() corev1.ConditionStatus { + condition := c.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +// ToConsul converts the resource to the corresponding Consul API definition. +// Its return type is the generic ConfigEntry but a specific config entry +// type should be constructed e.g. ServiceConfigEntry. +func (c *ControlPlaneRequestLimit) ToConsul(datacenter string) consul.ConfigEntry { + return &consul.RateLimitIPConfigEntry{ + Kind: c.ConsulKind(), + Name: c.ConsulName(), + Mode: c.Spec.Mode, + ReadRate: c.Spec.ReadRate, + WriteRate: c.Spec.WriteRate, + Meta: meta(datacenter), + ACL: c.Spec.ACL.toConsul(), + Catalog: c.Spec.Catalog.toConsul(), + ConfigEntry: c.Spec.ConfigEntry.toConsul(), + ConnectCA: c.Spec.ConnectCA.toConsul(), + Coordinate: c.Spec.Coordinate.toConsul(), + DiscoveryChain: c.Spec.DiscoveryChain.toConsul(), + Health: c.Spec.Health.toConsul(), + Intention: c.Spec.Intention.toConsul(), + KV: c.Spec.KV.toConsul(), + Tenancy: c.Spec.Tenancy.toConsul(), + PreparedQuery: c.Spec.PreparedQuery.toConsul(), + Session: c.Spec.Session.toConsul(), + Txn: c.Spec.Txn.toConsul(), + } +} + +// MatchesConsul returns true if the resource has the same fields as the Consul +// config entry. +func (c *ControlPlaneRequestLimit) MatchesConsul(candidate consul.ConfigEntry) bool { + configEntry, ok := candidate.(*consul.RateLimitIPConfigEntry) + if !ok { + return false + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. + return cmp.Equal(c.ToConsul(""), configEntry, cmpopts.IgnoreFields(consul.RateLimitIPConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) +} + +// Validate returns an error if the resource is invalid. +func (c *ControlPlaneRequestLimit) Validate(consulMeta common.ConsulMeta) error { + var errs field.ErrorList + path := field.NewPath("spec") + + if c.Spec.Mode != "permissive" && c.Spec.Mode != "enforcing" && c.Spec.Mode != "disabled" { + errs = append(errs, field.Invalid(path.Child("mode"), c.Spec.Mode, "mode must be one of: permissive, enforcing, disabled")) + } + + errs = append(errs, c.Spec.ReadWriteRatesConfig.validate(path)...) + errs = append(errs, c.Spec.ACL.validate(path.Child("acl"))...) + errs = append(errs, c.Spec.Catalog.validate(path.Child("catalog"))...) + errs = append(errs, c.Spec.ConfigEntry.validate(path.Child("configEntry"))...) + errs = append(errs, c.Spec.ConnectCA.validate(path.Child("connectCA"))...) + errs = append(errs, c.Spec.Coordinate.validate(path.Child("coordinate"))...) + errs = append(errs, c.Spec.DiscoveryChain.validate(path.Child("discoveryChain"))...) + errs = append(errs, c.Spec.Health.validate(path.Child("health"))...) + errs = append(errs, c.Spec.Intention.validate(path.Child("intention"))...) + errs = append(errs, c.Spec.KV.validate(path.Child("kv"))...) + errs = append(errs, c.Spec.Tenancy.validate(path.Child("tenancy"))...) + errs = append(errs, c.Spec.PreparedQuery.validate(path.Child("preparedQuery"))...) + errs = append(errs, c.Spec.Session.validate(path.Child("session"))...) + errs = append(errs, c.Spec.Txn.validate(path.Child("txn"))...) + + if len(errs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: ConsulHashicorpGroup, Kind: ControlPlaneRequestLimitKubeKind}, + c.KubernetesName(), errs) + } + + return nil +} + +// DefaultNamespaceFields has no behaviour here as control-plane-request-limit have no namespace specific fields. +func (s *ControlPlaneRequestLimit) DefaultNamespaceFields(_ common.ConsulMeta) { +} diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go b/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go new file mode 100644 index 0000000000..5b8c4b4352 --- /dev/null +++ b/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go @@ -0,0 +1,566 @@ +package v1alpha1 + +import ( + "testing" + "time" + + consul "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +func TestControlPlaneRequestLimit_ToConsul(t *testing.T) { + cases := map[string]struct { + input *ControlPlaneRequestLimit + expected *consul.RateLimitIPConfigEntry + }{ + "empty fields": { + &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "disabled", + ReadWriteRatesConfig: ReadWriteRatesConfig{ + ReadRate: 0, + WriteRate: 0, + }, + }, + }, + &consul.RateLimitIPConfigEntry{ + Name: "foo", + Kind: consul.RateLimitIPConfig, + Mode: "disabled", + Meta: map[string]string{ + common.DatacenterKey: "datacenter", + common.SourceKey: common.SourceValue, + }, + ReadRate: 0, + WriteRate: 0, + }, + }, + "every field set": { + &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ACL: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + &consul.RateLimitIPConfigEntry{ + Kind: consul.RateLimitIPConfig, + Name: "foo", + Mode: "permissive", + ReadRate: 100.0, + WriteRate: 100.0, + Meta: map[string]string{ + common.DatacenterKey: "datacenter", + common.SourceKey: common.SourceValue, + }, + ACL: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + } + + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + output := testCase.input.ToConsul("datacenter") + require.Equal(t, testCase.expected, output) + }) + } +} + +func TestControlPlaneRequestLimit_MatchesConsul(t *testing.T) { + cases := map[string]struct { + internal *ControlPlaneRequestLimit + consul consul.ConfigEntry + matches bool + }{ + "empty fields matches": { + &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-test-service", + }, + Spec: ControlPlaneRequestLimitSpec{}, + }, + &consul.RateLimitIPConfigEntry{ + Kind: consul.RateLimitIPConfig, + Name: "my-test-service", + Namespace: "namespace", + CreateIndex: 1, + ModifyIndex: 2, + Meta: map[string]string{ + common.SourceKey: common.SourceValue, + common.DatacenterKey: "datacenter", + }, + }, + true, + }, + "all fields populated matches": { + &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-test-service", + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ACL: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + &consul.RateLimitIPConfigEntry{ + Kind: consul.RateLimitIPConfig, + Name: "my-test-service", + Mode: "permissive", + ReadRate: 100.0, + WriteRate: 100.0, + Meta: map[string]string{ + common.DatacenterKey: "datacenter", + common.SourceKey: common.SourceValue, + }, + ACL: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &consul.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + true, + }, + "mismatched types does not match": { + &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-test-service", + }, + Spec: ControlPlaneRequestLimitSpec{}, + }, + &consul.ProxyConfigEntry{ + Kind: consul.RateLimitIPConfig, + Name: "my-test-service", + Namespace: "namespace", + CreateIndex: 1, + ModifyIndex: 2, + }, + false, + }, + } + + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + require.Equal(t, testCase.matches, testCase.internal.MatchesConsul(testCase.consul)) + }) + } +} + +func TestControlPlaneRequestLimit_Validate(t *testing.T) { + invalidReadWriteRatesConfig := &ReadWriteRatesConfig{ + ReadRate: -1, + WriteRate: 0, + } + + validReadWriteRatesConfig := &ReadWriteRatesConfig{ + ReadRate: 100, + WriteRate: 100, + } + + cases := map[string]struct { + input *ControlPlaneRequestLimit + expectedErrMsgs []string + }{ + "invalid": { + input: &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ControlPlaneRequestLimit, + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "invalid", + ACL: invalidReadWriteRatesConfig, + Catalog: invalidReadWriteRatesConfig, + ConfigEntry: invalidReadWriteRatesConfig, + ConnectCA: invalidReadWriteRatesConfig, + Coordinate: invalidReadWriteRatesConfig, + DiscoveryChain: invalidReadWriteRatesConfig, + Health: invalidReadWriteRatesConfig, + Intention: invalidReadWriteRatesConfig, + KV: invalidReadWriteRatesConfig, + Tenancy: invalidReadWriteRatesConfig, + PreparedQuery: invalidReadWriteRatesConfig, + Session: invalidReadWriteRatesConfig, + Txn: invalidReadWriteRatesConfig, + }, + }, + expectedErrMsgs: []string{ + `spec.mode: Invalid value: "invalid": mode must be one of: permissive, enforcing, disabled`, + `spec.acl.readRate: Invalid value: -1: readRate must be >= 0, spec.acl.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.catalog.readRate: Invalid value: -1: readRate must be >= 0, spec.catalog.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.configEntry.readRate: Invalid value: -1: readRate must be >= 0, spec.configEntry.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.connectCA.readRate: Invalid value: -1: readRate must be >= 0, spec.connectCA.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.coordinate.readRate: Invalid value: -1: readRate must be >= 0, spec.coordinate.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.discoveryChain.readRate: Invalid value: -1: readRate must be >= 0, spec.discoveryChain.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.health.readRate: Invalid value: -1: readRate must be >= 0, spec.health.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.intention.readRate: Invalid value: -1: readRate must be >= 0, spec.intention.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.kv.readRate: Invalid value: -1: readRate must be >= 0, spec.kv.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.tenancy.readRate: Invalid value: -1: readRate must be >= 0, spec.tenancy.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.preparedQuery.readRate: Invalid value: -1: readRate must be >= 0, spec.preparedQuery.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.session.readRate: Invalid value: -1: readRate must be >= 0, spec.session.writeRate: Invalid value: 0: writeRate must be > 0`, + `spec.txn.readRate: Invalid value: -1: readRate must be >= 0, spec.txn.writeRate: Invalid value: 0: writeRate must be > 0`, + }, + }, + "valid": { + input: &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ControlPlaneRequestLimit, + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: *validReadWriteRatesConfig, + ACL: validReadWriteRatesConfig, + Catalog: validReadWriteRatesConfig, + ConfigEntry: validReadWriteRatesConfig, + ConnectCA: validReadWriteRatesConfig, + Coordinate: validReadWriteRatesConfig, + DiscoveryChain: validReadWriteRatesConfig, + Health: validReadWriteRatesConfig, + Intention: validReadWriteRatesConfig, + KV: validReadWriteRatesConfig, + Tenancy: validReadWriteRatesConfig, + PreparedQuery: validReadWriteRatesConfig, + Session: validReadWriteRatesConfig, + Txn: validReadWriteRatesConfig, + }, + }, + expectedErrMsgs: []string{}, + }, + } + + for name, testCase := range cases { + t.Run(name, func(t *testing.T) { + err := testCase.input.Validate(common.ConsulMeta{}) + if len(testCase.expectedErrMsgs) != 0 { + require.Error(t, err) + for _, s := range testCase.expectedErrMsgs { + require.Contains(t, err.Error(), s) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestControlPlaneRequestLimit_AddFinalizer(t *testing.T) { + controlPlaneRequestLimit := &ControlPlaneRequestLimit{} + controlPlaneRequestLimit.AddFinalizer("finalizer") + require.Equal(t, []string{"finalizer"}, controlPlaneRequestLimit.ObjectMeta.Finalizers) +} + +func TestControlPlaneRequestLimit_RemoveFinalizer(t *testing.T) { + controlPlaneRequestLimit := &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{"f1", "f2"}, + }, + } + controlPlaneRequestLimit.RemoveFinalizer("f1") + require.Equal(t, []string{"f2"}, controlPlaneRequestLimit.ObjectMeta.Finalizers) +} + +func TestControlPlaneRequestLimit_SetSyncedCondition(t *testing.T) { + controlPlaneRequestLimit := &ControlPlaneRequestLimit{} + controlPlaneRequestLimit.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") + + require.Equal(t, corev1.ConditionTrue, controlPlaneRequestLimit.Status.Conditions[0].Status) + require.Equal(t, "reason", controlPlaneRequestLimit.Status.Conditions[0].Reason) + require.Equal(t, "message", controlPlaneRequestLimit.Status.Conditions[0].Message) + now := metav1.Now() + require.True(t, controlPlaneRequestLimit.Status.Conditions[0].LastTransitionTime.Before(&now)) +} + +func TestControlPlaneRequestLimit_SetLastSyncedTime(t *testing.T) { + controlPlaneRequestLimit := &ControlPlaneRequestLimit{} + syncedTime := metav1.NewTime(time.Now()) + controlPlaneRequestLimit.SetLastSyncedTime(&syncedTime) + + require.Equal(t, &syncedTime, controlPlaneRequestLimit.Status.LastSyncedTime) +} + +func TestControlPlaneRequestLimit_GetSyncedConditionStatus(t *testing.T) { + cases := []corev1.ConditionStatus{ + corev1.ConditionUnknown, + corev1.ConditionFalse, + corev1.ConditionTrue, + } + for _, status := range cases { + t.Run(string(status), func(t *testing.T) { + controlPlaneRequestLimit := &ControlPlaneRequestLimit{ + Status: Status{ + Conditions: []Condition{{ + Type: ConditionSynced, + Status: status, + }}, + }, + } + + require.Equal(t, status, controlPlaneRequestLimit.SyncedConditionStatus()) + }) + } +} + +func TestControlPlaneRequestLimit_GetConditionWhenStatusNil(t *testing.T) { + require.Nil(t, (&ControlPlaneRequestLimit{}).GetCondition(ConditionSynced)) +} + +func TestControlPlaneRequestLimit_SyncedConditionStatusWhenStatusNil(t *testing.T) { + require.Equal(t, corev1.ConditionUnknown, (&ControlPlaneRequestLimit{}).SyncedConditionStatus()) +} + +func TestControlPlaneRequestLimit_SyncedConditionWhenStatusNil(t *testing.T) { + status, reason, message := (&ControlPlaneRequestLimit{}).SyncedCondition() + require.Equal(t, corev1.ConditionUnknown, status) + require.Equal(t, "", reason) + require.Equal(t, "", message) +} + +func TestControlPlaneRequestLimit_ConsulKind(t *testing.T) { + require.Equal(t, consul.RateLimitIPConfig, (&ControlPlaneRequestLimit{}).ConsulKind()) +} + +func TestControlPlaneRequestLimit_KubeKind(t *testing.T) { + require.Equal(t, "controlplanerequestlimit", (&ControlPlaneRequestLimit{}).KubeKind()) +} + +func TestControlPlaneRequestLimit_ConsulName(t *testing.T) { + require.Equal(t, "foo", (&ControlPlaneRequestLimit{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).ConsulName()) +} + +func TestControlPlaneRequestLimit_KubernetesName(t *testing.T) { + require.Equal(t, "foo", (&ControlPlaneRequestLimit{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).KubernetesName()) +} + +func TestControlPlaneRequestLimit_ConsulNamespace(t *testing.T) { + require.Equal(t, "default", (&ControlPlaneRequestLimit{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}}).ConsulMirroringNS()) +} + +func TestControlPlaneRequestLimit_ConsulGlobalResource(t *testing.T) { + require.True(t, (&ControlPlaneRequestLimit{}).ConsulGlobalResource()) +} + +func TestControlPlaneRequestLimit_ObjectMeta(t *testing.T) { + meta := metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + } + controlPlaneRequestLimit := &ControlPlaneRequestLimit{ + ObjectMeta: meta, + } + require.Equal(t, meta, controlPlaneRequestLimit.GetObjectMeta()) +} diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_webhook.go b/control-plane/api/v1alpha1/controlplanerequestlimit_webhook.go new file mode 100644 index 0000000000..d99d9143f7 --- /dev/null +++ b/control-plane/api/v1alpha1/controlplanerequestlimit_webhook.go @@ -0,0 +1,83 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "context" + "fmt" + "net/http" + + "github.com/go-logr/logr" + admissionv1 "k8s.io/api/admission/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +// +kubebuilder:object:generate=false + +type ControlPlaneRequestLimitWebhook struct { + client.Client + Logger logr.Logger + decoder *admission.Decoder + ConsulMeta common.ConsulMeta +} + +// NOTE: The path value in the below line is the path to the webhook. +// If it is updated, run code-gen, update subcommand/controller/command.go +// and the consul-helm value for the path to the webhook. +// +// NOTE: The below line cannot be combined with any other comment. If it is +// it will break the code generation. +// +// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-controlplanerequestlimits,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=controlplanerequestlimits,versions=v1alpha1,name=mutate-controlplanerequestlimits.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *ControlPlaneRequestLimitWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var limit ControlPlaneRequestLimit + var limitList ControlPlaneRequestLimitList + err := v.decoder.Decode(req, &limit) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + if req.Operation == admissionv1.Create { + v.Logger.Info("validate create", "name", limit.KubernetesName()) + + if limit.KubernetesName() != common.ControlPlaneRequestLimit { + return admission.Errored(http.StatusBadRequest, + fmt.Errorf(`%s resource name must be "%s"`, + limit.KubeKind(), common.ControlPlaneRequestLimit)) + } + + if err := v.Client.List(ctx, &limitList); err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + + if len(limitList.Items) > 0 { + return admission.Errored(http.StatusBadRequest, + fmt.Errorf("%s resource already defined - only one control plane request limit entry is supported", + limit.KubeKind())) + } + } + + return common.ValidateConfigEntry(ctx, req, v.Logger, v, &limit, v.ConsulMeta) +} + +func (v *ControlPlaneRequestLimitWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { + var limitList ControlPlaneRequestLimitList + if err := v.Client.List(ctx, &limitList); err != nil { + return nil, err + } + var entries []common.ConfigEntryResource + for _, item := range limitList.Items { + entries = append(entries, common.ConfigEntryResource(&item)) + } + return entries, nil +} + +func (v *ControlPlaneRequestLimitWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_webhook_test.go b/control-plane/api/v1alpha1/controlplanerequestlimit_webhook_test.go new file mode 100644 index 0000000000..c1ab7cc6af --- /dev/null +++ b/control-plane/api/v1alpha1/controlplanerequestlimit_webhook_test.go @@ -0,0 +1,145 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "context" + "encoding/json" + "testing" + + logrtest "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +func TestValidateControlPlaneRequestLimit(t *testing.T) { + otherNS := "other" + + cases := map[string]struct { + existingResources []runtime.Object + newResource *ControlPlaneRequestLimit + expAllow bool + expErrMessage string + }{ + "no duplicates, valid": { + existingResources: nil, + newResource: &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ControlPlaneRequestLimit, + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: ReadWriteRatesConfig{ + ReadRate: 100, + WriteRate: 100, + }, + }, + }, + expAllow: true, + }, + "invalid resource name": { + existingResources: nil, + newResource: &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid", + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: ReadWriteRatesConfig{ + ReadRate: 100, + WriteRate: 100, + }, + }, + }, + expAllow: false, + expErrMessage: `controlplanerequestlimit resource name must be "controlplanerequestlimit"`, + }, + "resource already exists": { + existingResources: []runtime.Object{ + &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ControlPlaneRequestLimit, + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: ReadWriteRatesConfig{ + ReadRate: 100, + WriteRate: 100, + }, + }, + }, + }, + newResource: &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ControlPlaneRequestLimit, + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: ReadWriteRatesConfig{ + ReadRate: 100, + WriteRate: 100, + }, + }, + }, + expAllow: false, + expErrMessage: `controlplanerequestlimit resource already defined - only one control plane request limit entry is supported`, + }, + "invalid spec": { + existingResources: nil, + newResource: &ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.ControlPlaneRequestLimit, + }, + Spec: ControlPlaneRequestLimitSpec{ + Mode: "invalid", + ReadWriteRatesConfig: ReadWriteRatesConfig{ + ReadRate: 100, + WriteRate: 100, + }, + }, + }, + expAllow: false, + expErrMessage: `controlplanerequestlimit.consul.hashicorp.com "controlplanerequestlimit" is invalid: spec.mode: Invalid value: "invalid": mode must be one of: permissive, enforcing, disabled`, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + ctx := context.Background() + marshalledRequestObject, err := json.Marshal(c.newResource) + require.NoError(t, err) + s := runtime.NewScheme() + s.AddKnownTypes(GroupVersion, &ControlPlaneRequestLimit{}, &ControlPlaneRequestLimitList{}) + client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + + validator := &ControlPlaneRequestLimitWebhook{ + Client: client, + Logger: logrtest.New(t), + decoder: decoder, + } + response := validator.Handle(ctx, admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Name: c.newResource.KubernetesName(), + Namespace: otherNS, + Operation: admissionv1.Create, + Object: runtime.RawExtension{ + Raw: marshalledRequestObject, + }, + }, + }) + + require.Equal(t, c.expAllow, response.Allowed) + if c.expErrMessage != "" { + require.Equal(t, c.expErrMessage, response.AdmissionResponse.Result.Message) + } + }) + } +} diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index e8bff89986..0787f24097 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -64,6 +64,145 @@ func (in Conditions) DeepCopy() Conditions { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControlPlaneRequestLimit) DeepCopyInto(out *ControlPlaneRequestLimit) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneRequestLimit. +func (in *ControlPlaneRequestLimit) DeepCopy() *ControlPlaneRequestLimit { + if in == nil { + return nil + } + out := new(ControlPlaneRequestLimit) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ControlPlaneRequestLimit) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControlPlaneRequestLimitList) DeepCopyInto(out *ControlPlaneRequestLimitList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ControlPlaneRequestLimit, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneRequestLimitList. +func (in *ControlPlaneRequestLimitList) DeepCopy() *ControlPlaneRequestLimitList { + if in == nil { + return nil + } + out := new(ControlPlaneRequestLimitList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ControlPlaneRequestLimitList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControlPlaneRequestLimitSpec) DeepCopyInto(out *ControlPlaneRequestLimitSpec) { + *out = *in + if in.ACL != nil { + in, out := &in.ACL, &out.ACL + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.Catalog != nil { + in, out := &in.Catalog, &out.Catalog + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.ConfigEntry != nil { + in, out := &in.ConfigEntry, &out.ConfigEntry + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.ConnectCA != nil { + in, out := &in.ConnectCA, &out.ConnectCA + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.Coordinate != nil { + in, out := &in.Coordinate, &out.Coordinate + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.DiscoveryChain != nil { + in, out := &in.DiscoveryChain, &out.DiscoveryChain + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.Health != nil { + in, out := &in.Health, &out.Health + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.Intention != nil { + in, out := &in.Intention, &out.Intention + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.KV != nil { + in, out := &in.KV, &out.KV + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.Tenancy != nil { + in, out := &in.Tenancy, &out.Tenancy + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.PreparedQuery != nil { + in, out := &in.PreparedQuery, &out.PreparedQuery + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.Session != nil { + in, out := &in.Session, &out.Session + *out = new(ReadWriteRatesConfig) + **out = **in + } + if in.Txn != nil { + in, out := &in.Txn, &out.Txn + *out = new(ReadWriteRatesConfig) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneRequestLimitSpec. +func (in *ControlPlaneRequestLimitSpec) DeepCopy() *ControlPlaneRequestLimitSpec { + if in == nil { + return nil + } + out := new(ControlPlaneRequestLimitSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CookieConfig) DeepCopyInto(out *CookieConfig) { *out = *in @@ -1848,6 +1987,20 @@ func (in *RetryPolicyBackOff) DeepCopy() *RetryPolicyBackOff { return out } +func (in *ReadWriteRatesConfig) DeepCopyInto(out *ReadWriteRatesConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReadWriteRatesConfig. +func (in *ReadWriteRatesConfig) DeepCopy() *ReadWriteRatesConfig { + if in == nil { + return nil + } + out := new(ReadWriteRatesConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RingHashConfig) DeepCopyInto(out *RingHashConfig) { *out = *in diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml new file mode 100644 index 0000000000..4d1d808428 --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml @@ -0,0 +1,191 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: controlplanerequestlimits.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: ControlPlaneRequestLimit + listKind: ControlPlaneRequestLimitList + plural: controlplanerequestlimits + singular: controlplanerequestlimit + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: ControlPlaneRequestLimit is the Schema for the controlplanerequestlimits + API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ControlPlaneRequestLimitSpec defines the desired state of + ControlPlaneRequestLimit. + properties: + acl: + properties: + readRate: + type: number + writeRate: + type: number + type: object + catalog: + properties: + readRate: + type: number + writeRate: + type: number + type: object + configEntry: + properties: + readRate: + type: number + writeRate: + type: number + type: object + connectCA: + properties: + readRate: + type: number + writeRate: + type: number + type: object + coordinate: + properties: + readRate: + type: number + writeRate: + type: number + type: object + discoveryChain: + properties: + readRate: + type: number + writeRate: + type: number + type: object + health: + properties: + readRate: + type: number + writeRate: + type: number + type: object + intention: + properties: + readRate: + type: number + writeRate: + type: number + type: object + kv: + properties: + readRate: + type: number + writeRate: + type: number + type: object + mode: + type: string + perparedQuery: + properties: + readRate: + type: number + writeRate: + type: number + type: object + readRate: + type: number + session: + properties: + readRate: + type: number + writeRate: + type: number + type: object + tenancy: + properties: + readRate: + type: number + writeRate: + type: number + type: object + txn: + properties: + readRate: + type: number + writeRate: + type: number + type: object + writeRate: + type: number + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index f066c90612..dac72f3646 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml index a8393cd8fd..44eff52492 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index f7ccf205d9..e9cf081721 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml index 8ca1ec0748..7506cc57dc 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com 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 bc46b6ab37..16dd398f99 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml index 0871fc32e5..125883bdc5 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index f6f9eda72b..894228a218 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index 7e0927c169..51c3e38319 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 7396816f7e..1be3b37703 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml index 23de092485..259ca7b910 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 0890c6323b..c86138cf3d 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com 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 cd28173ba8..9553c73450 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index 3cd3b37324..a8a8107439 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 5919e23005..04590cc007 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index d5848ed6ec..3a47472ba7 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index 4910e42829..acf61cde4c 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/kustomization.yaml b/control-plane/config/crd/kustomization.yaml new file mode 100644 index 0000000000..2b1d90d6d0 --- /dev/null +++ b/control-plane/config/crd/kustomization.yaml @@ -0,0 +1,21 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/consul.hashicorp.com_controlplanerequestlimits.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- patches/webhook_in_controlplanerequestlimits.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- patches/cainjection_in_controlplanerequestlimits.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/control-plane/config/crd/kustomizeconfig.yaml b/control-plane/config/crd/kustomizeconfig.yaml new file mode 100644 index 0000000000..6f83d9a94b --- /dev/null +++ b/control-plane/config/crd/kustomizeconfig.yaml @@ -0,0 +1,17 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + group: apiextensions.k8s.io + path: spec/conversion/webhookClientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + group: apiextensions.k8s.io + path: spec/conversion/webhookClientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 28ddd913b5..4fac4ac9b8 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -5,7 +5,6 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - creationTimestamp: null name: manager-role rules: - apiGroups: @@ -26,6 +25,26 @@ rules: - secrets/status verbs: - get +- apiGroups: + - consul.hashicorp.com + resources: + - controlplanerequestlimits + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - consul.hashicorp.com + resources: + - controlplanerequestlimits/status + verbs: + - get + - patch + - update - apiGroups: - consul.hashicorp.com resources: diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index 3a65e10d1b..6d748ed06c 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -5,9 +5,29 @@ apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: - creationTimestamp: null name: mutating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v1alpha1-controlplanerequestlimits + failurePolicy: Fail + name: mutate-controlplanerequestlimits.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - controlplanerequestlimits + sideEffects: None - admissionReviewVersions: - v1beta1 - v1 diff --git a/control-plane/controllers/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go index 38a02732d0..0d5f8af5bf 100644 --- a/control-plane/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -476,6 +476,119 @@ func TestConfigEntryControllers_createsConfigEntry(t *testing.T) { require.Equal(t, "test-issuer", jwt.Issuer) }, }, + { + kubeKind: "ControlPlaneRequestLimit", + consulKind: capi.RateLimitIPConfig, + configEntryResource: &v1alpha1.ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: v1alpha1.ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ACL: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &ControlPlaneRequestLimitController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + resource, ok := consulEntry.(*capi.RateLimitIPConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, "permissive", resource.Mode) + require.Equal(t, 100.0, resource.ReadRate) + require.Equal(t, 100.0, resource.WriteRate) + require.Equal(t, 100.0, resource.ACL.ReadRate) + require.Equal(t, 100.0, resource.ACL.WriteRate) + require.Equal(t, 100.0, resource.Catalog.ReadRate) + require.Equal(t, 100.0, resource.Catalog.WriteRate) + require.Equal(t, 100.0, resource.ConfigEntry.ReadRate) + require.Equal(t, 100.0, resource.ConfigEntry.WriteRate) + require.Equal(t, 100.0, resource.ConnectCA.ReadRate) + require.Equal(t, 100.0, resource.ConnectCA.WriteRate) + require.Equal(t, 100.0, resource.Coordinate.ReadRate) + require.Equal(t, 100.0, resource.Coordinate.WriteRate) + require.Equal(t, 100.0, resource.DiscoveryChain.ReadRate) + require.Equal(t, 100.0, resource.DiscoveryChain.WriteRate) + require.Equal(t, 100.0, resource.Health.ReadRate) + require.Equal(t, 100.0, resource.Health.WriteRate) + require.Equal(t, 100.0, resource.Intention.ReadRate) + require.Equal(t, 100.0, resource.Intention.WriteRate) + require.Equal(t, 100.0, resource.KV.ReadRate) + require.Equal(t, 100.0, resource.KV.WriteRate) + require.Equal(t, 100.0, resource.Tenancy.ReadRate) + require.Equal(t, 100.0, resource.Tenancy.WriteRate) + require.Equal(t, 100.0, resource.PreparedQuery.ReadRate) + require.Equal(t, 100.0, resource.PreparedQuery.WriteRate) + require.Equal(t, 100.0, resource.Session.ReadRate) + require.Equal(t, 100.0, resource.Session.WriteRate) + require.Equal(t, 100.0, resource.Txn.ReadRate) + require.Equal(t, 100.0, resource.Txn.WriteRate, 100.0) + }, + }, } for _, c := range cases { @@ -953,7 +1066,6 @@ func TestConfigEntryControllers_updatesConfigEntry(t *testing.T) { require.Equal(t, "new-sni", resource.Services[0].SNI) }, }, - { kubeKind: "JWTProvider", consulKind: capi.JWTProvider, @@ -1004,6 +1116,123 @@ func TestConfigEntryControllers_updatesConfigEntry(t *testing.T) { require.Equal(t, []string{"aud1"}, jwt.Audiences) }, }, + { + kubeKind: "ControlPlaneRequestLimit", + consulKind: capi.RateLimitIPConfig, + configEntryResource: &v1alpha1.ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: v1alpha1.ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ACL: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &ControlPlaneRequestLimitController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + updateF: func(resource common.ConfigEntryResource) { + ipRateLimit := resource.(*v1alpha1.ControlPlaneRequestLimit) + ipRateLimit.Spec.Mode = "enforcing" + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + resource, ok := consulEntry.(*capi.RateLimitIPConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, "enforcing", resource.Mode) + require.Equal(t, 100.0, resource.ReadRate) + require.Equal(t, 100.0, resource.WriteRate) + require.Equal(t, 100.0, resource.ACL.ReadRate) + require.Equal(t, 100.0, resource.ACL.WriteRate) + require.Equal(t, 100.0, resource.Catalog.ReadRate) + require.Equal(t, 100.0, resource.Catalog.WriteRate) + require.Equal(t, 100.0, resource.ConfigEntry.ReadRate) + require.Equal(t, 100.0, resource.ConfigEntry.WriteRate) + require.Equal(t, 100.0, resource.ConnectCA.ReadRate) + require.Equal(t, 100.0, resource.ConnectCA.WriteRate) + require.Equal(t, 100.0, resource.Coordinate.ReadRate) + require.Equal(t, 100.0, resource.Coordinate.WriteRate) + require.Equal(t, 100.0, resource.DiscoveryChain.ReadRate) + require.Equal(t, 100.0, resource.DiscoveryChain.WriteRate) + require.Equal(t, 100.0, resource.Health.ReadRate) + require.Equal(t, 100.0, resource.Health.WriteRate) + require.Equal(t, 100.0, resource.Intention.ReadRate) + require.Equal(t, 100.0, resource.Intention.WriteRate) + require.Equal(t, 100.0, resource.KV.ReadRate) + require.Equal(t, 100.0, resource.KV.WriteRate) + require.Equal(t, 100.0, resource.Tenancy.ReadRate) + require.Equal(t, 100.0, resource.Tenancy.WriteRate) + require.Equal(t, 100.0, resource.PreparedQuery.ReadRate) + require.Equal(t, 100.0, resource.PreparedQuery.WriteRate) + require.Equal(t, 100.0, resource.Session.ReadRate) + require.Equal(t, 100.0, resource.Session.WriteRate) + require.Equal(t, 100.0, resource.Txn.ReadRate) + require.Equal(t, 100.0, resource.Txn.WriteRate) + }, + }, } for _, c := range cases { @@ -1436,6 +1665,89 @@ func TestConfigEntryControllers_deletesConfigEntry(t *testing.T) { } }, }, + { + + kubeKind: "ControlPlaneRequestLimit", + consulKind: capi.RateLimitIPConfig, + configEntryResourceWithDeletion: &v1alpha1.ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + Finalizers: []string{FinalizerName}, + }, + Spec: v1alpha1.ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ACL: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &ControlPlaneRequestLimitController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + }, } for _, c := range cases { diff --git a/control-plane/controllers/controlplanerequestlimit_controller.go b/control-plane/controllers/controlplanerequestlimit_controller.go new file mode 100644 index 0000000000..0441f1ed14 --- /dev/null +++ b/control-plane/controllers/controlplanerequestlimit_controller.go @@ -0,0 +1,40 @@ +package controllers + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +// ControlPlaneRequestLimitController reconciles a ControlPlaneRequestLimit object. +type ControlPlaneRequestLimitController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + ConfigEntryController *ConfigEntryController +} + +//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=controlplanerequestlimits,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=controlplanerequestlimits/status,verbs=get;update;patch + +func (r *ControlPlaneRequestLimitController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.ConfigEntryController.ReconcileEntry(ctx, r, req, &consulv1alpha1.ControlPlaneRequestLimit{}) +} + +func (r *ControlPlaneRequestLimitController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *ControlPlaneRequestLimitController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *ControlPlaneRequestLimitController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &consulv1alpha1.ControlPlaneRequestLimit{}, r) +} diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index c969b3058b..9e99bd1a03 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -646,6 +646,15 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", apicommon.JWTProvider) return 1 } + if err = (&controllers.ControlPlaneRequestLimitController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ControlPlaneRequestLimit), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ControlPlaneRequestLimit) + return 1 + } if err = mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) @@ -817,6 +826,12 @@ func (c *Command) Run(args []string) int { Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.JWTProvider), ConsulMeta: consulMeta, }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-controlplanerequestlimits", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ControlPlaneRequestLimitWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ControlPlaneRequestLimit), + ConsulMeta: consulMeta, + }}) if c.flagEnableWebhookCAUpdate { err = c.updateWebhookCABundle(ctx) From 48f97c8a55c9f7d4df1955615f85ad5c103906a7 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 5 Jun 2023 11:51:11 -0400 Subject: [PATCH 198/592] Update casing of json tag for ServiceDefault field (#2266) --- .changelog/2266.txt | 3 +++ acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml | 2 +- charts/consul/templates/crd-servicedefaults.yaml | 4 ++-- control-plane/api/v1alpha1/servicedefaults_types.go | 2 +- .../crd/bases/consul.hashicorp.com_servicedefaults.yaml | 4 ++-- 5 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 .changelog/2266.txt diff --git a/.changelog/2266.txt b/.changelog/2266.txt new file mode 100644 index 0000000000..e156f95f8c --- /dev/null +++ b/.changelog/2266.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. +``` \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index 1a6818f7fe..cd9c35fa39 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -21,7 +21,7 @@ spec: passiveHealthCheck: interval: 1s maxFailures: 10 - enforcing_consecutive_5xx: 60 + enforcingConsecutive5xx: 60 maxEjectionPercent: 100 baseEjectionTime: 20s - name: "bar" diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 8f284782e9..e295732bfa 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -292,7 +292,7 @@ spec: capped by max_ejection_time (Default 300s). Defaults to 30000ms or 30s. type: string - enforcing_consecutive_5xx: + enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can @@ -409,7 +409,7 @@ spec: is capped by max_ejection_time (Default 300s). Defaults to 30000ms or 30s. type: string - enforcing_consecutive_5xx: + enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 2f3b95a297..54044cb3a8 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -195,7 +195,7 @@ type PassiveHealthCheck struct { // EnforcingConsecutive5xx is the % chance that a host will be actually ejected // when an outlier status is detected through consecutive 5xx. // This setting can be used to disable ejection or to ramp it up slowly. - EnforcingConsecutive5xx *uint32 `json:"enforcing_consecutive_5xx,omitempty"` + EnforcingConsecutive5xx *uint32 `json:"enforcingConsecutive5xx,omitempty"` // The maximum % of an upstream cluster that can be ejected due to outlier detection. // Defaults to 10% but will eject at least one host regardless of the value. MaxEjectionPercent *uint32 `json:"maxEjectionPercent,omitempty"` diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index c86138cf3d..83503f11f3 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -288,7 +288,7 @@ spec: capped by max_ejection_time (Default 300s). Defaults to 30000ms or 30s. type: string - enforcing_consecutive_5xx: + enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can @@ -405,7 +405,7 @@ spec: is capped by max_ejection_time (Default 300s). Defaults to 30000ms or 30s. type: string - enforcing_consecutive_5xx: + enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting From 2ddd05a218d8d22ce807fd8b9d1af1f303f4ee39 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Mon, 5 Jun 2023 15:06:37 -0400 Subject: [PATCH 199/592] Add the endpoint ignoring logic for triggering gateway reconciliation (#2227) --- .../controllers/gateway_controller.go | 13 +- .../controllers/gateway_controller_test.go | 254 ++++++++++++++++++ 2 files changed, 261 insertions(+), 6 deletions(-) diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index cc90ad6f17..80c8e83977 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -368,10 +368,6 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co source.NewKindWithCache(&corev1.Secret{}, mgr.GetCache()), handler.EnqueueRequestsFromMapFunc(r.transformSecret(ctx)), ). - Watches( - source.NewKindWithCache(&gwv1beta1.ReferenceGrant{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformReferenceGrant(ctx)), - ). Watches( source.NewKindWithCache(&v1alpha1.MeshService{}, mgr.GetCache()), handler.EnqueueRequestsFromMapFunc(r.transformMeshService(ctx)), @@ -573,9 +569,14 @@ func gatewayReferencesCertificate(certificateKey api.ResourceReference, gateway // by a TCPRoute or HTTPRoute that references the service. func (r *GatewayController) transformEndpoints(ctx context.Context) func(o client.Object) []reconcile.Request { return func(o client.Object) []reconcile.Request { - key := client.ObjectKeyFromObject(o).String() + key := client.ObjectKeyFromObject(o) + endpoints := o.(*corev1.Endpoints) + + if shouldIgnore(key.Namespace, r.denyK8sNamespacesSet, r.allowK8sNamespacesSet) || isLabeledIgnore(endpoints.Labels) { + return nil + } - return r.gatewaysForRoutesReferencing(ctx, TCPRoute_ServiceIndex, HTTPRoute_ServiceIndex, key) + return r.gatewaysForRoutesReferencing(ctx, TCPRoute_ServiceIndex, HTTPRoute_ServiceIndex, key.String()) } } diff --git a/control-plane/api-gateway/controllers/gateway_controller_test.go b/control-plane/api-gateway/controllers/gateway_controller_test.go index d1028eb945..7b21d01ff8 100644 --- a/control-plane/api-gateway/controllers/gateway_controller_test.go +++ b/control-plane/api-gateway/controllers/gateway_controller_test.go @@ -7,8 +7,11 @@ import ( "context" "testing" + mapset "github.com/deckarep/golang-set" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,6 +24,257 @@ import ( gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) +func TestTransformEndpoints(t *testing.T) { + t.Parallel() + + httpRoute := &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http", + Namespace: "test", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + Rules: []gwv1beta1.HTTPRouteRule{ + {BackendRefs: []gwv1beta1.HTTPBackendRef{ + {BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-test-namespace"}, + }}, + {BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-other-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, + }}, + {BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-system-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("system"))}, + }}, + {BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-public-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("public"))}, + }}, + {BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-local-path-storage-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("local-path-storage"))}, + }}}, + }, + }, + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "http-gateway"}, + {Name: "general-gateway"}, + }, + }, + }, + } + + tcpRoute := &gwv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp", + Namespace: "test", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + Rules: []gwv1alpha2.TCPRouteRule{ + {BackendRefs: []gwv1beta1.BackendRef{ + {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-test-namespace"}}, + {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-other-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}}, + {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-system-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("system"))}}, + {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-public-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("public"))}}, + {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-local-path-storage-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("local-path-storage"))}}, + }}, + }, + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + {Name: "tcp-gateway"}, + {Name: "general-gateway"}, + }, + }, + }, + } + + for name, tt := range map[string]struct { + endpoints *corev1.Endpoints + expected []reconcile.Request + allowedNamespaces []string + denyNamespaces []string + }{ + "ignore system namespace": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-system-namespace", + Namespace: metav1.NamespaceSystem, + }, + }, + allowedNamespaces: []string{"*"}, + }, + "ignore public namespace": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-public-namespace", + Namespace: metav1.NamespacePublic, + }, + }, + allowedNamespaces: []string{"*"}, + }, + "ignore local-path-storage namespace": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-local-path-storage-namespace", + Namespace: "local-path-storage", + }, + }, + allowedNamespaces: []string{"*"}, + }, + "explicit deny namespace": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-test-namespace", + Namespace: "test", + }, + }, + allowedNamespaces: []string{"*"}, + denyNamespaces: []string{"test"}, + }, + "ignore labels": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-test-namespace", + Namespace: "test", + Labels: map[string]string{ + constants.LabelServiceIgnore: "true", + }, + }, + }, + allowedNamespaces: []string{"test"}, + }, + "http same namespace wildcard allow": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-test-namespace", + Namespace: "test", + }, + }, + allowedNamespaces: []string{"*"}, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "http-gateway", Namespace: "test"}}, + {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, + }, + }, + "http same namespace explicit allow": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-test-namespace", + Namespace: "test", + }, + }, + allowedNamespaces: []string{"test"}, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "http-gateway", Namespace: "test"}}, + {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, + }, + }, + "http other namespace wildcard allow": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-other-namespace", + Namespace: "other", + }, + }, + allowedNamespaces: []string{"*"}, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "http-gateway", Namespace: "test"}}, + {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, + }, + }, + "http other namespace explicit allow": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "http-other-namespace", + Namespace: "other", + }, + }, + allowedNamespaces: []string{"other"}, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "http-gateway", Namespace: "test"}}, + {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, + }, + }, + "tcp same namespace wildcard allow": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-test-namespace", + Namespace: "test", + }, + }, + allowedNamespaces: []string{"*"}, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "tcp-gateway", Namespace: "test"}}, + {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, + }, + }, + "tcp same namespace explicit allow": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-test-namespace", + Namespace: "test", + }, + }, + allowedNamespaces: []string{"test"}, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "tcp-gateway", Namespace: "test"}}, + {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, + }, + }, + "tcp other namespace wildcard allow": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-other-namespace", + Namespace: "other", + }, + }, + allowedNamespaces: []string{"*"}, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "tcp-gateway", Namespace: "test"}}, + {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, + }, + }, + "tcp other namespace explicit allow": { + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-other-namespace", + Namespace: "other", + }, + }, + allowedNamespaces: []string{"other"}, + expected: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "tcp-gateway", Namespace: "test"}}, + {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, + }, + }, + } { + t.Run(name, func(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(s)) + require.NoError(t, gwv1alpha2.Install(s)) + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + denySet := mapset.NewSet() + for _, v := range tt.denyNamespaces { + denySet.Add(v) + } + allowSet := mapset.NewSet() + for _, v := range tt.allowedNamespaces { + allowSet.Add(v) + } + + fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(httpRoute, tcpRoute)).Build() + + controller := GatewayController{ + Client: fakeClient, + denyK8sNamespacesSet: denySet, + allowK8sNamespacesSet: allowSet, + } + + fn := controller.transformEndpoints(context.Background()) + require.ElementsMatch(t, tt.expected, fn(tt.endpoints)) + }) + } +} + func TestTransformHTTPRoute(t *testing.T) { t.Parallel() From ff021cc1cc9366ed4a9f6c5fd3d54bdcf789f895 Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 19:05:20 -0400 Subject: [PATCH 200/592] [COMPLIANCE] Add Copyright and License Headers (#2271) Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- control-plane/api-gateway/cache/gateway.go | 3 +++ control-plane/api/v1alpha1/controlplanerequestlimit_types.go | 3 +++ .../api/v1alpha1/controlplanerequestlimit_types_test.go | 3 +++ control-plane/config/crd/kustomization.yaml | 3 +++ control-plane/config/crd/kustomizeconfig.yaml | 3 +++ .../controllers/controlplanerequestlimit_controller.go | 3 +++ 6 files changed, 18 insertions(+) diff --git a/control-plane/api-gateway/cache/gateway.go b/control-plane/api-gateway/cache/gateway.go index d6d0c27ed2..846131d11e 100644 --- a/control-plane/api-gateway/cache/gateway.go +++ b/control-plane/api-gateway/cache/gateway.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cache import ( diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_types.go b/control-plane/api/v1alpha1/controlplanerequestlimit_types.go index 6b469696d1..ac2c05ded0 100644 --- a/control-plane/api/v1alpha1/controlplanerequestlimit_types.go +++ b/control-plane/api/v1alpha1/controlplanerequestlimit_types.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go b/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go index 5b8c4b4352..12633250ab 100644 --- a/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go +++ b/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package v1alpha1 import ( diff --git a/control-plane/config/crd/kustomization.yaml b/control-plane/config/crd/kustomization.yaml index 2b1d90d6d0..2c8358a48b 100644 --- a/control-plane/config/crd/kustomization.yaml +++ b/control-plane/config/crd/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # This kustomization.yaml is not intended to be run by itself, # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default diff --git a/control-plane/config/crd/kustomizeconfig.yaml b/control-plane/config/crd/kustomizeconfig.yaml index 6f83d9a94b..5d1332c4bf 100644 --- a/control-plane/config/crd/kustomizeconfig.yaml +++ b/control-plane/config/crd/kustomizeconfig.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + # This file is for teaching kustomize how to substitute name and namespace reference in CRD nameReference: - kind: Service diff --git a/control-plane/controllers/controlplanerequestlimit_controller.go b/control-plane/controllers/controlplanerequestlimit_controller.go index 0441f1ed14..f5afbdcc48 100644 --- a/control-plane/controllers/controlplanerequestlimit_controller.go +++ b/control-plane/controllers/controlplanerequestlimit_controller.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controllers import ( From fe2c481f38747d5bf06b5ac402c167a985b038a7 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 6 Jun 2023 10:08:00 -0400 Subject: [PATCH 201/592] Add additional helm hook for resource management (#2259) * Add additional helm hook for resource management * Move GatewayClassConfig CRD to templates * Add CRDs to templates * Add value to values.yaml * Remove GatewayClass and GatewayClassConfig bats * Fix CRD ExportedServices * Change -release to -release-name on gateway-resources subcommand * switch to pointer to avoid lock copy for linter * Move forcible test cleanup to before helm delete since it will now drop CRDs * adjust cleanup logic since it looks like the testing framework sometimes uninstalls the helm chart early * Fix cli unit test and drop CRD reading data since it's no longer embedded in the CLI * Add BATs for Gateway CRDs * Add BATs for Gateway Resources * Update Contributing --------- Co-authored-by: Thomas Eckert --- CONTRIBUTING.md | 8 +- Makefile | 2 +- acceptance/framework/consul/helm_cluster.go | 82 ++-- .../crd-controlplanerequestlimits.yaml | 5 +- .../templates/crd-exportedservices.yaml | 5 +- .../crd-gatewayclassconfigs.yaml} | 14 +- .../crd-gatewayclasses.yaml} | 11 +- .../crd-gateways.yaml} | 11 +- .../crd-grpcroutes.yaml} | 11 +- .../crd-httproutes.yaml} | 11 +- .../consul/templates/crd-ingressgateways.yaml | 5 +- charts/consul/templates/crd-jwtproviders.yaml | 5 +- charts/consul/templates/crd-meshes.yaml | 5 +- charts/consul/templates/crd-meshservices.yaml | 5 +- .../templates/crd-peeringacceptors.yaml | 5 +- .../consul/templates/crd-peeringdialers.yaml | 5 +- .../consul/templates/crd-proxydefaults.yaml | 5 +- .../crd-referencegrants.yaml} | 11 +- .../consul/templates/crd-samenessgroups.yaml | 5 +- .../consul/templates/crd-servicedefaults.yaml | 5 +- .../templates/crd-serviceintentions.yaml | 5 +- .../templates/crd-serviceresolvers.yaml | 5 +- .../consul/templates/crd-servicerouters.yaml | 5 +- .../templates/crd-servicesplitters.yaml | 5 +- .../crd-tcproutes.yaml} | 11 +- .../templates/crd-terminatinggateways.yaml | 5 +- .../crd-tlsroutes.yaml} | 11 +- .../crd-udproutes.yaml} | 11 +- .../gateway-cleanup-podsecuritypolicy.yaml | 4 +- .../templates/gateway-gatewayclass.yaml | 18 - .../templates/gateway-gatewayclassconfig.yaml | 74 ---- .../gateway-resources-clusterrole.yaml | 37 ++ .../gateway-resources-clusterrolebinding.yaml | 20 + .../templates/gateway-resources-job.yaml | 112 ++++++ .../gateway-resources-podsecuritypolicy.yaml | 32 ++ .../gateway-resources-serviceaccount.yaml | 13 + .../test/unit/crd-exportedservices.bats | 2 +- .../test/unit/crd-gatewayclassconfigs.bats | 20 + .../consul/test/unit/crd-gatewayclasses.bats | 28 ++ charts/consul/test/unit/crd-gateways.bats | 28 ++ charts/consul/test/unit/crd-grpcroutes.bats | 28 ++ charts/consul/test/unit/crd-httproutes.bats | 28 ++ charts/consul/test/unit/crd-meshservices.bats | 21 ++ charts/consul/test/unit/crd-tcproutes.bats | 28 ++ charts/consul/test/unit/crd-tlsroutes.bats | 28 ++ charts/consul/test/unit/crd-udproutes.bats | 28 ++ .../unit/gateway-cleanup-clusterrole.bats | 33 ++ .../gateway-cleanup-clusterrolebinding.bats | 23 ++ .../consul/test/unit/gateway-cleanup-job.bats | 23 ++ .../gateway-cleanup-podsecuritypolicy.bats | 41 ++ .../unit/gateway-cleanup-serviceaccount.bats | 23 ++ .../test/unit/gateway-gatewayclass.bats | 21 -- .../test/unit/gateway-gatewayclassconfig.bats | 117 ------ .../unit/gateway-resources-clusterrole.bats | 33 ++ .../gateway-resources-clusterrolebinding.bats | 23 ++ .../test/unit/gateway-resources-job.bats | 118 ++++++ .../gateway-resources-podsecuritypolicy.bats | 41 ++ .../gateway-resources-serviceaccount.bats | 23 ++ charts/consul/todo.txt | 3 + charts/consul/values.yaml | 6 + charts/demo/crds/blank | 4 - charts/embed_chart.go | 4 +- cli/helm/chart.go | 20 - cli/helm/chart_test.go | 1 - cli/helm/test_fixtures/consul/crds/foo.yaml | 4 - control-plane/commands.go | 5 + ...shicorp.com_controlplanerequestlimits.yaml | 3 +- ...consul.hashicorp.com_exportedservices.yaml | 3 +- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 3 +- .../consul.hashicorp.com_ingressgateways.yaml | 3 +- .../consul.hashicorp.com_jwtproviders.yaml | 3 +- .../bases/consul.hashicorp.com_meshes.yaml | 3 +- .../consul.hashicorp.com_meshservices.yaml | 3 +- ...consul.hashicorp.com_peeringacceptors.yaml | 3 +- .../consul.hashicorp.com_peeringdialers.yaml | 3 +- .../consul.hashicorp.com_proxydefaults.yaml | 3 +- .../consul.hashicorp.com_samenessgroups.yaml | 3 +- .../consul.hashicorp.com_servicedefaults.yaml | 3 +- ...onsul.hashicorp.com_serviceintentions.yaml | 3 +- ...consul.hashicorp.com_serviceresolvers.yaml | 3 +- .../consul.hashicorp.com_servicerouters.yaml | 3 +- ...consul.hashicorp.com_servicesplitters.yaml | 3 +- ...sul.hashicorp.com_terminatinggateways.yaml | 3 +- ...ewayclasses.gateway.networking.k8s.io.yaml | 0 .../gateways.gateway.networking.k8s.io.yaml | 0 .../grpcroutes.gateway.networking.k8s.io.yaml | 0 .../httproutes.gateway.networking.k8s.io.yaml | 0 .../config/crd/external}/kustomization.yaml | 0 ...rencegrants.gateway.networking.k8s.io.yaml | 0 .../tcproutes.gateway.networking.k8s.io.yaml | 0 .../tlsroutes.gateway.networking.k8s.io.yaml | 0 .../udproutes.gateway.networking.k8s.io.yaml | 0 control-plane/config/rbac/role.yaml | 1 + control-plane/config/webhook/manifests.yaml | 1 + control-plane/go.mod | 2 +- .../subcommand/gateway-cleanup/command.go | 2 +- .../subcommand/gateway-resources/command.go | 352 ++++++++++++++++++ .../gateway-resources/command_test.go | 253 +++++++++++++ hack/copy-crds-to-chart/main.go | 94 ++--- 99 files changed, 1702 insertions(+), 443 deletions(-) rename charts/consul/{crds/gatewayclassconfigs.consul.hashicorp.com.yaml => templates/crd-gatewayclassconfigs.yaml} (95%) rename charts/consul/{crds/gatewayclasses.gateway.networking.k8s.io.yaml.yml => templates/crd-gatewayclasses.yaml} (98%) rename charts/consul/{crds/gateways.gateway.networking.k8s.io.yaml.yml => templates/crd-gateways.yaml} (99%) rename charts/consul/{crds/grpcroutes.gateway.networking.k8s.io.yaml.yml => templates/crd-grpcroutes.yaml} (99%) rename charts/consul/{crds/httproutes.gateway.networking.k8s.io.yaml.yml => templates/crd-httproutes.yaml} (99%) rename charts/consul/{crds/referencegrants.gateway.networking.k8s.io.yaml.yml => templates/crd-referencegrants.yaml} (97%) rename charts/consul/{crds/tcproutes.gateway.networking.k8s.io.yaml.yml => templates/crd-tcproutes.yaml} (98%) rename charts/consul/{crds/tlsroutes.gateway.networking.k8s.io.yaml.yml => templates/crd-tlsroutes.yaml} (99%) rename charts/consul/{crds/udproutes.gateway.networking.k8s.io.yaml.yml => templates/crd-udproutes.yaml} (98%) delete mode 100644 charts/consul/templates/gateway-gatewayclass.yaml delete mode 100644 charts/consul/templates/gateway-gatewayclassconfig.yaml create mode 100644 charts/consul/templates/gateway-resources-clusterrole.yaml create mode 100644 charts/consul/templates/gateway-resources-clusterrolebinding.yaml create mode 100644 charts/consul/templates/gateway-resources-job.yaml create mode 100644 charts/consul/templates/gateway-resources-podsecuritypolicy.yaml create mode 100644 charts/consul/templates/gateway-resources-serviceaccount.yaml create mode 100644 charts/consul/test/unit/crd-gatewayclassconfigs.bats create mode 100644 charts/consul/test/unit/crd-gatewayclasses.bats create mode 100644 charts/consul/test/unit/crd-gateways.bats create mode 100644 charts/consul/test/unit/crd-grpcroutes.bats create mode 100644 charts/consul/test/unit/crd-httproutes.bats create mode 100644 charts/consul/test/unit/crd-meshservices.bats create mode 100644 charts/consul/test/unit/crd-tcproutes.bats create mode 100644 charts/consul/test/unit/crd-tlsroutes.bats create mode 100644 charts/consul/test/unit/crd-udproutes.bats create mode 100644 charts/consul/test/unit/gateway-cleanup-clusterrole.bats create mode 100644 charts/consul/test/unit/gateway-cleanup-clusterrolebinding.bats create mode 100644 charts/consul/test/unit/gateway-cleanup-job.bats create mode 100644 charts/consul/test/unit/gateway-cleanup-podsecuritypolicy.bats create mode 100644 charts/consul/test/unit/gateway-cleanup-serviceaccount.bats delete mode 100755 charts/consul/test/unit/gateway-gatewayclass.bats delete mode 100644 charts/consul/test/unit/gateway-gatewayclassconfig.bats create mode 100644 charts/consul/test/unit/gateway-resources-clusterrole.bats create mode 100644 charts/consul/test/unit/gateway-resources-clusterrolebinding.bats create mode 100644 charts/consul/test/unit/gateway-resources-job.bats create mode 100644 charts/consul/test/unit/gateway-resources-podsecuritypolicy.bats create mode 100644 charts/consul/test/unit/gateway-resources-serviceaccount.bats create mode 100644 charts/consul/todo.txt delete mode 100644 charts/demo/crds/blank delete mode 100644 cli/helm/test_fixtures/consul/crds/foo.yaml rename {charts/consul/crds => control-plane/config/crd/external}/gatewayclasses.gateway.networking.k8s.io.yaml (100%) rename {charts/consul/crds => control-plane/config/crd/external}/gateways.gateway.networking.k8s.io.yaml (100%) rename {charts/consul/crds => control-plane/config/crd/external}/grpcroutes.gateway.networking.k8s.io.yaml (100%) rename {charts/consul/crds => control-plane/config/crd/external}/httproutes.gateway.networking.k8s.io.yaml (100%) rename {charts/consul/crds => control-plane/config/crd/external}/kustomization.yaml (100%) rename {charts/consul/crds => control-plane/config/crd/external}/referencegrants.gateway.networking.k8s.io.yaml (100%) rename {charts/consul/crds => control-plane/config/crd/external}/tcproutes.gateway.networking.k8s.io.yaml (100%) rename {charts/consul/crds => control-plane/config/crd/external}/tlsroutes.gateway.networking.k8s.io.yaml (100%) rename {charts/consul/crds => control-plane/config/crd/external}/udproutes.gateway.networking.k8s.io.yaml (100%) create mode 100644 control-plane/subcommand/gateway-resources/command.go create mode 100644 control-plane/subcommand/gateway-resources/command_test.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63ae3b2323..f0deb97ce9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1224,17 +1224,17 @@ Networking. To pull external CRDs into our Helm chart and make sure they get installed, we generate their configuration using [Kustomize](https://kustomize.io/) which can pull in Kubernetes config from external sources. We split these -generated CRDs into individual files and store them in the -[Helm `/crds` directory](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/). +generated CRDs into individual files and store them in the `charts/consul/templates` directory. If you need to update the external CRDs we depend on, or add to them, you can do this by editing the -[crds/kustomization.yaml](/charts/consul/crds/kustomization.yaml) file. Once modified, running +[control-plane/config/crd/external/kustomization.yaml](/control-plane/config/crd/external/kustomization.yaml) file. +Once modified, running ```bash make generate-external-crds ``` -will update the CRDs in the `/crds` directory. +will update the CRDs in the `/templates` directory. ## Adding a Changelog Entry diff --git a/Makefile b/Makefile index 437357898a..5adfb55657 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ copy-crds-to-chart: ## Copy generated CRD YAML into charts/consul. Usage: make c @cd hack/copy-crds-to-chart; go run ./... generate-external-crds: ## Generate CRDs for externally defined CRDs and copy them to charts/consul. Usage: make generate-external-crds - @cd ./charts/consul/crds/; \ + @cd ./control-plane/config/crd/external; \ kustomize build | yq --split-exp '.metadata.name + ".yaml"' --no-doc bats-tests: ## Run Helm chart bats tests. diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 613b69da91..6c61a605f6 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -153,6 +153,39 @@ func (h *HelmCluster) Destroy(t *testing.T) { "--wait": nil, } + // Clean up any stuck gateway resources, note that we swallow all errors from + // here down since the terratest helm installation may actually already be + // deleted at this point, in which case these operations will fail on non-existent + // CRD cleanups. + requirement, err := labels.NewRequirement("release", selection.Equals, []string{h.releaseName}) + require.NoError(t, err) + + // Forcibly delete all gateway classes and remove their finalizers. + _ = h.runtimeClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}, client.HasLabels{"release=" + h.releaseName}) + + var gatewayClassList gwv1beta1.GatewayClassList + if h.runtimeClient.List(context.Background(), &gatewayClassList, &client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*requirement), + }) == nil { + for _, item := range gatewayClassList.Items { + item.SetFinalizers([]string{}) + _ = h.runtimeClient.Update(context.Background(), &item) + } + } + + // Forcibly delete all gateway class configs and remove their finalizers. + _ = h.runtimeClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}, client.HasLabels{"release=" + h.releaseName}) + + var gatewayClassConfigList v1alpha1.GatewayClassConfigList + if h.runtimeClient.List(context.Background(), &gatewayClassConfigList, &client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*requirement), + }) == nil { + for _, item := range gatewayClassConfigList.Items { + item.SetFinalizers([]string{}) + _ = h.runtimeClient.Update(context.Background(), &item) + } + } + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 30}, t, func(r *retry.R) { err := helm.DeleteE(t, h.helmOptions, h.releaseName, false) require.NoError(r, err) @@ -161,9 +194,6 @@ func (h *HelmCluster) Destroy(t *testing.T) { // Retry because sometimes certain resources (like PVC) take time to delete // in cloud providers. retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { - requirement, err := labels.NewRequirement("release", selection.Equals, []string{h.releaseName}) - require.NoError(r, err) - // Force delete any pods that have h.releaseName in their name because sometimes // graceful termination takes a long time and since this is an uninstall // we don't care that they're stopped gracefully. @@ -243,34 +273,6 @@ func (h *HelmCluster) Destroy(t *testing.T) { } } - // Forcibly delete all gateway classes and remove their finalizers. - err = h.runtimeClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}, client.HasLabels{"release=" + h.releaseName}) - require.NoError(r, err) - - var gatewayClassList gwv1beta1.GatewayClassList - err = h.runtimeClient.List(context.Background(), &gatewayClassList, &client.ListOptions{ - LabelSelector: labels.NewSelector().Add(*requirement), - }) - require.NoError(r, err) - for _, item := range gatewayClassList.Items { - item.SetFinalizers([]string{}) - require.NoError(r, h.runtimeClient.Update(context.Background(), &item)) - } - - // Forcibly delete all gateway class configs and remove their finalizers. - err = h.runtimeClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}, client.HasLabels{"release=" + h.releaseName}) - require.NoError(r, err) - - var gatewayClassConfigList v1alpha1.GatewayClassConfigList - err = h.runtimeClient.List(context.Background(), &gatewayClassConfigList, &client.ListOptions{ - LabelSelector: labels.NewSelector().Add(*requirement), - }) - require.NoError(r, err) - for _, item := range gatewayClassConfigList.Items { - item.SetFinalizers([]string{}) - require.NoError(r, h.runtimeClient.Update(context.Background(), &item)) - } - // Verify all Consul Pods are deleted. pods, err = h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) require.NoError(r, err) @@ -329,24 +331,6 @@ func (h *HelmCluster) Destroy(t *testing.T) { r.Errorf("Found job which should have been deleted: %s", job.Name) } } - - // Verify all Gateway Classes are deleted. - err = h.runtimeClient.List(context.Background(), &gatewayClassList, &client.ListOptions{ - LabelSelector: labels.NewSelector().Add(*requirement), - }) - require.NoError(r, err) - for _, gatewayClass := range gatewayClassList.Items { - r.Errorf("Found gateway class which should have been deleted: %s", gatewayClass.Name) - } - - // Verify all Gateway Class Configs are deleted. - err = h.runtimeClient.List(context.Background(), &gatewayClassConfigList, &client.ListOptions{ - LabelSelector: labels.NewSelector().Add(*requirement), - }) - require.NoError(r, err) - for _, gatewayClassConfig := range gatewayClassConfigList.Items { - r.Errorf("Found gateway class config which should have been deleted: %s", gatewayClassConfig.Name) - } }) } diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml index 67ff258eb8..bd1d6118b9 100644 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: controlplanerequestlimits.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ControlPlaneRequestLimit diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 8581ac4e88..7ffddf7537 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: exportedservices.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ExportedServices diff --git a/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml similarity index 95% rename from charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml rename to charts/consul/templates/crd-gatewayclassconfigs.yaml index 44eff52492..65d425edc4 100644 --- a/charts/consul/crds/gatewayclassconfigs.consul.hashicorp.com.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -1,13 +1,18 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if .Values.connectInject.enabled }} --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: gatewayclassconfigs.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd spec: group: consul.hashicorp.com names: @@ -137,3 +142,4 @@ spec: type: object served: true storage: true +{{- end }} diff --git a/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml.yml b/charts/consul/templates/crd-gatewayclasses.yaml similarity index 98% rename from charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml.yml rename to charts/consul/templates/crd-gatewayclasses.yaml index 044c7af939..93435b7fce 100644 --- a/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml.yml +++ b/charts/consul/templates/crd-gatewayclasses.yaml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9,6 +7,12 @@ metadata: gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental creationTimestamp: null + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd name: gatewayclasses.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -321,3 +325,4 @@ status: plural: "" conditions: [] storedVersions: [] +{{- end }} diff --git a/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml.yml b/charts/consul/templates/crd-gateways.yaml similarity index 99% rename from charts/consul/crds/gateways.gateway.networking.k8s.io.yaml.yml rename to charts/consul/templates/crd-gateways.yaml index b7a7c8a7d1..41df34942a 100644 --- a/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml.yml +++ b/charts/consul/templates/crd-gateways.yaml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9,6 +7,12 @@ metadata: gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental creationTimestamp: null + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd name: gateways.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -875,3 +879,4 @@ status: plural: "" conditions: [] storedVersions: [] +{{- end }} diff --git a/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/templates/crd-grpcroutes.yaml similarity index 99% rename from charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml.yml rename to charts/consul/templates/crd-grpcroutes.yaml index 8f3ab6d385..739ed2c659 100644 --- a/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml.yml +++ b/charts/consul/templates/crd-grpcroutes.yaml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9,6 +7,12 @@ metadata: gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental creationTimestamp: null + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd name: grpcroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -759,3 +763,4 @@ status: plural: "" conditions: [] storedVersions: [] +{{- end }} diff --git a/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/templates/crd-httproutes.yaml similarity index 99% rename from charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml.yml rename to charts/consul/templates/crd-httproutes.yaml index b455d788de..bba3672d16 100644 --- a/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml.yml +++ b/charts/consul/templates/crd-httproutes.yaml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9,6 +7,12 @@ metadata: gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental creationTimestamp: null + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd name: httproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -1907,3 +1911,4 @@ status: plural: "" conditions: [] storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index eff7ef61a9..ef33890461 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: ingressgateways.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: IngressGateway diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index fa87f37489..c7d20883e8 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: jwtproviders.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: JWTProvider diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index f2549b5111..cdc11b6ed9 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: meshes.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: Mesh diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml index aa808113a2..859c8683ee 100644 --- a/charts/consul/templates/crd-meshservices.yaml +++ b/charts/consul/templates/crd-meshservices.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: meshservices.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: MeshService diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index 40f7f1d4d6..3822f3bdfe 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: peeringacceptors.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: PeeringAcceptor diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index bfe4778d0c..405361c486 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: peeringdialers.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: PeeringDialer diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index a224effc12..30dd25f674 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: proxydefaults.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ProxyDefaults diff --git a/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml.yml b/charts/consul/templates/crd-referencegrants.yaml similarity index 97% rename from charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml.yml rename to charts/consul/templates/crd-referencegrants.yaml index cd39b9c12a..db9cf12027 100644 --- a/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml.yml +++ b/charts/consul/templates/crd-referencegrants.yaml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9,6 +7,12 @@ metadata: gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental creationTimestamp: null + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd name: referencegrants.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -201,3 +205,4 @@ status: plural: "" conditions: [] storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index 7cc3b71ae1..c1d1c85a8e 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: samenessgroups.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: SamenessGroup diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index e295732bfa..c926ece62a 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: servicedefaults.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceDefaults diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 5f849f65ba..335d2eff7a 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: serviceintentions.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceIntentions diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index ef380f77b5..ed95c15846 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: serviceresolvers.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceResolver diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index c5ba99466c..0157f646b4 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: servicerouters.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceRouter diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index abe3ac85cc..18fb10341e 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: servicesplitters.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceSplitter diff --git a/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/templates/crd-tcproutes.yaml similarity index 98% rename from charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml.yml rename to charts/consul/templates/crd-tcproutes.yaml index 906b442d31..b5bc7be13c 100644 --- a/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml.yml +++ b/charts/consul/templates/crd-tcproutes.yaml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9,6 +7,12 @@ metadata: gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental creationTimestamp: null + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd name: tcproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -274,3 +278,4 @@ status: plural: "" conditions: [] storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index cd58d1679c..955496aeee 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: terminatinggateways.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: TerminatingGateway diff --git a/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/templates/crd-tlsroutes.yaml similarity index 99% rename from charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml.yml rename to charts/consul/templates/crd-tlsroutes.yaml index 2e22b04ef0..1acd1b973a 100644 --- a/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml.yml +++ b/charts/consul/templates/crd-tlsroutes.yaml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9,6 +7,12 @@ metadata: gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental creationTimestamp: null + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd name: tlsroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -284,3 +288,4 @@ status: plural: "" conditions: [] storedVersions: [] +{{- end }} diff --git a/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml.yml b/charts/consul/templates/crd-udproutes.yaml similarity index 98% rename from charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml.yml rename to charts/consul/templates/crd-udproutes.yaml index 19b03dcd0b..0661b24c1a 100644 --- a/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml.yml +++ b/charts/consul/templates/crd-udproutes.yaml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -9,6 +7,12 @@ metadata: gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental creationTimestamp: null + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd name: udproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io @@ -274,3 +278,4 @@ status: plural: "" conditions: [] storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml index c4d4e8acca..ffbad130cc 100644 --- a/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml +++ b/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml @@ -1,5 +1,4 @@ -{{- if .Values.global.enablePodSecurityPolicies }} -{{- if .Values.connectInject.enabled }} +{{- if (and .Values.connectInject.enabled .Values.global.enablePodSecurityPolicies)}} apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: @@ -31,4 +30,3 @@ spec: rule: 'RunAsAny' readOnlyRootFilesystem: false {{- end }} -{{- end }} diff --git a/charts/consul/templates/gateway-gatewayclass.yaml b/charts/consul/templates/gateway-gatewayclass.yaml deleted file mode 100644 index a6856620d2..0000000000 --- a/charts/consul/templates/gateway-gatewayclass.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: GatewayClass -metadata: - name: consul-api-gateway - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: api-gateway-controller -spec: - controllerName: "hashicorp.com/consul-api-gateway-controller" - parametersRef: - group: consul.hashicorp.com - kind: GatewayClassConfig - name: consul-api-gateway -{{- end }} diff --git a/charts/consul/templates/gateway-gatewayclassconfig.yaml b/charts/consul/templates/gateway-gatewayclassconfig.yaml deleted file mode 100644 index b812ebd814..0000000000 --- a/charts/consul/templates/gateway-gatewayclassconfig.yaml +++ /dev/null @@ -1,74 +0,0 @@ -{{- if .Values.connectInject.enabled }} ---- -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayClassConfig -metadata: - name: consul-api-gateway - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: api-gateway -spec: - {{- if .Values.apiGateway.enabled }} # Overide values from the old stanza. To be removed in 1.17 (t-eckert 2023-05-19) - - {{- if .Values.apiGateway.managedGatewayClass.deployment }} - deployment: - {{- if .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} - defaultInstances: {{ .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} - {{- end}} - {{- if .Values.apiGateway.managedGatewayClass.deployment.maxInstances }} - maxInstances: {{ .Values.apiGateway.managedGatewayClass.deployment.maxInstances }} - {{- end}} - {{- if .Values.apiGateway.managedGatewayClass.deployment.minInstances }} - minInstances: {{ .Values.apiGateway.managedGatewayClass.deployment.minInstances }} - {{- end}} - {{- end}} - {{- if .Values.apiGateway.managedGatewayClass.nodeSelector }} - nodeSelector: - {{ tpl .Values.apiGateway.managedGatewayClass.nodeSelector . | indent 4 | trim }} - {{- end }} - {{- if .Values.apiGateway.managedGatewayClass.tolerations }} - tolerations: - {{ tpl .Values.apiGateway.managedGatewayClass.tolerations . | indent 4 | trim }} - {{- end }} - {{- if .Values.apiGateway.managedGatewayClass.copyAnnotations.service }} - copyAnnotations: - service: - {{ tpl .Values.apiGateway.managedGatewayClass.copyAnnotations.service.annotations . | nindent 6 | trim }} - {{- end }} - serviceType: {{ .Values.apiGateway.managedGatewayClass.serviceType }} - - {{- else }} - - {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment }} - deployment: - {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} - defaultInstances: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} - {{- end}} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} - maxInstances: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} - {{- end}} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} - minInstances: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} - {{- end}} - {{- end}} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector }} - nodeSelector: - {{ tpl .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector . | indent 4 | trim }} - {{- end }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} - tolerations: - {{ tpl .Values.connectInject.apiGateway.managedGatewayClass.tolerations . | indent 4 | trim }} - {{- end }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service }} - copyAnnotations: - service: - {{ tpl .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations . | nindent 6 | trim }} - {{- end }} - serviceType: {{ .Values.connectInject.apiGateway.managedGatewayClass.serviceType }} - - {{- end }} - -{{- end }} diff --git a/charts/consul/templates/gateway-resources-clusterrole.yaml b/charts/consul/templates/gateway-resources-clusterrole.yaml new file mode 100644 index 0000000000..c3bdfeb4a3 --- /dev/null +++ b/charts/consul/templates/gateway-resources-clusterrole.yaml @@ -0,0 +1,37 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "consul.fullname" . }}-gateway-resources + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-resources +rules: + - apiGroups: + - consul.hashicorp.com + resources: + - gatewayclassconfigs + verbs: + - get + - update + - create + - apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + verbs: + - get + - update + - create +{{- if .Values.global.enablePodSecurityPolicies }} + - apiGroups: ["policy"] + resources: ["podsecuritypolicies"] + resourceNames: + - {{ template "consul.fullname" . }}-gateway-resources + verbs: + - use +{{- end }} +{{- end }} diff --git a/charts/consul/templates/gateway-resources-clusterrolebinding.yaml b/charts/consul/templates/gateway-resources-clusterrolebinding.yaml new file mode 100644 index 0000000000..921df23239 --- /dev/null +++ b/charts/consul/templates/gateway-resources-clusterrolebinding.yaml @@ -0,0 +1,20 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "consul.fullname" . }}-gateway-resources + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-resources +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "consul.fullname" . }}-gateway-resources +subjects: + - kind: ServiceAccount + name: {{ template "consul.fullname" . }}-gateway-resources + namespace: {{ .Release.Namespace }} +{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml new file mode 100644 index 0000000000..f8f92f799d --- /dev/null +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -0,0 +1,112 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "consul.fullname" . }}-gateway-resources + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-resources + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": hook-succeeded +spec: + template: + metadata: + name: {{ template "consul.fullname" . }}-gateway-resources + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: gateway-resources + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} + annotations: + "consul.hashicorp.com/connect-inject": "false" + spec: + restartPolicy: Never + serviceAccountName: {{ template "consul.fullname" . }}-gateway-resources + containers: + - name: gateway-resources + image: {{ .Values.global.imageK8S }} + command: + - consul-k8s-control-plane + args: + - gateway-resources + - -gateway-class-name=consul-api-gateway + - -gateway-class-config-name=consul-api-gateway + - -controller-name=hashicorp.com/consul-api-gateway-controller + - -app={{template "consul.name" .}} + - -chart={{template "consul.chart" .}} + - -heritage={{ .Release.Service }} + - -release-name={{ .Release.Name }} + - -component=api-gateway + {{- if .Values.apiGateway.enabled }} # Overide values from the old stanza. To be removed in 1.17 (t-eckert 2023-05-19) + {{- if .Values.apiGateway.managedGatewayClass.deployment }} + {{- if .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} + - -deployment-default-instances={{ .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} + {{- end}} + {{- if .Values.apiGateway.managedGatewayClass.deployment.maxInstances }} + - -deployment-max-instances={{ .Values.apiGateway.managedGatewayClass.deployment.maxInstances }} + {{- end}} + {{- if .Values.apiGateway.managedGatewayClass.deployment.minInstances }} + - -deployment-min-instances={{ .Values.apiGateway.managedGatewayClass.deployment.minInstances }} + {{- end}} + {{- end}} + {{- if .Values.apiGateway.managedGatewayClass.nodeSelector }} + - -node-selector={{ .Values.apiGateway.managedGatewayClass.nodeSelector }} + {{- end }} + {{- if .Values.apiGateway.managedGatewayClass.tolerations }} + - -tolerations={{ .Values.apiGateway.managedGatewayClass.tolerations }} + {{- end }} + {{- if .Values.apiGateway.managedGatewayClass.copyAnnotations.service }} + - -service-annotations={{ .Values.apiGateway.managedGatewayClass.copyAnnotations.service.annotations }} + {{- end }} + - -service-type={{ .Values.apiGateway.managedGatewayClass.serviceType }} + {{- else }} # the new stanza + {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment }} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} + - -deployment-default-instances={{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} + {{- end}} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} + - -deployment-max-instances={{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} + {{- end}} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} + - -deployment-min-instances={{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} + {{- end}} + {{- end}} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector }} + - -node-selector={{ .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector }} + {{- end }} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} + - -tolerations={{ .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} + {{- end }} + {{- if .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service }} + - -service-annotations={{ .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations }} + {{- end }} + - -service-type={{ .Values.connectInject.apiGateway.managedGatewayClass.serviceType }} + {{- end}} + resources: + requests: + memory: "50Mi" + cpu: "50m" + limits: + memory: "50Mi" + cpu: "50m" + {{- if .Values.global.acls.tolerations }} + tolerations: + {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} + {{- end }} + {{- if .Values.global.acls.nodeSelector }} + nodeSelector: + {{ tpl .Values.global.acls.nodeSelector . | indent 8 | trim }} + {{- end }} +{{- end }} diff --git a/charts/consul/templates/gateway-resources-podsecuritypolicy.yaml b/charts/consul/templates/gateway-resources-podsecuritypolicy.yaml new file mode 100644 index 0000000000..da5299194c --- /dev/null +++ b/charts/consul/templates/gateway-resources-podsecuritypolicy.yaml @@ -0,0 +1,32 @@ +{{- if (and .Values.global.enablePodSecurityPolicies .Values.connectInject.enabled)}} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "consul.fullname" . }}-gateway-resources + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-resources +spec: + privileged: false + allowPrivilegeEscalation: false + # This is redundant with non-root + disallow privilege escalation, + # but we can provide it for defense in depth. + requiredDropCapabilities: + - ALL + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/consul/templates/gateway-resources-serviceaccount.yaml b/charts/consul/templates/gateway-resources-serviceaccount.yaml new file mode 100644 index 0000000000..4611dc38e1 --- /dev/null +++ b/charts/consul/templates/gateway-resources-serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "consul.fullname" . }}-gateway-resources + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-resources +{{- end }} diff --git a/charts/consul/test/unit/crd-exportedservices.bats b/charts/consul/test/unit/crd-exportedservices.bats index 1b8f4430b5..235fe6bd24 100644 --- a/charts/consul/test/unit/crd-exportedservices.bats +++ b/charts/consul/test/unit/crd-exportedservices.bats @@ -7,7 +7,7 @@ load _helpers local actual=$(helm template \ -s templates/crd-exportedservices.yaml \ . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) + yq -s 'length > 0' | tee /dev/stderr) [ "${actual}" = "true" ] } diff --git a/charts/consul/test/unit/crd-gatewayclassconfigs.bats b/charts/consul/test/unit/crd-gatewayclassconfigs.bats new file mode 100644 index 0000000000..0228110b6b --- /dev/null +++ b/charts/consul/test/unit/crd-gatewayclassconfigs.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats + +load _helpers + +@test "gatewayclassconfigs/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-gatewayclassconfigs.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewayclassconfigs/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-gatewayclassconfigs.yaml \ + --set 'connectInject.enabled=false' \ + . +} diff --git a/charts/consul/test/unit/crd-gatewayclasses.bats b/charts/consul/test/unit/crd-gatewayclasses.bats new file mode 100644 index 0000000000..8400590606 --- /dev/null +++ b/charts/consul/test/unit/crd-gatewayclasses.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats + +load _helpers + +@test "gatewayclasses/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-gatewayclasses.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewayclasses/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-gatewayclasses.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gatewayclasses/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-gatewayclasses.yaml \ + --set 'connectInject.apiGateway.manageExternalCRDs=false' \ + . +} diff --git a/charts/consul/test/unit/crd-gateways.bats b/charts/consul/test/unit/crd-gateways.bats new file mode 100644 index 0000000000..8a7f0284e2 --- /dev/null +++ b/charts/consul/test/unit/crd-gateways.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats + +load _helpers + +@test "gateways/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-gateways.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gateways/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-gateways.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gateways/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-gateways.yaml \ + --set 'connectInject.apiGateway.manageExternalCRDs=false' \ + . +} diff --git a/charts/consul/test/unit/crd-grpcroutes.bats b/charts/consul/test/unit/crd-grpcroutes.bats new file mode 100644 index 0000000000..d5e3e298d7 --- /dev/null +++ b/charts/consul/test/unit/crd-grpcroutes.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats + +load _helpers + +@test "grpcroutes/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-grpcroutes.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "grpcroutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-grpcroutes.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +@test "grpcroutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-grpcroutes.yaml \ + --set 'connectInject.apiGateway.manageExternalCRDs=false' \ + . +} diff --git a/charts/consul/test/unit/crd-httproutes.bats b/charts/consul/test/unit/crd-httproutes.bats new file mode 100644 index 0000000000..99c58e0492 --- /dev/null +++ b/charts/consul/test/unit/crd-httproutes.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats + +load _helpers + +@test "httproutes/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-httproutes.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "httproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-httproutes.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +@test "httproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-httproutes.yaml \ + --set 'connectInject.apiGateway.manageExternalCRDs=false' \ + . +} diff --git a/charts/consul/test/unit/crd-meshservices.bats b/charts/consul/test/unit/crd-meshservices.bats new file mode 100644 index 0000000000..c1ee806ad4 --- /dev/null +++ b/charts/consul/test/unit/crd-meshservices.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load _helpers + +@test "meshservices/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-meshservices.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "meshservices/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-meshservices.yaml \ + --set 'connectInject.enabled=false' \ + . +} + diff --git a/charts/consul/test/unit/crd-tcproutes.bats b/charts/consul/test/unit/crd-tcproutes.bats new file mode 100644 index 0000000000..6916efdc6f --- /dev/null +++ b/charts/consul/test/unit/crd-tcproutes.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats + +load _helpers + +@test "tcproutes/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-tcproutes.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "tcproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-tcproutes.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +@test "tcproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-tcproutes.yaml \ + --set 'connectInject.apiGateway.manageExternalCRDs=false' \ + . +} diff --git a/charts/consul/test/unit/crd-tlsroutes.bats b/charts/consul/test/unit/crd-tlsroutes.bats new file mode 100644 index 0000000000..7e1d5c471f --- /dev/null +++ b/charts/consul/test/unit/crd-tlsroutes.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats + +load _helpers + +@test "tlsroutes/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-tlsroutes.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "tlsroutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-tlsroutes.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +@test "tlsroutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-tlsroutes.yaml \ + --set 'connectInject.apiGateway.manageExternalCRDs=false' \ + . +} diff --git a/charts/consul/test/unit/crd-udproutes.bats b/charts/consul/test/unit/crd-udproutes.bats new file mode 100644 index 0000000000..407592a770 --- /dev/null +++ b/charts/consul/test/unit/crd-udproutes.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats + +load _helpers + +@test "udproutes/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-udproutes.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "udproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-udproutes.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +@test "udproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-udproutes.yaml \ + --set 'connectInject.apiGateway.manageExternalCRDs=false' \ + . +} diff --git a/charts/consul/test/unit/gateway-cleanup-clusterrole.bats b/charts/consul/test/unit/gateway-cleanup-clusterrole.bats new file mode 100644 index 0000000000..c672ac5593 --- /dev/null +++ b/charts/consul/test/unit/gateway-cleanup-clusterrole.bats @@ -0,0 +1,33 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-cleanup-clusterrole.yaml + +@test "gatewaycleanup/ClusterRole: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewaycleanup/ClusterRole: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gatewaycleanup/ClusterRole: can use podsecuritypolicies with global.enablePodSecurityPolicy=true" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set "global.enablePodSecurityPolicies=true" \ + . | tee /dev/stderr | + yq '.rules[] | select((.resources[0] == "podsecuritypolicies") and (.verbs[0] == "use")) | length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + diff --git a/charts/consul/test/unit/gateway-cleanup-clusterrolebinding.bats b/charts/consul/test/unit/gateway-cleanup-clusterrolebinding.bats new file mode 100644 index 0000000000..a6e4af5d2c --- /dev/null +++ b/charts/consul/test/unit/gateway-cleanup-clusterrolebinding.bats @@ -0,0 +1,23 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-cleanup-clusterrolebinding.yaml + +@test "gatewaycleanup/ClusterRoleBinding: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewaycleanup/ClusterRoleBinding: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + diff --git a/charts/consul/test/unit/gateway-cleanup-job.bats b/charts/consul/test/unit/gateway-cleanup-job.bats new file mode 100644 index 0000000000..ff59768c75 --- /dev/null +++ b/charts/consul/test/unit/gateway-cleanup-job.bats @@ -0,0 +1,23 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-cleanup-job.yaml + +@test "gatewaycleanup/Job: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewaycleanup/Job: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + diff --git a/charts/consul/test/unit/gateway-cleanup-podsecuritypolicy.bats b/charts/consul/test/unit/gateway-cleanup-podsecuritypolicy.bats new file mode 100644 index 0000000000..66974da2fd --- /dev/null +++ b/charts/consul/test/unit/gateway-cleanup-podsecuritypolicy.bats @@ -0,0 +1,41 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-cleanup-podsecuritypolicy.yaml + +@test "gatewaycleanup/PodSecurityPolicy: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gatewaycleanup/PodSecurityPolicy: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gatewaycleanup/PodSecurityPolicy: disabled with global.enablePodSecurityPolicies=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'global.enablePodSecurityPolicies=false' \ + . +} + + +@test "gatewaycleanup/PodSecurityPolicy: enabled with connectInject.enabled=true and global.enablePodSecurityPolicies=true" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set 'connectInject.enabled=true' \ + --set 'global.enablePodSecurityPolicies=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} diff --git a/charts/consul/test/unit/gateway-cleanup-serviceaccount.bats b/charts/consul/test/unit/gateway-cleanup-serviceaccount.bats new file mode 100644 index 0000000000..50d01b99e9 --- /dev/null +++ b/charts/consul/test/unit/gateway-cleanup-serviceaccount.bats @@ -0,0 +1,23 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-cleanup-serviceaccount.yaml + +@test "gatewaycleanup/ServiceAccount: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewaycleanup/ServiceAccount: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + diff --git a/charts/consul/test/unit/gateway-gatewayclass.bats b/charts/consul/test/unit/gateway-gatewayclass.bats deleted file mode 100755 index ac8a53aed9..0000000000 --- a/charts/consul/test/unit/gateway-gatewayclass.bats +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "apiGateway/GatewayClass: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/gateway-gatewayclass.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "apiGateway/GatewayClass: disabled with connectInject.enabled false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/gateway-gatewayclass.yaml \ - --set 'connectInject.enabled=false' \ - . -} - diff --git a/charts/consul/test/unit/gateway-gatewayclassconfig.bats b/charts/consul/test/unit/gateway-gatewayclassconfig.bats deleted file mode 100644 index 30aa972cbe..0000000000 --- a/charts/consul/test/unit/gateway-gatewayclassconfig.bats +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "apiGateway/GatewayClassConfig: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/gateway-gatewayclassconfig.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "apiGateway/GatewayClassConfig: disabled with connectInject.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/gateway-gatewayclassconfig.yaml \ - --set 'connectInject.enabled=false' \ - . -} - -#-------------------------------------------------------------------- -# fallback configuration -# to be removed in 1.17 (t-eckert 2023-05-23) - -@test "apiGateway/GatewayClassConfig: fallback configuration is used when apiGateway.enabled is true" { - cd `chart_dir` - local spec=$(helm template \ - -s templates/gateway-gatewayclassconfig.yaml \ - --set 'apiGateway.enabled=true' \ - --set 'apiGateway.image=testing' \ - --set 'apiGateway.managedGatewayClass.nodeSelector=foo: bar' \ - --set 'apiGateway.managedGatewayClass.tolerations=- key: bar' \ - --set 'apiGateway.managedGatewayClass.copyAnnotations.service.annotations=- bingo' \ - --set 'apiGateway.managedGatewayClass.serviceType=LoadBalancer' \ - . | tee /dev/stderr | - yq '.spec' | tee /dev/stderr) - - local actual=$(echo "$spec" | - jq -r '.nodeSelector.foo') - [ "${actual}" = "bar" ] - - local actual=$(echo "$spec" | - jq -r '.tolerations[0].key') - [ "${actual}" = "bar" ] - - local actual=$(echo "$spec" | - jq -r '.copyAnnotations.service[0]') - [ "${actual}" = "bingo" ] - - local actual=$(echo "$spec" | - jq -r '.serviceType') - [ "${actual}" = "LoadBalancer" ] -} - -#-------------------------------------------------------------------- -# configuration - -@test "apiGateway/GatewayClassConfig: default configuration" { - cd `chart_dir` - local spec=$(helm template \ - -s templates/gateway-gatewayclassconfig.yaml \ - . | tee /dev/stderr | - yq '.spec' | tee /dev/stderr) - - local actual=$(echo "$spec" | - jq -r '.deployment.defaultInstances') - [ "${actual}" = 1 ] - - local actual=$(echo "$spec" | - jq -r '.deployment.maxInstances') - [ "${actual}" = 1 ] - - local actual=$(echo "$spec" | - jq -r '.deployment.minInstances') - [ "${actual}" = 1 ] -} - -@test "apigateway/gatewayclassconfig: custom configuration" { - cd `chart_dir` - local spec=$(helm template \ - -s templates/gateway-gatewayclassconfig.yaml \ - --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=LoadBalancer' \ - . | tee /dev/stderr | - yq '.spec' | tee /dev/stderr) - - local actual=$(echo "$spec" | - jq -r '.deployment.defaultInstances') - [ "${actual}" = "1" ] - - local actual=$(echo "$spec" | - jq -r '.deployment.maxInstances') - [ "${actual}" = "1" ] - - local actual=$(echo "$spec" | - jq -r '.deployment.minInstances') - [ "${actual}" = "1" ] - - local actual=$(echo "$spec" | - jq -r '.nodeSelector.foo') - [ "${actual}" = "bar" ] - - local actual=$(echo "$spec" | - jq -r '.tolerations[0].key') - [ "${actual}" = "bar" ] - - local actual=$(echo "$spec" | - jq -r '.copyAnnotations.service[0]') - [ "${actual}" = "bingo" ] - - local actual=$(echo "$spec" | - jq -r '.serviceType') - [ "${actual}" = "LoadBalancer" ] -} diff --git a/charts/consul/test/unit/gateway-resources-clusterrole.bats b/charts/consul/test/unit/gateway-resources-clusterrole.bats new file mode 100644 index 0000000000..152209a1b5 --- /dev/null +++ b/charts/consul/test/unit/gateway-resources-clusterrole.bats @@ -0,0 +1,33 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-resources-clusterrole.yaml + +@test "gatewayresources/ClusterRole: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewayresources/ClusterRole: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gatewayresources/ClusterRole: can use podsecuritypolicies with global.enablePodSecurityPolicy=true" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set "global.enablePodSecurityPolicies=true" \ + . | tee /dev/stderr | + yq '.rules[] | select((.resources[0] == "podsecuritypolicies") and (.verbs[0] == "use")) | length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + diff --git a/charts/consul/test/unit/gateway-resources-clusterrolebinding.bats b/charts/consul/test/unit/gateway-resources-clusterrolebinding.bats new file mode 100644 index 0000000000..efc1429e20 --- /dev/null +++ b/charts/consul/test/unit/gateway-resources-clusterrolebinding.bats @@ -0,0 +1,23 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-resources-clusterrolebinding.yaml + +@test "gatewayresources/ClusterRoleBinding: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewayresources/ClusterRoleBinding: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats new file mode 100644 index 0000000000..0d3bfa2e4d --- /dev/null +++ b/charts/consul/test/unit/gateway-resources-job.bats @@ -0,0 +1,118 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-resources-job.yaml + +@test "gatewayresources/Job: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewayresources/Job: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gatewayresources/Job: imageK8S set properly" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set 'global.imageK8S=foo' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].image == "foo"' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +#-------------------------------------------------------------------- +# fallback configuration +# to be removed in 1.17 (t-eckert 2023-05-23) + +@test "gatewayresources/Job: fallback configuration is used when apiGateway.enabled is true" { + cd `chart_dir` + local spec=$(helm template \ + -s $target \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=testing' \ + --set 'apiGateway.managedGatewayClass.nodeSelector=foo: bar' \ + --set 'apiGateway.managedGatewayClass.tolerations=- key: bar' \ + --set 'apiGateway.managedGatewayClass.copyAnnotations.service.annotations=- bingo' \ + --set 'apiGateway.managedGatewayClass.serviceType=LoadBalancer' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$spec" | jq '.[9] | ."-node-selector=foo"') + [ "${actual}" = "\"bar\"" ] + + local actual=$(echo "$spec" | jq '.[10] | ."-tolerations=- key"') + [ "${actual}" = "\"bar\"" ] + + local actual=$(echo "$spec" | jq '.[11]') + [ "${actual}" = "\"-service-annotations=- bingo\"" ] +} + +#-------------------------------------------------------------------- +# configuration + +@test "gatewayresources/Job: default configuration" { + cd `chart_dir` + local spec=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$spec" | jq 'any(index("-deployment-default-instances=1"))') + [ "${actual}" = "true" ] + + local actual=$(echo "$spec" | jq 'any(index("-deployment-max-instances=1"))') + [ "${actual}" = "true" ] + + local actual=$(echo "$spec" | jq 'any(index("-deployment-min-instances=1"))') + [ "${actual}" = "true" ] + + local actual=$(echo "$spec" | jq 'any(index("-service-type=LoadBalancer"))') + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: custom configuration" { + cd `chart_dir` + local spec=$(helm template \ + -s $target \ + --set 'connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances=2' \ + --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' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$spec" | jq 'any(index("-deployment-default-instances=2"))') + [ "${actual}" = "true" ] + + local actual=$(echo "$spec" | jq 'any(index("-deployment-max-instances=3"))') + [ "${actual}" = "true" ] + + local actual=$(echo "$spec" | jq 'any(index("-deployment-min-instances=1"))') + [ "${actual}" = "true" ] + + local actual=$(echo "$spec" | jq 'any(index("-service-type=Foo"))') + [ "${actual}" = "true" ] + + local actual=$(echo "$spec" | jq '.[12] | ."-node-selector=foo"') + [ "${actual}" = "\"bar\"" ] + + local actual=$(echo "$spec" | jq '.[13] | ."-tolerations=- key"') + [ "${actual}" = "\"bar\"" ] + + local actual=$(echo "$spec" | jq '.[14]') + [ "${actual}" = "\"-service-annotations=- bingo\"" ] +} diff --git a/charts/consul/test/unit/gateway-resources-podsecuritypolicy.bats b/charts/consul/test/unit/gateway-resources-podsecuritypolicy.bats new file mode 100644 index 0000000000..81818c525a --- /dev/null +++ b/charts/consul/test/unit/gateway-resources-podsecuritypolicy.bats @@ -0,0 +1,41 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-resources-podsecuritypolicy.yaml + +@test "gatewayresources/PodSecurityPolicy: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gatewayresources/PodSecurityPolicy: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gatewayresources/PodSecurityPolicy: disabled with global.enablePodSecurityPolicies=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'global.enablePodSecurityPolicies=false' \ + . +} + + +@test "gatewayresources/PodSecurityPolicy: enabled with connectInject.enabled=true and global.enablePodSecurityPolicies=true" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set 'connectInject.enabled=true' \ + --set 'global.enablePodSecurityPolicies=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} diff --git a/charts/consul/test/unit/gateway-resources-serviceaccount.bats b/charts/consul/test/unit/gateway-resources-serviceaccount.bats new file mode 100644 index 0000000000..90011e226b --- /dev/null +++ b/charts/consul/test/unit/gateway-resources-serviceaccount.bats @@ -0,0 +1,23 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/gateway-resources-serviceaccount.yaml + +@test "gatewayresources/ServiceAccount: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewayresources/ServiceAccount: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s $target \ + --set 'connectInject.enabled=false' \ + . +} + diff --git a/charts/consul/todo.txt b/charts/consul/todo.txt new file mode 100644 index 0000000000..c79bef389b --- /dev/null +++ b/charts/consul/todo.txt @@ -0,0 +1,3 @@ + +- [x] Remove gatewayclass gatewayclassconfig bats +- [ ] Add test for each of the CRDs diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 9f004cab30..c6c225b3d0 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2006,6 +2006,12 @@ connectInject: # Configuration settings for the Consul API Gateway integration. apiGateway: + # Enables Consul on Kubernetes to manage the CRDs used for Gateway API. + # Setting this to true will install the CRDs used for the Gateway API when Consul on Kubernetes is installed. + # These CRDs can clash with existing Gateway API CRDs if they are already installed in your cluster. + # If this setting is false, you will need to install the Gateway API CRDs manually. + manageExternalCRDs: true + # Configuration settings for the GatewayClass installed by Consul on Kubernetes. managedGatewayClass: # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) diff --git a/charts/demo/crds/blank b/charts/demo/crds/blank deleted file mode 100644 index a6560dc716..0000000000 --- a/charts/demo/crds/blank +++ /dev/null @@ -1,4 +0,0 @@ -This is here purely so we can embed it so the HelmDeployment that references does not fail. Otherwise, at the time of writing, we get: - -==> Installing Consul demo application - ! open demo/crds: file does not exist \ No newline at end of file diff --git a/charts/embed_chart.go b/charts/embed_chart.go index 72650e8819..8e36abba23 100644 --- a/charts/embed_chart.go +++ b/charts/embed_chart.go @@ -16,8 +16,8 @@ import "embed" // The embed directive does not include files with underscores unless explicitly listed, which is why _helpers.tpl is // explicitly embedded. -//go:embed consul/Chart.yaml consul/values.yaml consul/crds consul/templates consul/templates/_helpers.tpl +//go:embed consul/Chart.yaml consul/values.yaml consul/templates consul/templates/_helpers.tpl var ConsulHelmChart embed.FS -//go:embed demo/Chart.yaml demo/values.yaml demo/crds/blank demo/templates +//go:embed demo/Chart.yaml demo/values.yaml demo/templates var DemoHelmChart embed.FS diff --git a/cli/helm/chart.go b/cli/helm/chart.go index 3bb9484487..c57d9220bc 100644 --- a/cli/helm/chart.go +++ b/cli/helm/chart.go @@ -18,7 +18,6 @@ const ( chartFileName = "Chart.yaml" valuesFileName = "values.yaml" templatesDirName = "templates" - crdDirName = "crds" ) // LoadChart will attempt to load a Helm chart from the embedded file system. @@ -94,25 +93,6 @@ func readChartFiles(chart embed.FS, chartDirName string) ([]*loader.BufferedFile chartFiles = append(chartFiles, file) } - // Load everything under crds/. - dirs, err = chart.ReadDir(path.Join(chartDirName, crdDirName)) - if err != nil { - return nil, err - } - - for _, f := range dirs { - if f.IsDir() || f.Name() == "kustomization.yaml" { - // We only need to include files in the crds directory. - continue - } - - file, err := readFile(chart, path.Join(chartDirName, crdDirName, f.Name()), chartDirName) - if err != nil { - return nil, err - } - chartFiles = append(chartFiles, file) - } - return chartFiles, nil } diff --git a/cli/helm/chart_test.go b/cli/helm/chart_test.go index 7fe6cf50a2..f0e33e092b 100644 --- a/cli/helm/chart_test.go +++ b/cli/helm/chart_test.go @@ -42,7 +42,6 @@ func TestReadChartFiles(t *testing.T) { "values.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\n# This is a mock Helm values.yaml file used for testing.\nkey: value", "templates/_helpers.tpl": "helpers", "templates/foo.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nfoo: bar\n", - "crds/foo.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nfoo: bar\n", } files, err := readChartFiles(testChartFiles, directory) diff --git a/cli/helm/test_fixtures/consul/crds/foo.yaml b/cli/helm/test_fixtures/consul/crds/foo.yaml deleted file mode 100644 index b17972509f..0000000000 --- a/cli/helm/test_fixtures/consul/crds/foo.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -foo: bar diff --git a/control-plane/commands.go b/control-plane/commands.go index 4e76abf00d..e2bcb0f693 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -13,6 +13,7 @@ import ( cmdDeleteCompletedJob "github.com/hashicorp/consul-k8s/control-plane/subcommand/delete-completed-job" cmdFetchServerRegion "github.com/hashicorp/consul-k8s/control-plane/subcommand/fetch-server-region" cmdGatewayCleanup "github.com/hashicorp/consul-k8s/control-plane/subcommand/gateway-cleanup" + cmdGatewayResources "github.com/hashicorp/consul-k8s/control-plane/subcommand/gateway-resources" cmdGetConsulClientCA "github.com/hashicorp/consul-k8s/control-plane/subcommand/get-consul-client-ca" cmdGossipEncryptionAutogenerate "github.com/hashicorp/consul-k8s/control-plane/subcommand/gossip-encryption-autogenerate" cmdInjectConnect "github.com/hashicorp/consul-k8s/control-plane/subcommand/inject-connect" @@ -54,6 +55,10 @@ func init() { return &cmdGatewayCleanup.Command{UI: ui}, nil }, + "gateway-resources": func() (cli.Command, error) { + return &cmdGatewayResources.Command{UI: ui}, nil + }, + "server-acl-init": func() (cli.Command, error) { return &cmdServerACLInit.Command{UI: ui}, nil }, diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml index 4d1d808428..2eef465ada 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index dac72f3646..f066c90612 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml index 44eff52492..a8393cd8fd 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index e9cf081721..f7ccf205d9 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml index 7506cc57dc..8ca1ec0748 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com 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 16dd398f99..bc46b6ab37 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml index 125883bdc5..0871fc32e5 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index 894228a218..f6f9eda72b 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index 51c3e38319..7e0927c169 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 1be3b37703..7396816f7e 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml index 259ca7b910..23de092485 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 83503f11f3..a5501a98d2 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com 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 9553c73450..cd28173ba8 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index a8a8107439..3cd3b37324 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 04590cc007..5919e23005 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index 3a47472ba7..d5848ed6ec 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index acf61cde4c..4910e42829 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml similarity index 100% rename from charts/consul/crds/gatewayclasses.gateway.networking.k8s.io.yaml rename to control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml diff --git a/charts/consul/crds/gateways.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml similarity index 100% rename from charts/consul/crds/gateways.gateway.networking.k8s.io.yaml rename to control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml diff --git a/charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml similarity index 100% rename from charts/consul/crds/grpcroutes.gateway.networking.k8s.io.yaml rename to control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml diff --git a/charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml similarity index 100% rename from charts/consul/crds/httproutes.gateway.networking.k8s.io.yaml rename to control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml diff --git a/charts/consul/crds/kustomization.yaml b/control-plane/config/crd/external/kustomization.yaml similarity index 100% rename from charts/consul/crds/kustomization.yaml rename to control-plane/config/crd/external/kustomization.yaml diff --git a/charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml similarity index 100% rename from charts/consul/crds/referencegrants.gateway.networking.k8s.io.yaml rename to control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml diff --git a/charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml similarity index 100% rename from charts/consul/crds/tcproutes.gateway.networking.k8s.io.yaml rename to control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml diff --git a/charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml similarity index 100% rename from charts/consul/crds/tlsroutes.gateway.networking.k8s.io.yaml rename to control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml diff --git a/charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml similarity index 100% rename from charts/consul/crds/udproutes.gateway.networking.k8s.io.yaml rename to control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 4fac4ac9b8..7f90780e02 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -5,6 +5,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: + creationTimestamp: null name: manager-role rules: - apiGroups: diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index 6d748ed06c..0861f9253a 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -5,6 +5,7 @@ apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: + creationTimestamp: null name: mutating-webhook-configuration webhooks: - admissionReviewVersions: diff --git a/control-plane/go.mod b/control-plane/go.mod index 96f1d91fed..c916acb745 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -32,6 +32,7 @@ require ( golang.org/x/text v0.9.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 + gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 @@ -158,7 +159,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect k8s.io/component-base v0.26.1 // indirect diff --git a/control-plane/subcommand/gateway-cleanup/command.go b/control-plane/subcommand/gateway-cleanup/command.go index cecb225290..04aa8f60a9 100644 --- a/control-plane/subcommand/gateway-cleanup/command.go +++ b/control-plane/subcommand/gateway-cleanup/command.go @@ -173,7 +173,7 @@ func (c *Command) Help() string { const synopsis = "Clean up global gateway resources prior to uninstall." const help = ` -Usage: consul-k8s-control-plane gateay-cleanup [options] +Usage: consul-k8s-control-plane gateway-cleanup [options] Deletes installed gateway class and gateway class config objects prior to helm uninstallation. This is required due to finalizers diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go new file mode 100644 index 0000000000..2da2abccb1 --- /dev/null +++ b/control-plane/subcommand/gateway-resources/command.go @@ -0,0 +1,352 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatewayresources + +import ( + "context" + "errors" + "flag" + "fmt" + "sync" + "time" + + "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/mitchellh/cli" + yaml "gopkg.in/yaml.v2" + 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/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// this dupes the Kubernetes tolerations +// struct with yaml tags for validation. +type toleration struct { + Key string `yaml:"key"` + Operator string `yaml:"operator"` + Value string `yaml:"value"` + Effect string `yaml:"effect"` + TolerationSeconds *int64 `yaml:"tolerationSeconds"` +} + +func tolerationToKubernetes(t toleration) corev1.Toleration { + return corev1.Toleration{ + Key: t.Key, + Operator: corev1.TolerationOperator(t.Operator), + Value: t.Value, + Effect: corev1.TaintEffect(t.Effect), + TolerationSeconds: t.TolerationSeconds, + } +} + +type Command struct { + UI cli.Ui + + flags *flag.FlagSet + k8s *flags.K8SFlags + + flagHeritage string + flagChart string + flagApp string + flagRelease string + flagComponent string + flagControllerName string + flagGatewayClassName string + flagGatewayClassConfigName string + + flagServiceType string + flagDeploymentDefaultInstances int + flagDeploymentMaxInstances int + flagDeploymentMinInstances int + + flagNodeSelector string // this is a yaml multiline string map + flagTolerations string // this is a multiline yaml string matching the tolerations array + flagServiceAnnotations string // this is a multiline yaml string array of annotations to allow + + k8sClient client.Client + + once sync.Once + help string + + nodeSelector map[string]string + tolerations []corev1.Toleration + serviceAnnotations []string + + ctx context.Context +} + +func (c *Command) init() { + c.flags = flag.NewFlagSet("", flag.ContinueOnError) + + c.flags.StringVar(&c.flagGatewayClassName, "gateway-class-name", "", + "Name of Kubernetes GatewayClass to ensure is created.") + c.flags.StringVar(&c.flagGatewayClassConfigName, "gateway-class-config-name", "", + "Name of Kubernetes GatewayClassConfig to ensure is created.") + c.flags.StringVar(&c.flagHeritage, "heritage", "", + "Helm chart heritage for created objects.") + c.flags.StringVar(&c.flagChart, "chart", "", + "Helm chart name for created objects.") + c.flags.StringVar(&c.flagApp, "app", "", + "Helm chart app for created objects.") + c.flags.StringVar(&c.flagRelease, "release-name", "", + "Helm chart release for created objects.") + c.flags.StringVar(&c.flagComponent, "component", "", + "Helm chart component for created objects.") + c.flags.StringVar(&c.flagControllerName, "controller-name", "", + "The controller name value to use in the GatewayClass.") + c.flags.StringVar(&c.flagServiceType, "service-type", "", + "The service type to use for a gateway deployment.", + ) + c.flags.IntVar(&c.flagDeploymentDefaultInstances, "deployment-default-instances", 0, + "The number of instances to deploy for each gateway by default.", + ) + c.flags.IntVar(&c.flagDeploymentMaxInstances, "deployment-max-instances", 0, + "The maximum number of instances to deploy for each gateway.", + ) + c.flags.IntVar(&c.flagDeploymentMinInstances, "deployment-min-instances", 0, + "The minimum number of instances to deploy for each gateway.", + ) + c.flags.StringVar(&c.flagNodeSelector, "node-selector", "", + "The node selector to use in scheduling a gateway.", + ) + c.flags.StringVar(&c.flagTolerations, "tolerations", "", + "The tolerations to use in a deployed gateway.", + ) + c.flags.StringVar(&c.flagServiceAnnotations, "service-annotations", "", + "The annotations to copy over from a gateway to its service.", + ) + + c.k8s = &flags.K8SFlags{} + flags.Merge(c.flags, c.k8s.Flags()) + c.help = flags.Usage(help, c.flags) +} + +func (c *Command) Run(args []string) int { + var err error + c.once.Do(c.init) + if err = c.flags.Parse(args); err != nil { + return 1 + } + // Validate flags + if err := c.validateFlags(); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if c.ctx == nil { + c.ctx = context.Background() + } + + // Create the Kubernetes client + if c.k8sClient == nil { + config, err := subcommand.K8SConfig(c.k8s.KubeConfig()) + if err != nil { + c.UI.Error(fmt.Sprintf("Error retrieving Kubernetes auth: %s", err)) + return 1 + } + + s := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add client-go schema: %s", err)) + return 1 + } + if err := gwv1beta1.Install(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add api-gateway schema: %s", err)) + return 1 + } + if err := v1alpha1.AddToScheme(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add consul-k8s schema: %s", err)) + return 1 + } + + c.k8sClient, err = client.New(config, client.Options{Scheme: s}) + if err != nil { + c.UI.Error(fmt.Sprintf("Error initializing Kubernetes client: %s", err)) + return 1 + } + } + + // do the creation + + labels := map[string]string{ + "app": c.flagApp, + "chart": c.flagChart, + "heritage": c.flagHeritage, + "release": c.flagRelease, + "component": c.flagComponent, + } + classConfig := &v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{Name: c.flagGatewayClassConfigName, Labels: labels}, + Spec: v1alpha1.GatewayClassConfigSpec{ + ServiceType: serviceTypeIfSet(c.flagServiceType), + NodeSelector: c.nodeSelector, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ + Service: c.serviceAnnotations, + }, + Tolerations: c.tolerations, + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: nonZeroOrNil(c.flagDeploymentDefaultInstances), + MaxInstances: nonZeroOrNil(c.flagDeploymentMaxInstances), + MinInstances: nonZeroOrNil(c.flagDeploymentMinInstances), + }, + }, + } + + class := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{Name: c.flagGatewayClassName, Labels: labels}, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: gwv1beta1.GatewayController(c.flagControllerName), + ParametersRef: &gwv1beta1.ParametersReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: gwv1beta1.Kind(v1alpha1.GatewayClassConfigKind), + Name: c.flagGatewayClassConfigName, + }, + }, + } + + if err := forceClassConfig(context.Background(), c.k8sClient, classConfig); err != nil { + c.UI.Error(err.Error()) + return 1 + } + if err := forceClass(context.Background(), c.k8sClient, class); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + return 0 +} + +func (c *Command) validateFlags() error { + if c.flagGatewayClassConfigName == "" { + return errors.New("-gateway-class-config-name must be set") + } + if c.flagGatewayClassName == "" { + return errors.New("-gateway-class-name must be set") + } + if c.flagHeritage == "" { + return errors.New("-heritage must be set") + } + if c.flagChart == "" { + return errors.New("-chart must be set") + } + if c.flagApp == "" { + return errors.New("-app must be set") + } + if c.flagRelease == "" { + return errors.New("-release-name must be set") + } + if c.flagComponent == "" { + return errors.New("-component must be set") + } + if c.flagControllerName == "" { + return errors.New("-controller-name must be set") + } + if c.flagTolerations != "" { + var tolerations []toleration + if err := yaml.Unmarshal([]byte(c.flagTolerations), &tolerations); err != nil { + return fmt.Errorf("error decoding tolerations: %w", err) + } + c.tolerations = common.ConvertSliceFunc(tolerations, tolerationToKubernetes) + } + if c.flagNodeSelector != "" { + if err := yaml.Unmarshal([]byte(c.flagNodeSelector), &c.nodeSelector); err != nil { + return fmt.Errorf("error decoding node selector: %w", err) + } + } + if c.flagNodeSelector != "" { + if err := yaml.Unmarshal([]byte(c.flagNodeSelector), &c.nodeSelector); err != nil { + return fmt.Errorf("error decoding node selector: %w", err) + } + } + if c.flagServiceAnnotations != "" { + if err := yaml.Unmarshal([]byte(c.flagServiceAnnotations), &c.serviceAnnotations); err != nil { + return fmt.Errorf("error decoding service annotations: %w", err) + } + } + + return nil +} + +func (c *Command) Synopsis() string { return synopsis } +func (c *Command) Help() string { + c.once.Do(c.init) + return c.help +} + +const synopsis = "Create managed gateway resources after installation/upgrade." +const help = ` +Usage: consul-k8s-control-plane gateway-resources [options] + + Installs managed gateway class and configuration resources + after a helm installation or upgrade in order to avoid the + dependencies of CRDs being in-place prior to resource creation. + +` + +func forceClassConfig(ctx context.Context, k8sClient client.Client, o *v1alpha1.GatewayClassConfig) error { + return backoff.Retry(func() error { + var existing v1alpha1.GatewayClassConfig + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } + + if k8serrors.IsNotFound(err) { + return k8sClient.Create(ctx, o) + } + + existing.Spec = o.Spec + existing.Labels = o.Labels + + return k8sClient.Update(ctx, &existing) + }, exponentialBackoffWithMaxIntervalAndTime()) +} + +func forceClass(ctx context.Context, k8sClient client.Client, o *gwv1beta1.GatewayClass) error { + return backoff.Retry(func() error { + var existing gwv1beta1.GatewayClass + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } + + if k8serrors.IsNotFound(err) { + return k8sClient.Create(ctx, o) + } + + existing.Spec = o.Spec + existing.Labels = o.Labels + + return k8sClient.Update(ctx, &existing) + }, exponentialBackoffWithMaxIntervalAndTime()) +} + +func exponentialBackoffWithMaxIntervalAndTime() *backoff.ExponentialBackOff { + backoff := backoff.NewExponentialBackOff() + backoff.MaxElapsedTime = 10 * time.Second + backoff.MaxInterval = 1 * time.Second + backoff.Reset() + return backoff +} + +func nonZeroOrNil(v int) *int32 { + if v == 0 { + return nil + } + return common.PointerTo(int32(v)) +} + +func serviceTypeIfSet(v string) *corev1.ServiceType { + if v == "" { + return nil + } + return common.PointerTo(corev1.ServiceType(v)) +} diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go new file mode 100644 index 0000000000..0c40e67244 --- /dev/null +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -0,0 +1,253 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gatewayresources + +import ( + "testing" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestRun_flagValidation(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + cmd *Command + expectedErr string + }{ + "required gateway class config name": { + cmd: &Command{}, + expectedErr: "-gateway-class-config-name must be set", + }, + "required gateway class name": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + }, + expectedErr: "-gateway-class-name must be set", + }, + "required heritage": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + }, + expectedErr: "-heritage must be set", + }, + "required chart": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + }, + expectedErr: "-chart must be set", + }, + "required app": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + }, + expectedErr: "-app must be set", + }, + "required release": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + flagApp: "test", + }, + expectedErr: "-release-name must be set", + }, + "required component": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + flagApp: "test", + flagRelease: "test", + }, + expectedErr: "-component must be set", + }, + "required controller name": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + flagApp: "test", + flagRelease: "test", + flagComponent: "test", + }, + expectedErr: "-controller-name must be set", + }, + "required valid tolerations": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + flagApp: "test", + flagRelease: "test", + flagComponent: "test", + flagControllerName: "test", + flagTolerations: "foo", + }, + expectedErr: "error decoding tolerations: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `foo` into []gatewayresources.toleration", + }, + "required valid nodeSelector": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + flagApp: "test", + flagRelease: "test", + flagComponent: "test", + flagControllerName: "test", + flagNodeSelector: "foo", + }, + expectedErr: "error decoding node selector: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `foo` into map[string]string", + }, + "required valid service annotations": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + flagApp: "test", + flagRelease: "test", + flagComponent: "test", + flagControllerName: "test", + flagServiceAnnotations: "foo", + }, + expectedErr: "error decoding service annotations: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `foo` into []string", + }, + "valid without optional flags": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + flagApp: "test", + flagRelease: "test", + flagComponent: "test", + flagControllerName: "test", + }, + }, + "valid with optional flags": { + cmd: &Command{ + flagGatewayClassConfigName: "test", + flagGatewayClassName: "test", + flagHeritage: "test", + flagChart: "test", + flagApp: "test", + flagRelease: "test", + flagComponent: "test", + flagControllerName: "test", + flagNodeSelector: ` +foo: 1 +bar: 2`, + flagTolerations: ` +- value: foo +- value: bar`, + flagServiceAnnotations: ` +- foo +- bar`, + }, + }, + } { + t.Run(name, func(t *testing.T) { + tt := tt + + t.Parallel() + + err := tt.cmd.validateFlags() + if tt.expectedErr == "" && err != nil { + t.Errorf("unexpected error occured: %v", err) + } + if tt.expectedErr != "" && err == nil { + t.Error("expected error but got none") + } + if tt.expectedErr != "" { + require.EqualError(t, err, tt.expectedErr) + } + }) + } +} + +func TestRun(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + existingGatewayClass bool + existingGatewayClassConfig bool + }{ + "both exist": { + existingGatewayClass: true, + existingGatewayClassConfig: true, + }, + "gateway class config doesn't exist": { + existingGatewayClass: true, + }, + "gateway class doesn't exist": { + existingGatewayClassConfig: true, + }, + "neither exist": {}, + } { + t.Run(name, func(t *testing.T) { + tt := tt + + t.Parallel() + + existingGatewayClassConfig := &v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + existingGatewayClass := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + objs := []client.Object{} + if tt.existingGatewayClass { + objs = append(objs, existingGatewayClass) + } + if tt.existingGatewayClassConfig { + objs = append(objs, existingGatewayClassConfig) + } + + client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + } + + code := cmd.Run([]string{ + "-gateway-class-config-name", "test", + "-gateway-class-name", "test", + "-heritage", "test", + "-chart", "test", + "-app", "test", + "-release-name", "test", + "-component", "test", + "-controller-name", "test", + }) + + require.Equal(t, 0, code) + }) + } +} diff --git a/hack/copy-crds-to-chart/main.go b/hack/copy-crds-to-chart/main.go index b5eef3e1aa..149126b392 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -18,12 +18,6 @@ var ( "consul.hashicorp.com_peeringacceptors.yaml": {}, "consul.hashicorp.com_peeringdialers.yaml": {}, } - // HACK IT (again)! These CRDs need to go in the Helm chart's crds directory which means they - // cannot have any templating in them. They need to be in the CRD directory because we install - // resources that reference them in the main installation sequence. - toCRDDir = map[string]struct{}{ - "consul.hashicorp.com_gatewayclassconfigs.yaml": {}, - } ) func main() { @@ -40,37 +34,40 @@ func main() { } func realMain(helmPath string) error { - return filepath.Walk("../../control-plane/config/crd/bases", func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } + root := "../../control-plane/config/crd/" + dirs := []string{"bases", "external"} - if info.IsDir() || filepath.Ext(path) != ".yaml" { - return nil - } + for _, dir := range dirs { + err := filepath.Walk(root+dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } - printf("processing %s", filepath.Base(path)) + if info.IsDir() || filepath.Ext(path) != ".yaml" || filepath.Base(path) == "kustomization.yaml" { + return nil + } - contentBytes, err := os.ReadFile(path) - if err != nil { - return err - } - contents := string(contentBytes) - - // Strip leading newline. - contents = strings.TrimPrefix(contents, "\n") - - if _, ok := requiresPeering[info.Name()]; ok { - // Add {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} {{- end }} wrapper. - contents = fmt.Sprintf("{{- if and .Values.connectInject.enabled .Values.global.peering.enabled }}\n%s{{- end }}\n", contents) - } else if _, ok := toCRDDir[info.Name()]; ok { - // No-op (we don't want templating). - } else { - // Add {{- if .Values.connectInject.enabled }} {{- end }} wrapper. - contents = fmt.Sprintf("{{- if .Values.connectInject.enabled }}\n%s{{- end }}\n", contents) - } + printf("processing %s", filepath.Base(path)) + + contentBytes, err := os.ReadFile(path) + if err != nil { + return err + } + contents := string(contentBytes) + + // Strip leading newline. + contents = strings.TrimPrefix(contents, "\n") + + if _, ok := requiresPeering[info.Name()]; ok { + // Add {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} {{- end }} wrapper. + contents = fmt.Sprintf("{{- if and .Values.connectInject.enabled .Values.global.peering.enabled }}\n%s{{- end }}\n", contents) + } else if dir == "external" { + contents = fmt.Sprintf("{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }}\n%s{{- end }}\n", contents) + } else { + // Add {{- if .Values.connectInject.enabled }} {{- end }} wrapper. + contents = fmt.Sprintf("{{- if .Values.connectInject.enabled }}\n%s{{- end }}\n", contents) + } - if _, ok := toCRDDir[info.Name()]; !ok { // Add labels, this is hacky because we're relying on the line number // but it means we don't need to regex or yaml parse. splitOnNewlines := strings.Split(contents, "\n") @@ -84,20 +81,27 @@ func realMain(helmPath string) error { } withLabels := append(splitOnNewlines[0:9], append(labelLines, splitOnNewlines[9:]...)...) contents = strings.Join(withLabels, "\n") - } - // Construct the destination filename. - filenameSplit := strings.Split(info.Name(), "_") - crdName := filenameSplit[1] - destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName)) - if _, ok := toCRDDir[info.Name()]; ok { - destinationPath = filepath.Join(helmPath, "crds", formatCRDName(info.Name())) - } + var crdName string + if dir == "bases" { + // Construct the destination filename. + filenameSplit := strings.Split(info.Name(), "_") + crdName = filenameSplit[1] + } else if dir == "external" { + filenameSplit := strings.Split(info.Name(), ".") + crdName = filenameSplit[0] + ".yaml" + } - // Write it. - printf("writing to %s", destinationPath) - return os.WriteFile(destinationPath, []byte(contents), 0644) - }) + destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName)) + // Write it. + printf("writing to %s", destinationPath) + return os.WriteFile(destinationPath, []byte(contents), 0644) + }) + if err != nil { + return err + } + } + return nil } func printf(format string, args ...interface{}) { From 8d014c0c4f3c652c11dcd289106b10fdd05333f3 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 6 Jun 2023 10:17:21 -0400 Subject: [PATCH 202/592] Add missing entries to main CHANGELOG (#2275) --- CHANGELOG.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0df700efa..1cf1d2084f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,115 @@ +## 1.1.2 (June 5, 2023) + +SECURITY: + +* Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.2`. [[GH-2204](https://github.com/hashicorp/consul-k8s/issues/2204)] +* Bump `controller-runtime` to address CVEs in dependencies. [[GH-2226](https://github.com/hashicorp/consul-k8s/issues/2226)] +* Upgrade to use Go 1.20.4. +This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), +[CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), +[CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and +[CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). +Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41721 +](https://github.com/advisories/GHSA-fxg5-wq6x-vr4w +), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 +](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h +.) [[GH-2104](https://github.com/hashicorp/consul-k8s/issues/2104)] + +FEATURES: + +* Add support for consul-telemetry-collector to forward envoy metrics to an otelhttp compatible receiver or HCP [[GH-2134](https://github.com/hashicorp/consul-k8s/issues/2134)] +* consul-telemetry-collector: Configure envoy proxy config during registration when consul-telemetry-collector is enabled. [[GH-2143](https://github.com/hashicorp/consul-k8s/issues/2143)] +* sync-catalog: add ability to sync hostname from a Kubernetes Ingress resource to the Consul Catalog during service registration. [[GH-2098](https://github.com/hashicorp/consul-k8s/issues/2098)] + +IMPROVEMENTS: + +* cli: Add `consul-k8s config read` command that returns the helm configuration in yaml format. [[GH-2078](https://github.com/hashicorp/consul-k8s/issues/2078)] +* cli: add consul-telemetry-gateway allow-all intention for -demo [[GH-2262](https://github.com/hashicorp/consul-k8s/issues/2262)] +* cli: update cloud preset to enable telemetry collector [[GH-2205](https://github.com/hashicorp/consul-k8s/issues/2205)] +* consul-telemetry-collector: add acceptance tests for consul telemetry collector component [[GH-2195](https://github.com/hashicorp/consul-k8s/issues/2195)] + +BUG FIXES: + +* crd: fix bug on service intentions CRD causing some updates to be ignored. [[GH-2194](https://github.com/hashicorp/consul-k8s/issues/2083)] +* api-gateway: fix issue where the API Gateway controller is unable to start up successfully when Vault is configured as the secrets backend [[GH-2083](https://github.com/hashicorp/consul-k8s/issues/2083)] +* control-plane: add support for idleTimeout in the Service Router config [[GH-2156](https://github.com/hashicorp/consul-k8s/issues/2156)] +* control-plane: fix issue with json tags of service defaults fields EnforcingConsecutive5xx, MaxEjectionPercent and BaseEjectionTime. [[GH-2160](https://github.com/hashicorp/consul-k8s/issues/2160)] +* control-plane: fix issue with multiport pods crashlooping due to dataplane port conflicts by ensuring dns redirection is disabled for non-tproxy pods [[GH-2176](https://github.com/hashicorp/consul-k8s/issues/2176)] +* helm: add missing `$HOST_IP` environment variable to to mesh gateway deployments. [[GH-1808](https://github.com/hashicorp/consul-k8s/issues/1808)] +* sync-catalog: fix issue where the sync-catalog ACL token were set with an incorrect ENV VAR. [[GH-2068](https://github.com/hashicorp/consul-k8s/issues/2068)] + +## 1.0.7 (May 17, 2023) + +SECURITY: + +* Upgrade to use Go 1.19.9. +This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), +[CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), +[CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and +[CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). +Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41721 +](https://github.com/advisories/GHSA-fxg5-wq6x-vr4w +), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 +](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h +.) [[GH-2108](https://github.com/hashicorp/consul-k8s/issues/2108)] + +FEATURES: + +* sync-catalog: add ability to sync hostname from a Kubernetes Ingress resource to the Consul Catalog during service registration. [[GH-2098](https://github.com/hashicorp/consul-k8s/issues/2098)] + +IMPROVEMENTS: + +* cli: Add `consul-k8s config read` command that returns the helm configuration in yaml format. [[GH-2078](https://github.com/hashicorp/consul-k8s/issues/2078)] +* helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.0.2`, `image` value to `hashicorp/consul:1.14.7`, +and `imageEnvoy` to `envoyproxy/envoy:v1.24.7`. [[GH-2140](https://github.com/hashicorp/consul-k8s/issues/2140)] + +BUG FIXES: + +* api-gateway: fix issue where the API Gateway controller is unable to start up successfully when Vault is configured as the secrets backend [[GH-2083](https://github.com/hashicorp/consul-k8s/issues/2083)] +* helm: add missing `$HOST_IP` environment variable to to mesh gateway deployments. [[GH-1808](https://github.com/hashicorp/consul-k8s/issues/1808)] +* sync-catalog: fix issue where the sync-catalog ACL token were set with an incorrect ENV VAR. [[GH-2068](https://github.com/hashicorp/consul-k8s/issues/2068)] + +## 0.49.6 (May 17, 2023) + +SECURITY: + +* Upgrade to use Go 1.19.9. +This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), +[CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), +[CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and +[CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). +Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41721 +](https://github.com/advisories/GHSA-fxg5-wq6x-vr4w +), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 +](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h +.) [[GH-2110](https://github.com/hashicorp/consul-k8s/issues/2110)] + +IMPROVEMENTS: + +* helm: Set default `limits.cpu` resource setting to `null` for `consul-connect-inject-init` container to speed up registration times when onboarding services onto the mesh during the init container lifecycle. [[GH-2008](https://github.com/hashicorp/consul-k8s/issues/2008)] + +## 1.1.1 (March 31, 2023) + +IMPROVEMENTS: + +* helm: Set default `limits.cpu` resource setting to `null` for `consul-connect-inject-init` container to speed up registration times when onboarding services onto the mesh during the init container lifecycle. [[GH-2008](https://github.com/hashicorp/consul-k8s/issues/2008)] +* helm: When the `global.acls.bootstrapToken` field is set and the content of the secret is empty, the bootstrap ACL token is written to that secret after bootstrapping ACLs. This applies to both the Vault and Consul secrets backends. [[GH-1920](https://github.com/hashicorp/consul-k8s/issues/1920)] + +BUG FIXES: + +* api-gateway: fix ACL issue where when adminPartitions and ACLs are enabled, API Gateway Controller is unable to create a new namespace in Consul [[GH-2029](https://github.com/hashicorp/consul-k8s/issues/2029)] +* api-gateway: fix issue where specifying an external server SNI name while using client nodes resulted in a TLS verification error. [[GH-2013](https://github.com/hashicorp/consul-k8s/issues/2013)] + +## 1.0.6 (March 20, 2023) + +IMPROVEMENTS: + +* helm: Set default `limits.cpu` resource setting to `null` for `consul-connect-inject-init` container to speed up registration times when onboarding services onto the mesh during the init container lifecycle. [[GH-2008](https://github.com/hashicorp/consul-k8s/issues/2008)] + +BUG FIXES: + +* api-gateway: fix issue where specifying an external server SNI name while using client nodes resulted in a TLS verification error. [[GH-2013](https://github.com/hashicorp/consul-k8s/issues/2013)] + ## 1.0.5 (March 9, 2023) SECURITY: From 38cd4d724ed201c7bbea83ba3f0a46e5a0e3e92a Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 6 Jun 2023 10:20:50 -0400 Subject: [PATCH 203/592] Fixing changelog for 2195 (#2277) --- .changelog/2195.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changelog/2195.txt b/.changelog/2195.txt index 1b450eb40d..e7475f88e0 100644 --- a/.changelog/2195.txt +++ b/.changelog/2195.txt @@ -1,3 +1,3 @@ -```release-note:imrpovement +```release-note:improvement consul-telemetry-collector: add acceptance tests for consul telemetry collector component. -``` \ No newline at end of file +``` From 0f893beacd8616798ffb2ddf31b6616405dc1c26 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 6 Jun 2023 11:23:13 -0400 Subject: [PATCH 204/592] [API Gateway] Add external consul servers test (#2270) * [API Gateway] Add external consul servers test * Fix up releaseName usage on CLI-based tests to mirror helm-based tests --- acceptance/framework/consul/cli_cluster.go | 15 +- acceptance/framework/consul/cluster.go | 2 +- acceptance/framework/consul/helm_cluster.go | 21 ++- .../api_gateway_external_servers_test.go | 133 ++++++++++++++++++ 4 files changed, 159 insertions(+), 12 deletions(-) create mode 100644 acceptance/tests/api-gateway/api_gateway_external_servers_test.go diff --git a/acceptance/framework/consul/cli_cluster.go b/acceptance/framework/consul/cli_cluster.go index a9b1dbe74f..ba4cfc93ab 100644 --- a/acceptance/framework/consul/cli_cluster.go +++ b/acceptance/framework/consul/cli_cluster.go @@ -200,9 +200,14 @@ func (c *CLICluster) Destroy(t *testing.T) { require.NoError(t, err) } -func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool) (*api.Client, string) { +func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool, release ...string) (*api.Client, string) { t.Helper() + releaseName := c.releaseName + if len(release) > 0 { + releaseName = release[0] + } + namespace := c.kubectlOptions.Namespace config := api.DefaultConfig() localPort := terratestk8s.GetAvailablePort(t) @@ -222,13 +227,13 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool) (*api.Client, // In secondary servers, we don't create a bootstrap token since ACLs are only bootstrapped in the primary. // Instead, we provide a replication token that serves the role of the bootstrap token. - aclSecretName := fmt.Sprintf("%s-consul-bootstrap-acl-token", c.releaseName) + aclSecretName := fmt.Sprintf("%s-consul-bootstrap-acl-token", releaseName) if c.releaseName == CLIReleaseName { aclSecretName = "consul-bootstrap-acl-token" } aclSecret, err := c.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), aclSecretName, metav1.GetOptions{}) if err != nil && errors.IsNotFound(err) { - federationSecret := fmt.Sprintf("%s-consul-federation", c.releaseName) + federationSecret := fmt.Sprintf("%s-consul-federation", releaseName) if c.releaseName == CLIReleaseName { federationSecret = "consul-federation" } @@ -242,8 +247,8 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool) (*api.Client, } } - serverPod := fmt.Sprintf("%s-consul-server-0", c.releaseName) - if c.releaseName == CLIReleaseName { + serverPod := fmt.Sprintf("%s-consul-server-0", releaseName) + if releaseName == CLIReleaseName { serverPod = "consul-server-0" } tunnel := terratestk8s.NewTunnelWithLogger( diff --git a/acceptance/framework/consul/cluster.go b/acceptance/framework/consul/cluster.go index 734f07f36e..1b9a543245 100644 --- a/acceptance/framework/consul/cluster.go +++ b/acceptance/framework/consul/cluster.go @@ -12,7 +12,7 @@ import ( // Cluster represents a consul cluster object. type Cluster interface { // SetupConsulClient returns a new Consul client. - SetupConsulClient(t *testing.T, secure bool) (*api.Client, string) + SetupConsulClient(t *testing.T, secure bool, release ...string) (*api.Client, string) // Create creates a new Consul Cluster. Create(t *testing.T) diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 6c61a605f6..aa6fdef7d8 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -346,14 +346,23 @@ func (h *HelmCluster) Upgrade(t *testing.T, helmValues map[string]string) { k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName)) } -func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int) string { - serverPod := fmt.Sprintf("%s-consul-server-0", h.releaseName) +func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int, release ...string) string { + releaseName := h.releaseName + if len(release) > 0 { + releaseName = release[0] + } + serverPod := fmt.Sprintf("%s-consul-server-0", releaseName) return portforward.CreateTunnelToResourcePort(t, serverPod, remotePort, h.helmOptions.KubectlOptions, h.logger) } -func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool) (client *api.Client, configAddress string) { +func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool, release ...string) (client *api.Client, configAddress string) { t.Helper() + releaseName := h.releaseName + if len(release) > 0 { + releaseName = release[0] + } + namespace := h.helmOptions.KubectlOptions.Namespace config := api.DefaultConfig() remotePort := 8500 // use non-secure by default @@ -376,9 +385,9 @@ func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool) (client *api. // and will try to read the replication token from the federation secret. // In secondary servers, we don't create a bootstrap token since ACLs are only bootstrapped in the primary. // Instead, we provide a replication token that serves the role of the bootstrap token. - aclSecret, err := h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), h.releaseName+"-consul-bootstrap-acl-token", metav1.GetOptions{}) + aclSecret, err := h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), releaseName+"-consul-bootstrap-acl-token", metav1.GetOptions{}) if err != nil && errors.IsNotFound(err) { - federationSecret := fmt.Sprintf("%s-consul-federation", h.releaseName) + federationSecret := fmt.Sprintf("%s-consul-federation", releaseName) aclSecret, err = h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), federationSecret, metav1.GetOptions{}) require.NoError(r, err) config.Token = string(aclSecret.Data["replicationToken"]) @@ -392,7 +401,7 @@ func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool) (client *api. } } - config.Address = h.CreatePortForwardTunnel(t, remotePort) + config.Address = h.CreatePortForwardTunnel(t, remotePort, release...) consulClient, err := api.NewClient(config) require.NoError(t, err) diff --git a/acceptance/tests/api-gateway/api_gateway_external_servers_test.go b/acceptance/tests/api-gateway/api_gateway_external_servers_test.go new file mode 100644 index 0000000000..c0fa8bbcca --- /dev/null +++ b/acceptance/tests/api-gateway/api_gateway_external_servers_test.go @@ -0,0 +1,133 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package apigateway + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/types" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// TestAPIGateway_ExternalServers tests that connect works when using external servers. +// It sets up an external Consul server in the same cluster but a different Helm installation +// and then treats this server as external. +func TestAPIGateway_ExternalServers(t *testing.T) { + cfg := suite.Config() + ctx := suite.Environment().DefaultContext(t) + + serverHelmValues := map[string]string{ + "global.acls.manageSystemACLs": "true", + "global.tls.enabled": "true", + + // Don't install injector, controller and cni on this cluster so that it's not installed twice. + "connectInject.enabled": "false", + "connectInject.cni.enabled": "false", + } + serverReleaseName := helpers.RandomName() + consulServerCluster := consul.NewHelmCluster(t, serverHelmValues, ctx, cfg, serverReleaseName) + + consulServerCluster.Create(t) + + helmValues := map[string]string{ + "server.enabled": "false", + "global.acls.manageSystemACLs": "true", + "global.tls.enabled": "true", + "connectInject.enabled": "true", + "externalServers.enabled": "true", + "externalServers.hosts[0]": fmt.Sprintf("%s-consul-server", serverReleaseName), + "externalServers.httpsPort": "8501", + "global.tls.caCert.secretName": fmt.Sprintf("%s-consul-ca-cert", serverReleaseName), + "global.tls.caCert.secretKey": "tls.crt", + "global.acls.bootstrapToken.secretName": fmt.Sprintf("%s-consul-bootstrap-acl-token", serverReleaseName), + "global.acls.bootstrapToken.secretKey": "token", + } + + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + consulCluster.SkipCheckForPreviousInstallations = true + + consulCluster.Create(t) + + logger.Log(t, "creating static-server and static-client deployments") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + + // Override the default proxy config settings for this test + consulClient, _ := consulCluster.SetupConsulClient(t, true, serverReleaseName) + logger.Log(t, "have consul client") + _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + }, nil) + require.NoError(t, err) + logger.Log(t, "set consul config entry") + + logger.Log(t, "creating api-gateway resources") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") + }) + + logger.Log(t, "patching route to target server") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"name":"static-server","port":80}]}]}}`, "--type=merge") + + // Grab a kubernetes client so that we can verify binding + // behavior prior to issuing requests through the gateway. + k8sClient := ctx.ControllerRuntimeClient(t) + + // On startup, the controller can take upwards of 1m to perform + // leader election so we may need to wait a long time for + // the reconcile loop to run (hence a ~1m timeout here). + var gatewayAddress string + retryCheck(t, 30, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) + require.NoError(r, err) + + // check that we have an address to use + require.Len(r, gateway.Status.Addresses, 1) + // now we know we have an address, set it so we can use it + gatewayAddress = gateway.Status.Addresses[0].Value + }) + + k8sOptions := ctx.KubectlOptions(t) + targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) + + // check that intentions keep our connection from happening + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetAddress) + + // Now we create the allow intention. + _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: "static-server", + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Action: api.IntentionActionAllow, + }, + }, + }, nil) + require.NoError(t, err) + + // Test that we can make a call to the api gateway + // via the static-client pod. It should route to the static-server pod. + logger.Log(t, "trying calls to api gateway") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetAddress) +} From b5b0b27d8222bb59f6698ac869a9f6c05d84a18f Mon Sep 17 00:00:00 2001 From: John Maguire Date: Tue, 6 Jun 2023 11:55:44 -0400 Subject: [PATCH 205/592] Add check for timeout error (#2280) --- control-plane/api-gateway/cache/consul.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index 5c4125a040..9acfb2b1ee 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -160,7 +160,11 @@ func (c *Cache) subscribeToConsul(ctx context.Context, kind string) { entries, meta, err := client.ConfigEntries().List(kind, opts.WithContext(ctx)) if err != nil { - c.logger.Error(err, fmt.Sprintf("error fetching config entries for kind: %s", kind)) + // if we timeout we don't care about the error message because it's expected to happen on long polls + // any other error we want to alert on + if !strings.Contains(strings.ToLower(err.Error()), "timeout") { + c.logger.Error(err, fmt.Sprintf("error fetching config entries for kind: %s", kind)) + } continue } From 9dd605e238dc81a493134793138a2f248c923644 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 6 Jun 2023 15:21:08 -0400 Subject: [PATCH 206/592] Add Consul status to routes and gateways (#2281) --- .../tests/api-gateway/api_gateway_test.go | 2 + control-plane/api-gateway/binding/binder.go | 8 ++-- .../api-gateway/binding/route_binding.go | 38 ++++++++++++++-- control-plane/api-gateway/common/resources.go | 45 ++++++++++++------- 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index 94a91e79c2..2291587bcc 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -110,6 +110,7 @@ func TestAPIGateway_Basic(t *testing.T) { // check our statuses checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) require.Len(r, gateway.Status.Listeners, 3) require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) @@ -157,6 +158,7 @@ func TestAPIGateway_Basic(t *testing.T) { require.EqualValues(t, "gateway", httproute.Status.Parents[0].ParentRef.Name) checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) // check that the Consul entries were created entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index 96056920c3..b677d69253 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -152,7 +152,6 @@ func (b *Binder) Snapshot() *Snapshot { // process secrets gatewaySecrets := secretsForGateway(b.config.Gateway, b.config.Resources) - certs := mapset.NewSet() if !isGatewayDeleted { // we only do this if the gateway isn't going to be deleted so that the // resources can get GC'd @@ -163,7 +162,6 @@ func (b *Binder) Snapshot() *Snapshot { b.config.Logger.Error(err, "error parsing referenced secret, ignoring") continue } - certs.Add(secret) } } @@ -180,10 +178,14 @@ func (b *Binder) Snapshot() *Snapshot { snapshot.GatewayClassConfig = gatewayClassConfig snapshot.UpsertGatewayDeployment = true + var consulStatus api.ConfigEntryStatus + if b.config.ConsulGateway != nil { + consulStatus = b.config.ConsulGateway.Status + } entry := b.config.Translator.ToAPIGateway(b.config.Gateway, b.config.Resources) snapshot.Consul.Updates = append(snapshot.Consul.Updates, &common.ConsulUpdateOperation{ Entry: entry, - OnUpdate: b.handleGatewaySyncStatus(snapshot, &b.config.Gateway), + OnUpdate: b.handleGatewaySyncStatus(snapshot, &b.config.Gateway, consulStatus), }) registrations := registrationsForPods(entry.Namespace, b.config.Gateway, registrationPods) diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index 2a3be4884b..cc33a00e07 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -280,6 +280,7 @@ func (r *Binder) mutateRouteWithBindingResults(snapshot *Snapshot, object client // set the old parent states new.Parents = old.Parents + new.Status = old.Status } // and now add what is left for parent := range parents.Iter() { @@ -299,6 +300,7 @@ func (r *Binder) mutateRouteWithBindingResults(snapshot *Snapshot, object client // set the old parent states new.Parents = old.Parents + new.Status = old.Status } // and now add what is left for parent := range parents.Iter() { @@ -402,8 +404,8 @@ func canReferenceBackend(object client.Object, ref gwv1beta1.BackendRef, resourc return false } -func (r *Binder) handleRouteSyncStatus(snapshot *Snapshot, object client.Object) func(error) { - return func(err error) { +func (r *Binder) handleRouteSyncStatus(snapshot *Snapshot, object client.Object) func(error, api.ConfigEntryStatus) { + return func(err error, status api.ConfigEntryStatus) { condition := metav1.Condition{ Type: "Synced", Status: metav1.ConditionTrue, @@ -425,10 +427,15 @@ func (r *Binder) handleRouteSyncStatus(snapshot *Snapshot, object client.Object) if r.statusSetter.setRouteConditionOnAllRefs(object, condition) { snapshot.Kubernetes.StatusUpdates.Add(object) } + if consulCondition := consulCondition(object.GetGeneration(), status); consulCondition != nil { + if r.statusSetter.setRouteConditionOnAllRefs(object, *consulCondition) { + snapshot.Kubernetes.StatusUpdates.Add(object) + } + } } } -func (r *Binder) handleGatewaySyncStatus(snapshot *Snapshot, gateway *gwv1beta1.Gateway) func(error) { +func (r *Binder) handleGatewaySyncStatus(snapshot *Snapshot, gateway *gwv1beta1.Gateway, status api.ConfigEntryStatus) func(error) { return func(err error) { condition := metav1.Condition{ Type: "Synced", @@ -453,5 +460,30 @@ func (r *Binder) handleGatewaySyncStatus(snapshot *Snapshot, gateway *gwv1beta1. gateway.Status.Conditions = conditions snapshot.Kubernetes.StatusUpdates.Add(gateway) } + + if consulCondition := consulCondition(gateway.Generation, status); consulCondition != nil { + if conditions, updated := setCondition(gateway.Status.Conditions, *consulCondition); updated { + gateway.Status.Conditions = conditions + snapshot.Kubernetes.StatusUpdates.Add(gateway) + } + } } } + +func consulCondition(generation int64, status api.ConfigEntryStatus) *metav1.Condition { + for _, c := range status.Conditions { + // we only care about the top-level status that isn't in reference + // to a resource. + if c.Type == "Accepted" && c.Resource.Name == "" { + return &metav1.Condition{ + Type: "ConsulAccepted", + Reason: c.Reason, + Status: metav1.ConditionStatus(c.Status), + Message: c.Message, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + } + } + } + return nil +} diff --git a/control-plane/api-gateway/common/resources.go b/control-plane/api-gateway/common/resources.go index 4cd3bfc3d2..a8f36502d3 100644 --- a/control-plane/api-gateway/common/resources.go +++ b/control-plane/api-gateway/common/resources.go @@ -401,7 +401,7 @@ func (s *ResourceMap) gatewaysForRoute(namespace string, refs []gwv1beta1.Parent return gateways } -func (s *ResourceMap) TranslateAndMutateHTTPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(old *api.HTTPRouteConfigEntry, new api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { +func (s *ResourceMap) TranslateAndMutateHTTPRoute(key types.NamespacedName, onUpdate func(error, api.ConfigEntryStatus), mutateFn func(old *api.HTTPRouteConfigEntry, new api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { consulKey := NormalizeMeta(s.toConsulReference(api.HTTPRoute, key)) route, ok := s.httpRouteGateways[consulKey] @@ -419,8 +419,10 @@ func (s *ResourceMap) TranslateAndMutateHTTPRoute(key types.NamespacedName, onUp // to be GC'd. delete(s.consulHTTPRoutes, consulKey) s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, + Entry: &mutated, + OnUpdate: func(err error) { + onUpdate(err, mutated.Status) + }, }) } return @@ -431,13 +433,15 @@ func (s *ResourceMap) TranslateAndMutateHTTPRoute(key types.NamespacedName, onUp // to be GC'd. delete(s.consulHTTPRoutes, consulKey) s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, + Entry: &mutated, + OnUpdate: func(err error) { + onUpdate(err, mutated.Status) + }, }) } } -func (s *ResourceMap) MutateHTTPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { +func (s *ResourceMap) MutateHTTPRoute(key types.NamespacedName, onUpdate func(error, api.ConfigEntryStatus), mutateFn func(api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { consulKey := NormalizeMeta(s.toConsulReference(api.HTTPRoute, key)) consulRoute, ok := s.consulHTTPRoutes[consulKey] @@ -448,8 +452,10 @@ func (s *ResourceMap) MutateHTTPRoute(key types.NamespacedName, onUpdate func(er // to be GC'd. delete(s.consulHTTPRoutes, consulKey) s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, + Entry: &mutated, + OnUpdate: func(err error) { + onUpdate(err, mutated.Status) + }, }) } } @@ -462,12 +468,11 @@ func (s *ResourceMap) CanGCHTTPRouteOnUnbind(id api.ResourceReference) bool { return true } -func (s *ResourceMap) TranslateAndMutateTCPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(*api.TCPRouteConfigEntry, api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { +func (s *ResourceMap) TranslateAndMutateTCPRoute(key types.NamespacedName, onUpdate func(error, api.ConfigEntryStatus), mutateFn func(*api.TCPRouteConfigEntry, api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) route, ok := s.tcpRouteGateways[consulKey] if !ok { - return } @@ -481,8 +486,10 @@ func (s *ResourceMap) TranslateAndMutateTCPRoute(key types.NamespacedName, onUpd // to be GC'd. delete(s.consulTCPRoutes, consulKey) s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, + Entry: &mutated, + OnUpdate: func(err error) { + onUpdate(err, mutated.Status) + }, }) } return @@ -493,13 +500,15 @@ func (s *ResourceMap) TranslateAndMutateTCPRoute(key types.NamespacedName, onUpd // to be GC'd. delete(s.consulTCPRoutes, consulKey) s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, + Entry: &mutated, + OnUpdate: func(err error) { + onUpdate(err, mutated.Status) + }, }) } } -func (s *ResourceMap) MutateTCPRoute(key types.NamespacedName, onUpdate func(error), mutateFn func(api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { +func (s *ResourceMap) MutateTCPRoute(key types.NamespacedName, onUpdate func(error, api.ConfigEntryStatus), mutateFn func(api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) consulRoute, ok := s.consulTCPRoutes[consulKey] @@ -510,8 +519,10 @@ func (s *ResourceMap) MutateTCPRoute(key types.NamespacedName, onUpdate func(err // to be GC'd. delete(s.consulTCPRoutes, consulKey) s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: onUpdate, + Entry: &mutated, + OnUpdate: func(err error) { + onUpdate(err, mutated.Status) + }, }) } } From ee256e98a756bbd98f864f2259cc9fd1f009a4c2 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 6 Jun 2023 16:03:20 -0400 Subject: [PATCH 207/592] Update alpine to 3.18 to fix CVE-2023-2650 (#2284) * Update alpine to 3.18 --- .changelog/2284.txt | 3 +++ control-plane/Dockerfile | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .changelog/2284.txt diff --git a/.changelog/2284.txt b/.changelog/2284.txt new file mode 100644 index 0000000000..27c51dff07 --- /dev/null +++ b/.changelog/2284.txt @@ -0,0 +1,3 @@ +```release-note:security +Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 +``` diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index a870e20e2b..de0c1cf1ff 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -22,7 +22,7 @@ RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@49f60 # dev copies the binary from a local build # ----------------------------------- # BIN_NAME is a requirement in the hashicorp docker github action -FROM alpine:3.17 AS dev +FROM alpine:3.18 AS dev # NAME and VERSION are the name of the software in releases.hashicorp.com # and the version to download. Example: NAME=consul VERSION=1.2.3. @@ -74,7 +74,7 @@ CMD /bin/${BIN_NAME} # We don't rebuild the software because we want the exact checksums and # binary signatures to match the software and our builds aren't fully # reproducible currently. -FROM alpine:3.17 AS release-default +FROM alpine:3.18 AS release-default ARG BIN_NAME=consul-k8s-control-plane ARG CNI_BIN_NAME=consul-cni From 49c5219255ee28142693e9ed7498999e8aeb8508 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Tue, 6 Jun 2023 17:28:00 -0400 Subject: [PATCH 208/592] Remove check for reference grant for route to gateway (#2283) * Remove check for reference grant for route to gateway * Fix tenancy tests * Final cleaning up of acceptance test --- .../api-gateway/api_gateway_tenancy_test.go | 19 +-- .../api-gateway/binding/binder_test.go | 24 +-- .../api-gateway/binding/reference_grant.go | 31 +--- .../binding/reference_grant_test.go | 158 ------------------ control-plane/api-gateway/binding/result.go | 15 +- .../api-gateway/binding/route_binding.go | 23 +-- control-plane/api-gateway/common/resources.go | 15 +- .../api-gateway/common/translation_test.go | 9 +- 8 files changed, 37 insertions(+), 257 deletions(-) diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go index f2e6899094..2f0005da80 100644 --- a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go +++ b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go @@ -19,6 +19,12 @@ import ( "time" terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" @@ -28,11 +34,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) var ( @@ -142,7 +143,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { checkStatusCondition(r, gateway.Status.Conditions, falseCondition("Synced", "SyncError")) require.Len(r, gateway.Status.Listeners, 1) - require.EqualValues(r, 0, gateway.Status.Listeners[0].AttachedRoutes) + require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("ResolvedRefs", "RefNotPermitted")) @@ -162,19 +163,15 @@ func TestAPIGateway_Tenancy(t *testing.T) { require.EqualValues(r, "gateway", httproute.Status.Parents[0].ParentRef.Name) require.NotNil(r, httproute.Status.Parents[0].ParentRef.Namespace) require.EqualValues(r, gatewayNamespace, *httproute.Status.Parents[0].ParentRef.Namespace) - checkStatusCondition(r, httproute.Status.Parents[0].Conditions, falseCondition("Accepted", "RefNotPermitted")) + checkStatusCondition(r, httproute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, httproute.Status.Parents[0].Conditions, falseCondition("ResolvedRefs", "RefNotPermitted")) }) - // since we're not bound to anything, check to make sure that the route doesn't get created in Consul. - checkConsulNotExists(t, consulClient, api.HTTPRoute, "route", namespaceForConsul(c.namespaceMirroring, routeNamespace)) - // we only sync validly referenced certificates over, so check to make sure it is not created. checkConsulNotExists(t, consulClient, api.InlineCertificate, "certificate", namespaceForConsul(c.namespaceMirroring, certificateNamespace)) // now create reference grants createReferenceGrant(t, k8sClient, "gateway-certificate", gatewayNamespace, certificateNamespace) - createReferenceGrant(t, k8sClient, "route-gateway", routeNamespace, gatewayNamespace) createReferenceGrant(t, k8sClient, "route-service", routeNamespace, serviceNamespace) // gateway updated with references allowed diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index 065290f56a..65cca94419 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -15,9 +15,6 @@ import ( logrtest "github.com/go-logr/logr/testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,6 +22,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" 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" + "github.com/hashicorp/consul/api" ) func init() { @@ -34,9 +35,8 @@ func init() { } const ( - testGatewayClassName = "gateway-class" - testControllerName = "test-controller" - routeListenerReferenceGrantErrorMessage = `http-listener-allowed-selector: reference not permitted due to lack of ReferenceGrant; http-listener-default-same: reference not permitted due to lack of ReferenceGrant; http-listener-explicit-all-allowed: reference not permitted due to lack of ReferenceGrant; http-listener-explicit-allowed-same: reference not permitted due to lack of ReferenceGrant; http-listener-hostname: reference not permitted due to lack of ReferenceGrant; http-listener-mismatched-kind-allowed: reference not permitted due to lack of ReferenceGrant; http-listener-tls: reference not permitted due to lack of ReferenceGrant; tcp-listener-allowed-selector: reference not permitted due to lack of ReferenceGrant; tcp-listener-default-same: reference not permitted due to lack of ReferenceGrant; tcp-listener-explicit-all-allowed: reference not permitted due to lack of ReferenceGrant; tcp-listener-explicit-allowed-same: reference not permitted due to lack of ReferenceGrant; tcp-listener-mismatched-kind-allowed: reference not permitted due to lack of ReferenceGrant; tcp-listener-tls: reference not permitted due to lack of ReferenceGrant` + testGatewayClassName = "gateway-class" + testControllerName = "test-controller" ) var ( @@ -1113,9 +1113,9 @@ func TestBinder_BindingRulesKitchenSink(t *testing.T) { Message: "resolved backend references", }, { Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: routeListenerReferenceGrantErrorMessage, + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", }, }}, }), @@ -1628,9 +1628,9 @@ func TestBinder_BindingRulesKitchenSink(t *testing.T) { Message: "resolved backend references", }, { Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: routeListenerReferenceGrantErrorMessage, + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "route accepted", }, }}, }), diff --git a/control-plane/api-gateway/binding/reference_grant.go b/control-plane/api-gateway/binding/reference_grant.go index 12c0f3b048..c2cc421a30 100644 --- a/control-plane/api-gateway/binding/reference_grant.go +++ b/control-plane/api-gateway/binding/reference_grant.go @@ -4,12 +4,13 @@ package binding import ( - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" ) type referenceValidator struct { @@ -45,20 +46,6 @@ func (rv *referenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gatewa return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(secretRef.Name)) } -func (rv *referenceValidator) HTTPRouteCanReferenceGateway(httproute gwv1beta1.HTTPRoute, parentRef gwv1beta1.ParentReference) bool { - fromNS := httproute.GetNamespace() - fromGK := metav1.GroupKind{ - Group: httproute.GroupVersionKind().Group, - Kind: httproute.GroupVersionKind().Kind, - } - - // Kind should default to Gateway if not set - // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/shared_types.go#L48 - toNS, toGK := createValuesFromRef(parentRef.Namespace, parentRef.Group, parentRef.Kind, common.BetaGroup, common.KindGateway) - - return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(parentRef.Name)) -} - func (rv *referenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { fromNS := httproute.GetNamespace() fromGK := metav1.GroupKind{ @@ -73,20 +60,6 @@ func (rv *referenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.H return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(backendRef.Name)) } -func (rv *referenceValidator) TCPRouteCanReferenceGateway(tcpRoute gwv1alpha2.TCPRoute, parentRef gwv1beta1.ParentReference) bool { - fromNS := tcpRoute.GetNamespace() - fromGK := metav1.GroupKind{ - Group: tcpRoute.GroupVersionKind().Group, - Kind: tcpRoute.GroupVersionKind().Kind, - } - - // Kind should default to Gateway if not set - // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/shared_types.go#L48 - toNS, toGK := createValuesFromRef(parentRef.Namespace, parentRef.Group, parentRef.Kind, common.BetaGroup, common.KindGateway) - - return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(parentRef.Name)) -} - func (rv *referenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { fromNS := tcpRoute.GetNamespace() fromGK := metav1.GroupKind{ diff --git a/control-plane/api-gateway/binding/reference_grant_test.go b/control-plane/api-gateway/binding/reference_grant_test.go index a325a2e927..12f01478fc 100644 --- a/control-plane/api-gateway/binding/reference_grant_test.go +++ b/control-plane/api-gateway/binding/reference_grant_test.go @@ -105,85 +105,6 @@ func TestGatewayCanReferenceSecret(t *testing.T) { } } -func TestHTTPRouteCanReferenceGateway(t *testing.T) { - t.Parallel() - - objName := gwv1beta1.ObjectName("mygateway") - - basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: ToNamespace, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - { - Group: Group, - Kind: HTTPRouteKind, - Namespace: FromNamespace, - }, - }, - To: []gwv1beta1.ReferenceGrantTo{ - { - Group: Group, - Kind: GatewayKind, - Name: &objName, - }, - }, - }, - } - - gatewayRefGroup := gwv1beta1.Group(Group) - gatewayRefKind := gwv1beta1.Kind(GatewayKind) - gatewayRefNamespace := gwv1beta1.Namespace(ToNamespace) - - cases := map[string]struct { - canReference bool - err error - ctx context.Context - httpRoute gwv1beta1.HTTPRoute - gatewayRef gwv1beta1.ParentReference - k8sReferenceGrants []gwv1beta1.ReferenceGrant - }{ - "httproute allowed to gateway": { - canReference: true, - err: nil, - ctx: context.TODO(), - httpRoute: gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: HTTPRouteKind, - APIVersion: Group + V1Beta1, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: FromNamespace, - }, - Spec: gwv1beta1.HTTPRouteSpec{}, - Status: gwv1beta1.HTTPRouteStatus{}, - }, - gatewayRef: gwv1beta1.ParentReference{ - Group: &gatewayRefGroup, - Kind: &gatewayRefKind, - Namespace: &gatewayRefNamespace, - Name: objName, - SectionName: nil, - Port: nil, - }, - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - basicValidReferenceGrant, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - rv := NewReferenceValidator(tc.k8sReferenceGrants) - canReference := rv.HTTPRouteCanReferenceGateway(tc.httpRoute, tc.gatewayRef) - - require.Equal(t, tc.canReference, canReference) - }) - } -} - func TestHTTPRouteCanReferenceBackend(t *testing.T) { t.Parallel() @@ -265,85 +186,6 @@ func TestHTTPRouteCanReferenceBackend(t *testing.T) { } } -func TestTCPRouteCanReferenceGateway(t *testing.T) { - t.Parallel() - - objName := gwv1beta1.ObjectName("mygateway") - - basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: ToNamespace, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - { - Group: Group, - Kind: TCPRouteKind, - Namespace: FromNamespace, - }, - }, - To: []gwv1beta1.ReferenceGrantTo{ - { - Group: Group, - Kind: GatewayKind, - Name: &objName, - }, - }, - }, - } - - gatewayRefGroup := gwv1beta1.Group(Group) - gatewayRefKind := gwv1beta1.Kind(GatewayKind) - gatewayRefNamespace := gwv1beta1.Namespace(ToNamespace) - - cases := map[string]struct { - canReference bool - err error - ctx context.Context - tcpRoute gwv1alpha2.TCPRoute - gatewayRef gwv1beta1.ParentReference - k8sReferenceGrants []gwv1beta1.ReferenceGrant - }{ - "tcpRoute allowed to gateway": { - canReference: true, - err: nil, - ctx: context.TODO(), - tcpRoute: gwv1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: TCPRouteKind, - APIVersion: Group + V1Alpha2, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: FromNamespace, - }, - Spec: gwv1alpha2.TCPRouteSpec{}, - Status: gwv1alpha2.TCPRouteStatus{}, - }, - gatewayRef: gwv1beta1.ParentReference{ - Group: &gatewayRefGroup, - Kind: &gatewayRefKind, - Namespace: &gatewayRefNamespace, - Name: objName, - SectionName: nil, - Port: nil, - }, - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - basicValidReferenceGrant, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - rv := NewReferenceValidator(tc.k8sReferenceGrants) - canReference := rv.TCPRouteCanReferenceGateway(tc.tcpRoute, tc.gatewayRef) - - require.Equal(t, tc.canReference, canReference) - }) - } -} - func TestTCPRouteCanReferenceBackend(t *testing.T) { t.Parallel() diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index 65198eeaf4..dd82cd55b5 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -10,21 +10,18 @@ import ( "strings" mapset "github.com/deckarep/golang-set" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) -var ( - // override function for tests. - timeFunc = metav1.Now + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" ) -var ( - // This is used for any error related to a lack of proper reference grant creation. - errRefNotPermitted = errors.New("reference not permitted due to lack of ReferenceGrant") -) +// override function for tests. +var timeFunc = metav1.Now + +// This is used for any error related to a lack of proper reference grant creation. +var errRefNotPermitted = errors.New("reference not permitted due to lack of ReferenceGrant") var ( // Each of the below are specified in the Gateway spec under RouteConditionReason diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index cc33a00e07..93e68241a0 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -5,13 +5,14 @@ package binding import ( mapset "github.com/deckarep/golang-set" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul/api" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul/api" ) // bindRoute contains the main logic for binding a route to a given gateway. @@ -104,14 +105,6 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section var result bindResults for _, listener := range listenersFor(&r.config.Gateway, ref.SectionName) { - if !canReferenceGateway(route, ref, r.config.Resources) { - result = append(result, bindResult{ - section: listener.Name, - err: errRefNotPermitted, - }) - continue - } - if !routeKindIsAllowedForListener(supportedKindsForProtocol[listener.Protocol], groupKind) { result = append(result, bindResult{ section: listener.Name, @@ -384,16 +377,6 @@ func getRouteBackends(object client.Object) []gwv1beta1.BackendRef { return nil } -func canReferenceGateway(object client.Object, ref gwv1beta1.ParentReference, resources *common.ResourceMap) bool { - switch v := object.(type) { - case *gwv1beta1.HTTPRoute: - return resources.HTTPRouteCanReferenceGateway(*v, ref) - case *gwv1alpha2.TCPRoute: - return resources.TCPRouteCanReferenceGateway(*v, ref) - } - return false -} - func canReferenceBackend(object client.Object, ref gwv1beta1.BackendRef, resources *common.ResourceMap) bool { switch v := object.(type) { case *gwv1beta1.HTTPRoute: diff --git a/control-plane/api-gateway/common/resources.go b/control-plane/api-gateway/common/resources.go index a8f36502d3..d412c01eee 100644 --- a/control-plane/api-gateway/common/resources.go +++ b/control-plane/api-gateway/common/resources.go @@ -6,13 +6,14 @@ package common import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" ) // ConsulUpdateOperation is an operation representing an @@ -61,9 +62,7 @@ func (k *KubernetesUpdates) Operations() []client.Object { type ReferenceValidator interface { GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool - HTTPRouteCanReferenceGateway(httproute gwv1beta1.HTTPRoute, parentRef gwv1beta1.ParentReference) bool HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool - TCPRouteCanReferenceGateway(tcpRoute gwv1alpha2.TCPRoute, parentRef gwv1beta1.ParentReference) bool TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool } @@ -606,14 +605,6 @@ func (s *ResourceMap) HTTPRouteCanReferenceBackend(route gwv1beta1.HTTPRoute, re return s.referenceValidator.HTTPRouteCanReferenceBackend(route, ref) } -func (s *ResourceMap) HTTPRouteCanReferenceGateway(route gwv1beta1.HTTPRoute, ref gwv1beta1.ParentReference) bool { - return s.referenceValidator.HTTPRouteCanReferenceGateway(route, ref) -} - func (s *ResourceMap) TCPRouteCanReferenceBackend(route gwv1alpha2.TCPRoute, ref gwv1beta1.BackendRef) bool { return s.referenceValidator.TCPRouteCanReferenceBackend(route, ref) } - -func (s *ResourceMap) TCPRouteCanReferenceGateway(route gwv1alpha2.TCPRoute, ref gwv1beta1.ParentReference) bool { - return s.referenceValidator.TCPRouteCanReferenceGateway(route, ref) -} diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index 029e4affa7..2c735ad4ac 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -22,6 +22,7 @@ import ( gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul/api" @@ -32,15 +33,11 @@ type fakeReferenceValidator struct{} func (v fakeReferenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool { return true } -func (v fakeReferenceValidator) HTTPRouteCanReferenceGateway(httproute gwv1beta1.HTTPRoute, parentRef gwv1beta1.ParentReference) bool { - return true -} + func (v fakeReferenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { return true } -func (v fakeReferenceValidator) TCPRouteCanReferenceGateway(tcpRoute gwv1alpha2.TCPRoute, parentRef gwv1beta1.ParentReference) bool { - return true -} + func (v fakeReferenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { return true } From 31269554d83424ebffb611f6ece781c9fddce40f Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 6 Jun 2023 21:17:47 -0400 Subject: [PATCH 209/592] [API Gateway] Add partition test (#2278) * Add partition test * drop superfluous sprintf * fix linter issue on acceptance test * Add predicated watch for pods --- .../bases/api-gateway/kustomization.yaml | 3 +- .../bases/api-gateway/meshservice.yaml | 9 + .../api-gateways/mesh/kustomization.yaml | 5 + .../api-gateways/mesh/proxydefaults.yaml | 12 + .../api-gateways/resolver/kustomization.yaml | 5 + .../resolver/serviceresolver.yaml | 12 + .../partitions/partitions_gateway_test.go | 340 ++++++++++++++++++ control-plane/api-gateway/cache/consul.go | 152 ++++++-- control-plane/api-gateway/common/labels.go | 16 +- .../controllers/gateway_controller.go | 42 ++- .../api-gateway/gatekeeper/dataplane.go | 3 - control-plane/api-gateway/gatekeeper/init.go | 5 - .../subcommand/inject-connect/command.go | 2 +- 13 files changed, 551 insertions(+), 55 deletions(-) create mode 100644 acceptance/tests/fixtures/bases/api-gateway/meshservice.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/mesh/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/mesh/proxydefaults.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/resolver/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/resolver/serviceresolver.yaml create mode 100644 acceptance/tests/partitions/partitions_gateway_test.go diff --git a/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml b/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml index 2049f1af0b..e2125414d9 100644 --- a/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml @@ -5,4 +5,5 @@ resources: - gatewayclassconfig.yaml - gatewayclass.yaml - apigateway.yaml - - httproute.yaml \ No newline at end of file + - httproute.yaml + - meshservice.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway/meshservice.yaml b/acceptance/tests/fixtures/bases/api-gateway/meshservice.yaml new file mode 100644 index 0000000000..4c32452bc3 --- /dev/null +++ b/acceptance/tests/fixtures/bases/api-gateway/meshservice.yaml @@ -0,0 +1,9 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: MeshService +metadata: + name: mesh-service +spec: + name: static-server \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/mesh/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/mesh/kustomization.yaml new file mode 100644 index 0000000000..c271e6af8b --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/mesh/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - proxydefaults.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/mesh/proxydefaults.yaml b/acceptance/tests/fixtures/cases/api-gateways/mesh/proxydefaults.yaml new file mode 100644 index 0000000000..ccc0905e32 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/mesh/proxydefaults.yaml @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ProxyDefaults +metadata: + name: global +spec: + config: + protocol: http + meshGateway: + mode: local \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/resolver/kustomization.yaml new file mode 100644 index 0000000000..cdbcd688c0 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/resolver/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - serviceresolver.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/resolver/serviceresolver.yaml b/acceptance/tests/fixtures/cases/api-gateways/resolver/serviceresolver.yaml new file mode 100644 index 0000000000..18960a37db --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/resolver/serviceresolver.yaml @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: static-server +spec: + redirect: + partition: default + namespace: ns1 + service: static-server \ No newline at end of file diff --git a/acceptance/tests/partitions/partitions_gateway_test.go b/acceptance/tests/partitions/partitions_gateway_test.go new file mode 100644 index 0000000000..06bc933ce8 --- /dev/null +++ b/acceptance/tests/partitions/partitions_gateway_test.go @@ -0,0 +1,340 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package partitions + +import ( + "context" + "fmt" + "strconv" + "testing" + "time" + + terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/types" + + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// Test that Gateway works in a default and ACLsEnabled installations for X-Partition and in-partition networking. +func TestPartitions_Gateway(t *testing.T) { + env := suite.Environment() + cfg := suite.Config() + + if !cfg.EnableEnterprise { + t.Skipf("skipping this test because -enable-enterprise is not set") + } + + const defaultPartition = "default" + const secondaryPartition = "secondary" + + defaultPartitionClusterContext := env.DefaultContext(t) + secondaryPartitionClusterContext := env.Context(t, environment.SecondaryContextName) + + commonHelmValues := map[string]string{ + "global.adminPartitions.enabled": "true", + "global.enableConsulNamespaces": "true", + "global.logLevel": "debug", + + "global.tls.enabled": "true", + "global.tls.httpsOnly": "true", + + "global.acls.manageSystemACLs": "true", + + "connectInject.enabled": "true", + // When mirroringK8S is set, this setting is ignored. + "connectInject.consulNamespaces.consulDestinationNamespace": staticServerNamespace, + "connectInject.consulNamespaces.mirroringK8S": "true", + + "meshGateway.enabled": "true", + "meshGateway.replicas": "1", + + "dns.enabled": "true", + "dns.enableRedirection": strconv.FormatBool(cfg.EnableTransparentProxy), + } + + defaultPartitionHelmValues := make(map[string]string) + + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + defaultPartitionHelmValues["meshGateway.service.type"] = "NodePort" + defaultPartitionHelmValues["meshGateway.service.nodePort"] = "30200" // todo: do we need to set this port? + defaultPartitionHelmValues["server.exposeService.type"] = "NodePort" + defaultPartitionHelmValues["server.exposeService.nodePort.https"] = "30000" + defaultPartitionHelmValues["server.exposeService.nodePort.grpc"] = "30100" + } + + releaseName := helpers.RandomName() + + helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) + + // Install the consul cluster with servers in the default kubernetes context. + serverConsulCluster := consul.NewHelmCluster(t, defaultPartitionHelmValues, defaultPartitionClusterContext, cfg, releaseName) + serverConsulCluster.Create(t) + + // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. + caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) + + logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) + k8s.CopySecret(t, defaultPartitionClusterContext, secondaryPartitionClusterContext, caCertSecretName) + + partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) + logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) + k8s.CopySecret(t, defaultPartitionClusterContext, secondaryPartitionClusterContext, partitionToken) + + partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) + partitionSvcAddress := k8s.ServiceHost(t, cfg, defaultPartitionClusterContext, partitionServiceName) + + k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryPartitionClusterContext) + + // Create client cluster. + secondaryPartitionHelmValues := map[string]string{ + "global.enabled": "false", + + "global.adminPartitions.name": secondaryPartition, + + "global.tls.caCert.secretName": caCertSecretName, + "global.tls.caCert.secretKey": "tls.crt", + + "externalServers.enabled": "true", + "externalServers.hosts[0]": partitionSvcAddress, + "externalServers.tlsServerName": "server.dc1.consul", + } + + // Setup partition token and auth method host since ACLs enabled. + secondaryPartitionHelmValues["global.acls.bootstrapToken.secretName"] = partitionToken + secondaryPartitionHelmValues["global.acls.bootstrapToken.secretKey"] = "token" + secondaryPartitionHelmValues["externalServers.k8sAuthMethodHost"] = k8sAuthMethodHost + + if cfg.UseKind { + secondaryPartitionHelmValues["externalServers.httpsPort"] = "30000" + secondaryPartitionHelmValues["externalServers.grpcPort"] = "30100" + secondaryPartitionHelmValues["meshGateway.service.type"] = "NodePort" + secondaryPartitionHelmValues["meshGateway.service.nodePort"] = "30200" + } + + helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) + + // Install the consul cluster without servers in the client cluster kubernetes context. + clientConsulCluster := consul.NewHelmCluster(t, secondaryPartitionHelmValues, secondaryPartitionClusterContext, cfg, releaseName) + clientConsulCluster.Create(t) + + defaultPartitionClusterStaticServerOpts := &terratestk8s.KubectlOptions{ + ContextName: defaultPartitionClusterContext.KubectlOptions(t).ContextName, + ConfigPath: defaultPartitionClusterContext.KubectlOptions(t).ConfigPath, + Namespace: staticServerNamespace, + } + secondaryPartitionClusterStaticServerOpts := &terratestk8s.KubectlOptions{ + ContextName: secondaryPartitionClusterContext.KubectlOptions(t).ContextName, + ConfigPath: secondaryPartitionClusterContext.KubectlOptions(t).ConfigPath, + Namespace: staticServerNamespace, + } + secondaryPartitionClusterStaticClientOpts := &terratestk8s.KubectlOptions{ + ContextName: secondaryPartitionClusterContext.KubectlOptions(t).ContextName, + ConfigPath: secondaryPartitionClusterContext.KubectlOptions(t).ConfigPath, + Namespace: StaticClientNamespace, + } + + logger.Logf(t, "creating namespaces %s and %s in servers cluster", staticServerNamespace, StaticClientNamespace) + k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) + k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) + }) + + logger.Logf(t, "creating namespaces %s and %s in clients cluster", staticServerNamespace, StaticClientNamespace) + k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) + k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) + }) + + consulClient, _ := serverConsulCluster.SetupConsulClient(t, true) + + serverQueryServerOpts := &api.QueryOptions{Namespace: staticServerNamespace, Partition: defaultPartition} + clientQueryServerOpts := &api.QueryOptions{Namespace: StaticClientNamespace, Partition: defaultPartition} + + serverQueryClientOpts := &api.QueryOptions{Namespace: staticServerNamespace, Partition: secondaryPartition} + clientQueryClientOpts := &api.QueryOptions{Namespace: StaticClientNamespace, Partition: secondaryPartition} + + // We need to register the cleanup function before we create the deployments + // because golang will execute them in reverse order i.e. the last registered + // cleanup function will be executed first. + t.Cleanup(func() { + retry.Run(t, func(r *retry.R) { + tokens, _, err := consulClient.ACL().TokenList(serverQueryServerOpts) + require.NoError(r, err) + for _, token := range tokens { + require.NotContains(r, token.Description, staticServerName) + } + + tokens, _, err = consulClient.ACL().TokenList(clientQueryServerOpts) + require.NoError(r, err) + for _, token := range tokens { + require.NotContains(r, token.Description, StaticClientName) + } + tokens, _, err = consulClient.ACL().TokenList(serverQueryClientOpts) + require.NoError(r, err) + for _, token := range tokens { + require.NotContains(r, token.Description, staticServerName) + } + + tokens, _, err = consulClient.ACL().TokenList(clientQueryClientOpts) + require.NoError(r, err) + for _, token := range tokens { + require.NotContains(r, token.Description, StaticClientName) + } + }) + }) + + // Create a ProxyDefaults resource to configure services to use the mesh + // gateways. + logger.Log(t, "creating proxy-defaults config") + kustomizeDir := "../fixtures/cases/api-gateways/mesh" + + k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) + }) + + k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) + }) + + // We use the static-client pod so that we can make calls to the api gateway + // via kubectl exec without needing a route into the cluster from the test machine. + // Since we're deploying the gateway in the secondary cluster, we create the static client + // in the secondary as well. + logger.Log(t, "creating static-client pod in secondary partition cluster") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + + logger.Log(t, "creating api-gateway resources") + out, err := k8s.RunKubectlAndGetOutputE(t, secondaryPartitionClusterStaticServerOpts, "apply", "-k", "../fixtures/bases/api-gateway") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, secondaryPartitionClusterStaticServerOpts, "delete", "-k", "../fixtures/bases/api-gateway") + }) + + // Grab a kubernetes client so that we can verify binding + // behavior prior to issuing requests through the gateway. + k8sClient := secondaryPartitionClusterContext.ControllerRuntimeClient(t) + + // On startup, the controller can take upwards of 1m to perform + // leader election so we may need to wait a long time for + // the reconcile loop to run (hence the 1m timeout here). + var gatewayAddress string + counter := &retry.Counter{Count: 600, Wait: 2 * time.Second} + retry.RunWith(counter, t, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: staticServerNamespace}, &gateway) + require.NoError(r, err) + + // check that we have an address to use + require.Len(r, gateway.Status.Addresses, 1) + // now we know we have an address, set it so we can use it + gatewayAddress = gateway.Status.Addresses[0].Value + }) + + targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) + + // This section of the tests runs the in-partition networking tests. + t.Run("in-partition", func(t *testing.T) { + logger.Log(t, "test in-partition networking") + logger.Log(t, "creating target server in secondary partition cluster") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + logger.Log(t, "patching route to target server") + k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") + + logger.Log(t, "checking that the connection is not successful because there's no intention") + k8s.CheckStaticServerHTTPConnectionFailing(t, secondaryPartitionClusterStaticClientOpts, StaticClientName, targetAddress) + + intention := &api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: staticServerName, + Namespace: staticServerNamespace, + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Namespace: staticServerNamespace, + Action: api.IntentionActionAllow, + }, + }, + } + + logger.Log(t, "creating intention") + _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: secondaryPartition}) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: secondaryPartition}) + require.NoError(t, err) + }) + + logger.Log(t, "checking that connection is successful") + k8s.CheckStaticServerConnectionSuccessful(t, secondaryPartitionClusterStaticClientOpts, StaticClientName, targetAddress) + }) + + // This section of the tests runs the cross-partition networking tests. + t.Run("cross-partition", func(t *testing.T) { + logger.Log(t, "test cross-partition networking") + + logger.Log(t, "creating target server in default partition cluster") + k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + logger.Log(t, "creating exported services") + k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") + }) + + logger.Log(t, "creating local service resolver") + k8s.KubectlApplyK(t, secondaryPartitionClusterStaticServerOpts, "../fixtures/cases/api-gateways/resolver") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, secondaryPartitionClusterStaticServerOpts, "../fixtures/cases/api-gateways/resolver") + }) + + logger.Log(t, "patching route to target server") + k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") + + logger.Log(t, "checking that the connection is not successful because there's no intention") + k8s.CheckStaticServerHTTPConnectionFailing(t, secondaryPartitionClusterStaticClientOpts, StaticClientName, targetAddress) + + intention := &api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: staticServerName, + Namespace: staticServerNamespace, + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Namespace: staticServerNamespace, + Action: api.IntentionActionAllow, + Partition: secondaryPartition, + }, + }, + } + + logger.Log(t, "creating intention") + _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: defaultPartition}) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: defaultPartition}) + require.NoError(t, err) + }) + + logger.Log(t, "checking that connection is successful") + k8s.CheckStaticServerConnectionSuccessful(t, secondaryPartitionClusterStaticClientOpts, StaticClientName, targetAddress) + }) +} diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index 9acfb2b1ee..e47df71522 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -4,10 +4,12 @@ package cache import ( + "bytes" "context" "fmt" "strings" "sync" + "text/template" "time" "github.com/go-logr/logr" @@ -20,6 +22,33 @@ import ( "github.com/hashicorp/consul/api" ) +func init() { + gatewayTpl = template.Must(template.New("root").Parse(strings.TrimSpace(gatewayRulesTpl))) +} + +type templateArgs struct { + EnableNamespaces bool +} + +var ( + gatewayTpl *template.Template + gatewayRulesTpl = ` +mesh = "read" +{{- if .EnableNamespaces }} + namespace_prefix "" { +{{- end }} + node_prefix "" { + policy = "read" + } + service_prefix "" { + policy = "write" + } +{{- if .EnableNamespaces }} + } +{{- end }} +` +) + const ( namespaceWildcard = "*" apiTimeout = 5 * time.Minute @@ -282,6 +311,66 @@ func (c *Cache) Write(ctx context.Context, entry api.ConfigEntry) error { return nil } +func (c *Cache) ensurePolicy(client *api.Client) (string, error) { + policy := c.gatewayPolicy() + + created, _, err := client.ACL().PolicyCreate(&policy, &api.WriteOptions{}) + + if isPolicyExistsErr(err, policy.Name) { + existing, _, err := client.ACL().PolicyReadByName(policy.Name, &api.QueryOptions{}) + if err != nil { + return "", err + } + return existing.ID, nil + } + if err != nil { + return "", err + } + return created.ID, nil +} + +func (c *Cache) ensureRole(client *api.Client) (string, error) { + policyID, err := c.ensurePolicy(client) + if err != nil { + return "", err + } + + aclRoleName := "managed-gateway-acl-role" + aclRole, _, err := client.ACL().RoleReadByName(aclRoleName, &api.QueryOptions{}) + if err != nil { + return "", err + } + if aclRole != nil { + return aclRoleName, nil + } + + role := &api.ACLRole{ + Name: aclRoleName, + Description: "ACL Role for Managed API Gateways", + Policies: []*api.ACLLink{{ID: policyID}}, + } + + _, _, err = client.ACL().RoleCreate(role, &api.WriteOptions{}) + return aclRoleName, err +} + +func (c *Cache) gatewayPolicy() api.ACLPolicy { + var data bytes.Buffer + if err := gatewayTpl.Execute(&data, templateArgs{ + EnableNamespaces: c.namespacesEnabled, + }); err != nil { + // just panic if we can't compile the simple template + // as it means something else is going severly wrong. + panic(err) + } + + return api.ACLPolicy{ + Name: "api-gateway-token-policy", + Description: "API Gateway token Policy", + Rules: data.String(), + } +} + // Get returns a config entry from the cache that corresponds to the given resource reference. func (c *Cache) Get(ref api.ResourceReference) api.ConfigEntry { c.cacheMutex.Lock() @@ -335,56 +424,42 @@ func (c *Cache) List(kind string) []api.ConfigEntry { return refMap.Entries() } -// LinkPolicy links a mesh write policy to a token associated with the service. -func (c *Cache) LinkPolicy(ctx context.Context, name, namespace string) error { +func (c *Cache) EnsureRoleBinding(authMethod, service, namespace string) error { client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) if err != nil { return err } - options := &api.QueryOptions{} - - if c.namespacesEnabled { - options.Namespace = namespace - } - - policies, _, err := client.ACL().PolicyList(options.WithContext(ctx)) + role, err := c.ensureRole(client) if err != nil { return ignoreACLsDisabled(err) } - links := []*api.ACLLink{} - for _, policy := range policies { - if strings.HasPrefix(policy.Name, "connect-inject-policy") { - links = append(links, &api.ACLLink{ - Name: policy.Name, - }) - } + bindingRule := &api.ACLBindingRule{ + Description: fmt.Sprintf("Binding Rule for %s/%s", namespace, service), + AuthMethod: authMethod, + Selector: fmt.Sprintf("serviceaccount.name==%q and serviceaccount.namespace==%q", service, namespace), + BindType: api.BindingRuleBindTypeRole, + BindName: role, } - tokens, _, err := client.ACL().TokenList(options.WithContext(ctx)) + existingRules, _, err := client.ACL().BindingRuleList(authMethod, &api.QueryOptions{}) if err != nil { - return ignoreACLsDisabled(err) + return err } - for _, token := range tokens { - for _, identity := range token.ServiceIdentities { - if identity.ServiceName == name { - token, _, err := client.ACL().TokenRead(token.AccessorID, options.WithContext(ctx)) - if err != nil { - return ignoreACLsDisabled(err) - } - token.Policies = links - - _, _, err = client.ACL().TokenUpdate(token, &api.WriteOptions{}) - if err != nil { - return ignoreACLsDisabled(err) - } - } + for _, existingRule := range existingRules { + if existingRule.BindName == bindingRule.BindName && existingRule.Description == bindingRule.Description { + bindingRule.ID = existingRule.ID } } - return nil + if bindingRule.ID == "" { + _, _, err := client.ACL().BindingRuleCreate(bindingRule, &api.WriteOptions{}) + return err + } + _, _, err = client.ACL().BindingRuleUpdate(bindingRule, &api.WriteOptions{}) + return err } // Register registers a service in Consul. @@ -414,8 +489,19 @@ func (c *Cache) Deregister(ctx context.Context, deregistration api.CatalogDeregi } func ignoreACLsDisabled(err error) error { + if err == nil { + return nil + } if err.Error() == "Unexpected response code: 401 (ACL support disabled)" { return nil } return err } + +// isPolicyExistsErr returns true if err is due to trying to call the +// policy create API when the policy already exists. +func isPolicyExistsErr(err error, policyName string) bool { + return err != nil && + strings.Contains(err.Error(), "Unexpected response code: 500") && + strings.Contains(err.Error(), fmt.Sprintf("Invalid Policy: A Policy with Name %q already exists", policyName)) +} diff --git a/control-plane/api-gateway/common/labels.go b/control-plane/api-gateway/common/labels.go index 3ab7eaf164..cba13a603e 100644 --- a/control-plane/api-gateway/common/labels.go +++ b/control-plane/api-gateway/common/labels.go @@ -6,6 +6,8 @@ package common import ( "fmt" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -13,7 +15,7 @@ const ( nameLabel = "gateway.consul.hashicorp.com/name" namespaceLabel = "gateway.consul.hashicorp.com/namespace" createdAtLabel = "gateway.consul.hashicorp.com/created" - managedLabel = "gateway.consul.hashicorp.com/managed" + ManagedLabel = "gateway.consul.hashicorp.com/managed" ) // LabelsForGateway formats the default labels that appear on objects managed by the controllers. @@ -22,6 +24,16 @@ func LabelsForGateway(gateway *gwv1beta1.Gateway) map[string]string { nameLabel: gateway.Name, namespaceLabel: gateway.Namespace, createdAtLabel: fmt.Sprintf("%d", gateway.CreationTimestamp.Unix()), - managedLabel: "true", + ManagedLabel: "true", } } + +func GatewayFromPod(pod *corev1.Pod) (types.NamespacedName, bool) { + if pod.Labels[ManagedLabel] == "true" { + return types.NamespacedName{ + Name: pod.Labels[nameLabel], + Namespace: pod.Labels[namespaceLabel], + }, true + } + return types.NamespacedName{}, false +} diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index 80c8e83977..ab2b6af1a5 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -20,8 +20,10 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -188,6 +190,11 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct updates := binder.Snapshot() if updates.UpsertGatewayDeployment { + if err := r.cache.EnsureRoleBinding(r.HelmConfig.AuthMethod, gateway.Name, gateway.Namespace); err != nil { + log.Error(err, "error linking token policy") + return ctrl.Result{}, err + } + err := r.updateGatekeeperResources(ctx, log, &gateway, updates.GatewayClassConfig) if err != nil { log.Error(err, "unable to update gateway resources") @@ -259,16 +266,6 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct } } - // link up policy - TODO: this is really a nasty hack to inject a known policy with - // mesh == read on the provisioned gateway token if needed, figure out some other - // way of handling it. - if updates.UpsertGatewayDeployment { - if err := r.cache.LinkPolicy(ctx, nonNormalizedConsulKey.Name, nonNormalizedConsulKey.Namespace); err != nil { - log.Error(err, "error linking token policy") - return ctrl.Result{}, err - } - } - return ctrl.Result{}, nil } @@ -326,6 +323,12 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co c := cache.New(cacheConfig) gwc := cache.NewGatewayCache(ctx, cacheConfig) + predicate, _ := predicate.LabelSelectorPredicate( + *metav1.SetAsLabelSelector(map[string]string{ + common.ManagedLabel: "true", + }), + ) + r := &GatewayController{ Client: mgr.GetClient(), Log: mgr.GetLogger(), @@ -376,6 +379,11 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co source.NewKindWithCache(&corev1.Endpoints{}, mgr.GetCache()), handler.EnqueueRequestsFromMapFunc(r.transformEndpoints(ctx)), ). + Watches( + &source.Kind{Type: &corev1.Pod{}}, + handler.EnqueueRequestsFromMapFunc(r.transformPods(ctx)), + builder.WithPredicates(predicate), + ). Watches( // Subscribe to changes from Consul for APIGateways &source.Channel{Source: c.Subscribe(ctx, api.APIGateway, r.transformConsulGateway).Events()}, @@ -565,6 +573,20 @@ func gatewayReferencesCertificate(certificateKey api.ResourceReference, gateway return false } +func (r *GatewayController) transformPods(ctx context.Context) func(o client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + pod := o.(*corev1.Pod) + + if gateway, managed := common.GatewayFromPod(pod); managed { + return []reconcile.Request{ + {NamespacedName: gateway}, + } + } + + return nil + } +} + // transformEndpoints will return a list of gateways that are referenced // by a TCPRoute or HTTPRoute that references the service. func (r *GatewayController) transformEndpoints(ctx context.Context) func(o client.Object) []reconcile.Request { diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go index 36829e2b7c..f82e12e8a4 100644 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ b/control-plane/api-gateway/gatekeeper/dataplane.go @@ -142,9 +142,6 @@ func getDataplaneArgs(namespace string, config common.HelmConfig, bearerTokenFil "-login-bearer-token-path="+bearerTokenFile, "-login-meta="+fmt.Sprintf("gateway=%s/%s", namespace, name), ) - if config.EnableNamespaces { - args = append(args, "-login-namespace="+consulNamespace) - } if config.ConsulPartition != "" { args = append(args, "-login-partition="+config.ConsulPartition) } diff --git a/control-plane/api-gateway/gatekeeper/init.go b/control-plane/api-gateway/gatekeeper/init.go index 831380cb52..35360b7f87 100644 --- a/control-plane/api-gateway/gatekeeper/init.go +++ b/control-plane/api-gateway/gatekeeper/init.go @@ -147,11 +147,6 @@ func initContainer(config common.HelmConfig, name, namespace string) (corev1.Con Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", }) - container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_LOGIN_NAMESPACE", - Value: consulNamespace, - }) - if config.ConsulPartition != "" { container.Env = append(container.Env, corev1.EnvVar{ Name: "CONSUL_LOGIN_PARTITION", diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 9e99bd1a03..a4fcf7c99d 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -500,7 +500,7 @@ func (c *Command) Run(args []string) int { PeeringEnabled: c.flagEnablePeering, EnableOpenShift: c.flagEnableOpenShift, EnableNamespaceMirroring: c.flagEnableK8SNSMirroring, - AuthMethod: c.flagACLAuthMethod, + AuthMethod: c.consul.ConsulLogin.AuthMethod, LogLevel: c.flagLogLevel, LogJSON: c.flagLogJSON, TLSEnabled: c.consul.UseTLS, From 644e02ea206fa4d28dcba34712fce00246ef655c Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 6 Jun 2023 21:18:45 -0400 Subject: [PATCH 210/592] Update memory defaults for connect inject controller (#2249) * Update memory defaults for connect inject controllers * Add changelog entry * Bump up Consul server statefulset memory defaults too --- .changelog/2249.txt | 3 +++ .../consul/test/unit/connect-inject-deployment.bats | 2 +- charts/consul/test/unit/server-statefulset.bats | 2 +- charts/consul/values.yaml | 12 ++++++------ 4 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 .changelog/2249.txt diff --git a/.changelog/2249.txt b/.changelog/2249.txt new file mode 100644 index 0000000000..9c6a50e098 --- /dev/null +++ b/.changelog/2249.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. +``` diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 26e3038759..c1bc63ffc3 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -960,7 +960,7 @@ load _helpers --set 'connectInject.enabled=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"}}' ] + [ "${actual}" = '{"limits":{"cpu":"50m","memory":"200Mi"},"requests":{"cpu":"50m","memory":"200Mi"}}' ] } @test "connectInject/Deployment: can set resources" { diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 29621187ab..108fd9bbf8 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -99,7 +99,7 @@ load _helpers -s templates/server-statefulset.yaml \ . | tee /dev/stderr | yq -rc '.spec.template.spec.containers[0].resources' | tee /dev/stderr) - [ "${actual}" = '{"limits":{"cpu":"100m","memory":"100Mi"},"requests":{"cpu":"100m","memory":"100Mi"}}' ] + [ "${actual}" = '{"limits":{"cpu":"100m","memory":"200Mi"},"requests":{"cpu":"100m","memory":"200Mi"}}' ] } @test "server/StatefulSet: resources can be overridden" { diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index c6c225b3d0..0e325ca66c 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -808,10 +808,10 @@ server: # ```yaml # resources: # requests: - # memory: '100Mi' + # memory: '200Mi' # cpu: '100m' # limits: - # memory: '100Mi' + # memory: '200Mi' # cpu: '100m' # ``` # @@ -819,10 +819,10 @@ server: # @type: map resources: requests: - memory: "100Mi" + memory: "200Mi" cpu: "100m" limits: - memory: "100Mi" + memory: "200Mi" cpu: "100m" # The security context for the server pods. This should be a YAML map corresponding to a @@ -2283,14 +2283,14 @@ connectInject: requests: # Recommended production default: 500Mi # @type: string - memory: "50Mi" + memory: "200Mi" # Recommended production default: 250m # @type: string cpu: "50m" limits: # Recommended production default: 500Mi # @type: string - memory: "50Mi" + memory: "200Mi" # Recommended production default: 250m # @type: string cpu: "50m" From 3c565586ffd0c467b14771107d023c9a42854cfe Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Wed, 7 Jun 2023 10:47:35 -0700 Subject: [PATCH 211/592] Mw/fix pipeline 1 1 6 (#2282) * update eks and aks to use latest kubernetes version * updated the terraform provider as some fields were deprecated --- charts/consul/test/terraform/aks/main.tf | 11 +++++++++-- charts/consul/test/terraform/eks/main.tf | 15 +++++++++++---- charts/consul/test/terraform/gke/main.tf | 9 ++++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/charts/consul/test/terraform/aks/main.tf b/charts/consul/test/terraform/aks/main.tf index 8cf72a142c..bf8c925f15 100644 --- a/charts/consul/test/terraform/aks/main.tf +++ b/charts/consul/test/terraform/aks/main.tf @@ -1,8 +1,15 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +terraform { + required_providers { + azurerm = { + version = "3.40.0" + } + } +} + provider "azurerm" { - version = "3.40.0" features {} } @@ -48,7 +55,7 @@ resource "azurerm_kubernetes_cluster" "default" { location = azurerm_resource_group.default[count.index].location resource_group_name = azurerm_resource_group.default[count.index].name dns_prefix = "consul-k8s-${random_id.suffix[count.index].dec}" - kubernetes_version = "1.24.6" + kubernetes_version = "1.26" role_based_access_control_enabled = true // We're setting the network plugin and other network properties explicitly diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index 6301202161..efbab0e833 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -1,9 +1,16 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +terraform { + required_providers { + aws = { + version = ">= 4.0.0" + } + } +} + provider "aws" { - version = ">= 2.28.1" - region = var.region + region = var.region assume_role { role_arn = var.role_arn @@ -28,7 +35,7 @@ resource "random_string" "suffix" { module "vpc" { count = var.cluster_count source = "terraform-aws-modules/vpc/aws" - version = "3.11.0" + version = "4.0.0" name = "consul-k8s-${random_id.suffix[count.index].dec}" # The cidr range needs to be unique in each VPC to allow setting up a peering connection. @@ -61,7 +68,7 @@ module "eks" { kubeconfig_api_version = "client.authentication.k8s.io/v1beta1" cluster_name = "consul-k8s-${random_id.suffix[count.index].dec}" - cluster_version = "1.23" + cluster_version = "1.26" subnets = module.vpc[count.index].private_subnets enable_irsa = true diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index 98a063d450..fe5adc5e8d 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -1,9 +1,16 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +terraform { + required_providers { + google = { + version = "~> 4.58.0" + } + } +} + provider "google" { project = var.project - version = "~> 4.58.0" zone = var.zone } From 57fef1fc81f5620c9518a1a6bc082a4ff4342213 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Wed, 7 Jun 2023 22:51:02 -0400 Subject: [PATCH 212/592] Add bug to changelog so that go-changelog works (#2276) --- .changelog/2194.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/2194.txt b/.changelog/2194.txt index 997326218b..fb265d9739 100644 --- a/.changelog/2194.txt +++ b/.changelog/2194.txt @@ -1,3 +1,3 @@ -```release-note: +```release-note:bug crd: fix bug on service intentions CRD causing some updates to be ignored. ``` From e35eaa3cf48fb0264269b65ca196e1ed2792aee4 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Thu, 8 Jun 2023 12:14:35 -0400 Subject: [PATCH 213/592] Fix retry loops that use `t` (#2311) --- acceptance/tests/cli/cli_install_test.go | 2 +- .../config_entries_namespaces_test.go | 58 +++++++++---------- .../config-entries/config_entries_test.go | 58 +++++++++---------- .../create-federation-secret/command_test.go | 6 +- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index 009d3140fc..bb497f913f 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -74,7 +74,7 @@ func TestInstall(t *testing.T) { retry.RunWith(retrier, t, func(r *retry.R) { for podName := range list { out, err := cli.Run(t, ctx.KubectlOptions(t), "proxy", "read", podName) - require.NoError(t, err) + require.NoError(r, err) output := string(out) logger.Log(t, output) diff --git a/acceptance/tests/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index ced7cc8236..91d0c69df4 100644 --- a/acceptance/tests/config-entries/config_entries_namespaces_test.go +++ b/acceptance/tests/config-entries/config_entries_namespaces_test.go @@ -242,35 +242,35 @@ func TestControllerNamespaces(t *testing.T) { require.NoError(r, err) rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) require.True(r, ok, "could not cast to RateLimitIPConfigEntry") - require.Equal(t, "permissive", rateLimitIPConfigEntry.Mode) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ACL.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ACL.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Catalog.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Catalog.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ConfigEntry.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ConfigEntry.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ConnectCA.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ConnectCA.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Coordinate.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Coordinate.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.DiscoveryChain.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.DiscoveryChain.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Health.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Health.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Intention.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Intention.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.KV.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.KV.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) - //require.Equal(t, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) - //require.Equal(t, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Session.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Session.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Txn.WriteRate) + require.Equal(r, "permissive", rateLimitIPConfigEntry.Mode) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ACL.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ACL.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Catalog.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Catalog.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ConfigEntry.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ConfigEntry.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ConnectCA.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ConnectCA.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Coordinate.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Coordinate.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.DiscoveryChain.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.DiscoveryChain.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Health.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Health.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Intention.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Intention.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) + //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) + //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.WriteRate) }) } diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index 089f96767f..e37e3d6c7f 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -209,35 +209,35 @@ func TestController(t *testing.T) { require.NoError(r, err) rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) require.True(r, ok, "could not cast to RateLimitIPConfigEntry") - require.Equal(t, "permissive", rateLimitIPConfigEntry.Mode) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ACL.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ACL.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Catalog.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Catalog.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ConfigEntry.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ConfigEntry.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ConnectCA.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.ConnectCA.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Coordinate.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Coordinate.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.DiscoveryChain.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.DiscoveryChain.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Health.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Health.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Intention.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Intention.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.KV.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.KV.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) - //require.Equal(t, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) - //require.Equal(t, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Session.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Session.WriteRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) - require.Equal(t, 100.0, rateLimitIPConfigEntry.Txn.WriteRate, 100.0) + require.Equal(r, "permissive", rateLimitIPConfigEntry.Mode) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ACL.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ACL.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Catalog.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Catalog.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ConfigEntry.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ConfigEntry.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ConnectCA.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.ConnectCA.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Coordinate.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Coordinate.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.DiscoveryChain.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.DiscoveryChain.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Health.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Health.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Intention.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Intention.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) + //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) + //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.WriteRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) + require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.WriteRate) }) } diff --git a/control-plane/subcommand/create-federation-secret/command_test.go b/control-plane/subcommand/create-federation-secret/command_test.go index 16939a2868..15f12b132c 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -528,7 +528,7 @@ func TestRun_WaitsForMeshGatewayInstances(t *testing.T) { CAFile: caFile, }, }) - require.NoError(t, err) + require.NoError(r, err) }) err = client.Agent().ServiceRegister(&api.AgentServiceRegistration{ @@ -825,7 +825,7 @@ func TestRun_ReplicationSecretDelay(t *testing.T) { }, }, metav1.CreateOptions{}) - require.NoError(t, err) + require.NoError(r, err) }) }() @@ -1005,7 +1005,7 @@ func TestRun_ConsulClientDelay(t *testing.T) { Server: randomPorts[5], } }) - require.NoError(t, err) + require.NoError(r, err) }) // Construct Consul client. From f4435acf0020c1f6bda25f875660055116ef155a Mon Sep 17 00:00:00 2001 From: skpratt Date: Thu, 8 Jun 2023 13:28:55 -0500 Subject: [PATCH 214/592] Add FIPS builds (#2165) * Add FIPS builds for linux amd64 * add version check * fix CI labels and add local dev commands * fix ci version tagging * switch to ubuntu 20.04 * add CLI version tag * add gcompat for alpine glibc cgo compatibility * remove FIPS version check from connect-init * address comments --- .github/workflows/build.yml | 92 +++++++++++++------ Makefile | 15 +++ cli/version/fips_build.go | 27 ++++++ cli/version/non_fips_build.go | 12 +++ cli/version/version.go | 6 +- control-plane/Dockerfile | 6 +- .../build-support/functions/20-build.sh | 14 ++- .../build-support/scripts/build-local.sh | 7 ++ .../subcommand/connect-init/command.go | 23 ++++- control-plane/version/fips_build.go | 27 ++++++ control-plane/version/non_fips_build.go | 12 +++ control-plane/version/version.go | 6 +- 12 files changed, 210 insertions(+), 37 deletions(-) create mode 100644 cli/version/fips_build.go create mode 100644 cli/version/non_fips_build.go create mode 100644 control-plane/version/fips_build.go create mode 100644 control-plane/version/non_fips_build.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 52f94ec71c..8eef629401 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,7 +64,7 @@ jobs: build: needs: [get-go-version, get-product-version] - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 # the GLIBC is too high on 22.04 strategy: matrix: include: @@ -79,20 +79,28 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - # control-plane + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } + + # control-plane - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - # solaris is only built for the control plane + # solaris is only built for the control plane - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "solaris", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - # consul-cni + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } + + # consul-cni - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "386", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } @@ -104,10 +112,14 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } + fail-fast: true - name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} build + name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} ${{ matrix.fips }} build steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 @@ -116,6 +128,25 @@ jobs: with: go-version: ${{ matrix.go }} + - name: Replace Go for Windows FIPS with Microsoft Go + if: ${{ matrix.fips == '.fips1402' && matrix.goos == 'windows' }} + run: | + # Uninstall standard Go and use microsoft/go instead + rm -rf /home/runner/actions-runner/_work/_tool/go + curl https://aka.ms/golang/release/latest/go${{ matrix.go }}-1.linux-amd64.tar.gz -Lo go${{ matrix.go }}.linux-amd64.tar.gz + tar -C $HOME -xf go${{ matrix.go }}.linux-amd64.tar.gz + chmod +x $HOME/go/bin + export PATH=$HOME/go/bin:$PATH + if [ $(which go) != "$HOME/go/bin/go" ]; then + echo "Unable to verify microsoft/go toolchain" + exit 1 + fi + + - name: Install cross-compiler for FIPS on arm + if: ${{ matrix.fips == '.fips1402' && matrix.goarch == 'arm64' }} + run: | + sudo apt-get update --allow-releaseinfo-change-suite --allow-releaseinfo-change-version && sudo apt-get install -y gcc-aarch64-linux-gnu + - name: Build env: GOOS: ${{ matrix.goos }} @@ -130,14 +161,14 @@ jobs: export GIT_IMPORT=github.com/hashicorp/consul-k8s/${{ matrix.component }}/version export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X ${GIT_IMPORT}.GitDescribe=${{ needs.get-product-version.outputs.product-version }}" - CGO_ENABLED=0 go build -o dist/${{ matrix.bin_name }} -ldflags "${GOLDFLAGS}" . - zip -r -j out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ + ${{ matrix.env }} go build -o dist/${{ matrix.bin_name }} -ldflags "${GOLDFLAGS}" -tags=${{ matrix.gotags }} . + zip -r -j out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ - name: Upload built binaries uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: - name: ${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip - path: ${{ matrix.component}}/out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip + name: ${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip + path: ${{ matrix.component}}/out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip - name: Package rpm and deb files if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} @@ -146,7 +177,7 @@ jobs: name: consul-k8s description: "consul-k8s provides a cli interface to first-class integrations between Consul and Kubernetes." arch: ${{ matrix.goarch }} - version: ${{ needs.get-product-version.outputs.product-version }} + version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} maintainer: "HashiCorp" homepage: "https://github.com/hashicorp/consul-k8s" license: "MPL-2.0" @@ -171,7 +202,7 @@ jobs: cd /work rpm -ivh out/${{ env.RPM_PACKAGE }} CONSUL_K8S_VERSION="$(consul-k8s version | awk '{print $2}')" - VERSION="v${{ needs.get-product-version.outputs.product-version }}" + VERSION="v${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}" if [ "${VERSION}" != "${CONSUL_K8S_VERSION}" ]; then echo "Test FAILED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" exit 1 @@ -196,7 +227,7 @@ jobs: cd /work apt install ./out/${{ env.DEB_PACKAGE }} CONSUL_K8S_VERSION="$(consul-k8s version | awk '{print $2}')" - VERSION="v${{ needs.get-product-version.outputs.product-version }}" + VERSION="v${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}" if [ "${VERSION}" != "${CONSUL_K8S_VERSION}" ]; then echo "Test FAILED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" exit 1 @@ -211,24 +242,30 @@ jobs: path: out/${{ env.DEB_PACKAGE }} build-docker: - name: Docker ${{ matrix.arch }} default release build + name: Docker ${{ matrix.goarch }} ${{ matrix.fips }} default release build needs: [get-product-version, build] runs-on: ubuntu-latest strategy: matrix: - arch: ["arm", "arm64", "386", "amd64"] + include: + - { goos: "linux", goarch: "arm" } + - { goos: "linux", goarch: "arm64" } + - { goos: "linux", goarch: "386" } + - { goos: "linux", goarch: "amd64" } + - { goos: "linux", goarch: "amd64", fips: ".fips1402" } + - { goos: "linux", goarch: "arm64", fips: ".fips1402" } env: repo: ${{ github.event.repository.name }} - version: ${{ needs.get-product-version.outputs.product-version }} + version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: - name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip - path: control-plane/dist/cni/linux/${{ matrix.arch }} + name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos}}_${{ matrix.goarch }}.zip + path: control-plane/dist/cni/${{ matrix.goos}}/${{ matrix.goarch }} - name: extract consul-cni zip env: - ZIP_LOCATION: control-plane/dist/cni/linux/${{ matrix.arch }} + ZIP_LOCATION: control-plane/dist/cni/${{ matrix.goos}}/${{ matrix.goarch }} run: | cd "${ZIP_LOCATION}" unzip -j *.zip @@ -244,7 +281,7 @@ jobs: echo "Test PASSED" version: ${{ env.version }} target: release-default - arch: ${{ matrix.arch }} + arch: ${{ matrix.goarch }} pkg_name: consul-k8s-control-plane_${{ env.version }} bin_name: consul-k8s-control-plane workdir: control-plane @@ -255,20 +292,22 @@ jobs: docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-${{ github.sha }} build-docker-ubi-redhat-registry: - name: Docker ${{ matrix.arch }} UBI build for RedHat Registry + name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI build for RedHat Registry needs: [get-product-version, build] runs-on: ubuntu-latest strategy: matrix: - arch: ["amd64"] + include: + - { arch: "amd64" } + - { arch: "amd64", fips: ".fips1402" } env: repo: ${{ github.event.repository.name }} - version: ${{ needs.get-product-version.outputs.product-version }} + version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: - name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip + name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_linux_${{ matrix.arch }}.zip path: control-plane/dist/cni/linux/${{ matrix.arch }} - name: extract consul-cni zip env: @@ -297,20 +336,21 @@ jobs: redhat_tag: quay.io/redhat-isv-containers/611ca2f89a9b407267837100:${{env.version}}-ubi build-docker-ubi-dockerhub: - name: Docker ${{ matrix.arch }} UBI build for DockerHub + name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI build for DockerHub needs: [ get-product-version, build ] runs-on: ubuntu-latest strategy: matrix: arch: [ "amd64" ] + fips: [ ".fips1402", "" ] env: repo: ${{ github.event.repository.name }} - version: ${{ needs.get-product-version.outputs.product-version }} + version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: - name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip + name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_linux_${{ matrix.arch }}.zip path: control-plane/dist/cni/linux/${{ matrix.arch }} - name: extract consul-cni zip env: diff --git a/Makefile b/Makefile index 5adfb55657..628b13e2f9 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,17 @@ control-plane-dev-docker-multi-arch: check-remote-dev-image-env ## Build consul- --push \ -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane +control-plane-fips-dev-docker: ## Build consul-k8s-control-plane FIPS dev Docker image. + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) --fips + @docker build -t '$(DEV_IMAGE)' \ + --target=dev \ + --build-arg 'TARGETARCH=$(GOARCH)' \ + --build-arg 'GIT_COMMIT=$(GIT_COMMIT)' \ + --build-arg 'GIT_DIRTY=$(GIT_DIRTY)' \ + --build-arg 'GIT_DESCRIBE=$(GIT_DESCRIBE)' \ + --push \ + -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane + control-plane-test: ## Run go test for the control plane. cd control-plane; go test ./... @@ -98,6 +109,10 @@ cli-dev: @echo "==> Installing consul-k8s CLI tool for ${GOOS}/${GOARCH}" @cd cli; go build -o ./bin/consul-k8s; cp ./bin/consul-k8s ${GOPATH}/bin/ +cli-fips-dev: + @echo "==> Installing consul-k8s CLI tool for ${GOOS}/${GOARCH}" + @cd cli; CGO_ENABLED=1 GOEXPERIMENT=boringcrypto go build -o ./bin/consul-k8s -tags "fips"; cp ./bin/consul-k8s ${GOPATH}/bin/ + cli-lint: ## Run linter in the control-plane directory. cd cli; golangci-lint run -c ../.golangci.yml diff --git a/cli/version/fips_build.go b/cli/version/fips_build.go new file mode 100644 index 0000000000..4d04cc6539 --- /dev/null +++ b/cli/version/fips_build.go @@ -0,0 +1,27 @@ +//go:build fips + +package version + +// This validates during compilation that we are being built with a FIPS enabled go toolchain +import ( + _ "crypto/tls/fipsonly" + "runtime" + "strings" +) + +// IsFIPS returns true if consul-k8s is operating in FIPS-140-2 mode. +func IsFIPS() bool { + return true +} + +func GetFIPSInfo() string { + str := "Enabled" + // Try to get the crypto module name + gover := strings.Split(runtime.Version(), "X:") + if len(gover) >= 2 { + gover_last := gover[len(gover)-1] + // Able to find crypto module name; add that to status string. + str = "FIPS 140-2 Enabled, crypto module " + gover_last + } + return str +} diff --git a/cli/version/non_fips_build.go b/cli/version/non_fips_build.go new file mode 100644 index 0000000000..f72aecae73 --- /dev/null +++ b/cli/version/non_fips_build.go @@ -0,0 +1,12 @@ +//go:build !fips + +package version + +// IsFIPS returns true if consul-k8s is operating in FIPS-140-2 mode. +func IsFIPS() bool { + return false +} + +func GetFIPSInfo() string { + return "" +} diff --git a/cli/version/version.go b/cli/version/version.go index 81433c0a5f..320600c30b 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -39,8 +39,12 @@ func GetHumanVersion() string { release = "dev" } + if IsFIPS() { + version += ".fips1402" + } + if release != "" { - if !strings.HasSuffix(version, "-"+release) { + if !strings.Contains(version, "-"+release) { // if we tagged a prerelease version then the release is in the version already version += fmt.Sprintf("-%s", release) } diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index de0c1cf1ff..5b3d73e625 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -92,7 +92,11 @@ LABEL name=${BIN_NAME} \ ENV BIN_NAME=${BIN_NAME} ENV VERSION=${PRODUCT_VERSION} -RUN apk add --no-cache ca-certificates libcap openssl su-exec iputils libc6-compat iptables +RUN apk add --no-cache ca-certificates libcap openssl su-exec iputils gcompat libc6-compat libstdc++ iptables + +# for FIPS CGO glibc compatibility in alpine +# see https://github.com/golang/go/issues/59305 +RUN ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 # TARGETOS and TARGETARCH are set automatically when --platform is provided. ARG TARGETOS diff --git a/control-plane/build-support/functions/20-build.sh b/control-plane/build-support/functions/20-build.sh index a4f36ee3e4..e9540956c9 100644 --- a/control-plane/build-support/functions/20-build.sh +++ b/control-plane/build-support/functions/20-build.sh @@ -180,7 +180,7 @@ function build_consul_local { # * - error # # Note: - # The GOLDFLAGS and GOTAGS environment variables will be used if set + # The GOLDFLAGS, GOEXPERIMENT, and GOTAGS environment variables will be used if set # If the CONSUL_DEV environment var is truthy only the local platform/architecture is built. # If the XC_OS or the XC_ARCH environment vars are present then only those platforms/architectures # will be built. Otherwise all supported platform/architectures are built @@ -188,6 +188,14 @@ function build_consul_local { # build with go install. # The GOXPARALLEL environment variable is used if set + if [ $GOTAGS == "fips" ]; then + CGO_ENABLED=1 + else + CGO_ENABLED=0 + fi + + echo "GOEXPERIMENT: $GOEXPERIMENT, GOTAGS: $GOTAGS CGO_ENABLED: $CGO_ENABLED" >> ~/debug.txt + if ! test -d "$1" then err "ERROR: '$1' is not a directory. build_consul must be called with the path to the top level source as the first argument'" @@ -242,7 +250,7 @@ function build_consul_local { then status "Using gox for concurrent compilation" - CGO_ENABLED=0 gox \ + CGO_ENABLED=${CGO_ENABLED} GOEXPERIMENT=${GOEXPERIMENT} gox \ -os="${build_os}" \ -arch="${build_arch}" \ -ldflags="${GOLDFLAGS}" \ @@ -290,7 +298,7 @@ function build_consul_local { else OS_BIN_EXTENSION="" fi - CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go build -ldflags "${GOLDFLAGS}" -tags "${GOTAGS}" -o "${outdir}/${bin_name}" + CGO_ENABLED=${CGO_ENABLED} GOEXPERIMENT=${GOEXPERIMENT} GOOS=${os} GOARCH=${arch} go build -ldflags "${GOLDFLAGS}" -tags "${GOTAGS}" -o "${outdir}/${bin_name}" if test $? -ne 0 then err "ERROR: Failed to build Consul for ${osarch}" diff --git a/control-plane/build-support/scripts/build-local.sh b/control-plane/build-support/scripts/build-local.sh index 453310b0b7..7325e025b7 100755 --- a/control-plane/build-support/scripts/build-local.sh +++ b/control-plane/build-support/scripts/build-local.sh @@ -35,6 +35,8 @@ Options: -a | --arch ARCH Space separated string of architectures to build. + --fips FIPS Whether to use FIPS cryptography. + -h | --help Print this help text. EOF } @@ -94,6 +96,11 @@ function main { build_arch="$2" shift 2 ;; + --fips ) + GOTAGS="fips" + GOEXPERIMENT="boringcrypto" + shift 1 + ;; * ) err_usage "ERROR: Unknown argument: '$1'" return 1 diff --git a/control-plane/subcommand/connect-init/command.go b/control-plane/subcommand/connect-init/command.go index 72090d299b..4f83ea98f1 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -17,17 +17,19 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/iptables" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" "github.com/mitchellh/mapstructure" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul-k8s/control-plane/version" ) const ( @@ -161,6 +163,17 @@ func (c *Command) Run(args []string) int { c.logger.Error("Unable to get client connection", "error", err) return 1 } + if version.IsFIPS() { + // make sure we are also using FIPS Consul + var versionInfo map[string]interface{} + _, err := consulClient.Raw().Query("/v1/agent/version", versionInfo, nil) + if err != nil { + c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. Unable to verify FIPS Consul while setting up Consul API client.") + } + if val, ok := versionInfo["FIPS"]; !ok || val == "" { + c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. A non-FIPS version of Consul was detected.") + } + } proxyService := &api.AgentService{} if c.flagGatewayKind != "" { err = backoff.Retry(c.getGatewayRegistration(consulClient), backoff.WithMaxRetries(backoff.NewConstantBackOff(1*time.Second), c.serviceRegistrationPollingAttempts)) diff --git a/control-plane/version/fips_build.go b/control-plane/version/fips_build.go new file mode 100644 index 0000000000..4d04cc6539 --- /dev/null +++ b/control-plane/version/fips_build.go @@ -0,0 +1,27 @@ +//go:build fips + +package version + +// This validates during compilation that we are being built with a FIPS enabled go toolchain +import ( + _ "crypto/tls/fipsonly" + "runtime" + "strings" +) + +// IsFIPS returns true if consul-k8s is operating in FIPS-140-2 mode. +func IsFIPS() bool { + return true +} + +func GetFIPSInfo() string { + str := "Enabled" + // Try to get the crypto module name + gover := strings.Split(runtime.Version(), "X:") + if len(gover) >= 2 { + gover_last := gover[len(gover)-1] + // Able to find crypto module name; add that to status string. + str = "FIPS 140-2 Enabled, crypto module " + gover_last + } + return str +} diff --git a/control-plane/version/non_fips_build.go b/control-plane/version/non_fips_build.go new file mode 100644 index 0000000000..f72aecae73 --- /dev/null +++ b/control-plane/version/non_fips_build.go @@ -0,0 +1,12 @@ +//go:build !fips + +package version + +// IsFIPS returns true if consul-k8s is operating in FIPS-140-2 mode. +func IsFIPS() bool { + return false +} + +func GetFIPSInfo() string { + return "" +} diff --git a/control-plane/version/version.go b/control-plane/version/version.go index 81433c0a5f..320600c30b 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -39,8 +39,12 @@ func GetHumanVersion() string { release = "dev" } + if IsFIPS() { + version += ".fips1402" + } + if release != "" { - if !strings.HasSuffix(version, "-"+release) { + if !strings.Contains(version, "-"+release) { // if we tagged a prerelease version then the release is in the version already version += fmt.Sprintf("-%s", release) } From 097f94550903f6ab41e14e7a47a0f12f97cfeee3 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 8 Jun 2023 12:56:03 -0700 Subject: [PATCH 215/592] activated weekly acceptance tests for 1-2-x (#2315) - making this trigger nightly until after 1.2.0 GA - leaving 0.49.x active until after 1.2.0 GA --- .github/workflows/weekly-acceptance-1-2-x.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/weekly-acceptance-1-2-x.yml diff --git a/.github/workflows/weekly-acceptance-1-2-x.yml b/.github/workflows/weekly-acceptance-1-2-x.yml new file mode 100644 index 0000000000..353a086f16 --- /dev/null +++ b/.github/workflows/weekly-acceptance-1-2-x.yml @@ -0,0 +1,30 @@ +# 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-2-x +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST + # - cron: '0 3 * * 3' + - cron: '0 0 * * *' # Temporarily nightly until 1.2.0 GA + + +# these should be the only settings that you will ever need to change +env: + BRANCH: "release/1.2.x" + CONTEXT: "weekly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + 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 }}" }' From 61c72805622502bd13e461457a03acec40b2d1b2 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Thu, 8 Jun 2023 23:30:46 -0400 Subject: [PATCH 216/592] Net 4230/add tcp to basic acceptance test (#2297) * first run through, needs help * still need to make secure pass * left something uncommented * it works and also cleanup * fix acceptance tests --- acceptance/go.mod | 10 -- acceptance/go.sum | 17 ---- .../api-gateway/api_gateway_tenancy_test.go | 2 +- .../tests/api-gateway/api_gateway_test.go | 95 +++++++++++++++++-- .../anyuid-scc-rolebinding.yaml | 14 +++ .../bases/static-server-tcp/deployment.yaml | 49 ++++++++++ .../static-server-tcp/kustomization.yaml | 11 +++ .../privileged-scc-rolebinding.yaml | 14 +++ .../static-server-tcp/psp-rolebinding.yaml | 14 +++ .../bases/static-server-tcp/service.yaml | 15 +++ .../static-server-tcp/serviceaccount.yaml | 7 ++ .../static-server-tcp/servicedefaults.yaml | 7 ++ .../cases/api-gateways/tcproute/route.yaml | 14 +++ .../controllers/gateway_controller.go | 2 +- 14 files changed, 233 insertions(+), 38 deletions(-) create mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/anyuid-scc-rolebinding.yaml create mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/deployment.yaml create mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/privileged-scc-rolebinding.yaml create mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml create mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/service.yaml create mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/serviceaccount.yaml create mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/tcproute/route.yaml diff --git a/acceptance/go.mod b/acceptance/go.mod index a63e1187fe..e2221a09c0 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -26,18 +26,14 @@ require ( github.com/aws/aws-sdk-go v1.44.262 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect - github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deckarep/golang-set v1.7.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.2.3 // indirect @@ -46,7 +42,6 @@ require ( github.com/go-openapi/swag v0.22.3 // indirect github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect @@ -54,15 +49,12 @@ require ( github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect - github.com/hashicorp/consul-server-connection-manager v0.1.2 // indirect - github.com/hashicorp/consul/proto-public v0.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.2.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-netaddrs v0.1.0 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-retryablehttp v0.6.6 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect @@ -105,7 +97,6 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect github.com/urfave/cli v1.22.2 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.1.0 // indirect @@ -126,7 +117,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.26.3 // indirect k8s.io/component-base v0.26.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 1c9bd2ad25..77a5b5875a 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -104,12 +104,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -147,8 +143,6 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= -github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= @@ -195,7 +189,6 @@ github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -254,7 +247,6 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -341,12 +333,8 @@ github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+Xb github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb h1:9GUvDoKVoV3IW78QyfoNY4bRcKxcn26wTGLoBrz92N4= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb/go.mod h1:jKzTEgDc/np2gX/KPdfdm1mEUfZLrU8gc71XN3B15VI= -github.com/hashicorp/consul-server-connection-manager v0.1.2 h1:tNVQHUPuMbd+cMdD8kd+qkZUYpmLmrHMAV/49f4L53I= -github.com/hashicorp/consul-server-connection-manager v0.1.2/go.mod h1:NzQoVi1KcxGI2SangsDue8+ZPuXZWs+6BKAKrDNyg+w= github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 h1:6kUTk+YBgA5X5b3gNAoI18WEK4/t75LcWSimEgmpFdg= github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= -github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= -github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -370,8 +358,6 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-netaddrs v0.1.0 h1:TnlYvODD4C/wO+j7cX1z69kV5gOzI87u3OcUinANaW8= -github.com/hashicorp/go-netaddrs v0.1.0/go.mod h1:33+a/emi5R5dqRspOuZKO0E+Tuz5WV1F84eRWALkedA= github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= @@ -680,7 +666,6 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -872,7 +857,6 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1104,7 +1088,6 @@ k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= -k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go index 2f0005da80..e7748b9226 100644 --- a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go +++ b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go @@ -288,7 +288,7 @@ type certificateInfo struct { func generateCertificate(t *testing.T, ca *certificateInfo, commonName string) *certificateInfo { t.Helper() - bits := 1024 + bits := 2048 privateKey, err := rsa.GenerateKey(rand.Reader, bits) require.NoError(t, err) diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index 2291587bcc..17234cadf1 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -5,7 +5,9 @@ package apigateway import ( "context" + "encoding/base64" "fmt" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "strconv" "testing" "time" @@ -79,12 +81,39 @@ func TestAPIGateway_Basic(t *testing.T) { k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") }) - logger.Log(t, "creating target server") + // Create certificate secret, we do this separately since + // applying the secret will make an invalid certificate that breaks other tests + logger.Log(t, "creating certificate secret") + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/bases/api-gateway/certificate.yaml") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/bases/api-gateway/certificate.yaml") + }) + + // patch certificate with data + logger.Log(t, "patching certificate secret with generated data") + certificate := generateCertificate(t, nil, "gateway.test.local") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") + + logger.Log(t, "creating target http server") k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - logger.Log(t, "patching route to target server") + logger.Log(t, "patching route to target http server") k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"name":"static-server","port":80}]}]}}`, "--type=merge") + logger.Log(t, "creating target tcp server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server-tcp") + + logger.Log(t, "creating tcp-route") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/cases/api-gateways/tcproute/route.yaml") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/cases/api-gateways/tcproute/route.yaml") + }) + // We use the static-client pod so that we can make calls to the api gateway // via kubectl exec without needing a route into the cluster from the test machine. logger.Log(t, "creating static-client pod") @@ -112,18 +141,19 @@ func TestAPIGateway_Basic(t *testing.T) { checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) require.Len(r, gateway.Status.Listeners, 3) + require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, 0, gateway.Status.Listeners[1].AttachedRoutes) + require.EqualValues(r, 1, gateway.Status.Listeners[1].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) require.EqualValues(r, 1, gateway.Status.Listeners[2].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, falseCondition("ResolvedRefs", "InvalidCertificateRef")) + checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) // check that we have an address to use require.Len(r, gateway.Status.Addresses, 1) @@ -160,6 +190,23 @@ func TestAPIGateway_Basic(t *testing.T) { checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + // tcp route checks + var tcpRoute gwv1alpha2.TCPRoute + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "tcp-route", Namespace: "default"}, &tcpRoute) + require.NoError(t, err) + + // check our finalizers + require.Len(t, tcpRoute.Finalizers, 1) + require.EqualValues(t, gatewayFinalizer, tcpRoute.Finalizers[0]) + + // check parent status + require.Len(t, tcpRoute.Status.Parents, 1) + require.EqualValues(t, gatewayClassControllerName, tcpRoute.Status.Parents[0].ControllerName) + require.EqualValues(t, "gateway", tcpRoute.Status.Parents[0].ParentRef.Name) + checkStatusCondition(t, tcpRoute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(t, tcpRoute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(t, tcpRoute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + // check that the Consul entries were created entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) require.NoError(t, err) @@ -167,21 +214,32 @@ func TestAPIGateway_Basic(t *testing.T) { entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) require.NoError(t, err) - route := entry.(*api.HTTPRouteConfigEntry) + httpRoute := entry.(*api.HTTPRouteConfigEntry) + + entry, _, err = consulClient.ConfigEntries().Get(api.TCPRoute, "tcp-route", nil) + require.NoError(t, err) + route := entry.(*api.TCPRouteConfigEntry) // now check the gateway status conditions checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) // and the route status conditions + checkConsulStatusCondition(t, httpRoute.Status.Conditions, trueConsulCondition("Bound", "Bound")) checkConsulStatusCondition(t, route.Status.Conditions, trueConsulCondition("Bound", "Bound")) // finally we check that we can actually route to the service via the gateway k8sOptions := ctx.KubectlOptions(t) - targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) + targetHTTPAddress := fmt.Sprintf("http://%s", gatewayAddress) + targetHTTPSAddress := fmt.Sprintf("https://%s", gatewayAddress) + targetTCPAddress := fmt.Sprintf("http://%s:81", gatewayAddress) if c.secure { // check that intentions keep our connection from happening - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetAddress) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddress) + + k8s.CheckStaticServerConnectionFailing(t, k8sOptions, StaticClientName, targetTCPAddress) + + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-k", targetHTTPSAddress) // Now we create the allow intention. _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ @@ -195,12 +253,31 @@ func TestAPIGateway_Basic(t *testing.T) { }, }, nil) require.NoError(t, err) + + // Now we create the allow intention tcp. + _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: "static-server-tcp", + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Action: api.IntentionActionAllow, + }, + }, + }, nil) + require.NoError(t, err) } // Test that we can make a call to the api gateway // via the static-client pod. It should route to the static-server pod. - logger.Log(t, "trying calls to api gateway") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetAddress) + logger.Log(t, "trying calls to api gateway http") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) + + logger.Log(t, "trying calls to api gateway tcp") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetTCPAddress) + + logger.Log(t, "trying calls to api gateway https") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPSAddress, "-k") }) } } diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/anyuid-scc-rolebinding.yaml new file mode 100644 index 0000000000..eb86dc8bae --- /dev/null +++ b/acceptance/tests/fixtures/bases/static-server-tcp/anyuid-scc-rolebinding.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: static-server-tcp-openshift-anyuid +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:anyuid +subjects: + - kind: ServiceAccount + name: static-server-tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/deployment.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/deployment.yaml new file mode 100644 index 0000000000..9aa5177e9e --- /dev/null +++ b/acceptance/tests/fixtures/bases/static-server-tcp/deployment.yaml @@ -0,0 +1,49 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: static-server-tcp + name: static-server-tcp +spec: + replicas: 1 + selector: + matchLabels: + app: static-server-tcp + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + labels: + app: static-server-tcp + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/kschoche/http-echo:latest + args: + - -text="hello world" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + livenessProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + startupProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 1 + readinessProbe: + exec: + command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + serviceAccountName: static-server-tcp diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/kustomization.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/kustomization.yaml new file mode 100644 index 0000000000..2180aa94e1 --- /dev/null +++ b/acceptance/tests/fixtures/bases/static-server-tcp/kustomization.yaml @@ -0,0 +1,11 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - deployment.yaml + - service.yaml + - serviceaccount.yaml + - servicedefaults.yaml + - psp-rolebinding.yaml + - anyuid-scc-rolebinding.yaml + - privileged-scc-rolebinding.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/privileged-scc-rolebinding.yaml new file mode 100644 index 0000000000..ac28006765 --- /dev/null +++ b/acceptance/tests/fixtures/bases/static-server-tcp/privileged-scc-rolebinding.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: static-server-tcp-openshift-privileged +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: static-server-tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml new file mode 100644 index 0000000000..f4f008dbea --- /dev/null +++ b/acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: static-server-tcp +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: test-psp +subjects: + - kind: ServiceAccount + name: static-server-tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/service.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/service.yaml new file mode 100644 index 0000000000..6ceccf940a --- /dev/null +++ b/acceptance/tests/fixtures/bases/static-server-tcp/service.yaml @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: Service +metadata: + name: static-server-tcp + labels: + app: static-server-tcp +spec: + ports: + - name: http + port: 8080 + selector: + app: static-server-tcp diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/serviceaccount.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/serviceaccount.yaml new file mode 100644 index 0000000000..af2247af8e --- /dev/null +++ b/acceptance/tests/fixtures/bases/static-server-tcp/serviceaccount.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: static-server-tcp diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml new file mode 100644 index 0000000000..500051db87 --- /dev/null +++ b/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml @@ -0,0 +1,7 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: static-server-tcp + namespace: default +spec: + protocol: tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/tcproute/route.yaml b/acceptance/tests/fixtures/cases/api-gateways/tcproute/route.yaml new file mode 100644 index 0000000000..37602c65af --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/tcproute/route.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: tcp-route +spec: + parentRefs: + - name: gateway + rules: + - backendRefs: + - kind: Service + name: static-server-tcp \ No newline at end of file diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index ab2b6af1a5..ec8c2e9af0 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -191,7 +191,7 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct if updates.UpsertGatewayDeployment { if err := r.cache.EnsureRoleBinding(r.HelmConfig.AuthMethod, gateway.Name, gateway.Namespace); err != nil { - log.Error(err, "error linking token policy") + log.Error(err, "error creating role binding") return ctrl.Result{}, err } From 555d4a64a498c2f7462f3659c3858f2430f5c8e3 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 9 Jun 2023 00:31:40 -0400 Subject: [PATCH 217/592] [API Gateway] Add acceptance test for cluster peering (#2306) * [API Gateway] Add acceptance test for cluster peering * Fix linter * Fix random unrelated linter errors to get CI to run: revert later? * one more linter fix to later probably revert * more linter fixes * Revert "more linter fixes" This reverts commit 6210dff0e51bbcf2f754f6d666c08292ba958aaa. * Revert "one more linter fix to later probably revert" This reverts commit 030c563bbe0b0a9ef73b33cbea32464416156d8f. * Revert "Fix random unrelated linter errors to get CI to run: revert later?" This reverts commit fdeccabb2f6c4418168cad9be5b2459435b7e30b. --- .../peer-resolver/kustomization.yaml | 5 + .../peer-resolver/serviceresolver.yaml | 12 + .../tests/peering/peering_gateway_test.go | 291 ++++++++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 acceptance/tests/fixtures/cases/api-gateways/peer-resolver/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/peer-resolver/serviceresolver.yaml create mode 100644 acceptance/tests/peering/peering_gateway_test.go diff --git a/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/kustomization.yaml new file mode 100644 index 0000000000..cdbcd688c0 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - serviceresolver.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/serviceresolver.yaml b/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/serviceresolver.yaml new file mode 100644 index 0000000000..20874fe1f9 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/serviceresolver.yaml @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: static-server +spec: + redirect: + peer: server + namespace: ns1 + service: static-server \ No newline at end of file diff --git a/acceptance/tests/peering/peering_gateway_test.go b/acceptance/tests/peering/peering_gateway_test.go new file mode 100644 index 0000000000..76698102e6 --- /dev/null +++ b/acceptance/tests/peering/peering_gateway_test.go @@ -0,0 +1,291 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package peering + +import ( + "context" + "fmt" + "testing" + "time" + + terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/go-version" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/types" + + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestPeering_Gateway(t *testing.T) { + env := suite.Environment() + cfg := suite.Config() + + if !cfg.EnableEnterprise { + t.Skipf("skipping this test because -enable-enterprise is not set") + } + + ver, err := version.NewVersion("1.13.0") + require.NoError(t, err) + if cfg.ConsulVersion != nil && cfg.ConsulVersion.LessThan(ver) { + t.Skipf("skipping this test because peering is not supported in version %v", cfg.ConsulVersion.String()) + } + + const staticServerPeer = "server" + const staticClientPeer = "client" + + staticServerPeerClusterContext := env.DefaultContext(t) + staticClientPeerClusterContext := env.Context(t, environment.SecondaryContextName) + + commonHelmValues := map[string]string{ + "global.peering.enabled": "true", + "global.enableConsulNamespaces": "true", + + "global.tls.enabled": "true", + "global.tls.httpsOnly": "true", + + "global.acls.manageSystemACLs": "true", + + "connectInject.enabled": "true", + + // When mirroringK8S is set, this setting is ignored. + "connectInject.consulNamespaces.mirroringK8S": "true", + + "meshGateway.enabled": "true", + "meshGateway.replicas": "1", + + "dns.enabled": "true", + } + + staticServerPeerHelmValues := map[string]string{ + "global.datacenter": staticServerPeer, + } + + if !cfg.UseKind { + staticServerPeerHelmValues["server.replicas"] = "3" + } + + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } + + releaseName := helpers.RandomName() + + helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) + + // Install the first peer where static-server will be deployed in the static-server kubernetes context. + staticServerPeerCluster := consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) + staticServerPeerCluster.Create(t) + + staticClientPeerHelmValues := map[string]string{ + "global.datacenter": staticClientPeer, + } + + if !cfg.UseKind { + staticClientPeerHelmValues["server.replicas"] = "3" + } + + if cfg.UseKind { + staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } + + helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) + + // Install the second peer where static-client will be deployed in the static-client kubernetes context. + staticClientPeerCluster := consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) + staticClientPeerCluster.Create(t) + + // Create Mesh resource to use mesh gateways. + logger.Log(t, "creating mesh config") + kustomizeMeshDir := "../fixtures/bases/mesh-peering" + + k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) + }) + + k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) + }) + + staticServerPeerClient, _ := staticServerPeerCluster.SetupConsulClient(t, true) + staticClientPeerClient, _ := staticClientPeerCluster.SetupConsulClient(t, true) + + // Ensure mesh config entries are created in Consul. + timer := &retry.Timer{Timeout: 1 * time.Minute, Wait: 1 * time.Second} + retry.RunWith(timer, t, func(r *retry.R) { + ceServer, _, err := staticServerPeerClient.ConfigEntries().Get(api.MeshConfig, "mesh", &api.QueryOptions{}) + require.NoError(r, err) + configEntryServer, ok := ceServer.(*api.MeshConfigEntry) + require.True(r, ok) + require.Equal(r, configEntryServer.GetName(), "mesh") + require.NoError(r, err) + + ceClient, _, err := staticClientPeerClient.ConfigEntries().Get(api.MeshConfig, "mesh", &api.QueryOptions{}) + require.NoError(r, err) + configEntryClient, ok := ceClient.(*api.MeshConfigEntry) + require.True(r, ok) + require.Equal(r, configEntryClient.GetName(), "mesh") + require.NoError(r, err) + }) + + // Create the peering acceptor on the client peer. + k8s.KubectlApply(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDelete(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") + }) + + // Ensure the secret is created. + retry.RunWith(timer, t, func(r *retry.R) { + acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(t, staticClientPeerClusterContext.KubectlOptions(t), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") + require.NoError(r, err) + require.NotEmpty(r, acceptorSecretName) + }) + + // Copy secret from client peer to server peer. + k8s.CopySecret(t, staticClientPeerClusterContext, staticServerPeerClusterContext, "api-token") + + // Create the peering dialer on the server peer. + k8s.KubectlApply(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "secret", "api-token") + k8s.KubectlDelete(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") + }) + + staticServerOpts := &terratestk8s.KubectlOptions{ + ContextName: staticServerPeerClusterContext.KubectlOptions(t).ContextName, + ConfigPath: staticServerPeerClusterContext.KubectlOptions(t).ConfigPath, + Namespace: staticServerNamespace, + } + staticClientOpts := &terratestk8s.KubectlOptions{ + ContextName: staticClientPeerClusterContext.KubectlOptions(t).ContextName, + ConfigPath: staticClientPeerClusterContext.KubectlOptions(t).ConfigPath, + Namespace: staticClientNamespace, + } + + logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) + k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) + }) + + logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) + k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) + }) + + // Create a ProxyDefaults resource to configure services to use the mesh + // gateways. + logger.Log(t, "creating proxy-defaults config") + kustomizeDir := "../fixtures/cases/api-gateways/mesh" + + k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) + }) + + k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) + }) + + // We use the static-client pod so that we can make calls to the api gateway + // via kubectl exec without needing a route into the cluster from the test machine. + // Since we're deploying the gateway in the secondary cluster, we create the static client + // in the secondary as well. + logger.Log(t, "creating static-client pod in client peer") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/non-default-namespace") + + logger.Log(t, "creating static-server in server peer") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + logger.Log(t, "creating exported services") + k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") + }) + + logger.Log(t, "creating api-gateway resources in client peer") + out, err := k8s.RunKubectlAndGetOutputE(t, staticClientOpts, "apply", "-k", "../fixtures/bases/api-gateway") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, staticClientOpts, "delete", "-k", "../fixtures/bases/api-gateway") + }) + + // Grab a kubernetes client so that we can verify binding + // behavior prior to issuing requests through the gateway. + k8sClient := staticClientPeerClusterContext.ControllerRuntimeClient(t) + + // On startup, the controller can take upwards of 1m to perform + // leader election so we may need to wait a long time for + // the reconcile loop to run (hence the 1m timeout here). + var gatewayAddress string + counter := &retry.Counter{Count: 600, Wait: 2 * time.Second} + retry.RunWith(counter, t, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: staticClientNamespace}, &gateway) + require.NoError(r, err) + + // check that we have an address to use + require.Len(r, gateway.Status.Addresses, 1) + // now we know we have an address, set it so we can use it + gatewayAddress = gateway.Status.Addresses[0].Value + }) + + targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) + + logger.Log(t, "creating local service resolver") + k8s.KubectlApplyK(t, staticClientOpts, "../fixtures/cases/api-gateways/peer-resolver") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, staticClientOpts, "../fixtures/cases/api-gateways/peer-resolver") + }) + + logger.Log(t, "patching route to target server") + k8s.RunKubectl(t, staticClientOpts, "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") + + logger.Log(t, "checking that the connection is not successful because there's no intention") + k8s.CheckStaticServerHTTPConnectionFailing(t, staticClientOpts, staticClientName, targetAddress) + + intention := &api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: staticServerName, + Namespace: staticServerNamespace, + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Namespace: staticClientNamespace, + Action: api.IntentionActionAllow, + Peer: staticClientPeer, + }, + }, + } + + logger.Log(t, "creating intention") + _, _, err = staticServerPeerClient.ConfigEntries().Set(intention, &api.WriteOptions{}) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + _, err = staticServerPeerClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{}) + require.NoError(t, err) + }) + + logger.Log(t, "checking that connection is successful") + k8s.CheckStaticServerConnectionSuccessful(t, staticClientOpts, staticClientName, targetAddress) +} From b56b7dd4d7d90189d73a71b6a154a89ddafcd511 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 8 Jun 2023 22:39:08 -0700 Subject: [PATCH 218/592] Mw/net 3598 update kind for consul k8s acceptance tests with latest version of kind and k8s 1.27 (#2304) * update cloud tests to use 1.24, 1.25 and 1.26 version of kubernetes for more coverage * updated readme for supported kubernetes versions * added changelog --- .changelog/2304.txt | 3 +++ README.md | 2 +- charts/consul/test/terraform/aks/main.tf | 2 +- charts/consul/test/terraform/gke/main.tf | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 .changelog/2304.txt diff --git a/.changelog/2304.txt b/.changelog/2304.txt new file mode 100644 index 0000000000..c977da5acd --- /dev/null +++ b/.changelog/2304.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: Kubernetes v1.27 is now supported. Minimum tested version of Kubernetes is now v1.24. +``` diff --git a/README.md b/README.md index 1d3a3733ab..d43a12b455 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). The following pre-requisites must be met before installing Consul on Kubernetes. - * **Kubernetes 1.23.x - 1.26.x** - This represents the earliest versions of Kubernetes tested. + * **Kubernetes 1.24.x - 1.27.x** - This represents the earliest versions of Kubernetes tested. It is possible that this chart works with earlier versions, but it is untested. * Helm install diff --git a/charts/consul/test/terraform/aks/main.tf b/charts/consul/test/terraform/aks/main.tf index bf8c925f15..2683bdc1a7 100644 --- a/charts/consul/test/terraform/aks/main.tf +++ b/charts/consul/test/terraform/aks/main.tf @@ -55,7 +55,7 @@ resource "azurerm_kubernetes_cluster" "default" { location = azurerm_resource_group.default[count.index].location resource_group_name = azurerm_resource_group.default[count.index].name dns_prefix = "consul-k8s-${random_id.suffix[count.index].dec}" - kubernetes_version = "1.26" + kubernetes_version = "1.24.10" role_based_access_control_enabled = true // We're setting the network plugin and other network properties explicitly diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index fe5adc5e8d..34bb07906f 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -21,7 +21,7 @@ resource "random_id" "suffix" { data "google_container_engine_versions" "main" { location = var.zone - version_prefix = "1.25." + version_prefix = "1.25.9" } # We assume that the subnets are already created to save time. From 203c9d19766209460e782f650536795b9b82e668 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 9 Jun 2023 09:53:16 -0400 Subject: [PATCH 219/592] [API Gateway] WAN Federation test and fixes (#2295) * [API Gateway] WAN Federation test and fixes * Fix unit tests --- .../dc1-to-dc2-resolver/kustomization.yaml | 5 + .../dc1-to-dc2-resolver/serviceresolver.yaml | 11 + .../dc2-to-dc1-resolver/kustomization.yaml | 5 + .../dc2-to-dc1-resolver/serviceresolver.yaml | 11 + .../wan_federation_gateway_test.go | 241 ++++++++++++++++++ control-plane/api-gateway/cache/consul.go | 19 ++ .../api-gateway/cache/consul_test.go | 124 ++++++--- .../api-gateway/common/helm_config.go | 24 +- .../api-gateway/common/translation.go | 25 +- .../controllers/gateway_controller.go | 5 +- .../api/v1alpha1/serviceresolver_types.go | 2 +- .../connect-inject/constants/constants.go | 3 + .../subcommand/inject-connect/command.go | 2 + 13 files changed, 427 insertions(+), 50 deletions(-) create mode 100644 acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/serviceresolver.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/serviceresolver.yaml create mode 100644 acceptance/tests/wan-federation/wan_federation_gateway_test.go diff --git a/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/kustomization.yaml new file mode 100644 index 0000000000..cdbcd688c0 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - serviceresolver.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/serviceresolver.yaml b/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/serviceresolver.yaml new file mode 100644 index 0000000000..ca009754b4 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/serviceresolver.yaml @@ -0,0 +1,11 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: static-server +spec: + redirect: + service: static-server + datacenter: dc2 \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/kustomization.yaml new file mode 100644 index 0000000000..cdbcd688c0 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - serviceresolver.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/serviceresolver.yaml b/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/serviceresolver.yaml new file mode 100644 index 0000000000..af8cdb72ed --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/serviceresolver.yaml @@ -0,0 +1,11 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: static-server +spec: + redirect: + service: static-server + datacenter: dc1 \ No newline at end of file diff --git a/acceptance/tests/wan-federation/wan_federation_gateway_test.go b/acceptance/tests/wan-federation/wan_federation_gateway_test.go new file mode 100644 index 0000000000..0ef48b9920 --- /dev/null +++ b/acceptance/tests/wan-federation/wan_federation_gateway_test.go @@ -0,0 +1,241 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package wanfederation + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/serf/testutil/retry" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestWANFederation_Gateway(t *testing.T) { + env := suite.Environment() + cfg := suite.Config() + + if cfg.UseKind { + // the only way this test can currently run on kind, at least on a Mac, is via leveraging MetalLB, which + // isn't in CI, so we just skip for now. + t.Skipf("skipping wan federation tests as they currently fail on Kind even though they work on other clouds.") + } + + primaryContext := env.DefaultContext(t) + secondaryContext := env.Context(t, environment.SecondaryContextName) + + primaryHelmValues := map[string]string{ + "global.datacenter": "dc1", + + "global.tls.enabled": "true", + "global.tls.httpsOnly": "true", + + "global.federation.enabled": "true", + "global.federation.createFederationSecret": "true", + + "global.acls.manageSystemACLs": "true", + "global.acls.createReplicationToken": "true", + + "connectInject.enabled": "true", + "connectInject.replicas": "1", + + "meshGateway.enabled": "true", + "meshGateway.replicas": "1", + } + + releaseName := helpers.RandomName() + + // Install the primary consul cluster in the default kubernetes context + primaryConsulCluster := consul.NewHelmCluster(t, primaryHelmValues, primaryContext, cfg, releaseName) + primaryConsulCluster.Create(t) + + // Get the federation secret from the primary cluster and apply it to secondary cluster + federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) + logger.Logf(t, "retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) + federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) + require.NoError(t, err) + federationSecret.ResourceVersion = "" + _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + var k8sAuthMethodHost string + // When running on kind, the kube API address in kubeconfig will have a localhost address + // which will not work from inside the container. That's why we need to use the endpoints address instead + // which will point the node IP. + if cfg.UseKind { + // The Kubernetes AuthMethod host is read from the endpoints for the Kubernetes service. + kubernetesEndpoint, err := secondaryContext.KubernetesClient(t).CoreV1().Endpoints("default").Get(context.Background(), "kubernetes", metav1.GetOptions{}) + require.NoError(t, err) + k8sAuthMethodHost = fmt.Sprintf("%s:%d", kubernetesEndpoint.Subsets[0].Addresses[0].IP, kubernetesEndpoint.Subsets[0].Ports[0].Port) + } else { + k8sAuthMethodHost = k8s.KubernetesAPIServerHostFromOptions(t, secondaryContext.KubectlOptions(t)) + } + + // Create secondary cluster + secondaryHelmValues := map[string]string{ + "global.datacenter": "dc2", + + "global.tls.enabled": "true", + "global.tls.httpsOnly": "false", + "global.acls.manageSystemACLs": "true", + "global.tls.caCert.secretName": federationSecretName, + "global.tls.caCert.secretKey": "caCert", + "global.tls.caKey.secretName": federationSecretName, + "global.tls.caKey.secretKey": "caKey", + + "global.federation.enabled": "true", + + "server.extraVolumes[0].type": "secret", + "server.extraVolumes[0].name": federationSecretName, + "server.extraVolumes[0].load": "true", + "server.extraVolumes[0].items[0].key": "serverConfigJSON", + "server.extraVolumes[0].items[0].path": "config.json", + + "connectInject.enabled": "true", + "connectInject.replicas": "1", + + "meshGateway.enabled": "true", + "meshGateway.replicas": "1", + + "global.acls.replicationToken.secretName": federationSecretName, + "global.acls.replicationToken.secretKey": "replicationToken", + "global.federation.k8sAuthMethodHost": k8sAuthMethodHost, + "global.federation.primaryDatacenter": "dc1", + } + + // Install the secondary consul cluster in the secondary kubernetes context + secondaryConsulCluster := consul.NewHelmCluster(t, secondaryHelmValues, secondaryContext, cfg, releaseName) + secondaryConsulCluster.Create(t) + + primaryClient, _ := primaryConsulCluster.SetupConsulClient(t, true) + secondaryClient, _ := secondaryConsulCluster.SetupConsulClient(t, true) + + // Verify federation between servers + logger.Log(t, "verifying federation was successful") + helpers.VerifyFederation(t, primaryClient, secondaryClient, releaseName, true) + + // Create a ProxyDefaults resource to configure services to use the mesh + // gateways. + logger.Log(t, "creating proxy-defaults config in dc1") + kustomizeDir := "../fixtures/cases/api-gateways/mesh" + k8s.KubectlApplyK(t, primaryContext.KubectlOptions(t), kustomizeDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, primaryContext.KubectlOptions(t), kustomizeDir) + }) + + // these clients are just there so we can exec in and curl on them. + logger.Log(t, "creating static-client in dc1") + k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + + logger.Log(t, "creating static-client in dc2") + k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + + t.Run("from primary to secondary", func(t *testing.T) { + logger.Log(t, "creating static-server in dc2") + k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + logger.Log(t, "creating api-gateway resources in dc1") + out, err := k8s.RunKubectlAndGetOutputE(t, primaryContext.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, primaryContext.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") + }) + + // create a service resolver for doing cross-dc redirects. + k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc1-to-dc2-resolver") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc1-to-dc2-resolver") + }) + + // patching the route to target a MeshService since we don't have the corresponding Kubernetes service in this + // cluster. + k8s.RunKubectl(t, primaryContext.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") + + checkConnectivity(t, primaryContext, primaryClient) + }) + + t.Run("from secondary to primary", func(t *testing.T) { + // Check that we can connect services over the mesh gateways + logger.Log(t, "creating static-server in dc1") + k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + logger.Log(t, "creating api-gateway resources in dc2") + out, err := k8s.RunKubectlAndGetOutputE(t, secondaryContext.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, secondaryContext.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") + }) + + // create a service resolver for doing cross-dc redirects. + k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc2-to-dc1-resolver") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc2-to-dc1-resolver") + }) + + // patching the route to target a MeshService since we don't have the corresponding Kubernetes service in this + // cluster. + k8s.RunKubectl(t, secondaryContext.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") + + checkConnectivity(t, secondaryContext, primaryClient) + }) +} + +func checkConnectivity(t *testing.T, ctx environment.TestContext, client *api.Client) { + k8sClient := ctx.ControllerRuntimeClient(t) + + // On startup, the controller can take upwards of 1m to perform + // leader election so we may need to wait a long time for + // the reconcile loop to run (hence the 1m timeout here). + var gatewayAddress string + counter := &retry.Counter{Count: 600, Wait: 2 * time.Second} + retry.RunWith(counter, t, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) + require.NoError(r, err) + + // check that we have an address to use + require.Len(r, gateway.Status.Addresses, 1) + // now we know we have an address, set it so we can use it + gatewayAddress = gateway.Status.Addresses[0].Value + }) + + targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) + + logger.Log(t, "checking that the connection is not successful because there's no intention") + k8s.CheckStaticServerHTTPConnectionFailing(t, ctx.KubectlOptions(t), StaticClientName, targetAddress) + + logger.Log(t, "creating intention") + _, _, err := client.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: "static-server", + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Action: api.IntentionActionAllow, + }, + }, + }, nil) + require.NoError(t, err) + defer func() { + _, err := client.ConfigEntries().Delete(api.ServiceIntentions, "static-server", &api.WriteOptions{}) + require.NoError(t, err) + }() + + logger.Log(t, "checking that connection is successful") + k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), StaticClientName, targetAddress) +} diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index e47df71522..7737e80d57 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -17,6 +17,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" @@ -60,6 +61,7 @@ type Config struct { ConsulClientConfig *consul.Config ConsulServerConnMgr consul.ServerConnectionManager NamespacesEnabled bool + Datacenter string CrossNamespaceACLPolicy string Logger logr.Logger } @@ -83,6 +85,8 @@ type Cache struct { synced chan struct{} kinds []string + + datacenter string } func New(config Config) *Cache { @@ -104,6 +108,7 @@ func New(config Config) *Cache { synced: make(chan struct{}, len(Kinds)), logger: config.Logger, crossNamespaceACLPolicy: config.CrossNamespaceACLPolicy, + datacenter: config.Datacenter, } } @@ -216,6 +221,19 @@ func (c *Cache) updateAndNotify(ctx context.Context, once *sync.Once, kind strin cache := common.NewReferenceMap() for _, entry := range entries { + meta := entry.GetMeta() + if meta[constants.MetaKeyKubeName] == "" || meta[constants.MetaKeyDatacenter] != c.datacenter { + // Don't process things that don't belong to us. The main reason + // for this is so that we don't garbage collect config entries that + // are either user-created or that another controller running in a + // federated datacenter creates. While we still allow for competing controllers + // syncing/overriding each other due to conflicting Kubernetes objects in + // two federated clusters (which is what the rest of the controllers also allow + // for), we don't want to delete a config entry just because we don't have + // its corresponding Kubernetes object if we know it belongs to another datacenter. + continue + } + cache.Set(common.EntryToReference(entry), entry) } @@ -336,6 +354,7 @@ func (c *Cache) ensureRole(client *api.Client) (string, error) { } aclRoleName := "managed-gateway-acl-role" + aclRole, _, err := client.ACL().RoleReadByName(aclRoleName, &api.QueryOptions{}) if err != nil { return "", err diff --git a/control-plane/api-gateway/cache/consul_test.go b/control-plane/api-gateway/cache/consul_test.go index 555e13b6c2..59570e532f 100644 --- a/control-plane/api-gateway/cache/consul_test.go +++ b/control-plane/api-gateway/cache/consul_test.go @@ -22,6 +22,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" @@ -119,8 +120,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, })[api.HTTPRoute], args: args{ @@ -203,8 +206,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, })[api.HTTPRoute], }, @@ -291,8 +296,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, @@ -372,8 +379,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, })[api.HTTPRoute], args: args{ @@ -456,8 +465,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, })[api.HTTPRoute], }, @@ -540,8 +551,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, }, }, @@ -626,8 +639,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, })[api.HTTPRoute], args: args{ @@ -710,8 +725,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, @@ -791,8 +808,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, })[api.HTTPRoute], }, @@ -875,8 +894,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, }, }, @@ -962,8 +983,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, })[api.HTTPRoute], args: args{ @@ -1047,8 +1070,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, })[api.HTTPRoute], }, @@ -1132,8 +1157,10 @@ func Test_resourceCache_diff(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, }, }, }, @@ -1378,8 +1405,10 @@ func TestCache_Write(t *testing.T) { }, }, Hostnames: []string{"hostname.com"}, - Meta: map[string]string{}, - Status: api.ConfigEntryStatus{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, + Status: api.ConfigEntryStatus{}, } err = c.Write(context.Background(), entry) @@ -1410,18 +1439,24 @@ func TestCache_Get(t *testing.T) { want: &api.APIGatewayConfigEntry{ Kind: api.APIGateway, Name: "api-gw", - Meta: map[string]string{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, }, cache: loadedReferenceMaps([]api.ConfigEntry{ &api.APIGatewayConfigEntry{ Kind: api.APIGateway, Name: "api-gw", - Meta: map[string]string{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, }, &api.APIGatewayConfigEntry{ Kind: api.APIGateway, Name: "api-gw-2", - Meta: map[string]string{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, }, }), }, @@ -1438,12 +1473,16 @@ func TestCache_Get(t *testing.T) { &api.APIGatewayConfigEntry{ Kind: api.APIGateway, Name: "api-gw", - Meta: map[string]string{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, }, &api.APIGatewayConfigEntry{ Kind: api.APIGateway, Name: "api-gw-2", - Meta: map[string]string{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, }, }), }, @@ -1460,7 +1499,9 @@ func TestCache_Get(t *testing.T) { &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: "route", - Meta: map[string]string{}, + Meta: map[string]string{ + constants.MetaKeyKubeName: "name", + }, }, }), }, @@ -1766,7 +1807,8 @@ func setupHTTPRoutes() (*api.HTTPRouteConfigEntry, *api.HTTPRouteConfigEntry) { }, Hostnames: []string{"hostname.com"}, Meta: map[string]string{ - "metaKey": "metaVal", + "metaKey": "metaVal", + constants.MetaKeyKubeName: "name", }, Status: api.ConfigEntryStatus{}, } @@ -1849,7 +1891,8 @@ func setupHTTPRoutes() (*api.HTTPRouteConfigEntry, *api.HTTPRouteConfigEntry) { }, Hostnames: []string{"hostname.com"}, Meta: map[string]string{ - "metakey": "meta val", + "metakey": "meta val", + constants.MetaKeyKubeName: "name", }, } return routeOne, routeTwo @@ -1860,7 +1903,8 @@ func setupGateway() *api.APIGatewayConfigEntry { Kind: api.APIGateway, Name: "api-gw", Meta: map[string]string{ - "metakey": "meta val", + "metakey": "meta val", + constants.MetaKeyKubeName: "name", }, Listeners: []api.APIGatewayListener{ { @@ -1891,7 +1935,8 @@ func setupTCPRoute() *api.TCPRouteConfigEntry { }, }, Meta: map[string]string{ - "metakey": "meta val", + "metakey": "meta val", + constants.MetaKeyKubeName: "name", }, Status: api.ConfigEntryStatus{}, } @@ -1904,7 +1949,8 @@ func setupInlineCertificate() *api.InlineCertificateConfigEntry { Certificate: "cert", PrivateKey: "super secret", Meta: map[string]string{ - "metaKey": "meta val", + "metaKey": "meta val", + constants.MetaKeyKubeName: "name", }, } } diff --git a/control-plane/api-gateway/common/helm_config.go b/control-plane/api-gateway/common/helm_config.go index 2a6cc8211b..f0d4dc7988 100644 --- a/control-plane/api-gateway/common/helm_config.go +++ b/control-plane/api-gateway/common/helm_config.go @@ -3,7 +3,12 @@ package common -import "time" +import ( + "strings" + "time" +) + +const componentAuthMethod = "k8s-component-auth-method" // HelmConfig is the configuration of gateways that comes in from the user's Helm values. type HelmConfig struct { @@ -33,3 +38,20 @@ type ConsulConfig struct { HTTPPort int APITimeout time.Duration } + +func (h HelmConfig) Normalize() HelmConfig { + if h.AuthMethod != "" { + // strip off any DC naming off the back in case we're + // in a secondary DC, in which case our auth method is + // going to be a globally scoped auth method, and we want + // to target the locally scoped one, which is the auth + // method without the DC-specific suffix. + tokens := strings.Split(h.AuthMethod, componentAuthMethod) + if len(tokens) != 2 { + // skip the normalization if we can't do it. + return h + } + h.AuthMethod = tokens[0] + componentAuthMethod + } + return h +} diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index 5e577470d6..8644b64716 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -23,6 +23,7 @@ type ResourceTranslator struct { EnableK8sMirroring bool MirroringPrefix string ConsulPartition string + Datacenter string } func (t ResourceTranslator) NonNormalizedConfigEntryReference(kind string, id types.NamespacedName) api.ResourceReference { @@ -65,10 +66,10 @@ func (t ResourceTranslator) ToAPIGateway(gateway gwv1beta1.Gateway, resources *R Name: gateway.Name, Namespace: namespace, Partition: t.ConsulPartition, - Meta: map[string]string{ + Meta: t.addDatacenterToMeta(map[string]string{ constants.MetaKeyKubeNS: gateway.Namespace, constants.MetaKeyKubeName: gateway.Name, - }, + }), Listeners: listeners, } } @@ -128,10 +129,10 @@ func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *Re Name: route.Name, Namespace: namespace, Partition: t.ConsulPartition, - Meta: map[string]string{ + Meta: t.addDatacenterToMeta(map[string]string{ constants.MetaKeyKubeNS: route.Namespace, constants.MetaKeyKubeName: route.Name, - }, + }), Hostnames: hostnames, Rules: rules, } @@ -292,10 +293,10 @@ func (t ResourceTranslator) ToTCPRoute(route gwv1alpha2.TCPRoute, resources *Res Name: route.Name, Namespace: namespace, Partition: t.ConsulPartition, - Meta: map[string]string{ + Meta: t.addDatacenterToMeta(map[string]string{ constants.MetaKeyKubeNS: route.Namespace, constants.MetaKeyKubeName: route.Name, - }, + }), Services: services, } } @@ -346,10 +347,10 @@ func (t ResourceTranslator) ToInlineCertificate(secret corev1.Secret) (*api.Inli Partition: t.ConsulPartition, Certificate: strings.TrimSpace(certificate), PrivateKey: strings.TrimSpace(privateKey), - Meta: map[string]string{ + Meta: t.addDatacenterToMeta(map[string]string{ constants.MetaKeyKubeNS: secret.Namespace, constants.MetaKeyKubeName: secret.Name, - }, + }), }, nil } @@ -361,3 +362,11 @@ func EntryToNamespacedName(entry api.ConfigEntry) types.NamespacedName { Name: meta[constants.MetaKeyKubeName], } } + +func (t ResourceTranslator) addDatacenterToMeta(meta map[string]string) map[string]string { + if t.Datacenter == "" { + return meta + } + meta[constants.MetaKeyDatacenter] = t.Datacenter + return meta +} diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index ec8c2e9af0..664bb98d3c 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -46,6 +46,7 @@ type GatewayControllerConfig struct { NamespacesEnabled bool CrossNamespaceACLPolicy string Partition string + Datacenter string AllowK8sNamespacesSet mapset.Set DenyK8sNamespacesSet mapset.Set } @@ -317,6 +318,7 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co ConsulClientConfig: config.ConsulClientConfig, ConsulServerConnMgr: config.ConsulServerConnMgr, NamespacesEnabled: config.NamespacesEnabled, + Datacenter: config.Datacenter, CrossNamespaceACLPolicy: config.CrossNamespaceACLPolicy, Logger: mgr.GetLogger(), } @@ -332,13 +334,14 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co r := &GatewayController{ Client: mgr.GetClient(), Log: mgr.GetLogger(), - HelmConfig: config.HelmConfig, + HelmConfig: config.HelmConfig.Normalize(), Translator: common.ResourceTranslator{ EnableConsulNamespaces: config.HelmConfig.EnableNamespaces, ConsulDestNamespace: config.HelmConfig.ConsulDestinationNamespace, EnableK8sMirroring: config.HelmConfig.EnableNamespaceMirroring, MirroringPrefix: config.HelmConfig.NamespaceMirroringPrefix, ConsulPartition: config.HelmConfig.ConsulPartition, + Datacenter: config.Datacenter, }, denyK8sNamespacesSet: config.DenyK8sNamespacesSet, allowK8sNamespacesSet: config.AllowK8sNamespacesSet, diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 75aa44f6b9..d00821275d 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -425,7 +425,7 @@ func (in *ServiceResolverRedirect) validate(path *field.Path, consulMeta common. "service resolver redirect cannot be empty")) } - if consulMeta.Partition != "default" && in.Datacenter != "" { + if consulMeta.Partition != "default" && consulMeta.Partition != "" && in.Datacenter != "" { errs = append(errs, field.Invalid(path.Child("datacenter"), in.Datacenter, "cross-datacenter redirect is only supported in the default partition")) } diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 8987a9f5e8..0a341cd577 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -19,6 +19,9 @@ const ( // MetaKeyKubeName is the meta key name for Kubernetes object name used for a Consul object. MetaKeyKubeName = "k8s-name" + // MetaKeyDatacenter is the datacenter that this object was registered from. + MetaKeyDatacenter = "datacenter" + // MetaKeyKubeServiceName is the meta key name for Kubernetes service name used for the Consul services. MetaKeyKubeServiceName = "k8s-service-name" diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index a4fcf7c99d..6914894096 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -515,7 +515,9 @@ func (c *Command) Run(args []string) int { NamespacesEnabled: c.flagEnableNamespaces, CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, Partition: c.consul.Partition, + Datacenter: c.consul.Datacenter, }) + if err != nil { setupLog.Error(err, "unable to create controller", "controller", "Gateway") return 1 From da147c1d7503e307ee4b93e0139a27a80a073cfc Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 9 Jun 2023 10:16:55 -0400 Subject: [PATCH 220/592] [API Gateway] fix dangling service registrations (#2321) * Fix when gateways are deleted before we get services populated into cache * a bit of cleanup --- control-plane/api-gateway/cache/gateway.go | 18 +++++++ .../controllers/gateway_controller.go | 50 +++++++++++++++---- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/control-plane/api-gateway/cache/gateway.go b/control-plane/api-gateway/cache/gateway.go index 846131d11e..d8dd37ffac 100644 --- a/control-plane/api-gateway/cache/gateway.go +++ b/control-plane/api-gateway/cache/gateway.go @@ -52,6 +52,24 @@ func (r *GatewayCache) ServicesFor(ref api.ResourceReference) []api.CatalogServi return r.data[common.NormalizeMeta(ref)] } +func (r *GatewayCache) FetchServicesFor(ctx context.Context, ref api.ResourceReference) ([]api.CatalogService, error) { + client, err := consul.NewClientFromConnMgr(r.config, r.serverMgr) + if err != nil { + return nil, err + } + + opts := &api.QueryOptions{} + if ref.Namespace != "" { + opts.Namespace = ref.Namespace + } + + services, _, err := client.Catalog().Service(ref.Name, "", opts.WithContext(ctx)) + if err != nil { + return nil, err + } + return common.DerefAll(services), nil +} + func (r *GatewayCache) EnsureSubscribed(ref api.ResourceReference, resource types.NamespacedName) { r.mutex.Lock() defer r.mutex.Unlock() diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index 664bb98d3c..f83466d67a 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -209,6 +209,12 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } r.gatewayCache.RemoveSubscription(nonNormalizedConsulKey) + // make sure we have deregister all services even if they haven't + // hit cache yet + if err := r.deregisterAllServices(ctx, consulKey); err != nil { + log.Error(err, "error deregistering services") + return ctrl.Result{}, err + } } for _, deletion := range updates.Consul.Deletions { @@ -235,19 +241,24 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct } } - for _, registration := range updates.Consul.Registrations { - log.Info("registering service in Consul", "service", registration.Service.Service, "id", registration.Service.ID) - if err := r.cache.Register(ctx, registration); err != nil { - log.Error(err, "error registering service") - return ctrl.Result{}, err + if updates.UpsertGatewayDeployment { + // We only do some registration/deregistraion if we still have a valid gateway + // otherwise, we've already deregistered everything related to the gateway, so + // no need to do any of the following. + for _, registration := range updates.Consul.Registrations { + log.Info("registering service in Consul", "service", registration.Service.Service, "id", registration.Service.ID) + if err := r.cache.Register(ctx, registration); err != nil { + log.Error(err, "error registering service") + return ctrl.Result{}, err + } } - } - for _, deregistration := range updates.Consul.Deregistrations { - log.Info("deregistering service in Consul", "id", deregistration.ServiceID) - if err := r.cache.Deregister(ctx, deregistration); err != nil { - log.Error(err, "error deregistering service") - return ctrl.Result{}, err + for _, deregistration := range updates.Consul.Deregistrations { + log.Info("deregistering service in Consul", "id", deregistration.ServiceID) + if err := r.cache.Deregister(ctx, deregistration); err != nil { + log.Error(err, "error deregistering service") + return ctrl.Result{}, err + } } } @@ -270,6 +281,23 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, nil } +func (r *GatewayController) deregisterAllServices(ctx context.Context, consulKey api.ResourceReference) error { + services, err := r.gatewayCache.FetchServicesFor(ctx, consulKey) + if err != nil { + return err + } + for _, service := range services { + if err := r.cache.Deregister(ctx, api.CatalogDeregistration{ + Node: service.Node, + ServiceID: service.ServiceID, + Namespace: service.Namespace, + }); err != nil { + return err + } + } + return nil +} + func (r *GatewayController) updateAndResetStatus(ctx context.Context, o client.Object) error { // we create a copy so that we can re-update its status if need be status := reflect.ValueOf(o.DeepCopyObject()).Elem().FieldByName("Status") From 198c4433d8927e2ec9c77b4a75ef80aa02f653ff Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Fri, 9 Jun 2023 10:36:03 -0400 Subject: [PATCH 221/592] api-gateway: add unit tests verifying scaling parameters on GatewayClassConfig are obeyed (#2272) * Add unit tests verifying that scaling parameters on GatewayClassConfig are obeyed * Add test case for scaling w/ no min or max configured --- .../api-gateway/gatekeeper/gatekeeper_test.go | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go index e2da61177f..ba58cb441f 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -210,6 +210,76 @@ func TestUpsert(t *testing.T) { }, }, }, + "create a new gateway where the GatewayClassConfig has a default number of instances greater than the max on the GatewayClassConfig": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: common.PointerTo(int32(8)), + MaxInstances: common.PointerTo(int32(5)), + MinInstances: common.PointerTo(int32(2)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), + }, + }, + helmConfig: common.HelmConfig{}, + initialResources: resources{}, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + "create a new gateway where the GatewayClassConfig has a default number of instances lesser than the min on the GatewayClassConfig": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: common.PointerTo(int32(1)), + MaxInstances: common.PointerTo(int32(5)), + MinInstances: common.PointerTo(int32(2)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), + }, + }, + helmConfig: common.HelmConfig{}, + initialResources: resources{}, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 2, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, "update a gateway, adding a listener to a service": { gateway: gwv1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ @@ -409,6 +479,123 @@ func TestUpsert(t *testing.T) { serviceAccounts: []*corev1.ServiceAccount{}, }, }, + "update a gateway deployment by scaling it when no min or max number of instances is defined on the GatewayClassConfig": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: nil, + MinInstances: nil, + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), + }, + }, + helmConfig: common.HelmConfig{}, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 8, nil, nil, "", "1"), + }, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 8, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + "update a gateway deployment by scaling it lower than the min number of instances on the GatewayClassConfig": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(5)), + MinInstances: common.PointerTo(int32(2)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), + }, + }, + helmConfig: common.HelmConfig{}, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 1, nil, nil, "", "1"), + }, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 2, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, + "update a gateway deployment by scaling it higher than the max number of instances on the GatewayClassConfig": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(5)), + MinInstances: common.PointerTo(int32(2)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), + }, + }, + helmConfig: common.HelmConfig{}, + initialResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 10, nil, nil, "", "1"), + }, + }, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, } for name, tc := range cases { From 8245efc1f3ccb26dd051126a876b293cb5c2e28f Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Fri, 9 Jun 2023 16:34:49 -0400 Subject: [PATCH 222/592] Rename GatewayClassController to prevent name collision (#2317) * Rename GatewayClassController to prevent name collision * Use gateway instead of gatewayclass in name * Use the constant in ownership checks * Change GatewayClass name to "consul" * Change GatewayClass name in cases * Change ApiGatewayClass back --- acceptance/tests/api-gateway/api_gateway_test.go | 2 +- .../fixtures/bases/api-gateway/gatewayclass.yaml | 2 +- .../cases/api-gateways/gateway/gateway.yaml | 4 ++-- charts/consul/templates/gateway-cleanup-job.yaml | 2 +- .../consul/templates/gateway-resources-job.yaml | 4 ++-- control-plane/api-gateway/common/constants.go | 2 ++ .../api-gateway/controllers/gateway_controller.go | 13 +++++++------ .../controllers/gatewayclass_controller.go | 2 -- .../controllers/gatewayclass_controller_test.go | 15 ++++++++------- ...roller.go => gatewayclassconfig_controller.go} | 0 ...t.go => gatewayclassconfig_controller_test.go} | 0 .../subcommand/inject-connect/command.go | 2 +- 12 files changed, 25 insertions(+), 23 deletions(-) rename control-plane/api-gateway/controllers/{gateway_class_config_controller.go => gatewayclassconfig_controller.go} (100%) rename control-plane/api-gateway/controllers/{gateway_class_config_controller_test.go => gatewayclassconfig_controller_test.go} (100%) diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index 17234cadf1..143b793bf8 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -27,7 +27,7 @@ import ( const ( StaticClientName = "static-client" - gatewayClassControllerName = "hashicorp.com/consul-api-gateway-controller" + gatewayClassControllerName = "consul.hashicorp.com/gateway-controller" gatewayClassFinalizer = "gateway-exists-finalizer.consul.hashicorp.com" gatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" ) diff --git a/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml b/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml index 872faeb78c..9ff985fd49 100644 --- a/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml +++ b/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml @@ -6,7 +6,7 @@ kind: GatewayClass metadata: name: gateway-class spec: - controllerName: "hashicorp.com/consul-api-gateway-controller" + controllerName: "consul.hashicorp.com/gateway-controller" parametersRef: group: consul.hashicorp.com kind: GatewayClassConfig diff --git a/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml index 14c39978b7..7f0428b039 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml @@ -6,7 +6,7 @@ kind: Gateway metadata: name: gateway spec: - gatewayClassName: consul-api-gateway + gatewayClassName: consul listeners: - protocol: HTTPS port: 8080 @@ -17,4 +17,4 @@ spec: namespace: "default" allowedRoutes: namespaces: - from: "All" \ No newline at end of file + from: "All" diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index ff6f295357..44f032b5fd 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -41,7 +41,7 @@ spec: - consul-k8s-control-plane args: - gateway-cleanup - - -gateway-class-name=consul-api-gateway + - -gateway-class-name=consul - -gateway-class-config-name=consul-api-gateway resources: requests: diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index f8f92f799d..441e64eb14 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -41,9 +41,9 @@ spec: - consul-k8s-control-plane args: - gateway-resources - - -gateway-class-name=consul-api-gateway + - -gateway-class-name=consul - -gateway-class-config-name=consul-api-gateway - - -controller-name=hashicorp.com/consul-api-gateway-controller + - -controller-name=consul.hashicorp.com/gateway-controller - -app={{template "consul.name" .}} - -chart={{template "consul.chart" .}} - -heritage={{ .Release.Service }} diff --git a/control-plane/api-gateway/common/constants.go b/control-plane/api-gateway/common/constants.go index 68abfc96b1..c1ec0685a4 100644 --- a/control-plane/api-gateway/common/constants.go +++ b/control-plane/api-gateway/common/constants.go @@ -4,5 +4,7 @@ package common const ( + GatewayClassControllerName = "consul.hashicorp.com/gateway-controller" + AnnotationGatewayClassConfig = "consul.hashicorp.com/gateway-class-config" ) diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index f83466d67a..97805ccdfb 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -54,9 +54,10 @@ type GatewayControllerConfig struct { // GatewayController reconciles a Gateway object. // The Gateway is responsible for defining the behavior of API gateways. type GatewayController struct { - HelmConfig common.HelmConfig - Log logr.Logger - Translator common.ResourceTranslator + HelmConfig common.HelmConfig + Log logr.Logger + Translator common.ResourceTranslator + cache *cache.Cache gatewayCache *cache.GatewayCache allowK8sNamespacesSet mapset.Set @@ -174,7 +175,7 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct binder := binding.NewBinder(binding.BinderConfig{ Logger: log, Translator: r.Translator, - ControllerName: GatewayClassControllerName, + ControllerName: common.GatewayClassControllerName, Namespaces: namespaces, GatewayClassConfig: gatewayClassConfig, GatewayClass: gatewayClass, @@ -786,7 +787,7 @@ func (c *GatewayController) getConfigForGatewayClass(ctx context.Context, gatewa if ref := gatewayClassConfig.Spec.ParametersRef; ref != nil { if string(ref.Group) != v1alpha1.GroupVersion.Group || ref.Kind != v1alpha1.GatewayClassConfigKind || - gatewayClassConfig.Spec.ControllerName != GatewayClassControllerName { + gatewayClassConfig.Spec.ControllerName != common.GatewayClassControllerName { // we don't have supported params, so return nil return nil, nil } @@ -813,7 +814,7 @@ func (c *GatewayController) fetchControlledGateways(ctx context.Context, resourc list := gwv1beta1.GatewayClassList{} if err := c.Client.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(GatewayClass_ControllerNameIndex, GatewayClassControllerName), + FieldSelector: fields.OneTermEqualSelector(GatewayClass_ControllerNameIndex, common.GatewayClassControllerName), }); err != nil { return err } diff --git a/control-plane/api-gateway/controllers/gatewayclass_controller.go b/control-plane/api-gateway/controllers/gatewayclass_controller.go index 4180157616..e37d1b3bcd 100644 --- a/control-plane/api-gateway/controllers/gatewayclass_controller.go +++ b/control-plane/api-gateway/controllers/gatewayclass_controller.go @@ -22,8 +22,6 @@ import ( ) const ( - GatewayClassControllerName = "hashicorp.com/consul-api-gateway-controller" - gatewayClassFinalizer = "gateway-exists-finalizer.consul.hashicorp.com" // GatewayClass status fields. diff --git a/control-plane/api-gateway/controllers/gatewayclass_controller_test.go b/control-plane/api-gateway/controllers/gatewayclass_controller_test.go index ac5be25205..0eeaf4c1de 100644 --- a/control-plane/api-gateway/controllers/gatewayclass_controller_test.go +++ b/control-plane/api-gateway/controllers/gatewayclass_controller_test.go @@ -22,6 +22,7 @@ import ( "sigs.k8s.io/gateway-api/apis/v1beta1" 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" ) @@ -57,7 +58,7 @@ func TestGatewayClassReconciler(t *testing.T) { Finalizers: []string{gatewayClassFinalizer}, }, Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: GatewayClassControllerName, + ControllerName: common.GatewayClassControllerName, }, }, expectedResult: ctrl.Result{}, @@ -81,7 +82,7 @@ func TestGatewayClassReconciler(t *testing.T) { Finalizers: []string{}, }, Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: GatewayClassControllerName, + ControllerName: common.GatewayClassControllerName, }, }, expectedResult: ctrl.Result{}, @@ -127,7 +128,7 @@ func TestGatewayClassReconciler(t *testing.T) { Finalizers: []string{gatewayClassFinalizer}, }, Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: GatewayClassControllerName, + ControllerName: common.GatewayClassControllerName, ParametersRef: &gwv1beta1.ParametersReference{ Kind: "some-nonsense", }, @@ -153,7 +154,7 @@ func TestGatewayClassReconciler(t *testing.T) { Finalizers: []string{gatewayClassFinalizer}, }, Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: GatewayClassControllerName, + ControllerName: common.GatewayClassControllerName, ParametersRef: &gwv1beta1.ParametersReference{ Kind: v1alpha1.GatewayClassConfigKind, Name: "does-not-exist", @@ -189,7 +190,7 @@ func TestGatewayClassReconciler(t *testing.T) { DeletionTimestamp: &deletionTimestamp, }, Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: GatewayClassControllerName, + ControllerName: common.GatewayClassControllerName, }, }, expectedResult: ctrl.Result{}, @@ -208,7 +209,7 @@ func TestGatewayClassReconciler(t *testing.T) { DeletionTimestamp: &deletionTimestamp, }, Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: GatewayClassControllerName, + ControllerName: common.GatewayClassControllerName, }, }, k8sObjects: []runtime.Object{ @@ -246,7 +247,7 @@ func TestGatewayClassReconciler(t *testing.T) { r := &GatewayClassController{ Client: fakeClient, - ControllerName: GatewayClassControllerName, + ControllerName: common.GatewayClassControllerName, Log: logrtest.New(t), } result, err := r.Reconcile(context.Background(), req) diff --git a/control-plane/api-gateway/controllers/gateway_class_config_controller.go b/control-plane/api-gateway/controllers/gatewayclassconfig_controller.go similarity index 100% rename from control-plane/api-gateway/controllers/gateway_class_config_controller.go rename to control-plane/api-gateway/controllers/gatewayclassconfig_controller.go diff --git a/control-plane/api-gateway/controllers/gateway_class_config_controller_test.go b/control-plane/api-gateway/controllers/gatewayclassconfig_controller_test.go similarity index 100% rename from control-plane/api-gateway/controllers/gateway_class_config_controller_test.go rename to control-plane/api-gateway/controllers/gatewayclassconfig_controller_test.go diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 6914894096..8bada415db 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -476,7 +476,7 @@ func (c *Command) Run(args []string) int { } if err := (&gatewaycontrollers.GatewayClassController{ - ControllerName: gatewaycontrollers.GatewayClassControllerName, + ControllerName: gatewaycommon.GatewayClassControllerName, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("GatewayClass"), }).SetupWithManager(ctx, mgr); err != nil { From f07736b53f431f4c7956f5c69ac5b3de0fd4a481 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Fri, 9 Jun 2023 16:51:39 -0400 Subject: [PATCH 223/592] [API Gateway] Conformance Test Fixes (#2326) * Fix SupportedKinds array to be what Conformance test expects * Fix cert validation status condition for listeners * Add programmed condition for listeners * Fix unit test --------- Co-authored-by: Nathan Coleman --- control-plane/api-gateway/binding/binder.go | 14 +++++- .../api-gateway/binding/binder_test.go | 5 ++ control-plane/api-gateway/binding/result.go | 49 +++++++++++++++++-- .../api-gateway/binding/validation.go | 26 +++++++++- 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index b677d69253..28a26985a8 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -214,7 +214,7 @@ func (b *Binder) Snapshot() *Snapshot { for i, listener := range b.config.Gateway.Spec.Listeners { status.Listeners = append(status.Listeners, gwv1beta1.ListenerStatus{ Name: listener.Name, - SupportedKinds: supportedKindsForProtocol[listener.Protocol], + SupportedKinds: supportedKinds(listener), AttachedRoutes: int32(boundCounts[listener.Name]), Conditions: listenerValidation.Conditions(b.config.Gateway.Generation, i), }) @@ -374,3 +374,15 @@ func addressesFromPodHosts(pods []corev1.Pod) []gwv1beta1.GatewayAddress { func isDeleted(object client.Object) bool { return !object.GetDeletionTimestamp().IsZero() } + +func supportedKinds(listener gwv1beta1.Listener) []gwv1beta1.RouteGroupKind { + if listener.AllowedRoutes != nil && listener.AllowedRoutes.Kinds != nil { + return common.Filter(listener.AllowedRoutes.Kinds, func(kind gwv1beta1.RouteGroupKind) bool { + if _, ok := allSupportedRouteKinds[kind.Kind]; !ok { + return true + } + return !common.NilOrEqual(kind.Group, gwv1beta1.GroupVersion.Group) + }) + } + return supportedKindsForProtocol[listener.Protocol] +} diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index 65cca94419..a3af702dab 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -233,6 +233,11 @@ func TestBinder_Lifecycle(t *testing.T) { Status: metav1.ConditionTrue, Reason: "Accepted", Message: "listener accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionTrue, + Reason: "Programmed", + Message: "listener programmed", }, { Type: "Conflicted", Status: metav1.ConditionFalse, diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index dd82cd55b5..3ccb645c32 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -213,8 +213,8 @@ func (p parentBindResults) boundSections() mapset.Set { } var ( - // Each of the below are specified in the Gateway spec under ListenerConditionReason - // the general usage is that each error is specified as errListener* where * corresponds + // Each of the below are specified in the Gateway spec under ListenerConditionReason. + // The general usage is that each error is specified as errListener* where * corresponds // to the ListenerConditionReason given in the spec. If a reason is overloaded and can // be used with two different types of things (i.e. something is not found or it's not supported) // then we distinguish those two usages with errListener*_Usage. @@ -225,6 +225,8 @@ var ( errListenerInvalidCertificateRef_NotFound = errors.New("certificate not found") errListenerInvalidCertificateRef_NotSupported = errors.New("certificate type is not supported") errListenerInvalidCertificateRef_InvalidData = errors.New("certificate is invalid or does not contain a supported server name") + errListenerInvalidRouteKinds = errors.New("allowed route kind is invalid") + errListenerProgrammed_Invalid = errors.New("listener cannot be programmed because it is invalid") // Below is where any custom generic listener validation errors should go. // We map anything under here to a custom ListenerConditionReason of Invalid on @@ -243,7 +245,36 @@ type listenerValidationResult struct { conflictedErr error // status type: ResolvedRefs refErr error - // TODO: programmed + // status type: ResolvedRefs (but with internal validation) + routeKindErr error +} + +// programmedCondition constructs the condition for the Programmed status type. +// If there are no validation errors for the listener, we mark it as programmed. +// If there are validation errors for the listener, we mark it as invalid. +func (l listenerValidationResult) programmedCondition(generation int64) metav1.Condition { + now := timeFunc() + + switch { + case l.acceptedErr != nil, l.conflictedErr != nil, l.refErr != nil, l.routeKindErr != nil: + return metav1.Condition{ + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Invalid", + ObservedGeneration: generation, + Message: errListenerProgrammed_Invalid.Error(), + LastTransitionTime: now, + } + default: + return metav1.Condition{ + Type: "Programmed", + Status: metav1.ConditionTrue, + Reason: "Programmed", + ObservedGeneration: generation, + Message: "listener programmed", + LastTransitionTime: now, + } + } } // acceptedCondition constructs the condition for the Accepted status type. @@ -329,6 +360,17 @@ func (l listenerValidationResult) conflictedCondition(generation int64) metav1.C func (l listenerValidationResult) resolvedRefsCondition(generation int64) metav1.Condition { now := timeFunc() + if l.routeKindErr != nil { + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidRouteKinds", + ObservedGeneration: generation, + Message: l.routeKindErr.Error(), + LastTransitionTime: now, + } + } + switch l.refErr { case errListenerInvalidCertificateRef_NotFound, errListenerInvalidCertificateRef_NotSupported, errListenerInvalidCertificateRef_InvalidData: return metav1.Condition{ @@ -364,6 +406,7 @@ func (l listenerValidationResult) resolvedRefsCondition(generation int64) metav1 func (l listenerValidationResult) Conditions(generation int64) []metav1.Condition { return []metav1.Condition{ l.acceptedCondition(generation), + l.programmedCondition(generation), l.conflictedCondition(generation), l.resolvedRefsCondition(generation), } diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index 41c9484483..0e17e2b306 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -35,6 +35,10 @@ var ( Kind: "TCPRoute", }}, } + allSupportedRouteKinds = map[gwv1beta1.Kind]struct{}{ + gwv1beta1.Kind("HTTPRoute"): {}, + gwv1beta1.Kind("TCPRoute"): {}, + } ) // validateRefs validates backend references for a route, determining whether or @@ -202,7 +206,10 @@ func validateTLS(gateway gwv1beta1.Gateway, tls *gwv1beta1.GatewayTLSConfig, res func validateCertificateData(secret corev1.Secret) error { _, _, err := common.ParseCertificateData(secret) - return err + if err != nil { + return errListenerInvalidCertificateRef_InvalidData + } + return nil } // validateListeners validates the given listeners both internally and with respect to each @@ -231,6 +238,8 @@ func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener } else if listener.Port == 20000 { //admin port result.acceptedErr = errListenerPortUnavailable } + + result.routeKindErr = validateListenerAllowedRouteKinds(listener.AllowedRoutes) } if err := merged[listener.Port].validateProtocol(); err != nil { @@ -244,6 +253,21 @@ func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener return results } +func validateListenerAllowedRouteKinds(allowedRoutes *gwv1beta1.AllowedRoutes) error { + if allowedRoutes == nil { + return nil + } + for _, kind := range allowedRoutes.Kinds { + if _, ok := allSupportedRouteKinds[kind.Kind]; !ok { + return errListenerInvalidRouteKinds + } + if !common.NilOrEqual(kind.Group, gwv1beta1.GroupVersion.Group) { + return errListenerInvalidRouteKinds + } + } + return nil +} + // routeAllowedForListenerNamespaces determines whether the route is allowed // to bind to the Gateway based on the AllowedRoutes namespace selectors. func routeAllowedForListenerNamespaces(gatewayNamespace string, allowedRoutes *gwv1beta1.AllowedRoutes, namespace corev1.Namespace) bool { From 6933efec7fc2c05d4f6a2f7f3035c5b4bc0beb9f Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 9 Jun 2023 16:03:32 -0700 Subject: [PATCH 224/592] pin for 1.2.x-rc latest Consul submodules (#2327) --- acceptance/go.mod | 26 +++---- acceptance/go.sum | 56 ++++++++------ cli/go.mod | 57 +++++++------- cli/go.sum | 176 ++++++++++++++++--------------------------- control-plane/go.mod | 42 +++++------ control-plane/go.sum | 85 +++++++++++---------- 6 files changed, 209 insertions(+), 233 deletions(-) diff --git a/acceptance/go.mod b/acceptance/go.mod index e2221a09c0..ddce3e4e5c 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -4,14 +4,14 @@ go 1.20 require ( github.com/gruntwork-io/terratest v0.31.2 - github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb - github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 - github.com/hashicorp/consul/sdk v0.13.1 + github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 + github.com/hashicorp/consul/api v1.22.0-rc1 + github.com/hashicorp/consul/sdk v0.14.0-rc1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.8.3 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 @@ -29,11 +29,11 @@ require ( github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.14.1 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.2.3 // indirect @@ -52,7 +52,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.2 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect @@ -71,8 +71,8 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/miekg/dns v1.1.50 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect @@ -88,7 +88,7 @@ require ( github.com/oklog/run v1.0.0 // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pquerna/otp v1.2.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect @@ -102,10 +102,10 @@ require ( golang.org/x/crypto v0.1.0 // indirect golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 77a5b5875a..bd7421de60 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -141,8 +141,9 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= @@ -182,8 +183,9 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -331,12 +333,12 @@ github.com/gruntwork-io/gruntwork-cli v0.7.0 h1:YgSAmfCj9c61H+zuvHwKfYUwlMhu5arn github.com/gruntwork-io/gruntwork-cli v0.7.0/go.mod h1:jp6Z7NcLF2avpY8v71fBx6hds9eOFPELSuD/VPv7w00= github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+XbVVtytUHg= github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb h1:9GUvDoKVoV3IW78QyfoNY4bRcKxcn26wTGLoBrz92N4= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230601034256-0c28b9b000cb/go.mod h1:jKzTEgDc/np2gX/KPdfdm1mEUfZLrU8gc71XN3B15VI= -github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 h1:6kUTk+YBgA5X5b3gNAoI18WEK4/t75LcWSimEgmpFdg= -github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= -github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= -github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= +github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 h1:4iI0ztWbVPTSDax+m1/XDs4jIRorxY4kSMyuM0fX+Dc= +github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= +github.com/hashicorp/consul/api v1.22.0-rc1 h1:ePmGqndeMgaI38KUbSA/CqTzeEAIogXyWnfNJzglo70= +github.com/hashicorp/consul/api v1.22.0-rc1/go.mod h1:wtduXtbAqSGtBdi3tyA5SSAYGAG51rBejV9SEUBciMY= +github.com/hashicorp/consul/sdk v0.14.0-rc1 h1:PuETOfN0uxl28i0Pq6rK7TBCrIl7psMbL0YTSje4KvM= +github.com/hashicorp/consul/sdk v0.14.0-rc1/go.mod h1:gHYeuDa0+0qRAD6Wwr6yznMBvBwHKoxSBoW5l73+saE= 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= @@ -347,13 +349,13 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= -github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -444,8 +446,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -462,15 +464,18 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= @@ -547,8 +552,9 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= @@ -588,6 +594,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 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/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -636,8 +643,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -768,8 +775,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -790,7 +797,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -857,14 +864,15 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/cli/go.mod b/cli/go.mod index 9633fd5dd2..cdee871f38 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -5,19 +5,19 @@ go 1.20 require ( github.com/bgentry/speakeasy v0.1.0 github.com/cenkalti/backoff v2.2.1+incompatible - github.com/fatih/color v1.13.0 - github.com/google/go-cmp v0.5.8 + github.com/fatih/color v1.14.1 + github.com/google/go-cmp v0.5.9 github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 - github.com/hashicorp/consul/troubleshoot v0.1.2 - github.com/hashicorp/go-hclog v1.2.1 + github.com/hashicorp/consul/troubleshoot v0.3.0-rc1 + github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc github.com/kr/text v0.2.0 - github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-isatty v0.0.17 github.com/mitchellh/cli v1.1.2 github.com/olekukonko/tablewriter v0.0.5 github.com/posener/complete v1.2.3 - github.com/stretchr/testify v1.8.0 - golang.org/x/text v0.7.0 + github.com/stretchr/testify v1.8.3 + golang.org/x/text v0.9.0 helm.sh/helm/v3 v3.9.4 k8s.io/api v0.25.0 k8s.io/apiextensions-apiserver v0.25.0 @@ -28,10 +28,11 @@ require ( sigs.k8s.io/yaml v1.3.0 ) -require go.opentelemetry.io/proto/otlp v0.11.0 // indirect +require go.opentelemetry.io/proto/otlp v0.19.0 // indirect require ( - cloud.google.com/go v0.99.0 // indirect + cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.27 // indirect @@ -49,14 +50,14 @@ require ( github.com/Masterminds/squirrel v1.5.3 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/armon/go-metrics v0.3.10 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect 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.3.0 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect - github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc // indirect + github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 // indirect github.com/containerd/containerd v1.6.6 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -68,8 +69,9 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/emicklei/go-restful/v3 v3.8.0 // indirect - github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect - github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect + github.com/envoyproxy/go-control-plane v0.11.0 // indirect + github.com/envoyproxy/go-control-plane/xdsmatcher v0.0.0-20230524161521-aaaacbfbe53e // indirect + github.com/envoyproxy/protoc-gen-validate v0.10.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -90,7 +92,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -99,8 +101,8 @@ require ( github.com/gorilla/mux v1.8.0 // 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.20.0 // indirect - github.com/hashicorp/consul/envoyextensions v0.1.2 // indirect + github.com/hashicorp/consul/api v1.22.0-rc1 // indirect + github.com/hashicorp/consul/envoyextensions v0.3.0-rc1 // 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 @@ -147,7 +149,7 @@ require ( 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.12.2 // indirect - github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/rubenv/sql-migrate v1.1.1 // indirect @@ -165,16 +167,17 @@ require ( go.mongodb.org/mongo-driver v1.11.1 // indirect go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect - google.golang.org/grpc v1.49.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.55.0 // indirect + google.golang.org/protobuf v1.30.0 // 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 diff --git a/cli/go.sum b/cli/go.sum index b5ee614f52..41b515d2e4 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -18,21 +18,16 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -104,8 +99,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -132,12 +127,13 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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 v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/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/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -153,10 +149,9 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc h1:PYXxkRUBGUMa5xgMVMDl62vEklZvKpVaxQeN9ie7Hfk= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 h1:58f1tJ1ra+zFINPlwLWvQsR9CzAKt2e+EWV2yX9oXQ4= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= @@ -208,11 +203,13 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 h1:xvqufLtNVwAhN8NMyWklVgxnWohi+wtMGQMhtxexlm0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.11.0 h1:jtLewhRR2vMRNnq2ZZUoCjUlgut+Y0+sDDWPOfwOi1o= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= +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 v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.0 h1:oIfnZFdC0YhpNNEX+SuIqko4cqqVZeN9IGTrhZje83Y= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -221,8 +218,9 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -336,6 +334,7 @@ github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8 github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -349,7 +348,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -366,10 +364,10 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -390,8 +388,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -399,7 +397,6 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -411,9 +408,6 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -423,8 +417,6 @@ 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -436,15 +428,16 @@ github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16 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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= -github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= -github.com/hashicorp/consul/envoyextensions v0.1.2 h1:PvPqJ/td3UpOeIKQl5ycFPUy46XZP9awfhAUCduDeI4= -github.com/hashicorp/consul/envoyextensions v0.1.2/go.mod h1:N94DQQkgITiA40zuTQ/UdPOLAAWobgHfVT5u7wxE/aU= +github.com/hashicorp/consul/api v1.22.0-rc1 h1:ePmGqndeMgaI38KUbSA/CqTzeEAIogXyWnfNJzglo70= +github.com/hashicorp/consul/api v1.22.0-rc1/go.mod h1:wtduXtbAqSGtBdi3tyA5SSAYGAG51rBejV9SEUBciMY= +github.com/hashicorp/consul/envoyextensions v0.3.0-rc1 h1:weclrwjvLeX+vxPOyo4b4dCDxSpnDl60Z9K16nnCVnI= +github.com/hashicorp/consul/envoyextensions v0.3.0-rc1/go.mod h1:ckxoPHMiWXAe6dhyxmKsX1XqO4KTV64KWIyTu44z8UI= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= -github.com/hashicorp/consul/troubleshoot v0.1.2 h1:c6uMTSt/qTMhK3e18nl4xW4j7JcANdQNHOEYhoXH1P8= -github.com/hashicorp/consul/troubleshoot v0.1.2/go.mod h1:q35QOtN7K5kFLPm2SXHBDD+PzsuBekcqTZuuoOTzbWA= +github.com/hashicorp/consul/sdk v0.14.0-rc1 h1:PuETOfN0uxl28i0Pq6rK7TBCrIl7psMbL0YTSje4KvM= +github.com/hashicorp/consul/troubleshoot v0.3.0-rc1 h1:Z6ZUEKILsf85wA/zXK3XMop6IGtjui4ZZ0bAu+JIAz4= +github.com/hashicorp/consul/troubleshoot v0.3.0-rc1/go.mod h1:2WfcYZ8M4vpLtTv9M5Dp3egqSPZ16l5XsqMpO9QUYxc= 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= @@ -452,8 +445,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= -github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -472,7 +465,7 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 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.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -582,8 +575,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= @@ -711,8 +705,9 @@ github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrb 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 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 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= @@ -779,8 +774,8 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag 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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= 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/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= @@ -789,8 +784,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -835,8 +831,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= @@ -848,8 +844,8 @@ go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU= -go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.starlark.net v0.0.0-20230128213706-3f75dec8e403 h1:jPeC7Exc+m8OBJUlWbBLh0O5UZPM7yU5W4adnhhbG4U= go.starlark.net v0.0.0-20230128213706-3f75dec8e403/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= @@ -889,6 +885,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -959,14 +957,13 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -980,12 +977,9 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= 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= @@ -998,8 +992,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -1066,19 +1060,13 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1087,14 +1075,14 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1104,8 +1092,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1171,10 +1159,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= @@ -1204,15 +1189,6 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1262,27 +1238,11 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ= -google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1303,15 +1263,11 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= 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= @@ -1325,8 +1281,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= diff --git a/control-plane/go.mod b/control-plane/go.mod index c916acb745..71396e2fd1 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,11 +10,11 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d github.com/hashicorp/consul-server-connection-manager v0.1.2 - github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 - github.com/hashicorp/consul/sdk v0.13.1 + github.com/hashicorp/consul/api v1.22.0-rc1 + github.com/hashicorp/consul/sdk v0.14.0-rc1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 - github.com/hashicorp/go-hclog v1.2.2 + github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-netaddrs v0.1.0 github.com/hashicorp/go-rootcerts v1.0.2 @@ -26,20 +26,20 @@ require ( github.com/mitchellh/cli v1.1.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.3 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 golang.org/x/text v0.9.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.26.1 - k8s.io/apimachinery v0.26.1 - k8s.io/client-go v0.26.1 - k8s.io/klog/v2 v2.90.1 + k8s.io/api v0.26.3 + k8s.io/apimachinery v0.26.3 + k8s.io/client-go v0.26.3 + k8s.io/klog/v2 v2.100.1 k8s.io/utils v0.0.0-20230209194617-a36077c30491 sigs.k8s.io/controller-runtime v0.14.6 - sigs.k8s.io/gateway-api v0.6.2 + sigs.k8s.io/gateway-api v0.7.1 ) require ( @@ -63,14 +63,14 @@ require ( github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 // indirect github.com/digitalocean/godo v1.7.5 // indirect github.com/dimchansky/utfbom v1.1.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.14.1 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -96,7 +96,7 @@ require ( github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/mdns v1.0.4 // indirect @@ -110,8 +110,8 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/linode/linodego v0.7.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect @@ -125,7 +125,7 @@ require ( github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/posener/complete v1.2.3 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect @@ -145,11 +145,11 @@ require ( go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.1.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/api v0.30.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -160,8 +160,8 @@ require ( gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.26.1 // indirect - k8s.io/component-base v0.26.1 // indirect + k8s.io/apiextensions-apiserver v0.26.3 // indirect + k8s.io/component-base v0.26.3 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 3248aba347..2f7cbde2fc 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -113,8 +113,9 @@ github.com/containernetworking/cni v1.1.1 h1:ky20T7c0MvKvbMOwS/FrlbNwjEoqJEUUYfs github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72MbxIxFUzKmcMCdt9Oi8RzpAxzTNQHD7o= @@ -140,8 +141,9 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= @@ -262,12 +264,12 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= github.com/hashicorp/consul-server-connection-manager v0.1.2 h1:tNVQHUPuMbd+cMdD8kd+qkZUYpmLmrHMAV/49f4L53I= github.com/hashicorp/consul-server-connection-manager v0.1.2/go.mod h1:NzQoVi1KcxGI2SangsDue8+ZPuXZWs+6BKAKrDNyg+w= -github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4 h1:6kUTk+YBgA5X5b3gNAoI18WEK4/t75LcWSimEgmpFdg= -github.com/hashicorp/consul/api v1.10.1-0.20230530193107-04a0d0133ae4/go.mod h1:tXfrC6o0yFTgAW46xd5Ic8STHc9oIBcRVBcwhX5KNCQ= +github.com/hashicorp/consul/api v1.22.0-rc1 h1:ePmGqndeMgaI38KUbSA/CqTzeEAIogXyWnfNJzglo70= +github.com/hashicorp/consul/api v1.22.0-rc1/go.mod h1:wtduXtbAqSGtBdi3tyA5SSAYGAG51rBejV9SEUBciMY= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= -github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= -github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0= +github.com/hashicorp/consul/sdk v0.14.0-rc1 h1:PuETOfN0uxl28i0Pq6rK7TBCrIl7psMbL0YTSje4KvM= +github.com/hashicorp/consul/sdk v0.14.0-rc1/go.mod h1:gHYeuDa0+0qRAD6Wwr6yznMBvBwHKoxSBoW5l73+saE= 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= @@ -280,13 +282,13 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 h1:WUwSDou+memX/pb6xnjA0PfAqEEJtdWSrK00kl8ySK8= github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530/go.mod h1:RH2Jr1/cCsZ1nRLmAOC65hp/gRehf55SsUIYV2+NAxI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= -github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -313,8 +315,8 @@ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjG github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/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.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= @@ -372,8 +374,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -386,14 +388,17 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -457,8 +462,9 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= @@ -495,6 +501,7 @@ github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOe github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -524,8 +531,9 @@ 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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480 h1:Dwnfdrk3KXpYRH9Kwrk9sHpZSOmrE7P9LBoNsYUJKR4= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480 h1:YEDZmv2ABU8QvwXEVTOQgVEQzDOByhz73vdjL6sERkE= @@ -647,8 +655,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -669,8 +677,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -732,15 +740,16 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -939,18 +948,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= -k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= -k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= -k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= -k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= -k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= -k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= -k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= -k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= +k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= +k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= +k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= +k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= +k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= +k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= +k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= +k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= @@ -960,8 +969,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= -sigs.k8s.io/gateway-api v0.6.2 h1:583XHiX2M2bKEA0SAdkoxL1nY73W1+/M+IAm8LJvbEA= -sigs.k8s.io/gateway-api v0.6.2/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0= +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/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= From 7f6e1cb5c4c2d8797944c1a3e0dcd12943f75138 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Fri, 9 Jun 2023 19:12:13 -0400 Subject: [PATCH 225/592] Ensure Reconciliation Stops (#2305) * first pass at halting: got httproute and api-gateway done * clean up test * Handle all set for infinite reconcile check * Add table tests for minimal setup * Added some odd field names to test normalization is handled correctly * Use funky casing http routes --- .../gateway_controller_integration_test.go | 1320 +++++++++++++++++ 1 file changed, 1320 insertions(+) create mode 100644 control-plane/api-gateway/controllers/gateway_controller_integration_test.go diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go new file mode 100644 index 0000000000..5dd5357d77 --- /dev/null +++ b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go @@ -0,0 +1,1320 @@ +package controllers + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "sync" + "testing" + "time" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul/api" +) + +func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(s)) + require.NoError(t, gwv1alpha2.Install(s)) + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + testCases := map[string]struct { + namespace string + certFn func(*testing.T, context.Context, client.WithWatch, string) *corev1.Secret + gwFn func(*testing.T, context.Context, client.WithWatch, string) *gwv1beta1.Gateway + httpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute + tcpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *v1alpha2.TCPRoute + }{ + "all fields set": { + namespace: "consul", + certFn: createCert, + gwFn: createAllFieldsSetAPIGW, + httpRouteFn: createAllFieldsSetHTTPRoute, + tcpRouteFn: createAllFieldsSetTCPRoute, + }, + "minimal fields set": { + namespace: "", + certFn: createCert, + gwFn: minimalFieldsSetAPIGW, + httpRouteFn: minimalFieldsSetHTTPRoute, + tcpRouteFn: minimalFieldsSetTCPRoute, + }, + "funky casing to test normalization doesnt cause infinite reconciliation": { + namespace: "", + certFn: createCert, + gwFn: createFunkyCasingFieldsAPIGW, + httpRouteFn: createFunkyCasingFieldsHTTPRoute, + tcpRouteFn: createFunkyCasingFieldsTCPRoute, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + k8sClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s)).Build() + consulTestServerClient := test.TestServerWithMockConnMgrWatcher(t, nil) + ctx, cancel := context.WithCancel(context.Background()) + + t.Cleanup(func() { + cancel() + }) + logger := logrtest.New(t) + + cacheCfg := cache.Config{ + ConsulClientConfig: consulTestServerClient.Cfg, + ConsulServerConnMgr: consulTestServerClient.Watcher, + Logger: logger, + } + resourceCache := cache.New(cacheCfg) + + gwCache := cache.NewGatewayCache(ctx, cacheCfg) + + gwCtrl := GatewayController{ + HelmConfig: common.HelmConfig{}, + Log: logger, + Translator: common.ResourceTranslator{}, + cache: resourceCache, + gatewayCache: gwCache, + Client: k8sClient, + allowK8sNamespacesSet: mapset.NewSet(), + denyK8sNamespacesSet: mapset.NewSet(), + } + + go func() { + resourceCache.Run(ctx) + }() + + resourceCache.WaitSynced(ctx) + + gwSub := resourceCache.Subscribe(ctx, api.APIGateway, gwCtrl.transformConsulGateway) + httpRouteSub := resourceCache.Subscribe(ctx, api.HTTPRoute, gwCtrl.transformConsulHTTPRoute(ctx)) + tcpRouteSub := resourceCache.Subscribe(ctx, api.TCPRoute, gwCtrl.transformConsulTCPRoute(ctx)) + inlineCertSub := resourceCache.Subscribe(ctx, api.InlineCertificate, gwCtrl.transformConsulInlineCertificate(ctx)) + + cert := tc.certFn(t, ctx, k8sClient, tc.namespace) + k8sGWObj := tc.gwFn(t, ctx, k8sClient, tc.namespace) + + // reconcile so we add the finalizer + _, err := gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + Name: k8sGWObj.Name, + }, + }) + require.NoError(t, err) + + // reconcile again so that we get the creation with the finalizer + _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + Name: k8sGWObj.Name, + }, + }) + require.NoError(t, err) + + httpRouteObj := tc.httpRouteFn(t, ctx, k8sClient, k8sGWObj) + tcpRouteObj := tc.tcpRouteFn(t, ctx, k8sClient, k8sGWObj) + + // reconcile again so that we get the route bound to the gateway + _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + Name: k8sGWObj.Name, + }, + }) + require.NoError(t, err) + + // reconcile again so that we get the route bound to the gateway + _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + Name: k8sGWObj.Name, + }, + }) + require.NoError(t, err) + + wg := &sync.WaitGroup{} + // we never get the event from the cert because when it's created there are no gateways that reference it + wg.Add(3) + go func(w *sync.WaitGroup) { + gwDone := false + httpRouteDone := false + tcpRouteDone := false + for { + // get the creation events from the upsert and then continually read from channel so we dont block other subs + select { + case <-ctx.Done(): + return + case <-gwSub.Events(): + if !gwDone { + gwDone = true + wg.Done() + } + case <-httpRouteSub.Events(): + if !httpRouteDone { + httpRouteDone = true + wg.Done() + } + case <-tcpRouteSub.Events(): + if !tcpRouteDone { + tcpRouteDone = true + wg.Done() + } + case <-inlineCertSub.Events(): + } + } + }(wg) + + wg.Wait() + + gwNamespaceName := types.NamespacedName{ + Name: k8sGWObj.Name, + Namespace: k8sGWObj.Namespace, + } + + httpRouteNamespaceName := types.NamespacedName{ + Name: httpRouteObj.Name, + Namespace: httpRouteObj.Namespace, + } + + tcpRouteNamespaceName := types.NamespacedName{ + Name: tcpRouteObj.Name, + Namespace: tcpRouteObj.Namespace, + } + + certNamespaceName := types.NamespacedName{ + Name: cert.Name, + Namespace: cert.Namespace, + } + + gwRef := gwCtrl.Translator.ConfigEntryReference(api.APIGateway, gwNamespaceName) + httpRouteRef := gwCtrl.Translator.ConfigEntryReference(api.HTTPRoute, httpRouteNamespaceName) + tcpRouteRef := gwCtrl.Translator.ConfigEntryReference(api.TCPRoute, tcpRouteNamespaceName) + certRef := gwCtrl.Translator.ConfigEntryReference(api.InlineCertificate, certNamespaceName) + + curGWModifyIndex := resourceCache.Get(gwRef).GetModifyIndex() + curHTTPRouteModifyIndex := resourceCache.Get(httpRouteRef).GetModifyIndex() + curTCPRouteModifyIndex := resourceCache.Get(tcpRouteRef).GetModifyIndex() + curCertModifyIndex := resourceCache.Get(certRef).GetModifyIndex() + + err = k8sClient.Get(ctx, gwNamespaceName, k8sGWObj) + require.NoError(t, err) + curGWResourceVersion := k8sGWObj.ResourceVersion + + err = k8sClient.Get(ctx, httpRouteNamespaceName, httpRouteObj) + require.NoError(t, err) + curHTTPRouteResourceVersion := httpRouteObj.ResourceVersion + + err = k8sClient.Get(ctx, tcpRouteNamespaceName, tcpRouteObj) + require.NoError(t, err) + curTCPRouteResourceVersion := tcpRouteObj.ResourceVersion + + err = k8sClient.Get(ctx, certNamespaceName, cert) + require.NoError(t, err) + curCertResourceVersion := cert.ResourceVersion + + go func() { + // reconcile multiple times with no changes to be sure + for i := 0; i < 5; i++ { + _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + }, + }) + require.NoError(t, err) + } + }() + + require.Never(t, func() bool { + err = k8sClient.Get(ctx, gwNamespaceName, k8sGWObj) + require.NoError(t, err) + newGWResourceVersion := k8sGWObj.ResourceVersion + + err = k8sClient.Get(ctx, httpRouteNamespaceName, httpRouteObj) + require.NoError(t, err) + newHTTPRouteResourceVersion := httpRouteObj.ResourceVersion + + err = k8sClient.Get(ctx, tcpRouteNamespaceName, tcpRouteObj) + require.NoError(t, err) + newTCPRouteResourceVersion := tcpRouteObj.ResourceVersion + + err = k8sClient.Get(ctx, certNamespaceName, cert) + require.NoError(t, err) + newCertResourceVersion := cert.ResourceVersion + + return curGWModifyIndex == resourceCache.Get(gwRef).GetModifyIndex() && + curGWResourceVersion == newGWResourceVersion && + curHTTPRouteModifyIndex == resourceCache.Get(httpRouteRef).GetModifyIndex() && + curHTTPRouteResourceVersion == newHTTPRouteResourceVersion && + curTCPRouteModifyIndex == resourceCache.Get(tcpRouteRef).GetModifyIndex() && + curTCPRouteResourceVersion == newTCPRouteResourceVersion && + curCertModifyIndex == resourceCache.Get(certRef).GetModifyIndex() && + curCertResourceVersion == newCertResourceVersion + }, time.Duration(2*time.Second), time.Duration(500*time.Millisecond), fmt.Sprintf("curGWModifyIndex: %d, newIndx: %d", curGWModifyIndex, resourceCache.Get(gwRef).GetModifyIndex()), + ) + }) + } +} + +func createAllFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { + // listener one configuration + listenerOneName := "listener-one" + listenerOneHostname := "*.consul.io" + listenerOnePort := 3366 + listenerOneProtocol := "https" + + // listener two configuration + listenerTwoName := "listener-two" + listenerTwoHostname := "*.consul.io" + listenerTwoPort := 5432 + listenerTwoProtocol := "http" + + // listener three configuration + listenerThreeName := "listener-three" + listenerThreePort := 8081 + listenerThreeProtocol := "tcp" + + // listener four configuration + listenerFourName := "listener-four" + listenerFourHostname := "*.consul.io" + listenerFourPort := 5433 + listenerFourProtocol := "http" + + // Write gw to k8s + gwClassCfg := &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClassConfig", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{}, + } + gwClass := &gwv1beta1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClass", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass", + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "hashicorp.com/consul-api-gateway-controller", + ParametersRef: &gwv1beta1.ParametersReference{ + Group: "consul.hashicorp.com", + Kind: "GatewayClassConfig", + Name: "gateway-class-config", + }, + Description: new(string), + }, + } + gw := &gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: namespace, + Annotations: make(map[string]string), + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), + Listeners: []gwv1beta1.Listener{ + { + Name: gwv1beta1.SectionName(listenerOneName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), + Port: gwv1beta1.PortNumber(listenerOnePort), + Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + { + Kind: common.PointerTo(gwv1beta1.Kind("Secret")), + Name: gwv1beta1.ObjectName("one-cert"), + Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), + }, + }, + }, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerTwoName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), + Port: gwv1beta1.PortNumber(listenerTwoPort), + Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("Same")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerThreeName), + Port: gwv1beta1.PortNumber(listenerThreePort), + Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerFourName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerFourHostname)), + Port: gwv1beta1.PortNumber(listenerFourPort), + Protocol: gwv1beta1.ProtocolType(listenerFourProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("Selector")), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + common.NamespaceNameLabel: "consul", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{}, + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, gwClassCfg) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gwClass) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gw) + require.NoError(t, err) + + return gw +} + +func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { + svcDefault := &v1alpha1.ServiceDefaults{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceDefaults", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + } + + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "high", + Protocol: "TCP", + Port: 8080, + }, + }, + Selector: map[string]string{"app": "Service"}, + }, + } + + serviceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + } + + deployment := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: common.PointerTo(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "Service"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{}, + }, + }, + } + + err := k8sClient.Create(ctx, svcDefault) + require.NoError(t, err) + + err = k8sClient.Create(ctx, svc) + require.NoError(t, err) + + err = k8sClient.Create(ctx, serviceAccount) + require.NoError(t, err) + + err = k8sClient.Create(ctx, deployment) + require.NoError(t, err) + + route := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[0].Name, + Port: &gw.Spec.Listeners[0].Port, + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: common.PointerTo(gwv1beta1.PathMatchType("PathPrefix")), + Value: common.PointerTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: common.PointerTo(gwv1beta1.HeaderMatchExact), + Name: "version", + Value: "version", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "q", + }, + }, + Method: common.PointerTo(gwv1beta1.HTTPMethod("GET")), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "foo", + Value: "bax", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "arc", + Value: "reactor", + }, + }, + Remove: []string{"remove"}, + }, + }, + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.FullPathHTTPPathModifier, + ReplaceFullPath: common.PointerTo("/foobar"), + }, + }, + }, + + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: common.PointerTo("/foo"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(8080)), + }, + Weight: common.PointerTo(int32(50)), + }, + }, + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createAllFieldsSetTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { + route := &v1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "TCPRoute", + APIVersion: "gateway.networking.k8s.io/v1alpha2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[2].Name, + Port: &gw.Spec.Listeners[2].Port, + }, + }, + }, + Rules: []gwv1alpha2.TCPRouteRule{ + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(25000)), + }, + Weight: common.PointerTo(int32(50)), + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createCert(t *testing.T, ctx context.Context, k8sClient client.WithWatch, certNS string) *corev1.Secret { + // listener one tls config + certName := "one-cert" + + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(t, err) + + usage := x509.KeyUsageCertSign + expiration := time.Now().AddDate(10, 0, 0) + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "consul.test", + }, + IsCA: true, + NotBefore: time.Now().Add(-10 * time.Minute), + NotAfter: expiration, + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: usage, + BasicConstraintsValid: true, + } + caCert := cert + caPrivateKey := privateKey + + data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) + require.NoError(t, err) + + certBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: data, + }) + + privateKeyBytes := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: certNS, + Name: certName, + }, + Data: map[string][]byte{ + corev1.TLSCertKey: certBytes, + corev1.TLSPrivateKeyKey: privateKeyBytes, + }, + } + + err = k8sClient.Create(ctx, secret) + require.NoError(t, err) + + return secret +} + +func minimalFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { + // listener one configuration + listenerOneName := "listener-one" + listenerOneHostname := "*.consul.io" + listenerOnePort := 3366 + listenerOneProtocol := "https" + + // listener three configuration + listenerThreeName := "listener-three" + listenerThreePort := 8081 + listenerThreeProtocol := "tcp" + + // Write gw to k8s + gwClassCfg := &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClassConfig", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{}, + } + gwClass := &gwv1beta1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClass", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass", + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "hashicorp.com/consul-api-gateway-controller", + ParametersRef: &gwv1beta1.ParametersReference{ + Group: "consul.hashicorp.com", + Kind: "GatewayClassConfig", + Name: "gateway-class-config", + }, + Description: new(string), + }, + } + gw := &gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Annotations: make(map[string]string), + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), + Listeners: []gwv1beta1.Listener{ + { + Name: gwv1beta1.SectionName(listenerOneName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), + Port: gwv1beta1.PortNumber(listenerOnePort), + Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + { + Kind: common.PointerTo(gwv1beta1.Kind("Secret")), + Name: gwv1beta1.ObjectName("one-cert"), + Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), + }, + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerThreeName), + Port: gwv1beta1.PortNumber(listenerThreePort), + Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, gwClassCfg) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gwClass) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gw) + require.NoError(t, err) + + return gw +} + +func minimalFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { + svcDefault := &v1alpha1.ServiceDefaults{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceDefaults", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + } + + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "high", + Protocol: "TCP", + Port: 8080, + }, + }, + Selector: map[string]string{"app": "Service"}, + }, + } + + serviceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + } + + deployment := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: common.PointerTo(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "Service"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{}, + }, + }, + } + + err := k8sClient.Create(ctx, svcDefault) + require.NoError(t, err) + + err = k8sClient.Create(ctx, svc) + require.NoError(t, err) + + err = k8sClient.Create(ctx, serviceAccount) + require.NoError(t, err) + + err = k8sClient.Create(ctx, deployment) + require.NoError(t, err) + + route := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[0].Name, + Port: &gw.Spec.Listeners[0].Port, + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, + Rules: []gwv1beta1.HTTPRouteRule{ + { + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(8080)), + }, + }, + }, + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func minimalFieldsSetTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { + route := &v1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "TCPRoute", + APIVersion: "gateway.networking.k8s.io/v1alpha2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[1].Name, + Port: &gw.Spec.Listeners[1].Port, + }, + }, + }, + Rules: []gwv1alpha2.TCPRouteRule{ + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(25000)), + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createFunkyCasingFieldsAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { + // listener one configuration + listenerOneName := "listener-one" + listenerOneHostname := "*.consul.io" + listenerOnePort := 3366 + listenerOneProtocol := "hTtPs" + + // listener two configuration + listenerTwoName := "listener-two" + listenerTwoHostname := "*.consul.io" + listenerTwoPort := 5432 + listenerTwoProtocol := "HTTP" + + // listener three configuration + listenerThreeName := "listener-three" + listenerThreePort := 8081 + listenerThreeProtocol := "tCp" + + // listener four configuration + listenerFourName := "listener-four" + listenerFourHostname := "*.consul.io" + listenerFourPort := 5433 + listenerFourProtocol := "hTTp" + + // Write gw to k8s + gwClassCfg := &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClassConfig", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{}, + } + gwClass := &gwv1beta1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClass", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass", + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "hashicorp.com/consul-api-gateway-controller", + ParametersRef: &gwv1beta1.ParametersReference{ + Group: "consul.hashicorp.com", + Kind: "GatewayClassConfig", + Name: "gateway-class-config", + }, + Description: new(string), + }, + } + gw := &gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: namespace, + Annotations: make(map[string]string), + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), + Listeners: []gwv1beta1.Listener{ + { + Name: gwv1beta1.SectionName(listenerOneName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), + Port: gwv1beta1.PortNumber(listenerOnePort), + Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + { + Kind: common.PointerTo(gwv1beta1.Kind("Secret")), + Name: gwv1beta1.ObjectName("one-cert"), + Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), + }, + }, + }, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerTwoName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), + Port: gwv1beta1.PortNumber(listenerTwoPort), + Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("Same")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerThreeName), + Port: gwv1beta1.PortNumber(listenerThreePort), + Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerFourName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerFourHostname)), + Port: gwv1beta1.PortNumber(listenerFourPort), + Protocol: gwv1beta1.ProtocolType(listenerFourProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("Selector")), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + common.NamespaceNameLabel: "consul", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{}, + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, gwClassCfg) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gwClass) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gw) + require.NoError(t, err) + + return gw +} + +func createFunkyCasingFieldsHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { + svcDefault := &v1alpha1.ServiceDefaults{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceDefaults", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "hTtp", + }, + } + + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "high", + Protocol: "TCP", + Port: 8080, + }, + }, + Selector: map[string]string{"app": "Service"}, + }, + } + + serviceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + } + + deployment := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: common.PointerTo(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "Service"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{}, + }, + }, + } + + err := k8sClient.Create(ctx, svcDefault) + require.NoError(t, err) + + err = k8sClient.Create(ctx, svc) + require.NoError(t, err) + + err = k8sClient.Create(ctx, serviceAccount) + require.NoError(t, err) + + err = k8sClient.Create(ctx, deployment) + require.NoError(t, err) + + route := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[0].Name, + Port: &gw.Spec.Listeners[0].Port, + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: common.PointerTo(gwv1beta1.PathMatchPathPrefix), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: common.PointerTo(gwv1beta1.HeaderMatchExact), + Name: "version", + Value: "version", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "q", + }, + }, + Method: common.PointerTo(gwv1beta1.HTTPMethod("geT")), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "foo", + Value: "bax", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "arc", + Value: "reactor", + }, + }, + Remove: []string{"remove"}, + }, + }, + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.FullPathHTTPPathModifier, + ReplaceFullPath: common.PointerTo("/foobar"), + }, + }, + }, + + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: common.PointerTo("/foo"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(8080)), + }, + Weight: common.PointerTo(int32(-50)), + }, + }, + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createFunkyCasingFieldsTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { + route := &v1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "TCPRoute", + APIVersion: "gateway.networking.k8s.io/v1alpha2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[2].Name, + Port: &gw.Spec.Listeners[2].Port, + }, + }, + }, + Rules: []gwv1alpha2.TCPRouteRule{ + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(25000)), + }, + Weight: common.PointerTo(int32(-50)), + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} From 7e076bb6f9d95e6214b0a2ef66caeb8450740d84 Mon Sep 17 00:00:00 2001 From: skpratt Date: Fri, 9 Jun 2023 23:24:35 -0500 Subject: [PATCH 226/592] Add CRT docker changes for release workflow (#2333) --- .github/workflows/build.yml | 74 +++++++++++++++++++++++++++++++++++-- control-plane/Dockerfile | 5 +++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8eef629401..e46e16c9a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -271,6 +271,7 @@ jobs: unzip -j *.zip - name: Docker Build (Action) uses: hashicorp/actions-docker-build@v1 + if: ${{ !matrix.fips }} with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -291,6 +292,29 @@ jobs: hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }} docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-${{ github.sha }} + - name: Docker FIPS Build (Action) + uses: hashicorp/actions-docker-build@v1 + if: ${{ matrix.fips }} + with: + smoke_test: | + TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" + if [ "${TEST_VERSION}" != "v${version}" ]; then + echo "Test FAILED" + exit 1 + fi + echo "Test PASSED" + version: ${{ env.version }} + target: release-default-fips # duplicate target to distinguish FIPS builds in CRT machinery + arch: ${{ matrix.goarch }} + pkg_name: consul-k8s-control-plane_${{ env.version }} + bin_name: consul-k8s-control-plane + workdir: control-plane + tags: | + docker.io/hashicorp/${{ env.repo }}-control-plane-fips:${{ env.version }} + dev_tags: | + hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.version }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.version }}-${{ github.sha }} + build-docker-ubi-redhat-registry: name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI build for RedHat Registry needs: [get-product-version, build] @@ -318,7 +342,9 @@ jobs: - name: Copy LICENSE run: cp LICENSE ./control-plane - - uses: hashicorp/actions-docker-build@v1 + - name: Docker Build (Action) + if: ${{ !matrix.fips }} + uses: hashicorp/actions-docker-build@v1 with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -334,6 +360,24 @@ jobs: bin_name: consul-k8s-control-plane workdir: control-plane redhat_tag: quay.io/redhat-isv-containers/611ca2f89a9b407267837100:${{env.version}}-ubi + - name: Docker FIPS Build (Action) + if: ${{ matrix.fips }} + uses: hashicorp/actions-docker-build@v1 + with: + smoke_test: | + TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" + if [ "${TEST_VERSION}" != "v${version}" ]; then + echo "Test FAILED" + exit 1 + fi + echo "Test PASSED" + version: ${{ env.version }} + target: ubi-fips # duplicate target to distinguish FIPS builds in CRT machinery + arch: ${{ matrix.arch }} + pkg_name: consul-k8s-control-plane_${{ env.version }} + bin_name: consul-k8s-control-plane + workdir: control-plane + redhat_tag: quay.io/redhat-isv-containers/6483ed53b430df51b731406c:${{env.version}}-ubi # this is different than the non-FIPS one build-docker-ubi-dockerhub: name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI build for DockerHub @@ -361,7 +405,9 @@ jobs: - name: Copy LICENSE run: cp LICENSE ./control-plane - - uses: hashicorp/actions-docker-build@v1 + - name: Docker Build (Action) + uses: hashicorp/actions-docker-build@v1 + if: ${{ !matrix.fips }} with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -380,4 +426,26 @@ jobs: docker.io/hashicorp/${{ env.repo }}-control-plane:${{ env.version }}-ubi dev_tags: | hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-ubi - docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-ubi-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-ubi-${{ github.sha }} + - name: Docker FIPS Build (Action) + uses: hashicorp/actions-docker-build@v1 + if: ${{ matrix.fips }} + with: + smoke_test: | + TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" + if [ "${TEST_VERSION}" != "v${version}" ]; then + echo "Test FAILED" + exit 1 + fi + echo "Test PASSED" + version: ${{ env.version }} + target: ubi-fips # duplicate target to distinguish FIPS builds in CRT machinery + arch: ${{ matrix.arch }} + pkg_name: consul-k8s-control-plane_${{ env.version }} + bin_name: consul-k8s-control-plane + workdir: control-plane + tags: | + docker.io/hashicorp/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi + dev_tags: | + hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi + docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi-${{ github.sha }} diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index 5b3d73e625..f401ac8262 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -113,6 +113,9 @@ COPY dist/cni/${TARGETOS}/${TARGETARCH}/${CNI_BIN_NAME} /bin/ USER 100 CMD /bin/${BIN_NAME} +# Duplicate target for FIPS builds +FROM release-default AS release-default-fips + # ----------------------------------- # Dockerfile target for consul-k8s with UBI as its base image. Used for running on # OpenShift. @@ -175,6 +178,8 @@ COPY dist/cni/${TARGETOS}/${TARGETARCH}/${CNI_BIN_NAME} /bin/ USER 100 CMD /bin/${BIN_NAME} +# Duplicate target for FIPS builds +FROM ubi AS ubi-fips # =================================== # # Set default target to 'dev'. From 497621505160edd41ef209522f8924c90166c44d Mon Sep 17 00:00:00 2001 From: skpratt Date: Sun, 11 Jun 2023 16:45:14 -0500 Subject: [PATCH 227/592] Update var check with appropriate quotes (#2330) --- control-plane/build-support/functions/20-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/build-support/functions/20-build.sh b/control-plane/build-support/functions/20-build.sh index e9540956c9..dac626b88f 100644 --- a/control-plane/build-support/functions/20-build.sh +++ b/control-plane/build-support/functions/20-build.sh @@ -188,7 +188,7 @@ function build_consul_local { # build with go install. # The GOXPARALLEL environment variable is used if set - if [ $GOTAGS == "fips" ]; then + if [ "${GOTAGS:-}" == "fips" ]; then CGO_ENABLED=1 else CGO_ENABLED=0 From 60b214e19707f4739b628ed0b979a24eac903b3a Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Mon, 12 Jun 2023 12:50:16 -0400 Subject: [PATCH 228/592] Revert "Ensure Reconciliation Stops (#2305)" (#2341) This reverts commit 7f6e1cb5c4c2d8797944c1a3e0dcd12943f75138. --- .../gateway_controller_integration_test.go | 1320 ----------------- 1 file changed, 1320 deletions(-) delete mode 100644 control-plane/api-gateway/controllers/gateway_controller_integration_test.go diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go deleted file mode 100644 index 5dd5357d77..0000000000 --- a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go +++ /dev/null @@ -1,1320 +0,0 @@ -package controllers - -import ( - "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "math/big" - "sync" - "testing" - "time" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul/api" -) - -func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, clientgoscheme.AddToScheme(s)) - require.NoError(t, gwv1alpha2.Install(s)) - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - testCases := map[string]struct { - namespace string - certFn func(*testing.T, context.Context, client.WithWatch, string) *corev1.Secret - gwFn func(*testing.T, context.Context, client.WithWatch, string) *gwv1beta1.Gateway - httpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute - tcpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *v1alpha2.TCPRoute - }{ - "all fields set": { - namespace: "consul", - certFn: createCert, - gwFn: createAllFieldsSetAPIGW, - httpRouteFn: createAllFieldsSetHTTPRoute, - tcpRouteFn: createAllFieldsSetTCPRoute, - }, - "minimal fields set": { - namespace: "", - certFn: createCert, - gwFn: minimalFieldsSetAPIGW, - httpRouteFn: minimalFieldsSetHTTPRoute, - tcpRouteFn: minimalFieldsSetTCPRoute, - }, - "funky casing to test normalization doesnt cause infinite reconciliation": { - namespace: "", - certFn: createCert, - gwFn: createFunkyCasingFieldsAPIGW, - httpRouteFn: createFunkyCasingFieldsHTTPRoute, - tcpRouteFn: createFunkyCasingFieldsTCPRoute, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - k8sClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s)).Build() - consulTestServerClient := test.TestServerWithMockConnMgrWatcher(t, nil) - ctx, cancel := context.WithCancel(context.Background()) - - t.Cleanup(func() { - cancel() - }) - logger := logrtest.New(t) - - cacheCfg := cache.Config{ - ConsulClientConfig: consulTestServerClient.Cfg, - ConsulServerConnMgr: consulTestServerClient.Watcher, - Logger: logger, - } - resourceCache := cache.New(cacheCfg) - - gwCache := cache.NewGatewayCache(ctx, cacheCfg) - - gwCtrl := GatewayController{ - HelmConfig: common.HelmConfig{}, - Log: logger, - Translator: common.ResourceTranslator{}, - cache: resourceCache, - gatewayCache: gwCache, - Client: k8sClient, - allowK8sNamespacesSet: mapset.NewSet(), - denyK8sNamespacesSet: mapset.NewSet(), - } - - go func() { - resourceCache.Run(ctx) - }() - - resourceCache.WaitSynced(ctx) - - gwSub := resourceCache.Subscribe(ctx, api.APIGateway, gwCtrl.transformConsulGateway) - httpRouteSub := resourceCache.Subscribe(ctx, api.HTTPRoute, gwCtrl.transformConsulHTTPRoute(ctx)) - tcpRouteSub := resourceCache.Subscribe(ctx, api.TCPRoute, gwCtrl.transformConsulTCPRoute(ctx)) - inlineCertSub := resourceCache.Subscribe(ctx, api.InlineCertificate, gwCtrl.transformConsulInlineCertificate(ctx)) - - cert := tc.certFn(t, ctx, k8sClient, tc.namespace) - k8sGWObj := tc.gwFn(t, ctx, k8sClient, tc.namespace) - - // reconcile so we add the finalizer - _, err := gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - Name: k8sGWObj.Name, - }, - }) - require.NoError(t, err) - - // reconcile again so that we get the creation with the finalizer - _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - Name: k8sGWObj.Name, - }, - }) - require.NoError(t, err) - - httpRouteObj := tc.httpRouteFn(t, ctx, k8sClient, k8sGWObj) - tcpRouteObj := tc.tcpRouteFn(t, ctx, k8sClient, k8sGWObj) - - // reconcile again so that we get the route bound to the gateway - _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - Name: k8sGWObj.Name, - }, - }) - require.NoError(t, err) - - // reconcile again so that we get the route bound to the gateway - _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - Name: k8sGWObj.Name, - }, - }) - require.NoError(t, err) - - wg := &sync.WaitGroup{} - // we never get the event from the cert because when it's created there are no gateways that reference it - wg.Add(3) - go func(w *sync.WaitGroup) { - gwDone := false - httpRouteDone := false - tcpRouteDone := false - for { - // get the creation events from the upsert and then continually read from channel so we dont block other subs - select { - case <-ctx.Done(): - return - case <-gwSub.Events(): - if !gwDone { - gwDone = true - wg.Done() - } - case <-httpRouteSub.Events(): - if !httpRouteDone { - httpRouteDone = true - wg.Done() - } - case <-tcpRouteSub.Events(): - if !tcpRouteDone { - tcpRouteDone = true - wg.Done() - } - case <-inlineCertSub.Events(): - } - } - }(wg) - - wg.Wait() - - gwNamespaceName := types.NamespacedName{ - Name: k8sGWObj.Name, - Namespace: k8sGWObj.Namespace, - } - - httpRouteNamespaceName := types.NamespacedName{ - Name: httpRouteObj.Name, - Namespace: httpRouteObj.Namespace, - } - - tcpRouteNamespaceName := types.NamespacedName{ - Name: tcpRouteObj.Name, - Namespace: tcpRouteObj.Namespace, - } - - certNamespaceName := types.NamespacedName{ - Name: cert.Name, - Namespace: cert.Namespace, - } - - gwRef := gwCtrl.Translator.ConfigEntryReference(api.APIGateway, gwNamespaceName) - httpRouteRef := gwCtrl.Translator.ConfigEntryReference(api.HTTPRoute, httpRouteNamespaceName) - tcpRouteRef := gwCtrl.Translator.ConfigEntryReference(api.TCPRoute, tcpRouteNamespaceName) - certRef := gwCtrl.Translator.ConfigEntryReference(api.InlineCertificate, certNamespaceName) - - curGWModifyIndex := resourceCache.Get(gwRef).GetModifyIndex() - curHTTPRouteModifyIndex := resourceCache.Get(httpRouteRef).GetModifyIndex() - curTCPRouteModifyIndex := resourceCache.Get(tcpRouteRef).GetModifyIndex() - curCertModifyIndex := resourceCache.Get(certRef).GetModifyIndex() - - err = k8sClient.Get(ctx, gwNamespaceName, k8sGWObj) - require.NoError(t, err) - curGWResourceVersion := k8sGWObj.ResourceVersion - - err = k8sClient.Get(ctx, httpRouteNamespaceName, httpRouteObj) - require.NoError(t, err) - curHTTPRouteResourceVersion := httpRouteObj.ResourceVersion - - err = k8sClient.Get(ctx, tcpRouteNamespaceName, tcpRouteObj) - require.NoError(t, err) - curTCPRouteResourceVersion := tcpRouteObj.ResourceVersion - - err = k8sClient.Get(ctx, certNamespaceName, cert) - require.NoError(t, err) - curCertResourceVersion := cert.ResourceVersion - - go func() { - // reconcile multiple times with no changes to be sure - for i := 0; i < 5; i++ { - _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - }, - }) - require.NoError(t, err) - } - }() - - require.Never(t, func() bool { - err = k8sClient.Get(ctx, gwNamespaceName, k8sGWObj) - require.NoError(t, err) - newGWResourceVersion := k8sGWObj.ResourceVersion - - err = k8sClient.Get(ctx, httpRouteNamespaceName, httpRouteObj) - require.NoError(t, err) - newHTTPRouteResourceVersion := httpRouteObj.ResourceVersion - - err = k8sClient.Get(ctx, tcpRouteNamespaceName, tcpRouteObj) - require.NoError(t, err) - newTCPRouteResourceVersion := tcpRouteObj.ResourceVersion - - err = k8sClient.Get(ctx, certNamespaceName, cert) - require.NoError(t, err) - newCertResourceVersion := cert.ResourceVersion - - return curGWModifyIndex == resourceCache.Get(gwRef).GetModifyIndex() && - curGWResourceVersion == newGWResourceVersion && - curHTTPRouteModifyIndex == resourceCache.Get(httpRouteRef).GetModifyIndex() && - curHTTPRouteResourceVersion == newHTTPRouteResourceVersion && - curTCPRouteModifyIndex == resourceCache.Get(tcpRouteRef).GetModifyIndex() && - curTCPRouteResourceVersion == newTCPRouteResourceVersion && - curCertModifyIndex == resourceCache.Get(certRef).GetModifyIndex() && - curCertResourceVersion == newCertResourceVersion - }, time.Duration(2*time.Second), time.Duration(500*time.Millisecond), fmt.Sprintf("curGWModifyIndex: %d, newIndx: %d", curGWModifyIndex, resourceCache.Get(gwRef).GetModifyIndex()), - ) - }) - } -} - -func createAllFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { - // listener one configuration - listenerOneName := "listener-one" - listenerOneHostname := "*.consul.io" - listenerOnePort := 3366 - listenerOneProtocol := "https" - - // listener two configuration - listenerTwoName := "listener-two" - listenerTwoHostname := "*.consul.io" - listenerTwoPort := 5432 - listenerTwoProtocol := "http" - - // listener three configuration - listenerThreeName := "listener-three" - listenerThreePort := 8081 - listenerThreeProtocol := "tcp" - - // listener four configuration - listenerFourName := "listener-four" - listenerFourHostname := "*.consul.io" - listenerFourPort := 5433 - listenerFourProtocol := "http" - - // Write gw to k8s - gwClassCfg := &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClassConfig", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-class-config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{}, - } - gwClass := &gwv1beta1.GatewayClass{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClass", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewayclass", - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "hashicorp.com/consul-api-gateway-controller", - ParametersRef: &gwv1beta1.ParametersReference{ - Group: "consul.hashicorp.com", - Kind: "GatewayClassConfig", - Name: "gateway-class-config", - }, - Description: new(string), - }, - } - gw := &gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "Gateway", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: namespace, - Annotations: make(map[string]string), - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName(listenerOneName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), - Port: gwv1beta1.PortNumber(listenerOnePort), - Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - { - Kind: common.PointerTo(gwv1beta1.Kind("Secret")), - Name: gwv1beta1.ObjectName("one-cert"), - Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), - }, - }, - }, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerTwoName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), - Port: gwv1beta1.PortNumber(listenerTwoPort), - Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("Same")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerThreeName), - Port: gwv1beta1.PortNumber(listenerThreePort), - Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerFourName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerFourHostname)), - Port: gwv1beta1.PortNumber(listenerFourPort), - Protocol: gwv1beta1.ProtocolType(listenerFourProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("Selector")), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.NamespaceNameLabel: "consul", - }, - MatchExpressions: []metav1.LabelSelectorRequirement{}, - }, - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, gwClassCfg) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gwClass) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gw) - require.NoError(t, err) - - return gw -} - -func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { - svcDefault := &v1alpha1.ServiceDefaults{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceDefaults", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "high", - Protocol: "TCP", - Port: 8080, - }, - }, - Selector: map[string]string{"app": "Service"}, - }, - } - - serviceAccount := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - } - - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "Service"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{}, - }, - }, - } - - err := k8sClient.Create(ctx, svcDefault) - require.NoError(t, err) - - err = k8sClient.Create(ctx, svc) - require.NoError(t, err) - - err = k8sClient.Create(ctx, serviceAccount) - require.NoError(t, err) - - err = k8sClient.Create(ctx, deployment) - require.NoError(t, err) - - route := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[0].Name, - Port: &gw.Spec.Listeners[0].Port, - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: common.PointerTo(gwv1beta1.PathMatchType("PathPrefix")), - Value: common.PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: common.PointerTo(gwv1beta1.HeaderMatchExact), - Name: "version", - Value: "version", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "q", - }, - }, - Method: common.PointerTo(gwv1beta1.HTTPMethod("GET")), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "foo", - Value: "bax", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "arc", - Value: "reactor", - }, - }, - Remove: []string{"remove"}, - }, - }, - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.FullPathHTTPPathModifier, - ReplaceFullPath: common.PointerTo("/foobar"), - }, - }, - }, - - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: common.PointerTo("/foo"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(8080)), - }, - Weight: common.PointerTo(int32(50)), - }, - }, - }, - }, - }, - }, - } - - err = k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createAllFieldsSetTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { - route := &v1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "TCPRoute", - APIVersion: "gateway.networking.k8s.io/v1alpha2", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[2].Name, - Port: &gw.Spec.Listeners[2].Port, - }, - }, - }, - Rules: []gwv1alpha2.TCPRouteRule{ - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(25000)), - }, - Weight: common.PointerTo(int32(50)), - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createCert(t *testing.T, ctx context.Context, k8sClient client.WithWatch, certNS string) *corev1.Secret { - // listener one tls config - certName := "one-cert" - - privateKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(t, err) - - usage := x509.KeyUsageCertSign - expiration := time.Now().AddDate(10, 0, 0) - - cert := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: "consul.test", - }, - IsCA: true, - NotBefore: time.Now().Add(-10 * time.Minute), - NotAfter: expiration, - SubjectKeyId: []byte{1, 2, 3, 4, 6}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: usage, - BasicConstraintsValid: true, - } - caCert := cert - caPrivateKey := privateKey - - data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) - require.NoError(t, err) - - certBytes := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: data, - }) - - privateKeyBytes := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: certNS, - Name: certName, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: certBytes, - corev1.TLSPrivateKeyKey: privateKeyBytes, - }, - } - - err = k8sClient.Create(ctx, secret) - require.NoError(t, err) - - return secret -} - -func minimalFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { - // listener one configuration - listenerOneName := "listener-one" - listenerOneHostname := "*.consul.io" - listenerOnePort := 3366 - listenerOneProtocol := "https" - - // listener three configuration - listenerThreeName := "listener-three" - listenerThreePort := 8081 - listenerThreeProtocol := "tcp" - - // Write gw to k8s - gwClassCfg := &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClassConfig", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-class-config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{}, - } - gwClass := &gwv1beta1.GatewayClass{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClass", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewayclass", - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "hashicorp.com/consul-api-gateway-controller", - ParametersRef: &gwv1beta1.ParametersReference{ - Group: "consul.hashicorp.com", - Kind: "GatewayClassConfig", - Name: "gateway-class-config", - }, - Description: new(string), - }, - } - gw := &gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "Gateway", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Annotations: make(map[string]string), - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName(listenerOneName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), - Port: gwv1beta1.PortNumber(listenerOnePort), - Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - { - Kind: common.PointerTo(gwv1beta1.Kind("Secret")), - Name: gwv1beta1.ObjectName("one-cert"), - Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), - }, - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerThreeName), - Port: gwv1beta1.PortNumber(listenerThreePort), - Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, gwClassCfg) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gwClass) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gw) - require.NoError(t, err) - - return gw -} - -func minimalFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { - svcDefault := &v1alpha1.ServiceDefaults{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceDefaults", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "high", - Protocol: "TCP", - Port: 8080, - }, - }, - Selector: map[string]string{"app": "Service"}, - }, - } - - serviceAccount := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - } - - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "Service"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{}, - }, - }, - } - - err := k8sClient.Create(ctx, svcDefault) - require.NoError(t, err) - - err = k8sClient.Create(ctx, svc) - require.NoError(t, err) - - err = k8sClient.Create(ctx, serviceAccount) - require.NoError(t, err) - - err = k8sClient.Create(ctx, deployment) - require.NoError(t, err) - - route := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[0].Name, - Port: &gw.Spec.Listeners[0].Port, - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, - Rules: []gwv1beta1.HTTPRouteRule{ - { - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(8080)), - }, - }, - }, - }, - }, - }, - }, - } - - err = k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func minimalFieldsSetTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { - route := &v1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "TCPRoute", - APIVersion: "gateway.networking.k8s.io/v1alpha2", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[1].Name, - Port: &gw.Spec.Listeners[1].Port, - }, - }, - }, - Rules: []gwv1alpha2.TCPRouteRule{ - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(25000)), - }, - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createFunkyCasingFieldsAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { - // listener one configuration - listenerOneName := "listener-one" - listenerOneHostname := "*.consul.io" - listenerOnePort := 3366 - listenerOneProtocol := "hTtPs" - - // listener two configuration - listenerTwoName := "listener-two" - listenerTwoHostname := "*.consul.io" - listenerTwoPort := 5432 - listenerTwoProtocol := "HTTP" - - // listener three configuration - listenerThreeName := "listener-three" - listenerThreePort := 8081 - listenerThreeProtocol := "tCp" - - // listener four configuration - listenerFourName := "listener-four" - listenerFourHostname := "*.consul.io" - listenerFourPort := 5433 - listenerFourProtocol := "hTTp" - - // Write gw to k8s - gwClassCfg := &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClassConfig", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-class-config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{}, - } - gwClass := &gwv1beta1.GatewayClass{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClass", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewayclass", - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "hashicorp.com/consul-api-gateway-controller", - ParametersRef: &gwv1beta1.ParametersReference{ - Group: "consul.hashicorp.com", - Kind: "GatewayClassConfig", - Name: "gateway-class-config", - }, - Description: new(string), - }, - } - gw := &gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "Gateway", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: namespace, - Annotations: make(map[string]string), - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName(listenerOneName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), - Port: gwv1beta1.PortNumber(listenerOnePort), - Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - { - Kind: common.PointerTo(gwv1beta1.Kind("Secret")), - Name: gwv1beta1.ObjectName("one-cert"), - Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), - }, - }, - }, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerTwoName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), - Port: gwv1beta1.PortNumber(listenerTwoPort), - Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("Same")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerThreeName), - Port: gwv1beta1.PortNumber(listenerThreePort), - Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerFourName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerFourHostname)), - Port: gwv1beta1.PortNumber(listenerFourPort), - Protocol: gwv1beta1.ProtocolType(listenerFourProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("Selector")), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.NamespaceNameLabel: "consul", - }, - MatchExpressions: []metav1.LabelSelectorRequirement{}, - }, - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, gwClassCfg) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gwClass) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gw) - require.NoError(t, err) - - return gw -} - -func createFunkyCasingFieldsHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { - svcDefault := &v1alpha1.ServiceDefaults{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceDefaults", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "hTtp", - }, - } - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "high", - Protocol: "TCP", - Port: 8080, - }, - }, - Selector: map[string]string{"app": "Service"}, - }, - } - - serviceAccount := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - } - - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "Service"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{}, - }, - }, - } - - err := k8sClient.Create(ctx, svcDefault) - require.NoError(t, err) - - err = k8sClient.Create(ctx, svc) - require.NoError(t, err) - - err = k8sClient.Create(ctx, serviceAccount) - require.NoError(t, err) - - err = k8sClient.Create(ctx, deployment) - require.NoError(t, err) - - route := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[0].Name, - Port: &gw.Spec.Listeners[0].Port, - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: common.PointerTo(gwv1beta1.PathMatchPathPrefix), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: common.PointerTo(gwv1beta1.HeaderMatchExact), - Name: "version", - Value: "version", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "q", - }, - }, - Method: common.PointerTo(gwv1beta1.HTTPMethod("geT")), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "foo", - Value: "bax", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "arc", - Value: "reactor", - }, - }, - Remove: []string{"remove"}, - }, - }, - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.FullPathHTTPPathModifier, - ReplaceFullPath: common.PointerTo("/foobar"), - }, - }, - }, - - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: common.PointerTo("/foo"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(8080)), - }, - Weight: common.PointerTo(int32(-50)), - }, - }, - }, - }, - }, - }, - } - - err = k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createFunkyCasingFieldsTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { - route := &v1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "TCPRoute", - APIVersion: "gateway.networking.k8s.io/v1alpha2", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[2].Name, - Port: &gw.Spec.Listeners[2].Port, - }, - }, - }, - Rules: []gwv1alpha2.TCPRouteRule{ - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(25000)), - }, - Weight: common.PointerTo(int32(-50)), - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} From 8f474854c4a58e5b11290fb79b8ae434a430962e Mon Sep 17 00:00:00 2001 From: Ganesh S Date: Mon, 12 Jun 2023 15:50:08 -0700 Subject: [PATCH 229/592] Improvement- [NET-189] Added helm inputs for managing audit logs (#2265) * Added helm inputs for managing audit logs * Remove unwanted changes from values --- .changelog/2265.txt | 3 + .../templates/server-config-configmap.yaml | 25 +++- .../test/unit/server-config-configmap.bats | 140 ++++++++++++++++++ charts/consul/values.yaml | 54 +++++++ 4 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 .changelog/2265.txt diff --git a/.changelog/2265.txt b/.changelog/2265.txt new file mode 100644 index 0000000000..1cf6813c94 --- /dev/null +++ b/.changelog/2265.txt @@ -0,0 +1,3 @@ +```release-note:improvement +(Consul Enterprise) Add support to provide inputs via helm for audit log related configuration +``` diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 1ad04a42b5..d3a0206afd 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -1,6 +1,6 @@ {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} {{- if (not (or (eq .Values.server.limits.requestLimits.mode "disabled") (eq .Values.server.limits.requestLimits.mode "permissive") (eq .Values.server.limits.requestLimits.mode "enforce"))) }}{{fail "server.limits.requestLimits.mode must be one of the following values: disabled, permissive, and enforce." }}{{ end -}} - +{{- if and .Values.server.auditLogs.enabled (not .Values.global.acls.manageSystemACLs) }}{{fail "ACLs must be enabled inorder to configure audit logs"}}{{ end -}} # StatefulSet to run the actual Consul server cluster. apiVersion: v1 kind: ConfigMap @@ -187,4 +187,27 @@ data: } } {{- end }} + {{- if and .Values.server.auditLogs.enabled .Values.global.acls.manageSystemACLs }} + audit-logging.json: |- + { + "audit": { + "enabled": "true", + "sink": { + {{- range $index, $element := .Values.server.auditLogs.sinks }} + {{- if ne $index 0 }},{{end}} + "{{ $element.name }}": { + {{- $firstKeyValuePair := false }} + {{- range $k, $v := $element }} + {{- if ne $k "name" }} + {{- if ne $firstKeyValuePair false }},{{end}} + {{- $firstKeyValuePair = true }} + "{{ $k }}": "{{ $v }}" + {{- end }} + {{- end }} + } + {{- end }} + } + } + } + {{- end }} {{- end }} diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 2c8a83f4ca..d55c10dd3a 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -1057,3 +1057,143 @@ load _helpers [ "${actual}" = "100" ] } + +#-------------------------------------------------------------------- +# server.auditLogs + +@test "server/ConfigMap: server.auditLogs is disabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.auditLogs.enabled=false' \ + . | tee /dev/stderr | + yq -r '.data["audit-logging.json"]' | jq -r .audit | tee /dev/stderr) + + [ "${actual}" = "null" ] +} + +@test "server/ConfigMap: server.auditLogs is enabled but ACLs are disabled" { + cd `chart_dir` + run helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.auditLogs.enabled=true' \ + --set 'server.auditLogs.sinks[0].name=MySink' \ + --set 'server.auditLogs.sinks[0].type=file' \ + --set 'server.auditLogs.sinks[0].format=json' \ + --set 'server.auditLogs.sinks[0].delivery_guarantee=best-effort' \ + --set 'server.auditLogs.sinks[0].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[0].path=/tmp/audit.json' \ + . + + [ "$status" -eq 1 ] + [[ "$output" =~ "ACLs must be enabled inorder to configure audit logs" ]] +} + +@test "server/ConfigMap: server.auditLogs is enabled without sink inputs" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.auditLogs.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.data["audit-logging.json"]' | jq -r .audit.sink | tee /dev/stderr) + + [ "${actual}" = "{}" ] +} + +@test "server/ConfigMap: server.auditLogs is enabled with 1 sink input object" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.auditLogs.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'server.auditLogs.sinks[0].name=MySink' \ + --set 'server.auditLogs.sinks[0].type=file' \ + --set 'server.auditLogs.sinks[0].format=json' \ + --set 'server.auditLogs.sinks[0].delivery_guarantee=best-effort' \ + --set 'server.auditLogs.sinks[0].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[0].path=/tmp/audit.json' \ + . | tee /dev/stderr | + yq -r '.data["audit-logging.json"]' | tee /dev/stderr) + + local actual=$(echo $object | jq -r .audit.sink.MySink.path | tee /dev/stderr) + [ "${actual}" = "/tmp/audit.json" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink.delivery_guarantee | tee /dev/stderr) + [ "${actual}" = "best-effort" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink.rotate_duration | tee /dev/stderr) + [ "${actual}" = "24h" ] +} + +@test "server/ConfigMap: server.auditLogs is enabled with 1 sink input object and it does not contain the name attribute" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.auditLogs.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'server.auditLogs.sinks[0].name=MySink' \ + --set 'server.auditLogs.sinks[0].type=file' \ + --set 'server.auditLogs.sinks[0].format=json' \ + --set 'server.auditLogs.sinks[0].delivery_guarantee=best-effort' \ + --set 'server.auditLogs.sinks[0].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[0].path=/tmp/audit.json' \ + . | tee /dev/stderr | + yq -r '.data["audit-logging.json"]' | jq -r .audit.sink.name | tee /dev/stderr) + + [ "${actual}" = "null" ] +} + +@test "server/ConfigMap: server.auditLogs is enabled with multiple sink input objects" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.auditLogs.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'server.auditLogs.sinks[0].name=MySink1' \ + --set 'server.auditLogs.sinks[0].type=file' \ + --set 'server.auditLogs.sinks[0].format=json' \ + --set 'server.auditLogs.sinks[0].delivery_guarantee=best-effort' \ + --set 'server.auditLogs.sinks[0].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[0].path=/tmp/audit.json' \ + --set 'server.auditLogs.sinks[1].name=MySink2' \ + --set 'server.auditLogs.sinks[1].type=file' \ + --set 'server.auditLogs.sinks[1].format=json' \ + --set 'server.auditLogs.sinks[1].delivery_guarantee=best-effort' \ + --set 'server.auditLogs.sinks[1].rotate_max_files=15' \ + --set 'server.auditLogs.sinks[1].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[1].path=/tmp/audit-2.json' \ + --set 'server.auditLogs.sinks[2].name=MySink3' \ + --set 'server.auditLogs.sinks[2].type=file' \ + --set 'server.auditLogs.sinks[2].format=json' \ + --set 'server.auditLogs.sinks[2].delivery_guarantee=best-effort' \ + --set 'server.auditLogs.sinks[2].rotate_max_files=20' \ + --set 'server.auditLogs.sinks[2].rotate_duration=18h' \ + --set 'server.auditLogs.sinks[2].path=/tmp/audit-3.json' \ + . | tee /dev/stderr | + yq -r '.data["audit-logging.json"]' | tee /dev/stderr) + + local actual=$(echo $object | jq -r .audit.sink.MySink1.path | tee /dev/stderr) + [ "${actual}" = "/tmp/audit.json" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink3.path | tee /dev/stderr) + [ "${actual}" = "/tmp/audit-3.json" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink2.path | tee /dev/stderr) + [ "${actual}" = "/tmp/audit-2.json" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink1.name | tee /dev/stderr) + [ "${actual}" = "null" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink3.delivery_guarantee | tee /dev/stderr) + [ "${actual}" = "best-effort" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink2.rotate_duration | tee /dev/stderr) + [ "${actual}" = "24h" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink1.format | tee /dev/stderr) + [ "${actual}" = "json" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink3.type | tee /dev/stderr) + [ "${actual}" = "file" ] +} \ No newline at end of file diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 0e325ca66c..6514fde75a 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -1144,6 +1144,60 @@ server: # @type: string caCert: null + # [Enterprise Only] Added in Consul 1.8, the audit object allow users to enable auditing + # and configure a sink and filters for their audit logs. Please refer to + # [audit logs](https://developer.hashicorp.com/consul/docs/enterprise/audit-logging) documentation + # for further information. + auditLogs: + # Controls whether Consul logs out each time a user performs an operation. + # global.acls.manageSystemACLs must be enabled to use this feature. + enabled: false + + # A single entry of the sink object provides configuration for the destination to which Consul + # will log auditing events. + # + # Example: + # + # ```yaml + # sinks: + # - name: My Sink + # type: file + # format: json + # path: /tmp/audit.json + # delivery_guarantee: best-effort + # rotate_duration: 24h + # rotate_max_files: 15 + # rotate_bytes: 25165824 + # + # ``` + # + # The sink object supports the following keys: + # + # - `name` - Name of the sink. + # + # - `type` - Type specifies what kind of sink this is. Currently only file sinks are available + # + # - `format` - Format specifies what format the events will be emitted with. Currently only `json` + # events are emitted. + # + # - `path` - The directory and filename to write audit events to. + # + # - `delivery_guarantee` - Specifies the rules governing how audit events are written. Consul + # only supports `best-effort` event delivery. + # + # - `mode` - The permissions to set on the audit log files. + # + # - `rotate_duration` - Specifies the interval by which the system rotates to a new log file. + # At least one of `rotate_duration` or `rotate_bytes` must be configured to enable audit logging. + # + # - `rotate_bytes` - Specifies how large an individual log file can grow before Consul rotates to a new file. + # At least one of rotate_bytes or rotate_duration must be configured to enable audit logging. + # + # - `rotate_max_files` - Defines the limit that Consul should follow before it deletes old log files. + # + # @type: array + sinks: [] + # Settings for potentially limiting timeouts, rate limiting on clients as well # as servers, and other settings to limit exposure too many requests, requests # waiting for too long, and other runtime considerations. From fc40d5e5d09087818c3ee860f6c47c80c957d789 Mon Sep 17 00:00:00 2001 From: Eric Haberkorn Date: Tue, 13 Jun 2023 11:28:28 -0400 Subject: [PATCH 230/592] Set Consul service instance localities from K8s node labels (#2346) --- .changelog/2346.txt | 3 ++ .../endpoints/endpoints_controller.go | 20 ++++++++ .../endpoints/endpoints_controller_test.go | 48 ++++++++++++++++++- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 .changelog/2346.txt diff --git a/.changelog/2346.txt b/.changelog/2346.txt new file mode 100644 index 0000000000..fb062ee0fb --- /dev/null +++ b/.changelog/2346.txt @@ -0,0 +1,3 @@ +```release-note:feature +Set locality on services registered with connect-inject. +``` diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index eeaeeab485..7b236792ab 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -318,6 +318,20 @@ func (r *Controller) registerServicesAndHealthCheck(apiClient *api.Client, pod c return nil } +func parseLocality(node corev1.Node) *api.Locality { + region := node.Labels[corev1.LabelTopologyRegion] + zone := node.Labels[corev1.LabelTopologyZone] + + if region == "" { + return nil + } + + return &api.Locality{ + Region: region, + Zone: zone, + } +} + // registerGateway creates Consul registrations for the Connect Gateways and registers them with Consul. // It also upserts a Kubernetes health check for the service based on whether the endpoint address is ready. func (r *Controller) registerGateway(apiClient *api.Client, pod corev1.Pod, serviceEndpoints corev1.Endpoints, healthStatus string, endpointAddressMap map[string]bool) error { @@ -405,6 +419,11 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints } } + var node corev1.Node + // Ignore errors because we don't want failures to block running services. + _ = r.Client.Get(context.Background(), types.NamespacedName{Name: pod.Spec.NodeName, Namespace: pod.Namespace}, &node) + locality := parseLocality(node) + // We only want that annotation to be present when explicitly overriding the consul svc name // Otherwise, the Consul service name should equal the Kubernetes Service name. // The service name in Consul defaults to the Endpoints object name, and is overridden by the pod @@ -440,6 +459,7 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Meta: meta, Namespace: consulNS, Tags: tags, + Locality: locality, } serviceRegistration := &api.CatalogRegistration{ Node: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index acf62b2b0e..ea1ce686d6 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -1938,6 +1938,17 @@ func TestReconcileCreateEndpoint(t *testing.T) { pod1.Annotations[constants.AnnotationUpstreams] = "upstream1:1234" pod1.Annotations[constants.AnnotationEnableMetrics] = "true" pod1.Annotations[constants.AnnotationPrometheusScrapePort] = "12345" + pod1.Spec.NodeName = "my-node" + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node", + Namespace: "default", + Labels: map[string]string{ + corev1.LabelTopologyRegion: "us-west-1", + corev1.LabelTopologyZone: "us-west-1a", + }, + }, + } endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-created", @@ -1958,7 +1969,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, }, } - return []runtime.Object{pod1, endpoint} + return []runtime.Object{pod1, node, endpoint} }, expectedConsulSvcInstances: []*api.CatalogService{ { @@ -1978,6 +1989,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, ServiceTags: []string{"abc,123", "pod1"}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, + ServiceLocality: &api.Locality{ + Region: "us-west-1", + Zone: "us-west-1a", + }, }, }, expectedProxySvcInstances: []*api.CatalogService{ @@ -2190,6 +2205,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { require.Equal(t, tt.expectedConsulSvcInstances[i].ServicePort, instance.ServicePort) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceMeta, instance.ServiceMeta) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceTags, instance.ServiceTags) + require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceLocality, instance.ServiceLocality) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceTaggedAddresses, instance.ServiceTaggedAddresses) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceProxy, instance.ServiceProxy) if tt.nodeMeta != nil { @@ -2236,6 +2252,36 @@ func TestReconcileCreateEndpoint(t *testing.T) { } } +func TestParseLocality(t *testing.T) { + t.Run("no labels", func(t *testing.T) { + n := corev1.Node{} + require.Nil(t, parseLocality(n)) + }) + + t.Run("zone only", func(t *testing.T) { + n := corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + corev1.LabelTopologyZone: "us-west-1a", + }, + }, + } + require.Nil(t, parseLocality(n)) + }) + + t.Run("everything", func(t *testing.T) { + n := corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + corev1.LabelTopologyRegion: "us-west-1", + corev1.LabelTopologyZone: "us-west-1a", + }, + }, + } + require.Equal(t, &api.Locality{Region: "us-west-1", Zone: "us-west-1a"}, parseLocality(n)) + }) +} + // Tests updating an Endpoints object. // - Tests updates via the register codepath: // - When an address in an Endpoint is updated, that the corresponding service instance in Consul is updated. From 345f62cec6fe7a3b6e9661e994c24ac5b3333cab Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 13 Jun 2023 11:40:51 -0400 Subject: [PATCH 231/592] fix: use correct flag when translating namespaces (#2353) * fix: use correct flag when translating namespaces * Use non-normalized namespace when deregistering services * Guard against namespace queries when namespaces not enabled in cache --- control-plane/api-gateway/cache/gateway.go | 12 +-- .../api-gateway/common/translation.go | 2 +- .../api-gateway/common/translation_test.go | 73 +++++++++++++++++++ .../controllers/gateway_controller.go | 2 +- 4 files changed, 81 insertions(+), 8 deletions(-) diff --git a/control-plane/api-gateway/cache/gateway.go b/control-plane/api-gateway/cache/gateway.go index d8dd37ffac..d1de8dd7cc 100644 --- a/control-plane/api-gateway/cache/gateway.go +++ b/control-plane/api-gateway/cache/gateway.go @@ -18,7 +18,7 @@ import ( ) type GatewayCache struct { - config *consul.Config + config Config serverMgr consul.ServerConnectionManager logger logr.Logger @@ -35,7 +35,7 @@ type GatewayCache struct { func NewGatewayCache(ctx context.Context, config Config) *GatewayCache { return &GatewayCache{ - config: config.ConsulClientConfig, + config: config, serverMgr: config.ConsulServerConnMgr, logger: config.Logger, events: make(chan event.GenericEvent), @@ -53,13 +53,13 @@ func (r *GatewayCache) ServicesFor(ref api.ResourceReference) []api.CatalogServi } func (r *GatewayCache) FetchServicesFor(ctx context.Context, ref api.ResourceReference) ([]api.CatalogService, error) { - client, err := consul.NewClientFromConnMgr(r.config, r.serverMgr) + client, err := consul.NewClientFromConnMgr(r.config.ConsulClientConfig, r.serverMgr) if err != nil { return nil, err } opts := &api.QueryOptions{} - if ref.Namespace != "" { + if r.config.NamespacesEnabled && ref.Namespace != "" { opts.Namespace = ref.Namespace } @@ -95,7 +95,7 @@ func (r *GatewayCache) RemoveSubscription(ref api.ResourceReference) { func (r *GatewayCache) subscribeToGateway(ctx context.Context, ref api.ResourceReference, resource types.NamespacedName) { opts := &api.QueryOptions{} - if ref.Namespace != "" { + if r.config.NamespacesEnabled && ref.Namespace != "" { opts.Namespace = ref.Namespace } @@ -117,7 +117,7 @@ func (r *GatewayCache) subscribeToGateway(ctx context.Context, ref api.ResourceR retryBackoff := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 10) if err := backoff.Retry(func() error { - client, err := consul.NewClientFromConnMgr(r.config, r.serverMgr) + client, err := consul.NewClientFromConnMgr(r.config.ConsulClientConfig, r.serverMgr) if err != nil { return err } diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index 8644b64716..fd69c8601f 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -50,7 +50,7 @@ func (t ResourceTranslator) NormalizedResourceReference(kind, namespace string, } func (t ResourceTranslator) Namespace(namespace string) string { - return namespaces.ConsulNamespace(namespace, t.EnableK8sMirroring, t.ConsulDestNamespace, t.EnableK8sMirroring, t.MirroringPrefix) + return namespaces.ConsulNamespace(namespace, t.EnableConsulNamespaces, t.ConsulDestNamespace, t.EnableK8sMirroring, t.MirroringPrefix) } // ToAPIGateway translates a kuberenetes API gateway into a Consul APIGateway Config Entry. diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index 2c735ad4ac..caa3efbcac 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -9,11 +9,13 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "math/big" "testing" "time" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -42,6 +44,77 @@ func (v fakeReferenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2. return true } +func TestTranslator_Namespace(t *testing.T) { + testCases := []struct { + EnableConsulNamespaces bool + ConsulDestNamespace string + EnableK8sMirroring bool + MirroringPrefix string + Input, ExpectedOutput string + }{ + { + EnableConsulNamespaces: false, + ConsulDestNamespace: "default", + EnableK8sMirroring: false, + MirroringPrefix: "", + Input: "namespace-1", + ExpectedOutput: "", + }, + { + EnableConsulNamespaces: false, + ConsulDestNamespace: "default", + EnableK8sMirroring: true, + MirroringPrefix: "", + Input: "namespace-1", + ExpectedOutput: "", + }, + { + EnableConsulNamespaces: false, + ConsulDestNamespace: "default", + EnableK8sMirroring: true, + MirroringPrefix: "pre-", + Input: "namespace-1", + ExpectedOutput: "", + }, + { + EnableConsulNamespaces: true, + ConsulDestNamespace: "default", + EnableK8sMirroring: false, + MirroringPrefix: "", + Input: "namespace-1", + ExpectedOutput: "default", + }, + { + EnableConsulNamespaces: true, + ConsulDestNamespace: "default", + EnableK8sMirroring: true, + MirroringPrefix: "", + Input: "namespace-1", + ExpectedOutput: "namespace-1", + }, + { + EnableConsulNamespaces: true, + ConsulDestNamespace: "default", + EnableK8sMirroring: true, + MirroringPrefix: "pre-", + Input: "namespace-1", + ExpectedOutput: "pre-namespace-1", + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%s_%d", t.Name(), i), func(t *testing.T) { + translator := ResourceTranslator{ + EnableConsulNamespaces: tc.EnableConsulNamespaces, + ConsulDestNamespace: tc.ConsulDestNamespace, + EnableK8sMirroring: tc.EnableK8sMirroring, + MirroringPrefix: tc.MirroringPrefix, + } + assert.Equal(t, tc.ExpectedOutput, translator.Namespace(tc.Input)) + }) + } +} + func TestTranslator_ToAPIGateway(t *testing.T) { t.Parallel() k8sObjectName := "my-k8s-gw" diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index 97805ccdfb..8569508769 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -212,7 +212,7 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct r.gatewayCache.RemoveSubscription(nonNormalizedConsulKey) // make sure we have deregister all services even if they haven't // hit cache yet - if err := r.deregisterAllServices(ctx, consulKey); err != nil { + if err := r.deregisterAllServices(ctx, nonNormalizedConsulKey); err != nil { log.Error(err, "error deregistering services") return ctrl.Result{}, err } From 285096241e0d5c5b6d53dd8a37889ab3ea5a8af2 Mon Sep 17 00:00:00 2001 From: aahel Date: Tue, 13 Jun 2023 21:36:51 +0530 Subject: [PATCH 232/592] added imagePullPolicy for images in values.yaml (#2310) * added imagePullPolicy for images in values.yaml * fix: renamed pullPolicy key according to image * fixed dafault always in tmpl * changed structure of image in yaml * revert changes * added global imagePullPolicy * fixed typo * added changelog file --- .changelog/2310.txt | 3 +++ .../consul/templates/api-gateway-controller-deployment.yaml | 3 +++ charts/consul/templates/api-gateway-gatewayclassconfig.yaml | 1 + charts/consul/templates/client-daemonset.yaml | 1 + charts/consul/templates/cni-daemonset.yaml | 1 + charts/consul/templates/enterprise-license-job.yaml | 1 + charts/consul/templates/gateway-cleanup-job.yaml | 1 + charts/consul/templates/gateway-resources-job.yaml | 1 + charts/consul/templates/mesh-gateway-deployment.yaml | 2 ++ charts/consul/templates/partition-init-job.yaml | 1 + charts/consul/templates/server-acl-init-cleanup-job.yaml | 1 + charts/consul/templates/server-acl-init-job.yaml | 1 + charts/consul/templates/server-statefulset.yaml | 1 + .../consul/templates/webhook-cert-manager-deployment.yaml | 1 + charts/consul/values.yaml | 6 ++++++ 15 files changed, 25 insertions(+) create mode 100644 .changelog/2310.txt diff --git a/.changelog/2310.txt b/.changelog/2310.txt new file mode 100644 index 0000000000..5e37de44ea --- /dev/null +++ b/.changelog/2310.txt @@ -0,0 +1,3 @@ +```release-note:feature +helm: Added imagePullPolicy global field which can be configured to override the default behaviour. +``` \ No newline at end of file diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 8c5c2fa73e..c942576034 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -57,6 +57,7 @@ spec: containers: - name: api-gateway-controller image: {{ .Values.apiGateway.image }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} ports: - containerPort: 9090 name: sds @@ -219,6 +220,7 @@ spec: {{- if .Values.global.acls.manageSystemACLs }} - name: copy-consul-bin image: {{ .Values.global.image | quote }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - cp - /bin/consul @@ -256,6 +258,7 @@ spec: {{- end}} {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} volumeMounts: - mountPath: /consul/login name: consul-data diff --git a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml index ba0e6c63db..8688ee6ae7 100644 --- a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml +++ b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml @@ -65,6 +65,7 @@ spec: image: consulAPIGateway: {{ .Values.apiGateway.image }} envoy: {{ .Values.apiGateway.imageEnvoy }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} {{- if .Values.apiGateway.managedGatewayClass.nodeSelector }} nodeSelector: {{ tpl .Values.apiGateway.managedGatewayClass.nodeSelector . | indent 4 | trim }} diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 09a70b394e..ba19343652 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -493,6 +493,7 @@ spec: {{- if .Values.global.acls.manageSystemACLs }} - name: client-acl-init image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index ae04d9e657..33ffb0a77e 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -61,6 +61,7 @@ spec: # This container installs the consul CNI binaries and CNI network config file on each node - name: install-cni image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} securityContext: privileged: true command: diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index 0122690104..2a3fa01d00 100644 --- a/charts/consul/templates/enterprise-license-job.yaml +++ b/charts/consul/templates/enterprise-license-job.yaml @@ -124,6 +124,7 @@ spec: initContainers: - name: ent-license-acl-init image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - "/bin/sh" - "-ec" diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index 44f032b5fd..e4656916de 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -37,6 +37,7 @@ spec: containers: - name: gateway-cleanup image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 441e64eb14..ea38d7af32 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -37,6 +37,7 @@ spec: containers: - name: gateway-resources image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index 449d6ae492..4150b2bdfd 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -121,6 +121,7 @@ spec: initContainers: - name: mesh-gateway-init image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: NAMESPACE valueFrom: @@ -179,6 +180,7 @@ spec: containers: - name: mesh-gateway image: {{ .Values.global.imageConsulDataplane | quote }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} {{- if .Values.meshGateway.resources }} resources: {{- if eq (typeOf .Values.meshGateway.resources) "string" }} diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index db73ef783b..b351d10027 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -81,6 +81,7 @@ spec: containers: - name: partition-init-job image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 35b0877ab4..3676144b40 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -53,6 +53,7 @@ spec: containers: - name: server-acl-init-cleanup image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index e62db41ec2..e42a073c42 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -122,6 +122,7 @@ spec: containers: - name: server-acl-init-job image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 0bde9b881a..0c2eb1bffa 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -225,6 +225,7 @@ spec: initContainers: - name: locality-init image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: NODE_NAME valueFrom: diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index dd93c039d2..25e382be84 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -50,6 +50,7 @@ spec: -deployment-name={{ template "consul.fullname" . }}-webhook-cert-manager \ -deployment-namespace={{ .Release.Namespace }} image: {{ .Values.global.imageK8S }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} name: webhook-cert-manager resources: limits: diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 6514fde75a..5c77a27f33 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -52,6 +52,12 @@ global: # Changing the partition name would require an un-install and a re-install with the updated name. # Must be "default" in the server cluster ie the Kubernetes cluster that the Consul server pods are deployed onto. name: "default" + + # Set imagePullPolicy for all images used. This is applies to all the images being used. + # One of "IfNotPresent", "Always", "Never" + # Refer to https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy + # @type: string + imagePullPolicy: "" # The name (and tag) of the Consul Docker image for clients and servers. # This can be overridden per component. This should be pinned to a specific From f2c166fc8c77ad0e103b8ac0dcfe35e81860e97f Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 13 Jun 2023 12:59:55 -0400 Subject: [PATCH 233/592] [chore]: Pin github action workflows (#2356) --- .github/workflows/build.yml | 6 +++--- .github/workflows/jira-issues.yaml | 6 +++--- .github/workflows/jira-pr.yaml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e46e16c9a3..711c228e11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -124,7 +124,7 @@ jobs: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Setup go - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 + uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 with: go-version: ${{ matrix.go }} @@ -193,7 +193,7 @@ jobs: - name: Test rpm package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: registry.access.redhat.com/ubi9/ubi:latest options: -v ${{ github.workspace }}:/work @@ -218,7 +218,7 @@ jobs: - name: Test debian package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: ubuntu:latest options: -v ${{ github.workspace }}:/work diff --git a/.github/workflows/jira-issues.yaml b/.github/workflows/jira-issues.yaml index 700803a45f..bddc69c83f 100644 --- a/.github/workflows/jira-issues.yaml +++ b/.github/workflows/jira-issues.yaml @@ -15,7 +15,7 @@ jobs: name: Jira Community Issue sync steps: - name: Login - uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 + uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -72,14 +72,14 @@ jobs: - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 + uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 + uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index a285c4c176..078365ac88 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -13,7 +13,7 @@ jobs: name: Jira sync steps: - name: Login - uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 + uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -86,14 +86,14 @@ jobs: - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 + uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 + uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" From 80b1f52437e912349a1c86c19c58e653a607d80d Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 13 Jun 2023 13:14:31 -0400 Subject: [PATCH 234/592] ci: update backport assistant to 0.3.4 (#2365) This brings consul-k8s in line with consul. Most importantly, the backport assistant was updated to automatically assign created PRs to the author of the PR that is being backported. --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 7f39b4e5a0..dadde6a651 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -13,7 +13,7 @@ jobs: backport: if: github.event.pull_request.merged runs-on: ubuntu-latest - container: hashicorpdev/backport-assistant:0.3.3 + container: hashicorpdev/backport-assistant:0.3.4 steps: - name: Run Backport Assistant run: backport-assistant backport -merge-method=squash -gh-automerge From e691f464bafe3050360bc4a45fd1adb70b19255f Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:41:16 -0700 Subject: [PATCH 235/592] update changelog based on changes made to 1.2.x (#2348) * update changelog based on changes made to 1.2.x * fixed test cases - enterprise cases were in the OSS test cases --- .changelog/1975.txt | 11 - .changelog/1976.txt | 3 - .changelog/2048.txt | 3 + .changelog/2075.txt | 3 + .changelog/2086.txt | 3 + .changelog/2097.txt | 3 + .changelog/2102.txt | 9 + .changelog/2165.txt | 3 + .../configentry_controller_ent_test.go | 313 ++++++++++++++++++ .../configentry_controller_test.go | 313 ------------------ 10 files changed, 337 insertions(+), 327 deletions(-) delete mode 100644 .changelog/1975.txt delete mode 100644 .changelog/1976.txt create mode 100644 .changelog/2048.txt create mode 100644 .changelog/2075.txt create mode 100644 .changelog/2086.txt create mode 100644 .changelog/2097.txt create mode 100644 .changelog/2165.txt diff --git a/.changelog/1975.txt b/.changelog/1975.txt deleted file mode 100644 index ba26b1ab1e..0000000000 --- a/.changelog/1975.txt +++ /dev/null @@ -1,11 +0,0 @@ -```release-note:security -upgrade to use Go 1.19.6. This resolves vulnerabilities CVE-2022-41724 in crypto/tls and CVE-2022-41723 in net/http. -``` - -```release-note:improvement -cli: update minimum go version for project to 1.19. -``` - -```release-note:improvement -control-plane: update minimum go version for project to 1.19. -``` \ No newline at end of file diff --git a/.changelog/1976.txt b/.changelog/1976.txt deleted file mode 100644 index 65024aa6f9..0000000000 --- a/.changelog/1976.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:security -upgrade to use Go 1.19.6. This resolves vulnerabilities CVE-2022-41724 in crypto/tls and CVE-2022-41723 in net/http. -``` \ No newline at end of file diff --git a/.changelog/2048.txt b/.changelog/2048.txt new file mode 100644 index 0000000000..5796ce2397 --- /dev/null +++ b/.changelog/2048.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: add samenessGroup CRD +``` \ No newline at end of file diff --git a/.changelog/2075.txt b/.changelog/2075.txt new file mode 100644 index 0000000000..2f0f0344eb --- /dev/null +++ b/.changelog/2075.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: add samenessGroup field to exported services CRD +``` \ No newline at end of file diff --git a/.changelog/2086.txt b/.changelog/2086.txt new file mode 100644 index 0000000000..d4e43a630d --- /dev/null +++ b/.changelog/2086.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: add samenessGroup field to service resolver CRD +``` \ No newline at end of file diff --git a/.changelog/2097.txt b/.changelog/2097.txt new file mode 100644 index 0000000000..60e99a8515 --- /dev/null +++ b/.changelog/2097.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: add samenessGroup field to source intention CRD +``` \ No newline at end of file diff --git a/.changelog/2102.txt b/.changelog/2102.txt index 59d120f747..7adf361d2d 100644 --- a/.changelog/2102.txt +++ b/.changelog/2102.txt @@ -10,3 +10,12 @@ Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41 ](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h .) ``` + +```release-note:improvement +cli: update minimum go version for project to 1.20. +``` + +```release-note:improvement +control-plane: update minimum go version for project to 1.20. +``` + diff --git a/.changelog/2165.txt b/.changelog/2165.txt new file mode 100644 index 0000000000..15c4bdb1e0 --- /dev/null +++ b/.changelog/2165.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: add FIPS support +``` \ No newline at end of file diff --git a/control-plane/controllers/configentry_controller_ent_test.go b/control-plane/controllers/configentry_controller_ent_test.go index ab6c70b9ad..14ae477a56 100644 --- a/control-plane/controllers/configentry_controller_ent_test.go +++ b/control-plane/controllers/configentry_controller_ent_test.go @@ -87,6 +87,119 @@ func TestConfigEntryController_createsEntConfigEntry(t *testing.T) { require.Equal(t, "", resource.Members[0].Partition) }, }, + { + kubeKind: "ControlPlaneRequestLimit", + consulKind: capi.RateLimitIPConfig, + configEntryResource: &v1alpha1.ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: v1alpha1.ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ACL: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &ControlPlaneRequestLimitController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + resource, ok := consulEntry.(*capi.RateLimitIPConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, "permissive", resource.Mode) + require.Equal(t, 100.0, resource.ReadRate) + require.Equal(t, 100.0, resource.WriteRate) + require.Equal(t, 100.0, resource.ACL.ReadRate) + require.Equal(t, 100.0, resource.ACL.WriteRate) + require.Equal(t, 100.0, resource.Catalog.ReadRate) + require.Equal(t, 100.0, resource.Catalog.WriteRate) + require.Equal(t, 100.0, resource.ConfigEntry.ReadRate) + require.Equal(t, 100.0, resource.ConfigEntry.WriteRate) + require.Equal(t, 100.0, resource.ConnectCA.ReadRate) + require.Equal(t, 100.0, resource.ConnectCA.WriteRate) + require.Equal(t, 100.0, resource.Coordinate.ReadRate) + require.Equal(t, 100.0, resource.Coordinate.WriteRate) + require.Equal(t, 100.0, resource.DiscoveryChain.ReadRate) + require.Equal(t, 100.0, resource.DiscoveryChain.WriteRate) + require.Equal(t, 100.0, resource.Health.ReadRate) + require.Equal(t, 100.0, resource.Health.WriteRate) + require.Equal(t, 100.0, resource.Intention.ReadRate) + require.Equal(t, 100.0, resource.Intention.WriteRate) + require.Equal(t, 100.0, resource.KV.ReadRate) + require.Equal(t, 100.0, resource.KV.WriteRate) + require.Equal(t, 100.0, resource.Tenancy.ReadRate) + require.Equal(t, 100.0, resource.Tenancy.WriteRate) + require.Equal(t, 100.0, resource.PreparedQuery.ReadRate) + require.Equal(t, 100.0, resource.PreparedQuery.WriteRate) + require.Equal(t, 100.0, resource.Session.ReadRate) + require.Equal(t, 100.0, resource.Session.WriteRate) + require.Equal(t, 100.0, resource.Txn.ReadRate) + require.Equal(t, 100.0, resource.Txn.WriteRate, 100.0) + }, + }, } for _, c := range cases { @@ -191,6 +304,123 @@ func TestConfigEntryController_updatesEntConfigEntry(t *testing.T) { require.Equal(t, "", resource.Members[0].Partition) }, }, + { + kubeKind: "ControlPlaneRequestLimit", + consulKind: capi.RateLimitIPConfig, + configEntryResource: &v1alpha1.ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + }, + Spec: v1alpha1.ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ACL: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &ControlPlaneRequestLimitController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + updateF: func(resource common.ConfigEntryResource) { + ipRateLimit := resource.(*v1alpha1.ControlPlaneRequestLimit) + ipRateLimit.Spec.Mode = "enforcing" + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + resource, ok := consulEntry.(*capi.RateLimitIPConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, "enforcing", resource.Mode) + require.Equal(t, 100.0, resource.ReadRate) + require.Equal(t, 100.0, resource.WriteRate) + require.Equal(t, 100.0, resource.ACL.ReadRate) + require.Equal(t, 100.0, resource.ACL.WriteRate) + require.Equal(t, 100.0, resource.Catalog.ReadRate) + require.Equal(t, 100.0, resource.Catalog.WriteRate) + require.Equal(t, 100.0, resource.ConfigEntry.ReadRate) + require.Equal(t, 100.0, resource.ConfigEntry.WriteRate) + require.Equal(t, 100.0, resource.ConnectCA.ReadRate) + require.Equal(t, 100.0, resource.ConnectCA.WriteRate) + require.Equal(t, 100.0, resource.Coordinate.ReadRate) + require.Equal(t, 100.0, resource.Coordinate.WriteRate) + require.Equal(t, 100.0, resource.DiscoveryChain.ReadRate) + require.Equal(t, 100.0, resource.DiscoveryChain.WriteRate) + require.Equal(t, 100.0, resource.Health.ReadRate) + require.Equal(t, 100.0, resource.Health.WriteRate) + require.Equal(t, 100.0, resource.Intention.ReadRate) + require.Equal(t, 100.0, resource.Intention.WriteRate) + require.Equal(t, 100.0, resource.KV.ReadRate) + require.Equal(t, 100.0, resource.KV.WriteRate) + require.Equal(t, 100.0, resource.Tenancy.ReadRate) + require.Equal(t, 100.0, resource.Tenancy.WriteRate) + require.Equal(t, 100.0, resource.PreparedQuery.ReadRate) + require.Equal(t, 100.0, resource.PreparedQuery.WriteRate) + require.Equal(t, 100.0, resource.Session.ReadRate) + require.Equal(t, 100.0, resource.Session.WriteRate) + require.Equal(t, 100.0, resource.Txn.ReadRate) + require.Equal(t, 100.0, resource.Txn.WriteRate) + }, + }, } for _, c := range cases { @@ -296,6 +526,89 @@ func TestConfigEntryController_deletesEntConfigEntry(t *testing.T) { } }, }, + { + + kubeKind: "ControlPlaneRequestLimit", + consulKind: capi.RateLimitIPConfig, + configEntryResourceWithDeletion: &v1alpha1.ControlPlaneRequestLimit{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: kubeNS, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + Finalizers: []string{FinalizerName}, + }, + Spec: v1alpha1.ControlPlaneRequestLimitSpec{ + Mode: "permissive", + ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ACL: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Catalog: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + ConnectCA: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Coordinate: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Health: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Intention: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + KV: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Tenancy: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Session: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + Txn: &v1alpha1.ReadWriteRatesConfig{ + ReadRate: 100.0, + WriteRate: 100.0, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &ControlPlaneRequestLimitController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + }, } for _, c := range cases { diff --git a/control-plane/controllers/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go index 0d5f8af5bf..071d67ca6f 100644 --- a/control-plane/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -476,119 +476,6 @@ func TestConfigEntryControllers_createsConfigEntry(t *testing.T) { require.Equal(t, "test-issuer", jwt.Issuer) }, }, - { - kubeKind: "ControlPlaneRequestLimit", - consulKind: capi.RateLimitIPConfig, - configEntryResource: &v1alpha1.ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: v1alpha1.ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ACL: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &ControlPlaneRequestLimitController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - resource, ok := consulEntry.(*capi.RateLimitIPConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, "permissive", resource.Mode) - require.Equal(t, 100.0, resource.ReadRate) - require.Equal(t, 100.0, resource.WriteRate) - require.Equal(t, 100.0, resource.ACL.ReadRate) - require.Equal(t, 100.0, resource.ACL.WriteRate) - require.Equal(t, 100.0, resource.Catalog.ReadRate) - require.Equal(t, 100.0, resource.Catalog.WriteRate) - require.Equal(t, 100.0, resource.ConfigEntry.ReadRate) - require.Equal(t, 100.0, resource.ConfigEntry.WriteRate) - require.Equal(t, 100.0, resource.ConnectCA.ReadRate) - require.Equal(t, 100.0, resource.ConnectCA.WriteRate) - require.Equal(t, 100.0, resource.Coordinate.ReadRate) - require.Equal(t, 100.0, resource.Coordinate.WriteRate) - require.Equal(t, 100.0, resource.DiscoveryChain.ReadRate) - require.Equal(t, 100.0, resource.DiscoveryChain.WriteRate) - require.Equal(t, 100.0, resource.Health.ReadRate) - require.Equal(t, 100.0, resource.Health.WriteRate) - require.Equal(t, 100.0, resource.Intention.ReadRate) - require.Equal(t, 100.0, resource.Intention.WriteRate) - require.Equal(t, 100.0, resource.KV.ReadRate) - require.Equal(t, 100.0, resource.KV.WriteRate) - require.Equal(t, 100.0, resource.Tenancy.ReadRate) - require.Equal(t, 100.0, resource.Tenancy.WriteRate) - require.Equal(t, 100.0, resource.PreparedQuery.ReadRate) - require.Equal(t, 100.0, resource.PreparedQuery.WriteRate) - require.Equal(t, 100.0, resource.Session.ReadRate) - require.Equal(t, 100.0, resource.Session.WriteRate) - require.Equal(t, 100.0, resource.Txn.ReadRate) - require.Equal(t, 100.0, resource.Txn.WriteRate, 100.0) - }, - }, } for _, c := range cases { @@ -1116,123 +1003,6 @@ func TestConfigEntryControllers_updatesConfigEntry(t *testing.T) { require.Equal(t, []string{"aud1"}, jwt.Audiences) }, }, - { - kubeKind: "ControlPlaneRequestLimit", - consulKind: capi.RateLimitIPConfig, - configEntryResource: &v1alpha1.ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: v1alpha1.ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ACL: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &ControlPlaneRequestLimitController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - updateF: func(resource common.ConfigEntryResource) { - ipRateLimit := resource.(*v1alpha1.ControlPlaneRequestLimit) - ipRateLimit.Spec.Mode = "enforcing" - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - resource, ok := consulEntry.(*capi.RateLimitIPConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, "enforcing", resource.Mode) - require.Equal(t, 100.0, resource.ReadRate) - require.Equal(t, 100.0, resource.WriteRate) - require.Equal(t, 100.0, resource.ACL.ReadRate) - require.Equal(t, 100.0, resource.ACL.WriteRate) - require.Equal(t, 100.0, resource.Catalog.ReadRate) - require.Equal(t, 100.0, resource.Catalog.WriteRate) - require.Equal(t, 100.0, resource.ConfigEntry.ReadRate) - require.Equal(t, 100.0, resource.ConfigEntry.WriteRate) - require.Equal(t, 100.0, resource.ConnectCA.ReadRate) - require.Equal(t, 100.0, resource.ConnectCA.WriteRate) - require.Equal(t, 100.0, resource.Coordinate.ReadRate) - require.Equal(t, 100.0, resource.Coordinate.WriteRate) - require.Equal(t, 100.0, resource.DiscoveryChain.ReadRate) - require.Equal(t, 100.0, resource.DiscoveryChain.WriteRate) - require.Equal(t, 100.0, resource.Health.ReadRate) - require.Equal(t, 100.0, resource.Health.WriteRate) - require.Equal(t, 100.0, resource.Intention.ReadRate) - require.Equal(t, 100.0, resource.Intention.WriteRate) - require.Equal(t, 100.0, resource.KV.ReadRate) - require.Equal(t, 100.0, resource.KV.WriteRate) - require.Equal(t, 100.0, resource.Tenancy.ReadRate) - require.Equal(t, 100.0, resource.Tenancy.WriteRate) - require.Equal(t, 100.0, resource.PreparedQuery.ReadRate) - require.Equal(t, 100.0, resource.PreparedQuery.WriteRate) - require.Equal(t, 100.0, resource.Session.ReadRate) - require.Equal(t, 100.0, resource.Session.WriteRate) - require.Equal(t, 100.0, resource.Txn.ReadRate) - require.Equal(t, 100.0, resource.Txn.WriteRate) - }, - }, } for _, c := range cases { @@ -1665,89 +1435,6 @@ func TestConfigEntryControllers_deletesConfigEntry(t *testing.T) { } }, }, - { - - kubeKind: "ControlPlaneRequestLimit", - consulKind: capi.RateLimitIPConfig, - configEntryResourceWithDeletion: &v1alpha1.ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{FinalizerName}, - }, - Spec: v1alpha1.ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ACL: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &ControlPlaneRequestLimitController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - }, } for _, c := range cases { From 9121afcef8e2738f086b2e2bf76ead4ea4157b95 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Wed, 14 Jun 2023 09:23:46 -0500 Subject: [PATCH 236/592] api-gateway: nightly conformance test action (#2257) * trigger conformance tests nightly, squash * remove extra line * Update nightly-api-gateway-conformance.yml --- .../nightly-api-gateway-conformance.yml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/nightly-api-gateway-conformance.yml diff --git a/.github/workflows/nightly-api-gateway-conformance.yml b/.github/workflows/nightly-api-gateway-conformance.yml new file mode 100644 index 0000000000..cd63ee8467 --- /dev/null +++ b/.github/workflows/nightly-api-gateway-conformance.yml @@ -0,0 +1,28 @@ +# Dispatch to the consul-k8s-workflows with a nightly cron +name: nightly-api-gateway-conformance +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run nightly at 12AM UTC/8PM EST/5PM PST. + - cron: '0 0 * * *' + + +# these should be the only settings that you will ever need to change +env: + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests + BRANCH: ${{ github.ref_name }} + CONTEXT: "nightly" + +jobs: + api-gateway-conformance: + name: api-gateway-conformance + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + name: conformance + with: + workflow: api-gateway-conformance.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 }}" }' From 3ce33026a35947f4de13261d1a79e1d823fc5c49 Mon Sep 17 00:00:00 2001 From: Eric Haberkorn Date: Thu, 15 Jun 2023 09:12:59 -0400 Subject: [PATCH 237/592] add crds for prioritize by locality (#2357) --- .changelog/2357.txt | 3 + .../templates/crd-serviceresolvers.yaml | 9 +++ .../api/v1alpha1/serviceresolver_types.go | 59 ++++++++++++++++--- .../v1alpha1/serviceresolver_types_test.go | 28 +++++++++ ...consul.hashicorp.com_serviceresolvers.yaml | 9 +++ 5 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 .changelog/2357.txt diff --git a/.changelog/2357.txt b/.changelog/2357.txt new file mode 100644 index 0000000000..7cc35f595a --- /dev/null +++ b/.changelog/2357.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add the `PrioritizeByLocality` field to the `ServiceResolver` CRD. +``` diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index ed95c15846..99cc1bb090 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -227,6 +227,15 @@ spec: type: integer type: object type: object + prioritizeByLocality: + description: PrioritizeByLocality contains the configuration for + locality aware routing. + properties: + mode: + description: Mode specifies the behavior of PrioritizeByLocality + routing. Valid values are "", "none", and "failover". + type: string + type: object redirect: description: Redirect when configured, all attempts to resolve the service this resolver defines will be substituted for the supplied diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index d00821275d..e5ce3c6fa6 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -79,6 +79,16 @@ type ServiceResolverSpec struct { // LoadBalancer determines the load balancing policy and configuration for services // issuing requests to this upstream service. LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` + // PrioritizeByLocality controls whether the locality of services within the + // local partition will be used to prioritize connectivity. + PrioritizeByLocality *ServiceResolverPrioritizeByLocality `json:"prioritizeByLocality,omitempty"` +} + +type ServiceResolverPrioritizeByLocality struct { + // Mode specifies the type of prioritization that will be performed + // when selecting nodes in the local partition. + // Valid values are: "" (default "none"), "none", and "failover". + Mode string `json:"mode,omitempty"` } type ServiceResolverRedirect struct { @@ -300,15 +310,16 @@ func (in *ServiceResolver) SyncedConditionStatus() corev1.ConditionStatus { // ToConsul converts the entry into its Consul equivalent struct. func (in *ServiceResolver) ToConsul(datacenter string) capi.ConfigEntry { return &capi.ServiceResolverConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - DefaultSubset: in.Spec.DefaultSubset, - Subsets: in.Spec.Subsets.toConsul(), - Redirect: in.Spec.Redirect.toConsul(), - Failover: in.Spec.Failover.toConsul(), - ConnectTimeout: in.Spec.ConnectTimeout.Duration, - LoadBalancer: in.Spec.LoadBalancer.toConsul(), - Meta: meta(datacenter), + Kind: in.ConsulKind(), + Name: in.ConsulName(), + DefaultSubset: in.Spec.DefaultSubset, + Subsets: in.Spec.Subsets.toConsul(), + Redirect: in.Spec.Redirect.toConsul(), + Failover: in.Spec.Failover.toConsul(), + ConnectTimeout: in.Spec.ConnectTimeout.Duration, + LoadBalancer: in.Spec.LoadBalancer.toConsul(), + PrioritizeByLocality: in.Spec.PrioritizeByLocality.toConsul(), + Meta: meta(datacenter), } } @@ -338,6 +349,7 @@ func (in *ServiceResolver) Validate(consulMeta common.ConsulMeta) error { } errs = append(errs, in.Spec.Redirect.validate(path.Child("redirect"), consulMeta)...) + errs = append(errs, in.Spec.PrioritizeByLocality.validate(path.Child("prioritizeByLocality"))...) errs = append(errs, in.Spec.Subsets.validate(path.Child("subsets"))...) errs = append(errs, in.Spec.LoadBalancer.validate(path.Child("loadBalancer"))...) errs = append(errs, in.validateEnterprise(consulMeta)...) @@ -520,6 +532,16 @@ func (in *ServiceResolverFailover) toConsul() *capi.ServiceResolverFailover { } } +func (in *ServiceResolverPrioritizeByLocality) toConsul() *capi.ServiceResolverPrioritizeByLocality { + if in == nil { + return nil + } + + return &capi.ServiceResolverPrioritizeByLocality{ + Mode: in.Mode, + } +} + func (in ServiceResolverFailoverTarget) toConsul() capi.ServiceResolverFailoverTarget { return capi.ServiceResolverFailoverTarget{ Service: in.Service, @@ -629,6 +651,25 @@ func (in *ServiceResolverFailover) isEmpty() bool { return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 && in.Policy == nil && in.SamenessGroup == "" } +func (in *ServiceResolverPrioritizeByLocality) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + + if in == nil { + return nil + } + + switch in.Mode { + case "": + case "none": + case "failover": + default: + asJSON, _ := json.Marshal(in) + errs = append(errs, field.Invalid(path, string(asJSON), + "mode must be one of '', 'none', or 'failover'")) + } + return errs +} + func (in *ServiceResolverFailover) validate(path *field.Path, consulMeta common.ConsulMeta) field.ErrorList { var errs field.ErrorList if in.isEmpty() { diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index d09f0809c8..3a3d5a6016 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -66,6 +66,9 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, + PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + Mode: "failover", + }, Failover: map[string]ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -147,6 +150,9 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, + PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ + Mode: "failover", + }, Failover: map[string]capi.ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -277,6 +283,9 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, + PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + Mode: "none", + }, Failover: map[string]ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -358,6 +367,9 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, + PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ + Mode: "none", + }, Failover: map[string]capi.ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -882,6 +894,22 @@ func TestServiceResolver_Validate(t *testing.T) { "spec.failover[failB].namespace: Invalid value: \"namespace-b\": Consul Enterprise namespaces must be enabled to set failover.namespace", }, }, + "prioritize by locality none": { + input: &ServiceResolver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: ServiceResolverSpec{ + PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + Mode: "bad", + }, + }, + }, + namespacesEnabled: false, + expectedErrMsgs: []string{ + "mode must be one of '', 'none', or 'failover'", + }, + }, } for name, testCase := range cases { t.Run(name, func(t *testing.T) { diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index 3cd3b37324..ec52c04e05 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -223,6 +223,15 @@ spec: type: integer type: object type: object + prioritizeByLocality: + description: PrioritizeByLocality contains the configuration for + locality aware routing. + properties: + mode: + description: Mode specifies the behavior of PrioritizeByLocality + routing. Valid values are "", "none", and "failover". + type: string + type: object redirect: description: Redirect when configured, all attempts to resolve the service this resolver defines will be substituted for the supplied From 19d2fb50658fb88ed1bb79149f2929101ee355b9 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 15 Jun 2023 13:56:41 -0400 Subject: [PATCH 238/592] set everything to correct version (#2342) making scripts more robust and removing changing helm chart --- Makefile | 4 ++++ control-plane/build-support/functions/10-util.sh | 11 +++-------- .../scripts/consul-enterprise-version.sh | 11 +++++++++++ control-plane/build-support/scripts/consul-version.sh | 4 ++++ 4 files changed, 22 insertions(+), 8 deletions(-) create mode 100755 control-plane/build-support/scripts/consul-enterprise-version.sh diff --git a/Makefile b/Makefile index 628b13e2f9..9547fd3547 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ VERSION = $(shell ./control-plane/build-support/scripts/version.sh control-plane/version/version.go) CONSUL_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-version.sh charts/consul/values.yaml) +CONSUL_ENTERPRISE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-enterprise-version.sh charts/consul/values.yaml) CONSUL_DATAPLANE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-dataplane-version.sh charts/consul/values.yaml) # ===========> Helm Targets @@ -180,6 +181,9 @@ version: consul-version: @echo $(CONSUL_IMAGE_VERSION) +consul-enterprise-version: + @echo $(CONSUL_ENTERPRISE_IMAGE_VERSION) + consul-dataplane-version: @echo $(CONSUL_DATAPLANE_IMAGE_VERSION) diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index 9ba6c26da9..72ce91720c 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -631,13 +631,8 @@ function update_version_helm { sed_i ${SED_EXT} -e "s/(version:[[:space:]]*)[^\"]*/\1${full_version}/g" "${cfile}" sed_i ${SED_EXT} -e "s/(appVersion:[[:space:]]*)[^\"]*/\1${full_consul_version}/g" "${cfile}" sed_i ${SED_EXT} -e "s/(image:.*\/consul-k8s-control-plane:)[^\"]*/image: $4${full_version}/g" "${cfile}" - if ! test -z "$3"; then - sed_i ${SED_EXT} -e "s/(image:.*\/consul:)[^\"]*/image: $6:${full_consul_version}/g" "${cfile}" - sed_i ${SED_EXT} -e "s/(image:.*\/consul:)[^\"]*/image: $6:${full_consul_version}/g" "${vfile}" - else - sed_i ${SED_EXT} -e "s/(image:.*\/consul-enterprise:)[^\"]*/image: $6:${full_consul_version}/g" "${cfile}" - sed_i ${SED_EXT} -e "s/(image:.*\/consul-enterprise:)[^\"]*/image: $6:${full_consul_version}/g" "${vfile}" - fi + sed_i ${SED_EXT} -e "s/(image:.*\/consul:)[^\"]*/image: $6:${full_consul_version}/g" "${cfile}" + sed_i ${SED_EXT} -e "s/(image:.*\/consul:)[^\"]*/image: $6:${full_consul_version}/g" "${vfile}" if test -z "$3"; then sed_i ${SED_EXT} -e "s/(artifacthub.io\/prerelease:[[:space:]]*)[^\"]*/\1false/g" "${cfile}" @@ -774,7 +769,7 @@ function prepare_dev { # * - error echo "prepare_dev: dir:$1 consul-k8s:$5 consul:$6 date:"$3" mode:dev" - set_version "$1" "$5" "$3" "dev" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" "$6" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-enterprise" + set_version "$1" "$5" "$3" "dev" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" "$6" "docker.mirror.hashicorp.services\/hashicorppreview\/consul" return 0 } diff --git a/control-plane/build-support/scripts/consul-enterprise-version.sh b/control-plane/build-support/scripts/consul-enterprise-version.sh new file mode 100755 index 0000000000..6b48bb4678 --- /dev/null +++ b/control-plane/build-support/scripts/consul-enterprise-version.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +FILE=$1 +VERSION=$(yq .global.image $FILE) + +if [[ !"${VERSION}" == *"consul:"* ]]; then + VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") +fi + +echo "${VERSION}" diff --git a/control-plane/build-support/scripts/consul-version.sh b/control-plane/build-support/scripts/consul-version.sh index e245e2a239..faaed33b20 100755 --- a/control-plane/build-support/scripts/consul-version.sh +++ b/control-plane/build-support/scripts/consul-version.sh @@ -4,4 +4,8 @@ FILE=$1 VERSION=$(yq .global.image $FILE) +if [[ "${VERSION}" == *"consul-enterprise:"* ]]; then + VERSION=$(echo ${VERSION} | sed "s/consul-enterprise:/consul:/g") +fi + echo "${VERSION}" From c4617fcfe573df6c39839c593eef6143bcb047c4 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Thu, 15 Jun 2023 16:29:58 -0400 Subject: [PATCH 239/592] api-gateway: fix cache and service deletion issue (#2377) * Fix cache and service deletion issue * Add comments * add in acceptance test * Fix indentation * Fix unit test for deleting gateway w/ consul services * Remove redundant service deregistration code * Exit loop early once registration is found for service * Fix import blocking * Set status on pods added to test * Apply suggestions from code review * Reduce count of test gateways to 10 from 100 --------- Co-authored-by: Nathan Coleman Co-authored-by: Sarah Alsmiller --- acceptance/go.mod | 3 +- acceptance/go.sum | 2 + .../api_gateway_gatewayclassconfig_test.go | 207 ++++++++++++++++++ control-plane/api-gateway/binding/binder.go | 1 + .../api-gateway/binding/binder_test.go | 25 ++- control-plane/api-gateway/cache/gateway.go | 17 -- 6 files changed, 236 insertions(+), 19 deletions(-) create mode 100644 acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go diff --git a/acceptance/go.mod b/acceptance/go.mod index ddce3e4e5c..59cbbab79f 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -16,6 +16,7 @@ require ( k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 k8s.io/client-go v0.26.3 + k8s.io/utils v0.0.0-20230209194617-a36077c30491 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/gateway-api v0.7.0 ) @@ -30,6 +31,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/deckarep/golang-set v1.7.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect @@ -120,7 +122,6 @@ require ( k8s.io/component-base v0.26.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index bd7421de60..578a95b1dd 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -144,6 +144,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= +github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= diff --git a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go new file mode 100644 index 0000000000..add65b89af --- /dev/null +++ b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go @@ -0,0 +1,207 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package apigateway + +import ( + "context" + "testing" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// GatewayClassConfig tests the creation of a gatewayclassconfig object and makes sure that its configuration +// is properly applied to any child gateway objects, namely that the number of gateway instances match the defined +// minInstances,maxInstances and defaultInstances parameters, and that changing the parent gateway does not affect +// the child gateways. +func TestAPIGateway_GatewayClassConfig(t *testing.T) { + ctx := suite.Environment().DefaultContext(t) + cfg := suite.Config() + helmValues := map[string]string{ + "global.logLevel": "trace", + "connectInject.enabled": "true", + } + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + consulCluster.Create(t) + // Override the default proxy config settings for this test. + consulClient, _ := consulCluster.SetupConsulClient(t, false) + _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + }, nil) + require.NoError(t, err) + + k8sClient := ctx.ControllerRuntimeClient(t) + namespace := "gateway-namespace" + + //create clean namespace + err = k8sClient.Create(context.Background(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + logger.Log(t, "deleting gateway namesapce") + k8sClient.Delete(context.Background(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }) + }) + + defaultInstances := pointer.Int32(2) + maxInstances := pointer.Int32(8) + minInstances := pointer.Int32(1) + // create a GatewayClassConfig with configuration set + gatewayClassConfigName := "gateway-class-config" + gatewayClassConfig := &v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: gatewayClassConfigName, + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: defaultInstances, + MaxInstances: maxInstances, + MinInstances: minInstances, + }, + }, + } + logger.Log(t, "creating gateway class config") + err = k8sClient.Create(context.Background(), gatewayClassConfig) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + logger.Log(t, "deleting all gateway class configs") + k8sClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}) + }) + + gatewayParametersRef := &gwv1beta1.ParametersReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: gwv1beta1.Kind(v1alpha1.GatewayClassConfigKind), + Name: gatewayClassConfigName, + } + + // create gateway class referencing gateway-class-config + gatewayClassName := "gateway-class" + logger.Log(t, "creating controlled gateway class") + createGatewayClass(t, k8sClient, gatewayClassName, gatewayClassControllerName, gatewayParametersRef) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + logger.Log(t, "deleting all gateway classes") + k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}) + }) + + // Create a certificate to reference in listeners + certificateInfo := generateCertificate(t, nil, "certificate.consul.local") + certificateName := "certificate" + certificate := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: certificateName, + Namespace: namespace, + Labels: map[string]string{ + "test-certificate": "true", + }, + }, + Type: corev1.SecretTypeTLS, + Data: map[string][]byte{ + corev1.TLSCertKey: certificateInfo.CertPEM, + corev1.TLSPrivateKeyKey: certificateInfo.PrivateKeyPEM, + }, + } + logger.Log(t, "creating certificate") + err = k8sClient.Create(context.Background(), certificate) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8sClient.Delete(context.Background(), certificate) + }) + + // Create gateway referencing gateway class config + gatewayName := "gateway" + logger.Log(t, "creating controlled gateway") + gateway := createGateway(t, k8sClient, gatewayName, namespace, gatewayClassName, certificateName) + // make sure it exists + logger.Log(t, "checking that gateway one is synchronized to Consul") + checkConsulExists(t, consulClient, api.APIGateway, gatewayName) + + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + logger.Log(t, "deleting all gateways") + k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(namespace)) + }) + + // Scenario: Gateway deployment should match the default instances defined on the gateway class config + logger.Log(t, "checking that gateway instances match defined gateway class config") + checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, defaultInstances, gateway) + + //Scenario: Updating the GatewayClassConfig should not affect gateways that have already been created + logger.Log(t, "updating gatewayclassconfig values") + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: gatewayClassConfigName, Namespace: namespace}, gatewayClassConfig) + require.NoError(t, err) + gatewayClassConfig.Spec.DeploymentSpec.DefaultInstances = pointer.Int32(8) + gatewayClassConfig.Spec.DeploymentSpec.MinInstances = pointer.Int32(5) + err = k8sClient.Update(context.Background(), gatewayClassConfig) + require.NoError(t, err) + checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, defaultInstances, gateway) + + //Scenario: gateways should be able to scale independently and not get overridden by the controller unless it's above the max + scale(t, k8sClient, gateway.Name, gateway.Namespace, maxInstances) + checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, maxInstances, gateway) + scale(t, k8sClient, gateway.Name, gateway.Namespace, pointer.Int32(10)) + checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, maxInstances, gateway) + scale(t, k8sClient, gateway.Name, gateway.Namespace, pointer.Int32(0)) + checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, minInstances, gateway) + +} + +func scale(t *testing.T, client client.Client, name, namespace string, scaleTo *int32) { + t.Helper() + + retryCheck(t, 30, func(r *retry.R) { + var deployment appsv1.Deployment + err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &deployment) + require.NoError(r, err) + + deployment.Spec.Replicas = scaleTo + err = client.Update(context.Background(), &deployment) + require.NoError(r, err) + }) +} + +func checkNumberOfInstances(t *testing.T, k8client client.Client, consulClient *api.Client, name, namespace string, wantNumber *int32, gateway *gwv1beta1.Gateway) { + t.Helper() + + retryCheck(t, 30, func(r *retry.R) { + //first check to make sure the number of replicas has been set properly + var deployment appsv1.Deployment + err := k8client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &deployment) + require.NoError(r, err) + require.EqualValues(r, *wantNumber, *deployment.Spec.Replicas) + + //then check to make sure the number of gateway pods matches the replicas generated + podList := corev1.PodList{} + labels := common.LabelsForGateway(gateway) + err = k8client.List(context.Background(), &podList, client.InNamespace(namespace), client.MatchingLabels(labels)) + require.NoError(r, err) + require.EqualValues(r, *wantNumber, len(podList.Items)) + + services, _, err := consulClient.Catalog().Service(name, "", nil) + require.NoError(r, err) + require.EqualValues(r, *wantNumber, len(services)) + }) +} diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index 28a26985a8..7fbf18d412 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -197,6 +197,7 @@ func (b *Binder) Snapshot() *Snapshot { for _, registration := range registrations { if service.ServiceID == registration.Service.ID { found = true + break } } if !found { diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index a3af702dab..bb30b98f52 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -15,6 +15,7 @@ import ( logrtest "github.com/go-logr/logr/testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,7 +26,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" ) func init() { @@ -809,6 +809,29 @@ func TestBinder_Registrations(t *testing.T) { {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, }, + Pods: []corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pod1"}, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pod2"}, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pod3"}, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, + }, + }, + }, }), expectedDeregistrations: []api.CatalogDeregistration{ {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, diff --git a/control-plane/api-gateway/cache/gateway.go b/control-plane/api-gateway/cache/gateway.go index d1de8dd7cc..0d79542eec 100644 --- a/control-plane/api-gateway/cache/gateway.go +++ b/control-plane/api-gateway/cache/gateway.go @@ -14,7 +14,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/event" ) type GatewayCache struct { @@ -22,8 +21,6 @@ type GatewayCache struct { serverMgr consul.ServerConnectionManager logger logr.Logger - events chan event.GenericEvent - data map[api.ResourceReference][]api.CatalogService dataMutex sync.RWMutex @@ -38,7 +35,6 @@ func NewGatewayCache(ctx context.Context, config Config) *GatewayCache { config: config, serverMgr: config.ConsulServerConnMgr, logger: config.Logger, - events: make(chan event.GenericEvent), data: make(map[api.ResourceReference][]api.CatalogService), subscribedGateways: make(map[api.ResourceReference]context.CancelFunc), ctx: ctx, @@ -140,18 +136,5 @@ func (r *GatewayCache) subscribeToGateway(ctx context.Context, ref api.ResourceR r.dataMutex.Lock() r.data[common.NormalizeMeta(ref)] = derefed r.dataMutex.Unlock() - - event := event.GenericEvent{ - Object: newConfigEntryObject(resource), - } - - select { - case <-ctx.Done(): - r.dataMutex.Lock() - delete(r.data, ref) - r.dataMutex.Unlock() - return - case r.events <- event: - } } } From 47d40635a33ab8bb86b1d2d3953d2aecf151520b Mon Sep 17 00:00:00 2001 From: Rahul Date: Mon, 19 Jun 2023 10:57:36 +0530 Subject: [PATCH 240/592] Adding support for weighted k8s service (#2293) * Adding support for weighted k8s service * Adding changelog * if per-app weight is 0 then pull the weight to 1 * Addressing review comments * Addressing review comments * Addressing review comments * Comment update * Comment update * Parameterized table test * Parameterized table test * fixing linting issue * fixing linting issue --------- Co-authored-by: srahul3 --- .changelog/2293.txt | 3 + control-plane/catalog/to-consul/annotation.go | 6 + control-plane/catalog/to-consul/resource.go | 41 ++++++ .../catalog/to-consul/resource_test.go | 133 ++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 .changelog/2293.txt diff --git a/.changelog/2293.txt b/.changelog/2293.txt new file mode 100644 index 0000000000..ce6d888bcd --- /dev/null +++ b/.changelog/2293.txt @@ -0,0 +1,3 @@ +```release-note:feature +sync-catalog: add ability to support weighted loadbalancing by service annotation `consul.hashicorp.com/service-weight: ` +``` \ No newline at end of file diff --git a/control-plane/catalog/to-consul/annotation.go b/control-plane/catalog/to-consul/annotation.go index 27e37ca217..edca70b60c 100644 --- a/control-plane/catalog/to-consul/annotation.go +++ b/control-plane/catalog/to-consul/annotation.go @@ -26,4 +26,10 @@ const ( // annotationServiceMetaPrefix is the prefix for setting meta key/value // for a service. The remainder of the key is the meta key. annotationServiceMetaPrefix = "consul.hashicorp.com/service-meta-" + + // annotationServiceWeight is the key of the annotation that determines + // the traffic weight of the service which is spanned over multiple k8s cluster. + // e.g. Service `backend` in k8s cluster `A` receives 25% of the traffic + // compared to same `backend` service in k8s cluster `B`. + annotationServiceWeight = "consul.hashicorp.com/service-weight" ) diff --git a/control-plane/catalog/to-consul/resource.go b/control-plane/catalog/to-consul/resource.go index 6a4e913d80..08aeec8821 100644 --- a/control-plane/catalog/to-consul/resource.go +++ b/control-plane/catalog/to-consul/resource.go @@ -511,6 +511,19 @@ func (t *ServiceResource) generateRegistrations(key string) { r.Service = &rs r.Service.ID = serviceID(r.Service.Service, ip) r.Service.Address = ip + // Adding information about service weight. + // Overrides the existing weight if present. + if weight, ok := svc.Annotations[annotationServiceWeight]; ok && weight != "" { + weightI, err := getServiceWeight(weight) + if err == nil { + r.Service.Weights = consulapi.AgentWeights{ + Passing: weightI, + } + } else { + t.Log.Debug("[generateRegistrations] service weight err: ", err) + } + } + t.consulMap[key] = append(t.consulMap[key], &r) } @@ -547,6 +560,19 @@ func (t *ServiceResource) generateRegistrations(key string) { r.Service.ID = serviceID(r.Service.Service, addr) r.Service.Address = addr + // Adding information about service weight. + // Overrides the existing weight if present. + if weight, ok := svc.Annotations[annotationServiceWeight]; ok && weight != "" { + weightI, err := getServiceWeight(weight) + if err == nil { + r.Service.Weights = consulapi.AgentWeights{ + Passing: weightI, + } + } else { + t.Log.Debug("[generateRegistrations] service weight err: ", err) + } + } + t.consulMap[key] = append(t.consulMap[key], &r) } } @@ -999,3 +1025,18 @@ func (t *ServiceResource) isIngressService(key string) bool { func consulHealthCheckID(k8sNS string, serviceID string) string { return fmt.Sprintf("%s/%s", k8sNS, serviceID) } + +// Calculates the passing service weight. +func getServiceWeight(weight string) (int, error) { + // error validation if the input param is a number. + weightI, err := strconv.Atoi(weight) + if err != nil { + return -1, err + } + + if weightI <= 1 { + return -1, fmt.Errorf("expecting the service annotation %s value to be greater than 1", annotationServiceWeight) + } + + return weightI, nil +} diff --git a/control-plane/catalog/to-consul/resource_test.go b/control-plane/catalog/to-consul/resource_test.go index 3c01088c0d..3b8fb78497 100644 --- a/control-plane/catalog/to-consul/resource_test.go +++ b/control-plane/catalog/to-consul/resource_test.go @@ -56,6 +56,139 @@ func TestServiceResource_createDelete(t *testing.T) { }) } +// Test that Loadbalancer service weight is set from service annotation. +func TestServiceWeight_ingress(t *testing.T) { + t.Parallel() + client := fake.NewSimpleClientset() + syncer := newTestSyncer() + serviceResource := defaultServiceResource(client, syncer) + + // Start the controller + closer := controller.TestControllerRun(&serviceResource) + defer closer() + + // Insert an LB service + svc := lbService("foo", metav1.NamespaceDefault, "1.2.3.4") + svc.Annotations[annotationServiceWeight] = "22" + svc.Status.LoadBalancer.Ingress = append( + svc.Status.LoadBalancer.Ingress, + corev1.LoadBalancerIngress{IP: "3.3.3.3"}, + ) + + svc.Status.LoadBalancer.Ingress = append( + svc.Status.LoadBalancer.Ingress, + corev1.LoadBalancerIngress{IP: "4.4.4.4"}, + ) + + _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) + require.NoError(t, err) + + // Verify what we got + retry.Run(t, func(r *retry.R) { + syncer.Lock() + defer syncer.Unlock() + actual := syncer.Registrations + require.Len(r, actual, 3) + require.Equal(r, "foo", actual[1].Service.Service) + require.Equal(r, "3.3.3.3", actual[1].Service.Address) + require.Equal(r, 22, actual[1].Service.Weights.Passing) + require.Equal(r, "foo", actual[2].Service.Service) + require.Equal(r, "4.4.4.4", actual[2].Service.Address) + require.Equal(r, 22, actual[2].Service.Weights.Passing) + require.NotEqual(r, actual[1].Service.ID, actual[2].Service.ID) + }) +} + +// Test that Loadbalancer service weight is set from service annotation. +func TestServiceWeight_externalIP(t *testing.T) { + t.Parallel() + client := fake.NewSimpleClientset() + syncer := newTestSyncer() + serviceResource := defaultServiceResource(client, syncer) + + // Start the controller + closer := controller.TestControllerRun(&serviceResource) + defer closer() + + // Insert an LB service + svc := lbService("foo", metav1.NamespaceDefault, "1.2.3.4") + svc.Annotations[annotationServiceWeight] = "22" + svc.Spec.ExternalIPs = []string{"3.3.3.3", "4.4.4.4"} + + _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) + require.NoError(t, err) + + // Verify what we got + retry.Run(t, func(r *retry.R) { + syncer.Lock() + defer syncer.Unlock() + actual := syncer.Registrations + require.Len(r, actual, 2) + require.Equal(r, "foo", actual[0].Service.Service) + require.Equal(r, "3.3.3.3", actual[0].Service.Address) + require.Equal(r, 22, actual[0].Service.Weights.Passing) + require.Equal(r, "foo", actual[1].Service.Service) + require.Equal(r, "4.4.4.4", actual[1].Service.Address) + require.Equal(r, 22, actual[1].Service.Weights.Passing) + require.NotEqual(r, actual[0].Service.ID, actual[1].Service.ID) + }) +} + +// Test service weight. +func TestServiceWeight(t *testing.T) { + t.Parallel() + cases := map[string]struct { + Weight string + ExpectError bool + ExtectedWeight int + }{ + "external-IP": { + Weight: "22", + ExpectError: false, + ExtectedWeight: 22, + }, + "non-int-weight": { + Weight: "non-int", + ExpectError: true, + ExtectedWeight: 0, + }, + "one-weight": { + Weight: "1", + ExpectError: true, + ExtectedWeight: 0, + }, + "zero-weight": { + Weight: "0", + ExpectError: true, + ExtectedWeight: 0, + }, + "negative-weight": { + Weight: "-2", + ExpectError: true, + ExtectedWeight: 0, + }, + "greater-than-100-is-allowed": { + Weight: "1000", + ExpectError: false, + ExtectedWeight: 1000, + }, + } + + for name, c := range cases { + t.Run(name, func(tt *testing.T) { + weightI, err := getServiceWeight(c.Weight) + // Verify what we got + retry.Run(tt, func(r *retry.R) { + if c.ExpectError { + require.Error(r, err) + } else { + require.Equal(r, c.ExtectedWeight, weightI) + } + }) + }) + } +} + // Test that we're default enabled. func TestServiceResource_defaultEnable(t *testing.T) { t.Parallel() From fe4857e34b1eeea5aec9d588cb3c762ca83025a8 Mon Sep 17 00:00:00 2001 From: Bryan Eastes Date: Mon, 19 Jun 2023 06:42:30 -0700 Subject: [PATCH 241/592] Bumping go-discover to the lastest version (#2390) * Bumping go-discover to the lastest version --- .changelog/2390.txt | 3 +++ control-plane/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .changelog/2390.txt diff --git a/.changelog/2390.txt b/.changelog/2390.txt new file mode 100644 index 0000000000..a4546bd781 --- /dev/null +++ b/.changelog/2390.txt @@ -0,0 +1,3 @@ +```release-note:security +Update [Go-Discover](https://github.com/hashicorp/go-discover) in the container has been updated to address [CVE-2020-14040](https://github.com/advisories/GHSA-5rcv-m4m3-hfh7) +``` diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index f401ac8262..c09f5ecf80 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -17,7 +17,7 @@ # go-discover builds the discover binary (which we don't currently publish # either). FROM golang:1.19.2-alpine as go-discover -RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@49f60c093101c9c5f6b04d5b1c80164251a761a6 +RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@214571b6a5309addf3db7775f4ee8cf4d264fd5f # dev copies the binary from a local build # ----------------------------------- From a3c8771b8600f7045c08fccc67013c64e1276ec5 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:43:48 -0700 Subject: [PATCH 242/592] Pin Kind versions on release branches (#2384) * pinned kind configuration for CI tests - created a yaml file with the desired pinned versions - created a script to read the yaml - added a make target which can be used in CI to get the desired kind inputs/config --------- Co-authored-by: Curt Bushko --- Makefile | 15 +++++++++++++-- acceptance/ci-inputs/kind-inputs.yaml | 3 +++ .../build-support/scripts/read-yaml-config.sh | 10 ++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 acceptance/ci-inputs/kind-inputs.yaml create mode 100755 control-plane/build-support/scripts/read-yaml-config.sh diff --git a/Makefile b/Makefile index 9547fd3547..34497b1f07 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ VERSION = $(shell ./control-plane/build-support/scripts/version.sh control-plane CONSUL_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-version.sh charts/consul/values.yaml) CONSUL_ENTERPRISE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-enterprise-version.sh charts/consul/values.yaml) CONSUL_DATAPLANE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-dataplane-version.sh charts/consul/values.yaml) +KIND_VERSION= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh acceptance/ci-inputs/kind-inputs.yaml .kindVersion) +KIND_NODE_IMAGE= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh acceptance/ci-inputs/kind-inputs.yaml .kindNodeImage) +KUBECTL_VERSION= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh acceptance/ci-inputs/kind-inputs.yaml .kubectlVersion) # ===========> Helm Targets @@ -105,7 +108,6 @@ terraform-fmt: # ===========> CLI Targets - cli-dev: @echo "==> Installing consul-k8s CLI tool for ${GOOS}/${GOARCH}" @cd cli; go build -o ./bin/consul-k8s; cp ./bin/consul-k8s ${GOPATH}/bin/ @@ -114,7 +116,6 @@ cli-fips-dev: @echo "==> Installing consul-k8s CLI tool for ${GOOS}/${GOARCH}" @cd cli; CGO_ENABLED=1 GOEXPERIMENT=boringcrypto go build -o ./bin/consul-k8s -tags "fips"; cp ./bin/consul-k8s ${GOPATH}/bin/ - cli-lint: ## Run linter in the control-plane directory. cd cli; golangci-lint run -c ../.golangci.yml @@ -187,6 +188,16 @@ consul-enterprise-version: consul-dataplane-version: @echo $(CONSUL_DATAPLANE_IMAGE_VERSION) +kind-version: + @echo $(KIND_VERSION) + +kind-node-image: + @echo $(KIND_NODE_IMAGE) + +kubectl-version: + @echo $(KUBECTL_VERSION) + + # ===========> Release Targets diff --git a/acceptance/ci-inputs/kind-inputs.yaml b/acceptance/ci-inputs/kind-inputs.yaml new file mode 100644 index 0000000000..615ff302ba --- /dev/null +++ b/acceptance/ci-inputs/kind-inputs.yaml @@ -0,0 +1,3 @@ +kindVersion: v0.19.0 +kindNodeImage: kindest/node:v1.27.1 +kubectlVersion: v1.27.1 diff --git a/control-plane/build-support/scripts/read-yaml-config.sh b/control-plane/build-support/scripts/read-yaml-config.sh new file mode 100755 index 0000000000..37cfd0cc17 --- /dev/null +++ b/control-plane/build-support/scripts/read-yaml-config.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +INPUT_FILE=$1 +FIELD=$2 + +VALUE=$(yq $FIELD $INPUT_FILE) + +echo "${VALUE}" From aaa54c26105283690b181ec60d05c993404ea6ac Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:39:41 -0400 Subject: [PATCH 243/592] [COMPLIANCE] Add Copyright and License Headers (#2400) Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- acceptance/ci-inputs/kind-inputs.yaml | 3 +++ .../fixtures/bases/static-server-tcp/servicedefaults.yaml | 3 +++ cli/version/fips_build.go | 3 +++ cli/version/non_fips_build.go | 3 +++ control-plane/version/fips_build.go | 3 +++ control-plane/version/non_fips_build.go | 3 +++ 6 files changed, 18 insertions(+) diff --git a/acceptance/ci-inputs/kind-inputs.yaml b/acceptance/ci-inputs/kind-inputs.yaml index 615ff302ba..ba21d2cdaf 100644 --- a/acceptance/ci-inputs/kind-inputs.yaml +++ b/acceptance/ci-inputs/kind-inputs.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + kindVersion: v0.19.0 kindNodeImage: kindest/node:v1.27.1 kubectlVersion: v1.27.1 diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml index 500051db87..f89765cf6d 100644 --- a/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/cli/version/fips_build.go b/cli/version/fips_build.go index 4d04cc6539..63e0e68883 100644 --- a/cli/version/fips_build.go +++ b/cli/version/fips_build.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build fips package version diff --git a/cli/version/non_fips_build.go b/cli/version/non_fips_build.go index f72aecae73..ce99575d2c 100644 --- a/cli/version/non_fips_build.go +++ b/cli/version/non_fips_build.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build !fips package version diff --git a/control-plane/version/fips_build.go b/control-plane/version/fips_build.go index 4d04cc6539..63e0e68883 100644 --- a/control-plane/version/fips_build.go +++ b/control-plane/version/fips_build.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build fips package version diff --git a/control-plane/version/non_fips_build.go b/control-plane/version/non_fips_build.go index f72aecae73..ce99575d2c 100644 --- a/control-plane/version/non_fips_build.go +++ b/control-plane/version/non_fips_build.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + //go:build !fips package version From 63c76820712082fe7a2e62277843f5891ec68bb9 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 20 Jun 2023 10:42:55 -0400 Subject: [PATCH 244/592] update consul-dataplane on main to use 1.2-dev (#2325) --- charts/consul/Chart.yaml | 2 +- charts/consul/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index c55c6be6a2..64d7ed4ed0 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -20,7 +20,7 @@ annotations: - name: consul-k8s-control-plane image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.0-dev - name: consul-dataplane - image: hashicorp/consul-dataplane:1.1.0 + image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.2-dev - name: envoy image: envoyproxy/envoy:v1.25.1 artifacthub.io/license: MPL-2.0 diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 5c77a27f33..faf7f5bcdf 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -563,7 +563,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: "hashicorppreview/consul-dataplane:1.1-dev" + imageConsulDataplane: "docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.2-dev" # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. From 4141f6f1d35af6bc4802bb820c8d16455fbf9ed9 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Tue, 20 Jun 2023 09:53:04 -0500 Subject: [PATCH 245/592] Acceptance test for permissive mTLS (#2378) --- .../tests/connect/permissive_mtls_test.go | 94 +++++++++++++++++++ .../mesh-config-permissive-allowed.yaml | 6 ++ ...ice-defaults-static-server-permissive.yaml | 7 ++ ...service-defaults-static-server-strict.yaml | 7 ++ 4 files changed, 114 insertions(+) create mode 100644 acceptance/tests/connect/permissive_mtls_test.go create mode 100644 acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml create mode 100644 acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml create mode 100644 acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml diff --git a/acceptance/tests/connect/permissive_mtls_test.go b/acceptance/tests/connect/permissive_mtls_test.go new file mode 100644 index 0000000000..f0a55779ee --- /dev/null +++ b/acceptance/tests/connect/permissive_mtls_test.go @@ -0,0 +1,94 @@ +package connect + +import ( + "context" + "testing" + + "github.com/hashicorp/consul-k8s/acceptance/framework/config" + "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestConnectInject_PermissiveMTLS(t *testing.T) { + cfg := suite.Config() + if !cfg.EnableTransparentProxy { + t.Skipf("skipping this because -enable-transparent-proxy is not set") + } + + ctx := suite.Environment().DefaultContext(t) + + releaseName := helpers.RandomName() + connHelper := connhelper.ConnectHelper{ + ClusterKind: consul.Helm, + Secure: true, + ReleaseName: releaseName, + Ctx: ctx, + Cfg: cfg, + } + connHelper.Setup(t) + connHelper.Install(t) + + deployNonMeshClient(t, connHelper) + deployStaticServer(t, cfg, connHelper) + + kubectlOpts := connHelper.Ctx.KubectlOptions(t) + logger.Logf(t, "Check that incoming non-mTLS connection fails in MutualTLSMode = strict") + k8s.CheckStaticServerConnectionFailing(t, kubectlOpts, "static-client", "http://static-server") + + logger.Log(t, "Set allowEnablingPermissiveMutualTLS = true") + writeCrd(t, connHelper, "../fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml") + + logger.Log(t, "Set mutualTLSMode = permissive for static-server") + writeCrd(t, connHelper, "../fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml") + + logger.Log(t, "Check that incoming mTLS connection is successful in MutualTLSMode = permissive") + k8s.CheckStaticServerConnectionSuccessful(t, kubectlOpts, "static-client", "http://static-server") +} + +func deployNonMeshClient(t *testing.T, ch connhelper.ConnectHelper) { + t.Helper() + + logger.Log(t, "Creating static-client deployment with connect-inject=false") + k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), ch.Cfg.NoCleanupOnFailure, ch.Cfg.DebugDirectory, "../fixtures/bases/static-client") + requirePodContainers(t, ch, "app=static-client", 1) +} + +func deployStaticServer(t *testing.T, cfg *config.TestConfig, ch connhelper.ConnectHelper) { + t.Helper() + + logger.Log(t, "Creating static-server deployment with connect-inject=true") + k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + requirePodContainers(t, ch, "app=static-server", 2) +} + +func writeCrd(t *testing.T, ch connhelper.ConnectHelper, path string) { + t.Helper() + + t.Cleanup(func() { + _, _ = k8s.RunKubectlAndGetOutputE(t, ch.Ctx.KubectlOptions(t), "delete", "-f", path) + }) + + _, err := k8s.RunKubectlAndGetOutputE(t, ch.Ctx.KubectlOptions(t), "apply", "-f", path) + require.NoError(t, err) +} + +func requirePodContainers(t *testing.T, ch connhelper.ConnectHelper, selector string, nContainers int) { + t.Helper() + + opts := ch.Ctx.KubectlOptions(t) + client := ch.Ctx.KubernetesClient(t) + retry.Run(t, func(r *retry.R) { + podList, err := client.CoreV1(). + Pods(opts.Namespace). + List(context.Background(), metav1.ListOptions{LabelSelector: selector}) + require.NoError(r, err) + require.Len(r, podList.Items, 1) + require.Len(r, podList.Items[0].Spec.Containers, nContainers) + }) +} diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml new file mode 100644 index 0000000000..944792588a --- /dev/null +++ b/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml @@ -0,0 +1,6 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: Mesh +metadata: + name: mesh +spec: + allowEnablingPermissiveMutualTLS: true diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml new file mode 100644 index 0000000000..6fd335b361 --- /dev/null +++ b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml @@ -0,0 +1,7 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: static-server + namespace: default +spec: + mutualTLSMode: "permissive" diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml new file mode 100644 index 0000000000..e47ae7aa5d --- /dev/null +++ b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml @@ -0,0 +1,7 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: static-server + namespace: default +spec: + mutualTLSMode: "strict" From 08534e3454b6621bc654abc08a64a4720e8a9760 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Wed, 21 Jun 2023 16:01:15 -0400 Subject: [PATCH 246/592] Revert "added imagePullPolicy for images in values.yaml (#2310)" (#2415) This reverts commit 285096241e0d5c5b6d53dd8a37889ab3ea5a8af2. --- .changelog/2310.txt | 3 --- .../consul/templates/api-gateway-controller-deployment.yaml | 3 --- charts/consul/templates/api-gateway-gatewayclassconfig.yaml | 1 - charts/consul/templates/client-daemonset.yaml | 1 - charts/consul/templates/cni-daemonset.yaml | 1 - charts/consul/templates/enterprise-license-job.yaml | 1 - charts/consul/templates/gateway-cleanup-job.yaml | 1 - charts/consul/templates/gateway-resources-job.yaml | 1 - charts/consul/templates/mesh-gateway-deployment.yaml | 2 -- charts/consul/templates/partition-init-job.yaml | 1 - charts/consul/templates/server-acl-init-cleanup-job.yaml | 1 - charts/consul/templates/server-acl-init-job.yaml | 1 - charts/consul/templates/server-statefulset.yaml | 1 - .../consul/templates/webhook-cert-manager-deployment.yaml | 1 - charts/consul/values.yaml | 6 ------ 15 files changed, 25 deletions(-) delete mode 100644 .changelog/2310.txt diff --git a/.changelog/2310.txt b/.changelog/2310.txt deleted file mode 100644 index 5e37de44ea..0000000000 --- a/.changelog/2310.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -helm: Added imagePullPolicy global field which can be configured to override the default behaviour. -``` \ No newline at end of file diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index c942576034..8c5c2fa73e 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -57,7 +57,6 @@ spec: containers: - name: api-gateway-controller image: {{ .Values.apiGateway.image }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} ports: - containerPort: 9090 name: sds @@ -220,7 +219,6 @@ spec: {{- if .Values.global.acls.manageSystemACLs }} - name: copy-consul-bin image: {{ .Values.global.image | quote }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - cp - /bin/consul @@ -258,7 +256,6 @@ spec: {{- end}} {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} volumeMounts: - mountPath: /consul/login name: consul-data diff --git a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml index 8688ee6ae7..ba0e6c63db 100644 --- a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml +++ b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml @@ -65,7 +65,6 @@ spec: image: consulAPIGateway: {{ .Values.apiGateway.image }} envoy: {{ .Values.apiGateway.imageEnvoy }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} {{- if .Values.apiGateway.managedGatewayClass.nodeSelector }} nodeSelector: {{ tpl .Values.apiGateway.managedGatewayClass.nodeSelector . | indent 4 | trim }} diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index ba19343652..09a70b394e 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -493,7 +493,6 @@ spec: {{- if .Values.global.acls.manageSystemACLs }} - name: client-acl-init image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index 33ffb0a77e..ae04d9e657 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -61,7 +61,6 @@ spec: # This container installs the consul CNI binaries and CNI network config file on each node - name: install-cni image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} securityContext: privileged: true command: diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index 2a3fa01d00..0122690104 100644 --- a/charts/consul/templates/enterprise-license-job.yaml +++ b/charts/consul/templates/enterprise-license-job.yaml @@ -124,7 +124,6 @@ spec: initContainers: - name: ent-license-acl-init image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - "/bin/sh" - "-ec" diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index e4656916de..44f032b5fd 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -37,7 +37,6 @@ spec: containers: - name: gateway-cleanup image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index ea38d7af32..441e64eb14 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -37,7 +37,6 @@ spec: containers: - name: gateway-resources image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index 4150b2bdfd..449d6ae492 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -121,7 +121,6 @@ spec: initContainers: - name: mesh-gateway-init image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: NAMESPACE valueFrom: @@ -180,7 +179,6 @@ spec: containers: - name: mesh-gateway image: {{ .Values.global.imageConsulDataplane | quote }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} {{- if .Values.meshGateway.resources }} resources: {{- if eq (typeOf .Values.meshGateway.resources) "string" }} diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index b351d10027..db73ef783b 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -81,7 +81,6 @@ spec: containers: - name: partition-init-job image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 3676144b40..35b0877ab4 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -53,7 +53,6 @@ spec: containers: - name: server-acl-init-cleanup image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index e42a073c42..e62db41ec2 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -122,7 +122,6 @@ spec: containers: - name: server-acl-init-job image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 0c2eb1bffa..0bde9b881a 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -225,7 +225,6 @@ spec: initContainers: - name: locality-init image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: NODE_NAME valueFrom: diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index 25e382be84..dd93c039d2 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -50,7 +50,6 @@ spec: -deployment-name={{ template "consul.fullname" . }}-webhook-cert-manager \ -deployment-namespace={{ .Release.Namespace }} image: {{ .Values.global.imageK8S }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} name: webhook-cert-manager resources: limits: diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index faf7f5bcdf..eff13a24bc 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -52,12 +52,6 @@ global: # Changing the partition name would require an un-install and a re-install with the updated name. # Must be "default" in the server cluster ie the Kubernetes cluster that the Consul server pods are deployed onto. name: "default" - - # Set imagePullPolicy for all images used. This is applies to all the images being used. - # One of "IfNotPresent", "Always", "Never" - # Refer to https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy - # @type: string - imagePullPolicy: "" # The name (and tag) of the Consul Docker image for clients and servers. # This can be overridden per component. This should be pinned to a specific From 883fbdcfa4947cf38dd3e1062c2913021b2545ac Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:22:59 -0700 Subject: [PATCH 247/592] update with new make targets (#2411) - allow configuration of acceptance testing matrices --- Makefile | 11 +++++++++++ .../ci-inputs/aks_acceptance_test_packages.yaml | 3 +++ .../ci-inputs/eks_acceptance_test_packages.yaml | 3 +++ .../ci-inputs/gke_acceptance_test_packages.yaml | 3 +++ .../ci-inputs/kind_acceptance_test_packages.yaml | 6 ++++++ .../build-support/scripts/set_test_package_matrix.sh | 12 ++++++++++++ 6 files changed, 38 insertions(+) create mode 100644 acceptance/ci-inputs/aks_acceptance_test_packages.yaml create mode 100644 acceptance/ci-inputs/eks_acceptance_test_packages.yaml create mode 100644 acceptance/ci-inputs/gke_acceptance_test_packages.yaml create mode 100644 acceptance/ci-inputs/kind_acceptance_test_packages.yaml create mode 100755 control-plane/build-support/scripts/set_test_package_matrix.sh diff --git a/Makefile b/Makefile index 34497b1f07..de35275868 100644 --- a/Makefile +++ b/Makefile @@ -197,6 +197,17 @@ kind-node-image: kubectl-version: @echo $(KUBECTL_VERSION) +kind-test-packages: + @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/kind_acceptance_test_packages.yaml" + +gke-test-packages: + @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/gke_acceptance_test_packages.yaml" + +eks-test-packages: + @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/eks_acceptance_test_packages.yaml" + +aks-test-packages: + @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/aks_acceptance_test_packages.yaml" # ===========> Release Targets diff --git a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml new file mode 100644 index 0000000000..cef04a3205 --- /dev/null +++ b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml @@ -0,0 +1,3 @@ +- {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} +- {runner: 1, test-packages: "consul-dns example partitions metrics sync"} +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml new file mode 100644 index 0000000000..cef04a3205 --- /dev/null +++ b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml @@ -0,0 +1,3 @@ +- {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} +- {runner: 1, test-packages: "consul-dns example partitions metrics sync"} +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml new file mode 100644 index 0000000000..cef04a3205 --- /dev/null +++ b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml @@ -0,0 +1,3 @@ +- {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} +- {runner: 1, test-packages: "consul-dns example partitions metrics sync"} +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml new file mode 100644 index 0000000000..74991abd76 --- /dev/null +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -0,0 +1,6 @@ +- {runner: 0, test-packages: "partitions"} +- {runner: 1, test-packages: "peering"} +- {runner: 2, test-packages: "connect snapshot-agent wan-federation"} +- {runner: 3, test-packages: "cli vault metrics"} +- {runner: 4, test-packages: "api-gateway ingress-gateway sync example consul-dns"} +- {runner: 5, test-packages: "config-entries terminating-gateway basic"} \ No newline at end of file diff --git a/control-plane/build-support/scripts/set_test_package_matrix.sh b/control-plane/build-support/scripts/set_test_package_matrix.sh new file mode 100755 index 0000000000..b248cbad07 --- /dev/null +++ b/control-plane/build-support/scripts/set_test_package_matrix.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +INPUT_FILE=$1 + +# convert readable yaml to json for github actions consumption +# do not include any pretty print, print to single line with -I 0 +VALUE=$(yq eval 'select(fileIndex == 0)' "$INPUT_FILE" -o json -I 0) + +echo "$VALUE" \ No newline at end of file From 5b1856e3a26a3b03ac35cc0d5ea58c0ce20e3b80 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Fri, 23 Jun 2023 10:10:35 -0400 Subject: [PATCH 248/592] feat(helm): add configurable server-acl-init and cleanup resource limits (#2416) * feat(helm): add configurable server-acl-init and cleanup resource limits * Apply suggestions from code review Co-authored-by: Ashwin Venkatesh * bugfix yaml path * fix bats test --------- Co-authored-by: Ashwin Venkatesh --- .changelog/2416.txt | 3 +++ .../server-acl-init-cleanup-job.yaml | 9 +++---- .../consul/templates/server-acl-init-job.yaml | 9 +++---- .../unit/server-acl-init-cleanup-job.bats | 24 +++++++++++++++++ .../consul/test/unit/server-acl-init-job.bats | 24 +++++++++++++++++ charts/consul/values.yaml | 27 +++++++++++++++++++ 6 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 .changelog/2416.txt diff --git a/.changelog/2416.txt b/.changelog/2416.txt new file mode 100644 index 0000000000..e261758542 --- /dev/null +++ b/.changelog/2416.txt @@ -0,0 +1,3 @@ +```release-note:feature +helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. +``` diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 35b0877ab4..38ecfcf1b0 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -61,13 +61,10 @@ spec: - -log-json={{ .Values.global.logJSON }} - -k8s-namespace={{ .Release.Namespace }} - {{ template "consul.fullname" . }}-server-acl-init + {{- if .Values.global.acls.resources }} resources: - requests: - memory: "50Mi" - cpu: "50m" - limits: - memory: "50Mi" - cpu: "50m" + {{- toYaml .Values.global.acls.resources | nindent 12 }} + {{- end }} {{- if .Values.global.acls.tolerations }} tolerations: {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index e62db41ec2..27272d0f76 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -307,13 +307,10 @@ spec: {{- end }} {{- end }} {{- end }} + {{- if .Values.global.acls.resources }} resources: - requests: - memory: "50Mi" - cpu: "50m" - limits: - memory: "50Mi" - cpu: "50m" + {{- toYaml .Values.global.acls.resources | nindent 10 }} + {{- end }} {{- if .Values.global.acls.tolerations }} tolerations: {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} diff --git a/charts/consul/test/unit/server-acl-init-cleanup-job.bats b/charts/consul/test/unit/server-acl-init-cleanup-job.bats index 947cfa9b42..bf6a455d0e 100644 --- a/charts/consul/test/unit/server-acl-init-cleanup-job.bats +++ b/charts/consul/test/unit/server-acl-init-cleanup-job.bats @@ -159,3 +159,27 @@ load _helpers [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# resources + +@test "serverACLInitCleanup/Job: resources defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=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 "serverACLInitCleanup/Job: resources can be overridden" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.resources.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 81064c95eb..a0d0950e89 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -2202,3 +2202,27 @@ load _helpers [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# resources + +@test "serverACLInit/Job: resources defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=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 "serverACLInit/Job: resources can be overridden" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.resources.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index eff13a24bc..89336e319e 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -430,6 +430,33 @@ global: # @type: string secretKey: null + # The resource requests (CPU, memory, etc.) for the server-acl-init and server-acl-init-cleanup pods. + # This should be a YAML map corresponding to a Kubernetes + # [`ResourceRequirements``](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#resourcerequirements-v1-core) + # object. + # + # Example: + # + # ```yaml + # resources: + # requests: + # memory: '200Mi' + # cpu: '100m' + # limits: + # memory: '200Mi' + # cpu: '100m' + # ``` + # + # @recurse: false + # @type: map + resources: + requests: + memory: "50Mi" + cpu: "50m" + limits: + memory: "50Mi" + cpu: "50m" + # partitionToken references a Vault secret containing the ACL token to be used in non-default partitions. # This value should only be provided in the default partition and only when setting # the `global.secretsBackend.vault.enabled` value to true. From c6c5d521176749cbbb0e302afddd2620853aca7c Mon Sep 17 00:00:00 2001 From: Alvin Huang <17609145+alvin-huang@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:17:07 -0400 Subject: [PATCH 249/592] update redhat registry id (#2337) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 711c228e11..917456e9d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -377,7 +377,7 @@ jobs: pkg_name: consul-k8s-control-plane_${{ env.version }} bin_name: consul-k8s-control-plane workdir: control-plane - redhat_tag: quay.io/redhat-isv-containers/6483ed53b430df51b731406c:${{env.version}}-ubi # this is different than the non-FIPS one + redhat_tag: quay.io/redhat-isv-containers/6486b1beabfc4e51588c0416:${{env.version}}-ubi # this is different than the non-FIPS one build-docker-ubi-dockerhub: name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI build for DockerHub From f783f7e924cf93e0d39e5113f113dff8eaef4d1a Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Fri, 23 Jun 2023 14:18:58 -0400 Subject: [PATCH 250/592] Fix auditlog config (#2434) --- charts/consul/templates/server-config-configmap.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index d3a0206afd..7e3d251001 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -191,7 +191,7 @@ data: audit-logging.json: |- { "audit": { - "enabled": "true", + "enabled": true, "sink": { {{- range $index, $element := .Values.server.auditLogs.sinks }} {{- if ne $index 0 }},{{end}} From 79db26379e814694e57e5ddae0cbd5cb76ac03cd Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Fri, 23 Jun 2023 14:48:36 -0400 Subject: [PATCH 251/592] Add acceptance test to test sync + ingress (#2421) --- acceptance/framework/config/config.go | 1 + acceptance/framework/flags/flags.go | 4 + .../tests/fixtures/bases/ingress/ingress.yaml | 23 ++++++ .../fixtures/bases/ingress/kustomization.yaml | 5 ++ acceptance/tests/sync/sync_catalog_test.go | 81 +++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 acceptance/tests/fixtures/bases/ingress/ingress.yaml create mode 100644 acceptance/tests/fixtures/bases/ingress/kustomization.yaml diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index 8a5ba7893e..18673ca260 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -62,6 +62,7 @@ type TestConfig struct { DebugDirectory string UseAKS bool + UseEKS bool UseGKE bool UseKind bool diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index 3b542c5294..fd1831c5e6 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -50,6 +50,7 @@ type TestFlags struct { flagDebugDirectory string flagUseAKS bool + flagUseEKS bool flagUseGKE bool flagUseKind bool @@ -121,6 +122,8 @@ func (t *TestFlags) init() { flag.BoolVar(&t.flagUseAKS, "use-aks", false, "If true, the tests will assume they are running against an AKS cluster(s).") + flag.BoolVar(&t.flagUseEKS, "use-eks", false, + "If true, the tests will assume they are running against an EKS cluster(s).") flag.BoolVar(&t.flagUseGKE, "use-gke", false, "If true, the tests will assume they are running against a GKE cluster(s).") flag.BoolVar(&t.flagUseKind, "use-kind", false, @@ -191,6 +194,7 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { NoCleanupOnFailure: t.flagNoCleanupOnFailure, DebugDirectory: tempDir, UseAKS: t.flagUseAKS, + UseEKS: t.flagUseEKS, UseGKE: t.flagUseGKE, UseKind: t.flagUseKind, } diff --git a/acceptance/tests/fixtures/bases/ingress/ingress.yaml b/acceptance/tests/fixtures/bases/ingress/ingress.yaml new file mode 100644 index 0000000000..7632a187d6 --- /dev/null +++ b/acceptance/tests/fixtures/bases/ingress/ingress.yaml @@ -0,0 +1,23 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: test-ingress + annotations: + kubernetes.io/ingress.class: "alb" + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip +spec: + rules: + - http: + paths: + - path: / + pathType: ImplementationSpecific + backend: + service: + name: static-server + port: + number: 80 + host: test.acceptance.com \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/ingress/kustomization.yaml b/acceptance/tests/fixtures/bases/ingress/kustomization.yaml new file mode 100644 index 0000000000..09fd1b7d0b --- /dev/null +++ b/acceptance/tests/fixtures/bases/ingress/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ingress.yaml diff --git a/acceptance/tests/sync/sync_catalog_test.go b/acceptance/tests/sync/sync_catalog_test.go index 7407126580..2ca8b1ee1f 100644 --- a/acceptance/tests/sync/sync_catalog_test.go +++ b/acceptance/tests/sync/sync_catalog_test.go @@ -79,3 +79,84 @@ func TestSyncCatalog(t *testing.T) { }) } } + +// Test that sync catalog works in both the default installation and +// the secure installation when TLS and ACLs are enabled with an Ingress resource. +// The test will create a test service and a pod and will +// wait for the service to be synced *to* consul. +func TestSyncCatalogWithIngress(t *testing.T) { + cfg := suite.Config() + if cfg.EnableCNI { + t.Skipf("skipping because -enable-cni is set and sync catalog is already tested with regular tproxy") + } + if !cfg.UseEKS { + t.Skipf("skipping because -use-eks is not set and the ingress test only runs on EKS") + } + + cases := map[string]struct { + secure bool + }{ + "non-secure": {secure: false}, + "secure": {secure: true}, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + ctx := suite.Environment().DefaultContext(t) + helmValues := map[string]string{ + "syncCatalog.enabled": "true", + "syncCatalog.ingres.enabled": "true", + "global.tls.enabled": strconv.FormatBool(c.secure), + "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), + } + + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, suite.Config(), releaseName) + + logger.Log(t, "creating ingress resource") + retry.Run(t, func(r *retry.R) { + // Retry the kubectl apply because we've seen sporadic + // "connection refused" errors where the mutating webhook + // endpoint fails initially. + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/ingress") + require.NoError(r, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/ingress") + }) + }) + + consulCluster.Create(t) + + logger.Log(t, "creating a static-server with a service") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().DebugDirectory, "../fixtures/bases/static-server") + + consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) + + logger.Log(t, "checking that the service has been synced to Consul") + var services map[string][]string + syncedServiceName := fmt.Sprintf("static-server-%s", ctx.KubectlOptions(t).Namespace) + counter := &retry.Counter{Count: 10, Wait: 5 * time.Second} + retry.RunWith(counter, t, func(r *retry.R) { + var err error + services, _, err = consulClient.Catalog().Services(nil) + require.NoError(r, err) + if _, ok := services[syncedServiceName]; !ok { + r.Errorf("service '%s' is not in Consul's list of services %s", syncedServiceName, services) + } + }) + + service, _, err := consulClient.Catalog().Service(syncedServiceName, "", nil) + require.NoError(t, err) + require.Len(t, service, 1) + require.Equal(t, "test.acceptance.com", service[0].Address) + require.Equal(t, []string{"k8s"}, service[0].ServiceTags) + filter := fmt.Sprintf("ServiceID == %q", service[0].ServiceID) + healthChecks, _, err := consulClient.Health().Checks(syncedServiceName, &api.QueryOptions{Filter: filter}) + require.NoError(t, err) + require.Len(t, healthChecks, 1) + require.Equal(t, api.HealthPassing, healthChecks[0].Status) + }) + } +} From c2a149b36242dcfec21fba0dd81655abc91a4a34 Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:43:08 -0400 Subject: [PATCH 252/592] [COMPLIANCE] Add Copyright and License Headers (#2456) Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- acceptance/ci-inputs/aks_acceptance_test_packages.yaml | 3 +++ acceptance/ci-inputs/eks_acceptance_test_packages.yaml | 3 +++ acceptance/ci-inputs/gke_acceptance_test_packages.yaml | 3 +++ acceptance/ci-inputs/kind_acceptance_test_packages.yaml | 3 +++ acceptance/tests/connect/permissive_mtls_test.go | 3 +++ .../cases/permissive-mtls/mesh-config-permissive-allowed.yaml | 3 +++ .../service-defaults-static-server-permissive.yaml | 3 +++ .../permissive-mtls/service-defaults-static-server-strict.yaml | 3 +++ 8 files changed, 24 insertions(+) diff --git a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml index cef04a3205..c1f1093200 100644 --- a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} - {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml index cef04a3205..c1f1093200 100644 --- a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} - {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml index cef04a3205..c1f1093200 100644 --- a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} - {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml index 74991abd76..8677b83c4e 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + - {runner: 0, test-packages: "partitions"} - {runner: 1, test-packages: "peering"} - {runner: 2, test-packages: "connect snapshot-agent wan-federation"} diff --git a/acceptance/tests/connect/permissive_mtls_test.go b/acceptance/tests/connect/permissive_mtls_test.go index f0a55779ee..1dcc6fe911 100644 --- a/acceptance/tests/connect/permissive_mtls_test.go +++ b/acceptance/tests/connect/permissive_mtls_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package connect import ( diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml index 944792588a..c336a621e7 100644 --- a/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml +++ b/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: Mesh metadata: diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml index 6fd335b361..4559570544 100644 --- a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml +++ b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml index e47ae7aa5d..cf84c73407 100644 --- a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml +++ b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: From c83ce0c51d1a1a65eadb30d1cfb516716681dd76 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 26 Jun 2023 16:31:35 -0400 Subject: [PATCH 253/592] Fix GatewayClassConfig Test Timing Issue (#2409) * Add retryCheckWithWait func * Fix retry timing on GatewayClassConfig test * remove redundant scale, make scale up number max + 1 * NET-4627, fix acceptance tests flake --------- Co-authored-by: Sarah Alsmiller --- .../api_gateway_gatewayclassconfig_test.go | 103 +++++++++--------- .../api-gateway/api_gateway_tenancy_test.go | 6 +- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go index add65b89af..444af6af4d 100644 --- a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go +++ b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go @@ -5,7 +5,9 @@ package apigateway import ( "context" + "fmt" "testing" + "time" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" @@ -29,6 +31,15 @@ import ( // minInstances,maxInstances and defaultInstances parameters, and that changing the parent gateway does not affect // the child gateways. func TestAPIGateway_GatewayClassConfig(t *testing.T) { + var ( + defaultInstances = pointer.Int32(2) + maxInstances = pointer.Int32(3) + minInstances = pointer.Int32(1) + + namespace = "default" + gatewayClassName = "gateway-class" + ) + ctx := suite.Environment().DefaultContext(t) cfg := suite.Config() helmValues := map[string]string{ @@ -38,6 +49,7 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { releaseName := helpers.RandomName() consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) consulCluster.Create(t) + // Override the default proxy config settings for this test. consulClient, _ := consulCluster.SetupConsulClient(t, false) _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ @@ -50,28 +62,8 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { require.NoError(t, err) k8sClient := ctx.ControllerRuntimeClient(t) - namespace := "gateway-namespace" - //create clean namespace - err = k8sClient.Create(context.Background(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { - logger.Log(t, "deleting gateway namesapce") - k8sClient.Delete(context.Background(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }) - }) - - defaultInstances := pointer.Int32(2) - maxInstances := pointer.Int32(8) - minInstances := pointer.Int32(1) - // create a GatewayClassConfig with configuration set + // Create a GatewayClassConfig. gatewayClassConfigName := "gateway-class-config" gatewayClassConfig := &v1alpha1.GatewayClassConfig{ ObjectMeta: metav1.ObjectMeta{ @@ -99,8 +91,7 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { Name: gatewayClassConfigName, } - // create gateway class referencing gateway-class-config - gatewayClassName := "gateway-class" + // Create gateway class referencing gateway-class-config. logger.Log(t, "creating controlled gateway class") createGatewayClass(t, k8sClient, gatewayClassName, gatewayClassControllerName, gatewayParametersRef) helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { @@ -108,7 +99,7 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}) }) - // Create a certificate to reference in listeners + // Create a certificate to reference in listeners. certificateInfo := generateCertificate(t, nil, "certificate.consul.local") certificateName := "certificate" certificate := &corev1.Secret{ @@ -132,24 +123,24 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { k8sClient.Delete(context.Background(), certificate) }) - // Create gateway referencing gateway class config - gatewayName := "gateway" + // Create gateway referencing gateway class. + gatewayName := "gcctestgateway" + namespace logger.Log(t, "creating controlled gateway") gateway := createGateway(t, k8sClient, gatewayName, namespace, gatewayClassName, certificateName) - // make sure it exists - logger.Log(t, "checking that gateway one is synchronized to Consul") - checkConsulExists(t, consulClient, api.APIGateway, gatewayName) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { logger.Log(t, "deleting all gateways") k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(namespace)) }) + // Ensure it exists. + logger.Log(t, "checking that gateway is synchronized to Consul") + checkConsulExists(t, consulClient, api.APIGateway, gatewayName) + // Scenario: Gateway deployment should match the default instances defined on the gateway class config logger.Log(t, "checking that gateway instances match defined gateway class config") checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, defaultInstances, gateway) - //Scenario: Updating the GatewayClassConfig should not affect gateways that have already been created + // Scenario: Updating the GatewayClassConfig should not affect gateways that have already been created logger.Log(t, "updating gatewayclassconfig values") err = k8sClient.Get(context.Background(), types.NamespacedName{Name: gatewayClassConfigName, Namespace: namespace}, gatewayClassConfig) require.NoError(t, err) @@ -159,10 +150,8 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { require.NoError(t, err) checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, defaultInstances, gateway) - //Scenario: gateways should be able to scale independently and not get overridden by the controller unless it's above the max - scale(t, k8sClient, gateway.Name, gateway.Namespace, maxInstances) - checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, maxInstances, gateway) - scale(t, k8sClient, gateway.Name, gateway.Namespace, pointer.Int32(10)) + // Scenario: gateways should be able to scale independently and not get overridden by the controller unless it's above the max + scale(t, k8sClient, gateway.Name, gateway.Namespace, pointer.Int32(*maxInstances+1)) checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, maxInstances, gateway) scale(t, k8sClient, gateway.Name, gateway.Namespace, pointer.Int32(0)) checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, minInstances, gateway) @@ -172,36 +161,52 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { func scale(t *testing.T, client client.Client, name, namespace string, scaleTo *int32) { t.Helper() - retryCheck(t, 30, func(r *retry.R) { - var deployment appsv1.Deployment - err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &deployment) - require.NoError(r, err) + var deployment appsv1.Deployment + err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &deployment) + require.NoError(t, err) + + logger.Log(t, fmt.Sprintf("scaling gateway from %d to %d", *deployment.Spec.Replicas, *scaleTo)) + + deployment.Spec.Replicas = scaleTo + err = client.Update(context.Background(), &deployment) + require.NoError(t, err) - deployment.Spec.Replicas = scaleTo - err = client.Update(context.Background(), &deployment) - require.NoError(r, err) - }) } func checkNumberOfInstances(t *testing.T, k8client client.Client, consulClient *api.Client, name, namespace string, wantNumber *int32, gateway *gwv1beta1.Gateway) { t.Helper() - retryCheck(t, 30, func(r *retry.R) { - //first check to make sure the number of replicas has been set properly + retryCheckWithWait(t, 30, 10*time.Second, func(r *retry.R) { + logger.Log(t, "checking that gateway instances match defined gateway class config") + logger.Log(t, fmt.Sprintf("want: %d", *wantNumber)) + + // Ensure the number of replicas has been set properly. var deployment appsv1.Deployment err := k8client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &deployment) require.NoError(r, err) - require.EqualValues(r, *wantNumber, *deployment.Spec.Replicas) + logger.Log(t, fmt.Sprintf("deployment replicas: %d", *deployment.Spec.Replicas)) + require.EqualValues(r, *wantNumber, *deployment.Spec.Replicas, "deployment replicas should match the number of instances defined on the gateway class config") - //then check to make sure the number of gateway pods matches the replicas generated + // Ensure the number of gateway pods matches the replicas generated. podList := corev1.PodList{} labels := common.LabelsForGateway(gateway) err = k8client.List(context.Background(), &podList, client.InNamespace(namespace), client.MatchingLabels(labels)) require.NoError(r, err) - require.EqualValues(r, *wantNumber, len(podList.Items)) + logger.Log(t, fmt.Sprintf("number of pods: %d", len(podList.Items))) + require.EqualValues(r, *wantNumber, len(podList.Items), "number of pods should match the number of instances defined on the gateway class config") + // Ensure the number of services matches the replicas generated. services, _, err := consulClient.Catalog().Service(name, "", nil) + seenServices := map[string]interface{}{} require.NoError(r, err) - require.EqualValues(r, *wantNumber, len(services)) + logger.Log(t, fmt.Sprintf("number of services: %d", len(services))) + //we need to double check that we aren't double counting services with the same ID + for _, s := range services { + seenServices[s.ServiceID] = true + logger.Log(t, fmt.Sprintf("service info: id: %s, name: %s, namespace: %s", s.ServiceID, s.ServiceName, s.Namespace)) + } + + logger.Log(t, fmt.Sprintf("number of services: %d", len(services))) + require.EqualValues(r, int(*wantNumber), len(seenServices), "number of services should match the number of instances defined on the gateway class config") }) } diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go index e7748b9226..716f09bdba 100644 --- a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go +++ b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go @@ -347,9 +347,13 @@ func generateCertificate(t *testing.T, ca *certificateInfo, commonName string) * } func retryCheck(t *testing.T, count int, fn func(r *retry.R)) { + retryCheckWithWait(t, count, 2*time.Second, fn) +} + +func retryCheckWithWait(t *testing.T, count int, wait time.Duration, fn func(r *retry.R)) { t.Helper() - counter := &retry.Counter{Count: count, Wait: 2 * time.Second} + counter := &retry.Counter{Count: count, Wait: wait} retry.RunWith(counter, t, fn) } From 95af4c7b135fae46f7818a49ae7e927649d0b094 Mon Sep 17 00:00:00 2001 From: aahel Date: Tue, 27 Jun 2023 07:58:18 +0530 Subject: [PATCH 254/592] always update acl policy if it exists (#2392) * always update acl policy if it exists * added changelog * added unit test * fix typo * added some additional assertions to test * refactored create_or_update unit test --- .changelog/2392.txt | 3 + .../server-acl-init/create_or_update.go | 57 +++++++--------- .../server-acl-init/create_or_update_test.go | 68 +++++++++++++++++++ 3 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 .changelog/2392.txt diff --git a/.changelog/2392.txt b/.changelog/2392.txt new file mode 100644 index 0000000000..e15ef152b1 --- /dev/null +++ b/.changelog/2392.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: Always update ACL policies upon upgrade +``` \ No newline at end of file diff --git a/control-plane/subcommand/server-acl-init/create_or_update.go b/control-plane/subcommand/server-acl-init/create_or_update.go index d14fbc845c..50f215eacb 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update.go +++ b/control-plane/subcommand/server-acl-init/create_or_update.go @@ -315,42 +315,37 @@ func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, consulClient *ap // Allowing the Consul node name to be configurable also requires any sync // policy to be updated in case the node name has changed. if isPolicyExistsErr(err, policy.Name) { - if c.flagEnableNamespaces || c.flagSyncCatalog { - c.log.Info(fmt.Sprintf("Policy %q already exists, updating", policy.Name)) + c.log.Info(fmt.Sprintf("Policy %q already exists, updating", policy.Name)) - // The policy ID is required in any PolicyUpdate call, so first we need to - // get the existing policy to extract its ID. - existingPolicies, _, err := consulClient.ACL().PolicyList(&api.QueryOptions{}) - if err != nil { - return err - } - - // Find the policy that matches our name and description - // and that's the ID we need - for _, existingPolicy := range existingPolicies { - if existingPolicy.Name == policy.Name && existingPolicy.Description == policy.Description { - policy.ID = existingPolicy.ID - } - } + // The policy ID is required in any PolicyUpdate call, so first we need to + // get the existing policy to extract its ID. + existingPolicies, _, err := consulClient.ACL().PolicyList(&api.QueryOptions{}) + if err != nil { + return err + } - // This shouldn't happen, because we're looking for a policy - // only after we've hit a `Policy already exists` error. - // The only time it might happen is if a user has manually created a policy - // with this name but used a different description. In this case, - // we don't want to overwrite the policy so we just error. - if policy.ID == "" { - return fmt.Errorf("policy found with name %q but not with expected description %q; "+ - "if this policy was created manually it must be renamed to something else because this name is reserved by consul-k8s", - policy.Name, policy.Description) + // Find the policy that matches our name and description + // and that's the ID we need + for _, existingPolicy := range existingPolicies { + if existingPolicy.Name == policy.Name && existingPolicy.Description == policy.Description { + policy.ID = existingPolicy.ID } + } - // Update the policy now that we've found its ID - _, _, err = consulClient.ACL().PolicyUpdate(&policy, &api.WriteOptions{}) - return err - } else { - c.log.Info(fmt.Sprintf("Policy %q already exists, skipping update", policy.Name)) - return nil + // This shouldn't happen, because we're looking for a policy + // only after we've hit a `Policy already exists` error. + // The only time it might happen is if a user has manually created a policy + // with this name but used a different description. In this case, + // we don't want to overwrite the policy so we just error. + if policy.ID == "" { + return fmt.Errorf("policy found with name %q but not with expected description %q; "+ + "if this policy was created manually it must be renamed to something else because this name is reserved by consul-k8s", + policy.Name, policy.Description) } + + // Update the policy now that we've found its ID + _, _, err = consulClient.ACL().PolicyUpdate(&policy, &api.WriteOptions{}) + return err } return err } diff --git a/control-plane/subcommand/server-acl-init/create_or_update_test.go b/control-plane/subcommand/server-acl-init/create_or_update_test.go index 6aff677dda..84ccdc1635 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update_test.go +++ b/control-plane/subcommand/server-acl-init/create_or_update_test.go @@ -70,3 +70,71 @@ func TestCreateOrUpdateACLPolicy_ErrorsIfDescriptionDoesNotMatch(t *testing.T) { require.NoError(err) require.Equal(policyDescription, rereadPolicy.Description) } + +func TestCreateOrUpdateACLPolicy(t *testing.T) { + require := require.New(t) + ui := cli.NewMockUi() + k8s := fake.NewSimpleClientset() + cmd := Command{ + UI: ui, + clientset: k8s, + log: hclog.NewNullLogger(), + } + cmd.init() + // Start Consul. + bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + svr, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + c.ACL.Enabled = true + c.ACL.Tokens.InitialManagement = bootToken + }) + require.NoError(err) + defer svr.Stop() + svr.WaitForLeader(t) + + // Get a Consul client. + consul, err := api.NewClient(&api.Config{ + Address: svr.HTTPAddr, + Token: bootToken, + }) + require.NoError(err) + connectInjectRule, err := cmd.injectRules() + require.NoError(err) + aclReplRule, err := cmd.aclReplicationRules() + require.NoError(err) + policyDescription := "policy-description" + policyName := "policy-name" + cases := []struct { + Name string + PolicyDescription string + PolicyName string + Rules string + }{ + { + Name: "create", + PolicyDescription: policyDescription, + PolicyName: policyName, + Rules: connectInjectRule, + }, + { + Name: "update", + PolicyDescription: policyDescription, + PolicyName: policyName, + Rules: aclReplRule, + }, + } + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + err = cmd.createOrUpdateACLPolicy(api.ACLPolicy{ + Name: tt.PolicyName, + Description: tt.PolicyDescription, + Rules: tt.Rules, + }, consul) + require.Nil(err) + policy, _, err := consul.ACL().PolicyReadByName(tt.PolicyName, nil) + require.Nil(err) + require.Equal(tt.Rules, policy.Rules) + require.Equal(tt.PolicyName, policy.Name) + require.Equal(tt.PolicyDescription, policy.Description) + }) + } +} From e17684617a0f2b8894082513f78a135f3ab0b832 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 27 Jun 2023 13:55:10 -0400 Subject: [PATCH 255/592] Proxy Lifecycle helm, connect-inject and acceptance tests (#2233) Proxy Lifecycle helm, connect-inject and acceptance tests (#2233) Co-authored-by: Nitya Dhanushkodi --- .changelog/2233.txt | 3 + acceptance/framework/config/config.go | 15 +- .../framework/connhelper/connect_helper.go | 12 +- acceptance/framework/flags/flags.go | 39 +- .../connect/connect_proxy_lifecycle_test.go | 214 +++++++++++ .../templates/connect-inject-deployment.yaml | 13 + .../test/unit/connect-inject-deployment.bats | 155 +++++++- charts/consul/values.yaml | 26 +- cli/helm/values.go | 11 +- control-plane/connect-inject/common/common.go | 34 ++ .../connect-inject/common/common_test.go | 166 +++++++++ .../constants/annotations_and_labels.go | 7 + .../connect-inject/constants/constants.go | 6 + .../lifecycle/lifecycle_configuration.go | 95 +++++ .../lifecycle/lifecycle_configuration_test.go | 351 ++++++++++++++++++ .../metrics/metrics_configuration.go | 42 +-- .../metrics/metrics_configuration_test.go | 143 ------- .../webhook/consul_dataplane_sidecar.go | 39 ++ .../webhook/consul_dataplane_sidecar_test.go | 191 +++++++++- .../connect-inject/webhook/mesh_webhook.go | 8 + .../subcommand/inject-connect/command.go | 25 ++ 21 files changed, 1353 insertions(+), 242 deletions(-) create mode 100644 .changelog/2233.txt create mode 100644 acceptance/tests/connect/connect_proxy_lifecycle_test.go create mode 100644 control-plane/connect-inject/lifecycle/lifecycle_configuration.go create mode 100644 control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go diff --git a/.changelog/2233.txt b/.changelog/2233.txt new file mode 100644 index 0000000000..bb929501c9 --- /dev/null +++ b/.changelog/2233.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add support for configuring graceful shutdown proxy lifecycle management settings. +``` diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index 18673ca260..7151a75908 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -46,12 +46,14 @@ type TestConfig struct { DisablePeering bool - HelmChartVersion string - ConsulImage string - ConsulK8SImage string - ConsulVersion *version.Version - EnvoyImage string - ConsulCollectorImage string + HelmChartVersion string + ConsulImage string + ConsulK8SImage string + ConsulDataplaneImage string + ConsulVersion *version.Version + ConsulDataplaneVersion *version.Version + EnvoyImage string + ConsulCollectorImage string HCPResourceID string @@ -110,6 +112,7 @@ func (t *TestConfig) HelmValuesFromConfig() (map[string]string, error) { setIfNotEmpty(helmValues, "global.image", t.ConsulImage) setIfNotEmpty(helmValues, "global.imageK8S", t.ConsulK8SImage) setIfNotEmpty(helmValues, "global.imageEnvoy", t.EnvoyImage) + setIfNotEmpty(helmValues, "global.imageConsulDataplane", t.ConsulDataplaneImage) return helmValues, nil } diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 8a7f4d3d53..8695e74d56 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -50,8 +50,8 @@ type ConnectHelper struct { // consulCluster is the cluster to use for the test. consulCluster consul.Cluster - // consulClient is the client used to test service mesh connectivity. - consulClient *api.Client + // ConsulClient is the client used to test service mesh connectivity. + ConsulClient *api.Client } // Setup creates a new cluster using the New*Cluster function and assigns it @@ -69,14 +69,14 @@ func (c *ConnectHelper) Setup(t *testing.T) { func (c *ConnectHelper) Install(t *testing.T) { logger.Log(t, "Installing Consul cluster") c.consulCluster.Create(t) - c.consulClient, _ = c.consulCluster.SetupConsulClient(t, c.Secure) + c.ConsulClient, _ = c.consulCluster.SetupConsulClient(t, c.Secure) } // Upgrade uses the existing Consul cluster and upgrades it using Helm values // set by the Secure, AutoEncrypt, and HelmValues fields. func (c *ConnectHelper) Upgrade(t *testing.T) { require.NotNil(t, c.consulCluster, "consulCluster must be set before calling Upgrade (Call Install first).") - require.NotNil(t, c.consulClient, "consulClient must be set before calling Upgrade (Call Install first).") + require.NotNil(t, c.ConsulClient, "ConsulClient must be set before calling Upgrade (Call Install first).") logger.Log(t, "upgrading Consul cluster") c.consulCluster.Upgrade(t, c.helmValues()) @@ -96,7 +96,7 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { t.Cleanup(func() { retrier := &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond} retry.RunWith(retrier, t, func(r *retry.R) { - tokens, _, err := c.consulClient.ACL().TokenList(nil) + tokens, _, err := c.ConsulClient.ACL().TokenList(nil) require.NoError(r, err) for _, token := range tokens { require.NotContains(r, token.Description, StaticServerName) @@ -142,7 +142,7 @@ func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T) { // the static-client pod. func (c *ConnectHelper) CreateIntention(t *testing.T) { logger.Log(t, "creating intention") - _, _, err := c.consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + _, _, err := c.ConsulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ Kind: api.ServiceIntentions, Name: StaticServerName, Sources: []*api.SourceIntention{ diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index fd1831c5e6..5df09f853a 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -34,14 +34,16 @@ type TestFlags struct { flagEnableTransparentProxy bool - flagHelmChartVersion string - flagConsulImage string - flagConsulK8sImage string - flagConsulVersion string - flagEnvoyImage string - flagConsulCollectorImage string - flagVaultHelmChartVersion string - flagVaultServerVersion string + flagHelmChartVersion string + flagConsulImage string + flagConsulK8sImage string + flagConsulDataplaneImage string + flagConsulVersion string + flagConsulDataplaneVersion string + flagEnvoyImage string + flagConsulCollectorImage string + flagVaultHelmChartVersion string + flagVaultServerVersion string flagHCPResourceID string @@ -75,7 +77,9 @@ func (t *TestFlags) init() { flag.StringVar(&t.flagConsulImage, "consul-image", "", "The Consul image to use for all tests.") flag.StringVar(&t.flagConsulK8sImage, "consul-k8s-image", "", "The consul-k8s image to use for all tests.") + flag.StringVar(&t.flagConsulDataplaneImage, "consul-dataplane-image", "", "The consul-dataplane image to use for all tests.") flag.StringVar(&t.flagConsulVersion, "consul-version", "", "The consul version used for all tests.") + flag.StringVar(&t.flagConsulDataplaneVersion, "consul-dataplane-version", "", "The consul-dataplane version used for all tests.") flag.StringVar(&t.flagHelmChartVersion, "helm-chart-version", config.HelmChartPath, "The helm chart used for all tests.") flag.StringVar(&t.flagEnvoyImage, "envoy-image", "", "The Envoy image to use for all tests.") flag.StringVar(&t.flagConsulCollectorImage, "consul-collector-image", "", "The consul collector image to use for all tests.") @@ -155,6 +159,7 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { // if the Version is empty consulVersion will be nil consulVersion, _ := version.NewVersion(t.flagConsulVersion) + consulDataplaneVersion, _ := version.NewVersion(t.flagConsulDataplaneVersion) //vaultserverVersion, _ := version.NewVersion(t.flagVaultServerVersion) return &config.TestConfig{ @@ -180,14 +185,16 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { DisablePeering: t.flagDisablePeering, - HelmChartVersion: t.flagHelmChartVersion, - ConsulImage: t.flagConsulImage, - ConsulK8SImage: t.flagConsulK8sImage, - ConsulVersion: consulVersion, - EnvoyImage: t.flagEnvoyImage, - ConsulCollectorImage: t.flagConsulCollectorImage, - VaultHelmChartVersion: t.flagVaultHelmChartVersion, - VaultServerVersion: t.flagVaultServerVersion, + HelmChartVersion: t.flagHelmChartVersion, + ConsulImage: t.flagConsulImage, + ConsulK8SImage: t.flagConsulK8sImage, + ConsulDataplaneImage: t.flagConsulDataplaneImage, + ConsulVersion: consulVersion, + ConsulDataplaneVersion: consulDataplaneVersion, + EnvoyImage: t.flagEnvoyImage, + ConsulCollectorImage: t.flagConsulCollectorImage, + VaultHelmChartVersion: t.flagVaultHelmChartVersion, + VaultServerVersion: t.flagVaultServerVersion, HCPResourceID: t.flagHCPResourceID, diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go new file mode 100644 index 0000000000..ecdc51b547 --- /dev/null +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -0,0 +1,214 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package connect + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/go-version" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type LifecycleShutdownConfig struct { + secure bool + helmValues map[string]string +} + +const ( + helmDrainListenersKey = "connectInject.sidecarProxy.lifecycle.defaultEnableShutdownDrainListeners" + helmGracePeriodSecondsKey = "connectInject.sidecarProxy.lifecycle.defaultShutdownGracePeriodSeconds" +) + +// Test the endpoints controller cleans up force-killed pods. +func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { + cfg := suite.Config() + + ver, err := version.NewVersion("1.2.0") + require.NoError(t, err) + if cfg.ConsulDataplaneVersion != nil && cfg.ConsulDataplaneVersion.LessThan(ver) { + t.Skipf("skipping this test because proxy lifecycle management is not supported in consul-dataplane version %v", cfg.ConsulDataplaneVersion.String()) + } + + for _, testCfg := range []LifecycleShutdownConfig{ + {secure: false, helmValues: map[string]string{}}, + {secure: true, helmValues: map[string]string{}}, + {secure: false, helmValues: map[string]string{ + helmDrainListenersKey: "true", + helmGracePeriodSecondsKey: "15", + }}, + {secure: true, helmValues: map[string]string{ + helmDrainListenersKey: "true", + helmGracePeriodSecondsKey: "15", + }}, + {secure: false, helmValues: map[string]string{ + helmDrainListenersKey: "false", + helmGracePeriodSecondsKey: "15", + }}, + {secure: true, helmValues: map[string]string{ + helmDrainListenersKey: "false", + helmGracePeriodSecondsKey: "15", + }}, + {secure: false, helmValues: map[string]string{ + helmDrainListenersKey: "false", + helmGracePeriodSecondsKey: "0", + }}, + {secure: true, helmValues: map[string]string{ + helmDrainListenersKey: "false", + helmGracePeriodSecondsKey: "0", + }}, + } { + // Determine if listeners should be expected to drain inbound connections + var drainListenersEnabled bool + val, ok := testCfg.helmValues[helmDrainListenersKey] + if ok { + drainListenersEnabled, err = strconv.ParseBool(val) + require.NoError(t, err) + } + + // Determine expected shutdown grace period + var gracePeriodSeconds int64 + val, ok = testCfg.helmValues[helmGracePeriodSecondsKey] + if ok { + gracePeriodSeconds, err = strconv.ParseInt(val, 10, 64) + require.NoError(t, err) + } else { + // Half of the helm default to speed tests up + gracePeriodSeconds = 15 + } + + name := fmt.Sprintf("secure: %t, drainListeners: %t, gracePeriodSeconds: %d", testCfg.secure, drainListenersEnabled, gracePeriodSeconds) + t.Run(name, func(t *testing.T) { + ctx := suite.Environment().DefaultContext(t) + releaseName := helpers.RandomName() + + connHelper := connhelper.ConnectHelper{ + ClusterKind: consul.Helm, + Secure: testCfg.secure, + ReleaseName: releaseName, + Ctx: ctx, + Cfg: cfg, + HelmValues: testCfg.helmValues, + } + + connHelper.Setup(t) + connHelper.Install(t) + connHelper.DeployClientAndServer(t) + + // TODO: should this move into connhelper.DeployClientAndServer? + logger.Log(t, "waiting for static-client and static-server to be registered with Consul") + retry.Run(t, func(r *retry.R) { + for _, name := range []string{ + "static-client", + "static-client-sidecar-proxy", + "static-server", + "static-server-sidecar-proxy", + } { + logger.Logf(t, "checking for %s service in Consul catalog", name) + instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) + r.Check(err) + + if len(instances) != 1 { + r.Errorf("expected 1 instance of %s", name) + } + } + }) + + if testCfg.secure { + connHelper.TestConnectionFailureWithoutIntention(t) + connHelper.CreateIntention(t) + } + + connHelper.TestConnectionSuccess(t) + + // Get static-client pod name + ns := ctx.KubectlOptions(t).Namespace + pods, err := ctx.KubernetesClient(t).CoreV1().Pods(ns).List( + context.Background(), + metav1.ListOptions{ + LabelSelector: "app=static-client", + }, + ) + require.NoError(t, err) + require.Len(t, pods.Items, 1) + clientPodName := pods.Items[0].Name + + var terminationGracePeriod int64 = 60 + logger.Logf(t, "killing the %q pod with %dseconds termination grace period", clientPodName, terminationGracePeriod) + err = ctx.KubernetesClient(t).CoreV1().Pods(ns).Delete(context.Background(), clientPodName, metav1.DeleteOptions{GracePeriodSeconds: &terminationGracePeriod}) + require.NoError(t, err) + + // Exec into terminating pod, not just any static-client pod + args := []string{"exec", clientPodName, "-c", connhelper.StaticClientName, "--", "curl", "-vvvsSf"} + + if cfg.EnableTransparentProxy { + args = append(args, "http://static-server") + } else { + args = append(args, "http://localhost:1234") + } + + if gracePeriodSeconds > 0 { + // Ensure outbound requests are still successful during grace + // period. + retry.RunWith(&retry.Timer{Timeout: time.Duration(gracePeriodSeconds) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { + output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) + require.NoError(r, err) + require.Condition(r, func() bool { + exists := false + if strings.Contains(output, "curl: (7) Failed to connect") { + exists = true + } + return !exists + }) + }) + + // If listener draining is enabled, ensure inbound + // requests are rejected during grace period. + // connHelper.TestConnectionSuccess(t) + } else { + // Ensure outbound requests fail because proxy has terminated + retry.RunWith(&retry.Timer{Timeout: time.Duration(terminationGracePeriod) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { + output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) + require.Error(r, err) + require.Condition(r, func() bool { + exists := false + if strings.Contains(output, "curl: (7) Failed to connect") { + exists = true + } + return exists + }) + }) + } + + logger.Log(t, "ensuring pod is deregistered after termination") + retry.Run(t, func(r *retry.R) { + for _, name := range []string{ + "static-client", + "static-client-sidecar-proxy", + } { + logger.Logf(t, "checking for %s service in Consul catalog", name) + instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) + r.Check(err) + + for _, instance := range instances { + if strings.Contains(instance.ServiceID, clientPodName) { + r.Errorf("%s is still registered", instance.ServiceID) + } + } + } + }) + }) + } +} diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 479e05b25a..14c3961b4e 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -234,6 +234,19 @@ spec: -default-sidecar-proxy-cpu-request={{ $resources.requests.cpu }} \ {{- end }} -default-envoy-proxy-concurrency={{ .Values.connectInject.sidecarProxy.concurrency }} \ + {{- if .Values.connectInject.sidecarProxy.lifecycle.defaultEnabled }} + -default-enable-sidecar-proxy-lifecycle=true \ + {{- else }} + -default-enable-sidecar-proxy-lifecycle=false \ + {{- end }} + {{- if .Values.connectInject.sidecarProxy.lifecycle.defaultEnableShutdownDrainListeners }} + -default-enable-sidecar-proxy-lifecycle-shutdown-drain-listeners=true \ + {{- else }} + -default-enable-sidecar-proxy-lifecycle-shutdown-drain-listeners=false \ + {{- end }} + -default-sidecar-proxy-lifecycle-shutdown-grace-period-seconds={{ .Values.connectInject.sidecarProxy.lifecycle.defaultShutdownGracePeriodSeconds }} \ + -default-sidecar-proxy-lifecycle-graceful-port={{ .Values.connectInject.sidecarProxy.lifecycle.defaultGracefulPort }} \ + -default-sidecar-proxy-lifecycle-graceful-shutdown-path="{{ .Values.connectInject.sidecarProxy.lifecycle.defaultGracefulShutdownPath }}" \ {{- if .Values.connectInject.initContainer }} {{- $initResources := .Values.connectInject.initContainer.resources }} diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index c1bc63ffc3..ccc6eca68c 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -999,7 +999,7 @@ load _helpers local actual=$(echo "$cmd" | yq 'any(contains("-init-container-memory-limit=150Mi"))' | tee /dev/stderr) [ "${actual}" = "true" ] - + } @test "connectInject/Deployment: can set init container resources" { @@ -1231,6 +1231,144 @@ load _helpers [ "${actual}" = "true" ] } +#-------------------------------------------------------------------- +# sidecarProxy.lifecycle + +@test "connectInject/Deployment: by default sidecar proxy lifecycle management is enabled" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-enable-sidecar-proxy-lifecycle"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: sidecar proxy lifecycle management can be disabled" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.sidecarProxy.lifecycle.defaultEnabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-enable-sidecar-proxy-lifecycle=false"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: by default sidecar proxy lifecycle management shutdown listener draining is enabled" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-enable-sidecar-proxy-lifecycle-shutdown-drain-listeners"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: sidecar proxy lifecycle management shutdown listener draining can be disabled" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.sidecarProxy.lifecycle.defaultEnableShutdownDrainListeners=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-enable-sidecar-proxy-lifecycle-shutdown-drain-listeners=false"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: by default sidecar proxy lifecycle management shutdown grace period is set to 30 seconds" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-sidecar-proxy-lifecycle-shutdown-grace-period-seconds=30"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: sidecar proxy lifecycle management shutdown grace period can be set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.sidecarProxy.lifecycle.defaultShutdownGracePeriodSeconds=23' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-sidecar-proxy-lifecycle-shutdown-grace-period-seconds=23"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: by default sidecar proxy lifecycle management port is set to 20600" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-sidecar-proxy-lifecycle-graceful-port=20600"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: sidecar proxy lifecycle management port can be set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.sidecarProxy.lifecycle.defaultGracefulPort=20307' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-sidecar-proxy-lifecycle-graceful-port=20307"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: by default sidecar proxy lifecycle management graceful shutdown path is set to /graceful_shutdown" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-sidecar-proxy-lifecycle-graceful-shutdown-path=\"/graceful_shutdown\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: sidecar proxy lifecycle management graceful shutdown path can be set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.sidecarProxy.lifecycle.defaultGracefulShutdownPath=/exit' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-default-sidecar-proxy-lifecycle-graceful-shutdown-path=\"/exit\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # priorityClassName @@ -1418,7 +1556,7 @@ load _helpers } #-------------------------------------------------------------------- -# cni +# cni @test "connectInject/Deployment: cni is disabled by default" { cd `chart_dir` @@ -2300,7 +2438,7 @@ reservedNameTest() { --set 'global.cloud.authUrl.secretName=auth-url-name' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] } @@ -2321,7 +2459,7 @@ reservedNameTest() { --set 'global.cloud.authUrl.secretKey=auth-url-key' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] } @@ -2342,7 +2480,7 @@ reservedNameTest() { --set 'global.cloud.apiHost.secretName=auth-url-name' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] } @@ -2363,7 +2501,7 @@ reservedNameTest() { --set 'global.cloud.apiHost.secretKey=auth-url-key' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] } @@ -2384,7 +2522,7 @@ reservedNameTest() { --set 'global.cloud.scadaAddress.secretName=scada-address-name' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] } @@ -2405,7 +2543,7 @@ reservedNameTest() { --set 'global.cloud.scadaAddress.secretKey=scada-address-key' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] } @@ -2449,4 +2587,3 @@ reservedNameTest() { jq -r '. | select( .name == "CONSUL_TLS_SERVER_NAME").value' | tee /dev/stderr) [ "${actual}" = "server.dc1.consul" ] } - diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 89336e319e..ad1f829399 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -584,7 +584,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.2-dev" + imageConsulDataplane: "docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.3-dev" # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. @@ -2108,7 +2108,7 @@ connectInject: # @type: string nodeSelector: null - # Toleration settings for gateway pods created with the managed gateway class. + # Toleration settings for gateway pods created with the managed gateway class. # This should be a multi-line string matching the # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. # @@ -2134,7 +2134,7 @@ connectInject: service: null # This value defines the number of pods to deploy for each Gateway as well as a min and max number of pods for all Gateways - deployment: + deployment: defaultInstances: 1 maxInstances: 1 minInstances: 1 @@ -2554,6 +2554,26 @@ connectInject: # Recommended production default: 100m # @type: string cpu: null + # Set default lifecycle management configuration for sidecar proxy. + # These settings can be overridden on a per-pod basis via these annotations: + # + # - `consul.hashicorp.com/enable-sidecar-proxy-lifecycle` + # - `consul.hashicorp.com/enable-sidecar-proxy-shutdown-drain-listeners` + # - `consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds` + # - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-port` + # - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-shutdown-path` + # @type: map + lifecycle: + # @type: boolean + defaultEnabled: true + # @type: boolean + defaultEnableShutdownDrainListeners: true + # @type: integer + defaultShutdownGracePeriodSeconds: 30 + # @type: integer + defaultGracefulPort: 20600 + # @type: string + defaultGracefulShutdownPath: "/graceful_shutdown" # The resource settings for the Connect injected init container. If null, the resources # won't be set for the initContainer. The defaults are optimized for developer instances of diff --git a/cli/helm/values.go b/cli/helm/values.go index e6951074b1..06671382d1 100644 --- a/cli/helm/values.go +++ b/cli/helm/values.go @@ -411,7 +411,7 @@ type TransparentProxy struct { } type Metrics struct { - DefaultEnabled string `yaml:"defaultEnabled"` + DefaultEnabled bool `yaml:"defaultEnabled"` DefaultEnableMerging bool `yaml:"defaultEnableMerging"` DefaultMergedMetricsPort int `yaml:"defaultMergedMetricsPort"` DefaultPrometheusScrapePort int `yaml:"defaultPrometheusScrapePort"` @@ -425,12 +425,21 @@ type ACLInjectToken struct { type SidecarProxy struct { Resources Resources `yaml:"resources"` + Lifecycle Lifecycle `yaml:"lifecycle"` } type InitContainer struct { Resources Resources `yaml:"resources"` } +type Lifecycle struct { + DefaultEnabled bool `yaml:"defaultEnabled"` + DefaultEnableShutdownDrainListeners bool `yaml:"defaultEnableShutdownDrainListeners"` + DefaultShutdownGracePeriodSeconds int `yaml:"defaultShutdownGracePeriodSeconds"` + DefaultGracefulPort int `yaml:"defaultGracefulPort"` + DefaultGracefulShutdownPath string `yaml:"defaultGracefulShutdownPath"` +} + type ConnectInject struct { Enabled bool `yaml:"enabled"` Replicas int `yaml:"replicas"` diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 67182e6d0a..a99d9fd12e 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -12,6 +12,40 @@ import ( corev1 "k8s.io/api/core/v1" ) +// DetermineAndValidatePort behaves as follows: +// If the annotation exists, validate the port and return it. +// If the annotation does not exist, return the default port. +// If the privileged flag is true, it will allow the port to be in the +// privileged port range of 1-1023. Otherwise, it will only allow ports in the +// unprivileged range of 1024-65535. +func DetermineAndValidatePort(pod corev1.Pod, annotation string, defaultPort string, privileged bool) (string, error) { + if raw, ok := pod.Annotations[annotation]; ok && raw != "" { + port, err := PortValue(pod, raw) + if err != nil { + return "", fmt.Errorf("%s annotation value of %s is not a valid integer", annotation, raw) + } + + if privileged && (port < 1 || port > 65535) { + return "", fmt.Errorf("%s annotation value of %d is not in the valid port range 1-65535", annotation, port) + } else if !privileged && (port < 1024 || port > 65535) { + return "", fmt.Errorf("%s annotation value of %d is not in the unprivileged port range 1024-65535", annotation, port) + } + + // If the annotation exists, return the validated port. + return fmt.Sprint(port), nil + } + + // If the annotation does not exist, return the default. + if defaultPort != "" { + port, err := PortValue(pod, defaultPort) + if err != nil { + return "", fmt.Errorf("%s is not a valid port on the pod %s", defaultPort, pod.Name) + } + return fmt.Sprint(port), nil + } + return "", nil +} + // PortValue returns the port of the container for the string value passed // in as an argument on the provided pod. func PortValue(pod corev1.Pod, value string) (int32, error) { diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index 3f995e2874..79a9294fe2 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -6,10 +6,153 @@ package common import ( "testing" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func TestCommonDetermineAndValidatePort(t *testing.T) { + cases := []struct { + Name string + Pod func(*corev1.Pod) *corev1.Pod + Annotation string + Privileged bool + DefaultPort string + Expected string + Err string + }{ + { + Name: "Valid annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "1234" + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: false, + Expected: "1234", + Err: "", + }, + { + Name: "Uses default when there's no annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: false, + DefaultPort: "4321", + Expected: "4321", + Err: "", + }, + { + Name: "Gets the value of the named default port when there's no annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Spec.Containers[0].Ports = []corev1.ContainerPort{ + { + Name: "web-port", + ContainerPort: 2222, + }, + } + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: false, + DefaultPort: "web-port", + Expected: "2222", + Err: "", + }, + { + Name: "Errors if the named default port doesn't exist on the pod", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: false, + DefaultPort: "web-port", + Expected: "", + Err: "web-port is not a valid port on the pod minimal", + }, + { + Name: "Gets the value of the named port", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "web-port" + pod.Spec.Containers[0].Ports = []corev1.ContainerPort{ + { + Name: "web-port", + ContainerPort: 2222, + }, + } + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: false, + DefaultPort: "4321", + Expected: "2222", + Err: "", + }, + { + Name: "Invalid annotation (not an integer)", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "not-an-int" + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: false, + Expected: "", + Err: "consul.hashicorp.com/test-annotation-port annotation value of not-an-int is not a valid integer", + }, + { + Name: "Invalid annotation (integer not in port range)", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "100000" + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: true, + Expected: "", + Err: "consul.hashicorp.com/test-annotation-port annotation value of 100000 is not in the valid port range 1-65535", + }, + { + Name: "Invalid annotation (integer not in unprivileged port range)", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "22" + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: false, + Expected: "", + Err: "consul.hashicorp.com/test-annotation-port annotation value of 22 is not in the unprivileged port range 1024-65535", + }, + { + Name: "Privileged ports allowed", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "22" + return pod + }, + Annotation: "consul.hashicorp.com/test-annotation-port", + Privileged: true, + Expected: "22", + Err: "", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + + actual, err := DetermineAndValidatePort(*tt.Pod(minimal()), tt.Annotation, tt.DefaultPort, tt.Privileged) + + if tt.Err == "" { + require.NoError(err) + require.Equal(tt.Expected, actual) + } else { + require.EqualError(err, tt.Err) + } + }) + } +} + func TestPortValue(t *testing.T) { cases := []struct { Name string @@ -93,3 +236,26 @@ func TestPortValue(t *testing.T) { }) } } + +func minimal() *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespaces.DefaultNamespace, + Name: "minimal", + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web-side", + }, + }, + }, + } +} diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index fa5c7da26c..4efcc24c74 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -100,6 +100,13 @@ const ( AnnotationSidecarProxyMemoryLimit = "consul.hashicorp.com/sidecar-proxy-memory-limit" AnnotationSidecarProxyMemoryRequest = "consul.hashicorp.com/sidecar-proxy-memory-request" + // annotations for sidecar proxy lifecycle configuration. + AnnotationEnableSidecarProxyLifecycle = "consul.hashicorp.com/enable-sidecar-proxy-lifecycle" + AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners = "consul.hashicorp.com/enable-sidecar-proxy-lifecycle-shutdown-drain-listeners" + AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds = "consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds" + AnnotationSidecarProxyLifecycleGracefulPort = "consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-port" + AnnotationSidecarProxyLifecycleGracefulShutdownPath = "consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-shutdown-path" + // annotations for sidecar volumes. AnnotationConsulSidecarUserVolume = "consul.hashicorp.com/consul-sidecar-user-volume" AnnotationConsulSidecarUserVolumeMount = "consul.hashicorp.com/consul-sidecar-user-volume-mount" diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 0a341cd577..ca6fe23606 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -27,4 +27,10 @@ const ( // MetaKeyPodName is the meta key name for Kubernetes pod name used for the Consul services. MetaKeyPodName = "pod-name" + + // DefaultGracefulPort is the default port that consul-dataplane uses for graceful shutdown. + DefaultGracefulPort = 20600 + + // DefaultGracefulShutdownPath is the default path that consul-dataplane uses for graceful shutdown. + DefaultGracefulShutdownPath = "/graceful_shutdown" ) diff --git a/control-plane/connect-inject/lifecycle/lifecycle_configuration.go b/control-plane/connect-inject/lifecycle/lifecycle_configuration.go new file mode 100644 index 0000000000..651d4eecae --- /dev/null +++ b/control-plane/connect-inject/lifecycle/lifecycle_configuration.go @@ -0,0 +1,95 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package lifecycle + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + corev1 "k8s.io/api/core/v1" +) + +// Config represents configuration common to connect-inject components related to proxy lifecycle management. +type Config struct { + DefaultEnableProxyLifecycle bool + DefaultEnableShutdownDrainListeners bool + DefaultShutdownGracePeriodSeconds int + DefaultGracefulPort string + DefaultGracefulShutdownPath string +} + +// EnableProxyLifecycle returns whether proxy lifecycle management is enabled either via the default value in the meshWebhook, or if it's been +// overridden via the annotation. +func (lc Config) EnableProxyLifecycle(pod corev1.Pod) (bool, error) { + enabled := lc.DefaultEnableProxyLifecycle + if raw, ok := pod.Annotations[constants.AnnotationEnableSidecarProxyLifecycle]; ok && raw != "" { + enableProxyLifecycle, err := strconv.ParseBool(raw) + if err != nil { + return false, fmt.Errorf("%s annotation value of %s was invalid: %s", constants.AnnotationEnableSidecarProxyLifecycle, raw, err) + } + enabled = enableProxyLifecycle + } + return enabled, nil +} + +// EnableShutdownDrainListeners returns whether proxy listener draining during shutdown is enabled either via the default value in the meshWebhook, or if it's been +// overridden via the annotation. +func (lc Config) EnableShutdownDrainListeners(pod corev1.Pod) (bool, error) { + enabled := lc.DefaultEnableShutdownDrainListeners + if raw, ok := pod.Annotations[constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners]; ok && raw != "" { + enableShutdownDrainListeners, err := strconv.ParseBool(raw) + if err != nil { + return false, fmt.Errorf("%s annotation value of %s was invalid: %s", constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners, raw, err) + } + enabled = enableShutdownDrainListeners + } + return enabled, nil +} + +// ShutdownGracePeriodSeconds returns how long the sidecar proxy should wait before shutdown, either via the default value in the meshWebhook, or if it's been +// overridden via the annotation. +func (lc Config) ShutdownGracePeriodSeconds(pod corev1.Pod) (int, error) { + shutdownGracePeriodSeconds := lc.DefaultShutdownGracePeriodSeconds + if shutdownGracePeriodSecondsAnnotation, ok := pod.Annotations[constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds]; ok { + val, err := strconv.ParseUint(shutdownGracePeriodSecondsAnnotation, 10, 64) + if err != nil { + return 0, fmt.Errorf("unable to parse annotation %q: %w", constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds, err) + } + shutdownGracePeriodSeconds = int(val) + } + return shutdownGracePeriodSeconds, nil +} + +// GracefulPort returns the port on which consul-dataplane should serve the proxy lifecycle management HTTP endpoints, either via the default value in the meshWebhook, or +// if it's been overridden via the annotation. It also validates the port is in the unprivileged port range. +func (lc Config) GracefulPort(pod corev1.Pod) (int, error) { + anno, err := common.DetermineAndValidatePort(pod, constants.AnnotationSidecarProxyLifecycleGracefulPort, lc.DefaultGracefulPort, false) + if err != nil { + return 0, err + } + + if anno == "" { + return constants.DefaultGracefulPort, nil + } + + port, _ := strconv.Atoi(anno) + + return port, nil +} + +// GracefulShutdownPath returns the path on which consul-dataplane should serve the graceful shutdown HTTP endpoint, either via the default value in the meshWebhook, or +// if it's been overridden via the annotation. +func (lc Config) GracefulShutdownPath(pod corev1.Pod) string { + if raw, ok := pod.Annotations[constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath]; ok && raw != "" { + return raw + } + + if lc.DefaultGracefulShutdownPath == "" { + return constants.DefaultGracefulShutdownPath + } + + return lc.DefaultGracefulShutdownPath +} diff --git a/control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go b/control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go new file mode 100644 index 0000000000..64157a3d55 --- /dev/null +++ b/control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go @@ -0,0 +1,351 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package lifecycle + +import ( + "testing" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestLifecycleConfig_EnableSidecarProxyLifecycle(t *testing.T) { + cases := []struct { + Name string + Pod func(*corev1.Pod) *corev1.Pod + LifecycleConfig Config + Expected bool + Err string + }{ + { + Name: "Sidecar proxy lifecycle management enabled via meshWebhook", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + LifecycleConfig: Config{ + DefaultEnableProxyLifecycle: true, + }, + Expected: true, + Err: "", + }, + { + Name: "Sidecar proxy lifecycle management enabled via annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationEnableSidecarProxyLifecycle] = "true" + return pod + }, + LifecycleConfig: Config{ + DefaultEnableProxyLifecycle: false, + }, + Expected: true, + Err: "", + }, + { + Name: "Sidecar proxy lifecycle management configured via invalid annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationEnableSidecarProxyLifecycle] = "not-a-bool" + return pod + }, + LifecycleConfig: Config{ + DefaultEnableProxyLifecycle: false, + }, + Expected: false, + Err: "consul.hashicorp.com/enable-sidecar-proxy-lifecycle annotation value of not-a-bool was invalid: strconv.ParseBool: parsing \"not-a-bool\": invalid syntax", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + lc := tt.LifecycleConfig + + actual, err := lc.EnableProxyLifecycle(*tt.Pod(minimal())) + + if tt.Err == "" { + require.Equal(tt.Expected, actual) + require.NoError(err) + } else { + require.EqualError(err, tt.Err) + } + }) + } +} + +func TestLifecycleConfig_ShutdownDrainListeners(t *testing.T) { + cases := []struct { + Name string + Pod func(*corev1.Pod) *corev1.Pod + LifecycleConfig Config + Expected bool + Err string + }{ + { + Name: "Sidecar proxy shutdown listener draining enabled via meshWebhook", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + LifecycleConfig: Config{ + DefaultEnableShutdownDrainListeners: true, + }, + Expected: true, + Err: "", + }, + { + Name: "Sidecar proxy shutdown listener draining enabled via annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners] = "true" + return pod + }, + LifecycleConfig: Config{ + DefaultEnableShutdownDrainListeners: false, + }, + Expected: true, + Err: "", + }, + { + Name: "Sidecar proxy shutdown listener draining configured via invalid annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners] = "not-a-bool" + return pod + }, + Expected: false, + Err: "consul.hashicorp.com/enable-sidecar-proxy-lifecycle-shutdown-drain-listeners annotation value of not-a-bool was invalid: strconv.ParseBool: parsing \"not-a-bool\": invalid syntax", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + lc := tt.LifecycleConfig + + actual, err := lc.EnableShutdownDrainListeners(*tt.Pod(minimal())) + + if tt.Err == "" { + require.Equal(tt.Expected, actual) + require.NoError(err) + } else { + require.EqualError(err, tt.Err) + } + }) + } +} + +func TestLifecycleConfig_ShutdownGracePeriodSeconds(t *testing.T) { + cases := []struct { + Name string + Pod func(*corev1.Pod) *corev1.Pod + LifecycleConfig Config + Expected int + Err string + }{ + { + Name: "Sidecar proxy shutdown grace period set via meshWebhook", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + LifecycleConfig: Config{ + DefaultShutdownGracePeriodSeconds: 10, + }, + Expected: 10, + Err: "", + }, + { + Name: "Sidecar proxy shutdown grace period set via annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds] = "20" + return pod + }, + LifecycleConfig: Config{ + DefaultShutdownGracePeriodSeconds: 10, + }, + Expected: 20, + Err: "", + }, + { + Name: "Sidecar proxy shutdown grace period configured via invalid annotation, negative number", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds] = "-1" + return pod + }, + Err: "unable to parse annotation \"consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds\": strconv.ParseUint: parsing \"-1\": invalid syntax", + }, + { + Name: "Sidecar proxy shutdown grace period configured via invalid annotation, not-parseable string", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds] = "not-int" + return pod + }, + Err: "unable to parse annotation \"consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds\": strconv.ParseUint: parsing \"not-int\": invalid syntax", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + lc := tt.LifecycleConfig + + actual, err := lc.ShutdownGracePeriodSeconds(*tt.Pod(minimal())) + + if tt.Err == "" { + require.Equal(tt.Expected, actual) + require.NoError(err) + } else { + require.EqualError(err, tt.Err) + } + }) + } +} + +func TestLifecycleConfig_GracefulPort(t *testing.T) { + cases := []struct { + Name string + Pod func(*corev1.Pod) *corev1.Pod + LifecycleConfig Config + Expected int + Err string + }{ + { + Name: "Sidecar proxy lifecycle graceful port set to default", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + Expected: constants.DefaultGracefulPort, + Err: "", + }, + { + Name: "Sidecar proxy lifecycle graceful port set via meshWebhook", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + LifecycleConfig: Config{ + DefaultGracefulPort: "3000", + }, + Expected: 3000, + Err: "", + }, + { + Name: "Sidecar proxy lifecycle graceful port set via annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationSidecarProxyLifecycleGracefulPort] = "9000" + return pod + }, + LifecycleConfig: Config{ + DefaultGracefulPort: "3000", + }, + Expected: 9000, + Err: "", + }, + { + Name: "Sidecar proxy lifecycle graceful port configured via invalid annotation, negative number", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationSidecarProxyLifecycleGracefulPort] = "-1" + return pod + }, + Err: "consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-port annotation value of -1 is not in the unprivileged port range 1024-65535", + }, + { + Name: "Sidecar proxy lifecycle graceful port configured via invalid annotation, not-parseable string", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationSidecarProxyLifecycleGracefulPort] = "not-int" + return pod + }, + Err: "consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-port annotation value of not-int is not a valid integer", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + lc := tt.LifecycleConfig + + actual, err := lc.GracefulPort(*tt.Pod(minimal())) + + if tt.Err == "" { + require.Equal(tt.Expected, actual) + require.NoError(err) + } else { + require.EqualError(err, tt.Err) + } + }) + } +} + +func TestLifecycleConfig_GracefulShutdownPath(t *testing.T) { + cases := []struct { + Name string + Pod func(*corev1.Pod) *corev1.Pod + LifecycleConfig Config + Expected string + Err string + }{ + { + Name: "Sidecar proxy lifecycle graceful shutdown path defaults to /graceful_shutdown", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + Expected: "/graceful_shutdown", + Err: "", + }, + { + Name: "Sidecar proxy lifecycle graceful shutdown path set via meshWebhook", + Pod: func(pod *corev1.Pod) *corev1.Pod { + return pod + }, + LifecycleConfig: Config{ + DefaultGracefulShutdownPath: "/quit", + }, + Expected: "/quit", + Err: "", + }, + { + Name: "Sidecar proxy lifecycle graceful port set via annotation", + Pod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath] = "/custom-shutdown-path" + return pod + }, + LifecycleConfig: Config{ + DefaultGracefulShutdownPath: "/quit", + }, + Expected: "/custom-shutdown-path", + Err: "", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + lc := tt.LifecycleConfig + + actual := lc.GracefulShutdownPath(*tt.Pod(minimal())) + + require.Equal(tt.Expected, actual) + }) + } +} + +func minimal() *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespaces.DefaultNamespace, + Name: "minimal", + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web-side", + }, + }, + }, + } +} diff --git a/control-plane/connect-inject/metrics/metrics_configuration.go b/control-plane/connect-inject/metrics/metrics_configuration.go index f5b819af3d..6f9c29c85b 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration.go +++ b/control-plane/connect-inject/metrics/metrics_configuration.go @@ -98,13 +98,13 @@ func (mc Config) EnableMetricsMerging(pod corev1.Pod) (bool, error) { // MergedMetricsPort returns the port to run the merged metrics server on, either via the default value in the meshWebhook, // or if it's been overridden via the annotation. It also validates the port is in the unprivileged port range. func (mc Config) MergedMetricsPort(pod corev1.Pod) (string, error) { - return determineAndValidatePort(pod, constants.AnnotationMergedMetricsPort, mc.DefaultMergedMetricsPort, false) + return common.DetermineAndValidatePort(pod, constants.AnnotationMergedMetricsPort, mc.DefaultMergedMetricsPort, false) } // PrometheusScrapePort returns the port for Prometheus to scrape from, either via the default value in the meshWebhook, or // if it's been overridden via the annotation. It also validates the port is in the unprivileged port range. func (mc Config) PrometheusScrapePort(pod corev1.Pod) (string, error) { - return determineAndValidatePort(pod, constants.AnnotationPrometheusScrapePort, mc.DefaultPrometheusScrapePort, false) + return common.DetermineAndValidatePort(pod, constants.AnnotationPrometheusScrapePort, mc.DefaultPrometheusScrapePort, false) } // PrometheusScrapePath returns the path for Prometheus to scrape from, either via the default value in the meshWebhook, or @@ -133,14 +133,14 @@ func (mc Config) ServiceMetricsPort(pod corev1.Pod) (string, error) { // written their service in such a way that it expects to be able to use // privileged ports. So, the port metrics are exposed on the service can // be privileged. - return determineAndValidatePort(pod, constants.AnnotationServiceMetricsPort, raw, true) + return common.DetermineAndValidatePort(pod, constants.AnnotationServiceMetricsPort, raw, true) } // If the annotationPort is not set, the serviceMetrics port will be 0 // unless overridden by the service-metrics-port annotation. If the service // metrics port is 0, the consul sidecar will not run a merged metrics // server. - return determineAndValidatePort(pod, constants.AnnotationServiceMetricsPort, "0", true) + return common.DetermineAndValidatePort(pod, constants.AnnotationServiceMetricsPort, "0", true) } // ServiceMetricsPath returns a default of /metrics, or overrides @@ -180,37 +180,3 @@ func (mc Config) ShouldRunMergedMetricsServer(pod corev1.Pod) (bool, error) { } return false, nil } - -// determineAndValidatePort behaves as follows: -// If the annotation exists, validate the port and return it. -// If the annotation does not exist, return the default port. -// If the privileged flag is true, it will allow the port to be in the -// privileged port range of 1-1023. Otherwise, it will only allow ports in the -// unprivileged range of 1024-65535. -func determineAndValidatePort(pod corev1.Pod, annotation string, defaultPort string, privileged bool) (string, error) { - if raw, ok := pod.Annotations[annotation]; ok && raw != "" { - port, err := common.PortValue(pod, raw) - if err != nil { - return "", fmt.Errorf("%s annotation value of %s is not a valid integer", annotation, raw) - } - - if privileged && (port < 1 || port > 65535) { - return "", fmt.Errorf("%s annotation value of %d is not in the valid port range 1-65535", annotation, port) - } else if !privileged && (port < 1024 || port > 65535) { - return "", fmt.Errorf("%s annotation value of %d is not in the unprivileged port range 1024-65535", annotation, port) - } - - // If the annotation exists, return the validated port. - return fmt.Sprint(port), nil - } - - // If the annotation does not exist, return the default. - if defaultPort != "" { - port, err := common.PortValue(pod, defaultPort) - if err != nil { - return "", fmt.Errorf("%s is not a valid port on the pod %s", defaultPort, pod.Name) - } - return fmt.Sprint(port), nil - } - return "", nil -} diff --git a/control-plane/connect-inject/metrics/metrics_configuration_test.go b/control-plane/connect-inject/metrics/metrics_configuration_test.go index ec19d4f55a..12045e28d1 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration_test.go +++ b/control-plane/connect-inject/metrics/metrics_configuration_test.go @@ -307,149 +307,6 @@ func TestMetricsConfigShouldRunMergedMetricsServer(t *testing.T) { } } -// Tests determineAndValidatePort, which in turn tests the -// PrometheusScrapePort() and MergedMetricsPort() functions because their logic -// is just to call out to determineAndValidatePort(). -func TestMetricsConfigDetermineAndValidatePort(t *testing.T) { - cases := []struct { - Name string - Pod func(*corev1.Pod) *corev1.Pod - Annotation string - Privileged bool - DefaultPort string - Expected string - Err string - }{ - { - Name: "Valid annotation", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "1234" - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: false, - Expected: "1234", - Err: "", - }, - { - Name: "Uses default when there's no annotation", - Pod: func(pod *corev1.Pod) *corev1.Pod { - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: false, - DefaultPort: "4321", - Expected: "4321", - Err: "", - }, - { - Name: "Gets the value of the named default port when there's no annotation", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Spec.Containers[0].Ports = []corev1.ContainerPort{ - { - Name: "web-port", - ContainerPort: 2222, - }, - } - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: false, - DefaultPort: "web-port", - Expected: "2222", - Err: "", - }, - { - Name: "Errors if the named default port doesn't exist on the pod", - Pod: func(pod *corev1.Pod) *corev1.Pod { - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: false, - DefaultPort: "web-port", - Expected: "", - Err: "web-port is not a valid port on the pod minimal", - }, - { - Name: "Gets the value of the named port", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "web-port" - pod.Spec.Containers[0].Ports = []corev1.ContainerPort{ - { - Name: "web-port", - ContainerPort: 2222, - }, - } - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: false, - DefaultPort: "4321", - Expected: "2222", - Err: "", - }, - { - Name: "Invalid annotation (not an integer)", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "not-an-int" - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: false, - Expected: "", - Err: "consul.hashicorp.com/test-annotation-port annotation value of not-an-int is not a valid integer", - }, - { - Name: "Invalid annotation (integer not in port range)", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "100000" - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: true, - Expected: "", - Err: "consul.hashicorp.com/test-annotation-port annotation value of 100000 is not in the valid port range 1-65535", - }, - { - Name: "Invalid annotation (integer not in unprivileged port range)", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "22" - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: false, - Expected: "", - Err: "consul.hashicorp.com/test-annotation-port annotation value of 22 is not in the unprivileged port range 1024-65535", - }, - { - Name: "Privileged ports allowed", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations["consul.hashicorp.com/test-annotation-port"] = "22" - return pod - }, - Annotation: "consul.hashicorp.com/test-annotation-port", - Privileged: true, - Expected: "22", - Err: "", - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - - actual, err := determineAndValidatePort(*tt.Pod(minimal()), tt.Annotation, tt.DefaultPort, tt.Privileged) - - if tt.Err == "" { - require.NoError(err) - require.Equal(tt.Expected, actual) - } else { - require.EqualError(err, tt.Err) - } - }) - } -} - // Tests MergedMetricsServerConfiguration happy path and error case not covered by other Config tests. func TestMetricsConfigMergedMetricsServerConfiguration(t *testing.T) { cases := []struct { diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index fe37720b7d..68f57ed061 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -247,6 +247,45 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, mpi mu args = append(args, fmt.Sprintf("-envoy-admin-bind-port=%d", 19000+mpi.serviceIndex)) } + // The consul-dataplane HTTP listener always starts for graceful shutdown. To avoid port conflicts, the + // graceful port always needs to be set + gracefulPort, err := w.LifecycleConfig.GracefulPort(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine proxy lifecycle graceful port: %w", err) + } + + // To avoid conflicts + if mpi.serviceName != "" { + gracefulPort = gracefulPort + mpi.serviceIndex + } + args = append(args, fmt.Sprintf("-graceful-port=%d", gracefulPort)) + + enableProxyLifecycle, err := w.LifecycleConfig.EnableProxyLifecycle(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine if proxy lifecycle management is enabled: %w", err) + } + if enableProxyLifecycle { + shutdownDrainListeners, err := w.LifecycleConfig.EnableShutdownDrainListeners(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine if proxy lifecycle shutdown listener draining is enabled: %w", err) + } + if shutdownDrainListeners { + args = append(args, "-shutdown-drain-listeners") + } + + shutdownGracePeriodSeconds, err := w.LifecycleConfig.ShutdownGracePeriodSeconds(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine proxy lifecycle shutdown grace period: %w", err) + } + args = append(args, fmt.Sprintf("-shutdown-grace-period-seconds=%d", shutdownGracePeriodSeconds)) + + gracefulShutdownPath := w.LifecycleConfig.GracefulShutdownPath(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine proxy lifecycle graceful shutdown path: %w", err) + } + args = append(args, fmt.Sprintf("-graceful-shutdown-path=%s", gracefulShutdownPath)) + } + // Set a default scrape path that can be overwritten by the annotation. prometheusScrapePath := w.MetricsConfig.PrometheusScrapePath(pod) args = append(args, "-telemetry-prom-scrape-path="+prometheusScrapePath) diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index 0860293352..d83b094d99 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -28,20 +29,20 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { }{ "default": { webhookSetupFunc: nil, - additionalExpCmdArgs: " -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with custom gRPC port": { webhookSetupFunc: func(w *MeshWebhook) { w.ConsulConfig.GRPCPort = 8602 }, - additionalExpCmdArgs: " -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with ACLs": { webhookSetupFunc: func(w *MeshWebhook) { w.AuthMethod = "test-auth-method" }, additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-meta=pod=k8snamespace/test-pod -tls-disabled -telemetry-prom-scrape-path=/metrics", + "-login-meta=pod=k8snamespace/test-pod -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with ACLs and namespace mirroring": { webhookSetupFunc: func(w *MeshWebhook) { @@ -50,7 +51,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.EnableK8SNSMirroring = true }, additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-meta=pod=k8snamespace/test-pod -login-namespace=default -service-namespace=k8snamespace -tls-disabled -telemetry-prom-scrape-path=/metrics", + "-login-meta=pod=k8snamespace/test-pod -login-namespace=default -service-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with ACLs and single destination namespace": { webhookSetupFunc: func(w *MeshWebhook) { @@ -59,7 +60,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.ConsulDestinationNamespace = "test-ns" }, additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-meta=pod=k8snamespace/test-pod -login-namespace=test-ns -service-namespace=test-ns -tls-disabled -telemetry-prom-scrape-path=/metrics", + "-login-meta=pod=k8snamespace/test-pod -login-namespace=test-ns -service-namespace=test-ns -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with ACLs and partitions": { webhookSetupFunc: func(w *MeshWebhook) { @@ -67,7 +68,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.ConsulPartition = "test-part" }, additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-meta=pod=k8snamespace/test-pod -login-partition=test-part -service-partition=test-part -tls-disabled -telemetry-prom-scrape-path=/metrics", + "-login-meta=pod=k8snamespace/test-pod -login-partition=test-part -service-partition=test-part -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with TLS and CA cert provided": { webhookSetupFunc: func(w *MeshWebhook) { @@ -75,28 +76,28 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.ConsulTLSServerName = "server.dc1.consul" w.ConsulCACert = "consul-ca-cert" }, - additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -ca-certs=/consul/connect-inject/consul-ca.pem -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -ca-certs=/consul/connect-inject/consul-ca.pem -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with TLS and no CA cert provided": { webhookSetupFunc: func(w *MeshWebhook) { w.TLSEnabled = true w.ConsulTLSServerName = "server.dc1.consul" }, - additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with single destination namespace": { webhookSetupFunc: func(w *MeshWebhook) { w.EnableNamespaces = true w.ConsulDestinationNamespace = "consul-namespace" }, - additionalExpCmdArgs: " -service-namespace=consul-namespace -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -service-namespace=consul-namespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with namespace mirroring": { webhookSetupFunc: func(w *MeshWebhook) { w.EnableNamespaces = true w.EnableK8SNSMirroring = true }, - additionalExpCmdArgs: " -service-namespace=k8snamespace -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -service-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with namespace mirroring prefix": { webhookSetupFunc: func(w *MeshWebhook) { @@ -104,38 +105,38 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.EnableK8SNSMirroring = true w.K8SNSMirroringPrefix = "foo-" }, - additionalExpCmdArgs: " -service-namespace=foo-k8snamespace -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -service-namespace=foo-k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with partitions": { webhookSetupFunc: func(w *MeshWebhook) { w.ConsulPartition = "partition-1" }, - additionalExpCmdArgs: " -service-partition=partition-1 -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -service-partition=partition-1 -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with different log level": { webhookSetupFunc: func(w *MeshWebhook) { w.LogLevel = "debug" }, - additionalExpCmdArgs: " -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with different log level and log json": { webhookSetupFunc: func(w *MeshWebhook) { w.LogLevel = "debug" w.LogJSON = true }, - additionalExpCmdArgs: " -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "skip server watch enabled": { webhookSetupFunc: func(w *MeshWebhook) { w.SkipServerWatch = true }, - additionalExpCmdArgs: " -server-watch-disabled=true -tls-disabled -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -server-watch-disabled=true -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "custom prometheus scrape path": { webhookSetupFunc: func(w *MeshWebhook) { w.MetricsConfig.DefaultPrometheusScrapePath = "/scrape-path" // Simulate what would be passed as a flag }, - additionalExpCmdArgs: " -tls-disabled -telemetry-prom-scrape-path=/scrape-path", + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/scrape-path", }, } @@ -622,18 +623,18 @@ func TestHandlerConsulDataplaneSidecar_Multiport(t *testing.T) { } expArgs := []string{ "-addresses 1.1.1.1 -grpc-port=8502 -proxy-service-id-path=/consul/connect-inject/proxyid-web " + - "-log-level=info -log-json=false -envoy-concurrency=0 -tls-disabled -envoy-admin-bind-port=19000 -telemetry-prom-scrape-path=/metrics -- --base-id 0", + "-log-level=info -log-json=false -envoy-concurrency=0 -tls-disabled -envoy-admin-bind-port=19000 -graceful-port=20600 -telemetry-prom-scrape-path=/metrics -- --base-id 0", "-addresses 1.1.1.1 -grpc-port=8502 -proxy-service-id-path=/consul/connect-inject/proxyid-web-admin " + - "-log-level=info -log-json=false -envoy-concurrency=0 -tls-disabled -envoy-admin-bind-port=19001 -telemetry-prom-scrape-path=/metrics -- --base-id 1", + "-log-level=info -log-json=false -envoy-concurrency=0 -tls-disabled -envoy-admin-bind-port=19001 -graceful-port=20601 -telemetry-prom-scrape-path=/metrics -- --base-id 1", } if aclsEnabled { expArgs = []string{ "-addresses 1.1.1.1 -grpc-port=8502 -proxy-service-id-path=/consul/connect-inject/proxyid-web " + "-log-level=info -log-json=false -envoy-concurrency=0 -credential-type=login -login-auth-method=test-auth-method " + - "-login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token -login-meta=pod=k8snamespace/test-pod -tls-disabled -envoy-admin-bind-port=19000 -telemetry-prom-scrape-path=/metrics -- --base-id 0", + "-login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token -login-meta=pod=k8snamespace/test-pod -tls-disabled -envoy-admin-bind-port=19000 -graceful-port=20600 -telemetry-prom-scrape-path=/metrics -- --base-id 0", "-addresses 1.1.1.1 -grpc-port=8502 -proxy-service-id-path=/consul/connect-inject/proxyid-web-admin " + "-log-level=info -log-json=false -envoy-concurrency=0 -credential-type=login -login-auth-method=test-auth-method " + - "-login-bearer-token-path=/consul/serviceaccount-web-admin/token -login-meta=pod=k8snamespace/test-pod -tls-disabled -envoy-admin-bind-port=19001 -telemetry-prom-scrape-path=/metrics -- --base-id 1", + "-login-bearer-token-path=/consul/serviceaccount-web-admin/token -login-meta=pod=k8snamespace/test-pod -tls-disabled -envoy-admin-bind-port=19001 -graceful-port=20601 -telemetry-prom-scrape-path=/metrics -- --base-id 1", } } expSAVolumeMounts := []corev1.VolumeMount{ @@ -1299,6 +1300,156 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { } } +func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { + gracefulShutdownSeconds := 10 + gracefulPort := "20307" + gracefulShutdownPath := "/exit" + + cases := []struct { + name string + webhook MeshWebhook + annotations map[string]string + expCmdArgs string + expErr string + }{ + { + name: "no defaults, no annotations", + webhook: MeshWebhook{}, + annotations: nil, + expCmdArgs: "", + }, + { + name: "all defaults, no annotations", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: true, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + DefaultGracefulPort: gracefulPort, + DefaultGracefulShutdownPath: gracefulShutdownPath, + }, + }, + annotations: nil, + expCmdArgs: "graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", + }, + { + name: "no defaults, all annotations", + webhook: MeshWebhook{}, + annotations: map[string]string{ + constants.AnnotationEnableSidecarProxyLifecycle: "true", + constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "true", + constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds), + constants.AnnotationSidecarProxyLifecycleGracefulPort: gracefulPort, + constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: gracefulShutdownPath, + }, + expCmdArgs: "-graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", + }, + { + name: "annotations override defaults", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: false, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + DefaultGracefulPort: gracefulPort, + DefaultGracefulShutdownPath: gracefulShutdownPath, + }, + }, + annotations: map[string]string{ + constants.AnnotationEnableSidecarProxyLifecycle: "true", + constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", + constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds + 5), + constants.AnnotationSidecarProxyLifecycleGracefulPort: "20317", + constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: "/foo", + }, + expCmdArgs: "-graceful-port=20317 -shutdown-grace-period-seconds=15 -graceful-shutdown-path=/foo", + }, + { + name: "lifecycle disabled, no annotations", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: false, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + DefaultGracefulPort: gracefulPort, + DefaultGracefulShutdownPath: gracefulShutdownPath, + }, + }, + annotations: nil, + expCmdArgs: "-graceful-port=20307", + }, + { + name: "lifecycle enabled, defaults omited, no annotations", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: true, + }, + }, + annotations: nil, + expCmdArgs: "", + }, + { + name: "annotations disable lifecycle default", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: true, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + DefaultGracefulPort: gracefulPort, + DefaultGracefulShutdownPath: gracefulShutdownPath, + }, + }, + annotations: map[string]string{ + constants.AnnotationEnableSidecarProxyLifecycle: "false", + }, + expCmdArgs: "-graceful-port=20307", + }, + { + name: "annotations skip graceful shutdown", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: false, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + }, + }, + annotations: map[string]string{ + constants.AnnotationEnableSidecarProxyLifecycle: "false", + constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", + constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: "0", + }, + expCmdArgs: "", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + c.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} + require := require.New(t) + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: c.annotations, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + container, err := c.webhook.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) + if c.expErr != "" { + require.NotNil(err) + require.Contains(err.Error(), c.expErr) + } else { + require.NoError(err) + require.Contains(strings.Join(container.Args, " "), c.expCmdArgs) + } + }) + } +} + // boolPtr returns pointer to b. func boolPtr(b bool) *bool { return &b diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 96c73d93d4..d97bca6646 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -17,6 +17,7 @@ import ( "github.com/go-logr/logr" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/namespaces" @@ -151,6 +152,10 @@ type MeshWebhook struct { DefaultProxyMemoryRequest resource.Quantity DefaultProxyMemoryLimit resource.Quantity + // LifecycleConfig contains proxy lifecycle management configuration from the inject-connect command and has methods to determine whether + // configuration should come from the default flags or annotations. The meshWebhook uses this to configure container sidecar proxy args. + LifecycleConfig lifecycle.Config + // Default Envoy concurrency flag, this is the number of worker threads to be used by the proxy. DefaultEnvoyProxyConcurrency int @@ -306,6 +311,7 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) } + // TODO: invert to start the Envoy sidecar before the application container pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) } else { // For multi port pods, check for unsupported cases, mount all relevant service account tokens, and mount an init @@ -376,6 +382,8 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) } + // TODO: invert to start the Envoy sidecar container before the + // application container pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) } } diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 8bada415db..6767e60130 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -19,8 +19,10 @@ import ( gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" "github.com/hashicorp/consul-k8s/control-plane/controllers" @@ -86,6 +88,13 @@ type Command struct { flagDefaultSidecarProxyMemoryRequest string flagDefaultEnvoyProxyConcurrency int + // Proxy lifecycle settings. + flagDefaultEnableSidecarProxyLifecycle bool + flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners bool + flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds int + flagDefaultSidecarProxyLifecycleGracefulPort string + flagDefaultSidecarProxyLifecycleGracefulShutdownPath string + // Metrics settings. flagDefaultEnableMetrics bool flagEnableGatewayMetrics bool @@ -220,6 +229,13 @@ func (c *Command) init() { c.flagSet.StringVar(&c.flagDefaultSidecarProxyMemoryRequest, "default-sidecar-proxy-memory-request", "", "Default sidecar proxy memory request.") c.flagSet.StringVar(&c.flagDefaultSidecarProxyMemoryLimit, "default-sidecar-proxy-memory-limit", "", "Default sidecar proxy memory limit.") + // Proxy lifecycle setting flags. + c.flagSet.BoolVar(&c.flagDefaultEnableSidecarProxyLifecycle, "default-enable-sidecar-proxy-lifecycle", false, "Default for enabling sidecar proxy lifecycle management.") + c.flagSet.BoolVar(&c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, "default-enable-sidecar-proxy-lifecycle-shutdown-drain-listeners", false, "Default for enabling sidecar proxy listener draining of inbound connections during shutdown.") + c.flagSet.IntVar(&c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, "default-sidecar-proxy-lifecycle-shutdown-grace-period-seconds", 0, "Default sidecar proxy shutdown grace period in seconds.") + c.flagSet.StringVar(&c.flagDefaultSidecarProxyLifecycleGracefulPort, "default-sidecar-proxy-lifecycle-graceful-port", strconv.Itoa(constants.DefaultGracefulPort), "Default port for sidecar proxy lifecycle management HTTP endpoints.") + c.flagSet.StringVar(&c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, "default-sidecar-proxy-lifecycle-graceful-shutdown-path", "/graceful_shutdown", "Default sidecar proxy lifecycle management graceful shutdown path.") + // Metrics setting flags. c.flagSet.BoolVar(&c.flagDefaultEnableMetrics, "default-enable-metrics", false, "Default for enabling connect service metrics.") c.flagSet.BoolVar(&c.flagEnableGatewayMetrics, "enable-gateway-metrics", false, "Allows enabling Consul gateway metrics.") @@ -422,6 +438,14 @@ func (c *Command) Run(args []string) int { return 1 } + lifecycleConfig := lifecycle.Config{ + DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, + DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, + DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, + DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, + DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, + } + metricsConfig := metrics.Config{ DefaultEnableMetrics: c.flagDefaultEnableMetrics, EnableGatewayMetrics: c.flagEnableGatewayMetrics, @@ -725,6 +749,7 @@ func (c *Command) Run(args []string) int { DefaultProxyMemoryRequest: sidecarProxyMemoryRequest, DefaultProxyMemoryLimit: sidecarProxyMemoryLimit, DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, + LifecycleConfig: lifecycleConfig, MetricsConfig: metricsConfig, InitContainerResources: initResources, ConsulPartition: c.consul.Partition, From d3f9b670ab8055f0fc8ea4061c2d3c40abeb047f Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 27 Jun 2023 19:15:51 -0700 Subject: [PATCH 256/592] PR breaking change release note change (#2469) * Add breaking change to release notes --- .changelog/2392.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.changelog/2392.txt b/.changelog/2392.txt index e15ef152b1..e268c796ff 100644 --- a/.changelog/2392.txt +++ b/.changelog/2392.txt @@ -1,3 +1,6 @@ +```release-note:breaking-change +control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. +``` ```release-note:bug -control-plane: Always update ACL policies upon upgrade -``` \ No newline at end of file +control-plane: Always update ACL policies upon upgrade. +``` From 920ee3233d41792134a02f7a0e9e1d2d5ed4482d Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Wed, 28 Jun 2023 12:53:41 -0400 Subject: [PATCH 257/592] Adds back gateway controller halting integration test (#2412) Co-authored-by: John Maguire --- .../api-gateway/binding/route_binding.go | 2 +- .../gateway_controller_integration_test.go | 1320 +++++++++++++++++ 2 files changed, 1321 insertions(+), 1 deletion(-) create mode 100644 control-plane/api-gateway/controllers/gateway_controller_integration_test.go diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index 93e68241a0..36fcda0204 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -457,7 +457,7 @@ func consulCondition(generation int64, status api.ConfigEntryStatus) *metav1.Con for _, c := range status.Conditions { // we only care about the top-level status that isn't in reference // to a resource. - if c.Type == "Accepted" && c.Resource.Name == "" { + if c.Type == "Accepted" && (c.Resource == nil || c.Resource.Name == "") { return &metav1.Condition{ Type: "ConsulAccepted", Reason: c.Reason, diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go new file mode 100644 index 0000000000..b423ae54e7 --- /dev/null +++ b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go @@ -0,0 +1,1320 @@ +package controllers + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "sync" + "testing" + "time" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul/api" +) + +func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { + s := runtime.NewScheme() + require.NoError(t, clientgoscheme.AddToScheme(s)) + require.NoError(t, gwv1alpha2.Install(s)) + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + testCases := map[string]struct { + namespace string + certFn func(*testing.T, context.Context, client.WithWatch, string) *corev1.Secret + gwFn func(*testing.T, context.Context, client.WithWatch, string) *gwv1beta1.Gateway + httpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute + tcpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *v1alpha2.TCPRoute + }{ + "all fields set": { + namespace: "consul", + certFn: createCert, + gwFn: createAllFieldsSetAPIGW, + httpRouteFn: createAllFieldsSetHTTPRoute, + tcpRouteFn: createAllFieldsSetTCPRoute, + }, + "minimal fields set": { + namespace: "", + certFn: createCert, + gwFn: minimalFieldsSetAPIGW, + httpRouteFn: minimalFieldsSetHTTPRoute, + tcpRouteFn: minimalFieldsSetTCPRoute, + }, + "funky casing to test normalization doesnt cause infinite reconciliation": { + namespace: "", + certFn: createCert, + gwFn: createFunkyCasingFieldsAPIGW, + httpRouteFn: createFunkyCasingFieldsHTTPRoute, + tcpRouteFn: createFunkyCasingFieldsTCPRoute, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + k8sClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s)).Build() + consulTestServerClient := test.TestServerWithMockConnMgrWatcher(t, nil) + ctx, cancel := context.WithCancel(context.Background()) + + t.Cleanup(func() { + cancel() + }) + logger := logrtest.New(t) + + cacheCfg := cache.Config{ + ConsulClientConfig: consulTestServerClient.Cfg, + ConsulServerConnMgr: consulTestServerClient.Watcher, + Logger: logger, + } + resourceCache := cache.New(cacheCfg) + + gwCache := cache.NewGatewayCache(ctx, cacheCfg) + + gwCtrl := GatewayController{ + HelmConfig: common.HelmConfig{}, + Log: logger, + Translator: common.ResourceTranslator{}, + cache: resourceCache, + gatewayCache: gwCache, + Client: k8sClient, + allowK8sNamespacesSet: mapset.NewSet(), + denyK8sNamespacesSet: mapset.NewSet(), + } + + go func() { + resourceCache.Run(ctx) + }() + + resourceCache.WaitSynced(ctx) + + gwSub := resourceCache.Subscribe(ctx, api.APIGateway, gwCtrl.transformConsulGateway) + httpRouteSub := resourceCache.Subscribe(ctx, api.HTTPRoute, gwCtrl.transformConsulHTTPRoute(ctx)) + tcpRouteSub := resourceCache.Subscribe(ctx, api.TCPRoute, gwCtrl.transformConsulTCPRoute(ctx)) + inlineCertSub := resourceCache.Subscribe(ctx, api.InlineCertificate, gwCtrl.transformConsulInlineCertificate(ctx)) + + cert := tc.certFn(t, ctx, k8sClient, tc.namespace) + k8sGWObj := tc.gwFn(t, ctx, k8sClient, tc.namespace) + + // reconcile so we add the finalizer + _, err := gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + Name: k8sGWObj.Name, + }, + }) + require.NoError(t, err) + + // reconcile again so that we get the creation with the finalizer + _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + Name: k8sGWObj.Name, + }, + }) + require.NoError(t, err) + + httpRouteObj := tc.httpRouteFn(t, ctx, k8sClient, k8sGWObj) + tcpRouteObj := tc.tcpRouteFn(t, ctx, k8sClient, k8sGWObj) + + // reconcile again so that we get the route bound to the gateway + _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + Name: k8sGWObj.Name, + }, + }) + require.NoError(t, err) + + // reconcile again so that we get the route bound to the gateway + _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + Name: k8sGWObj.Name, + }, + }) + require.NoError(t, err) + + wg := &sync.WaitGroup{} + // we never get the event from the cert because when it's created there are no gateways that reference it + wg.Add(3) + go func(w *sync.WaitGroup) { + gwDone := false + httpRouteDone := false + tcpRouteDone := false + for { + // get the creation events from the upsert and then continually read from channel so we dont block other subs + select { + case <-ctx.Done(): + return + case <-gwSub.Events(): + if !gwDone { + gwDone = true + wg.Done() + } + case <-httpRouteSub.Events(): + if !httpRouteDone { + httpRouteDone = true + wg.Done() + } + case <-tcpRouteSub.Events(): + if !tcpRouteDone { + tcpRouteDone = true + wg.Done() + } + case <-inlineCertSub.Events(): + } + } + }(wg) + + wg.Wait() + + gwNamespaceName := types.NamespacedName{ + Name: k8sGWObj.Name, + Namespace: k8sGWObj.Namespace, + } + + httpRouteNamespaceName := types.NamespacedName{ + Name: httpRouteObj.Name, + Namespace: httpRouteObj.Namespace, + } + + tcpRouteNamespaceName := types.NamespacedName{ + Name: tcpRouteObj.Name, + Namespace: tcpRouteObj.Namespace, + } + + certNamespaceName := types.NamespacedName{ + Name: cert.Name, + Namespace: cert.Namespace, + } + + gwRef := gwCtrl.Translator.ConfigEntryReference(api.APIGateway, gwNamespaceName) + httpRouteRef := gwCtrl.Translator.ConfigEntryReference(api.HTTPRoute, httpRouteNamespaceName) + tcpRouteRef := gwCtrl.Translator.ConfigEntryReference(api.TCPRoute, tcpRouteNamespaceName) + certRef := gwCtrl.Translator.ConfigEntryReference(api.InlineCertificate, certNamespaceName) + + curGWModifyIndex := resourceCache.Get(gwRef).GetModifyIndex() + curHTTPRouteModifyIndex := resourceCache.Get(httpRouteRef).GetModifyIndex() + curTCPRouteModifyIndex := resourceCache.Get(tcpRouteRef).GetModifyIndex() + curCertModifyIndex := resourceCache.Get(certRef).GetModifyIndex() + + err = k8sClient.Get(ctx, gwNamespaceName, k8sGWObj) + require.NoError(t, err) + curGWResourceVersion := k8sGWObj.ResourceVersion + + err = k8sClient.Get(ctx, httpRouteNamespaceName, httpRouteObj) + require.NoError(t, err) + curHTTPRouteResourceVersion := httpRouteObj.ResourceVersion + + err = k8sClient.Get(ctx, tcpRouteNamespaceName, tcpRouteObj) + require.NoError(t, err) + curTCPRouteResourceVersion := tcpRouteObj.ResourceVersion + + err = k8sClient.Get(ctx, certNamespaceName, cert) + require.NoError(t, err) + curCertResourceVersion := cert.ResourceVersion + + go func() { + // reconcile multiple times with no changes to be sure + for i := 0; i < 5; i++ { + _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: k8sGWObj.Namespace, + }, + }) + require.NoError(t, err) + } + }() + + require.Never(t, func() bool { + err = k8sClient.Get(ctx, gwNamespaceName, k8sGWObj) + require.NoError(t, err) + newGWResourceVersion := k8sGWObj.ResourceVersion + + err = k8sClient.Get(ctx, httpRouteNamespaceName, httpRouteObj) + require.NoError(t, err) + newHTTPRouteResourceVersion := httpRouteObj.ResourceVersion + + err = k8sClient.Get(ctx, tcpRouteNamespaceName, tcpRouteObj) + require.NoError(t, err) + newTCPRouteResourceVersion := tcpRouteObj.ResourceVersion + + err = k8sClient.Get(ctx, certNamespaceName, cert) + require.NoError(t, err) + newCertResourceVersion := cert.ResourceVersion + + return curGWModifyIndex == resourceCache.Get(gwRef).GetModifyIndex() && + curGWResourceVersion == newGWResourceVersion && + curHTTPRouteModifyIndex == resourceCache.Get(httpRouteRef).GetModifyIndex() && + curHTTPRouteResourceVersion == newHTTPRouteResourceVersion && + curTCPRouteModifyIndex == resourceCache.Get(tcpRouteRef).GetModifyIndex() && + curTCPRouteResourceVersion == newTCPRouteResourceVersion && + curCertModifyIndex == resourceCache.Get(certRef).GetModifyIndex() && + curCertResourceVersion == newCertResourceVersion + }, time.Duration(2*time.Second), time.Duration(500*time.Millisecond), fmt.Sprintf("curGWModifyIndex: %d, newIndx: %d", curGWModifyIndex, resourceCache.Get(gwRef).GetModifyIndex()), + ) + }) + } +} + +func createAllFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { + // listener one configuration + listenerOneName := "listener-one" + listenerOneHostname := "*.consul.io" + listenerOnePort := 3366 + listenerOneProtocol := "https" + + // listener two configuration + listenerTwoName := "listener-two" + listenerTwoHostname := "*.consul.io" + listenerTwoPort := 5432 + listenerTwoProtocol := "http" + + // listener three configuration + listenerThreeName := "listener-three" + listenerThreePort := 8081 + listenerThreeProtocol := "tcp" + + // listener four configuration + listenerFourName := "listener-four" + listenerFourHostname := "*.consul.io" + listenerFourPort := 5433 + listenerFourProtocol := "http" + + // Write gw to k8s + gwClassCfg := &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClassConfig", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{}, + } + gwClass := &gwv1beta1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClass", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass", + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "consul.hashicorp.com/gateway-controller", + ParametersRef: &gwv1beta1.ParametersReference{ + Group: "consul.hashicorp.com", + Kind: "GatewayClassConfig", + Name: "gateway-class-config", + }, + Description: new(string), + }, + } + gw := &gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: namespace, + Annotations: make(map[string]string), + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), + Listeners: []gwv1beta1.Listener{ + { + Name: gwv1beta1.SectionName(listenerOneName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), + Port: gwv1beta1.PortNumber(listenerOnePort), + Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + { + Kind: common.PointerTo(gwv1beta1.Kind("Secret")), + Name: gwv1beta1.ObjectName("one-cert"), + Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), + }, + }, + }, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerTwoName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), + Port: gwv1beta1.PortNumber(listenerTwoPort), + Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("Same")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerThreeName), + Port: gwv1beta1.PortNumber(listenerThreePort), + Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerFourName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerFourHostname)), + Port: gwv1beta1.PortNumber(listenerFourPort), + Protocol: gwv1beta1.ProtocolType(listenerFourProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("Selector")), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + common.NamespaceNameLabel: "consul", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{}, + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, gwClassCfg) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gwClass) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gw) + require.NoError(t, err) + + return gw +} + +func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { + svcDefault := &v1alpha1.ServiceDefaults{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceDefaults", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + } + + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "high", + Protocol: "TCP", + Port: 8080, + }, + }, + Selector: map[string]string{"app": "Service"}, + }, + } + + serviceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + } + + deployment := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: common.PointerTo(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "Service"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{}, + }, + }, + } + + err := k8sClient.Create(ctx, svcDefault) + require.NoError(t, err) + + err = k8sClient.Create(ctx, svc) + require.NoError(t, err) + + err = k8sClient.Create(ctx, serviceAccount) + require.NoError(t, err) + + err = k8sClient.Create(ctx, deployment) + require.NoError(t, err) + + route := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[0].Name, + Port: &gw.Spec.Listeners[0].Port, + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: common.PointerTo(gwv1beta1.PathMatchType("PathPrefix")), + Value: common.PointerTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: common.PointerTo(gwv1beta1.HeaderMatchExact), + Name: "version", + Value: "version", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "q", + }, + }, + Method: common.PointerTo(gwv1beta1.HTTPMethod("GET")), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "foo", + Value: "bax", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "arc", + Value: "reactor", + }, + }, + Remove: []string{"remove"}, + }, + }, + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.FullPathHTTPPathModifier, + ReplaceFullPath: common.PointerTo("/foobar"), + }, + }, + }, + + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: common.PointerTo("/foo"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(8080)), + }, + Weight: common.PointerTo(int32(50)), + }, + }, + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createAllFieldsSetTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { + route := &v1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "TCPRoute", + APIVersion: "gateway.networking.k8s.io/v1alpha2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[2].Name, + Port: &gw.Spec.Listeners[2].Port, + }, + }, + }, + Rules: []gwv1alpha2.TCPRouteRule{ + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(25000)), + }, + Weight: common.PointerTo(int32(50)), + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createCert(t *testing.T, ctx context.Context, k8sClient client.WithWatch, certNS string) *corev1.Secret { + // listener one tls config + certName := "one-cert" + + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(t, err) + + usage := x509.KeyUsageCertSign + expiration := time.Now().AddDate(10, 0, 0) + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "consul.test", + }, + IsCA: true, + NotBefore: time.Now().Add(-10 * time.Minute), + NotAfter: expiration, + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: usage, + BasicConstraintsValid: true, + } + caCert := cert + caPrivateKey := privateKey + + data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) + require.NoError(t, err) + + certBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: data, + }) + + privateKeyBytes := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: certNS, + Name: certName, + }, + Data: map[string][]byte{ + corev1.TLSCertKey: certBytes, + corev1.TLSPrivateKeyKey: privateKeyBytes, + }, + } + + err = k8sClient.Create(ctx, secret) + require.NoError(t, err) + + return secret +} + +func minimalFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { + // listener one configuration + listenerOneName := "listener-one" + listenerOneHostname := "*.consul.io" + listenerOnePort := 3366 + listenerOneProtocol := "https" + + // listener three configuration + listenerThreeName := "listener-three" + listenerThreePort := 8081 + listenerThreeProtocol := "tcp" + + // Write gw to k8s + gwClassCfg := &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClassConfig", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{}, + } + gwClass := &gwv1beta1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClass", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass", + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "consul.hashicorp.com/gateway-controller", + ParametersRef: &gwv1beta1.ParametersReference{ + Group: "consul.hashicorp.com", + Kind: "GatewayClassConfig", + Name: "gateway-class-config", + }, + Description: new(string), + }, + } + gw := &gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Annotations: make(map[string]string), + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), + Listeners: []gwv1beta1.Listener{ + { + Name: gwv1beta1.SectionName(listenerOneName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), + Port: gwv1beta1.PortNumber(listenerOnePort), + Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + { + Kind: common.PointerTo(gwv1beta1.Kind("Secret")), + Name: gwv1beta1.ObjectName("one-cert"), + Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), + }, + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerThreeName), + Port: gwv1beta1.PortNumber(listenerThreePort), + Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, gwClassCfg) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gwClass) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gw) + require.NoError(t, err) + + return gw +} + +func minimalFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { + svcDefault := &v1alpha1.ServiceDefaults{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceDefaults", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + } + + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "high", + Protocol: "TCP", + Port: 8080, + }, + }, + Selector: map[string]string{"app": "Service"}, + }, + } + + serviceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + } + + deployment := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: common.PointerTo(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "Service"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{}, + }, + }, + } + + err := k8sClient.Create(ctx, svcDefault) + require.NoError(t, err) + + err = k8sClient.Create(ctx, svc) + require.NoError(t, err) + + err = k8sClient.Create(ctx, serviceAccount) + require.NoError(t, err) + + err = k8sClient.Create(ctx, deployment) + require.NoError(t, err) + + route := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[0].Name, + Port: &gw.Spec.Listeners[0].Port, + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, + Rules: []gwv1beta1.HTTPRouteRule{ + { + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(8080)), + }, + }, + }, + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func minimalFieldsSetTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { + route := &v1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "TCPRoute", + APIVersion: "gateway.networking.k8s.io/v1alpha2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[1].Name, + Port: &gw.Spec.Listeners[1].Port, + }, + }, + }, + Rules: []gwv1alpha2.TCPRouteRule{ + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(25000)), + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createFunkyCasingFieldsAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { + // listener one configuration + listenerOneName := "listener-one" + listenerOneHostname := "*.consul.io" + listenerOnePort := 3366 + listenerOneProtocol := "hTtPs" + + // listener two configuration + listenerTwoName := "listener-two" + listenerTwoHostname := "*.consul.io" + listenerTwoPort := 5432 + listenerTwoProtocol := "HTTP" + + // listener three configuration + listenerThreeName := "listener-three" + listenerThreePort := 8081 + listenerThreeProtocol := "tCp" + + // listener four configuration + listenerFourName := "listener-four" + listenerFourHostname := "*.consul.io" + listenerFourPort := 5433 + listenerFourProtocol := "hTTp" + + // Write gw to k8s + gwClassCfg := &v1alpha1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClassConfig", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-class-config", + }, + Spec: v1alpha1.GatewayClassConfigSpec{}, + } + gwClass := &gwv1beta1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClass", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclass", + }, + Spec: gwv1beta1.GatewayClassSpec{ + ControllerName: "consul.hashicorp.com/gateway-controller", + ParametersRef: &gwv1beta1.ParametersReference{ + Group: "consul.hashicorp.com", + Kind: "GatewayClassConfig", + Name: "gateway-class-config", + }, + Description: new(string), + }, + } + gw := &gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: namespace, + Annotations: make(map[string]string), + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), + Listeners: []gwv1beta1.Listener{ + { + Name: gwv1beta1.SectionName(listenerOneName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), + Port: gwv1beta1.PortNumber(listenerOnePort), + Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), + TLS: &gwv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwv1beta1.SecretObjectReference{ + { + Kind: common.PointerTo(gwv1beta1.Kind("Secret")), + Name: gwv1beta1.ObjectName("one-cert"), + Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), + }, + }, + }, + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerTwoName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), + Port: gwv1beta1.PortNumber(listenerTwoPort), + Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("Same")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerThreeName), + Port: gwv1beta1.PortNumber(listenerThreePort), + Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("All")), + }, + }, + }, + { + Name: gwv1beta1.SectionName(listenerFourName), + Hostname: common.PointerTo(gwv1beta1.Hostname(listenerFourHostname)), + Port: gwv1beta1.PortNumber(listenerFourPort), + Protocol: gwv1beta1.ProtocolType(listenerFourProtocol), + AllowedRoutes: &gwv1beta1.AllowedRoutes{ + Namespaces: &gwv1beta1.RouteNamespaces{ + From: common.PointerTo(gwv1beta1.FromNamespaces("Selector")), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + common.NamespaceNameLabel: "consul", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{}, + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, gwClassCfg) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gwClass) + require.NoError(t, err) + + err = k8sClient.Create(ctx, gw) + require.NoError(t, err) + + return gw +} + +func createFunkyCasingFieldsHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { + svcDefault := &v1alpha1.ServiceDefaults{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceDefaults", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "hTtp", + }, + } + + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "high", + Protocol: "TCP", + Port: 8080, + }, + }, + Selector: map[string]string{"app": "Service"}, + }, + } + + serviceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + } + + deployment := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: common.PointerTo(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "Service"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{}, + }, + }, + } + + err := k8sClient.Create(ctx, svcDefault) + require.NoError(t, err) + + err = k8sClient.Create(ctx, svc) + require.NoError(t, err) + + err = k8sClient.Create(ctx, serviceAccount) + require.NoError(t, err) + + err = k8sClient.Create(ctx, deployment) + require.NoError(t, err) + + route := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[0].Name, + Port: &gw.Spec.Listeners[0].Port, + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: common.PointerTo(gwv1beta1.PathMatchPathPrefix), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: common.PointerTo(gwv1beta1.HeaderMatchExact), + Name: "version", + Value: "version", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "q", + }, + }, + Method: common.PointerTo(gwv1beta1.HTTPMethod("geT")), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "foo", + Value: "bax", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "arc", + Value: "reactor", + }, + }, + Remove: []string{"remove"}, + }, + }, + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.FullPathHTTPPathModifier, + ReplaceFullPath: common.PointerTo("/foobar"), + }, + }, + }, + + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: common.PointerTo("/foo"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(8080)), + }, + Weight: common.PointerTo(int32(-50)), + }, + }, + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createFunkyCasingFieldsTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { + route := &v1alpha2.TCPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "TCPRoute", + APIVersion: "gateway.networking.k8s.io/v1alpha2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-route", + }, + Spec: gwv1alpha2.TCPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[2].Name, + Port: &gw.Spec.Listeners[2].Port, + }, + }, + }, + Rules: []gwv1alpha2.TCPRouteRule{ + { + BackendRefs: []gwv1beta1.BackendRef{ + { + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(25000)), + }, + Weight: common.PointerTo(int32(-50)), + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} From e976b88a26c2e0147daf9582b5e95ccf68383317 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Thu, 29 Jun 2023 09:54:04 -0500 Subject: [PATCH 258/592] api-gateway: Fix nil pointer exception panic (#2487) * fix nil pointer exception * add unit test * added changelog * delete changelog --- .../api-gateway/common/translation.go | 14 ++--- .../api-gateway/common/translation_test.go | 54 +++++++++++++++++++ .../gateway_controller_integration_test.go | 2 + 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index fd69c8601f..55bfb29a2f 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -254,14 +254,16 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi } for _, filter := range filters { - consulFilter.Remove = append(consulFilter.Remove, filter.RequestHeaderModifier.Remove...) + if filter.RequestHeaderModifier != nil { + consulFilter.Remove = append(consulFilter.Remove, filter.RequestHeaderModifier.Remove...) - for _, toAdd := range filter.RequestHeaderModifier.Add { - consulFilter.Add[string(toAdd.Name)] = toAdd.Value - } + for _, toAdd := range filter.RequestHeaderModifier.Add { + consulFilter.Add[string(toAdd.Name)] = toAdd.Value + } - for _, toSet := range filter.RequestHeaderModifier.Set { - consulFilter.Set[string(toSet.Name)] = toSet.Value + for _, toSet := range filter.RequestHeaderModifier.Set { + consulFilter.Set[string(toSet.Name)] = toSet.Value + } } // we drop any path rewrites that are not prefix matches as we don't support those diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index caa3efbcac..20917151f3 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -1372,3 +1372,57 @@ func generateTestCertificate(t *testing.T, namespace, name string) corev1.Secret }, } } + +func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { + type fields struct { + EnableConsulNamespaces bool + ConsulDestNamespace string + EnableK8sMirroring bool + MirroringPrefix string + ConsulPartition string + Datacenter string + } + type args struct { + filters []gwv1beta1.HTTPRouteFilter + } + tests := []struct { + name string + fields fields + args args + want api.HTTPFilters + }{ + { + name: "no httproutemodifier set", + fields: fields{}, + args: args{ + filters: []gwv1beta1.HTTPRouteFilter{ + { + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{}, + }, + }, + }, + want: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{}, + Set: map[string]string{}, + }, + }, + URLRewrite: nil, + }, + }, + } + for _, tt := range tests { + t1.Run(tt.name, func(t1 *testing.T) { + t := ResourceTranslator{ + EnableConsulNamespaces: tt.fields.EnableConsulNamespaces, + ConsulDestNamespace: tt.fields.ConsulDestNamespace, + EnableK8sMirroring: tt.fields.EnableK8sMirroring, + MirroringPrefix: tt.fields.MirroringPrefix, + ConsulPartition: tt.fields.ConsulPartition, + Datacenter: tt.fields.Datacenter, + } + assert.Equalf(t1, tt.want, t.translateHTTPFilters(tt.args.filters), "translateHTTPFilters(%v)", tt.args.filters) + }) + } +} diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go index b423ae54e7..43f3d7d41d 100644 --- a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go +++ b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go @@ -37,6 +37,8 @@ import ( ) func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { + //TODO @apigatewayteam test is consistently failing on main after merge, fix in a follow up PR + t.Skip() s := runtime.NewScheme() require.NoError(t, clientgoscheme.AddToScheme(s)) require.NoError(t, gwv1alpha2.Install(s)) From 83f050be498c3b93d5248c2a7020c7e45837b352 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 29 Jun 2023 13:57:20 -0400 Subject: [PATCH 259/592] Use correct length for certificate RSA key for tests (#2490) * Use correct length for certificate RSA key * api-gateway: Fix nil pointer exception panic (#2487) * fix nil pointer exception * add unit test * added changelog * delete changelog * Remove skip for fixed test --------- Co-authored-by: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> --- .../controllers/gateway_controller_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go index 43f3d7d41d..35b3c64652 100644 --- a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go +++ b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go @@ -652,7 +652,7 @@ func createCert(t *testing.T, ctx context.Context, k8sClient client.WithWatch, c // listener one tls config certName := "one-cert" - privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) usage := x509.KeyUsageCertSign From 8fe4fb6aa8f5217a9060f6d52182d5d5272c3300 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 29 Jun 2023 15:30:27 -0400 Subject: [PATCH 260/592] APIGW: Validate length of RSA Keys (#2478) * Validate length of RSA key for inline certs * Bring key length check functions over from consul * move validation of key length from certificate parsing into validation of cert * Update to use sentinel errors * Add changelog * Addressing PR comments: fixing text in changelog, fixing import blocks, slight refactor of cert validation for readability * Ensure cert is removed from consul if an invalid one is presented * Fix linting issues, added tests for validating keys --- .changelog/2478.txt | 5 + .../api-gateway/binding/binder_test.go | 4 +- control-plane/api-gateway/binding/result.go | 22 ++-- .../api-gateway/binding/validation.go | 22 +++- control-plane/api-gateway/common/secrets.go | 57 +++++++++- .../api-gateway/common/secrets_test.go | 105 ++++++++++++++++++ .../api-gateway/common/translation.go | 14 ++- 7 files changed, 207 insertions(+), 22 deletions(-) create mode 100644 .changelog/2478.txt create mode 100644 control-plane/api-gateway/common/secrets_test.go diff --git a/.changelog/2478.txt b/.changelog/2478.txt new file mode 100644 index 0000000000..ccbbb71ec8 --- /dev/null +++ b/.changelog/2478.txt @@ -0,0 +1,5 @@ +```release-note:bug +api-gateway: fixes bug where envoy will silently reject RSA keys less than 2048 bits in length when not in FIPS mode, and +will reject keys that are not 2048, 3072, or 4096 bits in length in FIPS mode. We now validate +and reject invalid certs earlier. +``` diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index bb30b98f52..7366d1a164 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -15,7 +15,6 @@ import ( logrtest "github.com/go-logr/logr/testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,6 +25,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" ) func init() { @@ -2331,7 +2331,7 @@ func controlledBinder(config BinderConfig) BinderConfig { } func generateTestCertificate(t *testing.T, namespace, name string) (*api.InlineCertificateConfigEntry, corev1.Secret) { - privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + privateKey, err := rsa.GenerateKey(rand.Reader, common.MinKeyLength) require.NoError(t, err) usage := x509.KeyUsageCertSign diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index 3ccb645c32..fd2eaca829 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -218,15 +218,17 @@ var ( // to the ListenerConditionReason given in the spec. If a reason is overloaded and can // be used with two different types of things (i.e. something is not found or it's not supported) // then we distinguish those two usages with errListener*_Usage. - errListenerUnsupportedProtocol = errors.New("listener protocol is unsupported") - errListenerPortUnavailable = errors.New("listener port is unavailable") - errListenerHostnameConflict = errors.New("listener hostname conflicts with another listener") - errListenerProtocolConflict = errors.New("listener protocol conflicts with another listener") - errListenerInvalidCertificateRef_NotFound = errors.New("certificate not found") - errListenerInvalidCertificateRef_NotSupported = errors.New("certificate type is not supported") - errListenerInvalidCertificateRef_InvalidData = errors.New("certificate is invalid or does not contain a supported server name") - errListenerInvalidRouteKinds = errors.New("allowed route kind is invalid") - errListenerProgrammed_Invalid = errors.New("listener cannot be programmed because it is invalid") + errListenerUnsupportedProtocol = errors.New("listener protocol is unsupported") + errListenerPortUnavailable = errors.New("listener port is unavailable") + errListenerHostnameConflict = errors.New("listener hostname conflicts with another listener") + errListenerProtocolConflict = errors.New("listener protocol conflicts with another listener") + errListenerInvalidCertificateRef_NotFound = errors.New("certificate not found") + errListenerInvalidCertificateRef_NotSupported = errors.New("certificate type is not supported") + errListenerInvalidCertificateRef_InvalidData = errors.New("certificate is invalid or does not contain a supported server name") + errListenerInvalidCertificateRef_NonFIPSRSAKeyLen = errors.New("certificate has an invalid length: RSA Keys must be at least 2048-bit") + errListenerInvalidCertificateRef_FIPSRSAKeyLen = errors.New("certificate has an invalid length: RSA keys must be either 2048-bit, 3072-bit, or 4096-bit in FIPS mode") + errListenerInvalidRouteKinds = errors.New("allowed route kind is invalid") + errListenerProgrammed_Invalid = errors.New("listener cannot be programmed because it is invalid") // Below is where any custom generic listener validation errors should go. // We map anything under here to a custom ListenerConditionReason of Invalid on @@ -372,7 +374,7 @@ func (l listenerValidationResult) resolvedRefsCondition(generation int64) metav1 } switch l.refErr { - case errListenerInvalidCertificateRef_NotFound, errListenerInvalidCertificateRef_NotSupported, errListenerInvalidCertificateRef_InvalidData: + case errListenerInvalidCertificateRef_NotFound, errListenerInvalidCertificateRef_NotSupported, errListenerInvalidCertificateRef_InvalidData, errListenerInvalidCertificateRef_NonFIPSRSAKeyLen, errListenerInvalidCertificateRef_FIPSRSAKeyLen: return metav1.Condition{ Type: "ResolvedRefs", Status: metav1.ConditionFalse, diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index 0e17e2b306..a57cf598a4 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -6,9 +6,6 @@ package binding import ( "strings" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" klabels "k8s.io/apimachinery/pkg/labels" @@ -17,6 +14,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" 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" + "github.com/hashicorp/consul-k8s/control-plane/version" + "github.com/hashicorp/consul/api" ) var ( @@ -205,10 +207,20 @@ func validateTLS(gateway gwv1beta1.Gateway, tls *gwv1beta1.GatewayTLSConfig, res } func validateCertificateData(secret corev1.Secret) error { - _, _, err := common.ParseCertificateData(secret) + _, privateKey, err := common.ParseCertificateData(secret) if err != nil { return errListenerInvalidCertificateRef_InvalidData } + + err = common.ValidateKeyLength(privateKey) + if err != nil { + if version.IsFIPS() { + return errListenerInvalidCertificateRef_FIPSRSAKeyLen + } + + return errListenerInvalidCertificateRef_NonFIPSRSAKeyLen + } + return nil } @@ -235,7 +247,7 @@ func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener _, supported := supportedKindsForProtocol[listener.Protocol] if !supported { result.acceptedErr = errListenerUnsupportedProtocol - } else if listener.Port == 20000 { //admin port + } else if listener.Port == 20000 { // admin port result.acceptedErr = errListenerPortUnavailable } diff --git a/control-plane/api-gateway/common/secrets.go b/control-plane/api-gateway/common/secrets.go index f7e6064d9f..1b7d8dec33 100644 --- a/control-plane/api-gateway/common/secrets.go +++ b/control-plane/api-gateway/common/secrets.go @@ -12,6 +12,14 @@ import ( "github.com/miekg/dns" corev1 "k8s.io/api/core/v1" + + "github.com/hashicorp/consul-k8s/control-plane/version" +) + +var ( + errFailedToParsePrivateKeyPem = errors.New("failed to parse private key PEM") + errKeyLengthTooShort = errors.New("RSA key length must be at least 2048-bit") + errKeyLengthTooShortFIPS = errors.New("RSA key length must be at either 2048-bit, 3072-bit, or 4096-bit in FIPS mode") ) func ParseCertificateData(secret corev1.Secret) (cert string, privateKey string, err error) { @@ -20,7 +28,7 @@ func ParseCertificateData(secret corev1.Secret) (cert string, privateKey string, privateKeyBlock, _ := pem.Decode(decodedPrivateKey) if privateKeyBlock == nil { - return "", "", errors.New("failed to parse private key PEM") + return "", "", errFailedToParsePrivateKeyPem } certificateBlock, _ := pem.Decode(decodedCertificate) @@ -66,3 +74,50 @@ func validateCertificateHosts(certificate *x509.Certificate) error { return nil } + +// Envoy will silently reject any keys that are less than 2048 bytes long +// https://github.com/envoyproxy/envoy/blob/main/source/extensions/transport_sockets/tls/context_impl.cc#L238 +const MinKeyLength = 2048 + +// ValidateKeyLength ensures that the key length for a certificate is of a valid length +// for envoy dependent on if consul is running in FIPS mode or not. +func ValidateKeyLength(privateKey string) error { + privateKeyBlock, _ := pem.Decode([]byte(privateKey)) + + if privateKeyBlock == nil { + return errFailedToParsePrivateKeyPem + } + + if privateKeyBlock.Type != "RSA PRIVATE KEY" { + return nil + } + + key, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes) + if err != nil { + return err + } + + keyBitLen := key.N.BitLen() + + if version.IsFIPS() { + return fipsLenCheck(keyBitLen) + } + + return nonFipsLenCheck(keyBitLen) +} + +func nonFipsLenCheck(keyLen int) error { + // ensure private key is of the correct length + if keyLen < MinKeyLength { + return errKeyLengthTooShort + } + + return nil +} + +func fipsLenCheck(keyLen int) error { + if keyLen != 2048 && keyLen != 3072 && keyLen != 4096 { + return errKeyLengthTooShortFIPS + } + return nil +} diff --git a/control-plane/api-gateway/common/secrets_test.go b/control-plane/api-gateway/common/secrets_test.go new file mode 100644 index 0000000000..d5a2578b5e --- /dev/null +++ b/control-plane/api-gateway/common/secrets_test.go @@ -0,0 +1,105 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidateKeyLength(t *testing.T) { + tooShortPrivateKey := `-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCtmK1VjmXJ7vm4CZkkOSjc+kjGNMlyce5rXxwlDRz9LcGGc3Tg +kwUJesyBpDtxLLVHXQIPr5mWYbX/W/ezQ9sntxrATbDek8pBgoOlARebwkD2ivVW +BWfVhlryVihWlXApKiJ2n3i0m+OVtdrceC9Bv2hEMhYVOwzxtb3O0YFkbwIDAQAB +AoGAIxgnipFUEKPIRiVimUkY8ruCdNd9Fi7kNT6wEOl6v9A9PHIg4bm3Hfh+WYMb +JUEVkMzDuuoUEavFQE+WXt5L8oE1lEBmN2++FQsvllN+MRBTRg2sfw4mUWDI6S4r +h8+XNTzTIg2sUd2J3o2qNmQoOheYb+iuYDj76IFoEdwwZ0kCQQDYKKs5HAbnrLj1 +UrOp8TyHdFf0YNw5tGdbNTbffq4rlBD6SW70+Sj624i2UqdnYwRiWzdXv3zN08aI +Vfoh2cGlAkEAzZe5B6BhiX/PcIYutMtuT3K+mysFNlowrutXWoQOpR7gGAkgEt6e +oCDgx1QJRjsp6NFQxKc6l034Hzs17gqJgwJAcu9U873aUg9+HTuHOoKB28haCCAE +mU46cr3d2oKCW7uUN3EaZXmid5iJneBfENMOfrnfuHGiC9NiShXlNWCS3QJAO5Ne +w83+1ahaxUGs4SkeExmuECrcPM7P0rBRxOIFmGWlDHIAgFdQYhiE6l34vghA8b1O +CV5oRRYL84jl7M/S3wJBALDfL5YXcc8P6scLJJ1biqhLYppvGN5CUwbsJsluvHCW +XCTVIbPOaS42A0xUfpoiTcdbNSFRvdCzPR5nsGy8Y7g= +-----END RSA PRIVATE KEY-----` + validPrivateKey := `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAzVKRcYlTHHPjPbCieOFIUT2hCouRYe4N8ZhNrSpZf/BAAn4M +d/LWn/9OrLagbxrRF6cWdWGNEI2COnBRLgNVxyPXneaHaYFqOBRi9GWhuD3sw1jn +7gf4/m/AVO8cu2JYjEX+s9RjSRzpjx+4nhit46bGNUyb9qUeQwoBidAzOSmU8nHY +y3LpuuzkjS3FEyNXHxqgpTJnV4ytx8YGkPnG92GBAlrZnr4Eclv0/Sq6OViTpeuh +z8noNkbugYWHMXGlTZ4lPnELJW2fx/HIpD2ovOO3X8XYBo5KDzs9qyKzDgIOMZLF +i/qLCLHgfosb4TMaXCeVu4fA7Y47jtGOO4mbiwIDAQABAoIBAFhicDibIDtRyaLv +K+l0NPC/4liLPwCUfM0gvmNKJS/VSICqKQzjbK+ANCpWDVb2iMaxRxItdY+IEuS8 +H736cozgaXtP1r+8lXBhmj1RmJ2ajpaC6YgGR5GjonwNWGVzjuGHaf6YcUryVrol +MhBgWE50psMf4M16Q74hCwt7o+k5Lz55xKasgc9dtSnvyCupPBwrOT+d55C1P2Wn +2oebWM4WKtCZIgvlvZrt4xQkGWy9qloxL6V1F67ZbizAyFMZUMmJv+4/whF8tmXi +aydleL64K23ZSK1pM/x0JI+7qo0GpEoA4k+2fdmh5dAOM0TrXhV5Kv01efLIaITT +s7lYjG0CgYEA4qGIM7qO3e9fHgSK/9UdxnpL/1OvfYATBMhEtR46sAxmKQGC8fTM +iTBkmLAKn3zBgDghCbygPIQjex+W+Ra7JkQIcGB6KLR8rr5GkOuF6vkqHV93RQRT +lT/1quqq3fVH6V4ymifKJCDNg0IEPcmo+M8RnXBgpFsCN4b5UyjXNScCgYEA5+4h +LITPJxGytlWzwtsy44U2PvafJYJCktW+LYqhk3xzz4qWX5ubmPz18LrEyybgcy/W +Dm4JCu+TOS2gvf2WbJKR/tKdgRN7dkU/dbgMtRL8QW5ir+5qqRITYOhiSZPIOpbP +5zg+c/ZvmK/t5h35/8l7b0bu/E1FOEF27ADpzP0CgYEArqch2gup0muI+A80N9i7 +q5vQOaL6mVM8VPEp0hLL06Sajnt1uJWZkxhSTkFMzoBMd03KWECflEOZPGep56iW +7fR8NG6Fdh0yAVDt/P0lJWKEDELoHa4p49l4sBFNQOSoWLaZdKe5ZoJJHyCfOCbT +K3wY7SYPtFnWqYhBWM8emv0CgYBdrNqNRp78orNR3c+bNjmZl6ZPTAD/f1swP1Bu +yH12Ol/0RX9y4kC4TANx1Z3Ch9ND8uA8N8lDN3x5Laqs0g29kH2TNLIU/i9xl4qI +G2xWfnKQYutNL7i4zOoyy+lW2m+W6m7Sbu8am0B7pSMrPJRK8a//Q+Em2nbIv/gu +XjgQaQKBgHKZUKkMv597vpAjgTNsKIl5RDFONBq3omnAwlK9EDLVeAxIrvrvMHBW +H/ZMFpSGp1eQgKyu1xkEqGdkYXx7BKtdTHK+Thqif2ZGWczy5rVSAIsBYDo1DGE2 +wbocWxkWNb5o2ZZtis5lTB6nr9EWo0zyaPqIh0pfjqVEES2YDEx6 +-----END RSA PRIVATE KEY-----` + nonTraditionalRSAKey := `-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCcrB9oNKLtzA3Q +02KDgtsnrxns7vJ5aCkjJCm/h0Ju7a2mel5YHSN5iLlU5oTMJVIMpWlW9E8P76/a +GLGMNfSBRVJdfW71iks/ddp4SjpDe9Bo+aY2snrR2/AP7eQepVNjFbg4YLQqvENh +05k1FuuP1/AgGVNn0kGEwzKxz35shmhRKBCvaRaHLz/fdkDIeIrVLON4FnmAmpOZ +AztZCwAZc6HZfj8Nh9Wlaw6Dg2boIgxTU160pwpX+nUxcJ9M5sUP9DBuNL0Mdrqi +U+R49uqG/5ssSk+xVik3q+WF+XySJ6H21fttWDJS2OTm/Nx/wHlBC73mthbA0emB +rkiBy9SBAgMBAAECggEAOhybz6aKcmKYE0d8yGPejwMjPh9JH+ATNh4hQBHXAdc1 +7ESCPvOb52XfvE5+nkwPeXJXNrIKq1IPq3kyTdvrc5F3Ygb3A6tGiuTXYnvBzasc +m/tRfANKjBGkovvte7J90ghJ2tt/qERJR/1Y2/jC6glB314VcjJqK+jNImfgsDa7 +1r47efKG7B5eUGvhQDTpL5ENXKxIdvCghHrLqj19QGUZ5MbXsEYrso0lxKw2Xk39 +uM8p3WTxIy0LQGyCm+FYlJ7r61tm7tUOGuNT0YiptVavIw1QPgIbRWdS2gnJu3+J +kHS0vu6AW1fJav48TA9hXcIQR70alrJA2VVqsvQouwKBgQDNs96l8BfWD6s/urIw +yzC3/VZPLFJ3BlxvkdP1UDC0S+7pgQ6qdEmJg0z5IfYzDB1PK2X/DS/70JA1LRSS +MRmjQGHCYIp9g8EqmABwfKf4YnN53KPRyR8Yq1pwaq7wKowtW+5GH95qQPINZsNO +J21AENEzq7IoB4gpM3tIaX73YwKBgQDC+yl5JvoV7e6FIpFrwL62aKrWmpidML/G +stdrg9ylCSM9SIVFINMhmFPicW1+DrkQ5HRV7DG//ZcOZNbbNmSu32PVcQI1MJgQ +rkMZ3ukUURnlvQYOEmZY4zHzTJ+jcw6kEH/+b47Bv13PpD7ZqA4/28dpU9wi9gt3 ++GiSnkKDywKBgHqjr63dPEjapK3lQFHJAu3fM7MWaMAf4cJ+/hD202LbFsDOuhC0 +Lhe3WY/7SI7cvSizZicvFJmcmi2qB+a1MWTcgKxj5I26nNMpNrHaEEcNY22XN3Be +6ZRKrSvy3wO/Sj3M3n2eiHtu5yFIUE7rQL5+iEu3JQuqmep+kBT3GMSjAoGAP77B +VlyJ0nWRT3F3vZSsRRJ/F94/GtT/PcTmbL4Vetc78CMvfuQ2YntcoWGX/Ghv1Lf7 +2MN5mF0d75TEMbLcw9dA2l0x7ZXPgVSXl3OrG/tPzi44No2JbHIKuJJKdrN9C+Jh +Fhv+vhUEZIg8DAjHb9U4opTKGZv7L+PEvHqFIHUCgYBTB2TxTgEMNZSsRwrhQRMh +tsz5rS2MoTgzk4BlSsv6xVC4GnBJ2HlNAjYEsBEg50zCCTPlZXcsNjrAxFrwWhLJ +DjN2iMsYFz4WHS94W5UYl6/35ye25KsHuS9vnNeidhFAvYgC1nIkh4mFhLoSeSCG +GODy2KwC2ssLuUHb6WoJ6A== +-----END PRIVATE KEY-----` + + testCases := map[string]struct { + key string + expectedError error + }{ + "key is RSA and of the correct length": { + key: validPrivateKey, + expectedError: nil, + }, + "key is RSA and too short": { + key: tooShortPrivateKey, + expectedError: errKeyLengthTooShort, + }, + "key is non-traditional RSA key": { + key: nonTraditionalRSAKey, + expectedError: nil, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + err := ValidateKeyLength(tc.key) + require.ErrorIs(t, err, tc.expectedError) + }) + } +} diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index 55bfb29a2f..94241eed22 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -6,14 +6,15 @@ package common import ( "strings" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul/api" ) // ResourceTranslator handles translating K8s resources into Consul config entries. @@ -340,6 +341,11 @@ func (t ResourceTranslator) ToInlineCertificate(secret corev1.Secret) (*api.Inli return nil, err } + err = ValidateKeyLength(privateKey) + if err != nil { + return nil, err + } + namespace := t.Namespace(secret.Namespace) return &api.InlineCertificateConfigEntry{ From ced0ae836a6588c0c5b09f4016f07bc8704bb2fd Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:07:23 -0700 Subject: [PATCH 261/592] add changelog for 1.2.0 dataplane and consul 1.16.0 (#2496) * add changelog for Consul 1.16.0 * add changelog for dataplane 1.2.0 --- .changelog/2476.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/2476.txt diff --git a/.changelog/2476.txt b/.changelog/2476.txt new file mode 100644 index 0000000000..e57889cabe --- /dev/null +++ b/.changelog/2476.txt @@ -0,0 +1,7 @@ +```release-note:improvement +helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.2.0` +``` + +```release-note:improvement +helm: update `image` value to `hashicorp/consul:1.16.0` +``` \ No newline at end of file From 736649d9d81334df1bddbfd8e4d04d66b719577b Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Fri, 30 Jun 2023 14:02:16 -0400 Subject: [PATCH 262/592] Adds chanelog values for 0.49.7 (#2501) --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cf1d2084f..445e0f1816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 0.49.7 (June 28, 2023) +BREAKING CHANGES: + +* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] + +SECURITY: + +* Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.2`. [[GH-2204](https://github.com/hashicorp/consul-k8s/issues/2204)] +* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] + +FEATURES: + +* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] + +IMPROVEMENTS: + +* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2265](https://github.com/hashicorp/consul-k8s/issues/2265)] +* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] + +BUG FIXES: + +* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] +* crd: fix bug on service intentions CRD causing some updates to be ignored. [[GH-2194](https://github.com/hashicorp/consul-k8s/issues/2194)] + ## 1.1.2 (June 5, 2023) SECURITY: From 30e9f55ff36085dfa35df822b1055f6a9d6f6d36 Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Mon, 3 Jul 2023 10:36:25 -0700 Subject: [PATCH 263/592] ci: fix eks terraform quota error by cleaning up oidc providers (#2470) cleans up oidc providers older than 8 hours. --- hack/aws-acceptance-test-cleanup/main.go | 125 +++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/hack/aws-acceptance-test-cleanup/main.go b/hack/aws-acceptance-test-cleanup/main.go index d62e5e4405..e4094ec47e 100644 --- a/hack/aws-acceptance-test-cleanup/main.go +++ b/hack/aws-acceptance-test-cleanup/main.go @@ -25,6 +25,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/elb" + "github.com/aws/aws-sdk-go/service/iam" "github.com/cenkalti/backoff/v4" ) @@ -38,6 +39,11 @@ var ( errNotDestroyed = errors.New("not yet destroyed") ) +type oidc struct { + arn string + buildUrl string +} + func main() { flag.BoolVar(&flagAutoApprove, "auto-approve", false, "Skip interactive approval before destroying.") flag.Parse() @@ -68,6 +74,106 @@ func realMain(ctx context.Context) error { eksClient := eks.New(clientSession, awsCfg) ec2Client := ec2.New(clientSession, awsCfg) elbClient := elb.New(clientSession, awsCfg) + iamClient := iam.New(clientSession, awsCfg) + + // Find OIDC providers to delete. + oidcProvidersOutput, err := iamClient.ListOpenIDConnectProvidersWithContext(ctx, &iam.ListOpenIDConnectProvidersInput{}) + if err != nil { + return err + } else if oidcProvidersOutput == nil { + return fmt.Errorf("nil output for OIDC Providers") + } + + toDeleteOidcArns := []*oidc{} + for _, providerEntry := range oidcProvidersOutput.OpenIDConnectProviderList { + arnString := "" + if providerEntry.Arn != nil { + arnString = *providerEntry.Arn + } + // Check if it's older than 8 hours. + older, err := oidcOlderThanEightHours(ctx, iamClient, providerEntry.Arn) + if err != nil { + return err + } + // Only add to delete list if it's older than 8 hours and has a buildURL tag. + if older { + output, err := iamClient.ListOpenIDConnectProviderTags(&iam.ListOpenIDConnectProviderTagsInput{OpenIDConnectProviderArn: providerEntry.Arn}) + if err != nil { + return err + } + for _, tag := range output.Tags { + if tag.Key != nil && *tag.Key == buildURLTag { + var buildUrl string + if tag.Value != nil { + buildUrl = *tag.Value + } + toDeleteOidcArns = append(toDeleteOidcArns, &oidc{arn: arnString, buildUrl: buildUrl}) + } + } + } else { + fmt.Printf("Skipping OIDC provider: %s because it's not over 8 hours old\n", arnString) + } + } + + oidcProvidersExist := true + if len(toDeleteOidcArns) == 0 { + fmt.Println("Found no OIDC Providers to clean up") + oidcProvidersExist = false + } else { + // Print out the OIDC Provider arns and the build tags. + var oidcPrint string + for _, oidcProvider := range toDeleteOidcArns { + oidcPrint += fmt.Sprintf("- %s (%s)\n", oidcProvider.arn, oidcProvider.buildUrl) + } + + fmt.Printf("Found OIDC Providers:\n%s", oidcPrint) + } + + // Check for approval. + if !flagAutoApprove && oidcProvidersExist { + type input struct { + text string + err error + } + inputCh := make(chan input) + + // Read input in a goroutine so we can also exit if we get a Ctrl-C + // (see select{} below). + go func() { + reader := bufio.NewReader(os.Stdin) + fmt.Println("\nDo you want to delete these OIDC Providers (y/n)?") + inputStr, err := reader.ReadString('\n') + if err != nil { + inputCh <- input{err: err} + return + } + inputCh <- input{text: inputStr} + }() + + select { + case in := <-inputCh: + if in.err != nil { + return in.err + } + inputTrimmed := strings.TrimSpace(in.text) + if inputTrimmed != "y" && inputTrimmed != "yes" { + return errors.New("exiting after negative") + } + case <-ctx.Done(): + return errors.New("context cancelled") + } + } + + // Actually delete the OIDC providers. + for _, oidcArn := range toDeleteOidcArns { + fmt.Printf("Deleting OIDC provider: %s\n", oidcArn.arn) + _, err := iamClient.DeleteOpenIDConnectProviderWithContext(ctx, &iam.DeleteOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: &oidcArn.arn, + }) + if err != nil { + return err + } + } // Find VPCs to delete. Most resources we create belong to a VPC, except // for IAM resources, and so if there are no VPCs, that means all leftover resources have been deleted. @@ -537,6 +643,25 @@ func realMain(ctx context.Context) error { return nil } +// oidcOlderThanEightHours checks if the oidc provider is older than 8 hours. +func oidcOlderThanEightHours(ctx context.Context, iamClient *iam.IAM, oidcArn *string) (bool, error) { + fullOidc, err := iamClient.GetOpenIDConnectProviderWithContext(ctx, &iam.GetOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: oidcArn, + }) + if err != nil { + return false, err + } + if fullOidc != nil { + if fullOidc.CreateDate != nil { + d := time.Since(*fullOidc.CreateDate) + if d.Hours() > 8 { + return true, nil + } + } + } + return false, nil +} + func vpcNameAndBuildURL(vpc *ec2.Vpc) (string, string) { var vpcName string var buildURL string From 1161322e494509ca2642811d88c92b898bb7a4d4 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 6 Jul 2023 12:18:33 -0400 Subject: [PATCH 264/592] build: update versions to 1.3.0-dev (#2511) --- charts/consul/Chart.yaml | 12 ++++++------ charts/consul/values.yaml | 4 ++-- cli/version/version.go | 2 +- control-plane/version/version.go | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index 64d7ed4ed0..de8e6e9df6 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -3,8 +3,8 @@ apiVersion: v2 name: consul -version: 1.2.0-dev -appVersion: 1.16-dev +version: 1.3.0-dev +appVersion: 1.17-dev kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -16,13 +16,13 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.16-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.17-dev - name: consul-k8s-control-plane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.0-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.3.0-dev - name: consul-dataplane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.2-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.3-dev - name: envoy - image: envoyproxy/envoy:v1.25.1 + image: envoyproxy/envoy:v1.26.2 artifacthub.io/license: MPL-2.0 artifacthub.io/links: | - name: Documentation diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index ad1f829399..6d8eca9d0d 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-enterprise:1.16-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.17-dev # 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.2.0-dev + imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.3.0-dev # The name of the datacenter that the agents should # register as. This can't be changed once the Consul cluster is up and running diff --git a/cli/version/version.go b/cli/version/version.go index 320600c30b..9cde4a2d66 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -17,7 +17,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.2.0" + Version = "1.3.0" // 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 diff --git a/control-plane/version/version.go b/control-plane/version/version.go index 320600c30b..9cde4a2d66 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -17,7 +17,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.2.0" + Version = "1.3.0" // 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 From cbcbdc591901a7e5d4183fe1676a0bb363edbb9b Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Fri, 7 Jul 2023 16:09:44 -0700 Subject: [PATCH 265/592] [COMPLIANCE] Add Copyright and License Headers (#2507) Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- control-plane/api-gateway/common/secrets_test.go | 3 +++ .../controllers/gateway_controller_integration_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/control-plane/api-gateway/common/secrets_test.go b/control-plane/api-gateway/common/secrets_test.go index d5a2578b5e..223e8aa24e 100644 --- a/control-plane/api-gateway/common/secrets_test.go +++ b/control-plane/api-gateway/common/secrets_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import ( diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go index 35b3c64652..2605991238 100644 --- a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go +++ b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controllers import ( From 0cb24d70e70b370599ba4e28addf003240ee2d70 Mon Sep 17 00:00:00 2001 From: David Yu Date: Mon, 10 Jul 2023 08:27:27 -0700 Subject: [PATCH 266/592] values.yaml - replace connect with service mesh for some instances (#2516) * fix connect/service mesh * Update values.yaml --- charts/consul/values.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 6d8eca9d0d..16b641d5cb 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -805,11 +805,11 @@ server: # @type: string storageClass: null - # This will enable/disable [Connect](https://developer.hashicorp.com/consul/docs/connect). Setting this to true + # This will enable/disable [service mesh](https://developer.hashicorp.com/consul/docs/connect). Setting this to true # _will not_ automatically secure pod communication, this # setting will only enable usage of the feature. Consul will automatically initialize - # a new CA and set of certificates. Additional Connect settings can be configured - # by setting the `server.extraConfig` value. + # a new CA and set of certificates. Additional service mesh settings can be configured + # by setting the `server.extraConfig` value or by applying [configuration entries](https://developer.hashicorp.com/consul/docs/connect/config-entries). connect: true serviceAccount: @@ -1611,7 +1611,7 @@ dns: # @type: boolean enabled: "-" - # If true, services using Consul Connect will use Consul DNS + # If true, services using Consul service mesh will use Consul DNS # for default DNS resolution. The DNS lookups fall back to the nameserver IPs # listed in /etc/resolv.conf if not found in Consul. # @type: boolean @@ -2264,7 +2264,7 @@ connectInject: # @type: map meta: null - # Configures metrics for Consul Connect services. All values are overridable + # Configures metrics for Consul service mesh services. All values are overridable # via annotations on a per-pod basis. metrics: # If true, the connect-injector will automatically @@ -2414,7 +2414,7 @@ connectInject: # annotated. Use `["*"]` to automatically allow all k8s namespaces. # # For example, `["namespace1", "namespace2"]` will only allow pods in the k8s - # namespaces `namespace1` and `namespace2` to have Connect sidecars injected + # namespaces `namespace1` and `namespace2` to have Consul service mesh sidecars injected # and registered with Consul. All other k8s namespaces will be ignored. # # To deny all namespaces, set this to `[]`. @@ -2599,7 +2599,7 @@ connectInject: # [Mesh Gateways](https://developer.hashicorp.com/consul/docs/connect/gateways/mesh-gateway) enable Consul Connect to work across Consul datacenters. meshGateway: # If [mesh gateways](https://developer.hashicorp.com/consul/docs/connect/gateways/mesh-gateway) are enabled, a Deployment will be created that runs - # gateways and Consul Connect will be configured to use gateways. + # gateways and Consul service mesh will be configured to use gateways. # This setting is required for [Cluster Peering](https://developer.hashicorp.com/consul/docs/connect/cluster-peering/k8s). # Requirements: consul 1.6.0+ if using `global.acls.manageSystemACLs``. enabled: false From 6624d34b9f1a4d0f12cef156c5f35152ee3e08b5 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 10 Jul 2023 12:18:38 -0400 Subject: [PATCH 267/592] docs: self service changelog instructions (#2526) --- .github/pull_request_template.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b615e69dd1..f5d211b137 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,7 +9,6 @@ How I expect reviewers to test this PR: Checklist: - [ ] Tests added -- [ ] CHANGELOG entry added - > HashiCorp engineers only, community PRs should not add a changelog entry. - > Entries should use present tense (e.g. Add support for...) +- [ ] [CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) + From 11a18515e988187dfb905a878a53eca04819b60a Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 10 Jul 2023 13:56:46 -0400 Subject: [PATCH 268/592] feat: adding security context and annotations to tls and acl init/cleanup jobs (#2525) * feat: adding security context and annotations to tls and acl init/cleanup jobs * changelog --------- Co-authored-by: Chinikins --- .changelog/2525.txt | 3 ++ .../consul/templates/gateway-cleanup-job.yaml | 3 ++ .../templates/gateway-resources-job.yaml | 3 ++ .../server-acl-init-cleanup-job.yaml | 7 +++ .../consul/templates/server-acl-init-job.yaml | 7 +++ .../templates/tls-init-cleanup-job.yaml | 7 +++ charts/consul/templates/tls-init-job.yaml | 7 +++ .../consul/test/unit/gateway-cleanup-job.bats | 24 ++++++++- .../test/unit/gateway-resources-job.bats | 25 ++++++++- .../unit/server-acl-init-cleanup-job.bats | 39 ++++++++++++++ .../consul/test/unit/server-acl-init-job.bats | 52 ++++++++++++++++--- .../test/unit/tls-init-cleanup-job.bats | 40 ++++++++++++++ charts/consul/test/unit/tls-init-job.bats | 39 ++++++++++++++ charts/consul/values.yaml | 32 ++++++++++++ 14 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 .changelog/2525.txt diff --git a/.changelog/2525.txt b/.changelog/2525.txt new file mode 100644 index 0000000000..74a2cd596e --- /dev/null +++ b/.changelog/2525.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. +``` diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index 44f032b5fd..8731aaed81 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -31,6 +31,9 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + {{- if .Values.global.acls.annotations }} + {{- tpl .Values.global.acls.annotations . | nindent 8 }} + {{- end }} spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gateway-cleanup diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 441e64eb14..5fcd96cad3 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -31,6 +31,9 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + {{- if .Values.global.acls.annotations }} + {{- tpl .Values.global.acls.annotations . | nindent 8 }} + {{- end }} spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gateway-resources diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 38ecfcf1b0..4d0aa8f05d 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -47,9 +47,16 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + {{- if .Values.global.acls.annotations }} + {{- tpl .Values.global.acls.annotations . | nindent 8 }} + {{- end }} spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-server-acl-init-cleanup + {{- if .Values.server.containerSecurityContext.aclInit }} + securityContext: + {{- toYaml .Values.server.containerSecurityContext.aclInit | nindent 8 }} + {{- end }} containers: - name: server-acl-init-cleanup image: {{ .Values.global.imageK8S }} diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index 27272d0f76..6625e23b38 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -46,6 +46,9 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + {{- if .Values.global.acls.annotations }} + {{- tpl .Values.global.acls.annotations . | nindent 8 }} + {{- end }} {{- if .Values.global.secretsBackend.vault.enabled }} {{- /* Run the Vault agent as both an init container and sidecar. @@ -94,6 +97,10 @@ spec: spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-server-acl-init + {{- if .Values.server.containerSecurityContext.aclInit }} + securityContext: + {{- toYaml .Values.server.containerSecurityContext.aclInit | nindent 8 }} + {{- end }} {{- if (or .Values.global.tls.enabled .Values.global.acls.replicationToken.secretName .Values.global.acls.bootstrapToken.secretName) }} volumes: {{- if and .Values.global.tls.enabled (not .Values.global.secretsBackend.vault.enabled) }} diff --git a/charts/consul/templates/tls-init-cleanup-job.yaml b/charts/consul/templates/tls-init-cleanup-job.yaml index ba29bb84ae..69b5a30849 100644 --- a/charts/consul/templates/tls-init-cleanup-job.yaml +++ b/charts/consul/templates/tls-init-cleanup-job.yaml @@ -35,9 +35,16 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + {{- if .Values.global.tls.annotations }} + {{- tpl .Values.global.tls.annotations . | nindent 8 }} + {{- end }} spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-tls-init-cleanup + {{- if .Values.server.containerSecurityContext.tlsInit }} + securityContext: + {{- toYaml .Values.server.containerSecurityContext.tlsInit | nindent 8 }} + {{- end }} containers: - name: tls-init-cleanup image: "{{ .Values.global.image }}" diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index d002ae7a75..5839f07dbf 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -35,9 +35,16 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + {{- if .Values.global.tls.annotations }} + {{- tpl .Values.global.tls.annotations . | nindent 8 }} + {{- end }} spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-tls-init + {{- if .Values.server.containerSecurityContext.tlsInit }} + securityContext: + {{- toYaml .Values.server.containerSecurityContext.tlsInit | nindent 8 }} + {{- end }} {{- if (and .Values.global.tls.caCert.secretName .Values.global.tls.caKey.secretName) }} volumes: - name: consul-ca-cert diff --git a/charts/consul/test/unit/gateway-cleanup-job.bats b/charts/consul/test/unit/gateway-cleanup-job.bats index ff59768c75..657bf4a791 100644 --- a/charts/consul/test/unit/gateway-cleanup-job.bats +++ b/charts/consul/test/unit/gateway-cleanup-job.bats @@ -18,6 +18,28 @@ target=templates/gateway-cleanup-job.yaml assert_empty helm template \ -s $target \ --set 'connectInject.enabled=false' \ - . + . } + +#-------------------------------------------------------------------- +# annotations + +@test "gatewaycleanup/Job: no annotations defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "gatewaycleanup/Job: annotations can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set 'global.acls.annotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats index 0d3bfa2e4d..27bba00ed5 100644 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ b/charts/consul/test/unit/gateway-resources-job.bats @@ -18,7 +18,7 @@ target=templates/gateway-resources-job.yaml assert_empty helm template \ -s $target \ --set 'connectInject.enabled=false' \ - . + . } @test "gatewayresources/Job: imageK8S set properly" { @@ -116,3 +116,26 @@ target=templates/gateway-resources-job.yaml local actual=$(echo "$spec" | jq '.[14]') [ "${actual}" = "\"-service-annotations=- bingo\"" ] } + + +#-------------------------------------------------------------------- +# annotations + +@test "gatewayresources/Job: no annotations defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "gatewayresources/Job: annotations can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set 'global.acls.annotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/server-acl-init-cleanup-job.bats b/charts/consul/test/unit/server-acl-init-cleanup-job.bats index bf6a455d0e..c886b2ec51 100644 --- a/charts/consul/test/unit/server-acl-init-cleanup-job.bats +++ b/charts/consul/test/unit/server-acl-init-cleanup-job.bats @@ -183,3 +183,42 @@ load _helpers yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) [ "${actual}" = "bar" ] } + +#-------------------------------------------------------------------- +# server.containerSecurityContext.aclInit + +@test "serverACLInitCleanup/Job: securityContext is set when server.containerSecurityContext.aclInit is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'server.containerSecurityContext.aclInit.runAsUser=100' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.securityContext.runAsUser' | tee /dev/stderr) + + [ "${actual}" = "100" ] +} + +#-------------------------------------------------------------------- +# annotations + +@test "serverACLInitCleanup/Job: no annotations defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "serverACLInitCleanup/Job: annotations can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.annotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index a0d0950e89..17c3e63935 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -590,6 +590,22 @@ load _helpers [ "${actual}" = "key" ] } +#-------------------------------------------------------------------- +# server.containerSecurityContext.aclInit + +@test "serverACLInit/Job: securityContext is set when server.containerSecurityContext.aclInit is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'server.containerSecurityContext.aclInit.runAsUser=100' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.securityContext.runAsUser' | tee /dev/stderr) + + [ "${actual}" = "100" ] + +} + #-------------------------------------------------------------------- # Vault @@ -2030,7 +2046,7 @@ load _helpers --set 'global.cloud.authUrl.secretName=auth-url-name' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] } @@ -2050,7 +2066,7 @@ load _helpers --set 'global.cloud.authUrl.secretKey=auth-url-key' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] } @@ -2070,7 +2086,7 @@ load _helpers --set 'global.cloud.apiHost.secretName=auth-url-name' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] } @@ -2090,7 +2106,7 @@ load _helpers --set 'global.cloud.apiHost.secretKey=auth-url-key' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] } @@ -2110,7 +2126,7 @@ load _helpers --set 'global.cloud.scadaAddress.secretName=scada-address-name' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] } @@ -2130,7 +2146,7 @@ load _helpers --set 'global.cloud.scadaAddress.secretKey=scada-address-key' \ . [ "$status" -eq 1 ] - + [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] } @@ -2226,3 +2242,27 @@ load _helpers yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) [ "${actual}" = "bar" ] } + +#-------------------------------------------------------------------- +# annotations + +@test "serverACLInit/Job: no annotations defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "serverACLInit/Job: annotations can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.annotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/tls-init-cleanup-job.bats b/charts/consul/test/unit/tls-init-cleanup-job.bats index 04b4a2df31..735d991780 100644 --- a/charts/consul/test/unit/tls-init-cleanup-job.bats +++ b/charts/consul/test/unit/tls-init-cleanup-job.bats @@ -119,3 +119,43 @@ load _helpers [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# server.containerSecurityContext.tlsInit + +@test "tlsInitCleanup/Job: securityContext is set when server.containerSecurityContext.tlsInit is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-cleanup-job.yaml \ + --set 'global.tls.enabled=true' \ + --set 'server.containerSecurityContext.tlsInit.runAsUser=100' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.securityContext.runAsUser' | tee /dev/stderr) + + [ "${actual}" = "100" ] +} + + +#-------------------------------------------------------------------- +# annotations + +@test "tlsInitCleanup/Job: no annotations defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-cleanup-job.yaml \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "tlsInitCleanup/Job: annotations can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-cleanup-job.yaml \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.annotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/tls-init-job.bats b/charts/consul/test/unit/tls-init-job.bats index f9294915a5..bf1f84a0a6 100644 --- a/charts/consul/test/unit/tls-init-job.bats +++ b/charts/consul/test/unit/tls-init-job.bats @@ -207,3 +207,42 @@ load _helpers [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# server.containerSecurityContext.tlsInit + +@test "tlsInit/Job: securityContext is set when server.containerSecurityContext.tlsInit is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=true' \ + --set 'server.containerSecurityContext.tlsInit.runAsUser=100' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.securityContext.runAsUser' | tee /dev/stderr) + + [ "${actual}" = "100" ] +} + +#-------------------------------------------------------------------- +# annotations + +@test "tlsInit/Job: no annotations defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "tlsInit/Job: annotations can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.annotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 16b641d5cb..b3f842e429 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -379,6 +379,18 @@ global: # @type: string secretKey: null + # This value defines additional annotations for + # tls init jobs. This should be formatted as a multi-line string. + # + # ```yaml + # annotations: | + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # ``` + # + # @type: string + annotations: null + # [Enterprise Only] `enableConsulNamespaces` indicates that you are running # Consul Enterprise v1.7+ with a valid Consul Enterprise license and would # like to make use of configuration beyond registering everything into @@ -489,6 +501,18 @@ global: # @type: string nodeSelector: null + # This value defines additional annotations for + # acl init jobs. This should be formatted as a multi-line string. + # + # ```yaml + # annotations: | + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # ``` + # + # @type: string + annotations: null + # [Enterprise Only] This value refers to a Kubernetes or Vault secret that you have created # that contains your enterprise license. It is required if you are using an # enterprise binary. Defining it here applies it to your cluster once a leader @@ -877,6 +901,14 @@ server: # @type: map # @recurse: false server: null + # The acl-init job + # @type: map + # @recurse: false + aclInit: null + # The tls-init job + # @type: map + # @recurse: false + tlsInit: null # This value is used to carefully # control a rolling update of Consul server agents. This value specifies the From fb02159cc1b96fab69e702a30f42724b0bd966fe Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:53:01 -0500 Subject: [PATCH 269/592] NET-4813: Fix issue where virtual IP saving had insufficient ACLs. (#2520) Fix issue where virtual IP saving had insufficient ACLs. --- .changelog/2520.txt | 4 +++ .../framework/connhelper/connect_helper.go | 13 +++++++++ .../tests/connect/connect_inject_test.go | 27 +++++++++++++++++++ .../bases/resolver-redirect/intention.yaml | 24 +++++++++++++++++ .../resolver-redirect/kustomization.yaml | 8 ++++++ .../bases/resolver-redirect/resolver.yaml | 10 +++++++ .../bases/resolver-redirect/service.yaml | 15 +++++++++++ .../resolver-redirect/serviceaccount.yaml | 7 +++++ .../kustomization.yaml | 5 ++++ .../subcommand/server-acl-init/rules.go | 1 + .../subcommand/server-acl-init/rules_test.go | 3 +++ 11 files changed, 117 insertions(+) create mode 100644 .changelog/2520.txt create mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/intention.yaml create mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/resolver.yaml create mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/service.yaml create mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/serviceaccount.yaml create mode 100644 acceptance/tests/fixtures/cases/resolver-redirect-virtualip/kustomization.yaml diff --git a/.changelog/2520.txt b/.changelog/2520.txt new file mode 100644 index 0000000000..96d03dc093 --- /dev/null +++ b/.changelog/2520.txt @@ -0,0 +1,4 @@ +```release-note:bug +transparent-proxy: Fix issue where connect-inject lacked sufficient `mesh:write` privileges in some deployments, +which prevented virtual IPs from persisting properly. +``` diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 8695e74d56..d10bf43b1a 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -127,6 +127,19 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { } } +// CreateResolverRedirect creates a resolver that redirects to a static-server, a corresponding k8s service, +// and intentions. This helper is primarly used to ensure that the virtual-ips are persisted to consul properly. +func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { + logger.Log(t, "creating resolver redirect") + options := c.Ctx.KubectlOptions(t) + kustomizeDir := "../fixtures/cases/resolver-redirect-virtualip" + k8s.KubectlApplyK(t, options, kustomizeDir) + + helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, options, kustomizeDir) + }) +} + // TestConnectionFailureWithoutIntention ensures the connection to the static // server fails when no intentions are configured. func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T) { diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index 3f1660319f..199bbf0f1b 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -60,6 +60,33 @@ func TestConnectInject(t *testing.T) { } } +// TestConnectInject_VirtualIPFailover ensures that KubeDNS entries are saved to the virtual IP address table in Consul. +func TestConnectInject_VirtualIPFailover(t *testing.T) { + cfg := suite.Config() + if !cfg.EnableTransparentProxy { + // This can only be tested in transparent proxy mode. + t.SkipNow() + } + ctx := suite.Environment().DefaultContext(t) + + releaseName := helpers.RandomName() + connHelper := connhelper.ConnectHelper{ + ClusterKind: consul.Helm, + Secure: true, + ReleaseName: releaseName, + Ctx: ctx, + Cfg: cfg, + } + + connHelper.Setup(t) + + connHelper.Install(t) + connHelper.CreateResolverRedirect(t) + connHelper.DeployClientAndServer(t) + + k8s.CheckStaticServerConnectionSuccessful(t, connHelper.Ctx.KubectlOptions(t), "static-client", "http://resolver-redirect") +} + // Test the endpoints controller cleans up force-killed pods. func TestConnectInject_CleanupKilledPods(t *testing.T) { for _, secure := range []bool{false, true} { diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/intention.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/intention.yaml new file mode 100644 index 0000000000..faff0cd251 --- /dev/null +++ b/acceptance/tests/fixtures/bases/resolver-redirect/intention.yaml @@ -0,0 +1,24 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions +metadata: + name: client-to-server +spec: + destination: + name: static-server + sources: + - name: static-client + action: allow +--- +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions +metadata: + name: client-to-redirect +spec: + destination: + name: resolver-redirect + sources: + - name: static-client + action: allow \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/kustomization.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/kustomization.yaml new file mode 100644 index 0000000000..323957ad53 --- /dev/null +++ b/acceptance/tests/fixtures/bases/resolver-redirect/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - intention.yaml + - service.yaml + - serviceaccount.yaml + - resolver.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/resolver.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/resolver.yaml new file mode 100644 index 0000000000..9adbcc9fb4 --- /dev/null +++ b/acceptance/tests/fixtures/bases/resolver-redirect/resolver.yaml @@ -0,0 +1,10 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: resolver-redirect +spec: + redirect: + service: static-server \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/service.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/service.yaml new file mode 100644 index 0000000000..e63ae97cca --- /dev/null +++ b/acceptance/tests/fixtures/bases/resolver-redirect/service.yaml @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: Service +metadata: + name: resolver-redirect +spec: + selector: + # Nothing needs to be selected. We only utilize this service so that KubeDNS has a ClusterIP to resolve. + app: idonotexist + ports: + - name: http + port: 80 + targetPort: 8080 diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/serviceaccount.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/serviceaccount.yaml new file mode 100644 index 0000000000..c74ecd667b --- /dev/null +++ b/acceptance/tests/fixtures/bases/resolver-redirect/serviceaccount.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: resolver-redirect diff --git a/acceptance/tests/fixtures/cases/resolver-redirect-virtualip/kustomization.yaml b/acceptance/tests/fixtures/cases/resolver-redirect-virtualip/kustomization.yaml new file mode 100644 index 0000000000..09790e05c6 --- /dev/null +++ b/acceptance/tests/fixtures/cases/resolver-redirect-virtualip/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../bases/resolver-redirect \ No newline at end of file diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index d86dd38a0a..5f65b6c75c 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -330,6 +330,7 @@ partition "{{ .PartitionName }}" { mesh = "write" acl = "write" {{- else }} + mesh = "write" operator = "write" acl = "write" {{- end }} diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index 1e629d68f7..a45af33c11 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -953,6 +953,7 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: false, Expected: ` + mesh = "write" operator = "write" acl = "write" node_prefix "" { @@ -969,6 +970,7 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: false, Expected: ` + mesh = "write" operator = "write" acl = "write" node_prefix "" { @@ -987,6 +989,7 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: true, Expected: ` + mesh = "write" operator = "write" acl = "write" peering = "write" From 6adb9a2f5ca3c6b86d34334bde49f82e0bca0cd0 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:18:02 -0700 Subject: [PATCH 270/592] reactivate proxy-lifecycle tests (#2532) --- .../tests/connect/connect_proxy_lifecycle_test.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go index ecdc51b547..321c002a4c 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/go-version" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -36,15 +35,7 @@ const ( func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { cfg := suite.Config() - ver, err := version.NewVersion("1.2.0") - require.NoError(t, err) - if cfg.ConsulDataplaneVersion != nil && cfg.ConsulDataplaneVersion.LessThan(ver) { - t.Skipf("skipping this test because proxy lifecycle management is not supported in consul-dataplane version %v", cfg.ConsulDataplaneVersion.String()) - } - for _, testCfg := range []LifecycleShutdownConfig{ - {secure: false, helmValues: map[string]string{}}, - {secure: true, helmValues: map[string]string{}}, {secure: false, helmValues: map[string]string{ helmDrainListenersKey: "true", helmGracePeriodSecondsKey: "15", @@ -72,6 +63,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { } { // Determine if listeners should be expected to drain inbound connections var drainListenersEnabled bool + var err error val, ok := testCfg.helmValues[helmDrainListenersKey] if ok { drainListenersEnabled, err = strconv.ParseBool(val) From 46766525bed97353e902acc42d338a100a9a47b1 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Mon, 10 Jul 2023 18:27:04 -0500 Subject: [PATCH 271/592] Fix test flakes. (#2483) --- .../framework/connhelper/connect_helper.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index d10bf43b1a..670307da88 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -117,14 +117,19 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { // Check that both static-server and static-client have been injected and // now have 2 containers. - for _, labelSelector := range []string{"app=static-server", "app=static-client"} { - podList, err := c.Ctx.KubernetesClient(t).CoreV1().Pods(c.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labelSelector, + retry.RunWith( + &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond}, t, + func(r *retry.R) { + for _, labelSelector := range []string{"app=static-server", "app=static-client"} { + podList, err := c.Ctx.KubernetesClient(t).CoreV1().Pods(c.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: labelSelector, + FieldSelector: `status.phase=Running`, + }) + require.NoError(r, err) + require.Len(r, podList.Items, 1) + require.Len(r, podList.Items[0].Spec.Containers, 2) + } }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - } } // CreateResolverRedirect creates a resolver that redirects to a static-server, a corresponding k8s service, From 486061a751d34be9565ff6be0fb9995bd3ae2e64 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 11 Jul 2023 08:18:21 -0400 Subject: [PATCH 272/592] Update chart to use OSS image (#2528) --- charts/consul/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index b3f842e429..b5f8437d83 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-enterprise:1.17-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.17-dev # 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. From 6b45156cc14689372ef4eef437aeac281a245b17 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 11 Jul 2023 10:06:31 -0400 Subject: [PATCH 273/592] Remove todo.txt (#2548) --- charts/consul/todo.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 charts/consul/todo.txt diff --git a/charts/consul/todo.txt b/charts/consul/todo.txt deleted file mode 100644 index c79bef389b..0000000000 --- a/charts/consul/todo.txt +++ /dev/null @@ -1,3 +0,0 @@ - -- [x] Remove gatewayclass gatewayclassconfig bats -- [ ] Add test for each of the CRDs From fd201c57caa59875492914392b6e45a368e3d749 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Tue, 11 Jul 2023 16:02:26 -0400 Subject: [PATCH 274/592] makes gateway controllers less chatty (#2524) --- .changelog/2524.txt | 3 +++ .../controllers/gateway_controller.go | 25 +++++++++++++++++-- .../controllers/gatewayclass_controller.go | 20 ++++++++++++--- .../gatewayclassconfig_controller.go | 21 +++++++++++----- .../api-gateway/gatekeeper/deployment.go | 8 +++--- .../api-gateway/gatekeeper/gatekeeper.go | 4 +-- .../api-gateway/gatekeeper/service.go | 6 ++--- 7 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 .changelog/2524.txt diff --git a/.changelog/2524.txt b/.changelog/2524.txt new file mode 100644 index 0000000000..5d634e68e1 --- /dev/null +++ b/.changelog/2524.txt @@ -0,0 +1,3 @@ +```release-note:improvement +(api-gateway) make API gateway controller less verbose +``` \ No newline at end of file diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index 8569508769..66347adea4 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -72,7 +72,7 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct var gateway gwv1beta1.Gateway - log := r.Log.WithValues("gateway", req.NamespacedName) + log := r.Log.V(1).WithValues("gateway", req.NamespacedName) log.Info("Reconciling Gateway") // get the gateway @@ -199,6 +199,11 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct err := r.updateGatekeeperResources(ctx, log, &gateway, updates.GatewayClassConfig) if err != nil { + if k8serrors.IsConflict(err) { + log.Info("error updating object when updating gateway resources, will try to re-reconcile") + + return ctrl.Result{Requeue: true}, nil + } log.Error(err, "unable to update gateway resources") return ctrl.Result{}, err } @@ -206,11 +211,16 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct } else { err := r.deleteGatekeeperResources(ctx, log, &gateway) if err != nil { + if k8serrors.IsConflict(err) { + log.Info("error updating object when deleting gateway resources, will try to re-reconcile") + + return ctrl.Result{Requeue: true}, nil + } log.Error(err, "unable to delete gateway resources") return ctrl.Result{}, err } r.gatewayCache.RemoveSubscription(nonNormalizedConsulKey) - // make sure we have deregister all services even if they haven't + // make sure we have deregistered all services even if they haven't // hit cache yet if err := r.deregisterAllServices(ctx, nonNormalizedConsulKey); err != nil { log.Error(err, "error deregistering services") @@ -266,6 +276,11 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct for _, update := range updates.Kubernetes.Updates.Operations() { log.Info("update in Kubernetes", "kind", update.GetObjectKind().GroupVersionKind().Kind, "namespace", update.GetNamespace(), "name", update.GetName()) if err := r.updateAndResetStatus(ctx, update); err != nil { + if k8serrors.IsConflict(err) { + log.Info("error updating object for gateway, will try to re-reconcile") + + return ctrl.Result{Requeue: true}, nil + } log.Error(err, "error updating object") return ctrl.Result{}, err } @@ -274,6 +289,11 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct for _, update := range updates.Kubernetes.StatusUpdates.Operations() { log.Info("update status in Kubernetes", "kind", update.GetObjectKind().GroupVersionKind().Kind, "namespace", update.GetNamespace(), "name", update.GetName()) if err := r.Client.Status().Update(ctx, update); err != nil { + if k8serrors.IsConflict(err) { + log.Info("error updating status for gateway, will try to re-reconcile") + + return ctrl.Result{Requeue: true}, nil + } log.Error(err, "error updating status") return ctrl.Result{}, err } @@ -305,6 +325,7 @@ func (r *GatewayController) updateAndResetStatus(ctx context.Context, o client.O if err := r.Client.Update(ctx, o); err != nil { return err } + // reset the status in case it needs to be updated below reflect.ValueOf(o).Elem().FieldByName("Status").Set(status) return nil diff --git a/control-plane/api-gateway/controllers/gatewayclass_controller.go b/control-plane/api-gateway/controllers/gatewayclass_controller.go index e37d1b3bcd..3bde2d6ab1 100644 --- a/control-plane/api-gateway/controllers/gatewayclass_controller.go +++ b/control-plane/api-gateway/controllers/gatewayclass_controller.go @@ -6,7 +6,6 @@ package controllers import ( "context" "fmt" - "github.com/go-logr/logr" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -42,7 +41,7 @@ type GatewayClassController struct { // Reconcile handles the reconciliation loop for GatewayClass objects. func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("gatewayClass", req.NamespacedName.Name) - log.Info("Reconciling GatewayClass") + log.V(1).Info("Reconciling GatewayClass") gc := &gwv1beta1.GatewayClass{} @@ -78,6 +77,11 @@ func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request } // Remove our finalizer. if _, err := RemoveFinalizer(ctx, r.Client, gc, gatewayClassFinalizer); err != nil { + if k8serrors.IsConflict(err) { + log.V(1).Info("error removing finalizer for gatewayClass, will try to re-reconcile") + + return ctrl.Result{Requeue: true}, nil + } log.Error(err, "unable to remove finalizer") return ctrl.Result{}, err } @@ -87,6 +91,11 @@ func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request // We are creating or updating the GatewayClass. didUpdate, err := EnsureFinalizer(ctx, r.Client, gc, gatewayClassFinalizer) if err != nil { + if k8serrors.IsConflict(err) { + log.V(1).Info("error adding finalizer for gatewayClass, will try to re-reconcile") + + return ctrl.Result{Requeue: true}, nil + } log.Error(err, "unable to add finalizer") return ctrl.Result{}, err } @@ -98,7 +107,12 @@ func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request didUpdate, err = r.validateParametersRef(ctx, gc, log) if didUpdate { if err := r.Client.Status().Update(ctx, gc); err != nil { - log.Error(err, "unable to update GatewayClass") + if k8serrors.IsConflict(err) { + log.V(1).Info("error updating status for gatewayClass, will try to re-reconcile") + + return ctrl.Result{Requeue: true}, nil + } + log.Error(err, "unable to update status for GatewayClass") return ctrl.Result{}, err } return ctrl.Result{}, nil diff --git a/control-plane/api-gateway/controllers/gatewayclassconfig_controller.go b/control-plane/api-gateway/controllers/gatewayclassconfig_controller.go index 3889778348..878d6549f9 100644 --- a/control-plane/api-gateway/controllers/gatewayclassconfig_controller.go +++ b/control-plane/api-gateway/controllers/gatewayclassconfig_controller.go @@ -37,14 +37,14 @@ type GatewayClassConfigController struct { // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile func (r *GatewayClassConfigController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("gatewayClassConfig", req.NamespacedName.Name) - log.Info("Reconciling GatewayClassConfig ") + log.V(1).Info("Reconciling GatewayClassConfig ") gcc := &v1alpha1.GatewayClassConfig{} if err := r.Client.Get(ctx, req.NamespacedName, gcc); err != nil { if k8serrors.IsNotFound(err) { return ctrl.Result{}, nil } - r.Log.Error(err, "failed to get gateway class config") + log.Error(err, "failed to get gateway class config") return ctrl.Result{}, err } @@ -52,24 +52,33 @@ func (r *GatewayClassConfigController) Reconcile(ctx context.Context, req ctrl.R // We have a deletion, ensure we're not in use. used, err := gatewayClassConfigInUse(ctx, r.Client, gcc) if err != nil { - r.Log.Error(err, "failed to check if the gateway class config is still in use") + log.Error(err, "failed to check if the gateway class config is still in use") return ctrl.Result{}, err } if used { - r.Log.Info("gateway class config still in use") + log.Info("gateway class config still in use") // Requeue as to not block the reconciliation loop. return ctrl.Result{RequeueAfter: 10 * time.Second}, nil } // gcc is no longer in use. if _, err := RemoveFinalizer(ctx, r.Client, gcc, gatewayClassConfigFinalizer); err != nil { - r.Log.Error(err, "error removing gateway class config finalizer") + if k8serrors.IsConflict(err) { + log.V(1).Info("error removing gateway class config finalizer, will try to re-reconcile") + return ctrl.Result{Requeue: true}, nil + } + log.Error(err, "error removing gateway class config finalizer") return ctrl.Result{}, err } return ctrl.Result{}, nil } if _, err := EnsureFinalizer(ctx, r.Client, gcc, gatewayClassConfigFinalizer); err != nil { - r.Log.Error(err, "error adding gateway class config finalizer") + if k8serrors.IsConflict(err) { + log.V(1).Info("error adding gateway class config finalizer, will try to re-reconcile") + + return ctrl.Result{Requeue: true}, nil + } + log.Error(err, "error adding gateway class config finalizer") return ctrl.Result{}, err } diff --git a/control-plane/api-gateway/gatekeeper/deployment.go b/control-plane/api-gateway/gatekeeper/deployment.go index cc08e1bbef..3590caaf52 100644 --- a/control-plane/api-gateway/gatekeeper/deployment.go +++ b/control-plane/api-gateway/gatekeeper/deployment.go @@ -49,7 +49,7 @@ func (g *Gatekeeper) upsertDeployment(ctx context.Context, gateway gwv1beta1.Gat } if exists { - g.Log.Info("Existing Gateway Deployment found.") + g.Log.V(1).Info("Existing Gateway Deployment found.") // If the user has set the number of replicas, let's respect that. deployment.Spec.Replicas = existingDeployment.Spec.Replicas @@ -65,11 +65,11 @@ func (g *Gatekeeper) upsertDeployment(ctx context.Context, gateway gwv1beta1.Gat switch result { case controllerutil.OperationResultCreated: - g.Log.Info("Created Deployment") + g.Log.V(1).Info("Created Deployment") case controllerutil.OperationResultUpdated: - g.Log.Info("Updated Deployment") + g.Log.V(1).Info("Updated Deployment") case controllerutil.OperationResultNone: - g.Log.Info("No change to deployment") + g.Log.V(1).Info("No change to deployment") } return nil diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper.go b/control-plane/api-gateway/gatekeeper/gatekeeper.go index 46243ff9a1..19444831ee 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper.go @@ -32,7 +32,7 @@ func New(log logr.Logger, client client.Client) *Gatekeeper { // Upsert creates or updates the resources for handling routing of network traffic. // This is done in order based on dependencies between resources. func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { - g.Log.Info(fmt.Sprintf("Upsert Gateway Deployment %s/%s", gateway.Namespace, gateway.Name)) + g.Log.V(1).Info(fmt.Sprintf("Upsert Gateway Deployment %s/%s", gateway.Namespace, gateway.Name)) if err := g.upsertRole(ctx, gateway, gcc, config); err != nil { return err @@ -60,7 +60,7 @@ func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc // Delete removes the resources for handling routing of network traffic. // This is done in the reverse order of Upsert due to dependencies between resources. func (g *Gatekeeper) Delete(ctx context.Context, gatewayName types.NamespacedName) error { - g.Log.Info(fmt.Sprintf("Delete Gateway Deployment %s/%s", gatewayName.Namespace, gatewayName.Name)) + g.Log.V(1).Info(fmt.Sprintf("Delete Gateway Deployment %s/%s", gatewayName.Namespace, gatewayName.Name)) if err := g.deleteDeployment(ctx, gatewayName); err != nil { return err diff --git a/control-plane/api-gateway/gatekeeper/service.go b/control-plane/api-gateway/gatekeeper/service.go index 80272b7495..3b0e848899 100644 --- a/control-plane/api-gateway/gatekeeper/service.go +++ b/control-plane/api-gateway/gatekeeper/service.go @@ -43,11 +43,11 @@ func (g *Gatekeeper) upsertService(ctx context.Context, gateway gwv1beta1.Gatewa switch result { case controllerutil.OperationResultCreated: - g.Log.Info("Created Service") + g.Log.V(1).Info("Created Service") case controllerutil.OperationResultUpdated: - g.Log.Info("Updated Service") + g.Log.V(1).Info("Updated Service") case controllerutil.OperationResultNone: - g.Log.Info("No change to service") + g.Log.V(1).Info("No change to service") } return nil From 592e45702f3680f713d35511b41b39d3cc421b5e Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:33:21 -0600 Subject: [PATCH 275/592] HCP Observability acceptance test (#2254) --- acceptance/go.mod | 21 +- acceptance/go.sum | 128 ++++++++- acceptance/tests/cloud/basic_test.go | 1 + acceptance/tests/cloud/remote_dev_test.go | 264 ++++++++++++++++++ .../bases/cloud/service-intentions/acl.yaml | 15 + .../service-intentions/kustomization.yaml | 5 + .../consul/templates/server-statefulset.yaml | 1 + .../telemetry-collector-deployment.yaml | 2 +- 8 files changed, 431 insertions(+), 6 deletions(-) create mode 100644 acceptance/tests/cloud/remote_dev_test.go create mode 100644 acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml create mode 100644 acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml diff --git a/acceptance/go.mod b/acceptance/go.mod index 59cbbab79f..02ef978cf6 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -9,6 +9,7 @@ require ( github.com/hashicorp/consul/sdk v0.14.0-rc1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 + github.com/hashicorp/hcp-sdk-go v0.50.0 github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.8.3 github.com/stretchr/testify v1.8.3 @@ -24,6 +25,7 @@ require ( require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/aws/aws-sdk-go v1.44.262 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect @@ -39,9 +41,18 @@ require ( github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.2.3 // 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.1 // 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/validate v0.22.1 // indirect + github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -77,6 +88,7 @@ require ( github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/miekg/dns v1.1.50 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect @@ -88,6 +100,8 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.0.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -98,14 +112,18 @@ require ( github.com/prometheus/procfs v0.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/urfave/cli v1.22.2 // indirect + go.mongodb.org/mongo-driver v1.11.0 // indirect + go.opentelemetry.io/otel v1.11.1 // indirect + go.opentelemetry.io/otel/trace v1.11.1 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.1.0 // indirect golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect + golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect @@ -119,7 +137,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.26.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 578a95b1dd..b3aaefe655 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -91,6 +91,9 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -214,31 +217,88 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +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= +github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= +github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 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/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +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.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +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= +github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc= +github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= +github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= +github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= +github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= +github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 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/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= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -277,6 +337,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -291,6 +352,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -393,6 +455,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcp-sdk-go v0.50.0 h1:vOUpVf4MQF/gtoBukuoYKs/i6KinTSpP5jhKCvsZ2bc= +github.com/hashicorp/hcp-sdk-go v0.50.0/go.mod h1:hZqky4HEzsKwvLOt4QJlZUrjeQmb4UCZUhDP2HyQFfc= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= @@ -421,6 +485,7 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -438,10 +503,13 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -460,8 +528,11 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -492,6 +563,8 @@ github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -501,6 +574,7 @@ github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -518,6 +592,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -525,8 +600,11 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -541,11 +619,14 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/oracle/oci-go-sdk v7.1.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -595,6 +676,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uY github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +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/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= @@ -612,9 +695,12 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -640,6 +726,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= @@ -647,6 +734,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -656,8 +745,14 @@ github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -666,11 +761,21 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= +go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= +go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= +go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE= +go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= +go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= +go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= +go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= +go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= @@ -683,6 +788,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/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= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -690,8 +796,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -771,8 +879,10 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -785,12 +895,14 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= 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= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -812,10 +924,13 @@ golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -854,6 +969,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -903,9 +1019,13 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -1056,6 +1176,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -1082,7 +1203,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= @@ -1110,8 +1233,7 @@ k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= -k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= -k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= +k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= diff --git a/acceptance/tests/cloud/basic_test.go b/acceptance/tests/cloud/basic_test.go index 8278309ff3..7a0a31430a 100644 --- a/acceptance/tests/cloud/basic_test.go +++ b/acceptance/tests/cloud/basic_test.go @@ -150,6 +150,7 @@ func TestBasicCloud(t *testing.T) { releaseName := helpers.RandomName() helmValues := map[string]string{ + "global.imagePullPolicy": "IfNotPresent", "global.cloud.enabled": "true", "global.cloud.resourceId.secretName": resourceSecretName, "global.cloud.resourceId.secretKey": resourceSecretKey, diff --git a/acceptance/tests/cloud/remote_dev_test.go b/acceptance/tests/cloud/remote_dev_test.go new file mode 100644 index 0000000000..aa7dbe70c7 --- /dev/null +++ b/acceptance/tests/cloud/remote_dev_test.go @@ -0,0 +1,264 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloud + +import ( + "crypto/tls" + "encoding/json" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + + hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" + "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" + hcpcfg "github.com/hashicorp/hcp-sdk-go/config" + "github.com/hashicorp/hcp-sdk-go/httpclient" + "github.com/hashicorp/hcp-sdk-go/resource" +) + +type DevTokenResponse struct { + Token string `json:"token"` +} + +type hcp struct { + ResourceID string + ClientID string + ClientSecret string + AuthURL string + APIHostname string + ScadaAddress string +} + +func TestRemoteDevCloud(t *testing.T) { + _, rIDok := os.LookupEnv("HCP_RESOURCE_ID") + _, cIDok := os.LookupEnv("HCP_CLIENT_ID") + _, cSECok := os.LookupEnv("HCP_CLIENT_SECRET") + + if !rIDok || !cIDok || !cSECok { + t.Log("Must set HCP_RESOURCE_ID, HCP_CLIENT_ID and HCP_CLIENT_SECRET") + t.FailNow() + } + + apiHost := os.Getenv("HCP_AUTH_URL") + if apiHost == "" { + apiHost = "https://api.hcp.dev" + } + authURL := os.Getenv("HCP_API_HOST") + if authURL == "" { + authURL = "https://auth.idp.hcp.dev" + } + scadaAddr := os.Getenv("HCP_SCADA_ADDRESS") + if scadaAddr == "" { + scadaAddr = "scada.internal.hcp.dev:7224" + } + + ctx := suite.Environment().DefaultContext(t) + + kubectlOptions := ctx.KubectlOptions(t) + ns := kubectlOptions.Namespace + k8sClient := environment.KubernetesClientFromOptions(t, kubectlOptions) + + var ( + resourceSecretName = "resource-sec-name" + resourceSecretKey = "resource-sec-key" + resourceSecretKeyValue = os.Getenv("HCP_RESOURCE_ID") + + clientIDSecretName = "clientid-sec-name" + clientIDSecretKey = "clientid-sec-key" + clientIDSecretKeyValue = os.Getenv("HCP_CLIENT_ID") + + clientSecretName = "client-sec-name" + clientSecretKey = "client-sec-key" + clientSecretKeyValue = os.Getenv("HCP_CLIENT_SECRET") + + apiHostSecretName = "apihost-sec-name" + apiHostSecretKey = "apihost-sec-key" + apiHostSecretKeyValue = apiHost + + authUrlSecretName = "authurl-sec-name" + authUrlSecretKey = "authurl-sec-key" + authUrlSecretKeyValue = authURL + + scadaAddressSecretName = "scadaaddress-sec-name" + scadaAddressSecretKey = "scadaaddress-sec-key" + scadaAddressSecretKeyValue = scadaAddr + + bootstrapTokenSecretName = "bootstrap-token" + bootstrapTokenSecretKey = "token" + ) + + hcpCfg := hcp{ + ResourceID: resourceSecretKeyValue, + ClientID: clientIDSecretKeyValue, + ClientSecret: clientSecretKeyValue, + AuthURL: authUrlSecretKeyValue, + APIHostname: apiHostSecretKeyValue, + ScadaAddress: scadaAddressSecretKeyValue, + } + + aclToken := hcpCfg.fetchAgentBootstrapConfig(t) + + cfg := suite.Config() + consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, bootstrapTokenSecretName, bootstrapTokenSecretKey, aclToken) + + releaseName := helpers.RandomName() + + helmValues := map[string]string{ + "global.imagePullPolicy": "IfNotPresent", + "global.cloud.enabled": "true", + "global.cloud.resourceId.secretName": resourceSecretName, + "global.cloud.resourceId.secretKey": resourceSecretKey, + + "global.cloud.clientId.secretName": clientIDSecretName, + "global.cloud.clientId.secretKey": clientIDSecretKey, + + "global.cloud.clientSecret.secretName": clientSecretName, + "global.cloud.clientSecret.secretKey": clientSecretKey, + + "global.cloud.apiHost.secretName": apiHostSecretName, + "global.cloud.apiHost.secretKey": apiHostSecretKey, + + "global.cloud.authUrl.secretName": authUrlSecretName, + "global.cloud.authUrl.secretKey": authUrlSecretKey, + + "global.cloud.scadaAddress.secretName": scadaAddressSecretName, + "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, + "connectInject.default": "true", + + "global.acls.manageSystemACLs": "true", + "global.acls.bootstrapToken.secretName": bootstrapTokenSecretName, + "global.acls.bootstrapToken.secretKey": bootstrapTokenSecretKey, + + "global.gossipEncryption.autoGenerate": "false", + "global.tls.enabled": "true", + "global.tls.enableAutoEncrypt": "true", + + "telemetryCollector.enabled": "true", + "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, + "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, + + "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, + "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, + // Either we set the global.trustedCAs (make sure it's idented exactly) or we + // set TLS to insecure + + "telemetryCollector.extraEnvironmentVars.HCP_API_ADDRESS": apiHostSecretKeyValue, + } + + if cfg.ConsulImage != "" { + helmValues["global.image"] = cfg.ConsulImage + } + if cfg.ConsulCollectorImage != "" { + helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage + } + + consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), cfg, releaseName) + consulCluster.Create(t) + + logger.Log(t, "setting acl permissions for collector and services") + aclDir := "../fixtures/bases/cloud/service-intentions" + k8s.KubectlApplyK(t, ctx.KubectlOptions(t), aclDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), aclDir) + }) + + logger.Log(t, "creating static-server deployment") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + time.Sleep(1 * time.Hour) + + // TODO: add in test assertions here + +} + +// fetchAgentBootstrapConfig use the resource-id, client-id, and client-secret +// to call to the agent bootstrap config endpoint and parse the response into a +// CloudBootstrapConfig struct. +func (c *hcp) fetchAgentBootstrapConfig(t *testing.T) string { + cfg, err := c.HCPConfig() + require.NoError(t, err) + logger.Log(t, "Fetching Consul cluster configuration from HCP") + httpClientCfg := httpclient.Config{ + HCPConfig: cfg, + } + clientRuntime, err := httpclient.New(httpClientCfg) + require.NoError(t, err) + + hcpgnmClient := hcpgnm.New(clientRuntime, nil) + clusterResource, err := resource.FromString(c.ResourceID) + require.NoError(t, err) + + params := hcpgnm.NewAgentBootstrapConfigParams(). + WithID(clusterResource.ID). + WithLocationOrganizationID(clusterResource.Organization). + WithLocationProjectID(clusterResource.Project) + + resp, err := hcpgnmClient.AgentBootstrapConfig(params, nil) + require.NoError(t, err) + + bootstrapConfig := resp.GetPayload() + logger.Log(t, "HCP configuration successfully fetched.") + + return c.parseBootstrapConfigResponse(t, bootstrapConfig) +} + +// ConsulConfig represents 'cluster.consul_config' in the response +// fetched from the agent bootstrap config endpoint in HCP. +type ConsulConfig struct { + ACL ACL `json:"acl"` +} + +// ACL represents 'cluster.consul_config.acl' in the response +// fetched from the agent bootstrap config endpoint in HCP. +type ACL struct { + Tokens Tokens `json:"tokens"` +} + +// Tokens represents 'cluster.consul_config.acl.tokens' in the +// response fetched from the agent bootstrap config endpoint in HCP. +type Tokens struct { + Agent string `json:"agent"` + InitialManagement string `json:"initial_management"` +} + +// parseBootstrapConfigResponse unmarshals the boostrap parseBootstrapConfigResponse +// and also sets the HCPConfig values to return CloudBootstrapConfig struct. +func (c *hcp) parseBootstrapConfigResponse(t *testing.T, bootstrapRepsonse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) string { + + var consulConfig ConsulConfig + err := json.Unmarshal([]byte(bootstrapRepsonse.Bootstrap.ConsulConfig), &consulConfig) + require.NoError(t, err) + + return consulConfig.ACL.Tokens.InitialManagement +} + +func (c *hcp) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { + if c.ClientID != "" && c.ClientSecret != "" { + opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret)) + } + if c.AuthURL != "" { + opts = append(opts, hcpcfg.WithAuth(c.AuthURL, &tls.Config{})) + } + if c.APIHostname != "" { + opts = append(opts, hcpcfg.WithAPI(c.APIHostname, &tls.Config{})) + } + if c.ScadaAddress != "" { + opts = append(opts, hcpcfg.WithSCADA(c.ScadaAddress, &tls.Config{})) + } + opts = append(opts, hcpcfg.FromEnv(), hcpcfg.WithoutBrowserLogin()) + return hcpcfg.NewHCPConfig(opts...) +} diff --git a/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml b/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml new file mode 100644 index 0000000000..fb3f77f496 --- /dev/null +++ b/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions +metadata: + name: consul-telemetry-collector +spec: + destination: + name: 'consul-telemetry-collector' + sources: + - name: '*' + action: allow + + + \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml b/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml new file mode 100644 index 0000000000..9c19bf4ca3 --- /dev/null +++ b/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - acl.yaml \ No newline at end of file diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 0bde9b881a..9efbcb8085 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -241,6 +241,7 @@ spec: containers: - name: consul image: "{{ default .Values.global.image .Values.server.image }}" + imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: ADVERTISE_IP valueFrom: diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index 62b8868f1f..b729273dd8 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -150,7 +150,7 @@ spec: containers: - name: consul-telemetry-collector image: {{ .Values.telemetryCollector.image }} - imagePullPolicy: Always + imagePullPolicy: {{ .Values.global.imagePullPolicy }} ports: - containerPort: 9090 name: metrics From 858228683c05d9d0e0727ad5f120b75f30ac6480 Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:59:26 -0600 Subject: [PATCH 276/592] HCP bootstrap preset to always downcase datacenter (#2551) * Lowercase datacenter name from HCP bootstrap response * Add test cases to cloud bootstrap --- cli/preset/cloud_preset.go | 3 ++- cli/preset/cloud_preset_test.go | 34 +++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/cli/preset/cloud_preset.go b/cli/preset/cloud_preset.go index 732bad1b14..cbc335ae17 100644 --- a/cli/preset/cloud_preset.go +++ b/cli/preset/cloud_preset.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "github.com/hashicorp/consul-k8s/cli/common" "github.com/hashicorp/consul-k8s/cli/common/terminal" @@ -235,7 +236,7 @@ connectInject: enabled: true controller: enabled: true -`, cfg.BootstrapResponse.Cluster.ID, secretNameServerCA, corev1.TLSCertKey, +`, strings.ToLower(cfg.BootstrapResponse.Cluster.ID), secretNameServerCA, corev1.TLSCertKey, secretNameGossipKey, secretKeyGossipKey, secretNameBootstrapToken, secretKeyBootstrapToken, secretNameHCPResourceID, secretKeyHCPResourceID, diff --git a/cli/preset/cloud_preset_test.go b/cli/preset/cloud_preset_test.go index 770b47ba5c..d905cb4088 100644 --- a/cli/preset/cloud_preset_test.go +++ b/cli/preset/cloud_preset_test.go @@ -43,7 +43,7 @@ const ( { "cluster": { - "id": "dc1", + "id": "Dc1", "bootstrap_expect" : 3 }, "bootstrap": @@ -63,7 +63,7 @@ const ( var validBootstrapReponse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse = &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ Bootstrap: &models.HashicorpCloudGlobalNetworkManager20220215ClusterBootstrap{ - ID: "dc1", + ID: "Dc1", GossipKey: "Wa6/XFAnYy0f9iqVH2iiG+yore3CqHSemUy4AIVTa/w=", BootstrapExpect: 3, ServerTLS: &models.HashicorpCloudGlobalNetworkManager20220215ServerTLS{ @@ -558,12 +558,30 @@ telemetryCollector: cloudPreset := &CloudPreset{} - testCases := []struct { - description string + testCases := map[string]struct { config *CloudBootstrapConfig expectedYaml string }{ - {"Config including optional parameters", + "Config_including_optional_parameters_with_mixedcase_DC": { + &CloudBootstrapConfig{ + BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ + Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ + BootstrapExpect: 3, + ID: "Dc1", + }, + }, + HCPConfig: HCPConfig{ + ResourceID: "consul-hcp-resource-id", + ClientID: "consul-hcp-client-id", + ClientSecret: "consul-hcp-client-secret", + AuthURL: "consul-hcp-auth-url", + APIHostname: "consul-hcp-api-host", + ScadaAddress: "consul-hcp-scada-address", + }, + }, + expectedFull, + }, + "Config_including_optional_parameters": { &CloudBootstrapConfig{ BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ @@ -582,7 +600,7 @@ telemetryCollector: }, expectedFull, }, - {"Config without optional parameters", + "Config_without_optional_parameters": { &CloudBootstrapConfig{ BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ @@ -599,8 +617,8 @@ telemetryCollector: expectedWithoutOptional, }, } - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { cloudHelmValues := cloudPreset.getHelmConfigWithMapSecretNames(tc.config) require.NotNil(t, cloudHelmValues) valuesYaml, err := yaml.Marshal(cloudHelmValues) From 4f064795810b2db052109bcbff9c0f18206d44c5 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 11 Jul 2023 16:53:11 -0500 Subject: [PATCH 277/592] api-gateway: when multiple listeners have the same port, only add to K8s Service once (#2413) * Modify unit tests to include multiple listeners w/ same port Running the tests on this commit will demonstrate the bug * When multiple listeners have the same port, only add to K8s Service once * Add changelog entry --- .changelog/2413.txt | 3 +++ control-plane/api-gateway/gatekeeper/gatekeeper_test.go | 7 +++++++ control-plane/api-gateway/gatekeeper/service.go | 8 ++++++++ 3 files changed, 18 insertions(+) create mode 100644 .changelog/2413.txt diff --git a/.changelog/2413.txt b/.changelog/2413.txt new file mode 100644 index 0000000000..89755b23a7 --- /dev/null +++ b/.changelog/2413.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: Fix creation of invalid Kubernetes Service when multiple Gateway listeners have the same port. +``` diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go index ba58cb441f..069643e301 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -40,12 +40,19 @@ var ( Name: "Listener 1", Port: 8080, Protocol: "TCP", + Hostname: common.PointerTo(gwv1beta1.Hostname("example.com")), }, { Name: "Listener 2", Port: 8081, Protocol: "TCP", }, + { + Name: "Listener 3", + Port: 8080, + Protocol: "TCP", + Hostname: common.PointerTo(gwv1beta1.Hostname("example.net")), + }, } ) diff --git a/control-plane/api-gateway/gatekeeper/service.go b/control-plane/api-gateway/gatekeeper/service.go index 3b0e848899..d534ad50d7 100644 --- a/control-plane/api-gateway/gatekeeper/service.go +++ b/control-plane/api-gateway/gatekeeper/service.go @@ -65,14 +65,22 @@ func (g *Gatekeeper) deleteService(ctx context.Context, gwName types.NamespacedN } func (g *Gatekeeper) service(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig) *corev1.Service { + seenPorts := map[gwv1beta1.PortNumber]struct{}{} ports := []corev1.ServicePort{} for _, listener := range gateway.Spec.Listeners { + if _, seen := seenPorts[listener.Port]; seen { + // We've already added this listener's port to the Service + continue + } + ports = append(ports, corev1.ServicePort{ Name: string(listener.Name), // only TCP-based services are supported for now Protocol: corev1.ProtocolTCP, Port: int32(listener.Port), }) + + seenPorts[listener.Port] = struct{}{} } // Copy annotations from the Gateway, filtered by those allowed by the GatewayClassConfig. From b8be6a0d9ab9ea2ab941c74207cd68d0af2454cb Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 11 Jul 2023 17:35:12 -0500 Subject: [PATCH 278/592] NET-4482: set route condition appropriately when parent ref includes non-existent section (#2420) * Set route accepted condition appropriately when no listener with section name matching parent * Adjust error message for bind errors that aren't specific to one listener * Include section name in message for NoMatchingParent when available * Add unit test coverage for conditions derived from binding results * Add changelog entry --- .changelog/2420.txt | 3 + control-plane/api-gateway/binding/result.go | 22 ++++-- .../api-gateway/binding/result_test.go | 67 +++++++++++++++++++ .../api-gateway/binding/route_binding.go | 23 +++++-- 4 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 .changelog/2420.txt create mode 100644 control-plane/api-gateway/binding/result_test.go diff --git a/.changelog/2420.txt b/.changelog/2420.txt new file mode 100644 index 0000000000..86776497c4 --- /dev/null +++ b/.changelog/2420.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: set route condition appropriately when parent ref includes non-existent section name +``` diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index fd2eaca829..b148e441e2 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -34,6 +34,7 @@ var ( errRouteNoMatchingListenerHostname = errors.New("listener cannot bind route with a non-aligned hostname") errRouteInvalidKind = errors.New("invalid backend kind") errRouteBackendNotFound = errors.New("backend not found") + errRouteNoMatchingParent = errors.New("no matching parent") ) // routeValidationResult holds the result of validating a route globally, in other @@ -128,13 +129,17 @@ type bindResult struct { type bindResults []bindResult // Error constructs a human readable error for bindResults, containing any errors that a route -// had in binding to a gateway, note that this is only used if a route failed to bind to every +// had in binding to a gateway. Note that this is only used if a route failed to bind to every // listener it attempted to bind to. func (b bindResults) Error() string { messages := []string{} for _, result := range b { if result.err != nil { - messages = append(messages, fmt.Sprintf("%s: %s", result.section, result.err.Error())) + message := result.err.Error() + if result.section != "" { + message = fmt.Sprintf("%s: %s", result.section, result.err.Error()) + } + messages = append(messages, message) } } @@ -171,13 +176,16 @@ func (b bindResults) Condition() metav1.Condition { // if we only have a single binding error, we can get more specific if len(b) == 1 { for _, result := range b { - // if we have a hostname mismatch error, then use the more specific reason - if result.err == errRouteNoMatchingListenerHostname { + switch result.err { + case errRouteNoMatchingListenerHostname: + // if we have a hostname mismatch error, then use the more specific reason reason = "NoMatchingListenerHostname" - } - // or if we have a ref not permitted, then use that - if result.err == errRefNotPermitted { + case errRefNotPermitted: + // or if we have a ref not permitted, then use that reason = "RefNotPermitted" + case errRouteNoMatchingParent: + // or if the route declares a parent that we can't find + reason = "NoMatchingParent" } } } diff --git a/control-plane/api-gateway/binding/result_test.go b/control-plane/api-gateway/binding/result_test.go new file mode 100644 index 0000000000..c6987cdaeb --- /dev/null +++ b/control-plane/api-gateway/binding/result_test.go @@ -0,0 +1,67 @@ +package binding + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestBindResults_Condition(t *testing.T) { + testCases := []struct { + Name string + Results bindResults + Expected metav1.Condition + }{ + { + Name: "route successfully bound", + Results: bindResults{{section: "", err: nil}}, + Expected: metav1.Condition{Type: "Accepted", Status: "True", Reason: "Accepted", Message: "route accepted"}, + }, + { + Name: "multiple bind results", + Results: bindResults{ + {section: "abc", err: errRouteNoMatchingListenerHostname}, + {section: "def", err: errRouteNoMatchingParent}, + }, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NotAllowedByListeners", Message: "abc: listener cannot bind route with a non-aligned hostname; def: no matching parent"}, + }, + { + Name: "no matching listener hostname error", + Results: bindResults{{section: "abc", err: errRouteNoMatchingListenerHostname}}, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NoMatchingListenerHostname", Message: "abc: listener cannot bind route with a non-aligned hostname"}, + }, + { + Name: "ref not permitted error", + Results: bindResults{{section: "abc", err: errRefNotPermitted}}, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "RefNotPermitted", Message: "abc: reference not permitted due to lack of ReferenceGrant"}, + }, + { + Name: "no matching parent error", + Results: bindResults{{section: "hello1", err: errRouteNoMatchingParent}}, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NoMatchingParent", Message: "hello1: no matching parent"}, + }, + { + Name: "bind result without section name", + Results: bindResults{{section: "", err: errRouteNoMatchingParent}}, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NoMatchingParent", Message: "no matching parent"}, + }, + { + Name: "unhandled error type", + Results: bindResults{{section: "abc", err: errors.New("you don't know me")}}, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NotAllowedByListeners", Message: "abc: you don't know me"}, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s_%s", t.Name(), tc.Name), func(t *testing.T) { + actual := tc.Results.Condition() + assert.Equalf(t, tc.Expected.Type, actual.Type, "expected condition with type %q but got %q", tc.Expected.Type, actual.Type) + assert.Equalf(t, tc.Expected.Status, actual.Status, "expected condition with status %q but got %q", tc.Expected.Status, actual.Status) + assert.Equalf(t, tc.Expected.Reason, actual.Reason, "expected condition with reason %q but got %q", tc.Expected.Reason, actual.Reason) + assert.Equalf(t, tc.Expected.Message, actual.Message, "expected condition with message %q but got %q", tc.Expected.Message, actual.Message) + }) + } +} diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index 36fcda0204..8b2e66e761 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -104,7 +104,22 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section for _, ref := range filteredParents { var result bindResults - for _, listener := range listenersFor(&r.config.Gateway, ref.SectionName) { + listeners := listenersFor(&r.config.Gateway, ref.SectionName) + + // If there are no matching listeners, then we failed to find the parent + if len(listeners) == 0 { + var sectionName gwv1beta1.SectionName + if ref.SectionName != nil { + sectionName = *ref.SectionName + } + + result = append(result, bindResult{ + section: sectionName, + err: errRouteNoMatchingParent, + }) + } + + for _, listener := range listeners { if !routeKindIsAllowedForListener(supportedKindsForProtocol[listener.Protocol], groupKind) { result = append(result, bindResult{ section: listener.Name, @@ -179,9 +194,9 @@ func filterParentRefs(gateway types.NamespacedName, namespace string, refs []gwv return references } -// listenersFor returns the listeners corresponding the given section name. If the section -// name is actually specified, the returned set should just have one listener, if it is -// unspecified, the all gatweway listeners should be returned. +// listenersFor returns the listeners corresponding to the given section name. If the section +// name is actually specified, the returned set will only contain the named listener. If it is +// unspecified, then all gateway listeners will be returned. func listenersFor(gateway *gwv1beta1.Gateway, name *gwv1beta1.SectionName) []gwv1beta1.Listener { listeners := []gwv1beta1.Listener{} for _, listener := range gateway.Spec.Listeners { From 73959e7acc0f0d6995bf749df2ec3c845d29b033 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Wed, 12 Jul 2023 10:04:36 -0400 Subject: [PATCH 279/592] test: update nightly tests to consul 1.17-dev (#2556) --- .github/workflows/merge.yml | 2 +- .github/workflows/nightly-acceptance.yml | 2 +- .github/workflows/nightly-api-gateway-conformance.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index b6037e0af3..201df1dadd 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -11,7 +11,7 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests. We use this consul image on release branches too + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.17-dev # Consul's enterprise version to use in tests. We use this consul image on release branches too BRANCH: ${{ github.head_ref || github.ref_name }} CONTEXT: "merge" SHA: ${{ github.event.pull_request.head.sha || github.sha }} diff --git a/.github/workflows/nightly-acceptance.yml b/.github/workflows/nightly-acceptance.yml index 6414d6a611..4d437b4990 100644 --- a/.github/workflows/nightly-acceptance.yml +++ b/.github/workflows/nightly-acceptance.yml @@ -8,7 +8,7 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.17-dev # Consul's enterprise version to use in tests BRANCH: ${{ github.ref_name }} CONTEXT: "nightly" diff --git a/.github/workflows/nightly-api-gateway-conformance.yml b/.github/workflows/nightly-api-gateway-conformance.yml index cd63ee8467..5038ffdb93 100644 --- a/.github/workflows/nightly-api-gateway-conformance.yml +++ b/.github/workflows/nightly-api-gateway-conformance.yml @@ -9,7 +9,7 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.16-dev # Consul's enterprise version to use in tests + CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.17-dev # Consul's enterprise version to use in tests BRANCH: ${{ github.ref_name }} CONTEXT: "nightly" From 65c4e7431013a1eb5416a596674a54c6a44a4499 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:29:11 -0700 Subject: [PATCH 280/592] Update Release Scripts (#2558) * update environment variables with CONSUL_K8s prefix - This will let us check that we have all the environment variables set more easily with `printenv | grep "CONSUL_K8S"` * update imageConsulDataplane without quotes - this makes it consistent with the other images - allows scripting to work similarly to other images * updated utils script - handle replace case where consul-enterprise is in the values.yaml file and charts.yaml file - handle adding pre-release tag in changelog - handle updating consul-dataplane --- Makefile | 42 ++++++----- charts/consul/values.yaml | 2 +- .../build-support/functions/10-util.sh | 71 ++++++++++++++----- 3 files changed, 78 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index de35275868..1b7e4d8d7b 100644 --- a/Makefile +++ b/Makefile @@ -209,38 +209,42 @@ eks-test-packages: aks-test-packages: @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/aks_acceptance_test_packages.yaml" - # ===========> Release Targets +check-env: + @printenv | grep "CONSUL_K8S" prepare-release: ## Sets the versions, updates changelog to prepare this repository to release -ifndef RELEASE_VERSION - $(error RELEASE_VERSION is required) +ifndef CONSUL_K8S_RELEASE_VERSION + $(error CONSUL_K8S_RELEASE_VERSION is required) endif -ifndef RELEASE_DATE - $(error RELEASE_DATE is required, use format , (ex. October 4, 2022)) +ifndef CONSUL_K8S_RELEASE_DATE + $(error CONSUL_K8S_RELEASE_DATE is required, use format , (ex. October 4, 2022)) endif -ifndef LAST_RELEASE_GIT_TAG - $(error LAST_RELEASE_GIT_TAG is required) +ifndef CONSUL_K8S_LAST_RELEASE_GIT_TAG + $(error CONSUL_K8S_LAST_RELEASE_GIT_TAG is required) endif -ifndef CONSUL_VERSION - $(error CONSUL_VERSION is required) +ifndef CONSUL_K8S_CONSUL_VERSION + $(error CONSUL_K8S_CONSUL_VERSION is required) endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" $(LAST_RELEASE_GIT_TAG) $(CONSUL_VERSION) $(PRERELEASE_VERSION) + source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION) prepare-dev: -ifndef RELEASE_VERSION - $(error RELEASE_VERSION is required) +ifndef CONSUL_K8S_RELEASE_VERSION + $(error CONSUL_K8S_RELEASE_VERSION is required) +endif +ifndef CONSUL_K8S_RELEASE_DATE + $(error CONSUL_K8S_RELEASE_DATE is required, use format , (ex. October 4, 2022)) endif -ifndef RELEASE_DATE - $(error RELEASE_DATE is required, use format , (ex. October 4, 2022)) +ifndef CONSUL_K8S_NEXT_RELEASE_VERSION + $(error CONSUL_K8S_NEXT_RELEASE_VERSION is required) endif -ifndef NEXT_RELEASE_VERSION - $(error NEXT_RELEASE_VERSION is required) +ifndef CONSUL_K8S_NEXT_CONSUL_VERSION + $(error CONSUL_K8S_NEXT_CONSUL_VERSION is required) endif -ifndef NEXT_CONSUL_VERSION - $(error NEXT_CONSUL_VERSION is required) +ifndef CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION + $(error CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION is required) endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(RELEASE_VERSION) "$(RELEASE_DATE)" "" $(NEXT_RELEASE_VERSION) $(NEXT_CONSUL_VERSION) + source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_NEXT_CONSUL_VERSION) $(CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION) # ===========> Makefile config diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index b5f8437d83..12c45ac958 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -608,7 +608,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.3-dev" + imageConsulDataplane: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.3-dev # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index 72ce91720c..3bc87124d9 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -599,6 +599,8 @@ function update_version_helm { # $4 - Image base path # $5 - Consul version string # $6 - Consul image base path + # $7 - Consul-Dataplane version string + # $8 - Consul-Dataplane base path # # Returns: # 0 - success @@ -620,19 +622,32 @@ function update_version_helm { local prerelease="$3" local full_version="$2" local full_consul_version="$5" - if ! test -z "$3"; then + local full_consul_dataplane_version="$7" + local consul_dataplane_base_path="$8" + if ! test -z "$3" && test "$3" != "dev"; then + full_version="$2-$3" + full_consul_version="$5-$3" + full_consul_dataplane_version="$7-$3" + elif test "$3" == "dev"; then full_version="$2-$3" # strip off the last minor patch version so that the consul image can be set to something like 1.16-dev. The image # is produced by Consul every night full_consul_version="${5%.*}-$3" + full_consul_dataplane_version="${7%.*}-$3" fi sed_i ${SED_EXT} -e "s/(imageK8S:.*\/consul-k8s-control-plane:)[^\"]*/imageK8S: $4${full_version}/g" "${vfile}" sed_i ${SED_EXT} -e "s/(version:[[:space:]]*)[^\"]*/\1${full_version}/g" "${cfile}" sed_i ${SED_EXT} -e "s/(appVersion:[[:space:]]*)[^\"]*/\1${full_consul_version}/g" "${cfile}" sed_i ${SED_EXT} -e "s/(image:.*\/consul-k8s-control-plane:)[^\"]*/image: $4${full_version}/g" "${cfile}" - sed_i ${SED_EXT} -e "s/(image:.*\/consul:)[^\"]*/image: $6:${full_consul_version}/g" "${cfile}" - sed_i ${SED_EXT} -e "s/(image:.*\/consul:)[^\"]*/image: $6:${full_consul_version}/g" "${vfile}" + + sed_i ${SED_EXT} -e "s,^( *image:)(.*/consul:)[^\"]*\$,\1 $6:${full_consul_version},g" ${cfile} + sed_i ${SED_EXT} -e "s,^( *image:)(.*/consul:)[^\"]*\$,\1 $6:${full_consul_version},g" ${vfile} + sed_i ${SED_EXT} -e "s,^( *image:)(.*/consul-enterprise:)[^\"]*\$,\1 $6:${full_consul_version},g" ${cfile} + sed_i ${SED_EXT} -e "s,^( *image:)(.*/consul-enterprise:)[^\"]*\$,\1 $6:${full_consul_version},g" ${vfile} + + sed_i ${SED_EXT} -e "s/(imageConsulDataplane:.*\/consul-dataplane:)[^\"]*/imageConsulDataplane: ${consul_dataplane_base_path}:${full_consul_dataplane_version}/g" "${vfile}" + sed_i ${SED_EXT} -e "s,^( *image:)(.*/consul-dataplane:)[^\"]*\$,\1 ${consul_dataplane_base_path}:${full_consul_dataplane_version},g" ${cfile} if test -z "$3"; then sed_i ${SED_EXT} -e "s/(artifacthub.io\/prerelease:[[:space:]]*)[^\"]*/\1false/g" "${cfile}" @@ -651,6 +666,8 @@ function set_version { # $5 - The consul-k8s helm docker image base path # $6 - The consul version # $7 - The consul helm docker image base path + # $8 - The consul dataplane version + # $9 - The consul-dataplane helm docker image base path # # # Returns: @@ -670,6 +687,7 @@ function set_version { local sdir="$1" local vers="$2" local consul_vers="$6" + local consul_dataplane_vers="$8" status_stage "==> Updating control-plane version/version.go with version info: ${vers} "$4"" if ! update_version "${sdir}/control-plane/version/version.go" "${vers}" "$4"; then @@ -681,8 +699,8 @@ function set_version { return 1 fi - status_stage "==> Updating Helm chart version, consul-k8s: ${vers} "$4" consul: ${consul_vers} "$4"" - if ! update_version_helm "${sdir}/charts/consul" "${vers}" "$4" "$5" "${consul_vers}" "$7"; then + status_stage "==> Updating Helm chart version, consul-k8s: ${vers} "$4" consul: ${consul_vers} "$4" consul-dataplane: ${consul_dataplane_vers} "$4"" + if ! update_version_helm "${sdir}/charts/consul" "${vers}" "$4" "$5" "${consul_vers}" "$7" "${consul_dataplane_vers}" "$9"; then return 1 fi @@ -695,6 +713,7 @@ function set_changelog { # $2 - Version # $3 - Release Date # $4 - The last git release tag + # $5 - Pre-release version # # # Returns: @@ -714,20 +733,21 @@ function set_changelog { rel_date="$3" fi local last_release_date_git_tag=$4 + local preReleaseVersion="-$5" if test -z "${version}"; then err "ERROR: Must specify a version to put into the changelog" return 1 fi - if [ -z "$LAST_RELEASE_GIT_TAG" ]; then - echo "Error: LAST_RELEASE_GIT_TAG not specified." + if [ -z "$CONSUL_K8S_LAST_RELEASE_GIT_TAG" ]; then + echo "Error: CONSUL_K8S_LAST_RELEASE_GIT_TAG not specified." exit 1 fi cat <tmp && mv tmp "${curdir}"/CHANGELOG.MD -## ${version} (${rel_date}) -$(changelog-build -last-release ${LAST_RELEASE_GIT_TAG} \ +## ${version}${preReleaseVersion} (${rel_date}) +$(changelog-build -last-release ${CONSUL_K8S_LAST_RELEASE_GIT_TAG} \ -entries-dir .changelog/ \ -changelog-template .changelog/changelog.tmpl \ -note-template .changelog/note.tmpl \ @@ -742,17 +762,26 @@ function prepare_release { # $2 - The version of the release # $3 - The release date # $4 - The last release git tag for this branch (eg. v1.1.0) - # $5 - The pre-release version - # $6 - The consul version + # $5 - The consul version + # $6 - The consul-dataplane version + # $7 - The pre-release version # # # Returns: # 0 - success # * - error - echo "prepare_release: dir:$1 consul-k8s:$2 consul:$5 date:"$3" git tag:$4" - set_version "$1" "$2" "$3" "$6" "hashicorp\/consul-k8s-control-plane:" "$5" "hashicorp\/consul" - set_changelog "$1" "$2" "$3" "$4" + local curDir=$1 + local version=$2 + local releaseDate=$3 + local lastGitTag=$4 + local consulVersion=$5 + local consulDataplaneVersion=$6 + local prereleaseVersion=$7 + + echo "prepare_release: dir:${curDir} consul-k8s:${version} consul:${consulVersion} consul-dataplane:${consulDataplaneVersion} date:"${releaseDate}" git tag:${lastGitTag}" + set_version "${curDir}" "${version}" "${releaseDate}" "${prereleaseVersion}" "hashicorp\/consul-k8s-control-plane:" "${consulVersion}" "hashicorp\/consul" "${consulDataplaneVersion}" "hashicorp\/consul-dataplane" + set_changelog "${curDir}" "${version}" "${releaseDate}" "${lastGitTag}" "${prereleaseVersion}" } function prepare_dev { @@ -763,13 +792,21 @@ function prepare_dev { # $4 - The last release git tag for this branch (eg. v1.1.0) (Unused) # $5 - The version of the next release # $6 - The version of the next consul release + # $7 - The next consul-dataplane version # # Returns: # 0 - success # * - error - echo "prepare_dev: dir:$1 consul-k8s:$5 consul:$6 date:"$3" mode:dev" - set_version "$1" "$5" "$3" "dev" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" "$6" "docker.mirror.hashicorp.services\/hashicorppreview\/consul" + local curDir=$1 + local version=$2 + local releaseDate=$3 + local nextReleaseVersion=$5 + local nextConsulVersion=$6 + local nextConsulDataplaneVersion=$7 + + echo "prepare_dev: dir:${curDir} consul-k8s:${nextReleaseVersion} consul:${nextConsulVersion} date:"${releaseDate}" mode:dev" + set_version "${curDir}" "${nextReleaseVersion}" "${releaseDate}" "dev" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" "${nextConsulVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul" "${nextConsulDataplaneVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-dataplane" return 0 } @@ -896,7 +933,7 @@ function ui_version { return 1 fi - local ui_version=$(sed -n ${SED_EXT} -e 's/.*CONSUL_VERSION%22%3A%22([^%]*)%22%2C%22.*/\1/p' <"$1") || return 1 + local ui_version=$(sed -n ${SED_EXT} -e 's/.*CONSUL_K8S_CONSUL_VERSION%22%3A%22([^%]*)%22%2C%22.*/\1/p' <"$1") || return 1 echo "$ui_version" return 0 } From df0e649e95caf9bd9ccf911d7e810c1dd781fa81 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:42:51 -0700 Subject: [PATCH 281/592] added missing changelogs (#2565) * added missing changelogs * Update CHANGELOG.md for 0.49.8 --------- Co-authored-by: Curt Bushko --- CHANGELOG.md | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 445e0f1816..54bdba549b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,116 @@ +## 0.49.8 (July 12, 2023) + +IMPROVEMENTS: + +* helm: Add `connectInject.prepareDataplanesUpgrade` setting for help upgrading to dataplanes. This setting is required if upgrading from non-dataplanes to dataplanes when ACLs are enabled. See https://developer.hashicorp.com/consul/docs/k8s/upgrade#upgrading-to-consul-dataplane for more information. [[GH-2514](https://github.com/hashicorp/consul-k8s/issues/2514)] + +## 1.2.0 (June 28, 2023) + +FEATURES: + +* Add support for configuring Consul server-side rate limiting [[GH-2166](https://github.com/hashicorp/consul-k8s/issues/2166)] +* api-gateway: Add API Gateway for Consul on Kubernetes leveraging Consul native API Gateway configuration. [[GH-2152](https://github.com/hashicorp/consul-k8s/issues/2152)] +* crd: Add `mutualTLSMode` to the ProxyDefaults and ServiceDefaults CRDs and `allowEnablingPermissiveMutualTLS` to the Mesh CRD to support configuring permissive mutual TLS. [[GH-2100](https://github.com/hashicorp/consul-k8s/issues/2100)] +* helm: Add `JWTProvider` CRD for configuring the `jwt-provider` config entry. [[GH-2209](https://github.com/hashicorp/consul-k8s/issues/2209)] +* helm: Update the ServiceIntentions CRD to support `JWT` fields. [[GH-2213](https://github.com/hashicorp/consul-k8s/issues/2213)] + +IMPROVEMENTS: + +* cli: update minimum go version for project to 1.20. [[GH-2102](https://github.com/hashicorp/consul-k8s/issues/2102)] +* control-plane: add FIPS support [[GH-2165](https://github.com/hashicorp/consul-k8s/issues/2165)] +* control-plane: server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/issues/1770)] +* control-plane: set agent localities on Consul servers to the server node's `topology.kubernetes.io/region` label. [[GH-2093](https://github.com/hashicorp/consul-k8s/issues/2093)] +* control-plane: update alpine to 3.17 in the Docker image. [[GH-1934](https://github.com/hashicorp/consul-k8s/issues/1934)] +* control-plane: update minimum go version for project to 1.20. [[GH-2102](https://github.com/hashicorp/consul-k8s/issues/2102)] +* helm: Kubernetes v1.27 is now supported. Minimum tested version of Kubernetes is now v1.24. [[GH-2304](https://github.com/hashicorp/consul-k8s/issues/2304)] +* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] +* helm: add failover policy field to service resolver and proxy default CRDs [[GH-2030](https://github.com/hashicorp/consul-k8s/issues/2030)] +* helm: add samenessGroup CRD [[GH-2048](https://github.com/hashicorp/consul-k8s/issues/2048)] +* helm: add samenessGroup field to exported services CRD [[GH-2075](https://github.com/hashicorp/consul-k8s/issues/2075)] +* helm: add samenessGroup field to service resolver CRD [[GH-2086](https://github.com/hashicorp/consul-k8s/issues/2086)] +* helm: add samenessGroup field to source intention CRD [[GH-2097](https://github.com/hashicorp/consul-k8s/issues/2097)] +* helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.1.0`. [[GH-1953](https://github.com/hashicorp/consul-k8s/issues/1953)] + +SECURITY: + +* Update [Go-Discover](https://github.com/hashicorp/go-discover) in the container has been updated to address [CVE-2020-14040](https://github.com/advisories/GHSA-5rcv-m4m3-hfh7) [[GH-2390](https://github.com/hashicorp/consul-k8s/issues/2390)] +* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] +* Fix Prometheus CVEs by bumping controller-runtime. [[GH-2183](https://github.com/hashicorp/consul-k8s/issues/2183)] +* Upgrade to use Go 1.20.4. + This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), + [CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), + [CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and + [CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). + Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41721 + ](https://github.com/advisories/GHSA-fxg5-wq6x-vr4w + ), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 + ](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h + .) [[GH-2102](https://github.com/hashicorp/consul-k8s/issues/2102)] + +BUG FIXES: + +* control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. [[GH-2266](https://github.com/hashicorp/consul-k8s/issues/2266)] +* control-plane: fix issue where consul-connect-injector acl token was unintentionally being deleted and not recreated when a container was restarted due to a livenessProbe failure. [[GH-1914](https://github.com/hashicorp/consul-k8s/issues/1914)] + +## 1.1.3 (June 28, 2023) +BREAKING CHANGES: + +* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] + +SECURITY: + +* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] +* Update [Go-Discover](https://github.com/hashicorp/go-discover) in the container has been updated to address [CVE-2020-14040](https://github.com/advisories/GHSA-5rcv-m4m3-hfh7) [[GH-2390](https://github.com/hashicorp/consul-k8s/issues/2390)] + +FEATURES: + +* Add support for configuring graceful shutdown proxy lifecycle management settings. [[GH-2233](https://github.com/hashicorp/consul-k8s/issues/2233)] +* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] +* sync-catalog: add ability to support weighted loadbalancing by service annotation `consul.hashicorp.com/service-weight: ` [[GH-2293](https://github.com/hashicorp/consul-k8s/issues/2293)] + +IMPROVEMENTS: + +* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2369](https://github.com/hashicorp/consul-k8s/issues/2369)] +* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] + +BUG FIXES: + +* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] +* control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. [[GH-2266](https://github.com/hashicorp/consul-k8s/issues/2266)] + +## 1.0.8 (June 28, 2023) +BREAKING CHANGES: + +* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] + +SECURITY: + +* Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.2`. [[GH-2204](https://github.com/hashicorp/consul-k8s/issues/2204)] +* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] +* Bump `controller-runtime` to address CVEs in dependencies. [[GH-2225](https://github.com/hashicorp/consul-k8s/issues/2225)] +* Update [Go-Discover](https://github.com/hashicorp/go-discover) in the container has been updated to address [CVE-2020-14040](https://github.com/advisories/GHSA-5rcv-m4m3-hfh7) [[GH-2390](https://github.com/hashicorp/consul-k8s/issues/2390)] + +FEATURES: + +* Add support for configuring graceful shutdown proxy lifecycle management settings. [[GH-2233](https://github.com/hashicorp/consul-k8s/issues/2233)] +* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] +* sync-catalog: add ability to support weighted loadbalancing by service annotation `consul.hashicorp.com/service-weight: ` [[GH-2293](https://github.com/hashicorp/consul-k8s/issues/2293)] + +IMPROVEMENTS: + +* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2265](https://github.com/hashicorp/consul-k8s/issues/2265)] +* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] + +BUG FIXES: + +* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] +* control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. [[GH-2266](https://github.com/hashicorp/consul-k8s/issues/2266)] +* control-plane: add support for idleTimeout in the Service Router config [[GH-2156](https://github.com/hashicorp/consul-k8s/issues/2156)] +* control-plane: fix issue with json tags of service defaults fields EnforcingConsecutive5xx, MaxEjectionPercent and BaseEjectionTime. [[GH-2159](https://github.com/hashicorp/consul-k8s/issues/2159)] +* control-plane: fix issue with multiport pods crashlooping due to dataplane port conflicts by ensuring dns redirection is disabled for non-tproxy pods [[GH-2176](https://github.com/hashicorp/consul-k8s/issues/2176)] +* crd: fix bug on service intentions CRD causing some updates to be ignored. [[GH-2194](https://github.com/hashicorp/consul-k8s/issues/2194)] + + ## 0.49.7 (June 28, 2023) BREAKING CHANGES: From 29b6ed36923498afc8f377455d4275653960230f Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:42:05 -0700 Subject: [PATCH 282/592] Refactor test framework to allow for more than two kube contexts (#2534) * updated contributing example with new configuration lists add new make target "kind" to makefile * This lets us setup our standard kind environment for testing refactor framework to take config list flags * removed primary/secondary kube flags as this limited us to only two clusters * added flags for kube configs, contexts and namespaces. This way we can support n clusters where n is the length of the longest list. The flags are then combined into a list of objects for use in testing added tests for new helper methods refactored tests * now TestMain for multicluster check that the test arguments contain the expected number of clusters * use helper method `env.GetSecondaryContextKey(t)` which grabs the second context in the list instead of using the defunct environment.SecondaryContextName refactored flag test to use new config lists refactored cli cluster to use get primary helper added multicluster check for vault acceptance * vault tests are multi-cluster but we weren't performing the necessary checks --- CONTRIBUTING.md | 21 ++-- Makefile | 29 +++-- acceptance/framework/config/config.go | 64 +++++++++-- acceptance/framework/config/config_test.go | 103 ++++++++++++++++++ acceptance/framework/consul/cli_cluster.go | 7 +- .../framework/environment/environment.go | 47 +++----- acceptance/framework/flags/flags.go | 82 ++++++++------ acceptance/framework/flags/flags_test.go | 97 ++++++++++++----- acceptance/tests/partitions/main_test.go | 6 +- .../partitions/partitions_connect_test.go | 3 +- .../partitions/partitions_gateway_test.go | 3 +- .../tests/partitions/partitions_sync_test.go | 3 +- acceptance/tests/peering/main_test.go | 6 +- .../peering_connect_namespaces_test.go | 3 +- .../tests/peering/peering_connect_test.go | 3 +- .../tests/peering/peering_gateway_test.go | 3 +- acceptance/tests/vault/main_test.go | 11 +- .../tests/vault/vault_partitions_test.go | 3 +- acceptance/tests/vault/vault_wan_fed_test.go | 2 +- acceptance/tests/wan-federation/main_test.go | 6 +- .../wan_federation_gateway_test.go | 2 +- .../wan-federation/wan_federation_test.go | 3 +- 22 files changed, 357 insertions(+), 150 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f0deb97ce9..c1e3446e8d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -642,8 +642,7 @@ you may use the following command: go test ./... -p 1 -timeout 20m \ -enable-multi-cluster \ - -kubecontext= \ - -secondary-kubecontext= + -kube-contexts=",, etc.>" Below is the list of available flags: @@ -667,20 +666,14 @@ Below is the list of available flags: This applies only to tests that enable connectInject. -enterprise-license The enterprise license for Consul. --kubeconfig string - The path to a kubeconfig file. If this is blank, the default kubeconfig path (~/.kube/config) will be used. --kubecontext string - The name of the Kubernetes context to use. If this is blank, the context set as the current context will be used by default. --namespace string - The Kubernetes namespace to use for tests. (default "default") +-kubeconfigs string + The comma separated list of Kubernetes configs to use (eg. "~/.kube/config,~/.kube/config2"). The first in the list will be treated as the primary config, followed by the secondary, etc. If the list is empty, or items are blank, then the default kubeconfig path (~/.kube/config) will be used. +-kube-contexts string + The comma separated list of Kubernetes contexts to use (eg. "kind-dc1,kind-dc2"). The first in the list will be treated as the primary context, followed by the secondary, etc. If the list is empty, or items are blank, then the current context will be used. +-kube-namespaces string + The comma separated list of Kubernetes namespaces to use (eg. "consul,consul-secondary"). The first in the list will be treated as the primary namespace, followed by the secondary, etc. If the list is empty, or fields are blank, then the current namespace will be used. -no-cleanup-on-failure If true, the tests will not cleanup Kubernetes resources they create when they finish running.Note this flag must be run with -failfast flag, otherwise subsequent tests will fail. --secondary-kubeconfig string - The path to a kubeconfig file of the secondary k8s cluster. If this is blank, the default kubeconfig path (~/.kube/config) will be used. --secondary-kubecontext string - The name of the Kubernetes context for the secondary cluster to use. If this is blank, the context set as the current context will be used by default. --secondary-namespace string - The Kubernetes namespace to use in the secondary k8s cluster. (default "default") ``` **Note:** There is a Terraform configuration in the diff --git a/Makefile b/Makefile index 1b7e4d8d7b..1d62035dbb 100644 --- a/Makefile +++ b/Makefile @@ -87,15 +87,6 @@ cni-plugin-lint: ctrl-generate: get-controller-gen ## Run CRD code generation. cd control-plane; $(CONTROLLER_GEN) object paths="./..." -# Helper target for doing local cni acceptance testing -kind-cni: - kind delete cluster --name dc1 - kind delete cluster --name dc2 - kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc1 --image kindest/node:v1.23.6 - make kind-cni-calico - kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc2 --image kindest/node:v1.23.6 - make kind-cni-calico - # Perform a terraform fmt check but don't change anything terraform-fmt-check: @$(CURDIR)/control-plane/build-support/scripts/terraformfmtcheck.sh $(TERRAFORM_DIR) @@ -133,7 +124,24 @@ kind-cni-calico: # Sleeps are needed as installs can happen too quickly for Kind to handle it @sleep 30 kubectl create -f $(CURDIR)/acceptance/framework/environment/cni-kind/custom-resources.yaml - @sleep 20 + @sleep 20 + +# Helper target for doing local cni acceptance testing +kind-cni: + kind delete cluster --name dc1 + kind delete cluster --name dc2 + kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc1 --image $(KIND_NODE_IMAGE) + make kind-cni-calico + kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc2 --image $(KIND_NODE_IMAGE) + make kind-cni-calico + +# Helper target for doing local acceptance testing +kind: + kind delete cluster --name dc1 + kind delete cluster --name dc2 + kind create cluster --name dc1 --image $(KIND_NODE_IMAGE) + kind create cluster --name dc2 --image $(KIND_NODE_IMAGE) + # ===========> Shared Targets @@ -247,7 +255,6 @@ endif source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_NEXT_CONSUL_VERSION) $(CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION) # ===========> Makefile config - .DEFAULT_GOAL := help .PHONY: gen-helm-docs copy-crds-to-chart generate-external-crds bats-tests help ci.aws-acceptance-test-cleanup version cli-dev prepare-dev prepare-release SHELL = bash diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index 7151a75908..eada42af20 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -5,6 +5,7 @@ package config import ( "fmt" + "math" "os" "path/filepath" "strconv" @@ -22,16 +23,48 @@ const ( LicenseSecretKey = "key" ) -// TestConfig holds configuration for the test suite. -type TestConfig struct { - Kubeconfig string +type KubeTestConfig struct { + KubeConfig string KubeContext string KubeNamespace string +} + +// NewKubeTestConfigList takes lists of kubernetes configs, contexts and namespaces and constructs KubeTestConfig +// We validate ahead of time that the lists are either 0 or the same length as we expect that if the length of a list +// is greater than 0, then the indexes should match. For example: []kubeContexts{"ctx1", "ctx2"} indexes 0, 1 match with []kubeNamespaces{"ns1", "ns2"}. +func NewKubeTestConfigList(kubeConfigs, kubeContexts, kubeNamespaces []string) []KubeTestConfig { + // Grab the longest length. + l := math.Max(float64(len(kubeConfigs)), + math.Max(float64(len(kubeContexts)), float64(len(kubeNamespaces)))) + + // If all are empty, then return a single empty entry + if l == 0 { + return []KubeTestConfig{{}} + } + + // Add each non-zero length list to the new structs, we should have + // n structs where n == l. + out := make([]KubeTestConfig, int(l)) + for i := range out { + kenv := KubeTestConfig{} + if len(kubeConfigs) != 0 { + kenv.KubeConfig = kubeConfigs[i] + } + if len(kubeContexts) != 0 { + kenv.KubeContext = kubeContexts[i] + } + if len(kubeNamespaces) != 0 { + kenv.KubeNamespace = kubeNamespaces[i] + } + out[i] = kenv + } + return out +} - EnableMultiCluster bool - SecondaryKubeconfig string - SecondaryKubeContext string - SecondaryKubeNamespace string +// TestConfig holds configuration for the test suite. +type TestConfig struct { + KubeEnvs []KubeTestConfig + EnableMultiCluster bool EnableEnterprise bool EnterpriseLicense string @@ -117,6 +150,23 @@ func (t *TestConfig) HelmValuesFromConfig() (map[string]string, error) { return helmValues, nil } +// IsExpectedClusterCount check that we have at least the required number of clusters to +// run a test. +func (t *TestConfig) IsExpectedClusterCount(count int) bool { + return len(t.KubeEnvs) >= count +} + +// GetPrimaryKubeEnv returns the primary Kubernetes environment. +func (t *TestConfig) GetPrimaryKubeEnv() KubeTestConfig { + // Return the first in the list as this is always the primary + // kube environment. If empty return an empty kubeEnv + if len(t.KubeEnvs) < 1 { + return KubeTestConfig{} + } else { + return t.KubeEnvs[0] + } +} + type values struct { Global globalValues `yaml:"global"` } diff --git a/acceptance/framework/config/config_test.go b/acceptance/framework/config/config_test.go index f5992cdd99..96f0f0e7eb 100644 --- a/acceptance/framework/config/config_test.go +++ b/acceptance/framework/config/config_test.go @@ -181,3 +181,106 @@ func TestConfig_HelmValuesFromConfig_EntImage(t *testing.T) { }) } } + +func Test_KubeEnvListFromStringList(t *testing.T) { + tests := []struct { + name string + kubeContexts []string + KubeConfigs []string + kubeNamespaces []string + expKubeEnvList []KubeTestConfig + }{ + { + name: "empty-lists", + kubeContexts: []string{}, + KubeConfigs: []string{}, + kubeNamespaces: []string{}, + expKubeEnvList: []KubeTestConfig{{}}, + }, + { + name: "kubeContext set", + kubeContexts: []string{"ctx1", "ctx2"}, + KubeConfigs: []string{}, + kubeNamespaces: []string{}, + expKubeEnvList: []KubeTestConfig{{KubeContext: "ctx1"}, {KubeContext: "ctx2"}}, + }, + { + name: "kubeNamespace set", + kubeContexts: []string{}, + KubeConfigs: []string{"/path/config1", "/path/config2"}, + kubeNamespaces: []string{}, + expKubeEnvList: []KubeTestConfig{{KubeConfig: "/path/config1"}, {KubeConfig: "/path/config2"}}, + }, + { + name: "kubeConfigs set", + kubeContexts: []string{}, + KubeConfigs: []string{}, + kubeNamespaces: []string{"ns1", "ns2"}, + expKubeEnvList: []KubeTestConfig{{KubeNamespace: "ns1"}, {KubeNamespace: "ns2"}}, + }, + { + name: "multiple everything", + kubeContexts: []string{"ctx1", "ctx2"}, + KubeConfigs: []string{"/path/config1", "/path/config2"}, + kubeNamespaces: []string{"ns1", "ns2"}, + expKubeEnvList: []KubeTestConfig{{KubeContext: "ctx1", KubeNamespace: "ns1", KubeConfig: "/path/config1"}, {KubeContext: "ctx2", KubeNamespace: "ns2", KubeConfig: "/path/config2"}}, + }, + { + name: "multiple context and configs", + kubeContexts: []string{"ctx1", "ctx2"}, + KubeConfigs: []string{"/path/config1", "/path/config2"}, + kubeNamespaces: []string{}, + expKubeEnvList: []KubeTestConfig{{KubeContext: "ctx1", KubeConfig: "/path/config1"}, {KubeContext: "ctx2", KubeConfig: "/path/config2"}}, + }, + { + name: "multiple namespace and configs", + kubeContexts: []string{}, + KubeConfigs: []string{"/path/config1", "/path/config2"}, + kubeNamespaces: []string{"ns1", "ns2"}, + expKubeEnvList: []KubeTestConfig{{KubeNamespace: "ns1", KubeConfig: "/path/config1"}, {KubeNamespace: "ns2", KubeConfig: "/path/config2"}}, + }, + { + name: "multiple context and namespace", + kubeContexts: []string{"ctx1", "ctx2"}, + KubeConfigs: []string{}, + kubeNamespaces: []string{"ns1", "ns2"}, + expKubeEnvList: []KubeTestConfig{{KubeContext: "ctx1", KubeNamespace: "ns1"}, {KubeContext: "ctx2", KubeNamespace: "ns2"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := NewKubeTestConfigList(tt.KubeConfigs, tt.kubeContexts, tt.kubeNamespaces) + require.Equal(t, tt.expKubeEnvList, actual) + }) + } +} + +func Test_GetPrimaryKubeEnv(t *testing.T) { + tests := []struct { + name string + kubeEnvList []KubeTestConfig + expPrimaryKubeEnv KubeTestConfig + }{ + { + name: "context config multiple namespace single", + kubeEnvList: []KubeTestConfig{{KubeContext: "ctx1", KubeNamespace: "ns1", KubeConfig: "/path/config1"}, {KubeContext: "ctx2", KubeConfig: "/path/config2"}}, + expPrimaryKubeEnv: KubeTestConfig{KubeContext: "ctx1", KubeNamespace: "ns1", KubeConfig: "/path/config1"}, + }, + { + name: "context config multiple namespace single", + kubeEnvList: []KubeTestConfig{}, + expPrimaryKubeEnv: KubeTestConfig{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := TestConfig{ + KubeEnvs: tt.kubeEnvList, + } + actual := cfg.GetPrimaryKubeEnv() + require.Equal(t, tt.expPrimaryKubeEnv, actual) + }) + } +} diff --git a/acceptance/framework/consul/cli_cluster.go b/acceptance/framework/consul/cli_cluster.go index ba4cfc93ab..9e119af76d 100644 --- a/acceptance/framework/consul/cli_cluster.go +++ b/acceptance/framework/consul/cli_cluster.go @@ -97,16 +97,17 @@ func NewCLICluster( cli, err := cli.NewCLI() require.NoError(t, err) + require.Greater(t, len(cfg.KubeEnvs), 0) return &CLICluster{ ctx: ctx, helmOptions: hopts, kubectlOptions: kopts, - namespace: cfg.KubeNamespace, + namespace: cfg.GetPrimaryKubeEnv().KubeNamespace, values: values, releaseName: releaseName, kubernetesClient: ctx.KubernetesClient(t), - kubeConfig: cfg.Kubeconfig, - kubeContext: cfg.KubeContext, + kubeConfig: cfg.GetPrimaryKubeEnv().KubeConfig, + kubeContext: cfg.GetPrimaryKubeEnv().KubeContext, noCleanupOnFailure: cfg.NoCleanupOnFailure, debugDirectory: cfg.DebugDirectory, logger: logger, diff --git a/acceptance/framework/environment/environment.go b/acceptance/framework/environment/environment.go index 58e4e83a5b..9150f4ff03 100644 --- a/acceptance/framework/environment/environment.go +++ b/acceptance/framework/environment/environment.go @@ -21,15 +21,14 @@ import ( ) const ( - DefaultContextName = "default" - SecondaryContextName = "secondary" + DefaultContextIndex = 0 ) // TestEnvironment represents the infrastructure environment of the test, // such as the kubernetes cluster(s) the test is running against. type TestEnvironment interface { DefaultContext(t *testing.T) TestContext - Context(t *testing.T, name string) TestContext + Context(t *testing.T, index int) TestContext } // TestContext represents a specific context a test needs, @@ -41,50 +40,40 @@ type TestContext interface { } type KubernetesEnvironment struct { - contexts map[string]*kubernetesContext + contexts []*kubernetesContext } func NewKubernetesEnvironmentFromConfig(config *config.TestConfig) *KubernetesEnvironment { - defaultContext := NewContext(config.KubeNamespace, config.Kubeconfig, config.KubeContext) + // First kubeEnv is the default + defaultContext := NewContext(config.GetPrimaryKubeEnv().KubeNamespace, config.GetPrimaryKubeEnv().KubeConfig, config.GetPrimaryKubeEnv().KubeContext) // Create a kubernetes environment with default context. kenv := &KubernetesEnvironment{ - contexts: map[string]*kubernetesContext{ - DefaultContextName: defaultContext, + contexts: []*kubernetesContext{ + defaultContext, }, } - // Add secondary context if multi cluster tests are enabled. + // Add additional contexts if multi cluster tests are enabled. if config.EnableMultiCluster { - kenv.contexts[SecondaryContextName] = NewContext(config.SecondaryKubeNamespace, config.SecondaryKubeconfig, config.SecondaryKubeContext) - } - - return kenv -} - -func NewKubernetesEnvironmentFromContext(context *kubernetesContext) *KubernetesEnvironment { - // Create a kubernetes environment with default context. - kenv := &KubernetesEnvironment{ - contexts: map[string]*kubernetesContext{ - DefaultContextName: context, - }, + for _, v := range config.KubeEnvs[1:] { + kenv.contexts = append(kenv.contexts, NewContext(v.KubeNamespace, v.KubeConfig, v.KubeContext)) + } } return kenv } -func (k *KubernetesEnvironment) Context(t *testing.T, name string) TestContext { - ctx, ok := k.contexts[name] - require.Truef(t, ok, fmt.Sprintf("requested context %s not found", name)) - - return ctx +func (k *KubernetesEnvironment) Context(t *testing.T, index int) TestContext { + lenContexts := len(k.contexts) + require.Greater(t, lenContexts, index, fmt.Sprintf("context list does not contain an index %d, length is %d", index, lenContexts)) + return k.contexts[index] } func (k *KubernetesEnvironment) DefaultContext(t *testing.T) TestContext { - ctx, ok := k.contexts[DefaultContextName] - require.Truef(t, ok, "default context not found") - - return ctx + lenContexts := len(k.contexts) + require.Greater(t, lenContexts, DefaultContextIndex, fmt.Sprintf("context list does not contain an index %d, length is %d", DefaultContextIndex, lenContexts)) + return k.contexts[DefaultContextIndex] } type kubernetesContext struct { diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index 5df09f853a..ad36099b37 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -7,6 +7,7 @@ import ( "errors" "flag" "os" + "strings" "sync" "github.com/hashicorp/consul-k8s/acceptance/framework/config" @@ -14,14 +15,10 @@ import ( ) type TestFlags struct { - flagKubeconfig string - flagKubecontext string - flagNamespace string - - flagEnableMultiCluster bool - flagSecondaryKubeconfig string - flagSecondaryKubecontext string - flagSecondaryNamespace string + flagKubeconfigs listFlag + flagKubecontexts listFlag + flagKubeNamespaces listFlag + flagEnableMultiCluster bool flagEnableEnterprise bool flagEnterpriseLicense string @@ -68,13 +65,19 @@ func NewTestFlags() *TestFlags { return t } -func (t *TestFlags) init() { - flag.StringVar(&t.flagKubeconfig, "kubeconfig", "", "The path to a kubeconfig file. If this is blank, "+ - "the default kubeconfig path (~/.kube/config) will be used.") - flag.StringVar(&t.flagKubecontext, "kubecontext", "", "The name of the Kubernetes context to use. If this is blank, "+ - "the context set as the current context will be used by default.") - flag.StringVar(&t.flagNamespace, "namespace", "", "The Kubernetes namespace to use for tests.") +type listFlag []string +// String() returns a comma separated list in the form "var1,var2,var3". +func (f *listFlag) String() string { + return strings.Join(*f, ",") +} + +func (f *listFlag) Set(value string) error { + *f = strings.Split(value, ",") + return nil +} + +func (t *TestFlags) init() { flag.StringVar(&t.flagConsulImage, "consul-image", "", "The Consul image to use for all tests.") flag.StringVar(&t.flagConsulK8sImage, "consul-k8s-image", "", "The consul-k8s image to use for all tests.") flag.StringVar(&t.flagConsulDataplaneImage, "consul-dataplane-image", "", "The consul-dataplane image to use for all tests.") @@ -86,16 +89,16 @@ func (t *TestFlags) init() { flag.StringVar(&t.flagVaultServerVersion, "vault-server-version", "", "The vault serverversion used for all tests.") flag.StringVar(&t.flagVaultHelmChartVersion, "vault-helm-chart-version", "", "The Vault helm chart used for all tests.") + flag.Var(&t.flagKubeconfigs, "kubeconfigs", "The list of paths to a kubeconfig files. If this is blank, "+ + "the default kubeconfig path (~/.kube/config) will be used.") + flag.Var(&t.flagKubecontexts, "kube-contexts", "The list of names of the Kubernetes contexts to use. If this is blank, "+ + "the context set as the current context will be used by default.") + flag.Var(&t.flagKubeNamespaces, "kube-namespaces", "The list of Kubernetes namespaces to use for tests.") flag.StringVar(&t.flagHCPResourceID, "hcp-resource-id", "", "The hcp resource id to use for all tests.") flag.BoolVar(&t.flagEnableMultiCluster, "enable-multi-cluster", false, "If true, the tests that require multiple Kubernetes clusters will be run. "+ - "At least one of -secondary-kubeconfig or -secondary-kubecontext is required when this flag is used.") - flag.StringVar(&t.flagSecondaryKubeconfig, "secondary-kubeconfig", "", "The path to a kubeconfig file of the secondary k8s cluster. "+ - "If this is blank, the default kubeconfig path (~/.kube/config) will be used.") - flag.StringVar(&t.flagSecondaryKubecontext, "secondary-kubecontext", "", "The name of the Kubernetes context for the secondary cluster to use. "+ - "If this is blank, the context set as the current context will be used by default.") - flag.StringVar(&t.flagSecondaryNamespace, "secondary-namespace", "", "The Kubernetes namespace to use in the secondary k8s cluster.") + "The lists -kubeconfig or -kube-context must contain more than one entry when this flag is used.") flag.BoolVar(&t.flagEnableEnterprise, "enable-enterprise", false, "If true, the test suite will run tests for enterprise features. "+ @@ -143,8 +146,26 @@ func (t *TestFlags) init() { func (t *TestFlags) Validate() error { if t.flagEnableMultiCluster { - if t.flagSecondaryKubecontext == "" && t.flagSecondaryKubeconfig == "" { - return errors.New("at least one of -secondary-kubecontext or -secondary-kubeconfig flags must be provided if -enable-multi-cluster is set") + if len(t.flagKubecontexts) <= 1 && len(t.flagKubeconfigs) <= 1 { + return errors.New("at least two contexts must be included in -kube-contexts or -kubeconfigs if -enable-multi-cluster is set") + } + } + + if len(t.flagKubecontexts) != 0 && len(t.flagKubeconfigs) != 0 { + if len(t.flagKubecontexts) != len(t.flagKubeconfigs) { + return errors.New("-kube-contexts and -kubeconfigs are both set but are not of equal length") + } + } + + if len(t.flagKubecontexts) != 0 && len(t.flagKubeNamespaces) != 0 { + if len(t.flagKubecontexts) != len(t.flagKubeNamespaces) { + return errors.New("-kube-contexts and -kube-namespaces are both set but are not of equal length") + } + } + + if len(t.flagKubeNamespaces) != 0 && len(t.flagKubeconfigs) != 0 { + if len(t.flagKubeNamespaces) != len(t.flagKubeconfigs) { + return errors.New("-kube-namespaces and -kubeconfigs are both set but are not of equal length") } } @@ -161,20 +182,15 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { consulVersion, _ := version.NewVersion(t.flagConsulVersion) consulDataplaneVersion, _ := version.NewVersion(t.flagConsulDataplaneVersion) //vaultserverVersion, _ := version.NewVersion(t.flagVaultServerVersion) + kubeEnvs := config.NewKubeTestConfigList(t.flagKubeconfigs, t.flagKubecontexts, t.flagKubeNamespaces) - return &config.TestConfig{ - Kubeconfig: t.flagKubeconfig, - KubeContext: t.flagKubecontext, - KubeNamespace: t.flagNamespace, - - EnableMultiCluster: t.flagEnableMultiCluster, - SecondaryKubeconfig: t.flagSecondaryKubeconfig, - SecondaryKubeContext: t.flagSecondaryKubecontext, - SecondaryKubeNamespace: t.flagSecondaryNamespace, - + c := &config.TestConfig{ EnableEnterprise: t.flagEnableEnterprise, EnterpriseLicense: t.flagEnterpriseLicense, + KubeEnvs: kubeEnvs, + EnableMultiCluster: t.flagEnableMultiCluster, + EnableOpenshift: t.flagEnableOpenshift, EnablePodSecurityPolicies: t.flagEnablePodSecurityPolicies, @@ -205,4 +221,6 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { UseGKE: t.flagUseGKE, UseKind: t.flagUseKind, } + + return c } diff --git a/acceptance/framework/flags/flags_test.go b/acceptance/framework/flags/flags_test.go index 7546ae911c..1e2bf0a039 100644 --- a/acceptance/framework/flags/flags_test.go +++ b/acceptance/framework/flags/flags_test.go @@ -11,9 +11,10 @@ import ( func TestFlags_validate(t *testing.T) { type fields struct { - flagEnableMultiCluster bool - flagSecondaryKubeconfig string - flagSecondaryKubecontext string + flagEnableMultiCluster bool + flagKubeConfigs listFlag + flagKubeContexts listFlag + flagNamespaces listFlag flagEnableEnt bool flagEntLicense string @@ -26,20 +27,16 @@ func TestFlags_validate(t *testing.T) { }{ { "no error by default", - fields{ - flagEnableMultiCluster: false, - flagSecondaryKubeconfig: "", - flagSecondaryKubecontext: "", - }, + fields{}, false, "", }, { "enable multi cluster: no error when multi cluster is disabled", fields{ - flagEnableMultiCluster: false, - flagSecondaryKubeconfig: "", - flagSecondaryKubecontext: "", + flagEnableMultiCluster: false, + flagKubeConfigs: listFlag{}, + flagKubeContexts: listFlag{}, }, false, "", @@ -47,19 +44,19 @@ func TestFlags_validate(t *testing.T) { { "enable multi cluster: errors when both secondary kubeconfig and kubecontext are empty", fields{ - flagEnableMultiCluster: true, - flagSecondaryKubeconfig: "", - flagSecondaryKubecontext: "", + flagEnableMultiCluster: true, + flagKubeConfigs: listFlag{}, + flagKubeContexts: listFlag{}, }, true, - "at least one of -secondary-kubecontext or -secondary-kubeconfig flags must be provided if -enable-multi-cluster is set", + "at least two contexts must be included in -kube-contexts or -kubeconfigs if -enable-multi-cluster is set", }, { "enable multi cluster: no error when secondary kubeconfig but not kubecontext is provided", fields{ - flagEnableMultiCluster: true, - flagSecondaryKubeconfig: "foo", - flagSecondaryKubecontext: "", + flagEnableMultiCluster: true, + flagKubeConfigs: listFlag{"foo", "bar"}, + flagKubeContexts: listFlag{}, }, false, "", @@ -67,9 +64,9 @@ func TestFlags_validate(t *testing.T) { { "enable multi cluster: no error when secondary kubecontext but not kubeconfig is provided", fields{ - flagEnableMultiCluster: true, - flagSecondaryKubeconfig: "", - flagSecondaryKubecontext: "foo", + flagEnableMultiCluster: true, + flagKubeConfigs: listFlag{}, + flagKubeContexts: listFlag{"foo", "bar"}, }, false, "", @@ -77,13 +74,54 @@ func TestFlags_validate(t *testing.T) { { "enable multi cluster: no error when both secondary kubecontext and kubeconfig are provided", fields{ - flagEnableMultiCluster: true, - flagSecondaryKubeconfig: "foo", - flagSecondaryKubecontext: "bar", + flagEnableMultiCluster: true, + flagKubeConfigs: listFlag{"foo", "bar"}, + flagKubeContexts: listFlag{"foo", "bar"}, + }, + false, + "", + }, + { + "enable multi cluster: no error when all of secondary kubecontext, kubeconfigs and namespaces are provided", + fields{ + flagEnableMultiCluster: true, + flagKubeConfigs: listFlag{"foo", "bar"}, + flagKubeContexts: listFlag{"foo", "bar"}, + flagNamespaces: listFlag{"foo", "bar"}, }, false, "", }, + { + "enable multi cluster: error when the list of kubeconfigs and kubecontexts do not match", + fields{ + flagEnableMultiCluster: true, + flagKubeConfigs: listFlag{"foo", "bar"}, + flagKubeContexts: listFlag{"foo"}, + }, + true, + "-kube-contexts and -kubeconfigs are both set but are not of equal length", + }, + { + "enable multi cluster: error when the list of kubeconfigs and namespaces do not match", + fields{ + flagEnableMultiCluster: true, + flagKubeConfigs: listFlag{"foo", "bar"}, + flagNamespaces: listFlag{"foo"}, + }, + true, + "-kube-namespaces and -kubeconfigs are both set but are not of equal length", + }, + { + "enable multi cluster: error when the list of kubecontexts and namespaces do not match", + fields{ + flagEnableMultiCluster: true, + flagKubeContexts: listFlag{"foo", "bar"}, + flagNamespaces: listFlag{"foo"}, + }, + true, + "-kube-contexts and -kube-namespaces are both set but are not of equal length", + }, { "enterprise license: error when only -enable-enterprise is true but env CONSUL_ENT_LICENSE is not provided", fields{ @@ -105,11 +143,12 @@ func TestFlags_validate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tf := &TestFlags{ - flagEnableMultiCluster: tt.fields.flagEnableMultiCluster, - flagSecondaryKubeconfig: tt.fields.flagSecondaryKubeconfig, - flagSecondaryKubecontext: tt.fields.flagSecondaryKubecontext, - flagEnableEnterprise: tt.fields.flagEnableEnt, - flagEnterpriseLicense: tt.fields.flagEntLicense, + flagEnableMultiCluster: tt.fields.flagEnableMultiCluster, + flagKubeconfigs: tt.fields.flagKubeConfigs, + flagKubecontexts: tt.fields.flagKubeContexts, + flagKubeNamespaces: tt.fields.flagNamespaces, + flagEnableEnterprise: tt.fields.flagEnableEnt, + flagEnterpriseLicense: tt.fields.flagEntLicense, } err := tf.Validate() if tt.wantErr { diff --git a/acceptance/tests/partitions/main_test.go b/acceptance/tests/partitions/main_test.go index 9368c12f00..89833ec2cc 100644 --- a/acceptance/tests/partitions/main_test.go +++ b/acceptance/tests/partitions/main_test.go @@ -16,10 +16,12 @@ var suite testsuite.Suite func TestMain(m *testing.M) { suite = testsuite.NewSuite(m) - if suite.Config().EnableMultiCluster { + expectedNumberOfClusters := 2 + if suite.Config().EnableMultiCluster && suite.Config().IsExpectedClusterCount(expectedNumberOfClusters) { os.Exit(suite.Run()) } else { - fmt.Println("Skipping partitions tests because -enable-multi-cluster is not set") + fmt.Println(fmt.Sprintf("Skipping partitions tests because either -enable-multi-cluster is "+ + "not set or the number of clusters did not match the expected count of %d", expectedNumberOfClusters)) os.Exit(0) } } diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index b14f079a68..aa73c17047 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -11,7 +11,6 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -85,7 +84,7 @@ func TestPartitions_Connect(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { defaultPartitionClusterContext := env.DefaultContext(t) - secondaryPartitionClusterContext := env.Context(t, environment.SecondaryContextName) + secondaryPartitionClusterContext := env.Context(t, 1) commonHelmValues := map[string]string{ "global.adminPartitions.enabled": "true", diff --git a/acceptance/tests/partitions/partitions_gateway_test.go b/acceptance/tests/partitions/partitions_gateway_test.go index 06bc933ce8..5c85e6725b 100644 --- a/acceptance/tests/partitions/partitions_gateway_test.go +++ b/acceptance/tests/partitions/partitions_gateway_test.go @@ -12,7 +12,6 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -37,7 +36,7 @@ func TestPartitions_Gateway(t *testing.T) { const secondaryPartition = "secondary" defaultPartitionClusterContext := env.DefaultContext(t) - secondaryPartitionClusterContext := env.Context(t, environment.SecondaryContextName) + secondaryPartitionClusterContext := env.Context(t, 1) commonHelmValues := map[string]string{ "global.adminPartitions.enabled": "true", diff --git a/acceptance/tests/partitions/partitions_sync_test.go b/acceptance/tests/partitions/partitions_sync_test.go index cf32c97ae3..8eaaff099e 100644 --- a/acceptance/tests/partitions/partitions_sync_test.go +++ b/acceptance/tests/partitions/partitions_sync_test.go @@ -11,7 +11,6 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -82,7 +81,7 @@ func TestPartitions_Sync(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { primaryClusterContext := env.DefaultContext(t) - secondaryClusterContext := env.Context(t, environment.SecondaryContextName) + secondaryClusterContext := env.Context(t, 1) commonHelmValues := map[string]string{ "global.adminPartitions.enabled": "true", diff --git a/acceptance/tests/peering/main_test.go b/acceptance/tests/peering/main_test.go index 64c5f8ed03..075051861d 100644 --- a/acceptance/tests/peering/main_test.go +++ b/acceptance/tests/peering/main_test.go @@ -16,10 +16,12 @@ var suite testsuite.Suite func TestMain(m *testing.M) { suite = testsuite.NewSuite(m) - if suite.Config().EnableMultiCluster && !suite.Config().DisablePeering { + expectedNumberOfClusters := 2 + if suite.Config().EnableMultiCluster && suite.Config().IsExpectedClusterCount(expectedNumberOfClusters) && !suite.Config().DisablePeering { os.Exit(suite.Run()) } else { - fmt.Println("Skipping peering tests because either -enable-multi-cluster is not set or -disable-peering is set") + fmt.Println(fmt.Sprintf("Skipping peerings tests because either -enable-multi-cluster is "+ + "not set, -disable-peering is set, or the number of clusters did not match the expected count of %d", expectedNumberOfClusters)) os.Exit(0) } } diff --git a/acceptance/tests/peering/peering_connect_namespaces_test.go b/acceptance/tests/peering/peering_connect_namespaces_test.go index 9276582db3..622e547091 100644 --- a/acceptance/tests/peering/peering_connect_namespaces_test.go +++ b/acceptance/tests/peering/peering_connect_namespaces_test.go @@ -12,7 +12,6 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -93,7 +92,7 @@ func TestPeering_ConnectNamespaces(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { staticServerPeerClusterContext := env.DefaultContext(t) - staticClientPeerClusterContext := env.Context(t, environment.SecondaryContextName) + staticClientPeerClusterContext := env.Context(t, 1) commonHelmValues := map[string]string{ "global.peering.enabled": "true", diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index ad62ca6926..a14cf3a805 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -12,7 +12,6 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -53,7 +52,7 @@ func TestPeering_Connect(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { staticServerPeerClusterContext := env.DefaultContext(t) - staticClientPeerClusterContext := env.Context(t, environment.SecondaryContextName) + staticClientPeerClusterContext := env.Context(t, 1) commonHelmValues := map[string]string{ "global.peering.enabled": "true", diff --git a/acceptance/tests/peering/peering_gateway_test.go b/acceptance/tests/peering/peering_gateway_test.go index 76698102e6..17824b8e69 100644 --- a/acceptance/tests/peering/peering_gateway_test.go +++ b/acceptance/tests/peering/peering_gateway_test.go @@ -11,7 +11,6 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -42,7 +41,7 @@ func TestPeering_Gateway(t *testing.T) { const staticClientPeer = "client" staticServerPeerClusterContext := env.DefaultContext(t) - staticClientPeerClusterContext := env.Context(t, environment.SecondaryContextName) + staticClientPeerClusterContext := env.Context(t, 1) commonHelmValues := map[string]string{ "global.peering.enabled": "true", diff --git a/acceptance/tests/vault/main_test.go b/acceptance/tests/vault/main_test.go index e20892bf1c..02a22c2b79 100644 --- a/acceptance/tests/vault/main_test.go +++ b/acceptance/tests/vault/main_test.go @@ -4,6 +4,7 @@ package vault import ( + "fmt" "os" "testing" @@ -14,5 +15,13 @@ var suite testsuite.Suite func TestMain(m *testing.M) { suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) + + expectedNumberOfClusters := 2 + if suite.Config().EnableMultiCluster && suite.Config().IsExpectedClusterCount(expectedNumberOfClusters) { + os.Exit(suite.Run()) + } else { + fmt.Println(fmt.Sprintf("Skipping vault tests because either -enable-multi-cluster is "+ + "not set or the number of clusters did not match the expected count of %d", expectedNumberOfClusters)) + os.Exit(0) + } } diff --git a/acceptance/tests/vault/vault_partitions_test.go b/acceptance/tests/vault/vault_partitions_test.go index 53bdc23e97..63002993a6 100644 --- a/acceptance/tests/vault/vault_partitions_test.go +++ b/acceptance/tests/vault/vault_partitions_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -26,7 +25,7 @@ func TestVault_Partitions(t *testing.T) { env := suite.Environment() cfg := suite.Config() serverClusterCtx := env.DefaultContext(t) - clientClusterCtx := env.Context(t, environment.SecondaryContextName) + clientClusterCtx := env.Context(t, 1) ns := serverClusterCtx.KubectlOptions(t).Namespace const secondaryPartition = "secondary" diff --git a/acceptance/tests/vault/vault_wan_fed_test.go b/acceptance/tests/vault/vault_wan_fed_test.go index 21a86937f4..d8c00b732a 100644 --- a/acceptance/tests/vault/vault_wan_fed_test.go +++ b/acceptance/tests/vault/vault_wan_fed_test.go @@ -44,7 +44,7 @@ func TestVault_WANFederationViaGateways(t *testing.T) { } primaryCtx := suite.Environment().DefaultContext(t) - secondaryCtx := suite.Environment().Context(t, environment.SecondaryContextName) + secondaryCtx := suite.Environment().Context(t, 1) ns := primaryCtx.KubectlOptions(t).Namespace diff --git a/acceptance/tests/wan-federation/main_test.go b/acceptance/tests/wan-federation/main_test.go index ced18d5cc7..4a47a8a00f 100644 --- a/acceptance/tests/wan-federation/main_test.go +++ b/acceptance/tests/wan-federation/main_test.go @@ -16,10 +16,12 @@ var suite testsuite.Suite func TestMain(m *testing.M) { suite = testsuite.NewSuite(m) - if suite.Config().EnableMultiCluster { + expectedNumberOfClusters := 2 + if suite.Config().EnableMultiCluster && suite.Config().IsExpectedClusterCount(expectedNumberOfClusters) { os.Exit(suite.Run()) } else { - fmt.Println("Skipping wan federation tests because -enable-multi-cluster is not set") + fmt.Println(fmt.Sprintf("Skipping wan-federation tests because either -enable-multi-cluster is "+ + "not set or the number of clusters did not match the expected count of %d", expectedNumberOfClusters)) os.Exit(0) } } diff --git a/acceptance/tests/wan-federation/wan_federation_gateway_test.go b/acceptance/tests/wan-federation/wan_federation_gateway_test.go index 0ef48b9920..c87ee7197b 100644 --- a/acceptance/tests/wan-federation/wan_federation_gateway_test.go +++ b/acceptance/tests/wan-federation/wan_federation_gateway_test.go @@ -33,7 +33,7 @@ func TestWANFederation_Gateway(t *testing.T) { } primaryContext := env.DefaultContext(t) - secondaryContext := env.Context(t, environment.SecondaryContextName) + secondaryContext := env.Context(t, 1) primaryHelmValues := map[string]string{ "global.datacenter": "dc1", diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index ced126af42..e7a128887e 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -49,7 +48,7 @@ func TestWANFederation(t *testing.T) { } primaryContext := env.DefaultContext(t) - secondaryContext := env.Context(t, environment.SecondaryContextName) + secondaryContext := env.Context(t, 1) primaryHelmValues := map[string]string{ "global.datacenter": "dc1", From 59228dd4cb064e892a6ef80668c447eb3689ca54 Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 15:04:09 -0400 Subject: [PATCH 283/592] [COMPLIANCE] Add Copyright and License Headers (#2577) Add copyright and license headers --- control-plane/api-gateway/binding/result_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/control-plane/api-gateway/binding/result_test.go b/control-plane/api-gateway/binding/result_test.go index c6987cdaeb..6989bb09ab 100644 --- a/control-plane/api-gateway/binding/result_test.go +++ b/control-plane/api-gateway/binding/result_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package binding import ( From ab462d0a483c6b31c6d6846f61fc6b66c45c0540 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 18 Jul 2023 11:01:55 -0500 Subject: [PATCH 284/592] Consume gateway-api v0.7.1 for acceptance testing (#2578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes proposed in this PR: - Consume the same version of gateway-api for acceptance testing that we're consuming in the control plane: https://github.com/hashicorp/consul-k8s/blob/29b6ed36923498afc8f377455d4275653960230f/control-plane/go.mod#L42 How I've tested this PR: - 👀 - 🤖 tests pass How I expect reviewers to test this PR: - See above Checklist: - [ ] Tests added - [ ] [CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) --- acceptance/go.mod | 2 +- acceptance/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance/go.mod b/acceptance/go.mod index 02ef978cf6..382d9f8225 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -19,7 +19,7 @@ require ( k8s.io/client-go v0.26.3 k8s.io/utils v0.0.0-20230209194617-a36077c30491 sigs.k8s.io/controller-runtime v0.14.6 - sigs.k8s.io/gateway-api v0.7.0 + sigs.k8s.io/gateway-api v0.7.1 ) require ( diff --git a/acceptance/go.sum b/acceptance/go.sum index b3aaefe655..53b9400c42 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -1264,8 +1264,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= -sigs.k8s.io/gateway-api v0.7.0 h1:/mG8yyJNBifqvuVLW5gwlI4CQs0NR/5q4BKUlf1bVdY= -sigs.k8s.io/gateway-api v0.7.0/go.mod h1:Xv0+ZMxX0lu1nSSDIIPEfbVztgNZ+3cfiYrJsa2Ooso= +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/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= From c790951382235e9a5f10365d1b46fba16c5f2e5a Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:10:23 -0600 Subject: [PATCH 285/592] Update to handle validation endpoints (#2580) Changes proposed in this PR: - add in new validation call in endpoint How I've tested this PR: Ran it locally and tested the changes How I expect reviewers to test this PR: Read the code and run the command themselves to verify: ``` ./consul-k8s/acceptance/tests/cloud && go test -run TestBasicCloud -v -p 1 -timeout 20m \ -use-kind \ -kubecontext="kind-dc1" \ -consul-image hashicorppreview/consul-enterprise:1.17-dev -consul-k8s-image hashicorppreview/consul-k8s-control-plane:1.3.0-dev -consul-collector-image hashicorp/consul-telemetry-collector:0.0.1 \ -enable-enterprise ``` Checklist: - [X] Tests added - [n/a] [CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) --- acceptance/tests/cloud/basic_test.go | 59 ++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/acceptance/tests/cloud/basic_test.go b/acceptance/tests/cloud/basic_test.go index 7a0a31430a..0b5e3da719 100644 --- a/acceptance/tests/cloud/basic_test.go +++ b/acceptance/tests/cloud/basic_test.go @@ -129,7 +129,7 @@ func TestBasicCloud(t *testing.T) { localPort, 443, logger.TestLogger{}) - + defer tunnel.Close() // Retry creating the port forward since it can fail occasionally. retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry @@ -144,7 +144,7 @@ func TestBasicCloud(t *testing.T) { logger.Log(t, "error finding consul token") return } - tunnel.Close() + logger.Log(t, "consul test token :"+consulToken) releaseName := helpers.RandomName() @@ -229,6 +229,57 @@ func TestBasicCloud(t *testing.T) { logger.Log(t, "creating static-server deployment") k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") - // time.Sleep(1 * time.Hour) - // TODO: add in test assertions here + t.Log("Finished deployment. Validating expected conditions now") + // Give some time for collector send metrics + time.Sleep(5 * time.Second) + err = validate(tunnel.Endpoint()) + logger.Log(t, fmt.Sprintf("result: %v", err)) + require.NoError(t, err) + +} + +func validate(endpoint string) error { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + client := &http.Client{Transport: tr} + url := fmt.Sprintf("https://%s/validation", endpoint) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Println("Error creating request:", err) + return errors.New("error creating validation request") + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error sending request:", err) + return errors.New("error making validation request") + } + if resp.StatusCode == http.StatusExpectationFailed { + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response:", err) + return errors.New("error reading body") + } + var message errMsg + err = json.Unmarshal(body, &message) + if err != nil { + fmt.Println("Error parsing response:", err) + return errors.New("error parsing body") + } + + return fmt.Errorf("Failed validation: %s", message) + } else if resp.StatusCode != http.StatusOK { + return errors.New("unexpected status code response from failure") + } + + return nil + +} + +type errMsg struct { + Error string `json:"error"` } From 07cc5cd57733f90c0ac7a1defe35ebef57dec437 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Wed, 19 Jul 2023 09:50:08 -0400 Subject: [PATCH 286/592] test(eks): fix deprecated CSI driver terraform (#2584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes proposed in this PR: - Replacing the deprecated [`resolve_conflicts`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_addon#resolve_conflicts) with the new attributes. I don't know if we really need this setting since it is optional and the addon has no user-defined config, but I'm keeping this to keep the behavior consistent. How I've tested this PR: I did not. How I expect reviewers to test this PR: 👀 Checklist: - [ ] ~Tests added~ - [ ] ~[CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry)~ --- charts/consul/test/terraform/eks/main.tf | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index efbab0e833..3bc8b40451 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -124,12 +124,13 @@ resource "aws_iam_role_policy_attachment" "csi" { } resource "aws_eks_addon" "csi-driver" { - count = var.cluster_count - cluster_name = module.eks[count.index].cluster_id - addon_name = "aws-ebs-csi-driver" - addon_version = "v1.15.0-eksbuild.1" - service_account_role_arn = aws_iam_role.csi-driver-role[count.index].arn - resolve_conflicts = "OVERWRITE" + count = var.cluster_count + cluster_name = module.eks[count.index].cluster_id + addon_name = "aws-ebs-csi-driver" + addon_version = "v1.15.0-eksbuild.1" + service_account_role_arn = aws_iam_role.csi-driver-role[count.index].arn + resolve_conflicts_on_create = "OVERWRITE" + resolve_conflicts_on_update = "OVERWRITE" } data "aws_eks_cluster" "cluster" { From f0530d9a5ac9c0e8ee5de910975bd722a4ade2c0 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Wed, 19 Jul 2023 13:53:00 -0400 Subject: [PATCH 287/592] Add a check to prevent a nil-pointer dereference on Ingress LB (#2592) --- control-plane/catalog/to-consul/resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/catalog/to-consul/resource.go b/control-plane/catalog/to-consul/resource.go index 08aeec8821..2d29d6c15a 100644 --- a/control-plane/catalog/to-consul/resource.go +++ b/control-plane/catalog/to-consul/resource.go @@ -938,7 +938,7 @@ func (t *serviceIngressResource) Upsert(key string, raw interface{}) error { continue } if t.SyncLoadBalancerIPs { - if ingress.Status.LoadBalancer.Ingress[0].IP == "" { + if len(ingress.Status.LoadBalancer.Ingress) > 0 && ingress.Status.LoadBalancer.Ingress[0].IP == "" { continue } hostName = ingress.Status.LoadBalancer.Ingress[0].IP From b3769b1cb19004112f83badd5028a3fce1030bb4 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Wed, 19 Jul 2023 14:13:27 -0400 Subject: [PATCH 288/592] test: remove unused workflow inputs (#2589) Changes proposed in this PR: - Removed unused workflow inputs. --- .github/workflows/merge.yml | 3 +-- .github/workflows/nightly-acceptance.yml | 3 +-- .github/workflows/nightly-api-gateway-conformance.yml | 1 - .github/workflows/nightly-cleanup.yml | 3 +-- .github/workflows/weekly-acceptance-0-49-x.yml | 3 +-- .github/workflows/weekly-acceptance-1-0-x.yml | 3 +-- .github/workflows/weekly-acceptance-1-1-x.yml | 3 +-- 7 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 201df1dadd..e95af6cdcc 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -11,7 +11,6 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.17-dev # Consul's enterprise version to use in tests. We use this consul image on release branches too BRANCH: ${{ github.head_ref || github.ref_name }} CONTEXT: "merge" SHA: ${{ github.event.pull_request.head.sha || github.sha }} @@ -28,4 +27,4 @@ jobs: repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/nightly-acceptance.yml b/.github/workflows/nightly-acceptance.yml index 4d437b4990..6db7684bb8 100644 --- a/.github/workflows/nightly-acceptance.yml +++ b/.github/workflows/nightly-acceptance.yml @@ -8,7 +8,6 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.17-dev # Consul's enterprise version to use in tests BRANCH: ${{ github.ref_name }} CONTEXT: "nightly" @@ -24,4 +23,4 @@ jobs: 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/nightly-api-gateway-conformance.yml b/.github/workflows/nightly-api-gateway-conformance.yml index 5038ffdb93..abeec34659 100644 --- a/.github/workflows/nightly-api-gateway-conformance.yml +++ b/.github/workflows/nightly-api-gateway-conformance.yml @@ -9,7 +9,6 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.17-dev # Consul's enterprise version to use in tests BRANCH: ${{ github.ref_name }} CONTEXT: "nightly" diff --git a/.github/workflows/nightly-cleanup.yml b/.github/workflows/nightly-cleanup.yml index 4a304549df..83d6688ac5 100644 --- a/.github/workflows/nightly-cleanup.yml +++ b/.github/workflows/nightly-cleanup.yml @@ -8,7 +8,6 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: "not used" BRANCH: ${{ github.ref_name }} CONTEXT: "nightly" @@ -24,4 +23,4 @@ jobs: 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/weekly-acceptance-0-49-x.yml b/.github/workflows/weekly-acceptance-0-49-x.yml index adba13846a..5e1c17f3c7 100644 --- a/.github/workflows/weekly-acceptance-0-49-x.yml +++ b/.github/workflows/weekly-acceptance-0-49-x.yml @@ -10,7 +10,6 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.13-dev # Consul's enterprise version to use in tests BRANCH: "release/0.49.x" CONTEXT: "weekly" @@ -26,4 +25,4 @@ jobs: 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/weekly-acceptance-1-0-x.yml b/.github/workflows/weekly-acceptance-1-0-x.yml index 72769f0ca1..11dda52bed 100644 --- a/.github/workflows/weekly-acceptance-1-0-x.yml +++ b/.github/workflows/weekly-acceptance-1-0-x.yml @@ -11,7 +11,6 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.14-dev # Consul's enterprise version to use in tests BRANCH: "release/1.0.x" CONTEXT: "weekly" @@ -27,4 +26,4 @@ jobs: 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/weekly-acceptance-1-1-x.yml b/.github/workflows/weekly-acceptance-1-1-x.yml index b77da7eff0..86153587b0 100644 --- a/.github/workflows/weekly-acceptance-1-1-x.yml +++ b/.github/workflows/weekly-acceptance-1-1-x.yml @@ -11,7 +11,6 @@ on: # these should be the only settings that you will ever need to change env: - CONSUL_IMAGE: hashicorppreview/consul-enterprise:1.15-dev # Consul's enterprise version to use in tests BRANCH: "release/1.1.x" CONTEXT: "weekly" @@ -27,4 +26,4 @@ jobs: 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 }}", "consul-image":"${{ env.CONSUL_IMAGE }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' From 4d4c35a832fdebc87364c7ca895533ca2838b963 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Wed, 19 Jul 2023 22:57:49 -0400 Subject: [PATCH 289/592] chore: Update actions for security (#2601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes proposed in this PR: - Update actions that are out of date How I've tested this PR: 👀 How I expect reviewers to test this PR: 👀 Checklist: - [ ] Tests added - [ ] [CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) --- .github/workflows/build.yml | 18 +++++++++--------- .github/workflows/changelog-checker.yml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 917456e9d0..1d7942617d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: outputs: go-version: ${{ steps.get-go-version.outputs.go-version }} steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Determine Go version id: get-go-version # We use .go-version as our source of truth for current Go @@ -35,7 +35,7 @@ jobs: outputs: product-version: ${{ steps.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: get product version id: get-product-version run: | @@ -49,7 +49,7 @@ jobs: filepath: ${{ steps.generate-metadata-file.outputs.filepath }} steps: - name: "Checkout directory" - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Generate metadata file id: generate-metadata-file uses: hashicorp/actions-generate-metadata@v1 @@ -121,7 +121,7 @@ jobs: name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} ${{ matrix.fips }} build steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup go uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 @@ -193,7 +193,7 @@ jobs: - name: Test rpm package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: registry.access.redhat.com/ubi9/ubi:latest options: -v ${{ github.workspace }}:/work @@ -218,7 +218,7 @@ jobs: - name: Test debian package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: ubuntu:latest options: -v ${{ github.workspace }}:/work @@ -258,7 +258,7 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos}}_${{ matrix.goarch }}.zip @@ -328,7 +328,7 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_linux_${{ matrix.arch }}.zip @@ -391,7 +391,7 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_linux_${{ matrix.arch }}.zip diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 1eea4196e7..40c9b17c68 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 # by default the checkout action doesn't checkout all branches From a4d9487df5613afc518f2275ca541c3d297c7dca Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Thu, 20 Jul 2023 15:28:59 -0400 Subject: [PATCH 290/592] [NET-4122] Doc guidance for federation with externalServers (#2583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add guidance for proper configuration when joining to a secondary cluster using WAN fed with external servers also enabled. Also clarify federation requirements and fix formatting for an unrelated value. Changes proposed in this PR: - Update base content for generating Helm chart docs to clarify the use case encountered in https://github.com/hashicorp/consul-k8s/issues/2138 - Minor additional fixes - _Follow-up: propagate generated doc changes to `consul` and additionally update https://developer.hashicorp.com/consul/docs/k8s/deployment-configurations/servers-outside-kubernetes there_ How I've tested this PR: N/A (docs only) How I expect reviewers to test this PR: 👀 Checklist: - [ ] Tests added - [ ] [CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) --- charts/consul/values.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 12c45ac958..81065d4bb2 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -535,8 +535,9 @@ global: # If enabled, this datacenter will be federation-capable. Only federation # via mesh gateways is supported. # Mesh gateways and servers will be configured to allow federation. - # Requires `global.tls.enabled`, `meshGateway.enabled` and `connectInject.enabled` - # to be true. Requires Consul 1.8+. + # Requires `global.tls.enabled`, `connectInject.enabled`, and one of + # `meshGateway.enabled` or `externalServers.enabled` to be true. + # Requires Consul 1.8+. enabled: false # If true, the chart will create a Kubernetes secret that can be imported @@ -552,8 +553,8 @@ global: # @type: string primaryDatacenter: null - # A list of addresses of the primary mesh gateways in the form `:`. - # (e.g. ["1.1.1.1:443", "2.3.4.5:443"] + # A list of addresses of the primary mesh gateways in the form `:` + # (e.g. `["1.1.1.1:443", "2.3.4.5:443"]`). # @type: array primaryGateways: [] @@ -564,6 +565,9 @@ global: # from the one used by the Consul Service Mesh. # Please refer to the [Kubernetes Auth Method documentation](https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes). # + # If `externalServers.enabled` is set to true, `global.federation.k8sAuthMethodHost` and + # `externalServers.k8sAuthMethodHost` should be set to the same value. + # # You can retrieve this value from your `kubeconfig` by running: # # ```shell-session @@ -1339,6 +1343,9 @@ externalServers: # This address must be reachable from the Consul servers. # Please refer to the [Kubernetes Auth Method documentation](https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes). # + # If `global.federation.enabled` is set to true, `global.federation.k8sAuthMethodHost` and + # `externalServers.k8sAuthMethodHost` should be set to the same value. + # # You could retrieve this value from your `kubeconfig` by running: # # ```shell-session From 414554c056d5b66131bc0f6a58d6c7ef1b3d29ce Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 20 Jul 2023 16:53:47 -0400 Subject: [PATCH 291/592] Handle errors properly when services are de-registered from the catalog (#2571) - In the past, kubernetes nodes were used as the source of truth to determine the list of services that should exist in Consul. - In most cases this was ok but becomes a problem when nodes are quickly deleted from kubernetes such as the case when using spot instances. - Instead, use consul synthetic-nodes to get the list of services and deregister the services that do not have endpoint addresses. --------- Co-authored-by: mr-miles --- .changelog/2571.txt | 3 + .../endpoints/endpoints_controller.go | 62 ++++++---- .../endpoints/endpoints_controller_test.go | 117 ++++++++++++++++++ 3 files changed, 159 insertions(+), 23 deletions(-) create mode 100644 .changelog/2571.txt diff --git a/.changelog/2571.txt b/.changelog/2571.txt new file mode 100644 index 0000000000..91b3f2943b --- /dev/null +++ b/.changelog/2571.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. +``` diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 7b236792ab..cdf56b187f 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -159,7 +159,6 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } err = r.Client.Get(ctx, req.NamespacedName, &serviceEndpoints) - // endpointPods holds a set of all pods this endpoints object is currently pointing to. // We use this later when we reconcile ACL tokens to decide whether an ACL token in Consul // is for a pod that no longer exists. @@ -183,7 +182,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // It is possible that the endpoints object has never been registered, in which case deregistration is a no-op. if isLabeledIgnore(serviceEndpoints.Labels) { // We always deregister the service to handle the case where a user has registered the service, then added the label later. - r.Log.Info("Ignoring endpoint labeled with `consul.hashicorp.com/service-ignore: \"true\"`", "name", req.Name, "namespace", req.Namespace) + r.Log.Info("ignoring endpoint labeled with `consul.hashicorp.com/service-ignore: \"true\"`", "name", req.Name, "namespace", req.Namespace) err = r.deregisterService(apiClient, req.Name, req.Namespace, nil) return ctrl.Result{}, err } @@ -915,14 +914,14 @@ func getHealthCheckStatusReason(healthCheckStatus, podName, podNamespace string) // them only if they are not in endpointsAddressesMap. If the map is nil, it will deregister all instances. If the map // has addresses, it will only deregister instances not in the map. func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvcNamespace string, endpointsAddressesMap map[string]bool) error { - // Get services matching metadata. - nodesWithSvcs, err := r.serviceInstancesForK8sNodes(apiClient, k8sSvcName, k8sSvcNamespace) + // Get services matching metadata from Consul + nodesWithSvcs, err := r.serviceInstancesForNodes(apiClient, k8sSvcName, k8sSvcNamespace) if err != nil { r.Log.Error(err, "failed to get service instances", "name", k8sSvcName) return err } - // Deregister each service instance that matches the metadata. + var errs error for _, nodeSvcs := range nodesWithSvcs { for _, svc := range nodeSvcs.Services { // We need to get services matching "k8s-service-name" and "k8s-namespace" metadata. @@ -933,42 +932,48 @@ func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvc if _, ok := endpointsAddressesMap[svc.Address]; !ok { // If the service address is not in the Endpoints addresses, deregister it. r.Log.Info("deregistering service from consul", "svc", svc.ID) - _, err = apiClient.Catalog().Deregister(&api.CatalogDeregistration{ + _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{ Node: nodeSvcs.Node.Node, ServiceID: svc.ID, Namespace: svc.Namespace, }, nil) if err != nil { + // Do not exit right away as there might be other services that need to be deregistered. r.Log.Error(err, "failed to deregister service instance", "id", svc.ID) - return err + errs = multierror.Append(errs, err) + } else { + serviceDeregistered = true } - serviceDeregistered = true } } else { r.Log.Info("deregistering service from consul", "svc", svc.ID) - if _, err = apiClient.Catalog().Deregister(&api.CatalogDeregistration{ + _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{ Node: nodeSvcs.Node.Node, ServiceID: svc.ID, Namespace: svc.Namespace, - }, nil); err != nil { + }, nil) + if err != nil { + // Do not exit right away as there might be other services that need to be deregistered. r.Log.Error(err, "failed to deregister service instance", "id", svc.ID) - return err + errs = multierror.Append(errs, err) + } else { + serviceDeregistered = true } - serviceDeregistered = true } if r.AuthMethod != "" && serviceDeregistered { r.Log.Info("reconciling ACL tokens for service", "svc", svc.Service) - err = r.deleteACLTokensForServiceInstance(apiClient, svc, k8sSvcNamespace, svc.Meta[constants.MetaKeyPodName]) + err := r.deleteACLTokensForServiceInstance(apiClient, svc, k8sSvcNamespace, svc.Meta[constants.MetaKeyPodName]) if err != nil { r.Log.Error(err, "failed to reconcile ACL tokens for service", "svc", svc.Service) - return err + errs = multierror.Append(errs, err) } } } } - return nil + return errs + } // deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. @@ -1088,21 +1093,32 @@ func getTokenMetaFromDescription(description string) (map[string]string, error) return tokenMeta, nil } -func (r *Controller) serviceInstancesForK8sNodes(apiClient *api.Client, k8sServiceName, k8sServiceNamespace string) ([]*api.CatalogNodeServiceList, error) { +func (r *Controller) serviceInstancesForNodes(apiClient *api.Client, k8sServiceName, k8sServiceNamespace string) ([]*api.CatalogNodeServiceList, error) { var serviceList []*api.CatalogNodeServiceList - // Get a list of k8s nodes. - var nodeList corev1.NodeList - err := r.Client.List(r.Context, &nodeList) + + // The nodelist may have changed between this point and when the event was raised + // For example, if a pod is evicted because a node has been deleted, there is no guarantee that that node will show up here + // query consul catalog for a list of nodes supporting this service + // quite a lot of results as synthetic nodes are never deregistered. + var nodes []*api.Node + filter := fmt.Sprintf(`Meta[%q] == %q `, "synthetic-node", "true") + nodes, _, err := apiClient.Catalog().Nodes(&api.QueryOptions{Filter: filter, Namespace: namespaces.WildcardNamespace}) if err != nil { return nil, err } - for _, node := range nodeList.Items { + + var errs error + for _, node := range nodes { var nodeServices *api.CatalogNodeServiceList - nodeServices, err = r.serviceInstancesForK8SServiceNameAndNamespace(apiClient, k8sServiceName, k8sServiceNamespace, common.ConsulNodeNameFromK8sNode(node.Name)) - serviceList = append(serviceList, nodeServices) + nodeServices, err := r.serviceInstancesForK8SServiceNameAndNamespace(apiClient, k8sServiceName, k8sServiceNamespace, node.Node) + if err != nil { + errs = multierror.Append(errs, err) + } else { + serviceList = append(serviceList, nodeServices) + } } - return serviceList, err + return serviceList, errs } // serviceInstancesForK8SServiceNameAndNamespace calls Consul's ServicesWithFilter to get the list diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index ea1ce686d6..477be49e9f 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -893,6 +893,9 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { catalogRegistration := &api.CatalogRegistration{ Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: svc, } _, err := consulClient.Catalog().Register(catalogRegistration, nil) @@ -2339,6 +2342,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -2358,6 +2364,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -2444,6 +2453,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -2463,6 +2475,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: "127.0.0.1", + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -2549,6 +2564,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -2566,6 +2584,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -2631,6 +2652,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-different-consul-svc-name", Service: "different-consul-svc-name", @@ -2648,6 +2672,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-different-consul-svc-name-sidecar-proxy", @@ -2721,6 +2748,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -2732,6 +2762,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -2835,6 +2868,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -2846,6 +2882,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -2862,6 +2901,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-service-updated", Service: "service-updated", @@ -2873,6 +2915,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-service-updated-sidecar-proxy", @@ -2932,6 +2977,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-different-consul-svc-name", Service: "different-consul-svc-name", @@ -2943,6 +2991,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-different-consul-svc-name-sidecar-proxy", @@ -2959,6 +3010,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-different-consul-svc-name", Service: "different-consul-svc-name", @@ -2970,6 +3024,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-different-consul-svc-name-sidecar-proxy", @@ -3015,6 +3072,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -3026,6 +3086,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -3042,6 +3105,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-service-updated", Service: "service-updated", @@ -3053,6 +3119,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-service-updated-sidecar-proxy", @@ -3088,6 +3157,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-different-consul-svc-name", Service: "different-consul-svc-name", @@ -3099,6 +3171,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-different-consul-svc-name-sidecar-proxy", @@ -3115,6 +3190,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-different-consul-svc-name", Service: "different-consul-svc-name", @@ -3126,6 +3204,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-different-consul-svc-name-sidecar-proxy", @@ -3174,6 +3255,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -3191,6 +3275,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -3270,6 +3357,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -3287,6 +3377,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -3309,6 +3402,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-service-updated", Service: "service-updated", @@ -3326,6 +3422,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-service-updated-sidecar-proxy", @@ -3409,6 +3508,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -3426,6 +3528,9 @@ func TestReconcileUpdateEndpoint(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -4116,6 +4221,9 @@ func TestReconcileDeleteEndpoint(t *testing.T) { serviceRegistration := &api.CatalogRegistration{ Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: svc, } _, err := consulClient.Catalog().Register(serviceRegistration, nil) @@ -4262,6 +4370,9 @@ func TestReconcileIgnoresServiceIgnoreLabel(t *testing.T) { serviceRegistration := &api.CatalogRegistration{ Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-" + svcName, Service: svcName, @@ -4385,6 +4496,9 @@ func TestReconcile_podSpecifiesExplicitService(t *testing.T) { _, err := consulClient.Catalog().Register(&api.CatalogRegistration{ Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-" + svcName, Service: svcName, @@ -4542,6 +4656,9 @@ func TestServiceInstancesForK8SServiceNameAndNamespace(t *testing.T) { catalogRegistration := &api.CatalogRegistration{ Node: consulNodeName, Address: "127.0.0.1", + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: svc, } _, err = consulClient.Catalog().Register(catalogRegistration, nil) From ff24495eb60406e863a69bc6910d16a671f6873e Mon Sep 17 00:00:00 2001 From: Sujata Roy <61177855+20sr20@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:05:51 -0700 Subject: [PATCH 292/592] Adding support for Enterprise and other improvement on the Customizing Vault Version for WanFed Test (#2481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding support for Enterprise and other improvement on the Customizing Vault Version for WanFed Test This is the extension of the PR - https://github.com/hashicorp/consul-k8s/pull/2043 In this PR, the followings were addressed - 1. Now the vault enterprise version can be provided in the cli command. The previous PR only addressed Vault OSS. 2. Two flags “-no-cleanup-wan-fed” and “test-duration” were introduced to not to cleanup the test environment after successful setup to give it time to do manual testing for features/to reproduce customer issues. Default is 1 hour. 3. This was tested in Kind environment and it works fine. The following was taken out to use the “use-kind” option for WanFed test. //if cfg.UseKind { // t.Skipf("Skipping this test because it's currently flaky on kind") //} * Fix indentation * Fix unit test for deleting gateway w/ consul services * Remove redundant service deregistration code * Exit loop early once registration is found for service * Fix import blocking * Set status on pods added to test * Apply suggestions from code review * Reduce count of test gateways to 10 from 100 --------- Co-authored-by: Nathan Coleman Co-authored-by: Sarah Alsmiller Changes proposed in this PR: - - How I've tested this PR: How I expect reviewers to test this PR: Checklist: - [ ] Tests added - [ ] CHANGELOG entry added > HashiCorp engineers only, community PRs should not add a changelog entry. > Entries should use present tense (e.g. Add support for...) * Removing the changes in vault_namespaces_test.go * Introducing new flag no-cleanup * Removed "go 1.20" from go.work file * cfg.USEKind check is added back * Removed previousy added "Test Duration" flag * Some changes * Some changes --- acceptance/framework/config/config.go | 1 + .../framework/connhelper/connect_helper.go | 8 +- acceptance/framework/consul/cli_cluster.go | 4 +- acceptance/framework/consul/helm_cluster.go | 10 +- acceptance/framework/flags/flags.go | 6 + acceptance/framework/helpers/helpers.go | 4 +- acceptance/framework/k8s/deploy.go | 8 +- acceptance/framework/vault/vault_cluster.go | 31 +- .../api_gateway_external_servers_test.go | 6 +- .../api_gateway_gatewayclassconfig_test.go | 31 +- .../api-gateway/api_gateway_lifecycle_test.go | 12 +- .../api-gateway/api_gateway_tenancy_test.go | 6 +- .../tests/api-gateway/api_gateway_test.go | 15 +- acceptance/tests/cloud/basic_test.go | 5 +- acceptance/tests/cloud/remote_dev_test.go | 4 +- .../config_entries_namespaces_test.go | 2 +- .../config-entries/config_entries_test.go | 2 +- .../connect/connect_external_servers_test.go | 6 +- .../connect/connect_inject_namespaces_test.go | 14 +- .../tests/connect/connect_inject_test.go | 8 +- .../tests/connect/permissive_mtls_test.go | 4 +- .../tests/consul-dns/consul_dns_test.go | 2 +- acceptance/tests/example/example_test.go | 2 +- .../ingress_gateway_namespaces_test.go | 12 +- .../ingress-gateway/ingress_gateway_test.go | 4 +- acceptance/tests/metrics/metrics_test.go | 6 +- .../partitions/partitions_connect_test.go | 48 +-- .../partitions/partitions_gateway_test.go | 24 +- .../tests/partitions/partitions_sync_test.go | 8 +- .../peering_connect_namespaces_test.go | 28 +- .../tests/peering/peering_connect_test.go | 24 +- .../tests/peering/peering_gateway_test.go | 28 +- .../sync/sync_catalog_namespaces_test.go | 4 +- acceptance/tests/sync/sync_catalog_test.go | 6 +- .../terminating_gateway_destinations_test.go | 4 +- .../terminating_gateway_namespaces_test.go | 14 +- .../terminating_gateway_test.go | 4 +- .../tests/vault/vault_namespaces_test.go | 8 +- acceptance/tests/vault/vault_test.go | 8 +- .../tests/vault/vault_tls_auto_reload_test.go | 8 +- acceptance/tests/vault/vault_wan_fed_test.go | 10 +- .../wan_federation_gateway_test.go | 18 +- .../wan-federation/wan_federation_test.go | 6 +- go.work | 11 + go.work.sum | 352 ++++++++++++++++++ 45 files changed, 626 insertions(+), 200 deletions(-) create mode 100644 go.work create mode 100644 go.work.sum diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index eada42af20..74bb7daf6e 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -94,6 +94,7 @@ type TestConfig struct { VaultServerVersion string NoCleanupOnFailure bool + NoCleanup bool DebugDirectory string UseAKS bool diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 670307da88..c34f663563 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -108,11 +108,11 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if c.Cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } // Check that both static-server and static-client have been injected and @@ -140,7 +140,7 @@ func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { kustomizeDir := "../fixtures/cases/resolver-redirect-virtualip" k8s.KubectlApplyK(t, options, kustomizeDir) - helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, options, kustomizeDir) }) } diff --git a/acceptance/framework/consul/cli_cluster.go b/acceptance/framework/consul/cli_cluster.go index 9e119af76d..a45bcc665c 100644 --- a/acceptance/framework/consul/cli_cluster.go +++ b/acceptance/framework/consul/cli_cluster.go @@ -45,6 +45,7 @@ type CLICluster struct { kubeConfig string kubeContext string noCleanupOnFailure bool + noCleanup bool debugDirectory string logger terratestLogger.TestLogger cli cli.CLI @@ -109,6 +110,7 @@ func NewCLICluster( kubeConfig: cfg.GetPrimaryKubeEnv().KubeConfig, kubeContext: cfg.GetPrimaryKubeEnv().KubeContext, noCleanupOnFailure: cfg.NoCleanupOnFailure, + noCleanup: cfg.NoCleanup, debugDirectory: cfg.DebugDirectory, logger: logger, cli: *cli, @@ -122,7 +124,7 @@ func (c *CLICluster) Create(t *testing.T) { // Make sure we delete the cluster if we receive an interrupt signal and // register cleanup so that we delete the cluster when test finishes. - helpers.Cleanup(t, c.noCleanupOnFailure, func() { + helpers.Cleanup(t, c.noCleanupOnFailure, c.noCleanup, func() { c.Destroy(t) }) diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index aa6fdef7d8..81787ebfc3 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -52,6 +52,7 @@ type HelmCluster struct { runtimeClient client.Client kubernetesClient kubernetes.Interface noCleanupOnFailure bool + noCleanup bool debugDirectory string logger terratestLogger.TestLogger } @@ -107,6 +108,7 @@ func NewHelmCluster( runtimeClient: ctx.ControllerRuntimeClient(t), kubernetesClient: ctx.KubernetesClient(t), noCleanupOnFailure: cfg.NoCleanupOnFailure, + noCleanup: cfg.NoCleanup, debugDirectory: cfg.DebugDirectory, logger: logger, } @@ -117,7 +119,7 @@ func (h *HelmCluster) Create(t *testing.T) { // Make sure we delete the cluster if we receive an interrupt signal and // register cleanup so that we delete the cluster when test finishes. - helpers.Cleanup(t, h.noCleanupOnFailure, func() { + helpers.Cleanup(t, h.noCleanupOnFailure, h.noCleanup, func() { h.Destroy(t) }) @@ -508,7 +510,7 @@ func configurePodSecurityPolicies(t *testing.T, client kubernetes.Interface, cfg } } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { _ = client.PolicyV1beta1().PodSecurityPolicies().Delete(context.Background(), pspName, metav1.DeleteOptions{}) _ = client.RbacV1().ClusterRoles().Delete(context.Background(), pspName, metav1.DeleteOptions{}) _ = client.RbacV1().RoleBindings(namespace).Delete(context.Background(), pspName, metav1.DeleteOptions{}) @@ -559,7 +561,7 @@ func configureSCCs(t *testing.T, client kubernetes.Interface, cfg *config.TestCo } } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { _ = client.RbacV1().RoleBindings(namespace).Delete(context.Background(), anyuidRoleBinding, metav1.DeleteOptions{}) _ = client.RbacV1().RoleBindings(namespace).Delete(context.Background(), privilegedRoleBinding, metav1.DeleteOptions{}) }) @@ -601,7 +603,7 @@ func CreateK8sSecret(t *testing.T, client kubernetes.Interface, cfg *config.Test } }) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { _ = client.CoreV1().Secrets(namespace).Delete(context.Background(), secretName, metav1.DeleteOptions{}) }) } diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index ad36099b37..b1ad7e4332 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -45,6 +45,7 @@ type TestFlags struct { flagHCPResourceID string flagNoCleanupOnFailure bool + flagNoCleanup bool flagDebugDirectory string @@ -124,6 +125,9 @@ func (t *TestFlags) init() { "If true, the tests will not cleanup Kubernetes resources they create when they finish running."+ "Note this flag must be run with -failfast flag, otherwise subsequent tests will fail.") + flag.BoolVar(&t.flagNoCleanup, "no-cleanup", false, + "If true, the tests will not cleanup Kubernetes resources for Vault test") + flag.StringVar(&t.flagDebugDirectory, "debug-directory", "", "The directory where to write debug information about failed test runs, "+ "such as logs and pod definitions. If not provided, a temporary directory will be created by the tests.") @@ -185,6 +189,7 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { kubeEnvs := config.NewKubeTestConfigList(t.flagKubeconfigs, t.flagKubecontexts, t.flagKubeNamespaces) c := &config.TestConfig{ + EnableEnterprise: t.flagEnableEnterprise, EnterpriseLicense: t.flagEnterpriseLicense, @@ -215,6 +220,7 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { HCPResourceID: t.flagHCPResourceID, NoCleanupOnFailure: t.flagNoCleanupOnFailure, + NoCleanup: t.flagNoCleanup, DebugDirectory: tempDir, UseAKS: t.flagUseAKS, UseEKS: t.flagUseEKS, diff --git a/acceptance/framework/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index 3e6ef039b2..f8b1d2f15d 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -87,7 +87,7 @@ func SetupInterruptHandler(cleanup func()) { // Cleanup will both register a cleanup function with t // and SetupInterruptHandler to make sure resources get cleaned up // if an interrupt signal is caught. -func Cleanup(t *testing.T, noCleanupOnFailure bool, cleanup func()) { +func Cleanup(t *testing.T, noCleanupOnFailure bool, noCleanup bool, cleanup func()) { t.Helper() // Always clean up when an interrupt signal is caught. @@ -97,7 +97,7 @@ func Cleanup(t *testing.T, noCleanupOnFailure bool, cleanup func()) { // We need to wrap the cleanup function because t that is passed in to this function // might not have the information on whether the test has failed yet. wrappedCleanupFunc := func() { - if !(noCleanupOnFailure && t.Failed()) { + if !((noCleanupOnFailure && t.Failed()) || noCleanup) { logger.Logf(t, "cleaning up resources for %s", t.Name()) cleanup() } else { diff --git a/acceptance/framework/k8s/deploy.go b/acceptance/framework/k8s/deploy.go index 6834284c33..828586c8cf 100644 --- a/acceptance/framework/k8s/deploy.go +++ b/acceptance/framework/k8s/deploy.go @@ -21,7 +21,7 @@ import ( // Deploy creates a Kubernetes deployment by applying configuration stored at filepath, // sets up a cleanup function and waits for the deployment to become available. -func Deploy(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, debugDirectory string, filepath string) { +func Deploy(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, filepath string) { t.Helper() KubectlApply(t, options, filepath) @@ -33,7 +33,7 @@ func Deploy(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, err = yaml.NewYAMLOrJSONDecoder(file, 1024).Decode(&deployment) require.NoError(t, err) - helpers.Cleanup(t, noCleanupOnFailure, func() { + helpers.Cleanup(t, noCleanupOnFailure, noCleanup, func() { // Note: this delete command won't wait for pods to be fully terminated. // This shouldn't cause any test pollution because the underlying // objects are deployments, and so when other tests create these @@ -47,7 +47,7 @@ func Deploy(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, // DeployKustomize creates a Kubernetes deployment by applying the kustomize directory stored at kustomizeDir, // sets up a cleanup function and waits for the deployment to become available. -func DeployKustomize(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, debugDirectory string, kustomizeDir string) { +func DeployKustomize(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, kustomizeDir string) { t.Helper() KubectlApplyK(t, options, kustomizeDir) @@ -59,7 +59,7 @@ func DeployKustomize(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailu err = yaml.NewYAMLOrJSONDecoder(strings.NewReader(output), 1024).Decode(&deployment) require.NoError(t, err) - helpers.Cleanup(t, noCleanupOnFailure, func() { + helpers.Cleanup(t, noCleanupOnFailure, noCleanup, func() { // Note: this delete command won't wait for pods to be fully terminated. // This shouldn't cause any test pollution because the underlying // objects are deployments, and so when other tests create these diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index e0030490c9..4dc832bcb6 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -6,6 +6,8 @@ package vault import ( "context" "fmt" + "os" + "strings" "testing" "time" @@ -13,6 +15,7 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" terratestLogger "github.com/gruntwork-io/terratest/modules/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/config" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" @@ -44,6 +47,7 @@ type VaultCluster struct { kubernetesClient kubernetes.Interface noCleanupOnFailure bool + noCleanup bool debugDirectory string logger terratestLogger.TestLogger } @@ -54,12 +58,32 @@ func NewVaultCluster(t *testing.T, ctx environment.TestContext, cfg *config.Test logger := terratestLogger.New(logger.TestLogger{}) kopts := ctx.KubectlOptions(t) + ns := ctx.KubectlOptions(t).Namespace + + entstr := "-ent" values := defaultHelmValues(releaseName) if cfg.EnablePodSecurityPolicies { values["global.psp.enable"] = "true" } + vaultReleaseName := helpers.RandomName() + k8sClient := environment.KubernetesClientFromOptions(t, ctx.KubectlOptions(t)) + vaultLicenseSecretName := fmt.Sprintf("%s-enterprise-license", vaultReleaseName) + vaultLicenseSecretKey := "license" + + vaultEnterpriseLicense := os.Getenv("VAULT_LICENSE") + if cfg.VaultServerVersion != "" { + + if strings.Contains(cfg.VaultServerVersion, entstr) { + + logger.Logf(t, "Creating secret for Vault license") + consul.CreateK8sSecret(t, k8sClient, cfg, ns, vaultLicenseSecretName, vaultLicenseSecretKey, vaultEnterpriseLicense) + + values["server.image.repository"] = "docker.mirror.hashicorp.services/hashicorp/vault-enterprise" + values["server.enterpriseLicense.secretName"] = vaultLicenseSecretName + values["server.enterpriseLicense.secretKey"] = vaultLicenseSecretKey + } values["server.image.tag"] = cfg.VaultServerVersion } vaultHelmChartVersion := defaultVaultHelmChartVersion @@ -89,6 +113,7 @@ func NewVaultCluster(t *testing.T, ctx environment.TestContext, cfg *config.Test kubectlOptions: kopts, kubernetesClient: ctx.KubernetesClient(t), noCleanupOnFailure: cfg.NoCleanupOnFailure, + noCleanup: cfg.NoCleanup, debugDirectory: cfg.DebugDirectory, logger: logger, releaseName: releaseName, @@ -224,7 +249,7 @@ func (v *VaultCluster) Create(t *testing.T, ctx environment.TestContext, vaultNa // Make sure we delete the cluster if we receive an interrupt signal and // register cleanup so that we delete the cluster when test finishes. - helpers.Cleanup(t, v.noCleanupOnFailure, func() { + helpers.Cleanup(t, v.noCleanupOnFailure, v.noCleanup, func() { v.Destroy(t) }) @@ -346,7 +371,7 @@ func (v *VaultCluster) createTLSCerts(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { - if !v.noCleanupOnFailure { + if !(v.noCleanupOnFailure || v.noCleanup) { // We're ignoring error here because secret deletion is best-effort. _ = v.kubernetesClient.CoreV1().Secrets(namespace).Delete(context.Background(), certSecretName(v.releaseName), metav1.DeleteOptions{}) _ = v.kubernetesClient.CoreV1().Secrets(namespace).Delete(context.Background(), CASecretName(v.releaseName), metav1.DeleteOptions{}) @@ -419,7 +444,7 @@ func (v *VaultCluster) initAndUnseal(t *testing.T) { rootTokenSecret := fmt.Sprintf("%s-vault-root-token", v.releaseName) v.logger.Logf(t, "saving Vault root token to %q Kubernetes secret", rootTokenSecret) - helpers.Cleanup(t, v.noCleanupOnFailure, func() { + helpers.Cleanup(t, v.noCleanupOnFailure, v.noCleanup, func() { _ = v.kubernetesClient.CoreV1().Secrets(namespace).Delete(context.Background(), rootTokenSecret, metav1.DeleteOptions{}) }) _, err := v.kubernetesClient.CoreV1().Secrets(namespace).Create(context.Background(), &corev1.Secret{ diff --git a/acceptance/tests/api-gateway/api_gateway_external_servers_test.go b/acceptance/tests/api-gateway/api_gateway_external_servers_test.go index c0fa8bbcca..d14ef59990 100644 --- a/acceptance/tests/api-gateway/api_gateway_external_servers_test.go +++ b/acceptance/tests/api-gateway/api_gateway_external_servers_test.go @@ -60,8 +60,8 @@ func TestAPIGateway_ExternalServers(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") // Override the default proxy config settings for this test consulClient, _ := consulCluster.SetupConsulClient(t, true, serverReleaseName) @@ -79,7 +79,7 @@ func TestAPIGateway_ExternalServers(t *testing.T) { logger.Log(t, "creating api-gateway resources") out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") diff --git a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go index 444af6af4d..ffe0424ef1 100644 --- a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go +++ b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go @@ -63,7 +63,23 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { k8sClient := ctx.ControllerRuntimeClient(t) - // Create a GatewayClassConfig. + //create clean namespace + err = k8sClient.Create(context.Background(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }) + require.NoError(t, err) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + logger.Log(t, "deleting gateway namesapce") + k8sClient.Delete(context.Background(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }) + }) + + // create a GatewayClassConfig with configuration set gatewayClassConfigName := "gateway-class-config" gatewayClassConfig := &v1alpha1.GatewayClassConfig{ ObjectMeta: metav1.ObjectMeta{ @@ -80,7 +96,7 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { logger.Log(t, "creating gateway class config") err = k8sClient.Create(context.Background(), gatewayClassConfig) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all gateway class configs") k8sClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}) }) @@ -94,7 +110,7 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { // Create gateway class referencing gateway-class-config. logger.Log(t, "creating controlled gateway class") createGatewayClass(t, k8sClient, gatewayClassName, gatewayClassControllerName, gatewayParametersRef) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all gateway classes") k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}) }) @@ -119,7 +135,7 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { logger.Log(t, "creating certificate") err = k8sClient.Create(context.Background(), certificate) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8sClient.Delete(context.Background(), certificate) }) @@ -127,7 +143,12 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { gatewayName := "gcctestgateway" + namespace logger.Log(t, "creating controlled gateway") gateway := createGateway(t, k8sClient, gatewayName, namespace, gatewayClassName, certificateName) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + + // make sure it exists + logger.Log(t, "checking that gateway one is synchronized to Consul") + checkConsulExists(t, consulClient, api.APIGateway, gatewayName) + + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all gateways") k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(namespace)) }) diff --git a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go index 08712815cc..e3ffa992ce 100644 --- a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go +++ b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go @@ -44,7 +44,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { // create a service to target targetName := "static-server" logger.Log(t, "creating target server") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // create a basic GatewayClassConfig gatewayClassConfigName := "controlled-gateway-class-config" @@ -56,7 +56,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { logger.Log(t, "creating gateway class config") err := k8sClient.Create(context.Background(), gatewayClassConfig) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all gateway class configs") k8sClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}) }) @@ -72,7 +72,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { logger.Log(t, "creating controlled gateway class one") createGatewayClass(t, k8sClient, controlledGatewayClassOneName, gatewayClassControllerName, gatewayParametersRef) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all gateway classes") k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}) }) @@ -105,7 +105,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { logger.Log(t, "creating certificate") err = k8sClient.Create(context.Background(), certificate) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8sClient.Delete(context.Background(), certificate) }) @@ -114,7 +114,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { logger.Log(t, "creating controlled gateway one") controlledGatewayOne := createGateway(t, k8sClient, controlledGatewayOneName, defaultNamespace, controlledGatewayClassOneName, certificateName) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all gateways") k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(defaultNamespace)) }) @@ -132,7 +132,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { logger.Log(t, "creating route one") routeOne := createRoute(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayOneName, targetName) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all http routes") k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.HTTPRoute{}, client.InNamespace(defaultNamespace)) }) diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go index 716f09bdba..19e85d60b0 100644 --- a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go +++ b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go @@ -100,7 +100,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { routeNamespace, routeK8SOptions := createNamespace(t, ctx, cfg) logger.Logf(t, "creating target server in %s namespace", serviceNamespace) - k8s.DeployKustomize(t, serviceK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, serviceK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Logf(t, "creating certificate resources in %s namespace", certificateNamespace) applyFixture(t, cfg, certificateK8SOptions, "cases/api-gateways/certificate") @@ -255,7 +255,7 @@ func applyFixture(t *testing.T, cfg *config.TestConfig, k8sOptions *terratestk8s out, err := k8s.RunKubectlAndGetOutputE(t, k8sOptions, "apply", "-k", path.Join("../fixtures", fixture)) require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectlAndGetOutputE(t, k8sOptions, "delete", "-k", path.Join("../fixtures", fixture)) }) } @@ -267,7 +267,7 @@ func createNamespace(t *testing.T, ctx environment.TestContext, cfg *config.Test logger.Logf(t, "creating Kubernetes namespace %s", namespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", namespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", namespace) }) diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index 143b793bf8..4b4db38afe 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -7,11 +7,12 @@ import ( "context" "encoding/base64" "fmt" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "strconv" "testing" "time" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" @@ -75,7 +76,7 @@ func TestAPIGateway_Basic(t *testing.T) { logger.Log(t, "creating api-gateway resources") out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") @@ -86,7 +87,7 @@ func TestAPIGateway_Basic(t *testing.T) { logger.Log(t, "creating certificate secret") out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/bases/api-gateway/certificate.yaml") require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/bases/api-gateway/certificate.yaml") @@ -98,17 +99,17 @@ func TestAPIGateway_Basic(t *testing.T) { k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") logger.Log(t, "creating target http server") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "patching route to target http server") k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"name":"static-server","port":80}]}]}}`, "--type=merge") logger.Log(t, "creating target tcp server") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server-tcp") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server-tcp") logger.Log(t, "creating tcp-route") k8s.RunKubectl(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/cases/api-gateways/tcproute/route.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/cases/api-gateways/tcproute/route.yaml") @@ -117,7 +118,7 @@ func TestAPIGateway_Basic(t *testing.T) { // We use the static-client pod so that we can make calls to the api gateway // via kubectl exec without needing a route into the cluster from the test machine. logger.Log(t, "creating static-client pod") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") // Grab a kubernetes client so that we can verify binding // behavior prior to issuing requests through the gateway. diff --git a/acceptance/tests/cloud/basic_test.go b/acceptance/tests/cloud/basic_test.go index 0b5e3da719..e17169700a 100644 --- a/acceptance/tests/cloud/basic_test.go +++ b/acceptance/tests/cloud/basic_test.go @@ -113,7 +113,7 @@ func TestBasicCloud(t *testing.T) { consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/cloud/hcp-mock") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/cloud/hcp-mock") podName, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "get", "pod", "-l", "app=fake-server", "-o", `jsonpath="{.items[0].metadata.name}"`) podName = strings.ReplaceAll(podName, "\"", "") if err != nil { @@ -228,7 +228,8 @@ func TestBasicCloud(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") t.Log("Finished deployment. Validating expected conditions now") // Give some time for collector send metrics time.Sleep(5 * time.Second) diff --git a/acceptance/tests/cloud/remote_dev_test.go b/acceptance/tests/cloud/remote_dev_test.go index aa7dbe70c7..457dc4f269 100644 --- a/acceptance/tests/cloud/remote_dev_test.go +++ b/acceptance/tests/cloud/remote_dev_test.go @@ -173,12 +173,12 @@ func TestRemoteDevCloud(t *testing.T) { logger.Log(t, "setting acl permissions for collector and services") aclDir := "../fixtures/bases/cloud/service-intentions" k8s.KubectlApplyK(t, ctx.KubectlOptions(t), aclDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), aclDir) }) logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") time.Sleep(1 * time.Hour) // TODO: add in test assertions here diff --git a/acceptance/tests/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index 91d0c69df4..a013c99e09 100644 --- a/acceptance/tests/config-entries/config_entries_namespaces_test.go +++ b/acceptance/tests/config-entries/config_entries_namespaces_test.go @@ -102,7 +102,7 @@ func TestControllerNamespaces(t *testing.T) { if err != nil && !strings.Contains(out, "(AlreadyExists)") { require.NoError(t, err) } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", KubeNS) }) diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index e37e3d6c7f..a36e9baaf5 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -86,7 +86,7 @@ func TestController(t *testing.T) { // endpoint fails initially. out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/crds-oss") require.NoError(r, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/crds-oss") diff --git a/acceptance/tests/connect/connect_external_servers_test.go b/acceptance/tests/connect/connect_external_servers_test.go index a7b0f656bf..c421c1258e 100644 --- a/acceptance/tests/connect/connect_external_servers_test.go +++ b/acceptance/tests/connect/connect_external_servers_test.go @@ -73,11 +73,11 @@ func TestConnectInject_ExternalServers(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } // Check that both static-server and static-client have been injected and now have 2 containers. diff --git a/acceptance/tests/connect/connect_inject_namespaces_test.go b/acceptance/tests/connect/connect_inject_namespaces_test.go index f848594cd2..6fb493ce17 100644 --- a/acceptance/tests/connect/connect_inject_namespaces_test.go +++ b/acceptance/tests/connect/connect_inject_namespaces_test.go @@ -100,12 +100,12 @@ func TestConnectInjectNamespaces(t *testing.T) { logger.Logf(t, "creating namespaces %s and %s", staticServerNamespace, StaticClientNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Note: this deletion will take longer in cases when the static-client deployment // hasn't yet fully terminated. k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", StaticClientNamespace) @@ -146,11 +146,11 @@ func TestConnectInjectNamespaces(t *testing.T) { } logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") } // Check that both static-server and static-client have been injected and now have 2 containers. @@ -303,7 +303,7 @@ func TestConnectInjectNamespaces_CleanupController(t *testing.T) { logger.Logf(t, "creating namespace %s", StaticClientNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", StaticClientNamespace) }) @@ -313,7 +313,7 @@ func TestConnectInjectNamespaces_CleanupController(t *testing.T) { ConfigPath: ctx.KubectlOptions(t).ConfigPath, Namespace: StaticClientNamespace, } - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") logger.Log(t, "waiting for static-client to be registered with Consul") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index 199bbf0f1b..c239698d2a 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -107,7 +107,7 @@ func TestConnectInject_CleanupKilledPods(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating static-client deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") logger.Log(t, "waiting for static-client to be registered with Consul") consulClient, _ := consulCluster.SetupConsulClient(t, secure) @@ -200,8 +200,8 @@ func TestConnectInject_MultiportServices(t *testing.T) { } logger.Log(t, "creating multiport static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/multiport-app") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject-multiport") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/multiport-app") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject-multiport") // Check that static-client has been injected and now has 2 containers. podList, err := ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ @@ -260,7 +260,7 @@ func TestConnectInject_MultiportServices(t *testing.T) { // pod to static-server. // Deploy static-server. - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // For outbound connections from the multi port pod, only intentions from the first service in the multiport // pod need to be created, since all upstream connections are made through the first service's envoy proxy. diff --git a/acceptance/tests/connect/permissive_mtls_test.go b/acceptance/tests/connect/permissive_mtls_test.go index 1dcc6fe911..d07c44ae2f 100644 --- a/acceptance/tests/connect/permissive_mtls_test.go +++ b/acceptance/tests/connect/permissive_mtls_test.go @@ -58,7 +58,7 @@ func deployNonMeshClient(t *testing.T, ch connhelper.ConnectHelper) { t.Helper() logger.Log(t, "Creating static-client deployment with connect-inject=false") - k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), ch.Cfg.NoCleanupOnFailure, ch.Cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), ch.Cfg.NoCleanupOnFailure, ch.Cfg.NoCleanup, ch.Cfg.DebugDirectory, "../fixtures/bases/static-client") requirePodContainers(t, ch, "app=static-client", 1) } @@ -66,7 +66,7 @@ func deployStaticServer(t *testing.T, cfg *config.TestConfig, ch connhelper.Conn t.Helper() logger.Log(t, "Creating static-server deployment with connect-inject=true") - k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") requirePodContainers(t, ch, "app=static-server", 2) } diff --git a/acceptance/tests/consul-dns/consul_dns_test.go b/acceptance/tests/consul-dns/consul_dns_test.go index c33de7747d..84741175af 100644 --- a/acceptance/tests/consul-dns/consul_dns_test.go +++ b/acceptance/tests/consul-dns/consul_dns_test.go @@ -65,7 +65,7 @@ func TestConsulDNS(t *testing.T) { "run", "-it", dnsPodName, "--restart", "Never", "--image", "anubhavmishra/tiny-tools", "--", "dig", fmt.Sprintf("@%s-consul-dns", releaseName), "consul.service.consul", } - helpers.Cleanup(t, suite.Config().NoCleanupOnFailure, func() { + helpers.Cleanup(t, suite.Config().NoCleanupOnFailure, suite.Config().NoCleanup, func() { // Note: this delete command won't wait for pods to be fully terminated. // This shouldn't cause any test pollution because the underlying // objects are deployments, and so when other tests create these diff --git a/acceptance/tests/example/example_test.go b/acceptance/tests/example/example_test.go index b324ac31fe..07b04d6097 100644 --- a/acceptance/tests/example/example_test.go +++ b/acceptance/tests/example/example_test.go @@ -44,7 +44,7 @@ func TestExample(t *testing.T) { k8s.KubectlApply(t, ctx.KubectlOptions(t), "path/to/config") // Clean up any Kubernetes resources you have created - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDelete(t, ctx.KubectlOptions(t), "path/to/config") }) diff --git a/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go b/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go index ec4878df04..9edb1db010 100644 --- a/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go +++ b/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go @@ -69,7 +69,7 @@ func TestIngressGatewaySingleNamespace(t *testing.T) { logger.Logf(t, "creating Kubernetes namespace %s", testNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", testNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", testNamespace) }) @@ -80,12 +80,12 @@ func TestIngressGatewaySingleNamespace(t *testing.T) { } logger.Logf(t, "creating server in %s namespace", testNamespace) - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // We use the static-client pod so that we can make calls to the ingress gateway // via kubectl exec without needing a route into the cluster from the test machine. logger.Logf(t, "creating static-client in %s namespace", testNamespace) - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") // With the cluster up, we can create our ingress-gateway config entry. logger.Log(t, "creating config entry") @@ -188,7 +188,7 @@ func TestIngressGatewayNamespaceMirroring(t *testing.T) { logger.Logf(t, "creating Kubernetes namespace %s", testNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", testNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", testNamespace) }) @@ -199,12 +199,12 @@ func TestIngressGatewayNamespaceMirroring(t *testing.T) { } logger.Logf(t, "creating server in %s namespace", testNamespace) - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // We use the static-client pod so that we can make calls to the ingress gateway // via kubectl exec without needing a route into the cluster from the test machine. logger.Logf(t, "creating static-client in %s namespace", testNamespace) - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) diff --git a/acceptance/tests/ingress-gateway/ingress_gateway_test.go b/acceptance/tests/ingress-gateway/ingress_gateway_test.go index b5df6287b6..d2b3d7193e 100644 --- a/acceptance/tests/ingress-gateway/ingress_gateway_test.go +++ b/acceptance/tests/ingress-gateway/ingress_gateway_test.go @@ -52,12 +52,12 @@ func TestIngressGateway(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating server") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // We use the static-client pod so that we can make calls to the ingress gateway // via kubectl exec without needing a route into the cluster from the test machine. logger.Log(t, "creating static-client pod") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") // With the cluster up, we can create our ingress-gateway config entry. logger.Log(t, "creating config entry") diff --git a/acceptance/tests/metrics/metrics_test.go b/acceptance/tests/metrics/metrics_test.go index 2a130f38db..3d40841e36 100644 --- a/acceptance/tests/metrics/metrics_test.go +++ b/acceptance/tests/metrics/metrics_test.go @@ -71,7 +71,7 @@ func TestComponentMetrics(t *testing.T) { // This simulates queries that would be made by a prometheus server that runs externally to the consul // components in the cluster. logger.Log(t, "creating static-client") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") // Server Metrics metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:8500/v1/agent/metrics?format=prometheus", fmt.Sprintf("%s-consul-server.%s.svc", releaseName, ns))) @@ -116,13 +116,13 @@ func TestAppMetrics(t *testing.T) { // Deploy service that will emit app and envoy metrics at merged metrics endpoint logger.Log(t, "creating static-metrics-app") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-metrics-app") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-metrics-app") // Create the static-client deployment so we can use it for in-cluster calls to metrics endpoints. // This simulates queries that would be made by a prometheus server that runs externally to the consul // components in the cluster. logger.Log(t, "creating static-client") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") // Merged App Metrics podList, err := ctx.KubernetesClient(t).CoreV1().Pods(ns).List(context.Background(), metav1.ListOptions{LabelSelector: "app=static-metrics-app"}) diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index aa73c17047..6b103614c7 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -204,14 +204,14 @@ func TestPartitions_Connect(t *testing.T) { logger.Logf(t, "creating namespaces %s and %s in servers cluster", staticServerNamespace, StaticClientNamespace) k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) }) logger.Logf(t, "creating namespaces %s and %s in clients cluster", staticServerNamespace, StaticClientNamespace) k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) }) @@ -271,37 +271,37 @@ func TestPartitions_Connect(t *testing.T) { kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) }) k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) }) // This section of the tests runs the in-partition networking tests. t.Run("in-partition", func(t *testing.T) { logger.Log(t, "test in-partition networking") logger.Log(t, "creating static-server and static-client deployments in server cluster") - k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } else { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") } } logger.Log(t, "creating static-server and static-client deployments in client cluster") - k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } else { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") } } // Check that both static-server and static-client have been injected and now have 2 containers in server cluster. @@ -383,7 +383,7 @@ func TestPartitions_Connect(t *testing.T) { require.NoError(t, err) _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: secondaryPartition}) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { _, err := consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: defaultPartition}) require.NoError(t, err) _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: secondaryPartition}) @@ -424,25 +424,25 @@ func TestPartitions_Connect(t *testing.T) { t.Run("cross-partition", func(t *testing.T) { logger.Log(t, "test cross-partition networking") logger.Log(t, "creating static-server and static-client deployments in server cluster") - k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/default-ns-partition") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/default-ns-partition") } else { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/ns-partition") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/ns-partition") } } logger.Log(t, "creating static-server and static-client deployments in client cluster") - k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/default-ns-default-partition") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/default-ns-default-partition") } else { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/ns-default-partition") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/ns-default-partition") } } // Check that both static-server and static-client have been injected and now have 2 containers in server cluster. @@ -496,14 +496,14 @@ func TestPartitions_Connect(t *testing.T) { if c.destinationNamespace == defaultNamespace { k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-default") k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/secondary-partition-default") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-default") k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/secondary-partition-default") }) } else { k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/secondary-partition-ns1") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/secondary-partition-ns1") }) @@ -551,7 +551,7 @@ func TestPartitions_Connect(t *testing.T) { intention.Sources[0].Partition = defaultPartition _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: secondaryPartition}) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { _, err := consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: defaultPartition}) require.NoError(t, err) _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: secondaryPartition}) diff --git a/acceptance/tests/partitions/partitions_gateway_test.go b/acceptance/tests/partitions/partitions_gateway_test.go index 5c85e6725b..acdb81fa65 100644 --- a/acceptance/tests/partitions/partitions_gateway_test.go +++ b/acceptance/tests/partitions/partitions_gateway_test.go @@ -147,14 +147,14 @@ func TestPartitions_Gateway(t *testing.T) { logger.Logf(t, "creating namespaces %s and %s in servers cluster", staticServerNamespace, StaticClientNamespace) k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) }) logger.Logf(t, "creating namespaces %s and %s in clients cluster", staticServerNamespace, StaticClientNamespace) k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) }) @@ -202,12 +202,12 @@ func TestPartitions_Gateway(t *testing.T) { kustomizeDir := "../fixtures/cases/api-gateways/mesh" k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) }) k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) }) @@ -216,12 +216,12 @@ func TestPartitions_Gateway(t *testing.T) { // Since we're deploying the gateway in the secondary cluster, we create the static client // in the secondary as well. logger.Log(t, "creating static-client pod in secondary partition cluster") - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") logger.Log(t, "creating api-gateway resources") out, err := k8s.RunKubectlAndGetOutputE(t, secondaryPartitionClusterStaticServerOpts, "apply", "-k", "../fixtures/bases/api-gateway") require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, secondaryPartitionClusterStaticServerOpts, "delete", "-k", "../fixtures/bases/api-gateway") @@ -253,7 +253,7 @@ func TestPartitions_Gateway(t *testing.T) { t.Run("in-partition", func(t *testing.T) { logger.Log(t, "test in-partition networking") logger.Log(t, "creating target server in secondary partition cluster") - k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "patching route to target server") k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") @@ -277,7 +277,7 @@ func TestPartitions_Gateway(t *testing.T) { logger.Log(t, "creating intention") _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: secondaryPartition}) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: secondaryPartition}) require.NoError(t, err) }) @@ -291,17 +291,17 @@ func TestPartitions_Gateway(t *testing.T) { logger.Log(t, "test cross-partition networking") logger.Log(t, "creating target server in default partition cluster") - k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating exported services") k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") }) logger.Log(t, "creating local service resolver") k8s.KubectlApplyK(t, secondaryPartitionClusterStaticServerOpts, "../fixtures/cases/api-gateways/resolver") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, secondaryPartitionClusterStaticServerOpts, "../fixtures/cases/api-gateways/resolver") }) @@ -328,7 +328,7 @@ func TestPartitions_Gateway(t *testing.T) { logger.Log(t, "creating intention") _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: defaultPartition}) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: defaultPartition}) require.NoError(t, err) }) diff --git a/acceptance/tests/partitions/partitions_sync_test.go b/acceptance/tests/partitions/partitions_sync_test.go index 8eaaff099e..da95d7f272 100644 --- a/acceptance/tests/partitions/partitions_sync_test.go +++ b/acceptance/tests/partitions/partitions_sync_test.go @@ -195,13 +195,13 @@ func TestPartitions_Sync(t *testing.T) { logger.Logf(t, "creating namespaces %s in servers cluster", staticServerNamespace) k8s.RunKubectl(t, primaryClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, primaryClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) logger.Logf(t, "creating namespaces %s in clients cluster", staticServerNamespace) k8s.RunKubectl(t, secondaryClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, secondaryClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) @@ -241,9 +241,9 @@ func TestPartitions_Sync(t *testing.T) { logger.Log(t, "creating a static-server with a service") // create service in default partition. - k8s.DeployKustomize(t, primaryStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, primaryStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") // create service in secondary partition. - k8s.DeployKustomize(t, secondaryStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, secondaryStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") logger.Log(t, "checking that the service has been synced to Consul") var services map[string][]string diff --git a/acceptance/tests/peering/peering_connect_namespaces_test.go b/acceptance/tests/peering/peering_connect_namespaces_test.go index 622e547091..618248c6a9 100644 --- a/acceptance/tests/peering/peering_connect_namespaces_test.go +++ b/acceptance/tests/peering/peering_connect_namespaces_test.go @@ -166,12 +166,12 @@ func TestPeering_ConnectNamespaces(t *testing.T) { kustomizeMeshDir := "../fixtures/bases/mesh-peering" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) @@ -198,7 +198,7 @@ func TestPeering_ConnectNamespaces(t *testing.T) { // Create the peering acceptor on the client peer. k8s.KubectlApply(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDelete(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") }) @@ -214,7 +214,7 @@ func TestPeering_ConnectNamespaces(t *testing.T) { // Create the peering dialer on the server peer. k8s.KubectlApply(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "secret", "api-token") k8s.KubectlDelete(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") }) @@ -232,13 +232,13 @@ func TestPeering_ConnectNamespaces(t *testing.T) { logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) }) @@ -256,26 +256,26 @@ func TestPeering_ConnectNamespaces(t *testing.T) { kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) }) logger.Log(t, "creating static-server in server peer") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client deployments in client peer") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/default-namespace") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/default-namespace") } else { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/non-default-namespace") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/non-default-namespace") } } // Check that both static-server and static-client have been injected and now have 2 containers. @@ -312,12 +312,12 @@ func TestPeering_ConnectNamespaces(t *testing.T) { logger.Log(t, "creating exported services") if c.destinationNamespace == defaultNamespace { k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/default-namespace") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/default-namespace") }) } else { k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") }) } diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index a14cf3a805..d92ab59990 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -121,12 +121,12 @@ func TestPeering_Connect(t *testing.T) { kustomizeMeshDir := "../fixtures/bases/mesh-peering" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) @@ -153,7 +153,7 @@ func TestPeering_Connect(t *testing.T) { // Create the peering acceptor on the client peer. k8s.KubectlApply(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDelete(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") }) @@ -169,7 +169,7 @@ func TestPeering_Connect(t *testing.T) { // Create the peering dialer on the server peer. k8s.KubectlApply(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "secret", "api-token") k8s.KubectlDelete(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") }) @@ -187,13 +187,13 @@ func TestPeering_Connect(t *testing.T) { logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) }) @@ -203,23 +203,23 @@ func TestPeering_Connect(t *testing.T) { kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) }) logger.Log(t, "creating static-server in server peer") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client deployments in client peer") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/default") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/default") } // Check that both static-server and static-client have been injected and now have 2 containers. podList, err := staticServerPeerClusterContext.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{ @@ -249,7 +249,7 @@ func TestPeering_Connect(t *testing.T) { logger.Log(t, "creating exported services") k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/default") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/default") }) diff --git a/acceptance/tests/peering/peering_gateway_test.go b/acceptance/tests/peering/peering_gateway_test.go index 17824b8e69..3ba04980a9 100644 --- a/acceptance/tests/peering/peering_gateway_test.go +++ b/acceptance/tests/peering/peering_gateway_test.go @@ -113,12 +113,12 @@ func TestPeering_Gateway(t *testing.T) { kustomizeMeshDir := "../fixtures/bases/mesh-peering" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) @@ -145,7 +145,7 @@ func TestPeering_Gateway(t *testing.T) { // Create the peering acceptor on the client peer. k8s.KubectlApply(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDelete(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") }) @@ -161,7 +161,7 @@ func TestPeering_Gateway(t *testing.T) { // Create the peering dialer on the server peer. k8s.KubectlApply(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "secret", "api-token") k8s.KubectlDelete(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") }) @@ -179,13 +179,13 @@ func TestPeering_Gateway(t *testing.T) { logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) }) @@ -195,12 +195,12 @@ func TestPeering_Gateway(t *testing.T) { kustomizeDir := "../fixtures/cases/api-gateways/mesh" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) }) @@ -209,21 +209,21 @@ func TestPeering_Gateway(t *testing.T) { // Since we're deploying the gateway in the secondary cluster, we create the static client // in the secondary as well. logger.Log(t, "creating static-client pod in client peer") - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/non-default-namespace") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/non-default-namespace") logger.Log(t, "creating static-server in server peer") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating exported services") k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") }) logger.Log(t, "creating api-gateway resources in client peer") out, err := k8s.RunKubectlAndGetOutputE(t, staticClientOpts, "apply", "-k", "../fixtures/bases/api-gateway") require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, staticClientOpts, "delete", "-k", "../fixtures/bases/api-gateway") @@ -253,7 +253,7 @@ func TestPeering_Gateway(t *testing.T) { logger.Log(t, "creating local service resolver") k8s.KubectlApplyK(t, staticClientOpts, "../fixtures/cases/api-gateways/peer-resolver") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, staticClientOpts, "../fixtures/cases/api-gateways/peer-resolver") }) @@ -280,7 +280,7 @@ func TestPeering_Gateway(t *testing.T) { logger.Log(t, "creating intention") _, _, err = staticServerPeerClient.ConfigEntries().Set(intention, &api.WriteOptions{}) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { _, err = staticServerPeerClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{}) require.NoError(t, err) }) diff --git a/acceptance/tests/sync/sync_catalog_namespaces_test.go b/acceptance/tests/sync/sync_catalog_namespaces_test.go index 7634220b6b..67123d6e4f 100644 --- a/acceptance/tests/sync/sync_catalog_namespaces_test.go +++ b/acceptance/tests/sync/sync_catalog_namespaces_test.go @@ -97,12 +97,12 @@ func TestSyncCatalogNamespaces(t *testing.T) { logger.Logf(t, "creating namespace %s", staticServerNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) logger.Log(t, "creating a static-server with a service") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) diff --git a/acceptance/tests/sync/sync_catalog_test.go b/acceptance/tests/sync/sync_catalog_test.go index 2ca8b1ee1f..dc30570422 100644 --- a/acceptance/tests/sync/sync_catalog_test.go +++ b/acceptance/tests/sync/sync_catalog_test.go @@ -50,7 +50,7 @@ func TestSyncCatalog(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating a static-server with a service") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().NoCleanup, suite.Config().DebugDirectory, "../fixtures/bases/static-server") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) @@ -120,7 +120,7 @@ func TestSyncCatalogWithIngress(t *testing.T) { // endpoint fails initially. out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/ingress") require.NoError(r, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/ingress") @@ -130,7 +130,7 @@ func TestSyncCatalogWithIngress(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating a static-server with a service") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().NoCleanup, suite.Config().DebugDirectory, "../fixtures/bases/static-server") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go index 109975d731..62485c6e44 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go @@ -73,7 +73,7 @@ func TestTerminatingGatewayDestinations(t *testing.T) { // Deploy a static-server that will play the role of an external service. logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server-https") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server-https") // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service @@ -91,7 +91,7 @@ func TestTerminatingGatewayDestinations(t *testing.T) { // Deploy the static client logger.Log(t, "deploying static client") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") staticServerIP, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "get", "po", "-l", "app=static-server", `-o=jsonpath={.items[0].status.podIP}`) require.NoError(t, err) diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go index 7ad9f7da10..73250ea810 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go @@ -62,7 +62,7 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { logger.Logf(t, "creating Kubernetes namespace %s", testNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", testNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", testNamespace) }) @@ -74,7 +74,7 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { // Deploy a static-server that will play the role of an external service. logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") // Register the external service. registerExternalService(t, consulClient, testNamespace) @@ -91,7 +91,7 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { // Deploy the static client. logger.Log(t, "deploying static client") - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") // If ACLs are enabled, test that intentions prevent connections. if c.secure { @@ -159,14 +159,14 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { logger.Logf(t, "creating Kubernetes namespace %s", testNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", testNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", testNamespace) }) StaticClientNamespace := "ns2" logger.Logf(t, "creating Kubernetes namespace %s", StaticClientNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", StaticClientNamespace) }) @@ -183,7 +183,7 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { // Deploy a static-server that will play the role of an external service. logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ns1K8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ns1K8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") // Register the external service registerExternalService(t, consulClient, testNamespace) @@ -200,7 +200,7 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { // Deploy the static client logger.Log(t, "deploying static client") - k8s.DeployKustomize(t, ns2K8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, ns2K8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") // If ACLs are enabled, test that intentions prevent connections. if c.secure { diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_test.go index 25792e9abb..6e7e95032f 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_test.go @@ -51,7 +51,7 @@ func TestTerminatingGateway(t *testing.T) { // Deploy a static-server that will play the role of an external service. logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") // Once the cluster is up, register the external service, then create the config entry. consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) @@ -71,7 +71,7 @@ func TestTerminatingGateway(t *testing.T) { // Deploy the static client logger.Log(t, "deploying static client") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") // If ACLs are enabled, test that intentions prevent connections. if c.secure { diff --git a/acceptance/tests/vault/vault_namespaces_test.go b/acceptance/tests/vault/vault_namespaces_test.go index 68fa819a84..4a9ca092d0 100644 --- a/acceptance/tests/vault/vault_namespaces_test.go +++ b/acceptance/tests/vault/vault_namespaces_test.go @@ -265,13 +265,13 @@ func TestVault_VaultNamespace(t *testing.T) { // Deploy two services and check that they can talk to each other. logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") }) k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") diff --git a/acceptance/tests/vault/vault_test.go b/acceptance/tests/vault/vault_test.go index 47d58b68c5..9dab0a3e71 100644 --- a/acceptance/tests/vault/vault_test.go +++ b/acceptance/tests/vault/vault_test.go @@ -350,13 +350,13 @@ func testVault(t *testing.T, testAutoBootstrap bool) { // Deploy two services and check that they can talk to each other. logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") }) k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") diff --git a/acceptance/tests/vault/vault_tls_auto_reload_test.go b/acceptance/tests/vault/vault_tls_auto_reload_test.go index c3a3ae4034..d5d4d33c4c 100644 --- a/acceptance/tests/vault/vault_tls_auto_reload_test.go +++ b/acceptance/tests/vault/vault_tls_auto_reload_test.go @@ -246,13 +246,13 @@ func TestVault_TLSAutoReload(t *testing.T) { // Deploy two services and check that they can talk to each other. logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") }) k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") diff --git a/acceptance/tests/vault/vault_wan_fed_test.go b/acceptance/tests/vault/vault_wan_fed_test.go index d8c00b732a..fa63c4d5fb 100644 --- a/acceptance/tests/vault/vault_wan_fed_test.go +++ b/acceptance/tests/vault/vault_wan_fed_test.go @@ -31,6 +31,7 @@ import ( // in the secondary that will treat the Vault server in the primary as an external server. func TestVault_WANFederationViaGateways(t *testing.T) { cfg := suite.Config() + if cfg.UseKind { t.Skipf("Skipping this test because it's currently flaky on kind") } @@ -491,16 +492,18 @@ func TestVault_WANFederationViaGateways(t *testing.T) { logger.Log(t, "creating proxy-defaults config") kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, primaryCtx.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, primaryCtx.KubectlOptions(t), kustomizeDir) }) // Check that we can connect services over the mesh gateways. + logger.Log(t, "creating static-server in dc2") - k8s.DeployKustomize(t, secondaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client in dc1") - k8s.DeployKustomize(t, primaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + k8s.DeployKustomize(t, primaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") logger.Log(t, "creating intention") _, _, err = primaryClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ @@ -517,6 +520,7 @@ func TestVault_WANFederationViaGateways(t *testing.T) { logger.Log(t, "checking that connection is successful") k8s.CheckStaticServerConnectionSuccessful(t, primaryCtx.KubectlOptions(t), StaticClientName, "http://localhost:1234") + } // vaultAddress returns Vault's server URL depending on test configuration. diff --git a/acceptance/tests/wan-federation/wan_federation_gateway_test.go b/acceptance/tests/wan-federation/wan_federation_gateway_test.go index c87ee7197b..6abacda438 100644 --- a/acceptance/tests/wan-federation/wan_federation_gateway_test.go +++ b/acceptance/tests/wan-federation/wan_federation_gateway_test.go @@ -130,25 +130,25 @@ func TestWANFederation_Gateway(t *testing.T) { logger.Log(t, "creating proxy-defaults config in dc1") kustomizeDir := "../fixtures/cases/api-gateways/mesh" k8s.KubectlApplyK(t, primaryContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, primaryContext.KubectlOptions(t), kustomizeDir) }) // these clients are just there so we can exec in and curl on them. logger.Log(t, "creating static-client in dc1") - k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") logger.Log(t, "creating static-client in dc2") - k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") t.Run("from primary to secondary", func(t *testing.T) { logger.Log(t, "creating static-server in dc2") - k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating api-gateway resources in dc1") out, err := k8s.RunKubectlAndGetOutputE(t, primaryContext.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, primaryContext.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") @@ -156,7 +156,7 @@ func TestWANFederation_Gateway(t *testing.T) { // create a service resolver for doing cross-dc redirects. k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc1-to-dc2-resolver") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc1-to-dc2-resolver") }) @@ -170,12 +170,12 @@ func TestWANFederation_Gateway(t *testing.T) { t.Run("from secondary to primary", func(t *testing.T) { // Check that we can connect services over the mesh gateways logger.Log(t, "creating static-server in dc1") - k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating api-gateway resources in dc2") out, err := k8s.RunKubectlAndGetOutputE(t, secondaryContext.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. k8s.RunKubectlAndGetOutputE(t, secondaryContext.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") @@ -183,7 +183,7 @@ func TestWANFederation_Gateway(t *testing.T) { // create a service resolver for doing cross-dc redirects. k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc2-to-dc1-resolver") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc2-to-dc1-resolver") }) diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index e7a128887e..bae8e8e9da 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -157,16 +157,16 @@ func TestWANFederation(t *testing.T) { logger.Log(t, "creating proxy-defaults config") kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), kustomizeDir) }) // Check that we can connect services over the mesh gateways logger.Log(t, "creating static-server in dc2") - k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client in dc1") - k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") if c.secure { logger.Log(t, "creating intention") diff --git a/go.work b/go.work new file mode 100644 index 0000000000..318dd011c0 --- /dev/null +++ b/go.work @@ -0,0 +1,11 @@ + +use ( + ./acceptance + ./charts + ./cli + ./control-plane + ./control-plane/cni + ./hack/aws-acceptance-test-cleanup + ./hack/copy-crds-to-chart + ./hack/helm-reference-gen +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000000..2961d25a18 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,352 @@ +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +github.com/Azure/azure-sdk-for-go v46.0.0+incompatible h1:4qlEOCDcDQZTGczYGzbGYCdJfVpZLIs8AEo5+MoXBPw= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.1 h1:bvUhZciHydpBxBmCheUgxxbSwJy7xcfjkUsjUcqSojc= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= +github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/vmware/govmomi v0.20.3 h1:gpw/0Ku+6RgF3jsi7fnCLmlcikBHfKBCUcu1qgc16OU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= +go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= +go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= +go.etcd.io/etcd/pkg/v3 v3.5.5/go.mod h1:6ksYFxttiUGzC2uxyqiyOEvhAiD0tuIqSZkX3TyPdaE= +go.etcd.io/etcd/raft/v3 v3.5.5/go.mod h1:76TA48q03g1y1VpTue92jZLr9lIHKUNcYdZOOGyx8rI= +go.etcd.io/etcd/server/v3 v3.5.5/go.mod h1:rZ95vDw/jrvsbj9XpTqPrTAB9/kzchVdhRirySPkUBc= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= +go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= +go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1/go.mod h1:Kv8liBeVNFkkkbilbgWRpV+wWuu+H5xdOT6HAgd30iw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1/go.mod h1:xOvWoTOrQjxjW61xtOmD/WKGRYb/P4NzRo3bs65U6Rk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= +go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= +go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= +go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= +go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys= +go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= +go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= +go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +k8s.io/apiserver v0.26.3 h1:blBpv+yOiozkPH2aqClhJmJY+rp53Tgfac4SKPDJnU4= +k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA= +k8s.io/code-generator v0.26.3/go.mod h1:ryaiIKwfxEJEaywEzx3dhWOydpVctKYbqLajJf0O8dI= +k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kms v0.26.3/go.mod h1:69qGnf1NsFOQP07fBYqNLZklqEHSJF024JqYCaeVxHg= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= +sigs.k8s.io/controller-tools v0.11.4/go.mod h1:qcfX7jfcfYD/b7lAhvqAyTbt/px4GpvN88WKLFFv7p8= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= From 8b45de8b153c158d68c972b9e872acc1e4b06825 Mon Sep 17 00:00:00 2001 From: skpratt Date: Thu, 20 Jul 2023 23:43:57 -0500 Subject: [PATCH 293/592] Differentiate FIPS linux package names (#2599) --- .github/workflows/build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d7942617d..12904b3070 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,8 +79,8 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402", pkg_suffix: "-fips" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } # control-plane @@ -96,8 +96,8 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402", pkg_suffix: "-fips" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } # consul-cni @@ -112,8 +112,8 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402", pkg_suffix: "-fips" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } @@ -174,7 +174,7 @@ jobs: if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} uses: hashicorp/actions-packaging-linux@v1 with: - name: consul-k8s + name: consul-k8s${{ matrix.pkg_suffix }} description: "consul-k8s provides a cli interface to first-class integrations between Consul and Kubernetes." arch: ${{ matrix.goarch }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} From efa2be8fe702bbbdbbc86fcecc8ba33a28c9f650 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 21 Jul 2023 09:47:53 -0700 Subject: [PATCH 294/592] added make target for checking for hashicorppreview (#2603) * added make target for checking for hashicorppreview * added check to prepare-release make target --- Makefile | 10 ++++++++-- .../build-support/scripts/check-hashicorppreview.sh | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100755 control-plane/build-support/scripts/check-hashicorppreview.sh diff --git a/Makefile b/Makefile index 1d62035dbb..4289b81b9b 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,10 @@ terraform-fmt: @terraform fmt -recursive .PHONY: terraform-fmt +# Check for hashicorppreview containers +check-preview-containers: + @source $(CURDIR)/control-plane/build-support/scripts/check-hashicorppreview.sh + # ===========> CLI Targets cli-dev: @@ -221,7 +225,7 @@ aks-test-packages: check-env: @printenv | grep "CONSUL_K8S" -prepare-release: ## Sets the versions, updates changelog to prepare this repository to release +prepare-release-script: ## Sets the versions, updates changelog to prepare this repository to release ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) endif @@ -234,7 +238,9 @@ endif ifndef CONSUL_K8S_CONSUL_VERSION $(error CONSUL_K8S_CONSUL_VERSION is required) endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION) + @source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION); \ + +prepare-release: prepare-release-script check-preview-containers prepare-dev: ifndef CONSUL_K8S_RELEASE_VERSION diff --git a/control-plane/build-support/scripts/check-hashicorppreview.sh b/control-plane/build-support/scripts/check-hashicorppreview.sh new file mode 100755 index 0000000000..cd694dad93 --- /dev/null +++ b/control-plane/build-support/scripts/check-hashicorppreview.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +echo "Checking charts for hashicorpreview images. . ." +if grep -rnw -e 'hashicorppreview' './charts'; then + echo Charts contain hashicorppreview images. If this is intended for release, please remove the preview images. +else + echo Charts do not contain hashicorpreview images, ready for release! +fi \ No newline at end of file From e2adf6fe0ada638295ae2fdeadba054abf65cfe6 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Fri, 21 Jul 2023 13:13:34 -0400 Subject: [PATCH 295/592] Increase golangci-lint timeout to 10m (#2621) This is meant to solve for recurrent timeouts in several steps, particularly `golangci-lint-control-plane` and `golang-ci-lint-cli`. An accompanying change in `consul-k8s-workflows` should disable caching until the (unclear) root of the issue can be resolved, or we can disable or clear cache in a more targeted way that solves for these cases. --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 142f5c2722..dcad005d10 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -34,4 +34,4 @@ linters-settings: simplify: true run: - timeout: 5m + timeout: 10m From 1690fe297fdd02afafeb3713b978a30cf846b86e Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Mon, 24 Jul 2023 10:38:31 -0500 Subject: [PATCH 296/592] Fix TestAPIGateway_GatewayClassConfig (#2631) * Fix TestAPIGateway_GatewayClassConfig * Remove stray files from bad merge --- .../api_gateway_gatewayclassconfig_test.go | 20 - go.work | 11 - go.work.sum | 352 ------------------ 3 files changed, 383 deletions(-) delete mode 100644 go.work delete mode 100644 go.work.sum diff --git a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go index ffe0424ef1..89ba07a1e7 100644 --- a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go +++ b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go @@ -63,22 +63,6 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { k8sClient := ctx.ControllerRuntimeClient(t) - //create clean namespace - err = k8sClient.Create(context.Background(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - logger.Log(t, "deleting gateway namesapce") - k8sClient.Delete(context.Background(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }) - }) - // create a GatewayClassConfig with configuration set gatewayClassConfigName := "gateway-class-config" gatewayClassConfig := &v1alpha1.GatewayClassConfig{ @@ -144,10 +128,6 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { logger.Log(t, "creating controlled gateway") gateway := createGateway(t, k8sClient, gatewayName, namespace, gatewayClassName, certificateName) - // make sure it exists - logger.Log(t, "checking that gateway one is synchronized to Consul") - checkConsulExists(t, consulClient, api.APIGateway, gatewayName) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all gateways") k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(namespace)) diff --git a/go.work b/go.work deleted file mode 100644 index 318dd011c0..0000000000 --- a/go.work +++ /dev/null @@ -1,11 +0,0 @@ - -use ( - ./acceptance - ./charts - ./cli - ./control-plane - ./control-plane/cni - ./hack/aws-acceptance-test-cleanup - ./hack/copy-crds-to-chart - ./hack/helm-reference-gen -) diff --git a/go.work.sum b/go.work.sum deleted file mode 100644 index 2961d25a18..0000000000 --- a/go.work.sum +++ /dev/null @@ -1,352 +0,0 @@ -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= -cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= -cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= -cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= -cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= -cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= -cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= -cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= -cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= -cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= -cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= -cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= -cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= -cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= -cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= -cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= -cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= -cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= -cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= -cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= -cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= -cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= -cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= -cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= -cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= -cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= -cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= -cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= -cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= -cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= -cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= -cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= -cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= -cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= -cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= -cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= -cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= -cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= -cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= -cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= -cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= -cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= -cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= -cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= -cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= -cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= -cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= -cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= -cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= -cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= -cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= -cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= -cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= -cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= -cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= -cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= -cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= -cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= -cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= -cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= -cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= -cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= -cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= -cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= -cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= -cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= -cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= -cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= -cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= -cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= -github.com/Azure/azure-sdk-for-go v46.0.0+incompatible h1:4qlEOCDcDQZTGczYGzbGYCdJfVpZLIs8AEo5+MoXBPw= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.1 h1:bvUhZciHydpBxBmCheUgxxbSwJy7xcfjkUsjUcqSojc= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= -github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/vmware/govmomi v0.20.3 h1:gpw/0Ku+6RgF3jsi7fnCLmlcikBHfKBCUcu1qgc16OU= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= -go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= -go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= -go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= -go.etcd.io/etcd/pkg/v3 v3.5.5/go.mod h1:6ksYFxttiUGzC2uxyqiyOEvhAiD0tuIqSZkX3TyPdaE= -go.etcd.io/etcd/raft/v3 v3.5.5/go.mod h1:76TA48q03g1y1VpTue92jZLr9lIHKUNcYdZOOGyx8rI= -go.etcd.io/etcd/server/v3 v3.5.5/go.mod h1:rZ95vDw/jrvsbj9XpTqPrTAB9/kzchVdhRirySPkUBc= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= -go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= -go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1/go.mod h1:Kv8liBeVNFkkkbilbgWRpV+wWuu+H5xdOT6HAgd30iw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1/go.mod h1:xOvWoTOrQjxjW61xtOmD/WKGRYb/P4NzRo3bs65U6Rk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= -go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= -go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= -go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= -go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys= -go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= -go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= -go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= -google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -k8s.io/apiserver v0.26.3 h1:blBpv+yOiozkPH2aqClhJmJY+rp53Tgfac4SKPDJnU4= -k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA= -k8s.io/code-generator v0.26.3/go.mod h1:ryaiIKwfxEJEaywEzx3dhWOydpVctKYbqLajJf0O8dI= -k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kms v0.26.3/go.mod h1:69qGnf1NsFOQP07fBYqNLZklqEHSJF024JqYCaeVxHg= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= -sigs.k8s.io/controller-tools v0.11.4/go.mod h1:qcfX7jfcfYD/b7lAhvqAyTbt/px4GpvN88WKLFFv7p8= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= From 3932e2882837c4db37e6c1acadcc959901b57b62 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Mon, 24 Jul 2023 11:06:15 -0500 Subject: [PATCH 297/592] Support running with restricted PSA enforcement enabled (part 1) (#2572) Support restricted PSA enforcement in a basic setup. This is enough to get a basic setup with ACLs and TLS working and an acceptance test passing (but does not update every component). On OpenShift, we have the option to set the security context or not. If the security context is unset, then it is set automatically by OpenShift SCCs. However, we prefer to set the security context to avoid useless warnings on OpenShift and to reduce the config difference between OpenShift and plain Kube. By default, OpenShift namespaces have the audit and warn PSA labels set to restricted, so we receive pod security warnings when deploying Consul to OpenShift even though the pods will be able to run. Helm chart changes: * Add a helper to the helm chart to define a "restricted" container security context (when pod security policies are not enabled) * Update the following container securityContexts to use the "restricted" settings (not exhaustive) - gateway-cleanup-job.yaml - gateway-resources-job.yaml - gossip-encryption-autogenerate-job.yaml - server-acl-init-cleanup-job.yaml - only if `.Values.server.containerSecurityContext.server.acl-init` is unset - server-acl-init-job.yaml - only if `.Values.server.containerSecurityContext.server.acl-init` is unset - server-statefulset.yaml: - the locality-init container receives the restricted context - the consul container receives the restricted context only if `.Values.server.containerSecurityContext.server` is unset - tls-init-cleanup-job.yaml - only if `.Values.server.containerSecurityContext.server.tls-init` is unset - tls-init-job.yaml - only if `.Values.server.containerSecurityContext.server.tls-init` is unset - webhook-cert-manager-deployment.yaml Acceptance test changes: * When `-enable-openshift` and `-enable-cni` are set, configure the CNI settings correctly for OpenShift. * Add the `-enable-restricted-psa-enforcement` test flag. When this is set, the tests assume the Consul namespace has restricted PSA enforcement enabled. The tests will deploy the CNI (if enabled) into the `kube-system` namespace. Compatible test cases will deploy applications outside of the Consul namespace. * Update the ConnectHelper to configure the NetworkAttachmentDefinition required to be compatible with the CNI on OpenShift. * Add fixtures for static-client and static-server for OpenShift. This is necessary because the deployment configs must reference the network attachment definition when using the CNI on OpenShift. * Update tests in the `acceptance/tests/connect` directory to either run or skip based on -enable-cni and -enable-openshift --- .changelog/2572.txt | 3 + acceptance/framework/config/config.go | 22 +++- acceptance/framework/config/config_test.go | 1 + .../framework/connhelper/connect_helper.go | 112 ++++++++++++++---- .../framework/consul/helm_cluster_test.go | 6 + .../framework/environment/environment.go | 10 ++ acceptance/framework/flags/flags.go | 14 ++- .../connect/connect_external_servers_test.go | 2 + .../connect/connect_inject_namespaces_test.go | 2 + .../tests/connect/connect_inject_test.go | 30 +++-- .../connect/connect_proxy_lifecycle_test.go | 1 + .../tests/connect/permissive_mtls_test.go | 1 + .../bases/openshift/network-attachment.yaml | 17 +++ .../kustomization.yaml | 8 ++ .../static-client-openshift-inject/patch.yaml | 14 +++ .../kustomization.yaml | 8 ++ .../static-client-openshift-tproxy/patch.yaml | 18 +++ .../kustomization.yaml | 8 ++ .../cases/static-server-openshift/patch.yaml | 42 +++++++ charts/consul/templates/_helpers.tpl | 25 +++- .../templates/connect-inject-deployment.yaml | 1 + .../consul/templates/gateway-cleanup-job.yaml | 1 + .../templates/gateway-resources-job.yaml | 1 + .../gossip-encryption-autogenerate-job.yaml | 1 + .../server-acl-init-cleanup-job.yaml | 3 + .../consul/templates/server-acl-init-job.yaml | 3 + .../consul/templates/server-statefulset.yaml | 5 +- .../templates/tls-init-cleanup-job.yaml | 3 + charts/consul/templates/tls-init-job.yaml | 3 + .../webhook-cert-manager-deployment.yaml | 1 + .../consul/test/unit/server-statefulset.bats | 70 ++++++++++- 31 files changed, 397 insertions(+), 39 deletions(-) create mode 100644 .changelog/2572.txt create mode 100644 acceptance/tests/fixtures/bases/openshift/network-attachment.yaml create mode 100644 acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/static-client-openshift-inject/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/static-client-openshift-tproxy/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/static-server-openshift/patch.yaml diff --git a/.changelog/2572.txt b/.changelog/2572.txt new file mode 100644 index 0000000000..4bc6c4ba50 --- /dev/null +++ b/.changelog/2572.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled +``` diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index 74bb7daf6e..ee07df63fd 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strconv" "strings" + "testing" "github.com/hashicorp/go-version" "gopkg.in/yaml.v2" @@ -73,7 +74,8 @@ type TestConfig struct { EnablePodSecurityPolicies bool - EnableCNI bool + EnableCNI bool + EnableRestrictedPSAEnforcement bool EnableTransparentProxy bool @@ -135,10 +137,22 @@ func (t *TestConfig) HelmValuesFromConfig() (map[string]string, error) { if t.EnableCNI { setIfNotEmpty(helmValues, "connectInject.cni.enabled", "true") + setIfNotEmpty(helmValues, "connectInject.cni.logLevel", "debug") // GKE is currently the only cloud provider that uses a different CNI bin dir. if t.UseGKE { setIfNotEmpty(helmValues, "connectInject.cni.cniBinDir", "/home/kubernetes/bin") } + if t.EnableOpenshift { + setIfNotEmpty(helmValues, "connectInject.cni.multus", "true") + setIfNotEmpty(helmValues, "connectInject.cni.cniBinDir", "/var/lib/cni/bin") + setIfNotEmpty(helmValues, "connectInject.cni.cniNetDir", "/etc/kubernetes/cni/net.d") + } + + if t.EnableRestrictedPSAEnforcement { + // The CNI requires privilege, so when restricted PSA enforcement is enabled on the Consul + // namespace it must be run in a different privileged namespace. + setIfNotEmpty(helmValues, "connectInject.cni.namespace", "kube-system") + } } setIfNotEmpty(helmValues, "connectInject.transparentProxy.defaultEnabled", strconv.FormatBool(t.EnableTransparentProxy)) @@ -220,6 +234,12 @@ func (t *TestConfig) entImage() (string, error) { return fmt.Sprintf("hashicorp/consul-enterprise:%s%s-ent", consulImageVersion, preRelease), nil } +func (c *TestConfig) SkipWhenOpenshiftAndCNI(t *testing.T) { + if c.EnableOpenshift && c.EnableCNI { + t.Skip("skipping because -enable-cni and -enable-openshift are set and this test doesn't deploy apps correctly") + } +} + // setIfNotEmpty sets key to val in map m if value is not empty. func setIfNotEmpty(m map[string]string, key, val string) { if val != "" { diff --git a/acceptance/framework/config/config_test.go b/acceptance/framework/config/config_test.go index 96f0f0e7eb..df981e26fa 100644 --- a/acceptance/framework/config/config_test.go +++ b/acceptance/framework/config/config_test.go @@ -116,6 +116,7 @@ func TestConfig_HelmValuesFromConfig(t *testing.T) { }, map[string]string{ "connectInject.cni.enabled": "true", + "connectInject.cni.logLevel": "debug", "connectInject.transparentProxy.defaultEnabled": "false", }, }, diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index c34f663563..2eb18c9dbb 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -6,9 +6,11 @@ package connhelper import ( "context" "strconv" + "strings" "testing" "time" + terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" @@ -44,7 +46,12 @@ type ConnectHelper struct { // ReleaseName is the name of the Consul cluster. ReleaseName string + // Ctx is used to deploy Consul Ctx environment.TestContext + // UseAppNamespace is used top optionally deploy applications into a separate namespace. + // If unset, the namespace associated with Ctx is used. + UseAppNamespace bool + Cfg *config.TestConfig // consulCluster is the cluster to use for the test. @@ -82,6 +89,14 @@ func (c *ConnectHelper) Upgrade(t *testing.T) { c.consulCluster.Upgrade(t, c.helmValues()) } +func (c *ConnectHelper) KubectlOptsForApp(t *testing.T) *terratestK8s.KubectlOptions { + opts := c.Ctx.KubectlOptions(t) + if !c.UseAppNamespace { + return opts + } + return c.Ctx.KubectlOptionsForNamespace(opts.Namespace + "-apps") +} + // DeployClientAndServer deploys a client and server pod to the Kubernetes // cluster which will be used to test service mesh connectivity. If the Secure // flag is true, a pre-check is done to ensure that the ACL tokens for the test @@ -108,23 +123,46 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - if c.Cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + c.setupAppNamespace(t) + + opts := c.KubectlOptsForApp(t) + if c.Cfg.EnableCNI && c.Cfg.EnableOpenshift { + // On OpenShift with the CNI, we need to create a network attachment definition in the namespace + // where the applications are running, and the app deployment configs need to reference that network + // attachment definition. + + // TODO: A base fixture is the wrong place for these files + k8s.KubectlApply(t, opts, "../fixtures/bases/openshift/") + helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { + k8s.KubectlDelete(t, opts, "../fixtures/bases/openshift/") + }) + + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-openshift") + if c.Cfg.EnableTransparentProxy { + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-openshift-tproxy") + } else { + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-openshift-inject") + } } else { - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + if c.Cfg.EnableTransparentProxy { + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + } else { + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + } } - // Check that both static-server and static-client have been injected and // now have 2 containers. retry.RunWith( &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond}, t, func(r *retry.R) { for _, labelSelector := range []string{"app=static-server", "app=static-client"} { - podList, err := c.Ctx.KubernetesClient(t).CoreV1().Pods(c.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labelSelector, - FieldSelector: `status.phase=Running`, - }) + podList, err := c.Ctx.KubernetesClient(t).CoreV1(). + Pods(opts.Namespace). + List(context.Background(), metav1.ListOptions{ + LabelSelector: labelSelector, + FieldSelector: `status.phase=Running`, + }) require.NoError(r, err) require.Len(r, podList.Items, 1) require.Len(r, podList.Items[0].Spec.Containers, 2) @@ -132,16 +170,46 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { }) } +// setupAppNamespace creates a namespace where applications are deployed. This +// does nothing if UseAppNamespace is not set. The app namespace is relevant +// when testing with restricted PSA enforcement enabled. +func (c *ConnectHelper) setupAppNamespace(t *testing.T) { + if !c.UseAppNamespace { + return + } + opts := c.KubectlOptsForApp(t) + // If we are deploying apps in another namespace, create the namespace. + + _, err := k8s.RunKubectlAndGetOutputE(t, opts, "create", "ns", opts.Namespace) + if err != nil && strings.Contains(err.Error(), "AlreadyExists") { + return + } + require.NoError(t, err) + helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { + k8s.RunKubectl(t, opts, "delete", "ns", opts.Namespace) + }) + + if c.Cfg.EnableRestrictedPSAEnforcement { + // Allow anything to run in the app namespace. + k8s.RunKubectl(t, opts, "label", "--overwrite", "ns", opts.Namespace, + "pod-security.kubernetes.io/enforce=privileged", + "pod-security.kubernetes.io/enforce-version=v1.24", + ) + } + +} + // CreateResolverRedirect creates a resolver that redirects to a static-server, a corresponding k8s service, // and intentions. This helper is primarly used to ensure that the virtual-ips are persisted to consul properly. func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { logger.Log(t, "creating resolver redirect") - options := c.Ctx.KubectlOptions(t) + opts := c.KubectlOptsForApp(t) + c.setupAppNamespace(t) kustomizeDir := "../fixtures/cases/resolver-redirect-virtualip" - k8s.KubectlApplyK(t, options, kustomizeDir) + k8s.KubectlApplyK(t, opts, kustomizeDir) helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, options, kustomizeDir) + k8s.KubectlDeleteK(t, opts, kustomizeDir) }) } @@ -149,10 +217,11 @@ func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { // server fails when no intentions are configured. func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T) { logger.Log(t, "checking that the connection is not successful because there's no intention") + opts := c.KubectlOptsForApp(t) if c.Cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionFailing(t, c.Ctx.KubectlOptions(t), StaticClientName, "http://static-server") + k8s.CheckStaticServerConnectionFailing(t, opts, StaticClientName, "http://static-server") } else { - k8s.CheckStaticServerConnectionFailing(t, c.Ctx.KubectlOptions(t), StaticClientName, "http://localhost:1234") + k8s.CheckStaticServerConnectionFailing(t, opts, StaticClientName, "http://localhost:1234") } } @@ -177,11 +246,12 @@ func (c *ConnectHelper) CreateIntention(t *testing.T) { // static-client pod once the intention is set. func (c *ConnectHelper) TestConnectionSuccess(t *testing.T) { logger.Log(t, "checking that connection is successful") + opts := c.KubectlOptsForApp(t) if c.Cfg.EnableTransparentProxy { // todo: add an assertion that the traffic is going through the proxy - k8s.CheckStaticServerConnectionSuccessful(t, c.Ctx.KubectlOptions(t), StaticClientName, "http://static-server") + k8s.CheckStaticServerConnectionSuccessful(t, opts, StaticClientName, "http://static-server") } else { - k8s.CheckStaticServerConnectionSuccessful(t, c.Ctx.KubectlOptions(t), StaticClientName, "http://localhost:1234") + k8s.CheckStaticServerConnectionSuccessful(t, opts, StaticClientName, "http://localhost:1234") } } @@ -192,8 +262,10 @@ func (c *ConnectHelper) TestConnectionFailureWhenUnhealthy(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create a file called "unhealthy" at "/tmp/" so that the readiness probe // of the static-server pod fails. + opts := c.KubectlOptsForApp(t) + logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, c.Ctx.KubectlOptions(t), "exec", "deploy/"+StaticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, // CheckStaticServerConnection will retry until Consul marks the service @@ -205,20 +277,20 @@ func (c *ConnectHelper) TestConnectionFailureWhenUnhealthy(t *testing.T) { // other tests. logger.Log(t, "checking that connection is unsuccessful") if c.Cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, c.Ctx.KubectlOptions(t), StaticClientName, false, []string{ + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, opts, StaticClientName, false, []string{ "curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server", "curl: (7) Failed to connect to static-server port 80: Connection refused", }, "", "http://static-server") } else { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, c.Ctx.KubectlOptions(t), StaticClientName, false, []string{ + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, opts, StaticClientName, false, []string{ "curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server", }, "", "http://localhost:1234") } // Return the static-server to a "healthy state". - k8s.RunKubectl(t, c.Ctx.KubectlOptions(t), "exec", "deploy/"+StaticServerName, "--", "rm", "/tmp/unhealthy") + k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "--", "rm", "/tmp/unhealthy") } // helmValues uses the Secure and AutoEncrypt fields to set values for the Helm diff --git a/acceptance/framework/consul/helm_cluster_test.go b/acceptance/framework/consul/helm_cluster_test.go index 9c44006d43..011ca23e0f 100644 --- a/acceptance/framework/consul/helm_cluster_test.go +++ b/acceptance/framework/consul/helm_cluster_test.go @@ -8,6 +8,7 @@ import ( "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/config" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/stretchr/testify/require" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" @@ -80,9 +81,14 @@ func (c *ctx) Name() string { func (c *ctx) KubectlOptions(_ *testing.T) *k8s.KubectlOptions { return &k8s.KubectlOptions{} } +func (c *ctx) KubectlOptionsForNamespace(ns string) *k8s.KubectlOptions { + return &k8s.KubectlOptions{} +} func (c *ctx) KubernetesClient(_ *testing.T) kubernetes.Interface { return fake.NewSimpleClientset() } func (c *ctx) ControllerRuntimeClient(_ *testing.T) client.Client { return runtimefake.NewClientBuilder().Build() } + +var _ environment.TestContext = (*ctx)(nil) diff --git a/acceptance/framework/environment/environment.go b/acceptance/framework/environment/environment.go index 9150f4ff03..7014a3c05f 100644 --- a/acceptance/framework/environment/environment.go +++ b/acceptance/framework/environment/environment.go @@ -35,6 +35,8 @@ type TestEnvironment interface { // for example, information about a specific Kubernetes cluster. type TestContext interface { KubectlOptions(t *testing.T) *k8s.KubectlOptions + // TODO: I don't love this. + KubectlOptionsForNamespace(ns string) *k8s.KubectlOptions KubernetesClient(t *testing.T) kubernetes.Interface ControllerRuntimeClient(t *testing.T) client.Client } @@ -138,6 +140,14 @@ func (k kubernetesContext) KubectlOptions(t *testing.T) *k8s.KubectlOptions { return k.options } +func (k kubernetesContext) KubectlOptionsForNamespace(ns string) *k8s.KubectlOptions { + return &k8s.KubectlOptions{ + ContextName: k.kubeContextName, + ConfigPath: k.pathToKubeConfig, + Namespace: ns, + } +} + // KubernetesClientFromOptions takes KubectlOptions and returns Kubernetes API client. func KubernetesClientFromOptions(t *testing.T, options *k8s.KubectlOptions) kubernetes.Interface { configPath, err := options.GetConfigPath(t) diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index b1ad7e4332..ce8e1ea726 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -27,7 +27,8 @@ type TestFlags struct { flagEnablePodSecurityPolicies bool - flagEnableCNI bool + flagEnableCNI bool + flagEnableRestrictedPSAEnforcement bool flagEnableTransparentProxy bool @@ -116,6 +117,13 @@ func (t *TestFlags) init() { flag.BoolVar(&t.flagEnableCNI, "enable-cni", false, "If true, the test suite will run tests with consul-cni plugin enabled. "+ "In general, this will only run against tests that are mesh related (connect, mesh-gateway, peering, etc") + flag.BoolVar(&t.flagEnableRestrictedPSAEnforcement, "enable-restricted-psa-enforcement", false, + "If true, this indicates that Consul is being run in a namespace with restricted PSA enforcement enabled. "+ + "The tests do not configure Consul's namespace with PSA enforcement enabled. This must configured before tests are run. "+ + "The CNI and test applications need more privilege than is allowed in a restricted namespace. "+ + "When set, the CNI will be deployed into the kube-system namespace, and in supported test cases, applications "+ + "are deployed, by default, into a namespace named '-apps' instead of being deployed into the "+ + "Consul namespace.") flag.BoolVar(&t.flagEnableTransparentProxy, "enable-transparent-proxy", false, "If true, the test suite will run tests with transparent proxy enabled. "+ @@ -176,6 +184,7 @@ func (t *TestFlags) Validate() error { if t.flagEnableEnterprise && t.flagEnterpriseLicense == "" { return errors.New("-enable-enterprise provided without setting env var CONSUL_ENT_LICENSE with consul license") } + return nil } @@ -200,7 +209,8 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { EnablePodSecurityPolicies: t.flagEnablePodSecurityPolicies, - EnableCNI: t.flagEnableCNI, + EnableCNI: t.flagEnableCNI, + EnableRestrictedPSAEnforcement: t.flagEnableRestrictedPSAEnforcement, EnableTransparentProxy: t.flagEnableTransparentProxy, diff --git a/acceptance/tests/connect/connect_external_servers_test.go b/acceptance/tests/connect/connect_external_servers_test.go index c421c1258e..46f9e14573 100644 --- a/acceptance/tests/connect/connect_external_servers_test.go +++ b/acceptance/tests/connect/connect_external_servers_test.go @@ -30,6 +30,8 @@ func TestConnectInject_ExternalServers(t *testing.T) { caseName := fmt.Sprintf("secure: %t", secure) t.Run(caseName, func(t *testing.T) { cfg := suite.Config() + cfg.SkipWhenOpenshiftAndCNI(t) + ctx := suite.Environment().DefaultContext(t) serverHelmValues := map[string]string{ diff --git a/acceptance/tests/connect/connect_inject_namespaces_test.go b/acceptance/tests/connect/connect_inject_namespaces_test.go index 6fb493ce17..7b7785a44f 100644 --- a/acceptance/tests/connect/connect_inject_namespaces_test.go +++ b/acceptance/tests/connect/connect_inject_namespaces_test.go @@ -34,6 +34,7 @@ func TestConnectInjectNamespaces(t *testing.T) { if !cfg.EnableEnterprise { t.Skipf("skipping this test because -enable-enterprise is not set") } + cfg.SkipWhenOpenshiftAndCNI(t) cases := []struct { name string @@ -246,6 +247,7 @@ func TestConnectInjectNamespaces_CleanupController(t *testing.T) { if !cfg.EnableEnterprise { t.Skipf("skipping this test because -enable-enterprise is not set") } + cfg.SkipWhenOpenshiftAndCNI(t) consulDestNS := "consul-dest" cases := []struct { diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index c239698d2a..7c7f17fc0a 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -38,11 +38,12 @@ func TestConnectInject(t *testing.T) { releaseName := helpers.RandomName() connHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: c.secure, - ReleaseName: releaseName, - Ctx: ctx, - Cfg: cfg, + ClusterKind: consul.Helm, + Secure: c.secure, + ReleaseName: releaseName, + Ctx: ctx, + UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, + Cfg: cfg, } connHelper.Setup(t) @@ -71,11 +72,12 @@ func TestConnectInject_VirtualIPFailover(t *testing.T) { releaseName := helpers.RandomName() connHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: true, - ReleaseName: releaseName, - Ctx: ctx, - Cfg: cfg, + ClusterKind: consul.Helm, + Secure: true, + ReleaseName: releaseName, + Ctx: ctx, + UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, + Cfg: cfg, } connHelper.Setup(t) @@ -84,7 +86,8 @@ func TestConnectInject_VirtualIPFailover(t *testing.T) { connHelper.CreateResolverRedirect(t) connHelper.DeployClientAndServer(t) - k8s.CheckStaticServerConnectionSuccessful(t, connHelper.Ctx.KubectlOptions(t), "static-client", "http://resolver-redirect") + opts := connHelper.KubectlOptsForApp(t) + k8s.CheckStaticServerConnectionSuccessful(t, opts, "static-client", "http://resolver-redirect") } // Test the endpoints controller cleans up force-killed pods. @@ -93,6 +96,9 @@ func TestConnectInject_CleanupKilledPods(t *testing.T) { name := fmt.Sprintf("secure: %t", secure) t.Run(name, func(t *testing.T) { cfg := suite.Config() + + cfg.SkipWhenOpenshiftAndCNI(t) + ctx := suite.Environment().DefaultContext(t) helmValues := map[string]string{ @@ -161,6 +167,8 @@ func TestConnectInject_MultiportServices(t *testing.T) { name := fmt.Sprintf("secure: %t", secure) t.Run(name, func(t *testing.T) { cfg := suite.Config() + cfg.SkipWhenOpenshiftAndCNI(t) + ctx := suite.Environment().DefaultContext(t) helmValues := map[string]string{ diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go index 321c002a4c..39dd0b4ae7 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -34,6 +34,7 @@ const ( // Test the endpoints controller cleans up force-killed pods. func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { cfg := suite.Config() + cfg.SkipWhenOpenshiftAndCNI(t) for _, testCfg := range []LifecycleShutdownConfig{ {secure: false, helmValues: map[string]string{ diff --git a/acceptance/tests/connect/permissive_mtls_test.go b/acceptance/tests/connect/permissive_mtls_test.go index d07c44ae2f..929d56acfd 100644 --- a/acceptance/tests/connect/permissive_mtls_test.go +++ b/acceptance/tests/connect/permissive_mtls_test.go @@ -23,6 +23,7 @@ func TestConnectInject_PermissiveMTLS(t *testing.T) { if !cfg.EnableTransparentProxy { t.Skipf("skipping this because -enable-transparent-proxy is not set") } + cfg.SkipWhenOpenshiftAndCNI(t) ctx := suite.Environment().DefaultContext(t) diff --git a/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml b/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml new file mode 100644 index 0000000000..4b3f7948ee --- /dev/null +++ b/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml @@ -0,0 +1,17 @@ +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: consul-cni +spec: + config: '{ + "cniVersion": "0.3.1", + "type": "consul-cni", + "cni_bin_dir": "/var/lib/cni/bin", + "cni_net_dir": "/etc/kubernetes/cni/net.d", + "kubeconfig": "ZZZ-consul-cni-kubeconfig", + "log_level": "debug", + "multus": true, + "name": "consul-cni", + "type": "consul-cni" + }' + diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml new file mode 100644 index 0000000000..4d4a53b87f --- /dev/null +++ b/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-inject/patch.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-inject/patch.yaml new file mode 100644 index 0000000000..8cc6d10411 --- /dev/null +++ b/acceptance/tests/fixtures/cases/static-client-openshift-inject/patch.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" + "k8s.v1.cni.cncf.io/networks": '[{ "name":"consul-cni" }]' diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml new file mode 100644 index 0000000000..4d4a53b87f --- /dev/null +++ b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/patch.yaml new file mode 100644 index 0000000000..3b9c91fcc0 --- /dev/null +++ b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/patch.yaml @@ -0,0 +1,18 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# When using the CNI on OpenShift, we need to specify the +# network attachment definition for the pods to use. This assumes +# that one named 'consul-cni' was created by the acceptance tests. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + "k8s.v1.cni.cncf.io/networks": '[{ "name":"consul-cni" }]' + diff --git a/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml new file mode 100644 index 0000000000..bc50c78adf --- /dev/null +++ b/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-server-openshift/patch.yaml b/acceptance/tests/fixtures/cases/static-server-openshift/patch.yaml new file mode 100644 index 0000000000..8e2ed857f3 --- /dev/null +++ b/acceptance/tests/fixtures/cases/static-server-openshift/patch.yaml @@ -0,0 +1,42 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + "k8s.v1.cni.cncf.io/networks": '[{ "name":"consul-cni" }]' + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/kschoche/http-echo:latest + args: + - -text="hello world" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + livenessProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + startupProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 1 + readinessProbe: + exec: + command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + serviceAccountName: static-server diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 1b866888c0..18f57b188c 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -15,6 +15,29 @@ as well as the global.name setting. {{- end -}} {{- end -}} +{{- define "consul.restrictedSecurityContext" -}} +{{- if not .Values.global.enablePodSecurityPolicies -}} +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault +{{- if not .Values.global.openshift.enabled -}} +{{/* +We must set runAsUser or else the root user will be used in some cases and +containers will fail to start due to runAsNonRoot above (e.g. +tls-init-cleanup). On OpenShift, runAsUser is automatically. We pick user 100 +because it is a non-root user id that exists in the consul, consul-dataplane, +and consul-k8s-control-plane images. +*/}} + runAsUser: 100 +{{- end -}} +{{- end -}} +{{- end -}} + {{- define "consul.vaultSecretTemplate" -}} | {{ "{{" }}- with secret "{{ .secretName }}" -{{ "}}" }} @@ -422,4 +445,4 @@ Usage: {{ template "consul.validateTelemetryCollectorCloud" . }} {{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not .Values.global.cloud.resourceId.secretKey)) }} {{fail "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set"}} {{- end }} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 14c3961b4e..e726c9ecc9 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -94,6 +94,7 @@ spec: - containerPort: 8080 name: webhook-server protocol: TCP + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index 8731aaed81..a987c3b591 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -40,6 +40,7 @@ spec: containers: - name: gateway-cleanup image: {{ .Values.global.imageK8S }} + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 5fcd96cad3..3a29f75e66 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -40,6 +40,7 @@ spec: containers: - name: gateway-resources image: {{ .Values.global.imageK8S }} + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml index 9d296478a1..240bfe3f9c 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml @@ -48,6 +48,7 @@ spec: containers: - name: gossip-encryption-autogen image: "{{ .Values.global.imageK8S }}" + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} command: - "/bin/sh" - "-ec" diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 4d0aa8f05d..c9f6763bd8 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -60,6 +60,9 @@ spec: containers: - name: server-acl-init-cleanup image: {{ .Values.global.imageK8S }} + {{- if not .Values.server.containerSecurityContext.aclInit }} + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} + {{- end }} command: - consul-k8s-control-plane args: diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index 6625e23b38..c3d4a710e8 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -129,6 +129,9 @@ spec: containers: - name: server-acl-init-job image: {{ .Values.global.imageK8S }} + {{- if not .Values.server.containerSecurityContext.aclInit }} + {{- include "consul.restrictedSecurityContext" . | nindent 8 }} + {{- end }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 9efbcb8085..2ad04f0755 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -238,6 +238,7 @@ spec: volumeMounts: - name: extra-config mountPath: /consul/extra-config + {{- include "consul.restrictedSecurityContext" . | nindent 8 }} containers: - name: consul image: "{{ default .Values.global.image .Values.server.image }}" @@ -527,9 +528,11 @@ spec: {{- toYaml .Values.server.resources | nindent 12 }} {{- end }} {{- end }} - {{- if not .Values.global.openshift.enabled }} + {{- if .Values.server.containerSecurityContext.server }} securityContext: {{- toYaml .Values.server.containerSecurityContext.server | nindent 12 }} + {{- else }} + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} {{- end }} {{- if .Values.server.extraContainers }} {{ toYaml .Values.server.extraContainers | nindent 8 }} diff --git a/charts/consul/templates/tls-init-cleanup-job.yaml b/charts/consul/templates/tls-init-cleanup-job.yaml index 69b5a30849..2254a38ed2 100644 --- a/charts/consul/templates/tls-init-cleanup-job.yaml +++ b/charts/consul/templates/tls-init-cleanup-job.yaml @@ -48,6 +48,9 @@ spec: containers: - name: tls-init-cleanup image: "{{ .Values.global.image }}" + {{- if not .Values.server.containerSecurityContext.tlsInit }} + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} + {{- end }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index 5839f07dbf..12d3acbad8 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -63,6 +63,9 @@ spec: containers: - name: tls-init image: "{{ .Values.global.imageK8S }}" + {{- if not .Values.server.containerSecurityContext.tlsInit }} + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} + {{- end }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index dd93c039d2..7ba25b330c 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -51,6 +51,7 @@ spec: -deployment-namespace={{ .Release.Namespace }} image: {{ .Values.global.imageK8S }} name: webhook-cert-manager + {{- include "consul.restrictedSecurityContext" . | nindent 8 }} resources: limits: cpu: 100m diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 108fd9bbf8..a60884d20c 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -829,9 +829,9 @@ load _helpers } #-------------------------------------------------------------------- -# global.openshift.enabled & client.containerSecurityContext +# global.openshift.enabled && server.containerSecurityContext -@test "server/StatefulSet: container level securityContexts are not set when global.openshift.enabled=true" { +@test "server/StatefulSet: Can set container level securityContexts when global.openshift.enabled=true" { cd `chart_dir` local manifest=$(helm template \ -s templates/server-statefulset.yaml \ @@ -839,8 +839,72 @@ load _helpers --set 'server.containerSecurityContext.server.privileged=false' \ . | tee /dev/stderr) + local actual=$(echo "$manifest" | yq -r '.spec.template.spec.containers | map(select(.name == "consul")) | .[0].securityContext.privileged') + [ "${actual}" = "false" ] +} + +#-------------------------------------------------------------------- +# global.openshift.enabled + +@test "server/StatefulSet: restricted container securityContexts are set when global.openshift.enabled=true" { + cd `chart_dir` + local manifest=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.openshift.enabled=true' \ + . | tee /dev/stderr) + + local expected=$(echo '{ + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": ["ALL"] + }, + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + }') + + # Check consul container local actual=$(echo "$manifest" | yq -r '.spec.template.spec.containers | map(select(.name == "consul")) | .[0].securityContext') - [ "${actual}" = "null" ] + local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') + [ "$equal" == "true" ] + + # Check locality-init container + local actual=$(echo "$manifest" | yq -r '.spec.template.spec.initContainers | map(select(.name == "locality-init")) | .[0].securityContext') + local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') + [ "$equal" == "true" ] +} + +#-------------------------------------------------------------------- +# global.openshift.enabled = false + +@test "server/StatefulSet: restricted container securityContexts are set by default" { + cd `chart_dir` + local manifest=$(helm template \ + -s templates/server-statefulset.yaml \ + . | tee /dev/stderr) + + local expected=$(echo '{ + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": ["ALL"] + }, + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + }, + "runAsUser": 100 + }') + + # Check consul container + local actual=$(echo "$manifest" | yq -r '.spec.template.spec.containers | map(select(.name == "consul")) | .[0].securityContext') + local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') + [ "$equal" == "true" ] + + # Check locality-init container + local actual=$(echo "$manifest" | yq -r '.spec.template.spec.initContainers | map(select(.name == "locality-init")) | .[0].securityContext') + local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') + [ "$equal" == "true" ] } #-------------------------------------------------------------------- From a924e88554a2c2a8ce1110734c15660e7688a451 Mon Sep 17 00:00:00 2001 From: skpratt Date: Mon, 24 Jul 2023 12:18:27 -0500 Subject: [PATCH 298/592] change fips delimiter to + (#2480) (#2591) --- .github/workflows/build.yml | 30 +++++++++++++++--------------- cli/version/version.go | 2 +- control-plane/version/version.go | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 12904b3070..f79369b440 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,9 +79,9 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: "+fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: "+fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: "+fips1402" } # control-plane - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } @@ -96,9 +96,9 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: "+fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: "+fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: "+fips1402" } # consul-cni - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } @@ -112,9 +112,9 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: ".fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: ".fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: ".fips1402" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: "+fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: "+fips1402", pkg_suffix: "-fips" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: "+fips1402" } fail-fast: true @@ -129,7 +129,7 @@ jobs: go-version: ${{ matrix.go }} - name: Replace Go for Windows FIPS with Microsoft Go - if: ${{ matrix.fips == '.fips1402' && matrix.goos == 'windows' }} + if: ${{ matrix.fips == '+fips1402' && matrix.goos == 'windows' }} run: | # Uninstall standard Go and use microsoft/go instead rm -rf /home/runner/actions-runner/_work/_tool/go @@ -143,7 +143,7 @@ jobs: fi - name: Install cross-compiler for FIPS on arm - if: ${{ matrix.fips == '.fips1402' && matrix.goarch == 'arm64' }} + if: ${{ matrix.fips == '+fips1402' && matrix.goarch == 'arm64' }} run: | sudo apt-get update --allow-releaseinfo-change-suite --allow-releaseinfo-change-version && sudo apt-get install -y gcc-aarch64-linux-gnu @@ -252,8 +252,8 @@ jobs: - { goos: "linux", goarch: "arm64" } - { goos: "linux", goarch: "386" } - { goos: "linux", goarch: "amd64" } - - { goos: "linux", goarch: "amd64", fips: ".fips1402" } - - { goos: "linux", goarch: "arm64", fips: ".fips1402" } + - { goos: "linux", goarch: "amd64", fips: "+fips1402" } + - { goos: "linux", goarch: "arm64", fips: "+fips1402" } env: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} @@ -323,7 +323,7 @@ jobs: matrix: include: - { arch: "amd64" } - - { arch: "amd64", fips: ".fips1402" } + - { arch: "amd64", fips: "+fips1402" } env: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} @@ -386,7 +386,7 @@ jobs: strategy: matrix: arch: [ "amd64" ] - fips: [ ".fips1402", "" ] + fips: [ "+fips1402", "" ] env: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} diff --git a/cli/version/version.go b/cli/version/version.go index 9cde4a2d66..952cd9ca81 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -40,7 +40,7 @@ func GetHumanVersion() string { } if IsFIPS() { - version += ".fips1402" + version += "+fips1402" } if release != "" { diff --git a/control-plane/version/version.go b/control-plane/version/version.go index 9cde4a2d66..952cd9ca81 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -40,7 +40,7 @@ func GetHumanVersion() string { } if IsFIPS() { - version += ".fips1402" + version += "+fips1402" } if release != "" { From 5b57e6340dff44157cb7a984ac7220e47849dfb9 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 24 Jul 2023 16:59:34 -0400 Subject: [PATCH 299/592] [NET-4865] security: Upgrade Go and net/http CVE-2023-29406 (#2642) security: Upgrade Go and net/http Upgrade to Go 1.20.6 and `net/http` 1.12.0 to resolve CVE-2023-29406. --- .changelog/2642.txt | 4 ++++ .go-version | 2 +- acceptance/go.mod | 10 +++++----- acceptance/go.sum | 20 ++++++++++---------- cli/go.mod | 10 +++++----- cli/go.sum | 19 ++++++++++--------- control-plane/go.mod | 10 +++++----- control-plane/go.sum | 20 ++++++++++---------- 8 files changed, 50 insertions(+), 45 deletions(-) create mode 100644 .changelog/2642.txt diff --git a/.changelog/2642.txt b/.changelog/2642.txt new file mode 100644 index 0000000000..5278ed705c --- /dev/null +++ b/.changelog/2642.txt @@ -0,0 +1,4 @@ +```release-note:security +Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. +This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). +``` diff --git a/.go-version b/.go-version index 0bd54efd31..e63679c766 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.20.4 +1.20.6 diff --git a/acceptance/go.mod b/acceptance/go.mod index 382d9f8225..b19015eda4 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -119,14 +119,14 @@ require ( go.opentelemetry.io/otel v1.11.1 // indirect go.opentelemetry.io/otel/trace v1.11.1 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.1.0 // indirect + golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 53b9400c42..43a557f32b 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -800,8 +800,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -887,8 +887,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -984,13 +984,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1001,8 +1001,8 @@ 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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/cli/go.mod b/cli/go.mod index cdee871f38..5744e9dad0 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -17,7 +17,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.8.3 - golang.org/x/text v0.9.0 + golang.org/x/text v0.11.0 helm.sh/helm/v3 v3.9.4 k8s.io/api v0.25.0 k8s.io/apiextensions-apiserver v0.25.0 @@ -166,13 +166,13 @@ require ( github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.mongodb.org/mongo-driver v1.11.1 // indirect go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect diff --git a/cli/go.sum b/cli/go.sum index 41b515d2e4..7861cb73d3 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -873,8 +873,9 @@ golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -962,8 +963,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1075,14 +1076,14 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1092,8 +1093,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/control-plane/go.mod b/control-plane/go.mod index 71396e2fd1..9d184840cb 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -29,7 +29,7 @@ require ( github.com/stretchr/testify v1.8.3 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 - golang.org/x/text v0.9.0 + golang.org/x/text v0.11.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 gopkg.in/yaml.v2 v2.4.0 @@ -143,13 +143,13 @@ require ( go.opencensus.io v0.22.4 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.1.0 // indirect + golang.org/x/crypto v0.11.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/api v0.30.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 2f7cbde2fc..fa88dab62a 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -574,8 +574,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -655,8 +655,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -743,13 +743,13 @@ 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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -759,8 +759,8 @@ 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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 6b26d91a8a2c1b5f9e4716779cf3b13aa35bfdd6 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Tue, 25 Jul 2023 15:18:51 -0400 Subject: [PATCH 300/592] Consul client always logs into the local datacenter (#2652) The consul client always logs into the local datacenter --- .changelog/2652.txt | 3 +++ charts/consul/templates/client-daemonset.yaml | 4 ---- charts/consul/test/unit/client-daemonset.bats | 23 ------------------- 3 files changed, 3 insertions(+), 27 deletions(-) create mode 100644 .changelog/2652.txt diff --git a/.changelog/2652.txt b/.changelog/2652.txt new file mode 100644 index 0000000000..efa290c0e7 --- /dev/null +++ b/.changelog/2652.txt @@ -0,0 +1,3 @@ +```release-note:bug +helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. +``` \ No newline at end of file diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 09a70b394e..345c5c731e 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -510,11 +510,7 @@ spec: value: "component=client,pod=$(NAMESPACE)/$(POD_NAME)" {{- end }} - name: CONSUL_LOGIN_DATACENTER - {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter }} - value: {{ .Values.global.federation.primaryDatacenter }} - {{- else }} value: {{ .Values.global.datacenter }} - {{- end}} command: - "/bin/sh" - "-ec" diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index 4c38207635..6e7a030cb1 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -2127,29 +2127,6 @@ rollingUpdate: [[ "$output" =~ "If global.federation.enabled is true, global.adminPartitions.enabled must be false because they are mutually exclusive" ]] } -@test "client/DaemonSet: consul login datacenter is set to primary when when federation enabled in non-primary datacenter" { - cd `chart_dir` - local object=$(helm template \ - -s templates/client-daemonset.yaml \ - --set 'client.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.datacenter=dc1' \ - --set 'global.federation.enabled=true' \ - --set 'global.federation.primaryDatacenter=dc2' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.initContainers[] | select(.name == "client-acl-init")' | tee /dev/stderr) - - local actual=$(echo $object | - yq '[.env[11].name] | any(contains("CONSUL_LOGIN_DATACENTER"))' | tee /dev/stderr) - [ "${actual}" = "true" ] - - local actual=$(echo $object | - yq '[.env[11].value] | any(contains("dc2"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - #-------------------------------------------------------------------- # extraContainers From 89a1c6ddfebe00050d7a99a4525aa4da3e6a72ab Mon Sep 17 00:00:00 2001 From: Mark Campbell-Vincent Date: Tue, 25 Jul 2023 16:54:20 -0400 Subject: [PATCH 301/592] Add support for requestTimeout in Service Resolver spec (#2641) * Add support for requestTimeout in Service Resolver spec * preserve serviceresolvers.yaml Preserving yaml from main, only adding requesttimeout property. * update generated.deepcopy.go * Use latest controller-gen to generate CRDs --------- Co-authored-by: Ashwin Venkatesh --- .../crd-controlplanerequestlimits.yaml | 5 +- .../templates/crd-exportedservices.yaml | 5 +- .../templates/crd-gatewayclassconfigs.yaml | 5 +- .../consul/templates/crd-gatewayclasses.yaml | 3 +- charts/consul/templates/crd-gateways.yaml | 3 +- charts/consul/templates/crd-grpcroutes.yaml | 3 +- charts/consul/templates/crd-httproutes.yaml | 3 +- .../consul/templates/crd-ingressgateways.yaml | 5 +- charts/consul/templates/crd-jwtproviders.yaml | 5 +- charts/consul/templates/crd-meshes.yaml | 5 +- charts/consul/templates/crd-meshservices.yaml | 5 +- .../templates/crd-peeringacceptors.yaml | 5 +- .../consul/templates/crd-peeringdialers.yaml | 5 +- .../consul/templates/crd-proxydefaults.yaml | 5 +- .../consul/templates/crd-referencegrants.yaml | 3 + .../consul/templates/crd-samenessgroups.yaml | 5 +- .../consul/templates/crd-servicedefaults.yaml | 5 +- .../templates/crd-serviceintentions.yaml | 5 +- .../templates/crd-serviceresolvers.yaml | 18 +- .../consul/templates/crd-servicerouters.yaml | 5 +- .../templates/crd-servicesplitters.yaml | 5 +- charts/consul/templates/crd-tcproutes.yaml | 3 + .../templates/crd-terminatinggateways.yaml | 5 +- charts/consul/templates/crd-tlsroutes.yaml | 3 + charts/consul/templates/crd-udproutes.yaml | 3 + .../api/v1alpha1/serviceresolver_types.go | 4 + .../v1alpha1/serviceresolver_types_test.go | 4 + .../api/v1alpha1/zz_generated.deepcopy.go | 158 ++++++++++++++++-- ...shicorp.com_controlplanerequestlimits.yaml | 3 +- ...consul.hashicorp.com_exportedservices.yaml | 3 +- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 3 +- .../consul.hashicorp.com_ingressgateways.yaml | 3 +- .../consul.hashicorp.com_jwtproviders.yaml | 3 +- .../bases/consul.hashicorp.com_meshes.yaml | 3 +- .../consul.hashicorp.com_meshservices.yaml | 3 +- ...consul.hashicorp.com_peeringacceptors.yaml | 3 +- .../consul.hashicorp.com_peeringdialers.yaml | 3 +- .../consul.hashicorp.com_proxydefaults.yaml | 3 +- .../consul.hashicorp.com_samenessgroups.yaml | 3 +- .../consul.hashicorp.com_servicedefaults.yaml | 3 +- ...onsul.hashicorp.com_serviceintentions.yaml | 3 +- ...consul.hashicorp.com_serviceresolvers.yaml | 16 +- .../consul.hashicorp.com_servicerouters.yaml | 3 +- ...consul.hashicorp.com_servicesplitters.yaml | 3 +- ...sul.hashicorp.com_terminatinggateways.yaml | 3 +- control-plane/config/rbac/role.yaml | 4 - control-plane/config/webhook/manifests.yaml | 4 - 47 files changed, 241 insertions(+), 119 deletions(-) diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml index bd1d6118b9..67ff258eb8 100644 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: controlplanerequestlimits.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ControlPlaneRequestLimit diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 7ffddf7537..8581ac4e88 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: exportedservices.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ExportedServices diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 65d425edc4..7060757b23 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: gatewayclassconfigs.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: GatewayClassConfig diff --git a/charts/consul/templates/crd-gatewayclasses.yaml b/charts/consul/templates/crd-gatewayclasses.yaml index 93435b7fce..8f381a7065 100644 --- a/charts/consul/templates/crd-gatewayclasses.yaml +++ b/charts/consul/templates/crd-gatewayclasses.yaml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -6,7 +8,6 @@ metadata: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/crd-gateways.yaml b/charts/consul/templates/crd-gateways.yaml index 41df34942a..d9c381b1de 100644 --- a/charts/consul/templates/crd-gateways.yaml +++ b/charts/consul/templates/crd-gateways.yaml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -6,7 +8,6 @@ metadata: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/crd-grpcroutes.yaml b/charts/consul/templates/crd-grpcroutes.yaml index 739ed2c659..cc126d73c3 100644 --- a/charts/consul/templates/crd-grpcroutes.yaml +++ b/charts/consul/templates/crd-grpcroutes.yaml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -6,7 +8,6 @@ metadata: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/crd-httproutes.yaml b/charts/consul/templates/crd-httproutes.yaml index bba3672d16..5f45528567 100644 --- a/charts/consul/templates/crd-httproutes.yaml +++ b/charts/consul/templates/crd-httproutes.yaml @@ -1,3 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -6,7 +8,6 @@ metadata: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 gateway.networking.k8s.io/bundle-version: v0.6.2 gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index ef33890461..eff7ef61a9 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: ingressgateways.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: IngressGateway diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index c7d20883e8..fa87f37489 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: jwtproviders.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: JWTProvider diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index cdc11b6ed9..f2549b5111 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: meshes.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: Mesh diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml index 859c8683ee..aa808113a2 100644 --- a/charts/consul/templates/crd-meshservices.yaml +++ b/charts/consul/templates/crd-meshservices.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: meshservices.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: MeshService diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index 3822f3bdfe..40f7f1d4d6 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: peeringacceptors.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: PeeringAcceptor diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index 405361c486..bfe4778d0c 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: peeringdialers.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: PeeringDialer diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 30dd25f674..a224effc12 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: proxydefaults.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ProxyDefaults diff --git a/charts/consul/templates/crd-referencegrants.yaml b/charts/consul/templates/crd-referencegrants.yaml index db9cf12027..d50211291d 100644 --- a/charts/consul/templates/crd-referencegrants.yaml +++ b/charts/consul/templates/crd-referencegrants.yaml @@ -1,4 +1,7 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index c1d1c85a8e..7cc3b71ae1 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: samenessgroups.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: SamenessGroup diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index c926ece62a..e295732bfa 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicedefaults.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceDefaults diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 335d2eff7a..5f849f65ba 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: serviceintentions.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceIntentions diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 99cc1bb090..a18cc94de4 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: serviceresolvers.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceResolver @@ -228,12 +227,13 @@ spec: type: object type: object prioritizeByLocality: - description: PrioritizeByLocality contains the configuration for - locality aware routing. + description: PrioritizeByLocality controls whether the locality of + services within the local partition will be used to prioritize connectivity. properties: mode: - description: Mode specifies the behavior of PrioritizeByLocality - routing. Valid values are "", "none", and "failover". + description: 'Mode specifies the type of prioritization that will + be performed when selecting nodes in the local partition. Valid + values are: "" (default "none"), "none", and "failover".' type: string type: object redirect: @@ -275,6 +275,10 @@ spec: If empty the default subset is used. type: string type: object + requestTimeout: + description: RequestTimeout is the timeout for receiving an HTTP response + from this service before the connection is terminated. + type: string subsets: additionalProperties: properties: diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index 0157f646b4..c5ba99466c 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicerouters.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceRouter diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index 18fb10341e..abe3ac85cc 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicesplitters.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: ServiceSplitter diff --git a/charts/consul/templates/crd-tcproutes.yaml b/charts/consul/templates/crd-tcproutes.yaml index b5bc7be13c..a17f457a78 100644 --- a/charts/consul/templates/crd-tcproutes.yaml +++ b/charts/consul/templates/crd-tcproutes.yaml @@ -1,4 +1,7 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 955496aeee..cd58d1679c 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -4,16 +4,15 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: terminatinggateways.consul.hashicorp.com +spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd -spec: group: consul.hashicorp.com names: kind: TerminatingGateway diff --git a/charts/consul/templates/crd-tlsroutes.yaml b/charts/consul/templates/crd-tlsroutes.yaml index 1acd1b973a..be72f47d65 100644 --- a/charts/consul/templates/crd-tlsroutes.yaml +++ b/charts/consul/templates/crd-tlsroutes.yaml @@ -1,4 +1,7 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/charts/consul/templates/crd-udproutes.yaml b/charts/consul/templates/crd-udproutes.yaml index 0661b24c1a..fe331cca30 100644 --- a/charts/consul/templates/crd-udproutes.yaml +++ b/charts/consul/templates/crd-udproutes.yaml @@ -1,4 +1,7 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index e5ce3c6fa6..714cd94c2a 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -76,6 +76,9 @@ type ServiceResolverSpec struct { // ConnectTimeout is the timeout for establishing new network connections // to this service. ConnectTimeout metav1.Duration `json:"connectTimeout,omitempty"` + // RequestTimeout is the timeout for receiving an HTTP response from this + // service before the connection is terminated. + RequestTimeout metav1.Duration `json:"requestTimeout,omitempty"` // LoadBalancer determines the load balancing policy and configuration for services // issuing requests to this upstream service. LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` @@ -317,6 +320,7 @@ func (in *ServiceResolver) ToConsul(datacenter string) capi.ConfigEntry { Redirect: in.Spec.Redirect.toConsul(), Failover: in.Spec.Failover.toConsul(), ConnectTimeout: in.Spec.ConnectTimeout.Duration, + RequestTimeout: in.Spec.RequestTimeout.Duration, LoadBalancer: in.Spec.LoadBalancer.toConsul(), PrioritizeByLocality: in.Spec.PrioritizeByLocality.toConsul(), Meta: meta(datacenter), diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index 3a3d5a6016..11751aaa2a 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -104,6 +104,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { }, }, ConnectTimeout: metav1.Duration{Duration: 1 * time.Second}, + RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, LoadBalancer: &LoadBalancer{ Policy: "policy", RingHashConfig: &RingHashConfig{ @@ -188,6 +189,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { }, }, ConnectTimeout: 1 * time.Second, + RequestTimeout: 1 * time.Second, LoadBalancer: &capi.LoadBalancer{ Policy: "policy", RingHashConfig: &capi.RingHashConfig{ @@ -321,6 +323,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { }, }, ConnectTimeout: metav1.Duration{Duration: 1 * time.Second}, + RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, LoadBalancer: &LoadBalancer{ Policy: "policy", RingHashConfig: &RingHashConfig{ @@ -405,6 +408,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { }, }, ConnectTimeout: 1 * time.Second, + RequestTimeout: 1 * time.Second, LoadBalancer: &capi.LoadBalancer{ Policy: "policy", RingHashConfig: &capi.RingHashConfig{ diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 0787f24097..05000031ad 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -126,6 +126,7 @@ func (in *ControlPlaneRequestLimitList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ControlPlaneRequestLimitSpec) DeepCopyInto(out *ControlPlaneRequestLimitSpec) { *out = *in + out.ReadWriteRatesConfig = in.ReadWriteRatesConfig if in.ACL != nil { in, out := &in.ACL, &out.ACL *out = new(ReadWriteRatesConfig) @@ -928,6 +929,78 @@ func (in *IntentionHTTPPermission) DeepCopy() *IntentionHTTPPermission { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IntentionJWTClaimVerification) DeepCopyInto(out *IntentionJWTClaimVerification) { + *out = *in + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionJWTClaimVerification. +func (in *IntentionJWTClaimVerification) DeepCopy() *IntentionJWTClaimVerification { + if in == nil { + return nil + } + out := new(IntentionJWTClaimVerification) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IntentionJWTProvider) DeepCopyInto(out *IntentionJWTProvider) { + *out = *in + if in.VerifyClaims != nil { + in, out := &in.VerifyClaims, &out.VerifyClaims + *out = make([]*IntentionJWTClaimVerification, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(IntentionJWTClaimVerification) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionJWTProvider. +func (in *IntentionJWTProvider) DeepCopy() *IntentionJWTProvider { + if in == nil { + return nil + } + out := new(IntentionJWTProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IntentionJWTRequirement) DeepCopyInto(out *IntentionJWTRequirement) { + *out = *in + if in.Providers != nil { + in, out := &in.Providers, &out.Providers + *out = make([]*IntentionJWTProvider, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(IntentionJWTProvider) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionJWTRequirement. +func (in *IntentionJWTRequirement) DeepCopy() *IntentionJWTRequirement { + if in == nil { + return nil + } + out := new(IntentionJWTRequirement) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IntentionPermission) DeepCopyInto(out *IntentionPermission) { *out = *in @@ -936,6 +1009,11 @@ func (in *IntentionPermission) DeepCopyInto(out *IntentionPermission) { *out = new(IntentionHTTPPermission) (*in).DeepCopyInto(*out) } + if in.JWT != nil { + in, out := &in.JWT, &out.JWT + *out = new(IntentionJWTRequirement) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionPermission. @@ -1123,6 +1201,31 @@ func (in *JWTLocationQueryParam) DeepCopy() *JWTLocationQueryParam { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in JWTLocations) DeepCopyInto(out *JWTLocations) { + { + in := &in + *out = make(JWTLocations, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(JWTLocation) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocations. +func (in JWTLocations) DeepCopy() JWTLocations { + if in == nil { + return nil + } + out := new(JWTLocations) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTProvider) DeepCopyInto(out *JWTProvider) { *out = *in @@ -1952,6 +2055,21 @@ func (in *ProxyDefaultsSpec) DeepCopy() *ProxyDefaultsSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReadWriteRatesConfig) DeepCopyInto(out *ReadWriteRatesConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReadWriteRatesConfig. +func (in *ReadWriteRatesConfig) DeepCopy() *ReadWriteRatesConfig { + if in == nil { + return nil + } + out := new(ReadWriteRatesConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RemoteJWKS) DeepCopyInto(out *RemoteJWKS) { *out = *in @@ -1987,20 +2105,6 @@ func (in *RetryPolicyBackOff) DeepCopy() *RetryPolicyBackOff { return out } -func (in *ReadWriteRatesConfig) DeepCopyInto(out *ReadWriteRatesConfig) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReadWriteRatesConfig. -func (in *ReadWriteRatesConfig) DeepCopy() *ReadWriteRatesConfig { - if in == nil { - return nil - } - out := new(ReadWriteRatesConfig) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RingHashConfig) DeepCopyInto(out *RingHashConfig) { *out = *in @@ -2372,6 +2476,11 @@ func (in *ServiceIntentionsSpec) DeepCopyInto(out *ServiceIntentionsSpec) { } } } + if in.JWT != nil { + in, out := &in.JWT, &out.JWT + *out = new(IntentionJWTRequirement) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceIntentionsSpec. @@ -2509,6 +2618,21 @@ func (in *ServiceResolverList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceResolverPrioritizeByLocality) DeepCopyInto(out *ServiceResolverPrioritizeByLocality) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceResolverPrioritizeByLocality. +func (in *ServiceResolverPrioritizeByLocality) DeepCopy() *ServiceResolverPrioritizeByLocality { + if in == nil { + return nil + } + out := new(ServiceResolverPrioritizeByLocality) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceResolverRedirect) DeepCopyInto(out *ServiceResolverRedirect) { *out = *in @@ -2547,11 +2671,17 @@ func (in *ServiceResolverSpec) DeepCopyInto(out *ServiceResolverSpec) { } } out.ConnectTimeout = in.ConnectTimeout + out.RequestTimeout = in.RequestTimeout if in.LoadBalancer != nil { in, out := &in.LoadBalancer, &out.LoadBalancer *out = new(LoadBalancer) (*in).DeepCopyInto(*out) } + if in.PrioritizeByLocality != nil { + in, out := &in.PrioritizeByLocality, &out.PrioritizeByLocality + *out = new(ServiceResolverPrioritizeByLocality) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceResolverSpec. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml index 2eef465ada..4d1d808428 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index f066c90612..dac72f3646 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml index a8393cd8fd..44eff52492 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index f7ccf205d9..e9cf081721 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml index 8ca1ec0748..7506cc57dc 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com 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 bc46b6ab37..16dd398f99 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml index 0871fc32e5..125883bdc5 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index f6f9eda72b..894228a218 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index 7e0927c169..51c3e38319 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 7396816f7e..1be3b37703 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml index 23de092485..259ca7b910 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index a5501a98d2..83503f11f3 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com 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 cd28173ba8..9553c73450 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index ec52c04e05..b83a859dc4 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -224,12 +223,13 @@ spec: type: object type: object prioritizeByLocality: - description: PrioritizeByLocality contains the configuration for - locality aware routing. + description: PrioritizeByLocality controls whether the locality of + services within the local partition will be used to prioritize connectivity. properties: mode: - description: Mode specifies the behavior of PrioritizeByLocality - routing. Valid values are "", "none", and "failover". + description: 'Mode specifies the type of prioritization that will + be performed when selecting nodes in the local partition. Valid + values are: "" (default "none"), "none", and "failover".' type: string type: object redirect: @@ -271,6 +271,10 @@ spec: If empty the default subset is used. type: string type: object + requestTimeout: + description: RequestTimeout is the timeout for receiving an HTTP response + from this service before the connection is terminated. + type: string subsets: additionalProperties: properties: diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 5919e23005..04590cc007 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index d5848ed6ec..3a47472ba7 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index 4910e42829..acf61cde4c 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -6,8 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 7f90780e02..74328a8ae3 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -1,11 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - creationTimestamp: null name: manager-role rules: - apiGroups: diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index 0861f9253a..a515888527 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -1,11 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: - creationTimestamp: null name: mutating-webhook-configuration webhooks: - admissionReviewVersions: From 94414a72dc3ed576621c31ce2f75d4d6bc214d54 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Wed, 26 Jul 2023 10:32:57 -0400 Subject: [PATCH 302/592] Increase timeout for acl replication to 60 seconds and poll every 500 ms (#2656) increase timeout for acl replication to 60 seconds and poll every 500 ms --- .changelog/2656.txt | 3 +++ control-plane/subcommand/common/common.go | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .changelog/2656.txt diff --git a/.changelog/2656.txt b/.changelog/2656.txt new file mode 100644 index 0000000000..07436087d3 --- /dev/null +++ b/.changelog/2656.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: increase timeout after login for ACL replication to 60 seconds +``` \ No newline at end of file diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index 598ba66ea5..1636c0b10e 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -39,8 +39,8 @@ const ( // The number of times to attempt ACL Login. numLoginRetries = 100 - raftReplicationTimeout = 2 * time.Second - tokenReadPollingInterval = 100 * time.Millisecond + raftReplicationTimeout = 60 * time.Second + tokenReadPollingInterval = 500 * time.Millisecond ) // Logger returns an hclog instance with log level set and JSON logging enabled/disabled, or an error if level is invalid. From 596a2a78d8e512003f2f4855d4cd312d79910f81 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Wed, 26 Jul 2023 15:24:38 -0500 Subject: [PATCH 303/592] Update changelog to address cloud auto-join change in 1.0.0 (#2667) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54bdba549b..0d60f91b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -404,6 +404,7 @@ BREAKING CHANGES: * `client.enabled` now defaults to `false`. Setting it to `true` will deploy client agents, however, none of the consul-k8s components will use clients for their operation. * `global.imageEnvoy` is no longer used for sidecar proxies, as well as mesh, terminating, and ingress gateways. * `externalServers.grpcPort` default is now `8502` instead of `8503`. + * `externalServers.hosts` no longer supports [cloud auto-join](https://developer.hashicorp.com/consul/docs/install/cloud-auto-join) strings directly. Instead, include an [`exec=`](https://github.com/hashicorp/go-netaddrs#command-line-tool-usage) string in the `externalServers.hosts` list to invoke the `discover` CLI. For example, the following string invokes the `discover` CLI with a cloud auto-join string: `exec=discover -q addrs provider=aws region=us-west-2 tag_key=consul-server tag_value=true`. The `discover` CLI is included in the official `hashicorp/consul-dataplane` images by default. * `meshGateway.service.enabled` value is removed. Mesh gateways now will always have a Kubernetes service as this is required to register them as a service with Consul. * `meshGateway.initCopyConsulContainer`, `ingressGateways.initCopyConsulContainer`, `terminatingGateways.initCopyConsulContainer` values are removed. * `connectInject.enabled` now defaults to `true`. [[GH-1551](https://github.com/hashicorp/consul-k8s/pull/1551)] From f026d439b553071444899edcde8925ac4f317cc7 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Wed, 26 Jul 2023 17:22:46 -0500 Subject: [PATCH 304/592] NET-4967: Fix helm install when setting copyAnnotations or nodeSelector for apiGateway (#2597) * Support multiline nodeSelector arg * Support multiline service annotations arg * Update test assertions * Add changelog entry --- .changelog/2597.txt | 3 +++ .../consul/templates/gateway-resources-job.yaml | 6 ++++-- .../consul/test/unit/gateway-resources-job.bats | 16 +++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 .changelog/2597.txt diff --git a/.changelog/2597.txt b/.changelog/2597.txt new file mode 100644 index 0000000000..83cc369b6d --- /dev/null +++ b/.changelog/2597.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: fix helm install when setting copyAnnotations or nodeSelector +``` diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 3a29f75e66..1fa712759d 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -88,13 +88,15 @@ spec: {{- end}} {{- end}} {{- if .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector }} - - -node-selector={{ .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector }} + - -node-selector + - {{- toYaml .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector | nindent 14 -}} {{- end }} {{- if .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} - -tolerations={{ .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} {{- end }} {{- if .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service }} - - -service-annotations={{ .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations }} + - -service-annotations + - {{- toYaml .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations | nindent 14 -}} {{- end }} - -service-type={{ .Values.connectInject.apiGateway.managedGatewayClass.serviceType }} {{- end}} diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats index 27bba00ed5..d79838770d 100644 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ b/charts/consul/test/unit/gateway-resources-job.bats @@ -107,14 +107,20 @@ target=templates/gateway-resources-job.yaml local actual=$(echo "$spec" | jq 'any(index("-service-type=Foo"))') [ "${actual}" = "true" ] - local actual=$(echo "$spec" | jq '.[12] | ."-node-selector=foo"') - [ "${actual}" = "\"bar\"" ] + local actual=$(echo "$spec" | jq '.[12]') + [ "${actual}" = "\"-node-selector\"" ] + + local actual=$(echo "$spec" | jq '.[13]') + [ "${actual}" = "\"foo: bar\"" ] - local actual=$(echo "$spec" | jq '.[13] | ."-tolerations=- key"') + local actual=$(echo "$spec" | jq '.[14] | ."-tolerations=- key"') [ "${actual}" = "\"bar\"" ] - local actual=$(echo "$spec" | jq '.[14]') - [ "${actual}" = "\"-service-annotations=- bingo\"" ] + local actual=$(echo "$spec" | jq '.[15]') + [ "${actual}" = "\"-service-annotations\"" ] + + local actual=$(echo "$spec" | jq '.[16]') + [ "${actual}" = "\"- bingo\"" ] } From 7bb0a57aec89dbdab4a6313536ae4de540e43d5f Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Thu, 27 Jul 2023 09:47:02 -0400 Subject: [PATCH 305/592] Fix ordering of licence in templates (#2675) --- charts/consul/templates/crd-gatewayclasses.yaml | 3 ++- charts/consul/templates/crd-gateways.yaml | 3 ++- charts/consul/templates/crd-grpcroutes.yaml | 3 ++- charts/consul/templates/crd-httproutes.yaml | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclasses.yaml b/charts/consul/templates/crd-gatewayclasses.yaml index 8f381a7065..b0725c2baf 100644 --- a/charts/consul/templates/crd-gatewayclasses.yaml +++ b/charts/consul/templates/crd-gatewayclasses.yaml @@ -1,6 +1,7 @@ +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/charts/consul/templates/crd-gateways.yaml b/charts/consul/templates/crd-gateways.yaml index d9c381b1de..923a75477d 100644 --- a/charts/consul/templates/crd-gateways.yaml +++ b/charts/consul/templates/crd-gateways.yaml @@ -1,6 +1,7 @@ +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/charts/consul/templates/crd-grpcroutes.yaml b/charts/consul/templates/crd-grpcroutes.yaml index cc126d73c3..07412ba60b 100644 --- a/charts/consul/templates/crd-grpcroutes.yaml +++ b/charts/consul/templates/crd-grpcroutes.yaml @@ -1,6 +1,7 @@ +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/charts/consul/templates/crd-httproutes.yaml b/charts/consul/templates/crd-httproutes.yaml index 5f45528567..d9055e7406 100644 --- a/charts/consul/templates/crd-httproutes.yaml +++ b/charts/consul/templates/crd-httproutes.yaml @@ -1,6 +1,7 @@ +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: From b6d3e61058e60e8dc644c3de66a0d186123d907e Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:48:26 -0700 Subject: [PATCH 306/592] Mw/net 4260 phase 2 automate the k8s sameness tests (#2579) * add kustomize files - These reflect the different test cases - sameness.yaml defines the ordered list of failovers - static-server responds with a unique name so we can track failover order - static-client includes both DNS and CURL in the image used so we can exec in for testing * add sameness tests - We do a bunch of infra setup for peering and partitions, but after the initial setup only partitions are tested - We test service failover, dns failover and PQ failover scenarios * add 4 kind clusters to make target - The sameness tests require 4 kind clusters, so the make target will now spin up 4 kind clusters - not all tests need 4 kind clusters, but the entire suite of tests can be run with 4 * increase kubectl timeout to 90s - add variable for configuring timeout - timeout was triggering locally on intel mac machine, so this timeout should cover our devs lowest performing machines * add sameness test to test packages * Fix comments on partition connect test --- Makefile | 16 +- .../kind_acceptance_test_packages.yaml | 9 +- acceptance/framework/k8s/kubectl.go | 15 +- .../sameness/default-ns/kustomization.yaml | 5 + .../bases/sameness/default-ns/sameness.yaml | 12 + .../exportedservices-ap1.yaml | 9 + .../exportedservices-ap1/kustomization.yaml | 5 + .../sameness/override-ns/intentions.yaml | 12 + .../sameness/override-ns/kustomization.yaml | 7 + .../override-ns/payment-service-resolver.yaml | 13 + .../override-ns/service-defaults.yaml | 6 + .../bases/sameness/peering/kustomization.yaml | 5 + .../fixtures/bases/sameness/peering/mesh.yaml | 7 + .../ap1-partition/kustomization.yaml | 8 + .../ap1-partition/patch.yaml | 16 + .../default-partition/kustomization.yaml | 8 + .../default-partition/patch.yaml | 16 + .../static-client/default/kustomization.yaml | 8 + .../sameness/static-client/default/patch.yaml | 22 + .../partition/kustomization.yaml | 8 + .../static-client/partition/patch.yaml | 22 + .../static-server/default/kustomization.yaml | 8 + .../sameness/static-server/default/patch.yaml | 23 + .../partition/kustomization.yaml | 8 + .../static-server/partition/patch.yaml | 23 + .../partitions/partitions_connect_test.go | 4 +- acceptance/tests/sameness/main_test.go | 28 ++ acceptance/tests/sameness/sameness_test.go | 454 ++++++++++++++++++ 28 files changed, 764 insertions(+), 13 deletions(-) create mode 100644 acceptance/tests/fixtures/bases/sameness/default-ns/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/exportedservices-ap1.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/default/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/default/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/partition/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/partition/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/default/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/default/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/partition/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/partition/patch.yaml create mode 100644 acceptance/tests/sameness/main_test.go create mode 100644 acceptance/tests/sameness/sameness_test.go diff --git a/Makefile b/Makefile index 4289b81b9b..e2c39de2ea 100644 --- a/Makefile +++ b/Makefile @@ -130,22 +130,26 @@ kind-cni-calico: kubectl create -f $(CURDIR)/acceptance/framework/environment/cni-kind/custom-resources.yaml @sleep 20 -# Helper target for doing local cni acceptance testing -kind-cni: +kind-delete: kind delete cluster --name dc1 kind delete cluster --name dc2 + kind delete cluster --name dc3 + kind delete cluster --name dc4 + + +# Helper target for doing local cni acceptance testing +kind-cni: kind-delete kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc1 --image $(KIND_NODE_IMAGE) make kind-cni-calico kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc2 --image $(KIND_NODE_IMAGE) make kind-cni-calico # Helper target for doing local acceptance testing -kind: - kind delete cluster --name dc1 - kind delete cluster --name dc2 +kind: kind-delete kind create cluster --name dc1 --image $(KIND_NODE_IMAGE) kind create cluster --name dc2 --image $(KIND_NODE_IMAGE) - + kind create cluster --name dc3 --image $(KIND_NODE_IMAGE) + kind create cluster --name dc4 --image $(KIND_NODE_IMAGE) # ===========> Shared Targets diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml index 8677b83c4e..e0e126bbda 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -3,7 +3,8 @@ - {runner: 0, test-packages: "partitions"} - {runner: 1, test-packages: "peering"} -- {runner: 2, test-packages: "connect snapshot-agent wan-federation"} -- {runner: 3, test-packages: "cli vault metrics"} -- {runner: 4, test-packages: "api-gateway ingress-gateway sync example consul-dns"} -- {runner: 5, test-packages: "config-entries terminating-gateway basic"} \ No newline at end of file +- {runner: 2, test-packages: "sameness"} +- {runner: 3, test-packages: "connect snapshot-agent wan-federation"} +- {runner: 4, test-packages: "cli vault metrics"} +- {runner: 5, test-packages: "api-gateway ingress-gateway sync example consul-dns"} +- {runner: 6, test-packages: "config-entries terminating-gateway basic"} diff --git a/acceptance/framework/k8s/kubectl.go b/acceptance/framework/k8s/kubectl.go index ea90212e04..acfedbdb3d 100644 --- a/acceptance/framework/k8s/kubectl.go +++ b/acceptance/framework/k8s/kubectl.go @@ -4,6 +4,7 @@ package k8s import ( + "fmt" "strings" "testing" "time" @@ -16,6 +17,10 @@ import ( "github.com/stretchr/testify/require" ) +const ( + kubectlTimeout = "--timeout=120s" +) + // kubeAPIConnectErrs are errors that sometimes occur when talking to the // Kubernetes API related to connection issues. var kubeAPIConnectErrs = []string{ @@ -97,7 +102,7 @@ func KubectlApplyK(t *testing.T, options *k8s.KubectlOptions, kustomizeDir strin // deletes it from the cluster by running 'kubectl delete -f'. // If there's an error deleting the file, fail the test. func KubectlDelete(t *testing.T, options *k8s.KubectlOptions, configPath string) { - _, err := RunKubectlAndGetOutputE(t, options, "delete", "--timeout=60s", "-f", configPath) + _, err := RunKubectlAndGetOutputE(t, options, "delete", kubectlTimeout, "-f", configPath) require.NoError(t, err) } @@ -107,7 +112,13 @@ func KubectlDelete(t *testing.T, options *k8s.KubectlOptions, configPath string) func KubectlDeleteK(t *testing.T, options *k8s.KubectlOptions, kustomizeDir string) { // Ignore not found errors because Kubernetes automatically cleans up the kube secrets that we deployed // referencing the ServiceAccount when it is deleted. - _, err := RunKubectlAndGetOutputE(t, options, "delete", "--timeout=60s", "--ignore-not-found", "-k", kustomizeDir) + _, err := RunKubectlAndGetOutputE(t, options, "delete", kubectlTimeout, "--ignore-not-found", "-k", kustomizeDir) + require.NoError(t, err) +} + +// KubectlScale takes a deployment and scales it to the provided number of replicas. +func KubectlScale(t *testing.T, options *k8s.KubectlOptions, deployment string, replicas int) { + _, err := RunKubectlAndGetOutputE(t, options, "scale", kubectlTimeout, fmt.Sprintf("--replicas=%d", replicas), deployment) require.NoError(t, err) } diff --git a/acceptance/tests/fixtures/bases/sameness/default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/default-ns/kustomization.yaml new file mode 100644 index 0000000000..3f9d23c28a --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/default-ns/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - sameness.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml new file mode 100644 index 0000000000..0eb7d9e008 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml @@ -0,0 +1,12 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: SamenessGroup +metadata: + name: mine +spec: + members: + - partition: default + - partition: ap1 + - peer: cluster-01-a + - peer: cluster-01-b + - peer: cluster-02-a + - peer: cluster-03-a diff --git a/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/exportedservices-ap1.yaml b/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/exportedservices-ap1.yaml new file mode 100644 index 0000000000..3dc494dd43 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/exportedservices-ap1.yaml @@ -0,0 +1,9 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ExportedServices +metadata: + name: ap1 +spec: + services: [] diff --git a/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/kustomization.yaml new file mode 100644 index 0000000000..1793fa6db7 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - exportedservices-ap1.yaml diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml new file mode 100644 index 0000000000..425b9fe21d --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml @@ -0,0 +1,12 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions +metadata: + name: static-server +spec: + destination: + name: static-server + sources: + - name: static-client + namespace: ns1 + samenessGroup: mine + action: allow diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml new file mode 100644 index 0000000000..adfd1c827b --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - intentions.yaml + - payment-service-resolver.yaml + - service-defaults.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml new file mode 100644 index 0000000000..b2b6b68c3d --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml @@ -0,0 +1,13 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: static-server +spec: + connectTimeout: 15s + failover: + '*': + samenessGroup: mine + policy: + mode: order-by-locality + regions: + - us-west-2 diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml new file mode 100644 index 0000000000..f88d143728 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml @@ -0,0 +1,6 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: static-server +spec: + protocol: http \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/kustomization.yaml new file mode 100644 index 0000000000..926e91236d --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - mesh.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml b/acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml new file mode 100644 index 0000000000..de84382d3e --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml @@ -0,0 +1,7 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: Mesh +metadata: + name: mesh +spec: + peering: + peerThroughMeshGateways: true diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml new file mode 100644 index 0000000000..2a2f47a332 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/sameness/exportedservices-ap1 + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml new file mode 100644 index 0000000000..d71e8211ba --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml @@ -0,0 +1,16 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ExportedServices +metadata: + name: ap1 +spec: + services: + - name: static-server + namespace: ns2 + consumers: + - samenessGroup: mine + - name: mesh-gateway + consumers: + - samenessGroup: mine diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml new file mode 100644 index 0000000000..05de6151fc --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/exportedservices-default + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml new file mode 100644 index 0000000000..9bb440637e --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml @@ -0,0 +1,16 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ExportedServices +metadata: + name: default +spec: + services: + - name: static-server + namespace: ns2 + consumers: + - samenessGroup: mine + - name: mesh-gateway + consumers: + - samenessGroup: mine diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default/kustomization.yaml new file mode 100644 index 0000000000..227f223c9f --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-client/default/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default/patch.yaml new file mode 100644 index 0000000000..1775e9abb1 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-client/default/patch.yaml @@ -0,0 +1,22 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + 'consul.hashicorp.com/connect-inject': 'true' + 'consul.hashicorp.com/connect-service-upstreams': 'static-server.ns2.default:8080' + spec: + containers: + - name: static-client + image: anubhavmishra/tiny-tools:latest + # Just spin & wait forever, we'll use `kubectl exec` to demo + command: ['/bin/sh', '-c', '--'] + args: ['while true; do sleep 30; done;'] + # If ACLs are enabled, the serviceAccountName must match the Consul service name. + serviceAccountName: static-client \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/partition/kustomization.yaml new file mode 100644 index 0000000000..227f223c9f --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-client/partition/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/partition/patch.yaml new file mode 100644 index 0000000000..c1a14c6070 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-client/partition/patch.yaml @@ -0,0 +1,22 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + 'consul.hashicorp.com/connect-inject': 'true' + 'consul.hashicorp.com/connect-service-upstreams': 'static-server.ns2.ap1:8080' + spec: + containers: + - name: static-client + image: anubhavmishra/tiny-tools:latest + # Just spin & wait forever, we'll use `kubectl exec` to demo + command: ['/bin/sh', '-c', '--'] + args: ['while true; do sleep 30; done;'] + # If ACLs are enabled, the serviceAccountName must match the Consul service name. + serviceAccountName: static-client diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/default/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/default/kustomization.yaml new file mode 100644 index 0000000000..c15bfe7ba7 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-server/default/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/default/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/default/patch.yaml new file mode 100644 index 0000000000..ca27b7ba42 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-server/default/patch.yaml @@ -0,0 +1,23 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine + args: + - -text="cluster-01-a" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/partition/kustomization.yaml new file mode 100644 index 0000000000..c15bfe7ba7 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-server/partition/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/partition/patch.yaml new file mode 100644 index 0000000000..044115d1d1 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-server/partition/patch.yaml @@ -0,0 +1,23 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine + args: + - -text="cluster-01-b" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + serviceAccountName: static-server diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index 6b103614c7..c53e5b6171 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -108,6 +108,7 @@ func TestPartitions_Connect(t *testing.T) { "dns.enableRedirection": strconv.FormatBool(cfg.EnableTransparentProxy), } + // Setup the default partition defaultPartitionHelmValues := make(map[string]string) // On Kind, there are no load balancers but since all clusters @@ -129,6 +130,7 @@ func TestPartitions_Connect(t *testing.T) { serverConsulCluster := consul.NewHelmCluster(t, defaultPartitionHelmValues, defaultPartitionClusterContext, cfg, releaseName) serverConsulCluster.Create(t) + // Copy secrets from the default partition to the secondary partition // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) @@ -146,7 +148,7 @@ func TestPartitions_Connect(t *testing.T) { k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryPartitionClusterContext) - // Create client cluster. + // Create secondary partition cluster. secondaryPartitionHelmValues := map[string]string{ "global.enabled": "false", diff --git a/acceptance/tests/sameness/main_test.go b/acceptance/tests/sameness/main_test.go new file mode 100644 index 0000000000..67e6ee42b7 --- /dev/null +++ b/acceptance/tests/sameness/main_test.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sameness + +import ( + "fmt" + "os" + "testing" + + testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" +) + +var suite testsuite.Suite + +func TestMain(m *testing.M) { + suite = testsuite.NewSuite(m) + + expectedNumberOfClusters := 4 + + if suite.Config().EnableMultiCluster && suite.Config().IsExpectedClusterCount(expectedNumberOfClusters) && suite.Config().UseKind { + os.Exit(suite.Run()) + } else { + fmt.Println(fmt.Sprintf("Skipping sameness tests because either -enable-multi-cluster is "+ + "not set, the number of clusters did not match the expected count of %d, or --useKind is false. "+ + "Sameness acceptance tests are currently only suopported on Kind clusters", expectedNumberOfClusters)) + } +} diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go new file mode 100644 index 0000000000..a7a926cd42 --- /dev/null +++ b/acceptance/tests/sameness/sameness_test.go @@ -0,0 +1,454 @@ +package sameness + +import ( + "context" + "fmt" + "strconv" + "testing" + + terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/config" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + primaryDatacenterPartition = "ap1" + primaryServerDatacenter = "dc1" + peer1Datacenter = "dc2" + peer2Datacenter = "dc3" + staticClientNamespace = "ns1" + staticServerNamespace = "ns2" + + keyPrimaryServer = "server" + keyPartition = "partition" + keyPeer1 = "peer1" + keyPeer2 = "peer2" + + staticServerDeployment = "deploy/static-server" + staticClientDeployment = "deploy/static-client" + + primaryServerClusterName = "cluster-01-a" + partitionClusterName = "cluster-01-b" +) + +func TestFailover_Connect(t *testing.T) { + env := suite.Environment() + cfg := suite.Config() + + if !cfg.EnableEnterprise { + t.Skipf("skipping this test because -enable-enterprise is not set") + } + + cases := []struct { + name string + ACLsEnabled bool + }{ + { + "default failover", + false, + }, + { + "secure failover", + true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + /* + Architecture Overview: + Primary Datacenter (DC1) + Default Partition + Peer -> DC2 (cluster-02-a) + Peer -> DC3 (cluster-03-a) + AP1 Partition + Peer -> DC2 (cluster-02-a) + Peer -> DC3 (cluster-03-a) + Datacenter 2 (DC2) + Default Partition + Peer -> DC1 (cluster-01-a) + Peer -> DC1 (cluster-01-b) + Peer -> DC3 (cluster-03-a) + Datacenter 3 (DC3) + Default Partition + Peer -> DC1 (cluster-01-a) + Peer -> DC1 (cluster-01-b) + Peer -> DC2 (cluster-02-a) + + + Architecture Diagram + failover scenarios from perspective of DC1 Default Partition Static-Server + +-------------------------------------------+ + | | + | DC1 | + | | + | +-----------------------------+ | +-----------------------------------+ + | | | | | DC2 | + | | +------------------+ | | Failover 2 | +------------------+ | + | | | +-------+--------+-----------------+------>| | | + | | | Static-Server | | | | | Static-Server | | + | | | +-------+---+ | | | | | + | | | | | | | | | | | + | | | | | | | | | | | + | | | +-------+---+----+-------------+ | | | | + | | +------------------+ | | | | | +------------------+ | + | | Admin Partitions: Default | | | | | | + | | Name: cluster-01-a | | | | | Admin Partitions: Default | + | | | | | | | Name: cluster-02-a | + | +-----------------------------+ | | | | | + | | | | +-----------------------------------+ + | Failover 1| | Failover 3 | + | +-------------------------------+ | | | +-----------------------------------+ + | | | | | | | DC3 | + | | +------------------+ | | | | | +------------------+ | + | | | | | | | | | | Static-Server | | + | | | Static-Server | | | | | | | | | + | | | | | | | | | | | | + | | | | | | | +---+------>| | | + | | | |<------+--+ | | | | | + | | | | | | | +------------------+ | + | | +------------------+ | | | | + | | Admin Partitions: ap1 | | | Admin Partitions: Default | + | | Name: cluster-01-b | | | Name: cluster-03-a | + | | | | | | + | +-------------------------------+ | | | + | | +-----------------------------------+ + +-------------------------------------------+ + */ + + members := map[string]*member{ + keyPrimaryServer: {context: env.DefaultContext(t), hasServer: true}, + keyPartition: {context: env.Context(t, 1), hasServer: false}, + keyPeer1: {context: env.Context(t, 2), hasServer: true}, + keyPeer2: {context: env.Context(t, 3), hasServer: true}, + } + + // Setup Namespaces. + for _, v := range members { + createNamespaces(t, cfg, v.context) + } + + // Create the Default Cluster. + commonHelmValues := map[string]string{ + "global.peering.enabled": "true", + + "global.tls.enabled": "true", + "global.tls.httpsOnly": strconv.FormatBool(c.ACLsEnabled), + + "global.enableConsulNamespaces": "true", + + "global.adminPartitions.enabled": "true", + + "global.logLevel": "debug", + + "global.acls.manageSystemACLs": strconv.FormatBool(c.ACLsEnabled), + + "connectInject.enabled": "true", + "connectInject.consulNamespaces.mirroringK8S": "true", + + "meshGateway.enabled": "true", + "meshGateway.replicas": "1", + + "dns.enabled": "true", + } + + defaultPartitionHelmValues := map[string]string{ + "global.datacenter": primaryServerDatacenter, + } + + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + defaultPartitionHelmValues["meshGateway.service.type"] = "NodePort" + defaultPartitionHelmValues["meshGateway.service.nodePort"] = "30200" + defaultPartitionHelmValues["server.exposeService.type"] = "NodePort" + defaultPartitionHelmValues["server.exposeService.nodePort.https"] = "30000" + defaultPartitionHelmValues["server.exposeService.nodePort.grpc"] = "30100" + } + helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) + + releaseName := helpers.RandomName() + members[keyPrimaryServer].helmCluster = consul.NewHelmCluster(t, defaultPartitionHelmValues, members[keyPrimaryServer].context, cfg, releaseName) + members[keyPrimaryServer].helmCluster.Create(t) + + // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. + caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) + + logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) + k8s.CopySecret(t, members[keyPrimaryServer].context, members[keyPartition].context, caCertSecretName) + + // Create Secondary Partition Cluster which will apply the primary datacenter. + partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) + if c.ACLsEnabled { + logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) + k8s.CopySecret(t, members[keyPrimaryServer].context, members[keyPartition].context, partitionToken) + } + + partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) + partitionSvcAddress := k8s.ServiceHost(t, cfg, members[keyPrimaryServer].context, partitionServiceName) + + k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, members[keyPartition].context) + + secondaryPartitionHelmValues := map[string]string{ + "global.enabled": "false", + "global.datacenter": primaryServerDatacenter, + + "global.adminPartitions.name": primaryDatacenterPartition, + + "global.tls.caCert.secretName": caCertSecretName, + "global.tls.caCert.secretKey": "tls.crt", + + "externalServers.enabled": "true", + "externalServers.hosts[0]": partitionSvcAddress, + "externalServers.tlsServerName": fmt.Sprintf("server.%s.consul", primaryServerDatacenter), + "global.server.enabled": "false", + } + + if c.ACLsEnabled { + // Setup partition token and auth method host if ACLs enabled. + secondaryPartitionHelmValues["global.acls.bootstrapToken.secretName"] = partitionToken + secondaryPartitionHelmValues["global.acls.bootstrapToken.secretKey"] = "token" + secondaryPartitionHelmValues["externalServers.k8sAuthMethodHost"] = k8sAuthMethodHost + } + + if cfg.UseKind { + secondaryPartitionHelmValues["externalServers.httpsPort"] = "30000" + secondaryPartitionHelmValues["externalServers.grpcPort"] = "30100" + secondaryPartitionHelmValues["meshGateway.service.type"] = "NodePort" + secondaryPartitionHelmValues["meshGateway.service.nodePort"] = "30200" + } + helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) + + members[keyPartition].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, members[keyPartition].context, cfg, releaseName) + members[keyPartition].helmCluster.Create(t) + + // Create Peer 1 Cluster. + PeerOneHelmValues := map[string]string{ + "global.datacenter": peer1Datacenter, + } + + if cfg.UseKind { + PeerOneHelmValues["server.exposeGossipAndRPCPorts"] = "true" + PeerOneHelmValues["meshGateway.service.type"] = "NodePort" + PeerOneHelmValues["meshGateway.service.nodePort"] = "30100" + } + helpers.MergeMaps(PeerOneHelmValues, commonHelmValues) + + members[keyPeer1].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, members[keyPeer1].context, cfg, releaseName) + members[keyPeer1].helmCluster.Create(t) + + // Create Peer 2 Cluster. + PeerTwoHelmValues := map[string]string{ + "global.datacenter": peer2Datacenter, + } + + if cfg.UseKind { + PeerTwoHelmValues["server.exposeGossipAndRPCPorts"] = "true" + PeerTwoHelmValues["meshGateway.service.type"] = "NodePort" + PeerTwoHelmValues["meshGateway.service.nodePort"] = "30100" + } + helpers.MergeMaps(PeerTwoHelmValues, commonHelmValues) + + members[keyPeer2].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, members[keyPeer2].context, cfg, releaseName) + members[keyPeer2].helmCluster.Create(t) + + // Create a ProxyDefaults resource to configure services to use the mesh + // gateways and set server and client opts. + for k, v := range members { + logger.Logf(t, "applying resources on %s", v.context.KubectlOptions(t).ContextName) + + // Client will use the client namespace. + members[k].clientOpts = &terratestk8s.KubectlOptions{ + ContextName: v.context.KubectlOptions(t).ContextName, + ConfigPath: v.context.KubectlOptions(t).ConfigPath, + Namespace: staticClientNamespace, + } + + // Server will use the server namespace. + members[k].serverOpts = &terratestk8s.KubectlOptions{ + ContextName: v.context.KubectlOptions(t).ContextName, + ConfigPath: v.context.KubectlOptions(t).ConfigPath, + Namespace: staticServerNamespace, + } + + // Sameness Defaults need to be applied first so that the sameness group exists. + applyResources(t, cfg, "../fixtures/bases/mesh-gateway", members[k].context.KubectlOptions(t)) + applyResources(t, cfg, "../fixtures/bases/sameness/default-ns", members[k].context.KubectlOptions(t)) + applyResources(t, cfg, "../fixtures/bases/sameness/override-ns", members[k].serverOpts) + + // Only assign a client if the cluster is running a Consul server. + if v.hasServer { + members[k].client, _ = members[k].helmCluster.SetupConsulClient(t, c.ACLsEnabled) + } + } + + // TODO: Add further setup for peering, right now the rest of this test will only cover Partitions + // Create static server deployments. + logger.Log(t, "creating static-server and static-client deployments") + k8s.DeployKustomize(t, members[keyPrimaryServer].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/default") + k8s.DeployKustomize(t, members[keyPartition].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/partition") + + // Create static client deployments. + k8s.DeployKustomize(t, members[keyPrimaryServer].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-client/default") + k8s.DeployKustomize(t, members[keyPartition].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-client/partition") + + // Verify that both static-server and static-client have been injected and now have 2 containers in server cluster. + // Also get the server IP + for _, labelSelector := range []string{"app=static-server", "app=static-client"} { + podList, err := members[keyPrimaryServer].context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), + metav1.ListOptions{LabelSelector: labelSelector}) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + if labelSelector == "app=static-server" { + ip := &podList.Items[0].Status.PodIP + require.NotNil(t, ip) + logger.Logf(t, "default-static-server-ip: %s", *ip) + members[keyPrimaryServer].staticServerIP = ip + } + + podList, err = members[keyPartition].context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), + metav1.ListOptions{LabelSelector: labelSelector}) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + if labelSelector == "app=static-server" { + ip := &podList.Items[0].Status.PodIP + require.NotNil(t, ip) + logger.Logf(t, "partition-static-server-ip: %s", *ip) + members[keyPartition].staticServerIP = ip + } + } + + logger.Log(t, "creating exported services") + applyResources(t, cfg, "../fixtures/cases/sameness/exported-services/default-partition", members[keyPrimaryServer].context.KubectlOptions(t)) + applyResources(t, cfg, "../fixtures/cases/sameness/exported-services/ap1-partition", members[keyPartition].context.KubectlOptions(t)) + + // Setup DNS. + dnsService, err := members[keyPrimaryServer].context.KubernetesClient(t).CoreV1().Services("default").Get(context.Background(), fmt.Sprintf("%s-%s", releaseName, "consul-dns"), metav1.GetOptions{}) + require.NoError(t, err) + dnsIP := dnsService.Spec.ClusterIP + logger.Logf(t, "dnsIP: %s", dnsIP) + + // Setup Prepared Query. + definition := &api.PreparedQueryDefinition{ + Name: "my-query", + Service: api.ServiceQuery{ + Service: "static-server", + SamenessGroup: "mine", + Namespace: staticServerNamespace, + OnlyPassing: false, + }, + } + resp, _, err := members[keyPrimaryServer].client.PreparedQuery().Create(definition, &api.WriteOptions{}) + require.NoError(t, err) + logger.Logf(t, "PQ ID: %s", resp) + + logger.Log(t, "all infrastructure up and running") + logger.Log(t, "verifying failover scenarios") + + const dnsLookup = "static-server.service.ns2.ns.mine.sg.consul" + const dnsPQLookup = "my-query.query.consul" + + // Verify initial server. + serviceFailoverCheck(t, primaryServerClusterName, members[keyPrimaryServer]) + + // Verify initial dns. + dnsFailoverCheck(t, releaseName, dnsIP, dnsLookup, members[keyPrimaryServer], members[keyPrimaryServer]) + + // Verify initial dns with PQ. + dnsFailoverCheck(t, releaseName, dnsIP, dnsPQLookup, members[keyPrimaryServer], members[keyPrimaryServer]) + + // Scale down static-server on the server, will fail over to partition. + k8s.KubectlScale(t, members[keyPrimaryServer].serverOpts, staticServerDeployment, 0) + + // Verify failover to partition. + serviceFailoverCheck(t, partitionClusterName, members[keyPrimaryServer]) + + // Verify dns failover to partition. + dnsFailoverCheck(t, releaseName, dnsIP, dnsLookup, members[keyPrimaryServer], members[keyPartition]) + + // Verify prepared query failover. + dnsFailoverCheck(t, releaseName, dnsIP, dnsPQLookup, members[keyPrimaryServer], members[keyPartition]) + + logger.Log(t, "tests complete") + }) + } +} + +type member struct { + context environment.TestContext + helmCluster *consul.HelmCluster + client *api.Client + hasServer bool + serverOpts *terratestk8s.KubectlOptions + clientOpts *terratestk8s.KubectlOptions + staticServerIP *string +} + +func createNamespaces(t *testing.T, cfg *config.TestConfig, context environment.TestContext) { + logger.Logf(t, "creating namespaces in %s", context.KubectlOptions(t).ContextName) + k8s.RunKubectl(t, context.KubectlOptions(t), "create", "ns", staticServerNamespace) + k8s.RunKubectl(t, context.KubectlOptions(t), "create", "ns", staticClientNamespace) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.RunKubectl(t, context.KubectlOptions(t), "delete", "ns", staticClientNamespace, staticServerNamespace) + }) +} + +func applyResources(t *testing.T, cfg *config.TestConfig, kustomizeDir string, opts *terratestk8s.KubectlOptions) { + k8s.KubectlApplyK(t, opts, kustomizeDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.KubectlDeleteK(t, opts, kustomizeDir) + }) +} + +// serviceFailoverCheck verifies that the server failed over as expected by checking that curling the `static-server` +// using the `static-client` responds with the expected cluster name. Each static-server responds with a uniquue +// name so that we can verify failover occured as expected. +func serviceFailoverCheck(t *testing.T, expectedClusterName string, server *member) { + retry.Run(t, func(r *retry.R) { + resp, err := k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", + staticClientDeployment, "-c", "static-client", "--", "curl", "localhost:8080") + require.NoError(r, err) + assert.Contains(r, resp, expectedClusterName) + logger.Log(t, resp) + }) +} + +func dnsFailoverCheck(t *testing.T, releaseName string, dnsIP string, dnsQuery string, server, failover *member) { + retry.Run(t, func(r *retry.R) { + logs, err := k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", + staticClientDeployment, "-c", "static-client", "--", "dig", fmt.Sprintf("@%s-consul-dns.default", releaseName), dnsQuery) + require.NoError(r, err) + + // When the `dig` request is successful, a section of its response looks like the following: + // + // ;; ANSWER SECTION: + // static-server.service.mine.sg.ns2.ns.consul. 0 IN A + // + // ;; Query time: 2 msec + // ;; SERVER: #() + // ;; WHEN: Mon Aug 10 15:02:40 UTC 2020 + // ;; MSG SIZE rcvd: 98 + // + // We assert on the existence of the ANSWER SECTION, The consul-server IPs being present in + // the ANSWER SECTION and the DNS IP mentioned in the SERVER: field + + assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", dnsIP)) + assert.Contains(r, logs, "ANSWER SECTION:") + assert.Contains(r, logs, *failover.staticServerIP) + }) +} From 89ee905738aca9e1f68aff47676c3bd60d7dc237 Mon Sep 17 00:00:00 2001 From: Ganesh S Date: Fri, 28 Jul 2023 10:02:15 -0700 Subject: [PATCH 307/592] Added logLevel field for components (#2302) * Added logLevel field for components * Add changelog * Fix tests * Rename 2298.txt to 2302.txt * Address comments * Fix tests * Fix helm tests * Address comments * Add client and server loglevels * Fix bats * Update changelog * Fix bats tests --- .changelog/2302.txt | 13 ++++ .../templates/client-config-configmap.yaml | 6 ++ .../create-federation-secret-job.yaml | 2 +- .../gossip-encryption-autogenerate-job.yaml | 2 +- .../ingress-gateways-deployment.yaml | 4 +- .../templates/mesh-gateway-deployment.yaml | 4 +- .../server-acl-init-cleanup-job.yaml | 2 +- .../consul/templates/server-acl-init-job.yaml | 2 +- .../templates/server-config-configmap.yaml | 3 + .../telemetry-collector-deployment.yaml | 4 +- .../terminating-gateways-deployment.yaml | 4 +- charts/consul/templates/tls-init-job.yaml | 2 +- .../test/unit/client-config-configmap.bats | 26 ++++++++ charts/consul/test/unit/client-daemonset.bats | 6 +- .../unit/create-federation-secret-job.bats | 38 ++++++++++++ .../gossip-encryption-autogenerate-job.bats | 30 +++++++++ .../unit/ingress-gateways-deployment.bats | 61 +++++++++++++++++++ .../test/unit/mesh-gateway-deployment.bats | 61 +++++++++++++++++++ .../unit/server-acl-init-cleanup-job.bats | 28 +++++++++ .../consul/test/unit/server-acl-init-job.bats | 30 +++++++++ .../test/unit/server-config-configmap.bats | 21 +++++++ .../unit/telemetry-collector-deployment.bats | 57 +++++++++++++++++ .../unit/terminating-gateways-deployment.bats | 61 +++++++++++++++++++ charts/consul/test/unit/tls-init-job.bats | 30 +++++++++ charts/consul/values.yaml | 39 ++++++++++++ 25 files changed, 520 insertions(+), 16 deletions(-) create mode 100644 .changelog/2302.txt diff --git a/.changelog/2302.txt b/.changelog/2302.txt new file mode 100644 index 0000000000..7bf7e6b0f6 --- /dev/null +++ b/.changelog/2302.txt @@ -0,0 +1,13 @@ +```release-note:improvement +Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields +1. `global.acls.logLevel` +2. `global.tls.logLevel` +3. `global.federation.logLevel` +4. `global.gossipEncryption.logLevel` +5. `server.logLevel` +6. `client.logLevel` +7. `meshGateway.logLevel` +8. `ingressGateways.logLevel` +9. `terminatingGateways.logLevel` +10. `telemetryCollector.logLevel` +``` diff --git a/charts/consul/templates/client-config-configmap.yaml b/charts/consul/templates/client-config-configmap.yaml index f9650a100b..d91a4d21bf 100644 --- a/charts/consul/templates/client-config-configmap.yaml +++ b/charts/consul/templates/client-config-configmap.yaml @@ -19,6 +19,12 @@ data: "auto_reload_config": true {{- end }} } + log-level.json: |- + { + {{- if .Values.client.logLevel }} + "log_level": "{{ .Values.client.logLevel | upper }}" + {{- end }} + } extra-from-values.json: |- {{ tpl .Values.client.extraConfig . | trimAll "\"" | indent 4 }} central-config.json: |- diff --git a/charts/consul/templates/create-federation-secret-job.yaml b/charts/consul/templates/create-federation-secret-job.yaml index 4f83a1f82a..bc3e0a988b 100644 --- a/charts/consul/templates/create-federation-secret-job.yaml +++ b/charts/consul/templates/create-federation-secret-job.yaml @@ -119,7 +119,7 @@ spec: - "-ec" - | consul-k8s-control-plane create-federation-secret \ - -log-level={{ .Values.global.logLevel }} \ + -log-level={{ default .Values.global.logLevel .Values.global.federation.logLevel }} \ -log-json={{ .Values.global.logJSON }} \ {{- if (or .Values.global.gossipEncryption.autoGenerate (and .Values.global.gossipEncryption.secretName .Values.global.gossipEncryption.secretKey)) }} -gossip-key-file=/consul/gossip/gossip.key \ diff --git a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml index 240bfe3f9c..02fb3ea168 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml @@ -57,7 +57,7 @@ spec: -namespace={{ .Release.Namespace }} \ -secret-name={{ template "consul.fullname" . }}-gossip-encryption-key \ -secret-key="key" \ - -log-level={{ .Values.global.logLevel }} \ + -log-level={{ default .Values.global.logLevel .Values.global.gossipEncryption.logLevel }} \ -log-json={{ .Values.global.logJSON }} resources: requests: diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index 4f72031855..328c06ee3e 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -211,7 +211,7 @@ spec: -gateway-kind="ingress-gateway" \ -proxy-id-file=/consul/service/proxy-id \ -service-name={{ template "consul.fullname" $root }}-{{ .name }} \ - -log-level={{ default $root.Values.global.logLevel }} \ + -log-level={{ default $root.Values.global.logLevel $root.Values.ingressGateways.logLevel }} \ -log-json={{ $root.Values.global.logJSON }} volumeMounts: - name: consul-service @@ -319,7 +319,7 @@ spec: {{- if $root.Values.global.adminPartitions.enabled }} - -service-partition={{ $root.Values.global.adminPartitions.name }} {{- end }} - - -log-level={{ default $root.Values.global.logLevel }} + - -log-level={{ default $root.Values.global.logLevel $root.Values.ingressGateways.logLevel }} - -log-json={{ $root.Values.global.logJSON }} {{- if (and $root.Values.global.metrics.enabled $root.Values.global.metrics.enableGatewayMetrics) }} - -telemetry-prom-scrape-path=/metrics diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index 449d6ae492..1936138db3 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -161,7 +161,7 @@ spec: -gateway-kind="mesh-gateway" \ -proxy-id-file=/consul/service/proxy-id \ -service-name={{ .Values.meshGateway.consulServiceName }} \ - -log-level={{ default .Values.global.logLevel }} \ + -log-level={{ default .Values.global.logLevel .Values.meshGateway.logLevel }} \ -log-json={{ .Values.global.logJSON }} volumeMounts: - name: consul-service @@ -267,7 +267,7 @@ spec: {{- if .Values.global.adminPartitions.enabled }} - -service-partition={{ .Values.global.adminPartitions.name }} {{- end }} - - -log-level={{ default .Values.global.logLevel }} + - -log-level={{ default .Values.global.logLevel .Values.meshGateway.logLevel }} - -log-json={{ .Values.global.logJSON }} {{- if (and .Values.global.metrics.enabled .Values.global.metrics.enableGatewayMetrics) }} - -telemetry-prom-scrape-path=/metrics diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index c9f6763bd8..39754d6c6f 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -67,7 +67,7 @@ spec: - consul-k8s-control-plane args: - delete-completed-job - - -log-level={{ .Values.global.logLevel }} + - -log-level={{ default .Values.global.logLevel .Values.global.acls.logLevel }} - -log-json={{ .Values.global.logJSON }} - -k8s-namespace={{ .Release.Namespace }} - {{ template "consul.fullname" . }}-server-acl-init diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index c3d4a710e8..e8a06cf7aa 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -171,7 +171,7 @@ spec: CONSUL_FULLNAME="{{template "consul.fullname" . }}" consul-k8s-control-plane server-acl-init \ - -log-level={{ .Values.global.logLevel }} \ + -log-level={{ default .Values.global.logLevel .Values.global.acls.logLevel}} \ -log-json={{ .Values.global.logJSON }} \ -resource-prefix=${CONSUL_FULLNAME} \ -k8s-namespace={{ .Release.Namespace }} \ diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 7e3d251001..6c102f0ae3 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -27,6 +27,9 @@ data: }, "datacenter": "{{ .Values.global.datacenter }}", "data_dir": "/consul/data", + {{- if .Values.server.logLevel }} + "log_level": "{{ .Values.server.logLevel | upper }}", + {{- end }} "domain": "{{ .Values.global.domain }}", "limits": { "request_limits": { diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index b729273dd8..4b5bb6df7b 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -115,7 +115,7 @@ spec: - -ec - |- consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ - -log-level={{ default .Values.global.logLevel }} \ + -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} \ -log-json={{ .Values.global.logJSON }} \ -service-account-name="consul-telemetry-collector" \ -service-name="" \ @@ -303,7 +303,7 @@ spec: {{- if .Values.global.metrics.enabled }} - -telemetry-prom-scrape-path=/metrics {{- end }} - - -log-level={{ default .Values.global.logLevel }} + - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} - -log-json={{ .Values.global.logJSON }} - -envoy-concurrency=2 {{- if and .Values.externalServers.enabled .Values.externalServers.skipServerWatch }} diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index 2f2cb9a921..fdf2c17d05 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -196,7 +196,7 @@ spec: -gateway-kind="terminating-gateway" \ -proxy-id-file=/consul/service/proxy-id \ -service-name={{ .name }} \ - -log-level={{ default $root.Values.global.logLevel }} \ + -log-level={{ default $root.Values.global.logLevel $root.Values.terminatingGateways.logLevel }} \ -log-json={{ $root.Values.global.logJSON }} volumeMounts: - name: consul-service @@ -300,7 +300,7 @@ spec: {{- if $root.Values.global.adminPartitions.enabled }} - -service-partition={{ $root.Values.global.adminPartitions.name }} {{- end }} - - -log-level={{ default $root.Values.global.logLevel }} + - -log-level={{ default $root.Values.global.logLevel $root.Values.terminatingGateways.logLevel }} - -log-json={{ $root.Values.global.logJSON }} {{- if (and $root.Values.global.metrics.enabled $root.Values.global.metrics.enableGatewayMetrics) }} - -telemetry-prom-scrape-path=/metrics diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index 12d3acbad8..47651fe14b 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -80,7 +80,7 @@ spec: # and use * at the start of the dns name when setting -additional-dnsname. set -o noglob consul-k8s-control-plane tls-init \ - -log-level={{ .Values.global.logLevel }} \ + -log-level={{ default .Values.global.logLevel .Values.global.tls.logLevel }} \ -log-json={{ .Values.global.logJSON }} \ -domain={{ .Values.global.domain }} \ -days=730 \ diff --git a/charts/consul/test/unit/client-config-configmap.bats b/charts/consul/test/unit/client-config-configmap.bats index 5fc4a186d9..1f1443a156 100755 --- a/charts/consul/test/unit/client-config-configmap.bats +++ b/charts/consul/test/unit/client-config-configmap.bats @@ -95,3 +95,29 @@ load _helpers [ "${actual}" = null ] } + +#-------------------------------------------------------------------- +# logLevel + +@test "client/ConfigMap: client.logLevel is empty" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-config-configmap.yaml \ + --set 'client.enabled=true' \ + . | tee /dev/stderr | + yq -r '.data["log-level.json"]' | jq -r .log_level | tee /dev/stderr) + + [ "${actual}" = "null" ] +} + +@test "client/ConfigMap: client.logLevel is non empty" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-config-configmap.yaml \ + --set 'client.enabled=true' \ + --set 'client.logLevel=DEBUG' \ + . | tee /dev/stderr | + yq -r '.data["log-level.json"]' | jq -r .log_level | tee /dev/stderr) + + [ "${actual}" = "DEBUG" ] +} diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index 6e7a030cb1..d512ad8ab2 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -621,7 +621,7 @@ load _helpers --set 'client.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = f9be2829fed80a127e3752e10be32f29c2f9ca0ea548abcf3d4fc2c985cb7201 ] + [ "${actual}" = 4fa9ddc3abc4c79eafccb19e5beef80006b7c9736b867d8873554ca03f42a6b3 ] } @test "client/DaemonSet: config-checksum annotation changes when extraConfig is provided" { @@ -632,7 +632,7 @@ load _helpers --set 'client.extraConfig="{\"hello\": \"world\"}"' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = e9fb5f0b4ff4e36a89e8ca2dc1aed2072306e0dd6d4cc60b3edf155cf8dbe2e9 ] + [ "${actual}" = 42b99932385e7a0580b134fe36a9bda405aab2e375593326677b9838708f0796 ] } @test "client/DaemonSet: config-checksum annotation changes when connectInject.enabled=true" { @@ -643,7 +643,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = f9be2829fed80a127e3752e10be32f29c2f9ca0ea548abcf3d4fc2c985cb7201 ] + [ "${actual}" = 4fa9ddc3abc4c79eafccb19e5beef80006b7c9736b867d8873554ca03f42a6b3 ] } #-------------------------------------------------------------------- diff --git a/charts/consul/test/unit/create-federation-secret-job.bats b/charts/consul/test/unit/create-federation-secret-job.bats index e528f28f0e..872cf2e36c 100644 --- a/charts/consul/test/unit/create-federation-secret-job.bats +++ b/charts/consul/test/unit/create-federation-secret-job.bats @@ -418,3 +418,41 @@ load _helpers [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# logLevel + +@test "createFederationSecret/Job: logLevel is not set by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/create-federation-secret-job.yaml \ + --set 'global.federation.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.federation.createFederationSecret=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 "createFederationSecret/Job: override the global.logLevel flag with global.federation.logLevel" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/create-federation-secret-job.yaml \ + --set 'global.federation.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.federation.createFederationSecret=true' \ + --set 'global.federation.logLevel=debug' \ + . | 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" ] +} diff --git a/charts/consul/test/unit/gossip-encryption-autogenerate-job.bats b/charts/consul/test/unit/gossip-encryption-autogenerate-job.bats index 662b523bc0..7520696182 100644 --- a/charts/consul/test/unit/gossip-encryption-autogenerate-job.bats +++ b/charts/consul/test/unit/gossip-encryption-autogenerate-job.bats @@ -105,3 +105,33 @@ load _helpers [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# logLevel + +@test "gossipEncryptionAutogenerate/Job: uses the global.logLevel flag by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/gossip-encryption-autogenerate-job.yaml \ + --set 'global.gossipEncryption.autoGenerate=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 "gossipEncryptionAutogenerate/Job: overrides the global.logLevel flag when global.gossipEncryption.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/gossip-encryption-autogenerate-job.yaml \ + --set 'global.gossipEncryption.autoGenerate=true' \ + --set 'global.gossipEncryption.logLevel=debug' \ + . | 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" ] +} diff --git a/charts/consul/test/unit/ingress-gateways-deployment.bats b/charts/consul/test/unit/ingress-gateways-deployment.bats index 8ed76be13a..e8390278a8 100644 --- a/charts/consul/test/unit/ingress-gateways-deployment.bats +++ b/charts/consul/test/unit/ingress-gateways-deployment.bats @@ -1504,3 +1504,64 @@ key2: value2' \ [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# logLevel + +@test "ingressGateways/Deployment: use global.logLevel by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/ingress-gateways-deployment.yaml \ + --set 'ingressGateways.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "ingressGateways/Deployment: override global.logLevel when ingressGateways.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/ingress-gateways-deployment.yaml \ + --set 'ingressGateways.enabled=true' \ + --set 'ingressGateways.logLevel=warn' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "ingressGateways/Deployment: use global.logLevel by default for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/ingress-gateways-deployment.yaml \ + --set 'ingressGateways.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "ingressGateways/Deployment: override global.logLevel when ingressGateways.logLevel is set for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/ingress-gateways-deployment.yaml \ + --set 'ingressGateways.enabled=true' \ + --set 'ingressGateways.logLevel=trace' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=trace"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/mesh-gateway-deployment.bats b/charts/consul/test/unit/mesh-gateway-deployment.bats index 588b026d40..d58def05da 100755 --- a/charts/consul/test/unit/mesh-gateway-deployment.bats +++ b/charts/consul/test/unit/mesh-gateway-deployment.bats @@ -1644,3 +1644,64 @@ key2: value2' \ [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# logLevel + +@test "meshGateway/Deployment: use global.logLevel by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "meshGateway/Deployment: override global.logLevel when meshGateway.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'meshGateway.enabled=true' \ + --set 'meshGateway.logLevel=warn' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "meshGateway/Deployment: use global.logLevel by default for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "meshGateway/Deployment: override global.logLevel when meshGateway.logLevel is set for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'meshGateway.enabled=true' \ + --set 'meshGateway.logLevel=warn' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/server-acl-init-cleanup-job.bats b/charts/consul/test/unit/server-acl-init-cleanup-job.bats index c886b2ec51..8743ea4a8d 100644 --- a/charts/consul/test/unit/server-acl-init-cleanup-job.bats +++ b/charts/consul/test/unit/server-acl-init-cleanup-job.bats @@ -161,6 +161,34 @@ load _helpers } #-------------------------------------------------------------------- +# logLevel + +@test "serverACLInitCleanup/Job: use global.logLevel by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "serverACLInitCleanup/Job: override global.logLevel when global.acls.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/server-acl-init-cleanup-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.logLevel=debug' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} # resources @test "serverACLInitCleanup/Job: resources defined by default" { diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 17c3e63935..1dc55a9551 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -2219,6 +2219,36 @@ load _helpers [ "${actualTemplateBaz}" = "qux" ] } +#-------------------------------------------------------------------- +# logLevel + +@test "serverACLInit/Job: use global.logLevel by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.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 "serverACLInit/Job: override global.logLevel when global.acls.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.logLevel=debug' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # resources diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index d55c10dd3a..643caeb0a1 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -1196,4 +1196,25 @@ load _helpers local actual=$(echo $object | jq -r .audit.sink.MySink3.type | tee /dev/stderr) [ "${actual}" = "file" ] +} + +@test "server/ConfigMap: server.logLevel is empty" { + cd `chart_dir` + local configmap=$(helm template \ + -s templates/server-config-configmap.yaml \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .log_level | tee /dev/stderr) + + [ "${configmap}" = "null" ] +} + +@test "server/ConfigMap: server.logLevel is non empty" { + cd `chart_dir` + local configmap=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.logLevel=debug' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .log_level | tee /dev/stderr) + + [ "${configmap}" = "DEBUG" ] } \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index 705447621e..7809039e11 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -1074,3 +1074,60 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ yq -r 'map(select(.name == "foo")) | .[0].value' | tee /dev/stderr) [ "${actual}" = "bar" ] } + +#-------------------------------------------------------------------- +# logLevel + +@test "telemetryCollector/Deployment: use global.logLevel by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment: override global.logLevel when telemetryCollector.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.logLevel=warn' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment: use global.logLevel by default for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment: override global.logLevel when telemetryCollector.logLevel is set for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.logLevel=debug' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/terminating-gateways-deployment.bats b/charts/consul/test/unit/terminating-gateways-deployment.bats index 523138a351..1dc3befbdf 100644 --- a/charts/consul/test/unit/terminating-gateways-deployment.bats +++ b/charts/consul/test/unit/terminating-gateways-deployment.bats @@ -1504,3 +1504,64 @@ key2: value2' \ [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# logLevel + +@test "terminatingGateways/Deployment: use global.logLevel by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/terminating-gateways-deployment.yaml \ + --set 'terminatingGateways.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "terminatingGateways/Deployment: override global.logLevel when terminatingGateways.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/terminating-gateways-deployment.yaml \ + --set 'terminatingGateways.enabled=true' \ + --set 'terminatingGateways.logLevel=debug' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "terminatingGateways/Deployment: use global.logLevel by default for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/terminating-gateways-deployment.yaml \ + --set 'terminatingGateways.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "terminatingGateways/Deployment: override global.logLevel when terminatingGateways.logLevel is set for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/terminating-gateways-deployment.yaml \ + --set 'terminatingGateways.enabled=true' \ + --set 'terminatingGateways.logLevel=debug' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/charts/consul/test/unit/tls-init-job.bats b/charts/consul/test/unit/tls-init-job.bats index bf1f84a0a6..f71edc43d5 100644 --- a/charts/consul/test/unit/tls-init-job.bats +++ b/charts/consul/test/unit/tls-init-job.bats @@ -208,6 +208,36 @@ load _helpers [ "${actualTemplateBaz}" = "qux" ] } +#-------------------------------------------------------------------- +# logLevel + +@test "tlsInit/Job: use global.logLevel by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.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 "tlsInit/Job: override global.logLevel when global.tls.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/tls-init-job.yaml \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.logLevel=error' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=error"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # server.containerSecurityContext.tlsInit diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 81065d4bb2..d373a240f0 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -289,6 +289,9 @@ global: # The key within the Kubernetes secret or Vault secret key that holds the gossip # encryption key. secretKey: "" + # Override global log verbosity level for gossip-encryption-autogenerate-job pods. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" # A list of addresses of upstream DNS servers that are used to recursively resolve DNS queries. # These values are given as `-recursor` flags to Consul servers and clients. @@ -307,6 +310,10 @@ global: # This setting is required for [Cluster Peering](https://developer.hashicorp.com/consul/docs/connect/cluster-peering/k8s). enabled: false + # Override global log verbosity level. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # If true, turns on the auto-encrypt feature on clients and servers. # It also switches consul-k8s-control-plane components to retrieve the CA from the servers # via the API. Requires Consul 1.7.1+. @@ -406,6 +413,10 @@ global: # This requires Consul >= 1.4. manageSystemACLs: false + # Override global log verbosity level. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # A Kubernetes or Vault secret containing the bootstrap token to use for creating policies and # tokens for all Consul and consul-k8s-control-plane components. If `secretName` and `secretKey` # are unset, a default secret name and secret key are used. If the secret is populated, then @@ -578,6 +589,10 @@ global: # @type: string k8sAuthMethodHost: null + # Override global log verbosity level for the create-federation-secret-job pods. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # Configures metrics for Consul service mesh metrics: # Configures the Helm chart’s components @@ -731,6 +746,10 @@ server: # @type: boolean enabled: "-" + # Override global log verbosity level. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # The name of the Docker image (including any tag) for the containers running # Consul server agents. # @type: string @@ -1368,6 +1387,10 @@ client: # @type: boolean enabled: false + # Override global log verbosity level. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # The name of the Docker image (including any tag) for the containers # running Consul client agents. # @type: string @@ -2643,6 +2666,10 @@ meshGateway: # Requirements: consul 1.6.0+ if using `global.acls.manageSystemACLs``. enabled: false + # Override global log verbosity level for mesh-gateway-deployment pods. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # Number of replicas for the Deployment. replicas: 1 @@ -2855,6 +2882,10 @@ ingressGateways: # Enable ingress gateway deployment. Requires `connectInject.enabled=true`. enabled: false + # Override global log verbosity level for ingress-gateways-deployment pods. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # Defaults sets default values for all gateway fields. With the exception # of annotations, defining any of these values in the `gateways` list # will override the default values provided here. Annotations will @@ -3021,6 +3052,10 @@ terminatingGateways: # Enable terminating gateway deployment. Requires `connectInject.enabled=true`. enabled: false + # Override global log verbosity level. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # Defaults sets default values for all gateway fields. With the exception # of annotations, defining any of these values in the `gateways` list # will override the default values provided here. Annotations will @@ -3359,6 +3394,10 @@ telemetryCollector: # @type: boolean enabled: false + # Override global log verbosity level. One of "trace", "debug", "info", "warn", or "error". + # @type: string + logLevel: "" + # The name of the Docker image (including any tag) for the containers running # the consul-telemetry-collector # @type: string From 3e1f79912fa72634b20ba4f31cdc1bf9c9e92803 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 28 Jul 2023 13:46:07 -0400 Subject: [PATCH 308/592] Add missing tsccr entries (#2682) --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f79369b440..414c875b26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -193,7 +193,7 @@ jobs: - name: Test rpm package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" + uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 with: image: registry.access.redhat.com/ubi9/ubi:latest options: -v ${{ github.workspace }}:/work @@ -218,7 +218,7 @@ jobs: - name: Test debian package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" + uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 with: image: ubuntu:latest options: -v ${{ github.workspace }}:/work From 63567cb3eee78eb66a1589c53d919430fb93a027 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Fri, 28 Jul 2023 14:57:33 -0400 Subject: [PATCH 309/592] Use controller-gen 0.8.0 for CRDs (#2684) - Add missing license headers. --- Makefile | 2 +- .../fixtures/bases/openshift/network-attachment.yaml | 3 +++ .../fixtures/bases/sameness/default-ns/sameness.yaml | 3 +++ .../bases/sameness/override-ns/intentions.yaml | 3 +++ .../override-ns/payment-service-resolver.yaml | 3 +++ .../bases/sameness/override-ns/service-defaults.yaml | 3 +++ .../tests/fixtures/bases/sameness/peering/mesh.yaml | 3 +++ acceptance/tests/sameness/sameness_test.go | 3 +++ .../templates/crd-controlplanerequestlimits.yaml | 11 +++++++++-- charts/consul/templates/crd-exportedservices.yaml | 11 +++++++++-- charts/consul/templates/crd-gatewayclassconfigs.yaml | 11 +++++++++-- charts/consul/templates/crd-ingressgateways.yaml | 11 +++++++++-- charts/consul/templates/crd-jwtproviders.yaml | 11 +++++++++-- charts/consul/templates/crd-meshes.yaml | 11 +++++++++-- charts/consul/templates/crd-meshservices.yaml | 11 +++++++++-- charts/consul/templates/crd-peeringacceptors.yaml | 11 +++++++++-- charts/consul/templates/crd-peeringdialers.yaml | 11 +++++++++-- charts/consul/templates/crd-proxydefaults.yaml | 11 +++++++++-- charts/consul/templates/crd-referencegrants.yaml | 6 +++--- charts/consul/templates/crd-samenessgroups.yaml | 11 +++++++++-- charts/consul/templates/crd-servicedefaults.yaml | 11 +++++++++-- charts/consul/templates/crd-serviceintentions.yaml | 11 +++++++++-- charts/consul/templates/crd-serviceresolvers.yaml | 11 +++++++++-- charts/consul/templates/crd-servicerouters.yaml | 11 +++++++++-- charts/consul/templates/crd-servicesplitters.yaml | 11 +++++++++-- charts/consul/templates/crd-terminatinggateways.yaml | 11 +++++++++-- ...onsul.hashicorp.com_controlplanerequestlimits.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_exportedservices.yaml | 9 ++++++++- .../consul.hashicorp.com_gatewayclassconfigs.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_ingressgateways.yaml | 9 ++++++++- .../crd/bases/consul.hashicorp.com_jwtproviders.yaml | 9 ++++++++- .../config/crd/bases/consul.hashicorp.com_meshes.yaml | 9 ++++++++- .../crd/bases/consul.hashicorp.com_meshservices.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_peeringacceptors.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_peeringdialers.yaml | 9 ++++++++- .../crd/bases/consul.hashicorp.com_proxydefaults.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_samenessgroups.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_servicedefaults.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_serviceintentions.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_serviceresolvers.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_servicerouters.yaml | 9 ++++++++- .../bases/consul.hashicorp.com_servicesplitters.yaml | 9 ++++++++- .../consul.hashicorp.com_terminatinggateways.yaml | 9 ++++++++- control-plane/config/rbac/role.yaml | 4 ++++ control-plane/config/webhook/manifests.yaml | 4 ++++ 45 files changed, 322 insertions(+), 55 deletions(-) diff --git a/Makefile b/Makefile index e2c39de2ea..a141fa22cd 100644 --- a/Makefile +++ b/Makefile @@ -172,7 +172,7 @@ ifeq (, $(shell which controller-gen)) CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ cd $$CONTROLLER_GEN_TMP_DIR ;\ go mod init tmp ;\ - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.12.0 ;\ + go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 ;\ rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ } CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen diff --git a/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml b/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml index 4b3f7948ee..c2f36c5e1a 100644 --- a/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml +++ b/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: diff --git a/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml index 0eb7d9e008..4d27ed72ae 100644 --- a/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml +++ b/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: SamenessGroup metadata: diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml index 425b9fe21d..ae075c85e4 100644 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceIntentions metadata: diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml index b2b6b68c3d..4257294c6b 100644 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceResolver metadata: diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml index f88d143728..87f6a71f32 100644 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml b/acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml index de84382d3e..2fb6a04bb6 100644 --- a/acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml +++ b/acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: Mesh metadata: diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index a7a926cd42..3971ccf27a 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package sameness import ( diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml index 67ff258eb8..2b0c45a621 100644 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: controlplanerequestlimits.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ControlPlaneRequestLimit @@ -193,4 +194,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 8581ac4e88..591500cb12 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: exportedservices.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ExportedServices @@ -137,4 +138,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 7060757b23..4ab6570e31 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: gatewayclassconfigs.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: GatewayClassConfig @@ -141,4 +142,10 @@ spec: type: object served: true storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index eff7ef61a9..a01fafd8dd 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: ingressgateways.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: IngressGateway @@ -367,4 +368,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index fa87f37489..8a51d16b68 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: jwtproviders.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: JWTProvider @@ -255,4 +256,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index f2549b5111..0710d41280 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshes.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: Mesh @@ -205,4 +206,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml index aa808113a2..df8f673bdc 100644 --- a/charts/consul/templates/crd-meshservices.yaml +++ b/charts/consul/templates/crd-meshservices.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshservices.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: MeshService @@ -54,4 +55,10 @@ spec: type: object served: true storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index 40f7f1d4d6..e06e830f04 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringacceptors.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: PeeringAcceptor @@ -144,4 +145,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index bfe4778d0c..e24401e761 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringdialers.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: PeeringDialer @@ -144,4 +145,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index a224effc12..362672c1c1 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: proxydefaults.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ProxyDefaults @@ -253,4 +254,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-referencegrants.yaml b/charts/consul/templates/crd-referencegrants.yaml index d50211291d..6ae177d987 100644 --- a/charts/consul/templates/crd-referencegrants.yaml +++ b/charts/consul/templates/crd-referencegrants.yaml @@ -7,15 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null name: referencegrants.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index 7cc3b71ae1..60beb5662c 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: samenessgroups.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: SamenessGroup @@ -127,4 +128,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index e295732bfa..870f5ad86c 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicedefaults.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceDefaults @@ -493,4 +494,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 5f849f65ba..c4d2b5f20d 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceintentions.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceIntentions @@ -309,4 +310,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index a18cc94de4..eb5643fe49 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceresolvers.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceResolver @@ -346,4 +347,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index c5ba99466c..f28da9e7c1 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicerouters.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceRouter @@ -310,4 +311,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index abe3ac85cc..a2af050c3d 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicesplitters.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: ServiceSplitter @@ -184,4 +185,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index cd58d1679c..583c218be8 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -4,15 +4,16 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: terminatinggateways.consul.hashicorp.com -spec: labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd +spec: group: consul.hashicorp.com names: kind: TerminatingGateway @@ -135,4 +136,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml index 4d1d808428..11da54e9ac 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -189,3 +190,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index dac72f3646..0b6b969856 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -133,3 +134,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml index 44eff52492..e60e4a1cfa 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -137,3 +138,9 @@ spec: type: object served: true storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index e9cf081721..fd8ebc86ff 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -363,3 +364,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml index 7506cc57dc..2e8ac24330 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -251,3 +252,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] 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 16dd398f99..adbb12bba6 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -201,3 +202,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml index 125883bdc5..04f8f493e7 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -50,3 +51,9 @@ spec: type: object served: true storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index 894228a218..50df179f04 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -140,3 +141,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index 51c3e38319..01e4363f14 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -140,3 +141,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 1be3b37703..7084980bf0 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -249,3 +250,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml index 259ca7b910..c71a211f63 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -123,3 +124,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 83503f11f3..5a2c7a58fd 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -489,3 +490,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] 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 9553c73450..a4efd6e958 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -305,3 +306,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index b83a859dc4..0146eca982 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -342,3 +343,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 04590cc007..31f5ee2924 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -306,3 +307,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index 3a47472ba7..aa2b592c94 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -180,3 +181,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index acf61cde4c..b465cd9494 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -6,7 +6,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -131,3 +132,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 74328a8ae3..7f90780e02 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -1,7 +1,11 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: + creationTimestamp: null name: manager-role rules: - apiGroups: diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index a515888527..0861f9253a 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -1,7 +1,11 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: + creationTimestamp: null name: mutating-webhook-configuration webhooks: - admissionReviewVersions: From 3cb0cce4f21dcc6111355b092ba1c22984576243 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Fri, 28 Jul 2023 16:08:17 -0400 Subject: [PATCH 310/592] Fix ingress (#2687) --- .changelog/2687.txt | 3 +++ charts/consul/templates/ui-ingress.yaml | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changelog/2687.txt diff --git a/.changelog/2687.txt b/.changelog/2687.txt new file mode 100644 index 0000000000..5fa4a92b4d --- /dev/null +++ b/.changelog/2687.txt @@ -0,0 +1,3 @@ +```release-note:bug +helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. +``` \ No newline at end of file diff --git a/charts/consul/templates/ui-ingress.yaml b/charts/consul/templates/ui-ingress.yaml index 0414a7cc2d..f8c7f92a77 100644 --- a/charts/consul/templates/ui-ingress.yaml +++ b/charts/consul/templates/ui-ingress.yaml @@ -25,9 +25,11 @@ metadata: {{ tpl .Values.ui.ingress.annotations . | nindent 4 | trim }} {{- end }} spec: + {{- if ne .Values.ui.ingress.ingressClassName "" }} ingressClassName: {{ .Values.ui.ingress.ingressClassName }} + {{- end }} rules: - {{ $global := .Values.global }} + {{- $global := .Values.global }} {{- if or ( gt .Capabilities.KubeVersion.Major "1" ) ( ge .Capabilities.KubeVersion.Minor "19" ) }} {{- range .Values.ui.ingress.hosts }} - host: {{ .host | quote }} From 6835b1ef5367752d245981b2af15873f50a3ecae Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Fri, 28 Jul 2023 16:36:50 -0400 Subject: [PATCH 311/592] [NET-4865] Bump golang.org/x/net to 0.12.0 in cni (#2668) * Bump golang.org/x/net to 0.12.0 in cni This was missed in 5b57e6340dff44157cb7a984ac7220e47849dfb9 as part of a general upgrade of that dependency. * Bump server-connection-manager to v0.1.3 Tidying up following CVE dependency bumps, leading to a new release of this library. --- control-plane/cni/go.mod | 8 ++++---- control-plane/cni/go.sum | 16 ++++++++-------- control-plane/go.mod | 2 +- control-plane/go.sum | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index b594015392..fe67475524 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -30,11 +30,11 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index 845baf6231..f95d4d991a 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -287,8 +287,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ 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.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -339,21 +339,21 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/control-plane/go.mod b/control-plane/go.mod index 9d184840cb..8469502795 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d - github.com/hashicorp/consul-server-connection-manager v0.1.2 + github.com/hashicorp/consul-server-connection-manager v0.1.3 github.com/hashicorp/consul/api v1.22.0-rc1 github.com/hashicorp/consul/sdk v0.14.0-rc1 github.com/hashicorp/go-bexpr v0.1.11 diff --git a/control-plane/go.sum b/control-plane/go.sum index fa88dab62a..39e89efec8 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -262,8 +262,8 @@ github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEo github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d h1:RJ1MZ8JKnfgKQ1kR3IBQAMpOpzXrdseZAYN/QR//MFM= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= -github.com/hashicorp/consul-server-connection-manager v0.1.2 h1:tNVQHUPuMbd+cMdD8kd+qkZUYpmLmrHMAV/49f4L53I= -github.com/hashicorp/consul-server-connection-manager v0.1.2/go.mod h1:NzQoVi1KcxGI2SangsDue8+ZPuXZWs+6BKAKrDNyg+w= +github.com/hashicorp/consul-server-connection-manager v0.1.3 h1:fxsZ15XBNNWhV26yBVdCcnxHwSRgf9wqHGS2ZVCQIhc= +github.com/hashicorp/consul-server-connection-manager v0.1.3/go.mod h1:Md2IGKaFJ4ek9GUA0pW1S2R60wpquMOUs27GiD9kZd0= github.com/hashicorp/consul/api v1.22.0-rc1 h1:ePmGqndeMgaI38KUbSA/CqTzeEAIogXyWnfNJzglo70= github.com/hashicorp/consul/api v1.22.0-rc1/go.mod h1:wtduXtbAqSGtBdi3tyA5SSAYGAG51rBejV9SEUBciMY= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= From da99ce404f3f35f87dd51ace4fb11f68097fdfc8 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 31 Jul 2023 09:55:04 -0400 Subject: [PATCH 312/592] Fix default Ent image tag in acceptance tests (#2683) * Fix default Ent image tag in acceptance tests Rather than hard-coding the Docker repository and parsing the non-Ent image tag for a version, simply replace the image name and retain other coordinates. This is consistent with our tagging scheme introduced in https://github.com/hashicorp/consul/pull/13541 and will allow for using `hashicorppreview` images seamlessly regardless of whether OSS or Ent is being tested. * Add make target for loading images in kind Complement other multi-cluster make targets by supporting image loading across kind clusters. --- Makefile | 7 ++++++ acceptance/framework/config/config.go | 21 ++++++---------- acceptance/framework/config/config_test.go | 29 ++++++++++++++-------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index a141fa22cd..e9a02a01b3 100644 --- a/Makefile +++ b/Makefile @@ -151,6 +151,13 @@ kind: kind-delete kind create cluster --name dc3 --image $(KIND_NODE_IMAGE) kind create cluster --name dc4 --image $(KIND_NODE_IMAGE) +# Helper target for loading local dev images (run with `DEV_IMAGE=...` to load non-k8s images) +kind-load: + kind load docker-image --name dc1 $(DEV_IMAGE) + kind load docker-image --name dc2 $(DEV_IMAGE) + kind load docker-image --name dc3 $(DEV_IMAGE) + kind load docker-image --name dc4 $(DEV_IMAGE) + # ===========> Shared Targets help: ## Show targets and their descriptions. diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index ee07df63fd..a638732c37 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -217,21 +217,16 @@ func (t *TestConfig) entImage() (string, error) { } // Otherwise, assume that we have an image tag with a version in it. - consulImageSplits := strings.Split(v.Global.Image, ":") - if len(consulImageSplits) != 2 { - return "", fmt.Errorf("could not determine consul version from global.image: %s", v.Global.Image) - } - consulImageVersion := consulImageSplits[1] - - var preRelease string - // Handle versions like 1.9.0-rc1. - if strings.Contains(consulImageVersion, "-") { - split := strings.Split(consulImageVersion, "-") - consulImageVersion = split[0] - preRelease = fmt.Sprintf("-%s", split[1]) + // Use the same Docker repository and tagging scheme, but replace 'consul' with 'consul-enterprise'. + imageTag := strings.Replace(v.Global.Image, "/consul:", "/consul-enterprise:", 1) + + // We currently add an '-ent' suffix to release versions of enterprise images (nightly previews + // do not include this suffix). + if strings.HasPrefix(imageTag, "hashicorp/consul-enterprise:") { + imageTag = fmt.Sprintf("%s-ent", imageTag) } - return fmt.Sprintf("hashicorp/consul-enterprise:%s%s-ent", consulImageVersion, preRelease), nil + return imageTag, nil } func (c *TestConfig) SkipWhenOpenshiftAndCNI(t *testing.T) { diff --git a/acceptance/framework/config/config_test.go b/acceptance/framework/config/config_test.go index df981e26fa..4d432da3b0 100644 --- a/acceptance/framework/config/config_test.go +++ b/acceptance/framework/config/config_test.go @@ -137,25 +137,34 @@ func TestConfig_HelmValuesFromConfig_EntImage(t *testing.T) { expErr string }{ { - consulImage: "hashicorp/consul:1.9.0", - expImage: "hashicorp/consul-enterprise:1.9.0-ent", + consulImage: "hashicorp/consul:1.15.3", + expImage: "hashicorp/consul-enterprise:1.15.3-ent", }, { - consulImage: "hashicorp/consul:1.8.5-rc1", - expImage: "hashicorp/consul-enterprise:1.8.5-rc1-ent", + consulImage: "hashicorp/consul:1.16.0-rc1", + expImage: "hashicorp/consul-enterprise:1.16.0-rc1-ent", }, { - consulImage: "hashicorp/consul:1.7.0-beta3", - expImage: "hashicorp/consul-enterprise:1.7.0-beta3-ent", - }, - { - consulImage: "invalid", - expErr: "could not determine consul version from global.image: invalid", + consulImage: "hashicorp/consul:1.14.0-beta1", + expImage: "hashicorp/consul-enterprise:1.14.0-beta1-ent", }, { consulImage: "hashicorp/consul@sha256:oioi2452345kjhlkh", expImage: "hashicorp/consul@sha256:oioi2452345kjhlkh", }, + // Nightly tags differ from release tags ('-ent' suffix is omitted) + { + consulImage: "docker.mirror.hashicorp.services/hashicorppreview/consul:1.17-dev", + expImage: "docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.17-dev", + }, + { + consulImage: "docker.mirror.hashicorp.services/hashicorppreview/consul:1.17-dev-ubi", + expImage: "docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.17-dev-ubi", + }, + { + consulImage: "docker.mirror.hashicorp.services/hashicorppreview/consul@sha256:oioi2452345kjhlkh", + expImage: "docker.mirror.hashicorp.services/hashicorppreview/consul@sha256:oioi2452345kjhlkh", + }, } for _, tt := range tests { t.Run(tt.consulImage, func(t *testing.T) { From 8379be90d833aee74ec2ffc21f6068bab006fb41 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Wed, 2 Aug 2023 13:04:28 -0400 Subject: [PATCH 313/592] [NET-5146] security: Upgrade Go and `x/net` (#2710) security: Upgrade Go and x/net Upgrade to Go 1.20.7 and `x/net` 1.13.0 to resolve [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409) and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978). --- .changelog/2710.txt | 5 +++++ .go-version | 2 +- acceptance/go.mod | 2 +- acceptance/go.sum | 4 ++-- cli/go.mod | 2 +- cli/go.sum | 4 ++-- control-plane/cni/go.mod | 2 +- control-plane/cni/go.sum | 4 ++-- control-plane/go.mod | 2 +- control-plane/go.sum | 4 ++-- 10 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 .changelog/2710.txt diff --git a/.changelog/2710.txt b/.changelog/2710.txt new file mode 100644 index 0000000000..1d37b32dfb --- /dev/null +++ b/.changelog/2710.txt @@ -0,0 +1,5 @@ +```release-note:security +Upgrade to use Go 1.20.7 and `x/net` 0.13.0. +This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) +and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). +``` diff --git a/.go-version b/.go-version index e63679c766..8909929f6e 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.20.6 +1.20.7 diff --git a/acceptance/go.mod b/acceptance/go.mod index b19015eda4..1dd344a5c8 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -122,7 +122,7 @@ require ( golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/term v0.10.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 43a557f32b..7361efbbb6 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -887,8 +887,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/cli/go.mod b/cli/go.mod index 5744e9dad0..3e92113d18 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -168,7 +168,7 @@ require ( go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.10.0 // indirect diff --git a/cli/go.sum b/cli/go.sum index 7861cb73d3..6b54e646b2 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -963,8 +963,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index fe67475524..e8fcc980ef 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -30,7 +30,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/term v0.10.0 // indirect diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index f95d4d991a..8f4c0668ea 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -287,8 +287,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ 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.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/control-plane/go.mod b/control-plane/go.mod index 8469502795..2dcd78848c 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -145,7 +145,7 @@ require ( go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.10.0 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 39e89efec8..dec3ba9eb4 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -655,8 +655,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 61c77616d390b9077b9bbfa7b9d0f80bae847571 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Wed, 2 Aug 2023 16:07:27 -0400 Subject: [PATCH 314/592] Increase timeout while waiting for vault server to be ready (#2709) increase timeout while waiting for server to be ready and fix require.Equal check --- acceptance/framework/vault/vault_cluster.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index 4dc832bcb6..8b82e41841 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -414,12 +414,12 @@ func (v *VaultCluster) initAndUnseal(t *testing.T) { v.logger.Logf(t, "initializing and unsealing Vault") namespace := v.helmOptions.KubectlOptions.Namespace - retrier := &retry.Timer{Timeout: 2 * time.Minute, Wait: 1 * time.Second} + retrier := &retry.Timer{Timeout: 4 * time.Minute, Wait: 1 * time.Second} retry.RunWith(retrier, t, func(r *retry.R) { // Wait for vault server pod to be running so that we can create Vault client without errors. serverPod, err := v.kubernetesClient.CoreV1().Pods(namespace).Get(context.Background(), fmt.Sprintf("%s-vault-0", v.releaseName), metav1.GetOptions{}) require.NoError(r, err) - require.Equal(r, serverPod.Status.Phase, corev1.PodRunning) + require.Equal(r, corev1.PodRunning, serverPod.Status.Phase) // Set up the client so that we can make API calls to initialize and unseal. v.vaultClient = v.SetupVaultClient(t) From 939e7c31993b0f3c83feceea79ae6c2e3064f700 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 3 Aug 2023 16:43:08 -0400 Subject: [PATCH 315/592] Acceptance tests: increase api-gateway retries (#2716) * Increase the retries and add config entry retries --- .../api_gateway_external_servers_test.go | 2 +- .../api-gateway/api_gateway_lifecycle_test.go | 14 ++++---- .../api-gateway/api_gateway_tenancy_test.go | 6 ++-- .../tests/api-gateway/api_gateway_test.go | 35 +++++++++++-------- .../partitions/partitions_gateway_test.go | 21 +++++++++++ 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/acceptance/tests/api-gateway/api_gateway_external_servers_test.go b/acceptance/tests/api-gateway/api_gateway_external_servers_test.go index d14ef59990..aa0934dc65 100644 --- a/acceptance/tests/api-gateway/api_gateway_external_servers_test.go +++ b/acceptance/tests/api-gateway/api_gateway_external_servers_test.go @@ -96,7 +96,7 @@ func TestAPIGateway_ExternalServers(t *testing.T) { // leader election so we may need to wait a long time for // the reconcile loop to run (hence a ~1m timeout here). var gatewayAddress string - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { var gateway gwv1beta1.Gateway err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) require.NoError(r, err) diff --git a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go index e3ffa992ce..f6f66ed995 100644 --- a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go +++ b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go @@ -196,7 +196,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { // check that the route is unbound and all Consul objects and Kubernetes statuses are cleaned up logger.Log(t, "checking that http route one is not bound to gateway two") - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { var route gwv1beta1.HTTPRoute err := k8sClient.Get(context.Background(), types.NamespacedName{Name: routeOneName, Namespace: defaultNamespace}, &route) require.NoError(r, err) @@ -246,7 +246,7 @@ func TestAPIGateway_Lifecycle(t *testing.T) { // check that the Kubernetes gateway is cleaned up logger.Log(t, "checking that gateway one is cleaned up in Kubernetes") - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { var route gwv1beta1.Gateway err := k8sClient.Get(context.Background(), types.NamespacedName{Name: controlledGatewayOneName, Namespace: defaultNamespace}, &route) require.NoError(r, err) @@ -299,7 +299,7 @@ func checkConsulNotExists(t *testing.T, client *api.Client, kind, name string, n opts.Namespace = namespace[0] } - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { _, _, err := client.ConfigEntries().Get(kind, name, opts) require.Error(r, err) require.EqualError(r, err, fmt.Sprintf("Unexpected response code: 404 (Config entry not found for %q / %q)", kind, name)) @@ -309,7 +309,7 @@ func checkConsulNotExists(t *testing.T, client *api.Client, kind, name string, n func checkConsulExists(t *testing.T, client *api.Client, kind, name string) { t.Helper() - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { _, _, err := client.ConfigEntries().Get(kind, name, nil) require.NoError(r, err) }) @@ -318,7 +318,7 @@ func checkConsulExists(t *testing.T, client *api.Client, kind, name string) { func checkConsulRouteParent(t *testing.T, client *api.Client, name, parent string) { t.Helper() - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { entry, _, err := client.ConfigEntries().Get(api.HTTPRoute, name, nil) require.NoError(r, err) route := entry.(*api.HTTPRouteConfigEntry) @@ -331,7 +331,7 @@ func checkConsulRouteParent(t *testing.T, client *api.Client, name, parent strin func checkEmptyRoute(t *testing.T, client client.Client, name, namespace string) { t.Helper() - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { var route gwv1beta1.HTTPRoute err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &route) require.NoError(r, err) @@ -344,7 +344,7 @@ func checkEmptyRoute(t *testing.T, client client.Client, name, namespace string) func checkRouteBound(t *testing.T, client client.Client, name, namespace, parent string) { t.Helper() - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { var route gwv1beta1.HTTPRoute err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &route) require.NoError(r, err) diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go index 19e85d60b0..f7b0ac6d79 100644 --- a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go +++ b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go @@ -131,7 +131,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { k8sClient := ctx.ControllerRuntimeClient(t) consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) - retryCheck(t, 60, func(r *retry.R) { + retryCheck(t, 120, func(r *retry.R) { var gateway gwv1beta1.Gateway err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: gatewayNamespace}, &gateway) require.NoError(r, err) @@ -153,7 +153,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { checkConsulNotExists(t, consulClient, api.APIGateway, "gateway", namespaceForConsul(c.namespaceMirroring, gatewayNamespace)) // route failure - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { var httproute gwv1beta1.HTTPRoute err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "route", Namespace: routeNamespace}, &httproute) require.NoError(r, err) @@ -175,7 +175,7 @@ func TestAPIGateway_Tenancy(t *testing.T) { createReferenceGrant(t, k8sClient, "route-service", routeNamespace, serviceNamespace) // gateway updated with references allowed - retryCheck(t, 30, func(r *retry.R) { + retryCheck(t, 60, func(r *retry.R) { var gateway gwv1beta1.Gateway err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: gatewayNamespace}, &gateway) require.NoError(r, err) diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index 4b4db38afe..721bbf2527 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -101,6 +101,11 @@ func TestAPIGateway_Basic(t *testing.T) { logger.Log(t, "creating target http server") k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + // We use the static-client pod so that we can make calls to the api gateway + // via kubectl exec without needing a route into the cluster from the test machine. + logger.Log(t, "creating static-client pod") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") + logger.Log(t, "patching route to target http server") k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"name":"static-server","port":80}]}]}}`, "--type=merge") @@ -115,11 +120,6 @@ func TestAPIGateway_Basic(t *testing.T) { k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/cases/api-gateways/tcproute/route.yaml") }) - // We use the static-client pod so that we can make calls to the api gateway - // via kubectl exec without needing a route into the cluster from the test machine. - logger.Log(t, "creating static-client pod") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") - // Grab a kubernetes client so that we can verify binding // behavior prior to issuing requests through the gateway. k8sClient := ctx.ControllerRuntimeClient(t) @@ -128,7 +128,7 @@ func TestAPIGateway_Basic(t *testing.T) { // leader election so we may need to wait a long time for // the reconcile loop to run (hence the 1m timeout here). var gatewayAddress string - counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} + counter := &retry.Counter{Count: 120, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { var gateway gwv1beta1.Gateway err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) @@ -209,17 +209,22 @@ func TestAPIGateway_Basic(t *testing.T) { checkStatusCondition(t, tcpRoute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) // check that the Consul entries were created - entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) - require.NoError(t, err) - gateway := entry.(*api.APIGatewayConfigEntry) + var gateway *api.APIGatewayConfigEntry + var httpRoute *api.HTTPRouteConfigEntry + var route *api.TCPRouteConfigEntry + retry.RunWith(counter, t, func(r *retry.R) { + entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) + require.NoError(r, err) + gateway = entry.(*api.APIGatewayConfigEntry) - entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) - require.NoError(t, err) - httpRoute := entry.(*api.HTTPRouteConfigEntry) + entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) + require.NoError(r, err) + httpRoute = entry.(*api.HTTPRouteConfigEntry) - entry, _, err = consulClient.ConfigEntries().Get(api.TCPRoute, "tcp-route", nil) - require.NoError(t, err) - route := entry.(*api.TCPRouteConfigEntry) + entry, _, err = consulClient.ConfigEntries().Get(api.TCPRoute, "tcp-route", nil) + require.NoError(r, err) + route = entry.(*api.TCPRouteConfigEntry) + }) // now check the gateway status conditions checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) diff --git a/acceptance/tests/partitions/partitions_gateway_test.go b/acceptance/tests/partitions/partitions_gateway_test.go index acdb81fa65..a90a790cb6 100644 --- a/acceptance/tests/partitions/partitions_gateway_test.go +++ b/acceptance/tests/partitions/partitions_gateway_test.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -255,6 +256,16 @@ func TestPartitions_Gateway(t *testing.T) { logger.Log(t, "creating target server in secondary partition cluster") k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + // Check that static-server injected 2 containers. + for _, labelSelector := range []string{"app=static-server"} { + podList, err := secondaryPartitionClusterContext.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{ + LabelSelector: labelSelector, + }) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + } + logger.Log(t, "patching route to target server") k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") @@ -293,6 +304,16 @@ func TestPartitions_Gateway(t *testing.T) { logger.Log(t, "creating target server in default partition cluster") k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + // Check that static-server injected 2 containers. + for _, labelSelector := range []string{"app=static-server"} { + podList, err := defaultPartitionClusterContext.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{ + LabelSelector: labelSelector, + }) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + } + logger.Log(t, "creating exported services") k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { From 671675d49fbca59272fbbd54becd6366620704a5 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Mon, 7 Aug 2023 22:47:05 -0400 Subject: [PATCH 316/592] NET-3908: allow configuration of SecurityContextConstraints when running on OpenShift (#2184) Co-authored-by: Melisa Griffin --- .changelog/2184.txt | 3 + .../templates/connect-inject-clusterrole.yaml | 10 +++ .../templates/crd-gatewayclassconfigs.yaml | 4 ++ .../templates/gateway-resources-job.yaml | 3 + .../test/unit/connect-inject-clusterrole.bats | 24 +++++++ .../test/unit/gateway-resources-job.bats | 17 +++++ charts/consul/values.yaml | 5 ++ cli/helm/values.go | 11 +-- .../api-gateway/common/helm_config.go | 6 +- .../api-gateway/gatekeeper/gatekeeper.go | 2 +- .../api-gateway/gatekeeper/gatekeeper_test.go | 72 ++++++++++++++++--- control-plane/api-gateway/gatekeeper/init.go | 19 ++--- control-plane/api-gateway/gatekeeper/role.go | 15 +++- .../api-gateway/gatekeeper/rolebinding.go | 4 +- .../api-gateway/gatekeeper/serviceaccount.go | 2 +- .../api/v1alpha1/api_gateway_types.go | 3 + .../api/v1alpha1/api_gateway_types_test.go | 1 + .../subcommand/gateway-resources/command.go | 6 ++ .../gateway-resources/command_test.go | 2 + 19 files changed, 180 insertions(+), 29 deletions(-) create mode 100644 .changelog/2184.txt diff --git a/.changelog/2184.txt b/.changelog/2184.txt new file mode 100644 index 0000000000..bdcb6039fd --- /dev/null +++ b/.changelog/2184.txt @@ -0,0 +1,3 @@ +```release-note:feature +api-gateway: support deploying to OpenShift 4.11 +``` diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 8c0bbe9bf7..f1f6b3878f 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -186,4 +186,14 @@ rules: - "get" - "list" - "watch" +{{- if .Values.global.openshift.enabled }} +- apiGroups: + - security.openshift.io + resources: + - securitycontextconstraints + resourceNames: + - {{ .Values.connectInject.apiGateway.managedGatewayClass.openshiftSCCName }} + verbs: + - use + {{- end }} {{- end }} diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 4ab6570e31..38625c9368 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -138,6 +138,10 @@ spec: type: string type: object type: array + openshiftSCCName: + description: The name of an existing SecurityContextConstraints + resource to bind to the managed role when running on OpenShift. + type: string type: object type: object served: true diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 1fa712759d..048af9fc26 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -99,6 +99,9 @@ spec: - {{- toYaml .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations | nindent 14 -}} {{- end }} - -service-type={{ .Values.connectInject.apiGateway.managedGatewayClass.serviceType }} + {{- if .Values.global.openshift.enabled }} + - -openshift-scc-name={{ .Values.connectInject.apiGateway.managedGatewayClass.openshiftSCCName }} + {{- end }} {{- end}} resources: requests: diff --git a/charts/consul/test/unit/connect-inject-clusterrole.bats b/charts/consul/test/unit/connect-inject-clusterrole.bats index ace8c18d4a..d02b9eacde 100644 --- a/charts/consul/test/unit/connect-inject-clusterrole.bats +++ b/charts/consul/test/unit/connect-inject-clusterrole.bats @@ -217,3 +217,27 @@ load _helpers local actual=$(echo $object | yq -r '.verbs | index("watch")' | tee /dev/stderr) [ "${actual}" != null ] } + +#-------------------------------------------------------------------- +# openshift + +@test "connectInject/ClusterRole: adds permission to securitycontextconstraints for Openshift with global.openshift.enabled=true with default apiGateway Openshift SCC Name" { + cd `chart_dir` + local object=$(helm template \ + -s templates/connect-inject-clusterrole.yaml \ + --set 'global.openshift.enabled=true' \ + . | tee /dev/stderr | + yq '.rules[13].resourceNames | index("restricted-v2")' | tee /dev/stderr) + [ "${object}" == 0 ] +} + +@test "connectInject/ClusterRole: adds permission to securitycontextconstraints for Openshift with global.openshift.enabled=true and sets apiGateway Openshift SCC Name" { + cd `chart_dir` + local object=$(helm template \ + -s templates/connect-inject-clusterrole.yaml \ + --set 'global.openshift.enabled=true' \ + --set 'connectInject.apiGateway.managedGatewayClass.openshiftSCCName=fakescc' \ + . | tee /dev/stderr | + yq '.rules[13].resourceNames | index("fakescc")' | tee /dev/stderr) + [ "${object}" == 0 ] +} diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats index d79838770d..09322bd2a7 100644 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ b/charts/consul/test/unit/gateway-resources-job.bats @@ -92,6 +92,7 @@ target=templates/gateway-resources-job.yaml --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' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) @@ -121,6 +122,22 @@ target=templates/gateway-resources-job.yaml local actual=$(echo "$spec" | jq '.[16]') [ "${actual}" = "\"- bingo\"" ] + + local actual=$(echo "$spec" | jq '.[17]') + [ "${actual}" = "\"-service-type=Foo\"" ] +} + +@test "apiGateway/GatewayClassConfig: custom configuration openshift enabled" { + cd `chart_dir` + local spec=$(helm template \ + -s $target \ + --set 'global.openshift.enabled=true' \ + --set 'connectInject.apiGateway.managedGatewayClass.openshiftSCCName=hello' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo "$spec" | jq '.[13]') + [ "${actual}" = "\"-openshift-scc-name=hello\"" ] } diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index d373a240f0..487b6f9ac1 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2201,6 +2201,11 @@ connectInject: maxInstances: 1 minInstances: 1 + # The name of the OpenShift SecurityContextConstraints resource to use for Gateways. + # Only applicable if `global.openshift.enabled` is true. + # @type: string + openshiftSCCName: "restricted-v2" + # Configuration for the ServiceAccount created for the api-gateway component serviceAccount: # This value defines additional annotations for the client service account. This should be formatted as a multi-line diff --git a/cli/helm/values.go b/cli/helm/values.go index 06671382d1..31b3508e80 100644 --- a/cli/helm/values.go +++ b/cli/helm/values.go @@ -576,11 +576,12 @@ type CopyAnnotations struct { } type ManagedGatewayClass struct { - Enabled bool `yaml:"enabled"` - NodeSelector interface{} `yaml:"nodeSelector"` - ServiceType string `yaml:"serviceType"` - UseHostPorts bool `yaml:"useHostPorts"` - CopyAnnotations CopyAnnotations `yaml:"copyAnnotations"` + Enabled bool `yaml:"enabled"` + NodeSelector interface{} `yaml:"nodeSelector"` + ServiceType string `yaml:"serviceType"` + UseHostPorts bool `yaml:"useHostPorts"` + CopyAnnotations CopyAnnotations `yaml:"copyAnnotations"` + OpenshiftSCCName string `yaml:"openshiftSCCName"` } type Service struct { diff --git a/control-plane/api-gateway/common/helm_config.go b/control-plane/api-gateway/common/helm_config.go index f0d4dc7988..d83b298d92 100644 --- a/control-plane/api-gateway/common/helm_config.go +++ b/control-plane/api-gateway/common/helm_config.go @@ -11,6 +11,7 @@ import ( const componentAuthMethod = "k8s-component-auth-method" // HelmConfig is the configuration of gateways that comes in from the user's Helm values. +// This is a combination of the apiGateway stanza and other settings that impact api-gateways. type HelmConfig struct { // ImageDataplane is the Consul Dataplane image to use in gateway deployments. ImageDataplane string @@ -18,7 +19,6 @@ type HelmConfig struct { ConsulDestinationNamespace string NamespaceMirroringPrefix string EnableNamespaces bool - EnableOpenShift bool EnableNamespaceMirroring bool AuthMethod string // LogLevel is the logging level of the deployed Consul Dataplanes. @@ -30,6 +30,10 @@ type HelmConfig struct { ConsulTLSServerName string ConsulCACert string ConsulConfig ConsulConfig + + // EnableOpenShift indicates whether we're deploying into an OpenShift environment + // and should create SecurityContextConstraints. + EnableOpenShift bool } type ConsulConfig struct { diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper.go b/control-plane/api-gateway/gatekeeper/gatekeeper.go index 19444831ee..6cb7170fc8 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper.go @@ -96,7 +96,7 @@ func (g *Gatekeeper) namespacedName(gateway gwv1beta1.Gateway) types.NamespacedN } func (g *Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config common.HelmConfig) string { - if config.AuthMethod == "" { + if config.AuthMethod == "" && !config.EnableOpenShift { 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 069643e301..30cc78d464 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -193,7 +193,7 @@ func TestUpsert(t *testing.T) { configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), }, roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1"), + configureRole(name, namespace, labels, "1", false), }, roleBindings: []*rbac.RoleBinding{ configureRoleBinding(name, namespace, labels, "1"), @@ -319,7 +319,7 @@ func TestUpsert(t *testing.T) { configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), }, roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1"), + configureRole(name, namespace, labels, "1", false), }, roleBindings: []*rbac.RoleBinding{ configureRoleBinding(name, namespace, labels, "1"), @@ -342,7 +342,7 @@ func TestUpsert(t *testing.T) { configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"), }, roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1"), + configureRole(name, namespace, labels, "1", false), }, roleBindings: []*rbac.RoleBinding{ configureRoleBinding(name, namespace, labels, "1"), @@ -400,7 +400,7 @@ func TestUpsert(t *testing.T) { configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), }, roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1"), + configureRole(name, namespace, labels, "1", false), }, roleBindings: []*rbac.RoleBinding{ configureRoleBinding(name, namespace, labels, "1"), @@ -428,7 +428,7 @@ func TestUpsert(t *testing.T) { configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"), }, roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1"), + configureRole(name, namespace, labels, "1", false), }, roleBindings: []*rbac.RoleBinding{ configureRoleBinding(name, namespace, labels, "1"), @@ -603,6 +603,50 @@ func TestUpsert(t *testing.T) { serviceAccounts: []*corev1.ServiceAccount{}, }, }, + "create a new gateway with openshift enabled": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: listeners, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + OpenshiftSCCName: "test-api-gateway", + }, + }, + helmConfig: common.HelmConfig{ + EnableOpenShift: true, + }, + initialResources: resources{}, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{ + configureRole(name, namespace, labels, "1", true), + }, + roleBindings: []*rbac.RoleBinding{ + configureRoleBinding(name, namespace, labels, "1"), + }, + services: []*corev1.Service{}, + serviceAccounts: []*corev1.ServiceAccount{ + configureServiceAccount(name, namespace, labels, "1"), + }, + }, + }, } for name, tc := range cases { @@ -754,7 +798,7 @@ func TestDelete(t *testing.T) { configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), }, roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1"), + configureRole(name, namespace, labels, "1", false), }, roleBindings: []*rbac.RoleBinding{ configureRoleBinding(name, namespace, labels, "1"), @@ -1057,7 +1101,19 @@ func configureDeployment(name, namespace string, labels map[string]string, repli } } -func configureRole(name, namespace string, labels map[string]string, resourceVersion string) *rbac.Role { +func configureRole(name, namespace string, labels map[string]string, resourceVersion string, openshiftEnabled bool) *rbac.Role { + rules := []rbac.PolicyRule{} + + if openshiftEnabled { + rules = []rbac.PolicyRule{ + { + APIGroups: []string{"security.openshift.io"}, + Resources: []string{"securitycontextconstraints"}, + ResourceNames: []string{name + "-api-gateway"}, + Verbs: []string{"use"}, + }, + } + } return &rbac.Role{ TypeMeta: metav1.TypeMeta{ APIVersion: "rbac.authorization.k8s.io/v1", @@ -1078,7 +1134,7 @@ func configureRole(name, namespace string, labels map[string]string, resourceVer }, }, }, - Rules: []rbac.PolicyRule{}, + Rules: rules, } } diff --git a/control-plane/api-gateway/gatekeeper/init.go b/control-plane/api-gateway/gatekeeper/init.go index 35360b7f87..f3d4ad1f95 100644 --- a/control-plane/api-gateway/gatekeeper/init.go +++ b/control-plane/api-gateway/gatekeeper/init.go @@ -168,14 +168,17 @@ func initContainer(config common.HelmConfig, name, namespace string) (corev1.Con }) } - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(initContainersUserAndGroupID), - RunAsGroup: pointer.Int64(initContainersUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(false), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - }, + // Openshift Assigns the security context for us, do not enable if it is enabled. + if !config.EnableOpenShift { + container.SecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(initContainersUserAndGroupID), + RunAsGroup: pointer.Int64(initContainersUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + Privileged: pointer.Bool(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + } } return container, nil diff --git a/control-plane/api-gateway/gatekeeper/role.go b/control-plane/api-gateway/gatekeeper/role.go index eb8431075a..705e9bffff 100644 --- a/control-plane/api-gateway/gatekeeper/role.go +++ b/control-plane/api-gateway/gatekeeper/role.go @@ -19,7 +19,7 @@ import ( ) func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { - if config.AuthMethod == "" { + if config.AuthMethod == "" && !config.EnableOpenShift { return g.deleteRole(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) } @@ -40,7 +40,7 @@ func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, return errors.New("role not owned by controller") } - role = g.role(gateway, gcc) + role = g.role(gateway, gcc, config) if err := ctrl.SetControllerReference(&gateway, role, g.Client.Scheme()); err != nil { return err } @@ -62,7 +62,7 @@ func (g *Gatekeeper) deleteRole(ctx context.Context, gwName types.NamespacedName return nil } -func (g *Gatekeeper) role(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig) *rbac.Role { +func (g *Gatekeeper) role(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) *rbac.Role { role := &rbac.Role{ ObjectMeta: metav1.ObjectMeta{ Name: gateway.Name, @@ -81,5 +81,14 @@ func (g *Gatekeeper) role(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassCo }) } + if config.EnableOpenShift { + role.Rules = append(role.Rules, rbac.PolicyRule{ + APIGroups: []string{"security.openshift.io"}, + Resources: []string{"securitycontextconstraints"}, + ResourceNames: []string{gcc.Spec.OpenshiftSCCName}, + Verbs: []string{"use"}, + }) + } + return role } diff --git a/control-plane/api-gateway/gatekeeper/rolebinding.go b/control-plane/api-gateway/gatekeeper/rolebinding.go index 8891a754e6..1a60e752c8 100644 --- a/control-plane/api-gateway/gatekeeper/rolebinding.go +++ b/control-plane/api-gateway/gatekeeper/rolebinding.go @@ -19,7 +19,7 @@ import ( ) func (g *Gatekeeper) upsertRoleBinding(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { - if config.AuthMethod == "" { + if config.AuthMethod == "" && !config.EnableOpenShift { return g.deleteRole(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) } @@ -66,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) - role := g.role(gateway, gcc) + role := g.role(gateway, gcc, config) return &rbac.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api-gateway/gatekeeper/serviceaccount.go b/control-plane/api-gateway/gatekeeper/serviceaccount.go index 47336867aa..d1c5c9883a 100644 --- a/control-plane/api-gateway/gatekeeper/serviceaccount.go +++ b/control-plane/api-gateway/gatekeeper/serviceaccount.go @@ -18,7 +18,7 @@ import ( ) func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1.Gateway, config common.HelmConfig) error { - if config.AuthMethod == "" { + if config.AuthMethod == "" && !config.EnableOpenShift { return g.deleteServiceAccount(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) } diff --git a/control-plane/api/v1alpha1/api_gateway_types.go b/control-plane/api/v1alpha1/api_gateway_types.go index f9be4bdb47..7f0b958701 100644 --- a/control-plane/api/v1alpha1/api_gateway_types.go +++ b/control-plane/api/v1alpha1/api_gateway_types.go @@ -59,6 +59,9 @@ type GatewayClassConfigSpec struct { // The name of an existing Kubernetes PodSecurityPolicy to bind to the managed ServiceAccount if ACLs are managed. PodSecurityPolicy string `json:"podSecurityPolicy,omitempty"` + + // The name of the OpenShift SecurityContextConstraints resource for this gateway class to use. + OpenshiftSCCName string `json:"openshiftSCCName,omitempty"` } // +k8s:deepcopy-gen=true diff --git a/control-plane/api/v1alpha1/api_gateway_types_test.go b/control-plane/api/v1alpha1/api_gateway_types_test.go index 1f9d8ebef0..6e0690b9b2 100644 --- a/control-plane/api/v1alpha1/api_gateway_types_test.go +++ b/control-plane/api/v1alpha1/api_gateway_types_test.go @@ -21,6 +21,7 @@ func TestGatewayClassConfigDeepCopy(t *testing.T) { NodeSelector: map[string]string{ "test": "test", }, + OpenshiftSCCName: "restricted-v2", } config := &GatewayClassConfig{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 2da2abccb1..3ad3ff7f53 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -71,6 +71,8 @@ type Command struct { flagTolerations string // this is a multiline yaml string matching the tolerations array flagServiceAnnotations string // this is a multiline yaml string array of annotations to allow + flagOpenshiftSCCName string + k8sClient client.Client once sync.Once @@ -123,6 +125,9 @@ func (c *Command) init() { c.flags.StringVar(&c.flagServiceAnnotations, "service-annotations", "", "The annotations to copy over from a gateway to its service.", ) + c.flags.StringVar(&c.flagOpenshiftSCCName, "openshift-scc-name", "", + "Name of security context constraint to use for gateways on Openshift.", + ) c.k8s = &flags.K8SFlags{} flags.Merge(c.flags, c.k8s.Flags()) @@ -197,6 +202,7 @@ func (c *Command) Run(args []string) int { MaxInstances: nonZeroOrNil(c.flagDeploymentMaxInstances), MinInstances: nonZeroOrNil(c.flagDeploymentMinInstances), }, + OpenshiftSCCName: c.flagOpenshiftSCCName, }, } diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 0c40e67244..f60e376042 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -163,6 +163,7 @@ bar: 2`, flagServiceAnnotations: ` - foo - bar`, + flagOpenshiftSCCName: "restricted-v2", }, }, } { @@ -245,6 +246,7 @@ func TestRun(t *testing.T) { "-release-name", "test", "-component", "test", "-controller-name", "test", + "-openshift-scc-name", "restricted-v2", }) require.Equal(t, 0, code) From 71cdbc24fa8ad3bf3571b4c71744a5171cafdd50 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Tue, 8 Aug 2023 10:47:26 -0400 Subject: [PATCH 317/592] Gateway privileged port mapping (#2707) * Adds port mapping to Gateway Class Config to avoid running container on privileged ports Co-authored-by: Nathan Coleman --- .changelog/2707.txt | 3 + .../templates/crd-gatewayclassconfigs.yaml | 9 ++ .../templates/gateway-resources-job.yaml | 1 + charts/consul/values.yaml | 6 + cli/helm/values.go | 13 +- control-plane/api-gateway/binding/binder.go | 4 +- control-plane/api-gateway/binding/result.go | 8 +- .../api-gateway/binding/validation.go | 19 ++- .../api-gateway/binding/validation_test.go | 31 ++++- .../api-gateway/common/helm_config.go | 4 + .../api-gateway/common/translation.go | 22 +++- .../api-gateway/common/translation_test.go | 2 +- .../api-gateway/gatekeeper/gatekeeper_test.go | 112 ++++++++++++++---- .../api-gateway/gatekeeper/service.go | 6 +- .../api/v1alpha1/api_gateway_types.go | 3 + .../subcommand/gateway-resources/command.go | 9 +- 16 files changed, 209 insertions(+), 43 deletions(-) create mode 100644 .changelog/2707.txt diff --git a/.changelog/2707.txt b/.changelog/2707.txt new file mode 100644 index 0000000000..370aaa7c17 --- /dev/null +++ b/.changelog/2707.txt @@ -0,0 +1,3 @@ +```release-note:feature +api-gateway: adds ability to map privileged ports on Gateway listeners to unprivileged ports so that containers do not require additional privileges +``` diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 38625c9368..8140902f78 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -142,6 +142,15 @@ spec: description: The name of an existing SecurityContextConstraints resource to bind to the managed role when running on OpenShift. type: string + mapPrivilegedContainerPorts: + type: integer + format: int32 + minimum: 0 + maximum: 64512 + description: mapPrivilegedContainerPorts is the value which Consul will add to privileged container port + values (ports < 1024) defined on a Gateway when the number is greater than 0. This cannot be more than + 64512 as the highest privileged port is 1023, which would then map to 65535, which is the highest + valid port number. type: object type: object served: true diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 048af9fc26..de64e2d70d 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -102,6 +102,7 @@ spec: {{- if .Values.global.openshift.enabled }} - -openshift-scc-name={{ .Values.connectInject.apiGateway.managedGatewayClass.openshiftSCCName }} {{- end }} + - -map-privileged-container-ports={{ .Values.connectInject.apiGateway.managedGatewayClass.mapPrivilegedContainerPorts }} {{- end}} resources: requests: diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 487b6f9ac1..954680262a 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2206,6 +2206,12 @@ connectInject: # @type: string openshiftSCCName: "restricted-v2" + # This value defines the amount we will add to privileged container ports on gateways that use this class. + # This is useful if you don't want to give your containers extra permissions to run privileged ports. + # Example: The gateway listener is defined on port 80, but the underlying value of the port on the container + # will be the 80 + the number defined below. + mapPrivilegedContainerPorts: 0 + # Configuration for the ServiceAccount created for the api-gateway component serviceAccount: # This value defines additional annotations for the client service account. This should be formatted as a multi-line diff --git a/cli/helm/values.go b/cli/helm/values.go index 31b3508e80..19e19d8d05 100644 --- a/cli/helm/values.go +++ b/cli/helm/values.go @@ -576,12 +576,13 @@ type CopyAnnotations struct { } type ManagedGatewayClass struct { - Enabled bool `yaml:"enabled"` - NodeSelector interface{} `yaml:"nodeSelector"` - ServiceType string `yaml:"serviceType"` - UseHostPorts bool `yaml:"useHostPorts"` - CopyAnnotations CopyAnnotations `yaml:"copyAnnotations"` - OpenshiftSCCName string `yaml:"openshiftSCCName"` + Enabled bool `yaml:"enabled"` + NodeSelector interface{} `yaml:"nodeSelector"` + ServiceType string `yaml:"serviceType"` + UseHostPorts bool `yaml:"useHostPorts"` + CopyAnnotations CopyAnnotations `yaml:"copyAnnotations"` + OpenshiftSCCName string `yaml:"openshiftSCCName"` + MapPrivilegedContainerPorts int `yaml:"mapPrivilegedContainerPorts"` } type Service struct { diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index 7fbf18d412..7798a6b49c 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -132,7 +132,7 @@ func (b *Binder) Snapshot() *Snapshot { // calculate the status for the gateway gatewayValidation = validateGateway(b.config.Gateway, registrationPods, b.config.ConsulGateway) - listenerValidation = validateListeners(b.config.Gateway, b.config.Gateway.Spec.Listeners, b.config.Resources) + listenerValidation = validateListeners(b.config.Gateway, b.config.Gateway.Spec.Listeners, b.config.Resources, b.config.GatewayClassConfig) } // used for tracking how many routes have successfully bound to which listeners @@ -182,7 +182,7 @@ func (b *Binder) Snapshot() *Snapshot { if b.config.ConsulGateway != nil { consulStatus = b.config.ConsulGateway.Status } - entry := b.config.Translator.ToAPIGateway(b.config.Gateway, b.config.Resources) + entry := b.config.Translator.ToAPIGateway(b.config.Gateway, b.config.Resources, gatewayClassConfig) snapshot.Consul.Updates = append(snapshot.Consul.Updates, &common.ConsulUpdateOperation{ Entry: entry, OnUpdate: b.handleGatewaySyncStatus(snapshot, &b.config.Gateway, consulStatus), diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index b148e441e2..1953d4836c 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -242,6 +242,12 @@ var ( // We map anything under here to a custom ListenerConditionReason of Invalid on // an Accepted status type. errListenerNoTLSPassthrough = errors.New("TLS passthrough is not supported") + + // This custom listener validation error is used to differentiate between an errListenerPortUnavailable because of + // direct port conflicts defined by the user (two listeners on the same port) vs a port conflict because we map + // privileged ports by adding the value passed into the gatewayClassConfig. + // (i.e. one listener on 80 with a privileged port mapping of 2000, and one listener on 2080 would conflict). + errListenerMappedToPrivilegedPortMapping = errors.New("listener conflicts with privileged port mapped by GatewayClassConfig privileged port mapping setting") ) // listenerValidationResult contains the result of internally validating a single listener @@ -291,7 +297,7 @@ func (l listenerValidationResult) programmedCondition(generation int64) metav1.C func (l listenerValidationResult) acceptedCondition(generation int64) metav1.Condition { now := timeFunc() switch l.acceptedErr { - case errListenerPortUnavailable: + case errListenerPortUnavailable, errListenerMappedToPrivilegedPortMapping: return metav1.Condition{ Type: "Accepted", Status: metav1.ConditionFalse, diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index a57cf598a4..d5a97499ca 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -226,7 +226,7 @@ func validateCertificateData(secret corev1.Secret) error { // validateListeners validates the given listeners both internally and with respect to each // other for purposes of setting "Conflicted" status conditions. -func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener, resources *common.ResourceMap) listenerValidationResults { +func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener, resources *common.ResourceMap, gwcc *v1alpha1.GatewayClassConfig) listenerValidationResults { var results listenerValidationResults merged := make(map[gwv1beta1.PortNumber]mergedListeners) for i, listener := range listeners { @@ -235,7 +235,15 @@ func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener listener: listener, }) } - + // This list keeps track of port conflicts directly on gateways. i.e., two listeners on the same port as + // defined by the user. + seenListenerPorts := map[int]struct{}{} + // This list keeps track of port conflicts caused by privileged port mappings. + seenContainerPorts := map[int]struct{}{} + portMapping := int32(0) + if gwcc != nil { + portMapping = gwcc.Spec.MapPrivilegedContainerPorts + } for i, listener := range listeners { var result listenerValidationResult @@ -249,6 +257,10 @@ func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener result.acceptedErr = errListenerUnsupportedProtocol } else if listener.Port == 20000 { // admin port result.acceptedErr = errListenerPortUnavailable + } else if _, ok := seenListenerPorts[int(listener.Port)]; ok { + result.acceptedErr = errListenerPortUnavailable + } else if _, ok := seenContainerPorts[common.ToContainerPort(listener.Port, portMapping)]; ok { + result.acceptedErr = errListenerMappedToPrivilegedPortMapping } result.routeKindErr = validateListenerAllowedRouteKinds(listener.AllowedRoutes) @@ -261,6 +273,9 @@ func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener } results = append(results, result) + + seenListenerPorts[int(listener.Port)] = struct{}{} + seenContainerPorts[common.ToContainerPort(listener.Port, portMapping)] = struct{}{} } return results } diff --git a/control-plane/api-gateway/binding/validation_test.go b/control-plane/api-gateway/binding/validation_test.go index da0ed83f95..10cdf851c3 100644 --- a/control-plane/api-gateway/binding/validation_test.go +++ b/control-plane/api-gateway/binding/validation_test.go @@ -530,8 +530,10 @@ func TestValidateListeners(t *testing.T) { t.Parallel() for name, tt := range map[string]struct { - listeners []gwv1beta1.Listener - expectedAcceptedErr error + listeners []gwv1beta1.Listener + expectedAcceptedErr error + listenerIndexToTest int + mapPrivilegedContainerPorts int32 }{ "valid protocol HTTP": { listeners: []gwv1beta1.Listener{ @@ -563,9 +565,32 @@ func TestValidateListeners(t *testing.T) { }, expectedAcceptedErr: errListenerPortUnavailable, }, + "conflicted port": { + listeners: []gwv1beta1.Listener{ + {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, + {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, + }, + expectedAcceptedErr: errListenerPortUnavailable, + listenerIndexToTest: 1, + }, + "conflicted mapped port": { + listeners: []gwv1beta1.Listener{ + {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, + {Protocol: gwv1beta1.TCPProtocolType, Port: 2080}, + }, + expectedAcceptedErr: errListenerMappedToPrivilegedPortMapping, + listenerIndexToTest: 1, + mapPrivilegedContainerPorts: 2000, + }, } { t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expectedAcceptedErr, validateListeners(gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tt.listeners, nil)[0].acceptedErr) + gwcc := &v1alpha1.GatewayClassConfig{ + Spec: v1alpha1.GatewayClassConfigSpec{ + MapPrivilegedContainerPorts: tt.mapPrivilegedContainerPorts, + }, + } + + require.Equal(t, tt.expectedAcceptedErr, validateListeners(gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tt.listeners, nil, gwcc)[tt.listenerIndexToTest].acceptedErr) }) } } diff --git a/control-plane/api-gateway/common/helm_config.go b/control-plane/api-gateway/common/helm_config.go index d83b298d92..ecd9d42c29 100644 --- a/control-plane/api-gateway/common/helm_config.go +++ b/control-plane/api-gateway/common/helm_config.go @@ -34,6 +34,10 @@ type HelmConfig struct { // EnableOpenShift indicates whether we're deploying into an OpenShift environment // and should create SecurityContextConstraints. EnableOpenShift bool + + // MapPrivilegedServicePorts is the value which Consul will add to privileged container port values (ports < 1024) + // defined on a Gateway. + MapPrivilegedServicePorts int } type ConsulConfig struct { diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index 94241eed22..2f66749c94 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -55,11 +55,11 @@ func (t ResourceTranslator) Namespace(namespace string) string { } // ToAPIGateway translates a kuberenetes API gateway into a Consul APIGateway Config Entry. -func (t ResourceTranslator) ToAPIGateway(gateway gwv1beta1.Gateway, resources *ResourceMap) *api.APIGatewayConfigEntry { +func (t ResourceTranslator) ToAPIGateway(gateway gwv1beta1.Gateway, resources *ResourceMap, gwcc *v1alpha1.GatewayClassConfig) *api.APIGatewayConfigEntry { namespace := t.Namespace(gateway.Namespace) listeners := ConvertSliceFuncIf(gateway.Spec.Listeners, func(listener gwv1beta1.Listener) (api.APIGatewayListener, bool) { - return t.toAPIGatewayListener(gateway, listener, resources) + return t.toAPIGatewayListener(gateway, listener, resources, gwcc) }) return &api.APIGatewayConfigEntry{ @@ -81,7 +81,7 @@ var listenerProtocolMap = map[string]string{ "tcp": "tcp", } -func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, listener gwv1beta1.Listener, resources *ResourceMap) (api.APIGatewayListener, bool) { +func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, listener gwv1beta1.Listener, resources *ResourceMap, gwcc *v1alpha1.GatewayClassConfig) (api.APIGatewayListener, bool) { namespace := gateway.Namespace var certificates []api.ResourceReference @@ -104,10 +104,15 @@ func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, list } } + portMapping := int32(0) + if gwcc != nil { + portMapping = gwcc.Spec.MapPrivilegedContainerPorts + } + return api.APIGatewayListener{ Name: string(listener.Name), Hostname: DerefStringOr(listener.Hostname, ""), - Port: int(listener.Port), + Port: ToContainerPort(listener.Port, portMapping), Protocol: listenerProtocolMap[strings.ToLower(string(listener.Protocol))], TLS: api.APIGatewayTLSConfiguration{ Certificates: certificates, @@ -115,6 +120,15 @@ func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, list }, true } +func ToContainerPort(portNumber gwv1beta1.PortNumber, mapPrivilegedContainerPorts int32) int { + if portNumber >= 1024 { + // We don't care about privileged port-mapping, this is a non-privileged port + return int(portNumber) + } + + return int(portNumber) + int(mapPrivilegedContainerPorts) +} + func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *ResourceMap) *api.HTTPRouteConfigEntry { namespace := t.Namespace(route.Namespace) diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index 20917151f3..daa89a698f 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -320,7 +320,7 @@ func TestTranslator_ToAPIGateway(t *testing.T) { resources.ReferenceCountCertificate(listenerOneCert) resources.ReferenceCountCertificate(listenerTwoCert) - actualConfigEntry := translator.ToAPIGateway(input, resources) + actualConfigEntry := translator.ToAPIGateway(input, resources, &v1alpha1.GatewayClassConfig{}) if diff := cmp.Diff(expectedConfigEntry, actualConfigEntry); diff != "" { t.Errorf("Translator.GatewayToAPIGateway() mismatch (-want +got):\n%s", diff) diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go index 30cc78d464..562b139274 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -19,6 +19,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -112,6 +113,68 @@ func TestUpsert(t *testing.T) { serviceAccounts: []*corev1.ServiceAccount{}, }, }, + "create a new gateway with service and map privileged ports correctly": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "Listener 1", + Port: 80, + Protocol: "TCP", + }, + { + Name: "Listener 2", + Port: 8080, + Protocol: "TCP", + }, + }, + }, + }, + gatewayClassConfig: v1alpha1.GatewayClassConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-gatewayclassconfig", + }, + Spec: v1alpha1.GatewayClassConfigSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + DefaultInstances: common.PointerTo(int32(3)), + MaxInstances: common.PointerTo(int32(3)), + MinInstances: common.PointerTo(int32(1)), + }, + CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, + ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), + MapPrivilegedContainerPorts: 2000, + }, + }, + helmConfig: common.HelmConfig{}, + initialResources: resources{}, + finalResources: resources{ + deployments: []*appsv1.Deployment{ + configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + }, + roles: []*rbac.Role{}, + services: []*corev1.Service{ + configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ + { + Name: "Listener 1", + Protocol: "TCP", + Port: 80, + TargetPort: intstr.FromInt(2080), + }, + { + Name: "Listener 2", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), + }, + }, "1"), + }, + serviceAccounts: []*corev1.ServiceAccount{}, + }, + }, "create a new gateway deployment with managed Service": { gateway: gwv1beta1.Gateway{ ObjectMeta: metav1.ObjectMeta{ @@ -146,14 +209,16 @@ func TestUpsert(t *testing.T) { services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), }, { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, + Name: "Listener 2", + Protocol: "TCP", + Port: 8081, + TargetPort: intstr.FromInt(8081), }, }, "1"), }, @@ -201,14 +266,16 @@ func TestUpsert(t *testing.T) { services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), }, { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, + Name: "Listener 2", + Protocol: "TCP", + Port: 8081, + TargetPort: intstr.FromInt(8081), }, }, "1"), }, @@ -350,14 +417,16 @@ func TestUpsert(t *testing.T) { services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), }, { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, + Name: "Listener 2", + Protocol: "TCP", + Port: 8081, + TargetPort: intstr.FromInt(8081), }, }, "2"), }, @@ -436,9 +505,10 @@ func TestUpsert(t *testing.T) { services: []*corev1.Service{ configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, + Name: "Listener 1", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), }, }, "2"), }, diff --git a/control-plane/api-gateway/gatekeeper/service.go b/control-plane/api-gateway/gatekeeper/service.go index d534ad50d7..a30a3df89f 100644 --- a/control-plane/api-gateway/gatekeeper/service.go +++ b/control-plane/api-gateway/gatekeeper/service.go @@ -15,6 +15,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -76,8 +77,9 @@ func (g *Gatekeeper) service(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClas ports = append(ports, corev1.ServicePort{ Name: string(listener.Name), // only TCP-based services are supported for now - Protocol: corev1.ProtocolTCP, - Port: int32(listener.Port), + Protocol: corev1.ProtocolTCP, + Port: int32(listener.Port), + TargetPort: intstr.FromInt(common.ToContainerPort(listener.Port, gcc.Spec.MapPrivilegedContainerPorts)), }) seenPorts[listener.Port] = struct{}{} diff --git a/control-plane/api/v1alpha1/api_gateway_types.go b/control-plane/api/v1alpha1/api_gateway_types.go index 7f0b958701..c06ac3825f 100644 --- a/control-plane/api/v1alpha1/api_gateway_types.go +++ b/control-plane/api/v1alpha1/api_gateway_types.go @@ -62,6 +62,9 @@ type GatewayClassConfigSpec struct { // The name of the OpenShift SecurityContextConstraints resource for this gateway class to use. OpenshiftSCCName string `json:"openshiftSCCName,omitempty"` + + // The value to add to privileged ports ( ports < 1024) for gateway containers + MapPrivilegedContainerPorts int32 `json:"mapPrivilegedContainerPorts,omitempty"` } // +k8s:deepcopy-gen=true diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 3ad3ff7f53..6deea27a26 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -73,6 +73,8 @@ type Command struct { flagOpenshiftSCCName string + flagMapPrivilegedContainerPorts int + k8sClient client.Client once sync.Once @@ -128,6 +130,10 @@ func (c *Command) init() { c.flags.StringVar(&c.flagOpenshiftSCCName, "openshift-scc-name", "", "Name of security context constraint to use for gateways on Openshift.", ) + c.flags.IntVar(&c.flagMapPrivilegedContainerPorts, "map-privileged-container-ports", 0, + "The value to add to privileged container ports (< 1024) to avoid requiring addition privileges for the "+ + "gateway container.", + ) c.k8s = &flags.K8SFlags{} flags.Merge(c.flags, c.k8s.Flags()) @@ -202,7 +208,8 @@ func (c *Command) Run(args []string) int { MaxInstances: nonZeroOrNil(c.flagDeploymentMaxInstances), MinInstances: nonZeroOrNil(c.flagDeploymentMinInstances), }, - OpenshiftSCCName: c.flagOpenshiftSCCName, + OpenshiftSCCName: c.flagOpenshiftSCCName, + MapPrivilegedContainerPorts: int32(c.flagMapPrivilegedContainerPorts), }, } From a1eb32bae371bb205de5d102ee6c9057bb90fe14 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Tue, 8 Aug 2023 09:48:18 -0500 Subject: [PATCH 318/592] Support restricted PSA enforcement part 2 (#2702) --- .../framework/connhelper/connect_helper.go | 8 +-- acceptance/framework/k8s/helpers.go | 1 + .../wan-federation/wan_federation_test.go | 50 +++++++++++-------- .../create-federation-secret-job.yaml | 1 + .../ingress-gateways-deployment.yaml | 2 + .../consul/templates/partition-init-job.yaml | 1 + .../templates/sync-catalog-deployment.yaml | 1 + .../terminating-gateways-deployment.yaml | 2 + 8 files changed, 42 insertions(+), 24 deletions(-) diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 2eb18c9dbb..314c0d853a 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -123,7 +123,7 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { logger.Log(t, "creating static-server and static-client deployments") - c.setupAppNamespace(t) + c.SetupAppNamespace(t) opts := c.KubectlOptsForApp(t) if c.Cfg.EnableCNI && c.Cfg.EnableOpenshift { @@ -170,10 +170,10 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { }) } -// setupAppNamespace creates a namespace where applications are deployed. This +// SetupAppNamespace creates a namespace where applications are deployed. This // does nothing if UseAppNamespace is not set. The app namespace is relevant // when testing with restricted PSA enforcement enabled. -func (c *ConnectHelper) setupAppNamespace(t *testing.T) { +func (c *ConnectHelper) SetupAppNamespace(t *testing.T) { if !c.UseAppNamespace { return } @@ -204,7 +204,7 @@ func (c *ConnectHelper) setupAppNamespace(t *testing.T) { func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { logger.Log(t, "creating resolver redirect") opts := c.KubectlOptsForApp(t) - c.setupAppNamespace(t) + c.SetupAppNamespace(t) kustomizeDir := "../fixtures/cases/resolver-redirect-virtualip" k8s.KubectlApplyK(t, opts, kustomizeDir) diff --git a/acceptance/framework/k8s/helpers.go b/acceptance/framework/k8s/helpers.go index 235d26d061..a6b4d1ca7c 100644 --- a/acceptance/framework/k8s/helpers.go +++ b/acceptance/framework/k8s/helpers.go @@ -139,6 +139,7 @@ func CopySecret(t *testing.T, sourceContext, destContext environment.TestContext secret.ResourceVersion = "" require.NoError(r, err) }) + secret.Namespace = destContext.KubectlOptions(t).Namespace _, err = destContext.KubernetesClient(t).CoreV1().Secrets(destContext.KubectlOptions(t).Namespace).Create(context.Background(), secret, metav1.CreateOptions{}) require.NoError(t, err) } diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index bae8e8e9da..8edc1f5d03 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -9,11 +9,11 @@ import ( "strconv" "testing" + "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -43,10 +43,6 @@ func TestWANFederation(t *testing.T) { env := suite.Environment() cfg := suite.Config() - if cfg.UseKind { - t.Skipf("skipping wan federation tests as they currently fail on Kind even though they work on other clouds.") - } - primaryContext := env.DefaultContext(t) secondaryContext := env.Context(t, 1) @@ -86,6 +82,7 @@ func TestWANFederation(t *testing.T) { federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) require.NoError(t, err) federationSecret.ResourceVersion = "" + federationSecret.Namespace = secondaryContext.KubectlOptions(t).Namespace _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) require.NoError(t, err) @@ -161,30 +158,43 @@ func TestWANFederation(t *testing.T) { k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), kustomizeDir) }) + primaryHelper := connhelper.ConnectHelper{ + Secure: c.secure, + ReleaseName: releaseName, + Ctx: primaryContext, + UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, + Cfg: cfg, + ConsulClient: primaryClient, + } + secondaryHelper := connhelper.ConnectHelper{ + Secure: c.secure, + ReleaseName: releaseName, + Ctx: secondaryContext, + UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, + Cfg: cfg, + ConsulClient: secondaryClient, + } + + // When restricted PSA enforcement is enabled on the Consul + // namespace, deploy the test apps to a different unrestricted + // namespace because they can't run in a restricted namespace. + // This creates the app namespace only if necessary. + primaryHelper.SetupAppNamespace(t) + secondaryHelper.SetupAppNamespace(t) + // Check that we can connect services over the mesh gateways logger.Log(t, "creating static-server in dc2") - k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryHelper.KubectlOptsForApp(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client in dc1") - k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + k8s.DeployKustomize(t, primaryHelper.KubectlOptsForApp(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") if c.secure { - logger.Log(t, "creating intention") - _, _, err = primaryClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server", - Sources: []*api.SourceIntention{ - { - Name: StaticClientName, - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) + primaryHelper.CreateIntention(t) } logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, primaryContext.KubectlOptions(t), StaticClientName, "http://localhost:1234") + k8s.CheckStaticServerConnectionSuccessful(t, primaryHelper.KubectlOptsForApp(t), StaticClientName, "http://localhost:1234") }) } } diff --git a/charts/consul/templates/create-federation-secret-job.yaml b/charts/consul/templates/create-federation-secret-job.yaml index bc3e0a988b..678a2af3ba 100644 --- a/charts/consul/templates/create-federation-secret-job.yaml +++ b/charts/consul/templates/create-federation-secret-job.yaml @@ -93,6 +93,7 @@ spec: containers: - name: create-federation-secret image: "{{ .Values.global.imageK8S }}" + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} env: - name: NAMESPACE valueFrom: diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index 328c06ee3e..c10f1549f6 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -175,6 +175,7 @@ spec: # ingress-gateway-init registers the ingress gateway service with Consul. - name: ingress-gateway-init image: {{ $root.Values.global.imageK8S }} + {{- include "consul.restrictedSecurityContext" $ | nindent 8 }} env: - name: NAMESPACE valueFrom: @@ -233,6 +234,7 @@ spec: containers: - name: ingress-gateway image: {{ $root.Values.global.imageConsulDataplane | quote }} + {{- include "consul.restrictedSecurityContext" $ | nindent 8 }} {{- if (default $defaults.resources .resources) }} resources: {{ toYaml (default $defaults.resources .resources) | nindent 10 }} {{- end }} diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index db73ef783b..9209f850c8 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -81,6 +81,7 @@ spec: containers: - name: partition-init-job image: {{ .Values.global.imageK8S }} + {{- include "consul.restrictedSecurityContext" . | nindent 10 }} env: {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index e88adea533..a8793ef6f6 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -77,6 +77,7 @@ spec: containers: - name: sync-catalog image: "{{ default .Values.global.imageK8S .Values.syncCatalog.image }}" + {{- include "consul.restrictedSecurityContext" . | nindent 8 }} env: {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} {{- if .Values.global.acls.manageSystemACLs }} diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index fdf2c17d05..9433e44bc9 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -160,6 +160,7 @@ spec: # terminating-gateway-init registers the terminating gateway service with Consul. - name: terminating-gateway-init image: {{ $root.Values.global.imageK8S }} + {{- include "consul.restrictedSecurityContext" $ | nindent 10 }} env: - name: NAMESPACE valueFrom: @@ -218,6 +219,7 @@ spec: containers: - name: terminating-gateway image: {{ $root.Values.global.imageConsulDataplane | quote }} + {{- include "consul.restrictedSecurityContext" $ | nindent 10 }} volumeMounts: - name: consul-service mountPath: /consul/service From f3d099cf52625058e8fa6dffd938e6c02fce0b37 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Wed, 9 Aug 2023 15:41:43 -0400 Subject: [PATCH 319/592] NET-4413 Implement translation + validation of TLS options (#2711) * Implement validation of TLS options * Use constants for annotation keys * Add changelog entry * Implement TLS options translation * Update changelog entry * Add unit test coverage for TLS option validation * Code review feedback --- .changelog/2711.txt | 3 + control-plane/api-gateway/binding/result.go | 6 +- .../api-gateway/binding/validation.go | 124 +++++++++++++++--- .../api-gateway/binding/validation_test.go | 41 ++++++ control-plane/api-gateway/common/constants.go | 5 + .../api-gateway/common/translation.go | 12 ++ .../api-gateway/common/translation_test.go | 22 ++++ 7 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 .changelog/2711.txt diff --git a/.changelog/2711.txt b/.changelog/2711.txt new file mode 100644 index 0000000000..abb0b7e4fb --- /dev/null +++ b/.changelog/2711.txt @@ -0,0 +1,3 @@ +```release-note:feature +api-gateway: translate and validate TLS configuration options, including min/max version and cipher suites, setting Gateway status appropriately +``` diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index 1953d4836c..e6c0760e7a 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -241,7 +241,11 @@ var ( // Below is where any custom generic listener validation errors should go. // We map anything under here to a custom ListenerConditionReason of Invalid on // an Accepted status type. - errListenerNoTLSPassthrough = errors.New("TLS passthrough is not supported") + errListenerNoTLSPassthrough = errors.New("TLS passthrough is not supported") + errListenerTLSCipherSuiteNotConfigurable = errors.New("tls_min_version does not allow tls_cipher_suites configuration") + errListenerUnsupportedTLSCipherSuite = errors.New("unsupported cipher suite in tls_cipher_suites") + errListenerUnsupportedTLSMaxVersion = errors.New("unsupported tls_max_version") + errListenerUnsupportedTLSMinVersion = errors.New("unsupported tls_min_version") // This custom listener validation error is used to differentiate between an errListenerPortUnavailable because of // direct port conflicts defined by the user (two listeners on the same port) vs a port conflict because we map diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index d5a97499ca..6029c10b24 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -41,6 +41,45 @@ var ( gwv1beta1.Kind("HTTPRoute"): {}, gwv1beta1.Kind("TCPRoute"): {}, } + + allSupportedTLSVersions = map[string]struct{}{ + "TLS_AUTO": {}, + "TLSv1_0": {}, + "TLSv1_1": {}, + "TLSv1_2": {}, + "TLSv1_3": {}, + } + + allTLSVersionsWithConfigurableCipherSuites = map[string]struct{}{ + // Remove "" and "TLS_AUTO" if Envoy ever sets TLS 1.3 as default minimum + "": {}, + "TLS_AUTO": {}, + "TLSv1_0": {}, + "TLSv1_1": {}, + "TLSv1_2": {}, + } + + allSupportedTLSCipherSuites = map[string]struct{}{ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": {}, + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": {}, + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": {}, + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": {}, + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": {}, + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": {}, + + // NOTE: the following cipher suites are currently supported by Envoy + // but have been identified as insecure and are pending removal + // https://github.com/envoyproxy/envoy/issues/5399 + "TLS_RSA_WITH_AES_128_GCM_SHA256": {}, + "TLS_RSA_WITH_AES_128_CBC_SHA": {}, + "TLS_RSA_WITH_AES_256_GCM_SHA384": {}, + "TLS_RSA_WITH_AES_256_CBC_SHA": {}, + // https://github.com/envoyproxy/envoy/issues/5400 + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": {}, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": {}, + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": {}, + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": {}, + } ) // validateRefs validates backend references for a route, determining whether or @@ -167,43 +206,92 @@ func (m mergedListeners) validateHostname(index int, listener gwv1beta1.Listener // validateTLS validates that the TLS configuration for a given listener is valid and that // the certificates that it references exist. func validateTLS(gateway gwv1beta1.Gateway, tls *gwv1beta1.GatewayTLSConfig, resources *common.ResourceMap) (error, error) { - namespace := gateway.Namespace - + // If there's no TLS, there's nothing to validate if tls == nil { return nil, nil } - var err error + // Validate the certificate references and then return any error + // alongside any TLS configuration error that we find below. + refsErr := validateCertificateRefs(gateway, tls.CertificateRefs, resources) + + if tls.Mode != nil && *tls.Mode == gwv1beta1.TLSModePassthrough { + return errListenerNoTLSPassthrough, refsErr + } + + if err := validateTLSOptions(tls.Options); err != nil { + return err, refsErr + } + + return nil, refsErr +} - for _, cert := range tls.CertificateRefs { - // break on the first error +func validateCertificateRefs(gateway gwv1beta1.Gateway, refs []gwv1beta1.SecretObjectReference, resources *common.ResourceMap) error { + for _, cert := range refs { + // Verify that the reference has a group and kind that we support if !common.NilOrEqual(cert.Group, "") || !common.NilOrEqual(cert.Kind, common.KindSecret) { - err = errListenerInvalidCertificateRef_NotSupported - break + return errListenerInvalidCertificateRef_NotSupported } + // Verify that the reference is within the namespace or, + // if cross-namespace, that it's allowed by a ReferenceGrant if !resources.GatewayCanReferenceSecret(gateway, cert) { - err = errRefNotPermitted - break + return errRefNotPermitted } - key := common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, namespace) + // Verify that the referenced resource actually exists + key := common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace) secret := resources.Certificate(key) - if secret == nil { - err = errListenerInvalidCertificateRef_NotFound - break + return errListenerInvalidCertificateRef_NotFound } - err = validateCertificateData(*secret) + // Verify that the referenced resource contains the data shape that we expect + if err := validateCertificateData(*secret); err != nil { + return err + } } - if tls.Mode != nil && *tls.Mode == gwv1beta1.TLSModePassthrough { - return errListenerNoTLSPassthrough, err + return nil +} + +func validateTLSOptions(options map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue) error { + if options == nil { + return nil + } + + tlsMinVersionValue := string(options[common.TLSMinVersionAnnotationKey]) + if tlsMinVersionValue != "" { + if _, supported := allSupportedTLSVersions[tlsMinVersionValue]; !supported { + return errListenerUnsupportedTLSMinVersion + } + } + + tlsMaxVersionValue := string(options[common.TLSMaxVersionAnnotationKey]) + if tlsMaxVersionValue != "" { + if _, supported := allSupportedTLSVersions[tlsMaxVersionValue]; !supported { + return errListenerUnsupportedTLSMaxVersion + } } - // TODO: validate tls options - return nil, err + tlsCipherSuitesValue := string(options[common.TLSCipherSuitesAnnotationKey]) + if tlsCipherSuitesValue != "" { + // If a minimum TLS version is configured, verify that it supports configuring cipher suites + if tlsMinVersionValue != "" { + if _, supported := allTLSVersionsWithConfigurableCipherSuites[tlsMinVersionValue]; !supported { + return errListenerTLSCipherSuiteNotConfigurable + } + } + + for _, tlsCipherSuiteValue := range strings.Split(tlsCipherSuitesValue, ",") { + tlsCipherSuite := strings.TrimSpace(tlsCipherSuiteValue) + if _, supported := allSupportedTLSCipherSuites[tlsCipherSuite]; !supported { + return errListenerUnsupportedTLSCipherSuite + } + } + } + + return nil } func validateCertificateData(secret corev1.Secret) error { diff --git a/control-plane/api-gateway/binding/validation_test.go b/control-plane/api-gateway/binding/validation_test.go index 10cdf851c3..1f2b143387 100644 --- a/control-plane/api-gateway/binding/validation_test.go +++ b/control-plane/api-gateway/binding/validation_test.go @@ -510,6 +510,47 @@ func TestValidateTLS(t *testing.T) { expectedResolvedRefsErr: nil, expectedAcceptedErr: nil, }, + "invalid cipher suite": { + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + tls: &gwv1beta1.GatewayTLSConfig{ + Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ + common.TLSCipherSuitesAnnotationKey: "invalid", + }, + }, + certificates: nil, + expectedAcceptedErr: errListenerUnsupportedTLSCipherSuite, + }, + "cipher suite not configurable": { + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + tls: &gwv1beta1.GatewayTLSConfig{ + Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ + common.TLSMinVersionAnnotationKey: "TLSv1_3", + common.TLSCipherSuitesAnnotationKey: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + }, + }, + certificates: nil, + expectedAcceptedErr: errListenerTLSCipherSuiteNotConfigurable, + }, + "invalid max version": { + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + tls: &gwv1beta1.GatewayTLSConfig{ + Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ + common.TLSMaxVersionAnnotationKey: "invalid", + }, + }, + certificates: nil, + expectedAcceptedErr: errListenerUnsupportedTLSMaxVersion, + }, + "invalid min version": { + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + tls: &gwv1beta1.GatewayTLSConfig{ + Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ + common.TLSMinVersionAnnotationKey: "invalid", + }, + }, + certificates: nil, + expectedAcceptedErr: errListenerUnsupportedTLSMinVersion, + }, } { t.Run(name, func(t *testing.T) { resources := common.NewResourceMap(common.ResourceTranslator{}, NewReferenceValidator(tt.grants), logrtest.NewTestLogger(t)) diff --git a/control-plane/api-gateway/common/constants.go b/control-plane/api-gateway/common/constants.go index c1ec0685a4..04701662b7 100644 --- a/control-plane/api-gateway/common/constants.go +++ b/control-plane/api-gateway/common/constants.go @@ -7,4 +7,9 @@ const ( GatewayClassControllerName = "consul.hashicorp.com/gateway-controller" AnnotationGatewayClassConfig = "consul.hashicorp.com/gateway-class-config" + + // The following annotation keys are used in the v1beta1.GatewayTLSConfig's Options on a v1beta1.Listener. + TLSCipherSuitesAnnotationKey = "api-gateway.consul.hashicorp.com/tls_cipher_suites" + TLSMaxVersionAnnotationKey = "api-gateway.consul.hashicorp.com/tls_max_version" + TLSMinVersionAnnotationKey = "api-gateway.consul.hashicorp.com/tls_min_version" ) diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index 2f66749c94..9303540e82 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -85,8 +85,17 @@ func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, list namespace := gateway.Namespace var certificates []api.ResourceReference + var cipherSuites []string + var maxVersion, minVersion string if listener.TLS != nil { + cipherSuitesVal := string(listener.TLS.Options[TLSCipherSuitesAnnotationKey]) + if cipherSuitesVal != "" { + cipherSuites = strings.Split(cipherSuitesVal, ",") + } + maxVersion = string(listener.TLS.Options[TLSMaxVersionAnnotationKey]) + minVersion = string(listener.TLS.Options[TLSMinVersionAnnotationKey]) + for _, ref := range listener.TLS.CertificateRefs { if !resources.GatewayCanReferenceSecret(gateway, ref) { return api.APIGatewayListener{}, false @@ -116,6 +125,9 @@ func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, list Protocol: listenerProtocolMap[strings.ToLower(string(listener.Protocol))], TLS: api.APIGatewayTLSConfiguration{ Certificates: certificates, + CipherSuites: cipherSuites, + MaxVersion: maxVersion, + MinVersion: minVersion, }, }, true } diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index daa89a698f..e8caad76ed 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -11,6 +11,7 @@ import ( "encoding/pem" "fmt" "math/big" + "strings" "testing" "time" @@ -134,6 +135,9 @@ func TestTranslator_ToAPIGateway(t *testing.T) { listenerOneCertK8sNamespace := "one-cert-ns" listenerOneCertConsulNamespace := "one-cert-ns" listenerOneCert := generateTestCertificate(t, "one-cert-ns", "one-cert") + listenerOneMaxVersion := "TLSv1_2" + listenerOneMinVersion := "TLSv1_3" + listenerOneCipherSuites := []string{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"} // listener one status listenerOneLastTransmissionTime := time.Now() @@ -157,6 +161,7 @@ func TestTranslator_ToAPIGateway(t *testing.T) { annotations map[string]string expectedGWName string listenerOneK8sCertRefs []gwv1beta1.SecretObjectReference + listenerOneTLSOptions map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue }{ "gw name": { annotations: make(map[string]string), @@ -167,6 +172,11 @@ func TestTranslator_ToAPIGateway(t *testing.T) { Namespace: PointerTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), }, }, + listenerOneTLSOptions: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ + TLSMaxVersionAnnotationKey: gwv1beta1.AnnotationValue(listenerOneMaxVersion), + TLSMinVersionAnnotationKey: gwv1beta1.AnnotationValue(listenerOneMinVersion), + TLSCipherSuitesAnnotationKey: gwv1beta1.AnnotationValue(strings.Join(listenerOneCipherSuites, ",")), + }, }, "when k8s has certs that are not referenced in consul": { annotations: make(map[string]string), @@ -181,6 +191,11 @@ func TestTranslator_ToAPIGateway(t *testing.T) { Namespace: PointerTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), }, }, + listenerOneTLSOptions: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ + TLSMaxVersionAnnotationKey: gwv1beta1.AnnotationValue(listenerOneMaxVersion), + TLSMinVersionAnnotationKey: gwv1beta1.AnnotationValue(listenerOneMinVersion), + TLSCipherSuitesAnnotationKey: gwv1beta1.AnnotationValue(strings.Join(listenerOneCipherSuites, ",")), + }, }, } @@ -207,6 +222,7 @@ func TestTranslator_ToAPIGateway(t *testing.T) { Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), TLS: &gwv1beta1.GatewayTLSConfig{ CertificateRefs: tc.listenerOneK8sCertRefs, + Options: tc.listenerOneTLSOptions, }, }, { @@ -288,6 +304,9 @@ func TestTranslator_ToAPIGateway(t *testing.T) { Namespace: listenerOneCertConsulNamespace, }, }, + CipherSuites: listenerOneCipherSuites, + MaxVersion: listenerOneMaxVersion, + MinVersion: listenerOneMinVersion, }, }, { @@ -303,6 +322,9 @@ func TestTranslator_ToAPIGateway(t *testing.T) { Namespace: listenerTwoCertConsulNamespace, }, }, + CipherSuites: nil, + MaxVersion: "", + MinVersion: "", }, }, }, From a287fce551affef7d358394032e8997f79220c8b Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 9 Aug 2023 16:03:14 -0400 Subject: [PATCH 320/592] NET-4993 JWT auth basic acceptance test (#2706) * JWT auth basic acceptance test * Update to run only in enterprise mode, update comment to be correct * Remove usage of `testing.t` in retry block * Fixed last `t` in retry block in tests * Update acceptance/tests/api-gateway/api_gateway_test.go Co-authored-by: Nathan Coleman * Update acceptance/tests/api-gateway/api_gateway_test.go Co-authored-by: Nathan Coleman * Updating filenames for gw jwt cases and adding message about why this test is skipped --------- Co-authored-by: Nathan Coleman --- .../tests/api-gateway/api_gateway_test.go | 253 +++++++++++++++++- .../api-gateways/jwt-auth/api-gateway.yaml | 37 +++ .../api-gateways/jwt-auth/gateway-policy.yaml | 24 ++ .../api-gateways/jwt-auth/httproute-auth.yaml | 32 +++ .../api-gateways/jwt-auth/httproute.yaml | 19 ++ .../api-gateways/jwt-auth/jwt-provider.yaml | 9 + .../jwt-auth/jwt-route-filter.yaml | 12 + .../api-gateways/jwt-auth/kustomization.yaml | 12 + 8 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index 721bbf2527..df4a097b7e 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -126,7 +126,7 @@ func TestAPIGateway_Basic(t *testing.T) { // On startup, the controller can take upwards of 1m to perform // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 1m timeout here). + // the reconcile loop to run (hence the timeout here). var gatewayAddress string counter := &retry.Counter{Count: 120, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { @@ -288,6 +288,257 @@ func TestAPIGateway_Basic(t *testing.T) { } } +func TestAPIGateway_JWTAuth_Basic(t *testing.T) { + t.Skip("skipping this test until GW JWT auth is complete") + ctx := suite.Environment().DefaultContext(t) + cfg := suite.Config() + + if !cfg.EnableEnterprise { + t.Skipf("skipping this test because -enable-enterprise is not set") + } + + helmValues := map[string]string{ + "connectInject.enabled": "true", + "connectInject.consulNamespaces.mirroringK8S": "true", + "global.acls.manageSystemACLs": "true", // acls must be enabled for JWT auth to take place + "global.tls.enabled": "true", + "global.logLevel": "trace", + } + + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + + consulCluster.Create(t) + + // Override the default proxy config settings for this test + consulClient, _ := consulCluster.SetupConsulClient(t, true) + _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + }, nil) + require.NoError(t, err) + + logger.Log(t, "creating api-gateway resources") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth") + }) + + // Create certificate secret, we do this separately since + // applying the secret will make an invalid certificate that breaks other tests + logger.Log(t, "creating certificate secret") + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/bases/api-gateway/certificate.yaml") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/bases/api-gateway/certificate.yaml") + }) + + // patch certificate with data + logger.Log(t, "patching certificate secret with generated data") + certificate := generateCertificate(t, nil, "gateway.test.local") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") + + // We use the static-client pod so that we can make calls to the api gateway + // via kubectl exec without needing a route into the cluster from the test machine. + logger.Log(t, "creating static-client pod") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") + + k8s.RunKubectl(t, ctx.KubectlOptions(t), "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", "static-server")) + // Grab a kubernetes client so that we can verify binding + // behavior prior to issuing requests through the gateway. + k8sClient := ctx.ControllerRuntimeClient(t) + + // On startup, the controller can take upwards of 1m to perform + // leader election so we may need to wait a long time for + // the reconcile loop to run (hence the 2m timeout here). + var ( + gatewayAddress string + gatewayClass gwv1beta1.GatewayClass + httpRoute gwv1beta1.HTTPRoute + httpRouteAuth gwv1beta1.HTTPRoute + ) + + counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} + retry.RunWith(counter, t, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) + require.NoError(r, err) + + // check our finalizers + require.Len(r, gateway.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, gateway.Finalizers[0]) + + // check our statuses + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) + require.Len(r, gateway.Status.Listeners, 4) + + require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + require.EqualValues(r, 1, gateway.Status.Listeners[1].AttachedRoutes) + checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) + checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + + // check that we have an address to use + require.Len(r, gateway.Status.Addresses, 1) + // now we know we have an address, set it so we can use it + gatewayAddress = gateway.Status.Addresses[0].Value + + // gateway class checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway-class"}, &gatewayClass) + require.NoError(r, err) + + // check our finalizers + require.Len(r, gatewayClass.Finalizers, 1) + require.EqualValues(r, gatewayClassFinalizer, gatewayClass.Finalizers[0]) + + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route", Namespace: "default"}, &httpRoute) + require.NoError(r, err) + + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth", Namespace: "default"}, &httpRouteAuth) + require.NoError(r, err) + + // check our finalizers + require.Len(r, httpRoute.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, httpRoute.Finalizers[0]) + + // check parent status + require.Len(r, httpRoute.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRoute.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRoute.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + + // check our finalizers + require.Len(r, httpRouteAuth.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, httpRouteAuth.Finalizers[0]) + + // check parent status + require.Len(r, httpRouteAuth.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRouteAuth.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRouteAuth.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + }) + + // check that the Consul entries were created + entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) + require.NoError(t, err) + gateway := entry.(*api.APIGatewayConfigEntry) + + entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) + require.NoError(t, err) + consulHTTPRoute := entry.(*api.HTTPRouteConfigEntry) + + entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route-auth", nil) + require.NoError(t, err) + consulHTTPRouteAuth := entry.(*api.HTTPRouteConfigEntry) + + // now check the gateway status conditions + checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) + + // and the route status conditions + checkConsulStatusCondition(t, consulHTTPRoute.Status.Conditions, trueConsulCondition("Bound", "Bound")) + checkConsulStatusCondition(t, consulHTTPRouteAuth.Status.Conditions, trueConsulCondition("Bound", "Bound")) + + // finally we check that we can actually route to the service(s) via the gateway + k8sOptions := ctx.KubectlOptions(t) + targetHTTPAddress := fmt.Sprintf("http://%s/v1", gatewayAddress) + targetHTTPAddressAdmin := fmt.Sprintf("http://%s:8080/admin", gatewayAddress) + targetHTTPAddressPet := fmt.Sprintf("http://%s:8080/pet", gatewayAddress) + // valid JWT token with role of "doctor" + doctorToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJkb2N0b3IifQ.FfgpzjMf8Evh6K-fJ1cLXklfIXOm-vojVbWlPPbGVFtzxZ9hxMxoyAY_G8i36SfGrpUlp-RJ6ohMvprMrEgyRgbenu7u5kkm5iGHW-zpMus4izXRxPELBcpWOGF105HIssT2NYRstXieNR8EVzvGfLdvR0GW8ttEERgseqGvuAfdb4-aNYsysGwUUHbsZjazA6H1rZmWqHdCLOJ2ZwFsIdckO9CadnkyTILpcPUmLYyUVJdtlLGOySb0GG8c_dPML_IR5jSXCSUZt6S2JBNBNBdqukrlqpA-fIaaWft0dbWVMhv8DqPC8znult8dKvLZ1qXeU0itsqqJUyE16ihJjw" + // valid JWT token with role of "pet" + petToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJwZXQifQ.l94rJayGGTMB426HwEw5ipSjaIHjm-UWDHiBAlB_Slmi814AxAfl_0AdRwSz67UDnkoygKbvPpR5xUB03JCXNshLZuKLegWsBeQg_OJYvZGmFagl5NglBFvH7Jbta4e1eQoAxZI6Xyy1jHbu7jFBjQPVnK8EaRvWoW8Pe8a8rp_5xhub0pomhvRF6Pm5kAS4cMnxvqpVc5Oo5nO7ws_SmoNnbt2Ok14k23Zx5E2EWmGStOfbgFsdbhVbepB2DMzqv1j8jvBbwa_OxCwc_7pEOthOOxRV6L3ZjgbRSB4GumlXAOCBYXD1cRLgrMSrWB1GkefAKu8PV0Ho1px6sI9Evg" + + // check that intentions keep our connection from happening + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddress) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressAdmin) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressAdmin) + + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressPet) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressPet) + + // Now we create the allow intention. + _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: "static-server", + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Action: api.IntentionActionAllow, + }, + }, + }, nil) + require.NoError(t, err) + + _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: "static-server-protected", + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Action: api.IntentionActionAllow, + }, + }, + }, nil) + require.NoError(t, err) + + // Test that we can make a call to the api gateway + logger.Log(t, "trying calls to api gateway http") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) + + // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has an issuer of "local" and a "role" of "doctor" + // we can see that: + // * the "iss" verification in the gateway override takes precedence over the "iss" verification in the route filter + // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /admin should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /admin should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressAdmin) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /admin should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressAdmin) + + // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has an issuer of "local" and a "role" of "pet" + // the route does not define + // we can see that: + // * the "iss" verification in the gateway override takes precedence over the "iss" verification in the route filter + // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /pet should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /pet should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressPet) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /pet should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressPet) +} + func checkStatusCondition(t require.TestingT, conditions []metav1.Condition, toCheck metav1.Condition) { for _, c := range conditions { if c.Type == toCheck.Type { diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml new file mode 100644 index 0000000000..bef7e96f12 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml @@ -0,0 +1,37 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: gateway +spec: + gatewayClassName: gateway-class + listeners: + - protocol: HTTP + port: 8080 + name: http-auth + allowedRoutes: + namespaces: + from: "All" + - protocol: HTTP + port: 80 + name: http + allowedRoutes: + namespaces: + from: "All" + - protocol: TCP + port: 81 + name: tcp + allowedRoutes: + namespaces: + from: "All" + - protocol: HTTPS + port: 443 + name: https + tls: + certificateRefs: + - name: "certificate" + allowedRoutes: + namespaces: + from: "All" diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml new file mode 100644 index 0000000000..8e47d75062 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml @@ -0,0 +1,24 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ConsulGatewayPolicy +metadata: + name: my-policy +spec: + targetRef: + name: gateway + kind: Gateway + group: gateway.networking.kuberenetes.io + sectionName: http + override: + Providers: + - Provider: "local" + VerifyClaims: + - Path: + - "iss" + Value: "local" + default: + Providers: + - Provider: "local" + VerifyClaims: + - Path: + - "iss" + Value: "local" diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml new file mode 100644 index 0000000000..4963277c55 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml @@ -0,0 +1,32 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route-auth +spec: + parentRefs: + - name: gateway + sectionName: http-auth + rules: + - matches: + - path: + type: PathPrefix + value: "/admin" + backendRefs: + - name: static-server + port: 80 + filters: + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: HTTPRouteAuthFilter + name: route-jwt-auth-filter + - matches: + - path: + type: PathPrefix + value: "/pet" + backendRefs: + - name: static-server + port: 80 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml new file mode 100644 index 0000000000..52e206a91e --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml @@ -0,0 +1,19 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route +spec: + parentRefs: + - name: gateway + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: "/v1" + backendRefs: + - name: static-server + port: 80 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml new file mode 100644 index 0000000000..37eb034d3c --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml @@ -0,0 +1,9 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: JWTProvider +metadata: + name: local +spec: + issuer: local + jsonWebKeySet: + local: + jwks: "ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicCI6ICI5TTlWSVhJR0hpR3FlTnhseEJ2V0xFV09oUFh3dXhXZUpod01uM3dGdG9STEtfZmF6VWxjWEc1cUViLTdpMXo3VmlPUWVZRnh6WUZYTS1pbVU3OVFRa1dTVUVSazR2dHZuc2R5UnpUSnVPc3A0ZUhuWFVMSHJPOU51NkJ5bC1VeVprMzFvSnFGeGllM0pHQXlRLUM2OVF2NVFkVjFZV0hfVDkyTzk4d1hYZGMiLAogICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICJxIjogInFIVnZBb3h0ckgxUTVza25veXNMMkhvbC1ubnU3ZlM3Mjg4clRGdE9jeG9Jb29nWXBKVTljemxwcjctSlo2bjc0TUViVHBBMHRkSUR5TEtQQ0xIN3JKTFRrZzBDZVZNQWpmY01zdkRUcWdFOHNBWE42bzd2ZjYya2hwcExYOHVCU3JxSHkyV1JhZXJsbDROU09hcmRGSkQ2MWhHSVF2cEpXRk4xazFTV3pWcyIsCiAgICAgICAgICAgICJkIjogIlp3elJsVklRZkg5ekZ6d1hOZ2hEMHhkZVctalBCbmRkWnJNZ0wwQ2JjeXZZYlg2X1c0ajlhM1dmYWpobmI2bTFILW9CWjRMczVmNXNRVTB2ZFJ2ZG1laFItUG43aWNRcUdURFNKUTYtdWVtNm15UVRWaEo2UmZiM0lINVJ2VDJTOXUzcVFDZWFadWN3aXFoZ1RCbFhnOWFfV0pwVHJYNFhPQ3JCR1ZsTng3Z2JETVJOamNEN0FnRkZ3S2p2TEZVdDRLTkZmdEJqaFF0TDFLQ2VwblNmamtvRm1RUTVlX3RSS2ozX2U1V3pNSkJkekpQejNkR2YxZEk3OF9wYmJFbmFMcWhqNWg0WUx2UU5JUUhVcURYSGx4ZDc1Qlh3aFJReE1nUDRfd1EwTFk2cVRKNGFDa2Q0RDJBTUtqMzJqeVFiVTRKTE9jQjFNMnZBRWFyc2NTU3l0USIsCiAgICAgICAgICAgICJlIjogIkFRQUIiLAogICAgICAgICAgICAidXNlIjogInNpZyIsCiAgICAgICAgICAgICJraWQiOiAiQy1FMW5DandnQkMtUHVHTTJPNDY3RlJEaEt4OEFrVmN0SVNBYm8zcmlldyIsCiAgICAgICAgICAgICJxaSI6ICJ0N2VOQjhQV21xVHdKREZLQlZKZExrZnJJT2drMFJ4MnREODBGNHB5cjhmNzRuNGlVWXFmWG1haVZtbGx2c2FlT3JlNHlIczQ4UE45NVZsZlVvS3Z6ZEJFaDNZTDFINGZTOGlYYXNzNGJiVnVuWHR4U0hMZFFPYUNZYUplSmhBbGMyUWQ4elR0NFFQWk9yRWVWLVJTYU0tN095ekkwUWtSSF9tcmk1YmRrOXMiLAogICAgICAgICAgICAiZHAiOiAiYnBLckQtVXhrRENDal81MFZLU0NFeE1Ec1Zob2VBZm1tNjMxb1o5aDhUTkZ4TUU1YVptbUJ2VzBJUG9wMm1PUF9qTW9FVWxfUG1RYUlBOEgtVEdqTFp2QTMxSlZBeFN3TU5aQzdwaVFPRjYzVnhneTZUTzlmb1hENVdndC1oLUNxU1N6T2V3eFdmUWNTMmpMcTA3NUFxOTYwTnA2SHhjbE8weUdRN1JDSlpjIiwKICAgICAgICAgICAgImFsZyI6ICJQUzI1NiIsCiAgICAgICAgICAgICJkcSI6ICJpdVZveGwwckFKSEM1c2JzbTZpZWQ3c2ZIVXIwS2Rja0hiVFBLb0lPU1BFcU5YaXBlT3BrWkdEdU55NWlDTXNyRnNHaDFrRW9kTkhZdE40ay1USm5KSDliV296SGdXbGloNnN2R1V0Zi1raFMxWC16ckxaMTJudzlyNDRBbjllWG54bjFaVXMxZm5OakltM3dtZ083algyTWxIeVlNVUZVd0RMd09xNEFPUWsiLAogICAgICAgICAgICAibiI6ICJvUmhjeUREdmp3NFZ4SHRRNTZhRDlNSmRTaWhWSk1nTHd1b2FCQVhhc0RjVDNEWVZjcENlVGxDMVBPdzdPNW1Ec2ZSWVFtcGpoendyRDVZWU8yeDE4REl4czdyNTNJdFMxRy1ybnQxQ1diVE9fUzFJT01DR2xxYzh5VWJnLUhSUkRETXQyb2V3TjJoRGtxYlBKVFJNbXpjRkpNMHRpTm1RZVVMcWViZEVYaWVUblJMT1BkMWg2ZmJycVNLS01mSXlIbGZ1WXFQc1VWSEdkMVBESGljZ3NMazFtZDhtYTNIS1hWM0hJdzZrdUV6R0hQb1gxNHo4YWF6RFFZWndUR3ZxVGlPLUdRUlVDZUJueVo4bVhyWnRmSjNqVk83UUhXcEx3MlM1VDVwVTRwcE0xQXppWTFxUDVfY3ZpOTNZT2Zrb09PalRTX3V3RENZWGFxWjB5bTJHYlEiCiAgICAgICAgfQogICAgXQp9Cg==" diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml new file mode 100644 index 0000000000..e0a3128bed --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml @@ -0,0 +1,12 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: HTTPRouteAuthFilter +metadata: + name: example-route-jwt-filter +spec: + type: JWT + JWTProviders: + - Provider: "local" + VerifyClaims: + - Path: + - "role" + Value: "doctor" diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml new file mode 100644 index 0000000000..3dc38a090c --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/api-gateway + - ../../static-server-inject + - ./httproute.yaml + - ./jwt-provider.yaml + +patchesStrategicMerge: + - httproute-no-auth.yaml + - api-gateway.yaml From a86533b117bbda4711bbcb0947aab86a45e8f230 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Thu, 10 Aug 2023 11:28:50 -0400 Subject: [PATCH 321/592] [NET-5217] Apply K8s node locality to services and sidecars (#2748) Apply K8s node locality to services and sidecars Locality-aware routing is based on proxy locality rather than the proxied service. Ensure we propagate locality to both when registering services. --- .changelog/2748.txt | 3 +++ .../controllers/endpoints/endpoints_controller.go | 2 ++ .../controllers/endpoints/endpoints_controller_test.go | 5 +++++ 3 files changed, 10 insertions(+) create mode 100644 .changelog/2748.txt diff --git a/.changelog/2748.txt b/.changelog/2748.txt new file mode 100644 index 0000000000..2a8c922d13 --- /dev/null +++ b/.changelog/2748.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: Set locality on sidecar proxies in addition to services when registering with connect-inject. +``` diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index cdf56b187f..3f139c2662 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -535,6 +535,8 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Namespace: consulNS, Proxy: proxyConfig, Tags: tags, + // Sidecar locality (not proxied service locality) is used for locality-aware routing. + Locality: locality, } // A user can enable/disable tproxy for an entire namespace. diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 477be49e9f..2cec69dd0a 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -2021,6 +2021,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { "envoy_telemetry_collector_bind_socket_dir": "/consul/connect-inject", }, }, + ServiceLocality: &api.Locality{ + Region: "us-west-1", + Zone: "us-west-1a", + }, ServiceMeta: map[string]string{ "name": "abc", "version": "2", @@ -2225,6 +2229,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { require.Equal(t, tt.expectedProxySvcInstances[i].ServicePort, instance.ServicePort) require.Equal(t, tt.expectedProxySvcInstances[i].ServiceMeta, instance.ServiceMeta) require.Equal(t, tt.expectedProxySvcInstances[i].ServiceTags, instance.ServiceTags) + require.Equal(t, tt.expectedProxySvcInstances[i].ServiceLocality, instance.ServiceLocality) if tt.nodeMeta != nil { require.Equal(t, tt.expectedProxySvcInstances[i].NodeMeta, instance.NodeMeta) } From 0100fa4f066a184de8ae11976ef70b5afa85fe39 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Fri, 11 Aug 2023 10:46:44 -0400 Subject: [PATCH 322/592] Adds changelog for release of 1.1.4 (#2754) --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d60f91b67..aa47db462e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +## 1.1.4 (Aug 10, 2023) + +SECURITY: + +* Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. + This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2642](https://github.com/hashicorp/consul-k8s/issues/2642)] +* Upgrade to use Go 1.20.7 and `x/net` 0.13.0. + This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) + and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2710](https://github.com/hashicorp/consul-k8s/issues/2710)] + +IMPROVEMENTS: + +* Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields +1. `global.acls.logLevel` +2. `global.tls.logLevel` +3. `global.federation.logLevel` +4. `global.gossipEncryption.logLevel` +5. `server.logLevel` +6. `client.logLevel` +7. `meshGateway.logLevel` +8. `ingressGateways.logLevel` +9. `terminatingGateways.logLevel` +10. `telemetryCollector.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] +* control-plane: increase timeout after login for ACL replication to 60 seconds [[GH-2656](https://github.com/hashicorp/consul-k8s/issues/2656)] +* helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. [[GH-2525](https://github.com/hashicorp/consul-k8s/issues/2525)] +* helm: do not set container securityContexts by default on OpenShift < 4.11 [[GH-2678](https://github.com/hashicorp/consul-k8s/issues/2678)] +* helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled [[GH-2572](https://github.com/hashicorp/consul-k8s/issues/2572)] + +BUG FIXES: + +* control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] +* helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] +* helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] + ## 0.49.8 (July 12, 2023) IMPROVEMENTS: From 6e98cf90d890d74371214fc5b52628e4bfe96cbc Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 11 Aug 2023 14:29:29 -0400 Subject: [PATCH 323/592] Set privileged to false unless on OpenShift without CNI (#2755) * Set privileged to false unless on OpenShift without CNI --- .changelog/2755.txt | 3 + .../connect-inject/webhook/container_init.go | 10 +++- .../webhook/container_init_test.go | 57 +++++++++++++++---- 3 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 .changelog/2755.txt diff --git a/.changelog/2755.txt b/.changelog/2755.txt new file mode 100644 index 0000000000..1d8cf20360 --- /dev/null +++ b/.changelog/2755.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: When using transparent proxy or CNI, reduced required permissions by setting privileged to false. Privileged must be true when using OpenShift without CNI. +``` diff --git a/control-plane/connect-inject/webhook/container_init.go b/control-plane/connect-inject/webhook/container_init.go index f180de88a3..88962f771e 100644 --- a/control-plane/connect-inject/webhook/container_init.go +++ b/control-plane/connect-inject/webhook/container_init.go @@ -223,6 +223,12 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, }) } + // OpenShift without CNI is the only environment where privileged must be true. + privileged := false + if w.EnableOpenShift && !w.EnableCNI { + privileged = true + } + if tproxyEnabled { if !w.EnableCNI { // Set redirect traffic config for the container so that we can apply iptables rules. @@ -243,7 +249,7 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, RunAsGroup: pointer.Int64(rootUserAndGroupID), // RunAsNonRoot overrides any setting in the Pod so that we can still run as root here as required. RunAsNonRoot: pointer.Bool(false), - Privileged: pointer.Bool(true), + Privileged: pointer.Bool(privileged), Capabilities: &corev1.Capabilities{ Add: []corev1.Capability{netAdminCapability}, }, @@ -253,7 +259,7 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, RunAsUser: pointer.Int64(initContainersUserAndGroupID), RunAsGroup: pointer.Int64(initContainersUserAndGroupID), RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(false), + Privileged: pointer.Bool(privileged), Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, diff --git a/control-plane/connect-inject/webhook/container_init_test.go b/control-plane/connect-inject/webhook/container_init_test.go index fd89d7eba6..fa2a95dbf9 100644 --- a/control-plane/connect-inject/webhook/container_init_test.go +++ b/control-plane/connect-inject/webhook/container_init_test.go @@ -176,77 +176,104 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { annotations map[string]string expTproxyEnabled bool namespaceLabel map[string]string + openShiftEnabled bool }{ - "enabled globally, ns not set, annotation not provided, cni disabled": { + "enabled globally, ns not set, annotation not provided, cni disabled, openshift disabled": { true, false, nil, true, nil, + false, }, - "enabled globally, ns not set, annotation is false, cni disabled": { + "enabled globally, ns not set, annotation is false, cni disabled, openshift disabled": { true, false, map[string]string{constants.KeyTransparentProxy: "false"}, false, nil, + false, }, - "enabled globally, ns not set, annotation is true, cni disabled": { + "enabled globally, ns not set, annotation is true, cni disabled, openshift disabled": { true, false, map[string]string{constants.KeyTransparentProxy: "true"}, true, nil, + false, }, - "disabled globally, ns not set, annotation not provided, cni disabled": { + "disabled globally, ns not set, annotation not provided, cni disabled, openshift disabled": { false, false, nil, false, nil, + false, }, - "disabled globally, ns not set, annotation is false, cni disabled": { + "disabled globally, ns not set, annotation is false, cni disabled, openshift disabled": { false, false, map[string]string{constants.KeyTransparentProxy: "false"}, false, nil, + false, }, - "disabled globally, ns not set, annotation is true, cni disabled": { + "disabled globally, ns not set, annotation is true, cni disabled, openshift disabled": { false, false, map[string]string{constants.KeyTransparentProxy: "true"}, true, nil, + false, }, - "disabled globally, ns enabled, annotation not set, cni disabled": { + "disabled globally, ns enabled, annotation not set, cni disabled, openshift disabled": { false, false, nil, true, map[string]string{constants.KeyTransparentProxy: "true"}, + false, }, - "enabled globally, ns disabled, annotation not set, cni disabled": { + "enabled globally, ns disabled, annotation not set, cni disabled, openshift disabled": { true, false, nil, false, map[string]string{constants.KeyTransparentProxy: "false"}, + false, }, - "disabled globally, ns enabled, annotation not set, cni enabled": { + "disabled globally, ns enabled, annotation not set, cni enabled, openshift disabled": { false, true, nil, false, map[string]string{constants.KeyTransparentProxy: "true"}, + false, }, - "enabled globally, ns not set, annotation not set, cni enabled": { + "enabled globally, ns not set, annotation not set, cni enabled, openshift disabled": { + true, + true, + nil, + false, + nil, + false, + }, + "enabled globally, ns not set, annotation not set, cni enabled, openshift enabled": { true, true, nil, false, nil, + true, + }, + "enabled globally, ns not set, annotation not set, cni disabled, openshift enabled": { + true, + false, + nil, + true, + nil, + true, }, } for name, c := range cases { @@ -255,17 +282,23 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { EnableTransparentProxy: c.globalEnabled, EnableCNI: c.cniEnabled, ConsulConfig: &consul.Config{HTTPPort: 8500}, + EnableOpenShift: c.openShiftEnabled, } pod := minimal() pod.Annotations = c.annotations + privileged := false + if c.openShiftEnabled && !c.cniEnabled { + privileged = true + } + var expectedSecurityContext *corev1.SecurityContext if c.cniEnabled { expectedSecurityContext = &corev1.SecurityContext{ RunAsUser: pointer.Int64(initContainersUserAndGroupID), RunAsGroup: pointer.Int64(initContainersUserAndGroupID), RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(false), + Privileged: pointer.Bool(privileged), Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, @@ -275,7 +308,7 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { RunAsUser: pointer.Int64(0), RunAsGroup: pointer.Int64(0), RunAsNonRoot: pointer.Bool(false), - Privileged: pointer.Bool(true), + Privileged: pointer.Bool(privileged), Capabilities: &corev1.Capabilities{ Add: []corev1.Capability{netAdminCapability}, }, From b57b9369c61ee60b4f2a48a1bf0abf7a71cd8e8a Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 11 Aug 2023 16:21:09 -0400 Subject: [PATCH 324/592] Update consul-enterprise-version script to add -ent (#2756) --- .../build-support/scripts/consul-enterprise-version.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/control-plane/build-support/scripts/consul-enterprise-version.sh b/control-plane/build-support/scripts/consul-enterprise-version.sh index 6b48bb4678..37df85dfc5 100755 --- a/control-plane/build-support/scripts/consul-enterprise-version.sh +++ b/control-plane/build-support/scripts/consul-enterprise-version.sh @@ -4,8 +4,10 @@ FILE=$1 VERSION=$(yq .global.image $FILE) -if [[ !"${VERSION}" == *"consul:"* ]]; then +if [[ !"${VERSION}" == *"hashicorppreview/consul:"* ]]; then VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") +elif [[ !"${VERSION}" == *"hashicorp/consul:"* ]]; then + VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") fi echo "${VERSION}" From 1968df4fdc7cc54b782f450f1c5fb4f569aa9322 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 11 Aug 2023 15:35:14 -0700 Subject: [PATCH 325/592] Automate the k8s sameness tests add peering (#2725) * added fixtures * removed fixtures - intentions only gets added now if acls are enabled - payment-service-resolver is only for locality aware which isn't in scope for this PR * updated sameness tests to include peering - refactored with some helper functions for members (now TestClusters) - made names more uniform, tend more towards the cluster-01-a/cluster-02-a/etc. nomenclature * added 4 clusters to cni make target * disable proxy lifecycle --- Makefile | 4 + .../kustomization.yaml | 0 .../cluster-01-a-default-ns/sameness.yaml | 14 + .../kustomization.yaml | 5 + .../cluster-01-b-default-ns/sameness.yaml | 14 + .../kustomization.yaml | 5 + .../sameness.yaml | 7 +- .../kustomization.yaml | 5 + .../cluster-03-a-default-ns/sameness.yaml | 14 + .../sameness/override-ns/intentions.yaml | 15 - .../sameness/override-ns/kustomization.yaml | 2 - .../override-ns/payment-service-resolver.yaml | 16 - .../cluster-01-a-dialer/kustomization.yaml | 6 + .../peering-dialer-cluster-02-a.yaml | 13 + .../peering-dialer-cluster-03-a.yaml | 13 + .../cluster-01-b-dialer/kustomization.yaml | 6 + .../peering-dialer-cluster-02-a.yaml | 13 + .../peering-dialer-cluster-03-a.yaml | 13 + .../cluster-02-a-acceptor/kustomization.yaml | 6 + .../peering-acceptor-cluster-01-a.yaml | 13 + .../peering-acceptor-cluster-01-b.yaml | 13 + .../cluster-02-a-dialer/kustomization.yaml | 5 + .../peering-dialer-cluster-03-a.yaml | 13 + .../cluster-03-a-acceptor/kustomization.yaml | 7 + .../peering-acceptor-cluster-01-a.yaml | 13 + .../peering-acceptor-cluster-01-b.yaml | 13 + .../peering-acceptor-cluster-02-a.yaml | 13 + .../peering/{ => mesh}/kustomization.yaml | 0 .../sameness/peering/{ => mesh}/mesh.yaml | 0 .../cluster-01-a-acceptor/kustomization.yaml | 8 + .../sameness/cluster-01-a-acceptor/patch.yaml | 13 + .../cluster-01-b-acceptor/kustomization.yaml | 8 + .../sameness/cluster-01-b-acceptor/patch.yaml | 13 + .../cluster-02-a-acceptor/kustomization.yaml | 8 + .../sameness/cluster-02-a-acceptor/patch.yaml | 13 + .../cluster-03-a-acceptor/kustomization.yaml | 8 + .../sameness/cluster-03-a-acceptor/patch.yaml | 13 + .../ap1-partition/patch.yaml | 4 +- .../default-partition/patch.yaml | 4 +- .../kustomization.yaml | 0 .../{partition => ap1-partition}/patch.yaml | 0 .../kustomization.yaml | 0 .../{default => default-partition}/patch.yaml | 0 .../kustomization.yaml | 0 .../{default => dc1-default}/patch.yaml | 0 .../kustomization.yaml | 0 .../{partition => dc1-partition}/patch.yaml | 0 .../static-server/dc2/kustomization.yaml | 8 + .../sameness/static-server/dc2/patch.yaml | 23 + .../static-server/dc3/kustomization.yaml | 8 + .../sameness/static-server/dc3/patch.yaml | 23 + acceptance/tests/sameness/main_test.go | 2 +- acceptance/tests/sameness/sameness_test.go | 578 +++++++++++++----- 53 files changed, 812 insertions(+), 183 deletions(-) rename acceptance/tests/fixtures/bases/sameness/{default-ns => cluster-01-a-default-ns}/kustomization.yaml (100%) create mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/sameness.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/sameness.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/kustomization.yaml rename acceptance/tests/fixtures/bases/sameness/{default-ns => cluster-02-a-default-ns}/sameness.yaml (73%) create mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/sameness.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-02-a.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-03-a.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-02-a.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-03-a.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-a.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-b.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/peering-dialer-cluster-03-a.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-a.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-b.yaml create mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-02-a.yaml rename acceptance/tests/fixtures/bases/sameness/peering/{ => mesh}/kustomization.yaml (100%) rename acceptance/tests/fixtures/bases/sameness/peering/{ => mesh}/mesh.yaml (100%) create mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/patch.yaml rename acceptance/tests/fixtures/cases/sameness/static-client/{default => ap1-partition}/kustomization.yaml (100%) rename acceptance/tests/fixtures/cases/sameness/static-client/{partition => ap1-partition}/patch.yaml (100%) rename acceptance/tests/fixtures/cases/sameness/static-client/{partition => default-partition}/kustomization.yaml (100%) rename acceptance/tests/fixtures/cases/sameness/static-client/{default => default-partition}/patch.yaml (100%) rename acceptance/tests/fixtures/cases/sameness/static-server/{default => dc1-default}/kustomization.yaml (100%) rename acceptance/tests/fixtures/cases/sameness/static-server/{default => dc1-default}/patch.yaml (100%) rename acceptance/tests/fixtures/cases/sameness/static-server/{partition => dc1-partition}/kustomization.yaml (100%) rename acceptance/tests/fixtures/cases/sameness/static-server/{partition => dc1-partition}/patch.yaml (100%) create mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc2/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc3/patch.yaml diff --git a/Makefile b/Makefile index e9a02a01b3..866a37fbfc 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,10 @@ kind-cni: kind-delete make kind-cni-calico kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc2 --image $(KIND_NODE_IMAGE) make kind-cni-calico + kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc3 --image $(KIND_NODE_IMAGE) + make kind-cni-calico + kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc4 --image $(KIND_NODE_IMAGE) + make kind-cni-calico # Helper target for doing local acceptance testing kind: kind-delete diff --git a/acceptance/tests/fixtures/bases/sameness/default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/kustomization.yaml similarity index 100% rename from acceptance/tests/fixtures/bases/sameness/default-ns/kustomization.yaml rename to acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/kustomization.yaml diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/sameness.yaml new file mode 100644 index 0000000000..9c43bb505f --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/sameness.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: SamenessGroup +metadata: + name: group-01 +spec: + defaultForFailover: true + members: + - partition: default + - partition: ap1 + - peer: cluster-02-a + - peer: cluster-03-a \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/kustomization.yaml new file mode 100644 index 0000000000..3f9d23c28a --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - sameness.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/sameness.yaml new file mode 100644 index 0000000000..bf83338243 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/sameness.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: SamenessGroup +metadata: + name: group-01 +spec: + defaultForFailover: true + members: + - partition: ap1 + - partition: default + - peer: cluster-02-a + - peer: cluster-03-a \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/kustomization.yaml new file mode 100644 index 0000000000..3f9d23c28a --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - sameness.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/sameness.yaml similarity index 73% rename from acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml rename to acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/sameness.yaml index 4d27ed72ae..2ed466585b 100644 --- a/acceptance/tests/fixtures/bases/sameness/default-ns/sameness.yaml +++ b/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/sameness.yaml @@ -4,12 +4,11 @@ apiVersion: consul.hashicorp.com/v1alpha1 kind: SamenessGroup metadata: - name: mine + name: group-01 spec: + defaultForFailover: true members: - partition: default - - partition: ap1 - peer: cluster-01-a - peer: cluster-01-b - - peer: cluster-02-a - - peer: cluster-03-a + - peer: cluster-03-a \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/kustomization.yaml new file mode 100644 index 0000000000..3f9d23c28a --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - sameness.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/sameness.yaml new file mode 100644 index 0000000000..83a3c1e71a --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/sameness.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: SamenessGroup +metadata: + name: group-01 +spec: + defaultForFailover: true + members: + - partition: default + - peer: cluster-01-a + - peer: cluster-01-b + - peer: cluster-02-a \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml deleted file mode 100644 index ae075c85e4..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/intentions.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceIntentions -metadata: - name: static-server -spec: - destination: - name: static-server - sources: - - name: static-client - namespace: ns1 - samenessGroup: mine - action: allow diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml index adfd1c827b..0646179949 100644 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml @@ -2,6 +2,4 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - intentions.yaml - - payment-service-resolver.yaml - service-defaults.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml deleted file mode 100644 index 4257294c6b..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/payment-service-resolver.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server -spec: - connectTimeout: 15s - failover: - '*': - samenessGroup: mine - policy: - mode: order-by-locality - regions: - - us-west-2 diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/kustomization.yaml new file mode 100644 index 0000000000..cf214eac6c --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/kustomization.yaml @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - peering-dialer-cluster-02-a.yaml + - peering-dialer-cluster-03-a.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-02-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-02-a.yaml new file mode 100644 index 0000000000..d4c51553f3 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-02-a.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringDialer +metadata: + name: cluster-02-a +spec: + peer: + secret: + name: "cluster-02-a-cluster-01-a-peering-token" + key: "data" + backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-03-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-03-a.yaml new file mode 100644 index 0000000000..e6f9f9a6c9 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-03-a.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringDialer +metadata: + name: cluster-03-a +spec: + peer: + secret: + name: "cluster-03-a-cluster-01-a-peering-token" + key: "data" + backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/kustomization.yaml new file mode 100644 index 0000000000..cf214eac6c --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/kustomization.yaml @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - peering-dialer-cluster-02-a.yaml + - peering-dialer-cluster-03-a.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-02-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-02-a.yaml new file mode 100644 index 0000000000..8f0f7064df --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-02-a.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringDialer +metadata: + name: cluster-02-a +spec: + peer: + secret: + name: "cluster-02-a-cluster-01-b-peering-token" + key: "data" + backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-03-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-03-a.yaml new file mode 100644 index 0000000000..27cdd27ff8 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-03-a.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringDialer +metadata: + name: cluster-03-a +spec: + peer: + secret: + name: "cluster-03-a-cluster-01-b-peering-token" + key: "data" + backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/kustomization.yaml new file mode 100644 index 0000000000..4c485ee633 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/kustomization.yaml @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - peering-acceptor-cluster-01-a.yaml + - peering-acceptor-cluster-01-b.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-a.yaml new file mode 100644 index 0000000000..b20b61328f --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-a.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: cluster-01-a +spec: + peer: + secret: + name: "cluster-02-a-cluster-01-a-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-b.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-b.yaml new file mode 100644 index 0000000000..c2d5c21b37 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-b.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: cluster-01-b +spec: + peer: + secret: + name: "cluster-02-a-cluster-01-b-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/kustomization.yaml new file mode 100644 index 0000000000..c90eab30cc --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - peering-dialer-cluster-03-a.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/peering-dialer-cluster-03-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/peering-dialer-cluster-03-a.yaml new file mode 100644 index 0000000000..80518a04c2 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/peering-dialer-cluster-03-a.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringDialer +metadata: + name: cluster-03-a +spec: + peer: + secret: + name: "cluster-03-a-cluster-02-a-peering-token" + key: "data" + backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/kustomization.yaml new file mode 100644 index 0000000000..543a846805 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/kustomization.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - peering-acceptor-cluster-01-a.yaml + - peering-acceptor-cluster-01-b.yaml + - peering-acceptor-cluster-02-a.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-a.yaml new file mode 100644 index 0000000000..06c87e15a6 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-a.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: cluster-01-a +spec: + peer: + secret: + name: "cluster-03-a-cluster-01-a-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-b.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-b.yaml new file mode 100644 index 0000000000..0a835ecef5 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-b.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: cluster-01-b +spec: + peer: + secret: + name: "cluster-03-a-cluster-01-b-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-02-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-02-a.yaml new file mode 100644 index 0000000000..e60ea8b083 --- /dev/null +++ b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-02-a.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: cluster-02-a +spec: + peer: + secret: + name: "cluster-03-a-cluster-02-a-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/mesh/kustomization.yaml similarity index 100% rename from acceptance/tests/fixtures/bases/sameness/peering/kustomization.yaml rename to acceptance/tests/fixtures/bases/sameness/peering/mesh/kustomization.yaml diff --git a/acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml b/acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml similarity index 100% rename from acceptance/tests/fixtures/bases/sameness/peering/mesh.yaml rename to acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml new file mode 100644 index 0000000000..30ddacd76c --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/sameness/peering/acceptor + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/patch.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/patch.yaml new file mode 100644 index 0000000000..2746eeef2e --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/patch.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: acceptor +spec: + peer: + secret: + name: "cluster-01-a-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml new file mode 100644 index 0000000000..30ddacd76c --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/sameness/peering/acceptor + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/patch.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/patch.yaml new file mode 100644 index 0000000000..9ca48dad0c --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/patch.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: acceptor +spec: + peer: + secret: + name: "cluster-01-b-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml new file mode 100644 index 0000000000..30ddacd76c --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/sameness/peering/acceptor + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/patch.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/patch.yaml new file mode 100644 index 0000000000..4343992f8f --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/patch.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: acceptor +spec: + peer: + secret: + name: "cluster-02-a-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml new file mode 100644 index 0000000000..6a54cd6eab --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/sameness/peering/acceptor + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/patch.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/patch.yaml new file mode 100644 index 0000000000..1cd49b79d7 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/patch.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: acceptor +spec: + peer: + secret: + name: "cluster-03-a-peering-token" + key: "data" + backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml index d71e8211ba..22fa816fed 100644 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml @@ -10,7 +10,7 @@ spec: - name: static-server namespace: ns2 consumers: - - samenessGroup: mine + - samenessGroup: group-01 - name: mesh-gateway consumers: - - samenessGroup: mine + - samenessGroup: group-01 diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml index 9bb440637e..4dbacf99e1 100644 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml @@ -10,7 +10,7 @@ spec: - name: static-server namespace: ns2 consumers: - - samenessGroup: mine + - samenessGroup: group-01 - name: mesh-gateway consumers: - - samenessGroup: mine + - samenessGroup: group-01 diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml similarity index 100% rename from acceptance/tests/fixtures/cases/sameness/static-client/default/kustomization.yaml rename to acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/patch.yaml similarity index 100% rename from acceptance/tests/fixtures/cases/sameness/static-client/partition/patch.yaml rename to acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml similarity index 100% rename from acceptance/tests/fixtures/cases/sameness/static-client/partition/kustomization.yaml rename to acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/patch.yaml similarity index 100% rename from acceptance/tests/fixtures/cases/sameness/static-client/default/patch.yaml rename to acceptance/tests/fixtures/cases/sameness/static-client/default-partition/patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/default/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml similarity index 100% rename from acceptance/tests/fixtures/cases/sameness/static-server/default/kustomization.yaml rename to acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/default/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/patch.yaml similarity index 100% rename from acceptance/tests/fixtures/cases/sameness/static-server/default/patch.yaml rename to acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml similarity index 100% rename from acceptance/tests/fixtures/cases/sameness/static-server/partition/kustomization.yaml rename to acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/patch.yaml similarity index 100% rename from acceptance/tests/fixtures/cases/sameness/static-server/partition/patch.yaml rename to acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml new file mode 100644 index 0000000000..c15bfe7ba7 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/patch.yaml new file mode 100644 index 0000000000..07ac3b9aa9 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/patch.yaml @@ -0,0 +1,23 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine + args: + - -text="cluster-02-a" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml new file mode 100644 index 0000000000..c15bfe7ba7 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/patch.yaml new file mode 100644 index 0000000000..135e7b14fb --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/patch.yaml @@ -0,0 +1,23 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine + args: + - -text="cluster-03-a" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + serviceAccountName: static-server diff --git a/acceptance/tests/sameness/main_test.go b/acceptance/tests/sameness/main_test.go index 67e6ee42b7..ded943c6f0 100644 --- a/acceptance/tests/sameness/main_test.go +++ b/acceptance/tests/sameness/main_test.go @@ -23,6 +23,6 @@ func TestMain(m *testing.M) { } else { fmt.Println(fmt.Sprintf("Skipping sameness tests because either -enable-multi-cluster is "+ "not set, the number of clusters did not match the expected count of %d, or --useKind is false. "+ - "Sameness acceptance tests are currently only suopported on Kind clusters", expectedNumberOfClusters)) + "Sameness acceptance tests are currently only supported on Kind clusters", expectedNumberOfClusters)) } } diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index 3971ccf27a..629b886d95 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -7,7 +7,9 @@ import ( "context" "fmt" "strconv" + "strings" "testing" + "time" terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/config" @@ -24,23 +26,32 @@ import ( ) const ( - primaryDatacenterPartition = "ap1" - primaryServerDatacenter = "dc1" - peer1Datacenter = "dc2" - peer2Datacenter = "dc3" - staticClientNamespace = "ns1" - staticServerNamespace = "ns2" - - keyPrimaryServer = "server" - keyPartition = "partition" - keyPeer1 = "peer1" - keyPeer2 = "peer2" + cluster01Partition = "ap1" + cluster01Datacenter = "dc1" + cluster02Datacenter = "dc2" + cluster03Datacenter = "dc3" + staticClientNamespace = "ns1" + staticServerNamespace = "ns2" + + keyCluster01a = "cluster-01-a" + keyCluster01b = "cluster-01-b" + keyCluster02a = "cluster-02-a" + keyCluster03a = "cluster-03-a" + + staticServerName = "static-server" + staticClientName = "static-client" staticServerDeployment = "deploy/static-server" staticClientDeployment = "deploy/static-client" - primaryServerClusterName = "cluster-01-a" - partitionClusterName = "cluster-01-b" + peerName1a = keyCluster01a + peerName1b = keyCluster01b + peerName2a = keyCluster02a + peerName3a = keyCluster03a + + samenessGroupName = "group-01" + + retryTimeout = 5 * time.Minute ) func TestFailover_Connect(t *testing.T) { @@ -127,19 +138,19 @@ func TestFailover_Connect(t *testing.T) { +-------------------------------------------+ */ - members := map[string]*member{ - keyPrimaryServer: {context: env.DefaultContext(t), hasServer: true}, - keyPartition: {context: env.Context(t, 1), hasServer: false}, - keyPeer1: {context: env.Context(t, 2), hasServer: true}, - keyPeer2: {context: env.Context(t, 3), hasServer: true}, + testClusters := clusters{ + keyCluster01a: {name: peerName1a, context: env.DefaultContext(t), hasServer: true, acceptors: []string{peerName2a, peerName3a}}, + keyCluster01b: {name: peerName1b, context: env.Context(t, 1), partition: cluster01Partition, hasServer: false, acceptors: []string{peerName2a, peerName3a}}, + keyCluster02a: {name: peerName2a, context: env.Context(t, 2), hasServer: true, acceptors: []string{peerName3a}}, + keyCluster03a: {name: peerName3a, context: env.Context(t, 3), hasServer: true}, } // Setup Namespaces. - for _, v := range members { + for _, v := range testClusters { createNamespaces(t, cfg, v.context) } - // Create the Default Cluster. + // Create the cluster-01-a. commonHelmValues := map[string]string{ "global.peering.enabled": "true", @@ -150,7 +161,7 @@ func TestFailover_Connect(t *testing.T) { "global.adminPartitions.enabled": "true", - "global.logLevel": "debug", + "global.logLevel": "warn", "global.acls.manageSystemACLs": strconv.FormatBool(c.ACLsEnabled), @@ -161,10 +172,11 @@ func TestFailover_Connect(t *testing.T) { "meshGateway.replicas": "1", "dns.enabled": "true", + "connectInject.sidecarProxy.lifecycle.defaultEnabled": "false", } defaultPartitionHelmValues := map[string]string{ - "global.datacenter": primaryServerDatacenter, + "global.datacenter": cluster01Datacenter, } // On Kind, there are no load balancers but since all clusters @@ -180,39 +192,39 @@ func TestFailover_Connect(t *testing.T) { helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) releaseName := helpers.RandomName() - members[keyPrimaryServer].helmCluster = consul.NewHelmCluster(t, defaultPartitionHelmValues, members[keyPrimaryServer].context, cfg, releaseName) - members[keyPrimaryServer].helmCluster.Create(t) + testClusters[keyCluster01a].helmCluster = consul.NewHelmCluster(t, defaultPartitionHelmValues, testClusters[keyCluster01a].context, cfg, releaseName) + testClusters[keyCluster01a].helmCluster.Create(t) // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) - k8s.CopySecret(t, members[keyPrimaryServer].context, members[keyPartition].context, caCertSecretName) + k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, caCertSecretName) - // Create Secondary Partition Cluster which will apply the primary datacenter. + // Create Secondary Partition Cluster (cluster-01-b) which will apply the primary (dc1) datacenter. partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) if c.ACLsEnabled { logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) - k8s.CopySecret(t, members[keyPrimaryServer].context, members[keyPartition].context, partitionToken) + k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, partitionToken) } partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) - partitionSvcAddress := k8s.ServiceHost(t, cfg, members[keyPrimaryServer].context, partitionServiceName) + partitionSvcAddress := k8s.ServiceHost(t, cfg, testClusters[keyCluster01a].context, partitionServiceName) - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, members[keyPartition].context) + k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, testClusters[keyCluster01b].context) secondaryPartitionHelmValues := map[string]string{ "global.enabled": "false", - "global.datacenter": primaryServerDatacenter, + "global.datacenter": cluster01Datacenter, - "global.adminPartitions.name": primaryDatacenterPartition, + "global.adminPartitions.name": cluster01Partition, "global.tls.caCert.secretName": caCertSecretName, "global.tls.caCert.secretKey": "tls.crt", "externalServers.enabled": "true", "externalServers.hosts[0]": partitionSvcAddress, - "externalServers.tlsServerName": fmt.Sprintf("server.%s.consul", primaryServerDatacenter), + "externalServers.tlsServerName": fmt.Sprintf("server.%s.consul", cluster01Datacenter), "global.server.enabled": "false", } @@ -231,12 +243,12 @@ func TestFailover_Connect(t *testing.T) { } helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) - members[keyPartition].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, members[keyPartition].context, cfg, releaseName) - members[keyPartition].helmCluster.Create(t) + testClusters[keyCluster01b].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, testClusters[keyCluster01b].context, cfg, releaseName) + testClusters[keyCluster01b].helmCluster.Create(t) - // Create Peer 1 Cluster. + // Create cluster-02-a Cluster. PeerOneHelmValues := map[string]string{ - "global.datacenter": peer1Datacenter, + "global.datacenter": cluster02Datacenter, } if cfg.UseKind { @@ -246,12 +258,12 @@ func TestFailover_Connect(t *testing.T) { } helpers.MergeMaps(PeerOneHelmValues, commonHelmValues) - members[keyPeer1].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, members[keyPeer1].context, cfg, releaseName) - members[keyPeer1].helmCluster.Create(t) + testClusters[keyCluster02a].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, testClusters[keyCluster02a].context, cfg, releaseName) + testClusters[keyCluster02a].helmCluster.Create(t) - // Create Peer 2 Cluster. + // Create cluster-03-a Cluster. PeerTwoHelmValues := map[string]string{ - "global.datacenter": peer2Datacenter, + "global.datacenter": cluster03Datacenter, } if cfg.UseKind { @@ -261,138 +273,298 @@ func TestFailover_Connect(t *testing.T) { } helpers.MergeMaps(PeerTwoHelmValues, commonHelmValues) - members[keyPeer2].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, members[keyPeer2].context, cfg, releaseName) - members[keyPeer2].helmCluster.Create(t) + testClusters[keyCluster03a].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, testClusters[keyCluster03a].context, cfg, releaseName) + testClusters[keyCluster03a].helmCluster.Create(t) // Create a ProxyDefaults resource to configure services to use the mesh // gateways and set server and client opts. - for k, v := range members { + for k, v := range testClusters { logger.Logf(t, "applying resources on %s", v.context.KubectlOptions(t).ContextName) // Client will use the client namespace. - members[k].clientOpts = &terratestk8s.KubectlOptions{ + testClusters[k].clientOpts = &terratestk8s.KubectlOptions{ ContextName: v.context.KubectlOptions(t).ContextName, ConfigPath: v.context.KubectlOptions(t).ConfigPath, Namespace: staticClientNamespace, } // Server will use the server namespace. - members[k].serverOpts = &terratestk8s.KubectlOptions{ + testClusters[k].serverOpts = &terratestk8s.KubectlOptions{ ContextName: v.context.KubectlOptions(t).ContextName, ConfigPath: v.context.KubectlOptions(t).ConfigPath, Namespace: staticServerNamespace, } // Sameness Defaults need to be applied first so that the sameness group exists. - applyResources(t, cfg, "../fixtures/bases/mesh-gateway", members[k].context.KubectlOptions(t)) - applyResources(t, cfg, "../fixtures/bases/sameness/default-ns", members[k].context.KubectlOptions(t)) - applyResources(t, cfg, "../fixtures/bases/sameness/override-ns", members[k].serverOpts) + applyResources(t, cfg, "../fixtures/bases/mesh-gateway", v.context.KubectlOptions(t)) + applyResources(t, cfg, "../fixtures/bases/sameness/override-ns", v.serverOpts) // Only assign a client if the cluster is running a Consul server. if v.hasServer { - members[k].client, _ = members[k].helmCluster.SetupConsulClient(t, c.ACLsEnabled) + testClusters[k].client, _ = testClusters[k].helmCluster.SetupConsulClient(t, c.ACLsEnabled) } } - // TODO: Add further setup for peering, right now the rest of this test will only cover Partitions - // Create static server deployments. - logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, members[keyPrimaryServer].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/default") - k8s.DeployKustomize(t, members[keyPartition].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/partition") + // Assign the client default partition client to the partition + testClusters[keyCluster01b].client = testClusters[keyCluster01a].client - // Create static client deployments. - k8s.DeployKustomize(t, members[keyPrimaryServer].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-client/default") - k8s.DeployKustomize(t, members[keyPartition].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-client/partition") + // Apply Mesh resource to default partition and peers + for _, v := range testClusters { + if v.hasServer { + applyResources(t, cfg, "../fixtures/bases/sameness/peering/mesh", v.context.KubectlOptions(t)) + } + } - // Verify that both static-server and static-client have been injected and now have 2 containers in server cluster. - // Also get the server IP - for _, labelSelector := range []string{"app=static-server", "app=static-client"} { - podList, err := members[keyPrimaryServer].context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), - metav1.ListOptions{LabelSelector: labelSelector}) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - if labelSelector == "app=static-server" { - ip := &podList.Items[0].Status.PodIP - require.NotNil(t, ip) - logger.Logf(t, "default-static-server-ip: %s", *ip) - members[keyPrimaryServer].staticServerIP = ip + // Peering/Dialer relationship + /* + cluster-01-a cluster-02-a + Dialer -> 2a 1a -> acceptor + Dialer -> 3a 1b -> acceptor + Dialer -> 3a + + cluster-01-b cluster-03-a + Dialer -> 2a 1a -> acceptor + Dialer -> 3a 1b -> acceptor + 2a -> acceptor + */ + for _, v := range []*cluster{testClusters[keyCluster02a], testClusters[keyCluster03a]} { + logger.Logf(t, "creating acceptor on %s", v.name) + // Create an acceptor token on the cluster + applyResources(t, cfg, fmt.Sprintf("../fixtures/bases/sameness/peering/%s-acceptor", v.name), v.context.KubectlOptions(t)) + + // Copy secrets to the necessary peers to be used for dialing later + for _, vv := range testClusters { + if isAcceptor(v.name, vv.acceptors) { + acceptorSecretName := getPeeringAcceptorSecret(t, cfg, v, vv.name) + logger.Logf(t, "acceptor %s created on %s", acceptorSecretName, v.name) + + logger.Logf(t, "copying acceptor token %s from %s to %s", acceptorSecretName, v.name, vv.name) + copySecret(t, cfg, v.context, vv.context, acceptorSecretName) + } } + } - podList, err = members[keyPartition].context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), - metav1.ListOptions{LabelSelector: labelSelector}) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - if labelSelector == "app=static-server" { - ip := &podList.Items[0].Status.PodIP - require.NotNil(t, ip) - logger.Logf(t, "partition-static-server-ip: %s", *ip) - members[keyPartition].staticServerIP = ip + // Create the dialers + for _, v := range []*cluster{testClusters[keyCluster01a], testClusters[keyCluster01b], testClusters[keyCluster02a]} { + applyResources(t, cfg, fmt.Sprintf("../fixtures/bases/sameness/peering/%s-dialer", v.name), v.context.KubectlOptions(t)) + } + + // If ACLs are enabled, we need to create the intentions + if c.ACLsEnabled { + intention := &api.ServiceIntentionsConfigEntry{ + Name: staticServerName, + Kind: api.ServiceIntentions, + Namespace: staticServerNamespace, + Sources: []*api.SourceIntention{ + { + Name: staticClientName, + Namespace: staticClientNamespace, + SamenessGroup: samenessGroupName, + Action: api.IntentionActionAllow, + }, + }, + } + + for _, v := range testClusters { + logger.Logf(t, "creating intentions on server %s", v.name) + _, _, err := v.client.ConfigEntries().Set(intention, &api.WriteOptions{Partition: v.partition}) + require.NoError(t, err) } } logger.Log(t, "creating exported services") - applyResources(t, cfg, "../fixtures/cases/sameness/exported-services/default-partition", members[keyPrimaryServer].context.KubectlOptions(t)) - applyResources(t, cfg, "../fixtures/cases/sameness/exported-services/ap1-partition", members[keyPartition].context.KubectlOptions(t)) + for _, v := range testClusters { + if v.hasServer { + applyResources(t, cfg, "../fixtures/cases/sameness/exported-services/default-partition", v.context.KubectlOptions(t)) + } else { + applyResources(t, cfg, "../fixtures/cases/sameness/exported-services/ap1-partition", v.context.KubectlOptions(t)) + } + } + + // Create sameness group after exporting the services, this will reduce flakiness in an automated test + for _, v := range testClusters { + applyResources(t, cfg, fmt.Sprintf("../fixtures/bases/sameness/%s-default-ns", v.name), v.context.KubectlOptions(t)) + } // Setup DNS. - dnsService, err := members[keyPrimaryServer].context.KubernetesClient(t).CoreV1().Services("default").Get(context.Background(), fmt.Sprintf("%s-%s", releaseName, "consul-dns"), metav1.GetOptions{}) - require.NoError(t, err) - dnsIP := dnsService.Spec.ClusterIP - logger.Logf(t, "dnsIP: %s", dnsIP) + for _, v := range testClusters { + dnsService, err := v.context.KubernetesClient(t).CoreV1().Services("default").Get(context.Background(), fmt.Sprintf("%s-%s", releaseName, "consul-dns"), metav1.GetOptions{}) + require.NoError(t, err) + v.dnsIP = &dnsService.Spec.ClusterIP + logger.Logf(t, "%s dnsIP: %s", v.name, *v.dnsIP) + } // Setup Prepared Query. definition := &api.PreparedQueryDefinition{ Name: "my-query", Service: api.ServiceQuery{ - Service: "static-server", - SamenessGroup: "mine", + Service: staticServerName, + SamenessGroup: samenessGroupName, Namespace: staticServerNamespace, OnlyPassing: false, }, } - resp, _, err := members[keyPrimaryServer].client.PreparedQuery().Create(definition, &api.WriteOptions{}) - require.NoError(t, err) - logger.Logf(t, "PQ ID: %s", resp) - - logger.Log(t, "all infrastructure up and running") - logger.Log(t, "verifying failover scenarios") - - const dnsLookup = "static-server.service.ns2.ns.mine.sg.consul" - const dnsPQLookup = "my-query.query.consul" - - // Verify initial server. - serviceFailoverCheck(t, primaryServerClusterName, members[keyPrimaryServer]) - // Verify initial dns. - dnsFailoverCheck(t, releaseName, dnsIP, dnsLookup, members[keyPrimaryServer], members[keyPrimaryServer]) + for k, v := range testClusters { + if v.hasServer { + pqID, _, err := v.client.PreparedQuery().Create(definition, &api.WriteOptions{}) + require.NoError(t, err) + logger.Logf(t, "%s PQ ID: %s", v.name, pqID) + testClusters[k].pqID = &pqID + testClusters[k].pqName = &definition.Name + } + } - // Verify initial dns with PQ. - dnsFailoverCheck(t, releaseName, dnsIP, dnsPQLookup, members[keyPrimaryServer], members[keyPrimaryServer]) + // Create static server/client after the rest of the config is setup for a more stable testing experience + // Create static server deployments. + logger.Log(t, "creating static-server and static-client deployments") + k8s.DeployKustomize(t, testClusters[keyCluster01a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc1-default") + k8s.DeployKustomize(t, testClusters[keyCluster01b].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc1-partition") + k8s.DeployKustomize(t, testClusters[keyCluster02a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc2") + k8s.DeployKustomize(t, testClusters[keyCluster03a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc3") - // Scale down static-server on the server, will fail over to partition. - k8s.KubectlScale(t, members[keyPrimaryServer].serverOpts, staticServerDeployment, 0) + // Create static client deployments. + k8s.DeployKustomize(t, testClusters[keyCluster01a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-client/default-partition") + k8s.DeployKustomize(t, testClusters[keyCluster02a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-client/default-partition") + k8s.DeployKustomize(t, testClusters[keyCluster03a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-client/default-partition") + k8s.DeployKustomize(t, testClusters[keyCluster01b].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-client/ap1-partition") + + // Verify that both static-server and static-client have been injected and now have 2 containers in each cluster. + // Also get the server IP + testClusters.setServerIP(t) - // Verify failover to partition. - serviceFailoverCheck(t, partitionClusterName, members[keyPrimaryServer]) + // Everything should be up and running now + testClusters.verifyServerUpState(t) + logger.Log(t, "all infrastructure up and running") - // Verify dns failover to partition. - dnsFailoverCheck(t, releaseName, dnsIP, dnsLookup, members[keyPrimaryServer], members[keyPartition]) + // Verify all the failover Scenarios + logger.Log(t, "verifying failover scenarios") - // Verify prepared query failover. - dnsFailoverCheck(t, releaseName, dnsIP, dnsPQLookup, members[keyPrimaryServer], members[keyPartition]) + subCases := []struct { + name string + server *cluster + failovers []struct { + failoverServer *cluster + expectedPQ expectedPQ + } + checkDNSPQ bool + }{ + { + name: "cluster-01-a perspective", // This matches the diagram at the beginning of the test + server: testClusters[keyCluster01a], + failovers: []struct { + failoverServer *cluster + expectedPQ expectedPQ + }{ + {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "ap1", peerName: "", namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, + }, + checkDNSPQ: true, + }, + { + name: "cluster-01-b partition perspective", + server: testClusters[keyCluster01b], + failovers: []struct { + failoverServer *cluster + expectedPQ expectedPQ + }{ + {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "ap1", peerName: "", namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, + }, + checkDNSPQ: false, + }, + { + name: "cluster-02-a perspective", + server: testClusters[keyCluster02a], + failovers: []struct { + failoverServer *cluster + expectedPQ expectedPQ + }{ + {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01a].name, namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01b].name, namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, + }, + checkDNSPQ: true, + }, + { + name: "cluster-03-a perspective", + server: testClusters[keyCluster03a], + failovers: []struct { + failoverServer *cluster + expectedPQ expectedPQ + }{ + {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01a].name, namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01b].name, namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, + }, + checkDNSPQ: true, + }, + } - logger.Log(t, "tests complete") + for _, sc := range subCases { + t.Run(sc.name, func(t *testing.T) { + // Reset the scale of all servers + testClusters.resetScale(t) + testClusters.verifyServerUpState(t) + // We're resetting the scale, so make sure we have all the new IP addresses saved + testClusters.setServerIP(t) + + for _, v := range sc.failovers { + // Verify Failover (If this is the first check, then just verifying we're starting with the right server) + logger.Log(t, "checking service failover") + serviceFailoverCheck(t, sc.server, v.failoverServer.name) + + // Verify DNS + if sc.checkDNSPQ { + logger.Log(t, "verifying dns") + dnsFailoverCheck(t, cfg, releaseName, *sc.server.dnsIP, sc.server, v.failoverServer) + + // Verify PQ + logger.Log(t, "verifying prepared query") + preparedQueryFailoverCheck(t, releaseName, *sc.server.dnsIP, v.expectedPQ, sc.server, v.failoverServer) + } else { + // We currently skip running DNS and PQ tests for a couple of reasons + // 1. The admin partition does not contain a server, so DNS service will not resolve on the admin partition cluster + // 2. A workaround to perform the DNS and PQ queries on the primary datacenter cluster by specifying the admin partition + // e.g kubectl --context kind-dc1 --namespace ns1 exec -i deploy/static-client -c static-client \ + // -- dig @test-3lmypr-consul-dns.default static-server.service.ns2.ns.mine.sg.ap1.ap.consul + // is not possible at the moment due to a bug. The workaround will be used once this bug is fixed. + logger.Logf(t, "skipping DNS and PQ checks for %s", sc.name) + } + + // Scale down static-server on the current failover, will fail over to the next. + logger.Logf(t, "scaling server down on %s", v.failoverServer.name) + k8s.KubectlScale(t, v.failoverServer.serverOpts, staticServerDeployment, 0) + } + }) + } }) } } -type member struct { +type expectedPQ struct { + partition string + peerName string + namespace string +} + +type cluster struct { + name string + partition string context environment.TestContext helmCluster *consul.HelmCluster client *api.Client @@ -400,6 +572,56 @@ type member struct { serverOpts *terratestk8s.KubectlOptions clientOpts *terratestk8s.KubectlOptions staticServerIP *string + pqID *string + pqName *string + dnsIP *string + acceptors []string +} + +type clusters map[string]*cluster + +func (c clusters) resetScale(t *testing.T) { + for _, v := range c { + k8s.KubectlScale(t, v.serverOpts, staticServerDeployment, 1) + } +} + +// setServerIP makes sure everything is up and running and then saves the +// static-server IP to the appropriate cluster. IP addresses can change when +// services are scaled up and down. +func (c clusters) setServerIP(t *testing.T) { + for _, labelSelector := range []string{"app=static-server", "app=static-client"} { + for k, v := range c { + podList, err := v.context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), + metav1.ListOptions{LabelSelector: labelSelector}) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + if labelSelector == "app=static-server" { + ip := &podList.Items[0].Status.PodIP + require.NotNil(t, ip) + logger.Logf(t, "%s-static-server-ip: %s", v.name, *ip) + c[k].staticServerIP = ip + } + } + } +} + +// verifyServerUpState will verify that the static-servers are all up and running as +// expected by curling them from their local datacenters. +func (c clusters) verifyServerUpState(t *testing.T) { + logger.Logf(t, "verifying that static-servers are up") + for _, v := range c { + // Query using a client and expect its own name, no failover should occur + serviceFailoverCheck(t, v, v.name) + } +} + +func copySecret(t *testing.T, cfg *config.TestConfig, sourceContext, destContext environment.TestContext, secretName string) { + k8s.CopySecret(t, sourceContext, destContext, secretName) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.RunKubectl(t, destContext.KubectlOptions(t), "delete", "secret", secretName) + }) } func createNamespaces(t *testing.T, cfg *config.TestConfig, context environment.TestContext) { @@ -421,37 +643,111 @@ func applyResources(t *testing.T, cfg *config.TestConfig, kustomizeDir string, o // serviceFailoverCheck verifies that the server failed over as expected by checking that curling the `static-server` // using the `static-client` responds with the expected cluster name. Each static-server responds with a uniquue // name so that we can verify failover occured as expected. -func serviceFailoverCheck(t *testing.T, expectedClusterName string, server *member) { - retry.Run(t, func(r *retry.R) { - resp, err := k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", - staticClientDeployment, "-c", "static-client", "--", "curl", "localhost:8080") +func serviceFailoverCheck(t *testing.T, server *cluster, expectedName string) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + var resp string + var err error + retry.RunWith(timer, t, func(r *retry.R) { + resp, err = k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", + staticClientDeployment, "-c", staticClientName, "--", "curl", "localhost:8080") require.NoError(r, err) - assert.Contains(r, resp, expectedClusterName) - logger.Log(t, resp) + assert.Contains(r, resp, expectedName) }) + logger.Log(t, resp) } -func dnsFailoverCheck(t *testing.T, releaseName string, dnsIP string, dnsQuery string, server, failover *member) { - retry.Run(t, func(r *retry.R) { - logs, err := k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", - staticClientDeployment, "-c", "static-client", "--", "dig", fmt.Sprintf("@%s-consul-dns.default", releaseName), dnsQuery) - require.NoError(r, err) +// preparedQueryFailoverCheck verifies that failover occurs when executing the prepared query. It also assures that +// executing the prepared query via DNS also provides expected results. +func preparedQueryFailoverCheck(t *testing.T, releaseName string, dnsIP string, epq expectedPQ, server, failover *cluster) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + resp, _, err := server.client.PreparedQuery().Execute(*server.pqID, &api.QueryOptions{Namespace: staticServerNamespace, Partition: server.partition}) + require.NoError(t, err) + require.Len(t, resp.Nodes, 1) + + assert.Equal(t, epq.partition, resp.Nodes[0].Service.Partition) + assert.Equal(t, epq.peerName, resp.Nodes[0].Service.PeerName) + assert.Equal(t, epq.namespace, resp.Nodes[0].Service.Namespace) + assert.Equal(t, *failover.staticServerIP, resp.Nodes[0].Service.Address) + + // Verify that dns lookup is successful, there is no guarantee that the ip address is unique, so for PQ this is + // just verifying that we can query using DNS and that the ip address is correct. It does not however prove + // that failover occured, that is left to client `Execute` + dnsPQLookup := []string{fmt.Sprintf("%s.query.consul", *server.pqName)} + retry.RunWith(timer, t, func(r *retry.R) { + logs := dnsQuery(t, releaseName, dnsPQLookup, server, failover) + assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", dnsIP)) + assert.Contains(r, logs, "ANSWER SECTION:") + assert.Contains(r, logs, *failover.staticServerIP) + }) +} - // When the `dig` request is successful, a section of its response looks like the following: - // - // ;; ANSWER SECTION: - // static-server.service.mine.sg.ns2.ns.consul. 0 IN A - // - // ;; Query time: 2 msec - // ;; SERVER: #() - // ;; WHEN: Mon Aug 10 15:02:40 UTC 2020 - // ;; MSG SIZE rcvd: 98 - // - // We assert on the existence of the ANSWER SECTION, The consul-server IPs being present in - // the ANSWER SECTION and the DNS IP mentioned in the SERVER: field +// DNS failover check verifies that failover occurred when querying the DNS. +func dnsFailoverCheck(t *testing.T, cfg *config.TestConfig, releaseName string, dnsIP string, server, failover *cluster) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + dnsLookup := []string{fmt.Sprintf("static-server.service.ns2.ns.%s.sg.consul", samenessGroupName), "+tcp", "SRV"} + retry.RunWith(timer, t, func(r *retry.R) { + logs := dnsQuery(t, releaseName, dnsLookup, server, failover) assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", dnsIP)) assert.Contains(r, logs, "ANSWER SECTION:") assert.Contains(r, logs, *failover.staticServerIP) + + // Additional checks + // When accessing the SRV record for DNS we can get more information. In the case of Kind, + // the context can be used to determine that failover occured to the expected kubernetes cluster + // hosting Consul + assert.Contains(r, logs, "ADDITIONAL SECTION:") + expectedName := failover.context.KubectlOptions(t).ContextName + if cfg.UseKind { + expectedName = strings.Replace(expectedName, "kind-", "", -1) + } + assert.Contains(r, logs, expectedName) + }) +} + +// dnsQuery performs a dns query with the provided query string. +func dnsQuery(t *testing.T, releaseName string, dnsQuery []string, server, failover *cluster) string { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} + var logs string + retry.RunWith(timer, t, func(r *retry.R) { + args := []string{"exec", "-i", + staticClientDeployment, "-c", staticClientName, "--", "dig", fmt.Sprintf("@%s-consul-dns.default", + releaseName)} + args = append(args, dnsQuery...) + var err error + logs, err = k8s.RunKubectlAndGetOutputE(t, server.clientOpts, args...) + require.NoError(r, err) }) + logger.Logf(t, "%s: %s", failover.name, logs) + return logs +} + +// isAcceptor iterates through the provided acceptor list of cluster names and determines if +// any match the provided name. Returns true if a match is found, false otherwise. +func isAcceptor(name string, acceptorList []string) bool { + for _, v := range acceptorList { + if name == v { + return true + } + } + return false +} + +// getPeeringAcceptorSecret assures that the secret is created and retrieves the secret from the provided acceptor. +func getPeeringAcceptorSecret(t *testing.T, cfg *config.TestConfig, server *cluster, acceptorName string) string { + // Ensure the secrets are created. + var acceptorSecretName string + timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} + retry.RunWith(timer, t, func(r *retry.R) { + var err error + acceptorSecretName, err = k8s.RunKubectlAndGetOutputE(t, server.context.KubectlOptions(t), "get", "peeringacceptor", acceptorName, "-o", "jsonpath={.status.secret.name}") + require.NoError(r, err) + require.NotEmpty(r, acceptorSecretName) + }) + + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.RunKubectl(t, server.context.KubectlOptions(t), "delete", "secret", acceptorSecretName) + }) + + return acceptorSecretName } From 6e9f4731e76642dbb5d0869cdf1aa3fa40e1681a Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Mon, 14 Aug 2023 11:12:59 -0400 Subject: [PATCH 326/592] Updates changelog to include 1.0.9 (#2758) --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa47db462e..52529c952a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +## 1.0.9 (Aug 10, 2023) + +SECURITY: + +* Upgrade to use Go 1.19.11 and `x/net/http` 0.12.0. + This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2650](https://github.com/hashicorp/consul-k8s/issues/2650)] +* Upgrade to use Go 1.19.12 and `x/net` 0.13.0. + This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) + and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2717](https://github.com/hashicorp/consul-k8s/issues/2717)] + +IMPROVEMENTS: + +* Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields +1. `global.acls.logLevel` +2. `global.tls.logLevel` +3. `global.federation.logLevel` +4. `global.gossipEncryption.logLevel` +5. `server.logLevel` +6. `client.logLevel` +7. `meshGateway.logLevel` +8. `ingressGateways.logLevel` +9. `terminatingGateways.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] +* control-plane: increase timeout after login for ACL replication to 60 seconds [[GH-2656](https://github.com/hashicorp/consul-k8s/issues/2656)] +* helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. [[GH-2525](https://github.com/hashicorp/consul-k8s/issues/2525)] +* helm: do not set container securityContexts by default on OpenShift < 4.11 [[GH-2678](https://github.com/hashicorp/consul-k8s/issues/2678)] +* helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled [[GH-2572](https://github.com/hashicorp/consul-k8s/issues/2572)] + +BUG FIXES: + +* control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] +* helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] +* helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] + ## 1.1.4 (Aug 10, 2023) SECURITY: From ab00c033f386f5f3fdc1614092c1494ca36e6a1b Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Tue, 15 Aug 2023 09:37:16 -0400 Subject: [PATCH 327/592] Adds changelog for 1.2.1, reorders 1.1.4 and 1.0.9 (#2768) --- CHANGELOG.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52529c952a..d9a4d76bef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,28 @@ -## 1.0.9 (Aug 10, 2023) +## 1.2.1 (Aug 10, 2023) +BREAKING CHANGES: + +* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] SECURITY: -* Upgrade to use Go 1.19.11 and `x/net/http` 0.12.0. - This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2650](https://github.com/hashicorp/consul-k8s/issues/2650)] -* Upgrade to use Go 1.19.12 and `x/net` 0.13.0. +* Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. + This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2642](https://github.com/hashicorp/consul-k8s/issues/2642)] +* Upgrade to use Go 1.20.7 and `x/net` 0.13.0. This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) - and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2717](https://github.com/hashicorp/consul-k8s/issues/2717)] + and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2710](https://github.com/hashicorp/consul-k8s/issues/2710)] + +FEATURES: + +* Add support for configuring graceful shutdown proxy lifecycle management settings. [[GH-2233](https://github.com/hashicorp/consul-k8s/issues/2233)] +* api-gateway: adds ability to map privileged ports on Gateway listeners to unprivileged ports so that containers do not require additional privileges [[GH-2707](https://github.com/hashicorp/consul-k8s/issues/2707)] +* api-gateway: support deploying to OpenShift 4.11 [[GH-2184](https://github.com/hashicorp/consul-k8s/issues/2184)] +* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] +* sync-catalog: add ability to support weighted loadbalancing by service annotation `consul.hashicorp.com/service-weight: ` [[GH-2293](https://github.com/hashicorp/consul-k8s/issues/2293)] IMPROVEMENTS: +* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2370](https://github.com/hashicorp/consul-k8s/issues/2370)] +* (api-gateway) make API gateway controller less verbose [[GH-2524](https://github.com/hashicorp/consul-k8s/issues/2524)] * Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields 1. `global.acls.logLevel` 2. `global.tls.logLevel` @@ -19,17 +32,28 @@ IMPROVEMENTS: 6. `client.logLevel` 7. `meshGateway.logLevel` 8. `ingressGateways.logLevel` -9. `terminatingGateways.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] +9. `terminatingGateways.logLevel` +10. `telemetryCollector.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] * control-plane: increase timeout after login for ACL replication to 60 seconds [[GH-2656](https://github.com/hashicorp/consul-k8s/issues/2656)] * helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. [[GH-2525](https://github.com/hashicorp/consul-k8s/issues/2525)] -* helm: do not set container securityContexts by default on OpenShift < 4.11 [[GH-2678](https://github.com/hashicorp/consul-k8s/issues/2678)] * helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled [[GH-2572](https://github.com/hashicorp/consul-k8s/issues/2572)] +* helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.2.0` [[GH-2476](https://github.com/hashicorp/consul-k8s/issues/2476)] +* helm: update `image` value to `hashicorp/consul:1.16.0` [[GH-2476](https://github.com/hashicorp/consul-k8s/issues/2476)] BUG FIXES: +* api-gateway: Fix creation of invalid Kubernetes Service when multiple Gateway listeners have the same port. [[GH-2413](https://github.com/hashicorp/consul-k8s/issues/2413)] +* api-gateway: fix helm install when setting copyAnnotations or nodeSelector [[GH-2597](https://github.com/hashicorp/consul-k8s/issues/2597)] +* api-gateway: fixes bug where envoy will silently reject RSA keys less than 2048 bits in length when not in FIPS mode, and + will reject keys that are not 2048, 3072, or 4096 bits in length in FIPS mode. We now validate + and reject invalid certs earlier. [[GH-2478](https://github.com/hashicorp/consul-k8s/issues/2478)] +* api-gateway: set route condition appropriately when parent ref includes non-existent section name [[GH-2420](https://github.com/hashicorp/consul-k8s/issues/2420)] +* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] * control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] * helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] * helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] +* transparent-proxy: Fix issue where connect-inject lacked sufficient `mesh:write` privileges in some deployments, + which prevented virtual IPs from persisting properly. [[GH-2520](https://github.com/hashicorp/consul-k8s/issues/2520)] ## 1.1.4 (Aug 10, 2023) @@ -65,6 +89,39 @@ BUG FIXES: * helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] * helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] +## 1.0.9 (Aug 10, 2023) + +SECURITY: + +* Upgrade to use Go 1.19.11 and `x/net/http` 0.12.0. + This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2650](https://github.com/hashicorp/consul-k8s/issues/2650)] +* Upgrade to use Go 1.19.12 and `x/net` 0.13.0. + This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) + and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2717](https://github.com/hashicorp/consul-k8s/issues/2717)] + +IMPROVEMENTS: + +* Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields +1. `global.acls.logLevel` +2. `global.tls.logLevel` +3. `global.federation.logLevel` +4. `global.gossipEncryption.logLevel` +5. `server.logLevel` +6. `client.logLevel` +7. `meshGateway.logLevel` +8. `ingressGateways.logLevel` +9. `terminatingGateways.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] +* control-plane: increase timeout after login for ACL replication to 60 seconds [[GH-2656](https://github.com/hashicorp/consul-k8s/issues/2656)] +* helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. [[GH-2525](https://github.com/hashicorp/consul-k8s/issues/2525)] +* helm: do not set container securityContexts by default on OpenShift < 4.11 [[GH-2678](https://github.com/hashicorp/consul-k8s/issues/2678)] +* helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled [[GH-2572](https://github.com/hashicorp/consul-k8s/issues/2572)] + +BUG FIXES: + +* control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] +* helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] +* helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] + ## 0.49.8 (July 12, 2023) IMPROVEMENTS: From 8a5eff010a6ee72a72f9e83ea86bc4e52bbf4b2d Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Wed, 16 Aug 2023 12:36:38 -0700 Subject: [PATCH 328/592] Mw/net 4260 add tproxy coverage (#2776) * add additional tproxy static-client - this doesn't specify an upstream so that tproxy will be able to handle routing * add tproxy coverage - add control-flow to handle using the virtual host name when tproxy is enabled --- .../ap1-partition-tproxy/kustomization.yaml | 8 +++ .../ap1-partition-tproxy/patch.yaml | 21 ++++++++ .../kustomization.yaml | 8 +++ .../default-partition-tproxy/patch.yaml | 21 ++++++++ acceptance/tests/sameness/sameness_test.go | 49 ++++++++++++++----- 5 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml new file mode 100644 index 0000000000..227f223c9f --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/patch.yaml new file mode 100644 index 0000000000..68f3c8dd91 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/patch.yaml @@ -0,0 +1,21 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + 'consul.hashicorp.com/connect-inject': 'true' + spec: + containers: + - name: static-client + image: anubhavmishra/tiny-tools:latest + # Just spin & wait forever, we'll use `kubectl exec` to demo + command: ['/bin/sh', '-c', '--'] + args: ['while true; do sleep 30; done;'] + # If ACLs are enabled, the serviceAccountName must match the Consul service name. + serviceAccountName: static-client diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml new file mode 100644 index 0000000000..227f223c9f --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/patch.yaml new file mode 100644 index 0000000000..e53ef7b509 --- /dev/null +++ b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/patch.yaml @@ -0,0 +1,21 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + 'consul.hashicorp.com/connect-inject': 'true' + spec: + containers: + - name: static-client + image: anubhavmishra/tiny-tools:latest + # Just spin & wait forever, we'll use `kubectl exec` to demo + command: ['/bin/sh', '-c', '--'] + args: ['while true; do sleep 30; done;'] + # If ACLs are enabled, the serviceAccountName must match the Consul service name. + serviceAccountName: static-client \ No newline at end of file diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index 629b886d95..b45f771773 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -428,21 +428,30 @@ func TestFailover_Connect(t *testing.T) { "../fixtures/cases/sameness/static-server/dc3") // Create static client deployments. + staticClientKustomizeDirDefault := "../fixtures/cases/sameness/static-client/default-partition" + staticClientKustomizeDirAP1 := "../fixtures/cases/sameness/static-client/ap1-partition" + + // If transparent proxy is enabled create clients without explicit upstreams + if cfg.EnableTransparentProxy { + staticClientKustomizeDirDefault = fmt.Sprintf("%s-%s", staticClientKustomizeDirDefault, "tproxy") + staticClientKustomizeDirAP1 = fmt.Sprintf("%s-%s", staticClientKustomizeDirAP1, "tproxy") + } + k8s.DeployKustomize(t, testClusters[keyCluster01a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-client/default-partition") + staticClientKustomizeDirDefault) k8s.DeployKustomize(t, testClusters[keyCluster02a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-client/default-partition") + staticClientKustomizeDirDefault) k8s.DeployKustomize(t, testClusters[keyCluster03a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-client/default-partition") + staticClientKustomizeDirDefault) k8s.DeployKustomize(t, testClusters[keyCluster01b].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-client/ap1-partition") + staticClientKustomizeDirAP1) // Verify that both static-server and static-client have been injected and now have 2 containers in each cluster. // Also get the server IP testClusters.setServerIP(t) // Everything should be up and running now - testClusters.verifyServerUpState(t) + testClusters.verifyServerUpState(t, cfg.EnableTransparentProxy) logger.Log(t, "all infrastructure up and running") // Verify all the failover Scenarios @@ -514,19 +523,23 @@ func TestFailover_Connect(t *testing.T) { checkDNSPQ: true, }, } - for _, sc := range subCases { t.Run(sc.name, func(t *testing.T) { // Reset the scale of all servers testClusters.resetScale(t) - testClusters.verifyServerUpState(t) + testClusters.verifyServerUpState(t, cfg.EnableTransparentProxy) // We're resetting the scale, so make sure we have all the new IP addresses saved testClusters.setServerIP(t) for _, v := range sc.failovers { // Verify Failover (If this is the first check, then just verifying we're starting with the right server) logger.Log(t, "checking service failover") - serviceFailoverCheck(t, sc.server, v.failoverServer.name) + + if cfg.EnableTransparentProxy { + serviceFailoverCheck(t, sc.server, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) + } else { + serviceFailoverCheck(t, sc.server, v.failoverServer.name, "localhost:8080") + } // Verify DNS if sc.checkDNSPQ { @@ -578,6 +591,14 @@ type cluster struct { acceptors []string } +func (c cluster) fullTextPartition() string { + if c.partition == "" { + return "default" + } else { + return c.partition + } +} + type clusters map[string]*cluster func (c clusters) resetScale(t *testing.T) { @@ -609,11 +630,15 @@ func (c clusters) setServerIP(t *testing.T) { // verifyServerUpState will verify that the static-servers are all up and running as // expected by curling them from their local datacenters. -func (c clusters) verifyServerUpState(t *testing.T) { +func (c clusters) verifyServerUpState(t *testing.T, isTproxyEnabled bool) { logger.Logf(t, "verifying that static-servers are up") for _, v := range c { // Query using a client and expect its own name, no failover should occur - serviceFailoverCheck(t, v, v.name) + if isTproxyEnabled { + serviceFailoverCheck(t, v, v.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", v.fullTextPartition())) + } else { + serviceFailoverCheck(t, v, v.name, "localhost:8080") + } } } @@ -643,13 +668,13 @@ func applyResources(t *testing.T, cfg *config.TestConfig, kustomizeDir string, o // serviceFailoverCheck verifies that the server failed over as expected by checking that curling the `static-server` // using the `static-client` responds with the expected cluster name. Each static-server responds with a uniquue // name so that we can verify failover occured as expected. -func serviceFailoverCheck(t *testing.T, server *cluster, expectedName string) { +func serviceFailoverCheck(t *testing.T, server *cluster, expectedName string, curlAddress string) { timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} var resp string var err error retry.RunWith(timer, t, func(r *retry.R) { resp, err = k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", - staticClientDeployment, "-c", staticClientName, "--", "curl", "localhost:8080") + staticClientDeployment, "-c", staticClientName, "--", "curl", curlAddress) require.NoError(r, err) assert.Contains(r, resp, expectedName) }) From 48184c6fd2b7fdb1f3dfcbca886de0c56c1fa4af Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Thu, 17 Aug 2023 17:00:05 -0400 Subject: [PATCH 329/592] [NET-2880] Add `PrioritizeByLocality` to `ProxyDefaults` CRD (#2784) Add `PrioritizeByLocality` to `ProxyDefaults` CRD In addition to service resolver, add this field to the CRD for proxy defaults for parity with Consul config options. --- .changelog/2357.txt | 3 -- .changelog/2784.txt | 3 ++ .../consul/templates/crd-proxydefaults.yaml | 9 +++++ .../api/v1alpha1/proxydefaults_types.go | 27 +++++++------ .../api/v1alpha1/proxydefaults_types_test.go | 25 ++++++++++++ .../api/v1alpha1/serviceresolver_types.go | 38 +------------------ .../v1alpha1/serviceresolver_types_test.go | 10 ++--- control-plane/api/v1alpha1/shared_types.go | 31 +++++++++++++++ .../api/v1alpha1/zz_generated.deepcopy.go | 37 ++++++++++-------- .../consul.hashicorp.com_proxydefaults.yaml | 9 +++++ 10 files changed, 120 insertions(+), 72 deletions(-) delete mode 100644 .changelog/2357.txt create mode 100644 .changelog/2784.txt diff --git a/.changelog/2357.txt b/.changelog/2357.txt deleted file mode 100644 index 7cc35f595a..0000000000 --- a/.changelog/2357.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Add the `PrioritizeByLocality` field to the `ServiceResolver` CRD. -``` diff --git a/.changelog/2784.txt b/.changelog/2784.txt new file mode 100644 index 0000000000..5b11ca3d43 --- /dev/null +++ b/.changelog/2784.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add the `PrioritizeByLocality` field to the `ServiceResolver` and `ProxyDefaults` CRDs. +``` diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 362672c1c1..1e931dd888 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -160,6 +160,15 @@ spec: type: string type: array type: object + prioritizeByLocality: + description: PrioritizeByLocality contains the configuration for + locality aware routing. + properties: + mode: + description: Mode specifies the behavior of PrioritizeByLocality + routing. Valid values are "", "none", and "failover". + type: string + type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index 1100cd107a..7b1529b941 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -95,6 +95,9 @@ type ProxyDefaultsSpec struct { EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` // FailoverPolicy specifies the exact mechanism used for failover. FailoverPolicy *FailoverPolicy `json:"failoverPolicy,omitempty"` + // PrioritizeByLocality controls whether the locality of services within the + // local partition will be used to prioritize connectivity. + PrioritizeByLocality *PrioritizeByLocality `json:"prioritizeByLocality,omitempty"` } func (in *ProxyDefaults) GetObjectMeta() metav1.ObjectMeta { @@ -179,17 +182,18 @@ func (in *ProxyDefaults) SetLastSyncedTime(time *metav1.Time) { func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry { consulConfig := in.convertConfig() return &capi.ProxyConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - MeshGateway: in.Spec.MeshGateway.toConsul(), - Expose: in.Spec.Expose.toConsul(), - Config: consulConfig, - TransparentProxy: in.Spec.TransparentProxy.toConsul(), - MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), - AccessLogs: in.Spec.AccessLogs.toConsul(), - EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), - FailoverPolicy: in.Spec.FailoverPolicy.toConsul(), - Meta: meta(datacenter), + Kind: in.ConsulKind(), + Name: in.ConsulName(), + MeshGateway: in.Spec.MeshGateway.toConsul(), + Expose: in.Spec.Expose.toConsul(), + Config: consulConfig, + TransparentProxy: in.Spec.TransparentProxy.toConsul(), + MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), + AccessLogs: in.Spec.AccessLogs.toConsul(), + EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), + FailoverPolicy: in.Spec.FailoverPolicy.toConsul(), + PrioritizeByLocality: in.Spec.PrioritizeByLocality.toConsul(), + Meta: meta(datacenter), } } @@ -228,6 +232,7 @@ func (in *ProxyDefaults) Validate(_ common.ConsulMeta) error { allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) allErrs = append(allErrs, in.Spec.FailoverPolicy.validate(path.Child("failoverPolicy"))...) + allErrs = append(allErrs, in.Spec.PrioritizeByLocality.validate(path.Child("prioritizeByLocality"))...) if len(allErrs) > 0 { return apierrors.NewInvalid( diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 07f894f322..6a965fce3a 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -98,6 +98,9 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-1"}, }, + PrioritizeByLocality: &PrioritizeByLocality{ + Mode: "failover", + }, }, }, Theirs: &capi.ProxyConfigEntry{ @@ -161,6 +164,9 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-1"}, }, + PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ + Mode: "failover", + }, }, Matches: true, }, @@ -318,6 +324,9 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-1"}, }, + PrioritizeByLocality: &PrioritizeByLocality{ + Mode: "none", + }, }, }, Exp: &capi.ProxyConfigEntry{ @@ -382,6 +391,9 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-1"}, }, + PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ + Mode: "none", + }, Meta: map[string]string{ common.SourceKey: common.SourceValue, common.DatacenterKey: "datacenter", @@ -652,6 +664,19 @@ func TestProxyDefaults_Validate(t *testing.T) { }, expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.failoverPolicy.mode: Invalid value: "wrong-mode": must be one of "", "sequential", "order-by-locality"`, }, + "prioritize by locality invalid": { + input: &ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + }, + Spec: ProxyDefaultsSpec{ + PrioritizeByLocality: &PrioritizeByLocality{ + Mode: "wrong-mode", + }, + }, + }, + expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.prioritizeByLocality.mode: Invalid value: "wrong-mode": must be one of "", "none", "failover"`, + }, "multi-error": { input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 714cd94c2a..3a8e907222 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -84,14 +84,7 @@ type ServiceResolverSpec struct { LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` // PrioritizeByLocality controls whether the locality of services within the // local partition will be used to prioritize connectivity. - PrioritizeByLocality *ServiceResolverPrioritizeByLocality `json:"prioritizeByLocality,omitempty"` -} - -type ServiceResolverPrioritizeByLocality struct { - // Mode specifies the type of prioritization that will be performed - // when selecting nodes in the local partition. - // Valid values are: "" (default "none"), "none", and "failover". - Mode string `json:"mode,omitempty"` + PrioritizeByLocality *PrioritizeByLocality `json:"prioritizeByLocality,omitempty"` } type ServiceResolverRedirect struct { @@ -536,16 +529,6 @@ func (in *ServiceResolverFailover) toConsul() *capi.ServiceResolverFailover { } } -func (in *ServiceResolverPrioritizeByLocality) toConsul() *capi.ServiceResolverPrioritizeByLocality { - if in == nil { - return nil - } - - return &capi.ServiceResolverPrioritizeByLocality{ - Mode: in.Mode, - } -} - func (in ServiceResolverFailoverTarget) toConsul() capi.ServiceResolverFailoverTarget { return capi.ServiceResolverFailoverTarget{ Service: in.Service, @@ -655,25 +638,6 @@ func (in *ServiceResolverFailover) isEmpty() bool { return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 && in.Policy == nil && in.SamenessGroup == "" } -func (in *ServiceResolverPrioritizeByLocality) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - - if in == nil { - return nil - } - - switch in.Mode { - case "": - case "none": - case "failover": - default: - asJSON, _ := json.Marshal(in) - errs = append(errs, field.Invalid(path, string(asJSON), - "mode must be one of '', 'none', or 'failover'")) - } - return errs -} - func (in *ServiceResolverFailover) validate(path *field.Path, consulMeta common.ConsulMeta) field.ErrorList { var errs field.ErrorList if in.isEmpty() { diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index 11751aaa2a..3fbc2b4fce 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -66,7 +66,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, - PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + PrioritizeByLocality: &PrioritizeByLocality{ Mode: "failover", }, Failover: map[string]ServiceResolverFailover{ @@ -285,7 +285,7 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, - PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + PrioritizeByLocality: &PrioritizeByLocality{ Mode: "none", }, Failover: map[string]ServiceResolverFailover{ @@ -898,20 +898,20 @@ func TestServiceResolver_Validate(t *testing.T) { "spec.failover[failB].namespace: Invalid value: \"namespace-b\": Consul Enterprise namespaces must be enabled to set failover.namespace", }, }, - "prioritize by locality none": { + "prioritize by locality invalid": { input: &ServiceResolver{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Spec: ServiceResolverSpec{ - PrioritizeByLocality: &ServiceResolverPrioritizeByLocality{ + PrioritizeByLocality: &PrioritizeByLocality{ Mode: "bad", }, }, }, namespacesEnabled: false, expectedErrMsgs: []string{ - "mode must be one of '', 'none', or 'failover'", + "serviceresolver.consul.hashicorp.com \"foo\" is invalid: spec.prioritizeByLocality.mode: Invalid value: \"bad\": must be one of \"\", \"none\", \"failover\"", }, }, } diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index aa19c339da..148376a393 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -310,6 +310,37 @@ func (in *FailoverPolicy) validate(path *field.Path) field.ErrorList { return errs } +// PrioritizeByLocality controls whether the locality of services within the +// local partition will be used to prioritize connectivity. +type PrioritizeByLocality struct { + // Mode specifies the type of prioritization that will be performed + // when selecting nodes in the local partition. + // Valid values are: "" (default "none"), "none", and "failover". + Mode string `json:"mode,omitempty"` +} + +func (in *PrioritizeByLocality) toConsul() *capi.ServiceResolverPrioritizeByLocality { + if in == nil { + return nil + } + + return &capi.ServiceResolverPrioritizeByLocality{ + Mode: in.Mode, + } +} + +func (in *PrioritizeByLocality) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if in == nil { + return nil + } + modes := []string{"", "none", "failover"} + if !sliceContains(modes, in.Mode) { + errs = append(errs, field.Invalid(path.Child("mode"), in.Mode, notInSliceMessage(modes))) + } + return errs +} + func notInSliceMessage(slice []string) string { return fmt.Sprintf(`must be one of "%s"`, strings.Join(slice, `", "`)) } diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 05000031ad..5de7c61c4e 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -1947,6 +1947,21 @@ func (in *PeeringMeshConfig) DeepCopy() *PeeringMeshConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PrioritizeByLocality) DeepCopyInto(out *PrioritizeByLocality) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrioritizeByLocality. +func (in *PrioritizeByLocality) DeepCopy() *PrioritizeByLocality { + if in == nil { + return nil + } + out := new(PrioritizeByLocality) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyDefaults) DeepCopyInto(out *ProxyDefaults) { *out = *in @@ -2043,6 +2058,11 @@ func (in *ProxyDefaultsSpec) DeepCopyInto(out *ProxyDefaultsSpec) { *out = new(FailoverPolicy) (*in).DeepCopyInto(*out) } + if in.PrioritizeByLocality != nil { + in, out := &in.PrioritizeByLocality, &out.PrioritizeByLocality + *out = new(PrioritizeByLocality) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsSpec. @@ -2618,21 +2638,6 @@ func (in *ServiceResolverList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceResolverPrioritizeByLocality) DeepCopyInto(out *ServiceResolverPrioritizeByLocality) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceResolverPrioritizeByLocality. -func (in *ServiceResolverPrioritizeByLocality) DeepCopy() *ServiceResolverPrioritizeByLocality { - if in == nil { - return nil - } - out := new(ServiceResolverPrioritizeByLocality) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceResolverRedirect) DeepCopyInto(out *ServiceResolverRedirect) { *out = *in @@ -2679,7 +2684,7 @@ func (in *ServiceResolverSpec) DeepCopyInto(out *ServiceResolverSpec) { } if in.PrioritizeByLocality != nil { in, out := &in.PrioritizeByLocality, &out.PrioritizeByLocality - *out = new(ServiceResolverPrioritizeByLocality) + *out = new(PrioritizeByLocality) **out = **in } } diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 7084980bf0..25e61a51ee 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -156,6 +156,15 @@ spec: type: string type: array type: object + prioritizeByLocality: + description: PrioritizeByLocality contains the configuration for + locality aware routing. + properties: + mode: + description: Mode specifies the behavior of PrioritizeByLocality + routing. Valid values are "", "none", and "failover". + type: string + type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. From 9f089ec53f201eefb6ca8d87da35dab4138c3db1 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 18 Aug 2023 11:12:07 -0400 Subject: [PATCH 330/592] AKS 1.24 is deprecated, update to latest 1.25 patch (#2792) --- charts/consul/test/terraform/aks/main.tf | 2 +- charts/consul/test/terraform/aks/variables.tf | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/charts/consul/test/terraform/aks/main.tf b/charts/consul/test/terraform/aks/main.tf index 2683bdc1a7..f9dc36a51c 100644 --- a/charts/consul/test/terraform/aks/main.tf +++ b/charts/consul/test/terraform/aks/main.tf @@ -55,7 +55,7 @@ resource "azurerm_kubernetes_cluster" "default" { location = azurerm_resource_group.default[count.index].location resource_group_name = azurerm_resource_group.default[count.index].name dns_prefix = "consul-k8s-${random_id.suffix[count.index].dec}" - kubernetes_version = "1.24.10" + kubernetes_version = var.kubernetes_version role_based_access_control_enabled = true // We're setting the network plugin and other network properties explicitly diff --git a/charts/consul/test/terraform/aks/variables.tf b/charts/consul/test/terraform/aks/variables.tf index 554d1b0965..9115517029 100644 --- a/charts/consul/test/terraform/aks/variables.tf +++ b/charts/consul/test/terraform/aks/variables.tf @@ -6,6 +6,11 @@ variable "location" { description = "The location to launch this AKS cluster in." } +variable "kubernetes_version" { + default = "1.25.17" + description = "Kubernetes version supported on AKS (1.25.17 Released August 2nd, 2023)" +} + variable "client_id" { default = "" description = "The client ID of the service principal to be used by Kubernetes when creating Azure resources like load balancers." From e5ad4475cccc64f5142e434ea165a5f822ca0eec Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Fri, 18 Aug 2023 14:54:45 -0500 Subject: [PATCH 331/592] Net 4889 implement retry feature on the api gateway (#2735) * squash, add support for retry loops and timeouts to api-gateway NET-4889, NET-4890 * Update .changelog/2735.txt Co-authored-by: Andrew Stucki * clean up extra files * delete custom struct, just use client.Object * delete * revert kustomization * lint cleanups * fix merge reversion, last bit of cleanup --------- Co-authored-by: Andrew Stucki --- .changelog/2735.txt | 3 + .../templates/connect-inject-clusterrole.yaml | 2 + .../templates/crd-routeretryfilters.yaml | 117 ++++++++++ .../templates/crd-routetimeoutfilters.yaml | 115 ++++++++++ control-plane/PROJECT | 18 ++ control-plane/api-gateway/binding/result.go | 15 +- .../api-gateway/binding/route_binding.go | 26 +++ .../api-gateway/binding/validation.go | 61 ++++++ control-plane/api-gateway/common/diff.go | 13 +- control-plane/api-gateway/common/helpers.go | 19 ++ control-plane/api-gateway/common/resources.go | 37 ++++ .../api-gateway/common/translation.go | 56 ++++- .../api-gateway/common/translation_test.go | 203 +++++++++++++++++- .../controllers/gateway_controller.go | 115 +++++++++- .../api-gateway/controllers/index.go | 54 +++++ .../api/v1alpha1/routeretryfilter_types.go | 55 +++++ .../api/v1alpha1/routetimeoutfilter_types.go | 52 +++++ .../api/v1alpha1/zz_generated.deepcopy.go | 168 +++++++++++++++ ...onsul.hashicorp.com_routeretryfilters.yaml | 112 ++++++++++ ...sul.hashicorp.com_routetimeoutfilters.yaml | 110 ++++++++++ control-plane/go.mod | 4 +- control-plane/go.sum | 8 +- 22 files changed, 1330 insertions(+), 33 deletions(-) create mode 100644 .changelog/2735.txt create mode 100644 charts/consul/templates/crd-routeretryfilters.yaml create mode 100644 charts/consul/templates/crd-routetimeoutfilters.yaml create mode 100644 control-plane/api/v1alpha1/routeretryfilter_types.go create mode 100644 control-plane/api/v1alpha1/routetimeoutfilter_types.go create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml diff --git a/.changelog/2735.txt b/.changelog/2735.txt new file mode 100644 index 0000000000..8b74b5552d --- /dev/null +++ b/.changelog/2735.txt @@ -0,0 +1,3 @@ +```release-note:feature +api-gateway: add RouteRetryFilter and RouteTimeoutFilter CRDs +``` \ No newline at end of file diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index f1f6b3878f..c95b7c143a 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -28,6 +28,8 @@ rules: - meshservices - samenessgroups - controlplanerequestlimits + - routeretryfilters + - routetimeoutfilters {{- if .Values.global.peering.enabled }} - peeringacceptors - peeringdialers diff --git a/charts/consul/templates/crd-routeretryfilters.yaml b/charts/consul/templates/crd-routeretryfilters.yaml new file mode 100644 index 0000000000..3c69a5a2ae --- /dev/null +++ b/charts/consul/templates/crd-routeretryfilters.yaml @@ -0,0 +1,117 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: routeretryfilters.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: consul.hashicorp.com + names: + kind: RouteRetryFilter + listKind: RouteRetryFilterList + plural: routeretryfilters + singular: routeretryfilter + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: RouteRetryFilter is the Schema for the routeretryfilters API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter + properties: + numRetries: + format: int32 + minimum: 0 + type: integer + retryOn: + items: + type: string + type: array + retryOnConnectFailure: + type: boolean + retryOnStatusCodes: + items: + format: int32 + type: integer + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/charts/consul/templates/crd-routetimeoutfilters.yaml b/charts/consul/templates/crd-routetimeoutfilters.yaml new file mode 100644 index 0000000000..992d21b35b --- /dev/null +++ b/charts/consul/templates/crd-routetimeoutfilters.yaml @@ -0,0 +1,115 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: routetimeoutfilters.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: consul.hashicorp.com + names: + kind: RouteTimeoutFilter + listKind: RouteTimeoutFilterList + plural: routetimeoutfilters + singular: routetimeoutfilter + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: RouteTimeoutFilter is the Schema for the httproutetimeoutfilters + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter + properties: + idleTimeout: + description: A Duration represents the elapsed time between two instants + as an int64 nanosecond count. The representation limits the largest + representable duration to approximately 290 years. + format: int64 + type: integer + requestTimeout: + description: A Duration represents the elapsed time between two instants + as an int64 nanosecond count. The representation limits the largest + representable duration to approximately 290 years. + format: int64 + type: integer + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/control-plane/PROJECT b/control-plane/PROJECT index 5338cff047..5a4e24d0f8 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -108,4 +108,22 @@ resources: kind: ControlPlaneRequestLimit path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: hashicorp.com + group: consul + kind: RouteRetryFilter + path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: hashicorp.com + group: consul + kind: RouteTimeoutFilter + path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index e6c0760e7a..0ba4a257fd 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -25,7 +25,6 @@ var errRefNotPermitted = errors.New("reference not permitted due to lack of Refe var ( // Each of the below are specified in the Gateway spec under RouteConditionReason - // the general usage is that each error is specified as errRoute* where * corresponds // to the RouteConditionReason given in the spec. If a reason is overloaded and can // be used with two different types of things (i.e. something is not found or it's not supported) // then we distinguish those two usages with errRoute*_Usage. @@ -35,6 +34,8 @@ var ( errRouteInvalidKind = errors.New("invalid backend kind") errRouteBackendNotFound = errors.New("backend not found") errRouteNoMatchingParent = errors.New("no matching parent") + errInvalidExternalRefType = errors.New("invalid externalref filter kind") + errExternalRefNotFound = errors.New("ref not found") ) // routeValidationResult holds the result of validating a route globally, in other @@ -176,16 +177,20 @@ func (b bindResults) Condition() metav1.Condition { // if we only have a single binding error, we can get more specific if len(b) == 1 { for _, result := range b { - switch result.err { - case errRouteNoMatchingListenerHostname: + switch { + case errors.Is(result.err, errRouteNoMatchingListenerHostname): // if we have a hostname mismatch error, then use the more specific reason reason = "NoMatchingListenerHostname" - case errRefNotPermitted: + case errors.Is(result.err, errRefNotPermitted): // or if we have a ref not permitted, then use that reason = "RefNotPermitted" - case errRouteNoMatchingParent: + case errors.Is(result.err, errRouteNoMatchingParent): // or if the route declares a parent that we can't find reason = "NoMatchingParent" + case errors.Is(result.err, errExternalRefNotFound): + reason = "FilterNotFound" + case errors.Is(result.err, errInvalidExternalRefType): + reason = "UnsupportedValue" } } } diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index 8b2e66e761..75a70c2974 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -163,6 +163,31 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section parent: ref, results: result, }) + + httproute, ok := route.(*gwv1beta1.HTTPRoute) + if ok { + if !externalRefsOnRouteAllExist(httproute, r.config.Resources) { + results = append(results, parentBindResult{ + parent: ref, + results: []bindResult{ + { + err: errExternalRefNotFound, + }, + }, + }) + } + + if !externalRefsKindAllowedOnRoute(httproute) { + results = append(results, parentBindResult{ + parent: ref, + results: []bindResult{ + { + err: errInvalidExternalRefType, + }, + }, + }) + } + } } updated := false @@ -294,6 +319,7 @@ func (r *Binder) mutateRouteWithBindingResults(snapshot *Snapshot, object client for parent := range parents.Iter() { new.Parents = append(new.Parents, parent.(api.ResourceReference)) } + return new }) case *gwv1alpha2.TCPRoute: diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index 6029c10b24..a2726b8bb1 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -431,6 +431,67 @@ func routeAllowedForListenerHostname(hostname *gwv1beta1.Hostname, hostnames []g return false } +// externalRefsOnRouteAllExist checks to make sure that all external filters referenced by the route exist in the resource map. +func externalRefsOnRouteAllExist(route *gwv1beta1.HTTPRoute, resources *common.ResourceMap) bool { + for _, rule := range route.Spec.Rules { + for _, filter := range rule.Filters { + if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { + continue + } + + if !resources.ExternalFilterExists(*filter.ExtensionRef, route.Namespace) { + return false + } + + } + + for _, backendRef := range rule.BackendRefs { + for _, filter := range backendRef.Filters { + if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { + continue + } + + if !resources.ExternalFilterExists(*filter.ExtensionRef, route.Namespace) { + return false + } + } + + } + } + + return true +} + +// externalRefsKindAllowedOnRoute makes sure that all externalRefs reference a kind supported by gatewaycontroller. +func externalRefsKindAllowedOnRoute(route *gwv1beta1.HTTPRoute) bool { + for _, rule := range route.Spec.Rules { + if !filtersAllAllowedType(rule.Filters) { + return false + } + + //same thing but for backendref + for _, backendRef := range rule.BackendRefs { + if !filtersAllAllowedType(backendRef.Filters) { + return false + } + } + } + return true +} + +func filtersAllAllowedType(filters []gwv1beta1.HTTPRouteFilter) bool { + for _, filter := range filters { + if filter.ExtensionRef == nil { + continue + } + + if !common.FilterIsExternalFilter(filter) { + return false + } + } + return true +} + // hostnameMatch checks that an individual hostname matches another hostname for // compatibility. func hostnamesMatch(a gwv1alpha2.Hostname, b gwv1beta1.Hostname) bool { diff --git a/control-plane/api-gateway/common/diff.go b/control-plane/api-gateway/common/diff.go index b58bf23901..a50581ca31 100644 --- a/control-plane/api-gateway/common/diff.go +++ b/control-plane/api-gateway/common/diff.go @@ -149,7 +149,9 @@ func (e entryComparator) httpRouteRulesEqual(a, b api.HTTPRouteRule) bool { return slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) && slices.EqualFunc(a.Matches, b.Matches, e.httpMatchesEqual) && - slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) + slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) && + bothNilOrEqualFunc(a.Filters.RetryFilter, b.Filters.RetryFilter, e.retryFiltersEqual) && + bothNilOrEqualFunc(a.Filters.TimeoutFilter, b.Filters.TimeoutFilter, e.timeoutFiltersEqual) } func (e entryComparator) httpServicesEqual(a, b api.HTTPService) bool { @@ -190,6 +192,15 @@ func (e entryComparator) urlRewritesEqual(a, b api.URLRewrite) bool { return a.Path == b.Path } +func (e entryComparator) retryFiltersEqual(a, b api.RetryFilter) bool { + return BothNilOrEqual(a.NumRetries, b.NumRetries) && BothNilOrEqual(a.RetryOnConnectFailure, b.RetryOnConnectFailure) && + slices.Equal(a.RetryOn, b.RetryOn) && slices.Equal(a.RetryOnStatusCodes, b.RetryOnStatusCodes) +} + +func (e entryComparator) timeoutFiltersEqual(a, b api.TimeoutFilter) bool { + return a.RequestTimeout == b.RequestTimeout && a.IdleTimeout == b.IdleTimeout +} + func tcpRoutesEqual(a, b *api.TCPRouteConfigEntry) bool { if a == nil || b == nil { return false diff --git a/control-plane/api-gateway/common/helpers.go b/control-plane/api-gateway/common/helpers.go index b0eeb46510..f2ac883571 100644 --- a/control-plane/api-gateway/common/helpers.go +++ b/control-plane/api-gateway/common/helpers.go @@ -4,6 +4,7 @@ package common import ( + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -27,6 +28,24 @@ func NilOrEqual[T ~string](v *T, check string) bool { return v == nil || string(*v) == check } +func FilterIsExternalFilter(filter gwv1beta1.HTTPRouteFilter) bool { + if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { + return false + } + + if !DerefEqual(&filter.ExtensionRef.Group, v1alpha1.ConsulHashicorpGroup) { + return false + } + + switch filter.ExtensionRef.Kind { + case v1alpha1.RouteRetryFilterKind, v1alpha1.RouteTimeoutFilterKind: + return true + } + + return false + +} + func IndexedNamespacedNameWithDefault[T ~string, U ~string, V ~string](t T, u *U, v V) types.NamespacedName { return types.NamespacedName{ Namespace: DerefStringOr(u, v), diff --git a/control-plane/api-gateway/common/resources.go b/control-plane/api-gateway/common/resources.go index d412c01eee..40fe74bf8d 100644 --- a/control-plane/api-gateway/common/resources.go +++ b/control-plane/api-gateway/common/resources.go @@ -115,6 +115,7 @@ type ResourceMap struct { tcpRouteGateways map[api.ResourceReference]*tcpRoute httpRouteGateways map[api.ResourceReference]*httpRoute gatewayResources map[api.ResourceReference]*resourceSet + externalFilters map[corev1.ObjectReference]client.Object // consul resources for a gateway consulTCPRoutes map[api.ResourceReference]*consulTCPRoute @@ -364,6 +365,42 @@ func (s *ResourceMap) ReferenceCountHTTPRoute(route gwv1beta1.HTTPRoute) { s.httpRouteGateways[consulKey] = set } +func localObjectReferenceToObjectReference(filterRef gwv1beta1.LocalObjectReference, namespace string) corev1.ObjectReference { + return corev1.ObjectReference{ + Kind: string(filterRef.Kind), + Name: string(filterRef.Name), + Namespace: namespace, + } +} + +func objectToObjectReference(object client.Object) corev1.ObjectReference { + return corev1.ObjectReference{ + Kind: object.GetObjectKind().GroupVersionKind().Kind, + Name: object.GetName(), + Namespace: object.GetNamespace(), + } +} + +func (s *ResourceMap) AddExternalFilter(filter client.Object) { + if s.externalFilters == nil { + s.externalFilters = make(map[corev1.ObjectReference]client.Object) + } + + key := objectToObjectReference(filter) + s.externalFilters[key] = filter +} + +func (s *ResourceMap) GetExternalFilter(filterRef gwv1beta1.LocalObjectReference, namespace string) (client.Object, bool) { + key := localObjectReferenceToObjectReference(filterRef, namespace) + filter, ok := s.externalFilters[key] + return filter, ok +} + +func (s *ResourceMap) ExternalFilterExists(filterRef gwv1beta1.LocalObjectReference, namespace string) bool { + _, ok := s.GetExternalFilter(filterRef, namespace) + return ok +} + func (s *ResourceMap) ReferenceCountTCPRoute(route gwv1alpha2.TCPRoute) { key := client.ObjectKeyFromObject(&route) consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index 9303540e82..d3627b11b4 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -151,7 +151,7 @@ func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *Re return t.translateHTTPRouteRule(route, rule, resources) }) - return &api.HTTPRouteConfigEntry{ + configEntry := api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: route.Name, Namespace: namespace, @@ -163,10 +163,13 @@ func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *Re Hostnames: hostnames, Rules: rules, } + + return &configEntry } func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, rule gwv1beta1.HTTPRouteRule, resources *ResourceMap) (api.HTTPRouteRule, bool) { services := ConvertSliceFuncIf(rule.BackendRefs, func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) { + return t.translateHTTPBackendRef(route, ref, resources) }) @@ -175,7 +178,7 @@ func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, ru } matches := ConvertSliceFunc(rule.Matches, t.translateHTTPMatch) - filters := t.translateHTTPFilters(rule.Filters) + filters := t.translateHTTPFilters(rule.Filters, resources, route.Namespace) return api.HTTPRouteRule{ Services: services, @@ -193,9 +196,8 @@ func (t ResourceTranslator) translateHTTPBackendRef(route gwv1beta1.HTTPRoute, r isServiceRef := NilOrEqual(ref.Group, "") && NilOrEqual(ref.Kind, "Service") if isServiceRef && resources.HasService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { - filters := t.translateHTTPFilters(ref.Filters) + filters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) service := resources.Service(id) - return api.HTTPService{ Name: service.Name, Namespace: service.Namespace, @@ -207,7 +209,7 @@ func (t ResourceTranslator) translateHTTPBackendRef(route gwv1beta1.HTTPRoute, r isMeshServiceRef := DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) if isMeshServiceRef && resources.HasMeshService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { - filters := t.translateHTTPFilters(ref.Filters) + filters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) service := resources.MeshService(id) return api.HTTPService{ @@ -273,12 +275,14 @@ func (t ResourceTranslator) translateHTTPQueryMatch(match gwv1beta1.HTTPQueryPar } } -func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter) api.HTTPFilters { +func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter, resourceMap *ResourceMap, namespace string) api.HTTPFilters { var urlRewrite *api.URLRewrite consulFilter := api.HTTPHeaderFilter{ Add: make(map[string]string), Set: make(map[string]string), } + var retryFilter *api.RetryFilter + var timeoutFilter *api.TimeoutFilter for _, filter := range filters { if filter.RequestHeaderModifier != nil { @@ -299,10 +303,46 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi filter.URLRewrite.Path.Type == gwv1beta1.PrefixMatchHTTPPathModifier { urlRewrite = &api.URLRewrite{Path: DerefStringOr(filter.URLRewrite.Path.ReplacePrefixMatch, "")} } + + if filter.ExtensionRef != nil { + //get crd from resources map + crdFilter, exists := resourceMap.GetExternalFilter(*filter.ExtensionRef, namespace) + if !exists { + // this should never be the case because we only translate a route if it's actually valid, and if we're missing filters during the validation step, then we won't get here + continue + } + switch filter.ExtensionRef.Kind { + case v1alpha1.RouteRetryFilterKind: + + retryFilterCRD := crdFilter.(*v1alpha1.RouteRetryFilter) + //new filter that needs to be appended + + retryFilter = &api.RetryFilter{ + NumRetries: retryFilterCRD.Spec.NumRetries, + RetryOn: retryFilterCRD.Spec.RetryOn, + RetryOnStatusCodes: retryFilterCRD.Spec.RetryOnStatusCodes, + RetryOnConnectFailure: retryFilterCRD.Spec.RetryOnConnectFailure, + } + + case v1alpha1.RouteTimeoutFilterKind: + + timeoutFilterCRD := crdFilter.(*v1alpha1.RouteTimeoutFilter) + //new filter that needs to be appended + + timeoutFilter = &api.TimeoutFilter{ + RequestTimeout: timeoutFilterCRD.Spec.RequestTimeout, + IdleTimeout: timeoutFilterCRD.Spec.IdleTimeout, + } + + } + } + } return api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{consulFilter}, - URLRewrite: urlRewrite, + Headers: []api.HTTPHeaderFilter{consulFilter}, + URLRewrite: urlRewrite, + RetryFilter: retryFilter, + TimeoutFilter: timeoutFilter, } } diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index e8caad76ed..d562845fc8 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -10,7 +10,9 @@ import ( "crypto/x509/pkix" "encoding/pem" "fmt" + "k8s.io/utils/pointer" "math/big" + "sigs.k8s.io/controller-runtime/pkg/client" "strings" "testing" "time" @@ -354,10 +356,12 @@ func TestTranslator_ToAPIGateway(t *testing.T) { func TestTranslator_ToHTTPRoute(t *testing.T) { t.Parallel() type args struct { - k8sHTTPRoute gwv1beta1.HTTPRoute - services []types.NamespacedName - meshServices []v1alpha1.MeshService + k8sHTTPRoute gwv1beta1.HTTPRoute + services []types.NamespacedName + meshServices []v1alpha1.MeshService + externalFilters []client.Object } + tests := map[string]struct { args args want api.HTTPRouteConfigEntry @@ -1210,6 +1214,193 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Namespace: "k8s-ns", }, }, + "test with external filters": { + args: args{ + k8sHTTPRoute: gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8s-http-route", + Namespace: "k8s-ns", + Annotations: map[string]string{}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), + Name: gwv1beta1.ObjectName("api-gw"), + Kind: PointerTo(gwv1beta1.Kind("Gateway")), + SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{ + "host-name.example.com", + "consul.io", + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: PointerTo(gwv1beta1.PathMatchPathPrefix), + Value: PointerTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: PointerTo(gwv1beta1.HeaderMatchExact), + Name: "my header match", + Value: "the value", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: PointerTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "term", + }, + }, + Method: PointerTo(gwv1beta1.HTTPMethodGet), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Name: "test", + Kind: v1alpha1.RouteRetryFilterKind, + Group: "consul.hashicorp.com/v1alpha1", + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "service one", + Namespace: PointerTo(gwv1beta1.Namespace("other")), + }, + Weight: PointerTo(int32(45)), + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Name: "test", + Kind: v1alpha1.RouteRetryFilterKind, + Group: "consul.hashicorp.com/v1alpha1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + services: []types.NamespacedName{ + {Name: "service one", Namespace: "other"}, + }, + externalFilters: []client.Object{ + &v1alpha1.RouteRetryFilter{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.RouteRetryFilterKind, + APIVersion: "consul.hashicorp.com/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "k8s-ns", + }, + Spec: v1alpha1.RouteRetryFilterSpec{ + NumRetries: pointer.Uint32(3), + RetryOn: []string{"cancelled"}, + RetryOnStatusCodes: []uint32{500, 502}, + RetryOnConnectFailure: pointer.Bool(false), + }, + }, + + &v1alpha1.RouteRetryFilter{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.RouteRetryFilterKind, + APIVersion: "consul.hashicorp.com/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "other-namespace-even-though-same-name", + }, + Spec: v1alpha1.RouteRetryFilterSpec{ + NumRetries: pointer.Uint32(3), + RetryOn: []string{"don't"}, + RetryOnStatusCodes: []uint32{404}, + RetryOnConnectFailure: pointer.Bool(true), + }, + }, + }, + }, + want: api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: "k8s-http-route", + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{{Add: map[string]string{}, Set: map[string]string{}}}, + URLRewrite: nil, + RetryFilter: &api.RetryFilter{ + NumRetries: pointer.Uint32(3), + RetryOn: []string{"cancelled"}, + RetryOnStatusCodes: []uint32{500, 502}, + RetryOnConnectFailure: pointer.Bool(false), + }, + }, + Matches: []api.HTTPMatch{ + { + Headers: []api.HTTPHeaderMatch{ + { + Match: api.HTTPHeaderMatchExact, + Name: "my header match", + Value: "the value", + }, + }, + Method: api.HTTPMatchMethodGet, + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: "/v1", + }, + Query: []api.HTTPQueryMatch{ + { + Match: api.HTTPQueryMatchExact, + Name: "search", + Value: "term", + }, + }, + }, + }, + Services: []api.HTTPService{ + { + Name: "service one", + Weight: 45, + Filters: api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{{Add: map[string]string{}, Set: map[string]string{}}}, + RetryFilter: &api.RetryFilter{ + NumRetries: pointer.Uint32(3), + RetryOn: []string{"cancelled"}, + RetryOnStatusCodes: []uint32{500, 502}, + RetryOnConnectFailure: pointer.Bool(false), + }, + }, + Namespace: "other", + }, + }, + }, + }, + Hostnames: []string{ + "host-name.example.com", + "consul.io", + }, + Meta: map[string]string{ + constants.MetaKeyKubeNS: "k8s-ns", + constants.MetaKeyKubeName: "k8s-http-route", + }, + Namespace: "k8s-ns", + }, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -1226,6 +1417,10 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { resources.AddMeshService(service) } + for _, filterToAdd := range tc.args.externalFilters { + resources.AddExternalFilter(filterToAdd) + } + got := tr.ToHTTPRoute(tc.args.k8sHTTPRoute, resources) if diff := cmp.Diff(&tc.want, got); diff != "" { t.Errorf("Translator.ToHTTPRoute() mismatch (-want +got):\n%s", diff) @@ -1444,7 +1639,7 @@ func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { ConsulPartition: tt.fields.ConsulPartition, Datacenter: tt.fields.Datacenter, } - assert.Equalf(t1, tt.want, t.translateHTTPFilters(tt.args.filters), "translateHTTPFilters(%v)", tt.args.filters) + assert.Equalf(t1, tt.want, t.translateHTTPFilters(tt.args.filters, nil, ""), "translateHTTPFilters(%v)", tt.args.filters) }) } } diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index 66347adea4..f7ef3af83b 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -5,6 +5,7 @@ package controllers import ( "context" + "fmt" "reflect" "strconv" "strings" @@ -456,6 +457,14 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co // Subscribe to changes from Consul for InlineCertificates &source.Channel{Source: c.Subscribe(ctx, api.InlineCertificate, r.transformConsulInlineCertificate(ctx)).Events()}, &handler.EnqueueRequestForObject{}, + ). + Watches( + source.NewKindWithCache((&v1alpha1.RouteRetryFilter{}), mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformRouteRetryFilter(ctx)), + ). + Watches( + source.NewKindWithCache((&v1alpha1.RouteTimeoutFilter{}), mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformRouteTimeoutFilter(ctx)), ).Complete(r) } @@ -572,6 +581,20 @@ func (r *GatewayController) transformConsulHTTPRoute(ctx context.Context) func(e } } +// transformRouteRetryFilter will return a list of routes that need to be reconciled. +func (r *GatewayController) transformRouteRetryFilter(ctx context.Context) func(object client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteRetryFilterIndex, client.ObjectKeyFromObject(o).String()) + } +} + +// transformTimeoutRetryFilter will return a list of routes that need to be reconciled. +func (r *GatewayController) transformRouteTimeoutFilter(ctx context.Context) func(object client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteTimeoutFilterIndex, client.ObjectKeyFromObject(o).String()) + } +} + func (r *GatewayController) transformConsulTCPRoute(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { return func(entry api.ConfigEntry) []types.NamespacedName { parents := mapset.NewSet() @@ -660,15 +683,17 @@ func (r *GatewayController) transformEndpoints(ctx context.Context) func(o clien func (r *GatewayController) gatewaysForRoutesReferencing(ctx context.Context, tcpIndex, httpIndex, key string) []reconcile.Request { requestSet := make(map[types.NamespacedName]struct{}) - tcpRouteList := &gwv1alpha2.TCPRouteList{} - if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(tcpIndex, key), - }); err != nil { - r.Log.Error(err, "unable to list TCPRoutes") - } - for _, route := range tcpRouteList.Items { - for _, ref := range common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs) { - requestSet[ref] = struct{}{} + if tcpIndex != "" { + tcpRouteList := &gwv1alpha2.TCPRouteList{} + if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(tcpIndex, key), + }); err != nil { + r.Log.Error(err, "unable to list TCPRoutes") + } + for _, route := range tcpRouteList.Items { + for _, ref := range common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs) { + requestSet[ref] = struct{}{} + } } } @@ -777,11 +802,81 @@ func (c *GatewayController) getRelatedHTTPRoutes(ctx context.Context, gateway ty for _, route := range list.Items { resources.ReferenceCountHTTPRoute(route) + + _, err := c.getExternalFiltersForHTTPRoute(ctx, route, resources) + if err != nil { + c.Log.Error(err, "unable to list HTTPRoute ExternalFilters") + return nil, err + } } return list.Items, nil } +func (c *GatewayController) getExternalFiltersForHTTPRoute(ctx context.Context, route gwv1beta1.HTTPRoute, resources *common.ResourceMap) ([]interface{}, error) { + var externalFilters []interface{} + for _, rule := range route.Spec.Rules { + ruleFilters, err := c.filterFiltersForExternalRefs(ctx, route, rule.Filters, resources) + if err != nil { + return nil, err + } + externalFilters = append(externalFilters, ruleFilters...) + + for _, backendRef := range rule.BackendRefs { + backendRefFilter, err := c.filterFiltersForExternalRefs(ctx, route, backendRef.Filters, resources) + if err != nil { + return nil, err + } + + externalFilters = append(externalFilters, backendRefFilter...) + } + } + + return externalFilters, nil +} + +func (c *GatewayController) filterFiltersForExternalRefs(ctx context.Context, route gwv1beta1.HTTPRoute, filters []gwv1beta1.HTTPRouteFilter, resources *common.ResourceMap) ([]interface{}, error) { + var externalFilters []interface{} + + for _, filter := range filters { + var externalFilter client.Object + + //check to see if we need to grab this filter + if filter.ExtensionRef == nil { + continue + } + switch kind := filter.ExtensionRef.Kind; kind { + case v1alpha1.RouteRetryFilterKind: + externalFilter = &v1alpha1.RouteRetryFilter{} + case v1alpha1.RouteTimeoutFilterKind: + externalFilter = &v1alpha1.RouteTimeoutFilter{} + default: + continue + } + + //get object from API + err := c.Client.Get(ctx, client.ObjectKey{ + Name: string(filter.ExtensionRef.Name), + Namespace: route.Namespace, + }, externalFilter) + + if err != nil { + if k8serrors.IsNotFound(err) { + c.Log.Info(fmt.Sprintf("externalref %s:%s not found: %v", filter.ExtensionRef.Kind, filter.ExtensionRef.Name, err)) + //ignore, the validation call should mark this route as error + continue + } else { + return nil, err + } + } + + //add external ref (or error) to resource map for this route + resources.AddExternalFilter(externalFilter) + externalFilters = append(externalFilters, externalFilter) + } + return externalFilters, nil +} + func (c *GatewayController) getRelatedTCPRoutes(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]gwv1alpha2.TCPRoute, error) { var list gwv1alpha2.TCPRouteList @@ -945,6 +1040,7 @@ func (c *GatewayController) fetchMeshService(ctx context.Context, resources *com } func (c *GatewayController) fetchServicesForEndpoints(ctx context.Context, resources *common.ResourceMap, key types.NamespacedName) error { + if shouldIgnore(key.Namespace, c.denyK8sNamespacesSet, c.allowK8sNamespacesSet) { return nil } @@ -972,6 +1068,7 @@ func (c *GatewayController) fetchServicesForEndpoints(ctx context.Context, resou } resources.AddService(key, serviceName(pod, endpoints)) + } } } diff --git a/control-plane/api-gateway/controllers/index.go b/control-plane/api-gateway/controllers/index.go index 7fd13de1de..131ec383e7 100644 --- a/control-plane/api-gateway/controllers/index.go +++ b/control-plane/api-gateway/controllers/index.go @@ -28,6 +28,8 @@ const ( TCPRoute_MeshServiceIndex = "__tcproute_referencing_mesh_service" MeshService_PeerIndex = "__meshservice_referencing_peer" Secret_GatewayIndex = "__secret_referencing_gateway" + HTTPRoute_RouteRetryFilterIndex = "__httproute_referencing_retryfilter" + HTTPRoute_RouteTimeoutFilterIndex = "__httproute_referencing_timeoutfilter" ) // RegisterFieldIndexes registers all of the field indexes for the API gateway controllers. @@ -104,6 +106,16 @@ var indexes = []index{ target: &v1alpha1.MeshService{}, indexerFunc: peersForMeshService, }, + { + name: HTTPRoute_RouteRetryFilterIndex, + target: &gwv1beta1.HTTPRoute{}, + indexerFunc: filtersForHTTPRoute, + }, + { + name: HTTPRoute_RouteTimeoutFilterIndex, + target: &gwv1beta1.HTTPRoute{}, + indexerFunc: filtersForHTTPRoute, + }, } // gatewayClassConfigForGatewayClass creates an index of every GatewayClassConfig referenced by a GatewayClass. @@ -271,3 +283,45 @@ func gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference, status } return references } + +func filtersForHTTPRoute(o client.Object) []string { + route := o.(*gwv1beta1.HTTPRoute) + filters := []string{} + var nilString *string + + for _, rule := range route.Spec.Rules { + FILTERS_LOOP: + for _, filter := range rule.Filters { + if common.FilterIsExternalFilter(filter) { + //TODO this seems like its type agnostic, so this might just work without having to make + //multiple index functions per custom filter type? + + //index external filters + filter := common.IndexedNamespacedNameWithDefault(string(filter.ExtensionRef.Name), nilString, route.Namespace).String() + for _, member := range filters { + if member == filter { + continue FILTERS_LOOP + } + } + filters = append(filters, filter) + } + } + + //same thing but over the backend refs + BACKEND_LOOP: + for _, ref := range rule.BackendRefs { + for _, filter := range ref.Filters { + if common.FilterIsExternalFilter(filter) { + filter := common.IndexedNamespacedNameWithDefault(string(filter.ExtensionRef.Name), nilString, route.Namespace).String() + for _, member := range filters { + if member == filter { + continue BACKEND_LOOP + } + } + filters = append(filters, filter) + } + } + } + } + return filters +} diff --git a/control-plane/api/v1alpha1/routeretryfilter_types.go b/control-plane/api/v1alpha1/routeretryfilter_types.go new file mode 100644 index 0000000000..79fa85c608 --- /dev/null +++ b/control-plane/api/v1alpha1/routeretryfilter_types.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func init() { + SchemeBuilder.Register(&RouteRetryFilter{}, &RouteRetryFilterList{}) +} + +const RouteRetryFilterKind = "RouteRetryFilter" + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// RouteRetryFilter is the Schema for the routeretryfilters API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +type RouteRetryFilter struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RouteRetryFilterSpec `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// RouteRetryFilterList contains a list of RouteRetryFilter. +type RouteRetryFilterList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RouteRetryFilter `json:"items"` +} + +// RouteRetryFilterSpec defines the desired state of RouteRetryFilter. +type RouteRetryFilterSpec struct { + // +kubebuilder:validation:Minimum:=0 + // +kubebuilder:validation:Optional + NumRetries *uint32 `json:"numRetries"` + // +kubebuilder:validation:Optional + RetryOn []string `json:"retryOn"` + // +kubebuilder:validation:Optional + RetryOnStatusCodes []uint32 `json:"retryOnStatusCodes"` + // +kubebuilder:validation:Optional + RetryOnConnectFailure *bool `json:"retryOnConnectFailure"` +} + +func (h *RouteRetryFilter) GetNamespace() string { + return h.Namespace +} diff --git a/control-plane/api/v1alpha1/routetimeoutfilter_types.go b/control-plane/api/v1alpha1/routetimeoutfilter_types.go new file mode 100644 index 0000000000..59b25f62ea --- /dev/null +++ b/control-plane/api/v1alpha1/routetimeoutfilter_types.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "time" +) + +func init() { + SchemeBuilder.Register(&RouteTimeoutFilter{}, &RouteTimeoutFilterList{}) +} + +const RouteTimeoutFilterKind = "RouteTimeoutFilter" + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// RouteTimeoutFilter is the Schema for the httproutetimeoutfilters API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +type RouteTimeoutFilter struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RouteTimeoutFilterSpec `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// RouteTimeoutFilterList contains a list of RouteTimeoutFilter. +type RouteTimeoutFilterList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RouteTimeoutFilter `json:"items"` +} + +// RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. +type RouteTimeoutFilterSpec struct { + // +kubebuilder:validation:Optional + RequestTimeout time.Duration `json:"requestTimeout"` + + // +kubebuilder:validation:Optional + IdleTimeout time.Duration `json:"idleTimeout"` +} + +func (h *RouteTimeoutFilter) GetNamespace() string { + return h.Namespace +} diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 5de7c61c4e..ccd69c4281 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -2140,6 +2140,174 @@ func (in *RingHashConfig) DeepCopy() *RingHashConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteRetryFilter) DeepCopyInto(out *RouteRetryFilter) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilter. +func (in *RouteRetryFilter) DeepCopy() *RouteRetryFilter { + if in == nil { + return nil + } + out := new(RouteRetryFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RouteRetryFilter) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteRetryFilterList) DeepCopyInto(out *RouteRetryFilterList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RouteRetryFilter, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilterList. +func (in *RouteRetryFilterList) DeepCopy() *RouteRetryFilterList { + if in == nil { + return nil + } + out := new(RouteRetryFilterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RouteRetryFilterList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteRetryFilterSpec) DeepCopyInto(out *RouteRetryFilterSpec) { + *out = *in + if in.NumRetries != nil { + in, out := &in.NumRetries, &out.NumRetries + *out = new(uint32) + **out = **in + } + if in.RetryOn != nil { + in, out := &in.RetryOn, &out.RetryOn + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.RetryOnStatusCodes != nil { + in, out := &in.RetryOnStatusCodes, &out.RetryOnStatusCodes + *out = make([]uint32, len(*in)) + copy(*out, *in) + } + if in.RetryOnConnectFailure != nil { + in, out := &in.RetryOnConnectFailure, &out.RetryOnConnectFailure + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilterSpec. +func (in *RouteRetryFilterSpec) DeepCopy() *RouteRetryFilterSpec { + if in == nil { + return nil + } + out := new(RouteRetryFilterSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteTimeoutFilter) DeepCopyInto(out *RouteTimeoutFilter) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilter. +func (in *RouteTimeoutFilter) DeepCopy() *RouteTimeoutFilter { + if in == nil { + return nil + } + out := new(RouteTimeoutFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RouteTimeoutFilter) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteTimeoutFilterList) DeepCopyInto(out *RouteTimeoutFilterList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RouteTimeoutFilter, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilterList. +func (in *RouteTimeoutFilterList) DeepCopy() *RouteTimeoutFilterList { + if in == nil { + return nil + } + out := new(RouteTimeoutFilterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RouteTimeoutFilterList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteTimeoutFilterSpec) DeepCopyInto(out *RouteTimeoutFilterSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilterSpec. +func (in *RouteTimeoutFilterSpec) DeepCopy() *RouteTimeoutFilterSpec { + if in == nil { + return nil + } + out := new(RouteTimeoutFilterSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SamenessGroup) DeepCopyInto(out *SamenessGroup) { *out = *in diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml new file mode 100644 index 0000000000..5928864ac5 --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml @@ -0,0 +1,112 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: routeretryfilters.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: RouteRetryFilter + listKind: RouteRetryFilterList + plural: routeretryfilters + singular: routeretryfilter + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: RouteRetryFilter is the Schema for the routeretryfilters API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter + properties: + numRetries: + format: int32 + minimum: 0 + type: integer + retryOn: + items: + type: string + type: array + retryOnConnectFailure: + type: boolean + retryOnStatusCodes: + items: + format: int32 + type: integer + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml new file mode 100644 index 0000000000..e671ecd9b9 --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml @@ -0,0 +1,110 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: routetimeoutfilters.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: RouteTimeoutFilter + listKind: RouteTimeoutFilterList + plural: routetimeoutfilters + singular: routetimeoutfilter + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: RouteTimeoutFilter is the Schema for the httproutetimeoutfilters + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter + properties: + idleTimeout: + description: A Duration represents the elapsed time between two instants + as an int64 nanosecond count. The representation limits the largest + representable duration to approximately 290 years. + format: int64 + type: integer + requestTimeout: + description: A Duration represents the elapsed time between two instants + as an int64 nanosecond count. The representation limits the largest + representable duration to approximately 290 years. + format: int64 + type: integer + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/go.mod b/control-plane/go.mod index 2dcd78848c..1a36ef889c 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,8 +10,8 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d github.com/hashicorp/consul-server-connection-manager v0.1.3 - github.com/hashicorp/consul/api v1.22.0-rc1 - github.com/hashicorp/consul/sdk v0.14.0-rc1 + github.com/hashicorp/consul/api v1.10.1-0.20230802160219-cf2bf7fdecdf + github.com/hashicorp/consul/sdk v0.14.0 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 github.com/hashicorp/go-hclog v1.5.0 diff --git a/control-plane/go.sum b/control-plane/go.sum index dec3ba9eb4..70c7220dd6 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -264,12 +264,12 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= github.com/hashicorp/consul-server-connection-manager v0.1.3 h1:fxsZ15XBNNWhV26yBVdCcnxHwSRgf9wqHGS2ZVCQIhc= github.com/hashicorp/consul-server-connection-manager v0.1.3/go.mod h1:Md2IGKaFJ4ek9GUA0pW1S2R60wpquMOUs27GiD9kZd0= -github.com/hashicorp/consul/api v1.22.0-rc1 h1:ePmGqndeMgaI38KUbSA/CqTzeEAIogXyWnfNJzglo70= -github.com/hashicorp/consul/api v1.22.0-rc1/go.mod h1:wtduXtbAqSGtBdi3tyA5SSAYGAG51rBejV9SEUBciMY= +github.com/hashicorp/consul/api v1.10.1-0.20230802160219-cf2bf7fdecdf h1:XCfEJhwSx188jLPjctsGoOfG7OueuXaqceR0HwHAH1s= +github.com/hashicorp/consul/api v1.10.1-0.20230802160219-cf2bf7fdecdf/go.mod h1:TqtEfVYyzax2gaBwU1vsCGFcysmK9g9UANUWCd8qMBw= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= -github.com/hashicorp/consul/sdk v0.14.0-rc1 h1:PuETOfN0uxl28i0Pq6rK7TBCrIl7psMbL0YTSje4KvM= -github.com/hashicorp/consul/sdk v0.14.0-rc1/go.mod h1:gHYeuDa0+0qRAD6Wwr6yznMBvBwHKoxSBoW5l73+saE= +github.com/hashicorp/consul/sdk v0.14.0 h1:Hly+BMNMssVzoWddbBnBFi3W+Fzytvm0haSkihhj3GU= +github.com/hashicorp/consul/sdk v0.14.0/go.mod h1:gHYeuDa0+0qRAD6Wwr6yznMBvBwHKoxSBoW5l73+saE= 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= From 4d40591d4eb93b07f0eb7770623cff457a082890 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 21 Aug 2023 13:07:50 -0400 Subject: [PATCH 332/592] Update Kustomize to use `patches` instead of `patchesStrategicMerge` (#2786) * Fix Kustomization for cases * Fix patches in config * Update `Contributing` --- CONTRIBUTING.md | 2 +- .../api-gateways/jwt-auth/kustomization.yaml | 16 +++++++++------- .../default-partition-default/kustomization.yaml | 9 +++++---- .../default-partition-ns1/kustomization.yaml | 9 +++++---- .../kustomization.yaml | 9 +++++---- .../secondary-partition-ns1/kustomization.yaml | 9 +++++---- .../default-namespace/kustomization.yaml | 9 +++++---- .../cases/crd-peers/default/kustomization.yaml | 9 +++++---- .../non-default-namespace/kustomization.yaml | 9 +++++---- .../fixtures/cases/crds-ent/kustomization.yaml | 6 ++++-- .../cluster-01-a-acceptor/kustomization.yaml | 9 +++++---- .../cluster-01-b-acceptor/kustomization.yaml | 9 +++++---- .../cluster-02-a-acceptor/kustomization.yaml | 9 +++++---- .../cluster-03-a-acceptor/kustomization.yaml | 9 +++++---- .../ap1-partition/kustomization.yaml | 9 +++++---- .../default-partition/kustomization.yaml | 9 +++++---- .../ap1-partition-tproxy/kustomization.yaml | 9 +++++---- .../ap1-partition/kustomization.yaml | 9 +++++---- .../default-partition-tproxy/kustomization.yaml | 9 +++++---- .../default-partition/kustomization.yaml | 9 +++++---- .../static-server/dc1-default/kustomization.yaml | 9 +++++---- .../dc1-partition/kustomization.yaml | 9 +++++---- .../static-server/dc2/kustomization.yaml | 9 +++++---- .../static-server/dc3/kustomization.yaml | 9 +++++---- .../kustomization.yaml | 9 +++++---- .../static-client-inject/kustomization.yaml | 9 +++++---- .../static-client-multi-dc/kustomization.yaml | 9 +++++---- .../static-client-namespaces/kustomization.yaml | 9 +++++---- .../kustomization.yaml | 9 +++++---- .../kustomization.yaml | 9 +++++---- .../kustomization.yaml | 9 +++++---- .../default-ns-partition/kustomization.yaml | 9 +++++---- .../ns-default-partition/kustomization.yaml | 9 +++++---- .../ns-partition/kustomization.yaml | 9 +++++---- .../default-namespace/kustomization.yaml | 9 +++++---- .../default/kustomization.yaml | 9 +++++---- .../non-default-namespace/kustomization.yaml | 9 +++++---- .../static-client-tproxy/kustomization.yaml | 9 +++++---- .../static-server-inject/kustomization.yaml | 9 +++++---- .../static-server-openshift/kustomization.yaml | 9 +++++---- control-plane/config/crd/kustomization.yaml | 5 ++++- 41 files changed, 203 insertions(+), 159 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1e3446e8d..4eca76a1dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -376,7 +376,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ### Generating YAML 1. Run `make ctrl-manifests` to generate the CRD and webhook YAML. -1. Uncomment your CRD in `control-plane/config/crd/kustomization` under `patchesStrategicMerge:` +1. Uncomment your CRD in `control-plane/config/crd/kustomization` under `patches:` 1. Update the sample, e.g. `control-plane/config/samples/consul_v1alpha1_ingressgateway.yaml` to a valid resource that can be used for testing: ```yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml index 3dc38a090c..9730a1a4ac 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml @@ -2,11 +2,13 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ../../../bases/api-gateway - - ../../static-server-inject - - ./httproute.yaml - - ./jwt-provider.yaml +- ../../../bases/api-gateway +- ../../static-server-inject +- ./httproute.yaml +- ./jwt-provider.yaml -patchesStrategicMerge: - - httproute-no-auth.yaml - - api-gateway.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- patch: httproute-no-auth.yaml +- path: api-gateway.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml index a175d8ece0..f3d0bca3ce 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/exportedservices-default - -patchesStrategicMerge: -- patch.yaml +- ../../../bases/exportedservices-default +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml index a175d8ece0..f3d0bca3ce 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/exportedservices-default - -patchesStrategicMerge: -- patch.yaml +- ../../../bases/exportedservices-default +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml index bb16f51e64..77c6bd3fae 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/exportedservices-secondary - -patchesStrategicMerge: -- patch.yaml +- ../../../bases/exportedservices-secondary +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml index bb16f51e64..77c6bd3fae 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/exportedservices-secondary - -patchesStrategicMerge: -- patch.yaml +- ../../../bases/exportedservices-secondary +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml index a175d8ece0..f3d0bca3ce 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/exportedservices-default - -patchesStrategicMerge: -- patch.yaml +- ../../../bases/exportedservices-default +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml index a175d8ece0..f3d0bca3ce 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/exportedservices-default - -patchesStrategicMerge: -- patch.yaml +- ../../../bases/exportedservices-default +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml index a175d8ece0..f3d0bca3ce 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/exportedservices-default - -patchesStrategicMerge: -- patch.yaml +- ../../../bases/exportedservices-default +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml index 69d972b417..3fd5556ebd 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml @@ -1,7 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - ../../bases/crds-oss -patchesStrategicMerge: -- exportedservices.yaml +patches: +- path: exportedservices.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml index 30ddacd76c..08c7c9b818 100644 --- a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/sameness/peering/acceptor - -patchesStrategicMerge: - - patch.yaml +- ../../../bases/sameness/peering/acceptor +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml index 30ddacd76c..08c7c9b818 100644 --- a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/sameness/peering/acceptor - -patchesStrategicMerge: - - patch.yaml +- ../../../bases/sameness/peering/acceptor +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml index 30ddacd76c..08c7c9b818 100644 --- a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/sameness/peering/acceptor - -patchesStrategicMerge: - - patch.yaml +- ../../../bases/sameness/peering/acceptor +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml index 6a54cd6eab..08c7c9b818 100644 --- a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/sameness/peering/acceptor - -patchesStrategicMerge: -- patch.yaml +- ../../../bases/sameness/peering/acceptor +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml index 2a2f47a332..d25bed6eee 100644 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/sameness/exportedservices-ap1 - -patchesStrategicMerge: -- patch.yaml +- ../../../../bases/sameness/exportedservices-ap1 +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml index 05de6151fc..7f4ab4ba7c 100644 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/exportedservices-default - -patchesStrategicMerge: -- patch.yaml +- ../../../../bases/exportedservices-default +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml index 227f223c9f..096edd19ed 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml index 227f223c9f..096edd19ed 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml index 227f223c9f..096edd19ed 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml index 227f223c9f..096edd19ed 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml index c15bfe7ba7..e03603d26d 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/static-server - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../../bases/static-server +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml index c15bfe7ba7..e03603d26d 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/static-server - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../../bases/static-server +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml index c15bfe7ba7..e03603d26d 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/static-server - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../../bases/static-server +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml index c15bfe7ba7..e03603d26d 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../../bases/static-server - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../../bases/static-server +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml index 4d4a53b87f..564d02a68f 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml index 4d4a53b87f..564d02a68f 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml index 4d4a53b87f..564d02a68f 100644 --- a/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml index 97a00c6466..564d02a68f 100644 --- a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-client - -patchesStrategicMerge: - - patch.yaml +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml index 4d4a53b87f..564d02a68f 100644 --- a/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml index 4d4a53b87f..564d02a68f 100644 --- a/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml index 38bc36bffd..0ae44380dd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml index 38bc36bffd..0ae44380dd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml index 38bc36bffd..0ae44380dd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml index 38bc36bffd..0ae44380dd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml index 38bc36bffd..0ae44380dd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml index 38bc36bffd..0ae44380dd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml index 38bc36bffd..0ae44380dd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml index 4d4a53b87f..564d02a68f 100644 --- a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-client - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml index bc50c78adf..bd2c22ff5f 100644 --- a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-server - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../bases/static-server +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml index bc50c78adf..bd2c22ff5f 100644 --- a/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml @@ -1,8 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - - ../../bases/static-server - -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +- ../../bases/static-server +patches: +- path: patch.yaml diff --git a/control-plane/config/crd/kustomization.yaml b/control-plane/config/crd/kustomization.yaml index 2c8358a48b..cbb41bc60c 100644 --- a/control-plane/config/crd/kustomization.yaml +++ b/control-plane/config/crd/kustomization.yaml @@ -1,6 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + # This kustomization.yaml is not intended to be run by itself, # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default @@ -8,7 +11,7 @@ resources: - bases/consul.hashicorp.com_controlplanerequestlimits.yaml #+kubebuilder:scaffold:crdkustomizeresource -patchesStrategicMerge: +patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_controlplanerequestlimits.yaml From fd19813e725df80cc316eb5de8eb2e3c42fa5fb4 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 21 Aug 2023 14:08:13 -0400 Subject: [PATCH 333/592] [NET-4498] Test locality propagation to services from k8s (#2791) Test locality propagation to services from k8s Verify that we propagate locality (region and zone) from standard k8s annotations to services registered by consul-k8s. This will later be expanded to exercise multi-cluster locality-based failover. --- acceptance/framework/k8s/kubectl.go | 8 ++ acceptance/tests/sameness/sameness_test.go | 106 +++++++++++++++++---- 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/acceptance/framework/k8s/kubectl.go b/acceptance/framework/k8s/kubectl.go index acfedbdb3d..2e37b9bd0a 100644 --- a/acceptance/framework/k8s/kubectl.go +++ b/acceptance/framework/k8s/kubectl.go @@ -122,6 +122,14 @@ func KubectlScale(t *testing.T, options *k8s.KubectlOptions, deployment string, require.NoError(t, err) } +// KubectlLabel takes an object and applies the given label to it. +// Example: `KubectlLabel(t, options, "node", nodeId, corev1.LabelTopologyRegion, "us-east-1")`. +func KubectlLabel(t *testing.T, options *k8s.KubectlOptions, objectType string, objectId string, key string, value string) { + // `kubectl label` doesn't support timeouts + _, err := RunKubectlAndGetOutputE(t, options, "label", objectType, objectId, "--overwrite", fmt.Sprintf("%s=%s", key, value)) + require.NoError(t, err) +} + // RunKubectl runs an arbitrary kubectl command provided via args and ignores the output. // If there's an error running the command, fail the test. func RunKubectl(t *testing.T, options *k8s.KubectlOptions, args ...string) { diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index b45f771773..5e1c268169 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -4,7 +4,7 @@ package sameness import ( - "context" + ctx "context" "fmt" "strconv" "strings" @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -51,6 +52,10 @@ const ( samenessGroupName = "group-01" + cluster01Region = "us-east-1" + cluster02Region = "us-west-1" + cluster03Region = "us-east-2" + retryTimeout = 5 * time.Minute ) @@ -116,8 +121,8 @@ func TestFailover_Connect(t *testing.T) { | | +------------------+ | | | | | +------------------+ | | | Admin Partitions: Default | | | | | | | | Name: cluster-01-a | | | | | Admin Partitions: Default | - | | | | | | | Name: cluster-02-a | - | +-----------------------------+ | | | | | + | | Region: us-east-1 | | | | | Name: cluster-02-a | + | +-----------------------------+ | | | | Region: us-west-1 | | | | | +-----------------------------------+ | Failover 1| | Failover 3 | | +-------------------------------+ | | | +-----------------------------------+ @@ -132,17 +137,17 @@ func TestFailover_Connect(t *testing.T) { | | +------------------+ | | | | | | Admin Partitions: ap1 | | | Admin Partitions: Default | | | Name: cluster-01-b | | | Name: cluster-03-a | - | | | | | | + | | Region: us-east-1 | | | Region: us-east-2 | | +-------------------------------+ | | | | | +-----------------------------------+ +-------------------------------------------+ */ testClusters := clusters{ - keyCluster01a: {name: peerName1a, context: env.DefaultContext(t), hasServer: true, acceptors: []string{peerName2a, peerName3a}}, - keyCluster01b: {name: peerName1b, context: env.Context(t, 1), partition: cluster01Partition, hasServer: false, acceptors: []string{peerName2a, peerName3a}}, - keyCluster02a: {name: peerName2a, context: env.Context(t, 2), hasServer: true, acceptors: []string{peerName3a}}, - keyCluster03a: {name: peerName3a, context: env.Context(t, 3), hasServer: true}, + keyCluster01a: {name: peerName1a, context: env.DefaultContext(t), hasServer: true, acceptors: []string{peerName2a, peerName3a}, locality: localityForRegion(cluster01Region)}, + keyCluster01b: {name: peerName1b, context: env.Context(t, 1), partition: cluster01Partition, hasServer: false, acceptors: []string{peerName2a, peerName3a}, locality: localityForRegion(cluster01Region)}, + keyCluster02a: {name: peerName2a, context: env.Context(t, 2), hasServer: true, acceptors: []string{peerName3a}, locality: localityForRegion(cluster02Region)}, + keyCluster03a: {name: peerName3a, context: env.Context(t, 3), hasServer: true, locality: localityForRegion(cluster03Region)}, } // Setup Namespaces. @@ -315,6 +320,11 @@ func TestFailover_Connect(t *testing.T) { } } + // Apply locality to clusters + for _, v := range testClusters { + setK8sNodeLocality(t, v.context, v) + } + // Peering/Dialer relationship /* cluster-01-a cluster-02-a @@ -388,7 +398,7 @@ func TestFailover_Connect(t *testing.T) { // Setup DNS. for _, v := range testClusters { - dnsService, err := v.context.KubernetesClient(t).CoreV1().Services("default").Get(context.Background(), fmt.Sprintf("%s-%s", releaseName, "consul-dns"), metav1.GetOptions{}) + dnsService, err := v.context.KubernetesClient(t).CoreV1().Services("default").Get(ctx.Background(), fmt.Sprintf("%s-%s", releaseName, "consul-dns"), metav1.GetOptions{}) require.NoError(t, err) v.dnsIP = &dnsService.Spec.ClusterIP logger.Logf(t, "%s dnsIP: %s", v.name, *v.dnsIP) @@ -454,6 +464,15 @@ func TestFailover_Connect(t *testing.T) { testClusters.verifyServerUpState(t, cfg.EnableTransparentProxy) logger.Log(t, "all infrastructure up and running") + // Verify locality is set on services based on node labels previously applied. + // + // This is currently the only locality testing we do for k8s and ensures that single-partition + // locality-aware routing will function in consul-k8s. In the future, this test will be expanded + // to test multi-cluster locality-based failover with sameness groups. + for _, v := range testClusters { + checkLocalities(t, v) + } + // Verify all the failover Scenarios logger.Log(t, "verifying failover scenarios") @@ -536,9 +555,9 @@ func TestFailover_Connect(t *testing.T) { logger.Log(t, "checking service failover") if cfg.EnableTransparentProxy { - serviceFailoverCheck(t, sc.server, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) + serviceTargetCheck(t, sc.server, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) } else { - serviceFailoverCheck(t, sc.server, v.failoverServer.name, "localhost:8080") + serviceTargetCheck(t, sc.server, v.failoverServer.name, "localhost:8080") } // Verify DNS @@ -578,6 +597,7 @@ type expectedPQ struct { type cluster struct { name string partition string + locality api.Locality context environment.TestContext helmCluster *consul.HelmCluster client *api.Client @@ -613,7 +633,7 @@ func (c clusters) resetScale(t *testing.T) { func (c clusters) setServerIP(t *testing.T) { for _, labelSelector := range []string{"app=static-server", "app=static-client"} { for k, v := range c { - podList, err := v.context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), + podList, err := v.context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(ctx.Background(), metav1.ListOptions{LabelSelector: labelSelector}) require.NoError(t, err) require.Len(t, podList.Items, 1) @@ -635,9 +655,9 @@ func (c clusters) verifyServerUpState(t *testing.T, isTproxyEnabled bool) { for _, v := range c { // Query using a client and expect its own name, no failover should occur if isTproxyEnabled { - serviceFailoverCheck(t, v, v.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", v.fullTextPartition())) + serviceTargetCheck(t, v, v.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", v.fullTextPartition())) } else { - serviceFailoverCheck(t, v, v.name, "localhost:8080") + serviceTargetCheck(t, v, v.name, "localhost:8080") } } } @@ -665,16 +685,28 @@ func applyResources(t *testing.T, cfg *config.TestConfig, kustomizeDir string, o }) } -// serviceFailoverCheck verifies that the server failed over as expected by checking that curling the `static-server` -// using the `static-client` responds with the expected cluster name. Each static-server responds with a uniquue -// name so that we can verify failover occured as expected. -func serviceFailoverCheck(t *testing.T, server *cluster, expectedName string, curlAddress string) { +// setK8sNodeLocality labels the k8s node corresponding to the given cluster with standard labels indicating the +// locality of that node. These are propagated by connect-inject to registered Consul services. +func setK8sNodeLocality(t *testing.T, context environment.TestContext, c *cluster) { + nodeList, err := context.KubernetesClient(t).CoreV1().Nodes().List(ctx.Background(), metav1.ListOptions{}) + require.NoError(t, err) + // Get the name of the (only) node from the Kind cluster. + node := nodeList.Items[0].Name + k8s.KubectlLabel(t, context.KubectlOptions(t), "node", node, corev1.LabelTopologyRegion, c.locality.Region) + k8s.KubectlLabel(t, context.KubectlOptions(t), "node", node, corev1.LabelTopologyZone, c.locality.Zone) +} + +// serviceTargetCheck verifies that curling the `static-server` using the `static-client` responds with the expected +// cluster name. Each static-server responds with a unique name so that we can verify failover occured as expected. +func serviceTargetCheck(t *testing.T, server *cluster, expectedName string, curlAddress string) { timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} var resp string var err error retry.RunWith(timer, t, func(r *retry.R) { + // Use -s/--silent and -S/--show-error flags w/ curl to reduce noise during retries. + // This silences extra output like the request progress bar, but preserves errors. resp, err = k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", - staticClientDeployment, "-c", staticClientName, "--", "curl", curlAddress) + staticClientDeployment, "-c", staticClientName, "--", "curl", "-sS", curlAddress) require.NoError(r, err) assert.Contains(r, resp, expectedName) }) @@ -776,3 +808,39 @@ func getPeeringAcceptorSecret(t *testing.T, cfg *config.TestConfig, server *clus return acceptorSecretName } + +// localityForRegion returns the full api.Locality to use in tests for a given region string. +func localityForRegion(r string) api.Locality { + return api.Locality{ + Region: r, + Zone: r + "a", + } +} + +// checkLocalities checks the given cluster for `static-client` and `static-server` instances matching the locality +// expected for the cluster. +func checkLocalities(t *testing.T, c *cluster) { + for ns, svcs := range map[string][]string{ + staticClientNamespace: { + staticClientName, + staticClientName + "-sidecar-proxy", + }, + staticServerNamespace: { + staticServerName, + staticServerName + "-sidecar-proxy", + }, + } { + for _, svc := range svcs { + cs := getCatalogService(t, c, svc, ns, c.partition) + assert.NotNil(t, cs.ServiceLocality, "service %s in %s did not have locality set", svc, c.name) + assert.Equal(t, c.locality, *cs.ServiceLocality, "locality for service %s in %s did not match expected", svc, c.name) + } + } +} + +func getCatalogService(t *testing.T, c *cluster, svc, ns, partition string) *api.CatalogService { + resp, _, err := c.client.Catalog().Service(svc, "", &api.QueryOptions{Namespace: ns, Partition: partition}) + require.NoError(t, err) + assert.NotEmpty(t, resp, "did not find service %s in cluster %s (partition=%s ns=%s)", svc, c.name, partition, ns) + return resp[0] +} From 13f42c26c38ee3ed7e0344a58f8dd70d003d7884 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Mon, 21 Aug 2023 16:18:23 -0400 Subject: [PATCH 334/592] Use Kubernetes 1.25 on AKS (#2801) --- charts/consul/test/terraform/aks/variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/consul/test/terraform/aks/variables.tf b/charts/consul/test/terraform/aks/variables.tf index 9115517029..bb8d48251a 100644 --- a/charts/consul/test/terraform/aks/variables.tf +++ b/charts/consul/test/terraform/aks/variables.tf @@ -7,8 +7,8 @@ variable "location" { } variable "kubernetes_version" { - default = "1.25.17" - description = "Kubernetes version supported on AKS (1.25.17 Released August 2nd, 2023)" + default = "1.25" + description = "Kubernetes version supported on AKS" } variable "client_id" { From 4c95f8ff8d2041f4eec39ad53d6060ccd39e93b8 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:30:06 -0500 Subject: [PATCH 335/592] Point mod to main to fix build errors (#2805) point mod to main to fix build errors --- control-plane/go.mod | 4 ++-- control-plane/go.sum | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/control-plane/go.mod b/control-plane/go.mod index 1a36ef889c..c199f954d6 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,8 +10,8 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d github.com/hashicorp/consul-server-connection-manager v0.1.3 - github.com/hashicorp/consul/api v1.10.1-0.20230802160219-cf2bf7fdecdf - github.com/hashicorp/consul/sdk v0.14.0 + github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 + github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 github.com/hashicorp/go-hclog v1.5.0 diff --git a/control-plane/go.sum b/control-plane/go.sum index 70c7220dd6..36797f0e3d 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -266,10 +266,18 @@ github.com/hashicorp/consul-server-connection-manager v0.1.3 h1:fxsZ15XBNNWhV26y github.com/hashicorp/consul-server-connection-manager v0.1.3/go.mod h1:Md2IGKaFJ4ek9GUA0pW1S2R60wpquMOUs27GiD9kZd0= github.com/hashicorp/consul/api v1.10.1-0.20230802160219-cf2bf7fdecdf h1:XCfEJhwSx188jLPjctsGoOfG7OueuXaqceR0HwHAH1s= github.com/hashicorp/consul/api v1.10.1-0.20230802160219-cf2bf7fdecdf/go.mod h1:TqtEfVYyzax2gaBwU1vsCGFcysmK9g9UANUWCd8qMBw= +github.com/hashicorp/consul/api v1.10.1-0.20230821140749-587663dbcbd1 h1:HulZABqJoIf1NLkWkXRqH1Vl/OLKiJQBEuuFk/XezuI= +github.com/hashicorp/consul/api v1.10.1-0.20230821140749-587663dbcbd1/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 h1:TTTgXv9YeaRnODyFP1k2b2Nq5RIGrUUgI5SkDhuSNwM= +github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul/api v1.24.0 h1:u2XyStA2j0jnCiVUU7Qyrt8idjRn4ORhK6DlvZ3bWhA= +github.com/hashicorp/consul/api v1.24.0/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= github.com/hashicorp/consul/sdk v0.14.0 h1:Hly+BMNMssVzoWddbBnBFi3W+Fzytvm0haSkihhj3GU= github.com/hashicorp/consul/sdk v0.14.0/go.mod h1:gHYeuDa0+0qRAD6Wwr6yznMBvBwHKoxSBoW5l73+saE= +github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= +github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= 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= From 3c07c5d6cacad7d06087f5f7c96f7722a0b7f062 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:08:56 -0500 Subject: [PATCH 336/592] Fix peer test flakes. (#2812) This commit fixes an issue where the peering tests would flake due to the fact that we were concurrently modifying a global map. It also adds in retry logic so that the consul servers have sufficient time to initialize before attempting to generate peering tokens. --- .../peering_acceptor_controller_test.go | 19 +++++++---- .../peering/peering_dialer_controller_test.go | 34 ++++++++++++------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go index 5a0d37135a..601be1a833 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go @@ -22,7 +22,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -501,7 +500,8 @@ func TestReconcile_CreateUpdatePeeringAcceptor(t *testing.T) { // Create fake k8s client k8sObjects := append(tt.k8sObjects(), &ns) - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() @@ -622,7 +622,8 @@ func TestReconcile_DeletePeeringAcceptor(t *testing.T) { k8sObjects := []runtime.Object{&ns, acceptor} // Add peering types to the scheme. - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() @@ -768,7 +769,8 @@ func TestReconcile_VersionAnnotation(t *testing.T) { // Create fake k8s client k8sObjects := []runtime.Object{acceptor, secret, ns} - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() @@ -1080,7 +1082,8 @@ func TestAcceptorUpdateStatus(t *testing.T) { k8sObjects = append(k8sObjects, tt.peeringAcceptor) // Add peering types to the scheme. - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() // Create the peering acceptor controller. @@ -1192,7 +1195,8 @@ func TestAcceptorUpdateStatusError(t *testing.T) { k8sObjects = append(k8sObjects, tt.acceptor) // Add peering types to the scheme. - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() // Create the peering acceptor controller. @@ -1476,7 +1480,8 @@ func TestAcceptor_RequestsForPeeringTokens(t *testing.T) { for name, tt := range cases { t.Run(name, func(t *testing.T) { - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.secret, &tt.acceptors).Build() controller := AcceptorController{ diff --git a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go index 8d999cbf2a..c142cd9eee 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go @@ -24,7 +24,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -280,9 +279,11 @@ func TestReconcile_CreateUpdatePeeringDialer(t *testing.T) { var encodedPeeringToken string if tt.peeringName != "" { // Create the initial token. - baseToken, _, err := acceptorClient.Peerings().GenerateToken(context.Background(), api.PeeringGenerateTokenRequest{PeerName: tt.peeringName}, nil) - require.NoError(t, err) - encodedPeeringToken = baseToken.PeeringToken + retry.Run(t, func(r *retry.R) { + baseToken, _, err := acceptorClient.Peerings().GenerateToken(context.Background(), api.PeeringGenerateTokenRequest{PeerName: tt.peeringName}, nil) + require.NoError(r, err) + encodedPeeringToken = baseToken.PeeringToken + }) } // If the peering is not supposed to already exist in Consul, then create a secret with the generated token. @@ -314,7 +315,8 @@ func TestReconcile_CreateUpdatePeeringDialer(t *testing.T) { secret.SetResourceVersion("latest-version") k8sObjects = append(k8sObjects, secret) } - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() @@ -482,8 +484,11 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { // Create a peering connection in Consul by generating and establishing a peering connection before calling // Reconcile(). // Generate a token. - generatedToken, _, err := acceptorClient.Peerings().GenerateToken(context.Background(), api.PeeringGenerateTokenRequest{PeerName: "peering"}, nil) - require.NoError(t, err) + var generatedToken *api.PeeringGenerateTokenResponse + retry.Run(t, func(r *retry.R) { + generatedToken, _, err = acceptorClient.Peerings().GenerateToken(context.Background(), api.PeeringGenerateTokenRequest{PeerName: "peering"}, nil) + require.NoError(r, err) + }) // Create test consul server. var testServerCfg *testutil.TestServerConfig @@ -524,7 +529,8 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { secret.SetResourceVersion("latest-version") k8sObjects = append(k8sObjects, secret) - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() @@ -740,7 +746,8 @@ func TestReconcileDeletePeeringDialer(t *testing.T) { k8sObjects := []runtime.Object{ns, dialer} // Add peering types to the scheme. - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() @@ -881,7 +888,8 @@ func TestDialerUpdateStatus(t *testing.T) { k8sObjects = append(k8sObjects, tt.peeringDialer) // Add peering types to the scheme. - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() // Create the peering dialer controller. @@ -993,7 +1001,8 @@ func TestDialerUpdateStatusError(t *testing.T) { k8sObjects = append(k8sObjects, tt.dialer) // Add peering types to the scheme. - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() // Create the peering dialer controller. @@ -1277,7 +1286,8 @@ func TestDialer_RequestsForPeeringTokens(t *testing.T) { for name, tt := range cases { t.Run(name, func(t *testing.T) { - s := scheme.Scheme + s := runtime.NewScheme() + corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.secret, &tt.dialers).Build() controller := PeeringDialerController{ From c064868527f9287df6a74d7a5a3eb4f3d3c3e40e Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:14:43 -0500 Subject: [PATCH 337/592] NET-4806: Fix ACL tokens for pods don't have pod name set (#2808) Fix issue where tokens had missing pod name. Prior to this commit, tokens descriptions would have a missing pod name and would have the form: {pod: "default/"} This poses issues for the endpoints controller, which will try to parse the metadata and use it to clean up the token. Without the pod name, consul-k8s will continually leak tokens. --- .changelog/2808.txt | 3 +++ .../tests/connect/connect_inject_test.go | 12 +++++++++ .../webhook/consul_dataplane_sidecar.go | 25 ++++++++++++++++++- .../webhook/consul_dataplane_sidecar_test.go | 20 +++++++++------ 4 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 .changelog/2808.txt diff --git a/.changelog/2808.txt b/.changelog/2808.txt new file mode 100644 index 0000000000..d6d0270e44 --- /dev/null +++ b/.changelog/2808.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: Fix issue where ACL tokens would have an empty pod name that prevented proper token cleanup. +``` diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index 7c7f17fc0a..ff8b5bf2df 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -152,6 +152,18 @@ func TestConnectInject_CleanupKilledPods(t *testing.T) { } } }) + // Ensure the token is cleaned up + if secure { + retry.Run(t, func(r *retry.R) { + tokens, _, err := consulClient.ACL().TokenList(nil) + require.NoError(r, err) + for _, t := range tokens { + if strings.Contains(t.Description, podName) { + r.Errorf("Found a token that was supposed to be deleted for pod %v", podName) + } + } + }) + } }) } } diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index 68f57ed061..ad2e07fffa 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -98,6 +98,29 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor Name: "DP_SERVICE_NODE_NAME", Value: "$(NODE_NAME)-virtual", }, + // The pod name isn't known currently, so we must rely on the environment variable to fill it in rather than using args. + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, + { + Name: "DP_CREDENTIAL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }, + // This entry exists to support certain versions of consul dataplane, where environment variable entries + // utilize this numbered notation to indicate individual KV pairs in a map. + { + Name: "DP_CREDENTIAL_LOGIN_META1", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }, }, VolumeMounts: []corev1.VolumeMount{ { @@ -208,7 +231,7 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, mpi mu "-credential-type=login", "-login-auth-method="+w.AuthMethod, "-login-bearer-token-path="+bearerTokenFile, - "-login-meta="+fmt.Sprintf("pod=%s/%s", namespace.Name, pod.Name), + // We don't know the pod name at this time, so we must use environment variables to populate the login-meta instead. ) if w.EnableNamespaces { if w.EnableK8SNSMirroring { diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index d83b094d99..f89703d5ad 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -42,7 +42,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.AuthMethod = "test-auth-method" }, additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-meta=pod=k8snamespace/test-pod -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + "-tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with ACLs and namespace mirroring": { webhookSetupFunc: func(w *MeshWebhook) { @@ -51,7 +51,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.EnableK8SNSMirroring = true }, additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-meta=pod=k8snamespace/test-pod -login-namespace=default -service-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + "-login-namespace=default -service-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with ACLs and single destination namespace": { webhookSetupFunc: func(w *MeshWebhook) { @@ -60,7 +60,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.ConsulDestinationNamespace = "test-ns" }, additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-meta=pod=k8snamespace/test-pod -login-namespace=test-ns -service-namespace=test-ns -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + "-login-namespace=test-ns -service-namespace=test-ns -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with ACLs and partitions": { webhookSetupFunc: func(w *MeshWebhook) { @@ -68,7 +68,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.ConsulPartition = "test-part" }, additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-meta=pod=k8snamespace/test-pod -login-partition=test-part -service-partition=test-part -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + "-login-partition=test-part -service-partition=test-part -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with TLS and CA cert provided": { webhookSetupFunc: func(w *MeshWebhook) { @@ -219,11 +219,17 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { } require.Equal(t, expectedProbe, container.ReadinessProbe) require.Nil(t, container.StartupProbe) - require.Len(t, container.Env, 3) + require.Len(t, container.Env, 7) require.Equal(t, container.Env[0].Name, "TMPDIR") require.Equal(t, container.Env[0].Value, "/consul/connect-inject") require.Equal(t, container.Env[2].Name, "DP_SERVICE_NODE_NAME") require.Equal(t, container.Env[2].Value, "$(NODE_NAME)-virtual") + require.Equal(t, container.Env[3].Name, "POD_NAME") + require.Equal(t, container.Env[4].Name, "POD_NAMESPACE") + require.Equal(t, container.Env[5].Name, "DP_CREDENTIAL_LOGIN_META") + require.Equal(t, container.Env[5].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") + require.Equal(t, container.Env[6].Name, "DP_CREDENTIAL_LOGIN_META1") + require.Equal(t, container.Env[6].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") }) } } @@ -631,10 +637,10 @@ func TestHandlerConsulDataplaneSidecar_Multiport(t *testing.T) { expArgs = []string{ "-addresses 1.1.1.1 -grpc-port=8502 -proxy-service-id-path=/consul/connect-inject/proxyid-web " + "-log-level=info -log-json=false -envoy-concurrency=0 -credential-type=login -login-auth-method=test-auth-method " + - "-login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token -login-meta=pod=k8snamespace/test-pod -tls-disabled -envoy-admin-bind-port=19000 -graceful-port=20600 -telemetry-prom-scrape-path=/metrics -- --base-id 0", + "-login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token -tls-disabled -envoy-admin-bind-port=19000 -graceful-port=20600 -telemetry-prom-scrape-path=/metrics -- --base-id 0", "-addresses 1.1.1.1 -grpc-port=8502 -proxy-service-id-path=/consul/connect-inject/proxyid-web-admin " + "-log-level=info -log-json=false -envoy-concurrency=0 -credential-type=login -login-auth-method=test-auth-method " + - "-login-bearer-token-path=/consul/serviceaccount-web-admin/token -login-meta=pod=k8snamespace/test-pod -tls-disabled -envoy-admin-bind-port=19001 -graceful-port=20601 -telemetry-prom-scrape-path=/metrics -- --base-id 1", + "-login-bearer-token-path=/consul/serviceaccount-web-admin/token -tls-disabled -envoy-admin-bind-port=19001 -graceful-port=20601 -telemetry-prom-scrape-path=/metrics -- --base-id 1", } } expSAVolumeMounts := []corev1.VolumeMount{ From d647bee32df6f73249343bd8581f308619b4e532 Mon Sep 17 00:00:00 2001 From: trevorLeonHC <137422945+trevorLeonHC@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:27:21 -0500 Subject: [PATCH 338/592] net-1776, add job lifecycle test and changes to connhelper (#2669) * changes to connhelper, add job lifecycle test * yaml fixes * move around job yaml files, update grace period times * yaml change * timer change * wait for job to start when deploying * fix file paths * Skip Lifecycle Test on t-proxy --------- Co-authored-by: Thomas Eckert --- .../framework/connhelper/connect_helper.go | 108 ++++++++++- acceptance/framework/k8s/deploy.go | 34 +++- .../connect/connect_proxy_lifecycle_test.go | 169 ++++++++++++++++++ .../tests/fixtures/bases/job-client/job.yaml | 27 +++ .../bases/job-client/kustomization.yaml | 7 + .../fixtures/bases/job-client/service.yaml | 10 ++ .../bases/job-client/serviceaccount.yaml | 5 + .../kustomization.yaml | 6 + .../patch.yaml | 12 ++ .../kustomization.yaml | 6 + .../patch.yaml | 12 ++ .../jobs/job-client-inject/kustomization.yaml | 8 + .../cases/jobs/job-client-inject/patch.yaml | 12 ++ 13 files changed, 406 insertions(+), 10 deletions(-) create mode 100644 acceptance/tests/fixtures/bases/job-client/job.yaml create mode 100644 acceptance/tests/fixtures/bases/job-client/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/job-client/service.yaml create mode 100644 acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml create mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 314c0d853a..8a1a1a7870 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -26,6 +26,7 @@ import ( const ( StaticClientName = "static-client" StaticServerName = "static-server" + JobName = "job-client" ) // ConnectHelper configures a Consul cluster for connect injection tests. @@ -170,6 +171,82 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { }) } +// DeployJob deploys a job pod to the Kubernetes +// cluster which will be used to test service mesh connectivity. If the Secure +// flag is true, a pre-check is done to ensure that the ACL tokens for the test +// are deleted. The status of the deployment and injection is checked after the +// deployment is complete to ensure success. +func (c *ConnectHelper) DeployJob(t *testing.T, path string) { + // Check that the ACL token is deleted. + if c.Secure { + // We need to register the cleanup function before we create the + // deployments because golang will execute them in reverse order + // (i.e. the last registered cleanup function will be executed first). + t.Cleanup(func() { + retrier := &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond} + retry.RunWith(retrier, t, func(r *retry.R) { + tokens, _, err := c.ConsulClient.ACL().TokenList(nil) + require.NoError(r, err) + for _, token := range tokens { + require.NotContains(r, token.Description, JobName) + } + }) + }) + } + + logger.Log(t, "creating job-client deployment") + k8s.DeployJob(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, path) + + // Check that job-client has been injected and + // now have 2 containers. + for _, labelSelector := range []string{"app=job-client"} { + podList, err := c.Ctx.KubernetesClient(t).CoreV1().Pods(c.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: labelSelector, + }) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + } +} + +// DeployServer deploys a server pod to the Kubernetes +// cluster which will be used to test service mesh connectivity. If the Secure +// flag is true, a pre-check is done to ensure that the ACL tokens for the test +// are deleted. The status of the deployment and injection is checked after the +// deployment is complete to ensure success. +func (c *ConnectHelper) DeployServer(t *testing.T) { + // Check that the ACL token is deleted. + if c.Secure { + // We need to register the cleanup function before we create the + // deployments because golang will execute them in reverse order + // (i.e. the last registered cleanup function will be executed first). + t.Cleanup(func() { + retrier := &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond} + retry.RunWith(retrier, t, func(r *retry.R) { + tokens, _, err := c.ConsulClient.ACL().TokenList(nil) + require.NoError(r, err) + for _, token := range tokens { + require.NotContains(r, token.Description, StaticServerName) + } + }) + }) + } + + logger.Log(t, "creating static-server deployment") + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + + // Check that static-server has been injected and + // now have 2 containers. + for _, labelSelector := range []string{"app=static-server"} { + podList, err := c.Ctx.KubernetesClient(t).CoreV1().Pods(c.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: labelSelector, + }) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + } +} + // SetupAppNamespace creates a namespace where applications are deployed. This // does nothing if UseAppNamespace is not set. The app namespace is relevant // when testing with restricted PSA enforcement enabled. @@ -215,26 +292,36 @@ func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { // TestConnectionFailureWithoutIntention ensures the connection to the static // server fails when no intentions are configured. -func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T) { +func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T, clientType ...string) { logger.Log(t, "checking that the connection is not successful because there's no intention") opts := c.KubectlOptsForApp(t) + //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. + client := StaticClientName + if len(clientType) > 0 { + client = clientType[0] + } if c.Cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionFailing(t, opts, StaticClientName, "http://static-server") + k8s.CheckStaticServerConnectionFailing(t, opts, client, "http://static-server") } else { - k8s.CheckStaticServerConnectionFailing(t, opts, StaticClientName, "http://localhost:1234") + k8s.CheckStaticServerConnectionFailing(t, opts, client, "http://localhost:1234") } } // CreateIntention creates an intention for the static-server pod to connect to // the static-client pod. -func (c *ConnectHelper) CreateIntention(t *testing.T) { +func (c *ConnectHelper) CreateIntention(t *testing.T, clientType ...string) { logger.Log(t, "creating intention") + //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. + client := StaticClientName + if len(clientType) > 0 { + client = clientType[0] + } _, _, err := c.ConsulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ Kind: api.ServiceIntentions, Name: StaticServerName, Sources: []*api.SourceIntention{ { - Name: StaticClientName, + Name: client, Action: api.IntentionActionAllow, }, }, @@ -244,14 +331,19 @@ func (c *ConnectHelper) CreateIntention(t *testing.T) { // TestConnectionSuccess ensures the static-server pod can connect to the // static-client pod once the intention is set. -func (c *ConnectHelper) TestConnectionSuccess(t *testing.T) { +func (c *ConnectHelper) TestConnectionSuccess(t *testing.T, clientType ...string) { logger.Log(t, "checking that connection is successful") opts := c.KubectlOptsForApp(t) + //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. + client := StaticClientName + if len(clientType) > 0 { + client = clientType[0] + } if c.Cfg.EnableTransparentProxy { // todo: add an assertion that the traffic is going through the proxy - k8s.CheckStaticServerConnectionSuccessful(t, opts, StaticClientName, "http://static-server") + k8s.CheckStaticServerConnectionSuccessful(t, opts, client, "http://static-server") } else { - k8s.CheckStaticServerConnectionSuccessful(t, opts, StaticClientName, "http://localhost:1234") + k8s.CheckStaticServerConnectionSuccessful(t, opts, client, "http://localhost:1234") } } diff --git a/acceptance/framework/k8s/deploy.go b/acceptance/framework/k8s/deploy.go index 828586c8cf..e60f4c4730 100644 --- a/acceptance/framework/k8s/deploy.go +++ b/acceptance/framework/k8s/deploy.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" v1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/util/yaml" ) @@ -72,6 +73,32 @@ func DeployKustomize(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailu RunKubectl(t, options, "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", deployment.Name)) } +func DeployJob(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory, kustomizeDir string) { + t.Helper() + + KubectlApplyK(t, options, kustomizeDir) + + output, err := RunKubectlAndGetOutputE(t, options, "kustomize", kustomizeDir) + require.NoError(t, err) + + job := batchv1.Job{} + err = yaml.NewYAMLOrJSONDecoder(strings.NewReader(output), 1024).Decode(&job) + require.NoError(t, err) + + helpers.Cleanup(t, noCleanupOnFailure, noCleanup, func() { + // Note: this delete command won't wait for pods to be fully terminated. + // This shouldn't cause any test pollution because the underlying + // objects are deployments, and so when other tests create these + // they should have different pod names. + WritePodsDebugInfoIfFailed(t, options, debugDirectory, labelMapToString(job.GetLabels())) + KubectlDeleteK(t, options, kustomizeDir) + }) + logger.Log(t, "job deployed") + + // Because Jobs don't have a "started" condition, we have to check the status of the Pods they create. + RunKubectl(t, options, "wait", "--for=condition=Ready", "--timeout=5m", "pods", "--selector", fmt.Sprintf("job-name=%s", job.Name)) +} + // CheckStaticServerConnection execs into a pod of sourceApp // and runs a curl command with the provided curlArgs. // This function assumes that the connection is made to the static-server and expects the output @@ -93,7 +120,10 @@ func CheckStaticServerConnection(t *testing.T, options *k8s.KubectlOptions, sour // on the existence of any of them. func CheckStaticServerConnectionMultipleFailureMessages(t *testing.T, options *k8s.KubectlOptions, sourceApp string, expectSuccess bool, failureMessages []string, expectedSuccessOutput string, curlArgs ...string) { t.Helper() - + resourceType := "deploy/" + if sourceApp == "job-client" { + resourceType = "jobs/" + } expectedOutput := "hello world" if expectedSuccessOutput != "" { expectedOutput = expectedSuccessOutput @@ -101,7 +131,7 @@ func CheckStaticServerConnectionMultipleFailureMessages(t *testing.T, options *k retrier := &retry.Timer{Timeout: 320 * time.Second, Wait: 2 * time.Second} - args := []string{"exec", "deploy/" + sourceApp, "-c", sourceApp, "--", "curl", "-vvvsSf"} + args := []string{"exec", resourceType + sourceApp, "-c", sourceApp, "--", "curl", "-vvvsSf"} args = append(args, curlArgs...) retry.RunWith(retrier, t, func(r *retry.R) { diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go index 39dd0b4ae7..d24a36883b 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -205,3 +205,172 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { }) } } + +func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { + cfg := suite.Config() + + if cfg.EnableTransparentProxy { + // TODO t-eckert: Come back and review this with wise counsel. + t.Skip("Skipping test because transparent proxy is enabled") + } + + defaultGracePeriod := 5 + + cases := map[string]int{ + "../fixtures/cases/jobs/job-client-inject": defaultGracePeriod, + "../fixtures/cases/jobs/job-client-inject-grace-period-0s": 0, + "../fixtures/cases/jobs/job-client-inject-grace-period-10s": 10, + } + + // Set up the installation and static-server once. + ctx := suite.Environment().DefaultContext(t) + releaseName := helpers.RandomName() + + connHelper := connhelper.ConnectHelper{ + ClusterKind: consul.Helm, + ReleaseName: releaseName, + Ctx: ctx, + Cfg: cfg, + HelmValues: map[string]string{ + "connectInject.sidecarProxy.lifecycle.defaultShutdownGracePeriodSeconds": strconv.FormatInt(int64(defaultGracePeriod), 10), + "connectInject.sidecarProxy.lifecycle.defaultEnabled": strconv.FormatBool(true), + }, + } + + connHelper.Setup(t) + connHelper.Install(t) + connHelper.DeployServer(t) + + logger.Log(t, "waiting for static-server to be registered with Consul") + retry.RunWith(&retry.Timer{Timeout: 3 * time.Minute, Wait: 5 * time.Second}, t, func(r *retry.R) { + for _, name := range []string{ + "static-server", + "static-server-sidecar-proxy", + } { + logger.Logf(t, "checking for %s service in Consul catalog", name) + instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) + r.Check(err) + + if len(instances) != 1 { + r.Errorf("expected 1 instance of %s", name) + + } + } + }) + + // Iterate over the Job cases and test connection. + for path, gracePeriod := range cases { + connHelper.DeployJob(t, path) // Default case. + + logger.Log(t, "waiting for job-client to be registered with Consul") + retry.RunWith(&retry.Timer{Timeout: 300 * time.Second, Wait: 5 * time.Second}, t, func(r *retry.R) { + for _, name := range []string{ + "job-client", + "job-client-sidecar-proxy", + } { + logger.Logf(t, "checking for %s service in Consul catalog", name) + instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) + r.Check(err) + + if len(instances) != 1 { + r.Errorf("expected 1 instance of %s", name) + } + } + }) + + connHelper.TestConnectionSuccess(t, connhelper.JobName) + + // Get job-client pod name + ns := ctx.KubectlOptions(t).Namespace + pods, err := ctx.KubernetesClient(t).CoreV1().Pods(ns).List( + context.Background(), + metav1.ListOptions{ + LabelSelector: "app=job-client", + }, + ) + require.NoError(t, err) + require.Len(t, pods.Items, 1) + jobName := pods.Items[0].Name + + // Exec into job and send shutdown request to running proxy. + // curl --max-time 2 -s -f -XPOST http://127.0.0.1:20600/graceful_shutdown + sendProxyShutdownArgs := []string{"exec", jobName, "-c", connhelper.JobName, "--", "curl", "--max-time", "2", "-s", "-f", "-XPOST", "http://127.0.0.1:20600/graceful_shutdown"} + _, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), sendProxyShutdownArgs...) + require.NoError(t, err) + + logger.Log(t, "Proxy killed...") + + args := []string{"exec", jobName, "-c", connhelper.JobName, "--", "curl", "-vvvsSf"} + if cfg.EnableTransparentProxy { + args = append(args, "http://static-server") + } else { + args = append(args, "http://localhost:1234") + } + + if gracePeriod > 0 { + logger.Log(t, "Checking if connection successful within grace period...") + retry.RunWith(&retry.Timer{Timeout: time.Duration(gracePeriod) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { + output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) + require.NoError(r, err) + require.True(r, !strings.Contains(output, "curl: (7) Failed to connect")) + }) + //wait for the grace period to end after successful request + time.Sleep(time.Duration(gracePeriod) * time.Second) + } + + // Test that requests fail once grace period has ended, or there was no grace period set. + logger.Log(t, "Checking that requests fail now that proxy is killed...") + retry.RunWith(&retry.Timer{Timeout: 2 * time.Minute, Wait: 2 * time.Second}, t, func(r *retry.R) { + output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) + require.Error(r, err) + require.True(r, strings.Contains(output, "curl: (7) Failed to connect")) + }) + + // Wait for the job to complete. + retry.RunWith(&retry.Timer{Timeout: 4 * time.Minute, Wait: 30 * time.Second}, t, func(r *retry.R) { + logger.Log(t, "Checking if job completed...") + jobs, err := ctx.KubernetesClient(t).BatchV1().Jobs(ns).List( + context.Background(), + metav1.ListOptions{ + LabelSelector: "app=job-client", + }, + ) + require.NoError(r, err) + require.True(r, jobs.Items[0].Status.Succeeded == 1) + }) + + // Delete the job and its associated Pod. + pods, err = ctx.KubernetesClient(t).CoreV1().Pods(ns).List( + context.Background(), + metav1.ListOptions{ + LabelSelector: "app=job-client", + }, + ) + require.NoError(t, err) + podName := pods.Items[0].Name + + err = ctx.KubernetesClient(t).BatchV1().Jobs(ns).Delete(context.Background(), "job-client", metav1.DeleteOptions{}) + require.NoError(t, err) + + err = ctx.KubernetesClient(t).CoreV1().Pods(ns).Delete(context.Background(), podName, metav1.DeleteOptions{}) + require.NoError(t, err) + + logger.Log(t, "ensuring job is deregistered after termination") + retry.RunWith(&retry.Timer{Timeout: 4 * time.Minute, Wait: 30 * time.Second}, t, func(r *retry.R) { + for _, name := range []string{ + "job-client", + "job-client-sidecar-proxy", + } { + logger.Logf(t, "checking for %s service in Consul catalog", name) + instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) + r.Check(err) + + for _, instance := range instances { + if strings.Contains(instance.ServiceID, jobName) { + r.Errorf("%s is still registered", instance.ServiceID) + } + } + } + }) + } +} diff --git a/acceptance/tests/fixtures/bases/job-client/job.yaml b/acceptance/tests/fixtures/bases/job-client/job.yaml new file mode 100644 index 0000000000..cc5f2f2896 --- /dev/null +++ b/acceptance/tests/fixtures/bases/job-client/job.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: job-client + namespace: default + labels: + app: job-client +spec: + template: + metadata: + labels: + app: job-client + spec: + containers: + - name: job-client + image: alpine/curl:3.14 + ports: + - containerPort: 80 + command: + - /bin/sh + - -c + - | + echo "Started test job" + sleep 120 + echo "Ended test job" + serviceAccountName: job-client + restartPolicy: Never diff --git a/acceptance/tests/fixtures/bases/job-client/kustomization.yaml b/acceptance/tests/fixtures/bases/job-client/kustomization.yaml new file mode 100644 index 0000000000..390d19c859 --- /dev/null +++ b/acceptance/tests/fixtures/bases/job-client/kustomization.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ./job.yaml + - service.yaml + - serviceaccount.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/job-client/service.yaml b/acceptance/tests/fixtures/bases/job-client/service.yaml new file mode 100644 index 0000000000..36ec16133a --- /dev/null +++ b/acceptance/tests/fixtures/bases/job-client/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: job-client + namespace: default +spec: + selector: + app: job-client + ports: + - port: 80 \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml b/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml new file mode 100644 index 0000000000..b4637d4d55 --- /dev/null +++ b/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: job-client + namespace: default \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml new file mode 100644 index 0000000000..d87dbf0481 --- /dev/null +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml @@ -0,0 +1,6 @@ + +resources: + - ../../../bases/job-client + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml new file mode 100644 index 0000000000..46d1a417ea --- /dev/null +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml @@ -0,0 +1,12 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: job-client +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + "consul.hashicorp.com/transparent-proxy": "false" + "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" + "consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds": "0" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml new file mode 100644 index 0000000000..d87dbf0481 --- /dev/null +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml @@ -0,0 +1,6 @@ + +resources: + - ../../../bases/job-client + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml new file mode 100644 index 0000000000..4db2df127e --- /dev/null +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml @@ -0,0 +1,12 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: job-client +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + "consul.hashicorp.com/transparent-proxy": "false" + "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" + "consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds": "10" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml new file mode 100644 index 0000000000..0aee0558b7 --- /dev/null +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/job-client + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml new file mode 100644 index 0000000000..5f390c1f7e --- /dev/null +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml @@ -0,0 +1,12 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: job-client +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + "consul.hashicorp.com/transparent-proxy": "false" + "consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds": "5" + "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" \ No newline at end of file From 55cb0403b89a14aac378c53640ee00d62a889f53 Mon Sep 17 00:00:00 2001 From: trevorLeonHC <137422945+trevorLeonHC@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:08:28 -0500 Subject: [PATCH 339/592] Net 1784 inject sidecar first (#2743) * change container creation order. Change order of container creation so that envoy container is created before app container. * change tests to fit proxy container added first * add sidecar first iff lifecycle enabled * update tests to include/exclude lifecycle * container ordering in multiport + lifecycle, test case * create changelog * change exec calls to specify container specify containers when exec'ing * Update 2743.txt * small fixes to appending sidecar --- .changelog/2743.txt | 3 + .../framework/connhelper/connect_helper.go | 4 +- .../connect/connect_external_servers_test.go | 2 +- .../connect/connect_inject_namespaces_test.go | 2 +- .../tests/connect/connect_inject_test.go | 4 +- acceptance/tests/metrics/metrics_test.go | 8 +- .../partitions/partitions_connect_test.go | 8 +- .../connect-inject/webhook/mesh_webhook.go | 36 ++++- .../webhook/mesh_webhook_test.go | 153 ++++++++++++++++++ 9 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 .changelog/2743.txt diff --git a/.changelog/2743.txt b/.changelog/2743.txt new file mode 100644 index 0000000000..4e8db233b1 --- /dev/null +++ b/.changelog/2743.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: Changed the container ordering in connect-inject to insert consul-dataplane container first if lifecycle is enabled. Container ordering is unchanged if lifecycle is disabled. +``` diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 8a1a1a7870..2058fd955c 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -357,7 +357,7 @@ func (c *ConnectHelper) TestConnectionFailureWhenUnhealthy(t *testing.T) { opts := c.KubectlOptsForApp(t) logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, // CheckStaticServerConnection will retry until Consul marks the service @@ -382,7 +382,7 @@ func (c *ConnectHelper) TestConnectionFailureWhenUnhealthy(t *testing.T) { } // Return the static-server to a "healthy state". - k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "--", "rm", "/tmp/unhealthy") + k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "-c", "static-server", "--", "rm", "/tmp/unhealthy") } // helmValues uses the Secure and AutoEncrypt fields to set values for the Helm diff --git a/acceptance/tests/connect/connect_external_servers_test.go b/acceptance/tests/connect/connect_external_servers_test.go index 46f9e14573..c0a61f160f 100644 --- a/acceptance/tests/connect/connect_external_servers_test.go +++ b/acceptance/tests/connect/connect_external_servers_test.go @@ -128,7 +128,7 @@ func TestConnectInject_ExternalServers(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+connhelper.StaticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+connhelper.StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/connect/connect_inject_namespaces_test.go b/acceptance/tests/connect/connect_inject_namespaces_test.go index 7b7785a44f..03200cc75b 100644 --- a/acceptance/tests/connect/connect_inject_namespaces_test.go +++ b/acceptance/tests/connect/connect_inject_namespaces_test.go @@ -221,7 +221,7 @@ func TestConnectInjectNamespaces(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, staticServerOpts, "exec", "deploy/"+connhelper.StaticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, staticServerOpts, "exec", "deploy/"+connhelper.StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index ff8b5bf2df..13fb41f562 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -310,9 +310,9 @@ func TestConnectInject_MultiportServices(t *testing.T) { // and check inbound connections to the multi port pods' services. // Create the files so that the readiness probes of the multi port pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the multiport unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "--", "touch", "/tmp/unhealthy-multiport") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport", "--", "touch", "/tmp/unhealthy-multiport") logger.Log(t, "testing k8s -> consul health checks sync by making the multiport-admin unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "--", "touch", "/tmp/unhealthy-multiport-admin") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport-admin", "--", "touch", "/tmp/unhealthy-multiport-admin") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/metrics/metrics_test.go b/acceptance/tests/metrics/metrics_test.go index 3d40841e36..ca80d38b75 100644 --- a/acceptance/tests/metrics/metrics_test.go +++ b/acceptance/tests/metrics/metrics_test.go @@ -74,12 +74,12 @@ func TestComponentMetrics(t *testing.T) { k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") // Server Metrics - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:8500/v1/agent/metrics?format=prometheus", fmt.Sprintf("%s-consul-server.%s.svc", releaseName, ns))) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:8500/v1/agent/metrics?format=prometheus", fmt.Sprintf("%s-consul-server.%s.svc", releaseName, ns))) require.NoError(t, err) require.Contains(t, metricsOutput, `consul_acl_ResolveToken{quantile="0.5"}`) // Client Metrics - metricsOutput, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "sh", "-c", "curl --silent --show-error http://$HOST_IP:8500/v1/agent/metrics?format=prometheus") + metricsOutput, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "sh", "-c", "curl --silent --show-error http://$HOST_IP:8500/v1/agent/metrics?format=prometheus") require.NoError(t, err) require.Contains(t, metricsOutput, `consul_acl_ResolveToken{quantile="0.5"}`) @@ -133,7 +133,7 @@ func TestAppMetrics(t *testing.T) { // Retry because sometimes the merged metrics server takes a couple hundred milliseconds // to start. retry.RunWith(&retry.Counter{Count: 20, Wait: 2 * time.Second}, t, func(r *retry.R) { - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) require.NoError(r, err) // This assertion represents the metrics from the envoy sidecar. require.Contains(r, metricsOutput, `envoy_cluster_assignment_stale{local_cluster="server",consul_source_service="server"`) @@ -147,7 +147,7 @@ func assertGatewayMetricsEnabled(t *testing.T, ctx environment.TestContext, ns, require.NoError(t, err) for _, pod := range pods.Items { podIP := pod.Status.PodIP - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) require.NoError(t, err) require.Contains(t, metricsOutput, metricsAssertion) } diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index c53e5b6171..444e70b952 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -405,8 +405,8 @@ func TestPartitions_Connect(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") - k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. @@ -578,8 +578,8 @@ func TestPartitions_Connect(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") - k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index d97bca6646..69d0f96c75 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -294,7 +294,10 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi // port. annotatedSvcNames := w.annotatedServiceNames(pod) multiPort := len(annotatedSvcNames) > 1 - + lifecycleEnabled, ok := w.LifecycleConfig.EnableProxyLifecycle(pod) + if ok != nil { + w.Log.Error(err, "unable to get lifecycle enabled status") + } // For single port pods, add the single init container and envoy sidecar. if !multiPort { // Add the init container that registers the service and sets up the Envoy configuration. @@ -311,8 +314,14 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) } - // TODO: invert to start the Envoy sidecar before the application container - pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) + //Append the Envoy sidecar before the application container only if lifecycle enabled. + + if lifecycleEnabled && ok == nil { + pod.Spec.Containers = append([]corev1.Container{envoySidecar}, pod.Spec.Containers...) + } else { + pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) + } + } else { // For multi port pods, check for unsupported cases, mount all relevant service account tokens, and mount an init // container and envoy sidecar per port. Tproxy, metrics, and metrics merging are not supported for multi port pods. @@ -327,6 +336,10 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "checking unsupported cases for multi port pods") return admission.Errored(http.StatusInternalServerError, err) } + + //List of sidecar containers for each service. Build as a list to preserve correct ordering in relation + //to services. + sidecarContainers := []corev1.Container{} for i, svc := range annotatedSvcNames { w.Log.Info(fmt.Sprintf("service: %s", svc)) if w.AuthMethod != "" { @@ -382,9 +395,20 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) } - // TODO: invert to start the Envoy sidecar container before the - // application container - pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) + // If Lifecycle is enabled, add to the list of sidecar containers to be added + // to pod containers at the end in order to preserve relative ordering. + if lifecycleEnabled { + sidecarContainers = append(sidecarContainers, envoySidecar) + } else { + pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) + + } + + } + + //Add sidecar containers first if lifecycle enabled. + if lifecycleEnabled { + pod.Spec.Containers = append(sidecarContainers, pod.Spec.Containers...) } } diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index 946933f7d7..c709c830b4 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -12,6 +12,7 @@ import ( mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/namespaces" @@ -138,6 +139,73 @@ func TestHandlerHandle(t *testing.T) { }, }, }, + { + "empty pod basic with lifecycle", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations", + }, + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + + { + Operation: "add", + Path: "/spec/containers/0/readinessProbe", + }, + { + Operation: "add", + Path: "/spec/containers/0/securityContext", + }, + { + Operation: "replace", + Path: "/spec/containers/0/name", + }, + { + Operation: "add", + Path: "/spec/containers/0/args", + }, + { + Operation: "add", + Path: "/spec/containers/0/env", + }, + { + Operation: "add", + Path: "/spec/containers/0/volumeMounts", + }, + }, + }, { "pod with upstreams specified", @@ -811,6 +879,91 @@ func TestHandlerHandle(t *testing.T) { }, }, }, + { + "multiport pod kube < 1.24 with AuthMethod, serviceaccount has secret ref, lifecycle enabled", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: testClientWithServiceAccountAndSecretRefs(), + AuthMethod: "k8s", + LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "web,web-admin", + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/containers/0/env", + }, + { + Operation: "add", + Path: "/spec/containers/0/volumeMounts", + }, + { + Operation: "add", + Path: "/spec/containers/0/readinessProbe", + }, + { + Operation: "add", + Path: "/spec/containers/0/securityContext", + }, + { + Operation: "replace", + Path: "/spec/containers/0/name", + }, + { + Operation: "add", + Path: "/spec/containers/0/args", + }, + + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/spec/containers/2", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + }, + }, { "dns redirection enabled", MeshWebhook{ From b75d8034b96ae1e21c0cca66ad5ee9a63af20505 Mon Sep 17 00:00:00 2001 From: David Yu Date: Wed, 23 Aug 2023 10:58:51 -0700 Subject: [PATCH 340/592] Add readOnlyRootFilesystem to security context (#2771) (#2789) * Add readOnlyRootFilesystem to security context (#2771) --------- Co-authored-by: mr-miles Co-authored-by: Paul Glass --- .changelog/2789.txt | 3 +++ charts/consul/templates/_helpers.tpl | 1 + charts/consul/templates/ingress-gateways-deployment.yaml | 7 +++++++ charts/consul/templates/server-statefulset.yaml | 5 +++++ .../consul/templates/terminating-gateways-deployment.yaml | 7 +++++++ charts/consul/test/unit/server-statefulset.bats | 2 ++ 6 files changed, 25 insertions(+) create mode 100644 .changelog/2789.txt diff --git a/.changelog/2789.txt b/.changelog/2789.txt new file mode 100644 index 0000000000..c8db7d0f08 --- /dev/null +++ b/.changelog/2789.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: Add readOnlyRootFilesystem to the default restricted security context when runnning `consul-k8s` in a restricted namespaces. +``` diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 18f57b188c..e2a6a7fd6b 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -19,6 +19,7 @@ as well as the global.name setting. {{- if not .Values.global.enablePodSecurityPolicies -}} securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: true capabilities: drop: - ALL diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index c10f1549f6..d755a80e39 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -154,6 +154,9 @@ spec: terminationGracePeriodSeconds: {{ default $defaults.terminationGracePeriodSeconds .terminationGracePeriodSeconds }} serviceAccountName: {{ template "consul.fullname" $root }}-{{ .name }} volumes: + - name: tmp + emptyDir: + medium: "Memory" - name: consul-service emptyDir: medium: "Memory" @@ -215,6 +218,8 @@ spec: -log-level={{ default $root.Values.global.logLevel $root.Values.ingressGateways.logLevel }} \ -log-json={{ $root.Values.global.logJSON }} volumeMounts: + - name: tmp + mountPath: /tmp - name: consul-service mountPath: /consul/service {{- if $root.Values.global.tls.enabled }} @@ -239,6 +244,8 @@ spec: resources: {{ toYaml (default $defaults.resources .resources) | nindent 10 }} {{- end }} volumeMounts: + - name: tmp + mountPath: /tmp - name: consul-service mountPath: /consul/service readOnly: true diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 2ad04f0755..23343a4335 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -141,6 +141,8 @@ spec: {{- toYaml .Values.server.securityContext | nindent 8 }} {{- end }} volumes: + - name: tmp + emptyDir: {} - name: config configMap: name: {{ template "consul.fullname" . }}-server-config @@ -450,6 +452,9 @@ spec: mountPath: /trusted-cas readOnly: false {{- end }} + - name: tmp + mountPath: /tmp + readOnly: false ports: {{- if (or (not .Values.global.tls.enabled) (not .Values.global.tls.httpsOnly)) }} - name: http diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index 9433e44bc9..ccfcf3c6a6 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -123,6 +123,9 @@ spec: terminationGracePeriodSeconds: 10 serviceAccountName: {{ template "consul.fullname" $root }}-{{ .name }} volumes: + - name: tmp + emptyDir: + medium: "Memory" - name: consul-service emptyDir: medium: "Memory" @@ -200,6 +203,8 @@ spec: -log-level={{ default $root.Values.global.logLevel $root.Values.terminatingGateways.logLevel }} \ -log-json={{ $root.Values.global.logJSON }} volumeMounts: + - name: tmp + mountPath: /tmp - name: consul-service mountPath: /consul/service {{- if $root.Values.global.tls.enabled }} @@ -221,6 +226,8 @@ spec: image: {{ $root.Values.global.imageConsulDataplane | quote }} {{- include "consul.restrictedSecurityContext" $ | nindent 10 }} volumeMounts: + - name: tmp + mountPath: /tmp - name: consul-service mountPath: /consul/service readOnly: true diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index a60884d20c..52475973f5 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -858,6 +858,7 @@ load _helpers "capabilities": { "drop": ["ALL"] }, + "readOnlyRootFilesystem": true, "runAsNonRoot": true, "seccompProfile": { "type": "RuntimeDefault" @@ -889,6 +890,7 @@ load _helpers "capabilities": { "drop": ["ALL"] }, + "readOnlyRootFilesystem": true, "runAsNonRoot": true, "seccompProfile": { "type": "RuntimeDefault" From 3fba398ed98944897dcec18c0a0ff82145c387bc Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Wed, 23 Aug 2023 16:29:51 -0400 Subject: [PATCH 341/592] feat: func to create V2 resource client (#2823) --- control-plane/consul/consul.go | 9 +- control-plane/consul/resource_client.go | 40 ++++++++ control-plane/consul/resource_client_test.go | 102 +++++++++++++++++++ control-plane/go.mod | 34 ++++--- control-plane/go.sum | 85 +++++++--------- 5 files changed, 204 insertions(+), 66 deletions(-) create mode 100644 control-plane/consul/resource_client.go create mode 100644 control-plane/consul/resource_client_test.go diff --git a/control-plane/consul/consul.go b/control-plane/consul/consul.go index 3cf916ffbf..8dea334607 100644 --- a/control-plane/consul/consul.go +++ b/control-plane/consul/consul.go @@ -8,9 +8,10 @@ import ( "net/http" "time" - "github.com/hashicorp/consul-k8s/control-plane/version" "github.com/hashicorp/consul-server-connection-manager/discovery" capi "github.com/hashicorp/consul/api" + + "github.com/hashicorp/consul-k8s/control-plane/version" ) //go:generate mockery --name ServerConnectionManager --inpkg @@ -20,7 +21,7 @@ type ServerConnectionManager interface { Stop() } -// NewClient returns a Consul API client. It adds a required User-Agent +// NewClient returns a V1 Consul API client. It adds a required User-Agent // header that describes the version of consul-k8s making the call. func NewClient(config *capi.Config, consulAPITimeout time.Duration) (*capi.Client, error) { if consulAPITimeout <= 0 { @@ -69,7 +70,7 @@ type Config struct { } // todo (ishustava): replace all usages of this one. -// NewClientFromConnMgrState creates a new API client with an IP address from the state +// NewClientFromConnMgrState creates a new V1 API client with an IP address from the state // of the consul-server-connection-manager. func NewClientFromConnMgrState(config *Config, state discovery.State) (*capi.Client, error) { ipAddress := state.Address.IP @@ -80,7 +81,7 @@ func NewClientFromConnMgrState(config *Config, state discovery.State) (*capi.Cli return NewClient(config.APIClientConfig, config.APITimeout) } -// NewClientFromConnMgr creates a new API client by first getting the state of the passed watcher. +// NewClientFromConnMgr creates a new V1 API client by first getting the state of the passed watcher. func NewClientFromConnMgr(config *Config, watcher ServerConnectionManager) (*capi.Client, error) { // Create a new consul client. serverState, err := watcher.State() diff --git a/control-plane/consul/resource_client.go b/control-plane/consul/resource_client.go new file mode 100644 index 0000000000..e476f4e737 --- /dev/null +++ b/control-plane/consul/resource_client.go @@ -0,0 +1,40 @@ +package consul + +import ( + "context" + "fmt" + + "github.com/hashicorp/consul-server-connection-manager/discovery" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/go-hclog" +) + +// NewResourceServiceClient creates a pbresource.ResourceServiceClient for creating V2 Consul resources. +// It is initialized with a consul-server-connection-manager discovery config to continuously find Consul +// server addresses. +// The caller should make sure to Stop() the returned `watcher` (preferably with a `defer`) to clean up the gRPC +// connection and the discovery client. +// The caller can also set `config.ServerWatchDisabled=false` to prevent subscribing to Consul server address +// changes, as is the case with single-shot operations. +func NewResourceServiceClient(ctx context.Context, config discovery.Config, logger hclog.Logger, hack int) (pbresource.ResourceServiceClient, *discovery.Watcher, error) { + + watcher, err := discovery.NewWatcher(ctx, config, logger.Named("consul-server-connection-manager")) + if err != nil { + return nil, nil, fmt.Errorf("unable to create Consul server watcher: %w", err) + } + + go watcher.Run() + + // We recycle the GRPC connection from the discovery client because it + // should have all the necessary dial options, including the resolver that + // continuously updates Consul server addresses. Otherwise, a lot of code from consul-server-connection-manager + // would need to be duplicated + state, err := watcher.State() + if err != nil { + watcher.Stop() + return nil, nil, fmt.Errorf("unable to get connection manager state: %w", err) + } + resourceClient := pbresource.NewResourceServiceClient(state.GRPCConn) + + return resourceClient, watcher, nil +} diff --git a/control-plane/consul/resource_client_test.go b/control-plane/consul/resource_client_test.go new file mode 100644 index 0000000000..7dd9ef0666 --- /dev/null +++ b/control-plane/consul/resource_client_test.go @@ -0,0 +1,102 @@ +package consul + +import ( + "context" + "testing" + + "github.com/hashicorp/consul-server-connection-manager/discovery" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/anypb" +) + +func Test_NewResourceServiceClient(t *testing.T) { + + var serverConfig *testutil.TestServerConfig + server, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + serverConfig = c + }) + require.NoError(t, err) + defer server.Stop() + + server.WaitForLeader(t) + server.WaitForActiveCARoot(t) + + t.Logf("server grpc address on %d", serverConfig.Ports.GRPC) + + // Create discovery configuration + discoverConfig := discovery.Config{ + Addresses: "127.0.0.1", + GRPCPort: serverConfig.Ports.GRPC, + } + + opts := hclog.LoggerOptions{Name: "resource-service-client"} + logger := hclog.New(&opts) + client, watcher, err := NewResourceServiceClient(context.Background(), discoverConfig, logger, serverConfig.Ports.GRPCTLS) + require.NoError(t, err) + require.NotNil(t, client) + require.NotNil(t, watcher) + + defer watcher.Stop() + + req := createWriteRequest(t, "foo") + res, err := client.Write(context.Background(), req) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, "foo", res.GetResource().GetId().GetName()) + + // NOTE: currently it isn't possible to test that the grpc connection responds to changes in the + // discovery server. The discovery response only includes the IP address of the host, so all servers + // for a local test are de-duplicated as a single entry. +} + +func createWriteRequest(t *testing.T, name string) *pbresource.WriteRequest { + + workload := &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + }, + "mesh": { + Port: 20000, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: "k8s-node-0-virtual", + Identity: name, + } + + proto, err := anypb.New(workload) + require.NoError(t, err) + + req := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Workload", + }, + Tenancy: &pbresource.Tenancy{ + Partition: "default", + Namespace: "default", + }, + }, + Data: proto, + }, + } + return req +} diff --git a/control-plane/go.mod b/control-plane/go.mod index c199f954d6..19375ddf5c 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -1,5 +1,10 @@ module github.com/hashicorp/consul-k8s/control-plane +// TODO(dans) +// This points to a commit on a dev branch. The replace directive should be removed when the SDK is published +// Even after this commit goes into main, the replace directive is needed be because `api` requires 0.14.1 of SDK +replace github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230821222840-992198e5f8c7 + require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 @@ -8,9 +13,8 @@ require ( github.com/go-logr/logr v1.2.3 github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d - github.com/hashicorp/consul-server-connection-manager v0.1.3 github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 + github.com/hashicorp/consul/proto-public v0.1.2-0.20230821180813-217d305b38d5 // this points to a commit on Consul main github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 @@ -43,7 +47,14 @@ require ( ) require ( - cloud.google.com/go v0.65.0 // indirect + github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230821203006-4c95f8ff8d20 + github.com/hashicorp/consul-server-connection-manager v0.1.4 + google.golang.org/protobuf v1.30.0 +) + +require ( + cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/azure-sdk-for-go v44.0.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.18 // indirect @@ -62,7 +73,7 @@ require ( github.com/bgentry/speakeasy v0.1.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 // indirect github.com/digitalocean/godo v1.7.5 // indirect @@ -84,9 +95,9 @@ require ( github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect + github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/gophercloud/gophercloud v0.1.0 // indirect - github.com/hashicorp/consul/proto-public v0.1.0 // 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 @@ -140,22 +151,21 @@ require ( github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480 // indirect github.com/vmware/govmomi v0.18.0 // indirect - go.opencensus.io v0.22.4 // indirect + go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.13.0 // indirect - golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect + golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/term v0.10.0 // indirect golang.org/x/tools v0.7.0 // indirect - google.golang.org/api v0.30.0 // indirect + google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect - google.golang.org/grpc v1.49.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.55.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 36797f0e3d..245b7d913a 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -12,16 +12,21 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -69,7 +74,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= @@ -95,8 +99,9 @@ github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8 github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -104,11 +109,6 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D 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/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containernetworking/cni v1.1.1 h1:ky20T7c0MvKvbMOwS/FrlbNwjEoqJEUUYfsL4b0mc4k= github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -132,8 +132,6 @@ github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -153,7 +151,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -228,9 +225,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= @@ -254,30 +251,24 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 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/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d h1:RJ1MZ8JKnfgKQ1kR3IBQAMpOpzXrdseZAYN/QR//MFM= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= -github.com/hashicorp/consul-server-connection-manager v0.1.3 h1:fxsZ15XBNNWhV26yBVdCcnxHwSRgf9wqHGS2ZVCQIhc= -github.com/hashicorp/consul-server-connection-manager v0.1.3/go.mod h1:Md2IGKaFJ4ek9GUA0pW1S2R60wpquMOUs27GiD9kZd0= -github.com/hashicorp/consul/api v1.10.1-0.20230802160219-cf2bf7fdecdf h1:XCfEJhwSx188jLPjctsGoOfG7OueuXaqceR0HwHAH1s= -github.com/hashicorp/consul/api v1.10.1-0.20230802160219-cf2bf7fdecdf/go.mod h1:TqtEfVYyzax2gaBwU1vsCGFcysmK9g9UANUWCd8qMBw= -github.com/hashicorp/consul/api v1.10.1-0.20230821140749-587663dbcbd1 h1:HulZABqJoIf1NLkWkXRqH1Vl/OLKiJQBEuuFk/XezuI= -github.com/hashicorp/consul/api v1.10.1-0.20230821140749-587663dbcbd1/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230821203006-4c95f8ff8d20 h1:zGzmrEaOhcDDAshfZcj+K0kSSsg4OIV7bf0GlSKFJ74= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230821203006-4c95f8ff8d20/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= +github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= +github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 h1:TTTgXv9YeaRnODyFP1k2b2Nq5RIGrUUgI5SkDhuSNwM= github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= -github.com/hashicorp/consul/api v1.24.0 h1:u2XyStA2j0jnCiVUU7Qyrt8idjRn4ORhK6DlvZ3bWhA= -github.com/hashicorp/consul/api v1.24.0/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= -github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= -github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= -github.com/hashicorp/consul/sdk v0.14.0 h1:Hly+BMNMssVzoWddbBnBFi3W+Fzytvm0haSkihhj3GU= -github.com/hashicorp/consul/sdk v0.14.0/go.mod h1:gHYeuDa0+0qRAD6Wwr6yznMBvBwHKoxSBoW5l73+saE= -github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= -github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230821180813-217d305b38d5 h1:mN5hKOn+G5fQBuXjdne/HluE4FhesUxscZEblqP4OSQ= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230821180813-217d305b38d5/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= +github.com/hashicorp/consul/sdk v0.4.1-0.20230821222840-992198e5f8c7 h1:PZG0XUHJS5f2wie4bmn5CFEUt4dYhNG5lg2Zp5dH3/8= +github.com/hashicorp/consul/sdk v0.4.1-0.20230821222840-992198e5f8c7/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= 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= @@ -507,7 +498,6 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -559,9 +549,9 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -653,6 +643,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= @@ -671,8 +662,9 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= 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= @@ -732,7 +724,6 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -763,7 +754,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -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= @@ -844,8 +834,9 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -877,7 +868,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -885,8 +875,8 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -899,11 +889,9 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= 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= @@ -916,10 +904,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -937,7 +923,6 @@ gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 11ded8c3e4cde4154ec470295314670a16abf2fd Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Wed, 23 Aug 2023 16:30:22 -0400 Subject: [PATCH 342/592] feat: add helm value for consul resource-apis experiment (#2800) * feat: add helm value for consul resource-apis experiment * Apply suggestions from code review Co-authored-by: John Murret * PR feedback part 2 --------- Co-authored-by: John Murret --- charts/consul/templates/_helpers.tpl | 49 +++++++ .../templates/connect-inject-deployment.yaml | 4 + .../consul/templates/partition-init-job.yaml | 3 + .../consul/templates/server-statefulset.yaml | 3 + .../test/unit/connect-inject-deployment.bats | 27 ++++ charts/consul/test/unit/helpers.bats | 137 ++++++++++++++++++ .../consul/test/unit/partition-init-job.bats | 40 +++++ .../consul/test/unit/server-statefulset.bats | 28 ++++ charts/consul/values.yaml | 18 +++ .../subcommand/inject-connect/command.go | 32 ++-- .../subcommand/partition-init/command.go | 17 ++- 11 files changed, 341 insertions(+), 17 deletions(-) diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index e2a6a7fd6b..f3ab8cb636 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -447,3 +447,52 @@ Usage: {{ template "consul.validateTelemetryCollectorCloud" . }} {{fail "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set"}} {{- end }} {{- end -}} + +{{/* +Fails if global.experiments.resourceAPIs is set along with any of these unsupported features. +- global.peering.enabled +- global.federation.enabled +- global.cloud.enabled +- client.enabled +- ui.enabled +- syncCatalog.enabled +- meshGateway.enabled +- ingressGateways.enabled +- terminatingGateways.enabled +- apiGateway.enabled + +Usage: {{ template "consul.validateResourceAPIs" . }} + +*/}} +{{- define "consul.validateResourceAPIs" -}} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.peering.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.federation.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, global.federation.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.cloud.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, global.cloud.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.client.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, client.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.ui.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, ui.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.syncCatalog.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, syncCatalog.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.meshGateway.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, meshGateway.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.ingressGateways.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, ingressGateways.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.terminatingGateways.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, terminatingGateways.enabled is currently unsupported."}} +{{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.apiGateway.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, apiGateway.enabled is currently unsupported."}} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index e726c9ecc9..a1be4a79b7 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -11,6 +11,7 @@ {{- $dnsRedirectionEnabled := (or (and (ne (.Values.dns.enableRedirection | toString) "-") .Values.dns.enableRedirection) (and (eq (.Values.dns.enableRedirection | toString) "-") .Values.connectInject.transparentProxy.defaultEnabled)) -}} {{ template "consul.validateRequiredCloudSecretsExist" . }} {{ template "consul.validateCloudSecretKeys" . }} +{{ template "consul.validateResourceAPIs" . }} # The deployment for running the Connect sidecar injector apiVersion: apps/v1 kind: Deployment @@ -146,6 +147,9 @@ spec: -release-namespace="{{ .Release.Namespace }}" \ -resource-prefix={{ template "consul.fullname" . }} \ -listen=:8080 \ + {{- if (mustHas "resource-apis" .Values.global.experiments) }} + -enable-resource-apis=true + {{- end }} {{- range $k, $v := .Values.connectInject.consulNode.meta }} -node-meta={{ $k }}={{ $v }} \ {{- end }} diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 9209f850c8..34f5888a58 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -114,6 +114,9 @@ spec: {{- if .Values.global.cloud.enabled }} -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} \ {{- end }} + {{- if (mustHas "resource-apis" .Values.global.experiments) }} + -enable-resource-apis=true + {{- end }} resources: requests: memory: "50Mi" diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 23343a4335..d6b44d0c1f 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -417,6 +417,9 @@ spec: {{- if and .Values.global.cloud.enabled .Values.global.cloud.resourceId.secretName }} -hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }" {{- end }} + {{- if (mustHas "resource-apis" .Values.global.experiments) }} + -hcl="experiments=[\"resource-apis\"]" + {{- end }} volumeMounts: - name: data-{{ .Release.Namespace | trunc 58 | trimSuffix "-" }} mountPath: /consul/data diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index ccc6eca68c..7180468cea 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -2587,3 +2587,30 @@ reservedNameTest() { jq -r '. | select( .name == "CONSUL_TLS_SERVER_NAME").value' | tee /dev/stderr) [ "${actual}" = "server.dc1.consul" ] } + +#-------------------------------------------------------------------- +# resource-apis + +@test "connectInject/Deployment: resource-apis is not set by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) + + [ "${actual}" = "false" ] +} + +@test "connectInject/Deployment: -enable-resource-apis=true is set when global.experiments contains [\"resource-apis\"] " { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) + + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/helpers.bats b/charts/consul/test/unit/helpers.bats index efcbc116b5..82fc54bcf2 100644 --- a/charts/consul/test/unit/helpers.bats +++ b/charts/consul/test/unit/helpers.bats @@ -327,3 +327,140 @@ load _helpers actual=$(echo $object | jq '.volumeMounts[] | select(.name == "consul-ca-cert")') [ "${actual}" = "" ] } + +#-------------------------------------------------------------------- +# consul.validateResourceAPIs +# These tests use test-runner.yaml to test the +# consul.validateResourceAPIs helper since we need an existing template + +@test "connectInject/Deployment: fails if resource-apis is set and peering is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'global.tls.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'global.peering.enabled=true' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and federation is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'global.tls.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'global.federation.enabled=true' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.federation.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and cloud is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.resourceId.secretName=hello' \ + --set 'global.cloud.resourceId.secretKey=hello' \ + --set 'global.cloud.clientId.secretName=hello' \ + --set 'global.cloud.clientId.secretKey=hello' \ + --set 'global.cloud.clientSecret.secretName=hello' \ + --set 'global.cloud.clientSecret.secretKey=hello' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.cloud.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and client is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'client.enabled=true' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, client.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and ui is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, ui.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and syncCatalog is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'syncCatalog.enabled=true' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, syncCatalog.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and meshGateway is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'meshGateway.enabled=true' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, meshGateway.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and ingressGateways is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'ingressGateways.enabled=true' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, ingressGateways.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and terminatingGateways is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'terminatingGateways.enabled=true' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, terminatingGateways.enabled is currently unsupported." ]] +} + +@test "connectInject/Deployment: fails if resource-apis is set and apiGateway is enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'apiGateway.enabled=true' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, apiGateway.enabled is currently unsupported." ]] +} diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index a3524090aa..d4b3b4e38a 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -938,3 +938,43 @@ reservedNameTest() { [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] } + +#-------------------------------------------------------------------- +# global.experiments=["resource-apis"] + +@test "partitionInit/Job: -enable-resource-apis=true is not set in command when global.experiments is empty" { + cd `chart_dir` + local object=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'server.enabled=false' \ + --set 'global.adminPartitions.name=bar' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + . | tee /dev/stderr) + + # Test the flag is set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[] | select(.name == "partition-init-job") | .command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "partitionInit/Job: -enable-resource-apis=true is set in command when global.experiments contains \"resource-apis\"" { + cd `chart_dir` + local object=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'server.enabled=false' \ + --set 'global.adminPartitions.name=bar' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr) + + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[] | select(.name == "partition-init-job") | .command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 52475973f5..afbe361ba4 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -2898,3 +2898,31 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ yq -r '.spec.template.spec.containers[1].command[2] | contains("-interval=10h34m5s")' | tee /dev/stderr) [ "${actual}" = "true" ] } + +#-------------------------------------------------------------------- +# global.experiments=["resource-apis"] + +@test "server/StatefulSet: experiments=[\"resource-apis\"] is not set in command when global.experiments is empty" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-statefulset.yaml \ + . | tee /dev/stderr) + + # Test the flag is set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[] | select(.name == "consul") | .command | any(contains("-hcl=\"experiments=[\\\"resource-apis\\\"]\""))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "server/StatefulSet: experiments=[\"resource-apis\"] is set in command when global.experiments contains \"resource-apis\"" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr) + + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[] | select(.name == "consul") | .command | any(contains("-hcl=\"experiments=[\\\"resource-apis\\\"]\""))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 954680262a..52e6f77c1d 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -735,6 +735,24 @@ global: # @type: array trustedCAs: [ ] + # Consul feature flags that will be enabled across components. + # Supported feature flags: + # * `resource-apis`: + # _**Danger**_! This feature is under active development. It is not + # recommended for production use. Setting this flag during an + # upgrade could risk breaking your Consul cluster. + # If this flag is set, Consul components will use the + # V2 resources APIs for all operations. + # + # Example: + # + # ```yaml + # experiments: [ "resource-apis" ] + # ``` + # @type: array + experiments: [ ] + + # Server, when enabled, configures a server cluster to run. This should # be disabled if you plan on connecting to a Consul cluster external to # the Kube cluster. diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 6767e60130..c6ad8109e9 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -15,20 +15,6 @@ import ( "sync" "syscall" - gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" - apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - "github.com/hashicorp/consul-k8s/control-plane/controllers" - mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/mitchellh/cli" "go.uber.org/zap/zapcore" @@ -44,6 +30,21 @@ import ( ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" + apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" + "github.com/hashicorp/consul-k8s/control-plane/controllers" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( @@ -64,6 +65,7 @@ type Command struct { flagEnableWebhookCAUpdate bool flagLogLevel string flagLogJSON bool + flagResourceAPIs bool // Use V2 APIs flagAllowK8sNamespacesList []string // K8s namespaces to explicitly inject flagDenyK8sNamespacesList []string // K8s namespaces to deny injection (has precedence) @@ -222,6 +224,8 @@ func (c *Command) init() { "%q, %q, %q, and %q.", zapcore.DebugLevel.String(), zapcore.InfoLevel.String(), zapcore.WarnLevel.String(), zapcore.ErrorLevel.String())) c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, "Enable or disable JSON output format for logging.") + c.flagSet.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, + "Enable of disable Consul V2 Resource APIs.") // Proxy sidecar resource setting flags. c.flagSet.StringVar(&c.flagDefaultSidecarProxyCPURequest, "default-sidecar-proxy-cpu-request", "", "Default sidecar proxy CPU request.") diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go index 72c4ceeff0..0684f8b1bb 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -11,13 +11,14 @@ import ( "sync" "time" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { @@ -30,6 +31,8 @@ type Command struct { flagLogJSON bool flagTimeout time.Duration + flagResourceAPIs bool // Use V2 APIs + // ctx is cancelled when the command timeout is reached. ctx context.Context retryDuration time.Duration @@ -51,6 +54,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.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, + "Enable of disable V2 Resource APIs.") c.consul = &flags.ConsulFlags{} flags.Merge(c.flags, c.consul.Flags()) @@ -171,6 +176,12 @@ func (c *Command) validateFlags() error { if c.consul.APITimeout <= 0 { return errors.New("-api-timeout must be set to a value greater than 0") } + + // TODO(dans) this needs to be replaced when the partition workflow is available. + if c.flagResourceAPIs { + return errors.New("partition-init is not implemented when the -enable-resource-apis flag is set for V2 Resource APIs") + } + return nil } From a73c716e323f62187437ef48749af354c52e5733 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 24 Aug 2023 12:24:11 -0700 Subject: [PATCH 343/592] add sameness testing performance enhancement (#2822) --- acceptance/tests/sameness/sameness_test.go | 220 ++++++++++++--------- 1 file changed, 125 insertions(+), 95 deletions(-) diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index 5e1c268169..a677859deb 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -8,6 +8,7 @@ import ( "fmt" "strconv" "strings" + "sync" "testing" "time" @@ -155,7 +156,6 @@ func TestFailover_Connect(t *testing.T) { createNamespaces(t, cfg, v.context) } - // Create the cluster-01-a. commonHelmValues := map[string]string{ "global.peering.enabled": "true", @@ -180,106 +180,127 @@ func TestFailover_Connect(t *testing.T) { "connectInject.sidecarProxy.lifecycle.defaultEnabled": "false", } - defaultPartitionHelmValues := map[string]string{ - "global.datacenter": cluster01Datacenter, - } + releaseName := helpers.RandomName() - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - defaultPartitionHelmValues["meshGateway.service.type"] = "NodePort" - defaultPartitionHelmValues["meshGateway.service.nodePort"] = "30200" - defaultPartitionHelmValues["server.exposeService.type"] = "NodePort" - defaultPartitionHelmValues["server.exposeService.nodePort.https"] = "30000" - defaultPartitionHelmValues["server.exposeService.nodePort.grpc"] = "30100" - } - helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) + var wg sync.WaitGroup - releaseName := helpers.RandomName() - testClusters[keyCluster01a].helmCluster = consul.NewHelmCluster(t, defaultPartitionHelmValues, testClusters[keyCluster01a].context, cfg, releaseName) - testClusters[keyCluster01a].helmCluster.Create(t) + // Create the cluster-01-a and cluster-01-b + // create in same routine as 01-b depends on 01-a being created first + wg.Add(1) + go func() { + // Create the cluster-01-a + defaultPartitionHelmValues := map[string]string{ + "global.datacenter": cluster01Datacenter, + } - // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. - caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + defaultPartitionHelmValues["meshGateway.service.type"] = "NodePort" + defaultPartitionHelmValues["meshGateway.service.nodePort"] = "30200" + defaultPartitionHelmValues["server.exposeService.type"] = "NodePort" + defaultPartitionHelmValues["server.exposeService.nodePort.https"] = "30000" + defaultPartitionHelmValues["server.exposeService.nodePort.grpc"] = "30100" + } + helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) - logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) - k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, caCertSecretName) + testClusters[keyCluster01a].helmCluster = consul.NewHelmCluster(t, defaultPartitionHelmValues, testClusters[keyCluster01a].context, cfg, releaseName) + testClusters[keyCluster01a].helmCluster.Create(t) - // Create Secondary Partition Cluster (cluster-01-b) which will apply the primary (dc1) datacenter. - partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) - if c.ACLsEnabled { - logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) - k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, partitionToken) - } + // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. + caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) - partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) - partitionSvcAddress := k8s.ServiceHost(t, cfg, testClusters[keyCluster01a].context, partitionServiceName) + logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) + k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, caCertSecretName) - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, testClusters[keyCluster01b].context) + // Create Secondary Partition Cluster (cluster-01-b) which will apply the primary (dc1) datacenter. + partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) + if c.ACLsEnabled { + logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) + k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, partitionToken) + } - secondaryPartitionHelmValues := map[string]string{ - "global.enabled": "false", - "global.datacenter": cluster01Datacenter, + partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) + partitionSvcAddress := k8s.ServiceHost(t, cfg, testClusters[keyCluster01a].context, partitionServiceName) - "global.adminPartitions.name": cluster01Partition, + k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, testClusters[keyCluster01b].context) - "global.tls.caCert.secretName": caCertSecretName, - "global.tls.caCert.secretKey": "tls.crt", + secondaryPartitionHelmValues := map[string]string{ + "global.enabled": "false", + "global.datacenter": cluster01Datacenter, - "externalServers.enabled": "true", - "externalServers.hosts[0]": partitionSvcAddress, - "externalServers.tlsServerName": fmt.Sprintf("server.%s.consul", cluster01Datacenter), - "global.server.enabled": "false", - } + "global.adminPartitions.name": cluster01Partition, - if c.ACLsEnabled { - // Setup partition token and auth method host if ACLs enabled. - secondaryPartitionHelmValues["global.acls.bootstrapToken.secretName"] = partitionToken - secondaryPartitionHelmValues["global.acls.bootstrapToken.secretKey"] = "token" - secondaryPartitionHelmValues["externalServers.k8sAuthMethodHost"] = k8sAuthMethodHost - } + "global.tls.caCert.secretName": caCertSecretName, + "global.tls.caCert.secretKey": "tls.crt", - if cfg.UseKind { - secondaryPartitionHelmValues["externalServers.httpsPort"] = "30000" - secondaryPartitionHelmValues["externalServers.grpcPort"] = "30100" - secondaryPartitionHelmValues["meshGateway.service.type"] = "NodePort" - secondaryPartitionHelmValues["meshGateway.service.nodePort"] = "30200" - } - helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) + "externalServers.enabled": "true", + "externalServers.hosts[0]": partitionSvcAddress, + "externalServers.tlsServerName": fmt.Sprintf("server.%s.consul", cluster01Datacenter), + "global.server.enabled": "false", + } - testClusters[keyCluster01b].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, testClusters[keyCluster01b].context, cfg, releaseName) - testClusters[keyCluster01b].helmCluster.Create(t) + if c.ACLsEnabled { + // Setup partition token and auth method host if ACLs enabled. + secondaryPartitionHelmValues["global.acls.bootstrapToken.secretName"] = partitionToken + secondaryPartitionHelmValues["global.acls.bootstrapToken.secretKey"] = "token" + secondaryPartitionHelmValues["externalServers.k8sAuthMethodHost"] = k8sAuthMethodHost + } + + if cfg.UseKind { + secondaryPartitionHelmValues["externalServers.httpsPort"] = "30000" + secondaryPartitionHelmValues["externalServers.grpcPort"] = "30100" + secondaryPartitionHelmValues["meshGateway.service.type"] = "NodePort" + secondaryPartitionHelmValues["meshGateway.service.nodePort"] = "30200" + } + helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) + + testClusters[keyCluster01b].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, testClusters[keyCluster01b].context, cfg, releaseName) + testClusters[keyCluster01b].helmCluster.Create(t) + wg.Done() + }() // Create cluster-02-a Cluster. - PeerOneHelmValues := map[string]string{ - "global.datacenter": cluster02Datacenter, - } + wg.Add(1) + go func() { + PeerOneHelmValues := map[string]string{ + "global.datacenter": cluster02Datacenter, + } - if cfg.UseKind { - PeerOneHelmValues["server.exposeGossipAndRPCPorts"] = "true" - PeerOneHelmValues["meshGateway.service.type"] = "NodePort" - PeerOneHelmValues["meshGateway.service.nodePort"] = "30100" - } - helpers.MergeMaps(PeerOneHelmValues, commonHelmValues) + if cfg.UseKind { + PeerOneHelmValues["server.exposeGossipAndRPCPorts"] = "true" + PeerOneHelmValues["meshGateway.service.type"] = "NodePort" + PeerOneHelmValues["meshGateway.service.nodePort"] = "30100" + } + helpers.MergeMaps(PeerOneHelmValues, commonHelmValues) - testClusters[keyCluster02a].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, testClusters[keyCluster02a].context, cfg, releaseName) - testClusters[keyCluster02a].helmCluster.Create(t) + testClusters[keyCluster02a].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, testClusters[keyCluster02a].context, cfg, releaseName) + testClusters[keyCluster02a].helmCluster.Create(t) + wg.Done() + }() // Create cluster-03-a Cluster. - PeerTwoHelmValues := map[string]string{ - "global.datacenter": cluster03Datacenter, - } + wg.Add(1) + go func() { + PeerTwoHelmValues := map[string]string{ + "global.datacenter": cluster03Datacenter, + } - if cfg.UseKind { - PeerTwoHelmValues["server.exposeGossipAndRPCPorts"] = "true" - PeerTwoHelmValues["meshGateway.service.type"] = "NodePort" - PeerTwoHelmValues["meshGateway.service.nodePort"] = "30100" - } - helpers.MergeMaps(PeerTwoHelmValues, commonHelmValues) + if cfg.UseKind { + PeerTwoHelmValues["server.exposeGossipAndRPCPorts"] = "true" + PeerTwoHelmValues["meshGateway.service.type"] = "NodePort" + PeerTwoHelmValues["meshGateway.service.nodePort"] = "30100" + } + helpers.MergeMaps(PeerTwoHelmValues, commonHelmValues) - testClusters[keyCluster03a].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, testClusters[keyCluster03a].context, cfg, releaseName) - testClusters[keyCluster03a].helmCluster.Create(t) + testClusters[keyCluster03a].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, testClusters[keyCluster03a].context, cfg, releaseName) + testClusters[keyCluster03a].helmCluster.Create(t) + wg.Done() + }() + + // Wait for the clusters to start up + wg.Wait() // Create a ProxyDefaults resource to configure services to use the mesh // gateways and set server and client opts. @@ -428,14 +449,14 @@ func TestFailover_Connect(t *testing.T) { // Create static server/client after the rest of the config is setup for a more stable testing experience // Create static server deployments. logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, testClusters[keyCluster01a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc1-default") - k8s.DeployKustomize(t, testClusters[keyCluster01b].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc1-partition") - k8s.DeployKustomize(t, testClusters[keyCluster02a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc2") - k8s.DeployKustomize(t, testClusters[keyCluster03a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc3") + deployCustomizeAsync(t, testClusters[keyCluster01a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc1-default", &wg) + deployCustomizeAsync(t, testClusters[keyCluster01b].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc1-partition", &wg) + deployCustomizeAsync(t, testClusters[keyCluster02a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc2", &wg) + deployCustomizeAsync(t, testClusters[keyCluster03a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc3", &wg) // Create static client deployments. staticClientKustomizeDirDefault := "../fixtures/cases/sameness/static-client/default-partition" @@ -447,14 +468,15 @@ func TestFailover_Connect(t *testing.T) { staticClientKustomizeDirAP1 = fmt.Sprintf("%s-%s", staticClientKustomizeDirAP1, "tproxy") } - k8s.DeployKustomize(t, testClusters[keyCluster01a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault) - k8s.DeployKustomize(t, testClusters[keyCluster02a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault) - k8s.DeployKustomize(t, testClusters[keyCluster03a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault) - k8s.DeployKustomize(t, testClusters[keyCluster01b].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirAP1) + deployCustomizeAsync(t, testClusters[keyCluster01a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + staticClientKustomizeDirDefault, &wg) + deployCustomizeAsync(t, testClusters[keyCluster02a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + staticClientKustomizeDirDefault, &wg) + deployCustomizeAsync(t, testClusters[keyCluster03a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + staticClientKustomizeDirDefault, &wg) + deployCustomizeAsync(t, testClusters[keyCluster01b].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + staticClientKustomizeDirAP1, &wg) + wg.Wait() // Verify that both static-server and static-client have been injected and now have 2 containers in each cluster. // Also get the server IP @@ -844,3 +866,11 @@ func getCatalogService(t *testing.T, c *cluster, svc, ns, partition string) *api assert.NotEmpty(t, resp, "did not find service %s in cluster %s (partition=%s ns=%s)", svc, c.name, partition, ns) return resp[0] } + +func deployCustomizeAsync(t *testing.T, opts *terratestk8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, kustomizeDir string, wg *sync.WaitGroup) { + wg.Add(1) + go func() { + k8s.DeployKustomize(t, opts, noCleanupOnFailure, noCleanup, debugDirectory, kustomizeDir) + wg.Done() + }() +} From 95f3a28acd3c392ce7b7eaf340f3665184ab9295 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Thu, 24 Aug 2023 16:32:49 -0400 Subject: [PATCH 344/592] NET-5186 Add NET_BIND_SERVICE capability to Consul's restricted securityContext (#2787) * Add NET_BIND_SERVICE capability to Consul's restricted securityContext * Add changelog entry * Update related bats tests * Change type of release note --- .changelog/2787.txt | 3 +++ charts/consul/templates/_helpers.tpl | 2 ++ charts/consul/test/unit/server-statefulset.bats | 6 ++++-- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changelog/2787.txt diff --git a/.changelog/2787.txt b/.changelog/2787.txt new file mode 100644 index 0000000000..2fe921ef23 --- /dev/null +++ b/.changelog/2787.txt @@ -0,0 +1,3 @@ +```release-note:improvement +Add NET_BIND_SERVICE capability to restricted security context used for consul-dataplane +``` diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index f3ab8cb636..044833c11d 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -23,6 +23,8 @@ securityContext: capabilities: drop: - ALL + add: + - NET_BIND_SERVICE runAsNonRoot: true seccompProfile: type: RuntimeDefault diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index afbe361ba4..3248f14da3 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -856,7 +856,8 @@ load _helpers local expected=$(echo '{ "allowPrivilegeEscalation": false, "capabilities": { - "drop": ["ALL"] + "drop": ["ALL"], + "add": ["NET_BIND_SERVICE"] }, "readOnlyRootFilesystem": true, "runAsNonRoot": true, @@ -888,7 +889,8 @@ load _helpers local expected=$(echo '{ "allowPrivilegeEscalation": false, "capabilities": { - "drop": ["ALL"] + "drop": ["ALL"], + "add": ["NET_BIND_SERVICE"] }, "readOnlyRootFilesystem": true, "runAsNonRoot": true, From 835a10caa844a173da5b305644e22ac3da1a5ef4 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 24 Aug 2023 15:33:22 -0700 Subject: [PATCH 345/592] Added tests for partition dns/pq (#2816) * Added tests for partition dns/pq - did some light refactoring --- acceptance/tests/sameness/sameness_test.go | 314 ++++++++++----------- 1 file changed, 156 insertions(+), 158 deletions(-) diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index a677859deb..9688ff9855 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -151,6 +151,13 @@ func TestFailover_Connect(t *testing.T) { keyCluster03a: {name: peerName3a, context: env.Context(t, 3), hasServer: true, locality: localityForRegion(cluster03Region)}, } + // Set primary clusters per cluster + // This is helpful for cases like DNS with partitions where many aspects of the primary cluster must be used + testClusters[keyCluster01a].primaryCluster = testClusters[keyCluster01a] + testClusters[keyCluster01b].primaryCluster = testClusters[keyCluster01a] + testClusters[keyCluster02a].primaryCluster = testClusters[keyCluster02a] + testClusters[keyCluster03a].primaryCluster = testClusters[keyCluster03a] + // Setup Namespaces. for _, v := range testClusters { createNamespaces(t, cfg, v.context) @@ -366,7 +373,7 @@ func TestFailover_Connect(t *testing.T) { // Copy secrets to the necessary peers to be used for dialing later for _, vv := range testClusters { if isAcceptor(v.name, vv.acceptors) { - acceptorSecretName := getPeeringAcceptorSecret(t, cfg, v, vv.name) + acceptorSecretName := v.getPeeringAcceptorSecret(t, cfg, vv.name) logger.Logf(t, "acceptor %s created on %s", acceptorSecretName, v.name) logger.Logf(t, "copying acceptor token %s from %s to %s", acceptorSecretName, v.name, vv.name) @@ -426,24 +433,24 @@ func TestFailover_Connect(t *testing.T) { } // Setup Prepared Query. - definition := &api.PreparedQueryDefinition{ - Name: "my-query", - Service: api.ServiceQuery{ - Service: staticServerName, - SamenessGroup: samenessGroupName, - Namespace: staticServerNamespace, - OnlyPassing: false, - }, - } for k, v := range testClusters { - if v.hasServer { - pqID, _, err := v.client.PreparedQuery().Create(definition, &api.WriteOptions{}) - require.NoError(t, err) - logger.Logf(t, "%s PQ ID: %s", v.name, pqID) - testClusters[k].pqID = &pqID - testClusters[k].pqName = &definition.Name + definition := &api.PreparedQueryDefinition{ + Name: fmt.Sprintf("my-query-%s", v.fullTextPartition()), + Service: api.ServiceQuery{ + Service: staticServerName, + SamenessGroup: samenessGroupName, + Namespace: staticServerNamespace, + OnlyPassing: false, + Partition: v.fullTextPartition(), + }, } + + pqID, _, err := v.client.PreparedQuery().Create(definition, &api.WriteOptions{}) + require.NoError(t, err) + logger.Logf(t, "%s PQ ID: %s", v.name, pqID) + testClusters[k].pqID = &pqID + testClusters[k].pqName = &definition.Name } // Create static server/client after the rest of the config is setup for a more stable testing experience @@ -492,7 +499,7 @@ func TestFailover_Connect(t *testing.T) { // locality-aware routing will function in consul-k8s. In the future, this test will be expanded // to test multi-cluster locality-based failover with sameness groups. for _, v := range testClusters { - checkLocalities(t, v) + v.checkLocalities(t) } // Verify all the failover Scenarios @@ -505,7 +512,6 @@ func TestFailover_Connect(t *testing.T) { failoverServer *cluster expectedPQ expectedPQ } - checkDNSPQ bool }{ { name: "cluster-01-a perspective", // This matches the diagram at the beginning of the test @@ -519,7 +525,6 @@ func TestFailover_Connect(t *testing.T) { {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, }, - checkDNSPQ: true, }, { name: "cluster-01-b partition perspective", @@ -533,7 +538,6 @@ func TestFailover_Connect(t *testing.T) { {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, }, - checkDNSPQ: false, }, { name: "cluster-02-a perspective", @@ -547,7 +551,6 @@ func TestFailover_Connect(t *testing.T) { {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01b].name, namespace: "ns2"}}, {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, }, - checkDNSPQ: true, }, { name: "cluster-03-a perspective", @@ -561,7 +564,6 @@ func TestFailover_Connect(t *testing.T) { {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01b].name, namespace: "ns2"}}, {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, }, - checkDNSPQ: true, }, } for _, sc := range subCases { @@ -577,28 +579,21 @@ func TestFailover_Connect(t *testing.T) { logger.Log(t, "checking service failover") if cfg.EnableTransparentProxy { - serviceTargetCheck(t, sc.server, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) + sc.server.serviceTargetCheck(t, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) } else { - serviceTargetCheck(t, sc.server, v.failoverServer.name, "localhost:8080") + sc.server.serviceTargetCheck(t, v.failoverServer.name, "localhost:8080") } - // Verify DNS - if sc.checkDNSPQ { - logger.Log(t, "verifying dns") - dnsFailoverCheck(t, cfg, releaseName, *sc.server.dnsIP, sc.server, v.failoverServer) + // 1. The admin partition does not contain a server, so DNS service will not resolve on the admin partition cluster + // 2. A workaround to perform the DNS and PQ queries on the primary datacenter cluster by specifying the admin partition + // e.g kubectl --context kind-dc1 --namespace ns1 exec -i deploy/static-client -c static-client \ + // -- dig @test-3lmypr-consul-dns.default static-server.service.ns2.ns.mine.sg.ap1.ap.consul + // Verify DNS. + logger.Log(t, "verifying dns") + sc.server.dnsFailoverCheck(t, cfg, releaseName, v.failoverServer) - // Verify PQ - logger.Log(t, "verifying prepared query") - preparedQueryFailoverCheck(t, releaseName, *sc.server.dnsIP, v.expectedPQ, sc.server, v.failoverServer) - } else { - // We currently skip running DNS and PQ tests for a couple of reasons - // 1. The admin partition does not contain a server, so DNS service will not resolve on the admin partition cluster - // 2. A workaround to perform the DNS and PQ queries on the primary datacenter cluster by specifying the admin partition - // e.g kubectl --context kind-dc1 --namespace ns1 exec -i deploy/static-client -c static-client \ - // -- dig @test-3lmypr-consul-dns.default static-server.service.ns2.ns.mine.sg.ap1.ap.consul - // is not possible at the moment due to a bug. The workaround will be used once this bug is fixed. - logger.Logf(t, "skipping DNS and PQ checks for %s", sc.name) - } + logger.Log(t, "verifying prepared query") + sc.server.preparedQueryFailoverCheck(t, releaseName, v.expectedPQ, v.failoverServer) // Scale down static-server on the current failover, will fail over to the next. logger.Logf(t, "scaling server down on %s", v.failoverServer.name) @@ -631,9 +626,10 @@ type cluster struct { pqName *string dnsIP *string acceptors []string + primaryCluster *cluster } -func (c cluster) fullTextPartition() string { +func (c *cluster) fullTextPartition() string { if c.partition == "" { return "default" } else { @@ -641,6 +637,121 @@ func (c cluster) fullTextPartition() string { } } +// serviceTargetCheck verifies that curling the `static-server` using the `static-client` responds with the expected +// cluster name. Each static-server responds with a unique name so that we can verify failover occured as expected. +func (c *cluster) serviceTargetCheck(t *testing.T, expectedName string, curlAddress string) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + var resp string + var err error + retry.RunWith(timer, t, func(r *retry.R) { + // Use -s/--silent and -S/--show-error flags w/ curl to reduce noise during retries. + // This silences extra output like the request progress bar, but preserves errors. + resp, err = k8s.RunKubectlAndGetOutputE(t, c.clientOpts, "exec", "-i", + staticClientDeployment, "-c", staticClientName, "--", "curl", "-sS", curlAddress) + require.NoError(r, err) + assert.Contains(r, resp, expectedName) + }) + logger.Log(t, resp) +} + +// preparedQueryFailoverCheck verifies that failover occurs when executing the prepared query. It also assures that +// executing the prepared query via DNS also provides expected results. +func (c *cluster) preparedQueryFailoverCheck(t *testing.T, releaseName string, epq expectedPQ, failover *cluster) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + resp, _, err := c.client.PreparedQuery().Execute(*c.pqID, &api.QueryOptions{Namespace: staticServerNamespace, Partition: c.partition}) + require.NoError(t, err) + require.Len(t, resp.Nodes, 1) + + assert.Equal(t, epq.partition, resp.Nodes[0].Service.Partition) + assert.Equal(t, epq.peerName, resp.Nodes[0].Service.PeerName) + assert.Equal(t, epq.namespace, resp.Nodes[0].Service.Namespace) + assert.Equal(t, *failover.staticServerIP, resp.Nodes[0].Service.Address) + + // Verify that dns lookup is successful, there is no guarantee that the ip address is unique, so for PQ this is + // just verifying that we can query using DNS and that the ip address is correct. It does not however prove + // that failover occurred, that is left to client `Execute` + dnsPQLookup := []string{fmt.Sprintf("%s.query.consul", *c.pqName)} + retry.RunWith(timer, t, func(r *retry.R) { + logs := dnsQuery(t, releaseName, dnsPQLookup, c.primaryCluster, failover) + assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", *c.primaryCluster.dnsIP)) + assert.Contains(r, logs, "ANSWER SECTION:") + assert.Contains(r, logs, *failover.staticServerIP) + }) +} + +// DNS failover check verifies that failover occurred when querying the DNS. +func (c *cluster) dnsFailoverCheck(t *testing.T, cfg *config.TestConfig, releaseName string, failover *cluster) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + dnsLookup := []string{fmt.Sprintf("static-server.service.ns2.ns.%s.sg.%s.ap.consul", samenessGroupName, c.fullTextPartition()), "+tcp", "SRV"} + retry.RunWith(timer, t, func(r *retry.R) { + // Use the primary cluster when performing a DNS lookup, this mostly affects cases + // where we are verifying DNS for a partition + logs := dnsQuery(t, releaseName, dnsLookup, c.primaryCluster, failover) + + assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", *c.primaryCluster.dnsIP)) + assert.Contains(r, logs, "ANSWER SECTION:") + assert.Contains(r, logs, *failover.staticServerIP) + + // Additional checks + // When accessing the SRV record for DNS we can get more information. In the case of Kind, + // the context can be used to determine that failover occured to the expected kubernetes cluster + // hosting Consul + assert.Contains(r, logs, "ADDITIONAL SECTION:") + expectedName := failover.context.KubectlOptions(t).ContextName + if cfg.UseKind { + expectedName = strings.Replace(expectedName, "kind-", "", -1) + } + assert.Contains(r, logs, expectedName) + }) +} + +// getPeeringAcceptorSecret assures that the secret is created and retrieves the secret from the provided acceptor. +func (c *cluster) getPeeringAcceptorSecret(t *testing.T, cfg *config.TestConfig, acceptorName string) string { + // Ensure the secrets are created. + var acceptorSecretName string + timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} + retry.RunWith(timer, t, func(r *retry.R) { + var err error + acceptorSecretName, err = k8s.RunKubectlAndGetOutputE(t, c.context.KubectlOptions(t), "get", "peeringacceptor", acceptorName, "-o", "jsonpath={.status.secret.name}") + require.NoError(r, err) + require.NotEmpty(r, acceptorSecretName) + }) + + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.RunKubectl(t, c.context.KubectlOptions(t), "delete", "secret", acceptorSecretName) + }) + + return acceptorSecretName +} + +// checkLocalities checks the given cluster for `static-client` and `static-server` instances matching the locality +// expected for the cluster. +func (c *cluster) checkLocalities(t *testing.T) { + for ns, svcs := range map[string][]string{ + staticClientNamespace: { + staticClientName, + staticClientName + "-sidecar-proxy", + }, + staticServerNamespace: { + staticServerName, + staticServerName + "-sidecar-proxy", + }, + } { + for _, svc := range svcs { + cs := c.getCatalogService(t, svc, ns, c.partition) + assert.NotNil(t, cs.ServiceLocality, "service %s in %s did not have locality set", svc, c.name) + assert.Equal(t, c.locality, *cs.ServiceLocality, "locality for service %s in %s did not match expected", svc, c.name) + } + } +} + +func (c *cluster) getCatalogService(t *testing.T, svc, ns, partition string) *api.CatalogService { + resp, _, err := c.client.Catalog().Service(svc, "", &api.QueryOptions{Namespace: ns, Partition: partition}) + require.NoError(t, err) + assert.NotEmpty(t, resp, "did not find service %s in cluster %s (partition=%s ns=%s)", svc, c.name, partition, ns) + return resp[0] +} + type clusters map[string]*cluster func (c clusters) resetScale(t *testing.T) { @@ -677,9 +788,9 @@ func (c clusters) verifyServerUpState(t *testing.T, isTproxyEnabled bool) { for _, v := range c { // Query using a client and expect its own name, no failover should occur if isTproxyEnabled { - serviceTargetCheck(t, v, v.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", v.fullTextPartition())) + v.serviceTargetCheck(t, v.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", v.fullTextPartition())) } else { - serviceTargetCheck(t, v, v.name, "localhost:8080") + v.serviceTargetCheck(t, v.name, "localhost:8080") } } } @@ -718,74 +829,8 @@ func setK8sNodeLocality(t *testing.T, context environment.TestContext, c *cluste k8s.KubectlLabel(t, context.KubectlOptions(t), "node", node, corev1.LabelTopologyZone, c.locality.Zone) } -// serviceTargetCheck verifies that curling the `static-server` using the `static-client` responds with the expected -// cluster name. Each static-server responds with a unique name so that we can verify failover occured as expected. -func serviceTargetCheck(t *testing.T, server *cluster, expectedName string, curlAddress string) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - var resp string - var err error - retry.RunWith(timer, t, func(r *retry.R) { - // Use -s/--silent and -S/--show-error flags w/ curl to reduce noise during retries. - // This silences extra output like the request progress bar, but preserves errors. - resp, err = k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", - staticClientDeployment, "-c", staticClientName, "--", "curl", "-sS", curlAddress) - require.NoError(r, err) - assert.Contains(r, resp, expectedName) - }) - logger.Log(t, resp) -} - -// preparedQueryFailoverCheck verifies that failover occurs when executing the prepared query. It also assures that -// executing the prepared query via DNS also provides expected results. -func preparedQueryFailoverCheck(t *testing.T, releaseName string, dnsIP string, epq expectedPQ, server, failover *cluster) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - resp, _, err := server.client.PreparedQuery().Execute(*server.pqID, &api.QueryOptions{Namespace: staticServerNamespace, Partition: server.partition}) - require.NoError(t, err) - require.Len(t, resp.Nodes, 1) - - assert.Equal(t, epq.partition, resp.Nodes[0].Service.Partition) - assert.Equal(t, epq.peerName, resp.Nodes[0].Service.PeerName) - assert.Equal(t, epq.namespace, resp.Nodes[0].Service.Namespace) - assert.Equal(t, *failover.staticServerIP, resp.Nodes[0].Service.Address) - - // Verify that dns lookup is successful, there is no guarantee that the ip address is unique, so for PQ this is - // just verifying that we can query using DNS and that the ip address is correct. It does not however prove - // that failover occured, that is left to client `Execute` - dnsPQLookup := []string{fmt.Sprintf("%s.query.consul", *server.pqName)} - retry.RunWith(timer, t, func(r *retry.R) { - logs := dnsQuery(t, releaseName, dnsPQLookup, server, failover) - assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", dnsIP)) - assert.Contains(r, logs, "ANSWER SECTION:") - assert.Contains(r, logs, *failover.staticServerIP) - }) -} - -// DNS failover check verifies that failover occurred when querying the DNS. -func dnsFailoverCheck(t *testing.T, cfg *config.TestConfig, releaseName string, dnsIP string, server, failover *cluster) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - dnsLookup := []string{fmt.Sprintf("static-server.service.ns2.ns.%s.sg.consul", samenessGroupName), "+tcp", "SRV"} - retry.RunWith(timer, t, func(r *retry.R) { - logs := dnsQuery(t, releaseName, dnsLookup, server, failover) - - assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", dnsIP)) - assert.Contains(r, logs, "ANSWER SECTION:") - assert.Contains(r, logs, *failover.staticServerIP) - - // Additional checks - // When accessing the SRV record for DNS we can get more information. In the case of Kind, - // the context can be used to determine that failover occured to the expected kubernetes cluster - // hosting Consul - assert.Contains(r, logs, "ADDITIONAL SECTION:") - expectedName := failover.context.KubectlOptions(t).ContextName - if cfg.UseKind { - expectedName = strings.Replace(expectedName, "kind-", "", -1) - } - assert.Contains(r, logs, expectedName) - }) -} - // dnsQuery performs a dns query with the provided query string. -func dnsQuery(t *testing.T, releaseName string, dnsQuery []string, server, failover *cluster) string { +func dnsQuery(t *testing.T, releaseName string, dnsQuery []string, dnsServer, failover *cluster) string { timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} var logs string retry.RunWith(timer, t, func(r *retry.R) { @@ -794,7 +839,7 @@ func dnsQuery(t *testing.T, releaseName string, dnsQuery []string, server, failo releaseName)} args = append(args, dnsQuery...) var err error - logs, err = k8s.RunKubectlAndGetOutputE(t, server.clientOpts, args...) + logs, err = k8s.RunKubectlAndGetOutputE(t, dnsServer.clientOpts, args...) require.NoError(r, err) }) logger.Logf(t, "%s: %s", failover.name, logs) @@ -812,25 +857,6 @@ func isAcceptor(name string, acceptorList []string) bool { return false } -// getPeeringAcceptorSecret assures that the secret is created and retrieves the secret from the provided acceptor. -func getPeeringAcceptorSecret(t *testing.T, cfg *config.TestConfig, server *cluster, acceptorName string) string { - // Ensure the secrets are created. - var acceptorSecretName string - timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} - retry.RunWith(timer, t, func(r *retry.R) { - var err error - acceptorSecretName, err = k8s.RunKubectlAndGetOutputE(t, server.context.KubectlOptions(t), "get", "peeringacceptor", acceptorName, "-o", "jsonpath={.status.secret.name}") - require.NoError(r, err) - require.NotEmpty(r, acceptorSecretName) - }) - - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, server.context.KubectlOptions(t), "delete", "secret", acceptorSecretName) - }) - - return acceptorSecretName -} - // localityForRegion returns the full api.Locality to use in tests for a given region string. func localityForRegion(r string) api.Locality { return api.Locality{ @@ -839,34 +865,6 @@ func localityForRegion(r string) api.Locality { } } -// checkLocalities checks the given cluster for `static-client` and `static-server` instances matching the locality -// expected for the cluster. -func checkLocalities(t *testing.T, c *cluster) { - for ns, svcs := range map[string][]string{ - staticClientNamespace: { - staticClientName, - staticClientName + "-sidecar-proxy", - }, - staticServerNamespace: { - staticServerName, - staticServerName + "-sidecar-proxy", - }, - } { - for _, svc := range svcs { - cs := getCatalogService(t, c, svc, ns, c.partition) - assert.NotNil(t, cs.ServiceLocality, "service %s in %s did not have locality set", svc, c.name) - assert.Equal(t, c.locality, *cs.ServiceLocality, "locality for service %s in %s did not match expected", svc, c.name) - } - } -} - -func getCatalogService(t *testing.T, c *cluster, svc, ns, partition string) *api.CatalogService { - resp, _, err := c.client.Catalog().Service(svc, "", &api.QueryOptions{Namespace: ns, Partition: partition}) - require.NoError(t, err) - assert.NotEmpty(t, resp, "did not find service %s in cluster %s (partition=%s ns=%s)", svc, c.name, partition, ns) - return resp[0] -} - func deployCustomizeAsync(t *testing.T, opts *terratestk8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, kustomizeDir string, wg *sync.WaitGroup) { wg.Add(1) go func() { From aa8ff6733afcfe44d643e6e2eb64a26dc356238d Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:11:35 -0700 Subject: [PATCH 346/592] Mw/net 4888 add namespace tests failover wan fed (#2797) * added fixtures * modified connHelper Create Intention - Function can now take optional intention ops. For now just supports overriding the source/destination namespaces * added WAN Federation test - split out into own test because TestWANFederation also does some PSA related tests. Didn't want to change this test too much, and my test requires consul-k8s mirroring - added new test TestWANFederationFailover which tests some failover scenarios, including to different namespaces and datacenters * refactored connHelper to use opts --- .../framework/connhelper/connect_helper.go | 109 ++++-- acceptance/tests/cli/cli_install_test.go | 6 +- .../tests/connect/connect_inject_test.go | 6 +- .../connect/connect_proxy_lifecycle_test.go | 8 +- .../bases/service-resolver/kustomization.yaml | 5 + .../service-resolver/service-resolver.yaml | 7 + .../dc1-ns2-static-server/kustomization.yaml | 8 + .../dc1-ns2-static-server/patch.yaml | 41 +++ .../dc1-static-server/kustomization.yaml | 8 + .../dc1-static-server/patch.yaml | 41 +++ .../dc2-static-server/kustomization.yaml | 8 + .../dc2-static-server/patch.yaml | 41 +++ .../service-resolver/kustomization.yaml | 8 + .../service-resolver/patch.yaml | 15 + .../static-client/kustomization.yaml | 8 + .../wan-federation/static-client/patch.yaml | 22 ++ .../wan_federation_gateway_test.go | 16 +- .../wan-federation/wan_federation_test.go | 309 ++++++++++++++++-- 18 files changed, 585 insertions(+), 81 deletions(-) create mode 100644 acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 2058fd955c..9db30b0281 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -27,6 +27,8 @@ const ( StaticClientName = "static-client" StaticServerName = "static-server" JobName = "job-client" + + retryTimeout = 120 * time.Second ) // ConnectHelper configures a Consul cluster for connect injection tests. @@ -49,6 +51,7 @@ type ConnectHelper struct { // Ctx is used to deploy Consul Ctx environment.TestContext + // UseAppNamespace is used top optionally deploy applications into a separate namespace. // If unset, the namespace associated with Ctx is used. UseAppNamespace bool @@ -62,6 +65,13 @@ type ConnectHelper struct { ConsulClient *api.Client } +// ConnHelperOpts allows for configuring optional parameters to be passed into the +// conn helper methods. This provides added flexibility, although not every value will be used +// by every method. See documentation for more details. +type ConnHelperOpts struct { + ClientType string +} + // Setup creates a new cluster using the New*Cluster function and assigns it // to the consulCluster field. func (c *ConnectHelper) Setup(t *testing.T) { @@ -90,6 +100,8 @@ func (c *ConnectHelper) Upgrade(t *testing.T) { c.consulCluster.Upgrade(t, c.helmValues()) } +// KubectlOptsForApp returns options using the -apps appended namespace if +// UseAppNamespace is enabled. Otherwise, it returns the ctx options. func (c *ConnectHelper) KubectlOptsForApp(t *testing.T) *terratestK8s.KubectlOptions { opts := c.Ctx.KubectlOptions(t) if !c.UseAppNamespace { @@ -110,7 +122,7 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { // deployments because golang will execute them in reverse order // (i.e. the last registered cleanup function will be executed first). t.Cleanup(func() { - retrier := &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond} + retrier := &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond} retry.RunWith(retrier, t, func(r *retry.R) { tokens, _, err := c.ConsulClient.ACL().TokenList(nil) require.NoError(r, err) @@ -155,7 +167,7 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { // Check that both static-server and static-client have been injected and // now have 2 containers. retry.RunWith( - &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond}, t, + &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond}, t, func(r *retry.R) { for _, labelSelector := range []string{"app=static-server", "app=static-client"} { podList, err := c.Ctx.KubernetesClient(t).CoreV1(). @@ -171,6 +183,18 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { }) } +func (c *ConnectHelper) CreateNamespace(t *testing.T, namespace string) { + opts := c.Ctx.KubectlOptions(t) + _, err := k8s.RunKubectlAndGetOutputE(t, opts, "create", "ns", namespace) + if err != nil && strings.Contains(err.Error(), "AlreadyExists") { + return + } + require.NoError(t, err) + helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { + k8s.RunKubectl(t, opts, "delete", "ns", namespace) + }) +} + // DeployJob deploys a job pod to the Kubernetes // cluster which will be used to test service mesh connectivity. If the Secure // flag is true, a pre-check is done to ensure that the ACL tokens for the test @@ -257,14 +281,7 @@ func (c *ConnectHelper) SetupAppNamespace(t *testing.T) { opts := c.KubectlOptsForApp(t) // If we are deploying apps in another namespace, create the namespace. - _, err := k8s.RunKubectlAndGetOutputE(t, opts, "create", "ns", opts.Namespace) - if err != nil && strings.Contains(err.Error(), "AlreadyExists") { - return - } - require.NoError(t, err) - helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { - k8s.RunKubectl(t, opts, "delete", "ns", opts.Namespace) - }) + c.CreateNamespace(t, opts.Namespace) if c.Cfg.EnableRestrictedPSAEnforcement { // Allow anything to run in the app namespace. @@ -273,7 +290,6 @@ func (c *ConnectHelper) SetupAppNamespace(t *testing.T) { "pod-security.kubernetes.io/enforce-version=v1.24", ) } - } // CreateResolverRedirect creates a resolver that redirects to a static-server, a corresponding k8s service, @@ -291,15 +307,17 @@ func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { } // TestConnectionFailureWithoutIntention ensures the connection to the static -// server fails when no intentions are configured. -func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T, clientType ...string) { +// server fails when no intentions are configured. When provided with a ClientType option +// the client is overridden, otherwise a default will be used. +func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T, connHelperOpts ConnHelperOpts) { logger.Log(t, "checking that the connection is not successful because there's no intention") opts := c.KubectlOptsForApp(t) //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. client := StaticClientName - if len(clientType) > 0 { - client = clientType[0] + if connHelperOpts.ClientType != "" { + client = connHelperOpts.ClientType } + if c.Cfg.EnableTransparentProxy { k8s.CheckStaticServerConnectionFailing(t, opts, client, "http://static-server") } else { @@ -307,38 +325,63 @@ func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T, clie } } +type IntentionOpts struct { + ConnHelperOpts + SourceNamespace string + DestinationNamespace string +} + // CreateIntention creates an intention for the static-server pod to connect to -// the static-client pod. -func (c *ConnectHelper) CreateIntention(t *testing.T, clientType ...string) { +// the static-client pod. opts parameter allows for overriding of some fields. If opts is empty +// then all namespaces and clients use defaults. +func (c *ConnectHelper) CreateIntention(t *testing.T, opts IntentionOpts) { logger.Log(t, "creating intention") //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. client := StaticClientName - if len(clientType) > 0 { - client = clientType[0] + if opts.ClientType != "" { + client = opts.ClientType } - _, _, err := c.ConsulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: StaticServerName, - Sources: []*api.SourceIntention{ - { - Name: client, - Action: api.IntentionActionAllow, + + sourceNamespace := c.KubectlOptsForApp(t).Namespace + if opts.SourceNamespace != "" { + sourceNamespace = opts.SourceNamespace + } + + destinationNamespace := c.KubectlOptsForApp(t).Namespace + if opts.DestinationNamespace != "" { + destinationNamespace = opts.DestinationNamespace + } + + retrier := &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond} + retry.RunWith(retrier, t, func(r *retry.R) { + _, _, err := c.ConsulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: StaticServerName, + Namespace: destinationNamespace, + Sources: []*api.SourceIntention{ + { + Namespace: sourceNamespace, + Name: client, + Action: api.IntentionActionAllow, + }, }, - }, - }, nil) - require.NoError(t, err) + }, nil) + require.NoError(r, err) + }) } // TestConnectionSuccess ensures the static-server pod can connect to the -// static-client pod once the intention is set. -func (c *ConnectHelper) TestConnectionSuccess(t *testing.T, clientType ...string) { +// static-client pod once the intention is set. When provided with a ClientType option +// the client is overridden, otherwise a default will be used. +func (c *ConnectHelper) TestConnectionSuccess(t *testing.T, connHelperOpts ConnHelperOpts) { logger.Log(t, "checking that connection is successful") opts := c.KubectlOptsForApp(t) //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. client := StaticClientName - if len(clientType) > 0 { - client = clientType[0] + if connHelperOpts.ClientType != "" { + client = connHelperOpts.ClientType } + if c.Cfg.EnableTransparentProxy { // todo: add an assertion that the traffic is going through the proxy k8s.CheckStaticServerConnectionSuccessful(t, opts, client, "http://static-server") diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index bb497f913f..dc0ec37500 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -55,8 +55,8 @@ func TestInstall(t *testing.T) { connHelper.Install(t) connHelper.DeployClientAndServer(t) if c.secure { - connHelper.TestConnectionFailureWithoutIntention(t) - connHelper.CreateIntention(t) + connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) + connHelper.CreateIntention(t, connhelper.IntentionOpts{}) } // Run proxy list and get the two results. @@ -124,7 +124,7 @@ func TestInstall(t *testing.T) { logger.Log(t, string(proxyOut)) } - connHelper.TestConnectionSuccess(t) + connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) connHelper.TestConnectionFailureWhenUnhealthy(t) }) } diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index 13fb41f562..b44523235c 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -51,11 +51,11 @@ func TestConnectInject(t *testing.T) { connHelper.Install(t) connHelper.DeployClientAndServer(t) if c.secure { - connHelper.TestConnectionFailureWithoutIntention(t) - connHelper.CreateIntention(t) + connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) + connHelper.CreateIntention(t, connhelper.IntentionOpts{}) } - connHelper.TestConnectionSuccess(t) + connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) connHelper.TestConnectionFailureWhenUnhealthy(t) }) } diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go index d24a36883b..a94175270e 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -120,11 +120,11 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { }) if testCfg.secure { - connHelper.TestConnectionFailureWithoutIntention(t) - connHelper.CreateIntention(t) + connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) + connHelper.CreateIntention(t, connhelper.IntentionOpts{}) } - connHelper.TestConnectionSuccess(t) + connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) // Get static-client pod name ns := ctx.KubectlOptions(t).Namespace @@ -278,7 +278,7 @@ func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { } }) - connHelper.TestConnectionSuccess(t, connhelper.JobName) + connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{ClientType: connhelper.JobName}) // Get job-client pod name ns := ctx.KubectlOptions(t).Namespace diff --git a/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml b/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml new file mode 100644 index 0000000000..8e36fe276e --- /dev/null +++ b/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - service-resolver.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml b/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml new file mode 100644 index 0000000000..2e0459e381 --- /dev/null +++ b/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml new file mode 100644 index 0000000000..0775282c18 --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml new file mode 100644 index 0000000000..c4f181ce7d --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml @@ -0,0 +1,41 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/kschoche/http-echo:latest + args: + - -text="ns2" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + livenessProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + startupProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 1 + readinessProbe: + exec: + command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml new file mode 100644 index 0000000000..0775282c18 --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml new file mode 100644 index 0000000000..60c1219e33 --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml @@ -0,0 +1,41 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/kschoche/http-echo:latest + args: + - -text="dc1" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + livenessProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + startupProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 1 + readinessProbe: + exec: + command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml new file mode 100644 index 0000000000..0775282c18 --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml new file mode 100644 index 0000000000..b167f50c9a --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml @@ -0,0 +1,41 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + containers: + - name: static-server + image: docker.mirror.hashicorp.services/kschoche/http-echo:latest + args: + - -text="dc2" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + livenessProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + startupProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 1 + readinessProbe: + exec: + command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml new file mode 100644 index 0000000000..36e8097f47 --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/service-resolver + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml new file mode 100644 index 0000000000..e89156f605 --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: static-server +spec: + connectTimeout: 15s + failover: + '*': + targets: + - datacenter: "dc2" + - namespace: "ns2" + diff --git a/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml new file mode 100644 index 0000000000..38bc36bffd --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml @@ -0,0 +1,8 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - ../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml new file mode 100644 index 0000000000..f2f8981601 --- /dev/null +++ b/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml @@ -0,0 +1,22 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + 'consul.hashicorp.com/connect-inject': 'true' + "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" + spec: + containers: + - name: static-client + image: anubhavmishra/tiny-tools:latest + # Just spin & wait forever, we'll use `kubectl exec` to demo + command: ['/bin/sh', '-c', '--'] + args: ['while true; do sleep 30; done;'] + # If ACLs are enabled, the serviceAccountName must match the Consul service name. + serviceAccountName: static-client \ No newline at end of file diff --git a/acceptance/tests/wan-federation/wan_federation_gateway_test.go b/acceptance/tests/wan-federation/wan_federation_gateway_test.go index 6abacda438..ec466c93ec 100644 --- a/acceptance/tests/wan-federation/wan_federation_gateway_test.go +++ b/acceptance/tests/wan-federation/wan_federation_gateway_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" @@ -60,15 +61,6 @@ func TestWANFederation_Gateway(t *testing.T) { primaryConsulCluster := consul.NewHelmCluster(t, primaryHelmValues, primaryContext, cfg, releaseName) primaryConsulCluster.Create(t) - // Get the federation secret from the primary cluster and apply it to secondary cluster - federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) - logger.Logf(t, "retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) - federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) - require.NoError(t, err) - federationSecret.ResourceVersion = "" - _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) - require.NoError(t, err) - var k8sAuthMethodHost string // When running on kind, the kube API address in kubeconfig will have a localhost address // which will not work from inside the container. That's why we need to use the endpoints address instead @@ -82,6 +74,8 @@ func TestWANFederation_Gateway(t *testing.T) { k8sAuthMethodHost = k8s.KubernetesAPIServerHostFromOptions(t, secondaryContext.KubectlOptions(t)) } + federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) + // Create secondary cluster secondaryHelmValues := map[string]string{ "global.datacenter": "dc2", @@ -217,7 +211,7 @@ func checkConnectivity(t *testing.T, ctx environment.TestContext, client *api.Cl targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) logger.Log(t, "checking that the connection is not successful because there's no intention") - k8s.CheckStaticServerHTTPConnectionFailing(t, ctx.KubectlOptions(t), StaticClientName, targetAddress) + k8s.CheckStaticServerHTTPConnectionFailing(t, ctx.KubectlOptions(t), connhelper.StaticClientName, targetAddress) logger.Log(t, "creating intention") _, _, err := client.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ @@ -237,5 +231,5 @@ func checkConnectivity(t *testing.T, ctx environment.TestContext, client *api.Cl }() logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), StaticClientName, targetAddress) + k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, targetAddress) } diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index 8edc1f5d03..41d237113a 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -8,17 +8,35 @@ import ( "fmt" "strconv" "testing" + "time" + terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const StaticClientName = "static-client" +const ( + staticClientDeployment = "deploy/static-client" + staticServerDeployment = "deploy/static-server" + + retryTimeout = 5 * time.Minute + + primaryDatacenter = "dc1" + secondaryDatacenter = "dc2" + + localServerPort = "1234" + + primaryNamespace = "ns1" + secondaryNamespace = "ns2" +) // Test that Connect and wan federation over mesh gateways work in a default installation // i.e. without ACLs because TLS is required for WAN federation over mesh gateways. @@ -47,7 +65,7 @@ func TestWANFederation(t *testing.T) { secondaryContext := env.Context(t, 1) primaryHelmValues := map[string]string{ - "global.datacenter": "dc1", + "global.datacenter": primaryDatacenter, "global.tls.enabled": "true", "global.tls.httpsOnly": strconv.FormatBool(c.secure), @@ -77,31 +95,13 @@ func TestWANFederation(t *testing.T) { primaryConsulCluster.Create(t) // Get the federation secret from the primary cluster and apply it to secondary cluster - federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) - logger.Logf(t, "retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) - federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) - require.NoError(t, err) - federationSecret.ResourceVersion = "" - federationSecret.Namespace = secondaryContext.KubectlOptions(t).Namespace - _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) - require.NoError(t, err) - - var k8sAuthMethodHost string - // When running on kind, the kube API address in kubeconfig will have a localhost address - // which will not work from inside the container. That's why we need to use the endpoints address instead - // which will point the node IP. - if cfg.UseKind { - // The Kubernetes AuthMethod host is read from the endpoints for the Kubernetes service. - kubernetesEndpoint, err := secondaryContext.KubernetesClient(t).CoreV1().Endpoints("default").Get(context.Background(), "kubernetes", metav1.GetOptions{}) - require.NoError(t, err) - k8sAuthMethodHost = fmt.Sprintf("%s:%d", kubernetesEndpoint.Subsets[0].Addresses[0].IP, kubernetesEndpoint.Subsets[0].Ports[0].Port) - } else { - k8sAuthMethodHost = k8s.KubernetesAPIServerHostFromOptions(t, secondaryContext.KubectlOptions(t)) - } + federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) + + k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryContext) // Create secondary cluster secondaryHelmValues := map[string]string{ - "global.datacenter": "dc2", + "global.datacenter": secondaryDatacenter, "global.tls.enabled": "true", "global.tls.httpsOnly": "false", @@ -130,7 +130,7 @@ func TestWANFederation(t *testing.T) { secondaryHelmValues["global.acls.replicationToken.secretName"] = federationSecretName secondaryHelmValues["global.acls.replicationToken.secretKey"] = "replicationToken" secondaryHelmValues["global.federation.k8sAuthMethodHost"] = k8sAuthMethodHost - secondaryHelmValues["global.federation.primaryDatacenter"] = "dc1" + secondaryHelmValues["global.federation.primaryDatacenter"] = primaryDatacenter } if cfg.UseKind { @@ -190,11 +190,266 @@ func TestWANFederation(t *testing.T) { k8s.DeployKustomize(t, primaryHelper.KubectlOptsForApp(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") if c.secure { - primaryHelper.CreateIntention(t) + primaryHelper.CreateIntention(t, connhelper.IntentionOpts{}) } logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, primaryHelper.KubectlOptsForApp(t), StaticClientName, "http://localhost:1234") + k8s.CheckStaticServerConnectionSuccessful(t, primaryHelper.KubectlOptsForApp(t), connhelper.StaticClientName, "http://localhost:1234") }) } } + +// Test failover scenarios with a static-server in dc1 and a static-server +// in dc2. Use the static-client on dc1 to reach static-server on dc1 in the +// nominal scenario, then cause a failure in dc1 static-server to see the static-client failover to +// the static-server in dc2 +/* + dc1-static-client -- nominal -- > dc1-static-server in namespace ns1 + dc1-static-client -- failover --> dc2-static-server in namespace ns1 + dc1-static-client -- failover --> dc1-static-server in namespace ns2 +*/ +func TestWANFederationFailover(t *testing.T) { + cases := []struct { + name string + secure bool + }{ + { + name: "secure", + secure: true, + }, + { + name: "default", + secure: false, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + env := suite.Environment() + cfg := suite.Config() + + if cfg.EnableRestrictedPSAEnforcement { + t.Skip("This test case is not run with enable restricted PSA enforcement enabled") + } + + primaryContext := env.DefaultContext(t) + secondaryContext := env.Context(t, 1) + + primaryHelmValues := map[string]string{ + "global.datacenter": primaryDatacenter, + + "global.tls.enabled": "true", + "global.tls.httpsOnly": strconv.FormatBool(c.secure), + + "global.federation.enabled": "true", + "global.federation.createFederationSecret": "true", + + "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), + "global.acls.createReplicationToken": strconv.FormatBool(c.secure), + + "connectInject.enabled": "true", + "connectInject.replicas": "1", + + "meshGateway.enabled": "true", + "meshGateway.replicas": "1", + + "global.enableConsulNamespaces": "true", + "connectInject.consulNamespaces.mirroringK8S": "true", + } + + if cfg.UseKind { + primaryHelmValues["meshGateway.service.type"] = "NodePort" + primaryHelmValues["meshGateway.service.nodePort"] = "30000" + } + + releaseName := helpers.RandomName() + + // Install the primary consul cluster in the default kubernetes context + primaryConsulCluster := consul.NewHelmCluster(t, primaryHelmValues, primaryContext, cfg, releaseName) + primaryConsulCluster.Create(t) + + // Get the federation secret from the primary cluster and apply it to secondary cluster + federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) + + k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryContext) + + // Create secondary cluster + secondaryHelmValues := map[string]string{ + "global.datacenter": secondaryDatacenter, + + "global.tls.enabled": "true", + "global.tls.httpsOnly": "false", + "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), + "global.tls.caCert.secretName": federationSecretName, + "global.tls.caCert.secretKey": "caCert", + "global.tls.caKey.secretName": federationSecretName, + "global.tls.caKey.secretKey": "caKey", + + "global.federation.enabled": "true", + + "server.extraVolumes[0].type": "secret", + "server.extraVolumes[0].name": federationSecretName, + "server.extraVolumes[0].load": "true", + "server.extraVolumes[0].items[0].key": "serverConfigJSON", + "server.extraVolumes[0].items[0].path": "config.json", + + "connectInject.enabled": "true", + "connectInject.replicas": "1", + + "meshGateway.enabled": "true", + "meshGateway.replicas": "1", + + "global.enableConsulNamespaces": "true", + "connectInject.consulNamespaces.mirroringK8S": "true", + } + + if c.secure { + secondaryHelmValues["global.acls.replicationToken.secretName"] = federationSecretName + secondaryHelmValues["global.acls.replicationToken.secretKey"] = "replicationToken" + secondaryHelmValues["global.federation.k8sAuthMethodHost"] = k8sAuthMethodHost + secondaryHelmValues["global.federation.primaryDatacenter"] = primaryDatacenter + } + + if cfg.UseKind { + secondaryHelmValues["meshGateway.service.type"] = "NodePort" + secondaryHelmValues["meshGateway.service.nodePort"] = "30000" + } + + // Install the secondary consul cluster in the secondary kubernetes context + secondaryConsulCluster := consul.NewHelmCluster(t, secondaryHelmValues, secondaryContext, cfg, releaseName) + secondaryConsulCluster.Create(t) + + primaryClient, _ := primaryConsulCluster.SetupConsulClient(t, c.secure) + secondaryClient, _ := secondaryConsulCluster.SetupConsulClient(t, c.secure) + + // Verify federation between servers + logger.Log(t, "Verifying federation was successful") + helpers.VerifyFederation(t, primaryClient, secondaryClient, releaseName, c.secure) + + // Create a ProxyDefaults resource to configure services to use the mesh + // gateways. + logger.Log(t, "Creating proxy-defaults config") + kustomizeDir := "../fixtures/bases/mesh-gateway" + k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), kustomizeDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), kustomizeDir) + }) + + primaryHelper := connhelper.ConnectHelper{ + Secure: c.secure, + ReleaseName: releaseName, + Ctx: primaryContext, + UseAppNamespace: false, + Cfg: cfg, + ConsulClient: primaryClient, + } + secondaryHelper := connhelper.ConnectHelper{ + Secure: c.secure, + ReleaseName: releaseName, + Ctx: secondaryContext, + UseAppNamespace: false, + Cfg: cfg, + ConsulClient: secondaryClient, + } + + // Create Namespaces + // We create a namespace (ns1) in both the primary and secondary datacenters (dc1, dc2) + // We then create a secondary namespace (ns2) in the primary datacenter (dc1) + primaryNamespaceOpts := primaryHelper.Ctx.KubectlOptionsForNamespace(primaryNamespace) + primaryHelper.CreateNamespace(t, primaryNamespaceOpts.Namespace) + primarySecondaryNamepsaceOpts := primaryHelper.Ctx.KubectlOptionsForNamespace(secondaryNamespace) + primaryHelper.CreateNamespace(t, primarySecondaryNamepsaceOpts.Namespace) + secondaryNamespaceOpts := secondaryHelper.Ctx.KubectlOptionsForNamespace(primaryNamespace) + secondaryHelper.CreateNamespace(t, secondaryNamespaceOpts.Namespace) + + // Create a static-server in dc2 to respond with its own name for checking failover. + logger.Log(t, "Creating static-server in dc2") + k8s.DeployKustomize(t, secondaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc2-static-server") + + // Spin up a server on dc1 which will be the primary upstream for our client + logger.Log(t, "Creating static-server in dc1") + k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc1-static-server") + logger.Log(t, "Creating static-client in dc1") + k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/static-client") + + // Spin up a second server on dc1 in a separate namespace + logger.Logf(t, "Creating server on dc1 in namespace %s", primarySecondaryNamepsaceOpts.Namespace) + k8s.DeployKustomize(t, primarySecondaryNamepsaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc1-ns2-static-server") + + // There is currently an issue that requires the intentions and resolvers to be created after + // the static-server/clients when using namespaces. When created before, Consul gives a "namespace does not exist" + // error + if c.secure { + // Only need to create intentions in the primary datacenter as they will be replicated to the secondary + // ns1 static-client (source) -> ns1 static-server (destination) + primaryHelper.CreateIntention(t, connhelper.IntentionOpts{DestinationNamespace: primaryNamespaceOpts.Namespace, SourceNamespace: primaryNamespaceOpts.Namespace}) + + // ns1 static-client (source) -> ns2 static-server (destination) + primaryHelper.CreateIntention(t, connhelper.IntentionOpts{DestinationNamespace: primarySecondaryNamepsaceOpts.Namespace, SourceNamespace: primaryNamespaceOpts.Namespace}) + } + + // Create a service resolver for failover + logger.Log(t, "Creating service resolver") + k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/service-resolver") + + // Verify that we respond with the static-server in the primary datacenter + logger.Log(t, "Verifying static-server in dc1 responds") + serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, primaryDatacenter) + + // Scale down the primary datacenter static-server and see the failover + logger.Log(t, "Scale down dc1 static-server") + k8s.KubectlScale(t, primaryNamespaceOpts, staticServerDeployment, 0) + + // Verify that we respond with the static-server in the secondary datacenter + logger.Log(t, "Verifying static-server in dc2 responds") + serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, secondaryDatacenter) + + // scale down the primary datacenter static-server and see the failover + logger.Log(t, "Scale down dc2 static-server") + k8s.KubectlScale(t, secondaryNamespaceOpts, staticServerDeployment, 0) + + // Verify that we respond with the static-server in the secondary datacenter + logger.Log(t, "Verifying static-server in secondary namespace (ns2) responds") + serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, secondaryNamespace) + }) + } +} + +// serviceFailoverCheck verifies that the server failed over as expected by checking that curling the `static-server` +// using the `static-client` responds with the expected cluster name. Each static-server responds with a unique +// name so that we can verify failover occurred as expected. +func serviceFailoverCheck(t *testing.T, options *terratestK8s.KubectlOptions, port string, expectedName string) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + var resp string + var err error + + f := func(ft require.TestingT) { + resp, err = k8s.RunKubectlAndGetOutputE(t, options, "exec", "-i", + staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) + require.NoError(ft, err) + assert.Contains(ft, resp, expectedName) + } + + retry.RunWith(timer, t, func(r *retry.R) { + f(r) + }) + + // Try again to rule out load-balancing + f(t) + + logger.Log(t, resp) +} + +func copyFederationSecret(t *testing.T, releaseName string, primaryContext, secondaryContext environment.TestContext) string { + // Get the federation secret from the primary cluster and apply it to secondary cluster + federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) + logger.Logf(t, "Retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) + federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) + require.NoError(t, err) + federationSecret.ResourceVersion = "" + federationSecret.Namespace = secondaryContext.KubectlOptions(t).Namespace + _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + return federationSecretName +} From 4ea04860c5edaccecc7df12feedd2961b84b2d95 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Fri, 25 Aug 2023 17:38:44 -0400 Subject: [PATCH 347/592] fix: lifecycle enabled iptables mismatch (#2842) --- .../framework/connhelper/connect_helper.go | 9 +- .../connect-inject/webhook/mesh_webhook.go | 27 ++- .../webhook/mesh_webhook_test.go | 229 +++++++++++++++++- .../webhook/redirect_traffic.go | 19 +- 4 files changed, 256 insertions(+), 28 deletions(-) diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 9db30b0281..5f140c3368 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -11,16 +11,17 @@ import ( "time" terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 69d0f96c75..523200b96c 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -15,13 +15,6 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" "gomodules.xyz/jsonpatch/v2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -30,6 +23,14 @@ import ( "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/version" ) const ( @@ -533,20 +534,24 @@ func (w *MeshWebhook) overwriteProbes(ns corev1.Namespace, pod *corev1.Pod) erro } if tproxyEnabled && overwriteProbes { - for i, container := range pod.Spec.Containers { + // We don't use the loop index because this needs to line up w.withiptablesConfigJSON, + // which is performed before the sidecar is injected. + idx := 0 + for _, container := range pod.Spec.Containers { // skip the "envoy-sidecar" container from having it's probes overridden if container.Name == sidecarContainer { continue } if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + i) + container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + idx) } if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + i) + container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + idx) } if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + i) + container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + idx) } + idx++ } } return nil diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index c709c830b4..64dbd21c9a 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -6,17 +6,13 @@ package webhook import ( "context" "encoding/json" + "strconv" "strings" "testing" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" + "github.com/hashicorp/consul/sdk/iptables" "github.com/stretchr/testify/require" "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" @@ -28,6 +24,13 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/version" ) func TestHandlerHandle(t *testing.T) { @@ -1129,6 +1132,220 @@ func TestHandlerHandle(t *testing.T) { } } +// This test validates that overwrite probes match the iptables configuration fromiptablesConfigJSON() +// Because they happen at different points in the injection, the port numbers can get out of sync. +func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { + t.Parallel() + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{ + Group: "", + Version: "v1", + }, &corev1.Pod{}) + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + + cases := []struct { + Name string + Webhook MeshWebhook + Req admission.Request + Err string // expected error string, not exact + Patches []jsonpatch.Operation + }{ + { + "tproxy with overwriteProbes is enabled", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableTransparentProxy: true, + TProxyOverwriteProbes: true, + LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + // We're setting an existing annotation so that we can assert on the + // specific annotations that are set as a result of probes being overwritten. + Annotations: map[string]string{"foo": "bar"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8081), + }, + }, + }, + StartupProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8082), + }, + }, + }, + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "replace", + Path: "/spec/containers/0/name", + }, + { + Operation: "add", + Path: "/spec/containers/0/args", + }, + { + Operation: "add", + Path: "/spec/containers/0/env", + }, + { + Operation: "add", + Path: "/spec/containers/0/volumeMounts", + }, + { + Operation: "add", + Path: "/spec/containers/0/readinessProbe/tcpSocket", + }, + { + Operation: "add", + Path: "/spec/containers/0/readinessProbe/initialDelaySeconds", + }, + { + Operation: "remove", + Path: "/spec/containers/0/readinessProbe/httpGet", + }, + { + Operation: "add", + Path: "/spec/containers/0/securityContext", + }, + { + Operation: "remove", + Path: "/spec/containers/0/startupProbe", + }, + { + Operation: "remove", + Path: "/spec/containers/0/livenessProbe", + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), + }, + + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + }, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} + ctx := context.Background() + resp := tt.Webhook.Handle(ctx, tt.Req) + if (tt.Err == "") != resp.Allowed { + t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) + } + if tt.Err != "" { + require.Contains(t, resp.Result.Message, tt.Err) + return + } + + var iptablesCfg iptables.Config + var overwritePorts []string + actual := resp.Patches + if len(actual) > 0 { + for i := range actual { + + // We want to grab the iptables configuration from the connect-init container's + // environment. + if actual[i].Path == "/spec/initContainers" { + value := actual[i].Value.([]any) + valueMap := value[0].(map[string]any) + envs := valueMap["env"].([]any) + redirectEnv := envs[8].(map[string]any) + require.Equal(t, redirectEnv["name"].(string), "CONSUL_REDIRECT_TRAFFIC_CONFIG") + iptablesJson := redirectEnv["value"].(string) + + err := json.Unmarshal([]byte(iptablesJson), &iptablesCfg) + require.NoError(t, err) + } + + // We want to accumulate the httpGet Probes from the application container to + // compare them to the iptables rules. This is now the second container in the spec + if strings.Contains(actual[i].Path, "/spec/containers/1") { + valueMap, ok := actual[i].Value.(map[string]any) + require.True(t, ok) + + for k, v := range valueMap { + if strings.Contains(k, "Probe") { + probe := v.(map[string]any) + httpProbe := probe["httpGet"] + httpProbeMap := httpProbe.(map[string]any) + port := httpProbeMap["port"] + portNum := port.(float64) + + overwritePorts = append(overwritePorts, strconv.Itoa(int(portNum))) + } + } + } + + // nil out all the patch values to just compare the keys changing. + actual[i].Value = nil + } + } + // Make sure the iptables excluded ports match the ports on the container + require.ElementsMatch(t, iptablesCfg.ExcludeInboundPorts, overwritePorts) + require.ElementsMatch(t, tt.Patches, actual) + }) + } +} + func TestHandlerDefaultAnnotations(t *testing.T) { cases := []struct { Name string diff --git a/control-plane/connect-inject/webhook/redirect_traffic.go b/control-plane/connect-inject/webhook/redirect_traffic.go index b0cbefeeaa..f928df4afd 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic.go +++ b/control-plane/connect-inject/webhook/redirect_traffic.go @@ -8,10 +8,11 @@ import ( "fmt" "strconv" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul/sdk/iptables" corev1 "k8s.io/api/core/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) // addRedirectTrafficConfigAnnotation creates an iptables.Config in JSON format based on proxy configuration. @@ -62,20 +63,24 @@ func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (s } if overwriteProbes { - for i, container := range pod.Spec.Containers { - // skip the "envoy-sidecar" container from having its probes overridden + // We don't use the loop index because this needs to line up w.overwriteProbes(), + // which is performed after the sidecar is injected. + idx := 0 + for _, container := range pod.Spec.Containers { + // skip the "consul-dataplane" container from having its probes overridden if container.Name == sidecarContainer { continue } if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+i)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+idx)) } if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+i)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+idx)) } if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+i)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+idx)) } + idx++ } } From 0cd68fc9769a06443b6d3026988b52b83a348dd9 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Fri, 25 Aug 2023 17:55:10 -0400 Subject: [PATCH 348/592] refactor: make space for v2 controllers (#2832) refator: make space for v2 controllers --- control-plane/consul/resource_client.go | 22 +- control-plane/consul/resource_client_test.go | 10 +- .../subcommand/inject-connect/command.go | 616 +++--------------- .../inject-connect/v1controllers.go | 484 ++++++++++++++ .../inject-connect/v2controllers.go | 94 +++ 5 files changed, 675 insertions(+), 551 deletions(-) create mode 100644 control-plane/subcommand/inject-connect/v1controllers.go create mode 100644 control-plane/subcommand/inject-connect/v2controllers.go diff --git a/control-plane/consul/resource_client.go b/control-plane/consul/resource_client.go index e476f4e737..17dc4a9229 100644 --- a/control-plane/consul/resource_client.go +++ b/control-plane/consul/resource_client.go @@ -1,29 +1,16 @@ package consul import ( - "context" "fmt" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-hclog" ) // NewResourceServiceClient creates a pbresource.ResourceServiceClient for creating V2 Consul resources. -// It is initialized with a consul-server-connection-manager discovery config to continuously find Consul +// It is initialized with a consul-server-connection-manager Watcher to continuously find Consul // server addresses. -// The caller should make sure to Stop() the returned `watcher` (preferably with a `defer`) to clean up the gRPC -// connection and the discovery client. -// The caller can also set `config.ServerWatchDisabled=false` to prevent subscribing to Consul server address -// changes, as is the case with single-shot operations. -func NewResourceServiceClient(ctx context.Context, config discovery.Config, logger hclog.Logger, hack int) (pbresource.ResourceServiceClient, *discovery.Watcher, error) { - - watcher, err := discovery.NewWatcher(ctx, config, logger.Named("consul-server-connection-manager")) - if err != nil { - return nil, nil, fmt.Errorf("unable to create Consul server watcher: %w", err) - } - - go watcher.Run() +func NewResourceServiceClient(watcher *discovery.Watcher) (pbresource.ResourceServiceClient, error) { // We recycle the GRPC connection from the discovery client because it // should have all the necessary dial options, including the resolver that @@ -31,10 +18,9 @@ func NewResourceServiceClient(ctx context.Context, config discovery.Config, logg // would need to be duplicated state, err := watcher.State() if err != nil { - watcher.Stop() - return nil, nil, fmt.Errorf("unable to get connection manager state: %w", err) + return nil, fmt.Errorf("unable to get connection manager state: %w", err) } resourceClient := pbresource.NewResourceServiceClient(state.GRPCConn) - return resourceClient, watcher, nil + return resourceClient, nil } diff --git a/control-plane/consul/resource_client_test.go b/control-plane/consul/resource_client_test.go index 7dd9ef0666..29507304b0 100644 --- a/control-plane/consul/resource_client_test.go +++ b/control-plane/consul/resource_client_test.go @@ -36,12 +36,18 @@ func Test_NewResourceServiceClient(t *testing.T) { opts := hclog.LoggerOptions{Name: "resource-service-client"} logger := hclog.New(&opts) - client, watcher, err := NewResourceServiceClient(context.Background(), discoverConfig, logger, serverConfig.Ports.GRPCTLS) + + watcher, err := discovery.NewWatcher(context.Background(), discoverConfig, logger) require.NoError(t, err) - require.NotNil(t, client) require.NotNil(t, watcher) defer watcher.Stop() + go watcher.Run() + + client, err := NewResourceServiceClient(watcher) + require.NoError(t, err) + require.NotNil(t, client) + require.NotNil(t, watcher) req := createWriteRequest(t, "foo") res, err := client.Write(context.Background(), req) diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index c6ad8109e9..4434d52d98 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -27,22 +27,11 @@ import ( "k8s.io/client-go/rest" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" - ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" - apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - "github.com/hashicorp/consul-k8s/control-plane/controllers" - mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -143,6 +132,18 @@ type Command struct { clientset kubernetes.Interface + // sidecarProxy* are resource limits that are parsed and validated from other flags + // these are individual members because there are override annotations + sidecarProxyCPULimit resource.Quantity + sidecarProxyCPURequest resource.Quantity + sidecarProxyMemoryLimit resource.Quantity + sidecarProxyMemoryRequest resource.Quantity + + // static resources requirements for connect-init + initContainerResources corev1.ResourceRequirements + + caCertPem []byte + once sync.Once help string } @@ -278,67 +279,13 @@ func (c *Command) Run(args []string) int { return 1 } - // Proxy resources. - var sidecarProxyCPULimit, sidecarProxyCPURequest, sidecarProxyMemoryLimit, sidecarProxyMemoryRequest resource.Quantity - var err error - if c.flagDefaultSidecarProxyCPURequest != "" { - sidecarProxyCPURequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPURequest) - if err != nil { - c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-cpu-request is invalid: %s", err)) - return 1 - } - } - - if c.flagDefaultSidecarProxyCPULimit != "" { - sidecarProxyCPULimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPULimit) - if err != nil { - c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-cpu-limit is invalid: %s", err)) - return 1 - } - } - if sidecarProxyCPULimit.Value() != 0 && sidecarProxyCPURequest.Cmp(sidecarProxyCPULimit) > 0 { - c.UI.Error(fmt.Sprintf( - "request must be <= limit: -default-sidecar-proxy-cpu-request value of %q is greater than the -default-sidecar-proxy-cpu-limit value of %q", - c.flagDefaultSidecarProxyCPURequest, c.flagDefaultSidecarProxyCPULimit)) - return 1 - } - - if c.flagDefaultSidecarProxyMemoryRequest != "" { - sidecarProxyMemoryRequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryRequest) - if err != nil { - c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-memory-request is invalid: %s", err)) - return 1 - } - } - if c.flagDefaultSidecarProxyMemoryLimit != "" { - sidecarProxyMemoryLimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryLimit) - if err != nil { - c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-memory-limit is invalid: %s", err)) - return 1 - } - } - if sidecarProxyMemoryLimit.Value() != 0 && sidecarProxyMemoryRequest.Cmp(sidecarProxyMemoryLimit) > 0 { - c.UI.Error(fmt.Sprintf( - "request must be <= limit: -default-sidecar-proxy-memory-request value of %q is greater than the -default-sidecar-proxy-memory-limit value of %q", - c.flagDefaultSidecarProxyMemoryRequest, c.flagDefaultSidecarProxyMemoryLimit)) - return 1 - } - - // Validate ports in metrics flags. - err = common.ValidateUnprivilegedPort("-default-merged-metrics-port", c.flagDefaultMergedMetricsPort) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - err = common.ValidateUnprivilegedPort("-default-prometheus-scrape-port", c.flagDefaultPrometheusScrapePort) - if err != nil { + if err := c.parseAndValidateSidecarProxyFlags(); err != nil { c.UI.Error(err.Error()) return 1 } // Validate resource request/limit flags and parse into corev1.ResourceRequirements - initResources, err := c.parseAndValidateResourceFlags() - if err != nil { + if err := c.parseAndValidateResourceFlags(); err != nil { c.UI.Error(err.Error()) return 1 } @@ -357,10 +304,6 @@ func (c *Command) Run(args []string) int { } } - // Convert allow/deny lists to sets. - allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) - denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) - zapLogger, err := common.ZapLogger(c.flagLogLevel, c.flagLogJSON) if err != nil { c.UI.Error(fmt.Sprintf("Error setting up logging: %s", err.Error())) @@ -387,13 +330,9 @@ func (c *Command) Run(args []string) int { return 1 } - // Create Consul API config object. - consulConfig := c.consul.ConsulClientConfig() - - var caCertPem []byte if c.consul.CACertFile != "" { var err error - caCertPem, err = os.ReadFile(c.consul.CACertFile) + c.caCertPem, err = os.ReadFile(c.consul.CACertFile) if err != nil { c.UI.Error(fmt.Sprintf("error reading Consul's CA cert file %q", c.consul.CACertFile)) return 1 @@ -410,14 +349,14 @@ func (c *Command) Run(args []string) int { c.UI.Error(fmt.Sprintf("unable to create config for consul-server-connection-manager: %s", err)) return 1 } - watcher, err := discovery.NewWatcher(ctx, serverConnMgrCfg, hcLog) + + watcher, err := discovery.NewWatcher(ctx, serverConnMgrCfg, hcLog.Named("consul-server-connection-manager")) if err != nil { c.UI.Error(fmt.Sprintf("unable to create Consul server watcher: %s", err)) return 1 } - - go watcher.Run() defer watcher.Stop() + go watcher.Run() // This is a blocking command that is run in order to ensure we only start the // connect-inject controllers only after we have access to the Consul server. @@ -442,435 +381,17 @@ func (c *Command) Run(args []string) int { return 1 } - lifecycleConfig := lifecycle.Config{ - DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, - DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, - DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, - DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, - DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, - } - - metricsConfig := metrics.Config{ - DefaultEnableMetrics: c.flagDefaultEnableMetrics, - EnableGatewayMetrics: c.flagEnableGatewayMetrics, - DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, - DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, - DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, - DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, - } - - if err = (&endpoints.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - MetricsConfig: metricsConfig, - EnableConsulPartitions: c.flagEnablePartitions, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableWANFederation: c.flagEnableFederation, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - AuthMethod: c.flagACLAuthMethod, - NodeMeta: c.flagNodeMeta, - Log: ctrl.Log.WithName("controller").WithName("endpoints"), - Scheme: mgr.GetScheme(), - ReleaseName: c.flagReleaseName, - ReleaseNamespace: c.flagReleaseNamespace, - EnableAutoEncrypt: c.flagEnableAutoEncrypt, - EnableTelemetryCollector: c.flagEnableTelemetryCollector, - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", endpoints.Controller{}) - return 1 - } - - // API Gateway Controllers - if err := gatewaycontrollers.RegisterFieldIndexes(ctx, mgr); err != nil { - setupLog.Error(err, "unable to register field indexes") - return 1 - } - - if err = (&gatewaycontrollers.GatewayClassConfigController{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName("gateways"), - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", gatewaycontrollers.GatewayClassConfigController{}) - return 1 - } - - if err := (&gatewaycontrollers.GatewayClassController{ - ControllerName: gatewaycommon.GatewayClassControllerName, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("GatewayClass"), - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "GatewayClass") - return 1 + //Right now we exclusively start controllers for V1 or V2. + //In the future we might add a flag to pick and choose from both. + if c.flagResourceAPIs { + err = c.configureV2Controllers(ctx, mgr, watcher) + } else { + err = c.configureV1Controllers(ctx, mgr, watcher) } - - cache, err := gatewaycontrollers.SetupGatewayControllerWithManager(ctx, mgr, gatewaycontrollers.GatewayControllerConfig{ - HelmConfig: gatewaycommon.HelmConfig{ - ConsulConfig: gatewaycommon.ConsulConfig{ - Address: c.consul.Addresses, - GRPCPort: consulConfig.GRPCPort, - HTTPPort: consulConfig.HTTPPort, - APITimeout: consulConfig.APITimeout, - }, - ImageDataplane: c.flagConsulDataplaneImage, - ImageConsulK8S: c.flagConsulK8sImage, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - NamespaceMirroringPrefix: c.flagK8SNSMirroringPrefix, - EnableNamespaces: c.flagEnableNamespaces, - PeeringEnabled: c.flagEnablePeering, - EnableOpenShift: c.flagEnableOpenShift, - EnableNamespaceMirroring: c.flagEnableK8SNSMirroring, - AuthMethod: c.consul.ConsulLogin.AuthMethod, - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - TLSEnabled: c.consul.UseTLS, - ConsulTLSServerName: c.consul.TLSServerName, - ConsulPartition: c.consul.Partition, - ConsulCACert: string(caCertPem), - }, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - NamespacesEnabled: c.flagEnableNamespaces, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - Partition: c.consul.Partition, - Datacenter: c.consul.Datacenter, - }) - if err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Gateway") - return 1 - } - - go cache.Run(ctx) - - // wait for the cache to fill - setupLog.Info("waiting for Consul cache sync") - cache.WaitSynced(ctx) - setupLog.Info("Consul cache synced") - - configEntryReconciler := &controllers.ConfigEntryController{ - ConsulClientConfig: c.consul.ConsulClientConfig(), - ConsulServerConnMgr: watcher, - DatacenterName: c.consul.Datacenter, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, - } - if err = (&controllers.ServiceDefaultsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceDefaults), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceDefaults) - return 1 - } - if err = (&controllers.ServiceResolverController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceResolver), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceResolver) - return 1 - } - if err = (&controllers.ProxyDefaultsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ProxyDefaults), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ProxyDefaults) - return 1 - } - if err = (&controllers.MeshController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.Mesh), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.Mesh) - return 1 - } - if err = (&controllers.ExportedServicesController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ExportedServices), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ExportedServices) - return 1 - } - if err = (&controllers.ServiceRouterController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceRouter), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceRouter) + setupLog.Error(err, fmt.Sprintf("could not configure controllers: %s", err.Error())) return 1 } - if err = (&controllers.ServiceSplitterController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceSplitter), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceSplitter) - return 1 - } - if err = (&controllers.ServiceIntentionsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceIntentions), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceIntentions) - return 1 - } - if err = (&controllers.IngressGatewayController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.IngressGateway), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.IngressGateway) - return 1 - } - if err = (&controllers.TerminatingGatewayController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.TerminatingGateway), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.TerminatingGateway) - return 1 - } - if err = (&controllers.SamenessGroupController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.SamenessGroup), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.SamenessGroup) - return 1 - } - if err = (&controllers.JWTProviderController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.JWTProvider), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.JWTProvider) - return 1 - } - if err = (&controllers.ControlPlaneRequestLimitController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ControlPlaneRequestLimit), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ControlPlaneRequestLimit) - return 1 - } - - if err = mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { - setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) - return 1 - } - - if c.flagEnablePeering { - if err = (&peering.AcceptorController{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - ExposeServersServiceName: c.flagResourcePrefix + "-expose-servers", - ReleaseNamespace: c.flagReleaseNamespace, - Log: ctrl.Log.WithName("controller").WithName("peering-acceptor"), - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "peering-acceptor") - return 1 - } - if err = (&peering.PeeringDialerController{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - Log: ctrl.Log.WithName("controller").WithName("peering-dialer"), - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "peering-dialer") - return 1 - } - - mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringacceptors", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringAcceptorWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName("peering-acceptor"), - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringdialers", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringDialerWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName("peering-dialer"), - }}) - } - - mgr.GetWebhookServer().CertDir = c.flagCertDir - - mgr.GetWebhookServer().Register("/mutate", - &ctrlRuntimeWebhook.Admission{Handler: &webhook.MeshWebhook{ - Clientset: c.clientset, - ReleaseNamespace: c.flagReleaseNamespace, - ConsulConfig: consulConfig, - ConsulServerConnMgr: watcher, - ImageConsul: c.flagConsulImage, - ImageConsulDataplane: c.flagConsulDataplaneImage, - EnvoyExtraArgs: c.flagEnvoyExtraArgs, - ImageConsulK8S: c.flagConsulK8sImage, - RequireAnnotation: !c.flagDefaultInject, - AuthMethod: c.flagACLAuthMethod, - ConsulCACert: string(caCertPem), - TLSEnabled: c.consul.UseTLS, - ConsulAddress: c.consul.Addresses, - SkipServerWatch: c.consul.SkipServerWatch, - ConsulTLSServerName: c.consul.TLSServerName, - DefaultProxyCPURequest: sidecarProxyCPURequest, - DefaultProxyCPULimit: sidecarProxyCPULimit, - DefaultProxyMemoryRequest: sidecarProxyMemoryRequest, - DefaultProxyMemoryLimit: sidecarProxyMemoryLimit, - DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, - LifecycleConfig: lifecycleConfig, - MetricsConfig: metricsConfig, - InitContainerResources: initResources, - ConsulPartition: c.consul.Partition, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - EnableNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, - K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableCNI: c.flagEnableCNI, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - EnableConsulDNS: c.flagEnableConsulDNS, - EnableOpenShift: c.flagEnableOpenShift, - Log: ctrl.Log.WithName("handler").WithName("connect"), - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - }}) - - consulMeta := apicommon.ConsulMeta{ - PartitionsEnabled: c.flagEnablePartitions, - Partition: c.consul.Partition, - NamespacesEnabled: c.flagEnableNamespaces, - DestinationNamespace: c.flagConsulDestinationNamespace, - Mirroring: c.flagEnableK8SNSMirroring, - Prefix: c.flagK8SNSMirroringPrefix, - } - - // Note: The path here should be identical to the one on the kubebuilder - // annotation in each webhook file. - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceDefaults), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceResolver), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ProxyDefaults), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-mesh", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.MeshWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.Mesh), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-exportedservices", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ExportedServicesWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ExportedServices), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceRouter), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceSplitter), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceintentions", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceIntentionsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceIntentions), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-ingressgateway", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.IngressGatewayWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.IngressGateway), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-terminatinggateway", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.TerminatingGatewayWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-samenessgroup", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.SamenessGroupWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.SamenessGroup), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-jwtprovider", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.JWTProviderWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.JWTProvider), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-controlplanerequestlimits", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ControlPlaneRequestLimitWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ControlPlaneRequestLimit), - ConsulMeta: consulMeta, - }}) - - if c.flagEnableWebhookCAUpdate { - err = c.updateWebhookCABundle(ctx) - if err != nil { - setupLog.Error(err, "problem getting CA Cert") - return 1 - } - } if err = mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") @@ -880,20 +401,6 @@ func (c *Command) Run(args []string) int { return 0 } -func (c *Command) updateWebhookCABundle(ctx context.Context) error { - webhookConfigName := fmt.Sprintf("%s-connect-injector", c.flagResourcePrefix) - caPath := fmt.Sprintf("%s/%s", c.flagCertDir, WebhookCAFilename) - caCert, err := os.ReadFile(caPath) - if err != nil { - return err - } - err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) - if err != nil { - return err - } - return nil -} - func (c *Command) validateFlags() error { if c.flagConsulK8sImage == "" { return errors.New("-consul-k8s-image must be set") @@ -917,48 +424,95 @@ func (c *Command) validateFlags() error { return errors.New("-default-envoy-proxy-concurrency must be >= 0 if set") } + // Validate ports in metrics flags. + err := common.ValidateUnprivilegedPort("-default-merged-metrics-port", c.flagDefaultMergedMetricsPort) + if err != nil { + return err + } + err = common.ValidateUnprivilegedPort("-default-prometheus-scrape-port", c.flagDefaultPrometheusScrapePort) + if err != nil { + return err + } + + return nil +} + +func (c *Command) parseAndValidateSidecarProxyFlags() error { + var err error + + if c.flagDefaultSidecarProxyCPURequest != "" { + c.sidecarProxyCPURequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPURequest) + if err != nil { + return fmt.Errorf("-default-sidecar-proxy-cpu-request is invalid: %w", err) + } + } + + if c.flagDefaultSidecarProxyCPULimit != "" { + c.sidecarProxyCPULimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPULimit) + if err != nil { + return fmt.Errorf("-default-sidecar-proxy-cpu-limit is invalid: %w", err) + } + } + if c.sidecarProxyCPULimit.Value() != 0 && c.sidecarProxyCPURequest.Cmp(c.sidecarProxyMemoryLimit) > 0 { + return fmt.Errorf("request must be <= limit: -default-sidecar-proxy-cpu-request value of %q is greater than the -default-sidecar-proxy-cpu-limit value of %q", + c.flagDefaultSidecarProxyCPURequest, c.flagDefaultSidecarProxyCPULimit) + } + + if c.flagDefaultSidecarProxyMemoryRequest != "" { + c.sidecarProxyMemoryRequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryRequest) + if err != nil { + return fmt.Errorf("-default-sidecar-proxy-memory-request is invalid: %w", err) + } + } + if c.flagDefaultSidecarProxyMemoryLimit != "" { + c.sidecarProxyMemoryLimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryLimit) + if err != nil { + return fmt.Errorf("-default-sidecar-proxy-memory-limit is invalid: %w", err) + } + } + if c.sidecarProxyMemoryLimit.Value() != 0 && c.sidecarProxyMemoryRequest.Cmp(c.sidecarProxyMemoryLimit) > 0 { + return fmt.Errorf("request must be <= limit: -default-sidecar-proxy-memory-request value of %q is greater than the -default-sidecar-proxy-memory-limit value of %q", + c.flagDefaultSidecarProxyMemoryRequest, c.flagDefaultSidecarProxyMemoryLimit) + } + return nil } -func (c *Command) parseAndValidateResourceFlags() (corev1.ResourceRequirements, error) { +func (c *Command) parseAndValidateResourceFlags() error { // Init container var initContainerCPULimit, initContainerCPURequest, initContainerMemoryLimit, initContainerMemoryRequest resource.Quantity // Parse and validate the initContainer resources. initContainerCPURequest, err := resource.ParseQuantity(c.flagInitContainerCPURequest) if err != nil { - return corev1.ResourceRequirements{}, - fmt.Errorf("-init-container-cpu-request '%s' is invalid: %s", c.flagInitContainerCPURequest, err) + return fmt.Errorf("-init-container-cpu-request '%s' is invalid: %s", c.flagInitContainerCPURequest, err) } initContainerCPULimit, err = resource.ParseQuantity(c.flagInitContainerCPULimit) if err != nil { - return corev1.ResourceRequirements{}, - fmt.Errorf("-init-container-cpu-limit '%s' is invalid: %s", c.flagInitContainerCPULimit, err) + return fmt.Errorf("-init-container-cpu-limit '%s' is invalid: %s", c.flagInitContainerCPULimit, err) } if initContainerCPULimit.Value() != 0 && initContainerCPURequest.Cmp(initContainerCPULimit) > 0 { - return corev1.ResourceRequirements{}, fmt.Errorf( + return fmt.Errorf( "request must be <= limit: -init-container-cpu-request value of %q is greater than the -init-container-cpu-limit value of %q", c.flagInitContainerCPURequest, c.flagInitContainerCPULimit) } initContainerMemoryRequest, err = resource.ParseQuantity(c.flagInitContainerMemoryRequest) if err != nil { - return corev1.ResourceRequirements{}, - fmt.Errorf("-init-container-memory-request '%s' is invalid: %s", c.flagInitContainerMemoryRequest, err) + return fmt.Errorf("-init-container-memory-request '%s' is invalid: %s", c.flagInitContainerMemoryRequest, err) } initContainerMemoryLimit, err = resource.ParseQuantity(c.flagInitContainerMemoryLimit) if err != nil { - return corev1.ResourceRequirements{}, - fmt.Errorf("-init-container-memory-limit '%s' is invalid: %s", c.flagInitContainerMemoryLimit, err) + return fmt.Errorf("-init-container-memory-limit '%s' is invalid: %s", c.flagInitContainerMemoryLimit, err) } if initContainerMemoryLimit.Value() != 0 && initContainerMemoryRequest.Cmp(initContainerMemoryLimit) > 0 { - return corev1.ResourceRequirements{}, fmt.Errorf( + return fmt.Errorf( "request must be <= limit: -init-container-memory-request value of %q is greater than the -init-container-memory-limit value of %q", c.flagInitContainerMemoryRequest, c.flagInitContainerMemoryLimit) } // Put into corev1.ResourceRequirements form - initResources := corev1.ResourceRequirements{ + c.initContainerResources = corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: initContainerCPURequest, corev1.ResourceMemory: initContainerMemoryRequest, @@ -969,7 +523,7 @@ func (c *Command) parseAndValidateResourceFlags() (corev1.ResourceRequirements, }, } - return initResources, nil + return nil } func (c *Command) Synopsis() string { return synopsis } diff --git a/control-plane/subcommand/inject-connect/v1controllers.go b/control-plane/subcommand/inject-connect/v1controllers.go new file mode 100644 index 0000000000..57ac1691c3 --- /dev/null +++ b/control-plane/subcommand/inject-connect/v1controllers.go @@ -0,0 +1,484 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package connectinject + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/consul-server-connection-manager/discovery" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/manager" + ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + + gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" + apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" + "github.com/hashicorp/consul-k8s/control-plane/controllers" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" +) + +func (c *Command) configureV1Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { + + // Create Consul API config object. + consulConfig := c.consul.ConsulClientConfig() + + // Convert allow/deny lists to sets. + allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) + denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) + + lifecycleConfig := lifecycle.Config{ + DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, + DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, + DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, + DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, + DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, + } + + metricsConfig := metrics.Config{ + DefaultEnableMetrics: c.flagDefaultEnableMetrics, + EnableGatewayMetrics: c.flagEnableGatewayMetrics, + DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, + DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, + DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, + DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, + } + + if err := (&endpoints.Controller{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + MetricsConfig: metricsConfig, + EnableConsulPartitions: c.flagEnablePartitions, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + EnableWANFederation: c.flagEnableFederation, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + AuthMethod: c.flagACLAuthMethod, + NodeMeta: c.flagNodeMeta, + Log: ctrl.Log.WithName("controller").WithName("endpoints"), + Scheme: mgr.GetScheme(), + ReleaseName: c.flagReleaseName, + ReleaseNamespace: c.flagReleaseNamespace, + EnableAutoEncrypt: c.flagEnableAutoEncrypt, + EnableTelemetryCollector: c.flagEnableTelemetryCollector, + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", endpoints.Controller{}) + return err + } + + // API Gateway Controllers + if err := gatewaycontrollers.RegisterFieldIndexes(ctx, mgr); err != nil { + setupLog.Error(err, "unable to register field indexes") + return err + } + + if err := (&gatewaycontrollers.GatewayClassConfigController{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName("gateways"), + }).SetupWithManager(ctx, mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", gatewaycontrollers.GatewayClassConfigController{}) + return err + } + + if err := (&gatewaycontrollers.GatewayClassController{ + ControllerName: gatewaycommon.GatewayClassControllerName, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("GatewayClass"), + }).SetupWithManager(ctx, mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "GatewayClass") + return err + } + + cache, err := gatewaycontrollers.SetupGatewayControllerWithManager(ctx, mgr, gatewaycontrollers.GatewayControllerConfig{ + HelmConfig: gatewaycommon.HelmConfig{ + ConsulConfig: gatewaycommon.ConsulConfig{ + Address: c.consul.Addresses, + GRPCPort: consulConfig.GRPCPort, + HTTPPort: consulConfig.HTTPPort, + APITimeout: consulConfig.APITimeout, + }, + ImageDataplane: c.flagConsulDataplaneImage, + ImageConsulK8S: c.flagConsulK8sImage, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + NamespaceMirroringPrefix: c.flagK8SNSMirroringPrefix, + EnableNamespaces: c.flagEnableNamespaces, + PeeringEnabled: c.flagEnablePeering, + EnableOpenShift: c.flagEnableOpenShift, + EnableNamespaceMirroring: c.flagEnableK8SNSMirroring, + AuthMethod: c.consul.ConsulLogin.AuthMethod, + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, + TLSEnabled: c.consul.UseTLS, + ConsulTLSServerName: c.consul.TLSServerName, + ConsulPartition: c.consul.Partition, + ConsulCACert: string(c.caCertPem), + }, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + NamespacesEnabled: c.flagEnableNamespaces, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + Partition: c.consul.Partition, + Datacenter: c.consul.Datacenter, + }) + + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Gateway") + return err + } + + go cache.Run(ctx) + + // wait for the cache to fill + setupLog.Info("waiting for Consul cache sync") + cache.WaitSynced(ctx) + setupLog.Info("Consul cache synced") + + configEntryReconciler := &controllers.ConfigEntryController{ + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + DatacenterName: c.consul.Datacenter, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, + } + if err := (&controllers.ServiceDefaultsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceDefaults), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceDefaults) + return err + } + if err := (&controllers.ServiceResolverController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceResolver), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceResolver) + return err + } + if err := (&controllers.ProxyDefaultsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ProxyDefaults), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ProxyDefaults) + return err + } + if err := (&controllers.MeshController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.Mesh), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.Mesh) + return err + } + if err := (&controllers.ExportedServicesController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ExportedServices), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ExportedServices) + return err + } + if err := (&controllers.ServiceRouterController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceRouter), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceRouter) + return err + } + if err := (&controllers.ServiceSplitterController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceSplitter), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceSplitter) + return err + } + if err := (&controllers.ServiceIntentionsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceIntentions), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceIntentions) + return err + } + if err := (&controllers.IngressGatewayController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.IngressGateway), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.IngressGateway) + return err + } + if err := (&controllers.TerminatingGatewayController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.TerminatingGateway), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.TerminatingGateway) + return err + } + if err := (&controllers.SamenessGroupController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.SamenessGroup), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.SamenessGroup) + return err + } + if err := (&controllers.JWTProviderController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.JWTProvider), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.JWTProvider) + return err + } + if err := (&controllers.ControlPlaneRequestLimitController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ControlPlaneRequestLimit), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ControlPlaneRequestLimit) + return err + } + + if err := mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { + setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) + return err + } + + if c.flagEnablePeering { + if err := (&peering.AcceptorController{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + ExposeServersServiceName: c.flagResourcePrefix + "-expose-servers", + ReleaseNamespace: c.flagReleaseNamespace, + Log: ctrl.Log.WithName("controller").WithName("peering-acceptor"), + Scheme: mgr.GetScheme(), + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "peering-acceptor") + return err + } + if err := (&peering.PeeringDialerController{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + Log: ctrl.Log.WithName("controller").WithName("peering-dialer"), + Scheme: mgr.GetScheme(), + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "peering-dialer") + return err + } + + mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringacceptors", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringAcceptorWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName("peering-acceptor"), + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringdialers", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringDialerWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName("peering-dialer"), + }}) + } + + mgr.GetWebhookServer().CertDir = c.flagCertDir + + mgr.GetWebhookServer().Register("/mutate", + &ctrlRuntimeWebhook.Admission{Handler: &webhook.MeshWebhook{ + Clientset: c.clientset, + ReleaseNamespace: c.flagReleaseNamespace, + ConsulConfig: consulConfig, + ConsulServerConnMgr: watcher, + ImageConsul: c.flagConsulImage, + ImageConsulDataplane: c.flagConsulDataplaneImage, + EnvoyExtraArgs: c.flagEnvoyExtraArgs, + ImageConsulK8S: c.flagConsulK8sImage, + RequireAnnotation: !c.flagDefaultInject, + AuthMethod: c.flagACLAuthMethod, + ConsulCACert: string(c.caCertPem), + TLSEnabled: c.consul.UseTLS, + ConsulAddress: c.consul.Addresses, + SkipServerWatch: c.consul.SkipServerWatch, + ConsulTLSServerName: c.consul.TLSServerName, + DefaultProxyCPURequest: c.sidecarProxyCPURequest, + DefaultProxyCPULimit: c.sidecarProxyCPULimit, + DefaultProxyMemoryRequest: c.sidecarProxyMemoryRequest, + DefaultProxyMemoryLimit: c.sidecarProxyMemoryLimit, + DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, + LifecycleConfig: lifecycleConfig, + MetricsConfig: metricsConfig, + InitContainerResources: c.initContainerResources, + ConsulPartition: c.consul.Partition, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + EnableNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, + K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + EnableCNI: c.flagEnableCNI, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + EnableConsulDNS: c.flagEnableConsulDNS, + EnableOpenShift: c.flagEnableOpenShift, + Log: ctrl.Log.WithName("handler").WithName("connect"), + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, + }}) + + consulMeta := apicommon.ConsulMeta{ + PartitionsEnabled: c.flagEnablePartitions, + Partition: c.consul.Partition, + NamespacesEnabled: c.flagEnableNamespaces, + DestinationNamespace: c.flagConsulDestinationNamespace, + Mirroring: c.flagEnableK8SNSMirroring, + Prefix: c.flagK8SNSMirroringPrefix, + } + + // Note: The path here should be identical to the one on the kubebuilder + // annotation in each webhook file. + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceDefaults), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceResolver), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ProxyDefaults), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-mesh", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.MeshWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.Mesh), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-exportedservices", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ExportedServicesWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ExportedServices), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceRouter), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceSplitter), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceintentions", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceIntentionsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceIntentions), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-ingressgateway", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.IngressGatewayWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.IngressGateway), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-terminatinggateway", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.TerminatingGatewayWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-samenessgroup", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.SamenessGroupWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.SamenessGroup), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-jwtprovider", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.JWTProviderWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.JWTProvider), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-controlplanerequestlimits", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ControlPlaneRequestLimitWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ControlPlaneRequestLimit), + ConsulMeta: consulMeta, + }}) + + if c.flagEnableWebhookCAUpdate { + err = c.updateWebhookCABundle(ctx) + if err != nil { + setupLog.Error(err, "problem getting CA Cert") + return err + } + } + + return nil +} + +func (c *Command) updateWebhookCABundle(ctx context.Context) error { + webhookConfigName := fmt.Sprintf("%s-connect-injector", c.flagResourcePrefix) + caPath := fmt.Sprintf("%s/%s", c.flagCertDir, WebhookCAFilename) + caCert, err := os.ReadFile(caPath) + if err != nil { + return err + } + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) + if err != nil { + return err + } + return nil +} diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go new file mode 100644 index 0000000000..d851f71cb8 --- /dev/null +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -0,0 +1,94 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package connectinject + +import ( + "context" + + "github.com/hashicorp/consul-server-connection-manager/discovery" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { + + //resourceClient, err := consul.NewResourceServiceClient(watcher) + //if err != nil { + // return fmt.Errorf("unable to create Consul resource service client: %w", err) + //} + + //// Create Consul API config object. + //consulConfig := c.consul.ConsulClientConfig() + // + ////Convert allow/deny lists to sets. + //allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) + //denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) + + //lifecycleConfig := lifecycle.Config{ + // DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, + // DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, + // DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, + // DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, + // DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, + //} + + //metricsConfig := metrics.Config{ + // DefaultEnableMetrics: c.flagDefaultEnableMetrics, + // EnableGatewayMetrics: c.flagEnableGatewayMetrics, + // DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, + // DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, + // DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, + // DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, + //} + + // TODO(dans): Pods Controller + //if err := (&pod.Controller{ + // Client: mgr.GetClient(), + // ConsulClientConfig: consulConfig, + // ConsulServerConnMgr: watcher, + // ConsulResourceServiceClient: client, + // AllowK8sNamespacesSet: allowK8sNamespaces, + // DenyK8sNamespacesSet: denyK8sNamespaces, + // MetricsConfig: metricsConfig, + // EnableConsulPartitions: c.flagEnablePartitions, + // EnableConsulNamespaces: c.flagEnableNamespaces, + // ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + // EnableNSMirroring: c.flagEnableK8SNSMirroring, + // NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + // EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + // TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + // AuthMethod: c.flagACLAuthMethod, + // NodeMeta: c.flagNodeMeta, + // Log: ctrl.Log.WithName("controller").WithName("pods"), + // Scheme: mgr.GetScheme(), + // EnableTelemetryCollector: c.flagEnableTelemetryCollector, + // Context: ctx, + //}).SetupWithManager(mgr); err != nil { + // setupLog.Error(err, "unable to create controller", "controller", pod.Controller{}) + // return err + //} + + // TODO: V2 Endpoints Controller + + // TODO: Nodes Controller + + // TODO: Serviceaccounts Controller + + // TODO: V2 Config Controller(s) + + // // Metadata for webhooks + //consulMeta := apicommon.ConsulMeta{ + // PartitionsEnabled: c.flagEnablePartitions, + // Partition: c.consul.Partition, + // NamespacesEnabled: c.flagEnableNamespaces, + // DestinationNamespace: c.flagConsulDestinationNamespace, + // Mirroring: c.flagEnableK8SNSMirroring, + // Prefix: c.flagK8SNSMirroringPrefix, + //} + + // TODO: register webhooks + + // TODO: Update Webhook CA Bundle + + return nil +} From a7418366022ddb44a3b0dffe31ceef41f6f504e5 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Fri, 25 Aug 2023 18:49:07 -0400 Subject: [PATCH 349/592] build: update SDK version to use commit from (#2846) --- control-plane/go.mod | 9 ++++----- control-plane/go.sum | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/control-plane/go.mod b/control-plane/go.mod index 19375ddf5c..55918dceb4 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -1,9 +1,8 @@ module github.com/hashicorp/consul-k8s/control-plane -// TODO(dans) -// This points to a commit on a dev branch. The replace directive should be removed when the SDK is published -// Even after this commit goes into main, the replace directive is needed be because `api` requires 0.14.1 of SDK -replace github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230821222840-992198e5f8c7 +// TODO: remove when the SDK is released for Consul 1.17 +// The replace directive is needed be because `api` requires 0.14.1 of SDK and is both a direct and indirect dependency +replace github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924 require ( github.com/cenkalti/backoff v2.2.1+incompatible @@ -47,7 +46,7 @@ require ( ) require ( - github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230821203006-4c95f8ff8d20 + github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.4 google.golang.org/protobuf v1.30.0 ) diff --git a/control-plane/go.sum b/control-plane/go.sum index 245b7d913a..7868dc727b 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -259,16 +259,16 @@ github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6c github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230821203006-4c95f8ff8d20 h1:zGzmrEaOhcDDAshfZcj+K0kSSsg4OIV7bf0GlSKFJ74= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230821203006-4c95f8ff8d20/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed h1:eM9tGgSqAZbm4Ndkp35Dg8YROT0dNH3ghTYu5pcUIAc= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 h1:TTTgXv9YeaRnODyFP1k2b2Nq5RIGrUUgI5SkDhuSNwM= github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/proto-public v0.1.2-0.20230821180813-217d305b38d5 h1:mN5hKOn+G5fQBuXjdne/HluE4FhesUxscZEblqP4OSQ= github.com/hashicorp/consul/proto-public v0.1.2-0.20230821180813-217d305b38d5/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= -github.com/hashicorp/consul/sdk v0.4.1-0.20230821222840-992198e5f8c7 h1:PZG0XUHJS5f2wie4bmn5CFEUt4dYhNG5lg2Zp5dH3/8= -github.com/hashicorp/consul/sdk v0.4.1-0.20230821222840-992198e5f8c7/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= +github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924 h1:gkb6/ix0Tg1Th5FTjyq4QklLgrtIVQ/TUB0kbhIcPsY= +github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= 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= From b08a15931eac8ecce513b61259976bb1581fe8af Mon Sep 17 00:00:00 2001 From: David Yu Date: Mon, 28 Aug 2023 10:31:38 -0700 Subject: [PATCH 350/592] Revert "Add readOnlyRootFilesystem to security context (#2771)" (#2847) Revert "Add readOnlyRootFilesystem to security context (#2771) (#2789)" This reverts commit b75d8034b96ae1e21c0cca66ad5ee9a63af20505. --- .changelog/2789.txt | 3 --- charts/consul/templates/_helpers.tpl | 1 - charts/consul/templates/ingress-gateways-deployment.yaml | 7 ------- charts/consul/templates/server-statefulset.yaml | 5 ----- .../consul/templates/terminating-gateways-deployment.yaml | 7 ------- charts/consul/test/unit/server-statefulset.bats | 2 -- 6 files changed, 25 deletions(-) delete mode 100644 .changelog/2789.txt diff --git a/.changelog/2789.txt b/.changelog/2789.txt deleted file mode 100644 index c8db7d0f08..0000000000 --- a/.changelog/2789.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: Add readOnlyRootFilesystem to the default restricted security context when runnning `consul-k8s` in a restricted namespaces. -``` diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 044833c11d..06efdbc3a5 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -19,7 +19,6 @@ as well as the global.name setting. {{- if not .Values.global.enablePodSecurityPolicies -}} securityContext: allowPrivilegeEscalation: false - readOnlyRootFilesystem: true capabilities: drop: - ALL diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index d755a80e39..c10f1549f6 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -154,9 +154,6 @@ spec: terminationGracePeriodSeconds: {{ default $defaults.terminationGracePeriodSeconds .terminationGracePeriodSeconds }} serviceAccountName: {{ template "consul.fullname" $root }}-{{ .name }} volumes: - - name: tmp - emptyDir: - medium: "Memory" - name: consul-service emptyDir: medium: "Memory" @@ -218,8 +215,6 @@ spec: -log-level={{ default $root.Values.global.logLevel $root.Values.ingressGateways.logLevel }} \ -log-json={{ $root.Values.global.logJSON }} volumeMounts: - - name: tmp - mountPath: /tmp - name: consul-service mountPath: /consul/service {{- if $root.Values.global.tls.enabled }} @@ -244,8 +239,6 @@ spec: resources: {{ toYaml (default $defaults.resources .resources) | nindent 10 }} {{- end }} volumeMounts: - - name: tmp - mountPath: /tmp - name: consul-service mountPath: /consul/service readOnly: true diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index d6b44d0c1f..b501944da8 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -141,8 +141,6 @@ spec: {{- toYaml .Values.server.securityContext | nindent 8 }} {{- end }} volumes: - - name: tmp - emptyDir: {} - name: config configMap: name: {{ template "consul.fullname" . }}-server-config @@ -455,9 +453,6 @@ spec: mountPath: /trusted-cas readOnly: false {{- end }} - - name: tmp - mountPath: /tmp - readOnly: false ports: {{- if (or (not .Values.global.tls.enabled) (not .Values.global.tls.httpsOnly)) }} - name: http diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index ccfcf3c6a6..9433e44bc9 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -123,9 +123,6 @@ spec: terminationGracePeriodSeconds: 10 serviceAccountName: {{ template "consul.fullname" $root }}-{{ .name }} volumes: - - name: tmp - emptyDir: - medium: "Memory" - name: consul-service emptyDir: medium: "Memory" @@ -203,8 +200,6 @@ spec: -log-level={{ default $root.Values.global.logLevel $root.Values.terminatingGateways.logLevel }} \ -log-json={{ $root.Values.global.logJSON }} volumeMounts: - - name: tmp - mountPath: /tmp - name: consul-service mountPath: /consul/service {{- if $root.Values.global.tls.enabled }} @@ -226,8 +221,6 @@ spec: image: {{ $root.Values.global.imageConsulDataplane | quote }} {{- include "consul.restrictedSecurityContext" $ | nindent 10 }} volumeMounts: - - name: tmp - mountPath: /tmp - name: consul-service mountPath: /consul/service readOnly: true diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 3248f14da3..b0fafca1b8 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -859,7 +859,6 @@ load _helpers "drop": ["ALL"], "add": ["NET_BIND_SERVICE"] }, - "readOnlyRootFilesystem": true, "runAsNonRoot": true, "seccompProfile": { "type": "RuntimeDefault" @@ -892,7 +891,6 @@ load _helpers "drop": ["ALL"], "add": ["NET_BIND_SERVICE"] }, - "readOnlyRootFilesystem": true, "runAsNonRoot": true, "seccompProfile": { "type": "RuntimeDefault" From ae2b3185fa17e6322448a61995ec0136188a5c96 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 28 Aug 2023 13:32:40 -0400 Subject: [PATCH 351/592] Fix issue where CLI install test was running Tproxy manually (#2843) --- acceptance/tests/cli/cli_install_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index dc0ec37500..c8b66ff451 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -26,11 +26,9 @@ const ipv4RegEx = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9] func TestInstall(t *testing.T) { cases := map[string]struct { secure bool - tproxy bool }{ - "not-secure": {secure: false, tproxy: false}, - "secure": {secure: true, tproxy: false}, - "not-secure-tproxy": {secure: false, tproxy: true}, + "not-secure": {secure: false}, + "secure": {secure: true}, } for name, c := range cases { @@ -39,7 +37,6 @@ func TestInstall(t *testing.T) { require.NoError(t, err) cfg := suite.Config() - cfg.EnableTransparentProxy = c.tproxy ctx := suite.Environment().DefaultContext(t) connHelper := connhelper.ConnectHelper{ @@ -102,7 +99,7 @@ func TestInstall(t *testing.T) { logger.Log(t, string(upstreamsOut)) require.NoError(t, err) - if c.tproxy { + if cfg.EnableTransparentProxy { // If tproxy is enabled we are looking for the upstream ip which is the ClusterIP of the Kubernetes Service serverService, err := connHelper.Ctx.KubernetesClient(t).CoreV1().Services(connHelper.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ FieldSelector: "metadata.name=static-server", From 085c812761439c0a1be774dc7158eb441901099f Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 28 Aug 2023 13:33:31 -0400 Subject: [PATCH 352/592] Configure Gateway Deployment Resources (#2723) * Update comments on Deployment * Move resources into managedGatewayClass * Add resource configuration to GatewayClassConfig * Regenerate CRDs * Pass resource configuration into the gateway-resources-job * Pull in resources from GatewayClassConfig * Add flag for resources in `gateway-resources` subcommand * Clean up some comments in existing code * Add gateway-resources configmap * Load configmap into gateway-resources job * Load resources from json * Update CRDs * Read resources in from the configmap * Add BATs for Gateway Resources Configmap * Add Changelog * Fix unquoted value in BATs * Fix how resources.json is read * Fix BATs errors for real * Fix seg fault bug * Fix reading of resources file * Quote "$actual" * Fix zsh/sh differences in BATs * Update control-plane/api-gateway/common/helm_config.go Co-authored-by: Nathan Coleman * Move resources into DeploymentSpec * Remove extra split in crds --------- Co-authored-by: Nathan Coleman --- .changelog/2723.txt | 3 + .../api-gateways/jwt-auth/gateway-policy.yaml | 3 + .../api-gateways/jwt-auth/jwt-provider.yaml | 3 + .../jwt-auth/jwt-route-filter.yaml | 3 + .../crd-controlplanerequestlimits.yaml | 9 ++- .../templates/crd-exportedservices.yaml | 9 ++- .../templates/crd-gatewayclassconfigs.yaml | 81 +++++++++++++++---- .../consul/templates/crd-gatewayclasses.yaml | 5 +- charts/consul/templates/crd-gateways.yaml | 5 +- charts/consul/templates/crd-grpcroutes.yaml | 5 +- charts/consul/templates/crd-httproutes.yaml | 5 +- .../consul/templates/crd-ingressgateways.yaml | 9 ++- charts/consul/templates/crd-jwtproviders.yaml | 9 ++- charts/consul/templates/crd-meshes.yaml | 9 ++- charts/consul/templates/crd-meshservices.yaml | 10 ++- .../templates/crd-peeringacceptors.yaml | 9 ++- .../consul/templates/crd-peeringdialers.yaml | 9 ++- .../consul/templates/crd-proxydefaults.yaml | 9 ++- .../consul/templates/crd-samenessgroups.yaml | 9 ++- .../consul/templates/crd-servicedefaults.yaml | 9 ++- .../templates/crd-serviceintentions.yaml | 9 ++- .../templates/crd-serviceresolvers.yaml | 9 ++- .../consul/templates/crd-servicerouters.yaml | 9 ++- .../templates/crd-servicesplitters.yaml | 9 ++- charts/consul/templates/crd-tcproutes.yaml | 6 +- .../templates/crd-terminatinggateways.yaml | 9 ++- charts/consul/templates/crd-tlsroutes.yaml | 6 +- charts/consul/templates/crd-udproutes.yaml | 6 +- .../gateway-resources-configmap.yaml | 19 +++++ .../templates/gateway-resources-job.yaml | 8 ++ .../unit/gateway-resources-configmap.bats | 46 +++++++++++ charts/consul/values.yaml | 22 ++--- .../api-gateway/common/helm_config.go | 4 +- .../api-gateway/gatekeeper/dataplane.go | 7 +- .../api-gateway/gatekeeper/deployment.go | 11 ++- .../api/v1alpha1/api_gateway_types.go | 3 + .../api/v1alpha1/zz_generated.deepcopy.go | 5 ++ ...sul.hashicorp.com_gatewayclassconfigs.yaml | 58 +++++++++++++ .../subcommand/gateway-resources/command.go | 56 +++++++++++++ 39 files changed, 413 insertions(+), 102 deletions(-) create mode 100644 .changelog/2723.txt create mode 100644 charts/consul/templates/gateway-resources-configmap.yaml create mode 100644 charts/consul/test/unit/gateway-resources-configmap.bats diff --git a/.changelog/2723.txt b/.changelog/2723.txt new file mode 100644 index 0000000000..0e46cba7a7 --- /dev/null +++ b/.changelog/2723.txt @@ -0,0 +1,3 @@ +```release-note:feature +helm: Add ability to configure resource requests and limits for Gateway API deployments. +``` diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml index 8e47d75062..6c58632d87 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: ConsulGatewayPolicy metadata: diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml index 37eb034d3c..1e5cbf35d6 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: JWTProvider metadata: diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml index e0a3128bed..ed0b0bbe5e 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: HTTPRouteAuthFilter metadata: diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml index 2b0c45a621..01722c0cf0 100644 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: controlplanerequestlimits.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 591500cb12..dd6b6ba3b8 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: exportedservices.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 8140902f78..98ecb345f3 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -1,18 +1,20 @@ {{- if .Values.connectInject.enabled }} ---- +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: gatewayclassconfigs.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -78,7 +80,61 @@ spec: maximum: 8 minimum: 1 type: integer + resources: + description: Resources defines the resource requirements for the + gateway. + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be + set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object type: object + mapPrivilegedContainerPorts: + description: The value to add to privileged ports ( ports < 1024) + for gateway containers + format: int32 + type: integer nodeSelector: additionalProperties: type: string @@ -86,6 +142,10 @@ spec: pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' type: object + openshiftSCCName: + description: The name of the OpenShift SecurityContextConstraints + resource for this gateway class to use. + type: string podSecurityPolicy: description: The name of an existing Kubernetes PodSecurityPolicy to bind to the managed ServiceAccount if ACLs are managed. @@ -138,19 +198,6 @@ spec: type: string type: object type: array - openshiftSCCName: - description: The name of an existing SecurityContextConstraints - resource to bind to the managed role when running on OpenShift. - type: string - mapPrivilegedContainerPorts: - type: integer - format: int32 - minimum: 0 - maximum: 64512 - description: mapPrivilegedContainerPorts is the value which Consul will add to privileged container port - values (ports < 1024) defined on a Gateway when the number is greater than 0. This cannot be more than - 64512 as the highest privileged port is 1023, which would then map to 65535, which is the highest - valid port number. type: object type: object served: true diff --git a/charts/consul/templates/crd-gatewayclasses.yaml b/charts/consul/templates/crd-gatewayclasses.yaml index b0725c2baf..391637b5f7 100644 --- a/charts/consul/templates/crd-gatewayclasses.yaml +++ b/charts/consul/templates/crd-gatewayclasses.yaml @@ -7,14 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null name: gatewayclasses.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-gateways.yaml b/charts/consul/templates/crd-gateways.yaml index 923a75477d..ab56d4f5fb 100644 --- a/charts/consul/templates/crd-gateways.yaml +++ b/charts/consul/templates/crd-gateways.yaml @@ -7,14 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null name: gateways.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-grpcroutes.yaml b/charts/consul/templates/crd-grpcroutes.yaml index 07412ba60b..3e4aa75853 100644 --- a/charts/consul/templates/crd-grpcroutes.yaml +++ b/charts/consul/templates/crd-grpcroutes.yaml @@ -7,14 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null name: grpcroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-httproutes.yaml b/charts/consul/templates/crd-httproutes.yaml index d9055e7406..c89591376a 100644 --- a/charts/consul/templates/crd-httproutes.yaml +++ b/charts/consul/templates/crd-httproutes.yaml @@ -7,14 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null name: httproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index a01fafd8dd..7cc4a174ac 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: ingressgateways.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index 8a51d16b68..18b6e9bdcb 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: jwtproviders.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index 0710d41280..b1b2319579 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: meshes.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml index df8f673bdc..d52da5a028 100644 --- a/charts/consul/templates/crd-meshservices.yaml +++ b/charts/consul/templates/crd-meshservices.yaml @@ -1,18 +1,20 @@ {{- if .Values.connectInject.enabled }} ---- +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: meshservices.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index e06e830f04..6f335e83a2 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -1,18 +1,21 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: peeringacceptors.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index e24401e761..5fa49f1eed 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -1,18 +1,21 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: peeringdialers.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 1e931dd888..8596cc9a6f 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: proxydefaults.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index 60beb5662c..179972a9d6 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: samenessgroups.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 870f5ad86c..a0c26df70b 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicedefaults.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index c4d2b5f20d..edc7c7078b 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: serviceintentions.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index eb5643fe49..7965a9df53 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: serviceresolvers.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index f28da9e7c1..d36e8028b5 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicerouters.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index a2af050c3d..15f7714a84 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicesplitters.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-tcproutes.yaml b/charts/consul/templates/crd-tcproutes.yaml index a17f457a78..91989135e2 100644 --- a/charts/consul/templates/crd-tcproutes.yaml +++ b/charts/consul/templates/crd-tcproutes.yaml @@ -7,15 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null name: tcproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 583c218be8..fae09bff53 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -1,18 +1,21 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: terminatinggateways.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-tlsroutes.yaml b/charts/consul/templates/crd-tlsroutes.yaml index be72f47d65..dfabd80713 100644 --- a/charts/consul/templates/crd-tlsroutes.yaml +++ b/charts/consul/templates/crd-tlsroutes.yaml @@ -7,15 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null name: tlsroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-udproutes.yaml b/charts/consul/templates/crd-udproutes.yaml index fe331cca30..935cce22fa 100644 --- a/charts/consul/templates/crd-udproutes.yaml +++ b/charts/consul/templates/crd-udproutes.yaml @@ -7,15 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null name: udproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml new file mode 100644 index 0000000000..591aaa2129 --- /dev/null +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -0,0 +1,19 @@ +{{- if .Values.connectInject.enabled }} +# Configuration of Gateway Resources Job which creates managed Gateway configuration. +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "consul.fullname" . }}-gateway-resources-config + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: gateway-resources +data: + {{- if .Values.connectInject.apiGateway.managedGatewayClass.resources }} + resources.json: | + {{ toJson .Values.connectInject.apiGateway.managedGatewayClass.resources }} + {{- end }} +{{- end }} diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index de64e2d70d..a94c3f4e4e 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -111,6 +111,10 @@ spec: limits: memory: "50Mi" cpu: "50m" + volumeMounts: + - name: config + mountPath: /consul/config + readOnly: true {{- if .Values.global.acls.tolerations }} tolerations: {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} @@ -119,4 +123,8 @@ spec: nodeSelector: {{ tpl .Values.global.acls.nodeSelector . | indent 8 | trim }} {{- end }} + volumes: + - name: config + configMap: + name: {{ template "consul.fullname" . }}-gateway-resources-config {{- end }} diff --git a/charts/consul/test/unit/gateway-resources-configmap.bats b/charts/consul/test/unit/gateway-resources-configmap.bats new file mode 100644 index 0000000000..80225eeefb --- /dev/null +++ b/charts/consul/test/unit/gateway-resources-configmap.bats @@ -0,0 +1,46 @@ +#!/usr/bin/env bats + +load _helpers + +@test "gateway-resources/ConfigMap: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/gateway-resources-configmap.yaml \ + --set 'connectInject.enabled=false' \ + . +} + +@test "gateway-resources/ConfigMap: enabled with connectInject.enabled=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/gateway-resources-configmap.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gateway-resources/ConfigMap: contains resources configuration as JSON" { + cd `chart_dir` + local resources=$(helm template \ + -s templates/gateway-resources-configmap.yaml \ + --set 'connectInject.enabled=true' \ + --set 'connectInject.apiGateway.managedGatewayClass.resources.requests.memory=200Mi' \ + --set 'connectInject.apiGateway.managedGatewayClass.resources.requests.cpu=200m' \ + --set 'connectInject.apiGateway.managedGatewayClass.resources.limits.memory=220Mi' \ + --set 'connectInject.apiGateway.managedGatewayClass.resources.limits.cpu=220m' \ + . | tee /dev/stderr | + yq '.data["resources.json"] | fromjson' | tee /dev/stderr) + + local actual=$(echo $resources | jq -r '.requests.memory') + [ $actual = '200Mi' ] + + local actual=$(echo $resources | jq -r '.requests.cpu') + [ $actual = '200m' ] + + local actual=$(echo $resources | jq -r '.limits.memory') + [ $actual = '220Mi' ] + + local actual=$(echo $resources | jq -r '.limits.cpu') + [ $actual = '220m' ] +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 52e6f77c1d..6f98d05d35 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2213,6 +2213,17 @@ connectInject: # @type: string service: null + # The resource settings for Pods handling traffic for Gateway API. + # @recurse: false + # @type: map + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "100Mi" + cpu: "100m" + # This value defines the number of pods to deploy for each Gateway as well as a min and max number of pods for all Gateways deployment: defaultInstances: 1 @@ -2244,17 +2255,6 @@ connectInject: # @type: string annotations: null - # The resource settings for Pods handling traffic for Gateway API. - # @recurse: false - # @type: map - resources: - requests: - memory: "100Mi" - cpu: "100m" - limits: - memory: "100Mi" - cpu: "100m" - # Configures consul-cni plugin for Consul Service mesh services cni: # If true, then all traffic redirection setup uses the consul-cni plugin. diff --git a/control-plane/api-gateway/common/helm_config.go b/control-plane/api-gateway/common/helm_config.go index ecd9d42c29..7ce8e0778a 100644 --- a/control-plane/api-gateway/common/helm_config.go +++ b/control-plane/api-gateway/common/helm_config.go @@ -14,13 +14,15 @@ const componentAuthMethod = "k8s-component-auth-method" // This is a combination of the apiGateway stanza and other settings that impact api-gateways. type HelmConfig struct { // ImageDataplane is the Consul Dataplane image to use in gateway deployments. - ImageDataplane string + ImageDataplane string + // ImageConsulK8S is the Consul Kubernetes Control Plane image to use in gateway deployments. ImageConsulK8S string ConsulDestinationNamespace string NamespaceMirroringPrefix string EnableNamespaces bool EnableNamespaceMirroring bool AuthMethod string + // LogLevel is the logging level of the deployed Consul Dataplanes. LogLevel string ConsulPartition string diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go index f82e12e8a4..adaf0699b5 100644 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ b/control-plane/api-gateway/gatekeeper/dataplane.go @@ -11,6 +11,7 @@ import ( "k8s.io/utils/pointer" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" "k8s.io/apimachinery/pkg/util/intstr" @@ -26,7 +27,7 @@ const ( volumeName = "consul-connect-inject-data" ) -func consulDataplaneContainer(config common.HelmConfig, name, namespace string) (corev1.Container, error) { +func consulDataplaneContainer(config common.HelmConfig, gcc v1alpha1.GatewayClassConfig, name, namespace string) (corev1.Container, error) { // Extract the service account token's volume mount. var ( err error @@ -99,6 +100,10 @@ func consulDataplaneContainer(config common.HelmConfig, name, namespace string) Name: "proxy-health", ContainerPort: int32(constants.ProxyDefaultHealthPort), }) + // Configure the resource requests and limits for the proxy if they are set. + if gcc.Spec.DeploymentSpec.Resources != nil { + container.Resources = *gcc.Spec.DeploymentSpec.Resources + } // If not running in an OpenShift environment, // skip setting the security context and let OpenShift set it for us. diff --git a/control-plane/api-gateway/gatekeeper/deployment.go b/control-plane/api-gateway/gatekeeper/deployment.go index 3590caaf52..f3e9545e57 100644 --- a/control-plane/api-gateway/gatekeeper/deployment.go +++ b/control-plane/api-gateway/gatekeeper/deployment.go @@ -90,7 +90,7 @@ func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayC return nil, err } - container, err := consulDataplaneContainer(config, gateway.Name, gateway.Namespace) + container, err := consulDataplaneContainer(config, gcc, gateway.Name, gateway.Namespace) if err != nil { return nil, err } @@ -205,17 +205,16 @@ func newDeploymentMutator(deployment, mutated *appsv1.Deployment, gcc v1alpha1.G func deploymentReplicas(gcc v1alpha1.GatewayClassConfig, currentReplicas *int32) *int32 { instanceValue := defaultInstances - //if currentReplicas is not nil use current value when building deployment + // If currentReplicas is not nil use current value when building deployment... if currentReplicas != nil { instanceValue = *currentReplicas } else if gcc.Spec.DeploymentSpec.DefaultInstances != nil { - // otherwise use the default value on the GatewayClassConfig if set + // otherwise use the default value on the GatewayClassConfig if set. instanceValue = *gcc.Spec.DeploymentSpec.DefaultInstances } if gcc.Spec.DeploymentSpec.MaxInstances != nil { - - //check if over maximum and lower to maximum + // Check if the deployment replicas are greater than the maximum and lower to the maximum if so. maxValue := *gcc.Spec.DeploymentSpec.MaxInstances if instanceValue > maxValue { instanceValue = maxValue @@ -223,7 +222,7 @@ func deploymentReplicas(gcc v1alpha1.GatewayClassConfig, currentReplicas *int32) } if gcc.Spec.DeploymentSpec.MinInstances != nil { - //check if less than minimum and raise to minimum + // Check if the deployment replicas are less than the minimum and raise to the minimum if so. minValue := *gcc.Spec.DeploymentSpec.MinInstances if instanceValue < minValue { instanceValue = minValue diff --git a/control-plane/api/v1alpha1/api_gateway_types.go b/control-plane/api/v1alpha1/api_gateway_types.go index c06ac3825f..90f6376d98 100644 --- a/control-plane/api/v1alpha1/api_gateway_types.go +++ b/control-plane/api/v1alpha1/api_gateway_types.go @@ -85,6 +85,9 @@ type DeploymentSpec struct { // +kubebuilder:validation:Minimum=1 // Minimum allowed number of gateway instances MinInstances *int32 `json:"minInstances,omitempty"` + + // Resources defines the resource requirements for the gateway. + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` } //+kubebuilder:object:generate=true diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index ccd69c4281..6786b38004 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -258,6 +258,11 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { *out = new(int32) **out = **in } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml index e60e4a1cfa..c4a510ffad 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml @@ -74,7 +74,61 @@ spec: maximum: 8 minimum: 1 type: integer + resources: + description: Resources defines the resource requirements for the + gateway. + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be + set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object type: object + mapPrivilegedContainerPorts: + description: The value to add to privileged ports ( ports < 1024) + for gateway containers + format: int32 + type: integer nodeSelector: additionalProperties: type: string @@ -82,6 +136,10 @@ spec: pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' type: object + openshiftSCCName: + description: The name of the OpenShift SecurityContextConstraints + resource for this gateway class to use. + type: string podSecurityPolicy: description: The name of an existing Kubernetes PodSecurityPolicy to bind to the managed ServiceAccount if ACLs are managed. diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 6deea27a26..19c7b28742 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -5,9 +5,12 @@ package gatewayresources import ( "context" + "encoding/json" "errors" "flag" "fmt" + "io" + "os" "sync" "time" @@ -19,7 +22,9 @@ import ( "github.com/mitchellh/cli" yaml "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -83,6 +88,7 @@ type Command struct { nodeSelector map[string]string tolerations []corev1.Toleration serviceAnnotations []string + resources corev1.ResourceRequirements ctx context.Context } @@ -152,6 +158,12 @@ func (c *Command) Run(args []string) int { return 1 } + // Load config from the configmap. + if err := c.loadConfig(); err != nil { + c.UI.Error(fmt.Sprintf("Error loading config: %s", err)) + return 1 + } + if c.ctx == nil { c.ctx = context.Background() } @@ -207,6 +219,7 @@ func (c *Command) Run(args []string) int { DefaultInstances: nonZeroOrNil(c.flagDeploymentDefaultInstances), MaxInstances: nonZeroOrNil(c.flagDeploymentMaxInstances), MinInstances: nonZeroOrNil(c.flagDeploymentMinInstances), + Resources: &c.resources, }, OpenshiftSCCName: c.flagOpenshiftSCCName, MapPrivilegedContainerPorts: int32(c.flagMapPrivilegedContainerPorts), @@ -288,6 +301,35 @@ func (c *Command) validateFlags() error { return nil } +func (c *Command) loadConfig() error { + // Load resources.json + file, err := os.Open("/consul/config/resources.json") + if err != nil { + if !os.IsNotExist(err) { + return err + } + c.UI.Info("No resources.json found, using defaults") + c.resources = defaultResourceRequirements() + return nil + } + + resources, err := io.ReadAll(file) + if err != nil { + c.UI.Error(fmt.Sprintf("Unable to read resources.json, using defaults: %s", err)) + c.resources = defaultResourceRequirements() + return err + } + + if err := json.Unmarshal(resources, &c.resources); err != nil { + return err + } + + if err := file.Close(); err != nil { + return err + } + return nil +} + func (c *Command) Synopsis() string { return synopsis } func (c *Command) Help() string { c.once.Do(c.init) @@ -304,6 +346,20 @@ Usage: consul-k8s-control-plane gateway-resources [options] ` +func defaultResourceRequirements() v1.ResourceRequirements { + // This is a fallback. The resource.json file should be present unless explicitly removed. + return v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + v1.ResourceCPU: resource.MustParse("100m"), + }, + Limits: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + v1.ResourceCPU: resource.MustParse("100m"), + }, + } +} + func forceClassConfig(ctx context.Context, k8sClient client.Client, o *v1alpha1.GatewayClassConfig) error { return backoff.Retry(func() error { var existing v1alpha1.GatewayClassConfig From c4cbc323a88f740f62deae96be64dea56110bf0d Mon Sep 17 00:00:00 2001 From: Jitendra Gangwar Date: Tue, 29 Aug 2023 18:24:48 +0530 Subject: [PATCH 353/592] correct prometheus port and scheme annotations if tls is enabled (#2782) * correct prometheus port and scheme annotations if tls is enabled --- .changelog/2782.txt | 3 ++ .../consul/templates/server-statefulset.yaml | 6 ++++ .../consul/test/unit/server-statefulset.bats | 35 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 .changelog/2782.txt diff --git a/.changelog/2782.txt b/.changelog/2782.txt new file mode 100644 index 0000000000..f01db6bafa --- /dev/null +++ b/.changelog/2782.txt @@ -0,0 +1,3 @@ +```release-note:bug +helm: Update prometheus port and scheme annotations if tls is enabled +``` diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index b501944da8..568d28d386 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -119,7 +119,13 @@ spec: {{- if (and .Values.global.metrics.enabled .Values.global.metrics.enableAgentMetrics) }} "prometheus.io/scrape": "true" "prometheus.io/path": "/v1/agent/metrics" + {{- if .Values.global.tls.enabled }} + "prometheus.io/port": "8501" + "prometheus.io/scheme": "https" + {{- else }} "prometheus.io/port": "8500" + "prometheus.io/scheme": "http" + {{- end }} {{- end }} spec: {{- if .Values.server.affinity }} diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index b0fafca1b8..dcef8d1382 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -677,6 +677,41 @@ load _helpers [ "${actual}" = "/v1/agent/metrics" ] } +@test "server/StatefulSet: when global.metrics.enableAgentMetrics=true, adds prometheus scheme=http annotation" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.metrics.enabled=true' \ + --set 'global.metrics.enableAgentMetrics=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations."prometheus.io/scheme"' | tee /dev/stderr) + [ "${actual}" = "http" ] +} + +@test "server/StatefulSet: when global.metrics.enableAgentMetrics=true and global.tls.enabled=true, adds prometheus port=8501 annotation" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.metrics.enabled=true' \ + --set 'global.metrics.enableAgentMetrics=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations."prometheus.io/port"' | tee /dev/stderr) + [ "${actual}" = "8501" ] +} + +@test "server/StatefulSet: when global.metrics.enableAgentMetrics=true and global.tls.enabled=true, adds prometheus scheme=https annotation" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'global.metrics.enabled=true' \ + --set 'global.metrics.enableAgentMetrics=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations."prometheus.io/scheme"' | tee /dev/stderr) + [ "${actual}" = "https" ] +} + #-------------------------------------------------------------------- # config-configmap From 0dc6bc13acd30112fa2869603699e286c450d813 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Tue, 29 Aug 2023 16:19:23 -0400 Subject: [PATCH 354/592] Adds missing fields for PassiveHealthCheck on IngressGateway and ServiceDefault CRDs (#2796) Co-authored-by: Andrew Stucki --- .changelog/2796.txt | 3 + .../tests/fixtures/bases/job-client/job.yaml | 3 + .../fixtures/bases/job-client/service.yaml | 3 + .../bases/job-client/serviceaccount.yaml | 3 + .../kustomization.yaml | 3 + .../patch.yaml | 3 + .../kustomization.yaml | 3 + .../patch.yaml | 3 + .../cases/jobs/job-client-inject/patch.yaml | 3 + .../consul/templates/crd-ingressgateways.yaml | 78 +++++++++++++++++++ .../consul/templates/crd-proxydefaults.yaml | 19 ++--- .../templates/crd-routeretryfilters.yaml | 10 ++- .../templates/crd-routetimeoutfilters.yaml | 10 ++- .../consul/templates/crd-servicedefaults.yaml | 13 +++- .../api/v1alpha1/ingressgateway_types.go | 6 +- .../api/v1alpha1/ingressgateway_types_test.go | 37 +++++++++ .../api/v1alpha1/servicedefaults_types.go | 6 +- .../consul.hashicorp.com_ingressgateways.yaml | 78 +++++++++++++++++++ .../consul.hashicorp.com_proxydefaults.yaml | 19 ++--- ...onsul.hashicorp.com_routeretryfilters.yaml | 10 ++- ...sul.hashicorp.com_routetimeoutfilters.yaml | 10 ++- .../consul.hashicorp.com_servicedefaults.yaml | 13 +++- control-plane/consul/resource_client.go | 3 + control-plane/consul/resource_client_test.go | 3 + 24 files changed, 307 insertions(+), 35 deletions(-) create mode 100644 .changelog/2796.txt diff --git a/.changelog/2796.txt b/.changelog/2796.txt new file mode 100644 index 0000000000..84646b502d --- /dev/null +++ b/.changelog/2796.txt @@ -0,0 +1,3 @@ +```release-note:bug +ingress-gateway: Adds missing PassiveHealthCheck to IngressGateways CRD and updates missing fields on ServiceDefaults CRD +``` \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/job-client/job.yaml b/acceptance/tests/fixtures/bases/job-client/job.yaml index cc5f2f2896..8c31caa7b4 100644 --- a/acceptance/tests/fixtures/bases/job-client/job.yaml +++ b/acceptance/tests/fixtures/bases/job-client/job.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: batch/v1 kind: Job metadata: diff --git a/acceptance/tests/fixtures/bases/job-client/service.yaml b/acceptance/tests/fixtures/bases/job-client/service.yaml index 36ec16133a..c18e1dfa2e 100644 --- a/acceptance/tests/fixtures/bases/job-client/service.yaml +++ b/acceptance/tests/fixtures/bases/job-client/service.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml b/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml index b4637d4d55..006ea2a836 100644 --- a/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml index d87dbf0481..63bc1d1900 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/job-client diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml index 46d1a417ea..24d58895cf 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: batch/v1 kind: Job metadata: diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml index d87dbf0481..63bc1d1900 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + resources: - ../../../bases/job-client diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml index 4db2df127e..eb2774bceb 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: batch/v1 kind: Job metadata: diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml index 5f390c1f7e..338dadce18 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: batch/v1 kind: Job metadata: diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index 7cc4a174ac..51c02422b2 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -81,6 +81,43 @@ spec: while waiting for a connection to be established. format: int32 type: integer + passiveHealthCheck: + description: PassiveHealthCheck configuration determines how upstream + proxy instances will be monitored for removal from the load + balancing pool. + properties: + baseEjectionTime: + description: The base time that a host is ejected for. The + real time is equal to the base time multiplied by the number + of times the host has been ejected and is capped by max_ejection_time + (Default 300s). Defaults to 30s. + type: string + enforcingConsecutive5xx: + description: EnforcingConsecutive5xx is the % chance that + a host will be actually ejected when an outlier status is + detected through consecutive 5xx. This setting can be used + to disable ejection or to ramp it up slowly. Ex. Setting + this to 10 will make it a 10% chance that the host will + be ejected. + format: int32 + type: integer + interval: + description: Interval between health check analysis sweeps. + Each sweep may remove hosts or return hosts to the pool. + Ex. setting this to "10s" will set the interval to 10 seconds. + type: string + maxEjectionPercent: + description: The maximum % of an upstream cluster that can + be ejected due to outlier detection. Defaults to 10% but + will eject at least one host regardless of the value. + format: int32 + type: integer + maxFailures: + description: MaxFailures is the count of consecutive failures + that results in a host being removed from the pool. + format: int32 + type: integer + type: object type: object listeners: description: Listeners declares what ports the ingress gateway should @@ -160,6 +197,47 @@ spec: service is located. Partitioning is a Consul Enterprise feature. type: string + passiveHealthCheck: + description: PassiveHealthCheck configuration determines + how upstream proxy instances will be monitored for removal + from the load balancing pool. + properties: + baseEjectionTime: + description: The base time that a host is ejected + for. The real time is equal to the base time multiplied + by the number of times the host has been ejected + and is capped by max_ejection_time (Default 300s). + Defaults to 30s. + type: string + enforcingConsecutive5xx: + description: EnforcingConsecutive5xx is the % chance + that a host will be actually ejected when an outlier + status is detected through consecutive 5xx. This + setting can be used to disable ejection or to ramp + it up slowly. Ex. Setting this to 10 will make it + a 10% chance that the host will be ejected. + format: int32 + type: integer + interval: + description: Interval between health check analysis + sweeps. Each sweep may remove hosts or return hosts + to the pool. Ex. setting this to "10s" will set + the interval to 10 seconds. + type: string + maxEjectionPercent: + description: The maximum % of an upstream cluster + that can be ejected due to outlier detection. Defaults + to 10% but will eject at least one host regardless + of the value. + format: int32 + type: integer + maxFailures: + description: MaxFailures is the count of consecutive + failures that results in a host being removed from + the pool. + format: int32 + type: integer + type: object requestHeaders: description: Allow HTTP header manipulation to be configured. properties: diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 8596cc9a6f..c61ef28fd3 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -163,15 +163,6 @@ spec: type: string type: array type: object - prioritizeByLocality: - description: PrioritizeByLocality contains the configuration for - locality aware routing. - properties: - mode: - description: Mode specifies the behavior of PrioritizeByLocality - routing. Valid values are "", "none", and "failover". - type: string - type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. @@ -204,6 +195,16 @@ spec: your services secure, we recommend using "strict" mode whenever possible and enabling "permissive" mode only when necessary.' type: string + prioritizeByLocality: + description: PrioritizeByLocality controls whether the locality of + services within the local partition will be used to prioritize connectivity. + properties: + mode: + description: 'Mode specifies the type of prioritization that will + be performed when selecting nodes in the local partition. Valid + values are: "" (default "none"), "none", and "failover".' + type: string + type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the diff --git a/charts/consul/templates/crd-routeretryfilters.yaml b/charts/consul/templates/crd-routeretryfilters.yaml index 3c69a5a2ae..1d538aa8d6 100644 --- a/charts/consul/templates/crd-routeretryfilters.yaml +++ b/charts/consul/templates/crd-routeretryfilters.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: routeretryfilters.consul.hashicorp.com labels: @@ -53,7 +53,7 @@ spec: metadata: type: object spec: - description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter + description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter. properties: numRetries: format: int32 @@ -114,4 +114,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-routetimeoutfilters.yaml b/charts/consul/templates/crd-routetimeoutfilters.yaml index 992d21b35b..28e4ec3ffb 100644 --- a/charts/consul/templates/crd-routetimeoutfilters.yaml +++ b/charts/consul/templates/crd-routetimeoutfilters.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: routetimeoutfilters.consul.hashicorp.com labels: @@ -54,7 +54,7 @@ spec: metadata: type: object spec: - description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter + description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. properties: idleTimeout: description: A Duration represents the elapsed time between two instants @@ -112,4 +112,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index a0c26df70b..9e6c304bec 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -294,18 +294,22 @@ spec: The real time is equal to the base time multiplied by the number of times the host has been ejected and is capped by max_ejection_time (Default 300s). Defaults - to 30000ms or 30s. + to 30s. type: string enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can be used to disable ejection or to ramp it up slowly. + Ex. Setting this to 10 will make it a 10% chance that + the host will be ejected. format: int32 type: integer interval: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts to the pool. + Ex. setting this to "10s" will set the interval to 10 + seconds. type: string maxEjectionPercent: description: The maximum % of an upstream cluster that @@ -411,19 +415,22 @@ spec: The real time is equal to the base time multiplied by the number of times the host has been ejected and is capped by max_ejection_time (Default 300s). Defaults - to 30000ms or 30s. + to 30s. type: string enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can be used to disable ejection or to ramp it up slowly. + Ex. Setting this to 10 will make it a 10% chance that + the host will be ejected. format: int32 type: integer interval: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts - to the pool. + to the pool. Ex. setting this to "10s" will set the + interval to 10 seconds. type: string maxEjectionPercent: description: The maximum % of an upstream cluster that diff --git a/control-plane/api/v1alpha1/ingressgateway_types.go b/control-plane/api/v1alpha1/ingressgateway_types.go index 64e024fbd5..c781ab8cc8 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types.go +++ b/control-plane/api/v1alpha1/ingressgateway_types.go @@ -6,7 +6,6 @@ package v1alpha1 import ( "encoding/json" "fmt" - "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/consul-k8s/control-plane/api/common" @@ -77,6 +76,9 @@ type IngressServiceConfig struct { // will be allowed at a single point in time. Use this to limit HTTP/2 traffic, // since HTTP/2 has many requests per connection. MaxConcurrentRequests *uint32 `json:"maxConcurrentRequests,omitempty"` + // PassiveHealthCheck configuration determines how upstream proxy instances will + // be monitored for removal from the load balancing pool. + PassiveHealthCheck *PassiveHealthCheck `json:"passiveHealthCheck,omitempty"` } type GatewayTLSConfig struct { @@ -364,6 +366,7 @@ func (in IngressService) toConsul() capi.IngressService { MaxConnections: in.MaxConnections, MaxPendingRequests: in.MaxPendingRequests, MaxConcurrentRequests: in.MaxConcurrentRequests, + PassiveHealthCheck: in.PassiveHealthCheck.toConsul(), } } @@ -468,5 +471,6 @@ func (in *IngressServiceConfig) toConsul() *capi.IngressServiceConfig { MaxConnections: in.MaxConnections, MaxPendingRequests: in.MaxPendingRequests, MaxConcurrentRequests: in.MaxConcurrentRequests, + PassiveHealthCheck: in.PassiveHealthCheck.toConsul(), } } diff --git a/control-plane/api/v1alpha1/ingressgateway_types_test.go b/control-plane/api/v1alpha1/ingressgateway_types_test.go index dd1c3835e0..73b53f5fff 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types_test.go +++ b/control-plane/api/v1alpha1/ingressgateway_types_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" ) func TestIngressGateway_MatchesConsul(t *testing.T) { @@ -70,6 +71,17 @@ func TestIngressGateway_MatchesConsul(t *testing.T) { MaxConnections: &defaultMaxConnections, MaxPendingRequests: &defaultMaxPendingRequests, MaxConcurrentRequests: &defaultMaxConcurrentRequests, + PassiveHealthCheck: &PassiveHealthCheck{ + Interval: metav1.Duration{ + Duration: 2 * time.Second, + }, + MaxFailures: uint32(20), + EnforcingConsecutive5xx: pointer.Uint32(100), + MaxEjectionPercent: pointer.Uint32(10), + BaseEjectionTime: &metav1.Duration{ + Duration: 10 * time.Second, + }, + }, }, Listeners: []IngressListener{ { @@ -170,6 +182,13 @@ func TestIngressGateway_MatchesConsul(t *testing.T) { MaxConnections: &defaultMaxConnections, MaxPendingRequests: &defaultMaxPendingRequests, MaxConcurrentRequests: &defaultMaxConcurrentRequests, + PassiveHealthCheck: &capi.PassiveHealthCheck{ + Interval: 2 * time.Second, + MaxFailures: uint32(20), + EnforcingConsecutive5xx: pointer.Uint32(100), + MaxEjectionPercent: pointer.Uint32(10), + BaseEjectionTime: pointer.Duration(10 * time.Second), + }, }, Listeners: []capi.IngressListener{ { @@ -332,6 +351,17 @@ func TestIngressGateway_ToConsul(t *testing.T) { MaxConnections: &defaultMaxConnections, MaxPendingRequests: &defaultMaxPendingRequests, MaxConcurrentRequests: &defaultMaxConcurrentRequests, + PassiveHealthCheck: &PassiveHealthCheck{ + Interval: metav1.Duration{ + Duration: 2 * time.Second, + }, + MaxFailures: uint32(20), + EnforcingConsecutive5xx: pointer.Uint32(100), + MaxEjectionPercent: pointer.Uint32(10), + BaseEjectionTime: &metav1.Duration{ + Duration: 10 * time.Second, + }, + }, }, Listeners: []IngressListener{ { @@ -431,6 +461,13 @@ func TestIngressGateway_ToConsul(t *testing.T) { MaxConnections: &defaultMaxConnections, MaxPendingRequests: &defaultMaxPendingRequests, MaxConcurrentRequests: &defaultMaxConcurrentRequests, + PassiveHealthCheck: &capi.PassiveHealthCheck{ + Interval: 2 * time.Second, + MaxFailures: uint32(20), + EnforcingConsecutive5xx: pointer.Uint32(100), + MaxEjectionPercent: pointer.Uint32(10), + BaseEjectionTime: pointer.Duration(10 * time.Second), + }, }, Listeners: []capi.IngressListener{ { diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 54044cb3a8..2896475f75 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -187,7 +187,8 @@ type UpstreamLimits struct { // be monitored for removal from the load balancing pool. type PassiveHealthCheck struct { // Interval between health check analysis sweeps. Each sweep may remove - // hosts or return hosts to the pool. + // hosts or return hosts to the pool. Ex. setting this to "10s" will set + // the interval to 10 seconds. Interval metav1.Duration `json:"interval,omitempty"` // MaxFailures is the count of consecutive failures that results in a host // being removed from the pool. @@ -195,13 +196,14 @@ type PassiveHealthCheck struct { // EnforcingConsecutive5xx is the % chance that a host will be actually ejected // when an outlier status is detected through consecutive 5xx. // This setting can be used to disable ejection or to ramp it up slowly. + // Ex. Setting this to 10 will make it a 10% chance that the host will be ejected. EnforcingConsecutive5xx *uint32 `json:"enforcingConsecutive5xx,omitempty"` // The maximum % of an upstream cluster that can be ejected due to outlier detection. // Defaults to 10% but will eject at least one host regardless of the value. MaxEjectionPercent *uint32 `json:"maxEjectionPercent,omitempty"` // The base time that a host is ejected for. The real time is equal to the base time // multiplied by the number of times the host has been ejected and is capped by - // max_ejection_time (Default 300s). Defaults to 30000ms or 30s. + // max_ejection_time (Default 300s). Defaults to 30s. BaseEjectionTime *metav1.Duration `json:"baseEjectionTime,omitempty"` } diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index fd8ebc86ff..e9994d8457 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -74,6 +74,43 @@ spec: while waiting for a connection to be established. format: int32 type: integer + passiveHealthCheck: + description: PassiveHealthCheck configuration determines how upstream + proxy instances will be monitored for removal from the load + balancing pool. + properties: + baseEjectionTime: + description: The base time that a host is ejected for. The + real time is equal to the base time multiplied by the number + of times the host has been ejected and is capped by max_ejection_time + (Default 300s). Defaults to 30s. + type: string + enforcingConsecutive5xx: + description: EnforcingConsecutive5xx is the % chance that + a host will be actually ejected when an outlier status is + detected through consecutive 5xx. This setting can be used + to disable ejection or to ramp it up slowly. Ex. Setting + this to 10 will make it a 10% chance that the host will + be ejected. + format: int32 + type: integer + interval: + description: Interval between health check analysis sweeps. + Each sweep may remove hosts or return hosts to the pool. + Ex. setting this to "10s" will set the interval to 10 seconds. + type: string + maxEjectionPercent: + description: The maximum % of an upstream cluster that can + be ejected due to outlier detection. Defaults to 10% but + will eject at least one host regardless of the value. + format: int32 + type: integer + maxFailures: + description: MaxFailures is the count of consecutive failures + that results in a host being removed from the pool. + format: int32 + type: integer + type: object type: object listeners: description: Listeners declares what ports the ingress gateway should @@ -153,6 +190,47 @@ spec: service is located. Partitioning is a Consul Enterprise feature. type: string + passiveHealthCheck: + description: PassiveHealthCheck configuration determines + how upstream proxy instances will be monitored for removal + from the load balancing pool. + properties: + baseEjectionTime: + description: The base time that a host is ejected + for. The real time is equal to the base time multiplied + by the number of times the host has been ejected + and is capped by max_ejection_time (Default 300s). + Defaults to 30s. + type: string + enforcingConsecutive5xx: + description: EnforcingConsecutive5xx is the % chance + that a host will be actually ejected when an outlier + status is detected through consecutive 5xx. This + setting can be used to disable ejection or to ramp + it up slowly. Ex. Setting this to 10 will make it + a 10% chance that the host will be ejected. + format: int32 + type: integer + interval: + description: Interval between health check analysis + sweeps. Each sweep may remove hosts or return hosts + to the pool. Ex. setting this to "10s" will set + the interval to 10 seconds. + type: string + maxEjectionPercent: + description: The maximum % of an upstream cluster + that can be ejected due to outlier detection. Defaults + to 10% but will eject at least one host regardless + of the value. + format: int32 + type: integer + maxFailures: + description: MaxFailures is the count of consecutive + failures that results in a host being removed from + the pool. + format: int32 + type: integer + type: object requestHeaders: description: Allow HTTP header manipulation to be configured. properties: diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 25e61a51ee..e7ef98d96a 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -156,15 +156,6 @@ spec: type: string type: array type: object - prioritizeByLocality: - description: PrioritizeByLocality contains the configuration for - locality aware routing. - properties: - mode: - description: Mode specifies the behavior of PrioritizeByLocality - routing. Valid values are "", "none", and "failover". - type: string - type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. @@ -197,6 +188,16 @@ spec: your services secure, we recommend using "strict" mode whenever possible and enabling "permissive" mode only when necessary.' type: string + prioritizeByLocality: + description: PrioritizeByLocality controls whether the locality of + services within the local partition will be used to prioritize connectivity. + properties: + mode: + description: 'Mode specifies the type of prioritization that will + be performed when selecting nodes in the local partition. Valid + values are: "" (default "none"), "none", and "failover".' + type: string + type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml index 5928864ac5..c27f7d663f 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: routeretryfilters.consul.hashicorp.com spec: @@ -49,7 +49,7 @@ spec: metadata: type: object spec: - description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter + description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter. properties: numRetries: format: int32 @@ -110,3 +110,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml index e671ecd9b9..6eb46a6171 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: routetimeoutfilters.consul.hashicorp.com spec: @@ -50,7 +50,7 @@ spec: metadata: type: object spec: - description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter + description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. properties: idleTimeout: description: A Duration represents the elapsed time between two instants @@ -108,3 +108,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 5a2c7a58fd..d4d639e55c 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -287,18 +287,22 @@ spec: The real time is equal to the base time multiplied by the number of times the host has been ejected and is capped by max_ejection_time (Default 300s). Defaults - to 30000ms or 30s. + to 30s. type: string enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can be used to disable ejection or to ramp it up slowly. + Ex. Setting this to 10 will make it a 10% chance that + the host will be ejected. format: int32 type: integer interval: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts to the pool. + Ex. setting this to "10s" will set the interval to 10 + seconds. type: string maxEjectionPercent: description: The maximum % of an upstream cluster that @@ -404,19 +408,22 @@ spec: The real time is equal to the base time multiplied by the number of times the host has been ejected and is capped by max_ejection_time (Default 300s). Defaults - to 30000ms or 30s. + to 30s. type: string enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can be used to disable ejection or to ramp it up slowly. + Ex. Setting this to 10 will make it a 10% chance that + the host will be ejected. format: int32 type: integer interval: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts - to the pool. + to the pool. Ex. setting this to "10s" will set the + interval to 10 seconds. type: string maxEjectionPercent: description: The maximum % of an upstream cluster that diff --git a/control-plane/consul/resource_client.go b/control-plane/consul/resource_client.go index 17dc4a9229..edb2d2fce9 100644 --- a/control-plane/consul/resource_client.go +++ b/control-plane/consul/resource_client.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/control-plane/consul/resource_client_test.go b/control-plane/consul/resource_client_test.go index 29507304b0..86d8cee029 100644 --- a/control-plane/consul/resource_client_test.go +++ b/control-plane/consul/resource_client_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( From 30563234c4196a80c6c0bef9dd02ac6f17d599c1 Mon Sep 17 00:00:00 2001 From: aahel Date: Wed, 30 Aug 2023 10:54:20 +0530 Subject: [PATCH 355/592] added check if anonymous token policy exists (#2790) * added check if anonymous token policy exists * changed checkIfAnonymousTokenPolicyExists impl * made consts private * added test for configureAnonymousPolicy * fixed unit test * fixed test and minor refactoring * fix typo * changed some var names * added changelog --- .changelog/2790.txt | 3 + .../server-acl-init/anonymous_token.go | 34 +++++++++- .../server-acl-init/anonymous_token_test.go | 68 +++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 .changelog/2790.txt create mode 100644 control-plane/subcommand/server-acl-init/anonymous_token_test.go diff --git a/.changelog/2790.txt b/.changelog/2790.txt new file mode 100644 index 0000000000..c16b55f74d --- /dev/null +++ b/.changelog/2790.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: prevent updation of anonymous-token-policy and anonymous-token if anonymous-token-policy is already attached to the anonymous-token +``` \ No newline at end of file diff --git a/control-plane/subcommand/server-acl-init/anonymous_token.go b/control-plane/subcommand/server-acl-init/anonymous_token.go index 32c19ec208..2f7ad6f513 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token.go @@ -7,9 +7,24 @@ import ( "github.com/hashicorp/consul/api" ) +const ( + anonymousTokenPolicyName = "anonymous-token-policy" + anonymousTokenAccessorID = "00000000-0000-0000-0000-000000000002" +) + // configureAnonymousPolicy sets up policies and tokens so that Consul DNS and // cross-datacenter Consul connect calls will work. func (c *Command) configureAnonymousPolicy(consulClient *api.Client) error { + exists, err := checkIfAnonymousTokenPolicyExists(consulClient) + if err != nil { + c.log.Error("Error checking if anonymous token policy exists", "err", err) + return err + } + if exists { + c.log.Info("skipping creating anonymous token since it already exists") + return nil + } + anonRules, err := c.anonymousTokenRules() if err != nil { c.log.Error("Error templating anonymous token rules", "err", err) @@ -18,7 +33,7 @@ func (c *Command) configureAnonymousPolicy(consulClient *api.Client) error { // Create policy for the anonymous token anonPolicy := api.ACLPolicy{ - Name: "anonymous-token-policy", + Name: anonymousTokenPolicyName, Description: "Anonymous token Policy", Rules: anonRules, } @@ -33,7 +48,7 @@ func (c *Command) configureAnonymousPolicy(consulClient *api.Client) error { // Create token to get sent to TokenUpdate aToken := api.ACLToken{ - AccessorID: "00000000-0000-0000-0000-000000000002", + AccessorID: anonymousTokenAccessorID, Policies: []*api.ACLTokenPolicyLink{{Name: anonPolicy.Name}}, } @@ -44,3 +59,18 @@ func (c *Command) configureAnonymousPolicy(consulClient *api.Client) error { return err }) } + +func checkIfAnonymousTokenPolicyExists(consulClient *api.Client) (bool, error) { + token, _, err := consulClient.ACL().TokenRead(anonymousTokenAccessorID, nil) + if err != nil { + return false, err + } + + for _, policy := range token.Policies { + if policy.Name == anonymousTokenPolicyName { + return true, nil + } + } + + return false, nil +} diff --git a/control-plane/subcommand/server-acl-init/anonymous_token_test.go b/control-plane/subcommand/server-acl-init/anonymous_token_test.go new file mode 100644 index 0000000000..4a36c676e1 --- /dev/null +++ b/control-plane/subcommand/server-acl-init/anonymous_token_test.go @@ -0,0 +1,68 @@ +package serveraclinit + +import ( + "strings" + "testing" + + "github.com/hashicorp/consul/api" + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" +) + +func Test_configureAnonymousPolicy(t *testing.T) { + + k8s, testClient := completeSetup(t) + consulHTTPAddr := testClient.TestServer.HTTPAddr + consulGRPCAddr := testClient.TestServer.GRPCAddr + + setUpK8sServiceAccount(t, k8s, ns) + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + } + cmd.init() + flags := []string{"-connect-inject"} + cmdArgs := append([]string{ + "-timeout=1m", + "-resource-prefix=" + resourcePrefix, + "-k8s-namespace=" + ns, + "-auth-method-host=https://my-kube.com", + "-addresses", strings.Split(consulHTTPAddr, ":")[0], + "-http-port", strings.Split(consulHTTPAddr, ":")[1], + "-grpc-port", strings.Split(consulGRPCAddr, ":")[1], + }, flags...) + responseCode := cmd.Run(cmdArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + + bootToken := getBootToken(t, k8s, resourcePrefix, ns) + consul, err := api.NewClient(&api.Config{ + Address: consulHTTPAddr, + Token: bootToken, + }) + require.NoError(t, err) + + err = cmd.configureAnonymousPolicy(consul) + require.NoError(t, err) + + policy, _, err := consul.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) + require.NoError(t, err) + + testPolicy := api.ACLPolicy{ + ID: policy.ID, + Name: anonymousTokenPolicyName, + Description: "Anonymous token Policy", + Rules: `acl = "read"`, + } + readOnlyPolicy, _, err := consul.ACL().PolicyUpdate(&testPolicy, &api.WriteOptions{}) + require.NoError(t, err) + + err = cmd.configureAnonymousPolicy(consul) + require.NoError(t, err) + + actualPolicy, _, err := consul.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) + require.NoError(t, err) + + // assert policy is still same. + require.Equal(t, readOnlyPolicy, actualPolicy) +} From ef30dc0d09df4865fbbbc61bbe51936aadefa02e Mon Sep 17 00:00:00 2001 From: Ashesh Vidyut <134911583+absolutelightning@users.noreply.github.com> Date: Thu, 31 Aug 2023 08:10:45 +0530 Subject: [PATCH 356/592] Net 5229 create dedicated argocd stanza (#2785) * enable argocd * adds bats test and setting argo annotations if global.argocd.enabled = true * update comment * added change log * Update charts/consul/templates/gateway-cleanup-job.yaml Co-authored-by: Ganesh S * comments fixes * fix line diff * change log fix * fix comment * Update .changelog/2785.txt Co-authored-by: Luke Kysow <1034429+lkysow@users.noreply.github.com> --------- Co-authored-by: Ganesh S Co-authored-by: Luke Kysow <1034429+lkysow@users.noreply.github.com> --- .changelog/2785.txt | 3 ++ .../consul/templates/server-acl-init-job.yaml | 4 +++ .../consul/test/unit/server-acl-init-job.bats | 36 +++++++++++++++++++ charts/consul/values.yaml | 8 +++++ 4 files changed, 51 insertions(+) create mode 100644 .changelog/2785.txt diff --git a/.changelog/2785.txt b/.changelog/2785.txt new file mode 100644 index 0000000000..02a7b3748e --- /dev/null +++ b/.changelog/2785.txt @@ -0,0 +1,3 @@ +```release-note:improvement +Add new value `global.argocd.enabled`. Set this to `true` when using ArgoCD to deploy this chart. +``` \ No newline at end of file diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index e8a06cf7aa..a72d12f80d 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -49,6 +49,10 @@ spec: {{- if .Values.global.acls.annotations }} {{- tpl .Values.global.acls.annotations . | nindent 8 }} {{- end }} + {{- if .Values.global.argocd.enabled }} + "argocd.argoproj.io/hook": "Sync" + "argocd.argoproj.io/hook-delete-policy": "HookSucceeded" + {{- end }} {{- if .Values.global.secretsBackend.vault.enabled }} {{- /* Run the Vault agent as both an init container and sidecar. diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 1dc55a9551..81022a8e4c 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -2296,3 +2296,39 @@ load _helpers yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) [ "${actual}" = "bar" ] } + +@test "serverACLInit/Job: argocd annotations are set if global.argocd.enabled is true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.argocd.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations["argocd.argoproj.io/hook"]' | tee /dev/stderr) + [ "${actual}" = "Sync" ] + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.argocd.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations["argocd.argoproj.io/hook-delete-policy"]' | tee /dev/stderr) + [ "${actual}" = "HookSucceeded" ] +} + +@test "serverACLInit/Job: argocd annotations are not set if global.argocd.enabled is false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.argocd.enabled=false' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations["argocd.argoproj.io/hook"]' | tee /dev/stderr) + [ "${actual}" = null ] + local actual=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.argocd.enabled=false' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations["argocd.argoproj.io/hook-delete-policy"]' | tee /dev/stderr) + [ "${actual}" = null ] +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 6f98d05d35..38e7989744 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -524,6 +524,14 @@ global: # @type: string annotations: null + # If argocd.enabled is set to true, following annotations are added to + # job - server-acl-init-job + # annotations - + # argocd.argoproj.io/hook: Sync + # argocd.argoproj.io/hook-delete-policy: HookSucceeded + argocd: + enabled: false + # [Enterprise Only] This value refers to a Kubernetes or Vault secret that you have created # that contains your enterprise license. It is required if you are using an # enterprise binary. Defining it here applies it to your cluster once a leader From 88fa7e105a8a3bdd09bd5e98c9acfb2204a9d281 Mon Sep 17 00:00:00 2001 From: David Yu Date: Thu, 31 Aug 2023 14:51:58 -0700 Subject: [PATCH 357/592] docs - Update connectInject.logLevel docs (#2871) * Update values.yaml Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> --- charts/consul/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 38e7989744..a4074683b5 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2440,7 +2440,7 @@ connectInject: # @type: string imageConsul: null - # Override global log verbosity level. One of "debug", "info", "warn", or "error". + # Sets the `logLevel` for the `consul-dataplane` sidecar and the `consul-connect-inject-init` container. When set, this value overrides the global log verbosity level. One of "debug", "info", "warn", or "error". # @type: string logLevel: "" From af16373ee2a2fc8b6d9f2e8585d46a703ce0180d Mon Sep 17 00:00:00 2001 From: Sophie Gairo <97480023+sophie-gairo@users.noreply.github.com> Date: Fri, 1 Sep 2023 15:03:01 -0500 Subject: [PATCH 358/592] NET-5389- Remove global.acls.nodeSelector and global.acls.annotations from Gateway Resources Jobs (#2869) * Remove and from Gateway Resources Jobs * changelog * Remove acl annotations from gateway resources unit tests --- .changelog/2869.txt | 3 +++ charts/consul/templates/gateway-cleanup-job.yaml | 7 ------- charts/consul/templates/gateway-resources-job.yaml | 7 ------- charts/consul/test/unit/gateway-cleanup-job.bats | 10 ---------- charts/consul/test/unit/gateway-resources-job.bats | 10 ---------- 5 files changed, 3 insertions(+), 34 deletions(-) create mode 100644 .changelog/2869.txt diff --git a/.changelog/2869.txt b/.changelog/2869.txt new file mode 100644 index 0000000000..8771462414 --- /dev/null +++ b/.changelog/2869.txt @@ -0,0 +1,3 @@ +```release-note:bug +bug: Remove `global.acls.nodeSelector` and `global.acls.annotations` from Gateway Resources Jobs +``` diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index a987c3b591..20d2f8116e 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -31,9 +31,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - {{- if .Values.global.acls.annotations }} - {{- tpl .Values.global.acls.annotations . | nindent 8 }} - {{- end }} spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gateway-cleanup @@ -58,8 +55,4 @@ spec: tolerations: {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} {{- end }} - {{- if .Values.global.acls.nodeSelector }} - nodeSelector: - {{ tpl .Values.global.acls.nodeSelector . | indent 8 | trim }} - {{- end }} {{- end }} diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index a94c3f4e4e..de510d9dc4 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -31,9 +31,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - {{- if .Values.global.acls.annotations }} - {{- tpl .Values.global.acls.annotations . | nindent 8 }} - {{- end }} spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gateway-resources @@ -119,10 +116,6 @@ spec: tolerations: {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} {{- end }} - {{- if .Values.global.acls.nodeSelector }} - nodeSelector: - {{ tpl .Values.global.acls.nodeSelector . | indent 8 | trim }} - {{- end }} volumes: - name: config configMap: diff --git a/charts/consul/test/unit/gateway-cleanup-job.bats b/charts/consul/test/unit/gateway-cleanup-job.bats index 657bf4a791..711974b9c2 100644 --- a/charts/consul/test/unit/gateway-cleanup-job.bats +++ b/charts/consul/test/unit/gateway-cleanup-job.bats @@ -33,13 +33,3 @@ target=templates/gateway-cleanup-job.yaml yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } - -@test "gatewaycleanup/Job: annotations can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set 'global.acls.annotations=foo: bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats index 09322bd2a7..7d9334bae4 100644 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ b/charts/consul/test/unit/gateway-resources-job.bats @@ -152,13 +152,3 @@ target=templates/gateway-resources-job.yaml yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } - -@test "gatewayresources/Job: annotations can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set 'global.acls.annotations=foo: bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} From decdca592ff3231af53ff561b8869f02ae082e0b Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 5 Sep 2023 09:39:06 -0400 Subject: [PATCH 359/592] Filter api-gateway cache logging to reduce log output on server disconnect (#2880) * Reduce api-gateway logging * add changelog --- .changelog/2880.txt | 3 +++ control-plane/api-gateway/cache/consul.go | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changelog/2880.txt diff --git a/.changelog/2880.txt b/.changelog/2880.txt new file mode 100644 index 0000000000..b06fcf985f --- /dev/null +++ b/.changelog/2880.txt @@ -0,0 +1,3 @@ +```release-note:improvement +api-gateway: reduce log output when disconnecting from consul server +``` diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index 7737e80d57..f34538103d 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -196,7 +196,9 @@ func (c *Cache) subscribeToConsul(ctx context.Context, kind string) { if err != nil { // if we timeout we don't care about the error message because it's expected to happen on long polls // any other error we want to alert on - if !strings.Contains(strings.ToLower(err.Error()), "timeout") { + if !strings.Contains(strings.ToLower(err.Error()), "timeout") && + !strings.Contains(strings.ToLower(err.Error()), "no such host") && + !strings.Contains(strings.ToLower(err.Error()), "connection refused") { c.logger.Error(err, fmt.Sprintf("error fetching config entries for kind: %s", kind)) } continue From 096954cbd96a5d3dceea7d8667468fa9104392cf Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 5 Sep 2023 08:07:24 -0700 Subject: [PATCH 360/592] removed deprecated `patchesStrategicMerge` (#2894) * removed deprecated `patchesStrategicMerge` * fixed some extra whitespace --- .../job-client-inject-grace-period-0s/kustomization.yaml | 9 +++++---- .../kustomization.yaml | 9 +++++---- .../cases/jobs/job-client-inject/kustomization.yaml | 8 +++++--- .../dc1-ns2-static-server/kustomization.yaml | 8 +++++--- .../wan-federation/dc1-static-server/kustomization.yaml | 8 +++++--- .../wan-federation/dc2-static-server/kustomization.yaml | 8 +++++--- .../wan-federation/service-resolver/kustomization.yaml | 8 +++++--- .../wan-federation/static-client/kustomization.yaml | 8 +++++--- 8 files changed, 40 insertions(+), 26 deletions(-) diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml index 63bc1d1900..4a3d4f8f3a 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml @@ -1,9 +1,10 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 - resources: - - ../../../bases/job-client +- ../../../bases/job-client -patchesStrategicMerge: - - patch.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml index 63bc1d1900..4a3d4f8f3a 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml @@ -1,9 +1,10 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 - resources: - - ../../../bases/job-client +- ../../../bases/job-client -patchesStrategicMerge: - - patch.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml index 0aee0558b7..4a3d4f8f3a 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml @@ -2,7 +2,9 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ../../../bases/job-client +- ../../../bases/job-client -patchesStrategicMerge: - - patch.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml index 0775282c18..8fa56a3448 100644 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml @@ -2,7 +2,9 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ../../../bases/static-server +- ../../../bases/static-server -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml index 0775282c18..8fa56a3448 100644 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml @@ -2,7 +2,9 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ../../../bases/static-server +- ../../../bases/static-server -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml index 0775282c18..8fa56a3448 100644 --- a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml @@ -2,7 +2,9 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ../../../bases/static-server +- ../../../bases/static-server -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml index 36e8097f47..6be0f308c5 100644 --- a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml @@ -2,7 +2,9 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ../../../bases/service-resolver +- ../../../bases/service-resolver -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml index 38bc36bffd..583889f5d8 100644 --- a/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml @@ -2,7 +2,9 @@ # SPDX-License-Identifier: MPL-2.0 resources: - - ../../../bases/static-client +- ../../../bases/static-client -patchesStrategicMerge: - - patch.yaml \ No newline at end of file +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patch.yaml From d58a340c3d47f84e3a0e5987d8e188c34aeaacf9 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 5 Sep 2023 11:32:31 -0400 Subject: [PATCH 361/592] NET-5186 Add NET_BIND_SERVICE to built-in PSPs for consul-dataplane deployments (#2890) Add NET_BIND_SERVICE to built-in PSPs for consul-dataplane deployments --- charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml | 2 ++ charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml | 2 ++ .../consul/templates/telemetry-collector-podsecuritypolicy.yaml | 2 ++ .../templates/terminating-gateways-podsecuritypolicy.yaml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml b/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml index f7354da2b3..b847e44ebd 100644 --- a/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml +++ b/charts/consul/templates/ingress-gateways-podsecuritypolicy.yaml @@ -21,6 +21,8 @@ spec: # but we can provide it for defense in depth. requiredDropCapabilities: - ALL + defaultAddCapabilities: + - NET_BIND_SERVICE # Allow core volume types. volumes: - 'configMap' diff --git a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml index b5bbb2fa03..04576fe926 100644 --- a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml +++ b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml @@ -18,6 +18,8 @@ spec: # but we can provide it for defense in depth. requiredDropCapabilities: - ALL + defaultAddCapabilities: + - NET_BIND_SERVICE # Allow core volume types. volumes: - 'configMap' diff --git a/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml b/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml index 286a92d0bd..f4c05a2f33 100644 --- a/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml +++ b/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml @@ -18,6 +18,8 @@ spec: # but we can provide it for defense in depth. requiredDropCapabilities: - ALL + defaultAddCapabilities: + - NET_BIND_SERVICE # Allow core volume types. volumes: - 'configMap' diff --git a/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml b/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml index 97ad2af961..7307fb8be9 100644 --- a/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml +++ b/charts/consul/templates/terminating-gateways-podsecuritypolicy.yaml @@ -21,6 +21,8 @@ spec: # but we can provide it for defense in depth. requiredDropCapabilities: - ALL + defaultAddCapabilities: + - NET_BIND_SERVICE # Allow core volume types. volumes: - 'configMap' From 8c44e1de3a66a9cafe931026d91d5255f006e657 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Tue, 5 Sep 2023 13:43:53 -0400 Subject: [PATCH 362/592] feat: add v2 pod controller w/ workload lifecycle (#2868) * feat: add v2 pod controller w/ workload lifecycle --- .../api-gateway/cache/consul_test.go | 9 +- .../catalog/to-consul/syncer_test.go | 7 +- control-plane/connect-inject/common/common.go | 25 +- .../connect-inject/common/common_test.go | 59 +- .../constants/annotations_and_labels.go | 25 +- .../connect-inject/constants/constants.go | 6 + .../endpoints/consul_client_health_checks.go | 12 +- .../consul_client_health_checks_test.go | 7 +- .../endpoints/endpoints_controller.go | 36 +- .../endpoints_controller_ent_test.go | 9 +- .../endpoints/endpoints_controller_test.go | 66 +- .../controllers/pod/pod_controller.go | 337 ++++++ .../pod/pod_controller_ent_test.go | 44 + .../controllers/pod/pod_controller_test.go | 1035 +++++++++++++++++ .../connect-inject/webhook/mesh_webhook.go | 1 + .../webhook/mesh_webhook_test.go | 83 +- control-plane/consul/resource_client.go | 3 +- control-plane/go.mod | 4 +- control-plane/go.sum | 4 +- control-plane/helper/test/test_util.go | 49 +- .../inject-connect/v2controllers.go | 63 +- .../server-acl-init/command_test.go | 17 +- 22 files changed, 1725 insertions(+), 176 deletions(-) create mode 100644 control-plane/connect-inject/controllers/pod/pod_controller.go create mode 100644 control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go create mode 100644 control-plane/connect-inject/controllers/pod/pod_controller_test.go diff --git a/control-plane/api-gateway/cache/consul_test.go b/control-plane/api-gateway/cache/consul_test.go index 59570e532f..3a4423f6b4 100644 --- a/control-plane/api-gateway/cache/consul_test.go +++ b/control-plane/api-gateway/cache/consul_test.go @@ -21,11 +21,12 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/event" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul/api" ) func Test_resourceCache_diff(t *testing.T) { @@ -1322,7 +1323,7 @@ func TestCache_Write(t *testing.T) { GRPCPort: port, APITimeout: 0, }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), NamespacesEnabled: false, Logger: logrtest.NewTestLogger(t), }) @@ -1600,7 +1601,7 @@ func Test_Run(t *testing.T) { GRPCPort: port, APITimeout: 0, }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), NamespacesEnabled: false, Logger: logrtest.NewTestLogger(t), }) @@ -2001,7 +2002,7 @@ func TestCache_Delete(t *testing.T) { GRPCPort: port, APITimeout: 0, }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), NamespacesEnabled: false, Logger: logrtest.NewTestLogger(t), }) diff --git a/control-plane/catalog/to-consul/syncer_test.go b/control-plane/catalog/to-consul/syncer_test.go index 3fae7a3d16..ab2cfee0a2 100644 --- a/control-plane/catalog/to-consul/syncer_test.go +++ b/control-plane/catalog/to-consul/syncer_test.go @@ -13,12 +13,13 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const ( @@ -233,7 +234,7 @@ func TestConsulSyncer_stopsGracefully(t *testing.T) { testClient := &test.TestServerClient{ Cfg: &consul.Config{APIClientConfig: &api.Config{}, HTTPPort: port}, - Watcher: test.MockConnMgrForIPAndPort(parsedURL.Host, port), + Watcher: test.MockConnMgrForIPAndPort(t, parsedURL.Host, port, false), } // Start the syncer. diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index a99d9fd12e..acee282739 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -8,8 +8,11 @@ import ( "strconv" "strings" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + mapset "github.com/deckarep/golang-set" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) // DetermineAndValidatePort behaves as follows: @@ -90,6 +93,26 @@ func ShouldOverwriteProbes(pod corev1.Pod, globalOverwrite bool) (bool, error) { return globalOverwrite, nil } +// ShouldIgnore ignores namespaces where we don't mesh-inject. +func ShouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { + // Ignores system namespaces. + if namespace == metav1.NamespaceSystem || namespace == metav1.NamespacePublic || namespace == "local-path-storage" { + return true + } + + // Ignores deny list. + if denySet.Contains(namespace) { + return true + } + + // Ignores if not in allow list or allow list is not *. + if !allowSet.Contains("*") && !allowSet.Contains(namespace) { + return true + } + + return false +} + func ConsulNodeNameFromK8sNode(nodeName string) string { return fmt.Sprintf("%s-virtual", nodeName) } diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index 79a9294fe2..6f623b28db 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -6,11 +6,13 @@ package common import ( "testing" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" + mapset "github.com/deckarep/golang-set" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func TestCommonDetermineAndValidatePort(t *testing.T) { @@ -259,3 +261,56 @@ func minimal() *corev1.Pod { }, } } + +func TestShouldIgnore(t *testing.T) { + t.Parallel() + cases := []struct { + name string + namespace string + denySet mapset.Set + allowSet mapset.Set + expected bool + }{ + { + name: "system namespace", + namespace: "kube-system", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "other system namespace", + namespace: "local-path-storage", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "any namespace allowed", + namespace: "foo", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: false, + }, + { + name: "in deny list", + namespace: "foo", + denySet: mapset.NewSetWith("foo"), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "not in allow list", + namespace: "foo", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("bar"), + expected: true, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + actual := ShouldIgnore(tt.namespace, tt.denySet, tt.allowSet) + require.Equal(t, tt.expected, actual) + }) + } +} diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 4efcc24c74..76b83eaf62 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -181,8 +181,12 @@ const ( // to explicitly perform the peering operation again. AnnotationPeeringVersion = "consul.hashicorp.com/peering-version" + // LegacyAnnotationConsulK8sVersion is the current version of this binary. + // TODO: remove this annotation in a future release. + LegacyAnnotationConsulK8sVersion = "consul.hashicorp.com/connect-k8s-version" + // AnnotationConsulK8sVersion is the current version of this binary. - AnnotationConsulK8sVersion = "consul.hashicorp.com/connect-k8s-version" + AnnotationConsulK8sVersion = "consul.hashicorp.com/consul-k8s-version" // LabelServiceIgnore is a label that can be added to a service to prevent it from being // registered with Consul. @@ -202,6 +206,25 @@ const ( ManagedByValue = "consul-k8s-endpoints-controller" ) +// ******************** +// V2 Exclusive Annotations & Labels +// ******************** + +const ( + // AnnotationMeshInject is the key of the annotation that controls whether + // V2 mesh injection is explicitly enabled or disabled for a pod using. + // be set to a truthy or falsy value, as parseable by strconv.ParseBool. + AnnotationMeshInject = "consul.hashicorp.com/mesh-inject" + + // KeyMeshInjectStatus is the key of the annotation that is added to + // a pod after an injection is done. + KeyMeshInjectStatus = "consul.hashicorp.com/mesh-inject-status" + + // ManagedByPodValue is used in Consul metadata to identify the manager + // of this resource. + ManagedByPodValue = "consul-k8s-pod-controller" +) + // Annotations used by Prometheus. const ( AnnotationPrometheusScrape = "prometheus.io/scrape" diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index ca6fe23606..506654dfe1 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -7,6 +7,12 @@ const ( // ConsulCAFile is the location of the Consul CA file inside the injected pod. ConsulCAFile = "/consul/connect-inject/consul-ca.pem" + // DefaultConsulNS is the default Consul namespace name. + DefaultConsulNS = "default" + + // DefaultConsulPartition is the default Consul partition name. + DefaultConsulPartition = "default" + // ProxyDefaultInboundPort is the default inbound port for the proxy. ProxyDefaultInboundPort = 20000 diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go index c71ee9ba55..6d78654989 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go @@ -6,12 +6,13 @@ package endpoints import ( "fmt" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-version" corev1 "k8s.io/api/core/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" ) const minSupportedConsulDataplaneVersion = "v1.0.0-beta1" @@ -19,7 +20,12 @@ const minSupportedConsulDataplaneVersion = "v1.0.0-beta1" // isConsulDataplaneSupported returns true if the consul-k8s version on the pod supports // consul-dataplane architecture of Consul. func isConsulDataplaneSupported(pod corev1.Pod) bool { - if anno, ok := pod.Annotations[constants.AnnotationConsulK8sVersion]; ok { + anno, ok := pod.Annotations[constants.LegacyAnnotationConsulK8sVersion] + if !ok { + anno, ok = pod.Annotations[constants.AnnotationConsulK8sVersion] + } + + if ok { consulK8sVersion, err := version.NewVersion(anno) if err != nil { // Only consul-k8s v1.0.0+ (including pre-release versions) have the version annotation. So it would be diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go index 36ad222d68..5aa7448ef3 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go @@ -7,14 +7,15 @@ import ( "testing" logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) func TestIsConsulDataplaneSupported(t *testing.T) { @@ -46,7 +47,7 @@ func TestIsConsulDataplaneSupported(t *testing.T) { }, } if version != "" { - pod.ObjectMeta.Annotations[constants.AnnotationConsulK8sVersion] = version + pod.ObjectMeta.Annotations[constants.LegacyAnnotationConsulK8sVersion] = version } require.Equal(t, c.expIsConsulDataplaneSupported, isConsulDataplaneSupported(pod)) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 3f139c2662..a9f10f970b 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -13,22 +13,22 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/parsetags" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-multierror" 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/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/parsetags" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( @@ -142,7 +142,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu var serviceEndpoints corev1.Endpoints // Ignore the request if the namespace of the endpoint is not allowed. - if shouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { return ctrl.Result{}, nil } @@ -1287,26 +1287,6 @@ func (r *Controller) processLabeledUpstream(pod corev1.Pod, rawUpstream string) return upstream, nil } -// shouldIgnore ignores namespaces where we don't connect-inject. -func shouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { - // Ignores system namespaces. - if namespace == metav1.NamespaceSystem || namespace == metav1.NamespacePublic || namespace == "local-path-storage" { - return true - } - - // Ignores deny list. - if denySet.Contains(namespace) { - return true - } - - // Ignores if not in allow list or allow list is not *. - if !allowSet.Contains("*") && !allowSet.Contains(namespace) { - return true - } - - return false -} - // consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace // depending on Consul Namespaces being enabled and the value of namespace mirroring. func (r *Controller) consulNamespace(namespace string) string { diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index e0c7f034b1..49150dec6a 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -14,9 +14,6 @@ import ( logrtest "github.com/go-logr/logr/testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" @@ -26,6 +23,10 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) // TestReconcileCreateEndpoint tests the logic to create service instances in Consul from the addresses in the Endpoints @@ -2121,7 +2122,7 @@ func createPodWithNamespace(name, namespace, ip string, inject bool, managedByEn Namespace: namespace, Labels: map[string]string{}, Annotations: map[string]string{ - constants.AnnotationConsulK8sVersion: "1.0.0", + constants.LegacyAnnotationConsulK8sVersion: "1.0.0", }, }, Status: corev1.PodStatus{ diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 2cec69dd0a..380db5a0b6 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -13,9 +13,6 @@ import ( logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" @@ -27,6 +24,10 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const ( @@ -34,59 +35,6 @@ const ( consulNodeName = "test-node-virtual" ) -func TestShouldIgnore(t *testing.T) { - t.Parallel() - cases := []struct { - name string - namespace string - denySet mapset.Set - allowSet mapset.Set - expected bool - }{ - { - name: "system namespace", - namespace: "kube-system", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "other system namespace", - namespace: "local-path-storage", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "any namespace allowed", - namespace: "foo", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: false, - }, - { - name: "in deny list", - namespace: "foo", - denySet: mapset.NewSetWith("foo"), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "not in allow list", - namespace: "foo", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("bar"), - expected: true, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := shouldIgnore(tt.namespace, tt.denySet, tt.allowSet) - require.Equal(t, tt.expected, actual) - }) - } -} - func TestHasBeenInjected(t *testing.T) { t.Parallel() cases := []struct { @@ -3715,7 +3663,7 @@ func TestReconcileUpdateEndpoint_LegacyService(t *testing.T) { k8sObjects: func() []runtime.Object { pod1 := createServicePod("pod1", "1.2.3.4", true, true) pod1.Status.HostIP = "127.0.0.1" - pod1.Annotations[constants.AnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. + pod1.Annotations[constants.LegacyAnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-updated", @@ -3780,7 +3728,7 @@ func TestReconcileUpdateEndpoint_LegacyService(t *testing.T) { k8sObjects: func() []runtime.Object { pod1 := createServicePod("pod1", "1.2.3.4", true, true) pod1.Status.HostIP = "127.0.0.1" - pod1.Annotations[constants.AnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. + pod1.Annotations[constants.LegacyAnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-updated", @@ -6624,7 +6572,7 @@ func createServicePod(name, ip string, inject bool, managedByEndpointsController Namespace: "default", Labels: map[string]string{}, Annotations: map[string]string{ - constants.AnnotationConsulK8sVersion: "1.0.0", + constants.LegacyAnnotationConsulK8sVersion: "1.0.0", }, }, Status: corev1.PodStatus{ diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go new file mode 100644 index 0000000000..f4e3f56053 --- /dev/null +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -0,0 +1,337 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package pod + +import ( + "context" + "fmt" + "strconv" + + mapset "github.com/deckarep/golang-set" + "github.com/go-logr/logr" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/go-multierror" + "google.golang.org/protobuf/types/known/anypb" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" +) + +const ( + metaKeyManagedBy = "managed-by" + tokenMetaPodNameKey = "pod" +) + +type Controller struct { + client.Client + // ConsulClientConfig is the config for the Consul API client. + ConsulClientConfig *consul.Config + // ConsulServerConnMgr is the watcher for the Consul server addresses. + ConsulServerConnMgr consul.ServerConnectionManager + // Only pods in the AllowK8sNamespacesSet are reconciled. + AllowK8sNamespacesSet mapset.Set + // Pods in the DenyK8sNamespacesSet are ignored. + DenyK8sNamespacesSet mapset.Set + // EnableConsulPartitions indicates that a user is running Consul Enterprise + EnableConsulPartitions bool + // ConsulPartition is the Consul Partition to which this controller belongs + ConsulPartition string + // EnableConsulNamespaces indicates that a user is running Consul Enterprise + EnableConsulNamespaces bool + // ConsulDestinationNamespace is the name of the Consul namespace to create + // all config entries in. If EnableNSMirroring is true this is ignored. + ConsulDestinationNamespace string + // EnableNSMirroring causes Consul namespaces to be created to match the + // k8s namespace of any config entry custom resource. Config entries will + // be created in the matching Consul namespace. + EnableNSMirroring bool + // NSMirroringPrefix is an optional prefix that can be added to the Consul + // namespaces created while mirroring. For example, if it is set to "k8s-", + // then the k8s `default` namespace will be mirrored in Consul's + // `k8s-default` namespace. + NSMirroringPrefix string + + // TODO: EnableWANFederation + + // AuthMethod is the name of the Kubernetes Auth Method that + // was used to login with Consul. The Endpoints controller + // will delete any tokens associated with this auth method + // whenever service instances are deregistered. + AuthMethod string + + // EnableTelemetryCollector controls whether the proxy service should be registered + // with config to enable telemetry forwarding. + EnableTelemetryCollector bool + + MetricsConfig metrics.Config + + Log logr.Logger + + // ResourceClient is a gRPC client for the resource service. It is public for testing purposes + ResourceClient pbresource.ResourceServiceClient +} + +// TODO(dans): logs, logs, logs + +// Reconcile reads the state of an Endpoints object for a Kubernetes Service and reconciles Consul services which +// correspond to the Kubernetes Service. These events are driven by changes to the Pods backing the Kube service. +func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var errs error + var pod corev1.Pod + + // Ignore the request if the namespace of the endpoint is not allowed. + // Strictly speaking, this is not required because the mesh webhook also knows valid namespaces + // for injection, but it will somewhat reduce the amount of unnecessary deletions for non-injected + // pods + if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + return ctrl.Result{}, nil + } + + rc, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) + if err != nil { + r.Log.Error(err, "failed to create resource client", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + r.ResourceClient = rc + + err = r.Client.Get(ctx, req.NamespacedName, &pod) + + // If the pod object has been deleted (and we get an IsNotFound error), + // we need to remove the Workload from Consul. + if k8serrors.IsNotFound(err) { + + if err := r.deleteWorkload(ctx, req.NamespacedName); err != nil { + errs = multierror.Append(errs, err) + } + + // TODO: delete explicit upstreams + //if err := r.deleteUpstreams(ctx, pod); err != nil { + // errs = multierror.Append(errs, err) + //} + + // TODO(dans): delete proxyConfiguration + //if err := r.deleteProxyConfiguration(ctx, pod); err != nil { + // errs = multierror.Append(errs, err) + //} + + // TODO: clean up ACL Tokens + + // TODO(dans): delete health status, since we don't have finalizers + //if err := r.deleteHealthStatus(ctx, pod); err != nil { + // errs = multierror.Append(errs, err) + //} + + return ctrl.Result{}, errs + } else if err != nil { + r.Log.Error(err, "failed to get Pod", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + + r.Log.Info("retrieved", "name", pod.Name, "ns", pod.Namespace) + + if hasBeenInjected(pod) { + if err := r.writeWorkload(ctx, pod); err != nil { + errs = multierror.Append(errs, err) + } + + // TODO(dans): create proxyConfiguration + + // TODO: create explicit upstreams + //if err := r.writeUpstreams(ctx, pod); err != nil { + // errs = multierror.Append(errs, err) + //} + + // TODO(dans): write health status + //if err := r.writeHealthStatus(ctx, pod); err != nil { + // errs = multierror.Append(errs, err) + //} + } + + return ctrl.Result{}, errs +} + +func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Pod{}). + Complete(r) +} + +// hasBeenInjected checks the value of the status annotation and returns true if the Pod has been injected. +func hasBeenInjected(pod corev1.Pod) bool { + if anno, ok := pod.Annotations[constants.KeyMeshInjectStatus]; ok && anno == constants.Injected { + return true + } + return false +} + +func (r *Controller) deleteWorkload(ctx context.Context, pod types.NamespacedName) error { + req := &pbresource.DeleteRequest{ + Id: getWorkloadID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), + } + + _, err := r.ResourceClient.Delete(ctx, req) + return err +} + +//func (r *Controller) deleteHealthStatus(ctx context.Context, pod corev1.Pod) error { +// return nil +//} + +func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { + + // TODO: we should add some validation on the required fields here + // e.g. what if token automount is disabled and there is not SA. The API call + // will fail with no indication to the user other than controller logs + ports, workloadPorts := getWorkloadPorts(pod) + + var node corev1.Node + // Ignore errors because we don't want failures to block running services. + _ = r.Client.Get(context.Background(), types.NamespacedName{Name: pod.Spec.NodeName, Namespace: pod.Namespace}, &node) + locality := parseLocality(node) + + workload := &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: pod.Status.PodIP, Ports: ports}, + }, + Identity: pod.Spec.ServiceAccountName, + Locality: locality, + NodeName: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), + Ports: workloadPorts, + } + + // TODO(dans): replace with common.ToProtoAny when available + proto, err := anypb.New(workload) + if err != nil { + return fmt.Errorf("could not serialize workload: %w", err) + } + + // TODO: allow custom workload metadata + meta := map[string]string{ + constants.MetaKeyKubeNS: pod.Namespace, + metaKeyManagedBy: constants.ManagedByPodValue, + } + + req := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), + Metadata: meta, + Data: proto, + }, + } + _, err = r.ResourceClient.Write(ctx, req) + return err +} + +//func (r *Controller) writeHealthStatus(pod corev1.Pod) error { +// return nil +//} + +// TODO(dans): delete ACL token for workload +// deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. +// It will only check for ACL tokens that have been created with the auth method this controller +// has been configured with and will only delete tokens for the provided podName. +// func (r *Controller) deleteACLTokensForWorkload(apiClient *api.Client, svc *api.AgentService, k8sNS, podName string) error { + +// TODO: add support for explicit upstreams +//func (r *Controller) writeUpstreams(pod corev1.Pod) error + +// consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace +// depending on Consul Namespaces being enabled and the value of namespace mirroring. +func (r *Controller) getConsulNamespace(kubeNamespace string) string { + ns := namespaces.ConsulNamespace( + kubeNamespace, + r.EnableConsulNamespaces, + r.ConsulDestinationNamespace, + r.EnableNSMirroring, + r.NSMirroringPrefix, + ) + + // TODO: remove this if and when the default namespace of resources change. + if ns == "" { + ns = constants.DefaultConsulNS + } + return ns +} + +func (r *Controller) getPartition() string { + if !r.EnableConsulPartitions || r.ConsulPartition == "" { + return constants.DefaultConsulPartition + } + return r.ConsulPartition +} + +func getWorkloadPorts(pod corev1.Pod) ([]string, map[string]*pbcatalog.WorkloadPort) { + ports := make([]string, 0) + workloadPorts := map[string]*pbcatalog.WorkloadPort{} + + for _, container := range pod.Spec.Containers { + for _, port := range container.Ports { + name := port.Name + if name == "" { + name = strconv.Itoa(int(port.ContainerPort)) + } + + // TODO: error check reserved "mesh" keyword and 20000 + + if port.Protocol != corev1.ProtocolTCP { + // TODO: also throw an error here + continue + } + + ports = append(ports, name) + workloadPorts[name] = &pbcatalog.WorkloadPort{ + Port: uint32(port.ContainerPort), + + // We leave the protocol unspecified so that it can be inherited from the Service appProtocol + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + } + } + } + + ports = append(ports, "mesh") + workloadPorts["mesh"] = &pbcatalog.WorkloadPort{ + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + } + + return ports, workloadPorts +} + +func parseLocality(node corev1.Node) *pbcatalog.Locality { + region := node.Labels[corev1.LabelTopologyRegion] + zone := node.Labels[corev1.LabelTopologyZone] + + if region == "" { + return nil + } + + return &pbcatalog.Locality{ + Region: region, + Zone: zone, + } +} + +func getWorkloadID(name, namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Workload", + }, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + }, + } +} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go new file mode 100644 index 0000000000..802cb9d910 --- /dev/null +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package pod + +import "testing" + +// TODO(dans) +// Tests creating a Pod object in a non-default NS and Partition with namespaces set to mirroring +func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { + +} + +// TODO(dans) +// Tests updating a Pod object in a non-default NS and Partition with namespaces set to mirroring +func TestReconcileUpdatePodWithMirrorNamespaces(t *testing.T) { + +} + +// TODO(dans) +// Tests deleting a Pod object in a non-default NS and Partition with namespaces set to mirroring +func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { + +} + +// TODO(dans) +// Tests creating a Pod object in a non-default NS and Partition with namespaces set to a destination +func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { + +} + +// TODO(dans) +// Tests updating a Pod object in a non-default NS and Partition with namespaces set to a destination +func TestReconcileUpdatePodWithDestinationNamespace(t *testing.T) { + +} + +// TODO(dans) +// Tests deleting a Pod object in a non-default NS and Partition with namespaces set to a destination +func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { + +} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go new file mode 100644 index 0000000000..52a054581b --- /dev/null +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -0,0 +1,1035 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package pod + +import ( + "context" + "testing" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +const ( + nodeName = "test-node" + localityNodeName = "test-node-w-locality" + consulNodeName = "test-node-virtual" + consulLocalityNodeName = "test-node-w-locality-virtual" + consulNodeAddress = "127.0.0.1" +) + +func TestHasBeenInjected(t *testing.T) { + t.Parallel() + cases := []struct { + name string + pod func() corev1.Pod + expected bool + }{ + { + name: "Pod with injected annotation", + pod: func() corev1.Pod { + pod1 := createPod("pod1", "1.2.3.4", "foo", true, true) + return *pod1 + }, + expected: true, + }, + { + name: "Pod without injected annotation", + pod: func() corev1.Pod { + pod1 := createPod("pod1", "1.2.3.4", "foo", false, true) + return *pod1 + }, + expected: false, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + + actual := hasBeenInjected(tt.pod()) + require.Equal(t, tt.expected, actual) + }) + } +} + +func TestParseLocality(t *testing.T) { + t.Run("no labels", func(t *testing.T) { + n := corev1.Node{} + require.Nil(t, parseLocality(n)) + }) + + t.Run("zone only", func(t *testing.T) { + n := corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + corev1.LabelTopologyZone: "us-west-1a", + }, + }, + } + require.Nil(t, parseLocality(n)) + }) + + t.Run("everything", func(t *testing.T) { + n := corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + corev1.LabelTopologyRegion: "us-west-1", + corev1.LabelTopologyZone: "us-west-1a", + }, + }, + } + require.True(t, proto.Equal(&pbcatalog.Locality{Region: "us-west-1", Zone: "us-west-1a"}, parseLocality(n))) + }) +} + +func TestWorkloadWrite(t *testing.T) { + t.Parallel() + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: metav1.NamespaceDefault, + Namespace: metav1.NamespaceDefault, + }} + node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} + localityNode := corev1.Node{ObjectMeta: metav1.ObjectMeta{ + Name: localityNodeName, + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + corev1.LabelTopologyRegion: "us-east1", + corev1.LabelTopologyZone: "us-east1-b", + }, + }} + + type testCase struct { + name string + pod *corev1.Pod + podModifier func(pod *corev1.Pod) + expectedWorkload *pbcatalog.Workload + } + + run := func(t *testing.T, tc testCase) { + if tc.podModifier != nil { + tc.podModifier(tc.pod) + } + + k8sObjects := []runtime.Object{ + &ns, + &node, + &localityNode, + } + + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + ResourceClient: resourceClient, + } + + err = pc.writeWorkload(context.Background(), *tc.pod) + require.NoError(t, err) + + req := &pbresource.ReadRequest{Id: &pbresource.ID{ + Name: tc.pod.GetName(), + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Workload", + }, + Tenancy: &pbresource.Tenancy{ + Partition: constants.DefaultConsulPartition, + Namespace: metav1.NamespaceDefault, + }, + }} + actualRes, err := resourceClient.Read(context.Background(), req) + require.NoError(t, err) + require.NotNil(t, actualRes) + + require.Equal(t, tc.pod.GetName(), actualRes.GetResource().GetId().GetName()) + require.Equal(t, constants.DefaultConsulNS, actualRes.GetResource().GetId().GetTenancy().GetNamespace()) + require.Equal(t, constants.DefaultConsulPartition, actualRes.GetResource().GetId().GetTenancy().GetPartition()) + + require.NotNil(t, actualRes.GetResource().GetData()) + + actualWorkload := &pbcatalog.Workload{} + err = actualRes.GetResource().GetData().UnmarshalTo(actualWorkload) + require.NoError(t, err) + + require.True(t, proto.Equal(actualWorkload, tc.expectedWorkload)) + } + + testCases := []testCase{ + { + name: "multi-port single-container", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + expectedWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + }, + { + name: "multi-port multi-container", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + podModifier: func(pod *corev1.Pod) { + container := corev1.Container{ + Name: "logger", + Ports: []corev1.ContainerPort{ + { + Name: "agent", + Protocol: corev1.ProtocolTCP, + ContainerPort: 6666, + }, + }, + } + pod.Spec.Containers = append(pod.Spec.Containers, container) + }, + expectedWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "agent", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "agent": { + Port: 6666, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + }, + { + name: "pod with locality", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + podModifier: func(pod *corev1.Pod) { + pod.Spec.NodeName = localityNodeName + }, + expectedWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Locality: &pbcatalog.Locality{ + Region: "us-east1", + Zone: "us-east1-b", + }, + NodeName: consulLocalityNodeName, + Identity: "foo", + }, + }, + { + name: "pod with unnamed ports", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + podModifier: func(pod *corev1.Pod) { + pod.Spec.Containers[0].Ports[0].Name = "" + pod.Spec.Containers[0].Ports[1].Name = "" + }, + expectedWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"80", "8080", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "80": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "8080": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + }, + { + name: "pod with no ports", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + podModifier: func(pod *corev1.Pod) { + pod.Spec.Containers[0].Ports = nil + }, + expectedWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +func TestWorkloadDelete(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + pod *corev1.Pod + existingWorkload *pbcatalog.Workload + } + + run := func(t *testing.T, tc testCase) { + fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + ResourceClient: resourceClient, + } + + workload, err := anypb.New(tc.existingWorkload) + require.NoError(t, err) + + workloadID := getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) + writeReq := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: workloadID, + Data: workload, + }, + } + + _, err = resourceClient.Write(context.Background(), writeReq) + require.NoError(t, err) + test.ResourceHasPersisted(t, resourceClient, workloadID) + + reconcileReq := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: tc.pod.GetName(), + } + err = pc.deleteWorkload(context.Background(), reconcileReq) + require.NoError(t, err) + + readReq := &pbresource.ReadRequest{Id: &pbresource.ID{ + Name: tc.pod.GetName(), + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Workload", + }, + Tenancy: &pbresource.Tenancy{ + Partition: constants.DefaultConsulPartition, + Namespace: metav1.NamespaceDefault, + }, + }} + _, err = resourceClient.Read(context.Background(), readReq) + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + } + + testCases := []testCase{ + { + name: "basic pod delete", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + existingWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +// TODO +// func TestHealthStatusWrite(t *testing.T) + +// TODO +// func TestHealthStatusDelete(t *testing.T) + +// TODO +// func TestUpstreamsWrite(t *testing.T) + +// TODO +// func TestUpstreamsDelete(t *testing.T) + +// TODO +// func TestDeleteACLTokens(t *testing.T) + +// TestReconcileCreatePod ensures that a new pod reconciliation fans out to create +// the appropriate Consul resources. Translation details from pod to Consul workload are +// tested at the relevant private functions. Any error states that are also tested here. +func TestReconcileCreatePod(t *testing.T) { + t.Parallel() + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: metav1.NamespaceDefault, + Namespace: metav1.NamespaceDefault, + }} + node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} + + type testCase struct { + name string + podName string // This needs to be aligned with the pod created in `k8sObjects` + namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod + + k8sObjects func() []runtime.Object // testing node is injected separately + expectedWorkload *pbcatalog.Workload + //expectedHealthStatus *pbcatalog.HealthStatus + //expectedProxyConfiguration *pbmesh.ProxyConfiguration + //expectedUpstreams *pbmesh.Upstreams + + metricsEnabled bool + telemetryEnabled bool + + expErr string + } + + run := func(t *testing.T, tc testCase) { + k8sObjects := []runtime.Object{ + &ns, + &node, + } + if tc.k8sObjects != nil { + k8sObjects = append(k8sObjects, tc.k8sObjects()...) + } + + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + } + if tc.metricsEnabled { + pc.MetricsConfig = metrics.Config{ + DefaultEnableMetrics: true, + EnableGatewayMetrics: true, + } + } + pc.EnableTelemetryCollector = tc.telemetryEnabled + + namespace := tc.namespace + if namespace == "" { + namespace = metav1.NamespaceDefault + } + + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: tc.podName, + } + + resp, err := pc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: namespacedName, + }) + if tc.expErr != "" { + require.EqualError(t, err, tc.expErr) + } else { + require.NoError(t, err) + } + require.False(t, resp.Requeue) + + expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) + // TODO(dans): compare the following to expected values + // expectedHealthStatus + // expectedProxyConfiguration + // expectedUpstreams + } + + testCases := []testCase{ + { + name: "vanilla new pod", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, true) + return []runtime.Object{pod} + }, + expectedWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + }, + { + name: "pod in ignored namespace", + podName: "foo", + namespace: metav1.NamespaceSystem, + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod.ObjectMeta.Namespace = metav1.NamespaceSystem + return []runtime.Object{pod} + }, + }, + // TODO(dans): NotHealthyPod + // TODO(dans): tproxy + Metrics + Telemetry + // TODO: explicit upstreams + // TODO: at least one error cases + // TODO: make sure multi-error accumulates errors + // TODO: injection annotation added + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +// TestReconcileUpdatePod test updating a Pod object when there is already matching resources in Consul. +func TestReconcileUpdatePod(t *testing.T) { + t.Parallel() + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: metav1.NamespaceDefault, + Namespace: metav1.NamespaceDefault, + }} + node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} + + type testCase struct { + name string + podName string // This needs to be aligned with the pod created in `k8sObjects` + namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod + + k8sObjects func() []runtime.Object // testing node is injected separately + + existingWorkload *pbcatalog.Workload + //existingHealthStatus *pbcatalog.HealthStatus + //existingProxyConfiguration *pbmesh.ProxyConfiguration + //existingUpstreams *pbmesh.Upstreams + + expectedWorkload *pbcatalog.Workload + //expectedHealthStatus *pbcatalog.HealthStatus + //expectedProxyConfiguration *pbmesh.ProxyConfiguration + //expectedUpstreams *pbmesh.Upstreams + + metricsEnabled bool + telemetryEnabled bool + + expErr string + } + + run := func(t *testing.T, tc testCase) { + k8sObjects := []runtime.Object{ + &ns, + &node, + } + if tc.k8sObjects != nil { + k8sObjects = append(k8sObjects, tc.k8sObjects()...) + } + + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + } + if tc.metricsEnabled { + pc.MetricsConfig = metrics.Config{ + DefaultEnableMetrics: true, + EnableGatewayMetrics: true, + } + } + pc.EnableTelemetryCollector = tc.telemetryEnabled + + namespace := tc.namespace + if namespace == "" { + namespace = metav1.NamespaceDefault + } + + loadResource( + t, + resourceClient, + getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingWorkload, + ) + + // TODO(dans): load the existing resources + // loadHealthStatus + // loadProxyConfiguration + // loadUpstreams + + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: tc.podName, + } + + resp, err := pc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: namespacedName, + }) + if tc.expErr != "" { + require.EqualError(t, err, tc.expErr) + } else { + require.NoError(t, err) + } + require.False(t, resp.Requeue) + + expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) + // TODO(dans): compare the following to expected values + // expectedHealthStatus + // expectedProxyConfiguration + // expectedUpstreams + } + + testCases := []testCase{ + { + name: "pod update ports", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, true) + return []runtime.Object{pod} + }, + existingWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + expectedWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + }, + // TODO(dans): Pod Health to Unhealthy + // TODO(dans): update tproxy + Metrics + Telemetry + // TODO: update explicit upstreams + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +// Tests deleting a Pod object, with and without matching Consul resources. +func TestReconcileDeletePod(t *testing.T) { + t.Parallel() + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: metav1.NamespaceDefault, + Namespace: metav1.NamespaceDefault, + }} + node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} + + type testCase struct { + name string + podName string // This needs to be aligned with the pod created in `k8sObjects` + namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod + + k8sObjects func() []runtime.Object // testing node is injected separately + + existingWorkload *pbcatalog.Workload + //existingHealthStatus *pbcatalog.HealthStatus + //existingProxyConfiguration *pbmesh.ProxyConfiguration + //existingUpstreams *pbmesh.Upstreams + + expectedWorkload *pbcatalog.Workload + //expectedHealthStatus *pbcatalog.HealthStatus + //expectedProxyConfiguration *pbmesh.ProxyConfiguration + //expectedUpstreams *pbmesh.Upstreams + + aclsEnabled bool + metricsEnabled bool + telemetryEnabled bool + + expErr string + } + + run := func(t *testing.T, tc testCase) { + k8sObjects := []runtime.Object{ + &ns, + &node, + } + if tc.k8sObjects != nil { + k8sObjects = append(k8sObjects, tc.k8sObjects()...) + } + + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + } + if tc.metricsEnabled { + pc.MetricsConfig = metrics.Config{ + DefaultEnableMetrics: true, + EnableGatewayMetrics: true, + } + } + if tc.aclsEnabled { + pc.AuthMethod = test.AuthMethod + } + pc.EnableTelemetryCollector = tc.telemetryEnabled + + namespace := tc.namespace + if namespace == "" { + namespace = metav1.NamespaceDefault + } + + loadResource( + t, + resourceClient, + getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingWorkload, + ) + + // TODO(dans): load the existing resources + // loadHealthStatus + // loadProxyConfiguration + // loadUpstreams + + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: tc.podName, + } + + resp, err := pc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: namespacedName, + }) + if tc.expErr != "" { + require.EqualError(t, err, tc.expErr) + } else { + require.NoError(t, err) + } + require.False(t, resp.Requeue) + + expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) + // TODO(dans): compare the following to expected values + // expectedHealthStatus + // expectedProxyConfiguration + // expectedUpstreams + } + + testCases := []testCase{ + { + name: "vanilla delete pod", + podName: "foo", + existingWorkload: &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + }, + }, + // TODO: enable ACLs and make sure they are deleted + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +func createPod(name, ip string, identity string, inject bool, ready bool) *corev1.Pod { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{}, + Annotations: map[string]string{ + constants.AnnotationConsulK8sVersion: "1.3.0", + }, + }, + Status: corev1.PodStatus{ + PodIP: ip, + HostIP: consulNodeAddress, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + Ports: []corev1.ContainerPort{ + { + Name: "public", + Protocol: corev1.ProtocolTCP, + ContainerPort: 80, + }, + { + Name: "admin", + Protocol: corev1.ProtocolTCP, + ContainerPort: 8080, + }, + }, + }, + }, + NodeName: nodeName, + ServiceAccountName: identity, + }, + } + if ready { + pod.Status.Conditions = []corev1.PodCondition{ + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + }, + } + } else { + pod.Status.Conditions = []corev1.PodCondition{ + { + Type: corev1.PodReady, + Status: corev1.ConditionFalse, + }, + } + } + + if inject { + pod.Labels[constants.KeyMeshInjectStatus] = constants.Injected + pod.Annotations[constants.KeyMeshInjectStatus] = constants.Injected + } + return pod +} + +func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedWorkload *pbcatalog.Workload) { + req := &pbresource.ReadRequest{Id: getWorkloadID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} + + res, err := client.Read(context.Background(), req) + + if expectedWorkload == nil { + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + return + } + + require.NoError(t, err) + require.NotNil(t, res) + + require.Equal(t, name, res.GetResource().GetId().GetName()) + require.Equal(t, constants.DefaultConsulNS, res.GetResource().GetId().GetTenancy().GetNamespace()) + require.Equal(t, constants.DefaultConsulPartition, res.GetResource().GetId().GetTenancy().GetPartition()) + + require.NotNil(t, res.GetResource().GetData()) + + actualWorkload := &pbcatalog.Workload{} + err = res.GetResource().GetData().UnmarshalTo(actualWorkload) + require.NoError(t, err) + + require.True(t, proto.Equal(actualWorkload, expectedWorkload)) +} + +func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message) { + if id == nil || proto == nil { + return + } + + data, err := anypb.New(proto) + require.NoError(t, err) + + resource := &pbresource.Resource{ + Id: id, + Data: data, + } + + req := &pbresource.WriteRequest{Resource: resource} + _, err = client.Write(context.Background(), req) + require.NoError(t, err) + test.ResourceHasPersisted(t, client, id) +} diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 523200b96c..24c26d4f42 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -620,6 +620,7 @@ func (w *MeshWebhook) defaultAnnotations(pod *corev1.Pod, podJson string) error } } pod.Annotations[constants.AnnotationOriginalPod] = podJson + pod.Annotations[constants.LegacyAnnotationConsulK8sVersion] = version.GetHumanVersion() pod.Annotations[constants.AnnotationConsulK8sVersion] = version.GetHumanVersion() return nil diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index 64dbd21c9a..2b71c08500 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -246,6 +246,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -339,6 +343,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -394,6 +402,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -468,6 +480,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -528,6 +544,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -613,6 +633,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -750,6 +774,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -813,6 +841,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -872,6 +904,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -957,6 +993,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1027,6 +1067,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1099,6 +1143,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1275,6 +1323,10 @@ func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), + }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1357,8 +1409,9 @@ func TestHandlerDefaultAnnotations(t *testing.T) { "empty", &corev1.Pod{}, map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", + constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1378,8 +1431,9 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1405,9 +1459,10 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - "consul.hashicorp.com/connect-service": "foo", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null,\"annotations\":{\"consul.hashicorp.com/connect-service\":\"foo\"}},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + "consul.hashicorp.com/connect-service": "foo", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null,\"annotations\":{\"consul.hashicorp.com/connect-service\":\"foo\"}},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", @@ -1434,9 +1489,10 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationPort: "http", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationPort: "http", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1461,9 +1517,10 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationPort: "8080", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationPort: "8080", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, diff --git a/control-plane/consul/resource_client.go b/control-plane/consul/resource_client.go index edb2d2fce9..82c24af34f 100644 --- a/control-plane/consul/resource_client.go +++ b/control-plane/consul/resource_client.go @@ -6,14 +6,13 @@ package consul import ( "fmt" - "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/proto-public/pbresource" ) // NewResourceServiceClient creates a pbresource.ResourceServiceClient for creating V2 Consul resources. // It is initialized with a consul-server-connection-manager Watcher to continuously find Consul // server addresses. -func NewResourceServiceClient(watcher *discovery.Watcher) (pbresource.ResourceServiceClient, error) { +func NewResourceServiceClient(watcher ServerConnectionManager) (pbresource.ResourceServiceClient, error) { // We recycle the GRPC connection from the discovery client because it // should have all the necessary dial options, including the resolver that diff --git a/control-plane/go.mod b/control-plane/go.mod index 55918dceb4..3589eac8b7 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -13,7 +13,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 - github.com/hashicorp/consul/proto-public v0.1.2-0.20230821180813-217d305b38d5 // this points to a commit on Consul main + github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef // this points to a commit on Consul main github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 @@ -48,6 +48,7 @@ require ( require ( github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.4 + google.golang.org/grpc v1.55.0 google.golang.org/protobuf v1.30.0 ) @@ -164,7 +165,6 @@ require ( google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.55.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 7868dc727b..1a16c25165 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNb github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 h1:TTTgXv9YeaRnODyFP1k2b2Nq5RIGrUUgI5SkDhuSNwM= github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230821180813-217d305b38d5 h1:mN5hKOn+G5fQBuXjdne/HluE4FhesUxscZEblqP4OSQ= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230821180813-217d305b38d5/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef h1:Vt5NSnXc+RslTxXH2pz7dCb3hnE33CD2TrBP5AIQtMg= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924 h1:gkb6/ix0Tg1Th5FTjyq4QklLgrtIVQ/TUB0kbhIcPsY= github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index e29e44de59..915c65704e 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -4,6 +4,7 @@ package test import ( + "context" "fmt" "net" "net/http" @@ -13,12 +14,16 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" ) const ( @@ -62,20 +67,33 @@ func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConf TestServer: consulServer, APIClient: client, Cfg: consulConfig, - Watcher: MockConnMgrForIPAndPort("127.0.0.1", cfg.Ports.GRPC), + Watcher: MockConnMgrForIPAndPort(t, "127.0.0.1", cfg.Ports.GRPC, true), } } -func MockConnMgrForIPAndPort(ip string, port int) *consul.MockServerConnectionManager { +func MockConnMgrForIPAndPort(t *testing.T, ip string, port int, enableGRPCConn bool) *consul.MockServerConnectionManager { parsedIP := net.ParseIP(ip) connMgr := &consul.MockServerConnectionManager{} + mockState := discovery.State{ Address: discovery.Addr{ TCPAddr: net.TCPAddr{ IP: parsedIP, Port: port, }, - }} + }, + } + + // If the connection is enabled, some tests will receive extra HTTP API calls where + // the server is being dialed. + if enableGRPCConn { + conn, err := grpc.DialContext( + context.Background(), + fmt.Sprintf("%s:%d", parsedIP, port), + grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + mockState.GRPCConn = conn + } connMgr.On("State").Return(mockState, nil) connMgr.On("Run").Return(nil) connMgr.On("Stop").Return(nil) @@ -257,6 +275,27 @@ func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, se require.NoError(t, err) } +// ResourceHasPersisted checks that a recently written resource exists in the Consul +// state store with a valid version. This must be true before a resource is overwritten +// or deleted. +func ResourceHasPersisted(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID) { + req := &pbresource.ReadRequest{Id: id} + + require.Eventually(t, func() bool { + res, err := client.Read(context.Background(), req) + if err != nil { + return false + } + + if res.GetResource().GetVersion() == "" { + return false + } + + return true + }, 5*time.Second, + time.Second) +} + func TokenReviewsResponse(name, ns string) string { return fmt.Sprintf(`{ "kind": "TokenReview", diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index d851f71cb8..a9f29cd6a5 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -7,22 +7,21 @@ import ( "context" "github.com/hashicorp/consul-server-connection-manager/discovery" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { - //resourceClient, err := consul.NewResourceServiceClient(watcher) - //if err != nil { - // return fmt.Errorf("unable to create Consul resource service client: %w", err) - //} + // Create Consul API config object. + consulConfig := c.consul.ConsulClientConfig() - //// Create Consul API config object. - //consulConfig := c.consul.ConsulClientConfig() - // - ////Convert allow/deny lists to sets. - //allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) - //denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) + //Convert allow/deny lists to sets. + allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) + denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) //lifecycleConfig := lifecycle.Config{ // DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, @@ -41,32 +40,24 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage // DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, //} - // TODO(dans): Pods Controller - //if err := (&pod.Controller{ - // Client: mgr.GetClient(), - // ConsulClientConfig: consulConfig, - // ConsulServerConnMgr: watcher, - // ConsulResourceServiceClient: client, - // AllowK8sNamespacesSet: allowK8sNamespaces, - // DenyK8sNamespacesSet: denyK8sNamespaces, - // MetricsConfig: metricsConfig, - // EnableConsulPartitions: c.flagEnablePartitions, - // EnableConsulNamespaces: c.flagEnableNamespaces, - // ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - // EnableNSMirroring: c.flagEnableK8SNSMirroring, - // NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - // EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - // TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - // AuthMethod: c.flagACLAuthMethod, - // NodeMeta: c.flagNodeMeta, - // Log: ctrl.Log.WithName("controller").WithName("pods"), - // Scheme: mgr.GetScheme(), - // EnableTelemetryCollector: c.flagEnableTelemetryCollector, - // Context: ctx, - //}).SetupWithManager(mgr); err != nil { - // setupLog.Error(err, "unable to create controller", "controller", pod.Controller{}) - // return err - //} + if err := (&pod.Controller{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + EnableConsulPartitions: c.flagEnablePartitions, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + ConsulPartition: c.consul.Partition, + AuthMethod: c.flagACLAuthMethod, + Log: ctrl.Log.WithName("controller").WithName("pods"), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", pod.Controller{}) + return err + } // TODO: V2 Endpoints Controller diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index 68ce4c1e02..c7d44c0695 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -18,9 +18,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -32,6 +29,10 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) var ns = "default" @@ -1119,7 +1120,7 @@ func TestRun_NoLeader(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), } done := make(chan bool) @@ -1375,7 +1376,7 @@ func TestRun_ClientPolicyAndBindingRuleRetry(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), } responseCode := cmd.Run([]string{ "-timeout=1m", @@ -1524,7 +1525,7 @@ func TestRun_AlreadyBootstrapped(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), } responseCode := cmd.Run(cmdArgs) @@ -1709,7 +1710,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } responseCode := cmd.Run([]string{ @@ -1753,7 +1754,7 @@ func TestRun_Timeout(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort("localhost", 12345), + watcher: test.MockConnMgrForIPAndPort(t, "localhost", 12345, false), } responseCode := cmd.Run([]string{ From c6b703d1deffe15b3efac0a7b63d6b4e388acd95 Mon Sep 17 00:00:00 2001 From: "Chris S. Kim" Date: Tue, 5 Sep 2023 16:46:16 -0400 Subject: [PATCH 363/592] Add RateLimit config to serviceDefaults (#2844) --- .changelog/2844.txt | 3 + acceptance/go.mod | 4 +- acceptance/go.sum | 8 +- .../config-entries/config_entries_test.go | 12 +- .../bases/crds-oss/servicedefaults.yaml | 12 + .../consul/templates/crd-servicedefaults.yaml | 63 ++++++ .../api/v1alpha1/servicedefaults_types.go | 152 ++++++++++++- .../v1alpha1/servicedefaults_types_test.go | 214 ++++++++++++++++++ .../api/v1alpha1/zz_generated.deepcopy.go | 56 +++++ .../consul.hashicorp.com_servicedefaults.yaml | 63 ++++++ control-plane/go.mod | 15 +- control-plane/go.sum | 4 +- 12 files changed, 582 insertions(+), 24 deletions(-) create mode 100644 .changelog/2844.txt diff --git a/.changelog/2844.txt b/.changelog/2844.txt new file mode 100644 index 0000000000..89ba684575 --- /dev/null +++ b/.changelog/2844.txt @@ -0,0 +1,3 @@ +```release-note:feature +helm: (Consul Enterprise) Adds rate limiting config to serviceDefaults CRD +``` diff --git a/acceptance/go.mod b/acceptance/go.mod index 1dd344a5c8..28d1491837 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -5,8 +5,8 @@ go 1.20 require ( github.com/gruntwork-io/terratest v0.31.2 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 - github.com/hashicorp/consul/api v1.22.0-rc1 - github.com/hashicorp/consul/sdk v0.14.0-rc1 + github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 + github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hcp-sdk-go v0.50.0 diff --git a/acceptance/go.sum b/acceptance/go.sum index 7361efbbb6..b264e243c9 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -399,10 +399,10 @@ github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+Xb github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 h1:4iI0ztWbVPTSDax+m1/XDs4jIRorxY4kSMyuM0fX+Dc= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= -github.com/hashicorp/consul/api v1.22.0-rc1 h1:ePmGqndeMgaI38KUbSA/CqTzeEAIogXyWnfNJzglo70= -github.com/hashicorp/consul/api v1.22.0-rc1/go.mod h1:wtduXtbAqSGtBdi3tyA5SSAYGAG51rBejV9SEUBciMY= -github.com/hashicorp/consul/sdk v0.14.0-rc1 h1:PuETOfN0uxl28i0Pq6rK7TBCrIl7psMbL0YTSje4KvM= -github.com/hashicorp/consul/sdk v0.14.0-rc1/go.mod h1:gHYeuDa0+0qRAD6Wwr6yznMBvBwHKoxSBoW5l73+saE= +github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 h1:NPhzdwDho2r8pQv31oeGLlco7fnJ1i0WLYjtSXqWEck= +github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= +github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= 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= diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index a36e9baaf5..81b0a75ff4 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -9,6 +9,11 @@ import ( "testing" "time" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/go-uuid" + "github.com/stretchr/testify/require" + "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" @@ -16,10 +21,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/vault" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/go-uuid" - "github.com/stretchr/testify/require" ) const ( @@ -104,6 +105,7 @@ func TestController(t *testing.T) { svcDefaultEntry, ok := entry.(*api.ServiceConfigEntry) require.True(r, ok, "could not cast to ServiceConfigEntry") require.Equal(r, "http", svcDefaultEntry.Protocol) + require.Equal(r, 1234, svcDefaultEntry.RateLimits.InstanceLevel.RequestsPerSecond) // service-resolver entry, _, err = consulClient.ConfigEntries().Get(api.ServiceResolver, "resolver", nil) @@ -232,8 +234,6 @@ func TestController(t *testing.T) { require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.WriteRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) - //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) - //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.ReadRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.WriteRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index cd9c35fa39..d0d1fe73bb 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -31,6 +31,18 @@ spec: interval: 10s maxFailures: 2 balanceInboundConnections: "exact_balance" + rateLimits: + instanceLevel: + requestsPerSecond: 1234 + requestsMaxBurst: 2345 + routes: + - pathExact: "/exact" + requestsPerSecond: 222 + requestsMaxBurst: 333 + - pathPrefix: "/prefix" + requestsPerSecond: 444 + - pathRegex: "/regex" + requestsPerSecond: 555 envoyExtensions: - name: builtin/aws/lambda required: false diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 9e6c304bec..9fe23d160f 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -192,6 +192,69 @@ spec: unlock usage of the service-splitter and service-router config entries for a service. type: string + rateLimits: + description: RateLimits is rate limiting configuration that is applied + to inbound traffic for a service. Rate limiting is a Consul enterprise + feature. + properties: + instanceLevel: + description: InstanceLevel represents rate limit configuration + that is applied per service instance. + properties: + requestsMaxBurst: + description: "RequestsMaxBurst is the maximum number of requests + that can be sent in a burst. Should be equal to or greater + than RequestsPerSecond. If unset, defaults to RequestsPerSecond. + \n Internally, this is the maximum size of the token bucket + used for rate limiting." + type: integer + requestsPerSecond: + description: "RequestsPerSecond is the average number of requests + per second that can be made without being throttled. This + field is required if RequestsMaxBurst is set. The allowed + number of requests may exceed RequestsPerSecond up to the + value specified in RequestsMaxBurst. \n Internally, this + is the refill rate of the token bucket used for rate limiting." + type: integer + routes: + description: Routes is a list of rate limits applied to specific + routes. For a given request, the first matching route will + be applied, if any. Overrides any top-level configuration. + items: + properties: + pathExact: + description: Exact path to match. Exactly one of PathExact, + PathPrefix, or PathRegex must be specified. + type: string + pathPrefix: + description: Prefix to match. Exactly one of PathExact, + PathPrefix, or PathRegex must be specified. + type: string + pathRegex: + description: Regex to match. Exactly one of PathExact, + PathPrefix, or PathRegex must be specified. + type: string + requestsMaxBurst: + description: RequestsMaxBurst is the maximum number + of requests that can be sent in a burst. Should be + equal to or greater than RequestsPerSecond. If unset, + defaults to RequestsPerSecond. Internally, this is + the maximum size of the token bucket used for rate + limiting. + type: integer + requestsPerSecond: + description: RequestsPerSecond is the average number + of requests per second that can be made without being + throttled. This field is required if RequestsMaxBurst + is set. The allowed number of requests may exceed + RequestsPerSecond up to the value specified in RequestsMaxBurst. + Internally, this is the refill rate of the token bucket + used for rate limiting. + type: integer + type: object + type: array + type: object + type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 2896475f75..fd764b32b2 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -18,8 +18,9 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ( @@ -115,6 +116,9 @@ type ServiceDefaultsSpec struct { // proxy threads. The only supported value is exact_balance. By default, no connection balancing is used. // Refer to the Envoy Connection Balance config for details. BalanceInboundConnections string `json:"balanceInboundConnections,omitempty"` + // RateLimits is rate limiting configuration that is applied to + // inbound traffic for a service. Rate limiting is a Consul enterprise feature. + RateLimits *RateLimits `json:"rateLimits,omitempty"` // EnvoyExtensions are a list of extensions to modify Envoy proxy configuration. EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` } @@ -216,6 +220,150 @@ type ServiceDefaultsDestination struct { Port uint32 `json:"port,omitempty"` } +// RateLimits is rate limiting configuration that is applied to +// inbound traffic for a service. +// Rate limiting is a Consul Enterprise feature. +type RateLimits struct { + // InstanceLevel represents rate limit configuration + // that is applied per service instance. + InstanceLevel InstanceLevelRateLimits `json:"instanceLevel,omitempty"` +} + +func (rl *RateLimits) toConsul() *capi.RateLimits { + if rl == nil { + return nil + } + routes := make([]capi.InstanceLevelRouteRateLimits, len(rl.InstanceLevel.Routes)) + for i, r := range rl.InstanceLevel.Routes { + routes[i] = capi.InstanceLevelRouteRateLimits{ + PathExact: r.PathExact, + PathPrefix: r.PathPrefix, + PathRegex: r.PathRegex, + RequestsPerSecond: r.RequestsPerSecond, + RequestsMaxBurst: r.RequestsMaxBurst, + } + } + return &capi.RateLimits{ + InstanceLevel: capi.InstanceLevelRateLimits{ + RequestsPerSecond: rl.InstanceLevel.RequestsPerSecond, + RequestsMaxBurst: rl.InstanceLevel.RequestsMaxBurst, + Routes: routes, + }, + } +} + +func (rl *RateLimits) validate(path *field.Path) field.ErrorList { + if rl == nil { + return nil + } + + return rl.InstanceLevel.validate(path.Child("instanceLevel")) +} + +type InstanceLevelRateLimits struct { + // RequestsPerSecond is the average number of requests per second that can be + // made without being throttled. This field is required if RequestsMaxBurst + // is set. The allowed number of requests may exceed RequestsPerSecond up to + // the value specified in RequestsMaxBurst. + // + // Internally, this is the refill rate of the token bucket used for rate limiting. + RequestsPerSecond int `json:"requestsPerSecond,omitempty"` + + // RequestsMaxBurst is the maximum number of requests that can be sent + // in a burst. Should be equal to or greater than RequestsPerSecond. + // If unset, defaults to RequestsPerSecond. + // + // Internally, this is the maximum size of the token bucket used for rate limiting. + RequestsMaxBurst int `json:"requestsMaxBurst,omitempty"` + + // Routes is a list of rate limits applied to specific routes. + // For a given request, the first matching route will be applied, if any. + // Overrides any top-level configuration. + Routes []InstanceLevelRouteRateLimits `json:"routes,omitempty"` +} + +func (irl InstanceLevelRateLimits) validate(path *field.Path) field.ErrorList { + var allErrs field.ErrorList + + // Track if RequestsPerSecond is set in at least one place in the config + isRateLimitSet := irl.RequestsPerSecond > 0 + + // Top-level RequestsPerSecond can be 0 (unset) or a positive number. + if irl.RequestsPerSecond < 0 { + allErrs = append(allErrs, + field.Invalid(path.Child("requestsPerSecond"), + irl.RequestsPerSecond, + "RequestsPerSecond must be positive")) + } + + if irl.RequestsPerSecond == 0 && irl.RequestsMaxBurst > 0 { + allErrs = append(allErrs, + field.Invalid(path.Child("requestsPerSecond"), + irl.RequestsPerSecond, + "RequestsPerSecond must be greater than 0 if RequestsMaxBurst is set")) + } + + if irl.RequestsMaxBurst < 0 { + allErrs = append(allErrs, + field.Invalid(path.Child("requestsMaxBurst"), + irl.RequestsMaxBurst, + "RequestsMaxBurst must be positive")) + } + + for i, route := range irl.Routes { + if exact, prefix, regex := route.PathExact != "", route.PathPrefix != "", route.PathRegex != ""; (!exact && !prefix && !regex) || + (exact && prefix) || (exact && regex) || (prefix && regex) { + allErrs = append(allErrs, field.Required( + path.Child("routes").Index(i), + "Route must define exactly one of PathExact, PathPrefix, or PathRegex")) + } + + isRateLimitSet = isRateLimitSet || route.RequestsPerSecond > 0 + + // Unlike top-level RequestsPerSecond, any route MUST have a RequestsPerSecond defined. + if route.RequestsPerSecond <= 0 { + allErrs = append(allErrs, field.Invalid( + path.Child("routes").Index(i).Child("requestsPerSecond"), + route.RequestsPerSecond, "RequestsPerSecond must be greater than 0")) + } + + if route.RequestsMaxBurst < 0 { + allErrs = append(allErrs, field.Invalid( + path.Child("routes").Index(i).Child("requestsMaxBurst"), + route.RequestsMaxBurst, "RequestsMaxBurst must be positive")) + } + } + + if !isRateLimitSet { + allErrs = append(allErrs, field.Invalid( + path.Child("requestsPerSecond"), + irl.RequestsPerSecond, "At least one of top-level or route-level RequestsPerSecond must be set")) + } + return allErrs +} + +type InstanceLevelRouteRateLimits struct { + // Exact path to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. + PathExact string `json:"pathExact,omitempty"` + // Prefix to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. + PathPrefix string `json:"pathPrefix,omitempty"` + // Regex to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. + PathRegex string `json:"pathRegex,omitempty"` + + // RequestsPerSecond is the average number of requests per + // second that can be made without being throttled. This field is required + // if RequestsMaxBurst is set. The allowed number of requests may exceed + // RequestsPerSecond up to the value specified in RequestsMaxBurst. + // Internally, this is the refill rate of the token bucket used for rate limiting. + RequestsPerSecond int `json:"requestsPerSecond,omitempty"` + + // RequestsMaxBurst is the maximum number of requests that can be sent + // in a burst. Should be equal to or greater than RequestsPerSecond. If unset, + // defaults to RequestsPerSecond. Internally, this is the maximum size of the token + // bucket used for rate limiting. + RequestsMaxBurst int `json:"requestsMaxBurst,omitempty"` +} + func (in *ServiceDefaults) ConsulKind() string { return capi.ServiceDefaults } @@ -308,6 +456,7 @@ func (in *ServiceDefaults) ToConsul(datacenter string) capi.ConfigEntry { LocalConnectTimeoutMs: in.Spec.LocalConnectTimeoutMs, LocalRequestTimeoutMs: in.Spec.LocalRequestTimeoutMs, BalanceInboundConnections: in.Spec.BalanceInboundConnections, + RateLimits: in.Spec.RateLimits.toConsul(), EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), } } @@ -356,6 +505,7 @@ func (in *ServiceDefaults) Validate(consulMeta common.ConsulMeta) error { allErrs = append(allErrs, in.Spec.UpstreamConfig.validate(path.Child("upstreamConfig"), consulMeta.PartitionsEnabled)...) allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) + allErrs = append(allErrs, in.Spec.RateLimits.validate(path.Child("rateLimits"))...) allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) if len(allErrs) > 0 { diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 31a41f3f06..2292e55791 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -160,6 +160,23 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, }, BalanceInboundConnections: "exact_balance", + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + RequestsPerSecond: 1234, + RequestsMaxBurst: 2345, + Routes: []InstanceLevelRouteRateLimits{ + { + PathExact: "/foo", + RequestsPerSecond: 111, + RequestsMaxBurst: 222, + }, + { + PathPrefix: "/admin", + RequestsPerSecond: 333, + }, + }, + }, + }, EnvoyExtensions: EnvoyExtensions{ EnvoyExtension{ Name: "aws_request_signing", @@ -288,6 +305,23 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, }, BalanceInboundConnections: "exact_balance", + RateLimits: &capi.RateLimits{ + InstanceLevel: capi.InstanceLevelRateLimits{ + RequestsPerSecond: 1234, + RequestsMaxBurst: 2345, + Routes: []capi.InstanceLevelRouteRateLimits{ + { + PathExact: "/foo", + RequestsPerSecond: 111, + RequestsMaxBurst: 222, + }, + { + PathPrefix: "/admin", + RequestsPerSecond: 333, + }, + }, + }, + }, EnvoyExtensions: []capi.EnvoyExtension{ { Name: "aws_request_signing", @@ -559,6 +593,23 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, }, BalanceInboundConnections: "exact_balance", + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + RequestsPerSecond: 1234, + RequestsMaxBurst: 2345, + Routes: []InstanceLevelRouteRateLimits{ + { + PathExact: "/foo", + RequestsPerSecond: 111, + RequestsMaxBurst: 222, + }, + { + PathPrefix: "/admin", + RequestsPerSecond: 333, + }, + }, + }, + }, EnvoyExtensions: EnvoyExtensions{ EnvoyExtension{ Name: "aws_request_signing", @@ -680,6 +731,23 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, }, BalanceInboundConnections: "exact_balance", + RateLimits: &capi.RateLimits{ + InstanceLevel: capi.InstanceLevelRateLimits{ + RequestsPerSecond: 1234, + RequestsMaxBurst: 2345, + Routes: []capi.InstanceLevelRouteRateLimits{ + { + PathExact: "/foo", + RequestsPerSecond: 111, + RequestsMaxBurst: 222, + }, + { + PathPrefix: "/admin", + RequestsPerSecond: 333, + }, + }, + }, + }, EnvoyExtensions: []capi.EnvoyExtension{ { Name: "aws_request_signing", @@ -1329,6 +1397,152 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.envoyExtensions.envoyExtension[0].arguments: Invalid value: "{\"SOME_INVALID_JSON\"}": must be valid map value: invalid character '}' after object key`, }, + "rateLimits.instanceLevel.requestsPerSecond (negative value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + RequestsPerSecond: -1, + RequestsMaxBurst: 0, + Routes: []InstanceLevelRouteRateLimits{ + { + PathPrefix: "/admin", + RequestsPerSecond: 222, + }, + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: -1: RequestsPerSecond must be positive`, + }, + "rateLimits.instanceLevel.requestsPerSecond (invalid value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + RequestsMaxBurst: 1000, + Routes: []InstanceLevelRouteRateLimits{ + { + PathPrefix: "/admin", + RequestsPerSecond: 222, + }, + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0 if RequestsMaxBurst is set`, + }, + "rateLimits.instanceLevel.requestsMaxBurst (negative value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + RequestsMaxBurst: -1, + Routes: []InstanceLevelRouteRateLimits{ + { + PathPrefix: "/admin", + RequestsPerSecond: 222, + }, + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsMaxBurst: Invalid value: -1: RequestsMaxBurst must be positive`, + }, + "rateLimits.instanceLevel.routes (invalid path)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + RequestsPerSecond: 1234, + RequestsMaxBurst: 2345, + Routes: []InstanceLevelRouteRateLimits{ + { + RequestsPerSecond: 222, + }, + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0]: Required value: Route must define exactly one of PathExact, PathPrefix, or PathRegex`, + }, + "rateLimits.instanceLevel.routes.requestsPerSecond (zero value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + RequestsPerSecond: 1234, + Routes: []InstanceLevelRouteRateLimits{ + { + PathExact: "/", + }, + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0].requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0`, + }, + "rateLimits.instanceLevel.routes.requestsMaxBurst (negative value)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + RequestsPerSecond: 1234, + Routes: []InstanceLevelRouteRateLimits{ + { + PathExact: "/", + RequestsPerSecond: 222, + RequestsMaxBurst: -1, + }, + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0].requestsMaxBurst: Invalid value: -1: RequestsMaxBurst must be positive`, + }, + "rateLimits.requestsMaxBurst (top-level and route-level unset)": { + input: &ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + Spec: ServiceDefaultsSpec{ + RateLimits: &RateLimits{ + InstanceLevel: InstanceLevelRateLimits{ + Routes: []InstanceLevelRouteRateLimits{ + { + PathExact: "/", + }, + }, + }, + }, + }, + }, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.rateLimits.instanceLevel.routes[0].requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0, spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: 0: At least one of top-level or route-level RequestsPerSecond must be set]`, + }, } for name, testCase := range cases { diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 6786b38004..5b54f4a5c5 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -860,6 +860,41 @@ func (in *IngressServiceConfig) DeepCopy() *IngressServiceConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceLevelRateLimits) DeepCopyInto(out *InstanceLevelRateLimits) { + *out = *in + if in.Routes != nil { + in, out := &in.Routes, &out.Routes + *out = make([]InstanceLevelRouteRateLimits, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceLevelRateLimits. +func (in *InstanceLevelRateLimits) DeepCopy() *InstanceLevelRateLimits { + if in == nil { + return nil + } + out := new(InstanceLevelRateLimits) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceLevelRouteRateLimits) DeepCopyInto(out *InstanceLevelRouteRateLimits) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceLevelRouteRateLimits. +func (in *InstanceLevelRouteRateLimits) DeepCopy() *InstanceLevelRouteRateLimits { + if in == nil { + return nil + } + out := new(InstanceLevelRouteRateLimits) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IntentionDestination) DeepCopyInto(out *IntentionDestination) { *out = *in @@ -2080,6 +2115,22 @@ func (in *ProxyDefaultsSpec) DeepCopy() *ProxyDefaultsSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimits) DeepCopyInto(out *RateLimits) { + *out = *in + in.InstanceLevel.DeepCopyInto(&out.InstanceLevel) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimits. +func (in *RateLimits) DeepCopy() *RateLimits { + if in == nil { + return nil + } + out := new(RateLimits) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReadWriteRatesConfig) DeepCopyInto(out *ReadWriteRatesConfig) { *out = *in @@ -2576,6 +2627,11 @@ func (in *ServiceDefaultsSpec) DeepCopyInto(out *ServiceDefaultsSpec) { *out = new(ServiceDefaultsDestination) (*in).DeepCopyInto(*out) } + if in.RateLimits != nil { + in, out := &in.RateLimits, &out.RateLimits + *out = new(RateLimits) + (*in).DeepCopyInto(*out) + } if in.EnvoyExtensions != nil { in, out := &in.EnvoyExtensions, &out.EnvoyExtensions *out = make(EnvoyExtensions, len(*in)) diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index d4d639e55c..2b5ab54acd 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -185,6 +185,69 @@ spec: unlock usage of the service-splitter and service-router config entries for a service. type: string + rateLimits: + description: RateLimits is rate limiting configuration that is applied + to inbound traffic for a service. Rate limiting is a Consul enterprise + feature. + properties: + instanceLevel: + description: InstanceLevel represents rate limit configuration + that is applied per service instance. + properties: + requestsMaxBurst: + description: "RequestsMaxBurst is the maximum number of requests + that can be sent in a burst. Should be equal to or greater + than RequestsPerSecond. If unset, defaults to RequestsPerSecond. + \n Internally, this is the maximum size of the token bucket + used for rate limiting." + type: integer + requestsPerSecond: + description: "RequestsPerSecond is the average number of requests + per second that can be made without being throttled. This + field is required if RequestsMaxBurst is set. The allowed + number of requests may exceed RequestsPerSecond up to the + value specified in RequestsMaxBurst. \n Internally, this + is the refill rate of the token bucket used for rate limiting." + type: integer + routes: + description: Routes is a list of rate limits applied to specific + routes. For a given request, the first matching route will + be applied, if any. Overrides any top-level configuration. + items: + properties: + pathExact: + description: Exact path to match. Exactly one of PathExact, + PathPrefix, or PathRegex must be specified. + type: string + pathPrefix: + description: Prefix to match. Exactly one of PathExact, + PathPrefix, or PathRegex must be specified. + type: string + pathRegex: + description: Regex to match. Exactly one of PathExact, + PathPrefix, or PathRegex must be specified. + type: string + requestsMaxBurst: + description: RequestsMaxBurst is the maximum number + of requests that can be sent in a burst. Should be + equal to or greater than RequestsPerSecond. If unset, + defaults to RequestsPerSecond. Internally, this is + the maximum size of the token bucket used for rate + limiting. + type: integer + requestsPerSecond: + description: RequestsPerSecond is the average number + of requests per second that can be made without being + throttled. This field is required if RequestsMaxBurst + is set. The allowed number of requests may exceed + RequestsPerSecond up to the value specified in RequestsMaxBurst. + Internally, this is the refill rate of the token bucket + used for rate limiting. + type: integer + type: object + type: array + type: object + type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the diff --git a/control-plane/go.mod b/control-plane/go.mod index 3589eac8b7..e935420730 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -12,8 +12,10 @@ require ( github.com/go-logr/logr v1.2.3 github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 - github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef // this points to a commit on Consul main + github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed + github.com/hashicorp/consul-server-connection-manager v0.1.4 + github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 + github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 @@ -35,6 +37,8 @@ require ( golang.org/x/text v0.11.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 + google.golang.org/grpc v1.55.0 + google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 @@ -45,13 +49,6 @@ require ( sigs.k8s.io/gateway-api v0.7.1 ) -require ( - github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed - github.com/hashicorp/consul-server-connection-manager v0.1.4 - google.golang.org/grpc v1.55.0 - google.golang.org/protobuf v1.30.0 -) - require ( cloud.google.com/go/compute v1.19.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 1a16c25165..8dca0b7203 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -263,8 +263,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= -github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5 h1:TTTgXv9YeaRnODyFP1k2b2Nq5RIGrUUgI5SkDhuSNwM= -github.com/hashicorp/consul/api v1.10.1-0.20230821180813-217d305b38d5/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 h1:NPhzdwDho2r8pQv31oeGLlco7fnJ1i0WLYjtSXqWEck= +github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef h1:Vt5NSnXc+RslTxXH2pz7dCb3hnE33CD2TrBP5AIQtMg= github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924 h1:gkb6/ix0Tg1Th5FTjyq4QklLgrtIVQ/TUB0kbhIcPsY= From 837ad526c85f4f7e257443b3c7978ddc14a23203 Mon Sep 17 00:00:00 2001 From: David Yu Date: Wed, 6 Sep 2023 14:34:42 -0700 Subject: [PATCH 364/592] Address issue #1285 via docs (#2903) * Update values.yaml Co-authored-by: Tu Nguyen --- charts/consul/values.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index a4074683b5..061af0c2ac 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -3072,8 +3072,9 @@ ingressGateways: # Gateways is a list of gateway objects. The only required field for # each is `name`, though they can also contain any of the fields in - # `defaults`. Values defined here override the defaults except in the - # case of annotations where both will be applied. + # `defaults`. You must provide a unique name for each ingress gateway. These names + # must be unique across different namespaces. + # Values defined here override the defaults, except in the case of annotations where both will be applied. # @type: array gateways: - name: ingress-gateway From b66543f35bc0029ce58f41ed228b8b82c2a7dcbf Mon Sep 17 00:00:00 2001 From: Ganesh S Date: Wed, 6 Sep 2023 21:10:19 -0700 Subject: [PATCH 365/592] Fix audit log parse error (#2905) * Fix audit log parse error * Add changelog * Fix filename * Address comments --- .changelog/2905.txt | 3 +++ .../templates/server-config-configmap.yaml | 11 +------- .../test/unit/server-config-configmap.bats | 25 ++++++++++++++++++- 3 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 .changelog/2905.txt diff --git a/.changelog/2905.txt b/.changelog/2905.txt new file mode 100644 index 0000000000..eb1196fa0f --- /dev/null +++ b/.changelog/2905.txt @@ -0,0 +1,3 @@ +```release-note:bug +audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. +``` \ No newline at end of file diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 6c102f0ae3..1e2f7f8d00 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -198,16 +198,7 @@ data: "sink": { {{- range $index, $element := .Values.server.auditLogs.sinks }} {{- if ne $index 0 }},{{end}} - "{{ $element.name }}": { - {{- $firstKeyValuePair := false }} - {{- range $k, $v := $element }} - {{- if ne $k "name" }} - {{- if ne $firstKeyValuePair false }},{{end}} - {{- $firstKeyValuePair = true }} - "{{ $k }}": "{{ $v }}" - {{- end }} - {{- end }} - } + "{{ get $element "name" }}": {{ omit $element "name" | toJson }} {{- end }} } } diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 643caeb0a1..4d5f7cba83 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -1112,6 +1112,8 @@ load _helpers --set 'server.auditLogs.sinks[0].format=json' \ --set 'server.auditLogs.sinks[0].delivery_guarantee=best-effort' \ --set 'server.auditLogs.sinks[0].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[0].rotate_max_files=20' \ + --set 'server.auditLogs.sinks[0].rotate_bytes=12455355' \ --set 'server.auditLogs.sinks[0].path=/tmp/audit.json' \ . | tee /dev/stderr | yq -r '.data["audit-logging.json"]' | tee /dev/stderr) @@ -1124,6 +1126,12 @@ load _helpers local actual=$(echo $object | jq -r .audit.sink.MySink.rotate_duration | tee /dev/stderr) [ "${actual}" = "24h" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink.rotate_max_files | tee /dev/stderr) + [ ${actual} = 20 ] + + local actual=$(echo $object | jq -r .audit.sink.MySink.rotate_bytes | tee /dev/stderr) + [ ${actual} = 12455355 ] } @test "server/ConfigMap: server.auditLogs is enabled with 1 sink input object and it does not contain the name attribute" { @@ -1137,6 +1145,8 @@ load _helpers --set 'server.auditLogs.sinks[0].format=json' \ --set 'server.auditLogs.sinks[0].delivery_guarantee=best-effort' \ --set 'server.auditLogs.sinks[0].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[0].rotate_max_files=20' \ + --set 'server.auditLogs.sinks[0].rotate_bytes=12455355' \ --set 'server.auditLogs.sinks[0].path=/tmp/audit.json' \ . | tee /dev/stderr | yq -r '.data["audit-logging.json"]' | jq -r .audit.sink.name | tee /dev/stderr) @@ -1155,13 +1165,16 @@ load _helpers --set 'server.auditLogs.sinks[0].format=json' \ --set 'server.auditLogs.sinks[0].delivery_guarantee=best-effort' \ --set 'server.auditLogs.sinks[0].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[0].rotate_max_files=15' \ + --set 'server.auditLogs.sinks[0].rotate_bytes=12445' \ --set 'server.auditLogs.sinks[0].path=/tmp/audit.json' \ --set 'server.auditLogs.sinks[1].name=MySink2' \ --set 'server.auditLogs.sinks[1].type=file' \ --set 'server.auditLogs.sinks[1].format=json' \ --set 'server.auditLogs.sinks[1].delivery_guarantee=best-effort' \ - --set 'server.auditLogs.sinks[1].rotate_max_files=15' \ --set 'server.auditLogs.sinks[1].rotate_duration=24h' \ + --set 'server.auditLogs.sinks[1].rotate_max_files=25' \ + --set 'server.auditLogs.sinks[1].rotate_bytes=152445' \ --set 'server.auditLogs.sinks[1].path=/tmp/audit-2.json' \ --set 'server.auditLogs.sinks[2].name=MySink3' \ --set 'server.auditLogs.sinks[2].type=file' \ @@ -1169,6 +1182,7 @@ load _helpers --set 'server.auditLogs.sinks[2].delivery_guarantee=best-effort' \ --set 'server.auditLogs.sinks[2].rotate_max_files=20' \ --set 'server.auditLogs.sinks[2].rotate_duration=18h' \ + --set 'server.auditLogs.sinks[2].rotate_bytes=12445' \ --set 'server.auditLogs.sinks[2].path=/tmp/audit-3.json' \ . | tee /dev/stderr | yq -r '.data["audit-logging.json"]' | tee /dev/stderr) @@ -1185,17 +1199,26 @@ load _helpers local actual=$(echo $object | jq -r .audit.sink.MySink1.name | tee /dev/stderr) [ "${actual}" = "null" ] + local actual=$(echo $object | jq -r .audit.sink.MySink1.rotate_max_files | tee /dev/stderr) + [ ${actual} = 15 ] + local actual=$(echo $object | jq -r .audit.sink.MySink3.delivery_guarantee | tee /dev/stderr) [ "${actual}" = "best-effort" ] local actual=$(echo $object | jq -r .audit.sink.MySink2.rotate_duration | tee /dev/stderr) [ "${actual}" = "24h" ] + local actual=$(echo $object | jq -r .audit.sink.MySink2.rotate_bytes | tee /dev/stderr) + [ ${actual} = 152445 ] + local actual=$(echo $object | jq -r .audit.sink.MySink1.format | tee /dev/stderr) [ "${actual}" = "json" ] local actual=$(echo $object | jq -r .audit.sink.MySink3.type | tee /dev/stderr) [ "${actual}" = "file" ] + + local actual=$(echo $object | jq -r .audit.sink.MySink3.rotate_max_files | tee /dev/stderr) + [ ${actual} = 20 ] } @test "server/ConfigMap: server.logLevel is empty" { From 516d7629951f705fc6696cca93d1715d735b3eec Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Thu, 7 Sep 2023 12:37:32 -0400 Subject: [PATCH 366/592] Force a check on controller-gen version (#2902) --- Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Makefile b/Makefile index 866a37fbfc..336a880582 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,7 @@ cni-plugin-lint: cd control-plane/cni; golangci-lint run -c ../../.golangci.yml ctrl-generate: get-controller-gen ## Run CRD code generation. + make ensure-controller-gen-version cd control-plane; $(CONTROLLER_GEN) object paths="./..." # Perform a terraform fmt check but don't change anything @@ -171,6 +172,7 @@ lint: cni-plugin-lint ## Run linter in the control-plane, cli, and acceptance di for p in control-plane cli acceptance; do cd $$p; golangci-lint run --path-prefix $$p -c ../.golangci.yml; cd ..; done ctrl-manifests: get-controller-gen ## Generate CRD manifests. + make ensure-controller-gen-version cd control-plane; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases make copy-crds-to-chart make generate-external-crds @@ -191,6 +193,15 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif +ensure-controller-gen-version: ## Ensure controller-gen version is v0.8.0. +ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.8.0)) + @echo "controller-gen version is not v0.8.0, uninstall the binary and install the correct version with 'make get-controller-gen'." + @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" + @exit 1 +else + @echo "Found correct version: $(shell $(CONTROLLER_GEN) --version)" +endif + add-copyright-header: ## Add copyright header to all files in the project ifeq (, $(shell which copywrite)) @echo "Installing copywrite" From fd35b8963fcc182f1244ba97d2f4c5d6f5411edc Mon Sep 17 00:00:00 2001 From: Ashesh Vidyut <134911583+absolutelightning@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:53:57 +0530 Subject: [PATCH 367/592] NET-581 - Added vault namespace in helm (#2841) * added namespace * namespace in connect ca * updated tests * fix test desc * changelog * Update .changelog/2841.txt Co-authored-by: Michael Zalimeni * Update charts/consul/values.yaml Co-authored-by: Michael Zalimeni * removed new line added * fix templates * bats test * fix double colon * fix template * added 2 more tests * fixes bats tests * fix json in api gateway * updated bats test * Update charts/consul/values.yaml Co-authored-by: Michael Zalimeni * fix client daemon set bats * fix bats test * fix bats * api gateway fix * fix bats * fix clientdaemon set and api gateway controller * fix connect inject deployment * fix mesh gateway deployment * added tests for partition init job * server acl init job tests added * fix server stateful bats * fix sync catalog * fix includes check * bats test fixes * fix connect inject * fix yaml * fix yaml * fix assertions in bats * fix client daemon set bats * Update charts/consul/values.yaml Co-authored-by: Michael Zalimeni * Update charts/consul/templates/server-config-configmap.yaml Co-authored-by: Michael Zalimeni * change yaml * added addional config test * fix tests * added more tests * fix bats * Update charts/consul/test/unit/server-config-configmap.bats Co-authored-by: Michael Zalimeni * Update charts/consul/test/unit/server-config-configmap.bats Co-authored-by: Michael Zalimeni * Update .changelog/2841.txt Co-authored-by: David Yu * Update .changelog/2841.txt Co-authored-by: Michael Zalimeni * added dummy commit to run CI * fix change log * fix comment --------- Co-authored-by: Michael Zalimeni Co-authored-by: David Yu --- .changelog/2841.txt | 5 ++ .../tests/vault/vault_namespaces_test.go | 6 +- .../api-gateway-controller-deployment.yaml | 3 + charts/consul/templates/client-daemonset.yaml | 3 + .../templates/connect-inject-deployment.yaml | 3 + .../templates/mesh-gateway-deployment.yaml | 3 + .../consul/templates/partition-init-job.yaml | 3 + .../consul/templates/server-acl-init-job.yaml | 3 + .../templates/server-config-configmap.yaml | 3 + .../consul/templates/server-statefulset.yaml | 3 + .../templates/sync-catalog-deployment.yaml | 3 + .../telemetry-collector-deployment.yaml | 3 + .../api-gateway-controller-deployment.bats | 68 ++++++++++++++++ charts/consul/test/unit/client-daemonset.bats | 65 +++++++++++++++ .../test/unit/connect-inject-deployment.bats | 65 +++++++++++++++ .../test/unit/mesh-gateway-deployment.bats | 68 ++++++++++++++++ .../consul/test/unit/partition-init-job.bats | 80 +++++++++++++++++++ .../consul/test/unit/server-acl-init-job.bats | 74 +++++++++++++++++ .../test/unit/server-config-configmap.bats | 77 ++++++++++++++++++ .../consul/test/unit/server-statefulset.bats | 65 +++++++++++++++ .../test/unit/sync-catalog-deployment.bats | 65 +++++++++++++++ .../unit/telemetry-collector-deployment.bats | 68 ++++++++++++++++ charts/consul/values.yaml | 6 +- 23 files changed, 737 insertions(+), 5 deletions(-) create mode 100644 .changelog/2841.txt diff --git a/.changelog/2841.txt b/.changelog/2841.txt new file mode 100644 index 0000000000..a1e3594390 --- /dev/null +++ b/.changelog/2841.txt @@ -0,0 +1,5 @@ +```release-note:improvement +vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to +secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. +This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. +``` diff --git a/acceptance/tests/vault/vault_namespaces_test.go b/acceptance/tests/vault/vault_namespaces_test.go index 4a9ca092d0..a6605acc46 100644 --- a/acceptance/tests/vault/vault_namespaces_test.go +++ b/acceptance/tests/vault/vault_namespaces_test.go @@ -23,7 +23,7 @@ import ( // TestVault_VaultNamespace installs Vault, configures a Vault namespace, and then bootstraps it // with secrets, policies, and Kube Auth Method. // It then configures Consul to use vault as the backend and checks that it works -// with the vault namespace. +// with the vault namespace. Namespace is added in this via global.secretsBackend.vault.vaultNamespace. func TestVault_VaultNamespace(t *testing.T) { cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) @@ -195,9 +195,7 @@ func TestVault_VaultNamespace(t *testing.T) { "global.secretsBackend.vault.connectCA.address": vaultCluster.Address(), "global.secretsBackend.vault.connectCA.rootPKIPath": connectCARootPath, "global.secretsBackend.vault.connectCA.intermediatePKIPath": connectCAIntermediatePath, - "global.secretsBackend.vault.connectCA.additionalConfig": fmt.Sprintf(`"{\"connect\": [{ \"ca_config\": [{ \"namespace\": \"%s\"}]}]}"`, vaultNamespacePath), - - "global.secretsBackend.vault.agentAnnotations": fmt.Sprintf("\"vault.hashicorp.com/namespace\": \"%s\"", vaultNamespacePath), + "global.secretsBackend.vault.vaultNamespace": vaultNamespacePath, "global.acls.manageSystemACLs": "true", "global.acls.bootstrapToken.secretName": bootstrapTokenSecret.Path, diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 8c5c2fa73e..11396c8a03 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -38,6 +38,9 @@ spec: "vault.hashicorp.com/agent-inject-template-serverca.crt": {{ template "consul.serverTLSCATemplate" . }} {{- 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 }} {{- 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 }}" diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 345c5c731e..61425cfdb8 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -75,6 +75,9 @@ spec: {{- 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 }} {{- if and .Values.global.enterpriseLicense.secretName (not .Values.global.acls.manageSystemACLs) }} {{- with .Values.global.enterpriseLicense }} "vault.hashicorp.com/agent-inject-secret-enterpriselicense.txt": "{{ .secretName }}" diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index a1be4a79b7..c6e47951e4 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -85,6 +85,9 @@ spec: {{- 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 }} spec: serviceAccountName: {{ template "consul.fullname" . }}-connect-injector diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index 1936138db3..ac050e7199 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -70,6 +70,9 @@ spec: {{- 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 (and .Values.global.metrics.enabled .Values.global.metrics.enableGatewayMetrics) }} "prometheus.io/scrape": "true" diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 34f5888a58..663a64bcbf 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -59,6 +59,9 @@ spec: {{- 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 }} spec: restartPolicy: Never diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index a72d12f80d..0bdceadfd7 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -97,6 +97,9 @@ spec: {{- 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 }} spec: restartPolicy: Never diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 1e2f7f8d00..9ebfbd2571 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -72,6 +72,9 @@ data: "ca_file": "/consul/vault-ca/tls.crt", {{- end }} "intermediate_pki_path": "{{ .connectCA.intermediatePKIPath }}", + {{- if (and (.vaultNamespace) (not (contains "namespace" (default "" .connectCA.additionalConfig)))) }} + "namespace": "{{ .vaultNamespace }}", + {{- end }} "root_pki_path": "{{ .connectCA.rootPKIPath }}", "auth_method": { "type": "kubernetes", diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 568d28d386..44d9419c8f 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -98,6 +98,9 @@ spec: {{- 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 }} {{- if .Values.global.enterpriseLicense.secretName }} {{- with .Values.global.enterpriseLicense }} "vault.hashicorp.com/agent-inject-secret-enterpriselicense.txt": "{{ .secretName }}" diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index a8793ef6f6..f4aeb1cdb8 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -56,6 +56,9 @@ spec: {{- 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 }} spec: serviceAccountName: {{ template "consul.fullname" . }}-sync-catalog diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index 4b5bb6df7b..d6f3a91cfa 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -58,6 +58,9 @@ spec: {{- 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 }} labels: diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats index 327802af07..fbc8e6e581 100755 --- a/charts/consul/test/unit/api-gateway-controller-deployment.bats +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -1035,6 +1035,74 @@ load _helpers [ "${actual}" = "test" ] } +@test "apiGateway/Deployment: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/Deployment: correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set and agentAnnotations are set without vaultNamespace annotation" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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' \ + --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/agent-extra-secret: bar' \ + . | 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 "apiGateway/Deployment: 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 templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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' \ + --set 'global.secretsBackend.vault.agentAnnotations="vault.hashicorp.com/namespace": bar' \ + . | 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 "apiGateway/Deployment: vault agent annotations can be set" { cd `chart_dir` local actual=$(helm template \ diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index d512ad8ab2..ff9288a51d 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -2320,6 +2320,71 @@ rollingUpdate: [ "${actual}" = "foo" ] } +@test "client/DaemonSet: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'client.enabled=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 "client/DaemonSet: correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is see and agentAnnotations are set without vaultNamespace annotation" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'client.enabled=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' \ + --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/agent-extra-secret: bar' \ + . | 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 "client/DaemonSet: 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 templates/client-daemonset.yaml \ + --set 'client.enabled=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' \ + --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/namespace: bar' \ + . | 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 "client/DaemonSet: vault gossip annotations are set when gossip encryption enabled" { cd `chart_dir` local object=$(helm template \ diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 7180468cea..ee2fac4b97 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -1783,6 +1783,71 @@ load _helpers [ "${actual}" = "" ] } +@test "connectInject/Deployment: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=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 "connectInject/Deployment: correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is see and agentAnnotations are set without vaultNamespace annotation" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=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 "connectInject/Deployment: 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 templates/connect-inject-deployment.yaml \ + --set 'client.enabled=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" ] +} + #-------------------------------------------------------------------- # enable-webhook-ca-update diff --git a/charts/consul/test/unit/mesh-gateway-deployment.bats b/charts/consul/test/unit/mesh-gateway-deployment.bats index d58def05da..8e08463c43 100755 --- a/charts/consul/test/unit/mesh-gateway-deployment.bats +++ b/charts/consul/test/unit/mesh-gateway-deployment.bats @@ -1332,6 +1332,74 @@ key2: value2' \ [ "${actual}" = "true" ] } +@test "meshGateway/Deployment: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=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 "meshGateway/Deployment: 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 templates/mesh-gateway-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=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 "meshGateway/Deployment: 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 templates/mesh-gateway-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=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" ] +} + #-------------------------------------------------------------------- # Vault agent annotations diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index d4b3b4e38a..1e5bc954d7 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -326,6 +326,86 @@ reservedNameTest() { [ "${actual}" = "null" ] } +@test "partitionInit/Job: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set "global.adminPartitions.name=bar" \ + --set 'global.enableConsulNamespaces=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + --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 "partitionInit/Job: 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 templates/partition-init-job.yaml \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set "global.adminPartitions.name=bar" \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + --set 'global.enableConsulNamespaces=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 "partitionInit/Job: 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 templates/partition-init-job.yaml \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set "global.adminPartitions.name=bar" \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + --set 'global.enableConsulNamespaces=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 "partitionInit/Job: configures server CA to come from vault when vault and TLS are enabled" { cd `chart_dir` local object=$(helm template \ diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 81022a8e4c..a9873a8e61 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -688,6 +688,80 @@ load _helpers [ "${actual}" = "null" ] } +@test "serverACLInit/Job: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.bootstrapToken.secretName=foo' \ + --set 'global.acls.bootstrapToken.secretKey=bar' \ + --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' \ + --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ + . | 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 "serverACLInit/Job: 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 templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.bootstrapToken.secretName=foo' \ + --set 'global.acls.bootstrapToken.secretKey=bar' \ + --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' \ + --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ + . | 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 "serverACLInit/Job: 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 templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.bootstrapToken.secretName=foo' \ + --set 'global.acls.bootstrapToken.secretKey=bar' \ + --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' \ + --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ + . | 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 "serverACLInit/Job: configures server CA to come from vault when vault and TLS are enabled" { cd `chart_dir` local object=$(helm template \ diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 4d5f7cba83..53d842fc9c 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -658,6 +658,83 @@ load _helpers [ "${actual}" = "true" ] } +@test "server/ConfigMap: doesn't set Vault Namespace in connect CA config when global.secretsBackend.vault.vaultNamespace is blank in values.yaml" { + cd `chart_dir` + + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.connectCA.address=example.com' \ + --set 'global.secretsBackend.vault.connectCA.rootPKIPath=root' \ + --set 'global.secretsBackend.vault.connectCA.intermediatePKIPath=int' \ + --set 'global.secretsBackend.vault.ca.secretName=ca' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + . | tee /dev/stderr | + yq '.data["connect-ca-config.json"] | contains("namespace")' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "server/ConfigMap: set Vault Namespace in connect CA config when global.secretsBackend.vault.vaultNamespace is not blank in values.yaml" { + cd `chart_dir` + + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.connectCA.address=example.com' \ + --set 'global.secretsBackend.vault.connectCA.rootPKIPath=root' \ + --set 'global.secretsBackend.vault.connectCA.intermediatePKIPath=int' \ + --set 'global.secretsBackend.vault.ca.secretName=ca' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + --set 'global.secretsBackend.vault.vaultNamespace=vault-namespace' \ + . | tee /dev/stderr | + yq '.data["connect-ca-config.json"] | contains("\"namespace\": \"vault-namespace\"")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + + +@test "server/ConfigMap: do not set Vault Namespace in connect CA config from global.secretsBackend.vault.vaultNamespace when also set in connectCA.additionalConfig" { + cd `chart_dir` + + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.connectCA.address=example.com' \ + --set 'global.secretsBackend.vault.connectCA.rootPKIPath=root' \ + --set 'global.secretsBackend.vault.connectCA.intermediatePKIPath=int' \ + --set 'global.secretsBackend.vault.ca.secretName=ca' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + --set 'global.secretsBackend.vault.vaultNamespace=vault-namespace' \ + --set 'global.secretsBackend.vault.connectCA.additionalConfig=\{\"connect\":\[\{\"ca_config\":\[\{\"namespace\": \"vns\"}\]\}\]\}' \ + . | tee /dev/stderr | + yq '.data["connect-ca-config.json"] | contains("\"namespace\": \"vault-namespace\"")' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "server/ConfigMap: set Vault Namespace in connect CA config when global.secretsBackend.vault.vaultNamespace is not blank and connectCA.additionalConfig is blank" { + cd `chart_dir` + + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.connectCA.address=example.com' \ + --set 'global.secretsBackend.vault.connectCA.rootPKIPath=root' \ + --set 'global.secretsBackend.vault.connectCA.intermediatePKIPath=int' \ + --set 'global.secretsBackend.vault.ca.secretName=ca' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + --set 'global.secretsBackend.vault.vaultNamespace=vault-namespace' \ + . | tee /dev/stderr | + yq '.data["connect-ca-config.json"] | contains("\"namespace\": \"vault-namespace\"")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + @test "server/ConfigMap: doesn't add federation config when global.federation.enabled is false (default)" { cd `chart_dir` local actual=$(helm template \ diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index dcef8d1382..6fbb2e8e04 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -1621,6 +1621,71 @@ load _helpers [ "${actual}" = "false" ] } +@test "server/StatefulSet: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/server-statefulset.yaml \ + --set 'client.enabled=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 "server/StatefulSet: 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 templates/server-statefulset.yaml \ + --set 'client.enabled=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 "server/StatefulSet: 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 templates/server-statefulset.yaml \ + --set 'client.enabled=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 "server/StatefulSet: vault CA is not configured when secretName is set but secretKey is not" { cd `chart_dir` local object=$(helm template \ diff --git a/charts/consul/test/unit/sync-catalog-deployment.bats b/charts/consul/test/unit/sync-catalog-deployment.bats index 318b4d3d3c..d8321eefdf 100755 --- a/charts/consul/test/unit/sync-catalog-deployment.bats +++ b/charts/consul/test/unit/sync-catalog-deployment.bats @@ -1070,6 +1070,71 @@ load _helpers [ "${actual}" = "" ] } +@test "syncCatalog/Deployment: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.enabled=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 "syncCatalog/Deployment: 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 templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.enabled=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 "syncCatalog/Deployment: 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 templates/sync-catalog-deployment.yaml \ + --set 'syncCatalog.enabled=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 "syncCatalog/Deployment: vault CA is not configured by default" { cd `chart_dir` local object=$(helm template \ diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index 7809039e11..ad50341061 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -352,6 +352,74 @@ load _helpers [ "${actual}" = "false" ] } +@test "telemetryCollector/Deployment: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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 "telemetryCollector/Deployment: 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 templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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 "telemetryCollector/Deployment: 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 templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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 "telemetryCollector/Deployment: vault CA is not configured when secretKey is set but secretName is not" { cd `chart_dir` local object=$(helm template \ diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 061af0c2ac..762d64b207 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -114,6 +114,11 @@ global: # secretKey should be in the form of "key". secretsBackend: vault: + # Vault namespace (optional). This sets the Vault namespace for the `vault.hashicorp.com/namespace` + # agent annotation and [Vault Connect CA namespace](https://developer.hashicorp.com/consul/docs/connect/ca/vault#namespace). + # To override one of these values individually, see `agentAnnotations` and `connectCA.additionalConfig`. + vaultNamespace: "" + # Enabling the Vault secrets backend will replace Kubernetes secrets with referenced Vault secrets. enabled: false @@ -230,7 +235,6 @@ global: # { # "connect": [{ # "ca_config": [{ - # "namespace": "my-vault-ns", # "leaf_cert_ttl": "36h" # }] # }] From d5fdc0d119d8449ad5b28eb0579bb4b1b8870002 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:50:40 -0500 Subject: [PATCH 368/592] [NET-5399] Improve token fetching performance for endpoints controller. (#2910) Improve token fetching performance for endpoints controller. Prior to this change, the endpoints controller would list all ACL tokens in a namespace when a service instance is being deleted. This commit improves the performance by querying only the necessary subset of tokens by service-identity / service-name. --- .changelog/2910.txt | 3 +++ acceptance/go.mod | 2 +- acceptance/go.sum | 9 +++++++++ .../tests/connect/connect_inject_test.go | 18 ++++++++++++++++++ .../endpoints/endpoints_controller.go | 15 ++++++++++++--- control-plane/go.mod | 2 +- control-plane/go.sum | 13 +++++++++++++ 7 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 .changelog/2910.txt diff --git a/.changelog/2910.txt b/.changelog/2910.txt new file mode 100644 index 0000000000..8a1e40f6c2 --- /dev/null +++ b/.changelog/2910.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. +``` diff --git a/acceptance/go.mod b/acceptance/go.mod index 28d1491837..12fa4a80f7 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/gruntwork-io/terratest v0.31.2 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 - github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 + github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 diff --git a/acceptance/go.sum b/acceptance/go.sum index b264e243c9..c3645487ed 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -42,12 +42,14 @@ github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8 github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.5/go.mod h1:foo3aIXRQ90zFve3r0QiDsrjGDUwWhKl0ZOQy1CT14k= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/azure/auth v0.5.1/go.mod h1:ea90/jvmnAwDrSooLH4sRIehEPtG/EPUXavDh31MnA4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= @@ -64,6 +66,7 @@ github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQ github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -91,6 +94,7 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= @@ -192,6 +196,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -311,6 +316,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -343,6 +349,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -401,6 +408,8 @@ github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 h1:NPhzdwDho2r8pQv31oeGLlco7fnJ1i0WLYjtSXqWEck= github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= +github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index b44523235c..0badf69cc0 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -134,6 +134,24 @@ func TestConnectInject_CleanupKilledPods(t *testing.T) { require.Len(t, pods.Items, 1) podName := pods.Items[0].Name + // Ensure the token exists + if secure { + retry.Run(t, func(r *retry.R) { + tokens, _, err := consulClient.ACL().TokenListFiltered( + api.ACLTokenFilterOptions{ServiceName: "static-client"}, nil) + require.NoError(r, err) + // Ensure that the tokens exist. Note that we must iterate over the tokens and scan for the name, + // because older versions of Consul do not support the filtered query param and will return + // the full list of tokens instead. + count := 0 + for _, t := range tokens { + if len(t.ServiceIdentities) > 0 && t.ServiceIdentities[0].ServiceName == "static-client" { + count++ + } + } + require.Greater(r, count, 0) + }) + } logger.Logf(t, "force killing the static-client pod %q", podName) var gracePeriod int64 = 0 err = ctx.KubernetesClient(t).CoreV1().Pods(ns).Delete(context.Background(), podName, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod}) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index a9f10f970b..2a48f30bc9 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -987,9 +987,18 @@ func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, sv return nil } - tokens, _, err := apiClient.ACL().TokenList(&api.QueryOptions{ - Namespace: svc.Namespace, - }) + // Note that while the `TokenListFiltered` query below should only return a subset + // of tokens from the Consul servers, it will return an unfiltered list on older + // versions of Consul (because they do not yet support the query parameter). + // To be safe, we still need to iterate over tokens and assert the service name + // matches as well. + tokens, _, err := apiClient.ACL().TokenListFiltered( + api.ACLTokenFilterOptions{ + ServiceName: svc.Service, + }, + &api.QueryOptions{ + Namespace: svc.Namespace, + }) if err != nil { return fmt.Errorf("failed to get a list of tokens from Consul: %s", err) } diff --git a/control-plane/go.mod b/control-plane/go.mod index e935420730..8581b62e35 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -14,7 +14,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.4 - github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 + github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 diff --git a/control-plane/go.sum b/control-plane/go.sum index 8dca0b7203..8e391110ee 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -81,6 +81,7 @@ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -127,6 +128,7 @@ github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TR github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -216,6 +218,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -257,14 +260,20 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed h1:eM9tGgSqAZbm4Ndkp35Dg8YROT0dNH3ghTYu5pcUIAc= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 h1:NPhzdwDho2r8pQv31oeGLlco7fnJ1i0WLYjtSXqWEck= github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= +github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef h1:Vt5NSnXc+RslTxXH2pz7dCb3hnE33CD2TrBP5AIQtMg= github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924 h1:gkb6/ix0Tg1Th5FTjyq4QklLgrtIVQ/TUB0kbhIcPsY= @@ -423,6 +432,7 @@ github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQh github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -434,8 +444,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= @@ -455,6 +467,7 @@ github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otz github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 6e5d7d88a3a95d2a5a9e02df8cd722fa417d0952 Mon Sep 17 00:00:00 2001 From: David Yu Date: Fri, 8 Sep 2023 14:07:08 -0700 Subject: [PATCH 369/592] Update README.md - include Consul API Gateway as a use case (#2931) Update README.md --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d43a12b455..a52b2bcbf7 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,14 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). ## Features - * [**Consul Service Mesh**](https://www.consul.io/docs/k8s/connect): + * [**Consul Service Mesh**](https://developer.hashicorp.com/consul/docs/connect): Run Consul Service Mesh on Kubernetes. This feature injects Envoy sidecars and registers your Pods with Consul. + + * [**Consul API Gateway**](https://developer.hashicorp.com/consul/docs/api-gateway): + Run Consul API Gateway on Kubernetes to allow north/south traffic into Consul Service Mesh. - * [**Catalog Sync**](https://www.consul.io/docs/k8s/service-sync): + * [**Catalog Sync**](https://developer.hashicorp.com/consul/docs/k8s/service-sync): Sync Consul services into first-class Kubernetes services and vice versa. This enables Kubernetes to easily access external services and for non-Kubernetes nodes to easily discover and access Kubernetes services. @@ -47,7 +50,7 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). * A [Docker image `hashicorp/consul-k8s-control-plane`](https://hub.docker.com/r/hashicorp/consul-k8s-control-plane) is available. This can be used to manually run `consul-k8s-control-plane` within a scheduled environment. - * Consul K8s CLI, distributed as `consul-k8s`, can be used to install and uninstall Consul Kubernetes. See the [Consul K8s CLI Reference](https://www.consul.io/docs/k8s/k8s-cli) for more details on usage. + * Consul K8s CLI, distributed as `consul-k8s`, can be used to install and uninstall Consul Kubernetes. See the [Consul K8s CLI Reference](https://developer.hashicorp.com/consul/docs/k8s/k8s-cli) for more details on usage. ### Prerequisites @@ -89,7 +92,7 @@ for each subcommand. ### Helm -The Helm chart is ideal for those who prefer to use Helm for automation for either the installation or upgrade of Consul on Kubernetes. The chart supports multiple use cases of Consul on Kubernetes, depending on the values provided. Detailed installation instructions for Consul on Kubernetes are found [here](https://www.consul.io/docs/k8s/installation/overview). +The Helm chart is ideal for those who prefer to use Helm for automation for either the installation or upgrade of Consul on Kubernetes. The chart supports multiple use cases of Consul on Kubernetes, depending on the values provided. Detailed installation instructions for Consul on Kubernetes are found [here](https://developer.hashicorp.com/consul/docs/k8s/installation/install). 1. Add the HashiCorp Helm repository: @@ -112,7 +115,7 @@ The Helm chart is ideal for those who prefer to use Helm for automation for eith Please see the many options supported in the `values.yaml` file. These are also fully documented directly on the -[Consul website](https://www.consul.io/docs/platform/k8s/helm.html). +[Consul website](https://developer.hashicorp.com/consul/docs/k8s/helm). ## Tutorials From 2f84fa0b261082e39144d7cc543adc8f308b2925 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 11 Sep 2023 10:03:35 -0400 Subject: [PATCH 370/592] feat: add v2 endpoints controller (#2883) Implement the basic requirements of a new Endpoints controller that registers Services via Consul's V2 API. Further tests and TODOs will be addressed in follow-up changes. --- control-plane/connect-inject/common/common.go | 44 + .../connect-inject/common/common_test.go | 85 ++ .../constants/annotations_and_labels.go | 8 +- .../endpointsv2/endpoints_controller.go | 369 ++++++ .../endpoints_controller_ent_test.go | 28 + .../endpointsv2/endpoints_controller_test.go | 1081 +++++++++++++++++ .../inject-connect/v2controllers.go | 20 +- 7 files changed, 1632 insertions(+), 3 deletions(-) create mode 100644 control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go create mode 100644 control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go create mode 100644 control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index acee282739..b7946d78e0 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -9,10 +9,13 @@ import ( "strings" mapset "github.com/deckarep/golang-set" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" ) // DetermineAndValidatePort behaves as follows: @@ -116,3 +119,44 @@ func ShouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { func ConsulNodeNameFromK8sNode(nodeName string) string { return fmt.Sprintf("%s-virtual", nodeName) } + +// ******************** +// V2 Exclusive Common Code +// ******************** + +// ToProtoAny is a convenience function for converting proto.Message values to anypb.Any without error handling. +// This should _only_ be used in cases where a nil or valid proto.Message value is _guaranteed_, else it will panic. +// If the type of m is *anypb.Any, that value will be returned unmodified. +func ToProtoAny(m proto.Message) *anypb.Any { + switch v := m.(type) { + case nil: + return nil + case *anypb.Any: + return v + } + a, err := anypb.New(m) + if err != nil { + panic(fmt.Errorf("unexpected error: failed to convert proto message to anypb.Any: %w", err)) + } + return a +} + +// GetPortProtocol matches the Kubernetes EndpointPort.AppProtocol or ServicePort.AppProtocol (*string) to a supported +// Consul catalog port protocol. If nil or unrecognized, the default of `PROTOCOL_UNSPECIFIED` is returned. +func GetPortProtocol(appProtocol *string) pbcatalog.Protocol { + if appProtocol == nil { + return pbcatalog.Protocol_PROTOCOL_UNSPECIFIED + } + switch *appProtocol { + case "tcp": + return pbcatalog.Protocol_PROTOCOL_TCP + case "http": + return pbcatalog.Protocol_PROTOCOL_HTTP + case "http2": + return pbcatalog.Protocol_PROTOCOL_HTTP2 + case "grpc": + return pbcatalog.Protocol_PROTOCOL_GRPC + } + // If unrecognized or empty string, return default + return pbcatalog.Protocol_PROTOCOL_UNSPECIFIED +} diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index 6f623b28db..6cbbab5b88 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -7,12 +7,17 @@ import ( "testing" mapset "github.com/deckarep/golang-set" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" ) func TestCommonDetermineAndValidatePort(t *testing.T) { @@ -314,3 +319,83 @@ func TestShouldIgnore(t *testing.T) { }) } } + +func TestToProtoAny(t *testing.T) { + t.Parallel() + + t.Run("nil gets nil", func(t *testing.T) { + require.Nil(t, ToProtoAny(nil)) + }) + + t.Run("anypb.Any gets same value", func(t *testing.T) { + testMsg := &pbresource.Resource{Id: &pbresource.ID{Name: "foo"}} + testAny, err := anypb.New(testMsg) + require.NoError(t, err) + + require.Equal(t, testAny, ToProtoAny(testAny)) + }) + + t.Run("valid proto is successfully serialized", func(t *testing.T) { + testMsg := &pbresource.Resource{Id: &pbresource.ID{Name: "foo"}} + testAny, err := anypb.New(testMsg) + require.NoError(t, err) + + if diff := cmp.Diff(testAny, ToProtoAny(testMsg), protocmp.Transform()); diff != "" { + t.Errorf("unexpected difference:\n%v", diff) + } + }) +} + +func TestGetPortProtocol(t *testing.T) { + t.Parallel() + toStringPtr := func(s string) *string { + return &s + } + cases := []struct { + name string + input *string + expected pbcatalog.Protocol + }{ + { + name: "nil gets UNSPECIFIED", + input: nil, + expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + { + name: "tcp gets TCP", + input: toStringPtr("tcp"), + expected: pbcatalog.Protocol_PROTOCOL_TCP, + }, + { + name: "http gets HTTP", + input: toStringPtr("http"), + expected: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + name: "http2 gets HTTP2", + input: toStringPtr("http2"), + expected: pbcatalog.Protocol_PROTOCOL_HTTP2, + }, + { + name: "grpc gets GRPC", + input: toStringPtr("grpc"), + expected: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + { + name: "case sensitive", + input: toStringPtr("gRPC"), + expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + { + name: "unknown gets UNSPECIFIED", + input: toStringPtr("foo"), + expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + actual := GetPortProtocol(tt.input) + require.Equal(t, tt.expected, actual) + }) + } +} diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 76b83eaf62..5c14cb15f2 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -203,6 +203,7 @@ const ( Enabled = "enabled" // ManagedByValue is the value for keyManagedBy. + //TODO(zalimeni) rename this to ManagedByLegacyEndpointsValue. ManagedByValue = "consul-k8s-endpoints-controller" ) @@ -220,8 +221,13 @@ const ( // a pod after an injection is done. KeyMeshInjectStatus = "consul.hashicorp.com/mesh-inject-status" + // ManagedByEndpointsValue is used in Consul metadata to identify the manager + // of resources. The 'v2' suffix is used to differentiate from the legacy + // endpoints controller of the same name. + ManagedByEndpointsValue = "consul-k8s-endpoints-controller-v2" + // ManagedByPodValue is used in Consul metadata to identify the manager - // of this resource. + // of resources. ManagedByPodValue = "consul-k8s-pod-controller" ) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go new file mode 100644 index 0000000000..ad32510861 --- /dev/null +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -0,0 +1,369 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package endpointsv2 + +import ( + "context" + "net" + "strings" + + mapset "github.com/deckarep/golang-set" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/go-multierror" +) + +const ( + metaKeyManagedBy = "managed-by" + kindReplicaSet = "ReplicaSet" +) + +type Controller struct { + client.Client + // ConsulServerConnMgr is the watcher for the Consul server addresses used to create Consul API v2 clients. + ConsulServerConnMgr consul.ServerConnectionManager + // Only endpoints in the AllowK8sNamespacesSet are reconciled. + AllowK8sNamespacesSet mapset.Set + // Endpoints in the DenyK8sNamespacesSet are ignored. + DenyK8sNamespacesSet mapset.Set + // EnableConsulPartitions indicates that a user is running Consul Enterprise. + EnableConsulPartitions bool + // ConsulPartition is the Consul Partition to which this controller belongs. + ConsulPartition string + // EnableConsulNamespaces indicates that a user is running Consul Enterprise. + EnableConsulNamespaces bool + // ConsulDestinationNamespace is the name of the Consul namespace to create + // all config entries in. If EnableNSMirroring is true this is ignored. + ConsulDestinationNamespace string + // EnableNSMirroring causes Consul namespaces to be created to match the + // k8s namespace of any config entry custom resource. Config entries will + // be created in the matching Consul namespace. + EnableNSMirroring bool + // NSMirroringPrefix is an optional prefix that can be added to the Consul + // namespaces created while mirroring. For example, if it is set to "k8s-", + // then the k8s `default` namespace will be mirrored in Consul's + // `k8s-default` namespace. + NSMirroringPrefix string + + Log logr.Logger + + Scheme *runtime.Scheme + context.Context +} + +func (r *Controller) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Endpoints{}). + Complete(r) +} + +// Reconcile reads the state of an Endpoints object for a Kubernetes Service and reconciles Consul services which +// correspond to the Kubernetes Service. These events are driven by changes to the Pods backing the Kube service. +func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var errs error + var endpoints corev1.Endpoints + var service corev1.Service + + // Ignore the request if the namespace of the endpoint is not allowed. + if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + return ctrl.Result{}, nil + } + + // Create Consul resource service client for this reconcile. + resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) + if err != nil { + r.Log.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + + // If the Endpoints object has been deleted (and we get an IsNotFound error), + // we need to deregister that service from Consul. + err = r.Client.Get(ctx, req.NamespacedName, &endpoints) + if k8serrors.IsNotFound(err) { + err = r.deregisterService(ctx, resourceClient, req.Name, r.getConsulNamespace(req.Namespace), r.getConsulPartition()) + return ctrl.Result{}, err + } else if err != nil { + r.Log.Error(err, "failed to get Endpoints", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + r.Log.Info("retrieved Endpoints", "name", req.Name, "ns", req.Namespace) + + // We expect this to succeed if the Endpoints fetch for the Service succeeded. + err = r.Client.Get(r.Context, types.NamespacedName{Name: endpoints.Name, Namespace: endpoints.Namespace}, &service) + if err != nil { + r.Log.Error(err, "failed to get Service", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + r.Log.Info("retrieved Service", "name", req.Name, "ns", req.Namespace) + + workloadSelector, err := r.getWorkloadSelectorFromEndpoints(ctx, &ClientPodFetcher{client: r.Client}, &endpoints) + if err != nil { + errs = multierror.Append(errs, err) + } + + //TODO: Maybe check service-enable label here on service/deployments/other pod owners + if err = r.registerService(ctx, resourceClient, service, workloadSelector); err != nil { + errs = multierror.Append(errs, err) + } + + return ctrl.Result{}, errs +} + +// getWorkloadSelectorFromEndpoints calculates a Consul service WorkloadSelector from Endpoints based on pod names and +// owners. +func (r *Controller) getWorkloadSelectorFromEndpoints(ctx context.Context, pf PodFetcher, endpoints *corev1.Endpoints) (*pbcatalog.WorkloadSelector, error) { + podPrefixes := make(map[string]any) + podExactNames := make(map[string]any) + var errs error + for address := range allAddresses(endpoints.Subsets) { + if address.TargetRef != nil && address.TargetRef.Kind == "Pod" { + podName := types.NamespacedName{Name: address.TargetRef.Name, Namespace: endpoints.Namespace} + + // Accumulate owner prefixes and exact pod names for Consul workload selector. + // If this pod is already covered by a known owner prefix, skip it. + // If not, fetch the owner. If the owner has a unique prefix, add it to known prefixes. + // If not, add the pod name to exact name matches. + maybePodOwnerPrefix := getOwnerPrefixFromPodName(podName.Name) + if _, ok := podPrefixes[maybePodOwnerPrefix]; !ok { + pod, err := pf.GetPod(ctx, podName) + if err != nil { + r.Log.Error(err, "failed to get pod", "name", podName.Name, "ns", endpoints.Namespace) + errs = multierror.Append(errs, err) + continue + } + // Add to workload selector values. + // Pods can appear more than once in Endpoints subsets, so we use a set for exact names as well. + if prefix := getOwnerPrefixFromPod(pod); prefix != "" { + podPrefixes[prefix] = true + } else { + podExactNames[podName.Name] = true + } + } + } + } + return getWorkloadSelector(podPrefixes, podExactNames), errs +} + +// allAddresses combines all Endpoints subset addresses to a single set. Service registration by this controller +// operates independent of health, and an address can appear in multiple subsets if it has a mix of ready and not-ready +// ports, so we combine them here to simplify iteration. +func allAddresses(subsets []corev1.EndpointSubset) map[corev1.EndpointAddress]any { + m := make(map[corev1.EndpointAddress]any) + for _, sub := range subsets { + for _, readyAddress := range sub.Addresses { + m[readyAddress] = true + } + for _, notReadyAddress := range sub.NotReadyAddresses { + m[notReadyAddress] = true + } + } + return m +} + +// getOwnerPrefixFromPodName extracts the owner name prefix from a pod name. +func getOwnerPrefixFromPodName(podName string) string { + podNameParts := strings.Split(podName, "-") + return strings.Join(podNameParts[:len(podNameParts)-1], "-") +} + +// getOwnerPrefixFromPod returns the common name prefix of the pod, if the pod is a member of a set with a unique name +// prefix. Currently, this only applies to ReplicaSets. +// +// We have to fetch the owner and check its type because pod names cannot be disambiguated from pod owner names due to +// the `-` delimiter and unique ID parts also being valid name components. +// +// If the pod owner does not have a unique name, the empty string is returned. +func getOwnerPrefixFromPod(pod *corev1.Pod) string { + for _, ref := range pod.OwnerReferences { + if ref.Kind == "ReplicaSet" { + return ref.Name + } + } + return "" +} + +// registerService creates a Consul service registration from the provided Kuberetes service and endpoint information. +func (r *Controller) registerService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, service corev1.Service, selector *pbcatalog.WorkloadSelector) error { + serviceResource := r.getServiceResource( + &pbcatalog.Service{ + Workloads: selector, + Ports: getServicePorts(service), + VirtualIps: r.getServiceVIPs(service), + }, + service.Name, // Consul and Kubernetes service name will always match + r.getConsulNamespace(service.Namespace), + r.getConsulPartition(), + getServiceMeta(service), + ) + + r.Log.Info("registering service with Consul", getLogFieldsForResource(serviceResource.Id)...) + //TODO: Maybe attempt to debounce redundant writes. For now, we blindly rewrite state on each reconcile. + _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: serviceResource}) + if err != nil { + r.Log.Error(err, "failed to register service", getLogFieldsForResource(serviceResource.Id)...) + return err + } + + return nil +} + +// getServiceResource converts the given Consul service and metadata as a Consul resource API record. +func (r *Controller) getServiceResource(svc *pbcatalog.Service, name, namespace, partition string, meta map[string]string) *pbresource.Resource { + return &pbresource.Resource{ + Id: getServiceID(name, namespace, partition), + Data: common.ToProtoAny(svc), + Metadata: meta, + } +} + +func getServiceID(name, namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + }, + } +} + +// getServicePorts converts Kubernetes Service ports data into Consul service ports. +func getServicePorts(service corev1.Service) []*pbcatalog.ServicePort { + ports := make([]*pbcatalog.ServicePort, 0, len(service.Spec.Ports)+1) + + for _, p := range service.Spec.Ports { + ports = append(ports, &pbcatalog.ServicePort{ + VirtualPort: uint32(p.Port), + //TODO: If the value is a number, infer the correct name value based on + // the most prevalent endpoint subset for the port (best-effot, inspect a pod). + TargetPort: p.TargetPort.String(), + Protocol: common.GetPortProtocol(p.AppProtocol), + }) + } + + //TODO: Error check reserved "mesh" target port + + // Append Consul service mesh port in addition to discovered ports. + //TODO: Maybe omit if zero mesh ports present in service endpoints, or if some + // use of mesh-inject/other label should cause this to be excluded. + ports = append(ports, &pbcatalog.ServicePort{ + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }) + + return ports +} + +// getServiceVIPs returns the VIPs to associate with the registered Consul service. This will contain the Kubernetes +// Service ClusterIP if it exists. +// +// Note that we always provide this data regardless of whether TProxy is enabled, deferring to individual proxy configs +// to decide whether it's used. +func (r *Controller) getServiceVIPs(service corev1.Service) []string { + if parsedIP := net.ParseIP(service.Spec.ClusterIP); parsedIP == nil { + r.Log.Info("skipping service registration virtual IP assignment due to invalid or unset ClusterIP", "name", service.Name, "ns", service.Namespace, "ip", service.Spec.ClusterIP) + return nil + } + return []string{service.Spec.ClusterIP} +} + +func getServiceMeta(service corev1.Service) map[string]string { + meta := map[string]string{ + constants.MetaKeyKubeNS: service.Namespace, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + } + //TODO: Support arbitrary meta injection via annotation? (see v1) + return meta +} + +func getWorkloadSelector(podPrefixes, podExactNames map[string]any) *pbcatalog.WorkloadSelector { + workloads := &pbcatalog.WorkloadSelector{} + for v := range podPrefixes { + workloads.Prefixes = append(workloads.Prefixes, v) + } + for v := range podExactNames { + workloads.Names = append(workloads.Names, v) + } + return workloads +} + +// deregisterService deletes the service resource corresponding to the given name and namespace from Consul. +// This operation is idempotent and can be executed for non-existent services. +func (r *Controller) deregisterService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, name, namespace, partition string) error { + _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{ + Id: getServiceID(name, namespace, partition), + }) + return err +} + +// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace +// depending on Consul Namespaces being enabled and the value of namespace mirroring. +func (r *Controller) getConsulNamespace(kubeNamespace string) string { + ns := namespaces.ConsulNamespace( + kubeNamespace, + r.EnableConsulNamespaces, + r.ConsulDestinationNamespace, + r.EnableNSMirroring, + r.NSMirroringPrefix, + ) + + // TODO: remove this if and when the default namespace of resources is no longer required to be set explicitly. + if ns == "" { + ns = constants.DefaultConsulNS + } + return ns +} + +func (r *Controller) getConsulPartition() string { + if !r.EnableConsulPartitions || r.ConsulPartition == "" { + return constants.DefaultConsulPartition + } + return r.ConsulPartition +} + +func getLogFieldsForResource(id *pbresource.ID) []any { + return []any{ + "name", id.Name, + "ns", id.Tenancy.Namespace, + "partition", id.Tenancy.Partition, + } +} + +// PodFetcher fetches pods by NamespacedName. This interface primarily exists for testing. +type PodFetcher interface { + GetPod(context.Context, types.NamespacedName) (*corev1.Pod, error) +} + +// ClientPodFetcher wraps a Kubernetes client to implement PodFetcher. This is the only implementation outside of tests. +type ClientPodFetcher struct { + client client.Client +} + +func (c *ClientPodFetcher) GetPod(ctx context.Context, name types.NamespacedName) (*corev1.Pod, error) { + var pod corev1.Pod + err := c.client.Get(ctx, name, &pod) + if err != nil { + return nil, err + } + return &pod, nil +} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go new file mode 100644 index 0000000000..6d4e5460c4 --- /dev/null +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package endpointsv2 + +import ( + "testing" +) + +// TODO(zalimeni) +// Tests new Service registration in a non-default NS and Partition with namespaces set to mirroring +func TestReconcile_CreateService_WithNamespaces(t *testing.T) { + +} + +// TODO(zalimeni) +// Tests updating Service registration in a non-default NS and Partition with namespaces set to mirroring +func TestReconcile_UpdateService_WithNamespaces(t *testing.T) { + +} + +// TODO(zalimeni) +// Tests removing Service registration in a non-default NS and Partition with namespaces set to mirroring +func TestReconcile_DeleteService_WithNamespaces(t *testing.T) { + +} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go new file mode 100644 index 0000000000..706a9d60a0 --- /dev/null +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -0,0 +1,1081 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package endpointsv2 + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/anypb" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/go-uuid" +) + +var ( + appProtocolHttp = "http" + appProtocolHttp2 = "http2" + appProtocolGrpc = "grpc" +) + +type reconcileCase struct { + name string + svcName string + k8sObjects func() []runtime.Object + existingResource *pbresource.Resource + expectedResource *pbresource.Resource + targetConsulNs string + targetConsulPartition string + expErr string +} + +// TODO: Allow/deny namespaces for reconcile tests +// TODO: ConsulDestinationNamespace and EnableNSMirroring +/- prefix + +func TestReconcile_CreateService(t *testing.T) { + t.Parallel() + cases := []reconcileCase{ + { + // In this test, we expect the same service registration as the "basic" + // case, but without any workload selector values due to missing endpoints. + name: "Empty endpoints", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{}, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 9090, + TargetPort: intstr.FromString("my-grpc-port"), + AppProtocol: &appProtocolGrpc, + }, + { + Name: "other", + Port: 10001, + TargetPort: intstr.FromString("10001"), + // no protocol specified + }, + }, + }, + } + return []runtime.Object{endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + VirtualPort: 9090, + TargetPort: "my-grpc-port", + Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + { + VirtualPort: 10001, + TargetPort: "10001", + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{}, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + { + name: "Basic endpoints", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + pod2 := createServicePod("DaemonSet", "service-created-ds", "12345") + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1, pod2), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + AppProtocol: &appProtocolHttp, + Port: 2345, + }, + { + Name: "my-grpc-port", + AppProtocol: &appProtocolGrpc, + Port: 6789, + }, + { + Name: "10001", + Port: 10001, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 9090, + TargetPort: intstr.FromString("my-grpc-port"), + AppProtocol: &appProtocolGrpc, + }, + { + Name: "other", + Port: 10001, + TargetPort: intstr.FromString("10001"), + // no protocol specified + }, + }, + }, + } + return []runtime.Object{pod1, pod2, endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + VirtualPort: 9090, + TargetPort: "my-grpc-port", + Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + { + VirtualPort: 10001, + TargetPort: "10001", + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + Names: []string{"service-created-ds-12345"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + { + name: "Unhealthy endpoints should be registered", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + // Split addresses between ready and not-ready + Addresses: addressesForPods(pod1), + NotReadyAddresses: addressesForPods(pod2), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + AppProtocol: &appProtocolHttp, + Port: 2345, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + }, + }, + } + return []runtime.Object{pod1, pod2, endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + // Both replicasets (ready and not ready) should be present + Prefixes: []string{ + "service-created-rs-abcde", + "service-created-rs-fghij", + }, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + { + name: "Pods with only some service ports should be registered", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + // Two separate endpoint subsets w/ each of 2 ports served by a different replicaset + { + Addresses: addressesForPods(pod1), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + AppProtocol: &appProtocolHttp, + Port: 2345, + }, + }, + }, + { + Addresses: addressesForPods(pod2), + Ports: []corev1.EndpointPort{ + { + Name: "my-grpc-port", + AppProtocol: &appProtocolGrpc, + Port: 6789, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 9090, + TargetPort: intstr.FromString("my-grpc-port"), + AppProtocol: &appProtocolGrpc, + }, + }, + }, + } + return []runtime.Object{pod1, pod2, endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + VirtualPort: 9090, + TargetPort: "my-grpc-port", + Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + // Both replicasets should be present even though neither serves both ports + Prefixes: []string{ + "service-created-rs-abcde", + "service-created-rs-fghij", + }, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + runReconcileCase(t, tc) + }) + } +} + +func TestReconcile_UpdateService(t *testing.T) { + t.Parallel() + cases := []reconcileCase{ + { + name: "Pods changed", + svcName: "service-updated", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno") + pod3 := createServicePod("DaemonSet", "service-created-ds", "12345") + pod4 := createServicePod("DaemonSet", "service-created-ds", "34567") + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-updated", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1, pod2, pod3, pod4), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + AppProtocol: &appProtocolHttp, + Port: 2345, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-updated", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + }, + }, + } + return []runtime.Object{pod1, pod2, pod3, pod4, endpoints, service} + }, + existingResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{ + "service-created-rs-abcde", // Retained + "service-created-rs-fghij", // Removed + }, + Names: []string{ + "service-created-ds-12345", // Retained + "service-created-ds-23456", // Removed + }, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + + Prefixes: []string{ + "service-created-rs-abcde", // Retained + "service-created-rs-klmno", // New + }, + Names: []string{ + "service-created-ds-12345", // Retained + "service-created-ds-34567", // New + }, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + { + name: "Service ports changed", + svcName: "service-updated", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + pod2 := createServicePod("DaemonSet", "service-created-ds", "12345") + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-updated", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1, pod2), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + AppProtocol: &appProtocolHttp, + Port: 2345, + }, + { + Name: "my-grpc-port", + AppProtocol: &appProtocolHttp, + Port: 6789, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-updated", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + TargetPort: intstr.FromString("new-http-port"), + AppProtocol: &appProtocolHttp2, + }, + { + Name: "api", + Port: 9091, + TargetPort: intstr.FromString("my-grpc-port"), + AppProtocol: &appProtocolGrpc, + }, + }, + }, + } + return []runtime.Object{pod1, pod2, endpoints, service} + }, + existingResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-updated", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + VirtualPort: 9090, + TargetPort: "my-grpc-port", + Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + { + VirtualPort: 10001, + TargetPort: "10001", + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + Names: []string{"service-created-ds-12345"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-updated", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "new-http-port", // Updated + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP2, // Updated + }, + { + VirtualPort: 9091, // Updated + TargetPort: "my-grpc-port", + Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + // Port 10001 removed + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + Names: []string{"service-created-ds-12345"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + runReconcileCase(t, tc) + }) + } +} + +func TestReconcile_DeleteService(t *testing.T) { + t.Parallel() + cases := []reconcileCase{ + { + name: "Basic Endpoints not found (service deleted)", + svcName: "service-deleted", + existingResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + Names: []string{"service-created-ds-12345"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + runReconcileCase(t, tc) + }) + } +} + +func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + type testCase struct { + name string + endpoints *corev1.Endpoints + responses map[types.NamespacedName]*corev1.Pod + expected *pbcatalog.WorkloadSelector + mockFn func(*testing.T, *MockPodFetcher) + } + + rsPods := []*corev1.Pod{ + createServicePod(kindReplicaSet, "svc-rs-abcde", "12345"), + createServicePod(kindReplicaSet, "svc-rs-abcde", "23456"), + createServicePod(kindReplicaSet, "svc-rs-abcde", "34567"), + createServicePod(kindReplicaSet, "svc-rs-fghij", "12345"), + createServicePod(kindReplicaSet, "svc-rs-fghij", "23456"), + createServicePod(kindReplicaSet, "svc-rs-fghij", "34567"), + } + otherPods := []*corev1.Pod{ + createServicePod("DaemonSet", "svc-ds", "12345"), + createServicePod("DaemonSet", "svc-ds", "23456"), + createServicePod("DaemonSet", "svc-ds", "34567"), + createServicePod("StatefulSet", "svc-ss", "12345"), + createServicePod("StatefulSet", "svc-ss", "23456"), + createServicePod("StatefulSet", "svc-ss", "34567"), + } + podsByName := make(map[types.NamespacedName]*corev1.Pod) + for _, p := range rsPods { + podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p + } + for _, p := range otherPods { + podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p + } + + cases := []testCase{ + { + name: "Pod is fetched once per ReplicaSet", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(rsPods...), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + AppProtocol: &appProtocolHttp, + Port: 2345, + }, + }, + }, + }, + }, + responses: podsByName, + expected: getWorkloadSelector( + // Selector should consist of prefixes only. + map[string]any{ + "svc-rs-abcde": true, + "svc-rs-fghij": true, + }, + map[string]any{}), + mockFn: func(t *testing.T, pf *MockPodFetcher) { + // Assert called once per set. + require.Equal(t, 2, len(pf.calls)) + }, + }, + { + name: "Pod is fetched once per other pod owner type", + endpoints: &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(otherPods...), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + AppProtocol: &appProtocolHttp, + Port: 2345, + }, + }, + }, + }, + }, + responses: podsByName, + expected: getWorkloadSelector( + // Selector should consist of exact name matches only. + map[string]any{}, + map[string]any{ + "svc-ds-12345": true, + "svc-ds-23456": true, + "svc-ds-34567": true, + "svc-ss-12345": true, + "svc-ss-23456": true, + "svc-ss-34567": true, + }), + mockFn: func(t *testing.T, pf *MockPodFetcher) { + // Assert called once per pod. + require.Equal(t, len(otherPods), len(pf.calls)) + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Create mock pod fetcher. + pf := MockPodFetcher{responses: tc.responses} + + // Create the Endpoints controller. + ep := &Controller{ + Log: logrtest.New(t), + } + + resp, err := ep.getWorkloadSelectorFromEndpoints(ctx, &pf, tc.endpoints) + require.NoError(t, err) + + // We don't care about order, so configure cmp.Diff to ignore slice order. + sorter := func(a, b string) bool { return a < b } + if diff := cmp.Diff(tc.expected, resp, protocmp.Transform(), cmpopts.SortSlices(sorter)); diff != "" { + t.Errorf("unexpected difference:\n%v", diff) + } + tc.mockFn(t, &pf) + }) + } +} + +type MockPodFetcher struct { + calls []types.NamespacedName + responses map[types.NamespacedName]*corev1.Pod +} + +func (m *MockPodFetcher) GetPod(_ context.Context, name types.NamespacedName) (*corev1.Pod, error) { + m.calls = append(m.calls, name) + if v, ok := m.responses[name]; !ok { + panic(fmt.Errorf("test is missing response for passed pod name: %v", name)) + } else { + return v, nil + } +} + +func runReconcileCase(t *testing.T, tc reconcileCase) { + t.Helper() + + // Create fake k8s client + var k8sObjects []runtime.Object + if tc.k8sObjects != nil { + k8sObjects = tc.k8sObjects() + } + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test Consul server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + + // Create the Endpoints controller. + ep := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + } + resourceClient, err := consul.NewResourceServiceClient(ep.ConsulServerConnMgr) + require.NoError(t, err) + + // Default ns and partition if not specified in test. + if tc.targetConsulNs == "" { + tc.targetConsulNs = constants.DefaultConsulNS + } + if tc.targetConsulPartition == "" { + tc.targetConsulPartition = constants.DefaultConsulPartition + } + + // If existing resource specified, create it and ensure it exists. + if tc.existingResource != nil { + writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} + _, err = resourceClient.Write(context.Background(), writeReq) + require.NoError(t, err) + test.ResourceHasPersisted(t, resourceClient, tc.existingResource.Id) + } + + // Run actual reconcile and verify results. + resp, err := ep.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: tc.svcName, + Namespace: tc.targetConsulNs, + }, + }) + if tc.expErr != "" { + require.ErrorContains(t, err, tc.expErr) + } else { + require.NoError(t, err) + } + require.False(t, resp.Requeue) + + expectedServiceMatches(t, resourceClient, tc.svcName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) + +} + +func expectedServiceMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { + req := &pbresource.ReadRequest{Id: getServiceID(name, namespace, partition)} + + res, err := client.Read(context.Background(), req) + + if expectedResource == nil { + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + return + } + + require.NoError(t, err) + require.NotNil(t, res) + require.NotNil(t, res.GetResource().GetData()) + + expectedService := &pbcatalog.Service{} + err = anypb.UnmarshalTo(expectedResource.Data, expectedService, proto.UnmarshalOptions{}) + require.NoError(t, err) + + actualService := &pbcatalog.Service{} + err = res.GetResource().GetData().UnmarshalTo(actualService) + require.NoError(t, err) + + if diff := cmp.Diff(expectedService, actualService, protocmp.Transform()); diff != "" { + t.Errorf("unexpected difference:\n%v", diff) + } +} + +func createServicePodOwnedBy(ownerKind, ownerName string) *corev1.Pod { + return createServicePod(ownerKind, ownerName, randomKubernetesId()) +} + +func createServicePod(ownerKind, ownerName, podId string) *corev1.Pod { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s", ownerName, podId), + Namespace: "default", + Labels: map[string]string{}, + Annotations: map[string]string{ + constants.AnnotationConsulK8sVersion: "1.3.0", + }, + OwnerReferences: []metav1.OwnerReference{ + { + Name: ownerName, + Kind: ownerKind, + }, + }, + }, + } + return pod +} + +func addressesForPods(pods ...*corev1.Pod) []corev1.EndpointAddress { + var addresses []corev1.EndpointAddress + for i, p := range pods { + addresses = append(addresses, corev1.EndpointAddress{ + IP: fmt.Sprintf("1.2.3.%d", i), + TargetRef: &corev1.ObjectReference{ + Kind: "Pod", + Name: p.Name, + Namespace: p.Namespace, + }, + }) + } + return addresses +} + +func randomKubernetesId() string { + u, err := uuid.GenerateUUID() + if err != nil { + panic(err) + } + return u[:5] +} diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index a9f29cd6a5..df3830a799 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -5,11 +5,11 @@ package connectinject import ( "context" - "github.com/hashicorp/consul-server-connection-manager/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -59,7 +59,23 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - // TODO: V2 Endpoints Controller + if err := (&endpointsv2.Controller{ + Client: mgr.GetClient(), + ConsulServerConnMgr: watcher, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + EnableConsulPartitions: c.flagEnablePartitions, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + Log: ctrl.Log.WithName("controller").WithName("endpoints"), + Scheme: mgr.GetScheme(), + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", endpointsv2.Controller{}) + return err + } // TODO: Nodes Controller From da909d76fcf808be62b71770f0cb2e2cb0ae29e6 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Mon, 11 Sep 2023 09:43:56 -0500 Subject: [PATCH 371/592] Tests: Fix/improve tests with Restricted PSA enforcement (#2780) * tests: Respect UseAppNamespace in ConnectHelper * tests: Auto-configure restricted PSA enforcement when enabled --------- Co-authored-by: Paul Glass --- .../framework/connhelper/connect_helper.go | 6 ++-- acceptance/framework/consul/helm_cluster.go | 33 +++++++++++++++++++ acceptance/framework/flags/flags.go | 12 +++---- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 5f140c3368..3f978e1d30 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -158,11 +158,11 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-openshift-inject") } } else { - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if c.Cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } } // Check that both static-server and static-client have been injected and diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 81787ebfc3..0525c5d442 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -64,6 +64,10 @@ func NewHelmCluster( cfg *config.TestConfig, releaseName string, ) *HelmCluster { + if cfg.EnableRestrictedPSAEnforcement { + configureNamespace(t, ctx.KubernetesClient(t), cfg, ctx.KubectlOptions(t).Namespace) + } + if cfg.EnablePodSecurityPolicies { configurePodSecurityPolicies(t, ctx.KubernetesClient(t), cfg, ctx.KubectlOptions(t).Namespace) } @@ -521,6 +525,35 @@ func createOrUpdateLicenseSecret(t *testing.T, client kubernetes.Interface, cfg CreateK8sSecret(t, client, cfg, namespace, config.LicenseSecretName, config.LicenseSecretKey, cfg.EnterpriseLicense) } +func configureNamespace(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace string) { + ctx := context.Background() + + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + Labels: map[string]string{}, + }, + } + if cfg.EnableRestrictedPSAEnforcement { + ns.ObjectMeta.Labels["pod-security.kubernetes.io/enforce"] = "restricted" + ns.ObjectMeta.Labels["pod-security.kubernetes.io/enforce-version"] = "latest" + } + + _, createErr := client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + if createErr == nil { + logger.Logf(t, "Created namespace %s", namespace) + return + } + + _, updateErr := client.CoreV1().Namespaces().Update(ctx, ns, metav1.UpdateOptions{}) + if updateErr == nil { + logger.Logf(t, "Updated namespace %s", namespace) + return + } + + require.Failf(t, "Failed to create or update namespace", "Namespace=%s, CreateError=%s, UpdateError=%s", namespace, createErr, updateErr) +} + // configureSCCs creates RoleBindings that bind the default service account to cluster roles // allowing access to the anyuid and privileged Security Context Constraints on OpenShift. func configureSCCs(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace string) { diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index ce8e1ea726..f4350f2fcb 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -117,13 +117,13 @@ func (t *TestFlags) init() { flag.BoolVar(&t.flagEnableCNI, "enable-cni", false, "If true, the test suite will run tests with consul-cni plugin enabled. "+ "In general, this will only run against tests that are mesh related (connect, mesh-gateway, peering, etc") + flag.BoolVar(&t.flagEnableRestrictedPSAEnforcement, "enable-restricted-psa-enforcement", false, - "If true, this indicates that Consul is being run in a namespace with restricted PSA enforcement enabled. "+ - "The tests do not configure Consul's namespace with PSA enforcement enabled. This must configured before tests are run. "+ - "The CNI and test applications need more privilege than is allowed in a restricted namespace. "+ - "When set, the CNI will be deployed into the kube-system namespace, and in supported test cases, applications "+ - "are deployed, by default, into a namespace named '-apps' instead of being deployed into the "+ - "Consul namespace.") + "If true, deploy Consul into a namespace with restricted PSA enforcement enabled. "+ + "The Consul namespaces (-kube-namespaces) will be configured with restricted PSA enforcement. "+ + "The CNI and test applications are deployed in different namespaces because they need more privilege than is allowed in a restricted namespace. "+ + "The CNI will be deployed into the kube-system namespace, which is a privileged namespace that should always exist. "+ + "Test applications are deployed, by default, into a namespace named '-apps' instead of the Consul namespace.") flag.BoolVar(&t.flagEnableTransparentProxy, "enable-transparent-proxy", false, "If true, the test suite will run tests with transparent proxy enabled. "+ From 6e8831fc492a6e9e0689d3fdf6b4beb6494c77c8 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 11 Sep 2023 10:51:47 -0400 Subject: [PATCH 372/592] feat: add v2 pod controller healthstatus and proxyconfiguration (#2911) --- control-plane/connect-inject/common/common.go | 17 + .../connect-inject/constants/constants.go | 11 + .../endpoints/endpoints_controller.go | 21 +- .../endpoints_controller_ent_test.go | 42 +- .../endpoints/endpoints_controller_test.go | 204 ++-- .../controllers/pod/pod_controller.go | 354 ++++++- .../controllers/pod/pod_controller_test.go | 987 ++++++++++++++---- control-plane/consul/resource_client_test.go | 7 +- control-plane/go.mod | 2 +- control-plane/go.sum | 17 +- .../inject-connect/v2controllers.go | 21 +- 11 files changed, 1260 insertions(+), 423 deletions(-) diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index b7946d78e0..1e7cf5415c 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -13,6 +13,7 @@ import ( "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" @@ -160,3 +161,19 @@ func GetPortProtocol(appProtocol *string) pbcatalog.Protocol { // If unrecognized or empty string, return default return pbcatalog.Protocol_PROTOCOL_UNSPECIFIED } + +// PortValueFromIntOrString returns the integer port value from the port that can be +// a named port, an integer string (e.g. "80"), or an integer. If the port is a named port, +// this function will attempt to find the value from the containers of the pod. +func PortValueFromIntOrString(pod corev1.Pod, port intstr.IntOrString) (uint32, error) { + if port.Type == intstr.Int { + return uint32(port.IntValue()), nil + } + + // Otherwise, find named port or try to parse the string as an int. + portVal, err := PortValue(pod, port.StrVal) + if err != nil { + return 0, err + } + return uint32(portVal), nil +} diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 506654dfe1..f9dccd4f14 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -13,6 +13,9 @@ const ( // DefaultConsulPartition is the default Consul partition name. DefaultConsulPartition = "default" + // DefaultConsulPeer is the name used to refer to resources that are in the same cluster. + DefaultConsulPeer = "local" + // ProxyDefaultInboundPort is the default inbound port for the proxy. ProxyDefaultInboundPort = 20000 @@ -39,4 +42,12 @@ const ( // DefaultGracefulShutdownPath is the default path that consul-dataplane uses for graceful shutdown. DefaultGracefulShutdownPath = "/graceful_shutdown" + + // ConsulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. + ConsulKubernetesCheckType = "kubernetes-readiness" + + // ConsulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. + ConsulKubernetesCheckName = "Kubernetes Readiness Check" + + KubernetesSuccessReasonMsg = "Kubernetes health checks passing" ) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 2a48f30bc9..136934a451 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -44,7 +44,6 @@ const ( terminatingGateway = "terminating-gateway" ingressGateway = "ingress-gateway" - kubernetesSuccessReasonMsg = "Kubernetes health checks passing" envoyPrometheusBindAddr = "envoy_prometheus_bind_addr" envoyTelemetryCollectorBindSocketDir = "envoy_telemetry_collector_bind_socket_dir" defaultNS = "default" @@ -57,12 +56,6 @@ const ( // This address does not need to be routable as this node is ephemeral, and we're only providing it because // Consul's API currently requires node address to be provided when registering a node. consulNodeAddress = "127.0.0.1" - - // consulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. - consulKubernetesCheckType = "kubernetes-readiness" - - // consulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. - consulKubernetesCheckName = "Kubernetes Readiness Check" ) type Controller struct { @@ -469,8 +462,8 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Service: service, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, svcID), - Name: consulKubernetesCheckName, - Type: consulKubernetesCheckType, + Name: constants.ConsulKubernetesCheckName, + Type: constants.ConsulKubernetesCheckType, Status: healthStatus, ServiceID: svcID, Output: getHealthCheckStatusReason(healthStatus, pod.Name, pod.Namespace), @@ -664,8 +657,8 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Service: proxyService, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, proxySvcID), - Name: consulKubernetesCheckName, - Type: consulKubernetesCheckType, + Name: constants.ConsulKubernetesCheckName, + Type: constants.ConsulKubernetesCheckType, Status: healthStatus, ServiceID: proxySvcID, Output: getHealthCheckStatusReason(healthStatus, pod.Name, pod.Namespace), @@ -807,8 +800,8 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints Service: service, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, pod.Name), - Name: consulKubernetesCheckName, - Type: consulKubernetesCheckType, + Name: constants.ConsulKubernetesCheckName, + Type: constants.ConsulKubernetesCheckType, Status: healthStatus, ServiceID: pod.Name, Namespace: consulNS, @@ -900,7 +893,7 @@ func consulHealthCheckID(k8sNS string, serviceID string) string { // as well as pod name and namespace and returns the reason message. func getHealthCheckStatusReason(healthCheckStatus, podName, podNamespace string) string { if healthCheckStatus == api.HealthPassing { - return kubernetesSuccessReasonMsg + return constants.KubernetesSuccessReasonMsg } return fmt.Sprintf("Pod \"%s/%s\" is not ready", podNamespace, podName) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 49150dec6a..9f9f54ba45 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -172,40 +172,40 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { CheckID: fmt.Sprintf("%s/pod1-service-created", testCase.SourceKubeNS), ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod1-service-created-sidecar-proxy", testCase.SourceKubeNS), ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod2-service-created", testCase.SourceKubeNS), ServiceName: "service-created", ServiceID: "pod2-service-created", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod2-service-created-sidecar-proxy", testCase.SourceKubeNS), ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, }, @@ -446,30 +446,30 @@ func TestReconcileCreateGatewayWithNamespaces(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, Namespace: "default", }, { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, Namespace: testCase.ConsulNS, }, { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, Namespace: testCase.ConsulNS, }, }, diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 380db5a0b6..6a888d61f6 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -787,37 +787,37 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { CheckID: "default/pod1-web", ServiceName: "web", ServiceID: "pod1-web", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-web-sidecar-proxy", ServiceName: "web-sidecar-proxy", ServiceID: "pod1-web-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-web-admin", ServiceName: "web-admin", ServiceID: "pod1-web-admin", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-web-admin-sidecar-proxy", ServiceName: "web-admin-sidecar-proxy", ServiceID: "pod1-web-admin-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -1045,19 +1045,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -1130,10 +1130,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -1202,10 +1202,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, metricsEnabled: true, @@ -1275,10 +1275,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, metricsEnabled: true, @@ -1338,10 +1338,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -1404,10 +1404,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -1504,10 +1504,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -1606,10 +1606,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -1707,37 +1707,37 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod2-service-created", ServiceName: "service-created", ServiceID: "pod2-service-created", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod2-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -1848,28 +1848,28 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod2-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, expErr: "1 error occurred:\n\t* pods \"pod3\" not found\n\n", @@ -1991,19 +1991,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-different-consul-svc-name", ServiceName: "different-consul-svc-name", ServiceID: "pod1-different-consul-svc-name", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-different-consul-svc-name-sidecar-proxy", ServiceName: "different-consul-svc-name-sidecar-proxy", ServiceID: "pod1-different-consul-svc-name-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -2082,19 +2082,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -2307,8 +2307,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated", - Name: consulKubernetesCheckName, - Type: consulKubernetesCheckType, + Name: constants.ConsulKubernetesCheckName, + Type: constants.ConsulKubernetesCheckType, Status: api.HealthCritical, ServiceID: "pod1-service-updated", ServiceName: "service-updated", @@ -2334,8 +2334,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated-sidecar-proxy", - Name: consulKubernetesCheckName, - Type: consulKubernetesCheckType, + Name: constants.ConsulKubernetesCheckName, + Type: constants.ConsulKubernetesCheckType, Status: api.HealthCritical, ServiceID: "pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", @@ -2359,19 +2359,19 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -2418,8 +2418,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated", - Name: consulKubernetesCheckName, - Type: consulKubernetesCheckType, + Name: constants.ConsulKubernetesCheckName, + Type: constants.ConsulKubernetesCheckType, Status: api.HealthPassing, ServiceName: "service-updated", ServiceID: "pod1-service-updated", @@ -2445,8 +2445,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated-sidecar-proxy", - Name: consulKubernetesCheckName, - Type: consulKubernetesCheckType, + Name: constants.ConsulKubernetesCheckName, + Type: constants.ConsulKubernetesCheckType, Status: api.HealthPassing, ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", @@ -2470,19 +2470,19 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthCritical, Output: "Pod \"default/pod1\" is not ready", - Type: consulKubernetesCheckType, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthCritical, Output: "Pod \"default/pod1\" is not ready", - Type: consulKubernetesCheckType, + Type: constants.ConsulKubernetesCheckType, }, }, }, @@ -2756,37 +2756,37 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod2-service-updated", ServiceName: "service-updated", ServiceID: "pod2-service-updated", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, { CheckID: "default/pod2-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod2-service-updated-sidecar-proxy", - Name: consulKubernetesCheckName, + Name: constants.ConsulKubernetesCheckName, Status: api.HealthPassing, - Output: kubernetesSuccessReasonMsg, - Type: consulKubernetesCheckType, + Output: constants.KubernetesSuccessReasonMsg, + Type: constants.ConsulKubernetesCheckType, }, }, }, diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index f4e3f56053..d21e3d663a 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -5,15 +5,17 @@ package pod import ( "context" + "encoding/json" "fmt" "strconv" mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/go-multierror" - "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/proto" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" @@ -28,8 +30,9 @@ import ( ) const ( - metaKeyManagedBy = "managed-by" - tokenMetaPodNameKey = "pod" + metaKeyManagedBy = "managed-by" + + DefaultTelemetryBindSocketDir = "/consul/mesh-inject" ) type Controller struct { @@ -63,6 +66,13 @@ type Controller struct { // TODO: EnableWANFederation + // EnableTransparentProxy controls whether transparent proxy should be enabled + // for all proxy service registrations. + EnableTransparentProxy bool + // TProxyOverwriteProbes controls whether the pods controller should expose pod's HTTP probes + // via Envoy proxy. + TProxyOverwriteProbes bool + // AuthMethod is the name of the Kubernetes Auth Method that // was used to login with Consul. The Endpoints controller // will delete any tokens associated with this auth method @@ -74,14 +84,13 @@ type Controller struct { EnableTelemetryCollector bool MetricsConfig metrics.Config - - Log logr.Logger + Log logr.Logger // ResourceClient is a gRPC client for the resource service. It is public for testing purposes ResourceClient pbresource.ResourceServiceClient } -// TODO(dans): logs, logs, logs +// TODO: logs, logs, logs // Reconcile reads the state of an Endpoints object for a Kubernetes Service and reconciles Consul services which // correspond to the Kubernetes Service. These events are driven by changes to the Pods backing the Kube service. @@ -110,26 +119,21 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // we need to remove the Workload from Consul. if k8serrors.IsNotFound(err) { + // Consul should also clean up the orphaned HealthStatus if err := r.deleteWorkload(ctx, req.NamespacedName); err != nil { errs = multierror.Append(errs, err) } + // TODO: clean up ACL Tokens + // TODO: delete explicit upstreams //if err := r.deleteUpstreams(ctx, pod); err != nil { // errs = multierror.Append(errs, err) //} - // TODO(dans): delete proxyConfiguration - //if err := r.deleteProxyConfiguration(ctx, pod); err != nil { - // errs = multierror.Append(errs, err) - //} - - // TODO: clean up ACL Tokens - - // TODO(dans): delete health status, since we don't have finalizers - //if err := r.deleteHealthStatus(ctx, pod); err != nil { - // errs = multierror.Append(errs, err) - //} + if err := r.deleteProxyConfiguration(ctx, req.NamespacedName); err != nil { + errs = multierror.Append(errs, err) + } return ctrl.Result{}, errs } else if err != nil { @@ -140,21 +144,22 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.Log.Info("retrieved", "name", pod.Name, "ns", pod.Namespace) if hasBeenInjected(pod) { - if err := r.writeWorkload(ctx, pod); err != nil { + if err := r.writeProxyConfiguration(ctx, pod); err != nil { errs = multierror.Append(errs, err) } - // TODO(dans): create proxyConfiguration + if err := r.writeWorkload(ctx, pod); err != nil { + errs = multierror.Append(errs, err) + } // TODO: create explicit upstreams //if err := r.writeUpstreams(ctx, pod); err != nil { // errs = multierror.Append(errs, err) //} - // TODO(dans): write health status - //if err := r.writeHealthStatus(ctx, pod); err != nil { - // errs = multierror.Append(errs, err) - //} + if err := r.writeHealthStatus(ctx, pod); err != nil { + errs = multierror.Append(errs, err) + } } return ctrl.Result{}, errs @@ -183,9 +188,14 @@ func (r *Controller) deleteWorkload(ctx context.Context, pod types.NamespacedNam return err } -//func (r *Controller) deleteHealthStatus(ctx context.Context, pod corev1.Pod) error { -// return nil -//} +func (r *Controller) deleteProxyConfiguration(ctx context.Context, pod types.NamespacedName) error { + req := &pbresource.DeleteRequest{ + Id: getProxyConfigurationID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), + } + + _, err := r.ResourceClient.Delete(ctx, req) + return err +} func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { @@ -208,35 +218,221 @@ func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { NodeName: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), Ports: workloadPorts, } + data := common.ToProtoAny(workload) - // TODO(dans): replace with common.ToProtoAny when available - proto, err := anypb.New(workload) + req := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), + Metadata: metaFromPod(pod), + Data: data, + }, + } + _, err := r.ResourceClient.Write(ctx, req) + return err +} + +func (r *Controller) writeProxyConfiguration(ctx context.Context, pod corev1.Pod) error { + mode, err := r.getTproxyMode(ctx, pod) if err != nil { - return fmt.Errorf("could not serialize workload: %w", err) + return fmt.Errorf("failed to get transparent proxy mode: %w", err) } - // TODO: allow custom workload metadata - meta := map[string]string{ - constants.MetaKeyKubeNS: pod.Namespace, - metaKeyManagedBy: constants.ManagedByPodValue, + exposeConfig, err := r.getExposeConfig(pod) + if err != nil { + return fmt.Errorf("failed to get expose config: %w", err) } + bootstrapConfig, err := r.getBootstrapConfig(pod) + if err != nil { + return fmt.Errorf("failed to get bootstrap config: %w", err) + } + + if exposeConfig == nil && + bootstrapConfig == nil && + mode == pbmesh.ProxyMode_PROXY_MODE_DEFAULT { + // It's possible to remove interesting annotations and need to clear any existing config, + // but for now we treat pods as immutable configs owned by other managers. + return nil + } + + pc := &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{pod.GetName()}, + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: mode, + ExposeConfig: exposeConfig, + }, + BootstrapConfig: bootstrapConfig, + } + data := common.ToProtoAny(pc) + req := &pbresource.WriteRequest{ Resource: &pbresource.Resource{ - Id: getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Metadata: meta, - Data: proto, + Id: getProxyConfigurationID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), + Metadata: metaFromPod(pod), + Data: data, }, } _, err = r.ResourceClient.Write(ctx, req) return err } -//func (r *Controller) writeHealthStatus(pod corev1.Pod) error { -// return nil -//} +func (r *Controller) getTproxyMode(ctx context.Context, pod corev1.Pod) (pbmesh.ProxyMode, error) { + // A user can enable/disable tproxy for an entire namespace. + var ns corev1.Namespace + err := r.Client.Get(ctx, types.NamespacedName{Name: pod.GetNamespace()}, &ns) + if err != nil { + return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, fmt.Errorf("could not get namespace info for %s: %w", pod.GetNamespace(), err) + } + + tproxyEnabled, err := common.TransparentProxyEnabled(ns, pod, r.EnableTransparentProxy) + if err != nil { + return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, fmt.Errorf("could not determine if transparent proxy is enabled: %w", err) + } + + if tproxyEnabled { + return pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, nil + } + return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, nil +} + +func (r *Controller) getExposeConfig(pod corev1.Pod) (*pbmesh.ExposeConfig, error) { + // Expose k8s probes as Envoy listeners if needed. + overwriteProbes, err := common.ShouldOverwriteProbes(pod, r.TProxyOverwriteProbes) + if err != nil { + return nil, fmt.Errorf("could not determine if probes should be overwritten: %w", err) + } + + if !overwriteProbes { + return nil, nil + } + + var originalPod corev1.Pod + err = json.Unmarshal([]byte(pod.Annotations[constants.AnnotationOriginalPod]), &originalPod) + if err != nil { + return nil, fmt.Errorf("failed to get original pod spec: %w", err) + } -// TODO(dans): delete ACL token for workload + exposeConfig := &pbmesh.ExposeConfig{} + for _, mutatedContainer := range pod.Spec.Containers { + for _, originalContainer := range originalPod.Spec.Containers { + if originalContainer.Name == mutatedContainer.Name { + paths, err := getContainerExposePaths(originalPod, originalContainer, mutatedContainer) + if err != nil { + return nil, fmt.Errorf("error getting container expose path for %s: %w", originalContainer.Name, err) + } + + exposeConfig.ExposePaths = append(exposeConfig.ExposePaths, paths...) + } + } + } + + if len(exposeConfig.ExposePaths) == 0 { + return nil, nil + } + return exposeConfig, nil +} + +func getContainerExposePaths(originalPod corev1.Pod, originalContainer, mutatedContainer corev1.Container) ([]*pbmesh.ExposePath, error) { + var paths []*pbmesh.ExposePath + if mutatedContainer.LivenessProbe != nil && mutatedContainer.LivenessProbe.HTTPGet != nil { + originalLivenessPort, err := common.PortValueFromIntOrString(originalPod, originalContainer.LivenessProbe.HTTPGet.Port) + if err != nil { + return nil, err + } + + newPath := &pbmesh.ExposePath{ + ListenerPort: uint32(mutatedContainer.LivenessProbe.HTTPGet.Port.IntValue()), + LocalPathPort: originalLivenessPort, + Path: mutatedContainer.LivenessProbe.HTTPGet.Path, + } + paths = append(paths, newPath) + } + if mutatedContainer.ReadinessProbe != nil && mutatedContainer.ReadinessProbe.HTTPGet != nil { + originalReadinessPort, err := common.PortValueFromIntOrString(originalPod, originalContainer.ReadinessProbe.HTTPGet.Port) + if err != nil { + return nil, err + } + + newPath := &pbmesh.ExposePath{ + ListenerPort: uint32(mutatedContainer.ReadinessProbe.HTTPGet.Port.IntValue()), + LocalPathPort: originalReadinessPort, + Path: mutatedContainer.ReadinessProbe.HTTPGet.Path, + } + paths = append(paths, newPath) + } + if mutatedContainer.StartupProbe != nil && mutatedContainer.StartupProbe.HTTPGet != nil { + originalStartupPort, err := common.PortValueFromIntOrString(originalPod, originalContainer.StartupProbe.HTTPGet.Port) + if err != nil { + return nil, err + } + + newPath := &pbmesh.ExposePath{ + ListenerPort: uint32(mutatedContainer.StartupProbe.HTTPGet.Port.IntValue()), + LocalPathPort: originalStartupPort, + Path: mutatedContainer.StartupProbe.HTTPGet.Path, + } + paths = append(paths, newPath) + } + return paths, nil +} + +func (r *Controller) getBootstrapConfig(pod corev1.Pod) (*pbmesh.BootstrapConfig, error) { + bootstrap := &pbmesh.BootstrapConfig{} + + // If metrics are enabled, the BootstrapConfig should set envoy_prometheus_bind_addr to a listener on 0.0.0.0 on + // the PrometheusScrapePort that points to a metrics backend. The backend for this listener will be determined by + // the envoy bootstrapping command (consul connect envoy) or the consul-dataplane GetBoostrapParams rpc. + // If there is a merged metrics server, the backend would be that server. + // If we are not running the merged metrics server, the backend should just be the Envoy metrics endpoint. + enableMetrics, err := r.MetricsConfig.EnableMetrics(pod) + if err != nil { + return nil, fmt.Errorf("error determining if metrics are enabled: %w", err) + } + if enableMetrics { + prometheusScrapePort, err := r.MetricsConfig.PrometheusScrapePort(pod) + if err != nil { + return nil, err + } + prometheusScrapeListener := fmt.Sprintf("0.0.0.0:%s", prometheusScrapePort) + bootstrap.PrometheusBindAddr = prometheusScrapeListener + } + + if r.EnableTelemetryCollector { + bootstrap.TelemetryCollectorBindSocketDir = DefaultTelemetryBindSocketDir + } + + if proto.Equal(bootstrap, &pbmesh.BootstrapConfig{}) { + return nil, nil + } + return bootstrap, nil +} + +func (r *Controller) writeHealthStatus(ctx context.Context, pod corev1.Pod) error { + status := getHealthStatusFromPod(pod) + + hs := &pbcatalog.HealthStatus{ + Type: constants.ConsulKubernetesCheckType, + Status: status, + Description: constants.ConsulKubernetesCheckName, + Output: getHealthStatusReason(status, pod), + } + data := common.ToProtoAny(hs) + + req := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: getHealthStatusID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), + Owner: getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), + Metadata: metaFromPod(pod), + Data: data, + }, + } + _, err := r.ResourceClient.Write(ctx, req) + return err +} + +// TODO: delete ACL token for workload // deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. // It will only check for ACL tokens that have been created with the auth method this controller // has been configured with and will only delete tokens for the provided podName. @@ -245,6 +441,8 @@ func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { // TODO: add support for explicit upstreams //func (r *Controller) writeUpstreams(pod corev1.Pod) error +//func (r *Controller) deleteUpstreams(pod corev1.Pod) error + // consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace // depending on Consul Namespaces being enabled and the value of namespace mirroring. func (r *Controller) getConsulNamespace(kubeNamespace string) string { @@ -321,6 +519,40 @@ func parseLocality(node corev1.Node) *pbcatalog.Locality { } } +func metaFromPod(pod corev1.Pod) map[string]string { + // TODO: allow custom workload metadata + return map[string]string{ + constants.MetaKeyKubeNS: pod.GetNamespace(), + metaKeyManagedBy: constants.ManagedByPodValue, + } +} + +// getHealthStatusFromPod checks the Pod for a "Ready" condition that is true. +// This is true when all the containers are ready, vs. "Running" on the PodPhase, +// which is true if any container is running. +func getHealthStatusFromPod(pod corev1.Pod) pbcatalog.Health { + if pod.Status.Conditions == nil { + return pbcatalog.Health_HEALTH_CRITICAL + } + + for _, condition := range pod.Status.Conditions { + if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue { + return pbcatalog.Health_HEALTH_PASSING + } + } + return pbcatalog.Health_HEALTH_CRITICAL +} + +// getHealthStatusReason takes Consul's health check status (either passing or critical) +// and the pod to return a descriptive output for the HealthStatus Output. +func getHealthStatusReason(state pbcatalog.Health, pod corev1.Pod) string { + if state == pbcatalog.Health_HEALTH_PASSING { + return constants.KubernetesSuccessReasonMsg + } + + return fmt.Sprintf("Pod \"%s/%s\" is not ready", pod.GetNamespace(), pod.GetName()) +} + func getWorkloadID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, @@ -332,6 +564,48 @@ func getWorkloadID(name, namespace, partition string) *pbresource.ID { Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "mesh", + GroupVersion: "v1alpha1", + Kind: "ProxyConfiguration", + }, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func getHealthStatusID(name, namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "HealthStatus", + }, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, }, } } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 52a054581b..c4fb6cb5ab 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -5,22 +5,27 @@ package pod import ( "context" + "encoding/json" "testing" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" + "github.com/google/go-cmp/cmp" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -160,26 +165,14 @@ func TestWorkloadWrite(t *testing.T) { err = pc.writeWorkload(context.Background(), *tc.pod) require.NoError(t, err) - req := &pbresource.ReadRequest{Id: &pbresource.ID{ - Name: tc.pod.GetName(), - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Workload", - }, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulPartition, - Namespace: metav1.NamespaceDefault, - }, - }} + req := &pbresource.ReadRequest{ + Id: getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), + } actualRes, err := resourceClient.Read(context.Background(), req) require.NoError(t, err) require.NotNil(t, actualRes) - require.Equal(t, tc.pod.GetName(), actualRes.GetResource().GetId().GetName()) - require.Equal(t, constants.DefaultConsulNS, actualRes.GetResource().GetId().GetTenancy().GetNamespace()) - require.Equal(t, constants.DefaultConsulPartition, actualRes.GetResource().GetId().GetTenancy().GetPartition()) - + requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) require.NotNil(t, actualRes.GetResource().GetData()) actualWorkload := &pbcatalog.Workload{} @@ -191,29 +184,9 @@ func TestWorkloadWrite(t *testing.T) { testCases := []testCase{ { - name: "multi-port single-container", - pod: createPod("foo", "10.0.0.1", "foo", true, true), - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: consulNodeName, - Identity: "foo", - }, + name: "multi-port single-container", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + expectedWorkload: createWorkload(), }, { name: "multi-port multi-container", @@ -399,18 +372,9 @@ func TestWorkloadDelete(t *testing.T) { err = pc.deleteWorkload(context.Background(), reconcileReq) require.NoError(t, err) - readReq := &pbresource.ReadRequest{Id: &pbresource.ID{ - Name: tc.pod.GetName(), - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Workload", - }, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulPartition, - Namespace: metav1.NamespaceDefault, - }, - }} + readReq := &pbresource.ReadRequest{ + Id: getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), + } _, err = resourceClient.Read(context.Background(), readReq) require.Error(t, err) s, ok := status.FromError(err) @@ -420,28 +384,312 @@ func TestWorkloadDelete(t *testing.T) { testCases := []testCase{ { - name: "basic pod delete", + name: "basic pod delete", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + existingWorkload: createWorkload(), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +func TestHealthStatusWrite(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + pod *corev1.Pod + podModifier func(pod *corev1.Pod) + expectedHealthStatus *pbcatalog.HealthStatus + } + + run := func(t *testing.T, tc testCase) { + if tc.podModifier != nil { + tc.podModifier(tc.pod) + } + + fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + ResourceClient: resourceClient, + } + + // The owner of a resource is validated, so create a dummy workload for the HealthStatus + workloadData, err := anypb.New(createWorkload()) + require.NoError(t, err) + + workloadID := getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) + writeReq := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: workloadID, + Data: workloadData, + }, + } + _, err = resourceClient.Write(context.Background(), writeReq) + require.NoError(t, err) + + // Test writing the pod to a HealthStatus + err = pc.writeHealthStatus(context.Background(), *tc.pod) + require.NoError(t, err) + + req := &pbresource.ReadRequest{ + Id: getHealthStatusID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), + } + actualRes, err := resourceClient.Read(context.Background(), req) + require.NoError(t, err) + require.NotNil(t, actualRes) + + requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) + require.NotNil(t, actualRes.GetResource().GetData()) + + actualHealthStatus := &pbcatalog.HealthStatus{} + err = actualRes.GetResource().GetData().UnmarshalTo(actualHealthStatus) + require.NoError(t, err) + + require.True(t, proto.Equal(actualHealthStatus, tc.expectedHealthStatus)) + } + + testCases := []testCase{ + { + name: "ready pod", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + expectedHealthStatus: createPassingHealthStatus(), + }, + { + name: "not ready pod", + pod: createPod("foo", "10.0.0.1", "foo", true, false), + expectedHealthStatus: createCriticalHealthStatus(), + }, + { + name: "pod with no condition", pod: createPod("foo", "10.0.0.1", "foo", true, true), - existingWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + podModifier: func(pod *corev1.Pod) { + pod.Status.Conditions = []corev1.PodCondition{} + }, + expectedHealthStatus: createCriticalHealthStatus(), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +func TestProxyConfigurationWrite(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + pod *corev1.Pod + podModifier func(pod *corev1.Pod) + expectedProxyConfiguration *pbmesh.ProxyConfiguration + + tproxy bool + overwriteProbes bool + metrics bool + telemetry bool + } + + run := func(t *testing.T, tc testCase) { + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: metav1.NamespaceDefault, + }} + + nsTproxy := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: "tproxy-party", + Labels: map[string]string{ + constants.KeyTransparentProxy: "true", + }, + }} + + if tc.podModifier != nil { + tc.podModifier(tc.pod) + } + + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(&ns, &nsTproxy).Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + EnableTransparentProxy: tc.tproxy, + TProxyOverwriteProbes: tc.overwriteProbes, + EnableTelemetryCollector: tc.telemetry, + ResourceClient: resourceClient, + } + + if tc.metrics { + pc.MetricsConfig = metrics.Config{ + DefaultEnableMetrics: true, + DefaultPrometheusScrapePort: "5678", + } + } + + // Test writing the pod to a HealthStatus + err = pc.writeProxyConfiguration(context.Background(), *tc.pod) + require.NoError(t, err) + + req := &pbresource.ReadRequest{ + Id: getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), + } + actualRes, err := resourceClient.Read(context.Background(), req) + + if tc.expectedProxyConfiguration == nil { + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + return + } + + require.NoError(t, err) + require.NotNil(t, actualRes) + + requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) + require.NotNil(t, actualRes.GetResource().GetData()) + + actualProxyConfiguration := &pbmesh.ProxyConfiguration{} + err = actualRes.GetResource().GetData().UnmarshalTo(actualProxyConfiguration) + require.NoError(t, err) + + diff := cmp.Diff(actualProxyConfiguration, tc.expectedProxyConfiguration, protocmp.Transform()) + require.Equal(t, "", diff) + } + + testCases := []testCase{ + { + name: "no tproxy, no telemetry, no metrics, no probe overwrite", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + expectedProxyConfiguration: nil, + }, + { + name: "kitchen sink - globally enabled", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + podModifier: func(pod *corev1.Pod) { + addProbesAndOriginalPodAnnotation(pod) + }, + tproxy: true, + overwriteProbes: true, + metrics: true, + telemetry: true, + expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{"foo"}, }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 20400, + LocalPathPort: 2001, + Path: "/livez", + }, + { + ListenerPort: 20300, + LocalPathPort: 2000, + Path: "/readyz", + }, + { + ListenerPort: 20500, + LocalPathPort: 2002, + Path: "/startupz", + }, + }, }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + BootstrapConfig: &pbmesh.BootstrapConfig{ + PrometheusBindAddr: "0.0.0.0:5678", + TelemetryCollectorBindSocketDir: DefaultTelemetryBindSocketDir, + }, + }, + }, + { + name: "tproxy, metrics, and probe overwrite enabled on pod", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + podModifier: func(pod *corev1.Pod) { + pod.Annotations[constants.KeyTransparentProxy] = "true" + pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "true" + pod.Annotations[constants.AnnotationEnableMetrics] = "true" + pod.Annotations[constants.AnnotationPrometheusScrapePort] = "21234" + + addProbesAndOriginalPodAnnotation(pod) + }, + expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{"foo"}, + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 20400, + LocalPathPort: 2001, + Path: "/livez", + }, + { + ListenerPort: 20300, + LocalPathPort: 2000, + Path: "/readyz", + }, + { + ListenerPort: 20500, + LocalPathPort: 2002, + Path: "/startupz", + }, + }, }, }, - NodeName: consulNodeName, - Identity: "foo", + BootstrapConfig: &pbmesh.BootstrapConfig{ + PrometheusBindAddr: "0.0.0.0:21234", + }, + }, + }, + { + name: "tproxy enabled on namespace", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + podModifier: func(pod *corev1.Pod) { + pod.Namespace = "tproxy-party" + }, + expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{"foo"}, + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, + }, }, }, } @@ -453,11 +701,89 @@ func TestWorkloadDelete(t *testing.T) { } } -// TODO -// func TestHealthStatusWrite(t *testing.T) +func requireEqualID(t *testing.T, res *pbresource.ReadResponse, name string, ns string, partition string) { + require.Equal(t, name, res.GetResource().GetId().GetName()) + require.Equal(t, ns, res.GetResource().GetId().GetTenancy().GetNamespace()) + require.Equal(t, partition, res.GetResource().GetId().GetTenancy().GetPartition()) +} -// TODO -// func TestHealthStatusDelete(t *testing.T) +func TestProxyConfigurationDelete(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + pod *corev1.Pod + existingProxyConfiguration *pbmesh.ProxyConfiguration + } + + run := func(t *testing.T, tc testCase) { + fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + ResourceClient: resourceClient, + } + + // Create the existing ProxyConfiguration + pcData, err := anypb.New(tc.existingProxyConfiguration) + require.NoError(t, err) + + pcID := getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) + writeReq := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: pcID, + Data: pcData, + }, + } + + _, err = resourceClient.Write(context.Background(), writeReq) + require.NoError(t, err) + test.ResourceHasPersisted(t, resourceClient, pcID) + + reconcileReq := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: tc.pod.GetName(), + } + err = pc.deleteProxyConfiguration(context.Background(), reconcileReq) + require.NoError(t, err) + + readReq := &pbresource.ReadRequest{ + Id: getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), + } + _, err = resourceClient.Read(context.Background(), readReq) + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + } + + testCases := []testCase{ + { + name: "proxy configuration delete", + pod: createPod("foo", "10.0.0.1", "foo", true, true), + existingProxyConfiguration: createProxyConfiguration(), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} // TODO // func TestUpstreamsWrite(t *testing.T) @@ -475,8 +801,7 @@ func TestReconcileCreatePod(t *testing.T) { t.Parallel() ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - Namespace: metav1.NamespaceDefault, + Name: metav1.NamespaceDefault, }} node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} @@ -485,14 +810,16 @@ func TestReconcileCreatePod(t *testing.T) { podName string // This needs to be aligned with the pod created in `k8sObjects` namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod - k8sObjects func() []runtime.Object // testing node is injected separately - expectedWorkload *pbcatalog.Workload - //expectedHealthStatus *pbcatalog.HealthStatus - //expectedProxyConfiguration *pbmesh.ProxyConfiguration + k8sObjects func() []runtime.Object // testing node is injected separately + expectedWorkload *pbcatalog.Workload + expectedHealthStatus *pbcatalog.HealthStatus + expectedProxyConfiguration *pbmesh.ProxyConfiguration //expectedUpstreams *pbmesh.Upstreams - metricsEnabled bool - telemetryEnabled bool + tproxy bool + overwriteProbes bool + metrics bool + telemetry bool expErr string } @@ -517,20 +844,22 @@ func TestReconcileCreatePod(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + TProxyOverwriteProbes: tc.overwriteProbes, + EnableTransparentProxy: tc.tproxy, + EnableTelemetryCollector: tc.telemetry, } - if tc.metricsEnabled { + if tc.metrics { pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - EnableGatewayMetrics: true, + DefaultEnableMetrics: true, + DefaultPrometheusScrapePort: "1234", } } - pc.EnableTelemetryCollector = tc.telemetryEnabled namespace := tc.namespace if namespace == "" { @@ -553,9 +882,8 @@ func TestReconcileCreatePod(t *testing.T) { require.False(t, resp.Requeue) expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) - // TODO(dans): compare the following to expected values - // expectedHealthStatus - // expectedProxyConfiguration + expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) + expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) // expectedUpstreams } @@ -565,29 +893,17 @@ func TestReconcileCreatePod(t *testing.T) { podName: "foo", k8sObjects: func() []runtime.Object { pod := createPod("foo", "10.0.0.1", "foo", true, true) + addProbesAndOriginalPodAnnotation(pod) + return []runtime.Object{pod} }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: consulNodeName, - Identity: "foo", - }, + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(), }, { name: "pod in ignored namespace", @@ -599,12 +915,39 @@ func TestReconcileCreatePod(t *testing.T) { return []runtime.Object{pod} }, }, - // TODO(dans): NotHealthyPod - // TODO(dans): tproxy + Metrics + Telemetry - // TODO: explicit upstreams - // TODO: at least one error cases + { + name: "unhealthy new pod", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, false) + return []runtime.Object{pod} + }, + expectedWorkload: createWorkload(), + expectedHealthStatus: createCriticalHealthStatus(), + }, + { + name: "return error - pod has no original pod annotation", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, false) + return []runtime.Object{pod} + }, + tproxy: true, + overwriteProbes: true, + expectedWorkload: createWorkload(), + expectedHealthStatus: createCriticalHealthStatus(), + expErr: "1 error occurred:\n\t* failed to get expose config: failed to get original pod spec: unexpected end of JSON input\n\n", + }, + { + name: "pod has not been injected", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", false, true) + return []runtime.Object{pod} + }, + }, // TODO: make sure multi-error accumulates errors - // TODO: injection annotation added + // TODO: explicit upstreams } for _, tc := range testCases { @@ -615,12 +958,14 @@ func TestReconcileCreatePod(t *testing.T) { } // TestReconcileUpdatePod test updating a Pod object when there is already matching resources in Consul. +// Updates are unlikely because of the immutable behaviors of pods as members of deployment/statefulset, +// but theoretically it is possible to update annotations and labels in-place. Most likely this will be +// from a change in health status. func TestReconcileUpdatePod(t *testing.T) { t.Parallel() ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - Namespace: metav1.NamespaceDefault, + Name: metav1.NamespaceDefault, }} node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} @@ -631,18 +976,20 @@ func TestReconcileUpdatePod(t *testing.T) { k8sObjects func() []runtime.Object // testing node is injected separately - existingWorkload *pbcatalog.Workload - //existingHealthStatus *pbcatalog.HealthStatus - //existingProxyConfiguration *pbmesh.ProxyConfiguration + existingWorkload *pbcatalog.Workload + existingHealthStatus *pbcatalog.HealthStatus + existingProxyConfiguration *pbmesh.ProxyConfiguration //existingUpstreams *pbmesh.Upstreams - expectedWorkload *pbcatalog.Workload - //expectedHealthStatus *pbcatalog.HealthStatus - //expectedProxyConfiguration *pbmesh.ProxyConfiguration + expectedWorkload *pbcatalog.Workload + expectedHealthStatus *pbcatalog.HealthStatus + expectedProxyConfiguration *pbmesh.ProxyConfiguration //expectedUpstreams *pbmesh.Upstreams - metricsEnabled bool - telemetryEnabled bool + tproxy bool + overwriteProbes bool + metrics bool + telemetry bool expErr string } @@ -667,36 +1014,43 @@ func TestReconcileUpdatePod(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + TProxyOverwriteProbes: tc.overwriteProbes, + EnableTransparentProxy: tc.tproxy, + EnableTelemetryCollector: tc.telemetry, } - if tc.metricsEnabled { + if tc.metrics { pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - EnableGatewayMetrics: true, + DefaultEnableMetrics: true, + DefaultPrometheusScrapePort: "1234", } } - pc.EnableTelemetryCollector = tc.telemetryEnabled namespace := tc.namespace if namespace == "" { namespace = metav1.NamespaceDefault } + workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) + loadResource(t, resourceClient, workloadID, tc.existingWorkload, nil) loadResource( t, resourceClient, - getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingWorkload, - ) + getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingHealthStatus, + workloadID) + loadResource(t, + resourceClient, + getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingProxyConfiguration, + nil) - // TODO(dans): load the existing resources - // loadHealthStatus - // loadProxyConfiguration + // TODO: load the existing resources // loadUpstreams namespacedName := types.NamespacedName{ @@ -715,9 +1069,9 @@ func TestReconcileUpdatePod(t *testing.T) { require.False(t, resp.Requeue) expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) - // TODO(dans): compare the following to expected values - // expectedHealthStatus - // expectedProxyConfiguration + expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) + expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) + // TODO: compare the following to expected values // expectedUpstreams } @@ -729,6 +1083,7 @@ func TestReconcileUpdatePod(t *testing.T) { pod := createPod("foo", "10.0.0.1", "foo", true, true) return []runtime.Object{pod} }, + existingHealthStatus: createPassingHealthStatus(), existingWorkload: &pbcatalog.Workload{ Addresses: []*pbcatalog.WorkloadAddress{ {Host: "10.0.0.1", Ports: []string{"public", "mesh"}}, @@ -746,30 +1101,69 @@ func TestReconcileUpdatePod(t *testing.T) { NodeName: consulNodeName, Identity: "foo", }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + }, + { + name: "pod healthy to unhealthy", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, false) + return []runtime.Object{pod} + }, + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + expectedWorkload: createWorkload(), + expectedHealthStatus: createCriticalHealthStatus(), + }, + { + name: "add metrics, tproxy and probe overwrite to pod", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod.Annotations[constants.KeyTransparentProxy] = "true" + pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "true" + pod.Annotations[constants.AnnotationEnableMetrics] = "true" + pod.Annotations[constants.AnnotationPrometheusScrapePort] = "21234" + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{"foo"}, }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 20400, + LocalPathPort: 2001, + Path: "/livez", + }, + { + ListenerPort: 20300, + LocalPathPort: 2000, + Path: "/readyz", + }, + { + ListenerPort: 20500, + LocalPathPort: 2002, + Path: "/startupz", + }, + }, }, }, - NodeName: consulNodeName, - Identity: "foo", + BootstrapConfig: &pbmesh.BootstrapConfig{ + PrometheusBindAddr: "0.0.0.0:21234", + }, }, }, - // TODO(dans): Pod Health to Unhealthy - // TODO(dans): update tproxy + Metrics + Telemetry // TODO: update explicit upstreams } @@ -797,19 +1191,17 @@ func TestReconcileDeletePod(t *testing.T) { k8sObjects func() []runtime.Object // testing node is injected separately - existingWorkload *pbcatalog.Workload - //existingHealthStatus *pbcatalog.HealthStatus - //existingProxyConfiguration *pbmesh.ProxyConfiguration + existingWorkload *pbcatalog.Workload + existingHealthStatus *pbcatalog.HealthStatus + existingProxyConfiguration *pbmesh.ProxyConfiguration //existingUpstreams *pbmesh.Upstreams - expectedWorkload *pbcatalog.Workload - //expectedHealthStatus *pbcatalog.HealthStatus - //expectedProxyConfiguration *pbmesh.ProxyConfiguration + expectedWorkload *pbcatalog.Workload + expectedHealthStatus *pbcatalog.HealthStatus + expectedProxyConfiguration *pbmesh.ProxyConfiguration //expectedUpstreams *pbmesh.Upstreams - aclsEnabled bool - metricsEnabled bool - telemetryEnabled bool + aclsEnabled bool expErr string } @@ -841,32 +1233,30 @@ func TestReconcileDeletePod(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), } - if tc.metricsEnabled { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - EnableGatewayMetrics: true, - } - } if tc.aclsEnabled { pc.AuthMethod = test.AuthMethod } - pc.EnableTelemetryCollector = tc.telemetryEnabled namespace := tc.namespace if namespace == "" { namespace = metav1.NamespaceDefault } + workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) + loadResource(t, resourceClient, workloadID, tc.existingWorkload, nil) loadResource( t, resourceClient, - getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingWorkload, - ) - - // TODO(dans): load the existing resources - // loadHealthStatus - // loadProxyConfiguration + getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingHealthStatus, + workloadID) + loadResource( + t, + resourceClient, + getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingProxyConfiguration, + nil) + // TODO: load the existing resources // loadUpstreams namespacedName := types.NamespacedName{ @@ -885,37 +1275,19 @@ func TestReconcileDeletePod(t *testing.T) { require.False(t, resp.Requeue) expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) - // TODO(dans): compare the following to expected values - // expectedHealthStatus - // expectedProxyConfiguration + expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) + expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) + // TODO: compare the following to expected values // expectedUpstreams } testCases := []testCase{ { - name: "vanilla delete pod", - podName: "foo", - existingWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: consulNodeName, - Identity: "foo", - }, + name: "vanilla delete pod", + podName: "foo", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration(), }, // TODO: enable ACLs and make sure they are deleted } @@ -927,6 +1299,7 @@ func TestReconcileDeletePod(t *testing.T) { } } +// createPod creates a multi-port pod as a base for tests. func createPod(name, ip string, identity string, inject bool, ready bool) *corev1.Pod { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -957,6 +1330,30 @@ func createPod(name, ip string, identity string, inject bool, ready bool) *corev ContainerPort: 8080, }, }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt(2000), + }, + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt(2001), + }, + }, + }, + StartupProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/startupz", + Port: intstr.FromInt(2002), + }, + }, + }, }, }, NodeName: nodeName, @@ -986,6 +1383,87 @@ func createPod(name, ip string, identity string, inject bool, ready bool) *corev return pod } +// createWorkload creates a workload that matches the pod from createPod. +func createWorkload() *pbcatalog.Workload { + return &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: consulNodeName, + Identity: "foo", + } +} + +// createPassingHealthStatus creates a passing HealthStatus that matches the pod from createPod. +func createPassingHealthStatus() *pbcatalog.HealthStatus { + return &pbcatalog.HealthStatus{ + Type: constants.ConsulKubernetesCheckType, + Status: pbcatalog.Health_HEALTH_PASSING, + Output: constants.KubernetesSuccessReasonMsg, + Description: constants.ConsulKubernetesCheckName, + } +} + +// createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. +func createCriticalHealthStatus() *pbcatalog.HealthStatus { + return &pbcatalog.HealthStatus{ + Type: constants.ConsulKubernetesCheckType, + Status: pbcatalog.Health_HEALTH_CRITICAL, + Output: "Pod \"default/foo\" is not ready", + Description: constants.ConsulKubernetesCheckName, + } +} + +// createProxyConfiguration creates a proxyConfiguration that matches the pod from createPod, +// assuming that metrics, telemetry, and overwrite probes are enabled separately. +func createProxyConfiguration() *pbmesh.ProxyConfiguration { + return &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{"foo"}, + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 20400, + LocalPathPort: 2001, + Path: "/livez", + }, + { + ListenerPort: 20300, + LocalPathPort: 2000, + Path: "/readyz", + }, + { + ListenerPort: 20500, + LocalPathPort: 2002, + Path: "/startupz", + }, + }, + }, + }, + BootstrapConfig: &pbmesh.BootstrapConfig{ + PrometheusBindAddr: "0.0.0.0:1234", + TelemetryCollectorBindSocketDir: DefaultTelemetryBindSocketDir, + }, + } +} + func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedWorkload *pbcatalog.Workload) { req := &pbresource.ReadRequest{Id: getWorkloadID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} @@ -1015,8 +1493,66 @@ func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClie require.True(t, proto.Equal(actualWorkload, expectedWorkload)) } -func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message) { - if id == nil || proto == nil { +func expectedHealthStatusMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedHealthStatus *pbcatalog.HealthStatus) { + req := &pbresource.ReadRequest{Id: getHealthStatusID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} + + res, err := client.Read(context.Background(), req) + + if expectedHealthStatus == nil { + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + return + } + + require.NoError(t, err) + require.NotNil(t, res) + + require.Equal(t, name, res.GetResource().GetId().GetName()) + require.Equal(t, constants.DefaultConsulNS, res.GetResource().GetId().GetTenancy().GetNamespace()) + require.Equal(t, constants.DefaultConsulPartition, res.GetResource().GetId().GetTenancy().GetPartition()) + + require.NotNil(t, res.GetResource().GetData()) + + actualHealthStatus := &pbcatalog.HealthStatus{} + err = res.GetResource().GetData().UnmarshalTo(actualHealthStatus) + require.NoError(t, err) + + require.True(t, proto.Equal(actualHealthStatus, expectedHealthStatus)) +} + +func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedProxyConfiguration *pbmesh.ProxyConfiguration) { + req := &pbresource.ReadRequest{Id: getProxyConfigurationID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} + + res, err := client.Read(context.Background(), req) + + if expectedProxyConfiguration == nil { + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + return + } + + require.NoError(t, err) + require.NotNil(t, res) + + require.Equal(t, name, res.GetResource().GetId().GetName()) + require.Equal(t, constants.DefaultConsulNS, res.GetResource().GetId().GetTenancy().GetNamespace()) + require.Equal(t, constants.DefaultConsulPartition, res.GetResource().GetId().GetTenancy().GetPartition()) + + require.NotNil(t, res.GetResource().GetData()) + + actualProxyConfiguration := &pbmesh.ProxyConfiguration{} + err = res.GetResource().GetData().UnmarshalTo(actualProxyConfiguration) + require.NoError(t, err) + + require.True(t, proto.Equal(actualProxyConfiguration, expectedProxyConfiguration)) +} + +func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { + if id == nil || !proto.ProtoReflect().IsValid() { return } @@ -1024,8 +1560,9 @@ func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbr require.NoError(t, err) resource := &pbresource.Resource{ - Id: id, - Data: data, + Id: id, + Data: data, + Owner: owner, } req := &pbresource.WriteRequest{Resource: resource} @@ -1033,3 +1570,13 @@ func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbr require.NoError(t, err) test.ResourceHasPersisted(t, client, id) } + +func addProbesAndOriginalPodAnnotation(pod *corev1.Pod) { + podBytes, _ := json.Marshal(pod) + pod.Annotations[constants.AnnotationOriginalPod] = string(podBytes) + + // Fake the probe changes that would be added by the mesh webhook + pod.Spec.Containers[0].ReadinessProbe.HTTPGet.Port = intstr.FromInt(20300) + pod.Spec.Containers[0].LivenessProbe.HTTPGet.Port = intstr.FromInt(20400) + pod.Spec.Containers[0].StartupProbe.HTTPGet.Port = intstr.FromInt(20500) +} diff --git a/control-plane/consul/resource_client_test.go b/control-plane/consul/resource_client_test.go index 86d8cee029..3992e766fa 100644 --- a/control-plane/consul/resource_client_test.go +++ b/control-plane/consul/resource_client_test.go @@ -14,6 +14,8 @@ import ( "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/anypb" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) func Test_NewResourceServiceClient(t *testing.T) { @@ -100,8 +102,9 @@ func createWriteRequest(t *testing.T, name string) *pbresource.WriteRequest { Kind: "Workload", }, Tenancy: &pbresource.Tenancy{ - Partition: "default", - Namespace: "default", + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + PeerName: constants.DefaultConsulPeer, }, }, Data: proto, diff --git a/control-plane/go.mod b/control-plane/go.mod index 8581b62e35..2fafd2ed1c 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -15,7 +15,7 @@ require ( github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.4 github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 - github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef + github.com/hashicorp/consul/proto-public v0.1.2-0.20230906155245-56917eb4c968 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 diff --git a/control-plane/go.sum b/control-plane/go.sum index 8e391110ee..435a090003 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -81,7 +81,6 @@ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -128,7 +127,6 @@ github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TR github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -218,7 +216,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -260,22 +257,16 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed h1:eM9tGgSqAZbm4Ndkp35Dg8YROT0dNH3ghTYu5pcUIAc= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= -github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 h1:NPhzdwDho2r8pQv31oeGLlco7fnJ1i0WLYjtSXqWEck= -github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef h1:Vt5NSnXc+RslTxXH2pz7dCb3hnE33CD2TrBP5AIQtMg= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230829221456-f8812eddf1ef/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230906155245-56917eb4c968 h1:J6FLkHXcGd80fUbouFn3kklR3GGHVV0OCyjItyZS8h0= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230906155245-56917eb4c968/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924 h1:gkb6/ix0Tg1Th5FTjyq4QklLgrtIVQ/TUB0kbhIcPsY= github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -432,7 +423,6 @@ github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQh github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -444,10 +434,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= @@ -467,7 +455,6 @@ github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otz github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index df3830a799..75c01ea943 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -31,14 +32,14 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage // DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, //} - //metricsConfig := metrics.Config{ - // DefaultEnableMetrics: c.flagDefaultEnableMetrics, - // EnableGatewayMetrics: c.flagEnableGatewayMetrics, - // DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, - // DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, - // DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, - // DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, - //} + metricsConfig := metrics.Config{ + DefaultEnableMetrics: c.flagDefaultEnableMetrics, + EnableGatewayMetrics: c.flagEnableGatewayMetrics, + DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, + DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, + DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, + DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, + } if err := (&pod.Controller{ Client: mgr.GetClient(), @@ -52,7 +53,11 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage EnableNSMirroring: c.flagEnableK8SNSMirroring, NSMirroringPrefix: c.flagK8SNSMirroringPrefix, ConsulPartition: c.consul.Partition, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, AuthMethod: c.flagACLAuthMethod, + MetricsConfig: metricsConfig, + EnableTelemetryCollector: c.flagEnableTelemetryCollector, Log: ctrl.Log.WithName("controller").WithName("pods"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", pod.Controller{}) From 43fb01e78c015f7ba7d9e9018dba1d952fd2024b Mon Sep 17 00:00:00 2001 From: Ashesh Vidyut <134911583+absolutelightning@users.noreply.github.com> Date: Tue, 12 Sep 2023 13:23:39 +0530 Subject: [PATCH 373/592] Fix for acceptance tests (#2937) * Fix for acceptance tests * fix accpetance test * fix spaces * fix get * added bats test * fix test name * fix bats --- .../ingress-gateways-deployment.yaml | 3 +++ .../consul/templates/server-acl-init-job.yaml | 5 ++++- .../terminating-gateways-deployment.yaml | 3 +++ .../unit/ingress-gateways-deployment.bats | 19 +++++++++++++++++++ .../unit/terminating-gateways-deployment.bats | 19 +++++++++++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index c10f1549f6..df9f500e3c 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -125,6 +125,9 @@ spec: {{- if $root.Values.global.secretsBackend.vault.agentAnnotations }} {{ tpl $root.Values.global.secretsBackend.vault.agentAnnotations $root | nindent 8 | trim }} {{- end }} + {{- if (and ($root.Values.global.secretsBackend.vault.vaultNamespace) (not (hasKey (default "" $root.Values.global.secretsBackend.vault.agentAnnotations | fromYaml) "vault.hashicorp.com/namespace")))}} + "vault.hashicorp.com/namespace": "{{ $root.Values.global.secretsBackend.vault.vaultNamespace }}" + {{- end }} {{- end }} {{- if (and $root.Values.global.metrics.enabled $root.Values.global.metrics.enableGatewayMetrics) }} "prometheus.io/scrape": "true" diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index 0bdceadfd7..0b46697f31 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -150,9 +150,12 @@ spec: fieldPath: metadata.name # Extract the Vault namespace from the Vault agent annotations. {{- if .Values.global.secretsBackend.vault.enabled }} - {{- if .Values.global.secretsBackend.vault.agentAnnotations }} + {{- if and (.Values.global.secretsBackend.vault.agentAnnotations) (hasKey (default "" .Values.global.secretsBackend.vault.agentAnnotations | fromYaml) "vault.hashicorp.com/namespace") }} - name: VAULT_NAMESPACE value: {{ get (tpl .Values.global.secretsBackend.vault.agentAnnotations . | fromYaml) "vault.hashicorp.com/namespace" }} + {{- else if .Values.global.secretsBackend.vault.vaultNamespace }} + - name: VAULT_NAMESPACE + value: {{ .Values.global.secretsBackend.vault.vaultNamespace }} {{- end }} {{- end }} {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index 9433e44bc9..ea2131b8a2 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -94,6 +94,9 @@ spec: {{- if $root.Values.global.secretsBackend.vault.agentAnnotations }} {{ tpl $root.Values.global.secretsBackend.vault.agentAnnotations $root | nindent 8 | trim }} {{- end }} + {{- if (and ($root.Values.global.secretsBackend.vault.vaultNamespace) (not (hasKey (default "" $root.Values.global.secretsBackend.vault.agentAnnotations | fromYaml) "vault.hashicorp.com/namespace")))}} + "vault.hashicorp.com/namespace": "{{ $root.Values.global.secretsBackend.vault.vaultNamespace }}" + {{- end }} {{- end }} {{- if (and $root.Values.global.metrics.enabled $root.Values.global.metrics.enableGatewayMetrics) }} "prometheus.io/scrape": "true" diff --git a/charts/consul/test/unit/ingress-gateways-deployment.bats b/charts/consul/test/unit/ingress-gateways-deployment.bats index e8390278a8..be617ac538 100644 --- a/charts/consul/test/unit/ingress-gateways-deployment.bats +++ b/charts/consul/test/unit/ingress-gateways-deployment.bats @@ -1168,6 +1168,25 @@ key2: value2' \ [ "${actual}" = "bar" ] } +@test "ingressGateway/Deployment: vault namespace annotations can be set when secretsBackend.vault.vaultNamespace is set and .global.secretsBackend.vault.agentAnnotations is not set." { + cd `chart_dir` + local object=$(helm template \ + -s templates/ingress-gateways-deployment.yaml \ + --set 'ingressGateways.enabled=true' \ + --set 'connectInject.enabled=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.vaultNamespace=vns' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/namespace"') + [ "${actual}" = "vns" ] +} + #-------------------------------------------------------------------- # terminationGracePeriodSeconds diff --git a/charts/consul/test/unit/terminating-gateways-deployment.bats b/charts/consul/test/unit/terminating-gateways-deployment.bats index 1dc3befbdf..5e10c5b1fd 100644 --- a/charts/consul/test/unit/terminating-gateways-deployment.bats +++ b/charts/consul/test/unit/terminating-gateways-deployment.bats @@ -1236,6 +1236,25 @@ key2: value2' \ [ "${actual}" = "bar" ] } +@test "terminatingGateways/Deployment: vault namespace annotations can be set when secretsBackend.vault.vaultNamespace is set and .global.secretsBackend.vault.agentAnnotations is not set." { + cd `chart_dir` + local object=$(helm template \ + -s templates/terminating-gateways-deployment.yaml \ + --set 'terminatingGateways.enabled=true' \ + --set 'connectInject.enabled=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.vaultNamespace=vns' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/namespace"') + [ "${actual}" = "vns" ] +} + #-------------------------------------------------------------------- # global.cloud From 869b097c5a3fd6c5be613ab22f4d1c3c589ad68d Mon Sep 17 00:00:00 2001 From: Chris Thain <32781396+cthain@users.noreply.github.com> Date: Tue, 12 Sep 2023 06:07:31 -0700 Subject: [PATCH 374/592] Add local rate limiting acceptance test (#2932) --- .../tests/connect/local_rate_limit_test.go | 126 ++++++++++++++++++ .../service-defaults-static-server.yaml | 23 ++++ 2 files changed, 149 insertions(+) create mode 100644 acceptance/tests/connect/local_rate_limit_test.go create mode 100644 acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml diff --git a/acceptance/tests/connect/local_rate_limit_test.go b/acceptance/tests/connect/local_rate_limit_test.go new file mode 100644 index 0000000000..77a7f160c7 --- /dev/null +++ b/acceptance/tests/connect/local_rate_limit_test.go @@ -0,0 +1,126 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package connect + +import ( + "testing" + "time" + + terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" + + "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" +) + +// TestConnectInject_LocalRateLimiting tests that local rate limiting works as expected between services. +func TestConnectInject_LocalRateLimiting(t *testing.T) { + cfg := suite.Config() + ctx := suite.Environment().DefaultContext(t) + + releaseName := helpers.RandomName() + connHelper := connhelper.ConnectHelper{ + ClusterKind: consul.Helm, + Secure: false, + ReleaseName: releaseName, + Ctx: ctx, + UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, + Cfg: cfg, + } + + connHelper.Setup(t) + connHelper.Install(t) + connHelper.DeployClientAndServer(t) + connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + + // Map the static-server URL and path to the rate limits defined in the service defaults at: + // ../fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml + rateLimitMap := map[string]int{ + "http://localhost:1234": 2, + "http://localhost:1234/exact": 3, + "http://localhost:1234/prefix-test": 4, + "http://localhost:1234/regex": 5, + } + + opts := newRateLimitOptions(t, ctx) + + // Ensure that all requests from static-client to static-server succeed (no rate limiting set). + for addr, rps := range rateLimitMap { + opts.rps = rps + assertRateLimits(t, opts, addr) + } + + // Apply local rate limiting to the static-server + writeCrd(t, connHelper, "../fixtures/cases/local-rate-limiting") + + // Ensure that going over the limit causes the static-server to apply rate limiting and + // reply with 429 Too Many Requests + opts.enforced = true + for addr, reqPerSec := range rateLimitMap { + opts.rps = reqPerSec + assertRateLimits(t, opts, addr) + } +} + +func assertRateLimits(t *testing.T, opts *assertRateLimitOptions, curlArgs ...string) { + t.Helper() + + args := []string{"exec", opts.resourceType + opts.sourceApp, "-c", opts.sourceApp, "--", "curl", opts.curlOpts} + args = append(args, curlArgs...) + + // This check is time sensitive due to the nature of rate limiting. + // Run the entire assertion in a retry block and on each pass: + // 1. Send the exact number of requests that are allowed per the rate limiting configuration + // and check that all the requests succeed. + // 2. Send an extra request that should exceed the configured rate limit and check that this request fails. + // 3. Make sure that all requests happened within the rate limit enforcement window of one second. + retry.RunWith(opts.retryTimer, t, func(r *retry.R) { + // Make up to the allowed numbers of calls in a second + t0 := time.Now() + for i := 0; i < opts.rps; i++ { + output, err := k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, args...) + require.NoError(r, err) + require.Contains(r, output, opts.successOutput) + } + + // Exceed the configured rate limit. + output, err := k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, args...) + if opts.enforced { + require.Error(r, err) + require.Contains(r, output, opts.rateLimitOutput, "request was not rate limited") + } else { + require.NoError(r, err) + require.Contains(r, output, opts.successOutput, "request was not successful") + } + require.True(r, time.Since(t0) < time.Second, "failed to make all requests within one second window") + }) +} + +type assertRateLimitOptions struct { + resourceType string + successOutput string + rateLimitOutput string + k8sOpts *terratestK8s.KubectlOptions + sourceApp string + rps int + enforced bool + retryTimer *retry.Timer + curlOpts string +} + +func newRateLimitOptions(t *testing.T, ctx environment.TestContext) *assertRateLimitOptions { + return &assertRateLimitOptions{ + resourceType: "deploy/", + successOutput: "hello world", + rateLimitOutput: "curl: (22) The requested URL returned error: 429", + k8sOpts: ctx.KubectlOptions(t), + sourceApp: connhelper.StaticClientName, + retryTimer: &retry.Timer{Timeout: 120 * time.Second, Wait: 2 * time.Second}, + curlOpts: "-vvvsSf", + } +} diff --git a/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml b/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml new file mode 100644 index 0000000000..b38b25696d --- /dev/null +++ b/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml @@ -0,0 +1,23 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: static-server + namespace: default +spec: + protocol: http + rateLimits: + instanceLevel: + requestsPerSecond: 2 + requestsMaxBurst: 2 + routes: + - pathExact: "/exact" + requestsPerSecond: 3 + requestsMaxBurst: 3 + - pathPrefix: "/prefix" + requestsPerSecond: 4 + - pathRegex: "/regex" + requestsPerSecond: 5 + From e20ff98d871e2b244c2a8bad0d1a6e5c13258fe8 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Tue, 12 Sep 2023 11:41:38 -0400 Subject: [PATCH 375/592] [NET-5574] Update Go version to 1.20.8 (#2936) Update Go version to 1.20.8 This resolves several CVEs (see changelog entry). --- .changelog/2936.txt | 8 ++++++++ .go-version | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .changelog/2936.txt diff --git a/.changelog/2936.txt b/.changelog/2936.txt new file mode 100644 index 0000000000..923f9383f4 --- /dev/null +++ b/.changelog/2936.txt @@ -0,0 +1,8 @@ +```release-note:security +Upgrade to use Go 1.20.8. This resolves CVEs +[CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), +[CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), +[CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), +[CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and +[CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) +``` diff --git a/.go-version b/.go-version index 8909929f6e..95393fc7d4 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.20.7 +1.20.8 From c73605efb9a58ce0962688829662140f68ec0dad Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Tue, 12 Sep 2023 12:08:10 -0400 Subject: [PATCH 376/592] Create mesh webhook to support v2 resources (#2930) * mesh webhook v2 --- .../constants/annotations_and_labels.go | 6 + .../metrics/metrics_configuration.go | 3 +- .../webhook_v2/consul_dataplane_sidecar.go | 471 ++++ .../consul_dataplane_sidecar_test.go | 1103 +++++++++ .../webhook_v2/container_env.go | 37 + .../webhook_v2/container_env_test.go | 58 + .../webhook_v2/container_init.go | 300 +++ .../webhook_v2/container_init_test.go | 844 +++++++ .../webhook_v2/container_volume.go | 23 + .../connect-inject/webhook_v2/dns.go | 93 + .../connect-inject/webhook_v2/dns_test.go | 105 + .../webhook_v2/health_checks_test.go | 56 + .../connect-inject/webhook_v2/heath_checks.go | 30 + .../connect-inject/webhook_v2/mesh_webhook.go | 563 +++++ .../webhook_v2/mesh_webhook_ent_test.go | 656 ++++++ .../webhook_v2/mesh_webhook_test.go | 2043 +++++++++++++++++ .../webhook_v2/redirect_traffic.go | 137 ++ .../webhook_v2/redirect_traffic_test.go | 481 ++++ .../inject-connect/v2controllers.go | 72 +- 19 files changed, 7071 insertions(+), 10 deletions(-) create mode 100644 control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go create mode 100644 control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go create mode 100644 control-plane/connect-inject/webhook_v2/container_env.go create mode 100644 control-plane/connect-inject/webhook_v2/container_env_test.go create mode 100644 control-plane/connect-inject/webhook_v2/container_init.go create mode 100644 control-plane/connect-inject/webhook_v2/container_init_test.go create mode 100644 control-plane/connect-inject/webhook_v2/container_volume.go create mode 100644 control-plane/connect-inject/webhook_v2/dns.go create mode 100644 control-plane/connect-inject/webhook_v2/dns_test.go create mode 100644 control-plane/connect-inject/webhook_v2/health_checks_test.go create mode 100644 control-plane/connect-inject/webhook_v2/heath_checks.go create mode 100644 control-plane/connect-inject/webhook_v2/mesh_webhook.go create mode 100644 control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go create mode 100644 control-plane/connect-inject/webhook_v2/mesh_webhook_test.go create mode 100644 control-plane/connect-inject/webhook_v2/redirect_traffic.go create mode 100644 control-plane/connect-inject/webhook_v2/redirect_traffic_test.go diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 5c14cb15f2..cd563f2436 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -229,6 +229,12 @@ const ( // ManagedByPodValue is used in Consul metadata to identify the manager // of resources. ManagedByPodValue = "consul-k8s-pod-controller" + + // AnnotationMeshDestinations is a list of upstreams to register with the + // proxy. The service name should map to a Consul service namd and the local + // port is the local port in the pod that the listener will bind to. It can + // be a named port. + AnnotationMeshDestinations = "consul.hashicorp.com/mesh-service-destinations" ) // Annotations used by Prometheus. diff --git a/control-plane/connect-inject/metrics/metrics_configuration.go b/control-plane/connect-inject/metrics/metrics_configuration.go index 6f9c29c85b..2f217f233d 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration.go +++ b/control-plane/connect-inject/metrics/metrics_configuration.go @@ -8,9 +8,10 @@ import ( "fmt" "strconv" + corev1 "k8s.io/api/core/v1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - corev1 "k8s.io/api/core/v1" ) // Config represents configuration common to connect-inject components related to metrics. diff --git a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go new file mode 100644 index 0000000000..5576f919d4 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go @@ -0,0 +1,471 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/google/shlex" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + consulDataplaneDNSBindHost = "127.0.0.1" + consulDataplaneDNSBindPort = 8600 +) + +func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod corev1.Pod) (corev1.Container, error) { + resources, err := w.sidecarResources(pod) + if err != nil { + return corev1.Container{}, err + } + + // Extract the service account token's volume mount. + var bearerTokenFile string + var saTokenVolumeMount corev1.VolumeMount + if w.AuthMethod != "" { + saTokenVolumeMount, bearerTokenFile, err = findServiceAccountVolumeMount(pod) + if err != nil { + return corev1.Container{}, err + } + } + + args, err := w.getContainerSidecarArgs(namespace, bearerTokenFile, pod) + if err != nil { + return corev1.Container{}, err + } + + containerName := sidecarContainer + + var probe *corev1.Probe + if useProxyHealthCheck(pod) { + // If using the proxy health check for a service, configure an HTTP handler + // that queries the '/ready' endpoint of the proxy. + probe = &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(constants.ProxyDefaultHealthPort), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + } + } else { + probe = &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(constants.ProxyDefaultInboundPort), + }, + }, + InitialDelaySeconds: 1, + } + } + + container := corev1.Container{ + Name: containerName, + Image: w.ImageConsulDataplane, + Resources: resources, + // We need to set tmp dir to an ephemeral volume that we're mounting so that + // consul-dataplane can write files to it. Otherwise, it wouldn't be able to + // because we set file system to be read-only. + Env: []corev1.EnvVar{ + { + Name: "TMPDIR", + Value: "/consul/mesh-inject", + }, + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + // The pod name isn't known currently, so we must rely on the environment variable to fill it in rather than using args. + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, + { + Name: "DP_PROXY_ID", + Value: "$(POD_NAME)", + }, + { + Name: "DP_CREDENTIAL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }, + // This entry exists to support certain versions of consul dataplane, where environment variable entries + // utilize this numbered notation to indicate individual KV pairs in a map. + { + Name: "DP_CREDENTIAL_LOGIN_META1", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/consul/mesh-inject", + }, + }, + Args: args, + ReadinessProbe: probe, + } + + if w.AuthMethod != "" { + container.VolumeMounts = append(container.VolumeMounts, saTokenVolumeMount) + } + + if useProxyHealthCheck(pod) { + // Configure the Readiness Address for the proxy's health check to be the Pod IP. + container.Env = append(container.Env, corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + }) + // Configure the port on which the readiness probe will query the proxy for its health. + container.Ports = append(container.Ports, corev1.ContainerPort{ + Name: "proxy-health", + ContainerPort: int32(constants.ProxyDefaultHealthPort), + }) + } + + // Add any extra VolumeMounts. + if userVolMount, ok := pod.Annotations[constants.AnnotationConsulSidecarUserVolumeMount]; ok { + var volumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(userVolMount), &volumeMounts) + if err != nil { + return corev1.Container{}, err + } + container.VolumeMounts = append(container.VolumeMounts, volumeMounts...) + } + + tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) + if err != nil { + return corev1.Container{}, err + } + + // If not running in transparent proxy mode and in an OpenShift environment, + // skip setting the security context and let OpenShift set it for us. + // When transparent proxy is enabled, then consul-dataplane needs to run as our specific user + // so that traffic redirection will work. + if tproxyEnabled || !w.EnableOpenShift { + if pod.Spec.SecurityContext != nil { + // User container and consul-dataplane container cannot have the same UID. + if pod.Spec.SecurityContext.RunAsUser != nil && *pod.Spec.SecurityContext.RunAsUser == sidecarUserAndGroupID { + return corev1.Container{}, fmt.Errorf("pod's security context cannot have the same UID as consul-dataplane: %v", sidecarUserAndGroupID) + } + } + // Ensure that none of the user's containers have the same UID as consul-dataplane. At this point in injection the meshWebhook + // has only injected init containers so all containers defined in pod.Spec.Containers are from the user. + for _, c := range pod.Spec.Containers { + // User container and consul-dataplane container cannot have the same UID. + if c.SecurityContext != nil && c.SecurityContext.RunAsUser != nil && *c.SecurityContext.RunAsUser == sidecarUserAndGroupID && c.Image != w.ImageConsulDataplane { + return corev1.Container{}, fmt.Errorf("container %q has runAsUser set to the same UID \"%d\" as consul-dataplane which is not allowed", c.Name, sidecarUserAndGroupID) + } + } + container.SecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + } + } + + return container, nil +} + +func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, bearerTokenFile string, pod corev1.Pod) ([]string, error) { + envoyConcurrency := w.DefaultEnvoyProxyConcurrency + + // Check to see if the user has overriden concurrency via an annotation. + if envoyConcurrencyAnnotation, ok := pod.Annotations[constants.AnnotationEnvoyProxyConcurrency]; ok { + val, err := strconv.ParseUint(envoyConcurrencyAnnotation, 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse annotation %q: %w", constants.AnnotationEnvoyProxyConcurrency, err) + } + envoyConcurrency = int(val) + } + + args := []string{ + "-addresses", w.ConsulAddress, + "-grpc-port=" + strconv.Itoa(w.ConsulConfig.GRPCPort), + "-log-level=" + w.LogLevel, + "-log-json=" + strconv.FormatBool(w.LogJSON), + "-envoy-concurrency=" + strconv.Itoa(envoyConcurrency), + } + + if w.SkipServerWatch { + args = append(args, "-server-watch-disabled=true") + } + + if w.AuthMethod != "" { + args = append(args, + "-credential-type=login", + "-login-auth-method="+w.AuthMethod, + "-login-bearer-token-path="+bearerTokenFile, + // We don't know the pod name at this time, so we must use environment variables to populate the login-meta instead. + ) + if w.EnableNamespaces { + if w.EnableK8SNSMirroring { + args = append(args, "-login-namespace=default") + } else { + args = append(args, "-login-namespace="+w.consulNamespace(namespace.Name)) + } + } + if w.ConsulPartition != "" { + args = append(args, "-login-partition="+w.ConsulPartition) + } + } + if w.EnableNamespaces { + args = append(args, "-proxy-namespace="+w.consulNamespace(namespace.Name)) + } + if w.ConsulPartition != "" { + args = append(args, "-proxy-partition="+w.ConsulPartition) + } + if w.TLSEnabled { + if w.ConsulTLSServerName != "" { + args = append(args, "-tls-server-name="+w.ConsulTLSServerName) + } + if w.ConsulCACert != "" { + args = append(args, "-ca-certs="+constants.ConsulCAFile) + } + } else { + args = append(args, "-tls-disabled") + } + + // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. + if useProxyHealthCheck(pod) { + args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) + } + + // The consul-dataplane HTTP listener always starts for graceful shutdown. To avoid port conflicts, the + // graceful port always needs to be set + gracefulPort, err := w.LifecycleConfig.GracefulPort(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine proxy lifecycle graceful port: %w", err) + } + + args = append(args, fmt.Sprintf("-graceful-port=%d", gracefulPort)) + + enableProxyLifecycle, err := w.LifecycleConfig.EnableProxyLifecycle(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine if proxy lifecycle management is enabled: %w", err) + } + if enableProxyLifecycle { + shutdownDrainListeners, err := w.LifecycleConfig.EnableShutdownDrainListeners(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine if proxy lifecycle shutdown listener draining is enabled: %w", err) + } + if shutdownDrainListeners { + args = append(args, "-shutdown-drain-listeners") + } + + shutdownGracePeriodSeconds, err := w.LifecycleConfig.ShutdownGracePeriodSeconds(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine proxy lifecycle shutdown grace period: %w", err) + } + args = append(args, fmt.Sprintf("-shutdown-grace-period-seconds=%d", shutdownGracePeriodSeconds)) + + gracefulShutdownPath := w.LifecycleConfig.GracefulShutdownPath(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine proxy lifecycle graceful shutdown path: %w", err) + } + args = append(args, fmt.Sprintf("-graceful-shutdown-path=%s", gracefulShutdownPath)) + } + + // Set a default scrape path that can be overwritten by the annotation. + prometheusScrapePath := w.MetricsConfig.PrometheusScrapePath(pod) + args = append(args, "-telemetry-prom-scrape-path="+prometheusScrapePath) + + metricsServer, err := w.MetricsConfig.ShouldRunMergedMetricsServer(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine if merged metrics is enabled: %w", err) + } + if metricsServer { + + // (TODO) Figure out what port will be used for merged metrics and setup merged metrics + + mergedMetricsPort, err := w.MetricsConfig.MergedMetricsPort(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine if merged metrics port: %w", err) + } + args = append(args, "-telemetry-prom-merge-port="+mergedMetricsPort) + + // Pull the TLS config from the relevant annotations. + var prometheusCAFile string + if raw, ok := pod.Annotations[constants.AnnotationPrometheusCAFile]; ok && raw != "" { + prometheusCAFile = raw + } + + var prometheusCAPath string + if raw, ok := pod.Annotations[constants.AnnotationPrometheusCAPath]; ok && raw != "" { + prometheusCAPath = raw + } + + var prometheusCertFile string + if raw, ok := pod.Annotations[constants.AnnotationPrometheusCertFile]; ok && raw != "" { + prometheusCertFile = raw + } + + var prometheusKeyFile string + if raw, ok := pod.Annotations[constants.AnnotationPrometheusKeyFile]; ok && raw != "" { + prometheusKeyFile = raw + } + + // Validate required Prometheus TLS config is present if set. + if prometheusCAFile != "" || prometheusCAPath != "" || prometheusCertFile != "" || prometheusKeyFile != "" { + if prometheusCAFile == "" && prometheusCAPath == "" { + return nil, fmt.Errorf("must set one of %q or %q when providing prometheus TLS config", constants.AnnotationPrometheusCAFile, constants.AnnotationPrometheusCAPath) + } + if prometheusCertFile == "" { + return nil, fmt.Errorf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusCertFile) + } + if prometheusKeyFile == "" { + return nil, fmt.Errorf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusKeyFile) + } + // TLS config has been validated, add them to the consul-dataplane cmd args + args = append(args, "-telemetry-prom-ca-certs-file="+prometheusCAFile, + "-telemetry-prom-ca-certs-path="+prometheusCAPath, + "-telemetry-prom-cert-file="+prometheusCertFile, + "-telemetry-prom-key-file="+prometheusKeyFile) + } + } + + // If Consul DNS is enabled, we want to configure consul-dataplane to be the DNS proxy + // for Consul DNS in the pod. + dnsEnabled, err := consulDNSEnabled(namespace, pod, w.EnableConsulDNS, w.EnableTransparentProxy) + if err != nil { + return nil, err + } + if dnsEnabled { + args = append(args, "-consul-dns-bind-port="+strconv.Itoa(consulDataplaneDNSBindPort)) + } + + var envoyExtraArgs []string + extraArgs, annotationSet := pod.Annotations[constants.AnnotationEnvoyExtraArgs] + + if annotationSet || w.EnvoyExtraArgs != "" { + extraArgsToUse := w.EnvoyExtraArgs + + // Prefer args set by pod annotation over the flag to the consul-k8s binary (h.EnvoyExtraArgs). + if annotationSet { + extraArgsToUse = extraArgs + } + + // Split string into tokens. + // e.g. "--foo bar --boo baz" --> ["--foo", "bar", "--boo", "baz"] + tokens, err := shlex.Split(extraArgsToUse) + if err != nil { + return []string{}, err + } + for _, t := range tokens { + if strings.Contains(t, " ") { + t = strconv.Quote(t) + } + envoyExtraArgs = append(envoyExtraArgs, t) + } + } + if envoyExtraArgs != nil { + args = append(args, "--") + args = append(args, envoyExtraArgs...) + } + return args, nil +} + +func (w *MeshWebhook) sidecarResources(pod corev1.Pod) (corev1.ResourceRequirements, error) { + resources := corev1.ResourceRequirements{ + Limits: corev1.ResourceList{}, + Requests: corev1.ResourceList{}, + } + // zeroQuantity is used for comparison to see if a quantity was explicitly + // set. + var zeroQuantity resource.Quantity + + // NOTE: We only want to set the limit/request if the default or annotation + // was explicitly set. If it's not explicitly set, it will be the zero value + // which would show up in the pod spec as being explicitly set to zero if we + // set that key, e.g. "cpu" to zero. + // We want it to not show up in the pod spec at all if it's not explicitly + // set so that users aren't wondering why it's set to 0 when they didn't specify + // a request/limit. If they have explicitly set it to 0 then it will be set + // to 0 in the pod spec because we're doing a comparison to the zero-valued + // struct. + + // CPU Limit. + if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyCPULimit]; ok { + cpuLimit, err := resource.ParseQuantity(anno) + if err != nil { + return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyCPULimit, anno, err) + } + resources.Limits[corev1.ResourceCPU] = cpuLimit + } else if w.DefaultProxyCPULimit != zeroQuantity { + resources.Limits[corev1.ResourceCPU] = w.DefaultProxyCPULimit + } + + // CPU Request. + if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyCPURequest]; ok { + cpuRequest, err := resource.ParseQuantity(anno) + if err != nil { + return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyCPURequest, anno, err) + } + resources.Requests[corev1.ResourceCPU] = cpuRequest + } else if w.DefaultProxyCPURequest != zeroQuantity { + resources.Requests[corev1.ResourceCPU] = w.DefaultProxyCPURequest + } + + // Memory Limit. + if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyMemoryLimit]; ok { + memoryLimit, err := resource.ParseQuantity(anno) + if err != nil { + return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyMemoryLimit, anno, err) + } + resources.Limits[corev1.ResourceMemory] = memoryLimit + } else if w.DefaultProxyMemoryLimit != zeroQuantity { + resources.Limits[corev1.ResourceMemory] = w.DefaultProxyMemoryLimit + } + + // Memory Request. + if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyMemoryRequest]; ok { + memoryRequest, err := resource.ParseQuantity(anno) + if err != nil { + return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyMemoryRequest, anno, err) + } + resources.Requests[corev1.ResourceMemory] = memoryRequest + } else if w.DefaultProxyMemoryRequest != zeroQuantity { + resources.Requests[corev1.ResourceMemory] = w.DefaultProxyMemoryRequest + } + + return resources, nil +} + +// useProxyHealthCheck returns true if the pod has the annotation 'consul.hashicorp.com/use-proxy-health-check' +// set to truthy values. +func useProxyHealthCheck(pod corev1.Pod) bool { + if v, ok := pod.Annotations[constants.AnnotationUseProxyHealthCheck]; ok { + useProxyHealthCheck, err := strconv.ParseBool(v) + if err != nil { + return false + } + return useProxyHealthCheck + } + return false +} diff --git a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go new file mode 100644 index 0000000000..aaa94a191d --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go @@ -0,0 +1,1103 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/consul" +) + +const nodeName = "test-node" + +func TestHandlerConsulDataplaneSidecar(t *testing.T) { + cases := map[string]struct { + webhookSetupFunc func(w *MeshWebhook) + additionalExpCmdArgs string + }{ + "default": { + webhookSetupFunc: nil, + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with custom gRPC port": { + webhookSetupFunc: func(w *MeshWebhook) { + w.ConsulConfig.GRPCPort = 8602 + }, + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with ACLs": { + webhookSetupFunc: func(w *MeshWebhook) { + w.AuthMethod = "test-auth-method" + }, + additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + + "-tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with ACLs and namespace mirroring": { + webhookSetupFunc: func(w *MeshWebhook) { + w.AuthMethod = "test-auth-method" + w.EnableNamespaces = true + w.EnableK8SNSMirroring = true + }, + additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + + "-login-namespace=default -proxy-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with ACLs and single destination namespace": { + webhookSetupFunc: func(w *MeshWebhook) { + w.AuthMethod = "test-auth-method" + w.EnableNamespaces = true + w.ConsulDestinationNamespace = "test-ns" + }, + additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + + "-login-namespace=test-ns -proxy-namespace=test-ns -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with ACLs and partitions": { + webhookSetupFunc: func(w *MeshWebhook) { + w.AuthMethod = "test-auth-method" + w.ConsulPartition = "test-part" + }, + additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + + "-login-partition=test-part -proxy-partition=test-part -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with TLS and CA cert provided": { + webhookSetupFunc: func(w *MeshWebhook) { + w.TLSEnabled = true + w.ConsulTLSServerName = "server.dc1.consul" + w.ConsulCACert = "consul-ca-cert" + }, + additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -ca-certs=/consul/connect-inject/consul-ca.pem -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with TLS and no CA cert provided": { + webhookSetupFunc: func(w *MeshWebhook) { + w.TLSEnabled = true + w.ConsulTLSServerName = "server.dc1.consul" + }, + additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with single destination namespace": { + webhookSetupFunc: func(w *MeshWebhook) { + w.EnableNamespaces = true + w.ConsulDestinationNamespace = "consul-namespace" + }, + additionalExpCmdArgs: " -proxy-namespace=consul-namespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with namespace mirroring": { + webhookSetupFunc: func(w *MeshWebhook) { + w.EnableNamespaces = true + w.EnableK8SNSMirroring = true + }, + additionalExpCmdArgs: " -proxy-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with namespace mirroring prefix": { + webhookSetupFunc: func(w *MeshWebhook) { + w.EnableNamespaces = true + w.EnableK8SNSMirroring = true + w.K8SNSMirroringPrefix = "foo-" + }, + additionalExpCmdArgs: " -proxy-namespace=foo-k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with partitions": { + webhookSetupFunc: func(w *MeshWebhook) { + w.ConsulPartition = "partition-1" + }, + additionalExpCmdArgs: " -proxy-partition=partition-1 -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with different log level": { + webhookSetupFunc: func(w *MeshWebhook) { + w.LogLevel = "debug" + }, + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "with different log level and log json": { + webhookSetupFunc: func(w *MeshWebhook) { + w.LogLevel = "debug" + w.LogJSON = true + }, + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "skip server watch enabled": { + webhookSetupFunc: func(w *MeshWebhook) { + w.SkipServerWatch = true + }, + additionalExpCmdArgs: " -server-watch-disabled=true -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + }, + "custom prometheus scrape path": { + webhookSetupFunc: func(w *MeshWebhook) { + w.MetricsConfig.DefaultPrometheusScrapePath = "/scrape-path" // Simulate what would be passed as a flag + }, + additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/scrape-path", + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + w := &MeshWebhook{ + ConsulAddress: "1.1.1.1", + ConsulConfig: &consul.Config{GRPCPort: 8502}, + LogLevel: "info", + LogJSON: false, + } + if c.webhookSetupFunc != nil { + c.webhookSetupFunc(w) + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web-side", + }, + { + Name: "auth-method-secret", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "service-account-secret", + MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + }, + }, + }, + }, + ServiceAccountName: "web", + NodeName: nodeName, + }, + } + + container, err := w.consulDataplaneSidecar(testNS, pod) + require.NoError(t, err) + expCmd := "-addresses 1.1.1.1 -grpc-port=" + strconv.Itoa(w.ConsulConfig.GRPCPort) + + " -log-level=" + w.LogLevel + " -log-json=" + strconv.FormatBool(w.LogJSON) + " -envoy-concurrency=0" + c.additionalExpCmdArgs + require.Equal(t, expCmd, strings.Join(container.Args, " ")) + + if w.AuthMethod != "" { + require.Equal(t, container.VolumeMounts, []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/consul/mesh-inject", + }, + { + Name: "service-account-secret", + MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + }, + }) + } else { + require.Equal(t, container.VolumeMounts, []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/consul/mesh-inject", + }, + }) + } + + expectedProbe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(constants.ProxyDefaultInboundPort), + }, + }, + InitialDelaySeconds: 1, + } + require.Equal(t, expectedProbe, container.ReadinessProbe) + require.Nil(t, container.StartupProbe) + require.Len(t, container.Env, 7) + require.Equal(t, container.Env[0].Name, "TMPDIR") + require.Equal(t, container.Env[0].Value, "/consul/mesh-inject") + require.Equal(t, container.Env[2].Name, "POD_NAME") + require.Equal(t, container.Env[3].Name, "POD_NAMESPACE") + require.Equal(t, container.Env[4].Name, "DP_PROXY_ID") + require.Equal(t, container.Env[4].Value, "$(POD_NAME)") + require.Equal(t, container.Env[5].Name, "DP_CREDENTIAL_LOGIN_META") + require.Equal(t, container.Env[5].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") + require.Equal(t, container.Env[6].Name, "DP_CREDENTIAL_LOGIN_META1") + require.Equal(t, container.Env[6].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") + }) + } +} + +func TestHandlerConsulDataplaneSidecar_Concurrency(t *testing.T) { + cases := map[string]struct { + annotations map[string]string + expFlags string + expErr string + }{ + "default settings, no annotations": { + annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + expFlags: "-envoy-concurrency=0", + }, + "default settings, annotation override": { + annotations: map[string]string{ + constants.AnnotationService: "foo", + constants.AnnotationEnvoyProxyConcurrency: "42", + }, + expFlags: "-envoy-concurrency=42", + }, + "default settings, invalid concurrency annotation negative number": { + annotations: map[string]string{ + constants.AnnotationService: "foo", + constants.AnnotationEnvoyProxyConcurrency: "-42", + }, + expErr: "unable to parse annotation \"consul.hashicorp.com/consul-envoy-proxy-concurrency\": strconv.ParseUint: parsing \"-42\": invalid syntax", + }, + "default settings, not-parseable concurrency annotation": { + annotations: map[string]string{ + constants.AnnotationService: "foo", + constants.AnnotationEnvoyProxyConcurrency: "not-int", + }, + expErr: "unable to parse annotation \"consul.hashicorp.com/consul-envoy-proxy-concurrency\": strconv.ParseUint: parsing \"not-int\": invalid syntax", + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + h := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: c.annotations, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + container, err := h.consulDataplaneSidecar(testNS, pod) + if c.expErr != "" { + require.EqualError(t, err, c.expErr) + } else { + require.NoError(t, err) + require.Contains(t, strings.Join(container.Args, " "), c.expFlags) + } + }) + } +} + +// Test that we pass the dns proxy flag to dataplane correctly. +func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { + + // We only want the flag passed when DNS and tproxy are both enabled. DNS/tproxy can + // both be enabled/disabled with annotations/labels on the pod and namespace and then globally + // through the helm chart. To test this we use an outer loop with the possible DNS settings and then + // and inner loop with possible tproxy settings. + dnsCases := []struct { + GlobalConsulDNS bool + NamespaceDNS *bool + PodDNS *bool + ExpEnabled bool + }{ + { + GlobalConsulDNS: false, + ExpEnabled: false, + }, + { + GlobalConsulDNS: true, + ExpEnabled: true, + }, + { + GlobalConsulDNS: false, + NamespaceDNS: boolPtr(true), + ExpEnabled: true, + }, + { + GlobalConsulDNS: false, + PodDNS: boolPtr(true), + ExpEnabled: true, + }, + } + tproxyCases := []struct { + GlobalTProxy bool + NamespaceTProxy *bool + PodTProxy *bool + ExpEnabled bool + }{ + { + GlobalTProxy: false, + ExpEnabled: false, + }, + { + GlobalTProxy: true, + ExpEnabled: true, + }, + { + GlobalTProxy: false, + NamespaceTProxy: boolPtr(true), + ExpEnabled: true, + }, + { + GlobalTProxy: false, + PodTProxy: boolPtr(true), + ExpEnabled: true, + }, + } + + // Outer loop is permutations of dns being enabled. Inner loop is permutations of tproxy being enabled. + // Both must be enabled for dns to be enabled. + for i, dnsCase := range dnsCases { + for j, tproxyCase := range tproxyCases { + t.Run(fmt.Sprintf("dns=%d,tproxy=%d", i, j), func(t *testing.T) { + + // Test setup. + h := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + EnableTransparentProxy: tproxyCase.GlobalTProxy, + EnableConsulDNS: dnsCase.GlobalConsulDNS, + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + if dnsCase.PodDNS != nil { + pod.Annotations[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.PodDNS) + } + if tproxyCase.PodTProxy != nil { + pod.Annotations[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.PodTProxy) + } + + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: k8sNamespace, + Labels: map[string]string{}, + }, + } + if dnsCase.NamespaceDNS != nil { + ns.Labels[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.NamespaceDNS) + } + if tproxyCase.NamespaceTProxy != nil { + ns.Labels[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.NamespaceTProxy) + } + + // Actual test here. + container, err := h.consulDataplaneSidecar(ns, pod) + require.NoError(t, err) + // Flag should only be passed if both tproxy and dns are enabled. + if tproxyCase.ExpEnabled && dnsCase.ExpEnabled { + require.Contains(t, container.Args, "-consul-dns-bind-port=8600") + } else { + require.NotContains(t, container.Args, "-consul-dns-bind-port=8600") + } + }) + } + } +} + +func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck(t *testing.T) { + h := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + ConsulAddress: "1.1.1.1", + LogLevel: "info", + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationUseProxyHealthCheck: "true", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + container, err := h.consulDataplaneSidecar(testNS, pod) + expectedProbe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + } + require.NoError(t, err) + require.Contains(t, container.Args, "-envoy-ready-bind-port=21000") + require.Equal(t, expectedProbe, container.ReadinessProbe) + require.Contains(t, container.Env, corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + }) + require.Contains(t, container.Ports, corev1.ContainerPort{ + Name: "proxy-health", + ContainerPort: 21000, + }) +} + +func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { + cases := map[string]struct { + tproxyEnabled bool + openShiftEnabled bool + expSecurityContext *corev1.SecurityContext + }{ + "tproxy disabled; openshift disabled": { + tproxyEnabled: false, + openShiftEnabled: false, + expSecurityContext: &corev1.SecurityContext{ + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + }, + }, + "tproxy enabled; openshift disabled": { + tproxyEnabled: true, + openShiftEnabled: false, + expSecurityContext: &corev1.SecurityContext{ + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + }, + }, + "tproxy disabled; openshift enabled": { + tproxyEnabled: false, + openShiftEnabled: true, + expSecurityContext: nil, + }, + "tproxy enabled; openshift enabled": { + tproxyEnabled: true, + openShiftEnabled: true, + expSecurityContext: &corev1.SecurityContext{ + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + w := MeshWebhook{ + EnableTransparentProxy: c.tproxyEnabled, + EnableOpenShift: c.openShiftEnabled, + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + ec, err := w.consulDataplaneSidecar(testNS, pod) + require.NoError(t, err) + require.Equal(t, c.expSecurityContext, ec.SecurityContext) + }) + } +} + +// Test that if the user specifies a pod security context with the same uid as `sidecarUserAndGroupID` that we return +// an error to the meshWebhook. +func TestHandlerConsulDataplaneSidecar_FailsWithDuplicatePodSecurityContextUID(t *testing.T) { + require := require.New(t) + w := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + } + pod := corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + SecurityContext: &corev1.PodSecurityContext{ + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + }, + }, + } + _, err := w.consulDataplaneSidecar(testNS, pod) + require.EqualError(err, fmt.Sprintf("pod's security context cannot have the same UID as consul-dataplane: %v", sidecarUserAndGroupID)) +} + +// Test that if the user specifies a container with security context with the same uid as `sidecarUserAndGroupID` that we +// return an error to the meshWebhook. If a container using the consul-dataplane image has the same uid, we don't return an error +// because in multiport pod there can be multiple consul-dataplane sidecars. +func TestHandlerConsulDataplaneSidecar_FailsWithDuplicateContainerSecurityContextUID(t *testing.T) { + cases := []struct { + name string + pod corev1.Pod + webhook MeshWebhook + expErr bool + expErrMessage string + }{ + { + name: "fails with non consul-dataplane image", + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + // Setting RunAsUser: 1 should succeed. + SecurityContext: &corev1.SecurityContext{ + RunAsUser: pointer.Int64(1), + }, + }, + { + Name: "app", + // Setting RunAsUser: 5995 should fail. + SecurityContext: &corev1.SecurityContext{ + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + }, + Image: "not-consul-dataplane", + }, + }, + }, + }, + webhook: MeshWebhook{}, + expErr: true, + expErrMessage: fmt.Sprintf("container \"app\" has runAsUser set to the same UID \"%d\" as consul-dataplane which is not allowed", sidecarUserAndGroupID), + }, + { + name: "doesn't fail with envoy image", + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + // Setting RunAsUser: 1 should succeed. + SecurityContext: &corev1.SecurityContext{ + RunAsUser: pointer.Int64(1), + }, + }, + { + Name: "sidecar", + // Setting RunAsUser: 5995 should succeed if the image matches h.ImageConsulDataplane. + SecurityContext: &corev1.SecurityContext{ + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + }, + Image: "envoy", + }, + }, + }, + }, + webhook: MeshWebhook{ + ImageConsulDataplane: "envoy", + }, + expErr: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tc.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} + _, err := tc.webhook.consulDataplaneSidecar(testNS, tc.pod) + if tc.expErr { + require.EqualError(t, err, tc.expErrMessage) + } else { + require.NoError(t, err) + } + }) + } +} + +// Test that we can pass extra args to envoy via the extraEnvoyArgs flag +// or via pod annotations. When arguments are passed in both ways, the +// arguments set via pod annotations are used. +func TestHandlerConsulDataplaneSidecar_EnvoyExtraArgs(t *testing.T) { + cases := []struct { + name string + envoyExtraArgs string + pod *corev1.Pod + expectedExtraArgs string + }{ + { + name: "no extra options provided", + envoyExtraArgs: "", + pod: &corev1.Pod{}, + expectedExtraArgs: "", + }, + { + name: "via flag: extra log-level option", + envoyExtraArgs: "--log-level debug", + pod: &corev1.Pod{}, + expectedExtraArgs: "-- --log-level debug", + }, + { + name: "via flag: multiple arguments with quotes", + envoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", + pod: &corev1.Pod{}, + expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", + }, + { + name: "via annotation: multiple arguments with quotes", + envoyExtraArgs: "", + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", + }, + }, + }, + expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", + }, + { + name: "via flag and annotation: should prefer setting via the annotation", + envoyExtraArgs: "this should be overwritten", + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", + }, + }, + }, + expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + h := MeshWebhook{ + ImageConsul: "hashicorp/consul:latest", + ImageConsulDataplane: "hashicorp/consul-k8s:latest", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + EnvoyExtraArgs: tc.envoyExtraArgs, + } + + c, err := h.consulDataplaneSidecar(testNS, *tc.pod) + require.NoError(t, err) + require.Contains(t, strings.Join(c.Args, " "), tc.expectedExtraArgs) + }) + } +} + +func TestHandlerConsulDataplaneSidecar_UserVolumeMounts(t *testing.T) { + cases := []struct { + name string + pod corev1.Pod + expectedContainerVolumeMounts []corev1.VolumeMount + expErr string + }{ + { + name: "able to set a sidecar container volume mount via annotation", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", + constants.AnnotationConsulSidecarUserVolumeMount: "[{\"name\": \"tls-cert\", \"mountPath\": \"/custom/path\"}, {\"name\": \"tls-ca\", \"mountPath\": \"/custom/path2\"}]", + }, + }, + }, + expectedContainerVolumeMounts: []corev1.VolumeMount{ + { + Name: "consul-connect-inject-data", + MountPath: "/consul/mesh-inject", + }, + { + Name: "tls-cert", + MountPath: "/custom/path", + }, + { + Name: "tls-ca", + MountPath: "/custom/path2", + }, + }, + }, + { + name: "invalid annotation results in error", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", + constants.AnnotationConsulSidecarUserVolumeMount: "[abcdefg]", + }, + }, + }, + expErr: "invalid character 'a' looking ", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + h := MeshWebhook{ + ImageConsul: "hashicorp/consul:latest", + ImageConsulDataplane: "hashicorp/consul-k8s:latest", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + } + c, err := h.consulDataplaneSidecar(testNS, tc.pod) + if tc.expErr == "" { + require.NoError(t, err) + require.Equal(t, tc.expectedContainerVolumeMounts, c.VolumeMounts) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expErr) + } + }) + } +} + +func TestHandlerConsulDataplaneSidecar_Resources(t *testing.T) { + mem1 := resource.MustParse("100Mi") + mem2 := resource.MustParse("200Mi") + cpu1 := resource.MustParse("100m") + cpu2 := resource.MustParse("200m") + zero := resource.MustParse("0") + + cases := map[string]struct { + webhook MeshWebhook + annotations map[string]string + expResources corev1.ResourceRequirements + expErr string + }{ + "no defaults, no annotations": { + webhook: MeshWebhook{}, + annotations: nil, + expResources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{}, + Requests: corev1.ResourceList{}, + }, + }, + "all defaults, no annotations": { + webhook: MeshWebhook{ + DefaultProxyCPURequest: cpu1, + DefaultProxyCPULimit: cpu2, + DefaultProxyMemoryRequest: mem1, + DefaultProxyMemoryLimit: mem2, + }, + annotations: nil, + expResources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: cpu2, + corev1.ResourceMemory: mem2, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: cpu1, + corev1.ResourceMemory: mem1, + }, + }, + }, + "no defaults, all annotations": { + webhook: MeshWebhook{}, + annotations: map[string]string{ + constants.AnnotationSidecarProxyCPURequest: "100m", + constants.AnnotationSidecarProxyMemoryRequest: "100Mi", + constants.AnnotationSidecarProxyCPULimit: "200m", + constants.AnnotationSidecarProxyMemoryLimit: "200Mi", + }, + expResources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: cpu2, + corev1.ResourceMemory: mem2, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: cpu1, + corev1.ResourceMemory: mem1, + }, + }, + }, + "annotations override defaults": { + webhook: MeshWebhook{ + DefaultProxyCPURequest: zero, + DefaultProxyCPULimit: zero, + DefaultProxyMemoryRequest: zero, + DefaultProxyMemoryLimit: zero, + }, + annotations: map[string]string{ + constants.AnnotationSidecarProxyCPURequest: "100m", + constants.AnnotationSidecarProxyMemoryRequest: "100Mi", + constants.AnnotationSidecarProxyCPULimit: "200m", + constants.AnnotationSidecarProxyMemoryLimit: "200Mi", + }, + expResources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: cpu2, + corev1.ResourceMemory: mem2, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: cpu1, + corev1.ResourceMemory: mem1, + }, + }, + }, + "defaults set to zero, no annotations": { + webhook: MeshWebhook{ + DefaultProxyCPURequest: zero, + DefaultProxyCPULimit: zero, + DefaultProxyMemoryRequest: zero, + DefaultProxyMemoryLimit: zero, + }, + annotations: nil, + expResources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: zero, + corev1.ResourceMemory: zero, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: zero, + corev1.ResourceMemory: zero, + }, + }, + }, + "annotations set to 0": { + webhook: MeshWebhook{}, + annotations: map[string]string{ + constants.AnnotationSidecarProxyCPURequest: "0", + constants.AnnotationSidecarProxyMemoryRequest: "0", + constants.AnnotationSidecarProxyCPULimit: "0", + constants.AnnotationSidecarProxyMemoryLimit: "0", + }, + expResources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: zero, + corev1.ResourceMemory: zero, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: zero, + corev1.ResourceMemory: zero, + }, + }, + }, + "invalid cpu request": { + webhook: MeshWebhook{}, + annotations: map[string]string{ + constants.AnnotationSidecarProxyCPURequest: "invalid", + }, + expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-cpu-request:\"invalid\": quantities must match the regular expression", + }, + "invalid cpu limit": { + webhook: MeshWebhook{}, + annotations: map[string]string{ + constants.AnnotationSidecarProxyCPULimit: "invalid", + }, + expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-cpu-limit:\"invalid\": quantities must match the regular expression", + }, + "invalid memory request": { + webhook: MeshWebhook{}, + annotations: map[string]string{ + constants.AnnotationSidecarProxyMemoryRequest: "invalid", + }, + expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-memory-request:\"invalid\": quantities must match the regular expression", + }, + "invalid memory limit": { + webhook: MeshWebhook{}, + annotations: map[string]string{ + constants.AnnotationSidecarProxyMemoryLimit: "invalid", + }, + expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-memory-limit:\"invalid\": quantities must match the regular expression", + }, + } + + for name, c := range cases { + t.Run(name, func(tt *testing.T) { + c.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} + require := require.New(tt) + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: c.annotations, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + container, err := c.webhook.consulDataplaneSidecar(testNS, pod) + if c.expErr != "" { + require.NotNil(err) + require.Contains(err.Error(), c.expErr) + } else { + require.NoError(err) + require.Equal(c.expResources, container.Resources) + } + }) + } +} + +func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { + gracefulShutdownSeconds := 10 + gracefulPort := "20307" + gracefulShutdownPath := "/exit" + + cases := []struct { + name string + webhook MeshWebhook + annotations map[string]string + expCmdArgs string + expErr string + }{ + { + name: "no defaults, no annotations", + webhook: MeshWebhook{}, + annotations: nil, + expCmdArgs: "", + }, + { + name: "all defaults, no annotations", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: true, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + DefaultGracefulPort: gracefulPort, + DefaultGracefulShutdownPath: gracefulShutdownPath, + }, + }, + annotations: nil, + expCmdArgs: "graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", + }, + { + name: "no defaults, all annotations", + webhook: MeshWebhook{}, + annotations: map[string]string{ + constants.AnnotationEnableSidecarProxyLifecycle: "true", + constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "true", + constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds), + constants.AnnotationSidecarProxyLifecycleGracefulPort: gracefulPort, + constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: gracefulShutdownPath, + }, + expCmdArgs: "-graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", + }, + { + name: "annotations override defaults", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: false, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + DefaultGracefulPort: gracefulPort, + DefaultGracefulShutdownPath: gracefulShutdownPath, + }, + }, + annotations: map[string]string{ + constants.AnnotationEnableSidecarProxyLifecycle: "true", + constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", + constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds + 5), + constants.AnnotationSidecarProxyLifecycleGracefulPort: "20317", + constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: "/foo", + }, + expCmdArgs: "-graceful-port=20317 -shutdown-grace-period-seconds=15 -graceful-shutdown-path=/foo", + }, + { + name: "lifecycle disabled, no annotations", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: false, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + DefaultGracefulPort: gracefulPort, + DefaultGracefulShutdownPath: gracefulShutdownPath, + }, + }, + annotations: nil, + expCmdArgs: "-graceful-port=20307", + }, + { + name: "lifecycle enabled, defaults omited, no annotations", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: true, + }, + }, + annotations: nil, + expCmdArgs: "", + }, + { + name: "annotations disable lifecycle default", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: true, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + DefaultGracefulPort: gracefulPort, + DefaultGracefulShutdownPath: gracefulShutdownPath, + }, + }, + annotations: map[string]string{ + constants.AnnotationEnableSidecarProxyLifecycle: "false", + }, + expCmdArgs: "-graceful-port=20307", + }, + { + name: "annotations skip graceful shutdown", + webhook: MeshWebhook{ + LifecycleConfig: lifecycle.Config{ + DefaultEnableProxyLifecycle: false, + DefaultEnableShutdownDrainListeners: true, + DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, + }, + }, + annotations: map[string]string{ + constants.AnnotationEnableSidecarProxyLifecycle: "false", + constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", + constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: "0", + }, + expCmdArgs: "", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + c.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} + require := require.New(t) + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: c.annotations, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + container, err := c.webhook.consulDataplaneSidecar(testNS, pod) + if c.expErr != "" { + require.NotNil(err) + require.Contains(err.Error(), c.expErr) + } else { + require.NoError(err) + require.Contains(strings.Join(container.Args, " "), c.expCmdArgs) + } + }) + } +} + +// boolPtr returns pointer to b. +func boolPtr(b bool) *bool { + return &b +} diff --git a/control-plane/connect-inject/webhook_v2/container_env.go b/control-plane/connect-inject/webhook_v2/container_env.go new file mode 100644 index 0000000000..4c05a2ea72 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/container_env.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + corev1 "k8s.io/api/core/v1" +) + +func (w *MeshWebhook) containerEnvVars(pod corev1.Pod) []corev1.EnvVar { + // (TODO: ashwin) make this work with current upstreams + //raw, ok := pod.Annotations[constants.AnnotationMeshDestinations] + //if !ok || raw == "" { + // return []corev1.EnvVar{} + //} + // + //var result []corev1.EnvVar + //for _, raw := range strings.Split(raw, ",") { + // parts := strings.SplitN(raw, ":", 3) + // port, _ := common.PortValue(pod, strings.TrimSpace(parts[1])) + // if port > 0 { + // name := strings.TrimSpace(parts[0]) + // name = strings.ToUpper(strings.Replace(name, "-", "_", -1)) + // portStr := strconv.Itoa(int(port)) + // + // result = append(result, corev1.EnvVar{ + // Name: fmt.Sprintf("%s_CONNECT_SERVICE_HOST", name), + // Value: "127.0.0.1", + // }, corev1.EnvVar{ + // Name: fmt.Sprintf("%s_CONNECT_SERVICE_PORT", name), + // Value: portStr, + // }) + // } + //} + + return []corev1.EnvVar{} +} diff --git a/control-plane/connect-inject/webhook_v2/container_env_test.go b/control-plane/connect-inject/webhook_v2/container_env_test.go new file mode 100644 index 0000000000..f7cef104ea --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/container_env_test.go @@ -0,0 +1,58 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +func TestContainerEnvVars(t *testing.T) { + t.Skip() + // (TODO: ashwin) make these work once upstreams are fixed + cases := []struct { + Name string + Upstream string + }{ + { + "Upstream with datacenter", + "static-server:7890:dc1", + }, + { + "Upstream without datacenter", + "static-server:7890", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + + var w MeshWebhook + envVars := w.containerEnvVars(corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "foo", + constants.AnnotationMeshDestinations: tt.Upstream, + }, + }, + }) + + require.ElementsMatch(envVars, []corev1.EnvVar{ + { + Name: "STATIC_SERVER_CONNECT_SERVICE_HOST", + Value: "127.0.0.1", + }, { + Name: "STATIC_SERVER_CONNECT_SERVICE_PORT", + Value: "7890", + }, + }) + }) + } +} diff --git a/control-plane/connect-inject/webhook_v2/container_init.go b/control-plane/connect-inject/webhook_v2/container_init.go new file mode 100644 index 0000000000..ebf4b0e336 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/container_init.go @@ -0,0 +1,300 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "bytes" + "strconv" + "strings" + "text/template" + + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + injectInitContainerName = "consul-mesh-init" + rootUserAndGroupID = 0 + sidecarUserAndGroupID = 5995 + initContainersUserAndGroupID = 5996 + netAdminCapability = "NET_ADMIN" +) + +type initContainerCommandData struct { + ServiceName string + ServiceAccountName string + AuthMethod string + + // Log settings for the connect-init command. + LogLevel string + LogJSON bool +} + +// containerInit 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 (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) (corev1.Container, error) { + // Check if tproxy is enabled on this pod. + tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) + if err != nil { + return corev1.Container{}, err + } + + data := initContainerCommandData{ + AuthMethod: w.AuthMethod, + LogLevel: w.LogLevel, + LogJSON: w.LogJSON, + } + + // Create expected volume mounts + volMounts := []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/consul/connect-inject", + }, + } + + data.ServiceName = pod.Annotations[constants.AnnotationService] + var bearerTokenFile string + if w.AuthMethod != "" { + data.ServiceAccountName = pod.Spec.ServiceAccountName + // Extract the service account token's volume mount + var saTokenVolumeMount corev1.VolumeMount + saTokenVolumeMount, bearerTokenFile, err = findServiceAccountVolumeMount(pod) + if err != nil { + return corev1.Container{}, err + } + + // Append to volume mounts + volMounts = append(volMounts, saTokenVolumeMount) + } + + // Render the command + var buf bytes.Buffer + tpl := template.Must(template.New("root").Parse(strings.TrimSpace( + initContainerCommandTpl))) + err = tpl.Execute(&buf, &data) + if err != nil { + return corev1.Container{}, err + } + + initContainerName := injectInitContainerName + container := corev1.Container{ + Name: initContainerName, + Image: w.ImageConsulK8S, + Env: []corev1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "CONSUL_ADDRESSES", + Value: w.ConsulAddress, + }, + { + Name: "CONSUL_GRPC_PORT", + Value: strconv.Itoa(w.ConsulConfig.GRPCPort), + }, + { + Name: "CONSUL_HTTP_PORT", + Value: strconv.Itoa(w.ConsulConfig.HTTPPort), + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: w.ConsulConfig.APITimeout.String(), + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + }, + Resources: w.InitContainerResources, + VolumeMounts: volMounts, + Command: []string{"/bin/sh", "-ec", buf.String()}, + } + + if w.TLSEnabled { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_USE_TLS", + Value: "true", + }, + corev1.EnvVar{ + Name: "CONSUL_CACERT_PEM", + Value: w.ConsulCACert, + }, + corev1.EnvVar{ + Name: "CONSUL_TLS_SERVER_NAME", + Value: w.ConsulTLSServerName, + }) + } + + if w.AuthMethod != "" { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_AUTH_METHOD", + Value: w.AuthMethod, + }, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", + Value: bearerTokenFile, + }, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }) + + if w.EnableNamespaces { + if w.EnableK8SNSMirroring { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_NAMESPACE", + Value: "default", + }) + } else { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_NAMESPACE", + Value: w.consulNamespace(namespace.Name), + }) + } + } + + if w.ConsulPartition != "" { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_PARTITION", + Value: w.ConsulPartition, + }) + } + } + if w.EnableNamespaces { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_NAMESPACE", + Value: w.consulNamespace(namespace.Name), + }) + } + + if w.ConsulPartition != "" { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_PARTITION", + Value: w.ConsulPartition, + }) + } + + // OpenShift without CNI is the only environment where privileged must be true. + privileged := false + if w.EnableOpenShift && !w.EnableCNI { + privileged = true + } + + if tproxyEnabled { + if !w.EnableCNI { + // Set redirect traffic config for the container so that we can apply iptables rules. + redirectTrafficConfig, err := w.iptablesConfigJSON(pod, namespace) + if err != nil { + return corev1.Container{}, err + } + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_REDIRECT_TRAFFIC_CONFIG", + Value: redirectTrafficConfig, + }) + + // Running consul connect redirect-traffic with iptables + // requires both being a root user and having NET_ADMIN capability. + container.SecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(rootUserAndGroupID), + RunAsGroup: pointer.Int64(rootUserAndGroupID), + // RunAsNonRoot overrides any setting in the Pod so that we can still run as root here as required. + RunAsNonRoot: pointer.Bool(false), + Privileged: pointer.Bool(privileged), + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{netAdminCapability}, + }, + } + } else { + container.SecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(initContainersUserAndGroupID), + RunAsGroup: pointer.Int64(initContainersUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + Privileged: pointer.Bool(privileged), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + } + } + } + + return container, nil +} + +// consulDNSEnabled returns true if Consul DNS should be enabled for this pod. +// It returns an error when the annotation value cannot be parsed by strconv.ParseBool or if we are unable +// to read the pod's namespace label when it exists. +func consulDNSEnabled(namespace corev1.Namespace, pod corev1.Pod, globalDNSEnabled bool, globalTProxyEnabled bool) (bool, error) { + // DNS is only possible when tproxy is also enabled because it relies + // on traffic being redirected. + tproxy, err := common.TransparentProxyEnabled(namespace, pod, globalTProxyEnabled) + if err != nil { + return false, err + } + if !tproxy { + return false, nil + } + + // First check to see if the pod annotation exists to override the namespace or global settings. + if raw, ok := pod.Annotations[constants.KeyConsulDNS]; ok { + return strconv.ParseBool(raw) + } + // Next see if the namespace has been defaulted. + if raw, ok := namespace.Labels[constants.KeyConsulDNS]; ok { + return strconv.ParseBool(raw) + } + // Else fall back to the global default. + return globalDNSEnabled, nil +} + +// splitCommaSeparatedItemsFromAnnotation takes an annotation and a pod +// and returns the comma-separated value of the annotation as a list of strings. +func splitCommaSeparatedItemsFromAnnotation(annotation string, pod corev1.Pod) []string { + var items []string + if raw, ok := pod.Annotations[annotation]; ok { + items = append(items, strings.Split(raw, ",")...) + } + + return items +} + +// initContainerCommandTpl is the template for the command executed by +// the init container. +const initContainerCommandTpl = ` +consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level={{ .LogLevel }} \ + -log-json={{ .LogJSON }} \ + {{- if .AuthMethod }} + -service-account-name="{{ .ServiceAccountName }}" \ + -service-name="{{ .ServiceName }}" \ + {{- end }} +` diff --git a/control-plane/connect-inject/webhook_v2/container_init_test.go b/control-plane/connect-inject/webhook_v2/container_init_test.go new file mode 100644 index 0000000000..6931122124 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/container_init_test.go @@ -0,0 +1,844 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" +) + +const k8sNamespace = "k8snamespace" + +func TestHandlerContainerInit(t *testing.T) { + minimal := func() *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "test-namespace", + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web-side", + }, + }, + }, + Status: corev1.PodStatus{ + HostIP: "1.1.1.1", + PodIP: "2.2.2.2", + }, + } + } + + cases := []struct { + Name string + Pod func(*corev1.Pod) *corev1.Pod + Webhook MeshWebhook + ExpCmd string // Strings.Contains test + ExpEnv []corev1.EnvVar + }{ + { + "default cmd and env", + func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationService] = "web" + return pod + }, + MeshWebhook{ + ConsulAddress: "10.0.0.0", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + LogLevel: "info", + }, + `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level=info \ + -log-json=false \`, + []corev1.EnvVar{ + { + Name: "CONSUL_ADDRESSES", + Value: "10.0.0.0", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "8502", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "8500", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "0s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + }, + }, + + { + "with auth method", + func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationService] = "web" + pod.Spec.ServiceAccountName = "a-service-account-name" + pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{ + { + Name: "sa", + MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + }, + } + return pod + }, + MeshWebhook{ + AuthMethod: "an-auth-method", + ConsulAddress: "10.0.0.0", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, + LogLevel: "debug", + LogJSON: true, + }, + `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level=debug \ + -log-json=true \ + -service-account-name="a-service-account-name" \ + -service-name="web" \`, + []corev1.EnvVar{ + { + Name: "CONSUL_ADDRESSES", + Value: "10.0.0.0", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "8502", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "8500", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "5s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_LOGIN_AUTH_METHOD", + Value: "an-auth-method", + }, + { + Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", + Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", + }, + { + Name: "CONSUL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }, + }, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + w := tt.Webhook + pod := *tt.Pod(minimal()) + container, err := w.containerInit(testNS, pod) + require.NoError(t, err) + actual := strings.Join(container.Command, " ") + require.Contains(t, actual, tt.ExpCmd) + require.EqualValues(t, container.Env[3:], tt.ExpEnv) + }) + } +} + +func TestHandlerContainerInit_transparentProxy(t *testing.T) { + cases := map[string]struct { + globalEnabled bool + cniEnabled bool + annotations map[string]string + expTproxyEnabled bool + namespaceLabel map[string]string + openShiftEnabled bool + }{ + "enabled globally, ns not set, annotation not provided, cni disabled, openshift disabled": { + true, + false, + nil, + true, + nil, + false, + }, + "enabled globally, ns not set, annotation is false, cni disabled, openshift disabled": { + true, + false, + map[string]string{constants.KeyTransparentProxy: "false"}, + false, + nil, + false, + }, + "enabled globally, ns not set, annotation is true, cni disabled, openshift disabled": { + true, + false, + map[string]string{constants.KeyTransparentProxy: "true"}, + true, + nil, + false, + }, + "disabled globally, ns not set, annotation not provided, cni disabled, openshift disabled": { + false, + false, + nil, + false, + nil, + false, + }, + "disabled globally, ns not set, annotation is false, cni disabled, openshift disabled": { + false, + false, + map[string]string{constants.KeyTransparentProxy: "false"}, + false, + nil, + false, + }, + "disabled globally, ns not set, annotation is true, cni disabled, openshift disabled": { + false, + false, + map[string]string{constants.KeyTransparentProxy: "true"}, + true, + nil, + false, + }, + "disabled globally, ns enabled, annotation not set, cni disabled, openshift disabled": { + false, + false, + nil, + true, + map[string]string{constants.KeyTransparentProxy: "true"}, + false, + }, + "enabled globally, ns disabled, annotation not set, cni disabled, openshift disabled": { + true, + false, + nil, + false, + map[string]string{constants.KeyTransparentProxy: "false"}, + false, + }, + "disabled globally, ns enabled, annotation not set, cni enabled, openshift disabled": { + false, + true, + nil, + false, + map[string]string{constants.KeyTransparentProxy: "true"}, + false, + }, + + "enabled globally, ns not set, annotation not set, cni enabled, openshift disabled": { + true, + true, + nil, + false, + nil, + false, + }, + "enabled globally, ns not set, annotation not set, cni enabled, openshift enabled": { + true, + true, + nil, + false, + nil, + true, + }, + "enabled globally, ns not set, annotation not set, cni disabled, openshift enabled": { + true, + false, + nil, + true, + nil, + true, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + w := MeshWebhook{ + EnableTransparentProxy: c.globalEnabled, + EnableCNI: c.cniEnabled, + ConsulConfig: &consul.Config{HTTPPort: 8500}, + EnableOpenShift: c.openShiftEnabled, + } + pod := minimal() + pod.Annotations = c.annotations + + privileged := false + if c.openShiftEnabled && !c.cniEnabled { + privileged = true + } + + var expectedSecurityContext *corev1.SecurityContext + if c.cniEnabled { + expectedSecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(initContainersUserAndGroupID), + RunAsGroup: pointer.Int64(initContainersUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + Privileged: pointer.Bool(privileged), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + } + } else if c.expTproxyEnabled { + expectedSecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(0), + RunAsGroup: pointer.Int64(0), + RunAsNonRoot: pointer.Bool(false), + Privileged: pointer.Bool(privileged), + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{netAdminCapability}, + }, + } + } + ns := testNS + ns.Labels = c.namespaceLabel + container, err := w.containerInit(ns, *pod) + require.NoError(t, err) + + redirectTrafficEnvVarFound := false + for _, ev := range container.Env { + if ev.Name == "CONSUL_REDIRECT_TRAFFIC_CONFIG" { + redirectTrafficEnvVarFound = true + break + } + } + + require.Equal(t, c.expTproxyEnabled, redirectTrafficEnvVarFound) + require.Equal(t, expectedSecurityContext, container.SecurityContext) + }) + } +} + +func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { + minimal := func() *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web-side", + }, + { + Name: "auth-method-secret", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "service-account-secret", + MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + }, + }, + }, + }, + ServiceAccountName: "web", + }, + } + } + + cases := []struct { + Name string + Pod func(*corev1.Pod) *corev1.Pod + Webhook MeshWebhook + Cmd string + ExpEnv []corev1.EnvVar + }{ + { + "default namespace, no partition", + func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationService] = "web" + return pod + }, + MeshWebhook{ + EnableNamespaces: true, + ConsulDestinationNamespace: "default", + ConsulPartition: "", + ConsulAddress: "10.0.0.0", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, + }, + `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level=info \ + -log-json=false \`, + []corev1.EnvVar{ + { + Name: "CONSUL_ADDRESSES", + Value: "10.0.0.0", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "8502", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "8500", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "5s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "default", + }, + }, + }, + { + "default namespace, default partition", + func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationService] = "web" + return pod + }, + MeshWebhook{ + EnableNamespaces: true, + ConsulDestinationNamespace: "default", + ConsulPartition: "default", + ConsulAddress: "10.0.0.0", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, + }, + `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level=info \ + -log-json=false \`, + []corev1.EnvVar{ + { + Name: "CONSUL_ADDRESSES", + Value: "10.0.0.0", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "8502", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "8500", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "5s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "default", + }, + { + Name: "CONSUL_PARTITION", + Value: "default", + }, + }, + }, + { + "non-default namespace, no partition", + func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationService] = "web" + return pod + }, + MeshWebhook{ + EnableNamespaces: true, + ConsulDestinationNamespace: "non-default", + ConsulPartition: "", + ConsulAddress: "10.0.0.0", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, + }, + `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level=info \ + -log-json=false \`, + []corev1.EnvVar{ + { + Name: "CONSUL_ADDRESSES", + Value: "10.0.0.0", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "8502", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "8500", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "5s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "non-default", + }, + }, + }, + { + "non-default namespace, non-default partition", + func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationService] = "web" + return pod + }, + MeshWebhook{ + EnableNamespaces: true, + ConsulDestinationNamespace: "non-default", + ConsulPartition: "non-default-part", + ConsulAddress: "10.0.0.0", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, + }, + `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level=info \ + -log-json=false \`, + []corev1.EnvVar{ + { + Name: "CONSUL_ADDRESSES", + Value: "10.0.0.0", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "8502", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "8500", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "5s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "non-default", + }, + { + Name: "CONSUL_PARTITION", + Value: "non-default-part", + }, + }, + }, + { + "auth method, non-default namespace, mirroring disabled, default partition", + func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationService] = "" + return pod + }, + MeshWebhook{ + AuthMethod: "auth-method", + EnableNamespaces: true, + ConsulDestinationNamespace: "non-default", + ConsulPartition: "default", + ConsulAddress: "10.0.0.0", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, + }, + `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level=info \ + -log-json=false \ + -service-account-name="web" \ + -service-name="" \`, + []corev1.EnvVar{ + { + Name: "CONSUL_ADDRESSES", + Value: "10.0.0.0", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "8502", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "8500", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "5s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_LOGIN_AUTH_METHOD", + Value: "auth-method", + }, + { + Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", + Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", + }, + { + Name: "CONSUL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }, + { + Name: "CONSUL_LOGIN_NAMESPACE", + Value: "non-default", + }, + { + Name: "CONSUL_LOGIN_PARTITION", + Value: "default", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "non-default", + }, + { + Name: "CONSUL_PARTITION", + Value: "default", + }, + }, + }, + { + "auth method, non-default namespace, mirroring enabled, non-default partition", + func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[constants.AnnotationService] = "" + return pod + }, + MeshWebhook{ + AuthMethod: "auth-method", + EnableNamespaces: true, + ConsulDestinationNamespace: "non-default", // Overridden by mirroring + EnableK8SNSMirroring: true, + ConsulPartition: "non-default", + ConsulAddress: "10.0.0.0", + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, + }, + `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + -log-level=info \ + -log-json=false \ + -service-account-name="web" \ + -service-name="" \`, + []corev1.EnvVar{ + { + Name: "CONSUL_ADDRESSES", + Value: "10.0.0.0", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "8502", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "8500", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "5s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_LOGIN_AUTH_METHOD", + Value: "auth-method", + }, + { + Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", + Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", + }, + { + Name: "CONSUL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }, + { + Name: "CONSUL_LOGIN_NAMESPACE", + Value: "default", + }, + { + Name: "CONSUL_LOGIN_PARTITION", + Value: "non-default", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "k8snamespace", + }, + { + Name: "CONSUL_PARTITION", + Value: "non-default", + }, + }, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + h := tt.Webhook + h.LogLevel = "info" + container, err := h.containerInit(testNS, *tt.Pod(minimal())) + require.NoError(t, err) + actual := strings.Join(container.Command, " ") + require.Equal(t, tt.Cmd, actual) + if tt.ExpEnv != nil { + require.Equal(t, tt.ExpEnv, container.Env[3:]) + } + }) + } +} + +// If TLSEnabled is set, +// Consul addresses should use HTTPS +// and CA cert should be set as env variable if provided. +// Additionally, test that the init container is correctly configured +// when http or gRPC ports are different from defaults. +func TestHandlerContainerInit_WithTLSAndCustomPorts(t *testing.T) { + for _, caProvided := range []bool{true, false} { + name := fmt.Sprintf("ca provided: %t", caProvided) + t.Run(name, func(t *testing.T) { + w := MeshWebhook{ + ConsulAddress: "10.0.0.0", + TLSEnabled: true, + ConsulConfig: &consul.Config{HTTPPort: 443, GRPCPort: 8503}, + } + if caProvided { + w.ConsulCACert = "consul-ca-cert" + } + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + container, err := w.containerInit(testNS, *pod) + require.NoError(t, err) + require.Equal(t, "CONSUL_ADDRESSES", container.Env[3].Name) + require.Equal(t, w.ConsulAddress, container.Env[3].Value) + require.Equal(t, "CONSUL_GRPC_PORT", container.Env[4].Name) + require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.GRPCPort), container.Env[4].Value) + require.Equal(t, "CONSUL_HTTP_PORT", container.Env[5].Name) + require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.HTTPPort), container.Env[5].Value) + if w.TLSEnabled { + require.Equal(t, "CONSUL_USE_TLS", container.Env[8].Name) + require.Equal(t, "true", container.Env[8].Value) + if caProvided { + require.Equal(t, "CONSUL_CACERT_PEM", container.Env[9].Name) + require.Equal(t, "consul-ca-cert", container.Env[9].Value) + } else { + for _, ev := range container.Env { + if ev.Name == "CONSUL_CACERT_PEM" { + require.Empty(t, ev.Value) + } + } + } + } + + }) + } +} + +func TestHandlerContainerInit_Resources(t *testing.T) { + w := MeshWebhook{ + InitContainerResources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("10m"), + corev1.ResourceMemory: resource.MustParse("10Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("20m"), + corev1.ResourceMemory: resource.MustParse("25Mi"), + }, + }, + ConsulConfig: &consul.Config{HTTPPort: 8500, APITimeout: 5 * time.Second}, + } + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + container, err := w.containerInit(testNS, *pod) + require.NoError(t, err) + require.Equal(t, corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("20m"), + corev1.ResourceMemory: resource.MustParse("25Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("10m"), + corev1.ResourceMemory: resource.MustParse("10Mi"), + }, + }, container.Resources) +} + +var testNS = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: k8sNamespace, + Labels: map[string]string{}, + }, +} + +func minimal() *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespaces.DefaultNamespace, + Name: "minimal", + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web-side", + }, + }, + }, + } +} diff --git a/control-plane/connect-inject/webhook_v2/container_volume.go b/control-plane/connect-inject/webhook_v2/container_volume.go new file mode 100644 index 0000000000..8de3d5b6f5 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/container_volume.go @@ -0,0 +1,23 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + corev1 "k8s.io/api/core/v1" +) + +// volumeName is the name of the volume that is created to store the +// Consul Connect injection data. +const volumeName = "consul-connect-inject-data" + +// containerVolume returns the volume data to add to the pod. This volume +// is used for shared data between containers. +func (w *MeshWebhook) containerVolume() corev1.Volume { + return corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, + }, + } +} diff --git a/control-plane/connect-inject/webhook_v2/dns.go b/control-plane/connect-inject/webhook_v2/dns.go new file mode 100644 index 0000000000..e7aaa67830 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/dns.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "fmt" + "strconv" + + "github.com/miekg/dns" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" +) + +const ( + // These defaults are taken from the /etc/resolv.conf man page + // and are used by the dns library. + defaultDNSOptionNdots = 1 + defaultDNSOptionTimeout = 5 + defaultDNSOptionAttempts = 2 + + // defaultEtcResolvConfFile is the default location of the /etc/resolv.conf file. + defaultEtcResolvConfFile = "/etc/resolv.conf" +) + +func (w *MeshWebhook) configureDNS(pod *corev1.Pod, k8sNS string) error { + // First, we need to determine the nameservers configured in this cluster from /etc/resolv.conf. + etcResolvConf := defaultEtcResolvConfFile + if w.etcResolvFile != "" { + etcResolvConf = w.etcResolvFile + } + cfg, err := dns.ClientConfigFromFile(etcResolvConf) + if err != nil { + return err + } + + // Set DNS policy on the pod to None because we want DNS to work according to the config we will provide. + pod.Spec.DNSPolicy = corev1.DNSNone + + // Set the consul-dataplane's DNS server as the first server in the list (i.e. localhost). + // We want to do that so that when consul cannot resolve the record, we will fall back to the nameservers + // configured in our /etc/resolv.conf. It's important to add Consul DNS as the first nameserver because + // if we put kube DNS first, it will return NXDOMAIN response and a DNS client will not fall back to other nameservers. + if pod.Spec.DNSConfig == nil { + nameservers := []string{consulDataplaneDNSBindHost} + nameservers = append(nameservers, cfg.Servers...) + var options []corev1.PodDNSConfigOption + if cfg.Ndots != defaultDNSOptionNdots { + ndots := strconv.Itoa(cfg.Ndots) + options = append(options, corev1.PodDNSConfigOption{ + Name: "ndots", + Value: &ndots, + }) + } + if cfg.Timeout != defaultDNSOptionTimeout { + options = append(options, corev1.PodDNSConfigOption{ + Name: "timeout", + Value: pointer.String(strconv.Itoa(cfg.Timeout)), + }) + } + if cfg.Attempts != defaultDNSOptionAttempts { + options = append(options, corev1.PodDNSConfigOption{ + Name: "attempts", + Value: pointer.String(strconv.Itoa(cfg.Attempts)), + }) + } + + // Replace release namespace in the searches with the pod namespace. + // This is so that the searches we generate will be for the pod's namespace + // instead of the namespace of the connect-injector. E.g. instead of + // consul.svc.cluster.local it should be .svc.cluster.local. + var searches []string + // Kubernetes will add a search domain for .svc.cluster.local so we can always + // expect it to be there. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services. + consulReleaseNSSearchDomain := fmt.Sprintf("%s.svc.cluster.local", w.ReleaseNamespace) + for _, search := range cfg.Search { + if search == consulReleaseNSSearchDomain { + searches = append(searches, fmt.Sprintf("%s.svc.cluster.local", k8sNS)) + } else { + searches = append(searches, search) + } + } + + pod.Spec.DNSConfig = &corev1.PodDNSConfig{ + Nameservers: nameservers, + Searches: searches, + Options: options, + } + } else { + return fmt.Errorf("DNS redirection to Consul is not supported with an already defined DNSConfig on the pod") + } + return nil +} diff --git a/control-plane/connect-inject/webhook_v2/dns_test.go b/control-plane/connect-inject/webhook_v2/dns_test.go new file mode 100644 index 0000000000..7c45a5e577 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/dns_test.go @@ -0,0 +1,105 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" +) + +func TestMeshWebhook_configureDNS(t *testing.T) { + cases := map[string]struct { + etcResolv string + expDNSConfig *corev1.PodDNSConfig + }{ + "empty /etc/resolv.conf file": { + expDNSConfig: &corev1.PodDNSConfig{ + Nameservers: []string{"127.0.0.1"}, + }, + }, + "one nameserver": { + etcResolv: `nameserver 1.1.1.1`, + expDNSConfig: &corev1.PodDNSConfig{ + Nameservers: []string{"127.0.0.1", "1.1.1.1"}, + }, + }, + "mutiple nameservers, searches, and options": { + etcResolv: ` +nameserver 1.1.1.1 +nameserver 2.2.2.2 +search foo.bar bar.baz +options ndots:5 timeout:6 attempts:3`, + expDNSConfig: &corev1.PodDNSConfig{ + Nameservers: []string{"127.0.0.1", "1.1.1.1", "2.2.2.2"}, + Searches: []string{"foo.bar", "bar.baz"}, + Options: []corev1.PodDNSConfigOption{ + { + Name: "ndots", + Value: pointer.String("5"), + }, + { + Name: "timeout", + Value: pointer.String("6"), + }, + { + Name: "attempts", + Value: pointer.String("3"), + }, + }, + }, + }, + "replaces release specific search domains": { + etcResolv: ` +nameserver 1.1.1.1 +nameserver 2.2.2.2 +search consul.svc.cluster.local svc.cluster.local cluster.local +options ndots:5`, + expDNSConfig: &corev1.PodDNSConfig{ + Nameservers: []string{"127.0.0.1", "1.1.1.1", "2.2.2.2"}, + Searches: []string{"default.svc.cluster.local", "svc.cluster.local", "cluster.local"}, + Options: []corev1.PodDNSConfigOption{ + { + Name: "ndots", + Value: pointer.String("5"), + }, + }, + }, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + etcResolvFile, err := os.CreateTemp("", "") + require.NoError(t, err) + t.Cleanup(func() { + _ = os.RemoveAll(etcResolvFile.Name()) + }) + _, err = etcResolvFile.WriteString(c.etcResolv) + require.NoError(t, err) + w := MeshWebhook{ + etcResolvFile: etcResolvFile.Name(), + ReleaseNamespace: "consul", + } + + pod := minimal() + err = w.configureDNS(pod, "default") + require.NoError(t, err) + require.Equal(t, corev1.DNSNone, pod.Spec.DNSPolicy) + require.Equal(t, c.expDNSConfig, pod.Spec.DNSConfig) + }) + } +} + +func TestMeshWebhook_configureDNS_error(t *testing.T) { + w := MeshWebhook{} + + pod := minimal() + pod.Spec.DNSConfig = &corev1.PodDNSConfig{Nameservers: []string{"1.1.1.1"}} + err := w.configureDNS(pod, "default") + require.EqualError(t, err, "DNS redirection to Consul is not supported with an already defined DNSConfig on the pod") +} diff --git a/control-plane/connect-inject/webhook_v2/health_checks_test.go b/control-plane/connect-inject/webhook_v2/health_checks_test.go new file mode 100644 index 0000000000..ce5f3937bf --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/health_checks_test.go @@ -0,0 +1,56 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestReady(t *testing.T) { + + var cases = []struct { + name string + certFileContents *string + keyFileContents *string + expectError bool + }{ + {"Both cert and key files not present.", nil, nil, true}, + {"Cert file not empty and key file missing.", ptrToString("test"), nil, true}, + {"Key file not empty and cert file missing.", nil, ptrToString("test"), true}, + {"Both cert and key files are present and not empty.", ptrToString("test"), ptrToString("test"), false}, + {"Both cert and key files are present but both are empty.", ptrToString(""), ptrToString(""), true}, + {"Both cert and key files are present but key file is empty.", ptrToString("test"), ptrToString(""), true}, + {"Both cert and key files are present but cert file is empty.", ptrToString(""), ptrToString("test"), true}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "") + require.NoError(t, err) + if tt.certFileContents != nil { + err := os.WriteFile(filepath.Join(tmpDir, "tls.crt"), []byte(*tt.certFileContents), 0666) + require.NoError(t, err) + } + if tt.keyFileContents != nil { + err := os.WriteFile(filepath.Join(tmpDir, "tls.key"), []byte(*tt.keyFileContents), 0666) + require.NoError(t, err) + } + rc := ReadinessCheck{tmpDir} + err = rc.Ready(nil) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func ptrToString(s string) *string { + return &s +} diff --git a/control-plane/connect-inject/webhook_v2/heath_checks.go b/control-plane/connect-inject/webhook_v2/heath_checks.go new file mode 100644 index 0000000000..6d92172e78 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/heath_checks.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "errors" + "net/http" + "os" + "path/filepath" +) + +type ReadinessCheck struct { + CertDir string +} + +func (r ReadinessCheck) Ready(_ *http.Request) error { + certFile, err := os.ReadFile(filepath.Join(r.CertDir, "tls.crt")) + if err != nil { + return err + } + keyFile, err := os.ReadFile(filepath.Join(r.CertDir, "tls.key")) + if err != nil { + return err + } + if len(certFile) == 0 || len(keyFile) == 0 { + return errors.New("certificate files have not been loaded") + } + return nil +} diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook.go b/control-plane/connect-inject/webhook_v2/mesh_webhook.go new file mode 100644 index 0000000000..efe33985f2 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/mesh_webhook.go @@ -0,0 +1,563 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + + mapset "github.com/deckarep/golang-set" + "github.com/go-logr/logr" + "golang.org/x/exp/slices" + "gomodules.xyz/jsonpatch/v2" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/version" +) + +const ( + sidecarContainer = "consul-dataplane" + + // exposedPathsLivenessPortsRangeStart is the start of the port range that we will use as + // the ListenerPort for the Expose configuration of the proxy registration for a liveness probe. + exposedPathsLivenessPortsRangeStart = 20300 + + // exposedPathsReadinessPortsRangeStart is the start of the port range that we will use as + // the ListenerPort for the Expose configuration of the proxy registration for a readiness probe. + exposedPathsReadinessPortsRangeStart = 20400 + + // exposedPathsStartupPortsRangeStart is the start of the port range that we will use as + // the ListenerPort for the Expose configuration of the proxy registration for a startup probe. + exposedPathsStartupPortsRangeStart = 20500 +) + +// kubeSystemNamespaces is a set of namespaces that are considered +// "system" level namespaces and are always skipped (never injected). +var kubeSystemNamespaces = mapset.NewSetWith(metav1.NamespaceSystem, metav1.NamespacePublic) + +// MeshWebhook is the HTTP meshWebhook for admission webhooks. +type MeshWebhook struct { + Clientset kubernetes.Interface + + // ConsulClientConfig is the config to create a Consul API client. + ConsulConfig *consul.Config + + // ConsulServerConnMgr is the watcher for the Consul server addresses. + ConsulServerConnMgr consul.ServerConnectionManager + + // ImageConsul is the container image for Consul to use. + // ImageConsulDataplane is the container image for Envoy to use. + // + // Both of these MUST be set. + ImageConsul string + ImageConsulDataplane string + + // ImageConsulK8S is the container image for consul-k8s to use. + // This image is used for the consul-sidecar container. + ImageConsulK8S string + + // Optional: set when you need extra options to be set when running envoy + // See a list of args here: https://www.envoyproxy.io/docs/envoy/latest/operations/cli + EnvoyExtraArgs string + + // RequireAnnotation means that the annotation must be given to inject. + // If this is false, injection is default. + RequireAnnotation bool + + // AuthMethod is the name of the Kubernetes Auth Method to + // use for identity with connectInjection if ACLs are enabled. + AuthMethod string + + // The PEM-encoded CA certificate string + // to use when communicating with Consul clients over HTTPS. + // If not set, will use HTTP. + ConsulCACert string + + // TLSEnabled indicates whether we should use TLS for communicating to Consul. + TLSEnabled bool + + // ConsulAddress is the address of the Consul server. This should be only the + // host (i.e. not including port or protocol). + ConsulAddress string + + // ConsulTLSServerName is the SNI header to use to connect to the Consul servers + // over TLS. + ConsulTLSServerName string + + // ConsulPartition is the name of the Admin Partition that the controller + // is deployed in. It is an enterprise feature requiring Consul Enterprise 1.11+. + // Its value is an empty string if partitions aren't enabled. + ConsulPartition string + + // EnableNamespaces indicates that a user is running Consul Enterprise + // with version 1.7+ which is namespace aware. It enables Consul namespaces, + // with injection into either a single Consul namespace or mirrored from + // k8s namespaces. + EnableNamespaces bool + + // AllowK8sNamespacesSet is a set of k8s namespaces to explicitly allow for + // injection. It supports the special character `*` which indicates that + // all k8s namespaces are eligible unless explicitly denied. This filter + // is applied before checking pod annotations. + AllowK8sNamespacesSet mapset.Set + + // DenyK8sNamespacesSet is a set of k8s namespaces to explicitly deny + // injection and thus service registration with Consul. An empty set + // means that no namespaces are removed from consideration. This filter + // takes precedence over AllowK8sNamespacesSet. + DenyK8sNamespacesSet mapset.Set + + // ConsulDestinationNamespace is the name of the Consul namespace to register all + // injected services into if Consul namespaces are enabled and mirroring + // is disabled. This may be set, but will not be used if mirroring is enabled. + ConsulDestinationNamespace string + + // EnableK8SNSMirroring causes Consul namespaces to be created to match the + // k8s namespace of any service being registered into Consul. Services are + // registered into the Consul namespace that mirrors their k8s namespace. + EnableK8SNSMirroring bool + + // K8SNSMirroringPrefix is an optional prefix that can be added to the Consul + // namespaces created while mirroring. For example, if it is set to "k8s-", + // then the k8s `default` namespace will be mirrored in Consul's + // `k8s-default` namespace. + K8SNSMirroringPrefix string + + // CrossNamespaceACLPolicy is the name of the ACL policy to attach to + // any created Consul namespaces to allow cross namespace service discovery. + // Only necessary if ACLs are enabled. + CrossNamespaceACLPolicy string + + // Default resource settings for sidecar proxies. Some of these + // fields may be empty. + DefaultProxyCPURequest resource.Quantity + DefaultProxyCPULimit resource.Quantity + DefaultProxyMemoryRequest resource.Quantity + DefaultProxyMemoryLimit resource.Quantity + + // LifecycleConfig contains proxy lifecycle management configuration from the inject-connect command and has methods to determine whether + // configuration should come from the default flags or annotations. The meshWebhook uses this to configure container sidecar proxy args. + LifecycleConfig lifecycle.Config + + // Default Envoy concurrency flag, this is the number of worker threads to be used by the proxy. + DefaultEnvoyProxyConcurrency int + + // MetricsConfig contains metrics configuration from the inject-connect command and has methods to determine whether + // configuration should come from the default flags or annotations. The meshWebhook uses this to configure prometheus + // annotations and the merged metrics server. + MetricsConfig metrics.Config + + // Resource settings for init container. All of these fields + // will be populated by the defaults provided in the initial flags. + InitContainerResources corev1.ResourceRequirements + + // Resource settings for Consul sidecar. All of these fields + // will be populated by the defaults provided in the initial flags. + DefaultConsulSidecarResources corev1.ResourceRequirements + + // EnableTransparentProxy enables transparent proxy mode. + // This means that the injected init container will apply traffic redirection rules + // so that all traffic will go through the Envoy proxy. + EnableTransparentProxy bool + + // EnableCNI enables the CNI plugin and prevents the connect-inject init container + // from running the consul redirect-traffic command as the CNI plugin handles traffic + // redirection + EnableCNI bool + + // TProxyOverwriteProbes controls whether the webhook should mutate pod's HTTP probes + // to point them to the Envoy proxy. + TProxyOverwriteProbes bool + + // EnableConsulDNS enables traffic redirection so that DNS requests are directed to Consul + // from mesh services. + EnableConsulDNS bool + + // EnableOpenShift indicates that when tproxy is enabled, the security context for the Envoy and init + // containers should not be added because OpenShift sets a random user for those and will not allow + // those containers to be created otherwise. + EnableOpenShift bool + + // SkipServerWatch prevents consul-dataplane from consuming the server update stream. This is useful + // for situations where Consul servers are behind a load balancer. + SkipServerWatch bool + + // ReleaseNamespace is the Kubernetes namespace where this webhook is running. + ReleaseNamespace string + + // Log + Log logr.Logger + // Log settings for consul-dataplane and connect-init containers. + LogLevel string + LogJSON bool + + decoder *admission.Decoder + // etcResolvFile is only used in tests to stub out /etc/resolv.conf file. + etcResolvFile string +} + +// Handle is the admission.Webhook implementation that actually handles the +// webhook request for admission control. This should be registered or +// served via the controller runtime manager. +func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var pod corev1.Pod + + // Decode the pod from the request + if err := w.decoder.Decode(req, &pod); err != nil { + w.Log.Error(err, "could not unmarshal request to pod") + return admission.Errored(http.StatusBadRequest, err) + } + + // Marshall the contents of the pod that was received. This is compared with the + // marshalled contents of the pod after it has been updated to create the jsonpatch. + origPodJson, err := json.Marshal(pod) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + // Setup the default annotation values that are used for the container. + // This MUST be done before shouldInject is called since that function + // uses these annotations. + if err := w.defaultAnnotations(&pod, string(origPodJson)); err != nil { + w.Log.Error(err, "error creating default annotations", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error creating default annotations: %s", err)) + } + + // Check if we should inject, for example we don't inject in the + // system namespaces. + if shouldInject, err := w.shouldInject(pod, req.Namespace); err != nil { + w.Log.Error(err, "error checking if should inject", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking if should inject: %s", err)) + } else if !shouldInject { + return admission.Allowed(fmt.Sprintf("%s %s does not require injection", pod.Kind, pod.Name)) + } + + w.Log.Info("received pod", "name", req.Name, "ns", req.Namespace) + + // Add our volume that will be shared by the init container and + // the sidecar for passing data in the pod. + pod.Spec.Volumes = append(pod.Spec.Volumes, w.containerVolume()) + + // Optionally mount data volume to other containers + w.injectVolumeMount(pod) + + // Optionally add any volumes that are to be used by the envoy sidecar. + if _, ok := pod.Annotations[constants.AnnotationConsulSidecarUserVolume]; ok { + var userVolumes []corev1.Volume + err := json.Unmarshal([]byte(pod.Annotations[constants.AnnotationConsulSidecarUserVolume]), &userVolumes) + if err != nil { + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error unmarshalling sidecar user volumes: %s", err)) + } + pod.Spec.Volumes = append(pod.Spec.Volumes, userVolumes...) + } + + // Add the upstream services as environment variables for easy + // service discovery. + containerEnvVars := w.containerEnvVars(pod) + for i := range pod.Spec.InitContainers { + pod.Spec.InitContainers[i].Env = append(pod.Spec.InitContainers[i].Env, containerEnvVars...) + } + + for i := range pod.Spec.Containers { + pod.Spec.Containers[i].Env = append(pod.Spec.Containers[i].Env, containerEnvVars...) + } + + // A user can enable/disable tproxy for an entire namespace via a label. + ns, err := w.Clientset.CoreV1().Namespaces().Get(ctx, req.Namespace, metav1.GetOptions{}) + if err != nil { + w.Log.Error(err, "error fetching namespace metadata for container", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error getting namespace metadata for container: %s", err)) + } + + lifecycleEnabled, ok := w.LifecycleConfig.EnableProxyLifecycle(pod) + if ok != nil { + w.Log.Error(err, "unable to get lifecycle enabled status") + } + // Add the init container that registers the service and sets up the Envoy configuration. + initContainer, err := w.containerInit(*ns, pod) + if err != nil { + w.Log.Error(err, "error configuring injection init container", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection init container: %s", err)) + } + pod.Spec.InitContainers = append(pod.Spec.InitContainers, initContainer) + + // Add the Envoy sidecar. + envoySidecar, err := w.consulDataplaneSidecar(*ns, pod) + if err != nil { + w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) + } + //Append the Envoy sidecar before the application container only if lifecycle enabled. + + if lifecycleEnabled && ok == nil { + pod.Spec.Containers = append([]corev1.Container{envoySidecar}, pod.Spec.Containers...) + } else { + pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) + } + + // pod.Annotations has already been initialized by h.defaultAnnotations() + // and does not need to be checked for being a nil value. + pod.Annotations[constants.KeyMeshInjectStatus] = constants.Injected + + tproxyEnabled, err := common.TransparentProxyEnabled(*ns, pod, w.EnableTransparentProxy) + if err != nil { + w.Log.Error(err, "error determining if transparent proxy is enabled", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error determining if transparent proxy is enabled: %s", err)) + } + + // Add an annotation to the pod sets transparent-proxy-status to enabled or disabled. Used by the CNI plugin + // to determine if it should traffic redirect or not. + if tproxyEnabled { + pod.Annotations[constants.KeyTransparentProxyStatus] = constants.Enabled + } + + // If DNS redirection is enabled, we want to configure dns on the pod. + dnsEnabled, err := consulDNSEnabled(*ns, pod, w.EnableConsulDNS, w.EnableTransparentProxy) + if err != nil { + w.Log.Error(err, "error determining if dns redirection is enabled", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error determining if dns redirection is enabled: %s", err)) + } + if dnsEnabled { + if err = w.configureDNS(&pod, req.Namespace); err != nil { + w.Log.Error(err, "error configuring DNS on the pod", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring DNS on the pod: %s", err)) + } + } + + // Add annotations for metrics. + if err = w.prometheusAnnotations(&pod); err != nil { + w.Log.Error(err, "error configuring prometheus annotations", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring prometheus annotations: %s", err)) + } + + if pod.Labels == nil { + pod.Labels = make(map[string]string) + } + pod.Labels[constants.KeyMeshInjectStatus] = constants.Injected + + // Consul-ENT only: Add the Consul destination namespace as an annotation to the pod. + if w.EnableNamespaces { + pod.Annotations[constants.AnnotationConsulNamespace] = w.consulNamespace(req.Namespace) + } + + // Overwrite readiness/liveness probes if needed. + err = w.overwriteProbes(*ns, &pod) + if err != nil { + w.Log.Error(err, "error overwriting readiness or liveness probes", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error overwriting readiness or liveness probes: %s", err)) + } + + // When CNI and tproxy are enabled, we add an annotation to the pod that contains the iptables config so that the CNI + // plugin can apply redirect traffic rules on the pod. + if w.EnableCNI && tproxyEnabled { + if err = w.addRedirectTrafficConfigAnnotation(&pod, *ns); err != nil { + w.Log.Error(err, "error configuring annotation for CNI traffic redirection", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring annotation for CNI traffic redirection: %s", err)) + } + } + + // Marshall the pod into JSON after it has the desired envs, annotations, labels, + // sidecars and initContainers appended to it. + updatedPodJson, err := json.Marshal(pod) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + // Create a patches based on the Pod that was received by the meshWebhook + // and the desired Pod spec. + patches, err := jsonpatch.CreatePatch(origPodJson, updatedPodJson) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + // Check and potentially create Consul resources. This is done after + // all patches are created to guarantee no errors were encountered in + // that process before modifying the Consul cluster. + if w.EnableNamespaces { + serverState, err := w.ConsulServerConnMgr.State() + if err != nil { + w.Log.Error(err, "error checking or creating namespace", + "ns", w.consulNamespace(req.Namespace), "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking or creating namespace: %s", err)) + } + apiClient, err := consul.NewClientFromConnMgrState(w.ConsulConfig, serverState) + if err != nil { + w.Log.Error(err, "error checking or creating namespace", + "ns", w.consulNamespace(req.Namespace), "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking or creating namespace: %s", err)) + } + if _, err := namespaces.EnsureExists(apiClient, w.consulNamespace(req.Namespace), w.CrossNamespaceACLPolicy); err != nil { + w.Log.Error(err, "error checking or creating namespace", + "ns", w.consulNamespace(req.Namespace), "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking or creating namespace: %s", err)) + } + } + + // Return a Patched response along with the patches we intend on applying to the + // Pod received by the meshWebhook. + return admission.Patched(fmt.Sprintf("valid %s request", pod.Kind), patches...) +} + +// overwriteProbes overwrites readiness/liveness probes of this pod when +// both transparent proxy is enabled and overwrite probes is true for the pod. +func (w *MeshWebhook) overwriteProbes(ns corev1.Namespace, pod *corev1.Pod) error { + tproxyEnabled, err := common.TransparentProxyEnabled(ns, *pod, w.EnableTransparentProxy) + if err != nil { + return err + } + + overwriteProbes, err := common.ShouldOverwriteProbes(*pod, w.TProxyOverwriteProbes) + if err != nil { + return err + } + + if tproxyEnabled && overwriteProbes { + // We don't use the loop index because this needs to line up w.withiptablesConfigJSON, + // which is performed before the sidecar is injected. + idx := 0 + for _, container := range pod.Spec.Containers { + // skip the "envoy-sidecar" container from having it's probes overridden + if container.Name == sidecarContainer { + continue + } + if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { + container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + idx) + } + if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { + container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + idx) + } + if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { + container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + idx) + } + idx++ + } + } + return nil +} + +func (w *MeshWebhook) injectVolumeMount(pod corev1.Pod) { + containersToInject := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationInjectMountVolumes, pod) + + for index, container := range pod.Spec.Containers { + if slices.Contains(containersToInject, container.Name) { + pod.Spec.Containers[index].VolumeMounts = append(pod.Spec.Containers[index].VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: "/consul/connect-inject", + }) + } + } +} + +func (w *MeshWebhook) shouldInject(pod corev1.Pod, namespace string) (bool, error) { + // Don't inject in the Kubernetes system namespaces + if kubeSystemNamespaces.Contains(namespace) { + return false, nil + } + + // Namespace logic + // If in deny list, don't inject + if w.DenyK8sNamespacesSet.Contains(namespace) { + return false, nil + } + + // If not in allow list or allow list is not *, don't inject + if !w.AllowK8sNamespacesSet.Contains("*") && !w.AllowK8sNamespacesSet.Contains(namespace) { + return false, nil + } + + // If we already injected then don't inject again + if pod.Annotations[constants.KeyMeshInjectStatus] != "" || pod.Annotations[constants.KeyInjectStatus] != "" { + return false, nil + } + + // If the explicit true/false is on, then take that value. Note that + // this has to be the last check since it sets a default value after + // all other checks. + if raw, ok := pod.Annotations[constants.AnnotationMeshInject]; ok { + return strconv.ParseBool(raw) + } + + return !w.RequireAnnotation, nil +} + +func (w *MeshWebhook) defaultAnnotations(pod *corev1.Pod, podJson string) error { + if pod.Annotations == nil { + pod.Annotations = make(map[string]string) + } + + pod.Annotations[constants.AnnotationOriginalPod] = podJson + pod.Annotations[constants.AnnotationConsulK8sVersion] = version.GetHumanVersion() + + return nil +} + +// prometheusAnnotations sets the Prometheus scraping configuration +// annotations on the Pod. +func (w *MeshWebhook) prometheusAnnotations(pod *corev1.Pod) error { + enableMetrics, err := w.MetricsConfig.EnableMetrics(*pod) + if err != nil { + return err + } + prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(*pod) + if err != nil { + return err + } + prometheusScrapePath := w.MetricsConfig.PrometheusScrapePath(*pod) + + if enableMetrics { + pod.Annotations[constants.AnnotationPrometheusScrape] = "true" + pod.Annotations[constants.AnnotationPrometheusPort] = prometheusScrapePort + pod.Annotations[constants.AnnotationPrometheusPath] = prometheusScrapePath + } + return nil +} + +// consulNamespace returns the namespace that a service should be +// registered in based on the namespace options. It returns an +// empty string if namespaces aren't enabled. +func (w *MeshWebhook) consulNamespace(ns string) string { + return namespaces.ConsulNamespace(ns, w.EnableNamespaces, w.ConsulDestinationNamespace, w.EnableK8SNSMirroring, w.K8SNSMirroringPrefix) +} + +func findServiceAccountVolumeMount(pod corev1.Pod) (corev1.VolumeMount, string, error) { + // Find the volume mount that is mounted at the known + // service account token location + var volumeMount corev1.VolumeMount + for _, container := range pod.Spec.Containers { + for _, vm := range container.VolumeMounts { + if vm.MountPath == "/var/run/secrets/kubernetes.io/serviceaccount" { + volumeMount = vm + break + } + } + } + + // Return an error if volumeMount is still empty + if (corev1.VolumeMount{}) == volumeMount { + return volumeMount, "", errors.New("unable to find service account token volumeMount") + } + + return volumeMount, "/var/run/secrets/kubernetes.io/serviceaccount/token", nil +} + +func (w *MeshWebhook) InjectDecoder(d *admission.Decoder) error { + w.decoder = d + return nil +} diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go b/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go new file mode 100644 index 0000000000..d6920d83a2 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go @@ -0,0 +1,656 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package webhook_v2 + +import ( + "context" + "testing" + + "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +// This tests the checkAndCreate namespace function that is called +// in meshWebhook.Mutate. Patch generation is tested in the non-enterprise +// tests. Other namespace-specific logic is tested directly in the +// specific methods (shouldInject, consulNamespace). +func TestHandler_MutateWithNamespaces(t *testing.T) { + t.Parallel() + + basicSpec := corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + } + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + + cases := []struct { + Name string + Webhook MeshWebhook + Req admission.Request + ExpectedNamespaces []string + }{ + { + Name: "single destination namespace 'default' from k8s 'default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "default", + }, + }, + ExpectedNamespaces: []string{"default"}, + }, + + { + Name: "single destination namespace 'default' from k8s 'non-default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", + decoder: decoder, + Clientset: clientWithNamespace("non-default"), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "non-default", + }, + }, + ExpectedNamespaces: []string{"default"}, + }, + + { + Name: "single destination namespace 'dest' from k8s 'default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "dest", + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "default", + }, + }, + ExpectedNamespaces: []string{"default", "dest"}, + }, + + { + Name: "single destination namespace 'dest' from k8s 'non-default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "dest", + decoder: decoder, + Clientset: clientWithNamespace("non-default"), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "non-default", + }, + }, + ExpectedNamespaces: []string{"default", "dest"}, + }, + + { + Name: "mirroring from k8s 'default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", // will be overridden + EnableK8SNSMirroring: true, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "default", + }, + }, + ExpectedNamespaces: []string{"default"}, + }, + + { + Name: "mirroring from k8s 'dest'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", // will be overridden + EnableK8SNSMirroring: true, + decoder: decoder, + Clientset: clientWithNamespace("dest"), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "dest", + }, + }, + ExpectedNamespaces: []string{"default", "dest"}, + }, + + { + Name: "mirroring with prefix from k8s 'default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", // will be overridden + EnableK8SNSMirroring: true, + K8SNSMirroringPrefix: "k8s-", + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "default", + }, + }, + ExpectedNamespaces: []string{"default", "k8s-default"}, + }, + + { + Name: "mirroring with prefix from k8s 'dest'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", // will be overridden + EnableK8SNSMirroring: true, + K8SNSMirroringPrefix: "k8s-", + decoder: decoder, + Clientset: clientWithNamespace("dest"), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "dest", + }, + }, + ExpectedNamespaces: []string{"default", "k8s-dest"}, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + client := testClient.APIClient + + // Add the client config and watcher to the test's meshWebhook + tt.Webhook.ConsulConfig = testClient.Cfg + tt.Webhook.ConsulServerConnMgr = testClient.Watcher + + // Mutate! + resp := tt.Webhook.Handle(context.Background(), tt.Req) + require.Equal(t, resp.Allowed, true) + + // Check all the namespace things + // Check that we have the right number of namespaces + namespaces, _, err := client.Namespaces().List(&api.QueryOptions{}) + require.NoError(t, err) + require.Len(t, namespaces, len(tt.ExpectedNamespaces)) + + // Check the namespace details + for _, ns := range tt.ExpectedNamespaces { + actNamespace, _, err := client.Namespaces().Read(ns, &api.QueryOptions{}) + require.NoErrorf(t, err, "error getting namespace %s", ns) + require.NotNilf(t, actNamespace, "namespace %s was nil", ns) + require.Equalf(t, ns, actNamespace.Name, "namespace %s was improperly named", ns) + + // Check created namespace properties + if ns != "default" { + require.Equalf(t, "Auto-generated by consul-k8s", actNamespace.Description, + "wrong namespace description for namespace %s", ns) + require.Containsf(t, actNamespace.Meta, "external-source", + "namespace %s does not contain external-source metadata key", ns) + require.Equalf(t, "kubernetes", actNamespace.Meta["external-source"], + "namespace %s has wrong value for external-source metadata key", ns) + } + + } + }) + } +} + +// Tests that the correct cross-namespace policy is +// added to created namespaces. +func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { + basicSpec := corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + } + + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + + cases := []struct { + Name string + Webhook MeshWebhook + Req admission.Request + ExpectedNamespaces []string + }{ + { + Name: "acls + single destination namespace 'default' from k8s 'default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", + CrossNamespaceACLPolicy: "cross-namespace-policy", + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "default", + }, + }, + ExpectedNamespaces: []string{"default"}, + }, + + { + Name: "acls + single destination namespace 'default' from k8s 'non-default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", + CrossNamespaceACLPolicy: "cross-namespace-policy", + decoder: decoder, + Clientset: clientWithNamespace("non-default"), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "non-default", + }, + }, + ExpectedNamespaces: []string{"default"}, + }, + + { + Name: "acls + single destination namespace 'dest' from k8s 'default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "dest", + CrossNamespaceACLPolicy: "cross-namespace-policy", + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "default", + }, + }, + ExpectedNamespaces: []string{"default", "dest"}, + }, + + { + Name: "acls + single destination namespace 'dest' from k8s 'non-default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "dest", + CrossNamespaceACLPolicy: "cross-namespace-policy", + decoder: decoder, + Clientset: clientWithNamespace("non-default"), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "non-default", + }, + }, + ExpectedNamespaces: []string{"default", "dest"}, + }, + + { + Name: "acls + mirroring from k8s 'default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", // will be overridden + EnableK8SNSMirroring: true, + CrossNamespaceACLPolicy: "cross-namespace-policy", + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "default", + }, + }, + ExpectedNamespaces: []string{"default"}, + }, + + { + Name: "acls + mirroring from k8s 'dest'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", // will be overridden + EnableK8SNSMirroring: true, + CrossNamespaceACLPolicy: "cross-namespace-policy", + decoder: decoder, + Clientset: clientWithNamespace("dest"), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "dest", + }, + }, + ExpectedNamespaces: []string{"default", "dest"}, + }, + + { + Name: "acls + mirroring with prefix from k8s 'default'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", // will be overridden + EnableK8SNSMirroring: true, + K8SNSMirroringPrefix: "k8s-", + CrossNamespaceACLPolicy: "cross-namespace-policy", + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "default", + }, + }, + ExpectedNamespaces: []string{"default", "k8s-default"}, + }, + + { + Name: "acls + mirroring with prefix from k8s 'dest'", + Webhook: MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: "default", // will be overridden + EnableK8SNSMirroring: true, + K8SNSMirroringPrefix: "k8s-", + CrossNamespaceACLPolicy: "cross-namespace-policy", + decoder: decoder, + Clientset: clientWithNamespace("dest"), + }, + Req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + Namespace: "dest", + }, + }, + ExpectedNamespaces: []string{"default", "k8s-dest"}, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + // Set up consul server + adminToken := "123e4567-e89b-12d3-a456-426614174000" + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.ACL.Enabled = true + c.ACL.Tokens.InitialManagement = adminToken + }) + client := testClient.APIClient + + // Add the client config and watcher to the test's meshWebhook + tt.Webhook.ConsulConfig = testClient.Cfg + tt.Webhook.ConsulServerConnMgr = testClient.Watcher + + // Create cross namespace policy + // This would have been created by the acl bootstrapper in the + // default namespace to be attached to all created namespaces. + crossNamespaceRules := `namespace_prefix "" { + service_prefix "" { + policy = "read" + } + node_prefix "" { + policy = "read" + } +} ` + + policyTmpl := api.ACLPolicy{ + Name: "cross-namespace-policy", + Description: "Policy to allow permissions to cross Consul namespaces for k8s services", + Rules: crossNamespaceRules, + } + + _, _, err = client.ACL().PolicyCreate(&policyTmpl, &api.WriteOptions{}) + require.NoError(t, err) + + // Mutate! + resp := tt.Webhook.Handle(context.Background(), tt.Req) + require.Equal(t, resp.Allowed, true) + + // Check all the namespace things + // Check that we have the right number of namespaces + namespaces, _, err := client.Namespaces().List(&api.QueryOptions{}) + require.NoError(t, err) + require.Len(t, namespaces, len(tt.ExpectedNamespaces)) + + // Check the namespace details + for _, ns := range tt.ExpectedNamespaces { + actNamespace, _, err := client.Namespaces().Read(ns, &api.QueryOptions{}) + require.NoErrorf(t, err, "error getting namespace %s", ns) + require.NotNilf(t, actNamespace, "namespace %s was nil", ns) + require.Equalf(t, ns, actNamespace.Name, "namespace %s was improperly named", ns) + + // Check created namespace properties + if ns != "default" { + require.Equalf(t, "Auto-generated by consul-k8s", actNamespace.Description, + "wrong namespace description for namespace %s", ns) + require.Containsf(t, actNamespace.Meta, "external-source", + "namespace %s does not contain external-source metadata key", ns) + require.Equalf(t, "kubernetes", actNamespace.Meta["external-source"], + "namespace %s has wrong value for external-source metadata key", ns) + + // Check for ACL policy things + // The acl bootstrapper will update the `default` namespace, so that + // can't be tested here. + require.NotNilf(t, actNamespace.ACLs, "ACLs was nil for namespace %s", ns) + require.Lenf(t, actNamespace.ACLs.PolicyDefaults, 1, "wrong length for PolicyDefaults in namespace %s", ns) + require.Equalf(t, "cross-namespace-policy", actNamespace.ACLs.PolicyDefaults[0].Name, + "wrong policy name for namespace %s", ns) + } + + } + }) + } +} + +// Test that the annotation for the Consul namespace is added. +func TestHandler_MutateWithNamespaces_Annotation(t *testing.T) { + t.Parallel() + sourceKubeNS := "kube-ns" + + cases := map[string]struct { + ConsulDestinationNamespace string + Mirroring bool + MirroringPrefix string + ExpNamespaceAnnotation string + }{ + "dest: default": { + ConsulDestinationNamespace: "default", + ExpNamespaceAnnotation: "default", + }, + "dest: foo": { + ConsulDestinationNamespace: "foo", + ExpNamespaceAnnotation: "foo", + }, + "mirroring": { + Mirroring: true, + ExpNamespaceAnnotation: sourceKubeNS, + }, + "mirroring with prefix": { + Mirroring: true, + MirroringPrefix: "prefix-", + ExpNamespaceAnnotation: "prefix-" + sourceKubeNS, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + + require.NoError(t, err) + + webhook := MeshWebhook{ + Log: logrtest.NewTestLogger(t), + AllowK8sNamespacesSet: mapset.NewSet("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableNamespaces: true, + ConsulDestinationNamespace: c.ConsulDestinationNamespace, + EnableK8SNSMirroring: c.Mirroring, + K8SNSMirroringPrefix: c.MirroringPrefix, + ConsulConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + decoder: decoder, + Clientset: clientWithNamespace(sourceKubeNS), + } + + pod := corev1.Pod{ + ObjectMeta: v1.ObjectMeta{ + Namespace: sourceKubeNS, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + request := admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &pod), + Namespace: sourceKubeNS, + }, + } + resp := webhook.Handle(context.Background(), request) + require.Equal(t, resp.Allowed, true) + + // Check that the annotation was added as a patch. + var consulNamespaceAnnotationValue string + for _, patch := range resp.Patches { + if patch.Path == "/metadata/annotations" { + for annotationName, annotationValue := range patch.Value.(map[string]interface{}) { + if annotationName == constants.AnnotationConsulNamespace { + consulNamespaceAnnotationValue = annotationValue.(string) + } + } + } + } + require.NotEmpty(t, consulNamespaceAnnotationValue, "no namespace annotation set") + require.Equal(t, c.ExpNamespaceAnnotation, consulNamespaceAnnotationValue) + }) + } +} diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go b/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go new file mode 100644 index 0000000000..b2b1f47392 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go @@ -0,0 +1,2043 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "context" + "encoding/json" + "strconv" + "strings" + "testing" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul/sdk/iptables" + "github.com/stretchr/testify/require" + "gomodules.xyz/jsonpatch/v2" + admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/version" +) + +func TestHandlerHandle(t *testing.T) { + t.Parallel() + basicSpec := corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + } + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{ + Group: "", + Version: "v1", + }, &corev1.Pod{}) + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + + cases := []struct { + Name string + Webhook MeshWebhook + Req admission.Request + Err string // expected error string, not exact + Patches []jsonpatch.Operation + }{ + { + "kube-system namespace", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: metav1.NamespaceSystem, + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + }, + }, + "", + nil, + }, + + { + "already injected", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.KeyMeshInjectStatus: constants.Injected, + }, + }, + Spec: basicSpec, + }), + }, + }, + "", + nil, + }, + + { + "empty pod basic", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations", + }, + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + }, + }, + { + "empty pod basic with lifecycle", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations", + }, + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + + { + Operation: "add", + Path: "/spec/containers/0/readinessProbe", + }, + { + Operation: "add", + Path: "/spec/containers/0/securityContext", + }, + { + Operation: "replace", + Path: "/spec/containers/0/name", + }, + { + Operation: "add", + Path: "/spec/containers/0/args", + }, + { + Operation: "add", + Path: "/spec/containers/0/env", + }, + { + Operation: "add", + Path: "/spec/containers/0/volumeMounts", + }, + }, + }, + // (TODO: ashwin) fix this test once upstreams get correctly processed + //{ + // "pod with upstreams specified", + // MeshWebhook{ + // Log: logrtest.New(t), + // AllowK8sNamespacesSet: mapset.NewSetWith("*"), + // DenyK8sNamespacesSet: mapset.NewSet(), + // decoder: decoder, + // Clientset: defaultTestClientWithNamespace(), + // }, + // admission.Request{ + // AdmissionRequest: admissionv1.AdmissionRequest{ + // Namespace: namespaces.DefaultNamespace, + // Object: encodeRaw(t, &corev1.Pod{ + // ObjectMeta: metav1.ObjectMeta{ + // Annotations: map[string]string{ + // constants.AnnotationMeshDestinations: "echo:1234,db:1234", + // }, + // }, + // Spec: basicSpec, + // }), + // }, + // }, + // "", + // []jsonpatch.Operation{ + // { + // Operation: "add", + // Path: "/metadata/labels", + // }, + // { + // Operation: "add", + // Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + // }, + // { + // Operation: "add", + // Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + // }, + // { + // Operation: "add", + // Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + // }, + // { + // Operation: "add", + // Path: "/spec/volumes", + // }, + // { + // Operation: "add", + // Path: "/spec/initContainers", + // }, + // { + // Operation: "add", + // Path: "/spec/containers/1", + // }, + // { + // Operation: "add", + // Path: "/spec/containers/0/env", + // }, + // }, + //}, + + { + "empty pod with injection disabled", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationMeshInject: "false", + }, + }, + Spec: basicSpec, + }), + }, + }, + "", + nil, + }, + + { + "empty pod with injection truthy", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationMeshInject: "t", + }, + }, + Spec: basicSpec, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + }, + }, + + { + "pod with empty volume mount annotation", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationInjectMountVolumes: "", + }, + }, + Spec: basicSpec, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + }, + }, + { + "pod with volume mount annotation", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationInjectMountVolumes: "web,unknown,web_three_point_oh", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web_two_point_oh", + }, + { + Name: "web_three_point_oh", + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/containers/0/volumeMounts", + }, + { + Operation: "add", + Path: "/spec/containers/2/volumeMounts", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/3", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + }, + }, + { + "pod with sidecar volume mount annotation", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationConsulSidecarUserVolume: "[{\"name\":\"bbb\",\"csi\":{\"driver\":\"bob\"}}]", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + }, + }, + { + "pod with sidecar invalid volume mount annotation", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationConsulSidecarUserVolume: "[a]", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + }), + }, + }, + "error unmarshalling sidecar user volumes: invalid character 'a' looking for beginning of value", + nil, + }, + { + "pod with service annotation", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + Spec: basicSpec, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "foo", + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + }, + }, + + { + "pod with existing label", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "testLabel": "123", + }, + }, + Spec: basicSpec, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/annotations", + }, + { + Operation: "add", + Path: "/metadata/labels/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + }, + }, + { + "tproxy with overwriteProbes is enabled", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableTransparentProxy: true, + TProxyOverwriteProbes: true, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + // We're setting an existing annotation so that we can assert on the + // specific annotations that are set as a result of probes being overwritten. + Annotations: map[string]string{"foo": "bar"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8081), + }, + }, + }, + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), + }, + + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "replace", + Path: "/spec/containers/0/livenessProbe/httpGet/port", + }, + { + Operation: "replace", + Path: "/spec/containers/0/readinessProbe/httpGet/port", + }, + }, + }, + { + "dns redirection enabled", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableTransparentProxy: true, + TProxyOverwriteProbes: true, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + Annotations: map[string]string{constants.KeyConsulDNS: "true"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), + }, + + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/spec/dnsPolicy", + }, + { + Operation: "add", + Path: "/spec/dnsConfig", + }, + }, + }, + { + "dns redirection only enabled if tproxy enabled", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableTransparentProxy: true, + TProxyOverwriteProbes: true, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + Annotations: map[string]string{ + constants.KeyConsulDNS: "true", + constants.KeyTransparentProxy: "false", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + // Note: no DNS policy/config additions. + }, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} + ctx := context.Background() + resp := tt.Webhook.Handle(ctx, tt.Req) + if (tt.Err == "") != resp.Allowed { + t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) + } + if tt.Err != "" { + require.Contains(t, resp.Result.Message, tt.Err) + return + } + + actual := resp.Patches + if len(actual) > 0 { + for i := range actual { + actual[i].Value = nil + } + } + require.ElementsMatch(t, tt.Patches, actual) + }) + } +} + +// This test validates that overwrite probes match the iptables configuration fromiptablesConfigJSON() +// Because they happen at different points in the injection, the port numbers can get out of sync. +func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { + t.Parallel() + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{ + Group: "", + Version: "v1", + }, &corev1.Pod{}) + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + + cases := []struct { + Name string + Webhook MeshWebhook + Req admission.Request + Err string // expected error string, not exact + Patches []jsonpatch.Operation + }{ + { + "tproxy with overwriteProbes is enabled", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableTransparentProxy: true, + TProxyOverwriteProbes: true, + LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + // We're setting an existing annotation so that we can assert on the + // specific annotations that are set as a result of probes being overwritten. + Annotations: map[string]string{"foo": "bar"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8081), + }, + }, + }, + StartupProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8082), + }, + }, + }, + }, + }, + }, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "replace", + Path: "/spec/containers/0/name", + }, + { + Operation: "add", + Path: "/spec/containers/0/args", + }, + { + Operation: "add", + Path: "/spec/containers/0/env", + }, + { + Operation: "add", + Path: "/spec/containers/0/volumeMounts", + }, + { + Operation: "add", + Path: "/spec/containers/0/readinessProbe/tcpSocket", + }, + { + Operation: "add", + Path: "/spec/containers/0/readinessProbe/initialDelaySeconds", + }, + { + Operation: "remove", + Path: "/spec/containers/0/readinessProbe/httpGet", + }, + { + Operation: "add", + Path: "/spec/containers/0/securityContext", + }, + { + Operation: "remove", + Path: "/spec/containers/0/startupProbe", + }, + { + Operation: "remove", + Path: "/spec/containers/0/livenessProbe", + }, + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), + }, + + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + }, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} + ctx := context.Background() + resp := tt.Webhook.Handle(ctx, tt.Req) + if (tt.Err == "") != resp.Allowed { + t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) + } + if tt.Err != "" { + require.Contains(t, resp.Result.Message, tt.Err) + return + } + + var iptablesCfg iptables.Config + var overwritePorts []string + actual := resp.Patches + if len(actual) > 0 { + for i := range actual { + + // We want to grab the iptables configuration from the connect-init container's + // environment. + if actual[i].Path == "/spec/initContainers" { + value := actual[i].Value.([]any) + valueMap := value[0].(map[string]any) + envs := valueMap["env"].([]any) + redirectEnv := envs[8].(map[string]any) + require.Equal(t, redirectEnv["name"].(string), "CONSUL_REDIRECT_TRAFFIC_CONFIG") + iptablesJson := redirectEnv["value"].(string) + + err := json.Unmarshal([]byte(iptablesJson), &iptablesCfg) + require.NoError(t, err) + } + + // We want to accumulate the httpGet Probes from the application container to + // compare them to the iptables rules. This is now the second container in the spec + if strings.Contains(actual[i].Path, "/spec/containers/1") { + valueMap, ok := actual[i].Value.(map[string]any) + require.True(t, ok) + + for k, v := range valueMap { + if strings.Contains(k, "Probe") { + probe := v.(map[string]any) + httpProbe := probe["httpGet"] + httpProbeMap := httpProbe.(map[string]any) + port := httpProbeMap["port"] + portNum := port.(float64) + + overwritePorts = append(overwritePorts, strconv.Itoa(int(portNum))) + } + } + } + + // nil out all the patch values to just compare the keys changing. + actual[i].Value = nil + } + } + // Make sure the iptables excluded ports match the ports on the container + require.ElementsMatch(t, iptablesCfg.ExcludeInboundPorts, overwritePorts) + require.ElementsMatch(t, tt.Patches, actual) + }) + } +} + +func TestHandlerDefaultAnnotations(t *testing.T) { + cases := []struct { + Name string + Pod *corev1.Pod + Expected map[string]string + Err string + }{ + { + "empty", + &corev1.Pod{}, + map[string]string{ + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + }, + "", + }, + { + "basic pod, no ports", + &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + { + Name: "web-side", + }, + }, + }, + }, + map[string]string{ + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + }, + "", + }, + { + "basic pod, with ports", + &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + Ports: []corev1.ContainerPort{ + { + Name: "http", + ContainerPort: 8080, + }, + }, + }, + { + Name: "web-side", + }, + }, + }, + }, + map[string]string{ + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + }, + "", + }, + + { + "basic pod, with unnamed ports", + &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + Ports: []corev1.ContainerPort{ + { + ContainerPort: 8080, + }, + }, + }, + { + Name: "web-side", + }, + }, + }, + }, + map[string]string{ + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + }, + "", + }, + } + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + podJson, err := json.Marshal(tt.Pod) + require.NoError(t, err) + + var w MeshWebhook + err = w.defaultAnnotations(tt.Pod, string(podJson)) + if (tt.Err != "") != (err != nil) { + t.Fatalf("actual: %v, expected err: %v", err, tt.Err) + } + if tt.Err != "" { + require.Contains(t, err.Error(), tt.Err) + return + } + + actual := tt.Pod.Annotations + if len(actual) == 0 { + actual = nil + } + require.Equal(t, tt.Expected, actual) + }) + } +} + +func TestHandlerPrometheusAnnotations(t *testing.T) { + cases := []struct { + Name string + Webhook MeshWebhook + Expected map[string]string + }{ + { + Name: "Sets the correct prometheus annotations on the pod if metrics are enabled", + Webhook: MeshWebhook{ + MetricsConfig: metrics.Config{ + DefaultEnableMetrics: true, + DefaultPrometheusScrapePort: "20200", + DefaultPrometheusScrapePath: "/metrics", + }, + }, + Expected: map[string]string{ + constants.AnnotationPrometheusScrape: "true", + constants.AnnotationPrometheusPort: "20200", + constants.AnnotationPrometheusPath: "/metrics", + }, + }, + { + Name: "Does not set annotations if metrics are not enabled", + Webhook: MeshWebhook{ + MetricsConfig: metrics.Config{ + DefaultEnableMetrics: false, + DefaultPrometheusScrapePort: "20200", + DefaultPrometheusScrapePath: "/metrics", + }, + }, + Expected: map[string]string{}, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + h := tt.Webhook + pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}} + + err := h.prometheusAnnotations(pod) + require.NoError(err) + + require.Equal(pod.Annotations, tt.Expected) + }) + } +} + +// Test consulNamespace function. +func TestConsulNamespace(t *testing.T) { + cases := []struct { + Name string + EnableNamespaces bool + ConsulDestinationNamespace string + EnableK8SNSMirroring bool + K8SNSMirroringPrefix string + K8sNamespace string + Expected string + }{ + { + "namespaces disabled", + false, + "default", + false, + "", + "namespace", + "", + }, + + { + "namespaces disabled, mirroring enabled", + false, + "default", + true, + "", + "namespace", + "", + }, + + { + "namespaces disabled, mirroring enabled, prefix defined", + false, + "default", + true, + "test-", + "namespace", + "", + }, + + { + "namespaces enabled, mirroring disabled", + true, + "default", + false, + "", + "namespace", + "default", + }, + + { + "namespaces enabled, mirroring disabled, prefix defined", + true, + "default", + false, + "test-", + "namespace", + "default", + }, + + { + "namespaces enabled, mirroring enabled", + true, + "default", + true, + "", + "namespace", + "namespace", + }, + + { + "namespaces enabled, mirroring enabled, prefix defined", + true, + "default", + true, + "test-", + "namespace", + "test-namespace", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + + w := MeshWebhook{ + EnableNamespaces: tt.EnableNamespaces, + ConsulDestinationNamespace: tt.ConsulDestinationNamespace, + EnableK8SNSMirroring: tt.EnableK8SNSMirroring, + K8SNSMirroringPrefix: tt.K8SNSMirroringPrefix, + } + + ns := w.consulNamespace(tt.K8sNamespace) + + require.Equal(tt.Expected, ns) + }) + } +} + +// Test shouldInject function. +func TestShouldInject(t *testing.T) { + cases := []struct { + Name string + Pod *corev1.Pod + K8sNamespace string + EnableNamespaces bool + AllowK8sNamespacesSet mapset.Set + DenyK8sNamespacesSet mapset.Set + Expected bool + }{ + { + "kube-system not injected", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + // Service annotation is required for injection + constants.AnnotationService: "testing", + }, + }, + }, + "kube-system", + false, + mapset.NewSet(), + mapset.NewSet(), + false, + }, + { + "kube-public not injected", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "kube-public", + false, + mapset.NewSet(), + mapset.NewSet(), + false, + }, + { + "namespaces disabled, empty allow/deny lists", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + false, + mapset.NewSet(), + mapset.NewSet(), + false, + }, + { + "namespaces disabled, allow *", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + false, + mapset.NewSetWith("*"), + mapset.NewSet(), + true, + }, + { + "namespaces disabled, allow default", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + false, + mapset.NewSetWith("default"), + mapset.NewSet(), + true, + }, + { + "namespaces disabled, allow * and default", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + false, + mapset.NewSetWith("*", "default"), + mapset.NewSet(), + true, + }, + { + "namespaces disabled, allow only ns1 and ns2", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + false, + mapset.NewSetWith("ns1", "ns2"), + mapset.NewSet(), + false, + }, + { + "namespaces disabled, deny default ns", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + false, + mapset.NewSet(), + mapset.NewSetWith("default"), + false, + }, + { + "namespaces disabled, allow *, deny default ns", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + false, + mapset.NewSetWith("*"), + mapset.NewSetWith("default"), + false, + }, + { + "namespaces disabled, default ns in both allow and deny lists", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + false, + mapset.NewSetWith("default"), + mapset.NewSetWith("default"), + false, + }, + { + "namespaces enabled, empty allow/deny lists", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + true, + mapset.NewSet(), + mapset.NewSet(), + false, + }, + { + "namespaces enabled, allow *", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + true, + mapset.NewSetWith("*"), + mapset.NewSet(), + true, + }, + { + "namespaces enabled, allow default", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + true, + mapset.NewSetWith("default"), + mapset.NewSet(), + true, + }, + { + "namespaces enabled, allow * and default", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + true, + mapset.NewSetWith("*", "default"), + mapset.NewSet(), + true, + }, + { + "namespaces enabled, allow only ns1 and ns2", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + true, + mapset.NewSetWith("ns1", "ns2"), + mapset.NewSet(), + false, + }, + { + "namespaces enabled, deny default ns", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + true, + mapset.NewSet(), + mapset.NewSetWith("default"), + false, + }, + { + "namespaces enabled, allow *, deny default ns", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + true, + mapset.NewSetWith("*"), + mapset.NewSetWith("default"), + false, + }, + { + "namespaces enabled, default ns in both allow and deny lists", + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "testing", + }, + }, + }, + "default", + true, + mapset.NewSetWith("default"), + mapset.NewSetWith("default"), + false, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + require := require.New(t) + + w := MeshWebhook{ + RequireAnnotation: false, + EnableNamespaces: tt.EnableNamespaces, + AllowK8sNamespacesSet: tt.AllowK8sNamespacesSet, + DenyK8sNamespacesSet: tt.DenyK8sNamespacesSet, + } + + injected, err := w.shouldInject(*tt.Pod, tt.K8sNamespace) + + require.Equal(nil, err) + require.Equal(tt.Expected, injected) + }) + } +} + +func TestOverwriteProbes(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + tproxyEnabled bool + overwriteProbes bool + podContainers []corev1.Container + expLivenessPort []int + expReadinessPort []int + expStartupPort []int + additionalAnnotations map[string]string + }{ + "transparent proxy disabled; overwrites probes disabled": { + tproxyEnabled: false, + podContainers: []corev1.Container{ + { + Name: "test", + Ports: []corev1.ContainerPort{ + { + Name: "tcp", + ContainerPort: 8081, + }, + { + Name: "http", + ContainerPort: 8080, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + }, + }, + expReadinessPort: []int{8080}, + }, + "transparent proxy enabled; overwrite probes disabled": { + tproxyEnabled: true, + podContainers: []corev1.Container{ + { + Name: "test", + Ports: []corev1.ContainerPort{ + { + Name: "tcp", + ContainerPort: 8081, + }, + { + Name: "http", + ContainerPort: 8080, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + }, + }, + expReadinessPort: []int{8080}, + }, + "transparent proxy disabled; overwrite probes enabled": { + tproxyEnabled: false, + overwriteProbes: true, + podContainers: []corev1.Container{ + { + Name: "test", + Ports: []corev1.ContainerPort{ + { + Name: "tcp", + ContainerPort: 8081, + }, + { + Name: "http", + ContainerPort: 8080, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + }, + }, + expReadinessPort: []int{8080}, + }, + "just the readiness probe": { + tproxyEnabled: true, + overwriteProbes: true, + podContainers: []corev1.Container{ + { + Name: "test", + Ports: []corev1.ContainerPort{ + { + Name: "tcp", + ContainerPort: 8081, + }, + { + Name: "http", + ContainerPort: 8080, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + }, + }, + expReadinessPort: []int{exposedPathsReadinessPortsRangeStart}, + }, + "just the liveness probe": { + tproxyEnabled: true, + overwriteProbes: true, + podContainers: []corev1.Container{ + { + Name: "test", + Ports: []corev1.ContainerPort{ + { + Name: "tcp", + ContainerPort: 8081, + }, + { + Name: "http", + ContainerPort: 8080, + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8081), + }, + }, + }, + }, + }, + expLivenessPort: []int{exposedPathsLivenessPortsRangeStart}, + }, + "skips envoy sidecar": { + tproxyEnabled: true, + overwriteProbes: true, + podContainers: []corev1.Container{ + { + Name: sidecarContainer, + }, + }, + }, + "readiness, liveness and startup probes": { + tproxyEnabled: true, + overwriteProbes: true, + podContainers: []corev1.Container{ + { + Name: "test", + Ports: []corev1.ContainerPort{ + { + Name: "tcp", + ContainerPort: 8081, + }, + { + Name: "http", + ContainerPort: 8080, + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8081), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + StartupProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8082), + }, + }, + }, + }, + }, + expLivenessPort: []int{exposedPathsLivenessPortsRangeStart}, + expReadinessPort: []int{exposedPathsReadinessPortsRangeStart}, + expStartupPort: []int{exposedPathsStartupPortsRangeStart}, + }, + "readiness, liveness and startup probes multiple containers": { + tproxyEnabled: true, + overwriteProbes: true, + podContainers: []corev1.Container{ + { + Name: "test", + Ports: []corev1.ContainerPort{ + { + Name: "tcp", + ContainerPort: 8081, + }, + { + Name: "http", + ContainerPort: 8080, + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8081), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + StartupProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8080), + }, + }, + }, + }, + { + Name: "test-2", + Ports: []corev1.ContainerPort{ + { + Name: "tcp", + ContainerPort: 8083, + }, + { + Name: "http", + ContainerPort: 8082, + }, + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8083), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8082), + }, + }, + }, + StartupProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(8082), + }, + }, + }, + }, + }, + expLivenessPort: []int{exposedPathsLivenessPortsRangeStart, exposedPathsLivenessPortsRangeStart + 1}, + expReadinessPort: []int{exposedPathsReadinessPortsRangeStart, exposedPathsReadinessPortsRangeStart + 1}, + expStartupPort: []int{exposedPathsStartupPortsRangeStart, exposedPathsStartupPortsRangeStart + 1}, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{}, + Annotations: map[string]string{}, + }, + Spec: corev1.PodSpec{ + Containers: c.podContainers, + }, + } + if c.additionalAnnotations != nil { + pod.ObjectMeta.Annotations = c.additionalAnnotations + } + + w := MeshWebhook{ + EnableTransparentProxy: c.tproxyEnabled, + TProxyOverwriteProbes: c.overwriteProbes, + } + err := w.overwriteProbes(corev1.Namespace{}, pod) + require.NoError(t, err) + for i, container := range pod.Spec.Containers { + if container.ReadinessProbe != nil { + require.Equal(t, c.expReadinessPort[i], container.ReadinessProbe.HTTPGet.Port.IntValue()) + } + if container.LivenessProbe != nil { + require.Equal(t, c.expLivenessPort[i], container.LivenessProbe.HTTPGet.Port.IntValue()) + } + if container.StartupProbe != nil { + require.Equal(t, c.expStartupPort[i], container.StartupProbe.HTTPGet.Port.IntValue()) + } + } + }) + } +} + +// encodeRaw is a helper to encode some data into a RawExtension. +func encodeRaw(t *testing.T, input interface{}) runtime.RawExtension { + data, err := json.Marshal(input) + require.NoError(t, err) + return runtime.RawExtension{Raw: data} +} + +// https://tools.ietf.org/html/rfc6901 +func escapeJSONPointer(s string) string { + s = strings.Replace(s, "~", "~0", -1) + s = strings.Replace(s, "/", "~1", -1) + return s +} + +func defaultTestClientWithNamespace() kubernetes.Interface { + return clientWithNamespace("default") +} + +func clientWithNamespace(name string) kubernetes.Interface { + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + return fake.NewSimpleClientset(&ns) +} diff --git a/control-plane/connect-inject/webhook_v2/redirect_traffic.go b/control-plane/connect-inject/webhook_v2/redirect_traffic.go new file mode 100644 index 0000000000..ac45df125f --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/redirect_traffic.go @@ -0,0 +1,137 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/hashicorp/consul/sdk/iptables" + corev1 "k8s.io/api/core/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +// addRedirectTrafficConfigAnnotation creates an iptables.Config in JSON format based on proxy configuration. +// iptables.Config: +// +// ConsulDNSIP: an environment variable named RESOURCE_PREFIX_DNS_SERVICE_HOST where RESOURCE_PREFIX is the consul.fullname in helm. +// ProxyUserID: a constant set in Annotations +// ProxyInboundPort: the service port or bind port +// ProxyOutboundPort: default transparent proxy outbound port or transparent proxy outbound listener port +// ExcludeInboundPorts: prometheus, envoy stats, expose paths, checks and excluded pod annotations +// ExcludeOutboundPorts: pod annotations +// ExcludeOutboundCIDRs: pod annotations +// ExcludeUIDs: pod annotations +func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (string, error) { + cfg := iptables.Config{ + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + } + + // Set the proxy's inbound port. + cfg.ProxyInboundPort = constants.ProxyDefaultInboundPort + + // Set the proxy's outbound port. + cfg.ProxyOutboundPort = iptables.DefaultTProxyOutboundPort + + // If metrics are enabled, get the prometheusScrapePort and exclude it from the inbound ports + enableMetrics, err := w.MetricsConfig.EnableMetrics(pod) + if err != nil { + return "", err + } + if enableMetrics { + prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(pod) + if err != nil { + return "", err + } + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, prometheusScrapePort) + } + + // Exclude any overwritten liveness/readiness/startup ports from redirection. + overwriteProbes, err := common.ShouldOverwriteProbes(pod, w.TProxyOverwriteProbes) + if err != nil { + return "", err + } + + // Exclude the port on which the proxy health check port will be configured if + // using the proxy health check for a service. + if useProxyHealthCheck(pod) { + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(constants.ProxyDefaultHealthPort)) + } + + if overwriteProbes { + // We don't use the loop index because this needs to line up w.overwriteProbes(), + // which is performed after the sidecar is injected. + idx := 0 + for _, container := range pod.Spec.Containers { + // skip the "consul-dataplane" container from having its probes overridden + if container.Name == sidecarContainer { + continue + } + if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+idx)) + } + if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+idx)) + } + if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+idx)) + } + idx++ + } + } + + // Inbound ports + excludeInboundPorts := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeInboundPorts, pod) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, excludeInboundPorts...) + + // Outbound ports + excludeOutboundPorts := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeOutboundPorts, pod) + cfg.ExcludeOutboundPorts = append(cfg.ExcludeOutboundPorts, excludeOutboundPorts...) + + // Outbound CIDRs + excludeOutboundCIDRs := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeOutboundCIDRs, pod) + cfg.ExcludeOutboundCIDRs = append(cfg.ExcludeOutboundCIDRs, excludeOutboundCIDRs...) + + // UIDs + excludeUIDs := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeUIDs, pod) + cfg.ExcludeUIDs = append(cfg.ExcludeUIDs, excludeUIDs...) + + // Add init container user ID to exclude from traffic redirection. + cfg.ExcludeUIDs = append(cfg.ExcludeUIDs, strconv.Itoa(initContainersUserAndGroupID)) + + dnsEnabled, err := consulDNSEnabled(ns, pod, w.EnableConsulDNS, w.EnableTransparentProxy) + if err != nil { + return "", err + } + + if dnsEnabled { + // If Consul DNS is enabled, we find the environment variable that has the value + // of the ClusterIP of the Consul DNS Service. constructDNSServiceHostName returns + // the name of the env variable whose value is the ClusterIP of the Consul DNS Service. + cfg.ConsulDNSIP = consulDataplaneDNSBindHost + cfg.ConsulDNSPort = consulDataplaneDNSBindPort + } + + iptablesConfigJson, err := json.Marshal(&cfg) + if err != nil { + return "", fmt.Errorf("could not marshal iptables config: %w", err) + } + + return string(iptablesConfigJson), nil +} + +// addRedirectTrafficConfigAnnotation add the created iptables JSON config as an annotation on the provided pod. +func (w *MeshWebhook) addRedirectTrafficConfigAnnotation(pod *corev1.Pod, ns corev1.Namespace) error { + iptablesConfig, err := w.iptablesConfigJSON(*pod, ns) + if err != nil { + return err + } + + pod.Annotations[constants.AnnotationRedirectTraffic] = iptablesConfig + + return nil +} diff --git a/control-plane/connect-inject/webhook_v2/redirect_traffic_test.go b/control-plane/connect-inject/webhook_v2/redirect_traffic_test.go new file mode 100644 index 0000000000..077189ead4 --- /dev/null +++ b/control-plane/connect-inject/webhook_v2/redirect_traffic_test.go @@ -0,0 +1,481 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhook_v2 + +import ( + "encoding/json" + "fmt" + "strconv" + "testing" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul/sdk/iptables" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" +) + +const ( + defaultPodName = "fakePod" + defaultNamespace = "default" +) + +func TestAddRedirectTrafficConfig(t *testing.T) { + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{ + Group: "", + Version: "v1", + }, &corev1.Pod{}) + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + cases := []struct { + name string + webhook MeshWebhook + pod *corev1.Pod + namespace corev1.Namespace + dnsEnabled bool + expCfg iptables.Config + expErr error + }{ + { + name: "basic bare minimum pod", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"5996"}, + }, + }, + { + name: "proxy health checks enabled", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationUseProxyHealthCheck: "true", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"5996"}, + ExcludeInboundPorts: []string{"21000"}, + }, + }, + { + name: "metrics enabled", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationEnableMetrics: "true", + constants.AnnotationPrometheusScrapePort: "13373", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"5996"}, + ExcludeInboundPorts: []string{"13373"}, + }, + }, + { + name: "metrics enabled with incorrect annotation", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationEnableMetrics: "invalid", + constants.AnnotationPrometheusScrapePort: "13373", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"5996"}, + ExcludeInboundPorts: []string{"13373"}, + }, + expErr: fmt.Errorf("%s annotation value of %s was invalid: %s", constants.AnnotationEnableMetrics, "invalid", "strconv.ParseBool: parsing \"invalid\": invalid syntax"), + }, + { + name: "overwrite probes, transparent proxy annotation set", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationTransparentProxyOverwriteProbes: "true", + constants.KeyTransparentProxy: "true", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(exposedPathsLivenessPortsRangeStart), + }, + }, + }, + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"5996"}, + ExcludeInboundPorts: []string{strconv.Itoa(exposedPathsLivenessPortsRangeStart)}, + }, + }, + { + name: "exclude inbound ports", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationTProxyExcludeInboundPorts: "1111,11111", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"5996"}, + ExcludeInboundPorts: []string{"1111", "11111"}, + }, + }, + { + name: "exclude outbound ports", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationTProxyExcludeOutboundPorts: "2222,22222", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"5996"}, + ExcludeOutboundPorts: []string{"2222", "22222"}, + }, + }, + { + name: "exclude outbound CIDRs", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationTProxyExcludeOutboundCIDRs: "3.3.3.3,3.3.3.3/24", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{strconv.Itoa(initContainersUserAndGroupID)}, + ExcludeOutboundCIDRs: []string{"3.3.3.3", "3.3.3.3/24"}, + }, + }, + { + name: "exclude UIDs", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationTProxyExcludeUIDs: "4444,44444", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "", + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeUIDs: []string{"4444", "44444", strconv.Itoa(initContainersUserAndGroupID)}, + }, + }, + { + name: "exclude inbound ports, outbound ports, outbound CIDRs, and UIDs", + webhook: MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + }, + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultNamespace, + Name: defaultPodName, + Annotations: map[string]string{ + constants.AnnotationTProxyExcludeInboundPorts: "1111,11111", + constants.AnnotationTProxyExcludeOutboundPorts: "2222,22222", + constants.AnnotationTProxyExcludeOutboundCIDRs: "3.3.3.3,3.3.3.3/24", + constants.AnnotationTProxyExcludeUIDs: "4444,44444", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + }, + }, + }, + }, + expCfg: iptables.Config{ + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), + ProxyInboundPort: constants.ProxyDefaultInboundPort, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + ExcludeInboundPorts: []string{"1111", "11111"}, + ExcludeOutboundPorts: []string{"2222", "22222"}, + ExcludeOutboundCIDRs: []string{"3.3.3.3", "3.3.3.3/24"}, + ExcludeUIDs: []string{"4444", "44444", strconv.Itoa(initContainersUserAndGroupID)}, + }, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + err = c.webhook.addRedirectTrafficConfigAnnotation(c.pod, c.namespace) + + // Only compare annotation and iptables config on successful runs + if c.expErr == nil { + require.NoError(t, err) + anno, ok := c.pod.Annotations[constants.AnnotationRedirectTraffic] + require.Equal(t, ok, true) + + actualConfig := iptables.Config{} + err = json.Unmarshal([]byte(anno), &actualConfig) + require.NoError(t, err) + require.Equal(t, c.expCfg, actualConfig) + } else { + require.EqualError(t, err, c.expErr.Error()) + } + }) + } +} + +func TestRedirectTraffic_consulDNS(t *testing.T) { + cases := map[string]struct { + globalEnabled bool + annotations map[string]string + namespaceLabel map[string]string + expectConsulDNSConfig bool + }{ + "enabled globally, ns not set, annotation not provided": { + globalEnabled: true, + expectConsulDNSConfig: true, + }, + "enabled globally, ns not set, annotation is false": { + globalEnabled: true, + annotations: map[string]string{constants.KeyConsulDNS: "false"}, + expectConsulDNSConfig: false, + }, + "enabled globally, ns not set, annotation is true": { + globalEnabled: true, + annotations: map[string]string{constants.KeyConsulDNS: "true"}, + expectConsulDNSConfig: true, + }, + "disabled globally, ns not set, annotation not provided": { + expectConsulDNSConfig: false, + }, + "disabled globally, ns not set, annotation is false": { + annotations: map[string]string{constants.KeyConsulDNS: "false"}, + expectConsulDNSConfig: false, + }, + "disabled globally, ns not set, annotation is true": { + annotations: map[string]string{constants.KeyConsulDNS: "true"}, + expectConsulDNSConfig: true, + }, + "disabled globally, ns enabled, annotation not set": { + namespaceLabel: map[string]string{constants.KeyConsulDNS: "true"}, + expectConsulDNSConfig: true, + }, + "enabled globally, ns disabled, annotation not set": { + globalEnabled: true, + namespaceLabel: map[string]string{constants.KeyConsulDNS: "false"}, + expectConsulDNSConfig: false, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + w := MeshWebhook{ + EnableConsulDNS: c.globalEnabled, + EnableTransparentProxy: true, + ConsulConfig: &consul.Config{HTTPPort: 8500}, + } + + pod := minimal() + pod.Annotations = c.annotations + + ns := testNS + ns.Labels = c.namespaceLabel + iptablesConfig, err := w.iptablesConfigJSON(*pod, ns) + require.NoError(t, err) + + actualConfig := iptables.Config{} + err = json.Unmarshal([]byte(iptablesConfig), &actualConfig) + require.NoError(t, err) + if c.expectConsulDNSConfig { + require.Equal(t, "127.0.0.1", actualConfig.ConsulDNSIP) + require.Equal(t, 8600, actualConfig.ConsulDNSPort) + } else { + require.Empty(t, actualConfig.ConsulDNSIP) + } + }) + } +} diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 75c01ea943..32b97ac245 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -5,13 +5,17 @@ package connectinject import ( "context" + "github.com/hashicorp/consul-server-connection-manager/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" + ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + webhookV2 "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook_v2" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -24,13 +28,13 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) - //lifecycleConfig := lifecycle.Config{ - // DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, - // DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, - // DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, - // DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, - // DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, - //} + lifecycleConfig := lifecycle.Config{ + DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, + DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, + DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, + DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, + DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, + } metricsConfig := metrics.Config{ DefaultEnableMetrics: c.flagDefaultEnableMetrics, @@ -99,8 +103,58 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage //} // TODO: register webhooks - - // TODO: Update Webhook CA Bundle + mgr.GetWebhookServer().CertDir = c.flagCertDir + + mgr.GetWebhookServer().Register("/mutate", + &ctrlRuntimeWebhook.Admission{Handler: &webhookV2.MeshWebhook{ + Clientset: c.clientset, + ReleaseNamespace: c.flagReleaseNamespace, + ConsulConfig: consulConfig, + ConsulServerConnMgr: watcher, + ImageConsul: c.flagConsulImage, + ImageConsulDataplane: c.flagConsulDataplaneImage, + EnvoyExtraArgs: c.flagEnvoyExtraArgs, + ImageConsulK8S: c.flagConsulK8sImage, + RequireAnnotation: !c.flagDefaultInject, + AuthMethod: c.flagACLAuthMethod, + ConsulCACert: string(c.caCertPem), + TLSEnabled: c.consul.UseTLS, + ConsulAddress: c.consul.Addresses, + SkipServerWatch: c.consul.SkipServerWatch, + ConsulTLSServerName: c.consul.TLSServerName, + DefaultProxyCPURequest: c.sidecarProxyCPURequest, + DefaultProxyCPULimit: c.sidecarProxyCPULimit, + DefaultProxyMemoryRequest: c.sidecarProxyMemoryRequest, + DefaultProxyMemoryLimit: c.sidecarProxyMemoryLimit, + DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, + LifecycleConfig: lifecycleConfig, + MetricsConfig: metricsConfig, + InitContainerResources: c.initContainerResources, + ConsulPartition: c.consul.Partition, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + EnableNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, + K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + EnableCNI: c.flagEnableCNI, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + EnableConsulDNS: c.flagEnableConsulDNS, + EnableOpenShift: c.flagEnableOpenShift, + Log: ctrl.Log.WithName("handler").WithName("consul-mesh"), + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, + }}) + + if c.flagEnableWebhookCAUpdate { + err := c.updateWebhookCABundle(ctx) + if err != nil { + setupLog.Error(err, "problem getting CA Cert") + return err + } + } return nil } From fd4e184426d3e99563c982c7d27b49d5833ce0ad Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 12 Sep 2023 15:07:49 -0400 Subject: [PATCH 377/592] NET-5531 Translate response header modifier(s) from HTTPRoute onto Consul config entry (#2904) * Translate response header modifier(s) from HTTPRoute onto Consul config entry * Update dependency pins to include response filter changes in consul modules * Add changelog entry * Account for response filters when determining whether an HTTPRoute change requires a sync * Stop setting empty header modifier in Consul when not present in HTTPRoute * Remove unnecessary len check * Make comments more robust for replace directives Also use same pin for `sdk` that we're using for `api` and `proto-public` --- .changelog/2904.txt | 3 + control-plane/api-gateway/common/diff.go | 4 +- .../api-gateway/common/translation.go | 109 +++++++++++++----- .../api-gateway/common/translation_test.go | 86 +++++++++----- control-plane/go.mod | 34 +++--- control-plane/go.sum | 48 ++++---- 6 files changed, 186 insertions(+), 98 deletions(-) create mode 100644 .changelog/2904.txt diff --git a/.changelog/2904.txt b/.changelog/2904.txt new file mode 100644 index 0000000000..69755454d9 --- /dev/null +++ b/.changelog/2904.txt @@ -0,0 +1,3 @@ +```release-note:feature +api-gateway: Add support for response header modifiers in HTTPRoute filters +``` diff --git a/control-plane/api-gateway/common/diff.go b/control-plane/api-gateway/common/diff.go index a50581ca31..f2fdf89d8b 100644 --- a/control-plane/api-gateway/common/diff.go +++ b/control-plane/api-gateway/common/diff.go @@ -148,6 +148,7 @@ func (e entryComparator) httpRoutesEqual(a, b api.HTTPRouteConfigEntry) bool { func (e entryComparator) httpRouteRulesEqual(a, b api.HTTPRouteRule) bool { return slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) && + slices.EqualFunc(a.ResponseFilters.Headers, b.ResponseFilters.Headers, e.httpHeaderFiltersEqual) && slices.EqualFunc(a.Matches, b.Matches, e.httpMatchesEqual) && slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) && bothNilOrEqualFunc(a.Filters.RetryFilter, b.Filters.RetryFilter, e.retryFiltersEqual) && @@ -160,7 +161,8 @@ func (e entryComparator) httpServicesEqual(a, b api.HTTPService) bool { orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) && orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) && slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && - bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) + bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) && + slices.EqualFunc(a.ResponseFilters.Headers, b.ResponseFilters.Headers, e.httpHeaderFiltersEqual) } func (e entryComparator) httpMatchesEqual(a, b api.HTTPMatch) bool { diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index d3627b11b4..4b6092ee41 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -178,12 +178,13 @@ func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, ru } matches := ConvertSliceFunc(rule.Matches, t.translateHTTPMatch) - filters := t.translateHTTPFilters(rule.Filters, resources, route.Namespace) + filters, responseFilters := t.translateHTTPFilters(rule.Filters, resources, route.Namespace) return api.HTTPRouteRule{ - Services: services, - Matches: matches, - Filters: filters, + Filters: filters, + Matches: matches, + ResponseFilters: responseFilters, + Services: services, }, true } @@ -196,28 +197,30 @@ func (t ResourceTranslator) translateHTTPBackendRef(route gwv1beta1.HTTPRoute, r isServiceRef := NilOrEqual(ref.Group, "") && NilOrEqual(ref.Kind, "Service") if isServiceRef && resources.HasService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { - filters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) + filters, responseFilters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) service := resources.Service(id) return api.HTTPService{ - Name: service.Name, - Namespace: service.Namespace, - Partition: t.ConsulPartition, - Filters: filters, - Weight: DerefIntOr(ref.Weight, 1), + Name: service.Name, + Namespace: service.Namespace, + Partition: t.ConsulPartition, + Filters: filters, + ResponseFilters: responseFilters, + Weight: DerefIntOr(ref.Weight, 1), }, true } isMeshServiceRef := DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) if isMeshServiceRef && resources.HasMeshService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { - filters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) + filters, responseFilters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) service := resources.MeshService(id) return api.HTTPService{ - Name: service.Name, - Namespace: service.Namespace, - Partition: t.ConsulPartition, - Filters: filters, - Weight: DerefIntOr(ref.Weight, 1), + Name: service.Name, + Namespace: service.Namespace, + Partition: t.ConsulPartition, + Filters: filters, + ResponseFilters: responseFilters, + Weight: DerefIntOr(ref.Weight, 1), }, true } @@ -275,26 +278,61 @@ func (t ResourceTranslator) translateHTTPQueryMatch(match gwv1beta1.HTTPQueryPar } } -func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter, resourceMap *ResourceMap, namespace string) api.HTTPFilters { - var urlRewrite *api.URLRewrite - consulFilter := api.HTTPHeaderFilter{ - Add: make(map[string]string), - Set: make(map[string]string), - } - var retryFilter *api.RetryFilter - var timeoutFilter *api.TimeoutFilter - +func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter, resourceMap *ResourceMap, namespace string) (api.HTTPFilters, api.HTTPResponseFilters) { + var ( + urlRewrite *api.URLRewrite + retryFilter *api.RetryFilter + timeoutFilter *api.TimeoutFilter + requestHeaderFilters = []api.HTTPHeaderFilter{} + responseHeaderFilters = []api.HTTPHeaderFilter{} + ) + + // Convert Gateway API filters to portions of the Consul request and response filters. + // Multiple filters applying the same or conflicting operations are allowed but may + // result in unexpected behavior. for _, filter := range filters { if filter.RequestHeaderModifier != nil { - consulFilter.Remove = append(consulFilter.Remove, filter.RequestHeaderModifier.Remove...) + newFilter := api.HTTPHeaderFilter{} - for _, toAdd := range filter.RequestHeaderModifier.Add { - consulFilter.Add[string(toAdd.Name)] = toAdd.Value + newFilter.Remove = append(newFilter.Remove, filter.RequestHeaderModifier.Remove...) + + if len(filter.RequestHeaderModifier.Add) > 0 { + newFilter.Add = map[string]string{} + for _, toAdd := range filter.RequestHeaderModifier.Add { + newFilter.Add[string(toAdd.Name)] = toAdd.Value + } } - for _, toSet := range filter.RequestHeaderModifier.Set { - consulFilter.Set[string(toSet.Name)] = toSet.Value + if len(filter.RequestHeaderModifier.Set) > 0 { + newFilter.Set = map[string]string{} + for _, toSet := range filter.RequestHeaderModifier.Set { + newFilter.Set[string(toSet.Name)] = toSet.Value + } } + + requestHeaderFilters = append(requestHeaderFilters, newFilter) + } + + if filter.ResponseHeaderModifier != nil { + newFilter := api.HTTPHeaderFilter{} + + newFilter.Remove = append(newFilter.Remove, filter.ResponseHeaderModifier.Remove...) + + if len(filter.ResponseHeaderModifier.Add) > 0 { + newFilter.Add = map[string]string{} + for _, toAdd := range filter.ResponseHeaderModifier.Add { + newFilter.Add[string(toAdd.Name)] = toAdd.Value + } + } + + if len(filter.ResponseHeaderModifier.Set) > 0 { + newFilter.Set = map[string]string{} + for _, toSet := range filter.ResponseHeaderModifier.Set { + newFilter.Set[string(toSet.Name)] = toSet.Value + } + } + + responseHeaderFilters = append(responseHeaderFilters, newFilter) } // we drop any path rewrites that are not prefix matches as we don't support those @@ -338,12 +376,19 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi } } - return api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{consulFilter}, + + requestFilter := api.HTTPFilters{ + Headers: requestHeaderFilters, URLRewrite: urlRewrite, RetryFilter: retryFilter, TimeoutFilter: timeoutFilter, } + + responseFilter := api.HTTPResponseFilters{ + Headers: responseHeaderFilters, + } + + return requestFilter, responseFilter } func (t ResourceTranslator) ToTCPRoute(route gwv1alpha2.TCPRoute, resources *ResourceMap) *api.TCPRouteConfigEntry { diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index d562845fc8..94c78d1ee7 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -511,6 +511,7 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, URLRewrite: &api.URLRewrite{Path: "v1"}, }, + ResponseFilters: api.HTTPResponseFilters{Headers: []api.HTTPHeaderFilter{}}, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -536,8 +537,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, Services: []api.HTTPService{ { - Name: "service one", - Weight: 45, + Name: "service one", + Namespace: "other", Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{ { @@ -555,7 +556,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Path: "path", }, }, - Namespace: "other", + ResponseFilters: api.HTTPResponseFilters{Headers: []api.HTTPHeaderFilter{}}, + Weight: 45, }, }, }, @@ -715,6 +717,9 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, }, }, + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -740,8 +745,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, Services: []api.HTTPService{ { - Name: "service one", - Weight: 45, + Name: "service one", + Namespace: "some ns", Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{ { @@ -759,7 +764,10 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Path: "path", }, }, - Namespace: "some ns", + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, + Weight: 45, }, }, }, @@ -927,6 +935,9 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, URLRewrite: &api.URLRewrite{Path: "v1"}, }, + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -952,8 +963,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, Services: []api.HTTPService{ { - Name: "service one", - Weight: 45, + Name: "service one", + Namespace: "some ns", Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{ { @@ -971,7 +982,10 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Path: "path", }, }, - Namespace: "some ns", + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, + Weight: 45, }, }, }, @@ -1153,6 +1167,9 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, URLRewrite: &api.URLRewrite{Path: "v1"}, }, + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -1177,10 +1194,17 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, }, Services: []api.HTTPService{ - {Name: "some-override", Namespace: "svc-ns", Weight: 1, Filters: api.HTTPFilters{Headers: []api.HTTPHeaderFilter{{Add: make(map[string]string), Set: make(map[string]string)}}}}, { - Name: "service one", - Weight: 45, + Name: "some-override", + Namespace: "svc-ns", + Weight: 1, + Filters: api.HTTPFilters{Headers: []api.HTTPHeaderFilter{}}, + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }}, + { + Name: "service one", + Namespace: "some ns", Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{ { @@ -1198,7 +1222,10 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Path: "path", }, }, - Namespace: "some ns", + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, + Weight: 45, }, }, }, @@ -1340,7 +1367,7 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Rules: []api.HTTPRouteRule{ { Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{{Add: map[string]string{}, Set: map[string]string{}}}, + Headers: []api.HTTPHeaderFilter{}, URLRewrite: nil, RetryFilter: &api.RetryFilter{ NumRetries: pointer.Uint32(3), @@ -1349,6 +1376,9 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { RetryOnConnectFailure: pointer.Bool(false), }, }, + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -1377,7 +1407,7 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Name: "service one", Weight: 45, Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{{Add: map[string]string{}, Set: map[string]string{}}}, + Headers: []api.HTTPHeaderFilter{}, RetryFilter: &api.RetryFilter{ NumRetries: pointer.Uint32(3), RetryOn: []string{"cancelled"}, @@ -1385,6 +1415,9 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { RetryOnConnectFailure: pointer.Bool(false), }, }, + ResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, Namespace: "other", }, }, @@ -1603,10 +1636,11 @@ func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { filters []gwv1beta1.HTTPRouteFilter } tests := []struct { - name string - fields fields - args args - want api.HTTPFilters + name string + fields fields + args args + want api.HTTPFilters + wantResponseFilters api.HTTPResponseFilters }{ { name: "no httproutemodifier set", @@ -1619,14 +1653,12 @@ func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { }, }, want: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{}, - Set: map[string]string{}, - }, - }, + Headers: []api.HTTPHeaderFilter{}, URLRewrite: nil, }, + wantResponseFilters: api.HTTPResponseFilters{ + Headers: []api.HTTPHeaderFilter{}, + }, }, } for _, tt := range tests { @@ -1639,7 +1671,9 @@ func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { ConsulPartition: tt.fields.ConsulPartition, Datacenter: tt.fields.Datacenter, } - assert.Equalf(t1, tt.want, t.translateHTTPFilters(tt.args.filters, nil, ""), "translateHTTPFilters(%v)", tt.args.filters) + requestHeaders, responseHeaders := t.translateHTTPFilters(tt.args.filters, nil, "") + assert.Equalf(t1, tt.want, requestHeaders, "translateHTTPFilters(%v)", tt.args.filters) + assert.Equalf(t1, tt.wantResponseFilters, responseHeaders, "translateHTTPFilters(%v)", tt.args.filters) }) } } diff --git a/control-plane/go.mod b/control-plane/go.mod index 2fafd2ed1c..8625d7ca66 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -1,8 +1,12 @@ module github.com/hashicorp/consul-k8s/control-plane -// TODO: remove when the SDK is released for Consul 1.17 -// The replace directive is needed be because `api` requires 0.14.1 of SDK and is both a direct and indirect dependency -replace github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924 +// TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases +replace ( + // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version + github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230911164019-a69e901660bd + // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version + github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd +) require ( github.com/cenkalti/backoff v2.2.1+incompatible @@ -14,8 +18,8 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.4 - github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 - github.com/hashicorp/consul/proto-public v0.1.2-0.20230906155245-56917eb4c968 + github.com/hashicorp/consul/api v1.10.1-0.20230911164019-a69e901660bd + github.com/hashicorp/consul/proto-public v0.4.1 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 @@ -23,6 +27,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-netaddrs v0.1.0 github.com/hashicorp/go-rootcerts v1.0.2 + github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.8.3 @@ -33,8 +38,8 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.8.3 go.uber.org/zap v1.24.0 - golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 - golang.org/x/text v0.11.0 + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 + golang.org/x/text v0.12.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 google.golang.org/grpc v1.55.0 @@ -104,7 +109,6 @@ require ( github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/mdns v1.0.4 // indirect @@ -151,14 +155,14 @@ require ( go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.13.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.11.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 435a090003..1091e90f90 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -263,12 +263,12 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= -github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= -github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230906155245-56917eb4c968 h1:J6FLkHXcGd80fUbouFn3kklR3GGHVV0OCyjItyZS8h0= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230906155245-56917eb4c968/go.mod h1:ENwzmloQTUPAYPu7nC1mli3VY0Ny9QNi/FSzJ+KlZD0= -github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924 h1:gkb6/ix0Tg1Th5FTjyq4QklLgrtIVQ/TUB0kbhIcPsY= -github.com/hashicorp/consul/sdk v0.4.1-0.20230825164720-ecdcde430924/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= +github.com/hashicorp/consul/api v1.10.1-0.20230911164019-a69e901660bd h1:TXcz98wzcjBdCUa8q8BgossUKh9slyVCgzE91chvJTc= +github.com/hashicorp/consul/api v1.10.1-0.20230911164019-a69e901660bd/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230911164019-a69e901660bd h1:3qvFUa6FTiYPiHmZadPnsVskphYIjv4n6vZlB57oiPM= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230911164019-a69e901660bd/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= +github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd h1:tRrSgVY71Jl6T2lJUokMLj3T1MO9uiSvW0CieBkjTvo= +github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= 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= @@ -572,8 +572,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -584,8 +584,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= -golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -608,8 +608,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -654,8 +654,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -677,8 +677,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -742,13 +742,13 @@ 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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -757,8 +757,8 @@ 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.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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -811,8 +811,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 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= From ed61c564ca6f893031e21c6fb08e1c4b8501be4a Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Wed, 13 Sep 2023 14:48:35 -0400 Subject: [PATCH 378/592] feat(helm): block v2 installs when admin partitions enabled (#2953) --- charts/consul/templates/_helpers.tpl | 3 ++ charts/consul/test/unit/helpers.bats | 14 +++++++ .../consul/test/unit/partition-init-job.bats | 40 ------------------- 3 files changed, 17 insertions(+), 40 deletions(-) diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 06efdbc3a5..5f06839923 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -469,6 +469,9 @@ Usage: {{ template "consul.validateResourceAPIs" . }} {{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.peering.enabled ) }} {{fail "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported."}} {{- end }} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.adminPartitions.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, global.adminPartitions.enabled is currently unsupported."}} +{{- end }} {{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.federation.enabled ) }} {{fail "When the value global.experiments.resourceAPIs is set, global.federation.enabled is currently unsupported."}} {{- end }} diff --git a/charts/consul/test/unit/helpers.bats b/charts/consul/test/unit/helpers.bats index 82fc54bcf2..f8523a7220 100644 --- a/charts/consul/test/unit/helpers.bats +++ b/charts/consul/test/unit/helpers.bats @@ -348,6 +348,20 @@ load _helpers [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported." ]] } +@test "connectInject/Deployment: fails if resource-apis is set and admin partitions are enabled" { + cd `chart_dir` + run helm template \ + -s templates/tests/test-runner.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.adminPartitions.enabled=true' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.adminPartitions.enabled is currently unsupported." ]] +} + @test "connectInject/Deployment: fails if resource-apis is set and federation is enabled" { cd `chart_dir` run helm template \ diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index 1e5bc954d7..7b2dfb940f 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -1017,44 +1017,4 @@ reservedNameTest() { local actualTemplateBaz=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.baz' | tee /dev/stderr) [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] -} - -#-------------------------------------------------------------------- -# global.experiments=["resource-apis"] - -@test "partitionInit/Job: -enable-resource-apis=true is not set in command when global.experiments is empty" { - cd `chart_dir` - local object=$(helm template \ - -s templates/partition-init-job.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'server.enabled=false' \ - --set 'global.adminPartitions.name=bar' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=foo' \ - . | tee /dev/stderr) - - # Test the flag is set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[] | select(.name == "partition-init-job") | .command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) - [ "${actual}" = "false" ] -} - -@test "partitionInit/Job: -enable-resource-apis=true is set in command when global.experiments contains \"resource-apis\"" { - cd `chart_dir` - local object=$(helm template \ - -s templates/partition-init-job.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'server.enabled=false' \ - --set 'global.adminPartitions.name=bar' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=foo' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr) - - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[] | select(.name == "partition-init-job") | .command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) - [ "${actual}" = "true" ] } \ No newline at end of file From 38dc912170e7b83e020175fd4abcdb6649191ddc Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Wed, 13 Sep 2023 21:36:34 -0400 Subject: [PATCH 379/592] feat: v2 mesh-init command (#2941) * feat: v2 mesh-init command * bugfix mesh-init test * add mesh-init args to webhook * fix: remove v2 flags from partition-init * update telemetry-collector with v2 flags * Apply suggestions from code review Co-authored-by: Michael Zalimeni * PR feedback Part II * bugfix test * fix: endpoints v2 selector stability --------- Co-authored-by: Michael Zalimeni --- .../consul/templates/partition-init-job.yaml | 3 - .../telemetry-collector-deployment.yaml | 2 +- .../telemetry-collector-v2-deployment.yaml | 378 +++++ .../unit/telemetry-collector-deployment.bats | 14 + .../telemetry-collector-v2-deployment.bats | 1349 +++++++++++++++++ control-plane/commands.go | 8 +- .../constants/annotations_and_labels.go | 6 + .../endpointsv2/endpoints_controller.go | 12 +- .../endpointsv2/endpoints_controller_test.go | 12 +- .../consul_dataplane_sidecar_test.go | 2 +- .../webhook_v2/container_init.go | 29 +- .../webhook_v2/container_init_test.go | 84 +- .../webhook_v2/container_volume.go | 2 +- .../connect-inject/webhook_v2/mesh_webhook.go | 4 +- .../webhook_v2/mesh_webhook_test.go | 6 +- control-plane/consul/dataplane_client.go | 28 + control-plane/consul/dataplane_client_test.go | 206 +++ control-plane/subcommand/mesh-init/command.go | 283 ++++ .../subcommand/mesh-init/command_ent_test.go | 118 ++ .../subcommand/mesh-init/command_test.go | 425 ++++++ .../subcommand/partition-init/command.go | 9 - 21 files changed, 2868 insertions(+), 112 deletions(-) create mode 100644 charts/consul/templates/telemetry-collector-v2-deployment.yaml create mode 100755 charts/consul/test/unit/telemetry-collector-v2-deployment.bats create mode 100644 control-plane/consul/dataplane_client.go create mode 100644 control-plane/consul/dataplane_client_test.go create mode 100644 control-plane/subcommand/mesh-init/command.go create mode 100644 control-plane/subcommand/mesh-init/command_ent_test.go create mode 100644 control-plane/subcommand/mesh-init/command_test.go diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 663a64bcbf..6e21289f22 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -117,9 +117,6 @@ spec: {{- if .Values.global.cloud.enabled }} -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} \ {{- end }} - {{- if (mustHas "resource-apis" .Values.global.experiments) }} - -enable-resource-apis=true - {{- end }} resources: requests: memory: "50Mi" diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index d6f3a91cfa..396cc147ab 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -1,4 +1,4 @@ -{{- if .Values.telemetryCollector.enabled }} +{{- if and .Values.telemetryCollector.enabled (not (mustHas "resource-apis" .Values.global.experiments)) }} {{- if not .Values.telemetryCollector.image}}{{ fail "telemetryCollector.image must be set to enable consul-telemetry-collector" }}{{ end }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} diff --git a/charts/consul/templates/telemetry-collector-v2-deployment.yaml b/charts/consul/templates/telemetry-collector-v2-deployment.yaml new file mode 100644 index 0000000000..a88277f3b2 --- /dev/null +++ b/charts/consul/templates/telemetry-collector-v2-deployment.yaml @@ -0,0 +1,378 @@ +{{- if and .Values.telemetryCollector.enabled (mustHas "resource-apis" .Values.global.experiments) }} +{{- if not .Values.telemetryCollector.image}}{{ fail "telemetryCollector.image must be set to enable consul-telemetry-collector" }}{{ end }} +{{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} +{{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} +{{ template "consul.validateCloudSecretKeys" . }} +{{ template "consul.validateTelemetryCollectorCloud" . }} +{{ template "consul.validateTelemetryCollectorCloudSecretKeys" . }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "consul.fullname" . }}-telemetry-collector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: consul-telemetry-collector + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.telemetryCollector.replicas }} + selector: + matchLabels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: consul-telemetry-collector + template: + metadata: + annotations: + "consul.hashicorp.com/mesh-inject": "false" + # This annotation tells the pod controller that this pod was injected even though it wasn't. + # This ensures the pod controller will sync a workload for the pod into Consul + "consul.hashicorp.com/mesh-inject-status": "injected" + # We aren't using tproxy and we don't have an original pod. This would be simpler if we made a path similar + # to gateways + "consul.hashicorp.com/transparent-proxy": "false" + "consul.hashicorp.com/transparent-proxy-overwrite-probes": "false" + "consul.hashicorp.com/consul-k8s-version": {{ $.Chart.Version }} + {{- if .Values.telemetryCollector.customExporterConfig }} + # configmap checksum + "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/telemetry-collector-configmap.yaml") . | sha256sum }} + {{- end }} + # vault annotations + {{- 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 }} + + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: consul-telemetry-collector + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} + spec: + # This needs to explicitly be consul-telemetry-collector because we look this up from each service consul-dataplane + # to forward metrics to it. + serviceAccountName: consul-telemetry-collector + initContainers: + # We're manually managing this init container instead of using the mesh injector so that we don't run into + # any race conditions on the mesh-injector deployment or upgrade + - name: consul-mesh-init + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if .Values.global.acls.manageSystemACLs }} + - name: CONSUL_LOGIN_AUTH_METHOD + value: {{ template "consul.fullname" . }}-k8s-auth-method + - name: CONSUL_LOGIN_META + value: "component=consul-telemetry-collector,pod=$(NAMESPACE)/$(POD_NAME)" + {{- end }} + {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} + {{- if .Values.global.enableConsulNamespaces }} + - name: CONSUL_NAMESPACE + value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + - name: CONSUL_LOGIN_NAMESPACE + value: "default" + {{- else }} + - name: CONSUL_LOGIN_NAMESPACE + value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- end }} + command: + - /bin/sh + - -ec + - |- + consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ + -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} \ + -log-json={{ .Values.global.logJSON }} + + image: {{ .Values.global.imageK8S }} + imagePullPolicy: IfNotPresent + {{- if .Values.telemetryCollector.initContainer.resources }} + resources: + {{- toYaml .Values.telemetryCollector.initContainer.resources | nindent 12 }} + {{- else }} + resources: + limits: + cpu: 50m + memory: 150Mi + requests: + cpu: 50m + memory: 25Mi + {{- end }} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /consul/mesh-inject + name: consul-mesh-inject-data + {{- 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 }} + containers: + - name: consul-telemetry-collector + image: {{ .Values.telemetryCollector.image }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + ports: + - containerPort: 9090 + name: metrics + protocol: TCP + - containerPort: 9356 + name: metricsserver + protocol: TCP + env: + # These are mounted as secrets so that the telemetry-collector can use them when cloud is enabled. + # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, + # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. + # - HCP_RESOURCE_ID is created for use in the global cloud section but we will share it here + {{- if .Values.telemetryCollector.cloud.clientId.secretName }} + - name: HCP_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ .Values.telemetryCollector.cloud.clientId.secretName }} + key: {{ .Values.telemetryCollector.cloud.clientId.secretKey }} + {{- end }} + {{- if .Values.telemetryCollector.cloud.clientSecret.secretName }} + - name: HCP_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.telemetryCollector.cloud.clientSecret.secretName }} + key: {{ .Values.telemetryCollector.cloud.clientSecret.secretKey }} + {{- end}} + {{- if .Values.global.cloud.resourceId.secretName }} + - name: HCP_RESOURCE_ID + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.resourceId.secretName }} + key: {{ .Values.global.cloud.resourceId.secretKey }} + {{- end }} + {{- if .Values.global.cloud.authUrl.secretName }} + - name: HCP_AUTH_URL + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.authUrl.secretName }} + key: {{ .Values.global.cloud.authUrl.secretKey }} + {{- end}} + {{- if .Values.global.cloud.apiHost.secretName }} + - name: HCP_API_HOST + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.apiHost.secretName }} + key: {{ .Values.global.cloud.apiHost.secretKey }} + {{- end}} + {{- if .Values.global.cloud.scadaAddress.secretName }} + - name: HCP_SCADA_ADDRESS + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.scadaAddress.secretName }} + key: {{ .Values.global.cloud.scadaAddress.secretKey }} + {{- end}} + {{- if .Values.global.trustedCAs }} + - name: SSL_CERT_DIR + value: "/etc/ssl/certs:/trusted-cas" + {{- end }} + {{- include "consul.extraEnvironmentVars" .Values.telemetryCollector | nindent 12 }} + command: + - "/bin/sh" + - "-ec" + - | + {{- if .Values.global.trustedCAs }} + {{- range $i, $cert := .Values.global.trustedCAs }} + cat < /trusted-cas/custom-ca-{{$i}}.pem + {{- $cert | nindent 10 }} + EOF + {{- end }} + {{- end }} + + consul-telemetry-collector agent \ + {{- if .Values.telemetryCollector.customExporterConfig }} + -config-file-path /consul/config/config.json \ + {{ end }} + volumeMounts: + {{- if .Values.telemetryCollector.customExporterConfig }} + - name: config + mountPath: /consul/config + {{- end }} + {{- if .Values.global.trustedCAs }} + - name: trusted-cas + mountPath: /trusted-cas + readOnly: false + {{- end }} + resources: + {{- if .Values.telemetryCollector.resources }} + {{- toYaml .Values.telemetryCollector.resources | nindent 12 }} + {{- end }} + # consul-dataplane container + - name: consul-dataplane + image: "{{ .Values.global.imageConsulDataplane }}" + imagePullPolicy: IfNotPresent + command: + - consul-dataplane + args: + # addresses + {{- if .Values.externalServers.enabled }} + - -addresses={{ .Values.externalServers.hosts | first }} + {{- else }} + - -addresses={{ template "consul.fullname" . }}-server.{{ .Release.Namespace }}.svc + {{- end }} + # grpc + {{- if .Values.externalServers.enabled }} + - -grpc-port={{ .Values.externalServers.grpcPort }} + {{- else }} + - -grpc-port=8502 + {{- end }} + # tls + {{- if .Values.global.tls.enabled }} + {{- if (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) }} + {{- if .Values.global.secretsBackend.vault.enabled }} + - -ca-certs=/vault/secrets/serverca.crt + {{- else }} + - -ca-certs=/consul/tls/ca/tls.crt + {{- end }} + {{- end }} + {{- if and .Values.externalServers.enabled .Values.externalServers.tlsServerName }} + - -tls-server-name={{.Values.externalServers.tlsServerName }} + {{- else if .Values.global.cloud.enabled }} + - -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} + {{- end }} + {{- else }} + - -tls-disabled + {{- end }} + # credentials + {{- if .Values.global.acls.manageSystemACLs }} + - -credential-type=login + - -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token + - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method + {{- if .Values.global.enableConsulNamespaces }} + {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + - -login-namespace="default" + {{- else }} + - -login-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- end }} + {{- if .Values.global.adminPartitions.enabled }} + - foo + - -login-partition={{ .Values.global.adminPartitions.name }} + {{- end }} + {{- end }} + {{- if .Values.global.enableConsulNamespaces }} + - -service-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- if .Values.global.adminPartitions.enabled }} + - -service-partition={{ .Values.global.adminPartitions.name }} + {{- end }} + {{- if .Values.global.metrics.enabled }} + - -telemetry-prom-scrape-path=/metrics + {{- end }} + - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} + - -log-json={{ .Values.global.logJSON }} + - -envoy-concurrency=2 + {{- if and .Values.externalServers.enabled .Values.externalServers.skipServerWatch }} + - -server-watch-disabled=true + {{- end }} + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: DP_PROXY_ID + value: $(POD_NAME) + - name: DP_CREDENTIAL_LOGIN_META1 + value: pod=$(NAMESPACE)/$(POD_NAME) + - name: DP_CREDENTIAL_LOGIN_META2 + value: component=consul-telemetry-collector + - name: TMPDIR + value: /consul/mesh-inject + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 1 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 20000 + timeoutSeconds: 1 + securityContext: + readOnlyRootFilesystem: true + runAsGroup: 5995 + runAsNonRoot: true + runAsUser: 5995 + # dataplane volume mounts + volumeMounts: + - mountPath: /consul/mesh-inject + name: consul-mesh-inject-data + {{- 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 }} + + {{- if .Values.telemetryCollector.nodeSelector }} + nodeSelector: + {{ tpl .Values.telemetryCollector.nodeSelector . | indent 8 | trim }} + {{- end }} + {{- if .Values.telemetryCollector.priorityClassName }} + priorityClassName: {{ .Values.telemetryCollector.priorityClassName }} + {{- end }} + volumes: + - emptyDir: + medium: Memory + name: consul-mesh-inject-data + {{- if .Values.global.trustedCAs }} + - name: trusted-cas + emptyDir: + medium: "Memory" + {{- end }} + {{- 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 }} + - name: config + configMap: + name: {{ template "consul.fullname" . }}-telemetry-collector +{{- end }} diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index ad50341061..432200541b 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -1198,4 +1198,18 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ local actual=$(echo "$cmd" | yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# global.experiments=["resource-apis"] + +@test "telemetryCollector/Deployment: disabled when V2 is enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + . } \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats new file mode 100755 index 0000000000..3f77169fd6 --- /dev/null +++ b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats @@ -0,0 +1,1349 @@ +#!/usr/bin/env bats + +load _helpers + +@test "telemetryCollector/Deployment(V2): disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + . +} + +@test "telemetryCollector/Deployment(V2): fails if no image is set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=null' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "telemetryCollector.image must be set to enable consul-telemetry-collector" ]] +} + +@test "telemetryCollector/Deployment(V2): disable with telemetry-collector.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=false' \ + . +} + +@test "telemetryCollector/Deployment(V2): disable with global.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'global.enabled=false' \ + . +} + +@test "telemetryCollector/Deployment(V2): container image overrides" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].image' | tee /dev/stderr) + [ "${actual}" = "\"bar\"" ] +} + +#-------------------------------------------------------------------- +# nodeSelector + +@test "telemetryCollector/Deployment(V2): nodeSelector is not set by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + . | tee /dev/stderr | + yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "telemetryCollector/Deployment(V2): specified nodeSelector" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.nodeSelector=testing' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "testing" ] +} + +#-------------------------------------------------------------------- +# consul.name + +@test "telemetryCollector/Deployment(V2): name is constant regardless of consul name" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'consul.name=foobar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].name' | tee /dev/stderr) + [ "${actual}" = "consul-telemetry-collector" ] +} + +#-------------------------------------------------------------------- +# global.tls.enabled + +@test "telemetryCollector/Deployment(V2): Adds tls-ca-cert volume when global.tls.enabled is true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" != "" ] +} + +@test "telemetryCollector/Deployment(V2): Adds tls-ca-cert volumeMounts when global.tls.enabled is true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" != "" ] +} + +@test "telemetryCollector/Deployment(V2): can overwrite CA secret with the provided one" { + cd `chart_dir` + local ca_cert_volume=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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" ] +} + +#-------------------------------------------------------------------- +# global.tls.enableAutoEncrypt + +@test "telemetryCollector/Deployment(V2): consul-ca-cert volumeMount is added when TLS with auto-encrypt is enabled without clients" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert") | length > 0' | tee + /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment(V2): consul-ca-cert volume is not added if externalServers.enabled=true and externalServers.useSystemRoots=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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 "telemetryCollector/Deployment(V2): resources has default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].resources' | tee /dev/stderr) + + [ $(echo "${actual}" | yq -r '.requests.memory') = "512Mi" ] + [ $(echo "${actual}" | yq -r '.requests.cpu') = "1000m" ] + [ $(echo "${actual}" | yq -r '.limits.memory') = "512Mi" ] + [ $(echo "${actual}" | yq -r '.limits.cpu') = "1000m" ] +} + +@test "telemetryCollector/Deployment(V2): resources can be overridden" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'telemetryCollector.resources.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +#-------------------------------------------------------------------- +# init container resources + +@test "telemetryCollector/Deployment(V2): init container has default resources" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) + + [ $(echo "${actual}" | yq -r '.requests.memory') = "25Mi" ] + [ $(echo "${actual}" | yq -r '.requests.cpu') = "50m" ] + [ $(echo "${actual}" | yq -r '.limits.memory') = "150Mi" ] + [ $(echo "${actual}" | yq -r '.limits.cpu') = "50m" ] +} + +@test "telemetryCollector/Deployment(V2): init container resources can be set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'telemetryCollector.initContainer.resources.requests.memory=memory' \ + --set 'telemetryCollector.initContainer.resources.requests.cpu=cpu' \ + --set 'telemetryCollector.initContainer.resources.limits.memory=memory2' \ + --set 'telemetryCollector.initContainer.resources.limits.cpu=cpu2' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.requests.memory' | tee /dev/stderr) + [ "${actual}" = "memory" ] + + local actual=$(echo $object | yq -r '.requests.cpu' | tee /dev/stderr) + [ "${actual}" = "cpu" ] + + local actual=$(echo $object | yq -r '.limits.memory' | tee /dev/stderr) + [ "${actual}" = "memory2" ] + + local actual=$(echo $object | yq -r '.limits.cpu' | tee /dev/stderr) + [ "${actual}" = "cpu2" ] +} + +#-------------------------------------------------------------------- +# priorityClassName + +@test "telemetryCollector/Deployment(V2): no priorityClassName by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) + + [ "${actual}" = "null" ] +} + +@test "telemetryCollector/Deployment(V2): can set a priorityClassName" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'telemetryCollector.priorityClassName=name' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) + + [ "${actual}" = "name" ] +} + +#-------------------------------------------------------------------- +# replicas + +@test "telemetryCollector/Deployment(V2): replicas defaults to 1" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + . | tee /dev/stderr | + yq '.spec.replicas' | tee /dev/stderr) + + [ "${actual}" = "1" ] +} + +@test "telemetryCollector/Deployment(V2): replicas can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'telemetryCollector.replicas=3' \ + . | tee /dev/stderr | + yq '.spec.replicas' | tee /dev/stderr) + + [ "${actual}" = "3" ] +} + +#-------------------------------------------------------------------- +# Vault + +@test "telemetryCollector/Deployment(V2): vault CA is not configured by default" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + . | 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 "telemetryCollector/Deployment(V2): vault CA is not configured when secretName is set but secretKey is not" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + --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 "telemetryCollector/Deployment(V2): vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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 "telemetryCollector/Deployment(V2): 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 templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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 "telemetryCollector/Deployment(V2): 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 templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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 "telemetryCollector/Deployment(V2): vault CA is not configured when secretKey is set but secretName is not" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + --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 "telemetryCollector/Deployment(V2): vault CA is configured when both secretName and secretKey are set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + --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" ] +} + +@test "telemetryCollector/Deployment(V2): vault tls annotations are set when tls is enabled" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --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.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'server.serverCert.secretName=pki_int/issue/test' \ + --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"pki_int/cert/ca\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr)" + [ "${actual}" = "pki_int/cert/ca" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr)" + [ "${actual}" = "true" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr)" + [ "${actual}" = "true" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" + [ "${actual}" = "test" ] +} + +@test "telemetryCollector/Deployment(V2): vault agent annotations can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=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=test' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +#-------------------------------------------------------------------- +# telemetryCollector.cloud + +@test "telemetryCollector/Deployment(V2): success with all cloud bits set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientSecret.secretName=client-secret-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-key' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ + . +} + +@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId is set and global.cloud.resourceId is not set or global.cloud.clientSecret.secretName is not set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientSecret.secretName=client-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-id-key' \ + --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.enabled is true and global.cloud.clientSecret.secretName is not set but global.cloud.clientId.secretName and global.cloud.resourceId.secretName is set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.enabled is true and global.cloud.resourceId.secretName is not set but global.cloud.clientId.secretName and global.cloud.clientSecret.secretName is set" { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.resourceId.secretName is set but global.cloud.resourceId.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "When either global.cloud.resourceId.secretName or global.cloud.resourceId.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.authURL.secretName is set but global.cloud.authURL.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.authUrl.secretName=auth-url-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.authURL.secretKey is set but global.cloud.authURL.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.authUrl.secretKey=auth-url-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.apiHost.secretName is set but global.cloud.apiHost.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.apiHost.secretName=auth-url-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.apiHost.secretKey is set but global.cloud.apiHost.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.apiHost.secretKey=auth-url-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.scadaAddress.secretName is set but global.cloud.scadaAddress.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.scadaAddress.secretName=scada-address-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when global.cloud.scadaAddress.secretKey is set but global.cloud.scadaAddress.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.datacenter=dc-foo' \ + --set 'global.domain=bar' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + --set 'global.cloud.scadaAddress.secretKey=scada-address-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretName is set but telemetryCollector.cloud.clientId.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ + --set 'telemetryCollector.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.resourceId.secretName=resource-id-name' \ + --set 'global.resourceId.secretKey=resource-id-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretKey is set but telemetryCollector.cloud.clientId.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ + --set 'global.resourceId.secretName=resource-id-name' \ + --set 'global.resourceId.secretKey=resource-id-key' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetryCollector.cloud.clientId.secretName is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ + --set 'telemetryCollector.clientSecret.secretKey=client-secret-key-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretName is set but telemetry.cloud.clientId.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetry.cloud.clientSecret.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] +} + +@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId and telemetryCollector.cloud.clientSecret is set but global.cloud.resourceId.secretKey is not set." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set" ]] +} + +#-------------------------------------------------------------------- +# global.tls.enabled + +@test "telemetryCollector/Deployment(V2): sets -tls-disabled args when when not using TLS." { + cd `chart_dir` + + local flags=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=false' \ + . | yq -r .spec.template.spec.containers[1].args) + + local actual=$(echo $flags | yq -r '. | any(contains("-tls-disabled"))') + [ "${actual}" = 'true' ] + +} + +@test "telemetryCollector/Deployment(V2): -ca-certs set correctly when using TLS." { + cd `chart_dir` + local flags=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] +} + +#-------------------------------------------------------------------- +# External Server + +@test "telemetryCollector/Deployment(V2): sets external server args when global.tls.enabled and externalServers.enabled" { + cd `chart_dir` + local flags=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.tls.enabled=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.httpsPort=8501' \ + --set 'externalServers.tlsServerName=foo.tls.server' \ + --set 'externalServers.useSystemRoots=true' \ + --set 'server.enabled=false' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) + [ "${actual}" = 'false' ] + + local actual=$(echo $flags | yq -r '. | any(contains("-tls-server-name=foo.tls.server"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] + + local actual=$(echo $flags | jq -r '. | any(contains("-addresses=external-consul.host"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] +} + +#-------------------------------------------------------------------- +# Admin Partitions + +@test "telemetryCollector/Deployment(V2): partition flags are set when using admin partitions" { + cd `chart_dir` + local flags=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.adminPartitions.name=hashi' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo $flags | jq -r '. | any(contains("-login-partition=hashi"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] + + local actual=$(echo $flags | jq -r '. | any(contains("-service-partition=hashi"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment(V2): consul-ca-cert volume mount is not set when using externalServers and useSystemRoots" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + +@test "telemetryCollector/Deployment(V2): config volume mount is set when config exists" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.customExporterConfig="foo"' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "config") | .name' | tee /dev/stderr) + [ "${actual}" = "config" ] +} + +@test "telemetryCollector/Deployment(V2): config flag is set when config exists" { + cd `chart_dir` + local flags=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.customExporterConfig="foo"' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command') + + local actual=$(echo $flags | yq -r '. | any(contains("-config-file-path /consul/config/config.json"))') + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment(V2): consul-ca-cert volume mount is not set on acl-init when using externalServers and useSystemRoots" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'server.enabled=false' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} +#-------------------------------------------------------------------- +# trustedCAs + +@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set command is modified correctly" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment(V2): trustedCAs: if multiple Trusted cas were set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + --set 'global.trustedCAs[1]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0]' | tee /dev/stderr) + + + local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-1.pem")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set /trusted-cas volumeMount is added" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) + local actual=$(echo $object | jq -r '.volumes[] | select(.name == "trusted-cas") | .name' | tee /dev/stderr) + [ "${actual}" = "trusted-cas" ] +} + + +@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set SSL_CERT_DIR env var is set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- +MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ + . | tee /dev/stderr | yq -r '.spec.template.spec.containers[0].env[] | select(.name == "SSL_CERT_DIR")' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.name' | tee /dev/stderr) + [ "${actual}" = "SSL_CERT_DIR" ] + local actual=$(echo $object | jq -r '.value' | tee /dev/stderr) + [ "${actual}" = "/etc/ssl/certs:/trusted-cas" ] +} + +#-------------------------------------------------------------------- +# extraLabels + +@test "telemetryCollector/Deployment(V2): no extra labels defined by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."consul.hashicorp.com/connect-inject-managed-by")' \ + | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "telemetryCollector/Deployment(V2): extra global labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --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 "telemetryCollector/Deployment(V2): multiple global extra labels can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --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" ] +} + +#-------------------------------------------------------------------- +# extraEnvironmentVariables + +@test "telemetryCollector/Deployment(V2): extra environment variables" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS=insecure' \ + --set 'telemetryCollector.extraEnvironmentVars.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_AUTH_TLS")) | .[0].value' | tee /dev/stderr) + [ "${actual}" = "insecure" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "foo")) | .[0].value' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +#-------------------------------------------------------------------- +# logLevel + +@test "telemetryCollector/Deployment(V2): use global.logLevel by default" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment(V2): override global.logLevel when telemetryCollector.logLevel is set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.logLevel=warn' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment(V2): use global.logLevel by default for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "telemetryCollector/Deployment(V2): override global.logLevel when telemetryCollector.logLevel is set for dataplane container" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.logLevel=debug' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# global.experiments=["resource-apis"] + +@test "telemetryCollector/Deployment(V2): disabled when V2 is disabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + . +} \ No newline at end of file diff --git a/control-plane/commands.go b/control-plane/commands.go index e2bcb0f693..01f5163bc3 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -6,6 +6,8 @@ package main import ( "os" + "github.com/mitchellh/cli" + cmdACLInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/acl-init" cmdConnectInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/connect-init" cmdConsulLogout "github.com/hashicorp/consul-k8s/control-plane/subcommand/consul-logout" @@ -18,6 +20,7 @@ import ( cmdGossipEncryptionAutogenerate "github.com/hashicorp/consul-k8s/control-plane/subcommand/gossip-encryption-autogenerate" cmdInjectConnect "github.com/hashicorp/consul-k8s/control-plane/subcommand/inject-connect" cmdInstallCNI "github.com/hashicorp/consul-k8s/control-plane/subcommand/install-cni" + cmdMeshInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/mesh-init" cmdPartitionInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/partition-init" cmdServerACLInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/server-acl-init" cmdSyncCatalog "github.com/hashicorp/consul-k8s/control-plane/subcommand/sync-catalog" @@ -25,7 +28,6 @@ import ( cmdVersion "github.com/hashicorp/consul-k8s/control-plane/subcommand/version" webhookCertManager "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager" "github.com/hashicorp/consul-k8s/control-plane/version" - "github.com/mitchellh/cli" ) // Commands is the mapping of all available consul-k8s commands. @@ -43,6 +45,10 @@ func init() { return &cmdConnectInit.Command{UI: ui}, nil }, + "mesh-init": func() (cli.Command, error) { + return &cmdMeshInit.Command{UI: ui}, nil + }, + "inject-connect": func() (cli.Command, error) { return &cmdInjectConnect.Command{UI: ui}, nil }, diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index cd563f2436..bc28930eae 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -235,6 +235,12 @@ const ( // port is the local port in the pod that the listener will bind to. It can // be a named port. AnnotationMeshDestinations = "consul.hashicorp.com/mesh-service-destinations" + + // AnnotationMeshInjectMountVolumes is the key of the annotation that controls whether + // the data volume that mesh inject uses to store data including the Consul ACL token + // is mounted to other containers in the pod. It is a comma-separated list of container names + // to mount the volume on. It will be mounted at the path `/consul/mesh-inject`. + AnnotationMeshInjectMountVolumes = "consul.hashicorp.com/mesh-inject-mount-volume" ) // Annotations used by Prometheus. diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index ad32510861..dca7dad950 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -5,6 +5,7 @@ package endpointsv2 import ( "context" "net" + "sort" "strings" mapset "github.com/deckarep/golang-set" @@ -16,13 +17,14 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/namespaces" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-multierror" ) const ( @@ -304,6 +306,10 @@ func getWorkloadSelector(podPrefixes, podExactNames map[string]any) *pbcatalog.W for v := range podExactNames { workloads.Names = append(workloads.Names, v) } + // sort for stability + sort.Strings(workloads.Prefixes) + sort.Strings(workloads.Names) + return workloads } diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 706a9d60a0..2458444008 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -6,9 +6,10 @@ package endpointsv2 import ( "context" "fmt" - "github.com/google/go-cmp/cmp/cmpopts" "testing" + "github.com/google/go-cmp/cmp/cmpopts" + mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" @@ -26,14 +27,15 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/go-uuid" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) var ( diff --git a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go index aaa94a191d..12cff4289d 100644 --- a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go @@ -722,7 +722,7 @@ func TestHandlerConsulDataplaneSidecar_UserVolumeMounts(t *testing.T) { }, expectedContainerVolumeMounts: []corev1.VolumeMount{ { - Name: "consul-connect-inject-data", + Name: "consul-mesh-inject-data", MountPath: "/consul/mesh-inject", }, { diff --git a/control-plane/connect-inject/webhook_v2/container_init.go b/control-plane/connect-inject/webhook_v2/container_init.go index ebf4b0e336..ce9145be55 100644 --- a/control-plane/connect-inject/webhook_v2/container_init.go +++ b/control-plane/connect-inject/webhook_v2/container_init.go @@ -29,13 +29,14 @@ type initContainerCommandData struct { ServiceAccountName string AuthMethod string - // Log settings for the connect-init command. + // Log settings for the mesh-init command. LogLevel string LogJSON bool } -// containerInit 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. +// containerInit returns the init container spec for mesh-init that polls for the workload's bootstrap config +// so that it optionally set up iptables for transparent proxy. Otherwise, it ensures the workload exists before +// the pod starts. func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) (corev1.Container, error) { // Check if tproxy is enabled on this pod. tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) @@ -53,7 +54,7 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) volMounts := []corev1.VolumeMount{ { Name: volumeName, - MountPath: "/consul/connect-inject", + MountPath: "/consul/mesh-inject", }, } @@ -98,14 +99,6 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, }, }, - { - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, - }, { Name: "CONSUL_ADDRESSES", Value: w.ConsulAddress, @@ -122,10 +115,6 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) Name: "CONSUL_API_TIMEOUT", Value: w.ConsulConfig.APITimeout.String(), }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, }, Resources: w.InitContainerResources, VolumeMounts: volMounts, @@ -222,7 +211,7 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) Value: redirectTrafficConfig, }) - // Running consul connect redirect-traffic with iptables + // Running consul mesh-init redirect-traffic with iptables // requires both being a root user and having NET_ADMIN capability. container.SecurityContext = &corev1.SecurityContext{ RunAsUser: pointer.Int64(rootUserAndGroupID), @@ -290,11 +279,7 @@ func splitCommaSeparatedItemsFromAnnotation(annotation string, pod corev1.Pod) [ // initContainerCommandTpl is the template for the command executed by // the init container. const initContainerCommandTpl = ` -consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ +consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level={{ .LogLevel }} \ -log-json={{ .LogJSON }} \ - {{- if .AuthMethod }} - -service-account-name="{{ .ServiceAccountName }}" \ - -service-name="{{ .ServiceName }}" \ - {{- end }} ` diff --git a/control-plane/connect-inject/webhook_v2/container_init_test.go b/control-plane/connect-inject/webhook_v2/container_init_test.go index 6931122124..87cb83306c 100644 --- a/control-plane/connect-inject/webhook_v2/container_init_test.go +++ b/control-plane/connect-inject/webhook_v2/container_init_test.go @@ -68,7 +68,7 @@ func TestHandlerContainerInit(t *testing.T) { ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, LogLevel: "info", }, - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level=info \ -log-json=false \`, []corev1.EnvVar{ @@ -88,10 +88,6 @@ func TestHandlerContainerInit(t *testing.T) { Name: "CONSUL_API_TIMEOUT", Value: "0s", }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, }, }, @@ -115,11 +111,9 @@ func TestHandlerContainerInit(t *testing.T) { LogLevel: "debug", LogJSON: true, }, - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level=debug \ - -log-json=true \ - -service-account-name="a-service-account-name" \ - -service-name="web" \`, + -log-json=true \`, []corev1.EnvVar{ { Name: "CONSUL_ADDRESSES", @@ -137,10 +131,6 @@ func TestHandlerContainerInit(t *testing.T) { Name: "CONSUL_API_TIMEOUT", Value: "5s", }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "CONSUL_LOGIN_AUTH_METHOD", Value: "an-auth-method", @@ -165,7 +155,7 @@ func TestHandlerContainerInit(t *testing.T) { require.NoError(t, err) actual := strings.Join(container.Command, " ") require.Contains(t, actual, tt.ExpCmd) - require.EqualValues(t, container.Env[3:], tt.ExpEnv) + require.EqualValues(t, container.Env[2:], tt.ExpEnv) }) } } @@ -386,7 +376,7 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { ConsulAddress: "10.0.0.0", ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, }, - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level=info \ -log-json=false \`, []corev1.EnvVar{ @@ -406,10 +396,6 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { Name: "CONSUL_API_TIMEOUT", Value: "5s", }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "CONSUL_NAMESPACE", Value: "default", @@ -429,7 +415,7 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { ConsulAddress: "10.0.0.0", ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, }, - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level=info \ -log-json=false \`, []corev1.EnvVar{ @@ -449,10 +435,6 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { Name: "CONSUL_API_TIMEOUT", Value: "5s", }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "CONSUL_NAMESPACE", Value: "default", @@ -476,7 +458,7 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { ConsulAddress: "10.0.0.0", ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, }, - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level=info \ -log-json=false \`, []corev1.EnvVar{ @@ -496,10 +478,6 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { Name: "CONSUL_API_TIMEOUT", Value: "5s", }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "CONSUL_NAMESPACE", Value: "non-default", @@ -519,7 +497,7 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { ConsulAddress: "10.0.0.0", ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, }, - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level=info \ -log-json=false \`, []corev1.EnvVar{ @@ -539,10 +517,6 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { Name: "CONSUL_API_TIMEOUT", Value: "5s", }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "CONSUL_NAMESPACE", Value: "non-default", @@ -567,11 +541,9 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { ConsulAddress: "10.0.0.0", ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, }, - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level=info \ - -log-json=false \ - -service-account-name="web" \ - -service-name="" \`, + -log-json=false \`, []corev1.EnvVar{ { Name: "CONSUL_ADDRESSES", @@ -589,10 +561,6 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { Name: "CONSUL_API_TIMEOUT", Value: "5s", }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "CONSUL_LOGIN_AUTH_METHOD", Value: "auth-method", @@ -638,11 +606,9 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { ConsulAddress: "10.0.0.0", ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, }, - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ -log-level=info \ - -log-json=false \ - -service-account-name="web" \ - -service-name="" \`, + -log-json=false \`, []corev1.EnvVar{ { Name: "CONSUL_ADDRESSES", @@ -660,10 +626,6 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { Name: "CONSUL_API_TIMEOUT", Value: "5s", }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "CONSUL_LOGIN_AUTH_METHOD", Value: "auth-method", @@ -705,7 +667,7 @@ func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { actual := strings.Join(container.Command, " ") require.Equal(t, tt.Cmd, actual) if tt.ExpEnv != nil { - require.Equal(t, tt.ExpEnv, container.Env[3:]) + require.Equal(t, tt.ExpEnv, container.Env[2:]) } }) } @@ -745,18 +707,18 @@ func TestHandlerContainerInit_WithTLSAndCustomPorts(t *testing.T) { } container, err := w.containerInit(testNS, *pod) require.NoError(t, err) - require.Equal(t, "CONSUL_ADDRESSES", container.Env[3].Name) - require.Equal(t, w.ConsulAddress, container.Env[3].Value) - require.Equal(t, "CONSUL_GRPC_PORT", container.Env[4].Name) - require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.GRPCPort), container.Env[4].Value) - require.Equal(t, "CONSUL_HTTP_PORT", container.Env[5].Name) - require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.HTTPPort), container.Env[5].Value) + require.Equal(t, "CONSUL_ADDRESSES", container.Env[2].Name) + require.Equal(t, w.ConsulAddress, container.Env[2].Value) + require.Equal(t, "CONSUL_GRPC_PORT", container.Env[3].Name) + require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.GRPCPort), container.Env[3].Value) + require.Equal(t, "CONSUL_HTTP_PORT", container.Env[4].Name) + require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.HTTPPort), container.Env[4].Value) if w.TLSEnabled { - require.Equal(t, "CONSUL_USE_TLS", container.Env[8].Name) - require.Equal(t, "true", container.Env[8].Value) + require.Equal(t, "CONSUL_USE_TLS", container.Env[6].Name) + require.Equal(t, "true", container.Env[6].Value) if caProvided { - require.Equal(t, "CONSUL_CACERT_PEM", container.Env[9].Name) - require.Equal(t, "consul-ca-cert", container.Env[9].Value) + require.Equal(t, "CONSUL_CACERT_PEM", container.Env[7].Name) + require.Equal(t, "consul-ca-cert", container.Env[7].Value) } else { for _, ev := range container.Env { if ev.Name == "CONSUL_CACERT_PEM" { diff --git a/control-plane/connect-inject/webhook_v2/container_volume.go b/control-plane/connect-inject/webhook_v2/container_volume.go index 8de3d5b6f5..5c7e65c905 100644 --- a/control-plane/connect-inject/webhook_v2/container_volume.go +++ b/control-plane/connect-inject/webhook_v2/container_volume.go @@ -9,7 +9,7 @@ import ( // volumeName is the name of the volume that is created to store the // Consul Connect injection data. -const volumeName = "consul-connect-inject-data" +const volumeName = "consul-mesh-inject-data" // containerVolume returns the volume data to add to the pod. This volume // is used for shared data between containers. diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook.go b/control-plane/connect-inject/webhook_v2/mesh_webhook.go index efe33985f2..d41601b6b7 100644 --- a/control-plane/connect-inject/webhook_v2/mesh_webhook.go +++ b/control-plane/connect-inject/webhook_v2/mesh_webhook.go @@ -453,13 +453,13 @@ func (w *MeshWebhook) overwriteProbes(ns corev1.Namespace, pod *corev1.Pod) erro } func (w *MeshWebhook) injectVolumeMount(pod corev1.Pod) { - containersToInject := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationInjectMountVolumes, pod) + containersToInject := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationMeshInjectMountVolumes, pod) for index, container := range pod.Spec.Containers { if slices.Contains(containersToInject, container.Name) { pod.Spec.Containers[index].VolumeMounts = append(pod.Spec.Containers[index].VolumeMounts, corev1.VolumeMount{ Name: volumeName, - MountPath: "/consul/connect-inject", + MountPath: "/consul/mesh-inject", }) } } diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go b/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go index b2b1f47392..6f3d1f339a 100644 --- a/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go @@ -365,7 +365,7 @@ func TestHandlerHandle(t *testing.T) { Object: encodeRaw(t, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - constants.AnnotationInjectMountVolumes: "", + constants.AnnotationMeshInjectMountVolumes: "", }, }, Spec: basicSpec, @@ -419,7 +419,7 @@ func TestHandlerHandle(t *testing.T) { Object: encodeRaw(t, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - constants.AnnotationInjectMountVolumes: "web,unknown,web_three_point_oh", + constants.AnnotationMeshInjectMountVolumes: "web,unknown,web_three_point_oh", }, }, Spec: corev1.PodSpec{ @@ -1101,7 +1101,7 @@ func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { value := actual[i].Value.([]any) valueMap := value[0].(map[string]any) envs := valueMap["env"].([]any) - redirectEnv := envs[8].(map[string]any) + redirectEnv := envs[6].(map[string]any) require.Equal(t, redirectEnv["name"].(string), "CONSUL_REDIRECT_TRAFFIC_CONFIG") iptablesJson := redirectEnv["value"].(string) diff --git a/control-plane/consul/dataplane_client.go b/control-plane/consul/dataplane_client.go new file mode 100644 index 0000000000..628d353252 --- /dev/null +++ b/control-plane/consul/dataplane_client.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package consul + +import ( + "fmt" + + "github.com/hashicorp/consul/proto-public/pbdataplane" +) + +// NewDataplaneServiceClient creates a pbdataplane.DataplaneServiceClient for gathering proxy bootstrap config. +// It is initialized with a consul-server-connection-manager Watcher to continuously find Consul +// server addresses. +func NewDataplaneServiceClient(watcher ServerConnectionManager) (pbdataplane.DataplaneServiceClient, error) { + + // We recycle the GRPC connection from the discovery client because it + // should have all the necessary dial options, including the resolver that + // continuously updates Consul server addresses. Otherwise, a lot of code from consul-server-connection-manager + // would need to be duplicated + state, err := watcher.State() + if err != nil { + return nil, fmt.Errorf("unable to get connection manager state: %w", err) + } + dpClient := pbdataplane.NewDataplaneServiceClient(state.GRPCConn) + + return dpClient, nil +} diff --git a/control-plane/consul/dataplane_client_test.go b/control-plane/consul/dataplane_client_test.go new file mode 100644 index 0000000000..4e76b80125 --- /dev/null +++ b/control-plane/consul/dataplane_client_test.go @@ -0,0 +1,206 @@ +package consul + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-server-connection-manager/discovery" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbdataplane" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/anypb" +) + +func Test_NewDataplaneServiceClient(t *testing.T) { + + var serverConfig *testutil.TestServerConfig + server, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + serverConfig = c + }) + require.NoError(t, err) + defer server.Stop() + + server.WaitForLeader(t) + server.WaitForActiveCARoot(t) + + t.Logf("server grpc address on %d", serverConfig.Ports.GRPC) + + // Create discovery configuration + discoverConfig := discovery.Config{ + Addresses: "127.0.0.1", + GRPCPort: serverConfig.Ports.GRPC, + } + + opts := hclog.LoggerOptions{Name: "dataplane-service-client"} + logger := hclog.New(&opts) + + watcher, err := discovery.NewWatcher(context.Background(), discoverConfig, logger) + require.NoError(t, err) + require.NotNil(t, watcher) + + defer watcher.Stop() + go watcher.Run() + + // Create a workload and create a proxyConfiguration + createWorkload(t, watcher, "foo") + pc := createProxyConfiguration(t, watcher, "foo") + + client, err := NewDataplaneServiceClient(watcher) + require.NoError(t, err) + require.NotNil(t, client) + require.NotNil(t, watcher) + + req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ + ProxyId: "foo", + Namespace: "default", + Partition: "default", + } + + res, err := client.GetEnvoyBootstrapParams(context.Background(), req) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, "foo", res.GetIdentity()) + require.Equal(t, "default", res.GetNamespace()) + require.Equal(t, "default", res.GetPartition()) + + if diff := cmp.Diff(pc.BootstrapConfig, res.GetBootstrapConfig(), protocmp.Transform()); diff != "" { + t.Errorf("unexpected difference:\n%v", diff) + } + + // NOTE: currently it isn't possible to test that the grpc connection responds to changes in the + // discovery server. The discovery response only includes the IP address of the host, so all servers + // for a local test are de-duplicated as a single entry. +} + +func createWorkload(t *testing.T, watcher ServerConnectionManager, name string) { + + client, err := NewResourceServiceClient(watcher) + require.NoError(t, err) + + workload := &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + }, + "mesh": { + Port: 20000, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: "k8s-node-0-virtual", + Identity: name, + } + + id := &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Workload", + }, + Tenancy: &pbresource.Tenancy{ + Partition: "default", + Namespace: "default", + PeerName: "local", + }, + } + + proto, err := anypb.New(workload) + require.NoError(t, err) + + req := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: id, + Data: proto, + }, + } + + _, err = client.Write(context.Background(), req) + require.NoError(t, err) + + resourceHasPersisted(t, client, id) +} + +func createProxyConfiguration(t *testing.T, watcher ServerConnectionManager, name string) *pbmesh.ProxyConfiguration { + + client, err := NewResourceServiceClient(watcher) + require.NoError(t, err) + + pc := &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{"foo"}, + }, + BootstrapConfig: &pbmesh.BootstrapConfig{ + StatsBindAddr: "127.0.0.2:1234", + ReadyBindAddr: "127.0.0.3:5678", + }, + } + + id := &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "mesh", + GroupVersion: "v1alpha1", + Kind: "ProxyConfiguration", + }, + Tenancy: &pbresource.Tenancy{ + Partition: "default", + Namespace: "default", + PeerName: "local", + }, + } + + proto, err := anypb.New(pc) + require.NoError(t, err) + + req := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: id, + Data: proto, + }, + } + + _, err = client.Write(context.Background(), req) + require.NoError(t, err) + + resourceHasPersisted(t, client, id) + return pc +} + +// resourceHasPersisted checks that a recently written resource exists in the Consul +// state store with a valid version. This must be true before a resource is overwritten +// or deleted. +// TODO: refactor so that there isn't an import cycle when using test.ResourceHasPersisted. +func resourceHasPersisted(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID) { + req := &pbresource.ReadRequest{Id: id} + + require.Eventually(t, func() bool { + res, err := client.Read(context.Background(), req) + if err != nil { + return false + } + + if res.GetResource().GetVersion() == "" { + return false + } + + return true + }, 5*time.Second, + time.Second) +} diff --git a/control-plane/subcommand/mesh-init/command.go b/control-plane/subcommand/mesh-init/command.go new file mode 100644 index 0000000000..91ea8129cf --- /dev/null +++ b/control-plane/subcommand/mesh-init/command.go @@ -0,0 +1,283 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package meshinit + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "net" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-server-connection-manager/discovery" + "github.com/hashicorp/consul/proto-public/pbdataplane" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + "github.com/hashicorp/consul/sdk/iptables" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul-k8s/control-plane/version" +) + +const ( + // The number of times to attempt to read this proxy registration (120s). + defaultMaxPollingRetries = 120 +) + +type Command struct { + UI cli.Ui + + flagProxyName string + + maxPollingAttempts uint64 // Number of times to poll Consul for proxy registrations. + + flagRedirectTrafficConfig string + flagLogLevel string + flagLogJSON bool + + flagSet *flag.FlagSet + consul *flags.ConsulFlags + + once sync.Once + help string + logger hclog.Logger + + watcher *discovery.Watcher + + // Only used in tests. + iptablesProvider iptables.Provider + iptablesConfig iptables.Config +} + +func (c *Command) init() { + c.flagSet = flag.NewFlagSet("", flag.ContinueOnError) + + // V2 Flags + c.flagSet.StringVar(&c.flagProxyName, "proxy-name", os.Getenv("PROXY_NAME"), "The Consul proxy name. This is the K8s Pod name, which is also the name of the Workload in Consul. (Required)") + + // Universal flags + c.flagSet.StringVar(&c.flagRedirectTrafficConfig, "redirect-traffic-config", os.Getenv("CONSUL_REDIRECT_TRAFFIC_CONFIG"), "Config (in JSON format) to configure iptables for this pod.") + c.flagSet.StringVar(&c.flagLogLevel, "log-level", "info", + "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ + "\"debug\", \"info\", \"warn\", and \"error\".") + c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, + "Enable or disable JSON output format for logging.") + + if c.maxPollingAttempts == 0 { + c.maxPollingAttempts = defaultMaxPollingRetries + } + + c.consul = &flags.ConsulFlags{} + flags.Merge(c.flagSet, c.consul.Flags()) + c.help = flags.Usage(help, c.flagSet) +} + +func (c *Command) Run(args []string) int { + c.once.Do(c.init) + + if err := c.flagSet.Parse(args); err != nil { + return 1 + } + // Validate flags + if err := c.validateFlags(); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if c.consul.Namespace == "" { + c.consul.Namespace = constants.DefaultConsulNS + } + if c.consul.Partition == "" { + c.consul.Partition = constants.DefaultConsulPartition + } + + // Set up logging. + if c.logger == nil { + var err error + c.logger, err = common.Logger(c.flagLogLevel, c.flagLogJSON) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + } + + // Create Consul API config object. + consulConfig := c.consul.ConsulClientConfig() + + // Create a context to be used by the processes started in this command. + ctx, cancelFunc := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancelFunc() + + // Start Consul server Connection manager. + serverConnMgrCfg, err := c.consul.ConsulServerConnMgrConfig() + // Disable server watch because we only need to get server IPs once. + serverConnMgrCfg.ServerWatchDisabled = true + if err != nil { + c.UI.Error(fmt.Sprintf("unable to create config for consul-server-connection-manager: %s", err)) + return 1 + } + if c.watcher == nil { + c.watcher, err = discovery.NewWatcher(ctx, serverConnMgrCfg, c.logger.Named("consul-server-connection-manager")) + if err != nil { + c.UI.Error(fmt.Sprintf("unable to create Consul server watcher: %s", err)) + return 1 + } + go c.watcher.Run() // The actual ACL login happens here + defer c.watcher.Stop() + } + + state, err := c.watcher.State() + if err != nil { + c.logger.Error("Unable to get state from consul-server-connection-manager", "error", err) + return 1 + } + + consulClient, err := consul.NewClientFromConnMgrState(consulConfig, state) + if err != nil { + c.logger.Error("Unable to get client connection", "error", err) + return 1 + } + + if version.IsFIPS() { + // make sure we are also using FIPS Consul + var versionInfo map[string]interface{} + _, err := consulClient.Raw().Query("/v1/agent/version", versionInfo, nil) + if err != nil { + c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. Unable to verify FIPS Consul while setting up Consul API client.") + } + if val, ok := versionInfo["FIPS"]; !ok || val == "" { + c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. A non-FIPS version of Consul was detected.") + } + } + + // todo (agentless): this should eventually be passed to consul-dataplane as a string so we don't need to write it to file. + if c.consul.UseTLS && c.consul.CACertPEM != "" { + if err = common.WriteFileWithPerms(constants.ConsulCAFile, c.consul.CACertPEM, 0444); err != nil { + c.logger.Error("error writing CA cert file", "error", err) + return 1 + } + } + + dc, err := consul.NewDataplaneServiceClient(c.watcher) + if err != nil { + c.logger.Error("failed to create resource client", "error", err) + return 1 + } + + var bootstrapConfig pbmesh.BootstrapConfig + if err := backoff.Retry(c.getBootstrapParams(dc, &bootstrapConfig), backoff.WithMaxRetries(backoff.NewConstantBackOff(1*time.Second), c.maxPollingAttempts)); err != nil { + c.logger.Error("Timed out waiting for bootstrap parameters", "error", err) + return 1 + } + + if c.flagRedirectTrafficConfig != "" { + err := c.applyTrafficRedirectionRules(&bootstrapConfig) // BootstrapConfig is always populated non-nil from the RPC + if err != nil { + c.logger.Error("error applying traffic redirection rules", "err", err) + return 1 + } + } + + c.logger.Info("Proxy initialization completed") + return 0 +} + +func (c *Command) validateFlags() error { + if c.flagProxyName == "" { + return errors.New("-proxy-name must be set") + } + return nil +} + +func (c *Command) Synopsis() string { return synopsis } +func (c *Command) Help() string { + c.once.Do(c.init) + return c.help +} + +func (c *Command) getBootstrapParams( + client pbdataplane.DataplaneServiceClient, + bootstrapConfig *pbmesh.BootstrapConfig) backoff.Operation { + + return func() error { + req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ + ProxyId: c.flagProxyName, + Namespace: c.consul.Namespace, + Partition: c.consul.Partition, + } + res, err := client.GetEnvoyBootstrapParams(context.Background(), req) + if err != nil { + c.logger.Error("Unable to get bootstrap parameters", "error", err) + return err + } + if res.GetBootstrapConfig() != nil { + *bootstrapConfig = *res.GetBootstrapConfig() + } + return nil + } +} + +// This below implementation is loosely based on +// https://github.com/hashicorp/consul/blob/fe2d41ddad9ba2b8ff86cbdebbd8f05855b1523c/command/connect/redirecttraffic/redirect_traffic.go#L136. + +func (c *Command) applyTrafficRedirectionRules(config *pbmesh.BootstrapConfig) error { + + err := json.Unmarshal([]byte(c.flagRedirectTrafficConfig), &c.iptablesConfig) + if err != nil { + return err + } + if c.iptablesProvider != nil { + c.iptablesConfig.IptablesProvider = c.iptablesProvider + } + + // TODO: provide dynamic updates to the c.iptablesConfig.ProxyOutboundPort + // We currently don't have a V2 endpoint that can gather the fully synthesized ProxyConfiguration. + // We need this to dynamically set c.iptablesConfig.ProxyOutboundPort with the outbound port configuration from + // pbmesh.DynamicConfiguration.TransparentProxy.OutboundListenerPort. + // We would either need to grab another resource that has this information rendered in it, or add + // pbmesh.DynamicConfiguration to the GetBootstrapParameters rpc. + // Right now this is an edge case because the mesh webhook configured the flagRedirectTrafficConfig with the default + // 15001 port. + + // TODO: provide dyanmic updates to the c.iptablesConfig.ProxyInboundPort + // This is the `mesh` port in the workload resource. + // Right now this will always be the default port (20000) + + if config.StatsBindAddr != "" { + _, port, err := net.SplitHostPort(config.StatsBindAddr) + if err != nil { + return fmt.Errorf("failed parsing host and port from StatsBindAddr: %s", err) + } + + c.iptablesConfig.ExcludeInboundPorts = append(c.iptablesConfig.ExcludeInboundPorts, port) + } + + // Configure any relevant information from the proxy service + err = iptables.Setup(c.iptablesConfig) + if err != nil { + return err + } + c.logger.Info("Successfully applied traffic redirection rules") + return nil +} + +const synopsis = "Inject mesh init command." +const help = ` +Usage: consul-k8s-control-plane mesh-init [options] + + Bootstraps mesh-injected pod components. + Uses V2 Consul Catalog APIs. + Not intended for stand-alone use. +` diff --git a/control-plane/subcommand/mesh-init/command_ent_test.go b/control-plane/subcommand/mesh-init/command_ent_test.go new file mode 100644 index 0000000000..ad3ea8c87d --- /dev/null +++ b/control-plane/subcommand/mesh-init/command_ent_test.go @@ -0,0 +1,118 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package meshinit + +import ( + "context" + "strconv" + "testing" + + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" +) + +func TestRun_WithNamespaces(t *testing.T) { + t.Parallel() + cases := []struct { + name string + consulNamespace string + consulPartition string + }{ + { + name: "default ns, default partition", + consulNamespace: constants.DefaultConsulNS, + consulPartition: constants.DefaultConsulPartition, + }, + { + name: "non-default ns, default partition", + consulNamespace: "bar", + consulPartition: constants.DefaultConsulPartition, + }, + { + name: "non-default ns, non-default partition", + consulNamespace: "bar", + consulPartition: "baz", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + var serverCfg *testutil.TestServerConfig + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + serverCfg = c + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + _, err = EnsurePartitionExists(testClient.APIClient, c.consulPartition) + require.NoError(t, err) + + partitionedCfg := testClient.Cfg.APIClientConfig + partitionedCfg.Partition = c.consulPartition + + partitionedClient, err := api.NewClient(partitionedCfg) + require.NoError(t, err) + + _, err = namespaces.EnsureExists(partitionedClient, c.consulNamespace, "") + require.NoError(t, err) + + // Register Consul workload. + loadResource(t, resourceClient, getWorkloadID(testPodName, c.consulNamespace, c.consulPartition), getWorkload(), nil) + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + maxPollingAttempts: 5, + } + // We build the consul-addr because normally it's defined by the init container setting + // CONSUL_HTTP_ADDR when it processes the command template. + flags := []string{"-proxy-name", testPodName, + "-addresses", "127.0.0.1", + "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), + "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), + "-namespace", c.consulNamespace, + "-partition", c.consulPartition, + } + + // Run the command. + code := cmd.Run(flags) + require.Equal(t, 0, code, ui.ErrorWriter.String()) + }) + } +} + +// EnsurePartitionExists ensures a Consul partition exists. +// Boolean return value indicates if the partition was created by this call. +// This is borrowed from namespaces.EnsureExists +func EnsurePartitionExists(client *api.Client, name string) (bool, error) { + if name == constants.DefaultConsulPartition { + return false, nil + } + // Check if the Consul namespace exists. + partitionInfo, _, err := client.Partitions().Read(context.Background(), name, nil) + if err != nil { + return false, err + } + if partitionInfo != nil { + return false, nil + } + + consulPartition := api.Partition{ + Name: name, + Description: "Auto-generated by consul-k8s", + } + + _, _, err = client.Partitions().Create(context.Background(), &consulPartition, nil) + return true, err +} diff --git a/control-plane/subcommand/mesh-init/command_test.go b/control-plane/subcommand/mesh-init/command_test.go new file mode 100644 index 0000000000..ec280d70ac --- /dev/null +++ b/control-plane/subcommand/mesh-init/command_test.go @@ -0,0 +1,425 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package meshinit + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "strings" + "sync" + "testing" + "time" + + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/iptables" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +func TestRun_FlagValidation(t *testing.T) { + t.Parallel() + cases := []struct { + flags []string + env string + expErr string + }{ + { + flags: []string{}, + expErr: "-proxy-name must be set", + }, + { + flags: []string{ + "-proxy-name", testPodName, + "-log-level", "invalid", + }, + expErr: "unknown log level: invalid", + }, + } + for _, c := range cases { + t.Run(c.expErr, func(t *testing.T) { + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + code := cmd.Run(c.flags) + require.Equal(t, 1, code) + require.Contains(t, ui.ErrorWriter.String(), c.expErr) + }) + } +} + +// TestRun_MeshServices tests that the command can log in to Consul (if ACLs are enabled) using a kubernetes +// auth method and, using the obtained token, make call to the dataplane GetBootstrapParams() RPC. +func TestRun_MeshServices(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + workload *pbcatalog.Workload + proxyConfiguration *pbmesh.ProxyConfiguration + aclsEnabled bool + expFail bool + }{ + { + name: "basic workload bootstrap", + workload: getWorkload(), + }, + { + name: "workload and proxyconfiguration bootstrap", + workload: getWorkload(), + proxyConfiguration: getProxyConfiguration(), + }, + { + name: "missing workload", + expFail: true, + }, + // TODO: acls enabled + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + //tokenFile := fmt.Sprintf("/tmp/%d1", rand.Int()) + //t.Cleanup(func() { + // _ = os.RemoveAll(tokenFile) + //}) + + // Create test consulServer server. + var serverCfg *testutil.TestServerConfig + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + serverCfg = c + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.workload, nil) + loadResource(t, resourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.proxyConfiguration, nil) + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + maxPollingAttempts: 3, + } + + // We build the consul-addr because normally it's defined by the init container setting + // CONSUL_HTTP_ADDR when it processes the command template. + flags := []string{"-proxy-name", testPodName, + "-addresses", "127.0.0.1", + "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), + "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), + } + //if tt.aclsEnabled { + // flags = append(flags, "-auth-method-name", test.AuthMethod, + // "-service-account-name", tt.serviceAccountName, + // "-acl-token-sink", tokenFile) //TODO: what happens if this is unspecified? We don't need this file + //} + + // Run the command. + code := cmd.Run(flags) + if tt.expFail { + require.Equal(t, 1, code) + return + } + require.Equal(t, 0, code, ui.ErrorWriter.String()) + + // TODO: Can we remove the tokenFile from this workflow? + // consul-dataplane performs it's own login using the Serviceaccount bearer token + //if tt.aclsEnabled { + // // Validate the ACL token was written. + // tokenData, err := os.ReadFile(tokenFile) + // require.NoError(t, err) + // require.NotEmpty(t, tokenData) + // + // // Check that the token has the metadata with pod name and pod namespace. + // consulClient, err = api.NewClient(&api.Config{Address: server.HTTPAddr, Token: string(tokenData)}) + // require.NoError(t, err) + // token, _, err := consulClient.ACL().TokenReadSelf(nil) + // require.NoError(t, err) + // require.Equal(t, "token created via login: {\"pod\":\"default-ns/counting-pod\"}", token.Description) + //} + }) + } +} + +// TestRun_RetryServicePolling runs the command but does not register the consul service +// for 2 seconds and then asserts the command exits successfully. +func TestRun_RetryServicePolling(t *testing.T) { + t.Parallel() + + // Start Consul server. + var serverCfg *testutil.TestServerConfig + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + serverCfg = c + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Start the consul service registration in a go func and delay it so that it runs + // after the cmd.Run() starts. + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + // Wait a moment, this ensures that we are already in the retry logic. + time.Sleep(time.Second * 2) + // Register counting service. + loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) + }() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + maxPollingAttempts: 10, + } + flags := []string{ + "-proxy-name", testPodName, + "-addresses", "127.0.0.1", + "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), + "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), + } + code := cmd.Run(flags) + wg.Wait() + require.Equal(t, 0, code) +} + +func TestRun_TrafficRedirection(t *testing.T) { + cases := map[string]struct { + registerProxyConfiguration bool + expIptablesParamsFunc func(actual iptables.Config) error + }{ + "no proxyConfiguration provided": { + expIptablesParamsFunc: func(actual iptables.Config) error { + if len(actual.ExcludeInboundPorts) != 0 { + return fmt.Errorf("ExcludeInboundPorts in iptables.Config was %v, but should be empty", actual.ExcludeInboundPorts) + } + if actual.ProxyInboundPort != 20000 { + return fmt.Errorf("ProxyInboundPort in iptables.Config was %d, but should be [20000]", actual.ProxyOutboundPort) + } + if actual.ProxyOutboundPort != 15001 { + return fmt.Errorf("ProxyOutboundPort in iptables.Config was %d, but should be [15001]", actual.ProxyOutboundPort) + } + return nil + }, + }, + "stats bind port is provided in proxyConfiguration": { + registerProxyConfiguration: true, + expIptablesParamsFunc: func(actual iptables.Config) error { + if len(actual.ExcludeInboundPorts) != 1 || actual.ExcludeInboundPorts[0] != "9090" { + return fmt.Errorf("ExcludeInboundPorts in iptables.Config was %v, but should be [9090, 1234]", actual.ExcludeInboundPorts) + } + if actual.ProxyInboundPort != 20000 { + return fmt.Errorf("ProxyInboundPort in iptables.Config was %d, but should be [20000]", actual.ProxyOutboundPort) + } + if actual.ProxyOutboundPort != 15001 { + return fmt.Errorf("ProxyOutboundPort in iptables.Config was %d, but should be [15001]", actual.ProxyOutboundPort) + } + return nil + }, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + + // Start Consul server. + var serverCfg *testutil.TestServerConfig + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + serverCfg = c + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Add additional proxy configuration either to a config entry or to the service itself. + if c.registerProxyConfiguration { + loadResource(t, resourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getProxyConfiguration(), nil) + } + + // Register Consul workload. + loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) + + iptablesProvider := &fakeIptablesProvider{} + iptablesCfg := iptables.Config{ + ProxyUserID: "5995", + ProxyInboundPort: 20000, + ProxyOutboundPort: 15001, + } + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + maxPollingAttempts: 3, + iptablesProvider: iptablesProvider, + } + iptablesCfgJSON, err := json.Marshal(iptablesCfg) + require.NoError(t, err) + flags := []string{ + "-proxy-name", testPodName, + "-addresses", "127.0.0.1", + "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), + "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), + "-redirect-traffic-config", string(iptablesCfgJSON), + } + code := cmd.Run(flags) + require.Equal(t, 0, code, ui.ErrorWriter.String()) + require.Truef(t, iptablesProvider.applyCalled, "redirect traffic rules were not applied") + if c.expIptablesParamsFunc != nil { + errMsg := c.expIptablesParamsFunc(cmd.iptablesConfig) + require.NoError(t, errMsg) + } + }) + } +} + +const ( + testPodName = "foo" +) + +type fakeIptablesProvider struct { + applyCalled bool + rules []string +} + +func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { + if id == nil || !proto.ProtoReflect().IsValid() { + return + } + + data, err := anypb.New(proto) + require.NoError(t, err) + + resource := &pbresource.Resource{ + Id: id, + Data: data, + Owner: owner, + } + + req := &pbresource.WriteRequest{Resource: resource} + _, err = client.Write(context.Background(), req) + require.NoError(t, err) + test.ResourceHasPersisted(t, client, id) +} + +func getWorkloadID(name, namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Workload", + }, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +// getWorkload creates a proxyConfiguration that matches the pod from createPod, +// assuming that metrics, telemetry, and overwrite probes are enabled separately. +func getWorkload() *pbcatalog.Workload { + return &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "public": { + Port: 80, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "admin": { + Port: 8080, + Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + }, + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: "k8s-node-0", + Identity: testPodName, + } +} + +func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "mesh", + GroupVersion: "v1alpha1", + Kind: "ProxyConfiguration", + }, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +// getProxyConfiguration creates a proxyConfiguration that matches the pod from createWorkload. +func getProxyConfiguration() *pbmesh.ProxyConfiguration { + return &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{testPodName}, + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 20400, + LocalPathPort: 2001, + Path: "/livez", + }, + { + ListenerPort: 20300, + LocalPathPort: 2000, + Path: "/readyz", + }, + { + ListenerPort: 20500, + LocalPathPort: 2002, + Path: "/startupz", + }, + }, + }, + }, + BootstrapConfig: &pbmesh.BootstrapConfig{ + StatsBindAddr: "0.0.0.0:9090", + PrometheusBindAddr: "0.0.0.0:21234", // This gets added to the iptables exclude directly in the webhook + }, + } +} + +func (f *fakeIptablesProvider) AddRule(_ string, args ...string) { + f.rules = append(f.rules, strings.Join(args, " ")) +} + +func (f *fakeIptablesProvider) ApplyRules() error { + f.applyCalled = true + return nil +} + +func (f *fakeIptablesProvider) Rules() []string { + return f.rules +} diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go index 0684f8b1bb..0aa8cdc724 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -31,8 +31,6 @@ type Command struct { flagLogJSON bool flagTimeout time.Duration - flagResourceAPIs bool // Use V2 APIs - // ctx is cancelled when the command timeout is reached. ctx context.Context retryDuration time.Duration @@ -54,8 +52,6 @@ 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.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, - "Enable of disable V2 Resource APIs.") c.consul = &flags.ConsulFlags{} flags.Merge(c.flags, c.consul.Flags()) @@ -177,11 +173,6 @@ func (c *Command) validateFlags() error { return errors.New("-api-timeout must be set to a value greater than 0") } - // TODO(dans) this needs to be replaced when the partition workflow is available. - if c.flagResourceAPIs { - return errors.New("partition-init is not implemented when the -enable-resource-apis flag is set for V2 Resource APIs") - } - return nil } From d2fe5edf4c195cc4f5022f9de571326fa602dcc3 Mon Sep 17 00:00:00 2001 From: Ronald Date: Thu, 14 Sep 2023 10:45:31 -0400 Subject: [PATCH 380/592] [NET-5346] Expose JWKCluster fields in jwt-provider config entry (#2881) --- .changelog/2881.txt | 3 + charts/consul/templates/crd-jwtproviders.yaml | 60 +++++ .../api/v1alpha1/jwtprovider_types.go | 203 +++++++++++++- .../api/v1alpha1/jwtprovider_types_test.go | 253 +++++++++++++++++- .../api/v1alpha1/zz_generated.deepcopy.go | 90 +++++++ .../consul.hashicorp.com_jwtproviders.yaml | 60 +++++ .../configentry_controller_test.go | 62 +++++ 7 files changed, 724 insertions(+), 7 deletions(-) create mode 100644 .changelog/2881.txt diff --git a/.changelog/2881.txt b/.changelog/2881.txt new file mode 100644 index 0000000000..5d76975cd3 --- /dev/null +++ b/.changelog/2881.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: Add `JWKSCluster` field to `JWTProvider` CRD. +``` \ No newline at end of file diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index 18b6e9bdcb..9f97922eb5 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -123,6 +123,66 @@ spec: the proxy listener will wait for the JWKS to be fetched before being activated. \n Default value is false." type: boolean + jwksCluster: + description: "JWKSCluster defines how the specified Remote JWKS + URI is to be fetched." + properties: + connectTimeout: + description: "The timeout for new network connections to hosts + in the cluster. \n If not set, a default value of 5s will be + used." + format: int64 + type: integer + discoveryType: + description: "DiscoveryType refers to the service discovery type + to use for resolving the cluster. \n Defaults to STRICT_DNS." + type: string + tlsCertificates: + description: "TLSCertificates refers to the data containing + certificate authority certificates to use in verifying a presented + peer certificate." + properties: + caCertificateProviderInstance: + description: "CaCertificateProviderInstance Certificate provider + instance for fetching TLS certificates." + properties: + instanceName: + description: "InstanceName refers to the certificate provider + instance name. \n The default value is 'default'." + type: string + certificateName: + description: "CertificateName is used to specify certificate + instances or types. For example, \"ROOTCA\" to specify a + root-certificate (validation context) or \"example.com\" + to specify a certificate for a particular domain. \n + The default value is the empty string." + type: string + type: object + trustedCA: + description: "TrustedCA defines TLS certificate data containing + certificate authority certificates to use in verifying a presented + peer certificate. \n Exactly one of Filename, EnvironmentVariable, + InlineString or InlineBytes must be specified." + properties: + filename: + description: "The name of the file on the local system to use a + data source for trusted CA certificates." + type: string + environmentVariable: + description: "The environment variable on the local system to use + a data source for trusted CA certificates." + type: string + inlineString: + description: "A string to inline in the configuration for use as + a data source for trusted CA certificates." + type: string + inlineBytes: + description: "A sequence of bytes to inline in the configuration + for use as a data source for trusted CA certificates." + type: string + type: object + type: object + type: object requestTimeoutMs: description: RequestTimeoutMs is the number of milliseconds to time out when making a request for the JWKS. diff --git a/control-plane/api/v1alpha1/jwtprovider_types.go b/control-plane/api/v1alpha1/jwtprovider_types.go index fee0ef9a78..8e80ece1dc 100644 --- a/control-plane/api/v1alpha1/jwtprovider_types.go +++ b/control-plane/api/v1alpha1/jwtprovider_types.go @@ -22,7 +22,12 @@ import ( ) const ( - JWTProviderKubeKind string = "jwtprovider" + JWTProviderKubeKind string = "jwtprovider" + DiscoveryTypeStrictDNS ClusterDiscoveryType = "STRICT_DNS" + DiscoveryTypeStatic ClusterDiscoveryType = "STATIC" + DiscoveryTypeLogicalDNS ClusterDiscoveryType = "LOGICAL_DNS" + DiscoveryTypeEDS ClusterDiscoveryType = "EDS" + DiscoveryTypeOriginalDST ClusterDiscoveryType = "ORIGINAL_DST" ) func init() { @@ -404,6 +409,9 @@ type RemoteJWKS struct { // // There is no retry by default. RetryPolicy *JWKSRetryPolicy `json:"retryPolicy,omitempty"` + + // JWKSCluster defines how the specified Remote JWKS URI is to be fetched. + JWKSCluster *JWKSCluster `json:"jwksCluster,omitempty"` } func (r *RemoteJWKS) toConsul() *capi.RemoteJWKS { @@ -416,6 +424,7 @@ func (r *RemoteJWKS) toConsul() *capi.RemoteJWKS { CacheDuration: r.CacheDuration, FetchAsynchronously: r.FetchAsynchronously, RetryPolicy: r.RetryPolicy.toConsul(), + JWKSCluster: r.JWKSCluster.toConsul(), } } @@ -432,9 +441,188 @@ func (r *RemoteJWKS) validate(path *field.Path) field.ErrorList { } errs = append(errs, r.RetryPolicy.validate(path.Child("retryPolicy"))...) + errs = append(errs, r.JWKSCluster.validate(path.Child("jwksCluster"))...) + return errs +} + +// JWKSCluster defines how the specified Remote JWKS URI is to be fetched. +type JWKSCluster struct { + // DiscoveryType refers to the service discovery type to use for resolving the cluster. + // + // This defaults to STRICT_DNS. + // Other options include STATIC, LOGICAL_DNS, EDS or ORIGINAL_DST. + DiscoveryType ClusterDiscoveryType `json:"discoveryType,omitempty"` + + // TLSCertificates refers to the data containing certificate authority certificates to use + // in verifying a presented peer certificate. + // If not specified and a peer certificate is presented it will not be verified. + // + // Must be either CaCertificateProviderInstance or TrustedCA. + TLSCertificates *JWKSTLSCertificate `json:"tlsCertificates,omitempty"` + + // The timeout for new network connections to hosts in the cluster. + // If not set, a default value of 5s will be used. + ConnectTimeout time.Duration `json:"connectTimeout,omitempty"` +} + +func (c *JWKSCluster) toConsul() *capi.JWKSCluster { + if c == nil { + return nil + } + return &capi.JWKSCluster{ + DiscoveryType: c.DiscoveryType.toConsul(), + TLSCertificates: c.TLSCertificates.toConsul(), + ConnectTimeout: c.ConnectTimeout, + } +} + +func (c *JWKSCluster) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if c == nil { + return errs + } + + errs = append(errs, c.DiscoveryType.validate(path.Child("discoveryType"))...) + errs = append(errs, c.TLSCertificates.validate(path.Child("tlsCertificates"))...) + + return errs +} + +type ClusterDiscoveryType string + +func (d ClusterDiscoveryType) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + + switch d { + case DiscoveryTypeStatic, DiscoveryTypeStrictDNS, DiscoveryTypeLogicalDNS, DiscoveryTypeEDS, DiscoveryTypeOriginalDST: + return errs + default: + errs = append(errs, field.Invalid(path, string(d), "unsupported jwks cluster discovery type.")) + } return errs } +func (d ClusterDiscoveryType) toConsul() capi.ClusterDiscoveryType { + return capi.ClusterDiscoveryType(string(d)) +} + +// JWKSTLSCertificate refers to the data containing certificate authority certificates to use +// in verifying a presented peer certificate. +// If not specified and a peer certificate is presented it will not be verified. +// +// Must be either CaCertificateProviderInstance or TrustedCA. +type JWKSTLSCertificate struct { + // CaCertificateProviderInstance Certificate provider instance for fetching TLS certificates. + CaCertificateProviderInstance *JWKSTLSCertProviderInstance `json:"caCertificateProviderInstance,omitempty"` + + // TrustedCA defines TLS certificate data containing certificate authority certificates + // to use in verifying a presented peer certificate. + // + // Exactly one of Filename, EnvironmentVariable, InlineString or InlineBytes must be specified. + TrustedCA *JWKSTLSCertTrustedCA `json:"trustedCA,omitempty"` +} + +func (c *JWKSTLSCertificate) toConsul() *capi.JWKSTLSCertificate { + if c == nil { + return nil + } + + return &capi.JWKSTLSCertificate{ + TrustedCA: c.TrustedCA.toConsul(), + CaCertificateProviderInstance: c.CaCertificateProviderInstance.toConsul(), + } +} + +func (c *JWKSTLSCertificate) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if c == nil { + return errs + } + + hasProviderInstance := c.CaCertificateProviderInstance != nil + hasTrustedCA := c.TrustedCA != nil + + if countTrue(hasTrustedCA, hasProviderInstance) != 1 { + asJSON, _ := json.Marshal(c) + errs = append(errs, field.Invalid(path, string(asJSON), "exactly one of 'trustedCa' or 'caCertificateProviderInstance' is required")) + } + + errs = append(errs, c.TrustedCA.validate(path.Child("trustedCa"))...) + + return errs +} + +// JWKSTLSCertProviderInstance Certificate provider instance for fetching TLS certificates. +type JWKSTLSCertProviderInstance struct { + // InstanceName refers to the certificate provider instance name. + // + // The default value is "default". + InstanceName string `json:"instanceName,omitempty"` + + // CertificateName is used to specify certificate instances or types. For example, "ROOTCA" to specify + // a root-certificate (validation context) or "example.com" to specify a certificate for a + // particular domain. + // + // The default value is the empty string. + CertificateName string `json:"certificateName,omitempty"` +} + +func (c *JWKSTLSCertProviderInstance) toConsul() *capi.JWKSTLSCertProviderInstance { + if c == nil { + return nil + } + + return &capi.JWKSTLSCertProviderInstance{ + InstanceName: c.InstanceName, + CertificateName: c.CertificateName, + } +} + +// JWKSTLSCertTrustedCA defines TLS certificate data containing certificate authority certificates +// to use in verifying a presented peer certificate. +// +// Exactly one of Filename, EnvironmentVariable, InlineString or InlineBytes must be specified. +type JWKSTLSCertTrustedCA struct { + Filename string `json:"filename,omitempty"` + EnvironmentVariable string `json:"environmentVariable,omitempty"` + InlineString string `json:"inlineString,omitempty"` + InlineBytes []byte `json:"inlineBytes,omitempty"` +} + +func (c *JWKSTLSCertTrustedCA) toConsul() *capi.JWKSTLSCertTrustedCA { + if c == nil { + return nil + } + + return &capi.JWKSTLSCertTrustedCA{ + Filename: c.Filename, + EnvironmentVariable: c.EnvironmentVariable, + InlineBytes: c.InlineBytes, + InlineString: c.InlineString, + } +} + +func (c *JWKSTLSCertTrustedCA) validate(path *field.Path) field.ErrorList { + var errs field.ErrorList + if c == nil { + return errs + } + + hasFilename := c.Filename != "" + hasEnv := c.EnvironmentVariable != "" + hasInlineBytes := len(c.InlineBytes) > 0 + hasInlineString := c.InlineString != "" + + if countTrue(hasFilename, hasEnv, hasInlineString, hasInlineBytes) != 1 { + asJSON, _ := json.Marshal(c) + errs = append(errs, field.Invalid(path, string(asJSON), "exactly one of 'filename', 'environmentVariable', 'inlineString' or 'inlineBytes' is required")) + } + return errs +} + +// JWKSRetryPolicy defines a retry policy for fetching JWKS. +// +// There is no retry by default. type JWKSRetryPolicy struct { // NumRetries is the number of times to retry fetching the JWKS. // The retry strategy uses jittered exponential backoff with @@ -443,9 +631,9 @@ type JWKSRetryPolicy struct { // Default value is 0. NumRetries int `json:"numRetries,omitempty"` - // Backoff policy + // Retry's backoff policy. // - // Defaults to Envoy's backoff policy + // Defaults to Envoy's backoff policy. RetryPolicyBackOff *RetryPolicyBackOff `json:"retryPolicyBackOff,omitempty"` } @@ -468,16 +656,19 @@ func (j *JWKSRetryPolicy) validate(path *field.Path) field.ErrorList { return append(errs, j.RetryPolicyBackOff.validate(path.Child("retryPolicyBackOff"))...) } +// RetryPolicyBackOff defines retry's policy backoff. +// +// Defaults to Envoy's backoff policy. type RetryPolicyBackOff struct { - // BaseInterval to be used for the next back off computation + // BaseInterval to be used for the next back off computation. // - // The default value from envoy is 1s + // The default value from envoy is 1s. BaseInterval time.Duration `json:"baseInterval,omitempty"` // MaxInternal to be used to specify the maximum interval between retries. // Optional but should be greater or equal to BaseInterval. // - // Defaults to 10 times BaseInterval + // Defaults to 10 times BaseInterval. MaxInterval time.Duration `json:"maxInterval,omitempty"` } diff --git a/control-plane/api/v1alpha1/jwtprovider_types_test.go b/control-plane/api/v1alpha1/jwtprovider_types_test.go index 15a3e7a5d6..8f6219e8d6 100644 --- a/control-plane/api/v1alpha1/jwtprovider_types_test.go +++ b/control-plane/api/v1alpha1/jwtprovider_types_test.go @@ -64,6 +64,22 @@ func TestJWTProvider_MatchesConsul(t *testing.T) { MaxInterval: 456, }, }, + JWKSCluster: &JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &JWKSTLSCertificate{ + CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{ + InstanceName: "InstanceName", + CertificateName: "ROOTCA", + }, + TrustedCA: &JWKSTLSCertTrustedCA{ + Filename: "cert.crt", + EnvironmentVariable: "env-variable", + InlineString: "inline-string", + InlineBytes: []byte("inline-bytes"), + }, + }, + ConnectTimeout: 890, + }, }, }, Issuer: "test-issuer", @@ -118,6 +134,22 @@ func TestJWTProvider_MatchesConsul(t *testing.T) { MaxInterval: 456, }, }, + JWKSCluster: &capi.JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &capi.JWKSTLSCertificate{ + CaCertificateProviderInstance: &capi.JWKSTLSCertProviderInstance{ + InstanceName: "InstanceName", + CertificateName: "ROOTCA", + }, + TrustedCA: &capi.JWKSTLSCertTrustedCA{ + Filename: "cert.crt", + EnvironmentVariable: "env-variable", + InlineString: "inline-string", + InlineBytes: []byte("inline-bytes"), + }, + }, + ConnectTimeout: 890, + }, }, }, Issuer: "test-issuer", @@ -215,6 +247,22 @@ func TestJWTProvider_ToConsul(t *testing.T) { MaxInterval: 456, }, }, + JWKSCluster: &JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &JWKSTLSCertificate{ + CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{ + InstanceName: "InstanceName", + CertificateName: "ROOTCA", + }, + TrustedCA: &JWKSTLSCertTrustedCA{ + Filename: "cert.crt", + EnvironmentVariable: "env-variable", + InlineString: "inline-string", + InlineBytes: []byte("inline-bytes"), + }, + }, + ConnectTimeout: 890, + }, }, }, Issuer: "test-issuer", @@ -268,6 +316,22 @@ func TestJWTProvider_ToConsul(t *testing.T) { MaxInterval: 456, }, }, + JWKSCluster: &capi.JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &capi.JWKSTLSCertificate{ + CaCertificateProviderInstance: &capi.JWKSTLSCertProviderInstance{ + InstanceName: "InstanceName", + CertificateName: "ROOTCA", + }, + TrustedCA: &capi.JWKSTLSCertTrustedCA{ + Filename: "cert.crt", + EnvironmentVariable: "env-variable", + InlineString: "inline-string", + InlineBytes: []byte("inline-bytes"), + }, + }, + ConnectTimeout: 890, + }, }, }, Issuer: "test-issuer", @@ -366,7 +430,7 @@ func TestJWTProvider_Validate(t *testing.T) { expectedErrMsgs: nil, }, - "valid - remote jwks with all fields": { + "valid - remote jwks with all fields with trustedCa": { input: &JWTProvider{ ObjectMeta: metav1.ObjectMeta{ Name: "test-jwt-provider", @@ -385,6 +449,80 @@ func TestJWTProvider_Validate(t *testing.T) { MaxInterval: 20 * time.Second, }, }, + JWKSCluster: &JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &JWKSTLSCertificate{ + TrustedCA: &JWKSTLSCertTrustedCA{ + Filename: "cert.crt", + }, + }, + ConnectTimeout: 890, + }, + }, + }, + Issuer: "test-issuer", + Audiences: []string{"aud1", "aud2"}, + Locations: []*JWTLocation{ + { + Header: &JWTLocationHeader{ + Name: "Authorization", + ValuePrefix: "Bearer", + Forward: true, + }, + }, + { + QueryParam: &JWTLocationQueryParam{ + Name: "access-token", + }, + }, + { + Cookie: &JWTLocationCookie{ + Name: "session-id", + }, + }, + }, + Forwarding: &JWTForwardingConfig{ + HeaderName: "jwt-forward-header", + PadForwardPayloadHeader: true, + }, + ClockSkewSeconds: 20, + CacheConfig: &JWTCacheConfig{ + Size: 30, + }, + }, + }, + expectedErrMsgs: nil, + }, + + "valid - remote jwks with all fields with CaCertificateProviderInstance": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwt-provider", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + RequestTimeoutMs: 5000, + CacheDuration: 10 * time.Second, + FetchAsynchronously: true, + RetryPolicy: &JWKSRetryPolicy{ + NumRetries: 3, + RetryPolicyBackOff: &RetryPolicyBackOff{ + BaseInterval: 5 * time.Second, + MaxInterval: 20 * time.Second, + }, + }, + JWKSCluster: &JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &JWKSTLSCertificate{ + CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{ + InstanceName: "InstanceName", + CertificateName: "ROOTCA", + }, + }, + ConnectTimeout: 890, + }, }, }, Issuer: "test-issuer", @@ -522,6 +660,119 @@ func TestJWTProvider_Validate(t *testing.T) { }, }, + "invalid - remote jwks invalid jwkcluster - all TLSCertificates fields set": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-invalid-uri", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + JWKSCluster: &JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &JWKSTLSCertificate{ + CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{ + InstanceName: "InstanceName", + }, + TrustedCA: &JWKSTLSCertTrustedCA{ + Filename: "cert.crt", + }, + }, + ConnectTimeout: 890, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.jwksCluster.tlsCertificates: Invalid value:`, + `exactly one of 'trustedCa' or 'caCertificateProviderInstance' is required`, + }, + }, + + "invalid - remote jwks invalid jwkcluster - invalid discovery type": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-invalid-uri", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + JWKSCluster: &JWKSCluster{ + DiscoveryType: "FAKE_DNS", + ConnectTimeout: 890, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.jwksCluster.discoveryType: Invalid value: "FAKE_DNS": unsupported jwks cluster discovery type.`, + }, + }, + + "invalid - remote jwks invalid jwkcluster - all trustedCa fields set": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-invalid-uri", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + JWKSCluster: &JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &JWKSTLSCertificate{ + TrustedCA: &JWKSTLSCertTrustedCA{ + Filename: "cert.crt", + EnvironmentVariable: "env-variable", + InlineString: "inline-string", + InlineBytes: []byte("inline-bytes"), + }, + }, + ConnectTimeout: 890, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.jwksCluster.tlsCertificates.trustedCa: Invalid value:`, + `exactly one of 'filename', 'environmentVariable', 'inlineString' or 'inlineBytes' is required`, + }, + }, + + "invalid - remote jwks invalid jwkcluster - set 2 trustedCa fields": { + input: &JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwks-invalid-uri", + }, + Spec: JWTProviderSpec{ + JSONWebKeySet: &JSONWebKeySet{ + Remote: &RemoteJWKS{ + URI: "https://jwks.example.com", + JWKSCluster: &JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &JWKSTLSCertificate{ + TrustedCA: &JWKSTLSCertTrustedCA{ + Filename: "cert.crt", + EnvironmentVariable: "env-variable", + }, + }, + ConnectTimeout: 890, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.jwksCluster.tlsCertificates.trustedCa: Invalid value:`, + `exactly one of 'filename', 'environmentVariable', 'inlineString' or 'inlineBytes' is required`, + }, + }, + "invalid - JWT location with all fields": { input: &JWTProvider{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 5b54f4a5c5..23269fd8f0 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -848,6 +848,11 @@ func (in *IngressServiceConfig) DeepCopyInto(out *IngressServiceConfig) { *out = new(uint32) **out = **in } + if in.PassiveHealthCheck != nil { + in, out := &in.PassiveHealthCheck, &out.PassiveHealthCheck + *out = new(PassiveHealthCheck) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressServiceConfig. @@ -1116,6 +1121,26 @@ func (in *JSONWebKeySet) DeepCopy() *JSONWebKeySet { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWKSCluster) DeepCopyInto(out *JWKSCluster) { + *out = *in + if in.TLSCertificates != nil { + in, out := &in.TLSCertificates, &out.TLSCertificates + *out = new(JWKSTLSCertificate) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSCluster. +func (in *JWKSCluster) DeepCopy() *JWKSCluster { + if in == nil { + return nil + } + out := new(JWKSCluster) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWKSRetryPolicy) DeepCopyInto(out *JWKSRetryPolicy) { *out = *in @@ -1136,6 +1161,66 @@ func (in *JWKSRetryPolicy) DeepCopy() *JWKSRetryPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWKSTLSCertProviderInstance) DeepCopyInto(out *JWKSTLSCertProviderInstance) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSTLSCertProviderInstance. +func (in *JWKSTLSCertProviderInstance) DeepCopy() *JWKSTLSCertProviderInstance { + if in == nil { + return nil + } + out := new(JWKSTLSCertProviderInstance) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWKSTLSCertTrustedCA) DeepCopyInto(out *JWKSTLSCertTrustedCA) { + *out = *in + if in.InlineBytes != nil { + in, out := &in.InlineBytes, &out.InlineBytes + *out = make([]byte, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSTLSCertTrustedCA. +func (in *JWKSTLSCertTrustedCA) DeepCopy() *JWKSTLSCertTrustedCA { + if in == nil { + return nil + } + out := new(JWKSTLSCertTrustedCA) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWKSTLSCertificate) DeepCopyInto(out *JWKSTLSCertificate) { + *out = *in + if in.CaCertificateProviderInstance != nil { + in, out := &in.CaCertificateProviderInstance, &out.CaCertificateProviderInstance + *out = new(JWKSTLSCertProviderInstance) + **out = **in + } + if in.TrustedCA != nil { + in, out := &in.TrustedCA, &out.TrustedCA + *out = new(JWKSTLSCertTrustedCA) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSTLSCertificate. +func (in *JWKSTLSCertificate) DeepCopy() *JWKSTLSCertificate { + if in == nil { + return nil + } + out := new(JWKSTLSCertificate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTCacheConfig) DeepCopyInto(out *JWTCacheConfig) { *out = *in @@ -2154,6 +2239,11 @@ func (in *RemoteJWKS) DeepCopyInto(out *RemoteJWKS) { *out = new(JWKSRetryPolicy) (*in).DeepCopyInto(*out) } + if in.JWKSCluster != nil { + in, out := &in.JWKSCluster, &out.JWKSCluster + *out = new(JWKSCluster) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteJWKS. diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml index 2e8ac24330..8584404c56 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -116,6 +116,66 @@ spec: the proxy listener will wait for the JWKS to be fetched before being activated. \n Default value is false." type: boolean + jwksCluster: + description: "JWKSCluster defines how the specified Remote JWKS + URI is to be fetched." + properties: + connectTimeout: + description: "The timeout for new network connections to hosts + in the cluster. \n If not set, a default value of 5s will be + used." + format: int64 + type: integer + discoveryType: + description: "DiscoveryType refers to the service discovery type + to use for resolving the cluster. \n Defaults to STRICT_DNS." + type: string + tlsCertificates: + description: "TLSCertificates refers to the data containing + certificate authority certificates to use in verifying a presented + peer certificate." + properties: + caCertificateProviderInstance: + description: "CaCertificateProviderInstance Certificate provider + instance for fetching TLS certificates." + properties: + instanceName: + description: "InstanceName refers to the certificate provider + instance name. \n The default value is 'default'." + type: string + certificateName: + description: "CertificateName is used to specify certificate + instances or types. For example, \"ROOTCA\" to specify a + root-certificate (validation context) or \"example.com\" + to specify a certificate for a particular domain. \n + The default value is the empty string." + type: string + type: object + trustedCA: + description: "TrustedCA defines TLS certificate data containing + certificate authority certificates to use in verifying a presented + peer certificate. \n Exactly one of Filename, EnvironmentVariable, + InlineString or InlineBytes must be specified." + properties: + filename: + description: "The name of the file on the local system to use a + data source for trusted CA certificates." + type: string + environmentVariable: + description: "The environment variable on the local system to use + a data source for trusted CA certificates." + type: string + inlineString: + description: "A string to inline in the configuration for use as + a data source for trusted CA certificates." + type: string + inlineBytes: + description: "A sequence of bytes to inline in the configuration + for use as a data source for trusted CA certificates." + type: string + type: object + type: object + type: object requestTimeoutMs: description: RequestTimeoutMs is the number of milliseconds to time out when making a request for the JWKS. diff --git a/control-plane/controllers/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go index 071d67ca6f..07a3ea3730 100644 --- a/control-plane/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -476,6 +476,68 @@ func TestConfigEntryControllers_createsConfigEntry(t *testing.T) { require.Equal(t, "test-issuer", jwt.Issuer) }, }, + { + kubeKind: "JWTProvider", + consulKind: capi.JWTProvider, + configEntryResource: &v1alpha1.JWTProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwt-provider", + Namespace: kubeNS, + }, + Spec: v1alpha1.JWTProviderSpec{ + JSONWebKeySet: &v1alpha1.JSONWebKeySet{ + Remote: &v1alpha1.RemoteJWKS{ + URI: "https://jwks.example.com", + JWKSCluster: &v1alpha1.JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &v1alpha1.JWKSTLSCertificate{ + CaCertificateProviderInstance: &v1alpha1.JWKSTLSCertProviderInstance{ + InstanceName: "InstanceName", + CertificateName: "ROOTCA", + }, + }, + }, + }, + }, + Issuer: "test-issuer", + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &JWTProviderController{ + Client: client, + Log: logger, + ConfigEntryController: &ConfigEntryController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + DatacenterName: datacenterName, + }, + } + }, + compare: func(t *testing.T, consulEntry capi.ConfigEntry) { + jwt, ok := consulEntry.(*capi.JWTProviderConfigEntry) + require.True(t, ok, "cast error") + require.Equal(t, capi.JWTProvider, jwt.Kind) + require.Equal(t, "test-jwt-provider", jwt.Name) + require.Equal(t, + &capi.JSONWebKeySet{ + Remote: &capi.RemoteJWKS{ + URI: "https://jwks.example.com", + JWKSCluster: &capi.JWKSCluster{ + DiscoveryType: "STRICT_DNS", + TLSCertificates: &capi.JWKSTLSCertificate{ + CaCertificateProviderInstance: &capi.JWKSTLSCertProviderInstance{ + InstanceName: "InstanceName", + CertificateName: "ROOTCA", + }, + }, + }, + }, + }, + jwt.JSONWebKeySet, + ) + require.Equal(t, "test-issuer", jwt.Issuer) + }, + }, } for _, c := range cases { From 7966629e58e43f0165ae6c814b9f40c627917dc5 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 14 Sep 2023 11:48:21 -0400 Subject: [PATCH 381/592] feat: add mesh-inject annotation to helm chart (#2957) --- .../api-gateway-controller-deployment.yaml | 1 + charts/consul/templates/client-daemonset.yaml | 1 + charts/consul/templates/cni-daemonset.yaml | 1 + .../templates/connect-inject-deployment.yaml | 1 + .../create-federation-secret-job.yaml | 1 + .../templates/enterprise-license-job.yaml | 1 + .../consul/templates/gateway-cleanup-job.yaml | 1 + .../templates/gateway-resources-job.yaml | 1 + .../gossip-encryption-autogenerate-job.yaml | 1 + .../ingress-gateways-deployment.yaml | 1 + .../templates/mesh-gateway-deployment.yaml | 1 + .../consul/templates/partition-init-job.yaml | 1 + charts/consul/templates/prometheus.yaml | 2 +- .../server-acl-init-cleanup-job.yaml | 1 + .../consul/templates/server-acl-init-job.yaml | 1 + .../consul/templates/server-statefulset.yaml | 1 + .../templates/sync-catalog-deployment.yaml | 1 + .../terminating-gateways-deployment.yaml | 1 + .../templates/tls-init-cleanup-job.yaml | 1 + charts/consul/templates/tls-init-job.yaml | 1 + .../webhook-cert-manager-deployment.yaml | 1 + charts/consul/test/unit/client-daemonset.bats | 15 ++++++++++++-- .../test/unit/connect-inject-deployment.bats | 12 +++++++++-- .../consul/test/unit/gateway-cleanup-job.bats | 6 +++++- .../test/unit/gateway-resources-job.bats | 6 +++++- .../unit/ingress-gateways-deployment.bats | 20 ++++++++++++++----- .../test/unit/mesh-gateway-deployment.bats | 17 +++++++++++++--- .../consul/test/unit/partition-init-job.bats | 10 +++++++++- .../unit/server-acl-init-cleanup-job.bats | 6 +++++- .../consul/test/unit/server-acl-init-job.bats | 7 ++++++- .../consul/test/unit/server-statefulset.bats | 14 +++++++++++-- .../test/unit/sync-catalog-deployment.bats | 12 +++++++++-- .../unit/terminating-gateways-deployment.bats | 16 ++++++++++----- .../test/unit/tls-init-cleanup-job.bats | 6 +++++- charts/consul/test/unit/tls-init-job.bats | 6 +++++- 35 files changed, 146 insertions(+), 29 deletions(-) diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 11396c8a03..1bd1f8500a 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -30,6 +30,7 @@ spec: metadata: annotations: consul.hashicorp.com/connect-inject: "false" + consul.hashicorp.com/mesh-inject: "false" {{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.tls.enabled) }} "vault.hashicorp.com/agent-init-first": "true" "vault.hashicorp.com/agent-inject": "true" diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 61425cfdb8..e7dd83ef26 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -86,6 +86,7 @@ spec: {{- end }} {{- end }} "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/client-config-configmap.yaml") . | sha256sum }} {{- if .Values.client.annotations }} {{- tpl .Values.client.annotations . | nindent 8 }} diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index ae04d9e657..258924f449 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -37,6 +37,7 @@ spec: {{- end }} annotations: consul.hashicorp.com/connect-inject: "false" + consul.hashicorp.com/mesh-inject: "false" spec: # consul-cni only runs on linux operating systems nodeSelector: diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index c6e47951e4..0b7649089d 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -50,6 +50,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.connectInject.annotations }} {{- tpl .Values.connectInject.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/create-federation-secret-job.yaml b/charts/consul/templates/create-federation-secret-job.yaml index 678a2af3ba..f0d5a1c821 100644 --- a/charts/consul/templates/create-federation-secret-job.yaml +++ b/charts/consul/templates/create-federation-secret-job.yaml @@ -37,6 +37,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-create-federation-secret diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index 0122690104..80ae582152 100644 --- a/charts/consul/templates/enterprise-license-job.yaml +++ b/charts/consul/templates/enterprise-license-job.yaml @@ -39,6 +39,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-enterprise-license diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index 20d2f8116e..df6c22fd30 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -31,6 +31,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gateway-cleanup diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index de510d9dc4..1136d2e0fe 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -31,6 +31,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gateway-resources diff --git a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml index 02fb3ea168..cc5b5397c2 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml @@ -35,6 +35,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gossip-encryption-autogenerate diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index df9f500e3c..4eef2e96ac 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -74,6 +74,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "ingress-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .name }}" {{- if $root.Values.global.enableConsulNamespaces }} diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index ac050e7199..afb5d44a0e 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -43,6 +43,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "mesh-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .Values.meshGateway.consulServiceName }}" "consul.hashicorp.com/mesh-gateway-container-port": "{{ .Values.meshGateway.containerPort }}" diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 6e21289f22..59a119e0c7 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -36,6 +36,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" {{- if (and .Values.global.secretsBackend.vault.enabled (or .Values.global.tls.enabled .Values.global.acls.manageSystemACLs)) }} "vault.hashicorp.com/agent-pre-populate-only": "true" "vault.hashicorp.com/agent-inject": "true" diff --git a/charts/consul/templates/prometheus.yaml b/charts/consul/templates/prometheus.yaml index 4dcede1745..a708708daf 100644 --- a/charts/consul/templates/prometheus.yaml +++ b/charts/consul/templates/prometheus.yaml @@ -410,8 +410,8 @@ spec: template: metadata: annotations: - consul.hashicorp.com/connect-inject: "false" + consul.hashicorp.com/mesh-inject: "false" labels: component: "server" app: prometheus diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index 39754d6c6f..b47e04188f 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -47,6 +47,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.acls.annotations }} {{- tpl .Values.global.acls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index 0b46697f31..de9b0dfc30 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -46,6 +46,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.acls.annotations }} {{- tpl .Values.global.acls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 44d9419c8f..c746846ebd 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -115,6 +115,7 @@ spec: {{- end }} {{- end }} "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/server-config-configmap.yaml") . | sha256sum }} {{- if .Values.server.annotations }} {{- tpl .Values.server.annotations . | nindent 8 }} diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index f4aeb1cdb8..f81b999e79 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -40,6 +40,7 @@ spec: {{- 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 }} diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index ea2131b8a2..3a1a23fad3 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -76,6 +76,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "terminating-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .name }}" {{- if $root.Values.global.enableConsulNamespaces }} diff --git a/charts/consul/templates/tls-init-cleanup-job.yaml b/charts/consul/templates/tls-init-cleanup-job.yaml index 2254a38ed2..9500410a53 100644 --- a/charts/consul/templates/tls-init-cleanup-job.yaml +++ b/charts/consul/templates/tls-init-cleanup-job.yaml @@ -35,6 +35,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.tls.annotations }} {{- tpl .Values.global.tls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index 47651fe14b..54727e03dd 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -35,6 +35,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.tls.annotations }} {{- tpl .Values.global.tls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index 7ba25b330c..29b85d7079 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -36,6 +36,7 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/webhook-cert-manager-configmap.yaml") . | sha256sum }} spec: containers: diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index ff9288a51d..c9889c2ffd 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -530,7 +530,11 @@ load _helpers -s templates/client-daemonset.yaml \ --set 'client.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -2724,7 +2728,14 @@ rollingUpdate: --set 'global.secretsBackend.vault.consulClientRole=test' \ --set 'global.secretsBackend.vault.consulServerRole=foo' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-init-first")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum") | + del(."vault.hashicorp.com/agent-inject") | + del(."vault.hashicorp.com/role") | + del(."vault.hashicorp.com/agent-init-first")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index ee2fac4b97..748b75de5d 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -1461,7 +1461,10 @@ load _helpers -s templates/connect-inject-deployment.yaml \ --set 'connectInject.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject")' | 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}" = "{}" ] } @@ -2217,7 +2220,12 @@ load _helpers --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(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | 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}" = "{}" ] } diff --git a/charts/consul/test/unit/gateway-cleanup-job.bats b/charts/consul/test/unit/gateway-cleanup-job.bats index 711974b9c2..26c3d08e97 100644 --- a/charts/consul/test/unit/gateway-cleanup-job.bats +++ b/charts/consul/test/unit/gateway-cleanup-job.bats @@ -30,6 +30,10 @@ target=templates/gateway-cleanup-job.yaml local actual=$(helm template \ -s $target \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats index 7d9334bae4..e38397231b 100644 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ b/charts/consul/test/unit/gateway-resources-job.bats @@ -149,6 +149,10 @@ target=templates/gateway-resources-job.yaml local actual=$(helm template \ -s $target \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/ingress-gateways-deployment.bats b/charts/consul/test/unit/ingress-gateways-deployment.bats index be617ac538..96f6d7ee3c 100644 --- a/charts/consul/test/unit/ingress-gateways-deployment.bats +++ b/charts/consul/test/unit/ingress-gateways-deployment.bats @@ -799,7 +799,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -s -r '.[0].spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "5" ] + [ "${actual}" = "6" ] } @test "ingressGateways/Deployment: extra annotations can be set through defaults" { @@ -814,7 +814,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "7" ] + [ "${actual}" = "8" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -836,7 +836,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "7" ] + [ "${actual}" = "8" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -859,7 +859,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "8" ] + [ "${actual}" = "9" ] local actual=$(echo $object | yq -r '.defaultkey' | tee /dev/stderr) [ "${actual}" = "defaultvalue" ] @@ -1146,7 +1146,17 @@ key2: value2' \ --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(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/gateway-wan-port") | del(."vconsul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/gateway-consul-service-name") | del(."consul.hashicorp.com/gateway-kind")' | 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") | + del(."consul.hashicorp.com/gateway-wan-address-source") | + del(."consul.hashicorp.com/gateway-wan-port") | + del(."vconsul.hashicorp.com/gateway-wan-address-source") | + del(."consul.hashicorp.com/gateway-consul-service-name") | + del(."consul.hashicorp.com/gateway-kind")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/mesh-gateway-deployment.bats b/charts/consul/test/unit/mesh-gateway-deployment.bats index 8e08463c43..afde4976a7 100755 --- a/charts/consul/test/unit/mesh-gateway-deployment.bats +++ b/charts/consul/test/unit/mesh-gateway-deployment.bats @@ -44,7 +44,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "7" ] + [ "${actual}" = "8" ] } @test "meshGateway/Deployment: extra annotations can be set" { @@ -57,7 +57,7 @@ load _helpers key2: value2' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "9" ] + [ "${actual}" = "10" ] } #-------------------------------------------------------------------- @@ -1415,7 +1415,18 @@ key2: value2' \ --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(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-kind") | del(."consul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/mesh-gateway-container-port") | del(."consul.hashicorp.com/gateway-wan-address-static") | del(."consul.hashicorp.com/gateway-wan-port") | del(."consul.hashicorp.com/gateway-consul-service-name")' | 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") | + del(."consul.hashicorp.com/gateway-kind") | + del(."consul.hashicorp.com/gateway-wan-address-source") | + del(."consul.hashicorp.com/mesh-gateway-container-port") | + del(."consul.hashicorp.com/gateway-wan-address-static") | + del(."consul.hashicorp.com/gateway-wan-port") | + del(."consul.hashicorp.com/gateway-consul-service-name")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index 7b2dfb940f..131d13f8a1 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -635,7 +635,15 @@ reservedNameTest() { --set 'global.secretsBackend.vault.consulCARole=carole' \ --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/agent-pre-populate-only") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-inject-secret-serverca.crt") | del(."vault.hashicorp.com/agent-inject-template-serverca.crt")' | 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/agent-pre-populate-only") | + del(."vault.hashicorp.com/role") | + del(."vault.hashicorp.com/agent-inject-secret-serverca.crt") | + del(."vault.hashicorp.com/agent-inject-template-serverca.crt")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/server-acl-init-cleanup-job.bats b/charts/consul/test/unit/server-acl-init-cleanup-job.bats index 8743ea4a8d..f6a67a1893 100644 --- a/charts/consul/test/unit/server-acl-init-cleanup-job.bats +++ b/charts/consul/test/unit/server-acl-init-cleanup-job.bats @@ -236,7 +236,11 @@ load _helpers -s templates/server-acl-init-cleanup-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index a9873a8e61..b92dd10218 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -1081,6 +1081,7 @@ load _helpers local expected=$(echo '{ "consul.hashicorp.com/connect-inject": "false", + "consul.hashicorp.com/mesh-inject": "false", "vault.hashicorp.com/agent-inject": "true", "vault.hashicorp.com/agent-pre-populate": "true", "vault.hashicorp.com/agent-pre-populate-only": "false", @@ -2356,7 +2357,11 @@ load _helpers -s templates/server-acl-init-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 6fbb2e8e04..f4ab239aa8 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -627,7 +627,11 @@ load _helpers local actual=$(helm template \ -s templates/server-statefulset.yaml \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -1979,7 +1983,13 @@ load _helpers --set 'global.secretsBackend.vault.consulClientRole=test' \ --set 'global.secretsBackend.vault.consulServerRole=foo' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum") | + del(."vault.hashicorp.com/agent-inject") | + del(."vault.hashicorp.com/role")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/sync-catalog-deployment.bats b/charts/consul/test/unit/sync-catalog-deployment.bats index d8321eefdf..0c9579df20 100755 --- a/charts/consul/test/unit/sync-catalog-deployment.bats +++ b/charts/consul/test/unit/sync-catalog-deployment.bats @@ -984,7 +984,10 @@ load _helpers -s templates/sync-catalog-deployment.yaml \ --set 'syncCatalog.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject")' | 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}" = "{}" ] } @@ -1233,7 +1236,12 @@ load _helpers --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(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | 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}" = "{}" ] } diff --git a/charts/consul/test/unit/terminating-gateways-deployment.bats b/charts/consul/test/unit/terminating-gateways-deployment.bats index 5e10c5b1fd..0c4ab7dcca 100644 --- a/charts/consul/test/unit/terminating-gateways-deployment.bats +++ b/charts/consul/test/unit/terminating-gateways-deployment.bats @@ -871,7 +871,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -s -r '.[0].spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "3" ] + [ "${actual}" = "4" ] } @test "terminatingGateways/Deployment: extra annotations can be set through defaults" { @@ -886,7 +886,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "5" ] + [ "${actual}" = "6" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -908,7 +908,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "5" ] + [ "${actual}" = "6" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -931,7 +931,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "6" ] + [ "${actual}" = "7" ] local actual=$(echo $object | yq -r '.defaultkey' | tee /dev/stderr) [ "${actual}" = "defaultvalue" ] @@ -1214,7 +1214,13 @@ key2: value2' \ --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(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-consul-service-name") | del(."consul.hashicorp.com/gateway-kind")' | 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") | + del(."consul.hashicorp.com/gateway-consul-service-name") | + del(."consul.hashicorp.com/gateway-kind")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/tls-init-cleanup-job.bats b/charts/consul/test/unit/tls-init-cleanup-job.bats index 735d991780..2e72033396 100644 --- a/charts/consul/test/unit/tls-init-cleanup-job.bats +++ b/charts/consul/test/unit/tls-init-cleanup-job.bats @@ -145,7 +145,11 @@ load _helpers -s templates/tls-init-cleanup-job.yaml \ --set 'global.tls.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/tls-init-job.bats b/charts/consul/test/unit/tls-init-job.bats index f71edc43d5..1c5b7ae7a1 100644 --- a/charts/consul/test/unit/tls-init-job.bats +++ b/charts/consul/test/unit/tls-init-job.bats @@ -262,7 +262,11 @@ load _helpers -s templates/tls-init-job.yaml \ --set 'global.tls.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."consul.hashicorp.com/config-checksum")' | + tee /dev/stderr) [ "${actual}" = "{}" ] } From 6e163e31ac9a9b44efc251ecc32d85ca3fbe38af Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 14 Sep 2023 18:28:04 -0400 Subject: [PATCH 382/592] feat: add namespace controller (#2956) * feat: add node controller * lint fix * Apply suggestions from code review Co-authored-by: Michael Zalimeni Co-authored-by: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> * PR feedback Part II --------- Co-authored-by: Michael Zalimeni Co-authored-by: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> --- .../controllers/pod/pod_controller.go | 5 +- .../namespace/namespace_controller.go | 131 +++++ .../namespace_controller_ent_test.go | 413 ++++++++++++++ .../connect-inject/webhook_v2/mesh_webhook.go | 23 - .../webhook_v2/mesh_webhook_ent_test.go | 539 ------------------ control-plane/namespaces/namespaces.go | 23 + control-plane/namespaces/namespaces_test.go | 50 ++ .../inject-connect/v2controllers.go | 22 +- 8 files changed, 639 insertions(+), 567 deletions(-) create mode 100644 control-plane/connect-inject/namespace/namespace_controller.go create mode 100644 control-plane/connect-inject/namespace/namespace_controller_ent_test.go diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index d21e3d663a..a0ada9a69f 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -92,13 +92,12 @@ type Controller struct { // TODO: logs, logs, logs -// Reconcile reads the state of an Endpoints object for a Kubernetes Service and reconciles Consul services which -// correspond to the Kubernetes Service. These events are driven by changes to the Pods backing the Kube service. +// Reconcile reads the state of a Kubernetes Pod and reconciles Consul workloads that are 1:1 mapped. func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var errs error var pod corev1.Pod - // Ignore the request if the namespace of the endpoint is not allowed. + // Ignore the request if the namespace of the pod is not allowed. // Strictly speaking, this is not required because the mesh webhook also knows valid namespaces // for injection, but it will somewhat reduce the amount of unnecessary deletions for non-injected // pods diff --git a/control-plane/connect-inject/namespace/namespace_controller.go b/control-plane/connect-inject/namespace/namespace_controller.go new file mode 100644 index 0000000000..86035bc69f --- /dev/null +++ b/control-plane/connect-inject/namespace/namespace_controller.go @@ -0,0 +1,131 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package namespace + +import ( + "context" + "fmt" + + mapset "github.com/deckarep/golang-set" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" +) + +type Controller struct { + client.Client + // ConsulClientConfig is the config for the Consul API client. + ConsulClientConfig *consul.Config + // ConsulServerConnMgr is the watcher for the Consul server addresses. + ConsulServerConnMgr consul.ServerConnectionManager + // AllowK8sNamespacesSet determines kube namespace that are reconciled. + AllowK8sNamespacesSet mapset.Set + // DenyK8sNamespacesSet determines kube namespace that are ignored. + DenyK8sNamespacesSet mapset.Set + + // Partition is not required. It should already be set in the API ClientConfig + + // ConsulDestinationNamespace is the name of the Consul namespace to create + // all config entries in. If EnableNSMirroring is true this is ignored. + ConsulDestinationNamespace string + // EnableNSMirroring causes Consul namespaces to be created to match the + // k8s namespace of any config entry custom resource. Config entries will + // be created in the matching Consul namespace. + EnableNSMirroring bool + // NSMirroringPrefix is an optional prefix that can be added to the Consul + // namespaces created while mirroring. For example, if it is set to "k8s-", + // then the k8s `default` namespace will be mirrored in Consul's + // `k8s-default` namespace. + NSMirroringPrefix string + + // CrossNamespaceACLPolicy is the name of the ACL policy to attach to + // any created Consul namespaces to allow cross namespace service discovery. + // Only necessary if ACLs are enabled. + CrossNamespaceACLPolicy string + + Log logr.Logger +} + +// Reconcile reads a Kubernetes Namespace and reconciles the mapped namespace in Consul. +// TODO: Move the creation of a destination namespace to a dedicated, single-flight goroutine. +func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var namespace corev1.Namespace + + // Ignore the request if the namespace is not allowed. + if common.ShouldIgnore(req.Name, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + return ctrl.Result{}, nil + } + + apiClient, err := consul.NewClientFromConnMgr(r.ConsulClientConfig, r.ConsulServerConnMgr) + if err != nil { + r.Log.Error(err, "failed to create Consul API client", "name", req.Name) + return ctrl.Result{}, err + } + + err = r.Client.Get(ctx, req.NamespacedName, &namespace) + + // If the namespace object has been deleted (and we get an IsNotFound error), + // we need to remove the Namespace from Consul. + if k8serrors.IsNotFound(err) { + + // if we are using a destination namespace, NEVER delete it. + if !r.EnableNSMirroring { + return ctrl.Result{}, nil + } + + if err := namespaces.EnsureDeleted(apiClient, r.getConsulNamespace(req.Name)); err != nil { + r.Log.Error(err, "error deleting namespace", + "namespace", r.getConsulNamespace(req.Name)) + return ctrl.Result{}, fmt.Errorf("error deleting namespace: %w", err) + } + + return ctrl.Result{}, nil + } else if err != nil { + r.Log.Error(err, "failed to get namespace", "name", req.Name) + return ctrl.Result{}, err + } + + r.Log.Info("retrieved", "namespace", namespace.GetName()) + + // TODO: eventually we will want to replace the V1 namespace APIs with the native V2 resource creation for tenancy + if _, err := namespaces.EnsureExists(apiClient, r.getConsulNamespace(namespace.GetName()), r.CrossNamespaceACLPolicy); err != nil { + r.Log.Error(err, "error checking or creating namespace", + "namespace", r.getConsulNamespace(namespace.GetName())) + return ctrl.Result{}, fmt.Errorf("error checking or creating namespace: %w", err) + } + + return ctrl.Result{}, nil +} + +// SetupWithManager registers this controller with the manager. +func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Namespace{}). + Complete(r) +} + +// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace +// depending on Consul Namespaces being enabled and the value of namespace mirroring. +func (r *Controller) getConsulNamespace(kubeNamespace string) string { + ns := namespaces.ConsulNamespace( + kubeNamespace, + true, + r.ConsulDestinationNamespace, + r.EnableNSMirroring, + r.NSMirroringPrefix, + ) + + // TODO: remove this if and when the default namespace of resources change. + if ns == "" { + ns = constants.DefaultConsulNS + } + return ns +} diff --git a/control-plane/connect-inject/namespace/namespace_controller_ent_test.go b/control-plane/connect-inject/namespace/namespace_controller_ent_test.go new file mode 100644 index 0000000000..1b63161976 --- /dev/null +++ b/control-plane/connect-inject/namespace/namespace_controller_ent_test.go @@ -0,0 +1,413 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package namespace + +import ( + "context" + "testing" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + capi "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +const ( + testNamespaceName = "foo" + testCrossACLPolicy = "cross-namespace-policy" +) + +// TestReconcileCreateNamespace ensures that a new namespace is reconciled to a +// Consul namespace. The actual namespace in Consul depends on if the controller +// is configured with a destination namespace or mirroring enabled. +func TestReconcileCreateNamespace(t *testing.T) { + t.Parallel() + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: testNamespaceName, + }} + nsDefault := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: metav1.NamespaceDefault, + }} + + type testCase struct { + name string + kubeNamespaceName string // this will default to "foo" + partition string + + consulDestinationNamespace string + enableNSMirroring bool + nsMirrorPrefix string + + expectedConsulNamespaceName string + expectedConsulNamespace *capi.Namespace + + acls bool + expErr string + } + + run := func(t *testing.T, tc testCase) { + k8sObjects := []runtime.Object{ + &ns, + &nsDefault, + } + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test consulServer server. + adminToken := "123e4567-e89b-12d3-a456-426614174000" + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + if tc.acls { + c.ACL.Enabled = tc.acls + c.ACL.Tokens.InitialManagement = adminToken + } + }) + + if tc.partition != "" { + testClient.Cfg.APIClientConfig.Partition = tc.partition + + partition := &capi.Partition{ + Name: tc.partition, + } + _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) + require.NoError(t, err) + } + + // Create the namespace controller. + nc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + EnableNSMirroring: tc.enableNSMirroring, + NSMirroringPrefix: tc.nsMirrorPrefix, + ConsulDestinationNamespace: tc.consulDestinationNamespace, + } + if tc.acls { + nc.CrossNamespaceACLPolicy = testCrossACLPolicy + + policy := &capi.ACLPolicy{Name: testCrossACLPolicy} + _, _, err := testClient.APIClient.ACL().PolicyCreate(policy, nil) + require.NoError(t, err) + } + + if tc.kubeNamespaceName == "" { + tc.kubeNamespaceName = testNamespaceName + } + + namespacedName := types.NamespacedName{ + Name: tc.kubeNamespaceName, + } + + resp, err := nc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: namespacedName, + }) + if tc.expErr != "" { + require.EqualError(t, err, tc.expErr) + } else { + require.NoError(t, err) + } + require.False(t, resp.Requeue) + + expectedNamespaceMatches(t, testClient.APIClient, tc.expectedConsulNamespaceName, tc.partition, tc.expectedConsulNamespace) + } + + testCases := []testCase{ + { + // This also tests that we don't overwrite anything about the default Consul namespace, + // because the original description is maintained. + name: "destination namespace default", + expectedConsulNamespaceName: constants.DefaultConsulNS, + expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), + }, + { + name: "destination namespace, non-default", + consulDestinationNamespace: "bar", + expectedConsulNamespaceName: "bar", + expectedConsulNamespace: getNamespace("bar", "", false), + }, + { + name: "destination namespace, non-default with ACLs enabled", + consulDestinationNamespace: "bar", + acls: true, + expectedConsulNamespaceName: "bar", + expectedConsulNamespace: getNamespace("bar", constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default + }, + { + name: "destination namespace, non-default, non-default partition", + partition: "baz", + consulDestinationNamespace: "bar", + expectedConsulNamespaceName: "bar", + expectedConsulNamespace: getNamespace("bar", "baz", false), + }, + { + name: "mirrored namespaces", + enableNSMirroring: true, + expectedConsulNamespaceName: testNamespaceName, + expectedConsulNamespace: getNamespace(testNamespaceName, "", false), + }, + { + name: "mirrored namespaces, non-default partition", + partition: "baz", + enableNSMirroring: true, + expectedConsulNamespaceName: testNamespaceName, + expectedConsulNamespace: getNamespace(testNamespaceName, "baz", false), + }, + { + name: "mirrored namespaces with acls", + acls: true, + enableNSMirroring: true, + expectedConsulNamespaceName: testNamespaceName, + expectedConsulNamespace: getNamespace(testNamespaceName, constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default + }, + { + name: "mirrored namespaces with prefix", + nsMirrorPrefix: "k8s-", + enableNSMirroring: true, + expectedConsulNamespaceName: "k8s-foo", + expectedConsulNamespace: getNamespace("k8s-foo", "", false), + }, + { + name: "mirrored namespaces with prefix, non-default partition", + nsMirrorPrefix: "k8s-", + partition: "baz", + enableNSMirroring: true, + expectedConsulNamespaceName: "k8s-foo", + expectedConsulNamespace: getNamespace("k8s-foo", "baz", false), + }, + { + name: "mirrored namespaces with prefix and acls", + nsMirrorPrefix: "k8s-", + acls: true, + enableNSMirroring: true, + expectedConsulNamespaceName: "k8s-foo", + expectedConsulNamespace: getNamespace("k8s-foo", constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default + }, + { + name: "mirrored namespaces overrides destination namespace", + enableNSMirroring: true, + consulDestinationNamespace: "baz", + expectedConsulNamespaceName: testNamespaceName, + expectedConsulNamespace: getNamespace(testNamespaceName, "", false), + }, + { + name: "ignore kube-system", + kubeNamespaceName: metav1.NamespaceSystem, + consulDestinationNamespace: "bar", + expectedConsulNamespaceName: "bar", // we make sure that this doesn't get created from the kube-system space by not providing the actual struct + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +// Tests deleting a Namespace object, with and without matching Consul resources. +func TestReconcileDeleteNamespace(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + kubeNamespaceName string // this will default to "foo" + partition string + + destinationNamespace string + enableNSMirroring bool + nsMirrorPrefix string + + existingConsulNamespace *capi.Namespace + + expectedConsulNamespace *capi.Namespace + } + + run := func(t *testing.T, tc testCase) { + fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + + if tc.partition != "" { + testClient.Cfg.APIClientConfig.Partition = tc.partition + + partition := &capi.Partition{ + Name: tc.partition, + } + _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) + require.NoError(t, err) + } + + if tc.existingConsulNamespace != nil { + _, _, err := testClient.APIClient.Namespaces().Create(tc.existingConsulNamespace, nil) + require.NoError(t, err) + } + + // Create the namespace controller. + nc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + EnableNSMirroring: tc.enableNSMirroring, + NSMirroringPrefix: tc.nsMirrorPrefix, + ConsulDestinationNamespace: tc.destinationNamespace, + } + + if tc.kubeNamespaceName == "" { + tc.kubeNamespaceName = testNamespaceName + } + + namespacedName := types.NamespacedName{ + Name: tc.kubeNamespaceName, + } + + resp, err := nc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: namespacedName, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + if tc.existingConsulNamespace != nil { + expectedNamespaceMatches(t, testClient.APIClient, tc.existingConsulNamespace.Name, tc.partition, tc.expectedConsulNamespace) + } else { + expectedNamespaceMatches(t, testClient.APIClient, testNamespaceName, tc.partition, tc.expectedConsulNamespace) + } + } + + testCases := []testCase{ + { + name: "destination namespace with default is not cleaned up", + existingConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), + expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), + }, + { + name: "destination namespace with non-default is not cleaned up", + destinationNamespace: "bar", + existingConsulNamespace: getNamespace("bar", "", false), + expectedConsulNamespace: getNamespace("bar", "", false), + }, + { + name: "destination namespace with non-default is not cleaned up, non-default partition", + destinationNamespace: "bar", + partition: "baz", + existingConsulNamespace: getNamespace("bar", "baz", false), + expectedConsulNamespace: getNamespace("bar", "baz", false), + }, + { + name: "mirrored namespaces", + enableNSMirroring: true, + existingConsulNamespace: getNamespace(testNamespaceName, "", false), + }, + { + name: "mirrored namespaces but it's the default namespace", + kubeNamespaceName: metav1.NamespaceDefault, + enableNSMirroring: true, + existingConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), + expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), // Don't ever delete the Consul default NS + }, + { + name: "mirrored namespaces, non-default partition", + partition: "baz", + enableNSMirroring: true, + existingConsulNamespace: getNamespace(testNamespaceName, "baz", false), + }, + { + name: "mirrored namespaces with prefix", + nsMirrorPrefix: "k8s-", + enableNSMirroring: true, + existingConsulNamespace: getNamespace("k8s-foo", "", false), + }, + { + name: "mirrored namespaces with prefix, non-default partition", + partition: "baz", + nsMirrorPrefix: "k8s-", + enableNSMirroring: true, + existingConsulNamespace: getNamespace("k8s-foo", "baz", false), + }, + { + name: "mirrored namespaces overrides destination namespace", + enableNSMirroring: true, + destinationNamespace: "baz", + existingConsulNamespace: getNamespace(testNamespaceName, "", false), + }, + { + name: "mirrored namespace, but the namespace is already removed from Consul", + enableNSMirroring: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +// getNamespace return a basic Consul V1 namespace for testing setup and comparison +func getNamespace(name string, partition string, acls bool) *capi.Namespace { + ns := &capi.Namespace{ + Name: name, + Partition: partition, + } + + if name != constants.DefaultConsulNS { + ns.Description = "Auto-generated by consul-k8s" + ns.Meta = map[string]string{"external-source": "kubernetes"} + ns.ACLs = &capi.NamespaceACLConfig{} + } else { + ns.Description = "Builtin Default Namespace" + } + + if acls && name != constants.DefaultConsulNS { + // Create the ACLs config for the cross-Consul-namespace + // default policy that needs to be attached + ns.ACLs = &capi.NamespaceACLConfig{ + PolicyDefaults: []capi.ACLLink{ + {Name: testCrossACLPolicy}, + }, + } + } + + return ns +} + +func expectedNamespaceMatches(t *testing.T, client *capi.Client, name string, partition string, expectedNamespace *capi.Namespace) { + namespaceInfo, _, err := client.Namespaces().Read(name, &capi.QueryOptions{Partition: partition}) + + require.NoError(t, err) + + if expectedNamespace == nil { + require.True(t, namespaceInfo == nil || namespaceInfo.DeletedAt != nil) + return + } + + require.NotNil(t, namespaceInfo) + // Zero out the Raft Index, in this case it is irrelevant. + namespaceInfo.CreateIndex = 0 + namespaceInfo.ModifyIndex = 0 + if namespaceInfo.ACLs != nil && len(namespaceInfo.ACLs.PolicyDefaults) > 0 { + namespaceInfo.ACLs.PolicyDefaults[0].ID = "" // Zero out the ID for ACLs enabled to facilitate testing. + } + require.Equal(t, *expectedNamespace, *namespaceInfo) +} diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook.go b/control-plane/connect-inject/webhook_v2/mesh_webhook.go index d41601b6b7..e76cd0f82f 100644 --- a/control-plane/connect-inject/webhook_v2/mesh_webhook.go +++ b/control-plane/connect-inject/webhook_v2/mesh_webhook.go @@ -387,29 +387,6 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi return admission.Errored(http.StatusBadRequest, err) } - // Check and potentially create Consul resources. This is done after - // all patches are created to guarantee no errors were encountered in - // that process before modifying the Consul cluster. - if w.EnableNamespaces { - serverState, err := w.ConsulServerConnMgr.State() - if err != nil { - w.Log.Error(err, "error checking or creating namespace", - "ns", w.consulNamespace(req.Namespace), "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking or creating namespace: %s", err)) - } - apiClient, err := consul.NewClientFromConnMgrState(w.ConsulConfig, serverState) - if err != nil { - w.Log.Error(err, "error checking or creating namespace", - "ns", w.consulNamespace(req.Namespace), "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking or creating namespace: %s", err)) - } - if _, err := namespaces.EnsureExists(apiClient, w.consulNamespace(req.Namespace), w.CrossNamespaceACLPolicy); err != nil { - w.Log.Error(err, "error checking or creating namespace", - "ns", w.consulNamespace(req.Namespace), "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking or creating namespace: %s", err)) - } - } - // Return a Patched response along with the patches we intend on applying to the // Pod received by the meshWebhook. return admission.Patched(fmt.Sprintf("valid %s request", pod.Kind), patches...) diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go b/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go index d6920d83a2..5d1c51a0de 100644 --- a/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go +++ b/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go @@ -11,8 +11,6 @@ import ( "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testing" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" @@ -25,543 +23,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) -// This tests the checkAndCreate namespace function that is called -// in meshWebhook.Mutate. Patch generation is tested in the non-enterprise -// tests. Other namespace-specific logic is tested directly in the -// specific methods (shouldInject, consulNamespace). -func TestHandler_MutateWithNamespaces(t *testing.T) { - t.Parallel() - - basicSpec := corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - } - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - cases := []struct { - Name string - Webhook MeshWebhook - Req admission.Request - ExpectedNamespaces []string - }{ - { - Name: "single destination namespace 'default' from k8s 'default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "default", - }, - }, - ExpectedNamespaces: []string{"default"}, - }, - - { - Name: "single destination namespace 'default' from k8s 'non-default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", - decoder: decoder, - Clientset: clientWithNamespace("non-default"), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "non-default", - }, - }, - ExpectedNamespaces: []string{"default"}, - }, - - { - Name: "single destination namespace 'dest' from k8s 'default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "dest", - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "default", - }, - }, - ExpectedNamespaces: []string{"default", "dest"}, - }, - - { - Name: "single destination namespace 'dest' from k8s 'non-default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "dest", - decoder: decoder, - Clientset: clientWithNamespace("non-default"), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "non-default", - }, - }, - ExpectedNamespaces: []string{"default", "dest"}, - }, - - { - Name: "mirroring from k8s 'default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", // will be overridden - EnableK8SNSMirroring: true, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "default", - }, - }, - ExpectedNamespaces: []string{"default"}, - }, - - { - Name: "mirroring from k8s 'dest'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", // will be overridden - EnableK8SNSMirroring: true, - decoder: decoder, - Clientset: clientWithNamespace("dest"), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "dest", - }, - }, - ExpectedNamespaces: []string{"default", "dest"}, - }, - - { - Name: "mirroring with prefix from k8s 'default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", // will be overridden - EnableK8SNSMirroring: true, - K8SNSMirroringPrefix: "k8s-", - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "default", - }, - }, - ExpectedNamespaces: []string{"default", "k8s-default"}, - }, - - { - Name: "mirroring with prefix from k8s 'dest'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", // will be overridden - EnableK8SNSMirroring: true, - K8SNSMirroringPrefix: "k8s-", - decoder: decoder, - Clientset: clientWithNamespace("dest"), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "dest", - }, - }, - ExpectedNamespaces: []string{"default", "k8s-dest"}, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - client := testClient.APIClient - - // Add the client config and watcher to the test's meshWebhook - tt.Webhook.ConsulConfig = testClient.Cfg - tt.Webhook.ConsulServerConnMgr = testClient.Watcher - - // Mutate! - resp := tt.Webhook.Handle(context.Background(), tt.Req) - require.Equal(t, resp.Allowed, true) - - // Check all the namespace things - // Check that we have the right number of namespaces - namespaces, _, err := client.Namespaces().List(&api.QueryOptions{}) - require.NoError(t, err) - require.Len(t, namespaces, len(tt.ExpectedNamespaces)) - - // Check the namespace details - for _, ns := range tt.ExpectedNamespaces { - actNamespace, _, err := client.Namespaces().Read(ns, &api.QueryOptions{}) - require.NoErrorf(t, err, "error getting namespace %s", ns) - require.NotNilf(t, actNamespace, "namespace %s was nil", ns) - require.Equalf(t, ns, actNamespace.Name, "namespace %s was improperly named", ns) - - // Check created namespace properties - if ns != "default" { - require.Equalf(t, "Auto-generated by consul-k8s", actNamespace.Description, - "wrong namespace description for namespace %s", ns) - require.Containsf(t, actNamespace.Meta, "external-source", - "namespace %s does not contain external-source metadata key", ns) - require.Equalf(t, "kubernetes", actNamespace.Meta["external-source"], - "namespace %s has wrong value for external-source metadata key", ns) - } - - } - }) - } -} - -// Tests that the correct cross-namespace policy is -// added to created namespaces. -func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { - basicSpec := corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - } - - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - cases := []struct { - Name string - Webhook MeshWebhook - Req admission.Request - ExpectedNamespaces []string - }{ - { - Name: "acls + single destination namespace 'default' from k8s 'default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", - CrossNamespaceACLPolicy: "cross-namespace-policy", - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "default", - }, - }, - ExpectedNamespaces: []string{"default"}, - }, - - { - Name: "acls + single destination namespace 'default' from k8s 'non-default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", - CrossNamespaceACLPolicy: "cross-namespace-policy", - decoder: decoder, - Clientset: clientWithNamespace("non-default"), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "non-default", - }, - }, - ExpectedNamespaces: []string{"default"}, - }, - - { - Name: "acls + single destination namespace 'dest' from k8s 'default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "dest", - CrossNamespaceACLPolicy: "cross-namespace-policy", - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "default", - }, - }, - ExpectedNamespaces: []string{"default", "dest"}, - }, - - { - Name: "acls + single destination namespace 'dest' from k8s 'non-default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "dest", - CrossNamespaceACLPolicy: "cross-namespace-policy", - decoder: decoder, - Clientset: clientWithNamespace("non-default"), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "non-default", - }, - }, - ExpectedNamespaces: []string{"default", "dest"}, - }, - - { - Name: "acls + mirroring from k8s 'default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", // will be overridden - EnableK8SNSMirroring: true, - CrossNamespaceACLPolicy: "cross-namespace-policy", - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "default", - }, - }, - ExpectedNamespaces: []string{"default"}, - }, - - { - Name: "acls + mirroring from k8s 'dest'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", // will be overridden - EnableK8SNSMirroring: true, - CrossNamespaceACLPolicy: "cross-namespace-policy", - decoder: decoder, - Clientset: clientWithNamespace("dest"), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "dest", - }, - }, - ExpectedNamespaces: []string{"default", "dest"}, - }, - - { - Name: "acls + mirroring with prefix from k8s 'default'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", // will be overridden - EnableK8SNSMirroring: true, - K8SNSMirroringPrefix: "k8s-", - CrossNamespaceACLPolicy: "cross-namespace-policy", - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "default", - }, - }, - ExpectedNamespaces: []string{"default", "k8s-default"}, - }, - - { - Name: "acls + mirroring with prefix from k8s 'dest'", - Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: "default", // will be overridden - EnableK8SNSMirroring: true, - K8SNSMirroringPrefix: "k8s-", - CrossNamespaceACLPolicy: "cross-namespace-policy", - decoder: decoder, - Clientset: clientWithNamespace("dest"), - }, - Req: admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - Namespace: "dest", - }, - }, - ExpectedNamespaces: []string{"default", "k8s-dest"}, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - // Set up consul server - adminToken := "123e4567-e89b-12d3-a456-426614174000" - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.ACL.Enabled = true - c.ACL.Tokens.InitialManagement = adminToken - }) - client := testClient.APIClient - - // Add the client config and watcher to the test's meshWebhook - tt.Webhook.ConsulConfig = testClient.Cfg - tt.Webhook.ConsulServerConnMgr = testClient.Watcher - - // Create cross namespace policy - // This would have been created by the acl bootstrapper in the - // default namespace to be attached to all created namespaces. - crossNamespaceRules := `namespace_prefix "" { - service_prefix "" { - policy = "read" - } - node_prefix "" { - policy = "read" - } -} ` - - policyTmpl := api.ACLPolicy{ - Name: "cross-namespace-policy", - Description: "Policy to allow permissions to cross Consul namespaces for k8s services", - Rules: crossNamespaceRules, - } - - _, _, err = client.ACL().PolicyCreate(&policyTmpl, &api.WriteOptions{}) - require.NoError(t, err) - - // Mutate! - resp := tt.Webhook.Handle(context.Background(), tt.Req) - require.Equal(t, resp.Allowed, true) - - // Check all the namespace things - // Check that we have the right number of namespaces - namespaces, _, err := client.Namespaces().List(&api.QueryOptions{}) - require.NoError(t, err) - require.Len(t, namespaces, len(tt.ExpectedNamespaces)) - - // Check the namespace details - for _, ns := range tt.ExpectedNamespaces { - actNamespace, _, err := client.Namespaces().Read(ns, &api.QueryOptions{}) - require.NoErrorf(t, err, "error getting namespace %s", ns) - require.NotNilf(t, actNamespace, "namespace %s was nil", ns) - require.Equalf(t, ns, actNamespace.Name, "namespace %s was improperly named", ns) - - // Check created namespace properties - if ns != "default" { - require.Equalf(t, "Auto-generated by consul-k8s", actNamespace.Description, - "wrong namespace description for namespace %s", ns) - require.Containsf(t, actNamespace.Meta, "external-source", - "namespace %s does not contain external-source metadata key", ns) - require.Equalf(t, "kubernetes", actNamespace.Meta["external-source"], - "namespace %s has wrong value for external-source metadata key", ns) - - // Check for ACL policy things - // The acl bootstrapper will update the `default` namespace, so that - // can't be tested here. - require.NotNilf(t, actNamespace.ACLs, "ACLs was nil for namespace %s", ns) - require.Lenf(t, actNamespace.ACLs.PolicyDefaults, 1, "wrong length for PolicyDefaults in namespace %s", ns) - require.Equalf(t, "cross-namespace-policy", actNamespace.ACLs.PolicyDefaults[0].Name, - "wrong policy name for namespace %s", ns) - } - - } - }) - } -} - // Test that the annotation for the Consul namespace is added. func TestHandler_MutateWithNamespaces_Annotation(t *testing.T) { t.Parallel() diff --git a/control-plane/namespaces/namespaces.go b/control-plane/namespaces/namespaces.go index 84b8c15ee4..217765c59b 100644 --- a/control-plane/namespaces/namespaces.go +++ b/control-plane/namespaces/namespaces.go @@ -55,6 +55,29 @@ func EnsureExists(client *capi.Client, ns string, crossNSAClPolicy string) (bool return true, err } +// EnsureDeleted ensures a Consul namespace with name ns is deleted. If it is already not found +// the call to delete will be skipped. +func EnsureDeleted(client *capi.Client, ns string) error { + if ns == WildcardNamespace || ns == DefaultNamespace { + return nil + } + // Check if the Consul namespace exists. + namespaceInfo, _, err := client.Namespaces().Read(ns, nil) + if err != nil { + return fmt.Errorf("could not read namespace %s: %w", ns, err) + } + if namespaceInfo == nil { + return nil + } + + // If not empty, delete it. + _, err = client.Namespaces().Delete(ns, nil) + if err != nil { + return fmt.Errorf("could not delete namespace %s: %w", ns, err) + } + return nil +} + // ConsulNamespace returns the consul namespace that a service should be // registered in based on the namespace options. It returns an // empty string if namespaces aren't enabled. diff --git a/control-plane/namespaces/namespaces_test.go b/control-plane/namespaces/namespaces_test.go index a2c9989ae8..d34bd40ee4 100644 --- a/control-plane/namespaces/namespaces_test.go +++ b/control-plane/namespaces/namespaces_test.go @@ -159,6 +159,56 @@ func TestEnsureExists_CreatesNS(tt *testing.T) { } } +// Test that it creates the namespace if it doesn't exist. +func TestEnsureDelete(tt *testing.T) { + name := "foo" + for _, c := range []struct { + NamespaceExists bool + }{ + { + NamespaceExists: true, + }, + { + NamespaceExists: false, + }, + } { + tt.Run(fmt.Sprintf("namespace: %t", c.NamespaceExists), func(t *testing.T) { + consul, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) + defer consul.Stop() + consul.WaitForLeader(t) + + consulClient, err := capi.NewClient(&capi.Config{ + Address: consul.HTTPAddr, + }) + require.NoError(t, err) + + if c.NamespaceExists { + ns := capi.Namespace{ + Name: name, + } + _, _, err = consulClient.Namespaces().Create(&ns, nil) + require.NoError(t, err) + + check, _, err := consulClient.Namespaces().Read(name, nil) + require.NoError(t, err) + require.NotNil(t, check) + require.Equal(t, name, check.Name) + } + + err = EnsureDeleted(consulClient, name) + require.NoError(t, err) + + // Ensure it was deleted. + cNS, _, err := consulClient.Namespaces().Read(name, nil) + require.NoError(t, err) + if cNS != nil && cNS.DeletedAt == nil { + require.Fail(t, "the namespace was not deleted") + } + }) + } +} + func TestConsulNamespace(t *testing.T) { cases := map[string]struct { kubeNS string diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 32b97ac245..b3938baace 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/namespace" webhookV2 "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook_v2" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -62,7 +63,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage AuthMethod: c.flagACLAuthMethod, MetricsConfig: metricsConfig, EnableTelemetryCollector: c.flagEnableTelemetryCollector, - Log: ctrl.Log.WithName("controller").WithName("pods"), + Log: ctrl.Log.WithName("controller").WithName("pod"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", pod.Controller{}) return err @@ -86,7 +87,24 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - // TODO: Nodes Controller + if c.flagEnableNamespaces { + err := (&namespace.Controller{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + Log: ctrl.Log.WithName("controller").WithName("namespace"), + }).SetupWithManager(mgr) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", namespace.Controller{}) + return err + } + } // TODO: Serviceaccounts Controller From ca870a17912551f0cf297c31243095c0c7a069f3 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 15 Sep 2023 10:29:26 -0400 Subject: [PATCH 383/592] GKE Autopilot support (#2952) * GKE Autopilot support --- .changelog/2952.txt | 3 +++ acceptance/framework/config/config.go | 18 ++++++++++++++---- acceptance/framework/consul/helm_cluster.go | 1 + acceptance/framework/flags/flags.go | 13 +++++++++---- charts/consul/templates/crd-tcproutes.yaml | 2 +- charts/consul/test/unit/crd-tcproutes.bats | 19 +++++++++++++++++++ charts/consul/values.yaml | 5 +++++ 7 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 .changelog/2952.txt diff --git a/.changelog/2952.txt b/.changelog/2952.txt new file mode 100644 index 0000000000..d07be130cf --- /dev/null +++ b/.changelog/2952.txt @@ -0,0 +1,3 @@ +```release-note:improvement +Add support for running on GKE Autopilot. +``` diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index a638732c37..49a24a6d10 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -99,10 +99,11 @@ type TestConfig struct { NoCleanup bool DebugDirectory string - UseAKS bool - UseEKS bool - UseGKE bool - UseKind bool + UseAKS bool + UseEKS bool + UseGKE bool + UseGKEAutopilot bool + UseKind bool helmChartPath string } @@ -155,6 +156,15 @@ func (t *TestConfig) HelmValuesFromConfig() (map[string]string, error) { } } + // UseGKEAutopilot is a temporary hack that we need in place as GKE Autopilot is already installing + // Gateway CRDs in the clusters. There are still other CRDs we need to install though (see helm cluster install) + if t.UseGKEAutopilot { + setIfNotEmpty(helmValues, "global.server.resources.requests.cpu", "500m") + setIfNotEmpty(helmValues, "global.server.resources.limits.cpu", "500m") + setIfNotEmpty(helmValues, "connectInject.apiGateway.manageExternalCRDs", "false") + setIfNotEmpty(helmValues, "connectInject.apiGateway.manageNonStandardCRDs", "true") + } + setIfNotEmpty(helmValues, "connectInject.transparentProxy.defaultEnabled", strconv.FormatBool(t.EnableTransparentProxy)) setIfNotEmpty(helmValues, "global.image", t.ConsulImage) diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 0525c5d442..74405bfebe 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -143,6 +143,7 @@ func (h *HelmCluster) Create(t *testing.T) { logger.Logf(t, "Unable to update helm repository, proceeding anyway: %s.", err) } } + helm.Install(t, h.helmOptions, chartName, h.releaseName) k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName)) diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index f4350f2fcb..c68983fe8c 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -50,10 +50,11 @@ type TestFlags struct { flagDebugDirectory string - flagUseAKS bool - flagUseEKS bool - flagUseGKE bool - flagUseKind bool + flagUseAKS bool + flagUseEKS bool + flagUseGKE bool + flagUseGKEAutopilot bool + flagUseKind bool flagDisablePeering bool @@ -145,6 +146,9 @@ func (t *TestFlags) init() { "If true, the tests will assume they are running against an EKS cluster(s).") flag.BoolVar(&t.flagUseGKE, "use-gke", false, "If true, the tests will assume they are running against a GKE cluster(s).") + flag.BoolVar(&t.flagUseGKEAutopilot, "use-gke-autopilot", false, + "If true, the tests will assume they are running against a GKE Autopilot cluster(s).") + flag.BoolVar(&t.flagUseKind, "use-kind", false, "If true, the tests will assume they are running against a local kind cluster(s).") @@ -235,6 +239,7 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { UseAKS: t.flagUseAKS, UseEKS: t.flagUseEKS, UseGKE: t.flagUseGKE, + UseGKEAutopilot: t.flagUseGKEAutopilot, UseKind: t.flagUseKind, } diff --git a/charts/consul/templates/crd-tcproutes.yaml b/charts/consul/templates/crd-tcproutes.yaml index 91989135e2..ba21ccd58a 100644 --- a/charts/consul/templates/crd-tcproutes.yaml +++ b/charts/consul/templates/crd-tcproutes.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +{{- if and .Values.connectInject.enabled (or .Values.connectInject.apiGateway.manageExternalCRDs .Values.connectInject.apiGateway.manageNonStandardCRDs ) }} # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 diff --git a/charts/consul/test/unit/crd-tcproutes.bats b/charts/consul/test/unit/crd-tcproutes.bats index 6916efdc6f..9cfdd182e7 100644 --- a/charts/consul/test/unit/crd-tcproutes.bats +++ b/charts/consul/test/unit/crd-tcproutes.bats @@ -26,3 +26,22 @@ load _helpers --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } + +@test "tcproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false and connectInject.apiGateway.manageNonStandardCRDs=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-tcproutes.yaml \ + --set 'connectInject.apiGateway.manageExternalCRDs=false' \ + --set 'connectInject.apiGateway.manageNonStandardCRDs=false' \ + . +} + +@test "tcproutes/CustomResourceDefinition: enabled with connectInject.apiGateway.manageNonStandardCRDs=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-tcproutes.yaml \ + --set 'connectInject.apiGateway.manageNonStandardCRDs=true' \ + . | tee /dev/stderr | + yq -s 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 762d64b207..f083622387 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2185,6 +2185,11 @@ connectInject: # If this setting is false, you will need to install the Gateway API CRDs manually. manageExternalCRDs: true + # Enables Consul on Kubernets to manage only the non-standard CRDs used for Gateway API. If manageExternalCRDs is true + # then all CRDs will be installed; otherwise, if manageNonStandardCRDs is true then only TCPRoute, GatewayClassConfig and MeshService + # will be installed. + manageNonStandardCRDs: false + # Configuration settings for the GatewayClass installed by Consul on Kubernetes. managedGatewayClass: # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) From b1bc57e23240ab258a1bf25b992947204ad1259a Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Fri, 15 Sep 2023 15:53:17 -0400 Subject: [PATCH 384/592] test(chart): fix telemetry V2 bats (#2966) --- .../telemetry-collector-v2-deployment.bats | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats index 3f77169fd6..87982abf4e 100755 --- a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats @@ -1031,28 +1031,29 @@ load _helpers #-------------------------------------------------------------------- # Admin Partitions - -@test "telemetryCollector/Deployment(V2): partition flags are set when using admin partitions" { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.adminPartitions.name=hashi' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo $flags | jq -r '. | any(contains("-login-partition=hashi"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $flags | jq -r '. | any(contains("-service-partition=hashi"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} +# TODO: re-enable this test when V2 supports admin partitions. + +# @test "telemetryCollector/Deployment: partition flags are set when using admin partitions" { +# cd `chart_dir` +# local flags=$(helm template \ +# -s templates/telemetry-collector-deployment.yaml \ +# --set 'ui.enabled=false' \ +# --set 'global.experiments[0]=resource-apis' \ +# --set 'telemetryCollector.enabled=true' \ +# --set 'telemetryCollector.image=bar' \ +# --set 'global.enableConsulNamespaces=true' \ +# --set 'global.adminPartitions.enabled=true' \ +# --set 'global.adminPartitions.name=hashi' \ +# --set 'global.acls.manageSystemACLs=true' \ +# . | tee /dev/stderr | +# yq '.spec.template.spec.containers[1].args' | tee /dev/stderr) +# +# local actual=$(echo $flags | jq -r '. | any(contains("-login-partition=hashi"))' | tee /dev/stderr) +# [ "${actual}" = 'true' ] +# +# local actual=$(echo $flags | jq -r '. | any(contains("-service-partition=hashi"))' | tee /dev/stderr) +# [ "${actual}" = "true" ] +# } @test "telemetryCollector/Deployment(V2): consul-ca-cert volume mount is not set when using externalServers and useSystemRoots" { cd `chart_dir` From 60d09e21f959c9faac642c386c67b52ca3ea158e Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Fri, 15 Sep 2023 16:26:17 -0400 Subject: [PATCH 385/592] [NET-5314] Limit v2 Service port registration to L4 TCP ports (#2965) Limit v2 Service port registration to L4 TCP ports Ignore non-TCP L4 ports in K8s services. This is expected behavior and also prevents unintended duplication of Service port values registered to Consul (which is not supported) when ports have multiplexed L4 traffic. --- .../endpointsv2/endpoints_controller.go | 42 +++-- .../endpointsv2/endpoints_controller_test.go | 144 +++++++++++++++--- 2 files changed, 153 insertions(+), 33 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index dca7dad950..739f1bade2 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -4,6 +4,7 @@ package endpointsv2 import ( "context" + "fmt" "net" "sort" "strings" @@ -202,23 +203,24 @@ func getOwnerPrefixFromPod(pod *corev1.Pod) string { // registerService creates a Consul service registration from the provided Kuberetes service and endpoint information. func (r *Controller) registerService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, service corev1.Service, selector *pbcatalog.WorkloadSelector) error { - serviceResource := r.getServiceResource( - &pbcatalog.Service{ - Workloads: selector, - Ports: getServicePorts(service), - VirtualIps: r.getServiceVIPs(service), - }, + consulSvc := &pbcatalog.Service{ + Workloads: selector, + Ports: getServicePorts(service), + VirtualIps: r.getServiceVIPs(service), + } + consulSvcResource := r.getServiceResource( + consulSvc, service.Name, // Consul and Kubernetes service name will always match r.getConsulNamespace(service.Namespace), r.getConsulPartition(), getServiceMeta(service), ) - r.Log.Info("registering service with Consul", getLogFieldsForResource(serviceResource.Id)...) + r.Log.Info("registering service with Consul", getLogFieldsForResource(consulSvcResource.Id)...) //TODO: Maybe attempt to debounce redundant writes. For now, we blindly rewrite state on each reconcile. - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: serviceResource}) + _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: consulSvcResource}) if err != nil { - r.Log.Error(err, "failed to register service", getLogFieldsForResource(serviceResource.Id)...) + r.Log.Error(err, fmt.Sprintf("failed to register service: %+v", consulSvc), getLogFieldsForResource(consulSvcResource.Id)...) return err } @@ -254,13 +256,21 @@ func getServicePorts(service corev1.Service) []*pbcatalog.ServicePort { ports := make([]*pbcatalog.ServicePort, 0, len(service.Spec.Ports)+1) for _, p := range service.Spec.Ports { - ports = append(ports, &pbcatalog.ServicePort{ - VirtualPort: uint32(p.Port), - //TODO: If the value is a number, infer the correct name value based on - // the most prevalent endpoint subset for the port (best-effot, inspect a pod). - TargetPort: p.TargetPort.String(), - Protocol: common.GetPortProtocol(p.AppProtocol), - }) + // Service mesh only supports TCP as the L4 Protocol (not to be confused w/ L7 AppProtocol). + // + // This check is necessary to deduplicate VirtualPort values when multiple declared ServicePort values exist + // for the same port, which is possible in K8s when e.g. multiplexing TCP and UDP traffic over a single port. + // + // If we otherwise see repeat port values in a K8s service, we pass along and allow Consul to fail validation. + if p.Protocol == corev1.ProtocolTCP { + ports = append(ports, &pbcatalog.ServicePort{ + VirtualPort: uint32(p.Port), + //TODO: If the value is a number, infer the correct name value based on + // the most prevalent endpoint subset for the port (best-effot, inspect a pod). + TargetPort: p.TargetPort.String(), + Protocol: common.GetPortProtocol(p.AppProtocol), + }) + } } //TODO: Error check reserved "mesh" target port diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 2458444008..caadfb8d9c 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -89,20 +89,23 @@ func TestReconcile_CreateService(t *testing.T) { { Name: "public", Port: 8080, + Protocol: "TCP", TargetPort: intstr.FromString("my-http-port"), AppProtocol: &appProtocolHttp, }, { Name: "api", Port: 9090, + Protocol: "TCP", TargetPort: intstr.FromString("my-grpc-port"), AppProtocol: &appProtocolGrpc, }, { Name: "other", Port: 10001, + Protocol: "TCP", TargetPort: intstr.FromString("10001"), - // no protocol specified + // no app protocol specified }, }, }, @@ -169,18 +172,21 @@ func TestReconcile_CreateService(t *testing.T) { Addresses: addressesForPods(pod1, pod2), Ports: []corev1.EndpointPort{ { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, + Name: "public", Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, }, { - Name: "my-grpc-port", - AppProtocol: &appProtocolGrpc, + Name: "api", Port: 6789, + Protocol: "TCP", + AppProtocol: &appProtocolGrpc, }, { - Name: "10001", - Port: 10001, + Name: "other", + Port: 10001, + Protocol: "TCP", }, }, }, @@ -197,20 +203,23 @@ func TestReconcile_CreateService(t *testing.T) { { Name: "public", Port: 8080, + Protocol: "TCP", TargetPort: intstr.FromString("my-http-port"), AppProtocol: &appProtocolHttp, }, { Name: "api", Port: 9090, + Protocol: "TCP", TargetPort: intstr.FromString("my-grpc-port"), AppProtocol: &appProtocolGrpc, }, { Name: "other", Port: 10001, + Protocol: "TCP", TargetPort: intstr.FromString("10001"), - // no protocol specified + // no app protocol specified }, }, }, @@ -282,9 +291,10 @@ func TestReconcile_CreateService(t *testing.T) { NotReadyAddresses: addressesForPods(pod2), Ports: []corev1.EndpointPort{ { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, + Name: "public", Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, }, }, }, @@ -301,6 +311,7 @@ func TestReconcile_CreateService(t *testing.T) { { Name: "public", Port: 8080, + Protocol: "TCP", TargetPort: intstr.FromString("my-http-port"), AppProtocol: &appProtocolHttp, }, @@ -366,9 +377,10 @@ func TestReconcile_CreateService(t *testing.T) { Addresses: addressesForPods(pod1), Ports: []corev1.EndpointPort{ { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, + Name: "public", Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, }, }, }, @@ -376,9 +388,10 @@ func TestReconcile_CreateService(t *testing.T) { Addresses: addressesForPods(pod2), Ports: []corev1.EndpointPort{ { - Name: "my-grpc-port", - AppProtocol: &appProtocolGrpc, + Name: "api", Port: 6789, + Protocol: "TCP", + AppProtocol: &appProtocolGrpc, }, }, }, @@ -395,12 +408,14 @@ func TestReconcile_CreateService(t *testing.T) { { Name: "public", Port: 8080, + Protocol: "TCP", TargetPort: intstr.FromString("my-http-port"), AppProtocol: &appProtocolHttp, }, { Name: "api", Port: 9090, + Protocol: "TCP", TargetPort: intstr.FromString("my-grpc-port"), AppProtocol: &appProtocolGrpc, }, @@ -454,6 +469,95 @@ func TestReconcile_CreateService(t *testing.T) { }, }, }, + { + name: "Only L4 TCP ports get a Consul Service port when L4 protocols are multiplexed", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1), + Ports: []corev1.EndpointPort{ + { + Name: "public-tcp", + Port: 2345, + Protocol: "TCP", + }, + { + Name: "public-udp", + Port: 2345, + Protocol: "UDP", + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + // Two L4 protocols on one exposed port + { + Name: "public-tcp", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromString("my-svc-port"), + }, + { + Name: "public-udp", + Port: 8080, + Protocol: "UDP", + TargetPort: intstr.FromString("my-svc-port"), + }, + }, + }, + } + return []runtime.Object{pod1, endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-svc-port", + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + metaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -484,8 +588,9 @@ func TestReconcile_UpdateService(t *testing.T) { Ports: []corev1.EndpointPort{ { Name: "my-http-port", - AppProtocol: &appProtocolHttp, Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, }, }, }, @@ -502,6 +607,7 @@ func TestReconcile_UpdateService(t *testing.T) { { Name: "public", Port: 8080, + Protocol: "TCP", TargetPort: intstr.FromString("my-http-port"), AppProtocol: &appProtocolHttp, }, @@ -613,13 +719,15 @@ func TestReconcile_UpdateService(t *testing.T) { Ports: []corev1.EndpointPort{ { Name: "my-http-port", - AppProtocol: &appProtocolHttp, Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, }, { Name: "my-grpc-port", - AppProtocol: &appProtocolHttp, Port: 6789, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, }, }, }, @@ -636,12 +744,14 @@ func TestReconcile_UpdateService(t *testing.T) { { Name: "public", Port: 8080, + Protocol: "TCP", TargetPort: intstr.FromString("new-http-port"), AppProtocol: &appProtocolHttp2, }, { Name: "api", Port: 9091, + Protocol: "TCP", TargetPort: intstr.FromString("my-grpc-port"), AppProtocol: &appProtocolGrpc, }, From 2d03f3e7c513f3abde003040ebdbc53a8b30858b Mon Sep 17 00:00:00 2001 From: John Maguire Date: Fri, 15 Sep 2023 16:33:15 -0400 Subject: [PATCH 386/592] APIGW NS JWT Auth (#2962) * NET-4978: New CRDs for GW JWT Auth (#2734) * Added CRDs for gateway policy and httproute auth filter * Added bats tests * Correctly configured http route auth filter extension * Small docs update for operator-sdk usage * updated docs a bit, added gateway policy CRD * removed extra crd, updated bats tests * Added changelog * Added periods for consistency * Revert unnecessary changes * make jwt requirement optional * Updated jwt config to be optional to allow for other auth types * Rename HTTPRouteAuthFilter to RouteAuthFilter * Fix typo for omitempty * finish httprouteauthfilters rename to routeauthfilters * Added target reference for gateway policies * Add period to sentence for linter * Rename APIGatewayJWT* fields to GatewayJWT* and fixed spots of renaming of HTTPRouteAuthFilter to RouteAuthFilter * Gateway policy translation NET 4980 (#2835) * squash * reset crd-gatewaypolicies * reset * reset * fix lint issues * fix nil pointer issue * checkpoint * change to resourseref key * update to pull all policies * add nil checks * more nil pointer checks for defensice programing * fix lint issue * delete comment * add unit test, fix add function * Update control-plane/api-gateway/common/translation.go Co-authored-by: Thomas Eckert * Translate HTTPAuthFilter onto HTTPRoute (#2836) * Add function * Add RouteAuthFilterKind export * Add ServicesForRoute function * Start adding translateHTTPRouteAuth * Added translation filter to existing filter processing * Split out formatting into subfunctions * Remove original function * Remove ServicesForRoute * Change httprouteauthfilter to routeauthfilter * Reuse GatewayJWT type for Routes * Match Sarah's style for translation functions * Start adding filter tests * Wrap up test for filters * Uncomment other tests * Use existing v1alpha1 import for group * Remove old make* function * Use ConvertSliceFunc * Fix group in translation_test * Manually un-diff CRDs * cleanup * cleanup * clean up * update index function --------- Co-authored-by: Thomas Eckert * Added validating webhook for gateway policy (#2912) * Added validating webhook for gateway policy * Change denied message to provide more information to the operator * [APIGW] Add comparison of gateway policies to diffing logic (#2939) * Fix bug in comparison of gateway policies * fix fmting * Added gateway equal test * Finished adding tests and refactored to use slices convencience functions * Reconcile Route Auth Filter changes (#2954) * Group indices by resource * Add index for HTTPRoutes referencing RouteAuthFilters * Add watch for HTTPRoutes referencing RouteAuthFilters * Add permissions to connect-inject clusterrole * Compare JWT filters for equality * Add RouteAuthFilter to resource translator * [NET-5017] APIGW Status Conditions for Gateway for JWT/Reconcile on JWTProvider Changes (#2950) * Added watches and status condition on gateway listeners for JWT validation * Only append errors if they're non-nil * Added tests for validating jwt on listener and for adding/retrieving jwt from resource map * fix fmting * Clean up from PR review * Use two value form of map access * Rename function * clean up from PR review * [NET-5017] APIGW Status Conditions for Gateway Policies (#2955) * Adding status conditions for gw policy * Fixed issue where status was not being propagated for policies * Moved code to correct places * Revert formatting * Cleaned up error creation, added validation tests * Added results tests, updated binding test * Updates from PR review: clean up comments/appends, use correct conditions for defaults * [NET-5017] APIGW Status Conditions for RouteAuthFilter and Routes wrt JWT (#2961) * NET-4978: New CRDs for GW JWT Auth (#2734) * Added CRDs for gateway policy and httproute auth filter * Added bats tests * Correctly configured http route auth filter extension * Small docs update for operator-sdk usage * updated docs a bit, added gateway policy CRD * removed extra crd, updated bats tests * Added changelog * Added periods for consistency * Revert unnecessary changes * make jwt requirement optional * Updated jwt config to be optional to allow for other auth types * Rename HTTPRouteAuthFilter to RouteAuthFilter * Fix typo for omitempty * finish httprouteauthfilters rename to routeauthfilters * Added target reference for gateway policies * Add period to sentence for linter * Rename APIGatewayJWT* fields to GatewayJWT* and fixed spots of renaming of HTTPRouteAuthFilter to RouteAuthFilter * Gateway policy translation NET 4980 (#2835) * squash * reset crd-gatewaypolicies * reset * reset * fix lint issues * fix nil pointer issue * checkpoint * change to resourseref key * update to pull all policies * add nil checks * more nil pointer checks for defensice programing * fix lint issue * delete comment * add unit test, fix add function * Update control-plane/api-gateway/common/translation.go Co-authored-by: Thomas Eckert * Translate HTTPAuthFilter onto HTTPRoute (#2836) * Add function * Add RouteAuthFilterKind export * Add ServicesForRoute function * Start adding translateHTTPRouteAuth * Added translation filter to existing filter processing * Split out formatting into subfunctions * Remove original function * Remove ServicesForRoute * Change httprouteauthfilter to routeauthfilter * Reuse GatewayJWT type for Routes * Match Sarah's style for translation functions * Start adding filter tests * Wrap up test for filters * Uncomment other tests * Use existing v1alpha1 import for group * Remove old make* function * Use ConvertSliceFunc * Fix group in translation_test * Manually un-diff CRDs * cleanup * cleanup * clean up * update index function --------- Co-authored-by: Thomas Eckert * Added status conditions for JWT for auth filters and for routes * Extract function * Use more generic error for invalid filter * Re-run ctrl-manifests with correct controller-generate version * Clean up from pr review * gofmt --------- Co-authored-by: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Co-authored-by: Thomas Eckert * Added changelog * clean up some renames from httprouteauthfilter -> routeauthfilter * Fix broken webhook test, added new test --------- Co-authored-by: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Co-authored-by: Thomas Eckert --- .changelog/2962.txt | 3 + CONTRIBUTING.md | 88 +- .../templates/connect-inject-clusterrole.yaml | 6 + ...inject-validatingwebhookconfiguration.yaml | 31 + .../consul/templates/crd-gatewaypolicies.yaml | 301 +++ .../templates/crd-routeauthfilters.yaml | 207 ++ .../webhook-cert-manager-clusterrole.yaml | 1 + .../consul/test/unit/crd-gatewaypolicies.bats | 20 + .../test/unit/crd-routeauthfilters.bats | 20 + .../telemetry-collector-v2-deployment.bats | 2 +- control-plane/PROJECT | 16 + control-plane/api-gateway/binding/binder.go | 45 +- .../api-gateway/binding/binder_test.go | 13 +- control-plane/api-gateway/binding/result.go | 230 +- .../api-gateway/binding/result_test.go | 178 ++ .../api-gateway/binding/route_binding.go | 17 +- .../api-gateway/binding/validation.go | 179 +- .../api-gateway/binding/validation_test.go | 717 +++++- control-plane/api-gateway/cache/consul.go | 25 +- .../api-gateway/cache/consul_test.go | 38 +- control-plane/api-gateway/common/diff.go | 93 +- control-plane/api-gateway/common/diff_test.go | 2155 +++++++++++++++++ control-plane/api-gateway/common/helpers.go | 2 +- control-plane/api-gateway/common/resources.go | 75 +- .../api-gateway/common/resources_test.go | 57 + .../api-gateway/common/translation.go | 137 +- .../api-gateway/common/translation_test.go | 231 +- .../controllers/gateway_controller.go | 140 +- .../api-gateway/controllers/index.go | 69 +- control-plane/api/common/common.go | 2 + .../api/v1alpha1/gatewaypolicy_types.go | 135 ++ .../api/v1alpha1/gatewaypolicy_webhook.go | 82 + .../v1alpha1/gatewaypolicy_webhook_test.go | 282 +++ .../api/v1alpha1/routeauthfilter_types.go | 65 + .../api/v1alpha1/zz_generated.deepcopy.go | 321 +++ .../consul.hashicorp.com_gatewaypolicies.yaml | 296 +++ ...consul.hashicorp.com_routeauthfilters.yaml | 196 ++ control-plane/config/webhook/manifests.yaml | 28 + control-plane/go.mod | 4 +- control-plane/go.sum | 9 +- .../mutating_webhook_configuration.go | 54 - .../webhook_configuration.go | 98 + .../webhook_configuration_test.go} | 26 +- .../inject-connect/v1controllers.go | 13 +- .../webhook-cert-manager/command.go | 43 +- .../webhook-cert-manager/command_test.go | 7 +- 46 files changed, 6513 insertions(+), 244 deletions(-) create mode 100644 .changelog/2962.txt create mode 100644 charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml create mode 100644 charts/consul/templates/crd-gatewaypolicies.yaml create mode 100644 charts/consul/templates/crd-routeauthfilters.yaml create mode 100644 charts/consul/test/unit/crd-gatewaypolicies.bats create mode 100644 charts/consul/test/unit/crd-routeauthfilters.bats create mode 100644 control-plane/api-gateway/common/diff_test.go create mode 100644 control-plane/api-gateway/common/resources_test.go create mode 100644 control-plane/api/v1alpha1/gatewaypolicy_types.go create mode 100644 control-plane/api/v1alpha1/gatewaypolicy_webhook.go create mode 100644 control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go create mode 100644 control-plane/api/v1alpha1/routeauthfilter_types.go create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml create mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml delete mode 100644 control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go create mode 100644 control-plane/helper/webhook-configuration/webhook_configuration.go rename control-plane/helper/{mutating-webhook-configuration/mutating_webhook_configuration_test.go => webhook-configuration/webhook_configuration_test.go} (60%) diff --git a/.changelog/2962.txt b/.changelog/2962.txt new file mode 100644 index 0000000000..f707e4828f --- /dev/null +++ b/.changelog/2962.txt @@ -0,0 +1,3 @@ +```releast-note:feature +api-gateway: (Consul Enterprise) Add JWT authentication and authorization for API Gateway and HTTPRoutes. +``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4eca76a1dd..5b06c27d8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ 1. [Running linters locally](#running-linters-locally) 1. [Rebasing contributions against main](#rebasing-contributions-against-main) 1. [Creating a new CRD](#creating-a-new-crd) - 1. [The Structs](#the-structs) + 1. [The Structs](#the-structs) 1. [Spec Methods](#spec-methods) 1. [Spec Tests](#spec-tests) 1. [Controller](#controller) @@ -31,13 +31,13 @@ ### Building and running `consul-k8s-control-plane` -To build and install the control plane binary `consul-k8s-control-plane` locally, Go version 1.17.0+ is required. +To build and install the control plane binary `consul-k8s-control-plane` locally, Go version 1.17.0+ is required. You will also need to install the Docker engine: - [Docker for Mac](https://docs.docker.com/engine/installation/mac/) - [Docker for Windows](https://docs.docker.com/engine/installation/windows/) - [Docker for Linux](https://docs.docker.com/engine/installation/linux/ubuntulinux/) - + Install [gox](https://github.com/mitchellh/gox) (v1.14+). For Mac and Linux: ```bash brew install gox @@ -102,7 +102,7 @@ controller: enabled: true ``` -Run a `helm install` from the project root directory to target your dev version of the Helm chart. +Run a `helm install` from the project root directory to target your dev version of the Helm chart. ```shell helm install consul --create-namespace -n consul -f ./values.dev.yaml ./charts/consul @@ -125,7 +125,7 @@ consul-k8s version ### Making changes to consul-k8s -The first step to making changes is to fork Consul K8s. Afterwards, the easiest way +The first step to making changes is to fork Consul K8s. Afterwards, the easiest way to work on the fork is to set it as a remote of the Consul K8s project: 1. Rename the existing remote's name: `git remote rename origin upstream`. @@ -164,7 +164,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ## Creating a new CRD ### The Structs -1. Run the generate command: +1. Run the generate command from the `control-plane` directory: (installation instructions for `operator-sdk` found [here](https://sdk.operatorframework.io/docs/installation/): ```bash operator-sdk create api --group consul --version v1alpha1 --kind IngressGateway --controller --namespaced=true --make=false --resource=true ``` @@ -173,37 +173,37 @@ rebase the branch on main, fixing any conflicts along the way before the code ca func init() { SchemeBuilder.Register(&IngressGateway{}, &IngressGatewayList{}) } - + // +kubebuilder:object:root=true // +kubebuilder:subresource:status - + // IngressGateway is the Schema for the ingressgateways API type IngressGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - + Spec IngressGatewaySpec `json:"spec,omitempty"` Status IngressGatewayStatus `json:"status,omitempty"` } - + // +kubebuilder:object:root=true - + // IngressGatewayList contains a list of IngressGateway type IngressGatewayList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []IngressGateway `json:"items"` } - + // IngressGatewaySpec defines the desired state of IngressGateway type IngressGatewaySpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - + // Foo is an example field of IngressGateway. Edit IngressGateway_types.go to remove/update Foo string `json:"foo,omitempty"` } - + // IngressGatewayStatus defines the observed state of IngressGateway type IngressGatewayStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster @@ -225,7 +225,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca type IngressGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - + Spec IngressGatewaySpec `json:"spec,omitempty"` - Status IngressGatewayStatus `json:"status,omitempty"` + Status `json:"status,omitempty"` @@ -235,7 +235,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca 1. Copy the top-level fields over into the `Spec` struct except for `Kind`, `Name`, `Namespace`, `Partition`, `Meta`, `CreateIndex` and `ModifyIndex`. In this example, the top-level fields remaining are `TLS` and `Listeners`: - + ```go // IngressGatewaySpec defines the desired state of IngressGateway type IngressGatewaySpec struct { @@ -261,8 +261,8 @@ rebase the branch on main, fixing any conflicts along the way before the code ca automatically stub out all the methods by using Code -> Generate -> IngressGateway -> ConfigEntryResource. 1. Use existing implementations of other types to implement the methods. We have to copy their code because we can't use a common struct that implements the methods - because that messes up the CRD code generation. - + because that messes up the CRD code generation. + You should be able to follow the other "normal" types. The non-normal types are `ServiceIntention` and `ProxyDefault` because they have special behaviour around being global or their spec not matching up with Consul's directly. @@ -273,7 +273,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca 1. For `Validate`, we again follow the pattern of implementing the method on each sub-struct. You'll need to read the Consul documentation to understand what validation needs to be done. - + Things to keep in mind: 1. Re-use the `sliceContains` and `notInSliceMessage` helper methods where applicable. 1. If the invalid field is an entire struct, encode as json (look for `asJSON` for an example). @@ -333,7 +333,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca 1. `TestConfigEntryControllers_doesNotCreateUnownedConfigEntry` 1. `TestConfigEntryControllers_doesNotDeleteUnownedConfig` 1. Note: we don't add tests to `configentry_controller_ent_test.go` because we decided - it's too much duplication and the controllers are already properly exercised in the oss tests. + it's too much duplication and the controllers are already properly exercised in the oss tests. ### Webhook 1. Copy an existing webhook to `control-plane/api/v1alpha/ingressgateway_webhook.go` @@ -507,7 +507,7 @@ a token named `foo`. ``` * Add `if` statement in `Run` to create your token (follow placement of other tokens). You'll need to decide if you need a local token (use `createLocalACL()`) or a global token (use `createGlobalACL()`). - + ```go if c.flagCreateFooToken { err := c.createLocalACL("foo", fooRules, consulDC, isPrimary, consulClient) @@ -588,7 +588,7 @@ The acceptance tests require a Kubernetes cluster with a configured `kubectl`. ```bash brew install python-yq ``` -* [Helm 3](https://helm.sh) (Currently, must use v3.8.0+.) +* [Helm 3](https://helm.sh) (Currently, must use v3.8.0+.) ```bash brew install kubernetes-helm ``` @@ -617,7 +617,7 @@ To run a specific test by name use the `--filter` flag: bats ./charts/consul/test/unit/.bats --filter "my test name" #### Acceptance Tests -##### Pre-requisites +##### Pre-requisites * [gox](https://github.com/mitchellh/gox) (v1.14+) ```bash brew install gox @@ -629,7 +629,7 @@ To run the acceptance tests: cd acceptance/tests go test ./... -p 1 - + The above command will run all tests that can run against a single Kubernetes cluster, using the current context set in your kubeconfig locally. @@ -689,7 +689,7 @@ Changes to the Helm chart should be accompanied by appropriate unit tests. #### Formatting -- Put tests in the test file in the same order as the variables appear in the `values.yaml`. +- Put tests in the test file in the same order as the variables appear in the `values.yaml`. - Start tests for a chart value with a header that says what is being tested, like this: ``` #-------------------------------------------------------------------- @@ -710,8 +710,8 @@ In all of the tests in this repo, the base command being run is [helm template]( In this way, we're able to test that the various conditionals in the templates render as we would expect. Each test defines the files that should be rendered using the `-x` flag, then it might adjust chart values by adding `--set` flags as well. -The output from this `helm template` command is then piped to [yq](https://pypi.org/project/yq/). -`yq` allows us to pull out just the information we're interested in, either by referencing its position in the yaml file directly or giving information about it (like its length). +The output from this `helm template` command is then piped to [yq](https://pypi.org/project/yq/). +`yq` allows us to pull out just the information we're interested in, either by referencing its position in the yaml file directly or giving information about it (like its length). The `-r` flag can be used with `yq` to return a raw string instead of a quoted one which is especially useful when looking for an exact match. The test passes or fails based on the conditional at the end that is in square brackets, which is a comparison of our expected value and the output of `helm template` piped to `yq`. @@ -786,11 +786,11 @@ Here are some examples of common test patterns: cd `chart_dir` assert_empty helm template \ -s templates/sync-catalog-deployment.yaml \ - . + . } ``` Here we are using the `assert_empty` helper command. - + ### Writing Acceptance Tests If you are adding a feature that fits thematically with one of the existing test suites, @@ -831,9 +831,9 @@ you need to handle that in the `TestMain` function. ```go func TestMain(m *testing.M) { - // First, create a new suite so that all flags are parsed. + // First, create a new suite so that all flags are parsed. suite = framework.NewSuite(m) - + // Run the suite only if our example feature test flag is set. if suite.Config().EnableExampleFeature { os.Exit(suite.Run()) @@ -866,16 +866,16 @@ func TestExample(t *testing.T) { helmValues := map[string]string{ "exampleFeature.enabled": "true", } - - // Generate a random name for this test. + + // Generate a random name for this test. releaseName := helpers.RandomName() // Create a new Consul cluster object. consulCluster := framework.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - + // Create the Consul cluster with Helm. consulCluster.Create(t) - + // Make test assertions. } ``` @@ -981,7 +981,7 @@ Any given test can be run either through GoLand or another IDE, or via command l To run all of the connect tests from command line: ```shell $ cd acceptance/tests -$ go test ./connect/... -v -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-enterprise -enable-multi-cluster -debug-directory=/tmp/debug -consul-k8s-image=kyleschochenmaier/consul-k8s-acls +$ go test ./connect/... -v -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-enterprise -enable-multi-cluster -debug-directory=/tmp/debug -consul-k8s-image=kyleschochenmaier/consul-k8s-acls ``` When running from command line a few things are important: @@ -1129,17 +1129,17 @@ Certificate: X509v3 Subject Alternative Name: DNS:pri-1dchdli.vault.ca.34a76791.consul, URI:spiffe://34a76791-b9b2-b93e-b0e4-1989ed11a28e.consul -``` +``` --- ## Helm Reference Docs - + The Helm reference docs (https://www.consul.io/docs/k8s/helm) are automatically generated from our `values.yaml` file. ### Generating Helm Reference Docs - + To generate the docs and update the `helm.mdx` file: 1. Fork `hashicorp/consul` (https://github.com/hashicorp/consul) on GitHub. @@ -1147,7 +1147,7 @@ To generate the docs and update the `helm.mdx` file: ```shell-session git clone https://github.com//consul.git ``` -1. Change directory into your `consul-k8s` repo: +1. Change directory into your `consul-k8s` repo: ```shell-session cd /path/to/consul-k8s ``` @@ -1216,11 +1216,11 @@ manage. One such example is the Gateway API CRDs which we use to configure API G Networking. To pull external CRDs into our Helm chart and make sure they get installed, we generate their configuration using -[Kustomize](https://kustomize.io/) which can pull in Kubernetes config from external sources. We split these +[Kustomize](https://kustomize.io/) which can pull in Kubernetes config from external sources. We split these generated CRDs into individual files and store them in the `charts/consul/templates` directory. -If you need to update the external CRDs we depend on, or add to them, you can do this by editing the -[control-plane/config/crd/external/kustomization.yaml](/control-plane/config/crd/external/kustomization.yaml) file. +If you need to update the external CRDs we depend on, or add to them, you can do this by editing the +[control-plane/config/crd/external/kustomization.yaml](/control-plane/config/crd/external/kustomization.yaml) file. Once modified, running ```bash @@ -1261,7 +1261,7 @@ Some common values are: - `control-plane`: related to control-plane functionality - `helm`: related to the charts module and any files, yaml, go, etc. therein -There may be cases where a `code area` doesn't make sense (i.e. addressing a Go CVE). In these +There may be cases where a `code area` doesn't make sense (i.e. addressing a Go CVE). In these cases it is okay not to provide a `code area`. For more examples, look in the [`.changelog/`](../.changelog) folder for existing changelog entries. diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index c95b7c143a..af82ba2775 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -30,11 +30,14 @@ rules: - controlplanerequestlimits - routeretryfilters - routetimeoutfilters + - routeauthfilters + - gatewaypolicies {{- if .Values.global.peering.enabled }} - peeringacceptors - peeringdialers {{- end }} - jwtproviders + - routeauthfilters verbs: - create - delete @@ -63,6 +66,8 @@ rules: - peeringdialers/status {{- end }} - jwtproviders/status + - routeauthfilters/status + - gatewaypolicies/status verbs: - get - patch @@ -107,6 +112,7 @@ rules: - admissionregistration.k8s.io resources: - mutatingwebhookconfigurations + - validatingwebhookconfigurations verbs: - get - list diff --git a/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml new file mode 100644 index 0000000000..8d01ace911 --- /dev/null +++ b/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml @@ -0,0 +1,31 @@ +{{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} +# The ValidatingWebhookConfiguration to enable the Connect injector. +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: {{ template "consul.fullname" . }}-connect-injector + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: connect-injector +webhooks: +- name: validate-gatewaypolicy.consul.hashicorp.com + matchPolicy: Equivalent + rules: + - operations: [ "CREATE" , "UPDATE" ] + apiGroups: [ "consul.hashicorp.com" ] + apiVersions: [ "v1alpha1" ] + resources: [ "gatewaypolicies" ] + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ template "consul.fullname" . }}-connect-injector + namespace: {{ .Release.Namespace }} + path: /validate-v1alpha1-gatewaypolicy +{{- end }} diff --git a/charts/consul/templates/crd-gatewaypolicies.yaml b/charts/consul/templates/crd-gatewaypolicies.yaml new file mode 100644 index 0000000000..93ad655917 --- /dev/null +++ b/charts/consul/templates/crd-gatewaypolicies.yaml @@ -0,0 +1,301 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: gatewaypolicies.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: consul.hashicorp.com + names: + kind: GatewayPolicy + listKind: GatewayPolicyList + plural: gatewaypolicies + singular: gatewaypolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: GatewayPolicy is the Schema for the gatewaypolicies API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GatewayPolicySpec defines the desired state of GatewayPolicy. + properties: + default: + properties: + jwt: + description: GatewayJWTRequirement holds the list of JWT providers + to be verified against. + properties: + providers: + description: Providers is a list of providers to consider + when verifying a JWT. + items: + description: GatewayJWTProvider holds the provider and claim + verification information. + properties: + name: + description: Name is the name of the JWT provider. There + MUST be a corresponding "jwt-provider" config entry + with this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional claims + to verify in a JWT's payload. + items: + description: GatewayJWTClaimVerification holds the + actual claim information to be verified. + properties: + path: + description: Path is the path to the claim in + the token JSON. + items: + type: string + type: array + value: + description: "Value is the expected value at the + given path: - If the type at the path is a list + then we verify that this value is contained + in the list. \n - If the type at the path is + a string then we verify that this value matches." + type: string + required: + - path + - value + type: object + type: array + required: + - name + type: object + type: array + required: + - providers + type: object + type: object + override: + properties: + jwt: + description: GatewayJWTRequirement holds the list of JWT providers + to be verified against. + properties: + providers: + description: Providers is a list of providers to consider + when verifying a JWT. + items: + description: GatewayJWTProvider holds the provider and claim + verification information. + properties: + name: + description: Name is the name of the JWT provider. There + MUST be a corresponding "jwt-provider" config entry + with this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional claims + to verify in a JWT's payload. + items: + description: GatewayJWTClaimVerification holds the + actual claim information to be verified. + properties: + path: + description: Path is the path to the claim in + the token JSON. + items: + type: string + type: array + value: + description: "Value is the expected value at the + given path: - If the type at the path is a list + then we verify that this value is contained + in the list. \n - If the type at the path is + a string then we verify that this value matches." + type: string + required: + - path + - value + type: object + type: array + required: + - name + type: object + type: array + required: + - providers + type: object + type: object + targetRef: + description: TargetRef identifies an API object to apply policy to. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. When + unspecified, the local namespace is inferred. Even when policy + targets a resource in a different namespace, it may only apply + to traffic originating from the same namespace as the policy. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: SectionName refers to the listener targeted by this + policy. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + required: + - targetRef + type: object + status: + description: GatewayPolicyStatus defines the observed state of the gateway. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: ResolvedRefs + description: "Conditions describe the current conditions of the Policy. + \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/crd-routeauthfilters.yaml b/charts/consul/templates/crd-routeauthfilters.yaml new file mode 100644 index 0000000000..1a19d1f3ec --- /dev/null +++ b/charts/consul/templates/crd-routeauthfilters.yaml @@ -0,0 +1,207 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: routeauthfilters.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: consul.hashicorp.com + names: + kind: RouteAuthFilter + listKind: RouteAuthFilterList + plural: routeauthfilters + singular: routeauthfilter + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: RouteAuthFilter is the Schema for the httpauthfilters API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RouteAuthFilterSpec defines the desired state of RouteAuthFilter. + properties: + jwt: + description: This re-uses the JWT requirement type from Gateway Policy + Types. + properties: + providers: + description: Providers is a list of providers to consider when + verifying a JWT. + items: + description: GatewayJWTProvider holds the provider and claim + verification information. + properties: + name: + description: Name is the name of the JWT provider. There + MUST be a corresponding "jwt-provider" config entry with + this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional claims + to verify in a JWT's payload. + items: + description: GatewayJWTClaimVerification holds the actual + claim information to be verified. + properties: + path: + description: Path is the path to the claim in the + token JSON. + items: + type: string + type: array + value: + description: "Value is the expected value at the given + path: - If the type at the path is a list then we + verify that this value is contained in the list. + \n - If the type at the path is a string then we + verify that this value matches." + type: string + required: + - path + - value + type: object + type: array + required: + - name + type: object + type: array + required: + - providers + type: object + type: object + status: + description: RouteAuthFilterStatus defines the observed state of the gateway. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: ResolvedRefs + description: "Conditions describe the current conditions of the Filter. + \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml index e13e2dc741..2a5c80d94c 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml @@ -27,6 +27,7 @@ rules: - admissionregistration.k8s.io resources: - mutatingwebhookconfigurations + - validatingwebhookconfigurations verbs: - get - list diff --git a/charts/consul/test/unit/crd-gatewaypolicies.bats b/charts/consul/test/unit/crd-gatewaypolicies.bats new file mode 100644 index 0000000000..2a40a8182e --- /dev/null +++ b/charts/consul/test/unit/crd-gatewaypolicies.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats + +load _helpers + +@test "gatewaypolicies/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-gatewaypolicies.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "gatewaypolicies/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-gatewaypolicies.yaml \ + --set 'connectInject.enabled=false' \ + . +} diff --git a/charts/consul/test/unit/crd-routeauthfilters.bats b/charts/consul/test/unit/crd-routeauthfilters.bats new file mode 100644 index 0000000000..d4af62dd5c --- /dev/null +++ b/charts/consul/test/unit/crd-routeauthfilters.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats + +load _helpers + +@test "routeauth-filters/CustomResourceDefinition: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-routeauthfilters.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "$actual" = "true" ] +} + +@test "routeauth-filter/CustomResourceDefinition: disabled with connectInject.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-routeauthfilters.yaml \ + --set 'connectInject.enabled=false' \ + . +} diff --git a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats index 87982abf4e..f1d5de2597 100755 --- a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats @@ -1347,4 +1347,4 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ --set 'telemetryCollector.enabled=true' \ --set 'telemetryCollector.image=bar' \ . -} \ No newline at end of file +} diff --git a/control-plane/PROJECT b/control-plane/PROJECT index 5a4e24d0f8..7726ec3e4b 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -126,4 +126,20 @@ resources: kind: RouteTimeoutFilter path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + domain: hashicorp.com + group: consul + kind: RouteAuthFilter + path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + domain: hashicorp.com + group: consul + kind: GatewayPolicy + path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index 7798a6b49c..071f01a16f 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -6,14 +6,15 @@ package binding import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" 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" ) // BinderConfig configures a binder instance with all of the information @@ -52,6 +53,8 @@ type BinderConfig struct { Pods []corev1.Pod // Service is the deployed service associated with the Gateway deployment. Service *corev1.Service + // JWTProviders is the list of all JWTProviders in the cluster + JWTProviders []v1alpha1.JWTProvider // ConsulGateway is the config entry we've created in Consul. ConsulGateway *api.APIGatewayConfigEntry @@ -61,6 +64,9 @@ type BinderConfig struct { // Resources is a map containing all service targets to verify // against the routing backends. Resources *common.ResourceMap + + // Policies is a list containing all GatewayPolicies that are part of the Gateway Deployment + Policies []v1alpha1.GatewayPolicy } // Binder is used for generating a Snapshot of all operations that should occur both @@ -116,7 +122,10 @@ func (b *Binder) Snapshot() *Snapshot { var gatewayValidation gatewayValidationResult var listenerValidation listenerValidationResults + var policyValidation gatewayPolicyValidationResults + var authFilterValidation authFilterValidationResults + authFilters := b.config.Resources.GetExternalAuthFilters() if !isGatewayDeleted { var updated bool @@ -133,6 +142,8 @@ func (b *Binder) Snapshot() *Snapshot { // calculate the status for the gateway gatewayValidation = validateGateway(b.config.Gateway, registrationPods, b.config.ConsulGateway) listenerValidation = validateListeners(b.config.Gateway, b.config.Gateway.Spec.Listeners, b.config.Resources, b.config.GatewayClassConfig) + policyValidation = validateGatewayPolicies(b.config.Gateway, b.config.Policies, b.config.Resources) + authFilterValidation = validateAuthFilters(authFilters, b.config.Resources) } // used for tracking how many routes have successfully bound to which listeners @@ -235,6 +246,36 @@ func (b *Binder) Snapshot() *Snapshot { b.config.Gateway.Status = status snapshot.Kubernetes.StatusUpdates.Add(&b.config.Gateway) } + + for idx, policy := range b.config.Policies { + policy := policy + + var policyStatus v1alpha1.GatewayPolicyStatus + + policyStatus.Conditions = policyValidation.Conditions(policy.Generation, idx) + // only mark the policy as needing a status update if there's a diff with its old status + if !common.GatewayPolicyStatusesEqual(policyStatus, policy.Status) { + b.config.Policies[idx].Status = policyStatus + snapshot.Kubernetes.StatusUpdates.Add(&b.config.Policies[idx]) + } + } + + for idx, authFilter := range authFilters { + if authFilter == nil { + continue + } + authFilter := authFilter + + var filterStatus v1alpha1.RouteAuthFilterStatus + + filterStatus.Conditions = authFilterValidation.Conditions(authFilter.Generation, idx) + + // only mark the filter as needing a status update if there's a diff with its old status + if !common.RouteAuthFilterStatusesEqual(filterStatus, authFilter.Status) { + authFilter.Status = filterStatus + snapshot.Kubernetes.StatusUpdates.Add(authFilter) + } + } } else { // if the gateway has been deleted, unset whatever we've set on it snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, b.nonNormalizedConsulKey) diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index 7366d1a164..cb13c1ba04 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -23,9 +23,10 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" ) func init() { @@ -61,6 +62,8 @@ type resourceMapResources struct { tcpRoutes []gwv1alpha2.TCPRoute meshServices []v1alpha1.MeshService services []types.NamespacedName + jwtProviders []*v1alpha1.JWTProvider + gatewayPolicies []*v1alpha1.GatewayPolicy consulInlineCertificates []api.InlineCertificateConfigEntry consulHTTPRoutes []api.HTTPRouteConfigEntry consulTCPRoutes []api.TCPRouteConfigEntry @@ -93,6 +96,12 @@ func newTestResourceMap(t *testing.T, resources resourceMapResources) *common.Re for _, r := range resources.consulTCPRoutes { resourceMap.ReferenceCountConsulTCPRoute(r) } + for _, r := range resources.gatewayPolicies { + resourceMap.AddGatewayPolicy(r) + } + for _, r := range resources.jwtProviders { + resourceMap.AddJWTProvider(r) + } return resourceMap } @@ -247,7 +256,7 @@ func TestBinder_Lifecycle(t *testing.T) { Type: "ResolvedRefs", Status: metav1.ConditionTrue, Reason: "ResolvedRefs", - Message: "resolved certificate references", + Message: "resolved references", }, }, }}, diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index 0ba4a257fd..93999ca782 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -36,6 +36,7 @@ var ( errRouteNoMatchingParent = errors.New("no matching parent") errInvalidExternalRefType = errors.New("invalid externalref filter kind") errExternalRefNotFound = errors.New("ref not found") + errFilterInvalid = errors.New("filter invalid") ) // routeValidationResult holds the result of validating a route globally, in other @@ -189,6 +190,8 @@ func (b bindResults) Condition() metav1.Condition { reason = "NoMatchingParent" case errors.Is(result.err, errExternalRefNotFound): reason = "FilterNotFound" + case errors.Is(result.err, errFilterInvalid): + reason = "JWTProviderNotFound" case errors.Is(result.err, errInvalidExternalRefType): reason = "UnsupportedValue" } @@ -240,6 +243,7 @@ var ( errListenerInvalidCertificateRef_InvalidData = errors.New("certificate is invalid or does not contain a supported server name") errListenerInvalidCertificateRef_NonFIPSRSAKeyLen = errors.New("certificate has an invalid length: RSA Keys must be at least 2048-bit") errListenerInvalidCertificateRef_FIPSRSAKeyLen = errors.New("certificate has an invalid length: RSA keys must be either 2048-bit, 3072-bit, or 4096-bit in FIPS mode") + errListenerJWTProviderNotFound = errors.New("policy referencing this listener references unknown JWT provider") errListenerInvalidRouteKinds = errors.New("allowed route kind is invalid") errListenerProgrammed_Invalid = errors.New("listener cannot be programmed because it is invalid") @@ -269,7 +273,7 @@ type listenerValidationResult struct { // status type: Conflicted conflictedErr error // status type: ResolvedRefs - refErr error + refErrs []error // status type: ResolvedRefs (but with internal validation) routeKindErr error } @@ -281,7 +285,7 @@ func (l listenerValidationResult) programmedCondition(generation int64) metav1.C now := timeFunc() switch { - case l.acceptedErr != nil, l.conflictedErr != nil, l.refErr != nil, l.routeKindErr != nil: + case l.acceptedErr != nil, l.conflictedErr != nil, len(l.refErrs) != 0, l.routeKindErr != nil: return metav1.Condition{ Type: "Programmed", Status: metav1.ConditionFalse, @@ -382,59 +386,78 @@ func (l listenerValidationResult) conflictedCondition(generation int64) metav1.C } // acceptedCondition constructs the condition for the ResolvedRefs status type. -func (l listenerValidationResult) resolvedRefsCondition(generation int64) metav1.Condition { +func (l listenerValidationResult) resolvedRefsConditions(generation int64) []metav1.Condition { now := timeFunc() + conditions := make([]metav1.Condition, 0) + if l.routeKindErr != nil { - return metav1.Condition{ + return []metav1.Condition{{ Type: "ResolvedRefs", Status: metav1.ConditionFalse, Reason: "InvalidRouteKinds", ObservedGeneration: generation, Message: l.routeKindErr.Error(), LastTransitionTime: now, - } + }} } - switch l.refErr { - case errListenerInvalidCertificateRef_NotFound, errListenerInvalidCertificateRef_NotSupported, errListenerInvalidCertificateRef_InvalidData, errListenerInvalidCertificateRef_NonFIPSRSAKeyLen, errListenerInvalidCertificateRef_FIPSRSAKeyLen: - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidCertificateRef", - ObservedGeneration: generation, - Message: l.refErr.Error(), - LastTransitionTime: now, - } - case errRefNotPermitted: - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "RefNotPermitted", - ObservedGeneration: generation, - Message: l.refErr.Error(), - LastTransitionTime: now, + for _, refErr := range l.refErrs { + switch refErr { + case errListenerInvalidCertificateRef_NotFound, + errListenerInvalidCertificateRef_NotSupported, + errListenerInvalidCertificateRef_InvalidData, + errListenerInvalidCertificateRef_NonFIPSRSAKeyLen, + errListenerInvalidCertificateRef_FIPSRSAKeyLen: + conditions = append(conditions, metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidCertificateRef", + ObservedGeneration: generation, + Message: refErr.Error(), + LastTransitionTime: now, + }) + case errListenerJWTProviderNotFound: + conditions = append(conditions, metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidJWTProviderRef", + ObservedGeneration: generation, + Message: refErr.Error(), + LastTransitionTime: now, + }) + case errRefNotPermitted: + conditions = append(conditions, metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "RefNotPermitted", + ObservedGeneration: generation, + Message: refErr.Error(), + LastTransitionTime: now, + }) } - default: - return metav1.Condition{ + } + if len(conditions) == 0 { + conditions = append(conditions, metav1.Condition{ Type: "ResolvedRefs", Status: metav1.ConditionTrue, Reason: "ResolvedRefs", ObservedGeneration: generation, - Message: "resolved certificate references", + Message: "resolved references", LastTransitionTime: now, - } + }) } + return conditions } // Conditions constructs the entire set of conditions for a given gateway listener. func (l listenerValidationResult) Conditions(generation int64) []metav1.Condition { - return []metav1.Condition{ + conditions := []metav1.Condition{ l.acceptedCondition(generation), l.programmedCondition(generation), l.conflictedCondition(generation), - l.resolvedRefsCondition(generation), } + return append(conditions, l.resolvedRefsConditions(generation)...) } // listenerValidationResults holds all of the results for a gateway's listeners @@ -562,3 +585,152 @@ func (l gatewayValidationResult) Conditions(generation int64, listenersInvalid b l.programmedCondition(generation), } } + +type gatewayPolicyValidationResult struct { + acceptedErr error + resolvedRefsErrs []error +} + +type gatewayPolicyValidationResults []gatewayPolicyValidationResult + +var ( + errPolicyListenerReferenceDoesNotExist = errors.New("gateway policy references a listener that does not exist") + errPolicyJWTProvidersReferenceDoesNotExist = errors.New("gateway policy references one or more jwt providers that do not exist") + errNotAcceptedDueToInvalidRefs = errors.New("policy is not accepted due to errors with references") +) + +func (g gatewayPolicyValidationResults) Conditions(generation int64, idx int) []metav1.Condition { + result := g[idx] + return result.Conditions(generation) +} + +func (g gatewayPolicyValidationResult) Conditions(generation int64) []metav1.Condition { + return append([]metav1.Condition{g.acceptedCondition(generation)}, g.resolvedRefsConditions(generation)...) +} + +func (g gatewayPolicyValidationResult) acceptedCondition(generation int64) metav1.Condition { + now := timeFunc() + if g.acceptedErr != nil { + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "ReferencesNotValid", + ObservedGeneration: generation, + Message: g.acceptedErr.Error(), + LastTransitionTime: now, + } + } + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + ObservedGeneration: generation, + Message: "gateway policy accepted", + LastTransitionTime: now, + } +} + +func (g gatewayPolicyValidationResult) resolvedRefsConditions(generation int64) []metav1.Condition { + now := timeFunc() + if len(g.resolvedRefsErrs) == 0 { + return []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + ObservedGeneration: generation, + Message: "resolved references", + LastTransitionTime: now, + }, + } + } + + conditions := make([]metav1.Condition, 0, len(g.resolvedRefsErrs)) + for _, err := range g.resolvedRefsErrs { + switch { + case errors.Is(err, errPolicyListenerReferenceDoesNotExist): + conditions = append(conditions, metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "MissingListenerReference", + ObservedGeneration: generation, + Message: err.Error(), + LastTransitionTime: now, + }) + case errors.Is(err, errPolicyJWTProvidersReferenceDoesNotExist): + conditions = append(conditions, metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "MissingJWTProviderReference", + ObservedGeneration: generation, + Message: err.Error(), + LastTransitionTime: now, + }) + } + } + return conditions +} + +type authFilterValidationResults []authFilterValidationResult + +type authFilterValidationResult struct { + acceptedErr error + resolvedRefErr error +} + +func (g authFilterValidationResults) Conditions(generation int64, idx int) []metav1.Condition { + result := g[idx] + return result.Conditions(generation) +} + +func (g authFilterValidationResult) Conditions(generation int64) []metav1.Condition { + return []metav1.Condition{ + g.acceptedCondition(generation), + g.resolvedRefsCondition(generation), + } +} + +func (g authFilterValidationResult) acceptedCondition(generation int64) metav1.Condition { + now := timeFunc() + if g.acceptedErr != nil { + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "ReferencesNotValid", + ObservedGeneration: generation, + Message: g.acceptedErr.Error(), + LastTransitionTime: now, + } + } + return metav1.Condition{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + ObservedGeneration: generation, + Message: "route auth filter accepted", + LastTransitionTime: now, + } +} + +func (g authFilterValidationResult) resolvedRefsCondition(generation int64) metav1.Condition { + now := timeFunc() + if g.resolvedRefErr == nil { + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + ObservedGeneration: generation, + Message: "resolved references", + LastTransitionTime: now, + } + } + + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "MissingJWTProviderReference", + ObservedGeneration: generation, + Message: g.resolvedRefErr.Error(), + LastTransitionTime: now, + } +} diff --git a/control-plane/api-gateway/binding/result_test.go b/control-plane/api-gateway/binding/result_test.go index 6989bb09ab..07216f7207 100644 --- a/control-plane/api-gateway/binding/result_test.go +++ b/control-plane/api-gateway/binding/result_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -68,3 +69,180 @@ func TestBindResults_Condition(t *testing.T) { }) } } + +func TestGatewayPolicyValidationResult_Conditions(t *testing.T) { + t.Parallel() + var generation int64 = 5 + for name, tc := range map[string]struct { + results gatewayPolicyValidationResult + expected []metav1.Condition + }{ + "policy valid": { + results: gatewayPolicyValidationResult{}, + expected: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "Accepted", + Message: "gateway policy accepted", + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "ResolvedRefs", + Message: "resolved references", + }, + }, + }, + "errors with JWT references": { + results: gatewayPolicyValidationResult{ + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{errorForMissingJWTProviders(map[string]struct{}{"okta": {}})}, + }, + expected: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "ReferencesNotValid", + Message: errNotAcceptedDueToInvalidRefs.Error(), + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "MissingJWTProviderReference", + Message: errorForMissingJWTProviders(map[string]struct{}{"okta": {}}).Error(), + }, + }, + }, + "errors with listener references": { + results: gatewayPolicyValidationResult{ + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{errorForMissingListener("gw", "l1")}, + }, + expected: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "ReferencesNotValid", + Message: errNotAcceptedDueToInvalidRefs.Error(), + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "MissingListenerReference", + Message: errorForMissingListener("gw", "l1").Error(), + }, + }, + }, + "errors with listener and jwt references": { + results: gatewayPolicyValidationResult{ + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{ + errorForMissingJWTProviders(map[string]struct{}{"okta": {}}), + errorForMissingListener("gw", "l1"), + }, + }, + expected: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "ReferencesNotValid", + Message: errNotAcceptedDueToInvalidRefs.Error(), + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "MissingJWTProviderReference", + Message: errorForMissingJWTProviders(map[string]struct{}{"okta": {}}).Error(), + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "MissingListenerReference", + Message: errorForMissingListener("gw", "l1").Error(), + }, + }, + }, + } { + t.Run(name, func(t *testing.T) { + require.EqualValues(t, tc.expected, tc.results.Conditions(generation)) + }) + } +} + +func TestAuthFilterValidationResult_Conditions(t *testing.T) { + t.Parallel() + var generation int64 = 5 + for name, tc := range map[string]struct { + results authFilterValidationResult + expected []metav1.Condition + }{ + "policy valid": { + results: authFilterValidationResult{}, + expected: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "Accepted", + Message: "route auth filter accepted", + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "ResolvedRefs", + Message: "resolved references", + }, + }, + }, + "errors with JWT references": { + results: authFilterValidationResult{ + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefErr: fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta"), + }, + expected: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "ReferencesNotValid", + Message: errNotAcceptedDueToInvalidRefs.Error(), + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + ObservedGeneration: generation, + LastTransitionTime: timeFunc(), + Reason: "MissingJWTProviderReference", + Message: fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta").Error(), + }, + }, + }, + } { + t.Run(name, func(t *testing.T) { + require.EqualValues(t, tc.expected, tc.results.Conditions(generation)) + }) + } +} diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index 75a70c2974..a2a1c49754 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -4,6 +4,9 @@ package binding import ( + "fmt" + "strings" + mapset "github.com/deckarep/golang-set" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -11,8 +14,9 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul/api" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" ) // bindRoute contains the main logic for binding a route to a given gateway. @@ -177,6 +181,17 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section }) } + if invalidFilterNames := authFilterReferencesMissingJWTProvider(httproute, r.config.Resources); len(invalidFilterNames) > 0 { + results = append(results, parentBindResult{ + parent: ref, + results: []bindResult{ + { + err: fmt.Errorf("%w: %s", errFilterInvalid, strings.Join(invalidFilterNames, ",")), + }, + }, + }) + } + if !externalRefsKindAllowedOnRoute(httproute) { results = append(results, parentBindResult{ parent: ref, diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index a2726b8bb1..b99e7568b7 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -4,8 +4,11 @@ package binding import ( + "fmt" "strings" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" klabels "k8s.io/apimachinery/pkg/labels" @@ -15,10 +18,11 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/version" - "github.com/hashicorp/consul/api" ) var ( @@ -161,6 +165,70 @@ func validateGateway(gateway gwv1beta1.Gateway, pods []corev1.Pod, consulGateway return result } +func validateGatewayPolicies(gateway gwv1beta1.Gateway, policies []v1alpha1.GatewayPolicy, resources *common.ResourceMap) gatewayPolicyValidationResults { + results := make(gatewayPolicyValidationResults, 0, len(policies)) + + for _, policy := range policies { + result := gatewayPolicyValidationResult{ + resolvedRefsErrs: []error{}, + } + + exists := listenerExistsForPolicy(gateway, policy) + if !exists { + result.resolvedRefsErrs = append(result.resolvedRefsErrs, errorForMissingListener(policy.Spec.TargetRef.Name, string(*policy.Spec.TargetRef.SectionName))) + } + + missingJWTProviders := make(map[string]struct{}) + if policy.Spec.Override != nil && policy.Spec.Override.JWT != nil { + for _, policyJWTProvider := range policy.Spec.Override.JWT.Providers { + _, jwtExists := resources.GetJWTProviderForGatewayJWTProvider(policyJWTProvider) + if !jwtExists { + missingJWTProviders[policyJWTProvider.Name] = struct{}{} + } + } + } + + if policy.Spec.Default != nil && policy.Spec.Default.JWT != nil { + for _, policyJWTProvider := range policy.Spec.Default.JWT.Providers { + _, jwtExists := resources.GetJWTProviderForGatewayJWTProvider(policyJWTProvider) + if !jwtExists { + missingJWTProviders[policyJWTProvider.Name] = struct{}{} + } + } + } + + if len(missingJWTProviders) > 0 { + result.resolvedRefsErrs = append(result.resolvedRefsErrs, errorForMissingJWTProviders(missingJWTProviders)) + } + + if len(result.resolvedRefsErrs) > 0 { + result.acceptedErr = errNotAcceptedDueToInvalidRefs + } + results = append(results, result) + + } + return results +} + +func listenerExistsForPolicy(gateway gwv1beta1.Gateway, policy v1alpha1.GatewayPolicy) bool { + return gateway.Name == policy.Spec.TargetRef.Name && + slices.ContainsFunc(gateway.Spec.Listeners, func(l gwv1beta1.Listener) bool { return l.Name == *policy.Spec.TargetRef.SectionName }) +} + +func errorForMissingListener(name, listenerName string) error { + return fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, name, listenerName) +} + +func errorForMissingJWTProviders(names map[string]struct{}) error { + namesList := make([]string, 0, len(names)) + for name := range names { + namesList = append(namesList, name) + } + slices.Sort(namesList) + mergedNames := strings.Join(namesList, ",") + return fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, mergedNames) +} + // mergedListener associates a listener with its indexed position // in the gateway spec, it's used to re-associate a status with // a listener after we merge compatible listeners together and then @@ -226,6 +294,32 @@ func validateTLS(gateway gwv1beta1.Gateway, tls *gwv1beta1.GatewayTLSConfig, res return nil, refsErr } +func validateJWT(gateway gwv1beta1.Gateway, listener gwv1beta1.Listener, resources *common.ResourceMap) error { + policy, _ := resources.GetPolicyForGatewayListener(gateway, listener) + if policy == nil { + return nil + } + + if policy.Spec.Override != nil && policy.Spec.Override.JWT != nil { + for _, provider := range policy.Spec.Override.JWT.Providers { + _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) + if !ok { + return errListenerJWTProviderNotFound + } + } + } + + if policy.Spec.Default != nil && policy.Spec.Default.JWT != nil { + for _, provider := range policy.Spec.Default.JWT.Providers { + _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) + if !ok { + return errListenerJWTProviderNotFound + } + } + } + return nil +} + func validateCertificateRefs(gateway gwv1beta1.Gateway, refs []gwv1beta1.SecretObjectReference, resources *common.ResourceMap) error { for _, cert := range refs { // Verify that the reference has a group and kind that we support @@ -336,9 +430,19 @@ func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener var result listenerValidationResult err, refErr := validateTLS(gateway, listener.TLS, resources) - result.refErr = refErr + if refErr != nil { + result.refErrs = append(result.refErrs, refErr) + } + + jwtErr := validateJWT(gateway, listener, resources) + if jwtErr != nil { + result.refErrs = append(result.refErrs, jwtErr) + } + if err != nil { result.acceptedErr = err + } else if jwtErr != nil { + result.acceptedErr = jwtErr } else { _, supported := supportedKindsForProtocol[listener.Protocol] if !supported { @@ -455,13 +559,51 @@ func externalRefsOnRouteAllExist(route *gwv1beta1.HTTPRoute, resources *common.R return false } } - } } return true } +func checkIfReferencesMissingJWTProvider(filter gwv1beta1.HTTPRouteFilter, resources *common.ResourceMap, namespace string, invalidFilters map[string]struct{}) { + if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { + return + } + externalFilter, ok := resources.GetExternalFilter(*filter.ExtensionRef, namespace) + if !ok { + return + } + authFilter, ok := externalFilter.(*v1alpha1.RouteAuthFilter) + if !ok { + return + } + + for _, provider := range authFilter.Spec.JWT.Providers { + _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) + if !ok { + invalidFilters[fmt.Sprintf("%s/%s", namespace, authFilter.Name)] = struct{}{} + return + } + } +} + +func authFilterReferencesMissingJWTProvider(httproute *gwv1beta1.HTTPRoute, resources *common.ResourceMap) []string { + invalidFilters := make(map[string]struct{}) + for _, rule := range httproute.Spec.Rules { + for _, filter := range rule.Filters { + checkIfReferencesMissingJWTProvider(filter, resources, httproute.Namespace, invalidFilters) + } + + for _, backendRef := range rule.BackendRefs { + for _, filter := range backendRef.Filters { + checkIfReferencesMissingJWTProvider(filter, resources, httproute.Namespace, invalidFilters) + } + } + } + + return maps.Keys(invalidFilters) +} + // externalRefsKindAllowedOnRoute makes sure that all externalRefs reference a kind supported by gatewaycontroller. func externalRefsKindAllowedOnRoute(route *gwv1beta1.HTTPRoute) bool { for _, rule := range route.Spec.Rules { @@ -469,7 +611,7 @@ func externalRefsKindAllowedOnRoute(route *gwv1beta1.HTTPRoute) bool { return false } - //same thing but for backendref + // same thing but for backendref for _, backendRef := range rule.BackendRefs { if !filtersAllAllowedType(backendRef.Filters) { return false @@ -542,6 +684,35 @@ func routeKindIsAllowedForListenerExplicit(allowedRoutes *gwv1alpha2.AllowedRout return routeKindIsAllowedForListener(allowedRoutes.Kinds, gk) } +func validateAuthFilters(authFilters []*v1alpha1.RouteAuthFilter, resources *common.ResourceMap) authFilterValidationResults { + results := make(authFilterValidationResults, 0, len(authFilters)) + + for _, filter := range authFilters { + if filter == nil { + continue + } + var result authFilterValidationResult + missingJWTProviders := make([]string, 0) + for _, provider := range filter.Spec.JWT.Providers { + if _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider); !ok { + missingJWTProviders = append(missingJWTProviders, provider.Name) + } + } + + if len(missingJWTProviders) > 0 { + mergedNames := strings.Join(missingJWTProviders, ",") + result.resolvedRefErr = fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, mergedNames) + } + + if result.resolvedRefErr != nil { + result.acceptedErr = errNotAcceptedDueToInvalidRefs + } + + results = append(results, result) + } + return results +} + // toNamespaceSet constructs a list of labels used to match a Namespace. func toNamespaceSet(name string, labels map[string]string) klabels.Labels { // If namespace label is not set, implicitly insert it to support older Kubernetes versions diff --git a/control-plane/api-gateway/binding/validation_test.go b/control-plane/api-gateway/binding/validation_test.go index 1f2b143387..94e3d09eeb 100644 --- a/control-plane/api-gateway/binding/validation_test.go +++ b/control-plane/api-gateway/binding/validation_test.go @@ -4,11 +4,10 @@ package binding import ( + "fmt" "testing" logrtest "github.com/go-logr/logr/testing" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -16,6 +15,9 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" 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" ) func TestValidateRefs(t *testing.T) { @@ -575,35 +577,47 @@ func TestValidateListeners(t *testing.T) { expectedAcceptedErr error listenerIndexToTest int mapPrivilegedContainerPorts int32 + gateway gwv1beta1.Gateway + resources resourceMapResources }{ "valid protocol HTTP": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.HTTPProtocolType}, }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{}, expectedAcceptedErr: nil, }, "valid protocol HTTPS": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.HTTPSProtocolType}, }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{}, expectedAcceptedErr: nil, }, "valid protocol TCP": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.TCPProtocolType}, }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{}, expectedAcceptedErr: nil, }, "invalid protocol UDP": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.UDPProtocolType}, }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{}, expectedAcceptedErr: errListenerUnsupportedProtocol, }, "invalid port": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.TCPProtocolType, Port: 20000}, }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{}, expectedAcceptedErr: errListenerPortUnavailable, }, "conflicted port": { @@ -611,6 +625,8 @@ func TestValidateListeners(t *testing.T) { {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{}, expectedAcceptedErr: errListenerPortUnavailable, listenerIndexToTest: 1, }, @@ -619,10 +635,180 @@ func TestValidateListeners(t *testing.T) { {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, {Protocol: gwv1beta1.TCPProtocolType, Port: 2080}, }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), expectedAcceptedErr: errListenerMappedToPrivilegedPortMapping, + resources: resourceMapResources{}, listenerIndexToTest: 1, mapPrivilegedContainerPorts: 2000, }, + "valid JWT provider in override of policy": { + listeners: []gwv1beta1.Listener{ + {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, + }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{ + jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "okta", + }, + }, + }, + gatewayPolicies: []*v1alpha1.GatewayPolicy{ + { + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: common.KindGateway, + Name: "gateway", + Namespace: "default", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + Default: &v1alpha1.GatewayPolicyConfig{}, + }, + }, + }, + }, + expectedAcceptedErr: nil, + }, + "valid JWT provider in default of policy": { + listeners: []gwv1beta1.Listener{ + {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, + }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{ + jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "okta", + }, + }, + }, + gatewayPolicies: []*v1alpha1.GatewayPolicy{ + { + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: common.KindGateway, + Name: "gateway", + Namespace: "default", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Default: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + Override: &v1alpha1.GatewayPolicyConfig{}, + }, + }, + }, + }, + expectedAcceptedErr: nil, + }, + "invalid JWT provider in override of policy": { + listeners: []gwv1beta1.Listener{ + {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, + }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{ + jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "okta", + }, + }, + }, + gatewayPolicies: []*v1alpha1.GatewayPolicy{ + { + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: common.KindGateway, + Name: "gateway", + Namespace: "default", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "local", + }, + }, + }, + }, + Default: &v1alpha1.GatewayPolicyConfig{}, + }, + }, + }, + }, + expectedAcceptedErr: errListenerJWTProviderNotFound, + }, + "invalid JWT provider in default of policy": { + listeners: []gwv1beta1.Listener{ + {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, + }, + gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), + resources: resourceMapResources{ + jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "okta", + }, + }, + }, + gatewayPolicies: []*v1alpha1.GatewayPolicy{ + { + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: common.KindGateway, + Name: "gateway", + Namespace: "default", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Default: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "local", + }, + }, + }, + }, + Override: &v1alpha1.GatewayPolicyConfig{}, + }, + }, + }, + }, + expectedAcceptedErr: errListenerJWTProviderNotFound, + }, } { t.Run(name, func(t *testing.T) { gwcc := &v1alpha1.GatewayClassConfig{ @@ -631,7 +817,7 @@ func TestValidateListeners(t *testing.T) { }, } - require.Equal(t, tt.expectedAcceptedErr, validateListeners(gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tt.listeners, nil, gwcc)[tt.listenerIndexToTest].acceptedErr) + require.Equal(t, tt.expectedAcceptedErr, validateListeners(tt.gateway, tt.listeners, newTestResourceMap(t, tt.resources), gwcc)[tt.listenerIndexToTest].acceptedErr) }) } } @@ -860,3 +1046,528 @@ func TestRouteKindIsAllowedForListener(t *testing.T) { }) } } + +func TestValidateGatewayPolicies(t *testing.T) { + for name, tc := range map[string]struct { + gateway gwv1beta1.Gateway + policies []v1alpha1.GatewayPolicy + resources *common.ResourceMap + expected gatewayPolicyValidationResults + }{ + "happy path, everything exists": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + policies: []v1alpha1.GatewayPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Name: "gw", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "local", + }, + }, + }, + }, + Default: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "local", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "local", + }, + }, + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "okta", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "okta", + }, + }, + }}), + expected: gatewayPolicyValidationResults{ + { + acceptedErr: nil, + resolvedRefsErrs: []error{}, + }, + }, + }, + "a policy references a gateway that does not exist": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + policies: []v1alpha1.GatewayPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Name: "gw", + SectionName: common.PointerTo(gwv1beta1.SectionName("does not exist")), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "auth0", + }, + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "auth0", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "auth0", + }, + }, + }}), + expected: gatewayPolicyValidationResults{ + { + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, "gw", "does not exist")}, + }, + }, + }, + "a policy references a JWT provider in the override section that does not exist": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + policies: []v1alpha1.GatewayPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Name: "gw", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "auth0", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "auth0", + }, + }, + }}), + expected: gatewayPolicyValidationResults{ + { + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, + }, + }, + }, + "a policy references a JWT provider in the default section that does not exist": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + policies: []v1alpha1.GatewayPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Name: "gw", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Default: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "auth0", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "auth0", + }, + }, + }}), + expected: gatewayPolicyValidationResults{ + { + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, + }, + }, + }, + "a policy references the same JWT provider in the both override and default section that does not exist": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + policies: []v1alpha1.GatewayPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Name: "gw", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + Default: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "auth0", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "auth0", + }, + }, + }}), + expected: gatewayPolicyValidationResults{ + { + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, + }, + }, + }, + "a policy references different JWT providers in the both override and default section that does not exist": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + policies: []v1alpha1.GatewayPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Name: "gw", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "local", + }, + }, + }, + }, + Default: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "auth0", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "auth0", + }, + }, + }}), + expected: gatewayPolicyValidationResults{ + { + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "local,okta")}, + }, + }, + }, + "everything is wrong: listener does not exist and override and default both reference different missing jwt providers": { + gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + policies: []v1alpha1.GatewayPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Name: "gw", + SectionName: common.PointerTo(gwv1beta1.SectionName("does not exist")), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "local", + }, + }, + }, + }, + Default: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "auth0", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "auth0", + }, + }, + }}), + expected: gatewayPolicyValidationResults{ + { + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefsErrs: []error{ + fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, "gw", "does not exist"), + fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "local,okta"), + }, + }, + }, + }, + } { + t.Run(name, func(t *testing.T) { + require.EqualValues(t, tc.expected, validateGatewayPolicies(tc.gateway, tc.policies, tc.resources)) + }) + } +} + +func TestValidateAuthFilters(t *testing.T) { + for name, tc := range map[string]struct { + authFilters []*v1alpha1.RouteAuthFilter + resources *common.ResourceMap + expected authFilterValidationResults + }{ + "auth filter valid": { + authFilters: []*v1alpha1.RouteAuthFilter{ + { + Spec: v1alpha1.RouteAuthFilterSpec{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "okta", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "okta", + }, + }, + }}), + expected: authFilterValidationResults{authFilterValidationResult{}}, + }, + "auth filter references missing JWT Provider": { + authFilters: []*v1alpha1.RouteAuthFilter{ + { + Spec: v1alpha1.RouteAuthFilterSpec{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "auth0", + }, + }, + }, + }, + }, + }, + resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "okta", + }, + Spec: v1alpha1.JWTProviderSpec{ + Issuer: "okta", + }, + }, + }}), + expected: authFilterValidationResults{ + authFilterValidationResult{ + acceptedErr: errNotAcceptedDueToInvalidRefs, + resolvedRefErr: fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "auth0"), + }, + }, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.expected, validateAuthFilters(tc.authFilters, tc.resources)) + }) + } +} diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index f34538103d..0e08d3bdc8 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -55,7 +55,7 @@ const ( apiTimeout = 5 * time.Minute ) -var Kinds = []string{api.APIGateway, api.HTTPRoute, api.TCPRoute, api.InlineCertificate} +var Kinds = []string{api.APIGateway, api.HTTPRoute, api.TCPRoute, api.InlineCertificate, api.JWTProvider} type Config struct { ConsulClientConfig *consul.Config @@ -94,6 +94,7 @@ func New(config Config) *Cache { for _, kind := range Kinds { cache[kind] = common.NewReferenceMap() } + config.ConsulClientConfig.APITimeout = apiTimeout return &Cache{ @@ -224,16 +225,18 @@ func (c *Cache) updateAndNotify(ctx context.Context, once *sync.Once, kind strin for _, entry := range entries { meta := entry.GetMeta() - if meta[constants.MetaKeyKubeName] == "" || meta[constants.MetaKeyDatacenter] != c.datacenter { - // Don't process things that don't belong to us. The main reason - // for this is so that we don't garbage collect config entries that - // are either user-created or that another controller running in a - // federated datacenter creates. While we still allow for competing controllers - // syncing/overriding each other due to conflicting Kubernetes objects in - // two federated clusters (which is what the rest of the controllers also allow - // for), we don't want to delete a config entry just because we don't have - // its corresponding Kubernetes object if we know it belongs to another datacenter. - continue + if kind != api.JWTProvider { + if meta[constants.MetaKeyKubeName] == "" || meta[constants.MetaKeyDatacenter] != c.datacenter { + // Don't process things that don't belong to us. The main reason + // for this is so that we don't garbage collect config entries that + // are either user-created or that another controller running in a + // federated datacenter creates. While we still allow for competing controllers + // syncing/overriding each other due to conflicting Kubernetes objects in + // two federated clusters (which is what the rest of the controllers also allow + // for), we don't want to delete a config entry just because we don't have + // its corresponding Kubernetes object if we know it belongs to another datacenter. + continue + } } cache.Set(common.EntryToReference(entry), entry) diff --git a/control-plane/api-gateway/cache/consul_test.go b/control-plane/api-gateway/cache/consul_test.go index 3a4423f6b4..d206c0a8a8 100644 --- a/control-plane/api-gateway/cache/consul_test.go +++ b/control-plane/api-gateway/cache/consul_test.go @@ -1543,6 +1543,10 @@ func Test_Run(t *testing.T) { inlineCert := setupInlineCertificate() certs := []*api.InlineCertificateConfigEntry{inlineCert} + // setup jwt providers + jwtProvider := setupJWTProvider() + providers := []*api.JWTProviderConfigEntry{jwtProvider} + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/config/http-route": @@ -1577,6 +1581,14 @@ func Test_Run(t *testing.T) { return } fmt.Fprintln(w, string(val)) + case "/v1/config/jwt-provider": + val, err := json.Marshal(providers) + if err != nil { + w.WriteHeader(500) + fmt.Fprintln(w, err) + return + } + fmt.Fprintln(w, string(val)) case "/v1/catalog/services": fmt.Fprintln(w, `{}`) case "/v1/peerings": @@ -1615,7 +1627,7 @@ func Test_Run(t *testing.T) { } expectedCache := loadedReferenceMaps([]api.ConfigEntry{ - gw, tcpRoute, httpRouteOne, httpRouteTwo, inlineCert, + gw, tcpRoute, httpRouteOne, httpRouteTwo, inlineCert, jwtProvider, }) ctx, cancelFn := context.WithCancel(context.Background()) @@ -1675,6 +1687,16 @@ func Test_Run(t *testing.T) { } }) + jwtProviderNsn := types.NamespacedName{ + Name: jwtProvider.Name, + Namespace: jwtProvider.Namespace, + } + + jwtSubscriber := c.Subscribe(ctx, api.JWTProvider, func(cfe api.ConfigEntry) []types.NamespacedName { + return []types.NamespacedName{ + {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, + } + }) // mark this subscription as ended canceledSub.Cancel() @@ -1685,9 +1707,10 @@ func Test_Run(t *testing.T) { gwExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(gwNsn)} tcpExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(tcpRouteNsn)} certExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(certNsn)} + jwtProviderExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(jwtProviderNsn)} - // 2 http routes + 1 gw + 1 tcp route + 1 cert = 5 - i := 5 + // 2 http routes + 1 gw + 1 tcp route + 1 cert + 1 jwtProvider = 6 + i := 6 for { if i == 0 { break @@ -1701,6 +1724,8 @@ func Test_Run(t *testing.T) { require.Equal(t, tcpExpectedEvent, actualTCPRouteEvent) case actualCertExpectedEvent := <-certSubscriber.Events(): require.Equal(t, certExpectedEvent, actualCertExpectedEvent) + case actualJWTExpectedEvent := <-jwtSubscriber.Events(): + require.Equal(t, jwtProviderExpectedEvent, actualJWTExpectedEvent) } i -= 1 } @@ -1956,6 +1981,13 @@ func setupInlineCertificate() *api.InlineCertificateConfigEntry { } } +func setupJWTProvider() *api.JWTProviderConfigEntry { + return &api.JWTProviderConfigEntry{ + Kind: api.JWTProvider, + Name: "okta", + } +} + func TestCache_Delete(t *testing.T) { t.Parallel() testCases := []struct { diff --git a/control-plane/api-gateway/common/diff.go b/control-plane/api-gateway/common/diff.go index f2fdf89d8b..df4f2cb4f0 100644 --- a/control-plane/api-gateway/common/diff.go +++ b/control-plane/api-gateway/common/diff.go @@ -11,6 +11,8 @@ import ( "golang.org/x/exp/slices" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) func GatewayStatusesEqual(a, b gwv1beta1.GatewayStatus) bool { @@ -19,6 +21,14 @@ func GatewayStatusesEqual(a, b gwv1beta1.GatewayStatus) bool { slices.EqualFunc(a.Listeners, b.Listeners, gatewayStatusesListenersEqual) } +func GatewayPolicyStatusesEqual(a, b v1alpha1.GatewayPolicyStatus) bool { + return slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) +} + +func RouteAuthFilterStatusesEqual(a, b v1alpha1.RouteAuthFilterStatus) bool { + return slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) +} + func gatewayStatusesAddressesEqual(a, b gwv1beta1.GatewayAddress) bool { return BothNilOrEqual(a.Type, b.Type) && a.Value == b.Value @@ -103,7 +113,75 @@ func (e entryComparator) apiGatewayListenersEqual(a, b api.APIGatewayListener) b a.Port == b.Port && // normalize the protocol name strings.EqualFold(a.Protocol, b.Protocol) && - e.apiGatewayListenerTLSConfigurationsEqual(a.TLS, b.TLS) + e.apiGatewayListenerTLSConfigurationsEqual(a.TLS, b.TLS) && + e.apiGatewayPoliciesEqual(a.Override, b.Override) && + e.apiGatewayPoliciesEqual(a.Default, b.Default) +} + +func (e entryComparator) apiGatewayPoliciesEqual(a, b *api.APIGatewayPolicy) bool { + // if both are nil then return true + if a == nil && b == nil { + return true + } + + // if only one is nil then return false + if a == nil || b == nil { + return false + } + + return e.equalJWTProviders(a.JWT, b.JWT) +} + +func (e entryComparator) equalJWTProviders(a, b *api.APIGatewayJWTRequirement) bool { + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + return slices.EqualFunc(a.Providers, b.Providers, providersEqual) +} + +func providersEqual(a, b *api.APIGatewayJWTProvider) bool { + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + if a.Name != b.Name { + return false + } + + return slices.EqualFunc(a.VerifyClaims, b.VerifyClaims, equalClaims) +} + +func equalClaims(a, b *api.APIGatewayJWTClaimVerification) bool { + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + if a.Value != b.Value { + return false + } + + if len(a.Path) != len(b.Path) { + return false + } + + if !slices.Equal(a.Path, b.Path) { + return false + } + + return true } func (e entryComparator) apiGatewayListenerTLSConfigurationsEqual(a, b api.APIGatewayTLSConfiguration) bool { @@ -152,7 +230,8 @@ func (e entryComparator) httpRouteRulesEqual(a, b api.HTTPRouteRule) bool { slices.EqualFunc(a.Matches, b.Matches, e.httpMatchesEqual) && slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) && bothNilOrEqualFunc(a.Filters.RetryFilter, b.Filters.RetryFilter, e.retryFiltersEqual) && - bothNilOrEqualFunc(a.Filters.TimeoutFilter, b.Filters.TimeoutFilter, e.timeoutFiltersEqual) + bothNilOrEqualFunc(a.Filters.TimeoutFilter, b.Filters.TimeoutFilter, e.timeoutFiltersEqual) && + bothNilOrEqualFunc(a.Filters.JWT, b.Filters.JWT, e.jwtFiltersEqual) } func (e entryComparator) httpServicesEqual(a, b api.HTTPService) bool { @@ -203,6 +282,16 @@ func (e entryComparator) timeoutFiltersEqual(a, b api.TimeoutFilter) bool { return a.RequestTimeout == b.RequestTimeout && a.IdleTimeout == b.IdleTimeout } +// jwtFiltersEqual compares the contents of the list of providers on the JWT filters for a route, returning true if the +// filters have equal contents. +func (e entryComparator) jwtFiltersEqual(a, b api.JWTFilter) bool { + if len(a.Providers) != len(b.Providers) { + return false + } + + return slices.EqualFunc(a.Providers, b.Providers, providersEqual) +} + func tcpRoutesEqual(a, b *api.TCPRouteConfigEntry) bool { if a == nil || b == nil { return false diff --git a/control-plane/api-gateway/common/diff_test.go b/control-plane/api-gateway/common/diff_test.go new file mode 100644 index 0000000000..04312c8162 --- /dev/null +++ b/control-plane/api-gateway/common/diff_test.go @@ -0,0 +1,2155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "testing" + + "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" +) + +func TestEntriesEqual(t *testing.T) { + testCases := map[string]struct { + a api.ConfigEntry + b api.ConfigEntry + expectedResult bool + }{ + "gateway equal": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: true, + }, + "gateway name different": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway-2", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway meta different": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey2": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different name": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l2", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different hostname": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host-different.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different port": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 123, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different protocol": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "https", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different TLS max version": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "15", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different TLS min version": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "0", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different TLS cipher suites": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher", "another one"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different TLS certificate references": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert-2", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different override policies jwt provider name": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "auth0", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different override policy jwt claims path": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"roles"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different override policy jwt claims value": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "user", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different default policies jwt provider name": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "auth0", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different default policy jwt claims path": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"roles"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + "gateway listeners different default policy jwt claims value": { + a: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "admin", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + b: &api.APIGatewayConfigEntry{ + Kind: api.APIGateway, + Name: "api-gateway", + Meta: map[string]string{ + "somekey": "somevalue", + }, + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Hostname: "host.com", + Port: 590, + Protocol: "http", + TLS: api.APIGatewayTLSConfiguration{ + Certificates: []api.ResourceReference{ + { + Kind: api.InlineCertificate, + Name: "cert", + SectionName: "section", + Partition: "partition", + Namespace: "ns", + }, + }, + MaxVersion: "5", + MinVersion: "2", + CipherSuites: []string{"cipher"}, + }, + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"role"}, + Value: "user", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "okta", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"aud"}, + Value: "consul.com", + }, + }, + }, + }, + }, + }, + }, + }, + Partition: "partition", + Namespace: "ns", + }, + expectedResult: false, + }, + } + + for name, tc := range testCases { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := EntriesEqual(tc.a, tc.b) + require.Equal(t, tc.expectedResult, actual) + }) + } +} diff --git a/control-plane/api-gateway/common/helpers.go b/control-plane/api-gateway/common/helpers.go index f2ac883571..7bc7eb61b6 100644 --- a/control-plane/api-gateway/common/helpers.go +++ b/control-plane/api-gateway/common/helpers.go @@ -38,7 +38,7 @@ func FilterIsExternalFilter(filter gwv1beta1.HTTPRouteFilter) bool { } switch filter.ExtensionRef.Kind { - case v1alpha1.RouteRetryFilterKind, v1alpha1.RouteTimeoutFilterKind: + case v1alpha1.RouteRetryFilterKind, v1alpha1.RouteTimeoutFilterKind, v1alpha1.RouteAuthFilterKind: return true } diff --git a/control-plane/api-gateway/common/resources.go b/control-plane/api-gateway/common/resources.go index 40fe74bf8d..051c914ae7 100644 --- a/control-plane/api-gateway/common/resources.go +++ b/control-plane/api-gateway/common/resources.go @@ -12,8 +12,9 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) // ConsulUpdateOperation is an operation representing an @@ -116,10 +117,12 @@ type ResourceMap struct { httpRouteGateways map[api.ResourceReference]*httpRoute gatewayResources map[api.ResourceReference]*resourceSet externalFilters map[corev1.ObjectReference]client.Object + gatewayPolicies map[api.ResourceReference]*v1alpha1.GatewayPolicy // consul resources for a gateway consulTCPRoutes map[api.ResourceReference]*consulTCPRoute consulHTTPRoutes map[api.ResourceReference]*consulHTTPRoute + jwtProviders map[api.ResourceReference]*v1alpha1.JWTProvider // mutations consulMutations []*ConsulUpdateOperation @@ -140,6 +143,8 @@ func NewResourceMap(translator ResourceTranslator, validator ReferenceValidator, tcpRouteGateways: make(map[api.ResourceReference]*tcpRoute), httpRouteGateways: make(map[api.ResourceReference]*httpRoute), gatewayResources: make(map[api.ResourceReference]*resourceSet), + gatewayPolicies: make(map[api.ResourceReference]*v1alpha1.GatewayPolicy), + jwtProviders: make(map[api.ResourceReference]*v1alpha1.JWTProvider), } } @@ -401,6 +406,74 @@ func (s *ResourceMap) ExternalFilterExists(filterRef gwv1beta1.LocalObjectRefere return ok } +func (s *ResourceMap) GetExternalAuthFilters() []*v1alpha1.RouteAuthFilter { + filters := make([]*v1alpha1.RouteAuthFilter, 0, len(s.externalFilters)) + for _, filter := range s.externalFilters { + if authFilter, ok := filter.(*v1alpha1.RouteAuthFilter); ok { + filters = append(filters, authFilter) + } + } + return filters +} + +func (s *ResourceMap) AddGatewayPolicy(gatewayPolicy *v1alpha1.GatewayPolicy) *v1alpha1.GatewayPolicy { + sectionName := "" + if gatewayPolicy.Spec.TargetRef.SectionName != nil { + sectionName = string(*gatewayPolicy.Spec.TargetRef.SectionName) + } + + gwNamespace := gatewayPolicy.Spec.TargetRef.Namespace + if gwNamespace == "" { + gwNamespace = gatewayPolicy.Namespace + } + + key := api.ResourceReference{ + Kind: gatewayPolicy.Spec.TargetRef.Kind, + Name: gatewayPolicy.Spec.TargetRef.Name, + SectionName: sectionName, + Namespace: gwNamespace, + } + + if s.gatewayPolicies == nil { + s.gatewayPolicies = make(map[api.ResourceReference]*v1alpha1.GatewayPolicy) + } + + s.gatewayPolicies[key] = gatewayPolicy + + return s.gatewayPolicies[key] +} + +func (s *ResourceMap) AddJWTProvider(provider *v1alpha1.JWTProvider) { + key := api.ResourceReference{ + Kind: provider.Kind, + Name: provider.Name, + } + s.jwtProviders[key] = provider +} + +func (s *ResourceMap) GetJWTProviderForGatewayJWTProvider(provider *v1alpha1.GatewayJWTProvider) (*v1alpha1.JWTProvider, bool) { + key := api.ResourceReference{ + Name: provider.Name, + Kind: "JWTProvider", + } + + value, exists := s.jwtProviders[key] + return value, exists +} + +func (s *ResourceMap) GetPolicyForGatewayListener(gateway gwv1beta1.Gateway, gatewayListener gwv1beta1.Listener) (*v1alpha1.GatewayPolicy, bool) { + key := api.ResourceReference{ + Name: gateway.Name, + Kind: gateway.Kind, + SectionName: string(gatewayListener.Name), + Namespace: gateway.Namespace, + } + + value, exists := s.gatewayPolicies[key] + + return value, exists +} + func (s *ResourceMap) ReferenceCountTCPRoute(route gwv1alpha2.TCPRoute) { key := client.ObjectKeyFromObject(&route) consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) diff --git a/control-plane/api-gateway/common/resources_test.go b/control-plane/api-gateway/common/resources_test.go new file mode 100644 index 0000000000..7f5619496f --- /dev/null +++ b/control-plane/api-gateway/common/resources_test.go @@ -0,0 +1,57 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "testing" + + logrtest "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul/api" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +func TestResourceMap_JWTProvider(t *testing.T) { + resourceMap := NewResourceMap(ResourceTranslator{}, mockReferenceValidator{}, logrtest.New(t)) + require.Empty(t, resourceMap.jwtProviders) + provider := &v1alpha1.JWTProvider{ + TypeMeta: metav1.TypeMeta{ + Kind: "JWTProvider", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-jwt", + }, + Spec: v1alpha1.JWTProviderSpec{}, + } + + key := api.ResourceReference{ + Name: provider.Name, + Kind: "JWTProvider", + } + + resourceMap.AddJWTProvider(provider) + + require.Len(t, resourceMap.jwtProviders, 1) + require.NotNil(t, resourceMap.jwtProviders[key]) + require.Equal(t, resourceMap.jwtProviders[key], provider) +} + +type mockReferenceValidator struct{} + +func (m mockReferenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool { + return true +} + +func (m mockReferenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { + return true +} + +func (m mockReferenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { + return true +} diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index 4b6092ee41..07c526be4d 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -11,10 +11,11 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul/api" ) // ResourceTranslator handles translating K8s resources into Consul config entries. @@ -113,6 +114,10 @@ func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, list } } + // Grab policy if it exists. + gatewayPolicyCrd, _ := resources.GetPolicyForGatewayListener(gateway, listener) + defaultPolicy, overridePolicy := t.translateGatewayPolicy(gatewayPolicyCrd) + portMapping := int32(0) if gwcc != nil { portMapping = gwcc.Spec.MapPrivilegedContainerPorts @@ -129,6 +134,8 @@ func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, list MaxVersion: maxVersion, MinVersion: minVersion, }, + Default: defaultPolicy, + Override: overridePolicy, }, true } @@ -141,15 +148,96 @@ func ToContainerPort(portNumber gwv1beta1.PortNumber, mapPrivilegedContainerPort return int(portNumber) + int(mapPrivilegedContainerPorts) } +func (t ResourceTranslator) translateRouteRetryFilter(routeRetryFilter *v1alpha1.RouteRetryFilter) *api.RetryFilter { + return &api.RetryFilter{ + NumRetries: routeRetryFilter.Spec.NumRetries, + RetryOn: routeRetryFilter.Spec.RetryOn, + RetryOnStatusCodes: routeRetryFilter.Spec.RetryOnStatusCodes, + RetryOnConnectFailure: routeRetryFilter.Spec.RetryOnConnectFailure, + } +} + +func (t ResourceTranslator) translateRouteTimeoutFilter(routeTimeoutFilter *v1alpha1.RouteTimeoutFilter) *api.TimeoutFilter { + return &api.TimeoutFilter{ + RequestTimeout: routeTimeoutFilter.Spec.RequestTimeout, + IdleTimeout: routeTimeoutFilter.Spec.IdleTimeout, + } +} + +func (t ResourceTranslator) translateRouteJWTFilter(routeJWTFilter *v1alpha1.RouteAuthFilter) *api.JWTFilter { + if routeJWTFilter.Spec.JWT == nil { + return nil + } + + return &api.JWTFilter{ + Providers: ConvertSliceFunc(routeJWTFilter.Spec.JWT.Providers, t.translateJWTProvider), + } +} + +func (t ResourceTranslator) translateGatewayPolicy(policy *v1alpha1.GatewayPolicy) (*api.APIGatewayPolicy, *api.APIGatewayPolicy) { + if policy == nil { + return nil, nil + } + + var defaultPolicy, overridePolicy *api.APIGatewayPolicy + + if policy.Spec.Default != nil { + defaultPolicy = &api.APIGatewayPolicy{ + JWT: t.translateJWTRequirement(policy.Spec.Default.JWT), + } + } + + if policy.Spec.Override != nil { + overridePolicy = &api.APIGatewayPolicy{ + JWT: t.translateJWTRequirement(policy.Spec.Override.JWT), + } + } + return defaultPolicy, overridePolicy +} + +func (t ResourceTranslator) translateJWTRequirement(crdRequirement *v1alpha1.GatewayJWTRequirement) *api.APIGatewayJWTRequirement { + apiRequirement := api.APIGatewayJWTRequirement{} + providers := ConvertSliceFunc(crdRequirement.Providers, t.translateJWTProvider) + apiRequirement.Providers = providers + return &apiRequirement +} + +func (t ResourceTranslator) translateJWTProvider(crdProvider *v1alpha1.GatewayJWTProvider) *api.APIGatewayJWTProvider { + if crdProvider == nil { + return nil + } + + apiProvider := api.APIGatewayJWTProvider{ + Name: crdProvider.Name, + } + claims := ConvertSliceFunc(crdProvider.VerifyClaims, t.translateVerifyClaims) + apiProvider.VerifyClaims = claims + + return &apiProvider +} + +func (t ResourceTranslator) translateVerifyClaims(crdClaims *v1alpha1.GatewayJWTClaimVerification) *api.APIGatewayJWTClaimVerification { + if crdClaims == nil { + return nil + } + verifyClaim := api.APIGatewayJWTClaimVerification{ + Path: crdClaims.Path, + Value: crdClaims.Value, + } + return &verifyClaim +} + func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *ResourceMap) *api.HTTPRouteConfigEntry { namespace := t.Namespace(route.Namespace) - // we don't translate parent refs + // We don't translate parent refs. hostnames := StringLikeSlice(route.Spec.Hostnames) - rules := ConvertSliceFuncIf(route.Spec.Rules, func(rule gwv1beta1.HTTPRouteRule) (api.HTTPRouteRule, bool) { - return t.translateHTTPRouteRule(route, rule, resources) - }) + rules := ConvertSliceFuncIf( + route.Spec.Rules, + func(rule gwv1beta1.HTTPRouteRule) (api.HTTPRouteRule, bool) { + return t.translateHTTPRouteRule(route, rule, resources) + }) configEntry := api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, @@ -168,10 +256,11 @@ func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *Re } func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, rule gwv1beta1.HTTPRouteRule, resources *ResourceMap) (api.HTTPRouteRule, bool) { - services := ConvertSliceFuncIf(rule.BackendRefs, func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) { - - return t.translateHTTPBackendRef(route, ref, resources) - }) + services := ConvertSliceFuncIf( + rule.BackendRefs, + func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) { + return t.translateHTTPBackendRef(route, ref, resources) + }) if len(services) == 0 { return api.HTTPRouteRule{}, false @@ -285,6 +374,7 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi timeoutFilter *api.TimeoutFilter requestHeaderFilters = []api.HTTPHeaderFilter{} responseHeaderFilters = []api.HTTPHeaderFilter{} + jwtFilter *api.JWTFilter ) // Convert Gateway API filters to portions of the Consul request and response filters. @@ -343,38 +433,22 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi } if filter.ExtensionRef != nil { - //get crd from resources map + // get crd from resources map crdFilter, exists := resourceMap.GetExternalFilter(*filter.ExtensionRef, namespace) if !exists { // this should never be the case because we only translate a route if it's actually valid, and if we're missing filters during the validation step, then we won't get here continue } + switch filter.ExtensionRef.Kind { case v1alpha1.RouteRetryFilterKind: - - retryFilterCRD := crdFilter.(*v1alpha1.RouteRetryFilter) - //new filter that needs to be appended - - retryFilter = &api.RetryFilter{ - NumRetries: retryFilterCRD.Spec.NumRetries, - RetryOn: retryFilterCRD.Spec.RetryOn, - RetryOnStatusCodes: retryFilterCRD.Spec.RetryOnStatusCodes, - RetryOnConnectFailure: retryFilterCRD.Spec.RetryOnConnectFailure, - } - + retryFilter = t.translateRouteRetryFilter(crdFilter.(*v1alpha1.RouteRetryFilter)) case v1alpha1.RouteTimeoutFilterKind: - - timeoutFilterCRD := crdFilter.(*v1alpha1.RouteTimeoutFilter) - //new filter that needs to be appended - - timeoutFilter = &api.TimeoutFilter{ - RequestTimeout: timeoutFilterCRD.Spec.RequestTimeout, - IdleTimeout: timeoutFilterCRD.Spec.IdleTimeout, - } - + timeoutFilter = t.translateRouteTimeoutFilter(crdFilter.(*v1alpha1.RouteTimeoutFilter)) + case v1alpha1.RouteAuthFilterKind: + jwtFilter = t.translateRouteJWTFilter(crdFilter.(*v1alpha1.RouteAuthFilter)) } } - } requestFilter := api.HTTPFilters{ @@ -382,6 +456,7 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi URLRewrite: urlRewrite, RetryFilter: retryFilter, TimeoutFilter: timeoutFilter, + JWT: jwtFilter, } responseFilter := api.HTTPResponseFilters{ diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index 94c78d1ee7..4ce3805cff 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -10,13 +10,14 @@ import ( "crypto/x509/pkix" "encoding/pem" "fmt" - "k8s.io/utils/pointer" "math/big" - "sigs.k8s.io/controller-runtime/pkg/client" "strings" "testing" "time" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -28,9 +29,10 @@ import ( logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul/api" ) type fakeReferenceValidator struct{} @@ -1201,7 +1203,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Filters: api.HTTPFilters{Headers: []api.HTTPHeaderFilter{}}, ResponseFilters: api.HTTPResponseFilters{ Headers: []api.HTTPHeaderFilter{}, - }}, + }, + }, { Name: "service one", Namespace: "some ns", @@ -1294,7 +1297,21 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { ExtensionRef: &gwv1beta1.LocalObjectReference{ Name: "test", Kind: v1alpha1.RouteRetryFilterKind, - Group: "consul.hashicorp.com/v1alpha1", + Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), + }, + }, + { + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Name: "test-timeout-filter", + Kind: v1alpha1.RouteTimeoutFilterKind, + Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), + }, + }, + { + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Name: "test-jwt-filter", + Kind: v1alpha1.RouteAuthFilterKind, + Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), }, }, }, @@ -1359,6 +1376,47 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { RetryOnConnectFailure: pointer.Bool(true), }, }, + + &v1alpha1.RouteTimeoutFilter{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.RouteTimeoutFilterKind, + APIVersion: "consul.hashicorp.com/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-timeout-filter", + Namespace: "k8s-ns", + }, + Spec: v1alpha1.RouteTimeoutFilterSpec{ + RequestTimeout: 10, + IdleTimeout: 30, + }, + }, + + &v1alpha1.RouteAuthFilter{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.RouteAuthFilterKind, + APIVersion: "consul.hashicorp.com/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-jwt-filter", + Namespace: "k8s-ns", + }, + Spec: v1alpha1.RouteAuthFilterSpec{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "test-jwt-provider", + VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ + { + Path: []string{"/okta"}, + Value: "okta", + }, + }, + }, + }, + }, + }, + }, }, }, want: api.HTTPRouteConfigEntry{ @@ -1375,6 +1433,23 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { RetryOnStatusCodes: []uint32{500, 502}, RetryOnConnectFailure: pointer.Bool(false), }, + TimeoutFilter: &api.TimeoutFilter{ + RequestTimeout: time.Duration(10 * time.Nanosecond), + IdleTimeout: time.Duration(30 * time.Nanosecond), + }, + JWT: &api.JWTFilter{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "test-jwt-provider", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"/okta"}, + Value: "okta", + }, + }, + }, + }, + }, }, ResponseFilters: api.HTTPResponseFilters{ Headers: []api.HTTPHeaderFilter{}, @@ -1677,3 +1752,149 @@ func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { }) } } + +func newSectionNamePtr(s string) *gwv1beta1.SectionName { + sectionName := gwv1beta1.SectionName(s) + return §ionName +} + +func TestResourceTranslator_toAPIGatewayListener(t *testing.T) { + type args struct { + gateway gwv1beta1.Gateway + listener gwv1beta1.Listener + gwcc *v1alpha1.GatewayClassConfig + } + tests := []struct { + name string + args args + policies []v1alpha1.GatewayPolicy + want api.APIGatewayListener + want1 bool + }{ + { + name: "listener with jwt auth", + policies: []v1alpha1.GatewayPolicy{ + { + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Kind: KindGateway, + Name: "test", + Namespace: "test", + SectionName: newSectionNamePtr("test-listener"), + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "override-provider", + VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ + { + Path: []string{"path"}, + Value: "value", + }, + }, + }, + }, + }, + }, + Default: &v1alpha1.GatewayPolicyConfig{JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "default-provider", + VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ + { + Path: []string{"path"}, + Value: "value", + }, + }, + }, + }, + }}, + }, + }, + }, + args: args{ + gateway: gwv1beta1.Gateway{ + TypeMeta: metav1.TypeMeta{ + Kind: KindGateway, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "test-listener", + Port: 80, + Protocol: "HTTP", + }, + }, + }, + }, + listener: gwv1beta1.Listener{ + Name: "test-listener", + Port: 80, + Protocol: "HTTP", + }, + gwcc: &v1alpha1.GatewayClassConfig{ + Spec: v1alpha1.GatewayClassConfigSpec{}, + }, + }, + want: api.APIGatewayListener{ + Name: "test-listener", + Port: 80, + Protocol: "http", + Override: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "override-provider", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"path"}, + Value: "value", + }, + }, + }, + }, + }, + }, + Default: &api.APIGatewayPolicy{ + JWT: &api.APIGatewayJWTRequirement{ + Providers: []*api.APIGatewayJWTProvider{ + { + Name: "default-provider", + VerifyClaims: []*api.APIGatewayJWTClaimVerification{ + { + Path: []string{"path"}, + Value: "value", + }, + }, + }, + }, + }, + }, + }, + want1: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t1 *testing.T) { + translator := ResourceTranslator{ + EnableConsulNamespaces: true, + ConsulDestNamespace: "", + EnableK8sMirroring: true, + MirroringPrefix: "", + } + + resources := NewResourceMap(translator, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) + for _, p := range tt.policies { + resources.AddGatewayPolicy(&p) + } + got, got1 := translator.toAPIGatewayListener(tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) + assert.Equalf(t, tt.want, got, "toAPIGatewayListener(%v, %v, %v, %v)", tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) + assert.Equalf(t, tt.want1, got1, "toAPIGatewayListener(%v, %v, %v, %v)", tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) + }) + } +} diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index f7ef3af83b..8447e69f64 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -11,6 +11,7 @@ import ( "strings" mapset "github.com/deckarep/golang-set" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/go-logr/logr" @@ -30,13 +31,14 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/binding" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/gatekeeper" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul/api" ) // GatewayControllerConfig holds the values necessary for configuring the GatewayController. @@ -167,6 +169,19 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } + // get all gatewaypolicies referencing this gateway + policies, err := r.getRelatedGatewayPolicies(ctx, req.NamespacedName, resources) + if err != nil { + log.Error(err, "unable to list gateway policies") + return ctrl.Result{}, err + } + + _, err = r.getJWTProviders(ctx, resources) + if err != nil { + log.Error(err, "unable to list JWT providers") + return ctrl.Result{}, err + } + // fetch the rest of the consul objects from cache consulServices := r.getConsulServices(consulKey) consulGateway := r.getConsulGateway(consulKey) @@ -188,6 +203,7 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct Resources: resources, ConsulGateway: consulGateway, ConsulGatewayServices: consulServices, + Policies: policies, }) updates := binder.Snapshot() @@ -458,6 +474,14 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co &source.Channel{Source: c.Subscribe(ctx, api.InlineCertificate, r.transformConsulInlineCertificate(ctx)).Events()}, &handler.EnqueueRequestForObject{}, ). + Watches( + &source.Channel{Source: c.Subscribe(ctx, api.JWTProvider, r.transformConsulJWTProvider(ctx)).Events()}, + &handler.EnqueueRequestForObject{}, + ). + Watches( + source.NewKindWithCache((&v1alpha1.GatewayPolicy{}), mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformGatewayPolicy(ctx)), + ). Watches( source.NewKindWithCache((&v1alpha1.RouteRetryFilter{}), mgr.GetCache()), handler.EnqueueRequestsFromMapFunc(r.transformRouteRetryFilter(ctx)), @@ -465,7 +489,13 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co Watches( source.NewKindWithCache((&v1alpha1.RouteTimeoutFilter{}), mgr.GetCache()), handler.EnqueueRequestsFromMapFunc(r.transformRouteTimeoutFilter(ctx)), - ).Complete(r) + ). + Watches( + // Subscribe to changes in RouteAuthFilter custom resources referenced by HTTPRoutes. + source.NewKindWithCache((&v1alpha1.RouteAuthFilter{}), mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(r.transformRouteAuthFilter(ctx)), + ). + Complete(r) } // transformGatewayClass will check the list of GatewayClass objects for a matching @@ -581,6 +611,26 @@ func (r *GatewayController) transformConsulHTTPRoute(ctx context.Context) func(e } } +// transformGatewayPolicy will return a list of all gateways that need to be reconcilled. +func (r *GatewayController) transformGatewayPolicy(ctx context.Context) func(object client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + gatewayPolicy := o.(*v1alpha1.GatewayPolicy) + gwNamespace := gatewayPolicy.Spec.TargetRef.Namespace + if gwNamespace == "" { + gwNamespace = gatewayPolicy.Namespace + } + gatewayRef := types.NamespacedName{ + Namespace: gwNamespace, + Name: gatewayPolicy.Spec.TargetRef.Name, + } + return []reconcile.Request{ + { + NamespacedName: gatewayRef, + }, + } + } +} + // transformRouteRetryFilter will return a list of routes that need to be reconciled. func (r *GatewayController) transformRouteRetryFilter(ctx context.Context) func(object client.Object) []reconcile.Request { return func(o client.Object) []reconcile.Request { @@ -595,6 +645,12 @@ func (r *GatewayController) transformRouteTimeoutFilter(ctx context.Context) fun } } +func (r *GatewayController) transformRouteAuthFilter(ctx context.Context) func(object client.Object) []reconcile.Request { + return func(o client.Object) []reconcile.Request { + return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteAuthFilterIndex, client.ObjectKeyFromObject(o).String()) + } +} + func (r *GatewayController) transformConsulTCPRoute(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { return func(entry api.ConfigEntry) []types.NamespacedName { parents := mapset.NewSet() @@ -638,6 +694,42 @@ func (r *GatewayController) transformConsulInlineCertificate(ctx context.Context } } +func (r *GatewayController) transformConsulJWTProvider(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { + return func(entry api.ConfigEntry) []types.NamespacedName { + var gateways []types.NamespacedName + + jwtEntry := entry.(*api.JWTProviderConfigEntry) + r.Log.Info("gatewaycontroller", "gateway items", r.cache.List(api.APIGateway)) + for _, gwEntry := range r.cache.List(api.APIGateway) { + gateway := gwEntry.(*api.APIGatewayConfigEntry) + LISTENER_LOOP: + for _, listener := range gateway.Listeners { + + r.Log.Info("override names", "listener", fmt.Sprintf("%#v", listener)) + if listener.Override != nil && listener.Override.JWT != nil { + for _, provider := range listener.Override.JWT.Providers { + r.Log.Info("override names", "provider", provider.Name, "entry", jwtEntry.Name) + if provider.Name == jwtEntry.Name { + gateways = append(gateways, common.EntryToNamespacedName(gateway)) + continue LISTENER_LOOP + } + } + } + + if listener.Default != nil && listener.Default.JWT != nil { + for _, provider := range listener.Default.JWT.Providers { + if provider.Name == jwtEntry.Name { + gateways = append(gateways, common.EntryToNamespacedName(gateway)) + continue LISTENER_LOOP + } + } + } + } + } + return gateways + } +} + func gatewayReferencesCertificate(certificateKey api.ResourceReference, gateway *api.APIGatewayConfigEntry) bool { for _, listener := range gateway.Listeners { for _, cert := range listener.TLS.Certificates { @@ -841,7 +933,7 @@ func (c *GatewayController) filterFiltersForExternalRefs(ctx context.Context, ro for _, filter := range filters { var externalFilter client.Object - //check to see if we need to grab this filter + // check to see if we need to grab this filter if filter.ExtensionRef == nil { continue } @@ -850,33 +942,66 @@ func (c *GatewayController) filterFiltersForExternalRefs(ctx context.Context, ro externalFilter = &v1alpha1.RouteRetryFilter{} case v1alpha1.RouteTimeoutFilterKind: externalFilter = &v1alpha1.RouteTimeoutFilter{} + case v1alpha1.RouteAuthFilterKind: + externalFilter = &v1alpha1.RouteAuthFilter{} default: continue } - //get object from API + // get object from API err := c.Client.Get(ctx, client.ObjectKey{ Name: string(filter.ExtensionRef.Name), Namespace: route.Namespace, }, externalFilter) - if err != nil { if k8serrors.IsNotFound(err) { c.Log.Info(fmt.Sprintf("externalref %s:%s not found: %v", filter.ExtensionRef.Kind, filter.ExtensionRef.Name, err)) - //ignore, the validation call should mark this route as error + // ignore, the validation call should mark this route as error continue } else { return nil, err } } - //add external ref (or error) to resource map for this route + // add external ref (or error) to resource map for this route resources.AddExternalFilter(externalFilter) externalFilters = append(externalFilters, externalFilter) } return externalFilters, nil } +func (c *GatewayController) getRelatedGatewayPolicies(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]v1alpha1.GatewayPolicy, error) { + var list v1alpha1.GatewayPolicyList + + if err := c.Client.List(ctx, &list, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gateway.String()), + }); err != nil { + return nil, err + } + + // add all policies to the resourcemap + for _, policy := range list.Items { + resources.AddGatewayPolicy(&policy) + } + + return list.Items, nil +} + +func (c *GatewayController) getJWTProviders(ctx context.Context, resources *common.ResourceMap) ([]v1alpha1.JWTProvider, error) { + var list v1alpha1.JWTProviderList + + if err := c.Client.List(ctx, &list, &client.ListOptions{}); err != nil { + return nil, err + } + + // add all policies to the resourcemap + for _, provider := range list.Items { + resources.AddJWTProvider(&provider) + } + + return list.Items, nil +} + func (c *GatewayController) getRelatedTCPRoutes(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]gwv1alpha2.TCPRoute, error) { var list gwv1alpha2.TCPRouteList @@ -1040,7 +1165,6 @@ func (c *GatewayController) fetchMeshService(ctx context.Context, resources *com } func (c *GatewayController) fetchServicesForEndpoints(ctx context.Context, resources *common.ResourceMap, key types.NamespacedName) error { - if shouldIgnore(key.Namespace, c.denyK8sNamespacesSet, c.allowK8sNamespacesSet) { return nil } diff --git a/control-plane/api-gateway/controllers/index.go b/control-plane/api-gateway/controllers/index.go index 131ec383e7..46c1f98459 100644 --- a/control-plane/api-gateway/controllers/index.go +++ b/control-plane/api-gateway/controllers/index.go @@ -6,6 +6,8 @@ package controllers import ( "context" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -19,17 +21,23 @@ const ( // Naming convention: TARGET_REFERENCE. GatewayClass_GatewayClassConfigIndex = "__gatewayclass_referencing_gatewayclassconfig" GatewayClass_ControllerNameIndex = "__gatewayclass_controller_name" - Gateway_GatewayClassIndex = "__gateway_referencing_gatewayclass" - HTTPRoute_GatewayIndex = "__httproute_referencing_gateway" - HTTPRoute_ServiceIndex = "__httproute_referencing_service" - HTTPRoute_MeshServiceIndex = "__httproute_referencing_mesh_service" - TCPRoute_GatewayIndex = "__tcproute_referencing_gateway" - TCPRoute_ServiceIndex = "__tcproute_referencing_service" - TCPRoute_MeshServiceIndex = "__tcproute_referencing_mesh_service" - MeshService_PeerIndex = "__meshservice_referencing_peer" - Secret_GatewayIndex = "__secret_referencing_gateway" - HTTPRoute_RouteRetryFilterIndex = "__httproute_referencing_retryfilter" - HTTPRoute_RouteTimeoutFilterIndex = "__httproute_referencing_timeoutfilter" + + Gateway_GatewayClassIndex = "__gateway_referencing_gatewayclass" + + HTTPRoute_GatewayIndex = "__httproute_referencing_gateway" + HTTPRoute_ServiceIndex = "__httproute_referencing_service" + HTTPRoute_MeshServiceIndex = "__httproute_referencing_mesh_service" + HTTPRoute_RouteRetryFilterIndex = "__httproute_referencing_retryfilter" + HTTPRoute_RouteTimeoutFilterIndex = "__httproute_referencing_timeoutfilter" + HTTPRoute_RouteAuthFilterIndex = "__httproute_referencing_routeauthfilter" + + TCPRoute_GatewayIndex = "__tcproute_referencing_gateway" + TCPRoute_ServiceIndex = "__tcproute_referencing_service" + TCPRoute_MeshServiceIndex = "__tcproute_referencing_mesh_service" + + MeshService_PeerIndex = "__meshservice_referencing_peer" + Secret_GatewayIndex = "__secret_referencing_gateway" + Gatewaypolicy_GatewayIndex = "__gatewaypolicy_referencing_gateway" ) // RegisterFieldIndexes registers all of the field indexes for the API gateway controllers. @@ -116,6 +124,16 @@ var indexes = []index{ target: &gwv1beta1.HTTPRoute{}, indexerFunc: filtersForHTTPRoute, }, + { + name: HTTPRoute_RouteAuthFilterIndex, + target: &gwv1beta1.HTTPRoute{}, + indexerFunc: filtersForHTTPRoute, + }, + { + name: Gatewaypolicy_GatewayIndex, + target: &v1alpha1.GatewayPolicy{}, + indexerFunc: gatewayForGatewayPolicy, + }, } // gatewayClassConfigForGatewayClass creates an index of every GatewayClassConfig referenced by a GatewayClass. @@ -293,10 +311,10 @@ func filtersForHTTPRoute(o client.Object) []string { FILTERS_LOOP: for _, filter := range rule.Filters { if common.FilterIsExternalFilter(filter) { - //TODO this seems like its type agnostic, so this might just work without having to make - //multiple index functions per custom filter type? + // TODO this seems like its type agnostic, so this might just work without having to make + // multiple index functions per custom filter type? - //index external filters + // index external filters filter := common.IndexedNamespacedNameWithDefault(string(filter.ExtensionRef.Name), nilString, route.Namespace).String() for _, member := range filters { if member == filter { @@ -307,7 +325,7 @@ func filtersForHTTPRoute(o client.Object) []string { } } - //same thing but over the backend refs + // same thing but over the backend refs BACKEND_LOOP: for _, ref := range rule.BackendRefs { for _, filter := range ref.Filters { @@ -325,3 +343,24 @@ func filtersForHTTPRoute(o client.Object) []string { } return filters } + +func gatewayForGatewayPolicy(o client.Object) []string { + gatewayPolicy := o.(*v1alpha1.GatewayPolicy) + + targetGateway := gatewayPolicy.Spec.TargetRef + if targetGateway.Group == gwv1beta1.GroupVersion.String() && targetGateway.Kind == common.KindGateway { + policyNamespace := gatewayPolicy.Namespace + if policyNamespace == "" { + policyNamespace = "default" + } + targetNS := targetGateway.Namespace + if targetNS == "" { + targetNS = policyNamespace + } + + namespacedName := types.NamespacedName{Name: targetGateway.Name, Namespace: targetNS} + return []string{namespacedName.String()} + } + + return []string{} +} diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index a4063d6147..6d9c636e33 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -17,6 +17,8 @@ const ( SamenessGroup string = "samenessgroup" JWTProvider string = "jwtprovider" ControlPlaneRequestLimit string = "controlplanerequestlimit" + RouteAuthFilter string = "routeauthfilter" + GatewayPolicy string = "gatewaypolicy" Global string = "global" Mesh string = "mesh" diff --git a/control-plane/api/v1alpha1/gatewaypolicy_types.go b/control-plane/api/v1alpha1/gatewaypolicy_types.go new file mode 100644 index 0000000000..76fd0772a8 --- /dev/null +++ b/control-plane/api/v1alpha1/gatewaypolicy_types.go @@ -0,0 +1,135 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func init() { + SchemeBuilder.Register(&GatewayPolicy{}, &GatewayPolicyList{}) +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// GatewayPolicy is the Schema for the gatewaypolicies API. +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +type GatewayPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GatewayPolicySpec `json:"spec,omitempty"` + Status GatewayPolicyStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// GatewayPolicyList contains a list of GatewayPolicy. +type GatewayPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []GatewayPolicy `json:"items"` +} + +// GatewayPolicySpec defines the desired state of GatewayPolicy. +type GatewayPolicySpec struct { + // TargetRef identifies an API object to apply policy to. + TargetRef PolicyTargetReference `json:"targetRef"` + //+kubebuilder:validation:Optional + Override *GatewayPolicyConfig `json:"override,omitempty"` + //+kubebuilder:validation:Optional + Default *GatewayPolicyConfig `json:"default,omitempty"` +} + +// PolicyTargetReference identifies the target that the policy applies to. +type PolicyTargetReference struct { + // Group is the group of the target resource. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + Group string `json:"group"` + + // Kind is kind of the target resource. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + Kind string `json:"kind"` + + // Name is the name of the target resource. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + Name string `json:"name"` + + // Namespace is the namespace of the referent. When unspecified, the local + // namespace is inferred. Even when policy targets a resource in a different + // namespace, it may only apply to traffic originating from the same + // namespace as the policy. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +optional + Namespace string `json:"namespace,omitempty"` + + // SectionName refers to the listener targeted by this policy. + SectionName *gwv1beta1.SectionName `json:"sectionName,omitempty"` +} + +type GatewayPolicyConfig struct { + //+kubebuilder:validation:Optional + JWT *GatewayJWTRequirement `json:"jwt,omitempty"` +} + +// GatewayJWTRequirement holds the list of JWT providers to be verified against. +type GatewayJWTRequirement struct { + // Providers is a list of providers to consider when verifying a JWT. + Providers []*GatewayJWTProvider `json:"providers"` +} + +// GatewayJWTProvider holds the provider and claim verification information. +type GatewayJWTProvider struct { + // Name is the name of the JWT provider. There MUST be a corresponding + // "jwt-provider" config entry with this name. + Name string `json:"name"` + + // VerifyClaims is a list of additional claims to verify in a JWT's payload. + VerifyClaims []*GatewayJWTClaimVerification `json:"verifyClaims,omitempty"` +} + +// GatewayJWTClaimVerification holds the actual claim information to be verified. +type GatewayJWTClaimVerification struct { + // Path is the path to the claim in the token JSON. + Path []string `json:"path"` + + // Value is the expected value at the given path: + // - If the type at the path is a list then we verify + // that this value is contained in the list. + // + // - If the type at the path is a string then we verify + // that this value matches. + Value string `json:"value"` +} + +// GatewayPolicyStatus defines the observed state of the gateway. +type GatewayPolicyStatus struct { + // Conditions describe the current conditions of the Policy. + // + // + // Known condition types are: + // + // * "Accepted" + // * "ResolvedRefs" + // + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=8 + // +kubebuilder:default={{type: "Accepted", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"},{type: "ResolvedRefs", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}} + Conditions []metav1.Condition `json:"conditions,omitempty"` +} diff --git a/control-plane/api/v1alpha1/gatewaypolicy_webhook.go b/control-plane/api/v1alpha1/gatewaypolicy_webhook.go new file mode 100644 index 0000000000..12bc30416e --- /dev/null +++ b/control-plane/api/v1alpha1/gatewaypolicy_webhook.go @@ -0,0 +1,82 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "context" + "fmt" + "net/http" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +const Gatewaypolicy_GatewayIndex = "__gatewaypolicy_referencing_gateway" + +// +kubebuilder:object:generate=false + +type GatewayPolicyWebhook struct { + Logger logr.Logger + + // ConsulMeta contains metadata specific to the Consul installation. + ConsulMeta common.ConsulMeta + + decoder *admission.Decoder + client.Client +} + +// +kubebuilder:webhook:verbs=create;update,path=/validate-v1alpha1-gatewaypolicy,mutating=false,failurePolicy=fail,groups=consul.hashicorp.com,resources=gatewaypolicies,versions=v1alpha1,name=validate-gatewaypolicy.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *GatewayPolicyWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var resource GatewayPolicy + err := v.decoder.Decode(req, &resource) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + var list GatewayPolicyList + + gwNamespaceName := types.NamespacedName{Name: resource.Spec.TargetRef.Name, Namespace: resource.Namespace} + err = v.Client.List(ctx, &list, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gwNamespaceName.String()), + }) + + if err != nil { + v.Logger.Error(err, "error getting list of policies referencing gateway") + return admission.Errored(http.StatusInternalServerError, err) + } + + for _, policy := range list.Items { + if differentPolicySameTarget(resource, policy) { + return admission.Denied(fmt.Sprintf("policy targets gateway listener %q that is already the target of an existing policy %q", DerefStringOr(resource.Spec.TargetRef.SectionName, ""), policy.Name)) + } + } + + return admission.Allowed("gateway policy is valid") +} + +func differentPolicySameTarget(resource, policy GatewayPolicy) bool { + return resource.Name != policy.Name && + resource.Spec.TargetRef.Name == policy.Spec.TargetRef.Name && + resource.Spec.TargetRef.Group == policy.Spec.TargetRef.Group && + resource.Spec.TargetRef.Kind == policy.Spec.TargetRef.Kind && + resource.Spec.TargetRef.Namespace == policy.Spec.TargetRef.Namespace && + DerefStringOr(resource.Spec.TargetRef.SectionName, "") == DerefStringOr(policy.Spec.TargetRef.SectionName, "") +} + +func (v *GatewayPolicyWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} + +func DerefStringOr[T ~string, U ~string](v *T, val U) string { + if v == nil { + return string(val) + } + return string(*v) +} diff --git a/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go b/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go new file mode 100644 index 0000000000..99b2b55896 --- /dev/null +++ b/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go @@ -0,0 +1,282 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + "context" + "encoding/json" + "testing" + + logrtest "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestGatewayPolicyWebhook_Handle(t *testing.T) { + tests := map[string]struct { + existingResources []runtime.Object + newResource *GatewayPolicy + expAllow bool + expErrMessage string + }{ + "valid - no other policy targets listener": { + existingResources: []runtime.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gateway", + Namespace: "default", + }, + Spec: gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + }, + newResource: &GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "default", + }, + Spec: GatewayPolicySpec{ + TargetRef: PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: "Gateway", + Name: "my-gateway", + SectionName: ptrTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + expAllow: true, + }, + "valid - existing policy targets different gateway": { + existingResources: []runtime.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gateway", + Namespace: "default", + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: "", + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + }, + }, + }, + &GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy-2", + Namespace: "default", + }, + Spec: GatewayPolicySpec{ + TargetRef: PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: "Gateway", + Name: "another-gateway", + SectionName: ptrTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + }, + newResource: &GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewaypolicy", + Namespace: "default", + }, + Spec: GatewayPolicySpec{ + TargetRef: PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: "Gateway", + Name: "my-gateway", + SectionName: ptrTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + expAllow: true, + }, + + "valid - existing policy targets different listener on the same gateway": { + existingResources: []runtime.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "my-gateway", + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: "", + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + { + Name: "l2", + }, + }, + }, + }, + &GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy-2", + Namespace: "default", + }, + Spec: GatewayPolicySpec{ + TargetRef: PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: "Gateway", + Name: "my-gateway", + SectionName: ptrTo(gwv1beta1.SectionName("l2")), + }, + }, + }, + }, + newResource: &GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "default", + }, + Spec: GatewayPolicySpec{ + TargetRef: PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: "Gateway", + Name: "my-gateway", + SectionName: ptrTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + expAllow: true, + }, + "invalid - existing policy targets same listener on same gateway": { + existingResources: []runtime.Object{ + &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gateway", + Namespace: "default", + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: "", + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + { + Name: "l2", + }, + }, + }, + }, + &GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "default", + }, + Spec: GatewayPolicySpec{ + TargetRef: PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: "Gateway", + Name: "my-gateway", + SectionName: ptrTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + }, + newResource: &GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy-2", + Namespace: "default", + }, + Spec: GatewayPolicySpec{ + TargetRef: PolicyTargetReference{ + Group: gwv1beta1.GroupVersion.String(), + Kind: "Gateway", + Name: "my-gateway", + SectionName: ptrTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + expAllow: false, + expErrMessage: "policy targets gateway listener \"l1\" that is already the target of an existing policy \"my-policy\"", + }, + } + for name, tt := range tests { + name := name + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + marshalledRequestObject, err := json.Marshal(tt.newResource) + require.NoError(t, err) + s := runtime.NewScheme() + s.AddKnownTypes(GroupVersion, &GatewayPolicy{}, &GatewayPolicyList{}) + s.AddKnownTypes(gwv1beta1.SchemeGroupVersion, &gwv1beta1.Gateway{}) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.existingResources...).WithIndex(&GatewayPolicy{}, Gatewaypolicy_GatewayIndex, gatewayForGatewayPolicy).Build() + + var list GatewayPolicyList + + gwNamespaceName := types.NamespacedName{Name: "my-gateway", Namespace: "default"} + fakeClient.List(ctx, &list, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gwNamespaceName.String()), + }) + + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + v := &GatewayPolicyWebhook{ + Logger: logrtest.New(t), + decoder: decoder, + Client: fakeClient, + } + + response := v.Handle(ctx, admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Name: tt.newResource.Name, + Operation: admissionv1.Create, + Object: runtime.RawExtension{ + Raw: marshalledRequestObject, + }, + }, + }) + + require.Equal(t, tt.expAllow, response.Allowed) + if tt.expErrMessage != "" { + require.Equal(t, tt.expErrMessage, string(response.AdmissionResponse.Result.Reason)) + } + }) + } +} + +func ptrTo[T any](v T) *T { + return &v +} + +func gatewayForGatewayPolicy(o client.Object) []string { + gatewayPolicy := o.(*GatewayPolicy) + + targetGateway := gatewayPolicy.Spec.TargetRef + // gateway policy is 1to1 + if targetGateway.Group == "gateway.networking.k8s.io/v1beta1" && targetGateway.Kind == "Gateway" { + policyNamespace := gatewayPolicy.Namespace + if policyNamespace == "" { + policyNamespace = "default" + } + targetNS := targetGateway.Namespace + if targetNS == "" { + targetNS = policyNamespace + } + + return []string{types.NamespacedName{Name: targetGateway.Name, Namespace: targetNS}.String()} + } + + return []string{} +} diff --git a/control-plane/api/v1alpha1/routeauthfilter_types.go b/control-plane/api/v1alpha1/routeauthfilter_types.go new file mode 100644 index 0000000000..1fb2b02030 --- /dev/null +++ b/control-plane/api/v1alpha1/routeauthfilter_types.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + RouteAuthFilterKind = "RouteAuthFilter" +) + +func init() { + SchemeBuilder.Register(&RouteAuthFilter{}, &RouteAuthFilterList{}) +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// RouteAuthFilter is the Schema for the routeauthfilters API. +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +type RouteAuthFilter struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RouteAuthFilterSpec `json:"spec,omitempty"` + Status RouteAuthFilterStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// RouteAuthFilterList contains a list of RouteAuthFilter. +type RouteAuthFilterList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RouteAuthFilter `json:"items"` +} + +// RouteAuthFilterSpec defines the desired state of RouteAuthFilter. +type RouteAuthFilterSpec struct { + // This re-uses the JWT requirement type from Gateway Policy Types. + //+kubebuilder:validation:Optional + JWT *GatewayJWTRequirement `json:"jwt,omitempty"` +} + +// RouteAuthFilterStatus defines the observed state of the gateway. +type RouteAuthFilterStatus struct { + // Conditions describe the current conditions of the Filter. + // + // + // Known condition types are: + // + // * "Accepted" + // * "ResolvedRefs" + // + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=8 + // +kubebuilder:default={{type: "Accepted", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"},{type: "ResolvedRefs", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}} + Conditions []metav1.Condition `json:"conditions,omitempty"` +} diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 23269fd8f0..cc8447c6c2 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -10,6 +10,7 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/gateway-api/apis/v1beta1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -566,6 +567,205 @@ func (in *GatewayClassConfigSpec) DeepCopy() *GatewayClassConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayJWTClaimVerification) DeepCopyInto(out *GatewayJWTClaimVerification) { + *out = *in + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTClaimVerification. +func (in *GatewayJWTClaimVerification) DeepCopy() *GatewayJWTClaimVerification { + if in == nil { + return nil + } + out := new(GatewayJWTClaimVerification) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayJWTProvider) DeepCopyInto(out *GatewayJWTProvider) { + *out = *in + if in.VerifyClaims != nil { + in, out := &in.VerifyClaims, &out.VerifyClaims + *out = make([]*GatewayJWTClaimVerification, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(GatewayJWTClaimVerification) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTProvider. +func (in *GatewayJWTProvider) DeepCopy() *GatewayJWTProvider { + if in == nil { + return nil + } + out := new(GatewayJWTProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayJWTRequirement) DeepCopyInto(out *GatewayJWTRequirement) { + *out = *in + if in.Providers != nil { + in, out := &in.Providers, &out.Providers + *out = make([]*GatewayJWTProvider, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(GatewayJWTProvider) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTRequirement. +func (in *GatewayJWTRequirement) DeepCopy() *GatewayJWTRequirement { + if in == nil { + return nil + } + out := new(GatewayJWTRequirement) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayPolicy) DeepCopyInto(out *GatewayPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicy. +func (in *GatewayPolicy) DeepCopy() *GatewayPolicy { + if in == nil { + return nil + } + out := new(GatewayPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayPolicyConfig) DeepCopyInto(out *GatewayPolicyConfig) { + *out = *in + if in.JWT != nil { + in, out := &in.JWT, &out.JWT + *out = new(GatewayJWTRequirement) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyConfig. +func (in *GatewayPolicyConfig) DeepCopy() *GatewayPolicyConfig { + if in == nil { + return nil + } + out := new(GatewayPolicyConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayPolicyList) DeepCopyInto(out *GatewayPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GatewayPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyList. +func (in *GatewayPolicyList) DeepCopy() *GatewayPolicyList { + if in == nil { + return nil + } + out := new(GatewayPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayPolicySpec) DeepCopyInto(out *GatewayPolicySpec) { + *out = *in + in.TargetRef.DeepCopyInto(&out.TargetRef) + if in.Override != nil { + in, out := &in.Override, &out.Override + *out = new(GatewayPolicyConfig) + (*in).DeepCopyInto(*out) + } + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = new(GatewayPolicyConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicySpec. +func (in *GatewayPolicySpec) DeepCopy() *GatewayPolicySpec { + if in == nil { + return nil + } + out := new(GatewayPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayPolicyStatus) DeepCopyInto(out *GatewayPolicyStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyStatus. +func (in *GatewayPolicyStatus) DeepCopy() *GatewayPolicyStatus { + if in == nil { + return nil + } + out := new(GatewayPolicyStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayServiceTLSConfig) DeepCopyInto(out *GatewayServiceTLSConfig) { *out = *in @@ -2072,6 +2272,26 @@ func (in *PeeringMeshConfig) DeepCopy() *PeeringMeshConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyTargetReference) DeepCopyInto(out *PolicyTargetReference) { + *out = *in + if in.SectionName != nil { + in, out := &in.SectionName, &out.SectionName + *out = new(v1beta1.SectionName) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyTargetReference. +func (in *PolicyTargetReference) DeepCopy() *PolicyTargetReference { + if in == nil { + return nil + } + out := new(PolicyTargetReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PrioritizeByLocality) DeepCopyInto(out *PrioritizeByLocality) { *out = *in @@ -2286,6 +2506,107 @@ func (in *RingHashConfig) DeepCopy() *RingHashConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteAuthFilter) DeepCopyInto(out *RouteAuthFilter) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilter. +func (in *RouteAuthFilter) DeepCopy() *RouteAuthFilter { + if in == nil { + return nil + } + out := new(RouteAuthFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RouteAuthFilter) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteAuthFilterList) DeepCopyInto(out *RouteAuthFilterList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RouteAuthFilter, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterList. +func (in *RouteAuthFilterList) DeepCopy() *RouteAuthFilterList { + if in == nil { + return nil + } + out := new(RouteAuthFilterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RouteAuthFilterList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteAuthFilterSpec) DeepCopyInto(out *RouteAuthFilterSpec) { + *out = *in + if in.JWT != nil { + in, out := &in.JWT, &out.JWT + *out = new(GatewayJWTRequirement) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterSpec. +func (in *RouteAuthFilterSpec) DeepCopy() *RouteAuthFilterSpec { + if in == nil { + return nil + } + out := new(RouteAuthFilterSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteAuthFilterStatus) DeepCopyInto(out *RouteAuthFilterStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterStatus. +func (in *RouteAuthFilterStatus) DeepCopy() *RouteAuthFilterStatus { + if in == nil { + return nil + } + out := new(RouteAuthFilterStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RouteRetryFilter) DeepCopyInto(out *RouteRetryFilter) { *out = *in diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml new file mode 100644 index 0000000000..481e5a4b90 --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml @@ -0,0 +1,296 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: gatewaypolicies.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: GatewayPolicy + listKind: GatewayPolicyList + plural: gatewaypolicies + singular: gatewaypolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: GatewayPolicy is the Schema for the gatewaypolicies API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GatewayPolicySpec defines the desired state of GatewayPolicy. + properties: + default: + properties: + jwt: + description: GatewayJWTRequirement holds the list of JWT providers + to be verified against. + properties: + providers: + description: Providers is a list of providers to consider + when verifying a JWT. + items: + description: GatewayJWTProvider holds the provider and claim + verification information. + properties: + name: + description: Name is the name of the JWT provider. There + MUST be a corresponding "jwt-provider" config entry + with this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional claims + to verify in a JWT's payload. + items: + description: GatewayJWTClaimVerification holds the + actual claim information to be verified. + properties: + path: + description: Path is the path to the claim in + the token JSON. + items: + type: string + type: array + value: + description: "Value is the expected value at the + given path: - If the type at the path is a list + then we verify that this value is contained + in the list. \n - If the type at the path is + a string then we verify that this value matches." + type: string + required: + - path + - value + type: object + type: array + required: + - name + type: object + type: array + required: + - providers + type: object + type: object + override: + properties: + jwt: + description: GatewayJWTRequirement holds the list of JWT providers + to be verified against. + properties: + providers: + description: Providers is a list of providers to consider + when verifying a JWT. + items: + description: GatewayJWTProvider holds the provider and claim + verification information. + properties: + name: + description: Name is the name of the JWT provider. There + MUST be a corresponding "jwt-provider" config entry + with this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional claims + to verify in a JWT's payload. + items: + description: GatewayJWTClaimVerification holds the + actual claim information to be verified. + properties: + path: + description: Path is the path to the claim in + the token JSON. + items: + type: string + type: array + value: + description: "Value is the expected value at the + given path: - If the type at the path is a list + then we verify that this value is contained + in the list. \n - If the type at the path is + a string then we verify that this value matches." + type: string + required: + - path + - value + type: object + type: array + required: + - name + type: object + type: array + required: + - providers + type: object + type: object + targetRef: + description: TargetRef identifies an API object to apply policy to. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + minLength: 1 + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. When + unspecified, the local namespace is inferred. Even when policy + targets a resource in a different namespace, it may only apply + to traffic originating from the same namespace as the policy. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: SectionName refers to the listener targeted by this + policy. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + required: + - targetRef + type: object + status: + description: GatewayPolicyStatus defines the observed state of the gateway + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: ResolvedRefs + description: "Conditions describe the current conditions of the Policy. + \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml new file mode 100644 index 0000000000..f08a9336a1 --- /dev/null +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml @@ -0,0 +1,196 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + creationTimestamp: null + name: routeauthfilters.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: RouteAuthFilter + listKind: RouteAuthFilterList + plural: routeauthfilters + singular: routeauthfilter + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: RouteAuthFilter is the Schema for the httpauthfilters API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RouteAuthFilterSpec defines the desired state of RouteAuthFilter. + properties: + jwt: + description: This re-uses the JWT requirement type from Gateway Policy + Types. + properties: + providers: + description: Providers is a list of providers to consider when + verifying a JWT. + items: + description: GatewayJWTProvider holds the provider and claim + verification information. + properties: + name: + description: Name is the name of the JWT provider. There + MUST be a corresponding "jwt-provider" config entry with + this name. + type: string + verifyClaims: + description: VerifyClaims is a list of additional claims + to verify in a JWT's payload. + items: + description: GatewayJWTClaimVerification holds the actual + claim information to be verified. + properties: + path: + description: Path is the path to the claim in the + token JSON. + items: + type: string + type: array + value: + description: "Value is the expected value at the given + path: - If the type at the path is a list then we + verify that this value is contained in the list. + \n - If the type at the path is a string then we + verify that this value matches." + type: string + required: + - path + - value + type: object + type: array + required: + - name + type: object + type: array + required: + - providers + type: object + type: object + status: + description: RouteAuthFilterStatus defines the observed state of the gateway. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: ResolvedRefs + description: "Conditions describe the current conditions of the Filter. + \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index 0861f9253a..9fa4043e66 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -323,3 +323,31 @@ webhooks: resources: - terminatinggateways sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-v1alpha1-gatewaypolicy + failurePolicy: Fail + name: validate-gatewaypolicy.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - gatewaypolicies + sideEffects: None diff --git a/control-plane/go.mod b/control-plane/go.mod index 8625d7ca66..486a09b875 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -83,7 +83,7 @@ require ( github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.14.1 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -123,7 +123,7 @@ require ( github.com/linode/linodego v0.7.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 1091e90f90..f9cf933ea2 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -140,8 +140,8 @@ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= @@ -396,8 +396,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -742,6 +742,7 @@ 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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go deleted file mode 100644 index 093b1ef908..0000000000 --- a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package mutatingwebhookconfiguration - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" -) - -// UpdateWithCABundle iterates over every webhook on the specified webhook configuration and updates -// their caBundle with the the specified CA. -func UpdateWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookConfigName string, caCert []byte) error { - if len(caCert) == 0 { - return errors.New("no CA certificate in the bundle") - } - value := base64.StdEncoding.EncodeToString(caCert) - webhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) - - if err != nil { - return err - } - type patch struct { - Op string `json:"op,omitempty"` - Path string `json:"path,omitempty"` - Value string `json:"value,omitempty"` - } - - var patches []patch - for i := range webhookCfg.Webhooks { - patches = append(patches, patch{ - Op: "add", - Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), - Value: value, - }) - } - patchesJson, err := json.Marshal(patches) - if err != nil { - return err - } - - if _, err = clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(ctx, webhookConfigName, types.JSONPatchType, patchesJson, metav1.PatchOptions{}); err != nil { - return err - } - - return nil -} diff --git a/control-plane/helper/webhook-configuration/webhook_configuration.go b/control-plane/helper/webhook-configuration/webhook_configuration.go new file mode 100644 index 0000000000..02dcc54cd4 --- /dev/null +++ b/control-plane/helper/webhook-configuration/webhook_configuration.go @@ -0,0 +1,98 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package webhookconfiguration + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + admissionv1 "k8s.io/api/admissionregistration/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" +) + +// UpdateWithCABundle iterates over every webhook on the specified webhook configuration and updates +// their caBundle with the the specified CA. +func UpdateWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookConfigName string, caCert []byte) error { + if len(caCert) == 0 { + return errors.New("no CA certificate in the bundle") + } + + mutatingWebhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) + if err == nil { + return updateMutatingWebhooksWithCABundle(ctx, clientset, mutatingWebhookCfg, caCert) + } + + if !k8serrors.IsNotFound(err) { + return err + } + + validatingWebhookCfg, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) + if err != nil { + return err + } + + return updateValidatingWebhooksWithCABundle(ctx, clientset, validatingWebhookCfg, caCert) +} + +func updateMutatingWebhooksWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookCfg *admissionv1.MutatingWebhookConfiguration, caCert []byte) error { + value := base64.StdEncoding.EncodeToString(caCert) + type patch struct { + Op string `json:"op,omitempty"` + Path string `json:"path,omitempty"` + Value string `json:"value,omitempty"` + } + + var patches []patch + for i := range webhookCfg.Webhooks { + patches = append(patches, patch{ + Op: "add", + Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), + Value: value, + }) + } + patchesJSON, err := json.Marshal(patches) + if err != nil { + return err + } + + if _, err = clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(ctx, webhookCfg.Name, types.JSONPatchType, patchesJSON, metav1.PatchOptions{}); err != nil { + return err + } + + return nil +} + +func updateValidatingWebhooksWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookCfg *admissionv1.ValidatingWebhookConfiguration, caCert []byte) error { + value := base64.StdEncoding.EncodeToString(caCert) + type patch struct { + Op string `json:"op,omitempty"` + Path string `json:"path,omitempty"` + Value string `json:"value,omitempty"` + } + + var patches []patch + for i := range webhookCfg.Webhooks { + patches = append(patches, patch{ + Op: "add", + Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), + Value: value, + }) + } + patchesJSON, err := json.Marshal(patches) + if err != nil { + return err + } + + if _, err = clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Patch(ctx, webhookCfg.Name, types.JSONPatchType, patchesJSON, metav1.PatchOptions{}); err != nil { + return err + } + + return nil +} diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go b/control-plane/helper/webhook-configuration/webhook_configuration_test.go similarity index 60% rename from control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go rename to control-plane/helper/webhook-configuration/webhook_configuration_test.go index be1a3b5c64..e574020f5e 100644 --- a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go +++ b/control-plane/helper/webhook-configuration/webhook_configuration_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package mutatingwebhookconfiguration +package webhookconfiguration import ( "context" @@ -45,3 +45,27 @@ func TestUpdateWithCABundle_patchesExistingConfiguration(t *testing.T) { require.NoError(t, err) require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) } + +func TestUpdateWithCABundle_patchesExistingConfigurationForValidating(t *testing.T) { + caBundleOne := []byte("ca-bundle-for-mwc") + ctx := context.Background() + clientset := fake.NewSimpleClientset() + + mwc := &admissionv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mwc-one", + }, + Webhooks: []admissionv1.ValidatingWebhook{ + { + Name: "webhook-under-test", + }, + }, + } + mwcCreated, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) + require.NoError(t, err) + err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) + require.NoError(t, err) + mwcFetched, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) +} diff --git a/control-plane/subcommand/inject-connect/v1controllers.go b/control-plane/subcommand/inject-connect/v1controllers.go index 57ac1691c3..1270458851 100644 --- a/control-plane/subcommand/inject-connect/v1controllers.go +++ b/control-plane/subcommand/inject-connect/v1controllers.go @@ -23,12 +23,11 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" "github.com/hashicorp/consul-k8s/control-plane/controllers" - mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" + webhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/webhook-configuration" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) func (c *Command) configureV1Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { - // Create Consul API config object. consulConfig := c.consul.ConsulClientConfig() @@ -139,7 +138,6 @@ func (c *Command) configureV1Controllers(ctx context.Context, mgr manager.Manage Partition: c.consul.Partition, Datacenter: c.consul.Datacenter, }) - if err != nil { setupLog.Error(err, "unable to create controller", "controller", "Gateway") return err @@ -458,6 +456,13 @@ func (c *Command) configureV1Controllers(ctx context.Context, mgr manager.Manage ConsulMeta: consulMeta, }}) + mgr.GetWebhookServer().Register("/validate-v1alpha1-gatewaypolicy", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.GatewayPolicyWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.GatewayPolicy), + ConsulMeta: consulMeta, + }}) + if c.flagEnableWebhookCAUpdate { err = c.updateWebhookCABundle(ctx) if err != nil { @@ -476,7 +481,7 @@ func (c *Command) updateWebhookCABundle(ctx context.Context) error { if err != nil { return err } - err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) + err = webhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) if err != nil { return err } diff --git a/control-plane/subcommand/webhook-cert-manager/command.go b/control-plane/subcommand/webhook-cert-manager/command.go index 4d85565b62..ae9d75d29e 100644 --- a/control-plane/subcommand/webhook-cert-manager/command.go +++ b/control-plane/subcommand/webhook-cert-manager/command.go @@ -17,11 +17,6 @@ import ( "syscall" "time" - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" "github.com/mitchellh/cli" @@ -29,6 +24,12 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + webhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/webhook-configuration" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( @@ -266,7 +267,7 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete } iterLog.Info("Updating webhook configuration") - err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, bundle.WebhookConfigName, bundle.CACert) + err = webhookconfiguration.UpdateWithCABundle(ctx, c.clientset, bundle.WebhookConfigName, bundle.CACert) if err != nil { iterLog.Error("Error updating webhook configuration") return err @@ -309,7 +310,7 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete } iterLog.Info("Updating webhook configuration with new CA") - err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, clientset, bundle.WebhookConfigName, bundle.CACert) + err = webhookconfiguration.UpdateWithCABundle(ctx, clientset, bundle.WebhookConfigName, bundle.CACert) if err != nil { iterLog.Error("Error updating webhook configuration", "err", err) return err @@ -320,11 +321,21 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete // webhookUpdated verifies if every caBundle on the specified webhook configuration matches the desired CA certificate. // It returns true if the CA is up-to date and false if it needs to be updated. func (c *Command) webhookUpdated(ctx context.Context, bundle cert.MetaBundle, clientset kubernetes.Interface) bool { - webhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) + mutatingWebhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) if err != nil { return false } - for _, webhook := range webhookCfg.Webhooks { + for _, webhook := range mutatingWebhookCfg.Webhooks { + if !bytes.Equal(webhook.ClientConfig.CABundle, bundle.CACert) { + return false + } + } + + validatingWebhookCfg, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) + if err != nil { + return false + } + for _, webhook := range validatingWebhookCfg.Webhooks { if !bytes.Equal(webhook.ClientConfig.CABundle, bundle.CACert) { return false } @@ -344,8 +355,12 @@ func (c webhookConfig) validate(ctx context.Context, client kubernetes.Interface if c.Name == "" { err = multierror.Append(err, errors.New(`config.Name cannot be ""`)) } else { - if _, err2 := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}); err2 != nil && k8serrors.IsNotFound(err2) { - err = multierror.Append(err, fmt.Errorf("MutatingWebhookConfiguration with name \"%s\" must exist in cluster", c.Name)) + _, mutHookErr := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}) + + _, validatingHookErr := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}) + + if (mutHookErr != nil && k8serrors.IsNotFound(mutHookErr)) && (validatingHookErr != nil && k8serrors.IsNotFound(validatingHookErr)) { + err = multierror.Append(err, fmt.Errorf("ValidatingWebhookConfiguration or MutatingWebhookConfiguration with name \"%s\" must exist in cluster", c.Name)) } } if c.SecretName == "" { @@ -387,10 +402,12 @@ func (c *Command) sendSignal(sig os.Signal) { c.sigCh <- sig } -const synopsis = "Starts the Consul Kubernetes webhook-cert-manager" -const help = ` +const ( + synopsis = "Starts the Consul Kubernetes webhook-cert-manager" + help = ` Usage: consul-k8s-control-plane webhook-cert-manager [options] Starts the Consul Kubernetes webhook-cert-manager that manages the lifecycle for webhook TLS certificates. ` +) diff --git a/control-plane/subcommand/webhook-cert-manager/command_test.go b/control-plane/subcommand/webhook-cert-manager/command_test.go index 31c98b0ebe..dd4b6504c0 100644 --- a/control-plane/subcommand/webhook-cert-manager/command_test.go +++ b/control-plane/subcommand/webhook-cert-manager/command_test.go @@ -10,8 +10,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager/mocks" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" @@ -22,6 +20,9 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager/mocks" ) func TestRun_ExitsCleanlyOnSignals(t *testing.T) { @@ -701,7 +702,7 @@ func TestValidate(t *testing.T) { SecretNamespace: "default", }, clientset: fake.NewSimpleClientset(), - expErr: `MutatingWebhookConfiguration with name "webhook-config-name" must exist in cluster`, + expErr: `ValidatingWebhookConfiguration or MutatingWebhookConfiguration with name "webhook-config-name" must exist in cluster`, }, "secretName": { config: webhookConfig{ From cf13889fcc4d220090f27f40fe083c36754696e4 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Fri, 15 Sep 2023 17:27:13 -0400 Subject: [PATCH 387/592] [NET-5318] feat: add v2 service account controller (#2947) feat: add v2 service account controller Implement the basic requirements of a new Service Account controller that registers Workload Identities via Consul's V2 API. Also lightly refactor some of the shared controller data in V2. Further tests and TODOs will be addressed in follow-up changes. --- control-plane/connect-inject/common/config.go | 36 ++ .../constants/annotations_and_labels.go | 4 + .../connect-inject/constants/constants.go | 3 + .../endpointsv2/endpoints_controller.go | 38 +- .../endpoints_controller_ent_test.go | 2 + .../endpointsv2/endpoints_controller_test.go | 59 ++- .../controllers/pod/pod_controller.go | 33 +- .../controllers/pod/pod_controller_test.go | 124 ++++--- .../serviceaccount_controller.go | 179 +++++++++ .../serviceaccount_controller_ent_test.go | 24 ++ .../serviceaccount_controller_test.go | 347 ++++++++++++++++++ control-plane/go.mod | 4 +- control-plane/go.sum | 8 +- control-plane/helper/test/test_util.go | 13 + .../inject-connect/v2controllers.go | 81 ++-- 15 files changed, 774 insertions(+), 181 deletions(-) create mode 100644 control-plane/connect-inject/common/config.go create mode 100644 control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go create mode 100644 control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go create mode 100644 control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go diff --git a/control-plane/connect-inject/common/config.go b/control-plane/connect-inject/common/config.go new file mode 100644 index 0000000000..7167f1ee43 --- /dev/null +++ b/control-plane/connect-inject/common/config.go @@ -0,0 +1,36 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import mapset "github.com/deckarep/golang-set" + +// K8sNamespaceConfig manages allow/deny Kubernetes namespaces. +type K8sNamespaceConfig struct { + // Only endpoints in the AllowK8sNamespacesSet are reconciled. + AllowK8sNamespacesSet mapset.Set + // Endpoints in the DenyK8sNamespacesSet are ignored. + DenyK8sNamespacesSet mapset.Set +} + +// ConsulTenancyConfig manages settings related to Consul namespaces and partitions. +type ConsulTenancyConfig struct { + // EnableConsulPartitions indicates that a user is running Consul Enterprise. + EnableConsulPartitions bool + // ConsulPartition is the Consul Partition to which this controller belongs. + ConsulPartition string + // EnableConsulNamespaces indicates that a user is running Consul Enterprise. + EnableConsulNamespaces bool + // ConsulDestinationNamespace is the name of the Consul namespace to create + // all config entries in. If EnableNSMirroring is true this is ignored. + ConsulDestinationNamespace string + // EnableNSMirroring causes Consul namespaces to be created to match the + // k8s namespace of any config entry custom resource. Config entries will + // be created in the matching Consul namespace. + EnableNSMirroring bool + // NSMirroringPrefix is an optional prefix that can be added to the Consul + // namespaces created while mirroring. For example, if it is set to "k8s-", + // then the k8s `default` namespace will be mirrored in Consul's + // `k8s-default` namespace. + NSMirroringPrefix string +} diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index bc28930eae..371820a5c4 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -230,6 +230,10 @@ const ( // of resources. ManagedByPodValue = "consul-k8s-pod-controller" + // ManagedByServiceAccountValue is used in Consul metadata to identify the manager + // of resources. + ManagedByServiceAccountValue = "consul-k8s-service-account-controller" + // AnnotationMeshDestinations is a list of upstreams to register with the // proxy. The service name should map to a Consul service namd and the local // port is the local port in the pod that the listener will bind to. It can diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index f9dccd4f14..0729207d24 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -22,6 +22,9 @@ const ( // ProxyDefaultHealthPort is the default HTTP health check port for the proxy. ProxyDefaultHealthPort = 21000 + // MetaKeyManagedBy is the meta key name for indicating which Kubernetes controller manages a Consul resource. + MetaKeyManagedBy = "managed-by" + // MetaKeyKubeNS is the meta key name for Kubernetes namespace used for the Consul services. MetaKeyKubeNS = "k8s-namespace" diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index 739f1bade2..e94f719a1b 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -1,5 +1,6 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 + package endpointsv2 import ( @@ -9,7 +10,6 @@ import ( "sort" "strings" - mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -29,36 +29,17 @@ import ( ) const ( - metaKeyManagedBy = "managed-by" - kindReplicaSet = "ReplicaSet" + kindReplicaSet = "ReplicaSet" ) type Controller struct { client.Client // ConsulServerConnMgr is the watcher for the Consul server addresses used to create Consul API v2 clients. ConsulServerConnMgr consul.ServerConnectionManager - // Only endpoints in the AllowK8sNamespacesSet are reconciled. - AllowK8sNamespacesSet mapset.Set - // Endpoints in the DenyK8sNamespacesSet are ignored. - DenyK8sNamespacesSet mapset.Set - // EnableConsulPartitions indicates that a user is running Consul Enterprise. - EnableConsulPartitions bool - // ConsulPartition is the Consul Partition to which this controller belongs. - ConsulPartition string - // EnableConsulNamespaces indicates that a user is running Consul Enterprise. - EnableConsulNamespaces bool - // ConsulDestinationNamespace is the name of the Consul namespace to create - // all config entries in. If EnableNSMirroring is true this is ignored. - ConsulDestinationNamespace string - // EnableNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any config entry custom resource. Config entries will - // be created in the matching Consul namespace. - EnableNSMirroring bool - // NSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - NSMirroringPrefix string + // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. + common.K8sNamespaceConfig + // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. + common.ConsulTenancyConfig Log logr.Logger @@ -227,7 +208,7 @@ func (r *Controller) registerService(ctx context.Context, resourceClient pbresou return nil } -// getServiceResource converts the given Consul service and metadata as a Consul resource API record. +// getServiceResource converts the given Consul service and metadata to a Consul resource API record. func (r *Controller) getServiceResource(svc *pbcatalog.Service, name, namespace, partition string, meta map[string]string) *pbresource.Resource { return &pbresource.Resource{ Id: getServiceID(name, namespace, partition), @@ -247,6 +228,7 @@ func getServiceID(name, namespace, partition string) *pbresource.ID { Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, + PeerName: constants.DefaultConsulPeer, }, } } @@ -301,8 +283,8 @@ func (r *Controller) getServiceVIPs(service corev1.Service) []string { func getServiceMeta(service corev1.Service) map[string]string { meta := map[string]string{ - constants.MetaKeyKubeNS: service.Namespace, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: service.Namespace, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, } //TODO: Support arbitrary meta injection via annotation? (see v1) return meta diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go index 6d4e5460c4..636a1ab923 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go @@ -9,6 +9,8 @@ import ( "testing" ) +// TODO: ConsulDestinationNamespace and EnableNSMirroring +/- prefix + // TODO(zalimeni) // Tests new Service registration in a non-default NS and Partition with namespaces set to mirroring func TestReconcile_CreateService_WithNamespaces(t *testing.T) { diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index caadfb8d9c..69bf14aec9 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -8,8 +8,6 @@ import ( "fmt" "testing" - "github.com/google/go-cmp/cmp/cmpopts" - mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" @@ -17,7 +15,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -56,7 +53,6 @@ type reconcileCase struct { } // TODO: Allow/deny namespaces for reconcile tests -// TODO: ConsulDestinationNamespace and EnableNSMirroring +/- prefix func TestReconcile_CreateService(t *testing.T) { t.Parallel() @@ -151,8 +147,8 @@ func TestReconcile_CreateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, }, @@ -268,8 +264,8 @@ func TestReconcile_CreateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, }, @@ -355,8 +351,8 @@ func TestReconcile_CreateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, }, @@ -464,8 +460,8 @@ func TestReconcile_CreateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, }, @@ -654,8 +650,8 @@ func TestReconcile_UpdateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, expectedResource: &pbresource.Resource{ @@ -697,8 +693,8 @@ func TestReconcile_UpdateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, }, @@ -802,8 +798,8 @@ func TestReconcile_UpdateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, expectedResource: &pbresource.Resource{ @@ -844,8 +840,8 @@ func TestReconcile_UpdateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, }, @@ -895,8 +891,8 @@ func TestReconcile_DeleteService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, }, @@ -1031,9 +1027,7 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { resp, err := ep.getWorkloadSelectorFromEndpoints(ctx, &pf, tc.endpoints) require.NoError(t, err) - // We don't care about order, so configure cmp.Diff to ignore slice order. - sorter := func(a, b string) bool { return a < b } - if diff := cmp.Diff(tc.expected, resp, protocmp.Transform(), cmpopts.SortSlices(sorter)); diff != "" { + if diff := cmp.Diff(tc.expected, resp, test.CmpProtoIgnoreOrder()...); diff != "" { t.Errorf("unexpected difference:\n%v", diff) } tc.mockFn(t, &pf) @@ -1072,11 +1066,13 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { // Create the Endpoints controller. ep := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), + Client: fakeClient, + Log: logrtest.New(t), + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, } resourceClient, err := consul.NewResourceServiceClient(ep.ConsulServerConnMgr) require.NoError(t, err) @@ -1112,7 +1108,6 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { require.False(t, resp.Requeue) expectedServiceMatches(t, resourceClient, tc.svcName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) - } func expectedServiceMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { @@ -1140,7 +1135,7 @@ func expectedServiceMatches(t *testing.T, client pbresource.ResourceServiceClien err = res.GetResource().GetData().UnmarshalTo(actualService) require.NoError(t, err) - if diff := cmp.Diff(expectedService, actualService, protocmp.Transform()); diff != "" { + if diff := cmp.Diff(expectedService, actualService, test.CmpProtoIgnoreOrder()...); diff != "" { t.Errorf("unexpected difference:\n%v", diff) } } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index a0ada9a69f..f8d9b0ff83 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -9,7 +9,6 @@ import ( "fmt" "strconv" - mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" @@ -30,8 +29,6 @@ import ( ) const ( - metaKeyManagedBy = "managed-by" - DefaultTelemetryBindSocketDir = "/consul/mesh-inject" ) @@ -41,28 +38,10 @@ type Controller struct { ConsulClientConfig *consul.Config // ConsulServerConnMgr is the watcher for the Consul server addresses. ConsulServerConnMgr consul.ServerConnectionManager - // Only pods in the AllowK8sNamespacesSet are reconciled. - AllowK8sNamespacesSet mapset.Set - // Pods in the DenyK8sNamespacesSet are ignored. - DenyK8sNamespacesSet mapset.Set - // EnableConsulPartitions indicates that a user is running Consul Enterprise - EnableConsulPartitions bool - // ConsulPartition is the Consul Partition to which this controller belongs - ConsulPartition string - // EnableConsulNamespaces indicates that a user is running Consul Enterprise - EnableConsulNamespaces bool - // ConsulDestinationNamespace is the name of the Consul namespace to create - // all config entries in. If EnableNSMirroring is true this is ignored. - ConsulDestinationNamespace string - // EnableNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any config entry custom resource. Config entries will - // be created in the matching Consul namespace. - EnableNSMirroring bool - // NSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - NSMirroringPrefix string + // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. + common.K8sNamespaceConfig + // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. + common.ConsulTenancyConfig // TODO: EnableWANFederation @@ -521,8 +500,8 @@ func parseLocality(node corev1.Node) *pbcatalog.Locality { func metaFromPod(pod corev1.Pod) map[string]string { // TODO: allow custom workload metadata return map[string]string{ - constants.MetaKeyKubeNS: pod.GetNamespace(), - metaKeyManagedBy: constants.ManagedByPodValue, + constants.MetaKeyKubeNS: pod.GetNamespace(), + constants.MetaKeyManagedBy: constants.ManagedByPodValue, } } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index c4fb6cb5ab..31d797ae5e 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -19,7 +19,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,6 +28,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/consul" @@ -153,13 +153,15 @@ func TestWorkloadWrite(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - ResourceClient: resourceClient, + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ResourceClient: resourceClient, } err = pc.writeWorkload(context.Background(), *tc.pod) @@ -341,13 +343,15 @@ func TestWorkloadDelete(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - ResourceClient: resourceClient, + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ResourceClient: resourceClient, } workload, err := anypb.New(tc.existingWorkload) @@ -423,13 +427,15 @@ func TestHealthStatusWrite(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - ResourceClient: resourceClient, + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ResourceClient: resourceClient, } // The owner of a resource is validated, so create a dummy workload for the HealthStatus @@ -537,12 +543,14 @@ func TestProxyConfigurationWrite(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, EnableTransparentProxy: tc.tproxy, TProxyOverwriteProbes: tc.overwriteProbes, EnableTelemetryCollector: tc.telemetry, @@ -583,7 +591,7 @@ func TestProxyConfigurationWrite(t *testing.T) { err = actualRes.GetResource().GetData().UnmarshalTo(actualProxyConfiguration) require.NoError(t, err) - diff := cmp.Diff(actualProxyConfiguration, tc.expectedProxyConfiguration, protocmp.Transform()) + diff := cmp.Diff(actualProxyConfiguration, tc.expectedProxyConfiguration, test.CmpProtoIgnoreOrder()...) require.Equal(t, "", diff) } @@ -728,13 +736,15 @@ func TestProxyConfigurationDelete(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - ResourceClient: resourceClient, + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ResourceClient: resourceClient, } // Create the existing ProxyConfiguration @@ -844,12 +854,14 @@ func TestReconcileCreatePod(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, TProxyOverwriteProbes: tc.overwriteProbes, EnableTransparentProxy: tc.tproxy, EnableTelemetryCollector: tc.telemetry, @@ -1014,12 +1026,14 @@ func TestReconcileUpdatePod(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, TProxyOverwriteProbes: tc.overwriteProbes, EnableTransparentProxy: tc.tproxy, EnableTelemetryCollector: tc.telemetry, @@ -1226,12 +1240,14 @@ func TestReconcileDeletePod(t *testing.T) { // Create the pod controller. pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, } if tc.aclsEnabled { pc.AuthMethod = test.AuthMethod diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go new file mode 100644 index 0000000000..2ccb1c7e8e --- /dev/null +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go @@ -0,0 +1,179 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package serviceaccount + +import ( + "context" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + auth "github.com/hashicorp/consul/proto-public/pbauth/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/go-multierror" +) + +type Controller struct { + client.Client + // ConsulServerConnMgr is the watcher for the Consul server addresses used to create Consul API v2 clients. + ConsulServerConnMgr consul.ServerConnectionManager + // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. + common.K8sNamespaceConfig + // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. + common.ConsulTenancyConfig + + Log logr.Logger + + Scheme *runtime.Scheme + context.Context +} + +func (r *Controller) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.ServiceAccount{}). + Complete(r) +} + +// Reconcile reads the state of a ServiceAccount object for a Kubernetes namespace and reconciles the corresponding +// Consul WorkloadIdentity. +func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var errs error + var serviceAccount corev1.ServiceAccount + + // Ignore the request if the namespace of the service account is not allowed. + if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + return ctrl.Result{}, nil + } + + // Create Consul resource service client for this reconcile. + resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) + if err != nil { + r.Log.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + + // If the ServiceAccount object has been deleted (and we get an IsNotFound error), + // we need to deregister that WorkloadIdentity from Consul. + err = r.Client.Get(ctx, req.NamespacedName, &serviceAccount) + if k8serrors.IsNotFound(err) { + err = r.deregisterWorkloadIdentity(ctx, resourceClient, req.Name, r.getConsulNamespace(req.Namespace), r.getConsulPartition()) + return ctrl.Result{}, err + } else if err != nil { + r.Log.Error(err, "failed to get ServiceAccount", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + r.Log.Info("retrieved ServiceAccount", "name", req.Name, "ns", req.Namespace) + + // Ensure the WorkloadIdentity exists. + if err = r.registerWorkloadIdentity(ctx, resourceClient, &serviceAccount); err != nil { + errs = multierror.Append(errs, err) + } + + return ctrl.Result{}, errs +} + +func (r *Controller) registerWorkloadIdentity(ctx context.Context, resourceClient pbresource.ResourceServiceClient, a *corev1.ServiceAccount) error { + workloadIdentityResource := r.getWorkloadIdentityResource( + a.Name, // Consul and Kubernetes service account name will always match + r.getConsulNamespace(a.Namespace), + r.getConsulPartition(), + map[string]string{ + constants.MetaKeyKubeNS: a.Namespace, + constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, + }, + ) + + r.Log.Info("registering workload identity with Consul", getLogFieldsForResource(workloadIdentityResource.Id)...) + // We currently blindly write these records as changes to service accounts and resulting reconciles should be rare, + // and there's no data to conflict with in the payload. + _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: workloadIdentityResource}) + if err != nil { + r.Log.Error(err, "failed to register workload identity", getLogFieldsForResource(workloadIdentityResource.Id)...) + return err + } + + return nil +} + +// deregisterWorkloadIdentity deletes the WorkloadIdentity resource corresponding to the given name and namespace from +// Consul. This operation is idempotent and can be executed for non-existent service accounts. +func (r *Controller) deregisterWorkloadIdentity(ctx context.Context, resourceClient pbresource.ResourceServiceClient, name, namespace, partition string) error { + _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{ + Id: getWorkloadIdentityID(name, namespace, partition), + }) + return err +} + +// getWorkloadIdentityResource converts the given Consul WorkloadIdentity and metadata to a Consul resource API record. +func (r *Controller) getWorkloadIdentityResource(name, namespace, partition string, meta map[string]string) *pbresource.Resource { + return &pbresource.Resource{ + Id: getWorkloadIdentityID(name, namespace, partition), + // WorkloadIdentity is currently an empty message. + Data: common.ToProtoAny(&auth.WorkloadIdentity{}), + Metadata: meta, + } +} + +func getWorkloadIdentityID(name, namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "auth", + GroupVersion: "v1alpha1", + Kind: "WorkloadIdentity", + }, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace +// depending on Consul Namespaces being enabled and the value of namespace mirroring. +func (r *Controller) getConsulNamespace(kubeNamespace string) string { + ns := namespaces.ConsulNamespace( + kubeNamespace, + r.EnableConsulNamespaces, + r.ConsulDestinationNamespace, + r.EnableNSMirroring, + r.NSMirroringPrefix, + ) + + // TODO: remove this if and when the default namespace of resources is no longer required to be set explicitly. + if ns == "" { + ns = constants.DefaultConsulNS + } + return ns +} + +func (r *Controller) getConsulPartition() string { + if !r.EnableConsulPartitions || r.ConsulPartition == "" { + return constants.DefaultConsulPartition + } + return r.ConsulPartition +} + +func getLogFieldsForResource(id *pbresource.ID) []any { + return []any{ + "name", id.Name, + "ns", id.Tenancy.Namespace, + "partition", id.Tenancy.Partition, + } +} diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go new file mode 100644 index 0000000000..45f38783c5 --- /dev/null +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go @@ -0,0 +1,24 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package serviceaccount + +import ( + "testing" +) + +// TODO: ConsulDestinationNamespace and EnableNSMirroring +/- prefix + +// TODO(zalimeni) +// Tests new WorkloadIdentity registration in a non-default NS and Partition with namespaces set to mirroring +func TestReconcile_CreateWorkloadIdentity_WithNamespaces(t *testing.T) { + +} + +// TODO(zalimeni) +// Tests removing WorkloadIdentity registration in a non-default NS and Partition with namespaces set to mirroring +func TestReconcile_DeleteWorkloadIdentity_WithNamespaces(t *testing.T) { + +} diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go new file mode 100644 index 0000000000..4c3e37da3c --- /dev/null +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go @@ -0,0 +1,347 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package serviceaccount + +import ( + "context" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/proto" + "testing" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/anypb" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +type reconcileCase struct { + name string + svcAccountName string + k8sObjects func() []runtime.Object + existingResource *pbresource.Resource + expectedResource *pbresource.Resource + targetConsulNs string + targetConsulPartition string + expErr string +} + +// TODO: Allow/deny namespaces for reconcile tests + +// TestReconcile_CreateWorkloadIdentity ensures that a new ServiceAccount is reconciled +// to a Consul WorkloadIdentity. +func TestReconcile_CreateWorkloadIdentity(t *testing.T) { + t.Parallel() + cases := []reconcileCase{ + { + name: "Default ServiceAccount", + svcAccountName: "default", + k8sObjects: func() []runtime.Object { + return []runtime.Object{createServiceAccount("default", "default")} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "default", + Type: &pbresource.Type{ + Group: "auth", + GroupVersion: "v1alpha1", + Kind: "WorkloadIdentity", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: getWorkloadIdentityData(), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, + }, + }, + }, + { + name: "Custom ServiceAccount", + svcAccountName: "my-svc-account", + k8sObjects: func() []runtime.Object { + return []runtime.Object{ + createServiceAccount("default", "default"), + createServiceAccount("my-svc-account", "default"), + } + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "my-svc-account", + Type: &pbresource.Type{ + Group: "auth", + GroupVersion: "v1alpha1", + Kind: "WorkloadIdentity", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: getWorkloadIdentityData(), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, + }, + }, + }, + { + name: "Already exists", + svcAccountName: "my-svc-account", + k8sObjects: func() []runtime.Object { + return []runtime.Object{ + createServiceAccount("default", "default"), + createServiceAccount("my-svc-account", "default"), + } + }, + existingResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "my-svc-account", + Type: &pbresource.Type{ + Group: "auth", + GroupVersion: "v1alpha1", + Kind: "WorkloadIdentity", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: getWorkloadIdentityData(), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, + }, + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "my-svc-account", + Type: &pbresource.Type{ + Group: "auth", + GroupVersion: "v1alpha1", + Kind: "WorkloadIdentity", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: getWorkloadIdentityData(), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + runReconcileCase(t, tc) + }) + } +} + +// Tests deleting a WorkloadIdentity object, with and without matching Consul resources. +func TestReconcile_DeleteWorkloadIdentity(t *testing.T) { + t.Parallel() + cases := []reconcileCase{ + { + name: "Basic ServiceAccount not found (deleted)", + svcAccountName: "my-svc-account", + k8sObjects: func() []runtime.Object { + // Only default exists (always exists). + return []runtime.Object{createServiceAccount("default", "default")} + }, + existingResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "my-svc-account", + Type: &pbresource.Type{ + Group: "auth", + GroupVersion: "v1alpha1", + Kind: "WorkloadIdentity", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: getWorkloadIdentityData(), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, + }, + }, + }, + { + name: "Other ServiceAccount exists", + svcAccountName: "my-svc-account", + k8sObjects: func() []runtime.Object { + // Default and other ServiceAccount exist + return []runtime.Object{ + createServiceAccount("default", "default"), + createServiceAccount("other-svc-account", "default"), + } + }, + existingResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "my-svc-account", + Type: &pbresource.Type{ + Group: "auth", + GroupVersion: "v1alpha1", + Kind: "WorkloadIdentity", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: getWorkloadIdentityData(), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, + }, + }, + }, + { + name: "Already deleted", + svcAccountName: "my-svc-account", + k8sObjects: func() []runtime.Object { + // Only default exists (always exists). + return []runtime.Object{createServiceAccount("default", "default")} + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + runReconcileCase(t, tc) + }) + } +} + +func runReconcileCase(t *testing.T, tc reconcileCase) { + t.Helper() + + // Create fake k8s client + var k8sObjects []runtime.Object + if tc.k8sObjects != nil { + k8sObjects = tc.k8sObjects() + } + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test Consul server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + + // Create the ServiceAccount controller. + sa := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + } + resourceClient, err := consul.NewResourceServiceClient(sa.ConsulServerConnMgr) + require.NoError(t, err) + + // Default ns and partition if not specified in test. + if tc.targetConsulNs == "" { + tc.targetConsulNs = constants.DefaultConsulNS + } + if tc.targetConsulPartition == "" { + tc.targetConsulPartition = constants.DefaultConsulPartition + } + + // If existing resource specified, create it and ensure it exists. + if tc.existingResource != nil { + writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} + _, err = resourceClient.Write(context.Background(), writeReq) + require.NoError(t, err) + test.ResourceHasPersisted(t, resourceClient, tc.existingResource.Id) + } + + // Run actual reconcile and verify results. + resp, err := sa.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: tc.svcAccountName, + Namespace: tc.targetConsulNs, + }, + }) + if tc.expErr != "" { + require.ErrorContains(t, err, tc.expErr) + } else { + require.NoError(t, err) + } + require.False(t, resp.Requeue) + + expectedWorkloadIdentityMatches(t, resourceClient, tc.svcAccountName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) +} + +func expectedWorkloadIdentityMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { + req := &pbresource.ReadRequest{Id: getWorkloadIdentityID(name, namespace, partition)} + + res, err := client.Read(context.Background(), req) + + if expectedResource == nil { + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + return + } + + require.NoError(t, err) + require.NotNil(t, res) + require.NotNil(t, res.GetResource().GetData()) + + // This equality check isn't technically necessary because WorkloadIdentity is an empty message, + // but this supports the addition of fields in the future. + expectedWorkloadIdentity := &pbauth.WorkloadIdentity{} + err = anypb.UnmarshalTo(expectedResource.Data, expectedWorkloadIdentity, proto.UnmarshalOptions{}) + require.NoError(t, err) + + actualWorkloadIdentity := &pbauth.WorkloadIdentity{} + err = res.GetResource().GetData().UnmarshalTo(actualWorkloadIdentity) + require.NoError(t, err) + + if diff := cmp.Diff(expectedWorkloadIdentity, actualWorkloadIdentity, test.CmpProtoIgnoreOrder()...); diff != "" { + t.Errorf("unexpected difference:\n%v", diff) + } +} + +// getWorkloadIdentityData returns a WorkloadIdentity resource payload. +// This function takes no arguments because WorkloadIdentity is currently an empty proto message. +func getWorkloadIdentityData() *anypb.Any { + return common.ToProtoAny(&pbauth.WorkloadIdentity{}) +} + +func createServiceAccount(name, namespace string) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + // Other fields exist, but we ignore them in this controller. + } +} diff --git a/control-plane/go.mod b/control-plane/go.mod index 486a09b875..66c9e4e507 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases replace ( // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version - github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230911164019-a69e901660bd + github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230914174054-e5808d85f751 // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd ) @@ -18,7 +18,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.4 - github.com/hashicorp/consul/api v1.10.1-0.20230911164019-a69e901660bd + github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 github.com/hashicorp/consul/proto-public v0.4.1 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 diff --git a/control-plane/go.sum b/control-plane/go.sum index f9cf933ea2..693c42251e 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -263,10 +263,10 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= -github.com/hashicorp/consul/api v1.10.1-0.20230911164019-a69e901660bd h1:TXcz98wzcjBdCUa8q8BgossUKh9slyVCgzE91chvJTc= -github.com/hashicorp/consul/api v1.10.1-0.20230911164019-a69e901660bd/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230911164019-a69e901660bd h1:3qvFUa6FTiYPiHmZadPnsVskphYIjv4n6vZlB57oiPM= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230911164019-a69e901660bd/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= +github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 h1:LnzgDq4e7ZfM1+XS6S21B9taQrbfdydXenL1xHyG1PQ= +github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230914174054-e5808d85f751 h1:kFfExHyeRbC5hi0REatLNSYwMFONnhkn/IyhnG3vG2A= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230914174054-e5808d85f751/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd h1:tRrSgVY71Jl6T2lJUokMLj3T1MO9uiSvW0CieBkjTvo= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index 915c65704e..072d2544ee 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -14,6 +14,8 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/proto-public/pbresource" @@ -21,6 +23,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/testing/protocmp" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/cert" @@ -341,6 +344,16 @@ func ServiceAccountGetResponse(name, ns string) string { }`, name, ns, ns, name, name) } +// CmpProtoIgnoreOrder returns a slice of cmp.Option useful for comparing proto messages independent of the order of +// their repeated fields. +func CmpProtoIgnoreOrder() []cmp.Option { + return []cmp.Option{ + protocmp.Transform(), + // Stringify any type passed to the sorter so that we can reliably compare most values. + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + } +} + const AuthMethod = "consul-k8s-auth-method" const ServiceAccountJWTToken = `eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImtoYWtpLWFyYWNobmlkLWNvbnN1bC1jb25uZWN0LWluamVjdG9yLWF1dGhtZXRob2Qtc3ZjLWFjY29obmRidiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJraGFraS1hcmFjaG5pZC1jb25zdWwtY29ubmVjdC1pbmplY3Rvci1hdXRobWV0aG9kLXN2Yy1hY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiN2U5NWUxMjktZTQ3My0xMWU5LThmYWEtNDIwMTBhODAwMTIyIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6a2hha2ktYXJhY2huaWQtY29uc3VsLWNvbm5lY3QtaW5qZWN0b3ItYXV0aG1ldGhvZC1zdmMtYWNjb3VudCJ9.Yi63MMtzh5MBWKKd3a7dzCJjTITE15ikFy_Tnpdk_AwdwA9J4AMSGEeHN5vWtCuuFjo_lMJqBBPHkK2AqbnoFUj9m5CopWyqICJQlvEOP4fUQ-Rc0W1P_JjU1rZERHG39b5TMLgKPQguyhaiZEJ6CjVtm9wUTagrgiuqYV2iUqLuF6SYNm6SrKtkPS-lqIO-u7C06wVk5m5uqwIVQNpZSIC_5Ls5aLmyZU3nHvH-V7E3HmBhVyZAB76jgKB0TyVX1IOskt9PDFarNtU3suZyCjvqC-UJA6sYeySe4dBNKsKlSZ6YuxUUmn1Rgv32YMdImnsWg8khf-zJvqgWk7B5EA` const serviceAccountCACert = `-----BEGIN CERTIFICATE----- diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index b3938baace..74fc908e64 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -5,19 +5,20 @@ package connectinject import ( "context" - - "github.com/hashicorp/consul-server-connection-manager/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/serviceaccount" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/namespace" webhookV2 "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook_v2" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul-server-connection-manager/discovery" ) func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { @@ -25,9 +26,21 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage // Create Consul API config object. consulConfig := c.consul.ConsulClientConfig() - //Convert allow/deny lists to sets. + // Convert allow/deny lists to sets. allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) + k8sNsConfig := common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + } + consulTenancyConfig := common.ConsulTenancyConfig{ + EnableConsulPartitions: c.flagEnablePartitions, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + ConsulPartition: c.consul.Partition, + } lifecycleConfig := lifecycle.Config{ DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, @@ -47,46 +60,48 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage } if err := (&pod.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - EnableConsulPartitions: c.flagEnablePartitions, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - ConsulPartition: c.consul.Partition, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - AuthMethod: c.flagACLAuthMethod, - MetricsConfig: metricsConfig, - EnableTelemetryCollector: c.flagEnableTelemetryCollector, - Log: ctrl.Log.WithName("controller").WithName("pod"), + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + K8sNamespaceConfig: k8sNsConfig, + ConsulTenancyConfig: consulTenancyConfig, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + AuthMethod: c.flagACLAuthMethod, + MetricsConfig: metricsConfig, + EnableTelemetryCollector: c.flagEnableTelemetryCollector, + Log: ctrl.Log.WithName("controller").WithName("pod"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", pod.Controller{}) return err } if err := (&endpointsv2.Controller{ - Client: mgr.GetClient(), - ConsulServerConnMgr: watcher, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - EnableConsulPartitions: c.flagEnablePartitions, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - Log: ctrl.Log.WithName("controller").WithName("endpoints"), - Scheme: mgr.GetScheme(), - Context: ctx, + Client: mgr.GetClient(), + ConsulServerConnMgr: watcher, + K8sNamespaceConfig: k8sNsConfig, + ConsulTenancyConfig: consulTenancyConfig, + Log: ctrl.Log.WithName("controller").WithName("endpoints"), + Scheme: mgr.GetScheme(), + Context: ctx, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", endpointsv2.Controller{}) return err } + if err := (&serviceaccount.Controller{ + Client: mgr.GetClient(), + ConsulServerConnMgr: watcher, + K8sNamespaceConfig: k8sNsConfig, + ConsulTenancyConfig: consulTenancyConfig, + Log: ctrl.Log.WithName("controller").WithName("serviceaccount"), + Scheme: mgr.GetScheme(), + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", serviceaccount.Controller{}) + return err + } + if c.flagEnableNamespaces { err := (&namespace.Controller{ Client: mgr.GetClient(), @@ -106,8 +121,6 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage } } - // TODO: Serviceaccounts Controller - // TODO: V2 Config Controller(s) // // Metadata for webhooks From 7cf20be0ed1e01c0d801c88e4cc52345d00115a9 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 18 Sep 2023 09:19:13 -0400 Subject: [PATCH 388/592] Fix missing constant endpoints controller v2 test (#2968) --- .../controllers/endpointsv2/endpoints_controller_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 69bf14aec9..109b6dcca3 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -136,7 +136,7 @@ func TestReconcile_CreateService(t *testing.T) { { VirtualPort: 10001, TargetPort: "10001", - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, }, { TargetPort: "mesh", @@ -250,7 +250,7 @@ func TestReconcile_CreateService(t *testing.T) { { VirtualPort: 10001, TargetPort: "10001", - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, }, { TargetPort: "mesh", @@ -537,6 +537,7 @@ func TestReconcile_CreateService(t *testing.T) { { VirtualPort: 8080, TargetPort: "my-svc-port", + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, }, { TargetPort: "mesh", @@ -549,8 +550,8 @@ func TestReconcile_CreateService(t *testing.T) { VirtualIps: []string{"172.18.0.1"}, }), Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - metaKeyManagedBy: constants.ManagedByEndpointsValue, + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, }, From 58749fe0bab65cfc94e4c3162a390d85b8341a45 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 18 Sep 2023 10:10:05 -0400 Subject: [PATCH 389/592] fix(control-plane): Only register v2 service when pods injected (#2969) We are currently registering all services in k8s regardless of whether they represent mesh-injected workloads. This is both creating "junk" registrations for Consul and k8s components, but additionally can create issues in Consul core when generating routes with TProxy enabled, since these services will not have endpoints. To solve for both of these issues, selectively sync k8s services to Consul in the v2 Endpoints controller only when at least one of its pods is injected. Follow-up work will address edge cases where we want to maintain a service entry even without workloads, such as when the global inject flag is set, and when a service temporarily loses endpoints but is already registered and part of the mesh. --- control-plane/connect-inject/common/common.go | 9 + .../connect-inject/common/common_test.go | 59 +++++ .../endpointsv2/endpoints_controller.go | 30 ++- .../endpointsv2/endpoints_controller_test.go | 241 +++++++++++------- .../controllers/pod/pod_controller.go | 10 +- .../controllers/pod/pod_controller_test.go | 34 --- 6 files changed, 239 insertions(+), 144 deletions(-) diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 1e7cf5415c..b9bc092278 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -177,3 +177,12 @@ func PortValueFromIntOrString(pod corev1.Pod, port intstr.IntOrString) (uint32, } return uint32(portVal), nil } + +// HasBeenMeshInjected checks the value of the status annotation and returns true if the Pod has been injected. +// Does not apply to V1 pods, which use a different key (`constants.KeyInjectStatus`). +func HasBeenMeshInjected(pod corev1.Pod) bool { + if anno, ok := pod.Annotations[constants.KeyMeshInjectStatus]; ok && anno == constants.Injected { + return true + } + return false +} diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index 6cbbab5b88..8d15b85bfc 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -399,3 +399,62 @@ func TestGetPortProtocol(t *testing.T) { }) } } + +func TestHasBeenMeshInjected(t *testing.T) { + t.Parallel() + cases := []struct { + name string + pod corev1.Pod + expected bool + }{ + { + name: "Pod with injected annotation", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{}, + Annotations: map[string]string{ + constants.KeyMeshInjectStatus: constants.Injected, + }, + }, + }, + expected: true, + }, + { + name: "Pod without injected annotation", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{}, + Annotations: map[string]string{ + "consul.hashicorp.com/foo": "bar", + }, + }, + }, + expected: false, + }, + { + name: "Pod with injected annotation but wrong value", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{}, + Annotations: map[string]string{ + constants.KeyMeshInjectStatus: "hiya", + }, + }, + }, + expected: false, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + actual := HasBeenMeshInjected(tt.pod) + require.Equal(t, tt.expected, actual) + }) + } +} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index e94f719a1b..f0daa68f01 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -101,9 +101,14 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu errs = multierror.Append(errs, err) } - //TODO: Maybe check service-enable label here on service/deployments/other pod owners - if err = r.registerService(ctx, resourceClient, service, workloadSelector); err != nil { - errs = multierror.Append(errs, err) + // If we have at least one mesh-injected pod targeted by the service, register it in Consul. + //TODO: Register service with mesh port added if global flag for inject is true, + // even if Endpoints are empty or have no mesh pod, iff. the service has a selector. + // This should ensure that we don't target kube or consul (system) services. + if workloadSelector != nil { + if err = r.registerService(ctx, resourceClient, service, workloadSelector); err != nil { + errs = multierror.Append(errs, err) + } } return ctrl.Result{}, errs @@ -131,6 +136,12 @@ func (r *Controller) getWorkloadSelectorFromEndpoints(ctx context.Context, pf Po errs = multierror.Append(errs, err) continue } + + // If the pod hasn't been mesh-injected, skip it, as it won't be available as a workload. + if !common.HasBeenMeshInjected(*pod) { + continue + } + // Add to workload selector values. // Pods can appear more than once in Endpoints subsets, so we use a set for exact names as well. if prefix := getOwnerPrefixFromPod(pod); prefix != "" { @@ -258,8 +269,6 @@ func getServicePorts(service corev1.Service) []*pbcatalog.ServicePort { //TODO: Error check reserved "mesh" target port // Append Consul service mesh port in addition to discovered ports. - //TODO: Maybe omit if zero mesh ports present in service endpoints, or if some - // use of mesh-inject/other label should cause this to be excluded. ports = append(ports, &pbcatalog.ServicePort{ TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH, @@ -290,7 +299,15 @@ func getServiceMeta(service corev1.Service) map[string]string { return meta } +// getWorkloadSelector returns the WorkloadSelector for the given pod name prefixes and exact names. +// It returns nil if the provided name sets are empty. func getWorkloadSelector(podPrefixes, podExactNames map[string]any) *pbcatalog.WorkloadSelector { + // If we don't have any values, return nil + if len(podPrefixes) == 0 && len(podExactNames) == 0 { + return nil + } + + // Create the WorkloadSelector workloads := &pbcatalog.WorkloadSelector{} for v := range podPrefixes { workloads.Prefixes = append(workloads.Prefixes, v) @@ -298,7 +315,8 @@ func getWorkloadSelector(podPrefixes, podExactNames map[string]any) *pbcatalog.W for v := range podExactNames { workloads.Names = append(workloads.Names, v) } - // sort for stability + + // Sort for comparison stability sort.Strings(workloads.Prefixes) sort.Strings(workloads.Names) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 109b6dcca3..1762471a46 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -57,101 +57,102 @@ type reconcileCase struct { func TestReconcile_CreateService(t *testing.T) { t.Parallel() cases := []reconcileCase{ - { - // In this test, we expect the same service registration as the "basic" - // case, but without any workload selector values due to missing endpoints. - name: "Empty endpoints", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: []corev1.EndpointAddress{}, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: common.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10001, - TargetPort: "10001", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{}, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, + //TODO: reenable this test as a conditional on global mesh inject flag rather than "any time we see endpoints" + //{ + // // In this test, we expect the same service registration as the "basic" + // // case, but without any workload selector values due to missing endpoints. + // name: "Empty endpoints", + // svcName: "service-created", + // k8sObjects: func() []runtime.Object { + // endpoints := &corev1.Endpoints{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "service-created", + // Namespace: "default", + // }, + // Subsets: []corev1.EndpointSubset{ + // { + // Addresses: []corev1.EndpointAddress{}, + // }, + // }, + // } + // service := &corev1.Service{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "service-created", + // Namespace: "default", + // }, + // Spec: corev1.ServiceSpec{ + // ClusterIP: "172.18.0.1", + // Ports: []corev1.ServicePort{ + // { + // Name: "public", + // Port: 8080, + // Protocol: "TCP", + // TargetPort: intstr.FromString("my-http-port"), + // AppProtocol: &appProtocolHttp, + // }, + // { + // Name: "api", + // Port: 9090, + // Protocol: "TCP", + // TargetPort: intstr.FromString("my-grpc-port"), + // AppProtocol: &appProtocolGrpc, + // }, + // { + // Name: "other", + // Port: 10001, + // Protocol: "TCP", + // TargetPort: intstr.FromString("10001"), + // // no app protocol specified + // }, + // }, + // }, + // } + // return []runtime.Object{endpoints, service} + // }, + // expectedResource: &pbresource.Resource{ + // Id: &pbresource.ID{ + // Name: "service-created", + // Type: &pbresource.Type{ + // Group: "catalog", + // GroupVersion: "v1alpha1", + // Kind: "Service", + // }, + // Tenancy: &pbresource.Tenancy{ + // Namespace: constants.DefaultConsulNS, + // Partition: constants.DefaultConsulPartition, + // }, + // }, + // Data: common.ToProtoAny(&pbcatalog.Service{ + // Ports: []*pbcatalog.ServicePort{ + // { + // VirtualPort: 8080, + // TargetPort: "my-http-port", + // Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + // }, + // { + // VirtualPort: 9090, + // TargetPort: "my-grpc-port", + // Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + // }, + // { + // VirtualPort: 10001, + // TargetPort: "10001", + // Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + // }, + // { + // TargetPort: "mesh", + // Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + // }, + // }, + // Workloads: &pbcatalog.WorkloadSelector{}, + // VirtualIps: []string{"172.18.0.1"}, + // }), + // Metadata: map[string]string{ + // constants.MetaKeyKubeNS: constants.DefaultConsulNS, + // constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, + // }, + // }, + //}, { name: "Basic endpoints", svcName: "service-created", @@ -555,6 +556,54 @@ func TestReconcile_CreateService(t *testing.T) { }, }, }, + { + name: "Services without mesh-injected pods should not be registered", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + // Clear mesh inject status + delete(pod1.Annotations, constants.KeyMeshInjectStatus) + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1), + Ports: []corev1.EndpointPort{ + { + Name: "public", + Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + }, + }, + } + return []runtime.Object{pod1, endpoints, service} + }, + // No expected resource + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -1013,6 +1062,7 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { require.Equal(t, len(otherPods), len(pf.calls)) }, }, + //TODO: Add cases to cover non-injected pod skipping. } for _, tc := range cases { @@ -1153,6 +1203,7 @@ func createServicePod(ownerKind, ownerName, podId string) *corev1.Pod { Labels: map[string]string{}, Annotations: map[string]string{ constants.AnnotationConsulK8sVersion: "1.3.0", + constants.KeyMeshInjectStatus: constants.Injected, }, OwnerReferences: []metav1.OwnerReference{ { diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index f8d9b0ff83..bbe2f69e95 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -121,7 +121,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.Log.Info("retrieved", "name", pod.Name, "ns", pod.Namespace) - if hasBeenInjected(pod) { + if common.HasBeenMeshInjected(pod) { if err := r.writeProxyConfiguration(ctx, pod); err != nil { errs = multierror.Append(errs, err) } @@ -149,14 +149,6 @@ func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -// hasBeenInjected checks the value of the status annotation and returns true if the Pod has been injected. -func hasBeenInjected(pod corev1.Pod) bool { - if anno, ok := pod.Annotations[constants.KeyMeshInjectStatus]; ok && anno == constants.Injected { - return true - } - return false -} - func (r *Controller) deleteWorkload(ctx context.Context, pod types.NamespacedName) error { req := &pbresource.DeleteRequest{ Id: getWorkloadID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 31d797ae5e..5e41c44449 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -43,40 +43,6 @@ const ( consulNodeAddress = "127.0.0.1" ) -func TestHasBeenInjected(t *testing.T) { - t.Parallel() - cases := []struct { - name string - pod func() corev1.Pod - expected bool - }{ - { - name: "Pod with injected annotation", - pod: func() corev1.Pod { - pod1 := createPod("pod1", "1.2.3.4", "foo", true, true) - return *pod1 - }, - expected: true, - }, - { - name: "Pod without injected annotation", - pod: func() corev1.Pod { - pod1 := createPod("pod1", "1.2.3.4", "foo", false, true) - return *pod1 - }, - expected: false, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - - actual := hasBeenInjected(tt.pod()) - require.Equal(t, tt.expected, actual) - }) - } -} - func TestParseLocality(t *testing.T) { t.Run("no labels", func(t *testing.T) { n := corev1.Node{} From a4616cf97382002a6b78dfd829d6d479ee663910 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 18 Sep 2023 18:25:34 -0400 Subject: [PATCH 390/592] feat(control-plane): v2 backoff on missing namespace (#2960) --- control-plane/connect-inject/common/common.go | 21 +++- .../connect-inject/common/common_test.go | 115 +++++++++++++++++- .../endpointsv2/endpoints_controller.go | 10 +- .../endpointsv2/endpoints_controller_test.go | 32 ++--- .../controllers/pod/pod_controller.go | 24 ++++ .../pod/pod_controller_ent_test.go | 6 + 6 files changed, 188 insertions(+), 20 deletions(-) diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index b9bc092278..5ad336f327 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -9,14 +9,17 @@ import ( "strings" mapset "github.com/deckarep/golang-set" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) // DetermineAndValidatePort behaves as follows: @@ -186,3 +189,19 @@ func HasBeenMeshInjected(pod corev1.Pod) bool { } return false } + +// ConsulNamespaceIsNotFound checks the gRPC error code and message to determine +// if a namespace does not exist. If the namespace exists this function returns false, true otherwise. +func ConsulNamespaceIsNotFound(err error) bool { + if err == nil { + return false + } + s, ok := status.FromError(err) + if !ok { + return false + } + if codes.InvalidArgument == s.Code() && strings.Contains(s.Message(), "namespace resource not found") { + return true + } + return false +} diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index 8d15b85bfc..3bf19c72d4 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -4,20 +4,28 @@ package common import ( + "context" + "fmt" "testing" mapset "github.com/deckarep/golang-set" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" "github.com/hashicorp/consul/proto-public/pbresource" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func TestCommonDetermineAndValidatePort(t *testing.T) { @@ -458,3 +466,106 @@ func TestHasBeenMeshInjected(t *testing.T) { }) } } + +func Test_ConsulNamespaceIsNotFound(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input error + expectMissingNamespace bool + }{ + { + name: "nil error", + expectMissingNamespace: false, + }, + { + name: "random error", + input: fmt.Errorf("namespace resource not found"), + expectMissingNamespace: false, + }, + { + name: "grpc code is not InvalidArgument", + input: status.Error(codes.NotFound, "namespace resource not found"), + expectMissingNamespace: false, + }, + { + name: "grpc code is InvalidArgument, but the message is not for namespaces", + input: status.Error(codes.InvalidArgument, "blurg resource not found"), + expectMissingNamespace: false, + }, + { + name: "namespace is missing", + input: status.Error(codes.InvalidArgument, "namespace resource not found"), + expectMissingNamespace: true, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + actual := ConsulNamespaceIsNotFound(tt.input) + require.Equal(t, tt.expectMissingNamespace, actual) + }) + } +} + +// Test_ConsulNamespaceIsNotFound_ErrorMsg is an integration test that verifies the error message +// associated with a missing namespace while creating a resource doesn't drift. +func Test_ConsulNamespaceIsNotFound_ErrorMsg(t *testing.T) { + t.Parallel() + + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + id := &pbresource.ID{ + Name: "foo", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Workload", + }, + Tenancy: &pbresource.Tenancy{ + Partition: constants.DefaultConsulPartition, + Namespace: "i-dont-exist-but-its-ok-we-will-meet-again-someday", + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } + + workload := &pbcatalog.Workload{ + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: "10.0.0.1", Ports: []string{"mesh"}}, + }, + Ports: map[string]*pbcatalog.WorkloadPort{ + "mesh": { + Port: constants.ProxyDefaultInboundPort, + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + NodeName: "banana", + Identity: "foo", + } + + data := ToProtoAny(workload) + + resource := &pbresource.Resource{ + Id: id, + Data: data, + } + + _, err = resourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: resource}) + require.Error(t, err) + + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.InvalidArgument, s.Code()) + require.Contains(t, s.Message(), "namespace resource not found") + + require.True(t, ConsulNamespaceIsNotFound(err)) +} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index f0daa68f01..9b7cfa8971 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -106,11 +106,19 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // even if Endpoints are empty or have no mesh pod, iff. the service has a selector. // This should ensure that we don't target kube or consul (system) services. if workloadSelector != nil { + //TODO: Maybe check service-enable label here on service/deployments/other pod owners if err = r.registerService(ctx, resourceClient, service, workloadSelector); err != nil { + // We could be racing with the namespace controller. + // Requeue (which includes backoff) to try again. + if common.ConsulNamespaceIsNotFound(err) { + r.Log.Info("Consul namespace not found; re-queueing request", + "service", service.GetName(), "ns", req.Namespace, + "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) + return ctrl.Result{Requeue: true}, nil + } errs = multierror.Append(errs, err) } } - return ctrl.Result{}, errs } diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 1762471a46..a504fc42d8 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -212,9 +212,9 @@ func TestReconcile_CreateService(t *testing.T) { AppProtocol: &appProtocolGrpc, }, { - Name: "other", - Port: 10001, - Protocol: "TCP", + Name: "other", + Port: 10001, + //Protocol: "TCP", TargetPort: intstr.FromString("10001"), // no app protocol specified }, @@ -248,11 +248,11 @@ func TestReconcile_CreateService(t *testing.T) { TargetPort: "my-grpc-port", Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, }, - { - VirtualPort: 10001, - TargetPort: "10001", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, + //{ + // VirtualPort: 10001, + // TargetPort: "10001", + // Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + //}, { TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH, @@ -504,9 +504,9 @@ func TestReconcile_CreateService(t *testing.T) { Ports: []corev1.ServicePort{ // Two L4 protocols on one exposed port { - Name: "public-tcp", - Port: 8080, - Protocol: "TCP", + Name: "public-tcp", + Port: 8080, + //Protocol: "TCP", TargetPort: intstr.FromString("my-svc-port"), }, { @@ -535,11 +535,11 @@ func TestReconcile_CreateService(t *testing.T) { }, Data: common.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-svc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, + //{ + // VirtualPort: 8080, + // TargetPort: "my-svc-port", + // Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + //}, { TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH, diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index bbe2f69e95..1e6c5e1ddb 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -123,10 +123,26 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if common.HasBeenMeshInjected(pod) { if err := r.writeProxyConfiguration(ctx, pod); err != nil { + // We could be racing with the namespace controller. + // Requeue (which includes backoff) to try again. + if common.ConsulNamespaceIsNotFound(err) { + r.Log.Info("Consul namespace not found; re-queueing request", + "pod", req.Name, "ns", req.Namespace, "consul-ns", + r.getConsulNamespace(req.Namespace), "err", err.Error()) + return ctrl.Result{Requeue: true}, nil + } errs = multierror.Append(errs, err) } if err := r.writeWorkload(ctx, pod); err != nil { + // Technically this is not needed, but keeping in case this gets refactored in + // a different order + if common.ConsulNamespaceIsNotFound(err) { + r.Log.Info("Consul namespace not found; re-queueing request", + "pod", req.Name, "ns", req.Namespace, "consul-ns", + r.getConsulNamespace(req.Namespace), "err", err.Error()) + return ctrl.Result{Requeue: true}, nil + } errs = multierror.Append(errs, err) } @@ -136,6 +152,14 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu //} if err := r.writeHealthStatus(ctx, pod); err != nil { + // Technically this is not needed, but keeping in case this gets refactored in + // a different order + if common.ConsulNamespaceIsNotFound(err) { + r.Log.Info("Consul namespace not found; re-queueing request", + "pod", req.Name, "ns", req.Namespace, "consul-ns", + r.getConsulNamespace(req.Namespace), "err", err.Error()) + return ctrl.Result{Requeue: true}, nil + } errs = multierror.Append(errs, err) } } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index 802cb9d910..56fb362d9d 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -11,34 +11,40 @@ import "testing" // Tests creating a Pod object in a non-default NS and Partition with namespaces set to mirroring func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { + //TODO: Add test case to cover Consul namespace missing and check for backoff } // TODO(dans) // Tests updating a Pod object in a non-default NS and Partition with namespaces set to mirroring func TestReconcileUpdatePodWithMirrorNamespaces(t *testing.T) { + //TODO: Add test case to cover Consul namespace missing and check for backoff } // TODO(dans) // Tests deleting a Pod object in a non-default NS and Partition with namespaces set to mirroring func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { + //TODO: Add test case to cover Consul namespace missing and check for backoff } // TODO(dans) // Tests creating a Pod object in a non-default NS and Partition with namespaces set to a destination func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { + //TODO: Add test case to cover Consul namespace missing and check for backoff } // TODO(dans) // Tests updating a Pod object in a non-default NS and Partition with namespaces set to a destination func TestReconcileUpdatePodWithDestinationNamespace(t *testing.T) { + //TODO: Add test case to cover Consul namespace missing and check for backoff } // TODO(dans) // Tests deleting a Pod object in a non-default NS and Partition with namespaces set to a destination func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { + //TODO: Add test case to cover Consul namespace missing and check for backoff } From f662cf5566a3a9c18c5fc7863f9922d4a001538e Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Tue, 19 Sep 2023 10:36:33 -0400 Subject: [PATCH 391/592] Revert temporary fixes to endpoints v2 tests (#2971) These were necessary to get tests to pass during the merge of several PRs across `consul` and `consul-k8s` but are no longer needed. --- .../endpointsv2/endpoints_controller_test.go | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index a504fc42d8..1762471a46 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -212,9 +212,9 @@ func TestReconcile_CreateService(t *testing.T) { AppProtocol: &appProtocolGrpc, }, { - Name: "other", - Port: 10001, - //Protocol: "TCP", + Name: "other", + Port: 10001, + Protocol: "TCP", TargetPort: intstr.FromString("10001"), // no app protocol specified }, @@ -248,11 +248,11 @@ func TestReconcile_CreateService(t *testing.T) { TargetPort: "my-grpc-port", Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, }, - //{ - // VirtualPort: 10001, - // TargetPort: "10001", - // Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - //}, + { + VirtualPort: 10001, + TargetPort: "10001", + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + }, { TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH, @@ -504,9 +504,9 @@ func TestReconcile_CreateService(t *testing.T) { Ports: []corev1.ServicePort{ // Two L4 protocols on one exposed port { - Name: "public-tcp", - Port: 8080, - //Protocol: "TCP", + Name: "public-tcp", + Port: 8080, + Protocol: "TCP", TargetPort: intstr.FromString("my-svc-port"), }, { @@ -535,11 +535,11 @@ func TestReconcile_CreateService(t *testing.T) { }, Data: common.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ - //{ - // VirtualPort: 8080, - // TargetPort: "my-svc-port", - // Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - //}, + { + VirtualPort: 8080, + TargetPort: "my-svc-port", + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + }, { TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH, From 1b52ac305e7e9a4c70969a330629f265e8b11d57 Mon Sep 17 00:00:00 2001 From: Connor Date: Tue, 19 Sep 2023 16:38:58 -0500 Subject: [PATCH 392/592] feat: Add HCP Observability ClientID and ClientSecret (#2958) * feat: Add HCP Observability ClientID and ClientSecret * go mod tidy * changelog --- .changelog/2958.txt | 3 + cli/go.mod | 25 ++-- cli/go.sum | 50 ++++---- cli/preset/cloud_preset.go | 114 +++++++++++++----- cli/preset/cloud_preset_test.go | 205 +++++++++++++++++++++++++------- 5 files changed, 295 insertions(+), 102 deletions(-) create mode 100644 .changelog/2958.txt diff --git a/.changelog/2958.txt b/.changelog/2958.txt new file mode 100644 index 0000000000..49d10d70e7 --- /dev/null +++ b/.changelog/2958.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add support for new observability service principal in cloud preset +``` diff --git a/cli/go.mod b/cli/go.mod index 3e92113d18..75bb12479b 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -10,7 +10,7 @@ require ( github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 github.com/hashicorp/consul/troubleshoot v0.3.0-rc1 github.com/hashicorp/go-hclog v1.5.0 - github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc + github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54 github.com/kr/text v0.2.0 github.com/mattn/go-isatty v0.0.17 github.com/mitchellh/cli v1.1.2 @@ -48,8 +48,6 @@ require ( github.com/Masterminds/sprig v2.22.0+incompatible // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect @@ -78,16 +76,17 @@ require ( github.com/go-errors/errors v1.0.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.2.3 // indirect - github.com/go-openapi/analysis v0.21.2 // indirect - github.com/go-openapi/errors v0.20.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.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/loads v0.21.1 // indirect - github.com/go-openapi/runtime v0.24.1 // indirect - github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/jsonreference v0.20.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.21.1 // indirect - github.com/go-openapi/validate v0.21.0 // indirect + github.com/go-openapi/swag v0.22.3 // 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 github.com/gogo/protobuf v1.3.2 // indirect @@ -160,11 +159,13 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // 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 v0.0.0-20181112141820-a009c3971eca // indirect go.mongodb.org/mongo-driver v1.11.1 // indirect + go.opentelemetry.io/otel v1.11.1 // indirect + go.opentelemetry.io/otel/trace v1.11.1 // indirect go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect diff --git a/cli/go.sum b/cli/go.sum index 6b54e646b2..6cc5d4e83d 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -86,9 +86,7 @@ github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6 github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -249,40 +247,49 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= -github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU= 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= github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= +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 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= -github.com/go-openapi/runtime v0.24.1 h1:Sml5cgQKGYQHF+M7yYSHaH1eOjvTykrddTE/KtQVjqo= -github.com/go-openapi/runtime v0.24.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +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= +github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc= +github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= +github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI= -github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +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/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= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -290,7 +297,6 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= @@ -474,8 +480,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc h1:on26TCKYnX7JzZCtwkR/LWHSqMu40PoZ6h/0e6Pq8ug= -github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc/go.mod h1:/9UoDY2FYYA8lFaKBb2HmM/jKYZGANmf65q9QRc/cVw= +github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54 h1:m05LS8cYY4A81y5hUaNKTn2/F+V9BVvulB2GxopoZFo= +github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54/go.mod h1:xP7wmWAmdMxs/7+ovH3jZn+MCDhHRj50Rn+m7JIY3Ck= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= @@ -614,7 +620,6 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -796,8 +801,9 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+ github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= @@ -821,7 +827,6 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= -go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8= go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= @@ -836,13 +841,18 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= +go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= +go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -869,7 +879,6 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1078,7 +1087,6 @@ 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.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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= diff --git a/cli/preset/cloud_preset.go b/cli/preset/cloud_preset.go index cbc335ae17..6122c3f6d1 100644 --- a/cli/preset/cloud_preset.go +++ b/cli/preset/cloud_preset.go @@ -25,24 +25,26 @@ import ( ) const ( - secretNameHCPClientID = "consul-hcp-client-id" - secretKeyHCPClientID = "client-id" - secretNameHCPClientSecret = "consul-hcp-client-secret" - secretKeyHCPClientSecret = "client-secret" - secretNameHCPResourceID = "consul-hcp-resource-id" - secretKeyHCPResourceID = "resource-id" - secretNameHCPAPIHostname = "consul-hcp-api-host" - secretKeyHCPAPIHostname = "api-hostname" - secretNameHCPAuthURL = "consul-hcp-auth-url" - secretKeyHCPAuthURL = "auth-url" - secretNameHCPScadaAddress = "consul-hcp-scada-address" - secretKeyHCPScadaAddress = "scada-address" - secretNameGossipKey = "consul-gossip-key" - secretKeyGossipKey = "key" - secretNameBootstrapToken = "consul-bootstrap-token" - secretKeyBootstrapToken = "token" - secretNameServerCA = "consul-server-ca" - secretNameServerCert = "consul-server-cert" + secretNameHCPClientID = "consul-hcp-client-id" + secretKeyHCPClientID = "client-id" + secretNameHCPClientSecret = "consul-hcp-client-secret" + secretKeyHCPClientSecret = "client-secret" + secretNameHCPObservabilityClientID = "consul-hcp-observability-client-id" + secretNameHCPObservabilityClientSecret = "consul-hcp-observability-client-secret" + secretNameHCPResourceID = "consul-hcp-resource-id" + secretKeyHCPResourceID = "resource-id" + secretNameHCPAPIHostname = "consul-hcp-api-host" + secretKeyHCPAPIHostname = "api-hostname" + secretNameHCPAuthURL = "consul-hcp-auth-url" + secretKeyHCPAuthURL = "auth-url" + secretNameHCPScadaAddress = "consul-hcp-scada-address" + secretKeyHCPScadaAddress = "scada-address" + secretNameGossipKey = "consul-gossip-key" + secretKeyGossipKey = "key" + secretNameBootstrapToken = "consul-bootstrap-token" + secretKeyBootstrapToken = "token" + secretNameServerCA = "consul-server-ca" + secretNameServerCert = "consul-server-cert" ) // CloudBootstrapConfig represents the response fetched from the agent @@ -57,12 +59,14 @@ type CloudBootstrapConfig struct { // provided by the user in order to make a call to fetch the agent bootstrap // config data from the endpoint in HCP. type HCPConfig struct { - ResourceID string - ClientID string - ClientSecret string - AuthURL string - APIHostname string - ScadaAddress string + ResourceID string + ClientID string + ClientSecret string + ObservabilityClientID string + ObservabilityClientSecret string + AuthURL string + APIHostname string + ScadaAddress string } // ConsulConfig represents 'cluster.consul_config' in the response @@ -143,10 +147,32 @@ func (c *CloudPreset) fetchAgentBootstrapConfig() (*CloudBootstrapConfig, error) return nil, err } + obsParams := hcpgnm.NewGetObservabilitySecretParamsWithContext(c.Context). + WithID(clusterResource.ID). + WithLocationOrganizationID(clusterResource.Organization). + WithLocationProjectID(clusterResource.Project). + WithHTTPClient(c.HTTPClient) + + obsResp, err := hcpgnmClient.GetObservabilitySecret(obsParams, nil) + if err != nil { + return nil, err + } + bootstrapConfig := resp.GetPayload() c.UI.Output("HCP configuration successfully fetched.", terminal.WithSuccessStyle()) - return c.parseBootstrapConfigResponse(bootstrapConfig) + cloudConfig, err := c.parseBootstrapConfigResponse(bootstrapConfig) + if err != nil { + return nil, err + } + + // if we don't have any keys fall back to the cluster credentials. Remove fallback in the future probably + if len(obsResp.GetPayload().Keys) != 0 { + cloudConfig.HCPConfig.ObservabilityClientID = obsResp.GetPayload().Keys[0].ClientID + cloudConfig.HCPConfig.ObservabilityClientSecret = obsResp.GetPayload().Keys[0].ClientSecret + } + + return cloudConfig, nil } // parseBootstrapConfigResponse unmarshals the boostrap parseBootstrapConfigResponse @@ -184,6 +210,16 @@ func (c *CloudPreset) getHelmConfigWithMapSecretNames(cfg *CloudBootstrapConfig) authURLCfg := getOptionalSecretFromHCPConfig(cfg.HCPConfig.AuthURL, "authUrl", secretNameHCPAuthURL, secretKeyHCPAuthURL) scadaAddressCfg := getOptionalSecretFromHCPConfig(cfg.HCPConfig.ScadaAddress, "scadaAddress", secretNameHCPScadaAddress, secretKeyHCPScadaAddress) + var ( + observabilityClientIDSecretName = secretNameHCPObservabilityClientID + observabilityClientSecretSecretName = secretNameHCPObservabilityClientSecret + ) + + if cfg.HCPConfig.ObservabilityClientID == "" && cfg.HCPConfig.ObservabilityClientSecret == "" { + observabilityClientIDSecretName = secretNameHCPClientID + observabilityClientSecretSecretName = secretNameHCPClientSecret + } + // Need to make sure the below has strict spaces and no tabs values := fmt.Sprintf(` global: @@ -230,7 +266,7 @@ telemetryCollector: server: replicas: %d affinity: null - serverCert: + serverCert: secretName: %s connectInject: enabled: true @@ -243,8 +279,8 @@ controller: secretNameHCPClientID, secretKeyHCPClientID, secretNameHCPClientSecret, secretKeyHCPClientSecret, apiHostCfg, authURLCfg, scadaAddressCfg, - secretNameHCPClientID, secretKeyHCPClientID, - secretNameHCPClientSecret, secretKeyHCPClientSecret, + observabilityClientIDSecretName, secretKeyHCPClientID, + observabilityClientSecretSecretName, secretKeyHCPClientSecret, cfg.BootstrapResponse.Cluster.BootstrapExpect, secretNameServerCert) valuesMap := config.ConvertToMap(values) return valuesMap @@ -305,6 +341,28 @@ func (c *CloudPreset) saveSecretsFromBootstrapConfig(config *CloudBootstrapConfi secretKeyHCPClientSecret, c.KubernetesNamespace), terminal.WithSuccessStyle()) } + if config.HCPConfig.ObservabilityClientID != "" { + data := map[string][]byte{ + secretKeyHCPClientID: []byte(config.HCPConfig.ObservabilityClientID), + } + if err := c.saveSecret(secretNameHCPObservabilityClientID, data, corev1.SecretTypeOpaque); err != nil { + return err + } + c.UI.Output(fmt.Sprintf("HCP client secret saved in '%s' secret in namespace '%s'.", + "observability-"+secretKeyHCPClientID, c.KubernetesNamespace), terminal.WithSuccessStyle()) + } + + if config.HCPConfig.ObservabilityClientSecret != "" { + data := map[string][]byte{ + secretKeyHCPClientSecret: []byte(config.HCPConfig.ObservabilityClientSecret), + } + if err := c.saveSecret(secretNameHCPObservabilityClientSecret, data, corev1.SecretTypeOpaque); err != nil { + return err + } + c.UI.Output(fmt.Sprintf("HCP client secret saved in '%s' secret in namespace '%s'.", + "observability-"+secretKeyHCPClientSecret, c.KubernetesNamespace), terminal.WithSuccessStyle()) + } + // bootstrap token if config.ConsulConfig.ACL.Tokens.InitialManagement != "" { data := map[string][]byte{ diff --git a/cli/preset/cloud_preset_test.go b/cli/preset/cloud_preset_test.go index d905cb4088..001f5c762e 100644 --- a/cli/preset/cloud_preset_test.go +++ b/cli/preset/cloud_preset_test.go @@ -25,28 +25,32 @@ import ( ) const ( - hcpClientID = "RAxJflDbxDXw8kLY6jWmwqMz3kVe7NnL" - hcpClientSecret = "1fNzurLatQPLPwf7jnD4fRtU9f5nH31RKBHayy08uQ6P-6nwI1rFZjMXb4m3cCKH" - hcpResourceID = "organization/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/project/36019e0d-ed59-4df6-9990-05bb7fc793b6/hashicorp.consul.global-network-manager.cluster/prod-on-prem" - expectedSecretNameHCPClientId = "consul-hcp-client-id" - expectedSecretNameHCPClientSecret = "consul-hcp-client-secret" - expectedSecretNameHCPResourceId = "consul-hcp-resource-id" - expectedSecretNameHCPAuthURL = "consul-hcp-auth-url" - expectedSecretNameHCPApiHostname = "consul-hcp-api-host" - expectedSecretNameHCPScadaAddress = "consul-hcp-scada-address" - expectedSecretNameGossipKey = "consul-gossip-key" - expectedSecretNameBootstrap = "consul-bootstrap-token" - expectedSecretNameServerCA = "consul-server-ca" - expectedSecretNameServerCert = "consul-server-cert" - namespace = "consul" - validResponse = ` + hcpClientID = "RAxJflDbxDXw8kLY6jWmwqMz3kVe7NnL" + hcpClientSecret = "1fNzurLatQPLPwf7jnD4fRtU9f5nH31RKBHayy08uQ6P-6nwI1rFZjMXb4m3cCKH" + observabilityHCPClientId = "fake-client-id" + observabilityHCPClientSecret = "fake-client-secret" + hcpResourceID = "organization/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/project/36019e0d-ed59-4df6-9990-05bb7fc793b6/hashicorp.consul.global-network-manager.cluster/prod-on-prem" + expectedSecretNameHCPClientId = "consul-hcp-client-id" + expectedSecretNameHCPClientSecret = "consul-hcp-client-secret" + expectedSecretNameHCPObservabilityClientId = "consul-hcp-observability-client-id" + expectedSecretNameHCPObservabilityClientSecret = "consul-hcp-observability-client-secret" + expectedSecretNameHCPResourceId = "consul-hcp-resource-id" + expectedSecretNameHCPAuthURL = "consul-hcp-auth-url" + expectedSecretNameHCPApiHostname = "consul-hcp-api-host" + expectedSecretNameHCPScadaAddress = "consul-hcp-scada-address" + expectedSecretNameGossipKey = "consul-gossip-key" + expectedSecretNameBootstrap = "consul-bootstrap-token" + expectedSecretNameServerCA = "consul-server-ca" + expectedSecretNameServerCert = "consul-server-cert" + namespace = "consul" + validResponse = ` { - "cluster": + "cluster": { "id": "Dc1", "bootstrap_expect" : 3 }, - "bootstrap": + "bootstrap": { "gossip_key": "Wa6/XFAnYy0f9iqVH2iiG+yore3CqHSemUy4AIVTa/w=", "server_tls": { @@ -59,6 +63,22 @@ const ( "consul_config": "{\"acl\":{\"default_policy\":\"deny\",\"enable_token_persistence\":true,\"enabled\":true,\"tokens\":{\"agent\":\"74044c72-03c8-42b0-b57f-728bb22ca7fb\",\"initial_management\":\"74044c72-03c8-42b0-b57f-728bb22ca7fb\"}},\"auto_encrypt\":{\"allow_tls\":true},\"bootstrap_expect\":1,\"encrypt\":\"yUPhgtteok1/bHoVIoRnJMfOrKrb1TDDyWJRh9rlUjg=\",\"encrypt_verify_incoming\":true,\"encrypt_verify_outgoing\":true,\"ports\":{\"http\":-1,\"https\":8501},\"retry_join\":[],\"verify_incoming\":true,\"verify_outgoing\":true,\"verify_server_hostname\":true}" } }` + observabilityResponse = ` +{ + "id": "Dc1", + "location": { + "organization_id": "abc123", + "project_id": "123abc" + }, + "keys": [ + { + "created_at":"", + "client_id": "fake-client-id", + "client_secret": "fake-client-secret" + } + ] +} +` ) var validBootstrapReponse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse = &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ @@ -79,12 +99,14 @@ var validBootstrapReponse *models.HashicorpCloudGlobalNetworkManager20220215Agen } var hcpConfig *HCPConfig = &HCPConfig{ - ResourceID: hcpResourceID, - ClientID: hcpClientID, - ClientSecret: hcpClientSecret, - AuthURL: "https://foobar", - APIHostname: "https://foo.bar", - ScadaAddress: "10.10.10.10", + ResourceID: hcpResourceID, + ClientID: hcpClientID, + ClientSecret: hcpClientSecret, + AuthURL: "https://foobar", + APIHostname: "https://foo.bar", + ScadaAddress: "10.10.10.10", + ObservabilityClientID: observabilityHCPClientId, + ObservabilityClientSecret: observabilityHCPClientSecret, } var validBootstrapConfig *CloudBootstrapConfig = &CloudBootstrapConfig{ @@ -108,9 +130,19 @@ func TestGetValueMap(t *testing.T) { // Start the mock HCP server. hcpMockServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/json") - if r != nil && r.URL.Path == "/global-network-manager/2022-02-15/organizations/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/projects/36019e0d-ed59-4df6-9990-05bb7fc793b6/clusters/prod-on-prem/agent/bootstrap_config" && - r.Method == "GET" { - w.Write([]byte(validResponse)) + if r != nil && r.Method == "GET" { + switch r.URL.Path { + case "/global-network-manager/2022-02-15/organizations/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/projects/36019e0d-ed59-4df6-9990-05bb7fc793b6/clusters/prod-on-prem/agent/bootstrap_config": + w.Write([]byte(validResponse)) + case "/global-network-manager/2022-02-15/organizations/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/projects/36019e0d-ed59-4df6-9990-05bb7fc793b6/clusters/prod-on-prem/credentials/observability": + w.Write([]byte(observabilityResponse)) + default: + w.Write([]byte(` + { + "access_token": "dummy-token" + } + `)) + } } else { w.Write([]byte(` { @@ -203,6 +235,14 @@ func TestGetValueMap(t *testing.T) { ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPClientSecret, secretKeyHCPClientSecret, bsConfig.HCPConfig.ClientSecret, corev1.SecretTypeOpaque) + // Check the observability hcp client id secret is as expected. + ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPObservabilityClientID, secretKeyHCPClientID, + bsConfig.HCPConfig.ObservabilityClientID, corev1.SecretTypeOpaque) + + // Check the observability hcp client secret secret is as expected. + ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPObservabilityClientSecret, secretKeyHCPClientSecret, + bsConfig.HCPConfig.ObservabilityClientSecret, corev1.SecretTypeOpaque) + // Check the bootstrap token secret is as expected. ensureSecretKeyValueMatchesExpected(t, k8s, secretNameBootstrapToken, secretKeyBootstrapToken, bsConfig.ConsulConfig.ACL.Tokens.InitialManagement, corev1.SecretTypeOpaque) @@ -497,6 +537,26 @@ server: serverCert: secretName: consul-server-cert telemetryCollector: + cloud: + clientId: + secretKey: client-id + secretName: consul-hcp-observability-client-id + clientSecret: + secretKey: client-secret + secretName: consul-hcp-observability-client-secret + enabled: true +` + + const expectedWithoutOptional = `connectInject: + enabled: true +controller: + enabled: true +global: + acls: + bootstrapToken: + secretKey: token + secretName: consul-bootstrap-token + manageSystemACLs: true cloud: clientId: secretKey: client-id @@ -504,10 +564,39 @@ telemetryCollector: clientSecret: secretKey: client-secret secretName: consul-hcp-client-secret + enabled: true + resourceId: + secretKey: resource-id + secretName: consul-hcp-resource-id + datacenter: dc1 + gossipEncryption: + secretKey: key + secretName: consul-gossip-key + metrics: + enableTelemetryCollector: true + tls: + caCert: + secretKey: tls.crt + secretName: consul-server-ca + enableAutoEncrypt: true + enabled: true +server: + affinity: null + replicas: 3 + serverCert: + secretName: consul-server-cert +telemetryCollector: + cloud: + clientId: + secretKey: client-id + secretName: consul-hcp-observability-client-id + clientSecret: + secretKey: client-secret + secretName: consul-hcp-observability-client-secret enabled: true ` - const expectedWithoutOptional = `connectInject: + const expectedWithoutObservability = `connectInject: enabled: true controller: enabled: true @@ -571,12 +660,14 @@ telemetryCollector: }, }, HCPConfig: HCPConfig{ - ResourceID: "consul-hcp-resource-id", - ClientID: "consul-hcp-client-id", - ClientSecret: "consul-hcp-client-secret", - AuthURL: "consul-hcp-auth-url", - APIHostname: "consul-hcp-api-host", - ScadaAddress: "consul-hcp-scada-address", + ResourceID: "consul-hcp-resource-id", + ClientID: "consul-hcp-client-id", + ClientSecret: "consul-hcp-client-secret", + AuthURL: "consul-hcp-auth-url", + APIHostname: "consul-hcp-api-host", + ScadaAddress: "consul-hcp-scada-address", + ObservabilityClientID: "consul-hcp-observability-client-id", + ObservabilityClientSecret: "consul-hcp-observability-client-secret", }, }, expectedFull, @@ -590,17 +681,37 @@ telemetryCollector: }, }, HCPConfig: HCPConfig{ - ResourceID: "consul-hcp-resource-id", - ClientID: "consul-hcp-client-id", - ClientSecret: "consul-hcp-client-secret", - AuthURL: "consul-hcp-auth-url", - APIHostname: "consul-hcp-api-host", - ScadaAddress: "consul-hcp-scada-address", + ResourceID: "consul-hcp-resource-id", + ClientID: "consul-hcp-client-id", + ClientSecret: "consul-hcp-client-secret", + AuthURL: "consul-hcp-auth-url", + APIHostname: "consul-hcp-api-host", + ScadaAddress: "consul-hcp-scada-address", + ObservabilityClientID: "consul-hcp-observability-client-id", + ObservabilityClientSecret: "consul-hcp-observability-client-secret", }, }, expectedFull, }, "Config_without_optional_parameters": { + &CloudBootstrapConfig{ + BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ + Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ + BootstrapExpect: 3, + ID: "dc1", + }, + }, + HCPConfig: HCPConfig{ + ResourceID: "consul-hcp-resource-id", + ClientID: "consul-hcp-client-id", + ClientSecret: "consul-hcp-client-secret", + ObservabilityClientID: "consul-hcp-observability-client-id", + ObservabilityClientSecret: "consul-hcp-observability-client-secret", + }, + }, + expectedWithoutOptional, + }, + "Config_without_observability_parameters": { &CloudBootstrapConfig{ BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ @@ -614,7 +725,7 @@ telemetryCollector: ClientSecret: "consul-hcp-client-secret", }, }, - expectedWithoutOptional, + expectedWithoutObservability, }, } for name, tc := range testCases { @@ -647,6 +758,8 @@ func savePlaceholderSecret(secretName string, k8sClient kubernetes.Interface) { func deleteSecrets(k8sClient kubernetes.Interface) { k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPClientId, metav1.DeleteOptions{}) k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPClientSecret, metav1.DeleteOptions{}) + k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPObservabilityClientId, metav1.DeleteOptions{}) + k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPObservabilityClientSecret, metav1.DeleteOptions{}) k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPResourceId, metav1.DeleteOptions{}) k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPAuthURL, metav1.DeleteOptions{}) k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPApiHostname, metav1.DeleteOptions{}) @@ -675,6 +788,14 @@ func checkAllSecretsWereSaved(t require.TestingT, k8s kubernetes.Interface, expe ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPClientSecret, secretKeyHCPClientSecret, expectedConfig.HCPConfig.ClientSecret, corev1.SecretTypeOpaque) + // Check the hcp client id secret is as expected. + ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPObservabilityClientID, secretKeyHCPClientID, + expectedConfig.HCPConfig.ObservabilityClientID, corev1.SecretTypeOpaque) + + // Check the hcp client secret secret is as expected. + ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPObservabilityClientSecret, secretKeyHCPClientSecret, + expectedConfig.HCPConfig.ObservabilityClientSecret, corev1.SecretTypeOpaque) + // Check the hcp auth URL secret is as expected. ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPAuthURL, secretKeyHCPAuthURL, expectedConfig.HCPConfig.AuthURL, corev1.SecretTypeOpaque) @@ -720,6 +841,8 @@ func checkSecretsWereNotSaved(k8s kubernetes.Interface) bool { ns, _ := k8s.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) hcpClientIdSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPClientID, metav1.GetOptions{}) hcpClientSecretSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPClientSecret, metav1.GetOptions{}) + hcpObservabilityClientIdSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPObservabilityClientID, metav1.GetOptions{}) + hcpObservabilityClientSecretSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPObservabilityClientSecret, metav1.GetOptions{}) hcpResourceIdSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPResourceID, metav1.GetOptions{}) bootstrapSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameBootstrapToken, metav1.GetOptions{}) gossipKeySecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameGossipKey, metav1.GetOptions{}) @@ -727,7 +850,7 @@ func checkSecretsWereNotSaved(k8s kubernetes.Interface) bool { serverCASecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameServerCA, metav1.GetOptions{}) return ns == nil && hcpClientIdSecret == nil && hcpClientSecretSecret == nil && hcpResourceIdSecret == nil && bootstrapSecret == nil && - gossipKeySecret == nil && serverCASecret == nil && serverCertSecret == nil + gossipKeySecret == nil && serverCASecret == nil && serverCertSecret == nil && hcpObservabilityClientIdSecret == nil && hcpObservabilityClientSecretSecret == nil } func getDeepCopyOfValidBootstrapConfig() *CloudBootstrapConfig { From 1fde7ab81a255542292209a979810a176f35f14f Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Wed, 20 Sep 2023 15:19:31 -0700 Subject: [PATCH 393/592] v2: multiport acceptance test in tproxy mode (#2970) * wip: controllers are running, and now multiport mesh init is stuck, with workloads not being synced to consul and error logs from endpoints v2 controller * explicitly specify dataplane tenancy (needed from manual testing) * connect inject deployment was missing a \ to complete the command * server statefulset was also missing a \ so v2 mode wasn't being turned on. --- Thanks for pairing, I may have missed a few names of folks who've hopped in to help debug these: Co-authored-by: Iryna Shustava Co-authored-by: Dan Stough Co-authored-by: Michael Wilkerson Co-authored-by: John Murret Co-authored-by: Michael Zalimeni --- acceptance/tests-v2/mesh/main_test.go | 18 ++++ acceptance/tests-v2/mesh/mesh_inject_test.go | 84 +++++++++++++++++++ .../anyuid-scc-rolebinding.yaml | 26 ++++++ .../bases/v2-multiport-app/deployment.yaml | 80 ++++++++++++++++++ .../bases/v2-multiport-app/kustomization.yaml | 11 +++ .../privileged-scc-rolebinding.yaml | 14 ++++ .../v2-multiport-app/psp-rolebinding.yaml | 14 ++++ .../bases/v2-multiport-app/secret.yaml | 10 +++ .../bases/v2-multiport-app/service.yaml | 18 ++++ .../v2-multiport-app/serviceaccount.yaml | 7 ++ .../kustomization.yaml | 9 ++ .../v2-static-client-inject-tproxy/patch.yaml | 13 +++ .../templates/connect-inject-deployment.yaml | 2 +- .../consul/templates/server-statefulset.yaml | 2 +- .../controllers/pod/pod_controller.go | 7 +- .../controllers/pod/pod_controller_test.go | 16 ++-- .../webhook_v2/consul_dataplane_sidecar.go | 9 +- .../webhook_v2/mesh_webhook_test.go | 2 + .../inject-connect/v2controllers.go | 10 ++- 19 files changed, 334 insertions(+), 18 deletions(-) create mode 100644 acceptance/tests-v2/mesh/main_test.go create mode 100644 acceptance/tests-v2/mesh/mesh_inject_test.go create mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml create mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml create mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml create mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml create mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml create mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml create mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml create mode 100644 acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml diff --git a/acceptance/tests-v2/mesh/main_test.go b/acceptance/tests-v2/mesh/main_test.go new file mode 100644 index 0000000000..6889dfbd13 --- /dev/null +++ b/acceptance/tests-v2/mesh/main_test.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mesh + +import ( + "os" + "testing" + + testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" +) + +var suite testsuite.Suite + +func TestMain(m *testing.M) { + suite = testsuite.NewSuite(m) + os.Exit(suite.Run()) +} diff --git a/acceptance/tests-v2/mesh/mesh_inject_test.go b/acceptance/tests-v2/mesh/mesh_inject_test.go new file mode 100644 index 0000000000..e6deb4aa8c --- /dev/null +++ b/acceptance/tests-v2/mesh/mesh_inject_test.go @@ -0,0 +1,84 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mesh + +import ( + "context" + "fmt" + "strconv" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" +) + +// Test that mesh sidecar proxies work for an application with multiple ports. The multiport application is a Pod listening on +// two ports. This tests inbound connections to each port of the multiport app, and outbound connections from the +// multiport app to static-server. +func TestMeshInject_MultiportService(t *testing.T) { + for _, secure := range []bool{false} { + name := fmt.Sprintf("secure: %t", secure) + + t.Run(name, func(t *testing.T) { + cfg := suite.Config() + cfg.SkipWhenOpenshiftAndCNI(t) + if !cfg.EnableTransparentProxy { + t.Skipf("skipping this because -enable-transparent-proxy is not set") + } + ctx := suite.Environment().DefaultContext(t) + + helmValues := map[string]string{ + "global.image": "ndhanushkodi/consul-dev:multiport36", + "global.imageK8S": "ndhanushkodi/consul-k8s-dev:multiport20", + "global.imageConsulDataplane": "hashicorppreview/consul-dataplane:1.3-dev", + "global.experiments[0]": "resource-apis", + // The UI is not supported for v2 in 1.17, so for now it must be disabled. + "ui.enabled": "false", + "connectInject.enabled": "true", + // Enable DNS so we can test that DNS redirection _isn't_ set in the pod. + "dns.enabled": "true", + + "global.tls.enabled": strconv.FormatBool(secure), + "global.acls.manageSystemACLs": strconv.FormatBool(secure), + } + + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + + consulCluster.Create(t) + + logger.Log(t, "creating multiport static-server and static-client deployments") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/bases/v2-multiport-app") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/v2-static-client-inject-tproxy") + + // Check that static-client has been injected and now has 2 containers. + podList, err := ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: "app=static-client", + }) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + + // Check that multiport has been injected and now has 3 containers. + podList, err = ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: "app=multiport", + }) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 3) + + // Check connection from static-client to multiport. + k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://multiport:8080") + + // Check connection from static-client to multiport-admin. + k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://multiport:9090") + }) + } +} diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml new file mode 100644 index 0000000000..5c2e0dcfa2 --- /dev/null +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml @@ -0,0 +1,26 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: multiport-openshift-anyuid +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:anyuid +subjects: + - kind: ServiceAccount + name: multiport +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: multiport-admin-openshift-anyuid +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:anyuid +subjects: + - kind: ServiceAccount + name: multiport-admin diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml new file mode 100644 index 0000000000..0aefa14d50 --- /dev/null +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml @@ -0,0 +1,80 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: multiport +spec: + replicas: 1 + selector: + matchLabels: + app: multiport + template: + metadata: + name: multiport + labels: + app: multiport + annotations: + "consul.hashicorp.com/mesh-inject": "true" + # TODO: remove this when we add tproxy patch support for this fixture + 'consul.hashicorp.com/transparent-proxy': 'true' + 'consul.hashicorp.com/enable-metrics': 'false' + 'consul.hashicorp.com/enable-metrics-merging': 'false' + spec: + containers: + - name: multiport + image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine + args: + - -text="hello world" + - -listen=:8080 + ports: + - containerPort: 8080 + name: web +# livenessProbe: +# httpGet: +# port: 8080 +# initialDelaySeconds: 1 +# failureThreshold: 1 +# periodSeconds: 1 +# startupProbe: +# httpGet: +# port: 8080 +# initialDelaySeconds: 1 +# failureThreshold: 30 +# periodSeconds: 1 + readinessProbe: + exec: + command: ['sh', '-c', 'test ! -f /tmp/unhealthy-multiport'] + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + - name: multiport-admin + image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine + args: + - -text="hello world from 9090 admin" + - -listen=:9090 + ports: + - containerPort: 9090 + name: admin +# TODO: (v2/nitya) add these probes back when expose paths and L7 are supported. +# livenessProbe: +# httpGet: +# port: 9090 +# initialDelaySeconds: 1 +# failureThreshold: 1 +# periodSeconds: 1 +# startupProbe: +# httpGet: +# port: 9090 +# initialDelaySeconds: 1 +# failureThreshold: 30 +# periodSeconds: 1 + readinessProbe: + exec: + command: ['sh', '-c', 'test ! -f /tmp/unhealthy-multiport-admin'] + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + serviceAccountName: multiport + terminationGracePeriodSeconds: 0 # so deletion is quick diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml new file mode 100644 index 0000000000..fb792d63a7 --- /dev/null +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml @@ -0,0 +1,11 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - deployment.yaml + - service.yaml + - secret.yaml + - serviceaccount.yaml + - psp-rolebinding.yaml + - anyuid-scc-rolebinding.yaml + - privileged-scc-rolebinding.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml new file mode 100644 index 0000000000..f4f734813e --- /dev/null +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: multiport-openshift-privileged +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: multiport diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml new file mode 100644 index 0000000000..623a388d20 --- /dev/null +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: multiport +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: test-psp +subjects: + - kind: ServiceAccount + name: multiport diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml new file mode 100644 index 0000000000..a412cac6c5 --- /dev/null +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml @@ -0,0 +1,10 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: Secret +metadata: + name: multiport + annotations: + kubernetes.io/service-account.name: multiport +type: kubernetes.io/service-account-token diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml new file mode 100644 index 0000000000..940a9a3a17 --- /dev/null +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml @@ -0,0 +1,18 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: Service +metadata: + name: multiport +spec: + selector: + app: multiport + ports: + - name: web + port: 8080 + # TODO: (v2/nitya) ensure this works with numeric target ports also once support for that is merged. + targetPort: web + - name: admin + port: 9090 + targetPort: admin diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml new file mode 100644 index 0000000000..8af955e059 --- /dev/null +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: multiport diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml new file mode 100644 index 0000000000..564d02a68f --- /dev/null +++ b/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml @@ -0,0 +1,9 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml new file mode 100644 index 0000000000..aa96c39398 --- /dev/null +++ b/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/mesh-inject": "true" + "consul.hashicorp.com/transparent-proxy": "true" \ No newline at end of file diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 0b7649089d..53f894035a 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -152,7 +152,7 @@ spec: -resource-prefix={{ template "consul.fullname" . }} \ -listen=:8080 \ {{- if (mustHas "resource-apis" .Values.global.experiments) }} - -enable-resource-apis=true + -enable-resource-apis=true \ {{- end }} {{- range $k, $v := .Values.connectInject.consulNode.meta }} -node-meta={{ $k }}={{ $v }} \ diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index c746846ebd..d2785369c7 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -421,7 +421,7 @@ spec: {{- end }} {{- end }} -config-file=/consul/extra-config/extra-from-values.json \ - -config-file=/consul/extra-config/locality.json + -config-file=/consul/extra-config/locality.json \ {{- if and .Values.global.cloud.enabled .Values.global.cloud.resourceId.secretName }} -hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }" {{- end }} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 1e6c5e1ddb..b0f9897a5c 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -209,8 +209,11 @@ func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { }, Identity: pod.Spec.ServiceAccountName, Locality: locality, - NodeName: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), - Ports: workloadPorts, + // Adding a node does not currently work because the node doesn't exist so its health status will always be + // unhealthy, causing any endpoints on that node to also be unhealthy. + // TODO: (v2/nitya) Bring this back when node controller is built. + //NodeName: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), + Ports: workloadPorts, } data := common.ToProtoAny(workload) diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 5e41c44449..ce326ff56d 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -36,11 +36,12 @@ import ( ) const ( - nodeName = "test-node" - localityNodeName = "test-node-w-locality" - consulNodeName = "test-node-virtual" - consulLocalityNodeName = "test-node-w-locality-virtual" - consulNodeAddress = "127.0.0.1" + // TODO: (v2/nitya) Bring back consulLocalityNodeName once node controller is implemented and assertions for + // workloads need node names again. + nodeName = "test-node" + localityNodeName = "test-node-w-locality" + consulNodeName = "test-node-virtual" + consulNodeAddress = "127.0.0.1" ) func TestParseLocality(t *testing.T) { @@ -194,7 +195,6 @@ func TestWorkloadWrite(t *testing.T) { Protocol: pbcatalog.Protocol_PROTOCOL_MESH, }, }, - NodeName: consulNodeName, Identity: "foo", }, }, @@ -226,7 +226,6 @@ func TestWorkloadWrite(t *testing.T) { Region: "us-east1", Zone: "us-east1-b", }, - NodeName: consulLocalityNodeName, Identity: "foo", }, }, @@ -255,7 +254,6 @@ func TestWorkloadWrite(t *testing.T) { Protocol: pbcatalog.Protocol_PROTOCOL_MESH, }, }, - NodeName: consulNodeName, Identity: "foo", }, }, @@ -275,7 +273,6 @@ func TestWorkloadWrite(t *testing.T) { Protocol: pbcatalog.Protocol_PROTOCOL_MESH, }, }, - NodeName: consulNodeName, Identity: "foo", }, }, @@ -1385,7 +1382,6 @@ func createWorkload() *pbcatalog.Workload { Protocol: pbcatalog.Protocol_PROTOCOL_MESH, }, }, - NodeName: consulNodeName, Identity: "foo", } } diff --git a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go index 5576f919d4..46e01c62f5 100644 --- a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go @@ -125,8 +125,13 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor MountPath: "/consul/mesh-inject", }, }, - Args: args, - ReadinessProbe: probe, + Args: args, + } + + // Omit the readiness probe in transparent proxy mode until expose paths are implemented. Otherwise all probes will fail. + // TODO: (v2/nitya) add probes in tproxy mode when expose paths and L7 are supported. + if !w.EnableTransparentProxy { + container.ReadinessProbe = probe } if w.AuthMethod != "" { diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go b/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go index 6f3d1f339a..799082ee4b 100644 --- a/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go @@ -928,6 +928,8 @@ func TestHandlerHandle(t *testing.T) { // This test validates that overwrite probes match the iptables configuration fromiptablesConfigJSON() // Because they happen at different points in the injection, the port numbers can get out of sync. func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { + // TODO (v2/nitya): enable when expose paths and L7 are implemented + t.Skip("Tproxy probes are not supported yet") t.Parallel() s := runtime.NewScheme() s.AddKnownTypes(schema.GroupVersion{ diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 74fc908e64..7291229b22 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -5,6 +5,8 @@ package connectinject import ( "context" + + "github.com/hashicorp/consul-server-connection-manager/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -16,13 +18,12 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/namespace" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" webhookV2 "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook_v2" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - "github.com/hashicorp/consul-server-connection-manager/discovery" ) func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { - // Create Consul API config object. consulConfig := c.consul.ConsulClientConfig() @@ -179,6 +180,11 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage LogJSON: c.flagLogJSON, }}) + if err := mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { + setupLog.Error(err, "unable to create readiness check") + return err + } + if c.flagEnableWebhookCAUpdate { err := c.updateWebhookCABundle(ctx) if err != nil { From 04b7db2efde7b0125024499b2ba11cce4a872700 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Wed, 20 Sep 2023 22:11:03 -0700 Subject: [PATCH 394/592] Mw/net 5340 add support for explicit upstreams (#2977) * fixed some comments and old things - spelling mistakes - clarity on old controller also removed peer from unlabeled as this is unsupported * added explicit upstreams writing/deleting - added processing for pod annotations to handle labeled and unlabeled case. This is heavily based on what was done in the endpoints controller - one deviation is instead of looking for the first key to determine the labeled case, I scan for all keys. I think this will provide us with more meaningful errors since the ordering matters. * added explicit upstream write/delete tests - most of the tests cases are based on what is currently in the endpoint controller - split some of the write cases into just testing the processing logic so that we don't have to spin up a Consul client each time which made the test very slow. * disable support for peers and dcs in annotations, this will be added back when supported --- .../constants/annotations_and_labels.go | 4 +- .../endpoints/endpoints_controller.go | 5 +- .../controllers/pod/pod_controller.go | 353 +++- .../controllers/pod/pod_controller_test.go | 1494 ++++++++++++++++- 4 files changed, 1810 insertions(+), 46 deletions(-) diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 371820a5c4..0bab084b1f 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -75,7 +75,7 @@ const ( // AnnotationUpstreams is a list of upstreams to register with the // proxy in the format of `:,...`. The - // service name should map to a Consul service namd and the local port + // service name should map to a Consul service name and the local port // is the local port in the pod that the listener will bind to. It can // be a named port. AnnotationUpstreams = "consul.hashicorp.com/connect-service-upstreams" @@ -235,7 +235,7 @@ const ( ManagedByServiceAccountValue = "consul-k8s-service-account-controller" // AnnotationMeshDestinations is a list of upstreams to register with the - // proxy. The service name should map to a Consul service namd and the local + // proxy. The service name should map to a Consul service name and the local // port is the local port in the pod that the listener will bind to. It can // be a named port. AnnotationMeshDestinations = "consul.hashicorp.com/mesh-service-destinations" diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 136934a451..ea22628022 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -1164,8 +1164,9 @@ func processPreparedQueryUpstream(pod corev1.Pod, rawUpstream string) api.Upstre // processUnlabeledUpstream processes an upstream in the format: // [service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. +// There is no unlabeled field for peering. func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string) (api.Upstream, error) { - var datacenter, svcName, namespace, partition, peer string + var datacenter, svcName, namespace, partition string var port int32 var upstream api.Upstream @@ -1199,7 +1200,7 @@ func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string upstream = api.Upstream{ DestinationType: api.UpstreamDestTypeService, DestinationPartition: partition, - DestinationPeer: peer, + DestinationPeer: "", DestinationNamespace: namespace, DestinationName: svcName, Datacenter: datacenter, diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index b0f9897a5c..9feb090043 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "strconv" + "strings" "github.com/go-logr/logr" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" @@ -30,8 +31,13 @@ import ( const ( DefaultTelemetryBindSocketDir = "/consul/mesh-inject" + consulNodeAddress = "127.0.0.1" ) +// Controller watches Pod events and converts them to V2 Workloads and HealthStatus. +// The translation from Pod to Workload is 1:1 and the HealthStatus object is a representation +// of the Pod's Status field. Controller is also responsible for generating V2 Upstreams resources +// when not in transparent proxy mode. ProxyConfiguration is also optionally created. type Controller struct { client.Client // ConsulClientConfig is the config for the Consul API client. @@ -53,7 +59,7 @@ type Controller struct { TProxyOverwriteProbes bool // AuthMethod is the name of the Kubernetes Auth Method that - // was used to login with Consul. The Endpoints controller + // was used to login with Consul. The pods controller // will delete any tokens associated with this auth method // whenever service instances are deregistered. AuthMethod string @@ -104,10 +110,10 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // TODO: clean up ACL Tokens - // TODO: delete explicit upstreams - //if err := r.deleteUpstreams(ctx, pod); err != nil { - // errs = multierror.Append(errs, err) - //} + // Delete upstreams, if any exist + if err := r.deleteUpstreams(ctx, req.NamespacedName); err != nil { + errs = multierror.Append(errs, err) + } if err := r.deleteProxyConfiguration(ctx, req.NamespacedName); err != nil { errs = multierror.Append(errs, err) @@ -146,10 +152,18 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu errs = multierror.Append(errs, err) } - // TODO: create explicit upstreams - //if err := r.writeUpstreams(ctx, pod); err != nil { - // errs = multierror.Append(errs, err) - //} + // Create explicit upstreams (if any exist) + if err := r.writeUpstreams(ctx, pod); err != nil { + // Technically this is not needed, but keeping in case this gets refactored in + // a different order + if common.ConsulNamespaceIsNotFound(err) { + r.Log.Info("Consul namespace not found; re-queueing request", + "pod", req.Name, "ns", req.Namespace, "consul-ns", + r.getConsulNamespace(req.Namespace), "err", err.Error()) + return ctrl.Result{Requeue: true}, nil + } + errs = multierror.Append(errs, err) + } if err := r.writeHealthStatus(ctx, pod); err != nil { // Technically this is not needed, but keeping in case this gets refactored in @@ -435,10 +449,274 @@ func (r *Controller) writeHealthStatus(ctx context.Context, pod corev1.Pod) erro // has been configured with and will only delete tokens for the provided podName. // func (r *Controller) deleteACLTokensForWorkload(apiClient *api.Client, svc *api.AgentService, k8sNS, podName string) error { -// TODO: add support for explicit upstreams -//func (r *Controller) writeUpstreams(pod corev1.Pod) error +// writeUpstreams will write explicit upstreams if pod annotations exist. +func (r *Controller) writeUpstreams(ctx context.Context, pod corev1.Pod) error { + uss, err := r.processUpstreams(pod) + if err != nil { + return fmt.Errorf("error processing upstream annotations: %s", err.Error()) + } + if uss == nil { + return nil + } + + data := common.ToProtoAny(uss) + req := &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: getUpstreamsID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), + Metadata: metaFromPod(pod), + Data: data, + }, + } + _, err = r.ResourceClient.Write(ctx, req) + + return err +} + +func (r *Controller) deleteUpstreams(ctx context.Context, pod types.NamespacedName) error { + req := &pbresource.DeleteRequest{ + Id: getUpstreamsID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), + } + + _, err := r.ResourceClient.Delete(ctx, req) + return err +} + +// processUpstreams reads the list of upstreams from the Pod annotation and converts them into a list of pbmesh.Upstreams +// objects. +func (r *Controller) processUpstreams(pod corev1.Pod) (*pbmesh.Upstreams, error) { + upstreams := &pbmesh.Upstreams{} + raw, ok := pod.Annotations[constants.AnnotationMeshDestinations] + if !ok || raw == "" { + return nil, nil + } + + upstreams.Workloads = &pbcatalog.WorkloadSelector{ + Names: []string{pod.Name}, + } + + for _, raw := range strings.Split(raw, ",") { + var upstream *pbmesh.Upstream + + // Determine the type of processing required unlabeled or labeled + // [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter] + // or + // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] + // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] + // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port] + + // Scan the string for the annotation keys. + // Even if the first key is missing, and the order is unexpected, we should let the processing + // provide us with errors + labeledFormat := false + keys := []string{"port", "svc", "ns", "ap", "peer", "dc"} + for _, v := range keys { + if strings.Contains(raw, fmt.Sprintf(".%s.", v)) || strings.Contains(raw, fmt.Sprintf(".%s:", v)) { + labeledFormat = true + break + } + } + + if labeledFormat { + var err error + upstream, err = r.processLabeledUpstream(pod, raw) + if err != nil { + return &pbmesh.Upstreams{}, err + } + } else { + var err error + upstream, err = r.processUnlabeledUpstream(pod, raw) + if err != nil { + return &pbmesh.Upstreams{}, err + } + } + + upstreams.Upstreams = append(upstreams.Upstreams, upstream) + } + + return upstreams, nil +} + +// processLabeledUpstream processes an upstream in the format: +// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] +// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] +// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port]. +// peer/ap/dc are mutually exclusive. At minimum service-port-name and service-name are required. +// The ordering matters for labeled as well as unlabeled. The ordering of the labeled parameters should follow +// the order and requirements of the unlabeled parameters. +// TODO: enable dc and peer support when ready, currently return errors if set. +func (r *Controller) processLabeledUpstream(pod corev1.Pod, rawUpstream string) (*pbmesh.Upstream, error) { + parts := strings.SplitN(rawUpstream, ":", 3) + var port int32 + port, _ = common.PortValue(pod, strings.TrimSpace(parts[1])) + if port <= 0 { + return &pbmesh.Upstream{}, fmt.Errorf("port value %d in upstream is invalid: %s", port, rawUpstream) + } + + service := parts[0] + pieces := strings.Split(service, ".") + + var portName, datacenter, svcName, namespace, partition, peer string + if r.EnableConsulNamespaces || r.EnableConsulPartitions { + switch len(pieces) { + case 8: + end := strings.TrimSpace(pieces[7]) + switch end { + case "peer": + // TODO: uncomment and remove error when peers supported + //peer = strings.TrimSpace(pieces[6]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support peers: %s", rawUpstream) + case "ap": + partition = strings.TrimSpace(pieces[6]) + case "dc": + // TODO: uncomment and remove error when datacenters are supported + //datacenter = strings.TrimSpace(pieces[6]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + fallthrough + case 6: + if strings.TrimSpace(pieces[5]) == "ns" { + namespace = strings.TrimSpace(pieces[4]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + fallthrough + case 4: + if strings.TrimSpace(pieces[3]) == "svc" { + svcName = strings.TrimSpace(pieces[2]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + if strings.TrimSpace(pieces[1]) == "port" { + portName = strings.TrimSpace(pieces[0]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + } else { + switch len(pieces) { + case 6: + end := strings.TrimSpace(pieces[5]) + switch end { + case "peer": + // TODO: uncomment and remove error when peers supported + //peer = strings.TrimSpace(pieces[4]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support peers: %s", rawUpstream) + case "dc": + // TODO: uncomment and remove error when datacenter supported + //datacenter = strings.TrimSpace(pieces[4]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + // TODO: uncomment and remove error when datacenter and/or peers supported + //fallthrough + case 4: + if strings.TrimSpace(pieces[3]) == "svc" { + svcName = strings.TrimSpace(pieces[2]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + if strings.TrimSpace(pieces[1]) == "port" { + portName = strings.TrimSpace(pieces[0]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + } + + upstream := pbmesh.Upstream{ + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(partition), + Namespace: getDefaultConsulNamespace(namespace), + PeerName: getDefaultConsulPeer(peer), + }, + Name: svcName, + }, + DestinationPort: portName, + Datacenter: datacenter, + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(port), + Ip: consulNodeAddress, + }, + }, + } -//func (r *Controller) deleteUpstreams(pod corev1.Pod) error + return &upstream, nil +} + +// processUnlabeledUpstream processes an upstream in the format: +// [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. +// There is no unlabeled field for peering. +// TODO: enable dc and peer support when ready, currently return errors if set. We also most likely won't need to return an error at all. +func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string) (*pbmesh.Upstream, error) { + var portName, datacenter, svcName, namespace, partition string + var port int32 + var upstream pbmesh.Upstream + + parts := strings.SplitN(rawUpstream, ":", 3) + + port, _ = common.PortValue(pod, strings.TrimSpace(parts[1])) + + // If Consul Namespaces or Admin Partitions are enabled, attempt to parse the + // upstream for a namespace. + if r.EnableConsulNamespaces || r.EnableConsulPartitions { + pieces := strings.SplitN(parts[0], ".", 4) + switch len(pieces) { + case 4: + partition = strings.TrimSpace(pieces[3]) + fallthrough + case 3: + namespace = strings.TrimSpace(pieces[2]) + fallthrough + default: + svcName = strings.TrimSpace(pieces[1]) + portName = strings.TrimSpace(pieces[0]) + } + } else { + pieces := strings.SplitN(parts[0], ".", 2) + svcName = strings.TrimSpace(pieces[1]) + portName = strings.TrimSpace(pieces[0]) + } + + // parse the optional datacenter + if len(parts) > 2 { + // TODO: uncomment and remove error when datacenters supported + //datacenter = strings.TrimSpace(parts[2]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + } + + if port > 0 { + upstream = pbmesh.Upstream{ + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(partition), + Namespace: getDefaultConsulNamespace(namespace), + PeerName: getDefaultConsulPeer(""), + }, + Name: svcName, + }, + DestinationPort: portName, + Datacenter: datacenter, + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(port), + Ip: consulNodeAddress, + }, + }, + } + } + return &upstream, nil +} // consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace // depending on Consul Namespaces being enabled and the value of namespace mirroring. @@ -606,3 +884,54 @@ func getHealthStatusID(name, namespace, partition string) *pbresource.ID { }, } } + +func getUpstreamsID(name, namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: name, + Type: &pbresource.Type{ + Group: "mesh", + GroupVersion: "v1alpha1", + Kind: "Upstreams", + }, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func upstreamReferenceType() *pbresource.Type { + return &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + } +} + +func getDefaultConsulNamespace(ns string) string { + if ns == "" { + ns = constants.DefaultConsulNS + } + + return ns +} + +func getDefaultConsulPartition(ap string) string { + if ap == "" { + ap = constants.DefaultConsulPartition + } + + return ap +} + +func getDefaultConsulPeer(peer string) string { + if peer == "" { + peer = constants.DefaultConsulPeer + } + + return peer +} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index ce326ff56d..3ea54e66b6 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -11,6 +11,7 @@ import ( mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul/api" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" "github.com/hashicorp/consul/proto-public/pbresource" @@ -19,6 +20,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,10 +40,9 @@ import ( const ( // TODO: (v2/nitya) Bring back consulLocalityNodeName once node controller is implemented and assertions for // workloads need node names again. - nodeName = "test-node" - localityNodeName = "test-node-w-locality" - consulNodeName = "test-node-virtual" - consulNodeAddress = "127.0.0.1" + nodeName = "test-node" + localityNodeName = "test-node-w-locality" + consulNodeName = "test-node-virtual" ) func TestParseLocality(t *testing.T) { @@ -747,7 +748,7 @@ func TestProxyConfigurationDelete(t *testing.T) { { name: "proxy configuration delete", pod: createPod("foo", "10.0.0.1", "foo", true, true), - existingProxyConfiguration: createProxyConfiguration(), + existingProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, } @@ -758,11 +759,1318 @@ func TestProxyConfigurationDelete(t *testing.T) { } } -// TODO -// func TestUpstreamsWrite(t *testing.T) +// TestUpstreamsWrite does a subsampling of tests covered in TestProcessUpstreams to make sure things are hooked up +// correctly. For the sake of test speed, more exhaustive testing is performed in TestProcessUpstreams. +func TestUpstreamsWrite(t *testing.T) { + t.Parallel() -// TODO -// func TestUpstreamsDelete(t *testing.T) + const podName = "pod1" + + cases := []struct { + name string + pod func() *corev1.Pod + expected *pbmesh.Upstreams + expErr string + consulNamespacesEnabled bool + consulPartitionsEnabled bool + }{ + { + name: "labeled annotated upstream with svc only", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc, ns, and peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234" + return pod1 + }, + expErr: "error processing upstream annotations: upstream currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", + // TODO: uncomment this and remove expErr when peers is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "ns1", + // PeerName: "peer1", + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc, ns, and partition", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: "part1", + Namespace: "ns1", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: invalid partition/dc/peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.err:1234" + return pod1 + }, + expErr: "error processing upstream annotations: upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled single upstream", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled single upstream with namespace and partition", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo.bar:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: "bar", + Namespace: "foo", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + // Create test consulServer client. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + pc := &Controller{ + Log: logrtest.New(t), + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ConsulTenancyConfig: common.ConsulTenancyConfig{ + EnableConsulNamespaces: tt.consulNamespacesEnabled, + EnableConsulPartitions: tt.consulPartitionsEnabled, + }, + ResourceClient: resourceClient, + } + + err = pc.writeUpstreams(context.Background(), *tt.pod()) + + if tt.expErr != "" { + require.EqualError(t, err, tt.expErr) + } else { + require.NoError(t, err) + expectedUpstreamMatches(t, resourceClient, tt.pod().Name, tt.expected) + } + }) + } +} + +func TestProcessUpstreams(t *testing.T) { + t.Parallel() + + const podName = "pod1" + + cases := []struct { + name string + pod func() *corev1.Pod + expected *pbmesh.Upstreams + expErr string + configEntry func() api.ConfigEntry + consulUnavailable bool + consulNamespacesEnabled bool + consulPartitionsEnabled bool + }{ + { + name: "labeled annotated upstream with svc only", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc and dc", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.dc1.dc:1234" + return pod1 + }, + expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.dc1.dc:1234", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: getDefaultConsulNamespace(""), + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "dc1", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc and peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.peer:1234" + return pod1 + }, + expErr: "upstream currently does not support peers: myPort.port.upstream1.svc.peer1.peer:1234", + // TODO: uncomment this and remove expErr when peers is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: getDefaultConsulNamespace(""), + // PeerName: "peer1", + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc, ns, and peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234" + return pod1 + }, + expErr: "upstream currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", + // TODO: uncomment this and remove expErr when peers is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "ns1", + // PeerName: "peer1", + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc, ns, and partition", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: "part1", + Namespace: "ns1", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "labeled annotated upstream with svc, ns, and dc", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234" + return pod1 + }, + expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "ns1", + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "dc1", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "labeled multiple annotated upstreams", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.port.upstream2.svc:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: "ns1", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream2", + }, + DestinationPort: "myPort2", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(2234), + Ip: consulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: "ap1", + Namespace: "ns1", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream4", + }, + DestinationPort: "myPort4", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(4234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "labeled multiple annotated upstreams with dcs and peers", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234, myPort2.port.upstream2.svc:2234, myPort3.port.upstream3.svc.ns1.ns:3234, myPort4.port.upstream4.svc.ns1.ns.peer1.peer:4234" + return pod1 + }, + expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "ns1", + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "dc1", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: getDefaultConsulNamespace(""), + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream2", + // }, + // DestinationPort: "myPort2", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(2234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "ns1", + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream3", + // }, + // DestinationPort: "myPort3", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(3234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "ns1", + // PeerName: "peer1", + // }, + // Name: "upstream4", + // }, + // DestinationPort: "myPort4", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(4234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: invalid partition/dc/peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.err:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream with svc and peer, needs ns before peer if namespaces enabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.peer:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.peer1.peer:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: invalid namespace", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.err:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.err:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: invalid number of pieces in the address", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.err:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.err:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: invalid peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.err:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.peer1.err:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: invalid number of pieces in the address without namespaces and partitions", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.err:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.err:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: both peer and partition provided", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: both peer and dc provided", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: both dc and partition provided", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition enabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition disabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "upstream1.svc.myPort.port:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: upstream1.svc.myPort.port:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: incorrect key name namespace partition enabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: incorrect key name namespace partition disabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.portage.upstream1.svc:1234" + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.portage.upstream1.svc:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled and labeled multiple annotated upstreams", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.upstream2:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: "ns1", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream2", + }, + DestinationPort: "myPort2", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(2234), + Ip: consulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: "ap1", + Namespace: "ns1", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream4", + }, + DestinationPort: "myPort4", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(4234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "unlabeled single upstream", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled single upstream with namespace", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: "foo", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled single upstream with namespace and partition", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo.bar:1234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: "bar", + Namespace: "foo", + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "unlabeled multiple upstreams", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2:2234" + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream2", + }, + DestinationPort: "myPort2", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(2234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled multiple upstreams with consul namespaces, partitions and datacenters", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo.baz:3234:dc2" + return pod1 + }, + configEntry: func() api.ConfigEntry { + ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") + pd := ce.(*api.ProxyConfigEntry) + pd.MeshGateway.Mode = "remote" + return pd + }, + expErr: "upstream currently does not support datacenters: myPort3.upstream3.foo.baz:3234:dc2", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: getDefaultConsulNamespace(""), + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "bar", + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream2", + // }, + // DestinationPort: "myPort2", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(2234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: "baz", + // Namespace: "foo", + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream3", + // }, + // DestinationPort: "myPort3", + // Datacenter: "dc2", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(3234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "unlabeled multiple upstreams with consul namespaces and datacenters", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo:3234:dc2" + return pod1 + }, + configEntry: func() api.ConfigEntry { + ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") + pd := ce.(*api.ProxyConfigEntry) + pd.MeshGateway.Mode = "remote" + return pd + }, + expErr: "upstream currently does not support datacenters: myPort3.upstream3.foo:3234:dc2", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: getDefaultConsulNamespace(""), + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "bar", + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream2", + // }, + // DestinationPort: "myPort2", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(2234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: upstreamReferenceType(), + // Tenancy: &pbresource.Tenancy{ + // Partition: getDefaultConsulPartition(""), + // Namespace: "foo", + // PeerName: getDefaultConsulPeer(""), + // }, + // Name: "upstream3", + // }, + // DestinationPort: "myPort3", + // Datacenter: "dc2", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(3234), + // Ip: consulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + pc := &Controller{ + Log: logrtest.New(t), + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ConsulTenancyConfig: common.ConsulTenancyConfig{ + EnableConsulNamespaces: tt.consulNamespacesEnabled, + EnableConsulPartitions: tt.consulPartitionsEnabled, + }, + } + + upstreams, err := pc.processUpstreams(*tt.pod()) + if tt.expErr != "" { + require.EqualError(t, err, tt.expErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, upstreams) + + if diff := cmp.Diff(tt.expected, upstreams, protocmp.Transform()); diff != "" { + t.Errorf("unexpected difference:\n%v", diff) + } + } + }) + } +} + +func TestUpstreamsDelete(t *testing.T) { + t.Parallel() + + const podName = "pod1" + + cases := []struct { + name string + pod func() *corev1.Pod + existingUpstreams *pbmesh.Upstreams + expErr string + configEntry func() api.ConfigEntry + consulUnavailable bool + }{ + { + name: "labeled annotated upstream with svc only", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" + return pod1 + }, + existingUpstreams: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + // Create test consulServer server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + pc := &Controller{ + Log: logrtest.New(t), + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ResourceClient: resourceClient, + } + + // Load in the upstream for us to delete and check that it's there + loadResource(t, + resourceClient, + getUpstreamsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tt.existingUpstreams, + nil) + expectedUpstreamMatches(t, resourceClient, tt.pod().Name, tt.existingUpstreams) + + // Delete the upstream + nn := types.NamespacedName{Name: tt.pod().Name} + err = pc.deleteUpstreams(context.Background(), nn) + + // Verify the upstream has been deleted or that an expected error has been returned + if tt.expErr != "" { + require.EqualError(t, err, tt.expErr) + } else { + require.NoError(t, err) + expectedUpstreamMatches(t, resourceClient, tt.pod().Name, nil) + } + }) + } +} // TODO // func TestDeleteACLTokens(t *testing.T) @@ -787,7 +2095,7 @@ func TestReconcileCreatePod(t *testing.T) { expectedWorkload *pbcatalog.Workload expectedHealthStatus *pbcatalog.HealthStatus expectedProxyConfiguration *pbmesh.ProxyConfiguration - //expectedUpstreams *pbmesh.Upstreams + expectedUpstreams *pbmesh.Upstreams tproxy bool overwriteProbes bool @@ -859,7 +2167,7 @@ func TestReconcileCreatePod(t *testing.T) { expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) - // expectedUpstreams + expectedUpstreamMatches(t, resourceClient, tc.podName, tc.expectedUpstreams) } testCases := []testCase{ @@ -878,7 +2186,7 @@ func TestReconcileCreatePod(t *testing.T) { overwriteProbes: true, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(), + expectedProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "pod in ignored namespace", @@ -921,8 +2229,25 @@ func TestReconcileCreatePod(t *testing.T) { return []runtime.Object{pod} }, }, + { + name: "pod with annotations", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, true) + addProbesAndOriginalPodAnnotation(pod) + pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" + return []runtime.Object{pod} + }, + tproxy: false, + telemetry: true, + metrics: true, + overwriteProbes: true, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + expectedUpstreams: createUpstreams(), + }, // TODO: make sure multi-error accumulates errors - // TODO: explicit upstreams } for _, tc := range testCases { @@ -954,12 +2279,12 @@ func TestReconcileUpdatePod(t *testing.T) { existingWorkload *pbcatalog.Workload existingHealthStatus *pbcatalog.HealthStatus existingProxyConfiguration *pbmesh.ProxyConfiguration - //existingUpstreams *pbmesh.Upstreams + existingUpstreams *pbmesh.Upstreams expectedWorkload *pbcatalog.Workload expectedHealthStatus *pbcatalog.HealthStatus expectedProxyConfiguration *pbmesh.ProxyConfiguration - //expectedUpstreams *pbmesh.Upstreams + expectedUpstreams *pbmesh.Upstreams tproxy bool overwriteProbes bool @@ -1026,9 +2351,11 @@ func TestReconcileUpdatePod(t *testing.T) { getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) - - // TODO: load the existing resources - // loadUpstreams + loadResource(t, + resourceClient, + getUpstreamsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingUpstreams, + nil) namespacedName := types.NamespacedName{ Namespace: namespace, @@ -1048,8 +2375,7 @@ func TestReconcileUpdatePod(t *testing.T) { expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) - // TODO: compare the following to expected values - // expectedUpstreams + expectedUpstreamMatches(t, resourceClient, tc.podName, tc.expectedUpstreams) } testCases := []testCase{ @@ -1141,7 +2467,46 @@ func TestReconcileUpdatePod(t *testing.T) { }, }, }, - // TODO: update explicit upstreams + { + name: "pod update explicit upstreams", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" + return []runtime.Object{pod} + }, + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingUpstreams: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{"foo"}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: "ap1", + Namespace: "ns1", + PeerName: getDefaultConsulPeer(""), + }, + Name: "mySVC3", + }, + DestinationPort: "myPort2", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: consulNodeAddress, + }, + }, + }, + }, + }, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedUpstreams: createUpstreams(), + }, } for _, tc := range testCases { @@ -1171,12 +2536,12 @@ func TestReconcileDeletePod(t *testing.T) { existingWorkload *pbcatalog.Workload existingHealthStatus *pbcatalog.HealthStatus existingProxyConfiguration *pbmesh.ProxyConfiguration - //existingUpstreams *pbmesh.Upstreams + existingUpstreams *pbmesh.Upstreams expectedWorkload *pbcatalog.Workload expectedHealthStatus *pbcatalog.HealthStatus expectedProxyConfiguration *pbmesh.ProxyConfiguration - //expectedUpstreams *pbmesh.Upstreams + expectedUpstreams *pbmesh.Upstreams aclsEnabled bool @@ -1235,8 +2600,12 @@ func TestReconcileDeletePod(t *testing.T) { getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) - // TODO: load the existing resources - // loadUpstreams + loadResource( + t, + resourceClient, + getUpstreamsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingUpstreams, + nil) namespacedName := types.NamespacedName{ Namespace: namespace, @@ -1256,8 +2625,7 @@ func TestReconcileDeletePod(t *testing.T) { expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) - // TODO: compare the following to expected values - // expectedUpstreams + expectedUpstreamMatches(t, resourceClient, tc.podName, tc.expectedUpstreams) } testCases := []testCase{ @@ -1266,7 +2634,15 @@ func TestReconcileDeletePod(t *testing.T) { podName: "foo", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(), + existingProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + }, + { + name: "annotated delete pod", + podName: "foo", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + existingUpstreams: createUpstreams(), }, // TODO: enable ACLs and make sure they are deleted } @@ -1408,13 +2784,13 @@ func createCriticalHealthStatus() *pbcatalog.HealthStatus { // createProxyConfiguration creates a proxyConfiguration that matches the pod from createPod, // assuming that metrics, telemetry, and overwrite probes are enabled separately. -func createProxyConfiguration() *pbmesh.ProxyConfiguration { +func createProxyConfiguration(mode pbmesh.ProxyMode) *pbmesh.ProxyConfiguration { return &pbmesh.ProxyConfiguration{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{"foo"}, }, DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, + Mode: mode, ExposeConfig: &pbmesh.ExposeConfig{ ExposePaths: []*pbmesh.ExposePath{ { @@ -1442,6 +2818,36 @@ func createProxyConfiguration() *pbmesh.ProxyConfiguration { } } +// createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. +func createUpstreams() *pbmesh.Upstreams { + return &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{"foo"}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: upstreamReferenceType(), + Tenancy: &pbresource.Tenancy{ + Partition: getDefaultConsulPartition(""), + Namespace: getDefaultConsulNamespace(""), + PeerName: getDefaultConsulPeer(""), + }, + Name: "mySVC", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(24601), + Ip: consulNodeAddress, + }, + }, + }, + }, + } +} + func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedWorkload *pbcatalog.Workload) { req := &pbresource.ReadRequest{Id: getWorkloadID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} @@ -1529,6 +2935,34 @@ func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceS require.True(t, proto.Equal(actualProxyConfiguration, expectedProxyConfiguration)) } +func expectedUpstreamMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedUpstreams *pbmesh.Upstreams) { + req := &pbresource.ReadRequest{Id: getUpstreamsID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} + res, err := client.Read(context.Background(), req) + + if expectedUpstreams == nil { + require.Error(t, err) + s, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.NotFound, s.Code()) + return + } + + require.NoError(t, err) + require.NotNil(t, res) + + require.Equal(t, name, res.GetResource().GetId().GetName()) + require.Equal(t, constants.DefaultConsulNS, res.GetResource().GetId().GetTenancy().GetNamespace()) + require.Equal(t, constants.DefaultConsulPartition, res.GetResource().GetId().GetTenancy().GetPartition()) + + require.NotNil(t, res.GetResource().GetData()) + + actualUpstreams := &pbmesh.Upstreams{} + err = res.GetResource().GetData().UnmarshalTo(actualUpstreams) + require.NoError(t, err) + + require.True(t, proto.Equal(actualUpstreams, expectedUpstreams)) +} + func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { if id == nil || !proto.ProtoReflect().IsValid() { return From 57317edd373718040d266683f912f54436276e92 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Thu, 21 Sep 2023 09:44:06 -0400 Subject: [PATCH 395/592] [NET-5682] Disable flaky Vault namespace test (#2983) Disable flaky Vault namespace test --- acceptance/tests/vault/vault_namespaces_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/acceptance/tests/vault/vault_namespaces_test.go b/acceptance/tests/vault/vault_namespaces_test.go index a6605acc46..939795f199 100644 --- a/acceptance/tests/vault/vault_namespaces_test.go +++ b/acceptance/tests/vault/vault_namespaces_test.go @@ -25,6 +25,8 @@ import ( // It then configures Consul to use vault as the backend and checks that it works // with the vault namespace. Namespace is added in this via global.secretsBackend.vault.vaultNamespace. func TestVault_VaultNamespace(t *testing.T) { + t.Skipf("TODO(flaky): NET-5682") + cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) ns := ctx.KubectlOptions(t).Namespace From 7b20a99d5d1a6a5c75ad404ef69c8ea37ffc72c9 Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:17:07 -0600 Subject: [PATCH 396/592] Bump default consul collector image (#2981) --- charts/consul/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index f083622387..c9b49b6231 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -3448,7 +3448,7 @@ telemetryCollector: # The name of the Docker image (including any tag) for the containers running # the consul-telemetry-collector # @type: string - image: "hashicorp/consul-telemetry-collector:0.0.1" + image: "hashicorp/consul-telemetry-collector:0.0.2" # The resource settings for consul-telemetry-collector pods. # @recurse: false From c1bcdf445114db68c8b5dfe790e70833bff9c416 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 21 Sep 2023 15:35:09 -0400 Subject: [PATCH 397/592] test(control-plane): add ENT tests for pod controller (#2974) * test(control-plane): add ENT tests for pod controller * Apply suggestions from code review Co-authored-by: Michael Zalimeni * Add Upstreams ENT tests --------- Co-authored-by: Michael Zalimeni --- .../pod/pod_controller_ent_test.go | 772 +++++++++++++++++- .../controllers/pod/pod_controller_test.go | 277 ++++--- 2 files changed, 910 insertions(+), 139 deletions(-) diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index 56fb362d9d..8f2c20974d 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -5,46 +5,780 @@ package pod -import "testing" +import ( + "context" + "testing" -// TODO(dans) -// Tests creating a Pod object in a non-default NS and Partition with namespaces set to mirroring + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + capi "github.com/hashicorp/consul/api" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +const ( + testPodName = "foo" + testPartition = "my-partition" +) + +type testCase struct { + name string + podName string // This needs to be aligned with the pod created in `k8sObjects` + podNamespace string // Defaults to metav1.NamespaceDefault if empty. + partition string + + k8sObjects func() []runtime.Object // testing node is injected separately + + // Pod Controller Settings + acls bool + tproxy bool + overwriteProbes bool + metrics bool + telemetry bool + + namespaceMirroring bool + namespaceDestination string + namespacePrefix string + + // Initial Consul state. + existingConsulNamespace string // This namespace will be populated before the test is executed. + existingWorkload *pbcatalog.Workload + existingHealthStatus *pbcatalog.HealthStatus + existingProxyConfiguration *pbmesh.ProxyConfiguration + existingUpstreams *pbmesh.Upstreams + + // Expected Consul state. + expectedConsulNamespace string // This namespace will be used to query Consul for the results + expectedWorkload *pbcatalog.Workload + expectedHealthStatus *pbcatalog.HealthStatus + expectedProxyConfiguration *pbmesh.ProxyConfiguration + expectedUpstreams *pbmesh.Upstreams + + // Reconcile loop outputs + expErr string + expRequeue bool // The response from the reconcile function +} + +// TestReconcileCreatePodWithMirrorNamespaces creates a Pod object in a non-default NS and Partition +// with namespaces set to mirroring func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { + t.Parallel() + + testCases := []testCase{ + { + name: "kitchen sink new pod, ns and partition", + podName: testPodName, + partition: constants.DefaultConsulPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, metav1.NamespaceDefault, true, true) + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceMirroring: true, + + expectedConsulNamespace: constants.DefaultConsulNS, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + }, + { + name: "kitchen sink new pod, non-default ns and partition", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, true) + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceMirroring: true, + + existingConsulNamespace: "bar", + + expectedConsulNamespace: "bar", + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + }, + { + name: "new pod with namespace prefix", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, true) + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, + + namespaceMirroring: true, + namespacePrefix: "foo-", + + existingConsulNamespace: "foo-bar", + + expectedConsulNamespace: "foo-bar", + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + }, + { + name: "namespace mirroring overrides destination namespace", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, true) + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, + + namespaceMirroring: true, + namespaceDestination: "supernova", + + existingConsulNamespace: "bar", + + expectedConsulNamespace: "bar", + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + }, + { + name: "new pod with explicit upstreams, ns and partition", + podName: testPodName, + partition: constants.DefaultConsulPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, metav1.NamespaceDefault, true, true) + addProbesAndOriginalPodAnnotation(pod) + pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" + return []runtime.Object{pod} + }, + tproxy: false, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceMirroring: true, - //TODO: Add test case to cover Consul namespace missing and check for backoff + expectedConsulNamespace: constants.DefaultConsulNS, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + expectedUpstreams: createUpstreams(), + }, + { + name: "namespace in Consul does not exist", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, true) + return []runtime.Object{pod} + }, + + namespaceMirroring: true, + + // The equivalent namespace in Consul does not exist, so requeue for backoff. + expRequeue: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runControllerTest(t, tc) + }) + } } -// TODO(dans) -// Tests updating a Pod object in a non-default NS and Partition with namespaces set to mirroring +// TestReconcileUpdatePodWithMirrorNamespaces updates a Pod object in a non-default NS and Partition +// with namespaces set to mirroring. func TestReconcileUpdatePodWithMirrorNamespaces(t *testing.T) { + t.Parallel() + + testCases := []testCase{ + { + name: "update pod health", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, false) // failing + return []runtime.Object{pod} + }, + + namespaceMirroring: true, + namespacePrefix: "foo-", + + existingConsulNamespace: "foo-bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + + expectedConsulNamespace: "foo-bar", + expectedWorkload: createWorkload(), + expectedHealthStatus: createCriticalHealthStatus(testPodName, "bar"), + }, + { + name: "duplicated pod event", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, - //TODO: Add test case to cover Consul namespace missing and check for backoff + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, true) + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, + + namespaceMirroring: true, + + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + existingConsulNamespace: "bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + + expectedConsulNamespace: "bar", + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runControllerTest(t, tc) + }) + } } -// TODO(dans) -// Tests deleting a Pod object in a non-default NS and Partition with namespaces set to mirroring +// TestReconcileDeletePodWithMirrorNamespaces deletes a Pod object in a non-default NS and Partition +// with namespaces set to mirroring. func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { + t.Parallel() + + testCases := []testCase{ + { + name: "delete kitchen sink pod", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceMirroring: true, + + existingConsulNamespace: "bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + + expectedConsulNamespace: "bar", + }, + { + name: "delete pod w/ explicit upstreams", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceMirroring: true, + + existingConsulNamespace: "bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + existingUpstreams: createUpstreams(), + + expectedConsulNamespace: "bar", + }, + { + name: "delete pod with namespace prefix", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + namespaceMirroring: true, + namespacePrefix: "foo-", + + existingConsulNamespace: "foo-bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + + expectedConsulNamespace: "foo-bar", + }, + { + name: "resources are already gone in Consul", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceMirroring: true, + + existingConsulNamespace: "bar", + + expectedConsulNamespace: "bar", + }, + { + name: "namespace is already missing in Consul", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + namespaceMirroring: true, + + expectedConsulNamespace: "bar", + }, + } - //TODO: Add test case to cover Consul namespace missing and check for backoff + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runControllerTest(t, tc) + }) + } } -// TODO(dans) -// Tests creating a Pod object in a non-default NS and Partition with namespaces set to a destination +// TestReconcileCreatePodWithDestinationNamespace creates a Pod object in a non-default NS and Partition +// with namespaces set to a destination. func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { + t.Parallel() - //TODO: Add test case to cover Consul namespace missing and check for backoff + testCases := []testCase{ + { + name: "kitchen sink new pod, ns and partition", + podName: testPodName, + partition: constants.DefaultConsulPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, metav1.NamespaceDefault, true, true) + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceDestination: constants.DefaultConsulNS, + + existingConsulNamespace: constants.DefaultConsulNS, + + expectedConsulNamespace: constants.DefaultConsulNS, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + }, + { + name: "new pod with explicit upstreams, ns and partition", + podName: testPodName, + partition: constants.DefaultConsulPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, metav1.NamespaceDefault, true, true) + addProbesAndOriginalPodAnnotation(pod) + pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" + return []runtime.Object{pod} + }, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceDestination: constants.DefaultConsulNS, + + existingConsulNamespace: constants.DefaultConsulNS, + + expectedConsulNamespace: constants.DefaultConsulNS, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + expectedUpstreams: createUpstreams(), + }, + { + name: "kitchen sink new pod, non-default ns and partition", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, true) + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceDestination: "a-penguin-walks-into-a-bar", + + existingConsulNamespace: "a-penguin-walks-into-a-bar", + + expectedConsulNamespace: "a-penguin-walks-into-a-bar", + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + }, + { + name: "namespace in Consul does not exist", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, true) + return []runtime.Object{pod} + }, + + namespaceDestination: "a-penguin-walks-into-a-bar", + + // The equivalent namespace in Consul does not exist, so requeue for backoff. + expRequeue: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runControllerTest(t, tc) + }) + } } -// TODO(dans) -// Tests updating a Pod object in a non-default NS and Partition with namespaces set to a destination +// TestReconcileUpdatePodWithDestinationNamespace updates a Pod object in a non-default NS and Partition +// with namespaces set to a destination. func TestReconcileUpdatePodWithDestinationNamespace(t *testing.T) { + t.Parallel() + + testCases := []testCase{ + { + name: "update pod health", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, false) // failing + return []runtime.Object{pod} + }, + + namespaceDestination: "a-penguin-walks-into-a-bar", + + existingConsulNamespace: "a-penguin-walks-into-a-bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + + expectedConsulNamespace: "a-penguin-walks-into-a-bar", + expectedWorkload: createWorkload(), + expectedHealthStatus: createCriticalHealthStatus(testPodName, "bar"), + }, + { + name: "duplicated pod event", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + k8sObjects: func() []runtime.Object { + pod := createPod(testPodName, "bar", true, true) + addProbesAndOriginalPodAnnotation(pod) + + return []runtime.Object{pod} + }, - //TODO: Add test case to cover Consul namespace missing and check for backoff + namespaceDestination: "a-penguin-walks-into-a-bar", + + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + existingConsulNamespace: "a-penguin-walks-into-a-bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + + expectedConsulNamespace: "a-penguin-walks-into-a-bar", + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runControllerTest(t, tc) + }) + } } -// TODO(dans) -// Tests deleting a Pod object in a non-default NS and Partition with namespaces set to a destination +// TestReconcileDeletePodWithDestinationNamespace deletes a Pod object in a non-default NS and Partition +// with namespaces set to a destination. func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { + t.Parallel() + + testCases := []testCase{ + { + name: "delete kitchen sink pod", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceDestination: "a-penguin-walks-into-a-bar", + + existingConsulNamespace: "a-penguin-walks-into-a-bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + + expectedConsulNamespace: "a-penguin-walks-into-a-bar", + }, + { + name: "delete pod with explicit upstreams", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceDestination: "a-penguin-walks-into-a-bar", + + existingConsulNamespace: "a-penguin-walks-into-a-bar", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + existingUpstreams: createUpstreams(), + + expectedConsulNamespace: "a-penguin-walks-into-a-bar", + }, + { + name: "resources are already gone in Consul", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + + namespaceDestination: "a-penguin-walks-into-a-bar", + + existingConsulNamespace: "a-penguin-walks-into-a-bar", + + expectedConsulNamespace: "a-penguin-walks-into-a-bar", + }, + { + name: "namespace is already missing in Consul", + podName: testPodName, + podNamespace: "bar", + partition: testPartition, + + namespaceDestination: "a-penguin-walks-into-a-bar", + + expectedConsulNamespace: "a-penguin-walks-into-a-bar", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runControllerTest(t, tc) + }) + } +} + +func runControllerTest(t *testing.T, tc testCase) { + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: metav1.NamespaceDefault, + }} + nsBar := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + }} + node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} + + k8sObjects := []runtime.Object{ + &ns, + &nsBar, + &node, + } + if tc.k8sObjects != nil { + k8sObjects = append(k8sObjects, tc.k8sObjects()...) + } + + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test consulServer server. + adminToken := "123e4567-e89b-12d3-a456-426614174000" + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + if tc.acls { + c.ACL.Enabled = tc.acls + c.ACL.Tokens.InitialManagement = adminToken + } + }) + + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Create the partition in Consul. + if tc.partition != "" { + testClient.Cfg.APIClientConfig.Partition = tc.partition + + partition := &capi.Partition{ + Name: tc.partition, + } + _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) + require.NoError(t, err) + } + + // Create the namespace in Consul if specified. + if tc.existingConsulNamespace != "" { + namespace := &capi.Namespace{ + Name: tc.existingConsulNamespace, + Partition: tc.partition, + } + + _, _, err := testClient.APIClient.Namespaces().Create(namespace, nil) + require.NoError(t, err) + } + + // Create the pod controller. + pc := &Controller{ + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ConsulTenancyConfig: common.ConsulTenancyConfig{ + EnableConsulNamespaces: true, + NSMirroringPrefix: tc.namespacePrefix, + EnableNSMirroring: tc.namespaceMirroring, + ConsulDestinationNamespace: tc.namespaceDestination, + EnableConsulPartitions: true, + ConsulPartition: tc.partition, + }, + TProxyOverwriteProbes: tc.overwriteProbes, + EnableTransparentProxy: tc.tproxy, + EnableTelemetryCollector: tc.telemetry, + } + if tc.metrics { + pc.MetricsConfig = metrics.Config{ + DefaultEnableMetrics: true, + DefaultPrometheusScrapePort: "1234", + } + } + if tc.acls { + pc.AuthMethod = test.AuthMethod + } + + podNamespace := tc.podNamespace + if podNamespace == "" { + podNamespace = metav1.NamespaceDefault + } + + workloadID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) + loadResource(t, resourceClient, workloadID, tc.existingWorkload, nil) + loadResource( + t, + resourceClient, + getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition), + tc.existingHealthStatus, + workloadID) + loadResource( + t, + resourceClient, + getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition), + tc.existingProxyConfiguration, + nil) + loadResource( + t, + resourceClient, + getUpstreamsID(tc.podName, tc.expectedConsulNamespace, tc.partition), + tc.existingUpstreams, + nil) + + namespacedName := types.NamespacedName{ + Namespace: podNamespace, + Name: tc.podName, + } + + resp, err := pc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: namespacedName, + }) + if tc.expErr != "" { + require.EqualError(t, err, tc.expErr) + } else { + require.NoError(t, err) + } + + require.Equal(t, tc.expRequeue, resp.Requeue) + + wID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) + expectedWorkloadMatches(t, resourceClient, wID, tc.expectedWorkload) + + hsID := getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition) + expectedHealthStatusMatches(t, resourceClient, hsID, tc.expectedHealthStatus) + + pcID := getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition) + expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) - //TODO: Add test case to cover Consul namespace missing and check for backoff + uID := getUpstreamsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedUpstreamMatches(t, resourceClient, uID, tc.expectedUpstreams) } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 3ea54e66b6..a749ea8522 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -6,7 +6,9 @@ package pod import ( "context" "encoding/json" + "fmt" "testing" + "time" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" @@ -155,12 +157,12 @@ func TestWorkloadWrite(t *testing.T) { testCases := []testCase{ { name: "multi-port single-container", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), expectedWorkload: createWorkload(), }, { name: "multi-port multi-container", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), podModifier: func(pod *corev1.Pod) { container := corev1.Container{ Name: "logger", @@ -201,7 +203,7 @@ func TestWorkloadWrite(t *testing.T) { }, { name: "pod with locality", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), podModifier: func(pod *corev1.Pod) { pod.Spec.NodeName = localityNodeName }, @@ -232,7 +234,7 @@ func TestWorkloadWrite(t *testing.T) { }, { name: "pod with unnamed ports", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), podModifier: func(pod *corev1.Pod) { pod.Spec.Containers[0].Ports[0].Name = "" pod.Spec.Containers[0].Ports[1].Name = "" @@ -260,7 +262,7 @@ func TestWorkloadWrite(t *testing.T) { }, { name: "pod with no ports", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), podModifier: func(pod *corev1.Pod) { pod.Spec.Containers[0].Ports = nil }, @@ -353,7 +355,7 @@ func TestWorkloadDelete(t *testing.T) { testCases := []testCase{ { name: "basic pod delete", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), existingWorkload: createWorkload(), }, } @@ -440,21 +442,21 @@ func TestHealthStatusWrite(t *testing.T) { testCases := []testCase{ { name: "ready pod", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), expectedHealthStatus: createPassingHealthStatus(), }, { name: "not ready pod", - pod: createPod("foo", "10.0.0.1", "foo", true, false), - expectedHealthStatus: createCriticalHealthStatus(), + pod: createPod("foo", "", true, false), + expectedHealthStatus: createCriticalHealthStatus("foo", "default"), }, { name: "pod with no condition", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), podModifier: func(pod *corev1.Pod) { pod.Status.Conditions = []corev1.PodCondition{} }, - expectedHealthStatus: createCriticalHealthStatus(), + expectedHealthStatus: createCriticalHealthStatus("foo", "default"), }, } @@ -562,12 +564,12 @@ func TestProxyConfigurationWrite(t *testing.T) { testCases := []testCase{ { name: "no tproxy, no telemetry, no metrics, no probe overwrite", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), expectedProxyConfiguration: nil, }, { name: "kitchen sink - globally enabled", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), podModifier: func(pod *corev1.Pod) { addProbesAndOriginalPodAnnotation(pod) }, @@ -609,7 +611,7 @@ func TestProxyConfigurationWrite(t *testing.T) { }, { name: "tproxy, metrics, and probe overwrite enabled on pod", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), podModifier: func(pod *corev1.Pod) { pod.Annotations[constants.KeyTransparentProxy] = "true" pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "true" @@ -651,7 +653,7 @@ func TestProxyConfigurationWrite(t *testing.T) { }, { name: "tproxy enabled on namespace", - pod: createPod("foo", "10.0.0.1", "foo", true, true), + pod: createPod("foo", "", true, true), podModifier: func(pod *corev1.Pod) { pod.Namespace = "tproxy-party" }, @@ -747,8 +749,8 @@ func TestProxyConfigurationDelete(t *testing.T) { testCases := []testCase{ { name: "proxy configuration delete", - pod: createPod("foo", "10.0.0.1", "foo", true, true), - existingProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + pod: createPod("foo", "", true, true), + existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, } @@ -777,7 +779,7 @@ func TestUpstreamsWrite(t *testing.T) { { name: "labeled annotated upstream with svc only", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" return pod1 }, @@ -813,7 +815,7 @@ func TestUpstreamsWrite(t *testing.T) { { name: "labeled annotated upstream with svc, ns, and peer", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234" return pod1 }, @@ -851,7 +853,7 @@ func TestUpstreamsWrite(t *testing.T) { { name: "labeled annotated upstream with svc, ns, and partition", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234" return pod1 }, @@ -887,7 +889,7 @@ func TestUpstreamsWrite(t *testing.T) { { name: "error labeled annotated upstream error: invalid partition/dc/peer", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.err:1234" return pod1 }, @@ -898,7 +900,7 @@ func TestUpstreamsWrite(t *testing.T) { { name: "unlabeled single upstream", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream:1234" return pod1 }, @@ -934,7 +936,7 @@ func TestUpstreamsWrite(t *testing.T) { { name: "unlabeled single upstream with namespace and partition", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo.bar:1234" return pod1 }, @@ -996,7 +998,8 @@ func TestUpstreamsWrite(t *testing.T) { require.EqualError(t, err, tt.expErr) } else { require.NoError(t, err) - expectedUpstreamMatches(t, resourceClient, tt.pod().Name, tt.expected) + uID := getUpstreamsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedUpstreamMatches(t, resourceClient, uID, tt.expected) } }) } @@ -1020,7 +1023,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "labeled annotated upstream with svc only", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" return pod1 }, @@ -1056,7 +1059,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "labeled annotated upstream with svc and dc", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.dc1.dc:1234" return pod1 }, @@ -1094,7 +1097,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "labeled annotated upstream with svc and peer", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.peer:1234" return pod1 }, @@ -1132,7 +1135,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "labeled annotated upstream with svc, ns, and peer", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234" return pod1 }, @@ -1170,7 +1173,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "labeled annotated upstream with svc, ns, and partition", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234" return pod1 }, @@ -1206,7 +1209,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "labeled annotated upstream with svc, ns, and dc", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234" return pod1 }, @@ -1244,7 +1247,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "labeled multiple annotated upstreams", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.port.upstream2.svc:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234" return pod1 }, @@ -1318,7 +1321,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "labeled multiple annotated upstreams with dcs and peers", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234, myPort2.port.upstream2.svc:2234, myPort3.port.upstream3.svc.ns1.ns:3234, myPort4.port.upstream4.svc.ns1.ns.peer1.peer:4234" return pod1 }, @@ -1413,7 +1416,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: invalid partition/dc/peer", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.err:1234" return pod1 }, @@ -1424,7 +1427,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream with svc and peer, needs ns before peer if namespaces enabled", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.peer:1234" return pod1 }, @@ -1435,7 +1438,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: invalid namespace", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.err:1234" return pod1 }, @@ -1446,7 +1449,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: invalid number of pieces in the address", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.err:1234" return pod1 }, @@ -1457,7 +1460,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: invalid peer", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.err:1234" return pod1 }, @@ -1468,7 +1471,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: invalid number of pieces in the address without namespaces and partitions", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.err:1234" return pod1 }, @@ -1479,7 +1482,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: both peer and partition provided", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234" return pod1 }, @@ -1490,7 +1493,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: both peer and dc provided", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234" return pod1 }, @@ -1501,7 +1504,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: both dc and partition provided", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234" return pod1 }, @@ -1512,7 +1515,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition enabled", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234" return pod1 }, @@ -1523,7 +1526,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition disabled", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "upstream1.svc.myPort.port:1234" return pod1 }, @@ -1534,7 +1537,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: incorrect key name namespace partition enabled", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234" return pod1 }, @@ -1545,7 +1548,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "error labeled annotated upstream error: incorrect key name namespace partition disabled", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.portage.upstream1.svc:1234" return pod1 }, @@ -1556,7 +1559,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "unlabeled and labeled multiple annotated upstreams", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.upstream2:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234" return pod1 }, @@ -1630,7 +1633,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "unlabeled single upstream", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream:1234" return pod1 }, @@ -1666,7 +1669,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "unlabeled single upstream with namespace", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo:1234" return pod1 }, @@ -1702,7 +1705,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "unlabeled single upstream with namespace and partition", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo.bar:1234" return pod1 }, @@ -1738,7 +1741,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "unlabeled multiple upstreams", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2:2234" return pod1 }, @@ -1793,7 +1796,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "unlabeled multiple upstreams with consul namespaces, partitions and datacenters", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo.baz:3234:dc2" return pod1 }, @@ -1875,7 +1878,7 @@ func TestProcessUpstreams(t *testing.T) { { name: "unlabeled multiple upstreams with consul namespaces and datacenters", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo:3234:dc2" return pod1 }, @@ -1999,7 +2002,7 @@ func TestUpstreamsDelete(t *testing.T) { { name: "labeled annotated upstream with svc only", pod: func() *corev1.Pod { - pod1 := createPod(podName, "1.2.3.4", "foo", true, true) + pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" return pod1 }, @@ -2055,7 +2058,8 @@ func TestUpstreamsDelete(t *testing.T) { getUpstreamsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.existingUpstreams, nil) - expectedUpstreamMatches(t, resourceClient, tt.pod().Name, tt.existingUpstreams) + uID := getUpstreamsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedUpstreamMatches(t, resourceClient, uID, tt.existingUpstreams) // Delete the upstream nn := types.NamespacedName{Name: tt.pod().Name} @@ -2066,7 +2070,8 @@ func TestUpstreamsDelete(t *testing.T) { require.EqualError(t, err, tt.expErr) } else { require.NoError(t, err) - expectedUpstreamMatches(t, resourceClient, tt.pod().Name, nil) + uID := getUpstreamsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedUpstreamMatches(t, resourceClient, uID, nil) } }) } @@ -2164,10 +2169,17 @@ func TestReconcileCreatePod(t *testing.T) { } require.False(t, resp.Requeue) - expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) - expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) - expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) - expectedUpstreamMatches(t, resourceClient, tc.podName, tc.expectedUpstreams) + wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedWorkloadMatches(t, resourceClient, wID, tc.expectedWorkload) + + hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedHealthStatusMatches(t, resourceClient, hsID, tc.expectedHealthStatus) + + pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) + + uID := getUpstreamsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedUpstreamMatches(t, resourceClient, uID, tc.expectedUpstreams) } testCases := []testCase{ @@ -2175,7 +2187,7 @@ func TestReconcileCreatePod(t *testing.T) { name: "vanilla new pod", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod := createPod("foo", "", true, true) addProbesAndOriginalPodAnnotation(pod) return []runtime.Object{pod} @@ -2186,14 +2198,14 @@ func TestReconcileCreatePod(t *testing.T) { overwriteProbes: true, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + expectedProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "pod in ignored namespace", podName: "foo", namespace: metav1.NamespaceSystem, k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod := createPod("foo", "", true, true) pod.ObjectMeta.Namespace = metav1.NamespaceSystem return []runtime.Object{pod} }, @@ -2202,30 +2214,30 @@ func TestReconcileCreatePod(t *testing.T) { name: "unhealthy new pod", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, false) + pod := createPod("foo", "", true, false) return []runtime.Object{pod} }, expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus(), + expectedHealthStatus: createCriticalHealthStatus("foo", "default"), }, { name: "return error - pod has no original pod annotation", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, false) + pod := createPod("foo", "", true, false) return []runtime.Object{pod} }, tproxy: true, overwriteProbes: true, expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus(), + expectedHealthStatus: createCriticalHealthStatus("foo", "default"), expErr: "1 error occurred:\n\t* failed to get expose config: failed to get original pod spec: unexpected end of JSON input\n\n", }, { name: "pod has not been injected", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", false, true) + pod := createPod("foo", "", false, true) return []runtime.Object{pod} }, }, @@ -2233,7 +2245,7 @@ func TestReconcileCreatePod(t *testing.T) { name: "pod with annotations", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod := createPod("foo", "", true, true) addProbesAndOriginalPodAnnotation(pod) pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" return []runtime.Object{pod} @@ -2244,7 +2256,7 @@ func TestReconcileCreatePod(t *testing.T) { overwriteProbes: true, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + expectedProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), expectedUpstreams: createUpstreams(), }, // TODO: make sure multi-error accumulates errors @@ -2372,10 +2384,17 @@ func TestReconcileUpdatePod(t *testing.T) { } require.False(t, resp.Requeue) - expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) - expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) - expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) - expectedUpstreamMatches(t, resourceClient, tc.podName, tc.expectedUpstreams) + wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedWorkloadMatches(t, resourceClient, wID, tc.expectedWorkload) + + hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedHealthStatusMatches(t, resourceClient, hsID, tc.expectedHealthStatus) + + pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) + + uID := getUpstreamsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedUpstreamMatches(t, resourceClient, uID, tc.expectedUpstreams) } testCases := []testCase{ @@ -2383,7 +2402,7 @@ func TestReconcileUpdatePod(t *testing.T) { name: "pod update ports", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod := createPod("foo", "", true, true) return []runtime.Object{pod} }, existingHealthStatus: createPassingHealthStatus(), @@ -2411,19 +2430,19 @@ func TestReconcileUpdatePod(t *testing.T) { name: "pod healthy to unhealthy", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, false) + pod := createPod("foo", "", true, false) return []runtime.Object{pod} }, existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus(), + expectedHealthStatus: createCriticalHealthStatus("foo", "default"), }, { name: "add metrics, tproxy and probe overwrite to pod", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod := createPod("foo", "", true, true) pod.Annotations[constants.KeyTransparentProxy] = "true" pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "true" pod.Annotations[constants.AnnotationEnableMetrics] = "true" @@ -2471,7 +2490,7 @@ func TestReconcileUpdatePod(t *testing.T) { name: "pod update explicit upstreams", podName: "foo", k8sObjects: func() []runtime.Object { - pod := createPod("foo", "10.0.0.1", "foo", true, true) + pod := createPod("foo", "", true, true) pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" return []runtime.Object{pod} }, @@ -2622,10 +2641,17 @@ func TestReconcileDeletePod(t *testing.T) { } require.False(t, resp.Requeue) - expectedWorkloadMatches(t, resourceClient, tc.podName, tc.expectedWorkload) - expectedHealthStatusMatches(t, resourceClient, tc.podName, tc.expectedHealthStatus) - expectedProxyConfigurationMatches(t, resourceClient, tc.podName, tc.expectedProxyConfiguration) - expectedUpstreamMatches(t, resourceClient, tc.podName, tc.expectedUpstreams) + wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedWorkloadMatches(t, resourceClient, wID, tc.expectedWorkload) + + hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedHealthStatusMatches(t, resourceClient, hsID, tc.expectedHealthStatus) + + pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) + + uID := getUpstreamsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedUpstreamMatches(t, resourceClient, uID, tc.expectedUpstreams) } testCases := []testCase{ @@ -2634,14 +2660,14 @@ func TestReconcileDeletePod(t *testing.T) { podName: "foo", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "annotated delete pod", podName: "foo", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), existingUpstreams: createUpstreams(), }, // TODO: enable ACLs and make sure they are deleted @@ -2654,19 +2680,24 @@ func TestReconcileDeletePod(t *testing.T) { } } -// createPod creates a multi-port pod as a base for tests. -func createPod(name, ip string, identity string, inject bool, ready bool) *corev1.Pod { +// createPod creates a multi-port pod as a base for tests. If `namespace` is empty, +// the default Kube namespace will be used. +func createPod(name, namespace string, inject, ready bool) *corev1.Pod { + if namespace == "" { + namespace = metav1.NamespaceDefault + } + pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: metav1.NamespaceDefault, + Namespace: namespace, Labels: map[string]string{}, Annotations: map[string]string{ constants.AnnotationConsulK8sVersion: "1.3.0", }, }, Status: corev1.PodStatus{ - PodIP: ip, + PodIP: "10.0.0.1", HostIP: consulNodeAddress, }, Spec: corev1.PodSpec{ @@ -2712,7 +2743,7 @@ func createPod(name, ip string, identity string, inject bool, ready bool) *corev }, }, NodeName: nodeName, - ServiceAccountName: identity, + ServiceAccountName: name, }, } if ready { @@ -2773,21 +2804,21 @@ func createPassingHealthStatus() *pbcatalog.HealthStatus { } // createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. -func createCriticalHealthStatus() *pbcatalog.HealthStatus { +func createCriticalHealthStatus(name string, namespace string) *pbcatalog.HealthStatus { return &pbcatalog.HealthStatus{ Type: constants.ConsulKubernetesCheckType, Status: pbcatalog.Health_HEALTH_CRITICAL, - Output: "Pod \"default/foo\" is not ready", + Output: fmt.Sprintf("Pod \"%s/%s\" is not ready", namespace, name), Description: constants.ConsulKubernetesCheckName, } } // createProxyConfiguration creates a proxyConfiguration that matches the pod from createPod, // assuming that metrics, telemetry, and overwrite probes are enabled separately. -func createProxyConfiguration(mode pbmesh.ProxyMode) *pbmesh.ProxyConfiguration { +func createProxyConfiguration(podName string, mode pbmesh.ProxyMode) *pbmesh.ProxyConfiguration { return &pbmesh.ProxyConfiguration{ Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, + Names: []string{podName}, }, DynamicConfig: &pbmesh.DynamicConfig{ Mode: mode, @@ -2848,8 +2879,8 @@ func createUpstreams() *pbmesh.Upstreams { } } -func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedWorkload *pbcatalog.Workload) { - req := &pbresource.ReadRequest{Id: getWorkloadID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} +func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedWorkload *pbcatalog.Workload) { + req := &pbresource.ReadRequest{Id: id} res, err := client.Read(context.Background(), req) @@ -2864,9 +2895,7 @@ func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClie require.NoError(t, err) require.NotNil(t, res) - require.Equal(t, name, res.GetResource().GetId().GetName()) - require.Equal(t, constants.DefaultConsulNS, res.GetResource().GetId().GetTenancy().GetNamespace()) - require.Equal(t, constants.DefaultConsulPartition, res.GetResource().GetId().GetTenancy().GetPartition()) + requireEqualResourceID(t, id, res.GetResource().GetId()) require.NotNil(t, res.GetResource().GetData()) @@ -2874,28 +2903,29 @@ func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClie err = res.GetResource().GetData().UnmarshalTo(actualWorkload) require.NoError(t, err) - require.True(t, proto.Equal(actualWorkload, expectedWorkload)) + diff := cmp.Diff(expectedWorkload, actualWorkload, test.CmpProtoIgnoreOrder()...) + require.Equal(t, "", diff, "Workloads do not match") } -func expectedHealthStatusMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedHealthStatus *pbcatalog.HealthStatus) { - req := &pbresource.ReadRequest{Id: getHealthStatusID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} +func expectedHealthStatusMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedHealthStatus *pbcatalog.HealthStatus) { + req := &pbresource.ReadRequest{Id: id} res, err := client.Read(context.Background(), req) if expectedHealthStatus == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) + // Because HealthStatus is asynchronously garbage-collected, we can retry to make sure it gets cleaned up. + require.Eventually(t, func() bool { + _, err := client.Read(context.Background(), req) + s, ok := status.FromError(err) + return ok && codes.NotFound == s.Code() + }, 3*time.Second, 500*time.Millisecond) return } require.NoError(t, err) require.NotNil(t, res) - require.Equal(t, name, res.GetResource().GetId().GetName()) - require.Equal(t, constants.DefaultConsulNS, res.GetResource().GetId().GetTenancy().GetNamespace()) - require.Equal(t, constants.DefaultConsulPartition, res.GetResource().GetId().GetTenancy().GetPartition()) + requireEqualResourceID(t, id, res.GetResource().GetId()) require.NotNil(t, res.GetResource().GetData()) @@ -2903,11 +2933,12 @@ func expectedHealthStatusMatches(t *testing.T, client pbresource.ResourceService err = res.GetResource().GetData().UnmarshalTo(actualHealthStatus) require.NoError(t, err) - require.True(t, proto.Equal(actualHealthStatus, expectedHealthStatus)) + diff := cmp.Diff(expectedHealthStatus, actualHealthStatus, test.CmpProtoIgnoreOrder()...) + require.Equal(t, "", diff, "HealthStatuses do not match") } -func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedProxyConfiguration *pbmesh.ProxyConfiguration) { - req := &pbresource.ReadRequest{Id: getProxyConfigurationID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} +func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedProxyConfiguration *pbmesh.ProxyConfiguration) { + req := &pbresource.ReadRequest{Id: id} res, err := client.Read(context.Background(), req) @@ -2922,9 +2953,7 @@ func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceS require.NoError(t, err) require.NotNil(t, res) - require.Equal(t, name, res.GetResource().GetId().GetName()) - require.Equal(t, constants.DefaultConsulNS, res.GetResource().GetId().GetTenancy().GetNamespace()) - require.Equal(t, constants.DefaultConsulPartition, res.GetResource().GetId().GetTenancy().GetPartition()) + requireEqualResourceID(t, id, res.GetResource().GetId()) require.NotNil(t, res.GetResource().GetData()) @@ -2932,11 +2961,12 @@ func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceS err = res.GetResource().GetData().UnmarshalTo(actualProxyConfiguration) require.NoError(t, err) - require.True(t, proto.Equal(actualProxyConfiguration, expectedProxyConfiguration)) + diff := cmp.Diff(expectedProxyConfiguration, actualProxyConfiguration, test.CmpProtoIgnoreOrder()...) + require.Equal(t, "", diff, "ProxyConfigurations do not match") } -func expectedUpstreamMatches(t *testing.T, client pbresource.ResourceServiceClient, name string, expectedUpstreams *pbmesh.Upstreams) { - req := &pbresource.ReadRequest{Id: getUpstreamsID(name, metav1.NamespaceDefault, constants.DefaultConsulPartition)} +func expectedUpstreamMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedUpstreams *pbmesh.Upstreams) { + req := &pbresource.ReadRequest{Id: id} res, err := client.Read(context.Background(), req) if expectedUpstreams == nil { @@ -2950,9 +2980,7 @@ func expectedUpstreamMatches(t *testing.T, client pbresource.ResourceServiceClie require.NoError(t, err) require.NotNil(t, res) - require.Equal(t, name, res.GetResource().GetId().GetName()) - require.Equal(t, constants.DefaultConsulNS, res.GetResource().GetId().GetTenancy().GetNamespace()) - require.Equal(t, constants.DefaultConsulPartition, res.GetResource().GetId().GetTenancy().GetPartition()) + requireEqualResourceID(t, id, res.GetResource().GetId()) require.NotNil(t, res.GetResource().GetData()) @@ -2992,3 +3020,12 @@ func addProbesAndOriginalPodAnnotation(pod *corev1.Pod) { pod.Spec.Containers[0].LivenessProbe.HTTPGet.Port = intstr.FromInt(20400) pod.Spec.Containers[0].StartupProbe.HTTPGet.Port = intstr.FromInt(20500) } + +func requireEqualResourceID(t *testing.T, expected, actual *pbresource.ID) { + opts := []cmp.Option{ + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + } + opts = append(opts, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(expected, actual, opts...) + require.Equal(t, "", diff, "resource IDs do not match") +} From f700c2dba9173bf5bcc91e9b75a77213a2d5778e Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Thu, 21 Sep 2023 15:53:23 -0400 Subject: [PATCH 398/592] [NET-5674] v2: Conditional target port when numeric in k8s (#2978) v2: Conditional target port when numeric in k8s When choosing a Consul service TargetPort (name) in the v2 Endpoints Controller, attempt to find the best match among endpoint container ports when the K8s service target port value is a number. This bridges an existing gap between Consul (which always expects a Workload port to be named), and K8s (where container ports need not be named, and names are ignored when the K8s service targets by number). This change will be mostly reverted in a future release once Consul's v2 data model allows for target ports to be a name or number in alignment w/ K8s behavior. --- .../bases/v2-multiport-app/deployment.yaml | 2 + .../bases/v2-multiport-app/service.yaml | 4 +- control-plane/connect-inject/common/common.go | 3 + .../connect-inject/common/common_test.go | 11 + .../endpointsv2/endpoints_controller.go | 247 +++++++-- .../endpointsv2/endpoints_controller_test.go | 499 ++++++++++++++++-- 6 files changed, 686 insertions(+), 80 deletions(-) diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml index 0aefa14d50..77c7de3bc9 100644 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml @@ -56,6 +56,8 @@ spec: - -listen=:9090 ports: - containerPort: 9090 + # This name is meant to be used alongside the _numeric_ K8s service target port + # to verify that we can still route traffic to the named port when there's a mismatch. name: admin # TODO: (v2/nitya) add these probes back when expose paths and L7 are supported. # livenessProbe: diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml index 940a9a3a17..fe47663c3d 100644 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml @@ -11,8 +11,8 @@ spec: ports: - name: web port: 8080 - # TODO: (v2/nitya) ensure this works with numeric target ports also once support for that is merged. targetPort: web - name: admin port: 9090 - targetPort: admin + # Test with a mix of named and numeric target ports. + targetPort: 9090 diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 5ad336f327..402f17bbb3 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -184,6 +184,9 @@ func PortValueFromIntOrString(pod corev1.Pod, port intstr.IntOrString) (uint32, // HasBeenMeshInjected checks the value of the status annotation and returns true if the Pod has been injected. // Does not apply to V1 pods, which use a different key (`constants.KeyInjectStatus`). func HasBeenMeshInjected(pod corev1.Pod) bool { + if pod.Annotations == nil { + return false + } if anno, ok := pod.Annotations[constants.KeyMeshInjectStatus]; ok && anno == constants.Injected { return true } diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index 3bf19c72d4..86618088f0 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -457,6 +457,17 @@ func TestHasBeenMeshInjected(t *testing.T) { }, expected: false, }, + { + name: "Pod with nil annotations", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{}, + }, + }, + expected: false, + }, } for _, tt := range cases { diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index 9b7cfa8971..b652aa3b6b 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -15,6 +15,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -60,7 +61,6 @@ func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { // Reconcile reads the state of an Endpoints object for a Kubernetes Service and reconciles Consul services which // correspond to the Kubernetes Service. These events are driven by changes to the Pods backing the Kube service. func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var errs error var endpoints corev1.Endpoints var service corev1.Service @@ -96,38 +96,74 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } r.Log.Info("retrieved Service", "name", req.Name, "ns", req.Namespace) - workloadSelector, err := r.getWorkloadSelectorFromEndpoints(ctx, &ClientPodFetcher{client: r.Client}, &endpoints) + consulSvc, err := r.getConsulService(ctx, &ClientPodFetcher{client: r.Client}, service, endpoints) if err != nil { - errs = multierror.Append(errs, err) + r.Log.Error(err, "failed to build Consul service resource", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err } - // If we have at least one mesh-injected pod targeted by the service, register it in Consul. + // If we don't have at least one mesh-injected pod targeted by the service, do not register the service. //TODO: Register service with mesh port added if global flag for inject is true, // even if Endpoints are empty or have no mesh pod, iff. the service has a selector. // This should ensure that we don't target kube or consul (system) services. - if workloadSelector != nil { - //TODO: Maybe check service-enable label here on service/deployments/other pod owners - if err = r.registerService(ctx, resourceClient, service, workloadSelector); err != nil { - // We could be racing with the namespace controller. - // Requeue (which includes backoff) to try again. - if common.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "service", service.GetName(), "ns", req.Namespace, - "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) + if consulSvc.Workloads == nil { + return ctrl.Result{}, nil + } + + // Register the service in Consul. + //TODO: Maybe check service-enable label here on service/deployments/other pod owners + if err = r.registerService(ctx, resourceClient, service, consulSvc); err != nil { + // We could be racing with the namespace controller. + // Requeue (which includes backoff) to try again. + if common.ConsulNamespaceIsNotFound(err) { + r.Log.Info("Consul namespace not found; re-queueing request", + "service", service.GetName(), "ns", req.Namespace, + "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) + return ctrl.Result{Requeue: true}, nil } + return ctrl.Result{}, err } - return ctrl.Result{}, errs + + return ctrl.Result{}, nil } -// getWorkloadSelectorFromEndpoints calculates a Consul service WorkloadSelector from Endpoints based on pod names and -// owners. -func (r *Controller) getWorkloadSelectorFromEndpoints(ctx context.Context, pf PodFetcher, endpoints *corev1.Endpoints) (*pbcatalog.WorkloadSelector, error) { - podPrefixes := make(map[string]any) - podExactNames := make(map[string]any) +func (r *Controller) getConsulService(ctx context.Context, pf PodFetcher, service corev1.Service, endpoints corev1.Endpoints) (*pbcatalog.Service, error) { + prefixedPods, exactNamePods, err := r.getWorkloadDataFromEndpoints(ctx, pf, endpoints) + if err != nil { + return nil, err + } + + // Create Consul Service resource to be registered. + return &pbcatalog.Service{ + Workloads: getWorkloadSelector(prefixedPods, exactNamePods), + Ports: getServicePorts(service, prefixedPods, exactNamePods), + VirtualIps: r.getServiceVIPs(service), + }, nil +} + +type podSetData struct { + podCount int + samplePod *corev1.Pod +} + +// selectorPodData represents data for each set of pods represented by a WorkloadSelector value. +// The data may be for several pods (prefix) or a single pod (exact name). +// This is used for choosing the ideal Consul service TargetPort value when the K8s service target port is numeric. +type selectorPodData map[string]*podSetData + +// getWorkloadDataFromEndpoints accumulates data to supply the Consul service WorkloadSelector and TargetPort from +// Endpoints based on pod names and owners. +func (r *Controller) getWorkloadDataFromEndpoints(ctx context.Context, pf PodFetcher, endpoints corev1.Endpoints) (selectorPodData, selectorPodData, error) { var errs error + + // Determine the workload selector by fetching as many pods as needed to accumulate prefixes + // and exact pod name matches. + // + // If the K8s service target port is numeric, we also use this information to determine the + // appropriate Consul target port value. + prefixedPods := make(selectorPodData) + exactNamePods := make(selectorPodData) + ignoredPodPrefixes := make(map[string]any) for address := range allAddresses(endpoints.Subsets) { if address.TargetRef != nil && address.TargetRef.Kind == "Pod" { podName := types.NamespacedName{Name: address.TargetRef.Name, Namespace: endpoints.Namespace} @@ -137,7 +173,14 @@ func (r *Controller) getWorkloadSelectorFromEndpoints(ctx context.Context, pf Po // If not, fetch the owner. If the owner has a unique prefix, add it to known prefixes. // If not, add the pod name to exact name matches. maybePodOwnerPrefix := getOwnerPrefixFromPodName(podName.Name) - if _, ok := podPrefixes[maybePodOwnerPrefix]; !ok { + + // If prefix is ignored, skip pod. + if _, ok := ignoredPodPrefixes[maybePodOwnerPrefix]; ok { + continue + } + + if existingPodData, ok := prefixedPods[maybePodOwnerPrefix]; !ok { + // Fetch pod info from K8s. pod, err := pf.GetPod(ctx, podName) if err != nil { r.Log.Error(err, "failed to get pod", "name", podName.Name, "ns", endpoints.Namespace) @@ -145,22 +188,41 @@ func (r *Controller) getWorkloadSelectorFromEndpoints(ctx context.Context, pf Po continue } - // If the pod hasn't been mesh-injected, skip it, as it won't be available as a workload. - if !common.HasBeenMeshInjected(*pod) { - continue + // Store data corresponding to the new selector value, which may be an actual set or exact pod. + podData := podSetData{ + podCount: 1, + samplePod: pod, } - // Add to workload selector values. + // Add pod to workload selector values as appropriate. // Pods can appear more than once in Endpoints subsets, so we use a set for exact names as well. if prefix := getOwnerPrefixFromPod(pod); prefix != "" { - podPrefixes[prefix] = true + if common.HasBeenMeshInjected(*pod) { + // Add to the list of pods represented by this prefix. This list is used by + // `getEffectiveTargetPort` to determine the most-used target container port name if the + // k8s service target port is numeric. + prefixedPods[prefix] = &podData + } else { + // If the pod hasn't been mesh-injected, ignore it, as it won't be available as a workload. + // Remember its prefix to avoid fetching its siblings needlessly. + ignoredPodPrefixes[prefix] = true + } } else { - podExactNames[podName.Name] = true + if common.HasBeenMeshInjected(*pod) { + exactNamePods[podName.Name] = &podData + } + // If the pod hasn't been mesh-injected, ignore it, as it won't be available as a workload. + // No need to remember ignored exact pod names since we don't expect to see them twice. } + } else { + // We've seen this prefix before. + // Keep track of how many times so that we can choose a container port name if needed later. + existingPodData.podCount += 1 } } } - return getWorkloadSelector(podPrefixes, podExactNames), errs + + return prefixedPods, exactNamePods, errs } // allAddresses combines all Endpoints subset addresses to a single set. Service registration by this controller @@ -202,18 +264,13 @@ func getOwnerPrefixFromPod(pod *corev1.Pod) string { } // registerService creates a Consul service registration from the provided Kuberetes service and endpoint information. -func (r *Controller) registerService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, service corev1.Service, selector *pbcatalog.WorkloadSelector) error { - consulSvc := &pbcatalog.Service{ - Workloads: selector, - Ports: getServicePorts(service), - VirtualIps: r.getServiceVIPs(service), - } +func (r *Controller) registerService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, k8sService corev1.Service, consulSvc *pbcatalog.Service) error { consulSvcResource := r.getServiceResource( consulSvc, - service.Name, // Consul and Kubernetes service name will always match - r.getConsulNamespace(service.Namespace), + k8sService.Name, // Consul and Kubernetes service name will always match + r.getConsulNamespace(k8sService.Namespace), r.getConsulPartition(), - getServiceMeta(service), + getServiceMeta(k8sService), ) r.Log.Info("registering service with Consul", getLogFieldsForResource(consulSvcResource.Id)...) @@ -253,7 +310,7 @@ func getServiceID(name, namespace, partition string) *pbresource.ID { } // getServicePorts converts Kubernetes Service ports data into Consul service ports. -func getServicePorts(service corev1.Service) []*pbcatalog.ServicePort { +func getServicePorts(service corev1.Service, prefixedPods selectorPodData, exactNamePods selectorPodData) []*pbcatalog.ServicePort { ports := make([]*pbcatalog.ServicePort, 0, len(service.Spec.Ports)+1) for _, p := range service.Spec.Ports { @@ -266,10 +323,8 @@ func getServicePorts(service corev1.Service) []*pbcatalog.ServicePort { if p.Protocol == corev1.ProtocolTCP { ports = append(ports, &pbcatalog.ServicePort{ VirtualPort: uint32(p.Port), - //TODO: If the value is a number, infer the correct name value based on - // the most prevalent endpoint subset for the port (best-effot, inspect a pod). - TargetPort: p.TargetPort.String(), - Protocol: common.GetPortProtocol(p.AppProtocol), + TargetPort: getEffectiveTargetPort(p.TargetPort, prefixedPods, exactNamePods), + Protocol: common.GetPortProtocol(p.AppProtocol), }) } } @@ -285,6 +340,102 @@ func getServicePorts(service corev1.Service) []*pbcatalog.ServicePort { return ports } +func getEffectiveTargetPort(targetPort intstr.IntOrString, prefixedPods selectorPodData, exactNamePods selectorPodData) string { + // The Kubernetes service is targeting a port name; use it directly. + // The expected behavior of Kubernetes is that all included Endpoints conform and have a matching named port. + // This is the simplest path and preferred over services targeting by port number. + if targetPort.Type == intstr.String { + return targetPort.String() + } + + // The Kubernetes service is targeting a numeric port. This is more complicated for mapping to Consul: + // - Endpoints will contain _all_ selected pods, not just those with a matching declared port number. + // - Consul Workload ports always have a name, so we must determine the best name to match on. + // - There may be more than one option among the pods with named ports, including no name at all. + // + // Our best-effort approach is to find the most prevalent port name among selected pods that _do_ declare the target + // port explicitly in container ports. We'll assume that for each set of pods, the first pod is "representative" - + // i.e. we expect a ReplicaSet to be homogenous. In the vast majority of cases, this means we'll be looking for the + // largest selected ReplicaSet and using the first pod from it. + // + // The goal is to make this determination without fetching all pods belonging to the service, as that would be a + // very expensive operation to repeat every time endpoints change, and we don't expect the target port to change + // often if ever across pod/deployment lifecycles. + // + //TODO in GA, we intend to change port selection to allow for Consul TargetPort to be numeric. If we retain the + // port selection model used here beyond GA, we should consider updating it to also consider pod health, s.t. when + // the selected port name changes between deployments of a ReplicaSet, we route traffic to ports belonging to the + // set most able to serve traffic, rather than simply the largest one. + targetPortInt := int32(targetPort.IntValue()) + var mostPrevalentContainerPort *corev1.ContainerPort + maxCount := 0 + effectiveNameForPort := func(port *corev1.ContainerPort) string { + if port.Name != "" { + return port.Name + } + return targetPort.String() + } + for _, podData := range prefixedPods { + containerPort := getTargetContainerPort(targetPortInt, podData.samplePod) + + // Ignore pods without a declared port matching the service targetPort. + if containerPort == nil { + continue + } + + // If this is the most prevalent container port by pod set size, update result. + if maxCount < podData.podCount { + mostPrevalentContainerPort = containerPort + maxCount = podData.podCount + } + } + + if mostPrevalentContainerPort != nil { + return effectiveNameForPort(mostPrevalentContainerPort) + } + + // If no pod sets have the expected target port, fall back to the most common name among exact-name pods. + // An assumption here is that exact name pods mixed with pod sets will be rare, and sets should be preferred. + if len(exactNamePods) > 0 { + nameCount := make(map[string]int) + for _, podData := range exactNamePods { + if containerPort := getTargetContainerPort(targetPortInt, podData.samplePod); containerPort != nil { + nameCount[effectiveNameForPort(containerPort)] += 1 + } + } + if len(nameCount) > 0 { + maxNameCount := 0 + mostPrevalentContainerPortName := "" + for name, count := range nameCount { + if maxNameCount < count { + mostPrevalentContainerPortName = name + maxNameCount = count + } + } + return mostPrevalentContainerPortName + } + } + + // If still no match for the target port, fall back to string-ifying the target port name, which + // is what the PodController will do when converting unnamed ContainerPorts to Workload ports. + return targetPort.String() +} + +// getTargetContainerPort returns the pod ContainerPort matching the given numeric port value, or nil if none is found. +func getTargetContainerPort(targetPort int32, pod *corev1.Pod) *corev1.ContainerPort { + for _, c := range pod.Spec.Containers { + if len(c.Ports) == 0 { + continue + } + for _, p := range c.Ports { + if p.ContainerPort == targetPort && p.Protocol == corev1.ProtocolTCP { + return &p + } + } + } + return nil +} + // getServiceVIPs returns the VIPs to associate with the registered Consul service. This will contain the Kubernetes // Service ClusterIP if it exists. // @@ -309,18 +460,18 @@ func getServiceMeta(service corev1.Service) map[string]string { // getWorkloadSelector returns the WorkloadSelector for the given pod name prefixes and exact names. // It returns nil if the provided name sets are empty. -func getWorkloadSelector(podPrefixes, podExactNames map[string]any) *pbcatalog.WorkloadSelector { +func getWorkloadSelector(prefixedPods selectorPodData, exactNamePods selectorPodData) *pbcatalog.WorkloadSelector { // If we don't have any values, return nil - if len(podPrefixes) == 0 && len(podExactNames) == 0 { + if len(prefixedPods) == 0 && len(exactNamePods) == 0 { return nil } // Create the WorkloadSelector workloads := &pbcatalog.WorkloadSelector{} - for v := range podPrefixes { + for v := range prefixedPods { workloads.Prefixes = append(workloads.Prefixes, v) } - for v := range podExactNames { + for v := range exactNamePods { workloads.Names = append(workloads.Names, v) } diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 1762471a46..f696e06150 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -35,6 +35,10 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) +const ( + kindDaemonSet = "DaemonSet" +) + var ( appProtocolHttp = "http" appProtocolHttp2 = "http2" @@ -158,7 +162,7 @@ func TestReconcile_CreateService(t *testing.T) { svcName: "service-created", k8sObjects: func() []runtime.Object { pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePod("DaemonSet", "service-created-ds", "12345") + pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") endpoints := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-created", @@ -466,6 +470,385 @@ func TestReconcile_CreateService(t *testing.T) { }, }, }, + { + name: "Numeric service target port: Named container port gets the pod port name", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde", + // Named port with container port value matching service target port + containerWithPort("named-port", 2345), + // Unnamed port with container port value matching service target port + containerWithPort("", 6789)) + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1), + Ports: []corev1.EndpointPort{ + { + Name: "public", + Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 6789, + Protocol: "TCP", + AppProtocol: &appProtocolGrpc, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromInt(2345), // Numeric target port + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 9090, + Protocol: "TCP", + TargetPort: intstr.FromInt(6789), // Numeric target port + AppProtocol: &appProtocolGrpc, + }, + { + Name: "unmatched-port", + Port: 10010, + Protocol: "TCP", + TargetPort: intstr.FromInt(10010), // Numeric target port + AppProtocol: &appProtocolHttp, + }, + }, + }, + } + return []runtime.Object{pod1, endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "named-port", // Matches container port name, not service target number + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + VirtualPort: 9090, + TargetPort: "6789", // Matches service target number + Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + { + VirtualPort: 10010, + TargetPort: "10010", // Matches service target number (unmatched by container ports) + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + { + name: "Numeric service target port: Container port mix gets the name from largest matching pod set", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + // Unnamed port matching service target port. + // Also has second named port, and is not the most prevalent set for that port. + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde", + containerWithPort("", 2345), + containerWithPort("api-port", 6789)) + + // Named port with different name from most prevalent pods. + // Also has second unnamed port, and _is_ the most prevalent set for that port. + pod2a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij", + containerWithPort("another-port-name", 2345), + containerWithPort("", 6789)) + pod2b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij", + containerWithPort("another-port-name", 2345), + containerWithPort("", 6789)) + + // Named port with container port value matching service target port. + // The most common "set" of pods, so should become the port name for service target port. + pod3a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", + containerWithPort("named-port", 2345)) + pod3b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", + containerWithPort("named-port", 2345)) + pod3c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", + containerWithPort("named-port", 2345)) + + // Named port that does not match service target port. + // More common "set" of pods selected by the service, but does not have a target port (value) match. + pod4a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", + containerWithPort("non-matching-named-port", 5432)) + pod4b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", + containerWithPort("non-matching-named-port", 5432)) + pod4c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", + containerWithPort("non-matching-named-port", 5432)) + pod4d := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", + containerWithPort("non-matching-named-port", 5432)) + + // Named port from non-injected pods. + // More common "set" of pods selected by the service, but should be filtered out. + pod5a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", + containerWithPort("ignored-named-port", 2345)) + pod5b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", + containerWithPort("ignored-named-port", 2345)) + pod5c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", + containerWithPort("ignored-named-port", 2345)) + pod5d := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", + containerWithPort("ignored-named-port", 2345)) + for _, p := range []*corev1.Pod{pod5a, pod5b, pod5c, pod5d} { + removeMeshInjectStatus(t, p) + } + + // Named port with container port value matching service target port. + // Single pod from non-ReplicaSet owner. Should not take precedence over set pods. + pod6a := createServicePod(kindDaemonSet, "service-created-ds", "12345", + containerWithPort("another-port-name", 2345)) + + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods( + pod1, + pod2a, pod2b, + pod3a, pod3b, pod3c, + pod4a, pod4b, pod4c, pod4d, + pod5a, pod5b, pod5c, pod5d, + pod6a), + Ports: []corev1.EndpointPort{ + { + Name: "public", + Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromInt(2345), // Numeric target port + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 9090, + Protocol: "TCP", + TargetPort: intstr.FromInt(6789), // Numeric target port + AppProtocol: &appProtocolGrpc, + }, + }, + }, + } + return []runtime.Object{ + pod1, + pod2a, pod2b, + pod3a, pod3b, pod3c, + pod4a, pod4b, pod4c, pod4d, + pod5a, pod5b, pod5c, pod5d, + pod6a, + endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "named-port", // Matches container port name, not service target number + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + VirtualPort: 9090, + TargetPort: "6789", // Matches service target number due to unnamed being most common + Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{ + "service-created-rs-abcde", + "service-created-rs-fghij", + "service-created-rs-klmno", + "service-created-rs-pqrst", + }, + Names: []string{ + "service-created-ds-12345", + }, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + { + name: "Numeric service target port: Most used container port name from exact name pods used when no pod sets present", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + // Named port with different name from most prevalent pods. + pod1a := createServicePod(kindDaemonSet, "service-created-ds1", "12345", + containerWithPort("another-port-name", 2345)) + + // Named port with container port value matching service target port. + // The most common container port name, so should become the port name for service target port. + pod2a := createServicePod(kindDaemonSet, "service-created-ds2", "12345", + containerWithPort("named-port", 2345)) + pod2b := createServicePod(kindDaemonSet, "service-created-ds2", "23456", + containerWithPort("named-port", 2345)) + + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods( + pod1a, + pod2a, pod2b), + Ports: []corev1.EndpointPort{ + { + Name: "public", + Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromInt(2345), // Numeric target port + AppProtocol: &appProtocolHttp, + }, + }, + }, + } + return []runtime.Object{ + pod1a, + pod2a, pod2b, + endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: &pbresource.Type{ + Group: "catalog", + GroupVersion: "v1alpha1", + Kind: "Service", + }, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: common.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "named-port", // Matches container port name, not service target number + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{ + "service-created-ds1-12345", + "service-created-ds2-12345", + "service-created-ds2-23456", + }, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, { name: "Only L4 TCP ports get a Consul Service port when L4 protocols are multiplexed", svcName: "service-created", @@ -561,8 +944,7 @@ func TestReconcile_CreateService(t *testing.T) { svcName: "service-created", k8sObjects: func() []runtime.Object { pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - // Clear mesh inject status - delete(pod1.Annotations, constants.KeyMeshInjectStatus) + removeMeshInjectStatus(t, pod1) endpoints := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-created", @@ -621,8 +1003,8 @@ func TestReconcile_UpdateService(t *testing.T) { k8sObjects: func() []runtime.Object { pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno") - pod3 := createServicePod("DaemonSet", "service-created-ds", "12345") - pod4 := createServicePod("DaemonSet", "service-created-ds", "34567") + pod3 := createServicePod(kindDaemonSet, "service-created-ds", "12345") + pod4 := createServicePod(kindDaemonSet, "service-created-ds", "34567") endpoints := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-updated", @@ -753,7 +1135,7 @@ func TestReconcile_UpdateService(t *testing.T) { svcName: "service-updated", k8sObjects: func() []runtime.Object { pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePod("DaemonSet", "service-created-ds", "12345") + pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") endpoints := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-updated", @@ -961,7 +1343,7 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { type testCase struct { name string - endpoints *corev1.Endpoints + endpoints corev1.Endpoints responses map[types.NamespacedName]*corev1.Pod expected *pbcatalog.WorkloadSelector mockFn func(*testing.T, *MockPodFetcher) @@ -976,13 +1358,19 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { createServicePod(kindReplicaSet, "svc-rs-fghij", "34567"), } otherPods := []*corev1.Pod{ - createServicePod("DaemonSet", "svc-ds", "12345"), - createServicePod("DaemonSet", "svc-ds", "23456"), - createServicePod("DaemonSet", "svc-ds", "34567"), + createServicePod(kindDaemonSet, "svc-ds", "12345"), + createServicePod(kindDaemonSet, "svc-ds", "23456"), + createServicePod(kindDaemonSet, "svc-ds", "34567"), createServicePod("StatefulSet", "svc-ss", "12345"), createServicePod("StatefulSet", "svc-ss", "23456"), createServicePod("StatefulSet", "svc-ss", "34567"), } + ignoredPods := []*corev1.Pod{ + createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "12345"), + createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "23456"), + createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "34567"), + } + podsByName := make(map[types.NamespacedName]*corev1.Pod) for _, p := range rsPods { podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p @@ -990,11 +1378,15 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { for _, p := range otherPods { podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p } + for _, p := range ignoredPods { + removeMeshInjectStatus(t, p) + podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p + } cases := []testCase{ { name: "Pod is fetched once per ReplicaSet", - endpoints: &corev1.Endpoints{ + endpoints: corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "svc", Namespace: "default", @@ -1015,11 +1407,11 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { responses: podsByName, expected: getWorkloadSelector( // Selector should consist of prefixes only. - map[string]any{ - "svc-rs-abcde": true, - "svc-rs-fghij": true, + selectorPodData{ + "svc-rs-abcde": &podSetData{}, + "svc-rs-fghij": &podSetData{}, }, - map[string]any{}), + selectorPodData{}), mockFn: func(t *testing.T, pf *MockPodFetcher) { // Assert called once per set. require.Equal(t, 2, len(pf.calls)) @@ -1027,7 +1419,7 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { }, { name: "Pod is fetched once per other pod owner type", - endpoints: &corev1.Endpoints{ + endpoints: corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "svc", Namespace: "default", @@ -1048,21 +1440,47 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { responses: podsByName, expected: getWorkloadSelector( // Selector should consist of exact name matches only. - map[string]any{}, - map[string]any{ - "svc-ds-12345": true, - "svc-ds-23456": true, - "svc-ds-34567": true, - "svc-ss-12345": true, - "svc-ss-23456": true, - "svc-ss-34567": true, + selectorPodData{}, + selectorPodData{ + "svc-ds-12345": &podSetData{}, + "svc-ds-23456": &podSetData{}, + "svc-ds-34567": &podSetData{}, + "svc-ss-12345": &podSetData{}, + "svc-ss-23456": &podSetData{}, + "svc-ss-34567": &podSetData{}, }), mockFn: func(t *testing.T, pf *MockPodFetcher) { // Assert called once per pod. require.Equal(t, len(otherPods), len(pf.calls)) }, }, - //TODO: Add cases to cover non-injected pod skipping. + { + name: "Pod is ignored if not mesh-injected", + endpoints: corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(ignoredPods...), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + AppProtocol: &appProtocolHttp, + Port: 2345, + }, + }, + }, + }, + }, + responses: podsByName, + expected: nil, + mockFn: func(t *testing.T, pf *MockPodFetcher) { + // Assert called once for single set. + require.Equal(t, 1, len(pf.calls)) + }, + }, } for _, tc := range cases { @@ -1075,10 +1493,11 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { Log: logrtest.New(t), } - resp, err := ep.getWorkloadSelectorFromEndpoints(ctx, &pf, tc.endpoints) + prefixedPods, exactNamePods, err := ep.getWorkloadDataFromEndpoints(ctx, &pf, tc.endpoints) require.NoError(t, err) - if diff := cmp.Diff(tc.expected, resp, test.CmpProtoIgnoreOrder()...); diff != "" { + ws := getWorkloadSelector(prefixedPods, exactNamePods) + if diff := cmp.Diff(tc.expected, ws, test.CmpProtoIgnoreOrder()...); diff != "" { t.Errorf("unexpected difference:\n%v", diff) } tc.mockFn(t, &pf) @@ -1191,11 +1610,11 @@ func expectedServiceMatches(t *testing.T, client pbresource.ResourceServiceClien } } -func createServicePodOwnedBy(ownerKind, ownerName string) *corev1.Pod { - return createServicePod(ownerKind, ownerName, randomKubernetesId()) +func createServicePodOwnedBy(ownerKind, ownerName string, containers ...corev1.Container) *corev1.Pod { + return createServicePod(ownerKind, ownerName, randomKubernetesId(), containers...) } -func createServicePod(ownerKind, ownerName, podId string) *corev1.Pod { +func createServicePod(ownerKind, ownerName, podId string, containers ...corev1.Container) *corev1.Pod { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s-%s", ownerName, podId), @@ -1212,10 +1631,25 @@ func createServicePod(ownerKind, ownerName, podId string) *corev1.Pod { }, }, }, + Spec: corev1.PodSpec{ + Containers: containers, + }, } return pod } +func containerWithPort(name string, port int32) corev1.Container { + return corev1.Container{ + Ports: []corev1.ContainerPort{ + { + Name: name, + ContainerPort: port, + Protocol: "TCP", + }, + }, + } +} + func addressesForPods(pods ...*corev1.Pod) []corev1.EndpointAddress { var addresses []corev1.EndpointAddress for i, p := range pods { @@ -1238,3 +1672,8 @@ func randomKubernetesId() string { } return u[:5] } + +func removeMeshInjectStatus(t *testing.T, pod *corev1.Pod) { + delete(pod.Annotations, constants.KeyMeshInjectStatus) + require.False(t, common.HasBeenMeshInjected(*pod)) +} From de31ec71102b379f10dcb3418f994a2237fd1242 Mon Sep 17 00:00:00 2001 From: "Chris S. Kim" Date: Fri, 22 Sep 2023 10:42:34 -0400 Subject: [PATCH 399/592] Update changelog (#2990) --- CHANGELOG.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9a4d76bef..06934dd5cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,91 @@ +## 1.2.2 (September 21, 2023) + +SECURITY: + +* Upgrade to use Go 1.20.8. This resolves CVEs + [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), + [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), + [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), + [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and + [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2936](https://github.com/hashicorp/consul-k8s/issues/2936)] + +FEATURES: + +* Add support for new observability service principal in cloud preset [[GH-2958](https://github.com/hashicorp/consul-k8s/issues/2958)] +* helm: Add ability to configure resource requests and limits for Gateway API deployments. [[GH-2723](https://github.com/hashicorp/consul-k8s/issues/2723)] + +IMPROVEMENTS: + +* Add NET_BIND_SERVICE capability to restricted security context used for consul-dataplane [[GH-2787](https://github.com/hashicorp/consul-k8s/issues/2787)] +* Add new value `global.argocd.enabled`. Set this to `true` when using ArgoCD to deploy this chart. [[GH-2785](https://github.com/hashicorp/consul-k8s/issues/2785)] +* Add support for running on GKE Autopilot. [[GH-2952](https://github.com/hashicorp/consul-k8s/issues/2952)] +* api-gateway: reduce log output when disconnecting from consul server [[GH-2880](https://github.com/hashicorp/consul-k8s/issues/2880)] +* control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. [[GH-2910](https://github.com/hashicorp/consul-k8s/issues/2910)] +* control-plane: prevent updation of anonymous-token-policy and anonymous-token if anonymous-token-policy is already attached to the anonymous-token [[GH-2790](https://github.com/hashicorp/consul-k8s/issues/2790)] +* helm: Add `JWKSCluster` field to `JWTProvider` CRD. [[GH-2881](https://github.com/hashicorp/consul-k8s/issues/2881)] +* vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to + secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. + This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] + +BUG FIXES: + +* audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. [[GH-2905](https://github.com/hashicorp/consul-k8s/issues/2905)] +* bug: Remove `global.acls.nodeSelector` and `global.acls.annotations` from Gateway Resources Jobs [[GH-2869](https://github.com/hashicorp/consul-k8s/issues/2869)] +* control-plane: Fix issue where ACL tokens would have an empty pod name that prevented proper token cleanup. [[GH-2808](https://github.com/hashicorp/consul-k8s/issues/2808)] +* control-plane: When using transparent proxy or CNI, reduced required permissions by setting privileged to false. Privileged must be true when using OpenShift without CNI. [[GH-2755](https://github.com/hashicorp/consul-k8s/issues/2755)] +* helm: Update prometheus port and scheme annotations if tls is enabled [[GH-2782](https://github.com/hashicorp/consul-k8s/issues/2782)] +* ingress-gateway: Adds missing PassiveHealthCheck to IngressGateways CRD and updates missing fields on ServiceDefaults CRD [[GH-2796](https://github.com/hashicorp/consul-k8s/issues/2796)] + +## 1.1.6 (September 21, 2023) + +SECURITY: + +* Upgrade to use Go 1.20.8. This resolves CVEs + [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), + [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), + [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), + [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and + [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2936](https://github.com/hashicorp/consul-k8s/issues/2936)] + +IMPROVEMENTS: + +* control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. [[GH-2910](https://github.com/hashicorp/consul-k8s/issues/2910)] +* vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to + secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. + This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] + +BUG FIXES: + +* audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. [[GH-2905](https://github.com/hashicorp/consul-k8s/issues/2905)] + +## 1.0.10 (September 21, 2023) + +SECURITY: + +* Upgrade to use Go 1.19.13. This resolves CVEs + [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), + [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), + [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), + [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and + [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2938](https://github.com/hashicorp/consul-k8s/issues/2938)] + +IMPROVEMENTS: + +* Add NET_BIND_SERVICE capability to restricted security context used for consul-dataplane [[GH-2787](https://github.com/hashicorp/consul-k8s/issues/2787)] +* Add new value `global.argocd.enabled`. Set this to `true` when using ArgoCD to deploy this chart. [[GH-2785](https://github.com/hashicorp/consul-k8s/issues/2785)] +* control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. [[GH-2910](https://github.com/hashicorp/consul-k8s/issues/2910)] +* control-plane: prevent updation of anonymous-token-policy and anonymous-token if anonymous-token-policy is already attached to the anonymous-token [[GH-2790](https://github.com/hashicorp/consul-k8s/issues/2790)] +* vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to + secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. + This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] + +BUG FIXES: + +* audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. [[GH-2905](https://github.com/hashicorp/consul-k8s/issues/2905)] +* control-plane: Fix issue where ACL tokens would have an empty pod name that prevented proper token cleanup. [[GH-2808](https://github.com/hashicorp/consul-k8s/issues/2808)] +* control-plane: When using transparent proxy or CNI, reduced required permissions by setting privileged to false. Privileged must be true when using OpenShift without CNI. [[GH-2755](https://github.com/hashicorp/consul-k8s/issues/2755)] +* helm: Update prometheus port and scheme annotations if tls is enabled [[GH-2782](https://github.com/hashicorp/consul-k8s/issues/2782)] + ## 1.2.1 (Aug 10, 2023) BREAKING CHANGES: From d11a467edcb285e17b91b0f1f0fe12f72678d392 Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Fri, 22 Sep 2023 08:58:46 -0700 Subject: [PATCH 400/592] add health status syncing check in acceptance test (#2994) --- acceptance/tests-v2/mesh/mesh_inject_test.go | 22 +++++++++++++++++-- .../controllers/pod/pod_controller.go | 1 + 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/acceptance/tests-v2/mesh/mesh_inject_test.go b/acceptance/tests-v2/mesh/mesh_inject_test.go index e6deb4aa8c..0ebccbc7ab 100644 --- a/acceptance/tests-v2/mesh/mesh_inject_test.go +++ b/acceptance/tests-v2/mesh/mesh_inject_test.go @@ -19,6 +19,8 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/logger" ) +const multiport = "multiport" + // Test that mesh sidecar proxies work for an application with multiple ports. The multiport application is a Pod listening on // two ports. This tests inbound connections to each port of the multiport app, and outbound connections from the // multiport app to static-server. @@ -35,8 +37,8 @@ func TestMeshInject_MultiportService(t *testing.T) { ctx := suite.Environment().DefaultContext(t) helmValues := map[string]string{ - "global.image": "ndhanushkodi/consul-dev:multiport36", - "global.imageK8S": "ndhanushkodi/consul-k8s-dev:multiport20", + "global.image": "ndhanushkodi/consul-dev:multiport37", + "global.imageK8S": "ndhanushkodi/consul-k8s-dev:multiport25", "global.imageConsulDataplane": "hashicorppreview/consul-dataplane:1.3-dev", "global.experiments[0]": "resource-apis", // The UI is not supported for v2 in 1.17, so for now it must be disabled. @@ -79,6 +81,22 @@ func TestMeshInject_MultiportService(t *testing.T) { // Check connection from static-client to multiport-admin. k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://multiport:9090") + + // Test that kubernetes readiness status is synced to Consul. This will make the multi port pods unhealthy + // and check inbound connections to the multi port pods' services. + // Create the files so that the readiness probes of the multi port pod fails. + logger.Log(t, "testing k8s -> consul health checks sync by making the multiport unhealthy") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport", "--", "touch", "/tmp/unhealthy-multiport") + logger.Log(t, "testing k8s -> consul health checks sync by making the multiport-admin unhealthy") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport-admin", "--", "touch", "/tmp/unhealthy-multiport-admin") + + // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry + // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. + // We are expecting a "connection reset by peer" error because in a case of health checks, + // there will be no healthy proxy host to connect to. That's why we can't assert that we receive an empty reply + // from server, which is the case when a connection is unsuccessful due to intentions in other tests. + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") }) } } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 9feb090043..358aebf9e4 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -815,6 +815,7 @@ func getHealthStatusFromPod(pod corev1.Pod) pbcatalog.Health { return pbcatalog.Health_HEALTH_PASSING } } + return pbcatalog.Health_HEALTH_CRITICAL } From e57edf5a0d239a0d4a1d937458af4887ba3772cc Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Fri, 22 Sep 2023 16:16:55 -0400 Subject: [PATCH 401/592] V2 MeshConfig Controller and TrafficPermissions CRD (#2967) Co-authored-by: DanStough --- .../templates/connect-inject-clusterrole.yaml | 22 + ...t-inject-mutatingwebhookconfiguration.yaml | 23 + .../crd-controlplanerequestlimits.yaml | 9 +- .../templates/crd-exportedservices.yaml | 9 +- .../templates/crd-gatewayclassconfigs.yaml | 10 +- .../consul/templates/crd-ingressgateways.yaml | 9 +- charts/consul/templates/crd-jwtproviders.yaml | 89 +- charts/consul/templates/crd-meshes.yaml | 9 +- charts/consul/templates/crd-meshservices.yaml | 10 +- .../templates/crd-peeringacceptors.yaml | 9 +- .../consul/templates/crd-peeringdialers.yaml | 9 +- .../consul/templates/crd-proxydefaults.yaml | 9 +- .../templates/crd-routeauthfilters.yaml | 2 +- .../consul/templates/crd-samenessgroups.yaml | 9 +- .../consul/templates/crd-servicedefaults.yaml | 9 +- .../templates/crd-serviceintentions.yaml | 9 +- .../templates/crd-serviceresolvers.yaml | 9 +- .../consul/templates/crd-servicerouters.yaml | 9 +- .../templates/crd-servicesplitters.yaml | 9 +- charts/consul/templates/crd-tcproutes.yaml | 2 +- .../templates/crd-terminatinggateways.yaml | 9 +- .../templates/crd-trafficpermissions.yaml | 268 ++++++ control-plane/PROJECT | 9 + control-plane/api/common/common.go | 36 + control-plane/api/common/meshconfig.go | 59 ++ .../api/common/meshconfig_webhook.go | 87 ++ .../api/common/meshconfig_webhook_test.go | 333 ++++++++ .../api/v2beta1/groupversion_info.go | 28 + control-plane/api/v2beta1/shared_types.go | 14 + control-plane/api/v2beta1/status.go | 93 ++ .../api/v2beta1/traffic_permissions_types.go | 427 ++++++++++ .../v2beta1/traffic_permissions_types_test.go | 803 ++++++++++++++++++ .../api/v2beta1/trafficpermissions_webhook.go | 65 ++ .../api/v2beta1/zz_generated.deepcopy.go | 467 ++++++++++ .../controllers/configentry_controller.go | 7 +- .../configentry_controller_ent_test.go | 9 +- .../configentry_controller_test.go | 9 +- .../controlplanerequestlimit_controller.go | 0 .../exportedservices_controller.go | 0 .../exportedservices_controller_ent_test.go | 9 +- .../controllers/ingressgateway_controller.go | 0 .../controllers/jwtprovider_controller.go | 0 .../controllers/mesh_controller.go | 0 .../controllers/proxydefaults_controller.go | 0 .../controllers/samenessgroups_controller.go | 0 .../controllers/servicedefaults_controller.go | 0 .../serviceintentions_controller.go | 0 .../controllers/serviceresolver_controller.go | 0 .../controllers/servicerouter_controller.go | 0 .../controllers/servicesplitter_controller.go | 0 .../terminatinggateway_controller.go | 0 .../controllersv2/meshconfig_controller.go | 317 +++++++ .../meshconfig_controller_test.go | 772 +++++++++++++++++ .../traffic_permissions_controller.go | 43 + ...nsul.hashicorp.com_trafficpermissions.yaml | 263 ++++++ .../consul.hashicorp.com_gatewaypolicies.yaml | 2 +- .../consul.hashicorp.com_jwtproviders.yaml | 80 +- ...consul.hashicorp.com_routeauthfilters.yaml | 10 +- control-plane/config/crd/external/.yml | 4 + control-plane/config/crd/kustomization.yaml | 27 - control-plane/config/rbac/role.yaml | 20 + control-plane/config/webhook/manifests.yaml | 21 + control-plane/connect-inject/common/common.go | 3 +- .../connect-inject/common/common_test.go | 8 +- control-plane/connect-inject/common/config.go | 36 - .../endpointsv2/endpoints_controller.go | 28 +- .../endpointsv2/endpoints_controller_test.go | 116 +-- .../controllers/pod/pod_controller.go | 79 +- .../pod/pod_controller_ent_test.go | 6 +- .../controllers/pod/pod_controller_test.go | 76 +- .../serviceaccount_controller.go | 20 +- .../serviceaccount_controller_test.go | 46 +- .../consul_dataplane_sidecar.go | 2 +- .../consul_dataplane_sidecar_test.go | 2 +- .../container_env.go | 2 +- .../container_env_test.go | 2 +- .../container_init.go | 2 +- .../container_init_test.go | 2 +- .../container_volume.go | 2 +- .../{webhook_v2 => webhookv2}/dns.go | 2 +- .../{webhook_v2 => webhookv2}/dns_test.go | 2 +- .../health_checks_test.go | 2 +- .../{webhook_v2 => webhookv2}/heath_checks.go | 2 +- .../{webhook_v2 => webhookv2}/mesh_webhook.go | 2 +- .../mesh_webhook_ent_test.go | 2 +- .../mesh_webhook_test.go | 2 +- .../redirect_traffic.go | 2 +- .../redirect_traffic_test.go | 2 +- control-plane/consul/dataplane_client_test.go | 19 +- control-plane/consul/resource_client_test.go | 8 +- control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- control-plane/subcommand/acl-init/command.go | 9 +- .../subcommand/acl-init/command_test.go | 5 +- control-plane/subcommand/common/common.go | 3 +- .../subcommand/common/common_test.go | 3 +- .../connect-init/command_ent_test.go | 3 +- .../subcommand/connect-init/command_test.go | 5 +- .../subcommand/consul-logout/command.go | 7 +- .../subcommand/consul-logout/command_test.go | 5 +- .../create-federation-secret/command.go | 9 +- .../create-federation-secret/command_test.go | 5 +- .../delete-completed-job/command.go | 7 +- .../subcommand/fetch-server-region/command.go | 5 +- control-plane/subcommand/flags/consul.go | 3 +- control-plane/subcommand/flags/http.go | 3 +- .../subcommand/gateway-cleanup/command.go | 7 +- .../gateway-cleanup/command_test.go | 3 +- .../subcommand/gateway-resources/command.go | 11 +- .../gateway-resources/command_test.go | 3 +- .../get-consul-client-ca/command.go | 9 +- .../gossip-encryption-autogenerate/command.go | 7 +- .../subcommand/inject-connect/command.go | 10 +- .../inject-connect/v1controllers.go | 2 +- .../inject-connect/v2controllers.go | 41 +- .../subcommand/install-cni/command.go | 5 +- .../subcommand/install-cni/command_test.go | 3 +- control-plane/subcommand/mesh-init/command.go | 2 +- .../subcommand/mesh-init/command_test.go | 16 +- .../server-acl-init/anonymous_token_test.go | 3 + .../subcommand/server-acl-init/command.go | 11 +- .../server-acl-init/command_ent_test.go | 7 +- .../server-acl-init/connect_inject.go | 3 +- .../server-acl-init/connect_inject_test.go | 3 +- .../server-acl-init/create_or_update.go | 3 +- .../server-acl-init/k8s_secrets_backend.go | 3 +- .../subcommand/server-acl-init/rules_test.go | 3 +- .../subcommand/sync-catalog/command.go | 13 +- .../sync-catalog/command_ent_test.go | 3 +- .../subcommand/sync-catalog/command_test.go | 3 +- control-plane/subcommand/tls-init/command.go | 9 +- 131 files changed, 4652 insertions(+), 652 deletions(-) create mode 100644 charts/consul/templates/crd-trafficpermissions.yaml create mode 100644 control-plane/api/common/meshconfig.go create mode 100644 control-plane/api/common/meshconfig_webhook.go create mode 100644 control-plane/api/common/meshconfig_webhook_test.go create mode 100644 control-plane/api/v2beta1/groupversion_info.go create mode 100644 control-plane/api/v2beta1/shared_types.go create mode 100644 control-plane/api/v2beta1/status.go create mode 100644 control-plane/api/v2beta1/traffic_permissions_types.go create mode 100644 control-plane/api/v2beta1/traffic_permissions_types_test.go create mode 100644 control-plane/api/v2beta1/trafficpermissions_webhook.go create mode 100644 control-plane/api/v2beta1/zz_generated.deepcopy.go rename control-plane/{ => config-entries}/controllers/configentry_controller.go (99%) rename control-plane/{ => config-entries}/controllers/configentry_controller_ent_test.go (99%) rename control-plane/{ => config-entries}/controllers/configentry_controller_test.go (99%) rename control-plane/{ => config-entries}/controllers/controlplanerequestlimit_controller.go (100%) rename control-plane/{ => config-entries}/controllers/exportedservices_controller.go (100%) rename control-plane/{ => config-entries}/controllers/exportedservices_controller_ent_test.go (99%) rename control-plane/{ => config-entries}/controllers/ingressgateway_controller.go (100%) rename control-plane/{ => config-entries}/controllers/jwtprovider_controller.go (100%) rename control-plane/{ => config-entries}/controllers/mesh_controller.go (100%) rename control-plane/{ => config-entries}/controllers/proxydefaults_controller.go (100%) rename control-plane/{ => config-entries}/controllers/samenessgroups_controller.go (100%) rename control-plane/{ => config-entries}/controllers/servicedefaults_controller.go (100%) rename control-plane/{ => config-entries}/controllers/serviceintentions_controller.go (100%) rename control-plane/{ => config-entries}/controllers/serviceresolver_controller.go (100%) rename control-plane/{ => config-entries}/controllers/servicerouter_controller.go (100%) rename control-plane/{ => config-entries}/controllers/servicesplitter_controller.go (100%) rename control-plane/{ => config-entries}/controllers/terminatinggateway_controller.go (100%) create mode 100644 control-plane/config-entries/controllersv2/meshconfig_controller.go create mode 100644 control-plane/config-entries/controllersv2/meshconfig_controller_test.go create mode 100644 control-plane/config-entries/controllersv2/traffic_permissions_controller.go create mode 100644 control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml create mode 100644 control-plane/config/crd/external/.yml delete mode 100644 control-plane/config/crd/kustomization.yaml delete mode 100644 control-plane/connect-inject/common/config.go rename control-plane/connect-inject/{webhook_v2 => webhookv2}/consul_dataplane_sidecar.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/consul_dataplane_sidecar_test.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/container_env.go (98%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/container_env_test.go (98%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/container_init.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/container_init_test.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/container_volume.go (96%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/dns.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/dns_test.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/health_checks_test.go (98%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/heath_checks.go (96%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/mesh_webhook.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/mesh_webhook_ent_test.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/mesh_webhook_test.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/redirect_traffic.go (99%) rename control-plane/connect-inject/{webhook_v2 => webhookv2}/redirect_traffic_test.go (99%) diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index af82ba2775..f99d82409f 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -72,6 +72,28 @@ rules: - get - patch - update +{{- if (mustHas "resource-apis" .Values.global.experiments) }} +- apiGroups: + - auth.consul.hashicorp.com + resources: + - trafficpermissions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - auth.consul.hashicorp.com + resources: + - trafficpermissions/status + verbs: + - get + - patch + - update +{{- end }} - apiGroups: [ "" ] resources: [ "secrets", "serviceaccounts", "endpoints", "services", "namespaces", "nodes" ] verbs: diff --git a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml index e4fe79f621..e65c386636 100644 --- a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml +++ b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml @@ -333,6 +333,29 @@ webhooks: resources: - samenessgroups sideEffects: None +{{- if (mustHas "resource-apis" .Values.global.experiments) }} +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: {{ template "consul.fullname" . }}-connect-injector + namespace: {{ .Release.Namespace }} + path: /mutate-v2beta1-trafficpermissions + failurePolicy: Fail + name: mutate-trafficpermissions.auth.consul.hashicorp.com + rules: + - apiGroups: + - auth.consul.hashicorp.com + apiVersions: + - v2beta1 + operations: + - CREATE + - UPDATE + resources: + - trafficpermissions + sideEffects: None +{{- end }} {{- end }} - admissionReviewVersions: - v1beta1 diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml index 01722c0cf0..2b0c45a621 100644 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: controlplanerequestlimits.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index dd6b6ba3b8..591500cb12 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: exportedservices.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 98ecb345f3..67eb30944f 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -1,20 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: gatewayclassconfigs.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index 51c02422b2..9fa5ef7edd 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: ingressgateways.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index 9f97922eb5..e5726cefe3 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: jwtproviders.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -124,61 +121,61 @@ spec: before being activated. \n Default value is false." type: boolean jwksCluster: - description: "JWKSCluster defines how the specified Remote JWKS - URI is to be fetched." + description: JWKSCluster defines how the specified Remote + JWKS URI is to be fetched. properties: connectTimeout: - description: "The timeout for new network connections to hosts - in the cluster. \n If not set, a default value of 5s will be - used." + description: The timeout for new network connections to + hosts in the cluster. If not set, a default value of + 5s will be used. format: int64 type: integer discoveryType: - description: "DiscoveryType refers to the service discovery type - to use for resolving the cluster. \n Defaults to STRICT_DNS." + description: "DiscoveryType refers to the service discovery + type to use for resolving the cluster. \n This defaults + to STRICT_DNS. Other options include STATIC, LOGICAL_DNS, + EDS or ORIGINAL_DST." type: string tlsCertificates: description: "TLSCertificates refers to the data containing - certificate authority certificates to use in verifying a presented - peer certificate." + certificate authority certificates to use in verifying + a presented peer certificate. If not specified and a + peer certificate is presented it will not be verified. + \n Must be either CaCertificateProviderInstance or TrustedCA." properties: caCertificateProviderInstance: - description: "CaCertificateProviderInstance Certificate provider - instance for fetching TLS certificates." + description: CaCertificateProviderInstance Certificate + provider instance for fetching TLS certificates. properties: - instanceName: - description: "InstanceName refers to the certificate provider - instance name. \n The default value is 'default'." - type: string certificateName: - description: "CertificateName is used to specify certificate - instances or types. For example, \"ROOTCA\" to specify a - root-certificate (validation context) or \"example.com\" - to specify a certificate for a particular domain. \n - The default value is the empty string." + description: "CertificateName is used to specify + certificate instances or types. For example, + \"ROOTCA\" to specify a root-certificate (validation + context) or \"example.com\" to specify a certificate + for a particular domain. \n The default value + is the empty string." + type: string + instanceName: + description: "InstanceName refers to the certificate + provider instance name. \n The default value + is \"default\"." type: string type: object trustedCA: - description: "TrustedCA defines TLS certificate data containing - certificate authority certificates to use in verifying a presented - peer certificate. \n Exactly one of Filename, EnvironmentVariable, - InlineString or InlineBytes must be specified." + description: "TrustedCA defines TLS certificate data + containing certificate authority certificates to + use in verifying a presented peer certificate. \n + Exactly one of Filename, EnvironmentVariable, InlineString + or InlineBytes must be specified." properties: - filename: - description: "The name of the file on the local system to use a - data source for trusted CA certificates." - type: string environmentVariable: - description: "The environment variable on the local system to use - a data source for trusted CA certificates." type: string - inlineString: - description: "A string to inline in the configuration for use as - a data source for trusted CA certificates." + filename: type: string inlineBytes: - description: "A sequence of bytes to inline in the configuration - for use as a data source for trusted CA certificates." + format: byte + type: string + inlineString: type: string type: object type: object @@ -198,20 +195,20 @@ spec: of 10s. \n Default value is 0." type: integer retryPolicyBackOff: - description: "Backoff policy \n Defaults to Envoy's backoff - policy" + description: "Retry's backoff policy. \n Defaults to Envoy's + backoff policy." properties: baseInterval: description: "BaseInterval to be used for the next - back off computation \n The default value from envoy - is 1s" + back off computation. \n The default value from + envoy is 1s." format: int64 type: integer maxInterval: description: "MaxInternal to be used to specify the maximum interval between retries. Optional but should be greater or equal to BaseInterval. \n Defaults - to 10 times BaseInterval" + to 10 times BaseInterval." format: int64 type: integer type: object diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index b1b2319579..0710d41280 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: meshes.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml index d52da5a028..df8f673bdc 100644 --- a/charts/consul/templates/crd-meshservices.yaml +++ b/charts/consul/templates/crd-meshservices.yaml @@ -1,20 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: meshservices.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index 6f335e83a2..e06e830f04 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: peeringacceptors.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index 5fa49f1eed..e24401e761 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: peeringdialers.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index c61ef28fd3..3d2df52a7e 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: proxydefaults.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-routeauthfilters.yaml b/charts/consul/templates/crd-routeauthfilters.yaml index 1a19d1f3ec..dcea7192a4 100644 --- a/charts/consul/templates/crd-routeauthfilters.yaml +++ b/charts/consul/templates/crd-routeauthfilters.yaml @@ -38,7 +38,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: RouteAuthFilter is the Schema for the httpauthfilters API. + description: RouteAuthFilter is the Schema for the routeauthfilters API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index 179972a9d6..60beb5662c 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: samenessgroups.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index 9fe23d160f..ddff40ced2 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicedefaults.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index edc7c7078b..c4d2b5f20d 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: serviceintentions.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 7965a9df53..eb5643fe49 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: serviceresolvers.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index d36e8028b5..f28da9e7c1 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicerouters.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index 15f7714a84..a2af050c3d 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicesplitters.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-tcproutes.yaml b/charts/consul/templates/crd-tcproutes.yaml index ba21ccd58a..91989135e2 100644 --- a/charts/consul/templates/crd-tcproutes.yaml +++ b/charts/consul/templates/crd-tcproutes.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.connectInject.enabled (or .Values.connectInject.apiGateway.manageExternalCRDs .Values.connectInject.apiGateway.manageNonStandardCRDs ) }} +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index fae09bff53..583c218be8 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -1,21 +1,18 @@ {{- if .Values.connectInject.enabled }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: terminatinggateways.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com names: diff --git a/charts/consul/templates/crd-trafficpermissions.yaml b/charts/consul/templates/crd-trafficpermissions.yaml new file mode 100644 index 0000000000..ef8e8a73ca --- /dev/null +++ b/charts/consul/templates/crd-trafficpermissions.yaml @@ -0,0 +1,268 @@ +{{- if .Values.connectInject.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: trafficpermissions.auth.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: auth.consul.hashicorp.com + names: + kind: TrafficPermissions + listKind: TrafficPermissionsList + plural: trafficpermissions + shortNames: + - traffic-permissions + singular: trafficpermissions + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: TrafficPermissions is the Schema for the traffic-permissions + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TrafficPermissionsSpec defines the desired state of TrafficPermissions. + properties: + action: + description: "Action can be either allow or deny for the entire object. + It will default to allow. \n If action is allow, we will allow the + connection if one of the rules in Rules matches, in other words, + we will deny all requests except for the ones that match Rules. + If Consul is in default allow mode, then allow actions have no effect + without a deny permission as everything is allowed by default. \n + If action is deny, we will deny the connection if one of the rules + in Rules match, in other words, we will allow all requests except + for the ones that match Rules. If Consul is default deny mode, then + deny permissions have no effect without an allow permission as everything + is denied by default. \n Action unspecified is reserved for compatibility + with the addition of future actions." + type: string + destination: + description: Destination is a configuration of the destination proxies + where these traffic permissions should apply. + properties: + identityName: + description: Name is the destination of all intentions defined + in this config entry. This may be set to the wildcard character + (*) to match all services that don't otherwise have intentions + defined. + type: string + type: object + permissions: + description: Permissions is a list of permissions to match on. They + are applied using OR semantics. + items: + properties: + destinationRules: + description: destinationRules is a list of rules to apply for + matching sources in this Permission. These rules are specific + to the request or connection that is going to the destination(s) + selected by the TrafficPermissions resource. + items: + description: DestinationRule contains rules to apply to the + incoming connection. + properties: + exclude: + description: exclude contains a list of rules to exclude + when evaluating rules for the incoming connection. + items: + properties: + header: + properties: + exact: + type: string + invert: + type: boolean + name: + type: string + prefix: + type: string + present: + type: boolean + regex: + type: string + suffix: + type: string + type: object + methods: + description: methods is the list of HTTP methods. + items: + type: string + type: array + pathExact: + type: string + pathPrefix: + type: string + pathRegex: + type: string + portNames: + description: portNames is a list of workload ports + to apply this rule to. The ports specified here + must be the ports used in the connection. + items: + type: string + type: array + type: object + type: array + header: + properties: + exact: + type: string + invert: + type: boolean + name: + type: string + prefix: + type: string + present: + type: boolean + regex: + type: string + suffix: + type: string + type: object + methods: + description: methods is the list of HTTP methods. If no + methods are specified, this rule will apply to all methods. + items: + type: string + type: array + pathExact: + type: string + pathPrefix: + type: string + pathRegex: + type: string + portNames: + items: + type: string + type: array + type: object + type: array + sources: + description: sources is a list of sources in this traffic permission. + items: + description: Source represents the source identity. To specify + any of the wildcard sources, the specific fields need to + be omitted. For example, for a wildcard namespace, identityName + should be omitted. + properties: + exclude: + description: exclude is a list of sources to exclude from + this source. + items: + description: ExcludeSource is almost the same as source + but it prevents the addition of matchiing sources. + properties: + identityName: + type: string + namespace: + type: string + partition: + type: string + peer: + type: string + samenessGroup: + type: string + type: object + type: array + identityName: + type: string + namespace: + type: string + partition: + type: string + peer: + type: string + samenessGroup: + type: string + type: object + type: array + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/control-plane/PROJECT b/control-plane/PROJECT index 7726ec3e4b..b77d2be9a9 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -142,4 +142,13 @@ resources: kind: GatewayPolicy path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: consul.hashicorp.com + group: auth + kind: TrafficPermissions + path: github.com/hashicorp/consul-k8s/control-plane/api/v2beta1 + version: v2beta1 version: "3" diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 6d9c636e33..8d05e8e46b 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -4,7 +4,10 @@ // Package common holds code that isn't tied to a particular CRD version or type. package common +import mapset "github.com/deckarep/golang-set" + const ( + // V1 config entries. ServiceDefaults string = "servicedefaults" ProxyDefaults string = "proxydefaults" ServiceResolver string = "serviceresolver" @@ -20,6 +23,9 @@ const ( RouteAuthFilter string = "routeauthfilter" GatewayPolicy string = "gatewaypolicy" + // V2 config entries. + TrafficPermissions string = "trafficpermissions" + Global string = "global" Mesh string = "mesh" DefaultConsulNamespace string = "default" @@ -32,3 +38,33 @@ const ( MigrateEntryTrue string = "true" SourceValue string = "kubernetes" ) + +// ConsulTenancyConfig manages settings related to Consul namespaces and partitions. +type ConsulTenancyConfig struct { + // EnableConsulPartitions indicates that a user is running Consul Enterprise. + EnableConsulPartitions bool + // ConsulPartition is the Consul Partition to which this controller belongs. + ConsulPartition string + // EnableConsulNamespaces indicates that a user is running Consul Enterprise. + EnableConsulNamespaces bool + // ConsulDestinationNamespace is the name of the Consul namespace to create + // all resources in. If EnableNSMirroring is true this is ignored. + ConsulDestinationNamespace string + // EnableNSMirroring causes Consul namespaces to be created to match the + // k8s namespace of any config entry custom resource. Resources will + // be created in the matching Consul namespace. + EnableNSMirroring bool + // NSMirroringPrefix is an optional prefix that can be added to the Consul + // namespaces created while mirroring. For example, if it is set to "k8s-", + // then the k8s `default` namespace will be mirrored in Consul's + // `k8s-default` namespace. + NSMirroringPrefix string +} + +// K8sNamespaceConfig manages allow/deny Kubernetes namespaces. +type K8sNamespaceConfig struct { + // Only endpoints in the AllowK8sNamespacesSet are reconciled. + AllowK8sNamespacesSet mapset.Set + // Endpoints in the DenyK8sNamespacesSet are ignored. + DenyK8sNamespacesSet mapset.Set +} diff --git a/control-plane/api/common/meshconfig.go b/control-plane/api/common/meshconfig.go new file mode 100644 index 0000000000..139818d307 --- /dev/null +++ b/control-plane/api/common/meshconfig.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "github.com/hashicorp/consul/proto-public/pbresource" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type MeshConfig interface { + ResourceID(namespace, partition string) *pbresource.ID + Resource(namespace, partition string) *pbresource.Resource + + // GetObjectKind should be implemented by the generated code. + GetObjectKind() schema.ObjectKind + // DeepCopyObject should be implemented by the generated code. + DeepCopyObject() runtime.Object + + // AddFinalizer adds a finalizer to the list of finalizers. + AddFinalizer(name string) + // RemoveFinalizer removes this finalizer from the list. + RemoveFinalizer(name string) + // Finalizers returns the list of finalizers for this object. + Finalizers() []string + + // MatchesConsul returns true if the resource has the same fields as the Consul + // config entry. + MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool + + // KubeKind returns the Kube config entry kind, i.e. servicedefaults, not + // service-defaults. + KubeKind() string + // KubernetesName returns the name of the Kubernetes resource. + KubernetesName() string + + // SetSyncedCondition updates the synced condition. + SetSyncedCondition(status corev1.ConditionStatus, reason, message string) + // SetLastSyncedTime updates the last synced time. + SetLastSyncedTime(time *metav1.Time) + // SyncedCondition gets the synced condition. + SyncedCondition() (status corev1.ConditionStatus, reason, message string) + // SyncedConditionStatus returns the status of the synced condition. + SyncedConditionStatus() corev1.ConditionStatus + + // Validate returns an error if the resource is invalid. + Validate(tenancy ConsulTenancyConfig) error + + // DefaultNamespaceFields sets Consul namespace fields on the resource + // spec to their default values if namespaces are enabled. + DefaultNamespaceFields(tenancy ConsulTenancyConfig) + + // Object is required so that MeshConfig implements metav1.Object, which is + // the interface supported by controller-runtime reconcile-able resources. + metav1.Object +} diff --git a/control-plane/api/common/meshconfig_webhook.go b/control-plane/api/common/meshconfig_webhook.go new file mode 100644 index 0000000000..004b47a589 --- /dev/null +++ b/control-plane/api/common/meshconfig_webhook.go @@ -0,0 +1,87 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/go-logr/logr" + "gomodules.xyz/jsonpatch/v2" + admissionv1 "k8s.io/api/admission/v1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// MeshConfigLister is implemented by CRD-specific webhooks. +type MeshConfigLister interface { + // List returns all resources of this type across all namespaces in a + // Kubernetes cluster. + List(ctx context.Context) ([]MeshConfig, error) +} + +// ValidateMeshConfig validates a MeshConfig. It is a generic method that +// can be used by all CRD-specific validators. +// Callers should pass themselves as validator and kind should be the custom +// resource name, e.g. "TrafficPermissions". +func ValidateMeshConfig( + ctx context.Context, + req admission.Request, + logger logr.Logger, + meshConfigLister MeshConfigLister, + meshConfig MeshConfig, + tenancy ConsulTenancyConfig) admission.Response { + + defaultingPatches, err := MeshConfigDefaultingPatches(meshConfig, tenancy) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + // On create we need to validate that there isn't already a resource with + // the same name in a different namespace if we're mapping all Kube + // resources to a single Consul namespace. The only case where we're not + // mapping all kube resources to a single Consul namespace is when we + // are running Consul enterprise with namespace mirroring. + singleConsulDestNS := !(tenancy.EnableConsulNamespaces && tenancy.EnableNSMirroring) + if req.Operation == admissionv1.Create && singleConsulDestNS { + logger.Info("validate create", "name", meshConfig.KubernetesName()) + + list, err := meshConfigLister.List(ctx) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + for _, item := range list { + if item.KubernetesName() == meshConfig.KubernetesName() { + return admission.Errored(http.StatusBadRequest, + fmt.Errorf("%s resource with name %q is already defined – all %s resources must have unique names across namespaces", + meshConfig.KubeKind(), + meshConfig.KubernetesName(), + meshConfig.KubeKind())) + } + } + } + if err := meshConfig.Validate(tenancy); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + return admission.Patched(fmt.Sprintf("valid %s request", meshConfig.KubeKind()), defaultingPatches...) +} + +// MeshConfigDefaultingPatches returns the patches needed to set fields to their defaults. +func MeshConfigDefaultingPatches(meshConfig MeshConfig, tenancy ConsulTenancyConfig) ([]jsonpatch.Operation, error) { + beforeDefaulting, err := json.Marshal(meshConfig) + if err != nil { + return nil, fmt.Errorf("marshalling input: %s", err) + } + meshConfig.DefaultNamespaceFields(tenancy) + afterDefaulting, err := json.Marshal(meshConfig) + if err != nil { + return nil, fmt.Errorf("marshalling after defaulting: %s", err) + } + + defaultingPatches, err := jsonpatch.CreatePatch(beforeDefaulting, afterDefaulting) + if err != nil { + return nil, fmt.Errorf("creating patches: %s", err) + } + return defaultingPatches, nil +} diff --git a/control-plane/api/common/meshconfig_webhook_test.go b/control-plane/api/common/meshconfig_webhook_test.go new file mode 100644 index 0000000000..1da2c143dd --- /dev/null +++ b/control-plane/api/common/meshconfig_webhook_test.go @@ -0,0 +1,333 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "context" + "encoding/json" + "errors" + "testing" + + logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/stretchr/testify/require" + "gomodules.xyz/jsonpatch/v2" + admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +func TestValidateMeshConfig(t *testing.T) { + otherNS := "other" + + cases := map[string]struct { + existingResources []MeshConfig + newResource MeshConfig + enableNamespaces bool + nsMirroring bool + consulDestinationNS string + nsMirroringPrefix string + expAllow bool + expErrMessage string + }{ + "no duplicates, valid": { + existingResources: nil, + newResource: &mockMeshConfig{ + MockName: "foo", + MockNamespace: otherNS, + Valid: true, + }, + expAllow: true, + }, + "no duplicates, invalid": { + existingResources: nil, + newResource: &mockMeshConfig{ + MockName: "foo", + MockNamespace: otherNS, + Valid: false, + }, + expAllow: false, + expErrMessage: "invalid", + }, + "duplicate name": { + existingResources: []MeshConfig{&mockMeshConfig{ + MockName: "foo", + MockNamespace: "default", + }}, + newResource: &mockMeshConfig{ + MockName: "foo", + MockNamespace: otherNS, + Valid: true, + }, + expAllow: false, + expErrMessage: "mockkind resource with name \"foo\" is already defined – all mockkind resources must have unique names across namespaces", + }, + "duplicate name, namespaces enabled": { + existingResources: []MeshConfig{&mockMeshConfig{ + MockName: "foo", + MockNamespace: "default", + }}, + newResource: &mockMeshConfig{ + MockName: "foo", + MockNamespace: otherNS, + Valid: true, + }, + enableNamespaces: true, + expAllow: false, + expErrMessage: "mockkind resource with name \"foo\" is already defined – all mockkind resources must have unique names across namespaces", + }, + "duplicate name, namespaces enabled, mirroring enabled": { + existingResources: []MeshConfig{&mockMeshConfig{ + MockName: "foo", + MockNamespace: "default", + }}, + newResource: &mockMeshConfig{ + MockName: "foo", + MockNamespace: otherNS, + Valid: true, + }, + enableNamespaces: true, + nsMirroring: true, + expAllow: true, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + ctx := context.Background() + marshalledRequestObject, err := json.Marshal(c.newResource) + require.NoError(t, err) + + lister := &mockMeshConfigLister{ + Resources: c.existingResources, + } + response := ValidateMeshConfig(ctx, admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Name: c.newResource.KubernetesName(), + Namespace: otherNS, + Operation: admissionv1.Create, + Object: runtime.RawExtension{ + Raw: marshalledRequestObject, + }, + }, + }, + logrtest.New(t), + lister, + c.newResource, + ConsulTenancyConfig{ + EnableConsulNamespaces: c.enableNamespaces, + ConsulDestinationNamespace: c.consulDestinationNS, + EnableNSMirroring: c.nsMirroring, + NSMirroringPrefix: c.nsMirroringPrefix, + }) + require.Equal(t, c.expAllow, response.Allowed) + if c.expErrMessage != "" { + require.Equal(t, c.expErrMessage, response.AdmissionResponse.Result.Message) + } + }) + } +} + +func TestMeshConfigDefaultingPatches(t *testing.T) { + meshConfig := &mockMeshConfig{ + MockName: "test", + Valid: true, + } + + // This test validates that DefaultingPatches invokes DefaultNamespaceFields on the Config Entry. + patches, err := MeshConfigDefaultingPatches(meshConfig, ConsulTenancyConfig{}) + require.NoError(t, err) + + require.Equal(t, []jsonpatch.Operation{ + { + Operation: "replace", + Path: "/MockNamespace", + Value: "bar", + }, + }, patches) +} + +type mockMeshConfigLister struct { + Resources []MeshConfig +} + +var _ MeshConfigLister = &mockMeshConfigLister{} + +func (in *mockMeshConfigLister) List(_ context.Context) ([]MeshConfig, error) { + return in.Resources, nil +} + +type mockMeshConfig struct { + MockName string + MockNamespace string + Valid bool +} + +var _ MeshConfig = &mockMeshConfig{} + +func (in *mockMeshConfig) ResourceID(_, _ string) *pbresource.ID { + return nil +} + +func (in *mockMeshConfig) Resource(_, _ string) *pbresource.Resource { + return nil +} + +func (in *mockMeshConfig) GetNamespace() string { + return in.MockNamespace +} + +func (in *mockMeshConfig) SetNamespace(namespace string) { + in.MockNamespace = namespace +} + +func (in *mockMeshConfig) GetName() string { + return in.MockName +} + +func (in *mockMeshConfig) SetName(name string) { + in.MockName = name +} + +func (in *mockMeshConfig) GetGenerateName() string { + return "" +} + +func (in *mockMeshConfig) SetGenerateName(_ string) {} + +func (in *mockMeshConfig) GetUID() types.UID { + return "" +} + +func (in *mockMeshConfig) SetUID(_ types.UID) {} + +func (in *mockMeshConfig) GetResourceVersion() string { + return "" +} + +func (in *mockMeshConfig) SetResourceVersion(_ string) {} + +func (in *mockMeshConfig) GetGeneration() int64 { + return 0 +} + +func (in *mockMeshConfig) SetGeneration(_ int64) {} + +func (in *mockMeshConfig) GetSelfLink() string { + return "" +} + +func (in *mockMeshConfig) SetSelfLink(_ string) {} + +func (in *mockMeshConfig) GetCreationTimestamp() metav1.Time { + return metav1.Time{} +} + +func (in *mockMeshConfig) SetCreationTimestamp(_ metav1.Time) {} + +func (in *mockMeshConfig) GetDeletionTimestamp() *metav1.Time { + return nil +} + +func (in *mockMeshConfig) SetDeletionTimestamp(_ *metav1.Time) {} + +func (in *mockMeshConfig) GetDeletionGracePeriodSeconds() *int64 { + return nil +} + +func (in *mockMeshConfig) SetDeletionGracePeriodSeconds(_ *int64) {} + +func (in *mockMeshConfig) GetLabels() map[string]string { + return nil +} + +func (in *mockMeshConfig) SetLabels(_ map[string]string) {} + +func (in *mockMeshConfig) GetAnnotations() map[string]string { + return nil +} + +func (in *mockMeshConfig) SetAnnotations(_ map[string]string) {} + +func (in *mockMeshConfig) GetFinalizers() []string { + return nil +} + +func (in *mockMeshConfig) SetFinalizers(_ []string) {} + +func (in *mockMeshConfig) GetOwnerReferences() []metav1.OwnerReference { + return nil +} + +func (in *mockMeshConfig) SetOwnerReferences(_ []metav1.OwnerReference) {} + +func (in *mockMeshConfig) GetClusterName() string { + return "" +} + +func (in *mockMeshConfig) SetClusterName(_ string) {} + +func (in *mockMeshConfig) GetManagedFields() []metav1.ManagedFieldsEntry { + return nil +} + +func (in *mockMeshConfig) SetManagedFields(_ []metav1.ManagedFieldsEntry) {} + +func (in *mockMeshConfig) KubernetesName() string { + return in.MockName +} + +func (in *mockMeshConfig) GetObjectMeta() metav1.ObjectMeta { + return metav1.ObjectMeta{} +} + +func (in *mockMeshConfig) GetObjectKind() schema.ObjectKind { + return schema.EmptyObjectKind +} + +func (in *mockMeshConfig) DeepCopyObject() runtime.Object { + return in +} + +func (in *mockMeshConfig) AddFinalizer(_ string) {} + +func (in *mockMeshConfig) RemoveFinalizer(_ string) {} + +func (in *mockMeshConfig) Finalizers() []string { + return nil +} + +func (in *mockMeshConfig) KubeKind() string { + return "mockkind" +} + +func (in *mockMeshConfig) SetSyncedCondition(_ corev1.ConditionStatus, _ string, _ string) {} + +func (in *mockMeshConfig) SetLastSyncedTime(_ *metav1.Time) {} + +func (in *mockMeshConfig) SyncedCondition() (status corev1.ConditionStatus, reason string, message string) { + return corev1.ConditionTrue, "", "" +} + +func (in *mockMeshConfig) SyncedConditionStatus() corev1.ConditionStatus { + return corev1.ConditionTrue +} + +func (in *mockMeshConfig) Validate(_ ConsulTenancyConfig) error { + if !in.Valid { + return errors.New("invalid") + } + return nil +} + +func (in *mockMeshConfig) DefaultNamespaceFields(_ ConsulTenancyConfig) { + in.MockNamespace = "bar" +} + +func (in *mockMeshConfig) MatchesConsul(_ *pbresource.Resource, _, _ string) bool { + return false +} diff --git a/control-plane/api/v2beta1/groupversion_info.go b/control-plane/api/v2beta1/groupversion_info.go new file mode 100644 index 0000000000..35e6d2056b --- /dev/null +++ b/control-plane/api/v2beta1/groupversion_info.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package v2beta1 contains API Schema definitions for the consul.hashicorp.com v2beta1 API group +// +kubebuilder:object:generate=true +// +groupName=auth.consul.hashicorp.com +package v2beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + + // AUTH group. + + AuthGroup = "auth.consul.hashicorp.com" + + // AuthGroupVersion is group version used to register these objects. + AuthGroupVersion = schema.GroupVersion{Group: AuthGroup, Version: "v2beta1"} + + // AuthSchemeBuilder is used to add go types to the GroupVersionKind scheme. + AuthSchemeBuilder = &scheme.Builder{GroupVersion: AuthGroupVersion} + + // AddAuthToScheme adds the types in this group-version to the given scheme. + AddAuthToScheme = AuthSchemeBuilder.AddToScheme +) diff --git a/control-plane/api/v2beta1/shared_types.go b/control-plane/api/v2beta1/shared_types.go new file mode 100644 index 0000000000..a5225afb71 --- /dev/null +++ b/control-plane/api/v2beta1/shared_types.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +func meshConfigMeta() map[string]string { + return map[string]string{ + common.SourceKey: common.SourceValue, + } +} diff --git a/control-plane/api/v2beta1/status.go b/control-plane/api/v2beta1/status.go new file mode 100644 index 0000000000..cc75a1cd82 --- /dev/null +++ b/control-plane/api/v2beta1/status.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Conditions is the schema for the conditions portion of the payload. +type Conditions []Condition + +// ConditionType is a camel-cased condition type. +type ConditionType string + +const ( + // ConditionSynced specifies that the resource has been synced with Consul. + ConditionSynced ConditionType = "Synced" +) + +// Conditions define a readiness condition for a Consul resource. +// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Condition struct { + // Type of condition. + // +required + Type ConditionType `json:"type" description:"type of status condition"` + + // Status of the condition, one of True, False, Unknown. + // +required + Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` + + // LastTransitionTime is the last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"` + + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` + + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` +} + +// IsTrue is true if the condition is True. +func (c *Condition) IsTrue() bool { + if c == nil { + return false + } + return c.Status == corev1.ConditionTrue +} + +// IsFalse is true if the condition is False. +func (c *Condition) IsFalse() bool { + if c == nil { + return false + } + return c.Status == corev1.ConditionFalse +} + +// IsUnknown is true if the condition is Unknown. +func (c *Condition) IsUnknown() bool { + if c == nil { + return true + } + return c.Status == corev1.ConditionUnknown +} + +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Status struct { + // Conditions indicate the latest available observations of a resource's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // LastSyncedTime is the last time the resource successfully synced with Consul. + // +optional + LastSyncedTime *metav1.Time `json:"lastSyncedTime,omitempty" description:"last time the condition transitioned from one status to another"` +} + +func (s *Status) GetCondition(t ConditionType) *Condition { + for _, cond := range s.Conditions { + if cond.Type == t { + return &cond + } + } + return nil +} diff --git a/control-plane/api/v2beta1/traffic_permissions_types.go b/control-plane/api/v2beta1/traffic_permissions_types.go new file mode 100644 index 0000000000..533b5d0e33 --- /dev/null +++ b/control-plane/api/v2beta1/traffic_permissions_types.go @@ -0,0 +1,427 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + trafficpermissionsKubeKind = "trafficpermissions" +) + +func init() { + AuthSchemeBuilder.Register(&TrafficPermissions{}, &TrafficPermissionsList{}) +} + +var _ common.MeshConfig = &TrafficPermissions{} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// TrafficPermissions is the Schema for the traffic-permissions API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="traffic-permissions" +type TrafficPermissions struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TrafficPermissionsSpec `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// TrafficPermissionsList contains a list of TrafficPermissions. +type TrafficPermissionsList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TrafficPermissions `json:"items"` +} + +// TrafficPermissionsSpec defines the desired state of TrafficPermissions. +type TrafficPermissionsSpec struct { + // Destination is a configuration of the destination proxies + // where these traffic permissions should apply. + Destination *Destination `json:"destination,omitempty"` + // Action can be either allow or deny for the entire object. It will default to allow. + // + // If action is allow, + // we will allow the connection if one of the rules in Rules matches, in other words, we will deny + // all requests except for the ones that match Rules. If Consul is in default allow mode, then allow + // actions have no effect without a deny permission as everything is allowed by default. + // + // If action is deny, + // we will deny the connection if one of the rules in Rules match, in other words, + // we will allow all requests except for the ones that match Rules. If Consul is default deny mode, + // then deny permissions have no effect without an allow permission as everything is denied by default. + // + // Action unspecified is reserved for compatibility with the addition of future actions. + Action IntentionAction `json:"action,omitempty"` + // Permissions is a list of permissions to match on. + // They are applied using OR semantics. + Permissions Permissions `json:"permissions,omitempty"` +} + +type Destination struct { + // Name is the destination of all intentions defined in this config entry. + // This may be set to the wildcard character (*) to match + // all services that don't otherwise have intentions defined. + IdentityName string `json:"identityName,omitempty"` +} + +func (in *Destination) validate(path *field.Path) *field.Error { + if in == nil { + return field.Required(path, `destination and destination.identityName are required`) + } + if in.IdentityName == "" { + return field.Required(path.Child("identityName"), `identityName is required`) + } + return nil +} + +// IntentionAction is the action that the intention represents. This +// can be "allow" or "deny" to allowlist or denylist intentions. +type IntentionAction string + +const ( + ActionDeny IntentionAction = "deny" + ActionAllow IntentionAction = "allow" + ActionUnspecified IntentionAction = "" +) + +func (in IntentionAction) validate(path *field.Path) *field.Error { + switch in { + case ActionDeny, ActionAllow: + return nil + default: + return field.Invalid(path.Child("action"), in, "must be one of \"allow\" or \"deny\"") + } +} + +type Permissions []*Permission + +type Permission struct { + // sources is a list of sources in this traffic permission. + Sources Sources `json:"sources,omitempty"` + // destinationRules is a list of rules to apply for matching sources in this Permission. + // These rules are specific to the request or connection that is going to the destination(s) + // selected by the TrafficPermissions resource. + DestinationRules DestinationRules `json:"destinationRules,omitempty"` +} + +type Sources []*Source + +type DestinationRules []*DestinationRule + +// Source represents the source identity. +// To specify any of the wildcard sources, the specific fields need to be omitted. +// For example, for a wildcard namespace, identityName should be omitted. +type Source struct { + IdentityName string `json:"identityName,omitempty"` + Namespace string `json:"namespace,omitempty"` + Partition string `json:"partition,omitempty"` + Peer string `json:"peer,omitempty"` + SamenessGroup string `json:"samenessGroup,omitempty"` + // exclude is a list of sources to exclude from this source. + Exclude Exclude `json:"exclude,omitempty"` +} + +// DestinationRule contains rules to apply to the incoming connection. +type DestinationRule struct { + PathExact string `json:"pathExact,omitempty"` + PathPrefix string `json:"pathPrefix,omitempty"` + PathRegex string `json:"pathRegex,omitempty"` + // methods is the list of HTTP methods. If no methods are specified, + // this rule will apply to all methods. + Methods []string `json:"methods,omitempty"` + Header *DestinationRuleHeader `json:"header,omitempty"` + PortNames []string `json:"portNames,omitempty"` + // exclude contains a list of rules to exclude when evaluating rules for the incoming connection. + Exclude ExcludePermissions `json:"exclude,omitempty"` +} + +type Exclude []*ExcludeSource + +// ExcludeSource is almost the same as source but it prevents the addition of +// matchiing sources. +type ExcludeSource struct { + IdentityName string `json:"identityName,omitempty"` + Namespace string `json:"namespace,omitempty"` + Partition string `json:"partition,omitempty"` + Peer string `json:"peer,omitempty"` + SamenessGroup string `json:"samenessGroup,omitempty"` +} + +type DestinationRuleHeader struct { + Name string `json:"name,omitempty"` + Present bool `json:"present,omitempty"` + Exact string `json:"exact,omitempty"` + Prefix string `json:"prefix,omitempty"` + Suffix string `json:"suffix,omitempty"` + Regex string `json:"regex,omitempty"` + Invert bool `json:"invert,omitempty"` +} + +type ExcludePermissions []*ExcludePermissionRule + +type ExcludePermissionRule struct { + PathExact string `json:"pathExact,omitempty"` + PathPrefix string `json:"pathPrefix,omitempty"` + PathRegex string `json:"pathRegex,omitempty"` + // methods is the list of HTTP methods. + Methods []string `json:"methods,omitempty"` + Header *DestinationRuleHeader `json:"header,omitempty"` + // portNames is a list of workload ports to apply this rule to. The ports specified here + // must be the ports used in the connection. + PortNames []string `json:"portNames,omitempty"` +} + +func (in *TrafficPermissions) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbauth.TrafficPermissionsType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *TrafficPermissions) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&pbauth.TrafficPermissions{ + Destination: in.Spec.Destination.toProto(), + Action: in.Spec.Action.toProto(), + Permissions: in.Spec.Permissions.toProto(), + }), + Metadata: meshConfigMeta(), + } +} + +func (in *TrafficPermissions) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *TrafficPermissions) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *TrafficPermissions) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *TrafficPermissions) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *TrafficPermissions) KubeKind() string { + return trafficpermissionsKubeKind +} + +func (in *TrafficPermissions) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *TrafficPermissions) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *TrafficPermissions) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *TrafficPermissions) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *TrafficPermissions) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *TrafficPermissions) Validate(_ common.ConsulTenancyConfig) error { + var errs field.ErrorList + path := field.NewPath("spec") + + if in.Spec.Action == ActionUnspecified { + errs = append(errs, field.Required(path.Child("action"), `action is required`)) + } + if err := in.Spec.Action.validate(path); err != nil { + errs = append(errs, err) + } + + // Validate Destinations + if err := in.Spec.Destination.validate(path.Child("destination")); err != nil { + errs = append(errs, err) + } + + // TODO: add validation for permissions + // Validate permissions in Consul: + // https://github.com/hashicorp/consul/blob/203a36821ef6182b2d2b30c1012ca5a42c7dd8f3/internal/auth/internal/types/traffic_permissions.go#L59-L141 + + if len(errs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: AuthGroup, Kind: common.TrafficPermissions}, + in.KubernetesName(), errs) + } + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *TrafficPermissions) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} + +func (p Permissions) toProto() []*pbauth.Permission { + var perms []*pbauth.Permission + for _, permission := range p { + perms = append(perms, &pbauth.Permission{ + Sources: permission.Sources.toProto(), + DestinationRules: permission.DestinationRules.toProto(), + }) + } + return perms +} + +func (s Sources) toProto() []*pbauth.Source { + var srcs []*pbauth.Source + for _, source := range s { + srcs = append(srcs, &pbauth.Source{ + IdentityName: source.IdentityName, + Namespace: source.Namespace, + Partition: source.Partition, + Peer: source.Peer, + SamenessGroup: source.SamenessGroup, + Exclude: source.Exclude.toProto(), + }) + } + return srcs +} + +func (r DestinationRules) toProto() []*pbauth.DestinationRule { + var dstnRules []*pbauth.DestinationRule + for _, rule := range r { + dstnRules = append(dstnRules, &pbauth.DestinationRule{ + PathExact: rule.PathExact, + PathPrefix: rule.PathPrefix, + PathRegex: rule.PathRegex, + Methods: rule.Methods, + Header: rule.Header.toProto(), + PortNames: rule.PortNames, + Exclude: rule.Exclude.toProto(), + }) + } + return dstnRules +} + +func (e Exclude) toProto() []*pbauth.ExcludeSource { + var exSrcs []*pbauth.ExcludeSource + for _, source := range e { + exSrcs = append(exSrcs, &pbauth.ExcludeSource{ + IdentityName: source.IdentityName, + Namespace: source.Namespace, + Partition: source.Partition, + Peer: source.Peer, + SamenessGroup: source.SamenessGroup, + }) + } + return exSrcs +} + +func (p ExcludePermissions) toProto() []*pbauth.ExcludePermissionRule { + var exclPerms []*pbauth.ExcludePermissionRule + for _, rule := range p { + exclPerms = append(exclPerms, &pbauth.ExcludePermissionRule{ + PathExact: rule.PathExact, + PathPrefix: rule.PathPrefix, + PathRegex: rule.PathRegex, + Methods: rule.Methods, + Header: rule.Header.toProto(), + PortNames: rule.PortNames, + }) + } + return exclPerms +} + +func (h *DestinationRuleHeader) toProto() *pbauth.DestinationRuleHeader { + if h == nil { + return nil + } + return &pbauth.DestinationRuleHeader{ + Name: h.Name, + Present: h.Present, + Exact: h.Exact, + Prefix: h.Prefix, + Suffix: h.Suffix, + Regex: h.Regex, + Invert: h.Invert, + } +} + +func (in *Destination) toProto() *pbauth.Destination { + if in == nil { + return nil + } + return &pbauth.Destination{ + IdentityName: in.IdentityName, + } +} + +func (in IntentionAction) toProto() pbauth.Action { + if in == ActionAllow { + return pbauth.Action_ACTION_ALLOW + } else if in == ActionDeny { + return pbauth.Action_ACTION_DENY + } + return pbauth.Action_ACTION_UNSPECIFIED +} diff --git a/control-plane/api/v2beta1/traffic_permissions_types_test.go b/control-plane/api/v2beta1/traffic_permissions_types_test.go new file mode 100644 index 0000000000..f48740e496 --- /dev/null +++ b/control-plane/api/v2beta1/traffic_permissions_types_test.go @@ -0,0 +1,803 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +func TestTrafficPermissions_MatchesConsul(t *testing.T) { + cases := map[string]struct { + OurConsulNamespace string + OurConsulPartition string + OurData TrafficPermissions + + TheirName string + TheirConsulNamespace string + TheirConsulPartition string + TheirData *pbauth.TrafficPermissions + ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match + + Matches bool + }{ + "empty fields matches": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: TrafficPermissionsSpec{}, + }, + TheirName: "name", + TheirConsulNamespace: constants.DefaultConsulNS, + TheirConsulPartition: constants.DefaultConsulPartition, + TheirData: &pbauth.TrafficPermissions{ + Destination: nil, + Action: pbauth.Action_ACTION_UNSPECIFIED, + Permissions: nil, + }, + Matches: true, + }, + "source namespaces and partitions are compared": { + OurConsulNamespace: "consul-ns", + OurConsulPartition: "consul-partition", + OurData: TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: TrafficPermissionsSpec{ + Destination: &Destination{ + IdentityName: "destination-identity", + }, + Action: ActionAllow, + Permissions: Permissions{ + { + Sources: []*Source{ + { + IdentityName: "source-identity", + Namespace: "the space namespace space", + }, + }, + }, + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "source-identity", + Namespace: "not space namespace", + }, + }, + }, + }, + }, + Matches: false, + }, + "destination namespaces and partitions are compared": { + OurConsulNamespace: "not-consul-ns", + OurConsulPartition: "not-consul-partition", + OurData: TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: TrafficPermissionsSpec{ + Destination: &Destination{ + IdentityName: "destination-identity", + }, + Action: ActionAllow, + Permissions: Permissions{ + { + Sources: []*Source{ + { + IdentityName: "source-identity", + }, + }, + }, + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "source-identity", + }, + }, + }, + }, + }, + Matches: false, + }, + "all fields set matches": { + OurConsulNamespace: "consul-ns", + OurConsulPartition: "consul-partition", + OurData: TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: TrafficPermissionsSpec{ + Destination: &Destination{ + IdentityName: "destination-identity", + }, + Action: ActionAllow, + Permissions: Permissions{ + { + Sources: []*Source{ + { + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + Exclude: Exclude{ + { + IdentityName: "not-source-identity", + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + }, + }, + }, + { + IdentityName: "source-identity", + }, + }, + DestinationRules: DestinationRules{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &DestinationRuleHeader{ + Name: "x-consul-test", + Present: true, + Exact: "true", + Prefix: "prefix", + Suffix: "suffix", + Regex: "reg.*ex", + Invert: true, + }, + Methods: []string{"GET", "POST"}, + Exclude: ExcludePermissions{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &DestinationRuleHeader{ + Name: "x-consul-not-test", + Present: true, + Exact: "false", + Prefix: "~prefix", + Suffix: "~suffix", + Regex: "~reg.*ex", + Invert: true, + }, + Methods: []string{"DELETE"}, + PortNames: []string{"log"}, + }, + }, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + // These are intentionally in a different order to show that it doesn't matter + { + IdentityName: "source-identity", + }, + { + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + Exclude: []*pbauth.ExcludeSource{ + { + IdentityName: "not-source-identity", + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + }, + }, + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &pbauth.DestinationRuleHeader{ + Name: "x-consul-test", + Present: true, + Exact: "true", + Prefix: "prefix", + Suffix: "suffix", + Regex: "reg.*ex", + Invert: true, + }, + Methods: []string{"GET", "POST"}, + Exclude: []*pbauth.ExcludePermissionRule{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &pbauth.DestinationRuleHeader{ + Name: "x-consul-not-test", + Present: true, + Exact: "false", + Prefix: "~prefix", + Suffix: "~suffix", + Regex: "~reg.*ex", + Invert: true, + }, + Methods: []string{"DELETE"}, + PortNames: []string{"log"}, + }, + }, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + Matches: true, + }, + "different types does not match": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: TrafficPermissionsSpec{}, + }, + ResourceOverride: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "name", + Type: pbmesh.ProxyConfigurationType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.DefaultConsulNS, + Namespace: constants.DefaultConsulPartition, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + }, + Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), + Metadata: meshConfigMeta(), + }, + Matches: false, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + consulResource := c.ResourceOverride + if c.TheirName != "" { + consulResource = constructTrafficPermissionResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) + } + require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) + }) + } +} + +// TestTrafficPermissions_Resource also includes test to verify ResourceID(). +func TestTrafficPermissions_Resource(t *testing.T) { + cases := map[string]struct { + Ours TrafficPermissions + ConsulNamespace string + ConsulPartition string + ExpectedName string + ExpectedData *pbauth.TrafficPermissions + }{ + "empty fields": { + Ours: TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: TrafficPermissionsSpec{}, + }, + ConsulNamespace: constants.DefaultConsulNS, + ConsulPartition: constants.DefaultConsulPartition, + ExpectedName: "foo", + ExpectedData: &pbauth.TrafficPermissions{}, + }, + "every field set": { + Ours: TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: TrafficPermissionsSpec{ + Destination: &Destination{ + IdentityName: "destination-identity", + }, + Action: ActionAllow, + Permissions: Permissions{ + { + Sources: []*Source{ + { + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + Exclude: Exclude{ + { + IdentityName: "not-source-identity", + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + }, + }, + }, + { + IdentityName: "source-identity", + }, + }, + DestinationRules: DestinationRules{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &DestinationRuleHeader{ + Name: "x-consul-test", + Present: true, + Exact: "true", + Prefix: "prefix", + Suffix: "suffix", + Regex: "reg.*ex", + Invert: true, + }, + Methods: []string{"GET", "POST"}, + Exclude: ExcludePermissions{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &DestinationRuleHeader{ + Name: "x-consul-not-test", + Present: true, + Exact: "false", + Prefix: "~prefix", + Suffix: "~suffix", + Regex: "~reg.*ex", + Invert: true, + }, + Methods: []string{"DELETE"}, + PortNames: []string{"log"}, + }, + }, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + }, + ConsulNamespace: "not-default-namespace", + ConsulPartition: "not-default-partition", + ExpectedName: "foo", + ExpectedData: &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + // These are intentionally in a different order to show that it doesn't matter + { + IdentityName: "source-identity", + }, + { + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + Exclude: []*pbauth.ExcludeSource{ + { + IdentityName: "not-source-identity", + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + }, + }, + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &pbauth.DestinationRuleHeader{ + Name: "x-consul-test", + Present: true, + Exact: "true", + Prefix: "prefix", + Suffix: "suffix", + Regex: "reg.*ex", + Invert: true, + }, + Methods: []string{"GET", "POST"}, + Exclude: []*pbauth.ExcludePermissionRule{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &pbauth.DestinationRuleHeader{ + Name: "x-consul-not-test", + Present: true, + Exact: "false", + Prefix: "~prefix", + Suffix: "~suffix", + Regex: "~reg.*ex", + Invert: true, + }, + Methods: []string{"DELETE"}, + PortNames: []string{"log"}, + }, + }, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) + expected := constructTrafficPermissionResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) + + opts := append([]cmp.Option{ + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + }, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(expected, actual, opts...) + require.Equal(t, "", diff, "TrafficPermissions do not match") + }) + } +} + +func TestTrafficPermissions_SetSyncedCondition(t *testing.T) { + trafficPermissions := &TrafficPermissions{} + trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") + + require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) + require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) + require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) + now := metav1.Now() + require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) +} + +func TestTrafficPermissions_SetLastSyncedTime(t *testing.T) { + trafficPermissions := &TrafficPermissions{} + syncedTime := metav1.NewTime(time.Now()) + trafficPermissions.SetLastSyncedTime(&syncedTime) + + require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) +} + +func TestTrafficPermissions_GetSyncedConditionStatus(t *testing.T) { + cases := []corev1.ConditionStatus{ + corev1.ConditionUnknown, + corev1.ConditionFalse, + corev1.ConditionTrue, + } + for _, status := range cases { + t.Run(string(status), func(t *testing.T) { + trafficPermissions := &TrafficPermissions{ + Status: Status{ + Conditions: []Condition{{ + Type: ConditionSynced, + Status: status, + }}, + }, + } + + require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) + }) + } +} + +func TestTrafficPermissions_GetConditionWhenStatusNil(t *testing.T) { + require.Nil(t, (&TrafficPermissions{}).GetCondition(ConditionSynced)) +} + +func TestTrafficPermissions_SyncedConditionStatusWhenStatusNil(t *testing.T) { + require.Equal(t, corev1.ConditionUnknown, (&TrafficPermissions{}).SyncedConditionStatus()) +} + +func TestTrafficPermissions_SyncedConditionWhenStatusNil(t *testing.T) { + status, reason, message := (&TrafficPermissions{}).SyncedCondition() + require.Equal(t, corev1.ConditionUnknown, status) + require.Equal(t, "", reason) + require.Equal(t, "", message) +} + +func TestTrafficPermissions_KubeKind(t *testing.T) { + require.Equal(t, "trafficpermissions", (&TrafficPermissions{}).KubeKind()) +} + +func TestTrafficPermissions_KubernetesName(t *testing.T) { + require.Equal(t, "test", (&TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "bar", + }, + Spec: TrafficPermissionsSpec{ + Destination: &Destination{ + IdentityName: "foo", + }, + }, + }).KubernetesName()) +} + +func TestTrafficPermissions_ObjectMeta(t *testing.T) { + meta := metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + } + trafficPermissions := &TrafficPermissions{ + ObjectMeta: meta, + } + require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) +} + +// Test defaulting behavior when namespaces are enabled as well as disabled. +// TODO: add when implemented +//func TestTrafficPermissions_DefaultNamespaceFields(t *testing.T) + +func TestTrafficPermissions_Validate(t *testing.T) { + cases := []struct { + name string + input *TrafficPermissions + expectedErrMsgs []string + }{ + { + name: "kitchen sink OK", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: TrafficPermissionsSpec{ + Destination: &Destination{ + IdentityName: "destination-identity", + }, + Action: ActionAllow, + Permissions: Permissions{ + { + Sources: []*Source{ + { + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + Exclude: Exclude{ + { + IdentityName: "not-source-identity", + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + SamenessGroup: "space-group", + }, + }, + }, + { + IdentityName: "source-identity", + }, + }, + DestinationRules: DestinationRules{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &DestinationRuleHeader{ + Name: "x-consul-test", + Present: true, + Exact: "true", + Prefix: "prefix", + Suffix: "suffix", + Regex: "reg.*ex", + Invert: true, + }, + Methods: []string{"GET", "POST"}, + Exclude: ExcludePermissions{ + { + PathExact: "/hello", + PathPrefix: "/world", + PathRegex: "/.*/foo", + Header: &DestinationRuleHeader{ + Name: "x-consul-not-test", + Present: true, + Exact: "false", + Prefix: "~prefix", + Suffix: "~suffix", + Regex: "~reg.*ex", + Invert: true, + }, + Methods: []string{"DELETE"}, + PortNames: []string{"log"}, + }, + }, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: nil, + }, + { + name: "must have an action", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: TrafficPermissionsSpec{ + Destination: &Destination{ + IdentityName: "dest-service", + }, + }, + }, + expectedErrMsgs: []string{ + "spec.action: Required value: action is required", + "spec.action: Invalid value: \"\": must be one of \"allow\" or \"deny\"", + }, + }, + { + name: "action must be valid", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: TrafficPermissionsSpec{ + Destination: &Destination{ + IdentityName: "dest-service", + }, + Action: "blurg", + }, + }, + expectedErrMsgs: []string{ + "spec.action: Invalid value: \"blurg\": must be one of \"allow\" or \"deny\"", + }, + }, + { + name: "destination is required", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: TrafficPermissionsSpec{ + Action: "allow", + }, + }, + expectedErrMsgs: []string{ + "spec.destination: Required value: destination and destination.identityName are required", + }, + }, + { + name: "destination.identityName is required", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: TrafficPermissionsSpec{ + Action: "allow", + Destination: &Destination{}, + }, + }, + expectedErrMsgs: []string{ + "spec.destination.identityName: Required value: identityName is required", + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.input.Validate(common.ConsulTenancyConfig{}) + if len(tc.expectedErrMsgs) != 0 { + require.Error(t, err) + for _, s := range tc.expectedErrMsgs { + require.Contains(t, err.Error(), s) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func constructTrafficPermissionResource(tp *pbauth.TrafficPermissions, name, namespace, partition string) *pbresource.Resource { + data := inject.ToProtoAny(tp) + + id := &pbresource.ID{ + Name: name, + Type: pbauth.TrafficPermissionsType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + Uid: "ABCD", // We add this to show it does not factor into the comparison + } + + return &pbresource.Resource{ + Id: id, + Data: data, + Metadata: meshConfigMeta(), + + // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. + Version: "123456", + Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Status: map[string]*pbresource.Status{ + "knock": { + ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Conditions: make([]*pbresource.Condition, 0), + UpdatedAt: timestamppb.Now(), + }, + }, + } +} diff --git a/control-plane/api/v2beta1/trafficpermissions_webhook.go b/control-plane/api/v2beta1/trafficpermissions_webhook.go new file mode 100644 index 0000000000..a3371468d2 --- /dev/null +++ b/control-plane/api/v2beta1/trafficpermissions_webhook.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "context" + "net/http" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +// +kubebuilder:object:generate=false + +type TrafficPermissionsWebhook struct { + Logger logr.Logger + + // ConsulTenancyConfig contains the injector's namespace and partition configuration. + ConsulTenancyConfig common.ConsulTenancyConfig + + decoder *admission.Decoder + client.Client +} + +var _ common.MeshConfigLister = &TrafficPermissionsWebhook{} + +// NOTE: The path value in the below line is the path to the webhook. +// If it is updated, run code-gen, update subcommand/inject-connect/command.go +// and the consul-helm value for the path to the webhook. +// +// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. +// +// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-trafficpermissions,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=trafficpermissions,versions=v2beta1,name=mutate-trafficpermissions.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *TrafficPermissionsWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var resource TrafficPermissions + err := v.decoder.Decode(req, &resource) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) +} + +func (v *TrafficPermissionsWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { + var resourceList TrafficPermissionsList + if err := v.Client.List(ctx, &resourceList); err != nil { + return nil, err + } + var entries []common.MeshConfig + for _, item := range resourceList.Items { + entries = append(entries, common.MeshConfig(&item)) + } + return entries, nil +} + +func (v *TrafficPermissionsWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} diff --git a/control-plane/api/v2beta1/zz_generated.deepcopy.go b/control-plane/api/v2beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..0be67d0aa2 --- /dev/null +++ b/control-plane/api/v2beta1/zz_generated.deepcopy.go @@ -0,0 +1,467 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v2beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Conditions) DeepCopyInto(out *Conditions) { + { + in := &in + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. +func (in Conditions) DeepCopy() Conditions { + if in == nil { + return nil + } + out := new(Conditions) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Destination) DeepCopyInto(out *Destination) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Destination. +func (in *Destination) DeepCopy() *Destination { + if in == nil { + return nil + } + out := new(Destination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DestinationRule) DeepCopyInto(out *DestinationRule) { + *out = *in + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Header != nil { + in, out := &in.Header, &out.Header + *out = new(DestinationRuleHeader) + **out = **in + } + if in.PortNames != nil { + in, out := &in.PortNames, &out.PortNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Exclude != nil { + in, out := &in.Exclude, &out.Exclude + *out = make(ExcludePermissions, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ExcludePermissionRule) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationRule. +func (in *DestinationRule) DeepCopy() *DestinationRule { + if in == nil { + return nil + } + out := new(DestinationRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DestinationRuleHeader) DeepCopyInto(out *DestinationRuleHeader) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationRuleHeader. +func (in *DestinationRuleHeader) DeepCopy() *DestinationRuleHeader { + if in == nil { + return nil + } + out := new(DestinationRuleHeader) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in DestinationRules) DeepCopyInto(out *DestinationRules) { + { + in := &in + *out = make(DestinationRules, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(DestinationRule) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationRules. +func (in DestinationRules) DeepCopy() DestinationRules { + if in == nil { + return nil + } + out := new(DestinationRules) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Exclude) DeepCopyInto(out *Exclude) { + { + in := &in + *out = make(Exclude, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ExcludeSource) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Exclude. +func (in Exclude) DeepCopy() Exclude { + if in == nil { + return nil + } + out := new(Exclude) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExcludePermissionRule) DeepCopyInto(out *ExcludePermissionRule) { + *out = *in + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Header != nil { + in, out := &in.Header, &out.Header + *out = new(DestinationRuleHeader) + **out = **in + } + if in.PortNames != nil { + in, out := &in.PortNames, &out.PortNames + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExcludePermissionRule. +func (in *ExcludePermissionRule) DeepCopy() *ExcludePermissionRule { + if in == nil { + return nil + } + out := new(ExcludePermissionRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ExcludePermissions) DeepCopyInto(out *ExcludePermissions) { + { + in := &in + *out = make(ExcludePermissions, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ExcludePermissionRule) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExcludePermissions. +func (in ExcludePermissions) DeepCopy() ExcludePermissions { + if in == nil { + return nil + } + out := new(ExcludePermissions) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExcludeSource) DeepCopyInto(out *ExcludeSource) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExcludeSource. +func (in *ExcludeSource) DeepCopy() *ExcludeSource { + if in == nil { + return nil + } + out := new(ExcludeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Permission) DeepCopyInto(out *Permission) { + *out = *in + if in.Sources != nil { + in, out := &in.Sources, &out.Sources + *out = make(Sources, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Source) + (*in).DeepCopyInto(*out) + } + } + } + if in.DestinationRules != nil { + in, out := &in.DestinationRules, &out.DestinationRules + *out = make(DestinationRules, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(DestinationRule) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permission. +func (in *Permission) DeepCopy() *Permission { + if in == nil { + return nil + } + out := new(Permission) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Permissions) DeepCopyInto(out *Permissions) { + { + in := &in + *out = make(Permissions, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Permission) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permissions. +func (in Permissions) DeepCopy() Permissions { + if in == nil { + return nil + } + out := new(Permissions) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Source) DeepCopyInto(out *Source) { + *out = *in + if in.Exclude != nil { + in, out := &in.Exclude, &out.Exclude + *out = make(Exclude, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ExcludeSource) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source. +func (in *Source) DeepCopy() *Source { + if in == nil { + return nil + } + out := new(Source) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Sources) DeepCopyInto(out *Sources) { + { + in := &in + *out = make(Sources, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Source) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sources. +func (in Sources) DeepCopy() Sources { + if in == nil { + return nil + } + out := new(Sources) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Status) DeepCopyInto(out *Status) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.LastSyncedTime != nil { + in, out := &in.LastSyncedTime, &out.LastSyncedTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. +func (in *Status) DeepCopy() *Status { + if in == nil { + return nil + } + out := new(Status) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrafficPermissions) DeepCopyInto(out *TrafficPermissions) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissions. +func (in *TrafficPermissions) DeepCopy() *TrafficPermissions { + if in == nil { + return nil + } + out := new(TrafficPermissions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrafficPermissions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrafficPermissionsList) DeepCopyInto(out *TrafficPermissionsList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TrafficPermissions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissionsList. +func (in *TrafficPermissionsList) DeepCopy() *TrafficPermissionsList { + if in == nil { + return nil + } + out := new(TrafficPermissionsList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrafficPermissionsList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrafficPermissionsSpec) DeepCopyInto(out *TrafficPermissionsSpec) { + *out = *in + if in.Destination != nil { + in, out := &in.Destination, &out.Destination + *out = new(Destination) + **out = **in + } + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = make(Permissions, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Permission) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissionsSpec. +func (in *TrafficPermissionsSpec) DeepCopy() *TrafficPermissionsSpec { + if in == nil { + return nil + } + out := new(TrafficPermissionsSpec) + in.DeepCopyInto(out) + return out +} diff --git a/control-plane/controllers/configentry_controller.go b/control-plane/config-entries/controllers/configentry_controller.go similarity index 99% rename from control-plane/controllers/configentry_controller.go rename to control-plane/config-entries/controllers/configentry_controller.go index 133afc0a1c..f1a4e316e8 100644 --- a/control-plane/controllers/configentry_controller.go +++ b/control-plane/config-entries/controllers/configentry_controller.go @@ -11,9 +11,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" capi "github.com/hashicorp/consul/api" "golang.org/x/time/rate" corev1 "k8s.io/api/core/v1" @@ -25,6 +22,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( diff --git a/control-plane/controllers/configentry_controller_ent_test.go b/control-plane/config-entries/controllers/configentry_controller_ent_test.go similarity index 99% rename from control-plane/controllers/configentry_controller_ent_test.go rename to control-plane/config-entries/controllers/configentry_controller_ent_test.go index 14ae477a56..29c114042f 100644 --- a/control-plane/controllers/configentry_controller_ent_test.go +++ b/control-plane/config-entries/controllers/configentry_controller_ent_test.go @@ -13,10 +13,6 @@ import ( "github.com/go-logr/logr" logrtest "github.com/go-logr/logr/testing" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -27,6 +23,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // NOTE: We're not testing each controller type here because that's mostly done in diff --git a/control-plane/controllers/configentry_controller_test.go b/control-plane/config-entries/controllers/configentry_controller_test.go similarity index 99% rename from control-plane/controllers/configentry_controller_test.go rename to control-plane/config-entries/controllers/configentry_controller_test.go index 07a3ea3730..57e48dd46c 100644 --- a/control-plane/controllers/configentry_controller_test.go +++ b/control-plane/config-entries/controllers/configentry_controller_test.go @@ -13,10 +13,6 @@ import ( logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -27,6 +23,11 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const datacenterName = "datacenter" diff --git a/control-plane/controllers/controlplanerequestlimit_controller.go b/control-plane/config-entries/controllers/controlplanerequestlimit_controller.go similarity index 100% rename from control-plane/controllers/controlplanerequestlimit_controller.go rename to control-plane/config-entries/controllers/controlplanerequestlimit_controller.go diff --git a/control-plane/controllers/exportedservices_controller.go b/control-plane/config-entries/controllers/exportedservices_controller.go similarity index 100% rename from control-plane/controllers/exportedservices_controller.go rename to control-plane/config-entries/controllers/exportedservices_controller.go diff --git a/control-plane/controllers/exportedservices_controller_ent_test.go b/control-plane/config-entries/controllers/exportedservices_controller_ent_test.go similarity index 99% rename from control-plane/controllers/exportedservices_controller_ent_test.go rename to control-plane/config-entries/controllers/exportedservices_controller_ent_test.go index 94a605eab4..70b774eb53 100644 --- a/control-plane/controllers/exportedservices_controller_ent_test.go +++ b/control-plane/config-entries/controllers/exportedservices_controller_ent_test.go @@ -12,10 +12,6 @@ import ( "time" logrtest "github.com/go-logr/logr/testing" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/controllers" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -24,6 +20,11 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllers" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // This tests explicitly tests ExportedServicesController instead of using the existing diff --git a/control-plane/controllers/ingressgateway_controller.go b/control-plane/config-entries/controllers/ingressgateway_controller.go similarity index 100% rename from control-plane/controllers/ingressgateway_controller.go rename to control-plane/config-entries/controllers/ingressgateway_controller.go diff --git a/control-plane/controllers/jwtprovider_controller.go b/control-plane/config-entries/controllers/jwtprovider_controller.go similarity index 100% rename from control-plane/controllers/jwtprovider_controller.go rename to control-plane/config-entries/controllers/jwtprovider_controller.go diff --git a/control-plane/controllers/mesh_controller.go b/control-plane/config-entries/controllers/mesh_controller.go similarity index 100% rename from control-plane/controllers/mesh_controller.go rename to control-plane/config-entries/controllers/mesh_controller.go diff --git a/control-plane/controllers/proxydefaults_controller.go b/control-plane/config-entries/controllers/proxydefaults_controller.go similarity index 100% rename from control-plane/controllers/proxydefaults_controller.go rename to control-plane/config-entries/controllers/proxydefaults_controller.go diff --git a/control-plane/controllers/samenessgroups_controller.go b/control-plane/config-entries/controllers/samenessgroups_controller.go similarity index 100% rename from control-plane/controllers/samenessgroups_controller.go rename to control-plane/config-entries/controllers/samenessgroups_controller.go diff --git a/control-plane/controllers/servicedefaults_controller.go b/control-plane/config-entries/controllers/servicedefaults_controller.go similarity index 100% rename from control-plane/controllers/servicedefaults_controller.go rename to control-plane/config-entries/controllers/servicedefaults_controller.go diff --git a/control-plane/controllers/serviceintentions_controller.go b/control-plane/config-entries/controllers/serviceintentions_controller.go similarity index 100% rename from control-plane/controllers/serviceintentions_controller.go rename to control-plane/config-entries/controllers/serviceintentions_controller.go diff --git a/control-plane/controllers/serviceresolver_controller.go b/control-plane/config-entries/controllers/serviceresolver_controller.go similarity index 100% rename from control-plane/controllers/serviceresolver_controller.go rename to control-plane/config-entries/controllers/serviceresolver_controller.go diff --git a/control-plane/controllers/servicerouter_controller.go b/control-plane/config-entries/controllers/servicerouter_controller.go similarity index 100% rename from control-plane/controllers/servicerouter_controller.go rename to control-plane/config-entries/controllers/servicerouter_controller.go diff --git a/control-plane/controllers/servicesplitter_controller.go b/control-plane/config-entries/controllers/servicesplitter_controller.go similarity index 100% rename from control-plane/controllers/servicesplitter_controller.go rename to control-plane/config-entries/controllers/servicesplitter_controller.go diff --git a/control-plane/controllers/terminatinggateway_controller.go b/control-plane/config-entries/controllers/terminatinggateway_controller.go similarity index 100% rename from control-plane/controllers/terminatinggateway_controller.go rename to control-plane/config-entries/controllers/terminatinggateway_controller.go diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller.go b/control-plane/config-entries/controllersv2/meshconfig_controller.go new file mode 100644 index 0000000000..5ce749dbe0 --- /dev/null +++ b/control-plane/config-entries/controllersv2/meshconfig_controller.go @@ -0,0 +1,317 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + "fmt" + "time" + + "github.com/go-logr/logr" + "github.com/hashicorp/consul/proto-public/pbresource" + "golang.org/x/time/rate" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + corev1 "k8s.io/api/core/v1" + k8serr "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "k8s.io/utils/strings/slices" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + tenancy "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" +) + +const ( + FinalizerName = "finalizers.consul.hashicorp.com" + ConsulAgentError = "ConsulAgentError" + ExternallyManagedConfigError = "ExternallyManagedConfigError" +) + +// Controller is implemented by CRD-specific config-entries. It is used by +// MeshConfigController to abstract CRD-specific config-entries. +type Controller interface { + // Update updates the state of the whole object. + Update(context.Context, client.Object, ...client.UpdateOption) error + // UpdateStatus updates the state of just the object's status. + UpdateStatus(context.Context, client.Object, ...client.SubResourceUpdateOption) error + // Get retrieves an obj for the given object key from the Kubernetes Cluster. + // obj must be a struct pointer so that obj can be updated with the response + // returned by the Server. + Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error + // Logger returns a logger with values added for the specific controller + // and request name. + Logger(types.NamespacedName) logr.Logger +} + +// MeshConfigController is a generic controller that is used to reconcile +// all resource types, e.g. TrafficPermissions, ProxyConfiguration, etc., since +// they share the same reconcile behaviour. +type MeshConfigController struct { + // ConsulClientConfig is the config for the Consul API client. + ConsulClientConfig *consul.Config + + // ConsulServerConnMgr is the watcher for the Consul server addresses. + ConsulServerConnMgr consul.ServerConnectionManager + + common.ConsulTenancyConfig +} + +// ReconcileEntry reconciles an update to a resource. CRD-specific controller's +// call this function because it handles reconciliation of config entries +// generically. +// CRD-specific controller should pass themselves in as updater since we +// need to call back into their own update methods to ensure they update their +// internal state. +func (r *MeshConfigController) ReconcileEntry(ctx context.Context, crdCtrl Controller, req ctrl.Request, meshConfig common.MeshConfig) (ctrl.Result, error) { + logger := crdCtrl.Logger(req.NamespacedName) + err := crdCtrl.Get(ctx, req.NamespacedName, meshConfig) + if k8serr.IsNotFound(err) { + return ctrl.Result{}, client.IgnoreNotFound(err) + } else if err != nil { + logger.Error(err, "retrieving resource") + return ctrl.Result{}, err + } + + // Create Consul resource service client for this reconcile. + resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) + if err != nil { + logger.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + + if meshConfig.GetDeletionTimestamp().IsZero() { + // The object is not being deleted, so if it does not have our finalizer, + // then let's add the finalizer and update the object. This is equivalent + // registering our finalizer. + if !slices.Contains(meshConfig.GetFinalizers(), FinalizerName) { + meshConfig.AddFinalizer(FinalizerName) + if err := r.syncUnknown(ctx, crdCtrl, meshConfig); err != nil { + return ctrl.Result{}, err + } + } + } + + if !meshConfig.GetDeletionTimestamp().IsZero() { + if slices.Contains(meshConfig.GetFinalizers(), FinalizerName) { + // The object is being deleted + logger.Info("deletion event") + // Check to see if consul has config entry with the same name + res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + + // Ignore the error where the resource isn't found in Consul. + // It is indicative of desired state. + if err != nil && !isNotFoundErr(err) { + return ctrl.Result{}, fmt.Errorf("getting resource from Consul: %w", err) + } + + // In the case this resource was created outside of consul, skip the deletion process and continue + if !managedByMeshController(res.GetResource()) { + logger.Info("resource in Consul was created outside of kubernetes - skipping delete from Consul") + } + + if err == nil && managedByMeshController(res.GetResource()) { + _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + if err != nil { + return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, + fmt.Errorf("deleting config entry from consul: %w", err)) + } + logger.Info("deletion from Consul successful") + } + // remove our finalizer from the list and update it. + meshConfig.RemoveFinalizer(FinalizerName) + if err := crdCtrl.Update(ctx, meshConfig); err != nil { + return ctrl.Result{}, err + } + logger.Info("finalizer removed") + } + + // Stop reconciliation as the item is being deleted + return ctrl.Result{}, nil + } + + // Check to see if consul has config entry with the same name + res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + + // In the case the namespace doesn't exist in Consul yet, assume we are racing with the namespace controller + // and requeue. + if tenancy.ConsulNamespaceIsNotFound(err) { + logger.Info("Consul namespace not found; re-queueing request", + "name", req.Name, "ns", req.Namespace, "consul-ns", + r.consulNamespace(req.Namespace), "err", err.Error()) + return ctrl.Result{Requeue: true}, nil + } + + // If resource with this name does not exist + if isNotFoundErr(err) { + logger.Info("resource not found in consul") + + // Create the config entry + _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: meshConfig.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + if err != nil { + return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, + fmt.Errorf("writing resource to consul: %w", err)) + } + + logger.Info("resource created") + return r.syncSuccessful(ctx, crdCtrl, meshConfig) + } + + // If there is an error when trying to get the resource from the api server, + // fail the reconcile. + if err != nil { + return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, err) + } + + // TODO: consider the case where we want to migrate a resource existing into Consul to a CRD with an annotation + if !managedByMeshController(res.Resource) { + return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ExternallyManagedConfigError, + fmt.Errorf("resource already exists in Consul")) + } + + if !meshConfig.MatchesConsul(res.Resource, r.consulNamespace(req.Namespace), r.getConsulPartition()) { + logger.Info("resource does not match Consul") + _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: meshConfig.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + if err != nil { + return r.syncUnknownWithError(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, + fmt.Errorf("updating resource in Consul: %w", err)) + } + logger.Info("config entry updated") + return r.syncSuccessful(ctx, crdCtrl, meshConfig) + } else if meshConfig.SyncedConditionStatus() != corev1.ConditionTrue { + return r.syncSuccessful(ctx, crdCtrl, meshConfig) + } + + return ctrl.Result{}, nil +} + +// setupWithManager sets up the controller manager for the given resource +// with our default options. +func setupWithManager(mgr ctrl.Manager, resource client.Object, reconciler reconcile.Reconciler) error { + options := controller.Options{ + // Taken from https://github.com/kubernetes/client-go/blob/master/util/workqueue/default_rate_limiters.go#L39 + // and modified from a starting backoff of 5ms and max of 1000s to a + // starting backoff of 200ms and a max of 5s to better fit our most + // common error cases and performance characteristics. + // + // One common error case is that a resource is applied that requires + // a protocol like http or grpc. Often the user will apply a new resource + // to set the protocol in a minute or two. During this time, the + // default backoff could then be set up to 5m or more which means the + // original resource takes a long time to re-sync. + // + // In terms of performance, Consul servers can handle tens of thousands + // of writes per second, so retrying at max every 5s isn't an issue and + // provides a better UX. + RateLimiter: workqueue.NewMaxOfRateLimiter( + workqueue.NewItemExponentialFailureRateLimiter(200*time.Millisecond, 5*time.Second), + // 10 qps, 100 bucket size. This is only for retry speed, and it's only the overall factor (not per item) + &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)}, + ), + } + + return ctrl.NewControllerManagedBy(mgr). + For(resource). + WithOptions(options). + Complete(reconciler) +} + +func (r *MeshConfigController) syncFailed(ctx context.Context, logger logr.Logger, updater Controller, resource common.MeshConfig, errType string, err error) (ctrl.Result, error) { + resource.SetSyncedCondition(corev1.ConditionFalse, errType, err.Error()) + if updateErr := updater.UpdateStatus(ctx, resource); updateErr != nil { + // Log the original error here because we are returning the updateErr. + // Otherwise, the original error would be lost. + logger.Error(err, "sync failed") + return ctrl.Result{}, updateErr + } + return ctrl.Result{}, err +} + +func (r *MeshConfigController) syncSuccessful(ctx context.Context, updater Controller, resource common.MeshConfig) (ctrl.Result, error) { + resource.SetSyncedCondition(corev1.ConditionTrue, "", "") + timeNow := metav1.NewTime(time.Now()) + resource.SetLastSyncedTime(&timeNow) + return ctrl.Result{}, updater.UpdateStatus(ctx, resource) +} + +func (r *MeshConfigController) syncUnknown(ctx context.Context, updater Controller, resource common.MeshConfig) error { + resource.SetSyncedCondition(corev1.ConditionUnknown, "", "") + return updater.Update(ctx, resource) +} + +func (r *MeshConfigController) syncUnknownWithError(ctx context.Context, + logger logr.Logger, + updater Controller, + resource common.MeshConfig, + errType string, + err error) (ctrl.Result, error) { + + resource.SetSyncedCondition(corev1.ConditionUnknown, errType, err.Error()) + if updateErr := updater.UpdateStatus(ctx, resource); updateErr != nil { + // Log the original error here because we are returning the updateErr. + // Otherwise, the original error would be lost. + logger.Error(err, "sync status unknown") + return ctrl.Result{}, updateErr + } + return ctrl.Result{}, err +} + +// isNotFoundErr checks the grpc response code for "NotFound". +func isNotFoundErr(err error) bool { + if err == nil { + return false + } + s, ok := status.FromError(err) + if !ok { + return false + } + return codes.NotFound == s.Code() +} + +func (r *MeshConfigController) consulNamespace(namespace string) string { + ns := namespaces.ConsulNamespace( + namespace, + r.EnableConsulNamespaces, + r.ConsulDestinationNamespace, + r.EnableNSMirroring, + r.NSMirroringPrefix, + ) + + // TODO: remove this if and when the default namespace of resources is no longer required to be set explicitly. + if ns == "" { + ns = constants.DefaultConsulNS + } + return ns +} + +func (r *MeshConfigController) getConsulPartition() string { + if !r.EnableConsulPartitions || r.ConsulPartition == "" { + return constants.DefaultConsulPartition + } + return r.ConsulPartition +} + +func managedByMeshController(resource *pbresource.Resource) bool { + if resource == nil { + return false + } + + consulMeta := resource.GetMetadata() + if consulMeta == nil { + return false + } + + if val, ok := consulMeta[common.SourceKey]; ok && val == common.SourceValue { + return true + } + return false +} diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go new file mode 100644 index 0000000000..15543bee59 --- /dev/null +++ b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go @@ -0,0 +1,772 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/go-logr/logr" + logrtest "github.com/go-logr/logr/testr" + "github.com/google/go-cmp/cmp" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/api/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +type testReconciler interface { + Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) +} + +// TestMeshConfigController_createsMeshConfig validated resources are created in Consul from kube objects. +func TestMeshConfigController_createsMeshConfig(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + meshConfig common.MeshConfig + expected *pbauth.TrafficPermissions + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler + unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message + }{ + { + name: "TrafficPermissions", + meshConfig: &v2beta1.TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-traffic-permission", + Namespace: metav1.NamespaceDefault, + }, + Spec: v2beta1.TrafficPermissionsSpec{ + Destination: &v2beta1.Destination{ + IdentityName: "destination-identity", + }, + Action: v2beta1.ActionAllow, + Permissions: v2beta1.Permissions{ + { + Sources: v2beta1.Sources{ + { + Namespace: "the space namespace space", + }, + { + IdentityName: "source-identity", + }, + }, + DestinationRules: v2beta1.DestinationRules{ + { + PathExact: "/hello", + Methods: []string{"GET", "POST"}, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + }, + expected: &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "source-identity", + }, + { + Namespace: "the space namespace space", + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathExact: "/hello", + Methods: []string{"GET", "POST"}, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &TrafficPermissionsController{ + Client: client, + Log: logger, + MeshConfigController: &MeshConfigController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + }, + } + }, + unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { + data := resource.Data + + trafficPermission := &pbauth.TrafficPermissions{} + require.NoError(t, data.UnmarshalTo(trafficPermission)) + return trafficPermission + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + ctx := context.Background() + + s := runtime.NewScheme() + s.AddKnownTypes(v2beta1.AuthGroupVersion, c.meshConfig) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.meshConfig).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) + namespacedName := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: c.meshConfig.KubernetesName(), + } + resp, err := r.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + req := &pbresource.ReadRequest{Id: c.meshConfig.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + res, err := resourceClient.Read(ctx, req) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, c.meshConfig.GetName(), res.GetResource().GetId().GetName()) + + actual := c.unmarshal(t, res.GetResource()) + opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(c.expected, actual, opts...) + require.Equal(t, "", diff, "TrafficPermissions do not match") + + // Check that the status is "synced". + err = fakeClient.Get(ctx, namespacedName, c.meshConfig) + require.NoError(t, err) + require.Equal(t, corev1.ConditionTrue, c.meshConfig.SyncedConditionStatus()) + + // Check that the finalizer is added. + require.Contains(t, c.meshConfig.Finalizers(), FinalizerName) + }) + } +} + +func TestMeshConfigController_updatesMeshConfig(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + meshConfig common.MeshConfig + expected *pbauth.TrafficPermissions + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler + updateF func(config common.MeshConfig) + unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message + }{ + { + name: "TrafficPermissions", + meshConfig: &v2beta1.TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-traffic-permission", + Namespace: metav1.NamespaceDefault, + }, + Spec: v2beta1.TrafficPermissionsSpec{ + Destination: &v2beta1.Destination{ + IdentityName: "destination-identity", + }, + Action: v2beta1.ActionAllow, + Permissions: v2beta1.Permissions{ + { + Sources: v2beta1.Sources{ + { + Namespace: "the space namespace space", + }, + { + IdentityName: "source-identity", + }, + }, + DestinationRules: v2beta1.DestinationRules{ + { + PathExact: "/hello", + Methods: []string{"GET", "POST"}, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + }, + expected: &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_DENY, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + Namespace: "the space namespace space", + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathExact: "/hello", + Methods: []string{"GET", "POST"}, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &TrafficPermissionsController{ + Client: client, + Log: logger, + MeshConfigController: &MeshConfigController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + }, + } + }, + updateF: func(resource common.MeshConfig) { + trafficPermissions := resource.(*v2beta1.TrafficPermissions) + trafficPermissions.Spec.Action = "deny" + trafficPermissions.Spec.Permissions[0].Sources = trafficPermissions.Spec.Permissions[0].Sources[:1] + }, + unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { + data := resource.Data + + trafficPermission := &pbauth.TrafficPermissions{} + require.NoError(t, data.UnmarshalTo(trafficPermission)) + return trafficPermission + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + ctx := context.Background() + + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, c.meshConfig) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.meshConfig).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + // We haven't run reconcile yet, so we must create the MeshConfig + // in Consul ourselves. + { + resource := c.meshConfig.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) + req := &pbresource.WriteRequest{Resource: resource} + _, err := resourceClient.Write(ctx, req) + require.NoError(t, err) + } + + // Now run reconcile which should update the entry in Consul. + { + namespacedName := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: c.meshConfig.KubernetesName(), + } + // First get it, so we have the latest revision number. + err := fakeClient.Get(ctx, namespacedName, c.meshConfig) + require.NoError(t, err) + + // Update the entry in Kube and run reconcile. + c.updateF(c.meshConfig) + err = fakeClient.Update(ctx, c.meshConfig) + require.NoError(t, err) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) + resp, err := r.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Now check that the object in Consul is as expected. + req := &pbresource.ReadRequest{Id: c.meshConfig.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + res, err := resourceClient.Read(ctx, req) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, c.meshConfig.GetName(), res.GetResource().GetId().GetName()) + + actual := c.unmarshal(t, res.GetResource()) + opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(c.expected, actual, opts...) + require.Equal(t, "", diff, "TrafficPermissions do not match") + } + }) + } +} + +func TestMeshConfigController_deletesMeshConfig(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + MeshConfigWithDeletion common.MeshConfig + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler + }{ + { + name: "TrafficPermissions", + MeshConfigWithDeletion: &v2beta1.TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: metav1.NamespaceDefault, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + Finalizers: []string{FinalizerName}, + }, + Spec: v2beta1.TrafficPermissionsSpec{ + Destination: &v2beta1.Destination{ + IdentityName: "destination-identity", + }, + Action: v2beta1.ActionAllow, + Permissions: v2beta1.Permissions{ + { + Sources: v2beta1.Sources{ + { + Namespace: "the space namespace space", + }, + { + IdentityName: "source-identity", + }, + }, + DestinationRules: v2beta1.DestinationRules{ + { + PathExact: "/hello", + Methods: []string{"GET", "POST"}, + PortNames: []string{"web", "admin"}, + }, + }, + }, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &TrafficPermissionsController{ + Client: client, + Log: logger, + MeshConfigController: &MeshConfigController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + }, + } + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + ctx := context.Background() + + s := runtime.NewScheme() + s.AddKnownTypes(v2beta1.AuthGroupVersion, c.MeshConfigWithDeletion) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.MeshConfigWithDeletion).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // We haven't run reconcile yet, so we must create the config entry + // in Consul ourselves. + { + resource := c.MeshConfigWithDeletion.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) + req := &pbresource.WriteRequest{Resource: resource} + _, err := resourceClient.Write(ctx, req) + require.NoError(t, err) + } + + // Now run reconcile. It's marked for deletion so this should delete it. + { + namespacedName := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: c.MeshConfigWithDeletion.KubernetesName(), + } + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) + resp, err := r.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: namespacedName, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Now check that the object in Consul is as expected. + req := &pbresource.ReadRequest{Id: c.MeshConfigWithDeletion.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + _, err = resourceClient.Read(ctx, req) + require.Error(t, err) + require.True(t, isNotFoundErr(err)) + } + }) + } +} + +func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { + t.Parallel() + + req := require.New(t) + ctx := context.Background() + trafficpermissions := &v2beta1.TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + }, + Spec: v2beta1.TrafficPermissionsSpec{ + Destination: &v2beta1.Destination{ + IdentityName: "destination-identity", + }, + Action: v2beta1.ActionAllow, + Permissions: v2beta1.Permissions{ + { + Sources: v2beta1.Sources{ + { + IdentityName: "source-identity", + }, + }, + }, + }, + }, + } + + s := runtime.NewScheme() + s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + + // Get watcher state to make sure we can get a healthy address. + state, err := testClient.Watcher.State() + require.NoError(t, err) + // Stop the server before calling reconcile imitating a server that's not running. + _ = testClient.TestServer.Stop() + + reconciler := &TrafficPermissionsController{ + Client: fakeClient, + Log: logrtest.New(t), + MeshConfigController: &MeshConfigController{ + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + }, + } + + // ReconcileEntry should result in an error. + namespacedName := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: trafficpermissions.KubernetesName(), + } + resp, err := reconciler.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + req.Error(err) + + expErr := fmt.Sprintf("connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:%d: connect: connection refused\"", state.Address.Port) + req.Contains(err.Error(), expErr) + req.False(resp.Requeue) + + // Check that the status is "synced=false". + err = fakeClient.Get(ctx, namespacedName, trafficpermissions) + req.NoError(err) + status, reason, errMsg := trafficpermissions.SyncedCondition() + req.Equal(corev1.ConditionFalse, status) + req.Equal("ConsulAgentError", reason) + req.Contains(errMsg, expErr) +} + +// TestMeshConfigController_setsSyncedToTrue tests that if the resource hasn't changed in +// Consul but our resource's synced status isn't set to true, then we update its status. +func TestMeshConfigController_setsSyncedToTrue(t *testing.T) { + t.Parallel() + + ctx := context.Background() + s := runtime.NewScheme() + + trafficpermissions := &v2beta1.TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + }, + Spec: v2beta1.TrafficPermissionsSpec{ + Destination: &v2beta1.Destination{ + IdentityName: "destination-identity", + }, + Action: v2beta1.ActionAllow, + Permissions: v2beta1.Permissions{ + { + Sources: v2beta1.Sources{ + { + IdentityName: "source-identity", + }, + }, + }, + }, + }, + Status: v2beta1.Status{ + Conditions: v2beta1.Conditions{ + { + Type: v2beta1.ConditionSynced, + Status: corev1.ConditionUnknown, + }, + }, + }, + } + s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) + + // The config entry exists in kube but its status will be nil. + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + reconciler := &TrafficPermissionsController{ + Client: fakeClient, + Log: logrtest.New(t), + MeshConfigController: &MeshConfigController{ + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + }, + } + + // Create the resource in Consul to mimic that it was created + // successfully (but its status hasn't been updated). + { + resource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) + req := &pbresource.WriteRequest{Resource: resource} + _, err := resourceClient.Write(ctx, req) + require.NoError(t, err) + } + + namespacedName := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: trafficpermissions.KubernetesName(), + } + resp, err := reconciler.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Check that the status is now "synced". + err = fakeClient.Get(ctx, namespacedName, trafficpermissions) + require.NoError(t, err) + require.Equal(t, corev1.ConditionTrue, trafficpermissions.SyncedConditionStatus()) +} + +// TestMeshConfigController_doesNotCreateUnownedMeshConfig test that if the resource +// exists in Consul but is not managed by the controller, creating/updating the resource fails. +func TestMeshConfigController_doesNotCreateUnownedMeshConfig(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + s := runtime.NewScheme() + trafficpermissions := &v2beta1.TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + }, + Spec: v2beta1.TrafficPermissionsSpec{ + Destination: &v2beta1.Destination{ + IdentityName: "destination-identity", + }, + Action: v2beta1.ActionAllow, + Permissions: v2beta1.Permissions{ + { + Sources: v2beta1.Sources{ + { + IdentityName: "source-identity", + }, + }, + }, + }, + }, + } + s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + unmanagedResource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) + unmanagedResource.Metadata = make(map[string]string) // Zero out the metadata + + // We haven't run reconcile yet. We must create the resource + // in Consul ourselves, without the metadata indicating it is owned by the controller. + { + req := &pbresource.WriteRequest{Resource: unmanagedResource} + _, err := resourceClient.Write(ctx, req) + require.NoError(t, err) + } + + // Now run reconcile which should **not** update the entry in Consul. + { + namespacedName := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: trafficpermissions.KubernetesName(), + } + // First get it, so we have the latest revision number. + err := fakeClient.Get(ctx, namespacedName, trafficpermissions) + require.NoError(t, err) + + // Attempt to create the entry in Kube and run reconcile. + reconciler := TrafficPermissionsController{ + Client: fakeClient, + Log: logrtest.New(t), + MeshConfigController: &MeshConfigController{ + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + }, + } + resp, err := reconciler.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + require.EqualError(t, err, "resource already exists in Consul") + require.False(t, resp.Requeue) + + // Now check that the object in Consul is as expected. + req := &pbresource.ReadRequest{Id: trafficpermissions.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + readResp, err := resourceClient.Read(ctx, req) + require.NoError(t, err) + require.NotNil(t, readResp.GetResource()) + opts := append([]cmp.Option{ + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid")}, + test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(unmanagedResource, readResp.GetResource(), opts...) + require.Equal(t, "", diff, "TrafficPermissions do not match") + + // Check that the status is "synced=false". + err = fakeClient.Get(ctx, namespacedName, trafficpermissions) + require.NoError(t, err) + status, reason, errMsg := trafficpermissions.SyncedCondition() + require.Equal(t, corev1.ConditionFalse, status) + require.Equal(t, "ExternallyManagedConfigError", reason) + require.Equal(t, errMsg, "resource already exists in Consul") + } + +} + +// TestMeshConfigController_doesNotDeleteUnownedConfig tests that if the resource +// exists in Consul but is not managed by the controller, deleting the resource does +// not delete the Consul resource. +func TestMeshConfigController_doesNotDeleteUnownedConfig(t *testing.T) { + t.Parallel() + + ctx := context.Background() + s := runtime.NewScheme() + + trafficpermissionsWithDeletion := &v2beta1.TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + Finalizers: []string{FinalizerName}, + }, + Spec: v2beta1.TrafficPermissionsSpec{ + Destination: &v2beta1.Destination{ + IdentityName: "destination-identity", + }, + Action: v2beta1.ActionAllow, + Permissions: v2beta1.Permissions{ + { + Sources: v2beta1.Sources{ + { + IdentityName: "source-identity", + }, + }, + }, + }, + }, + } + s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissionsWithDeletion) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissionsWithDeletion).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + reconciler := &TrafficPermissionsController{ + Client: fakeClient, + Log: logrtest.New(t), + MeshConfigController: &MeshConfigController{ + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + }, + } + + unmanagedResource := trafficpermissionsWithDeletion.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) + unmanagedResource.Metadata = make(map[string]string) // Zero out the metadata + + // We haven't run reconcile yet. We must create the resource + // in Consul ourselves, without the metadata indicating it is owned by the controller. + { + req := &pbresource.WriteRequest{Resource: unmanagedResource} + _, err := resourceClient.Write(ctx, req) + require.NoError(t, err) + } + + // Now run reconcile. It's marked for deletion so this should delete the kubernetes resource + // but not the consul config entry. + { + namespacedName := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: trafficpermissionsWithDeletion.KubernetesName(), + } + resp, err := reconciler.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Now check that the object in Consul is as expected. + req := &pbresource.ReadRequest{Id: trafficpermissionsWithDeletion.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + readResp, err := resourceClient.Read(ctx, req) + require.NoError(t, err) + require.NotNil(t, readResp.GetResource()) + opts := append([]cmp.Option{ + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid")}, + test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(unmanagedResource, readResp.GetResource(), opts...) + require.Equal(t, "", diff, "TrafficPermissions do not match") + + // Check that the resource is deleted from cluster. + tp := &v2beta1.TrafficPermissions{} + _ = fakeClient.Get(ctx, namespacedName, tp) + require.Empty(t, tp.Finalizers()) + } +} diff --git a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go b/control-plane/config-entries/controllersv2/traffic_permissions_controller.go new file mode 100644 index 0000000000..a8c9f63e56 --- /dev/null +++ b/control-plane/config-entries/controllersv2/traffic_permissions_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + consulv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/v2beta1" +) + +// TrafficPermissionsController reconciles a TrafficPermissions object. +type TrafficPermissionsController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + MeshConfigController *MeshConfigController +} + +// +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions/status,verbs=get;update;patch + +func (r *TrafficPermissionsController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &consulv2beta1.TrafficPermissions{}) +} + +func (r *TrafficPermissionsController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *TrafficPermissionsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *TrafficPermissionsController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &consulv2beta1.TrafficPermissions{}, r) +} diff --git a/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml b/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml new file mode 100644 index 0000000000..270f86ee0f --- /dev/null +++ b/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml @@ -0,0 +1,263 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: trafficpermissions.auth.consul.hashicorp.com +spec: + group: auth.consul.hashicorp.com + names: + kind: TrafficPermissions + listKind: TrafficPermissionsList + plural: trafficpermissions + shortNames: + - traffic-permissions + singular: trafficpermissions + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: TrafficPermissions is the Schema for the traffic-permissions + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TrafficPermissionsSpec defines the desired state of TrafficPermissions. + properties: + action: + description: "Action can be either allow or deny for the entire object. + It will default to allow. \n If action is allow, we will allow the + connection if one of the rules in Rules matches, in other words, + we will deny all requests except for the ones that match Rules. + If Consul is in default allow mode, then allow actions have no effect + without a deny permission as everything is allowed by default. \n + If action is deny, we will deny the connection if one of the rules + in Rules match, in other words, we will allow all requests except + for the ones that match Rules. If Consul is default deny mode, then + deny permissions have no effect without an allow permission as everything + is denied by default. \n Action unspecified is reserved for compatibility + with the addition of future actions." + type: string + destination: + description: Destination is a configuration of the destination proxies + where these traffic permissions should apply. + properties: + identityName: + description: Name is the destination of all intentions defined + in this config entry. This may be set to the wildcard character + (*) to match all services that don't otherwise have intentions + defined. + type: string + type: object + permissions: + description: Permissions is a list of permissions to match on. They + are applied using OR semantics. + items: + properties: + destinationRules: + description: destinationRules is a list of rules to apply for + matching sources in this Permission. These rules are specific + to the request or connection that is going to the destination(s) + selected by the TrafficPermissions resource. + items: + description: DestinationRule contains rules to apply to the + incoming connection. + properties: + exclude: + description: exclude contains a list of rules to exclude + when evaluating rules for the incoming connection. + items: + properties: + header: + properties: + exact: + type: string + invert: + type: boolean + name: + type: string + prefix: + type: string + present: + type: boolean + regex: + type: string + suffix: + type: string + type: object + methods: + description: methods is the list of HTTP methods. + items: + type: string + type: array + pathExact: + type: string + pathPrefix: + type: string + pathRegex: + type: string + portNames: + description: portNames is a list of workload ports + to apply this rule to. The ports specified here + must be the ports used in the connection. + items: + type: string + type: array + type: object + type: array + header: + properties: + exact: + type: string + invert: + type: boolean + name: + type: string + prefix: + type: string + present: + type: boolean + regex: + type: string + suffix: + type: string + type: object + methods: + description: methods is the list of HTTP methods. If no + methods are specified, this rule will apply to all methods. + items: + type: string + type: array + pathExact: + type: string + pathPrefix: + type: string + pathRegex: + type: string + portNames: + items: + type: string + type: array + type: object + type: array + sources: + description: sources is a list of sources in this traffic permission. + items: + description: Source represents the source identity. To specify + any of the wildcard sources, the specific fields need to + be omitted. For example, for a wildcard namespace, identityName + should be omitted. + properties: + exclude: + description: exclude is a list of sources to exclude from + this source. + items: + description: ExcludeSource is almost the same as source + but it prevents the addition of matchiing sources. + properties: + identityName: + type: string + namespace: + type: string + partition: + type: string + peer: + type: string + samenessGroup: + type: string + type: object + type: array + identityName: + type: string + namespace: + type: string + partition: + type: string + peer: + type: string + samenessGroup: + type: string + type: object + type: array + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml index 481e5a4b90..7e9d9a40bf 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml @@ -195,7 +195,7 @@ spec: - targetRef type: object status: - description: GatewayPolicyStatus defines the observed state of the gateway + description: GatewayPolicyStatus defines the observed state of the gateway. properties: conditions: default: diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml index 8584404c56..e9bfd8330c 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -117,61 +117,61 @@ spec: before being activated. \n Default value is false." type: boolean jwksCluster: - description: "JWKSCluster defines how the specified Remote JWKS - URI is to be fetched." + description: JWKSCluster defines how the specified Remote + JWKS URI is to be fetched. properties: connectTimeout: - description: "The timeout for new network connections to hosts - in the cluster. \n If not set, a default value of 5s will be - used." + description: The timeout for new network connections to + hosts in the cluster. If not set, a default value of + 5s will be used. format: int64 type: integer discoveryType: - description: "DiscoveryType refers to the service discovery type - to use for resolving the cluster. \n Defaults to STRICT_DNS." + description: "DiscoveryType refers to the service discovery + type to use for resolving the cluster. \n This defaults + to STRICT_DNS. Other options include STATIC, LOGICAL_DNS, + EDS or ORIGINAL_DST." type: string tlsCertificates: description: "TLSCertificates refers to the data containing - certificate authority certificates to use in verifying a presented - peer certificate." + certificate authority certificates to use in verifying + a presented peer certificate. If not specified and a + peer certificate is presented it will not be verified. + \n Must be either CaCertificateProviderInstance or TrustedCA." properties: caCertificateProviderInstance: - description: "CaCertificateProviderInstance Certificate provider - instance for fetching TLS certificates." + description: CaCertificateProviderInstance Certificate + provider instance for fetching TLS certificates. properties: - instanceName: - description: "InstanceName refers to the certificate provider - instance name. \n The default value is 'default'." - type: string certificateName: - description: "CertificateName is used to specify certificate - instances or types. For example, \"ROOTCA\" to specify a - root-certificate (validation context) or \"example.com\" - to specify a certificate for a particular domain. \n - The default value is the empty string." + description: "CertificateName is used to specify + certificate instances or types. For example, + \"ROOTCA\" to specify a root-certificate (validation + context) or \"example.com\" to specify a certificate + for a particular domain. \n The default value + is the empty string." + type: string + instanceName: + description: "InstanceName refers to the certificate + provider instance name. \n The default value + is \"default\"." type: string type: object trustedCA: - description: "TrustedCA defines TLS certificate data containing - certificate authority certificates to use in verifying a presented - peer certificate. \n Exactly one of Filename, EnvironmentVariable, - InlineString or InlineBytes must be specified." + description: "TrustedCA defines TLS certificate data + containing certificate authority certificates to + use in verifying a presented peer certificate. \n + Exactly one of Filename, EnvironmentVariable, InlineString + or InlineBytes must be specified." properties: - filename: - description: "The name of the file on the local system to use a - data source for trusted CA certificates." - type: string environmentVariable: - description: "The environment variable on the local system to use - a data source for trusted CA certificates." type: string - inlineString: - description: "A string to inline in the configuration for use as - a data source for trusted CA certificates." + filename: type: string inlineBytes: - description: "A sequence of bytes to inline in the configuration - for use as a data source for trusted CA certificates." + format: byte + type: string + inlineString: type: string type: object type: object @@ -191,20 +191,20 @@ spec: of 10s. \n Default value is 0." type: integer retryPolicyBackOff: - description: "Backoff policy \n Defaults to Envoy's backoff - policy" + description: "Retry's backoff policy. \n Defaults to Envoy's + backoff policy." properties: baseInterval: description: "BaseInterval to be used for the next - back off computation \n The default value from envoy - is 1s" + back off computation. \n The default value from + envoy is 1s." format: int64 type: integer maxInterval: description: "MaxInternal to be used to specify the maximum interval between retries. Optional but should be greater or equal to BaseInterval. \n Defaults - to 10 times BaseInterval" + to 10 times BaseInterval." format: int64 type: integer type: object diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml index f08a9336a1..a83a4bb7d0 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml @@ -6,7 +6,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.3 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: routeauthfilters.consul.hashicorp.com spec: @@ -34,7 +34,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: RouteAuthFilter is the Schema for the httpauthfilters API. + description: RouteAuthFilter is the Schema for the routeauthfilters API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -194,3 +194,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/external/.yml b/control-plane/config/crd/external/.yml new file mode 100644 index 0000000000..c4837ac2c7 --- /dev/null +++ b/control-plane/config/crd/external/.yml @@ -0,0 +1,4 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + + diff --git a/control-plane/config/crd/kustomization.yaml b/control-plane/config/crd/kustomization.yaml deleted file mode 100644 index cbb41bc60c..0000000000 --- a/control-plane/config/crd/kustomization.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -# This kustomization.yaml is not intended to be run by itself, -# since it depends on service name and namespace that are out of this kustomize package. -# It should be run by config/default -resources: -- bases/consul.hashicorp.com_controlplanerequestlimits.yaml -#+kubebuilder:scaffold:crdkustomizeresource - -patches: -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. -# patches here are for enabling the conversion webhook for each CRD -#- patches/webhook_in_controlplanerequestlimits.yaml -#+kubebuilder:scaffold:crdkustomizewebhookpatch - -# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. -# patches here are for enabling the CA injection for each CRD -#- patches/cainjection_in_controlplanerequestlimits.yaml -#+kubebuilder:scaffold:crdkustomizecainjectionpatch - -# the following config is for teaching kustomize how to do kustomization for CRDs. -configurations: -- kustomizeconfig.yaml diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 7f90780e02..88c7c44980 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -26,6 +26,26 @@ rules: - secrets/status verbs: - get +- apiGroups: + - auth.consul.hashicorp.com + resources: + - trafficpermissions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - auth.consul.hashicorp.com + resources: + - trafficpermissions/status + verbs: + - get + - patch + - update - apiGroups: - consul.hashicorp.com resources: diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index 9fa4043e66..6b675d459a 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -323,6 +323,27 @@ webhooks: resources: - terminatinggateways sideEffects: None +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v2beta1-trafficpermissions + failurePolicy: Fail + name: mutate-trafficpermissions.auth.consul.hashicorp.com + rules: + - apiGroups: + - auth.consul.hashicorp.com + apiVersions: + - v2beta1 + operations: + - CREATE + - UPDATE + resources: + - trafficpermissions + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 402f17bbb3..baf74dabb3 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -9,6 +9,7 @@ import ( "strings" mapset "github.com/deckarep/golang-set" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -17,8 +18,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index 86618088f0..3f41c414fb 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -19,7 +19,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" @@ -534,11 +534,7 @@ func Test_ConsulNamespaceIsNotFound_ErrorMsg(t *testing.T) { id := &pbresource.ID{ Name: "foo", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Workload", - }, + Type: pbcatalog.WorkloadType, Tenancy: &pbresource.Tenancy{ Partition: constants.DefaultConsulPartition, Namespace: "i-dont-exist-but-its-ok-we-will-meet-again-someday", diff --git a/control-plane/connect-inject/common/config.go b/control-plane/connect-inject/common/config.go deleted file mode 100644 index 7167f1ee43..0000000000 --- a/control-plane/connect-inject/common/config.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import mapset "github.com/deckarep/golang-set" - -// K8sNamespaceConfig manages allow/deny Kubernetes namespaces. -type K8sNamespaceConfig struct { - // Only endpoints in the AllowK8sNamespacesSet are reconciled. - AllowK8sNamespacesSet mapset.Set - // Endpoints in the DenyK8sNamespacesSet are ignored. - DenyK8sNamespacesSet mapset.Set -} - -// ConsulTenancyConfig manages settings related to Consul namespaces and partitions. -type ConsulTenancyConfig struct { - // EnableConsulPartitions indicates that a user is running Consul Enterprise. - EnableConsulPartitions bool - // ConsulPartition is the Consul Partition to which this controller belongs. - ConsulPartition string - // EnableConsulNamespaces indicates that a user is running Consul Enterprise. - EnableConsulNamespaces bool - // ConsulDestinationNamespace is the name of the Consul namespace to create - // all config entries in. If EnableNSMirroring is true this is ignored. - ConsulDestinationNamespace string - // EnableNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any config entry custom resource. Config entries will - // be created in the matching Consul namespace. - EnableNSMirroring bool - // NSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - NSMirroringPrefix string -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index b652aa3b6b..54878ba68b 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -11,6 +11,9 @@ import ( "strings" "github.com/go-logr/logr" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/go-multierror" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -19,11 +22,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-multierror" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/namespaces" @@ -65,7 +65,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu var service corev1.Service // Ignore the request if the namespace of the endpoint is not allowed. - if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { return ctrl.Result{}, nil } @@ -115,7 +115,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if err = r.registerService(ctx, resourceClient, service, consulSvc); err != nil { // We could be racing with the namespace controller. // Requeue (which includes backoff) to try again. - if common.ConsulNamespaceIsNotFound(err) { + if inject.ConsulNamespaceIsNotFound(err) { r.Log.Info("Consul namespace not found; re-queueing request", "service", service.GetName(), "ns", req.Namespace, "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) @@ -197,7 +197,7 @@ func (r *Controller) getWorkloadDataFromEndpoints(ctx context.Context, pf PodFet // Add pod to workload selector values as appropriate. // Pods can appear more than once in Endpoints subsets, so we use a set for exact names as well. if prefix := getOwnerPrefixFromPod(pod); prefix != "" { - if common.HasBeenMeshInjected(*pod) { + if inject.HasBeenMeshInjected(*pod) { // Add to the list of pods represented by this prefix. This list is used by // `getEffectiveTargetPort` to determine the most-used target container port name if the // k8s service target port is numeric. @@ -208,7 +208,7 @@ func (r *Controller) getWorkloadDataFromEndpoints(ctx context.Context, pf PodFet ignoredPodPrefixes[prefix] = true } } else { - if common.HasBeenMeshInjected(*pod) { + if inject.HasBeenMeshInjected(*pod) { exactNamePods[podName.Name] = &podData } // If the pod hasn't been mesh-injected, ignore it, as it won't be available as a workload. @@ -288,7 +288,7 @@ func (r *Controller) registerService(ctx context.Context, resourceClient pbresou func (r *Controller) getServiceResource(svc *pbcatalog.Service, name, namespace, partition string, meta map[string]string) *pbresource.Resource { return &pbresource.Resource{ Id: getServiceID(name, namespace, partition), - Data: common.ToProtoAny(svc), + Data: inject.ToProtoAny(svc), Metadata: meta, } } @@ -296,11 +296,7 @@ func (r *Controller) getServiceResource(svc *pbcatalog.Service, name, namespace, func getServiceID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, @@ -324,7 +320,7 @@ func getServicePorts(service corev1.Service, prefixedPods selectorPodData, exact ports = append(ports, &pbcatalog.ServicePort{ VirtualPort: uint32(p.Port), TargetPort: getEffectiveTargetPort(p.TargetPort, prefixedPods, exactNamePods), - Protocol: common.GetPortProtocol(p.AppProtocol), + Protocol: inject.GetPortProtocol(p.AppProtocol), }) } } diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index f696e06150..49071b36b1 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -11,6 +11,10 @@ import ( mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -24,12 +28,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/go-uuid" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" @@ -116,11 +116,7 @@ func TestReconcile_CreateService(t *testing.T) { // expectedResource: &pbresource.Resource{ // Id: &pbresource.ID{ // Name: "service-created", - // Type: &pbresource.Type{ - // Group: "catalog", - // GroupVersion: "v1alpha1", - // Kind: "Service", - // }, + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Namespace: constants.DefaultConsulNS, // Partition: constants.DefaultConsulPartition, @@ -230,17 +226,13 @@ func TestReconcile_CreateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -324,17 +316,13 @@ func TestReconcile_CreateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -428,17 +416,13 @@ func TestReconcile_CreateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -541,17 +525,13 @@ func TestReconcile_CreateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -704,17 +684,13 @@ func TestReconcile_CreateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -812,17 +788,13 @@ func TestReconcile_CreateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -906,17 +878,13 @@ func TestReconcile_CreateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -1047,17 +1015,13 @@ func TestReconcile_UpdateService(t *testing.T) { existingResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -1089,17 +1053,13 @@ func TestReconcile_UpdateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -1191,17 +1151,13 @@ func TestReconcile_UpdateService(t *testing.T) { existingResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-updated", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -1237,17 +1193,13 @@ func TestReconcile_UpdateService(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-updated", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -1294,17 +1246,13 @@ func TestReconcile_DeleteService(t *testing.T) { existingResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "service-created", - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - }, + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, }, }, - Data: common.ToProtoAny(&pbcatalog.Service{ + Data: inject.ToProtoAny(&pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ { VirtualPort: 8080, @@ -1675,5 +1623,5 @@ func randomKubernetesId() string { func removeMeshInjectStatus(t *testing.T, pod *corev1.Pod) { delete(pod.Annotations, constants.KeyMeshInjectStatus) - require.False(t, common.HasBeenMeshInjected(*pod)) + require.False(t, inject.HasBeenMeshInjected(*pod)) } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 358aebf9e4..96dde403b1 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -11,8 +11,8 @@ import ( "strings" "github.com/go-logr/logr" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/go-multierror" "google.golang.org/protobuf/proto" @@ -22,7 +22,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/consul" @@ -86,7 +87,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // Strictly speaking, this is not required because the mesh webhook also knows valid namespaces // for injection, but it will somewhat reduce the amount of unnecessary deletions for non-injected // pods - if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { return ctrl.Result{}, nil } @@ -127,11 +128,11 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.Log.Info("retrieved", "name", pod.Name, "ns", pod.Namespace) - if common.HasBeenMeshInjected(pod) { + if inject.HasBeenMeshInjected(pod) { if err := r.writeProxyConfiguration(ctx, pod); err != nil { // We could be racing with the namespace controller. // Requeue (which includes backoff) to try again. - if common.ConsulNamespaceIsNotFound(err) { + if inject.ConsulNamespaceIsNotFound(err) { r.Log.Info("Consul namespace not found; re-queueing request", "pod", req.Name, "ns", req.Namespace, "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) @@ -143,7 +144,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if err := r.writeWorkload(ctx, pod); err != nil { // Technically this is not needed, but keeping in case this gets refactored in // a different order - if common.ConsulNamespaceIsNotFound(err) { + if inject.ConsulNamespaceIsNotFound(err) { r.Log.Info("Consul namespace not found; re-queueing request", "pod", req.Name, "ns", req.Namespace, "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) @@ -156,7 +157,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if err := r.writeUpstreams(ctx, pod); err != nil { // Technically this is not needed, but keeping in case this gets refactored in // a different order - if common.ConsulNamespaceIsNotFound(err) { + if inject.ConsulNamespaceIsNotFound(err) { r.Log.Info("Consul namespace not found; re-queueing request", "pod", req.Name, "ns", req.Namespace, "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) @@ -168,7 +169,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if err := r.writeHealthStatus(ctx, pod); err != nil { // Technically this is not needed, but keeping in case this gets refactored in // a different order - if common.ConsulNamespaceIsNotFound(err) { + if inject.ConsulNamespaceIsNotFound(err) { r.Log.Info("Consul namespace not found; re-queueing request", "pod", req.Name, "ns", req.Namespace, "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) @@ -226,10 +227,10 @@ func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { // Adding a node does not currently work because the node doesn't exist so its health status will always be // unhealthy, causing any endpoints on that node to also be unhealthy. // TODO: (v2/nitya) Bring this back when node controller is built. - //NodeName: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), + //NodeName: inject.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), Ports: workloadPorts, } - data := common.ToProtoAny(workload) + data := inject.ToProtoAny(workload) req := &pbresource.WriteRequest{ Resource: &pbresource.Resource{ @@ -276,7 +277,7 @@ func (r *Controller) writeProxyConfiguration(ctx context.Context, pod corev1.Pod }, BootstrapConfig: bootstrapConfig, } - data := common.ToProtoAny(pc) + data := inject.ToProtoAny(pc) req := &pbresource.WriteRequest{ Resource: &pbresource.Resource{ @@ -297,7 +298,7 @@ func (r *Controller) getTproxyMode(ctx context.Context, pod corev1.Pod) (pbmesh. return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, fmt.Errorf("could not get namespace info for %s: %w", pod.GetNamespace(), err) } - tproxyEnabled, err := common.TransparentProxyEnabled(ns, pod, r.EnableTransparentProxy) + tproxyEnabled, err := inject.TransparentProxyEnabled(ns, pod, r.EnableTransparentProxy) if err != nil { return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, fmt.Errorf("could not determine if transparent proxy is enabled: %w", err) } @@ -310,7 +311,7 @@ func (r *Controller) getTproxyMode(ctx context.Context, pod corev1.Pod) (pbmesh. func (r *Controller) getExposeConfig(pod corev1.Pod) (*pbmesh.ExposeConfig, error) { // Expose k8s probes as Envoy listeners if needed. - overwriteProbes, err := common.ShouldOverwriteProbes(pod, r.TProxyOverwriteProbes) + overwriteProbes, err := inject.ShouldOverwriteProbes(pod, r.TProxyOverwriteProbes) if err != nil { return nil, fmt.Errorf("could not determine if probes should be overwritten: %w", err) } @@ -348,7 +349,7 @@ func (r *Controller) getExposeConfig(pod corev1.Pod) (*pbmesh.ExposeConfig, erro func getContainerExposePaths(originalPod corev1.Pod, originalContainer, mutatedContainer corev1.Container) ([]*pbmesh.ExposePath, error) { var paths []*pbmesh.ExposePath if mutatedContainer.LivenessProbe != nil && mutatedContainer.LivenessProbe.HTTPGet != nil { - originalLivenessPort, err := common.PortValueFromIntOrString(originalPod, originalContainer.LivenessProbe.HTTPGet.Port) + originalLivenessPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.LivenessProbe.HTTPGet.Port) if err != nil { return nil, err } @@ -361,7 +362,7 @@ func getContainerExposePaths(originalPod corev1.Pod, originalContainer, mutatedC paths = append(paths, newPath) } if mutatedContainer.ReadinessProbe != nil && mutatedContainer.ReadinessProbe.HTTPGet != nil { - originalReadinessPort, err := common.PortValueFromIntOrString(originalPod, originalContainer.ReadinessProbe.HTTPGet.Port) + originalReadinessPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.ReadinessProbe.HTTPGet.Port) if err != nil { return nil, err } @@ -374,7 +375,7 @@ func getContainerExposePaths(originalPod corev1.Pod, originalContainer, mutatedC paths = append(paths, newPath) } if mutatedContainer.StartupProbe != nil && mutatedContainer.StartupProbe.HTTPGet != nil { - originalStartupPort, err := common.PortValueFromIntOrString(originalPod, originalContainer.StartupProbe.HTTPGet.Port) + originalStartupPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.StartupProbe.HTTPGet.Port) if err != nil { return nil, err } @@ -429,7 +430,7 @@ func (r *Controller) writeHealthStatus(ctx context.Context, pod corev1.Pod) erro Description: constants.ConsulKubernetesCheckName, Output: getHealthStatusReason(status, pod), } - data := common.ToProtoAny(hs) + data := inject.ToProtoAny(hs) req := &pbresource.WriteRequest{ Resource: &pbresource.Resource{ @@ -459,7 +460,7 @@ func (r *Controller) writeUpstreams(ctx context.Context, pod corev1.Pod) error { return nil } - data := common.ToProtoAny(uss) + data := inject.ToProtoAny(uss) req := &pbresource.WriteRequest{ Resource: &pbresource.Resource{ Id: getUpstreamsID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), @@ -547,7 +548,7 @@ func (r *Controller) processUpstreams(pod corev1.Pod) (*pbmesh.Upstreams, error) func (r *Controller) processLabeledUpstream(pod corev1.Pod, rawUpstream string) (*pbmesh.Upstream, error) { parts := strings.SplitN(rawUpstream, ":", 3) var port int32 - port, _ = common.PortValue(pod, strings.TrimSpace(parts[1])) + port, _ = inject.PortValue(pod, strings.TrimSpace(parts[1])) if port <= 0 { return &pbmesh.Upstream{}, fmt.Errorf("port value %d in upstream is invalid: %s", port, rawUpstream) } @@ -632,7 +633,7 @@ func (r *Controller) processLabeledUpstream(pod corev1.Pod, rawUpstream string) upstream := pbmesh.Upstream{ DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(partition), Namespace: getDefaultConsulNamespace(namespace), @@ -664,7 +665,7 @@ func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string parts := strings.SplitN(rawUpstream, ":", 3) - port, _ = common.PortValue(pod, strings.TrimSpace(parts[1])) + port, _ = inject.PortValue(pod, strings.TrimSpace(parts[1])) // If Consul Namespaces or Admin Partitions are enabled, attempt to parse the // upstream for a namespace. @@ -697,7 +698,7 @@ func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string if port > 0 { upstream = pbmesh.Upstream{ DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(partition), Namespace: getDefaultConsulNamespace(namespace), @@ -832,11 +833,7 @@ func getHealthStatusReason(state pbcatalog.Health, pod corev1.Pod) string { func getWorkloadID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Workload", - }, + Type: pbcatalog.WorkloadType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, @@ -851,11 +848,7 @@ func getWorkloadID(name, namespace, partition string) *pbresource.ID { func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "mesh", - GroupVersion: "v1alpha1", - Kind: "ProxyConfiguration", - }, + Type: pbmesh.ProxyConfigurationType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, @@ -870,11 +863,7 @@ func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { func getHealthStatusID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "HealthStatus", - }, + Type: pbcatalog.HealthStatusType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, @@ -889,11 +878,7 @@ func getHealthStatusID(name, namespace, partition string) *pbresource.ID { func getUpstreamsID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "mesh", - GroupVersion: "v1alpha1", - Kind: "Upstreams", - }, + Type: pbmesh.UpstreamsType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, @@ -905,14 +890,6 @@ func getUpstreamsID(name, namespace, partition string) *pbresource.ID { } } -func upstreamReferenceType() *pbresource.Type { - return &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Service", - } -} - func getDefaultConsulNamespace(ns string) string { if ns == "" { ns = constants.DefaultConsulNS diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index 8f2c20974d..9b0f45c3fe 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -12,8 +12,8 @@ import ( mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" capi "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -23,7 +23,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/consul" diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index a749ea8522..74031f9b3a 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -14,8 +14,8 @@ import ( logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" @@ -32,7 +32,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/consul" @@ -790,7 +790,7 @@ func TestUpstreamsWrite(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -828,7 +828,7 @@ func TestUpstreamsWrite(t *testing.T) { // Upstreams: []*pbmesh.Upstream{ // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "ns1", @@ -864,7 +864,7 @@ func TestUpstreamsWrite(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: "part1", Namespace: "ns1", @@ -911,7 +911,7 @@ func TestUpstreamsWrite(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -947,7 +947,7 @@ func TestUpstreamsWrite(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: "bar", Namespace: "foo", @@ -1034,7 +1034,7 @@ func TestProcessUpstreams(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -1072,7 +1072,7 @@ func TestProcessUpstreams(t *testing.T) { // Upstreams: []*pbmesh.Upstream{ // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: getDefaultConsulNamespace(""), @@ -1110,7 +1110,7 @@ func TestProcessUpstreams(t *testing.T) { // Upstreams: []*pbmesh.Upstream{ // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: getDefaultConsulNamespace(""), @@ -1148,7 +1148,7 @@ func TestProcessUpstreams(t *testing.T) { // Upstreams: []*pbmesh.Upstream{ // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "ns1", @@ -1184,7 +1184,7 @@ func TestProcessUpstreams(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: "part1", Namespace: "ns1", @@ -1222,7 +1222,7 @@ func TestProcessUpstreams(t *testing.T) { // Upstreams: []*pbmesh.Upstream{ // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "ns1", @@ -1258,7 +1258,7 @@ func TestProcessUpstreams(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: "ns1", @@ -1277,7 +1277,7 @@ func TestProcessUpstreams(t *testing.T) { }, { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -1296,7 +1296,7 @@ func TestProcessUpstreams(t *testing.T) { }, { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: "ap1", Namespace: "ns1", @@ -1334,7 +1334,7 @@ func TestProcessUpstreams(t *testing.T) { // Upstreams: []*pbmesh.Upstream{ // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "ns1", @@ -1353,7 +1353,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: getDefaultConsulNamespace(""), @@ -1372,7 +1372,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "ns1", @@ -1391,7 +1391,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "ns1", @@ -1570,7 +1570,7 @@ func TestProcessUpstreams(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: "ns1", @@ -1589,7 +1589,7 @@ func TestProcessUpstreams(t *testing.T) { }, { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -1608,7 +1608,7 @@ func TestProcessUpstreams(t *testing.T) { }, { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: "ap1", Namespace: "ns1", @@ -1644,7 +1644,7 @@ func TestProcessUpstreams(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -1680,7 +1680,7 @@ func TestProcessUpstreams(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: "foo", @@ -1716,7 +1716,7 @@ func TestProcessUpstreams(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: "bar", Namespace: "foo", @@ -1752,7 +1752,7 @@ func TestProcessUpstreams(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -1771,7 +1771,7 @@ func TestProcessUpstreams(t *testing.T) { }, { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -1815,7 +1815,7 @@ func TestProcessUpstreams(t *testing.T) { // Upstreams: []*pbmesh.Upstream{ // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: getDefaultConsulNamespace(""), @@ -1834,7 +1834,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "bar", @@ -1853,7 +1853,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: "baz", // Namespace: "foo", @@ -1897,7 +1897,7 @@ func TestProcessUpstreams(t *testing.T) { // Upstreams: []*pbmesh.Upstream{ // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: getDefaultConsulNamespace(""), @@ -1916,7 +1916,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "bar", @@ -1935,7 +1935,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // { // DestinationRef: &pbresource.Reference{ - // Type: upstreamReferenceType(), + // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ // Partition: getDefaultConsulPartition(""), // Namespace: "foo", @@ -2013,7 +2013,7 @@ func TestUpstreamsDelete(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), @@ -2503,7 +2503,7 @@ func TestReconcileUpdatePod(t *testing.T) { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: "ap1", Namespace: "ns1", @@ -2858,7 +2858,7 @@ func createUpstreams() *pbmesh.Upstreams { Upstreams: []*pbmesh.Upstream{ { DestinationRef: &pbresource.Reference{ - Type: upstreamReferenceType(), + Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Partition: getDefaultConsulPartition(""), Namespace: getDefaultConsulNamespace(""), diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go index 2ccb1c7e8e..38d1f10989 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go @@ -5,7 +5,11 @@ package serviceaccount import ( "context" + "github.com/go-logr/logr" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/go-multierror" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -13,13 +17,11 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/namespaces" - auth "github.com/hashicorp/consul/proto-public/pbauth/v1alpha1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-multierror" ) type Controller struct { @@ -54,7 +56,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu var serviceAccount corev1.ServiceAccount // Ignore the request if the namespace of the service account is not allowed. - if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { return ctrl.Result{}, nil } @@ -122,7 +124,7 @@ func (r *Controller) getWorkloadIdentityResource(name, namespace, partition stri return &pbresource.Resource{ Id: getWorkloadIdentityID(name, namespace, partition), // WorkloadIdentity is currently an empty message. - Data: common.ToProtoAny(&auth.WorkloadIdentity{}), + Data: inject.ToProtoAny(&pbauth.WorkloadIdentity{}), Metadata: meta, } } @@ -130,11 +132,7 @@ func (r *Controller) getWorkloadIdentityResource(name, namespace, partition stri func getWorkloadIdentityID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "auth", - GroupVersion: "v1alpha1", - Kind: "WorkloadIdentity", - }, + Type: pbauth.WorkloadIdentityType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go index 4c3e37da3c..41437c2072 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go @@ -5,13 +5,14 @@ package serviceaccount import ( "context" + "testing" + "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/proto" - "testing" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v1alpha1" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" @@ -25,7 +26,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" @@ -58,11 +60,7 @@ func TestReconcile_CreateWorkloadIdentity(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "default", - Type: &pbresource.Type{ - Group: "auth", - GroupVersion: "v1alpha1", - Kind: "WorkloadIdentity", - }, + Type: pbauth.WorkloadIdentityType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, @@ -87,11 +85,7 @@ func TestReconcile_CreateWorkloadIdentity(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "my-svc-account", - Type: &pbresource.Type{ - Group: "auth", - GroupVersion: "v1alpha1", - Kind: "WorkloadIdentity", - }, + Type: pbauth.WorkloadIdentityType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, @@ -116,11 +110,7 @@ func TestReconcile_CreateWorkloadIdentity(t *testing.T) { existingResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "my-svc-account", - Type: &pbresource.Type{ - Group: "auth", - GroupVersion: "v1alpha1", - Kind: "WorkloadIdentity", - }, + Type: pbauth.WorkloadIdentityType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, @@ -135,11 +125,7 @@ func TestReconcile_CreateWorkloadIdentity(t *testing.T) { expectedResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "my-svc-account", - Type: &pbresource.Type{ - Group: "auth", - GroupVersion: "v1alpha1", - Kind: "WorkloadIdentity", - }, + Type: pbauth.WorkloadIdentityType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, @@ -174,11 +160,7 @@ func TestReconcile_DeleteWorkloadIdentity(t *testing.T) { existingResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "my-svc-account", - Type: &pbresource.Type{ - Group: "auth", - GroupVersion: "v1alpha1", - Kind: "WorkloadIdentity", - }, + Type: pbauth.WorkloadIdentityType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, @@ -204,11 +186,7 @@ func TestReconcile_DeleteWorkloadIdentity(t *testing.T) { existingResource: &pbresource.Resource{ Id: &pbresource.ID{ Name: "my-svc-account", - Type: &pbresource.Type{ - Group: "auth", - GroupVersion: "v1alpha1", - Kind: "WorkloadIdentity", - }, + Type: pbauth.WorkloadIdentityType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, @@ -333,7 +311,7 @@ func expectedWorkloadIdentityMatches(t *testing.T, client pbresource.ResourceSer // getWorkloadIdentityData returns a WorkloadIdentity resource payload. // This function takes no arguments because WorkloadIdentity is currently an empty proto message. func getWorkloadIdentityData() *anypb.Any { - return common.ToProtoAny(&pbauth.WorkloadIdentity{}) + return inject.ToProtoAny(&pbauth.WorkloadIdentity{}) } func createServiceAccount(name, namespace string) *corev1.ServiceAccount { diff --git a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go rename to control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go index 46e01c62f5..2867257b6c 100644 --- a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "encoding/json" diff --git a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go rename to control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go index 12cff4289d..3aff917cac 100644 --- a/control-plane/connect-inject/webhook_v2/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "fmt" diff --git a/control-plane/connect-inject/webhook_v2/container_env.go b/control-plane/connect-inject/webhookv2/container_env.go similarity index 98% rename from control-plane/connect-inject/webhook_v2/container_env.go rename to control-plane/connect-inject/webhookv2/container_env.go index 4c05a2ea72..f7df9ded92 100644 --- a/control-plane/connect-inject/webhook_v2/container_env.go +++ b/control-plane/connect-inject/webhookv2/container_env.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( corev1 "k8s.io/api/core/v1" diff --git a/control-plane/connect-inject/webhook_v2/container_env_test.go b/control-plane/connect-inject/webhookv2/container_env_test.go similarity index 98% rename from control-plane/connect-inject/webhook_v2/container_env_test.go rename to control-plane/connect-inject/webhookv2/container_env_test.go index f7cef104ea..a9507d6c51 100644 --- a/control-plane/connect-inject/webhook_v2/container_env_test.go +++ b/control-plane/connect-inject/webhookv2/container_env_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "testing" diff --git a/control-plane/connect-inject/webhook_v2/container_init.go b/control-plane/connect-inject/webhookv2/container_init.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/container_init.go rename to control-plane/connect-inject/webhookv2/container_init.go index ce9145be55..dcd486660f 100644 --- a/control-plane/connect-inject/webhook_v2/container_init.go +++ b/control-plane/connect-inject/webhookv2/container_init.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "bytes" diff --git a/control-plane/connect-inject/webhook_v2/container_init_test.go b/control-plane/connect-inject/webhookv2/container_init_test.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/container_init_test.go rename to control-plane/connect-inject/webhookv2/container_init_test.go index 87cb83306c..33189f0d0c 100644 --- a/control-plane/connect-inject/webhook_v2/container_init_test.go +++ b/control-plane/connect-inject/webhookv2/container_init_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "fmt" diff --git a/control-plane/connect-inject/webhook_v2/container_volume.go b/control-plane/connect-inject/webhookv2/container_volume.go similarity index 96% rename from control-plane/connect-inject/webhook_v2/container_volume.go rename to control-plane/connect-inject/webhookv2/container_volume.go index 5c7e65c905..a05a6720db 100644 --- a/control-plane/connect-inject/webhook_v2/container_volume.go +++ b/control-plane/connect-inject/webhookv2/container_volume.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( corev1 "k8s.io/api/core/v1" diff --git a/control-plane/connect-inject/webhook_v2/dns.go b/control-plane/connect-inject/webhookv2/dns.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/dns.go rename to control-plane/connect-inject/webhookv2/dns.go index e7aaa67830..883c9ed034 100644 --- a/control-plane/connect-inject/webhook_v2/dns.go +++ b/control-plane/connect-inject/webhookv2/dns.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "fmt" diff --git a/control-plane/connect-inject/webhook_v2/dns_test.go b/control-plane/connect-inject/webhookv2/dns_test.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/dns_test.go rename to control-plane/connect-inject/webhookv2/dns_test.go index 7c45a5e577..e7a380b271 100644 --- a/control-plane/connect-inject/webhook_v2/dns_test.go +++ b/control-plane/connect-inject/webhookv2/dns_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "os" diff --git a/control-plane/connect-inject/webhook_v2/health_checks_test.go b/control-plane/connect-inject/webhookv2/health_checks_test.go similarity index 98% rename from control-plane/connect-inject/webhook_v2/health_checks_test.go rename to control-plane/connect-inject/webhookv2/health_checks_test.go index ce5f3937bf..82b7cdd99d 100644 --- a/control-plane/connect-inject/webhook_v2/health_checks_test.go +++ b/control-plane/connect-inject/webhookv2/health_checks_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "os" diff --git a/control-plane/connect-inject/webhook_v2/heath_checks.go b/control-plane/connect-inject/webhookv2/heath_checks.go similarity index 96% rename from control-plane/connect-inject/webhook_v2/heath_checks.go rename to control-plane/connect-inject/webhookv2/heath_checks.go index 6d92172e78..6bd11f6efa 100644 --- a/control-plane/connect-inject/webhook_v2/heath_checks.go +++ b/control-plane/connect-inject/webhookv2/heath_checks.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "errors" diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook.go b/control-plane/connect-inject/webhookv2/mesh_webhook.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/mesh_webhook.go rename to control-plane/connect-inject/webhookv2/mesh_webhook.go index e76cd0f82f..61031b386c 100644 --- a/control-plane/connect-inject/webhook_v2/mesh_webhook.go +++ b/control-plane/connect-inject/webhookv2/mesh_webhook.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "context" diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go rename to control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go index 5d1c51a0de..0924a01e6c 100644 --- a/control-plane/connect-inject/webhook_v2/mesh_webhook_ent_test.go +++ b/control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go @@ -3,7 +3,7 @@ //go:build enterprise -package webhook_v2 +package webhookv2 import ( "context" diff --git a/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/mesh_webhook_test.go rename to control-plane/connect-inject/webhookv2/mesh_webhook_test.go index 799082ee4b..e2aba39ec9 100644 --- a/control-plane/connect-inject/webhook_v2/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "context" diff --git a/control-plane/connect-inject/webhook_v2/redirect_traffic.go b/control-plane/connect-inject/webhookv2/redirect_traffic.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/redirect_traffic.go rename to control-plane/connect-inject/webhookv2/redirect_traffic.go index ac45df125f..8432372831 100644 --- a/control-plane/connect-inject/webhook_v2/redirect_traffic.go +++ b/control-plane/connect-inject/webhookv2/redirect_traffic.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "encoding/json" diff --git a/control-plane/connect-inject/webhook_v2/redirect_traffic_test.go b/control-plane/connect-inject/webhookv2/redirect_traffic_test.go similarity index 99% rename from control-plane/connect-inject/webhook_v2/redirect_traffic_test.go rename to control-plane/connect-inject/webhookv2/redirect_traffic_test.go index 077189ead4..62b25722db 100644 --- a/control-plane/connect-inject/webhook_v2/redirect_traffic_test.go +++ b/control-plane/connect-inject/webhookv2/redirect_traffic_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package webhook_v2 +package webhookv2 import ( "encoding/json" diff --git a/control-plane/consul/dataplane_client_test.go b/control-plane/consul/dataplane_client_test.go index 4e76b80125..9463839dda 100644 --- a/control-plane/consul/dataplane_client_test.go +++ b/control-plane/consul/dataplane_client_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( @@ -7,9 +10,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/consul-server-connection-manager/discovery" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "github.com/hashicorp/consul/proto-public/pbdataplane" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/go-hclog" @@ -109,11 +112,7 @@ func createWorkload(t *testing.T, watcher ServerConnectionManager, name string) id := &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Workload", - }, + Type: pbcatalog.WorkloadType, Tenancy: &pbresource.Tenancy{ Partition: "default", Namespace: "default", @@ -154,11 +153,7 @@ func createProxyConfiguration(t *testing.T, watcher ServerConnectionManager, nam id := &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "mesh", - GroupVersion: "v1alpha1", - Kind: "ProxyConfiguration", - }, + Type: pbmesh.ProxyConfigurationType, Tenancy: &pbresource.Tenancy{ Partition: "default", Namespace: "default", diff --git a/control-plane/consul/resource_client_test.go b/control-plane/consul/resource_client_test.go index 3992e766fa..0dbecc9798 100644 --- a/control-plane/consul/resource_client_test.go +++ b/control-plane/consul/resource_client_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/hashicorp/consul-server-connection-manager/discovery" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/go-hclog" @@ -96,11 +96,7 @@ func createWriteRequest(t *testing.T, name string) *pbresource.WriteRequest { Resource: &pbresource.Resource{ Id: &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Workload", - }, + Type: pbcatalog.WorkloadType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, Partition: constants.DefaultConsulPartition, diff --git a/control-plane/go.mod b/control-plane/go.mod index 66c9e4e507..56df356f55 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases replace ( // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version - github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230914174054-e5808d85f751 + github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230921190229-d8ac6ee34f5e // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd ) diff --git a/control-plane/go.sum b/control-plane/go.sum index 693c42251e..85ec362227 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNb github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 h1:LnzgDq4e7ZfM1+XS6S21B9taQrbfdydXenL1xHyG1PQ= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230914174054-e5808d85f751 h1:kFfExHyeRbC5hi0REatLNSYwMFONnhkn/IyhnG3vG2A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230914174054-e5808d85f751/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230921190229-d8ac6ee34f5e h1:DY0NJAjjFze8PxZjTsVYLCrNBxTZKqy21MY6LDfBvig= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230921190229-d8ac6ee34f5e/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd h1:tRrSgVY71Jl6T2lJUokMLj3T1MO9uiSvW0CieBkjTvo= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/subcommand/acl-init/command.go b/control-plane/subcommand/acl-init/command.go index 77e8f87d87..24ae5c7ebf 100644 --- a/control-plane/subcommand/acl-init/command.go +++ b/control-plane/subcommand/acl-init/command.go @@ -18,10 +18,6 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-netaddrs" @@ -29,6 +25,11 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( diff --git a/control-plane/subcommand/acl-init/command_test.go b/control-plane/subcommand/acl-init/command_test.go index acdafc939f..ca236bb031 100644 --- a/control-plane/subcommand/acl-init/command_test.go +++ b/control-plane/subcommand/acl-init/command_test.go @@ -13,14 +13,15 @@ import ( "testing" "text/template" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" 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/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) const ( diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index 1636c0b10e..2b1475daa4 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -13,12 +13,13 @@ import ( "github.com/cenkalti/backoff" "github.com/go-logr/logr" - godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "go.uber.org/zap/zapcore" "sigs.k8s.io/controller-runtime/pkg/log/zap" + + godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" ) const ( diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index 9e50302b17..2925638c6e 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -12,12 +12,13 @@ import ( "os" "testing" - "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover/mocks" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover/mocks" ) func TestLogger_InvalidLogLevel(t *testing.T) { diff --git a/control-plane/subcommand/connect-init/command_ent_test.go b/control-plane/subcommand/connect-init/command_ent_test.go index b3ef7109e0..743bd511fd 100644 --- a/control-plane/subcommand/connect-init/command_ent_test.go +++ b/control-plane/subcommand/connect-init/command_ent_test.go @@ -12,11 +12,12 @@ import ( "strconv" "testing" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func TestRun_WithNamespaces(t *testing.T) { diff --git a/control-plane/subcommand/connect-init/command_test.go b/control-plane/subcommand/connect-init/command_test.go index cb1d32d03b..f756ac7359 100644 --- a/control-plane/subcommand/connect-init/command_test.go +++ b/control-plane/subcommand/connect-init/command_test.go @@ -14,13 +14,14 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/iptables" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) const nodeName = "test-node" diff --git a/control-plane/subcommand/consul-logout/command.go b/control-plane/subcommand/consul-logout/command.go index a6b599dccd..b556556d43 100644 --- a/control-plane/subcommand/consul-logout/command.go +++ b/control-plane/subcommand/consul-logout/command.go @@ -7,12 +7,13 @@ import ( "flag" "sync" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( diff --git a/control-plane/subcommand/consul-logout/command_test.go b/control-plane/subcommand/consul-logout/command_test.go index 1a0744996a..3b4d6d39cc 100644 --- a/control-plane/subcommand/consul-logout/command_test.go +++ b/control-plane/subcommand/consul-logout/command_test.go @@ -9,12 +9,13 @@ import ( "os" "testing" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) func TestRun_FlagValidation(t *testing.T) { diff --git a/control-plane/subcommand/create-federation-secret/command.go b/control-plane/subcommand/create-federation-secret/command.go index 52600aedda..2d6b66cb83 100644 --- a/control-plane/subcommand/create-federation-secret/command.go +++ b/control-plane/subcommand/create-federation-secret/command.go @@ -15,10 +15,6 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" @@ -26,6 +22,11 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( diff --git a/control-plane/subcommand/create-federation-secret/command_test.go b/control-plane/subcommand/create-federation-secret/command_test.go index 15f12b132c..29fe3b3d61 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -14,8 +14,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -25,6 +23,9 @@ 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/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) func TestRun_FlagValidation(t *testing.T) { diff --git a/control-plane/subcommand/delete-completed-job/command.go b/control-plane/subcommand/delete-completed-job/command.go index f6f594393d..eb3705223f 100644 --- a/control-plane/subcommand/delete-completed-job/command.go +++ b/control-plane/subcommand/delete-completed-job/command.go @@ -10,15 +10,16 @@ import ( "sync" "time" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/mitchellh/cli" v1 "k8s.io/api/batch/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) // Command is the command for deleting completed jobs. diff --git a/control-plane/subcommand/fetch-server-region/command.go b/control-plane/subcommand/fetch-server-region/command.go index 248ce971e7..58297a10da 100644 --- a/control-plane/subcommand/fetch-server-region/command.go +++ b/control-plane/subcommand/fetch-server-region/command.go @@ -11,8 +11,6 @@ import ( "os" "sync" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" corev1 "k8s.io/api/core/v1" @@ -20,6 +18,9 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) // The consul-logout command issues a Consul logout API request to delete an ACL token. diff --git a/control-plane/subcommand/flags/consul.go b/control-plane/subcommand/flags/consul.go index 9368b95b3d..cd577790fc 100644 --- a/control-plane/subcommand/flags/consul.go +++ b/control-plane/subcommand/flags/consul.go @@ -11,10 +11,11 @@ import ( "strings" "time" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-rootcerts" + + "github.com/hashicorp/consul-k8s/control-plane/consul" ) const ( diff --git a/control-plane/subcommand/flags/http.go b/control-plane/subcommand/flags/http.go index 21ccb45df1..5421b4c672 100644 --- a/control-plane/subcommand/flags/http.go +++ b/control-plane/subcommand/flags/http.go @@ -9,8 +9,9 @@ import ( "strings" "time" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" + + "github.com/hashicorp/consul-k8s/control-plane/consul" ) // Taken from https://github.com/hashicorp/consul/blob/b5b9c8d953cd3c79c6b795946839f4cf5012f507/command/flags/http.go diff --git a/control-plane/subcommand/gateway-cleanup/command.go b/control-plane/subcommand/gateway-cleanup/command.go index 04aa8f60a9..583b153d01 100644 --- a/control-plane/subcommand/gateway-cleanup/command.go +++ b/control-plane/subcommand/gateway-cleanup/command.go @@ -12,9 +12,6 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/mitchellh/cli" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -22,6 +19,10 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { diff --git a/control-plane/subcommand/gateway-cleanup/command_test.go b/control-plane/subcommand/gateway-cleanup/command_test.go index 56b7651270..038c5b5667 100644 --- a/control-plane/subcommand/gateway-cleanup/command_test.go +++ b/control-plane/subcommand/gateway-cleanup/command_test.go @@ -6,7 +6,6 @@ package gatewaycleanup import ( "testing" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -14,6 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) func TestRun(t *testing.T) { diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 19c7b28742..ea4338f2d3 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -15,12 +15,8 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/mitchellh/cli" - yaml "gopkg.in/yaml.v2" + "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -30,6 +26,11 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" 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" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) // this dupes the Kubernetes tolerations diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index f60e376042..8455c1a315 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -6,7 +6,6 @@ package gatewayresources import ( "testing" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -14,6 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) func TestRun_flagValidation(t *testing.T) { diff --git a/control-plane/subcommand/get-consul-client-ca/command.go b/control-plane/subcommand/get-consul-client-ca/command.go index 619f08625d..069b21ad0f 100644 --- a/control-plane/subcommand/get-consul-client-ca/command.go +++ b/control-plane/subcommand/get-consul-client-ca/command.go @@ -13,14 +13,15 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-k8s/control-plane/consul" - godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) // get-consul-client-ca command talks to the Consul servers diff --git a/control-plane/subcommand/gossip-encryption-autogenerate/command.go b/control-plane/subcommand/gossip-encryption-autogenerate/command.go index cf871eca69..e14f24f847 100644 --- a/control-plane/subcommand/gossip-encryption-autogenerate/command.go +++ b/control-plane/subcommand/gossip-encryption-autogenerate/command.go @@ -11,15 +11,16 @@ import ( "fmt" "sync" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 4434d52d98..0d9fba6cb0 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -31,6 +31,7 @@ import ( gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/api/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" @@ -155,10 +156,15 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + // We need v1alpha1 here to add the peering api to the scheme utilruntime.Must(v1alpha1.AddToScheme(scheme)) utilruntime.Must(gwv1beta1.AddToScheme(scheme)) utilruntime.Must(gwv1alpha2.AddToScheme(scheme)) + + // V2 resources + utilruntime.Must(v2beta1.AddAuthToScheme(scheme)) + //+kubebuilder:scaffold:scheme } @@ -381,8 +387,8 @@ func (c *Command) Run(args []string) int { return 1 } - //Right now we exclusively start controllers for V1 or V2. - //In the future we might add a flag to pick and choose from both. + // Right now we exclusively start controllers for V1 or V2. + // In the future we might add a flag to pick and choose from both. if c.flagResourceAPIs { err = c.configureV2Controllers(ctx, mgr, watcher) } else { diff --git a/control-plane/subcommand/inject-connect/v1controllers.go b/control-plane/subcommand/inject-connect/v1controllers.go index 1270458851..c407c99ccf 100644 --- a/control-plane/subcommand/inject-connect/v1controllers.go +++ b/control-plane/subcommand/inject-connect/v1controllers.go @@ -17,12 +17,12 @@ import ( gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllers" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - "github.com/hashicorp/consul-k8s/control-plane/controllers" webhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/webhook-configuration" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 7291229b22..9d42082dee 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -11,7 +11,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllersv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/serviceaccount" @@ -19,7 +21,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/namespace" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - webhookV2 "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook_v2" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhookv2" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -122,23 +124,25 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage } } - // TODO: V2 Config Controller(s) - - // // Metadata for webhooks - //consulMeta := apicommon.ConsulMeta{ - // PartitionsEnabled: c.flagEnablePartitions, - // Partition: c.consul.Partition, - // NamespacesEnabled: c.flagEnableNamespaces, - // DestinationNamespace: c.flagConsulDestinationNamespace, - // Mirroring: c.flagEnableK8SNSMirroring, - // Prefix: c.flagK8SNSMirroringPrefix, - //} + meshConfigReconciler := &controllersv2.MeshConfigController{ + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + ConsulTenancyConfig: consulTenancyConfig, + } + if err := (&controllersv2.TrafficPermissionsController{ + MeshConfigController: meshConfigReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.TrafficPermissions), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.TrafficPermissions) + return err + } - // TODO: register webhooks mgr.GetWebhookServer().CertDir = c.flagCertDir mgr.GetWebhookServer().Register("/mutate", - &ctrlRuntimeWebhook.Admission{Handler: &webhookV2.MeshWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &webhookv2.MeshWebhook{ Clientset: c.clientset, ReleaseNamespace: c.flagReleaseNamespace, ConsulConfig: consulConfig, @@ -180,6 +184,13 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage LogJSON: c.flagLogJSON, }}) + mgr.GetWebhookServer().Register("/mutate-v2beta1-trafficpermissions", + &ctrlRuntimeWebhook.Admission{Handler: &v2beta1.TrafficPermissionsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(common.TrafficPermissions), + ConsulTenancyConfig: consulTenancyConfig, + }}) + if err := mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { setupLog.Error(err, "unable to create readiness check") return err diff --git a/control-plane/subcommand/install-cni/command.go b/control-plane/subcommand/install-cni/command.go index 53abe7cda1..c626ee006b 100644 --- a/control-plane/subcommand/install-cni/command.go +++ b/control-plane/subcommand/install-cni/command.go @@ -16,10 +16,11 @@ import ( "github.com/fsnotify/fsnotify" "github.com/hashicorp/consul-k8s/control-plane/cni/config" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( diff --git a/control-plane/subcommand/install-cni/command_test.go b/control-plane/subcommand/install-cni/command_test.go index d5ee65f928..4083ba5153 100644 --- a/control-plane/subcommand/install-cni/command_test.go +++ b/control-plane/subcommand/install-cni/command_test.go @@ -12,10 +12,11 @@ import ( "time" "github.com/hashicorp/consul-k8s/control-plane/cni/config" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/serf/testutil/retry" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) func TestRun_FlagDefaults(t *testing.T) { diff --git a/control-plane/subcommand/mesh-init/command.go b/control-plane/subcommand/mesh-init/command.go index 91ea8129cf..6c21210e9b 100644 --- a/control-plane/subcommand/mesh-init/command.go +++ b/control-plane/subcommand/mesh-init/command.go @@ -19,7 +19,7 @@ import ( "github.com/cenkalti/backoff" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/proto-public/pbdataplane" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/sdk/iptables" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" diff --git a/control-plane/subcommand/mesh-init/command_test.go b/control-plane/subcommand/mesh-init/command_test.go index ec280d70ac..530fc44670 100644 --- a/control-plane/subcommand/mesh-init/command_test.go +++ b/control-plane/subcommand/mesh-init/command_test.go @@ -13,8 +13,8 @@ import ( "testing" "time" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/iptables" "github.com/hashicorp/consul/sdk/testutil" @@ -315,11 +315,7 @@ func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbr func getWorkloadID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "catalog", - GroupVersion: "v1alpha1", - Kind: "Workload", - }, + Type: pbcatalog.WorkloadType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, @@ -360,11 +356,7 @@ func getWorkload() *pbcatalog.Workload { func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: &pbresource.Type{ - Group: "mesh", - GroupVersion: "v1alpha1", - Kind: "ProxyConfiguration", - }, + Type: pbmesh.ProxyConfigurationType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, diff --git a/control-plane/subcommand/server-acl-init/anonymous_token_test.go b/control-plane/subcommand/server-acl-init/anonymous_token_test.go index 4a36c676e1..98e3699216 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token_test.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package serveraclinit import ( diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index 7ff0ae2268..95bce0f7b5 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -16,11 +16,6 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - k8sflags "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" @@ -31,6 +26,12 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" "k8s.io/client-go/kubernetes" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + k8sflags "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index a67a2d0e41..c9157c368e 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -15,9 +15,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" @@ -26,6 +23,10 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // Test the auth method and acl binding rule created when namespaces are enabled diff --git a/control-plane/subcommand/server-acl-init/connect_inject.go b/control-plane/subcommand/server-acl-init/connect_inject.go index 58a36b988f..07fc7c6052 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject.go +++ b/control-plane/subcommand/server-acl-init/connect_inject.go @@ -6,10 +6,11 @@ package serveraclinit import ( "fmt" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) // We use the default Kubernetes service as the default host diff --git a/control-plane/subcommand/server-acl-init/connect_inject_test.go b/control-plane/subcommand/server-acl-init/connect_inject_test.go index 03e47c8ba6..2714bfa3f7 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject_test.go +++ b/control-plane/subcommand/server-acl-init/connect_inject_test.go @@ -7,12 +7,13 @@ import ( "context" "testing" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" 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/control-plane/subcommand/common" ) // Test that createAuthMethodTmpl returns an error when diff --git a/control-plane/subcommand/server-acl-init/create_or_update.go b/control-plane/subcommand/server-acl-init/create_or_update.go index 50f215eacb..8099e6e9c6 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update.go +++ b/control-plane/subcommand/server-acl-init/create_or_update.go @@ -7,10 +7,11 @@ import ( "fmt" "strings" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // createACLPolicyRoleAndBindingRule will create the ACL Policy for the component diff --git a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go index 93d9d0d2d8..e4c5d78e38 100644 --- a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go +++ b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go @@ -7,11 +7,12 @@ import ( "context" "fmt" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" apiv1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) const SecretsBackendTypeKubernetes SecretsBackendType = "kubernetes" diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index a45af33c11..5b631698e7 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -8,8 +8,9 @@ import ( "strings" "testing" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) func TestAgentRules(t *testing.T) { diff --git a/control-plane/subcommand/sync-catalog/command.go b/control-plane/subcommand/sync-catalog/command.go index 2dadf6e039..e461121f3d 100644 --- a/control-plane/subcommand/sync-catalog/command.go +++ b/control-plane/subcommand/sync-catalog/command.go @@ -16,6 +16,13 @@ import ( "time" mapset "github.com/deckarep/golang-set" + "github.com/hashicorp/consul-server-connection-manager/discovery" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + catalogtoconsul "github.com/hashicorp/consul-k8s/control-plane/catalog/to-consul" catalogtok8s "github.com/hashicorp/consul-k8s/control-plane/catalog/to-k8s" "github.com/hashicorp/consul-k8s/control-plane/consul" @@ -23,12 +30,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - "github.com/hashicorp/consul-server-connection-manager/discovery" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) // Command is the command for syncing the K8S and Consul service diff --git a/control-plane/subcommand/sync-catalog/command_ent_test.go b/control-plane/subcommand/sync-catalog/command_ent_test.go index fb6c6c4347..8af712dcbe 100644 --- a/control-plane/subcommand/sync-catalog/command_ent_test.go +++ b/control-plane/subcommand/sync-catalog/command_ent_test.go @@ -13,7 +13,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" @@ -23,6 +22,8 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // Test syncing to a single destination consul namespace. diff --git a/control-plane/subcommand/sync-catalog/command_test.go b/control-plane/subcommand/sync-catalog/command_test.go index 0223931cc1..fdabb957f7 100644 --- a/control-plane/subcommand/sync-catalog/command_test.go +++ b/control-plane/subcommand/sync-catalog/command_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" @@ -19,6 +18,8 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // Test flag validation. diff --git a/control-plane/subcommand/tls-init/command.go b/control-plane/subcommand/tls-init/command.go index c2498a3125..467e4cf89a 100644 --- a/control-plane/subcommand/tls-init/command.go +++ b/control-plane/subcommand/tls-init/command.go @@ -13,16 +13,17 @@ import ( "sync" "time" - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { From db6c3ded83ac28b1d35c3e0ae832938ca0ad9de6 Mon Sep 17 00:00:00 2001 From: Iryna Shustava Date: Fri, 22 Sep 2023 18:19:24 -0600 Subject: [PATCH 402/592] traffic-permissions: fix unit tests to account for mutation hook (#3004) Consul now will default tenancy for traffic permission sources. This PR changes unit tests to account for that --- .../controllersv2/meshconfig_controller_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go index 15543bee59..bc3a097079 100644 --- a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go +++ b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go @@ -92,9 +92,14 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { Sources: []*pbauth.Source{ { IdentityName: "source-identity", + Namespace: common.DefaultConsulNamespace, + Partition: common.DefaultConsulPartition, + Peer: constants.DefaultConsulPeer, }, { Namespace: "the space namespace space", + Partition: common.DefaultConsulPartition, + Peer: constants.DefaultConsulPeer, }, }, DestinationRules: []*pbauth.DestinationRule{ @@ -228,6 +233,8 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { Sources: []*pbauth.Source{ { Namespace: "the space namespace space", + Partition: common.DefaultConsulPartition, + Peer: constants.DefaultConsulPeer, }, }, DestinationRules: []*pbauth.DestinationRule{ @@ -604,6 +611,9 @@ func TestMeshConfigController_doesNotCreateUnownedMeshConfig(t *testing.T) { Sources: v2beta1.Sources{ { IdentityName: "source-identity", + Namespace: common.DefaultConsulNamespace, + Partition: common.DefaultConsulPartition, + Peer: constants.DefaultConsulPeer, }, }, }, @@ -704,6 +714,9 @@ func TestMeshConfigController_doesNotDeleteUnownedConfig(t *testing.T) { Sources: v2beta1.Sources{ { IdentityName: "source-identity", + Namespace: common.DefaultConsulNamespace, + Partition: common.DefaultConsulPartition, + Peer: constants.DefaultConsulPeer, }, }, }, From c84b059db281867b070461230ebea4caddb416c9 Mon Sep 17 00:00:00 2001 From: John Murret Date: Fri, 22 Sep 2023 17:53:12 -0700 Subject: [PATCH 403/592] Add explicit upstreams to multiport acceptance test (#2986) * added explicit upstream write/delete tests - most of the tests cases are based on what is currently in the endpoint controller - split some of the write cases into just testing the processing logic so that we don't have to spin up a Consul client each time which made the test very slow. --- .../kind_acceptance_test_packages.yaml | 1 + .../kustomization.yaml | 9 +++++ .../cases/v2-static-client-inject/patch.yaml | 13 +++++++ .../mesh => tests/mesh_v2}/main_test.go | 2 +- .../mesh_v2}/mesh_inject_test.go | 39 +++++++++++++------ 5 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml rename acceptance/{tests-v2/mesh => tests/mesh_v2}/main_test.go (94%) rename acceptance/{tests-v2/mesh => tests/mesh_v2}/mesh_inject_test.go (63%) diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml index e0e126bbda..eaf1dae73d 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -8,3 +8,4 @@ - {runner: 4, test-packages: "cli vault metrics"} - {runner: 5, test-packages: "api-gateway ingress-gateway sync example consul-dns"} - {runner: 6, test-packages: "config-entries terminating-gateway basic"} +- {runner: 7, test-packages: "mesh_v2"} diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml new file mode 100644 index 0000000000..564d02a68f --- /dev/null +++ b/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml @@ -0,0 +1,9 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../bases/static-client +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml new file mode 100644 index 0000000000..41b3f192f8 --- /dev/null +++ b/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + template: + metadata: + annotations: + "consul.hashicorp.com/mesh-inject": "true" + "consul.hashicorp.com/mesh-service-destinations": "web.port.multiport.svc:1234,admin.port.multiport.svc:2345" \ No newline at end of file diff --git a/acceptance/tests-v2/mesh/main_test.go b/acceptance/tests/mesh_v2/main_test.go similarity index 94% rename from acceptance/tests-v2/mesh/main_test.go rename to acceptance/tests/mesh_v2/main_test.go index 6889dfbd13..d510056a10 100644 --- a/acceptance/tests-v2/mesh/main_test.go +++ b/acceptance/tests/mesh_v2/main_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package mesh +package mesh_v2 import ( "os" diff --git a/acceptance/tests-v2/mesh/mesh_inject_test.go b/acceptance/tests/mesh_v2/mesh_inject_test.go similarity index 63% rename from acceptance/tests-v2/mesh/mesh_inject_test.go rename to acceptance/tests/mesh_v2/mesh_inject_test.go index 0ebccbc7ab..723ae2a8e8 100644 --- a/acceptance/tests-v2/mesh/mesh_inject_test.go +++ b/acceptance/tests/mesh_v2/mesh_inject_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package mesh +package mesh_v2 import ( "context" @@ -31,15 +31,12 @@ func TestMeshInject_MultiportService(t *testing.T) { t.Run(name, func(t *testing.T) { cfg := suite.Config() cfg.SkipWhenOpenshiftAndCNI(t) - if !cfg.EnableTransparentProxy { - t.Skipf("skipping this because -enable-transparent-proxy is not set") - } ctx := suite.Environment().DefaultContext(t) helmValues := map[string]string{ - "global.image": "ndhanushkodi/consul-dev:multiport37", - "global.imageK8S": "ndhanushkodi/consul-k8s-dev:multiport25", - "global.imageConsulDataplane": "hashicorppreview/consul-dataplane:1.3-dev", + "global.image": "jmurrethc/consul-dev", + "global.imageK8S": "jmurrethc/consul-k8s-control-plane-dev", + "global.imageConsulDataplane": "jmurrethc/consul-dataplane-dev", "global.experiments[0]": "resource-apis", // The UI is not supported for v2 in 1.17, so for now it must be disabled. "ui.enabled": "false", @@ -58,7 +55,11 @@ func TestMeshInject_MultiportService(t *testing.T) { logger.Log(t, "creating multiport static-server and static-client deployments") k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/bases/v2-multiport-app") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/v2-static-client-inject-tproxy") + if cfg.EnableTransparentProxy { + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/v2-static-client-inject-tproxy") + } else { + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/v2-static-client-inject") + } // Check that static-client has been injected and now has 2 containers. podList, err := ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ @@ -77,10 +78,18 @@ func TestMeshInject_MultiportService(t *testing.T) { require.Len(t, podList.Items[0].Spec.Containers, 3) // Check connection from static-client to multiport. - k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://multiport:8080") + if cfg.EnableTransparentProxy { + k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://multiport:8080") + } else { + k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://localhost:1234") + } // Check connection from static-client to multiport-admin. - k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://multiport:9090") + if cfg.EnableTransparentProxy { + k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://multiport:9090") + } else { + k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://localhost:2345") + } // Test that kubernetes readiness status is synced to Consul. This will make the multi port pods unhealthy // and check inbound connections to the multi port pods' services. @@ -95,8 +104,14 @@ func TestMeshInject_MultiportService(t *testing.T) { // We are expecting a "connection reset by peer" error because in a case of health checks, // there will be no healthy proxy host to connect to. That's why we can't assert that we receive an empty reply // from server, which is the case when a connection is unsuccessful due to intentions in other tests. - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") + if cfg.EnableTransparentProxy { + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") + } else { + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") + + } }) } } From 12aeb304fdce58546f7f6558f256a881108821b3 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 22 Sep 2023 22:01:59 -0700 Subject: [PATCH 404/592] refactored annotation processing (#2996) * refactored annotation processing for reuse --- .../common/annotation_processor.go | 261 +++++ .../common/annotation_processor_test.go | 973 ++++++++++++++++ .../connect-inject/constants/constants.go | 30 + .../constants/constants_test.go | 85 ++ .../controllers/pod/pod_controller.go | 264 +---- .../controllers/pod/pod_controller_test.go | 1013 +---------------- control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- 8 files changed, 1369 insertions(+), 1263 deletions(-) create mode 100644 control-plane/connect-inject/common/annotation_processor.go create mode 100644 control-plane/connect-inject/common/annotation_processor_test.go create mode 100644 control-plane/connect-inject/constants/constants_test.go diff --git a/control-plane/connect-inject/common/annotation_processor.go b/control-plane/connect-inject/common/annotation_processor.go new file mode 100644 index 0000000000..2899b100d9 --- /dev/null +++ b/control-plane/connect-inject/common/annotation_processor.go @@ -0,0 +1,261 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "fmt" + "strings" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + corev1 "k8s.io/api/core/v1" +) + +const ( + ConsulNodeAddress = "127.0.0.1" +) + +// ProcessPodUpstreams reads the list of upstreams from the Pod annotation and converts them into a pbmesh.Upstreams +// object. +func ProcessPodUpstreams(pod corev1.Pod, enablePartitions, enableNamespaces bool) (*pbmesh.Upstreams, error) { + upstreams := &pbmesh.Upstreams{} + raw, ok := pod.Annotations[constants.AnnotationMeshDestinations] + if !ok || raw == "" { + return nil, nil + } + + upstreams.Workloads = &pbcatalog.WorkloadSelector{ + Names: []string{pod.Name}, + } + + for _, raw := range strings.Split(raw, ",") { + var upstream *pbmesh.Upstream + + // Determine the type of processing required unlabeled or labeled + // [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter] + // or + // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] + // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] + // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port] + + // Scan the string for the annotation keys. + // Even if the first key is missing, and the order is unexpected, we should let the processing + // provide us with errors + labeledFormat := false + keys := []string{"port", "svc", "ns", "ap", "peer", "dc"} + for _, v := range keys { + if strings.Contains(raw, fmt.Sprintf(".%s.", v)) || strings.Contains(raw, fmt.Sprintf(".%s:", v)) { + labeledFormat = true + break + } + } + + if labeledFormat { + var err error + upstream, err = processPodLabeledUpstream(pod, raw, enablePartitions, enableNamespaces) + if err != nil { + return &pbmesh.Upstreams{}, err + } + } else { + var err error + upstream, err = processPodUnlabeledUpstream(pod, raw, enablePartitions, enableNamespaces) + if err != nil { + return &pbmesh.Upstreams{}, err + } + } + + upstreams.Upstreams = append(upstreams.Upstreams, upstream) + } + + return upstreams, nil +} + +// processPodLabeledUpstream processes an upstream in the format: +// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] +// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] +// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port]. +// peer/ap/dc are mutually exclusive. At minimum service-port-name and service-name are required. +// The ordering matters for labeled as well as unlabeled. The ordering of the labeled parameters should follow +// the order and requirements of the unlabeled parameters. +// TODO: enable dc and peer support when ready, currently return errors if set. +func processPodLabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Upstream, error) { + parts := strings.SplitN(rawUpstream, ":", 3) + var port int32 + port, _ = PortValue(pod, strings.TrimSpace(parts[1])) + if port <= 0 { + return &pbmesh.Upstream{}, fmt.Errorf("port value %d in upstream is invalid: %s", port, rawUpstream) + } + + service := parts[0] + pieces := strings.Split(service, ".") + + var portName, datacenter, svcName, namespace, partition, peer string + if enablePartitions || enableNamespaces { + switch len(pieces) { + case 8: + end := strings.TrimSpace(pieces[7]) + switch end { + case "peer": + // TODO: uncomment and remove error when peers supported + //peer = strings.TrimSpace(pieces[6]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support peers: %s", rawUpstream) + case "ap": + partition = strings.TrimSpace(pieces[6]) + case "dc": + // TODO: uncomment and remove error when datacenters are supported + //datacenter = strings.TrimSpace(pieces[6]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + fallthrough + case 6: + if strings.TrimSpace(pieces[5]) == "ns" { + namespace = strings.TrimSpace(pieces[4]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + fallthrough + case 4: + if strings.TrimSpace(pieces[3]) == "svc" { + svcName = strings.TrimSpace(pieces[2]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + if strings.TrimSpace(pieces[1]) == "port" { + portName = strings.TrimSpace(pieces[0]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + } else { + switch len(pieces) { + case 6: + end := strings.TrimSpace(pieces[5]) + switch end { + case "peer": + // TODO: uncomment and remove error when peers supported + //peer = strings.TrimSpace(pieces[4]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support peers: %s", rawUpstream) + case "dc": + // TODO: uncomment and remove error when datacenter supported + //datacenter = strings.TrimSpace(pieces[4]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + // TODO: uncomment and remove error when datacenter and/or peers supported + //fallthrough + case 4: + if strings.TrimSpace(pieces[3]) == "svc" { + svcName = strings.TrimSpace(pieces[2]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + if strings.TrimSpace(pieces[1]) == "port" { + portName = strings.TrimSpace(pieces[0]) + } else { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + } + + upstream := pbmesh.Upstream{ + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(partition), + Namespace: constants.GetNormalizedConsulNamespace(namespace), + PeerName: constants.GetNormalizedConsulPeer(peer), + }, + Name: svcName, + }, + DestinationPort: portName, + Datacenter: datacenter, + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(port), + Ip: ConsulNodeAddress, + }, + }, + } + + return &upstream, nil +} + +// processPodUnlabeledUpstream processes an upstream in the format: +// [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. +// There is no unlabeled field for peering. +// TODO: enable dc and peer support when ready, currently return errors if set. +func processPodUnlabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Upstream, error) { + var portName, datacenter, svcName, namespace, partition string + var port int32 + var upstream pbmesh.Upstream + + parts := strings.SplitN(rawUpstream, ":", 3) + + port, _ = PortValue(pod, strings.TrimSpace(parts[1])) + + // If Consul Namespaces or Admin Partitions are enabled, attempt to parse the + // upstream for a namespace. + if enableNamespaces || enablePartitions { + pieces := strings.SplitN(parts[0], ".", 4) + switch len(pieces) { + case 4: + partition = strings.TrimSpace(pieces[3]) + fallthrough + case 3: + namespace = strings.TrimSpace(pieces[2]) + fallthrough + case 2: + svcName = strings.TrimSpace(pieces[1]) + portName = strings.TrimSpace(pieces[0]) + default: + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + } else { + pieces := strings.SplitN(parts[0], ".", 2) + if len(pieces) < 2 { + return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + } + svcName = strings.TrimSpace(pieces[1]) + portName = strings.TrimSpace(pieces[0]) + } + + // parse the optional datacenter + if len(parts) > 2 { + // TODO: uncomment and remove error when datacenters supported + //datacenter = strings.TrimSpace(parts[2]) + return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + } + + if port > 0 { + upstream = pbmesh.Upstream{ + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(partition), + Namespace: constants.GetNormalizedConsulNamespace(namespace), + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: svcName, + }, + DestinationPort: portName, + Datacenter: datacenter, + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(port), + Ip: ConsulNodeAddress, + }, + }, + } + } + return &upstream, nil +} diff --git a/control-plane/connect-inject/common/annotation_processor_test.go b/control-plane/connect-inject/common/annotation_processor_test.go new file mode 100644 index 0000000000..35e50ae5c1 --- /dev/null +++ b/control-plane/connect-inject/common/annotation_processor_test.go @@ -0,0 +1,973 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul/api" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestProcessUpstreams(t *testing.T) { + t.Parallel() + + const podName = "pod1" + + cases := []struct { + name string + pod func() *corev1.Pod + expected *pbmesh.Upstreams + expErr string + configEntry func() api.ConfigEntry + consulUnavailable bool + consulNamespacesEnabled bool + consulPartitionsEnabled bool + }{ + { + name: "labeled annotated upstream with svc only", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc:1234") + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: ConsulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc and dc", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.dc1.dc:1234") + return pod1 + }, + expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.dc1.dc:1234", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: constants.GetNormalizedConsulNamespace(""), + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "dc1", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc and peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.peer:1234") + return pod1 + }, + expErr: "upstream currently does not support peers: myPort.port.upstream1.svc.peer1.peer:1234", + // TODO: uncomment this and remove expErr when peers is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: constants.GetNormalizedConsulNamespace(""), + // PeerName: "peer1", + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc, ns, and peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234") + return pod1 + }, + expErr: "upstream currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", + // TODO: uncomment this and remove expErr when peers is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: "ns1", + // PeerName: "peer1", + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "labeled annotated upstream with svc, ns, and partition", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234") + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: "part1", + Namespace: "ns1", + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: ConsulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "labeled annotated upstream with svc, ns, and dc", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234") + return pod1 + }, + expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: "ns1", + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "dc1", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "labeled multiple annotated upstreams", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.port.upstream2.svc:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234") + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: "ns1", + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: ConsulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream2", + }, + DestinationPort: "myPort2", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(2234), + Ip: ConsulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: "ap1", + Namespace: "ns1", + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream4", + }, + DestinationPort: "myPort4", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(4234), + Ip: ConsulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "labeled multiple annotated upstreams with dcs and peers", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234, myPort2.port.upstream2.svc:2234, myPort3.port.upstream3.svc.ns1.ns:3234, myPort4.port.upstream4.svc.ns1.ns.peer1.peer:4234") + return pod1 + }, + expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: "ns1", + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "dc1", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: constants.GetNormalizedConsulNamespace(""), + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream2", + // }, + // DestinationPort: "myPort2", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(2234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: "ns1", + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream3", + // }, + // DestinationPort: "myPort3", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(3234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: "ns1", + // PeerName: "peer1", + // }, + // Name: "upstream4", + // }, + // DestinationPort: "myPort4", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(4234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: invalid partition/dc/peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.err:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream with svc and peer, needs ns before peer if namespaces enabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.peer:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.peer1.peer:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: invalid namespace", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.err:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.err:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: invalid number of pieces in the address", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.err:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.err:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: invalid peer", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.err:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.peer1.err:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: invalid number of pieces in the address without namespaces and partitions", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.err:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.err:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: both peer and partition provided", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: both peer and dc provided", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: both dc and partition provided", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition enabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition disabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "upstream1.svc.myPort.port:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: upstream1.svc.myPort.port:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "error labeled annotated upstream error: incorrect key name namespace partition enabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "error labeled annotated upstream error: incorrect key name namespace partition disabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.portage.upstream1.svc:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: myPort.portage.upstream1.svc:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled and labeled multiple annotated upstreams", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.upstream2:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234") + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: "ns1", + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: ConsulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream2", + }, + DestinationPort: "myPort2", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(2234), + Ip: ConsulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: "ap1", + Namespace: "ns1", + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream4", + }, + DestinationPort: "myPort4", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(4234), + Ip: ConsulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "unlabeled single upstream", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.upstream:1234") + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: ConsulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled single upstream with namespace", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.upstream.foo:1234") + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: "foo", + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: ConsulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled single upstream with namespace and partition", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.upstream.foo.bar:1234") + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: "bar", + Namespace: "foo", + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: ConsulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "unlabeled multiple upstreams", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2:2234") + return pod1 + }, + expected: &pbmesh.Upstreams{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{podName}, + }, + Upstreams: []*pbmesh.Upstream{ + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream1", + }, + DestinationPort: "myPort", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(1234), + Ip: ConsulNodeAddress, + }, + }, + }, + { + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), + }, + Name: "upstream2", + }, + DestinationPort: "myPort2", + Datacenter: "", + ListenAddr: &pbmesh.Upstream_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Port: uint32(2234), + Ip: ConsulNodeAddress, + }, + }, + }, + }, + }, + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "unlabeled multiple upstreams with consul namespaces, partitions and datacenters", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo.baz:3234:dc2") + return pod1 + }, + configEntry: func() api.ConfigEntry { + ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") + pd := ce.(*api.ProxyConfigEntry) + pd.MeshGateway.Mode = "remote" + return pd + }, + expErr: "upstream currently does not support datacenters: myPort3.upstream3.foo.baz:3234:dc2", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: constants.GetNormalizedConsulNamespace(""), + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: "bar", + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream2", + // }, + // DestinationPort: "myPort2", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(2234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: "baz", + // Namespace: "foo", + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream3", + // }, + // DestinationPort: "myPort3", + // Datacenter: "dc2", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(3234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, + { + name: "unlabeled multiple upstreams with consul namespaces and datacenters", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo:3234:dc2") + return pod1 + }, + configEntry: func() api.ConfigEntry { + ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") + pd := ce.(*api.ProxyConfigEntry) + pd.MeshGateway.Mode = "remote" + return pd + }, + expErr: "upstream currently does not support datacenters: myPort3.upstream3.foo:3234:dc2", + // TODO: uncomment this and remove expErr when datacenters is supported + //expected: &pbmesh.Upstreams{ + // Workloads: &pbcatalog.WorkloadSelector{ + // Names: []string{podName}, + // }, + // Upstreams: []*pbmesh.Upstream{ + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: constants.GetNormalizedConsulNamespace(""), + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream1", + // }, + // DestinationPort: "myPort", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(1234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: "bar", + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream2", + // }, + // DestinationPort: "myPort2", + // Datacenter: "", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(2234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // { + // DestinationRef: &pbresource.Reference{ + // Type: pbcatalog.ServiceType, + // Tenancy: &pbresource.Tenancy{ + // Partition: constants.GetNormalizedConsulPartition(""), + // Namespace: "foo", + // PeerName: constants.GetNormalizedConsulPeer(""), + // }, + // Name: "upstream3", + // }, + // DestinationPort: "myPort3", + // Datacenter: "dc2", + // ListenAddr: &pbmesh.Upstream_IpPort{ + // IpPort: &pbmesh.IPPortAddress{ + // Port: uint32(3234), + // Ip: ConsulNodeAddress, + // }, + // }, + // }, + // }, + //}, + consulNamespacesEnabled: true, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + upstreams, err := ProcessPodUpstreams(*tt.pod(), tt.consulNamespacesEnabled, tt.consulPartitionsEnabled) + if tt.expErr != "" { + require.EqualError(t, err, tt.expErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, upstreams) + + if diff := cmp.Diff(tt.expected, upstreams, protocmp.Transform()); diff != "" { + t.Errorf("unexpected difference:\n%v", diff) + } + } + }) + } +} + +// createPod creates a multi-port pod as a base for tests. +func createPod(name string, annotation string) *corev1.Pod { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + pod.Annotations = map[string]string{ + constants.AnnotationMeshDestinations: annotation, + } + return pod +} diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 0729207d24..d4bafadc77 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -54,3 +54,33 @@ const ( KubernetesSuccessReasonMsg = "Kubernetes health checks passing" ) + +// GetNormalizedConsulNamespace returns the default namespace if the passed namespace +// is empty, otherwise returns back the passed in namespace. +func GetNormalizedConsulNamespace(ns string) string { + if ns == "" { + ns = DefaultConsulNS + } + + return ns +} + +// GetNormalizedConsulPartition returns the default partition if the passed partition +// is empty, otherwise returns back the passed in partition. +func GetNormalizedConsulPartition(ap string) string { + if ap == "" { + ap = DefaultConsulPartition + } + + return ap +} + +// GetNormalizedConsulPeer returns the default peer if the passed peer +// is empty, otherwise returns back the passed in peer. +func GetNormalizedConsulPeer(peer string) string { + if peer == "" { + peer = DefaultConsulPeer + } + + return peer +} diff --git a/control-plane/connect-inject/constants/constants_test.go b/control-plane/connect-inject/constants/constants_test.go new file mode 100644 index 0000000000..2637c3b7d3 --- /dev/null +++ b/control-plane/connect-inject/constants/constants_test.go @@ -0,0 +1,85 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package constants + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetNormalizedConsulNamespace(t *testing.T) { + tests := []struct { + name string + value string + expect string + }{ + { + name: "expect contant", + value: "", + expect: DefaultConsulNS, + }, + { + name: "expect passed in value", + value: "some-value", + expect: "some-value", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actual := GetNormalizedConsulNamespace(tc.value) + require.Equal(t, actual, tc.expect) + }) + } +} + +func TestGetNormalizedConsulPartition(t *testing.T) { + tests := []struct { + name string + value string + expect string + }{ + { + name: "expect contant", + value: "", + expect: DefaultConsulPartition, + }, + { + name: "expect passed in value", + value: "some-value", + expect: "some-value", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actual := GetNormalizedConsulPartition(tc.value) + require.Equal(t, actual, tc.expect) + }) + } +} + +func TestGetNormalizedConsulPeer(t *testing.T) { + tests := []struct { + name string + value string + expect string + }{ + { + name: "expect contant", + value: "", + expect: DefaultConsulPeer, + }, + { + name: "expect passed in value", + value: "some-value", + expect: "some-value", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actual := GetNormalizedConsulPeer(tc.value) + require.Equal(t, actual, tc.expect) + }) + } +} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 96dde403b1..b101b8c745 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "strconv" - "strings" "github.com/go-logr/logr" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" @@ -452,7 +451,7 @@ func (r *Controller) writeHealthStatus(ctx context.Context, pod corev1.Pod) erro // writeUpstreams will write explicit upstreams if pod annotations exist. func (r *Controller) writeUpstreams(ctx context.Context, pod corev1.Pod) error { - uss, err := r.processUpstreams(pod) + uss, err := inject.ProcessPodUpstreams(pod, r.EnableConsulPartitions, r.EnableConsulNamespaces) if err != nil { return fmt.Errorf("error processing upstream annotations: %s", err.Error()) } @@ -482,243 +481,6 @@ func (r *Controller) deleteUpstreams(ctx context.Context, pod types.NamespacedNa return err } -// processUpstreams reads the list of upstreams from the Pod annotation and converts them into a list of pbmesh.Upstreams -// objects. -func (r *Controller) processUpstreams(pod corev1.Pod) (*pbmesh.Upstreams, error) { - upstreams := &pbmesh.Upstreams{} - raw, ok := pod.Annotations[constants.AnnotationMeshDestinations] - if !ok || raw == "" { - return nil, nil - } - - upstreams.Workloads = &pbcatalog.WorkloadSelector{ - Names: []string{pod.Name}, - } - - for _, raw := range strings.Split(raw, ",") { - var upstream *pbmesh.Upstream - - // Determine the type of processing required unlabeled or labeled - // [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter] - // or - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port] - - // Scan the string for the annotation keys. - // Even if the first key is missing, and the order is unexpected, we should let the processing - // provide us with errors - labeledFormat := false - keys := []string{"port", "svc", "ns", "ap", "peer", "dc"} - for _, v := range keys { - if strings.Contains(raw, fmt.Sprintf(".%s.", v)) || strings.Contains(raw, fmt.Sprintf(".%s:", v)) { - labeledFormat = true - break - } - } - - if labeledFormat { - var err error - upstream, err = r.processLabeledUpstream(pod, raw) - if err != nil { - return &pbmesh.Upstreams{}, err - } - } else { - var err error - upstream, err = r.processUnlabeledUpstream(pod, raw) - if err != nil { - return &pbmesh.Upstreams{}, err - } - } - - upstreams.Upstreams = append(upstreams.Upstreams, upstream) - } - - return upstreams, nil -} - -// processLabeledUpstream processes an upstream in the format: -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port]. -// peer/ap/dc are mutually exclusive. At minimum service-port-name and service-name are required. -// The ordering matters for labeled as well as unlabeled. The ordering of the labeled parameters should follow -// the order and requirements of the unlabeled parameters. -// TODO: enable dc and peer support when ready, currently return errors if set. -func (r *Controller) processLabeledUpstream(pod corev1.Pod, rawUpstream string) (*pbmesh.Upstream, error) { - parts := strings.SplitN(rawUpstream, ":", 3) - var port int32 - port, _ = inject.PortValue(pod, strings.TrimSpace(parts[1])) - if port <= 0 { - return &pbmesh.Upstream{}, fmt.Errorf("port value %d in upstream is invalid: %s", port, rawUpstream) - } - - service := parts[0] - pieces := strings.Split(service, ".") - - var portName, datacenter, svcName, namespace, partition, peer string - if r.EnableConsulNamespaces || r.EnableConsulPartitions { - switch len(pieces) { - case 8: - end := strings.TrimSpace(pieces[7]) - switch end { - case "peer": - // TODO: uncomment and remove error when peers supported - //peer = strings.TrimSpace(pieces[6]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support peers: %s", rawUpstream) - case "ap": - partition = strings.TrimSpace(pieces[6]) - case "dc": - // TODO: uncomment and remove error when datacenters are supported - //datacenter = strings.TrimSpace(pieces[6]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) - default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - fallthrough - case 6: - if strings.TrimSpace(pieces[5]) == "ns" { - namespace = strings.TrimSpace(pieces[4]) - } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - fallthrough - case 4: - if strings.TrimSpace(pieces[3]) == "svc" { - svcName = strings.TrimSpace(pieces[2]) - } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - if strings.TrimSpace(pieces[1]) == "port" { - portName = strings.TrimSpace(pieces[0]) - } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - } else { - switch len(pieces) { - case 6: - end := strings.TrimSpace(pieces[5]) - switch end { - case "peer": - // TODO: uncomment and remove error when peers supported - //peer = strings.TrimSpace(pieces[4]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support peers: %s", rawUpstream) - case "dc": - // TODO: uncomment and remove error when datacenter supported - //datacenter = strings.TrimSpace(pieces[4]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) - default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - // TODO: uncomment and remove error when datacenter and/or peers supported - //fallthrough - case 4: - if strings.TrimSpace(pieces[3]) == "svc" { - svcName = strings.TrimSpace(pieces[2]) - } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - if strings.TrimSpace(pieces[1]) == "port" { - portName = strings.TrimSpace(pieces[0]) - } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) - } - } - - upstream := pbmesh.Upstream{ - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(partition), - Namespace: getDefaultConsulNamespace(namespace), - PeerName: getDefaultConsulPeer(peer), - }, - Name: svcName, - }, - DestinationPort: portName, - Datacenter: datacenter, - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(port), - Ip: consulNodeAddress, - }, - }, - } - - return &upstream, nil -} - -// processUnlabeledUpstream processes an upstream in the format: -// [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. -// There is no unlabeled field for peering. -// TODO: enable dc and peer support when ready, currently return errors if set. We also most likely won't need to return an error at all. -func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string) (*pbmesh.Upstream, error) { - var portName, datacenter, svcName, namespace, partition string - var port int32 - var upstream pbmesh.Upstream - - parts := strings.SplitN(rawUpstream, ":", 3) - - port, _ = inject.PortValue(pod, strings.TrimSpace(parts[1])) - - // If Consul Namespaces or Admin Partitions are enabled, attempt to parse the - // upstream for a namespace. - if r.EnableConsulNamespaces || r.EnableConsulPartitions { - pieces := strings.SplitN(parts[0], ".", 4) - switch len(pieces) { - case 4: - partition = strings.TrimSpace(pieces[3]) - fallthrough - case 3: - namespace = strings.TrimSpace(pieces[2]) - fallthrough - default: - svcName = strings.TrimSpace(pieces[1]) - portName = strings.TrimSpace(pieces[0]) - } - } else { - pieces := strings.SplitN(parts[0], ".", 2) - svcName = strings.TrimSpace(pieces[1]) - portName = strings.TrimSpace(pieces[0]) - } - - // parse the optional datacenter - if len(parts) > 2 { - // TODO: uncomment and remove error when datacenters supported - //datacenter = strings.TrimSpace(parts[2]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) - } - - if port > 0 { - upstream = pbmesh.Upstream{ - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(partition), - Namespace: getDefaultConsulNamespace(namespace), - PeerName: getDefaultConsulPeer(""), - }, - Name: svcName, - }, - DestinationPort: portName, - Datacenter: datacenter, - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(port), - Ip: consulNodeAddress, - }, - }, - } - } - return &upstream, nil -} - // consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace // depending on Consul Namespaces being enabled and the value of namespace mirroring. func (r *Controller) getConsulNamespace(kubeNamespace string) string { @@ -889,27 +651,3 @@ func getUpstreamsID(name, namespace, partition string) *pbresource.ID { }, } } - -func getDefaultConsulNamespace(ns string) string { - if ns == "" { - ns = constants.DefaultConsulNS - } - - return ns -} - -func getDefaultConsulPartition(ap string) string { - if ap == "" { - ap = constants.DefaultConsulPartition - } - - return ap -} - -func getDefaultConsulPeer(peer string) string { - if peer == "" { - peer = constants.DefaultConsulPeer - } - - return peer -} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 74031f9b3a..8928b9fc18 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -792,9 +792,9 @@ func TestUpstreamsWrite(t *testing.T) { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), }, Name: "upstream1", }, @@ -830,7 +830,7 @@ func TestUpstreamsWrite(t *testing.T) { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), + // Partition: constants.GetNormalizedConsulPartition(""), // Namespace: "ns1", // PeerName: "peer1", // }, @@ -868,7 +868,7 @@ func TestUpstreamsWrite(t *testing.T) { Tenancy: &pbresource.Tenancy{ Partition: "part1", Namespace: "ns1", - PeerName: getDefaultConsulPeer(""), + PeerName: constants.GetNormalizedConsulPeer(""), }, Name: "upstream1", }, @@ -913,9 +913,9 @@ func TestUpstreamsWrite(t *testing.T) { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), }, Name: "upstream", }, @@ -951,7 +951,7 @@ func TestUpstreamsWrite(t *testing.T) { Tenancy: &pbresource.Tenancy{ Partition: "bar", Namespace: "foo", - PeerName: getDefaultConsulPeer(""), + PeerName: constants.GetNormalizedConsulPeer(""), }, Name: "upstream", }, @@ -1005,987 +1005,6 @@ func TestUpstreamsWrite(t *testing.T) { } } -func TestProcessUpstreams(t *testing.T) { - t.Parallel() - - const podName = "pod1" - - cases := []struct { - name string - pod func() *corev1.Pod - expected *pbmesh.Upstreams - expErr string - configEntry func() api.ConfigEntry - consulUnavailable bool - consulNamespacesEnabled bool - consulPartitionsEnabled bool - }{ - { - name: "labeled annotated upstream with svc only", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" - return pod1 - }, - expected: &pbmesh.Upstreams{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Upstreams: []*pbmesh.Upstream{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated upstream with svc and dc", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.dc1.dc:1234" - return pod1 - }, - expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Upstream{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: getDefaultConsulNamespace(""), - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated upstream with svc and peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.peer:1234" - return pod1 - }, - expErr: "upstream currently does not support peers: myPort.port.upstream1.svc.peer1.peer:1234", - // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Upstreams{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Upstream{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: getDefaultConsulNamespace(""), - // PeerName: "peer1", - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated upstream with svc, ns, and peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234" - return pod1 - }, - expErr: "upstream currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", - // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Upstreams{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Upstream{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: "ns1", - // PeerName: "peer1", - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated upstream with svc, ns, and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234" - return pod1 - }, - expected: &pbmesh.Upstreams{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Upstreams: []*pbmesh.Upstream{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "part1", - Namespace: "ns1", - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "labeled annotated upstream with svc, ns, and dc", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234" - return pod1 - }, - expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Upstream{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: "ns1", - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "labeled multiple annotated upstreams", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.port.upstream2.svc:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234" - return pod1 - }, - expected: &pbmesh.Upstreams{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Upstreams: []*pbmesh.Upstream{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: "ns1", - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: consulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "ap1", - Namespace: "ns1", - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream4", - }, - DestinationPort: "myPort4", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(4234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "labeled multiple annotated upstreams with dcs and peers", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234, myPort2.port.upstream2.svc:2234, myPort3.port.upstream3.svc.ns1.ns:3234, myPort4.port.upstream4.svc.ns1.ns.peer1.peer:4234" - return pod1 - }, - expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Upstream{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: "ns1", - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: getDefaultConsulNamespace(""), - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: "ns1", - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: "ns1", - // PeerName: "peer1", - // }, - // Name: "upstream4", - // }, - // DestinationPort: "myPort4", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(4234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated upstream error: invalid partition/dc/peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.err:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated upstream with svc and peer, needs ns before peer if namespaces enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.peer:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.peer1.peer:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated upstream error: invalid namespace", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.err:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated upstream error: invalid number of pieces in the address", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.err:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated upstream error: invalid peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.peer1.err:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.peer1.err:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated upstream error: invalid number of pieces in the address without namespaces and partitions", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.err:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.err:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated upstream error: both peer and partition provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated upstream error: both peer and dc provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated upstream error: both dc and partition provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition disabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "upstream1.svc.myPort.port:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: upstream1.svc.myPort.port:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated upstream error: incorrect key name namespace partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated upstream error: incorrect key name namespace partition disabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.portage.upstream1.svc:1234" - return pod1 - }, - expErr: "upstream structured incorrectly: myPort.portage.upstream1.svc:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled and labeled multiple annotated upstreams", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.upstream2:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234" - return pod1 - }, - expected: &pbmesh.Upstreams{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Upstreams: []*pbmesh.Upstream{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: "ns1", - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: consulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "ap1", - Namespace: "ns1", - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream4", - }, - DestinationPort: "myPort4", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(4234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled single upstream", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream:1234" - return pod1 - }, - expected: &pbmesh.Upstreams{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Upstreams: []*pbmesh.Upstream{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single upstream with namespace", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo:1234" - return pod1 - }, - expected: &pbmesh.Upstreams{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Upstreams: []*pbmesh.Upstream{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: "foo", - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single upstream with namespace and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo.bar:1234" - return pod1 - }, - expected: &pbmesh.Upstreams{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Upstreams: []*pbmesh.Upstream{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "bar", - Namespace: "foo", - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled multiple upstreams", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2:2234" - return pod1 - }, - expected: &pbmesh.Upstreams{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Upstreams: []*pbmesh.Upstream{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled multiple upstreams with consul namespaces, partitions and datacenters", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo.baz:3234:dc2" - return pod1 - }, - configEntry: func() api.ConfigEntry { - ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") - pd := ce.(*api.ProxyConfigEntry) - pd.MeshGateway.Mode = "remote" - return pd - }, - expErr: "upstream currently does not support datacenters: myPort3.upstream3.foo.baz:3234:dc2", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Upstream{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: getDefaultConsulNamespace(""), - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: "bar", - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: "baz", - // Namespace: "foo", - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "dc2", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled multiple upstreams with consul namespaces and datacenters", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo:3234:dc2" - return pod1 - }, - configEntry: func() api.ConfigEntry { - ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") - pd := ce.(*api.ProxyConfigEntry) - pd.MeshGateway.Mode = "remote" - return pd - }, - expErr: "upstream currently does not support datacenters: myPort3.upstream3.foo:3234:dc2", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Upstream{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: getDefaultConsulNamespace(""), - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: "bar", - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: getDefaultConsulPartition(""), - // Namespace: "foo", - // PeerName: getDefaultConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "dc2", - // ListenAddr: &pbmesh.Upstream_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - pc := &Controller{ - Log: logrtest.New(t), - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ConsulTenancyConfig: common.ConsulTenancyConfig{ - EnableConsulNamespaces: tt.consulNamespacesEnabled, - EnableConsulPartitions: tt.consulPartitionsEnabled, - }, - } - - upstreams, err := pc.processUpstreams(*tt.pod()) - if tt.expErr != "" { - require.EqualError(t, err, tt.expErr) - } else { - require.NoError(t, err) - require.Equal(t, tt.expected, upstreams) - - if diff := cmp.Diff(tt.expected, upstreams, protocmp.Transform()); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - } - }) - } -} - func TestUpstreamsDelete(t *testing.T) { t.Parallel() @@ -2015,9 +1034,9 @@ func TestUpstreamsDelete(t *testing.T) { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), }, Name: "upstream1", }, @@ -2507,7 +1526,7 @@ func TestReconcileUpdatePod(t *testing.T) { Tenancy: &pbresource.Tenancy{ Partition: "ap1", Namespace: "ns1", - PeerName: getDefaultConsulPeer(""), + PeerName: constants.GetNormalizedConsulPeer(""), }, Name: "mySVC3", }, @@ -2860,9 +1879,9 @@ func createUpstreams() *pbmesh.Upstreams { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ - Partition: getDefaultConsulPartition(""), - Namespace: getDefaultConsulNamespace(""), - PeerName: getDefaultConsulPeer(""), + Partition: constants.GetNormalizedConsulPartition(""), + Namespace: constants.GetNormalizedConsulNamespace(""), + PeerName: constants.GetNormalizedConsulPeer(""), }, Name: "mySVC", }, diff --git a/control-plane/go.mod b/control-plane/go.mod index 56df356f55..920e3b3907 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases replace ( // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version - github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230921190229-d8ac6ee34f5e + github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230922204015-ac9209d8fba9 // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd ) diff --git a/control-plane/go.sum b/control-plane/go.sum index 85ec362227..8d35f5daaf 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNb github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 h1:LnzgDq4e7ZfM1+XS6S21B9taQrbfdydXenL1xHyG1PQ= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230921190229-d8ac6ee34f5e h1:DY0NJAjjFze8PxZjTsVYLCrNBxTZKqy21MY6LDfBvig= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230921190229-d8ac6ee34f5e/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230922204015-ac9209d8fba9 h1:3Xux09euVvBRlu66yJaPVasZf+OxFUlmCBzaigHwtEs= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230922204015-ac9209d8fba9/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd h1:tRrSgVY71Jl6T2lJUokMLj3T1MO9uiSvW0CieBkjTvo= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= From 8c1904b3e2254c3a5a4853eb4026967740245970 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 25 Sep 2023 09:07:58 -0400 Subject: [PATCH 405/592] Add acceptance test for traffic permissions (#2998) --- .../trafficpermissions/kustomization.yaml | 5 +++++ .../trafficpermissions.yaml | 13 +++++++++++++ .../kustomization.yaml | 9 +++++++++ .../cases/trafficpermissions-deny/patch.yaml | 9 +++++++++ acceptance/tests/mesh_v2/mesh_inject_test.go | 19 ++++++++++++++++--- 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml create mode 100644 acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml create mode 100644 acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml diff --git a/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml b/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml new file mode 100644 index 0000000000..249cb948bb --- /dev/null +++ b/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml @@ -0,0 +1,5 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resources: + - trafficpermissions.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml b/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml new file mode 100644 index 0000000000..48d2ace187 --- /dev/null +++ b/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: auth.consul.hashicorp.com/v2beta1 +kind: TrafficPermissions +metadata: + name: client-to-server +spec: + destination: + identityName: multiport + action: allow + sources: + - identityName: static-client diff --git a/acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml b/acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml new file mode 100644 index 0000000000..4d00c57dfd --- /dev/null +++ b/acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml @@ -0,0 +1,9 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../bases/trafficpermissions +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml b/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml new file mode 100644 index 0000000000..037859194f --- /dev/null +++ b/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml @@ -0,0 +1,9 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: auth.consul.hashicorp.com/v2beta1 +kind: TrafficPermissions +metadata: + name: client-to-server +spec: + action: deny diff --git a/acceptance/tests/mesh_v2/mesh_inject_test.go b/acceptance/tests/mesh_v2/mesh_inject_test.go index 723ae2a8e8..2ee713e520 100644 --- a/acceptance/tests/mesh_v2/mesh_inject_test.go +++ b/acceptance/tests/mesh_v2/mesh_inject_test.go @@ -34,8 +34,8 @@ func TestMeshInject_MultiportService(t *testing.T) { ctx := suite.Environment().DefaultContext(t) helmValues := map[string]string{ - "global.image": "jmurrethc/consul-dev", - "global.imageK8S": "jmurrethc/consul-k8s-control-plane-dev", + "global.image": "thisisnotashwin/consul:foo", + "global.imageK8S": "thisisnotashwin/consul-k8s:foo", "global.imageConsulDataplane": "jmurrethc/consul-dataplane-dev", "global.experiments[0]": "resource-apis", // The UI is not supported for v2 in 1.17, so for now it must be disabled. @@ -77,6 +77,20 @@ func TestMeshInject_MultiportService(t *testing.T) { require.Len(t, podList.Items, 1) require.Len(t, podList.Items[0].Spec.Containers, 3) + { + // TODO: once ACLs are implemented, only run this block when secure=true and delete the below line and fixture since the default is deny when secure is true. + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/trafficpermissions-deny") + // Now test that traffic is denied between the source and the destination. + if cfg.EnableTransparentProxy { + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") + } else { + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:2345") + } + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/bases/trafficpermissions") + } + // Check connection from static-client to multiport. if cfg.EnableTransparentProxy { k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://multiport:8080") @@ -110,7 +124,6 @@ func TestMeshInject_MultiportService(t *testing.T) { } else { k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") - } }) } From 756c37fefecd291e7d312a422034a15670387c46 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Mon, 25 Sep 2023 08:56:31 -0700 Subject: [PATCH 406/592] Mw/net 5679 explicit upstreams mesh webhook injects upstream env variable (#2995) * added logic for injecting environment variable based on annotations * fixed a bug in annotation processor - nil pointer exception because we weren't checking for a length of two in the unlabeled case - added a test to make sure it's covered * added container env logic for injecting env variables - due to refactoring can support same labeled/unlabeld parsing logic as when pod controller processes the annotations * added an error check to the `containerEnvVars(pod)` call --- .../common/annotation_processor.go | 9 +- .../common/annotation_processor_test.go | 40 +++++ .../connect-inject/webhookv2/container_env.go | 59 ++++---- .../webhookv2/container_env_test.go | 58 ++++--- .../connect-inject/webhookv2/mesh_webhook.go | 6 +- .../webhookv2/mesh_webhook_test.go | 143 ++++++++++-------- 6 files changed, 207 insertions(+), 108 deletions(-) diff --git a/control-plane/connect-inject/common/annotation_processor.go b/control-plane/connect-inject/common/annotation_processor.go index 2899b100d9..3dcb23842e 100644 --- a/control-plane/connect-inject/common/annotation_processor.go +++ b/control-plane/connect-inject/common/annotation_processor.go @@ -7,17 +7,24 @@ import ( "fmt" "strings" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" corev1 "k8s.io/api/core/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) const ( ConsulNodeAddress = "127.0.0.1" ) +// ProcessPodUpstreamsForMeshWebhook reads the list of upstreams from the Pod annotation and converts them into a pbmesh.Upstreams +// object. +func ProcessPodUpstreamsForMeshWebhook(pod corev1.Pod) (*pbmesh.Upstreams, error) { + return ProcessPodUpstreams(pod, true, true) +} + // ProcessPodUpstreams reads the list of upstreams from the Pod annotation and converts them into a pbmesh.Upstreams // object. func ProcessPodUpstreams(pod corev1.Pod, enablePartitions, enableNamespaces bool) (*pbmesh.Upstreams, error) { diff --git a/control-plane/connect-inject/common/annotation_processor_test.go b/control-plane/connect-inject/common/annotation_processor_test.go index 35e50ae5c1..f5d2788488 100644 --- a/control-plane/connect-inject/common/annotation_processor_test.go +++ b/control-plane/connect-inject/common/annotation_processor_test.go @@ -548,6 +548,26 @@ func TestProcessUpstreams(t *testing.T) { consulNamespacesEnabled: false, consulPartitionsEnabled: false, }, + { + name: "error labeled missing port name", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "upstream1.svc:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: upstream1.svc:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "error labeled missing port name namespace partition enabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "upstream1.svc:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: upstream1.svc:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, { name: "unlabeled and labeled multiple annotated upstreams", pod: func() *corev1.Pod { @@ -941,6 +961,26 @@ func TestProcessUpstreams(t *testing.T) { //}, consulNamespacesEnabled: true, }, + { + name: "error unlabeled missing port name with namespace and partition disabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "upstream1:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: upstream1:1234", + consulNamespacesEnabled: false, + consulPartitionsEnabled: false, + }, + { + name: "error unlabeled missing port name with namespace and partition enabled", + pod: func() *corev1.Pod { + pod1 := createPod(podName, "upstream1:1234") + return pod1 + }, + expErr: "upstream structured incorrectly: upstream1:1234", + consulNamespacesEnabled: true, + consulPartitionsEnabled: true, + }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { diff --git a/control-plane/connect-inject/webhookv2/container_env.go b/control-plane/connect-inject/webhookv2/container_env.go index f7df9ded92..568a07f124 100644 --- a/control-plane/connect-inject/webhookv2/container_env.go +++ b/control-plane/connect-inject/webhookv2/container_env.go @@ -4,34 +4,39 @@ package webhookv2 import ( + "fmt" + "strconv" + "strings" + corev1 "k8s.io/api/core/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" ) -func (w *MeshWebhook) containerEnvVars(pod corev1.Pod) []corev1.EnvVar { - // (TODO: ashwin) make this work with current upstreams - //raw, ok := pod.Annotations[constants.AnnotationMeshDestinations] - //if !ok || raw == "" { - // return []corev1.EnvVar{} - //} - // - //var result []corev1.EnvVar - //for _, raw := range strings.Split(raw, ",") { - // parts := strings.SplitN(raw, ":", 3) - // port, _ := common.PortValue(pod, strings.TrimSpace(parts[1])) - // if port > 0 { - // name := strings.TrimSpace(parts[0]) - // name = strings.ToUpper(strings.Replace(name, "-", "_", -1)) - // portStr := strconv.Itoa(int(port)) - // - // result = append(result, corev1.EnvVar{ - // Name: fmt.Sprintf("%s_CONNECT_SERVICE_HOST", name), - // Value: "127.0.0.1", - // }, corev1.EnvVar{ - // Name: fmt.Sprintf("%s_CONNECT_SERVICE_PORT", name), - // Value: portStr, - // }) - // } - //} - - return []corev1.EnvVar{} +func (w *MeshWebhook) containerEnvVars(pod corev1.Pod) ([]corev1.EnvVar, error) { + upstreams, err := common.ProcessPodUpstreamsForMeshWebhook(pod) + if err != nil { + return nil, fmt.Errorf("error processing the upstream for container environment variable creation: %s", err.Error()) + } + if upstreams == nil { + return nil, nil + } + + var result []corev1.EnvVar + for _, upstream := range upstreams.Upstreams { + serviceName := strings.TrimSpace(upstream.DestinationRef.Name) + serviceName = strings.ToUpper(strings.Replace(serviceName, "-", "_", -1)) + portName := strings.TrimSpace(upstream.DestinationPort) + portName = strings.ToUpper(strings.Replace(portName, "-", "_", -1)) + + result = append(result, corev1.EnvVar{ + Name: fmt.Sprintf("%s_%s_CONNECT_SERVICE_HOST", serviceName, portName), + Value: upstream.GetIpPort().Ip, + }, corev1.EnvVar{ + Name: fmt.Sprintf("%s_%s_CONNECT_SERVICE_PORT", serviceName, portName), + Value: strconv.Itoa(int(upstream.GetIpPort().Port)), + }) + } + + return result, nil } diff --git a/control-plane/connect-inject/webhookv2/container_env_test.go b/control-plane/connect-inject/webhookv2/container_env_test.go index a9507d6c51..01f5b1f82e 100644 --- a/control-plane/connect-inject/webhookv2/container_env_test.go +++ b/control-plane/connect-inject/webhookv2/container_env_test.go @@ -14,28 +14,43 @@ import ( ) func TestContainerEnvVars(t *testing.T) { - t.Skip() - // (TODO: ashwin) make these work once upstreams are fixed cases := []struct { - Name string - Upstream string + Name string + Upstream string + ExpectError bool }{ { - "Upstream with datacenter", - "static-server:7890:dc1", + // TODO: This will not error out when dcs are supported + Name: "Upstream with datacenter", + Upstream: "myPort.static-server:7890:dc1", + ExpectError: true, }, { - "Upstream without datacenter", - "static-server:7890", + Name: "Upstream without datacenter", + Upstream: "myPort.static-server:7890", + }, + { + // TODO: This will not error out when dcs are supported + Name: "Upstream with labels and datacenter", + Upstream: "myPort.port.static-server.svc.dc1.dc:7890", + ExpectError: true, + }, + { + Name: "Upstream with labels and no datacenter", + Upstream: "myPort.port.static-server.svc:7890", + }, + { + Name: "Error expected, wrong order", + Upstream: "static-server.svc.myPort.port:7890", + ExpectError: true, }, } for _, tt := range cases { t.Run(tt.Name, func(t *testing.T) { require := require.New(t) - var w MeshWebhook - envVars := w.containerEnvVars(corev1.Pod{ + envVars, err := w.containerEnvVars(corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ constants.AnnotationService: "foo", @@ -44,15 +59,20 @@ func TestContainerEnvVars(t *testing.T) { }, }) - require.ElementsMatch(envVars, []corev1.EnvVar{ - { - Name: "STATIC_SERVER_CONNECT_SERVICE_HOST", - Value: "127.0.0.1", - }, { - Name: "STATIC_SERVER_CONNECT_SERVICE_PORT", - Value: "7890", - }, - }) + if !tt.ExpectError { + require.NoError(err) + require.ElementsMatch(envVars, []corev1.EnvVar{ + { + Name: "STATIC_SERVER_MYPORT_CONNECT_SERVICE_HOST", + Value: "127.0.0.1", + }, { + Name: "STATIC_SERVER_MYPORT_CONNECT_SERVICE_PORT", + Value: "7890", + }, + }) + } else { + require.Error(err) + } }) } } diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook.go b/control-plane/connect-inject/webhookv2/mesh_webhook.go index 61031b386c..839b3fd4ab 100644 --- a/control-plane/connect-inject/webhookv2/mesh_webhook.go +++ b/control-plane/connect-inject/webhookv2/mesh_webhook.go @@ -270,7 +270,11 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi // Add the upstream services as environment variables for easy // service discovery. - containerEnvVars := w.containerEnvVars(pod) + containerEnvVars, err := w.containerEnvVars(pod) + if err != nil { + w.Log.Error(err, "error creating the port environment variables based on pod annotations", "request name", req.Name) + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error creating the port environment variables based on pod annotations: %s", err)) + } for i := range pod.Spec.InitContainers { pod.Spec.InitContainers[i].Env = append(pod.Spec.InitContainers[i].Env, containerEnvVars...) } diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go index e2aba39ec9..ffaf499621 100644 --- a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go @@ -209,66 +209,89 @@ func TestHandlerHandle(t *testing.T) { }, }, }, - // (TODO: ashwin) fix this test once upstreams get correctly processed - //{ - // "pod with upstreams specified", - // MeshWebhook{ - // Log: logrtest.New(t), - // AllowK8sNamespacesSet: mapset.NewSetWith("*"), - // DenyK8sNamespacesSet: mapset.NewSet(), - // decoder: decoder, - // Clientset: defaultTestClientWithNamespace(), - // }, - // admission.Request{ - // AdmissionRequest: admissionv1.AdmissionRequest{ - // Namespace: namespaces.DefaultNamespace, - // Object: encodeRaw(t, &corev1.Pod{ - // ObjectMeta: metav1.ObjectMeta{ - // Annotations: map[string]string{ - // constants.AnnotationMeshDestinations: "echo:1234,db:1234", - // }, - // }, - // Spec: basicSpec, - // }), - // }, - // }, - // "", - // []jsonpatch.Operation{ - // { - // Operation: "add", - // Path: "/metadata/labels", - // }, - // { - // Operation: "add", - // Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - // }, - // { - // Operation: "add", - // Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - // }, - // { - // Operation: "add", - // Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - // }, - // { - // Operation: "add", - // Path: "/spec/volumes", - // }, - // { - // Operation: "add", - // Path: "/spec/initContainers", - // }, - // { - // Operation: "add", - // Path: "/spec/containers/1", - // }, - // { - // Operation: "add", - // Path: "/spec/containers/0/env", - // }, - // }, - //}, - + { + "pod with upstreams specified", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationMeshDestinations: "myPort1.echo:1234,myPort2.db:1234", + }, + }, + Spec: basicSpec, + }), + }, + }, + "", + []jsonpatch.Operation{ + { + Operation: "add", + Path: "/metadata/labels", + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), + }, + { + Operation: "add", + Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), + }, + { + Operation: "add", + Path: "/spec/volumes", + }, + { + Operation: "add", + Path: "/spec/initContainers", + }, + { + Operation: "add", + Path: "/spec/containers/1", + }, + { + Operation: "add", + Path: "/spec/containers/0/env", + }, + }, + }, + { + "error pod with incorrect upstreams specified", + MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + decoder: decoder, + Clientset: defaultTestClientWithNamespace(), + }, + admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationMeshDestinations: "db:1234", + }, + }, + Spec: basicSpec, + }), + }, + }, + "error creating the port environment variables based on pod annotations", + []jsonpatch.Operation{}, + }, { "empty pod with injection disabled", MeshWebhook{ From 04a8d984574ee22dfaa7d8d3af867c7c8c030810 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 25 Sep 2023 12:55:09 -0400 Subject: [PATCH 407/592] Use branch where acceptance tests run in parallel (#3003) --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2959554e21..38c4c368ae 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,6 +19,6 @@ jobs: with: workflow: test.yml repo: hashicorp/consul-k8s-workflows - ref: main + ref: mw/1-18-parallel-tproxy token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' From 78ad3764233f363f98af9f7ad8ab3160fb2b9452 Mon Sep 17 00:00:00 2001 From: Iryna Shustava Date: Tue, 26 Sep 2023 14:54:24 -0600 Subject: [PATCH 408/592] Rename pbmesh.Upstreams to pbmesh.Destinations (#3005) * Rename pbmesh.Upstreams to pbmesh.Destinations * fix traffic perm acceptance tests fixture * more mesh v2 acceptance test fixes * kick off k8s tests * update images --- .../trafficpermissions.yaml | 5 +- acceptance/tests/mesh_v2/mesh_inject_test.go | 14 +- .../common/annotation_processor.go | 86 +++---- .../common/annotation_processor_test.go | 229 +++++++++--------- .../constants/annotations_and_labels.go | 2 +- .../controllers/pod/pod_controller.go | 26 +- .../pod/pod_controller_ent_test.go | 28 +-- .../controllers/pod/pod_controller_test.go | 150 ++++++------ .../connect-inject/webhookv2/container_env.go | 16 +- .../webhookv2/mesh_webhook_test.go | 4 +- control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- 12 files changed, 284 insertions(+), 282 deletions(-) diff --git a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml b/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml index 48d2ace187..f43bd2d62f 100644 --- a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml +++ b/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml @@ -9,5 +9,6 @@ spec: destination: identityName: multiport action: allow - sources: - - identityName: static-client + permissions: + - sources: + - identityName: static-client diff --git a/acceptance/tests/mesh_v2/mesh_inject_test.go b/acceptance/tests/mesh_v2/mesh_inject_test.go index 2ee713e520..56cae5640c 100644 --- a/acceptance/tests/mesh_v2/mesh_inject_test.go +++ b/acceptance/tests/mesh_v2/mesh_inject_test.go @@ -34,10 +34,7 @@ func TestMeshInject_MultiportService(t *testing.T) { ctx := suite.Environment().DefaultContext(t) helmValues := map[string]string{ - "global.image": "thisisnotashwin/consul:foo", - "global.imageK8S": "thisisnotashwin/consul-k8s:foo", - "global.imageConsulDataplane": "jmurrethc/consul-dataplane-dev", - "global.experiments[0]": "resource-apis", + "global.experiments[0]": "resource-apis", // The UI is not supported for v2 in 1.17, so for now it must be disabled. "ui.enabled": "false", "connectInject.enabled": "true", @@ -79,16 +76,19 @@ func TestMeshInject_MultiportService(t *testing.T) { { // TODO: once ACLs are implemented, only run this block when secure=true and delete the below line and fixture since the default is deny when secure is true. - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/trafficpermissions-deny") + k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/cases/trafficpermissions-deny") // Now test that traffic is denied between the source and the destination. if cfg.EnableTransparentProxy { k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") } else { k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:2345") + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") } - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/bases/trafficpermissions") + k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") + }) } // Check connection from static-client to multiport. diff --git a/control-plane/connect-inject/common/annotation_processor.go b/control-plane/connect-inject/common/annotation_processor.go index 3dcb23842e..778f630049 100644 --- a/control-plane/connect-inject/common/annotation_processor.go +++ b/control-plane/connect-inject/common/annotation_processor.go @@ -19,27 +19,27 @@ const ( ConsulNodeAddress = "127.0.0.1" ) -// ProcessPodUpstreamsForMeshWebhook reads the list of upstreams from the Pod annotation and converts them into a pbmesh.Upstreams +// ProcessPodDestinationsForMeshWebhook reads the list of destinations from the Pod annotation and converts them into a pbmesh.Destinations // object. -func ProcessPodUpstreamsForMeshWebhook(pod corev1.Pod) (*pbmesh.Upstreams, error) { - return ProcessPodUpstreams(pod, true, true) +func ProcessPodDestinationsForMeshWebhook(pod corev1.Pod) (*pbmesh.Destinations, error) { + return ProcessPodDestinations(pod, true, true) } -// ProcessPodUpstreams reads the list of upstreams from the Pod annotation and converts them into a pbmesh.Upstreams +// ProcessPodDestinations reads the list of destinations from the Pod annotation and converts them into a pbmesh.Destinations // object. -func ProcessPodUpstreams(pod corev1.Pod, enablePartitions, enableNamespaces bool) (*pbmesh.Upstreams, error) { - upstreams := &pbmesh.Upstreams{} +func ProcessPodDestinations(pod corev1.Pod, enablePartitions, enableNamespaces bool) (*pbmesh.Destinations, error) { + destinations := &pbmesh.Destinations{} raw, ok := pod.Annotations[constants.AnnotationMeshDestinations] if !ok || raw == "" { return nil, nil } - upstreams.Workloads = &pbcatalog.WorkloadSelector{ + destinations.Workloads = &pbcatalog.WorkloadSelector{ Names: []string{pod.Name}, } for _, raw := range strings.Split(raw, ",") { - var upstream *pbmesh.Upstream + var destination *pbmesh.Destination // Determine the type of processing required unlabeled or labeled // [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter] @@ -62,25 +62,25 @@ func ProcessPodUpstreams(pod corev1.Pod, enablePartitions, enableNamespaces bool if labeledFormat { var err error - upstream, err = processPodLabeledUpstream(pod, raw, enablePartitions, enableNamespaces) + destination, err = processPodLabeledDestination(pod, raw, enablePartitions, enableNamespaces) if err != nil { - return &pbmesh.Upstreams{}, err + return nil, err } } else { var err error - upstream, err = processPodUnlabeledUpstream(pod, raw, enablePartitions, enableNamespaces) + destination, err = processPodUnlabeledDestination(pod, raw, enablePartitions, enableNamespaces) if err != nil { - return &pbmesh.Upstreams{}, err + return nil, err } } - upstreams.Upstreams = append(upstreams.Upstreams, upstream) + destinations.Destinations = append(destinations.Destinations, destination) } - return upstreams, nil + return destinations, nil } -// processPodLabeledUpstream processes an upstream in the format: +// processPodLabeledDestination processes a destination in the format: // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port]. @@ -88,12 +88,12 @@ func ProcessPodUpstreams(pod corev1.Pod, enablePartitions, enableNamespaces bool // The ordering matters for labeled as well as unlabeled. The ordering of the labeled parameters should follow // the order and requirements of the unlabeled parameters. // TODO: enable dc and peer support when ready, currently return errors if set. -func processPodLabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Upstream, error) { +func processPodLabeledDestination(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Destination, error) { parts := strings.SplitN(rawUpstream, ":", 3) var port int32 port, _ = PortValue(pod, strings.TrimSpace(parts[1])) if port <= 0 { - return &pbmesh.Upstream{}, fmt.Errorf("port value %d in upstream is invalid: %s", port, rawUpstream) + return nil, fmt.Errorf("port value %d in destination is invalid: %s", port, rawUpstream) } service := parts[0] @@ -108,37 +108,37 @@ func processPodLabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartiti case "peer": // TODO: uncomment and remove error when peers supported //peer = strings.TrimSpace(pieces[6]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support peers: %s", rawUpstream) + return nil, fmt.Errorf("destination currently does not support peers: %s", rawUpstream) case "ap": partition = strings.TrimSpace(pieces[6]) case "dc": // TODO: uncomment and remove error when datacenters are supported //datacenter = strings.TrimSpace(pieces[6]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } fallthrough case 6: if strings.TrimSpace(pieces[5]) == "ns" { namespace = strings.TrimSpace(pieces[4]) } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } fallthrough case 4: if strings.TrimSpace(pieces[3]) == "svc" { svcName = strings.TrimSpace(pieces[2]) } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } if strings.TrimSpace(pieces[1]) == "port" { portName = strings.TrimSpace(pieces[0]) } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } } else { switch len(pieces) { @@ -148,13 +148,13 @@ func processPodLabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartiti case "peer": // TODO: uncomment and remove error when peers supported //peer = strings.TrimSpace(pieces[4]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support peers: %s", rawUpstream) + return nil, fmt.Errorf("destination currently does not support peers: %s", rawUpstream) case "dc": // TODO: uncomment and remove error when datacenter supported //datacenter = strings.TrimSpace(pieces[4]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } // TODO: uncomment and remove error when datacenter and/or peers supported //fallthrough @@ -162,19 +162,19 @@ func processPodLabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartiti if strings.TrimSpace(pieces[3]) == "svc" { svcName = strings.TrimSpace(pieces[2]) } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } if strings.TrimSpace(pieces[1]) == "port" { portName = strings.TrimSpace(pieces[0]) } else { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } } - upstream := pbmesh.Upstream{ + destination := pbmesh.Destination{ DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ @@ -186,7 +186,7 @@ func processPodLabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartiti }, DestinationPort: portName, Datacenter: datacenter, - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(port), Ip: ConsulNodeAddress, @@ -194,24 +194,24 @@ func processPodLabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartiti }, } - return &upstream, nil + return &destination, nil } -// processPodUnlabeledUpstream processes an upstream in the format: +// processPodUnlabeledDestination processes a destination in the format: // [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. // There is no unlabeled field for peering. // TODO: enable dc and peer support when ready, currently return errors if set. -func processPodUnlabeledUpstream(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Upstream, error) { +func processPodUnlabeledDestination(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Destination, error) { var portName, datacenter, svcName, namespace, partition string var port int32 - var upstream pbmesh.Upstream + var destination pbmesh.Destination parts := strings.SplitN(rawUpstream, ":", 3) port, _ = PortValue(pod, strings.TrimSpace(parts[1])) // If Consul Namespaces or Admin Partitions are enabled, attempt to parse the - // upstream for a namespace. + // destination for a namespace. if enableNamespaces || enablePartitions { pieces := strings.SplitN(parts[0], ".", 4) switch len(pieces) { @@ -225,12 +225,12 @@ func processPodUnlabeledUpstream(pod corev1.Pod, rawUpstream string, enableParti svcName = strings.TrimSpace(pieces[1]) portName = strings.TrimSpace(pieces[0]) default: - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } } else { pieces := strings.SplitN(parts[0], ".", 2) if len(pieces) < 2 { - return &pbmesh.Upstream{}, fmt.Errorf("upstream structured incorrectly: %s", rawUpstream) + return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) } svcName = strings.TrimSpace(pieces[1]) portName = strings.TrimSpace(pieces[0]) @@ -240,11 +240,11 @@ func processPodUnlabeledUpstream(pod corev1.Pod, rawUpstream string, enableParti if len(parts) > 2 { // TODO: uncomment and remove error when datacenters supported //datacenter = strings.TrimSpace(parts[2]) - return &pbmesh.Upstream{}, fmt.Errorf("upstream currently does not support datacenters: %s", rawUpstream) + return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) } if port > 0 { - upstream = pbmesh.Upstream{ + destination = pbmesh.Destination{ DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ @@ -256,7 +256,7 @@ func processPodUnlabeledUpstream(pod corev1.Pod, rawUpstream string, enableParti }, DestinationPort: portName, Datacenter: datacenter, - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(port), Ip: ConsulNodeAddress, @@ -264,5 +264,5 @@ func processPodUnlabeledUpstream(pod corev1.Pod, rawUpstream string, enableParti }, } } - return &upstream, nil + return &destination, nil } diff --git a/control-plane/connect-inject/common/annotation_processor_test.go b/control-plane/connect-inject/common/annotation_processor_test.go index f5d2788488..223067e6c5 100644 --- a/control-plane/connect-inject/common/annotation_processor_test.go +++ b/control-plane/connect-inject/common/annotation_processor_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul/api" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" @@ -16,6 +15,8 @@ import ( "google.golang.org/protobuf/testing/protocmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) func TestProcessUpstreams(t *testing.T) { @@ -26,7 +27,7 @@ func TestProcessUpstreams(t *testing.T) { cases := []struct { name string pod func() *corev1.Pod - expected *pbmesh.Upstreams + expected *pbmesh.Destinations expErr string configEntry func() api.ConfigEntry consulUnavailable bool @@ -34,16 +35,16 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled bool }{ { - name: "labeled annotated upstream with svc only", + name: "labeled annotated destination with svc only", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc:1234") return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -56,7 +57,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: ConsulNodeAddress, @@ -69,18 +70,18 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "labeled annotated upstream with svc and dc", + name: "labeled annotated destination with svc and dc", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.dc1.dc:1234") return pod1 }, - expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.dc1.dc:1234", + expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.dc1.dc:1234", // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ + //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ // Names: []string{podName}, // }, - // Upstreams: []*pbmesh.Upstream{ + // Upstreams: []*pbmesh.Destination{ // { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, @@ -93,7 +94,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort", // Datacenter: "dc1", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(1234), // Ip: ConsulNodeAddress, @@ -106,18 +107,18 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "labeled annotated upstream with svc and peer", + name: "labeled annotated destination with svc and peer", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.peer:1234") return pod1 }, - expErr: "upstream currently does not support peers: myPort.port.upstream1.svc.peer1.peer:1234", + expErr: "destination currently does not support peers: myPort.port.upstream1.svc.peer1.peer:1234", // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Upstreams{ + //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ // Names: []string{podName}, // }, - // Upstreams: []*pbmesh.Upstream{ + // Upstreams: []*pbmesh.Destination{ // { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, @@ -130,7 +131,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(1234), // Ip: ConsulNodeAddress, @@ -143,18 +144,18 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "labeled annotated upstream with svc, ns, and peer", + name: "labeled annotated destination with svc, ns, and peer", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234") return pod1 }, - expErr: "upstream currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", + expErr: "destination currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Upstreams{ + //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ // Names: []string{podName}, // }, - // Upstreams: []*pbmesh.Upstream{ + // Upstreams: []*pbmesh.Destination{ // { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, @@ -167,7 +168,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(1234), // Ip: ConsulNodeAddress, @@ -180,16 +181,16 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "labeled annotated upstream with svc, ns, and partition", + name: "labeled annotated destination with svc, ns, and partition", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234") return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -202,7 +203,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: ConsulNodeAddress, @@ -215,18 +216,18 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: true, }, { - name: "labeled annotated upstream with svc, ns, and dc", + name: "labeled annotated destination with svc, ns, and dc", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234") return pod1 }, - expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", + expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ + //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ // Names: []string{podName}, // }, - // Upstreams: []*pbmesh.Upstream{ + // Upstreams: []*pbmesh.Destination{ // { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, @@ -239,7 +240,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort", // Datacenter: "dc1", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(1234), // Ip: ConsulNodeAddress, @@ -252,16 +253,16 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "labeled multiple annotated upstreams", + name: "labeled multiple annotated destinations", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.port.upstream2.svc:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234") return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -274,7 +275,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: ConsulNodeAddress, @@ -293,7 +294,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort2", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(2234), Ip: ConsulNodeAddress, @@ -312,7 +313,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort4", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(4234), Ip: ConsulNodeAddress, @@ -325,18 +326,18 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: true, }, { - name: "labeled multiple annotated upstreams with dcs and peers", + name: "labeled multiple annotated destinations with dcs and peers", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234, myPort2.port.upstream2.svc:2234, myPort3.port.upstream3.svc.ns1.ns:3234, myPort4.port.upstream4.svc.ns1.ns.peer1.peer:4234") return pod1 }, - expErr: "upstream currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", + expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ + //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ // Names: []string{podName}, // }, - // Upstreams: []*pbmesh.Upstream{ + // Upstreams: []*pbmesh.Destination{ // { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, @@ -349,7 +350,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort", // Datacenter: "dc1", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(1234), // Ip: ConsulNodeAddress, @@ -368,7 +369,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort2", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(2234), // Ip: ConsulNodeAddress, @@ -387,7 +388,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort3", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(3234), // Ip: ConsulNodeAddress, @@ -406,7 +407,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort4", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(4234), // Ip: ConsulNodeAddress, @@ -419,132 +420,132 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: true, }, { - name: "error labeled annotated upstream error: invalid partition/dc/peer", + name: "error labeled annotated destination error: invalid partition/dc/peer", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.err:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: false, }, { - name: "error labeled annotated upstream with svc and peer, needs ns before peer if namespaces enabled", + name: "error labeled annotated destination with svc and peer, needs ns before peer if namespaces enabled", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.peer:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.peer1.peer:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.peer1.peer:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: false, }, { - name: "error labeled annotated upstream error: invalid namespace", + name: "error labeled annotated destination error: invalid namespace", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.err:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.err:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.err:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: false, }, { - name: "error labeled annotated upstream error: invalid number of pieces in the address", + name: "error labeled annotated destination error: invalid number of pieces in the address", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.err:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.err:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.err:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: false, }, { - name: "error labeled annotated upstream error: invalid peer", + name: "error labeled annotated destination error: invalid peer", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.err:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.peer1.err:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.peer1.err:1234", consulNamespacesEnabled: false, consulPartitionsEnabled: false, }, { - name: "error labeled annotated upstream error: invalid number of pieces in the address without namespaces and partitions", + name: "error labeled annotated destination error: invalid number of pieces in the address without namespaces and partitions", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.err:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.err:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.err:1234", consulNamespacesEnabled: false, consulPartitionsEnabled: false, }, { - name: "error labeled annotated upstream error: both peer and partition provided", + name: "error labeled annotated destination error: both peer and partition provided", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: true, }, { - name: "error labeled annotated upstream error: both peer and dc provided", + name: "error labeled annotated destination error: both peer and dc provided", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: false, }, { - name: "error labeled annotated upstream error: both dc and partition provided", + name: "error labeled annotated destination error: both dc and partition provided", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", + expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: true, }, { - name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition enabled", + name: "error labeled annotated destination error: wrong ordering for port and svc with namespace partition enabled", pod: func() *corev1.Pod { pod1 := createPod(podName, "upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234") return pod1 }, - expErr: "upstream structured incorrectly: upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234", + expErr: "destination structured incorrectly: upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: true, }, { - name: "error labeled annotated upstream error: wrong ordering for port and svc with namespace partition disabled", + name: "error labeled annotated destination error: wrong ordering for port and svc with namespace partition disabled", pod: func() *corev1.Pod { pod1 := createPod(podName, "upstream1.svc.myPort.port:1234") return pod1 }, - expErr: "upstream structured incorrectly: upstream1.svc.myPort.port:1234", + expErr: "destination structured incorrectly: upstream1.svc.myPort.port:1234", consulNamespacesEnabled: false, consulPartitionsEnabled: false, }, { - name: "error labeled annotated upstream error: incorrect key name namespace partition enabled", + name: "error labeled annotated destination error: incorrect key name namespace partition enabled", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", + expErr: "destination structured incorrectly: myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: true, }, { - name: "error labeled annotated upstream error: incorrect key name namespace partition disabled", + name: "error labeled annotated destination error: incorrect key name namespace partition disabled", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.portage.upstream1.svc:1234") return pod1 }, - expErr: "upstream structured incorrectly: myPort.portage.upstream1.svc:1234", + expErr: "destination structured incorrectly: myPort.portage.upstream1.svc:1234", consulNamespacesEnabled: false, consulPartitionsEnabled: false, }, @@ -554,7 +555,7 @@ func TestProcessUpstreams(t *testing.T) { pod1 := createPod(podName, "upstream1.svc:1234") return pod1 }, - expErr: "upstream structured incorrectly: upstream1.svc:1234", + expErr: "destination structured incorrectly: upstream1.svc:1234", consulNamespacesEnabled: false, consulPartitionsEnabled: false, }, @@ -564,21 +565,21 @@ func TestProcessUpstreams(t *testing.T) { pod1 := createPod(podName, "upstream1.svc:1234") return pod1 }, - expErr: "upstream structured incorrectly: upstream1.svc:1234", + expErr: "destination structured incorrectly: upstream1.svc:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: true, }, { - name: "unlabeled and labeled multiple annotated upstreams", + name: "unlabeled and labeled multiple annotated destinations", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.upstream2:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234") return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -591,7 +592,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: ConsulNodeAddress, @@ -610,7 +611,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort2", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(2234), Ip: ConsulNodeAddress, @@ -629,7 +630,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort4", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(4234), Ip: ConsulNodeAddress, @@ -642,16 +643,16 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: true, }, { - name: "unlabeled single upstream", + name: "unlabeled single destination", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.upstream:1234") return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -664,7 +665,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: ConsulNodeAddress, @@ -677,16 +678,16 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "unlabeled single upstream with namespace", + name: "unlabeled single destination with namespace", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.upstream.foo:1234") return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -699,7 +700,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: ConsulNodeAddress, @@ -712,16 +713,16 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "unlabeled single upstream with namespace and partition", + name: "unlabeled single destination with namespace and partition", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.upstream.foo.bar:1234") return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -734,7 +735,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: ConsulNodeAddress, @@ -747,16 +748,16 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: true, }, { - name: "unlabeled multiple upstreams", + name: "unlabeled multiple destinations", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2:2234") return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -769,7 +770,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: ConsulNodeAddress, @@ -788,7 +789,7 @@ func TestProcessUpstreams(t *testing.T) { }, DestinationPort: "myPort2", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(2234), Ip: ConsulNodeAddress, @@ -801,7 +802,7 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "unlabeled multiple upstreams with consul namespaces, partitions and datacenters", + name: "unlabeled multiple destinations with consul namespaces, partitions and datacenters", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo.baz:3234:dc2") return pod1 @@ -812,13 +813,13 @@ func TestProcessUpstreams(t *testing.T) { pd.MeshGateway.Mode = "remote" return pd }, - expErr: "upstream currently does not support datacenters: myPort3.upstream3.foo.baz:3234:dc2", + expErr: "destination currently does not support datacenters: myPort3.upstream3.foo.baz:3234:dc2", // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ + //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ // Names: []string{podName}, // }, - // Upstreams: []*pbmesh.Upstream{ + // Upstreams: []*pbmesh.Destination{ // { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, @@ -831,7 +832,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(1234), // Ip: ConsulNodeAddress, @@ -850,7 +851,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort2", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(2234), // Ip: ConsulNodeAddress, @@ -869,7 +870,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort3", // Datacenter: "dc2", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(3234), // Ip: ConsulNodeAddress, @@ -882,7 +883,7 @@ func TestProcessUpstreams(t *testing.T) { consulPartitionsEnabled: true, }, { - name: "unlabeled multiple upstreams with consul namespaces and datacenters", + name: "unlabeled multiple destinations with consul namespaces and datacenters", pod: func() *corev1.Pod { pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo:3234:dc2") return pod1 @@ -893,13 +894,13 @@ func TestProcessUpstreams(t *testing.T) { pd.MeshGateway.Mode = "remote" return pd }, - expErr: "upstream currently does not support datacenters: myPort3.upstream3.foo:3234:dc2", + expErr: "destination currently does not support datacenters: myPort3.upstream3.foo:3234:dc2", // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Upstreams{ + //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ // Names: []string{podName}, // }, - // Upstreams: []*pbmesh.Upstream{ + // Upstreams: []*pbmesh.Destination{ // { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, @@ -912,7 +913,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(1234), // Ip: ConsulNodeAddress, @@ -931,7 +932,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort2", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(2234), // Ip: ConsulNodeAddress, @@ -950,7 +951,7 @@ func TestProcessUpstreams(t *testing.T) { // }, // DestinationPort: "myPort3", // Datacenter: "dc2", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(3234), // Ip: ConsulNodeAddress, @@ -967,7 +968,7 @@ func TestProcessUpstreams(t *testing.T) { pod1 := createPod(podName, "upstream1:1234") return pod1 }, - expErr: "upstream structured incorrectly: upstream1:1234", + expErr: "destination structured incorrectly: upstream1:1234", consulNamespacesEnabled: false, consulPartitionsEnabled: false, }, @@ -977,21 +978,21 @@ func TestProcessUpstreams(t *testing.T) { pod1 := createPod(podName, "upstream1:1234") return pod1 }, - expErr: "upstream structured incorrectly: upstream1:1234", + expErr: "destination structured incorrectly: upstream1:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: true, }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - upstreams, err := ProcessPodUpstreams(*tt.pod(), tt.consulNamespacesEnabled, tt.consulPartitionsEnabled) + destinations, err := ProcessPodDestinations(*tt.pod(), tt.consulNamespacesEnabled, tt.consulPartitionsEnabled) if tt.expErr != "" { require.EqualError(t, err, tt.expErr) } else { require.NoError(t, err) - require.Equal(t, tt.expected, upstreams) + require.Equal(t, tt.expected, destinations) - if diff := cmp.Diff(tt.expected, upstreams, protocmp.Transform()); diff != "" { + if diff := cmp.Diff(tt.expected, destinations, protocmp.Transform()); diff != "" { t.Errorf("unexpected difference:\n%v", diff) } } diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 0bab084b1f..823776e577 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -234,7 +234,7 @@ const ( // of resources. ManagedByServiceAccountValue = "consul-k8s-service-account-controller" - // AnnotationMeshDestinations is a list of upstreams to register with the + // AnnotationMeshDestinations is a list of destinations to register with the // proxy. The service name should map to a Consul service name and the local // port is the local port in the pod that the listener will bind to. It can // be a named port. diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index b101b8c745..6a44c20f4c 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -110,8 +110,8 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // TODO: clean up ACL Tokens - // Delete upstreams, if any exist - if err := r.deleteUpstreams(ctx, req.NamespacedName); err != nil { + // Delete destinations, if any exist + if err := r.deleteDestinations(ctx, req.NamespacedName); err != nil { errs = multierror.Append(errs, err) } @@ -152,8 +152,8 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu errs = multierror.Append(errs, err) } - // Create explicit upstreams (if any exist) - if err := r.writeUpstreams(ctx, pod); err != nil { + // Create explicit destinations (if any exist) + if err := r.writeDestinations(ctx, pod); err != nil { // Technically this is not needed, but keeping in case this gets refactored in // a different order if inject.ConsulNamespaceIsNotFound(err) { @@ -449,11 +449,11 @@ func (r *Controller) writeHealthStatus(ctx context.Context, pod corev1.Pod) erro // has been configured with and will only delete tokens for the provided podName. // func (r *Controller) deleteACLTokensForWorkload(apiClient *api.Client, svc *api.AgentService, k8sNS, podName string) error { -// writeUpstreams will write explicit upstreams if pod annotations exist. -func (r *Controller) writeUpstreams(ctx context.Context, pod corev1.Pod) error { - uss, err := inject.ProcessPodUpstreams(pod, r.EnableConsulPartitions, r.EnableConsulNamespaces) +// writeDestinations will write explicit destinations if pod annotations exist. +func (r *Controller) writeDestinations(ctx context.Context, pod corev1.Pod) error { + uss, err := inject.ProcessPodDestinations(pod, r.EnableConsulPartitions, r.EnableConsulNamespaces) if err != nil { - return fmt.Errorf("error processing upstream annotations: %s", err.Error()) + return fmt.Errorf("error processing destination annotations: %s", err.Error()) } if uss == nil { return nil @@ -462,7 +462,7 @@ func (r *Controller) writeUpstreams(ctx context.Context, pod corev1.Pod) error { data := inject.ToProtoAny(uss) req := &pbresource.WriteRequest{ Resource: &pbresource.Resource{ - Id: getUpstreamsID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), + Id: getDestinationsID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), Metadata: metaFromPod(pod), Data: data, }, @@ -472,9 +472,9 @@ func (r *Controller) writeUpstreams(ctx context.Context, pod corev1.Pod) error { return err } -func (r *Controller) deleteUpstreams(ctx context.Context, pod types.NamespacedName) error { +func (r *Controller) deleteDestinations(ctx context.Context, pod types.NamespacedName) error { req := &pbresource.DeleteRequest{ - Id: getUpstreamsID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), + Id: getDestinationsID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), } _, err := r.ResourceClient.Delete(ctx, req) @@ -637,10 +637,10 @@ func getHealthStatusID(name, namespace, partition string) *pbresource.ID { } } -func getUpstreamsID(name, namespace, partition string) *pbresource.ID { +func getDestinationsID(name, namespace, partition string) *pbresource.ID { return &pbresource.ID{ Name: name, - Type: pbmesh.UpstreamsType, + Type: pbmesh.DestinationsType, Tenancy: &pbresource.Tenancy{ Partition: partition, Namespace: namespace, diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index 9b0f45c3fe..94bce9b29e 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -59,14 +59,14 @@ type testCase struct { existingWorkload *pbcatalog.Workload existingHealthStatus *pbcatalog.HealthStatus existingProxyConfiguration *pbmesh.ProxyConfiguration - existingUpstreams *pbmesh.Upstreams + existingDestinations *pbmesh.Destinations // Expected Consul state. expectedConsulNamespace string // This namespace will be used to query Consul for the results expectedWorkload *pbcatalog.Workload expectedHealthStatus *pbcatalog.HealthStatus expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedUpstreams *pbmesh.Upstreams + expectedDestinations *pbmesh.Destinations // Reconcile loop outputs expErr string @@ -173,7 +173,7 @@ func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { expectedHealthStatus: createPassingHealthStatus(), }, { - name: "new pod with explicit upstreams, ns and partition", + name: "new pod with explicit destinations, ns and partition", podName: testPodName, partition: constants.DefaultConsulPartition, @@ -194,7 +194,7 @@ func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedUpstreams: createUpstreams(), + expectedDestinations: createDestinations(), }, { name: "namespace in Consul does not exist", @@ -315,7 +315,7 @@ func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { expectedConsulNamespace: "bar", }, { - name: "delete pod w/ explicit upstreams", + name: "delete pod w/ explicit destinations", podName: testPodName, podNamespace: "bar", partition: testPartition, @@ -330,7 +330,7 @@ func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingUpstreams: createUpstreams(), + existingDestinations: createDestinations(), expectedConsulNamespace: "bar", }, @@ -417,7 +417,7 @@ func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { - name: "new pod with explicit upstreams, ns and partition", + name: "new pod with explicit destinations, ns and partition", podName: testPodName, partition: constants.DefaultConsulPartition, @@ -439,7 +439,7 @@ func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedUpstreams: createUpstreams(), + expectedDestinations: createDestinations(), }, { name: "kitchen sink new pod, non-default ns and partition", @@ -585,7 +585,7 @@ func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { expectedConsulNamespace: "a-penguin-walks-into-a-bar", }, { - name: "delete pod with explicit upstreams", + name: "delete pod with explicit destinations", podName: testPodName, podNamespace: "bar", partition: testPartition, @@ -600,7 +600,7 @@ func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingUpstreams: createUpstreams(), + existingDestinations: createDestinations(), expectedConsulNamespace: "a-penguin-walks-into-a-bar", }, @@ -750,8 +750,8 @@ func runControllerTest(t *testing.T, tc testCase) { loadResource( t, resourceClient, - getUpstreamsID(tc.podName, tc.expectedConsulNamespace, tc.partition), - tc.existingUpstreams, + getDestinationsID(tc.podName, tc.expectedConsulNamespace, tc.partition), + tc.existingDestinations, nil) namespacedName := types.NamespacedName{ @@ -779,6 +779,6 @@ func runControllerTest(t *testing.T, tc testCase) { pcID := getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition) expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) - uID := getUpstreamsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedUpstreamMatches(t, resourceClient, uID, tc.expectedUpstreams) + uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedDestinationMatches(t, resourceClient, uID, tc.expectedDestinations) } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 8928b9fc18..456f8fbfaf 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -761,9 +761,9 @@ func TestProxyConfigurationDelete(t *testing.T) { } } -// TestUpstreamsWrite does a subsampling of tests covered in TestProcessUpstreams to make sure things are hooked up +// TestDestinationsWrite does a subsampling of tests covered in TestProcessUpstreams to make sure things are hooked up // correctly. For the sake of test speed, more exhaustive testing is performed in TestProcessUpstreams. -func TestUpstreamsWrite(t *testing.T) { +func TestDestinationsWrite(t *testing.T) { t.Parallel() const podName = "pod1" @@ -771,23 +771,23 @@ func TestUpstreamsWrite(t *testing.T) { cases := []struct { name string pod func() *corev1.Pod - expected *pbmesh.Upstreams + expected *pbmesh.Destinations expErr string consulNamespacesEnabled bool consulPartitionsEnabled bool }{ { - name: "labeled annotated upstream with svc only", + name: "labeled annotated destination with svc only", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -800,7 +800,7 @@ func TestUpstreamsWrite(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: consulNodeAddress, @@ -813,19 +813,19 @@ func TestUpstreamsWrite(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "labeled annotated upstream with svc, ns, and peer", + name: "labeled annotated destination with svc, ns, and peer", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234" return pod1 }, - expErr: "error processing upstream annotations: upstream currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", + expErr: "error processing destination annotations: destination currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Upstreams{ + //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ // Names: []string{podName}, // }, - // Upstreams: []*pbmesh.Upstream{ + // Destinations: []*pbmesh.Destination{ // { // DestinationRef: &pbresource.Reference{ // Type: pbcatalog.ServiceType, @@ -838,7 +838,7 @@ func TestUpstreamsWrite(t *testing.T) { // }, // DestinationPort: "myPort", // Datacenter: "", - // ListenAddr: &pbmesh.Upstream_IpPort{ + // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ // Port: uint32(1234), // Ip: consulNodeAddress, @@ -851,17 +851,17 @@ func TestUpstreamsWrite(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "labeled annotated upstream with svc, ns, and partition", + name: "labeled annotated destination with svc, ns, and partition", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234" return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -874,7 +874,7 @@ func TestUpstreamsWrite(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: consulNodeAddress, @@ -887,28 +887,28 @@ func TestUpstreamsWrite(t *testing.T) { consulPartitionsEnabled: true, }, { - name: "error labeled annotated upstream error: invalid partition/dc/peer", + name: "error labeled annotated destination error: invalid partition/dc/peer", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.err:1234" return pod1 }, - expErr: "error processing upstream annotations: upstream structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", + expErr: "error processing destination annotations: destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: false, }, { - name: "unlabeled single upstream", + name: "unlabeled single destination", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream:1234" return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -921,7 +921,7 @@ func TestUpstreamsWrite(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: consulNodeAddress, @@ -934,17 +934,17 @@ func TestUpstreamsWrite(t *testing.T) { consulPartitionsEnabled: false, }, { - name: "unlabeled single upstream with namespace and partition", + name: "unlabeled single destination with namespace and partition", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo.bar:1234" return pod1 }, - expected: &pbmesh.Upstreams{ + expected: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -957,7 +957,7 @@ func TestUpstreamsWrite(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: consulNodeAddress, @@ -992,44 +992,44 @@ func TestUpstreamsWrite(t *testing.T) { ResourceClient: resourceClient, } - err = pc.writeUpstreams(context.Background(), *tt.pod()) + err = pc.writeDestinations(context.Background(), *tt.pod()) if tt.expErr != "" { require.EqualError(t, err, tt.expErr) } else { require.NoError(t, err) - uID := getUpstreamsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedUpstreamMatches(t, resourceClient, uID, tt.expected) + uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedDestinationMatches(t, resourceClient, uID, tt.expected) } }) } } -func TestUpstreamsDelete(t *testing.T) { +func TestDestinationsDelete(t *testing.T) { t.Parallel() const podName = "pod1" cases := []struct { - name string - pod func() *corev1.Pod - existingUpstreams *pbmesh.Upstreams - expErr string - configEntry func() api.ConfigEntry - consulUnavailable bool + name string + pod func() *corev1.Pod + existingDestinations *pbmesh.Destinations + expErr string + configEntry func() api.ConfigEntry + consulUnavailable bool }{ { - name: "labeled annotated upstream with svc only", + name: "labeled annotated destination with svc only", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" return pod1 }, - existingUpstreams: &pbmesh.Upstreams{ + existingDestinations: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -1042,7 +1042,7 @@ func TestUpstreamsDelete(t *testing.T) { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: consulNodeAddress, @@ -1074,23 +1074,23 @@ func TestUpstreamsDelete(t *testing.T) { // Load in the upstream for us to delete and check that it's there loadResource(t, resourceClient, - getUpstreamsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tt.existingUpstreams, + getDestinationsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tt.existingDestinations, nil) - uID := getUpstreamsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedUpstreamMatches(t, resourceClient, uID, tt.existingUpstreams) + uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedDestinationMatches(t, resourceClient, uID, tt.existingDestinations) // Delete the upstream nn := types.NamespacedName{Name: tt.pod().Name} - err = pc.deleteUpstreams(context.Background(), nn) + err = pc.deleteDestinations(context.Background(), nn) // Verify the upstream has been deleted or that an expected error has been returned if tt.expErr != "" { require.EqualError(t, err, tt.expErr) } else { require.NoError(t, err) - uID := getUpstreamsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedUpstreamMatches(t, resourceClient, uID, nil) + uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedDestinationMatches(t, resourceClient, uID, nil) } }) } @@ -1119,7 +1119,7 @@ func TestReconcileCreatePod(t *testing.T) { expectedWorkload *pbcatalog.Workload expectedHealthStatus *pbcatalog.HealthStatus expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedUpstreams *pbmesh.Upstreams + expectedDestinations *pbmesh.Destinations tproxy bool overwriteProbes bool @@ -1197,8 +1197,8 @@ func TestReconcileCreatePod(t *testing.T) { pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) - uID := getUpstreamsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedUpstreamMatches(t, resourceClient, uID, tc.expectedUpstreams) + uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedDestinationMatches(t, resourceClient, uID, tc.expectedDestinations) } testCases := []testCase{ @@ -1276,7 +1276,7 @@ func TestReconcileCreatePod(t *testing.T) { expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), expectedProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedUpstreams: createUpstreams(), + expectedDestinations: createDestinations(), }, // TODO: make sure multi-error accumulates errors } @@ -1310,12 +1310,12 @@ func TestReconcileUpdatePod(t *testing.T) { existingWorkload *pbcatalog.Workload existingHealthStatus *pbcatalog.HealthStatus existingProxyConfiguration *pbmesh.ProxyConfiguration - existingUpstreams *pbmesh.Upstreams + existingDestinations *pbmesh.Destinations expectedWorkload *pbcatalog.Workload expectedHealthStatus *pbcatalog.HealthStatus expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedUpstreams *pbmesh.Upstreams + expectedDestinations *pbmesh.Destinations tproxy bool overwriteProbes bool @@ -1384,8 +1384,8 @@ func TestReconcileUpdatePod(t *testing.T) { nil) loadResource(t, resourceClient, - getUpstreamsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingUpstreams, + getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingDestinations, nil) namespacedName := types.NamespacedName{ @@ -1412,8 +1412,8 @@ func TestReconcileUpdatePod(t *testing.T) { pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) - uID := getUpstreamsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedUpstreamMatches(t, resourceClient, uID, tc.expectedUpstreams) + uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedDestinationMatches(t, resourceClient, uID, tc.expectedDestinations) } testCases := []testCase{ @@ -1506,7 +1506,7 @@ func TestReconcileUpdatePod(t *testing.T) { }, }, { - name: "pod update explicit upstreams", + name: "pod update explicit destination", podName: "foo", k8sObjects: func() []runtime.Object { pod := createPod("foo", "", true, true) @@ -1515,11 +1515,11 @@ func TestReconcileUpdatePod(t *testing.T) { }, existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingUpstreams: &pbmesh.Upstreams{ + existingDestinations: &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{"foo"}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -1532,7 +1532,7 @@ func TestReconcileUpdatePod(t *testing.T) { }, DestinationPort: "myPort2", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(1234), Ip: consulNodeAddress, @@ -1543,7 +1543,7 @@ func TestReconcileUpdatePod(t *testing.T) { }, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedUpstreams: createUpstreams(), + expectedDestinations: createDestinations(), }, } @@ -1574,12 +1574,12 @@ func TestReconcileDeletePod(t *testing.T) { existingWorkload *pbcatalog.Workload existingHealthStatus *pbcatalog.HealthStatus existingProxyConfiguration *pbmesh.ProxyConfiguration - existingUpstreams *pbmesh.Upstreams + existingDestinations *pbmesh.Destinations expectedWorkload *pbcatalog.Workload expectedHealthStatus *pbcatalog.HealthStatus expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedUpstreams *pbmesh.Upstreams + expectedDestinations *pbmesh.Destinations aclsEnabled bool @@ -1641,8 +1641,8 @@ func TestReconcileDeletePod(t *testing.T) { loadResource( t, resourceClient, - getUpstreamsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingUpstreams, + getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), + tc.existingDestinations, nil) namespacedName := types.NamespacedName{ @@ -1669,8 +1669,8 @@ func TestReconcileDeletePod(t *testing.T) { pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) - uID := getUpstreamsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedUpstreamMatches(t, resourceClient, uID, tc.expectedUpstreams) + uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) + expectedDestinationMatches(t, resourceClient, uID, tc.expectedDestinations) } testCases := []testCase{ @@ -1687,7 +1687,7 @@ func TestReconcileDeletePod(t *testing.T) { existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingUpstreams: createUpstreams(), + existingDestinations: createDestinations(), }, // TODO: enable ACLs and make sure they are deleted } @@ -1869,12 +1869,12 @@ func createProxyConfiguration(podName string, mode pbmesh.ProxyMode) *pbmesh.Pro } // createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. -func createUpstreams() *pbmesh.Upstreams { - return &pbmesh.Upstreams{ +func createDestinations() *pbmesh.Destinations { + return &pbmesh.Destinations{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{"foo"}, }, - Upstreams: []*pbmesh.Upstream{ + Destinations: []*pbmesh.Destination{ { DestinationRef: &pbresource.Reference{ Type: pbcatalog.ServiceType, @@ -1887,7 +1887,7 @@ func createUpstreams() *pbmesh.Upstreams { }, DestinationPort: "myPort", Datacenter: "", - ListenAddr: &pbmesh.Upstream_IpPort{ + ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ Port: uint32(24601), Ip: consulNodeAddress, @@ -1984,7 +1984,7 @@ func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceS require.Equal(t, "", diff, "ProxyConfigurations do not match") } -func expectedUpstreamMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedUpstreams *pbmesh.Upstreams) { +func expectedDestinationMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedUpstreams *pbmesh.Destinations) { req := &pbresource.ReadRequest{Id: id} res, err := client.Read(context.Background(), req) @@ -2003,7 +2003,7 @@ func expectedUpstreamMatches(t *testing.T, client pbresource.ResourceServiceClie require.NotNil(t, res.GetResource().GetData()) - actualUpstreams := &pbmesh.Upstreams{} + actualUpstreams := &pbmesh.Destinations{} err = res.GetResource().GetData().UnmarshalTo(actualUpstreams) require.NoError(t, err) diff --git a/control-plane/connect-inject/webhookv2/container_env.go b/control-plane/connect-inject/webhookv2/container_env.go index 568a07f124..b612b3c6aa 100644 --- a/control-plane/connect-inject/webhookv2/container_env.go +++ b/control-plane/connect-inject/webhookv2/container_env.go @@ -14,27 +14,27 @@ import ( ) func (w *MeshWebhook) containerEnvVars(pod corev1.Pod) ([]corev1.EnvVar, error) { - upstreams, err := common.ProcessPodUpstreamsForMeshWebhook(pod) + destinations, err := common.ProcessPodDestinationsForMeshWebhook(pod) if err != nil { - return nil, fmt.Errorf("error processing the upstream for container environment variable creation: %s", err.Error()) + return nil, fmt.Errorf("error processing the destination for container environment variable creation: %s", err.Error()) } - if upstreams == nil { + if destinations == nil { return nil, nil } var result []corev1.EnvVar - for _, upstream := range upstreams.Upstreams { - serviceName := strings.TrimSpace(upstream.DestinationRef.Name) + for _, destination := range destinations.Destinations { + serviceName := strings.TrimSpace(destination.DestinationRef.Name) serviceName = strings.ToUpper(strings.Replace(serviceName, "-", "_", -1)) - portName := strings.TrimSpace(upstream.DestinationPort) + portName := strings.TrimSpace(destination.DestinationPort) portName = strings.ToUpper(strings.Replace(portName, "-", "_", -1)) result = append(result, corev1.EnvVar{ Name: fmt.Sprintf("%s_%s_CONNECT_SERVICE_HOST", serviceName, portName), - Value: upstream.GetIpPort().Ip, + Value: destination.GetIpPort().Ip, }, corev1.EnvVar{ Name: fmt.Sprintf("%s_%s_CONNECT_SERVICE_PORT", serviceName, portName), - Value: strconv.Itoa(int(upstream.GetIpPort().Port)), + Value: strconv.Itoa(int(destination.GetIpPort().Port)), }) } diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go index ffaf499621..ee68db5c14 100644 --- a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go @@ -210,7 +210,7 @@ func TestHandlerHandle(t *testing.T) { }, }, { - "pod with upstreams specified", + "pod with destinations specified", MeshWebhook{ Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -268,7 +268,7 @@ func TestHandlerHandle(t *testing.T) { }, }, { - "error pod with incorrect upstreams specified", + "error pod with incorrect destinations specified", MeshWebhook{ Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSetWith("*"), diff --git a/control-plane/go.mod b/control-plane/go.mod index 920e3b3907..d8c28e4932 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases replace ( // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version - github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230922204015-ac9209d8fba9 + github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230923011829-3436b2921af5 // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd ) diff --git a/control-plane/go.sum b/control-plane/go.sum index 8d35f5daaf..65ce4fd014 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNb github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 h1:LnzgDq4e7ZfM1+XS6S21B9taQrbfdydXenL1xHyG1PQ= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230922204015-ac9209d8fba9 h1:3Xux09euVvBRlu66yJaPVasZf+OxFUlmCBzaigHwtEs= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230922204015-ac9209d8fba9/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230923011829-3436b2921af5 h1:5u/qgx4HVbPlTN6xvbgEd7ayjl1AL2pXThvipdE7ofw= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230923011829-3436b2921af5/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd h1:tRrSgVY71Jl6T2lJUokMLj3T1MO9uiSvW0CieBkjTvo= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= From f014fc76a8ecd84ed1874109e0dab5d1673b387f Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Tue, 26 Sep 2023 17:48:46 -0400 Subject: [PATCH 409/592] docs: add V2 RC changelog (#2999) * docs: add V2 RC changelog --- .changelog/2941.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .changelog/2941.txt diff --git a/.changelog/2941.txt b/.changelog/2941.txt new file mode 100644 index 0000000000..7b92ac64f3 --- /dev/null +++ b/.changelog/2941.txt @@ -0,0 +1,22 @@ +```release-note:feature +:tada: This release provides the ability to preview Consul's v2 Catalog and Resource API if enabled. +The new model supports multi-port application deployments with only a single Envoy proxy. +Note that the v1 and v2 catalogs are not cross compatible, and not all Consul features are available within this v2 feature preview. +See the [v2 Catalog and Resource API documentation](https://developer.hashicorp.com/consul/docs/k8s/multiport) for more information. +The v2 Catalog and Resources API should be considered a feature preview within this release and should not be used in production environments. + +### Limitations +* The v1 and v2 catalog APIs cannot run concurrently. +* The Consul UI must be disable. It does not support multi-port services or the v2 catalog API in this release. +* HCP Consul does not support multi-port services or the v2 catalog API in this release. +* The v2 API only supports transparent proxy mode where services that have permissions to connect to each other can use + Kube DNS to connect. + +### Known Issues +* When using the v2 API with transparent proxy, Kubernetes pods cannot use L7 liveness, readiness, or startup probes. + +[[GH-2868]](https://github.com/hashicorp/consul-k8s/pull/2868) +[[GH-2883]](https://github.com/hashicorp/consul-k8s/pull/2883) +[[GH-2930]](https://github.com/hashicorp/consul-k8s/pull/2930) +[[GH-2967]](https://github.com/hashicorp/consul-k8s/pull/2967) +``` From d3e60dfba8a643e6fa4e77377bc6fdb0256eaa8e Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:18:30 -0700 Subject: [PATCH 410/592] Disable Flaky Acceptance Tests (#3006) * flaky proxy-lifecycle * flaky rate limiting --- acceptance/tests/connect/connect_proxy_lifecycle_test.go | 2 ++ acceptance/tests/connect/local_rate_limit_test.go | 1 + 2 files changed, 3 insertions(+) diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go index a94175270e..53c48281fd 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -36,6 +36,8 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { cfg := suite.Config() cfg.SkipWhenOpenshiftAndCNI(t) + t.Skipf("TODO(flaky-1.17): NET-XXXX") + for _, testCfg := range []LifecycleShutdownConfig{ {secure: false, helmValues: map[string]string{ helmDrainListenersKey: "true", diff --git a/acceptance/tests/connect/local_rate_limit_test.go b/acceptance/tests/connect/local_rate_limit_test.go index 77a7f160c7..27f8112563 100644 --- a/acceptance/tests/connect/local_rate_limit_test.go +++ b/acceptance/tests/connect/local_rate_limit_test.go @@ -20,6 +20,7 @@ import ( // TestConnectInject_LocalRateLimiting tests that local rate limiting works as expected between services. func TestConnectInject_LocalRateLimiting(t *testing.T) { + t.Skipf("TODO(flaky-1.17): NET-XXXX") cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) From 1c393284848ec574d27f1a298c1c2006440d6f0f Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Tue, 26 Sep 2023 19:46:14 -0400 Subject: [PATCH 411/592] fix test (#3009) --- .../meshconfig_controller_test.go | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go index bc3a097079..315817267e 100644 --- a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go +++ b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go @@ -5,7 +5,6 @@ package controllersv2 import ( "context" - "fmt" "testing" "time" @@ -436,7 +435,6 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { t.Parallel() - req := require.New(t) ctx := context.Background() trafficpermissions := &v2beta1.TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ @@ -468,9 +466,6 @@ func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { c.Experiments = []string{"resource-apis"} }) - // Get watcher state to make sure we can get a healthy address. - state, err := testClient.Watcher.State() - require.NoError(t, err) // Stop the server before calling reconcile imitating a server that's not running. _ = testClient.TestServer.Stop() @@ -491,19 +486,17 @@ func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { resp, err := reconciler.Reconcile(ctx, ctrl.Request{ NamespacedName: namespacedName, }) - req.Error(err) - - expErr := fmt.Sprintf("connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:%d: connect: connection refused\"", state.Address.Port) - req.Contains(err.Error(), expErr) - req.False(resp.Requeue) + require.Error(t, err) + require.False(t, resp.Requeue) + actualErrMsg := err.Error() // Check that the status is "synced=false". err = fakeClient.Get(ctx, namespacedName, trafficpermissions) - req.NoError(err) + require.NoError(t, err) status, reason, errMsg := trafficpermissions.SyncedCondition() - req.Equal(corev1.ConditionFalse, status) - req.Equal("ConsulAgentError", reason) - req.Contains(errMsg, expErr) + require.Equal(t, corev1.ConditionFalse, status) + require.Equal(t, "ConsulAgentError", reason) + require.Contains(t, errMsg, actualErrMsg) } // TestMeshConfigController_setsSyncedToTrue tests that if the resource hasn't changed in From 885511ce79a3dba44f785a18b9690a487b936e62 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Wed, 27 Sep 2023 09:24:54 -0700 Subject: [PATCH 412/592] activate rc nightly (#3013) --- .../nightly-acceptance-1-3-0-rc1.yml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/nightly-acceptance-1-3-0-rc1.yml diff --git a/.github/workflows/nightly-acceptance-1-3-0-rc1.yml b/.github/workflows/nightly-acceptance-1-3-0-rc1.yml new file mode 100644 index 0000000000..ad2b373ca4 --- /dev/null +++ b/.github/workflows/nightly-acceptance-1-3-0-rc1.yml @@ -0,0 +1,26 @@ +# Dispatch to the consul-k8s-workflows with a nightly cron +name: nightly-acceptance +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run nightly at 12AM UTC/8PM EST/5PM PST + - cron: '0 0 * * *' + +# these should be the only settings that you will ever need to change +env: + BRANCH: "release/1.3.0-rc1" + CONTEXT: "nightly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + 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 }}" }' From 5f34a843d52a11b948a9b1e937547d0515249465 Mon Sep 17 00:00:00 2001 From: chappie <6537530+chapmanc@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:46:24 -0600 Subject: [PATCH 413/592] Load test for CTGW (#3008) * CC-6461 Cleanup load test * Reset values to main * Fix lint failures in changed module --- acceptance/framework/consul/helm_cluster.go | 8 +- acceptance/tests/cloud/load/main_test.go | 18 + acceptance/tests/cloud/load/remote.go | 67 ++++ .../tests/cloud/load/remote_load_test.go | 336 ++++++++++++++++++ .../fixtures/bases/pingpong/template.tmpl | 123 +++++++ 5 files changed, 551 insertions(+), 1 deletion(-) create mode 100644 acceptance/tests/cloud/load/main_test.go create mode 100644 acceptance/tests/cloud/load/remote.go create mode 100644 acceptance/tests/cloud/load/remote_load_test.go create mode 100644 acceptance/tests/fixtures/bases/pingpong/template.tmpl diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 74405bfebe..492c7ec513 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -46,6 +46,10 @@ type HelmCluster struct { // if there are any previous installations of this Helm chart in the cluster. SkipCheckForPreviousInstallations bool + // ChartPath is an option field that allows consumers to change the default + // chart path if so desired + ChartPath string + ctx environment.TestContext helmOptions *helm.Options releaseName string @@ -143,7 +147,9 @@ func (h *HelmCluster) Create(t *testing.T) { logger.Logf(t, "Unable to update helm repository, proceeding anyway: %s.", err) } } - + if h.ChartPath != "" { + chartName = h.ChartPath + } helm.Install(t, h.helmOptions, chartName, h.releaseName) k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName)) diff --git a/acceptance/tests/cloud/load/main_test.go b/acceptance/tests/cloud/load/main_test.go new file mode 100644 index 0000000000..4629e0d4d0 --- /dev/null +++ b/acceptance/tests/cloud/load/main_test.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package load + +import ( + "os" + "testing" + + testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" +) + +var suite testsuite.Suite + +func TestMain(m *testing.M) { + suite = testsuite.NewSuite(m) + os.Exit(suite.Run()) +} diff --git a/acceptance/tests/cloud/load/remote.go b/acceptance/tests/cloud/load/remote.go new file mode 100644 index 0000000000..906c050f70 --- /dev/null +++ b/acceptance/tests/cloud/load/remote.go @@ -0,0 +1,67 @@ +package load + +import ( + "fmt" + "strings" +) + +// GetDomainSuffix gets the suffix. +func GetDomainSuffix(env string) (string, error) { + suffix, ok := map[string]string{ + "dev-remote": "hcp.dev", + "dev": "hcp.dev", + "int": "hcp.to", + "prod": "hashicorp.cloud", + }[strings.ToLower(env)] + + if !ok { + return "", fmt.Errorf("unrecognized env: %s", env) + } + return suffix, nil +} + +// GetAPIAddr returns the address of HCP given the passed environment. +func GetAPIAddr(env string) (string, error) { + suffix, ok := map[string]string{ + "local": "http://127.0.0.1:28081", + "dev-remote": "https://api.hcp.dev", + "dev": "https://api.hcp.dev", + "int": "https://api.hcp.to", + "prod": "https://api.hashicorp.cloud", + }[strings.ToLower(env)] + + if !ok { + return "", fmt.Errorf("unrecognized env: %s", env) + } + return suffix, nil +} + +// GetAuthIDP returns the authidp for the env. +func GetAuthIDP(env string) (string, error) { + suffix, err := GetDomainSuffix(env) + if err != nil { + return "", fmt.Errorf("unrecognized env: %w", err) + } + + return fmt.Sprintf("https://auth.idp.%s", suffix), nil +} + +// GetScadaAddr returns the scadara for the env. +func GetScadaAddr(env string) (string, error) { + suffix, err := GetDomainSuffix(env) + if err != nil { + return "", fmt.Errorf("unrecognized env: %w", err) + } + + return fmt.Sprintf("https://scada.internal.%s:7224", suffix), nil +} + +// GetScadaAddr returns the scadara for the env. +func GetScadaAddrWithoutProtocol(env string) (string, error) { + suffix, err := GetDomainSuffix(env) + if err != nil { + return "", fmt.Errorf("unrecognized env: %w", err) + } + + return fmt.Sprintf("scada.internal.%s:7224", suffix), nil +} diff --git a/acceptance/tests/cloud/load/remote_load_test.go b/acceptance/tests/cloud/load/remote_load_test.go new file mode 100644 index 0000000000..2ca8e1b814 --- /dev/null +++ b/acceptance/tests/cloud/load/remote_load_test.go @@ -0,0 +1,336 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package load + +import ( + "crypto/tls" + "encoding/json" + "os" + "strconv" + "testing" + "text/template" + + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + + hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" + "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" + hcpcfg "github.com/hashicorp/hcp-sdk-go/config" + "github.com/hashicorp/hcp-sdk-go/httpclient" + "github.com/hashicorp/hcp-sdk-go/resource" +) + +type DevTokenResponse struct { + Token string `json:"token"` +} + +type hcp struct { + ResourceID string + ClientID string + ClientSecret string + AuthURL string + APIHostname string + ScadaAddress string +} + +func TestLoadTestCTGW(t *testing.T) { + _, rIDok := os.LookupEnv("HCP_RESOURCE_ID") + _, cIDok := os.LookupEnv("HCP_CLIENT_ID") + _, cSECok := os.LookupEnv("HCP_CLIENT_SECRET") + + if !rIDok || !cIDok || !cSECok { + t.Log("Must set HCP_RESOURCE_ID, HCP_CLIENT_ID and HCP_CLIENT_SECRET") + t.FailNow() + } + + apiHost := os.Getenv("HCP_AUTH_URL") + if apiHost == "" { + apiHost = "https://api.hcp.dev" + } + authURL := os.Getenv("HCP_API_HOST") + if authURL == "" { + authURL = "https://auth.idp.hcp.dev" + } + scadaAddr := os.Getenv("HCP_SCADA_ADDRESS") + if scadaAddr == "" { + scadaAddr = "scada.internal.hcp.dev:7224" + } + + env := os.Getenv("HCP_ENV") + var err error + if env != "" { + apiHost, err = GetAPIAddr(env) + require.NoError(t, err) + authURL, err = GetAuthIDP(env) + require.NoError(t, err) + scadaAddr, err = GetScadaAddrWithoutProtocol(env) + require.NoError(t, err) + } + + ctx := suite.Environment().DefaultContext(t) + + kubectlOptions := ctx.KubectlOptions(t) + ns := kubectlOptions.Namespace + k8sClient := environment.KubernetesClientFromOptions(t, kubectlOptions) + + var ( + resourceSecretName = "resource-sec-name" + resourceSecretKey = "resource-sec-key" + resourceSecretKeyValue = os.Getenv("HCP_RESOURCE_ID") + + clientIDSecretName = "clientid-sec-name" + clientIDSecretKey = "clientid-sec-key" + clientIDSecretKeyValue = os.Getenv("HCP_CLIENT_ID") + + clientSecretName = "client-sec-name" + clientSecretKey = "client-sec-key" + clientSecretKeyValue = os.Getenv("HCP_CLIENT_SECRET") + + apiHostSecretName = "apihost-sec-name" + apiHostSecretKey = "apihost-sec-key" + apiHostSecretKeyValue = apiHost + + authUrlSecretName = "authurl-sec-name" + authUrlSecretKey = "authurl-sec-key" + authUrlSecretKeyValue = authURL + + scadaAddressSecretName = "scadaaddress-sec-name" + scadaAddressSecretKey = "scadaaddress-sec-key" + scadaAddressSecretKeyValue = scadaAddr + + bootstrapTokenSecretName = "bootstrap-token" + bootstrapTokenSecretKey = "token" + ) + + aclToken := os.Getenv("HCP_CONSUL_TOKEN") + + // This should never happen during the load test since we should have already controlled for it. + if aclToken == "" { + hcpCfg := hcp{ + ResourceID: resourceSecretKeyValue, + ClientID: clientIDSecretKeyValue, + ClientSecret: clientSecretKeyValue, + AuthURL: authUrlSecretKeyValue, + APIHostname: apiHostSecretKeyValue, + ScadaAddress: scadaAddressSecretKeyValue, + } + + aclToken = hcpCfg.fetchAgentBootstrapConfig(t) + } + + cfg := suite.Config() + consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, bootstrapTokenSecretName, bootstrapTokenSecretKey, aclToken) + + releaseName := helpers.RandomName() + + helmValues := map[string]string{ + "global.imagePullPolicy": "IfNotPresent", + "global.cloud.enabled": "true", + "global.cloud.resourceId.secretName": resourceSecretName, + "global.cloud.resourceId.secretKey": resourceSecretKey, + + "global.cloud.clientId.secretName": clientIDSecretName, + "global.cloud.clientId.secretKey": clientIDSecretKey, + + "global.cloud.clientSecret.secretName": clientSecretName, + "global.cloud.clientSecret.secretKey": clientSecretKey, + + "global.cloud.apiHost.secretName": apiHostSecretName, + "global.cloud.apiHost.secretKey": apiHostSecretKey, + + "global.cloud.authUrl.secretName": authUrlSecretName, + "global.cloud.authUrl.secretKey": authUrlSecretKey, + + "global.cloud.scadaAddress.secretName": scadaAddressSecretName, + "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, + "connectInject.default": "true", + + "global.acls.manageSystemACLs": "true", + "global.acls.bootstrapToken.secretName": bootstrapTokenSecretName, + "global.acls.bootstrapToken.secretKey": bootstrapTokenSecretKey, + + "global.gossipEncryption.autoGenerate": "false", + "global.tls.enabled": "true", + "global.tls.enableAutoEncrypt": "true", + + "global.metrics.enableTelemetryCollector": "true", + "server.replicas": "3", + "server.affinity": "null", + + "global.acls.resources.requests.memory": "15Mi", + "global.acls.resources.requests.cpu": "5m", + "global.acls.resources.limits.memory": "15Mi", + "global.acls.resources.limits.cpu": "5m", + + "connectInject.initContainer.resources.requests.memory": "15Mi", + "connectInject.initContainer.resources.requests.cpu": "5m", + + "connectInject.initContainer.resources.limits.memory": "15Mi", + "connectInject.initContainer.resources.limits.cpu": "5m", + + "telemetryCollector.enabled": "true", + "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, + "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, + + "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, + "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, + "telemetryCollector.resources.requests.cpu": "100m", + "telemetryCollector.resources.limits.cpu": "100m", + + // Either we set the global.trustedCAs (make sure it's idented exactly) or we + // set TLS to insecure + + "telemetryCollector.extraEnvironmentVars.HCP_API_ADDRESS": apiHostSecretKeyValue, + } + + if cfg.ConsulImage != "" { + helmValues["global.image"] = cfg.ConsulImage + } + if cfg.ConsulCollectorImage != "" { + helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage + } + + consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), cfg, releaseName) + consulCluster.ChartPath = "../../../../charts/consul" + consulCluster.Create(t) + + logger.Log(t, "setting acl permissions for collector and services") + aclDir := "../../fixtures/bases/cloud/service-intentions" + k8s.KubectlApplyK(t, ctx.KubectlOptions(t), aclDir) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), aclDir) + }) + + logger.Log(t, "creating static-server deployment") + nServices := 2 + nServicesStr := os.Getenv("LOAD_TEST_RUNS") + if nServicesStr != "" { + i, err := strconv.Atoi(nServicesStr) + require.NoError(t, err) + nServices = i + } + + tmpl, err := template.ParseFiles("../../fixtures/bases/pingpong/template.tmpl") + if err != nil { + require.NoError(t, err) + } + + for i := 0; i < nServices; i++ { + data := &tmplData{Iteration: i, Replicas: 1} + tmplIt, err := tmpl.Clone() + require.NoError(t, err) + + // Create a temporary file + tempFile, err := os.CreateTemp("", "temp*.yaml") + if err != nil { + require.NoError(t, err) + } + + err = tmplIt.Execute(tempFile, data) + require.NoError(t, err) + // Close the temporary file to ensure that changes are saved + err = tempFile.Close() + require.NoError(t, err) + + k8s.KubectlApply(t, ctx.KubectlOptions(t), tempFile.Name()) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.KubectlDelete(t, ctx.KubectlOptions(t), tempFile.Name()) + os.Remove(tempFile.Name()) + }) + } +} + +type tmplData struct { + Iteration int + Replicas int +} + +// fetchAgentBootstrapConfig use the resource-id, client-id, and client-secret +// to call to the agent bootstrap config endpoint and parse the response into a +// CloudBootstrapConfig struct. +func (c *hcp) fetchAgentBootstrapConfig(t *testing.T) string { + cfg, err := c.HCPConfig() + require.NoError(t, err) + logger.Log(t, "Fetching Consul cluster configuration from HCP") + httpClientCfg := httpclient.Config{ + HCPConfig: cfg, + } + clientRuntime, err := httpclient.New(httpClientCfg) + require.NoError(t, err) + + hcpgnmClient := hcpgnm.New(clientRuntime, nil) + clusterResource, err := resource.FromString(c.ResourceID) + require.NoError(t, err) + + params := hcpgnm.NewAgentBootstrapConfigParams(). + WithID(clusterResource.ID). + WithLocationOrganizationID(clusterResource.Organization). + WithLocationProjectID(clusterResource.Project) + + resp, err := hcpgnmClient.AgentBootstrapConfig(params, nil) + require.NoError(t, err) + + bootstrapConfig := resp.GetPayload() + logger.Log(t, "HCP configuration successfully fetched.") + + return c.parseBootstrapConfigResponse(t, bootstrapConfig) +} + +// ConsulConfig represents 'cluster.consul_config' in the response +// fetched from the agent bootstrap config endpoint in HCP. +type ConsulConfig struct { + ACL ACL `json:"acl"` +} + +// ACL represents 'cluster.consul_config.acl' in the response +// fetched from the agent bootstrap config endpoint in HCP. +type ACL struct { + Tokens Tokens `json:"tokens"` +} + +// Tokens represents 'cluster.consul_config.acl.tokens' in the +// response fetched from the agent bootstrap config endpoint in HCP. +type Tokens struct { + Agent string `json:"agent"` + InitialManagement string `json:"initial_management"` +} + +// parseBootstrapConfigResponse unmarshals the boostrap parseBootstrapConfigResponse +// and also sets the HCPConfig values to return CloudBootstrapConfig struct. +func (c *hcp) parseBootstrapConfigResponse(t *testing.T, bootstrapRepsonse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) string { + + var consulConfig ConsulConfig + err := json.Unmarshal([]byte(bootstrapRepsonse.Bootstrap.ConsulConfig), &consulConfig) + require.NoError(t, err) + + return consulConfig.ACL.Tokens.InitialManagement +} + +func (c *hcp) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { + if c.ClientID != "" && c.ClientSecret != "" { + opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret)) + } + if c.AuthURL != "" { + opts = append(opts, hcpcfg.WithAuth(c.AuthURL, &tls.Config{})) + } + if c.APIHostname != "" { + opts = append(opts, hcpcfg.WithAPI(c.APIHostname, &tls.Config{})) + } + if c.ScadaAddress != "" { + opts = append(opts, hcpcfg.WithSCADA(c.ScadaAddress, &tls.Config{})) + } + opts = append(opts, hcpcfg.FromEnv(), hcpcfg.WithoutBrowserLogin()) + return hcpcfg.NewHCPConfig(opts...) +} diff --git a/acceptance/tests/fixtures/bases/pingpong/template.tmpl b/acceptance/tests/fixtures/bases/pingpong/template.tmpl new file mode 100644 index 0000000000..a8b893025b --- /dev/null +++ b/acceptance/tests/fixtures/bases/pingpong/template.tmpl @@ -0,0 +1,123 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: pingpong-client-{{.Iteration}} +spec: + protocol: 'http' +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: pingpong-client-{{.Iteration}} +--- +apiVersion: v1 +kind: Service +metadata: + name: pingpong-client-{{.Iteration}} +spec: + selector: + app: pingpong-client-{{.Iteration}} + ports: + - port: 80 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: pingpong-client-{{.Iteration}} + name: pingpong-client-{{.Iteration}} +spec: + replicas: {{.Replicas}} + selector: + matchLabels: + app: pingpong-client-{{.Iteration}} + template: + metadata: + annotations: + consul.hashicorp.com/connect-inject: 'true' + labels: + app: pingpong-client-{{.Iteration}} + spec: + serviceAccountName: pingpong-client-{{.Iteration}} + containers: + - name: pingpong-client-{{.Iteration}} + image: rancher/curlimages-curl:7.73.0 + command: ['/bin/sh', '-c', '--'] + args: ['while true; do sleep 1; curl -s --output /dev/null http://pingpong-server-{{.Iteration}} ; done;'] + resources: + requests: + memory: "10Mi" + cpu: "5m" + limits: + memory: "10Mi" + cpu: "5m" +--- +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceDefaults +metadata: + name: pingpong-server-{{.Iteration}} +spec: + protocol: 'http' +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: pingpong-server-{{.Iteration}} +--- +apiVersion: v1 +kind: Service +metadata: + name: pingpong-server-{{.Iteration}} +spec: + selector: + app: pingpong-server-{{.Iteration}} + ports: + - port: 80 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: pingpong-server-{{.Iteration}} + name: pingpong-server-{{.Iteration}} +spec: + replicas: {{.Replicas}} + selector: + matchLabels: + app: pingpong-server-{{.Iteration}} + template: + metadata: + annotations: + consul.hashicorp.com/connect-inject: 'true' + labels: + app: pingpong-server-{{.Iteration}} + spec: + serviceAccountName: pingpong-server-{{.Iteration}} + containers: + - name: pingpong-server-{{.Iteration}} + image: hashicorp/http-echo:latest + args: + - -text="hello world" + - -listen=:8080 + ports: + - containerPort: 8080 + resources: + requests: + memory: "10Mi" + cpu: "5m" + limits: + memory: "10Mi" + cpu: "5m" +--- + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceIntentions +metadata: + name: client-to-server-{{.Iteration}} +spec: + destination: + name: pingpong-server-{{.Iteration}} + sources: + - name: pingpong-client-{{.Iteration}} + action: allow From 3f47215f13645635e4251c429142e4a2ae8b2daa Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 29 Sep 2023 08:46:03 -0700 Subject: [PATCH 414/592] support mesh v2 annotations in cni (#3023) - include the "consul.hashicorp.com/mesh-inject-status" when checking if traffic redirection can be skipped --- control-plane/cni/main.go | 12 ++++++++++-- control-plane/cni/main_test.go | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/control-plane/cni/main.go b/control-plane/cni/main.go index e35f5ad811..d642af45a8 100644 --- a/control-plane/cni/main.go +++ b/control-plane/cni/main.go @@ -33,6 +33,10 @@ const ( // a pod after an injection is done. keyInjectStatus = "consul.hashicorp.com/connect-inject-status" + // keyMeshInjectStatus is the mesh v2 key of the annotation that is added to + // a pod after an injection is done. + keyMeshInjectStatus = "consul.hashicorp.com/mesh-inject-status" + // keyTransparentProxyStatus is the key of the annotation that is added to // a pod when transparent proxy is done. keyTransparentProxyStatus = "consul.hashicorp.com/transparent-proxy-status" @@ -246,11 +250,15 @@ func main() { } // skipTrafficRedirection looks for annotations on the pod and determines if it should skip traffic redirection. -// The absence of the annotations is the equivalent of "disabled" because it means that the connect inject mutating +// The absence of the annotations is the equivalent of "disabled" because it means that the connect-inject mutating // webhook did not run against the pod. func skipTrafficRedirection(pod corev1.Pod) bool { + // If keyInjectStatus exists, then we are dealing with a mesh v1 pod + // else we have a mesh v2 pod. We need to check for both before we can skip. if anno, ok := pod.Annotations[keyInjectStatus]; !ok || anno == "" { - return true + if anno, ok := pod.Annotations[keyMeshInjectStatus]; !ok || anno == "" { + return true + } } if anno, ok := pod.Annotations[keyTransparentProxyStatus]; !ok || anno == "" { diff --git a/control-plane/cni/main_test.go b/control-plane/cni/main_test.go index 7c289a9825..dc929f1408 100644 --- a/control-plane/cni/main_test.go +++ b/control-plane/cni/main_test.go @@ -174,6 +174,15 @@ func TestSkipTrafficRedirection(t *testing.T) { }, expectedSkip: false, }, + { + name: "Pod with v2 annotations correctly set", + annotatedPod: func(pod *corev1.Pod) *corev1.Pod { + pod.Annotations[keyMeshInjectStatus] = "foo" + pod.Annotations[keyTransparentProxyStatus] = "bar" + return pod + }, + expectedSkip: false, + }, { name: "Pod without annotations, will timeout waiting", annotatedPod: func(pod *corev1.Pod) *corev1.Pod { From 8b554668af0d67848becdaecfaee8d48b82784e9 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 29 Sep 2023 08:51:25 -0700 Subject: [PATCH 415/592] make enterprise version script more flexible (#3022) --- .../build-support/scripts/consul-enterprise-version.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/control-plane/build-support/scripts/consul-enterprise-version.sh b/control-plane/build-support/scripts/consul-enterprise-version.sh index 37df85dfc5..ca65ca79f3 100755 --- a/control-plane/build-support/scripts/consul-enterprise-version.sh +++ b/control-plane/build-support/scripts/consul-enterprise-version.sh @@ -4,10 +4,10 @@ FILE=$1 VERSION=$(yq .global.image $FILE) -if [[ !"${VERSION}" == *"hashicorppreview/consul:"* ]]; then - VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") -elif [[ !"${VERSION}" == *"hashicorp/consul:"* ]]; then +if [[ !"${VERSION}" == *"hashicorp/consul:"* ]]; then VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") +else + VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") fi -echo "${VERSION}" +echo "${VERSION}" \ No newline at end of file From 6a34e8235c06706a2ea42c2b5c96c332be4ac62c Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 2 Oct 2023 14:09:15 -0400 Subject: [PATCH 416/592] [NET-5559] Clean up TODOs in Endpoints Controller V2 (#3010) Clean up TODOs in Endpoints Controller V2 --- .../endpointsv2/endpoints_controller.go | 19 +++++++++---------- .../endpointsv2/endpoints_controller_test.go | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index 54878ba68b..33856fdc26 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -103,7 +103,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } // If we don't have at least one mesh-injected pod targeted by the service, do not register the service. - //TODO: Register service with mesh port added if global flag for inject is true, + //TODO(NET-5704): Register service with mesh port added if global flag for inject is true, // even if Endpoints are empty or have no mesh pod, iff. the service has a selector. // This should ensure that we don't target kube or consul (system) services. if consulSvc.Workloads == nil { @@ -111,7 +111,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } // Register the service in Consul. - //TODO: Maybe check service-enable label here on service/deployments/other pod owners + //TODO(NET-5704): Check service-enable label here on service/deployments/other pod owners if err = r.registerService(ctx, resourceClient, service, consulSvc); err != nil { // We could be racing with the namespace controller. // Requeue (which includes backoff) to try again. @@ -274,7 +274,7 @@ func (r *Controller) registerService(ctx context.Context, resourceClient pbresou ) r.Log.Info("registering service with Consul", getLogFieldsForResource(consulSvcResource.Id)...) - //TODO: Maybe attempt to debounce redundant writes. For now, we blindly rewrite state on each reconcile. + //TODO(NET-5681): Maybe attempt to debounce redundant writes. For now, we blindly rewrite state on each reconcile. _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: consulSvcResource}) if err != nil { r.Log.Error(err, fmt.Sprintf("failed to register service: %+v", consulSvc), getLogFieldsForResource(consulSvcResource.Id)...) @@ -325,7 +325,7 @@ func getServicePorts(service corev1.Service, prefixedPods selectorPodData, exact } } - //TODO: Error check reserved "mesh" target port + //TODO(NET-5705): Error check reserved "mesh" target port // Append Consul service mesh port in addition to discovered ports. ports = append(ports, &pbcatalog.ServicePort{ @@ -358,10 +358,10 @@ func getEffectiveTargetPort(targetPort intstr.IntOrString, prefixedPods selector // very expensive operation to repeat every time endpoints change, and we don't expect the target port to change // often if ever across pod/deployment lifecycles. // - //TODO in GA, we intend to change port selection to allow for Consul TargetPort to be numeric. If we retain the - // port selection model used here beyond GA, we should consider updating it to also consider pod health, s.t. when - // the selected port name changes between deployments of a ReplicaSet, we route traffic to ports belonging to the - // set most able to serve traffic, rather than simply the largest one. + //TODO(NET-5706) in GA, we intend to change port selection to allow for Consul TargetPort to be numeric. If we + // retain the port selection model used here beyond GA, we should consider updating it to also consider pod health, + // s.t. when the selected port name changes between deployments of a ReplicaSet, we route traffic to ports + // belonging to the set most able to serve traffic, rather than simply the largest one. targetPortInt := int32(targetPort.IntValue()) var mostPrevalentContainerPort *corev1.ContainerPort maxCount := 0 @@ -450,7 +450,6 @@ func getServiceMeta(service corev1.Service) map[string]string { constants.MetaKeyKubeNS: service.Namespace, constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, } - //TODO: Support arbitrary meta injection via annotation? (see v1) return meta } @@ -498,7 +497,7 @@ func (r *Controller) getConsulNamespace(kubeNamespace string) string { r.NSMirroringPrefix, ) - // TODO: remove this if and when the default namespace of resources is no longer required to be set explicitly. + // TODO(NET-5652): remove this if and when the default namespace of resources is no longer required to be set explicitly. if ns == "" { ns = constants.DefaultConsulNS } diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 49071b36b1..028dd1104f 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -56,12 +56,12 @@ type reconcileCase struct { expErr string } -// TODO: Allow/deny namespaces for reconcile tests +// TODO(NET-5716): Allow/deny namespaces for reconcile tests func TestReconcile_CreateService(t *testing.T) { t.Parallel() cases := []reconcileCase{ - //TODO: reenable this test as a conditional on global mesh inject flag rather than "any time we see endpoints" + //TODO(5704): reenable this test as a conditional on global mesh inject flag rather than "any time we see endpoints" //{ // // In this test, we expect the same service registration as the "basic" // // case, but without any workload selector values due to missing endpoints. From 672c686b526cec363d7c828871283a0d536718f0 Mon Sep 17 00:00:00 2001 From: Blake Covarrubias Date: Mon, 2 Oct 2023 12:46:01 -0700 Subject: [PATCH 417/592] Fix typo in ControlPlaneRequestLimits CRD for preparedQuery (#3001) This commit fixes typo in ControlPlaneRequestLimits CRD where the `preparedQuery` rate limit config was exposed as `perparedQuery`. --- .changelog/3001.txt | 3 +++ charts/consul/templates/crd-controlplanerequestlimits.yaml | 2 +- control-plane/api/v1alpha1/controlplanerequestlimit_types.go | 2 +- .../bases/consul.hashicorp.com_controlplanerequestlimits.yaml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 .changelog/3001.txt diff --git a/.changelog/3001.txt b/.changelog/3001.txt new file mode 100644 index 0000000000..a2a6ea1a7a --- /dev/null +++ b/.changelog/3001.txt @@ -0,0 +1,3 @@ +```release-note:bug +crd: fix misspelling of preparedQuery field in ControlPlaneRequestLimit CRD +``` diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml index 2b0c45a621..dceaea1ece 100644 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -118,7 +118,7 @@ spec: type: object mode: type: string - perparedQuery: + preparedQuery: properties: readRate: type: number diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_types.go b/control-plane/api/v1alpha1/controlplanerequestlimit_types.go index ac2c05ded0..5b260c0abd 100644 --- a/control-plane/api/v1alpha1/controlplanerequestlimit_types.go +++ b/control-plane/api/v1alpha1/controlplanerequestlimit_types.go @@ -93,7 +93,7 @@ type ControlPlaneRequestLimitSpec struct { Intention *ReadWriteRatesConfig `json:"intention,omitempty"` KV *ReadWriteRatesConfig `json:"kv,omitempty"` Tenancy *ReadWriteRatesConfig `json:"tenancy,omitempty"` - PreparedQuery *ReadWriteRatesConfig `json:"perparedQuery,omitempty"` + PreparedQuery *ReadWriteRatesConfig `json:"preparedQuery,omitempty"` Session *ReadWriteRatesConfig `json:"session,omitempty"` Txn *ReadWriteRatesConfig `json:"txn,omitempty"` } diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml index 11da54e9ac..f74743655e 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml @@ -114,7 +114,7 @@ spec: type: object mode: type: string - perparedQuery: + preparedQuery: properties: readRate: type: number From ad4192eee1984c6fcc20019c0753d4982b867065 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Tue, 3 Oct 2023 12:01:36 -0400 Subject: [PATCH 418/592] [NET-5718] feat(control-plane): ServiceAccount v2 backoff on missing Consul NS (#3029) feat(control-plane): ServiceAccount v2 backoff on missing Consul NS Add backoff for missing Consul NS similar to other v2 controllers. --- .../serviceaccount_controller.go | 32 +++++++++---------- .../serviceaccount_controller_ent_test.go | 10 +++--- .../serviceaccount_controller_test.go | 2 +- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go index 38d1f10989..882abdd305 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go @@ -9,7 +9,6 @@ import ( "github.com/go-logr/logr" pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-multierror" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -52,7 +51,6 @@ func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { // Reconcile reads the state of a ServiceAccount object for a Kubernetes namespace and reconciles the corresponding // Consul WorkloadIdentity. func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var errs error var serviceAccount corev1.ServiceAccount // Ignore the request if the namespace of the service account is not allowed. @@ -80,20 +78,12 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.Log.Info("retrieved ServiceAccount", "name", req.Name, "ns", req.Namespace) // Ensure the WorkloadIdentity exists. - if err = r.registerWorkloadIdentity(ctx, resourceClient, &serviceAccount); err != nil { - errs = multierror.Append(errs, err) - } - - return ctrl.Result{}, errs -} - -func (r *Controller) registerWorkloadIdentity(ctx context.Context, resourceClient pbresource.ResourceServiceClient, a *corev1.ServiceAccount) error { workloadIdentityResource := r.getWorkloadIdentityResource( - a.Name, // Consul and Kubernetes service account name will always match - r.getConsulNamespace(a.Namespace), + serviceAccount.Name, // Consul and Kubernetes service account name will always match + r.getConsulNamespace(serviceAccount.Namespace), r.getConsulPartition(), map[string]string{ - constants.MetaKeyKubeNS: a.Namespace, + constants.MetaKeyKubeNS: serviceAccount.Namespace, constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, }, ) @@ -101,13 +91,21 @@ func (r *Controller) registerWorkloadIdentity(ctx context.Context, resourceClien r.Log.Info("registering workload identity with Consul", getLogFieldsForResource(workloadIdentityResource.Id)...) // We currently blindly write these records as changes to service accounts and resulting reconciles should be rare, // and there's no data to conflict with in the payload. - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: workloadIdentityResource}) - if err != nil { + if _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: workloadIdentityResource}); err != nil { + // We could be racing with the namespace controller. + // Requeue (which includes backoff) to try again. + if inject.ConsulNamespaceIsNotFound(err) { + r.Log.Info("Consul namespace not found; re-queueing request", + "service-account", serviceAccount.Name, "ns", serviceAccount.Namespace, + "consul-ns", workloadIdentityResource.GetId().GetTenancy().GetNamespace(), "err", err.Error()) + return ctrl.Result{Requeue: true}, nil + } + r.Log.Error(err, "failed to register workload identity", getLogFieldsForResource(workloadIdentityResource.Id)...) - return err + return ctrl.Result{}, err } - return nil + return ctrl.Result{}, nil } // deregisterWorkloadIdentity deletes the WorkloadIdentity resource corresponding to the given name and namespace from diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go index 45f38783c5..d90791d093 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go @@ -9,16 +9,16 @@ import ( "testing" ) -// TODO: ConsulDestinationNamespace and EnableNSMirroring +/- prefix +// TODO(NET-5719): ConsulDestinationNamespace and EnableNSMirroring +/- prefix -// TODO(zalimeni) +// TODO(NET-5719) // Tests new WorkloadIdentity registration in a non-default NS and Partition with namespaces set to mirroring func TestReconcile_CreateWorkloadIdentity_WithNamespaces(t *testing.T) { - + //TODO(NET-5719): Add test case to cover Consul namespace missing and check for backoff } -// TODO(zalimeni) +// TODO(NET-5719) // Tests removing WorkloadIdentity registration in a non-default NS and Partition with namespaces set to mirroring func TestReconcile_DeleteWorkloadIdentity_WithNamespaces(t *testing.T) { - + //TODO(NET-5719): Add test case to cover Consul namespace missing and check for backoff } diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go index 41437c2072..7e2e3b3793 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go @@ -44,7 +44,7 @@ type reconcileCase struct { expErr string } -// TODO: Allow/deny namespaces for reconcile tests +// TODO(NET-5719): Allow/deny namespaces for reconcile tests // TestReconcile_CreateWorkloadIdentity ensures that a new ServiceAccount is reconciled // to a Consul WorkloadIdentity. From 6355b9cda50e09c0d6ab194b65e61966910a4c37 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 3 Oct 2023 09:17:30 -0700 Subject: [PATCH 419/592] Mw/add tooling rc branches (#3030) * add make target for preparing rc branch * added rc branch enterprise handling --- Makefile | 17 ++++++++++++ .../build-support/functions/10-util.sh | 27 +++++++++++++++++++ .../scripts/consul-enterprise-version.sh | 2 ++ 3 files changed, 46 insertions(+) diff --git a/Makefile b/Makefile index 336a880582..5767218e25 100644 --- a/Makefile +++ b/Makefile @@ -268,6 +268,23 @@ endif prepare-release: prepare-release-script check-preview-containers +prepare-rc-script: ## Sets the versions, updates changelog to prepare this repository to release +ifndef CONSUL_K8S_RELEASE_VERSION + $(error CONSUL_K8S_RELEASE_VERSION is required) +endif +ifndef CONSUL_K8S_RELEASE_DATE + $(error CONSUL_K8S_RELEASE_DATE is required, use format , (ex. October 4, 2022)) +endif +ifndef CONSUL_K8S_LAST_RELEASE_GIT_TAG + $(error CONSUL_K8S_LAST_RELEASE_GIT_TAG is required) +endif +ifndef CONSUL_K8S_CONSUL_VERSION + $(error CONSUL_K8S_CONSUL_VERSION is required) +endif + @source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_rc_branch $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION); \ + +prepare-rc-branch: prepare-rc-script + prepare-dev: ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index 3bc87124d9..9473226b2c 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -784,6 +784,33 @@ function prepare_release { set_changelog "${curDir}" "${version}" "${releaseDate}" "${lastGitTag}" "${prereleaseVersion}" } +function prepare_rc_branch { + # Arguments: + # $1 - Path to top level Consul source + # $2 - The version of the release + # $3 - The release date + # $4 - The last release git tag for this branch (eg. v1.1.0) + # $5 - The consul version + # $6 - The consul-dataplane version + # $7 - The pre-release version + # + # + # Returns: + # 0 - success + # * - error + + local curDir=$1 + local version=$2 + local releaseDate=$3 + local lastGitTag=$4 + local consulVersion=$5 + local consulDataplaneVersion=$6 + local prereleaseVersion=$7 + + echo "prepare_rc: dir:${curDir} consul-k8s:${version} consul:${consulVersion} consul-dataplane:${consulDataplaneVersion} date:"${releaseDate}" git tag:${lastGitTag}" + set_version "${curDir}" "${version}" "${releaseDate}" "${prereleaseVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" "${consulVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul" "${consulDataplaneVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-dataplane" +} + function prepare_dev { # Arguments: # $1 - Path to top level Consul source diff --git a/control-plane/build-support/scripts/consul-enterprise-version.sh b/control-plane/build-support/scripts/consul-enterprise-version.sh index ca65ca79f3..d0a1a40b53 100755 --- a/control-plane/build-support/scripts/consul-enterprise-version.sh +++ b/control-plane/build-support/scripts/consul-enterprise-version.sh @@ -6,6 +6,8 @@ VERSION=$(yq .global.image $FILE) if [[ !"${VERSION}" == *"hashicorp/consul:"* ]]; then VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") +elif [[ !"${VERSION}" == *"-rc"* ]]; then + VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") else VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") fi From 012227d5f0ebf23f582e6f0301e73d161e1a3863 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:23:27 -0700 Subject: [PATCH 420/592] Fix Flaky Wan Federation Failover Test (#3032) (#3035) * fix check * add better check for load balancing --- .../wan-federation/wan_federation_test.go | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index 41d237113a..9ca520ab06 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -423,19 +423,27 @@ func serviceFailoverCheck(t *testing.T, options *terratestK8s.KubectlOptions, po var resp string var err error - f := func(ft require.TestingT) { + // Retry until we get the response we expect, sometimes you get back the previous server until things stabalize + logger.Log(t, "Initial failover check") + retry.RunWith(timer, t, func(r *retry.R) { resp, err = k8s.RunKubectlAndGetOutputE(t, options, "exec", "-i", staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) - require.NoError(ft, err) - assert.Contains(ft, resp, expectedName) - } - - retry.RunWith(timer, t, func(r *retry.R) { - f(r) + assert.NoError(r, err) + assert.Contains(r, resp, expectedName) }) - // Try again to rule out load-balancing - f(t) + // Try again to rule out load-balancing. Errors can still happen so retry + logger.Log(t, "Check failover again to rule out load balancing") + for i := 0; i < 10; i++ { + time.Sleep(500 * time.Millisecond) + resp = "" + retry.RunWith(timer, t, func(r *retry.R) { + resp, err = k8s.RunKubectlAndGetOutputE(t, options, "exec", "-i", + staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) + assert.NoError(r, err) + }) + require.Contains(t, resp, expectedName) + } logger.Log(t, resp) } From 07fa7eb5101e237a400d5550ddf742e1c8e6c8ec Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 4 Oct 2023 11:29:07 -0500 Subject: [PATCH 421/592] Fixing validating cert webhooks, fixing replace statements for go mod (#3034) * Fixing validating cert webhooks, fixing replace statements for go mod * Fixed nil pointer error, added test --- control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- .../webhook_configuration.go | 15 +++++-- .../webhook_configuration_test.go | 41 +++++++++++++++++++ 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/control-plane/go.mod b/control-plane/go.mod index d8c28e4932..497dea9f7e 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases replace ( // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version - github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230923011829-3436b2921af5 + github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd ) diff --git a/control-plane/go.sum b/control-plane/go.sum index 65ce4fd014..207dd008d4 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNb github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 h1:LnzgDq4e7ZfM1+XS6S21B9taQrbfdydXenL1xHyG1PQ= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230923011829-3436b2921af5 h1:5u/qgx4HVbPlTN6xvbgEd7ayjl1AL2pXThvipdE7ofw= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230923011829-3436b2921af5/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c h1:d1ULTfDs6Hha01yfITC55MPGIsQpv0VqQfS45WZHJiY= +github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd h1:tRrSgVY71Jl6T2lJUokMLj3T1MO9uiSvW0CieBkjTvo= github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/helper/webhook-configuration/webhook_configuration.go b/control-plane/helper/webhook-configuration/webhook_configuration.go index 02dcc54cd4..be8588434d 100644 --- a/control-plane/helper/webhook-configuration/webhook_configuration.go +++ b/control-plane/helper/webhook-configuration/webhook_configuration.go @@ -25,19 +25,26 @@ func UpdateWithCABundle(ctx context.Context, clientset kubernetes.Interface, web } mutatingWebhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) - if err == nil { - return updateMutatingWebhooksWithCABundle(ctx, clientset, mutatingWebhookCfg, caCert) + if err != nil && !k8serrors.IsNotFound(err) { + return err } if !k8serrors.IsNotFound(err) { - return err + err = updateMutatingWebhooksWithCABundle(ctx, clientset, mutatingWebhookCfg, caCert) + if err != nil { + return err + } } validatingWebhookCfg, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) - if err != nil { + if err != nil && !k8serrors.IsNotFound(err) { return err } + if k8serrors.IsNotFound(err) { + return nil + } + return updateValidatingWebhooksWithCABundle(ctx, clientset, validatingWebhookCfg, caCert) } diff --git a/control-plane/helper/webhook-configuration/webhook_configuration_test.go b/control-plane/helper/webhook-configuration/webhook_configuration_test.go index e574020f5e..bb02573804 100644 --- a/control-plane/helper/webhook-configuration/webhook_configuration_test.go +++ b/control-plane/helper/webhook-configuration/webhook_configuration_test.go @@ -69,3 +69,44 @@ func TestUpdateWithCABundle_patchesExistingConfigurationForValidating(t *testing require.NoError(t, err) require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) } + +func TestUpdateWithCABundle_patchesExistingConfigurationWhenMutatingAndValidatingExist(t *testing.T) { + caBundleOne := []byte("ca-bundle-for-mwc") + ctx := context.Background() + clientset := fake.NewSimpleClientset() + + vwc := &admissionv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mwc-one", + }, + Webhooks: []admissionv1.ValidatingWebhook{ + { + Name: "webhook-under-test", + }, + }, + } + + mwc := &admissionv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mwc-one", + }, + Webhooks: []admissionv1.MutatingWebhook{ + { + Name: "webhook-under-test", + }, + }, + } + mwcCreated, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) + require.NoError(t, err) + _, err = clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, vwc, metav1.CreateOptions{}) + require.NoError(t, err) + err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) + require.NoError(t, err) + vwcFetched, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, vwc.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, caBundleOne, vwcFetched.Webhooks[0].ClientConfig.CABundle) + + mwcFetched, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) +} From 31cef6f87d237a31a60b7a7de49bc0c0311e8233 Mon Sep 17 00:00:00 2001 From: Sarah Thompson Date: Thu, 5 Oct 2023 13:49:16 +0100 Subject: [PATCH 422/592] Update ci.hcl (#3036) * Update ci.hcl This update, moves consul-k8s to use the prepare workflow. This workflow encapsulates several previous workflows, running jobs in parallel to reduce the artifact processing time. See https://hashicorp.atlassian.net/wiki/spaces/RELENG/pages/2489712686/Dec+7th+2022+-+Introducing+the+new+Prepare+workflow for more info. * Update ci.hcl add branch for testing * Update build.yml add branch for testing * Update ci.hcl remove branch used for testing * Update build.yml remove branch used for testing --- .release/ci.hcl | 146 ++---------------------------------------------- 1 file changed, 6 insertions(+), 140 deletions(-) diff --git a/.release/ci.hcl b/.release/ci.hcl index 5c781045e7..0f550192e7 100644 --- a/.release/ci.hcl +++ b/.release/ci.hcl @@ -32,80 +32,13 @@ event "build" { } } -event "upload-dev" { +event "prepare" { depends = ["build"] - action "upload-dev" { + action "prepare" { organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "upload-dev" - depends = ["build"] - } - - notification { - on = "fail" - } -} - -event "security-scan-binaries" { - depends = ["upload-dev"] - action "security-scan-binaries" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "security-scan-binaries" - config = "security-scan.hcl" - } - - notification { - on = "fail" - } -} - -event "security-scan-containers" { - depends = ["security-scan-binaries"] - action "security-scan-containers" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "security-scan-containers" - config = "security-scan.hcl" - } - - notification { - on = "fail" - } -} - -event "notarize-darwin-amd64" { - depends = ["security-scan-containers"] - action "notarize-darwin-amd64" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "notarize-darwin-amd64" - } - - notification { - on = "fail" - } -} - -event "notarize-darwin-arm64" { - depends = ["notarize-darwin-amd64"] - action "notarize-darwin-arm64" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "notarize-darwin-arm64" - } - - notification { - on = "fail" - } -} - -event "notarize-windows-386" { - depends = ["notarize-darwin-arm64"] - action "notarize-windows-386" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "notarize-windows-386" + repository = "crt-workflows-common" + workflow = "prepare" + depends = ["build"] } notification { @@ -113,75 +46,8 @@ event "notarize-windows-386" { } } -event "notarize-windows-amd64" { - depends = ["notarize-windows-386"] - action "notarize-windows-amd64" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "notarize-windows-amd64" - } - - notification { - on = "fail" - } -} - -event "sign" { - depends = ["notarize-windows-amd64"] - action "sign" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "sign" - } - - notification { - on = "fail" - } -} - -event "sign-linux-rpms" { - depends = ["sign"] - action "sign-linux-rpms" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "sign-linux-rpms" - } - - notification { - on = "fail" - } -} - -event "verify" { - depends = ["sign-linux-rpms"] - action "verify" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "verify" - } - - notification { - on = "always" - } -} - -event "promote-dev-docker" { - depends = ["verify"] - action "promote-dev-docker" { - organization = "hashicorp" - repository = "crt-workflows-common" - workflow = "promote-dev-docker" - depends = ["verify"] - } - - notification { - on = "fail" - } -} - - ## These are promotion and post-publish events -## they should be added to the end of the file after the verify event stanza. +## they should be added to the end of the file after the prepare event stanza. event "trigger-staging" { // This event is dispatched by the bob trigger-promotion command From d5f55f56f0e4f0a7870221881f44b2571cd1f29e Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Thu, 5 Oct 2023 12:00:09 -0400 Subject: [PATCH 423/592] [NET-5704] feat(control-plane): v2 only register services for injected pods (#3039) feat(control-plane): v2 only register services for injected pods Rather than indiscriminately registering any service that is not excluded due to NS allow/deny-listing, selectively register services that target injected pods (i.e. in alignment with the mesh webhook). Do not deregister services that already exist when endpoints are empty; rather, predicate registration on pod injection and deletion on service deletion from Kubernetes. In the future, we can layer on explicit allow/deny annotations for services as needed. This is already implemented by existing workload selector logic that filters on mesh-inject status; this change removes TODOs, updates comments, and adds tests for the desired behavior. --- .../endpointsv2/endpoints_controller.go | 9 +- .../endpointsv2/endpoints_controller_test.go | 384 +++++++++++++----- 2 files changed, 294 insertions(+), 99 deletions(-) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index 33856fdc26..5196e87a8c 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -102,16 +102,15 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, err } - // If we don't have at least one mesh-injected pod targeted by the service, do not register the service. - //TODO(NET-5704): Register service with mesh port added if global flag for inject is true, - // even if Endpoints are empty or have no mesh pod, iff. the service has a selector. - // This should ensure that we don't target kube or consul (system) services. + // If we don't have at least one mesh-injected pod selected by the service, don't register. + // Note that we only _delete_ services when they're deleted from K8s, not when endpoints or + // workload selectors are empty. This ensures that failover can occur normally when targeting + // the existing VIP (ClusterIP) assigned to the service. if consulSvc.Workloads == nil { return ctrl.Result{}, nil } // Register the service in Consul. - //TODO(NET-5704): Check service-enable label here on service/deployments/other pod owners if err = r.registerService(ctx, resourceClient, service, consulSvc); err != nil { // We could be racing with the namespace controller. // Requeue (which includes backoff) to try again. diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 028dd1104f..4b65ca3e1c 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -61,98 +61,110 @@ type reconcileCase struct { func TestReconcile_CreateService(t *testing.T) { t.Parallel() cases := []reconcileCase{ - //TODO(5704): reenable this test as a conditional on global mesh inject flag rather than "any time we see endpoints" - //{ - // // In this test, we expect the same service registration as the "basic" - // // case, but without any workload selector values due to missing endpoints. - // name: "Empty endpoints", - // svcName: "service-created", - // k8sObjects: func() []runtime.Object { - // endpoints := &corev1.Endpoints{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "service-created", - // Namespace: "default", - // }, - // Subsets: []corev1.EndpointSubset{ - // { - // Addresses: []corev1.EndpointAddress{}, - // }, - // }, - // } - // service := &corev1.Service{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "service-created", - // Namespace: "default", - // }, - // Spec: corev1.ServiceSpec{ - // ClusterIP: "172.18.0.1", - // Ports: []corev1.ServicePort{ - // { - // Name: "public", - // Port: 8080, - // Protocol: "TCP", - // TargetPort: intstr.FromString("my-http-port"), - // AppProtocol: &appProtocolHttp, - // }, - // { - // Name: "api", - // Port: 9090, - // Protocol: "TCP", - // TargetPort: intstr.FromString("my-grpc-port"), - // AppProtocol: &appProtocolGrpc, - // }, - // { - // Name: "other", - // Port: 10001, - // Protocol: "TCP", - // TargetPort: intstr.FromString("10001"), - // // no app protocol specified - // }, - // }, - // }, - // } - // return []runtime.Object{endpoints, service} - // }, - // expectedResource: &pbresource.Resource{ - // Id: &pbresource.ID{ - // Name: "service-created", - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Namespace: constants.DefaultConsulNS, - // Partition: constants.DefaultConsulPartition, - // }, - // }, - // Data: common.ToProtoAny(&pbcatalog.Service{ - // Ports: []*pbcatalog.ServicePort{ - // { - // VirtualPort: 8080, - // TargetPort: "my-http-port", - // Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - // }, - // { - // VirtualPort: 9090, - // TargetPort: "my-grpc-port", - // Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - // }, - // { - // VirtualPort: 10001, - // TargetPort: "10001", - // Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - // }, - // { - // TargetPort: "mesh", - // Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - // }, - // }, - // Workloads: &pbcatalog.WorkloadSelector{}, - // VirtualIps: []string{"172.18.0.1"}, - // }), - // Metadata: map[string]string{ - // constants.MetaKeyKubeNS: constants.DefaultConsulNS, - // constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - // }, - // }, - //}, + { + name: "Empty endpoints do not get registered", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{}, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 9090, + Protocol: "TCP", + TargetPort: intstr.FromString("my-grpc-port"), + AppProtocol: &appProtocolGrpc, + }, + { + Name: "other", + Port: 10001, + Protocol: "TCP", + TargetPort: intstr.FromString("10001"), + // no app protocol specified + }, + }, + }, + } + return []runtime.Object{endpoints, service} + }, + }, + { + name: "Endpoints without injected pods do not get registered", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") + removeMeshInjectStatus(t, pod1) + removeMeshInjectStatus(t, pod2) + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1, pod2), + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 9090, + Protocol: "TCP", + TargetPort: intstr.FromString("my-grpc-port"), + AppProtocol: &appProtocolGrpc, + }, + { + Name: "other", + Port: 10001, + Protocol: "TCP", + TargetPort: intstr.FromString("10001"), + // no app protocol specified + }, + }, + }, + } + return []runtime.Object{pod1, pod2, endpoints, service} + }, + }, { name: "Basic endpoints", svcName: "service-created", @@ -954,6 +966,90 @@ func TestReconcile_CreateService(t *testing.T) { }, // No expected resource }, + { + name: "Services with mix of injected and non-injected pods registered with only injected selectors", + svcName: "service-created", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") + pod3 := createServicePod(kindDaemonSet, "service-created-ds", "12345") + pod4 := createServicePod(kindDaemonSet, "service-created-ds", "23456") + removeMeshInjectStatus(t, pod1) + removeMeshInjectStatus(t, pod3) + // Retain status of second pod + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1, pod2, pod3, pod4), + Ports: []corev1.EndpointPort{ + { + Name: "public", + Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-created", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + }, + }, + } + return []runtime.Object{pod1, pod2, pod3, pod4, endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-created", + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: inject.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + // Selector only contains values for injected pods + Prefixes: []string{"service-created-rs-fghij"}, + Names: []string{"service-created-ds-23456"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -1241,11 +1337,111 @@ func TestReconcile_DeleteService(t *testing.T) { t.Parallel() cases := []reconcileCase{ { - name: "Basic Endpoints not found (service deleted)", + name: "Basic Endpoints not found (service deleted) deregisters service", svcName: "service-deleted", existingResource: &pbresource.Resource{ Id: &pbresource.ID{ - Name: "service-created", + Name: "service-deleted", + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: inject.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + Names: []string{"service-created-ds-12345"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + }, + { + name: "Empty endpoints does not cause deregistration of existing service", + svcName: "service-deleted", + k8sObjects: func() []runtime.Object { + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-deleted", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{}, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-deleted", + Namespace: "default", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + }, + }, + } + return []runtime.Object{endpoints, service} + }, + existingResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-deleted", + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: inject.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + Names: []string{"service-created-ds-12345"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-deleted", Type: pbcatalog.ServiceType, Tenancy: &pbresource.Tenancy{ Namespace: constants.DefaultConsulNS, From d308a11817d76a9a87202e53520ebcdb3613aaa9 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 5 Oct 2023 15:12:33 -0400 Subject: [PATCH 424/592] fix(v2): ca cert mounting path in mesh-init and dataplane (#3044) --- control-plane/api-gateway/gatekeeper/dataplane.go | 5 +++-- control-plane/connect-inject/constants/constants.go | 7 ++++++- .../connect-inject/webhook/consul_dataplane_sidecar.go | 7 ++++--- .../webhookv2/consul_dataplane_sidecar_test.go | 2 +- control-plane/subcommand/connect-init/command.go | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go index adaf0699b5..062c3644ad 100644 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ b/control-plane/api-gateway/gatekeeper/dataplane.go @@ -10,11 +10,12 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/utils/pointer" + "k8s.io/apimachinery/pkg/util/intstr" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "k8s.io/apimachinery/pkg/util/intstr" ) const ( @@ -162,7 +163,7 @@ func getDataplaneArgs(namespace string, config common.HelmConfig, bearerTokenFil args = append(args, "-tls-server-name="+config.ConsulTLSServerName) } if config.ConsulCACert != "" { - args = append(args, "-ca-certs="+constants.ConsulCAFile) + args = append(args, "-ca-certs="+constants.LegacyConsulCAFile) } } else { args = append(args, "-tls-disabled") diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index d4bafadc77..c426c49134 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -4,8 +4,13 @@ package constants const ( + // LegacyConsulCAFile is the location of the Consul CA file inside the injected pod. + // This is used with the V1 API. + LegacyConsulCAFile = "/consul/connect-inject/consul-ca.pem" + // ConsulCAFile is the location of the Consul CA file inside the injected pod. - ConsulCAFile = "/consul/connect-inject/consul-ca.pem" + // This is used with the V2 API. + ConsulCAFile = "/consul/mesh-inject/consul-ca.pem" // DefaultConsulNS is the default Consul namespace name. DefaultConsulNS = "default" diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index ad2e07fffa..05a67c3736 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -10,12 +10,13 @@ import ( "strings" "github.com/google/shlex" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) const ( @@ -255,7 +256,7 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, mpi mu args = append(args, "-tls-server-name="+w.ConsulTLSServerName) } if w.ConsulCACert != "" { - args = append(args, "-ca-certs="+constants.ConsulCAFile) + args = append(args, "-ca-certs="+constants.LegacyConsulCAFile) } } else { args = append(args, "-tls-disabled") diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go index 3aff917cac..84b36746f9 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go @@ -77,7 +77,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { w.ConsulTLSServerName = "server.dc1.consul" w.ConsulCACert = "consul-ca-cert" }, - additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -ca-certs=/consul/connect-inject/consul-ca.pem -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", + additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -ca-certs=/consul/mesh-inject/consul-ca.pem -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", }, "with TLS and no CA cert provided": { webhookSetupFunc: func(w *MeshWebhook) { diff --git a/control-plane/subcommand/connect-init/command.go b/control-plane/subcommand/connect-init/command.go index 4f83ea98f1..6bb66d0948 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -199,7 +199,7 @@ func (c *Command) Run(args []string) int { // todo (agentless): this should eventually be passed to consul-dataplane as a string so we don't need to write it to file. if c.consul.UseTLS && c.consul.CACertPEM != "" { - if err = common.WriteFileWithPerms(constants.ConsulCAFile, c.consul.CACertPEM, 0444); err != nil { + if err = common.WriteFileWithPerms(constants.LegacyConsulCAFile, c.consul.CACertPEM, 0444); err != nil { c.logger.Error("error writing CA cert file", "error", err) return 1 } From 5ff131e96d1dc5f005afb0a35841e58d25c811e4 Mon Sep 17 00:00:00 2001 From: David Yu Date: Thu, 5 Oct 2023 14:31:12 -0700 Subject: [PATCH 425/592] Change from 'hub' to 'gh' for member checks on JIRA PR syncs (#3048) Update jira-pr.yaml --- .github/workflows/jira-pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index 078365ac88..54a532d940 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -39,7 +39,7 @@ jobs: id: is-team-member run: | TEAM=consul - ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" + ROLE="$(gh api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" if [[ -n ${ROLE} ]]; then echo "Actor ${{ github.actor }} is a ${TEAM} team member" echo "MESSAGE=true" >> $GITHUB_OUTPUT From 4d8735292663823ba6d7c97ac6620676dd11b230 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 6 Oct 2023 12:16:19 -0400 Subject: [PATCH 426/592] Update backport assistant to use merge commits (#3050) * Update backport assistant to use merge commits * update to true --- .github/workflows/backport.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index dadde6a651..fec6b9e79c 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -13,11 +13,15 @@ jobs: backport: if: github.event.pull_request.merged runs-on: ubuntu-latest - container: hashicorpdev/backport-assistant:0.3.4 + container: hashicorpdev/backport-assistant:0.3.5 steps: - name: Run Backport Assistant run: backport-assistant backport -merge-method=squash -gh-automerge env: BACKPORT_LABEL_REGEXP: "backport/(?P\\d+\\.\\d+\\.x)" BACKPORT_TARGET_TEMPLATE: "release/{{.target}}" + # This forces the backport assistant to backport the merged commit + # instead of each commit individually. The environment variable + # just needs to exist for this to happen. + BACKPORT_MERGE_COMMIT: true GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }} From b9318cb29a2ef8c3d6a8c3170ecb8c20a769e3b8 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Fri, 6 Oct 2023 15:42:36 -0400 Subject: [PATCH 427/592] NET-5818 Gateway Policy Status (#3046) * Fix gw policy status to mimic routes when referenced gateway cannot be found * Added test for binding when gateway is deleted --- .../consul/templates/crd-gatewaypolicies.yaml | 11 -- control-plane/api-gateway/binding/binder.go | 6 + .../api-gateway/binding/binder_test.go | 148 ++++++++++++++++++ .../api/v1alpha1/gatewaypolicy_types.go | 1 - .../consul.hashicorp.com_gatewaypolicies.yaml | 11 -- 5 files changed, 154 insertions(+), 23 deletions(-) diff --git a/charts/consul/templates/crd-gatewaypolicies.yaml b/charts/consul/templates/crd-gatewaypolicies.yaml index 93ad655917..54779f4356 100644 --- a/charts/consul/templates/crd-gatewaypolicies.yaml +++ b/charts/consul/templates/crd-gatewaypolicies.yaml @@ -202,17 +202,6 @@ spec: description: GatewayPolicyStatus defines the observed state of the gateway. properties: conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: ResolvedRefs description: "Conditions describe the current conditions of the Policy. \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" items: diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index 071f01a16f..c6557985b5 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -287,8 +287,14 @@ func (b *Binder) Snapshot() *Snapshot { Namespace: service.Namespace, }) } + if common.RemoveFinalizer(&b.config.Gateway) { snapshot.Kubernetes.Updates.Add(&b.config.Gateway) + for _, policy := range b.config.Policies { + policy := policy + policy.Status = v1alpha1.GatewayPolicyStatus{} + snapshot.Kubernetes.StatusUpdates.Add(&policy) + } } } diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index cb13c1ba04..1e415f974e 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -769,6 +769,154 @@ func TestBinder_Lifecycle(t *testing.T) { {Kind: api.APIGateway, Name: "gateway-deleted"}, }, }, + "gateway deletion policies": { + config: controlledBinder(BinderConfig{ + Gateway: gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-deleted", + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{common.GatewayFinalizer}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: testGatewayClassName, + Listeners: []gwv1beta1.Listener{ + { + Name: gwv1beta1.SectionName("l1"), + }, + { + Name: gwv1beta1.SectionName("l2"), + }, + }, + }, + }, + Policies: []v1alpha1.GatewayPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "p1", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Kind: "Gateway", + Name: "gateway-deleted", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + }, + Status: v1alpha1.GatewayPolicyStatus{ + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + ObservedGeneration: 5, + Message: "gateway policy accepted", + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + ObservedGeneration: 5, + Message: "resolved references", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "p2", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Kind: "Gateway", + Name: "gateway-deleted", + SectionName: common.PointerTo(gwv1beta1.SectionName("l2")), + }, + }, + Status: v1alpha1.GatewayPolicyStatus{ + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + ObservedGeneration: 5, + Message: "gateway policy accepted", + }, + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + ObservedGeneration: 5, + Message: "resolved references", + }, + }, + }, + }, + }, + }), + resources: resourceMapResources{ + gateways: []gwv1beta1.Gateway{ + gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + { + Name: "l2", + }, + }, + }), + }, + }, + expectedStatusUpdates: []client.Object{ + &v1alpha1.GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p1", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Kind: "Gateway", + Name: "gateway-deleted", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + &v1alpha1.GatewayPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p2", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Kind: "Gateway", + Name: "gateway-deleted", + SectionName: common.PointerTo(gwv1beta1.SectionName("l2")), + }, + }, + }, + }, + expectedUpdates: []client.Object{ + addClassConfig(gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-deleted", + DeletionTimestamp: deletionTimestamp, + Finalizers: []string{}, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: testGatewayClassName, + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + }, + { + Name: "l2", + }, + }, + }, + }), + }, + expectedConsulUpdates: []api.ConfigEntry{}, + expectedConsulDeletions: []api.ResourceReference{ + {Kind: api.APIGateway, Name: "gateway-deleted"}, + }, + }, } { t.Run(name, func(t *testing.T) { tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) diff --git a/control-plane/api/v1alpha1/gatewaypolicy_types.go b/control-plane/api/v1alpha1/gatewaypolicy_types.go index 76fd0772a8..7c5a633e2b 100644 --- a/control-plane/api/v1alpha1/gatewaypolicy_types.go +++ b/control-plane/api/v1alpha1/gatewaypolicy_types.go @@ -130,6 +130,5 @@ type GatewayPolicyStatus struct { // +listType=map // +listMapKey=type // +kubebuilder:validation:MaxItems=8 - // +kubebuilder:default={{type: "Accepted", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"},{type: "ResolvedRefs", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}} Conditions []metav1.Condition `json:"conditions,omitempty"` } diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml index 7e9d9a40bf..9b6b2e05c5 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml @@ -198,17 +198,6 @@ spec: description: GatewayPolicyStatus defines the observed state of the gateway. properties: conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: ResolvedRefs description: "Conditions describe the current conditions of the Policy. \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" items: From 8b826d4650393b15bf1ed921a0720a27daab9f89 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 6 Oct 2023 17:15:02 -0400 Subject: [PATCH 428/592] revert backport assistant changes (#3058) --- .github/workflows/backport.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index fec6b9e79c..2b26c2371e 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -20,8 +20,4 @@ jobs: env: BACKPORT_LABEL_REGEXP: "backport/(?P\\d+\\.\\d+\\.x)" BACKPORT_TARGET_TEMPLATE: "release/{{.target}}" - # This forces the backport assistant to backport the merged commit - # instead of each commit individually. The environment variable - # just needs to exist for this to happen. - BACKPORT_MERGE_COMMIT: true GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }} From 77125e516fcc90d8faab1166a2ecda1c4b1ea48c Mon Sep 17 00:00:00 2001 From: John Maguire Date: Mon, 9 Oct 2023 16:33:07 -0400 Subject: [PATCH 429/592] [Net-5640] Route Binding Unit Tests (#3051) * Added test for route missing external ref * Add test for missing JWT reference, fix message for status condition * Added test for when route auth filter references invalid filter type * Added tests for binding results status --- .../api-gateway/binding/binder_test.go | 646 ++++++++++++++++++ control-plane/api-gateway/binding/result.go | 5 + .../api-gateway/binding/result_test.go | 15 + .../api-gateway/binding/validation.go | 4 +- .../api-gateway/binding/validation_test.go | 4 +- 5 files changed, 670 insertions(+), 4 deletions(-) diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index 1e415f974e..b4a274c8fa 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -64,6 +64,7 @@ type resourceMapResources struct { services []types.NamespacedName jwtProviders []*v1alpha1.JWTProvider gatewayPolicies []*v1alpha1.GatewayPolicy + externalAuthFilters []*v1alpha1.RouteAuthFilter consulInlineCertificates []api.InlineCertificateConfigEntry consulHTTPRoutes []api.HTTPRouteConfigEntry consulTCPRoutes []api.TCPRouteConfigEntry @@ -102,6 +103,11 @@ func newTestResourceMap(t *testing.T, resources resourceMapResources) *common.Re for _, r := range resources.jwtProviders { resourceMap.AddJWTProvider(r) } + + for _, r := range resources.externalAuthFilters { + resourceMap.AddExternalFilter(r) + } + return resourceMap } @@ -917,6 +923,646 @@ func TestBinder_Lifecycle(t *testing.T) { {Kind: api.APIGateway, Name: "gateway-deleted"}, }, }, + "gateway http route references missing external ref": { + resources: resourceMapResources{ + gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{{ + Name: "l1", + Protocol: "HTTP", + }}, + })}, + httpRoutes: []gwv1beta1.HTTPRoute{}, + jwtProviders: []*v1alpha1.JWTProvider{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "okta", + }, + }, + }, + externalAuthFilters: []*v1alpha1.RouteAuthFilter{}, + }, + config: controlledBinder(BinderConfig{ + ConsulGateway: &api.APIGatewayConfigEntry{ + Name: "gateway", + Kind: "api-gateway", + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Protocol: "HTTP", + }, + }, + Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, + }, + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + Protocol: gwv1beta1.HTTPProtocolType, + }, + }, + }), + HTTPRoutes: []gwv1beta1.HTTPRoute{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "h1", + Finalizers: []string{common.GatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + Name: "gateway", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Filters: []gwv1beta1.HTTPRouteFilter{{ + Type: "ExtensionRef", + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: "RouteAuthFilter", + Name: "route-auth", + }, + }}, + }, + }, + }, + }, + testHTTPRoute("http-route-2", []string{"gateway"}, nil), + }, + }), + expectedStatusUpdates: []client.Object{ + &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "h1", + Finalizers: []string{common.GatewayFinalizer}, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + Name: "gateway", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Filters: []gwv1beta1.HTTPRouteFilter{{ + Type: "ExtensionRef", + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: "RouteAuthFilter", + Name: "route-auth", + }, + }}, + }, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + { + ParentRef: gwv1beta1.ParentReference{ + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Name: "gateway", + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + ControllerName: testControllerName, + Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, + { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "FilterNotFound", + Message: "ref not found", + }, + }, + }, + }, + }, + }, + }, + common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), + addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + Protocol: gwv1beta1.HTTPProtocolType, + }, + }, + }, gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + Listeners: []gwv1beta1.ListenerStatus{ + { + Name: "l1", + SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: "True", + Reason: "Accepted", + Message: "listener accepted", + }, + { + Type: "Programmed", + Status: "True", + Reason: "Programmed", + Message: "listener programmed", + }, + { + Type: "Conflicted", + Status: "False", + Reason: "NoConflicts", + Message: "listener has no conflicts", + }, + { + Type: "ResolvedRefs", + Status: "True", + Reason: "ResolvedRefs", + Message: "resolved references", + }, + }, + }, + }, + })), + }, + expectedUpdates: []client.Object{}, + expectedConsulDeletions: []api.ResourceReference{}, + expectedConsulUpdates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: "gateway", + Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, + Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, + }, + }, + }, + "gateway http route route auth filter references missing jwt provider": { + resources: resourceMapResources{ + gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{{ + Name: "l1", + Protocol: "HTTP", + }}, + })}, + httpRoutes: []gwv1beta1.HTTPRoute{}, + jwtProviders: []*v1alpha1.JWTProvider{}, + externalAuthFilters: []*v1alpha1.RouteAuthFilter{ + { + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.RouteAuthFilterKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "route-auth", + Namespace: "default", + }, + Spec: v1alpha1.RouteAuthFilterSpec{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + }, + }, + }, + config: controlledBinder(BinderConfig{ + ConsulGateway: &api.APIGatewayConfigEntry{ + Name: "gateway", + Kind: "api-gateway", + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Protocol: "HTTP", + }, + }, + Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, + }, + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + Protocol: gwv1beta1.HTTPProtocolType, + }, + }, + }), + HTTPRoutes: []gwv1beta1.HTTPRoute{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "h1", + Finalizers: []string{common.GatewayFinalizer}, + Namespace: "default", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + Name: "gateway", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Filters: []gwv1beta1.HTTPRouteFilter{{ + Type: "ExtensionRef", + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: v1alpha1.RouteAuthFilterKind, + Name: "route-auth", + }, + }}, + }, + }, + }, + }, + testHTTPRoute("http-route-2", []string{"gateway"}, nil), + }, + }), + expectedStatusUpdates: []client.Object{ + &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "h1", + Finalizers: []string{common.GatewayFinalizer}, + Namespace: "default", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + Name: "gateway", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Filters: []gwv1beta1.HTTPRouteFilter{{ + Type: "ExtensionRef", + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: "RouteAuthFilter", + Name: "route-auth", + }, + }}, + }, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + { + ParentRef: gwv1beta1.ParentReference{ + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Name: "gateway", + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + ControllerName: testControllerName, + Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, + { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "JWTProviderNotFound", + Message: "filter invalid: default/route-auth", + }, + }, + }, + }, + }, + }, + }, + common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), + addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + Protocol: gwv1beta1.HTTPProtocolType, + }, + }, + }, gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + Listeners: []gwv1beta1.ListenerStatus{ + { + Name: "l1", + SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: "True", + Reason: "Accepted", + Message: "listener accepted", + }, + { + Type: "Programmed", + Status: "True", + Reason: "Programmed", + Message: "listener programmed", + }, + { + Type: "Conflicted", + Status: "False", + Reason: "NoConflicts", + Message: "listener has no conflicts", + }, + { + Type: "ResolvedRefs", + Status: "True", + Reason: "ResolvedRefs", + Message: "resolved references", + }, + }, + }, + }, + })), + &v1alpha1.RouteAuthFilter{ + TypeMeta: metav1.TypeMeta{Kind: "RouteAuthFilter"}, + ObjectMeta: metav1.ObjectMeta{Name: "route-auth", Namespace: "default"}, + Spec: v1alpha1.RouteAuthFilterSpec{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: "okta", + }, + }, + }, + }, + Status: v1alpha1.RouteAuthFilterStatus{ + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: "False", + Reason: "ReferencesNotValid", + Message: "route filter is not accepted due to errors with references", + }, + { + Type: "ResolvedRefs", + Status: "False", + Reason: "MissingJWTProviderReference", + Message: "route filter references one or more jwt providers that do not exist: missingProviderNames: okta", + }, + }, + }, + }, + }, + expectedUpdates: []client.Object{}, + expectedConsulDeletions: []api.ResourceReference{}, + expectedConsulUpdates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: "gateway", + Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, + Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, + }, + }, + }, + "gateway http route route references invalid external ref type": { + resources: resourceMapResources{ + gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{{ + Name: "l1", + Protocol: "HTTP", + }}, + })}, + }, + config: controlledBinder(BinderConfig{ + ConsulGateway: &api.APIGatewayConfigEntry{ + Name: "gateway", + Kind: "api-gateway", + Listeners: []api.APIGatewayListener{ + { + Name: "l1", + Protocol: "HTTP", + }, + }, + Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, + }, + Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + Protocol: gwv1beta1.HTTPProtocolType, + }, + }, + }), + HTTPRoutes: []gwv1beta1.HTTPRoute{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "h1", + Finalizers: []string{common.GatewayFinalizer}, + Namespace: "default", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + Name: "gateway", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Filters: []gwv1beta1.HTTPRouteFilter{{ + Type: "ExtensionRef", + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: "OhNoThisIsInvalid", + Name: "route-auth", + }, + }}, + }, + }, + }, + }, + testHTTPRoute("http-route-2", []string{"gateway"}, nil), + }, + }), + expectedStatusUpdates: []client.Object{ + &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "h1", + Finalizers: []string{common.GatewayFinalizer}, + Namespace: "default", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + Name: "gateway", + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + }, + }, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Filters: []gwv1beta1.HTTPRouteFilter{{ + Type: "ExtensionRef", + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: "OhNoThisIsInvalid", + Name: "route-auth", + }, + }}, + }, + }, + }, + Status: gwv1beta1.HTTPRouteStatus{ + RouteStatus: gwv1beta1.RouteStatus{ + Parents: []gwv1beta1.RouteParentStatus{ + { + ParentRef: gwv1beta1.ParentReference{ + Group: (*gwv1beta1.Group)(&common.BetaGroup), + Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), + Name: "gateway", + Namespace: common.PointerTo(gwv1beta1.Namespace("default")), + SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), + }, + ControllerName: testControllerName, + Conditions: []metav1.Condition{ + { + Type: "ResolvedRefs", + Status: metav1.ConditionTrue, + Reason: "ResolvedRefs", + Message: "resolved backend references", + }, + { + Type: "Accepted", + Status: metav1.ConditionFalse, + Reason: "UnsupportedValue", + Message: "invalid externalref filter kind", + }, + }, + }, + }, + }, + }, + }, + common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), + addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ + Listeners: []gwv1beta1.Listener{ + { + Name: "l1", + Protocol: gwv1beta1.HTTPProtocolType, + }, + }, + }, gwv1beta1.GatewayStatus{ + Addresses: []gwv1beta1.GatewayAddress{}, + Conditions: []metav1.Condition{{ + Type: "Accepted", + Status: metav1.ConditionTrue, + Reason: "Accepted", + Message: "gateway accepted", + }, { + Type: "Programmed", + Status: metav1.ConditionFalse, + Reason: "Pending", + Message: "gateway pods are still being scheduled", + }}, + Listeners: []gwv1beta1.ListenerStatus{ + { + Name: "l1", + SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, + Conditions: []metav1.Condition{ + { + Type: "Accepted", + Status: "True", + Reason: "Accepted", + Message: "listener accepted", + }, + { + Type: "Programmed", + Status: "True", + Reason: "Programmed", + Message: "listener programmed", + }, + { + Type: "Conflicted", + Status: "False", + Reason: "NoConflicts", + Message: "listener has no conflicts", + }, + { + Type: "ResolvedRefs", + Status: "True", + Reason: "ResolvedRefs", + Message: "resolved references", + }, + }, + }, + }, + })), + }, + expectedUpdates: []client.Object{}, + expectedConsulDeletions: []api.ResourceReference{}, + expectedConsulUpdates: []api.ConfigEntry{ + &api.APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: "gateway", + Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, + Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, + }, + }, + }, } { t.Run(name, func(t *testing.T) { tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index 93999ca782..38219a2c79 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -678,6 +678,11 @@ type authFilterValidationResult struct { resolvedRefErr error } +var ( + errRouteFilterJWTProvidersReferenceDoesNotExist = errors.New("route filter references one or more jwt providers that do not exist") + errRouteFilterNotAcceptedDueToInvalidRefs = errors.New("route filter is not accepted due to errors with references") +) + func (g authFilterValidationResults) Conditions(generation int64, idx int) []metav1.Condition { result := g[idx] return result.Conditions(generation) diff --git a/control-plane/api-gateway/binding/result_test.go b/control-plane/api-gateway/binding/result_test.go index 07216f7207..327e1733ae 100644 --- a/control-plane/api-gateway/binding/result_test.go +++ b/control-plane/api-gateway/binding/result_test.go @@ -52,6 +52,21 @@ func TestBindResults_Condition(t *testing.T) { Results: bindResults{{section: "", err: errRouteNoMatchingParent}}, Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NoMatchingParent", Message: "no matching parent"}, }, + { + Name: "external filter ref not found", + Results: bindResults{{section: "", err: errExternalRefNotFound}}, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "FilterNotFound", Message: "ref not found"}, + }, + { + Name: "jwt provider referenced by external filter is not found", + Results: bindResults{{section: "", err: errFilterInvalid}}, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "JWTProviderNotFound", Message: "filter invalid"}, + }, + { + Name: "route references invalid filter type", + Results: bindResults{{section: "", err: errInvalidExternalRefType}}, + Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "UnsupportedValue", Message: "invalid externalref filter kind"}, + }, { Name: "unhandled error type", Results: bindResults{{section: "abc", err: errors.New("you don't know me")}}, diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index b99e7568b7..ea9208f150 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -701,11 +701,11 @@ func validateAuthFilters(authFilters []*v1alpha1.RouteAuthFilter, resources *com if len(missingJWTProviders) > 0 { mergedNames := strings.Join(missingJWTProviders, ",") - result.resolvedRefErr = fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, mergedNames) + result.resolvedRefErr = fmt.Errorf("%w: missingProviderNames: %s", errRouteFilterJWTProvidersReferenceDoesNotExist, mergedNames) } if result.resolvedRefErr != nil { - result.acceptedErr = errNotAcceptedDueToInvalidRefs + result.acceptedErr = errRouteFilterNotAcceptedDueToInvalidRefs } results = append(results, result) diff --git a/control-plane/api-gateway/binding/validation_test.go b/control-plane/api-gateway/binding/validation_test.go index 94e3d09eeb..c1c9e250ed 100644 --- a/control-plane/api-gateway/binding/validation_test.go +++ b/control-plane/api-gateway/binding/validation_test.go @@ -1560,8 +1560,8 @@ func TestValidateAuthFilters(t *testing.T) { }}), expected: authFilterValidationResults{ authFilterValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefErr: fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "auth0"), + acceptedErr: errRouteFilterNotAcceptedDueToInvalidRefs, + resolvedRefErr: fmt.Errorf("%w: missingProviderNames: %s", errRouteFilterJWTProvidersReferenceDoesNotExist, "auth0"), }, }, }, From e937e6d3735207eb34bf3207bb27f3bc9a4e0047 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 9 Oct 2023 16:35:49 -0400 Subject: [PATCH 430/592] Fix Gateway API CRDs (#3065) * Regenerate external Gateway CRDs * Remove errant .yml * Add external CRDs to copywrite ignore --- .copywrite.hcl | 2 ++ control-plane/config/crd/external/.yml | 4 ---- .../external/gatewayclasses.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/gateways.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/grpcroutes.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/httproutes.gateway.networking.k8s.io.yaml | 3 --- .../external/referencegrants.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/tcproutes.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/tlsroutes.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/udproutes.gateway.networking.k8s.io.yaml | 3 --- 10 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 control-plane/config/crd/external/.yml diff --git a/.copywrite.hcl b/.copywrite.hcl index 9c2e0c0310..7088df0e14 100644 --- a/.copywrite.hcl +++ b/.copywrite.hcl @@ -11,6 +11,8 @@ project { # ignoring charts templates as adding copyright headers breaks all tests "charts/consul/templates/**", + # we don't own these and copyright headers break them + "control-plane/config/crds/external/**", ] } diff --git a/control-plane/config/crd/external/.yml b/control-plane/config/crd/external/.yml deleted file mode 100644 index c4837ac2c7..0000000000 --- a/control-plane/config/crd/external/.yml +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - - diff --git a/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml index 044c7af939..ff0b2fc2f6 100644 --- a/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml index b7a7c8a7d1..fa64481667 100644 --- a/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml index 8f3ab6d385..8d190ea7b6 100644 --- a/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml index b455d788de..90c151a787 100644 --- a/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml index cd39b9c12a..5eee4889a4 100644 --- a/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml index 906b442d31..a136b28f41 100644 --- a/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml index 2e22b04ef0..cc3cf65d6c 100644 --- a/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml index 19b03dcd0b..204f8e4824 100644 --- a/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: From b5c8f6e0740e49f2f4b2ad91ed440e900b1d6c0b Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Tue, 10 Oct 2023 15:09:46 -0400 Subject: [PATCH 431/592] [NET-5717] feat(control-plane): v2 add service account name to workload identity meta (#3068) feat(control-plane): v2 add service account name to workload identity meta --- control-plane/connect-inject/constants/constants.go | 4 ++++ .../controllers/serviceaccount/serviceaccount_controller.go | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index c426c49134..07bdaecba8 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -42,6 +42,10 @@ const ( // MetaKeyKubeServiceName is the meta key name for Kubernetes service name used for the Consul services. MetaKeyKubeServiceName = "k8s-service-name" + // MetaKeyKubeServiceAccountName is the meta key name for Kubernetes service account name used for the Consul + // v2 workload identity. + MetaKeyKubeServiceAccountName = "k8s-service-account-name" + // MetaKeyPodName is the meta key name for Kubernetes pod name used for the Consul services. MetaKeyPodName = "pod-name" diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go index 882abdd305..d0e8aea5ba 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go @@ -83,8 +83,9 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.getConsulNamespace(serviceAccount.Namespace), r.getConsulPartition(), map[string]string{ - constants.MetaKeyKubeNS: serviceAccount.Namespace, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, + constants.MetaKeyKubeNS: serviceAccount.Namespace, + constants.MetaKeyKubeServiceAccountName: serviceAccount.Name, + constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, }, ) From 70d3b2184d615779f548019655ff7ec26921a4d3 Mon Sep 17 00:00:00 2001 From: Chris Thain <32781396+cthain@users.noreply.github.com> Date: Wed, 11 Oct 2023 13:56:58 -0700 Subject: [PATCH 432/592] Fix local rate limiting acceptance tests (#3057) --- .../tests/connect/local_rate_limit_test.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/acceptance/tests/connect/local_rate_limit_test.go b/acceptance/tests/connect/local_rate_limit_test.go index 27f8112563..a9b0df50f0 100644 --- a/acceptance/tests/connect/local_rate_limit_test.go +++ b/acceptance/tests/connect/local_rate_limit_test.go @@ -20,7 +20,6 @@ import ( // TestConnectInject_LocalRateLimiting tests that local rate limiting works as expected between services. func TestConnectInject_LocalRateLimiting(t *testing.T) { - t.Skipf("TODO(flaky-1.17): NET-XXXX") cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) @@ -39,13 +38,20 @@ func TestConnectInject_LocalRateLimiting(t *testing.T) { connHelper.DeployClientAndServer(t) connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + // By default, target the static-server on localhost:1234 + staticServer := "localhost:1234" + if cfg.EnableTransparentProxy { + // When TProxy is enabled, use the service name. + staticServer = connhelper.StaticServerName + } + // Map the static-server URL and path to the rate limits defined in the service defaults at: // ../fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml rateLimitMap := map[string]int{ - "http://localhost:1234": 2, - "http://localhost:1234/exact": 3, - "http://localhost:1234/prefix-test": 4, - "http://localhost:1234/regex": 5, + "http://" + staticServer: 2, + "http://" + staticServer + "/exact": 3, + "http://" + staticServer + "/prefix-test": 4, + "http://" + staticServer + "/regex": 5, } opts := newRateLimitOptions(t, ctx) From 070a571a836a78aa3586632bdc1f39165b1a3837 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Wed, 11 Oct 2023 17:26:25 -0400 Subject: [PATCH 433/592] test: fix tests now that v1 catalog disabled in v2 mode (#3069) --- control-plane/go.mod | 2 +- control-plane/go.sum | 4 ++-- control-plane/helper/test/test_util.go | 2 +- control-plane/namespaces/namespaces_test.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/control-plane/go.mod b/control-plane/go.mod index 497dea9f7e..2b2c470e57 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -5,7 +5,7 @@ replace ( // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version - github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd + github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 ) require ( diff --git a/control-plane/go.sum b/control-plane/go.sum index 207dd008d4..f04eab1013 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -267,8 +267,8 @@ github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 h1:LnzgDq4 github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c h1:d1ULTfDs6Hha01yfITC55MPGIsQpv0VqQfS45WZHJiY= github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= -github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd h1:tRrSgVY71Jl6T2lJUokMLj3T1MO9uiSvW0CieBkjTvo= -github.com/hashicorp/consul/sdk v0.4.1-0.20230911164019-a69e901660bd/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= +github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 h1:j0Rvt1KiKIKlMc2UnA0ilHfIGo66SzrBztGZaCou3+w= +github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= 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= diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index 072d2544ee..63f9283025 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -54,7 +54,7 @@ func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConf t.Cleanup(func() { _ = consulServer.Stop() }) - consulServer.WaitForSerfCheck(t) + consulServer.WaitForLeader(t) consulConfig := &consul.Config{ APIClientConfig: &api.Config{Address: consulServer.HTTPAddr}, diff --git a/control-plane/namespaces/namespaces_test.go b/control-plane/namespaces/namespaces_test.go index d34bd40ee4..6e1305d959 100644 --- a/control-plane/namespaces/namespaces_test.go +++ b/control-plane/namespaces/namespaces_test.go @@ -40,7 +40,7 @@ func TestEnsureExists_AlreadyExists(tt *testing.T) { }) req.NoError(err) defer consul.Stop() - consul.WaitForSerfCheck(t) + consul.WaitForLeader(t) consulClient, err := capi.NewClient(&capi.Config{ Address: consul.HTTPAddr, Token: masterToken, From d2d6125f03e1ada64beb6588cd42af38d8ee2a72 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Thu, 12 Oct 2023 16:20:09 -0400 Subject: [PATCH 434/592] NET-5947 Add NET_BIND_SERVICE capability in security context for api-gateway pod on OpenShift (#3070) * Add NET_BIND_SERVICE capability in security context for api-gateway pod(s) * Add changelog entry * Use proper casing for ALL capabilities constant * Add test assertion verifying security context is set for every api-gateway pod * Update .changelog/3070.txt * Update 3070.txt --- .changelog/3070.txt | 3 + .../api-gateway/gatekeeper/dataplane.go | 30 +++---- .../api-gateway/gatekeeper/gatekeeper_test.go | 80 +++++++++++++++---- 3 files changed, 79 insertions(+), 34 deletions(-) create mode 100644 .changelog/3070.txt diff --git a/.changelog/3070.txt b/.changelog/3070.txt new file mode 100644 index 0000000000..acbfdfdf9d --- /dev/null +++ b/.changelog/3070.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: fix issue where missing `NET_BIND_SERVICE` capability prevented api-gateway `Pod` from starting up when deployed to OpenShift +``` diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go index 062c3644ad..090464e9c8 100644 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ b/control-plane/api-gateway/gatekeeper/dataplane.go @@ -8,9 +8,8 @@ import ( "strconv" corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" - "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" @@ -19,7 +18,7 @@ import ( ) const ( - allCapabilities = "all" + allCapabilities = "ALL" netBindCapability = "NET_BIND_SERVICE" consulDataplaneDNSBindHost = "127.0.0.1" consulDataplaneDNSBindPort = 8600 @@ -106,21 +105,18 @@ func consulDataplaneContainer(config common.HelmConfig, gcc v1alpha1.GatewayClas container.Resources = *gcc.Spec.DeploymentSpec.Resources } - // If not running in an OpenShift environment, - // skip setting the security context and let OpenShift set it for us. + // If running in vanilla K8s, run as root to allow binding to privileged ports; + // otherwise, allow the user to be assigned by OpenShift. + container.SecurityContext = &corev1.SecurityContext{ + ReadOnlyRootFilesystem: pointer.Bool(true), + // Drop any Linux capabilities you'd get as root other than NET_BIND_SERVICE. + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{netBindCapability}, + Drop: []corev1.Capability{allCapabilities}, + }, + } if !config.EnableOpenShift { - container.SecurityContext = &corev1.SecurityContext{ - ReadOnlyRootFilesystem: pointer.Bool(true), - // We have to run as root if we want to bind to any - // sort of privileged ports. The drop "all" is intended - // to drop any Linux capabilities you'd get as root - // other than NET_BIND_SERVICE. - RunAsUser: pointer.Int64(0), - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{netBindCapability}, - Drop: []corev1.Capability{allCapabilities}, - }, - } + container.SecurityContext.RunAsUser = pointer.Int64(0) } return container, nil diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go index 562b139274..8d61e6948c 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -9,8 +9,7 @@ import ( "testing" logrtest "github.com/go-logr/logr/testr" - common "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -23,11 +22,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" 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" ) var ( createdAtLabelKey = "gateway.consul.hashicorp.com/created" createdAtLabelValue = "101010" + dataplaneImage = "hashicorp/consul-dataplane" name = "test" namespace = "default" labels = map[string]string{ @@ -102,7 +105,9 @@ func TestUpsert(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ @@ -149,7 +154,9 @@ func TestUpsert(t *testing.T) { MapPrivilegedContainerPorts: 2000, }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ @@ -199,7 +206,9 @@ func TestUpsert(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ @@ -250,7 +259,8 @@ func TestUpsert(t *testing.T) { }, }, helmConfig: common.HelmConfig{ - AuthMethod: "method", + AuthMethod: "method", + ImageDataplane: dataplaneImage, }, initialResources: resources{}, finalResources: resources{ @@ -308,7 +318,9 @@ func TestUpsert(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ @@ -343,7 +355,9 @@ func TestUpsert(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ @@ -379,7 +393,8 @@ func TestUpsert(t *testing.T) { }, }, helmConfig: common.HelmConfig{ - AuthMethod: "method", + AuthMethod: "method", + ImageDataplane: dataplaneImage, }, initialResources: resources{ deployments: []*appsv1.Deployment{ @@ -462,7 +477,8 @@ func TestUpsert(t *testing.T) { }, }, helmConfig: common.HelmConfig{ - AuthMethod: "method", + AuthMethod: "method", + ImageDataplane: dataplaneImage, }, initialResources: resources{ deployments: []*appsv1.Deployment{ @@ -541,7 +557,9 @@ func TestUpsert(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), @@ -580,7 +598,9 @@ func TestUpsert(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 8, nil, nil, "", "1"), @@ -619,7 +639,9 @@ func TestUpsert(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 1, nil, nil, "", "1"), @@ -658,7 +680,9 @@ func TestUpsert(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 10, nil, nil, "", "1"), @@ -699,6 +723,7 @@ func TestUpsert(t *testing.T) { }, helmConfig: common.HelmConfig{ EnableOpenShift: true, + ImageDataplane: "hashicorp/consul-dataplane", }, initialResources: resources{}, finalResources: resources{ @@ -770,7 +795,9 @@ func TestDelete(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), @@ -807,7 +834,9 @@ func TestDelete(t *testing.T) { ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), }, }, - helmConfig: common.HelmConfig{}, + helmConfig: common.HelmConfig{ + ImageDataplane: dataplaneImage, + }, initialResources: resources{ deployments: []*appsv1.Deployment{ configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), @@ -861,7 +890,8 @@ func TestDelete(t *testing.T) { }, }, helmConfig: common.HelmConfig{ - AuthMethod: "method", + AuthMethod: "method", + ImageDataplane: dataplaneImage, }, initialResources: resources{ deployments: []*appsv1.Deployment{ @@ -977,6 +1007,22 @@ func validateResourcesExist(t *testing.T, client client.Client, resources resour require.NotNil(t, actual.Spec.Replicas) require.EqualValues(t, *expected.Spec.Replicas, *actual.Spec.Replicas) } + + // Ensure there is a consul-dataplane container dropping ALL capabilities, adding + // back the NET_BIND_SERVICE capability, and establishing a read-only root filesystem + hasDataplaneContainer := false + for _, container := range actual.Spec.Template.Spec.Containers { + if container.Image == dataplaneImage { + hasDataplaneContainer = true + require.NotNil(t, container.SecurityContext) + require.NotNil(t, container.SecurityContext.Capabilities) + require.NotNil(t, container.SecurityContext.ReadOnlyRootFilesystem) + assert.True(t, *container.SecurityContext.ReadOnlyRootFilesystem) + assert.Equal(t, []corev1.Capability{netBindCapability}, container.SecurityContext.Capabilities.Add) + assert.Equal(t, []corev1.Capability{allCapabilities}, container.SecurityContext.Capabilities.Drop) + } + } + assert.True(t, hasDataplaneContainer) } for _, expected := range resources.roles { From c339c4118391c1347ebe3ad7e3e523713845b08c Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Thu, 12 Oct 2023 16:34:56 -0400 Subject: [PATCH 435/592] [NET-5681] v2: Debounce unnecessary Service writes to Consul (#3049) v2: Debounce unnecessary Service writes to Consul Endpoints Controller v2 should not write services every time endpoints change, as this can occur far more frequently and be subject to rapid updates in the case of large clusters or flapping pod health. Instead, track writes by storing a fingerprint of the written payload along with the Consul resource generation, and compare each potential write and existing Consul resource to them, writing only if a discrepancy is detected or the fingerprint read/compare itself fails (pessimistically fall back to blind writes). --- .../endpointsv2/endpoints_controller.go | 153 ++++- .../endpointsv2/endpoints_controller_test.go | 561 +++++++++++++++++- .../controllers/endpointsv2/write_cache.go | 130 ++++ .../endpointsv2/write_cache_test.go | 237 ++++++++ .../inject-connect/v2controllers.go | 4 +- 5 files changed, 1049 insertions(+), 36 deletions(-) create mode 100644 control-plane/connect-inject/controllers/endpointsv2/write_cache.go create mode 100644 control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index 5196e87a8c..36fb390f5a 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -5,6 +5,7 @@ package endpointsv2 import ( "context" + "crypto/sha256" "fmt" "net" "sort" @@ -14,6 +15,10 @@ import ( pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/go-multierror" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -42,6 +47,13 @@ type Controller struct { // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. common.ConsulTenancyConfig + // WriteCache keeps track of records already written to Consul in order to enable debouncing of writes. + // This is useful in particular for this controller which will see potentially many more reconciles due to + // endpoint changes (e.g. pod health) than changes to service data written to Consul. + // It is intentionally simple and best-effort, and does not guarantee against all redundant writes. + // It is not persistent across restarts of the controller process. + WriteCache WriteCache + Log logr.Logger Scheme *runtime.Scheme @@ -53,6 +65,9 @@ func (r *Controller) Logger(name types.NamespacedName) logr.Logger { } func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { + if r.WriteCache == nil { + return fmt.Errorf("WriteCache was not configured for Controller") + } return ctrl.NewControllerManagedBy(mgr). For(&corev1.Endpoints{}). Complete(r) @@ -80,7 +95,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // we need to deregister that service from Consul. err = r.Client.Get(ctx, req.NamespacedName, &endpoints) if k8serrors.IsNotFound(err) { - err = r.deregisterService(ctx, resourceClient, req.Name, r.getConsulNamespace(req.Namespace), r.getConsulPartition()) + err = r.deregisterService(ctx, resourceClient, req) return ctrl.Result{}, err } else if err != nil { r.Log.Error(err, "failed to get Endpoints", "name", req.Name, "ns", req.Namespace) @@ -111,7 +126,13 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } // Register the service in Consul. - if err = r.registerService(ctx, resourceClient, service, consulSvc); err != nil { + id := getServiceID( + service.Name, // Consul and Kubernetes service name will always match + r.getConsulNamespace(service.Namespace), + r.getConsulPartition()) + meta := getServiceMeta(service) + k8sUid := string(service.UID) + if err = r.ensureService(ctx, &defaultResourceReadWriter{resourceClient}, k8sUid, id, meta, consulSvc); err != nil { // We could be racing with the namespace controller. // Requeue (which includes backoff) to try again. if inject.ConsulNamespaceIsNotFound(err) { @@ -262,34 +283,79 @@ func getOwnerPrefixFromPod(pod *corev1.Pod) string { return "" } -// registerService creates a Consul service registration from the provided Kuberetes service and endpoint information. -func (r *Controller) registerService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, k8sService corev1.Service, consulSvc *pbcatalog.Service) error { - consulSvcResource := r.getServiceResource( - consulSvc, - k8sService.Name, // Consul and Kubernetes service name will always match - r.getConsulNamespace(k8sService.Namespace), - r.getConsulPartition(), - getServiceMeta(k8sService), - ) +// ensureService upserts a Consul service resource if an identical write has not already been made to Consul since this +// controller was started. If the check for a previous write fails, the resource is written anyway. +func (r *Controller) ensureService(ctx context.Context, rw resourceReadWriter, k8sUid string, id *pbresource.ID, meta map[string]string, consulSvc *pbcatalog.Service) error { + // Use Marshal w/ Deterministic option to ensure write hash generated from Data is consistent. + data := new(anypb.Any) + if err := anypb.MarshalFrom(data, consulSvc, proto.MarshalOptions{Deterministic: true}); err != nil { + return err + } + + // Use the locally-created Resource and ID (without Uid and Version) when writing so that it + // behaves as an upsert rather than CAS. + consulSvcResource := &pbresource.Resource{ + Id: id, + Data: data, + Metadata: meta, + } + + writeHash, err := getWriteHash(consulSvcResource) + if err != nil { + r.Log.Error(err, "failed to get write hash for service; assuming it is out of sync", + getLogFieldsForResource(id)...) + } + key := getWriteCacheKey(types.NamespacedName{Name: id.Name, Namespace: id.Tenancy.Namespace}) + generationFetchFn := func() string { + // Check for whether a matching service already exists in Consul. + // Gracefully fail on error. This allows us to make a best-effort write attempt in + // case of a persistent read error or permissions issue that does not impact writing. + resp, err := rw.Read(ctx, &pbresource.ReadRequest{Id: id}) + if s, ok := status.FromError(err); !ok || (s.Code() != codes.OK && s.Code() != codes.NotFound) { + r.Log.Error(err, "failed to read existing service resource from Consul; assuming it is out of sync", + append(getLogFieldsForResource(id), "code", s.Code(), "message", s.Message())...) + return "" + } + return resp.GetResource().GetGeneration() + } + if r.WriteCache.hasMatch(key, writeHash, generationFetchFn, k8sUid) { + r.Log.V(1).Info("skipping service write due to matching write hash") + return nil + } - r.Log.Info("registering service with Consul", getLogFieldsForResource(consulSvcResource.Id)...) - //TODO(NET-5681): Maybe attempt to debounce redundant writes. For now, we blindly rewrite state on each reconcile. - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: consulSvcResource}) + r.Log.Info("writing service to Consul", getLogFieldsForResource(consulSvcResource.Id)...) + resp, err := rw.Write(ctx, &pbresource.WriteRequest{Resource: consulSvcResource}) if err != nil { - r.Log.Error(err, fmt.Sprintf("failed to register service: %+v", consulSvc), getLogFieldsForResource(consulSvcResource.Id)...) + r.Log.Error(err, fmt.Sprintf("failed to write service: %+v", consulSvc), + getLogFieldsForResource(consulSvcResource.Id)...) return err } + generation := resp.GetResource().GetGeneration() + r.Log.Info("caching service write to Consul", "hash", writeHash, "generation", generation, + "k8sUid", k8sUid) + r.WriteCache.update(key, writeHash, generation, k8sUid) + return nil } -// getServiceResource converts the given Consul service and metadata to a Consul resource API record. -func (r *Controller) getServiceResource(svc *pbcatalog.Service, name, namespace, partition string, meta map[string]string) *pbresource.Resource { - return &pbresource.Resource{ - Id: getServiceID(name, namespace, partition), - Data: inject.ToProtoAny(svc), - Metadata: meta, - } +// resourceReadWriter wraps pbresource.ResourceServiceClient for testing purposes. +// The default implementation is a passthrough used outside of tests. +type resourceReadWriter interface { + Read(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) + Write(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) +} + +type defaultResourceReadWriter struct { + client pbresource.ResourceServiceClient +} + +func (c *defaultResourceReadWriter) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { + return c.client.Read(ctx, req) +} + +func (c *defaultResourceReadWriter) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { + return c.client.Write(ctx, req) } func getServiceID(name, namespace, partition string) *pbresource.ID { @@ -316,6 +382,7 @@ func getServicePorts(service corev1.Service, prefixedPods selectorPodData, exact // // If we otherwise see repeat port values in a K8s service, we pass along and allow Consul to fail validation. if p.Protocol == corev1.ProtocolTCP { + //TODO(NET-5705): Error check reserved "mesh" target port ports = append(ports, &pbcatalog.ServicePort{ VirtualPort: uint32(p.Port), TargetPort: getEffectiveTargetPort(p.TargetPort, prefixedPods, exactNamePods), @@ -324,7 +391,10 @@ func getServicePorts(service corev1.Service, prefixedPods selectorPodData, exact } } - //TODO(NET-5705): Error check reserved "mesh" target port + // Sort for comparison stability during write deduplication. + sort.Slice(ports, func(i, j int) bool { + return ports[i].VirtualPort < ports[j].VirtualPort + }) // Append Consul service mesh port in addition to discovered ports. ports = append(ports, &pbcatalog.ServicePort{ @@ -441,6 +511,9 @@ func (r *Controller) getServiceVIPs(service corev1.Service) []string { r.Log.Info("skipping service registration virtual IP assignment due to invalid or unset ClusterIP", "name", service.Name, "ns", service.Namespace, "ip", service.Spec.ClusterIP) return nil } + + // Note: This slice needs to be sorted for stable comparison during write deduplication. + // If additional values are added in the future, the output order should be consistent. return []string{service.Spec.ClusterIP} } @@ -469,7 +542,7 @@ func getWorkloadSelector(prefixedPods selectorPodData, exactNamePods selectorPod workloads.Names = append(workloads.Names, v) } - // Sort for comparison stability + // Sort for comparison stability during write deduplication sort.Strings(workloads.Prefixes) sort.Strings(workloads.Names) @@ -478,9 +551,12 @@ func getWorkloadSelector(prefixedPods selectorPodData, exactNamePods selectorPod // deregisterService deletes the service resource corresponding to the given name and namespace from Consul. // This operation is idempotent and can be executed for non-existent services. -func (r *Controller) deregisterService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, name, namespace, partition string) error { +func (r *Controller) deregisterService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, req ctrl.Request) error { + // Regardless of whether we get an error on delete, remove the resource from the cache as we intend for it + // to be deleted and the record is no longer valid for preventing writes. + r.WriteCache.remove(getWriteCacheKey(req.NamespacedName)) _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{ - Id: getServiceID(name, namespace, partition), + Id: getServiceID(req.Name, r.getConsulNamespace(req.Namespace), r.getConsulPartition()), }) return err } @@ -510,6 +586,31 @@ func (r *Controller) getConsulPartition() string { return r.ConsulPartition } +// getWriteCacheKey gets a key to track syncronization of a K8s service to deduplicate writes to Consul. +// See also WriteCache.hasMatch. +func getWriteCacheKey(serviceName types.NamespacedName) string { + return serviceName.String() +} + +// getWriteHash gets a hash of the given resource to deduplicate writes to Consul. +// +// This hash is not intended to be cryptographically secure, only deterministic and collision-resistent +// for tens of thousands of values. +// +// If an error occurs marshalling the resource for the hash, returns a nil hash value and the error. +// error will be returned. +func getWriteHash(r *pbresource.Resource) ([]byte, error) { + // We Marshal the entire resource (not just its own Data, which is already serialized) + // in order to take advantage of the deterministic marshal offered by proto and include + // fields like Meta, which are not part of the resource Data. + data, err := proto.MarshalOptions{Deterministic: true}.Marshal(r) + if err != nil { + return nil, err + } + h := sha256.Sum256(data) + return h[:], nil +} + func getLogFieldsForResource(id *pbresource.ID) []any { return []any{ "name", id.Name, diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 4b65ca3e1c..78dbcc9fe2 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -6,17 +6,21 @@ package endpointsv2 import ( "context" "fmt" - "testing" - mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" @@ -27,12 +31,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "testing" ) const ( @@ -54,9 +53,11 @@ type reconcileCase struct { targetConsulNs string targetConsulPartition string expErr string + caseFn func(*testing.T, *reconcileCase, *Controller, pbresource.ResourceServiceClient) } // TODO(NET-5716): Allow/deny namespaces for reconcile tests +// TODO(NET-5932): Add tests for consistently sorting repeated output fields (getConsulService, getServicePorts) func TestReconcile_CreateService(t *testing.T) { t.Parallel() @@ -1325,6 +1326,169 @@ func TestReconcile_UpdateService(t *testing.T) { }, }, }, + { + name: "Redundant reconcile does not write to Consul unless resource was modified", + svcName: "service-updated", + k8sObjects: func() []runtime.Object { + pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") + endpoints := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-updated", + Namespace: "default", + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: addressesForPods(pod1, pod2), + Ports: []corev1.EndpointPort{ + { + Name: "my-http-port", + Port: 2345, + Protocol: "TCP", + AppProtocol: &appProtocolHttp, + }, + { + Name: "my-grpc-port", + Port: 6789, + Protocol: "TCP", + AppProtocol: &appProtocolGrpc, + }, + { + Name: "other", + Port: 10001, + Protocol: "TCP", + }, + }, + }, + }, + } + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "service-updated", + Namespace: "default", + UID: types.UID(randomUid()), + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "172.18.0.1", + Ports: []corev1.ServicePort{ + { + Name: "public", + Port: 8080, + Protocol: "TCP", + TargetPort: intstr.FromString("my-http-port"), + AppProtocol: &appProtocolHttp, + }, + { + Name: "api", + Port: 9090, + Protocol: "TCP", + TargetPort: intstr.FromString("my-grpc-port"), + AppProtocol: &appProtocolGrpc, + }, + { + Name: "other", + Port: 10001, + Protocol: "TCP", + TargetPort: intstr.FromString("10001"), + // no app protocol specified + }, + }, + }, + } + return []runtime.Object{pod1, pod2, endpoints, service} + }, + expectedResource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "service-updated", + Type: pbcatalog.ServiceType, + Tenancy: &pbresource.Tenancy{ + Namespace: constants.DefaultConsulNS, + Partition: constants.DefaultConsulPartition, + }, + }, + Data: inject.ToProtoAny(&pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + VirtualPort: 9090, + TargetPort: "my-grpc-port", + Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, + }, + { + VirtualPort: 10001, + TargetPort: "10001", + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }), + Metadata: map[string]string{ + constants.MetaKeyKubeNS: constants.DefaultConsulNS, + constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, + }, + }, + caseFn: func(t *testing.T, tc *reconcileCase, ep *Controller, resourceClient pbresource.ResourceServiceClient) { + runReconcile := func() { + r, err := ep.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: tc.svcName, + Namespace: tc.targetConsulNs, + }}) + require.False(t, r.Requeue) + require.NoError(t, err) + } + + // Get resource before additional reconcile + beforeResource := getAndValidateResource(t, resourceClient, tc.expectedResource.Id) + + // Run several additional reconciles, expecting no writes to Consul + for i := 0; i < 5; i++ { + runReconcile() + require.Equal(t, beforeResource.GetGeneration(), + getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), + "wanted same version for before and after resources following repeat reconcile") + } + + // Modify resource external to controller + modified := proto.Clone(beforeResource).(*pbresource.Resource) + modified.Metadata = map[string]string{"foo": "bar"} + modified.Version = "" + modified.Generation = "" + _, err := resourceClient.Write(context.Background(), &pbresource.WriteRequest{ + Resource: modified, + }) + require.NoError(t, err) + + // Get resource after additional reconcile, now expecting a new write to occur + runReconcile() + + require.NotEqual(t, beforeResource.GetGeneration(), + getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), + "wanted different version for before and after resources following modification and reconcile") + + // Get resource before additional reconcile + beforeResource = getAndValidateResource(t, resourceClient, tc.expectedResource.Id) + + // Run several additional reconciles, expecting no writes to Consul + for i := 0; i < 5; i++ { + runReconcile() + require.Equal(t, beforeResource.GetGeneration(), + getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), + "wanted same version for before and after resources following repeat reconcile") + } + }, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -1333,6 +1497,352 @@ func TestReconcile_UpdateService(t *testing.T) { } } +func TestEnsureService(t *testing.T) { + t.Parallel() + + type args struct { + k8sUid string + meta map[string]string + consulSvc *pbcatalog.Service + } + + uuid1 := randomUid() + uuid2 := randomUid() + meta1 := getServiceMeta(corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }}) + meta2 := getServiceMeta(corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }}) + meta2["some-other-key"] = "value" + + id := getServiceID( + "my-svc", + constants.DefaultConsulNS, + constants.DefaultConsulPartition) + + cases := []struct { + name string + beforeArgs args + afterArgs args + readFn func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) + writeFn func(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) + expectWrite bool + expectAlwaysWrite bool + expectErr string + caseFn func(t *testing.T, ep *Controller) + }{ + { + name: "Identical args writes once", + beforeArgs: args{ + k8sUid: uuid1, + meta: meta1, + consulSvc: &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }, + }, + // Identical to before + afterArgs: args{ + k8sUid: uuid1, + meta: meta1, + consulSvc: &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }, + }, + expectWrite: false, + }, + { + name: "Changed service payload updates resource", + beforeArgs: args{ + k8sUid: uuid1, + meta: meta1, + consulSvc: &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }, + }, + afterArgs: args{ + k8sUid: uuid1, + meta: meta1, + consulSvc: &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + // Different workload selector + Prefixes: []string{"service-created-rs-fghij"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }, + }, + expectWrite: true, + }, + { + name: "Changed service meta updates resource", + beforeArgs: args{ + k8sUid: uuid1, + meta: meta1, + consulSvc: &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }, + }, + afterArgs: args{ + k8sUid: uuid1, + meta: meta2, // Updated meta + consulSvc: &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }, + }, + expectWrite: true, + }, + { + name: "Changed k8s UID updates resource", + beforeArgs: args{ + k8sUid: uuid1, + consulSvc: &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }, + }, + // Identical to before except K8s UID, indicating delete and rewrite of K8s service + afterArgs: args{ + k8sUid: uuid2, + consulSvc: &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + { + VirtualPort: 8080, + TargetPort: "my-http-port", + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + { + TargetPort: "mesh", + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }, + }, + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"service-created-rs-abcde"}, + }, + VirtualIps: []string{"172.18.0.1"}, + }, + }, + expectWrite: true, + }, + { + name: "Read not found fails open and writes update", + readFn: func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { + return nil, status.Error(codes.NotFound, "not found") + }, + expectWrite: true, + expectAlwaysWrite: true, + }, + { + name: "Read error fails open and writes update", + readFn: func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { + return nil, status.Error(codes.PermissionDenied, "not allowed") + }, + expectWrite: true, + expectAlwaysWrite: true, + }, + { + name: "Write error does not prevent future writes (cache not updated)", + writeFn: func(ctx context.Context, request *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { + return nil, status.Error(codes.Internal, "oops") + }, + expectErr: "rpc error: code = Internal desc = oops", + caseFn: func(t *testing.T, ep *Controller) { + require.Empty(t, ep.WriteCache.(*writeCache).data) + }, + }, + } + + // Create test Consul server. + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Create the Endpoints controller. + ep := &Controller{ + Client: fake.NewClientBuilder().WithRuntimeObjects().Build(), // No k8s fetches should be needed + WriteCache: NewWriteCache(logrtest.New(t)), + Log: logrtest.New(t), + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + } + + // Set up test resourceReadWriter + rw := struct{ testReadWriter }{} + defaultRw := defaultResourceReadWriter{resourceClient} + rw.readFn = defaultRw.Read + rw.writeFn = defaultRw.Write + if tc.readFn != nil { + rw.readFn = tc.readFn + } + if tc.writeFn != nil { + rw.writeFn = tc.writeFn + } + + // Ensure caseFn runs if provided, regardless of whether error is expected + if tc.caseFn != nil { + defer tc.caseFn(t, ep) + } + + // Call first time + err := ep.ensureService(context.Background(), &rw, tc.beforeArgs.k8sUid, id, tc.beforeArgs.meta, tc.beforeArgs.consulSvc) + if tc.expectErr != "" { + require.Contains(t, err.Error(), tc.expectErr) + return + } + require.NoError(t, err) + + // Get written resource before additional calls + beforeResource := getAndValidateResource(t, resourceClient, id) + + // Call a second time + err = ep.ensureService(context.Background(), &rw, tc.afterArgs.k8sUid, id, tc.afterArgs.meta, tc.afterArgs.consulSvc) + require.NoError(t, err) + + // Check for change on second call to ensureService + if tc.expectWrite { + require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), + "wanted different version for before and after resources following modification and reconcile") + } else { + require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), + "wanted same version for before and after resources following repeat reconcile") + } + + // Call several additional times + for i := 0; i < 5; i++ { + // Get written resource before each additional call + beforeResource = getAndValidateResource(t, resourceClient, id) + + err := ep.ensureService(context.Background(), &rw, tc.afterArgs.k8sUid, id, tc.afterArgs.meta, tc.afterArgs.consulSvc) + require.NoError(t, err) + + if tc.expectAlwaysWrite { + require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), + "wanted different version for before and after resources following modification and reconcile") + } else { + require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), + "wanted same version for before and after resources following repeat reconcile") + } + } + }) + } +} + +type testReadWriter struct { + readFn func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) + writeFn func(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) +} + +func (rw *testReadWriter) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { + return rw.readFn(ctx, req) +} + +func (rw *testReadWriter) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { + return rw.writeFn(ctx, req) +} + func TestReconcile_DeleteService(t *testing.T) { t.Parallel() cases := []reconcileCase{ @@ -1371,6 +1881,10 @@ func TestReconcile_DeleteService(t *testing.T) { constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, }, }, + caseFn: func(t *testing.T, _ *reconcileCase, ep *Controller, _ pbresource.ResourceServiceClient) { + // Ensure cache was also cleared + require.Empty(t, ep.WriteCache.(*writeCache).data) + }, }, { name: "Empty endpoints does not cause deregistration of existing service", @@ -1634,7 +2148,8 @@ func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { // Create the Endpoints controller. ep := &Controller{ - Log: logrtest.New(t), + WriteCache: NewWriteCache(logrtest.New(t)), + Log: logrtest.New(t), } prefixedPods, exactNamePods, err := ep.getWorkloadDataFromEndpoints(ctx, &pf, tc.endpoints) @@ -1681,6 +2196,7 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { // Create the Endpoints controller. ep := &Controller{ Client: fakeClient, + WriteCache: NewWriteCache(logrtest.New(t)), Log: logrtest.New(t), ConsulServerConnMgr: testClient.Watcher, K8sNamespaceConfig: common.K8sNamespaceConfig{ @@ -1722,6 +2238,10 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { require.False(t, resp.Requeue) expectedServiceMatches(t, resourceClient, tc.svcName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) + + if tc.caseFn != nil { + tc.caseFn(t, &tc, ep, resourceClient) + } } func expectedServiceMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { @@ -1817,7 +2337,30 @@ func randomKubernetesId() string { return u[:5] } +func randomUid() string { + u, err := uuid.GenerateUUID() + if err != nil { + panic(err) + } + return u +} + func removeMeshInjectStatus(t *testing.T, pod *corev1.Pod) { delete(pod.Annotations, constants.KeyMeshInjectStatus) require.False(t, inject.HasBeenMeshInjected(*pod)) } + +func getAndValidateResource(t *testing.T, resourceClient pbresource.ResourceServiceClient, id *pbresource.ID) *pbresource.Resource { + resp, err := resourceClient.Read(metadata.NewOutgoingContext( + context.Background(), + // Read with strong consistency to avoid race conditions + metadata.New(map[string]string{"x-consul-consistency-mode": "consistent"}), + ), &pbresource.ReadRequest{ + Id: id, + }) + require.NoError(t, err) + r := resp.GetResource() + require.NotNil(t, r) + require.NotEmpty(t, r.GetGeneration()) + return r +} diff --git a/control-plane/connect-inject/controllers/endpointsv2/write_cache.go b/control-plane/connect-inject/controllers/endpointsv2/write_cache.go new file mode 100644 index 0000000000..0baf537ef7 --- /dev/null +++ b/control-plane/connect-inject/controllers/endpointsv2/write_cache.go @@ -0,0 +1,130 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package endpointsv2 + +import ( + "bytes" + "fmt" + "github.com/go-logr/logr" + "github.com/hashicorp/go-multierror" + "sync" +) + +// consulWriteRecord is a record of writing a resource to Consul for the sake of deduplicating writes. +// +// It is bounded in size and even a low-resource pod should be able to store 10Ks of them in-memory without worrying +// about eviction. On average, assuming a SHA256 hash, the total size of each record should be approximately 150 bytes. +type consulWriteRecord struct { + // inputHash is a detrministic hash of the payload written to Consul. + // It should be derived from the "source" data rather than the returned payload in order to be unaffected by added + // fields and defaulting behavior defined by Consul. + inputHash []byte + // generation is the generation of the written resource in Consul. This ensures that we write to Consul if a + // redundant reconcile occurs, but the actual Consul resource has been modified since the last write. + generation string + // k8sUid is the UID of the corresponding resource in K8s. This allows us to check for K8s service recreation in + // between successful reconciles even though deletion of a K8s resource does not expose the UID of the deleted + // resource (the reconcile request only contains the namespaced name). + k8sUid string +} + +// WriteCache is a simple, unbounded, thread-safe in-memory cache for tracking writes of Consul resources. +// It can be used to deduplicate identical writes client-side to "debounce" writes during repeat reconciles +// that do not impact data already written to Consul. +type WriteCache interface { + hasMatch(key string, hash []byte, generationFetchFn func() string, k8sUid string) bool + update(key string, hash []byte, generation string, k8sUid string) + remove(key string) +} + +type writeCache struct { + data map[string]consulWriteRecord + dataMutex sync.RWMutex + + log logr.Logger +} + +func NewWriteCache(log logr.Logger) WriteCache { + return &writeCache{ + data: make(map[string]consulWriteRecord), + log: log.WithName("writeCache"), + } +} + +// update upserts a record containing the given hash and generation to the cache at the given key. +func (c *writeCache) update(key string, hash []byte, generation string, k8sUid string) { + c.dataMutex.Lock() + defer c.dataMutex.Unlock() + + var err error + if key == "" { + err = multierror.Append(err, fmt.Errorf("key was empty")) + } + if len(hash) == 0 { + err = multierror.Append(err, fmt.Errorf("hash was empty")) + } + if generation == "" { + err = multierror.Append(err, fmt.Errorf("generation was empty")) + } + if k8sUid == "" { + err = multierror.Append(err, fmt.Errorf("k8sUid was empty")) + } + if err != nil { + c.log.Error(err, "writeCache could not be updated due to empty value(s) - redundant writes may be repeated") + return + } + + c.data[key] = consulWriteRecord{ + inputHash: hash, + generation: generation, + k8sUid: k8sUid, + } +} + +// remove removes a record from the cache at the given key. +func (c *writeCache) remove(key string) { + c.dataMutex.Lock() + defer c.dataMutex.Unlock() + + delete(c.data, key) +} + +// hasMatch returns true iff. there is an existing write record for the given key in the cache, and that record matches +// the provided non-empty hash, generation, and Kubernetes UID. +// +// The generation is fetched rather than provided directly s.t. a call to Consul can be skipped if a record is not found +// or other available fields do not match. +// +// While not strictly necessary assuming the controller is the sole writer of the resource, the generation check ensures +// that the resource is kept in sync even if externally modified. +// +// When checking for a match, ensures the UID of the K8s service also matches s.t. we don't skip updates on recreation +// of a K8s service, as the intent of the user may have been to force a sync, and a future solution that stores write +// fingerprints in K8s annotations would also have this behavior. +func (c *writeCache) hasMatch(key string, hash []byte, generationFetchFn func() string, k8sUid string) bool { + var lastHash []byte + lastGeneration := "" + lastK8sUid := "" + if s, ok := c.get(key); ok { + lastHash = s.inputHash + lastGeneration = s.generation + lastK8sUid = s.k8sUid + } + + if len(lastHash) == 0 || lastGeneration == "" || lastK8sUid == "" { + return false + } + + return bytes.Equal(lastHash, hash) && + lastK8sUid == k8sUid && + lastGeneration == generationFetchFn() // Fetch generation only if other fields match +} + +func (c *writeCache) get(key string) (consulWriteRecord, bool) { + c.dataMutex.RLock() + defer c.dataMutex.RUnlock() + + v, ok := c.data[key] + return v, ok +} diff --git a/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go b/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go new file mode 100644 index 0000000000..059af35257 --- /dev/null +++ b/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go @@ -0,0 +1,237 @@ +package endpointsv2 + +import ( + logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/go-uuid" + "testing" +) + +func Test_writeCache(t *testing.T) { + testHash := randomBytes() + testGeneration := randomString() + testK8sUid := randomString() + + type args struct { + key string + hash []byte + generationFetchFn func() string + k8sUid string + } + cases := []struct { + name string + args args + setupFn func(args args, cache WriteCache) + want bool + }{ + { + name: "No data returns false", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + want: false, + }, + { + name: "Non-matching key returns false", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update("another-key", args.hash, args.generationFetchFn(), args.k8sUid) + }, + want: false, + }, + { + name: "Non-matching hash returns false", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, randomBytes(), args.generationFetchFn(), args.k8sUid) + }, + want: false, + }, + { + name: "Non-matching generation returns false", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, args.hash, randomString(), args.k8sUid) + }, + want: false, + }, + { + name: "Non-matching k8sUid returns false", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, args.hash, args.generationFetchFn(), randomString()) + }, + want: false, + }, + { + name: "Matching data returns true", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) + }, + want: true, + }, + { + name: "Removed data returns false", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) + cache.update("another-key", randomBytes(), randomString(), randomString()) + cache.remove(args.key) + }, + want: false, + }, + { + name: "Replaced data returns false", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) + cache.update(args.key, randomBytes(), args.generationFetchFn(), args.k8sUid) + }, + want: false, + }, + { + name: "Invalid hash does not update cache", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) + cache.update(args.key, []byte{}, args.generationFetchFn(), args.k8sUid) + }, + want: true, + }, + { + name: "Invalid generation does not update cache", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) + cache.update(args.key, args.hash, "", args.k8sUid) + }, + want: true, + }, + { + name: "Invalid k8sUid does not update cache", + args: args{ + "foo", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) + cache.update(args.key, args.hash, args.generationFetchFn(), "") + }, + want: true, + }, + { + name: "Invalid key is ignored", + args: args{ + "", + testHash, + func() string { + return testGeneration + }, + testK8sUid, + }, + setupFn: func(args args, cache WriteCache) { + cache.update("", args.hash, args.generationFetchFn(), args.k8sUid) + }, + want: false, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + c := NewWriteCache(logrtest.New(t)) + if tc.setupFn != nil { + tc.setupFn(tc.args, c) + } + if got := c.hasMatch(tc.args.key, tc.args.hash, tc.args.generationFetchFn, tc.args.k8sUid); got != tc.want { + t.Errorf("hasMatch() = %v, want %v", got, tc.want) + } + }) + } +} + +func randomBytes() []byte { + b, err := uuid.GenerateRandomBytes(32) + if err != nil { + panic(err) + } + return b +} + +func randomString() string { + u, err := uuid.GenerateUUID() + if err != nil { + panic(err) + } + return u +} diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 9d42082dee..da522c6840 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -79,12 +79,14 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } + endpointsLogger := ctrl.Log.WithName("controller").WithName("endpoints") if err := (&endpointsv2.Controller{ Client: mgr.GetClient(), ConsulServerConnMgr: watcher, K8sNamespaceConfig: k8sNsConfig, ConsulTenancyConfig: consulTenancyConfig, - Log: ctrl.Log.WithName("controller").WithName("endpoints"), + WriteCache: endpointsv2.NewWriteCache(endpointsLogger), + Log: endpointsLogger, Scheme: mgr.GetScheme(), Context: ctx, }).SetupWithManager(mgr); err != nil { From 0b6d4eb908f47f76760530694b365f7726869c02 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Fri, 13 Oct 2023 18:55:28 -0400 Subject: [PATCH 436/592] =?UTF-8?q?Fix=20ENT=20Tests=20Now=20that=20They?= =?UTF-8?q?=20Are=20Running=20Again=20=F0=9F=8F=83=20=20(#3077)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test: fix tests that are failing on main --- .../configentry_controller_ent_test.go | 6 +- .../meshconfig_controller_test.go | 36 ++++++ .../endpoints_controller_ent_test.go | 108 +++++++++++++++++- .../endpoints/endpoints_controller_test.go | 1 + .../endpointsv2/endpoints_controller_test.go | 20 +++- .../peering_acceptor_controller_test.go | 10 +- .../peering/peering_dialer_controller_test.go | 14 ++- .../pod/pod_controller_ent_test.go | 6 + .../controllers/pod/pod_controller_test.go | 72 +++++++++++- .../serviceaccount_controller_test.go | 6 + .../server-acl-init/command_ent_test.go | 24 ++-- 11 files changed, 269 insertions(+), 34 deletions(-) diff --git a/control-plane/config-entries/controllers/configentry_controller_ent_test.go b/control-plane/config-entries/controllers/configentry_controller_ent_test.go index 29c114042f..a0fb5ce91e 100644 --- a/control-plane/config-entries/controllers/configentry_controller_ent_test.go +++ b/control-plane/config-entries/controllers/configentry_controller_ent_test.go @@ -294,13 +294,13 @@ func TestConfigEntryController_updatesEntConfigEntry(t *testing.T) { }, updateF: func(resource common.ConfigEntryResource) { sg := resource.(*v1alpha1.SamenessGroup) - sg.Spec.IncludeLocal = false + sg.Spec.DefaultForFailover = false }, compare: func(t *testing.T, consulEntry capi.ConfigEntry) { resource, ok := consulEntry.(*capi.SamenessGroupConfigEntry) require.True(t, ok, "cast error") - require.Equal(t, true, resource.DefaultForFailover) - require.Equal(t, false, resource.IncludeLocal) + require.Equal(t, false, resource.DefaultForFailover) + require.Equal(t, true, resource.IncludeLocal) require.Equal(t, "dc1", resource.Members[0].Peer) require.Equal(t, "", resource.Members[0].Partition) }, diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go index 315817267e..ff2a94ae27 100644 --- a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go +++ b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go @@ -145,6 +145,11 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) namespacedName := types.NamespacedName{ Namespace: metav1.NamespaceDefault, @@ -284,6 +289,12 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { }) resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // We haven't run reconcile yet, so we must create the MeshConfig // in Consul ourselves. { @@ -400,6 +411,11 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // We haven't run reconcile yet, so we must create the config entry // in Consul ourselves. { @@ -466,6 +482,11 @@ func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { c.Experiments = []string{"resource-apis"} }) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Stop the server before calling reconcile imitating a server that's not running. _ = testClient.TestServer.Stop() @@ -547,6 +568,11 @@ func TestMeshConfigController_setsSyncedToTrue(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + reconciler := &TrafficPermissionsController{ Client: fakeClient, Log: logrtest.New(t), @@ -622,6 +648,11 @@ func TestMeshConfigController_doesNotCreateUnownedMeshConfig(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + unmanagedResource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) unmanagedResource.Metadata = make(map[string]string) // Zero out the metadata @@ -725,6 +756,11 @@ func TestMeshConfigController_doesNotDeleteUnownedConfig(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + reconciler := &TrafficPermissionsController{ Client: fakeClient, Log: logrtest.New(t), diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 9f9f54ba45..19d7718337 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -641,6 +641,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -653,6 +656,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -714,6 +720,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-different-consul-svc-name", Service: "different-consul-svc-name", @@ -726,6 +735,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-different-consul-svc-name-sidecar-proxy", @@ -795,6 +807,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -807,6 +822,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -877,6 +895,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -889,6 +910,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -906,6 +930,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-service-updated", Service: "service-updated", @@ -918,6 +945,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-service-updated-sidecar-proxy", @@ -980,6 +1010,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-different-consul-svc-name", Service: "different-consul-svc-name", @@ -992,6 +1025,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-different-consul-svc-name-sidecar-proxy", @@ -1009,6 +1045,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-different-consul-svc-name", Service: "different-consul-svc-name", @@ -1021,6 +1060,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-different-consul-svc-name-sidecar-proxy", @@ -1069,6 +1111,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -1081,6 +1126,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -1098,6 +1146,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-service-updated", Service: "service-updated", @@ -1110,6 +1161,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-service-updated-sidecar-proxy", @@ -1146,6 +1200,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-different-consul-svc-name", Service: "different-consul-svc-name", @@ -1158,6 +1215,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-different-consul-svc-name-sidecar-proxy", @@ -1175,6 +1235,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-different-consul-svc-name", Service: "different-consul-svc-name", @@ -1187,6 +1250,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-different-consul-svc-name-sidecar-proxy", @@ -1236,6 +1302,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -1254,6 +1323,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -1322,6 +1394,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod1-service-updated", Service: "service-updated", @@ -1340,6 +1415,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod1-service-updated-sidecar-proxy", @@ -1363,6 +1441,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ ID: "pod2-service-updated", Service: "service-updated", @@ -1381,6 +1462,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { { Node: consulNodeName, Address: consulNodeAddress, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, Service: &api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: "pod2-service-updated-sidecar-proxy", @@ -1436,8 +1520,9 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { c.ACL.Tokens.InitialManagement = adminToken } }) - consulClient := testClient.APIClient + // Coincidentally, this allows enough time for the bootstrap token to be generated + testClient.TestServer.WaitForActiveCARoot(t) _, err := namespaces.EnsureExists(consulClient, ts.ExpConsulNS, "") require.NoError(t, err) @@ -1742,6 +1827,8 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { } }) consulClient := testClient.APIClient + // Coincidentally, this allows enough time for the bootstrap token to be generated + testClient.TestServer.WaitForActiveCARoot(t) _, err := namespaces.EnsureExists(consulClient, ts.ExpConsulNS, "") require.NoError(t, err) @@ -1753,6 +1840,9 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { Node: consulNodeName, Address: consulNodeAddress, Service: svc, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, } _, err = consulClient.Catalog().Register(serviceRegistration, nil) require.NoError(t, err) @@ -2034,6 +2124,8 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { } }) consulClient := testClient.APIClient + // Coincidentally, this allows enough time for the bootstrap token to be generated + testClient.TestServer.WaitForActiveCARoot(t) _, err := namespaces.EnsureExists(consulClient, ts.ConsulNS, "") require.NoError(t, err) @@ -2045,6 +2137,9 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { Node: consulNodeName, Address: consulNodeAddress, Service: svc, + NodeMeta: map[string]string{ + metaKeySyntheticNode: "true", + }, } _, err = consulClient.Catalog().Register(serviceRegistration, nil) require.NoError(t, err) @@ -2107,8 +2202,15 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { require.Empty(t, append(defaultNS, testNS...)) if tt.enableACLs { - _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.Contains(t, err.Error(), "ACL not found") + queryOpts := &api.QueryOptions{} + if tt.initialConsulSvcs[0].Kind == api.ServiceKindMeshGateway { + queryOpts.Namespace = "default" // Mesh Gateways must always be registered in the "default" namespace. + } else { + queryOpts.Namespace = ts.ConsulNS + } + + token, _, err = consulClient.ACL().TokenRead(token.AccessorID, queryOpts) + require.Contains(t, err.Error(), "ACL not found", token) } }) } diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 6a888d61f6..cb0e6807c1 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -4167,6 +4167,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { } }) consulClient := testClient.APIClient + // TODO: stabilize this test by waiting for the ACL bootstrap // Register service and proxy in consul var token *api.ACLToken diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 78dbcc9fe2..3cc3aa9992 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -6,14 +6,12 @@ package endpointsv2 import ( "context" "fmt" + "testing" + "time" + mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" @@ -31,7 +29,12 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "testing" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const ( @@ -2193,6 +2196,11 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { c.Experiments = []string{"resource-apis"} }) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the Endpoints controller. ep := &Controller{ Client: fakeClient, diff --git a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go index 601be1a833..55ab7a22f9 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go @@ -11,9 +11,6 @@ import ( "time" logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" @@ -26,6 +23,10 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // TestReconcile_CreateUpdatePeeringAcceptor creates a peering acceptor. @@ -508,6 +509,7 @@ func TestReconcile_CreateUpdatePeeringAcceptor(t *testing.T) { // Create test consul server. testClient := test.TestServerWithMockConnMgrWatcher(t, nil) consulClient := testClient.APIClient + testClient.TestServer.WaitForActiveCARoot(t) if tt.initialConsulPeerName != "" { // Add the initial peerings into Consul by calling the Generate token endpoint. @@ -631,6 +633,7 @@ func TestReconcile_DeletePeeringAcceptor(t *testing.T) { // Create test consul server. testClient := test.TestServerWithMockConnMgrWatcher(t, nil) consulClient := testClient.APIClient + testClient.TestServer.WaitForActiveCARoot(t) // Add the initial peerings into Consul by calling the Generate token endpoint. _, _, err := consulClient.Peerings().GenerateToken(context.Background(), api.PeeringGenerateTokenRequest{PeerName: "acceptor-deleted"}, nil) @@ -777,6 +780,7 @@ func TestReconcile_VersionAnnotation(t *testing.T) { // Create test consul server. testClient := test.TestServerWithMockConnMgrWatcher(t, nil) consulClient := testClient.APIClient + testClient.TestServer.WaitForActiveCARoot(t) _, _, err := consulClient.Peerings().GenerateToken(context.Background(), api.PeeringGenerateTokenRequest{PeerName: "acceptor-created"}, nil) require.NoError(t, err) diff --git a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go index c142cd9eee..4997579e5b 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go @@ -10,10 +10,6 @@ import ( "time" logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" @@ -28,6 +24,11 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // TestReconcile_CreateUpdatePeeringDialer creates a peering dialer. @@ -263,6 +264,7 @@ func TestReconcile_CreateUpdatePeeringDialer(t *testing.T) { require.NoError(t, err) defer acceptorPeerServer.Stop() acceptorPeerServer.WaitForServiceIntentions(t) + acceptorPeerServer.WaitForActiveCARoot(t) cfg := &api.Config{ Address: acceptorPeerServer.HTTPAddr, @@ -298,6 +300,7 @@ func TestReconcile_CreateUpdatePeeringDialer(t *testing.T) { // Create test consul server. testClient := test.TestServerWithMockConnMgrWatcher(t, nil) dialerClient := testClient.APIClient + testClient.TestServer.WaitForActiveCARoot(t) // If the peering is supposed to already exist in Consul, then establish a peering with the existing token, so the peering will exist on the dialing side. if tt.peeringExists { @@ -443,6 +446,7 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { require.NoError(t, err) defer acceptorPeerServer.Stop() acceptorPeerServer.WaitForServiceIntentions(t) + acceptorPeerServer.WaitForActiveCARoot(t) cfg := &api.Config{ Address: acceptorPeerServer.HTTPAddr, @@ -498,6 +502,7 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { require.NoError(t, err) defer dialerPeerServer.Stop() dialerPeerServer.WaitForServiceIntentions(t) + dialerPeerServer.WaitForActiveCARoot(t) consulConfig := &consul.Config{ APIClientConfig: &api.Config{Address: dialerPeerServer.HTTPAddr}, @@ -754,6 +759,7 @@ func TestReconcileDeletePeeringDialer(t *testing.T) { // Create test consul server. testClient := test.TestServerWithMockConnMgrWatcher(t, nil) consulClient := testClient.APIClient + testClient.TestServer.WaitForActiveCARoot(t) // Add the initial peerings into Consul by calling the Generate token endpoint. _, _, err := consulClient.Peerings().GenerateToken(context.Background(), api.PeeringGenerateTokenRequest{PeerName: "dialer-deleted"}, nil) diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index 94bce9b29e..22af958fd9 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -8,6 +8,7 @@ package pod import ( "context" "testing" + "time" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" @@ -674,6 +675,11 @@ func runControllerTest(t *testing.T, tc testCase) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the partition in Consul. if tc.partition != "" { testClient.Cfg.APIClientConfig.Partition = tc.partition diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 456f8fbfaf..ed48f7bc78 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -121,6 +121,11 @@ func TestWorkloadWrite(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the pod controller. pc := &Controller{ Client: fakeClient, @@ -307,6 +312,11 @@ func TestWorkloadDelete(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the pod controller. pc := &Controller{ Client: fakeClient, @@ -391,6 +401,11 @@ func TestHealthStatusWrite(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the pod controller. pc := &Controller{ Client: fakeClient, @@ -507,6 +522,11 @@ func TestProxyConfigurationWrite(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the pod controller. pc := &Controller{ Client: fakeClient, @@ -602,6 +622,9 @@ func TestProxyConfigurationWrite(t *testing.T) { }, }, }, + TransparentProxy: &pbmesh.TransparentProxy{ + OutboundListenerPort: 15001, + }, }, BootstrapConfig: &pbmesh.BootstrapConfig{ PrometheusBindAddr: "0.0.0.0:5678", @@ -645,6 +668,9 @@ func TestProxyConfigurationWrite(t *testing.T) { }, }, }, + TransparentProxy: &pbmesh.TransparentProxy{ + OutboundListenerPort: 15001, + }, }, BootstrapConfig: &pbmesh.BootstrapConfig{ PrometheusBindAddr: "0.0.0.0:21234", @@ -663,6 +689,9 @@ func TestProxyConfigurationWrite(t *testing.T) { }, DynamicConfig: &pbmesh.DynamicConfig{ Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, + TransparentProxy: &pbmesh.TransparentProxy{ + OutboundListenerPort: 15001, + }, }, }, }, @@ -700,6 +729,11 @@ func TestProxyConfigurationDelete(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the pod controller. pc := &Controller{ Client: fakeClient, @@ -979,6 +1013,11 @@ func TestDestinationsWrite(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + pc := &Controller{ Log: logrtest.New(t), K8sNamespaceConfig: common.K8sNamespaceConfig{ @@ -1062,6 +1101,11 @@ func TestDestinationsDelete(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + pc := &Controller{ Log: logrtest.New(t), K8sNamespaceConfig: common.K8sNamespaceConfig{ @@ -1147,6 +1191,11 @@ func TestReconcileCreatePod(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the pod controller. pc := &Controller{ Client: fakeClient, @@ -1343,6 +1392,11 @@ func TestReconcileUpdatePod(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the pod controller. pc := &Controller{ Client: fakeClient, @@ -1499,6 +1553,9 @@ func TestReconcileUpdatePod(t *testing.T) { }, }, }, + TransparentProxy: &pbmesh.TransparentProxy{ + OutboundListenerPort: 15001, + }, }, BootstrapConfig: &pbmesh.BootstrapConfig{ PrometheusBindAddr: "0.0.0.0:21234", @@ -1604,6 +1661,11 @@ func TestReconcileDeletePod(t *testing.T) { resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the pod controller. pc := &Controller{ Client: fakeClient, @@ -1835,7 +1897,7 @@ func createCriticalHealthStatus(name string, namespace string) *pbcatalog.Health // createProxyConfiguration creates a proxyConfiguration that matches the pod from createPod, // assuming that metrics, telemetry, and overwrite probes are enabled separately. func createProxyConfiguration(podName string, mode pbmesh.ProxyMode) *pbmesh.ProxyConfiguration { - return &pbmesh.ProxyConfiguration{ + mesh := &pbmesh.ProxyConfiguration{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, @@ -1866,6 +1928,14 @@ func createProxyConfiguration(podName string, mode pbmesh.ProxyMode) *pbmesh.Pro TelemetryCollectorBindSocketDir: DefaultTelemetryBindSocketDir, }, } + + if mode == pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT { + mesh.DynamicConfig.TransparentProxy = &pbmesh.TransparentProxy{ + OutboundListenerPort: 15001, + } + } + + return mesh } // createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go index 7e2e3b3793..94253f995b 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go @@ -6,6 +6,7 @@ package serviceaccount import ( "context" "testing" + "time" "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/proto" @@ -243,6 +244,11 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { resourceClient, err := consul.NewResourceServiceClient(sa.ConsulServerConnMgr) require.NoError(t, err) + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Default ns and partition if not specified in test. if tc.targetConsulNs == "" { tc.targetConsulNs = constants.DefaultConsulNS diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index c9157c368e..4abd16737a 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -33,6 +33,7 @@ import ( // and there's a single consul destination namespace. func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { t.Parallel() + consulDestNamespaces := []string{"default", "destination"} for _, consulDestNamespace := range consulDestNamespaces { t.Run(consulDestNamespace, func(tt *testing.T) { @@ -321,6 +322,11 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { require.NoError(t, err) // Check that the expected policies were created. + // There will be more policies returned in the List API that are defaults + // existing in Consul on startup, including but not limited to: + // * global-management + // * builtin/global-read-only + // * agent-token firstRunExpectedPolicies := []string{ "anonymous-token-policy", "client-policy", @@ -337,12 +343,6 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { policies, _, err := consul.ACL().PolicyList(nil) require.NoError(t, err) - // Check that we have the right number of policies. The actual - // policies will have two more than expected because of the - // global management and namespace management polices that - // are automatically created, the latter in consul-ent v1.7+. - require.Equal(t, len(firstRunExpectedPolicies), len(policies)-2) - // Collect the actual policies into a map to make it easier to assert // on their existence and contents. actualPolicies := make(map[string]string) @@ -389,12 +389,6 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { policies, _, err = consul.ACL().PolicyList(nil) require.NoError(t, err) - // Check that we have the right number of policies. The actual - // policies will have two more than expected because of the - // global management and namespace management polices that - // are automatically created, the latter in consul-ent v1.7+. - require.Equal(t, len(secondRunExpectedPolicies), len(policies)-2) - // Collect the actual policies into a map to make it easier to assert // on their existence and contents. actualPolicies = make(map[string]string) @@ -418,6 +412,8 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { require.Contains(t, actRules, "acl = \"write\"") case "partitions-token": require.Contains(t, actRules, "operator = \"write\"") + case "anonymous-token-policy": + // TODO: This needs to be revisted due to recent changes in how we update the anonymous policy (NET-5174) default: // Assert that the policies have the word namespace in them. This // tests that they were updated. The actual contents are tested @@ -750,7 +746,7 @@ func TestRun_TokensWithNamespacesEnabled(t *testing.T) { } } -// Test the parsing the namespace from gateway names +// Test the parsing the namespace from gateway names. func TestRun_GatewayNamespaceParsing(t *testing.T) { t.Parallel() @@ -1050,7 +1046,7 @@ func TestRun_NamespaceEnabled_ValidateLoginToken_PrimaryDatacenter(t *testing.T) clientset: k8s, } cmdArgs := append([]string{ - "-timeout=500ms", + "-timeout=1m", "-resource-prefix=" + resourcePrefix, "-enable-namespaces", "-k8s-namespace=" + c.Namespace, From f4ccbaa9bc9152722fdb8ff032220f1bc95475e2 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 16 Oct 2023 10:57:39 -0400 Subject: [PATCH 437/592] L7 CRDs (#3019) * Create sub-folders for auth and mesh * Support CRDs for L7 mesh - TCPRoute - HTTPRoute - GRPCRoute - ProxyConfiguration Update TrafficPermissions to embed proto type directly. --- Makefile | 14 +- acceptance/tests/cloud/load/remote.go | 3 + .../trafficpermissions.yaml | 2 +- .../cases/trafficpermissions-deny/patch.yaml | 2 +- .../templates/connect-inject-clusterrole.yaml | 26 + .../crd-controlplanerequestlimits.yaml | 12 +- .../templates/crd-exportedservices.yaml | 12 +- .../templates/crd-gatewayclassconfigs.yaml | 12 +- ....yaml => crd-gatewayclasses-external.yaml} | 0 .../consul/templates/crd-gatewaypolicies.yaml | 12 +- ...teways.yaml => crd-gateways-external.yaml} | 0 .../templates/crd-grpcroutes-external.yaml | 769 ++++++ charts/consul/templates/crd-grpcroutes.yaml | 1274 ++++----- .../templates/crd-httproutes-external.yaml | 1917 +++++++++++++ charts/consul/templates/crd-httproutes.yaml | 2418 ++++------------- .../consul/templates/crd-ingressgateways.yaml | 12 +- charts/consul/templates/crd-jwtproviders.yaml | 24 +- charts/consul/templates/crd-meshes.yaml | 12 +- charts/consul/templates/crd-meshservices.yaml | 12 +- .../templates/crd-peeringacceptors.yaml | 12 +- .../consul/templates/crd-peeringdialers.yaml | 12 +- .../templates/crd-proxyconfigurations.yaml | 423 +++ .../consul/templates/crd-proxydefaults.yaml | 12 +- ...yaml => crd-referencegrants-external.yaml} | 0 .../templates/crd-routeauthfilters.yaml | 12 +- .../templates/crd-routeretryfilters.yaml | 12 +- .../templates/crd-routetimeoutfilters.yaml | 24 +- .../consul/templates/crd-samenessgroups.yaml | 12 +- .../consul/templates/crd-servicedefaults.yaml | 12 +- .../templates/crd-serviceintentions.yaml | 12 +- .../templates/crd-serviceresolvers.yaml | 12 +- .../consul/templates/crd-servicerouters.yaml | 12 +- .../templates/crd-servicesplitters.yaml | 12 +- .../templates/crd-tcproutes-external.yaml | 284 ++ charts/consul/templates/crd-tcproutes.yaml | 496 ++-- .../templates/crd-terminatinggateways.yaml | 12 +- ...outes.yaml => crd-tlsroutes-external.yaml} | 0 .../templates/crd-trafficpermissions.yaml | 45 +- ...outes.yaml => crd-udproutes-external.yaml} | 0 ....bats => crd-gatewayclasses-external.bats} | 6 +- ...teways.bats => crd-gateways-external.bats} | 6 +- ...utes.bats => crd-grpcroutes-external.bats} | 6 +- ...utes.bats => crd-httproutes-external.bats} | 6 +- ...outes.bats => crd-tcproutes-external.bats} | 10 +- ...outes.bats => crd-tlsroutes-external.bats} | 6 +- ...outes.bats => crd-udproutes-external.bats} | 6 +- control-plane/PROJECT | 2 +- .../api-gateway/common/translation.go | 4 +- .../api-gateway/common/translation_test.go | 4 +- .../v2beta1/auth_groupversion_info.go} | 3 +- .../api/{ => auth}/v2beta1/shared_types.go | 0 .../api/{ => auth}/v2beta1/status.go | 0 .../auth/v2beta1/traffic_permissions_types.go | 242 ++ .../v2beta1/traffic_permissions_types_test.go | 401 ++- .../v2beta1/trafficpermissions_webhook.go | 2 +- .../api/auth/v2beta1/zz_generated.deepcopy.go | 136 + control-plane/api/common/common.go | 8 + .../api/mesh/v2beta1/grpc_route_types.go | 327 +++ .../api/mesh/v2beta1/grpc_route_types_test.go | 1201 ++++++++ .../api/mesh/v2beta1/grpc_route_webhook.go | 65 + .../api/mesh/v2beta1/http_route_types.go | 309 +++ .../api/mesh/v2beta1/http_route_types_test.go | 1338 +++++++++ .../api/mesh/v2beta1/http_route_webhook.go | 65 + .../mesh/v2beta1/mesh_groupversion_info.go | 27 + .../proxy_configuration_route_webhook.go | 65 + .../mesh/v2beta1/proxy_configuration_types.go | 160 ++ .../v2beta1/proxy_configuration_types_test.go | 608 +++++ .../api/mesh/v2beta1/shared_types.go | 14 + control-plane/api/mesh/v2beta1/status.go | 93 + .../api/mesh/v2beta1/tcp_route_types.go | 195 ++ .../api/mesh/v2beta1/tcp_route_types_test.go | 577 ++++ .../api/mesh/v2beta1/tcp_route_webhook.go | 65 + .../api/mesh/v2beta1/zz_generated.deepcopy.go | 325 +++ .../api/v1alpha1/jwtprovider_types.go | 22 +- .../api/v1alpha1/jwtprovider_types_test.go | 51 +- .../api/v1alpha1/routetimeoutfilter_types.go | 5 +- .../api/v1alpha1/zz_generated.deepcopy.go | 6 + .../api/v2beta1/traffic_permissions_types.go | 427 --- .../api/v2beta1/zz_generated.deepcopy.go | 467 ---- .../controllersv2/grpc_route_controller.go | 43 + .../controllersv2/http_route_controller.go | 43 + .../meshconfig_controller_test.go | 80 +- .../proxy_configuration_controller.go | 43 + .../controllersv2/tcp_route_controller.go | 43 + .../traffic_permissions_controller.go | 2 +- ...nsul.hashicorp.com_trafficpermissions.yaml | 43 +- ...shicorp.com_controlplanerequestlimits.yaml | 10 +- ...consul.hashicorp.com_exportedservices.yaml | 10 +- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 10 +- .../consul.hashicorp.com_gatewaypolicies.yaml | 10 +- .../consul.hashicorp.com_ingressgateways.yaml | 10 +- .../consul.hashicorp.com_jwtproviders.yaml | 22 +- .../bases/consul.hashicorp.com_meshes.yaml | 10 +- .../consul.hashicorp.com_meshservices.yaml | 10 +- ...consul.hashicorp.com_peeringacceptors.yaml | 10 +- .../consul.hashicorp.com_peeringdialers.yaml | 10 +- .../consul.hashicorp.com_proxydefaults.yaml | 10 +- ...consul.hashicorp.com_routeauthfilters.yaml | 10 +- ...onsul.hashicorp.com_routeretryfilters.yaml | 10 +- ...sul.hashicorp.com_routetimeoutfilters.yaml | 22 +- .../consul.hashicorp.com_samenessgroups.yaml | 10 +- .../consul.hashicorp.com_servicedefaults.yaml | 10 +- ...onsul.hashicorp.com_serviceintentions.yaml | 10 +- ...consul.hashicorp.com_serviceresolvers.yaml | 10 +- .../consul.hashicorp.com_servicerouters.yaml | 10 +- ...consul.hashicorp.com_servicesplitters.yaml | 10 +- ...sul.hashicorp.com_terminatinggateways.yaml | 10 +- .../mesh.consul.hashicorp.com_grpcroutes.yaml | 612 +++++ .../mesh.consul.hashicorp.com_httproutes.yaml | 668 +++++ ...sul.hashicorp.com_proxyconfigurations.yaml | 418 +++ .../mesh.consul.hashicorp.com_tcproutes.yaml | 273 ++ ...ewayclasses.gateway.networking.k8s.io.yaml | 3 + .../gateways.gateway.networking.k8s.io.yaml | 3 + .../grpcroutes.gateway.networking.k8s.io.yaml | 3 + .../httproutes.gateway.networking.k8s.io.yaml | 3 + ...rencegrants.gateway.networking.k8s.io.yaml | 3 + .../tcproutes.gateway.networking.k8s.io.yaml | 3 + .../tlsroutes.gateway.networking.k8s.io.yaml | 3 + .../udproutes.gateway.networking.k8s.io.yaml | 3 + control-plane/config/rbac/role.yaml | 81 +- control-plane/config/webhook/manifests.yaml | 86 +- .../endpointsv2/write_cache_test.go | 3 + .../pod/pod_controller_ent_test.go | 4 +- .../controllers/pod/pod_controller_test.go | 38 +- control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- .../subcommand/inject-connect/command.go | 6 +- .../inject-connect/v2controllers.go | 65 +- hack/camel-crds/go.mod | 15 + hack/camel-crds/go.sum | 25 + hack/camel-crds/main.go | 117 + hack/copy-crds-to-chart/main.go | 10 +- 132 files changed, 13781 insertions(+), 4410 deletions(-) rename charts/consul/templates/{crd-gatewayclasses.yaml => crd-gatewayclasses-external.yaml} (100%) rename charts/consul/templates/{crd-gateways.yaml => crd-gateways-external.yaml} (100%) create mode 100644 charts/consul/templates/crd-grpcroutes-external.yaml create mode 100644 charts/consul/templates/crd-httproutes-external.yaml create mode 100644 charts/consul/templates/crd-proxyconfigurations.yaml rename charts/consul/templates/{crd-referencegrants.yaml => crd-referencegrants-external.yaml} (100%) create mode 100644 charts/consul/templates/crd-tcproutes-external.yaml rename charts/consul/templates/{crd-tlsroutes.yaml => crd-tlsroutes-external.yaml} (100%) rename charts/consul/templates/{crd-udproutes.yaml => crd-udproutes-external.yaml} (100%) rename charts/consul/test/unit/{crd-gatewayclasses.bats => crd-gatewayclasses-external.bats} (80%) rename charts/consul/test/unit/{crd-gateways.bats => crd-gateways-external.bats} (82%) rename charts/consul/test/unit/{crd-grpcroutes.bats => crd-grpcroutes-external.bats} (81%) rename charts/consul/test/unit/{crd-httproutes.bats => crd-httproutes-external.bats} (81%) rename charts/consul/test/unit/{crd-tcproutes.bats => crd-tcproutes-external.bats} (84%) rename charts/consul/test/unit/{crd-tlsroutes.bats => crd-tlsroutes-external.bats} (82%) rename charts/consul/test/unit/{crd-udproutes.bats => crd-udproutes-external.bats} (82%) rename control-plane/api/{v2beta1/groupversion_info.go => auth/v2beta1/auth_groupversion_info.go} (94%) rename control-plane/api/{ => auth}/v2beta1/shared_types.go (100%) rename control-plane/api/{ => auth}/v2beta1/status.go (100%) create mode 100644 control-plane/api/auth/v2beta1/traffic_permissions_types.go rename control-plane/api/{ => auth}/v2beta1/traffic_permissions_types_test.go (55%) rename control-plane/api/{ => auth}/v2beta1/trafficpermissions_webhook.go (97%) create mode 100644 control-plane/api/auth/v2beta1/zz_generated.deepcopy.go create mode 100644 control-plane/api/mesh/v2beta1/grpc_route_types.go create mode 100644 control-plane/api/mesh/v2beta1/grpc_route_types_test.go create mode 100644 control-plane/api/mesh/v2beta1/grpc_route_webhook.go create mode 100644 control-plane/api/mesh/v2beta1/http_route_types.go create mode 100644 control-plane/api/mesh/v2beta1/http_route_types_test.go create mode 100644 control-plane/api/mesh/v2beta1/http_route_webhook.go create mode 100644 control-plane/api/mesh/v2beta1/mesh_groupversion_info.go create mode 100644 control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go create mode 100644 control-plane/api/mesh/v2beta1/proxy_configuration_types.go create mode 100644 control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go create mode 100644 control-plane/api/mesh/v2beta1/shared_types.go create mode 100644 control-plane/api/mesh/v2beta1/status.go create mode 100644 control-plane/api/mesh/v2beta1/tcp_route_types.go create mode 100644 control-plane/api/mesh/v2beta1/tcp_route_types_test.go create mode 100644 control-plane/api/mesh/v2beta1/tcp_route_webhook.go create mode 100644 control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go delete mode 100644 control-plane/api/v2beta1/traffic_permissions_types.go delete mode 100644 control-plane/api/v2beta1/zz_generated.deepcopy.go create mode 100644 control-plane/config-entries/controllersv2/grpc_route_controller.go create mode 100644 control-plane/config-entries/controllersv2/http_route_controller.go create mode 100644 control-plane/config-entries/controllersv2/proxy_configuration_controller.go create mode 100644 control-plane/config-entries/controllersv2/tcp_route_controller.go create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml create mode 100644 hack/camel-crds/go.mod create mode 100644 hack/camel-crds/go.sum create mode 100644 hack/camel-crds/main.go diff --git a/Makefile b/Makefile index 5767218e25..a1c4e01834 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,9 @@ gen-helm-docs: ## Generate Helm reference docs from values.yaml and update Consu copy-crds-to-chart: ## Copy generated CRD YAML into charts/consul. Usage: make copy-crds-to-chart @cd hack/copy-crds-to-chart; go run ./... +camel-crds: ## Convert snake_case keys in yaml to camelCase. Usage: make camel-crds + @cd hack/camel-crds; go run ./... + generate-external-crds: ## Generate CRDs for externally defined CRDs and copy them to charts/consul. Usage: make generate-external-crds @cd ./control-plane/config/crd/external; \ kustomize build | yq --split-exp '.metadata.name + ".yaml"' --no-doc @@ -174,6 +177,7 @@ lint: cni-plugin-lint ## Run linter in the control-plane, cli, and acceptance di ctrl-manifests: get-controller-gen ## Generate CRD manifests. make ensure-controller-gen-version cd control-plane; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + make camel-crds make copy-crds-to-chart make generate-external-crds make add-copyright-header @@ -185,7 +189,7 @@ ifeq (, $(shell which controller-gen)) CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ cd $$CONTROLLER_GEN_TMP_DIR ;\ go mod init tmp ;\ - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 ;\ + go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.12.1 ;\ rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ } CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen @@ -193,9 +197,9 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif -ensure-controller-gen-version: ## Ensure controller-gen version is v0.8.0. -ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.8.0)) - @echo "controller-gen version is not v0.8.0, uninstall the binary and install the correct version with 'make get-controller-gen'." +ensure-controller-gen-version: ## Ensure controller-gen version is v0.12.1. +ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.12.1)) + @echo "controller-gen version is not v0.12.1, uninstall the binary and install the correct version with 'make get-controller-gen'." @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" @exit 1 else @@ -314,4 +318,4 @@ DOCKER_HUB_USER=$(shell cat $(HOME)/.dockerhub) GIT_COMMIT?=$(shell git rev-parse --short HEAD) GIT_DIRTY?=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true) GIT_DESCRIBE?=$(shell git describe --tags --always) -CRD_OPTIONS ?= "crd:allowDangerousTypes=true" +CRD_OPTIONS ?= "crd:ignoreUnexportedFields=true,allowDangerousTypes=true" diff --git a/acceptance/tests/cloud/load/remote.go b/acceptance/tests/cloud/load/remote.go index 906c050f70..5ce2ffa286 100644 --- a/acceptance/tests/cloud/load/remote.go +++ b/acceptance/tests/cloud/load/remote.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package load import ( diff --git a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml b/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml index f43bd2d62f..ed5c0436ed 100644 --- a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml +++ b/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml @@ -8,7 +8,7 @@ metadata: spec: destination: identityName: multiport - action: allow + action: ACTION_ALLOW permissions: - sources: - identityName: static-client diff --git a/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml b/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml index 037859194f..e1220bcba5 100644 --- a/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml +++ b/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml @@ -6,4 +6,4 @@ kind: TrafficPermissions metadata: name: client-to-server spec: - action: deny + action: ACTION_DENY diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index f99d82409f..2506637949 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -93,6 +93,32 @@ rules: - get - patch - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - grpcroutes + - httproutes + - tcproutes + - proxyconfigurations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - grpcroutes/status + - httproutes/status + - tcproutes/status + - proxyconfigurations/status + verbs: + - get + - patch + - update {{- end }} - apiGroups: [ "" ] resources: [ "secrets", "serviceaccounts", "endpoints", "services", "namespaces", "nodes" ] diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml index dceaea1ece..1939a8d373 100644 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: controlplanerequestlimits.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -194,10 +192,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 591500cb12..081a2b0cf0 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: exportedservices.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -138,10 +136,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 67eb30944f..130db72a22 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: gatewayclassconfigs.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -200,10 +198,4 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-gatewayclasses.yaml b/charts/consul/templates/crd-gatewayclasses-external.yaml similarity index 100% rename from charts/consul/templates/crd-gatewayclasses.yaml rename to charts/consul/templates/crd-gatewayclasses-external.yaml diff --git a/charts/consul/templates/crd-gatewaypolicies.yaml b/charts/consul/templates/crd-gatewaypolicies.yaml index 54779f4356..1cdfa331f5 100644 --- a/charts/consul/templates/crd-gatewaypolicies.yaml +++ b/charts/consul/templates/crd-gatewaypolicies.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: gatewaypolicies.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: gatewaypolicies.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -281,10 +279,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-gateways.yaml b/charts/consul/templates/crd-gateways-external.yaml similarity index 100% rename from charts/consul/templates/crd-gateways.yaml rename to charts/consul/templates/crd-gateways-external.yaml diff --git a/charts/consul/templates/crd-grpcroutes-external.yaml b/charts/consul/templates/crd-grpcroutes-external.yaml new file mode 100644 index 0000000000..3e4aa75853 --- /dev/null +++ b/charts/consul/templates/crd-grpcroutes-external.yaml @@ -0,0 +1,769 @@ +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge. \n Support: Extended" + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - method: + type: Exact + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: GRPCRouteRule defines the semantics for matching an gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + properties: + filters: + description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. Support: Core" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - method: + type: Exact + description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." + items: + description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. + items: + description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. + properties: + name: + description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + default: + type: Exact + description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. + properties: + method: + description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Method must be a valid Protobuf Method (https://protobuf.com/docs/language-spec#methods)." + maxLength: 1024 + pattern: ^[A-Za-z_][A-Za-z_0-9]*$ + type: string + service: + description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Service must be a valid Protobuf Type Name (https://protobuf.com/docs/language-spec#type-references)." + maxLength: 1024 + pattern: ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$ + type: string + type: + default: Exact + description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - RegularExpression + type: string + type: object + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/crd-grpcroutes.yaml b/charts/consul/templates/crd-grpcroutes.yaml index 3e4aa75853..31812fff35 100644 --- a/charts/consul/templates/crd-grpcroutes.yaml +++ b/charts/consul/templates/crd-grpcroutes.yaml @@ -1,769 +1,617 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if .Values.connectInject.enabled }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: grpcroutes.gateway.networking.k8s.io + name: grpcroutes.mesh.consul.hashicorp.com spec: - group: gateway.networking.k8s.io + group: mesh.consul.hashicorp.com names: - categories: - - gateway-api kind: GRPCRoute listKind: GRPCRouteList plural: grpcroutes + shortNames: + - grpc-route singular: grpcroute scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge. \n Support: Extended" - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GRPCRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - method: - type: Exact - description: Rules are a list of GRPC matchers, filters and actions. - items: - description: GRPCRouteRule defines the semantics for matching an gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: GRPCRoute is the Schema for the GRPC Route API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: "NOTE: this should align to the GAMMA/gateway-api version, + or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute + \n This is a Resource type." + properties: + hostnames: + description: "Hostnames are the hostnames for which this GRPCRoute + should respond to requests. \n This is only valid for north/south." + items: + type: string + type: array + parentRefs: + description: "ParentRefs references the resources (usually Services) + that a Route wants to be attached to. \n It is invalid to reference + an identical parent more than once. It is valid to reference multiple + distinct sections within the same parent resource." + items: + description: 'NOTE: roughly equivalent to structs.ResourceReference' + properties: + port: + description: For east/west this is the name of the Consul Service + port to direct traffic to or empty to imply all. For north/south + this is TBD. + type: string + ref: + description: For east/west configuration, this should point + to a Service. For north/south it should point to a Gateway. + properties: + name: + description: Name is the user-given name of the resource + (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of the resource + the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units (i.e. + partition, namespace) in which the resource resides. properties: - filters: - description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" - items: - description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + namespace: + description: "Namespace further isolates resources within + a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all partitions." + type: string + peerName: + description: "PeerName identifies which peer the resource + is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. "catalog", + "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when sweeping + or backward-incompatible changes are made to the group's + resource types. + type: string + kind: + description: Kind identifies the specific resource type + within the group. + type: string + type: object + type: object + type: object + type: array + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. Failure behavior here depends on + how many BackendRefs are specified and how many are invalid. + \n If all entries in BackendRefs are invalid, and there are + also no filters specified in this route rule, all traffic + which matches this rule MUST receive a 500 status code. \n + See the GRPCBackendRef definition for the rules about what + makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef + is invalid, 500 status codes MUST be returned for requests + that would have otherwise been routed to an invalid backend. + If multiple backends are specified, and some are invalid, + the proportion of requests that would otherwise have been + routed to an invalid backend MUST receive a 500 status code. + \n For example, if two backends are specified with equal weights, + and one is invalid, 50 percent of traffic must receive a 500. + Implementations may choose how that 50 percent is determined." + items: + properties: + backendRef: + properties: + datacenter: + type: string + port: + description: "For east/west this is the name of the + Consul Service port to direct traffic to or empty + to imply using the same value as the parent ref. + \n For north/south this is TBD." + type: string + ref: + description: For east/west configuration, this should + point to a Service. properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + name: + description: Name is the user-given name of the + resource (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of + the resource the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units + (i.e. partition, namespace) in which the resource + resides. properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + namespace: + description: "Namespace further isolates resources + within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all namespaces." type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all partitions." type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 + peerName: + description: "PeerName identifies which peer + the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all peers." type: string - required: - - group - - kind - - name type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + type: + description: Type identifies the resource's type. properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. + "catalog", "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when + sweeping or backward-incompatible changes + are made to the group's resource types. + type: string + kind: + description: Kind identifies the specific + resource type within the group. + type: string type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + type: object + type: object + filters: + description: Filters defined at this level should be executed + if and only if the request is being forwarded to the + backend defined here. + items: + properties: + requestHeaderModifier: + description: RequestHeaderModifier defines a schema + for a filter that modifies request headers. + properties: + add: + description: Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. + items: properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + name: type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + value: type: string + type: object + type: array + remove: + description: Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with + the given header (name, value) before the + action. + items: + properties: name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + value: type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name type: object - required: - - backendRef - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " - enum: - - ResponseHeaderModifier - - RequestHeaderModifier - - RequestMirror - - ExtensionRef - type: string - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. Support: Core" - items: - description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 + type: array + type: object + responseHeaderModifier: + description: ResponseHeaderModifier defines a schema + for a filter that modifies response headers. + properties: + add: + description: Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map + type: array + set: + description: Set overwrites the request with + the given header (name, value) before the + action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + urlRewrite: + description: URLRewrite defines a schema for a filter + that modifies a request during forwarding. + properties: + pathPrefix: + type: string + type: object type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + type: array + weight: + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1." + format: int32 + type: integer + type: object + type: array + filters: + items: + properties: + requestHeaderModifier: + description: RequestHeaderModifier defines a schema for + a filter that modifies request headers. + properties: + add: + description: Add adds the given header(s) (name, value) + to the request before the action. It appends to + any existing values associated with the header name. + items: properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + name: type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + value: type: string + type: object + type: array + remove: + description: Remove the given header(s) from the HTTP + request before the action. The value of Remove is + a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with the given + header (name, value) before the action. + items: + properties: name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + value: type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name type: object - required: - - backendRef - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " - enum: - - ResponseHeaderModifier - - RequestHeaderModifier - - RequestMirror - - ExtensionRef - type: string - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - method: - type: Exact - description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." - items: - description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" - properties: - headers: - description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. - items: - description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. - properties: - name: - description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: Type specifies how to match against the value of the header. - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of the gRPC Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - default: - type: Exact - description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. - properties: - method: - description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Method must be a valid Protobuf Method (https://protobuf.com/docs/language-spec#methods)." - maxLength: 1024 - pattern: ^[A-Za-z_][A-Za-z_0-9]*$ + type: array + type: object + responseHeaderModifier: + description: ResponseHeaderModifier defines a schema for + a filter that modifies response headers. + properties: + add: + description: Add adds the given header(s) (name, value) + to the request before the action. It appends to + any existing values associated with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from the HTTP + request before the action. The value of Remove is + a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: type: string - service: - description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Service must be a valid Protobuf Type Name (https://protobuf.com/docs/language-spec#type-references)." - maxLength: 1024 - pattern: ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$ + type: array + set: + description: Set overwrites the request with the given + header (name, value) before the action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + urlRewrite: + description: URLRewrite defines a schema for a filter + that modifies a request during forwarding. + properties: + pathPrefix: + type: string + type: object + type: object + type: array + matches: + items: + properties: + headers: + description: Headers specifies gRPC request header matchers. + Multiple match values are ANDed together, meaning, a + request MUST match all the specified headers to select + the route. + items: + properties: + name: type: string type: - default: Exact - description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" + description: "HeaderMatchType specifies the semantics + of how HTTP header values should be compared. + Valid HeaderMatchType values, along with their + conformance levels, are: \n Note that values may + be added to this enum, implementations must ensure + that unknown values will not cause a crash. \n + Unknown values here must result in the implementation + setting the Accepted Condition for the Route to + status: False, with a Reason of UnsupportedValue." enum: - - Exact - - RegularExpression + - HEADER_MATCH_TYPE_UNSPECIFIED + - HEADER_MATCH_TYPE_EXACT + - HEADER_MATCH_TYPE_REGEX + - HEADER_MATCH_TYPE_PRESENT + - HEADER_MATCH_TYPE_PREFIX + - HEADER_MATCH_TYPE_SUFFIX + format: int32 + type: string + value: type: string type: object - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of GRPCRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + type: array + method: + description: Method specifies a gRPC request service/method + matcher. If this field is not specified, all services + and methods will match. + properties: + method: + description: "Value of the method to match against. + If left empty or omitted, will match all services. + \n At least one of Service and Method MUST be a + non-empty string.}" + type: string + service: + description: "Value of the service to match against. + If left empty or omitted, will match any service. + \n At least one of Service and Method MUST be a + non-empty string." + type: string + type: + description: 'Type specifies how to match against + the service and/or method. Support: Core (Exact + with service and method specified)' + enum: + - GRPC_METHOD_MATCH_TYPE_UNSPECIFIED + - GRPC_METHOD_MATCH_TYPE_EXACT + - GRPC_METHOD_MATCH_TYPE_REGEX + format: int32 + type: string + type: object + type: object + type: array + retries: + properties: + number: + description: Number is the number of times to retry the + request when a retryable result occurs. properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 + value: + description: The uint32 value. + format: int32 type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + onConditions: + description: RetryOn allows setting envoy specific conditions + when a request should be automatically retried. + items: type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + type: array + onConnectFailure: + description: RetryOnConnectFailure allows for connection + failure errors to trigger a retry. + type: boolean + onStatusCodes: + description: RetryOnStatusCodes is a flat list of http response + status codes that are eligible for retry. This again should + be feasible in any reasonable proxy. + items: format: int32 - maximum: 65535 - minimum: 1 type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + type: array + type: object + timeouts: + description: HTTPRouteTimeouts defines timeouts that can be + configured for an HTTPRoute or GRPCRoute. + properties: + idle: + description: Idle specifies the total amount of time permitted + for the request stream to be idle. + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + request: + description: RequestTimeout is the total amount of time + permitted for the entire downstream request (and retries) + to be processed. + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + type: object + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} {{- end }} diff --git a/charts/consul/templates/crd-httproutes-external.yaml b/charts/consul/templates/crd-httproutes-external.yaml new file mode 100644 index 0000000000..c89591376a --- /dev/null +++ b/charts/consul/templates/crd-httproutes-external.yaml @@ -0,0 +1,1917 @@ +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of HTTPRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/crd-httproutes.yaml b/charts/consul/templates/crd-httproutes.yaml index c89591376a..3da6e1e637 100644 --- a/charts/consul/templates/crd-httproutes.yaml +++ b/charts/consul/templates/crd-httproutes.yaml @@ -1,1917 +1,673 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if .Values.connectInject.enabled }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: httproutes.gateway.networking.k8s.io + name: httproutes.mesh.consul.hashicorp.com spec: - group: gateway.networking.k8s.io + group: mesh.consul.hashicorp.com names: - categories: - - gateway-api kind: HTTPRoute listKind: HTTPRouteList plural: httproutes + shortNames: + - http-route singular: httproute scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: The v1alpha2 version of HTTPRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. - name: v1alpha2 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: HTTPRoute is the Schema for the HTTP Route API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: "NOTE: this should align to the GAMMA/gateway-api version, + or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute + \n This is a Resource type." + properties: + hostnames: + description: "Hostnames are the hostnames for which this HTTPRoute + should respond to requests. \n This is only valid for north/south." + items: + type: string + type: array + parentRefs: + description: "ParentRefs references the resources (usually Services) + that a Route wants to be attached to. \n It is invalid to reference + an identical parent more than once. It is valid to reference multiple + distinct sections within the same parent resource." + items: + description: 'NOTE: roughly equivalent to structs.ResourceReference' + properties: + port: + description: For east/west this is the name of the Consul Service + port to direct traffic to or empty to imply all. For north/south + this is TBD. + type: string + ref: + description: For east/west configuration, this should point + to a Service. For north/south it should point to a Gateway. + properties: + name: + description: Name is the user-given name of the resource + (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of the resource + the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units (i.e. + partition, namespace) in which the resource resides. properties: - filters: - description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + description: "Namespace further isolates resources within + a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all namespaces." type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all partitions." type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." - items: - description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. - items: - description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH + peerName: + description: "PeerName identifies which peer the resource + is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all peers." type: string - path: - default: - type: PathPrefix - value: / - description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. - properties: - type: - default: PathPrefix - description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - PathPrefix - - RegularExpression - type: string - value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 - type: string - type: object - queryParams: - description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" - items: - description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. - properties: - name: - description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." - maxLength: 256 - minLength: 1 - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP query param to be matched. - maxLength: 1024 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + type: + description: Type identifies the resource's type. properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. "catalog", + "authorization"). type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown + groupVersion: + description: GroupVersion is incremented when sweeping + or backward-incompatible changes are made to the group's + resource types. type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + kind: + description: Kind identifies the specific resource type + within the group. type: string - required: - - lastTransitionTime - - message - - reason - - status - - type type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + type: object + type: object + type: array + rules: + description: Rules are a list of HTTP-based routing rules that this + route should use for constructing a routing table. + items: + description: HTTPRouteRule specifies the routing rules used to determine + what upstream service an HTTP request is routed to. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. \n Failure behavior here depends + on how many BackendRefs are specified and how many are invalid. + \n If all entries in BackendRefs are invalid, and there are + also no filters specified in this route rule, all traffic + which matches this rule MUST receive a 500 status code. \n + See the HTTPBackendRef definition for the rules about what + makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef + is invalid, 500 status codes MUST be returned for requests + that would have otherwise been routed to an invalid backend. + If multiple backends are specified, and some are invalid, + the proportion of requests that would otherwise have been + routed to an invalid backend MUST receive a 500 status code. + \n For example, if two backends are specified with equal weights, + and one is invalid, 50 percent of traffic must receive a 500. + Implementations may choose how that 50 percent is determined." + items: properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. - properties: - filters: - description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + backendRef: + properties: + datacenter: + type: string + port: + description: "For east/west this is the name of the + Consul Service port to direct traffic to or empty + to imply using the same value as the parent ref. + \n For north/south this is TBD." + type: string + ref: + description: For east/west configuration, this should + point to a Service. properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + name: + description: Name is the user-given name of the + resource (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of + the resource the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units + (i.e. partition, namespace) in which the resource + resides. properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + namespace: + description: "Namespace further isolates resources + within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all namespaces." type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all partitions." type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 + peerName: + description: "PeerName identifies which peer + the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all peers." type: string - required: - - group - - kind - - name type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + type: + description: Type identifies the resource's type. properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. + "catalog", "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when + sweeping or backward-incompatible changes + are made to the group's resource types. + type: string + kind: + description: Kind identifies the specific + resource type within the group. + type: string type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + type: object + type: object + filters: + description: Filters defined at this level should be executed + if and only if the request is being forwarded to the + backend defined here. + items: + properties: + requestHeaderModifier: + description: RequestHeaderModifier defines a schema + for a filter that modifies request headers. + properties: + add: + description: Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. + items: properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + value: type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: array + remove: + description: Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + type: array + set: + description: Set overwrites the request with + the given header (name, value) before the + action. + items: properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 + name: + type: string + value: type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 + type: object + type: array + type: object + responseHeaderModifier: + description: ResponseHeaderModifier defines a schema + for a filter that modifies response headers. + properties: + add: + description: Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. + items: + properties: + name: type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch + value: type: string - required: - - type type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https + type: array + remove: + description: Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " + type: array + set: + description: Set overwrites the request with + the given header (name, value) before the + action. + items: properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 + name: type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch + value: type: string - required: - - type type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + type: array + type: object + urlRewrite: + description: URLRewrite defines a schema for a filter + that modifies a request during forwarding. properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + pathPrefix: type: string + type: object + type: object + type: array + weight: + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1." + format: int32 + type: integer + type: object + type: array + filters: + items: + properties: + requestHeaderModifier: + description: RequestHeaderModifier defines a schema for + a filter that modifies request headers. + properties: + add: + description: Add adds the given header(s) (name, value) + to the request before the action. It appends to + any existing values associated with the header name. + items: + properties: name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + value: type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: array + remove: + description: Remove the given header(s) from the HTTP + request before the action. The value of Remove is + a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + type: array + set: + description: Set overwrites the request with the given + header (name, value) before the action. + items: properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 + name: type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 + value: type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch + type: object + type: array + type: object + responseHeaderModifier: + description: ResponseHeaderModifier defines a schema for + a filter that modifies response headers. + properties: + add: + description: Add adds the given header(s) (name, value) + to the request before the action. It appends to + any existing values associated with the header name. + items: + properties: + name: + type: string + value: type: string - required: - - type type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https + type: array + remove: + description: Remove the given header(s) from the HTTP + request before the action. The value of Remove is + a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " + type: array + set: + description: Set overwrites the request with the given + header (name, value) before the action. + items: properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 + name: type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch + value: type: string - required: - - type type: object + type: array + type: object + urlRewrite: + description: URLRewrite defines a schema for a filter + that modifies a request during forwarding. + properties: + pathPrefix: + type: string + type: object + type: object + type: array + matches: + items: + properties: + headers: + description: Headers specifies HTTP request header matchers. + Multiple match values are ANDed together, meaning, a + request must match all the specified headers to select + the route. + items: + properties: + invert: + description: 'NOTE: not in gamma; service-router + compat' + type: boolean + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case insensitive. + (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent header + names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be + ignored. Due to the case-insensitivity of header + names, “foo” and “Foo” are considered equivalent. + \n When a header is repeated in an HTTP request, + it is implementation-specific behavior as to how + this is represented. Generally, proxies should + follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 + regarding processing a repeated header, with special + handling for “Set-Cookie”." + type: string + type: + description: Type specifies how to match against + the value of the header. + enum: + - HEADER_MATCH_TYPE_UNSPECIFIED + - HEADER_MATCH_TYPE_EXACT + - HEADER_MATCH_TYPE_REGEX + - HEADER_MATCH_TYPE_PRESENT + - HEADER_MATCH_TYPE_PREFIX + - HEADER_MATCH_TYPE_SUFFIX + format: int32 + type: string + value: + description: Value is the value of HTTP Header to + be matched. + type: string type: object - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." - items: - description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. - items: - description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH - type: string - path: - default: - type: PathPrefix - value: / - description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + type: array + method: + description: Method specifies HTTP method matcher. When + specified, this route will be matched only if the request + has the specified method. + type: string + path: + description: Path specifies a HTTP request path matcher. + If this field is not specified, a default prefix match + on the “/” path is provided. + properties: + type: + description: Type specifies how to match against the + path Value. + enum: + - PATH_MATCH_TYPE_UNSPECIFIED + - PATH_MATCH_TYPE_EXACT + - PATH_MATCH_TYPE_PREFIX + - PATH_MATCH_TYPE_REGEX + format: int32 + type: string + value: + description: Value of the HTTP path to match against. + type: string + type: object + queryParams: + description: QueryParams specifies HTTP query parameter + matchers. Multiple match values are ANDed together, + meaning, a request must match all the specified query + parameters to select the route. + items: properties: + name: + description: "Name is the name of the HTTP query + param to be matched. This must be an exact string + match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). + \n If multiple entries specify equivalent query + param names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST + be ignored. \n If a query param is repeated in + an HTTP request, the behavior is purposely left + undefined, since different data planes have different + capabilities. However, it is recommended that + implementations should match against the first + value of the param if the data plane supports + it, as this behavior is expected in other load + balancing contexts outside of the Gateway API. + \n Users SHOULD NOT route traffic based on repeated + query params to guard themselves against potential + differences in the implementations." + type: string type: - default: PathPrefix - description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + description: Type specifies how to match against + the value of the query parameter. enum: - - Exact - - PathPrefix - - RegularExpression + - QUERY_PARAM_MATCH_TYPE_UNSPECIFIED + - QUERY_PARAM_MATCH_TYPE_EXACT + - QUERY_PARAM_MATCH_TYPE_REGEX + - QUERY_PARAM_MATCH_TYPE_PRESENT + format: int32 type: string value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 + description: Value is the value of HTTP query param + to be matched. type: string type: object - queryParams: - description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" - items: - description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. - properties: - name: - description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." - maxLength: 256 - minLength: 1 - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP query param to be matched. - maxLength: 1024 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + type: array + type: object + type: array + retries: + properties: + number: + description: Number is the number of times to retry the + request when a retryable result occurs. properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 + value: + description: The uint32 value. + format: int32 type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + onConditions: + description: RetryOn allows setting envoy specific conditions + when a request should be automatically retried. + items: type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + type: array + onConnectFailure: + description: RetryOnConnectFailure allows for connection + failure errors to trigger a retry. + type: boolean + onStatusCodes: + description: RetryOnStatusCodes is a flat list of http response + status codes that are eligible for retry. This again should + be feasible in any reasonable proxy. + items: format: int32 - maximum: 65535 - minimum: 1 type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + type: array + type: object + timeouts: + description: HTTPRouteTimeouts defines timeouts that can be + configured for an HTTPRoute or GRPCRoute. + properties: + idle: + description: Idle specifies the total amount of time permitted + for the request stream to be idle. + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + request: + description: RequestTimeout is the total amount of time + permitted for the entire downstream request (and retries) + to be processed. + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + type: object + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} {{- end }} diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index 9fa5ef7edd..dcbc543525 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: ingressgateways.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -446,10 +444,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index e5726cefe3..94c9697b33 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: jwtproviders.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -111,8 +109,7 @@ spec: cacheDuration: description: "CacheDuration is the duration after which cached keys should be expired. \n Default value is 5 minutes." - format: int64 - type: integer + type: string fetchAsynchronously: description: "FetchAsynchronously indicates that the JWKS should be fetched when a client request arrives. Client @@ -128,8 +125,7 @@ spec: description: The timeout for new network connections to hosts in the cluster. If not set, a default value of 5s will be used. - format: int64 - type: integer + type: string discoveryType: description: "DiscoveryType refers to the service discovery type to use for resolving the cluster. \n This defaults @@ -202,15 +198,13 @@ spec: description: "BaseInterval to be used for the next back off computation. \n The default value from envoy is 1s." - format: int64 - type: integer + type: string maxInterval: description: "MaxInternal to be used to specify the maximum interval between retries. Optional but should be greater or equal to BaseInterval. \n Defaults to 10 times BaseInterval." - format: int64 - type: integer + type: string type: object type: object uri: @@ -316,10 +310,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index 0710d41280..f8ce4fc12e 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: meshes.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -206,10 +204,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml index df8f673bdc..a5d36fb966 100644 --- a/charts/consul/templates/crd-meshservices.yaml +++ b/charts/consul/templates/crd-meshservices.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: meshservices.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -55,10 +53,4 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index e06e830f04..2352ba7ad3 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -1,18 +1,16 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: peeringacceptors.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -145,10 +143,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index e24401e761..09991d2091 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -1,18 +1,16 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: peeringdialers.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -145,10 +143,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-proxyconfigurations.yaml b/charts/consul/templates/crd-proxyconfigurations.yaml new file mode 100644 index 0000000000..9a33bd2bab --- /dev/null +++ b/charts/consul/templates/crd-proxyconfigurations.yaml @@ -0,0 +1,423 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + name: proxyconfigurations.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: ProxyConfiguration + listKind: ProxyConfigurationList + plural: proxyconfigurations + shortNames: + - proxy-configuration + singular: proxyconfiguration + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: ProxyConfiguration is the Schema for the TCP Routes API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: This is a Resource type. + properties: + bootstrapConfig: + description: bootstrap_config is the configuration that requires proxies + to be restarted to be applied. + properties: + dogstatsdUrl: + type: string + overrideJsonTpl: + type: string + prometheusBindAddr: + type: string + readyBindAddr: + type: string + staticClustersJson: + type: string + staticListenersJson: + type: string + statsBindAddr: + type: string + statsConfigJson: + type: string + statsFlushInterval: + type: string + statsSinksJson: + type: string + statsTags: + items: + type: string + type: array + statsdUrl: + type: string + telemetryCollectorBindSocketDir: + type: string + tracingConfigJson: + type: string + type: object + dynamicConfig: + description: dynamic_config is the configuration that could be changed + dynamically (i.e. without needing restart). + properties: + accessLogs: + description: AccessLogs configures the output and format of Envoy + access logs + properties: + disableListenerLogs: + description: DisableListenerLogs turns off just listener logs + for connections rejected by Envoy because they don't have + a matching listener filter. + type: boolean + enabled: + description: Enabled turns off all access logging + type: boolean + jsonFormat: + description: The presence of one format string or the other + implies the access log string encoding. Defining both is + invalid. + type: string + path: + description: Path is the output file to write logs + type: string + textFormat: + type: string + type: + description: 'Type selects the output for logs: "file", "stderr". + "stdout"' + enum: + - LOG_SINK_TYPE_DEFAULT + - LOG_SINK_TYPE_FILE + - LOG_SINK_TYPE_STDERR + - LOG_SINK_TYPE_STDOUT + format: int32 + type: string + type: object + envoyExtensions: + items: + description: EnvoyExtension has configuration for an extension + that patches Envoy resources. + properties: + arguments: + type: object + x-kubernetes-preserve-unknown-fields: true + consulVersion: + type: string + envoyVersion: + type: string + name: + type: string + required: + type: boolean + type: object + type: array + exposeConfig: + properties: + exposePaths: + items: + properties: + listenerPort: + format: int32 + type: integer + localPathPort: + format: int32 + type: integer + path: + type: string + protocol: + enum: + - EXPOSE_PATH_PROTOCOL_HTTP + - EXPOSE_PATH_PROTOCOL_HTTP2 + format: int32 + type: string + type: object + type: array + type: object + inboundConnections: + description: inbound_connections configures inbound connections + to the proxy. + properties: + balanceInboundConnections: + enum: + - BALANCE_CONNECTIONS_DEFAULT + - BALANCE_CONNECTIONS_EXACT + format: int32 + type: string + maxInboundConnections: + format: int64 + type: integer + type: object + listenerTracingJson: + type: string + localClusterJson: + type: string + localConnection: + additionalProperties: + description: Referenced by ProxyConfiguration + properties: + connectTimeout: + description: "A Duration represents a signed, fixed-length + span of time represented as a count of seconds and fractions + of seconds at nanosecond resolution. It is independent + of any calendar and concepts like \"day\" or \"month\". + It is related to Timestamp in that the difference between + two Timestamp values is a Duration and it can be added + or subtracted from a Timestamp. Range is approximately + +-10,000 years. \n # Examples \n Example 1: Compute Duration + from two Timestamps in pseudo code. \n Timestamp start + = ...; Timestamp end = ...; Duration duration = ...; \n + duration.seconds = end.seconds - start.seconds; duration.nanos + = end.nanos - start.nanos; \n if (duration.seconds < 0 + && duration.nanos > 0) { duration.seconds += 1; duration.nanos + -= 1000000000; } else if (duration.seconds > 0 && duration.nanos + < 0) { duration.seconds -= 1; duration.nanos += 1000000000; + } \n Example 2: Compute Timestamp from Timestamp + Duration + in pseudo code. \n Timestamp start = ...; Duration duration + = ...; Timestamp end = ...; \n end.seconds = start.seconds + + duration.seconds; end.nanos = start.nanos + duration.nanos; + \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += + 1000000000; } else if (end.nanos >= 1000000000) { end.seconds + += 1; end.nanos -= 1000000000; } \n Example 3: Compute + Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, + minutes=10) duration = Duration() duration.FromTimedelta(td) + \n # JSON Mapping \n In JSON format, the Duration type + is encoded as a string rather than an object, where the + string ends in the suffix \"s\" (indicating seconds) and + is preceded by the number of seconds, with nanoseconds + expressed as fractional seconds. For example, 3 seconds + with 0 nanoseconds should be encoded in JSON format as + \"3s\", while 3 seconds and 1 nanosecond should be expressed + in JSON format as \"3.000000001s\", and 3 seconds and + 1 microsecond should be expressed in JSON format as \"3.000001s\"." + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + requestTimeout: + description: "A Duration represents a signed, fixed-length + span of time represented as a count of seconds and fractions + of seconds at nanosecond resolution. It is independent + of any calendar and concepts like \"day\" or \"month\". + It is related to Timestamp in that the difference between + two Timestamp values is a Duration and it can be added + or subtracted from a Timestamp. Range is approximately + +-10,000 years. \n # Examples \n Example 1: Compute Duration + from two Timestamps in pseudo code. \n Timestamp start + = ...; Timestamp end = ...; Duration duration = ...; \n + duration.seconds = end.seconds - start.seconds; duration.nanos + = end.nanos - start.nanos; \n if (duration.seconds < 0 + && duration.nanos > 0) { duration.seconds += 1; duration.nanos + -= 1000000000; } else if (duration.seconds > 0 && duration.nanos + < 0) { duration.seconds -= 1; duration.nanos += 1000000000; + } \n Example 2: Compute Timestamp from Timestamp + Duration + in pseudo code. \n Timestamp start = ...; Duration duration + = ...; Timestamp end = ...; \n end.seconds = start.seconds + + duration.seconds; end.nanos = start.nanos + duration.nanos; + \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += + 1000000000; } else if (end.nanos >= 1000000000) { end.seconds + += 1; end.nanos -= 1000000000; } \n Example 3: Compute + Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, + minutes=10) duration = Duration() duration.FromTimedelta(td) + \n # JSON Mapping \n In JSON format, the Duration type + is encoded as a string rather than an object, where the + string ends in the suffix \"s\" (indicating seconds) and + is preceded by the number of seconds, with nanoseconds + expressed as fractional seconds. For example, 3 seconds + with 0 nanoseconds should be encoded in JSON format as + \"3s\", while 3 seconds and 1 nanosecond should be expressed + in JSON format as \"3.000000001s\", and 3 seconds and + 1 microsecond should be expressed in JSON format as \"3.000001s\"." + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + type: object + description: local_connection is the configuration that should + be used to connect to the local application provided per-port. + The map keys should correspond to port names on the workload. + type: object + localWorkloadAddress: + description: "deprecated: local_workload_address, local_workload_port, + and local_workload_socket_path are deprecated and are only needed + for migration of existing resources. \n Deprecated: Marked as + deprecated in pbmesh/v2beta1/proxy_configuration.proto." + type: string + localWorkloadPort: + description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' + format: int32 + type: integer + localWorkloadSocketPath: + description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' + type: string + meshGatewayMode: + enum: + - MESH_GATEWAY_MODE_UNSPECIFIED + - MESH_GATEWAY_MODE_NONE + - MESH_GATEWAY_MODE_LOCAL + - MESH_GATEWAY_MODE_REMOTE + format: int32 + type: string + mode: + description: mode indicates the proxy's mode. This will default + to 'transparent'. + enum: + - PROXY_MODE_DEFAULT + - PROXY_MODE_TRANSPARENT + - PROXY_MODE_DIRECT + format: int32 + type: string + mutualTlsMode: + enum: + - MUTUAL_TLS_MODE_DEFAULT + - MUTUAL_TLS_MODE_STRICT + - MUTUAL_TLS_MODE_PERMISSIVE + format: int32 + type: string + publicListenerJson: + type: string + transparentProxy: + properties: + dialedDirectly: + description: dialed_directly indicates whether this proxy + should be dialed using original destination IP in the connection + rather than load balance between all endpoints. + type: boolean + outboundListenerPort: + description: outbound_listener_port is the port for the proxy's + outbound listener. This defaults to 15001. + format: int32 + type: integer + type: object + type: object + opaqueConfig: + description: "deprecated: prevent usage when using v2 APIs directly. + needed for backwards compatibility \n Deprecated: Marked as deprecated + in pbmesh/v2beta1/proxy_configuration.proto." + type: object + x-kubernetes-preserve-unknown-fields: true + workloads: + description: Selection of workloads this proxy configuration should + apply to. These can be prefixes or specific workload names. + properties: + filter: + type: string + names: + items: + type: string + type: array + prefixes: + items: + type: string + type: array + type: object + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index 3d2df52a7e..ce49c9149a 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: proxydefaults.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -264,10 +262,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-referencegrants.yaml b/charts/consul/templates/crd-referencegrants-external.yaml similarity index 100% rename from charts/consul/templates/crd-referencegrants.yaml rename to charts/consul/templates/crd-referencegrants-external.yaml diff --git a/charts/consul/templates/crd-routeauthfilters.yaml b/charts/consul/templates/crd-routeauthfilters.yaml index dcea7192a4..a51bf226cd 100644 --- a/charts/consul/templates/crd-routeauthfilters.yaml +++ b/charts/consul/templates/crd-routeauthfilters.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: routeauthfilters.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: routeauthfilters.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -198,10 +196,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-routeretryfilters.yaml b/charts/consul/templates/crd-routeretryfilters.yaml index 1d538aa8d6..14b6062f60 100644 --- a/charts/consul/templates/crd-routeretryfilters.yaml +++ b/charts/consul/templates/crd-routeretryfilters.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: routeretryfilters.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: routeretryfilters.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -114,10 +112,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-routetimeoutfilters.yaml b/charts/consul/templates/crd-routetimeoutfilters.yaml index 28e4ec3ffb..95ab50320d 100644 --- a/charts/consul/templates/crd-routetimeoutfilters.yaml +++ b/charts/consul/templates/crd-routetimeoutfilters.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: routetimeoutfilters.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: routetimeoutfilters.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -57,17 +55,9 @@ spec: description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. properties: idleTimeout: - description: A Duration represents the elapsed time between two instants - as an int64 nanosecond count. The representation limits the largest - representable duration to approximately 290 years. - format: int64 - type: integer + type: string requestTimeout: - description: A Duration represents the elapsed time between two instants - as an int64 nanosecond count. The representation limits the largest - representable duration to approximately 290 years. - format: int64 - type: integer + type: string type: object status: properties: @@ -112,10 +102,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index 60beb5662c..ea0ad7c8a0 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: samenessgroups.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -128,10 +126,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index ddff40ced2..c7e2b5bb2b 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicedefaults.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -564,10 +562,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index c4d2b5f20d..75299f016e 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: serviceintentions.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -310,10 +308,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index eb5643fe49..6d89125216 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: serviceresolvers.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -347,10 +345,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index f28da9e7c1..72690c60e4 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicerouters.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -311,10 +309,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index a2af050c3d..8d5ed58023 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: servicesplitters.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -185,10 +183,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-tcproutes-external.yaml b/charts/consul/templates/crd-tcproutes-external.yaml new file mode 100644 index 0000000000..91989135e2 --- /dev/null +++ b/charts/consul/templates/crd-tcproutes-external.yaml @@ -0,0 +1,284 @@ +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/charts/consul/templates/crd-tcproutes.yaml b/charts/consul/templates/crd-tcproutes.yaml index 91989135e2..ae9d2cd080 100644 --- a/charts/consul/templates/crd-tcproutes.yaml +++ b/charts/consul/templates/crd-tcproutes.yaml @@ -1,284 +1,278 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +{{- if .Values.connectInject.enabled }} apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: tcproutes.gateway.networking.k8s.io + name: tcproutes.mesh.consul.hashicorp.com spec: - group: gateway.networking.k8s.io + group: mesh.consul.hashicorp.com names: - categories: - - gateway-api kind: TCPRoute listKind: TCPRouteList plural: tcproutes + shortNames: + - tcp-route singular: tcproute scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of TCPRoute. - properties: - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - description: TCPRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: TCPRoute is the Schema for the TCP Route API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: "NOTE: this should align to the GAMMA/gateway-api version, + or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute + \n This is a Resource type." + properties: + parentRefs: + description: "ParentRefs references the resources (usually Services) + that a Route wants to be attached to. \n It is invalid to reference + an identical parent more than once. It is valid to reference multiple + distinct sections within the same parent resource." + items: + description: 'NOTE: roughly equivalent to structs.ResourceReference' + properties: + port: + description: For east/west this is the name of the Consul Service + port to direct traffic to or empty to imply all. For north/south + this is TBD. + type: string + ref: + description: For east/west configuration, this should point + to a Service. For north/south it should point to a Gateway. + properties: + name: + description: Name is the user-given name of the resource + (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of the resource + the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units (i.e. + partition, namespace) in which the resource resides. properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + namespace: + description: "Namespace further isolates resources within + a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all namespaces." type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all partitions." type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + peerName: + description: "PeerName identifies which peer the resource + is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all peers." type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of TCPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" + type: + description: Type identifies the resource's type. properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. "catalog", + "authorization"). type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown + groupVersion: + description: GroupVersion is incremented when sweeping + or backward-incompatible changes are made to the group's + resource types. type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + kind: + description: Kind identifies the specific resource type + within the group. type: string - required: - - lastTransitionTime - - message - - reason - - status - - type type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + type: object + type: object + type: array + rules: + description: Rules are a list of TCP matchers and actions. + items: + properties: + backendRefs: + description: BackendRefs defines the backend(s) where matching + requests should be sent. If unspecified or invalid (refers + to a non-existent resource or a Service with no endpoints), + the underlying implementation MUST actively reject connection + attempts to this backend. Connection rejections must respect + weight; if an invalid backend is requested to have 80% of + connections, then 80% of connections must be rejected instead. + items: properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + backendRef: + properties: + datacenter: + type: string + port: + description: "For east/west this is the name of the + Consul Service port to direct traffic to or empty + to imply using the same value as the parent ref. + \n For north/south this is TBD." + type: string + ref: + description: For east/west configuration, this should + point to a Service. + properties: + name: + description: Name is the user-given name of the + resource (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of + the resource the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units + (i.e. partition, namespace) in which the resource + resides. + properties: + namespace: + description: "Namespace further isolates resources + within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all partitions." + type: string + peerName: + description: "PeerName identifies which peer + the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. + "catalog", "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when + sweeping or backward-incompatible changes + are made to the group's resource types. + type: string + kind: + description: Kind identifies the specific + resource type within the group. + type: string + type: object + type: object + type: object + weight: + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1." format: int32 - maximum: 65535 - minimum: 1 type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + type: array + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} {{- end }} diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 583c218be8..565aa63381 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: terminatinggateways.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -136,10 +134,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-tlsroutes.yaml b/charts/consul/templates/crd-tlsroutes-external.yaml similarity index 100% rename from charts/consul/templates/crd-tlsroutes.yaml rename to charts/consul/templates/crd-tlsroutes-external.yaml diff --git a/charts/consul/templates/crd-trafficpermissions.yaml b/charts/consul/templates/crd-trafficpermissions.yaml index ef8e8a73ca..27ab6f5e3d 100644 --- a/charts/consul/templates/crd-trafficpermissions.yaml +++ b/charts/consul/templates/crd-trafficpermissions.yaml @@ -1,18 +1,16 @@ {{- if .Values.connectInject.enabled }} ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null - name: trafficpermissions.auth.consul.hashicorp.com + controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + name: trafficpermissions.auth.consul.hashicorp.com spec: group: auth.consul.hashicorp.com names: @@ -56,7 +54,6 @@ spec: metadata: type: object spec: - description: TrafficPermissionsSpec defines the desired state of TrafficPermissions. properties: action: description: "Action can be either allow or deny for the entire object. @@ -71,34 +68,36 @@ spec: deny permissions have no effect without an allow permission as everything is denied by default. \n Action unspecified is reserved for compatibility with the addition of future actions." + enum: + - ACTION_ALLOW + - ACTION_DENY + - ACTION_UNKNOWN + format: int32 type: string destination: description: Destination is a configuration of the destination proxies where these traffic permissions should apply. properties: identityName: - description: Name is the destination of all intentions defined - in this config entry. This may be set to the wildcard character - (*) to match all services that don't otherwise have intentions - defined. type: string type: object permissions: description: Permissions is a list of permissions to match on. They are applied using OR semantics. items: + description: Permissions is a list of permissions to match on. properties: destinationRules: - description: destinationRules is a list of rules to apply for + description: DestinationRules is a list of rules to apply for matching sources in this Permission. These rules are specific to the request or connection that is going to the destination(s) selected by the TrafficPermissions resource. items: - description: DestinationRule contains rules to apply to the - incoming connection. + description: DestinationRule contains rules rules to apply + to the incoming connection. properties: exclude: - description: exclude contains a list of rules to exclude + description: Exclude contains a list of rules to exclude when evaluating rules for the incoming connection. items: properties: @@ -120,7 +119,7 @@ spec: type: string type: object methods: - description: methods is the list of HTTP methods. + description: Methods is the list of HTTP methods. items: type: string type: array @@ -131,7 +130,7 @@ spec: pathRegex: type: string portNames: - description: portNames is a list of workload ports + description: PortNames is a list of workload ports to apply this rule to. The ports specified here must be the ports used in the connection. items: @@ -157,7 +156,7 @@ spec: type: string type: object methods: - description: methods is the list of HTTP methods. If no + description: Methods is the list of HTTP methods. If no methods are specified, this rule will apply to all methods. items: type: string @@ -175,19 +174,19 @@ spec: type: object type: array sources: - description: sources is a list of sources in this traffic permission. + description: Sources is a list of sources in this traffic permission. items: description: Source represents the source identity. To specify any of the wildcard sources, the specific fields need to - be omitted. For example, for a wildcard namespace, identityName + be omitted. For example, for a wildcard namespace, identity_name should be omitted. properties: exclude: - description: exclude is a list of sources to exclude from + description: Exclude is a list of sources to exclude from this source. items: description: ExcludeSource is almost the same as source - but it prevents the addition of matchiing sources. + but it prevents the addition of matching sources. properties: identityName: type: string @@ -259,10 +258,4 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-udproutes.yaml b/charts/consul/templates/crd-udproutes-external.yaml similarity index 100% rename from charts/consul/templates/crd-udproutes.yaml rename to charts/consul/templates/crd-udproutes-external.yaml diff --git a/charts/consul/test/unit/crd-gatewayclasses.bats b/charts/consul/test/unit/crd-gatewayclasses-external.bats similarity index 80% rename from charts/consul/test/unit/crd-gatewayclasses.bats rename to charts/consul/test/unit/crd-gatewayclasses-external.bats index 8400590606..a1a845a249 100644 --- a/charts/consul/test/unit/crd-gatewayclasses.bats +++ b/charts/consul/test/unit/crd-gatewayclasses-external.bats @@ -5,7 +5,7 @@ load _helpers @test "gatewayclasses/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-gatewayclasses.yaml \ + -s templates/crd-gatewayclasses-external.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "gatewayclasses/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-gatewayclasses.yaml \ + -s templates/crd-gatewayclasses-external.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "gatewayclasses/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-gatewayclasses.yaml \ + -s templates/crd-gatewayclasses-external.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-gateways.bats b/charts/consul/test/unit/crd-gateways-external.bats similarity index 82% rename from charts/consul/test/unit/crd-gateways.bats rename to charts/consul/test/unit/crd-gateways-external.bats index 8a7f0284e2..30b6d71630 100644 --- a/charts/consul/test/unit/crd-gateways.bats +++ b/charts/consul/test/unit/crd-gateways-external.bats @@ -5,7 +5,7 @@ load _helpers @test "gateways/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-gateways.yaml \ + -s templates/crd-gateways-external.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "gateways/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-gateways.yaml \ + -s templates/crd-gateways-external.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "gateways/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-gateways.yaml \ + -s templates/crd-gateways-external.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-grpcroutes.bats b/charts/consul/test/unit/crd-grpcroutes-external.bats similarity index 81% rename from charts/consul/test/unit/crd-grpcroutes.bats rename to charts/consul/test/unit/crd-grpcroutes-external.bats index d5e3e298d7..625648e326 100644 --- a/charts/consul/test/unit/crd-grpcroutes.bats +++ b/charts/consul/test/unit/crd-grpcroutes-external.bats @@ -5,7 +5,7 @@ load _helpers @test "grpcroutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-grpcroutes.yaml \ + -s templates/crd-grpcroutes-external.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "grpcroutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-grpcroutes.yaml \ + -s templates/crd-grpcroutes-external.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "grpcroutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-grpcroutes.yaml \ + -s templates/crd-grpcroutes-external.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-httproutes.bats b/charts/consul/test/unit/crd-httproutes-external.bats similarity index 81% rename from charts/consul/test/unit/crd-httproutes.bats rename to charts/consul/test/unit/crd-httproutes-external.bats index 99c58e0492..e35bebc3e4 100644 --- a/charts/consul/test/unit/crd-httproutes.bats +++ b/charts/consul/test/unit/crd-httproutes-external.bats @@ -5,7 +5,7 @@ load _helpers @test "httproutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-httproutes.yaml \ + -s templates/crd-httproutes-external.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "httproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-httproutes.yaml \ + -s templates/crd-httproutes-external.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "httproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-httproutes.yaml \ + -s templates/crd-httproutes-external.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-tcproutes.bats b/charts/consul/test/unit/crd-tcproutes-external.bats similarity index 84% rename from charts/consul/test/unit/crd-tcproutes.bats rename to charts/consul/test/unit/crd-tcproutes-external.bats index 9cfdd182e7..c91eb15e6b 100644 --- a/charts/consul/test/unit/crd-tcproutes.bats +++ b/charts/consul/test/unit/crd-tcproutes-external.bats @@ -5,7 +5,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-tcproutes.yaml \ + -s templates/crd-tcproutes-external.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tcproutes.yaml \ + -s templates/crd-tcproutes-external.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tcproutes.yaml \ + -s templates/crd-tcproutes-external.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } @@ -30,7 +30,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false and connectInject.apiGateway.manageNonStandardCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tcproutes.yaml \ + -s templates/crd-tcproutes-external.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ --set 'connectInject.apiGateway.manageNonStandardCRDs=false' \ . @@ -39,7 +39,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: enabled with connectInject.apiGateway.manageNonStandardCRDs=true" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-tcproutes.yaml \ + -s templates/crd-tcproutes-external.yaml \ --set 'connectInject.apiGateway.manageNonStandardCRDs=true' \ . | tee /dev/stderr | yq -s 'length > 0' | tee /dev/stderr) diff --git a/charts/consul/test/unit/crd-tlsroutes.bats b/charts/consul/test/unit/crd-tlsroutes-external.bats similarity index 82% rename from charts/consul/test/unit/crd-tlsroutes.bats rename to charts/consul/test/unit/crd-tlsroutes-external.bats index 7e1d5c471f..88b37521f2 100644 --- a/charts/consul/test/unit/crd-tlsroutes.bats +++ b/charts/consul/test/unit/crd-tlsroutes-external.bats @@ -5,7 +5,7 @@ load _helpers @test "tlsroutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-tlsroutes.yaml \ + -s templates/crd-tlsroutes-external.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "tlsroutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tlsroutes.yaml \ + -s templates/crd-tlsroutes-external.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "tlsroutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tlsroutes.yaml \ + -s templates/crd-tlsroutes-external.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-udproutes.bats b/charts/consul/test/unit/crd-udproutes-external.bats similarity index 82% rename from charts/consul/test/unit/crd-udproutes.bats rename to charts/consul/test/unit/crd-udproutes-external.bats index 407592a770..6693e67b2d 100644 --- a/charts/consul/test/unit/crd-udproutes.bats +++ b/charts/consul/test/unit/crd-udproutes-external.bats @@ -5,7 +5,7 @@ load _helpers @test "udproutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-udproutes.yaml \ + -s templates/crd-udproutes-external.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "udproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-udproutes.yaml \ + -s templates/crd-udproutes-external.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "udproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-udproutes.yaml \ + -s templates/crd-udproutes-external.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/control-plane/PROJECT b/control-plane/PROJECT index b77d2be9a9..7146070824 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -149,6 +149,6 @@ resources: domain: consul.hashicorp.com group: auth kind: TrafficPermissions - path: github.com/hashicorp/consul-k8s/control-plane/api/v2beta1 + path: github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1 version: v2beta1 version: "3" diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index 07c526be4d..ed949b1ade 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -159,8 +159,8 @@ func (t ResourceTranslator) translateRouteRetryFilter(routeRetryFilter *v1alpha1 func (t ResourceTranslator) translateRouteTimeoutFilter(routeTimeoutFilter *v1alpha1.RouteTimeoutFilter) *api.TimeoutFilter { return &api.TimeoutFilter{ - RequestTimeout: routeTimeoutFilter.Spec.RequestTimeout, - IdleTimeout: routeTimeoutFilter.Spec.IdleTimeout, + RequestTimeout: routeTimeoutFilter.Spec.RequestTimeout.Duration, + IdleTimeout: routeTimeoutFilter.Spec.IdleTimeout.Duration, } } diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index 4ce3805cff..8f02f6eca1 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -1387,8 +1387,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Namespace: "k8s-ns", }, Spec: v1alpha1.RouteTimeoutFilterSpec{ - RequestTimeout: 10, - IdleTimeout: 30, + RequestTimeout: metav1.Duration{Duration: 10}, + IdleTimeout: metav1.Duration{Duration: 30}, }, }, diff --git a/control-plane/api/v2beta1/groupversion_info.go b/control-plane/api/auth/v2beta1/auth_groupversion_info.go similarity index 94% rename from control-plane/api/v2beta1/groupversion_info.go rename to control-plane/api/auth/v2beta1/auth_groupversion_info.go index 35e6d2056b..3329d86855 100644 --- a/control-plane/api/v2beta1/groupversion_info.go +++ b/control-plane/api/auth/v2beta1/auth_groupversion_info.go @@ -13,8 +13,7 @@ import ( var ( - // AUTH group. - + // AuthGroup is a collection of auth resources. AuthGroup = "auth.consul.hashicorp.com" // AuthGroupVersion is group version used to register these objects. diff --git a/control-plane/api/v2beta1/shared_types.go b/control-plane/api/auth/v2beta1/shared_types.go similarity index 100% rename from control-plane/api/v2beta1/shared_types.go rename to control-plane/api/auth/v2beta1/shared_types.go diff --git a/control-plane/api/v2beta1/status.go b/control-plane/api/auth/v2beta1/status.go similarity index 100% rename from control-plane/api/v2beta1/status.go rename to control-plane/api/auth/v2beta1/status.go diff --git a/control-plane/api/auth/v2beta1/traffic_permissions_types.go b/control-plane/api/auth/v2beta1/traffic_permissions_types.go new file mode 100644 index 0000000000..e3a0d32f1a --- /dev/null +++ b/control-plane/api/auth/v2beta1/traffic_permissions_types.go @@ -0,0 +1,242 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + trafficpermissionsKubeKind = "trafficpermissions" +) + +func init() { + AuthSchemeBuilder.Register(&TrafficPermissions{}, &TrafficPermissionsList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// TrafficPermissions is the Schema for the traffic-permissions API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="traffic-permissions" +type TrafficPermissions struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbauth.TrafficPermissions `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// TrafficPermissionsList contains a list of TrafficPermissions. +type TrafficPermissionsList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*TrafficPermissions `json:"items"` +} + +func (in *TrafficPermissions) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbauth.TrafficPermissionsType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *TrafficPermissions) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *TrafficPermissions) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *TrafficPermissions) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *TrafficPermissions) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *TrafficPermissions) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *TrafficPermissions) KubeKind() string { + return trafficpermissionsKubeKind +} + +func (in *TrafficPermissions) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *TrafficPermissions) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *TrafficPermissions) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *TrafficPermissions) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *TrafficPermissions) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *TrafficPermissions) Validate(tenancy common.ConsulTenancyConfig) error { + var errs field.ErrorList + path := field.NewPath("spec") + var tp pbauth.TrafficPermissions + res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) + if err := res.Data.UnmarshalTo(&tp); err != nil { + return fmt.Errorf("error parsing resource data as type %q: %s", &tp, err) + } + + switch tp.Action { + case pbauth.Action_ACTION_ALLOW: + case pbauth.Action_ACTION_DENY: + case pbauth.Action_ACTION_UNSPECIFIED: + fallthrough + default: + errs = append(errs, field.Invalid(path.Child("action"), tp.Action, "action must be either allow or deny")) + } + + if tp.Destination == nil || (len(tp.Destination.IdentityName) == 0) { + errs = append(errs, field.Invalid(path.Child("destination"), tp.Destination, "cannot be empty")) + } + // Validate permissions + for i, permission := range tp.Permissions { + if err := validatePermission(permission, path.Child("permissions").Index(i)); err != nil { + errs = append(errs, err...) + } + } + if len(errs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: AuthGroup, Kind: common.TrafficPermissions}, + in.KubernetesName(), errs) + } + return nil +} + +func validatePermission(p *pbauth.Permission, path *field.Path) field.ErrorList { + var errs field.ErrorList + + for s, src := range p.Sources { + if sourceHasIncompatibleTenancies(src) { + errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "permission sources may not specify partitions, peers, and sameness_groups together")) + } + + if src.Namespace == "" && src.IdentityName != "" { + errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "permission sources may not have wildcard namespaces and explicit names")) + } + + // Excludes are only valid for wildcard sources. + if src.IdentityName != "" && len(src.Exclude) > 0 { + errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "must be defined on wildcard sources")) + continue + } + + for e, d := range src.Exclude { + if sourceHasIncompatibleTenancies(d) { + errs = append(errs, field.Invalid(path.Child("sources").Index(s).Child("exclude").Index(e), d, "permissions sources may not specify partitions, peers, and sameness_groups together")) + } + + if d.Namespace == "" && d.IdentityName != "" { + errs = append(errs, field.Invalid(path.Child("sources").Index(s).Child("exclude").Index(e), d, "permission sources may not have wildcard namespaces and explicit names")) + } + } + } + for d, dest := range p.DestinationRules { + if (len(dest.PathExact) > 0 && len(dest.PathPrefix) > 0) || + (len(dest.PathRegex) > 0 && len(dest.PathExact) > 0) || + (len(dest.PathRegex) > 0 && len(dest.PathPrefix) > 0) { + errs = append(errs, field.Invalid(path.Child("destinationRules").Index(d), dest, "prefix values, regex values, and explicit names must not combined")) + } + if len(dest.Exclude) > 0 { + for e, excl := range dest.Exclude { + if (len(excl.PathExact) > 0 && len(excl.PathPrefix) > 0) || + (len(excl.PathRegex) > 0 && len(excl.PathExact) > 0) || + (len(excl.PathRegex) > 0 && len(excl.PathPrefix) > 0) { + errs = append(errs, field.Invalid(path.Child("destinationRules").Index(d).Child("exclude").Index(e), excl, "prefix values, regex values, and explicit names must not combined")) + } + } + } + } + + return errs +} + +func sourceHasIncompatibleTenancies(src pbauth.SourceToSpiffe) bool { + peerSet := src.GetPeer() != common.DefaultPeerName && src.GetPeer() != "" + apSet := src.GetPartition() != common.DefaultPartitionName && src.GetPartition() != "" + sgSet := src.GetSamenessGroup() != "" + + return (apSet && peerSet) || (apSet && sgSet) || (peerSet && sgSet) +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *TrafficPermissions) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/v2beta1/traffic_permissions_types_test.go b/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go similarity index 55% rename from control-plane/api/v2beta1/traffic_permissions_types_test.go rename to control-plane/api/auth/v2beta1/traffic_permissions_types_test.go index f48740e496..79b5e71b85 100644 --- a/control-plane/api/v2beta1/traffic_permissions_types_test.go +++ b/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go @@ -27,7 +27,7 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { cases := map[string]struct { OurConsulNamespace string OurConsulPartition string - OurData TrafficPermissions + OurData *TrafficPermissions TheirName string TheirConsulNamespace string @@ -40,11 +40,11 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { "empty fields matches": { OurConsulNamespace: constants.DefaultConsulNS, OurConsulPartition: constants.DefaultConsulPartition, - OurData: TrafficPermissions{ + OurData: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "name", }, - Spec: TrafficPermissionsSpec{}, + Spec: pbauth.TrafficPermissions{}, }, TheirName: "name", TheirConsulNamespace: constants.DefaultConsulNS, @@ -59,19 +59,19 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { "source namespaces and partitions are compared": { OurConsulNamespace: "consul-ns", OurConsulPartition: "consul-partition", - OurData: TrafficPermissions{ + OurData: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "kube-ns", }, - Spec: TrafficPermissionsSpec{ - Destination: &Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: ActionAllow, - Permissions: Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: []*Source{ + Sources: []*pbauth.Source{ { IdentityName: "source-identity", Namespace: "the space namespace space", @@ -105,19 +105,19 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { "destination namespaces and partitions are compared": { OurConsulNamespace: "not-consul-ns", OurConsulPartition: "not-consul-partition", - OurData: TrafficPermissions{ + OurData: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "kube-ns", }, - Spec: TrafficPermissionsSpec{ - Destination: &Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: ActionAllow, - Permissions: Permissions{ + Action: pbauth.Action_ACTION_DENY, + Permissions: []*pbauth.Permission{ { - Sources: []*Source{ + Sources: []*pbauth.Source{ { IdentityName: "source-identity", }, @@ -149,25 +149,25 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { "all fields set matches": { OurConsulNamespace: "consul-ns", OurConsulPartition: "consul-partition", - OurData: TrafficPermissions{ + OurData: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "kube-ns", }, - Spec: TrafficPermissionsSpec{ - Destination: &Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: ActionAllow, - Permissions: Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: []*Source{ + Sources: []*pbauth.Source{ { Namespace: "the space namespace space", Partition: "space-partition", Peer: "space-peer", SamenessGroup: "space-group", - Exclude: Exclude{ + Exclude: []*pbauth.ExcludeSource{ { IdentityName: "not-source-identity", Namespace: "the space namespace space", @@ -181,12 +181,12 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { IdentityName: "source-identity", }, }, - DestinationRules: DestinationRules{ + DestinationRules: []*pbauth.DestinationRule{ { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &DestinationRuleHeader{ + Header: &pbauth.DestinationRuleHeader{ Name: "x-consul-test", Present: true, Exact: "true", @@ -196,12 +196,12 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { Invert: true, }, Methods: []string{"GET", "POST"}, - Exclude: ExcludePermissions{ + Exclude: []*pbauth.ExcludePermissionRule{ { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &DestinationRuleHeader{ + Header: &pbauth.DestinationRuleHeader{ Name: "x-consul-not-test", Present: true, Exact: "false", @@ -296,11 +296,11 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { "different types does not match": { OurConsulNamespace: constants.DefaultConsulNS, OurConsulPartition: constants.DefaultConsulPartition, - OurData: TrafficPermissions{ + OurData: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "name", }, - Spec: TrafficPermissionsSpec{}, + Spec: pbauth.TrafficPermissions{}, }, ResourceOverride: &pbresource.Resource{ Id: &pbresource.ID{ @@ -335,18 +335,18 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { // TestTrafficPermissions_Resource also includes test to verify ResourceID(). func TestTrafficPermissions_Resource(t *testing.T) { cases := map[string]struct { - Ours TrafficPermissions + Ours *TrafficPermissions ConsulNamespace string ConsulPartition string ExpectedName string ExpectedData *pbauth.TrafficPermissions }{ "empty fields": { - Ours: TrafficPermissions{ + Ours: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: TrafficPermissionsSpec{}, + Spec: pbauth.TrafficPermissions{}, }, ConsulNamespace: constants.DefaultConsulNS, ConsulPartition: constants.DefaultConsulPartition, @@ -354,25 +354,25 @@ func TestTrafficPermissions_Resource(t *testing.T) { ExpectedData: &pbauth.TrafficPermissions{}, }, "every field set": { - Ours: TrafficPermissions{ + Ours: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "kube-ns", }, - Spec: TrafficPermissionsSpec{ - Destination: &Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: ActionAllow, - Permissions: Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: []*Source{ + Sources: []*pbauth.Source{ { Namespace: "the space namespace space", Partition: "space-partition", Peer: "space-peer", SamenessGroup: "space-group", - Exclude: Exclude{ + Exclude: []*pbauth.ExcludeSource{ { IdentityName: "not-source-identity", Namespace: "the space namespace space", @@ -386,12 +386,12 @@ func TestTrafficPermissions_Resource(t *testing.T) { IdentityName: "source-identity", }, }, - DestinationRules: DestinationRules{ + DestinationRules: []*pbauth.DestinationRule{ { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &DestinationRuleHeader{ + Header: &pbauth.DestinationRuleHeader{ Name: "x-consul-test", Present: true, Exact: "true", @@ -401,12 +401,12 @@ func TestTrafficPermissions_Resource(t *testing.T) { Invert: true, }, Methods: []string{"GET", "POST"}, - Exclude: ExcludePermissions{ + Exclude: []*pbauth.ExcludePermissionRule{ { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &DestinationRuleHeader{ + Header: &pbauth.DestinationRuleHeader{ Name: "x-consul-not-test", Present: true, Exact: "false", @@ -579,8 +579,8 @@ func TestTrafficPermissions_KubernetesName(t *testing.T) { Name: "test", Namespace: "bar", }, - Spec: TrafficPermissionsSpec{ - Destination: &Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "foo", }, }, @@ -615,39 +615,34 @@ func TestTrafficPermissions_Validate(t *testing.T) { Name: "foo", Namespace: "kube-ns", }, - Spec: TrafficPermissionsSpec{ - Destination: &Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: ActionAllow, - Permissions: Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: []*Source{ + Sources: []*pbauth.Source{ { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: Exclude{ + Namespace: "the space namespace space", + Partition: "space-partition", + Exclude: []*pbauth.ExcludeSource{ { IdentityName: "not-source-identity", Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", SamenessGroup: "space-group", }, }, }, { IdentityName: "source-identity", + Namespace: "another-namespace", }, }, - DestinationRules: DestinationRules{ + DestinationRules: []*pbauth.DestinationRule{ { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &DestinationRuleHeader{ + PathExact: "/hello", + Header: &pbauth.DestinationRuleHeader{ Name: "x-consul-test", Present: true, Exact: "true", @@ -657,12 +652,10 @@ func TestTrafficPermissions_Validate(t *testing.T) { Invert: true, }, Methods: []string{"GET", "POST"}, - Exclude: ExcludePermissions{ + Exclude: []*pbauth.ExcludePermissionRule{ { - PathExact: "/hello", PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &DestinationRuleHeader{ + Header: &pbauth.DestinationRuleHeader{ Name: "x-consul-not-test", Present: true, Exact: "false", @@ -691,64 +684,304 @@ func TestTrafficPermissions_Validate(t *testing.T) { Name: "does-not-matter", Namespace: "not-default-ns", }, - Spec: TrafficPermissionsSpec{ - Destination: &Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "dest-service", }, }, }, expectedErrMsgs: []string{ - "spec.action: Required value: action is required", - "spec.action: Invalid value: \"\": must be one of \"allow\" or \"deny\"", + `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.action: Invalid value: ACTION_UNSPECIFIED: action must be either allow or deny`, }, }, { - name: "action must be valid", + name: "destination is required", input: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "does-not-matter", Namespace: "not-default-ns", }, - Spec: TrafficPermissionsSpec{ - Destination: &Destination{ - IdentityName: "dest-service", + Spec: pbauth.TrafficPermissions{ + Action: pbauth.Action_ACTION_ALLOW, + }, + }, + expectedErrMsgs: []string{ + `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.destination: Invalid value: "null": cannot be empty`, + }, + }, + { + name: "destination.identityName is required", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: pbauth.TrafficPermissions{ + Action: pbauth.Action_ACTION_ALLOW, + Destination: &pbauth.Destination{}, + }, + }, + expectedErrMsgs: []string{ + `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.destination: Invalid value: authv2beta1.Destination{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:""}: cannot be empty`, + }, + }, + { + name: "permission.sources: partitions, peers, and sameness_groups", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + }, + { + Namespace: "the space namespace space", + Partition: "space-partition", + SamenessGroup: "space-sameness", + }, + { + Namespace: "the space namespace space", + Peer: "space-peer", + SamenessGroup: "space-sameness", + }, + }, + }, }, - Action: "blurg", }, }, expectedErrMsgs: []string{ - "spec.action: Invalid value: \"blurg\": must be one of \"allow\" or \"deny\"", + `spec.permissions[0].sources[0]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"space-peer", SamenessGroup:"", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, + `spec.permissions[0].sources[1]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"", SamenessGroup:"space-sameness", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, + `spec.permissions[0].sources[2]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"", Peer:"space-peer", SamenessGroup:"space-sameness", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, }, }, { - name: "destination is required", + name: "permission.sources: identity name without namespace", input: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "does-not-matter", Namespace: "not-default-ns", }, - Spec: TrafficPermissionsSpec{ - Action: "allow", + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "false-identity", + }, + }, + }, + }, }, }, expectedErrMsgs: []string{ - "spec.destination: Required value: destination and destination.identityName are required", + `spec.permissions[0].sources[0]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"false-identity", Namespace:"", Partition:"", Peer:"", SamenessGroup:"", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not have wildcard namespaces and explicit names`, }, }, { - name: "destination.identityName is required", + name: "permission.sources: identity name with excludes", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + Namespace: "default-namespace", + IdentityName: "false-identity", + Exclude: []*pbauth.ExcludeSource{ + { + IdentityName: "not-source-identity", + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `must be defined on wildcard sources`, + }, + }, + { + name: "permission.sources.exclude: incompatible tenancies", input: &TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "does-not-matter", Namespace: "not-default-ns", }, - Spec: TrafficPermissionsSpec{ - Action: "allow", - Destination: &Destination{}, + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + Namespace: "default-namespace", + Exclude: []*pbauth.ExcludeSource{ + { + Namespace: "the space namespace space", + Partition: "space-partition", + Peer: "space-peer", + }, + { + Namespace: "the space namespace space", + Partition: "space-partition", + SamenessGroup: "space-sameness", + }, + { + Namespace: "the space namespace space", + Peer: "space-peer", + SamenessGroup: "space-sameness", + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.permissions[0].sources[0].exclude[0]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"space-peer", SamenessGroup:""}: permissions sources may not specify partitions, peers, and sameness_groups together`, + `spec.permissions[0].sources[0].exclude[1]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"", SamenessGroup:"space-sameness"}: permissions sources may not specify partitions, peers, and sameness_groups together`, + `spec.permissions[0].sources[0].exclude[2]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"", Peer:"space-peer", SamenessGroup:"space-sameness"}: permissions sources may not specify partitions, peers, and sameness_groups together`, + }, + }, + { + name: "permission.sources.exclude: identity name without namespace", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + Namespace: "default-namespace", + Exclude: []*pbauth.ExcludeSource{ + { + IdentityName: "false-identity", + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.permissions[0].sources[0].exclude[0]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"false-identity", Namespace:"", Partition:"", Peer:"", SamenessGroup:""}: permission sources may not have wildcard namespaces and explicit names`, + }, + }, + { + name: "permission.destinationRules: incompatible destination rules", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + DestinationRules: []*pbauth.DestinationRule{ + { + PathExact: "/hello", + PathPrefix: "foobar", + }, + { + PathExact: "/hello", + PathRegex: "path-regex", + }, + { + PathPrefix: "foobar", + PathRegex: "path-regex", + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.permissions[0].destinationRules[0]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[1]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[2]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, + }, + }, + { + name: "permission.destinationRules.exclude: incompatible destination rules", + input: &TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "does-not-matter", + Namespace: "not-default-ns", + }, + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + DestinationRules: []*pbauth.DestinationRule{ + { + Exclude: []*pbauth.ExcludePermissionRule{ + { + PathExact: "/hello", + PathPrefix: "foobar", + }, + { + PathExact: "/hello", + PathRegex: "path-regex", + }, + { + PathPrefix: "foobar", + PathRegex: "path-regex", + }, + }, + }, + }, + }, + }, }, }, expectedErrMsgs: []string{ - "spec.destination.identityName: Required value: identityName is required", + `spec.permissions[0].destinationRules[0].exclude[0]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[0].exclude[1]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[0].exclude[2]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, }, }, } diff --git a/control-plane/api/v2beta1/trafficpermissions_webhook.go b/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go similarity index 97% rename from control-plane/api/v2beta1/trafficpermissions_webhook.go rename to control-plane/api/auth/v2beta1/trafficpermissions_webhook.go index a3371468d2..c7d8d0189d 100644 --- a/control-plane/api/v2beta1/trafficpermissions_webhook.go +++ b/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go @@ -54,7 +54,7 @@ func (v *TrafficPermissionsWebhook) List(ctx context.Context) ([]common.MeshConf } var entries []common.MeshConfig for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(&item)) + entries = append(entries, common.MeshConfig(item)) } return entries, nil } diff --git a/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go b/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..3aa46646cb --- /dev/null +++ b/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go @@ -0,0 +1,136 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v2beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Conditions) DeepCopyInto(out *Conditions) { + { + in := &in + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. +func (in Conditions) DeepCopy() Conditions { + if in == nil { + return nil + } + out := new(Conditions) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Status) DeepCopyInto(out *Status) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.LastSyncedTime != nil { + in, out := &in.LastSyncedTime, &out.LastSyncedTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. +func (in *Status) DeepCopy() *Status { + if in == nil { + return nil + } + out := new(Status) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrafficPermissions) DeepCopyInto(out *TrafficPermissions) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissions. +func (in *TrafficPermissions) DeepCopy() *TrafficPermissions { + if in == nil { + return nil + } + out := new(TrafficPermissions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrafficPermissions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrafficPermissionsList) DeepCopyInto(out *TrafficPermissionsList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*TrafficPermissions, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TrafficPermissions) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissionsList. +func (in *TrafficPermissionsList) DeepCopy() *TrafficPermissionsList { + if in == nil { + return nil + } + out := new(TrafficPermissionsList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrafficPermissionsList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 8d05e8e46b..2cf32fdc0b 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -25,6 +25,10 @@ const ( // V2 config entries. TrafficPermissions string = "trafficpermissions" + GRPCRoute string = "grpcroute" + HTTPRoute string = "httproute" + TCPRoute string = "tcproute" + ProxyConfiguration string = "proxyconfiguration" Global string = "global" Mesh string = "mesh" @@ -37,6 +41,10 @@ const ( MigrateEntryKey string = "consul.hashicorp.com/migrate-entry" MigrateEntryTrue string = "true" SourceValue string = "kubernetes" + + DefaultPartitionName = "default" + DefaultNamespaceName = "default" + DefaultPeerName = "local" ) // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. diff --git a/control-plane/api/mesh/v2beta1/grpc_route_types.go b/control-plane/api/mesh/v2beta1/grpc_route_types.go new file mode 100644 index 0000000000..44a8949e1d --- /dev/null +++ b/control-plane/api/mesh/v2beta1/grpc_route_types.go @@ -0,0 +1,327 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + grpcRouteKubeKind = "grpcroute" +) + +func init() { + MeshSchemeBuilder.Register(&GRPCRoute{}, &GRPCRouteList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// GRPCRoute is the Schema for the GRPC Route API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="grpc-route" +type GRPCRoute struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.GRPCRoute `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// GRPCRouteList contains a list of GRPCRoute. +type GRPCRouteList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*GRPCRoute `json:"items"` +} + +func (in *GRPCRoute) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.GRPCRouteType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *GRPCRoute) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *GRPCRoute) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *GRPCRoute) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *GRPCRoute) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *GRPCRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *GRPCRoute) KubeKind() string { + return grpcRouteKubeKind +} + +func (in *GRPCRoute) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *GRPCRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *GRPCRoute) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *GRPCRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *GRPCRoute) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *GRPCRoute) Validate(tenancy common.ConsulTenancyConfig) error { + var errs field.ErrorList + var route pbmesh.GRPCRoute + path := field.NewPath("spec") + + res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) + + if err := res.Data.UnmarshalTo(&route); err != nil { + return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) + } + + if len(route.ParentRefs) == 0 { + errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) + } + + if len(route.Hostnames) > 0 { + errs = append(errs, field.Invalid(path.Child("hostnames"), route.Hostnames, "should not populate hostnames")) + } + + for i, rule := range route.Rules { + rulePath := path.Child("rules").Index(i) + for j, match := range rule.Matches { + ruleMatchPath := rulePath.Child("matches").Index(j) + if match.Method != nil { + switch match.Method.Type { + case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: + errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("type"), match.Method.Type, "missing required field")) + case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT: + case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_REGEX: + default: + errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("type"), match.Method.Type, fmt.Sprintf("not a supported enum value: %v", match.Method.Type))) + } + if match.Method.Service == "" && match.Method.Method == "" { + errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("service"), match.Method.Service, "at least one of \"service\" or \"method\" must be set")) + } + } + + for k, header := range match.Headers { + ruleHeaderPath := ruleMatchPath.Child("headers").Index(k) + if err := validateHeaderMatchType(header.Type); err != nil { + errs = append(errs, field.Invalid(ruleHeaderPath.Child("type"), header.Type, err.Error())) + } + + if header.Name == "" { + errs = append(errs, field.Required(ruleHeaderPath.Child("name"), "missing required field")) + } + } + } + + for j, filter := range rule.Filters { + set := 0 + if filter.RequestHeaderModifier != nil { + set++ + } + if filter.ResponseHeaderModifier != nil { + set++ + } + if filter.UrlRewrite != nil { + set++ + if filter.UrlRewrite.PathPrefix == "" { + errs = append(errs, field.Required(rulePath.Child("filters").Index(j).Child("urlRewrite").Child("pathPrefix"), "field should not be empty if enclosing section is set")) + } + } + if set != 1 { + errs = append(errs, field.Invalid(rulePath.Child("filters").Index(j), filter, "exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required")) + } + } + + if len(rule.BackendRefs) == 0 { + errs = append(errs, field.Required(rulePath.Child("backendRefs"), "missing required field")) + } + for j, hbref := range rule.BackendRefs { + ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) + if hbref.BackendRef == nil { + errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) + continue + } + + if hbref.BackendRef.Datacenter != "" { + errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) + } + + if len(hbref.Filters) > 0 { + errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("filters"), hbref.Filters, "filters are not supported at this level yet")) + } + } + + if rule.Timeouts != nil { + errs = append(errs, validateHTTPTimeouts(rule.Timeouts, rulePath.Child("timeouts"))...) + } + if rule.Retries != nil { + errs = append(errs, validateHTTPRetries(rule.Retries, rulePath.Child("retries"))...) + } + } + + if len(errs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: MeshGroup, Kind: common.GRPCRoute}, + in.KubernetesName(), errs) + } + return nil +} + +func validateHeaderMatchType(typ pbmesh.HeaderMatchType) error { + switch typ { + case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED: + return fmt.Errorf("missing required field") + case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_EXACT: + case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_REGEX: + case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PRESENT: + case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX: + case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_SUFFIX: + default: + return fmt.Errorf("not a supported enum value: %v", typ) + } + return nil +} + +func validateHTTPTimeouts(timeouts *pbmesh.HTTPRouteTimeouts, path *field.Path) field.ErrorList { + if timeouts == nil { + return nil + } + + var errs field.ErrorList + + if timeouts.Request != nil { + val := timeouts.Request.AsDuration() + if val < 0 { + errs = append(errs, field.Invalid(path.Child("request"), val, "timeout cannot be negative")) + } + } + if timeouts.Idle != nil { + val := timeouts.Idle.AsDuration() + if val < 0 { + errs = append(errs, field.Invalid(path.Child("idle"), val, "timeout cannot be negative")) + } + } + + return errs +} + +func validateHTTPRetries(retries *pbmesh.HTTPRouteRetries, path *field.Path) field.ErrorList { + if retries == nil { + return nil + } + + var errs field.ErrorList + + for i, condition := range retries.OnConditions { + if !isValidRetryCondition(condition) { + errs = append(errs, field.Invalid(path.Child("onConditions").Index(i), condition, "not a valid retry condition")) + } + } + + return errs +} + +func isValidRetryCondition(retryOn string) bool { + switch retryOn { + case "5xx", + "gateway-error", + "reset", + "connect-failure", + "envoy-ratelimited", + "retriable-4xx", + "refused-stream", + "cancelled", + "deadline-exceeded", + "internal", + "resource-exhausted", + "unavailable": + return true + default: + return false + } +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *GRPCRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/grpc_route_types_test.go b/control-plane/api/mesh/v2beta1/grpc_route_types_test.go new file mode 100644 index 0000000000..69695284d0 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/grpc_route_types_test.go @@ -0,0 +1,1201 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +func TestGRPCRoute_MatchesConsul(t *testing.T) { + cases := map[string]struct { + OurConsulNamespace string + OurConsulPartition string + OurData *GRPCRoute + + TheirName string + TheirConsulNamespace string + TheirConsulPartition string + TheirData *pbmesh.GRPCRoute + ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match + + Matches bool + }{ + "empty fields matches": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: pbmesh.GRPCRoute{}, + }, + TheirName: "name", + TheirConsulNamespace: constants.DefaultConsulNS, + TheirConsulPartition: constants.DefaultConsulPartition, + TheirData: &pbmesh.GRPCRoute{}, + Matches: true, + }, + "hostnames are compared": { + OurConsulNamespace: "consul-ns", + OurConsulPartition: "consul-partition", + OurData: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + Hostnames: []string{ + "a-hostname", "another-hostname", + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbmesh.GRPCRoute{ + Hostnames: []string{ + "not-a-hostname", "another-hostname", + }, + }, + Matches: false, + }, + "all fields set matches": { + OurConsulNamespace: "consul-ns", + OurConsulPartition: "consul-partition", + OurData: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + Rules: []*pbmesh.GRPCRouteRule{ + { + Matches: []*pbmesh.GRPCRouteMatch{ + { + Method: &pbmesh.GRPCMethodMatch{ + Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, + Service: "test-service", + Method: "GET", + }, + Headers: []*pbmesh.GRPCHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + }, + }, + Filters: []*pbmesh.GRPCRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "condition-one", "condition-two", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + }, + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbmesh.GRPCRoute{ + Rules: []*pbmesh.GRPCRouteRule{ + { + Matches: []*pbmesh.GRPCRouteMatch{ + { + Method: &pbmesh.GRPCMethodMatch{ + Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, + Service: "test-service", + Method: "GET", + }, + Headers: []*pbmesh.GRPCHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + }, + }, + Filters: []*pbmesh.GRPCRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "condition-one", "condition-two", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + }, + }, + }, + Matches: true, + }, + "different types does not match": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: pbmesh.GRPCRoute{}, + }, + ResourceOverride: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "name", + Type: pbmesh.ProxyConfigurationType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.DefaultConsulNS, + Namespace: constants.DefaultConsulPartition, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + }, + Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), + Metadata: meshConfigMeta(), + }, + Matches: false, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + consulResource := c.ResourceOverride + if c.TheirName != "" { + consulResource = constructGRPCRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) + } + require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) + }) + } +} + +// TestGRPCRoute_Resource also includes test to verify ResourceID(). +func TestGRPCRoute_Resource(t *testing.T) { + cases := map[string]struct { + Ours *GRPCRoute + ConsulNamespace string + ConsulPartition string + ExpectedName string + ExpectedData *pbmesh.GRPCRoute + }{ + "empty fields": { + Ours: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: pbmesh.GRPCRoute{}, + }, + ConsulNamespace: constants.DefaultConsulNS, + ConsulPartition: constants.DefaultConsulPartition, + ExpectedName: "foo", + ExpectedData: &pbmesh.GRPCRoute{}, + }, + "every field set": { + Ours: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + Rules: []*pbmesh.GRPCRouteRule{ + { + Matches: []*pbmesh.GRPCRouteMatch{ + { + Method: &pbmesh.GRPCMethodMatch{ + Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, + Service: "test-service", + Method: "GET", + }, + Headers: []*pbmesh.GRPCHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + }, + }, + Filters: []*pbmesh.GRPCRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "condition-one", "condition-two", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + }, + }, + }, + }, + ConsulNamespace: "not-default-namespace", + ConsulPartition: "not-default-partition", + ExpectedName: "foo", + ExpectedData: &pbmesh.GRPCRoute{ + Rules: []*pbmesh.GRPCRouteRule{ + { + Matches: []*pbmesh.GRPCRouteMatch{ + { + Method: &pbmesh.GRPCMethodMatch{ + Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, + Service: "test-service", + Method: "GET", + }, + Headers: []*pbmesh.GRPCHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + }, + }, + Filters: []*pbmesh.GRPCRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "condition-one", "condition-two", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + }, + }, + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) + expected := constructGRPCRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) + + opts := append([]cmp.Option{ + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + }, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(expected, actual, opts...) + require.Equal(t, "", diff, "GRPCRoute do not match") + }) + } +} + +func TestGRPCRoute_SetSyncedCondition(t *testing.T) { + trafficPermissions := &GRPCRoute{} + trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") + + require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) + require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) + require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) + now := metav1.Now() + require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) +} + +func TestGRPCRoute_SetLastSyncedTime(t *testing.T) { + trafficPermissions := &GRPCRoute{} + syncedTime := metav1.NewTime(time.Now()) + trafficPermissions.SetLastSyncedTime(&syncedTime) + + require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) +} + +func TestGRPCRoute_GetSyncedConditionStatus(t *testing.T) { + cases := []corev1.ConditionStatus{ + corev1.ConditionUnknown, + corev1.ConditionFalse, + corev1.ConditionTrue, + } + for _, status := range cases { + t.Run(string(status), func(t *testing.T) { + trafficPermissions := &GRPCRoute{ + Status: Status{ + Conditions: []Condition{{ + Type: ConditionSynced, + Status: status, + }}, + }, + } + + require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) + }) + } +} + +func TestGRPCRoute_GetConditionWhenStatusNil(t *testing.T) { + require.Nil(t, (&GRPCRoute{}).GetCondition(ConditionSynced)) +} + +func TestGRPCRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { + require.Equal(t, corev1.ConditionUnknown, (&GRPCRoute{}).SyncedConditionStatus()) +} + +func TestGRPCRoute_SyncedConditionWhenStatusNil(t *testing.T) { + status, reason, message := (&GRPCRoute{}).SyncedCondition() + require.Equal(t, corev1.ConditionUnknown, status) + require.Equal(t, "", reason) + require.Equal(t, "", message) +} + +func TestGRPCRoute_KubeKind(t *testing.T) { + require.Equal(t, "grpcroute", (&GRPCRoute{}).KubeKind()) +} + +func TestGRPCRoute_KubernetesName(t *testing.T) { + require.Equal(t, "test", (&GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "bar", + }, + Spec: pbmesh.GRPCRoute{}, + }).KubernetesName()) +} + +func TestGRPCRoute_ObjectMeta(t *testing.T) { + meta := metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + } + trafficPermissions := &GRPCRoute{ + ObjectMeta: meta, + } + require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) +} + +// Test defaulting behavior when namespaces are enabled as well as disabled. +// TODO: add when implemented +//func TestGRPCRoute_DefaultNamespaceFields(t *testing.T) + +func TestGRPCRoute_Validate(t *testing.T) { + cases := []struct { + name string + input *GRPCRoute + expectedErrMsgs []string + }{ + { + name: "kitchen sink OK", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.GRPCRouteRule{ + { + Matches: []*pbmesh.GRPCRouteMatch{ + { + Method: &pbmesh.GRPCMethodMatch{ + Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, + Service: "test-service", + Method: "GET", + }, + Headers: []*pbmesh.GRPCHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + }, + }, + Filters: []*pbmesh.GRPCRouteFilter{ + { + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "5xx", "resource-exhausted", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + BackendRefs: []*pbmesh.GRPCBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "21000", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: nil, + }, + { + name: "empty parentRefs", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{}, + }, + }, + expectedErrMsgs: []string{ + `spec.parentRefs: Required value: cannot be empty`, + }, + }, + { + name: "populated hostnames", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{"a-hostname"}, + }, + }, + expectedErrMsgs: []string{ + `spec.hostnames: Invalid value: []string{"a-hostname"}: should not populate hostnames`, + }, + }, + { + name: "rules.matches.method", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.GRPCRouteRule{ + { + Matches: []*pbmesh.GRPCRouteMatch{ + { + Method: &pbmesh.GRPCMethodMatch{ + Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_UNSPECIFIED, + Service: "test-service", + Method: "GET", + }, + }, { + Method: &pbmesh.GRPCMethodMatch{ + Service: "test-service", + Method: "GET", + }, + }, { + Method: &pbmesh.GRPCMethodMatch{ + Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, + }, + }, + }, + BackendRefs: []*pbmesh.GRPCBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "21000", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].matches[0].method.type: Invalid value: GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: missing required field`, + `spec.rules[0].matches[1].method.type: Invalid value: GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: missing required field`, + `spec.rules[0].matches[2].method.service: Invalid value: "": at least one of "service" or "method" must be set`, + }, + }, + { + name: "rules.matches.headers", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.GRPCRouteRule{ + { + Matches: []*pbmesh.GRPCRouteMatch{ + { + Headers: []*pbmesh.GRPCHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED, + Name: "test-header", + Value: "header-value", + }, + { + Name: "test-header", + Value: "header-value", + }, + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Value: "header-value", + }, + }, + }, + }, + BackendRefs: []*pbmesh.GRPCBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "21000", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].matches[0].headers[0].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, + `spec.rules[0].matches[0].headers[1].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, + `spec.rules[0].matches[0].headers[2].name: Required value: missing required field`, + }, + }, + { + name: "rules.filters", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.GRPCRouteRule{ + { + Filters: []*pbmesh.GRPCRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "", + }, + }, + }, + BackendRefs: []*pbmesh.GRPCBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "21000", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].filters[0].urlRewrite.pathPrefix: Required value: field should not be empty if enclosing section is set`, + `exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required`, + }, + }, + { + name: "missing backendRefs", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.GRPCRouteRule{ + { + BackendRefs: []*pbmesh.GRPCBackendRef{}, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].backendRefs: Required value: missing required field`, + }, + }, + { + name: "rules.backendRefs", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.GRPCRouteRule{ + { + BackendRefs: []*pbmesh.GRPCBackendRef{ + { + Weight: 50, + }, + { + BackendRef: &pbmesh.BackendReference{ + Datacenter: "wrong-datacenter", + Port: "21000", + }, + Weight: 50, + }, + { + BackendRef: &pbmesh.BackendReference{ + Port: "21000", + }, + Filters: []*pbmesh.GRPCRouteFilter{{}}, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].backendRefs[0].backendRef: Required value: missing required field`, + `spec.rules[0].backendRefs[1].backendRef.datacenter: Invalid value: "wrong-datacenter": datacenter is not yet supported on backend refs`, + `filters are not supported at this level yet`, + }, + }, + { + name: "rules.timeout", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.GRPCRouteRule{ + { + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: -9, + Nanos: -10, + }, + Idle: &durationpb.Duration{ + Seconds: -2, + Nanos: -3, + }, + }, + BackendRefs: []*pbmesh.GRPCBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "21000", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].timeouts.request: Invalid value: -9.00000001s: timeout cannot be negative`, + `spec.rules[0].timeouts.idle: Invalid value: -2.000000003s: timeout cannot be negative`, + }, + }, + { + name: "rules.retries", + input: &GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "20020", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.GRPCRouteRule{ + { + Retries: &pbmesh.HTTPRouteRetries{ + OnConditions: []string{"invalid-condition", "another-invalid-condition", "internal"}, + }, + BackendRefs: []*pbmesh.GRPCBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "reference", + Section: "some-section", + }, + Port: "21000", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].retries.onConditions[0]: Invalid value: "invalid-condition": not a valid retry condition`, + `spec.rules[0].retries.onConditions[1]: Invalid value: "another-invalid-condition": not a valid retry condition`, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.input.Validate(common.ConsulTenancyConfig{}) + if len(tc.expectedErrMsgs) != 0 { + require.Error(t, err) + for _, s := range tc.expectedErrMsgs { + require.Contains(t, err.Error(), s) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func constructGRPCRouteResource(tp *pbmesh.GRPCRoute, name, namespace, partition string) *pbresource.Resource { + data := inject.ToProtoAny(tp) + + id := &pbresource.ID{ + Name: name, + Type: pbmesh.GRPCRouteType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + Uid: "ABCD", // We add this to show it does not factor into the comparison + } + + return &pbresource.Resource{ + Id: id, + Data: data, + Metadata: meshConfigMeta(), + + // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. + Version: "123456", + Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Status: map[string]*pbresource.Status{ + "knock": { + ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Conditions: make([]*pbresource.Condition, 0), + UpdatedAt: timestamppb.Now(), + }, + }, + } +} diff --git a/control-plane/api/mesh/v2beta1/grpc_route_webhook.go b/control-plane/api/mesh/v2beta1/grpc_route_webhook.go new file mode 100644 index 0000000000..15b4cbfc46 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/grpc_route_webhook.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "context" + "net/http" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +// +kubebuilder:object:generate=false + +type GRPCRouteWebhook struct { + Logger logr.Logger + + // ConsulTenancyConfig contains the injector's namespace and partition configuration. + ConsulTenancyConfig common.ConsulTenancyConfig + + decoder *admission.Decoder + client.Client +} + +var _ common.MeshConfigLister = &GRPCRouteWebhook{} + +// NOTE: The path value in the below line is the path to the webhook. +// If it is updated, run code-gen, update subcommand/inject-connect/command.go +// and the consul-helm value for the path to the webhook. +// +// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. +// +// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-grpcroute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=grpcroute,versions=v2beta1,name=mutate-grpcroute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *GRPCRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var resource GRPCRoute + err := v.decoder.Decode(req, &resource) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) +} + +func (v *GRPCRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { + var resourceList GRPCRouteList + if err := v.Client.List(ctx, &resourceList); err != nil { + return nil, err + } + var entries []common.MeshConfig + for _, item := range resourceList.Items { + entries = append(entries, common.MeshConfig(item)) + } + return entries, nil +} + +func (v *GRPCRouteWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} diff --git a/control-plane/api/mesh/v2beta1/http_route_types.go b/control-plane/api/mesh/v2beta1/http_route_types.go new file mode 100644 index 0000000000..7ea1b97936 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/http_route_types.go @@ -0,0 +1,309 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + "net/http" + "strings" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + httpRouteKubeKind = "httproute" +) + +func init() { + MeshSchemeBuilder.Register(&HTTPRoute{}, &HTTPRouteList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// HTTPRoute is the Schema for the HTTP Route API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="http-route" +type HTTPRoute struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.HTTPRoute `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// HTTPRouteList contains a list of HTTPRoute. +type HTTPRouteList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*HTTPRoute `json:"items"` +} + +func (in *HTTPRoute) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.HTTPRouteType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *HTTPRoute) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *HTTPRoute) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *HTTPRoute) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *HTTPRoute) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *HTTPRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *HTTPRoute) KubeKind() string { + return httpRouteKubeKind +} + +func (in *HTTPRoute) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *HTTPRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *HTTPRoute) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *HTTPRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *HTTPRoute) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *HTTPRoute) Validate(tenancy common.ConsulTenancyConfig) error { + var errs field.ErrorList + var route pbmesh.HTTPRoute + path := field.NewPath("spec") + + res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) + + if err := res.Data.UnmarshalTo(&route); err != nil { + return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) + } + + if len(route.ParentRefs) == 0 { + errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) + } + + if len(route.Hostnames) > 0 { + errs = append(errs, field.Invalid(path.Child("hostnames"), route.Hostnames, "should not populate hostnames")) + } + + for i, rule := range route.Rules { + rulePath := path.Child("rules").Index(i) + for j, match := range rule.Matches { + ruleMatchPath := rulePath.Child("matches").Index(j) + if match.Path != nil { + switch match.Path.Type { + case pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED: + errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("type"), pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED, "missing required field")) + case pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT: + if !strings.HasPrefix(match.Path.Value, "/") { + errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("value"), match.Path.Value, "exact patch value does not start with '/'")) + } + case pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX: + if !strings.HasPrefix(match.Path.Value, "/") { + errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("value"), match.Path.Value, "prefix patch value does not start with '/'")) + } + case pbmesh.PathMatchType_PATH_MATCH_TYPE_REGEX: + if match.Path.Value == "" { + errs = append(errs, field.Required(ruleMatchPath.Child("path").Child("value"), "missing required field")) + } + default: + errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("type"), match.Path, "not a supported enum value")) + } + } + + for k, hdr := range match.Headers { + if err := validateHeaderMatchType(hdr.Type); err != nil { + errs = append(errs, field.Invalid(ruleMatchPath.Child("headers").Index(k).Child("type"), hdr.Type, err.Error())) + } + + if hdr.Name == "" { + errs = append(errs, field.Required(ruleMatchPath.Child("headers").Index(k).Child("name"), "missing required field")) + } + } + + for k, qm := range match.QueryParams { + switch qm.Type { + case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_UNSPECIFIED: + errs = append(errs, field.Invalid(ruleMatchPath.Child("queryParams").Index(k).Child("type"), pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_UNSPECIFIED, "missing required field")) + case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_EXACT: + case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_REGEX: + case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT: + default: + errs = append(errs, field.Invalid(ruleMatchPath.Child("queryParams").Index(k).Child("type"), qm.Type, "not a supported enum value")) + } + + if qm.Name == "" { + errs = append(errs, field.Required(ruleMatchPath.Child("queryParams").Index(k).Child("name"), "missing required field")) + } + } + + if match.Method != "" && !isValidHTTPMethod(match.Method) { + errs = append(errs, field.Invalid(ruleMatchPath.Child("method"), match.Method, "not a valid http method")) + } + } + + var ( + hasReqMod bool + hasUrlRewrite bool + ) + for j, filter := range rule.Filters { + ruleFilterPath := path.Child("filters").Index(j) + set := 0 + if filter.RequestHeaderModifier != nil { + set++ + hasReqMod = true + } + if filter.ResponseHeaderModifier != nil { + set++ + } + if filter.UrlRewrite != nil { + set++ + hasUrlRewrite = true + if filter.UrlRewrite.PathPrefix == "" { + errs = append(errs, field.Invalid(ruleFilterPath.Child("urlRewrite").Child("pathPrefix"), filter.UrlRewrite.PathPrefix, "field should not be empty if enclosing section is set")) + } + } + if set != 1 { + errs = append(errs, field.Invalid(ruleFilterPath, filter, "exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required")) + } + } + + if hasReqMod && hasUrlRewrite { + errs = append(errs, field.Invalid(rulePath.Child("filters"), rule.Filters, "exactly one of request_header_modifier or url_rewrite can be set at a time")) + } + + if len(rule.BackendRefs) == 0 { + errs = append(errs, field.Required(rulePath.Child("backendRefs"), "missing required field")) + } + for j, hbref := range rule.BackendRefs { + ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) + if hbref.BackendRef == nil { + errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) + continue + } + + if hbref.BackendRef.Datacenter != "" { + errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) + } + + if len(hbref.Filters) > 0 { + errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("filters"), hbref.Filters, "filters are not supported at this level yet")) + } + } + + if rule.Timeouts != nil { + errs = append(errs, validateHTTPTimeouts(rule.Timeouts, rulePath.Child("timeouts"))...) + } + if rule.Retries != nil { + errs = append(errs, validateHTTPRetries(rule.Retries, rulePath.Child("retries"))...) + } + } + + if len(errs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: MeshGroup, Kind: common.HTTPRoute}, + in.KubernetesName(), errs) + } + return nil +} + +func isValidHTTPMethod(method string) bool { + switch method { + case http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + http.MethodConnect, + http.MethodOptions, + http.MethodTrace: + return true + default: + return false + } +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *HTTPRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/http_route_types_test.go b/control-plane/api/mesh/v2beta1/http_route_types_test.go new file mode 100644 index 0000000000..824d7e35b7 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/http_route_types_test.go @@ -0,0 +1,1338 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +func TestHTTPRoute_MatchesConsul(t *testing.T) { + cases := map[string]struct { + OurConsulNamespace string + OurConsulPartition string + OurData *HTTPRoute + + TheirName string + TheirConsulNamespace string + TheirConsulPartition string + TheirData *pbmesh.HTTPRoute + ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match + + Matches bool + }{ + "empty fields matches": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: pbmesh.HTTPRoute{}, + }, + TheirName: "name", + TheirConsulNamespace: constants.DefaultConsulNS, + TheirConsulPartition: constants.DefaultConsulPartition, + TheirData: &pbmesh.HTTPRoute{}, + Matches: true, + }, + "hostnames are compared": { + OurConsulNamespace: "consul-ns", + OurConsulPartition: "consul-partition", + OurData: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + Hostnames: []string{ + "a-hostname", "another-hostname", + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbmesh.HTTPRoute{ + Hostnames: []string{ + "not-a-hostname", "another-hostname", + }, + }, + Matches: false, + }, + "all fields set matches": { + OurConsulNamespace: "consul-ns", + OurConsulPartition: "consul-partition", + OurData: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{ + "a-hostname", "another-hostname", + }, + Rules: []*pbmesh.HTTPRouteRule{ + { + Matches: []*pbmesh.HTTPRouteMatch{ + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, + Value: "exact-value", + }, + Headers: []*pbmesh.HTTPHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + QueryParams: []*pbmesh.HTTPQueryParamMatch{ + { + Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, + Name: "query-param-name", + Value: "query-value", + }, + }, + Method: "GET", + }, + }, + Filters: []*pbmesh.HTTPRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "condition-one", "condition-two", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + BackendRefs: []*pbmesh.HTTPBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "backend-name", + Section: "backend-section", + }, + Port: "20211", + Datacenter: "another-datacenter", + }, + Weight: 12, + Filters: []*pbmesh.HTTPRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "setting", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "adding", + }, + }, + Remove: []string{"removing"}, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "another-set-header", + Value: "setting", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "another-added-header", + Value: "adding", + }, + }, + Remove: []string{"also-removing"}, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "/prefixing-it", + }, + }, + }, + }, + }, + }, + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{ + "a-hostname", "another-hostname", + }, + Rules: []*pbmesh.HTTPRouteRule{ + { + Matches: []*pbmesh.HTTPRouteMatch{ + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, + Value: "exact-value", + }, + Headers: []*pbmesh.HTTPHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + QueryParams: []*pbmesh.HTTPQueryParamMatch{ + { + Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, + Name: "query-param-name", + Value: "query-value", + }, + }, + Method: "GET", + }, + }, + Filters: []*pbmesh.HTTPRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "condition-one", "condition-two", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + BackendRefs: []*pbmesh.HTTPBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "backend-name", + Section: "backend-section", + }, + Port: "20211", + Datacenter: "another-datacenter", + }, + Weight: 12, + Filters: []*pbmesh.HTTPRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "setting", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "adding", + }, + }, + Remove: []string{"removing"}, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "another-set-header", + Value: "setting", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "another-added-header", + Value: "adding", + }, + }, + Remove: []string{"also-removing"}, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "/prefixing-it", + }, + }, + }, + }, + }, + }, + }, + }, + Matches: true, + }, + "different types does not match": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: pbmesh.HTTPRoute{}, + }, + ResourceOverride: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "name", + Type: pbmesh.ProxyConfigurationType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.DefaultConsulNS, + Namespace: constants.DefaultConsulPartition, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + }, + Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), + Metadata: meshConfigMeta(), + }, + Matches: false, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + consulResource := c.ResourceOverride + if c.TheirName != "" { + consulResource = constructHTTPRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) + } + require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) + }) + } +} + +// TestHTTPRoute_Resource also includes test to verify ResourceID(). +func TestHTTPRoute_Resource(t *testing.T) { + cases := map[string]struct { + Ours *HTTPRoute + ConsulNamespace string + ConsulPartition string + ExpectedName string + ExpectedData *pbmesh.HTTPRoute + }{ + "empty fields": { + Ours: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: pbmesh.HTTPRoute{}, + }, + ConsulNamespace: constants.DefaultConsulNS, + ConsulPartition: constants.DefaultConsulPartition, + ExpectedName: "foo", + ExpectedData: &pbmesh.HTTPRoute{}, + }, + "every field set": { + Ours: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{ + "a-hostname", "another-hostname", + }, + Rules: []*pbmesh.HTTPRouteRule{ + { + Matches: []*pbmesh.HTTPRouteMatch{ + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, + Value: "exact-value", + }, + Headers: []*pbmesh.HTTPHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + QueryParams: []*pbmesh.HTTPQueryParamMatch{ + { + Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, + Name: "query-param-name", + Value: "query-value", + }, + }, + Method: "GET", + }, + }, + Filters: []*pbmesh.HTTPRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "condition-one", "condition-two", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + }, + }, + }, + }, + ConsulNamespace: "not-default-namespace", + ConsulPartition: "not-default-partition", + ExpectedName: "foo", + ExpectedData: &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{ + "a-hostname", "another-hostname", + }, + Rules: []*pbmesh.HTTPRouteRule{ + { + Matches: []*pbmesh.HTTPRouteMatch{ + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, + Value: "exact-value", + }, + Headers: []*pbmesh.HTTPHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + QueryParams: []*pbmesh.HTTPQueryParamMatch{ + { + Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, + Name: "query-param-name", + Value: "query-value", + }, + }, + Method: "GET", + }, + }, + Filters: []*pbmesh.HTTPRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ + Set: []*pbmesh.HTTPHeader{ + { + Name: "set-header", + Value: "a-header-value", + }, + }, + Add: []*pbmesh.HTTPHeader{ + { + Name: "added-header", + Value: "another-header-value", + }, + }, + Remove: []string{ + "remove-header", + }, + }, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "condition-one", "condition-two", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + }, + }, + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) + expected := constructHTTPRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) + + opts := append([]cmp.Option{ + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + }, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(expected, actual, opts...) + require.Equal(t, "", diff, "HTTPRoute do not match") + }) + } +} + +func TestHTTPRoute_SetSyncedCondition(t *testing.T) { + trafficPermissions := &HTTPRoute{} + trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") + + require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) + require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) + require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) + now := metav1.Now() + require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) +} + +func TestHTTPRoute_SetLastSyncedTime(t *testing.T) { + trafficPermissions := &HTTPRoute{} + syncedTime := metav1.NewTime(time.Now()) + trafficPermissions.SetLastSyncedTime(&syncedTime) + + require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) +} + +func TestHTTPRoute_GetSyncedConditionStatus(t *testing.T) { + cases := []corev1.ConditionStatus{ + corev1.ConditionUnknown, + corev1.ConditionFalse, + corev1.ConditionTrue, + } + for _, status := range cases { + t.Run(string(status), func(t *testing.T) { + trafficPermissions := &HTTPRoute{ + Status: Status{ + Conditions: []Condition{{ + Type: ConditionSynced, + Status: status, + }}, + }, + } + + require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) + }) + } +} + +func TestHTTPRoute_GetConditionWhenStatusNil(t *testing.T) { + require.Nil(t, (&HTTPRoute{}).GetCondition(ConditionSynced)) +} + +func TestHTTPRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { + require.Equal(t, corev1.ConditionUnknown, (&HTTPRoute{}).SyncedConditionStatus()) +} + +func TestHTTPRoute_SyncedConditionWhenStatusNil(t *testing.T) { + status, reason, message := (&HTTPRoute{}).SyncedCondition() + require.Equal(t, corev1.ConditionUnknown, status) + require.Equal(t, "", reason) + require.Equal(t, "", message) +} + +func TestHTTPRoute_KubeKind(t *testing.T) { + require.Equal(t, "httproute", (&HTTPRoute{}).KubeKind()) +} + +func TestHTTPRoute_KubernetesName(t *testing.T) { + require.Equal(t, "test", (&HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "bar", + }, + Spec: pbmesh.HTTPRoute{}, + }).KubernetesName()) +} + +func TestHTTPRoute_ObjectMeta(t *testing.T) { + meta := metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + } + trafficPermissions := &HTTPRoute{ + ObjectMeta: meta, + } + require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) +} + +// Test defaulting behavior when namespaces are enabled as well as disabled. +// TODO: add when implemented +//func TestHTTPRoute_DefaultNamespaceFields(t *testing.T) + +func TestHTTPRoute_Validate(t *testing.T) { + cases := []struct { + name string + input *HTTPRoute + expectedErrMsgs []string + }{ + { + name: "kitchen sink OK", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.HTTPRouteRule{ + { + Matches: []*pbmesh.HTTPRouteMatch{ + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, + Value: "/exactValue", + }, + Headers: []*pbmesh.HTTPHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, + Name: "test-header", + Value: "header-value", + }, + }, + QueryParams: []*pbmesh.HTTPQueryParamMatch{ + { + Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, + Name: "query-param-name", + Value: "query-value", + }, + }, + Method: "GET", + }, + }, + Filters: []*pbmesh.HTTPRouteFilter{ + { + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "a-path-prefix", + }, + }, + }, + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: 10, + Nanos: 5, + }, + Idle: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + }, + Retries: &pbmesh.HTTPRouteRetries{ + Number: &wrapperspb.UInt32Value{ + Value: 1, + }, + OnConnectFailure: false, + OnConditions: []string{ + "reset", "cancelled", + }, + OnStatusCodes: []uint32{ + 200, 201, 202, + }, + }, + BackendRefs: []*pbmesh.HTTPBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "backend", + Section: "backend-section", + }, + Port: "20101", + }, + Weight: 15, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: nil, + }, + { + name: "missing parentRefs", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{}, + }, + }, + expectedErrMsgs: []string{ + `spec.parentRefs: Required value: cannot be empty`, + }, + }, + { + name: "hostnames created", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{"a-hostname", "another-hostname"}, + }, + }, + expectedErrMsgs: []string{ + `spec.hostnames: Invalid value: []string{"a-hostname", "another-hostname"}: should not populate hostnames`, + }, + }, + { + name: "rules.matches.path", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.HTTPRouteRule{ + { + Matches: []*pbmesh.HTTPRouteMatch{ + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED, + }, + }, + { + Path: &pbmesh.HTTPPathMatch{}, + }, + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, + Value: "does-not-have-/-prefix", + }, + }, + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "does-not-have-/-prefix-either", + }, + }, + { + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_REGEX, + Value: "", + }, + }, + }, + BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].matches[0].path.type: Invalid value: PATH_MATCH_TYPE_UNSPECIFIED: missing required field`, + `spec.rules[0].matches[1].path.type: Invalid value: PATH_MATCH_TYPE_UNSPECIFIED: missing required field`, + `spec.rules[0].matches[2].path.value: Invalid value: "does-not-have-/-prefix": exact patch value does not start with '/'`, + `spec.rules[0].matches[3].path.value: Invalid value: "does-not-have-/-prefix-either": prefix patch value does not start with '/'`, + `spec.rules[0].matches[4].path.value: Required value: missing required field`, + }, + }, + { + name: "rules.matches.headers", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.HTTPRouteRule{ + { + Matches: []*pbmesh.HTTPRouteMatch{ + { + Headers: []*pbmesh.HTTPHeaderMatch{ + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED, + Name: "test-header", + Value: "header-value", + }, + { + // Type: "", + Name: "test-header", + Value: "header-value", + }, + { + Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_EXACT, + Name: "", + }, + }, + Method: "GET", + }, + }, + BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].matches[0].headers[0].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, + `spec.rules[0].matches[0].headers[1].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, + `spec.rules[0].matches[0].headers[2].name: Required value: missing required field`, + }, + }, + { + name: "rules.filters", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.HTTPRouteRule{ + { + Filters: []*pbmesh.HTTPRouteFilter{ + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, + }, + { + RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "prefix-1", + }, + }, + { + ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "prefix-2", + }, + }, + { + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "", + }, + }, + }, + BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.filters[0]: Invalid value`, + `spec.filters[1]: Invalid value`, + `spec.filters[2]: Invalid value`, + `spec.filters[3].urlRewrite.pathPrefix: Invalid value: "": field should not be empty if enclosing section is set`, + `exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required`, + }, + }, + { + name: "rule.backendRefs", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.HTTPRouteRule{ + { + BackendRefs: []*pbmesh.HTTPBackendRef{}, + }, + { + BackendRefs: []*pbmesh.HTTPBackendRef{ + {}, + { + BackendRef: &pbmesh.BackendReference{ + Datacenter: "some-datacenter", + }, + }, + { + BackendRef: &pbmesh.BackendReference{}, + Filters: []*pbmesh.HTTPRouteFilter{ + { + UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ + PathPrefix: "/prefixed", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].backendRefs: Required value: missing required field`, + `spec.rules[1].backendRefs[0].backendRef: Required value: missing required field`, + `spec.rules[1].backendRefs[1].backendRef.datacenter: Invalid value: "some-datacenter": datacenter is not yet supported on backend refs`, + `spec.rules[1].backendRefs[2].filters: Invalid value`, + `filters are not supported at this level yet`, + }, + }, + { + name: "rules.timeouts", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.HTTPRouteRule{ + { + Timeouts: &pbmesh.HTTPRouteTimeouts{ + Request: &durationpb.Duration{ + Seconds: -10, + Nanos: -5, + }, + Idle: &durationpb.Duration{ + Seconds: -5, + Nanos: -10, + }, + }, + BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].timeouts.request: Invalid value: -10.000000005s: timeout cannot be negative`, + `spec.rules[0].timeouts.idle: Invalid value: -5.00000001s: timeout cannot be negative`, + }, + }, + { + name: "rules.timeouts", + input: &HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "a-partition", + Namespace: "a-namespace", + }, + Name: "reference-name", + Section: "section-name", + }, + Port: "20201", + }, + }, + Hostnames: []string{}, + Rules: []*pbmesh.HTTPRouteRule{ + { + Retries: &pbmesh.HTTPRouteRetries{ + OnConditions: []string{ + "invalid-condition", "another-invalid-condition", + }, + }, + BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].retries.onConditions[0]: Invalid value: "invalid-condition": not a valid retry condition`, + `spec.rules[0].retries.onConditions[1]: Invalid value: "another-invalid-condition": not a valid retry condition`, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.input.Validate(common.ConsulTenancyConfig{}) + if len(tc.expectedErrMsgs) != 0 { + require.Error(t, err) + for _, s := range tc.expectedErrMsgs { + require.Contains(t, err.Error(), s) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func constructHTTPRouteResource(tp *pbmesh.HTTPRoute, name, namespace, partition string) *pbresource.Resource { + data := inject.ToProtoAny(tp) + + id := &pbresource.ID{ + Name: name, + Type: pbmesh.HTTPRouteType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + Uid: "ABCD", // We add this to show it does not factor into the comparison + } + + return &pbresource.Resource{ + Id: id, + Data: data, + Metadata: meshConfigMeta(), + + // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. + Version: "123456", + Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Status: map[string]*pbresource.Status{ + "knock": { + ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Conditions: make([]*pbresource.Condition, 0), + UpdatedAt: timestamppb.Now(), + }, + }, + } +} diff --git a/control-plane/api/mesh/v2beta1/http_route_webhook.go b/control-plane/api/mesh/v2beta1/http_route_webhook.go new file mode 100644 index 0000000000..e16db17458 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/http_route_webhook.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "context" + "net/http" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +// +kubebuilder:object:generate=false + +type HTTPRouteWebhook struct { + Logger logr.Logger + + // ConsulTenancyConfig contains the injector's namespace and partition configuration. + ConsulTenancyConfig common.ConsulTenancyConfig + + decoder *admission.Decoder + client.Client +} + +var _ common.MeshConfigLister = &HTTPRouteWebhook{} + +// NOTE: The path value in the below line is the path to the webhook. +// If it is updated, run code-gen, update subcommand/inject-connect/command.go +// and the consul-helm value for the path to the webhook. +// +// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. +// +// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-httproute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=httproute,versions=v2beta1,name=mutate-httproute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *HTTPRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var resource HTTPRoute + err := v.decoder.Decode(req, &resource) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) +} + +func (v *HTTPRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { + var resourceList HTTPRouteList + if err := v.Client.List(ctx, &resourceList); err != nil { + return nil, err + } + var entries []common.MeshConfig + for _, item := range resourceList.Items { + entries = append(entries, common.MeshConfig(item)) + } + return entries, nil +} + +func (v *HTTPRouteWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} diff --git a/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go b/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go new file mode 100644 index 0000000000..a9fe6a6a83 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package v2beta1 contains API Schema definitions for the consul.hashicorp.com v2beta1 API group +// +kubebuilder:object:generate=true +// +groupName=mesh.consul.hashicorp.com +package v2beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + + // MeshGroup is a collection of mesh resources. + MeshGroup = "mesh.consul.hashicorp.com" + + // MeshGroupVersion is group version used to register these objects. + MeshGroupVersion = schema.GroupVersion{Group: MeshGroup, Version: "v2beta1"} + + // MeshSchemeBuilder is used to add go types to the GroupVersionKind scheme. + MeshSchemeBuilder = &scheme.Builder{GroupVersion: MeshGroupVersion} + + // AddMeshToScheme adds the types in this group-version to the given scheme. + AddMeshToScheme = MeshSchemeBuilder.AddToScheme +) diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go b/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go new file mode 100644 index 0000000000..711d8c81bc --- /dev/null +++ b/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "context" + "net/http" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +// +kubebuilder:object:generate=false + +type ProxyConfigurationWebhook struct { + Logger logr.Logger + + // ConsulTenancyConfig contains the injector's namespace and partition configuration. + ConsulTenancyConfig common.ConsulTenancyConfig + + decoder *admission.Decoder + client.Client +} + +var _ common.MeshConfigLister = &ProxyConfigurationWebhook{} + +// NOTE: The path value in the below line is the path to the webhook. +// If it is updated, run code-gen, update subcommand/inject-connect/command.go +// and the consul-helm value for the path to the webhook. +// +// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. +// +// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-proxyconfiguration,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=proxyconfiguration,versions=v2beta1,name=mutate-proxyconfiguration.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *ProxyConfigurationWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var resource ProxyConfiguration + err := v.decoder.Decode(req, &resource) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) +} + +func (v *ProxyConfigurationWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { + var resourceList ProxyConfigurationList + if err := v.Client.List(ctx, &resourceList); err != nil { + return nil, err + } + var entries []common.MeshConfig + for _, item := range resourceList.Items { + entries = append(entries, common.MeshConfig(item)) + } + return entries, nil +} + +func (v *ProxyConfigurationWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_types.go b/control-plane/api/mesh/v2beta1/proxy_configuration_types.go new file mode 100644 index 0000000000..4df6ff95e1 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/proxy_configuration_types.go @@ -0,0 +1,160 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + proxyConfigurationKubeKind = "proxyconfiguration" +) + +func init() { + MeshSchemeBuilder.Register(&ProxyConfiguration{}, &ProxyConfigurationList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// ProxyConfiguration is the Schema for the TCP Routes API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="proxy-configuration" +type ProxyConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.ProxyConfiguration `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ProxyConfigurationList contains a list of ProxyConfiguration. +type ProxyConfigurationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*ProxyConfiguration `json:"items"` +} + +func (in *ProxyConfiguration) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.ProxyConfigurationType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *ProxyConfiguration) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *ProxyConfiguration) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *ProxyConfiguration) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *ProxyConfiguration) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *ProxyConfiguration) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *ProxyConfiguration) KubeKind() string { + return proxyConfigurationKubeKind +} + +func (in *ProxyConfiguration) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *ProxyConfiguration) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *ProxyConfiguration) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *ProxyConfiguration) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *ProxyConfiguration) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *ProxyConfiguration) Validate(_ common.ConsulTenancyConfig) error { + var errs field.ErrorList + if len(errs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: MeshGroup, Kind: common.ProxyConfiguration}, + in.KubernetesName(), errs) + } + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *ProxyConfiguration) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go b/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go new file mode 100644 index 0000000000..5e432175f4 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go @@ -0,0 +1,608 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +func TestProxyConfiguration_MatchesConsul(t *testing.T) { + cases := map[string]struct { + OurConsulNamespace string + OurConsulPartition string + OurData *ProxyConfiguration + + TheirName string + TheirConsulNamespace string + TheirConsulPartition string + TheirData *pbmesh.ProxyConfiguration + ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match + + Matches bool + }{ + "empty fields matches": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: &ProxyConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: pbmesh.ProxyConfiguration{}, + }, + TheirName: "name", + TheirConsulNamespace: constants.DefaultConsulNS, + TheirConsulPartition: constants.DefaultConsulPartition, + TheirData: &pbmesh.ProxyConfiguration{}, + Matches: true, + }, + "all fields set matches": { + OurConsulNamespace: "consul-ns", + OurConsulPartition: "consul-partition", + OurData: &ProxyConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"prefix-1", "prefix-2"}, + Names: []string{"workload-name"}, + Filter: "first-filter", + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: 2, + TransparentProxy: &pbmesh.TransparentProxy{ + OutboundListenerPort: 1234, + DialedDirectly: true, + }, + MutualTlsMode: 1, + LocalConnection: map[string]*pbmesh.ConnectionConfig{ + "local": { + ConnectTimeout: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + RequestTimeout: &durationpb.Duration{ + Seconds: 2, + Nanos: 10, + }, + }, + }, + InboundConnections: &pbmesh.InboundConnectionsConfig{ + MaxInboundConnections: 5, + BalanceInboundConnections: 10, + }, + MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 19000, + Path: "/expose-path", + LocalPathPort: 1901, + Protocol: 2, + }, + }, + }, + AccessLogs: &pbmesh.AccessLogsConfig{ + Enabled: true, + DisableListenerLogs: true, + Type: 3, + Path: "/path", + JsonFormat: "jsonFormat", + TextFormat: "text format.", + }, + EnvoyExtensions: []*pbmesh.EnvoyExtension{ + { + Name: "extension-1", + Required: true, + Arguments: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "field-1": {}, + "field-2": {}, + }, + }, + ConsulVersion: "1.22.3-beta1", + EnvoyVersion: "1.33.4", + }, + }, + PublicListenerJson: "publicListenerJson{}", + ListenerTracingJson: "listenerTracingJson{}", + LocalClusterJson: "localClusterJson{}", + }, + BootstrapConfig: &pbmesh.BootstrapConfig{ + StatsdUrl: "statsdURL", + DogstatsdUrl: "dogstatsdURL", + StatsTags: []string{"statsTags"}, + PrometheusBindAddr: "firstBindAddr", + StatsBindAddr: "secondBindAddr", + ReadyBindAddr: "thirdBindAddr", + OverrideJsonTpl: "overrideJSON", + StaticClustersJson: "staticClusterJSON", + StaticListenersJson: "staticListenersJSON", + StatsSinksJson: "statsSinksJSON", + StatsConfigJson: "statsConfigJSON", + StatsFlushInterval: "45s", + TracingConfigJson: "tracingConfigJSON", + TelemetryCollectorBindSocketDir: "/bindSocketDir", + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"prefix-1", "prefix-2"}, + Names: []string{"workload-name"}, + Filter: "first-filter", + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: 2, + TransparentProxy: &pbmesh.TransparentProxy{ + OutboundListenerPort: 1234, + DialedDirectly: true, + }, + MutualTlsMode: 1, + LocalConnection: map[string]*pbmesh.ConnectionConfig{ + "local": { + ConnectTimeout: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + RequestTimeout: &durationpb.Duration{ + Seconds: 2, + Nanos: 10, + }, + }, + }, + InboundConnections: &pbmesh.InboundConnectionsConfig{ + MaxInboundConnections: 5, + BalanceInboundConnections: 10, + }, + MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 19000, + Path: "/expose-path", + LocalPathPort: 1901, + Protocol: 2, + }, + }, + }, + AccessLogs: &pbmesh.AccessLogsConfig{ + Enabled: true, + DisableListenerLogs: true, + Type: 3, + Path: "/path", + JsonFormat: "jsonFormat", + TextFormat: "text format.", + }, + EnvoyExtensions: []*pbmesh.EnvoyExtension{ + { + Name: "extension-1", + Required: true, + Arguments: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "field-1": {}, + "field-2": {}, + }, + }, + ConsulVersion: "1.22.3-beta1", + EnvoyVersion: "1.33.4", + }, + }, + PublicListenerJson: "publicListenerJson{}", + ListenerTracingJson: "listenerTracingJson{}", + LocalClusterJson: "localClusterJson{}", + }, + BootstrapConfig: &pbmesh.BootstrapConfig{ + StatsdUrl: "statsdURL", + DogstatsdUrl: "dogstatsdURL", + StatsTags: []string{"statsTags"}, + PrometheusBindAddr: "firstBindAddr", + StatsBindAddr: "secondBindAddr", + ReadyBindAddr: "thirdBindAddr", + OverrideJsonTpl: "overrideJSON", + StaticClustersJson: "staticClusterJSON", + StaticListenersJson: "staticListenersJSON", + StatsSinksJson: "statsSinksJSON", + StatsConfigJson: "statsConfigJSON", + StatsFlushInterval: "45s", + TracingConfigJson: "tracingConfigJSON", + TelemetryCollectorBindSocketDir: "/bindSocketDir", + }, + }, + Matches: true, + }, + "different types does not match": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: &ProxyConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: pbmesh.ProxyConfiguration{}, + }, + ResourceOverride: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "name", + Type: pbmesh.TCPRouteType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.DefaultConsulNS, + Namespace: constants.DefaultConsulPartition, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + }, + Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), + Metadata: meshConfigMeta(), + }, + Matches: false, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + consulResource := c.ResourceOverride + if c.TheirName != "" { + consulResource = constructProxyConfigurationResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) + } + require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) + }) + } +} + +// TestProxyConfiguration_Resource also includes test to verify ResourceID(). +func TestProxyConfiguration_Resource(t *testing.T) { + cases := map[string]struct { + Ours *ProxyConfiguration + ConsulNamespace string + ConsulPartition string + ExpectedName string + ExpectedData *pbmesh.ProxyConfiguration + }{ + "empty fields": { + Ours: &ProxyConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: pbmesh.ProxyConfiguration{}, + }, + ConsulNamespace: constants.DefaultConsulNS, + ConsulPartition: constants.DefaultConsulPartition, + ExpectedName: "foo", + ExpectedData: &pbmesh.ProxyConfiguration{}, + }, + "every field set": { + Ours: &ProxyConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"prefix-1", "prefix-2"}, + Names: []string{"workload-name"}, + Filter: "first-filter", + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: 2, + TransparentProxy: &pbmesh.TransparentProxy{ + OutboundListenerPort: 1234, + DialedDirectly: true, + }, + MutualTlsMode: 1, + LocalConnection: map[string]*pbmesh.ConnectionConfig{ + "local": { + ConnectTimeout: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + RequestTimeout: &durationpb.Duration{ + Seconds: 2, + Nanos: 10, + }, + }, + }, + InboundConnections: &pbmesh.InboundConnectionsConfig{ + MaxInboundConnections: 5, + BalanceInboundConnections: 10, + }, + MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 19000, + Path: "/expose-path", + LocalPathPort: 1901, + Protocol: 2, + }, + }, + }, + AccessLogs: &pbmesh.AccessLogsConfig{ + Enabled: true, + DisableListenerLogs: true, + Type: 3, + Path: "/path", + JsonFormat: "jsonFormat", + TextFormat: "text format.", + }, + EnvoyExtensions: []*pbmesh.EnvoyExtension{ + { + Name: "extension-1", + Required: true, + Arguments: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "field-1": {}, + "field-2": {}, + }, + }, + ConsulVersion: "1.22.3-beta1", + EnvoyVersion: "1.33.4", + }, + }, + PublicListenerJson: "publicListenerJson{}", + ListenerTracingJson: "listenerTracingJson{}", + LocalClusterJson: "localClusterJson{}", + }, + BootstrapConfig: &pbmesh.BootstrapConfig{ + StatsdUrl: "statsdURL", + DogstatsdUrl: "dogstatsdURL", + StatsTags: []string{"statsTags"}, + PrometheusBindAddr: "firstBindAddr", + StatsBindAddr: "secondBindAddr", + ReadyBindAddr: "thirdBindAddr", + OverrideJsonTpl: "overrideJSON", + StaticClustersJson: "staticClusterJSON", + StaticListenersJson: "staticListenersJSON", + StatsSinksJson: "statsSinksJSON", + StatsConfigJson: "statsConfigJSON", + StatsFlushInterval: "45s", + TracingConfigJson: "tracingConfigJSON", + TelemetryCollectorBindSocketDir: "/bindSocketDir", + }, + }, + }, + ConsulNamespace: "not-default-namespace", + ConsulPartition: "not-default-partition", + ExpectedName: "foo", + ExpectedData: &pbmesh.ProxyConfiguration{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"prefix-1", "prefix-2"}, + Names: []string{"workload-name"}, + Filter: "first-filter", + }, + DynamicConfig: &pbmesh.DynamicConfig{ + Mode: 2, + TransparentProxy: &pbmesh.TransparentProxy{ + OutboundListenerPort: 1234, + DialedDirectly: true, + }, + MutualTlsMode: 1, + LocalConnection: map[string]*pbmesh.ConnectionConfig{ + "local": { + ConnectTimeout: &durationpb.Duration{ + Seconds: 5, + Nanos: 10, + }, + RequestTimeout: &durationpb.Duration{ + Seconds: 2, + Nanos: 10, + }, + }, + }, + InboundConnections: &pbmesh.InboundConnectionsConfig{ + MaxInboundConnections: 5, + BalanceInboundConnections: 10, + }, + MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, + ExposeConfig: &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 19000, + Path: "/expose-path", + LocalPathPort: 1901, + Protocol: 2, + }, + }, + }, + AccessLogs: &pbmesh.AccessLogsConfig{ + Enabled: true, + DisableListenerLogs: true, + Type: 3, + Path: "/path", + JsonFormat: "jsonFormat", + TextFormat: "text format.", + }, + EnvoyExtensions: []*pbmesh.EnvoyExtension{ + { + Name: "extension-1", + Required: true, + Arguments: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "field-1": {}, + "field-2": {}, + }, + }, + ConsulVersion: "1.22.3-beta1", + EnvoyVersion: "1.33.4", + }, + }, + PublicListenerJson: "publicListenerJson{}", + ListenerTracingJson: "listenerTracingJson{}", + LocalClusterJson: "localClusterJson{}", + }, + BootstrapConfig: &pbmesh.BootstrapConfig{ + StatsdUrl: "statsdURL", + DogstatsdUrl: "dogstatsdURL", + StatsTags: []string{"statsTags"}, + PrometheusBindAddr: "firstBindAddr", + StatsBindAddr: "secondBindAddr", + ReadyBindAddr: "thirdBindAddr", + OverrideJsonTpl: "overrideJSON", + StaticClustersJson: "staticClusterJSON", + StaticListenersJson: "staticListenersJSON", + StatsSinksJson: "statsSinksJSON", + StatsConfigJson: "statsConfigJSON", + StatsFlushInterval: "45s", + TracingConfigJson: "tracingConfigJSON", + TelemetryCollectorBindSocketDir: "/bindSocketDir", + }, + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) + expected := constructProxyConfigurationResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) + + opts := append([]cmp.Option{ + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + }, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(expected, actual, opts...) + require.Equal(t, "", diff, "ProxyConfiguration do not match") + }) + } +} + +func TestProxyConfiguration_SetSyncedCondition(t *testing.T) { + trafficPermissions := &ProxyConfiguration{} + trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") + + require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) + require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) + require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) + now := metav1.Now() + require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) + require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) +} + +func TestProxyConfiguration_SetLastSyncedTime(t *testing.T) { + trafficPermissions := &ProxyConfiguration{} + syncedTime := metav1.NewTime(time.Now()) + trafficPermissions.SetLastSyncedTime(&syncedTime) + + require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) +} + +func TestProxyConfiguration_GetSyncedConditionStatus(t *testing.T) { + cases := []corev1.ConditionStatus{ + corev1.ConditionUnknown, + corev1.ConditionFalse, + corev1.ConditionTrue, + } + for _, status := range cases { + t.Run(string(status), func(t *testing.T) { + trafficPermissions := &ProxyConfiguration{ + Status: Status{ + Conditions: []Condition{{ + Type: ConditionSynced, + Status: status, + }}, + }, + } + + require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) + }) + } +} + +func TestProxyConfiguration_GetConditionWhenStatusNil(t *testing.T) { + require.Nil(t, (&ProxyConfiguration{}).GetCondition(ConditionSynced)) +} + +func TestProxyConfiguration_SyncedConditionStatusWhenStatusNil(t *testing.T) { + require.Equal(t, corev1.ConditionUnknown, (&ProxyConfiguration{}).SyncedConditionStatus()) +} + +func TestProxyConfiguration_SyncedConditionWhenStatusNil(t *testing.T) { + status, reason, message := (&ProxyConfiguration{}).SyncedCondition() + require.Equal(t, corev1.ConditionUnknown, status) + require.Equal(t, "", reason) + require.Equal(t, "", message) +} + +func TestProxyConfiguration_KubeKind(t *testing.T) { + require.Equal(t, "proxyconfiguration", (&ProxyConfiguration{}).KubeKind()) +} + +func TestProxyConfiguration_KubernetesName(t *testing.T) { + require.Equal(t, "test", (&ProxyConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "bar", + }, + Spec: pbmesh.ProxyConfiguration{}, + }).KubernetesName()) +} + +func TestProxyConfiguration_ObjectMeta(t *testing.T) { + meta := metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + } + trafficPermissions := &ProxyConfiguration{ + ObjectMeta: meta, + } + require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) +} + +// Test defaulting behavior when namespaces are enabled as well as disabled. +// TODO: add when implemented +//func TestProxyConfiguration_DefaultNamespaceFields(t *testing.T) + +func constructProxyConfigurationResource(tp *pbmesh.ProxyConfiguration, name, namespace, partition string) *pbresource.Resource { + data := inject.ToProtoAny(tp) + + id := &pbresource.ID{ + Name: name, + Type: pbmesh.ProxyConfigurationType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + Uid: "ABCD", // We add this to show it does not factor into the comparison + } + + return &pbresource.Resource{ + Id: id, + Data: data, + Metadata: meshConfigMeta(), + + // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. + Version: "123456", + Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Status: map[string]*pbresource.Status{ + "knock": { + ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Conditions: make([]*pbresource.Condition, 0), + UpdatedAt: timestamppb.Now(), + }, + }, + } +} diff --git a/control-plane/api/mesh/v2beta1/shared_types.go b/control-plane/api/mesh/v2beta1/shared_types.go new file mode 100644 index 0000000000..a5225afb71 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/shared_types.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +func meshConfigMeta() map[string]string { + return map[string]string{ + common.SourceKey: common.SourceValue, + } +} diff --git a/control-plane/api/mesh/v2beta1/status.go b/control-plane/api/mesh/v2beta1/status.go new file mode 100644 index 0000000000..cc75a1cd82 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/status.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Conditions is the schema for the conditions portion of the payload. +type Conditions []Condition + +// ConditionType is a camel-cased condition type. +type ConditionType string + +const ( + // ConditionSynced specifies that the resource has been synced with Consul. + ConditionSynced ConditionType = "Synced" +) + +// Conditions define a readiness condition for a Consul resource. +// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Condition struct { + // Type of condition. + // +required + Type ConditionType `json:"type" description:"type of status condition"` + + // Status of the condition, one of True, False, Unknown. + // +required + Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` + + // LastTransitionTime is the last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"` + + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` + + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` +} + +// IsTrue is true if the condition is True. +func (c *Condition) IsTrue() bool { + if c == nil { + return false + } + return c.Status == corev1.ConditionTrue +} + +// IsFalse is true if the condition is False. +func (c *Condition) IsFalse() bool { + if c == nil { + return false + } + return c.Status == corev1.ConditionFalse +} + +// IsUnknown is true if the condition is Unknown. +func (c *Condition) IsUnknown() bool { + if c == nil { + return true + } + return c.Status == corev1.ConditionUnknown +} + +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Status struct { + // Conditions indicate the latest available observations of a resource's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // LastSyncedTime is the last time the resource successfully synced with Consul. + // +optional + LastSyncedTime *metav1.Time `json:"lastSyncedTime,omitempty" description:"last time the condition transitioned from one status to another"` +} + +func (s *Status) GetCondition(t ConditionType) *Condition { + for _, cond := range s.Conditions { + if cond.Type == t { + return &cond + } + } + return nil +} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_types.go b/control-plane/api/mesh/v2beta1/tcp_route_types.go new file mode 100644 index 0000000000..207ef7afb3 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/tcp_route_types.go @@ -0,0 +1,195 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + tcpRouteKubeKind = "tcproute" +) + +func init() { + MeshSchemeBuilder.Register(&TCPRoute{}, &TCPRouteList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// TCPRoute is the Schema for the TCP Route API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="tcp-route" +type TCPRoute struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.TCPRoute `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// TCPRouteList contains a list of TCPRoute. +type TCPRouteList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*TCPRoute `json:"items"` +} + +func (in *TCPRoute) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.TCPRouteType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *TCPRoute) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *TCPRoute) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *TCPRoute) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *TCPRoute) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *TCPRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *TCPRoute) KubeKind() string { + return tcpRouteKubeKind +} + +func (in *TCPRoute) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *TCPRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *TCPRoute) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *TCPRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *TCPRoute) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *TCPRoute) Validate(tenancy common.ConsulTenancyConfig) error { + var errs field.ErrorList + var route pbmesh.TCPRoute + path := field.NewPath("spec") + res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) + + if err := res.Data.UnmarshalTo(&route); err != nil { + return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) + } + + if len(route.ParentRefs) == 0 { + errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) + } + + if len(route.Rules) > 1 { + errs = append(errs, field.Invalid(path.Child("rules"), route.Rules, "must only specify a single rule for now")) + } + + for i, rule := range route.Rules { + rulePath := path.Child("rules").Index(i) + + if len(rule.BackendRefs) == 0 { + errs = append(errs, field.Required(rulePath.Child("backendRefs"), "cannot be empty")) + } + for j, hbref := range rule.BackendRefs { + ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) + if hbref.BackendRef == nil { + errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) + continue + } + + if hbref.BackendRef.Datacenter != "" { + errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) + } + } + } + + if len(errs) > 0 { + return apierrors.NewInvalid( + schema.GroupKind{Group: MeshGroup, Kind: common.TCPRoute}, + in.KubernetesName(), errs) + } + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *TCPRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_types_test.go b/control-plane/api/mesh/v2beta1/tcp_route_types_test.go new file mode 100644 index 0000000000..9bedc58293 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/tcp_route_types_test.go @@ -0,0 +1,577 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +func TestTCPRoute_MatchesConsul(t *testing.T) { + cases := map[string]struct { + OurConsulNamespace string + OurConsulPartition string + OurData *TCPRoute + + TheirName string + TheirConsulNamespace string + TheirConsulPartition string + TheirData *pbmesh.TCPRoute + ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match + + Matches bool + }{ + "empty fields matches": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: pbmesh.TCPRoute{}, + }, + TheirName: "name", + TheirConsulNamespace: constants.DefaultConsulNS, + TheirConsulPartition: constants.DefaultConsulPartition, + TheirData: &pbmesh.TCPRoute{}, + Matches: true, + }, + "all fields set matches": { + OurConsulNamespace: "consul-ns", + OurConsulPartition: "consul-partition", + OurData: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "parent-name", + Section: "parent-section", + }, + Port: "20122", + }, + }, + Rules: []*pbmesh.TCPRouteRule{ + { + BackendRefs: []*pbmesh.TCPBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Namespace: "another-namespace", + PeerName: "another-peer", + }, + Name: "backend-name", + Section: "backend-section", + }, + Port: "20111", + Datacenter: "different-datacenter", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + TheirName: "foo", + TheirConsulNamespace: "consul-ns", + TheirConsulPartition: "consul-partition", + TheirData: &pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "parent-name", + Section: "parent-section", + }, + Port: "20122", + }, + }, + Rules: []*pbmesh.TCPRouteRule{ + { + BackendRefs: []*pbmesh.TCPBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Namespace: "another-namespace", + PeerName: "another-peer", + }, + Name: "backend-name", + Section: "backend-section", + }, + Port: "20111", + Datacenter: "different-datacenter", + }, + Weight: 50, + }, + }, + }, + }, + }, + Matches: true, + }, + "different types does not match": { + OurConsulNamespace: constants.DefaultConsulNS, + OurConsulPartition: constants.DefaultConsulPartition, + OurData: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: pbmesh.TCPRoute{}, + }, + ResourceOverride: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: "name", + Type: pbmesh.ProxyConfigurationType, + Tenancy: &pbresource.Tenancy{ + Partition: constants.DefaultConsulNS, + Namespace: constants.DefaultConsulPartition, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + }, + Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), + Metadata: meshConfigMeta(), + }, + Matches: false, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + consulResource := c.ResourceOverride + if c.TheirName != "" { + consulResource = constructTCPRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) + } + require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) + }) + } +} + +// TestTCPRoute_Resource also includes test to verify ResourceID(). +func TestTCPRoute_Resource(t *testing.T) { + cases := map[string]struct { + Ours *TCPRoute + ConsulNamespace string + ConsulPartition string + ExpectedName string + ExpectedData *pbmesh.TCPRoute + }{ + "empty fields": { + Ours: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: pbmesh.TCPRoute{}, + }, + ConsulNamespace: constants.DefaultConsulNS, + ConsulPartition: constants.DefaultConsulPartition, + ExpectedName: "foo", + ExpectedData: &pbmesh.TCPRoute{}, + }, + "every field set": { + Ours: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "parent-name", + Section: "parent-section", + }, + Port: "20122", + }, + }, + Rules: []*pbmesh.TCPRouteRule{ + { + BackendRefs: []*pbmesh.TCPBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Namespace: "another-namespace", + PeerName: "another-peer", + }, + Name: "backend-name", + Section: "backend-section", + }, + Port: "20111", + Datacenter: "different-datacenter", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + ConsulNamespace: "not-default-namespace", + ConsulPartition: "not-default-partition", + ExpectedName: "foo", + ExpectedData: &pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "parent-name", + Section: "parent-section", + }, + Port: "20122", + }, + }, + Rules: []*pbmesh.TCPRouteRule{ + { + BackendRefs: []*pbmesh.TCPBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Namespace: "another-namespace", + PeerName: "another-peer", + }, + Name: "backend-name", + Section: "backend-section", + }, + Port: "20111", + Datacenter: "different-datacenter", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) + expected := constructTCPRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) + + opts := append([]cmp.Option{ + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + }, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(expected, actual, opts...) + require.Equal(t, "", diff, "TCPRoute do not match") + }) + } +} + +func TestTCPRoute_SetSyncedCondition(t *testing.T) { + trafficPermissions := &TCPRoute{} + trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") + + require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) + require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) + require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) + now := metav1.Now() + require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) +} + +func TestTCPRoute_SetLastSyncedTime(t *testing.T) { + trafficPermissions := &TCPRoute{} + syncedTime := metav1.NewTime(time.Now()) + trafficPermissions.SetLastSyncedTime(&syncedTime) + + require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) +} + +func TestTCPRoute_GetSyncedConditionStatus(t *testing.T) { + cases := []corev1.ConditionStatus{ + corev1.ConditionUnknown, + corev1.ConditionFalse, + corev1.ConditionTrue, + } + for _, status := range cases { + t.Run(string(status), func(t *testing.T) { + trafficPermissions := &TCPRoute{ + Status: Status{ + Conditions: []Condition{{ + Type: ConditionSynced, + Status: status, + }}, + }, + } + + require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) + }) + } +} + +func TestTCPRoute_GetConditionWhenStatusNil(t *testing.T) { + require.Nil(t, (&TCPRoute{}).GetCondition(ConditionSynced)) +} + +func TestTCPRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { + require.Equal(t, corev1.ConditionUnknown, (&TCPRoute{}).SyncedConditionStatus()) +} + +func TestTCPRoute_SyncedConditionWhenStatusNil(t *testing.T) { + status, reason, message := (&TCPRoute{}).SyncedCondition() + require.Equal(t, corev1.ConditionUnknown, status) + require.Equal(t, "", reason) + require.Equal(t, "", message) +} + +func TestTCPRoute_KubeKind(t *testing.T) { + require.Equal(t, "tcproute", (&TCPRoute{}).KubeKind()) +} + +func TestTCPRoute_KubernetesName(t *testing.T) { + require.Equal(t, "test", (&TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "bar", + }, + Spec: pbmesh.TCPRoute{}, + }).KubernetesName()) +} + +func TestTCPRoute_ObjectMeta(t *testing.T) { + meta := metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + } + trafficPermissions := &TCPRoute{ + ObjectMeta: meta, + } + require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) +} + +// Test defaulting behavior when namespaces are enabled as well as disabled. +// TODO: add when implemented +//func TestTCPRoute_DefaultNamespaceFields(t *testing.T) + +func TestTCPRoute_Validate(t *testing.T) { + cases := []struct { + name string + input *TCPRoute + expectedErrMsgs []string + }{ + { + name: "kitchen sink OK", + input: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + { + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Partition: "some-partition", + Namespace: "some-namespace", + }, + Name: "parent-name", + Section: "parent-section", + }, + Port: "20122", + }, + }, + Rules: []*pbmesh.TCPRouteRule{ + { + BackendRefs: []*pbmesh.TCPBackendRef{ + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + Tenancy: &pbresource.Tenancy{ + Namespace: "another-namespace", + PeerName: "another-peer", + }, + Name: "backend-name", + Section: "backend-section", + }, + Port: "20111", + }, + Weight: 50, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: nil, + }, + { + name: "no parentRefs", + input: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{}, + }, + }, + expectedErrMsgs: []string{ + `spec.parentRefs: Required value: cannot be empty`, + }, + }, + { + name: "multiple rules", + input: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{{}}, + Rules: []*pbmesh.TCPRouteRule{ + {BackendRefs: []*pbmesh.TCPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}}, + {BackendRefs: []*pbmesh.TCPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}}, + }, + }, + }, + expectedErrMsgs: []string{ + `must only specify a single rule for now`, + }, + }, + { + name: "rules.backendRefs", + input: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{{}}, + Rules: []*pbmesh.TCPRouteRule{ + {BackendRefs: []*pbmesh.TCPBackendRef{}}, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].backendRefs: Required value: cannot be empty`, + }, + }, + { + name: "rules.backendRefs.backendRef", + input: &TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "kube-ns", + }, + Spec: pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{{}}, + Rules: []*pbmesh.TCPRouteRule{ + { + BackendRefs: []*pbmesh.TCPBackendRef{ + {}, + { + BackendRef: &pbmesh.BackendReference{ + Ref: &pbresource.Reference{ + Type: pbmesh.ComputedRoutesType, + }, + Datacenter: "backend-datacenter", + }, + }, + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.rules[0].backendRefs[0].backendRef: Required value: missing required field`, + `spec.rules[0].backendRefs[1].backendRef.datacenter: Invalid value: "backend-datacenter": datacenter is not yet supported on backend refs`, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.input.Validate(common.ConsulTenancyConfig{}) + if len(tc.expectedErrMsgs) != 0 { + require.Error(t, err) + for _, s := range tc.expectedErrMsgs { + require.Contains(t, err.Error(), s) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func constructTCPRouteResource(tp *pbmesh.TCPRoute, name, namespace, partition string) *pbresource.Resource { + data := inject.ToProtoAny(tp) + + id := &pbresource.ID{ + Name: name, + Type: pbmesh.TCPRouteType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + Uid: "ABCD", // We add this to show it does not factor into the comparison + } + + return &pbresource.Resource{ + Id: id, + Data: data, + Metadata: meshConfigMeta(), + + // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. + Version: "123456", + Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Status: map[string]*pbresource.Status{ + "knock": { + ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", + Conditions: make([]*pbresource.Condition, 0), + UpdatedAt: timestamppb.Now(), + }, + }, + } +} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_webhook.go b/control-plane/api/mesh/v2beta1/tcp_route_webhook.go new file mode 100644 index 0000000000..68e72c6e35 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/tcp_route_webhook.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "context" + "net/http" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +// +kubebuilder:object:generate=false + +type TCPRouteWebhook struct { + Logger logr.Logger + + // ConsulTenancyConfig contains the injector's namespace and partition configuration. + ConsulTenancyConfig common.ConsulTenancyConfig + + decoder *admission.Decoder + client.Client +} + +var _ common.MeshConfigLister = &TCPRouteWebhook{} + +// NOTE: The path value in the below line is the path to the webhook. +// If it is updated, run code-gen, update subcommand/inject-connect/command.go +// and the consul-helm value for the path to the webhook. +// +// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. +// +// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-tcproute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=tcproute,versions=v2beta1,name=mutate-tcproute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 + +func (v *TCPRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + var resource TCPRoute + err := v.decoder.Decode(req, &resource) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) +} + +func (v *TCPRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { + var resourceList TCPRouteList + if err := v.Client.List(ctx, &resourceList); err != nil { + return nil, err + } + var entries []common.MeshConfig + for _, item := range resourceList.Items { + entries = append(entries, common.MeshConfig(item)) + } + return entries, nil +} + +func (v *TCPRouteWebhook) InjectDecoder(d *admission.Decoder) error { + v.decoder = d + return nil +} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..2d7aadbee7 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -0,0 +1,325 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v2beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Conditions) DeepCopyInto(out *Conditions) { + { + in := &in + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. +func (in Conditions) DeepCopy() Conditions { + if in == nil { + return nil + } + out := new(Conditions) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GRPCRoute) DeepCopyInto(out *GRPCRoute) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCRoute. +func (in *GRPCRoute) DeepCopy() *GRPCRoute { + if in == nil { + return nil + } + out := new(GRPCRoute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GRPCRoute) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GRPCRouteList) DeepCopyInto(out *GRPCRouteList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*GRPCRoute, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(GRPCRoute) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCRouteList. +func (in *GRPCRouteList) DeepCopy() *GRPCRouteList { + if in == nil { + return nil + } + out := new(GRPCRouteList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GRPCRouteList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute. +func (in *HTTPRoute) DeepCopy() *HTTPRoute { + if in == nil { + return nil + } + out := new(HTTPRoute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HTTPRoute) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRouteList) DeepCopyInto(out *HTTPRouteList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*HTTPRoute, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(HTTPRoute) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteList. +func (in *HTTPRouteList) DeepCopy() *HTTPRouteList { + if in == nil { + return nil + } + out := new(HTTPRouteList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HTTPRouteList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyConfiguration) DeepCopyInto(out *ProxyConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfiguration. +func (in *ProxyConfiguration) DeepCopy() *ProxyConfiguration { + if in == nil { + return nil + } + out := new(ProxyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProxyConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyConfigurationList) DeepCopyInto(out *ProxyConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*ProxyConfiguration, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ProxyConfiguration) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfigurationList. +func (in *ProxyConfigurationList) DeepCopy() *ProxyConfigurationList { + if in == nil { + return nil + } + out := new(ProxyConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProxyConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Status) DeepCopyInto(out *Status) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.LastSyncedTime != nil { + in, out := &in.LastSyncedTime, &out.LastSyncedTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. +func (in *Status) DeepCopy() *Status { + if in == nil { + return nil + } + out := new(Status) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPRoute) DeepCopyInto(out *TCPRoute) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRoute. +func (in *TCPRoute) DeepCopy() *TCPRoute { + if in == nil { + return nil + } + out := new(TCPRoute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TCPRoute) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPRouteList) DeepCopyInto(out *TCPRouteList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*TCPRoute, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TCPRoute) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRouteList. +func (in *TCPRouteList) DeepCopy() *TCPRouteList { + if in == nil { + return nil + } + out := new(TCPRouteList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TCPRouteList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/control-plane/api/v1alpha1/jwtprovider_types.go b/control-plane/api/v1alpha1/jwtprovider_types.go index 8e80ece1dc..a38a1df0a7 100644 --- a/control-plane/api/v1alpha1/jwtprovider_types.go +++ b/control-plane/api/v1alpha1/jwtprovider_types.go @@ -7,11 +7,9 @@ import ( "encoding/base64" "encoding/json" "net/url" - "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul/api" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" @@ -19,6 +17,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ( @@ -394,7 +394,7 @@ type RemoteJWKS struct { // should be expired. // // Default value is 5 minutes. - CacheDuration time.Duration `json:"cacheDuration,omitempty"` + CacheDuration metav1.Duration `json:"cacheDuration,omitempty"` // FetchAsynchronously indicates that the JWKS should be fetched // when a client request arrives. Client requests will be paused @@ -421,7 +421,7 @@ func (r *RemoteJWKS) toConsul() *capi.RemoteJWKS { return &capi.RemoteJWKS{ URI: r.URI, RequestTimeoutMs: r.RequestTimeoutMs, - CacheDuration: r.CacheDuration, + CacheDuration: r.CacheDuration.Duration, FetchAsynchronously: r.FetchAsynchronously, RetryPolicy: r.RetryPolicy.toConsul(), JWKSCluster: r.JWKSCluster.toConsul(), @@ -462,7 +462,7 @@ type JWKSCluster struct { // The timeout for new network connections to hosts in the cluster. // If not set, a default value of 5s will be used. - ConnectTimeout time.Duration `json:"connectTimeout,omitempty"` + ConnectTimeout metav1.Duration `json:"connectTimeout,omitempty"` } func (c *JWKSCluster) toConsul() *capi.JWKSCluster { @@ -472,7 +472,7 @@ func (c *JWKSCluster) toConsul() *capi.JWKSCluster { return &capi.JWKSCluster{ DiscoveryType: c.DiscoveryType.toConsul(), TLSCertificates: c.TLSCertificates.toConsul(), - ConnectTimeout: c.ConnectTimeout, + ConnectTimeout: c.ConnectTimeout.Duration, } } @@ -663,13 +663,13 @@ type RetryPolicyBackOff struct { // BaseInterval to be used for the next back off computation. // // The default value from envoy is 1s. - BaseInterval time.Duration `json:"baseInterval,omitempty"` + BaseInterval metav1.Duration `json:"baseInterval,omitempty"` // MaxInternal to be used to specify the maximum interval between retries. // Optional but should be greater or equal to BaseInterval. // // Defaults to 10 times BaseInterval. - MaxInterval time.Duration `json:"maxInterval,omitempty"` + MaxInterval metav1.Duration `json:"maxInterval,omitempty"` } func (r *RetryPolicyBackOff) toConsul() *capi.RetryPolicyBackOff { @@ -677,8 +677,8 @@ func (r *RetryPolicyBackOff) toConsul() *capi.RetryPolicyBackOff { return nil } return &capi.RetryPolicyBackOff{ - BaseInterval: r.BaseInterval, - MaxInterval: r.MaxInterval, + BaseInterval: r.BaseInterval.Duration, + MaxInterval: r.MaxInterval.Duration, } } @@ -688,7 +688,7 @@ func (r *RetryPolicyBackOff) validate(path *field.Path) field.ErrorList { return errs } - if (r.MaxInterval != 0) && (r.BaseInterval > r.MaxInterval) { + if (r.MaxInterval.Duration != 0) && (r.BaseInterval.Duration > r.MaxInterval.Duration) { asJSON, _ := json.Marshal(r) errs = append(errs, field.Invalid(path, string(asJSON), "maxInterval should be greater or equal to baseInterval")) } diff --git a/control-plane/api/v1alpha1/jwtprovider_types_test.go b/control-plane/api/v1alpha1/jwtprovider_types_test.go index 8f6219e8d6..098e34494e 100644 --- a/control-plane/api/v1alpha1/jwtprovider_types_test.go +++ b/control-plane/api/v1alpha1/jwtprovider_types_test.go @@ -7,11 +7,12 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) // Test MatchesConsul for cases that should return true. @@ -55,13 +56,13 @@ func TestJWTProvider_MatchesConsul(t *testing.T) { Remote: &RemoteJWKS{ URI: "https://jwks.example.com", RequestTimeoutMs: 567, - CacheDuration: 890, + CacheDuration: metav1.Duration{Duration: 890}, FetchAsynchronously: true, RetryPolicy: &JWKSRetryPolicy{ NumRetries: 1, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: 23, - MaxInterval: 456, + BaseInterval: metav1.Duration{Duration: 23}, + MaxInterval: metav1.Duration{Duration: 456}, }, }, JWKSCluster: &JWKSCluster{ @@ -78,7 +79,7 @@ func TestJWTProvider_MatchesConsul(t *testing.T) { InlineBytes: []byte("inline-bytes"), }, }, - ConnectTimeout: 890, + ConnectTimeout: metav1.Duration{Duration: 890}, }, }, }, @@ -238,13 +239,13 @@ func TestJWTProvider_ToConsul(t *testing.T) { Remote: &RemoteJWKS{ URI: "https://jwks.example.com", RequestTimeoutMs: 567, - CacheDuration: 890, + CacheDuration: metav1.Duration{Duration: 890}, FetchAsynchronously: true, RetryPolicy: &JWKSRetryPolicy{ NumRetries: 1, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: 23, - MaxInterval: 456, + BaseInterval: metav1.Duration{Duration: 23}, + MaxInterval: metav1.Duration{Duration: 456}, }, }, JWKSCluster: &JWKSCluster{ @@ -261,7 +262,7 @@ func TestJWTProvider_ToConsul(t *testing.T) { InlineBytes: []byte("inline-bytes"), }, }, - ConnectTimeout: 890, + ConnectTimeout: metav1.Duration{Duration: 890}, }, }, }, @@ -440,13 +441,13 @@ func TestJWTProvider_Validate(t *testing.T) { Remote: &RemoteJWKS{ URI: "https://jwks.example.com", RequestTimeoutMs: 5000, - CacheDuration: 10 * time.Second, + CacheDuration: metav1.Duration{Duration: 10 * time.Second}, FetchAsynchronously: true, RetryPolicy: &JWKSRetryPolicy{ NumRetries: 3, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: 5 * time.Second, - MaxInterval: 20 * time.Second, + BaseInterval: metav1.Duration{Duration: 5 * time.Second}, + MaxInterval: metav1.Duration{Duration: 20 * time.Second}, }, }, JWKSCluster: &JWKSCluster{ @@ -456,7 +457,7 @@ func TestJWTProvider_Validate(t *testing.T) { Filename: "cert.crt", }, }, - ConnectTimeout: 890, + ConnectTimeout: metav1.Duration{Duration: 890}, }, }, }, @@ -504,13 +505,13 @@ func TestJWTProvider_Validate(t *testing.T) { Remote: &RemoteJWKS{ URI: "https://jwks.example.com", RequestTimeoutMs: 5000, - CacheDuration: 10 * time.Second, + CacheDuration: metav1.Duration{Duration: 10 * time.Second}, FetchAsynchronously: true, RetryPolicy: &JWKSRetryPolicy{ NumRetries: 3, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: 5 * time.Second, - MaxInterval: 20 * time.Second, + BaseInterval: metav1.Duration{Duration: 5 * time.Second}, + MaxInterval: metav1.Duration{Duration: 20 * time.Second}, }, }, JWKSCluster: &JWKSCluster{ @@ -521,7 +522,7 @@ func TestJWTProvider_Validate(t *testing.T) { CertificateName: "ROOTCA", }, }, - ConnectTimeout: 890, + ConnectTimeout: metav1.Duration{Duration: 890}, }, }, }, @@ -620,7 +621,7 @@ func TestJWTProvider_Validate(t *testing.T) { }, }, expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-local-and-remote" is invalid: spec.jsonWebKeySet: Invalid value: "{\"local\":{\"filename\":\"jwks.txt\"},\"remote\":{\"uri\":\"https://jwks.example.com\"}}": exactly one of 'local' or 'remote' is required`, + `jwtprovider.consul.hashicorp.com "test-jwks-local-and-remote" is invalid: spec.jsonWebKeySet: Invalid value: "{\"local\":{\"filename\":\"jwks.txt\"},\"remote\":{\"uri\":\"https://jwks.example.com\",\"cacheDuration\":\"0s\"}}": exactly one of 'local' or 'remote' is required`, }, }, @@ -679,7 +680,7 @@ func TestJWTProvider_Validate(t *testing.T) { Filename: "cert.crt", }, }, - ConnectTimeout: 890, + ConnectTimeout: metav1.Duration{Duration: 890}, }, }, }, @@ -702,7 +703,7 @@ func TestJWTProvider_Validate(t *testing.T) { URI: "https://jwks.example.com", JWKSCluster: &JWKSCluster{ DiscoveryType: "FAKE_DNS", - ConnectTimeout: 890, + ConnectTimeout: metav1.Duration{Duration: 890}, }, }, }, @@ -732,7 +733,7 @@ func TestJWTProvider_Validate(t *testing.T) { InlineBytes: []byte("inline-bytes"), }, }, - ConnectTimeout: 890, + ConnectTimeout: metav1.Duration{Duration: 890}, }, }, }, @@ -761,7 +762,7 @@ func TestJWTProvider_Validate(t *testing.T) { EnvironmentVariable: "env-variable", }, }, - ConnectTimeout: 890, + ConnectTimeout: metav1.Duration{Duration: 890}, }, }, }, @@ -844,8 +845,8 @@ func TestJWTProvider_Validate(t *testing.T) { RetryPolicy: &JWKSRetryPolicy{ NumRetries: 0, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: 100 * time.Second, - MaxInterval: 10 * time.Second, + BaseInterval: metav1.Duration{Duration: 100 * time.Second}, + MaxInterval: metav1.Duration{Duration: 10 * time.Second}, }, }, }, @@ -853,7 +854,7 @@ func TestJWTProvider_Validate(t *testing.T) { }, }, expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-retry-intervals" is invalid: spec.jsonWebKeySet.remote.retryPolicy.retryPolicyBackOff: Invalid value: "{\"baseInterval\":100000000000,\"maxInterval\":10000000000}": maxInterval should be greater or equal to baseInterval`, + `jwtprovider.consul.hashicorp.com "test-jwks-retry-intervals" is invalid: spec.jsonWebKeySet.remote.retryPolicy.retryPolicyBackOff: Invalid value: "{\"baseInterval\":\"1m40s\",\"maxInterval\":\"10s\"}": maxInterval should be greater or equal to baseInterval`, }, }, } diff --git a/control-plane/api/v1alpha1/routetimeoutfilter_types.go b/control-plane/api/v1alpha1/routetimeoutfilter_types.go index 59b25f62ea..5aed6461bf 100644 --- a/control-plane/api/v1alpha1/routetimeoutfilter_types.go +++ b/control-plane/api/v1alpha1/routetimeoutfilter_types.go @@ -5,7 +5,6 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "time" ) func init() { @@ -41,10 +40,10 @@ type RouteTimeoutFilterList struct { // RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. type RouteTimeoutFilterSpec struct { // +kubebuilder:validation:Optional - RequestTimeout time.Duration `json:"requestTimeout"` + RequestTimeout metav1.Duration `json:"requestTimeout"` // +kubebuilder:validation:Optional - IdleTimeout time.Duration `json:"idleTimeout"` + IdleTimeout metav1.Duration `json:"idleTimeout"` } func (h *RouteTimeoutFilter) GetNamespace() string { diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index cc8447c6c2..045bd5144e 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -1329,6 +1329,7 @@ func (in *JWKSCluster) DeepCopyInto(out *JWKSCluster) { *out = new(JWKSTLSCertificate) (*in).DeepCopyInto(*out) } + out.ConnectTimeout = in.ConnectTimeout } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSCluster. @@ -2454,6 +2455,7 @@ func (in *ReadWriteRatesConfig) DeepCopy() *ReadWriteRatesConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RemoteJWKS) DeepCopyInto(out *RemoteJWKS) { *out = *in + out.CacheDuration = in.CacheDuration if in.RetryPolicy != nil { in, out := &in.RetryPolicy, &out.RetryPolicy *out = new(JWKSRetryPolicy) @@ -2479,6 +2481,8 @@ func (in *RemoteJWKS) DeepCopy() *RemoteJWKS { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RetryPolicyBackOff) DeepCopyInto(out *RetryPolicyBackOff) { *out = *in + out.BaseInterval = in.BaseInterval + out.MaxInterval = in.MaxInterval } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryPolicyBackOff. @@ -2763,6 +2767,8 @@ func (in *RouteTimeoutFilterList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RouteTimeoutFilterSpec) DeepCopyInto(out *RouteTimeoutFilterSpec) { *out = *in + out.RequestTimeout = in.RequestTimeout + out.IdleTimeout = in.IdleTimeout } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilterSpec. diff --git a/control-plane/api/v2beta1/traffic_permissions_types.go b/control-plane/api/v2beta1/traffic_permissions_types.go deleted file mode 100644 index 533b5d0e33..0000000000 --- a/control-plane/api/v2beta1/traffic_permissions_types.go +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - trafficpermissionsKubeKind = "trafficpermissions" -) - -func init() { - AuthSchemeBuilder.Register(&TrafficPermissions{}, &TrafficPermissionsList{}) -} - -var _ common.MeshConfig = &TrafficPermissions{} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// TrafficPermissions is the Schema for the traffic-permissions API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="traffic-permissions" -type TrafficPermissions struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec TrafficPermissionsSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// TrafficPermissionsList contains a list of TrafficPermissions. -type TrafficPermissionsList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []TrafficPermissions `json:"items"` -} - -// TrafficPermissionsSpec defines the desired state of TrafficPermissions. -type TrafficPermissionsSpec struct { - // Destination is a configuration of the destination proxies - // where these traffic permissions should apply. - Destination *Destination `json:"destination,omitempty"` - // Action can be either allow or deny for the entire object. It will default to allow. - // - // If action is allow, - // we will allow the connection if one of the rules in Rules matches, in other words, we will deny - // all requests except for the ones that match Rules. If Consul is in default allow mode, then allow - // actions have no effect without a deny permission as everything is allowed by default. - // - // If action is deny, - // we will deny the connection if one of the rules in Rules match, in other words, - // we will allow all requests except for the ones that match Rules. If Consul is default deny mode, - // then deny permissions have no effect without an allow permission as everything is denied by default. - // - // Action unspecified is reserved for compatibility with the addition of future actions. - Action IntentionAction `json:"action,omitempty"` - // Permissions is a list of permissions to match on. - // They are applied using OR semantics. - Permissions Permissions `json:"permissions,omitempty"` -} - -type Destination struct { - // Name is the destination of all intentions defined in this config entry. - // This may be set to the wildcard character (*) to match - // all services that don't otherwise have intentions defined. - IdentityName string `json:"identityName,omitempty"` -} - -func (in *Destination) validate(path *field.Path) *field.Error { - if in == nil { - return field.Required(path, `destination and destination.identityName are required`) - } - if in.IdentityName == "" { - return field.Required(path.Child("identityName"), `identityName is required`) - } - return nil -} - -// IntentionAction is the action that the intention represents. This -// can be "allow" or "deny" to allowlist or denylist intentions. -type IntentionAction string - -const ( - ActionDeny IntentionAction = "deny" - ActionAllow IntentionAction = "allow" - ActionUnspecified IntentionAction = "" -) - -func (in IntentionAction) validate(path *field.Path) *field.Error { - switch in { - case ActionDeny, ActionAllow: - return nil - default: - return field.Invalid(path.Child("action"), in, "must be one of \"allow\" or \"deny\"") - } -} - -type Permissions []*Permission - -type Permission struct { - // sources is a list of sources in this traffic permission. - Sources Sources `json:"sources,omitempty"` - // destinationRules is a list of rules to apply for matching sources in this Permission. - // These rules are specific to the request or connection that is going to the destination(s) - // selected by the TrafficPermissions resource. - DestinationRules DestinationRules `json:"destinationRules,omitempty"` -} - -type Sources []*Source - -type DestinationRules []*DestinationRule - -// Source represents the source identity. -// To specify any of the wildcard sources, the specific fields need to be omitted. -// For example, for a wildcard namespace, identityName should be omitted. -type Source struct { - IdentityName string `json:"identityName,omitempty"` - Namespace string `json:"namespace,omitempty"` - Partition string `json:"partition,omitempty"` - Peer string `json:"peer,omitempty"` - SamenessGroup string `json:"samenessGroup,omitempty"` - // exclude is a list of sources to exclude from this source. - Exclude Exclude `json:"exclude,omitempty"` -} - -// DestinationRule contains rules to apply to the incoming connection. -type DestinationRule struct { - PathExact string `json:"pathExact,omitempty"` - PathPrefix string `json:"pathPrefix,omitempty"` - PathRegex string `json:"pathRegex,omitempty"` - // methods is the list of HTTP methods. If no methods are specified, - // this rule will apply to all methods. - Methods []string `json:"methods,omitempty"` - Header *DestinationRuleHeader `json:"header,omitempty"` - PortNames []string `json:"portNames,omitempty"` - // exclude contains a list of rules to exclude when evaluating rules for the incoming connection. - Exclude ExcludePermissions `json:"exclude,omitempty"` -} - -type Exclude []*ExcludeSource - -// ExcludeSource is almost the same as source but it prevents the addition of -// matchiing sources. -type ExcludeSource struct { - IdentityName string `json:"identityName,omitempty"` - Namespace string `json:"namespace,omitempty"` - Partition string `json:"partition,omitempty"` - Peer string `json:"peer,omitempty"` - SamenessGroup string `json:"samenessGroup,omitempty"` -} - -type DestinationRuleHeader struct { - Name string `json:"name,omitempty"` - Present bool `json:"present,omitempty"` - Exact string `json:"exact,omitempty"` - Prefix string `json:"prefix,omitempty"` - Suffix string `json:"suffix,omitempty"` - Regex string `json:"regex,omitempty"` - Invert bool `json:"invert,omitempty"` -} - -type ExcludePermissions []*ExcludePermissionRule - -type ExcludePermissionRule struct { - PathExact string `json:"pathExact,omitempty"` - PathPrefix string `json:"pathPrefix,omitempty"` - PathRegex string `json:"pathRegex,omitempty"` - // methods is the list of HTTP methods. - Methods []string `json:"methods,omitempty"` - Header *DestinationRuleHeader `json:"header,omitempty"` - // portNames is a list of workload ports to apply this rule to. The ports specified here - // must be the ports used in the connection. - PortNames []string `json:"portNames,omitempty"` -} - -func (in *TrafficPermissions) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbauth.TrafficPermissionsType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *TrafficPermissions) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&pbauth.TrafficPermissions{ - Destination: in.Spec.Destination.toProto(), - Action: in.Spec.Action.toProto(), - Permissions: in.Spec.Permissions.toProto(), - }), - Metadata: meshConfigMeta(), - } -} - -func (in *TrafficPermissions) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *TrafficPermissions) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *TrafficPermissions) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *TrafficPermissions) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *TrafficPermissions) KubeKind() string { - return trafficpermissionsKubeKind -} - -func (in *TrafficPermissions) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *TrafficPermissions) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *TrafficPermissions) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *TrafficPermissions) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *TrafficPermissions) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *TrafficPermissions) Validate(_ common.ConsulTenancyConfig) error { - var errs field.ErrorList - path := field.NewPath("spec") - - if in.Spec.Action == ActionUnspecified { - errs = append(errs, field.Required(path.Child("action"), `action is required`)) - } - if err := in.Spec.Action.validate(path); err != nil { - errs = append(errs, err) - } - - // Validate Destinations - if err := in.Spec.Destination.validate(path.Child("destination")); err != nil { - errs = append(errs, err) - } - - // TODO: add validation for permissions - // Validate permissions in Consul: - // https://github.com/hashicorp/consul/blob/203a36821ef6182b2d2b30c1012ca5a42c7dd8f3/internal/auth/internal/types/traffic_permissions.go#L59-L141 - - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: AuthGroup, Kind: common.TrafficPermissions}, - in.KubernetesName(), errs) - } - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *TrafficPermissions) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} - -func (p Permissions) toProto() []*pbauth.Permission { - var perms []*pbauth.Permission - for _, permission := range p { - perms = append(perms, &pbauth.Permission{ - Sources: permission.Sources.toProto(), - DestinationRules: permission.DestinationRules.toProto(), - }) - } - return perms -} - -func (s Sources) toProto() []*pbauth.Source { - var srcs []*pbauth.Source - for _, source := range s { - srcs = append(srcs, &pbauth.Source{ - IdentityName: source.IdentityName, - Namespace: source.Namespace, - Partition: source.Partition, - Peer: source.Peer, - SamenessGroup: source.SamenessGroup, - Exclude: source.Exclude.toProto(), - }) - } - return srcs -} - -func (r DestinationRules) toProto() []*pbauth.DestinationRule { - var dstnRules []*pbauth.DestinationRule - for _, rule := range r { - dstnRules = append(dstnRules, &pbauth.DestinationRule{ - PathExact: rule.PathExact, - PathPrefix: rule.PathPrefix, - PathRegex: rule.PathRegex, - Methods: rule.Methods, - Header: rule.Header.toProto(), - PortNames: rule.PortNames, - Exclude: rule.Exclude.toProto(), - }) - } - return dstnRules -} - -func (e Exclude) toProto() []*pbauth.ExcludeSource { - var exSrcs []*pbauth.ExcludeSource - for _, source := range e { - exSrcs = append(exSrcs, &pbauth.ExcludeSource{ - IdentityName: source.IdentityName, - Namespace: source.Namespace, - Partition: source.Partition, - Peer: source.Peer, - SamenessGroup: source.SamenessGroup, - }) - } - return exSrcs -} - -func (p ExcludePermissions) toProto() []*pbauth.ExcludePermissionRule { - var exclPerms []*pbauth.ExcludePermissionRule - for _, rule := range p { - exclPerms = append(exclPerms, &pbauth.ExcludePermissionRule{ - PathExact: rule.PathExact, - PathPrefix: rule.PathPrefix, - PathRegex: rule.PathRegex, - Methods: rule.Methods, - Header: rule.Header.toProto(), - PortNames: rule.PortNames, - }) - } - return exclPerms -} - -func (h *DestinationRuleHeader) toProto() *pbauth.DestinationRuleHeader { - if h == nil { - return nil - } - return &pbauth.DestinationRuleHeader{ - Name: h.Name, - Present: h.Present, - Exact: h.Exact, - Prefix: h.Prefix, - Suffix: h.Suffix, - Regex: h.Regex, - Invert: h.Invert, - } -} - -func (in *Destination) toProto() *pbauth.Destination { - if in == nil { - return nil - } - return &pbauth.Destination{ - IdentityName: in.IdentityName, - } -} - -func (in IntentionAction) toProto() pbauth.Action { - if in == ActionAllow { - return pbauth.Action_ACTION_ALLOW - } else if in == ActionDeny { - return pbauth.Action_ACTION_DENY - } - return pbauth.Action_ACTION_UNSPECIFIED -} diff --git a/control-plane/api/v2beta1/zz_generated.deepcopy.go b/control-plane/api/v2beta1/zz_generated.deepcopy.go deleted file mode 100644 index 0be67d0aa2..0000000000 --- a/control-plane/api/v2beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,467 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Code generated by controller-gen. DO NOT EDIT. - -package v2beta1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { - if in == nil { - return nil - } - out := new(Condition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Conditions) DeepCopyInto(out *Conditions) { - { - in := &in - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. -func (in Conditions) DeepCopy() Conditions { - if in == nil { - return nil - } - out := new(Conditions) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Destination) DeepCopyInto(out *Destination) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Destination. -func (in *Destination) DeepCopy() *Destination { - if in == nil { - return nil - } - out := new(Destination) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DestinationRule) DeepCopyInto(out *DestinationRule) { - *out = *in - if in.Methods != nil { - in, out := &in.Methods, &out.Methods - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Header != nil { - in, out := &in.Header, &out.Header - *out = new(DestinationRuleHeader) - **out = **in - } - if in.PortNames != nil { - in, out := &in.PortNames, &out.PortNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Exclude != nil { - in, out := &in.Exclude, &out.Exclude - *out = make(ExcludePermissions, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(ExcludePermissionRule) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationRule. -func (in *DestinationRule) DeepCopy() *DestinationRule { - if in == nil { - return nil - } - out := new(DestinationRule) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DestinationRuleHeader) DeepCopyInto(out *DestinationRuleHeader) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationRuleHeader. -func (in *DestinationRuleHeader) DeepCopy() *DestinationRuleHeader { - if in == nil { - return nil - } - out := new(DestinationRuleHeader) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in DestinationRules) DeepCopyInto(out *DestinationRules) { - { - in := &in - *out = make(DestinationRules, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(DestinationRule) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationRules. -func (in DestinationRules) DeepCopy() DestinationRules { - if in == nil { - return nil - } - out := new(DestinationRules) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Exclude) DeepCopyInto(out *Exclude) { - { - in := &in - *out = make(Exclude, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(ExcludeSource) - **out = **in - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Exclude. -func (in Exclude) DeepCopy() Exclude { - if in == nil { - return nil - } - out := new(Exclude) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExcludePermissionRule) DeepCopyInto(out *ExcludePermissionRule) { - *out = *in - if in.Methods != nil { - in, out := &in.Methods, &out.Methods - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Header != nil { - in, out := &in.Header, &out.Header - *out = new(DestinationRuleHeader) - **out = **in - } - if in.PortNames != nil { - in, out := &in.PortNames, &out.PortNames - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExcludePermissionRule. -func (in *ExcludePermissionRule) DeepCopy() *ExcludePermissionRule { - if in == nil { - return nil - } - out := new(ExcludePermissionRule) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in ExcludePermissions) DeepCopyInto(out *ExcludePermissions) { - { - in := &in - *out = make(ExcludePermissions, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(ExcludePermissionRule) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExcludePermissions. -func (in ExcludePermissions) DeepCopy() ExcludePermissions { - if in == nil { - return nil - } - out := new(ExcludePermissions) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExcludeSource) DeepCopyInto(out *ExcludeSource) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExcludeSource. -func (in *ExcludeSource) DeepCopy() *ExcludeSource { - if in == nil { - return nil - } - out := new(ExcludeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Permission) DeepCopyInto(out *Permission) { - *out = *in - if in.Sources != nil { - in, out := &in.Sources, &out.Sources - *out = make(Sources, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(Source) - (*in).DeepCopyInto(*out) - } - } - } - if in.DestinationRules != nil { - in, out := &in.DestinationRules, &out.DestinationRules - *out = make(DestinationRules, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(DestinationRule) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permission. -func (in *Permission) DeepCopy() *Permission { - if in == nil { - return nil - } - out := new(Permission) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Permissions) DeepCopyInto(out *Permissions) { - { - in := &in - *out = make(Permissions, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(Permission) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permissions. -func (in Permissions) DeepCopy() Permissions { - if in == nil { - return nil - } - out := new(Permissions) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Source) DeepCopyInto(out *Source) { - *out = *in - if in.Exclude != nil { - in, out := &in.Exclude, &out.Exclude - *out = make(Exclude, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(ExcludeSource) - **out = **in - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source. -func (in *Source) DeepCopy() *Source { - if in == nil { - return nil - } - out := new(Source) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Sources) DeepCopyInto(out *Sources) { - { - in := &in - *out = make(Sources, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(Source) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sources. -func (in Sources) DeepCopy() Sources { - if in == nil { - return nil - } - out := new(Sources) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Status) DeepCopyInto(out *Status) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastSyncedTime != nil { - in, out := &in.LastSyncedTime, &out.LastSyncedTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. -func (in *Status) DeepCopy() *Status { - if in == nil { - return nil - } - out := new(Status) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TrafficPermissions) DeepCopyInto(out *TrafficPermissions) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissions. -func (in *TrafficPermissions) DeepCopy() *TrafficPermissions { - if in == nil { - return nil - } - out := new(TrafficPermissions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TrafficPermissions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TrafficPermissionsList) DeepCopyInto(out *TrafficPermissionsList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]TrafficPermissions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissionsList. -func (in *TrafficPermissionsList) DeepCopy() *TrafficPermissionsList { - if in == nil { - return nil - } - out := new(TrafficPermissionsList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TrafficPermissionsList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TrafficPermissionsSpec) DeepCopyInto(out *TrafficPermissionsSpec) { - *out = *in - if in.Destination != nil { - in, out := &in.Destination, &out.Destination - *out = new(Destination) - **out = **in - } - if in.Permissions != nil { - in, out := &in.Permissions, &out.Permissions - *out = make(Permissions, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(Permission) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissionsSpec. -func (in *TrafficPermissionsSpec) DeepCopy() *TrafficPermissionsSpec { - if in == nil { - return nil - } - out := new(TrafficPermissionsSpec) - in.DeepCopyInto(out) - return out -} diff --git a/control-plane/config-entries/controllersv2/grpc_route_controller.go b/control-plane/config-entries/controllersv2/grpc_route_controller.go new file mode 100644 index 0000000000..06accae268 --- /dev/null +++ b/control-plane/config-entries/controllersv2/grpc_route_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// GRPCRouteController reconciles a GRPCRoute object. +type GRPCRouteController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + MeshConfigController *MeshConfigController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute/status,verbs=get;update;patch + +func (r *GRPCRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.GRPCRoute{}) +} + +func (r *GRPCRouteController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *GRPCRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *GRPCRouteController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.GRPCRoute{}, r) +} diff --git a/control-plane/config-entries/controllersv2/http_route_controller.go b/control-plane/config-entries/controllersv2/http_route_controller.go new file mode 100644 index 0000000000..545250af74 --- /dev/null +++ b/control-plane/config-entries/controllersv2/http_route_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// HTTPRouteController reconciles a HTTPRoute object. +type HTTPRouteController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + MeshConfigController *MeshConfigController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute/status,verbs=get;update;patch + +func (r *HTTPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.HTTPRoute{}) +} + +func (r *HTTPRouteController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *HTTPRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *HTTPRouteController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.HTTPRoute{}, r) +} diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go index ff2a94ae27..e95e722ea6 100644 --- a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go +++ b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go @@ -25,9 +25,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/api/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" @@ -55,14 +55,14 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { Name: "my-traffic-permission", Namespace: metav1.NamespaceDefault, }, - Spec: v2beta1.TrafficPermissionsSpec{ - Destination: &v2beta1.Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: v2beta1.ActionAllow, - Permissions: v2beta1.Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: v2beta1.Sources{ + Sources: []*pbauth.Source{ { Namespace: "the space namespace space", }, @@ -70,7 +70,7 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { IdentityName: "source-identity", }, }, - DestinationRules: v2beta1.DestinationRules{ + DestinationRules: []*pbauth.DestinationRule{ { PathExact: "/hello", Methods: []string{"GET", "POST"}, @@ -201,14 +201,14 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { Name: "my-traffic-permission", Namespace: metav1.NamespaceDefault, }, - Spec: v2beta1.TrafficPermissionsSpec{ - Destination: &v2beta1.Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: v2beta1.ActionAllow, - Permissions: v2beta1.Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: v2beta1.Sources{ + Sources: []*pbauth.Source{ { Namespace: "the space namespace space", }, @@ -216,7 +216,7 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { IdentityName: "source-identity", }, }, - DestinationRules: v2beta1.DestinationRules{ + DestinationRules: []*pbauth.DestinationRule{ { PathExact: "/hello", Methods: []string{"GET", "POST"}, @@ -263,7 +263,7 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { }, updateF: func(resource common.MeshConfig) { trafficPermissions := resource.(*v2beta1.TrafficPermissions) - trafficPermissions.Spec.Action = "deny" + trafficPermissions.Spec.Action = pbauth.Action_ACTION_DENY trafficPermissions.Spec.Permissions[0].Sources = trafficPermissions.Spec.Permissions[0].Sources[:1] }, unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { @@ -358,14 +358,14 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { DeletionTimestamp: &metav1.Time{Time: time.Now()}, Finalizers: []string{FinalizerName}, }, - Spec: v2beta1.TrafficPermissionsSpec{ - Destination: &v2beta1.Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: v2beta1.ActionAllow, - Permissions: v2beta1.Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: v2beta1.Sources{ + Sources: []*pbauth.Source{ { Namespace: "the space namespace space", }, @@ -373,7 +373,7 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { IdentityName: "source-identity", }, }, - DestinationRules: v2beta1.DestinationRules{ + DestinationRules: []*pbauth.DestinationRule{ { PathExact: "/hello", Methods: []string{"GET", "POST"}, @@ -457,14 +457,14 @@ func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { Name: "foo", Namespace: metav1.NamespaceDefault, }, - Spec: v2beta1.TrafficPermissionsSpec{ - Destination: &v2beta1.Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: v2beta1.ActionAllow, - Permissions: v2beta1.Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: v2beta1.Sources{ + Sources: []*pbauth.Source{ { IdentityName: "source-identity", }, @@ -533,14 +533,14 @@ func TestMeshConfigController_setsSyncedToTrue(t *testing.T) { Name: "foo", Namespace: metav1.NamespaceDefault, }, - Spec: v2beta1.TrafficPermissionsSpec{ - Destination: &v2beta1.Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: v2beta1.ActionAllow, - Permissions: v2beta1.Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: v2beta1.Sources{ + Sources: []*pbauth.Source{ { IdentityName: "source-identity", }, @@ -620,14 +620,14 @@ func TestMeshConfigController_doesNotCreateUnownedMeshConfig(t *testing.T) { Name: "foo", Namespace: metav1.NamespaceDefault, }, - Spec: v2beta1.TrafficPermissionsSpec{ - Destination: &v2beta1.Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: v2beta1.ActionAllow, - Permissions: v2beta1.Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: v2beta1.Sources{ + Sources: []*pbauth.Source{ { IdentityName: "source-identity", Namespace: common.DefaultConsulNamespace, @@ -728,14 +728,14 @@ func TestMeshConfigController_doesNotDeleteUnownedConfig(t *testing.T) { DeletionTimestamp: &metav1.Time{Time: time.Now()}, Finalizers: []string{FinalizerName}, }, - Spec: v2beta1.TrafficPermissionsSpec{ - Destination: &v2beta1.Destination{ + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: v2beta1.ActionAllow, - Permissions: v2beta1.Permissions{ + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ { - Sources: v2beta1.Sources{ + Sources: []*pbauth.Source{ { IdentityName: "source-identity", Namespace: common.DefaultConsulNamespace, diff --git a/control-plane/config-entries/controllersv2/proxy_configuration_controller.go b/control-plane/config-entries/controllersv2/proxy_configuration_controller.go new file mode 100644 index 0000000000..f45dda1962 --- /dev/null +++ b/control-plane/config-entries/controllersv2/proxy_configuration_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// ProxyConfigurationController reconciles a ProxyConfiguration object. +type ProxyConfigurationController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + MeshConfigController *MeshConfigController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration/status,verbs=get;update;patch + +func (r *ProxyConfigurationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.ProxyConfiguration{}) +} + +func (r *ProxyConfigurationController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *ProxyConfigurationController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *ProxyConfigurationController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.ProxyConfiguration{}, r) +} diff --git a/control-plane/config-entries/controllersv2/tcp_route_controller.go b/control-plane/config-entries/controllersv2/tcp_route_controller.go new file mode 100644 index 0000000000..170dbb9fd4 --- /dev/null +++ b/control-plane/config-entries/controllersv2/tcp_route_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// TCPRouteController reconciles a TCPRoute object. +type TCPRouteController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + MeshConfigController *MeshConfigController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute/status,verbs=get;update;patch + +func (r *TCPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.TCPRoute{}) +} + +func (r *TCPRouteController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *TCPRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *TCPRouteController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.TCPRoute{}, r) +} diff --git a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go b/control-plane/config-entries/controllersv2/traffic_permissions_controller.go index a8c9f63e56..abcaf98906 100644 --- a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go +++ b/control-plane/config-entries/controllersv2/traffic_permissions_controller.go @@ -12,7 +12,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - consulv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/v2beta1" + consulv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" ) // TrafficPermissionsController reconciles a TrafficPermissions object. diff --git a/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml b/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml index 270f86ee0f..3a7699dce4 100644 --- a/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml +++ b/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: trafficpermissions.auth.consul.hashicorp.com spec: group: auth.consul.hashicorp.com @@ -52,7 +50,6 @@ spec: metadata: type: object spec: - description: TrafficPermissionsSpec defines the desired state of TrafficPermissions. properties: action: description: "Action can be either allow or deny for the entire object. @@ -67,34 +64,36 @@ spec: deny permissions have no effect without an allow permission as everything is denied by default. \n Action unspecified is reserved for compatibility with the addition of future actions." + enum: + - ACTION_ALLOW + - ACTION_DENY + - ACTION_UNKNOWN + format: int32 type: string destination: description: Destination is a configuration of the destination proxies where these traffic permissions should apply. properties: identityName: - description: Name is the destination of all intentions defined - in this config entry. This may be set to the wildcard character - (*) to match all services that don't otherwise have intentions - defined. type: string type: object permissions: description: Permissions is a list of permissions to match on. They are applied using OR semantics. items: + description: Permissions is a list of permissions to match on. properties: destinationRules: - description: destinationRules is a list of rules to apply for + description: DestinationRules is a list of rules to apply for matching sources in this Permission. These rules are specific to the request or connection that is going to the destination(s) selected by the TrafficPermissions resource. items: - description: DestinationRule contains rules to apply to the - incoming connection. + description: DestinationRule contains rules rules to apply + to the incoming connection. properties: exclude: - description: exclude contains a list of rules to exclude + description: Exclude contains a list of rules to exclude when evaluating rules for the incoming connection. items: properties: @@ -116,7 +115,7 @@ spec: type: string type: object methods: - description: methods is the list of HTTP methods. + description: Methods is the list of HTTP methods. items: type: string type: array @@ -127,7 +126,7 @@ spec: pathRegex: type: string portNames: - description: portNames is a list of workload ports + description: PortNames is a list of workload ports to apply this rule to. The ports specified here must be the ports used in the connection. items: @@ -153,7 +152,7 @@ spec: type: string type: object methods: - description: methods is the list of HTTP methods. If no + description: Methods is the list of HTTP methods. If no methods are specified, this rule will apply to all methods. items: type: string @@ -171,19 +170,19 @@ spec: type: object type: array sources: - description: sources is a list of sources in this traffic permission. + description: Sources is a list of sources in this traffic permission. items: description: Source represents the source identity. To specify any of the wildcard sources, the specific fields need to - be omitted. For example, for a wildcard namespace, identityName + be omitted. For example, for a wildcard namespace, identity_name should be omitted. properties: exclude: - description: exclude is a list of sources to exclude from + description: Exclude is a list of sources to exclude from this source. items: description: ExcludeSource is almost the same as source - but it prevents the addition of matchiing sources. + but it prevents the addition of matching sources. properties: identityName: type: string @@ -255,9 +254,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml index f74743655e..49fc1ae135 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -190,9 +188,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index 0b6b969856..22f816cb18 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -134,9 +132,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml index c4a510ffad..ff3158f2a7 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -196,9 +194,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml index 9b6b2e05c5..e12db4cf20 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: gatewaypolicies.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -277,9 +275,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index e9994d8457..79450327cb 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -442,9 +440,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml index e9bfd8330c..df234ae1eb 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -107,8 +105,7 @@ spec: cacheDuration: description: "CacheDuration is the duration after which cached keys should be expired. \n Default value is 5 minutes." - format: int64 - type: integer + type: string fetchAsynchronously: description: "FetchAsynchronously indicates that the JWKS should be fetched when a client request arrives. Client @@ -124,8 +121,7 @@ spec: description: The timeout for new network connections to hosts in the cluster. If not set, a default value of 5s will be used. - format: int64 - type: integer + type: string discoveryType: description: "DiscoveryType refers to the service discovery type to use for resolving the cluster. \n This defaults @@ -198,15 +194,13 @@ spec: description: "BaseInterval to be used for the next back off computation. \n The default value from envoy is 1s." - format: int64 - type: integer + type: string maxInterval: description: "MaxInternal to be used to specify the maximum interval between retries. Optional but should be greater or equal to BaseInterval. \n Defaults to 10 times BaseInterval." - format: int64 - type: integer + type: string type: object type: object uri: @@ -312,9 +306,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] 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 adbb12bba6..3c22a4842e 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -202,9 +200,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml index 04f8f493e7..9eccd85cad 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -51,9 +49,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index 50df179f04..b568a94962 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -141,9 +139,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index 01e4363f14..ebf64adf67 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -141,9 +139,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index e7ef98d96a..20f2faeb63 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -260,9 +258,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml index a83a4bb7d0..5072fdf391 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: routeauthfilters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -194,9 +192,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml index c27f7d663f..8fa61cb683 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: routeretryfilters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -110,9 +108,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml index 6eb46a6171..0822050cb2 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: routetimeoutfilters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -53,17 +51,9 @@ spec: description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. properties: idleTimeout: - description: A Duration represents the elapsed time between two instants - as an int64 nanosecond count. The representation limits the largest - representable duration to approximately 290 years. - format: int64 - type: integer + type: string requestTimeout: - description: A Duration represents the elapsed time between two instants - as an int64 nanosecond count. The representation limits the largest - representable duration to approximately 290 years. - format: int64 - type: integer + type: string type: object status: properties: @@ -108,9 +98,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml index c71a211f63..4274efffc8 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -124,9 +122,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 2b5ab54acd..7e7bcfaacc 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -560,9 +558,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] 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 a4efd6e958..4718ee24e5 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -306,9 +304,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index 0146eca982..a1e3844b9c 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -343,9 +341,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 31f5ee2924..764b4614f3 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -307,9 +305,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index aa2b592c94..36f9c9f6c9 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -181,9 +179,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index b465cd9494..7f22c65d09 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -1,13 +1,11 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.8.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.1 name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -132,9 +130,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml new file mode 100644 index 0000000000..fda3e4255e --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml @@ -0,0 +1,612 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: grpcroutes.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + shortNames: + - grpc-route + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: GRPCRoute is the Schema for the GRPC Route API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: "NOTE: this should align to the GAMMA/gateway-api version, + or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute + \n This is a Resource type." + properties: + hostnames: + description: "Hostnames are the hostnames for which this GRPCRoute + should respond to requests. \n This is only valid for north/south." + items: + type: string + type: array + parentRefs: + description: "ParentRefs references the resources (usually Services) + that a Route wants to be attached to. \n It is invalid to reference + an identical parent more than once. It is valid to reference multiple + distinct sections within the same parent resource." + items: + description: 'NOTE: roughly equivalent to structs.ResourceReference' + properties: + port: + description: For east/west this is the name of the Consul Service + port to direct traffic to or empty to imply all. For north/south + this is TBD. + type: string + ref: + description: For east/west configuration, this should point + to a Service. For north/south it should point to a Gateway. + properties: + name: + description: Name is the user-given name of the resource + (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of the resource + the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units (i.e. + partition, namespace) in which the resource resides. + properties: + namespace: + description: "Namespace further isolates resources within + a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all partitions." + type: string + peerName: + description: "PeerName identifies which peer the resource + is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. "catalog", + "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when sweeping + or backward-incompatible changes are made to the group's + resource types. + type: string + kind: + description: Kind identifies the specific resource type + within the group. + type: string + type: object + type: object + type: object + type: array + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. Failure behavior here depends on + how many BackendRefs are specified and how many are invalid. + \n If all entries in BackendRefs are invalid, and there are + also no filters specified in this route rule, all traffic + which matches this rule MUST receive a 500 status code. \n + See the GRPCBackendRef definition for the rules about what + makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef + is invalid, 500 status codes MUST be returned for requests + that would have otherwise been routed to an invalid backend. + If multiple backends are specified, and some are invalid, + the proportion of requests that would otherwise have been + routed to an invalid backend MUST receive a 500 status code. + \n For example, if two backends are specified with equal weights, + and one is invalid, 50 percent of traffic must receive a 500. + Implementations may choose how that 50 percent is determined." + items: + properties: + backendRef: + properties: + datacenter: + type: string + port: + description: "For east/west this is the name of the + Consul Service port to direct traffic to or empty + to imply using the same value as the parent ref. + \n For north/south this is TBD." + type: string + ref: + description: For east/west configuration, this should + point to a Service. + properties: + name: + description: Name is the user-given name of the + resource (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of + the resource the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units + (i.e. partition, namespace) in which the resource + resides. + properties: + namespace: + description: "Namespace further isolates resources + within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all partitions." + type: string + peerName: + description: "PeerName identifies which peer + the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. + "catalog", "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when + sweeping or backward-incompatible changes + are made to the group's resource types. + type: string + kind: + description: Kind identifies the specific + resource type within the group. + type: string + type: object + type: object + type: object + filters: + description: Filters defined at this level should be executed + if and only if the request is being forwarded to the + backend defined here. + items: + properties: + requestHeaderModifier: + description: RequestHeaderModifier defines a schema + for a filter that modifies request headers. + properties: + add: + description: Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with + the given header (name, value) before the + action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaderModifier: + description: ResponseHeaderModifier defines a schema + for a filter that modifies response headers. + properties: + add: + description: Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with + the given header (name, value) before the + action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + urlRewrite: + description: URLRewrite defines a schema for a filter + that modifies a request during forwarding. + properties: + pathPrefix: + type: string + type: object + type: object + type: array + weight: + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1." + format: int32 + type: integer + type: object + type: array + filters: + items: + properties: + requestHeaderModifier: + description: RequestHeaderModifier defines a schema for + a filter that modifies request headers. + properties: + add: + description: Add adds the given header(s) (name, value) + to the request before the action. It appends to + any existing values associated with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from the HTTP + request before the action. The value of Remove is + a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with the given + header (name, value) before the action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaderModifier: + description: ResponseHeaderModifier defines a schema for + a filter that modifies response headers. + properties: + add: + description: Add adds the given header(s) (name, value) + to the request before the action. It appends to + any existing values associated with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from the HTTP + request before the action. The value of Remove is + a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with the given + header (name, value) before the action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + urlRewrite: + description: URLRewrite defines a schema for a filter + that modifies a request during forwarding. + properties: + pathPrefix: + type: string + type: object + type: object + type: array + matches: + items: + properties: + headers: + description: Headers specifies gRPC request header matchers. + Multiple match values are ANDed together, meaning, a + request MUST match all the specified headers to select + the route. + items: + properties: + name: + type: string + type: + description: "HeaderMatchType specifies the semantics + of how HTTP header values should be compared. + Valid HeaderMatchType values, along with their + conformance levels, are: \n Note that values may + be added to this enum, implementations must ensure + that unknown values will not cause a crash. \n + Unknown values here must result in the implementation + setting the Accepted Condition for the Route to + status: False, with a Reason of UnsupportedValue." + enum: + - HEADER_MATCH_TYPE_UNSPECIFIED + - HEADER_MATCH_TYPE_EXACT + - HEADER_MATCH_TYPE_REGEX + - HEADER_MATCH_TYPE_PRESENT + - HEADER_MATCH_TYPE_PREFIX + - HEADER_MATCH_TYPE_SUFFIX + format: int32 + type: string + value: + type: string + type: object + type: array + method: + description: Method specifies a gRPC request service/method + matcher. If this field is not specified, all services + and methods will match. + properties: + method: + description: "Value of the method to match against. + If left empty or omitted, will match all services. + \n At least one of Service and Method MUST be a + non-empty string.}" + type: string + service: + description: "Value of the service to match against. + If left empty or omitted, will match any service. + \n At least one of Service and Method MUST be a + non-empty string." + type: string + type: + description: 'Type specifies how to match against + the service and/or method. Support: Core (Exact + with service and method specified)' + enum: + - GRPC_METHOD_MATCH_TYPE_UNSPECIFIED + - GRPC_METHOD_MATCH_TYPE_EXACT + - GRPC_METHOD_MATCH_TYPE_REGEX + format: int32 + type: string + type: object + type: object + type: array + retries: + properties: + number: + description: Number is the number of times to retry the + request when a retryable result occurs. + properties: + value: + description: The uint32 value. + format: int32 + type: integer + type: object + onConditions: + description: RetryOn allows setting envoy specific conditions + when a request should be automatically retried. + items: + type: string + type: array + onConnectFailure: + description: RetryOnConnectFailure allows for connection + failure errors to trigger a retry. + type: boolean + onStatusCodes: + description: RetryOnStatusCodes is a flat list of http response + status codes that are eligible for retry. This again should + be feasible in any reasonable proxy. + items: + format: int32 + type: integer + type: array + type: object + timeouts: + description: HTTPRouteTimeouts defines timeouts that can be + configured for an HTTPRoute or GRPCRoute. + properties: + idle: + description: Idle specifies the total amount of time permitted + for the request stream to be idle. + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + request: + description: RequestTimeout is the total amount of time + permitted for the entire downstream request (and retries) + to be processed. + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + type: object + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml new file mode 100644 index 0000000000..46bf7162a6 --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml @@ -0,0 +1,668 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: httproutes.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + shortNames: + - http-route + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: HTTPRoute is the Schema for the HTTP Route API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: "NOTE: this should align to the GAMMA/gateway-api version, + or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute + \n This is a Resource type." + properties: + hostnames: + description: "Hostnames are the hostnames for which this HTTPRoute + should respond to requests. \n This is only valid for north/south." + items: + type: string + type: array + parentRefs: + description: "ParentRefs references the resources (usually Services) + that a Route wants to be attached to. \n It is invalid to reference + an identical parent more than once. It is valid to reference multiple + distinct sections within the same parent resource." + items: + description: 'NOTE: roughly equivalent to structs.ResourceReference' + properties: + port: + description: For east/west this is the name of the Consul Service + port to direct traffic to or empty to imply all. For north/south + this is TBD. + type: string + ref: + description: For east/west configuration, this should point + to a Service. For north/south it should point to a Gateway. + properties: + name: + description: Name is the user-given name of the resource + (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of the resource + the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units (i.e. + partition, namespace) in which the resource resides. + properties: + namespace: + description: "Namespace further isolates resources within + a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all partitions." + type: string + peerName: + description: "PeerName identifies which peer the resource + is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. "catalog", + "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when sweeping + or backward-incompatible changes are made to the group's + resource types. + type: string + kind: + description: Kind identifies the specific resource type + within the group. + type: string + type: object + type: object + type: object + type: array + rules: + description: Rules are a list of HTTP-based routing rules that this + route should use for constructing a routing table. + items: + description: HTTPRouteRule specifies the routing rules used to determine + what upstream service an HTTP request is routed to. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. \n Failure behavior here depends + on how many BackendRefs are specified and how many are invalid. + \n If all entries in BackendRefs are invalid, and there are + also no filters specified in this route rule, all traffic + which matches this rule MUST receive a 500 status code. \n + See the HTTPBackendRef definition for the rules about what + makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef + is invalid, 500 status codes MUST be returned for requests + that would have otherwise been routed to an invalid backend. + If multiple backends are specified, and some are invalid, + the proportion of requests that would otherwise have been + routed to an invalid backend MUST receive a 500 status code. + \n For example, if two backends are specified with equal weights, + and one is invalid, 50 percent of traffic must receive a 500. + Implementations may choose how that 50 percent is determined." + items: + properties: + backendRef: + properties: + datacenter: + type: string + port: + description: "For east/west this is the name of the + Consul Service port to direct traffic to or empty + to imply using the same value as the parent ref. + \n For north/south this is TBD." + type: string + ref: + description: For east/west configuration, this should + point to a Service. + properties: + name: + description: Name is the user-given name of the + resource (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of + the resource the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units + (i.e. partition, namespace) in which the resource + resides. + properties: + namespace: + description: "Namespace further isolates resources + within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all partitions." + type: string + peerName: + description: "PeerName identifies which peer + the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. + "catalog", "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when + sweeping or backward-incompatible changes + are made to the group's resource types. + type: string + kind: + description: Kind identifies the specific + resource type within the group. + type: string + type: object + type: object + type: object + filters: + description: Filters defined at this level should be executed + if and only if the request is being forwarded to the + backend defined here. + items: + properties: + requestHeaderModifier: + description: RequestHeaderModifier defines a schema + for a filter that modifies request headers. + properties: + add: + description: Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with + the given header (name, value) before the + action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaderModifier: + description: ResponseHeaderModifier defines a schema + for a filter that modifies response headers. + properties: + add: + description: Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with + the given header (name, value) before the + action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + urlRewrite: + description: URLRewrite defines a schema for a filter + that modifies a request during forwarding. + properties: + pathPrefix: + type: string + type: object + type: object + type: array + weight: + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1." + format: int32 + type: integer + type: object + type: array + filters: + items: + properties: + requestHeaderModifier: + description: RequestHeaderModifier defines a schema for + a filter that modifies request headers. + properties: + add: + description: Add adds the given header(s) (name, value) + to the request before the action. It appends to + any existing values associated with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from the HTTP + request before the action. The value of Remove is + a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with the given + header (name, value) before the action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaderModifier: + description: ResponseHeaderModifier defines a schema for + a filter that modifies response headers. + properties: + add: + description: Add adds the given header(s) (name, value) + to the request before the action. It appends to + any existing values associated with the header name. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + remove: + description: Remove the given header(s) from the HTTP + request before the action. The value of Remove is + a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + items: + type: string + type: array + set: + description: Set overwrites the request with the given + header (name, value) before the action. + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + urlRewrite: + description: URLRewrite defines a schema for a filter + that modifies a request during forwarding. + properties: + pathPrefix: + type: string + type: object + type: object + type: array + matches: + items: + properties: + headers: + description: Headers specifies HTTP request header matchers. + Multiple match values are ANDed together, meaning, a + request must match all the specified headers to select + the route. + items: + properties: + invert: + description: 'NOTE: not in gamma; service-router + compat' + type: boolean + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case insensitive. + (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent header + names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be + ignored. Due to the case-insensitivity of header + names, “foo” and “Foo” are considered equivalent. + \n When a header is repeated in an HTTP request, + it is implementation-specific behavior as to how + this is represented. Generally, proxies should + follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 + regarding processing a repeated header, with special + handling for “Set-Cookie”." + type: string + type: + description: Type specifies how to match against + the value of the header. + enum: + - HEADER_MATCH_TYPE_UNSPECIFIED + - HEADER_MATCH_TYPE_EXACT + - HEADER_MATCH_TYPE_REGEX + - HEADER_MATCH_TYPE_PRESENT + - HEADER_MATCH_TYPE_PREFIX + - HEADER_MATCH_TYPE_SUFFIX + format: int32 + type: string + value: + description: Value is the value of HTTP Header to + be matched. + type: string + type: object + type: array + method: + description: Method specifies HTTP method matcher. When + specified, this route will be matched only if the request + has the specified method. + type: string + path: + description: Path specifies a HTTP request path matcher. + If this field is not specified, a default prefix match + on the “/” path is provided. + properties: + type: + description: Type specifies how to match against the + path Value. + enum: + - PATH_MATCH_TYPE_UNSPECIFIED + - PATH_MATCH_TYPE_EXACT + - PATH_MATCH_TYPE_PREFIX + - PATH_MATCH_TYPE_REGEX + format: int32 + type: string + value: + description: Value of the HTTP path to match against. + type: string + type: object + queryParams: + description: QueryParams specifies HTTP query parameter + matchers. Multiple match values are ANDed together, + meaning, a request must match all the specified query + parameters to select the route. + items: + properties: + name: + description: "Name is the name of the HTTP query + param to be matched. This must be an exact string + match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). + \n If multiple entries specify equivalent query + param names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST + be ignored. \n If a query param is repeated in + an HTTP request, the behavior is purposely left + undefined, since different data planes have different + capabilities. However, it is recommended that + implementations should match against the first + value of the param if the data plane supports + it, as this behavior is expected in other load + balancing contexts outside of the Gateway API. + \n Users SHOULD NOT route traffic based on repeated + query params to guard themselves against potential + differences in the implementations." + type: string + type: + description: Type specifies how to match against + the value of the query parameter. + enum: + - QUERY_PARAM_MATCH_TYPE_UNSPECIFIED + - QUERY_PARAM_MATCH_TYPE_EXACT + - QUERY_PARAM_MATCH_TYPE_REGEX + - QUERY_PARAM_MATCH_TYPE_PRESENT + format: int32 + type: string + value: + description: Value is the value of HTTP query param + to be matched. + type: string + type: object + type: array + type: object + type: array + retries: + properties: + number: + description: Number is the number of times to retry the + request when a retryable result occurs. + properties: + value: + description: The uint32 value. + format: int32 + type: integer + type: object + onConditions: + description: RetryOn allows setting envoy specific conditions + when a request should be automatically retried. + items: + type: string + type: array + onConnectFailure: + description: RetryOnConnectFailure allows for connection + failure errors to trigger a retry. + type: boolean + onStatusCodes: + description: RetryOnStatusCodes is a flat list of http response + status codes that are eligible for retry. This again should + be feasible in any reasonable proxy. + items: + format: int32 + type: integer + type: array + type: object + timeouts: + description: HTTPRouteTimeouts defines timeouts that can be + configured for an HTTPRoute or GRPCRoute. + properties: + idle: + description: Idle specifies the total amount of time permitted + for the request stream to be idle. + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + request: + description: RequestTimeout is the total amount of time + permitted for the entire downstream request (and retries) + to be processed. + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + type: object + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml new file mode 100644 index 0000000000..1d15b34111 --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml @@ -0,0 +1,418 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: proxyconfigurations.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: ProxyConfiguration + listKind: ProxyConfigurationList + plural: proxyconfigurations + shortNames: + - proxy-configuration + singular: proxyconfiguration + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: ProxyConfiguration is the Schema for the TCP Routes API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: This is a Resource type. + properties: + bootstrapConfig: + description: bootstrap_config is the configuration that requires proxies + to be restarted to be applied. + properties: + dogstatsdUrl: + type: string + overrideJsonTpl: + type: string + prometheusBindAddr: + type: string + readyBindAddr: + type: string + staticClustersJson: + type: string + staticListenersJson: + type: string + statsBindAddr: + type: string + statsConfigJson: + type: string + statsFlushInterval: + type: string + statsSinksJson: + type: string + statsTags: + items: + type: string + type: array + statsdUrl: + type: string + telemetryCollectorBindSocketDir: + type: string + tracingConfigJson: + type: string + type: object + dynamicConfig: + description: dynamic_config is the configuration that could be changed + dynamically (i.e. without needing restart). + properties: + accessLogs: + description: AccessLogs configures the output and format of Envoy + access logs + properties: + disableListenerLogs: + description: DisableListenerLogs turns off just listener logs + for connections rejected by Envoy because they don't have + a matching listener filter. + type: boolean + enabled: + description: Enabled turns off all access logging + type: boolean + jsonFormat: + description: The presence of one format string or the other + implies the access log string encoding. Defining both is + invalid. + type: string + path: + description: Path is the output file to write logs + type: string + textFormat: + type: string + type: + description: 'Type selects the output for logs: "file", "stderr". + "stdout"' + enum: + - LOG_SINK_TYPE_DEFAULT + - LOG_SINK_TYPE_FILE + - LOG_SINK_TYPE_STDERR + - LOG_SINK_TYPE_STDOUT + format: int32 + type: string + type: object + envoyExtensions: + items: + description: EnvoyExtension has configuration for an extension + that patches Envoy resources. + properties: + arguments: + type: object + x-kubernetes-preserve-unknown-fields: true + consulVersion: + type: string + envoyVersion: + type: string + name: + type: string + required: + type: boolean + type: object + type: array + exposeConfig: + properties: + exposePaths: + items: + properties: + listenerPort: + format: int32 + type: integer + localPathPort: + format: int32 + type: integer + path: + type: string + protocol: + enum: + - EXPOSE_PATH_PROTOCOL_HTTP + - EXPOSE_PATH_PROTOCOL_HTTP2 + format: int32 + type: string + type: object + type: array + type: object + inboundConnections: + description: inbound_connections configures inbound connections + to the proxy. + properties: + balanceInboundConnections: + enum: + - BALANCE_CONNECTIONS_DEFAULT + - BALANCE_CONNECTIONS_EXACT + format: int32 + type: string + maxInboundConnections: + format: int64 + type: integer + type: object + listenerTracingJson: + type: string + localClusterJson: + type: string + localConnection: + additionalProperties: + description: Referenced by ProxyConfiguration + properties: + connectTimeout: + description: "A Duration represents a signed, fixed-length + span of time represented as a count of seconds and fractions + of seconds at nanosecond resolution. It is independent + of any calendar and concepts like \"day\" or \"month\". + It is related to Timestamp in that the difference between + two Timestamp values is a Duration and it can be added + or subtracted from a Timestamp. Range is approximately + +-10,000 years. \n # Examples \n Example 1: Compute Duration + from two Timestamps in pseudo code. \n Timestamp start + = ...; Timestamp end = ...; Duration duration = ...; \n + duration.seconds = end.seconds - start.seconds; duration.nanos + = end.nanos - start.nanos; \n if (duration.seconds < 0 + && duration.nanos > 0) { duration.seconds += 1; duration.nanos + -= 1000000000; } else if (duration.seconds > 0 && duration.nanos + < 0) { duration.seconds -= 1; duration.nanos += 1000000000; + } \n Example 2: Compute Timestamp from Timestamp + Duration + in pseudo code. \n Timestamp start = ...; Duration duration + = ...; Timestamp end = ...; \n end.seconds = start.seconds + + duration.seconds; end.nanos = start.nanos + duration.nanos; + \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += + 1000000000; } else if (end.nanos >= 1000000000) { end.seconds + += 1; end.nanos -= 1000000000; } \n Example 3: Compute + Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, + minutes=10) duration = Duration() duration.FromTimedelta(td) + \n # JSON Mapping \n In JSON format, the Duration type + is encoded as a string rather than an object, where the + string ends in the suffix \"s\" (indicating seconds) and + is preceded by the number of seconds, with nanoseconds + expressed as fractional seconds. For example, 3 seconds + with 0 nanoseconds should be encoded in JSON format as + \"3s\", while 3 seconds and 1 nanosecond should be expressed + in JSON format as \"3.000000001s\", and 3 seconds and + 1 microsecond should be expressed in JSON format as \"3.000001s\"." + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + requestTimeout: + description: "A Duration represents a signed, fixed-length + span of time represented as a count of seconds and fractions + of seconds at nanosecond resolution. It is independent + of any calendar and concepts like \"day\" or \"month\". + It is related to Timestamp in that the difference between + two Timestamp values is a Duration and it can be added + or subtracted from a Timestamp. Range is approximately + +-10,000 years. \n # Examples \n Example 1: Compute Duration + from two Timestamps in pseudo code. \n Timestamp start + = ...; Timestamp end = ...; Duration duration = ...; \n + duration.seconds = end.seconds - start.seconds; duration.nanos + = end.nanos - start.nanos; \n if (duration.seconds < 0 + && duration.nanos > 0) { duration.seconds += 1; duration.nanos + -= 1000000000; } else if (duration.seconds > 0 && duration.nanos + < 0) { duration.seconds -= 1; duration.nanos += 1000000000; + } \n Example 2: Compute Timestamp from Timestamp + Duration + in pseudo code. \n Timestamp start = ...; Duration duration + = ...; Timestamp end = ...; \n end.seconds = start.seconds + + duration.seconds; end.nanos = start.nanos + duration.nanos; + \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += + 1000000000; } else if (end.nanos >= 1000000000) { end.seconds + += 1; end.nanos -= 1000000000; } \n Example 3: Compute + Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, + minutes=10) duration = Duration() duration.FromTimedelta(td) + \n # JSON Mapping \n In JSON format, the Duration type + is encoded as a string rather than an object, where the + string ends in the suffix \"s\" (indicating seconds) and + is preceded by the number of seconds, with nanoseconds + expressed as fractional seconds. For example, 3 seconds + with 0 nanoseconds should be encoded in JSON format as + \"3s\", while 3 seconds and 1 nanosecond should be expressed + in JSON format as \"3.000000001s\", and 3 seconds and + 1 microsecond should be expressed in JSON format as \"3.000001s\"." + format: duration + properties: + nanos: + description: Signed fractions of a second at nanosecond + resolution of the span of time. Durations less than + one second are represented with a 0 `seconds` field + and a positive or negative `nanos` field. For durations + of one second or more, a non-zero value for the `nanos` + field must be of the same sign as the `seconds` field. + Must be from -999,999,999 to +999,999,999 inclusive. + format: int32 + type: integer + seconds: + description: 'Signed seconds of the span of time. Must + be from -315,576,000,000 to +315,576,000,000 inclusive. + Note: these bounds are computed from: 60 sec/min * + 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' + format: int64 + type: integer + type: object + type: object + description: local_connection is the configuration that should + be used to connect to the local application provided per-port. + The map keys should correspond to port names on the workload. + type: object + localWorkloadAddress: + description: "deprecated: local_workload_address, local_workload_port, + and local_workload_socket_path are deprecated and are only needed + for migration of existing resources. \n Deprecated: Marked as + deprecated in pbmesh/v2beta1/proxy_configuration.proto." + type: string + localWorkloadPort: + description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' + format: int32 + type: integer + localWorkloadSocketPath: + description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' + type: string + meshGatewayMode: + enum: + - MESH_GATEWAY_MODE_UNSPECIFIED + - MESH_GATEWAY_MODE_NONE + - MESH_GATEWAY_MODE_LOCAL + - MESH_GATEWAY_MODE_REMOTE + format: int32 + type: string + mode: + description: mode indicates the proxy's mode. This will default + to 'transparent'. + enum: + - PROXY_MODE_DEFAULT + - PROXY_MODE_TRANSPARENT + - PROXY_MODE_DIRECT + format: int32 + type: string + mutualTlsMode: + enum: + - MUTUAL_TLS_MODE_DEFAULT + - MUTUAL_TLS_MODE_STRICT + - MUTUAL_TLS_MODE_PERMISSIVE + format: int32 + type: string + publicListenerJson: + type: string + transparentProxy: + properties: + dialedDirectly: + description: dialed_directly indicates whether this proxy + should be dialed using original destination IP in the connection + rather than load balance between all endpoints. + type: boolean + outboundListenerPort: + description: outbound_listener_port is the port for the proxy's + outbound listener. This defaults to 15001. + format: int32 + type: integer + type: object + type: object + opaqueConfig: + description: "deprecated: prevent usage when using v2 APIs directly. + needed for backwards compatibility \n Deprecated: Marked as deprecated + in pbmesh/v2beta1/proxy_configuration.proto." + type: object + x-kubernetes-preserve-unknown-fields: true + workloads: + description: Selection of workloads this proxy configuration should + apply to. These can be prefixes or specific workload names. + properties: + filter: + type: string + names: + items: + type: string + type: array + prefixes: + items: + type: string + type: array + type: object + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml new file mode 100644 index 0000000000..21a3a9c5ec --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml @@ -0,0 +1,273 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: tcproutes.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + shortNames: + - tcp-route + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: TCPRoute is the Schema for the TCP Route API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: "NOTE: this should align to the GAMMA/gateway-api version, + or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute + \n This is a Resource type." + properties: + parentRefs: + description: "ParentRefs references the resources (usually Services) + that a Route wants to be attached to. \n It is invalid to reference + an identical parent more than once. It is valid to reference multiple + distinct sections within the same parent resource." + items: + description: 'NOTE: roughly equivalent to structs.ResourceReference' + properties: + port: + description: For east/west this is the name of the Consul Service + port to direct traffic to or empty to imply all. For north/south + this is TBD. + type: string + ref: + description: For east/west configuration, this should point + to a Service. For north/south it should point to a Gateway. + properties: + name: + description: Name is the user-given name of the resource + (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of the resource + the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units (i.e. + partition, namespace) in which the resource resides. + properties: + namespace: + description: "Namespace further isolates resources within + a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all partitions." + type: string + peerName: + description: "PeerName identifies which peer the resource + is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, provide + the wildcard value \"*\" to list resources across + all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. "catalog", + "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when sweeping + or backward-incompatible changes are made to the group's + resource types. + type: string + kind: + description: Kind identifies the specific resource type + within the group. + type: string + type: object + type: object + type: object + type: array + rules: + description: Rules are a list of TCP matchers and actions. + items: + properties: + backendRefs: + description: BackendRefs defines the backend(s) where matching + requests should be sent. If unspecified or invalid (refers + to a non-existent resource or a Service with no endpoints), + the underlying implementation MUST actively reject connection + attempts to this backend. Connection rejections must respect + weight; if an invalid backend is requested to have 80% of + connections, then 80% of connections must be rejected instead. + items: + properties: + backendRef: + properties: + datacenter: + type: string + port: + description: "For east/west this is the name of the + Consul Service port to direct traffic to or empty + to imply using the same value as the parent ref. + \n For north/south this is TBD." + type: string + ref: + description: For east/west configuration, this should + point to a Service. + properties: + name: + description: Name is the user-given name of the + resource (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of + the resource the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units + (i.e. partition, namespace) in which the resource + resides. + properties: + namespace: + description: "Namespace further isolates resources + within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all partitions." + type: string + peerName: + description: "PeerName identifies which peer + the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list + resources across all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. + "catalog", "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when + sweeping or backward-incompatible changes + are made to the group's resource types. + type: string + kind: + description: Kind identifies the specific + resource type within the group. + type: string + type: object + type: object + type: object + weight: + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1." + format: int32 + type: integer + type: object + type: array + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml index ff0b2fc2f6..044c7af939 100644 --- a/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml index fa64481667..b7a7c8a7d1 100644 --- a/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml index 8d190ea7b6..8f3ab6d385 100644 --- a/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml index 90c151a787..b455d788de 100644 --- a/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml index 5eee4889a4..cd39b9c12a 100644 --- a/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml index a136b28f41..906b442d31 100644 --- a/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml index cc3cf65d6c..2e22b04ef0 100644 --- a/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml index 204f8e4824..19b03dcd0b 100644 --- a/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 88c7c44980..cce5208eda 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -5,7 +5,6 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - creationTimestamp: null name: manager-role rules: - apiGroups: @@ -346,3 +345,83 @@ rules: - get - patch - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - grpcroute + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - grpcroute/status + verbs: + - get + - patch + - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - httproute + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - httproute/status + verbs: + - get + - patch + - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - proxyconfiguration + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - proxyconfiguration/status + verbs: + - get + - patch + - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - tcproute + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - tcproute/status + verbs: + - get + - patch + - update diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index 6b675d459a..b5e3bb52fd 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -5,7 +5,6 @@ apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: - creationTimestamp: null name: mutating-webhook-configuration webhooks: - admissionReviewVersions: @@ -344,11 +343,94 @@ webhooks: resources: - trafficpermissions sideEffects: None +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v2beta1-grpcroute + failurePolicy: Fail + name: mutate-grpcroute.auth.consul.hashicorp.com + rules: + - apiGroups: + - auth.consul.hashicorp.com + apiVersions: + - v2beta1 + operations: + - CREATE + - UPDATE + resources: + - grpcroute + sideEffects: None +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v2beta1-httproute + failurePolicy: Fail + name: mutate-httproute.auth.consul.hashicorp.com + rules: + - apiGroups: + - auth.consul.hashicorp.com + apiVersions: + - v2beta1 + operations: + - CREATE + - UPDATE + resources: + - httproute + sideEffects: None +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v2beta1-proxyconfiguration + failurePolicy: Fail + name: mutate-proxyconfiguration.auth.consul.hashicorp.com + rules: + - apiGroups: + - auth.consul.hashicorp.com + apiVersions: + - v2beta1 + operations: + - CREATE + - UPDATE + resources: + - proxyconfiguration + sideEffects: None +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v2beta1-tcproute + failurePolicy: Fail + name: mutate-tcproute.auth.consul.hashicorp.com + rules: + - apiGroups: + - auth.consul.hashicorp.com + apiVersions: + - v2beta1 + operations: + - CREATE + - UPDATE + resources: + - tcproute + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: - creationTimestamp: null name: validating-webhook-configuration webhooks: - admissionReviewVersions: diff --git a/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go b/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go index 059af35257..2b22c5707a 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package endpointsv2 import ( diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index 22af958fd9..8fdb2e0167 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -181,7 +181,7 @@ func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { k8sObjects: func() []runtime.Object { pod := createPod(testPodName, metav1.NamespaceDefault, true, true) addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" + pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" return []runtime.Object{pod} }, tproxy: false, @@ -425,7 +425,7 @@ func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { k8sObjects: func() []runtime.Object { pod := createPod(testPodName, metav1.NamespaceDefault, true, true) addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" + pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" return []runtime.Object{pod} }, telemetry: true, diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index ed48f7bc78..3ae9f3747c 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -814,7 +814,7 @@ func TestDestinationsWrite(t *testing.T) { name: "labeled annotated destination with svc only", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" + pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc:1234" return pod1 }, expected: &pbmesh.Destinations{ @@ -832,7 +832,7 @@ func TestDestinationsWrite(t *testing.T) { }, Name: "upstream1", }, - DestinationPort: "myPort", + DestinationPort: "destination", Datacenter: "", ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ @@ -850,10 +850,10 @@ func TestDestinationsWrite(t *testing.T) { name: "labeled annotated destination with svc, ns, and peer", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234" + pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.peer1.peer:1234" return pod1 }, - expErr: "error processing destination annotations: destination currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", + expErr: "error processing destination annotations: destination currently does not support peers: destination.port.upstream1.svc.ns1.ns.peer1.peer:1234", // TODO: uncomment this and remove expErr when peers is supported //expected: &pbmesh.Destinations{ // Workloads: &pbcatalog.WorkloadSelector{ @@ -870,7 +870,7 @@ func TestDestinationsWrite(t *testing.T) { // }, // Name: "upstream1", // }, - // DestinationPort: "myPort", + // DestinationPort: "destination", // Datacenter: "", // ListenAddr: &pbmesh.Destination_IpPort{ // IpPort: &pbmesh.IPPortAddress{ @@ -888,7 +888,7 @@ func TestDestinationsWrite(t *testing.T) { name: "labeled annotated destination with svc, ns, and partition", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234" + pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.part1.ap:1234" return pod1 }, expected: &pbmesh.Destinations{ @@ -906,7 +906,7 @@ func TestDestinationsWrite(t *testing.T) { }, Name: "upstream1", }, - DestinationPort: "myPort", + DestinationPort: "destination", Datacenter: "", ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ @@ -924,10 +924,10 @@ func TestDestinationsWrite(t *testing.T) { name: "error labeled annotated destination error: invalid partition/dc/peer", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc.ns1.ns.part1.err:1234" + pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.part1.err:1234" return pod1 }, - expErr: "error processing destination annotations: destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", + expErr: "error processing destination annotations: destination structured incorrectly: destination.port.upstream1.svc.ns1.ns.part1.err:1234", consulNamespacesEnabled: true, consulPartitionsEnabled: false, }, @@ -935,7 +935,7 @@ func TestDestinationsWrite(t *testing.T) { name: "unlabeled single destination", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream:1234" + pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.upstream:1234" return pod1 }, expected: &pbmesh.Destinations{ @@ -953,7 +953,7 @@ func TestDestinationsWrite(t *testing.T) { }, Name: "upstream", }, - DestinationPort: "myPort", + DestinationPort: "destination", Datacenter: "", ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ @@ -971,7 +971,7 @@ func TestDestinationsWrite(t *testing.T) { name: "unlabeled single destination with namespace and partition", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.upstream.foo.bar:1234" + pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.upstream.foo.bar:1234" return pod1 }, expected: &pbmesh.Destinations{ @@ -989,7 +989,7 @@ func TestDestinationsWrite(t *testing.T) { }, Name: "upstream", }, - DestinationPort: "myPort", + DestinationPort: "destination", Datacenter: "", ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ @@ -1061,7 +1061,7 @@ func TestDestinationsDelete(t *testing.T) { name: "labeled annotated destination with svc only", pod: func() *corev1.Pod { pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.upstream1.svc:1234" + pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc:1234" return pod1 }, existingDestinations: &pbmesh.Destinations{ @@ -1079,7 +1079,7 @@ func TestDestinationsDelete(t *testing.T) { }, Name: "upstream1", }, - DestinationPort: "myPort", + DestinationPort: "destination", Datacenter: "", ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ @@ -1315,7 +1315,7 @@ func TestReconcileCreatePod(t *testing.T) { k8sObjects: func() []runtime.Object { pod := createPod("foo", "", true, true) addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" + pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" return []runtime.Object{pod} }, tproxy: false, @@ -1567,7 +1567,7 @@ func TestReconcileUpdatePod(t *testing.T) { podName: "foo", k8sObjects: func() []runtime.Object { pod := createPod("foo", "", true, true) - pod.Annotations[constants.AnnotationMeshDestinations] = "myPort.port.mySVC.svc:24601" + pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" return []runtime.Object{pod} }, existingWorkload: createWorkload(), @@ -1587,7 +1587,7 @@ func TestReconcileUpdatePod(t *testing.T) { }, Name: "mySVC3", }, - DestinationPort: "myPort2", + DestinationPort: "destination2", Datacenter: "", ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ @@ -1955,7 +1955,7 @@ func createDestinations() *pbmesh.Destinations { }, Name: "mySVC", }, - DestinationPort: "myPort", + DestinationPort: "destination", Datacenter: "", ListenAddr: &pbmesh.Destination_IpPort{ IpPort: &pbmesh.IPPortAddress{ diff --git a/control-plane/go.mod b/control-plane/go.mod index 2b2c470e57..1f9c3e06fb 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases replace ( // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version - github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c + github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58 // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 ) diff --git a/control-plane/go.sum b/control-plane/go.sum index f04eab1013..d1b115f096 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNb github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 h1:LnzgDq4e7ZfM1+XS6S21B9taQrbfdydXenL1xHyG1PQ= github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c h1:d1ULTfDs6Hha01yfITC55MPGIsQpv0VqQfS45WZHJiY= -github.com/hashicorp/consul/proto-public v0.1.2-0.20230929231147-632fd65c091c/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58 h1:3VHvqLs2zTa9YWMsE4A9IricAZB6rAcXVe8e+pb5slw= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 h1:j0Rvt1KiKIKlMc2UnA0ilHfIGo66SzrBztGZaCou3+w= github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 0d9fba6cb0..a81a290dc4 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -30,8 +30,9 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/api/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" @@ -163,7 +164,8 @@ func init() { utilruntime.Must(gwv1alpha2.AddToScheme(scheme)) // V2 resources - utilruntime.Must(v2beta1.AddAuthToScheme(scheme)) + utilruntime.Must(authv2beta1.AddAuthToScheme(scheme)) + utilruntime.Must(meshv2beta1.AddMeshToScheme(scheme)) //+kubebuilder:scaffold:scheme } diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index da522c6840..abe4b38c53 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -11,8 +11,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v2beta1" + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllersv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" @@ -140,6 +141,42 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.TrafficPermissions) return err } + if err := (&controllersv2.GRPCRouteController{ + MeshConfigController: meshConfigReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.GRPCRoute), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.GRPCRoute) + return err + } + if err := (&controllersv2.HTTPRouteController{ + MeshConfigController: meshConfigReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.HTTPRoute), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.HTTPRoute) + return err + } + if err := (&controllersv2.TCPRouteController{ + MeshConfigController: meshConfigReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.TCPRoute), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.TCPRoute) + return err + } + if err := (&controllersv2.ProxyConfigurationController{ + MeshConfigController: meshConfigReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.ProxyConfiguration), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.ProxyConfiguration) + return err + } mgr.GetWebhookServer().CertDir = c.flagCertDir @@ -187,11 +224,35 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage }}) mgr.GetWebhookServer().Register("/mutate-v2beta1-trafficpermissions", - &ctrlRuntimeWebhook.Admission{Handler: &v2beta1.TrafficPermissionsWebhook{ + &ctrlRuntimeWebhook.Admission{Handler: &authv2beta1.TrafficPermissionsWebhook{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("webhooks").WithName(common.TrafficPermissions), ConsulTenancyConfig: consulTenancyConfig, }}) + mgr.GetWebhookServer().Register("/mutate-v2beta1-proxyconfigurations", + &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.ProxyConfigurationWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(common.ProxyConfiguration), + ConsulTenancyConfig: consulTenancyConfig, + }}) + mgr.GetWebhookServer().Register("/mutate-v2beta1-httproute", + &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.HTTPRouteWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(common.HTTPRoute), + ConsulTenancyConfig: consulTenancyConfig, + }}) + mgr.GetWebhookServer().Register("/mutate-v2beta1-grpcroute", + &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.GRPCRouteWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(common.GRPCRoute), + ConsulTenancyConfig: consulTenancyConfig, + }}) + mgr.GetWebhookServer().Register("/mutate-v2beta1-tcproute", + &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.TCPRouteWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(common.TCPRoute), + ConsulTenancyConfig: consulTenancyConfig, + }}) if err := mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { setupLog.Error(err, "unable to create readiness check") diff --git a/hack/camel-crds/go.mod b/hack/camel-crds/go.mod new file mode 100644 index 0000000000..0262e70483 --- /dev/null +++ b/hack/camel-crds/go.mod @@ -0,0 +1,15 @@ +module github.com/hashicorp/consul-k8s/hack/copy-crds-to-chart + +go 1.20 + +require ( + github.com/iancoleman/strcase v0.3.0 + sigs.k8s.io/yaml v1.3.0 +) + +require ( + github.com/kr/pretty v0.3.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/hack/camel-crds/go.sum b/hack/camel-crds/go.sum new file mode 100644 index 0000000000..21efe55d32 --- /dev/null +++ b/hack/camel-crds/go.sum @@ -0,0 +1,25 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +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= +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= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/camel-crds/main.go b/hack/camel-crds/main.go new file mode 100644 index 0000000000..dde2ac3de2 --- /dev/null +++ b/hack/camel-crds/main.go @@ -0,0 +1,117 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Script to parse a YAML CRD file and change all the +// snake_case keys to camelCase and rewrite the file in-situ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/iancoleman/strcase" + "sigs.k8s.io/yaml" +) + +func main() { + if len(os.Args) != 1 { + fmt.Println("Usage: go run ./...") + os.Exit(1) + } + + if err := realMain(); err != nil { + fmt.Printf("Error: %s\n", err) + os.Exit(1) + } + os.Exit(0) +} + +func realMain() error { + root := "../../control-plane/config/crd/" + // explicitly ignore the `external` folder since we only want this to apply to CRDs that we have built-in this project. + dirs := []string{"bases"} + + for _, dir := range dirs { + err := filepath.Walk(root+dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() || filepath.Ext(path) != ".yaml" || filepath.Base(path) == "kustomization.yaml" { + return nil + } + printf("processing %s", filepath.Base(path)) + + contentBytes, err := os.ReadFile(path) + if err != nil { + return err + } + + jsonBytes, err := yaml.YAMLToJSON(contentBytes) + if err != nil { + return err + } + fixedJsonBytes := convertKeys(jsonBytes) + contentsCamel, err := yaml.JSONToYAML(fixedJsonBytes) + return os.WriteFile(path, contentsCamel, os.ModePerm) + }) + if err != nil { + return err + } + } + return nil +} + +func convertKeys(j json.RawMessage) json.RawMessage { + m := make(map[string]json.RawMessage) + n := make([]json.RawMessage, 0) + array := false + if err := json.Unmarshal(j, &m); err != nil { + // Not a JSON object + errArray := json.Unmarshal(j, &n) + if errArray != nil { + return j + } else { + array = true + } + } + if !array { + for k, v := range m { + if k == "annotations" { + continue + } + var fixed string + if !strings.Contains(k, "_") { + fixed = k + } else { + fixed = strcase.ToLowerCamel(k) + } + delete(m, k) + m[fixed] = convertKeys(v) + } + + b, err := json.Marshal(m) + if err != nil { + fmt.Printf("something went wrong", err) + return j + } + return b + } else { + for i, message := range n { + fixed := convertKeys(message) + n[i] = fixed + } + b, err := json.Marshal(n) + if err != nil { + fmt.Printf("something went wrong", err) + return j + } + return b + } +} + +func printf(format string, args ...interface{}) { + fmt.Println(fmt.Sprintf(format, args...)) +} diff --git a/hack/copy-crds-to-chart/main.go b/hack/copy-crds-to-chart/main.go index 149126b392..435918c53d 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -79,7 +79,13 @@ func realMain(helmPath string) error { ` release: {{ .Release.Name }}`, ` component: crd`, } - withLabels := append(splitOnNewlines[0:9], append(labelLines, splitOnNewlines[9:]...)...) + var split int + if dir == "bases" { + split = 6 + } else { + split = 9 + } + withLabels := append(splitOnNewlines[0:split], append(labelLines, splitOnNewlines[split:]...)...) contents = strings.Join(withLabels, "\n") var crdName string @@ -89,7 +95,7 @@ func realMain(helmPath string) error { crdName = filenameSplit[1] } else if dir == "external" { filenameSplit := strings.Split(info.Name(), ".") - crdName = filenameSplit[0] + ".yaml" + crdName = filenameSplit[0] + "-external.yaml" } destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName)) From 5d498f1391669d618890b6399320f1be9de47929 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 16 Oct 2023 17:55:45 -0400 Subject: [PATCH 438/592] [NET-5944] security: Upgrade Go and x/net (#3085) security: Upgrade Go and x/net Upgrade to Go 1.20.10 and `x/net` 1.17.0 to resolve [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) / [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). --- .changelog/3085.txt | 5 +++++ .go-version | 2 +- acceptance/go.mod | 10 +++++----- acceptance/go.sum | 29 ++++++++++------------------- cli/go.mod | 10 +++++----- cli/go.sum | 20 ++++++++++---------- control-plane/cni/go.mod | 8 ++++---- control-plane/cni/go.sum | 16 ++++++++-------- control-plane/go.mod | 10 +++++----- control-plane/go.sum | 20 ++++++++++---------- 10 files changed, 63 insertions(+), 67 deletions(-) create mode 100644 .changelog/3085.txt diff --git a/.changelog/3085.txt b/.changelog/3085.txt new file mode 100644 index 0000000000..2597a740da --- /dev/null +++ b/.changelog/3085.txt @@ -0,0 +1,5 @@ +```release-note:security +Upgrade to use Go 1.20.10 and `x/net` 0.17.0. +This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) +/ [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). +``` diff --git a/.go-version b/.go-version index 95393fc7d4..acdfc7930c 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.20.8 +1.20.10 diff --git a/acceptance/go.mod b/acceptance/go.mod index 12fa4a80f7..8ba1ebd0bb 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -119,14 +119,14 @@ require ( go.opentelemetry.io/otel v1.11.1 // indirect go.opentelemetry.io/otel/trace v1.11.1 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.13.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index c3645487ed..91dc2e489f 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -42,14 +42,12 @@ github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8 github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.5/go.mod h1:foo3aIXRQ90zFve3r0QiDsrjGDUwWhKl0ZOQy1CT14k= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/azure/auth v0.5.1/go.mod h1:ea90/jvmnAwDrSooLH4sRIehEPtG/EPUXavDh31MnA4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= @@ -66,7 +64,6 @@ github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQ github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -94,7 +91,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= @@ -196,7 +192,6 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -316,7 +311,6 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -349,7 +343,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -406,8 +399,6 @@ github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+Xb github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 h1:4iI0ztWbVPTSDax+m1/XDs4jIRorxY4kSMyuM0fX+Dc= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= -github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924 h1:NPhzdwDho2r8pQv31oeGLlco7fnJ1i0WLYjtSXqWEck= -github.com/hashicorp/consul/api v1.10.1-0.20230825164720-ecdcde430924/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= @@ -809,8 +800,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -896,8 +887,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -993,13 +984,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1010,8 +1001,8 @@ 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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/cli/go.mod b/cli/go.mod index 75bb12479b..eb678f27e2 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -17,7 +17,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.8.3 - golang.org/x/text v0.11.0 + golang.org/x/text v0.13.0 helm.sh/helm/v3 v3.9.4 k8s.io/api v0.25.0 k8s.io/apiextensions-apiserver v0.25.0 @@ -167,13 +167,13 @@ require ( go.opentelemetry.io/otel v1.11.1 // indirect go.opentelemetry.io/otel/trace v1.11.1 // indirect go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect - golang.org/x/crypto v0.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/net v0.13.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect diff --git a/cli/go.sum b/cli/go.sum index 6cc5d4e83d..f3423c42e8 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -883,8 +883,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -972,8 +972,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1085,13 +1085,13 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1101,8 +1101,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index e8fcc980ef..27b016cad6 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -30,11 +30,11 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.13.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index 8f4c0668ea..bf1b21bebe 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -287,8 +287,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ 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.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -339,21 +339,21 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/control-plane/go.mod b/control-plane/go.mod index 1f9c3e06fb..5d937797ca 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -39,7 +39,7 @@ require ( github.com/stretchr/testify v1.8.3 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/text v0.12.0 + golang.org/x/text v0.13.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 google.golang.org/grpc v1.55.0 @@ -155,13 +155,13 @@ require ( go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.11.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index d1b115f096..cb576cd114 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -572,8 +572,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -654,8 +654,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -743,13 +743,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -758,8 +758,8 @@ 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.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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 2b0ef6ae5a06e7ec274d77f694a39567177140d7 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 16 Oct 2023 20:00:15 -0400 Subject: [PATCH 439/592] ACL Updates for v2 ResourceAPI (#3081) --- acceptance/tests/mesh_v2/mesh_inject_test.go | 34 ++- .../consul/templates/server-acl-init-job.yaml | 4 + .../consul/test/unit/server-acl-init-job.bats | 33 +++ .../controllersv2/meshconfig_controller.go | 10 + .../endpoints_controller_ent_test.go | 6 +- .../endpointsv2/endpoints_controller.go | 10 + .../endpointsv2/endpoints_controller_test.go | 2 +- .../controllers/pod/pod_controller.go | 95 ++++++- .../pod/pod_controller_ent_test.go | 31 +- .../controllers/pod/pod_controller_test.go | 222 ++++++++++----- .../serviceaccount_controller.go | 20 ++ .../serviceaccount_controller_test.go | 19 +- .../webhookv2/consul_dataplane_sidecar.go | 6 - .../consul_dataplane_sidecar_test.go | 4 +- control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- control-plane/helper/test/test_util.go | 44 ++- .../subcommand/inject-connect/command.go | 2 +- .../subcommand/mesh-init/command_test.go | 2 +- .../server-acl-init/anonymous_token_test.go | 2 +- .../subcommand/server-acl-init/command.go | 5 + .../server-acl-init/command_ent_test.go | 267 +++++++++++++++--- .../server-acl-init/command_test.go | 182 +++++++++--- .../server-acl-init/connect_inject.go | 30 +- .../server-acl-init/create_or_update_test.go | 8 + .../subcommand/server-acl-init/rules.go | 10 +- .../subcommand/server-acl-init/rules_test.go | 20 ++ 27 files changed, 827 insertions(+), 247 deletions(-) diff --git a/acceptance/tests/mesh_v2/mesh_inject_test.go b/acceptance/tests/mesh_v2/mesh_inject_test.go index 56cae5640c..3af7c31dc9 100644 --- a/acceptance/tests/mesh_v2/mesh_inject_test.go +++ b/acceptance/tests/mesh_v2/mesh_inject_test.go @@ -25,7 +25,7 @@ const multiport = "multiport" // two ports. This tests inbound connections to each port of the multiport app, and outbound connections from the // multiport app to static-server. func TestMeshInject_MultiportService(t *testing.T) { - for _, secure := range []bool{false} { + for _, secure := range []bool{false, true} { name := fmt.Sprintf("secure: %t", secure) t.Run(name, func(t *testing.T) { @@ -74,23 +74,25 @@ func TestMeshInject_MultiportService(t *testing.T) { require.Len(t, podList.Items, 1) require.Len(t, podList.Items[0].Spec.Containers, 3) - { - // TODO: once ACLs are implemented, only run this block when secure=true and delete the below line and fixture since the default is deny when secure is true. + if !secure { k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/cases/trafficpermissions-deny") - // Now test that traffic is denied between the source and the destination. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") - } else { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") - } - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") - }) } + // Now test that traffic is denied between the source and the destination. + if cfg.EnableTransparentProxy { + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") + } else { + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") + k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") + } + k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") + }) + + // TODO: add a trafficpermission to a particular port and validate + // Check connection from static-client to multiport. if cfg.EnableTransparentProxy { k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://multiport:8080") @@ -125,6 +127,8 @@ func TestMeshInject_MultiportService(t *testing.T) { k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") } + + // TODO: verify that ACL tokens are removed }) } } diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index de9b0dfc30..aca1444a9b 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -192,6 +192,10 @@ spec: {{- else }} -secrets-backend=kubernetes \ {{- end }} + + {{- if (mustHas "resource-apis" .Values.global.experiments) }} + -enable-resource-apis=true \ + {{- end }} {{- if .Values.global.acls.bootstrapToken.secretName }} -bootstrap-token-secret-name={{ .Values.global.acls.bootstrapToken.secretName }} \ diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index b92dd10218..2205192ad2 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -2411,3 +2411,36 @@ load _helpers yq -r '.spec.template.metadata.annotations["argocd.argoproj.io/hook-delete-policy"]' | tee /dev/stderr) [ "${actual}" = null ] } + +#-------------------------------------------------------------------- +# resource-apis + +@test "serverACLInit/Job: resource-apis is not set by default" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo $object | + yq 'any(contains("-enable-resource-apis"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "serverACLInit/Job: -enable-resource-apis=true is set when global.experiments contains [\"resource-apis\"] " { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.tls.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo $object | + yq 'any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller.go b/control-plane/config-entries/controllersv2/meshconfig_controller.go index 5ce749dbe0..c2b2501ebc 100644 --- a/control-plane/config-entries/controllersv2/meshconfig_controller.go +++ b/control-plane/config-entries/controllersv2/meshconfig_controller.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/consul/proto-public/pbresource" "golang.org/x/time/rate" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" corev1 "k8s.io/api/core/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" @@ -89,6 +90,15 @@ func (r *MeshConfigController) ReconcileEntry(ctx context.Context, crdCtrl Contr return ctrl.Result{}, err } + state, err := r.ConsulServerConnMgr.State() + if err != nil { + logger.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + if state.Token != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) + } + if meshConfig.GetDeletionTimestamp().IsZero() { // The object is not being deleted, so if it does not have our finalizer, // then let's add the finalizer and update the object. This is equivalent diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 19d7718337..44e64acba1 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1544,7 +1544,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { } else { writeOpts.Namespace = ts.ExpConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service.Service, svc.Service.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix) + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service.Service, svc.Service.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix, false) token, _, err := consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, @@ -1855,7 +1855,7 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { } else { writeOpts.Namespace = ts.ExpConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix) + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix, false) token, _, err = consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, @@ -2153,7 +2153,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { writeOpts.Namespace = ts.ConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], writeOpts.Namespace, false, "") + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], writeOpts.Namespace, false, "", false) token, _, err = consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index 36fb390f5a..82dd583dfc 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/go-multierror" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" @@ -91,6 +92,15 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, err } + state, err := r.ConsulServerConnMgr.State() + if err != nil { + r.Log.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + if state.Token != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) + } + // If the Endpoints object has been deleted (and we get an IsNotFound error), // we need to deregister that service from Consul. err = r.Client.Get(ctx, req.NamespacedName, &endpoints) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index 3cc3aa9992..cfbd7d7cc2 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -2228,7 +2228,7 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} _, err = resourceClient.Write(context.Background(), writeReq) require.NoError(t, err) - test.ResourceHasPersisted(t, resourceClient, tc.existingResource.Id) + test.ResourceHasPersisted(t, context.Background(), resourceClient, tc.existingResource.Id) } // Run actual reconcile and verify results. diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 6a44c20f4c..3864f0e213 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -7,13 +7,17 @@ import ( "context" "encoding/json" "fmt" + "regexp" "strconv" + "strings" "github.com/go-logr/logr" + "github.com/hashicorp/consul/api" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/go-multierror" + "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -32,6 +36,7 @@ import ( const ( DefaultTelemetryBindSocketDir = "/consul/mesh-inject" consulNodeAddress = "127.0.0.1" + tokenMetaPodNameKey = "pod" ) // Controller watches Pod events and converts them to V2 Workloads and HealthStatus. @@ -97,6 +102,16 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } r.ResourceClient = rc + apiClient, err := consul.NewClientFromConnMgr(r.ConsulClientConfig, r.ConsulServerConnMgr) + if err != nil { + r.Log.Error(err, "failed to create Consul API client", "name", req.Name) + return ctrl.Result{}, err + } + + if r.ConsulClientConfig.APIClientConfig.Token != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", r.ConsulClientConfig.APIClientConfig.Token) + } + err = r.Client.Get(ctx, req.NamespacedName, &pod) // If the pod object has been deleted (and we get an IsNotFound error), @@ -108,8 +123,6 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu errs = multierror.Append(errs, err) } - // TODO: clean up ACL Tokens - // Delete destinations, if any exist if err := r.deleteDestinations(ctx, req.NamespacedName); err != nil { errs = multierror.Append(errs, err) @@ -119,6 +132,15 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu errs = multierror.Append(errs, err) } + if r.AuthMethod != "" { + r.Log.Info("deleting ACL tokens for pod", "name", req.Name, "ns", req.Namespace) + err := r.deleteACLTokensForPod(apiClient, req.NamespacedName) + if err != nil { + r.Log.Error(err, "failed to delete ACL tokens for pod", "name", req.Name, "ns", req.Namespace) + errs = multierror.Append(errs, err) + } + } + return ctrl.Result{}, errs } else if err != nil { r.Log.Error(err, "failed to get Pod", "name", req.Name, "ns", req.Namespace) @@ -205,6 +227,75 @@ func (r *Controller) deleteProxyConfiguration(ctx context.Context, pod types.Nam return err } +// deleteACLTokensForPod finds the ACL tokens that belongs to the pod and delete them from Consul. +// It will only check for ACL tokens that have been created with the auth method this controller +// has been configured with and will only delete tokens for the provided pod Name. +func (r *Controller) deleteACLTokensForPod(apiClient *api.Client, pod types.NamespacedName) error { + // Skip if name is empty. + if pod.Name == "" { + return nil + } + + // Use the V1 logic for getting a compatible namespace + consulNamespace := namespaces.ConsulNamespace( + pod.Namespace, + r.EnableConsulNamespaces, + r.ConsulDestinationNamespace, r.EnableNSMirroring, r.NSMirroringPrefix, + ) + + // TODO: create an index for the workloadidentity in Consul, which will also require + // the identity to be attached to the token for templated-policies. + tokens, _, err := apiClient.ACL().TokenListFiltered( + api.ACLTokenFilterOptions{ + AuthMethod: r.AuthMethod, + }, + &api.QueryOptions{ + Namespace: consulNamespace, + }) + if err != nil { + return fmt.Errorf("failed to get a list of tokens from Consul: %s", err) + } + + // We iterate through each token in the auth method, which is terribly inefficient. + // See discussion above about optimizing the token list query. + for _, token := range tokens { + tokenMeta, err := getTokenMetaFromDescription(token.Description) + if err != nil { + return fmt.Errorf("failed to parse token metadata: %s", err) + } + + tokenPodName := strings.TrimPrefix(tokenMeta[tokenMetaPodNameKey], pod.Namespace+"/") + + // If we can't find token's pod, delete it. + if tokenPodName == pod.Name { + r.Log.Info("deleting ACL token", "name", pod.Name, "namespace", pod.Namespace, "ID", token.AccessorID) + if _, err := apiClient.ACL().TokenDelete(token.AccessorID, &api.WriteOptions{Namespace: consulNamespace}); err != nil { + return fmt.Errorf("failed to delete token from Consul: %s", err) + } + } + } + return nil +} + +// getTokenMetaFromDescription parses JSON metadata from token's description. +func getTokenMetaFromDescription(description string) (map[string]string, error) { + re := regexp.MustCompile(`.*({.+})`) + + matches := re.FindStringSubmatch(description) + if len(matches) != 2 { + return nil, fmt.Errorf("failed to extract token metadata from description: %s", description) + } + tokenMetaJSON := matches[1] + + var tokenMeta map[string]string + err := json.Unmarshal([]byte(tokenMetaJSON), &tokenMeta) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal token metadata '%s': %s", tokenMetaJSON, err) + } + + return tokenMeta, nil +} + func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { // TODO: we should add some validation on the required fields here diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index 8fdb2e0167..1c074dc940 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -740,25 +740,10 @@ func runControllerTest(t *testing.T, tc testCase) { } workloadID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) - loadResource(t, resourceClient, workloadID, tc.existingWorkload, nil) - loadResource( - t, - resourceClient, - getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition), - tc.existingHealthStatus, - workloadID) - loadResource( - t, - resourceClient, - getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition), - tc.existingProxyConfiguration, - nil) - loadResource( - t, - resourceClient, - getDestinationsID(tc.podName, tc.expectedConsulNamespace, tc.partition), - tc.existingDestinations, - nil) + loadResource(t, context.Background(), resourceClient, workloadID, tc.existingWorkload, nil) + loadResource(t, context.Background(), resourceClient, getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingHealthStatus, workloadID) + loadResource(t, context.Background(), resourceClient, getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingProxyConfiguration, nil) + loadResource(t, context.Background(), resourceClient, getDestinationsID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingDestinations, nil) namespacedName := types.NamespacedName{ Namespace: podNamespace, @@ -777,14 +762,14 @@ func runControllerTest(t *testing.T, tc testCase) { require.Equal(t, tc.expRequeue, resp.Requeue) wID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedWorkloadMatches(t, resourceClient, wID, tc.expectedWorkload) + expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) hsID := getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedHealthStatusMatches(t, resourceClient, hsID, tc.expectedHealthStatus) + expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) pcID := getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) + expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, resourceClient, uID, tc.expectedDestinations) + expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 3ae9f3747c..b6fb1d4d44 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" @@ -343,7 +344,7 @@ func TestWorkloadDelete(t *testing.T) { _, err = resourceClient.Write(context.Background(), writeReq) require.NoError(t, err) - test.ResourceHasPersisted(t, resourceClient, workloadID) + test.ResourceHasPersisted(t, context.Background(), resourceClient, workloadID) reconcileReq := types.NamespacedName{ Namespace: metav1.NamespaceDefault, @@ -761,7 +762,7 @@ func TestProxyConfigurationDelete(t *testing.T) { _, err = resourceClient.Write(context.Background(), writeReq) require.NoError(t, err) - test.ResourceHasPersisted(t, resourceClient, pcID) + test.ResourceHasPersisted(t, context.Background(), resourceClient, pcID) reconcileReq := types.NamespacedName{ Namespace: metav1.NamespaceDefault, @@ -1038,7 +1039,7 @@ func TestDestinationsWrite(t *testing.T) { } else { require.NoError(t, err) uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, resourceClient, uID, tt.expected) + expectedDestinationMatches(t, context.Background(), resourceClient, uID, tt.expected) } }) } @@ -1116,13 +1117,9 @@ func TestDestinationsDelete(t *testing.T) { } // Load in the upstream for us to delete and check that it's there - loadResource(t, - resourceClient, - getDestinationsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tt.existingDestinations, - nil) + loadResource(t, context.Background(), resourceClient, getDestinationsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.existingDestinations, nil) uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, resourceClient, uID, tt.existingDestinations) + expectedDestinationMatches(t, context.Background(), resourceClient, uID, tt.existingDestinations) // Delete the upstream nn := types.NamespacedName{Name: tt.pod().Name} @@ -1134,14 +1131,72 @@ func TestDestinationsDelete(t *testing.T) { } else { require.NoError(t, err) uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, resourceClient, uID, nil) + expectedDestinationMatches(t, context.Background(), resourceClient, uID, nil) } }) } } -// TODO -// func TestDeleteACLTokens(t *testing.T) +func TestDeleteACLTokens(t *testing.T) { + t.Parallel() + + podName := "foo-123" + serviceName := "foo" + + // Create test consulServer server. + masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.ACL.Enabled = true + c.ACL.Tokens.InitialManagement = masterToken + c.Experiments = []string{"resource-apis"} + }) + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + // Wait for the ACL system to be bootstraped + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.ACL().PolicyList(nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + + // Wait for the default partition to be created + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + + test.SetupK8sAuthMethodV2(t, testClient.APIClient, serviceName, metav1.NamespaceDefault) + token, _, err := testClient.APIClient.ACL().Login(&api.ACLLoginParams{ + AuthMethod: test.AuthMethod, + BearerToken: test.ServiceAccountJWTToken, + Meta: map[string]string{ + "pod": fmt.Sprintf("%s/%s", metav1.NamespaceDefault, podName), + "component": "connect-injector", + }, + }, nil) + require.NoError(t, err) + + pc := &Controller{ + Log: logrtest.New(t), + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ResourceClient: resourceClient, + AuthMethod: test.AuthMethod, + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + } + + // Delete the ACL Token + pod := types.NamespacedName{Name: podName, Namespace: metav1.NamespaceDefault} + err = pc.deleteACLTokensForPod(testClient.APIClient, pod) + require.NoError(t, err) + + // Verify the token has been deleted. + _, _, err = testClient.APIClient.ACL().TokenRead(token.AccessorID, nil) + require.Contains(t, err.Error(), "ACL not found") +} // TestReconcileCreatePod ensures that a new pod reconciliation fans out to create // the appropriate Consul resources. Translation details from pod to Consul workload are @@ -1238,16 +1293,16 @@ func TestReconcileCreatePod(t *testing.T) { require.False(t, resp.Requeue) wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, resourceClient, wID, tc.expectedWorkload) + expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, resourceClient, hsID, tc.expectedHealthStatus) + expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) + expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, resourceClient, uID, tc.expectedDestinations) + expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) } testCases := []testCase{ @@ -1424,23 +1479,10 @@ func TestReconcileUpdatePod(t *testing.T) { } workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) - loadResource(t, resourceClient, workloadID, tc.existingWorkload, nil) - loadResource( - t, - resourceClient, - getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingHealthStatus, - workloadID) - loadResource(t, - resourceClient, - getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingProxyConfiguration, - nil) - loadResource(t, - resourceClient, - getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingDestinations, - nil) + loadResource(t, context.Background(), resourceClient, workloadID, tc.existingWorkload, nil) + loadResource(t, context.Background(), resourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) + loadResource(t, context.Background(), resourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) + loadResource(t, context.Background(), resourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) namespacedName := types.NamespacedName{ Namespace: namespace, @@ -1458,16 +1500,16 @@ func TestReconcileUpdatePod(t *testing.T) { require.False(t, resp.Requeue) wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, resourceClient, wID, tc.expectedWorkload) + expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, resourceClient, hsID, tc.expectedHealthStatus) + expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) + expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, resourceClient, uID, tc.expectedDestinations) + expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) } testCases := []testCase{ @@ -1655,14 +1697,34 @@ func TestReconcileDeletePod(t *testing.T) { fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() // Create test consulServer server. + masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + if tc.aclsEnabled { + c.ACL.Enabled = true + c.ACL.Tokens.InitialManagement = masterToken + } c.Experiments = []string{"resource-apis"} }) resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) require.NoError(t, err) + if tc.aclsEnabled { + // Wait for the ACL system to be bootstraped + require.Eventually(t, func() bool { + _, _, err := testClient.APIClient.ACL().PolicyList(nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + } + + ctx := context.Background() + if tc.aclsEnabled { + ctx = metadata.AppendToOutgoingContext(context.Background(), "x-consul-token", masterToken) + } + + // Wait for the default partition to be created require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) + _, _, err := testClient.APIClient.Partitions().Read(ctx, constants.DefaultConsulPartition, nil) return err == nil }, 5*time.Second, 500*time.Millisecond) @@ -1687,25 +1749,24 @@ func TestReconcileDeletePod(t *testing.T) { } workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) - loadResource(t, resourceClient, workloadID, tc.existingWorkload, nil) - loadResource( - t, - resourceClient, - getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingHealthStatus, - workloadID) - loadResource( - t, - resourceClient, - getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingProxyConfiguration, - nil) - loadResource( - t, - resourceClient, - getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), - tc.existingDestinations, - nil) + loadResource(t, ctx, resourceClient, workloadID, tc.existingWorkload, nil) + loadResource(t, ctx, resourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) + loadResource(t, ctx, resourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) + loadResource(t, ctx, resourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) + + var token *api.ACLToken + if tc.aclsEnabled { + test.SetupK8sAuthMethodV2(t, testClient.APIClient, tc.podName, metav1.NamespaceDefault) //podName is a standin for the service name + token, _, err = testClient.APIClient.ACL().Login(&api.ACLLoginParams{ + AuthMethod: test.AuthMethod, + BearerToken: test.ServiceAccountJWTToken, + Meta: map[string]string{ + "pod": fmt.Sprintf("%s/%s", metav1.NamespaceDefault, tc.podName), + "component": "connect-injector", + }, + }, nil) + require.NoError(t, err) + } namespacedName := types.NamespacedName{ Namespace: namespace, @@ -1723,16 +1784,22 @@ func TestReconcileDeletePod(t *testing.T) { require.False(t, resp.Requeue) wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, resourceClient, wID, tc.expectedWorkload) + expectedWorkloadMatches(t, ctx, resourceClient, wID, tc.expectedWorkload) hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, resourceClient, hsID, tc.expectedHealthStatus) + expectedHealthStatusMatches(t, ctx, resourceClient, hsID, tc.expectedHealthStatus) pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, resourceClient, pcID, tc.expectedProxyConfiguration) + expectedProxyConfigurationMatches(t, ctx, resourceClient, pcID, tc.expectedProxyConfiguration) uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, resourceClient, uID, tc.expectedDestinations) + expectedDestinationMatches(t, ctx, resourceClient, uID, tc.expectedDestinations) + + if tc.aclsEnabled { + _, _, err = testClient.APIClient.ACL().TokenRead(token.AccessorID, nil) + require.Contains(t, err.Error(), "ACL not found") + } + } testCases := []testCase{ @@ -1751,7 +1818,14 @@ func TestReconcileDeletePod(t *testing.T) { existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), existingDestinations: createDestinations(), }, - // TODO: enable ACLs and make sure they are deleted + { + name: "delete pod w/ acls", + podName: "foo", + existingWorkload: createWorkload(), + existingHealthStatus: createPassingHealthStatus(), + existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + aclsEnabled: true, + }, } for _, tc := range testCases { @@ -1968,10 +2042,10 @@ func createDestinations() *pbmesh.Destinations { } } -func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedWorkload *pbcatalog.Workload) { +func expectedWorkloadMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedWorkload *pbcatalog.Workload) { req := &pbresource.ReadRequest{Id: id} - res, err := client.Read(context.Background(), req) + res, err := client.Read(ctx, req) if expectedWorkload == nil { require.Error(t, err) @@ -1996,15 +2070,15 @@ func expectedWorkloadMatches(t *testing.T, client pbresource.ResourceServiceClie require.Equal(t, "", diff, "Workloads do not match") } -func expectedHealthStatusMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedHealthStatus *pbcatalog.HealthStatus) { +func expectedHealthStatusMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedHealthStatus *pbcatalog.HealthStatus) { req := &pbresource.ReadRequest{Id: id} - res, err := client.Read(context.Background(), req) + res, err := client.Read(ctx, req) if expectedHealthStatus == nil { // Because HealthStatus is asynchronously garbage-collected, we can retry to make sure it gets cleaned up. require.Eventually(t, func() bool { - _, err := client.Read(context.Background(), req) + _, err := client.Read(ctx, req) s, ok := status.FromError(err) return ok && codes.NotFound == s.Code() }, 3*time.Second, 500*time.Millisecond) @@ -2026,10 +2100,10 @@ func expectedHealthStatusMatches(t *testing.T, client pbresource.ResourceService require.Equal(t, "", diff, "HealthStatuses do not match") } -func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedProxyConfiguration *pbmesh.ProxyConfiguration) { +func expectedProxyConfigurationMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedProxyConfiguration *pbmesh.ProxyConfiguration) { req := &pbresource.ReadRequest{Id: id} - res, err := client.Read(context.Background(), req) + res, err := client.Read(ctx, req) if expectedProxyConfiguration == nil { require.Error(t, err) @@ -2054,9 +2128,9 @@ func expectedProxyConfigurationMatches(t *testing.T, client pbresource.ResourceS require.Equal(t, "", diff, "ProxyConfigurations do not match") } -func expectedDestinationMatches(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedUpstreams *pbmesh.Destinations) { +func expectedDestinationMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedUpstreams *pbmesh.Destinations) { req := &pbresource.ReadRequest{Id: id} - res, err := client.Read(context.Background(), req) + res, err := client.Read(ctx, req) if expectedUpstreams == nil { require.Error(t, err) @@ -2080,7 +2154,7 @@ func expectedDestinationMatches(t *testing.T, client pbresource.ResourceServiceC require.True(t, proto.Equal(actualUpstreams, expectedUpstreams)) } -func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { +func loadResource(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { if id == nil || !proto.ProtoReflect().IsValid() { return } @@ -2095,9 +2169,9 @@ func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbr } req := &pbresource.WriteRequest{Resource: resource} - _, err = client.Write(context.Background(), req) + _, err = client.Write(ctx, req) require.NoError(t, err) - test.ResourceHasPersisted(t, client, id) + test.ResourceHasPersisted(t, ctx, client, id) } func addProbesAndOriginalPodAnnotation(pod *corev1.Pod) { diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go index d0e8aea5ba..8027995ac9 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go @@ -9,6 +9,7 @@ import ( "github.com/go-logr/logr" pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/grpc/metadata" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -23,6 +24,10 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) +const ( + defaultServiceAccountName = "default" +) + type Controller struct { client.Client // ConsulServerConnMgr is the watcher for the Consul server addresses used to create Consul API v2 clients. @@ -65,6 +70,21 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, err } + state, err := r.ConsulServerConnMgr.State() + if err != nil { + r.Log.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) + return ctrl.Result{}, err + } + if state.Token != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) + } + + // We don't allow the default service account synced to prevent unintended TrafficPermissions + if req.Name == defaultServiceAccountName { + r.Log.Info("Not syncing default Kubernetes service account", "namespace", req.Namespace) + return ctrl.Result{}, nil + } + // If the ServiceAccount object has been deleted (and we get an IsNotFound error), // we need to deregister that WorkloadIdentity from Consul. err = r.Client.Get(ctx, req.NamespacedName, &serviceAccount) diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go index 94253f995b..d2ea94c22d 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go @@ -53,26 +53,11 @@ func TestReconcile_CreateWorkloadIdentity(t *testing.T) { t.Parallel() cases := []reconcileCase{ { - name: "Default ServiceAccount", + name: "Default ServiceAccount not synced", svcAccountName: "default", k8sObjects: func() []runtime.Object { return []runtime.Object{createServiceAccount("default", "default")} }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "default", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, }, { name: "Custom ServiceAccount", @@ -262,7 +247,7 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} _, err = resourceClient.Write(context.Background(), writeReq) require.NoError(t, err) - test.ResourceHasPersisted(t, resourceClient, tc.existingResource.Id) + test.ResourceHasPersisted(t, context.Background(), resourceClient, tc.existingResource.Id) } // Run actual reconcile and verify results. diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go index 2867257b6c..527c77f265 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go @@ -112,12 +112,6 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", }, - // This entry exists to support certain versions of consul dataplane, where environment variable entries - // utilize this numbered notation to indicate individual KV pairs in a map. - { - Name: "DP_CREDENTIAL_LOGIN_META1", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, }, VolumeMounts: []corev1.VolumeMount{ { diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go index 84b36746f9..fecef1bb5d 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go @@ -219,7 +219,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { } require.Equal(t, expectedProbe, container.ReadinessProbe) require.Nil(t, container.StartupProbe) - require.Len(t, container.Env, 7) + require.Len(t, container.Env, 6) require.Equal(t, container.Env[0].Name, "TMPDIR") require.Equal(t, container.Env[0].Value, "/consul/mesh-inject") require.Equal(t, container.Env[2].Name, "POD_NAME") @@ -228,8 +228,6 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { require.Equal(t, container.Env[4].Value, "$(POD_NAME)") require.Equal(t, container.Env[5].Name, "DP_CREDENTIAL_LOGIN_META") require.Equal(t, container.Env[5].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") - require.Equal(t, container.Env[6].Name, "DP_CREDENTIAL_LOGIN_META1") - require.Equal(t, container.Env[6].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") }) } } diff --git a/control-plane/go.mod b/control-plane/go.mod index 5d937797ca..75cb34307b 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -18,7 +18,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.4 - github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 + github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a github.com/hashicorp/consul/proto-public v0.4.1 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 diff --git a/control-plane/go.sum b/control-plane/go.sum index cb576cd114..6e8cb3c55c 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -263,8 +263,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= -github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751 h1:LnzgDq4e7ZfM1+XS6S21B9taQrbfdydXenL1xHyG1PQ= -github.com/hashicorp/consul/api v1.10.1-0.20230914174054-e5808d85f751/go.mod h1:/Fz5sgOC0a5XY0BmPGj7aDSZRNgySLm02lV4xkU4DS4= +github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a h1:+cjavDME42W6nzw+xyCQ+eczqTFA+Qk4SLl7BHZ2dxI= +github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a/go.mod h1:+pNEP6hQgkrBLjQlYLI13/tyyb1GK3MGVw1PC/IHk9M= github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58 h1:3VHvqLs2zTa9YWMsE4A9IricAZB6rAcXVe8e+pb5slw= github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 h1:j0Rvt1KiKIKlMc2UnA0ilHfIGo66SzrBztGZaCou3+w= diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index 63f9283025..3a80232a23 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -219,13 +219,19 @@ func SetupK8sComponentAuthMethod(t *testing.T, consulClient *api.Client, service // SetupK8sAuthMethod create a k8s auth method and a binding rule in Consul for the // given k8s service and namespace. func SetupK8sAuthMethod(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS string) { - SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "") + SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "", false) +} + +// SetupK8sAuthMethodV2 create a k8s auth method and a binding rule in Consul for the +// given k8s service and namespace. +func SetupK8sAuthMethodV2(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS string) { + SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "", true) } // SetupK8sAuthMethodWithNamespaces creates a k8s auth method and binding rule // in Consul for the k8s service name and namespace. It sets up the auth method and the binding // rule so that it works with consul namespaces. -func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS, consulNS string, mirrorNS bool, nsPrefix string) { +func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS, consulNS string, mirrorNS bool, nsPrefix string, useV2 bool) { t.Helper() // Start the mock k8s server. k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -262,14 +268,30 @@ func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, se require.NoError(t, err) // Create the binding rule. - aclBindingRule := api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: AuthMethod, - BindType: api.BindingRuleBindTypeService, - BindName: "${serviceaccount.name}", - Selector: "serviceaccount.name!=default", - Namespace: consulNS, + var aclBindingRule api.ACLBindingRule + if useV2 { + aclBindingRule = api.ACLBindingRule{ + Description: "Kubernetes binding rule", + AuthMethod: AuthMethod, + BindType: api.BindingRuleBindTypeTemplatedPolicy, + BindName: api.ACLTemplatedPolicyWorkloadIdentityName, + BindVars: &api.ACLTemplatedPolicyVariables{ + Name: "${serviceaccount.name}", + }, + Selector: "serviceaccount.name!=default", + Namespace: consulNS, + } + } else { + aclBindingRule = api.ACLBindingRule{ + Description: "Kubernetes binding rule", + AuthMethod: AuthMethod, + BindType: api.BindingRuleBindTypeService, + BindName: "${serviceaccount.name}", + Selector: "serviceaccount.name!=default", + Namespace: consulNS, + } } + if mirrorNS { aclBindingRule.Namespace = "default" } @@ -281,11 +303,11 @@ func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, se // ResourceHasPersisted checks that a recently written resource exists in the Consul // state store with a valid version. This must be true before a resource is overwritten // or deleted. -func ResourceHasPersisted(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID) { +func ResourceHasPersisted(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID) { req := &pbresource.ReadRequest{Id: id} require.Eventually(t, func() bool { - res, err := client.Read(context.Background(), req) + res, err := client.Read(ctx, req) if err != nil { return false } diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index a81a290dc4..1a8a32901c 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -234,7 +234,7 @@ func (c *Command) init() { c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, "Enable or disable JSON output format for logging.") c.flagSet.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, - "Enable of disable Consul V2 Resource APIs.") + "Enable or disable Consul V2 Resource APIs.") // Proxy sidecar resource setting flags. c.flagSet.StringVar(&c.flagDefaultSidecarProxyCPURequest, "default-sidecar-proxy-cpu-request", "", "Default sidecar proxy CPU request.") diff --git a/control-plane/subcommand/mesh-init/command_test.go b/control-plane/subcommand/mesh-init/command_test.go index 530fc44670..85b68adc65 100644 --- a/control-plane/subcommand/mesh-init/command_test.go +++ b/control-plane/subcommand/mesh-init/command_test.go @@ -309,7 +309,7 @@ func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbr req := &pbresource.WriteRequest{Resource: resource} _, err = client.Write(context.Background(), req) require.NoError(t, err) - test.ResourceHasPersisted(t, client, id) + test.ResourceHasPersisted(t, context.Background(), client, id) } func getWorkloadID(name, namespace, partition string) *pbresource.ID { diff --git a/control-plane/subcommand/server-acl-init/anonymous_token_test.go b/control-plane/subcommand/server-acl-init/anonymous_token_test.go index 98e3699216..8ed58c045f 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token_test.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token_test.go @@ -14,7 +14,7 @@ import ( func Test_configureAnonymousPolicy(t *testing.T) { - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) consulHTTPAddr := testClient.TestServer.HTTPAddr consulGRPCAddr := testClient.TestServer.GRPCAddr diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index 95bce0f7b5..e1bd3c2356 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -44,6 +44,8 @@ type Command struct { flagResourcePrefix string flagK8sNamespace string + flagResourceAPIs bool // Use V2 APIs + flagAllowDNS bool flagSetServerTokens bool @@ -131,6 +133,9 @@ func (c *Command) init() { c.flags.BoolVar(&c.flagSetServerTokens, "set-server-tokens", true, "Toggle for setting agent tokens for the servers.") + c.flags.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, + "Enable or disable Consul V2 Resource APIs. This will affect the binding rule used for Kubernetes auth (Service vs. WorkloadIdentity)") + c.flags.BoolVar(&c.flagAllowDNS, "allow-dns", false, "Toggle for updating the anonymous token to allow DNS queries to work") c.flags.BoolVar(&c.flagClient, "client", true, diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index 4abd16737a..b33e4beb60 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -34,10 +34,27 @@ import ( func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { t.Parallel() - consulDestNamespaces := []string{"default", "destination"} - for _, consulDestNamespace := range consulDestNamespaces { - t.Run(consulDestNamespace, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt) + cases := map[string]struct { + Destination string + ExtraFlags []string + V2BindingRule bool + }{ + "consul default ns": { + Destination: "default", + }, + "consul non-default ns": { + Destination: "destination", + }, + "consul non-default ns w/ resource-apis": { + Destination: "destination", + ExtraFlags: []string{"-enable-resource-apis=true"}, + V2BindingRule: true, + }, + } + + for name, c := range cases { + t.Run(name, func(tt *testing.T) { + k8s, testAgent := completeSetup(tt, false) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -55,10 +72,14 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { "-connect-inject", "-partition=default", "-enable-namespaces", - "-consul-inject-destination-namespace", consulDestNamespace, + "-consul-inject-destination-namespace", c.Destination, "-acl-binding-rule-selector=serviceaccount.name!=default", } + if len(c.ExtraFlags) > 0 { + args = append(args, c.ExtraFlags...) + } + responseCode := cmd.Run(args) require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) @@ -71,11 +92,11 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { // Ensure there's only one auth method. namespaceQuery := &api.QueryOptions{ - Namespace: consulDestNamespace, + Namespace: c.Destination, } methods, _, err := consul.ACL().AuthMethodList(namespaceQuery) require.NoError(t, err) - if consulDestNamespace == "default" { + if c.Destination == "default" { // If the destination mamespace is default then AuthMethodList // will return the component-auth-method as well. require.Len(t, methods, 2) @@ -97,13 +118,19 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, namespaceQuery) require.NoError(t, err) require.Len(t, rules, 1) - actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, namespaceQuery) + aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, namespaceQuery) require.NoError(t, err) - require.NotNil(t, actRule) - require.Equal(t, "Kubernetes binding rule", actRule.Description) - require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) - require.Equal(t, "${serviceaccount.name}", actRule.BindName) - require.Equal(t, "serviceaccount.name!=default", actRule.Selector) + require.NotNil(t, aclRule) + if c.V2BindingRule { + require.Equal(t, api.BindingRuleBindTypeTemplatedPolicy, aclRule.BindType) + require.Equal(t, "builtin/workload-identity", aclRule.BindName) + require.Equal(t, "${serviceaccount.name}", aclRule.BindVars.Name) + } else { + require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) + require.Equal(t, "${serviceaccount.name}", aclRule.BindName) + } + require.Equal(t, "Kubernetes binding rule", aclRule.Description) + require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) // Check that the default namespace got an attached ACL policy defNamespace, _, err := consul.Namespaces().Read("default", &api.QueryOptions{}) @@ -113,7 +140,7 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { require.Len(t, defNamespace.ACLs.PolicyDefaults, 1) require.Equal(t, "cross-namespace-policy", defNamespace.ACLs.PolicyDefaults[0].Name) - if consulDestNamespace != "default" { + if c.Destination != "default" { // Check that only one namespace was created besides the // already existing `default` namespace namespaces, _, err := consul.Namespaces().List(&api.QueryOptions{}) @@ -121,10 +148,10 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { require.Len(t, namespaces, 2) // Check the created namespace properties - actNamespace, _, err := consul.Namespaces().Read(consulDestNamespace, &api.QueryOptions{}) + actNamespace, _, err := consul.Namespaces().Read(c.Destination, &api.QueryOptions{}) require.NoError(t, err) require.NotNil(t, actNamespace) - require.Equal(t, consulDestNamespace, actNamespace.Name) + require.Equal(t, c.Destination, actNamespace.Name) require.Equal(t, "Auto-generated by consul-k8s", actNamespace.Description) require.NotNil(t, actNamespace.ACLs) require.Len(t, actNamespace.ACLs.PolicyDefaults, 1) @@ -144,6 +171,7 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { cases := map[string]struct { MirroringPrefix string ExtraFlags []string + V2BindingRule bool }{ "no prefix": { MirroringPrefix: "", @@ -159,11 +187,16 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { // effect. ExtraFlags: []string{"-consul-inject-destination-namespace=dest"}, }, + "no prefix w/ resource-apis": { + MirroringPrefix: "", + ExtraFlags: []string{"-enable-resource-apis=true"}, + V2BindingRule: true, + }, } for name, c := range cases { t.Run(name, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt) + k8s, testAgent := completeSetup(tt, false) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -212,13 +245,19 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, nil) require.NoError(t, err) require.Len(t, rules, 1) - actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, nil) + aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, nil) require.NoError(t, err) - require.NotNil(t, actRule) - require.Equal(t, "Kubernetes binding rule", actRule.Description) - require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) - require.Equal(t, "${serviceaccount.name}", actRule.BindName) - require.Equal(t, "serviceaccount.name!=default", actRule.Selector) + require.NotNil(t, aclRule) + if c.V2BindingRule { + require.Equal(t, api.BindingRuleBindTypeTemplatedPolicy, aclRule.BindType) + require.Equal(t, "builtin/workload-identity", aclRule.BindName) + require.Equal(t, "${serviceaccount.name}", aclRule.BindVars.Name) + } else { + require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) + require.Equal(t, "${serviceaccount.name}", aclRule.BindName) + } + require.Equal(t, "Kubernetes binding rule", aclRule.Description) + require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) }) } } @@ -276,7 +315,7 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { k8sNamespaceFlags := []string{"default", "other"} for _, k8sNamespaceFlag := range k8sNamespaceFlags { t.Run(k8sNamespaceFlag, func(t *testing.T) { - k8s, testAgent := completeSetup(t) + k8s, testAgent := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, k8sNamespaceFlag) ui := cli.NewMockUi() @@ -336,6 +375,7 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { "enterprise-license-token", "igw-policy", "anotherigw-policy", + "builtin/global-read-only", "tgw-policy", "anothertgw-policy", "connect-inject-policy", @@ -352,12 +392,14 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { actualPolicies[p.Name] = policy.Rules } for _, expected := range firstRunExpectedPolicies { - actRules, ok := actualPolicies[expected] + aclRule, ok := actualPolicies[expected] require.True(t, ok, "Did not find policy %s", expected) // We assert that the policy doesn't have any namespace config // in it because later that's what we're using to test that it - // got updated. - require.NotContains(t, actRules, "namespace") + // got updated. builtin/global-ready-only always has namespaces and partitions included + if expected != "builtin/global-read-only" { + require.NotContains(t, aclRule, "namespace", "policy", expected) + } } // Re-run the command with namespace flags. The policies should be updated. @@ -382,6 +424,7 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { "cross-namespace-policy", "igw-policy", "anotherigw-policy", + "builtin/global-read-only", "tgw-policy", "anothertgw-policy", "partitions-token", @@ -398,27 +441,27 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { actualPolicies[p.Name] = policy.Rules } for _, expected := range secondRunExpectedPolicies { - actRules, ok := actualPolicies[expected] + aclRule, ok := actualPolicies[expected] require.True(t, ok, "Did not find policy %s", expected) switch expected { case "connect-inject-policy": // The connect inject token doesn't have namespace config, // but does change to operator:write from an empty string. - require.Contains(t, actRules, "policy = \"write\"") + require.Contains(t, aclRule, "policy = \"write\"") case "snapshot-agent-policy", "enterprise-license-token": // The snapshot agent and enterprise license tokens shouldn't change. - require.NotContains(t, actRules, "namespace") - require.Contains(t, actRules, "acl = \"write\"") + require.NotContains(t, aclRule, "namespace") + require.Contains(t, aclRule, "acl = \"write\"") case "partitions-token": - require.Contains(t, actRules, "operator = \"write\"") + require.Contains(t, aclRule, "operator = \"write\"") case "anonymous-token-policy": // TODO: This needs to be revisted due to recent changes in how we update the anonymous policy (NET-5174) default: // Assert that the policies have the word namespace in them. This // tests that they were updated. The actual contents are tested // in rules_test.go. - require.Contains(t, actRules, "namespace") + require.Contains(t, aclRule, "namespace") } } }) @@ -446,6 +489,8 @@ func TestRun_ConnectInject_Updates(t *testing.T) { AuthMethodExpectedNamespacePrefixConfig string // Expected namespace for the binding rule. BindingRuleExpectedNS string + // UseV2API, tests the bindingrule is compatible with workloadIdentites. + UseV2API bool }{ "no ns => mirroring ns, no prefix": { FirstRunArgs: nil, @@ -575,11 +620,148 @@ func TestRun_ConnectInject_Updates(t *testing.T) { AuthMethodExpectedNamespacePrefixConfig: "", BindingRuleExpectedNS: "default", }, + "(v2) no ns => mirroring ns, no prefix": { + FirstRunArgs: nil, + SecondRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + }, + AuthMethodExpectedNS: "default", + AuthMethodExpectMapNamespacesConfig: true, + AuthMethodExpectedNamespacePrefixConfig: "", + BindingRuleExpectedNS: "default", + UseV2API: true, + }, + "(v2) no ns => mirroring ns, prefix": { + FirstRunArgs: nil, + SecondRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=prefix-", + }, + AuthMethodExpectedNS: "default", + AuthMethodExpectMapNamespacesConfig: true, + AuthMethodExpectedNamespacePrefixConfig: "prefix-", + BindingRuleExpectedNS: "default", + UseV2API: true, + }, + "(v2) no ns => single dest ns": { + FirstRunArgs: nil, + SecondRunArgs: []string{ + "-enable-namespaces", + "-consul-inject-destination-namespace=dest", + }, + AuthMethodExpectedNS: "dest", + AuthMethodExpectMapNamespacesConfig: false, + AuthMethodExpectedNamespacePrefixConfig: "", + BindingRuleExpectedNS: "dest", + UseV2API: true, + }, + "(v2) mirroring ns => single dest ns": { + FirstRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=prefix-", + }, + SecondRunArgs: []string{ + "-enable-namespaces", + "-consul-inject-destination-namespace=dest", + }, + AuthMethodExpectedNS: "dest", + AuthMethodExpectMapNamespacesConfig: false, + AuthMethodExpectedNamespacePrefixConfig: "", + BindingRuleExpectedNS: "dest", + UseV2API: true, + }, + "(v2) single dest ns => mirroring ns": { + FirstRunArgs: []string{ + "-enable-namespaces", + "-consul-inject-destination-namespace=dest", + }, + SecondRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=prefix-", + }, + AuthMethodExpectedNS: "default", + AuthMethodExpectMapNamespacesConfig: true, + AuthMethodExpectedNamespacePrefixConfig: "prefix-", + BindingRuleExpectedNS: "default", + UseV2API: true, + }, + "(v2) mirroring ns (no prefix) => mirroring ns (no prefix)": { + FirstRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=", + }, + SecondRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=", + }, + AuthMethodExpectedNS: "default", + AuthMethodExpectMapNamespacesConfig: true, + AuthMethodExpectedNamespacePrefixConfig: "", + BindingRuleExpectedNS: "default", + UseV2API: true, + }, + "(v2) mirroring ns => mirroring ns (same prefix)": { + FirstRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=prefix-", + }, + SecondRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=prefix-", + }, + AuthMethodExpectedNS: "default", + AuthMethodExpectMapNamespacesConfig: true, + AuthMethodExpectedNamespacePrefixConfig: "prefix-", + BindingRuleExpectedNS: "default", + UseV2API: true, + }, + "(v2) mirroring ns (no prefix) => mirroring ns (prefix)": { + FirstRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=", + }, + SecondRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=prefix-", + }, + AuthMethodExpectedNS: "default", + AuthMethodExpectMapNamespacesConfig: true, + AuthMethodExpectedNamespacePrefixConfig: "prefix-", + BindingRuleExpectedNS: "default", + UseV2API: true, + }, + "(v2) mirroring ns (prefix) => mirroring ns (no prefix)": { + FirstRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=prefix-", + }, + SecondRunArgs: []string{ + "-enable-namespaces", + "-enable-inject-k8s-namespace-mirroring", + "-inject-k8s-namespace-mirroring-prefix=", + }, + AuthMethodExpectedNS: "default", + AuthMethodExpectMapNamespacesConfig: true, + AuthMethodExpectedNamespacePrefixConfig: "", + BindingRuleExpectedNS: "default", + UseV2API: true, + }, } for name, c := range cases { t.Run(name, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt) + k8s, testAgent := completeSetup(tt, c.UseV2API) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -593,6 +775,10 @@ func TestRun_ConnectInject_Updates(t *testing.T) { "-connect-inject", } + if c.UseV2API { + defaultArgs = append(defaultArgs, "-enable-resource-apis=true") + } + // First run. NOTE: we don't assert anything here since we've // tested these results in other tests. What we care about here // is the result after the second run. @@ -644,6 +830,11 @@ func TestRun_ConnectInject_Updates(t *testing.T) { }) require.NoError(t, err) require.Len(t, rules, 1) + if c.UseV2API { + require.Equal(tt, api.BindingRuleBindTypeTemplatedPolicy, rules[0].BindType) + } else { + require.Equal(tt, api.BindingRuleBindTypeService, rules[0].BindType) + } }) } } @@ -683,7 +874,7 @@ func TestRun_TokensWithNamespacesEnabled(t *testing.T) { } for testName, c := range cases { t.Run(testName, func(t *testing.T) { - k8s, testSvr := completeSetup(t) + k8s, testSvr := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -937,7 +1128,7 @@ partition "default" { } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testSvr := completeSetup(t) + k8s, testSvr := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -1024,7 +1215,7 @@ func TestRun_NamespaceEnabled_ValidateLoginToken_PrimaryDatacenter(t *testing.T) authMethodName := fmt.Sprintf("%s-%s", resourcePrefix, componentAuthMethod) serviceAccountName := fmt.Sprintf("%s-%s", resourcePrefix, c.ComponentName) - k8s, testSvr := completeSetup(t) + k8s, testSvr := completeSetup(t, false) _, jwtToken := setUpK8sServiceAccount(t, k8s, c.Namespace) k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -1184,7 +1375,7 @@ func TestRun_NamespaceEnabled_ValidateLoginToken_SecondaryDatacenter(t *testing. func TestRun_PartitionTokenDefaultPartition_WithProvidedSecretID(t *testing.T) { t.Parallel() - k8s, testSvr := completeSetup(t) + k8s, testSvr := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) partitionToken := "123e4567-e89b-12d3-a456-426614174000" diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index c7d44c0695..928a50e285 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -103,7 +103,7 @@ func TestRun_FlagValidation(t *testing.T) { func TestRun_Defaults(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -178,7 +178,7 @@ func TestRun_TokensPrimaryDC(t *testing.T) { } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -243,7 +243,7 @@ func TestRun_TokensPrimaryDC(t *testing.T) { func TestRun_ReplicationTokenPrimaryDC_WithProvidedSecretID(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) replicationToken := "123e4567-e89b-12d3-a456-426614174000" @@ -505,7 +505,7 @@ func TestRun_AnonymousTokenPolicy(t *testing.T) { flags = append(flags, "-acl-replication-token-file", tmp.Name()) } else { var testClient *test.TestServerClient - k8s, testClient = completeSetup(t) + k8s, testClient = completeSetup(t, false) consulHTTPAddr = testClient.TestServer.HTTPAddr consulGRPCAddr = testClient.TestServer.GRPCAddr } @@ -580,8 +580,9 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { t.Parallel() cases := map[string]struct { - flags []string - expectedHost string + flags []string + expectedHost string + v2BindingRule bool }{ "-connect-inject flag": { flags: []string{"-connect-inject"}, @@ -594,11 +595,16 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { }, expectedHost: "https://my-kube.com", }, + "-enable-resource-apis flag": { + flags: []string{"-connect-inject", "-enable-resource-apis=true"}, + expectedHost: "https://kubernetes.default.svc", + v2BindingRule: true, + }, } for testName, c := range cases { t.Run(testName, func(t *testing.T) { - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, c.v2BindingRule) caCert, jwtToken := setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -641,8 +647,15 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, &api.QueryOptions{Token: bootToken}) require.NoError(t, err) require.Len(t, rules, 1) - require.Equal(t, "service", string(rules[0].BindType)) - require.Equal(t, "${serviceaccount.name}", rules[0].BindName) + + if c.v2BindingRule { + require.Equal(t, "templated-policy", string(rules[0].BindType)) + require.Equal(t, "builtin/workload-identity", rules[0].BindName) + require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) + } else { + require.Equal(t, "service", string(rules[0].BindType)) + require.Equal(t, "${serviceaccount.name}", rules[0].BindName) + } require.Equal(t, bindingRuleSelector, rules[0].Selector) // Test that if the same command is re-run it doesn't error. @@ -665,7 +678,7 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { func TestRun_ConnectInjectAuthMethodUpdates(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) caCert, jwtToken := setUpK8sServiceAccount(t, k8s, ns) ui := cli.NewMockUi() @@ -746,7 +759,7 @@ func TestRun_ConnectInjectAuthMethodUpdates(t *testing.T) { // Test that ACL binding rules are updated if the rule selector changes. func TestRun_BindingRuleUpdates(t *testing.T) { - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) consul, err := api.NewClient(&api.Config{ @@ -786,13 +799,13 @@ func TestRun_BindingRuleUpdates(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) require.NoError(t, err) require.Len(t, rules, 1) - actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) + aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) require.NoError(t, err) - require.NotNil(t, actRule) - require.Equal(t, "Kubernetes binding rule", actRule.Description) - require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) - require.Equal(t, "${serviceaccount.name}", actRule.BindName) - require.Equal(t, "serviceaccount.name!=default", actRule.Selector) + require.NotNil(t, aclRule) + require.Equal(t, "Kubernetes binding rule", aclRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) + require.Equal(t, "${serviceaccount.name}", aclRule.BindName) + require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) } // Re-run the command with namespace flags. The policies should be updated. @@ -812,20 +825,102 @@ func TestRun_BindingRuleUpdates(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) require.NoError(t, err) require.Len(t, rules, 1) - actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) + aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) require.NoError(t, err) - require.NotNil(t, actRule) - require.Equal(t, "Kubernetes binding rule", actRule.Description) - require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) - require.Equal(t, "${serviceaccount.name}", actRule.BindName) - require.Equal(t, "serviceaccount.name!=changed", actRule.Selector) + require.NotNil(t, aclRule) + require.Equal(t, "Kubernetes binding rule", aclRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) + require.Equal(t, "${serviceaccount.name}", aclRule.BindName) + require.Equal(t, "serviceaccount.name!=changed", aclRule.Selector) + } +} + +// Test that the ACL binding template is updated if the rule selector changes. +// V2 only. +func TestRun_TemplateBindingRuleUpdates(t *testing.T) { + k8s, testClient := completeSetup(t, true) + setUpK8sServiceAccount(t, k8s, ns) + + consul, err := api.NewClient(&api.Config{ + Address: testClient.TestServer.HTTPAddr, + }) + require.NoError(t, err) + + ui := cli.NewMockUi() + commonArgs := []string{ + "-resource-prefix=" + resourcePrefix, + "-k8s-namespace=" + ns, + "-addresses", strings.Split(testClient.TestServer.HTTPAddr, ":")[0], + "-http-port", strings.Split(testClient.TestServer.HTTPAddr, ":")[1], + "-grpc-port", strings.Split(testClient.TestServer.GRPCAddr, ":")[1], + "-enable-resource-apis=true", + "-connect-inject", + } + firstRunArgs := append(commonArgs, + "-acl-binding-rule-selector=serviceaccount.name!=default", + ) + // On the second run, we change the binding rule selector. + secondRunArgs := append(commonArgs, + "-acl-binding-rule-selector=serviceaccount.name!=changed", + ) + + // Run the command first to populate the binding rule. + cmd := Command{ + UI: ui, + clientset: k8s, + } + responseCode := cmd.Run(firstRunArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + + // Validate the binding rule. + { + queryOpts := &api.QueryOptions{Token: getBootToken(t, k8s, resourcePrefix, ns)} + authMethodName := resourcePrefix + "-k8s-auth-method" + rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) + require.NoError(t, err) + require.Len(t, rules, 1) + aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) + require.NoError(t, err) + require.NotNil(t, aclRule) + require.Equal(t, "Kubernetes binding rule", aclRule.Description) + require.Equal(t, "templated-policy", string(rules[0].BindType)) + require.Equal(t, "builtin/workload-identity", rules[0].BindName) + require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) + require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) + } + + // Re-run the command with namespace flags. The policies should be updated. + // NOTE: We're redefining the command so that the old flag values are + // reset. + cmd = Command{ + UI: ui, + clientset: k8s, + } + responseCode = cmd.Run(secondRunArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + + // Check the binding rule is changed expected. + { + queryOpts := &api.QueryOptions{Token: getBootToken(t, k8s, resourcePrefix, ns)} + authMethodName := resourcePrefix + "-k8s-auth-method" + rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) + require.NoError(t, err) + require.Len(t, rules, 1) + aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) + require.NoError(t, err) + require.NotNil(t, aclRule) + require.Equal(t, "Kubernetes binding rule", aclRule.Description) + require.Equal(t, "templated-policy", string(rules[0].BindType)) + require.Equal(t, "builtin/workload-identity", rules[0].BindName) + require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) + require.Equal(t, "serviceaccount.name!=changed", aclRule.Selector) } } // Test that the catalog sync policy is updated if the Consul node name changes. func TestRun_SyncPolicyUpdates(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) ui := cli.NewMockUi() @@ -920,6 +1015,12 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { }) require.NoError(t, err) + // Make sure the ACL system is bootstrapped first + require.Eventually(t, func() bool { + _, _, err := consul.ACL().PolicyList(nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + // Create the policy manually. description := "not the expected description" policy, _, err := consul.ACL().PolicyCreate(&api.ACLPolicy{ @@ -965,7 +1066,7 @@ func TestRun_DelayedServers(t *testing.T) { t.Parallel() k8s := fake.NewSimpleClientset() setUpK8sServiceAccount(t, k8s, ns) - randomPorts := freeport.GetN(t, 7) + randomPorts := freeport.GetN(t, 8) ui := cli.NewMockUi() cmd := Command{ @@ -1006,10 +1107,11 @@ func TestRun_DelayedServers(t *testing.T) { DNS: randomPorts[0], HTTP: randomPorts[1], GRPC: randomPorts[2], - HTTPS: randomPorts[3], - SerfLan: randomPorts[4], - SerfWan: randomPorts[5], - Server: randomPorts[6], + GRPCTLS: randomPorts[3], + HTTPS: randomPorts[4], + SerfLan: randomPorts[5], + SerfWan: randomPorts[6], + Server: randomPorts[7], } }) require.NoError(t, err) @@ -1742,7 +1844,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { // Test that we exit after timeout. func TestRun_Timeout(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) _, err := api.NewClient(&api.Config{ @@ -1894,7 +1996,7 @@ func TestRun_GatewayErrors(t *testing.T) { for testName, c := range cases { t.Run(testName, func(tt *testing.T) { - k8s, testClient := completeSetup(tt) + k8s, testClient := completeSetup(tt, false) setUpK8sServiceAccount(t, k8s, ns) require := require.New(tt) @@ -1996,7 +2098,7 @@ func TestRun_PoliciesAndBindingRulesForACLLogin_PrimaryDatacenter(t *testing.T) } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -2310,7 +2412,7 @@ func TestRun_ValidateLoginToken_PrimaryDatacenter(t *testing.T) { serviceAccountName = c.ServiceAccountName } - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) _, jwtToken := setUpK8sServiceAccount(t, k8s, ns) k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -2526,7 +2628,7 @@ func TestRun_ValidateLoginToken_SecondaryDatacenter(t *testing.T) { func TestRun_PrimaryDatacenter_ComponentAuthMethod(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t) + k8s, testClient := completeSetup(t, false) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -2602,14 +2704,20 @@ func TestRun_SecondaryDatacenter_ComponentAuthMethod(t *testing.T) { } // Set up test consul agent and kubernetes cluster. -func completeSetup(t *testing.T) (*fake.Clientset, *test.TestServerClient) { +func completeSetup(t *testing.T, useResourceAPI bool) (*fake.Clientset, *test.TestServerClient) { k8s := fake.NewSimpleClientset() - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + testServerClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.ACL.Enabled = true + + if useResourceAPI { + c.Experiments = []string{"resource-apis"} + } }) - return k8s, testClient + testServerClient.TestServer.WaitForActiveCARoot(t) + + return k8s, testServerClient } // Set up test consul agent and kubernetes cluster. diff --git a/control-plane/subcommand/server-acl-init/connect_inject.go b/control-plane/subcommand/server-acl-init/connect_inject.go index 07fc7c6052..f853144a2c 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject.go +++ b/control-plane/subcommand/server-acl-init/connect_inject.go @@ -69,15 +69,29 @@ func (c *Command) configureConnectInjectAuthMethod(consulClient *api.Client, aut return err } - c.log.Info("creating inject binding rule") - // Create the binding rule. - abr := api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: authMethodName, - BindType: api.BindingRuleBindTypeService, - BindName: "${serviceaccount.name}", - Selector: c.flagBindingRuleSelector, + var abr api.ACLBindingRule + if c.flagResourceAPIs { + c.log.Info("creating consul binding rule for WorkloadIdentityName") + abr = api.ACLBindingRule{ + Description: "Kubernetes binding rule", + AuthMethod: authMethodName, + BindType: api.BindingRuleBindTypeTemplatedPolicy, + BindName: api.ACLTemplatedPolicyWorkloadIdentityName, + BindVars: &api.ACLTemplatedPolicyVariables{ + Name: "${serviceaccount.name}", + }, + Selector: c.flagBindingRuleSelector, + } + } else { + abr = api.ACLBindingRule{ + Description: "Kubernetes binding rule", + AuthMethod: authMethodName, + BindType: api.BindingRuleBindTypeService, + BindName: "${serviceaccount.name}", + Selector: c.flagBindingRuleSelector, + } } + return c.createConnectBindingRule(consulClient, authMethodName, &abr) } diff --git a/control-plane/subcommand/server-acl-init/create_or_update_test.go b/control-plane/subcommand/server-acl-init/create_or_update_test.go index 84ccdc1635..96d3945617 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update_test.go +++ b/control-plane/subcommand/server-acl-init/create_or_update_test.go @@ -5,6 +5,7 @@ package serveraclinit import ( "testing" + "time" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" @@ -96,6 +97,13 @@ func TestCreateOrUpdateACLPolicy(t *testing.T) { Address: svr.HTTPAddr, Token: bootToken, }) + + // Make sure the ACL system is bootstrapped first + require.Eventually(func() bool { + _, _, err := consul.ACL().PolicyList(nil) + return err == nil + }, 5*time.Second, 500*time.Millisecond) + require.NoError(err) connectInjectRule, err := cmd.injectRules() require.NoError(err) diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index 5f65b6c75c..2732188305 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -321,9 +321,9 @@ partition "{{ .PartitionName }}" { func (c *Command) injectRules() (string, error) { // The Connect injector needs permissions to create namespaces when namespaces are enabled. // It must also create/update service health checks via the endpoints controller. - // When ACLs are enabled, the endpoints controller needs "acl:write" permissions - // to delete ACL tokens created via "consul login". policy = "write" is required when - // creating namespaces within a partition. + // When ACLs are enabled, the endpoints controller (V1) or pod controller (v2) + // needs "acl:write" permissions to delete ACL tokens created via "consul login". + // policy = "write" is required when creating namespaces within a partition. injectRulesTpl := ` {{- if .EnablePartitions }} partition "{{ .PartitionName }}" { @@ -351,6 +351,10 @@ partition "{{ .PartitionName }}" { policy = "write" intentions = "write" } + identity_prefix "" { + policy = "write" + intentions = "write" + } {{- if .EnableNamespaces }} } {{- end }} diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index 5b631698e7..c1a02a2218 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -964,6 +964,10 @@ func TestInjectRules(t *testing.T) { service_prefix "" { policy = "write" intentions = "write" + } + identity_prefix "" { + policy = "write" + intentions = "write" }`, }, { @@ -983,6 +987,10 @@ func TestInjectRules(t *testing.T) { policy = "write" intentions = "write" } + identity_prefix "" { + policy = "write" + intentions = "write" + } }`, }, { @@ -1003,6 +1011,10 @@ func TestInjectRules(t *testing.T) { policy = "write" intentions = "write" } + identity_prefix "" { + policy = "write" + intentions = "write" + } }`, }, { @@ -1024,6 +1036,10 @@ partition "part-1" { policy = "write" intentions = "write" } + identity_prefix "" { + policy = "write" + intentions = "write" + } } }`, }, @@ -1047,6 +1063,10 @@ partition "part-1" { policy = "write" intentions = "write" } + identity_prefix "" { + policy = "write" + intentions = "write" + } } }`, }, From 5bf38c0deae25447963da85826e5e4fe7238e40a Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:01:00 -0700 Subject: [PATCH 440/592] Add New Make Target for prepare release dev (#3083) add new make target --- Makefile | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a1c4e01834..5626bbe99d 100644 --- a/Makefile +++ b/Makefile @@ -289,7 +289,7 @@ endif prepare-rc-branch: prepare-rc-script -prepare-dev: +prepare-main-dev: ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) endif @@ -307,6 +307,24 @@ ifndef CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION endif source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_NEXT_CONSUL_VERSION) $(CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION) +prepare-release-dev: +ifndef CONSUL_K8S_RELEASE_VERSION + $(error CONSUL_K8S_RELEASE_VERSION is required) +endif +ifndef CONSUL_K8S_RELEASE_DATE + $(error CONSUL_K8S_RELEASE_DATE is required, use format , (ex. October 4, 2022)) +endif +ifndef CONSUL_K8S_NEXT_RELEASE_VERSION + $(error CONSUL_K8S_RELEASE_VERSION is required) +endif +ifndef CONSUL_K8S_CONSUL_VERSION + $(error CONSUL_K8S_CONSUL_VERSION is required) +endif +ifndef CONSUL_K8S_CONSUL_DATAPLANE_VERSION + $(error CONSUL_K8S_CONSUL_DATAPLANE_VERSION is required) +endif + source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) + # ===========> Makefile config .DEFAULT_GOAL := help .PHONY: gen-helm-docs copy-crds-to-chart generate-external-crds bats-tests help ci.aws-acceptance-test-cleanup version cli-dev prepare-dev prepare-release From 8847cbd6d1b34da2c73ed253e1d591a62f0aec56 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 18 Oct 2023 13:13:02 -0400 Subject: [PATCH 441/592] [NET-5402] Halting Test with JWT (#3099) * Added jwt to routes for halting test * GW Policy halting tests added --- .../gateway_controller_integration_test.go | 342 +++++++++++++++++- 1 file changed, 327 insertions(+), 15 deletions(-) diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go index 2605991238..ee8d8240f6 100644 --- a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go +++ b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go @@ -32,16 +32,15 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul/api" ) func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { - //TODO @apigatewayteam test is consistently failing on main after merge, fix in a follow up PR - t.Skip() s := runtime.NewScheme() require.NoError(t, clientgoscheme.AddToScheme(s)) require.NoError(t, gwv1alpha2.Install(s)) @@ -49,11 +48,13 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { require.NoError(t, v1alpha1.AddToScheme(s)) testCases := map[string]struct { - namespace string - certFn func(*testing.T, context.Context, client.WithWatch, string) *corev1.Secret - gwFn func(*testing.T, context.Context, client.WithWatch, string) *gwv1beta1.Gateway - httpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute - tcpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *v1alpha2.TCPRoute + namespace string + certFn func(*testing.T, context.Context, client.WithWatch, string) *corev1.Secret + gwFn func(*testing.T, context.Context, client.WithWatch, string) *gwv1beta1.Gateway + httpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway, *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute + tcpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *v1alpha2.TCPRoute + externalFilterFn func(*testing.T, context.Context, client.WithWatch, string) *v1alpha1.RouteAuthFilter + policyFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway, string) }{ "all fields set": { namespace: "consul", @@ -61,6 +62,10 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { gwFn: createAllFieldsSetAPIGW, httpRouteFn: createAllFieldsSetHTTPRoute, tcpRouteFn: createAllFieldsSetTCPRoute, + externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { + return nil + }, + policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, }, "minimal fields set": { namespace: "", @@ -68,6 +73,10 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { gwFn: minimalFieldsSetAPIGW, httpRouteFn: minimalFieldsSetHTTPRoute, tcpRouteFn: minimalFieldsSetTCPRoute, + externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { + return nil + }, + policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, }, "funky casing to test normalization doesnt cause infinite reconciliation": { namespace: "", @@ -75,6 +84,30 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { gwFn: createFunkyCasingFieldsAPIGW, httpRouteFn: createFunkyCasingFieldsHTTPRoute, tcpRouteFn: createFunkyCasingFieldsTCPRoute, + externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { + return nil + }, + policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, + }, + "http route with JWT auth": { + namespace: "", + certFn: createCert, + gwFn: createAllFieldsSetAPIGW, + httpRouteFn: createJWTAuthHTTPRoute, + tcpRouteFn: createFunkyCasingFieldsTCPRoute, + externalFilterFn: createRouteAuthFilter, + policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, + }, + "policy attached to gateway": { + namespace: "", + certFn: createCert, + gwFn: createAllFieldsSetAPIGW, + httpRouteFn: createAllFieldsSetHTTPRoute, + tcpRouteFn: createFunkyCasingFieldsTCPRoute, + externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { + return nil + }, + policyFn: createGWPolicy, }, } @@ -141,8 +174,11 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { }) require.NoError(t, err) - httpRouteObj := tc.httpRouteFn(t, ctx, k8sClient, k8sGWObj) + jwtProvider := createJWTProvider(t, ctx, k8sClient) + authFilterObj := tc.externalFilterFn(t, ctx, k8sClient, jwtProvider.Name) + httpRouteObj := tc.httpRouteFn(t, ctx, k8sClient, k8sGWObj, authFilterObj) tcpRouteObj := tc.tcpRouteFn(t, ctx, k8sClient, k8sGWObj) + tc.policyFn(t, ctx, k8sClient, k8sGWObj, jwtProvider.Name) // reconcile again so that we get the route bound to the gateway _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ @@ -177,17 +213,17 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { case <-gwSub.Events(): if !gwDone { gwDone = true - wg.Done() + w.Done() } case <-httpRouteSub.Events(): if !httpRouteDone { httpRouteDone = true - wg.Done() + w.Done() } case <-tcpRouteSub.Events(): if !tcpRouteDone { tcpRouteDone = true - wg.Done() + w.Done() } case <-inlineCertSub.Events(): } @@ -425,7 +461,7 @@ func createAllFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client return gw } -func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { +func createJWTAuthHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, authFilter *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { svcDefault := &v1alpha1.ServiceDefaults{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceDefaults", @@ -585,6 +621,14 @@ func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient cl }, }, }, + { + Type: gwv1beta1.HTTPRouteFilterExtensionRef, + ExtensionRef: &gwv1beta1.LocalObjectReference{ + Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), + Kind: v1alpha1.RouteAuthFilterKind, + Name: gwv1beta1.ObjectName(authFilter.Name), + }, + }, }, BackendRefs: []gwv1beta1.HTTPBackendRef{ { @@ -801,7 +845,7 @@ func minimalFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.W return gw } -func minimalFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { +func minimalFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, _ *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { svcDefault := &v1alpha1.ServiceDefaults{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceDefaults", @@ -1101,7 +1145,7 @@ func createFunkyCasingFieldsAPIGW(t *testing.T, ctx context.Context, k8sClient c return gw } -func createFunkyCasingFieldsHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { +func createFunkyCasingFieldsHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, _ *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { svcDefault := &v1alpha1.ServiceDefaults{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceDefaults", @@ -1323,3 +1367,271 @@ func createFunkyCasingFieldsTCPRoute(t *testing.T, ctx context.Context, k8sClien return route } + +func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, filter *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { + svcDefault := &v1alpha1.ServiceDefaults{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceDefaults", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + } + + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "high", + Protocol: "TCP", + Port: 8080, + }, + }, + Selector: map[string]string{"app": "Service"}, + }, + } + + serviceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + }, + } + + deployment := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "Service", + Labels: map[string]string{"app": "Service"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: common.PointerTo(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "Service"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{}, + }, + }, + } + + err := k8sClient.Create(ctx, svcDefault) + require.NoError(t, err) + + err = k8sClient.Create(ctx, svc) + require.NoError(t, err) + + err = k8sClient.Create(ctx, serviceAccount) + require.NoError(t, err) + + err = k8sClient.Create(ctx, deployment) + require.NoError(t, err) + + route := &gwv1beta1.HTTPRoute{ + TypeMeta: metav1.TypeMeta{ + Kind: "HTTPRoute", + APIVersion: "gateway.networking.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "http-route", + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Kind: (*gwv1beta1.Kind)(&gw.Kind), + Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), + Name: gwv1beta1.ObjectName(gw.Name), + SectionName: &gw.Spec.Listeners[0].Name, + Port: &gw.Spec.Listeners[0].Port, + }, + }, + }, + Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, + Rules: []gwv1beta1.HTTPRouteRule{ + { + Matches: []gwv1beta1.HTTPRouteMatch{ + { + Path: &gwv1beta1.HTTPPathMatch{ + Type: common.PointerTo(gwv1beta1.PathMatchType("PathPrefix")), + Value: common.PointerTo("/v1"), + }, + Headers: []gwv1beta1.HTTPHeaderMatch{ + { + Type: common.PointerTo(gwv1beta1.HeaderMatchExact), + Name: "version", + Value: "version", + }, + }, + QueryParams: []gwv1beta1.HTTPQueryParamMatch{ + { + Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), + Name: "search", + Value: "q", + }, + }, + Method: common.PointerTo(gwv1beta1.HTTPMethod("GET")), + }, + }, + Filters: []gwv1beta1.HTTPRouteFilter{ + { + Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ + Set: []gwv1beta1.HTTPHeader{ + { + Name: "foo", + Value: "bax", + }, + }, + Add: []gwv1beta1.HTTPHeader{ + { + Name: "arc", + Value: "reactor", + }, + }, + Remove: []string{"remove"}, + }, + }, + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.FullPathHTTPPathModifier, + ReplaceFullPath: common.PointerTo("/foobar"), + }, + }, + }, + + { + Type: gwv1beta1.HTTPRouteFilterURLRewrite, + URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ + Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), + Path: &gwv1beta1.HTTPPathModifier{ + Type: gwv1beta1.PrefixMatchHTTPPathModifier, + ReplacePrefixMatch: common.PointerTo("/foo"), + }, + }, + }, + }, + BackendRefs: []gwv1beta1.HTTPBackendRef{ + { + BackendRef: gwv1beta1.BackendRef{ + BackendObjectReference: gwv1beta1.BackendObjectReference{ + Name: "Service", + Port: common.PointerTo(gwv1beta1.PortNumber(8080)), + }, + Weight: common.PointerTo(int32(50)), + }, + }, + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, route) + require.NoError(t, err) + + return route +} + +func createRouteAuthFilter(t *testing.T, ctx context.Context, k8sClient client.WithWatch, providerName string) *v1alpha1.RouteAuthFilter { + filter := &v1alpha1.RouteAuthFilter{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.RouteAuthFilterKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "auth-filter", + }, + Spec: v1alpha1.RouteAuthFilterSpec{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: providerName, + }, + }, + }, + }, + } + err := k8sClient.Create(ctx, filter) + require.NoError(t, err) + + return filter +} + +func createJWTProvider(t *testing.T, ctx context.Context, k8sClient client.WithWatch) *v1alpha1.JWTProvider { + provider := &v1alpha1.JWTProvider{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.JWTProviderKubeKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "provider", + }, + Spec: v1alpha1.JWTProviderSpec{ + JSONWebKeySet: &v1alpha1.JSONWebKeySet{}, + Issuer: "local", + }, + } + + err := k8sClient.Create(ctx, provider) + require.NoError(t, err) + + return provider +} + +func createGWPolicy(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, providerName string) { + policy := &v1alpha1.GatewayPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayPolicy", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-policy", + }, + Spec: v1alpha1.GatewayPolicySpec{ + TargetRef: v1alpha1.PolicyTargetReference{ + Group: gw.GroupVersionKind().Group, + Kind: gw.GroupVersionKind().Kind, + Name: gw.Name, + Namespace: gw.Namespace, + SectionName: &gw.Spec.Listeners[0].Name, + }, + Override: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: providerName, + }, + }, + }, + }, + Default: &v1alpha1.GatewayPolicyConfig{ + JWT: &v1alpha1.GatewayJWTRequirement{ + Providers: []*v1alpha1.GatewayJWTProvider{ + { + Name: providerName, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, policy) + require.NoError(t, err) +} From d09fc3d7fcfc4590b27b11779c4bf42c823d5f7f Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:26:33 -0700 Subject: [PATCH 442/592] prepare main for 1.4 dev (#3092) * prepare main for 1.4 dev * add cloud tests * add patch version for dataplane, now required. * test: use only minor versions for tests * build: remove last set-output from yaml * add patch version for dataplane, now required. * fixed error message change --------- Co-authored-by: DanStough --- .github/workflows/build.yml | 47 +++++++++++++++---- ...0-rc1.yml => nightly-acceptance-1-3-0.yml} | 4 +- ...0-49-x.yml => weekly-acceptance-1-3-x.yml} | 10 ++-- charts/consul/Chart.yaml | 10 ++-- charts/consul/values.yaml | 6 +-- cli/version/version.go | 2 +- control-plane/connect-inject/common/common.go | 2 +- .../connect-inject/common/common_test.go | 4 +- control-plane/version/version.go | 2 +- 9 files changed, 58 insertions(+), 29 deletions(-) rename .github/workflows/{nightly-acceptance-1-3-0-rc1.yml => nightly-acceptance-1-3-0.yml} (93%) rename .github/workflows/{weekly-acceptance-0-49-x.yml => weekly-acceptance-1-3-x.yml} (81%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 414c875b26..7499ead061 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: # version, because "goenv" can react to it automatically. run: | echo "Building with Go $(cat .go-version)" - echo "::set-output name=go-version::$(cat .go-version)" + echo "go-version=$(cat .go-version)" >> $GITHUB_OUTPUT get-product-version: runs-on: ubuntu-latest @@ -40,7 +40,7 @@ jobs: id: get-product-version run: | make version - echo "::set-output name=product-version::$(make version)" + echo "product-version=$(make version)" >> $GITHUB_OUTPUT generate-metadata-file: needs: get-product-version @@ -269,6 +269,15 @@ jobs: run: | cd "${ZIP_LOCATION}" unzip -j *.zip + + # This naming convention will be used ONLY for per-commit dev images + - name: Set docker dev tag + run: | + echo "full_dev_tag=${{ env.version }}" + echo "full_dev_tag=${{ env.version }}" >> $GITHUB_ENV + echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" + echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV + - name: Docker Build (Action) uses: hashicorp/actions-docker-build@v1 if: ${{ !matrix.fips }} @@ -289,8 +298,10 @@ jobs: tags: | docker.io/hashicorp/${{ env.repo }}-control-plane:${{ env.version }} dev_tags: | - hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }} - docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-${{ github.sha }} - name: Docker FIPS Build (Action) uses: hashicorp/actions-docker-build@v1 @@ -312,8 +323,10 @@ jobs: tags: | docker.io/hashicorp/${{ env.repo }}-control-plane-fips:${{ env.version }} dev_tags: | - hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.version }} - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.version }}-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.full_dev_tag }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.full_dev_tag }}-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }}-${{ github.sha }} build-docker-ubi-redhat-registry: name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI build for RedHat Registry @@ -405,6 +418,15 @@ jobs: - name: Copy LICENSE run: cp LICENSE ./control-plane + + # This naming convention will be used ONLY for per-commit dev images + - name: Set docker dev tag + run: | + echo "full_dev_tag=${{ env.version }}" + echo "full_dev_tag=${{ env.version }}" >> $GITHUB_ENV + echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" + echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV + - name: Docker Build (Action) uses: hashicorp/actions-docker-build@v1 if: ${{ !matrix.fips }} @@ -425,8 +447,11 @@ jobs: tags: | docker.io/hashicorp/${{ env.repo }}-control-plane:${{ env.version }}-ubi dev_tags: | - hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-ubi - docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-ubi-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-ubi + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-ubi-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi-${{ github.sha }} + - name: Docker FIPS Build (Action) uses: hashicorp/actions-docker-build@v1 if: ${{ matrix.fips }} @@ -447,5 +472,7 @@ jobs: tags: | docker.io/hashicorp/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi dev_tags: | - hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-ubi + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-ubi-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi-${{ github.sha }} diff --git a/.github/workflows/nightly-acceptance-1-3-0-rc1.yml b/.github/workflows/nightly-acceptance-1-3-0.yml similarity index 93% rename from .github/workflows/nightly-acceptance-1-3-0-rc1.yml rename to .github/workflows/nightly-acceptance-1-3-0.yml index ad2b373ca4..e7681a6835 100644 --- a/.github/workflows/nightly-acceptance-1-3-0-rc1.yml +++ b/.github/workflows/nightly-acceptance-1-3-0.yml @@ -1,5 +1,5 @@ # Dispatch to the consul-k8s-workflows with a nightly cron -name: nightly-acceptance +name: nightly-acceptance-1-3-0 on: schedule: # * is a special character in YAML so you have to quote this string @@ -8,7 +8,7 @@ on: # these should be the only settings that you will ever need to change env: - BRANCH: "release/1.3.0-rc1" + BRANCH: "release/1.3.0" CONTEXT: "nightly" jobs: diff --git a/.github/workflows/weekly-acceptance-0-49-x.yml b/.github/workflows/weekly-acceptance-1-3-x.yml similarity index 81% rename from .github/workflows/weekly-acceptance-0-49-x.yml rename to .github/workflows/weekly-acceptance-1-3-x.yml index 5e1c17f3c7..e0cd935204 100644 --- a/.github/workflows/weekly-acceptance-0-49-x.yml +++ b/.github/workflows/weekly-acceptance-1-3-x.yml @@ -1,16 +1,18 @@ # 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-0-49-x +name: weekly-acceptance-1-3-x on: schedule: # * is a special character in YAML so you have to quote this string - # Run weekly on Monday at 3AM UTC/11PM EST/8PM PST - - cron: '0 3 * * 1' + # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST + # - cron: '0 3 * * 3' + - cron: '0 0 * * *' # Temporarily nightly until 1.2.0 GA + # these should be the only settings that you will ever need to change env: - BRANCH: "release/0.49.x" + BRANCH: "release/1.3.x" CONTEXT: "weekly" jobs: diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index de8e6e9df6..28b84434a3 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -3,8 +3,8 @@ apiVersion: v2 name: consul -version: 1.3.0-dev -appVersion: 1.17-dev +version: 1.4.0-dev +appVersion: 1.18-dev kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -16,11 +16,11 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: docker.mirror.hashicorp.services/hashicorppreview/consul-enterprise:1.17-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.18-dev - name: consul-k8s-control-plane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.3.0-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.4-dev - name: consul-dataplane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.3-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.4-dev - name: envoy image: envoyproxy/envoy:v1.26.2 artifacthub.io/license: MPL-2.0 diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index c9b49b6231..c27af564fa 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.17-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.18-dev # 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.3.0-dev + imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.4-dev # The name of the datacenter that the agents should # register as. This can't be changed once the Consul cluster is up and running @@ -639,7 +639,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.3-dev + imageConsulDataplane: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.4-dev # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. diff --git a/cli/version/version.go b/cli/version/version.go index 952cd9ca81..da2c79a1b4 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -17,7 +17,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.3.0" + Version = "1.4.0" // 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 diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index baf74dabb3..99372f7aec 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -202,7 +202,7 @@ func ConsulNamespaceIsNotFound(err error) bool { if !ok { return false } - if codes.InvalidArgument == s.Code() && strings.Contains(s.Message(), "namespace resource not found") { + if codes.InvalidArgument == s.Code() && strings.Contains(s.Message(), "namespace not found") { return true } return false diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index 3f41c414fb..f7fff948a6 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -507,7 +507,7 @@ func Test_ConsulNamespaceIsNotFound(t *testing.T) { }, { name: "namespace is missing", - input: status.Error(codes.InvalidArgument, "namespace resource not found"), + input: status.Error(codes.InvalidArgument, "namespace not found"), expectMissingNamespace: true, }, } @@ -572,7 +572,7 @@ func Test_ConsulNamespaceIsNotFound_ErrorMsg(t *testing.T) { s, ok := status.FromError(err) require.True(t, ok) require.Equal(t, codes.InvalidArgument, s.Code()) - require.Contains(t, s.Message(), "namespace resource not found") + require.Contains(t, s.Message(), "namespace not found") require.True(t, ConsulNamespaceIsNotFound(err)) } diff --git a/control-plane/version/version.go b/control-plane/version/version.go index 952cd9ca81..da2c79a1b4 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -17,7 +17,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.3.0" + Version = "1.4.0" // 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 From 6a3d0ab7cc8bc1c569faf1cb89bd5194ce36d5c8 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 19 Oct 2023 15:27:59 -0400 Subject: [PATCH 443/592] Enable GW JWT Tests (#3097) * kind of running * WIP * Unskip test and setup so it works correctly * revert typo * Move tokens into constants * Adds test scenario for adding a second gateway policy to a gateway, should and will fail * linter * Add case for multiple routes on listener with only one defining JWT configuration * Multiple routes referencing the same external ref * Add check for route referencing route extension outside of local namespace failing --------- Co-authored-by: Melisa Griffin --- .../tests/api-gateway/api_gateway_test.go | 205 ++++++++++++++---- .../api-gateways/jwt-auth/api-gateway.yaml | 8 +- .../jwt-auth/external-ref-other-ns.yaml | 16 ++ .../extra-gateway-policy.yaml | 25 +++ .../extraGatewayPolicy/kustomization.yaml | 7 + .../api-gateways/jwt-auth/gateway-policy.yaml | 28 ++- .../api-gateways/jwt-auth/httproute-auth.yaml | 6 +- .../httproute-invalid-external-ref.yaml | 32 +++ .../httproute-no-auth-on-auth-listener.yaml | 26 +++ .../api-gateways/jwt-auth/httproute.yaml | 2 +- .../jwt-auth/httproute2-auth.yaml | 32 +++ .../jwt-auth/jwt-route-filter.yaml | 18 +- .../api-gateways/jwt-auth/kustomization.yaml | 16 +- 13 files changed, 348 insertions(+), 73 deletions(-) create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index df4a097b7e..bbbaa569d4 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -13,12 +13,13 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -288,8 +289,14 @@ func TestAPIGateway_Basic(t *testing.T) { } } +const ( + // valid JWT token with role of "doctor". + doctorToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJkb2N0b3IifQ.FfgpzjMf8Evh6K-fJ1cLXklfIXOm-vojVbWlPPbGVFtzxZ9hxMxoyAY_G8i36SfGrpUlp-RJ6ohMvprMrEgyRgbenu7u5kkm5iGHW-zpMus4izXRxPELBcpWOGF105HIssT2NYRstXieNR8EVzvGfLdvR0GW8ttEERgseqGvuAfdb4-aNYsysGwUUHbsZjazA6H1rZmWqHdCLOJ2ZwFsIdckO9CadnkyTILpcPUmLYyUVJdtlLGOySb0GG8c_dPML_IR5jSXCSUZt6S2JBNBNBdqukrlqpA-fIaaWft0dbWVMhv8DqPC8znult8dKvLZ1qXeU0itsqqJUyE16ihJjw" + // valid JWT token with role of "pet". + petToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJwZXQifQ.l94rJayGGTMB426HwEw5ipSjaIHjm-UWDHiBAlB_Slmi814AxAfl_0AdRwSz67UDnkoygKbvPpR5xUB03JCXNshLZuKLegWsBeQg_OJYvZGmFagl5NglBFvH7Jbta4e1eQoAxZI6Xyy1jHbu7jFBjQPVnK8EaRvWoW8Pe8a8rp_5xhub0pomhvRF6Pm5kAS4cMnxvqpVc5Oo5nO7ws_SmoNnbt2Ok14k23Zx5E2EWmGStOfbgFsdbhVbepB2DMzqv1j8jvBbwa_OxCwc_7pEOthOOxRV6L3ZjgbRSB4GumlXAOCBYXD1cRLgrMSrWB1GkefAKu8PV0Ho1px6sI9Evg" +) + func TestAPIGateway_JWTAuth_Basic(t *testing.T) { - t.Skip("skipping this test until GW JWT auth is complete") ctx := suite.Environment().DefaultContext(t) cfg := suite.Config() @@ -300,7 +307,7 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { helmValues := map[string]string{ "connectInject.enabled": "true", "connectInject.consulNamespaces.mirroringK8S": "true", - "global.acls.manageSystemACLs": "true", // acls must be enabled for JWT auth to take place + "global.acls.manageSystemACLs": "true", "global.tls.enabled": "true", "global.logLevel": "trace", } @@ -310,8 +317,10 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { consulCluster.Create(t) + // this is necesary when running tests with ACLs enabled + runTestsAsSecure := true // Override the default proxy config settings for this test - consulClient, _ := consulCluster.SetupConsulClient(t, true) + consulClient, _ := consulCluster.SetupConsulClient(t, runTestsAsSecure) _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ Kind: api.ProxyDefaults, Name: api.ProxyConfigGlobal, @@ -321,8 +330,17 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { }, nil) require.NoError(t, err) + logger.Log(t, "creating other namespace") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "create", "namespace", "other") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "namespace", "other") + }) + logger.Log(t, "creating api-gateway resources") - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth") + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth") require.NoError(t, err, out) helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected @@ -330,6 +348,23 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth") }) + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-n", "other", "-f", "../fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-n", "other", "-f", "../fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml") + }) + + logger.Log(t, "try (and fail) to add a second gateway policy to the gateway") + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy") + require.Error(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy") + }) + // Create certificate secret, we do this separately since // applying the secret will make an invalid certificate that breaks other tests logger.Log(t, "creating certificate secret") @@ -360,16 +395,19 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { // leader election so we may need to wait a long time for // the reconcile loop to run (hence the 2m timeout here). var ( - gatewayAddress string - gatewayClass gwv1beta1.GatewayClass - httpRoute gwv1beta1.HTTPRoute - httpRouteAuth gwv1beta1.HTTPRoute + gatewayAddress string + gatewayClass gwv1beta1.GatewayClass + httpRoute gwv1beta1.HTTPRoute + httpRouteAuth gwv1beta1.HTTPRoute + httpRouteAuth2 gwv1beta1.HTTPRoute + httpRouteNoAuthOnAuthListener gwv1beta1.HTTPRoute + httpRouteInvalid gwv1beta1.HTTPRoute ) counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) require.NoError(r, err) // check our finalizers @@ -379,13 +417,13 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { // check our statuses checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) - require.Len(r, gateway.Status.Listeners, 4) + require.Len(r, gateway.Status.Listeners, 5) - require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) + require.EqualValues(r, int32(3), gateway.Status.Listeners[0].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, 1, gateway.Status.Listeners[1].AttachedRoutes) + require.EqualValues(r, int32(1), gateway.Status.Listeners[1].AttachedRoutes) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) @@ -411,6 +449,18 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth", Namespace: "default"}, &httpRouteAuth) require.NoError(r, err) + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-no-auth-on-auth-listener", Namespace: "default"}, &httpRouteNoAuthOnAuthListener) + require.NoError(r, err) + + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route2-auth", Namespace: "default"}, &httpRouteAuth2) + require.NoError(r, err) + + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth-invalid", Namespace: "default"}, &httpRouteInvalid) + require.NoError(r, err) + // check our finalizers require.Len(r, httpRoute.Finalizers, 1) require.EqualValues(r, gatewayFinalizer, httpRoute.Finalizers[0]) @@ -434,6 +484,38 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + + // check our finalizers + require.Len(r, httpRouteNoAuthOnAuthListener.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, httpRouteNoAuthOnAuthListener.Finalizers[0]) + + // check parent status + require.Len(r, httpRouteNoAuthOnAuthListener.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRouteNoAuthOnAuthListener.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRouteNoAuthOnAuthListener.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + + // check our finalizers + require.Len(r, httpRouteAuth2.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, httpRouteAuth2.Finalizers[0]) + + // check parent status + require.Len(r, httpRouteAuth2.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRouteAuth2.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRouteAuth2.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + + // check parent status + require.Len(r, httpRouteInvalid.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRouteInvalid.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRouteInvalid.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, falseCondition("Accepted", "FilterNotFound")) + checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) }) // check that the Consul entries were created @@ -459,22 +541,12 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { // finally we check that we can actually route to the service(s) via the gateway k8sOptions := ctx.KubectlOptions(t) targetHTTPAddress := fmt.Sprintf("http://%s/v1", gatewayAddress) - targetHTTPAddressAdmin := fmt.Sprintf("http://%s:8080/admin", gatewayAddress) - targetHTTPAddressPet := fmt.Sprintf("http://%s:8080/pet", gatewayAddress) - // valid JWT token with role of "doctor" - doctorToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJkb2N0b3IifQ.FfgpzjMf8Evh6K-fJ1cLXklfIXOm-vojVbWlPPbGVFtzxZ9hxMxoyAY_G8i36SfGrpUlp-RJ6ohMvprMrEgyRgbenu7u5kkm5iGHW-zpMus4izXRxPELBcpWOGF105HIssT2NYRstXieNR8EVzvGfLdvR0GW8ttEERgseqGvuAfdb4-aNYsysGwUUHbsZjazA6H1rZmWqHdCLOJ2ZwFsIdckO9CadnkyTILpcPUmLYyUVJdtlLGOySb0GG8c_dPML_IR5jSXCSUZt6S2JBNBNBdqukrlqpA-fIaaWft0dbWVMhv8DqPC8znult8dKvLZ1qXeU0itsqqJUyE16ihJjw" - // valid JWT token with role of "pet" - petToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJwZXQifQ.l94rJayGGTMB426HwEw5ipSjaIHjm-UWDHiBAlB_Slmi814AxAfl_0AdRwSz67UDnkoygKbvPpR5xUB03JCXNshLZuKLegWsBeQg_OJYvZGmFagl5NglBFvH7Jbta4e1eQoAxZI6Xyy1jHbu7jFBjQPVnK8EaRvWoW8Pe8a8rp_5xhub0pomhvRF6Pm5kAS4cMnxvqpVc5Oo5nO7ws_SmoNnbt2Ok14k23Zx5E2EWmGStOfbgFsdbhVbepB2DMzqv1j8jvBbwa_OxCwc_7pEOthOOxRV6L3ZjgbRSB4GumlXAOCBYXD1cRLgrMSrWB1GkefAKu8PV0Ho1px6sI9Evg" - - // check that intentions keep our connection from happening - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddress) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressAdmin) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressAdmin) - - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressPet) - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressPet) + targetHTTPAddressAdmin := fmt.Sprintf("http://%s:8081/admin", gatewayAddress) + targetHTTPAddressPet := fmt.Sprintf("http://%s:8081/pet", gatewayAddress) + targetHTTPAddressAdmin2 := fmt.Sprintf("http://%s:8081/admin-2", gatewayAddress) + targetHTTPAddressPet2 := fmt.Sprintf("http://%s:8081/pet-2", gatewayAddress) + targetHTTPAddressAdminNoAuthOnRoute := fmt.Sprintf("http://%s:8081/admin-no-auth", gatewayAddress) + targetHTTPAddressPetNotAuthOnRoute := fmt.Sprintf("http://%s:8081/pet-no-auth", gatewayAddress) // Now we create the allow intention. _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ @@ -505,38 +577,93 @@ func TestAPIGateway_JWTAuth_Basic(t *testing.T) { logger.Log(t, "trying calls to api gateway http") k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) - // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has an issuer of "local" and a "role" of "doctor" + // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that a "role" of "doctor" // we can see that: - // * the "iss" verification in the gateway override takes precedence over the "iss" verification in the route filter // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default + // should fail because we're missing JWT logger.Log(t, "trying calls to api gateway /admin should fail without JWT token") k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin) // should fail because we use the token with the wrong role and correct issuer logger.Log(t, "trying calls to api gateway /admin should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressAdmin) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdmin) // will succeed because we use the token with the correct role and the correct issuer logger.Log(t, "trying calls to api gateway /admin should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressAdmin) + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdmin) - // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has an issuer of "local" and a "role" of "pet" + // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has a "role" of "pet" // the route does not define // we can see that: - // * the "iss" verification in the gateway override takes precedence over the "iss" verification in the route filter - // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default + // * the "role" verification in the gateway default is used + // should fail because we're missing JWT logger.Log(t, "trying calls to api gateway /pet should fail without JWT token") k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet) // should fail because we use the token with the wrong role and correct issuer logger.Log(t, "trying calls to api gateway /pet should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", doctorToken, targetHTTPAddressPet) + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPet) // will succeed because we use the token with the correct role and the correct issuer logger.Log(t, "trying calls to api gateway /pet should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", petToken, targetHTTPAddressPet) + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPet) + + // ensure that routes attached to the same gateway don't cause changes in another route + // * the verification on the gateway is the only one used as this route does not define any JWT configuration + + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /pet-no-auth should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPetNotAuthOnRoute) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /pet-no-auth should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPetNotAuthOnRoute) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /pet-no-auth should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPetNotAuthOnRoute) + + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /admin-no-auth should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdminNoAuthOnRoute) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /admin-no-auth should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdminNoAuthOnRoute) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /admin-no-auth should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdminNoAuthOnRoute) + + // multiple routes can use the same external ref + // we can see that: + // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default + + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /admin-2 should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin2) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /admin-2 should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdmin2) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /admin-2 should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdmin2) + + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /pet-2 should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet2) + + // should fail because we use the token with the wrong role and correct issuer + logger.Log(t, "trying calls to api gateway /pet-2 should fail with wrong role") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPet2) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /pet-2 should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPet2) } func checkStatusCondition(t require.TestingT, conditions []metav1.Condition, toCheck metav1.Condition) { diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml index bef7e96f12..64e3d3c8d5 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml @@ -9,11 +9,17 @@ spec: gatewayClassName: gateway-class listeners: - protocol: HTTP - port: 8080 + port: 8081 name: http-auth allowedRoutes: namespaces: from: "All" + - protocol: HTTP + port: 8082 + name: http-invalid-attach + allowedRoutes: + namespaces: + from: "All" - protocol: HTTP port: 80 name: http diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml new file mode 100644 index 0000000000..19b0669c1a --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml @@ -0,0 +1,16 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteAuthFilter +metadata: + name: route-jwt-auth-filter-other + namespace: other +spec: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml new file mode 100644 index 0000000000..03960f67be --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml @@ -0,0 +1,25 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +# This is used to show that a gateway cannot have more than one gateway policy attached to it +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayPolicy +metadata: + name: bad-policy +spec: + targetRef: + name: gateway + sectionName: http-auth + group: gateway.networking.k8s.io/v1beta1 + kind: Gateway + override: + jwt: + providers: + - name: "local" + default: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml new file mode 100644 index 0000000000..0886ca4bed --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- extra-gateway-policy.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml index 6c58632d87..5552d7e085 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml @@ -2,26 +2,24 @@ # SPDX-License-Identifier: MPL-2.0 apiVersion: consul.hashicorp.com/v1alpha1 -kind: ConsulGatewayPolicy +kind: GatewayPolicy metadata: name: my-policy spec: targetRef: name: gateway + sectionName: http-auth + group: gateway.networking.k8s.io/v1beta1 kind: Gateway - group: gateway.networking.kuberenetes.io - sectionName: http override: - Providers: - - Provider: "local" - VerifyClaims: - - Path: - - "iss" - Value: "local" + jwt: + providers: + - name: "local" default: - Providers: - - Provider: "local" - VerifyClaims: - - Path: - - "iss" - Value: "local" + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml index 4963277c55..93fee5f24a 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml @@ -16,12 +16,12 @@ spec: value: "/admin" backendRefs: - name: static-server - port: 80 + port: 8080 filters: - type: ExtensionRef extensionRef: group: consul.hashicorp.com - kind: HTTPRouteAuthFilter + kind: RouteAuthFilter name: route-jwt-auth-filter - matches: - path: @@ -29,4 +29,4 @@ spec: value: "/pet" backendRefs: - name: static-server - port: 80 + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml new file mode 100644 index 0000000000..55753c29aa --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml @@ -0,0 +1,32 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route-auth-invalid +spec: + parentRefs: + - name: gateway + sectionName: http-invalid-attach + rules: + - matches: + - path: + type: PathPrefix + value: "/admin" + backendRefs: + - name: static-server + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteAuthFilter + name: route-jwt-auth-filter-other + - matches: + - path: + type: PathPrefix + value: "/pet" + backendRefs: + - name: static-server + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml new file mode 100644 index 0000000000..e4dc1b5a8b --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml @@ -0,0 +1,26 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route-no-auth-on-auth-listener +spec: + parentRefs: + - name: gateway + sectionName: http-auth + rules: + - matches: + - path: + type: PathPrefix + value: "/admin-no-auth" + backendRefs: + - name: static-server + port: 8080 + - matches: + - path: + type: PathPrefix + value: "/pet-no-auth" + backendRefs: + - name: static-server + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml index 52e206a91e..b505d36cb1 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml @@ -16,4 +16,4 @@ spec: value: "/v1" backendRefs: - name: static-server - port: 80 + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml new file mode 100644 index 0000000000..3894e654ff --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml @@ -0,0 +1,32 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route2-auth +spec: + parentRefs: + - name: gateway + sectionName: http-auth + rules: + - matches: + - path: + type: PathPrefix + value: "/admin-2" + backendRefs: + - name: static-server + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteAuthFilter + name: route-jwt-auth-filter + - matches: + - path: + type: PathPrefix + value: "/pet-2" + backendRefs: + - name: static-server + port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml index ed0b0bbe5e..9ea3ee2acd 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml @@ -2,14 +2,14 @@ # SPDX-License-Identifier: MPL-2.0 apiVersion: consul.hashicorp.com/v1alpha1 -kind: HTTPRouteAuthFilter +kind: RouteAuthFilter metadata: - name: example-route-jwt-filter + name: route-jwt-auth-filter spec: - type: JWT - JWTProviders: - - Provider: "local" - VerifyClaims: - - Path: - - "role" - Value: "doctor" + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml index 9730a1a4ac..648c936746 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml @@ -1,14 +1,20 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization resources: - ../../../bases/api-gateway - ../../static-server-inject -- ./httproute.yaml -- ./jwt-provider.yaml +- httproute-auth.yaml +- httproute-invalid-external-ref.yaml +- httproute2-auth.yaml +- httproute-no-auth-on-auth-listener.yaml +- jwt-provider.yaml +- jwt-route-filter.yaml +- gateway-policy.yaml + -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization patches: -- patch: httproute-no-auth.yaml +- path: httproute.yaml - path: api-gateway.yaml From dfa21411fed6591a1d31e567ccca8a053ba4e4c1 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:24:05 -0700 Subject: [PATCH 444/592] disable TestPartitions_Connect for CNI due to flake (#3078) --- acceptance/tests/partitions/partitions_connect_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index 444e70b952..3a3824fade 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -30,6 +30,11 @@ func TestPartitions_Connect(t *testing.T) { env := suite.Environment() cfg := suite.Config() + // Currently there is a bug which causes flakes when CNI is enabled + if cfg.EnableCNI { + t.Skipf("TODO(flaky): NET-5819") + } + if !cfg.EnableEnterprise { t.Skipf("skipping this test because -enable-enterprise is not set") } From 75ed6de70149158d0f19384346d15ff0b5cc6f6b Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Thu, 19 Oct 2023 14:57:01 -0700 Subject: [PATCH 445/592] add probes now that expose paths are supported (#3096) --- .../bases/v2-multiport-app/deployment.yaml | 49 +++++++++---------- acceptance/tests/mesh_v2/mesh_inject_test.go | 1 + .../webhookv2/consul_dataplane_sidecar.go | 6 +-- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml index 77c7de3bc9..0fecd3b590 100644 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml +++ b/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml @@ -31,18 +31,18 @@ spec: ports: - containerPort: 8080 name: web -# livenessProbe: -# httpGet: -# port: 8080 -# initialDelaySeconds: 1 -# failureThreshold: 1 -# periodSeconds: 1 -# startupProbe: -# httpGet: -# port: 8080 -# initialDelaySeconds: 1 -# failureThreshold: 30 -# periodSeconds: 1 + livenessProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + startupProbe: + httpGet: + port: 8080 + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 1 readinessProbe: exec: command: ['sh', '-c', 'test ! -f /tmp/unhealthy-multiport'] @@ -59,19 +59,18 @@ spec: # This name is meant to be used alongside the _numeric_ K8s service target port # to verify that we can still route traffic to the named port when there's a mismatch. name: admin -# TODO: (v2/nitya) add these probes back when expose paths and L7 are supported. -# livenessProbe: -# httpGet: -# port: 9090 -# initialDelaySeconds: 1 -# failureThreshold: 1 -# periodSeconds: 1 -# startupProbe: -# httpGet: -# port: 9090 -# initialDelaySeconds: 1 -# failureThreshold: 30 -# periodSeconds: 1 + livenessProbe: + httpGet: + port: 9090 + initialDelaySeconds: 1 + failureThreshold: 1 + periodSeconds: 1 + startupProbe: + httpGet: + port: 9090 + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 1 readinessProbe: exec: command: ['sh', '-c', 'test ! -f /tmp/unhealthy-multiport-admin'] diff --git a/acceptance/tests/mesh_v2/mesh_inject_test.go b/acceptance/tests/mesh_v2/mesh_inject_test.go index 3af7c31dc9..69405a5da6 100644 --- a/acceptance/tests/mesh_v2/mesh_inject_test.go +++ b/acceptance/tests/mesh_v2/mesh_inject_test.go @@ -35,6 +35,7 @@ func TestMeshInject_MultiportService(t *testing.T) { helmValues := map[string]string{ "global.experiments[0]": "resource-apis", + "global.image": "ndhanushkodi/consul-dev:expose2", // The UI is not supported for v2 in 1.17, so for now it must be disabled. "ui.enabled": "false", "connectInject.enabled": "true", diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go index 527c77f265..ed0983a1c8 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go @@ -122,11 +122,7 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor Args: args, } - // Omit the readiness probe in transparent proxy mode until expose paths are implemented. Otherwise all probes will fail. - // TODO: (v2/nitya) add probes in tproxy mode when expose paths and L7 are supported. - if !w.EnableTransparentProxy { - container.ReadinessProbe = probe - } + container.ReadinessProbe = probe if w.AuthMethod != "" { container.VolumeMounts = append(container.VolumeMounts, saTokenVolumeMount) From 2aac33975ff7bfcd03b81517743fb113087426d0 Mon Sep 17 00:00:00 2001 From: Chris Thain <32781396+cthain@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:12:55 -0700 Subject: [PATCH 446/592] Update Envoy (#3116) --- .changelog/3116.txt | 3 +++ charts/consul/Chart.yaml | 2 +- charts/consul/values.yaml | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 .changelog/3116.txt diff --git a/.changelog/3116.txt b/.changelog/3116.txt new file mode 100644 index 0000000000..7a38debb7d --- /dev/null +++ b/.changelog/3116.txt @@ -0,0 +1,3 @@ +```release-note:security +Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) +``` diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index 28b84434a3..c33e0f4dac 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -22,7 +22,7 @@ annotations: - name: consul-dataplane image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.4-dev - name: envoy - image: envoyproxy/envoy:v1.26.2 + image: envoyproxy/envoy:v1.25.11 artifacthub.io/license: MPL-2.0 artifacthub.io/links: | - name: Documentation diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index c27af564fa..c9abd21713 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -3233,7 +3233,7 @@ terminatingGateways: gateways: - name: terminating-gateway -# [DEPRECATED] Use connectInject.apiGateway instead. This stanza will be removed with the release of Consul 1.17 +# [DEPRECATED] Use connectInject.apiGateway instead. # Configuration settings for the Consul API Gateway integration apiGateway: # When true the helm chart will install the Consul API Gateway controller @@ -3248,7 +3248,7 @@ apiGateway: # The name (and tag) of the Envoy Docker image used for the # apiGateway. For other Consul compoenents, imageEnvoy has been replaced with Consul Dataplane. # @default: envoyproxy/envoy: - imageEnvoy: "envoyproxy/envoy:v1.25.1" + imageEnvoy: "envoyproxy/envoy:v1.25.11" # Override global log verbosity level for api-gateway-controller pods. One of "debug", "info", "warn", or "error". # @type: string From 811c82d645fd0ee830e2a28a7ae974f439638a05 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Mon, 23 Oct 2023 17:48:35 -0500 Subject: [PATCH 447/592] Remove copyright headers from external crds (#3124) * Remove copyright headers from externally-defined CRDs not owned by HashiCorp * Make comment more accurate in .copywrite.hcl config * Fix exclude path in .copywrite.hcl --- .copywrite.hcl | 4 ++-- .../external/gatewayclasses.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/gateways.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/grpcroutes.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/httproutes.gateway.networking.k8s.io.yaml | 3 --- .../external/referencegrants.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/tcproutes.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/tlsroutes.gateway.networking.k8s.io.yaml | 3 --- .../crd/external/udproutes.gateway.networking.k8s.io.yaml | 3 --- 9 files changed, 2 insertions(+), 26 deletions(-) diff --git a/.copywrite.hcl b/.copywrite.hcl index 7088df0e14..9263741105 100644 --- a/.copywrite.hcl +++ b/.copywrite.hcl @@ -11,8 +11,8 @@ project { # ignoring charts templates as adding copyright headers breaks all tests "charts/consul/templates/**", - # we don't own these and copyright headers break them - "control-plane/config/crds/external/**", + # we don't own these and the tool that adds copyright headers breaks them + "control-plane/config/crd/external/**", ] } diff --git a/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml index 044c7af939..ff0b2fc2f6 100644 --- a/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml index b7a7c8a7d1..fa64481667 100644 --- a/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml index 8f3ab6d385..8d190ea7b6 100644 --- a/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml index b455d788de..90c151a787 100644 --- a/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml index cd39b9c12a..5eee4889a4 100644 --- a/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml index 906b442d31..a136b28f41 100644 --- a/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml index 2e22b04ef0..cc3cf65d6c 100644 --- a/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml index 19b03dcd0b..204f8e4824 100644 --- a/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml +++ b/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: From 901f55c1b77f6811fb6170d5ccb7a5fb03ae2f14 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Tue, 24 Oct 2023 09:38:15 -0400 Subject: [PATCH 448/592] test(v2): check for token deletion in acceptance test (#3103) --- acceptance/tests/mesh_v2/mesh_inject_test.go | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/acceptance/tests/mesh_v2/mesh_inject_test.go b/acceptance/tests/mesh_v2/mesh_inject_test.go index 69405a5da6..d54229d84b 100644 --- a/acceptance/tests/mesh_v2/mesh_inject_test.go +++ b/acceptance/tests/mesh_v2/mesh_inject_test.go @@ -8,7 +8,9 @@ import ( "fmt" "strconv" "testing" + "time" + "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -51,6 +53,26 @@ func TestMeshInject_MultiportService(t *testing.T) { consulCluster.Create(t) + consulClient, _ := consulCluster.SetupConsulClient(t, secure) + + // Check that the ACL token is deleted. + if secure { + // We need to register the cleanup function before we create the deployments + // because golang will execute them in reverse order i.e. the last registered + // cleanup function will be executed first. + t.Cleanup(func() { + retrier := &retry.Timer{Timeout: 5 * time.Minute, Wait: 1 * time.Second} + retry.RunWith(retrier, t, func(r *retry.R) { + tokens, _, err := consulClient.ACL().TokenList(nil) + require.NoError(r, err) + for _, token := range tokens { + require.NotContains(r, token.Description, multiport) + require.NotContains(r, token.Description, connhelper.StaticClientName) + } + }) + }) + } + logger.Log(t, "creating multiport static-server and static-client deployments") k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/bases/v2-multiport-app") if cfg.EnableTransparentProxy { @@ -128,8 +150,6 @@ func TestMeshInject_MultiportService(t *testing.T) { k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") } - - // TODO: verify that ACL tokens are removed }) } } From 0d85bbc3131ce8be23c57e30b213ba6056623976 Mon Sep 17 00:00:00 2001 From: Sophie Gairo <97480023+sophie-gairo@users.noreply.github.com> Date: Tue, 24 Oct 2023 18:10:43 -0500 Subject: [PATCH 449/592] NET-6204- Repeating error log in consul-connect-injector (#3128) * better handle gateway timeout errors * strings not refs * changelog * Add missing import, fix import blocking --------- Co-authored-by: Nathan Coleman --- .changelog/3128.txt | 3 +++ control-plane/api-gateway/cache/gateway.go | 14 +++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 .changelog/3128.txt diff --git a/.changelog/3128.txt b/.changelog/3128.txt new file mode 100644 index 0000000000..0e7d321518 --- /dev/null +++ b/.changelog/3128.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: only alert on valid errors, not timeouts in gateway +``` diff --git a/control-plane/api-gateway/cache/gateway.go b/control-plane/api-gateway/cache/gateway.go index 0d79542eec..c6d2c31099 100644 --- a/control-plane/api-gateway/cache/gateway.go +++ b/control-plane/api-gateway/cache/gateway.go @@ -6,14 +6,16 @@ package cache import ( "context" "fmt" + "strings" "sync" "github.com/cenkalti/backoff" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" "k8s.io/apimachinery/pkg/types" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/consul" ) type GatewayCache struct { @@ -125,7 +127,13 @@ func (r *GatewayCache) subscribeToGateway(ctx context.Context, ref api.ResourceR return nil }, backoff.WithContext(retryBackoff, ctx)); err != nil { - r.logger.Error(err, fmt.Sprintf("unable to fetch config entry for gateway: %s/%s", ref.Namespace, ref.Name)) + // if we timeout we don't care about the error message because it's expected to happen on long polls + // any other error we want to alert on + if !strings.Contains(strings.ToLower(err.Error()), "timeout") && + !strings.Contains(strings.ToLower(err.Error()), "no such host") && + !strings.Contains(strings.ToLower(err.Error()), "connection refused") { + r.logger.Error(err, fmt.Sprintf("unable to fetch config entry for gateway: %s/%s", ref.Namespace, ref.Name)) + } continue } From 692c783154b34e9c209eaf66eca5a94416b8b680 Mon Sep 17 00:00:00 2001 From: Ashvitha Date: Mon, 30 Oct 2023 13:22:31 -0400 Subject: [PATCH 450/592] [CC 5965] Improve HCP Observability E2E tests and add periodic tests (#2946) * Setup observability metrics tests * Cleanup http_client.go and observability_test.go * Refactor tests by using table driven approach * Refactor and add comments to the client * Remove param to token function * remove consul export test redundant * Remove hardcoded collector image * Move metrics validation from server to consul-k8s tests and update to use the /records endpoint * Change to achooo docker hub and fix lint errors --- acceptance/go.mod | 13 +- acceptance/go.sum | 50 ++--- acceptance/tests/cloud/fakeserver_client.go | 158 ++++++++++++++ acceptance/tests/cloud/metrics_validation.go | 114 ++++++++++ .../{basic_test.go => observability_test.go} | 204 ++++++------------ .../bases/cloud/hcp-mock/deployment.yaml | 2 +- 6 files changed, 370 insertions(+), 171 deletions(-) create mode 100644 acceptance/tests/cloud/fakeserver_client.go create mode 100644 acceptance/tests/cloud/metrics_validation.go rename acceptance/tests/cloud/{basic_test.go => observability_test.go} (58%) diff --git a/acceptance/go.mod b/acceptance/go.mod index 8ba1ebd0bb..13b68936bf 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -13,6 +13,8 @@ require ( github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.8.3 github.com/stretchr/testify v1.8.3 + go.opentelemetry.io/proto/otlp v1.0.0 + google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 @@ -30,7 +32,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deckarep/golang-set v1.7.1 // indirect @@ -61,6 +63,7 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect @@ -123,7 +126,7 @@ require ( golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect @@ -131,9 +134,9 @@ require ( golang.org/x/tools v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect - google.golang.org/grpc v1.49.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/grpc v1.56.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 91dc2e489f..ebf575b207 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -81,7 +81,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -111,8 +110,9 @@ github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3 github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -120,11 +120,6 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D 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/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -177,8 +172,6 @@ github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -306,6 +299,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -355,7 +349,6 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3/go.mod h1:2wIuQute9+hhWqvL3vEI7YB0EKluF4WcPzI1eAliazk= @@ -375,7 +368,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -392,7 +384,8 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/gruntwork-io/gruntwork-cli v0.7.0 h1:YgSAmfCj9c61H+zuvHwKfYUwlMhu5arnQQLM4RH+CYs= github.com/gruntwork-io/gruntwork-cli v0.7.0/go.mod h1:jp6Z7NcLF2avpY8v71fBx6hds9eOFPELSuD/VPv7w00= github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+XbVVtytUHg= @@ -517,7 +510,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -674,7 +667,6 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 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= @@ -776,7 +768,8 @@ go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZ go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +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.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -896,8 +889,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 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= @@ -965,7 +958,6 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1127,7 +1119,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -1135,8 +1126,11 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1151,11 +1145,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 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= @@ -1168,10 +1159,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -1195,7 +1184,6 @@ gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/acceptance/tests/cloud/fakeserver_client.go b/acceptance/tests/cloud/fakeserver_client.go new file mode 100644 index 0000000000..ec668d16e5 --- /dev/null +++ b/acceptance/tests/cloud/fakeserver_client.go @@ -0,0 +1,158 @@ +package cloud + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" +) + +const ( + // recordsPathConsul and recordsPathCollector distinguish metrics for consul vs. collector when fetching records. + recordsPathConsul = "v1/metrics/consul" + recordsPathCollector = "v1/metrics/collector" +) + +var ( + errEncodingPayload = errors.New("failed to encode payload") + errCreatingRequest = errors.New("failed to create HTTP request") + errMakingRequest = errors.New("failed to make request") + errReadingBody = errors.New("failed to read body") + errClosingBody = errors.New("failed to close body") + errParsingBody = errors.New("failed to parse body") +) + +// fakeServerClient provides an interface to communicate with the fakesever (a fake HCP Telemetry Gateway) via HTTP. +type fakeServerClient struct { + client *http.Client + tunnel string +} + +// modifyTelemetryConfigBody is a POST body that provides telemetry config changes to the fakeserver. +type modifyTelemetryConfigBody struct { + Filters []string `json:"filters"` + Labels map[string]string `json:"labels"` + Disabled bool `json:"disabled"` +} + +// TokenResponse is used to read a token response from the fakeserver. +type TokenResponse struct { + Token string `json:"token"` +} + +// RecordsResponse is used to read a /records response from the fakeserver. +type RecordsResponse struct { + Records []*RequestRecord `json:"records"` +} + +// RequestRecord holds info about a single request. +type RequestRecord struct { + Method string `json:"method"` + Path string `json:"path"` + Body []byte `json:"body"` + ValidRequest bool `json:"validRequest"` + Timestamp int64 `json:"timestamp"` +} + +// newfakeServerClient returns a fakeServerClient to be used in tests to communicate with the fake Telemetry Gateway. +func newfakeServerClient(tunnel string) *fakeServerClient { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + return &fakeServerClient{ + client: &http.Client{Transport: tr}, + tunnel: tunnel, + } +} + +// requestToken retrieves a token from the fakeserver's token endpoint. +func (f *fakeServerClient) requestToken() (string, error) { + url := fmt.Sprintf("https://%s/token", f.tunnel) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", fmt.Errorf("%w: %w", errCreatingRequest, err) + } + + resp, err := f.handleRequest(req) + if err != nil { + return "", err + } + + tokenResponse := &TokenResponse{} + err = json.Unmarshal(resp, tokenResponse) + if err != nil { + return "", fmt.Errorf("%w : %w", errParsingBody, err) + } + + return tokenResponse.Token, nil +} + +// modifyTelemetryConfig can update the telemetry config returned by the fakeserver. +// via the fakeserver's modify_telemetry_config endpoint. +func (f *fakeServerClient) modifyTelemetryConfig(payload *modifyTelemetryConfigBody) error { + url := fmt.Sprintf("https://%s/modify_telemetry_config", f.tunnel) + payloadBuf := new(bytes.Buffer) + + err := json.NewEncoder(payloadBuf).Encode(payload) + if err != nil { + return fmt.Errorf("%w:%w", errEncodingPayload, err) + } + + req, err := http.NewRequest("POST", url, payloadBuf) + if err != nil { + return fmt.Errorf("%w: %w", errCreatingRequest, err) + } + + _, err = f.handleRequest(req) + + return err +} + +func (f *fakeServerClient) getRecordsForPath(path string, refreshTime int64) ([]*RequestRecord, error) { + url := fmt.Sprintf("https://%s/records/%s", f.tunnel, path) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("%w: %w", errCreatingRequest, err) + } + if refreshTime > 0 { + q := req.URL.Query() + q.Add("since", strconv.FormatInt(refreshTime, 10)) + req.URL.RawQuery = q.Encode() + } + + resp, err := f.handleRequest(req) + if err != nil { + return nil, err + } + + recordsResponse := &RecordsResponse{} + err = json.Unmarshal(resp, recordsResponse) + if err != nil { + return nil, fmt.Errorf("%w : %w", errParsingBody, err) + } + + return recordsResponse.Records, nil +} + +// handleRequest returns the response body if the request is succesful. +func (f *fakeServerClient) handleRequest(req *http.Request) ([]byte, error) { + resp, err := f.client.Do(req) + if err != nil { + return nil, fmt.Errorf("%w : %w", errMakingRequest, err) + } + body, err := io.ReadAll(resp.Body) + cErr := resp.Body.Close() + if cErr != nil { + return nil, fmt.Errorf("%w : %w", errClosingBody, err) + } + if err != nil { + return nil, fmt.Errorf("%w : %w", errReadingBody, err) + } + + return body, nil +} diff --git a/acceptance/tests/cloud/metrics_validation.go b/acceptance/tests/cloud/metrics_validation.go new file mode 100644 index 0000000000..558ae54509 --- /dev/null +++ b/acceptance/tests/cloud/metrics_validation.go @@ -0,0 +1,114 @@ +package cloud + +import ( + "strings" + + "github.com/hashicorp/serf/testutil/retry" + "github.com/stretchr/testify/require" + otlpcolmetrics "go.opentelemetry.io/proto/otlp/collector/metrics/v1" + otlpcommon "go.opentelemetry.io/proto/otlp/common/v1" + otlpmetrics "go.opentelemetry.io/proto/otlp/metrics/v1" + "google.golang.org/protobuf/proto" +) + +type metricValidations struct { + disabled bool + expectedMetricName string + disallowedMetricName string + expectedLabelKeys []string +} + +// validateMetrics ensure OTLP metrics as recorded by the Collector or Consul as expected. +func validateMetrics(r *retry.R, records []*RequestRecord, validations *metricValidations, since int64) { + // If metrics are disabled, no metrics records should exist, and return early. + if validations.disabled { + require.Empty(r, records) + return + } + + // If metrics are not disabled, records should not be empty. + require.NotEmpty(r, records) + + for _, record := range records { + require.True(r, record.ValidRequest, "expected request to be valid") + + req := &otlpcolmetrics.ExportMetricsServiceRequest{} + err := proto.Unmarshal(record.Body, req) + require.NoError(r, err, "failed to extract metrics from body") + + // Basic validation that metrics are not empty. + require.NotEmpty(r, req.GetResourceMetrics()) + require.NotEmpty(r, req.ResourceMetrics[0].GetScopeMetrics()) + require.NotEmpty(r, req.ResourceMetrics[0].ScopeMetrics[0].GetMetrics()) + + // Verify expected key labels and metric names. + labels := externalLabels(req, since) + for _, key := range validations.expectedLabelKeys { + require.Contains(r, labels, key) + } + validateMetricName(r, req, validations) + } +} + +// validateMetricName ensures an expected metric name has been recorded based on filters and disallowed metrics are not present. +func validateMetricName(t *retry.R, request *otlpcolmetrics.ExportMetricsServiceRequest, validations *metricValidations) { + exists := false + for _, metric := range request.ResourceMetrics[0].ScopeMetrics[0].GetMetrics() { + require.NotContains(t, metric.Name, validations.disallowedMetricName) + + if strings.Contains(metric.Name, validations.expectedMetricName) { + exists = true + } + } + + require.True(t, exists) +} + +// externalLabels converts OTLP labels to a map[string]string format. +func externalLabels(request *otlpcolmetrics.ExportMetricsServiceRequest, since int64) map[string]string { + // For the Consul Telemetry Collector, labels are contained at the higher level scope. + attrs := request.ResourceMetrics[0].GetResource().GetAttributes() + + // For Consul server metrics, labels are contained with individual metrics, and must be extracted. + if len(attrs) < 1 { + attrs = getMetricLabel(request.ResourceMetrics[0].GetScopeMetrics(), since) + } + + labels := make(map[string]string, len(attrs)) + for _, kv := range attrs { + k := strings.ReplaceAll(kv.GetKey(), ".", "_") + labels[k] = kv.GetValue().GetStringValue() + } + + return labels +} + +// getMetricLabel returns labels at each datapoint within a metric. +func getMetricLabel(scopeMetrics []*otlpmetrics.ScopeMetrics, since int64) []*otlpcommon.KeyValue { + // The attributes field can only be accessed on the specific implementation (gauge, sum or hist). + for _, metric := range scopeMetrics[0].Metrics { + switch v := metric.Data.(type) { + case *otlpmetrics.Metric_Gauge: + for _, dp := range v.Gauge.GetDataPoints() { + // When a refresh has occured, filter time since last refresh as older data points may not have latest labels. + if dp.StartTimeUnixNano > uint64(since) { + return dp.Attributes + } + } + case *otlpmetrics.Metric_Histogram: + for _, dp := range v.Histogram.GetDataPoints() { + if dp.StartTimeUnixNano > uint64(since) { + return dp.Attributes + } + } + case *otlpmetrics.Metric_Sum: + for _, dp := range v.Sum.GetDataPoints() { + if dp.StartTimeUnixNano > uint64(since) { + return dp.Attributes + } + } + } + } + + return []*otlpcommon.KeyValue{} +} diff --git a/acceptance/tests/cloud/basic_test.go b/acceptance/tests/cloud/observability_test.go similarity index 58% rename from acceptance/tests/cloud/basic_test.go rename to acceptance/tests/cloud/observability_test.go index e17169700a..5a2bf7b365 100644 --- a/acceptance/tests/cloud/basic_test.go +++ b/acceptance/tests/cloud/observability_test.go @@ -4,12 +4,6 @@ package cloud import ( - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" "strings" "testing" "time" @@ -24,10 +18,6 @@ import ( "github.com/stretchr/testify/require" ) -type TokenResponse struct { - Token string `json:"token"` -} - var ( resourceSecretName = "resource-sec-name" resourceSecretKey = "resource-sec-key" @@ -54,47 +44,7 @@ var ( scadaAddressSecretKeyValue = "fake-server:443" ) -// The fake-server has a requestToken endpoint to retrieve the token. -func requestToken(endpoint string) (string, error) { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - - client := &http.Client{Transport: tr} - url := fmt.Sprintf("https://%s/token", endpoint) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - fmt.Println("Error creating request:", err) - return "", errors.New("error creating request") - } - - // Perform the request - resp, err := client.Do(req) - if err != nil { - fmt.Println("Error sending request:", err) - return "", errors.New("error making request") - } - defer resp.Body.Close() - - // Read the response body - body, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error reading response:", err) - return "", errors.New("error reading body") - } - - var tokenResponse TokenResponse - err = json.Unmarshal(body, &tokenResponse) - if err != nil { - fmt.Println("Error parsing response:", err) - return "", errors.New("error parsing body") - } - - return tokenResponse.Token, nil - -} - -func TestBasicCloud(t *testing.T) { +func TestObservabilityCloud(t *testing.T) { ctx := suite.Environment().DefaultContext(t) kubectlOptions := ctx.KubectlOptions(t) @@ -138,8 +88,9 @@ func TestBasicCloud(t *testing.T) { require.NoError(r, tunnel.ForwardPortE(t)) }) + fsClient := newfakeServerClient(tunnel.Endpoint()) logger.Log(t, "fake-server addr:"+tunnel.Endpoint()) - consulToken, err := requestToken(tunnel.Endpoint()) + consulToken, err := fsClient.requestToken() if err != nil { logger.Log(t, "error finding consul token") return @@ -171,13 +122,6 @@ func TestBasicCloud(t *testing.T) { "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, "connectInject.default": "true", - // TODO: Follow up with this bug - "global.acls.manageSystemACLs": "false", - "global.gossipEncryption.autoGenerate": "false", - "global.tls.enabled": "true", - "global.tls.enableAutoEncrypt": "true", - // TODO: Take this out - "telemetryCollector.enabled": "true", "telemetryCollector.image": cfg.ConsulCollectorImage, "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, @@ -185,8 +129,6 @@ func TestBasicCloud(t *testing.T) { "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, - // Either we set the global.trustedCAs (make sure it's idented exactly) or we - // set TLS to insecure "telemetryCollector.extraEnvironmentVars.HCP_API_TLS": "insecure", "telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", @@ -196,33 +138,10 @@ func TestBasicCloud(t *testing.T) { "server.extraEnvironmentVars.HCP_API_TLS": "insecure", "server.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", "server.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", - - // This is pregenerated CA used for testing. It can be replaced at any time and isn't - // meant for anything other than testing - // "global.trustedCAs[0]": `-----BEGIN CERTIFICATE----- - // MIICrjCCAZYCCQD5LxMcnMY8rDANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5m - // YWtlLXNlcnZlci1jYTAeFw0yMzA1MTkxMjIwMzhaFw0zMzA1MTYxMjIwMzhaMBkx - // FzAVBgNVBAMMDmZha2Utc2VydmVyLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A - // MIIBCgKCAQEAwhbiII7sMultedFzQVhVZz5Ti+9lWrpZb8y0ZR6NaNvoxDPX151t - // Adh5NegSeH/+351iDBGZHhmKECtBuk8FJgk88O7y8A7Yg+/lyeZd0SJTEeiYUe7d - // sSaBTYSmixyn6s15Y5MVp9gM7t2YXrocRkFxDtdhLMWf0zwzJEwDouFMMiFZw5II - // yDbI6UfwKyB8C8ln10+TcczbheaOMQ1jGn35YWAG/LEdutU6DO2Y/GZYQ41nyLF1 - // klqh34USQPVQSQW7R7GiDxyhh1fGaDF6RAzH4RerzQSNvvTHmBXIGurB/Hnu1n3p - // CwWeatWMU5POy1es73S/EPM0NpWD5RabSwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB - // AQBayoTltSW55PvKVp9cmqGOBMlkIMKPd6Ny4bCb/3UF+3bzQmIblh3O3kEt7WoY - // fA9vp+6cSRGVqgBfR2bi40RrerLNA79yywIZjfBMteNuRoul5VeD+mLyFCo4197r - // Atl2TEx2kl2V8rjCsEBcTqKqetVOMLYEZ2tbCeUt1A/K7OzaJfHgelEYcsVt68Q9 - // /BLoo2UXfOpRrcsx7u7s5HPVbG3bx+1MvGJZ2C3i0B6agnkGDzEpoM4KZGxEefB9 - // DOHIJfie9d9BQD52nZh3SGHz0b3vfJ430XrQmaNZ26fuIEyIYrpvyAhBXckj2iTD - // 1TXpqr/1D7EUbddktyhXTK9e - // -----END CERTIFICATE-----`, } if cfg.ConsulImage != "" { helmValues["global.image"] = cfg.ConsulImage } - if cfg.ConsulCollectorImage != "" { - helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage - } consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), suite.Config(), releaseName) consulCluster.Create(t) @@ -231,56 +150,73 @@ func TestBasicCloud(t *testing.T) { k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") t.Log("Finished deployment. Validating expected conditions now") - // Give some time for collector send metrics - time.Sleep(5 * time.Second) - err = validate(tunnel.Endpoint()) - logger.Log(t, fmt.Sprintf("result: %v", err)) - require.NoError(t, err) -} - -func validate(endpoint string) error { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - - client := &http.Client{Transport: tr} - url := fmt.Sprintf("https://%s/validation", endpoint) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - fmt.Println("Error creating request:", err) - return errors.New("error creating validation request") + for name, tc := range map[string]struct { + refresh *modifyTelemetryConfigBody + refreshTime int64 + recordsPath string + timeout time.Duration + wait time.Duration + validations *metricValidations + }{ + "collectorExportsMetrics": { + recordsPath: recordsPathCollector, + // High timeout as Collector metrics scraped every 1 minute (https://github.com/hashicorp/consul-telemetry-collector/blob/dfdbf51b91d502a18f3b143a94ab4d50cdff10b8/internal/otel/config/helpers/receivers/prometheus_receiver.go#L54) + timeout: 5 * time.Minute, + wait: 1 * time.Second, + validations: &metricValidations{ + expectedLabelKeys: []string{"service_name", "service_instance_id"}, + expectedMetricName: "otelcol_receiver_accepted_metric_points", + disallowedMetricName: "server.memory_heap_size", + }, + }, + "consulPeriodicRefreshUpdateConfig": { + refresh: &modifyTelemetryConfigBody{ + Filters: []string{"consul.state"}, + Labels: map[string]string{"new_label": "testLabel"}, + }, + recordsPath: recordsPathConsul, + // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) + timeout: 3 * time.Minute, + wait: 30 * time.Second, + validations: &metricValidations{ + expectedLabelKeys: []string{"node_id", "node_name", "new_label"}, + expectedMetricName: "consul.state.services", + disallowedMetricName: "consul.fsm", + }, + }, + "consulPeriodicRefreshDisabled": { + refresh: &modifyTelemetryConfigBody{ + Filters: []string{"consul.state"}, + Labels: map[string]string{"new_label": "testLabel"}, + Disabled: true, + }, + recordsPath: recordsPathConsul, + // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) + timeout: 3 * time.Minute, + wait: 30 * time.Second, + validations: &metricValidations{ + disabled: true, + }, + }, + } { + t.Run(name, func(t *testing.T) { + // For a refresh test, we force a telemetry config update before validating metrics using fakeserver's /telemetry_config_modify endpoint. + if tc.refresh != nil { + refreshTime := time.Now() + err := fsClient.modifyTelemetryConfig(tc.refresh) + require.NoError(t, err) + // Add 10 seconds (2 * periodic refresh interval in fakeserver) to allow a periodic refresh from Consul side to take place. + tc.refreshTime = refreshTime.Add(10 * time.Second).UnixNano() + } + + // Validate metrics are correct using fakeserver's /records endpoint to retrieve metric exports that occured from Consul/Collector to fakeserver. + // We use retry as we wait for Consul or the Collector to export metrics. This is the best we can do to avoid flakiness. + retry.RunWith(&retry.Timer{Timeout: tc.timeout, Wait: tc.wait}, t, func(r *retry.R) { + records, err := fsClient.getRecordsForPath(tc.recordsPath, tc.refreshTime) + require.NoError(r, err) + validateMetrics(r, records, tc.validations, tc.refreshTime) + }) + }) } - - // Perform the request - resp, err := client.Do(req) - if err != nil { - fmt.Println("Error sending request:", err) - return errors.New("error making validation request") - } - if resp.StatusCode == http.StatusExpectationFailed { - // Read the response body - body, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error reading response:", err) - return errors.New("error reading body") - } - var message errMsg - err = json.Unmarshal(body, &message) - if err != nil { - fmt.Println("Error parsing response:", err) - return errors.New("error parsing body") - } - - return fmt.Errorf("Failed validation: %s", message) - } else if resp.StatusCode != http.StatusOK { - return errors.New("unexpected status code response from failure") - } - - return nil - -} - -type errMsg struct { - Error string `json:"error"` } diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml index 7278557cdb..78547d5118 100644 --- a/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml +++ b/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml @@ -19,7 +19,7 @@ spec: containers: - name: fake-server # TODO: move this to a hashicorp mirror - image: docker.io/chaapppie/fakeserver:latest + image: docker.io/achooo/fakeserver:latest ports: - containerPort: 443 name: https From 7079ad53491caa6c64a2011b4254a92cf4f4d7c3 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 30 Oct 2023 15:38:31 -0400 Subject: [PATCH 451/592] [NET-6138] security: Bump google.golang.org/grpc to 1.56.3 (CVE-2023-44487) (#3139) Bump google.golang.org/grpc to 1.56.3 This resolves [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). Also bump `consul-server-connection-manager` to latest to align with that library's matching gRPC upgrade. --- .changelog/3139.txt | 4 +++ acceptance/go.mod | 2 +- acceptance/go.sum | 4 +-- charts/go.sum | 0 cli/go.mod | 18 +++++++------ cli/go.sum | 36 ++++++++++++++----------- control-plane/cni/go.mod | 8 +++--- control-plane/cni/go.sum | 16 +++++------ control-plane/go.mod | 12 +++++---- control-plane/go.sum | 24 ++++++++++------- hack/aws-acceptance-test-cleanup/go.mod | 2 +- hack/aws-acceptance-test-cleanup/go.sum | 4 +-- hack/copy-crds-to-chart/go.sum | 0 hack/helm-reference-gen/go.mod | 6 ++--- hack/helm-reference-gen/go.sum | 12 ++++----- 15 files changed, 82 insertions(+), 66 deletions(-) create mode 100644 .changelog/3139.txt create mode 100644 charts/go.sum create mode 100644 hack/copy-crds-to-chart/go.sum diff --git a/.changelog/3139.txt b/.changelog/3139.txt new file mode 100644 index 0000000000..416d8e559b --- /dev/null +++ b/.changelog/3139.txt @@ -0,0 +1,4 @@ +```release-note:security +Upgrade `google.golang.org/grpc` to 1.56.3. +This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). +``` diff --git a/acceptance/go.mod b/acceptance/go.mod index 13b68936bf..b34fcf246c 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -136,7 +136,7 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/grpc v1.56.2 // indirect + google.golang.org/grpc v1.56.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index ebf575b207..219f7a08ec 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -1145,8 +1145,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 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= diff --git a/charts/go.sum b/charts/go.sum new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/go.mod b/cli/go.mod index eb678f27e2..ea3c122e2e 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -31,7 +31,7 @@ require ( require go.opentelemetry.io/proto/otlp v0.19.0 // indirect require ( - cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go/compute v1.19.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -55,7 +55,7 @@ require ( github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect - github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 // indirect + github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect github.com/containerd/containerd v1.6.6 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -67,9 +67,9 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/emicklei/go-restful/v3 v3.8.0 // indirect - github.com/envoyproxy/go-control-plane v0.11.0 // indirect + github.com/envoyproxy/go-control-plane v0.11.1 // indirect github.com/envoyproxy/go-control-plane/xdsmatcher v0.0.0-20230524161521-aaaacbfbe53e // indirect - github.com/envoyproxy/protoc-gen-validate v0.10.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -148,7 +148,7 @@ require ( 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.12.2 // indirect - github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/rubenv/sql-migrate v1.1.1 // indirect @@ -170,14 +170,16 @@ require ( golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.55.0 // indirect + google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/grpc v1.56.3 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/cli/go.sum b/cli/go.sum index f3423c42e8..23eba93b3a 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -24,8 +24,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -148,8 +148,8 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 h1:58f1tJ1ra+zFINPlwLWvQsR9CzAKt2e+EWV2yX9oXQ4= -github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +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/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= @@ -201,13 +201,13 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.11.0 h1:jtLewhRR2vMRNnq2ZZUoCjUlgut+Y0+sDDWPOfwOi1o= -github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= +github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= +github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= 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 v0.10.0 h1:oIfnZFdC0YhpNNEX+SuIqko4cqqVZeN9IGTrhZje83Y= -github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.0.1 h1:kt9FtLiooDc0vbwTLhdg3dyNX1K9Qwa1EK9LcD4jVUQ= +github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -711,8 +711,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: 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.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 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= @@ -988,8 +988,8 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 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= @@ -1250,8 +1250,12 @@ google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaE google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e h1:AZX1ra8YbFMSb7+1pI8S9v4rrgRR7jU1FmuFSSjTVcQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1275,8 +1279,8 @@ google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 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= diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index 27b016cad6..9c354dfe2e 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -4,7 +4,7 @@ require ( github.com/containernetworking/cni v1.1.1 github.com/containernetworking/plugins v1.1.1 github.com/hashicorp/consul/sdk v0.13.1 - github.com/hashicorp/go-hclog v1.2.1 + github.com/hashicorp/go-hclog v1.2.2 github.com/stretchr/testify v1.7.2 k8s.io/api v0.22.2 k8s.io/apimachinery v0.22.2 @@ -17,8 +17,8 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/go-logr/logr v0.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.7 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.12 // indirect @@ -37,7 +37,7 @@ require ( golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/protobuf v1.30.0 // 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 diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index bf1b21bebe..c08ccadbde 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -99,8 +99,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= @@ -109,8 +110,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -135,8 +136,8 @@ github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmN github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= -github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= +github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -395,7 +396,6 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f 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= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -452,8 +452,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/control-plane/go.mod b/control-plane/go.mod index 75cb34307b..6671f553b8 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -17,7 +17,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed - github.com/hashicorp/consul-server-connection-manager v0.1.4 + github.com/hashicorp/consul-server-connection-manager v0.1.6 github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a github.com/hashicorp/consul/proto-public v0.4.1 github.com/hashicorp/consul/sdk v0.14.1 @@ -42,7 +42,7 @@ require ( golang.org/x/text v0.13.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 - google.golang.org/grpc v1.55.0 + google.golang.org/grpc v1.56.3 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 @@ -55,7 +55,7 @@ require ( ) require ( - cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go/compute v1.19.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/azure-sdk-for-go v44.0.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -158,14 +158,16 @@ require ( golang.org/x/crypto v0.14.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 6e8cb3c55c..e8929ad883 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -20,8 +20,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -261,8 +261,8 @@ github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/ github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed h1:eM9tGgSqAZbm4Ndkp35Dg8YROT0dNH3ghTYu5pcUIAc= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= -github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= -github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= +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.10.1-0.20231011171434-ca1a755f0c5a h1:+cjavDME42W6nzw+xyCQ+eczqTFA+Qk4SLl7BHZ2dxI= github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a/go.mod h1:+pNEP6hQgkrBLjQlYLI13/tyyb1GK3MGVw1PC/IHk9M= github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58 h1:3VHvqLs2zTa9YWMsE4A9IricAZB6rAcXVe8e+pb5slw= @@ -663,8 +663,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 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= @@ -876,8 +876,12 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e h1:AZX1ra8YbFMSb7+1pI8S9v4rrgRR7jU1FmuFSSjTVcQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -891,8 +895,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 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= diff --git a/hack/aws-acceptance-test-cleanup/go.mod b/hack/aws-acceptance-test-cleanup/go.mod index ac4f7b0d1a..27de04bc87 100644 --- a/hack/aws-acceptance-test-cleanup/go.mod +++ b/hack/aws-acceptance-test-cleanup/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/aws/aws-sdk-go v1.38.63 - github.com/cenkalti/backoff/v4 v4.1.1 + github.com/cenkalti/backoff/v4 v4.1.3 ) require ( diff --git a/hack/aws-acceptance-test-cleanup/go.sum b/hack/aws-acceptance-test-cleanup/go.sum index 10fa5f6fe0..6959ad24ed 100644 --- a/hack/aws-acceptance-test-cleanup/go.sum +++ b/hack/aws-acceptance-test-cleanup/go.sum @@ -1,7 +1,7 @@ github.com/aws/aws-sdk-go v1.38.63 h1:BqPxe0sujTRTbir6OWj0f1VmeJcAIv7ZhTCAhaU1zmE= github.com/aws/aws-sdk-go v1.38.63/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/hack/copy-crds-to-chart/go.sum b/hack/copy-crds-to-chart/go.sum new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hack/helm-reference-gen/go.mod b/hack/helm-reference-gen/go.mod index 36e1ff3a8d..165222c84e 100644 --- a/hack/helm-reference-gen/go.mod +++ b/hack/helm-reference-gen/go.mod @@ -3,11 +3,11 @@ module github.com/hashicorp/consul-k8s/hack/helm-reference-gen go 1.20 require ( - github.com/stretchr/testify v1.6.1 - gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 + github.com/stretchr/testify v1.7.2 + gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect ) diff --git a/hack/helm-reference-gen/go.sum b/hack/helm-reference-gen/go.sum index 23a1485aa8..47d6d021dd 100644 --- a/hack/helm-reference-gen/go.sum +++ b/hack/helm-reference-gen/go.sum @@ -1,12 +1,12 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= From 6024cec32773bd3bf3d2dff40255e19a9d6e35da Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 31 Oct 2023 10:39:50 -0400 Subject: [PATCH 452/592] Update kubernetes to 1.28.x (#3138) * Update kind to 1.28.0 --- .changelog/3138.txt | 3 +++ .github/workflows/pr.yml | 2 +- README.md | 2 +- acceptance/ci-inputs/kind-inputs.yaml | 2 +- charts/consul/test/terraform/gke/main.tf | 11 +++++++---- charts/consul/test/terraform/gke/outputs.tf | 4 ++++ 6 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 .changelog/3138.txt diff --git a/.changelog/3138.txt b/.changelog/3138.txt new file mode 100644 index 0000000000..2eefd6b616 --- /dev/null +++ b/.changelog/3138.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: Kubernetes v1.28 is now supported. Minimum tested version of Kubernetes is now v1.25. +``` diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 38c4c368ae..2959554e21 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,6 +19,6 @@ jobs: with: workflow: test.yml repo: hashicorp/consul-k8s-workflows - ref: mw/1-18-parallel-tproxy + ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/README.md b/README.md index a52b2bcbf7..52e909741b 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). The following pre-requisites must be met before installing Consul on Kubernetes. - * **Kubernetes 1.24.x - 1.27.x** - This represents the earliest versions of Kubernetes tested. + * **Kubernetes 1.25.x - 1.28.x** - This represents the earliest versions of Kubernetes tested. It is possible that this chart works with earlier versions, but it is untested. * Helm install diff --git a/acceptance/ci-inputs/kind-inputs.yaml b/acceptance/ci-inputs/kind-inputs.yaml index ba21d2cdaf..9b047bdac6 100644 --- a/acceptance/ci-inputs/kind-inputs.yaml +++ b/acceptance/ci-inputs/kind-inputs.yaml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: MPL-2.0 kindVersion: v0.19.0 -kindNodeImage: kindest/node:v1.27.1 +kindNodeImage: kindest/node:v1.28.0@sha256:dad5a6238c5e41d7cac405fae3b5eda2ad1de6f1190fa8bfc64ff5bb86173213 kubectlVersion: v1.27.1 diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index 34bb07906f..800aca5246 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { google = { - version = "~> 4.58.0" + version = "~> 5.3.0" } } } @@ -21,7 +21,7 @@ resource "random_id" "suffix" { data "google_container_engine_versions" "main" { location = var.zone - version_prefix = "1.25.9" + version_prefix = "1.27." } # We assume that the subnets are already created to save time. @@ -37,14 +37,17 @@ resource "google_container_cluster" "cluster" { project = var.project initial_node_count = 3 location = var.zone + # 2023-10-30 - There is a bug with the terraform provider where lastest_master_version is not being returned by the + # api. Hardcode GKE version for now. min_master_version = data.google_container_engine_versions.main.latest_master_version node_version = data.google_container_engine_versions.main.latest_master_version node_config { tags = ["consul-k8s-${random_id.suffix[count.index].dec}"] machine_type = "e2-standard-8" } - subnetwork = data.google_compute_subnetwork.subnet.name - resource_labels = var.labels + subnetwork = data.google_compute_subnetwork.subnet.name + resource_labels = var.labels + deletion_protection = false } resource "google_compute_firewall" "firewall-rules" { diff --git a/charts/consul/test/terraform/gke/outputs.tf b/charts/consul/test/terraform/gke/outputs.tf index a0ffac907f..b1c1343e9e 100644 --- a/charts/consul/test/terraform/gke/outputs.tf +++ b/charts/consul/test/terraform/gke/outputs.tf @@ -12,3 +12,7 @@ output "cluster_names" { output "kubeconfigs" { value = [for cl in google_container_cluster.cluster : format("$HOME/.kube/%s", cl.name)] } + +output "versions" { + value = data.google_container_engine_versions.main +} From fbf09e66c71cb347d6239a6cba8eb8bacaeb6184 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 31 Oct 2023 13:14:57 -0400 Subject: [PATCH 453/592] Update chart version (#3152) --- charts/consul/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index c33e0f4dac..f899b6309d 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -5,7 +5,7 @@ apiVersion: v2 name: consul version: 1.4.0-dev appVersion: 1.18-dev -kubeVersion: ">=1.22.0-0" +kubeVersion: ">=1.25.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io icon: https://raw.githubusercontent.com/hashicorp/consul-k8s/main/assets/icon.png From 2b7e1bba5898ce749de338e1b26fff14f435221f Mon Sep 17 00:00:00 2001 From: David Yu Date: Wed, 1 Nov 2023 06:17:44 -0700 Subject: [PATCH 454/592] main: revert k8s version to 1.22.x (#3153) Revert "Update chart version (#3152)" This reverts commit fbf09e66c71cb347d6239a6cba8eb8bacaeb6184. --- charts/consul/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index f899b6309d..c33e0f4dac 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -5,7 +5,7 @@ apiVersion: v2 name: consul version: 1.4.0-dev appVersion: 1.18-dev -kubeVersion: ">=1.25.0-0" +kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io icon: https://raw.githubusercontent.com/hashicorp/consul-k8s/main/assets/icon.png From c1ed354b334bf39958d08b4411bd23cb95400644 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 1 Nov 2023 11:23:51 -0400 Subject: [PATCH 455/592] Add Acceptance Test for Cluster Peered Terminating Gateway (#3114) * Add terminating gateway and external server to peering test Add external server and terminating gateway to cluster peered acceptance test * Export terminating gateway common functions Write the new terminating gateway role * Move other helper functions into the common file Add service defaults and destination Update terminating gateway section with proper config Change the address we are trying to hit Remove own external-server WIP * Move RegisterExternalService to helpers Register external service for terminating gateway Remove external static-server case Remove tgw.yaml * Add external services to exported services * Remove the old test output * Use HTTP instead of HTTPS for connection * Clean up tests * Remove test.sh * Remove ip hostname * Add a comment to RegisterExternalService * Log the name of the service created in RegisterExternalService * Apply suggestions from code review Co-authored-by: Nathan Coleman * Move `terminatinggateway` to the correct import block --------- Co-authored-by: Nathan Coleman --- acceptance/framework/helpers/helpers.go | 35 ++++++++ .../crd-peers/external/kustomization.yaml | 9 ++ .../cases/crd-peers/external/patch.yaml | 15 ++++ .../tests/peering/peering_connect_test.go | 85 +++++++++++++++++-- .../tests/terminating-gateway/common.go | 50 ++++++++++- .../terminating_gateway_destinations_test.go | 61 ++----------- .../terminating_gateway_namespaces_test.go | 16 ++-- .../terminating_gateway_test.go | 41 +-------- 8 files changed, 206 insertions(+), 106 deletions(-) create mode 100644 acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml diff --git a/acceptance/framework/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index f8b1d2f15d..6d2641ecd3 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -150,3 +150,38 @@ func MergeMaps(a, b map[string]string) { a[k] = v } } + +// RegisterExternalService registers an external service to a virtual node in Consul for testing purposes. +// This function takes a testing.T object, a Consul client, service namespace, service name, address, and port as +// parameters. It registers the service with Consul, and if a namespace is provided, it also creates the namespace +// in Consul. It uses the provided testing.T object to log registration details and verify the registration process. +// If the registration fails, the test calling the function will fail. +func RegisterExternalService(t *testing.T, consulClient *api.Client, namespace, name, address string, port int) { + t.Helper() + + service := &api.AgentService{ + ID: name, + Service: name, + Port: port, + } + + if namespace != "" { + address = fmt.Sprintf("%s.%s", name, namespace) + service.Namespace = namespace + + logger.Logf(t, "creating the %s namespace in Consul", namespace) + _, _, err := consulClient.Namespaces().Create(&api.Namespace{ + Name: namespace, + }, nil) + require.NoError(t, err) + } + + logger.Log(t, "registering the external service %s", name) + _, err := consulClient.Catalog().Register(&api.CatalogRegistration{ + Node: "external", + Address: address, + NodeMeta: map[string]string{"external-node": "true", "external-probe": "true"}, + Service: service, + }, nil) + require.NoError(t, err) +} diff --git a/acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml new file mode 100644 index 0000000000..f3d0bca3ce --- /dev/null +++ b/acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml @@ -0,0 +1,9 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../../bases/exportedservices-default +patches: +- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml new file mode 100644 index 0000000000..4512603bcd --- /dev/null +++ b/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ExportedServices +metadata: + name: default +spec: + services: + - name: static-server + consumers: + - peer: client + - name: static-server-hostname + consumers: + - peer: client diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index d92ab59990..60d038d5b8 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + terminatinggateway "github.com/hashicorp/consul-k8s/acceptance/tests/terminating-gateway" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-version" @@ -22,7 +23,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Test that Connect works in installations for X-Peers networking. +// TestPeering_Connect validates that service mesh works properly across two peered clusters. +// It deploys a static client in one cluster and a static server in another and checks that requests from the client +// can reach the server. +// It also deploys a static server pod that is not connected to the mesh, but added as a +// destination for a terminating gateway. It checks that requests from the client can reach this server through the +// terminating gateway via the mesh gateways. func TestPeering_Connect(t *testing.T) { env := suite.Environment() cfg := suite.Config() @@ -35,6 +41,7 @@ func TestPeering_Connect(t *testing.T) { const staticServerPeer = "server" const staticClientPeer = "client" + cases := []struct { name string ACLsEnabled bool @@ -72,7 +79,10 @@ func TestPeering_Connect(t *testing.T) { } staticServerPeerHelmValues := map[string]string{ - "global.datacenter": staticServerPeer, + "global.datacenter": staticServerPeer, + "terminatingGateways.enabled": "true", + "terminatingGateways.gateways[0].name": "terminating-gateway", + "terminatingGateways.gateways[0].replicas": "1", } if !cfg.UseKind { @@ -185,20 +195,19 @@ func TestPeering_Connect(t *testing.T) { Namespace: staticClientNamespace, } - logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) + logger.Logf(t, "creating namespace %s in server peer", staticServerNamespace) k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) - logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) + logger.Logf(t, "creating namespace %s in client peer", staticClientNamespace) k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) }) - // Create a ProxyDefaults resource to configure services to use the mesh - // gateways. + // Create a ProxyDefaults resource to configure services to use the mesh gateways. logger.Log(t, "creating proxy-defaults config") kustomizeDir := "../fixtures/bases/mesh-gateway" @@ -287,6 +296,70 @@ func TestPeering_Connect(t *testing.T) { k8s.CheckStaticServerConnectionSuccessful(t, staticClientOpts, staticClientName, "http://localhost:1234") } + // Check that requests can reach the external static-server from the static client cluster. + if cfg.EnableTransparentProxy { + const ( + externalServerK8sNamespace = "external" + externalServerServiceName = "static-server" + externalServerHostnameID = "static-server-hostname" + terminatingGatewayRules = `service_prefix "static-server" {policy = "write"}` + ) + + // Create the namespace for the "external" static server. + externalServerOpts := &terratestk8s.KubectlOptions{ + ContextName: staticServerOpts.ContextName, + ConfigPath: staticServerOpts.ConfigPath, + Namespace: externalServerK8sNamespace, + } + logger.Logf(t, "creating namespace %s in server peer", externalServerK8sNamespace) + k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", externalServerK8sNamespace) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", externalServerK8sNamespace) + }) + + // Create the external server in the server Kubernetes cluster, outside the mesh in the "external" namespace + logger.Log(t, "creating static-server deployment in server peer outside of mesh") + k8s.DeployKustomize(t, externalServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + + // Prevent dialing the server directly through the sidecar. + terminatinggateway.CreateMeshConfigEntry(t, staticServerPeerClient, "") + terminatinggateway.CreateMeshConfigEntry(t, staticClientPeerClient, "") + + // Create the config entry for the terminating gateway + terminatinggateway.CreateTerminatingGatewayConfigEntry(t, staticServerPeerClient, "", "", externalServerHostnameID) + if c.ACLsEnabled { + // Allow the terminating gateway write access to services prefixed with "static-server". + terminatinggateway.UpdateTerminatingGatewayRole(t, staticServerPeerClient, terminatingGatewayRules) + } + + // This is the URL that the static-client will use to dial the external static server in the server peer. + externalServerHostnameURL := fmt.Sprintf("http://%s.virtual.%s.consul", externalServerHostnameID, staticServerPeer) + + // Register the external service. + terminatinggateway.CreateServiceDefaultDestination(t, staticServerPeerClient, "", externalServerHostnameID, "http", 80, fmt.Sprintf("%s.%s", externalServerServiceName, externalServerK8sNamespace)) + // (t-eckert) this shouldn't be required but currently is with HTTP services. It works around a bug. + helpers.RegisterExternalService(t, staticServerPeerClient, "", externalServerHostnameID, fmt.Sprintf("%s.%s", externalServerServiceName, externalServerK8sNamespace), 80) + + // Export the external service to the client peer. + logger.Log(t, "creating exported external services") + k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/external") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/external") + }) + + // If ACLs are enabled, test that deny intentions prevent connections. + if c.ACLsEnabled { + logger.Log(t, "testing intentions prevent connections through the terminating gateway") + k8s.CheckStaticServerConnectionFailing(t, staticClientOpts, staticClientName, externalServerHostnameURL) + + logger.Log(t, "adding intentions to allow traffic from client ==> server") + terminatinggateway.AddIntention(t, staticServerPeerClient, staticClientPeer, "", staticClientName, "", externalServerHostnameID) + } + + // Test that we can make a call to the terminating gateway. + logger.Log(t, "trying calls to terminating gateway") + k8s.CheckStaticServerConnectionSuccessful(t, staticClientOpts, staticClientName, externalServerHostnameURL) + } }) } } diff --git a/acceptance/tests/terminating-gateway/common.go b/acceptance/tests/terminating-gateway/common.go index 908e6cbec1..65dd7545a8 100644 --- a/acceptance/tests/terminating-gateway/common.go +++ b/acceptance/tests/terminating-gateway/common.go @@ -19,7 +19,7 @@ const ( staticServerLocalAddress = "http://localhost:1234" ) -func addIntention(t *testing.T, consulClient *api.Client, sourceNS, sourceService, destinationNS, destinationsService string) { +func AddIntention(t *testing.T, consulClient *api.Client, sourcePeer, sourceNS, sourceService, destinationNS, destinationsService string) { t.Helper() logger.Log(t, fmt.Sprintf("creating %s => %s intention", sourceService, destinationsService)) @@ -32,13 +32,14 @@ func addIntention(t *testing.T, consulClient *api.Client, sourceNS, sourceServic Name: sourceService, Namespace: sourceNS, Action: api.IntentionActionAllow, + Peer: sourcePeer, }, }, }, nil) require.NoError(t, err) } -func createTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, gwNamespace, serviceNamespace string, serviceNames ...string) { +func CreateTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, gwNamespace, serviceNamespace string, serviceNames ...string) { t.Helper() logger.Log(t, "creating config entry") @@ -69,7 +70,7 @@ func createTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, require.True(t, created, "failed to create config entry") } -func updateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules string) { +func UpdateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules string) { t.Helper() logger.Log(t, "creating a write policy for the static-server") @@ -97,3 +98,46 @@ func updateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules _, _, err = consulClient.ACL().RoleUpdate(termGwRole, nil) require.NoError(t, err) } + +func CreateServiceDefaultDestination(t *testing.T, consulClient *api.Client, serviceNamespace string, name string, protocol string, port int, addresses ...string) { + t.Helper() + + logger.Log(t, "creating config entry") + + if serviceNamespace != "" { + logger.Logf(t, "creating the %s namespace in Consul", serviceNamespace) + _, _, err := consulClient.Namespaces().Create(&api.Namespace{ + Name: serviceNamespace, + }, nil) + require.NoError(t, err) + } + + configEntry := &api.ServiceConfigEntry{ + Kind: api.ServiceDefaults, + Name: name, + Namespace: serviceNamespace, + Protocol: protocol, + Destination: &api.DestinationConfig{ + Addresses: addresses, + Port: port, + }, + } + + created, _, err := consulClient.ConfigEntries().Set(configEntry, nil) + require.NoError(t, err) + require.True(t, created, "failed to create config entry") +} + +func CreateMeshConfigEntry(t *testing.T, consulClient *api.Client, namespace string) { + t.Helper() + + logger.Log(t, "creating mesh config entry to enable MeshDestinationOnly") + created, _, err := consulClient.ConfigEntries().Set(&api.MeshConfigEntry{ + Namespace: namespace, + TransparentProxy: api.TransparentProxyMeshConfig{ + MeshDestinationsOnly: true, + }, + }, nil) + require.NoError(t, err) + require.True(t, created, "failed to create config entry") +} diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go index 62485c6e44..67097b7648 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" "github.com/hashicorp/go-version" "github.com/stretchr/testify/require" ) @@ -79,15 +78,15 @@ func TestTerminatingGatewayDestinations(t *testing.T) { // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - updateTerminatingGatewayRole(t, consulClient, terminatingGatewayRules) + UpdateTerminatingGatewayRole(t, consulClient, terminatingGatewayRules) } // Since we are using the transparent kube DNS, disable the ability // of the service to dial the server directly through the sidecar - createMeshConfigEntry(t, consulClient, "") + CreateMeshConfigEntry(t, consulClient, "") // Create the config entry for the terminating gateway. - createTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerHostnameID, staticServerIPID) + CreateTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerHostnameID, staticServerIPID) // Deploy the static client logger.Log(t, "deploying static client") @@ -102,8 +101,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { // Create the service default declaring the external service (aka Destination) logger.Log(t, "creating tcp-based service defaults") - createServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "", 443, staticServerServiceName) - createServiceDefaultDestination(t, consulClient, "", staticServerIPID, "", 80, staticServerIP) + CreateServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "", 443, staticServerServiceName) + CreateServiceDefaultDestination(t, consulClient, "", staticServerIPID, "", 80, staticServerIP) // If ACLs are enabled, test that intentions prevent connections. if c.secure { @@ -115,8 +114,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ctx.KubectlOptions(t), staticClientName, "-k", staticServerHostnameURL) logger.Log(t, "adding intentions to allow traffic from client ==> server") - addIntention(t, consulClient, "", staticClientName, "", staticServerHostnameID) - addIntention(t, consulClient, "", staticClientName, "", staticServerIPID) + AddIntention(t, consulClient, "", "", staticClientName, "", staticServerHostnameID) + AddIntention(t, consulClient, "", "", staticClientName, "", staticServerIPID) } // Test that we can make a call to the terminating gateway. @@ -132,8 +131,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { logger.Log(t, "updating service defaults to try other scenarios") // You can't use TLS w/ protocol set to anything L7; Envoy can't snoop the traffic when the client encrypts it - createServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "http", 80, staticServerServiceName) - createServiceDefaultDestination(t, consulClient, "", staticServerIPID, "http", 80, staticServerIP) + CreateServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "http", 80, staticServerServiceName) + CreateServiceDefaultDestination(t, consulClient, "", staticServerIPID, "http", 80, staticServerIP) logger.Log(t, "trying calls to terminating gateway") k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), staticClientName, staticServerIPURL) @@ -141,45 +140,3 @@ func TestTerminatingGatewayDestinations(t *testing.T) { }) } } -func createServiceDefaultDestination(t *testing.T, consulClient *api.Client, serviceNamespace string, name string, protocol string, port int, addresses ...string) { - t.Helper() - - logger.Log(t, "creating config entry") - - if serviceNamespace != "" { - logger.Logf(t, "creating the %s namespace in Consul", serviceNamespace) - _, _, err := consulClient.Namespaces().Create(&api.Namespace{ - Name: serviceNamespace, - }, nil) - require.NoError(t, err) - } - - configEntry := &api.ServiceConfigEntry{ - Kind: api.ServiceDefaults, - Name: name, - Namespace: serviceNamespace, - Protocol: protocol, - Destination: &api.DestinationConfig{ - Addresses: addresses, - Port: port, - }, - } - - created, _, err := consulClient.ConfigEntries().Set(configEntry, nil) - require.NoError(t, err) - require.True(t, created, "failed to create config entry") -} - -func createMeshConfigEntry(t *testing.T, consulClient *api.Client, namespace string) { - t.Helper() - - logger.Log(t, "creating mesh config entry to enable MeshDestinationOnly") - created, _, err := consulClient.ConfigEntries().Set(&api.MeshConfigEntry{ - Namespace: namespace, - TransparentProxy: api.TransparentProxyMeshConfig{ - MeshDestinationsOnly: true, - }, - }, nil) - require.NoError(t, err) - require.True(t, created, "failed to create config entry") -} diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go index 73250ea810..ee51a64c0d 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go @@ -77,17 +77,17 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") // Register the external service. - registerExternalService(t, consulClient, testNamespace) + helpers.RegisterExternalService(t, consulClient, testNamespace, staticServerName, staticServerName, 80) // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - updateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) + UpdateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) } // Create the config entry for the terminating gateway. - createTerminatingGatewayConfigEntry(t, consulClient, testNamespace, testNamespace, staticServerName) + CreateTerminatingGatewayConfigEntry(t, consulClient, testNamespace, testNamespace, staticServerName) // Deploy the static client. logger.Log(t, "deploying static client") @@ -102,7 +102,7 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, nsK8SOptions, staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - addIntention(t, consulClient, testNamespace, staticClientName, testNamespace, staticServerName) + AddIntention(t, consulClient, "", testNamespace, staticClientName, testNamespace, staticServerName) } // Test that we can make a call to the terminating gateway. @@ -186,17 +186,17 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { k8s.DeployKustomize(t, ns1K8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") // Register the external service - registerExternalService(t, consulClient, testNamespace) + helpers.RegisterExternalService(t, consulClient, testNamespace, staticServerName, staticServerName, 80) // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - updateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) + UpdateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) } // Create the config entry for the terminating gateway - createTerminatingGatewayConfigEntry(t, consulClient, "", testNamespace, staticServerName) + CreateTerminatingGatewayConfigEntry(t, consulClient, "", testNamespace, staticServerName) // Deploy the static client logger.Log(t, "deploying static client") @@ -211,7 +211,7 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ns2K8SOptions, staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - addIntention(t, consulClient, StaticClientNamespace, staticClientName, testNamespace, staticServerName) + AddIntention(t, consulClient, "", StaticClientNamespace, staticClientName, testNamespace, staticServerName) } // Test that we can make a call to the terminating gateway diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_test.go index 6e7e95032f..acd0232227 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_test.go @@ -12,8 +12,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" ) // Test that terminating gateways work in a default and secure installations. @@ -57,17 +55,17 @@ func TestTerminatingGateway(t *testing.T) { consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) // Register the external service - registerExternalService(t, consulClient, "") + helpers.RegisterExternalService(t, consulClient, "", staticServerName, staticServerName, 80) // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - updateTerminatingGatewayRole(t, consulClient, staticServerPolicyRules) + UpdateTerminatingGatewayRole(t, consulClient, staticServerPolicyRules) } // Create the config entry for the terminating gateway. - createTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerName) + CreateTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerName) // Deploy the static client logger.Log(t, "deploying static client") @@ -82,7 +80,7 @@ func TestTerminatingGateway(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ctx.KubectlOptions(t), staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - addIntention(t, consulClient, "", staticClientName, "", staticServerName) + AddIntention(t, consulClient, "", "", staticClientName, "", staticServerName) } // Test that we can make a call to the terminating gateway. @@ -95,34 +93,3 @@ func TestTerminatingGateway(t *testing.T) { const staticServerPolicyRules = `service "static-server" { policy = "write" }` - -func registerExternalService(t *testing.T, consulClient *api.Client, namespace string) { - t.Helper() - - address := staticServerName - service := &api.AgentService{ - ID: staticServerName, - Service: staticServerName, - Port: 80, - } - - if namespace != "" { - address = fmt.Sprintf("%s.%s", staticServerName, namespace) - service.Namespace = namespace - - logger.Logf(t, "creating the %s namespace in Consul", namespace) - _, _, err := consulClient.Namespaces().Create(&api.Namespace{ - Name: namespace, - }, nil) - require.NoError(t, err) - } - - logger.Log(t, "registering the external service") - _, err := consulClient.Catalog().Register(&api.CatalogRegistration{ - Node: "legacy_node", - Address: address, - NodeMeta: map[string]string{"external-node": "true", "external-probe": "true"}, - Service: service, - }, nil) - require.NoError(t, err) -} From e50c8e20f039f33e0261409da91870daafc28aaa Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 2 Nov 2023 12:26:04 -0400 Subject: [PATCH 456/592] fix(controller): v2 pod controller error log for missing ip (#3162) --- .changelog/3162.txt | 3 +++ .../controllers/pod/pod_controller.go | 7 +++++++ .../controllers/pod/pod_controller_test.go | 15 +++++++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 .changelog/3162.txt diff --git a/.changelog/3162.txt b/.changelog/3162.txt new file mode 100644 index 0000000000..588e39e0f9 --- /dev/null +++ b/.changelog/3162.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: remove extraneous error log in v2 pod controller when a pod is scheduled, but not yet allocated an IP. +``` \ No newline at end of file diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 3864f0e213..85b17bfbbd 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -150,6 +150,13 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.Log.Info("retrieved", "name", pod.Name, "ns", pod.Namespace) if inject.HasBeenMeshInjected(pod) { + + // It is possible the pod was scheduled but doesn't have an allocated IP yet, so safely requeue + if pod.Status.PodIP == "" { + r.Log.Info("pod does not have IP allocated; re-queueing request", "pod", req.Name, "ns", req.Namespace) + return ctrl.Result{Requeue: true}, nil + } + if err := r.writeProxyConfiguration(ctx, pod); err != nil { // We could be racing with the namespace controller. // Requeue (which includes backoff) to try again. diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index b6fb1d4d44..30372e00b5 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -1225,7 +1225,8 @@ func TestReconcileCreatePod(t *testing.T) { metrics bool telemetry bool - expErr string + requeue bool + expErr string } run := func(t *testing.T, tc testCase) { @@ -1290,7 +1291,7 @@ func TestReconcileCreatePod(t *testing.T) { } else { require.NoError(t, err) } - require.False(t, resp.Requeue) + require.Equal(t, tc.requeue, resp.Requeue) wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) @@ -1382,6 +1383,16 @@ func TestReconcileCreatePod(t *testing.T) { expectedProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), expectedDestinations: createDestinations(), }, + { + name: "pod w/o IP", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "", true, true) + pod.Status.PodIP = "" + return []runtime.Object{pod} + }, + requeue: true, + }, // TODO: make sure multi-error accumulates errors } From cd79533d5fbcfb3f7a2110ca23bd55243a9f21bd Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Tue, 7 Nov 2023 11:51:29 -0500 Subject: [PATCH 457/592] fix(controller): v2 pod controller errors for acl deletion (#3172) * fix(controller): v2 pod controller errors for acl deletion * test: fix tests for unsupported L7 TPs --- .changelog/3172.txt | 7 ++ .../meshconfig_controller_test.go | 73 ++++++++++--------- .../controllers/pod/pod_controller.go | 9 ++- .../controllers/pod/pod_controller_test.go | 8 ++ .../webhookv2/consul_dataplane_sidecar.go | 6 ++ .../consul_dataplane_sidecar_test.go | 2 +- .../subcommand/connect-init/command.go | 1 + control-plane/subcommand/mesh-init/command.go | 1 + 8 files changed, 70 insertions(+), 37 deletions(-) create mode 100644 .changelog/3172.txt diff --git a/.changelog/3172.txt b/.changelog/3172.txt new file mode 100644 index 0000000000..9e255f4278 --- /dev/null +++ b/.changelog/3172.txt @@ -0,0 +1,7 @@ +```release-note:bug +control-plane: remove extraneous error log in v2 pod controller when attempting to delete ACL tokens. +``` +``` +release-note:bug +init container: fix a bug that didn't clear ACL tokens for init container when tproxy is enabled. +``` \ No newline at end of file diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go index e95e722ea6..c7da7b3de3 100644 --- a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go +++ b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go @@ -70,13 +70,14 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { IdentityName: "source-identity", }, }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, + // TODO: enable this when L7 traffic permissions are supported + //DestinationRules: []*pbauth.DestinationRule{ + // { + // PathExact: "/hello", + // Methods: []string{"GET", "POST"}, + // PortNames: []string{"web", "admin"}, + // }, + //}, }, }, }, @@ -101,13 +102,13 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { Peer: constants.DefaultConsulPeer, }, }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, + //DestinationRules: []*pbauth.DestinationRule{ + // { + // PathExact: "/hello", + // Methods: []string{"GET", "POST"}, + // PortNames: []string{"web", "admin"}, + // }, + //}, }, }, }, @@ -216,13 +217,14 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { IdentityName: "source-identity", }, }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, + // TODO: enable this when L7 traffic permissions are supported + //DestinationRules: []*pbauth.DestinationRule{ + // { + // PathExact: "/hello", + // Methods: []string{"GET", "POST"}, + // PortNames: []string{"web", "admin"}, + // }, + //}, }, }, }, @@ -241,13 +243,13 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { Peer: constants.DefaultConsulPeer, }, }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, + //DestinationRules: []*pbauth.DestinationRule{ + // { + // PathExact: "/hello", + // Methods: []string{"GET", "POST"}, + // PortNames: []string{"web", "admin"}, + // }, + //}, }, }, }, @@ -373,13 +375,14 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { IdentityName: "source-identity", }, }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, + // TODO: enable this when L7 traffic permissions are supported + //DestinationRules: []*pbauth.DestinationRule{ + // { + // PathExact: "/hello", + // Methods: []string{"GET", "POST"}, + // PortNames: []string{"web", "admin"}, + // }, + //}, }, }, }, diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 85b17bfbbd..c4b867f8c3 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -6,6 +6,7 @@ package pod import ( "context" "encoding/json" + "errors" "fmt" "regexp" "strconv" @@ -267,6 +268,10 @@ func (r *Controller) deleteACLTokensForPod(apiClient *api.Client, pod types.Name // See discussion above about optimizing the token list query. for _, token := range tokens { tokenMeta, err := getTokenMetaFromDescription(token.Description) + // It is possible this is from another component, so continue searching + if errors.Is(err, NoMetadataErr) { + continue + } if err != nil { return fmt.Errorf("failed to parse token metadata: %s", err) } @@ -284,13 +289,15 @@ func (r *Controller) deleteACLTokensForPod(apiClient *api.Client, pod types.Name return nil } +var NoMetadataErr = fmt.Errorf("failed to extract token metadata from description") + // getTokenMetaFromDescription parses JSON metadata from token's description. func getTokenMetaFromDescription(description string) (map[string]string, error) { re := regexp.MustCompile(`.*({.+})`) matches := re.FindStringSubmatch(description) if len(matches) != 2 { - return nil, fmt.Errorf("failed to extract token metadata from description: %s", description) + return nil, NoMetadataErr } tokenMetaJSON := matches[1] diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 30372e00b5..413f9ac49a 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -1777,6 +1777,14 @@ func TestReconcileDeletePod(t *testing.T) { }, }, nil) require.NoError(t, err) + + // We create another junk token here just to make sure it doesn't interfere with cleaning up the + // previous "real" token that has metadata. + _, _, err = testClient.APIClient.ACL().Login(&api.ACLLoginParams{ + AuthMethod: test.AuthMethod, + BearerToken: test.ServiceAccountJWTToken, + }, nil) + require.NoError(t, err) } namespacedName := types.NamespacedName{ diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go index ed0983a1c8..f8a8406d23 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go @@ -112,6 +112,12 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", }, + // This entry exists to support newer versions of consul dataplane, where environment variable entries + // utilize this numbered notation to indicate individual KV pairs in a map. + { + Name: "DP_CREDENTIAL_LOGIN_META1", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }, }, VolumeMounts: []corev1.VolumeMount{ { diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go index fecef1bb5d..81b3520511 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go @@ -219,7 +219,7 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { } require.Equal(t, expectedProbe, container.ReadinessProbe) require.Nil(t, container.StartupProbe) - require.Len(t, container.Env, 6) + require.Len(t, container.Env, 7) require.Equal(t, container.Env[0].Name, "TMPDIR") require.Equal(t, container.Env[0].Value, "/consul/mesh-inject") require.Equal(t, container.Env[2].Name, "POD_NAME") diff --git a/control-plane/subcommand/connect-init/command.go b/control-plane/subcommand/connect-init/command.go index 6bb66d0948..2d245797dc 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -206,6 +206,7 @@ func (c *Command) Run(args []string) int { } if c.flagRedirectTrafficConfig != "" { + c.watcher.Stop() // Explicitly stop the watcher so that ACLs are cleaned up before we apply re-direction. err = c.applyTrafficRedirectionRules(proxyService) if err != nil { c.logger.Error("error applying traffic redirection rules", "err", err) diff --git a/control-plane/subcommand/mesh-init/command.go b/control-plane/subcommand/mesh-init/command.go index 6c21210e9b..2a6b6ccebd 100644 --- a/control-plane/subcommand/mesh-init/command.go +++ b/control-plane/subcommand/mesh-init/command.go @@ -183,6 +183,7 @@ func (c *Command) Run(args []string) int { } if c.flagRedirectTrafficConfig != "" { + c.watcher.Stop() // Explicitly stop the watcher so that ACLs are cleaned up before we apply re-direction. err := c.applyTrafficRedirectionRules(&bootstrapConfig) // BootstrapConfig is always populated non-nil from the RPC if err != nil { c.logger.Error("error applying traffic redirection rules", "err", err) From faf3bed254b08b21de9668634f2285f390c72ea1 Mon Sep 17 00:00:00 2001 From: Ronald Date: Tue, 7 Nov 2023 13:44:17 -0500 Subject: [PATCH 458/592] Release 1.2.3, 1.1.7 and 1.0.11 changelog (#3167) --- CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06934dd5cf..8f79f7fe8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +## 1.2.3 (November 2, 2023) + +SECURITY: + +* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3119](https://github.com/hashicorp/consul-k8s/issues/3119)] +* Upgrade `google.golang.org/grpc` to 1.56.3. +This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3139](https://github.com/hashicorp/consul-k8s/issues/3139)] +* Upgrade to use Go 1.20.10 and `x/net` 0.17.0. +This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) +/ [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3085](https://github.com/hashicorp/consul-k8s/issues/3085)] + +BUG FIXES: + +* api-gateway: fix issue where missing `NET_BIND_SERVICE` capability prevented api-gateway `Pod` from starting up when deployed to OpenShift [[GH-3070](https://github.com/hashicorp/consul-k8s/issues/3070)] +* control-plane: only alert on valid errors, not timeouts in gateway [[GH-3128](https://github.com/hashicorp/consul-k8s/issues/3128)] +* crd: fix misspelling of preparedQuery field in ControlPlaneRequestLimit CRD [[GH-3001](https://github.com/hashicorp/consul-k8s/issues/3001)] + +## 1.1.7 (November 2, 2023) + +SECURITY: + +* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3120](https://github.com/hashicorp/consul-k8s/issues/3120)] +* Upgrade `google.golang.org/grpc` to 1.56.3. +This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3139](https://github.com/hashicorp/consul-k8s/issues/3139)] +* Upgrade to use Go 1.20.10 and `x/net` 0.17.0. +This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) +/ [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3085](https://github.com/hashicorp/consul-k8s/issues/3085)] + +## 1.0.11 (November 2, 2023) + +SECURITY: + +* Update Envoy version to 1.24.12 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3121](https://github.com/hashicorp/consul-k8s/issues/3121)] +* Upgrade `google.golang.org/grpc` to 1.56.3. +This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3139](https://github.com/hashicorp/consul-k8s/issues/3139)] +* Upgrade to use Go 1.20.10 and `x/net` 0.17.0. +This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) +/ [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3085](https://github.com/hashicorp/consul-k8s/issues/3085)] + ## 1.2.2 (September 21, 2023) SECURITY: From 173910a5bfe9f235e5eab1071005843baa6fe911 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 7 Nov 2023 17:16:43 -0500 Subject: [PATCH 459/592] NET-5392 Replace dev dependencies w/ latest release tags (#3181) * NET-5392 Replace dev dependencies w/ latest release tags * go mod tidy * Adjust api-gateway logic based on changes in consul/api * Stop setting removed field EnvoyExtensions in test The field was intentionally removed in https://github.com/hashicorp/consul/pull/19186 * gofmt --- control-plane/api-gateway/common/diff.go | 6 +- .../api-gateway/common/translation.go | 18 ++++-- .../api-gateway/common/translation_test.go | 8 +-- .../v2beta1/proxy_configuration_types_test.go | 57 ------------------- control-plane/go.mod | 14 +---- control-plane/go.sum | 12 ++-- 6 files changed, 30 insertions(+), 85 deletions(-) diff --git a/control-plane/api-gateway/common/diff.go b/control-plane/api-gateway/common/diff.go index df4f2cb4f0..7db86807b7 100644 --- a/control-plane/api-gateway/common/diff.go +++ b/control-plane/api-gateway/common/diff.go @@ -274,8 +274,10 @@ func (e entryComparator) urlRewritesEqual(a, b api.URLRewrite) bool { } func (e entryComparator) retryFiltersEqual(a, b api.RetryFilter) bool { - return BothNilOrEqual(a.NumRetries, b.NumRetries) && BothNilOrEqual(a.RetryOnConnectFailure, b.RetryOnConnectFailure) && - slices.Equal(a.RetryOn, b.RetryOn) && slices.Equal(a.RetryOnStatusCodes, b.RetryOnStatusCodes) + return a.NumRetries == b.NumRetries && + a.RetryOnConnectFailure == b.RetryOnConnectFailure && + slices.Equal(a.RetryOn, b.RetryOn) && + slices.Equal(a.RetryOnStatusCodes, b.RetryOnStatusCodes) } func (e entryComparator) timeoutFiltersEqual(a, b api.TimeoutFilter) bool { diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index ed949b1ade..5161e6b033 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -149,12 +149,20 @@ func ToContainerPort(portNumber gwv1beta1.PortNumber, mapPrivilegedContainerPort } func (t ResourceTranslator) translateRouteRetryFilter(routeRetryFilter *v1alpha1.RouteRetryFilter) *api.RetryFilter { - return &api.RetryFilter{ - NumRetries: routeRetryFilter.Spec.NumRetries, - RetryOn: routeRetryFilter.Spec.RetryOn, - RetryOnStatusCodes: routeRetryFilter.Spec.RetryOnStatusCodes, - RetryOnConnectFailure: routeRetryFilter.Spec.RetryOnConnectFailure, + filter := &api.RetryFilter{ + RetryOn: routeRetryFilter.Spec.RetryOn, + RetryOnStatusCodes: routeRetryFilter.Spec.RetryOnStatusCodes, } + + if routeRetryFilter.Spec.NumRetries != nil { + filter.NumRetries = *routeRetryFilter.Spec.NumRetries + } + + if routeRetryFilter.Spec.RetryOnConnectFailure != nil { + filter.RetryOnConnectFailure = *routeRetryFilter.Spec.RetryOnConnectFailure + } + + return filter } func (t ResourceTranslator) translateRouteTimeoutFilter(routeTimeoutFilter *v1alpha1.RouteTimeoutFilter) *api.TimeoutFilter { diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index 8f02f6eca1..4331e2b77a 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -1428,10 +1428,10 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Headers: []api.HTTPHeaderFilter{}, URLRewrite: nil, RetryFilter: &api.RetryFilter{ - NumRetries: pointer.Uint32(3), + NumRetries: 3, RetryOn: []string{"cancelled"}, RetryOnStatusCodes: []uint32{500, 502}, - RetryOnConnectFailure: pointer.Bool(false), + RetryOnConnectFailure: false, }, TimeoutFilter: &api.TimeoutFilter{ RequestTimeout: time.Duration(10 * time.Nanosecond), @@ -1484,10 +1484,10 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{}, RetryFilter: &api.RetryFilter{ - NumRetries: pointer.Uint32(3), + NumRetries: 3, RetryOn: []string{"cancelled"}, RetryOnStatusCodes: []uint32{500, 502}, - RetryOnConnectFailure: pointer.Bool(false), + RetryOnConnectFailure: false, }, }, ResponseFilters: api.HTTPResponseFilters{ diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go b/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go index 5e432175f4..67ad339697 100644 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go +++ b/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -109,20 +108,6 @@ func TestProxyConfiguration_MatchesConsul(t *testing.T) { JsonFormat: "jsonFormat", TextFormat: "text format.", }, - EnvoyExtensions: []*pbmesh.EnvoyExtension{ - { - Name: "extension-1", - Required: true, - Arguments: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "field-1": {}, - "field-2": {}, - }, - }, - ConsulVersion: "1.22.3-beta1", - EnvoyVersion: "1.33.4", - }, - }, PublicListenerJson: "publicListenerJson{}", ListenerTracingJson: "listenerTracingJson{}", LocalClusterJson: "localClusterJson{}", @@ -196,20 +181,6 @@ func TestProxyConfiguration_MatchesConsul(t *testing.T) { JsonFormat: "jsonFormat", TextFormat: "text format.", }, - EnvoyExtensions: []*pbmesh.EnvoyExtension{ - { - Name: "extension-1", - Required: true, - Arguments: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "field-1": {}, - "field-2": {}, - }, - }, - ConsulVersion: "1.22.3-beta1", - EnvoyVersion: "1.33.4", - }, - }, PublicListenerJson: "publicListenerJson{}", ListenerTracingJson: "listenerTracingJson{}", LocalClusterJson: "localClusterJson{}", @@ -347,20 +318,6 @@ func TestProxyConfiguration_Resource(t *testing.T) { JsonFormat: "jsonFormat", TextFormat: "text format.", }, - EnvoyExtensions: []*pbmesh.EnvoyExtension{ - { - Name: "extension-1", - Required: true, - Arguments: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "field-1": {}, - "field-2": {}, - }, - }, - ConsulVersion: "1.22.3-beta1", - EnvoyVersion: "1.33.4", - }, - }, PublicListenerJson: "publicListenerJson{}", ListenerTracingJson: "listenerTracingJson{}", LocalClusterJson: "localClusterJson{}", @@ -434,20 +391,6 @@ func TestProxyConfiguration_Resource(t *testing.T) { JsonFormat: "jsonFormat", TextFormat: "text format.", }, - EnvoyExtensions: []*pbmesh.EnvoyExtension{ - { - Name: "extension-1", - Required: true, - Arguments: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "field-1": {}, - "field-2": {}, - }, - }, - ConsulVersion: "1.22.3-beta1", - EnvoyVersion: "1.33.4", - }, - }, PublicListenerJson: "publicListenerJson{}", ListenerTracingJson: "listenerTracingJson{}", LocalClusterJson: "localClusterJson{}", diff --git a/control-plane/go.mod b/control-plane/go.mod index 6671f553b8..5640ab6f5d 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -1,13 +1,5 @@ module github.com/hashicorp/consul-k8s/control-plane -// TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases -replace ( - // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version - github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58 - // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version - github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 -) - require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 @@ -18,9 +10,9 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.6 - github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a - github.com/hashicorp/consul/proto-public v0.4.1 - github.com/hashicorp/consul/sdk v0.14.1 + github.com/hashicorp/consul/api v1.26.1 + github.com/hashicorp/consul/proto-public v0.5.1 + github.com/hashicorp/consul/sdk v0.15.0 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 github.com/hashicorp/go-hclog v1.5.0 diff --git a/control-plane/go.sum b/control-plane/go.sum index e8929ad883..161a8184e8 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -263,12 +263,12 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= 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.10.1-0.20231011171434-ca1a755f0c5a h1:+cjavDME42W6nzw+xyCQ+eczqTFA+Qk4SLl7BHZ2dxI= -github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a/go.mod h1:+pNEP6hQgkrBLjQlYLI13/tyyb1GK3MGVw1PC/IHk9M= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58 h1:3VHvqLs2zTa9YWMsE4A9IricAZB6rAcXVe8e+pb5slw= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= -github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 h1:j0Rvt1KiKIKlMc2UnA0ilHfIGo66SzrBztGZaCou3+w= -github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= +github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= +github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= +github.com/hashicorp/consul/proto-public v0.5.1 h1:g4xHZ7rJ56iktDi1uThKp+IbvHrP6nveZeGVt2Qw5x0= +github.com/hashicorp/consul/proto-public v0.5.1/go.mod h1:SayEhfXS3DQDnW/vKSZXvkwDObg7XK60KTfrJcp0wrg= +github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= +github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= 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= From b8512eee9af7c234198997977e914d87185994bd Mon Sep 17 00:00:00 2001 From: Ashesh Vidyut <134911583+absolutelightning@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:51:54 +0530 Subject: [PATCH 460/592] NET 6409 (#3158) * init * fix help and synopsis * added some tests * change log * some fixes * rename var name * tests for get envoy stats * fix tests * Update cli/cmd/envoy-stats/envoy_stats.go Co-authored-by: Thomas Eckert * proxy stats command * fix command options * pr comment resolved * fix globaloptions * fix lint --------- Co-authored-by: Thomas Eckert --- .changelog/3158.txt | 3 + cli/cmd/proxy/stats/command.go | 222 ++++++++++++++++++++++++++++ cli/cmd/proxy/stats/command_test.go | 157 ++++++++++++++++++++ cli/commands.go | 6 + cli/common/envoy/http_test.go | 2 + cli/common/portforward.go | 5 + 6 files changed, 395 insertions(+) create mode 100644 .changelog/3158.txt create mode 100644 cli/cmd/proxy/stats/command.go create mode 100644 cli/cmd/proxy/stats/command_test.go diff --git a/.changelog/3158.txt b/.changelog/3158.txt new file mode 100644 index 0000000000..8c89ec9a89 --- /dev/null +++ b/.changelog/3158.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod +``` \ No newline at end of file diff --git a/cli/cmd/proxy/stats/command.go b/cli/cmd/proxy/stats/command.go new file mode 100644 index 0000000000..5c2c5b1bea --- /dev/null +++ b/cli/cmd/proxy/stats/command.go @@ -0,0 +1,222 @@ +package stats + +import ( + "errors" + "fmt" + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/consul-k8s/cli/helm" + helmCLI "helm.sh/helm/v3/pkg/cli" + "io" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "net/http" + "strconv" + "strings" + "sync" +) + +const envoyAdminPort = 19000 + +type StatsCommand struct { + *common.BaseCommand + + helmActionsRunner helm.HelmActionsRunner + + kubernetes kubernetes.Interface + + restConfig *rest.Config + + set *flag.Sets + + flagKubeConfig string + flagKubeContext string + flagNamespace string + flagPod string + + once sync.Once + help string +} + +func (c *StatsCommand) init() { + c.set = flag.NewSets() + if c.helmActionsRunner == nil { + c.helmActionsRunner = &helm.ActionRunner{} + } + + f := c.set.NewSet("Command Options") + f.StringVar(&flag.StringVar{ + Name: "namespace", + Target: &c.flagNamespace, + Usage: "The namespace where the target Pod can be found.", + Aliases: []string{"n"}, + }) + + f = c.set.NewSet("Global Options") + f.StringVar(&flag.StringVar{ + Name: "kubeconfig", + Aliases: []string{"c"}, + Target: &c.flagKubeConfig, + Default: "", + Usage: "Path to kubeconfig file.", + }) + f.StringVar(&flag.StringVar{ + Name: "context", + Target: &c.flagKubeContext, + Default: "", + Usage: "Kubernetes context to use.", + }) + + c.help = c.set.Help() +} + +// validateFlags checks the command line flags and values for errors. +func (c *StatsCommand) validateFlags() error { + if len(c.set.Args()) > 0 { + return errors.New("should have no non-flag arguments") + } + return nil +} + +func (c *StatsCommand) Run(args []string) int { + c.once.Do(c.init) + + if err := c.parseFlags(args); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + c.UI.Output("\n" + c.Help()) + return 1 + } + + if err := c.validateFlags(); err != nil { + c.UI.Output(err.Error()) + return 1 + } + + if c.flagPod == "" { + c.UI.Output("pod name is required") + return 1 + } + + // helmCLI.New() will create a settings object which is used by the Helm Go SDK calls. + settings := helmCLI.New() + if c.flagKubeConfig != "" { + settings.KubeConfig = c.flagKubeConfig + } + if c.flagKubeContext != "" { + settings.KubeContext = c.flagKubeContext + } + + if c.flagNamespace == "" { + c.flagNamespace = settings.Namespace() + } + + if err := c.setupKubeClient(settings); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if c.restConfig == nil { + var err error + if c.restConfig, err = settings.RESTClientGetter().ToRESTConfig(); err != nil { + c.UI.Output("error setting rest config") + return 1 + } + } + + pf := common.PortForward{ + Namespace: c.flagNamespace, + PodName: c.flagPod, + RemotePort: envoyAdminPort, + KubeClient: c.kubernetes, + RestConfig: c.restConfig, + } + + stats, err := c.getEnvoyStats(&pf) + if err != nil { + c.UI.Output("error fetching envoy stats %v", err, terminal.WithErrorStyle()) + return 1 + } + + c.UI.Output(stats) + return 0 + +} + +func (c *StatsCommand) getEnvoyStats(pf common.PortForwarder) (string, error) { + _, err := pf.Open(c.Ctx) + if err != nil { + return "", fmt.Errorf("error port forwarding %s", err) + } + defer pf.Close() + + resp, err := http.Get(fmt.Sprintf("http://localhost:%s/stats", strconv.Itoa(pf.GetLocalPort()))) + if err != nil { + return "", fmt.Errorf("error hitting stats endpoint of envoy %s", err) + } + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("error reading body of http response %s", err) + } + + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + return string(bodyBytes), nil +} + +// setupKubeClient to use for non Helm SDK calls to the Kubernetes API The Helm SDK will use +// settings.RESTClientGetter for its calls as well, so this will use a consistent method to +// target the right cluster for both Helm SDK and non Helm SDK calls. +func (c *StatsCommand) setupKubeClient(settings *helmCLI.EnvSettings) error { + if c.kubernetes == nil { + restConfig, err := settings.RESTClientGetter().ToRESTConfig() + if err != nil { + c.UI.Output("Error retrieving Kubernetes authentication: %v", err, terminal.WithErrorStyle()) + return err + } + c.kubernetes, err = kubernetes.NewForConfig(restConfig) + if err != nil { + c.UI.Output("Error initializing Kubernetes client: %v", err, terminal.WithErrorStyle()) + return err + } + } + + return nil +} + +func (c *StatsCommand) parseFlags(args []string) error { + // Separate positional arguments from keyed arguments. + var positional []string + for _, arg := range args { + if strings.HasPrefix(arg, "-") { + break + } + positional = append(positional, arg) + } + keyed := args[len(positional):] + + if len(positional) != 1 { + return fmt.Errorf("exactly one positional argument is required: ") + } + c.flagPod = positional[0] + + if err := c.set.Parse(keyed); err != nil { + return err + } + + return nil +} + +// Help returns a description of the command and how it is used. +func (c *StatsCommand) Help() string { + c.once.Do(c.init) + return c.Synopsis() + "\n\nUsage: consul-k8s proxy stats pod-name -n namespace [flags]\n\n" + c.help +} + +// Synopsis returns a one-line command summary. +func (c *StatsCommand) Synopsis() string { + return "Display Envoy stats for a proxy" +} diff --git a/cli/cmd/proxy/stats/command_test.go b/cli/cmd/proxy/stats/command_test.go new file mode 100644 index 0000000000..a50b67c078 --- /dev/null +++ b/cli/cmd/proxy/stats/command_test.go @@ -0,0 +1,157 @@ +package stats + +import ( + "bytes" + "context" + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "io" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + "net/http" + "os" + "strconv" + "testing" +) + +func TestFlagParsing(t *testing.T) { + cases := map[string]struct { + args []string + out int + }{ + "No args, should fail": { + args: []string{}, + out: 1, + }, + "Nonexistent flag passed, -foo bar, should fail": { + args: []string{"-foo", "bar"}, + out: 1, + }, + "Invalid argument passed, -namespace notaname, should fail": { + args: []string{"-namespace", "notaname"}, + out: 1, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := setupCommand(new(bytes.Buffer)) + c.kubernetes = fake.NewSimpleClientset() + out := c.Run(tc.args) + require.Equal(t, tc.out, out) + }) + } +} +func setupCommand(buf io.Writer) *StatsCommand { + // 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 := &StatsCommand{ + BaseCommand: &common.BaseCommand{ + Log: log, + UI: terminal.NewUI(context.Background(), buf), + }, + } + command.init() + + return command +} + +type MockPortForwarder struct { +} + +func (mpf *MockPortForwarder) Open(ctx context.Context) (string, error) { + return "localhost:" + strconv.Itoa(envoyAdminPort), nil +} + +func (mpf *MockPortForwarder) Close() { + //noop +} + +func (mpf *MockPortForwarder) GetLocalPort() int { + return envoyAdminPort +} + +func TestEnvoyStats(t *testing.T) { + cases := map[string]struct { + namespace string + pods []v1.Pod + }{ + "Sidecar Pods": { + namespace: "default", + pods: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Labels: map[string]string{ + "consul.hashicorp.com/connect-inject-status": "injected", + }, + }, + }, + }, + }, + "Pods in consul namespaces": { + namespace: "consul", + pods: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "api-gateway", + Namespace: "consul", + Labels: map[string]string{ + "api-gateway.consul.hashicorp.com/managed": "true", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "consul", + Labels: map[string]string{ + "consul.hashicorp.com/connect-inject-status": "injected", + }, + }, + }, + }, + }, + } + + srv := startHttpServer(envoyAdminPort) + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := setupCommand(new(bytes.Buffer)) + c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: tc.pods}) + c.flagNamespace = tc.namespace + for _, pod := range tc.pods { + c.flagPod = pod.Name + mpf := &MockPortForwarder{} + resp, err := c.getEnvoyStats(mpf) + require.NoError(t, err) + require.Equal(t, resp, "Envoy Stats") + } + }) + } + srv.Shutdown(context.Background()) +} + +func startHttpServer(port int) *http.Server { + srv := &http.Server{Addr: ":" + strconv.Itoa(port)} + + http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "Envoy Stats") + }) + + go func() { + srv.ListenAndServe() + }() + + return srv +} diff --git a/cli/commands.go b/cli/commands.go index 1c58e75cf8..513bd6d9d7 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -5,6 +5,7 @@ package main import ( "context" + "github.com/hashicorp/consul-k8s/cli/cmd/proxy/stats" "github.com/hashicorp/consul-k8s/cli/cmd/config" config_read "github.com/hashicorp/consul-k8s/cli/cmd/config/read" @@ -81,6 +82,11 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm BaseCommand: baseCommand, }, nil }, + "proxy stats": func() (cli.Command, error) { + return &stats.StatsCommand{ + BaseCommand: baseCommand, + }, nil + }, "config": func() (cli.Command, error) { return &config.ConfigCommand{ BaseCommand: baseCommand, diff --git a/cli/common/envoy/http_test.go b/cli/common/envoy/http_test.go index 961046100e..197bb62e84 100644 --- a/cli/common/envoy/http_test.go +++ b/cli/common/envoy/http_test.go @@ -8,6 +8,7 @@ import ( "context" "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/util/rand" "net/http" "net/http/httptest" "os" @@ -528,6 +529,7 @@ type mockPortForwarder struct { func (m *mockPortForwarder) Open(ctx context.Context) (string, error) { return m.openBehavior(ctx) } func (m *mockPortForwarder) Close() {} +func (m *mockPortForwarder) GetLocalPort() int { return int(rand.Int63nRange(0, 65535)) } func testLogConfig() map[string]string { cfg := make(map[string]string, len(EnvoyLoggers)) diff --git a/cli/common/portforward.go b/cli/common/portforward.go index 79536706d9..3b0e030ac1 100644 --- a/cli/common/portforward.go +++ b/cli/common/portforward.go @@ -47,6 +47,7 @@ type PortForward struct { type PortForwarder interface { Open(context.Context) (string, error) Close() + GetLocalPort() int } // forwarder is an interface which can be used for opening a port forward session. @@ -117,6 +118,10 @@ func (pf *PortForward) Close() { close(pf.stopChan) } +func (pf *PortForward) GetLocalPort() int { + return pf.localPort +} + // allocateLocalPort looks for an open port on localhost and sets it to the // localPort field. func (pf *PortForward) allocateLocalPort() error { From 39aae718989cb6f081d5e214d1b5ffc82cf61b8e Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Wed, 8 Nov 2023 10:17:33 -0500 Subject: [PATCH 461/592] [NET-5025] Helm Chart: Support StatefulSet PVC retention (#3180) Helm Chart: Support StatefulSet PVC retention Add new variable on the Helm chart to define the PVC retention policy for the StatefulSet Co-authored-by: Aleix Murtra --- .changelog/3180.txt | 3 + .../consul/templates/server-statefulset.yaml | 3 + .../consul/test/unit/server-statefulset.bats | 67 +++++++++++++++++++ charts/consul/values.yaml | 15 +++++ 4 files changed, 88 insertions(+) create mode 100644 .changelog/3180.txt diff --git a/.changelog/3180.txt b/.changelog/3180.txt new file mode 100644 index 0000000000..aee3bfb8aa --- /dev/null +++ b/.changelog/3180.txt @@ -0,0 +1,3 @@ +```release-note:feature +helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. +``` diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index d2785369c7..c5935f1499 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -44,6 +44,9 @@ spec: rollingUpdate: partition: {{ .Values.server.updatePartition }} {{- end }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.server.persistentVolumeClaimRetentionPolicy) }} + persistentVolumeClaimRetentionPolicy: {{ toYaml .Values.server.persistentVolumeClaimRetentionPolicy | nindent 4 }} + {{- end }} selector: matchLabels: app: {{ template "consul.name" . }} diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index f4ab239aa8..895bdeb4e8 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -196,6 +196,73 @@ load _helpers [ "${actual}" = "foo" ] } +#-------------------------------------------------------------------- +# persistentVolumeClaimRetentionPolicy + +@test "server/StatefulSet: persistentVolumeClaimRetentionPolicy not set by default when kubernetes < 1.23" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --kube-version "1.22" \ + . | tee /dev/stderr | + yq -r '.spec.persistentVolumeClaimRetentionPolicy' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "server/StatefulSet: unset persistentVolumeClaimRetentionPolicy.whenDeleted when kubernetes < 1.23" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --kube-version "1.22" \ + --set 'server.persistentVolumeClaimRetentionPolicy.whenDeleted=Delete' \ + . | tee /dev/stderr | + yq -r '.spec.persistentVolumeClaimRetentionPolicy.whenDeleted' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "server/StatefulSet: unset persistentVolumeClaimRetentionPolicy.whenScaled when kubernetes < 1.23" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --kube-version "1.22" \ + --set 'server.persistentVolumeClaimRetentionPolicy.whenScaled=Delete' \ + . | tee /dev/stderr | + yq -r '.spec.persistentVolumeClaimRetentionPolicy.whenScaled' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "server/StatefulSet: persistentVolumeClaimRetentionPolicy not set by default when kubernetes >= 1.23" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --kube-version "1.23" \ + . | tee /dev/stderr | + yq -r '.spec.persistentVolumeClaimRetentionPolicy' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "server/StatefulSet: can set persistentVolumeClaimRetentionPolicy.whenDeleted when kubernetes >= 1.23" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --kube-version "1.23" \ + --set 'server.persistentVolumeClaimRetentionPolicy.whenDeleted=Delete' \ + . | tee /dev/stderr | + yq -r '.spec.persistentVolumeClaimRetentionPolicy.whenDeleted' | tee /dev/stderr) + [ "${actual}" = "Delete" ] +} + +@test "server/StatefulSet: can set persistentVolumeClaimRetentionPolicy.whenScaled when kubernetes >= 1.23" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-statefulset.yaml \ + --kube-version "1.23" \ + --set 'server.persistentVolumeClaimRetentionPolicy.whenScaled=Delete' \ + . | tee /dev/stderr | + yq -r '.spec.persistentVolumeClaimRetentionPolicy.whenScaled' | tee /dev/stderr) + [ "${actual}" = "Delete" ] +} + #-------------------------------------------------------------------- # serverCert diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index c9abd21713..577d1af369 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -882,6 +882,21 @@ server: # @type: string storageClass: null + # The [Persistent Volume Claim (PVC) retention policy](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#persistentvolumeclaim-retention) + # controls if and how PVCs are deleted during the lifecycle of a StatefulSet. + # WhenDeleted specifies what happens to PVCs created from StatefulSet VolumeClaimTemplates when the StatefulSet is deleted, + # and WhenScaled specifies what happens to PVCs created from StatefulSet VolumeClaimTemplates when the StatefulSet is scaled down. + # + # Example: + # + # ```yaml + # persistentVolumeClaimRetentionPolicy: + # whenDeleted: Retain + # whenScaled: Retain + # ``` + # @type: map + persistentVolumeClaimRetentionPolicy: null + # This will enable/disable [service mesh](https://developer.hashicorp.com/consul/docs/connect). Setting this to true # _will not_ automatically secure pod communication, this # setting will only enable usage of the feature. Consul will automatically initialize From a3d1715838101fc45103dd30f3094634ab23ef8e Mon Sep 17 00:00:00 2001 From: Joshua Timmons Date: Wed, 8 Nov 2023 16:35:22 -0500 Subject: [PATCH 462/592] Fix consul-telemetry-collector deployment templates (#3184) --- .changelog/3184.txt | 3 ++ .../telemetry-collector-deployment.yaml | 3 +- .../telemetry-collector-v2-deployment.yaml | 3 +- .../unit/telemetry-collector-deployment.bats | 38 +++++++++++++++++- .../telemetry-collector-v2-deployment.bats | 40 +++++++++++++++++++ 5 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 .changelog/3184.txt diff --git a/.changelog/3184.txt b/.changelog/3184.txt new file mode 100644 index 0000000000..4e1abf0f35 --- /dev/null +++ b/.changelog/3184.txt @@ -0,0 +1,3 @@ +```release-note:bug +consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs +``` \ No newline at end of file diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index 396cc147ab..780884f999 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -287,13 +287,12 @@ spec: - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method {{- if .Values.global.enableConsulNamespaces }} {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} - - -login-namespace="default" + - -login-namespace=default {{- else }} - -login-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} {{- end }} {{- end }} {{- if .Values.global.adminPartitions.enabled }} - - foo - -login-partition={{ .Values.global.adminPartitions.name }} {{- end }} {{- end }} diff --git a/charts/consul/templates/telemetry-collector-v2-deployment.yaml b/charts/consul/templates/telemetry-collector-v2-deployment.yaml index a88277f3b2..86b4edf159 100644 --- a/charts/consul/templates/telemetry-collector-v2-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-v2-deployment.yaml @@ -275,13 +275,12 @@ spec: - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method {{- if .Values.global.enableConsulNamespaces }} {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} - - -login-namespace="default" + - -login-namespace=default {{- else }} - -login-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} {{- end }} {{- end }} {{- if .Values.global.adminPartitions.enabled }} - - foo - -login-partition={{ .Values.global.adminPartitions.name }} {{- end }} {{- end }} diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index 432200541b..60b87961b5 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -1212,4 +1212,40 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ --set 'ui.enabled=false' \ --set 'global.experiments[0]=resource-apis' \ . -} \ No newline at end of file +} + +#-------------------------------------------------------------------- +# Namespaces + +@test "telemetryCollector/Deployment: namespace flags when mirroringK8S" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'syncCatalog.consulNamespaces.mirroringK8S=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-login-namespace=default"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] +} + +@test "telemetryCollector/Deployment: namespace flags when syncCatalog" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'syncCatalog.consulNamespaces.mirroringK8S=false' \ + --set 'syncCatalog.consulNamespaces.consulDestinationNamespace=fakenamespace' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.[1].args | any(contains("-login-namespace=fakenamespace"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] +} diff --git a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats index f1d5de2597..a53882731c 100755 --- a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats @@ -1348,3 +1348,43 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ --set 'telemetryCollector.image=bar' \ . } + +#-------------------------------------------------------------------- +# Namespaces + +@test "telemetryCollector/Deployment(V2): namespace flags when mirroringK8S" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'syncCatalog.consulNamespaces.mirroringK8S=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-login-namespace=default"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] +} + +@test "telemetryCollector/Deployment(V2): namespace flags when syncCatalog" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-v2-deployment.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'syncCatalog.consulNamespaces.mirroringK8S=false' \ + --set 'syncCatalog.consulNamespaces.consulDestinationNamespace=fakenamespace' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers' | tee /dev/stderr) + + local actual=$(echo $object | jq -r '.[1].args | any(contains("-login-namespace=fakenamespace"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] +} From dcfcaae611e343e0c6376aae49bb79103e9c18f0 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Thu, 9 Nov 2023 17:33:36 -0500 Subject: [PATCH 463/592] NET-6303 Add 1.3.0 notes to changelog (#3193) Add 1.3.0 notes to changelog --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f79f7fe8c..e2ee998095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +## 1.3.0 (November 8, 2023) + +SECURITY: + +* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3116](https://github.com/hashicorp/consul-k8s/issues/3116)] + +FEATURES: + +* :tada: This release provides the ability to preview Consul's v2 Catalog and Resource API if enabled. +The new model supports multi-port application deployments with only a single Envoy proxy. +Note that the v1 and v2 catalogs are not cross compatible, and not all Consul features are available within this v2 feature preview. +See the [v2 Catalog and Resource API documentation](https://developer.hashicorp.com/consul/docs/k8s/multiport) for more information. +The v2 Catalog and Resources API should be considered a feature preview within this release and should not be used in production environments. + +### Limitations +* The v1 and v2 catalog APIs cannot run concurrently. +* The Consul UI must be disable. It does not support multi-port services or the v2 catalog API in this release. +* HCP Consul does not support multi-port services or the v2 catalog API in this release. + +[[GH-2868]](https://github.com/hashicorp/consul-k8s/pull/2868) +[[GH-2883]](https://github.com/hashicorp/consul-k8s/pull/2883) +[[GH-2930]](https://github.com/hashicorp/consul-k8s/pull/2930) +[[GH-2967]](https://github.com/hashicorp/consul-k8s/pull/2967) [[GH-2941](https://github.com/hashicorp/consul-k8s/issues/2941)] +* Add the `PrioritizeByLocality` field to the `ServiceResolver` and `ProxyDefaults` CRDs. [[GH-2784](https://github.com/hashicorp/consul-k8s/issues/2784)] +* Set locality on services registered with connect-inject. [[GH-2346](https://github.com/hashicorp/consul-k8s/issues/2346)] +* api-gateway: Add support for response header modifiers in HTTPRoute filters [[GH-2904](https://github.com/hashicorp/consul-k8s/issues/2904)] +* api-gateway: add RouteRetryFilter and RouteTimeoutFilter CRDs [[GH-2735](https://github.com/hashicorp/consul-k8s/issues/2735)] +* helm: (Consul Enterprise) Adds rate limiting config to serviceDefaults CRD [[GH-2844](https://github.com/hashicorp/consul-k8s/issues/2844)] +* helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. [[GH-3180](https://github.com/hashicorp/consul-k8s/issues/3180)] + +IMPROVEMENTS: + +* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2265](https://github.com/hashicorp/consul-k8s/issues/2265)] +* cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod [[GH-3158](https://github.com/hashicorp/consul-k8s/issues/3158)] +* control-plane: Changed the container ordering in connect-inject to insert consul-dataplane container first if lifecycle is enabled. Container ordering is unchanged if lifecycle is disabled. [[GH-2743](https://github.com/hashicorp/consul-k8s/issues/2743)] +* helm: Kubernetes v1.28 is now supported. Minimum tested version of Kubernetes is now v1.25. [[GH-3138](https://github.com/hashicorp/consul-k8s/issues/3138)] + +BUG FIXES: + +* control-plane: Set locality on sidecar proxies in addition to services when registering with connect-inject. [[GH-2748](https://github.com/hashicorp/consul-k8s/issues/2748)] +* control-plane: remove extraneous error log in v2 pod controller when a pod is scheduled, but not yet allocated an IP. [[GH-3162](https://github.com/hashicorp/consul-k8s/issues/3162)] +* control-plane: remove extraneous error log in v2 pod controller when attempting to delete ACL tokens. [[GH-3172](https://github.com/hashicorp/consul-k8s/issues/3172)] + ## 1.2.3 (November 2, 2023) SECURITY: From 19685df196d38726c9c6caa5a1470dae0a421720 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Fri, 10 Nov 2023 10:55:03 -0500 Subject: [PATCH 464/592] Add replace directive in Go mod for Control Plane so that we pull in the latest Consul submodules (#3194) * Add replace directive in Go mod for Control Plane so that we pull in the latest Consul submodules * Add a comment to explain why we need a replace directive --- control-plane/go.mod | 4 ++++ control-plane/go.sum | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/control-plane/go.mod b/control-plane/go.mod index 5640ab6f5d..eb84c72225 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -1,5 +1,9 @@ module github.com/hashicorp/consul-k8s/control-plane +// TODO: Remove this when the next version of the submodule is released. +// We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231109213314-40c57f10a0fb + require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 diff --git a/control-plane/go.sum b/control-plane/go.sum index 161a8184e8..40c39844ff 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.5.1 h1:g4xHZ7rJ56iktDi1uThKp+IbvHrP6nveZeGVt2Qw5x0= -github.com/hashicorp/consul/proto-public v0.5.1/go.mod h1:SayEhfXS3DQDnW/vKSZXvkwDObg7XK60KTfrJcp0wrg= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231109213314-40c57f10a0fb h1:Vy9tVDskUrWMXCyMJHpChxRjzJVjWSsSZ457X1dZAWo= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231109213314-40c57f10a0fb/go.mod h1:SayEhfXS3DQDnW/vKSZXvkwDObg7XK60KTfrJcp0wrg= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= From 2d1724ef87bd3922414f344eac54d95b85d38c8c Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Fri, 10 Nov 2023 12:58:42 -0500 Subject: [PATCH 465/592] NET-6331 Create MeshGateway CRD (#3195) Add MeshGateway to `mesh.consul.hashicorp.com/v2beta1` --- charts/consul/templates/crd-meshgateways.yaml | 100 ++++++++++++ .../api/mesh/v2beta1/mesh_gateway_types.go | 151 ++++++++++++++++++ .../api/mesh/v2beta1/zz_generated.deepcopy.go | 63 ++++++++ ...esh.consul.hashicorp.com_meshgateways.yaml | 95 +++++++++++ 4 files changed, 409 insertions(+) create mode 100644 charts/consul/templates/crd-meshgateways.yaml create mode 100644 control-plane/api/mesh/v2beta1/mesh_gateway_types.go create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml diff --git a/charts/consul/templates/crd-meshgateways.yaml b/charts/consul/templates/crd-meshgateways.yaml new file mode 100644 index 0000000000..c2a376bd12 --- /dev/null +++ b/charts/consul/templates/crd-meshgateways.yaml @@ -0,0 +1,100 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + name: meshgateways.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: MeshGateway + listKind: MeshGatewayList + plural: meshgateways + shortNames: + - mesh-gateway + singular: meshgateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: MeshGateway is the Schema for the Mesh Gateway API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go new file mode 100644 index 0000000000..59fe712768 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go @@ -0,0 +1,151 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + meshGatewayKubeKind = "meshgateway" +) + +func init() { + MeshSchemeBuilder.Register(&MeshGateway{}, &MeshGatewayList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// MeshGateway is the Schema for the Mesh Gateway API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="mesh-gateway" +type MeshGateway struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.MeshGateway `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// MeshGatewayList contains a list of MeshGateway. +type MeshGatewayList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*MeshGateway `json:"items"` +} + +func (in *MeshGateway) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.MeshGatewayType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *MeshGateway) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *MeshGateway) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *MeshGateway) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *MeshGateway) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *MeshGateway) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *MeshGateway) KubeKind() string { + return meshGatewayKubeKind +} + +func (in *MeshGateway) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *MeshGateway) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *MeshGateway) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *MeshGateway) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *MeshGateway) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *MeshGateway) Validate(tenancy common.ConsulTenancyConfig) error { + // TODO add validation logic that ensures we only ever write this to the default namespace. + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *MeshGateway) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index 2d7aadbee7..9acfd073ef 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -172,6 +172,69 @@ func (in *HTTPRouteList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MeshGateway) DeepCopyInto(out *MeshGateway) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshGateway. +func (in *MeshGateway) DeepCopy() *MeshGateway { + if in == nil { + return nil + } + out := new(MeshGateway) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MeshGateway) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MeshGatewayList) DeepCopyInto(out *MeshGatewayList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*MeshGateway, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(MeshGateway) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshGatewayList. +func (in *MeshGatewayList) DeepCopy() *MeshGatewayList { + if in == nil { + return nil + } + out := new(MeshGatewayList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MeshGatewayList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyConfiguration) DeepCopyInto(out *ProxyConfiguration) { *out = *in diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml new file mode 100644 index 0000000000..6fc79979cf --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml @@ -0,0 +1,95 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: meshgateways.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: MeshGateway + listKind: MeshGatewayList + plural: meshgateways + shortNames: + - mesh-gateway + singular: meshgateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: MeshGateway is the Schema for the Mesh Gateway API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} From e13815463c2522a2a06a7737df7d5a0b41c74a62 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:48:37 -0600 Subject: [PATCH 466/592] Add validation to account for type change (#3202) add validation to account for type change --- control-plane/api/v1alpha1/routetimeoutfilter_types.go | 4 ++++ .../crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/control-plane/api/v1alpha1/routetimeoutfilter_types.go b/control-plane/api/v1alpha1/routetimeoutfilter_types.go index 5aed6461bf..96c8b79b30 100644 --- a/control-plane/api/v1alpha1/routetimeoutfilter_types.go +++ b/control-plane/api/v1alpha1/routetimeoutfilter_types.go @@ -40,9 +40,13 @@ type RouteTimeoutFilterList struct { // RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. type RouteTimeoutFilterSpec struct { // +kubebuilder:validation:Optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=duration RequestTimeout metav1.Duration `json:"requestTimeout"` // +kubebuilder:validation:Optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=duration IdleTimeout metav1.Duration `json:"idleTimeout"` } diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml index 0822050cb2..f6cc00f840 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml @@ -51,8 +51,10 @@ spec: description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. properties: idleTimeout: + format: duration type: string requestTimeout: + format: duration type: string type: object status: From a4813e985d0be06e5313f095745c5944ca4bfa8f Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:21:10 -0600 Subject: [PATCH 467/592] NET-4992- Kitchen sink gateway test (#3196) * checkpoint * checkpoint, passing test * kitchen sink, NET-4992 * lint issue * clean up unneeded calls --------- Co-authored-by: Sarah Alsmiller --- .../api_gateway_kitchen_sink_test.go | 232 ++++++++++++++++++ .../kitchen-sink-ent/api-gateway.yaml | 25 ++ .../kitchen-sink-ent/external-ref.yaml | 16 ++ .../kitchen-sink-ent/filters.yaml | 22 ++ .../kitchen-sink-ent/gateway-policy.yaml | 25 ++ .../kitchen-sink-ent/gatewayclassconfig.yaml | 12 + .../kitchen-sink-ent/httproute.yaml | 45 ++++ .../kitchen-sink-ent/jwt-provider.yaml | 12 + .../kitchen-sink-ent/jwt-route-filter.yaml | 15 ++ .../kitchen-sink-ent/kustomization.yaml | 18 ++ .../kitchen-sink/api-gateway.yaml | 25 ++ .../kitchen-sink/external-ref.yaml | 16 ++ .../api-gateways/kitchen-sink/filters.yaml | 22 ++ .../kitchen-sink/gateway-policy.yaml | 25 ++ .../kitchen-sink/gatewayclassconfig.yaml | 12 + .../api-gateways/kitchen-sink/httproute.yaml | 40 +++ .../kitchen-sink/kustomization.yaml | 15 ++ 17 files changed, 577 insertions(+) create mode 100644 acceptance/tests/api-gateway/api_gateway_kitchen_sink_test.go create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/api-gateway.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/external-ref.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gateway-policy.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gatewayclassconfig.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/httproute.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-provider.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-route-filter.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/kustomization.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/api-gateway.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/external-ref.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gateway-policy.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gatewayclassconfig.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/httproute.yaml create mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/kustomization.yaml diff --git a/acceptance/tests/api-gateway/api_gateway_kitchen_sink_test.go b/acceptance/tests/api-gateway/api_gateway_kitchen_sink_test.go new file mode 100644 index 0000000000..d701220a8c --- /dev/null +++ b/acceptance/tests/api-gateway/api_gateway_kitchen_sink_test.go @@ -0,0 +1,232 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package apigateway + +import ( + "context" + "encoding/base64" + "fmt" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "testing" + "time" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/types" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// Enabled everything possible, see if anything breaks. +func TestAPIGateway_KitchenSink(t *testing.T) { + ctx := suite.Environment().DefaultContext(t) + cfg := suite.Config() + + runWithEnterpriseOnlyFeatures := cfg.EnableEnterprise + + serverHelmValues := map[string]string{ + "global.acls.manageSystemACLs": "true", + "global.tls.enabled": "true", + + // Don't install injector, controller and cni on this cluster so that it's not installed twice. + "connectInject.enabled": "false", + "connectInject.cni.enabled": "false", + } + serverReleaseName := helpers.RandomName() + consulServerCluster := consul.NewHelmCluster(t, serverHelmValues, ctx, cfg, serverReleaseName) + consulServerCluster.Create(t) + + helmValues := map[string]string{ + "server.enabled": "false", + "connectInject.consulNamespaces.mirroringK8S": "true", + "global.acls.manageSystemACLs": "true", + "global.tls.enabled": "true", + "global.logLevel": "trace", + "externalServers.enabled": "true", + "externalServers.hosts[0]": fmt.Sprintf("%s-consul-server", serverReleaseName), + "externalServers.httpsPort": "8501", + "global.tls.caCert.secretName": fmt.Sprintf("%s-consul-ca-cert", serverReleaseName), + "global.tls.caCert.secretKey": "tls.crt", + "global.acls.bootstrapToken.secretName": fmt.Sprintf("%s-consul-bootstrap-acl-token", serverReleaseName), + "global.acls.bootstrapToken.secretKey": "token", + } + + releaseName := helpers.RandomName() + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + consulCluster.SkipCheckForPreviousInstallations = true + + consulCluster.Create(t) + + // Override the default proxy config settings for this test + consulClient, _ := consulCluster.SetupConsulClient(t, true, serverReleaseName) + logger.Log(t, "have consul client") + _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + }, nil) + require.NoError(t, err) + logger.Log(t, "set consul config entry") + + logger.Log(t, "creating other namespace") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "create", "namespace", "other") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "namespace", "other") + }) + + k8sClient := ctx.ControllerRuntimeClient(t) + + logger.Log(t, "creating api-gateway resources") + fixturePath := "../fixtures/cases/api-gateways/kitchen-sink" + if runWithEnterpriseOnlyFeatures { + fixturePath += "-ent" + } + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", fixturePath) + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", fixturePath) + }) + + // Create certificate secret, we do this separately since + // applying the secret will make an invalid certificate that breaks other tests + logger.Log(t, "creating certificate secret") + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/bases/api-gateway/certificate.yaml") + require.NoError(t, err, out) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/bases/api-gateway/certificate.yaml") + }) + + // patch certificate with data + logger.Log(t, "patching certificate secret with generated data") + certificate := generateCertificate(t, nil, "gateway.test.local") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") + + // Create static server and static client + logger.Log(t, "creating static-client pod") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", "static-server")) + + // On startup, the controller can take upwards of 1m to perform + // leader election so we may need to wait a long time for + // the reconcile loop to run (hence the 2m timeout here). + var ( + gatewayAddress string + httpRoute gwv1beta1.HTTPRoute + ) + + counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} + retry.RunWith(counter, t, func(r *retry.R) { + var gateway gwv1beta1.Gateway + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) + require.NoError(r, err) + + //CHECK TO MAKE SURE EVERYTHING WAS SET UP CORECTLY BEFORE RUNNING TESTS + require.Len(r, gateway.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, gateway.Finalizers[0]) + + // check our statuses + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) + require.Len(r, gateway.Status.Listeners, 2) + + require.EqualValues(r, int32(1), gateway.Status.Listeners[0].AttachedRoutes) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) + checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + + // check that we have an address to use + require.Len(r, gateway.Status.Addresses, 2) + // now we know we have an address, set it so we can use it + gatewayAddress = gateway.Status.Addresses[0].Value + + // http route checks + err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route", Namespace: "default"}, &httpRoute) + require.NoError(r, err) + + // check our finalizers + require.Len(r, httpRoute.Finalizers, 1) + require.EqualValues(r, gatewayFinalizer, httpRoute.Finalizers[0]) + + // check parent status + require.Len(r, httpRoute.Status.Parents, 1) + require.EqualValues(r, gatewayClassControllerName, httpRoute.Status.Parents[0].ControllerName) + require.EqualValues(r, "gateway", httpRoute.Status.Parents[0].ParentRef.Name) + checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) + checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) + checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) + + }) + + // GENERAL Asserts- test that assets were created as expected + entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) + require.NoError(t, err) + gateway := entry.(*api.APIGatewayConfigEntry) + + entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) + require.NoError(t, err) + consulHTTPRoute := entry.(*api.HTTPRouteConfigEntry) + + // now check the gateway status conditions + checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) + + // and the route status conditions + checkConsulStatusCondition(t, consulHTTPRoute.Status.Conditions, trueConsulCondition("Bound", "Bound")) + + // finally we check that we can actually route to the service(s) via the gateway + k8sOptions := ctx.KubectlOptions(t) + targetHTTPAddress := fmt.Sprintf("http://%s/v1", gatewayAddress) + + // Now we create the allow intention. + _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: "static-server", + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Action: api.IntentionActionAllow, + }, + }, + }, nil) + require.NoError(t, err) + + _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: "static-server-protected", + Sources: []*api.SourceIntention{ + { + Name: "gateway", + Action: api.IntentionActionAllow, + }, + }, + }, nil) + require.NoError(t, err) + + //asserts only valid when running with enterprise + if runWithEnterpriseOnlyFeatures { + //JWT Related Asserts + // should fail because we're missing JWT + logger.Log(t, "trying calls to api gateway /admin should fail without JWT token") + k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddress) + + // will succeed because we use the token with the correct role and the correct issuer + logger.Log(t, "trying calls to api gateway /admin should succeed with JWT token with correct role") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddress) + } else { + // Test that we can make a call to the api gateway + logger.Log(t, "trying calls to api gateway http") + k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) + } +} diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/api-gateway.yaml new file mode 100644 index 0000000000..3b59ada305 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/api-gateway.yaml @@ -0,0 +1,25 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: gateway +spec: + gatewayClassName: gateway-class + listeners: + - protocol: HTTP + port: 80 + name: http + allowedRoutes: + namespaces: + from: "All" + - protocol: HTTPS + port: 443 + name: https + tls: + certificateRefs: + - name: "certificate" + allowedRoutes: + namespaces: + from: "All" diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/external-ref.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/external-ref.yaml new file mode 100644 index 0000000000..57e6dfee7c --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/external-ref.yaml @@ -0,0 +1,16 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteAuthFilter +metadata: + name: route-jwt-auth-filter + namespace: default +spec: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml new file mode 100644 index 0000000000..a35f41ed61 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml @@ -0,0 +1,22 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteRetryFilter +metadata: + name: retrytrafficfilter +spec: + numRetries: 1 + retryOnConnectFailure: false + retryOn: + - reset + - unavailable + retryOnStatusCodes: + - 500 + - 502 + +--- +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteTimeoutFilter +metadata: + name: timeouttrafficfilter +spec: + requestTimeout: "1s" + idleTimeout: "1s" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gateway-policy.yaml new file mode 100644 index 0000000000..5552d7e085 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gateway-policy.yaml @@ -0,0 +1,25 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayPolicy +metadata: + name: my-policy +spec: + targetRef: + name: gateway + sectionName: http-auth + group: gateway.networking.k8s.io/v1beta1 + kind: Gateway + override: + jwt: + providers: + - name: "local" + default: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gatewayclassconfig.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gatewayclassconfig.yaml new file mode 100644 index 0000000000..42c9bee986 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gatewayclassconfig.yaml @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayClassConfig +metadata: + name: gateway-class-config +spec: + deployment: + defaultInstances: 2 + maxInstances: 3 + minInstances: 1 \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/httproute.yaml new file mode 100644 index 0000000000..760791cf51 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/httproute.yaml @@ -0,0 +1,45 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route +spec: + parentRefs: + - name: gateway + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: "/v1" + backendRefs: + - name: static-server + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteAuthFilter + name: route-jwt-auth-filter + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteRetryFilter + name: retrytrafficfilter + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteTimeoutFilter + name: timeouttrafficfilter + - type: RequestHeaderModifier + requestHeaderModifier: + add: + - name: my-header + value: foo + - type: URLRewrite + urlRewrite: + path: + type: "ReplacePrefixMatch" + replacePrefixMatch: "/v1/test" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-provider.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-provider.yaml new file mode 100644 index 0000000000..1e5cbf35d6 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-provider.yaml @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: JWTProvider +metadata: + name: local +spec: + issuer: local + jsonWebKeySet: + local: + jwks: "ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicCI6ICI5TTlWSVhJR0hpR3FlTnhseEJ2V0xFV09oUFh3dXhXZUpod01uM3dGdG9STEtfZmF6VWxjWEc1cUViLTdpMXo3VmlPUWVZRnh6WUZYTS1pbVU3OVFRa1dTVUVSazR2dHZuc2R5UnpUSnVPc3A0ZUhuWFVMSHJPOU51NkJ5bC1VeVprMzFvSnFGeGllM0pHQXlRLUM2OVF2NVFkVjFZV0hfVDkyTzk4d1hYZGMiLAogICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICJxIjogInFIVnZBb3h0ckgxUTVza25veXNMMkhvbC1ubnU3ZlM3Mjg4clRGdE9jeG9Jb29nWXBKVTljemxwcjctSlo2bjc0TUViVHBBMHRkSUR5TEtQQ0xIN3JKTFRrZzBDZVZNQWpmY01zdkRUcWdFOHNBWE42bzd2ZjYya2hwcExYOHVCU3JxSHkyV1JhZXJsbDROU09hcmRGSkQ2MWhHSVF2cEpXRk4xazFTV3pWcyIsCiAgICAgICAgICAgICJkIjogIlp3elJsVklRZkg5ekZ6d1hOZ2hEMHhkZVctalBCbmRkWnJNZ0wwQ2JjeXZZYlg2X1c0ajlhM1dmYWpobmI2bTFILW9CWjRMczVmNXNRVTB2ZFJ2ZG1laFItUG43aWNRcUdURFNKUTYtdWVtNm15UVRWaEo2UmZiM0lINVJ2VDJTOXUzcVFDZWFadWN3aXFoZ1RCbFhnOWFfV0pwVHJYNFhPQ3JCR1ZsTng3Z2JETVJOamNEN0FnRkZ3S2p2TEZVdDRLTkZmdEJqaFF0TDFLQ2VwblNmamtvRm1RUTVlX3RSS2ozX2U1V3pNSkJkekpQejNkR2YxZEk3OF9wYmJFbmFMcWhqNWg0WUx2UU5JUUhVcURYSGx4ZDc1Qlh3aFJReE1nUDRfd1EwTFk2cVRKNGFDa2Q0RDJBTUtqMzJqeVFiVTRKTE9jQjFNMnZBRWFyc2NTU3l0USIsCiAgICAgICAgICAgICJlIjogIkFRQUIiLAogICAgICAgICAgICAidXNlIjogInNpZyIsCiAgICAgICAgICAgICJraWQiOiAiQy1FMW5DandnQkMtUHVHTTJPNDY3RlJEaEt4OEFrVmN0SVNBYm8zcmlldyIsCiAgICAgICAgICAgICJxaSI6ICJ0N2VOQjhQV21xVHdKREZLQlZKZExrZnJJT2drMFJ4MnREODBGNHB5cjhmNzRuNGlVWXFmWG1haVZtbGx2c2FlT3JlNHlIczQ4UE45NVZsZlVvS3Z6ZEJFaDNZTDFINGZTOGlYYXNzNGJiVnVuWHR4U0hMZFFPYUNZYUplSmhBbGMyUWQ4elR0NFFQWk9yRWVWLVJTYU0tN095ekkwUWtSSF9tcmk1YmRrOXMiLAogICAgICAgICAgICAiZHAiOiAiYnBLckQtVXhrRENDal81MFZLU0NFeE1Ec1Zob2VBZm1tNjMxb1o5aDhUTkZ4TUU1YVptbUJ2VzBJUG9wMm1PUF9qTW9FVWxfUG1RYUlBOEgtVEdqTFp2QTMxSlZBeFN3TU5aQzdwaVFPRjYzVnhneTZUTzlmb1hENVdndC1oLUNxU1N6T2V3eFdmUWNTMmpMcTA3NUFxOTYwTnA2SHhjbE8weUdRN1JDSlpjIiwKICAgICAgICAgICAgImFsZyI6ICJQUzI1NiIsCiAgICAgICAgICAgICJkcSI6ICJpdVZveGwwckFKSEM1c2JzbTZpZWQ3c2ZIVXIwS2Rja0hiVFBLb0lPU1BFcU5YaXBlT3BrWkdEdU55NWlDTXNyRnNHaDFrRW9kTkhZdE40ay1USm5KSDliV296SGdXbGloNnN2R1V0Zi1raFMxWC16ckxaMTJudzlyNDRBbjllWG54bjFaVXMxZm5OakltM3dtZ083algyTWxIeVlNVUZVd0RMd09xNEFPUWsiLAogICAgICAgICAgICAibiI6ICJvUmhjeUREdmp3NFZ4SHRRNTZhRDlNSmRTaWhWSk1nTHd1b2FCQVhhc0RjVDNEWVZjcENlVGxDMVBPdzdPNW1Ec2ZSWVFtcGpoendyRDVZWU8yeDE4REl4czdyNTNJdFMxRy1ybnQxQ1diVE9fUzFJT01DR2xxYzh5VWJnLUhSUkRETXQyb2V3TjJoRGtxYlBKVFJNbXpjRkpNMHRpTm1RZVVMcWViZEVYaWVUblJMT1BkMWg2ZmJycVNLS01mSXlIbGZ1WXFQc1VWSEdkMVBESGljZ3NMazFtZDhtYTNIS1hWM0hJdzZrdUV6R0hQb1gxNHo4YWF6RFFZWndUR3ZxVGlPLUdRUlVDZUJueVo4bVhyWnRmSjNqVk83UUhXcEx3MlM1VDVwVTRwcE0xQXppWTFxUDVfY3ZpOTNZT2Zrb09PalRTX3V3RENZWGFxWjB5bTJHYlEiCiAgICAgICAgfQogICAgXQp9Cg==" diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-route-filter.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-route-filter.yaml new file mode 100644 index 0000000000..9ea3ee2acd --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-route-filter.yaml @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteAuthFilter +metadata: + name: route-jwt-auth-filter +spec: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/kustomization.yaml new file mode 100644 index 0000000000..194fc16b6c --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/kustomization.yaml @@ -0,0 +1,18 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- ../../../bases/api-gateway +- ../../static-server-inject +- filters.yaml +- jwt-provider.yaml +- jwt-route-filter.yaml +- gateway-policy.yaml + + +patches: +- path: gatewayclassconfig.yaml +- path: httproute.yaml +- path: api-gateway.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/api-gateway.yaml new file mode 100644 index 0000000000..3b59ada305 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/api-gateway.yaml @@ -0,0 +1,25 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: gateway +spec: + gatewayClassName: gateway-class + listeners: + - protocol: HTTP + port: 80 + name: http + allowedRoutes: + namespaces: + from: "All" + - protocol: HTTPS + port: 443 + name: https + tls: + certificateRefs: + - name: "certificate" + allowedRoutes: + namespaces: + from: "All" diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/external-ref.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/external-ref.yaml new file mode 100644 index 0000000000..57e6dfee7c --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/external-ref.yaml @@ -0,0 +1,16 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteAuthFilter +metadata: + name: route-jwt-auth-filter + namespace: default +spec: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml new file mode 100644 index 0000000000..a35f41ed61 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml @@ -0,0 +1,22 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteRetryFilter +metadata: + name: retrytrafficfilter +spec: + numRetries: 1 + retryOnConnectFailure: false + retryOn: + - reset + - unavailable + retryOnStatusCodes: + - 500 + - 502 + +--- +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteTimeoutFilter +metadata: + name: timeouttrafficfilter +spec: + requestTimeout: "1s" + idleTimeout: "1s" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gateway-policy.yaml new file mode 100644 index 0000000000..5552d7e085 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gateway-policy.yaml @@ -0,0 +1,25 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayPolicy +metadata: + name: my-policy +spec: + targetRef: + name: gateway + sectionName: http-auth + group: gateway.networking.k8s.io/v1beta1 + kind: Gateway + override: + jwt: + providers: + - name: "local" + default: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gatewayclassconfig.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gatewayclassconfig.yaml new file mode 100644 index 0000000000..42c9bee986 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gatewayclassconfig.yaml @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayClassConfig +metadata: + name: gateway-class-config +spec: + deployment: + defaultInstances: 2 + maxInstances: 3 + minInstances: 1 \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/httproute.yaml new file mode 100644 index 0000000000..519b790a4d --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/httproute.yaml @@ -0,0 +1,40 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route +spec: + parentRefs: + - name: gateway + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: "/v1" + backendRefs: + - name: static-server + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteRetryFilter + name: retrytrafficfilter + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteTimeoutFilter + name: timeouttrafficfilter + - type: RequestHeaderModifier + requestHeaderModifier: + add: + - name: my-header + value: foo + - type: URLRewrite + urlRewrite: + path: + type: "ReplacePrefixMatch" + replacePrefixMatch: "/v1/test" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/kustomization.yaml new file mode 100644 index 0000000000..55a32c7260 --- /dev/null +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/kustomization.yaml @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- ../../../bases/api-gateway +- ../../static-server-inject +- filters.yaml + + +patches: +- path: gatewayclassconfig.yaml +- path: httproute.yaml +- path: api-gateway.yaml From 7371bfadc56ab86c93bac37cb4dded8daf7120c5 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Mon, 13 Nov 2023 12:35:43 -0500 Subject: [PATCH 468/592] =?UTF-8?q?NET-6406=20Adds=20GatewayClassConfig=20?= =?UTF-8?q?and=20MeshGateway=20resources=20to=20the=20gateway-reso?= =?UTF-8?q?=E2=80=A6=20(#3200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds GatewayClassConfig and MeshGateway resources to the gateway-resources-configmap.yaml in the Helm chart * Updates the configmap to include more fields for the gatewayClassConfig for mesh gateways --- .../gateway-resources-configmap.yaml | 19 ++++++++++++++ .../templates/gateway-resources-job.yaml | 2 +- .../unit/gateway-resources-configmap.bats | 25 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 591aaa2129..5c19fbebe5 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -16,4 +16,23 @@ data: resources.json: | {{ toJson .Values.connectInject.apiGateway.managedGatewayClass.resources }} {{- end }} + {{- if (mustHas "resource-apis" .Values.global.experiments) }} + config.yaml: | + gatewayClassConfigs: + - apiVersion: mesh.consul.hashicorp.com/v2beta1 + metadata: + name: consul-mesh-gateway + namespace: {{ .Release.Namespace }} + kind: gatewayClassConfig + spec: + deployment: + resources: + {{ .Values.meshGateway.resources }} + nodeSelector: {{ .Values.meshGateway.nodeSelector }} + serviceType: {{ .Values.meshGateway.service.type }} + meshGateways: + - name: mesh-gateway + spec: + gatewayClassName: consul-mesh-gateway + {{- end }} {{- end }} diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 1136d2e0fe..5934372ed3 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -51,7 +51,7 @@ spec: - -heritage={{ .Release.Service }} - -release-name={{ .Release.Name }} - -component=api-gateway - {{- if .Values.apiGateway.enabled }} # Overide values from the old stanza. To be removed in 1.17 (t-eckert 2023-05-19) + {{- if .Values.apiGateway.enabled }} # Override values from the old stanza. To be removed after ~1.18 (t-eckert 2023-05-19) NET-6263 {{- if .Values.apiGateway.managedGatewayClass.deployment }} {{- if .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} - -deployment-default-instances={{ .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} diff --git a/charts/consul/test/unit/gateway-resources-configmap.bats b/charts/consul/test/unit/gateway-resources-configmap.bats index 80225eeefb..5c0182f602 100644 --- a/charts/consul/test/unit/gateway-resources-configmap.bats +++ b/charts/consul/test/unit/gateway-resources-configmap.bats @@ -44,3 +44,28 @@ load _helpers local actual=$(echo $resources | jq -r '.limits.cpu') [ $actual = '220m' ] } + +@test "gateway-resources/ConfigMap: does not contain config.yaml resources without .global.experiments equal to resource-apis" { + cd `chart_dir` + local resources=$(helm template \ + -s templates/gateway-resources-configmap.yaml \ + --set 'connectInject.enabled=true' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr | + yq '.data["config.yaml"]' | tee /dev/stderr) + [ $resources = null ] + +} + +@test "gateway-resources/ConfigMap: contains config.yaml resources with .global.experiments equal to resource-apis" { + cd `chart_dir` + local resources=$(helm template \ + -s templates/gateway-resources-configmap.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr | + yq '.data["config.yaml"]' | tee /dev/stderr) + + [ "$resources" != null ] +} From b18b849f5b1abea2928211f47aec25c153cf9af9 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 13 Nov 2023 15:14:08 -0500 Subject: [PATCH 469/592] chore: skaffold build experiment (#3179) * chore: skaffold build experiment * feedback: add experiment comments --- Makefile | 8 ++++++++ control-plane/Dockerfile.dev | 11 +++++++++++ 2 files changed, 19 insertions(+) create mode 100644 control-plane/Dockerfile.dev diff --git a/Makefile b/Makefile index 5626bbe99d..b5c742d7cd 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,14 @@ control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. --build-arg 'GIT_DESCRIBE=$(GIT_DESCRIBE)' \ -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane +# DANGER: this target is experimental and could be modified/removed at any time. +# Build consul-k8s-control-plane dev Docker image for use with skaffold or local development. +control-plane-dev-skaffold: + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) + @docker build -t '$(DEV_IMAGE)' \ + --build-arg 'TARGETARCH=$(GOARCH)' \ + -f $(CURDIR)/control-plane/Dockerfile.dev $(CURDIR)/control-plane + check-remote-dev-image-env: ifndef REMOTE_DEV_IMAGE $(error REMOTE_DEV_IMAGE is undefined: set this image to /:, e.g. hashicorp/consul-k8s-dev:latest) diff --git a/control-plane/Dockerfile.dev b/control-plane/Dockerfile.dev new file mode 100644 index 0000000000..5da7e2a236 --- /dev/null +++ b/control-plane/Dockerfile.dev @@ -0,0 +1,11 @@ +# DANGER: this dockerfile is experimental and could be modified/removed at any time. +# A simple image for testing changes to consul-k8s +# +# Meant to be used with the following make target +# DEV_IMAGE= make control-plane-dev-skaffold + +FROM hashicorp/consul-k8s-control-plane as cache +ARG TARGETARCH + +COPY pkg/bin/linux_${TARGETARCH}/consul-k8s-control-plane /bin +COPY cni/pkg/bin/linux_${TARGETARCH}/consul-cni /bin From d3d5b73993d8f8f1976cb2b01459d1af97e812fa Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Tue, 14 Nov 2023 10:25:36 -0500 Subject: [PATCH 470/592] fix(control-plane): sidecar CPU limit incorrectly validated against CPU request (#3209) --- .changelog/3209.txt | 4 ++++ control-plane/subcommand/inject-connect/command.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changelog/3209.txt diff --git a/.changelog/3209.txt b/.changelog/3209.txt new file mode 100644 index 0000000000..35554fc2d9 --- /dev/null +++ b/.changelog/3209.txt @@ -0,0 +1,4 @@ +```release-note:bug +control-plane: Fixes a bug with the control-plane CLI validation where the consul-dataplane sidecar CPU request is +compared against the memory limit instead of the CPU limit. +``` \ No newline at end of file diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 1a8a32901c..ab452e5769 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -461,7 +461,7 @@ func (c *Command) parseAndValidateSidecarProxyFlags() error { return fmt.Errorf("-default-sidecar-proxy-cpu-limit is invalid: %w", err) } } - if c.sidecarProxyCPULimit.Value() != 0 && c.sidecarProxyCPURequest.Cmp(c.sidecarProxyMemoryLimit) > 0 { + if c.sidecarProxyCPULimit.Value() != 0 && c.sidecarProxyCPURequest.Cmp(c.sidecarProxyCPULimit) > 0 { return fmt.Errorf("request must be <= limit: -default-sidecar-proxy-cpu-request value of %q is greater than the -default-sidecar-proxy-cpu-limit value of %q", c.flagDefaultSidecarProxyCPURequest, c.flagDefaultSidecarProxyCPULimit) } From 9d2fd316633ac8684ecbf91e4d5f9c5947f84ca3 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 14 Nov 2023 11:55:10 -0500 Subject: [PATCH 471/592] NET-6401 Stub MeshGateway controller (#3204) * NET-6401 Stub MeshGateway controller * Add MeshGateways resource to connect-inject-clusterrole * Setup v2controller for MeshGateway * Add bats test assertion for connect-inject-clusterrole * Regenerate control-plane/config/rbac/role.yaml --- .../templates/connect-inject-clusterrole.yaml | 2 + .../test/unit/connect-inject-clusterrole.bats | 14 ++++++ control-plane/api/common/common.go | 1 + .../controllersv2/mesh_gateway_controller.go | 44 +++++++++++++++++++ control-plane/config/rbac/role.yaml | 20 +++++++++ .../inject-connect/v2controllers.go | 9 ++++ 6 files changed, 90 insertions(+) create mode 100644 control-plane/config-entries/controllersv2/mesh_gateway_controller.go diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 2506637949..4cc749a0e6 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -98,6 +98,7 @@ rules: resources: - grpcroutes - httproutes + - meshgateways - tcproutes - proxyconfigurations verbs: @@ -113,6 +114,7 @@ rules: resources: - grpcroutes/status - httproutes/status + - meshgateways/status - tcproutes/status - proxyconfigurations/status verbs: diff --git a/charts/consul/test/unit/connect-inject-clusterrole.bats b/charts/consul/test/unit/connect-inject-clusterrole.bats index d02b9eacde..fafe4a4106 100644 --- a/charts/consul/test/unit/connect-inject-clusterrole.bats +++ b/charts/consul/test/unit/connect-inject-clusterrole.bats @@ -241,3 +241,17 @@ load _helpers yq '.rules[13].resourceNames | index("fakescc")' | tee /dev/stderr) [ "${object}" == 0 ] } + +#-------------------------------------------------------------------- +# resource-apis + +@test "connectInject/ClusterRole: adds permission to mesh.consul.hashicorp.com with resource-apis in global.experiments" { + cd `chart_dir` + local object=$(helm template \ + -s templates/connect-inject-clusterrole.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments={resource-apis}' \ + . | tee /dev/stderr | + yq '.rules[4].apiGroups | index("mesh.consul.hashicorp.com")' | tee /dev/stderr) + [ "${object}" == 0 ] +} \ No newline at end of file diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 2cf32fdc0b..c9f461bd87 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -29,6 +29,7 @@ const ( HTTPRoute string = "httproute" TCPRoute string = "tcproute" ProxyConfiguration string = "proxyconfiguration" + MeshGateway string = "meshgateway" Global string = "global" Mesh string = "mesh" diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go new file mode 100644 index 0000000000..992e16ae47 --- /dev/null +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// MeshGatewayController reconciles a MeshGateway object. +type MeshGatewayController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + MeshConfigController *MeshConfigController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshgateway,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshgateway/status,verbs=get;update;patch + +func (r *MeshGatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // TODO NET-6392 NET-6393 NET-6394 NET-6395 + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.MeshGateway{}) +} + +func (r *MeshGatewayController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *MeshGatewayController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *MeshGatewayController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.MeshGateway{}, r) +} diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index cce5208eda..c4fb15f80f 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -385,6 +385,26 @@ rules: - get - patch - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - meshgateway + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - meshgateway/status + verbs: + - get + - patch + - update - apiGroups: - mesh.consul.hashicorp.com resources: diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index abe4b38c53..ac2f0831a0 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -177,6 +177,15 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.ProxyConfiguration) return err } + if err := (&controllersv2.MeshGatewayController{ + MeshConfigController: meshConfigReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.MeshGateway), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.MeshGateway) + return err + } mgr.GetWebhookServer().CertDir = c.flagCertDir From 7215ec05d0f4ef093de2f4ddc80b385214522e4d Mon Sep 17 00:00:00 2001 From: "Chris S. Kim" Date: Wed, 15 Nov 2023 16:18:50 -0500 Subject: [PATCH 472/592] Update rate limiting acceptance test to burst requests in one exec (#3175) --- .../tests/connect/local_rate_limit_test.go | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/acceptance/tests/connect/local_rate_limit_test.go b/acceptance/tests/connect/local_rate_limit_test.go index a9b0df50f0..715444cf0f 100644 --- a/acceptance/tests/connect/local_rate_limit_test.go +++ b/acceptance/tests/connect/local_rate_limit_test.go @@ -4,23 +4,29 @@ package connect import ( + "fmt" "testing" "time" terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" ) // TestConnectInject_LocalRateLimiting tests that local rate limiting works as expected between services. func TestConnectInject_LocalRateLimiting(t *testing.T) { cfg := suite.Config() + + if !cfg.EnableEnterprise { + t.Skipf("rate limiting is an enterprise only feature. -enable-enterprise must be set to run this test.") + } + ctx := suite.Environment().DefaultContext(t) releaseName := helpers.RandomName() @@ -56,29 +62,35 @@ func TestConnectInject_LocalRateLimiting(t *testing.T) { opts := newRateLimitOptions(t, ctx) - // Ensure that all requests from static-client to static-server succeed (no rate limiting set). - for addr, rps := range rateLimitMap { - opts.rps = rps - assertRateLimits(t, opts, addr) - } + t.Run("without ratelimiting", func(t *testing.T) { + // Ensure that all requests from static-client to static-server succeed (no rate limiting set). + for addr, rps := range rateLimitMap { + opts.rps = rps + assertRateLimits(t, opts, addr) + } + }) // Apply local rate limiting to the static-server writeCrd(t, connHelper, "../fixtures/cases/local-rate-limiting") - // Ensure that going over the limit causes the static-server to apply rate limiting and - // reply with 429 Too Many Requests - opts.enforced = true - for addr, reqPerSec := range rateLimitMap { - opts.rps = reqPerSec - assertRateLimits(t, opts, addr) - } + t.Run("with ratelimiting", func(t *testing.T) { + // Ensure that going over the limit causes the static-server to apply rate limiting and + // reply with 429 Too Many Requests + opts.enforced = true + for addr, reqPerSec := range rateLimitMap { + opts.rps = reqPerSec + assertRateLimits(t, opts, addr) + } + }) } -func assertRateLimits(t *testing.T, opts *assertRateLimitOptions, curlArgs ...string) { +func assertRateLimits(t *testing.T, opts *assertRateLimitOptions, addr string) { t.Helper() - args := []string{"exec", opts.resourceType + opts.sourceApp, "-c", opts.sourceApp, "--", "curl", opts.curlOpts} - args = append(args, curlArgs...) + // curl can glob URLs to make requests to a range of addresses. + // We append a number as a query param since it will be ignored by + // the rate limit path matcher. + repeatAddr := fmt.Sprintf("%s?[1-%d]", addr, opts.rps) // This check is time sensitive due to the nature of rate limiting. // Run the entire assertion in a retry block and on each pass: @@ -89,22 +101,21 @@ func assertRateLimits(t *testing.T, opts *assertRateLimitOptions, curlArgs ...st retry.RunWith(opts.retryTimer, t, func(r *retry.R) { // Make up to the allowed numbers of calls in a second t0 := time.Now() - for i := 0; i < opts.rps; i++ { - output, err := k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, args...) - require.NoError(r, err) - require.Contains(r, output, opts.successOutput) - } + + output, err := k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, append(args, repeatAddr)...) + require.NoError(r, err) + require.Contains(r, output, opts.successOutput) // Exceed the configured rate limit. - output, err := k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, args...) + output, err = k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, append(args, addr)...) + require.True(r, time.Since(t0) < time.Second, "failed to make all requests within one second window") if opts.enforced { require.Error(r, err) require.Contains(r, output, opts.rateLimitOutput, "request was not rate limited") } else { require.NoError(r, err) - require.Contains(r, output, opts.successOutput, "request was not successful") + require.NotContains(r, output, opts.rateLimitOutput, "request was not successful") } - require.True(r, time.Since(t0) < time.Second, "failed to make all requests within one second window") }) } @@ -128,6 +139,6 @@ func newRateLimitOptions(t *testing.T, ctx environment.TestContext) *assertRateL k8sOpts: ctx.KubectlOptions(t), sourceApp: connhelper.StaticClientName, retryTimer: &retry.Timer{Timeout: 120 * time.Second, Wait: 2 * time.Second}, - curlOpts: "-vvvsSf", + curlOpts: "-f", } } From 2509cc8e2a2e8a3732b9efafc6267891b809d489 Mon Sep 17 00:00:00 2001 From: Ashesh Vidyut <134911583+absolutelightning@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:03:37 +0530 Subject: [PATCH 473/592] Add -output-format json to consul-k8s proxy list command (#3221) * Add -o json to consul-k8s proxy list command * added changelog * dummy commit to trigger ci * fix tests --- .changelog/3221.txt | 3 ++ cli/cmd/proxy/list/command.go | 24 ++++++++- cli/cmd/proxy/list/command_test.go | 79 ++++++++++++++++++++++++++++++ cli/common/terminal/table.go | 15 ++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 .changelog/3221.txt diff --git a/.changelog/3221.txt b/.changelog/3221.txt new file mode 100644 index 0000000000..953bde146b --- /dev/null +++ b/.changelog/3221.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Add -o json (-output-format json) to `consul-k8s proxy list` command that returns the result in json format. +``` diff --git a/cli/cmd/proxy/list/command.go b/cli/cmd/proxy/list/command.go index f427049de6..7159b448b8 100644 --- a/cli/cmd/proxy/list/command.go +++ b/cli/cmd/proxy/list/command.go @@ -4,6 +4,7 @@ package list import ( + "encoding/json" "errors" "fmt" "strings" @@ -25,6 +26,7 @@ const ( flagNameAllNamespaces = "all-namespaces" flagNameKubeConfig = "kubeconfig" flagNameKubeContext = "context" + flagOutputFormat = "output-format" ) // ListCommand is the command struct for the proxy list command. @@ -37,6 +39,7 @@ type ListCommand struct { flagNamespace string flagAllNamespaces bool + flagOutputFormat string flagKubeConfig string flagKubeContext string @@ -63,6 +66,13 @@ func (c *ListCommand) init() { Usage: "List pods in all namespaces.", Aliases: []string{"A"}, }) + f.StringVar(&flag.StringVar{ + Name: flagOutputFormat, + Default: "table", + Target: &c.flagOutputFormat, + Usage: "Output format", + Aliases: []string{"o"}, + }) f = c.set.NewSet("Global Options") f.StringVar(&flag.StringVar{ @@ -137,6 +147,7 @@ func (c *ListCommand) AutocompleteFlags() complete.Flags { fmt.Sprintf("-%s", flagNameAllNamespaces): complete.PredictNothing, fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, + fmt.Sprintf("-%s", flagOutputFormat): complete.PredictNothing, } } @@ -282,5 +293,16 @@ func (c *ListCommand) output(pods []v1.Pod) { } } - c.UI.Table(tbl) + if c.flagOutputFormat == "json" { + tableJson := tbl.ToJson() + jsonSt, err := json.MarshalIndent(tableJson, "", " ") + if err != nil { + c.UI.Output("Error converting table to json: %v", err.Error(), terminal.WithErrorStyle()) + } else { + c.UI.Output(string(jsonSt)) + } + } else { + c.UI.Table(tbl) + } + } diff --git a/cli/cmd/proxy/list/command_test.go b/cli/cmd/proxy/list/command_test.go index 3df205d3ca..9e7104886d 100644 --- a/cli/cmd/proxy/list/command_test.go +++ b/cli/cmd/proxy/list/command_test.go @@ -319,6 +319,85 @@ func TestListCommandOutput(t *testing.T) { } } +func TestListCommandOutputInJsonFormat(t *testing.T) { + // These regular expressions must be present in the output. + expected := ".*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.*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{ + Name: "ingress-gateway", + Namespace: "default", + Labels: map[string]string{ + "component": "ingress-gateway", + "chart": "consul-helm", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "mesh-gateway", + Namespace: "consul", + Labels: map[string]string{ + "component": "mesh-gateway", + "chart": "consul-helm", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "terminating-gateway", + Namespace: "consul", + Labels: map[string]string{ + "component": "terminating-gateway", + "chart": "consul-helm", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "api-gateway", + Namespace: "consul", + Labels: map[string]string{ + "api-gateway.consul.hashicorp.com/managed": "true", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "dont-fetch", + Namespace: "default", + Labels: map[string]string{}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Labels: map[string]string{ + "consul.hashicorp.com/connect-inject-status": "injected", + }, + }, + }, + } + client := fake.NewSimpleClientset(&v1.PodList{Items: pods}) + + buf := new(bytes.Buffer) + c := setupCommand(buf) + c.kubernetes = client + + 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) + } +} + func TestNoPodsFound(t *testing.T) { cases := map[string]struct { args []string diff --git a/cli/common/terminal/table.go b/cli/common/terminal/table.go index 7768a48bce..553b4e85f3 100644 --- a/cli/common/terminal/table.go +++ b/cli/common/terminal/table.go @@ -59,6 +59,21 @@ func (t *Table) AddRow(cols []string, colors []string) { t.Rows = append(t.Rows, row) } +func (t *Table) ToJson() []map[string]interface{} { + if t == nil { + return make([]map[string]interface{}, 0) + } + jsonRes := make([]map[string]interface{}, 0) + for _, row := range t.Rows { + jsonRow := make(map[string]interface{}) + for i, ent := range row { + jsonRow[t.Headers[i]] = ent.Value + } + jsonRes = append(jsonRes, jsonRow) + } + return jsonRes +} + // Table implements UI. func (u *basicUI) Table(tbl *Table, opts ...Option) { // Build our config and set our options From f2ccd72e293d7d87c717ddc847bbc65fed3a826e Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 17 Nov 2023 15:40:32 -0500 Subject: [PATCH 474/592] Remove nightly and weekly jobs (#3231) remove nightly and weekly jobs --- .../workflows/nightly-acceptance-1-3-0.yml | 26 ----------------- .github/workflows/weekly-acceptance-1-0-x.yml | 29 ------------------- 2 files changed, 55 deletions(-) delete mode 100644 .github/workflows/nightly-acceptance-1-3-0.yml delete mode 100644 .github/workflows/weekly-acceptance-1-0-x.yml diff --git a/.github/workflows/nightly-acceptance-1-3-0.yml b/.github/workflows/nightly-acceptance-1-3-0.yml deleted file mode 100644 index e7681a6835..0000000000 --- a/.github/workflows/nightly-acceptance-1-3-0.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Dispatch to the consul-k8s-workflows with a nightly cron -name: nightly-acceptance-1-3-0 -on: - schedule: - # * is a special character in YAML so you have to quote this string - # Run nightly at 12AM UTC/8PM EST/5PM PST - - cron: '0 0 * * *' - -# these should be the only settings that you will ever need to change -env: - BRANCH: "release/1.3.0" - CONTEXT: "nightly" - -jobs: - cloud: - name: cloud - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 - 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/.github/workflows/weekly-acceptance-1-0-x.yml b/.github/workflows/weekly-acceptance-1-0-x.yml deleted file mode 100644 index 11dda52bed..0000000000 --- a/.github/workflows/weekly-acceptance-1-0-x.yml +++ /dev/null @@ -1,29 +0,0 @@ -# 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-0-x -on: - schedule: - # * is a special character in YAML so you have to quote this string - # Run weekly on Tuesday at 3AM UTC/11PM EST/8PM PST - - cron: '0 3 * * 2' - - -# these should be the only settings that you will ever need to change -env: - BRANCH: "release/1.0.x" - CONTEXT: "weekly" - -jobs: - cloud: - name: cloud - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 - 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 }}" }' From 183c1e20a8ec8d40ca518cb5a61d06aa4aa4425a Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Mon, 20 Nov 2023 10:38:42 -0500 Subject: [PATCH 475/592] NET-6563 Stub CRUD hooks for MeshGatewayController (#3213) * Stub createUpdate and delete handlers in MeshGatewayController * Add unit test coverage * Fix import blocking * Move TODOs inside CRUD hooks * Specify scheme when initializing controller for tests --- .../api/mesh/v2beta1/mesh_gateway_types.go | 7 +- .../controllersv2/mesh_gateway_controller.go | 37 +++++- .../mesh_gateway_controller_test.go | 110 ++++++++++++++++++ 3 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go diff --git a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go index 59fe712768..cf23707a16 100644 --- a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go +++ b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go @@ -7,14 +7,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "google.golang.org/protobuf/testing/protocmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) const ( diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index 992e16ae47..be878e9160 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -5,8 +5,10 @@ package controllersv2 import ( "context" + "errors" "github.com/go-logr/logr" + k8serr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" @@ -27,7 +29,30 @@ type MeshGatewayController struct { // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshgateway/status,verbs=get;update;patch func (r *MeshGatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - // TODO NET-6392 NET-6393 NET-6394 NET-6395 + logger := r.Logger(req.NamespacedName) + + // Fetch the resource being reconciled + resource := &meshv2beta1.MeshGateway{} + if err := r.Get(ctx, req.NamespacedName, resource); k8serr.IsNotFound(err) { + return ctrl.Result{}, client.IgnoreNotFound(err) + } else if err != nil { + logger.Error(err, "retrieving resource") + return ctrl.Result{}, err + } + + // Call hooks + if !resource.GetDeletionTimestamp().IsZero() { + logger.Info("deletion event") + + if err := r.onDelete(ctx, req, resource); err != nil { + return ctrl.Result{}, err + } + } else { + if err := r.onCreateUpdate(ctx, req, resource); err != nil { + return ctrl.Result{}, err + } + } + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.MeshGateway{}) } @@ -42,3 +67,13 @@ func (r *MeshGatewayController) UpdateStatus(ctx context.Context, obj client.Obj func (r *MeshGatewayController) SetupWithManager(mgr ctrl.Manager) error { return setupWithManager(mgr, &meshv2beta1.MeshGateway{}, r) } + +func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { + // TODO NET-6392 NET-6393 NET-6394 NET-6395 + return errors.New("onCreateUpdate not implemented") +} + +func (r *MeshGatewayController) onDelete(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { + // TODO NET-6392 NET-6393 NET-6394 NET-6395 + return errors.New("onDelete not implemented") +} diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go new file mode 100644 index 0000000000..9a2b014dcb --- /dev/null +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -0,0 +1,110 @@ +package controllersv2 + +import ( + "context" + "errors" + "testing" + "time" + + logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +func TestMeshGatewayController_Reconcile(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + // k8sObjects is the list of Kubernetes resources that will be present in the cluster at runtime + k8sObjects []runtime.Object + // request is the request that will be provided to MeshGatewayController.Reconcile + request ctrl.Request + // expectedErr is the error we expect MeshGatewayController.Reconcile to return + expectedErr error + // expectedResult is the result we expect MeshGatewayController.Reconcile to return + expectedResult ctrl.Result + }{ + { + name: "onCreateUpdate", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedErr: errors.New("onCreateUpdate not implemented"), + expectedResult: ctrl.Result{}, + }, + { + name: "onDelete", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + DeletionTimestamp: common.PointerTo(metav1.NewTime(time.Now())), + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedErr: errors.New("onDelete not implemented"), + expectedResult: ctrl.Result{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + consulClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + + s := runtime.NewScheme() + s.AddKnownTypes(v2beta1.MeshGroupVersion, &v2beta1.MeshGateway{}) + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(testCase.k8sObjects...). + Build() + + controller := MeshGatewayController{ + Client: fakeClient, + Log: logrtest.New(t), + Scheme: s, + MeshConfigController: &MeshConfigController{ + ConsulClientConfig: consulClient.Cfg, + ConsulServerConnMgr: consulClient.Watcher, + }, + } + + res, err := controller.Reconcile(context.Background(), testCase.request) + if testCase.expectedErr != nil { + require.EqualError(t, err, testCase.expectedErr.Error()) + } else { + require.NoError(t, err) + } + assert.Equal(t, testCase.expectedResult, res) + }) + } +} From 3b15385edc248b8dad4a99125f14afb42aad43e8 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 20 Nov 2023 14:59:13 -0500 Subject: [PATCH 476/592] Add Mesh GatewayClass CRD (#3224) * Add gateway_class_types.go * Update dependencies so that CRDs can be added * Generate the CRD --- .../consul/templates/crd-gatewayclasses.yaml | 128 +++++++++++++++ .../api/mesh/v2beta1/gateway_class_types.go | 150 ++++++++++++++++++ .../api/mesh/v2beta1/zz_generated.deepcopy.go | 63 ++++++++ ...h.consul.hashicorp.com_gatewayclasses.yaml | 123 ++++++++++++++ control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- 6 files changed, 467 insertions(+), 3 deletions(-) create mode 100644 charts/consul/templates/crd-gatewayclasses.yaml create mode 100644 control-plane/api/mesh/v2beta1/gateway_class_types.go create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml diff --git a/charts/consul/templates/crd-gatewayclasses.yaml b/charts/consul/templates/crd-gatewayclasses.yaml new file mode 100644 index 0000000000..7ccdd524ed --- /dev/null +++ b/charts/consul/templates/crd-gatewayclasses.yaml @@ -0,0 +1,128 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + name: gatewayclasses.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: GatewayClass is the Schema for the Gateway Class API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: "NOTE: this should align to the GAMMA/gateway-api version, + or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayClass + \n This is a Resource type." + properties: + controllerName: + description: ControllerName is the name of the Kubernetes controller + that manages Gateways of this class + type: string + description: + description: Description of GatewayClass + type: string + parametersRef: + description: ParametersRef refers to a resource responsible for configuring + the behavior of the GatewayClass. + properties: + group: + description: The Kubernetes Group that the referred object belongs + to + type: string + kind: + description: The Kubernetes Kind that the referred object is + type: string + name: + description: The name of the referred object + type: string + namespace: + description: The kubernetes namespace that the referred object + is in + type: string + type: object + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/control-plane/api/mesh/v2beta1/gateway_class_types.go b/control-plane/api/mesh/v2beta1/gateway_class_types.go new file mode 100644 index 0000000000..6fc42ffed5 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/gateway_class_types.go @@ -0,0 +1,150 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + gatewayClassKubeKind = "gatewayclass" +) + +func init() { + MeshSchemeBuilder.Register(&GatewayClass{}, &GatewayClassList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// GatewayClass is the Schema for the Gateway Class API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:scope=Cluster +type GatewayClass struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.GatewayClass `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// GatewayClassList contains a list of GatewayClass. +type GatewayClassList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*GatewayClass `json:"items"` +} + +func (in *GatewayClass) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.GatewayClassType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *GatewayClass) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *GatewayClass) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *GatewayClass) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *GatewayClass) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *GatewayClass) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *GatewayClass) KubeKind() string { + return gatewayClassKubeKind +} + +func (in *GatewayClass) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *GatewayClass) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *GatewayClass) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *GatewayClass) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *GatewayClass) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *GatewayClass) Validate(tenancy common.ConsulTenancyConfig) error { + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *GatewayClass) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index 9acfd073ef..ec08e39d0f 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -109,6 +109,69 @@ func (in *GRPCRouteList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClass) DeepCopyInto(out *GatewayClass) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClass. +func (in *GatewayClass) DeepCopy() *GatewayClass { + if in == nil { + return nil + } + out := new(GatewayClass) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayClass) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassList) DeepCopyInto(out *GatewayClassList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*GatewayClass, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(GatewayClass) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassList. +func (in *GatewayClassList) DeepCopy() *GatewayClassList { + if in == nil { + return nil + } + out := new(GatewayClassList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayClassList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { *out = *in diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml new file mode 100644 index 0000000000..1da2d613f4 --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml @@ -0,0 +1,123 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: gatewayclasses.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: GatewayClass is the Schema for the Gateway Class API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: "NOTE: this should align to the GAMMA/gateway-api version, + or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayClass + \n This is a Resource type." + properties: + controllerName: + description: ControllerName is the name of the Kubernetes controller + that manages Gateways of this class + type: string + description: + description: Description of GatewayClass + type: string + parametersRef: + description: ParametersRef refers to a resource responsible for configuring + the behavior of the GatewayClass. + properties: + group: + description: The Kubernetes Group that the referred object belongs + to + type: string + kind: + description: The Kubernetes Kind that the referred object is + type: string + name: + description: The name of the referred object + type: string + namespace: + description: The kubernetes namespace that the referred object + is in + type: string + type: object + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/go.mod b/control-plane/go.mod index eb84c72225..2d6c30ca2f 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231109213314-40c57f10a0fb +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231116180314-d9432f903222 require ( github.com/cenkalti/backoff v2.2.1+incompatible diff --git a/control-plane/go.sum b/control-plane/go.sum index 40c39844ff..4486a92196 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231109213314-40c57f10a0fb h1:Vy9tVDskUrWMXCyMJHpChxRjzJVjWSsSZ457X1dZAWo= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231109213314-40c57f10a0fb/go.mod h1:SayEhfXS3DQDnW/vKSZXvkwDObg7XK60KTfrJcp0wrg= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231116180314-d9432f903222 h1:18lPEG4hGUdGRuYN5Acbv5hDgiIxr9hPs91Ps0SEHSE= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231116180314-d9432f903222/go.mod h1:SayEhfXS3DQDnW/vKSZXvkwDObg7XK60KTfrJcp0wrg= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= From 7363d571aa619866149bcfab25eaaaa81195e744 Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Mon, 20 Nov 2023 15:16:46 -0500 Subject: [PATCH 477/592] feat: add named prom port to dataplane sidecar (#3222) * feat: add named prom port to dataplane sidecar Co-authored-by: Hamish * PR Feedback --------- Co-authored-by: Hamish --- .changelog/3222.txt | 3 + .../controllers/pod/pod_controller.go | 4 +- .../webhook/consul_dataplane_sidecar.go | 43 +++++ .../webhook/consul_dataplane_sidecar_test.go | 56 +++++- .../webhookv2/consul_dataplane_sidecar.go | 56 +++++- .../consul_dataplane_sidecar_test.go | 173 ++++++++++++++++++ 6 files changed, 327 insertions(+), 8 deletions(-) create mode 100644 .changelog/3222.txt diff --git a/.changelog/3222.txt b/.changelog/3222.txt new file mode 100644 index 0000000000..9347bd077a --- /dev/null +++ b/.changelog/3222.txt @@ -0,0 +1,3 @@ +```release-note:feature +control-plane: adds a named port, `prometheus`, to the `consul-dataplane` sidecar for use with [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#podmetricsendpoint). +``` diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index c4b867f8c3..4cf7580d5c 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -498,8 +498,8 @@ func (r *Controller) getBootstrapConfig(pod corev1.Pod) (*pbmesh.BootstrapConfig bootstrap := &pbmesh.BootstrapConfig{} // If metrics are enabled, the BootstrapConfig should set envoy_prometheus_bind_addr to a listener on 0.0.0.0 on - // the PrometheusScrapePort that points to a metrics backend. The backend for this listener will be determined by - // the envoy bootstrapping command (consul connect envoy) or the consul-dataplane GetBoostrapParams rpc. + // the PrometheusScrapePort. The backend for this listener will be determined by + // the consul-dataplane command line flags generated by the webhook. // If there is a merged metrics server, the backend would be that server. // If we are not running the merged metrics server, the backend should just be the Envoy metrics endpoint. enableMetrics, err := r.MetricsConfig.EnableMetrics(pod) diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index 05a67c3736..03f47a8e84 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -162,6 +162,15 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor container.VolumeMounts = append(container.VolumeMounts, volumeMounts...) } + // Container Ports + metricsPorts, err := w.getMetricsPorts(pod) + if err != nil { + return corev1.Container{}, err + } + if metricsPorts != nil { + container.Ports = append(container.Ports, metricsPorts...) + } + tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) if err != nil { return corev1.Container{}, err @@ -500,3 +509,37 @@ func useProxyHealthCheck(pod corev1.Pod) bool { } return false } + +// getMetricsPorts creates container ports for exposing services such as prometheus. +// Prometheus in particular needs a named port for use with the operator. +// https://github.com/hashicorp/consul-k8s/pull/1440 +func (w *MeshWebhook) getMetricsPorts(pod corev1.Pod) ([]corev1.ContainerPort, error) { + enableMetrics, err := w.MetricsConfig.EnableMetrics(pod) + if err != nil { + return nil, fmt.Errorf("error determining if metrics are enabled: %w", err) + } + if !enableMetrics { + return nil, nil + } + + prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(pod) + if err != nil { + return nil, fmt.Errorf("error parsing prometheus port from pod: %w", err) + } + if prometheusScrapePort == "" { + return nil, nil + } + + port, err := strconv.Atoi(prometheusScrapePort) + if err != nil { + return nil, fmt.Errorf("error parsing prometheus port from pod: %w", err) + } + + return []corev1.ContainerPort{ + { + Name: "prometheus", + ContainerPort: int32(port), + Protocol: corev1.ProtocolTCP, + }, + }, nil +} diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index f89703d5ad..4261887b12 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -9,15 +9,17 @@ import ( "strings" "testing" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" ) const nodeName = "test-node" @@ -1187,6 +1189,7 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { name string pod corev1.Pod expCmdArgs string + expPorts []corev1.ContainerPort expErr string }{ { @@ -1209,6 +1212,37 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { }, }, expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20100 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics", + expPorts: []corev1.ContainerPort{ + { + Name: "prometheus", + ContainerPort: 20200, + Protocol: corev1.ProtocolTCP, + }, + }, + }, + { + name: "metrics with prometheus port override", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "web", + constants.AnnotationEnableMetrics: "true", + constants.AnnotationEnableMetricsMerging: "true", + constants.AnnotationMergedMetricsPort: "20123", + constants.AnnotationPort: "1234", + constants.AnnotationPrometheusScrapePath: "/scrape-path", + constants.AnnotationPrometheusScrapePort: "6789", + }, + }, + }, + expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20123 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics", + expPorts: []corev1.ContainerPort{ + { + Name: "prometheus", + ContainerPort: 6789, + Protocol: corev1.ProtocolTCP, + }, + }, }, { name: "merged metrics with TLS enabled", @@ -1229,6 +1263,13 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { }, }, expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20100 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics -telemetry-prom-ca-certs-file=/certs/ca.crt -telemetry-prom-ca-certs-path=/certs/ca -telemetry-prom-cert-file=/certs/server.crt -telemetry-prom-key-file=/certs/key.pem", + expPorts: []corev1.ContainerPort{ + { + Name: "prometheus", + ContainerPort: 20200, + Protocol: corev1.ProtocolTCP, + }, + }, }, { name: "merge metrics with TLS enabled, missing CA gives an error", @@ -1293,6 +1334,12 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { t.Run(c.name, func(t *testing.T) { h := MeshWebhook{ ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + MetricsConfig: metrics.Config{ + // These are all the default values passed from the CLI + DefaultPrometheusScrapePort: "20200", + DefaultPrometheusScrapePath: "/metrics", + DefaultMergedMetricsPort: "20100", + }, } container, err := h.consulDataplaneSidecar(testNS, c.pod, multiPortInfo{}) if c.expErr != "" { @@ -1301,6 +1348,9 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { } else { require.NoError(t, err) require.Contains(t, strings.Join(container.Args, " "), c.expCmdArgs) + if c.expPorts != nil { + require.ElementsMatch(t, container.Ports, c.expPorts) + } } }) } diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go index f8a8406d23..434899d67e 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go @@ -159,6 +159,15 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor container.VolumeMounts = append(container.VolumeMounts, volumeMounts...) } + // Container Ports + metricsPorts, err := w.getMetricsPorts(pod) + if err != nil { + return corev1.Container{}, err + } + if metricsPorts != nil { + container.Ports = append(container.Ports, metricsPorts...) + } + tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) if err != nil { return corev1.Container{}, err @@ -302,15 +311,22 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, bearer return nil, fmt.Errorf("unable to determine if merged metrics is enabled: %w", err) } if metricsServer { - - // (TODO) Figure out what port will be used for merged metrics and setup merged metrics - mergedMetricsPort, err := w.MetricsConfig.MergedMetricsPort(pod) if err != nil { return nil, fmt.Errorf("unable to determine if merged metrics port: %w", err) } args = append(args, "-telemetry-prom-merge-port="+mergedMetricsPort) + serviceMetricsPath := w.MetricsConfig.ServiceMetricsPath(pod) + serviceMetricsPort, err := w.MetricsConfig.ServiceMetricsPort(pod) + if err != nil { + return nil, fmt.Errorf("unable to determine if service metrics port: %w", err) + } + + if serviceMetricsPath != "" && serviceMetricsPort != "" { + args = append(args, "-telemetry-prom-service-metrics-url="+fmt.Sprintf("http://127.0.0.1:%s%s", serviceMetricsPort, serviceMetricsPath)) + } + // Pull the TLS config from the relevant annotations. var prometheusCAFile string if raw, ok := pod.Annotations[constants.AnnotationPrometheusCAFile]; ok && raw != "" { @@ -470,3 +486,37 @@ func useProxyHealthCheck(pod corev1.Pod) bool { } return false } + +// getMetricsPorts creates container ports for exposing services such as prometheus. +// Prometheus in particular needs a named port for use with the operator. +// https://github.com/hashicorp/consul-k8s/pull/1440 +func (w *MeshWebhook) getMetricsPorts(pod corev1.Pod) ([]corev1.ContainerPort, error) { + enableMetrics, err := w.MetricsConfig.EnableMetrics(pod) + if err != nil { + return nil, fmt.Errorf("error determining if metrics are enabled: %w", err) + } + if !enableMetrics { + return nil, nil + } + + prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(pod) + if err != nil { + return nil, fmt.Errorf("error parsing prometheus port from pod: %w", err) + } + if prometheusScrapePort == "" { + return nil, nil + } + + port, err := strconv.Atoi(prometheusScrapePort) + if err != nil { + return nil, fmt.Errorf("error parsing prometheus port from pod: %w", err) + } + + return []corev1.ContainerPort{ + { + Name: "prometheus", + ContainerPort: int32(port), + Protocol: corev1.ProtocolTCP, + }, + }, nil +} diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go index 81b3520511..994bf4e446 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/consul" ) @@ -945,6 +946,178 @@ func TestHandlerConsulDataplaneSidecar_Resources(t *testing.T) { } } +func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { + cases := []struct { + name string + pod corev1.Pod + expCmdArgs string + expPorts []corev1.ContainerPort + expErr string + }{ + { + name: "default", + pod: corev1.Pod{}, + expCmdArgs: "", + }, + { + name: "turning on merged metrics", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "web", + constants.AnnotationEnableMetrics: "true", + constants.AnnotationEnableMetricsMerging: "true", + constants.AnnotationMergedMetricsPort: "20100", + constants.AnnotationPort: "1234", + constants.AnnotationPrometheusScrapePath: "/scrape-path", + }, + }, + }, + expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20100 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics", + expPorts: []corev1.ContainerPort{ + { + Name: "prometheus", + ContainerPort: 20200, + Protocol: corev1.ProtocolTCP, + }, + }, + }, + { + name: "metrics with prometheus port override", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "web", + constants.AnnotationEnableMetrics: "true", + constants.AnnotationEnableMetricsMerging: "true", + constants.AnnotationMergedMetricsPort: "20123", + constants.AnnotationPort: "1234", + constants.AnnotationPrometheusScrapePath: "/scrape-path", + constants.AnnotationPrometheusScrapePort: "6789", + }, + }, + }, + expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20123 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics", + expPorts: []corev1.ContainerPort{ + { + Name: "prometheus", + ContainerPort: 6789, + Protocol: corev1.ProtocolTCP, + }, + }, + }, + { + name: "merged metrics with TLS enabled", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "web", + constants.AnnotationEnableMetrics: "true", + constants.AnnotationEnableMetricsMerging: "true", + constants.AnnotationMergedMetricsPort: "20100", + constants.AnnotationPort: "1234", + constants.AnnotationPrometheusScrapePath: "/scrape-path", + constants.AnnotationPrometheusCAFile: "/certs/ca.crt", + constants.AnnotationPrometheusCAPath: "/certs/ca", + constants.AnnotationPrometheusCertFile: "/certs/server.crt", + constants.AnnotationPrometheusKeyFile: "/certs/key.pem", + }, + }, + }, + expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20100 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics -telemetry-prom-ca-certs-file=/certs/ca.crt -telemetry-prom-ca-certs-path=/certs/ca -telemetry-prom-cert-file=/certs/server.crt -telemetry-prom-key-file=/certs/key.pem", + expPorts: []corev1.ContainerPort{ + { + Name: "prometheus", + ContainerPort: 20200, + Protocol: corev1.ProtocolTCP, + }, + }, + }, + { + name: "merge metrics with TLS enabled, missing CA gives an error", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "web", + constants.AnnotationEnableMetrics: "true", + constants.AnnotationEnableMetricsMerging: "true", + constants.AnnotationMergedMetricsPort: "20100", + constants.AnnotationPort: "1234", + constants.AnnotationPrometheusScrapePath: "/scrape-path", + constants.AnnotationPrometheusCertFile: "/certs/server.crt", + constants.AnnotationPrometheusKeyFile: "/certs/key.pem", + }, + }, + }, + expCmdArgs: "", + expErr: fmt.Sprintf("must set one of %q or %q when providing prometheus TLS config", constants.AnnotationPrometheusCAFile, constants.AnnotationPrometheusCAPath), + }, + { + name: "merge metrics with TLS enabled, missing cert gives an error", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "web", + constants.AnnotationEnableMetrics: "true", + constants.AnnotationEnableMetricsMerging: "true", + constants.AnnotationMergedMetricsPort: "20100", + constants.AnnotationPort: "1234", + constants.AnnotationPrometheusScrapePath: "/scrape-path", + constants.AnnotationPrometheusCAFile: "/certs/ca.crt", + constants.AnnotationPrometheusKeyFile: "/certs/key.pem", + }, + }, + }, + expCmdArgs: "", + expErr: fmt.Sprintf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusCertFile), + }, + { + name: "merge metrics with TLS enabled, missing key file gives an error", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationService: "web", + constants.AnnotationEnableMetrics: "true", + constants.AnnotationEnableMetricsMerging: "true", + constants.AnnotationMergedMetricsPort: "20100", + constants.AnnotationPort: "1234", + constants.AnnotationPrometheusScrapePath: "/scrape-path", + constants.AnnotationPrometheusCAPath: "/certs/ca", + constants.AnnotationPrometheusCertFile: "/certs/server.crt", + }, + }, + }, + expCmdArgs: "", + expErr: fmt.Sprintf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusKeyFile), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + h := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + MetricsConfig: metrics.Config{ + // These are all the default values passed from the CLI + DefaultPrometheusScrapePort: "20200", + DefaultPrometheusScrapePath: "/metrics", + DefaultMergedMetricsPort: "20100", + }, + } + container, err := h.consulDataplaneSidecar(testNS, c.pod) + if c.expErr != "" { + require.NotNil(t, err) + require.Contains(t, err.Error(), c.expErr) + } else { + require.NoError(t, err) + require.Contains(t, strings.Join(container.Args, " "), c.expCmdArgs) + if c.expPorts != nil { + require.ElementsMatch(t, container.Ports, c.expPorts) + } + } + }) + } +} + func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { gracefulShutdownSeconds := 10 gracefulPort := "20307" From b0568b0f140c9bb22738b30886af91889e61a69e Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 20 Nov 2023 16:43:54 -0500 Subject: [PATCH 478/592] Add CRD for MeshConfiguration (#3216) * Update the Consul package dependency to my branch * Add the stubbed gateway_configuration * Update proto-public dependency * Add deepcopy * Run make ctrl-manifests * Fix comment on MeshConfig struct --- .../templates/crd-meshconfigurations.yaml | 100 ++++++++++++ .../mesh/v2beta1/mesh_configuration_types.go | 150 ++++++++++++++++++ .../api/mesh/v2beta1/zz_generated.deepcopy.go | 63 ++++++++ ...nsul.hashicorp.com_meshconfigurations.yaml | 95 +++++++++++ control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- 6 files changed, 411 insertions(+), 3 deletions(-) create mode 100644 charts/consul/templates/crd-meshconfigurations.yaml create mode 100644 control-plane/api/mesh/v2beta1/mesh_configuration_types.go create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshconfigurations.yaml diff --git a/charts/consul/templates/crd-meshconfigurations.yaml b/charts/consul/templates/crd-meshconfigurations.yaml new file mode 100644 index 0000000000..21114d723f --- /dev/null +++ b/charts/consul/templates/crd-meshconfigurations.yaml @@ -0,0 +1,100 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + name: meshconfigurations.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: MeshConfiguration + listKind: MeshConfigurationList + plural: meshconfigurations + singular: meshconfiguration + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: MeshConfiguration is the Schema for the Mesh Configuration + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MeshConfiguration is responsible for configuring the default + behavior of Mesh Gateways. This is a Resource type. + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/control-plane/api/mesh/v2beta1/mesh_configuration_types.go b/control-plane/api/mesh/v2beta1/mesh_configuration_types.go new file mode 100644 index 0000000000..7ffba4f6d8 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/mesh_configuration_types.go @@ -0,0 +1,150 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + meshConfigurationKind = "meshconfiguration" +) + +func init() { + MeshSchemeBuilder.Register(&MeshConfiguration{}, &MeshConfigurationList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// MeshConfiguration is the Schema for the Mesh Configuration +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:scope=Cluster +type MeshConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.MeshConfiguration `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// MeshConfigurationList contains a list of MeshConfiguration. +type MeshConfigurationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*MeshConfiguration `json:"items"` +} + +func (in *MeshConfiguration) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.MeshConfigurationType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *MeshConfiguration) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *MeshConfiguration) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *MeshConfiguration) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *MeshConfiguration) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *MeshConfiguration) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *MeshConfiguration) KubeKind() string { + return meshConfigurationKind +} + +func (in *MeshConfiguration) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *MeshConfiguration) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *MeshConfiguration) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *MeshConfiguration) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *MeshConfiguration) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *MeshConfiguration) Validate(tenancy common.ConsulTenancyConfig) error { + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *MeshConfiguration) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index ec08e39d0f..08a346d6c3 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -235,6 +235,69 @@ func (in *HTTPRouteList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MeshConfiguration) DeepCopyInto(out *MeshConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshConfiguration. +func (in *MeshConfiguration) DeepCopy() *MeshConfiguration { + if in == nil { + return nil + } + out := new(MeshConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MeshConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MeshConfigurationList) DeepCopyInto(out *MeshConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*MeshConfiguration, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(MeshConfiguration) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshConfigurationList. +func (in *MeshConfigurationList) DeepCopy() *MeshConfigurationList { + if in == nil { + return nil + } + out := new(MeshConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MeshConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MeshGateway) DeepCopyInto(out *MeshGateway) { *out = *in diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshconfigurations.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshconfigurations.yaml new file mode 100644 index 0000000000..eb044ecb6c --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshconfigurations.yaml @@ -0,0 +1,95 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: meshconfigurations.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: MeshConfiguration + listKind: MeshConfigurationList + plural: meshconfigurations + singular: meshconfiguration + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: MeshConfiguration is the Schema for the Mesh Configuration + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MeshConfiguration is responsible for configuring the default + behavior of Mesh Gateways. This is a Resource type. + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/go.mod b/control-plane/go.mod index 2d6c30ca2f..3cd438fa71 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231116180314-d9432f903222 +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231120165139-d7323ca22c04 require ( github.com/cenkalti/backoff v2.2.1+incompatible diff --git a/control-plane/go.sum b/control-plane/go.sum index 4486a92196..5d2c027152 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231116180314-d9432f903222 h1:18lPEG4hGUdGRuYN5Acbv5hDgiIxr9hPs91Ps0SEHSE= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231116180314-d9432f903222/go.mod h1:SayEhfXS3DQDnW/vKSZXvkwDObg7XK60KTfrJcp0wrg= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231120165139-d7323ca22c04 h1:QPeZ2ek6gm3Rcs8d0CGKGYIJRutgqFOigyvQSybFE68= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231120165139-d7323ca22c04/go.mod h1:SayEhfXS3DQDnW/vKSZXvkwDObg7XK60KTfrJcp0wrg= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= From cc0c3d2ca52e72053003e581dff7b1b230df6e61 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Tue, 21 Nov 2023 10:14:01 -0600 Subject: [PATCH 479/592] Net 6529 Gatewayclassconfig CRD (#3225) * CRD file gneration * update to cluster scope * cluster scope --- .../v2beta1/gateway_class_config_types.go | 152 +++++++++++++++++ .../api/mesh/v2beta1/zz_generated.deepcopy.go | 63 +++++++ ...sul.hashicorp.com_gatewayclassconfigs.yaml | 161 ++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 control-plane/api/mesh/v2beta1/gateway_class_config_types.go create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go new file mode 100644 index 0000000000..dbfbc2f8b4 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -0,0 +1,152 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + gatewayClassConfigKubeKind = "gatewayclassconfig" +) + +func init() { + MeshSchemeBuilder.Register(&GatewayClassConfig{}, &GatewayClassConfigList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// GatewayClassConfig is the Schema for the Mesh Gateway API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:shortName="gateway-class-config" +// +kubebuilder:resource:scope=Cluster +type GatewayClassConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.GatewayClassConfig `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// GatewayClassConfigList contains a list of GatewayClassConfig. +type GatewayClassConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*GatewayClassConfig `json:"items"` +} + +func (in *GatewayClassConfig) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.GatewayClassConfigType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *GatewayClassConfig) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *GatewayClassConfig) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *GatewayClassConfig) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *GatewayClassConfig) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *GatewayClassConfig) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *GatewayClassConfig) KubeKind() string { + return gatewayClassConfigKubeKind +} + +func (in *GatewayClassConfig) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *GatewayClassConfig) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *GatewayClassConfig) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *GatewayClassConfig) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *GatewayClassConfig) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *GatewayClassConfig) Validate(tenancy common.ConsulTenancyConfig) error { + // TODO add validation logic that ensures we only ever write this to the default namespace. + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *GatewayClassConfig) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index 08a346d6c3..799bb5b543 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -136,6 +136,69 @@ func (in *GatewayClass) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassConfig) DeepCopyInto(out *GatewayClassConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfig. +func (in *GatewayClassConfig) DeepCopy() *GatewayClassConfig { + if in == nil { + return nil + } + out := new(GatewayClassConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayClassConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassConfigList) DeepCopyInto(out *GatewayClassConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*GatewayClassConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(GatewayClassConfig) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigList. +func (in *GatewayClassConfigList) DeepCopy() *GatewayClassConfigList { + if in == nil { + return nil + } + out := new(GatewayClassConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayClassConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayClassList) DeepCopyInto(out *GatewayClassList) { *out = *in diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml new file mode 100644 index 0000000000..eb68bcbdcf --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -0,0 +1,161 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: gatewayclassconfigs.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: GatewayClassConfig + listKind: GatewayClassConfigList + plural: gatewayclassconfigs + singular: gatewayclassconfig + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: GatewayClassConfig is the Schema for the Mesh Gateway API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: This is a Resource type. + properties: + consul: + properties: + address: + type: string + authentication: + properties: + address: + type: string + managed: + type: boolean + method: + type: string + namespace: + type: string + type: object + ports: + properties: + grpc: + format: int32 + type: integer + http: + format: int32 + type: integer + type: object + scheme: + type: string + type: object + copyAnnotations: + properties: + service: + items: + type: string + type: array + type: object + deployment: + properties: + defaultInstances: + format: int32 + type: integer + maxInstances: + format: int32 + type: integer + minInstances: + format: int32 + type: integer + type: object + image: + properties: + consulApiGateway: + type: string + envoy: + type: string + type: object + logLevel: + type: string + mapPrivilegedContainerPorts: + format: int32 + type: integer + nodeSelector: + type: string + openshiftSccName: + type: string + serviceType: + type: string + useHostPorts: + type: boolean + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} From bf12fb9b52ce6d1db931c1ace3a8826fc59821e1 Mon Sep 17 00:00:00 2001 From: Joshua Timmons Date: Tue, 21 Nov 2023 12:43:53 -0500 Subject: [PATCH 480/592] Add `telemetryCollector.cloud.resourceId` field that works even when `global.cloud.enabled` is false (#3219) --- .changelog/3219.txt | 3 + charts/consul/templates/_helpers.tpl | 37 ++++- .../telemetry-collector-deployment.yaml | 35 +++- .../telemetry-collector-v2-deployment.yaml | 33 +++- .../unit/telemetry-collector-deployment.bats | 157 +++++++++++++++--- .../telemetry-collector-v2-deployment.bats | 37 +++-- charts/consul/values.yaml | 121 +++++++++----- 7 files changed, 323 insertions(+), 100 deletions(-) create mode 100644 .changelog/3219.txt diff --git a/.changelog/3219.txt b/.changelog/3219.txt new file mode 100644 index 0000000000..90292c4b71 --- /dev/null +++ b/.changelog/3219.txt @@ -0,0 +1,3 @@ +```release-note:bug +consul-telemetry-collector: add telemetryCollector.cloud.resourceId that works even when not global.cloud.enabled +``` \ No newline at end of file diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 5f06839923..285715225b 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -415,7 +415,7 @@ Usage: {{ template "consul.validateCloudSecretKeys" . }} {{/* -Fails if temeletryCollector.clientId or telemetryCollector.clientSecret exist and one of other secrets is nil or empty. +Fails if telemetryCollector.clientId or telemetryCollector.clientSecret exist and one of other secrets is nil or empty. - telemetryCollector.cloud.clientId.secretName - telemetryCollector.cloud.clientSecret.secretName - global.cloud.resourceId.secretName @@ -424,11 +424,11 @@ Usage: {{ template "consul.validateTelemetryCollectorCloud" . }} */}} {{- define "consul.validateTelemetryCollectorCloud" -}} -{{- if (and .Values.telemetryCollector.cloud.clientId.secretName (or (not .Values.global.cloud.resourceId.secretName) (not .Values.telemetryCollector.cloud.clientSecret.secretName))) }} -{{fail "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set."}} +{{- if (and .Values.telemetryCollector.cloud.clientId.secretName (and (not .Values.global.cloud.clientSecret.secretName) (not .Values.telemetryCollector.cloud.clientSecret.secretName))) }} +{{fail "When telemetryCollector.cloud.clientId.secretName is set, telemetryCollector.cloud.clientSecret.secretName must also be set."}} {{- end }} -{{- if (and .Values.telemetryCollector.cloud.clientSecret.secretName (or (not .Values.global.cloud.resourceId.secretName) (not .Values.telemetryCollector.cloud.clientSecret.secretName))) }} -{{fail "When telemetryCollector.cloud.clientSecret.secretName is set, global.cloud.resourceId.secretName,telemetryCollector.cloud.clientId.secretName must also be set."}} +{{- if (and .Values.telemetryCollector.cloud.clientSecret.secretName (and (not .Values.global.cloud.clientId.secretName) (not .Values.telemetryCollector.cloud.clientId.secretName))) }} +{{fail "When telemetryCollector.cloud.clientSecret.secretName is set, telemetryCollector.cloud.clientId.secretName must also be set."}} {{- end }} {{- end }} @@ -441,14 +441,33 @@ Usage: {{ template "consul.validateTelemetryCollectorCloud" . }} {{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName (not .Values.telemetryCollector.cloud.clientSecret.secretKey)) (and .Values.telemetryCollector.cloud.clientSecret.secretKey (not .Values.telemetryCollector.cloud.clientSecret.secretName)) }} {{fail "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set."}} {{- end }} -{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not .Values.global.cloud.resourceId.secretName)) }} -{{fail "When telemetryCollector has clientId and clientSecret global.cloud.resourceId.secretName must be set"}} +{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not (or .Values.telemetryCollector.cloud.resourceId.secretName .Values.global.cloud.resourceId.secretName))) }} +{{fail "When telemetryCollector has clientId and clientSecret, telemetryCollector.cloud.resourceId.secretName or global.cloud.resourceId.secretName must be set"}} {{- end }} -{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not .Values.global.cloud.resourceId.secretKey)) }} -{{fail "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set"}} +{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not (or .Values.telemetryCollector.cloud.resourceId.secretKey .Values.global.cloud.resourceId.secretKey))) }} +{{fail "When telemetryCollector has clientId and clientSecret, telemetryCollector.cloud.resourceId.secretKey or global.cloud.resourceId.secretKey must be set"}} {{- end }} {{- end -}} +{{/* +Fails if telemetryCollector.cloud.resourceId is set but differs from global.cloud.resourceId. This should never happen. Either one or both are set, but they should never differ. +If they differ, that implies we're configuring servers for one HCP Consul cluster but pushing envoy metrics for a different HCP Consul cluster. A user could set the same value +in two secrets (it's questionable whether resourceId should be a secret at all) but we won't know at this point, so we just check secret name+key. + +Usage: {{ template "consul.validateTelemetryCollectorResourceId" . }} + +*/}} +{{- define "consul.validateTelemetryCollectorResourceId" -}} +{{- if and (and .Values.telemetryCollector.cloud.resourceId.secretName .Values.global.cloud.resourceId.secretName) (not (eq .Values.telemetryCollector.cloud.resourceId.secretName .Values.global.cloud.resourceId.secretName)) }} +{{fail "When both global.cloud.resourceId.secretName and telemetryCollector.cloud.resourceId.secretName are set, they should be the same."}} +{{- end }} +{{- if and (and .Values.telemetryCollector.cloud.resourceId.secretKey .Values.global.cloud.resourceId.secretKey) (not (eq .Values.telemetryCollector.cloud.resourceId.secretKey .Values.global.cloud.resourceId.secretKey)) }} +{{fail "When both global.cloud.resourceId.secretKey and telemetryCollector.cloud.resourceId.secretKey are set, they should be the same."}} +{{- end }} +{{- end }} + +{{/**/}} + {{/* Fails if global.experiments.resourceAPIs is set along with any of these unsupported features. - global.peering.enabled diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index 780884f999..66d07d2527 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -5,6 +5,7 @@ {{ template "consul.validateCloudSecretKeys" . }} {{ template "consul.validateTelemetryCollectorCloud" . }} {{ template "consul.validateTelemetryCollectorCloudSecretKeys" . }} +{{ template "consul.validateTelemetryCollectorResourceId" . }} apiVersion: apps/v1 kind: Deployment metadata: @@ -165,13 +166,32 @@ spec: # These are mounted as secrets so that the telemetry-collector can use them when cloud is enabled. # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. - # - HCP_RESOURCE_ID is created for use in the global cloud section but we will share it here + # - HCP_RESOURCE_ID is created either in the global cloud section or in telemetryCollector.cloud + {{- if .Values.telemetryCollector.cloud.resourceId.secretName }} + - name: HCP_RESOURCE_ID + valueFrom: + secretKeyRef: + name: {{ .Values.telemetryCollector.cloud.resourceId.secretName }} + key: {{ .Values.telemetryCollector.cloud.resourceId.secretKey }} + {{- else if .Values.global.cloud.resourceId.secretName }} + - name: HCP_RESOURCE_ID + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.resourceId.secretName }} + key: {{ .Values.global.cloud.resourceId.secretKey }} + {{- end }} {{- if .Values.telemetryCollector.cloud.clientId.secretName }} - name: HCP_CLIENT_ID valueFrom: secretKeyRef: name: {{ .Values.telemetryCollector.cloud.clientId.secretName }} key: {{ .Values.telemetryCollector.cloud.clientId.secretKey }} + {{- else if .Values.global.cloud.clientId.secretName }} + - name: HCP_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.clientId.secretName }} + key: {{ .Values.global.cloud.clientId.secretKey }} {{- end }} {{- if .Values.telemetryCollector.cloud.clientSecret.secretName }} - name: HCP_CLIENT_SECRET @@ -179,14 +199,13 @@ spec: secretKeyRef: name: {{ .Values.telemetryCollector.cloud.clientSecret.secretName }} key: {{ .Values.telemetryCollector.cloud.clientSecret.secretKey }} - {{- end}} - {{- if .Values.global.cloud.resourceId.secretName }} - - name: HCP_RESOURCE_ID + {{- else if .Values.global.cloud.clientSecret.secretName }} + - name: HCP_CLIENT_SECRET valueFrom: secretKeyRef: - name: {{ .Values.global.cloud.resourceId.secretName }} - key: {{ .Values.global.cloud.resourceId.secretKey }} - {{- end }} + name: {{ .Values.global.cloud.clientSecret.secretName }} + key: {{ .Values.global.cloud.clientSecret.secretKey }} + {{- end}} {{- if .Values.global.cloud.authUrl.secretName }} - name: HCP_AUTH_URL valueFrom: @@ -227,7 +246,7 @@ spec: consul-telemetry-collector agent \ {{- if .Values.telemetryCollector.customExporterConfig }} - -config-file-path /consul/config/config.json \ + -config-file-path /consul/config/config.json \ {{ end }} volumeMounts: {{- if .Values.telemetryCollector.customExporterConfig }} diff --git a/charts/consul/templates/telemetry-collector-v2-deployment.yaml b/charts/consul/templates/telemetry-collector-v2-deployment.yaml index 86b4edf159..34d1d30bcc 100644 --- a/charts/consul/templates/telemetry-collector-v2-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-v2-deployment.yaml @@ -5,6 +5,7 @@ {{ template "consul.validateCloudSecretKeys" . }} {{ template "consul.validateTelemetryCollectorCloud" . }} {{ template "consul.validateTelemetryCollectorCloudSecretKeys" . }} +{{ template "consul.validateTelemetryCollectorResourceId" . }} apiVersion: apps/v1 kind: Deployment metadata: @@ -154,13 +155,32 @@ spec: # These are mounted as secrets so that the telemetry-collector can use them when cloud is enabled. # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. - # - HCP_RESOURCE_ID is created for use in the global cloud section but we will share it here + # - HCP_RESOURCE_ID is created either in the global cloud section or in telemetryCollector.cloud + {{- if .Values.telemetryCollector.cloud.resourceId.secretName }} + - name: HCP_RESOURCE_ID + valueFrom: + secretKeyRef: + name: {{ .Values.telemetryCollector.cloud.resourceId.secretName }} + key: {{ .Values.telemetryCollector.cloud.resourceId.secretKey }} + {{- else if .Values.global.cloud.resourceId.secretName }} + - name: HCP_RESOURCE_ID + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.resourceId.secretName }} + key: {{ .Values.global.cloud.resourceId.secretKey }} + {{- end }} {{- if .Values.telemetryCollector.cloud.clientId.secretName }} - name: HCP_CLIENT_ID valueFrom: secretKeyRef: name: {{ .Values.telemetryCollector.cloud.clientId.secretName }} key: {{ .Values.telemetryCollector.cloud.clientId.secretKey }} + {{- else if .Values.global.cloud.clientId.secretName }} + - name: HCP_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ .Values.global.cloud.clientId.secretName }} + key: {{ .Values.global.cloud.clientId.secretKey }} {{- end }} {{- if .Values.telemetryCollector.cloud.clientSecret.secretName }} - name: HCP_CLIENT_SECRET @@ -168,14 +188,13 @@ spec: secretKeyRef: name: {{ .Values.telemetryCollector.cloud.clientSecret.secretName }} key: {{ .Values.telemetryCollector.cloud.clientSecret.secretKey }} - {{- end}} - {{- if .Values.global.cloud.resourceId.secretName }} - - name: HCP_RESOURCE_ID + {{- else if .Values.global.cloud.clientSecret.secretName }} + - name: HCP_CLIENT_SECRET valueFrom: secretKeyRef: - name: {{ .Values.global.cloud.resourceId.secretName }} - key: {{ .Values.global.cloud.resourceId.secretKey }} - {{- end }} + name: {{ .Values.global.cloud.clientSecret.secretName }} + key: {{ .Values.global.cloud.clientSecret.secretKey }} + {{- end}} {{- if .Values.global.cloud.authUrl.secretName }} - name: HCP_AUTH_URL valueFrom: diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index 60b87961b5..b4d0391d27 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -528,20 +528,86 @@ load _helpers #-------------------------------------------------------------------- # telemetryCollector.cloud -@test "telemetryCollector/Deployment: success with all cloud bits set" { +@test "telemetryCollector/Deployment: success with global.cloud env vars" { cd `chart_dir` - run helm template \ + local object=$(helm template \ -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ --set 'global.cloud.enabled=true' \ + --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ --set 'global.cloud.clientSecret.secretName=client-secret-name' \ --set 'global.cloud.clientSecret.secretKey=client-secret-key' \ --set 'global.cloud.clientId.secretName=client-id-name' \ --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ - . + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_RESOURCE_ID")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) + [ "${actual}" = "client-resource-id-name" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_RESOURCE_ID")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) + [ "${actual}" = "client-resource-id-key" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_CLIENT_ID")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) + [ "${actual}" = "client-id-name" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_CLIENT_ID")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) + [ "${actual}" = "client-id-key" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_CLIENT_SECRET")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) + [ "${actual}" = "client-secret-name" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_CLIENT_SECRET")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) + [ "${actual}" = "client-secret-key" ] +} + +@test "telemetryCollector/Deployment: success with telemetryCollector.cloud env vars" { + cd `chart_dir` + local object=$(helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'global.cloud.enabled=false' \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'telemetryCollector.cloud.resourceId.secretName=client-resource-id-name' \ + --set 'telemetryCollector.cloud.resourceId.secretKey=client-resource-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_RESOURCE_ID")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) + [ "${actual}" = "client-resource-id-name" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_RESOURCE_ID")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) + [ "${actual}" = "client-resource-id-key" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_CLIENT_ID")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) + [ "${actual}" = "client-id-name" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_CLIENT_ID")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) + [ "${actual}" = "client-id-key" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_CLIENT_SECRET")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) + [ "${actual}" = "client-secret-name" ] + + local actual=$(echo $object | + yq -r 'map(select(.name == "HCP_CLIENT_SECRET")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) + [ "${actual}" = "client-secret-key" ] } @test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId is set and global.cloud.resourceId is not set or global.cloud.clientSecret.secretName is not set" { @@ -776,14 +842,16 @@ load _helpers --set 'telemetryCollector.enabled=true' \ --set 'telemetryCollector.image=bar' \ --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.resourceId.secretName=resource-id-name' \ - --set 'global.resourceId.secretKey=resource-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ . [ "$status" -eq 1 ] - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] + echo "$output" > /dev/stderr + + [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] } @test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId.secretKey is set but telemetryCollector.cloud.clientId.secretName is not set." { @@ -794,13 +862,15 @@ load _helpers --set 'telemetryCollector.image=bar' \ --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'global.resourceId.secretName=resource-id-name' \ - --set 'global.resourceId.secretKey=resource-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ . [ "$status" -eq 1 ] - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] + echo "$output" > /dev/stderr + + [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] } @test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetryCollector.cloud.clientId.secretName is not set." { @@ -809,14 +879,17 @@ load _helpers -s templates/telemetry-collector-deployment.yaml \ --set 'telemetryCollector.enabled=true' \ --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.clientSecret.secretKey=client-secret-key-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key-name' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ . [ "$status" -eq 1 ] - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] + echo "$output" > /dev/stderr + + [[ "$output" =~ "When telemetryCollector.cloud.clientSecret.secretName is set, telemetryCollector.cloud.clientId.secretName must also be set." ]] } @test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId.secretName is set but telemetry.cloud.clientId.secretKey is not set." { @@ -828,6 +901,7 @@ load _helpers --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ . [ "$status" -eq 1 ] @@ -844,6 +918,7 @@ load _helpers --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ . [ "$status" -eq 1 ] @@ -864,7 +939,47 @@ load _helpers . [ "$status" -eq 1 ] - [[ "$output" =~ "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set" ]] + [[ "$output" =~ "When telemetryCollector has clientId and clientSecret, telemetryCollector.cloud.resourceId.secretKey or global.cloud.resourceId.secretKey must be set" ]] +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.resourceId.secretName differs from global.cloud.resourceId.secretName." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.cloud.resourceId.secretName=resource-id-1' \ + --set 'global.cloud.resourceId.secretKey=key' \ + --set 'telemetryCollector.cloud.resourceId.secretName=resource-id-2' \ + --set 'telemetryCollector.cloud.resourceId.secretKey=key' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When both global.cloud.resourceId.secretName and telemetryCollector.cloud.resourceId.secretName are set, they should be the same." ]] +} + +@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.resourceId.secretKey differs from global.cloud.resourceId.secretKey." { + cd `chart_dir` + run helm template \ + -s templates/telemetry-collector-deployment.yaml \ + --set 'telemetryCollector.enabled=true' \ + --set 'telemetryCollector.image=bar' \ + --set 'global.cloud.resourceId.secretName=name' \ + --set 'global.cloud.resourceId.secretKey=key-1' \ + --set 'telemetryCollector.cloud.resourceId.secretName=name' \ + --set 'telemetryCollector.cloud.resourceId.secretKey=key-2' \ + --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ + --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-name' \ + . + [ "$status" -eq 1 ] + + [[ "$output" =~ "When both global.cloud.resourceId.secretKey and telemetryCollector.cloud.resourceId.secretKey are set, they should be the same." ]] } #-------------------------------------------------------------------- diff --git a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats index a53882731c..70a8f9a822 100755 --- a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats @@ -860,14 +860,16 @@ load _helpers --set 'telemetryCollector.enabled=true' \ --set 'telemetryCollector.image=bar' \ --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.resourceId.secretName=resource-id-name' \ - --set 'global.resourceId.secretKey=resource-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ . [ "$status" -eq 1 ] - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] + echo "$output" > /dev/stderr + + [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] } @test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretKey is set but telemetryCollector.cloud.clientId.secretName is not set." { @@ -880,13 +882,15 @@ load _helpers --set 'telemetryCollector.image=bar' \ --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'global.resourceId.secretName=resource-id-name' \ - --set 'global.resourceId.secretKey=resource-id-key' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ . [ "$status" -eq 1 ] - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] + echo "$output" > /dev/stderr + + [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] } @test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetryCollector.cloud.clientId.secretName is not set." { @@ -897,14 +901,17 @@ load _helpers --set 'global.experiments[0]=resource-apis' \ --set 'telemetryCollector.enabled=true' \ --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.clientSecret.secretKey=client-secret-key-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key-name' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ . [ "$status" -eq 1 ] - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] + echo "$output" > /dev/stderr + + [[ "$output" =~ "When telemetryCollector.cloud.clientSecret.secretName is set, telemetryCollector.cloud.clientId.secretName must also be set." ]] } @test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretName is set but telemetry.cloud.clientId.secretKey is not set." { @@ -958,7 +965,9 @@ load _helpers . [ "$status" -eq 1 ] - [[ "$output" =~ "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set" ]] + echo "$output" > /dev/stderr + + [[ "$output" =~ "When telemetryCollector has clientId and clientSecret, telemetryCollector.cloud.resourceId.secretKey or global.cloud.resourceId.secretKey must be set" ]] } #-------------------------------------------------------------------- diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 577d1af369..49d986cd7d 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -114,7 +114,7 @@ global: # secretKey should be in the form of "key". secretsBackend: vault: - # Vault namespace (optional). This sets the Vault namespace for the `vault.hashicorp.com/namespace` + # Vault namespace (optional). This sets the Vault namespace for the `vault.hashicorp.com/namespace` # agent annotation and [Vault Connect CA namespace](https://developer.hashicorp.com/consul/docs/connect/ca/vault#namespace). # To override one of these values individually, see `agentAnnotations` and `connectCA.additionalConfig`. vaultNamespace: "" @@ -457,7 +457,7 @@ global: # @type: string secretKey: null - # The resource requests (CPU, memory, etc.) for the server-acl-init and server-acl-init-cleanup pods. + # The resource requests (CPU, memory, etc.) for the server-acl-init and server-acl-init-cleanup pods. # This should be a YAML map corresponding to a Kubernetes # [`ResourceRequirements``](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#resourcerequirements-v1-core) # object. @@ -558,7 +558,7 @@ global: # If enabled, this datacenter will be federation-capable. Only federation # via mesh gateways is supported. # Mesh gateways and servers will be configured to allow federation. - # Requires `global.tls.enabled`, `connectInject.enabled`, and one of + # Requires `global.tls.enabled`, `connectInject.enabled`, and one of # `meshGateway.enabled` or `externalServers.enabled` to be true. # Requires Consul 1.8+. enabled: false @@ -588,7 +588,7 @@ global: # from the one used by the Consul Service Mesh. # Please refer to the [Kubernetes Auth Method documentation](https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes). # - # If `externalServers.enabled` is set to true, `global.federation.k8sAuthMethodHost` and + # If `externalServers.enabled` is set to true, `global.federation.k8sAuthMethodHost` and # `externalServers.k8sAuthMethodHost` should be set to the same value. # # You can retrieve this value from your `kubeconfig` by running: @@ -652,14 +652,15 @@ global: # the API before cancelling the request. consulAPITimeout: 5s - # Enables installing an HCP Consul self-managed cluster. + # Enables installing an HCP Consul Central self-managed cluster. # Requires Consul v1.14+. cloud: - # If true, the Helm chart will enable the installation of an HCP Consul + # If true, the Helm chart will enable the installation of an HCP Consul Central # self-managed cluster. enabled: false - # The name of the Kubernetes secret that holds the HCP resource id. + # The resource id of the HCP Consul Central cluster to link to. Eg: + # organization/27109cd4-a309-4bf3-9986-e1d071914b18/project/fcef6c24-259d-4510-bb8d-1d812e120e34/hashicorp.consul.global-network-manager.cluster/consul-cluster # This is required when global.cloud.enabled is true. resourceId: # The name of the Kubernetes secret that holds the resource id. @@ -669,7 +670,8 @@ global: # @type: string secretKey: null - # The name of the Kubernetes secret that holds the HCP cloud client id. + # The client id portion of a [service principal](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals#service-principals) with authorization to link the cluster + # in global.cloud.resourceId to HCP Consul Central. # This is required when global.cloud.enabled is true. clientId: # The name of the Kubernetes secret that holds the client id. @@ -679,7 +681,8 @@ global: # @type: string secretKey: null - # The name of the Kubernetes secret that holds the HCP cloud client secret. + # The client secret portion of a [service principal](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals#service-principals) with authorization to link the cluster + # in global.cloud.resourceId to HCP Consul Central. # This is required when global.cloud.enabled is true. clientSecret: # The name of the Kubernetes secret that holds the client secret. @@ -689,8 +692,7 @@ global: # @type: string secretKey: null - # The name of the Kubernetes secret that holds the HCP cloud client id. - # This is optional when global.cloud.enabled is true. + # The hostname of HCP's API. This setting is used for internal testing and validation. apiHost: # The name of the Kubernetes secret that holds the api hostname. # @type: string @@ -699,8 +701,7 @@ global: # @type: string secretKey: null - # The name of the Kubernetes secret that holds the HCP cloud authorization url. - # This is optional when global.cloud.enabled is true. + # The URL of HCP's auth API. This setting is used for internal testing and validation. authUrl: # The name of the Kubernetes secret that holds the authorization url. # @type: string @@ -709,8 +710,7 @@ global: # @type: string secretKey: null - # The name of the Kubernetes secret that holds the HCP cloud scada address. - # This is optional when global.cloud.enabled is true. + # The address of HCP's scada service. This setting is used for internal testing and validation. scadaAddress: # The name of the Kubernetes secret that holds the scada address. # @type: string @@ -745,7 +745,7 @@ global: # ] # ``` # @type: array - trustedCAs: [ ] + trustedCAs: [] # Consul feature flags that will be enabled across components. # Supported feature flags: @@ -762,8 +762,7 @@ global: # experiments: [ "resource-apis" ] # ``` # @type: array - experiments: [ ] - + experiments: [] # Server, when enabled, configures a server cluster to run. This should # be disabled if you plan on connecting to a Consul cluster external to @@ -884,9 +883,9 @@ server: # The [Persistent Volume Claim (PVC) retention policy](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#persistentvolumeclaim-retention) # controls if and how PVCs are deleted during the lifecycle of a StatefulSet. - # WhenDeleted specifies what happens to PVCs created from StatefulSet VolumeClaimTemplates when the StatefulSet is deleted, + # WhenDeleted specifies what happens to PVCs created from StatefulSet VolumeClaimTemplates when the StatefulSet is deleted, # and WhenScaled specifies what happens to PVCs created from StatefulSet VolumeClaimTemplates when the StatefulSet is scaled down. - # + # # Example: # # ```yaml @@ -901,7 +900,7 @@ server: # _will not_ automatically secure pod communication, this # setting will only enable usage of the feature. Consul will automatically initialize # a new CA and set of certificates. Additional service mesh settings can be configured - # by setting the `server.extraConfig` value or by applying [configuration entries](https://developer.hashicorp.com/consul/docs/connect/config-entries). + # by setting the `server.extraConfig` value or by applying [configuration entries](https://developer.hashicorp.com/consul/docs/connect/config-entries). connect: true serviceAccount: @@ -1271,7 +1270,7 @@ server: # @type: string caCert: null - # [Enterprise Only] Added in Consul 1.8, the audit object allow users to enable auditing + # [Enterprise Only] Added in Consul 1.8, the audit object allow users to enable auditing # and configure a sink and filters for their audit logs. Please refer to # [audit logs](https://developer.hashicorp.com/consul/docs/enterprise/audit-logging) documentation # for further information. @@ -1280,7 +1279,7 @@ server: # global.acls.manageSystemACLs must be enabled to use this feature. enabled: false - # A single entry of the sink object provides configuration for the destination to which Consul + # A single entry of the sink object provides configuration for the destination to which Consul # will log auditing events. # # Example: @@ -1295,7 +1294,7 @@ server: # rotate_duration: 24h # rotate_max_files: 15 # rotate_bytes: 25165824 - # + # # ``` # # The sink object supports the following keys: @@ -1407,7 +1406,7 @@ externalServers: # This address must be reachable from the Consul servers. # Please refer to the [Kubernetes Auth Method documentation](https://developer.hashicorp.com/consul/docs/security/acl/auth-methods/kubernetes). # - # If `global.federation.enabled` is set to true, `global.federation.k8sAuthMethodHost` and + # If `global.federation.enabled` is set to true, `global.federation.k8sAuthMethodHost` and # `externalServers.k8sAuthMethodHost` should be set to the same value. # # You could retrieve this value from your `kubeconfig` by running: @@ -2200,7 +2199,7 @@ connectInject: # If this setting is false, you will need to install the Gateway API CRDs manually. manageExternalCRDs: true - # Enables Consul on Kubernets to manage only the non-standard CRDs used for Gateway API. If manageExternalCRDs is true + # Enables Consul on Kubernets to manage only the non-standard CRDs used for Gateway API. If manageExternalCRDs is true # then all CRDs will be installed; otherwise, if manageNonStandardCRDs is true then only TCPRoute, GatewayClassConfig and MeshService # will be installed. manageNonStandardCRDs: false @@ -2687,16 +2686,16 @@ connectInject: # - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-shutdown-path` # @type: map lifecycle: - # @type: boolean - defaultEnabled: true - # @type: boolean - defaultEnableShutdownDrainListeners: true - # @type: integer - defaultShutdownGracePeriodSeconds: 30 - # @type: integer - defaultGracefulPort: 20600 - # @type: string - defaultGracefulShutdownPath: "/graceful_shutdown" + # @type: boolean + defaultEnabled: true + # @type: boolean + defaultEnableShutdownDrainListeners: true + # @type: integer + defaultShutdownGracePeriodSeconds: 30 + # @type: integer + defaultGracefulPort: 20600 + # @type: string + defaultGracefulShutdownPath: "/graceful_shutdown" # The resource settings for the Connect injected init container. If null, the resources # won't be set for the initContainer. The defaults are optimized for developer instances of @@ -3096,8 +3095,8 @@ ingressGateways: # Gateways is a list of gateway objects. The only required field for # each is `name`, though they can also contain any of the fields in - # `defaults`. You must provide a unique name for each ingress gateway. These names - # must be unique across different namespaces. + # `defaults`. You must provide a unique name for each ingress gateway. These names + # must be unique across different namespaces. # Values defined here override the defaults, except in the case of annotations where both will be applied. # @type: array gateways: @@ -3491,7 +3490,7 @@ telemetryCollector: customExporterConfig: null service: - # This value defines additional annotations for the server service account. This should be formatted as a multi-line + # This value defines additional annotations for the telemetry-collector's service account. This should be formatted as a multi-line # string. # # ```yaml @@ -3517,11 +3516,51 @@ telemetryCollector: annotations: null cloud: + # The resource id of the HCP Consul Central cluster to push metrics for. Eg: + # `organization/27109cd4-a309-4bf3-9986-e1d071914b18/project/fcef6c24-259d-4510-bb8d-1d812e120e34/hashicorp.consul.global-network-manager.cluster/consul-cluster` + # + # This is used for HCP Consul Central-linked or managed clusters where global.cloud.resourceId is unset. For example, when using externalServers + # with HCP Consul-managed clusters or HCP Consul Central-linked clusters in a different admin partition. + # + # If global.cloud.resourceId is set, this should either be unset (defaulting to global.cloud.resourceId) or be the same as global.cloud.resourceId. + # + # @default: global.cloud.resourceId + resourceId: + # The name of the Kubernetes secret that holds the resource id. + # @type: string + secretName: null + # The key within the Kubernetes secret that holds the resource id. + # @type: string + secretKey: null + + # The client id portion of a [service principal](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals#service-principals) with authorization to push metrics to HCP + # + # This is set in two scenarios: + # - the service principal in global.cloud is unset + # - the HCP UI provides a service principal with more narrowly scoped permissions that the service principal used in global.cloud + # + # @default: global.cloud.clientId clientId: + # The name of the Kubernetes secret that holds the client id. + # @type: string secretName: null + # The key within the Kubernetes secret that holds the client id. + # @type: string secretKey: null + + # The client secret portion of a [service principal](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals#service-principals) with authorization to push metrics to HCP. + # + # This is set in two scenarios: + # - the service principal in global.cloud is unset + # - the HCP UI provides a service principal with more narrowly scoped permissions that the service principal used in global.cloud + # + # @default: global.cloud.clientSecret clientSecret: + # The name of the Kubernetes secret that holds the client secret. + # @type: string secretName: null + # The key within the Kubernetes secret that holds the client secret. + # @type: string secretKey: null initContainer: @@ -3538,9 +3577,9 @@ telemetryCollector: # @type: string priorityClassName: "" - # A list of extra environment variables to set within the stateful set. + # A list of extra environment variables to set within the deployment. # These could be used to include proxy settings required for cloud auto-join # feature, in case kubernetes cluster is behind egress http proxies. Additionally, # it could be used to configure custom consul parameters. # @type: map - extraEnvironmentVars: { } + extraEnvironmentVars: {} From 6446f429c85acb590730ac4aa44f1dd02f0be5f5 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 21 Nov 2023 13:57:46 -0500 Subject: [PATCH 481/592] NET-6664 Specify cluster scope for MeshGateway CRD (#3236) Specify cluster scope for MeshGateway CRD --- charts/consul/templates/crd-meshgateways.yaml | 4 +--- control-plane/api/mesh/v2beta1/mesh_gateway_types.go | 2 +- .../crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/charts/consul/templates/crd-meshgateways.yaml b/charts/consul/templates/crd-meshgateways.yaml index c2a376bd12..b40959eb5a 100644 --- a/charts/consul/templates/crd-meshgateways.yaml +++ b/charts/consul/templates/crd-meshgateways.yaml @@ -17,10 +17,8 @@ spec: kind: MeshGateway listKind: MeshGatewayList plural: meshgateways - shortNames: - - mesh-gateway singular: meshgateway - scope: Namespaced + scope: Cluster versions: - additionalPrinterColumns: - description: The sync status of the resource with Consul diff --git a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go index cf23707a16..b7a2b1a2e3 100644 --- a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go +++ b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go @@ -33,7 +33,7 @@ func init() { // +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" // +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="mesh-gateway" +// +kubebuilder:resource:scope="Cluster" type MeshGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml index 6fc79979cf..58ef05ce48 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml @@ -13,10 +13,8 @@ spec: kind: MeshGateway listKind: MeshGatewayList plural: meshgateways - shortNames: - - mesh-gateway singular: meshgateway - scope: Namespaced + scope: Cluster versions: - additionalPrinterColumns: - description: The sync status of the resource with Consul From 710918dcabd2bb9dcf6d0d1683d6aec036382cb1 Mon Sep 17 00:00:00 2001 From: Joshua Timmons Date: Tue, 21 Nov 2023 14:38:36 -0500 Subject: [PATCH 482/592] Fix consul-telemetry-collector deployments to non-default namespaces (#3215) --- .changelog/3215.txt | 3 + acceptance/tests/cloud/observability_test.go | 378 +++++++++++------- .../telemetry-collector-deployment.yaml | 62 ++- .../telemetry-collector-v2-deployment.yaml | 43 +- .../unit/telemetry-collector-deployment.bats | 15 +- .../telemetry-collector-v2-deployment.bats | 15 +- .../constants/annotations_and_labels.go | 5 + .../endpoints/endpoints_controller.go | 31 ++ 8 files changed, 365 insertions(+), 187 deletions(-) create mode 100644 .changelog/3215.txt diff --git a/.changelog/3215.txt b/.changelog/3215.txt new file mode 100644 index 0000000000..c753e1ff79 --- /dev/null +++ b/.changelog/3215.txt @@ -0,0 +1,3 @@ +```release-note:bug +consul-telemetry-collector: fix deployments to non-default namespaces when global.enableConsulNamespaces +``` \ No newline at end of file diff --git a/acceptance/tests/cloud/observability_test.go b/acceptance/tests/cloud/observability_test.go index 5a2bf7b365..63e2d15bd2 100644 --- a/acceptance/tests/cloud/observability_test.go +++ b/acceptance/tests/cloud/observability_test.go @@ -4,16 +4,19 @@ package cloud import ( + "fmt" "strings" "testing" "time" + "github.com/google/uuid" terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" "github.com/hashicorp/serf/testutil/retry" "github.com/stretchr/testify/require" ) @@ -42,181 +45,260 @@ var ( scadaAddressSecretName = "scadaaddress-sec-name" scadaAddressSecretKey = "scadaaddress-sec-key" scadaAddressSecretKeyValue = "fake-server:443" + + bootstrapTokenSecretName = "bootstrap-token" + bootstrapTokenSecretKey = "token" + bootstrapToken = uuid.NewString() ) func TestObservabilityCloud(t *testing.T) { - ctx := suite.Environment().DefaultContext(t) - - kubectlOptions := ctx.KubectlOptions(t) - ns := kubectlOptions.Namespace - k8sClient := environment.KubernetesClientFromOptions(t, kubectlOptions) - cfg := suite.Config() if cfg.HCPResourceID != "" { resourceSecretKeyValue = cfg.HCPResourceID } - consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) - - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/cloud/hcp-mock") - podName, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "get", "pod", "-l", "app=fake-server", "-o", `jsonpath="{.items[0].metadata.name}"`) - podName = strings.ReplaceAll(podName, "\"", "") - if err != nil { - logger.Log(t, "error finding pod name") - return - } - logger.Log(t, "fake-server pod name:"+podName) - localPort := terratestk8s.GetAvailablePort(t) - tunnel := terratestk8s.NewTunnelWithLogger( - ctx.KubectlOptions(t), - terratestk8s.ResourceTypePod, - podName, - localPort, - 443, - logger.TestLogger{}) - defer tunnel.Close() - // Retry creating the port forward since it can fail occasionally. - retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { - // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry - // because we're using ForwardPortE (not ForwardPort) so the `t` won't - // get used to fail the test, just for logging. - require.NoError(r, tunnel.ForwardPortE(t)) - }) - - fsClient := newfakeServerClient(tunnel.Endpoint()) - logger.Log(t, "fake-server addr:"+tunnel.Endpoint()) - consulToken, err := fsClient.requestToken() - if err != nil { - logger.Log(t, "error finding consul token") - return + + cases := []struct { + name string + validateCloudInteractions bool + enableConsulNamespaces bool + mirroringK8S bool + adminPartitionsEnabled bool + secure bool + }{ + { + name: "default namespace and partition", + validateCloudInteractions: true, + }, + { + name: "default namespace and partition; secure", + secure: true, + }, + { + name: "namespace mirroring; secure", + enableConsulNamespaces: true, + mirroringK8S: true, + secure: true, + }, + { + name: "admin partitions; secure", + enableConsulNamespaces: true, + mirroringK8S: true, + adminPartitionsEnabled: true, + secure: true, + }, } - logger.Log(t, "consul test token :"+consulToken) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + ctx := suite.Environment().DefaultContext(t) - releaseName := helpers.RandomName() + if c.enableConsulNamespaces && !cfg.EnableEnterprise { + t.Skip("skipping this test because -enable-enterprise is not set") + } - helmValues := map[string]string{ - "global.imagePullPolicy": "IfNotPresent", - "global.cloud.enabled": "true", - "global.cloud.resourceId.secretName": resourceSecretName, - "global.cloud.resourceId.secretKey": resourceSecretKey, + options := &terratestk8s.KubectlOptions{ + ContextName: ctx.KubectlOptions(t).ContextName, + ConfigPath: ctx.KubectlOptions(t).ConfigPath, + Namespace: ctx.KubectlOptions(t).Namespace, + } + ns := options.Namespace + + k8sClient := environment.KubernetesClientFromOptions(t, options) + + // Create cloud and telemetryCollector secrets. + consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) + consul.CreateK8sSecret(t, k8sClient, cfg, ns, bootstrapTokenSecretName, bootstrapTokenSecretKey, bootstrapToken) + + k8s.DeployKustomize(t, options, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/cloud/hcp-mock") + podName, err := k8s.RunKubectlAndGetOutputE(t, options, "get", "pod", "-l", "app=fake-server", "-o", `jsonpath="{.items[0].metadata.name}"`) + podName = strings.ReplaceAll(podName, "\"", "") + if err != nil { + logger.Log(t, "error finding pod name") + return + } + logger.Log(t, "fake-server pod name:"+podName) + localPort := terratestk8s.GetAvailablePort(t) + tunnel := terratestk8s.NewTunnelWithLogger( + options, + terratestk8s.ResourceTypePod, + podName, + localPort, + 443, + logger.TestLogger{}) + defer tunnel.Close() + // Retry creating the port forward since it can fail occasionally. + retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { + // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry + // because we're using ForwardPortE (not ForwardPort) so the `t` won't + // get used to fail the test, just for logging. + require.NoError(r, tunnel.ForwardPortE(t)) + }) - "global.cloud.clientId.secretName": clientIDSecretName, - "global.cloud.clientId.secretKey": clientIDSecretKey, + fsClient := newfakeServerClient(tunnel.Endpoint()) + logger.Log(t, "fake-server addr:"+tunnel.Endpoint()) + consulToken, err := fsClient.requestToken() + if err != nil { + logger.Log(t, "error finding consul token") + return + } - "global.cloud.clientSecret.secretName": clientSecretName, - "global.cloud.clientSecret.secretKey": clientSecretKey, + logger.Log(t, "consul test token :"+consulToken) - "global.cloud.apiHost.secretName": apiHostSecretName, - "global.cloud.apiHost.secretKey": apiHostSecretKey, + releaseName := helpers.RandomName() - "global.cloud.authUrl.secretName": authUrlSecretName, - "global.cloud.authUrl.secretKey": authUrlSecretKey, + helmValues := map[string]string{ + "global.imagePullPolicy": "IfNotPresent", - "global.cloud.scadaAddress.secretName": scadaAddressSecretName, - "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, - "connectInject.default": "true", + "global.acls.manageSystemACLs": fmt.Sprint(c.secure), + "global.tls.enabled": fmt.Sprint(c.secure), + "global.adminPartitions.enabled": fmt.Sprint(c.adminPartitionsEnabled), - "telemetryCollector.enabled": "true", - "telemetryCollector.image": cfg.ConsulCollectorImage, - "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, - "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, + "global.enableConsulNamespaces": fmt.Sprint(c.enableConsulNamespaces), + "connectInject.enabled": "true", + "connectInject.consulNamespaces.mirroringK8S": fmt.Sprint(c.mirroringK8S), - "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, - "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, + // TODO this doesn't appear to work because we just deploy to default using kubectl options from context. + // https://github.com/hashicorp/consul-k8s/blob/74097fe7b3023105ca755b45da9c72c716547f46/acceptance/framework/consul/helm_cluster.go#L107 + // "connectInject.consulNamespaces.consulDestinationNamespace": c.destinationNamespace, - "telemetryCollector.extraEnvironmentVars.HCP_API_TLS": "insecure", - "telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", - "telemetryCollector.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", - "telemetryCollector.extraEnvironmentVars.OTLP_EXPORTER_TLS": "insecure", + "global.cloud.enabled": "true", + "global.cloud.resourceId.secretName": resourceSecretName, + "global.cloud.resourceId.secretKey": resourceSecretKey, - "server.extraEnvironmentVars.HCP_API_TLS": "insecure", - "server.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", - "server.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", - } - if cfg.ConsulImage != "" { - helmValues["global.image"] = cfg.ConsulImage - } + "global.cloud.clientId.secretName": clientIDSecretName, + "global.cloud.clientId.secretKey": clientIDSecretKey, - consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), suite.Config(), releaseName) - consulCluster.Create(t) + "global.cloud.clientSecret.secretName": clientSecretName, + "global.cloud.clientSecret.secretKey": clientSecretKey, - logger.Log(t, "creating static-server deployment") + "global.cloud.apiHost.secretName": apiHostSecretName, + "global.cloud.apiHost.secretKey": apiHostSecretKey, - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") - t.Log("Finished deployment. Validating expected conditions now") + "global.cloud.authUrl.secretName": authUrlSecretName, + "global.cloud.authUrl.secretKey": authUrlSecretKey, - for name, tc := range map[string]struct { - refresh *modifyTelemetryConfigBody - refreshTime int64 - recordsPath string - timeout time.Duration - wait time.Duration - validations *metricValidations - }{ - "collectorExportsMetrics": { - recordsPath: recordsPathCollector, - // High timeout as Collector metrics scraped every 1 minute (https://github.com/hashicorp/consul-telemetry-collector/blob/dfdbf51b91d502a18f3b143a94ab4d50cdff10b8/internal/otel/config/helpers/receivers/prometheus_receiver.go#L54) - timeout: 5 * time.Minute, - wait: 1 * time.Second, - validations: &metricValidations{ - expectedLabelKeys: []string{"service_name", "service_instance_id"}, - expectedMetricName: "otelcol_receiver_accepted_metric_points", - disallowedMetricName: "server.memory_heap_size", - }, - }, - "consulPeriodicRefreshUpdateConfig": { - refresh: &modifyTelemetryConfigBody{ - Filters: []string{"consul.state"}, - Labels: map[string]string{"new_label": "testLabel"}, - }, - recordsPath: recordsPathConsul, - // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) - timeout: 3 * time.Minute, - wait: 30 * time.Second, - validations: &metricValidations{ - expectedLabelKeys: []string{"node_id", "node_name", "new_label"}, - expectedMetricName: "consul.state.services", - disallowedMetricName: "consul.fsm", - }, - }, - "consulPeriodicRefreshDisabled": { - refresh: &modifyTelemetryConfigBody{ - Filters: []string{"consul.state"}, - Labels: map[string]string{"new_label": "testLabel"}, - Disabled: true, - }, - recordsPath: recordsPathConsul, - // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) - timeout: 3 * time.Minute, - wait: 30 * time.Second, - validations: &metricValidations{ - disabled: true, - }, - }, - } { - t.Run(name, func(t *testing.T) { - // For a refresh test, we force a telemetry config update before validating metrics using fakeserver's /telemetry_config_modify endpoint. - if tc.refresh != nil { - refreshTime := time.Now() - err := fsClient.modifyTelemetryConfig(tc.refresh) - require.NoError(t, err) - // Add 10 seconds (2 * periodic refresh interval in fakeserver) to allow a periodic refresh from Consul side to take place. - tc.refreshTime = refreshTime.Add(10 * time.Second).UnixNano() + "global.cloud.scadaAddress.secretName": scadaAddressSecretName, + "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, + "connectInject.default": "true", + + "telemetryCollector.enabled": "true", + "telemetryCollector.image": cfg.ConsulCollectorImage, + "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, + "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, + + "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, + "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, + + "telemetryCollector.extraEnvironmentVars.HCP_API_TLS": "insecure", + "telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", + "telemetryCollector.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", + "telemetryCollector.extraEnvironmentVars.OTLP_EXPORTER_TLS": "insecure", + + "server.extraEnvironmentVars.HCP_API_TLS": "insecure", + "server.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", + "server.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", + } + if cfg.ConsulImage != "" { + helmValues["global.image"] = cfg.ConsulImage + } + if c.secure { + helmValues["global.acls.bootstrapToken.secretName"] = bootstrapTokenSecretName + helmValues["global.acls.bootstrapToken.secretKey"] = bootstrapTokenSecretKey } - // Validate metrics are correct using fakeserver's /records endpoint to retrieve metric exports that occured from Consul/Collector to fakeserver. - // We use retry as we wait for Consul or the Collector to export metrics. This is the best we can do to avoid flakiness. - retry.RunWith(&retry.Timer{Timeout: tc.timeout, Wait: tc.wait}, t, func(r *retry.R) { - records, err := fsClient.getRecordsForPath(tc.recordsPath, tc.refreshTime) - require.NoError(r, err) - validateMetrics(r, records, tc.validations, tc.refreshTime) - }) + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + consulCluster.ACLToken = bootstrapToken + consulCluster.Create(t) + + logger.Log(t, "creating static-server deployment") + k8s.DeployKustomize(t, options, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + t.Log("Finished deployment. Validating expected conditions now") + + // Validate that the consul-telemetry-collector service was deployed to the expected namespace. + consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) + instances, _, err := consulClient.Catalog().Service("consul-telemetry-collector", "", &api.QueryOptions{Namespace: ns}) + require.NoError(t, err) + require.Len(t, instances, 1) + require.Equal(t, "passing", instances[0].Checks.AggregatedStatus()) + + for name, tc := range map[string]struct { + refresh *modifyTelemetryConfigBody + refreshTime int64 + recordsPath string + timeout time.Duration + wait time.Duration + validations *metricValidations + }{ + "collectorExportsMetrics": { + recordsPath: recordsPathCollector, + // High timeout as Collector metrics scraped every 1 minute (https://github.com/hashicorp/consul-telemetry-collector/blob/dfdbf51b91d502a18f3b143a94ab4d50cdff10b8/internal/otel/config/helpers/receivers/prometheus_receiver.go#L54) + timeout: 5 * time.Minute, + wait: 1 * time.Second, + validations: &metricValidations{ + expectedLabelKeys: []string{"service_name", "service_instance_id"}, + expectedMetricName: "otelcol_receiver_accepted_metric_points", + disallowedMetricName: "server.memory_heap_size", + }, + }, + "consulPeriodicRefreshUpdateConfig": { + refresh: &modifyTelemetryConfigBody{ + Filters: []string{"consul.state"}, + Labels: map[string]string{"new_label": "testLabel"}, + }, + recordsPath: recordsPathConsul, + // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) + timeout: 3 * time.Minute, + wait: 1 * time.Second, + validations: &metricValidations{ + expectedLabelKeys: []string{"node_id", "node_name", "new_label"}, + expectedMetricName: "consul.state.services", + disallowedMetricName: "consul.fsm", + }, + }, + "consulPeriodicRefreshDisabled": { + refresh: &modifyTelemetryConfigBody{ + Filters: []string{"consul.state"}, + Labels: map[string]string{"new_label": "testLabel"}, + Disabled: true, + }, + recordsPath: recordsPathConsul, + // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) + timeout: 3 * time.Minute, + wait: 1 * time.Second, + validations: &metricValidations{ + disabled: true, + }, + }, + } { + t.Run(name, func(t *testing.T) { + if !c.validateCloudInteractions { + t.Skip("skipping server metric and config validation") + } + + // For a refresh test, we force a telemetry config update before validating metrics using fakeserver's /telemetry_config_modify endpoint. + if tc.refresh != nil { + refreshTime := time.Now() + err := fsClient.modifyTelemetryConfig(tc.refresh) + require.NoError(t, err) + // Add 10 seconds (2 * periodic refresh interval in fakeserver) to allow a periodic refresh from Consul side to take place. + tc.refreshTime = refreshTime.Add(10 * time.Second).UnixNano() + } + + // Validate metrics are correct using fakeserver's /records endpoint to retrieve metric exports that occured from Consul/Collector to fakeserver. + // We use retry as we wait for Consul or the Collector to export metrics. This is the best we can do to avoid flakiness. + retry.RunWith(&retry.Timer{Timeout: tc.timeout, Wait: tc.wait}, t, func(r *retry.R) { + records, err := fsClient.getRecordsForPath(tc.recordsPath, tc.refreshTime) + require.NoError(r, err) + validateMetrics(r, records, tc.validations, tc.refreshTime) + }) + }) + } }) } } diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index 66d07d2527..d36034b29c 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -35,6 +35,8 @@ spec: # This annotation tells the endpoints controller that this pod was injected even though it wasn't. The # endpoints controller would then sync the endpoint into Consul "consul.hashicorp.com/connect-inject-status": "injected" + # Signals to the endpoints controller that we should force Consul NS creation, since we bypass the mesh webhook. + "consul.hashicorp.com/telemetry-collector": "true" # We aren't using tproxy and we don't have an original pod. This would be simpler if we made a path similar # to gateways "consul.hashicorp.com/connect-service-port": "metricsserver" @@ -94,36 +96,51 @@ spec: valueFrom: fieldRef: fieldPath: spec.nodeName + - name: CONSUL_NODE_NAME + value: $(NODE_NAME)-virtual + {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} + # acl login info {{- if .Values.global.acls.manageSystemACLs }} - name: CONSUL_LOGIN_AUTH_METHOD value: {{ template "consul.fullname" . }}-k8s-auth-method + - name: CONSUL_LOGIN_DATACENTER + value: {{ .Values.global.datacenter }} - name: CONSUL_LOGIN_META value: "component=consul-telemetry-collector,pod=$(NAMESPACE)/$(POD_NAME)" {{- end }} - - name: CONSUL_NODE_NAME - value: $(NODE_NAME)-virtual - {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} + # service and login namespace + # this is attempting to replicate the behavior of webhooks in calculating namespace + # https://github.com/hashicorp/consul-k8s/blob/b84339050bb2c4b62b60cec96275f74952b0ac9d/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go#L200 {{- if .Values.global.enableConsulNamespaces }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - name: CONSUL_NAMESPACE - value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + value: {{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }}{{ .Release.Namespace }} + {{- else }} + - name: CONSUL_NAMESPACE + value: {{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- if .Values.global.acls.manageSystemACLs }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - name: CONSUL_LOGIN_NAMESPACE - value: "default" + value: default {{- else }} - name: CONSUL_LOGIN_NAMESPACE - value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + value: {{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + {{- end }} {{- end }} {{- end }} command: - /bin/sh - -ec - |- - consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} \ + consul-k8s-control-plane connect-init \ -log-json={{ .Values.global.logJSON }} \ + -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} \ + -pod-name=${POD_NAME} \ + -pod-namespace=${POD_NAMESPACE} \ + -proxy-id-file="/consul/connect-inject/proxyid" \ -service-account-name="consul-telemetry-collector" \ - -service-name="" \ - -proxy-id-file="/consul/connect-inject/proxyid" + -service-name="" image: {{ .Values.global.imageK8S }} imagePullPolicy: IfNotPresent @@ -304,23 +321,30 @@ spec: - -credential-type=login - -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method + {{- end }} + # service and login namespace {{- if .Values.global.enableConsulNamespaces }} - {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} + - -service-namespace={{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }}{{ .Release.Namespace }} + {{- else }} + - -service-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- if .Values.global.acls.manageSystemACLs }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - -login-namespace=default {{- else }} - - -login-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + - -login-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + {{- end }} {{- end }} {{- end }} + # service and login partition {{- if .Values.global.adminPartitions.enabled }} + - -service-partition={{ .Values.global.adminPartitions.name }} + {{- if .Values.global.acls.manageSystemACLs }} - -login-partition={{ .Values.global.adminPartitions.name }} {{- end }} {{- end }} - {{- if .Values.global.enableConsulNamespaces }} - - -service-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- if .Values.global.adminPartitions.enabled }} - - -service-partition={{ .Values.global.adminPartitions.name }} - {{- end }} + # telemetry {{- if .Values.global.metrics.enabled }} - -telemetry-prom-scrape-path=/metrics {{- end }} diff --git a/charts/consul/templates/telemetry-collector-v2-deployment.yaml b/charts/consul/templates/telemetry-collector-v2-deployment.yaml index 34d1d30bcc..d8c94e7ecf 100644 --- a/charts/consul/templates/telemetry-collector-v2-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-v2-deployment.yaml @@ -88,22 +88,34 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name + # acl login info {{- if .Values.global.acls.manageSystemACLs }} - name: CONSUL_LOGIN_AUTH_METHOD value: {{ template "consul.fullname" . }}-k8s-auth-method + - name: CONSUL_LOGIN_DATACENTER + value: {{ .Values.global.datacenter }} - name: CONSUL_LOGIN_META value: "component=consul-telemetry-collector,pod=$(NAMESPACE)/$(POD_NAME)" {{- end }} - {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} + # service and login namespace + # this is attempting to replicate the behavior of webhooks in calculating namespace + # https://github.com/hashicorp/consul-k8s/blob/b84339050bb2c4b62b60cec96275f74952b0ac9d/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go#L200 {{- if .Values.global.enableConsulNamespaces }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - name: CONSUL_NAMESPACE - value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + value: {{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }}{{ .Release.Namespace }} + {{- else }} + - name: CONSUL_NAMESPACE + value: {{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- if .Values.global.acls.manageSystemACLs }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - name: CONSUL_LOGIN_NAMESPACE value: "default" {{- else }} - name: CONSUL_LOGIN_NAMESPACE - value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + value: {{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + {{- end }} {{- end }} {{- end }} command: @@ -292,23 +304,30 @@ spec: - -credential-type=login - -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method + {{- end }} + # service and login namespace {{- if .Values.global.enableConsulNamespaces }} - {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} + - -service-namespace={{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }}{{ .Release.Namespace }} + {{- else }} + - -service-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + {{- end }} + {{- if .Values.global.acls.manageSystemACLs }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - -login-namespace=default {{- else }} - - -login-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} + - -login-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + {{- end }} {{- end }} {{- end }} + # service and login partition {{- if .Values.global.adminPartitions.enabled }} + - -service-partition={{ .Values.global.adminPartitions.name }} + {{- if .Values.global.acls.manageSystemACLs }} - -login-partition={{ .Values.global.adminPartitions.name }} {{- end }} {{- end }} - {{- if .Values.global.enableConsulNamespaces }} - - -service-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- if .Values.global.adminPartitions.enabled }} - - -service-partition={{ .Values.global.adminPartitions.name }} - {{- end }} + # telemetry {{- if .Values.global.metrics.enabled }} - -telemetry-prom-scrape-path=/metrics {{- end }} diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index b4d0391d27..dd871b30df 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -1340,15 +1340,19 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ --set 'telemetryCollector.image=bar' \ --set 'global.enableConsulNamespaces=true' \ --set 'global.acls.manageSystemACLs=true' \ - --set 'syncCatalog.consulNamespaces.mirroringK8S=true' \ + --set 'connectInject.consulNamespaces.mirroringK8S=true' \ + --namespace 'test-namespace' \ . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-login-namespace=default"))' | tee /dev/stderr) [ "${actual}" = 'true' ] + + local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-service-namespace=test-namespace"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] } -@test "telemetryCollector/Deployment: namespace flags when syncCatalog" { +@test "telemetryCollector/Deployment: namespace flags when not mirroringK8S" { cd `chart_dir` local object=$(helm template \ -s templates/telemetry-collector-deployment.yaml \ @@ -1356,11 +1360,14 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ --set 'telemetryCollector.image=bar' \ --set 'global.enableConsulNamespaces=true' \ --set 'global.acls.manageSystemACLs=true' \ - --set 'syncCatalog.consulNamespaces.mirroringK8S=false' \ - --set 'syncCatalog.consulNamespaces.consulDestinationNamespace=fakenamespace' \ + --set 'connectInject.consulNamespaces.mirroringK8S=false' \ + --set 'connectInject.consulNamespaces.consulDestinationNamespace=fakenamespace' \ . | tee /dev/stderr | yq -r '.spec.template.spec.containers' | tee /dev/stderr) local actual=$(echo $object | jq -r '.[1].args | any(contains("-login-namespace=fakenamespace"))' | tee /dev/stderr) [ "${actual}" = 'true' ] + + local actual=$(echo $object | jq -r '.[1].args | any(contains("-service-namespace=fakenamespace"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] } diff --git a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats index 70a8f9a822..5cfdab96cf 100755 --- a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats @@ -1371,15 +1371,19 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ --set 'telemetryCollector.image=bar' \ --set 'global.enableConsulNamespaces=true' \ --set 'global.acls.manageSystemACLs=true' \ - --set 'syncCatalog.consulNamespaces.mirroringK8S=true' \ + --set 'connectInject.consulNamespaces.mirroringK8S=true' \ + --namespace 'test-namespace' \ . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-login-namespace=default"))' | tee /dev/stderr) [ "${actual}" = 'true' ] + + local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-service-namespace=test-namespace"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] } -@test "telemetryCollector/Deployment(V2): namespace flags when syncCatalog" { +@test "telemetryCollector/Deployment(V2): namespace flags when not mirroringK8S" { cd `chart_dir` local object=$(helm template \ -s templates/telemetry-collector-v2-deployment.yaml \ @@ -1389,11 +1393,14 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ --set 'telemetryCollector.image=bar' \ --set 'global.enableConsulNamespaces=true' \ --set 'global.acls.manageSystemACLs=true' \ - --set 'syncCatalog.consulNamespaces.mirroringK8S=false' \ - --set 'syncCatalog.consulNamespaces.consulDestinationNamespace=fakenamespace' \ + --set 'connectInject.consulNamespaces.mirroringK8S=false' \ + --set 'connectInject.consulNamespaces.consulDestinationNamespace=fakenamespace' \ . | tee /dev/stderr | yq -r '.spec.template.spec.containers' | tee /dev/stderr) local actual=$(echo $object | jq -r '.[1].args | any(contains("-login-namespace=fakenamespace"))' | tee /dev/stderr) [ "${actual}" = 'true' ] + + local actual=$(echo $object | jq -r '.[1].args | any(contains("-service-namespace=fakenamespace"))' | tee /dev/stderr) + [ "${actual}" = 'true' ] } diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 823776e577..241be331be 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -196,6 +196,11 @@ const ( // by the peering controllers. LabelPeeringToken = "consul.hashicorp.com/peering-token" + // LabelTelemetryCollector is a label signaling the pod is associated with the deployment of a Consul Telemetry + // Collector. If this is set, during connect-inject, the endpoints-controller ensures the deployed Namespace exists in Consul and create it if it does not. + // This is only meant to be used by Deployment/consul-telemetry-collector. + LabelTelemetryCollector = "consul.hashicorp.com/telemetry-collector" + // Injected is used as the annotation value for keyInjectStatus and annotationInjected. Injected = "injected" diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index ea22628022..3402b4276d 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -204,6 +204,13 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu continue } + if isTelemetryCollector(pod) { + if err = r.ensureNamespaceExists(apiClient, pod); err != nil { + r.Log.Error(err, "failed to ensure a namespace exists for Consul Telemetry Collector") + errs = multierror.Append(errs, err) + } + } + if hasBeenInjected(pod) { endpointPods.Add(address.TargetRef.Name) if isConsulDataplaneSupported(pod) { @@ -231,6 +238,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu continue } } + if isGateway(pod) { endpointPods.Add(address.TargetRef.Name) if err = r.registerGateway(apiClient, pod, serviceEndpoints, healthStatus, endpointAddressMap); err != nil { @@ -443,6 +451,7 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints tags := consulTags(pod) consulNS := r.consulNamespace(pod.Namespace) + service := &api.AgentService{ ID: svcID, Service: svcName, @@ -1336,6 +1345,28 @@ func isGateway(pod corev1.Pod) bool { return ok && anno != "" } +// isTelemetryCollector checks whether a pod is part of a deployment for a Consul Telemetry Collector. If so, +// and this is the first pod deployed to a Namespace, we need to create the Namespace in Consul. Otherwise the +// deployment may fail out during service registration because it is deployed to a Namespace that does not exist. +func isTelemetryCollector(pod corev1.Pod) bool { + anno, ok := pod.Annotations[constants.LabelTelemetryCollector] + return ok && anno != "" +} + +// ensureNamespaceExists creates a Consul namespace for a pod in the event it does not exist. +// At the time of writing, we use this for the Consul Telemetry Collector which may be the first +// pod deployed to a namespace. If it is, it's connect-inject will fail for lack of a namespace. +func (r *Controller) ensureNamespaceExists(apiClient *api.Client, pod corev1.Pod) error { + if r.EnableConsulNamespaces { + consulNS := r.consulNamespace(pod.Namespace) + if _, err := namespaces.EnsureExists(apiClient, consulNS, r.CrossNSACLPolicy); err != nil { + r.Log.Error(err, "failed to ensure Consul namespace exists", "ns", pod.Namespace, "consul ns", consulNS) + return err + } + } + return nil +} + // mapAddresses combines all addresses to a mapping of address to its health status. func mapAddresses(addresses corev1.EndpointSubset) map[corev1.EndpointAddress]string { m := make(map[corev1.EndpointAddress]string) From 66382615a81b18418b65859052a5f7f06d8cbbac Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Tue, 21 Nov 2023 15:39:47 -0500 Subject: [PATCH 483/592] Add refreshes and retries to server-acl-init job (#3137) Add refreshes and retries to server-acl-init job --- .changelog/3137.txt | 3 + control-plane/consul/dynamic.go | 65 +++++++++ control-plane/consul/dynamic_test.go | 73 ++++++++++ .../server-acl-init/anonymous_token.go | 17 ++- .../server-acl-init/anonymous_token_test.go | 13 +- .../subcommand/server-acl-init/command.go | 136 ++++++++++++------ .../server-acl-init/command_test.go | 5 +- .../server-acl-init/connect_inject.go | 17 ++- .../server-acl-init/create_or_update.go | 76 +++++++--- .../server-acl-init/create_or_update_test.go | 17 +-- .../subcommand/server-acl-init/servers.go | 10 +- 11 files changed, 331 insertions(+), 101 deletions(-) create mode 100644 .changelog/3137.txt create mode 100644 control-plane/consul/dynamic.go create mode 100644 control-plane/consul/dynamic_test.go diff --git a/.changelog/3137.txt b/.changelog/3137.txt new file mode 100644 index 0000000000..8fd8e0876b --- /dev/null +++ b/.changelog/3137.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: fixes an issue with the server-acl-init job where the job would fail on upgrades due to consul server ip address changes. +``` diff --git a/control-plane/consul/dynamic.go b/control-plane/consul/dynamic.go new file mode 100644 index 0000000000..36201701dd --- /dev/null +++ b/control-plane/consul/dynamic.go @@ -0,0 +1,65 @@ +package consul + +import ( + "time" + + capi "github.com/hashicorp/consul/api" +) + +type DynamicClient struct { + ConsulClient *capi.Client + Config *Config + watcher ServerConnectionManager +} + +func NewDynamicClientFromConnMgr(config *Config, watcher ServerConnectionManager) (*DynamicClient, error) { + client, err := NewClientFromConnMgr(config, watcher) + if err != nil { + return nil, err + } + return &DynamicClient{ + ConsulClient: client, + Config: config, + watcher: watcher, + }, nil +} + +func (d *DynamicClient) RefreshClient() error { + var err error + var client *capi.Client + // If the watcher is not set then we did not create the client using NewDynamicClientFromConnMgr and are using it in + // testing + // TODO: Use watcher in testing ;) + if d.watcher == nil { + return nil + } + client, err = NewClientFromConnMgr(d.Config, d.watcher) + if err != nil { + return err + } + d.ConsulClient = client + return nil +} + +func NewDynamicClientWithTimeout(config *capi.Config, consulAPITimeout time.Duration) (*DynamicClient, error) { + client, err := NewClient(config, consulAPITimeout) + if err != nil { + return nil, err + } + return &DynamicClient{ + ConsulClient: client, + Config: &Config{ + APIClientConfig: config, + }, + }, nil +} + +func NewDynamicClient(config *capi.Config) (*DynamicClient, error) { + // defaultTimeout is taken from flags.go.. + defaultTimeout := 5 * time.Second + client, err := NewDynamicClientWithTimeout(config, defaultTimeout) + if err != nil { + return nil, err + } + return client, nil +} diff --git a/control-plane/consul/dynamic_test.go b/control-plane/consul/dynamic_test.go new file mode 100644 index 0000000000..8159b82d66 --- /dev/null +++ b/control-plane/consul/dynamic_test.go @@ -0,0 +1,73 @@ +package consul + +import ( + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/hashicorp/consul-server-connection-manager/discovery" + capi "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" +) + +func TestRefreshDynamicClient(t *testing.T) { + // Create a server + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "\"leader\"") + })) + defer consulServer.Close() + + serverURL, err := url.Parse(consulServer.URL) + require.NoError(t, err) + serverIP := net.ParseIP(serverURL.Hostname()) + + port, err := strconv.Atoi(serverURL.Port()) + require.NoError(t, err) + + connMgr := &MockServerConnectionManager{} + + // Use a bad IP so that the client call fails + badState := discovery.State{ + Address: discovery.Addr{ + TCPAddr: net.TCPAddr{ + IP: net.ParseIP("126.0.0.1"), + Port: port, + }, + }, + } + + goodState := discovery.State{ + Address: discovery.Addr{ + TCPAddr: net.TCPAddr{ + IP: serverIP, + Port: port, + }, + }, + } + + // testify/mock has a weird behaviour when returning function calls. You cannot update On("State") to return + // something different but instead need to load up the returns. Here we are simulating a bad consul server manager + // state and then a good one + connMgr.On("State").Return(badState, nil).Once() + connMgr.On("State").Return(goodState, nil).Once() + + cfg := capi.DefaultConfig() + client, err := NewDynamicClientFromConnMgr(&Config{APIClientConfig: cfg, HTTPPort: port, GRPCPort: port}, connMgr) + require.NoError(t, err) + + // Make a request to the bad ip of the server + _, err = client.ConsulClient.Status().Leader() + require.Error(t, err) + require.Contains(t, err.Error(), "connection refused") + + // Refresh the client and make a call to the server now that consul-server-connection-manager state is good + err = client.RefreshClient() + require.NoError(t, err) + leader, err := client.ConsulClient.Status().Leader() + require.NoError(t, err) + require.Equal(t, "leader", leader) +} diff --git a/control-plane/subcommand/server-acl-init/anonymous_token.go b/control-plane/subcommand/server-acl-init/anonymous_token.go index 2f7ad6f513..b050558968 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token.go @@ -4,6 +4,7 @@ package serveraclinit import ( + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" ) @@ -14,8 +15,8 @@ const ( // configureAnonymousPolicy sets up policies and tokens so that Consul DNS and // cross-datacenter Consul connect calls will work. -func (c *Command) configureAnonymousPolicy(consulClient *api.Client) error { - exists, err := checkIfAnonymousTokenPolicyExists(consulClient) +func (c *Command) configureAnonymousPolicy(client *consul.DynamicClient) error { + exists, err := checkIfAnonymousTokenPolicyExists(client) if err != nil { c.log.Error("Error checking if anonymous token policy exists", "err", err) return err @@ -40,7 +41,7 @@ func (c *Command) configureAnonymousPolicy(consulClient *api.Client) error { err = c.untilSucceeds("creating anonymous token policy - PUT /v1/acl/policy", func() error { - return c.createOrUpdateACLPolicy(anonPolicy, consulClient) + return c.createOrUpdateACLPolicy(anonPolicy, client) }) if err != nil { return err @@ -55,13 +56,17 @@ func (c *Command) configureAnonymousPolicy(consulClient *api.Client) error { // Update anonymous token to include this policy return c.untilSucceeds("updating anonymous token with policy", func() error { - _, _, err := consulClient.ACL().TokenUpdate(&aToken, &api.WriteOptions{}) + err := client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + _, _, err = client.ConsulClient.ACL().TokenUpdate(&aToken, &api.WriteOptions{}) return err }) } -func checkIfAnonymousTokenPolicyExists(consulClient *api.Client) (bool, error) { - token, _, err := consulClient.ACL().TokenRead(anonymousTokenAccessorID, nil) +func checkIfAnonymousTokenPolicyExists(client *consul.DynamicClient) (bool, error) { + token, _, err := client.ConsulClient.ACL().TokenRead(anonymousTokenAccessorID, nil) if err != nil { return false, err } diff --git a/control-plane/subcommand/server-acl-init/anonymous_token_test.go b/control-plane/subcommand/server-acl-init/anonymous_token_test.go index 8ed58c045f..41689a88c7 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token_test.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" @@ -39,16 +40,16 @@ func Test_configureAnonymousPolicy(t *testing.T) { require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) bootToken := getBootToken(t, k8s, resourcePrefix, ns) - consul, err := api.NewClient(&api.Config{ + client, err := consul.NewDynamicClient(&api.Config{ Address: consulHTTPAddr, Token: bootToken, }) require.NoError(t, err) - err = cmd.configureAnonymousPolicy(consul) + err = cmd.configureAnonymousPolicy(client) require.NoError(t, err) - policy, _, err := consul.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) + policy, _, err := client.ConsulClient.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) require.NoError(t, err) testPolicy := api.ACLPolicy{ @@ -57,13 +58,13 @@ func Test_configureAnonymousPolicy(t *testing.T) { Description: "Anonymous token Policy", Rules: `acl = "read"`, } - readOnlyPolicy, _, err := consul.ACL().PolicyUpdate(&testPolicy, &api.WriteOptions{}) + readOnlyPolicy, _, err := client.ConsulClient.ACL().PolicyUpdate(&testPolicy, &api.WriteOptions{}) require.NoError(t, err) - err = cmd.configureAnonymousPolicy(consul) + err = cmd.configureAnonymousPolicy(client) require.NoError(t, err) - actualPolicy, _, err := consul.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) + actualPolicy, _, err := client.ConsulClient.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) require.NoError(t, err) // assert policy is still same. diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index e1bd3c2356..9ceb7f8a11 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -115,6 +115,9 @@ type Command struct { ctx context.Context retryDuration time.Duration + // the amount of time to contact the Consul API before timing out + apiTimeoutDuration time.Duration + // log log hclog.Logger @@ -233,6 +236,12 @@ func (c *Command) init() { if c.retryDuration == 0 { c.retryDuration = 1 * time.Second } + + // Most of the API calls are in an infinite loop until the command cancels. This timeout + // allows us to refresh the server IPs so that calls will succeed. + if c.apiTimeoutDuration == 0 { + c.apiTimeoutDuration = 2 * time.Minute + } } func (c *Command) Synopsis() string { return synopsis } @@ -302,18 +311,6 @@ func (c *Command) Run(args []string) int { } } - var ipAddrs []net.IPAddr - if err := backoff.Retry(func() error { - ipAddrs, err = netaddrs.IPAddrs(c.ctx, c.consulFlags.Addresses, c.log) - if err != nil { - c.log.Error("Error resolving IP Address", "err", err) - return err - } - return nil - }, exponentialBackoffWithMaxInterval()); err != nil { - c.UI.Error(err.Error()) - } - if err := c.configureSecretsBackend(); err != nil { c.log.Error(err.Error()) return 1 @@ -328,8 +325,22 @@ func (c *Command) Run(args []string) int { c.log.Info("ACL replication is enabled so skipping Consul server ACL bootstrapping") bootstrapToken = aclReplicationToken } else { - bootstrapToken, err = c.bootstrapServers(ipAddrs, c.backend) - if err != nil { + // During upgrades, there is a rare case where a consul-server statefulset may be rotated out while the + // bootstrap tokens are being updated. Catch this case, refresh the server ip addresses and try again. + if err := backoff.Retry(func() error { + ipAddrs, err := c.serverIPAddresses() + if err != nil { + c.log.Error(err.Error()) + return err + } + + bootstrapToken, err = c.bootstrapServers(ipAddrs, c.backend) + if err != nil { + c.log.Error(err.Error()) + return err + } + return nil + }, exponentialBackoffWithMaxInterval()); err != nil { c.log.Error(err.Error()) return 1 } @@ -363,12 +374,12 @@ func (c *Command) Run(args []string) int { return 1 } - consulClient, err := consul.NewClientFromConnMgrState(c.consulFlags.ConsulClientConfig(), c.state) + dynamicClient, err := consul.NewDynamicClientFromConnMgr(c.consulFlags.ConsulClientConfig(), watcher) if err != nil { c.log.Error(fmt.Sprintf("Error creating Consul client for addr %q: %s", c.state.Address, err)) return 1 } - consulDC, primaryDC, err := c.consulDatacenterList(consulClient) + consulDC, primaryDC, err := c.consulDatacenterList(dynamicClient) if err != nil { c.log.Error("Error getting datacenter name", "err", err) return 1 @@ -379,9 +390,9 @@ func (c *Command) Run(args []string) int { if c.consulFlags.Partition == consulDefaultPartition && primary { // Partition token is local because only the Primary datacenter can have Admin Partitions. if c.flagPartitionTokenFile != "" { - err = c.createACLWithSecretID("partitions", partitionRules, consulDC, primary, consulClient, partitionToken, true) + err = c.createACLWithSecretID("partitions", partitionRules, consulDC, primary, dynamicClient, partitionToken, true) } else { - err = c.createLocalACL("partitions", partitionRules, consulDC, primary, consulClient) + err = c.createLocalACL("partitions", partitionRules, consulDC, primary, dynamicClient) } if err != nil { c.log.Error(err.Error()) @@ -408,7 +419,7 @@ func (c *Command) Run(args []string) int { } err = c.untilSucceeds(fmt.Sprintf("creating %s policy", policyTmpl.Name), func() error { - return c.createOrUpdateACLPolicy(policyTmpl, consulClient) + return c.createOrUpdateACLPolicy(policyTmpl, dynamicClient) }) if err != nil { c.log.Error("Error creating or updating the cross namespace policy", "err", err) @@ -425,7 +436,7 @@ func (c *Command) Run(args []string) int { Name: consulDefaultNamespace, ACLs: &aclConfig, } - _, _, err = consulClient.Namespaces().Update(&consulNamespace, &api.WriteOptions{}) + _, _, err = dynamicClient.ConsulClient.Namespaces().Update(&consulNamespace, &api.WriteOptions{}) if err != nil { if strings.Contains(strings.ToLower(err.Error()), "unexpected response code: 404") { // If this returns a 404 it's most likely because they're not running @@ -441,7 +452,7 @@ func (c *Command) Run(args []string) int { // Create the component auth method, this is the auth method that Consul components will use // to issue an `ACL().Login()` against at startup, for local tokens. localComponentAuthMethodName := c.withPrefix("k8s-component-auth-method") - err = c.configureLocalComponentAuthMethod(consulClient, localComponentAuthMethodName) + err = c.configureLocalComponentAuthMethod(dynamicClient, localComponentAuthMethodName) if err != nil { c.log.Error(err.Error()) return 1 @@ -449,7 +460,7 @@ func (c *Command) Run(args []string) int { globalComponentAuthMethodName := fmt.Sprintf("%s-%s", localComponentAuthMethodName, consulDC) if !primary && c.flagAuthMethodHost != "" { - err = c.configureGlobalComponentAuthMethod(consulClient, globalComponentAuthMethodName, primaryDC) + err = c.configureGlobalComponentAuthMethod(dynamicClient, globalComponentAuthMethodName, primaryDC) if err != nil { c.log.Error(err.Error()) return 1 @@ -464,7 +475,7 @@ func (c *Command) Run(args []string) int { } serviceAccountName := c.withPrefix("client") - err = c.createACLPolicyRoleAndBindingRule("client", agentRules, consulDC, primaryDC, false, primary, localComponentAuthMethodName, serviceAccountName, consulClient) + err = c.createACLPolicyRoleAndBindingRule("client", agentRules, consulDC, primaryDC, false, primary, localComponentAuthMethodName, serviceAccountName, dynamicClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -480,7 +491,7 @@ func (c *Command) Run(args []string) int { if c.consulFlags.Partition != "" { anonTokenConfig.APIClientConfig.Partition = consulDefaultPartition } - anonTokenClient, err := consul.NewClientFromConnMgrState(anonTokenConfig, c.state) + anonTokenClient, err := consul.NewDynamicClientFromConnMgr(anonTokenConfig, watcher) if err != nil { c.log.Error(err.Error()) return 1 @@ -511,9 +522,9 @@ func (c *Command) Run(args []string) int { if !primary { componentAuthMethodName = globalComponentAuthMethodName } - err = c.createACLPolicyRoleAndBindingRule("sync-catalog", syncRules, consulDC, primaryDC, globalPolicy, primary, componentAuthMethodName, serviceAccountName, consulClient) + err = c.createACLPolicyRoleAndBindingRule("sync-catalog", syncRules, consulDC, primaryDC, globalPolicy, primary, componentAuthMethodName, serviceAccountName, dynamicClient) } else { - err = c.createACLPolicyRoleAndBindingRule("sync-catalog", syncRules, consulDC, primaryDC, localPolicy, primary, componentAuthMethodName, serviceAccountName, consulClient) + err = c.createACLPolicyRoleAndBindingRule("sync-catalog", syncRules, consulDC, primaryDC, localPolicy, primary, componentAuthMethodName, serviceAccountName, dynamicClient) } if err != nil { c.log.Error(err.Error()) @@ -523,7 +534,7 @@ func (c *Command) Run(args []string) int { if c.flagConnectInject { connectAuthMethodName := c.withPrefix("k8s-auth-method") - err := c.configureConnectInjectAuthMethod(consulClient, connectAuthMethodName) + err := c.configureConnectInjectAuthMethod(dynamicClient, connectAuthMethodName) if err != nil { c.log.Error(err.Error()) return 1 @@ -545,7 +556,7 @@ func (c *Command) Run(args []string) int { if !primary { componentAuthMethodName = globalComponentAuthMethodName } - err = c.createACLPolicyRoleAndBindingRule("connect-inject", injectRules, consulDC, primaryDC, globalPolicy, primary, componentAuthMethodName, serviceAccountName, consulClient) + err = c.createACLPolicyRoleAndBindingRule("connect-inject", injectRules, consulDC, primaryDC, globalPolicy, primary, componentAuthMethodName, serviceAccountName, dynamicClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -555,9 +566,9 @@ func (c *Command) Run(args []string) int { if c.flagCreateEntLicenseToken { var err error if c.consulFlags.Partition != "" { - err = c.createLocalACL("enterprise-license", entPartitionLicenseRules, consulDC, primary, consulClient) + err = c.createLocalACL("enterprise-license", entPartitionLicenseRules, consulDC, primary, dynamicClient) } else { - err = c.createLocalACL("enterprise-license", entLicenseRules, consulDC, primary, consulClient) + err = c.createLocalACL("enterprise-license", entLicenseRules, consulDC, primary, dynamicClient) } if err != nil { c.log.Error(err.Error()) @@ -567,7 +578,7 @@ func (c *Command) Run(args []string) int { if c.flagSnapshotAgent { serviceAccountName := c.withPrefix("server") - if err := c.createACLPolicyRoleAndBindingRule("snapshot-agent", snapshotAgentRules, consulDC, primaryDC, localPolicy, primary, localComponentAuthMethodName, serviceAccountName, consulClient); err != nil { + if err := c.createACLPolicyRoleAndBindingRule("snapshot-agent", snapshotAgentRules, consulDC, primaryDC, localPolicy, primary, localComponentAuthMethodName, serviceAccountName, dynamicClient); err != nil { c.log.Error(err.Error()) return 1 } @@ -588,7 +599,7 @@ func (c *Command) Run(args []string) int { if !primary { authMethodName = globalComponentAuthMethodName } - err = c.createACLPolicyRoleAndBindingRule("api-gateway-controller", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, consulClient) + err = c.createACLPolicyRoleAndBindingRule("api-gateway-controller", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, dynamicClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -609,7 +620,7 @@ func (c *Command) Run(args []string) int { if !primary { authMethodName = globalComponentAuthMethodName } - err = c.createACLPolicyRoleAndBindingRule("mesh-gateway", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, consulClient) + err = c.createACLPolicyRoleAndBindingRule("mesh-gateway", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, dynamicClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -626,7 +637,7 @@ func (c *Command) Run(args []string) int { PrimaryDC: primaryDC, Primary: primary, } - err := c.configureGateway(params, consulClient) + err := c.configureGateway(params, dynamicClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -643,7 +654,7 @@ func (c *Command) Run(args []string) int { PrimaryDC: primaryDC, Primary: primary, } - err := c.configureGateway(params, consulClient) + err := c.configureGateway(params, dynamicClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -659,9 +670,9 @@ func (c *Command) Run(args []string) int { // Policy must be global because it replicates from the primary DC // and so the primary DC needs to be able to accept the token. if aclReplicationToken != "" { - err = c.createACLWithSecretID(common.ACLReplicationTokenName, rules, consulDC, primary, consulClient, aclReplicationToken, false) + err = c.createACLWithSecretID(common.ACLReplicationTokenName, rules, consulDC, primary, dynamicClient, aclReplicationToken, false) } else { - err = c.createGlobalACL(common.ACLReplicationTokenName, rules, consulDC, primary, consulClient) + err = c.createGlobalACL(common.ACLReplicationTokenName, rules, consulDC, primary, dynamicClient) } if err != nil { c.log.Error(err.Error()) @@ -685,7 +696,7 @@ func exponentialBackoffWithMaxInterval() *backoff.ExponentialBackOff { // configureGlobalComponentAuthMethod sets up an AuthMethod in the primary datacenter, // that the Consul components will use to issue global ACL tokens with. -func (c *Command) configureGlobalComponentAuthMethod(consulClient *api.Client, authMethodName, primaryDC string) error { +func (c *Command) configureGlobalComponentAuthMethod(client *consul.DynamicClient, authMethodName, primaryDC string) error { // Create the auth method template. This requires calls to the kubernetes environment. authMethod, err := c.createAuthMethodTmpl(authMethodName, false) if err != nil { @@ -693,29 +704,33 @@ func (c *Command) configureGlobalComponentAuthMethod(consulClient *api.Client, a } authMethod.TokenLocality = "global" writeOptions := &api.WriteOptions{Datacenter: primaryDC} - return c.createAuthMethod(consulClient, &authMethod, writeOptions) + return c.createAuthMethod(client, &authMethod, writeOptions) } // configureLocalComponentAuthMethod sets up an AuthMethod in the same datacenter, // that the Consul components will use to issue local ACL tokens with. -func (c *Command) configureLocalComponentAuthMethod(consulClient *api.Client, authMethodName string) error { +func (c *Command) configureLocalComponentAuthMethod(client *consul.DynamicClient, authMethodName string) error { // Create the auth method template. This requires calls to the kubernetes environment. authMethod, err := c.createAuthMethodTmpl(authMethodName, false) if err != nil { return err } - return c.createAuthMethod(consulClient, &authMethod, &api.WriteOptions{}) + return c.createAuthMethod(client, &authMethod, &api.WriteOptions{}) } // createAuthMethod creates the desired Authmethod. -func (c *Command) createAuthMethod(consulClient *api.Client, authMethod *api.ACLAuthMethod, writeOptions *api.WriteOptions) error { +func (c *Command) createAuthMethod(client *consul.DynamicClient, authMethod *api.ACLAuthMethod, writeOptions *api.WriteOptions) error { return c.untilSucceeds(fmt.Sprintf("creating auth method %s", authMethod.Name), func() error { var err error + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } // `AuthMethodCreate` will also be able to update an existing // AuthMethod based on the name provided. This means that any // configuration changes will correctly update the AuthMethod. - _, _, err = consulClient.ACL().AuthMethodCreate(authMethod, writeOptions) + _, _, err = client.ConsulClient.ACL().AuthMethodCreate(authMethod, writeOptions) return err }) } @@ -740,7 +755,7 @@ type ConfigureGatewayParams struct { Primary bool } -func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, consulClient *api.Client) error { +func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, client *consul.DynamicClient) error { // Each gateway needs to be configured // separately because users may need to attach different policies // to each gateway role depending on what services it represents. @@ -795,7 +810,7 @@ func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, consulC serviceAccountName := c.withPrefix(name) err = c.createACLPolicyRoleAndBindingRule(name, rules, gatewayParams.ConsulDC, gatewayParams.PrimaryDC, localPolicy, - gatewayParams.Primary, gatewayParams.AuthMethodName, serviceAccountName, consulClient) + gatewayParams.Primary, gatewayParams.AuthMethodName, serviceAccountName, client) if err != nil { c.log.Error(err.Error()) return err @@ -866,8 +881,10 @@ func (c *Command) configureSecretsBackend() error { } // untilSucceeds runs op until it returns a nil error. +// If c.timeoutDuration is reached it will exit so that the command can be retried with updated server settings // If c.cmdTimeout is cancelled it will exit. func (c *Command) untilSucceeds(opName string, op func() error) error { + timeoutCh := time.After(c.apiTimeoutDuration) for { err := op() if err == nil { @@ -881,6 +898,8 @@ func (c *Command) untilSucceeds(opName string, op func() error) error { select { case <-time.After(c.retryDuration): continue + case <-timeoutCh: + return errors.New("reached api timeout") case <-c.ctx.Done(): return errors.New("reached command timeout") } @@ -896,12 +915,16 @@ func (c *Command) withPrefix(resource string) string { // consulDatacenterList returns the current datacenter name and the primary datacenter using the // /agent/self API endpoint. -func (c *Command) consulDatacenterList(client *api.Client) (string, string, error) { +func (c *Command) consulDatacenterList(client *consul.DynamicClient) (string, string, error) { var agentCfg map[string]map[string]interface{} err := c.untilSucceeds("calling /agent/self to get datacenter", func() error { var opErr error - agentCfg, opErr = client.Agent().Self() + opErr = client.RefreshClient() + if opErr != nil { + c.log.Error("could not refresh client", opErr) + } + agentCfg, opErr = client.ConsulClient.Agent().Self() return opErr }) if err != nil { @@ -1032,6 +1055,25 @@ func (c *Command) quitVaultAgent() { } } +// serverIPAddresses attempts to refresh the server IPs using netaddrs methods. These 'raw' IPs are used +// when boostrapping ACLs and before consul-server-connection-manager runs. +func (c *Command) serverIPAddresses() ([]net.IPAddr, error) { + var ipAddrs []net.IPAddr + var err error + if err = backoff.Retry(func() error { + ipAddrs, err = netaddrs.IPAddrs(c.ctx, c.consulFlags.Addresses, c.log) + if err != nil { + c.log.Error("Error resolving IP Address", "err", err) + return err + } + c.log.Info("Refreshing server IP addresses", "addresses", ipAddrs) + return nil + }, exponentialBackoffWithMaxInterval()); err != nil { + return nil, err + } + return ipAddrs, nil +} + const ( consulDefaultNamespace = "default" consulDefaultPartition = "default" diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index 928a50e285..af15429508 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -30,6 +30,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/cert" "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" @@ -1389,14 +1390,14 @@ func TestConsulDatacenterList(t *testing.T) { })) defer consulServer.Close() - consulClient, err := api.NewClient(&api.Config{Address: consulServer.URL}) + client, err := consul.NewDynamicClient(&api.Config{Address: consulServer.URL}) require.NoError(t, err) command := Command{ log: hclog.New(hclog.DefaultOptions), ctx: context.Background(), } - actDC, actPrimaryDC, err := command.consulDatacenterList(consulClient) + actDC, actPrimaryDC, err := command.consulDatacenterList(client) if c.expErr != "" { require.EqualError(t, err, c.expErr) } else { diff --git a/control-plane/subcommand/server-acl-init/connect_inject.go b/control-plane/subcommand/server-acl-init/connect_inject.go index f853144a2c..0e373d2ea5 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject.go +++ b/control-plane/subcommand/server-acl-init/connect_inject.go @@ -10,6 +10,7 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) @@ -21,7 +22,7 @@ const defaultKubernetesHost = "https://kubernetes.default.svc" // configureConnectInject sets up auth methods so that connect injection will // work. -func (c *Command) configureConnectInjectAuthMethod(consulClient *api.Client, authMethodName string) error { +func (c *Command) configureConnectInjectAuthMethod(client *consul.DynamicClient, authMethodName string) error { // Create the auth method template. This requires calls to the // kubernetes environment. @@ -47,7 +48,11 @@ func (c *Command) configureConnectInjectAuthMethod(consulClient *api.Client, aut err = c.untilSucceeds(fmt.Sprintf("checking or creating namespace %s", c.flagConsulInjectDestinationNamespace), func() error { - _, err := namespaces.EnsureExists(consulClient, c.flagConsulInjectDestinationNamespace, "cross-namespace-policy") + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + _, err := namespaces.EnsureExists(client.ConsulClient, c.flagConsulInjectDestinationNamespace, "cross-namespace-policy") return err }) if err != nil { @@ -59,10 +64,14 @@ func (c *Command) configureConnectInjectAuthMethod(consulClient *api.Client, aut err = c.untilSucceeds(fmt.Sprintf("creating auth method %s", authMethodTmpl.Name), func() error { var err error + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } // `AuthMethodCreate` will also be able to update an existing // AuthMethod based on the name provided. This means that any // configuration changes will correctly update the AuthMethod. - _, _, err = consulClient.ACL().AuthMethodCreate(&authMethodTmpl, &writeOptions) + _, _, err = client.ConsulClient.ACL().AuthMethodCreate(&authMethodTmpl, &writeOptions) return err }) if err != nil { @@ -92,7 +101,7 @@ func (c *Command) configureConnectInjectAuthMethod(consulClient *api.Client, aut } } - return c.createConnectBindingRule(consulClient, authMethodName, &abr) + return c.createConnectBindingRule(client, authMethodName, &abr) } // createAuthMethodTmpl sets up the auth method template based on the connect-injector's service account diff --git a/control-plane/subcommand/server-acl-init/create_or_update.go b/control-plane/subcommand/server-acl-init/create_or_update.go index 8099e6e9c6..254fba4964 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update.go +++ b/control-plane/subcommand/server-acl-init/create_or_update.go @@ -11,13 +11,14 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // createACLPolicyRoleAndBindingRule will create the ACL Policy for the component // then create a set of ACLRole and ACLBindingRule which tie the component's serviceaccount // to the authMethod, allowing the serviceaccount to later be allowed to issue a Consul Login. -func (c *Command) createACLPolicyRoleAndBindingRule(componentName, rules, dc, primaryDC string, global, primary bool, authMethodName, serviceAccountName string, client *api.Client) error { +func (c *Command) createACLPolicyRoleAndBindingRule(componentName, rules, dc, primaryDC string, global, primary bool, authMethodName, serviceAccountName string, client *consul.DynamicClient) error { // Create policy with the given rules. policyName := fmt.Sprintf("%s-policy", componentName) if c.flagFederation && !primary { @@ -55,7 +56,7 @@ func (c *Command) createACLPolicyRoleAndBindingRule(componentName, rules, dc, pr } // addRoleAndBindingRule adds an ACLRole and ACLBindingRule which reference the authMethod. -func (c *Command) addRoleAndBindingRule(client *api.Client, componentName, serviceAccountName, authMethodName string, policies []*api.ACLRolePolicyLink, global, primary bool, primaryDC, dc string) error { +func (c *Command) addRoleAndBindingRule(client *consul.DynamicClient, componentName, serviceAccountName, authMethodName string, policies []*api.ACLRolePolicyLink, global, primary bool, primaryDC, dc string) error { // This is the ACLRole which will allow the component which uses the serviceaccount // to be able to do a consul login. aclRoleName := c.withPrefix(fmt.Sprintf("%s-acl-role", componentName)) @@ -92,24 +93,28 @@ func (c *Command) addRoleAndBindingRule(client *api.Client, componentName, servi // updateOrCreateACLRole will query to see if existing role is in place and update them // or create them if they do not yet exist. -func (c *Command) updateOrCreateACLRole(client *api.Client, role *api.ACLRole) error { +func (c *Command) updateOrCreateACLRole(client *consul.DynamicClient, role *api.ACLRole) error { err := c.untilSucceeds(fmt.Sprintf("update or create acl role for %s", role.Name), func() error { var err error - aclRole, _, err := client.ACL().RoleReadByName(role.Name, &api.QueryOptions{}) + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + aclRole, _, err := client.ConsulClient.ACL().RoleReadByName(role.Name, &api.QueryOptions{}) if err != nil { c.log.Error("unable to read ACL Roles", err) return err } if aclRole != nil { - _, _, err := client.ACL().RoleUpdate(aclRole, &api.WriteOptions{}) + _, _, err := client.ConsulClient.ACL().RoleUpdate(aclRole, &api.WriteOptions{}) if err != nil { c.log.Error("unable to update role", err) return err } return nil } - _, _, err = client.ACL().RoleCreate(role, &api.WriteOptions{}) + _, _, err = client.ConsulClient.ACL().RoleCreate(role, &api.WriteOptions{}) if err != nil { c.log.Error("unable to create role", err) return err @@ -121,7 +126,7 @@ func (c *Command) updateOrCreateACLRole(client *api.Client, role *api.ACLRole) e // createConnectBindingRule will query to see if existing binding rules are in place and update them // or create them if they do not yet exist. -func (c *Command) createConnectBindingRule(client *api.Client, authMethodName string, abr *api.ACLBindingRule) error { +func (c *Command) createConnectBindingRule(client *consul.DynamicClient, authMethodName string, abr *api.ACLBindingRule) error { // Binding rule list api call query options. queryOptions := api.QueryOptions{} @@ -136,12 +141,16 @@ func (c *Command) createConnectBindingRule(client *api.Client, authMethodName st return c.createOrUpdateBindingRule(client, authMethodName, abr, &queryOptions, nil) } -func (c *Command) createOrUpdateBindingRule(client *api.Client, authMethodName string, abr *api.ACLBindingRule, queryOptions *api.QueryOptions, writeOptions *api.WriteOptions) error { +func (c *Command) createOrUpdateBindingRule(client *consul.DynamicClient, authMethodName string, abr *api.ACLBindingRule, queryOptions *api.QueryOptions, writeOptions *api.WriteOptions) error { var existingRules []*api.ACLBindingRule err := c.untilSucceeds(fmt.Sprintf("listing binding rules for auth method %s", authMethodName), func() error { var err error - existingRules, _, err = client.ACL().BindingRuleList(authMethodName, queryOptions) + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + existingRules, _, err = client.ConsulClient.ACL().BindingRuleList(authMethodName, queryOptions) return err }) if err != nil { @@ -170,13 +179,21 @@ func (c *Command) createOrUpdateBindingRule(client *api.Client, authMethodName s c.log.Info("unable to find a matching ACL binding rule to update. creating ACL binding rule.") err = c.untilSucceeds(fmt.Sprintf("creating acl binding rule for %s", authMethodName), func() error { - _, _, err := client.ACL().BindingRuleCreate(abr, writeOptions) + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + _, _, err := client.ConsulClient.ACL().BindingRuleCreate(abr, writeOptions) return err }) } else { err = c.untilSucceeds(fmt.Sprintf("updating acl binding rule for %s", authMethodName), func() error { - _, _, err := client.ACL().BindingRuleUpdate(abr, writeOptions) + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + _, _, err := client.ConsulClient.ACL().BindingRuleUpdate(abr, writeOptions) return err }) } @@ -184,7 +201,11 @@ func (c *Command) createOrUpdateBindingRule(client *api.Client, authMethodName s // Otherwise create the binding rule err = c.untilSucceeds(fmt.Sprintf("creating acl binding rule for %s", authMethodName), func() error { - _, _, err := client.ACL().BindingRuleCreate(abr, writeOptions) + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + _, _, err := client.ConsulClient.ACL().BindingRuleCreate(abr, writeOptions) return err }) } @@ -193,19 +214,19 @@ func (c *Command) createOrUpdateBindingRule(client *api.Client, authMethodName s // createLocalACL creates a policy and acl token for this dc (datacenter), i.e. // the policy is only valid for this datacenter and the token is a local token. -func (c *Command) createLocalACL(name, rules, dc string, isPrimary bool, consulClient *api.Client) error { +func (c *Command) createLocalACL(name, rules, dc string, isPrimary bool, consulClient *consul.DynamicClient) error { return c.createACL(name, rules, true, dc, isPrimary, consulClient, "") } // createGlobalACL creates a global policy and acl token. The policy is valid // for all datacenters and the token is global. dc must be passed because the // policy name may have the datacenter name appended. -func (c *Command) createGlobalACL(name, rules, dc string, isPrimary bool, consulClient *api.Client) error { +func (c *Command) createGlobalACL(name, rules, dc string, isPrimary bool, consulClient *consul.DynamicClient) error { return c.createACL(name, rules, false, dc, isPrimary, consulClient, "") } // createACLWithSecretID creates a global policy and acl token with provided secret ID. -func (c *Command) createACLWithSecretID(name, rules, dc string, isPrimary bool, consulClient *api.Client, secretID string, local bool) error { +func (c *Command) createACLWithSecretID(name, rules, dc string, isPrimary bool, consulClient *consul.DynamicClient, secretID string, local bool) error { return c.createACL(name, rules, local, dc, isPrimary, consulClient, secretID) } @@ -215,7 +236,7 @@ func (c *Command) createACLWithSecretID(name, rules, dc string, isPrimary bool, // When secretID is provided, we will use that value for the created token and // will skip writing it to a Kubernetes secret (because in this case we assume that // this value already exists in some secrets storage). -func (c *Command) createACL(name, rules string, localToken bool, dc string, isPrimary bool, consulClient *api.Client, secretID string) error { +func (c *Command) createACL(name, rules string, localToken bool, dc string, isPrimary bool, client *consul.DynamicClient, secretID string) error { // Create policy with the given rules. policyName := fmt.Sprintf("%s-token", name) if c.flagFederation && !isPrimary { @@ -235,7 +256,7 @@ func (c *Command) createACL(name, rules string, localToken bool, dc string, isPr } err := c.untilSucceeds(fmt.Sprintf("creating %s policy", policyTmpl.Name), func() error { - return c.createOrUpdateACLPolicy(policyTmpl, consulClient) + return c.createOrUpdateACLPolicy(policyTmpl, client) }) if err != nil { return err @@ -263,7 +284,7 @@ func (c *Command) createACL(name, rules string, localToken bool, dc string, isPr } else { // If secretID is provided, we check if the token with secretID already exists in Consul // and exit if it does. Otherwise, set the secretID to the provided value. - _, _, err = consulClient.ACL().TokenReadSelf(&api.QueryOptions{Token: secretID}) + _, _, err = client.ConsulClient.ACL().TokenReadSelf(&api.QueryOptions{Token: secretID}) if err == nil { c.log.Info("ACL replication token already exists; skipping creation") return nil @@ -275,7 +296,11 @@ func (c *Command) createACL(name, rules string, localToken bool, dc string, isPr var token string err = c.untilSucceeds(fmt.Sprintf("creating token for policy %s", policyTmpl.Name), func() error { - createdToken, _, err := consulClient.ACL().TokenCreate(&tokenTmpl, &api.WriteOptions{}) + err = client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + createdToken, _, err := client.ConsulClient.ACL().TokenCreate(&tokenTmpl, &api.WriteOptions{}) if err == nil { token = createdToken.SecretID } @@ -305,9 +330,14 @@ func (c *Command) createACL(name, rules string, localToken bool, dc string, isPr return nil } -func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, consulClient *api.Client) error { +func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, client *consul.DynamicClient) error { + err := client.RefreshClient() + if err != nil { + c.log.Error("could not refresh client", err) + } + // Attempt to create the ACL policy. - _, _, err := consulClient.ACL().PolicyCreate(&policy, &api.WriteOptions{}) + _, _, err = client.ConsulClient.ACL().PolicyCreate(&policy, &api.WriteOptions{}) // With the introduction of Consul namespaces, if someone upgrades into a // Consul version with namespace support or changes any of their namespace @@ -320,7 +350,7 @@ func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, consulClient *ap // The policy ID is required in any PolicyUpdate call, so first we need to // get the existing policy to extract its ID. - existingPolicies, _, err := consulClient.ACL().PolicyList(&api.QueryOptions{}) + existingPolicies, _, err := client.ConsulClient.ACL().PolicyList(&api.QueryOptions{}) if err != nil { return err } @@ -345,7 +375,7 @@ func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, consulClient *ap } // Update the policy now that we've found its ID - _, _, err = consulClient.ACL().PolicyUpdate(&policy, &api.WriteOptions{}) + _, _, err = client.ConsulClient.ACL().PolicyUpdate(&policy, &api.WriteOptions{}) return err } return err diff --git a/control-plane/subcommand/server-acl-init/create_or_update_test.go b/control-plane/subcommand/server-acl-init/create_or_update_test.go index 96d3945617..7fd7dc29b3 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update_test.go +++ b/control-plane/subcommand/server-acl-init/create_or_update_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/go-hclog" @@ -41,7 +42,7 @@ func TestCreateOrUpdateACLPolicy_ErrorsIfDescriptionDoesNotMatch(t *testing.T) { svr.WaitForLeader(t) // Get a Consul client. - consul, err := api.NewClient(&api.Config{ + client, err := consul.NewDynamicClient(&api.Config{ Address: svr.HTTPAddr, Token: bootToken, }) @@ -50,7 +51,7 @@ func TestCreateOrUpdateACLPolicy_ErrorsIfDescriptionDoesNotMatch(t *testing.T) { // Create the policy manually. policyDescription := "not the expected description" policyName := "policy-name" - policy, _, err := consul.ACL().PolicyCreate(&api.ACLPolicy{ + policy, _, err := client.ConsulClient.ACL().PolicyCreate(&api.ACLPolicy{ Name: policyName, Description: policyDescription, }, nil) @@ -60,14 +61,14 @@ func TestCreateOrUpdateACLPolicy_ErrorsIfDescriptionDoesNotMatch(t *testing.T) { err = cmd.createOrUpdateACLPolicy(api.ACLPolicy{ Name: policyName, Description: "expected description", - }, consul) + }, client) require.EqualError(err, "policy found with name \"policy-name\" but not with expected description \"expected description\";"+ " if this policy was created manually it must be renamed to something else because this name is reserved by consul-k8s", ) // Check that the policy wasn't modified. - rereadPolicy, _, err := consul.ACL().PolicyRead(policy.ID, nil) + rereadPolicy, _, err := client.ConsulClient.ACL().PolicyRead(policy.ID, nil) require.NoError(err) require.Equal(policyDescription, rereadPolicy.Description) } @@ -93,14 +94,14 @@ func TestCreateOrUpdateACLPolicy(t *testing.T) { svr.WaitForLeader(t) // Get a Consul client. - consul, err := api.NewClient(&api.Config{ + client, err := consul.NewDynamicClient(&api.Config{ Address: svr.HTTPAddr, Token: bootToken, }) // Make sure the ACL system is bootstrapped first require.Eventually(func() bool { - _, _, err := consul.ACL().PolicyList(nil) + _, _, err := client.ConsulClient.ACL().PolicyList(nil) return err == nil }, 5*time.Second, 500*time.Millisecond) @@ -136,9 +137,9 @@ func TestCreateOrUpdateACLPolicy(t *testing.T) { Name: tt.PolicyName, Description: tt.PolicyDescription, Rules: tt.Rules, - }, consul) + }, client) require.Nil(err) - policy, _, err := consul.ACL().PolicyReadByName(tt.PolicyName, nil) + policy, _, err := client.ConsulClient.ACL().PolicyReadByName(tt.PolicyName, nil) require.Nil(err) require.Equal(tt.Rules, policy.Rules) require.Equal(tt.PolicyName, policy.Name) diff --git a/control-plane/subcommand/server-acl-init/servers.go b/control-plane/subcommand/server-acl-init/servers.go index c530f648e5..9665ec8f48 100644 --- a/control-plane/subcommand/server-acl-init/servers.go +++ b/control-plane/subcommand/server-acl-init/servers.go @@ -120,17 +120,17 @@ func (c *Command) setServerTokens(serverAddresses []net.IPAddr, bootstrapToken s clientConfig := c.consulFlags.ConsulClientConfig().APIClientConfig clientConfig.Address = fmt.Sprintf("%s:%d", serverAddresses[0].IP.String(), c.consulFlags.HTTPPort) clientConfig.Token = bootstrapToken - serverClient, err := consul.NewClient(clientConfig, + client, err := consul.NewDynamicClientWithTimeout(clientConfig, c.consulFlags.APITimeout) if err != nil { return err } - agentPolicy, err := c.setServerPolicy(serverClient) + agentPolicy, err := c.setServerPolicy(client) if err != nil { return err } - existingTokens, _, err := serverClient.ACL().TokenList(nil) + existingTokens, _, err := client.ConsulClient.ACL().TokenList(nil) if err != nil { return err } @@ -198,7 +198,7 @@ func (c *Command) setServerTokens(serverAddresses []net.IPAddr, bootstrapToken s return nil } -func (c *Command) setServerPolicy(consulClient *api.Client) (api.ACLPolicy, error) { +func (c *Command) setServerPolicy(client *consul.DynamicClient) (api.ACLPolicy, error) { agentRules, err := c.agentRules() if err != nil { c.log.Error("Error templating server agent rules", "err", err) @@ -213,7 +213,7 @@ func (c *Command) setServerPolicy(consulClient *api.Client) (api.ACLPolicy, erro } err = c.untilSucceeds("creating agent policy - PUT /v1/acl/policy", func() error { - return c.createOrUpdateACLPolicy(agentPolicy, consulClient) + return c.createOrUpdateACLPolicy(agentPolicy, client) }) if err != nil { return api.ACLPolicy{}, err From d435688de5b18042b075cf06257f6380be689503 Mon Sep 17 00:00:00 2001 From: wangxinyi7 <121973291+wangxinyi7@users.noreply.github.com> Date: Thu, 23 Nov 2023 10:00:22 -0800 Subject: [PATCH 484/592] uniform make file experience (#3198) * organize the sections * tag the image * add phony to all targets --- Makefile | 169 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 116 insertions(+), 53 deletions(-) diff --git a/Makefile b/Makefile index b5c742d7cd..f803bcd585 100644 --- a/Makefile +++ b/Makefile @@ -6,30 +6,40 @@ KIND_VERSION= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh KIND_NODE_IMAGE= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh acceptance/ci-inputs/kind-inputs.yaml .kindNodeImage) KUBECTL_VERSION= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh acceptance/ci-inputs/kind-inputs.yaml .kubectlVersion) -# ===========> Helm Targets +##@ Helm Targets +.PHONY: gen-helm-docs gen-helm-docs: ## Generate Helm reference docs from values.yaml and update Consul website. Usage: make gen-helm-docs consul=. @cd hack/helm-reference-gen; go run ./... $(consul) +.PHONY: copy-crds-to-chart copy-crds-to-chart: ## Copy generated CRD YAML into charts/consul. Usage: make copy-crds-to-chart @cd hack/copy-crds-to-chart; go run ./... +.PHONY: camel-crds camel-crds: ## Convert snake_case keys in yaml to camelCase. Usage: make camel-crds @cd hack/camel-crds; go run ./... +.PHONY: generate-external-crds generate-external-crds: ## Generate CRDs for externally defined CRDs and copy them to charts/consul. Usage: make generate-external-crds @cd ./control-plane/config/crd/external; \ kustomize build | yq --split-exp '.metadata.name + ".yaml"' --no-doc +.PHONY: bats-tests bats-tests: ## Run Helm chart bats tests. bats --jobs 4 charts/consul/test/unit +##@ Control Plane Targets -# ===========> Control Plane Targets - +.PHONY: control-plane-dev control-plane-dev: ## Build consul-k8s-control-plane binary. @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a amd64 +.PHONY: dev-docker +dev-docker: control-plane-dev-docker ## build dev local dev docker image + docker tag '$(DEV_IMAGE)' 'consul-k8s-control-plane:local' + +.PHONY: control-plane-dev-docker control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) @docker build -t '$(DEV_IMAGE)' \ @@ -40,19 +50,21 @@ control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. --build-arg 'GIT_DESCRIBE=$(GIT_DESCRIBE)' \ -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane +.PHONY: control-plane-dev-skaffold # DANGER: this target is experimental and could be modified/removed at any time. -# Build consul-k8s-control-plane dev Docker image for use with skaffold or local development. -control-plane-dev-skaffold: +control-plane-dev-skaffold: ## Build consul-k8s-control-plane dev Docker image for use with skaffold or local development. @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) @docker build -t '$(DEV_IMAGE)' \ --build-arg 'TARGETARCH=$(GOARCH)' \ -f $(CURDIR)/control-plane/Dockerfile.dev $(CURDIR)/control-plane +.PHONY: check-remote-dev-image-env check-remote-dev-image-env: ifndef REMOTE_DEV_IMAGE $(error REMOTE_DEV_IMAGE is undefined: set this image to /:, e.g. hashicorp/consul-k8s-dev:latest) endif +.PHONY: control-plane-dev-docker-multi-arch control-plane-dev-docker-multi-arch: check-remote-dev-image-env ## Build consul-k8s-control-plane dev multi-arch Docker image. @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a "arm64 amd64" @docker buildx create --use && docker buildx build -t '$(REMOTE_DEV_IMAGE)' \ @@ -64,6 +76,7 @@ control-plane-dev-docker-multi-arch: check-remote-dev-image-env ## Build consul- --push \ -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane +.PHONY: control-plane-fips-dev-docker control-plane-fips-dev-docker: ## Build consul-k8s-control-plane FIPS dev Docker image. @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) --fips @docker build -t '$(DEV_IMAGE)' \ @@ -75,66 +88,75 @@ control-plane-fips-dev-docker: ## Build consul-k8s-control-plane FIPS dev Docker --push \ -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane +.PHONY: control-plane-test control-plane-test: ## Run go test for the control plane. cd control-plane; go test ./... +.PHONY: control-plane-ent-test control-plane-ent-test: ## Run go test with Consul enterprise tests. The consul binary in your PATH must be Consul Enterprise. cd control-plane; go test ./... -tags=enterprise +.PHONY: control-plane-cov control-plane-cov: ## Run go test with code coverage. cd control-plane; go test ./... -coverprofile=coverage.out; go tool cover -html=coverage.out +.PHONY: control-plane-clean control-plane-clean: ## Delete bin and pkg dirs. @rm -rf \ $(CURDIR)/control-plane/bin \ $(CURDIR)/control-plane/pkg +.PHONY: control-plane-lint control-plane-lint: cni-plugin-lint ## Run linter in the control-plane directory. cd control-plane; golangci-lint run -c ../.golangci.yml +.PHONY: cni-plugin-lint cni-plugin-lint: cd control-plane/cni; golangci-lint run -c ../../.golangci.yml +.PHONY: ctrl-generate ctrl-generate: get-controller-gen ## Run CRD code generation. make ensure-controller-gen-version cd control-plane; $(CONTROLLER_GEN) object paths="./..." -# Perform a terraform fmt check but don't change anything -terraform-fmt-check: - @$(CURDIR)/control-plane/build-support/scripts/terraformfmtcheck.sh $(TERRAFORM_DIR) .PHONY: terraform-fmt-check +terraform-fmt-check: ## Perform a terraform fmt check but don't change anything + @$(CURDIR)/control-plane/build-support/scripts/terraformfmtcheck.sh $(TERRAFORM_DIR) -# Format all terraform files according to terraform fmt -terraform-fmt: - @terraform fmt -recursive .PHONY: terraform-fmt +terraform-fmt: ## Format all terraform files according to terraform fmt + @terraform fmt -recursive -# Check for hashicorppreview containers -check-preview-containers: +.PHONY: check-preview-containers +check-preview-containers: ## Check for hashicorppreview containers @source $(CURDIR)/control-plane/build-support/scripts/check-hashicorppreview.sh +##@ CLI Targets -# ===========> CLI Targets -cli-dev: +.PHONY: cli-dev +cli-dev: ## run cli dev @echo "==> Installing consul-k8s CLI tool for ${GOOS}/${GOARCH}" @cd cli; go build -o ./bin/consul-k8s; cp ./bin/consul-k8s ${GOPATH}/bin/ -cli-fips-dev: +.PHONY: cli-fips-dev +cli-fips-dev: ## run cli fips dev @echo "==> Installing consul-k8s CLI tool for ${GOOS}/${GOARCH}" @cd cli; CGO_ENABLED=1 GOEXPERIMENT=boringcrypto go build -o ./bin/consul-k8s -tags "fips"; cp ./bin/consul-k8s ${GOPATH}/bin/ +.PHONY: cli-lint cli-lint: ## Run linter in the control-plane directory. cd cli; golangci-lint run -c ../.golangci.yml +##@ Acceptance Tests Targets -# ===========> Acceptance Tests Targets - +.PHONY: acceptance-lint acceptance-lint: ## Run linter in the control-plane directory. cd acceptance; golangci-lint run -c ../.golangci.yml -# For CNI acceptance tests, the calico CNI pluging needs to be installed on Kind. Our consul-cni plugin will not work +.PHONY: kind-cni-calico +# For CNI acceptance tests, the calico CNI plugin needs to be installed on Kind. Our consul-cni plugin will not work # without another plugin installed first -kind-cni-calico: +kind-cni-calico: ## install cni plugin on kind kubectl create namespace calico-system ||true kubectl create -f $(CURDIR)/acceptance/framework/environment/cni-kind/tigera-operator.yaml # Sleeps are needed as installs can happen too quickly for Kind to handle it @@ -142,15 +164,15 @@ kind-cni-calico: kubectl create -f $(CURDIR)/acceptance/framework/environment/cni-kind/custom-resources.yaml @sleep 20 +.PHONY: kind-delete kind-delete: kind delete cluster --name dc1 kind delete cluster --name dc2 kind delete cluster --name dc3 kind delete cluster --name dc4 - -# Helper target for doing local cni acceptance testing -kind-cni: kind-delete +.PHONY: kind-cni +kind-cni: kind-delete ## Helper target for doing local cni acceptance testing kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc1 --image $(KIND_NODE_IMAGE) make kind-cni-calico kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc2 --image $(KIND_NODE_IMAGE) @@ -160,28 +182,27 @@ kind-cni: kind-delete kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc4 --image $(KIND_NODE_IMAGE) make kind-cni-calico -# Helper target for doing local acceptance testing -kind: kind-delete +.PHONY: kind +kind: kind-delete ## Helper target for doing local acceptance testing kind create cluster --name dc1 --image $(KIND_NODE_IMAGE) kind create cluster --name dc2 --image $(KIND_NODE_IMAGE) kind create cluster --name dc3 --image $(KIND_NODE_IMAGE) kind create cluster --name dc4 --image $(KIND_NODE_IMAGE) -# Helper target for loading local dev images (run with `DEV_IMAGE=...` to load non-k8s images) -kind-load: +.PHONY: kind-load +kind-load: ## Helper target for loading local dev images (run with `DEV_IMAGE=...` to load non-k8s images) kind load docker-image --name dc1 $(DEV_IMAGE) kind load docker-image --name dc2 $(DEV_IMAGE) kind load docker-image --name dc3 $(DEV_IMAGE) kind load docker-image --name dc4 $(DEV_IMAGE) -# ===========> Shared Targets - -help: ## Show targets and their descriptions. - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-38s\033[0m %s\n", $$1, $$2}' +##@ Shared Targets +.PHONY: lint lint: cni-plugin-lint ## Run linter in the control-plane, cli, and acceptance directories. for p in control-plane cli acceptance; do cd $$p; golangci-lint run --path-prefix $$p -c ../.golangci.yml; cd ..; done +.PHONY: ctrl-manifests ctrl-manifests: get-controller-gen ## Generate CRD manifests. make ensure-controller-gen-version cd control-plane; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases @@ -190,6 +211,7 @@ ctrl-manifests: get-controller-gen ## Generate CRD manifests. make generate-external-crds make add-copyright-header +.PHONY: get-controller-gen get-controller-gen: ## Download controller-gen program needed for operator SDK. ifeq (, $(shell which controller-gen)) @{ \ @@ -205,15 +227,21 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif +.PHONY: ensure-controller-gen-version ensure-controller-gen-version: ## Ensure controller-gen version is v0.12.1. -ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.12.1)) - @echo "controller-gen version is not v0.12.1, uninstall the binary and install the correct version with 'make get-controller-gen'." - @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" - @exit 1 +ifeq (, $(shell which $(CONTROLLER_GEN))) + @echo "You don't have $(CONTROLLER_GEN), please install it first." else - @echo "Found correct version: $(shell $(CONTROLLER_GEN) --version)" + ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.12.1)) + @echo "controller-gen version is not v0.12.1, uninstall the binary and install the correct version with 'make get-controller-gen'." + @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" + @exit 1 + else + @echo "Found correct version: $(shell $(CONTROLLER_GEN) --version)" + endif endif +.PHONY: add-copyright-header add-copyright-header: ## Add copyright header to all files in the project ifeq (, $(shell which copywrite)) @echo "Installing copywrite" @@ -221,48 +249,63 @@ ifeq (, $(shell which copywrite)) endif @copywrite headers --spdx "MPL-2.0" -# ===========> CI Targets +##@ CI Targets +.PHONY: ci.aws-acceptance-test-cleanup ci.aws-acceptance-test-cleanup: ## Deletes AWS resources left behind after failed acceptance tests. @cd hack/aws-acceptance-test-cleanup; go run ./... -auto-approve -version: +.PHONY: version +version: ## print version @echo $(VERSION) -consul-version: +.PHONY: consul-version +consul-version: ## print consul version @echo $(CONSUL_IMAGE_VERSION) -consul-enterprise-version: +.PHONY: consul-enterprise-version +consul-enterprise-version: ## print consul ent version @echo $(CONSUL_ENTERPRISE_IMAGE_VERSION) -consul-dataplane-version: +.PHONY: consul-dataplane-version +consul-dataplane-version: ## print consul data-plane version @echo $(CONSUL_DATAPLANE_IMAGE_VERSION) -kind-version: +.PHONY: kind-version +kind-version: ## print kind version @echo $(KIND_VERSION) -kind-node-image: +.PHONY: kind-node-image +kind-node-image: ## print kind node image @echo $(KIND_NODE_IMAGE) -kubectl-version: +.PHONY: kubectl-version +kubectl-version: ## print kubectl version @echo $(KUBECTL_VERSION) -kind-test-packages: +.PHONY: kind-test-packages +kind-test-packages: ## kind test packages @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/kind_acceptance_test_packages.yaml" -gke-test-packages: +.PHONY: gke-test-packages +gke-test-packages: ## gke test packages @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/gke_acceptance_test_packages.yaml" -eks-test-packages: +.PHONY: eks-test-packages +eks-test-packages: ## eks test packages @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/eks_acceptance_test_packages.yaml" -aks-test-packages: +.PHONY: aks-test-packages +aks-test-packages: ## aks test packages @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/aks_acceptance_test_packages.yaml" -# ===========> Release Targets -check-env: +##@ Release Targets + +.PHONY: check-env +check-env: ## check env @printenv | grep "CONSUL_K8S" +.PHONY: prepare-release-script prepare-release-script: ## Sets the versions, updates changelog to prepare this repository to release ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) @@ -278,8 +321,10 @@ ifndef CONSUL_K8S_CONSUL_VERSION endif @source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION); \ +.PHONY: prepare-release prepare-release: prepare-release-script check-preview-containers +.PHONY: prepare-rc-script prepare-rc-script: ## Sets the versions, updates changelog to prepare this repository to release ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) @@ -295,9 +340,11 @@ ifndef CONSUL_K8S_CONSUL_VERSION endif @source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_rc_branch $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION); \ +.PHONY: prepare-rc-branch prepare-rc-branch: prepare-rc-script -prepare-main-dev: +.PHONY: prepare-main-dev +prepare-main-dev: ## prepare main dev ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) endif @@ -315,7 +362,8 @@ ifndef CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION endif source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_NEXT_CONSUL_VERSION) $(CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION) -prepare-release-dev: +.PHONY: prepare-release-dev +prepare-release-dev: ## prepare release dev ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) endif @@ -335,7 +383,6 @@ endif # ===========> Makefile config .DEFAULT_GOAL := help -.PHONY: gen-helm-docs copy-crds-to-chart generate-external-crds bats-tests help ci.aws-acceptance-test-cleanup version cli-dev prepare-dev prepare-release SHELL = bash GOOS?=$(shell go env GOOS) GOARCH?=$(shell go env GOARCH) @@ -345,3 +392,19 @@ GIT_COMMIT?=$(shell git rev-parse --short HEAD) GIT_DIRTY?=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true) GIT_DESCRIBE?=$(shell git describe --tags --always) CRD_OPTIONS ?= "crd:ignoreUnexportedFields=true,allowDangerousTypes=true" + +##@ Help + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php +.PHONY: help +help: ## Display this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) From 83a86168058e64c8382b49e24aea1c84fced4f0c Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Mon, 27 Nov 2023 10:51:52 -0500 Subject: [PATCH 485/592] [NET-6617] security: Bump github.com/golang-jwt/jwt/v4 to 4.5.0 (#3237) security: Bump github.com/golang-jwt/jwt/v4 to 4.5.0 This version is accepted by Prisma/Twistlock, resolving scan results for issue PRISMA-2022-0270. Chosen over later versions to avoid a major version with breaking changes that is otherwise unnecessary. Note that in practice this is a false positive (see https://github.com/golang-jwt/jwt/issues/258), but we should update the version to aid customers relying on scanners that flag it. --- .changelog/3237.txt | 3 +++ cli/go.mod | 2 +- cli/go.sum | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changelog/3237.txt diff --git a/.changelog/3237.txt b/.changelog/3237.txt new file mode 100644 index 0000000000..7b9100d816 --- /dev/null +++ b/.changelog/3237.txt @@ -0,0 +1,3 @@ +```release-note:security +Update `github.com/golang-jwt/jwt/v4` to v4.5.0 to address [PRISMA-2022-0270](https://github.com/golang-jwt/jwt/issues/258). +``` diff --git a/cli/go.mod b/cli/go.mod index ea3c122e2e..664757f8cf 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -90,7 +90,7 @@ require ( github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.2.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect diff --git a/cli/go.sum b/cli/go.sum index 23eba93b3a..dc07c43292 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -336,8 +336,9 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= From dc6a70a30093d0165cc4959f029720018f604638 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Mon, 27 Nov 2023 19:01:47 -0500 Subject: [PATCH 486/592] NET-6536 Adds stub of GatewayClass controller into v2 controllers (#3245) * Adds stub of GatewayClass controller into v2 controllers * regen without weird spaces * Update control-plane/config-entries/controllersv2/gateway_class_controller.go --------- Co-authored-by: Nathan Coleman --- .../templates/connect-inject-clusterrole.yaml | 2 + control-plane/api/common/common.go | 1 + .../api/mesh/v2beta1/gateway_class_types.go | 7 +-- .../controllersv2/gateway_class_controller.go | 43 +++++++++++++++++++ control-plane/config/rbac/role.yaml | 20 +++++++++ .../inject-connect/v2controllers.go | 9 ++++ 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 control-plane/config-entries/controllersv2/gateway_class_controller.go diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 4cc749a0e6..ff181720fd 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -96,6 +96,7 @@ rules: - apiGroups: - mesh.consul.hashicorp.com resources: + - gatewayclasses - grpcroutes - httproutes - meshgateways @@ -112,6 +113,7 @@ rules: - apiGroups: - mesh.consul.hashicorp.com resources: + - gatewayclasses/status - grpcroutes/status - httproutes/status - meshgateways/status diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index c9f461bd87..a9758e0b9e 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -30,6 +30,7 @@ const ( TCPRoute string = "tcproute" ProxyConfiguration string = "proxyconfiguration" MeshGateway string = "meshgateway" + GatewayClass string = "gatewayclass" Global string = "global" Mesh string = "mesh" diff --git a/control-plane/api/mesh/v2beta1/gateway_class_types.go b/control-plane/api/mesh/v2beta1/gateway_class_types.go index 6fc42ffed5..fac8535ae5 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_types.go @@ -7,14 +7,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/proto-public/pbresource" "google.golang.org/protobuf/testing/protocmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) const ( diff --git a/control-plane/config-entries/controllersv2/gateway_class_controller.go b/control-plane/config-entries/controllersv2/gateway_class_controller.go new file mode 100644 index 0000000000..2e9572e16c --- /dev/null +++ b/control-plane/config-entries/controllersv2/gateway_class_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// GatewayClassController reconciles a MeshGateway object. +type GatewayClassController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + MeshConfigController *MeshConfigController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclass,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclass/status,verbs=get;update;patch + +func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.GatewayClass{}) +} + +func (r *GatewayClassController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *GatewayClassController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *GatewayClassController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.GatewayClass{}, r) +} diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index c4fb15f80f..52be967944 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -345,6 +345,26 @@ rules: - get - patch - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - gatewayclass + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - gatewayclass/status + verbs: + - get + - patch + - update - apiGroups: - mesh.consul.hashicorp.com resources: diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index ac2f0831a0..f33d863da5 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -186,6 +186,15 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.MeshGateway) return err } + if err := (&controllersv2.GatewayClassController{ + MeshConfigController: meshConfigReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.GatewayClass), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.GatewayClass) + return err + } mgr.GetWebhookServer().CertDir = c.flagCertDir From 1b6f700f17d8bc7976b6c04d92fbf2def7830e43 Mon Sep 17 00:00:00 2001 From: aahel Date: Tue, 28 Nov 2023 13:30:39 +0530 Subject: [PATCH 487/592] fixed acl deletetion in endpoints controller (#3210) * fixed acl deletetion in endpoints controller * fixed tests * fixed other tests * fixed ent tests * added changelog * updated TestReconcileDeleteEndpoint to support deleting token by pod uid * passed pod-uid to dataplane * fixed tests * fixed more tests * fixed dataplane env * fixed test * fixed passing env to dataplane * fixed unit test --- .changelog/3210.txt | 3 + .../connect-inject/constants/constants.go | 3 + .../endpoints/endpoints_controller.go | 13 ++- .../endpoints_controller_ent_test.go | 20 +++-- .../endpoints/endpoints_controller_test.go | 80 ++++++++++++++----- .../webhook/consul_dataplane_sidecar.go | 10 +++ .../webhook/consul_dataplane_sidecar_test.go | 11 ++- control-plane/subcommand/flags/consul.go | 3 +- 8 files changed, 105 insertions(+), 38 deletions(-) create mode 100644 .changelog/3210.txt diff --git a/.changelog/3210.txt b/.changelog/3210.txt new file mode 100644 index 0000000000..e0ae60679d --- /dev/null +++ b/.changelog/3210.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: Only delete ACL tokens matched Pod UID in Service Registration metadata +``` \ No newline at end of file diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 07bdaecba8..dd7cb4d243 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -49,6 +49,9 @@ const ( // MetaKeyPodName is the meta key name for Kubernetes pod name used for the Consul services. MetaKeyPodName = "pod-name" + // MetaKeyPodUID is the meta key name for Kubernetes pod uid used for the Consul services. + MetaKeyPodUID = "pod-uid" + // DefaultGracefulPort is the default port that consul-dataplane uses for graceful shutdown. DefaultGracefulPort = 20600 diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 3402b4276d..e9994516ad 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -437,6 +437,7 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints metaKeyKubeServiceName: serviceEndpoints.Name, constants.MetaKeyKubeNS: serviceEndpoints.Namespace, metaKeyManagedBy: constants.ManagedByValue, + constants.MetaKeyPodUID: string(pod.UID), metaKeySyntheticNode: "true", } for k, v := range pod.Annotations { @@ -688,6 +689,7 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints constants.MetaKeyKubeNS: serviceEndpoints.Namespace, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: string(pod.UID), } service := &api.AgentService{ @@ -967,7 +969,7 @@ func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvc if r.AuthMethod != "" && serviceDeregistered { r.Log.Info("reconciling ACL tokens for service", "svc", svc.Service) - err := r.deleteACLTokensForServiceInstance(apiClient, svc, k8sSvcNamespace, svc.Meta[constants.MetaKeyPodName]) + err := r.deleteACLTokensForServiceInstance(apiClient, svc, k8sSvcNamespace, svc.Meta[constants.MetaKeyPodName], svc.Meta[constants.MetaKeyPodUID]) if err != nil { r.Log.Error(err, "failed to reconcile ACL tokens for service", "svc", svc.Service) errs = multierror.Append(errs, err) @@ -982,8 +984,8 @@ func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvc // deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. // It will only check for ACL tokens that have been created with the auth method this controller -// has been configured with and will only delete tokens for the provided podName. -func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, svc *api.AgentService, k8sNS, podName string) error { +// has been configured with and will only delete tokens for the provided podName and podUID. +func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, svc *api.AgentService, k8sNS, podName, podUID string) error { // Skip if podName is empty. if podName == "" { return nil @@ -1019,8 +1021,11 @@ func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, sv tokenPodName := strings.TrimPrefix(tokenMeta[tokenMetaPodNameKey], k8sNS+"/") + // backward compability logic on token with no podUID in metadata + podUIDMatched := tokenMeta[constants.MetaKeyPodUID] == podUID || tokenMeta[constants.MetaKeyPodUID] == "" + // If we can't find token's pod, delete it. - if tokenPodName == podName { + if tokenPodName == podName && podUIDMatched { r.Log.Info("deleting ACL token for pod", "name", podName) if _, err := apiClient.ACL().TokenDelete(token.AccessorID, &api.WriteOptions{Namespace: svc.Namespace}); err != nil { return fmt.Errorf("failed to delete token from Consul: %s", err) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 44e64acba1..1c55bcd7f3 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -126,7 +126,7 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { ServiceID: "pod1-service-created", ServiceName: "service-created", ServiceAddress: "1.2.3.4", - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: testCase.SourceKubeNS, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: testCase.SourceKubeNS, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, Namespace: testCase.ExpConsulNS, }, @@ -134,7 +134,7 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { ServiceID: "pod2-service-created", ServiceName: "service-created", ServiceAddress: "2.2.3.4", - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: testCase.SourceKubeNS, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: testCase.SourceKubeNS, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, Namespace: testCase.ExpConsulNS, }, @@ -149,7 +149,7 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { DestinationServiceName: "service-created", DestinationServiceID: "pod1-service-created", }, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: testCase.SourceKubeNS, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: testCase.SourceKubeNS, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, Namespace: testCase.ExpConsulNS, }, @@ -162,7 +162,7 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { DestinationServiceName: "service-created", DestinationServiceID: "pod2-service-created", }, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: testCase.SourceKubeNS, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: testCase.SourceKubeNS, metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, Namespace: testCase.ExpConsulNS, }, @@ -397,7 +397,7 @@ func TestReconcileCreateGatewayWithNamespaces(t *testing.T) { ServiceID: "mesh-gateway", ServiceName: "mesh-gateway", ServiceAddress: "3.3.3.3", - ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServicePort: 8443, ServiceTaggedAddresses: map[string]api.ServiceAddress{ @@ -416,7 +416,7 @@ func TestReconcileCreateGatewayWithNamespaces(t *testing.T) { ServiceID: "terminating-gateway", ServiceName: "terminating-gateway", ServiceAddress: "4.4.4.4", - ServiceMeta: map[string]string{constants.MetaKeyPodName: "terminating-gateway", metaKeyKubeServiceName: "gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "terminating-gateway", metaKeyKubeServiceName: "gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServicePort: 8443, Namespace: testCase.ConsulNS, @@ -425,7 +425,7 @@ func TestReconcileCreateGatewayWithNamespaces(t *testing.T) { ServiceID: "ingress-gateway", ServiceName: "ingress-gateway", ServiceAddress: "5.5.5.5", - ServiceMeta: map[string]string{constants.MetaKeyPodName: "ingress-gateway", metaKeyKubeServiceName: "gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "ingress-gateway", metaKeyKubeServiceName: "gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServicePort: 21000, ServiceTaggedAddresses: map[string]api.ServiceAddress{ @@ -1316,6 +1316,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { constants.MetaKeyPodName: "pod1", constants.MetaKeyKubeNS: ts.SourceKubeNS, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, Namespace: ts.ExpConsulNS, }, @@ -1342,6 +1343,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { constants.MetaKeyPodName: "pod1", constants.MetaKeyKubeNS: ts.SourceKubeNS, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, Namespace: ts.ExpConsulNS, }, @@ -1408,6 +1410,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, constants.MetaKeyPodName: "pod1", metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, Namespace: ts.ExpConsulNS, }, @@ -1434,6 +1437,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, constants.MetaKeyPodName: "pod1", metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, Namespace: ts.ExpConsulNS, }, @@ -1455,6 +1459,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, constants.MetaKeyPodName: "pod2", metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, Namespace: ts.ExpConsulNS, }, @@ -1481,6 +1486,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, constants.MetaKeyPodName: "pod2", metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, Namespace: ts.ExpConsulNS, }, diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index cb0e6807c1..4c92892367 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -715,6 +715,7 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{}, }, @@ -729,6 +730,7 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{}, }, @@ -758,6 +760,7 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{}, }, @@ -778,6 +781,7 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{}, }, @@ -1010,7 +1014,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "service-created", ServiceAddress: "1.2.3.4", ServicePort: 0, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, NodeMeta: map[string]string{ @@ -1032,7 +1036,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { LocalServicePort: 0, Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, NodeMeta: map[string]string{ "synthetic-node": "true", @@ -1104,7 +1108,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "mesh-gateway", ServiceAddress: "1.2.3.4", ServicePort: 8443, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "mesh-gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "mesh-gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceTaggedAddresses: map[string]api.ServiceAddress{ "lan": { @@ -1177,7 +1181,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "mesh-gateway", ServiceAddress: "1.2.3.4", ServicePort: 8443, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "mesh-gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "mesh-gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceTaggedAddresses: map[string]api.ServiceAddress{ "lan": { @@ -1251,7 +1255,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "mesh-gateway", ServiceAddress: "1.2.3.4", ServicePort: 8443, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "mesh-gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "mesh-gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceTaggedAddresses: map[string]api.ServiceAddress{ "lan": { @@ -1326,6 +1330,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{ @@ -1389,6 +1394,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{ @@ -1474,6 +1480,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{}, ServiceTaggedAddresses: map[string]api.ServiceAddress{ @@ -1575,6 +1582,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{}, ServiceTaggedAddresses: map[string]api.ServiceAddress{ @@ -1656,7 +1664,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "service-created", ServiceAddress: "1.2.3.4", ServicePort: 0, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, }, @@ -1665,7 +1673,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "service-created", ServiceAddress: "2.2.3.4", ServicePort: 0, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, }, @@ -1683,7 +1691,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { LocalServicePort: 0, Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, }, { @@ -1698,7 +1706,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { LocalServicePort: 0, Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, }, }, @@ -1797,7 +1805,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "service-created", ServiceAddress: "1.2.3.4", ServicePort: 0, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, }, @@ -1806,7 +1814,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "service-created", ServiceAddress: "2.2.3.4", ServicePort: 0, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, }, @@ -1824,7 +1832,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { LocalServicePort: 0, Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, }, { @@ -1839,7 +1847,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { LocalServicePort: 0, Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, }, }, @@ -1937,6 +1945,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{"abc,123", "pod1"}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, @@ -1982,6 +1991,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, ServiceTags: []string{"abc,123", "pod1"}, }, @@ -2055,7 +2065,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceName: "service-created", ServiceAddress: "1.2.3.4", ServicePort: 0, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, }, @@ -2073,7 +2083,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { LocalServicePort: 0, Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true"}, + ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, }, }, @@ -2531,6 +2541,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyKubeServiceName: "service-updated", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, }, }, @@ -2552,6 +2563,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyKubeServiceName: "service-updated", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, Proxy: &api.AgentServiceConnectProxyConfig{ DestinationServiceName: "service-updated", @@ -2619,6 +2631,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { constants.MetaKeyKubeNS: "default", constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-updated", + constants.MetaKeyPodUID: "", }, }, }, @@ -2644,6 +2657,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { constants.MetaKeyKubeNS: "default", constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-updated", + constants.MetaKeyPodUID: "", }, }, }, @@ -3222,6 +3236,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyKubeServiceName: "service-updated", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, }, }, @@ -3243,6 +3258,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyKubeServiceName: "service-updated", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", + constants.MetaKeyPodUID: "", }, Proxy: &api.AgentServiceConnectProxyConfig{ DestinationServiceName: "service-updated", @@ -3261,6 +3277,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod2", + constants.MetaKeyPodUID: "", }, }, }, @@ -3274,6 +3291,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod2", + constants.MetaKeyPodUID: "", }, }, }, @@ -3324,6 +3342,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "", }, }, }, @@ -3349,6 +3368,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "", }, }, }, @@ -3369,6 +3389,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod2", + constants.MetaKeyPodUID: "", }, }, }, @@ -3394,6 +3415,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod2", + constants.MetaKeyPodUID: "", }, }, }, @@ -3409,6 +3431,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "", }, }, }, @@ -3423,6 +3446,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "", }, }, }, @@ -3475,6 +3499,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "", }, }, }, @@ -3500,6 +3525,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "", }, }, }, @@ -3859,6 +3885,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { cases := []struct { name string consulSvcName string + consulPodUid string expectServicesToBeDeleted bool initialConsulSvcs []*api.AgentService enableACLs bool @@ -3944,6 +3971,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { { name: "When ACLs are enabled, the token should be deleted", consulSvcName: "service-deleted", + consulPodUid: "123", expectServicesToBeDeleted: true, initialConsulSvcs: []*api.AgentService{ { @@ -3957,6 +3985,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "123", }, }, { @@ -3975,6 +4004,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "123", }, }, }, @@ -4014,6 +4044,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { { name: "When ACLs are enabled, the mesh-gateway token should be deleted", consulSvcName: "service-deleted", + consulPodUid: "124", expectServicesToBeDeleted: true, initialConsulSvcs: []*api.AgentService{ { @@ -4028,6 +4059,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "mesh-gateway", + constants.MetaKeyPodUID: "124", }, TaggedAddresses: map[string]api.ServiceAddress{ "lan": { @@ -4077,6 +4109,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { { name: "When ACLs are enabled, the ingress-gateway token should be deleted", consulSvcName: "service-deleted", + consulPodUid: "125", expectServicesToBeDeleted: true, initialConsulSvcs: []*api.AgentService{ { @@ -4091,6 +4124,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "ingress-gateway", + constants.MetaKeyPodUID: "125", }, TaggedAddresses: map[string]api.ServiceAddress{ "lan": { @@ -4130,6 +4164,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { { name: "When ACLs are enabled, the terminating-gateway token should be deleted", consulSvcName: "service-deleted", + consulPodUid: "126", expectServicesToBeDeleted: true, initialConsulSvcs: []*api.AgentService{ { @@ -4144,6 +4179,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "terminating-gateway", + constants.MetaKeyPodUID: "126", }, }, }, @@ -4190,8 +4226,9 @@ func TestReconcileDeleteEndpoint(t *testing.T) { AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, Meta: map[string]string{ - "pod": fmt.Sprintf("%s/%s", svc.Meta[constants.MetaKeyKubeNS], svc.Meta[constants.MetaKeyPodName]), - "component": tt.consulSvcName, + "pod": fmt.Sprintf("%s/%s", svc.Meta[constants.MetaKeyKubeNS], svc.Meta[constants.MetaKeyPodName]), + "component": tt.consulSvcName, + constants.MetaKeyPodUID: tt.consulPodUid, }, }, nil) require.NoError(t, err) @@ -4338,6 +4375,7 @@ func TestReconcileIgnoresServiceIgnoreLabel(t *testing.T) { metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodName: "pod1", + constants.MetaKeyPodUID: "", }, }, } @@ -5945,12 +5983,12 @@ func TestGetTokenMetaFromDescription(t *testing.T) { expectedTokenMeta map[string]string }{ "no description prefix": { - description: `{"pod":"default/pod"}`, - expectedTokenMeta: map[string]string{"pod": "default/pod"}, + description: `{"pod":"default/pod","pod-uid": "123"}`, + expectedTokenMeta: map[string]string{"pod": "default/pod", "pod-uid": "123"}, }, "consul's default description prefix": { - description: `token created via login: {"pod":"default/pod"}`, - expectedTokenMeta: map[string]string{"pod": "default/pod"}, + description: `token created via login: {"pod":"default/pod","pod-uid": "123"}`, + expectedTokenMeta: map[string]string{"pod": "default/pod", "pod-uid": "123"}, }, } diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index 03f47a8e84..6ff75c76c4 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -112,6 +112,12 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, }, }, + { + Name: "POD_UID", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.uid"}, + }, + }, { Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", @@ -122,6 +128,10 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor Name: "DP_CREDENTIAL_LOGIN_META1", Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", }, + { + Name: "DP_CREDENTIAL_LOGIN_META2", + Value: "pod-uid=$(POD_UID)", + }, }, VolumeMounts: []corev1.VolumeMount{ { diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index 4261887b12..c105e71b7b 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -221,17 +221,20 @@ func TestHandlerConsulDataplaneSidecar(t *testing.T) { } require.Equal(t, expectedProbe, container.ReadinessProbe) require.Nil(t, container.StartupProbe) - require.Len(t, container.Env, 7) + require.Len(t, container.Env, 9) require.Equal(t, container.Env[0].Name, "TMPDIR") require.Equal(t, container.Env[0].Value, "/consul/connect-inject") require.Equal(t, container.Env[2].Name, "DP_SERVICE_NODE_NAME") require.Equal(t, container.Env[2].Value, "$(NODE_NAME)-virtual") require.Equal(t, container.Env[3].Name, "POD_NAME") require.Equal(t, container.Env[4].Name, "POD_NAMESPACE") - require.Equal(t, container.Env[5].Name, "DP_CREDENTIAL_LOGIN_META") - require.Equal(t, container.Env[5].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") - require.Equal(t, container.Env[6].Name, "DP_CREDENTIAL_LOGIN_META1") + require.Equal(t, container.Env[5].Name, "POD_UID") + require.Equal(t, container.Env[6].Name, "DP_CREDENTIAL_LOGIN_META") require.Equal(t, container.Env[6].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") + require.Equal(t, container.Env[7].Name, "DP_CREDENTIAL_LOGIN_META1") + require.Equal(t, container.Env[7].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") + require.Equal(t, container.Env[8].Name, "DP_CREDENTIAL_LOGIN_META2") + require.Equal(t, container.Env[8].Value, "pod-uid=$(POD_UID)") }) } } diff --git a/control-plane/subcommand/flags/consul.go b/control-plane/subcommand/flags/consul.go index cd577790fc..9368b95b3d 100644 --- a/control-plane/subcommand/flags/consul.go +++ b/control-plane/subcommand/flags/consul.go @@ -11,11 +11,10 @@ import ( "strings" "time" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-rootcerts" - - "github.com/hashicorp/consul-k8s/control-plane/consul" ) const ( From a8857ea2f3496cd9eaada4bd7c7e2c03925afa68 Mon Sep 17 00:00:00 2001 From: emily neil <63985869+emilymianeil@users.noreply.github.com> Date: Tue, 28 Nov 2023 11:29:45 -0800 Subject: [PATCH 488/592] Remove Duplicate UBI Tags (#3265) - Amalgamate UBI with Dockerhub and Redhat tags into one step - Avoids a production incident that errors on duplicate tags: hashicorp/releng-support#123 --- .github/workflows/build.yml | 81 +++---------------------------------- 1 file changed, 6 insertions(+), 75 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7499ead061..87064484af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -328,8 +328,8 @@ jobs: docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }} docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }}-${{ github.sha }} - build-docker-ubi-redhat-registry: - name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI build for RedHat Registry + build-docker-ubi: + name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI builds needs: [get-product-version, build] runs-on: ubuntu-latest strategy: @@ -352,69 +352,6 @@ jobs: run: | cd "${ZIP_LOCATION}" unzip -j *.zip - - name: Copy LICENSE - run: - cp LICENSE ./control-plane - - name: Docker Build (Action) - if: ${{ !matrix.fips }} - uses: hashicorp/actions-docker-build@v1 - with: - smoke_test: | - TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" - if [ "${TEST_VERSION}" != "v${version}" ]; then - echo "Test FAILED" - exit 1 - fi - echo "Test PASSED" - version: ${{ env.version }} - target: ubi - arch: ${{ matrix.arch }} - pkg_name: consul-k8s-control-plane_${{ env.version }} - bin_name: consul-k8s-control-plane - workdir: control-plane - redhat_tag: quay.io/redhat-isv-containers/611ca2f89a9b407267837100:${{env.version}}-ubi - - name: Docker FIPS Build (Action) - if: ${{ matrix.fips }} - uses: hashicorp/actions-docker-build@v1 - with: - smoke_test: | - TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" - if [ "${TEST_VERSION}" != "v${version}" ]; then - echo "Test FAILED" - exit 1 - fi - echo "Test PASSED" - version: ${{ env.version }} - target: ubi-fips # duplicate target to distinguish FIPS builds in CRT machinery - arch: ${{ matrix.arch }} - pkg_name: consul-k8s-control-plane_${{ env.version }} - bin_name: consul-k8s-control-plane - workdir: control-plane - redhat_tag: quay.io/redhat-isv-containers/6486b1beabfc4e51588c0416:${{env.version}}-ubi # this is different than the non-FIPS one - - build-docker-ubi-dockerhub: - name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI build for DockerHub - needs: [ get-product-version, build ] - runs-on: ubuntu-latest - strategy: - matrix: - arch: [ "amd64" ] - fips: [ "+fips1402", "" ] - env: - repo: ${{ github.event.repository.name }} - version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} - steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 - with: - name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_linux_${{ matrix.arch }}.zip - path: control-plane/dist/cni/linux/${{ matrix.arch }} - - name: extract consul-cni zip - env: - ZIP_LOCATION: control-plane/dist/cni/linux/${{ matrix.arch }} - run: | - cd ${ZIP_LOCATION} - unzip -j *.zip - name: Copy LICENSE run: cp LICENSE ./control-plane @@ -428,8 +365,8 @@ jobs: echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV - name: Docker Build (Action) - uses: hashicorp/actions-docker-build@v1 if: ${{ !matrix.fips }} + uses: hashicorp/actions-docker-build@v1 with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -451,10 +388,10 @@ jobs: docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-ubi-${{ github.sha }} docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi-${{ github.sha }} - + redhat_tag: quay.io/redhat-isv-containers/611ca2f89a9b407267837100:${{env.version}}-ubi - name: Docker FIPS Build (Action) - uses: hashicorp/actions-docker-build@v1 if: ${{ matrix.fips }} + uses: hashicorp/actions-docker-build@v1 with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -469,10 +406,4 @@ jobs: pkg_name: consul-k8s-control-plane_${{ env.version }} bin_name: consul-k8s-control-plane workdir: control-plane - tags: | - docker.io/hashicorp/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi - dev_tags: | - docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-ubi - docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-ubi-${{ github.sha }} - docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi - docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi-${{ github.sha }} + redhat_tag: quay.io/redhat-isv-containers/6486b1beabfc4e51588c0416:${{env.version}}-ubi # this is different than the non-FIPS one From a1761c472af4748191a4fca66403a49577d2b81b Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 28 Nov 2023 14:58:26 -0500 Subject: [PATCH 489/592] NET-6394 Create/update/delete ServiceAccount on MeshGateway reconcile (#3244) * Create/update/delete ServiceAccount on MeshGateway reconcile * Implement owner ref checking for existing objects before create/update * Use builder struct for creating k8s resources * Extend upsert function to handle delete as well * Add unit tests asserting ServiceAccount CRUD w/ ownership enforcement * Update list of TODOs in reconcile handlers * Uncomment call to MeshConfigController.ReconcileEntry * Check error returned by SetControllerReference * Add unit test coverage for MeshGatewayBuilder.serviceAccount() * Omit namespace on MeshGateway's resource ID tenancy The MeshGateway is partition-scoped which means that it should never include a namespace in its resource ID * Provide existing object to operation This enables any future merge operations than need to be done before writing the new object, such as setting a value that needs to carry forward from the existing object onto the new object. * Declare ownership of ServiceAccount when setting up controller * Fix typo * Rely on garbage collector to delete resource instead of deleting explicitly --- .../api/mesh/v2beta1/mesh_gateway_types.go | 4 +- .../controllersv2/mesh_gateway_controller.go | 94 ++++++++++++++++++- .../mesh_gateway_controller_test.go | 66 +++++++++++-- control-plane/gateways/builder.go | 13 +++ control-plane/gateways/labels.go | 7 ++ control-plane/gateways/serviceaccount.go | 16 ++++ control-plane/gateways/serviceaccount_test.go | 30 ++++++ 7 files changed, 214 insertions(+), 16 deletions(-) create mode 100644 control-plane/gateways/builder.go create mode 100644 control-plane/gateways/labels.go create mode 100644 control-plane/gateways/serviceaccount.go create mode 100644 control-plane/gateways/serviceaccount_test.go diff --git a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go index b7a2b1a2e3..fe3af45feb 100644 --- a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go +++ b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go @@ -51,13 +51,13 @@ type MeshGatewayList struct { Items []*MeshGateway `json:"items"` } -func (in *MeshGateway) ResourceID(namespace, partition string) *pbresource.ID { +func (in *MeshGateway) ResourceID(_, partition string) *pbresource.ID { return &pbresource.ID{ Name: in.Name, Type: pbmesh.MeshGatewayType, Tenancy: &pbresource.Tenancy{ Partition: partition, - Namespace: namespace, + Namespace: "", // Namespace is always unset because MeshGateway is partition-scoped // Because we are explicitly defining NS/partition, this will not default and must be explicit. // At a future point, this will move out of the Tenancy block. diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index be878e9160..6f74802fd0 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -8,13 +8,16 @@ import ( "errors" "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/gateways" ) // MeshGatewayController reconciles a MeshGateway object. @@ -65,15 +68,96 @@ func (r *MeshGatewayController) UpdateStatus(ctx context.Context, obj client.Obj } func (r *MeshGatewayController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.MeshGateway{}, r) + return ctrl.NewControllerManagedBy(mgr). + For(&meshv2beta1.MeshGateway{}). + Owns(&corev1.ServiceAccount{}). + Complete(r) } +// onCreateUpdate is responsible for creating/updating all K8s resources that +// are required in order to run a meshv2beta1.MeshGateway. These are created/updated +// in dependency order. +// 1. ServiceAccount +// 2. Deployment +// 3. Service +// 4. Role +// 5. RoleBinding func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { - // TODO NET-6392 NET-6393 NET-6394 NET-6395 - return errors.New("onCreateUpdate not implemented") + builder := gateways.NewMeshGatewayBuilder(resource) + + upsertOp := func(ctx context.Context, _, object client.Object) error { + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, object, func() error { return nil }) + return err + } + + err := r.opIfNewOrOwned(ctx, resource, &corev1.ServiceAccount{}, builder.ServiceAccount(), upsertOp) + if err != nil { + return err + } + + // TODO NET-6392 NET-6393 NET-6395 + + return nil } +// onDelete is responsible for cleaning up any side effects of onCreateUpdate. +// We only clean up side effects because all resources that we create explicitly +// have an owner reference and will thus be cleaned up by the K8s garbage collector +// once the owning meshv2beta1.MeshGateway is deleted. func (r *MeshGatewayController) onDelete(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { - // TODO NET-6392 NET-6393 NET-6394 NET-6395 - return errors.New("onDelete not implemented") + // TODO NET-6392 NET-6393 NET-6395 + return nil +} + +// ownedObjectOp represents an operation that needs to be applied +// only if the newObject does not yet exist or if the existingObject +// has an owner reference pointing to the MeshGateway being reconciled. +// +// The existing and new object are available in case any merging needs +// to occur, such as unknown annotations and values from the existing object +// that need to be carried forward onto the new object. +type ownedObjectOp func(ctx context.Context, existingObject client.Object, newObject client.Object) error + +// opIfNewOrOwned runs a given ownedObjectOp to create, update, or delete a resource. +// The purpose of opIfNewOrOwned is to ensure that we aren't updating or deleting a +// resource that was not created by us. If this scenario is encountered, we error. +func (r *MeshGatewayController) opIfNewOrOwned(ctx context.Context, gateway *meshv2beta1.MeshGateway, scanTarget, writeSource client.Object, op ownedObjectOp) error { + // Ensure owner reference is always set on objects that we write + if err := ctrl.SetControllerReference(gateway, writeSource, r.Client.Scheme()); err != nil { + return err + } + + key := client.ObjectKey{ + Namespace: writeSource.GetNamespace(), + Name: writeSource.GetName(), + } + + exists := false + if err := r.Get(ctx, key, scanTarget); err != nil { + // We failed to fetch the object in a way that doesn't tell us about its existence + if !k8serr.IsNotFound(err) { + return err + } + } else { + // We successfully fetched the object, so it exists + exists = true + } + + // None exists, so we need only execute the operation + if !exists { + return op(ctx, nil, writeSource) + } + + // Ensure the existing object was put there by us so that we don't overwrite random objects + owned := false + for _, reference := range scanTarget.GetOwnerReferences() { + if reference.UID == gateway.GetUID() && reference.Name == gateway.GetName() { + owned = true + break + } + } + if !owned { + return errors.New("existing resource not owned by controller") + } + return op(ctx, scanTarget, writeSource) } diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index 9a2b014dcb..e10989f6f2 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -4,19 +4,19 @@ import ( "context" "errors" "testing" - "time" logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -34,9 +34,11 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { expectedErr error // expectedResult is the result we expect MeshGatewayController.Reconcile to return expectedResult ctrl.Result + // postReconcile runs some set of assertions on the state of k8s after Reconcile is called + postReconcile func(*testing.T, client.Client) }{ { - name: "onCreateUpdate", + name: "MeshGateway created with no existing ServiceAccount", k8sObjects: []runtime.Object{ &v2beta1.MeshGateway{ ObjectMeta: metav1.ObjectMeta{ @@ -51,17 +53,58 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Name: "mesh-gateway", }, }, - expectedErr: errors.New("onCreateUpdate not implemented"), expectedResult: ctrl.Result{}, + postReconcile: func(t *testing.T, c client.Client) { + // Verify ServiceAccount was created + key := client.ObjectKey{Namespace: "default", Name: "mesh-gateway"} + assert.NoError(t, c.Get(context.Background(), key, &corev1.ServiceAccount{})) + }, + }, + { + name: "MeshGateway created with existing ServiceAccount not owned by gateway", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + expectedErr: errors.New("existing resource not owned by controller"), }, { - name: "onDelete", + name: "MeshGateway created with existing ServiceAccount owned by gateway", k8sObjects: []runtime.Object{ &v2beta1.MeshGateway{ ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - DeletionTimestamp: common.PointerTo(metav1.NewTime(time.Now())), + Namespace: "default", + Name: "mesh-gateway", + UID: "abc123", + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + OwnerReferences: []metav1.OwnerReference{ + { + UID: "abc123", + Name: "mesh-gateway", + }, + }, }, }, }, @@ -71,8 +114,8 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Name: "mesh-gateway", }, }, - expectedErr: errors.New("onDelete not implemented"), expectedResult: ctrl.Result{}, + expectedErr: nil, // The Reconcile should be a no-op }, } @@ -83,6 +126,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { }) s := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(s)) s.AddKnownTypes(v2beta1.MeshGroupVersion, &v2beta1.MeshGateway{}) fakeClient := fake.NewClientBuilder().WithScheme(s). WithRuntimeObjects(testCase.k8sObjects...). @@ -105,6 +149,10 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { require.NoError(t, err) } assert.Equal(t, testCase.expectedResult, res) + + if testCase.postReconcile != nil { + testCase.postReconcile(t, fakeClient) + } }) } } diff --git a/control-plane/gateways/builder.go b/control-plane/gateways/builder.go new file mode 100644 index 0000000000..1987f904e4 --- /dev/null +++ b/control-plane/gateways/builder.go @@ -0,0 +1,13 @@ +package gateways + +import meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + +type meshGatewayBuilder struct { + gateway *meshv2beta1.MeshGateway +} + +func NewMeshGatewayBuilder(gateway *meshv2beta1.MeshGateway) *meshGatewayBuilder { + return &meshGatewayBuilder{ + gateway: gateway, + } +} diff --git a/control-plane/gateways/labels.go b/control-plane/gateways/labels.go new file mode 100644 index 0000000000..1ed6ec4ed0 --- /dev/null +++ b/control-plane/gateways/labels.go @@ -0,0 +1,7 @@ +package gateways + +func (b *meshGatewayBuilder) Labels() map[string]string { + return map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + } +} diff --git a/control-plane/gateways/serviceaccount.go b/control-plane/gateways/serviceaccount.go new file mode 100644 index 0000000000..aa51c53aae --- /dev/null +++ b/control-plane/gateways/serviceaccount.go @@ -0,0 +1,16 @@ +package gateways + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (b *meshGatewayBuilder) ServiceAccount() *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + Labels: b.Labels(), + }, + } +} diff --git a/control-plane/gateways/serviceaccount_test.go b/control-plane/gateways/serviceaccount_test.go new file mode 100644 index 0000000000..d769cb24ff --- /dev/null +++ b/control-plane/gateways/serviceaccount_test.go @@ -0,0 +1,30 @@ +package gateways + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +func TestNewMeshGatewayBuilder_ServiceAccount(t *testing.T) { + b := NewMeshGatewayBuilder(&meshv2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "mesh-gateway", + }, + }) + + expected := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "mesh-gateway", + Labels: b.Labels(), + }, + } + + assert.Equal(t, expected, b.ServiceAccount()) +} From c3ab09edcf225fa23797516cdad71f39af3133dc Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:12:16 -0600 Subject: [PATCH 490/592] Net 6535- GatewayClassConfig controller stubs (#3253) * rename v1 crd * gatewayclass controller stub * register controller * old version * reorder cluster role * fix accidental find and replace error --- .../templates/connect-inject-clusterrole.yaml | 2 + .../templates/crd-gatewayclassconfigs-v1.yaml | 201 +++++++++++++++++ .../templates/crd-gatewayclassconfigs.yaml | 213 ++++++++---------- control-plane/api/common/common.go | 1 + .../gateway_class_config_controller.go | 43 ++++ control-plane/config/rbac/role.yaml | 21 ++ .../inject-connect/v2controllers.go | 10 + 7 files changed, 367 insertions(+), 124 deletions(-) create mode 100644 charts/consul/templates/crd-gatewayclassconfigs-v1.yaml create mode 100644 control-plane/config-entries/controllersv2/gateway_class_config_controller.go diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index ff181720fd..1dc6d2c34a 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -96,6 +96,7 @@ rules: - apiGroups: - mesh.consul.hashicorp.com resources: + - gatewayclassconfigs - gatewayclasses - grpcroutes - httproutes @@ -113,6 +114,7 @@ rules: - apiGroups: - mesh.consul.hashicorp.com resources: + - gatewayclassconfigs/status - gatewayclasses/status - grpcroutes/status - httproutes/status diff --git a/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml b/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml new file mode 100644 index 0000000000..685dbf3617 --- /dev/null +++ b/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml @@ -0,0 +1,201 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + name: gatewayclassconfigs.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: GatewayClassConfig + listKind: GatewayClassConfigList + plural: gatewayclassconfigs + singular: gatewayclassconfig + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GatewayClassConfig defines the values that may be set on a GatewayClass + for Consul API Gateway. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClassConfig. + properties: + copyAnnotations: + description: Annotation Information to copy to services or deployments + properties: + service: + description: List of annotations to copy to the gateway service. + items: + type: string + type: array + type: object + deployment: + description: Deployment defines the deployment configuration for the + gateway. + properties: + defaultInstances: + default: 1 + description: Number of gateway instances that should be deployed + by default + format: int32 + maximum: 8 + minimum: 1 + type: integer + maxInstances: + default: 8 + description: Max allowed number of gateway instances + format: int32 + maximum: 8 + minimum: 1 + type: integer + minInstances: + default: 1 + description: Minimum allowed number of gateway instances + format: int32 + maximum: 8 + minimum: 1 + type: integer + resources: + description: Resources defines the resource requirements for the + gateway. + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be + set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + mapPrivilegedContainerPorts: + description: The value to add to privileged ports ( ports < 1024) + for gateway containers + format: int32 + type: integer + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for the + pod to fit on a node. Selector which must match a node''s labels + for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + openshiftSCCName: + description: The name of the OpenShift SecurityContextConstraints + resource for this gateway class to use. + type: string + podSecurityPolicy: + description: The name of an existing Kubernetes PodSecurityPolicy + to bind to the managed ServiceAccount if ACLs are managed. + type: string + serviceType: + description: Service Type string describes ingress methods for a service + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + tolerations: + description: 'Tolerations allow the scheduler to schedule nodes with + matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + {{- end }} \ No newline at end of file diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 130db72a22..c55702c0ac 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -10,9 +10,9 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: gatewayclassconfigs.consul.hashicorp.com + name: gatewayclassconfigs.mesh.consul.hashicorp.com spec: - group: consul.hashicorp.com + group: mesh.consul.hashicorp.com names: kind: GatewayClassConfig listKind: GatewayClassConfigList @@ -20,11 +20,23 @@ spec: singular: gatewayclassconfig scope: Cluster versions: - - name: v1alpha1 + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 schema: openAPIV3Schema: - description: GatewayClassConfig defines the values that may be set on a GatewayClass - for Consul API Gateway. + description: GatewayClassConfig is the Schema for the Mesh Gateway API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -39,163 +51,116 @@ spec: metadata: type: object spec: - description: Spec defines the desired state of GatewayClassConfig. + description: This is a Resource type. properties: + consul: + properties: + address: + type: string + authentication: + properties: + address: + type: string + managed: + type: boolean + method: + type: string + namespace: + type: string + type: object + ports: + properties: + grpc: + format: int32 + type: integer + http: + format: int32 + type: integer + type: object + scheme: + type: string + type: object copyAnnotations: - description: Annotation Information to copy to services or deployments properties: service: - description: List of annotations to copy to the gateway service. items: type: string type: array type: object deployment: - description: Deployment defines the deployment configuration for the - gateway. properties: defaultInstances: - default: 1 - description: Number of gateway instances that should be deployed - by default format: int32 - maximum: 8 - minimum: 1 type: integer maxInstances: - default: 8 - description: Max allowed number of gateway instances format: int32 - maximum: 8 - minimum: 1 type: integer minInstances: - default: 1 - description: Minimum allowed number of gateway instances format: int32 - maximum: 8 - minimum: 1 type: integer - resources: - description: Resources defines the resource requirements for the - gateway. - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only be - set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry in - pod.spec.resourceClaims of the Pod where this field - is used. It makes that resource available inside a - container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object type: object + image: + properties: + consulApiGateway: + type: string + envoy: + type: string + type: object + logLevel: + type: string mapPrivilegedContainerPorts: - description: The value to add to privileged ports ( ports < 1024) - for gateway containers format: int32 type: integer nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a selector which must be true for the - pod to fit on a node. Selector which must match a node''s labels - for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - openshiftSCCName: - description: The name of the OpenShift SecurityContextConstraints - resource for this gateway class to use. type: string - podSecurityPolicy: - description: The name of an existing Kubernetes PodSecurityPolicy - to bind to the managed ServiceAccount if ACLs are managed. + openshiftSccName: type: string serviceType: - description: Service Type string describes ingress methods for a service - enum: - - ClusterIP - - NodePort - - LoadBalancer type: string - tolerations: - description: 'Tolerations allow the scheduler to schedule nodes with - matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + useHostPorts: + type: boolean + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. + message: + description: A human readable message indicating details about + the transition. type: string - operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. + reason: + description: The reason for the condition's last transition. type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. + status: + description: Status of the condition, one of True, False, Unknown. type: string + type: + description: Type of condition. + type: string + required: + - status + - type type: object type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string type: object type: object served: true storage: true + subresources: + status: {} {{- end }} diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index a9758e0b9e..e74d5682ac 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -31,6 +31,7 @@ const ( ProxyConfiguration string = "proxyconfiguration" MeshGateway string = "meshgateway" GatewayClass string = "gatewayclass" + GatewayClassConfig string = "gatewayclassconfig" Global string = "global" Mesh string = "mesh" diff --git a/control-plane/config-entries/controllersv2/gateway_class_config_controller.go b/control-plane/config-entries/controllersv2/gateway_class_config_controller.go new file mode 100644 index 0000000000..c0a7dc45e4 --- /dev/null +++ b/control-plane/config-entries/controllersv2/gateway_class_config_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// GatewayClassConfigController reconciles a GatewayClassConfig object. +type GatewayClassConfigController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + MeshConfigController *MeshConfigController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclassconfig,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclassconfig/status,verbs=get;update;patch + +func (r *GatewayClassConfigController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.GatewayClassConfig{}) +} + +func (r *GatewayClassConfigController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *GatewayClassConfigController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *GatewayClassConfigController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.GatewayClassConfig{}, r) +} diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 52be967944..4f99e75e88 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -465,3 +465,24 @@ rules: - get - patch - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - gatewayclassconfig + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - gatewayclassconfig/status + verbs: + - get + - patch + - update + diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index f33d863da5..d0fa0ebb36 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -186,6 +186,16 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.MeshGateway) return err } + + if err := (&controllersv2.GatewayClassConfigController{ + MeshConfigController: meshConfigReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.GatewayClassConfig), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.GatewayClassConfig) + return err + } if err := (&controllersv2.GatewayClassController{ MeshConfigController: meshConfigReconciler, Client: mgr.GetClient(), From bac589a1be1badc8e6d6f2be1dab648b53990a12 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Wed, 29 Nov 2023 11:33:01 -0500 Subject: [PATCH 491/592] Set cron jobs for release branches back to weekly (#3282) Set jobs to weekly --- .github/workflows/weekly-acceptance-1-1-x.yml | 4 ++-- .github/workflows/weekly-acceptance-1-2-x.yml | 5 ++--- .github/workflows/weekly-acceptance-1-3-x.yml | 4 +--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/weekly-acceptance-1-1-x.yml b/.github/workflows/weekly-acceptance-1-1-x.yml index 86153587b0..c3c39fef32 100644 --- a/.github/workflows/weekly-acceptance-1-1-x.yml +++ b/.github/workflows/weekly-acceptance-1-1-x.yml @@ -5,8 +5,8 @@ name: weekly-acceptance-1-1-x on: schedule: # * is a special character in YAML so you have to quote this string - # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST - - cron: '0 3 * * 3' + # Run weekly on Monday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 1' # these should be the only settings that you will ever need to change diff --git a/.github/workflows/weekly-acceptance-1-2-x.yml b/.github/workflows/weekly-acceptance-1-2-x.yml index 353a086f16..3dac6c8755 100644 --- a/.github/workflows/weekly-acceptance-1-2-x.yml +++ b/.github/workflows/weekly-acceptance-1-2-x.yml @@ -5,9 +5,8 @@ name: weekly-acceptance-1-2-x on: schedule: # * is a special character in YAML so you have to quote this string - # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST - # - cron: '0 3 * * 3' - - cron: '0 0 * * *' # Temporarily nightly until 1.2.0 GA + # Run weekly on Tuesday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 2' # these should be the only settings that you will ever need to change diff --git a/.github/workflows/weekly-acceptance-1-3-x.yml b/.github/workflows/weekly-acceptance-1-3-x.yml index e0cd935204..9d1a2d65a6 100644 --- a/.github/workflows/weekly-acceptance-1-3-x.yml +++ b/.github/workflows/weekly-acceptance-1-3-x.yml @@ -6,9 +6,7 @@ on: schedule: # * is a special character in YAML so you have to quote this string # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST - # - cron: '0 3 * * 3' - - cron: '0 0 * * *' # Temporarily nightly until 1.2.0 GA - + - cron: '0 3 * * 3' # these should be the only settings that you will ever need to change env: From 4a7588e2ea45ef77cc2d5e17f45ecad4c97b3449 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Wed, 29 Nov 2023 16:08:16 -0500 Subject: [PATCH 492/592] Net 6555 Adds field to MeshGW Crd based on new Consul Proto (#3281) * Regenerates MeshGateway CRD with new fields added in Consul Proto * Fixes spacing issue in Makefile causing ensure-controller-gen-version to fail * Remove file that wasn't supposed to be added --- Makefile | 14 +++++++------- charts/consul/templates/crd-meshgateways.yaml | 5 +++++ .../mesh_gateway_controller_test.go | 3 +++ .../mesh.consul.hashicorp.com_meshgateways.yaml | 5 +++++ control-plane/go.mod | 8 ++++---- control-plane/go.sum | 16 ++++++++-------- 6 files changed, 32 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index f803bcd585..ee326b70a2 100644 --- a/Makefile +++ b/Makefile @@ -232,13 +232,13 @@ ensure-controller-gen-version: ## Ensure controller-gen version is v0.12.1. ifeq (, $(shell which $(CONTROLLER_GEN))) @echo "You don't have $(CONTROLLER_GEN), please install it first." else - ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.12.1)) - @echo "controller-gen version is not v0.12.1, uninstall the binary and install the correct version with 'make get-controller-gen'." - @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" - @exit 1 - else - @echo "Found correct version: $(shell $(CONTROLLER_GEN) --version)" - endif +ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.12.1)) + @echo "controller-gen version is not v0.12.1, uninstall the binary and install the correct version with 'make get-controller-gen'." + @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" + @exit 1 +else + @echo "Found correct version: $(shell $(CONTROLLER_GEN) --version)" +endif endif .PHONY: add-copyright-header diff --git a/charts/consul/templates/crd-meshgateways.yaml b/charts/consul/templates/crd-meshgateways.yaml index b40959eb5a..8b12b6bd4c 100644 --- a/charts/consul/templates/crd-meshgateways.yaml +++ b/charts/consul/templates/crd-meshgateways.yaml @@ -51,6 +51,11 @@ spec: metadata: type: object spec: + properties: + gatewayClassName: + description: GatewayClassName is the name of the GatewayClass used + by the MeshGateway + type: string type: object status: properties: diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index e10989f6f2..70caeee3bf 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controllersv2 import ( diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml index 58ef05ce48..4bfab67f6b 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml @@ -47,6 +47,11 @@ spec: metadata: type: object spec: + properties: + gatewayClassName: + description: GatewayClassName is the name of the GatewayClass used + by the MeshGateway + type: string type: object status: properties: diff --git a/control-plane/go.mod b/control-plane/go.mod index 3cd438fa71..3efa978fe7 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,14 +2,14 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231120165139-d7323ca22c04 +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231128204611-fd1d97c3349d require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 github.com/deckarep/golang-set v1.7.1 github.com/fsnotify/fsnotify v1.6.0 - github.com/go-logr/logr v1.2.3 + github.com/go-logr/logr v1.2.4 github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed @@ -45,7 +45,7 @@ require ( k8s.io/apimachinery v0.26.3 k8s.io/client-go v0.26.3 k8s.io/klog/v2 v2.100.1 - k8s.io/utils v0.0.0-20230209194617-a36077c30491 + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/gateway-api v0.7.1 ) @@ -91,7 +91,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.1 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 5d2c027152..1e3ab3e194 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -164,8 +164,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -233,8 +233,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231120165139-d7323ca22c04 h1:QPeZ2ek6gm3Rcs8d0CGKGYIJRutgqFOigyvQSybFE68= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231120165139-d7323ca22c04/go.mod h1:SayEhfXS3DQDnW/vKSZXvkwDObg7XK60KTfrJcp0wrg= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231128204611-fd1d97c3349d h1:9q+x68Y9QNwoUhD06FpjJK7Um3BzXJyK2XkFRWx9644= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231128204611-fd1d97c3349d/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -960,8 +960,8 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= -k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From fd0af5e9e605839900407487a48fe47eb2263b00 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Thu, 30 Nov 2023 14:32:49 -0500 Subject: [PATCH 493/592] NET-6722 Rename MeshConfigController to ConsulResourceController (#3283) * Refactor MeshConfigController to ConsulResouceController * Rename controller file * Rename MeshConfig to ConsulResource * Rename Controller to ResourceController * Update some references to meshConfig * Fixup some comments * Change MeshConfigController to ConsulResourceController in all implementations * Change MeshConfigController to ConsulResourceController in instances * Fix references to MeshConfigController in some tests * Rename mockMeshConfig to mockConsulResource * Rename meshConfigReconciler to consulResourceReconciler * Rename ConsulResourceController to Controller * Fix calls to validate * Use "Controller" instead of "Reconciler" * Empty commit to retrigger tests --- .../v2beta1/trafficpermissions_webhook.go | 10 +- .../{meshconfig.go => consul_resource.go} | 2 +- ..._webhook.go => consul_resource_webhook.go} | 42 +++--- ...est.go => consul_resource_webhook_test.go} | 142 +++++++++--------- .../api/mesh/v2beta1/grpc_route_webhook.go | 10 +- .../api/mesh/v2beta1/http_route_webhook.go | 10 +- .../proxy_configuration_route_webhook.go | 10 +- .../api/mesh/v2beta1/tcp_route_webhook.go | 10 +- ...oller.go => consul_resource_controller.go} | 98 ++++++------ ....go => consul_resource_controller_test.go} | 102 ++++++------- .../gateway_class_config_controller.go | 8 +- .../controllersv2/gateway_class_controller.go | 8 +- .../controllersv2/grpc_route_controller.go | 8 +- .../controllersv2/http_route_controller.go | 8 +- .../controllersv2/mesh_gateway_controller.go | 8 +- .../mesh_gateway_controller_test.go | 2 +- .../proxy_configuration_controller.go | 8 +- .../controllersv2/tcp_route_controller.go | 8 +- .../traffic_permissions_controller.go | 8 +- .../inject-connect/v2controllers.go | 66 ++++---- 20 files changed, 284 insertions(+), 284 deletions(-) rename control-plane/api/common/{meshconfig.go => consul_resource.go} (98%) rename control-plane/api/common/{meshconfig_webhook.go => consul_resource_webhook.go} (62%) rename control-plane/api/common/{meshconfig_webhook_test.go => consul_resource_webhook_test.go} (50%) rename control-plane/config-entries/controllersv2/{meshconfig_controller.go => consul_resource_controller.go} (72%) rename control-plane/config-entries/controllersv2/{meshconfig_controller_test.go => consul_resource_controller_test.go} (88%) diff --git a/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go b/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go index c7d8d0189d..277fa885ef 100644 --- a/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go +++ b/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go @@ -27,7 +27,7 @@ type TrafficPermissionsWebhook struct { client.Client } -var _ common.MeshConfigLister = &TrafficPermissionsWebhook{} +var _ common.ConsulResourceLister = &TrafficPermissionsWebhook{} // NOTE: The path value in the below line is the path to the webhook. // If it is updated, run code-gen, update subcommand/inject-connect/command.go @@ -44,17 +44,17 @@ func (v *TrafficPermissionsWebhook) Handle(ctx context.Context, req admission.Re return admission.Errored(http.StatusBadRequest, err) } - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) + return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) } -func (v *TrafficPermissionsWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { +func (v *TrafficPermissionsWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { var resourceList TrafficPermissionsList if err := v.Client.List(ctx, &resourceList); err != nil { return nil, err } - var entries []common.MeshConfig + var entries []common.ConsulResource for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) + entries = append(entries, common.ConsulResource(item)) } return entries, nil } diff --git a/control-plane/api/common/meshconfig.go b/control-plane/api/common/consul_resource.go similarity index 98% rename from control-plane/api/common/meshconfig.go rename to control-plane/api/common/consul_resource.go index 139818d307..b957d0fb79 100644 --- a/control-plane/api/common/meshconfig.go +++ b/control-plane/api/common/consul_resource.go @@ -11,7 +11,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -type MeshConfig interface { +type ConsulResource interface { ResourceID(namespace, partition string) *pbresource.ID Resource(namespace, partition string) *pbresource.Resource diff --git a/control-plane/api/common/meshconfig_webhook.go b/control-plane/api/common/consul_resource_webhook.go similarity index 62% rename from control-plane/api/common/meshconfig_webhook.go rename to control-plane/api/common/consul_resource_webhook.go index 004b47a589..afda672873 100644 --- a/control-plane/api/common/meshconfig_webhook.go +++ b/control-plane/api/common/consul_resource_webhook.go @@ -15,26 +15,26 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -// MeshConfigLister is implemented by CRD-specific webhooks. -type MeshConfigLister interface { +// ConsulResourceLister is implemented by CRD-specific webhooks. +type ConsulResourceLister interface { // List returns all resources of this type across all namespaces in a // Kubernetes cluster. - List(ctx context.Context) ([]MeshConfig, error) + List(ctx context.Context) ([]ConsulResource, error) } -// ValidateMeshConfig validates a MeshConfig. It is a generic method that +// ValidateConsulResource validates a Consul Resource. It is a generic method that // can be used by all CRD-specific validators. // Callers should pass themselves as validator and kind should be the custom // resource name, e.g. "TrafficPermissions". -func ValidateMeshConfig( +func ValidateConsulResource( ctx context.Context, req admission.Request, logger logr.Logger, - meshConfigLister MeshConfigLister, - meshConfig MeshConfig, + resourceLister ConsulResourceLister, + resource ConsulResource, tenancy ConsulTenancyConfig) admission.Response { - defaultingPatches, err := MeshConfigDefaultingPatches(meshConfig, tenancy) + defaultingPatches, err := ConsulResourceDefaultingPatches(resource, tenancy) if err != nil { return admission.Errored(http.StatusInternalServerError, err) } @@ -45,36 +45,36 @@ func ValidateMeshConfig( // are running Consul enterprise with namespace mirroring. singleConsulDestNS := !(tenancy.EnableConsulNamespaces && tenancy.EnableNSMirroring) if req.Operation == admissionv1.Create && singleConsulDestNS { - logger.Info("validate create", "name", meshConfig.KubernetesName()) + logger.Info("validate create", "name", resource.KubernetesName()) - list, err := meshConfigLister.List(ctx) + list, err := resourceLister.List(ctx) if err != nil { return admission.Errored(http.StatusInternalServerError, err) } for _, item := range list { - if item.KubernetesName() == meshConfig.KubernetesName() { + if item.KubernetesName() == resource.KubernetesName() { return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s resource with name %q is already defined – all %s resources must have unique names across namespaces", - meshConfig.KubeKind(), - meshConfig.KubernetesName(), - meshConfig.KubeKind())) + resource.KubeKind(), + resource.KubernetesName(), + resource.KubeKind())) } } } - if err := meshConfig.Validate(tenancy); err != nil { + if err := resource.Validate(tenancy); err != nil { return admission.Errored(http.StatusBadRequest, err) } - return admission.Patched(fmt.Sprintf("valid %s request", meshConfig.KubeKind()), defaultingPatches...) + return admission.Patched(fmt.Sprintf("valid %s request", resource.KubeKind()), defaultingPatches...) } -// MeshConfigDefaultingPatches returns the patches needed to set fields to their defaults. -func MeshConfigDefaultingPatches(meshConfig MeshConfig, tenancy ConsulTenancyConfig) ([]jsonpatch.Operation, error) { - beforeDefaulting, err := json.Marshal(meshConfig) +// ConsulResourceDefaultingPatches returns the patches needed to set fields to their defaults. +func ConsulResourceDefaultingPatches(resource ConsulResource, tenancy ConsulTenancyConfig) ([]jsonpatch.Operation, error) { + beforeDefaulting, err := json.Marshal(resource) if err != nil { return nil, fmt.Errorf("marshalling input: %s", err) } - meshConfig.DefaultNamespaceFields(tenancy) - afterDefaulting, err := json.Marshal(meshConfig) + resource.DefaultNamespaceFields(tenancy) + afterDefaulting, err := json.Marshal(resource) if err != nil { return nil, fmt.Errorf("marshalling after defaulting: %s", err) } diff --git a/control-plane/api/common/meshconfig_webhook_test.go b/control-plane/api/common/consul_resource_webhook_test.go similarity index 50% rename from control-plane/api/common/meshconfig_webhook_test.go rename to control-plane/api/common/consul_resource_webhook_test.go index 1da2c143dd..63bbf9a6e0 100644 --- a/control-plane/api/common/meshconfig_webhook_test.go +++ b/control-plane/api/common/consul_resource_webhook_test.go @@ -22,12 +22,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -func TestValidateMeshConfig(t *testing.T) { +func TestValidateConsulResource(t *testing.T) { otherNS := "other" cases := map[string]struct { - existingResources []MeshConfig - newResource MeshConfig + existingResources []ConsulResource + newResource ConsulResource enableNamespaces bool nsMirroring bool consulDestinationNS string @@ -37,7 +37,7 @@ func TestValidateMeshConfig(t *testing.T) { }{ "no duplicates, valid": { existingResources: nil, - newResource: &mockMeshConfig{ + newResource: &mockConsulResource{ MockName: "foo", MockNamespace: otherNS, Valid: true, @@ -46,7 +46,7 @@ func TestValidateMeshConfig(t *testing.T) { }, "no duplicates, invalid": { existingResources: nil, - newResource: &mockMeshConfig{ + newResource: &mockConsulResource{ MockName: "foo", MockNamespace: otherNS, Valid: false, @@ -55,11 +55,11 @@ func TestValidateMeshConfig(t *testing.T) { expErrMessage: "invalid", }, "duplicate name": { - existingResources: []MeshConfig{&mockMeshConfig{ + existingResources: []ConsulResource{&mockConsulResource{ MockName: "foo", MockNamespace: "default", }}, - newResource: &mockMeshConfig{ + newResource: &mockConsulResource{ MockName: "foo", MockNamespace: otherNS, Valid: true, @@ -68,11 +68,11 @@ func TestValidateMeshConfig(t *testing.T) { expErrMessage: "mockkind resource with name \"foo\" is already defined – all mockkind resources must have unique names across namespaces", }, "duplicate name, namespaces enabled": { - existingResources: []MeshConfig{&mockMeshConfig{ + existingResources: []ConsulResource{&mockConsulResource{ MockName: "foo", MockNamespace: "default", }}, - newResource: &mockMeshConfig{ + newResource: &mockConsulResource{ MockName: "foo", MockNamespace: otherNS, Valid: true, @@ -82,11 +82,11 @@ func TestValidateMeshConfig(t *testing.T) { expErrMessage: "mockkind resource with name \"foo\" is already defined – all mockkind resources must have unique names across namespaces", }, "duplicate name, namespaces enabled, mirroring enabled": { - existingResources: []MeshConfig{&mockMeshConfig{ + existingResources: []ConsulResource{&mockConsulResource{ MockName: "foo", MockNamespace: "default", }}, - newResource: &mockMeshConfig{ + newResource: &mockConsulResource{ MockName: "foo", MockNamespace: otherNS, Valid: true, @@ -102,10 +102,10 @@ func TestValidateMeshConfig(t *testing.T) { marshalledRequestObject, err := json.Marshal(c.newResource) require.NoError(t, err) - lister := &mockMeshConfigLister{ + lister := &mockConsulResourceLister{ Resources: c.existingResources, } - response := ValidateMeshConfig(ctx, admission.Request{ + response := ValidateConsulResource(ctx, admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ Name: c.newResource.KubernetesName(), Namespace: otherNS, @@ -132,14 +132,14 @@ func TestValidateMeshConfig(t *testing.T) { } } -func TestMeshConfigDefaultingPatches(t *testing.T) { - meshConfig := &mockMeshConfig{ +func TestConsulResourceDefaultingPatches(t *testing.T) { + meshConfig := &mockConsulResource{ MockName: "test", Valid: true, } // This test validates that DefaultingPatches invokes DefaultNamespaceFields on the Config Entry. - patches, err := MeshConfigDefaultingPatches(meshConfig, ConsulTenancyConfig{}) + patches, err := ConsulResourceDefaultingPatches(meshConfig, ConsulTenancyConfig{}) require.NoError(t, err) require.Equal(t, []jsonpatch.Operation{ @@ -151,183 +151,183 @@ func TestMeshConfigDefaultingPatches(t *testing.T) { }, patches) } -type mockMeshConfigLister struct { - Resources []MeshConfig +type mockConsulResourceLister struct { + Resources []ConsulResource } -var _ MeshConfigLister = &mockMeshConfigLister{} +var _ ConsulResourceLister = &mockConsulResourceLister{} -func (in *mockMeshConfigLister) List(_ context.Context) ([]MeshConfig, error) { +func (in *mockConsulResourceLister) List(_ context.Context) ([]ConsulResource, error) { return in.Resources, nil } -type mockMeshConfig struct { +type mockConsulResource struct { MockName string MockNamespace string Valid bool } -var _ MeshConfig = &mockMeshConfig{} +var _ ConsulResource = &mockConsulResource{} -func (in *mockMeshConfig) ResourceID(_, _ string) *pbresource.ID { +func (in *mockConsulResource) ResourceID(_, _ string) *pbresource.ID { return nil } -func (in *mockMeshConfig) Resource(_, _ string) *pbresource.Resource { +func (in *mockConsulResource) Resource(_, _ string) *pbresource.Resource { return nil } -func (in *mockMeshConfig) GetNamespace() string { +func (in *mockConsulResource) GetNamespace() string { return in.MockNamespace } -func (in *mockMeshConfig) SetNamespace(namespace string) { +func (in *mockConsulResource) SetNamespace(namespace string) { in.MockNamespace = namespace } -func (in *mockMeshConfig) GetName() string { +func (in *mockConsulResource) GetName() string { return in.MockName } -func (in *mockMeshConfig) SetName(name string) { +func (in *mockConsulResource) SetName(name string) { in.MockName = name } -func (in *mockMeshConfig) GetGenerateName() string { +func (in *mockConsulResource) GetGenerateName() string { return "" } -func (in *mockMeshConfig) SetGenerateName(_ string) {} +func (in *mockConsulResource) SetGenerateName(_ string) {} -func (in *mockMeshConfig) GetUID() types.UID { +func (in *mockConsulResource) GetUID() types.UID { return "" } -func (in *mockMeshConfig) SetUID(_ types.UID) {} +func (in *mockConsulResource) SetUID(_ types.UID) {} -func (in *mockMeshConfig) GetResourceVersion() string { +func (in *mockConsulResource) GetResourceVersion() string { return "" } -func (in *mockMeshConfig) SetResourceVersion(_ string) {} +func (in *mockConsulResource) SetResourceVersion(_ string) {} -func (in *mockMeshConfig) GetGeneration() int64 { +func (in *mockConsulResource) GetGeneration() int64 { return 0 } -func (in *mockMeshConfig) SetGeneration(_ int64) {} +func (in *mockConsulResource) SetGeneration(_ int64) {} -func (in *mockMeshConfig) GetSelfLink() string { +func (in *mockConsulResource) GetSelfLink() string { return "" } -func (in *mockMeshConfig) SetSelfLink(_ string) {} +func (in *mockConsulResource) SetSelfLink(_ string) {} -func (in *mockMeshConfig) GetCreationTimestamp() metav1.Time { +func (in *mockConsulResource) GetCreationTimestamp() metav1.Time { return metav1.Time{} } -func (in *mockMeshConfig) SetCreationTimestamp(_ metav1.Time) {} +func (in *mockConsulResource) SetCreationTimestamp(_ metav1.Time) {} -func (in *mockMeshConfig) GetDeletionTimestamp() *metav1.Time { +func (in *mockConsulResource) GetDeletionTimestamp() *metav1.Time { return nil } -func (in *mockMeshConfig) SetDeletionTimestamp(_ *metav1.Time) {} +func (in *mockConsulResource) SetDeletionTimestamp(_ *metav1.Time) {} -func (in *mockMeshConfig) GetDeletionGracePeriodSeconds() *int64 { +func (in *mockConsulResource) GetDeletionGracePeriodSeconds() *int64 { return nil } -func (in *mockMeshConfig) SetDeletionGracePeriodSeconds(_ *int64) {} +func (in *mockConsulResource) SetDeletionGracePeriodSeconds(_ *int64) {} -func (in *mockMeshConfig) GetLabels() map[string]string { +func (in *mockConsulResource) GetLabels() map[string]string { return nil } -func (in *mockMeshConfig) SetLabels(_ map[string]string) {} +func (in *mockConsulResource) SetLabels(_ map[string]string) {} -func (in *mockMeshConfig) GetAnnotations() map[string]string { +func (in *mockConsulResource) GetAnnotations() map[string]string { return nil } -func (in *mockMeshConfig) SetAnnotations(_ map[string]string) {} +func (in *mockConsulResource) SetAnnotations(_ map[string]string) {} -func (in *mockMeshConfig) GetFinalizers() []string { +func (in *mockConsulResource) GetFinalizers() []string { return nil } -func (in *mockMeshConfig) SetFinalizers(_ []string) {} +func (in *mockConsulResource) SetFinalizers(_ []string) {} -func (in *mockMeshConfig) GetOwnerReferences() []metav1.OwnerReference { +func (in *mockConsulResource) GetOwnerReferences() []metav1.OwnerReference { return nil } -func (in *mockMeshConfig) SetOwnerReferences(_ []metav1.OwnerReference) {} +func (in *mockConsulResource) SetOwnerReferences(_ []metav1.OwnerReference) {} -func (in *mockMeshConfig) GetClusterName() string { +func (in *mockConsulResource) GetClusterName() string { return "" } -func (in *mockMeshConfig) SetClusterName(_ string) {} +func (in *mockConsulResource) SetClusterName(_ string) {} -func (in *mockMeshConfig) GetManagedFields() []metav1.ManagedFieldsEntry { +func (in *mockConsulResource) GetManagedFields() []metav1.ManagedFieldsEntry { return nil } -func (in *mockMeshConfig) SetManagedFields(_ []metav1.ManagedFieldsEntry) {} +func (in *mockConsulResource) SetManagedFields(_ []metav1.ManagedFieldsEntry) {} -func (in *mockMeshConfig) KubernetesName() string { +func (in *mockConsulResource) KubernetesName() string { return in.MockName } -func (in *mockMeshConfig) GetObjectMeta() metav1.ObjectMeta { +func (in *mockConsulResource) GetObjectMeta() metav1.ObjectMeta { return metav1.ObjectMeta{} } -func (in *mockMeshConfig) GetObjectKind() schema.ObjectKind { +func (in *mockConsulResource) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } -func (in *mockMeshConfig) DeepCopyObject() runtime.Object { +func (in *mockConsulResource) DeepCopyObject() runtime.Object { return in } -func (in *mockMeshConfig) AddFinalizer(_ string) {} +func (in *mockConsulResource) AddFinalizer(_ string) {} -func (in *mockMeshConfig) RemoveFinalizer(_ string) {} +func (in *mockConsulResource) RemoveFinalizer(_ string) {} -func (in *mockMeshConfig) Finalizers() []string { +func (in *mockConsulResource) Finalizers() []string { return nil } -func (in *mockMeshConfig) KubeKind() string { +func (in *mockConsulResource) KubeKind() string { return "mockkind" } -func (in *mockMeshConfig) SetSyncedCondition(_ corev1.ConditionStatus, _ string, _ string) {} +func (in *mockConsulResource) SetSyncedCondition(_ corev1.ConditionStatus, _ string, _ string) {} -func (in *mockMeshConfig) SetLastSyncedTime(_ *metav1.Time) {} +func (in *mockConsulResource) SetLastSyncedTime(_ *metav1.Time) {} -func (in *mockMeshConfig) SyncedCondition() (status corev1.ConditionStatus, reason string, message string) { +func (in *mockConsulResource) SyncedCondition() (status corev1.ConditionStatus, reason string, message string) { return corev1.ConditionTrue, "", "" } -func (in *mockMeshConfig) SyncedConditionStatus() corev1.ConditionStatus { +func (in *mockConsulResource) SyncedConditionStatus() corev1.ConditionStatus { return corev1.ConditionTrue } -func (in *mockMeshConfig) Validate(_ ConsulTenancyConfig) error { +func (in *mockConsulResource) Validate(_ ConsulTenancyConfig) error { if !in.Valid { return errors.New("invalid") } return nil } -func (in *mockMeshConfig) DefaultNamespaceFields(_ ConsulTenancyConfig) { +func (in *mockConsulResource) DefaultNamespaceFields(_ ConsulTenancyConfig) { in.MockNamespace = "bar" } -func (in *mockMeshConfig) MatchesConsul(_ *pbresource.Resource, _, _ string) bool { +func (in *mockConsulResource) MatchesConsul(_ *pbresource.Resource, _, _ string) bool { return false } diff --git a/control-plane/api/mesh/v2beta1/grpc_route_webhook.go b/control-plane/api/mesh/v2beta1/grpc_route_webhook.go index 15b4cbfc46..b73388b4cf 100644 --- a/control-plane/api/mesh/v2beta1/grpc_route_webhook.go +++ b/control-plane/api/mesh/v2beta1/grpc_route_webhook.go @@ -27,7 +27,7 @@ type GRPCRouteWebhook struct { client.Client } -var _ common.MeshConfigLister = &GRPCRouteWebhook{} +var _ common.ConsulResourceLister = &GRPCRouteWebhook{} // NOTE: The path value in the below line is the path to the webhook. // If it is updated, run code-gen, update subcommand/inject-connect/command.go @@ -44,17 +44,17 @@ func (v *GRPCRouteWebhook) Handle(ctx context.Context, req admission.Request) ad return admission.Errored(http.StatusBadRequest, err) } - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) + return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) } -func (v *GRPCRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { +func (v *GRPCRouteWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { var resourceList GRPCRouteList if err := v.Client.List(ctx, &resourceList); err != nil { return nil, err } - var entries []common.MeshConfig + var entries []common.ConsulResource for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) + entries = append(entries, common.ConsulResource(item)) } return entries, nil } diff --git a/control-plane/api/mesh/v2beta1/http_route_webhook.go b/control-plane/api/mesh/v2beta1/http_route_webhook.go index e16db17458..323db0c74a 100644 --- a/control-plane/api/mesh/v2beta1/http_route_webhook.go +++ b/control-plane/api/mesh/v2beta1/http_route_webhook.go @@ -27,7 +27,7 @@ type HTTPRouteWebhook struct { client.Client } -var _ common.MeshConfigLister = &HTTPRouteWebhook{} +var _ common.ConsulResourceLister = &HTTPRouteWebhook{} // NOTE: The path value in the below line is the path to the webhook. // If it is updated, run code-gen, update subcommand/inject-connect/command.go @@ -44,17 +44,17 @@ func (v *HTTPRouteWebhook) Handle(ctx context.Context, req admission.Request) ad return admission.Errored(http.StatusBadRequest, err) } - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) + return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) } -func (v *HTTPRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { +func (v *HTTPRouteWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { var resourceList HTTPRouteList if err := v.Client.List(ctx, &resourceList); err != nil { return nil, err } - var entries []common.MeshConfig + var entries []common.ConsulResource for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) + entries = append(entries, common.ConsulResource(item)) } return entries, nil } diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go b/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go index 711d8c81bc..8c210703ea 100644 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go +++ b/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go @@ -27,7 +27,7 @@ type ProxyConfigurationWebhook struct { client.Client } -var _ common.MeshConfigLister = &ProxyConfigurationWebhook{} +var _ common.ConsulResourceLister = &ProxyConfigurationWebhook{} // NOTE: The path value in the below line is the path to the webhook. // If it is updated, run code-gen, update subcommand/inject-connect/command.go @@ -44,17 +44,17 @@ func (v *ProxyConfigurationWebhook) Handle(ctx context.Context, req admission.Re return admission.Errored(http.StatusBadRequest, err) } - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) + return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) } -func (v *ProxyConfigurationWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { +func (v *ProxyConfigurationWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { var resourceList ProxyConfigurationList if err := v.Client.List(ctx, &resourceList); err != nil { return nil, err } - var entries []common.MeshConfig + var entries []common.ConsulResource for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) + entries = append(entries, common.ConsulResource(item)) } return entries, nil } diff --git a/control-plane/api/mesh/v2beta1/tcp_route_webhook.go b/control-plane/api/mesh/v2beta1/tcp_route_webhook.go index 68e72c6e35..4a7038276e 100644 --- a/control-plane/api/mesh/v2beta1/tcp_route_webhook.go +++ b/control-plane/api/mesh/v2beta1/tcp_route_webhook.go @@ -27,7 +27,7 @@ type TCPRouteWebhook struct { client.Client } -var _ common.MeshConfigLister = &TCPRouteWebhook{} +var _ common.ConsulResourceLister = &TCPRouteWebhook{} // NOTE: The path value in the below line is the path to the webhook. // If it is updated, run code-gen, update subcommand/inject-connect/command.go @@ -44,17 +44,17 @@ func (v *TCPRouteWebhook) Handle(ctx context.Context, req admission.Request) adm return admission.Errored(http.StatusBadRequest, err) } - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) + return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) } -func (v *TCPRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { +func (v *TCPRouteWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { var resourceList TCPRouteList if err := v.Client.List(ctx, &resourceList); err != nil { return nil, err } - var entries []common.MeshConfig + var entries []common.ConsulResource for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) + entries = append(entries, common.ConsulResource(item)) } return entries, nil } diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller.go b/control-plane/config-entries/controllersv2/consul_resource_controller.go similarity index 72% rename from control-plane/config-entries/controllersv2/meshconfig_controller.go rename to control-plane/config-entries/controllersv2/consul_resource_controller.go index c2b2501ebc..82511649cb 100644 --- a/control-plane/config-entries/controllersv2/meshconfig_controller.go +++ b/control-plane/config-entries/controllersv2/consul_resource_controller.go @@ -38,14 +38,14 @@ const ( ExternallyManagedConfigError = "ExternallyManagedConfigError" ) -// Controller is implemented by CRD-specific config-entries. It is used by -// MeshConfigController to abstract CRD-specific config-entries. -type Controller interface { +// ResourceController is implemented by controllers syncing Consul Resources from their CRD counterparts. +// It is used by ConsulResourceController to abstract CRD-specific Consul Resources. +type ResourceController interface { // Update updates the state of the whole object. Update(context.Context, client.Object, ...client.UpdateOption) error // UpdateStatus updates the state of just the object's status. UpdateStatus(context.Context, client.Object, ...client.SubResourceUpdateOption) error - // Get retrieves an obj for the given object key from the Kubernetes Cluster. + // Get retrieves an object for the given object key from the Kubernetes Cluster. // obj must be a struct pointer so that obj can be updated with the response // returned by the Server. Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error @@ -54,10 +54,10 @@ type Controller interface { Logger(types.NamespacedName) logr.Logger } -// MeshConfigController is a generic controller that is used to reconcile -// all resource types, e.g. TrafficPermissions, ProxyConfiguration, etc., since +// ConsulResourceController is a generic controller that is used to reconcile +// all Consul Resource types, e.g. TrafficPermissions, ProxyConfiguration, etc., since // they share the same reconcile behaviour. -type MeshConfigController struct { +type ConsulResourceController struct { // ConsulClientConfig is the config for the Consul API client. ConsulClientConfig *consul.Config @@ -73,9 +73,9 @@ type MeshConfigController struct { // CRD-specific controller should pass themselves in as updater since we // need to call back into their own update methods to ensure they update their // internal state. -func (r *MeshConfigController) ReconcileEntry(ctx context.Context, crdCtrl Controller, req ctrl.Request, meshConfig common.MeshConfig) (ctrl.Result, error) { +func (r *ConsulResourceController) ReconcileEntry(ctx context.Context, crdCtrl ResourceController, req ctrl.Request, resource common.ConsulResource) (ctrl.Result, error) { logger := crdCtrl.Logger(req.NamespacedName) - err := crdCtrl.Get(ctx, req.NamespacedName, meshConfig) + err := crdCtrl.Get(ctx, req.NamespacedName, resource) if k8serr.IsNotFound(err) { return ctrl.Result{}, client.IgnoreNotFound(err) } else if err != nil { @@ -99,24 +99,24 @@ func (r *MeshConfigController) ReconcileEntry(ctx context.Context, crdCtrl Contr ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) } - if meshConfig.GetDeletionTimestamp().IsZero() { + if resource.GetDeletionTimestamp().IsZero() { // The object is not being deleted, so if it does not have our finalizer, // then let's add the finalizer and update the object. This is equivalent // registering our finalizer. - if !slices.Contains(meshConfig.GetFinalizers(), FinalizerName) { - meshConfig.AddFinalizer(FinalizerName) - if err := r.syncUnknown(ctx, crdCtrl, meshConfig); err != nil { + if !slices.Contains(resource.GetFinalizers(), FinalizerName) { + resource.AddFinalizer(FinalizerName) + if err := r.syncUnknown(ctx, crdCtrl, resource); err != nil { return ctrl.Result{}, err } } } - if !meshConfig.GetDeletionTimestamp().IsZero() { - if slices.Contains(meshConfig.GetFinalizers(), FinalizerName) { + if !resource.GetDeletionTimestamp().IsZero() { + if slices.Contains(resource.GetFinalizers(), FinalizerName) { // The object is being deleted logger.Info("deletion event") // Check to see if consul has config entry with the same name - res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: resource.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) // Ignore the error where the resource isn't found in Consul. // It is indicative of desired state. @@ -125,21 +125,21 @@ func (r *MeshConfigController) ReconcileEntry(ctx context.Context, crdCtrl Contr } // In the case this resource was created outside of consul, skip the deletion process and continue - if !managedByMeshController(res.GetResource()) { - logger.Info("resource in Consul was created outside of kubernetes - skipping delete from Consul") + if !managedByConsulResourceController(res.GetResource()) { + logger.Info("resource in Consul was created outside of Kubernetes - skipping delete from Consul") } - if err == nil && managedByMeshController(res.GetResource()) { - _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + if err == nil && managedByConsulResourceController(res.GetResource()) { + _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{Id: resource.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, - fmt.Errorf("deleting config entry from consul: %w", err)) + return r.syncFailed(ctx, logger, crdCtrl, resource, ConsulAgentError, + fmt.Errorf("deleting resource from Consul: %w", err)) } logger.Info("deletion from Consul successful") } // remove our finalizer from the list and update it. - meshConfig.RemoveFinalizer(FinalizerName) - if err := crdCtrl.Update(ctx, meshConfig); err != nil { + resource.RemoveFinalizer(FinalizerName) + if err := crdCtrl.Update(ctx, resource); err != nil { return ctrl.Result{}, err } logger.Info("finalizer removed") @@ -150,7 +150,7 @@ func (r *MeshConfigController) ReconcileEntry(ctx context.Context, crdCtrl Contr } // Check to see if consul has config entry with the same name - res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: resource.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) // In the case the namespace doesn't exist in Consul yet, assume we are racing with the namespace controller // and requeue. @@ -163,42 +163,42 @@ func (r *MeshConfigController) ReconcileEntry(ctx context.Context, crdCtrl Contr // If resource with this name does not exist if isNotFoundErr(err) { - logger.Info("resource not found in consul") + logger.Info("resource not found in Consul") // Create the config entry - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: meshConfig.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: resource.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, - fmt.Errorf("writing resource to consul: %w", err)) + return r.syncFailed(ctx, logger, crdCtrl, resource, ConsulAgentError, + fmt.Errorf("writing resource to Consul: %w", err)) } logger.Info("resource created") - return r.syncSuccessful(ctx, crdCtrl, meshConfig) + return r.syncSuccessful(ctx, crdCtrl, resource) } // If there is an error when trying to get the resource from the api server, // fail the reconcile. if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, err) + return r.syncFailed(ctx, logger, crdCtrl, resource, ConsulAgentError, err) } // TODO: consider the case where we want to migrate a resource existing into Consul to a CRD with an annotation - if !managedByMeshController(res.Resource) { - return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ExternallyManagedConfigError, + if !managedByConsulResourceController(res.Resource) { + return r.syncFailed(ctx, logger, crdCtrl, resource, ExternallyManagedConfigError, fmt.Errorf("resource already exists in Consul")) } - if !meshConfig.MatchesConsul(res.Resource, r.consulNamespace(req.Namespace), r.getConsulPartition()) { + if !resource.MatchesConsul(res.Resource, r.consulNamespace(req.Namespace), r.getConsulPartition()) { logger.Info("resource does not match Consul") - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: meshConfig.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) + _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: resource.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) if err != nil { - return r.syncUnknownWithError(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, + return r.syncUnknownWithError(ctx, logger, crdCtrl, resource, ConsulAgentError, fmt.Errorf("updating resource in Consul: %w", err)) } - logger.Info("config entry updated") - return r.syncSuccessful(ctx, crdCtrl, meshConfig) - } else if meshConfig.SyncedConditionStatus() != corev1.ConditionTrue { - return r.syncSuccessful(ctx, crdCtrl, meshConfig) + logger.Info("resource updated") + return r.syncSuccessful(ctx, crdCtrl, resource) + } else if resource.SyncedConditionStatus() != corev1.ConditionTrue { + return r.syncSuccessful(ctx, crdCtrl, resource) } return ctrl.Result{}, nil @@ -235,7 +235,7 @@ func setupWithManager(mgr ctrl.Manager, resource client.Object, reconciler recon Complete(reconciler) } -func (r *MeshConfigController) syncFailed(ctx context.Context, logger logr.Logger, updater Controller, resource common.MeshConfig, errType string, err error) (ctrl.Result, error) { +func (r *ConsulResourceController) syncFailed(ctx context.Context, logger logr.Logger, updater ResourceController, resource common.ConsulResource, errType string, err error) (ctrl.Result, error) { resource.SetSyncedCondition(corev1.ConditionFalse, errType, err.Error()) if updateErr := updater.UpdateStatus(ctx, resource); updateErr != nil { // Log the original error here because we are returning the updateErr. @@ -246,22 +246,22 @@ func (r *MeshConfigController) syncFailed(ctx context.Context, logger logr.Logge return ctrl.Result{}, err } -func (r *MeshConfigController) syncSuccessful(ctx context.Context, updater Controller, resource common.MeshConfig) (ctrl.Result, error) { +func (r *ConsulResourceController) syncSuccessful(ctx context.Context, updater ResourceController, resource common.ConsulResource) (ctrl.Result, error) { resource.SetSyncedCondition(corev1.ConditionTrue, "", "") timeNow := metav1.NewTime(time.Now()) resource.SetLastSyncedTime(&timeNow) return ctrl.Result{}, updater.UpdateStatus(ctx, resource) } -func (r *MeshConfigController) syncUnknown(ctx context.Context, updater Controller, resource common.MeshConfig) error { +func (r *ConsulResourceController) syncUnknown(ctx context.Context, updater ResourceController, resource common.ConsulResource) error { resource.SetSyncedCondition(corev1.ConditionUnknown, "", "") return updater.Update(ctx, resource) } -func (r *MeshConfigController) syncUnknownWithError(ctx context.Context, +func (r *ConsulResourceController) syncUnknownWithError(ctx context.Context, logger logr.Logger, - updater Controller, - resource common.MeshConfig, + updater ResourceController, + resource common.ConsulResource, errType string, err error) (ctrl.Result, error) { @@ -287,7 +287,7 @@ func isNotFoundErr(err error) bool { return codes.NotFound == s.Code() } -func (r *MeshConfigController) consulNamespace(namespace string) string { +func (r *ConsulResourceController) consulNamespace(namespace string) string { ns := namespaces.ConsulNamespace( namespace, r.EnableConsulNamespaces, @@ -303,14 +303,14 @@ func (r *MeshConfigController) consulNamespace(namespace string) string { return ns } -func (r *MeshConfigController) getConsulPartition() string { +func (r *ConsulResourceController) getConsulPartition() string { if !r.EnableConsulPartitions || r.ConsulPartition == "" { return constants.DefaultConsulPartition } return r.ConsulPartition } -func managedByMeshController(resource *pbresource.Resource) bool { +func managedByConsulResourceController(resource *pbresource.Resource) bool { if resource == nil { return false } diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go b/control-plane/config-entries/controllersv2/consul_resource_controller_test.go similarity index 88% rename from control-plane/config-entries/controllersv2/meshconfig_controller_test.go rename to control-plane/config-entries/controllersv2/consul_resource_controller_test.go index c7da7b3de3..7f5787882f 100644 --- a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go +++ b/control-plane/config-entries/controllersv2/consul_resource_controller_test.go @@ -37,20 +37,20 @@ type testReconciler interface { Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) } -// TestMeshConfigController_createsMeshConfig validated resources are created in Consul from kube objects. -func TestMeshConfigController_createsMeshConfig(t *testing.T) { +// TestConsulResourceController_CreatesConsulResource validated resources are created in Consul from kube objects. +func TestConsulResourceController_CreatesConsulResource(t *testing.T) { t.Parallel() cases := []struct { name string - meshConfig common.MeshConfig + resource common.ConsulResource expected *pbauth.TrafficPermissions reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message }{ { name: "TrafficPermissions", - meshConfig: &v2beta1.TrafficPermissions{ + resource: &v2beta1.TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "my-traffic-permission", Namespace: metav1.NamespaceDefault, @@ -116,7 +116,7 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { return &TrafficPermissionsController{ Client: client, Log: logger, - MeshConfigController: &MeshConfigController{ + Controller: &ConsulResourceController{ ConsulClientConfig: cfg, ConsulServerConnMgr: watcher, }, @@ -137,8 +137,8 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { ctx := context.Background() s := runtime.NewScheme() - s.AddKnownTypes(v2beta1.AuthGroupVersion, c.meshConfig) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.meshConfig).Build() + s.AddKnownTypes(v2beta1.AuthGroupVersion, c.resource) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.resource).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} @@ -154,7 +154,7 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) namespacedName := types.NamespacedName{ Namespace: metav1.NamespaceDefault, - Name: c.meshConfig.KubernetesName(), + Name: c.resource.KubernetesName(), } resp, err := r.Reconcile(ctx, ctrl.Request{ NamespacedName: namespacedName, @@ -162,11 +162,11 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { require.NoError(t, err) require.False(t, resp.Requeue) - req := &pbresource.ReadRequest{Id: c.meshConfig.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} res, err := resourceClient.Read(ctx, req) require.NoError(t, err) require.NotNil(t, res) - require.Equal(t, c.meshConfig.GetName(), res.GetResource().GetId().GetName()) + require.Equal(t, c.resource.GetName(), res.GetResource().GetId().GetName()) actual := c.unmarshal(t, res.GetResource()) opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) @@ -174,30 +174,30 @@ func TestMeshConfigController_createsMeshConfig(t *testing.T) { require.Equal(t, "", diff, "TrafficPermissions do not match") // Check that the status is "synced". - err = fakeClient.Get(ctx, namespacedName, c.meshConfig) + err = fakeClient.Get(ctx, namespacedName, c.resource) require.NoError(t, err) - require.Equal(t, corev1.ConditionTrue, c.meshConfig.SyncedConditionStatus()) + require.Equal(t, corev1.ConditionTrue, c.resource.SyncedConditionStatus()) // Check that the finalizer is added. - require.Contains(t, c.meshConfig.Finalizers(), FinalizerName) + require.Contains(t, c.resource.Finalizers(), FinalizerName) }) } } -func TestMeshConfigController_updatesMeshConfig(t *testing.T) { +func TestConsulResourceController_UpdatesConsulResource(t *testing.T) { t.Parallel() cases := []struct { name string - meshConfig common.MeshConfig + resource common.ConsulResource expected *pbauth.TrafficPermissions reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - updateF func(config common.MeshConfig) + updateF func(config common.ConsulResource) unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message }{ { name: "TrafficPermissions", - meshConfig: &v2beta1.TrafficPermissions{ + resource: &v2beta1.TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "my-traffic-permission", Namespace: metav1.NamespaceDefault, @@ -257,13 +257,13 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { return &TrafficPermissionsController{ Client: client, Log: logger, - MeshConfigController: &MeshConfigController{ + Controller: &ConsulResourceController{ ConsulClientConfig: cfg, ConsulServerConnMgr: watcher, }, } }, - updateF: func(resource common.MeshConfig) { + updateF: func(resource common.ConsulResource) { trafficPermissions := resource.(*v2beta1.TrafficPermissions) trafficPermissions.Spec.Action = pbauth.Action_ACTION_DENY trafficPermissions.Spec.Permissions[0].Sources = trafficPermissions.Spec.Permissions[0].Sources[:1] @@ -283,8 +283,8 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { ctx := context.Background() s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, c.meshConfig) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.meshConfig).Build() + s.AddKnownTypes(v1alpha1.GroupVersion, c.resource) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.resource).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} @@ -297,10 +297,10 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { return err == nil }, 5*time.Second, 500*time.Millisecond) - // We haven't run reconcile yet, so we must create the MeshConfig + // We haven't run reconcile yet, so we must create the resource // in Consul ourselves. { - resource := c.meshConfig.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) + resource := c.resource.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) req := &pbresource.WriteRequest{Resource: resource} _, err := resourceClient.Write(ctx, req) require.NoError(t, err) @@ -310,15 +310,15 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { { namespacedName := types.NamespacedName{ Namespace: metav1.NamespaceDefault, - Name: c.meshConfig.KubernetesName(), + Name: c.resource.KubernetesName(), } // First get it, so we have the latest revision number. - err := fakeClient.Get(ctx, namespacedName, c.meshConfig) + err := fakeClient.Get(ctx, namespacedName, c.resource) require.NoError(t, err) // Update the entry in Kube and run reconcile. - c.updateF(c.meshConfig) - err = fakeClient.Update(ctx, c.meshConfig) + c.updateF(c.resource) + err = fakeClient.Update(ctx, c.resource) require.NoError(t, err) r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) resp, err := r.Reconcile(ctx, ctrl.Request{ @@ -328,11 +328,11 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { require.False(t, resp.Requeue) // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: c.meshConfig.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} res, err := resourceClient.Read(ctx, req) require.NoError(t, err) require.NotNil(t, res) - require.Equal(t, c.meshConfig.GetName(), res.GetResource().GetId().GetName()) + require.Equal(t, c.resource.GetName(), res.GetResource().GetId().GetName()) actual := c.unmarshal(t, res.GetResource()) opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) @@ -343,17 +343,17 @@ func TestMeshConfigController_updatesMeshConfig(t *testing.T) { } } -func TestMeshConfigController_deletesMeshConfig(t *testing.T) { +func TestConsulResourceController_DeletesConsulResource(t *testing.T) { t.Parallel() cases := []struct { - name string - MeshConfigWithDeletion common.MeshConfig - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler + name string + resource common.ConsulResource + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler }{ { name: "TrafficPermissions", - MeshConfigWithDeletion: &v2beta1.TrafficPermissions{ + resource: &v2beta1.TrafficPermissions{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: metav1.NamespaceDefault, @@ -391,7 +391,7 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { return &TrafficPermissionsController{ Client: client, Log: logger, - MeshConfigController: &MeshConfigController{ + Controller: &ConsulResourceController{ ConsulClientConfig: cfg, ConsulServerConnMgr: watcher, }, @@ -405,8 +405,8 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { ctx := context.Background() s := runtime.NewScheme() - s.AddKnownTypes(v2beta1.AuthGroupVersion, c.MeshConfigWithDeletion) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.MeshConfigWithDeletion).Build() + s.AddKnownTypes(v2beta1.AuthGroupVersion, c.resource) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.resource).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} @@ -422,7 +422,7 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { // We haven't run reconcile yet, so we must create the config entry // in Consul ourselves. { - resource := c.MeshConfigWithDeletion.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) + resource := c.resource.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) req := &pbresource.WriteRequest{Resource: resource} _, err := resourceClient.Write(ctx, req) require.NoError(t, err) @@ -432,7 +432,7 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { { namespacedName := types.NamespacedName{ Namespace: metav1.NamespaceDefault, - Name: c.MeshConfigWithDeletion.KubernetesName(), + Name: c.resource.KubernetesName(), } r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) resp, err := r.Reconcile(context.Background(), ctrl.Request{ @@ -442,7 +442,7 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { require.False(t, resp.Requeue) // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: c.MeshConfigWithDeletion.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} _, err = resourceClient.Read(ctx, req) require.Error(t, err) require.True(t, isNotFoundErr(err)) @@ -451,7 +451,7 @@ func TestMeshConfigController_deletesMeshConfig(t *testing.T) { } } -func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { +func TestConsulResourceController_ErrorUpdatesSyncStatus(t *testing.T) { t.Parallel() ctx := context.Background() @@ -496,7 +496,7 @@ func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { reconciler := &TrafficPermissionsController{ Client: fakeClient, Log: logrtest.New(t), - MeshConfigController: &MeshConfigController{ + Controller: &ConsulResourceController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, }, @@ -523,9 +523,9 @@ func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { require.Contains(t, errMsg, actualErrMsg) } -// TestMeshConfigController_setsSyncedToTrue tests that if the resource hasn't changed in +// TestConsulResourceController_SetsSyncedToTrue tests that if the resource hasn't changed in // Consul but our resource's synced status isn't set to true, then we update its status. -func TestMeshConfigController_setsSyncedToTrue(t *testing.T) { +func TestConsulResourceController_SetsSyncedToTrue(t *testing.T) { t.Parallel() ctx := context.Background() @@ -579,7 +579,7 @@ func TestMeshConfigController_setsSyncedToTrue(t *testing.T) { reconciler := &TrafficPermissionsController{ Client: fakeClient, Log: logrtest.New(t), - MeshConfigController: &MeshConfigController{ + Controller: &ConsulResourceController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, }, @@ -610,9 +610,9 @@ func TestMeshConfigController_setsSyncedToTrue(t *testing.T) { require.Equal(t, corev1.ConditionTrue, trafficpermissions.SyncedConditionStatus()) } -// TestMeshConfigController_doesNotCreateUnownedMeshConfig test that if the resource +// TestConsulResourceController_DoesNotCreateUnownedResource test that if the resource // exists in Consul but is not managed by the controller, creating/updating the resource fails. -func TestMeshConfigController_doesNotCreateUnownedMeshConfig(t *testing.T) { +func TestConsulResourceController_DoesNotCreateUnownedResource(t *testing.T) { t.Parallel() ctx := context.Background() @@ -681,7 +681,7 @@ func TestMeshConfigController_doesNotCreateUnownedMeshConfig(t *testing.T) { reconciler := TrafficPermissionsController{ Client: fakeClient, Log: logrtest.New(t), - MeshConfigController: &MeshConfigController{ + Controller: &ConsulResourceController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, }, @@ -715,10 +715,10 @@ func TestMeshConfigController_doesNotCreateUnownedMeshConfig(t *testing.T) { } -// TestMeshConfigController_doesNotDeleteUnownedConfig tests that if the resource +// TestConsulResourceController_doesNotDeleteUnownedConfig tests that if the resource // exists in Consul but is not managed by the controller, deleting the resource does // not delete the Consul resource. -func TestMeshConfigController_doesNotDeleteUnownedConfig(t *testing.T) { +func TestConsulResourceController_doesNotDeleteUnownedConfig(t *testing.T) { t.Parallel() ctx := context.Background() @@ -767,7 +767,7 @@ func TestMeshConfigController_doesNotDeleteUnownedConfig(t *testing.T) { reconciler := &TrafficPermissionsController{ Client: fakeClient, Log: logrtest.New(t), - MeshConfigController: &MeshConfigController{ + Controller: &ConsulResourceController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, }, diff --git a/control-plane/config-entries/controllersv2/gateway_class_config_controller.go b/control-plane/config-entries/controllersv2/gateway_class_config_controller.go index c0a7dc45e4..190b913d34 100644 --- a/control-plane/config-entries/controllersv2/gateway_class_config_controller.go +++ b/control-plane/config-entries/controllersv2/gateway_class_config_controller.go @@ -18,16 +18,16 @@ import ( // GatewayClassConfigController reconciles a GatewayClassConfig object. type GatewayClassConfigController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController } // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclassconfig,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclassconfig/status,verbs=get;update;patch func (r *GatewayClassConfigController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.GatewayClassConfig{}) + return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.GatewayClassConfig{}) } func (r *GatewayClassConfigController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/gateway_class_controller.go b/control-plane/config-entries/controllersv2/gateway_class_controller.go index 2e9572e16c..31de9624bd 100644 --- a/control-plane/config-entries/controllersv2/gateway_class_controller.go +++ b/control-plane/config-entries/controllersv2/gateway_class_controller.go @@ -18,16 +18,16 @@ import ( // GatewayClassController reconciles a MeshGateway object. type GatewayClassController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController } // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclass,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclass/status,verbs=get;update;patch func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.GatewayClass{}) + return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.GatewayClass{}) } func (r *GatewayClassController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/grpc_route_controller.go b/control-plane/config-entries/controllersv2/grpc_route_controller.go index 06accae268..6555bb201f 100644 --- a/control-plane/config-entries/controllersv2/grpc_route_controller.go +++ b/control-plane/config-entries/controllersv2/grpc_route_controller.go @@ -18,16 +18,16 @@ import ( // GRPCRouteController reconciles a GRPCRoute object. type GRPCRouteController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController } // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute/status,verbs=get;update;patch func (r *GRPCRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.GRPCRoute{}) + return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.GRPCRoute{}) } func (r *GRPCRouteController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/http_route_controller.go b/control-plane/config-entries/controllersv2/http_route_controller.go index 545250af74..cf9686b47e 100644 --- a/control-plane/config-entries/controllersv2/http_route_controller.go +++ b/control-plane/config-entries/controllersv2/http_route_controller.go @@ -18,16 +18,16 @@ import ( // HTTPRouteController reconciles a HTTPRoute object. type HTTPRouteController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController } // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute/status,verbs=get;update;patch func (r *HTTPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.HTTPRoute{}) + return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.HTTPRoute{}) } func (r *HTTPRouteController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index 6f74802fd0..3889e7a20b 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -23,9 +23,9 @@ import ( // MeshGatewayController reconciles a MeshGateway object. type MeshGatewayController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController } // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshgateway,verbs=get;list;watch;create;update;patch;delete @@ -56,7 +56,7 @@ func (r *MeshGatewayController) Reconcile(ctx context.Context, req ctrl.Request) } } - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.MeshGateway{}) + return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.MeshGateway{}) } func (r *MeshGatewayController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index 70caeee3bf..a14a44868a 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -139,7 +139,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Client: fakeClient, Log: logrtest.New(t), Scheme: s, - MeshConfigController: &MeshConfigController{ + Controller: &ConsulResourceController{ ConsulClientConfig: consulClient.Cfg, ConsulServerConnMgr: consulClient.Watcher, }, diff --git a/control-plane/config-entries/controllersv2/proxy_configuration_controller.go b/control-plane/config-entries/controllersv2/proxy_configuration_controller.go index f45dda1962..af76aebbcb 100644 --- a/control-plane/config-entries/controllersv2/proxy_configuration_controller.go +++ b/control-plane/config-entries/controllersv2/proxy_configuration_controller.go @@ -18,16 +18,16 @@ import ( // ProxyConfigurationController reconciles a ProxyConfiguration object. type ProxyConfigurationController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController } // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration/status,verbs=get;update;patch func (r *ProxyConfigurationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.ProxyConfiguration{}) + return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.ProxyConfiguration{}) } func (r *ProxyConfigurationController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/tcp_route_controller.go b/control-plane/config-entries/controllersv2/tcp_route_controller.go index 170dbb9fd4..a573835a5c 100644 --- a/control-plane/config-entries/controllersv2/tcp_route_controller.go +++ b/control-plane/config-entries/controllersv2/tcp_route_controller.go @@ -18,16 +18,16 @@ import ( // TCPRouteController reconciles a TCPRoute object. type TCPRouteController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController } // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute/status,verbs=get;update;patch func (r *TCPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.TCPRoute{}) + return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.TCPRoute{}) } func (r *TCPRouteController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go b/control-plane/config-entries/controllersv2/traffic_permissions_controller.go index abcaf98906..960d165f97 100644 --- a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go +++ b/control-plane/config-entries/controllersv2/traffic_permissions_controller.go @@ -18,16 +18,16 @@ import ( // TrafficPermissionsController reconciles a TrafficPermissions object. type TrafficPermissionsController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController } // +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions/status,verbs=get;update;patch func (r *TrafficPermissionsController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &consulv2beta1.TrafficPermissions{}) + return r.Controller.ReconcileEntry(ctx, r, req, &consulv2beta1.TrafficPermissions{}) } func (r *TrafficPermissionsController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index d0fa0ebb36..6572dc2450 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -127,80 +127,80 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage } } - meshConfigReconciler := &controllersv2.MeshConfigController{ + consulResourceController := &controllersv2.ConsulResourceController{ ConsulClientConfig: consulConfig, ConsulServerConnMgr: watcher, ConsulTenancyConfig: consulTenancyConfig, } if err := (&controllersv2.TrafficPermissionsController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.TrafficPermissions), - Scheme: mgr.GetScheme(), + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.TrafficPermissions), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.TrafficPermissions) return err } if err := (&controllersv2.GRPCRouteController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.GRPCRoute), - Scheme: mgr.GetScheme(), + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.GRPCRoute), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.GRPCRoute) return err } if err := (&controllersv2.HTTPRouteController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.HTTPRoute), - Scheme: mgr.GetScheme(), + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.HTTPRoute), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.HTTPRoute) return err } if err := (&controllersv2.TCPRouteController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.TCPRoute), - Scheme: mgr.GetScheme(), + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.TCPRoute), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.TCPRoute) return err } if err := (&controllersv2.ProxyConfigurationController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.ProxyConfiguration), - Scheme: mgr.GetScheme(), + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.ProxyConfiguration), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.ProxyConfiguration) return err } if err := (&controllersv2.MeshGatewayController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.MeshGateway), - Scheme: mgr.GetScheme(), + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.MeshGateway), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.MeshGateway) return err } if err := (&controllersv2.GatewayClassConfigController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.GatewayClassConfig), - Scheme: mgr.GetScheme(), + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.GatewayClassConfig), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.GatewayClassConfig) return err } if err := (&controllersv2.GatewayClassController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.GatewayClass), - Scheme: mgr.GetScheme(), + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.GatewayClass), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.GatewayClass) return err From 6e9f63d64c1633abd0274c11c682f69019a0c73d Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Fri, 1 Dec 2023 11:12:43 -0500 Subject: [PATCH 494/592] Update MatchesConsul to normalize partitions during comparison. (#3284) * Update MatchesConsul to normalize partitions during comparison. * Update test cases with valid datasets --- .changelog/3284.txt | 3 +++ .../api/v1alpha1/exportedservices_types.go | 12 +++++++++-- .../v1alpha1/exportedservices_types_test.go | 4 +++- .../api/v1alpha1/ingressgateway_types.go | 18 +++++++++++++--- .../api/v1alpha1/ingressgateway_types_test.go | 4 ++-- .../api/v1alpha1/samenessgroup_types.go | 13 +++++++++--- .../api/v1alpha1/samenessgroup_types_test.go | 15 ++++++++++--- .../api/v1alpha1/servicedefaults_types.go | 14 +++++++++++-- .../v1alpha1/servicedefaults_types_test.go | 4 ++-- .../api/v1alpha1/serviceresolver_types.go | 21 +++++++++++++++++-- .../v1alpha1/serviceresolver_types_test.go | 6 +++++- .../api/v1alpha1/servicerouter_types.go | 17 ++++++++++++--- .../api/v1alpha1/servicerouter_types_test.go | 4 +++- .../api/v1alpha1/servicesplitter_types.go | 15 +++++++++++-- .../v1alpha1/servicesplitter_types_test.go | 4 +++- 15 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 .changelog/3284.txt diff --git a/.changelog/3284.txt b/.changelog/3284.txt new file mode 100644 index 0000000000..07b896f906 --- /dev/null +++ b/.changelog/3284.txt @@ -0,0 +1,3 @@ +```release-note:bug-fix +control-plane: normalize the `partition` and `namespace` fields in V1 CRDs when comparing with saved version of the config-entry. +``` diff --git a/control-plane/api/v1alpha1/exportedservices_types.go b/control-plane/api/v1alpha1/exportedservices_types.go index 06d6ce30cf..bcbb5e07d0 100644 --- a/control-plane/api/v1alpha1/exportedservices_types.go +++ b/control-plane/api/v1alpha1/exportedservices_types.go @@ -8,7 +8,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul/api" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" @@ -16,6 +15,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ExportedServicesKubeKind = "exportedservices" @@ -189,8 +190,15 @@ func (in *ExportedServices) MatchesConsul(candidate api.ConfigEntry) bool { if !ok { return false } + + specialEquality := cmp.Options{ + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Services.Consumers.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ExportedServicesConfigEntry{}, "Partition", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ExportedServicesConfigEntry{}, "Partition", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) } diff --git a/control-plane/api/v1alpha1/exportedservices_types_test.go b/control-plane/api/v1alpha1/exportedservices_types_test.go index d759a6f270..c9f2b66aa8 100644 --- a/control-plane/api/v1alpha1/exportedservices_types_test.go +++ b/control-plane/api/v1alpha1/exportedservices_types_test.go @@ -7,11 +7,12 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) // Test MatchesConsul for cases that should return true. @@ -103,6 +104,7 @@ func TestExportedServices_MatchesConsul(t *testing.T) { }, { SamenessGroup: "sg1", + Partition: "default", }, }, }, diff --git a/control-plane/api/v1alpha1/ingressgateway_types.go b/control-plane/api/v1alpha1/ingressgateway_types.go index c781ab8cc8..23ea9515b3 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types.go +++ b/control-plane/api/v1alpha1/ingressgateway_types.go @@ -6,16 +6,18 @@ package v1alpha1 import ( "encoding/json" "fmt" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( @@ -268,8 +270,18 @@ func (in *IngressGateway) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } + + specialEquality := cmp.Options{ + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Listeners.Services.Namespace" + }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Listeners.Services.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.IngressGatewayConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.IngressGatewayConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) } func (in *IngressGateway) Validate(consulMeta common.ConsulMeta) error { diff --git a/control-plane/api/v1alpha1/ingressgateway_types_test.go b/control-plane/api/v1alpha1/ingressgateway_types_test.go index 73b53f5fff..9250d4b0c6 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types_test.go +++ b/control-plane/api/v1alpha1/ingressgateway_types_test.go @@ -8,12 +8,13 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func TestIngressGateway_MatchesConsul(t *testing.T) { @@ -102,7 +103,6 @@ func TestIngressGateway_MatchesConsul(t *testing.T) { Name: "name1", Hosts: []string{"host1_1", "host1_2"}, Namespace: "ns1", - Partition: "default", IngressServiceConfig: IngressServiceConfig{ MaxConnections: &maxConnections, MaxPendingRequests: &maxPendingRequests, diff --git a/control-plane/api/v1alpha1/samenessgroup_types.go b/control-plane/api/v1alpha1/samenessgroup_types.go index 86b02445f6..2b5dbd372f 100644 --- a/control-plane/api/v1alpha1/samenessgroup_types.go +++ b/control-plane/api/v1alpha1/samenessgroup_types.go @@ -5,9 +5,9 @@ package v1alpha1 import ( "encoding/json" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul/api" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" @@ -15,6 +15,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ( @@ -166,8 +168,13 @@ func (in *SamenessGroup) MatchesConsul(candidate api.ConfigEntry) bool { if !ok { return false } - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.SamenessGroupConfigEntry{}, "Partition", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), - cmp.Comparer(transparentProxyConfigComparer)) + + specialEquality := cmp.Options{ + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Members.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + } + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.SamenessGroupConfigEntry{}, "Partition", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) } func (in *SamenessGroup) Validate(consulMeta common.ConsulMeta) error { diff --git a/control-plane/api/v1alpha1/samenessgroup_types_test.go b/control-plane/api/v1alpha1/samenessgroup_types_test.go index 0f461701cc..976f9c8db7 100644 --- a/control-plane/api/v1alpha1/samenessgroup_types_test.go +++ b/control-plane/api/v1alpha1/samenessgroup_types_test.go @@ -4,13 +4,15 @@ package v1alpha1 import ( - "github.com/hashicorp/consul-k8s/control-plane/api/common" + "testing" + "time" + capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "testing" - "time" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func TestSamenessGroups_ToConsul(t *testing.T) { @@ -120,6 +122,9 @@ func TestSamenessGroups_MatchesConsul(t *testing.T) { { Partition: "p2", }, + { + Peer: "test-peer", + }, }, }, }, @@ -139,6 +144,10 @@ func TestSamenessGroups_MatchesConsul(t *testing.T) { { Partition: "p2", }, + { + Peer: "test-peer", + Partition: "default", + }, }, }, true, diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index fd764b32b2..904154f184 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -703,9 +703,19 @@ func (in *ServiceDefaults) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } + + specialEquality := cmp.Options{ + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "UpstreamConfig.Overrides.Namespace" + }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "UpstreamConfig.Overrides.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + cmp.Comparer(transparentProxyConfigComparer), + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), - cmp.Comparer(transparentProxyConfigComparer)) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) } func (in *ServiceDefaults) ConsulGlobalResource() bool { diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 2292e55791..7cfe606385 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -565,7 +565,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, { Name: "upstream-default", - Namespace: "ns", EnvoyListenerJSON: `{"key": "value"}`, EnvoyClusterJSON: `{"key": "value"}`, Protocol: "http2", @@ -707,7 +706,8 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, { Name: "upstream-default", - Namespace: "ns", + Namespace: "default", + Partition: "default", EnvoyListenerJSON: `{"key": "value"}`, EnvoyClusterJSON: `{"key": "value"}`, Protocol: "http2", diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 3a8e907222..645cc23ac1 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -15,9 +15,10 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/hashicorp/go-bexpr" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ServiceResolverKubeKind string = "serviceresolver" @@ -325,8 +326,24 @@ func (in *ServiceResolver) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } + + specialEquality := cmp.Options{ + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Redirect.Namespace" + }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Redirect.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Failover.Targets.Namespace" + }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Failover.Targets.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceResolverConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceResolverConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) } func (in *ServiceResolver) ConsulGlobalResource() bool { diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index 3fbc2b4fce..2070bd9df3 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -8,12 +8,13 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func TestServiceResolver_MatchesConsul(t *testing.T) { @@ -63,6 +64,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Service: "redirect", ServiceSubset: "redirect_subset", Namespace: "redirect_namespace", + Partition: "default", Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, @@ -96,6 +98,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Targets: []ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, + {Peer: "failover_peer4"}, }, Policy: &FailoverPolicy{ Mode: "order-by-locality", @@ -181,6 +184,7 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Targets: []capi.ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, + {Peer: "failover_peer4", Partition: "default", Namespace: "default"}, }, Policy: &capi.ServiceResolverFailoverPolicy{ Mode: "order-by-locality", diff --git a/control-plane/api/v1alpha1/servicerouter_types.go b/control-plane/api/v1alpha1/servicerouter_types.go index 43e7353bf5..b322d13134 100644 --- a/control-plane/api/v1alpha1/servicerouter_types.go +++ b/control-plane/api/v1alpha1/servicerouter_types.go @@ -8,14 +8,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func init() { @@ -253,8 +254,18 @@ func (in *ServiceRouter) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } + + specialEquality := cmp.Options{ + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Routes.Destination.Namespace" + }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Routes.Destination.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceRouterConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceRouterConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) } func (in *ServiceRouter) Validate(consulMeta common.ConsulMeta) error { diff --git a/control-plane/api/v1alpha1/servicerouter_types_test.go b/control-plane/api/v1alpha1/servicerouter_types_test.go index 653bdc26c1..97d8e2b81a 100644 --- a/control-plane/api/v1alpha1/servicerouter_types_test.go +++ b/control-plane/api/v1alpha1/servicerouter_types_test.go @@ -8,11 +8,12 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) // Test MatchesConsul. @@ -158,6 +159,7 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { Service: "service", ServiceSubset: "serviceSubset", Namespace: "namespace", + Partition: "default", PrefixRewrite: "prefixRewrite", IdleTimeout: 1 * time.Second, RequestTimeout: 1 * time.Second, diff --git a/control-plane/api/v1alpha1/servicesplitter_types.go b/control-plane/api/v1alpha1/servicesplitter_types.go index d94dbb7120..99ded7324a 100644 --- a/control-plane/api/v1alpha1/servicesplitter_types.go +++ b/control-plane/api/v1alpha1/servicesplitter_types.go @@ -9,13 +9,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func init() { @@ -168,8 +169,18 @@ func (in *ServiceSplitter) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } + + specialEquality := cmp.Options{ + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Splits.Namespace" + }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), + cmp.FilterPath(func(path cmp.Path) bool { + return path.String() == "Splits.Partition" + }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), + } + // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceSplitterConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceSplitterConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) } func (in *ServiceSplitter) Validate(consulMeta common.ConsulMeta) error { diff --git a/control-plane/api/v1alpha1/servicesplitter_types_test.go b/control-plane/api/v1alpha1/servicesplitter_types_test.go index e2ade99f1c..c5ab83becc 100644 --- a/control-plane/api/v1alpha1/servicesplitter_types_test.go +++ b/control-plane/api/v1alpha1/servicesplitter_types_test.go @@ -7,11 +7,12 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" ) // Test MatchesConsul. @@ -96,6 +97,7 @@ func TestServiceSplitter_MatchesConsul(t *testing.T) { Service: "foo", ServiceSubset: "bar", Namespace: "baz", + Partition: "default", RequestHeaders: &capi.HTTPHeaderModifiers{ Add: map[string]string{ "foo": "bar", From 981eb8b7f41184fe7160c0e85eb6d2729e299d29 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Fri, 1 Dec 2023 12:17:43 -0500 Subject: [PATCH 495/592] NET-6752 Change scope of MeshGateway CRD to Namespaced (#3287) * Change scope of MeshGateway CRD to Namespaced This matches the scope of the corresponding Gateway CRD in the Kubernetes Gateway API, which we will one day use. It also prevents some undesirable side effects of being cluster-scoped. Namely, the cluster-scoped MeshGateway always resides in the "default" namespace implicitly and thus cannot be referenced as the owner of Deployments, ServiceAccounts, etc in any other namespace due to the fact that cross-namespace owner references are not allowed. * Specify namespace in serviceaccount builder test * Set namespace for meshGateways in resource job config map * Modify unit test to use non-default namespace --- charts/consul/templates/crd-meshgateways.yaml | 2 +- charts/consul/templates/gateway-resources-configmap.yaml | 1 + control-plane/api/mesh/v2beta1/mesh_gateway_types.go | 2 +- .../controllersv2/mesh_gateway_controller_test.go | 6 +++--- .../crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml | 2 +- control-plane/gateways/serviceaccount_test.go | 4 ++-- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/charts/consul/templates/crd-meshgateways.yaml b/charts/consul/templates/crd-meshgateways.yaml index 8b12b6bd4c..5cd493ea9e 100644 --- a/charts/consul/templates/crd-meshgateways.yaml +++ b/charts/consul/templates/crd-meshgateways.yaml @@ -18,7 +18,7 @@ spec: listKind: MeshGatewayList plural: meshgateways singular: meshgateway - scope: Cluster + scope: Namespaced versions: - additionalPrinterColumns: - description: The sync status of the resource with Consul diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 5c19fbebe5..c47b102f00 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -32,6 +32,7 @@ data: serviceType: {{ .Values.meshGateway.service.type }} meshGateways: - name: mesh-gateway + namespace: {{ .Release.Namespace }} spec: gatewayClassName: consul-mesh-gateway {{- end }} diff --git a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go index fe3af45feb..e8bf31cfe3 100644 --- a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go +++ b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go @@ -33,7 +33,7 @@ func init() { // +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" // +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:scope="Cluster" +// +kubebuilder:resource:scope="Namespaced" type MeshGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index a14a44868a..c3fae8126d 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -45,21 +45,21 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { k8sObjects: []runtime.Object{ &v2beta1.MeshGateway{ ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", + Namespace: "consul", Name: "mesh-gateway", }, }, }, request: ctrl.Request{ NamespacedName: types.NamespacedName{ - Namespace: "default", + Namespace: "consul", Name: "mesh-gateway", }, }, expectedResult: ctrl.Result{}, postReconcile: func(t *testing.T, c client.Client) { // Verify ServiceAccount was created - key := client.ObjectKey{Namespace: "default", Name: "mesh-gateway"} + key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} assert.NoError(t, c.Get(context.Background(), key, &corev1.ServiceAccount{})) }, }, diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml index 4bfab67f6b..2191581e0c 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml @@ -14,7 +14,7 @@ spec: listKind: MeshGatewayList plural: meshgateways singular: meshgateway - scope: Cluster + scope: Namespaced versions: - additionalPrinterColumns: - description: The sync status of the resource with Consul diff --git a/control-plane/gateways/serviceaccount_test.go b/control-plane/gateways/serviceaccount_test.go index d769cb24ff..8915a400b8 100644 --- a/control-plane/gateways/serviceaccount_test.go +++ b/control-plane/gateways/serviceaccount_test.go @@ -13,14 +13,14 @@ import ( func TestNewMeshGatewayBuilder_ServiceAccount(t *testing.T) { b := NewMeshGatewayBuilder(&meshv2beta1.MeshGateway{ ObjectMeta: metav1.ObjectMeta{ - Namespace: "", + Namespace: "default", Name: "mesh-gateway", }, }) expected := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Namespace: "", + Namespace: "default", Name: "mesh-gateway", Labels: b.Labels(), }, From dc9de11aece37766eb7b32b53c0a7def78efd18f Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Fri, 1 Dec 2023 17:14:53 -0500 Subject: [PATCH 496/592] Stop syncing GatewayClass + GatewayClassConfig into Consul (#3286) * Stop syncing GatewayClass + GatewayClassConfig into Consul * gofmt --- .../controllersv2/gateway_class_config_controller.go | 4 +++- .../config-entries/controllersv2/gateway_class_controller.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/control-plane/config-entries/controllersv2/gateway_class_config_controller.go b/control-plane/config-entries/controllersv2/gateway_class_config_controller.go index 190b913d34..1db5c9b765 100644 --- a/control-plane/config-entries/controllersv2/gateway_class_config_controller.go +++ b/control-plane/config-entries/controllersv2/gateway_class_config_controller.go @@ -27,7 +27,9 @@ type GatewayClassConfigController struct { // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclassconfig/status,verbs=get;update;patch func (r *GatewayClassConfigController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.GatewayClassConfig{}) + // GatewayClassConfig is not synced into Consul because Consul has no use for it. + // Consul is only aware of the resource for the sake of Kubernetes CRD generation. + return ctrl.Result{}, nil } func (r *GatewayClassConfigController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/gateway_class_controller.go b/control-plane/config-entries/controllersv2/gateway_class_controller.go index 31de9624bd..17299498b4 100644 --- a/control-plane/config-entries/controllersv2/gateway_class_controller.go +++ b/control-plane/config-entries/controllersv2/gateway_class_controller.go @@ -27,7 +27,9 @@ type GatewayClassController struct { // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclass/status,verbs=get;update;patch func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.GatewayClass{}) + // GatewayClass is not synced into Consul because Consul has no use for it. + // Consul is only aware of the resource for the sake of Kubernetes CRD generation. + return ctrl.Result{}, nil } func (r *GatewayClassController) Logger(name types.NamespacedName) logr.Logger { From cc812d1d818fc43f8300dea50dfc02e07ab69a37 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Mon, 4 Dec 2023 12:07:20 -0500 Subject: [PATCH 497/592] NET-6758 Use kubebuilder for v2 GatewayClassConfig CRD (#3297) * Create GatewayClassConfig CRD using kubebuilder instead of Consul proto * Hardcode inapplicable Consul resource logic * Restructure to embed labels/annotations + remove .kubernetes * Remove implementation of Consul resource interface This will prevent the GatewayClassConfig from ever being accidentally assumed to be syncable into Consul * Use existing GatewayClassConfig structure This allows us to keep the discussion about the new structure of the GatewayClassConfig in a separate PR for discussion --- .../templates/crd-gatewayclassconfigs.yaml | 168 +++++++++++++----- .../v2beta1/gateway_class_config_types.go | 159 ++++++----------- .../api/mesh/v2beta1/zz_generated.deepcopy.go | 92 ++++++++++ ...sul.hashicorp.com_gatewayclassconfigs.yaml | 168 +++++++++++++----- 4 files changed, 403 insertions(+), 184 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index c55702c0ac..7e89d05fe7 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -51,74 +51,162 @@ spec: metadata: type: object spec: - description: This is a Resource type. + description: GatewayClassConfigSpec specifies the desired state of the + Config CRD. properties: - consul: - properties: - address: - type: string - authentication: - properties: - address: - type: string - managed: - type: boolean - method: - type: string - namespace: - type: string - type: object - ports: - properties: - grpc: - format: int32 - type: integer - http: - format: int32 - type: integer - type: object - scheme: - type: string - type: object copyAnnotations: + description: Annotation Information to copy to services or deployments properties: service: + description: List of annotations to copy to the gateway service. items: type: string type: array type: object deployment: + description: Deployment defines the deployment configuration for the + gateway. properties: defaultInstances: + default: 1 + description: Number of gateway instances that should be deployed + by default format: int32 + maximum: 8 + minimum: 1 type: integer maxInstances: + default: 8 + description: Max allowed number of gateway instances format: int32 + maximum: 8 + minimum: 1 type: integer minInstances: + default: 1 + description: Minimum allowed number of gateway instances format: int32 + maximum: 8 + minimum: 1 type: integer + resources: + description: Resources defines the resource requirements for the + gateway. + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be + set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object type: object - image: - properties: - consulApiGateway: - type: string - envoy: - type: string - type: object - logLevel: - type: string mapPrivilegedContainerPorts: + description: The value to add to privileged ports ( ports < 1024) + for gateway containers format: int32 type: integer nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for the + pod to fit on a node. Selector which must match a node''s labels + for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + openshiftSCCName: + description: The name of the OpenShift SecurityContextConstraints + resource for this gateway class to use. type: string - openshiftSccName: + podSecurityPolicy: + description: The name of an existing Kubernetes PodSecurityPolicy + to bind to the managed ServiceAccount if ACLs are managed. type: string serviceType: + description: Service Type string describes ingress methods for a service + enum: + - ClusterIP + - NodePort + - LoadBalancer type: string - useHostPorts: - type: boolean + tolerations: + description: 'Tolerations allow the scheduler to schedule nodes with + matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array type: object status: properties: diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index dbfbc2f8b4..74f91a7246 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -4,23 +4,10 @@ package v2beta1 import ( - "fmt" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - gatewayClassConfigKubeKind = "gatewayclassconfig" -) - func init() { MeshSchemeBuilder.Register(&GatewayClassConfig{}, &GatewayClassConfigList{}) } @@ -38,115 +25,79 @@ type GatewayClassConfig struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec pbmesh.GatewayClassConfig `json:"spec,omitempty"` + Spec GatewayClassConfigSpec `json:"spec,omitempty"` Status `json:"status,omitempty"` } -// +kubebuilder:object:root=true +// +k8s:deepcopy-gen=true -// GatewayClassConfigList contains a list of GatewayClassConfig. -type GatewayClassConfigList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*GatewayClassConfig `json:"items"` -} +// GatewayClassConfigSpec specifies the desired state of the Config CRD. +type GatewayClassConfigSpec struct { -func (in *GatewayClassConfig) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.GatewayClassConfigType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} + // +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer + ServiceType *corev1.ServiceType `json:"serviceType,omitempty"` -func (in *GatewayClassConfig) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} + // NodeSelector is a selector which must be true for the pod to fit on a node. + // Selector which must match a node's labels for the pod to be scheduled on that node. + // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + NodeSelector map[string]string `json:"nodeSelector,omitempty"` -func (in *GatewayClassConfig) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} + // Tolerations allow the scheduler to schedule nodes with matching taints. + // More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` -func (in *GatewayClassConfig) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} + // Deployment defines the deployment configuration for the gateway. + DeploymentSpec DeploymentSpec `json:"deployment,omitempty"` -func (in *GatewayClassConfig) Finalizers() []string { - return in.ObjectMeta.Finalizers -} + // Annotation Information to copy to services or deployments + CopyAnnotations CopyAnnotationsSpec `json:"copyAnnotations,omitempty"` -func (in *GatewayClassConfig) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} + // The name of an existing Kubernetes PodSecurityPolicy to bind to the managed ServiceAccount if ACLs are managed. + PodSecurityPolicy string `json:"podSecurityPolicy,omitempty"` -func (in *GatewayClassConfig) KubeKind() string { - return gatewayClassConfigKubeKind -} + // The name of the OpenShift SecurityContextConstraints resource for this gateway class to use. + OpenshiftSCCName string `json:"openshiftSCCName,omitempty"` -func (in *GatewayClassConfig) KubernetesName() string { - return in.ObjectMeta.Name + // The value to add to privileged ports ( ports < 1024) for gateway containers + MapPrivilegedContainerPorts int32 `json:"mapPrivilegedContainerPorts,omitempty"` } -func (in *GatewayClassConfig) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} +// +k8s:deepcopy-gen=true -func (in *GatewayClassConfig) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} +type DeploymentSpec struct { + // +kubebuilder:default:=1 + // +kubebuilder:validation:Maximum=8 + // +kubebuilder:validation:Minimum=1 + // Number of gateway instances that should be deployed by default + DefaultInstances *int32 `json:"defaultInstances,omitempty"` + // +kubebuilder:default:=8 + // +kubebuilder:validation:Maximum=8 + // +kubebuilder:validation:Minimum=1 + // Max allowed number of gateway instances + MaxInstances *int32 `json:"maxInstances,omitempty"` + // +kubebuilder:default:=1 + // +kubebuilder:validation:Maximum=8 + // +kubebuilder:validation:Minimum=1 + // Minimum allowed number of gateway instances + MinInstances *int32 `json:"minInstances,omitempty"` -func (in *GatewayClassConfig) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message + // Resources defines the resource requirements for the gateway. + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` } -func (in *GatewayClassConfig) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} +//+kubebuilder:object:generate=true -func (in *GatewayClassConfig) Validate(tenancy common.ConsulTenancyConfig) error { - // TODO add validation logic that ensures we only ever write this to the default namespace. - return nil +// CopyAnnotationsSpec defines the annotations that should be copied to the gateway service. +type CopyAnnotationsSpec struct { + // List of annotations to copy to the gateway service. + Service []string `json:"service,omitempty"` } -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *GatewayClassConfig) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} +// +kubebuilder:object:root=true + +// GatewayClassConfigList contains a list of GatewayClassConfig. +type GatewayClassConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*GatewayClassConfig `json:"items"` +} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index 799bb5b543..b8461fca9e 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -6,6 +6,7 @@ package v2beta1 import ( + "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -46,6 +47,61 @@ func (in Conditions) DeepCopy() Conditions { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CopyAnnotationsSpec) DeepCopyInto(out *CopyAnnotationsSpec) { + *out = *in + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CopyAnnotationsSpec. +func (in *CopyAnnotationsSpec) DeepCopy() *CopyAnnotationsSpec { + if in == nil { + return nil + } + out := new(CopyAnnotationsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { + *out = *in + if in.DefaultInstances != nil { + in, out := &in.DefaultInstances, &out.DefaultInstances + *out = new(int32) + **out = **in + } + if in.MaxInstances != nil { + in, out := &in.MaxInstances, &out.MaxInstances + *out = new(int32) + **out = **in + } + if in.MinInstances != nil { + in, out := &in.MinInstances, &out.MinInstances + *out = new(int32) + **out = **in + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec. +func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { + if in == nil { + return nil + } + out := new(DeploymentSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GRPCRoute) DeepCopyInto(out *GRPCRoute) { *out = *in @@ -199,6 +255,42 @@ func (in *GatewayClassConfigList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassConfigSpec) DeepCopyInto(out *GatewayClassConfigSpec) { + *out = *in + if in.ServiceType != nil { + in, out := &in.ServiceType, &out.ServiceType + *out = new(v1.ServiceType) + **out = **in + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.DeploymentSpec.DeepCopyInto(&out.DeploymentSpec) + in.CopyAnnotations.DeepCopyInto(&out.CopyAnnotations) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigSpec. +func (in *GatewayClassConfigSpec) DeepCopy() *GatewayClassConfigSpec { + if in == nil { + return nil + } + out := new(GatewayClassConfigSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayClassList) DeepCopyInto(out *GatewayClassList) { *out = *in diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index eb68bcbdcf..38c134c158 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -47,74 +47,162 @@ spec: metadata: type: object spec: - description: This is a Resource type. + description: GatewayClassConfigSpec specifies the desired state of the + Config CRD. properties: - consul: - properties: - address: - type: string - authentication: - properties: - address: - type: string - managed: - type: boolean - method: - type: string - namespace: - type: string - type: object - ports: - properties: - grpc: - format: int32 - type: integer - http: - format: int32 - type: integer - type: object - scheme: - type: string - type: object copyAnnotations: + description: Annotation Information to copy to services or deployments properties: service: + description: List of annotations to copy to the gateway service. items: type: string type: array type: object deployment: + description: Deployment defines the deployment configuration for the + gateway. properties: defaultInstances: + default: 1 + description: Number of gateway instances that should be deployed + by default format: int32 + maximum: 8 + minimum: 1 type: integer maxInstances: + default: 8 + description: Max allowed number of gateway instances format: int32 + maximum: 8 + minimum: 1 type: integer minInstances: + default: 1 + description: Minimum allowed number of gateway instances format: int32 + maximum: 8 + minimum: 1 type: integer + resources: + description: Resources defines the resource requirements for the + gateway. + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be + set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object type: object - image: - properties: - consulApiGateway: - type: string - envoy: - type: string - type: object - logLevel: - type: string mapPrivilegedContainerPorts: + description: The value to add to privileged ports ( ports < 1024) + for gateway containers format: int32 type: integer nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for the + pod to fit on a node. Selector which must match a node''s labels + for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + openshiftSCCName: + description: The name of the OpenShift SecurityContextConstraints + resource for this gateway class to use. type: string - openshiftSccName: + podSecurityPolicy: + description: The name of an existing Kubernetes PodSecurityPolicy + to bind to the managed ServiceAccount if ACLs are managed. type: string serviceType: + description: Service Type string describes ingress methods for a service + enum: + - ClusterIP + - NodePort + - LoadBalancer type: string - useHostPorts: - type: boolean + tolerations: + description: 'Tolerations allow the scheduler to schedule nodes with + matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array type: object status: properties: From ccabee44129e0dc5b361977ae9cea35124838af4 Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 12:14:11 -0500 Subject: [PATCH 498/592] [COMPLIANCE] Add Copyright and License Headers (#3174) Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- acceptance/tests/cloud/fakeserver_client.go | 3 +++ acceptance/tests/cloud/metrics_validation.go | 3 +++ .../fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml | 3 +++ .../fixtures/cases/api-gateways/kitchen-sink/filters.yaml | 3 +++ cli/cmd/proxy/stats/command.go | 3 +++ cli/cmd/proxy/stats/command_test.go | 3 +++ control-plane/consul/dynamic.go | 3 +++ control-plane/consul/dynamic_test.go | 3 +++ control-plane/gateways/builder.go | 3 +++ control-plane/gateways/labels.go | 3 +++ control-plane/gateways/serviceaccount.go | 3 +++ control-plane/gateways/serviceaccount_test.go | 3 +++ 12 files changed, 36 insertions(+) diff --git a/acceptance/tests/cloud/fakeserver_client.go b/acceptance/tests/cloud/fakeserver_client.go index ec668d16e5..7cfe8e799d 100644 --- a/acceptance/tests/cloud/fakeserver_client.go +++ b/acceptance/tests/cloud/fakeserver_client.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cloud import ( diff --git a/acceptance/tests/cloud/metrics_validation.go b/acceptance/tests/cloud/metrics_validation.go index 558ae54509..9288f6e615 100644 --- a/acceptance/tests/cloud/metrics_validation.go +++ b/acceptance/tests/cloud/metrics_validation.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package cloud import ( diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml index a35f41ed61..966eab85a6 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: RouteRetryFilter metadata: diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml index a35f41ed61..966eab85a6 100644 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml +++ b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml @@ -1,3 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: consul.hashicorp.com/v1alpha1 kind: RouteRetryFilter metadata: diff --git a/cli/cmd/proxy/stats/command.go b/cli/cmd/proxy/stats/command.go index 5c2c5b1bea..ec46ec52bc 100644 --- a/cli/cmd/proxy/stats/command.go +++ b/cli/cmd/proxy/stats/command.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package stats import ( diff --git a/cli/cmd/proxy/stats/command_test.go b/cli/cmd/proxy/stats/command_test.go index a50b67c078..c223570a61 100644 --- a/cli/cmd/proxy/stats/command_test.go +++ b/cli/cmd/proxy/stats/command_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package stats import ( diff --git a/control-plane/consul/dynamic.go b/control-plane/consul/dynamic.go index 36201701dd..55db841e58 100644 --- a/control-plane/consul/dynamic.go +++ b/control-plane/consul/dynamic.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/control-plane/consul/dynamic_test.go b/control-plane/consul/dynamic_test.go index 8159b82d66..8a260cbdf0 100644 --- a/control-plane/consul/dynamic_test.go +++ b/control-plane/consul/dynamic_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package consul import ( diff --git a/control-plane/gateways/builder.go b/control-plane/gateways/builder.go index 1987f904e4..748a981ed1 100644 --- a/control-plane/gateways/builder.go +++ b/control-plane/gateways/builder.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" diff --git a/control-plane/gateways/labels.go b/control-plane/gateways/labels.go index 1ed6ec4ed0..d816a4c3f0 100644 --- a/control-plane/gateways/labels.go +++ b/control-plane/gateways/labels.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways func (b *meshGatewayBuilder) Labels() map[string]string { diff --git a/control-plane/gateways/serviceaccount.go b/control-plane/gateways/serviceaccount.go index aa51c53aae..e9bb22a41f 100644 --- a/control-plane/gateways/serviceaccount.go +++ b/control-plane/gateways/serviceaccount.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import ( diff --git a/control-plane/gateways/serviceaccount_test.go b/control-plane/gateways/serviceaccount_test.go index 8915a400b8..15d18dbbad 100644 --- a/control-plane/gateways/serviceaccount_test.go +++ b/control-plane/gateways/serviceaccount_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import ( From fd6d7659d7c446c7b5dc19403ddca019e17017a5 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Mon, 4 Dec 2023 16:47:47 -0500 Subject: [PATCH 499/592] [NET-6462] Update gateway resources job for mesh gw v2 (#3273) * create gateway class and gateway class config for mesh gateway if config file is present * Comment method, add mesh gw config to existing run test * use generic gateway names rather than specific mesh gateway, add test for when file is not found * Update control-plane/subcommand/gateway-resources/command.go Co-authored-by: Nathan Coleman * Update control-plane/subcommand/gateway-resources/command.go Co-authored-by: Nathan Coleman * Update control-plane/subcommand/gateway-resources/command_test.go Co-authored-by: Nathan Coleman * Modified test name, made function more generic * Rebase main, add tests for loading json * use k8s yaml * Use json for config file, move resources to separate config map * back to yaml * Use kubebuilder built gatewayclassconfig * Fix creating gatewayclass and classconfig resources * Fix bats tests for config map * Appease the linter --------- Co-authored-by: Nathan Coleman --- .../gateway-resources-clusterrole.yaml | 2 + .../gateway-resources-configmap.yaml | 7 +- .../unit/gateway-resources-configmap.bats | 2 +- control-plane/go.mod | 6 +- control-plane/go.sum | 4 +- .../subcommand/gateway-resources/command.go | 215 ++++++++++++-- .../gateway-resources/command_test.go | 269 +++++++++++++++++- 7 files changed, 460 insertions(+), 45 deletions(-) diff --git a/charts/consul/templates/gateway-resources-clusterrole.yaml b/charts/consul/templates/gateway-resources-clusterrole.yaml index c3bdfeb4a3..42e60f75aa 100644 --- a/charts/consul/templates/gateway-resources-clusterrole.yaml +++ b/charts/consul/templates/gateway-resources-clusterrole.yaml @@ -12,6 +12,7 @@ metadata: rules: - apiGroups: - consul.hashicorp.com + - mesh.consul.hashicorp.com resources: - gatewayclassconfigs verbs: @@ -20,6 +21,7 @@ rules: - create - apiGroups: - gateway.networking.k8s.io + - mesh.consul.hashicorp.com resources: - gatewayclasses verbs: diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index c47b102f00..740c0655f6 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -5,7 +5,7 @@ kind: ConfigMap metadata: name: {{ template "consul.fullname" . }}-gateway-resources-config namespace: {{ .Release.Namespace }} - labels: + labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} @@ -26,8 +26,7 @@ data: kind: gatewayClassConfig spec: deployment: - resources: - {{ .Values.meshGateway.resources }} + resources: {{ toJson .Values.meshGateway.resources }} nodeSelector: {{ .Values.meshGateway.nodeSelector }} serviceType: {{ .Values.meshGateway.service.type }} meshGateways: @@ -35,5 +34,5 @@ data: namespace: {{ .Release.Namespace }} spec: gatewayClassName: consul-mesh-gateway - {{- end }} + {{- end }} {{- end }} diff --git a/charts/consul/test/unit/gateway-resources-configmap.bats b/charts/consul/test/unit/gateway-resources-configmap.bats index 5c0182f602..bf7800866c 100644 --- a/charts/consul/test/unit/gateway-resources-configmap.bats +++ b/charts/consul/test/unit/gateway-resources-configmap.bats @@ -7,7 +7,7 @@ load _helpers assert_empty helm template \ -s templates/gateway-resources-configmap.yaml \ --set 'connectInject.enabled=false' \ - . + . } @test "gateway-resources/ConfigMap: enabled with connectInject.enabled=true" { diff --git a/control-plane/go.mod b/control-plane/go.mod index 3efa978fe7..027258c2b7 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231128204611-fd1d97c3349d +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f require ( github.com/cenkalti/backoff v2.2.1+incompatible @@ -40,7 +40,7 @@ require ( gomodules.xyz/jsonpatch/v2 v2.3.0 google.golang.org/grpc v1.56.3 google.golang.org/protobuf v1.30.0 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 k8s.io/client-go v0.26.3 @@ -167,7 +167,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.26.3 // indirect k8s.io/component-base v0.26.3 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 1e3ab3e194..2af704623b 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231128204611-fd1d97c3349d h1:9q+x68Y9QNwoUhD06FpjJK7Um3BzXJyK2XkFRWx9644= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231128204611-fd1d97c3349d/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f h1:wbJDaQukQ6LPIZwAqMSDl/fO7Wd1Yij8vFBam0ABy6A= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index ea4338f2d3..3fb7dd9c24 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -16,9 +16,8 @@ import ( "github.com/cenkalti/backoff" "github.com/mitchellh/cli" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,13 +25,24 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + k8syaml "sigs.k8s.io/yaml" + + meshv2beta1 "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + + authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) +const ( + gatewayConfigFilename = "/consul/config/config.yaml" + resourceConfigFilename = "/consul/config/resources.json" +) + // this dupes the Kubernetes tolerations // struct with yaml tags for validation. type toleration struct { @@ -73,6 +83,9 @@ type Command struct { flagDeploymentMaxInstances int flagDeploymentMinInstances int + flagResourceConfigFileLocation string + flagGatewayConfigLocation string + flagNodeSelector string // this is a yaml multiline string map flagTolerations string // this is a multiline yaml string matching the tolerations array flagServiceAnnotations string // this is a multiline yaml string array of annotations to allow @@ -90,10 +103,15 @@ type Command struct { tolerations []corev1.Toleration serviceAnnotations []string resources corev1.ResourceRequirements + gatewayConfig gatewayConfig ctx context.Context } +type gatewayConfig struct { + GatewayClassConfigs []*v2beta1.GatewayClassConfig `yaml:"gatewayClassConfigs"` +} + func (c *Command) init() { c.flags = flag.NewFlagSet("", flag.ContinueOnError) @@ -142,6 +160,12 @@ func (c *Command) init() { "gateway container.", ) + c.flags.StringVar(&c.flagGatewayConfigLocation, "gateway-config-file-location", gatewayConfigFilename, + "specify a different location for where the gateway config file is") + + c.flags.StringVar(&c.flagResourceConfigFileLocation, "resource-config-file-location", resourceConfigFilename, + "specify a different location for where the gateway resource config file is") + c.k8s = &flags.K8SFlags{} flags.Merge(c.flags, c.k8s.Flags()) c.help = flags.Usage(help, c.flags) @@ -159,9 +183,15 @@ func (c *Command) Run(args []string) int { return 1 } - // Load config from the configmap. - if err := c.loadConfig(); err != nil { - c.UI.Error(fmt.Sprintf("Error loading config: %s", err)) + // Load apigw resource config from the configmap. + if c.resources, err = c.loadResourceConfig(c.flagResourceConfigFileLocation); err != nil { + c.UI.Error(fmt.Sprintf("Error loading api-gateway resource config: %s", err)) + return 1 + } + + // Load gateway config from the configmap. + if err := c.loadGatewayConfigs(); err != nil { + c.UI.Error(fmt.Sprintf("Error loading gateway config: %s", err)) return 1 } @@ -191,6 +221,16 @@ func (c *Command) Run(args []string) int { return 1 } + if err := authv2beta1.AddAuthToScheme(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add authv2beta schema: %s", err)) + return 1 + } + + if err := v2beta1.AddMeshToScheme(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add meshv2 schema: %s", err)) + return 1 + } + c.k8sClient, err = client.New(config, client.Options{Scheme: s}) if err != nil { c.UI.Error(fmt.Sprintf("Error initializing Kubernetes client: %s", err)) @@ -199,7 +239,6 @@ func (c *Command) Run(args []string) int { } // do the creation - labels := map[string]string{ "app": c.flagApp, "chart": c.flagChart, @@ -239,15 +278,23 @@ func (c *Command) Run(args []string) int { }, } - if err := forceClassConfig(context.Background(), c.k8sClient, classConfig); err != nil { + if err := forceV1ClassConfig(context.Background(), c.k8sClient, classConfig); err != nil { c.UI.Error(err.Error()) return 1 } - if err := forceClass(context.Background(), c.k8sClient, class); err != nil { + if err := forceV1Class(context.Background(), c.k8sClient, class); err != nil { c.UI.Error(err.Error()) return 1 } + if len(c.gatewayConfig.GatewayClassConfigs) > 0 { + err = c.createV2GatewayClassAndClassConfigs(context.Background(), "consul-mesh-gateway", "consul-mesh-gateway-controller") + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + } + return 0 } @@ -302,26 +349,55 @@ func (c *Command) validateFlags() error { return nil } -func (c *Command) loadConfig() error { +func (c *Command) loadResourceConfig(filename string) (corev1.ResourceRequirements, error) { // Load resources.json - file, err := os.Open("/consul/config/resources.json") + file, err := os.Open(filename) if err != nil { if !os.IsNotExist(err) { - return err + return corev1.ResourceRequirements{}, err } c.UI.Info("No resources.json found, using defaults") - c.resources = defaultResourceRequirements() - return nil + return defaultResourceRequirements, nil } resources, err := io.ReadAll(file) if err != nil { c.UI.Error(fmt.Sprintf("Unable to read resources.json, using defaults: %s", err)) - c.resources = defaultResourceRequirements() + return defaultResourceRequirements, err + } + + reqs := corev1.ResourceRequirements{} + if err := json.Unmarshal(resources, &reqs); err != nil { + return corev1.ResourceRequirements{}, err + } + + if err := file.Close(); err != nil { + return corev1.ResourceRequirements{}, err + } + return reqs, nil +} + +// loadGatewayConfigs reads and loads the configs from `/consul/config/config.yaml`, if this file does not exist nothing is done. +func (c *Command) loadGatewayConfigs() error { + file, err := os.Open(c.flagGatewayConfigLocation) + if err != nil { + if os.IsNotExist(err) { + c.UI.Warn(fmt.Sprintf("gateway configuration file not found, skipping gateway configuration, filename: %s", c.flagGatewayConfigLocation)) + return nil + } + c.UI.Error(fmt.Sprintf("Error opening gateway configuration file %s: %s", c.flagGatewayConfigLocation, err)) + return err + } + + config, err := io.ReadAll(file) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading gateway configuration file %s: %s", c.flagGatewayConfigLocation, err)) return err } - if err := json.Unmarshal(resources, &c.resources); err != nil { + err = k8syaml.Unmarshal(config, &c.gatewayConfig) + if err != nil { + c.UI.Error(fmt.Sprintf("Error decoding gateway config file: %s", err)) return err } @@ -331,14 +407,55 @@ func (c *Command) loadConfig() error { return nil } +// createV2GatewayClassAndClassConfigs utilizes the configuration loaded from the gateway config file to +// create the GatewayClassConfig and GatewayClass for the gateway. +func (c *Command) createV2GatewayClassAndClassConfigs(ctx context.Context, component, controllerName string) error { + labels := map[string]string{ + "app": c.flagApp, + "chart": c.flagChart, + "heritage": c.flagHeritage, + "release": c.flagRelease, + "component": component, + } + for _, cfg := range c.gatewayConfig.GatewayClassConfigs { + cfg.Kind = "GatewayClassConfig" + err := forceV2ClassConfig(ctx, c.k8sClient, cfg) + if err != nil { + return err + } + + class := &v2beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{Name: cfg.Name, Labels: labels}, + TypeMeta: metav1.TypeMeta{Kind: "GatewayClass"}, + Spec: meshv2beta1.GatewayClass{ + ControllerName: controllerName, + ParametersRef: &meshv2beta1.ParametersReference{ + Group: v2beta1.MeshGroup, + Kind: "GatewayClass", + Namespace: &cfg.Namespace, + Name: cfg.Name, + }, + }, + } + + err = forceV2Class(ctx, c.k8sClient, class) + if err != nil { + return err + } + } + + return nil +} + func (c *Command) Synopsis() string { return synopsis } func (c *Command) Help() string { c.once.Do(c.init) return c.help } -const synopsis = "Create managed gateway resources after installation/upgrade." -const help = ` +const ( + synopsis = "Create managed gateway resources after installation/upgrade." + help = ` Usage: consul-k8s-control-plane gateway-resources [options] Installs managed gateway class and configuration resources @@ -346,22 +463,20 @@ Usage: consul-k8s-control-plane gateway-resources [options] dependencies of CRDs being in-place prior to resource creation. ` +) -func defaultResourceRequirements() v1.ResourceRequirements { - // This is a fallback. The resource.json file should be present unless explicitly removed. - return v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("100Mi"), - v1.ResourceCPU: resource.MustParse("100m"), - }, - Limits: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("100Mi"), - v1.ResourceCPU: resource.MustParse("100m"), - }, - } +var defaultResourceRequirements = corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("100Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("100Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, } -func forceClassConfig(ctx context.Context, k8sClient client.Client, o *v1alpha1.GatewayClassConfig) error { +func forceV1ClassConfig(ctx context.Context, k8sClient client.Client, o *v1alpha1.GatewayClassConfig) error { return backoff.Retry(func() error { var existing v1alpha1.GatewayClassConfig err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) @@ -380,7 +495,7 @@ func forceClassConfig(ctx context.Context, k8sClient client.Client, o *v1alpha1. }, exponentialBackoffWithMaxIntervalAndTime()) } -func forceClass(ctx context.Context, k8sClient client.Client, o *gwv1beta1.GatewayClass) error { +func forceV1Class(ctx context.Context, k8sClient client.Client, o *gwv1beta1.GatewayClass) error { return backoff.Retry(func() error { var existing gwv1beta1.GatewayClass err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) @@ -399,6 +514,44 @@ func forceClass(ctx context.Context, k8sClient client.Client, o *gwv1beta1.Gatew }, exponentialBackoffWithMaxIntervalAndTime()) } +func forceV2ClassConfig(ctx context.Context, k8sClient client.Client, o *v2beta1.GatewayClassConfig) error { + return backoff.Retry(func() error { + var existing v2beta1.GatewayClassConfig + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } + + if k8serrors.IsNotFound(err) { + return k8sClient.Create(ctx, o) + } + + existing.Spec = *o.Spec.DeepCopy() + existing.Labels = o.Labels + + return k8sClient.Update(ctx, &existing) + }, exponentialBackoffWithMaxIntervalAndTime()) +} + +func forceV2Class(ctx context.Context, k8sClient client.Client, o *v2beta1.GatewayClass) error { + return backoff.Retry(func() error { + var existing v2beta1.GatewayClass + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } + + if k8serrors.IsNotFound(err) { + return k8sClient.Create(ctx, o) + } + + existing.Spec = *o.Spec.DeepCopy() + existing.Labels = o.Labels + + return k8sClient.Update(ctx, &existing) + }, exponentialBackoffWithMaxIntervalAndTime()) +} + func exponentialBackoffWithMaxIntervalAndTime() *backoff.ExponentialBackOff { backoff := backoff.NewExponentialBackOff() backoff.MaxElapsedTime = 10 * time.Second diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 8455c1a315..bc9e8c041d 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -4,16 +4,20 @@ package gatewayresources import ( + "os" "testing" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) @@ -193,18 +197,22 @@ func TestRun(t *testing.T) { for name, tt := range map[string]struct { existingGatewayClass bool existingGatewayClassConfig bool + meshGWConfigFileExists bool }{ "both exist": { existingGatewayClass: true, existingGatewayClassConfig: true, }, - "gateway class config doesn't exist": { + "api gateway class config doesn't exist": { existingGatewayClass: true, }, - "gateway class doesn't exist": { + "api gateway class doesn't exist": { existingGatewayClassConfig: true, }, "neither exist": {}, + "mesh gw config file exists": { + meshGWConfigFileExists: true, + }, } { t.Run(name, func(t *testing.T) { tt := tt @@ -222,6 +230,11 @@ func TestRun(t *testing.T) { require.NoError(t, gwv1beta1.Install(s)) require.NoError(t, v1alpha1.AddToScheme(s)) + configFileName := gatewayConfigFilename + if tt.meshGWConfigFileExists { + configFileName = createGatewayConfigFile(t, validGWConfiguration, "config.yaml") + } + objs := []client.Object{} if tt.existingGatewayClass { objs = append(objs, existingGatewayClass) @@ -234,8 +247,9 @@ func TestRun(t *testing.T) { ui := cli.NewMockUi() cmd := Command{ - UI: ui, - k8sClient: client, + UI: ui, + k8sClient: client, + flagGatewayConfigLocation: configFileName, } code := cmd.Run([]string{ @@ -254,3 +268,250 @@ func TestRun(t *testing.T) { }) } } + +var validResourceConfiguration = `{ + "requests": { + "memory": "200Mi", + "cpu": "200m" + }, + "limits": { + "memory": "200Mi", + "cpu": "200m" + } +} +` + +var invalidResourceConfiguration = `{"resources": +{ + "memory": "100Mi" + "cpu": "100m" + }, + "limits": { + "memory": "100Mi" + "cpu": "100m" + }, +} +` + +func TestRun_loadResourceConfig(t *testing.T) { + filename := createGatewayConfigFile(t, validResourceConfiguration, "resource.json") + // setup k8s client + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + } + + expectedResources := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("200m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("200m"), + }, + } + + resources, err := cmd.loadResourceConfig(filename) + require.NoError(t, err) + require.Equal(t, resources, expectedResources) +} + +func TestRun_loadResourceConfigInvalidConfigFile(t *testing.T) { + filename := createGatewayConfigFile(t, invalidResourceConfiguration, "resource.json") + // setup k8s client + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + } + + _, err := cmd.loadResourceConfig(filename) + require.Error(t, err) +} + +func TestRun_loadResourceConfigFileWhenConfigFileDoesNotExist(t *testing.T) { + filename := "./consul/config/resources.json" + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + } + + resources, err := cmd.loadResourceConfig(filename) + require.NoError(t, err) + require.Equal(t, resources, defaultResourceRequirements) // should be using defaults + require.Contains(t, string(ui.OutputWriter.Bytes()), "No resources.json found, using defaults") +} + +var validGWConfiguration = ` +gatewayClassConfigs: +- apiVersion: mesh.consul.hashicorp.com/v2beta1 + kind: gatewayClassConfig + metadata: + name: consul-mesh-gateway + namespace: namespace + spec: + deployment: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + cpu: 200m + memory: 200Mi +meshGateways: +- name: mesh-gateway + spec: + gatewayClassName: consul-mesh-gateway +` + +var invalidGWConfiguration = ` +gatewayClassConfigs: +iVersion= mesh.consul.hashicorp.com/v2beta1 + kind: gatewayClassConfig + metadata: + name: consul-mesh-gateway + namespace: namespace + spec: + deployment: + resources: + requests: + cpu: 100m +meshGateways: +- name: mesh-gateway + spec: + gatewayClassName: consul-mesh-gateway +` + +func TestRun_loadGatewayConfigs(t *testing.T) { + expectedResources := &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("200m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("200m"), + }, + } + filename := createGatewayConfigFile(t, validGWConfiguration, "config.yaml") + // setup k8s client + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + flagGatewayConfigLocation: filename, + } + + err := cmd.loadGatewayConfigs() + require.NoError(t, err) + require.NotEmpty(t, cmd.gatewayConfig.GatewayClassConfigs) + + // we only created one class config + classConfig := cmd.gatewayConfig.GatewayClassConfigs[0].DeepCopy() + + expectedClassConfig := v2beta1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "mesh.consul.hashicorp.com/v2beta1", + Kind: "gatewayClassConfig", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-mesh-gateway", + Namespace: "namespace", + }, + Spec: v2beta1.GatewayClassConfigSpec{ + DeploymentSpec: v2beta1.DeploymentSpec{ + Resources: expectedResources, + }, + }, + Status: v2beta1.Status{}, + } + require.Equal(t, expectedClassConfig.DeepCopy(), classConfig) +} + +func TestRun_loadGatewayConfigsWithInvalidFile(t *testing.T) { + filename := createGatewayConfigFile(t, invalidGWConfiguration, "config.yaml") + // setup k8s client + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + flagGatewayConfigLocation: filename, + } + + err := cmd.loadGatewayConfigs() + require.Error(t, err) + require.Empty(t, cmd.gatewayConfig.GatewayClassConfigs) +} + +func TestRun_loadGatewayConfigsWhenConfigFileDoesNotExist(t *testing.T) { + filename := "./consul/config/config.yaml" + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + flagGatewayConfigLocation: filename, + } + + err := cmd.loadGatewayConfigs() + require.NoError(t, err) + require.Empty(t, cmd.gatewayConfig.GatewayClassConfigs) + require.Contains(t, string(ui.ErrorWriter.Bytes()), "gateway configuration file not found, skipping gateway configuration") +} + +func createGatewayConfigFile(t *testing.T, fileContent, filename string) string { + t.Helper() + + // create a temp file to store configuration yaml + tmpdir := t.TempDir() + file, err := os.CreateTemp(tmpdir, filename) + if err != nil { + t.Fatal(err) + } + defer file.Close() + + _, err = file.WriteString(fileContent) + if err != nil { + t.Fatal(err) + } + + return file.Name() +} From 278509109e664014280bba062754c87c7d2e4f56 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:12:53 -0600 Subject: [PATCH 500/592] Net 6392- Create MeshGateway Deployment (#3290) * checkpoint * checkpoint, deployment spec intial skeleton * set up reconcile * checkpoint * checkpoint * checkpoint * working deployment * cleaning up todos, working deployment * cleaned up todos, changed namespaces back from hardcoded default * unit test finished * fix pointer added in rebase * Update control-plane/config-entries/controllersv2/mesh_gateway_controller.go * additional cleanup/linting issues * rename files, clean up configuration to reuse tenacy config * import grouping * responding to code review * gofmt * Update control-plane/config-entries/controllersv2/mesh_gateway_controller.go Co-authored-by: Nathan Coleman * clean up incorrect comment * add gcc nil test to cover potential nil use cases, add log statment for gcc fetch error * clean up nit picks * checkpoint * fix typing * Update control-plane/gateways/config.go Co-authored-by: Nathan Coleman * Update control-plane/gateways/deployment_init_container.go Co-authored-by: Nathan Coleman * Update control-plane/gateways/deployment_init_container.go Co-authored-by: Nathan Coleman * Update control-plane/config-entries/controllersv2/mesh_gateway_controller.go Co-authored-by: Nathan Coleman * Add rudimentary Deployment assertions to mesh_gateway_controller_test.go * Fix command assertion whitespace * Use full GCC instead of GCCSpec so we have future access to annotations --------- Co-authored-by: Nathan Coleman --- control-plane/api/common/common.go | 13 +- .../controllersv2/mesh_gateway_controller.go | 102 +++- .../mesh_gateway_controller_test.go | 92 ++- .../connect-inject/constants/constants.go | 3 + control-plane/gateways/builder.go | 10 +- control-plane/gateways/config.go | 37 ++ control-plane/gateways/deployment.go | 149 +++++ .../deployment_dataplane_container.go | 171 ++++++ .../gateways/deployment_init_container.go | 169 ++++++ control-plane/gateways/deployment_test.go | 536 ++++++++++++++++++ control-plane/gateways/serviceaccount.go | 6 +- control-plane/gateways/serviceaccount_test.go | 2 +- control-plane/go.mod | 2 +- .../inject-connect/v2controllers.go | 21 +- 14 files changed, 1295 insertions(+), 18 deletions(-) create mode 100644 control-plane/gateways/config.go create mode 100644 control-plane/gateways/deployment.go create mode 100644 control-plane/gateways/deployment_dataplane_container.go create mode 100644 control-plane/gateways/deployment_init_container.go create mode 100644 control-plane/gateways/deployment_test.go diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index e74d5682ac..75343ae5f9 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -4,7 +4,10 @@ // Package common holds code that isn't tied to a particular CRD version or type. package common -import mapset "github.com/deckarep/golang-set" +import ( + mapset "github.com/deckarep/golang-set" + "time" +) const ( // V1 config entries. @@ -79,3 +82,11 @@ type K8sNamespaceConfig struct { // Endpoints in the DenyK8sNamespacesSet are ignored. DenyK8sNamespacesSet mapset.Set } + +// ConsulConfig manages config to tell a pod where consul is located. +type ConsulConfig struct { + Address string + GRPCPort int + HTTPPort int + APITimeout time.Duration +} diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index 3889e7a20b..00de015931 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -6,8 +6,10 @@ package controllersv2 import ( "context" "errors" + "fmt" "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -16,16 +18,23 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/gateways" ) +// errResourceNotOwned indicates that a resource the controller would have +// updated or deleted does not have an owner reference pointing to the MeshGateway. +var errResourceNotOwned = errors.New("existing resource not owned by controller") + // MeshGatewayController reconciles a MeshGateway object. type MeshGatewayController struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController + GatewayConfig gateways.GatewayConfig } // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshgateway,verbs=get;list;watch;create;update;patch;delete @@ -83,19 +92,54 @@ func (r *MeshGatewayController) SetupWithManager(mgr ctrl.Manager) error { // 4. Role // 5. RoleBinding func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { - builder := gateways.NewMeshGatewayBuilder(resource) + //fetch gatewayclassconfig + gcc, err := r.getGatewayClassConfigForGateway(ctx, resource) + if err != nil { + r.Log.Error(err, "unable to get gatewayclassconfig for gateway: %s gatewayclass: %s", resource.Name, resource.Spec.GatewayClassName) + return err + } + + builder := gateways.NewMeshGatewayBuilder(resource, r.GatewayConfig, gcc) upsertOp := func(ctx context.Context, _, object client.Object) error { _, err := controllerutil.CreateOrUpdate(ctx, r.Client, object, func() error { return nil }) return err } - err := r.opIfNewOrOwned(ctx, resource, &corev1.ServiceAccount{}, builder.ServiceAccount(), upsertOp) + err = r.opIfNewOrOwned(ctx, resource, &corev1.ServiceAccount{}, builder.ServiceAccount(), upsertOp) if err != nil { + return fmt.Errorf("unable to create service account: %w", err) + } + + // TODO NET-6393 NET-6395 + + //Create deployment + + mergeDeploymentOp := func(ctx context.Context, existingObject, object client.Object) error { + existingDeployment, ok := existingObject.(*appsv1.Deployment) + if !ok && existingDeployment != nil { + return fmt.Errorf("unable to infer existing deployment type") + } + builtDeployment, ok := object.(*appsv1.Deployment) + if !ok { + return fmt.Errorf("unable to infer built deployment type") + } + + mergedDeployment := builder.MergeDeployments(gcc, existingDeployment, builtDeployment) + + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, mergedDeployment, func() error { return nil }) return err } - // TODO NET-6392 NET-6393 NET-6395 + builtDeployment, err := builder.Deployment() + if err != nil { + return fmt.Errorf("Unable to build deployment: %w", err) + } + + err = r.opIfNewOrOwned(ctx, resource, &appsv1.Deployment{}, builtDeployment, mergeDeploymentOp) + if err != nil { + return fmt.Errorf("Unable to create deployment: %w", err) + } return nil } @@ -122,6 +166,7 @@ type ownedObjectOp func(ctx context.Context, existingObject client.Object, newOb // The purpose of opIfNewOrOwned is to ensure that we aren't updating or deleting a // resource that was not created by us. If this scenario is encountered, we error. func (r *MeshGatewayController) opIfNewOrOwned(ctx context.Context, gateway *meshv2beta1.MeshGateway, scanTarget, writeSource client.Object, op ownedObjectOp) error { + // Ensure owner reference is always set on objects that we write if err := ctrl.SetControllerReference(gateway, writeSource, r.Client.Scheme()); err != nil { return err @@ -157,7 +202,50 @@ func (r *MeshGatewayController) opIfNewOrOwned(ctx context.Context, gateway *mes } } if !owned { - return errors.New("existing resource not owned by controller") + return errResourceNotOwned } return op(ctx, scanTarget, writeSource) } + +func (r *MeshGatewayController) getGatewayClassConfigForGateway(ctx context.Context, gateway *meshv2beta1.MeshGateway) (*meshv2beta1.GatewayClassConfig, error) { + gatewayClass, err := r.getGatewayClassForGateway(ctx, gateway) + if err != nil { + return nil, err + } + gatewayClassConfig, err := r.getConfigForGatewayClass(ctx, gatewayClass) + if err != nil { + return nil, err + } + + return gatewayClassConfig, nil + +} + +func (r *MeshGatewayController) getConfigForGatewayClass(ctx context.Context, gatewayClassConfig *meshv2beta1.GatewayClass) (*meshv2beta1.GatewayClassConfig, error) { + if gatewayClassConfig == nil { + // if we don't have a gateway class we can't fetch the corresponding config + return nil, nil + } + + config := &meshv2beta1.GatewayClassConfig{} + if ref := gatewayClassConfig.Spec.ParametersRef; ref != nil { + if ref.Group != meshv2beta1.MeshGroup || + ref.Kind != common.GatewayClassConfig { + //TODO @Gateway-Management additionally check for controller name when available + return nil, nil + } + + if err := r.Client.Get(ctx, types.NamespacedName{Name: ref.Name}, config); err != nil { + return nil, client.IgnoreNotFound(err) + } + } + return config, nil +} + +func (r *MeshGatewayController) getGatewayClassForGateway(ctx context.Context, gateway *meshv2beta1.MeshGateway) (*meshv2beta1.GatewayClass, error) { + var gatewayClass meshv2beta1.GatewayClass + if err := r.Client.Get(ctx, types.NamespacedName{Name: string(gateway.Spec.GatewayClassName)}, &gatewayClass); err != nil { + return nil, client.IgnoreNotFound(err) + } + return &gatewayClass, nil +} diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index c3fae8126d..41e58c2f13 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -5,13 +5,13 @@ package controllersv2 import ( "context" - "errors" "testing" logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -40,6 +40,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { // postReconcile runs some set of assertions on the state of k8s after Reconcile is called postReconcile func(*testing.T, client.Client) }{ + // ServiceAccount { name: "MeshGateway created with no existing ServiceAccount", k8sObjects: []runtime.Object{ @@ -86,7 +87,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { }, }, expectedResult: ctrl.Result{}, - expectedErr: errors.New("existing resource not owned by controller"), + expectedErr: errResourceNotOwned, }, { name: "MeshGateway created with existing ServiceAccount owned by gateway", @@ -120,6 +121,87 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { expectedResult: ctrl.Result{}, expectedErr: nil, // The Reconcile should be a no-op }, + // Deployment + { + name: "MeshGateway created with no existing Deployment", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "consul", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "consul", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + postReconcile: func(t *testing.T, c client.Client) { + // Verify Deployment was created + key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} + assert.NoError(t, c.Get(context.Background(), key, &appsv1.Deployment{})) + }, + }, + { + name: "MeshGateway created with existing Deployment not owned by gateway", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + expectedErr: errResourceNotOwned, + }, + { + name: "MeshGateway created with existing Deployment owned by gateway", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + UID: "abc123", + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + OwnerReferences: []metav1.OwnerReference{ + { + UID: "abc123", + Name: "mesh-gateway", + }, + }, + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + expectedErr: nil, // The Reconcile should be a no-op + }, } for _, testCase := range testCases { @@ -130,7 +212,8 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { s := runtime.NewScheme() require.NoError(t, corev1.AddToScheme(s)) - s.AddKnownTypes(v2beta1.MeshGroupVersion, &v2beta1.MeshGateway{}) + require.NoError(t, appsv1.AddToScheme(s)) + s.AddKnownTypes(v2beta1.MeshGroupVersion, &v2beta1.MeshGateway{}, &v2beta1.GatewayClass{}, &v2beta1.GatewayClassConfig{}) fakeClient := fake.NewClientBuilder().WithScheme(s). WithRuntimeObjects(testCase.k8sObjects...). Build() @@ -147,7 +230,8 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { res, err := controller.Reconcile(context.Background(), testCase.request) if testCase.expectedErr != nil { - require.EqualError(t, err, testCase.expectedErr.Error()) + //require.EqualError(t, err, testCase.expectedErr.Error()) + require.ErrorIs(t, err, testCase.expectedErr) } else { require.NoError(t, err) } diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index dd7cb4d243..ac846b36cb 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -58,6 +58,9 @@ const ( // DefaultGracefulShutdownPath is the default path that consul-dataplane uses for graceful shutdown. DefaultGracefulShutdownPath = "/graceful_shutdown" + //DefaultWANPort is the default port that consul-dataplane uses for WAN. + DefaultWANPort = 8443 + // ConsulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. ConsulKubernetesCheckType = "kubernetes-readiness" diff --git a/control-plane/gateways/builder.go b/control-plane/gateways/builder.go index 748a981ed1..53dcbe03ec 100644 --- a/control-plane/gateways/builder.go +++ b/control-plane/gateways/builder.go @@ -3,14 +3,20 @@ package gateways -import meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +import ( + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) type meshGatewayBuilder struct { gateway *meshv2beta1.MeshGateway + config GatewayConfig + gcc *meshv2beta1.GatewayClassConfig } -func NewMeshGatewayBuilder(gateway *meshv2beta1.MeshGateway) *meshGatewayBuilder { +func NewMeshGatewayBuilder(gateway *meshv2beta1.MeshGateway, gatewayConfig GatewayConfig, gatewayClassConfig *meshv2beta1.GatewayClassConfig) *meshGatewayBuilder { return &meshGatewayBuilder{ gateway: gateway, + config: gatewayConfig, + gcc: gatewayClassConfig, } } diff --git a/control-plane/gateways/config.go b/control-plane/gateways/config.go new file mode 100644 index 0000000000..effd5ea946 --- /dev/null +++ b/control-plane/gateways/config.go @@ -0,0 +1,37 @@ +package gateways + +import "github.com/hashicorp/consul-k8s/control-plane/api/common" + +// GatewayConfig is a combination of settings relevant to Gateways. +type GatewayConfig struct { + // ImageDataplane is the Consul Dataplane image to use in gateway deployments. + ImageDataplane string + // ImageConsulK8S is the Consul Kubernetes Control Plane image to use in gateway deployments. + ImageConsulK8S string + // AuthMethod method used to authenticate with Consul Server. + AuthMethod string + + ConsulTenancyConfig common.ConsulTenancyConfig + + // LogLevel is the logging level of the deployed Consul Dataplanes. + LogLevel string + // LogJSON if JSONLogging has been enabled. + LogJSON bool + // TLSEnabled is the value of whether or not TLS has been enabled in Consul. + TLSEnabled bool + // PeeringEnabled toggles whether or not Peering is enabled in Consul. + PeeringEnabled bool + // ConsulTLSServerName the name of the server running TLS. + ConsulTLSServerName string + // ConsulCACert contains the Consul Certificate Authority. + ConsulCACert string + // ConsulConfig configuration for the consul server address. + ConsulConfig common.ConsulConfig + + // EnableOpenShift indicates whether we're deploying into an OpenShift environment + EnableOpenShift bool + + // MapPrivilegedServicePorts is the value which Consul will add to privileged container port values (ports < 1024) + // defined on a Gateway. + MapPrivilegedServicePorts int +} diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go new file mode 100644 index 0000000000..6da5930b66 --- /dev/null +++ b/control-plane/gateways/deployment.go @@ -0,0 +1,149 @@ +package gateways + +import ( + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +const ( + globalDefaultInstances int32 = 1 +) + +func (b *meshGatewayBuilder) Deployment() (*appsv1.Deployment, error) { + spec, err := b.deploymentSpec() + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + Labels: b.Labels(), + }, + Spec: *spec, + }, err +} + +func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { + initContainer, err := initContainer(b.config, b.gateway.Name, b.gateway.Namespace) + if err != nil { + return nil, err + } + + var resources *corev1.ResourceRequirements + if b.gcc != nil { + resources = b.gcc.Spec.DeploymentSpec.Resources + } + + container, err := consulDataplaneContainer(b.config, resources, b.gateway.Name, b.gateway.Namespace) + if err != nil { + return nil, err + } + + return &appsv1.DeploymentSpec{ + //TODO NET-6721 + Replicas: deploymentReplicaCount(nil, nil), + Selector: &metav1.LabelSelector{ + MatchLabels: b.Labels(), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: b.Labels(), + Annotations: map[string]string{ + "consul.hashicorp.com/mesh-inject": "false", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, + }, + }, + }, + InitContainers: []corev1.Container{ + initContainer, + }, + Containers: []corev1.Container{ + container, + }, + Affinity: &corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 1, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: b.Labels(), + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, + }, + NodeSelector: nil, + Tolerations: nil, + ServiceAccountName: b.serviceAccountName(), + }, + }, + }, nil +} + +func (b *meshGatewayBuilder) MergeDeployments(gcc *meshv2beta1.GatewayClassConfig, old, new *appsv1.Deployment) *appsv1.Deployment { + if old == nil { + return new + } + if !compareDeployments(old, new) { + old.Spec.Template = new.Spec.Template + new.Spec.Replicas = deploymentReplicaCount(nil, old.Spec.Replicas) + } + + return new +} + +func compareDeployments(a, b *appsv1.Deployment) bool { + // since K8s adds a bunch of defaults when we create a deployment, check that + // they don't differ by the things that we may actually change, namely container + // ports + if len(b.Spec.Template.Spec.Containers) != len(a.Spec.Template.Spec.Containers) { + return false + } + for i, container := range a.Spec.Template.Spec.Containers { + otherPorts := b.Spec.Template.Spec.Containers[i].Ports + if len(container.Ports) != len(otherPorts) { + return false + } + for j, port := range container.Ports { + otherPort := otherPorts[j] + if port.ContainerPort != otherPort.ContainerPort { + return false + } + if port.Protocol != otherPort.Protocol { + return false + } + } + } + + if b.Spec.Replicas == nil && a.Spec.Replicas == nil { + return true + } else if b.Spec.Replicas == nil { + return false + } else if a.Spec.Replicas == nil { + return false + } + + return *b.Spec.Replicas == *a.Spec.Replicas +} + +func deploymentReplicaCount(deployment *pbmesh.Deployment, currentReplicas *int32) *int32 { + //TODO NET-6721 tamp replica count up and down based on min and max values + instanceValue := globalDefaultInstances + if currentReplicas != nil { + return currentReplicas + } + return pointer.Int32(instanceValue) +} diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go new file mode 100644 index 0000000000..dd08a56ed7 --- /dev/null +++ b/control-plane/gateways/deployment_dataplane_container.go @@ -0,0 +1,171 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gateways + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" + + corev1 "k8s.io/api/core/v1" +) + +const ( + allCapabilities = "ALL" + netBindCapability = "NET_BIND_SERVICE" + consulDataplaneDNSBindHost = "127.0.0.1" + consulDataplaneDNSBindPort = 8600 + defaultPrometheusScrapePath = "/metrics" + defaultEnvoyProxyConcurrency = "1" + volumeName = "consul-connect-inject-data" +) + +func consulDataplaneContainer(config GatewayConfig, resources *corev1.ResourceRequirements, name, namespace string) (corev1.Container, error) { + // Extract the service account token's volume mount. + var ( + err error + bearerTokenFile string + ) + + if config.AuthMethod != "" { + bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" + } + + args, err := getDataplaneArgs(namespace, config, bearerTokenFile, name) + if err != nil { + return corev1.Container{}, err + } + + probe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(constants.ProxyDefaultHealthPort), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + } + + container := corev1.Container{ + Name: name, + Image: config.ImageDataplane, + + // We need to set tmp dir to an ephemeral volume that we're mounting so that + // consul-dataplane can write files to it. Otherwise, it wouldn't be able to + // because we set file system to be read-only. + + // TODO(nathancoleman): I don't believe consul-dataplane needs to write anymore, investigate. + Env: []corev1.EnvVar{ + { + Name: "TMPDIR", + Value: "/consul/connect-inject", + }, + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "DP_SERVICE_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/consul/connect-inject", + }, + }, + Args: args, + ReadinessProbe: probe, + } + + // Configure the Readiness Address for the proxy's health check to be the Pod IP. + container.Env = append(container.Env, corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + }) + // Configure the port on which the readiness probe will query the proxy for its health. + container.Ports = append(container.Ports, corev1.ContainerPort{ + Name: "proxy-health", + ContainerPort: int32(constants.ProxyDefaultHealthPort), + }) + + // Configure the wan port. + container.Ports = append(container.Ports, corev1.ContainerPort{ + Name: "wan", + ContainerPort: int32(constants.DefaultWANPort), + }) + + // Configure the resource requests and limits for the proxy if they are set. + if resources != nil { + container.Resources = *resources + } + + container.SecurityContext = &corev1.SecurityContext{ + AllowPrivilegeEscalation: pointer.Bool(false), + // Drop any Linux capabilities you'd get other than NET_BIND_SERVICE. + // FUTURE: We likely require some additional capability in order to support + // MeshGateway's host network option. + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{netBindCapability}, + Drop: []corev1.Capability{allCapabilities}, + }, + ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsNonRoot: pointer.Bool(true), + } + + return container, nil +} + +func getDataplaneArgs(namespace string, config GatewayConfig, bearerTokenFile string, name string) ([]string, error) { + proxyIDFileName := "/consul/connect-inject/proxyid" + + args := []string{ + "-addresses", config.ConsulConfig.Address, + "-grpc-port=" + strconv.Itoa(config.ConsulConfig.GRPCPort), + "-proxy-service-id-path=" + proxyIDFileName, + "-log-level=" + config.LogLevel, + "-log-json=" + strconv.FormatBool(config.LogJSON), + "-envoy-concurrency=" + defaultEnvoyProxyConcurrency, + } + + consulNamespace := namespaces.ConsulNamespace(namespace, config.ConsulTenancyConfig.EnableConsulNamespaces, config.ConsulTenancyConfig.ConsulDestinationNamespace, config.ConsulTenancyConfig.EnableConsulNamespaces, config.ConsulTenancyConfig.NSMirroringPrefix) + + if config.AuthMethod != "" { + args = append(args, + "-credential-type=login", + "-login-auth-method="+config.AuthMethod, + "-login-bearer-token-path="+bearerTokenFile, + "-login-meta="+fmt.Sprintf("gateway=%s/%s", namespace, name), + ) + if config.ConsulTenancyConfig.ConsulPartition != "" { + args = append(args, "-login-partition="+config.ConsulTenancyConfig.ConsulPartition) + } + } + if config.ConsulTenancyConfig.EnableConsulNamespaces { + args = append(args, "-service-namespace="+consulNamespace) + } + if config.ConsulTenancyConfig.ConsulPartition != "" { + args = append(args, "-service-partition="+config.ConsulTenancyConfig.ConsulPartition) + } + + args = append(args, "-tls-disabled") + + // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. + args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) + + args = append(args, fmt.Sprintf("-envoy-admin-bind-port=%d", 19000)) + + return args, nil +} diff --git a/control-plane/gateways/deployment_init_container.go b/control-plane/gateways/deployment_init_container.go new file mode 100644 index 0000000000..9e66a7ee22 --- /dev/null +++ b/control-plane/gateways/deployment_init_container.go @@ -0,0 +1,169 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gateways + +import ( + "bytes" + "strconv" + "strings" + "text/template" + + corev1 "k8s.io/api/core/v1" + + "github.com/hashicorp/consul-k8s/control-plane/namespaces" +) + +const ( + injectInitContainerName = "consul-connect-inject-init" + initContainersUserAndGroupID = 5996 +) + +type initContainerCommandData struct { + ServiceName string + ServiceAccountName string + AuthMethod string + + // Log settings for the connect-init command. + LogLevel string + LogJSON bool +} + +// containerInit 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 initContainer(config GatewayConfig, name, namespace string) (corev1.Container, error) { + data := initContainerCommandData{ + AuthMethod: config.AuthMethod, + LogLevel: config.LogLevel, + LogJSON: config.LogJSON, + ServiceName: name, + ServiceAccountName: name, + } + + // Create expected volume mounts + volMounts := []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/consul/connect-inject", + }, + } + + var bearerTokenFile string + if config.AuthMethod != "" { + bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" + } + + // Render the command + var buf bytes.Buffer + tpl := template.Must(template.New("root").Parse(strings.TrimSpace(initContainerCommandTpl))) + + if err := tpl.Execute(&buf, &data); err != nil { + return corev1.Container{}, err + } + + consulNamespace := namespaces.ConsulNamespace(namespace, config.ConsulTenancyConfig.EnableConsulNamespaces, config.ConsulTenancyConfig.ConsulDestinationNamespace, config.ConsulTenancyConfig.EnableConsulNamespaces, config.ConsulTenancyConfig.NSMirroringPrefix) + + initContainerName := injectInitContainerName + container := corev1.Container{ + Name: initContainerName, + Image: config.ImageConsulK8S, + + Env: []corev1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "CONSUL_ADDRESSES", + Value: config.ConsulConfig.Address, + }, + { + Name: "CONSUL_GRPC_PORT", + Value: strconv.Itoa(config.ConsulConfig.GRPCPort), + }, + { + Name: "CONSUL_HTTP_PORT", + Value: strconv.Itoa(config.ConsulConfig.HTTPPort), + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: config.ConsulConfig.APITimeout.String(), + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + }, + VolumeMounts: volMounts, + Command: []string{"/bin/sh", "-ec", buf.String()}, + } + + if config.AuthMethod != "" { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_AUTH_METHOD", + Value: config.AuthMethod, + }, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", + Value: bearerTokenFile, + }, + corev1.EnvVar{ + Name: "CONSUL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", + }) + + if config.ConsulTenancyConfig.ConsulPartition != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "CONSUL_LOGIN_PARTITION", + Value: config.ConsulTenancyConfig.ConsulPartition, + }) + } + } + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_NAMESPACE", + Value: consulNamespace, + }) + + if config.ConsulTenancyConfig.ConsulPartition != "" { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: "CONSUL_PARTITION", + Value: config.ConsulTenancyConfig.ConsulPartition, + }) + } + + return container, nil +} + +// initContainerCommandTpl is the template for the command executed by +// the init container. +// TODO @GatewayManagement parametrize gateway kind. +const initContainerCommandTpl = ` +consul-k8s-control-plane connect-init \ + -pod-name=${POD_NAME} \ + -pod-namespace=${POD_NAMESPACE} \ + -gateway-kind="mesh-gateway" \ + -log-json={{ .LogJSON }} \ + {{- if .AuthMethod }} + -service-account-name="{{ .ServiceAccountName }}" \ + {{- end }} + -service-name="{{ .ServiceName }}" +` diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go new file mode 100644 index 0000000000..d04d2701a9 --- /dev/null +++ b/control-plane/gateways/deployment_test.go @@ -0,0 +1,536 @@ +package gateways + +import ( + "testing" + + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +func Test_meshGatewayBuilder_Deployment(t *testing.T) { + type fields struct { + gateway *meshv2beta1.MeshGateway + config GatewayConfig + gcc *meshv2beta1.GatewayClassConfig + } + tests := []struct { + name string + fields fields + want *appsv1.Deployment + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + gateway: &meshv2beta1.MeshGateway{ + Spec: pbmesh.MeshGateway{ + GatewayClassName: "test-gateway-class", + }, + }, + config: GatewayConfig{}, + gcc: &meshv2beta1.GatewayClassConfig{ + Spec: meshv2beta1.GatewayClassConfigSpec{ + DeploymentSpec: meshv2beta1.DeploymentSpec{ + DefaultInstances: pointer.Int32(1), + MinInstances: pointer.Int32(1), + MaxInstances: pointer.Int32(8), + }, + }, + }, + }, + want: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: pointer.Int32(1), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + Annotations: map[string]string{ + "consul.hashicorp.com/mesh-inject": "false", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "consul-connect-inject-data", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: "consul-connect-inject-init", + Command: []string{ + "/bin/sh", + "-ec", + "consul-k8s-control-plane connect-init \\\n -pod-name=${POD_NAME} \\\n -pod-namespace=${POD_NAMESPACE} \\\n -gateway-kind=\"mesh-gateway\" \\\n -log-json=false \\\n -service-name=\"\"", + }, + Env: []corev1.EnvVar{ + { + Name: "POD_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "NODE_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "CONSUL_ADDRESSES", + Value: "", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "0", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "0", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "0s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "", + }, + }, + Resources: corev1.ResourceRequirements{}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "consul-connect-inject-data", + ReadOnly: false, + MountPath: "/consul/connect-inject", + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Args: []string{ + "-addresses", + "", + "-grpc-port=0", + "-proxy-service-id-path=/consul/connect-inject/proxyid", + "-log-level=", + "-log-json=false", + "-envoy-concurrency=1", + "-tls-disabled", + "-envoy-ready-bind-port=21000", + "-envoy-admin-bind-port=19000", + }, + Ports: []corev1.ContainerPort{ + { + Name: "proxy-health", + ContainerPort: 21000, + }, + { + Name: "wan", + ContainerPort: 8443, + }, + }, + Env: []corev1.EnvVar{ + { + Name: "TMPDIR", + Value: "/consul/connect-inject", + }, + { + Name: "NODE_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "DP_SERVICE_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "DP_ENVOY_READY_BIND_ADDRESS", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "status.podIP", + }, + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "consul-connect-inject-data", + MountPath: "/consul/connect-inject", + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/ready", + Port: intstr.IntOrString{ + Type: 0, + IntVal: 21000, + StrVal: "", + }, + }, + }, + InitialDelaySeconds: 1, + }, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "NET_BIND_SERVICE", + }, + Drop: []corev1.Capability{ + "ALL", + }, + }, + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), + ProcMount: nil, + SeccompProfile: nil, + }, + Stdin: false, + StdinOnce: false, + TTY: false, + }, + }, + Affinity: &corev1.Affinity{ + NodeAffinity: nil, + PodAffinity: nil, + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 1, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, + }, + }, + }, + Strategy: appsv1.DeploymentStrategy{}, + MinReadySeconds: 0, + RevisionHistoryLimit: nil, + Paused: false, + ProgressDeadlineSeconds: nil, + }, + Status: appsv1.DeploymentStatus{}, + }, + wantErr: false, + }, + { + name: "nil gatewayclassconfig - (notfound)", + fields: fields{ + gateway: &meshv2beta1.MeshGateway{ + Spec: pbmesh.MeshGateway{ + GatewayClassName: "test-gateway-class", + }, + }, + config: GatewayConfig{}, + gcc: nil, + }, + want: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: pointer.Int32(1), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + Annotations: map[string]string{ + "consul.hashicorp.com/mesh-inject": "false", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "consul-connect-inject-data", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: "consul-connect-inject-init", + Command: []string{ + "/bin/sh", + "-ec", + "consul-k8s-control-plane connect-init \\\n -pod-name=${POD_NAME} \\\n -pod-namespace=${POD_NAMESPACE} \\\n -gateway-kind=\"mesh-gateway\" \\\n -log-json=false \\\n -service-name=\"\"", + }, + Env: []corev1.EnvVar{ + { + Name: "POD_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "NODE_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "CONSUL_ADDRESSES", + Value: "", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "0", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "0", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "0s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "", + }, + }, + Resources: corev1.ResourceRequirements{}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "consul-connect-inject-data", + ReadOnly: false, + MountPath: "/consul/connect-inject", + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Args: []string{ + "-addresses", + "", + "-grpc-port=0", + "-proxy-service-id-path=/consul/connect-inject/proxyid", + "-log-level=", + "-log-json=false", + "-envoy-concurrency=1", + "-tls-disabled", + "-envoy-ready-bind-port=21000", + "-envoy-admin-bind-port=19000", + }, + Ports: []corev1.ContainerPort{ + { + Name: "proxy-health", + ContainerPort: 21000, + }, + { + Name: "wan", + ContainerPort: 8443, + }, + }, + Env: []corev1.EnvVar{ + { + Name: "TMPDIR", + Value: "/consul/connect-inject", + }, + { + Name: "NODE_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "DP_SERVICE_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "DP_ENVOY_READY_BIND_ADDRESS", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "status.podIP", + }, + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "consul-connect-inject-data", + MountPath: "/consul/connect-inject", + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/ready", + Port: intstr.IntOrString{ + Type: 0, + IntVal: 21000, + StrVal: "", + }, + }, + }, + InitialDelaySeconds: 1, + }, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "NET_BIND_SERVICE", + }, + Drop: []corev1.Capability{ + "ALL", + }, + }, + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), + ProcMount: nil, + SeccompProfile: nil, + }, + Stdin: false, + StdinOnce: false, + TTY: false, + }, + }, + Affinity: &corev1.Affinity{ + NodeAffinity: nil, + PodAffinity: nil, + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 1, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, + }, + }, + }, + Strategy: appsv1.DeploymentStrategy{}, + MinReadySeconds: 0, + RevisionHistoryLimit: nil, + Paused: false, + ProgressDeadlineSeconds: nil, + }, + Status: appsv1.DeploymentStatus{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &meshGatewayBuilder{ + gateway: tt.fields.gateway, + config: tt.fields.config, + gcc: tt.fields.gcc, + } + got, err := b.Deployment() + if !tt.wantErr && (err != nil) { + assert.Errorf(t, err, "Error") + } + assert.Equalf(t, tt.want, got, "Deployment()") + }) + } +} diff --git a/control-plane/gateways/serviceaccount.go b/control-plane/gateways/serviceaccount.go index e9bb22a41f..8459179521 100644 --- a/control-plane/gateways/serviceaccount.go +++ b/control-plane/gateways/serviceaccount.go @@ -11,9 +11,13 @@ import ( func (b *meshGatewayBuilder) ServiceAccount() *corev1.ServiceAccount { return &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.Name, + Name: b.serviceAccountName(), Namespace: b.gateway.Namespace, Labels: b.Labels(), }, } } + +func (b *meshGatewayBuilder) serviceAccountName() string { + return b.gateway.Name +} diff --git a/control-plane/gateways/serviceaccount_test.go b/control-plane/gateways/serviceaccount_test.go index 15d18dbbad..702fd1fbde 100644 --- a/control-plane/gateways/serviceaccount_test.go +++ b/control-plane/gateways/serviceaccount_test.go @@ -19,7 +19,7 @@ func TestNewMeshGatewayBuilder_ServiceAccount(t *testing.T) { Namespace: "default", Name: "mesh-gateway", }, - }) + }, GatewayConfig{}, nil) expected := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/go.mod b/control-plane/go.mod index 027258c2b7..e6f854dd07 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -48,6 +48,7 @@ require ( k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/gateway-api v0.7.1 + sigs.k8s.io/yaml v1.3.0 ) require ( @@ -173,7 +174,6 @@ require ( k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) go 1.20 diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 6572dc2450..c9e77c66df 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -5,7 +5,7 @@ package connectinject import ( "context" - + "github.com/hashicorp/consul-k8s/control-plane/gateways" "github.com/hashicorp/consul-server-connection-manager/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -182,6 +182,25 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.MeshGateway), Scheme: mgr.GetScheme(), + GatewayConfig: gateways.GatewayConfig{ + ConsulConfig: common.ConsulConfig{ + Address: c.consul.Addresses, + GRPCPort: consulConfig.GRPCPort, + HTTPPort: consulConfig.HTTPPort, + APITimeout: consulConfig.APITimeout, + }, + ImageDataplane: c.flagConsulDataplaneImage, + ImageConsulK8S: c.flagConsulK8sImage, + ConsulTenancyConfig: consulTenancyConfig, + PeeringEnabled: c.flagEnablePeering, + EnableOpenShift: c.flagEnableOpenShift, + AuthMethod: c.consul.ConsulLogin.AuthMethod, + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, + TLSEnabled: c.consul.UseTLS, + ConsulTLSServerName: c.consul.TLSServerName, + ConsulCACert: string(c.caCertPem), + }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.MeshGateway) return err From d5bc9a85246a9a96ea281a8f64c73664a7f35f58 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 5 Dec 2023 13:43:13 -0500 Subject: [PATCH 501/592] Update generated files and manifests (#3299) There is currently a diff when running `make ctrl-generate ctrl-manifests` on main. This makes development cycles tedious as we generally try to avoid including unrelated changes in new PRs. --- .../crd-gatewayclasses-external.yaml | 9 ++-- .../templates/crd-gateways-external.yaml | 9 ++-- .../templates/crd-grpcroutes-external.yaml | 9 ++-- .../templates/crd-httproutes-external.yaml | 9 ++-- .../templates/crd-proxyconfigurations.yaml | 20 +-------- .../crd-referencegrants-external.yaml | 9 ++-- .../templates/crd-routetimeoutfilters.yaml | 2 + .../templates/crd-tcproutes-external.yaml | 9 ++-- .../templates/crd-tlsroutes-external.yaml | 9 ++-- .../templates/crd-udproutes-external.yaml | 9 ++-- ...sul.hashicorp.com_proxyconfigurations.yaml | 20 +-------- control-plane/config/rbac/role.yaml | 41 +++++++++---------- 12 files changed, 48 insertions(+), 107 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclasses-external.yaml b/charts/consul/templates/crd-gatewayclasses-external.yaml index 391637b5f7..93435b7fce 100644 --- a/charts/consul/templates/crd-gatewayclasses-external.yaml +++ b/charts/consul/templates/crd-gatewayclasses-external.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: gatewayclasses.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-gateways-external.yaml b/charts/consul/templates/crd-gateways-external.yaml index ab56d4f5fb..41df34942a 100644 --- a/charts/consul/templates/crd-gateways-external.yaml +++ b/charts/consul/templates/crd-gateways-external.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: gateways.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-grpcroutes-external.yaml b/charts/consul/templates/crd-grpcroutes-external.yaml index 3e4aa75853..739ed2c659 100644 --- a/charts/consul/templates/crd-grpcroutes-external.yaml +++ b/charts/consul/templates/crd-grpcroutes-external.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: grpcroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-httproutes-external.yaml b/charts/consul/templates/crd-httproutes-external.yaml index c89591376a..bba3672d16 100644 --- a/charts/consul/templates/crd-httproutes-external.yaml +++ b/charts/consul/templates/crd-httproutes-external.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: httproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-proxyconfigurations.yaml b/charts/consul/templates/crd-proxyconfigurations.yaml index 9a33bd2bab..3d19d5ea4f 100644 --- a/charts/consul/templates/crd-proxyconfigurations.yaml +++ b/charts/consul/templates/crd-proxyconfigurations.yaml @@ -127,24 +127,6 @@ spec: format: int32 type: string type: object - envoyExtensions: - items: - description: EnvoyExtension has configuration for an extension - that patches Envoy resources. - properties: - arguments: - type: object - x-kubernetes-preserve-unknown-fields: true - consulVersion: - type: string - envoyVersion: - type: string - name: - type: string - required: - type: boolean - type: object - type: array exposeConfig: properties: exposePaths: @@ -178,7 +160,7 @@ spec: format: int32 type: string maxInboundConnections: - format: int64 + format: int32 type: integer type: object listenerTracingJson: diff --git a/charts/consul/templates/crd-referencegrants-external.yaml b/charts/consul/templates/crd-referencegrants-external.yaml index 6ae177d987..db9cf12027 100644 --- a/charts/consul/templates/crd-referencegrants-external.yaml +++ b/charts/consul/templates/crd-referencegrants-external.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: referencegrants.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-routetimeoutfilters.yaml b/charts/consul/templates/crd-routetimeoutfilters.yaml index 95ab50320d..07ebfe9386 100644 --- a/charts/consul/templates/crd-routetimeoutfilters.yaml +++ b/charts/consul/templates/crd-routetimeoutfilters.yaml @@ -55,8 +55,10 @@ spec: description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. properties: idleTimeout: + format: duration type: string requestTimeout: + format: duration type: string type: object status: diff --git a/charts/consul/templates/crd-tcproutes-external.yaml b/charts/consul/templates/crd-tcproutes-external.yaml index 91989135e2..b5bc7be13c 100644 --- a/charts/consul/templates/crd-tcproutes-external.yaml +++ b/charts/consul/templates/crd-tcproutes-external.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: tcproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-tlsroutes-external.yaml b/charts/consul/templates/crd-tlsroutes-external.yaml index dfabd80713..1acd1b973a 100644 --- a/charts/consul/templates/crd-tlsroutes-external.yaml +++ b/charts/consul/templates/crd-tlsroutes-external.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: tlsroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-udproutes-external.yaml b/charts/consul/templates/crd-udproutes-external.yaml index 935cce22fa..0661b24c1a 100644 --- a/charts/consul/templates/crd-udproutes-external.yaml +++ b/charts/consul/templates/crd-udproutes-external.yaml @@ -1,21 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: udproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml index 1d15b34111..4a505adeb9 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml @@ -123,24 +123,6 @@ spec: format: int32 type: string type: object - envoyExtensions: - items: - description: EnvoyExtension has configuration for an extension - that patches Envoy resources. - properties: - arguments: - type: object - x-kubernetes-preserve-unknown-fields: true - consulVersion: - type: string - envoyVersion: - type: string - name: - type: string - required: - type: boolean - type: object - type: array exposeConfig: properties: exposePaths: @@ -174,7 +156,7 @@ spec: format: int32 type: string maxInboundConnections: - format: int64 + format: int32 type: integer type: object listenerTracingJson: diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 4f99e75e88..b0904211b6 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -365,6 +365,26 @@ rules: - get - patch - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - gatewayclassconfig + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - gatewayclassconfig/status + verbs: + - get + - patch + - update - apiGroups: - mesh.consul.hashicorp.com resources: @@ -465,24 +485,3 @@ rules: - get - patch - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclassconfig - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclassconfig/status - verbs: - - get - - patch - - update - From c4f2621257a4fb45c10b7e255a5337162a5fb206 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 5 Dec 2023 13:50:25 -0500 Subject: [PATCH 502/592] Create role and role binding for mesh gateways (#3303) * Create role and role binding for mesh gateways * add some whitespace --- .../controllersv2/mesh_gateway_controller.go | 23 ++- .../mesh_gateway_controller_test.go | 136 +++++++++++++++++- control-plane/gateways/role.go | 34 +++++ 3 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 control-plane/gateways/role.go diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index 00de015931..6ce9baecba 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -11,6 +11,7 @@ import ( "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -111,7 +112,21 @@ func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Req return fmt.Errorf("unable to create service account: %w", err) } - // TODO NET-6393 NET-6395 + //Create Role + + err = r.opIfNewOrOwned(ctx, resource, &rbacv1.Role{}, builder.Role(), upsertOp) + if err != nil { + return fmt.Errorf("unable to create role: %w", err) + } + + //Create RoleBinding + + err = r.opIfNewOrOwned(ctx, resource, &rbacv1.RoleBinding{}, builder.RoleBinding(), upsertOp) + if err != nil { + return fmt.Errorf("unable to create role binding: %w", err) + } + + // TODO NET-6393 //Create deployment @@ -133,12 +148,12 @@ func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Req builtDeployment, err := builder.Deployment() if err != nil { - return fmt.Errorf("Unable to build deployment: %w", err) + return fmt.Errorf("unable to build deployment: %w", err) } err = r.opIfNewOrOwned(ctx, resource, &appsv1.Deployment{}, builtDeployment, mergeDeploymentOp) if err != nil { - return fmt.Errorf("Unable to create deployment: %w", err) + return fmt.Errorf("unable to create deployment: %w", err) } return nil @@ -149,7 +164,7 @@ func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Req // have an owner reference and will thus be cleaned up by the K8s garbage collector // once the owning meshv2beta1.MeshGateway is deleted. func (r *MeshGatewayController) onDelete(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { - // TODO NET-6392 NET-6393 NET-6395 + // TODO NET-6392 NET-6393 return nil } diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index 41e58c2f13..a4c472fc90 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -89,8 +90,57 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { expectedResult: ctrl.Result{}, expectedErr: errResourceNotOwned, }, + // Role { - name: "MeshGateway created with existing ServiceAccount owned by gateway", + name: "MeshGateway created with no existing Role", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "consul", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "consul", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + postReconcile: func(t *testing.T, c client.Client) { + // Verify ClusterRole was created + key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} + assert.NoError(t, c.Get(context.Background(), key, &rbacv1.Role{})) + }, + }, + { + name: "MeshGateway created with existing Role not owned by gateway", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + expectedErr: errResourceNotOwned, + }, + { + name: "MeshGateway created with existing Role owned by gateway", k8sObjects: []runtime.Object{ &v2beta1.MeshGateway{ ObjectMeta: metav1.ObjectMeta{ @@ -99,7 +149,88 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { UID: "abc123", }, }, - &corev1.ServiceAccount{ + &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + OwnerReferences: []metav1.OwnerReference{ + { + UID: "abc123", + Name: "mesh-gateway", + }, + }, + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + expectedErr: nil, // The Reconcile should be a no-op + }, + // RoleBinding + { + name: "MeshGateway created with no existing RoleBinding", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "consul", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "consul", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + postReconcile: func(t *testing.T, c client.Client) { + // Verify ClusterRole was created + key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} + assert.NoError(t, c.Get(context.Background(), key, &rbacv1.RoleBinding{})) + }, + }, + { + name: "MeshGateway created with existing RoleBinding not owned by gateway", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + expectedErr: errResourceNotOwned, + }, + { + name: "MeshGateway created with existing RoleBinding owned by gateway", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + UID: "abc123", + }, + }, + &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "mesh-gateway", @@ -213,6 +344,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { s := runtime.NewScheme() require.NoError(t, corev1.AddToScheme(s)) require.NoError(t, appsv1.AddToScheme(s)) + require.NoError(t, rbacv1.AddToScheme(s)) s.AddKnownTypes(v2beta1.MeshGroupVersion, &v2beta1.MeshGateway{}, &v2beta1.GatewayClass{}, &v2beta1.GatewayClassConfig{}) fakeClient := fake.NewClientBuilder().WithScheme(s). WithRuntimeObjects(testCase.k8sObjects...). diff --git a/control-plane/gateways/role.go b/control-plane/gateways/role.go new file mode 100644 index 0000000000..515ff0f3f9 --- /dev/null +++ b/control-plane/gateways/role.go @@ -0,0 +1,34 @@ +package gateways + +import ( + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (b *meshGatewayBuilder) Role() *rbacv1.Role { + return &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + Labels: b.Labels(), + }, + Rules: []rbacv1.PolicyRule{}, + } +} + +func (b *meshGatewayBuilder) RoleBinding() *rbacv1.RoleBinding { + return &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + Labels: b.Labels(), + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.ServiceAccountKind, + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + }, + }, + } +} From 46bf9bf4dbf67fcb6490f1bbd78d5a875f8ee60e Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:36:53 -0600 Subject: [PATCH 503/592] NET-6575- Add ConfigMap volume mount to gateway cleanup job (#3301) * add volume mount * fix spacing --- charts/consul/templates/gateway-cleanup-job.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index df6c22fd30..5f8afed3ea 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -52,8 +52,16 @@ spec: limits: memory: "50Mi" cpu: "50m" + volumeMounts: + - name: config + mountPath: /consul/config + readOnly: true {{- if .Values.global.acls.tolerations }} tolerations: {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} {{- end }} + volumes: + - name: config + configMap: + name: {{ template "consul.fullname" . }}-gateway-resources-config {{- end }} From 63e32dacecfd897fb8f3a0c78905c35eaac85ac2 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Wed, 6 Dec 2023 11:00:52 -0500 Subject: [PATCH 504/592] NET-6759 Restructure v2 GatewayClassConfig CRD (#3298) Restructure v2 GatewayClassConfig CRD --- .../templates/crd-gatewayclassconfigs.yaml | 856 +++++++++++++++--- .../gateway-resources-configmap.yaml | 57 +- .../v2beta1/gateway_class_config_types.go | 141 ++- .../api/mesh/v2beta1/zz_generated.deepcopy.go | 330 +++++-- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 856 +++++++++++++++--- control-plane/gateways/deployment.go | 7 +- control-plane/gateways/deployment_test.go | 13 +- .../subcommand/gateway-resources/command.go | 1 - .../gateway-resources/command_test.go | 32 +- 9 files changed, 1869 insertions(+), 424 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 7e89d05fe7..2e907e4595 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -21,14 +21,6 @@ spec: scope: Cluster versions: - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - description: The age of the resource jsonPath: .metadata.creationTimestamp name: Age @@ -52,161 +44,745 @@ spec: type: object spec: description: GatewayClassConfigSpec specifies the desired state of the - Config CRD. + GatewayClassConfig CRD. properties: - copyAnnotations: - description: Annotation Information to copy to services or deployments + annotations: + description: Annotations are applied to the created resource properties: - service: - description: List of annotations to copy to the gateway service. + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key included + here will override those in Set if specified on the Gateway. items: type: string type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included here + will be overridden if present in InheritFromGateway and set + on the Gateway. + type: object type: object deployment: - description: Deployment defines the deployment configuration for the - gateway. + description: Deployment contains config specific to the Deployment + created from this GatewayClass properties: - defaultInstances: - default: 1 - description: Number of gateway instances that should be deployed - by default - format: int32 - maximum: 8 - minimum: 1 - type: integer - maxInstances: - default: 8 - description: Max allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - minInstances: - default: 1 - description: Minimum allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - resources: - description: Resources defines the resource requirements for the - gateway. + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + container: + description: Container contains config specific to the created + Deployment's container + properties: + consul: + description: Consul specifies configuration for the consul-dataplane + container + properties: + logging: + description: Logging specifies the logging configuration + for Consul Dataplane + properties: + level: + description: Level sets the logging level for Consul + Dataplane (debug, info, etc.) + type: string + type: object + type: object + resources: + description: Resources specifies the resource requirements + for the created Deployment's container + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + initContainer: + description: InitContainer contains config specific to the created + Deployment's init container + properties: + consul: + description: Consul specifies configuration for the consul-k8s-control-plane + init container + properties: + logging: + description: Logging specifies the logging configuration + for Consul Dataplane + properties: + level: + description: Level sets the logging level for Consul + Dataplane (debug, info, etc.) + type: string + type: object + type: object + resources: + description: Resources specifies the resource requirements + for the created Deployment's init container + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + nodeSelector: + description: NodeSelector specifies the node selector to use on + the created Deployment properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only be - set for containers." + nodeSelectorTerms: + description: Required. A list of node selector terms. The + terms are ORed. items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The TopologySelectorTerm + type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by + node's labels. + items: + description: A node selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: The label key that the selector applies + to. + type: string + operator: + description: Represents a key's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator + is Gt or Lt, the values array must have a single + element, which will be interpreted as an integer. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements by + node's fields. + items: + description: A node selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: The label key that the selector applies + to. + type: string + operator: + description: Represents a key's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator + is Gt or Lt, the values array must have a single + element, which will be interpreted as an integer. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + priorityClassName: + description: PriorityClassName specifies the priority class name + to use on the created Deployment + type: string + replicas: + description: Replicas specifies the configuration to control the + number of replicas for the created Deployment + properties: + default: + description: Default is the number of replicas assigned to + the Deployment when created + format: int32 + type: integer + max: + description: Max is the maximum number of replicas allowed + for a gateway with this class. If the replica count exceeds + this value due to manual or automated scaling, the replica + count will be restored to this value. + format: int32 + type: integer + min: + description: Min is the minimum number of replicas allowed + for a gateway with this class. If the replica count drops + below this value due to manual or automated scaling, the + replica count will be restored to this value. + format: int32 + type: integer + type: object + securityContext: + description: SecurityContext specifies the security context for + the created Deployment's Pod + properties: + fsGroup: + description: "A special supplemental group that applies to + all containers in a pod. Some volume types allow the Kubelet + to change the ownership of that volume to be owned by the + pod: \n 1. The owning GID will be the FSGroup 2. The setgid + bit is set (new files created in the volume will be owned + by FSGroup) 3. The permission bits are OR'd with rw-rw---- + \n If unset, the Kubelet will not modify the ownership and + permissions of any volume. Note that this field cannot be + set when spec.os.name is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing + ownership and permission of the volume before being exposed + inside Pod. This field will only apply to volume types which + support fsGroup based ownership(and permissions). It will + have no effect on ephemeral volume types such as: secret, + configmaps and emptydir. Valid values are "OnRootMismatch" + and "Always". If not specified, "Always" is used. Note that + this field cannot be set when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this field + cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + Note that this field cannot be set when spec.os.name is + windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + SecurityContext. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's primary + GID, the fsGroup (if specified), and group memberships defined + in the container image for the uid of the container process. + If unspecified, no additional groups are added to any container. + Note that group memberships defined in the container image + for the uid of the container process are still effective, + even if they are not included in this list. Note that this + field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used + for the pod. Pods with unsupported sysctls (by the container + runtime) might fail to launch. Note that this field cannot + be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set properties: name: - description: Name must match the name of one entry in - pod.spec.resourceClaims of the Pod where this field - is used. It makes that resource available inside a - container. + description: Name of a property to set + type: string + value: + description: Value of a property to set type: string required: - name + - value type: object type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options within a container's + SecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + tolerations: + description: Tolerations specifies the tolerations to use on the + created Deployment + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key included + here will override those in Set if specified on the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included here + will be overridden if present in InheritFromGateway and set + on the Gateway. + type: object + type: object + role: + description: Role contains config specific to the Role created from + this GatewayClass + properties: + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. type: object type: object type: object - mapPrivilegedContainerPorts: - description: The value to add to privileged ports ( ports < 1024) - for gateway containers - format: int32 - type: integer - nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a selector which must be true for the - pod to fit on a node. Selector which must match a node''s labels - for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + roleBinding: + description: RoleBinding contains config specific to the RoleBinding + created from this GatewayClass + properties: + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + type: object + service: + description: Service contains config specific to the Service created + from this GatewayClass + properties: + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + type: + description: Type specifies the type of Service to use (LoadBalancer, + ClusterIP, etc.) + type: string + type: object + serviceAccount: + description: ServiceAccount contains config specific to the corev1.ServiceAccount + created from this GatewayClass + properties: + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object type: object - openshiftSCCName: - description: The name of the OpenShift SecurityContextConstraints - resource for this gateway class to use. - type: string - podSecurityPolicy: - description: The name of an existing Kubernetes PodSecurityPolicy - to bind to the managed ServiceAccount if ACLs are managed. - type: string - serviceType: - description: Service Type string describes ingress methods for a service - enum: - - ClusterIP - - NodePort - - LoadBalancer - type: string - tolerations: - description: 'Tolerations allow the scheduler to schedule nodes with - matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array type: object status: properties: diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 740c0655f6..d6ca88423b 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -22,13 +22,60 @@ data: - apiVersion: mesh.consul.hashicorp.com/v2beta1 metadata: name: consul-mesh-gateway - namespace: {{ .Release.Namespace }} - kind: gatewayClassConfig + kind: GatewayClassConfig spec: + labels: + set: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: mesh-gateway deployment: - resources: {{ toJson .Values.meshGateway.resources }} - nodeSelector: {{ .Values.meshGateway.nodeSelector }} - serviceType: {{ .Values.meshGateway.service.type }} + {{- if .Values.meshGateway.affinity }} + affinity: {{ toJson (default "{}" .Values.meshGateway.affinity) }} + {{- end }} + {{- if .Values.meshGateway.annotations }} + annotations: + set: {{ toJson .Values.meshGateway.annotations }} + {{- end }} + {{- if .Values.global.extraLabels }} + labels: + set: {{ toJson .Values.global.extraLabels }} + {{- end }} + container: + consul: + logging: + level: {{ default .Values.global.logLevel .Values.meshGateway.logLevel }} + portModifier: {{ sub .Values.meshGateway.containerPort .Values.meshGateway.service.port }} + resources: {{ toJson .Values.meshGateway.resources }} + initContainer: + consul: + logging: + level: {{ default .Values.global.logLevel .Values.meshGateway.logLevel }} + resources: {{ toJson .Values.meshGateway.initServiceInitContainer.resources }} + {{- if .Values.meshGateway.nodeSelector }} + nodeSelector: {{ toJson .Values.meshGateway.nodeSelector }} + {{- end }} + priorityClassName: {{ toJson .Values.meshGateway.priorityClassName }} + replicas: + default: {{ .Values.meshGateway.replicas }} + min: {{ .Values.meshGateway.replicas }} + max: {{ .Values.meshGateway.replicas }} + {{- if .Values.meshGateway.tolerations }} + tolerations: {{ toJson .Values.meshGateway.tolerations }} + {{- end }} + service: + {{- if .Values.meshGateway.service.annotations }} + annotations: + set: {{ toJson .Values.meshGateway.service.annotations }} + {{- end }} + type: {{ .Values.meshGateway.service.type }} + {{- if .Values.meshGateway.serviceAccount.annotations }} + serviceAccount: + annotations: + set: {{ toJson .Values.meshGateway.serviceAccount.annotations }} + {{- end }} meshGateways: - name: mesh-gateway namespace: {{ .Release.Namespace }} diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index 74f91a7246..dc2c3dbafe 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -16,10 +16,7 @@ func init() { // +kubebuilder:subresource:status // GatewayClassConfig is the Schema for the Mesh Gateway API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="gateway-class-config" // +kubebuilder:resource:scope=Cluster type GatewayClassConfig struct { metav1.TypeMeta `json:",inline"` @@ -31,66 +28,114 @@ type GatewayClassConfig struct { // +k8s:deepcopy-gen=true -// GatewayClassConfigSpec specifies the desired state of the Config CRD. +// GatewayClassConfigSpec specifies the desired state of the GatewayClassConfig CRD. type GatewayClassConfigSpec struct { + GatewayClassAnnotationsAndLabels `json:",inline"` + + // Deployment contains config specific to the Deployment created from this GatewayClass + Deployment GatewayClassDeploymentConfig `json:"deployment,omitempty"` + // Role contains config specific to the Role created from this GatewayClass + Role GatewayClassRoleConfig `json:"role,omitempty"` + // RoleBinding contains config specific to the RoleBinding created from this GatewayClass + RoleBinding GatewayClassRoleBindingConfig `json:"roleBinding,omitempty"` + // Service contains config specific to the Service created from this GatewayClass + Service GatewayClassServiceConfig `json:"service,omitempty"` + // ServiceAccount contains config specific to the corev1.ServiceAccount created from this GatewayClass + ServiceAccount GatewayClassServiceAccountConfig `json:"serviceAccount,omitempty"` +} - // +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer - ServiceType *corev1.ServiceType `json:"serviceType,omitempty"` +// GatewayClassDeploymentConfig specifies the desired state of the Deployment created from the GatewayClassConfig. +type GatewayClassDeploymentConfig struct { + GatewayClassAnnotationsAndLabels `json:",inline"` + + // Container contains config specific to the created Deployment's container + Container *GatewayClassContainerConfig `json:"container,omitempty"` + // InitContainer contains config specific to the created Deployment's init container + InitContainer *GatewayClassInitContainerConfig `json:"initContainer,omitempty"` + // NodeSelector specifies the node selector to use on the created Deployment + NodeSelector *corev1.NodeSelector `json:"nodeSelector,omitempty"` + // PriorityClassName specifies the priority class name to use on the created Deployment + PriorityClassName string `json:"priorityClassName,omitempty"` + // Replicas specifies the configuration to control the number of replicas for the created Deployment + Replicas *GatewayClassReplicasConfig `json:"replicas,omitempty"` + // SecurityContext specifies the security context for the created Deployment's Pod + SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` + // Tolerations specifies the tolerations to use on the created Deployment + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` +} - // NodeSelector is a selector which must be true for the pod to fit on a node. - // Selector which must match a node's labels for the pod to be scheduled on that node. - // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ - NodeSelector map[string]string `json:"nodeSelector,omitempty"` +type GatewayClassReplicasConfig struct { + // Default is the number of replicas assigned to the Deployment when created + Default *int32 `json:"default,omitempty"` + // Min is the minimum number of replicas allowed for a gateway with this class. + // If the replica count drops below this value due to manual or automated scaling, + // the replica count will be restored to this value. + Min *int32 `json:"min,omitempty"` + // Max is the maximum number of replicas allowed for a gateway with this class. + // If the replica count exceeds this value due to manual or automated scaling, + // the replica count will be restored to this value. + Max *int32 `json:"max,omitempty"` +} - // Tolerations allow the scheduler to schedule nodes with matching taints. - // More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` +type GatewayClassInitContainerConfig struct { + // Consul specifies configuration for the consul-k8s-control-plane init container + Consul GatewayClassConsulConfig `json:"consul,omitempty"` + // Resources specifies the resource requirements for the created Deployment's init container + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` +} - // Deployment defines the deployment configuration for the gateway. - DeploymentSpec DeploymentSpec `json:"deployment,omitempty"` +type GatewayClassContainerConfig struct { + // Consul specifies configuration for the consul-dataplane container + Consul GatewayClassConsulConfig `json:"consul,omitempty"` + // Resources specifies the resource requirements for the created Deployment's container + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` +} - // Annotation Information to copy to services or deployments - CopyAnnotations CopyAnnotationsSpec `json:"copyAnnotations,omitempty"` +type GatewayClassRoleConfig struct { + GatewayClassAnnotationsAndLabels `json:",inline"` +} - // The name of an existing Kubernetes PodSecurityPolicy to bind to the managed ServiceAccount if ACLs are managed. - PodSecurityPolicy string `json:"podSecurityPolicy,omitempty"` +type GatewayClassRoleBindingConfig struct { + GatewayClassAnnotationsAndLabels `json:",inline"` +} - // The name of the OpenShift SecurityContextConstraints resource for this gateway class to use. - OpenshiftSCCName string `json:"openshiftSCCName,omitempty"` +type GatewayClassServiceConfig struct { + GatewayClassAnnotationsAndLabels `json:",inline"` - // The value to add to privileged ports ( ports < 1024) for gateway containers - MapPrivilegedContainerPorts int32 `json:"mapPrivilegedContainerPorts,omitempty"` + // Type specifies the type of Service to use (LoadBalancer, ClusterIP, etc.) + Type *corev1.ServiceType `json:"type,omitempty"` } -// +k8s:deepcopy-gen=true +type GatewayClassServiceAccountConfig struct { + GatewayClassAnnotationsAndLabels `json:",inline"` +} -type DeploymentSpec struct { - // +kubebuilder:default:=1 - // +kubebuilder:validation:Maximum=8 - // +kubebuilder:validation:Minimum=1 - // Number of gateway instances that should be deployed by default - DefaultInstances *int32 `json:"defaultInstances,omitempty"` - // +kubebuilder:default:=8 - // +kubebuilder:validation:Maximum=8 - // +kubebuilder:validation:Minimum=1 - // Max allowed number of gateway instances - MaxInstances *int32 `json:"maxInstances,omitempty"` - // +kubebuilder:default:=1 - // +kubebuilder:validation:Maximum=8 - // +kubebuilder:validation:Minimum=1 - // Minimum allowed number of gateway instances - MinInstances *int32 `json:"minInstances,omitempty"` - - // Resources defines the resource requirements for the gateway. - Resources *corev1.ResourceRequirements `json:"resources,omitempty"` +type GatewayClassConsulConfig struct { + // Logging specifies the logging configuration for Consul Dataplane + Logging GatewayClassConsulLoggingConfig `json:"logging,omitempty"` +} + +type GatewayClassConsulLoggingConfig struct { + // Level sets the logging level for Consul Dataplane (debug, info, etc.) + Level string `json:"level,omitempty"` } -//+kubebuilder:object:generate=true +// GatewayClassAnnotationsAndLabels exists to provide a commonly-embedded wrapper +// for Annotations and Labels on a given resource configuration. +type GatewayClassAnnotationsAndLabels struct { + // Annotations are applied to the created resource + Annotations GatewayClassAnnotationsLabelsConfig `json:"annotations,omitempty"` + // Labels are applied to the created resource + Labels GatewayClassAnnotationsLabelsConfig `json:"labels,omitempty"` +} -// CopyAnnotationsSpec defines the annotations that should be copied to the gateway service. -type CopyAnnotationsSpec struct { - // List of annotations to copy to the gateway service. - Service []string `json:"service,omitempty"` +type GatewayClassAnnotationsLabelsConfig struct { + // InheritFromGateway lists the names/keys of annotations or labels to copy from the Gateway resource. + // Any name/key included here will override those in Set if specified on the Gateway. + InheritFromGateway []string `json:"inheritFromGateway,omitempty"` + // Set lists the names/keys and values of annotations or labels to set on the resource. + // Any name/key included here will be overridden if present in InheritFromGateway and set on the Gateway. + Set map[string]string `json:"set,omitempty"` } // +kubebuilder:object:root=true diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index b8461fca9e..4e53b52ccc 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -47,61 +47,6 @@ func (in Conditions) DeepCopy() Conditions { return *out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CopyAnnotationsSpec) DeepCopyInto(out *CopyAnnotationsSpec) { - *out = *in - if in.Service != nil { - in, out := &in.Service, &out.Service - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CopyAnnotationsSpec. -func (in *CopyAnnotationsSpec) DeepCopy() *CopyAnnotationsSpec { - if in == nil { - return nil - } - out := new(CopyAnnotationsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { - *out = *in - if in.DefaultInstances != nil { - in, out := &in.DefaultInstances, &out.DefaultInstances - *out = new(int32) - **out = **in - } - if in.MaxInstances != nil { - in, out := &in.MaxInstances, &out.MaxInstances - *out = new(int32) - **out = **in - } - if in.MinInstances != nil { - in, out := &in.MinInstances, &out.MinInstances - *out = new(int32) - **out = **in - } - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = new(v1.ResourceRequirements) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec. -func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { - if in == nil { - return nil - } - out := new(DeploymentSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GRPCRoute) DeepCopyInto(out *GRPCRoute) { *out = *in @@ -192,6 +137,50 @@ func (in *GatewayClass) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassAnnotationsAndLabels) DeepCopyInto(out *GatewayClassAnnotationsAndLabels) { + *out = *in + in.Annotations.DeepCopyInto(&out.Annotations) + in.Labels.DeepCopyInto(&out.Labels) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassAnnotationsAndLabels. +func (in *GatewayClassAnnotationsAndLabels) DeepCopy() *GatewayClassAnnotationsAndLabels { + if in == nil { + return nil + } + out := new(GatewayClassAnnotationsAndLabels) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassAnnotationsLabelsConfig) DeepCopyInto(out *GatewayClassAnnotationsLabelsConfig) { + *out = *in + if in.InheritFromGateway != nil { + in, out := &in.InheritFromGateway, &out.InheritFromGateway + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Set != nil { + in, out := &in.Set, &out.Set + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassAnnotationsLabelsConfig. +func (in *GatewayClassAnnotationsLabelsConfig) DeepCopy() *GatewayClassAnnotationsLabelsConfig { + if in == nil { + return nil + } + out := new(GatewayClassAnnotationsLabelsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayClassConfig) DeepCopyInto(out *GatewayClassConfig) { *out = *in @@ -258,17 +247,104 @@ func (in *GatewayClassConfigList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayClassConfigSpec) DeepCopyInto(out *GatewayClassConfigSpec) { *out = *in - if in.ServiceType != nil { - in, out := &in.ServiceType, &out.ServiceType - *out = new(v1.ServiceType) - **out = **in + in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) + in.Deployment.DeepCopyInto(&out.Deployment) + in.Role.DeepCopyInto(&out.Role) + in.RoleBinding.DeepCopyInto(&out.RoleBinding) + in.Service.DeepCopyInto(&out.Service) + in.ServiceAccount.DeepCopyInto(&out.ServiceAccount) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigSpec. +func (in *GatewayClassConfigSpec) DeepCopy() *GatewayClassConfigSpec { + if in == nil { + return nil + } + out := new(GatewayClassConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassConsulConfig) DeepCopyInto(out *GatewayClassConsulConfig) { + *out = *in + out.Logging = in.Logging +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConsulConfig. +func (in *GatewayClassConsulConfig) DeepCopy() *GatewayClassConsulConfig { + if in == nil { + return nil + } + out := new(GatewayClassConsulConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassConsulLoggingConfig) DeepCopyInto(out *GatewayClassConsulLoggingConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConsulLoggingConfig. +func (in *GatewayClassConsulLoggingConfig) DeepCopy() *GatewayClassConsulLoggingConfig { + if in == nil { + return nil + } + out := new(GatewayClassConsulLoggingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassContainerConfig) DeepCopyInto(out *GatewayClassContainerConfig) { + *out = *in + out.Consul = in.Consul + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassContainerConfig. +func (in *GatewayClassContainerConfig) DeepCopy() *GatewayClassContainerConfig { + if in == nil { + return nil + } + out := new(GatewayClassContainerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassDeploymentConfig) DeepCopyInto(out *GatewayClassDeploymentConfig) { + *out = *in + in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) + if in.Container != nil { + in, out := &in.Container, &out.Container + *out = new(GatewayClassContainerConfig) + (*in).DeepCopyInto(*out) + } + if in.InitContainer != nil { + in, out := &in.InitContainer, &out.InitContainer + *out = new(GatewayClassInitContainerConfig) + (*in).DeepCopyInto(*out) } if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } + *out = new(v1.NodeSelector) + (*in).DeepCopyInto(*out) + } + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(GatewayClassReplicasConfig) + (*in).DeepCopyInto(*out) + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.PodSecurityContext) + (*in).DeepCopyInto(*out) } if in.Tolerations != nil { in, out := &in.Tolerations, &out.Tolerations @@ -277,16 +353,35 @@ func (in *GatewayClassConfigSpec) DeepCopyInto(out *GatewayClassConfigSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - in.DeploymentSpec.DeepCopyInto(&out.DeploymentSpec) - in.CopyAnnotations.DeepCopyInto(&out.CopyAnnotations) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigSpec. -func (in *GatewayClassConfigSpec) DeepCopy() *GatewayClassConfigSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassDeploymentConfig. +func (in *GatewayClassDeploymentConfig) DeepCopy() *GatewayClassDeploymentConfig { if in == nil { return nil } - out := new(GatewayClassConfigSpec) + out := new(GatewayClassDeploymentConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassInitContainerConfig) DeepCopyInto(out *GatewayClassInitContainerConfig) { + *out = *in + out.Consul = in.Consul + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassInitContainerConfig. +func (in *GatewayClassInitContainerConfig) DeepCopy() *GatewayClassInitContainerConfig { + if in == nil { + return nil + } + out := new(GatewayClassInitContainerConfig) in.DeepCopyInto(out) return out } @@ -327,6 +422,105 @@ func (in *GatewayClassList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassReplicasConfig) DeepCopyInto(out *GatewayClassReplicasConfig) { + *out = *in + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = new(int32) + **out = **in + } + if in.Min != nil { + in, out := &in.Min, &out.Min + *out = new(int32) + **out = **in + } + if in.Max != nil { + in, out := &in.Max, &out.Max + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassReplicasConfig. +func (in *GatewayClassReplicasConfig) DeepCopy() *GatewayClassReplicasConfig { + if in == nil { + return nil + } + out := new(GatewayClassReplicasConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassRoleBindingConfig) DeepCopyInto(out *GatewayClassRoleBindingConfig) { + *out = *in + in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassRoleBindingConfig. +func (in *GatewayClassRoleBindingConfig) DeepCopy() *GatewayClassRoleBindingConfig { + if in == nil { + return nil + } + out := new(GatewayClassRoleBindingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassRoleConfig) DeepCopyInto(out *GatewayClassRoleConfig) { + *out = *in + in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassRoleConfig. +func (in *GatewayClassRoleConfig) DeepCopy() *GatewayClassRoleConfig { + if in == nil { + return nil + } + out := new(GatewayClassRoleConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassServiceAccountConfig) DeepCopyInto(out *GatewayClassServiceAccountConfig) { + *out = *in + in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassServiceAccountConfig. +func (in *GatewayClassServiceAccountConfig) DeepCopy() *GatewayClassServiceAccountConfig { + if in == nil { + return nil + } + out := new(GatewayClassServiceAccountConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayClassServiceConfig) DeepCopyInto(out *GatewayClassServiceConfig) { + *out = *in + in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(v1.ServiceType) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassServiceConfig. +func (in *GatewayClassServiceConfig) DeepCopy() *GatewayClassServiceConfig { + if in == nil { + return nil + } + out := new(GatewayClassServiceConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { *out = *in diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index 38c134c158..66f687a2a5 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -17,14 +17,6 @@ spec: scope: Cluster versions: - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - description: The age of the resource jsonPath: .metadata.creationTimestamp name: Age @@ -48,161 +40,745 @@ spec: type: object spec: description: GatewayClassConfigSpec specifies the desired state of the - Config CRD. + GatewayClassConfig CRD. properties: - copyAnnotations: - description: Annotation Information to copy to services or deployments + annotations: + description: Annotations are applied to the created resource properties: - service: - description: List of annotations to copy to the gateway service. + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key included + here will override those in Set if specified on the Gateway. items: type: string type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included here + will be overridden if present in InheritFromGateway and set + on the Gateway. + type: object type: object deployment: - description: Deployment defines the deployment configuration for the - gateway. + description: Deployment contains config specific to the Deployment + created from this GatewayClass properties: - defaultInstances: - default: 1 - description: Number of gateway instances that should be deployed - by default - format: int32 - maximum: 8 - minimum: 1 - type: integer - maxInstances: - default: 8 - description: Max allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - minInstances: - default: 1 - description: Minimum allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - resources: - description: Resources defines the resource requirements for the - gateway. + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + container: + description: Container contains config specific to the created + Deployment's container + properties: + consul: + description: Consul specifies configuration for the consul-dataplane + container + properties: + logging: + description: Logging specifies the logging configuration + for Consul Dataplane + properties: + level: + description: Level sets the logging level for Consul + Dataplane (debug, info, etc.) + type: string + type: object + type: object + resources: + description: Resources specifies the resource requirements + for the created Deployment's container + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + initContainer: + description: InitContainer contains config specific to the created + Deployment's init container + properties: + consul: + description: Consul specifies configuration for the consul-k8s-control-plane + init container + properties: + logging: + description: Logging specifies the logging configuration + for Consul Dataplane + properties: + level: + description: Level sets the logging level for Consul + Dataplane (debug, info, etc.) + type: string + type: object + type: object + resources: + description: Resources specifies the resource requirements + for the created Deployment's init container + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable. It can only be set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where this + field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + nodeSelector: + description: NodeSelector specifies the node selector to use on + the created Deployment properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only be - set for containers." + nodeSelectorTerms: + description: Required. A list of node selector terms. The + terms are ORed. items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The TopologySelectorTerm + type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by + node's labels. + items: + description: A node selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: The label key that the selector applies + to. + type: string + operator: + description: Represents a key's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator + is Gt or Lt, the values array must have a single + element, which will be interpreted as an integer. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements by + node's fields. + items: + description: A node selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: The label key that the selector applies + to. + type: string + operator: + description: Represents a key's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator + is Gt or Lt, the values array must have a single + element, which will be interpreted as an integer. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + priorityClassName: + description: PriorityClassName specifies the priority class name + to use on the created Deployment + type: string + replicas: + description: Replicas specifies the configuration to control the + number of replicas for the created Deployment + properties: + default: + description: Default is the number of replicas assigned to + the Deployment when created + format: int32 + type: integer + max: + description: Max is the maximum number of replicas allowed + for a gateway with this class. If the replica count exceeds + this value due to manual or automated scaling, the replica + count will be restored to this value. + format: int32 + type: integer + min: + description: Min is the minimum number of replicas allowed + for a gateway with this class. If the replica count drops + below this value due to manual or automated scaling, the + replica count will be restored to this value. + format: int32 + type: integer + type: object + securityContext: + description: SecurityContext specifies the security context for + the created Deployment's Pod + properties: + fsGroup: + description: "A special supplemental group that applies to + all containers in a pod. Some volume types allow the Kubelet + to change the ownership of that volume to be owned by the + pod: \n 1. The owning GID will be the FSGroup 2. The setgid + bit is set (new files created in the volume will be owned + by FSGroup) 3. The permission bits are OR'd with rw-rw---- + \n If unset, the Kubelet will not modify the ownership and + permissions of any volume. Note that this field cannot be + set when spec.os.name is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing + ownership and permission of the volume before being exposed + inside Pod. This field will only apply to volume types which + support fsGroup based ownership(and permissions). It will + have no effect on ephemeral volume types such as: secret, + configmaps and emptydir. Valid values are "OnRootMismatch" + and "Always". If not specified, "Always" is used. Note that + this field cannot be set when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this field + cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + Note that this field cannot be set when spec.os.name is + windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + SecurityContext. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's primary + GID, the fsGroup (if specified), and group memberships defined + in the container image for the uid of the container process. + If unspecified, no additional groups are added to any container. + Note that group memberships defined in the container image + for the uid of the container process are still effective, + even if they are not included in this list. Note that this + field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used + for the pod. Pods with unsupported sysctls (by the container + runtime) might fail to launch. Note that this field cannot + be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set properties: name: - description: Name must match the name of one entry in - pod.spec.resourceClaims of the Pod where this field - is used. It makes that resource available inside a - container. + description: Name of a property to set + type: string + value: + description: Value of a property to set type: string required: - name + - value type: object type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options within a container's + SecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + tolerations: + description: Tolerations specifies the tolerations to use on the + created Deployment + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key included + here will override those in Set if specified on the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included here + will be overridden if present in InheritFromGateway and set + on the Gateway. + type: object + type: object + role: + description: Role contains config specific to the Role created from + this GatewayClass + properties: + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. type: object type: object type: object - mapPrivilegedContainerPorts: - description: The value to add to privileged ports ( ports < 1024) - for gateway containers - format: int32 - type: integer - nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a selector which must be true for the - pod to fit on a node. Selector which must match a node''s labels - for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + roleBinding: + description: RoleBinding contains config specific to the RoleBinding + created from this GatewayClass + properties: + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + type: object + service: + description: Service contains config specific to the Service created + from this GatewayClass + properties: + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + type: + description: Type specifies the type of Service to use (LoadBalancer, + ClusterIP, etc.) + type: string + type: object + serviceAccount: + description: ServiceAccount contains config specific to the corev1.ServiceAccount + created from this GatewayClass + properties: + annotations: + description: Annotations are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object + labels: + description: Labels are applied to the created resource + properties: + inheritFromGateway: + description: InheritFromGateway lists the names/keys of annotations + or labels to copy from the Gateway resource. Any name/key + included here will override those in Set if specified on + the Gateway. + items: + type: string + type: array + set: + additionalProperties: + type: string + description: Set lists the names/keys and values of annotations + or labels to set on the resource. Any name/key included + here will be overridden if present in InheritFromGateway + and set on the Gateway. + type: object + type: object type: object - openshiftSCCName: - description: The name of the OpenShift SecurityContextConstraints - resource for this gateway class to use. - type: string - podSecurityPolicy: - description: The name of an existing Kubernetes PodSecurityPolicy - to bind to the managed ServiceAccount if ACLs are managed. - type: string - serviceType: - description: Service Type string describes ingress methods for a service - enum: - - ClusterIP - - NodePort - - LoadBalancer - type: string - tolerations: - description: 'Tolerations allow the scheduler to schedule nodes with - matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array type: object status: properties: diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 6da5930b66..e638b2e0b1 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import ( @@ -33,8 +36,8 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { } var resources *corev1.ResourceRequirements - if b.gcc != nil { - resources = b.gcc.Spec.DeploymentSpec.Resources + if b.gcc != nil && b.gcc.Spec.Deployment.Container != nil { + resources = b.gcc.Spec.Deployment.Container.Resources } container, err := consulDataplaneContainer(b.config, resources, b.gateway.Name, b.gateway.Namespace) diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index d04d2701a9..dd679caa8a 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import ( @@ -37,10 +40,12 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { config: GatewayConfig{}, gcc: &meshv2beta1.GatewayClassConfig{ Spec: meshv2beta1.GatewayClassConfigSpec{ - DeploymentSpec: meshv2beta1.DeploymentSpec{ - DefaultInstances: pointer.Int32(1), - MinInstances: pointer.Int32(1), - MaxInstances: pointer.Int32(8), + Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + Replicas: &meshv2beta1.GatewayClassReplicasConfig{ + Default: pointer.Int32(1), + Min: pointer.Int32(1), + Max: pointer.Int32(8), + }, }, }, }, diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 3fb7dd9c24..515110d397 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -418,7 +418,6 @@ func (c *Command) createV2GatewayClassAndClassConfigs(ctx context.Context, compo "component": component, } for _, cfg := range c.gatewayConfig.GatewayClassConfigs { - cfg.Kind = "GatewayClassConfig" err := forceV2ClassConfig(ctx, c.k8sClient, cfg) if err != nil { return err diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index bc9e8c041d..8f5e3e4b3f 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -363,22 +363,21 @@ func TestRun_loadResourceConfigFileWhenConfigFileDoesNotExist(t *testing.T) { require.Contains(t, string(ui.OutputWriter.Bytes()), "No resources.json found, using defaults") } -var validGWConfiguration = ` -gatewayClassConfigs: +var validGWConfiguration = `gatewayClassConfigs: - apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: gatewayClassConfig + kind: GatewayClassConfig metadata: name: consul-mesh-gateway - namespace: namespace spec: deployment: - resources: - requests: - cpu: 200m - memory: 200Mi - limits: - cpu: 200m - memory: 200Mi + container: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + cpu: 200m + memory: 200Mi meshGateways: - name: mesh-gateway spec: @@ -439,15 +438,16 @@ func TestRun_loadGatewayConfigs(t *testing.T) { expectedClassConfig := v2beta1.GatewayClassConfig{ TypeMeta: metav1.TypeMeta{ APIVersion: "mesh.consul.hashicorp.com/v2beta1", - Kind: "gatewayClassConfig", + Kind: "GatewayClassConfig", }, ObjectMeta: metav1.ObjectMeta{ - Name: "consul-mesh-gateway", - Namespace: "namespace", + Name: "consul-mesh-gateway", }, Spec: v2beta1.GatewayClassConfigSpec{ - DeploymentSpec: v2beta1.DeploymentSpec{ - Resources: expectedResources, + Deployment: v2beta1.GatewayClassDeploymentConfig{ + Container: &v2beta1.GatewayClassContainerConfig{ + Resources: expectedResources, + }, }, }, Status: v2beta1.Status{}, From 8e9d8fac909769fe25904422240cf8af0337c9ae Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Wed, 6 Dec 2023 11:52:34 -0500 Subject: [PATCH 505/592] Allow meshGateway.enabled when resource-apis experiment is enabled (#3285) * Allow meshGateway.enabled when resource-apis experiment is enabled * Exclude v1 mesh gateway templates when resource-apis experiment enabled * Enable mesh gateway for bats test where new gate added * Combine if statements for gating v2 configmap component --- charts/consul/templates/_helpers.tpl | 3 --- .../templates/gateway-resources-configmap.yaml | 2 +- .../consul/templates/mesh-gateway-clusterrole.yaml | 2 ++ .../templates/mesh-gateway-clusterrolebinding.yaml | 2 ++ charts/consul/templates/mesh-gateway-deployment.yaml | 2 ++ .../templates/mesh-gateway-podsecuritypolicy.yaml | 2 ++ charts/consul/templates/mesh-gateway-service.yaml | 2 ++ .../templates/mesh-gateway-serviceaccount.yaml | 2 ++ .../test/unit/gateway-resources-configmap.bats | 1 + charts/consul/test/unit/helpers.bats | 12 ------------ 10 files changed, 14 insertions(+), 16 deletions(-) diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 285715225b..aa62cb6600 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -506,9 +506,6 @@ Usage: {{ template "consul.validateResourceAPIs" . }} {{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.syncCatalog.enabled ) }} {{fail "When the value global.experiments.resourceAPIs is set, syncCatalog.enabled is currently unsupported."}} {{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.meshGateway.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, meshGateway.enabled is currently unsupported."}} -{{- end }} {{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.ingressGateways.enabled ) }} {{fail "When the value global.experiments.resourceAPIs is set, ingressGateways.enabled is currently unsupported."}} {{- end }} diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index d6ca88423b..25c4e72688 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -16,7 +16,7 @@ data: resources.json: | {{ toJson .Values.connectInject.apiGateway.managedGatewayClass.resources }} {{- end }} - {{- if (mustHas "resource-apis" .Values.global.experiments) }} + {{- if and (mustHas "resource-apis" .Values.global.experiments) .Values.meshGateway.enabled }} config.yaml: | gatewayClassConfigs: - apiVersion: mesh.consul.hashicorp.com/v2beta1 diff --git a/charts/consul/templates/mesh-gateway-clusterrole.yaml b/charts/consul/templates/mesh-gateway-clusterrole.yaml index b951418b26..3053105105 100644 --- a/charts/consul/templates/mesh-gateway-clusterrole.yaml +++ b/charts/consul/templates/mesh-gateway-clusterrole.yaml @@ -1,4 +1,5 @@ {{- if .Values.meshGateway.enabled }} +{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -32,3 +33,4 @@ rules: rules: [] {{- end }} {{- end }} +{{- end }} diff --git a/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml b/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml index f8150ebb53..2fb80fc04c 100644 --- a/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml +++ b/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml @@ -1,4 +1,5 @@ {{- if .Values.meshGateway.enabled }} +{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -18,3 +19,4 @@ subjects: name: {{ template "consul.fullname" . }}-mesh-gateway namespace: {{ .Release.Namespace }} {{- end }} +{{- end }} diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index afb5d44a0e..cda371d58a 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -1,4 +1,5 @@ {{- if .Values.meshGateway.enabled }} +{{- if not (mustHas "resource-apis" .Values.global.experiments) }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.acls.manageSystemACLs (ne .Values.meshGateway.consulServiceName "") (ne .Values.meshGateway.consulServiceName "mesh-gateway") }}{{ fail "if global.acls.manageSystemACLs is true, meshGateway.consulServiceName cannot be set" }}{{ end -}} {{- if .Values.meshGateway.globalMode }}{{ fail "meshGateway.globalMode is no longer supported; instead, you must migrate to CRDs (see www.consul.io/docs/k8s/crds/upgrade-to-crds)" }}{{ end -}} @@ -309,3 +310,4 @@ spec: {{ tpl .Values.meshGateway.nodeSelector . | indent 8 | trim }} {{- end }} {{- end }} +{{- end }} diff --git a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml index 04576fe926..56e4b7924c 100644 --- a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml +++ b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml @@ -1,4 +1,5 @@ {{- if and .Values.global.enablePodSecurityPolicies .Values.meshGateway.enabled }} +{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: @@ -52,3 +53,4 @@ spec: rule: 'RunAsAny' readOnlyRootFilesystem: false {{- end }} +{{- end }} diff --git a/charts/consul/templates/mesh-gateway-service.yaml b/charts/consul/templates/mesh-gateway-service.yaml index 5fdceca8df..80f82ac897 100644 --- a/charts/consul/templates/mesh-gateway-service.yaml +++ b/charts/consul/templates/mesh-gateway-service.yaml @@ -1,4 +1,5 @@ {{- if and .Values.meshGateway.enabled }} +{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: v1 kind: Service metadata: @@ -31,3 +32,4 @@ spec: {{ tpl .Values.meshGateway.service.additionalSpec . | nindent 2 | trim }} {{- end }} {{- end }} +{{- end }} diff --git a/charts/consul/templates/mesh-gateway-serviceaccount.yaml b/charts/consul/templates/mesh-gateway-serviceaccount.yaml index 8c2da5ae06..b1a0661eaa 100644 --- a/charts/consul/templates/mesh-gateway-serviceaccount.yaml +++ b/charts/consul/templates/mesh-gateway-serviceaccount.yaml @@ -1,4 +1,5 @@ {{- if .Values.meshGateway.enabled }} +{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: v1 kind: ServiceAccount metadata: @@ -21,3 +22,4 @@ imagePullSecrets: {{- end }} {{- end }} {{- end }} +{{- end }} diff --git a/charts/consul/test/unit/gateway-resources-configmap.bats b/charts/consul/test/unit/gateway-resources-configmap.bats index bf7800866c..df66606194 100644 --- a/charts/consul/test/unit/gateway-resources-configmap.bats +++ b/charts/consul/test/unit/gateway-resources-configmap.bats @@ -62,6 +62,7 @@ load _helpers local resources=$(helm template \ -s templates/gateway-resources-configmap.yaml \ --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=true' \ --set 'global.experiments[0]=resource-apis' \ --set 'ui.enabled=false' \ . | tee /dev/stderr | diff --git a/charts/consul/test/unit/helpers.bats b/charts/consul/test/unit/helpers.bats index f8523a7220..b1a7c54cb6 100644 --- a/charts/consul/test/unit/helpers.bats +++ b/charts/consul/test/unit/helpers.bats @@ -431,18 +431,6 @@ load _helpers [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, syncCatalog.enabled is currently unsupported." ]] } -@test "connectInject/Deployment: fails if resource-apis is set and meshGateway is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'meshGateway.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, meshGateway.enabled is currently unsupported." ]] -} - @test "connectInject/Deployment: fails if resource-apis is set and ingressGateways is enabled" { cd `chart_dir` run helm template \ From 7f79d29d3efc8f492f2f361af3d7a867131d4d05 Mon Sep 17 00:00:00 2001 From: Joshua Timmons Date: Wed, 6 Dec 2023 12:37:06 -0500 Subject: [PATCH 506/592] Add validation that externalServers.hosts is not set to HCP-managed cluster's addresses when global.cloud.enabled (#3218) --- .changelog/3218.txt | 3 +++ .../templates/connect-inject-deployment.yaml | 1 + .../test/unit/connect-inject-deployment.bats | 24 +++++++++++++++++++ charts/consul/values.yaml | 8 +++++-- 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 .changelog/3218.txt diff --git a/.changelog/3218.txt b/.changelog/3218.txt new file mode 100644 index 0000000000..b0c1729b1f --- /dev/null +++ b/.changelog/3218.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters +``` \ No newline at end of file diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 53f894035a..db140ddc1e 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -1,6 +1,7 @@ {{- if and .Values.global.peering.enabled (not .Values.connectInject.enabled) }}{{ fail "setting global.peering.enabled to true requires connectInject.enabled to be true" }}{{ end }} {{- if and .Values.global.peering.enabled (not .Values.global.tls.enabled) }}{{ fail "setting global.peering.enabled to true requires global.tls.enabled to be true" }}{{ end }} {{- if and .Values.global.peering.enabled (not .Values.meshGateway.enabled) }}{{ fail "setting global.peering.enabled to true requires meshGateway.enabled to be true" }}{{ end }} +{{- if and .Values.externalServers.enabled .Values.global.cloud.enabled (gt (len .Values.externalServers.hosts) 0) (regexMatch ".+.hashicorp.cloud$" ( first .Values.externalServers.hosts )) }}{{fail "global.cloud.enabled cannot be used in combination with an HCP-managed cluster address in externalServers.hosts. global.cloud.enabled is for linked self-managed clusters."}}{{- end }} {{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} {{ template "consul.validateVaultWebhookCertConfiguration" . }} diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 748b75de5d..67be4474ea 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -2639,6 +2639,30 @@ reservedNameTest() { [ "${actual}" = "true" ] } +@test "connectInject/Deployment: validates that externalServers.hosts is not set with an HCP-managed cluster's address" { + cd `chart_dir` + run helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'global.enabled=false' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=abc.aws.hashicorp.cloud' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + . > /dev/stderr + + [ "$status" -eq 1 ] + + [[ "$output" =~ "global.cloud.enabled cannot be used in combination with an HCP-managed cluster address in externalServers.hosts. global.cloud.enabled is for linked self-managed clusters." ]] +} + @test "connectInject/Deployment: can provide a TLS server name for the sidecar-injector when global.cloud.enabled is set" { cd `chart_dir` local env=$(helm template \ diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 49d986cd7d..16ae7f8bc4 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -655,8 +655,12 @@ global: # Enables installing an HCP Consul Central self-managed cluster. # Requires Consul v1.14+. cloud: - # If true, the Helm chart will enable the installation of an HCP Consul Central - # self-managed cluster. + # If true, the Helm chart will link a [self-managed cluster to HCP](https://developer.hashicorp.com/hcp/docs/consul/self-managed). + # This can either be used to [configure a new cluster](https://developer.hashicorp.com/hcp/docs/consul/self-managed/new) + # or [link an existing one](https://developer.hashicorp.com/hcp/docs/consul/self-managed/existing). + # + # Note: this setting should not be enabled for [HashiCorp-managed clusters](https://developer.hashicorp.com/hcp/docs/consul/hcp-managed). + # It is strictly for linking self-managed clusters. enabled: false # The resource id of the HCP Consul Central cluster to link to. Eg: From 0d1adccd933c917688ee5380c9f6f2f0546b39ba Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Wed, 6 Dec 2023 14:34:16 -0500 Subject: [PATCH 507/592] Revert "Add validation that externalServers.hosts is not set to HCP-managed cluster's addresses when global.cloud.enabled" (#3314) Revert "Add validation that externalServers.hosts is not set to HCP-managed cluster's addresses when global.cloud.enabled (#3218)" This reverts commit 7f79d29d3efc8f492f2f361af3d7a867131d4d05. --- .changelog/3218.txt | 3 --- .../templates/connect-inject-deployment.yaml | 1 - .../test/unit/connect-inject-deployment.bats | 24 ------------------- charts/consul/values.yaml | 8 ++----- 4 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 .changelog/3218.txt diff --git a/.changelog/3218.txt b/.changelog/3218.txt deleted file mode 100644 index b0c1729b1f..0000000000 --- a/.changelog/3218.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters -``` \ No newline at end of file diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index db140ddc1e..53f894035a 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -1,7 +1,6 @@ {{- if and .Values.global.peering.enabled (not .Values.connectInject.enabled) }}{{ fail "setting global.peering.enabled to true requires connectInject.enabled to be true" }}{{ end }} {{- if and .Values.global.peering.enabled (not .Values.global.tls.enabled) }}{{ fail "setting global.peering.enabled to true requires global.tls.enabled to be true" }}{{ end }} {{- if and .Values.global.peering.enabled (not .Values.meshGateway.enabled) }}{{ fail "setting global.peering.enabled to true requires meshGateway.enabled to be true" }}{{ end }} -{{- if and .Values.externalServers.enabled .Values.global.cloud.enabled (gt (len .Values.externalServers.hosts) 0) (regexMatch ".+.hashicorp.cloud$" ( first .Values.externalServers.hosts )) }}{{fail "global.cloud.enabled cannot be used in combination with an HCP-managed cluster address in externalServers.hosts. global.cloud.enabled is for linked self-managed clusters."}}{{- end }} {{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} {{ template "consul.validateVaultWebhookCertConfiguration" . }} diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 67be4474ea..748b75de5d 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -2639,30 +2639,6 @@ reservedNameTest() { [ "${actual}" = "true" ] } -@test "connectInject/Deployment: validates that externalServers.hosts is not set with an HCP-managed cluster's address" { - cd `chart_dir` - run helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'global.enabled=false' \ - --set 'connectInject.enabled=true' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=abc.aws.hashicorp.cloud' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . > /dev/stderr - - [ "$status" -eq 1 ] - - [[ "$output" =~ "global.cloud.enabled cannot be used in combination with an HCP-managed cluster address in externalServers.hosts. global.cloud.enabled is for linked self-managed clusters." ]] -} - @test "connectInject/Deployment: can provide a TLS server name for the sidecar-injector when global.cloud.enabled is set" { cd `chart_dir` local env=$(helm template \ diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 16ae7f8bc4..49d986cd7d 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -655,12 +655,8 @@ global: # Enables installing an HCP Consul Central self-managed cluster. # Requires Consul v1.14+. cloud: - # If true, the Helm chart will link a [self-managed cluster to HCP](https://developer.hashicorp.com/hcp/docs/consul/self-managed). - # This can either be used to [configure a new cluster](https://developer.hashicorp.com/hcp/docs/consul/self-managed/new) - # or [link an existing one](https://developer.hashicorp.com/hcp/docs/consul/self-managed/existing). - # - # Note: this setting should not be enabled for [HashiCorp-managed clusters](https://developer.hashicorp.com/hcp/docs/consul/hcp-managed). - # It is strictly for linking self-managed clusters. + # If true, the Helm chart will enable the installation of an HCP Consul Central + # self-managed cluster. enabled: false # The resource id of the HCP Consul Central cluster to link to. Eg: From 3190d30cac035a4833ecced219e2c449c1bd234e Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 6 Dec 2023 15:18:23 -0500 Subject: [PATCH 508/592] [NET-6404] Create Mesh Gateway Resource from Gateway Resources Job (#3306) * create mesh gateway from resources job * remove whitespace change * Update config map to be more complete representation of object, clean up cluster role * Update charts/consul/templates/gateway-resources-configmap.yaml Co-authored-by: Nathan Coleman * fix tests --------- Co-authored-by: Nathan Coleman --- .../gateway-resources-clusterrole.yaml | 8 +++ .../gateway-resources-configmap.yaml | 7 ++- .../subcommand/gateway-resources/command.go | 53 ++++++++++++++++++- .../gateway-resources/command_test.go | 32 ++++++++++- 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/charts/consul/templates/gateway-resources-clusterrole.yaml b/charts/consul/templates/gateway-resources-clusterrole.yaml index 42e60f75aa..ad7082f060 100644 --- a/charts/consul/templates/gateway-resources-clusterrole.yaml +++ b/charts/consul/templates/gateway-resources-clusterrole.yaml @@ -10,6 +10,14 @@ metadata: release: {{ .Release.Name }} component: gateway-resources rules: + - apiGroups: + - mesh.consul.hashicorp.com + resources: + - meshgateways + verbs: + - get + - update + - create - apiGroups: - consul.hashicorp.com - mesh.consul.hashicorp.com diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 25c4e72688..cf20a32d26 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -77,8 +77,11 @@ data: set: {{ toJson .Values.meshGateway.serviceAccount.annotations }} {{- end }} meshGateways: - - name: mesh-gateway - namespace: {{ .Release.Namespace }} + - apiVersion: mesh.consul.hashicorp.com/v2beta1 + kind: MeshGateway + metadata: + name: mesh-gateway + namespace: {{ .Release.Namespace }} spec: gatewayClassName: consul-mesh-gateway {{- end }} diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 515110d397..0f256479e6 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -41,6 +41,7 @@ import ( const ( gatewayConfigFilename = "/consul/config/config.yaml" resourceConfigFilename = "/consul/config/resources.json" + meshGatewayComponent = "consul-mesh-gateway" ) // this dupes the Kubernetes tolerations @@ -109,7 +110,8 @@ type Command struct { } type gatewayConfig struct { - GatewayClassConfigs []*v2beta1.GatewayClassConfig `yaml:"gatewayClassConfigs"` + GatewayClassConfigs []*v2beta1.GatewayClassConfig `json:"gatewayClassConfigs"` + MeshGateways []*v2beta1.MeshGateway `json:"meshGateways"` } func (c *Command) init() { @@ -288,7 +290,15 @@ func (c *Command) Run(args []string) int { } if len(c.gatewayConfig.GatewayClassConfigs) > 0 { - err = c.createV2GatewayClassAndClassConfigs(context.Background(), "consul-mesh-gateway", "consul-mesh-gateway-controller") + err = c.createV2GatewayClassAndClassConfigs(context.Background(), meshGatewayComponent, "consul-mesh-gateway-controller") + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + } + + if len(c.gatewayConfig.MeshGateways) > 0 { + err = c.createV2MeshGateways(context.Background(), meshGatewayComponent) if err != nil { c.UI.Error(err.Error()) return 1 @@ -417,6 +427,7 @@ func (c *Command) createV2GatewayClassAndClassConfigs(ctx context.Context, compo "release": c.flagRelease, "component": component, } + for _, cfg := range c.gatewayConfig.GatewayClassConfigs { err := forceV2ClassConfig(ctx, c.k8sClient, cfg) if err != nil { @@ -446,6 +457,25 @@ func (c *Command) createV2GatewayClassAndClassConfigs(ctx context.Context, compo return nil } +func (c *Command) createV2MeshGateways(ctx context.Context, component string) error { + labels := map[string]string{ + "app": c.flagApp, + "chart": c.flagChart, + "heritage": c.flagHeritage, + "release": c.flagRelease, + "component": component, + } + for _, meshGw := range c.gatewayConfig.MeshGateways { + meshGw.Labels = labels + err := forceV2MeshGateway(ctx, c.k8sClient, meshGw) + if err != nil { + return err + } + + } + return nil +} + func (c *Command) Synopsis() string { return synopsis } func (c *Command) Help() string { c.once.Do(c.init) @@ -551,6 +581,25 @@ func forceV2Class(ctx context.Context, k8sClient client.Client, o *v2beta1.Gatew }, exponentialBackoffWithMaxIntervalAndTime()) } +func forceV2MeshGateway(ctx context.Context, k8sClient client.Client, o *v2beta1.MeshGateway) error { + return backoff.Retry(func() error { + var existing v2beta1.MeshGateway + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } + + if k8serrors.IsNotFound(err) { + return k8sClient.Create(ctx, o) + } + + existing.Spec = *o.Spec.DeepCopy() + existing.Labels = o.Labels + + return k8sClient.Update(ctx, &existing) + }, exponentialBackoffWithMaxIntervalAndTime()) +} + func exponentialBackoffWithMaxIntervalAndTime() *backoff.ExponentialBackOff { backoff := backoff.NewExponentialBackOff() backoff.MaxElapsedTime = 10 * time.Second diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 8f5e3e4b3f..10ae4de8f9 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -17,6 +17,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + meshv2beta1 "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) @@ -379,7 +381,11 @@ var validGWConfiguration = `gatewayClassConfigs: cpu: 200m memory: 200Mi meshGateways: -- name: mesh-gateway +- apiVersion: mesh.consul.hashicorp.com/v2beta1 + kind: MeshGateway + metadata: + name: mesh-gateway + namespace: consul spec: gatewayClassName: consul-mesh-gateway ` @@ -431,13 +437,14 @@ func TestRun_loadGatewayConfigs(t *testing.T) { err := cmd.loadGatewayConfigs() require.NoError(t, err) require.NotEmpty(t, cmd.gatewayConfig.GatewayClassConfigs) + require.NotEmpty(t, cmd.gatewayConfig.MeshGateways) // we only created one class config classConfig := cmd.gatewayConfig.GatewayClassConfigs[0].DeepCopy() expectedClassConfig := v2beta1.GatewayClassConfig{ TypeMeta: metav1.TypeMeta{ - APIVersion: "mesh.consul.hashicorp.com/v2beta1", + APIVersion: v2beta1.MeshGroupVersion.String(), Kind: "GatewayClassConfig", }, ObjectMeta: metav1.ObjectMeta{ @@ -453,6 +460,25 @@ func TestRun_loadGatewayConfigs(t *testing.T) { Status: v2beta1.Status{}, } require.Equal(t, expectedClassConfig.DeepCopy(), classConfig) + + // check mesh gateway, we only created one of these + actualMeshGateway := cmd.gatewayConfig.MeshGateways[0] + + expectedMeshGateway := &v2beta1.MeshGateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "MeshGateway", + APIVersion: v2beta1.MeshGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "mesh-gateway", + Namespace: "consul", + }, + Spec: meshv2beta1.MeshGateway{ + GatewayClassName: "consul-mesh-gateway", + }, + } + + require.Equal(t, expectedMeshGateway.DeepCopy(), actualMeshGateway) } func TestRun_loadGatewayConfigsWithInvalidFile(t *testing.T) { @@ -474,6 +500,7 @@ func TestRun_loadGatewayConfigsWithInvalidFile(t *testing.T) { err := cmd.loadGatewayConfigs() require.Error(t, err) require.Empty(t, cmd.gatewayConfig.GatewayClassConfigs) + require.Empty(t, cmd.gatewayConfig.MeshGateways) } func TestRun_loadGatewayConfigsWhenConfigFileDoesNotExist(t *testing.T) { @@ -494,6 +521,7 @@ func TestRun_loadGatewayConfigsWhenConfigFileDoesNotExist(t *testing.T) { err := cmd.loadGatewayConfigs() require.NoError(t, err) require.Empty(t, cmd.gatewayConfig.GatewayClassConfigs) + require.Empty(t, cmd.gatewayConfig.MeshGateways) require.Contains(t, string(ui.ErrorWriter.Bytes()), "gateway configuration file not found, skipping gateway configuration") } From 1682744abe2b1439669964d33a1e062ba4896d6f Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Wed, 6 Dec 2023 16:10:14 -0500 Subject: [PATCH 509/592] Bold section headers in PR template (#3313) * Bold section headers in PR template * Use proper header markers instead of just bolding * Update pull_request_template.md --- .github/pull_request_template.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f5d211b137..e863abf7c5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,14 +1,13 @@ -Changes proposed in this PR: +### Changes proposed in this PR ### - - -How I've tested this PR: +### How I've tested this PR ### -How I expect reviewers to test this PR: +### How I expect reviewers to test this PR ### -Checklist: + +### Checklist ### - [ ] Tests added - [ ] [CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) - - From 11be160d0907219b35db6d1cd52f4caf48237d51 Mon Sep 17 00:00:00 2001 From: Ronald Date: Wed, 6 Dec 2023 16:39:43 -0500 Subject: [PATCH 510/592] [NET-6650] Bump go version to 1.20.12 (#3312) --- .changelog/3312.txt | 7 +++++++ .go-version | 2 +- control-plane/Dockerfile | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changelog/3312.txt diff --git a/.changelog/3312.txt b/.changelog/3312.txt new file mode 100644 index 0000000000..f63948096e --- /dev/null +++ b/.changelog/3312.txt @@ -0,0 +1,7 @@ +```release-note:security +Upgrade to use Go 1.20.12. This resolves CVEs +[CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) +[CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) +[CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead +[CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git +``` \ No newline at end of file diff --git a/.go-version b/.go-version index acdfc7930c..3b9e4a0c18 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.20.10 +1.20.12 diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index c09f5ecf80..42d8e08faf 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -16,7 +16,7 @@ # go-discover builds the discover binary (which we don't currently publish # either). -FROM golang:1.19.2-alpine as go-discover +FROM golang:1.20.12-alpine as go-discover RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@214571b6a5309addf3db7775f4ee8cf4d264fd5f # dev copies the binary from a local build From d36e464df002ae93d3104478d54051cebfb6322c Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Wed, 6 Dec 2023 16:53:34 -0500 Subject: [PATCH 511/592] [NET-6395] Specify required RoleRef on RoleBinding (#3316) Specify require RoleRef on RoleBinding --- .../controllersv2/mesh_gateway_controller_test.go | 4 ++-- control-plane/gateways/role.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index a4c472fc90..cccee422a1 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -109,7 +109,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { }, expectedResult: ctrl.Result{}, postReconcile: func(t *testing.T, c client.Client) { - // Verify ClusterRole was created + // Verify Role was created key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} assert.NoError(t, c.Get(context.Background(), key, &rbacv1.Role{})) }, @@ -190,7 +190,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { }, expectedResult: ctrl.Result{}, postReconcile: func(t *testing.T, c client.Client) { - // Verify ClusterRole was created + // Verify RoleBinding was created key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} assert.NoError(t, c.Get(context.Background(), key, &rbacv1.RoleBinding{})) }, diff --git a/control-plane/gateways/role.go b/control-plane/gateways/role.go index 515ff0f3f9..0b6668595a 100644 --- a/control-plane/gateways/role.go +++ b/control-plane/gateways/role.go @@ -25,10 +25,16 @@ func (b *meshGatewayBuilder) RoleBinding() *rbacv1.RoleBinding { }, Subjects: []rbacv1.Subject{ { + APIGroup: "", Kind: rbacv1.ServiceAccountKind, Name: b.gateway.Name, Namespace: b.gateway.Namespace, }, }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: b.Role().Name, + }, } } From 2cee7db658210f93535c1ca3e57a4d970f53a94e Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 7 Dec 2023 15:23:00 -0500 Subject: [PATCH 512/592] Port: "retryOn" configuration on ServiceRouter CRD (#3308) * chore: add license to gateways files * feat(crds): add support for retryOn in service router Co-authored-by: ilpianista --------- Co-authored-by: ilpianista --- .changelog/3308.txt | 3 +++ charts/consul/templates/crd-servicerouters.yaml | 7 +++++++ control-plane/api/v1alpha1/servicerouter_types.go | 4 ++++ control-plane/api/v1alpha1/servicerouter_types_test.go | 4 ++++ control-plane/api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ .../crd/bases/consul.hashicorp.com_servicerouters.yaml | 7 +++++++ control-plane/gateways/config.go | 3 +++ control-plane/gateways/role.go | 3 +++ 8 files changed, 36 insertions(+) create mode 100644 .changelog/3308.txt diff --git a/.changelog/3308.txt b/.changelog/3308.txt new file mode 100644 index 0000000000..a7fde80332 --- /dev/null +++ b/.changelog/3308.txt @@ -0,0 +1,3 @@ +```release-note:feature +crd: adds the [`retryOn`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon) field to the ServiceRouter CRD. +``` diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index 72690c60e4..c4e06d05bc 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -148,6 +148,13 @@ spec: any existing header values of the same name. type: object type: object + retryOn: + description: RetryOn is a flat list of conditions for Consul + to retry requests based on the response from an upstream + service. + items: + type: string + type: array retryOnConnectFailure: description: RetryOnConnectFailure allows for connection failure errors to trigger a retry. diff --git a/control-plane/api/v1alpha1/servicerouter_types.go b/control-plane/api/v1alpha1/servicerouter_types.go index b322d13134..7054d9e9e2 100644 --- a/control-plane/api/v1alpha1/servicerouter_types.go +++ b/control-plane/api/v1alpha1/servicerouter_types.go @@ -151,6 +151,9 @@ type ServiceRouteDestination struct { NumRetries uint32 `json:"numRetries,omitempty"` // RetryOnConnectFailure allows for connection failure errors to trigger a retry. RetryOnConnectFailure bool `json:"retryOnConnectFailure,omitempty"` + // RetryOn is a flat list of conditions for Consul to retry requests based on the response from an upstream service. + // Refer to the valid conditions here: https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon + RetryOn []string `json:"retryOn,omitempty"` // RetryOnStatusCodes is a flat list of http response status codes that are eligible for retry. RetryOnStatusCodes []uint32 `json:"retryOnStatusCodes,omitempty"` // Allow HTTP header manipulation to be configured. @@ -355,6 +358,7 @@ func (in *ServiceRouteDestination) toConsul() *capi.ServiceRouteDestination { RequestTimeout: in.RequestTimeout.Duration, NumRetries: in.NumRetries, RetryOnConnectFailure: in.RetryOnConnectFailure, + RetryOn: in.RetryOn, RetryOnStatusCodes: in.RetryOnStatusCodes, RequestHeaders: in.RequestHeaders.toConsul(), ResponseHeaders: in.ResponseHeaders.toConsul(), diff --git a/control-plane/api/v1alpha1/servicerouter_types_test.go b/control-plane/api/v1alpha1/servicerouter_types_test.go index 97d8e2b81a..f7eb766945 100644 --- a/control-plane/api/v1alpha1/servicerouter_types_test.go +++ b/control-plane/api/v1alpha1/servicerouter_types_test.go @@ -87,6 +87,7 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, NumRetries: 1, RetryOnConnectFailure: true, + RetryOn: []string{"gateway-error"}, RetryOnStatusCodes: []uint32{500, 400}, RequestHeaders: &HTTPHeaderModifiers{ Add: map[string]string{ @@ -165,6 +166,7 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { RequestTimeout: 1 * time.Second, NumRetries: 1, RetryOnConnectFailure: true, + RetryOn: []string{"gateway-error"}, RetryOnStatusCodes: []uint32{500, 400}, RequestHeaders: &capi.HTTPHeaderModifiers{ Add: map[string]string{ @@ -291,6 +293,7 @@ func TestServiceRouter_ToConsul(t *testing.T) { RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, NumRetries: 1, RetryOnConnectFailure: true, + RetryOn: []string{"gateway-error"}, RetryOnStatusCodes: []uint32{500, 400}, RequestHeaders: &HTTPHeaderModifiers{ Add: map[string]string{ @@ -368,6 +371,7 @@ func TestServiceRouter_ToConsul(t *testing.T) { RequestTimeout: 1 * time.Second, NumRetries: 1, RetryOnConnectFailure: true, + RetryOn: []string{"gateway-error"}, RetryOnStatusCodes: []uint32{500, 400}, RequestHeaders: &capi.HTTPHeaderModifiers{ Add: map[string]string{ diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 045bd5144e..320f05510f 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -3411,6 +3411,11 @@ func (in *ServiceRouteDestination) DeepCopyInto(out *ServiceRouteDestination) { *out = *in out.IdleTimeout = in.IdleTimeout out.RequestTimeout = in.RequestTimeout + if in.RetryOn != nil { + in, out := &in.RetryOn, &out.RetryOn + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.RetryOnStatusCodes != nil { in, out := &in.RetryOnStatusCodes, &out.RetryOnStatusCodes *out = make([]uint32, len(*in)) diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 764b4614f3..b43f225249 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -144,6 +144,13 @@ spec: any existing header values of the same name. type: object type: object + retryOn: + description: RetryOn is a flat list of conditions for Consul + to retry requests based on the response from an upstream + service. + items: + type: string + type: array retryOnConnectFailure: description: RetryOnConnectFailure allows for connection failure errors to trigger a retry. diff --git a/control-plane/gateways/config.go b/control-plane/gateways/config.go index effd5ea946..9a48ec4350 100644 --- a/control-plane/gateways/config.go +++ b/control-plane/gateways/config.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import "github.com/hashicorp/consul-k8s/control-plane/api/common" diff --git a/control-plane/gateways/role.go b/control-plane/gateways/role.go index 0b6668595a..96745b8db6 100644 --- a/control-plane/gateways/role.go +++ b/control-plane/gateways/role.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import ( From ff66cfe48113e3b091613c71452ce8485d6c09a5 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Thu, 7 Dec 2023 15:31:27 -0500 Subject: [PATCH 513/592] Stub mesh configuration resource controller (#3302) --- .../templates/connect-inject-clusterrole.yaml | 2 + control-plane/api/common/common.go | 4 +- .../mesh/v2beta1/mesh_configuration_types.go | 12 +++--- .../mesh_configuration_controller.go | 43 +++++++++++++++++++ control-plane/config/rbac/role.yaml | 20 +++++++++ .../inject-connect/v2controllers.go | 18 ++++++++ 6 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 control-plane/config-entries/controllersv2/mesh_configuration_controller.go diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 1dc6d2c34a..34c87f88c6 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -98,6 +98,7 @@ rules: resources: - gatewayclassconfigs - gatewayclasses + - meshconfigurations - grpcroutes - httproutes - meshgateways @@ -116,6 +117,7 @@ rules: resources: - gatewayclassconfigs/status - gatewayclasses/status + - meshconfigurations/status - grpcroutes/status - httproutes/status - meshgateways/status diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 75343ae5f9..d810c06dcc 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -5,8 +5,9 @@ package common import ( - mapset "github.com/deckarep/golang-set" "time" + + mapset "github.com/deckarep/golang-set" ) const ( @@ -35,6 +36,7 @@ const ( MeshGateway string = "meshgateway" GatewayClass string = "gatewayclass" GatewayClassConfig string = "gatewayclassconfig" + MeshConfiguration string = "meshconfiguration" Global string = "global" Mesh string = "mesh" diff --git a/control-plane/api/mesh/v2beta1/mesh_configuration_types.go b/control-plane/api/mesh/v2beta1/mesh_configuration_types.go index 7ffba4f6d8..4bfa41b2bf 100644 --- a/control-plane/api/mesh/v2beta1/mesh_configuration_types.go +++ b/control-plane/api/mesh/v2beta1/mesh_configuration_types.go @@ -50,13 +50,13 @@ type MeshConfigurationList struct { Items []*MeshConfiguration `json:"items"` } -func (in *MeshConfiguration) ResourceID(namespace, partition string) *pbresource.ID { +func (in *MeshConfiguration) ResourceID(_, partition string) *pbresource.ID { return &pbresource.ID{ Name: in.Name, Type: pbmesh.MeshConfigurationType, Tenancy: &pbresource.Tenancy{ + // we don't pass a namespace here because MeshConfiguration is partition-scoped Partition: partition, - Namespace: namespace, // Because we are explicitly defining NS/partition, this will not default and must be explicit. // At a future point, this will move out of the Tenancy block. @@ -65,9 +65,9 @@ func (in *MeshConfiguration) ResourceID(namespace, partition string) *pbresource } } -func (in *MeshConfiguration) Resource(namespace, partition string) *pbresource.Resource { +func (in *MeshConfiguration) Resource(_, partition string) *pbresource.Resource { return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), + Id: in.ResourceID("", partition), Data: inject.ToProtoAny(&in.Spec), Metadata: meshConfigMeta(), } @@ -91,9 +91,9 @@ func (in *MeshConfiguration) Finalizers() []string { return in.ObjectMeta.Finalizers } -func (in *MeshConfiguration) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { +func (in *MeshConfiguration) MatchesConsul(candidate *pbresource.Resource, _, partition string) bool { return cmp.Equal( - in.Resource(namespace, partition), + in.Resource("", partition), candidate, protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), protocmp.IgnoreFields(&pbresource.ID{}, "uid"), diff --git a/control-plane/config-entries/controllersv2/mesh_configuration_controller.go b/control-plane/config-entries/controllersv2/mesh_configuration_controller.go new file mode 100644 index 0000000000..d485ed3e89 --- /dev/null +++ b/control-plane/config-entries/controllersv2/mesh_configuration_controller.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// MeshConfigurationController reconciles a MeshConfiguration object. +type MeshConfigurationController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshconfiguration,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshconfiguration/status,verbs=get;update;patch + +func (r *MeshConfigurationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.MeshConfiguration{}) +} + +func (r *MeshConfigurationController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *MeshConfigurationController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *MeshConfigurationController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.MeshConfiguration{}, r) +} diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index b0904211b6..652dcb8e1c 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -445,6 +445,26 @@ rules: - get - patch - update +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - meshconfiguration + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mesh.consul.hashicorp.com + resources: + - meshconfiguration/status + verbs: + - get + - patch + - update - apiGroups: - mesh.consul.hashicorp.com resources: diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index c9e77c66df..989fdf43e3 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -5,6 +5,7 @@ package connectinject import ( "context" + "github.com/hashicorp/consul-k8s/control-plane/gateways" "github.com/hashicorp/consul-server-connection-manager/discovery" ctrl "sigs.k8s.io/controller-runtime" @@ -132,6 +133,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage ConsulServerConnMgr: watcher, ConsulTenancyConfig: consulTenancyConfig, } + if err := (&controllersv2.TrafficPermissionsController{ Controller: consulResourceController, Client: mgr.GetClient(), @@ -141,6 +143,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.TrafficPermissions) return err } + if err := (&controllersv2.GRPCRouteController{ Controller: consulResourceController, Client: mgr.GetClient(), @@ -150,6 +153,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.GRPCRoute) return err } + if err := (&controllersv2.HTTPRouteController{ Controller: consulResourceController, Client: mgr.GetClient(), @@ -159,6 +163,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.HTTPRoute) return err } + if err := (&controllersv2.TCPRouteController{ Controller: consulResourceController, Client: mgr.GetClient(), @@ -168,6 +173,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.TCPRoute) return err } + if err := (&controllersv2.ProxyConfigurationController{ Controller: consulResourceController, Client: mgr.GetClient(), @@ -177,6 +183,17 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.ProxyConfiguration) return err } + + if err := (&controllersv2.MeshConfigurationController{ + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.MeshConfiguration), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.MeshConfiguration) + return err + } + if err := (&controllersv2.MeshGatewayController{ Controller: consulResourceController, Client: mgr.GetClient(), @@ -215,6 +232,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage setupLog.Error(err, "unable to create controller", "controller", common.GatewayClassConfig) return err } + if err := (&controllersv2.GatewayClassController{ Controller: consulResourceController, Client: mgr.GetClient(), From 06077b9cbc2cdf56ddb4b50b46d1bc631fec1456 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 7 Dec 2023 15:41:06 -0500 Subject: [PATCH 514/592] [NET-6769] Update GatewayClassConfig for PortModifier (#3318) * Update class config for port modifier field * Add copywrite headers * Update test for port modifier value * Add comment from PR review, setup dataplane to include the port mapping * revise setup so the wan port is the one modified * linting * Cleaning up comments and regen --- .../templates/crd-gatewayclassconfigs.yaml | 6 +++++ .../v2beta1/gateway_class_config_types.go | 3 +++ ...sul.hashicorp.com_gatewayclassconfigs.yaml | 6 +++++ control-plane/gateways/deployment.go | 15 ++++++------ .../deployment_dataplane_container.go | 23 +++++++++++++++---- .../gateway-resources/command_test.go | 4 +++- 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 2e907e4595..33d231f87b 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -107,6 +107,12 @@ spec: type: string type: object type: object + portModifier: + description: PortModifier specifies the value to be added + to every port value for listeners on this gateway. This + is generally used to avoid binding to privileged ports in + the container. + type: integer resources: description: Resources specifies the resource requirements for the created Deployment's container diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index dc2c3dbafe..eb9a2bc69c 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -89,6 +89,9 @@ type GatewayClassContainerConfig struct { Consul GatewayClassConsulConfig `json:"consul,omitempty"` // Resources specifies the resource requirements for the created Deployment's container Resources *corev1.ResourceRequirements `json:"resources,omitempty"` + // PortModifier specifies the value to be added to every port value for listeners on this gateway. + // This is generally used to avoid binding to privileged ports in the container. + PortModifier int `json:"portModifier,omitempty"` } type GatewayClassRoleConfig struct { diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index 66f687a2a5..df9ebf5a77 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -103,6 +103,12 @@ spec: type: string type: object type: object + portModifier: + description: PortModifier specifies the value to be added + to every port value for listeners on this gateway. This + is generally used to avoid binding to privileged ports in + the container. + type: integer resources: description: Resources specifies the resource requirements for the created Deployment's container diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index e638b2e0b1..15935955cd 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -4,12 +4,13 @@ package gateways import ( - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" ) @@ -35,18 +36,18 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { return nil, err } - var resources *corev1.ResourceRequirements - if b.gcc != nil && b.gcc.Spec.Deployment.Container != nil { - resources = b.gcc.Spec.Deployment.Container.Resources + var containerConfig *meshv2beta1.GatewayClassContainerConfig + if b.gcc != nil { + containerConfig = b.gcc.Spec.Deployment.Container } - container, err := consulDataplaneContainer(b.config, resources, b.gateway.Name, b.gateway.Namespace) + container, err := consulDataplaneContainer(b.config, containerConfig, b.gateway.Name, b.gateway.Namespace) if err != nil { return nil, err } return &appsv1.DeploymentSpec{ - //TODO NET-6721 + // TODO NET-6721 Replicas: deploymentReplicaCount(nil, nil), Selector: &metav1.LabelSelector{ MatchLabels: b.Labels(), @@ -143,7 +144,7 @@ func compareDeployments(a, b *appsv1.Deployment) bool { } func deploymentReplicaCount(deployment *pbmesh.Deployment, currentReplicas *int32) *int32 { - //TODO NET-6721 tamp replica count up and down based on min and max values + // TODO NET-6721 tamp replica count up and down based on min and max values instanceValue := globalDefaultInstances if currentReplicas != nil { return currentReplicas diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go index dd08a56ed7..feb2bc569b 100644 --- a/control-plane/gateways/deployment_dataplane_container.go +++ b/control-plane/gateways/deployment_dataplane_container.go @@ -7,11 +7,13 @@ import ( "fmt" "strconv" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + corev1 "k8s.io/api/core/v1" ) @@ -25,13 +27,18 @@ const ( volumeName = "consul-connect-inject-data" ) -func consulDataplaneContainer(config GatewayConfig, resources *corev1.ResourceRequirements, name, namespace string) (corev1.Container, error) { +func consulDataplaneContainer(config GatewayConfig, containerConfig *v2beta1.GatewayClassContainerConfig, name, namespace string) (corev1.Container, error) { // Extract the service account token's volume mount. var ( err error bearerTokenFile string ) + var resources *corev1.ResourceRequirements + if containerConfig != nil { + resources = containerConfig.Resources + } + if config.AuthMethod != "" { bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" } @@ -102,10 +109,16 @@ func consulDataplaneContainer(config GatewayConfig, resources *corev1.ResourceRe }) // Configure the wan port. - container.Ports = append(container.Ports, corev1.ContainerPort{ + wanPort := corev1.ContainerPort{ Name: "wan", ContainerPort: int32(constants.DefaultWANPort), - }) + } + + if containerConfig != nil { + wanPort.ContainerPort = int32(443 + containerConfig.PortModifier) + } + + container.Ports = append(container.Ports, wanPort) // Configure the resource requests and limits for the proxy if they are set. if resources != nil { diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 10ae4de8f9..8855c83736 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -373,6 +373,7 @@ var validGWConfiguration = `gatewayClassConfigs: spec: deployment: container: + portModifier: 8000 resources: requests: cpu: 200m @@ -453,7 +454,8 @@ func TestRun_loadGatewayConfigs(t *testing.T) { Spec: v2beta1.GatewayClassConfigSpec{ Deployment: v2beta1.GatewayClassDeploymentConfig{ Container: &v2beta1.GatewayClassContainerConfig{ - Resources: expectedResources, + Resources: expectedResources, + PortModifier: 8000, }, }, }, From 4dadb458ffb8b149e444453625b75b1a8750ea63 Mon Sep 17 00:00:00 2001 From: Ronald Date: Fri, 8 Dec 2023 08:31:29 -0500 Subject: [PATCH 515/592] Reduce permissions of API gateway policy (#3230) --- control-plane/api-gateway/cache/consul.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index 0e08d3bdc8..881167895f 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -29,6 +29,7 @@ func init() { type templateArgs struct { EnableNamespaces bool + APIGatewayName string } var ( @@ -41,7 +42,10 @@ mesh = "read" node_prefix "" { policy = "read" } - service_prefix "" { + service_prefix "" { + policy = "read" + } + service "{{.APIGatewayName}}" { policy = "write" } {{- if .EnableNamespaces }} @@ -334,8 +338,8 @@ func (c *Cache) Write(ctx context.Context, entry api.ConfigEntry) error { return nil } -func (c *Cache) ensurePolicy(client *api.Client) (string, error) { - policy := c.gatewayPolicy() +func (c *Cache) ensurePolicy(client *api.Client, gatewayName string) (string, error) { + policy := c.gatewayPolicy(gatewayName) created, _, err := client.ACL().PolicyCreate(&policy, &api.WriteOptions{}) @@ -352,13 +356,13 @@ func (c *Cache) ensurePolicy(client *api.Client) (string, error) { return created.ID, nil } -func (c *Cache) ensureRole(client *api.Client) (string, error) { - policyID, err := c.ensurePolicy(client) +func (c *Cache) ensureRole(client *api.Client, gatewayName string) (string, error) { + policyID, err := c.ensurePolicy(client, gatewayName) if err != nil { return "", err } - aclRoleName := "managed-gateway-acl-role" + aclRoleName := fmt.Sprint("managed-gateway-acl-role-", gatewayName) aclRole, _, err := client.ACL().RoleReadByName(aclRoleName, &api.QueryOptions{}) if err != nil { @@ -378,10 +382,11 @@ func (c *Cache) ensureRole(client *api.Client) (string, error) { return aclRoleName, err } -func (c *Cache) gatewayPolicy() api.ACLPolicy { +func (c *Cache) gatewayPolicy(gatewayName string) api.ACLPolicy { var data bytes.Buffer if err := gatewayTpl.Execute(&data, templateArgs{ EnableNamespaces: c.namespacesEnabled, + APIGatewayName: gatewayName, }); err != nil { // just panic if we can't compile the simple template // as it means something else is going severly wrong. @@ -389,7 +394,7 @@ func (c *Cache) gatewayPolicy() api.ACLPolicy { } return api.ACLPolicy{ - Name: "api-gateway-token-policy", + Name: fmt.Sprint("api-gateway-policy-for-", gatewayName), Description: "API Gateway token Policy", Rules: data.String(), } @@ -454,7 +459,7 @@ func (c *Cache) EnsureRoleBinding(authMethod, service, namespace string) error { return err } - role, err := c.ensureRole(client) + role, err := c.ensureRole(client, service) if err != nil { return ignoreACLsDisabled(err) } From e5e57b0d32837448bfa81807a0b30e2bec1859f0 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 8 Dec 2023 13:58:36 -0800 Subject: [PATCH 516/592] add github actor to be used in workflows (#3340) add github actor to workflow inputs --- .github/workflows/merge.yml | 2 +- .github/workflows/pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index e95af6cdcc..a62906a5f9 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -27,4 +27,4 @@ jobs: repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "actor":"${{ github.actor }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2959554e21..c211718a2f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -21,4 +21,4 @@ jobs: repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "actor":"${{ github.actor }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' From 3f5486177031e7a75e45885fad499195f22894ae Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Fri, 8 Dec 2023 17:56:20 -0600 Subject: [PATCH 517/592] Net 6575- Modify Gateways Cleanup Job to cleanup v2 resources (#3317) * cleanup existing gatewayclasses and gatewayclassconfigs * add tests for v2 * fixed test to actually run against v2 resources, found issue where loop was failing out early * add TODO with jira ticket * cleanup debug line * delete extra newline --- .../gateway-cleanup-clusterrole.yaml | 9 + .../consul/templates/gateway-cleanup-job.yaml | 8 +- .../subcommand/gateway-cleanup/command.go | 158 ++++++++++++++++- .../gateway-cleanup/command_test.go | 166 ++++++++++++++++++ 4 files changed, 328 insertions(+), 13 deletions(-) diff --git a/charts/consul/templates/gateway-cleanup-clusterrole.yaml b/charts/consul/templates/gateway-cleanup-clusterrole.yaml index c533a882f5..5518bfc390 100644 --- a/charts/consul/templates/gateway-cleanup-clusterrole.yaml +++ b/charts/consul/templates/gateway-cleanup-clusterrole.yaml @@ -24,6 +24,15 @@ rules: verbs: - get - delete + - apiGroups: + - mesh.consul.hashicorp.com + resources: + - gatewayclassconfigs + - gatewayclasses + - meshgateways + verbs: + - get + - delete {{- if .Values.global.enablePodSecurityPolicies }} - apiGroups: ["policy"] resources: ["podsecuritypolicies"] diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index 5f8afed3ea..0d4f84272c 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -52,10 +52,10 @@ spec: limits: memory: "50Mi" cpu: "50m" - volumeMounts: - - name: config - mountPath: /consul/config - readOnly: true + volumeMounts: + - name: config + mountPath: /consul/config + readOnly: true {{- if .Values.global.acls.tolerations }} tolerations: {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} diff --git a/control-plane/subcommand/gateway-cleanup/command.go b/control-plane/subcommand/gateway-cleanup/command.go index 583b153d01..5e01ca0d3c 100644 --- a/control-plane/subcommand/gateway-cleanup/command.go +++ b/control-plane/subcommand/gateway-cleanup/command.go @@ -8,6 +8,8 @@ import ( "errors" "flag" "fmt" + "io" + "os" "sync" "time" @@ -19,29 +21,44 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + k8syaml "sigs.k8s.io/yaml" + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) +const ( + gatewayConfigFilename = "/consul/config/config.yaml" + resourceConfigFilename = "/consul/config/resources.json" +) + type Command struct { UI cli.Ui flags *flag.FlagSet k8s *flags.K8SFlags - flagGatewayClassName string - flagGatewayClassConfigName string + flagGatewayClassName string + flagGatewayClassConfigName string + flagGatewayConfigLocation string + flagResourceConfigFileLocation string k8sClient client.Client once sync.Once help string + gatewayConfig gatewayConfig + ctx context.Context } +type gatewayConfig struct { + GatewayClassConfigs []*v2beta1.GatewayClassConfig `yaml:"gatewayClassConfigs"` +} + func (c *Command) init() { c.flags = flag.NewFlagSet("", flag.ContinueOnError) @@ -50,6 +67,12 @@ func (c *Command) init() { c.flags.StringVar(&c.flagGatewayClassConfigName, "gateway-class-config-name", "", "Name of Kubernetes GatewayClassConfig to delete.") + c.flags.StringVar(&c.flagGatewayConfigLocation, "gateway-config-file-location", gatewayConfigFilename, + "specify a different location for where the gateway config file is") + + c.flags.StringVar(&c.flagResourceConfigFileLocation, "resource-config-file-location", resourceConfigFilename, + "specify a different location for where the gateway resource config file is") + c.k8s = &flags.K8SFlags{} flags.Merge(c.flags, c.k8s.Flags()) c.help = flags.Usage(help, c.flags) @@ -93,6 +116,11 @@ func (c *Command) Run(args []string) int { return 1 } + if err := v2beta1.AddMeshToScheme(s); err != nil { + c.UI.Error(fmt.Sprintf("Could not add consul-k8s schema: %s", err)) + return 1 + } + c.k8sClient, err = client.New(config, client.Options{Scheme: s}) if err != nil { c.UI.Error(fmt.Sprintf("Error initializing Kubernetes client: %s", err)) @@ -102,32 +130,59 @@ func (c *Command) Run(args []string) int { // do the cleanup + //V1 Cleanup + err = c.deleteV1GatewayClassAndGatewayClasConfig() + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + + //V2 Cleanup + err = c.loadGatewayConfigs() + if err != nil { + + c.UI.Error(err.Error()) + return 1 + } + err = c.deleteV2GatewayClassAndClassConfigs() + if err != nil { + c.UI.Error(err.Error()) + + return 1 + } + + return 0 +} + +func (c *Command) deleteV1GatewayClassAndGatewayClasConfig() error { // find the class config and mark it for deletion first so that we // can do an early return if the gateway class isn't found config := &v1alpha1.GatewayClassConfig{} - err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassConfigName}, config) + err := c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassConfigName}, config) if err != nil { + if k8serrors.IsNotFound(err) { // no gateway class config, just ignore and return - return 0 + return nil } c.UI.Error(err.Error()) - return 1 + return err } // ignore any returned errors _ = c.k8sClient.Delete(context.Background(), config) // find the gateway class + gatewayClass := &gwv1beta1.GatewayClass{} err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassName}, gatewayClass) if err != nil { if k8serrors.IsNotFound(err) { // no gateway class, just ignore and return - return 0 + return nil } c.UI.Error(err.Error()) - return 1 + return err } // ignore any returned errors @@ -151,8 +206,7 @@ func (c *Command) Run(args []string) int { // if we failed, return 0 anyway after logging the error // since we don't want to block someone from uninstallation } - - return 0 + return nil } func (c *Command) validateFlags() error { @@ -192,3 +246,89 @@ func exponentialBackoffWithMaxIntervalAndTime() *backoff.ExponentialBackOff { backoff.Reset() return backoff } + +// loadGatewayConfigs reads and loads the configs from `/consul/config/config.yaml`, if this file does not exist nothing is done. +func (c *Command) loadGatewayConfigs() error { + file, err := os.Open(c.flagGatewayConfigLocation) + if err != nil { + if os.IsNotExist(err) { + c.UI.Warn(fmt.Sprintf("gateway configuration file not found, skipping gateway configuration, filename: %s", c.flagGatewayConfigLocation)) + return nil + } + c.UI.Error(fmt.Sprintf("Error opening gateway configuration file %s: %s", c.flagGatewayConfigLocation, err)) + return err + } + + config, err := io.ReadAll(file) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading gateway configuration file %s: %s", c.flagGatewayConfigLocation, err)) + return err + } + + err = k8syaml.Unmarshal(config, &c.gatewayConfig) + if err != nil { + c.UI.Error(fmt.Sprintf("Error decoding gateway config file: %s", err)) + return err + } + + if err := file.Close(); err != nil { + return err + } + return nil +} + +func (c *Command) deleteV2GatewayClassAndClassConfigs() error { + for _, gcc := range c.gatewayConfig.GatewayClassConfigs { + + // find the class config and mark it for deletion first so that we + // can do an early return if the gateway class isn't found + config := &v2beta1.GatewayClassConfig{} + err := c.k8sClient.Get(context.Background(), types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, config) + if err != nil { + if k8serrors.IsNotFound(err) { + // no gateway class config, just ignore and continue + continue + } + return err + } + + // ignore any returned errors + _ = c.k8sClient.Delete(context.Background(), config) + + // find the gateway class + gatewayClass := &v2beta1.GatewayClass{} + //TODO: NET-6838 To pull the GatewayClassName from the Configmap + err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, gatewayClass) + if err != nil { + if k8serrors.IsNotFound(err) { + // no gateway class, just ignore and continue + continue + } + return err + } + + // ignore any returned errors + _ = c.k8sClient.Delete(context.Background(), gatewayClass) + + // make sure they're gone + if err := backoff.Retry(func() error { + err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, config) + if err == nil || !k8serrors.IsNotFound(err) { + return errors.New("gateway class config still exists") + } + + err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, gatewayClass) + if err == nil || !k8serrors.IsNotFound(err) { + return errors.New("gateway class still exists") + } + + return nil + }, exponentialBackoffWithMaxIntervalAndTime()); err != nil { + c.UI.Error(err.Error()) + // if we failed, return 0 anyway after logging the error + // since we don't want to block someone from uninstallation + } + } + + return nil +} diff --git a/control-plane/subcommand/gateway-cleanup/command_test.go b/control-plane/subcommand/gateway-cleanup/command_test.go index 038c5b5667..69c626db6c 100644 --- a/control-plane/subcommand/gateway-cleanup/command_test.go +++ b/control-plane/subcommand/gateway-cleanup/command_test.go @@ -4,6 +4,9 @@ package gatewaycleanup import ( + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + corev1 "k8s.io/api/core/v1" + "os" "testing" "github.com/mitchellh/cli" @@ -52,6 +55,7 @@ func TestRun(t *testing.T) { s := runtime.NewScheme() require.NoError(t, gwv1beta1.Install(s)) require.NoError(t, v1alpha1.AddToScheme(s)) + require.NoError(t, v2beta1.AddMeshToScheme(s)) objs := []client.Object{} if tt.gatewayClass != nil { @@ -82,3 +86,165 @@ func TestRun(t *testing.T) { }) } } + +func TestRunV2Resources(t *testing.T) { + t.Parallel() + + for name, tt := range map[string]struct { + gatewayClassConfig []*v2beta1.GatewayClassConfig + gatewayClass []*v2beta1.GatewayClass + configMapData string + }{ + + "v2 resources exists": { + gatewayClassConfig: []*v2beta1.GatewayClassConfig{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gateway", + }, + }, + }, + gatewayClass: []*v2beta1.GatewayClass{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gateway", + }, + }, + }, + configMapData: `gatewayClassConfigs: +- apiVersion: mesh.consul.hashicorp.com/v2beta1 + kind: GatewayClassConfig + metadata: + name: test-gateway + spec: + deployment: + container: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + cpu: 200m + memory: 200Mi +`, + }, + "multiple v2 resources exists": { + gatewayClassConfig: []*v2beta1.GatewayClassConfig{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gateway", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gateway2", + }, + }, + }, + gatewayClass: []*v2beta1.GatewayClass{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gateway", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gateway2", + }, + }, + }, + configMapData: `gatewayClassConfigs: +- apiVersion: mesh.consul.hashicorp.com/v2beta1 + kind: GatewayClassConfig + metadata: + name: test-gateway + spec: + deployment: + container: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + cpu: 200m + memory: 200Mi +- apiVersion: mesh.consul.hashicorp.com/v2beta1 + kind: GatewayClassConfig + metadata: + name: test-gateway2 + spec: + deployment: + container: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + cpu: 200m + memory: 200Mi +`, + }, + "v2 emptyconfigmap": { + configMapData: "", + }, + } { + t.Run(name, func(t *testing.T) { + tt := tt + + t.Parallel() + + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v2beta1.AddMeshToScheme(s)) + require.NoError(t, corev1.AddToScheme(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + objs := []client.Object{} + for _, gatewayClass := range tt.gatewayClass { + objs = append(objs, gatewayClass) + } + for _, gatewayClassConfig := range tt.gatewayClassConfig { + objs = append(objs, gatewayClassConfig) + } + + path := createGatewayConfigFile(t, tt.configMapData, "config.yaml") + + client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + flagGatewayClassName: "gateway-class", + flagGatewayClassConfigName: "gateway-class-config", + flagGatewayConfigLocation: path, + } + + code := cmd.Run([]string{ + "-gateway-class-config-name", "gateway-class-config", + "-gateway-class-name", "gateway-class", + "-gateway-config-file-location", path, + }) + + require.Equal(t, 0, code) + }) + } +} + +func createGatewayConfigFile(t *testing.T, fileContent, filename string) string { + t.Helper() + + // create a temp file to store configuration yaml + tmpdir := t.TempDir() + file, err := os.CreateTemp(tmpdir, filename) + if err != nil { + t.Fatal(err) + } + defer file.Close() + + _, err = file.WriteString(fileContent) + if err != nil { + t.Fatal(err) + } + return file.Name() +} From 84df1ba29e013b2597af664a504cfb1369b42d36 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:16:57 -0800 Subject: [PATCH 518/592] fixed incorrect test validation with peering test (#3330) fixed flake with peering test - test was performing the wrong connection check, since http should be checking for a '403' - added cluster creation in parallel to speed up test --- .../peering_connect_namespaces_test.go | 86 +++++++++------ .../tests/peering/peering_connect_test.go | 94 +++++++++------- .../tests/peering/peering_gateway_test.go | 100 ++++++++++-------- 3 files changed, 166 insertions(+), 114 deletions(-) diff --git a/acceptance/tests/peering/peering_connect_namespaces_test.go b/acceptance/tests/peering/peering_connect_namespaces_test.go index 618248c6a9..c660bf9d9c 100644 --- a/acceptance/tests/peering/peering_connect_namespaces_test.go +++ b/acceptance/tests/peering/peering_connect_namespaces_test.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "strconv" + "sync" "testing" "time" @@ -116,50 +117,67 @@ func TestPeering_ConnectNamespaces(t *testing.T) { "dns.enableRedirection": strconv.FormatBool(cfg.EnableTransparentProxy), } - staticServerPeerHelmValues := map[string]string{ - "global.datacenter": staticServerPeer, - } + var wg sync.WaitGroup + releaseName := helpers.RandomName() - if !cfg.UseKind { - staticServerPeerHelmValues["server.replicas"] = "3" - } + var staticServerPeerCluster *consul.HelmCluster + wg.Add(1) + go func() { + staticServerPeerHelmValues := map[string]string{ + "global.datacenter": staticServerPeer, + } - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } + if !cfg.UseKind { + staticServerPeerHelmValues["server.replicas"] = "3" + } - releaseName := helpers.RandomName() + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } - helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) + helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) - // Install the first peer where static-server will be deployed in the static-server kubernetes context. - staticServerPeerCluster := consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) - staticServerPeerCluster.Create(t) + // Install the first peer where static-server will be deployed in the static-server kubernetes context. + staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) + staticServerPeerCluster.Create(t) - staticClientPeerHelmValues := map[string]string{ - "global.datacenter": staticClientPeer, - } + wg.Done() + }() - if !cfg.UseKind { - staticClientPeerHelmValues["server.replicas"] = "3" - } + var staticClientPeerCluster *consul.HelmCluster + wg.Add(1) + go func() { + staticClientPeerHelmValues := map[string]string{ + "global.datacenter": staticClientPeer, + } - if cfg.UseKind { - staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } + if !cfg.UseKind { + staticClientPeerHelmValues["server.replicas"] = "3" + } + + if cfg.UseKind { + staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } + + helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) + + // Install the second peer where static-client will be deployed in the static-client kubernetes context. + staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) + staticClientPeerCluster.Create(t) - helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) + wg.Done() + }() - // Install the second peer where static-client will be deployed in the static-client kubernetes context. - staticClientPeerCluster := consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) - staticClientPeerCluster.Create(t) + // Wait for the clusters to start up + logger.Log(t, "waiting for clusters to start up . . .") + wg.Wait() // Create Mesh resource to use mesh gateways. logger.Log(t, "creating mesh config") diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index 60d038d5b8..144bb26495 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "strconv" + "sync" "testing" "time" @@ -61,6 +62,7 @@ func TestPeering_Connect(t *testing.T) { staticServerPeerClusterContext := env.DefaultContext(t) staticClientPeerClusterContext := env.Context(t, 1) + // Create Clusters starting with our first cluster commonHelmValues := map[string]string{ "global.peering.enabled": "true", @@ -78,53 +80,69 @@ func TestPeering_Connect(t *testing.T) { "dns.enableRedirection": strconv.FormatBool(cfg.EnableTransparentProxy), } - staticServerPeerHelmValues := map[string]string{ - "global.datacenter": staticServerPeer, - "terminatingGateways.enabled": "true", - "terminatingGateways.gateways[0].name": "terminating-gateway", - "terminatingGateways.gateways[0].replicas": "1", - } + var wg sync.WaitGroup + releaseName := helpers.RandomName() - if !cfg.UseKind { - staticServerPeerHelmValues["server.replicas"] = "3" - } + var staticServerPeerCluster *consul.HelmCluster + wg.Add(1) + go func() { + staticServerPeerHelmValues := map[string]string{ + "global.datacenter": staticServerPeer, + "terminatingGateways.enabled": "true", + "terminatingGateways.gateways[0].name": "terminating-gateway", + "terminatingGateways.gateways[0].replicas": "1", + } - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } + if !cfg.UseKind { + staticServerPeerHelmValues["server.replicas"] = "3" + } - releaseName := helpers.RandomName() + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } - helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) + helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) - // Install the first peer where static-server will be deployed in the static-server kubernetes context. - staticServerPeerCluster := consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) - staticServerPeerCluster.Create(t) + // Install the first peer where static-server will be deployed in the static-server kubernetes context. + staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) + staticServerPeerCluster.Create(t) + wg.Done() + }() - staticClientPeerHelmValues := map[string]string{ - "global.datacenter": staticClientPeer, - } + var staticClientPeerCluster *consul.HelmCluster + wg.Add(1) + go func() { + // Create a second cluster to be peered with + staticClientPeerHelmValues := map[string]string{ + "global.datacenter": staticClientPeer, + } - if !cfg.UseKind { - staticClientPeerHelmValues["server.replicas"] = "3" - } + if !cfg.UseKind { + staticClientPeerHelmValues["server.replicas"] = "3" + } - if cfg.UseKind { - staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } + if cfg.UseKind { + staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } + + helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) - helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) + // Install the second peer where static-client will be deployed in the static-client kubernetes context. + staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) + staticClientPeerCluster.Create(t) + wg.Done() + }() - // Install the second peer where static-client will be deployed in the static-client kubernetes context. - staticClientPeerCluster := consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) - staticClientPeerCluster.Create(t) + // Wait for the clusters to start up + logger.Log(t, "waiting for clusters to start up . . .") + wg.Wait() // Create Mesh resource to use mesh gateways. logger.Log(t, "creating mesh config") @@ -350,7 +368,7 @@ func TestPeering_Connect(t *testing.T) { // If ACLs are enabled, test that deny intentions prevent connections. if c.ACLsEnabled { logger.Log(t, "testing intentions prevent connections through the terminating gateway") - k8s.CheckStaticServerConnectionFailing(t, staticClientOpts, staticClientName, externalServerHostnameURL) + k8s.CheckStaticServerHTTPConnectionFailing(t, staticClientOpts, staticClientName, externalServerHostnameURL) logger.Log(t, "adding intentions to allow traffic from client ==> server") terminatinggateway.AddIntention(t, staticServerPeerClient, staticClientPeer, "", staticClientName, "", externalServerHostnameID) diff --git a/acceptance/tests/peering/peering_gateway_test.go b/acceptance/tests/peering/peering_gateway_test.go index 3ba04980a9..5ccb2b3461 100644 --- a/acceptance/tests/peering/peering_gateway_test.go +++ b/acceptance/tests/peering/peering_gateway_test.go @@ -6,6 +6,7 @@ package peering import ( "context" "fmt" + "sync" "testing" "time" @@ -63,50 +64,65 @@ func TestPeering_Gateway(t *testing.T) { "dns.enabled": "true", } - staticServerPeerHelmValues := map[string]string{ - "global.datacenter": staticServerPeer, - } - - if !cfg.UseKind { - staticServerPeerHelmValues["server.replicas"] = "3" - } - - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } - + var wg sync.WaitGroup releaseName := helpers.RandomName() - helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) - - // Install the first peer where static-server will be deployed in the static-server kubernetes context. - staticServerPeerCluster := consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) - staticServerPeerCluster.Create(t) - - staticClientPeerHelmValues := map[string]string{ - "global.datacenter": staticClientPeer, - } - - if !cfg.UseKind { - staticClientPeerHelmValues["server.replicas"] = "3" - } - - if cfg.UseKind { - staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } - - helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) - - // Install the second peer where static-client will be deployed in the static-client kubernetes context. - staticClientPeerCluster := consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) - staticClientPeerCluster.Create(t) + var staticServerPeerCluster *consul.HelmCluster + wg.Add(1) + go func() { + staticServerPeerHelmValues := map[string]string{ + "global.datacenter": staticServerPeer, + } + + if !cfg.UseKind { + staticServerPeerHelmValues["server.replicas"] = "3" + } + + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } + + helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) + + // Install the first peer where static-server will be deployed in the static-server kubernetes context. + staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) + staticServerPeerCluster.Create(t) + wg.Done() + }() + + var staticClientPeerCluster *consul.HelmCluster + wg.Add(1) + go func() { + staticClientPeerHelmValues := map[string]string{ + "global.datacenter": staticClientPeer, + } + + if !cfg.UseKind { + staticClientPeerHelmValues["server.replicas"] = "3" + } + + if cfg.UseKind { + staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } + + helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) + + // Install the second peer where static-client will be deployed in the static-client kubernetes context. + staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) + staticClientPeerCluster.Create(t) + wg.Done() + }() + + // Wait for the clusters to start up + logger.Log(t, "waiting for clusters to start up . . .") + wg.Wait() // Create Mesh resource to use mesh gateways. logger.Log(t, "creating mesh config") From 653a4c046609a17fbef076d5d861f5213445d36a Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:31:26 -0600 Subject: [PATCH 519/592] Add `consul.hashicorp.com/proxy-config-map` annotation. (#3347) This introduces an annotation that allows for generically setting data in the opaque config map for proxy service registrations. Use of this annotation is not encouraged in most situations, but it is necessary for certain circumstances. Notably, this new annotation allows users to specify the `xds_fetch_timeout_ms` configuration during service registration, which is sometimes needed by proxies with a large number of upstreams. --- .changelog/3347.txt | 3 ++ .../constants/annotations_and_labels.go | 5 ++ .../endpoints/endpoints_controller.go | 50 ++++++++++++------- .../endpoints/endpoints_controller_test.go | 36 ++++++++++--- 4 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 .changelog/3347.txt diff --git a/.changelog/3347.txt b/.changelog/3347.txt new file mode 100644 index 0000000000..0be1d2be2b --- /dev/null +++ b/.changelog/3347.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: Add new `consul.hashicorp.com/proxy-config-map` annotation that allows for setting values in the opaque config map for proxy service registrations. +``` diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 241be331be..cb74939359 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -73,6 +73,11 @@ const ( // connections to. AnnotationPort = "consul.hashicorp.com/connect-service-port" + // AnnotationProxyConfigMap allows for default values to be set in the opaque config map + // during proxy registration. The value for this annotation is expected to be valid json. + // Other annotations / configuration may overwrite the values in the map. + AnnotationProxyConfigMap = "consul.hashicorp.com/proxy-config-map" + // AnnotationUpstreams is a list of upstreams to register with the // proxy in the format of `:,...`. The // service name should map to a Consul service name and the local port diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index e9994516ad..7b630e49f5 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -398,6 +398,18 @@ func proxyServiceID(pod corev1.Pod, serviceEndpoints corev1.Endpoints) string { return fmt.Sprintf("%s-%s", pod.Name, proxySvcName) } +func annotationProxyConfigMap(pod corev1.Pod) (map[string]any, error) { + parsed := make(map[string]any) + if config, ok := pod.Annotations[constants.AnnotationProxyConfigMap]; ok && config != "" { + err := json.Unmarshal([]byte(config), &parsed) + if err != nil { + // Always return an empty map on error + return make(map[string]any), fmt.Errorf("unable to parse `%v` annotation for pod `%v`: %w", constants.AnnotationProxyConfigMap, pod.Name, err) + } + } + return parsed, nil +} + // createServiceRegistrations creates the service and proxy service instance registrations with the information from the // Pod. func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints corev1.Endpoints, healthStatus string) (*api.CatalogRegistration, *api.CatalogRegistration, error) { @@ -485,10 +497,16 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints proxySvcName := proxyServiceName(pod, serviceEndpoints) proxySvcID := proxyServiceID(pod, serviceEndpoints) + + // Set the default values from the annotation, if possible. + baseConfig, err := annotationProxyConfigMap(pod) + if err != nil { + r.Log.Error(err, "annotation unable to be applied") + } proxyConfig := &api.AgentServiceConnectProxyConfig{ DestinationServiceName: svcName, DestinationServiceID: svcID, - Config: make(map[string]interface{}), + Config: baseConfig, } // If metrics are enabled, the proxyConfig should set envoy_prometheus_bind_addr to a listener on 0.0.0.0 on @@ -692,12 +710,18 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints constants.MetaKeyPodUID: string(pod.UID), } + // Set the default values from the annotation, if possible. + baseConfig, err := annotationProxyConfigMap(pod) + if err != nil { + r.Log.Error(err, "annotation unable to be applied") + } + service := &api.AgentService{ ID: pod.Name, Address: pod.Status.PodIP, Meta: meta, Proxy: &api.AgentServiceConnectProxyConfig{ - Config: map[string]interface{}{}, + Config: baseConfig, }, } @@ -771,14 +795,10 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints Port: wanPort, }, } - service.Proxy = &api.AgentServiceConnectProxyConfig{ - Config: map[string]interface{}{ - "envoy_gateway_no_default_bind": true, - "envoy_gateway_bind_addresses": map[string]interface{}{ - "all-interfaces": map[string]interface{}{ - "address": "0.0.0.0", - }, - }, + service.Proxy.Config["envoy_gateway_no_default_bind"] = true + service.Proxy.Config["envoy_gateway_bind_addresses"] = map[string]interface{}{ + "all-interfaces": map[string]interface{}{ + "address": "0.0.0.0", }, } @@ -787,15 +807,7 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints } if r.MetricsConfig.DefaultEnableMetrics && r.MetricsConfig.EnableGatewayMetrics { - if pod.Annotations[constants.AnnotationGatewayKind] == ingressGateway { - service.Proxy.Config["envoy_prometheus_bind_addr"] = fmt.Sprintf("%s:20200", pod.Status.PodIP) - } else { - service.Proxy = &api.AgentServiceConnectProxyConfig{ - Config: map[string]interface{}{ - "envoy_prometheus_bind_addr": fmt.Sprintf("%s:20200", pod.Status.PodIP), - }, - } - } + service.Proxy.Config["envoy_prometheus_bind_addr"] = fmt.Sprintf("%s:20200", pod.Status.PodIP) } if r.EnableTelemetryCollector && service.Proxy != nil && service.Proxy.Config != nil { diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 4c92892367..4e03313ff0 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -15,6 +15,7 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -986,6 +987,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, k8sObjects: func() []runtime.Object { pod1 := createServicePod("pod1", "1.2.3.4", true, true) + pod1.Annotations[constants.AnnotationProxyConfigMap] = `{ "xds_fetch_timeout_ms": 9999 }` endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-created", @@ -1034,7 +1036,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, - Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, + Config: map[string]any{ + "envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject"), + "xds_fetch_timeout_ms": float64(9999), + }, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, @@ -1079,6 +1084,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.AnnotationGatewayWANAddress: "2.3.4.5", constants.AnnotationGatewayWANPort: "443", constants.AnnotationMeshGatewayContainerPort: "8443", + constants.AnnotationProxyConfigMap: `{ "xds_fetch_timeout_ms": 9999 }`, constants.AnnotationGatewayKind: meshGateway}) endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ @@ -1121,7 +1127,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, }, ServiceProxy: &api.AgentServiceConnectProxyConfig{ - Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/service")}, + Config: map[string]any{ + "envoy_telemetry_collector_bind_socket_dir": string("/consul/service"), + "xds_fetch_timeout_ms": float64(9999), + }, }, NodeMeta: map[string]string{ "synthetic-node": "true", @@ -1295,6 +1304,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { gateway := createGatewayPod("terminating-gateway", "1.2.3.4", map[string]string{ constants.AnnotationGatewayKind: terminatingGateway, constants.AnnotationGatewayConsulServiceName: "terminating-gateway", + constants.AnnotationProxyConfigMap: `{ "xds_fetch_timeout_ms": 9999 }`, }) endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ @@ -1334,7 +1344,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{ - Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/service")}, + Config: map[string]any{ + "envoy_telemetry_collector_bind_socket_dir": string("/consul/service"), + "xds_fetch_timeout_ms": float64(9999), + }, }, }, }, @@ -1427,6 +1440,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.AnnotationGatewayKind: ingressGateway, constants.AnnotationGatewayWANSource: "Service", constants.AnnotationGatewayWANPort: "8443", + constants.AnnotationProxyConfigMap: `{ "xds_fetch_timeout_ms": 9999 }`, }) endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ @@ -1502,6 +1516,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, }, "envoy_telemetry_collector_bind_socket_dir": "/consul/service", + "xds_fetch_timeout_ms": float64(9999), }, }, }, @@ -3558,8 +3573,11 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Register service and proxy in consul. for _, svc := range tt.initialConsulSvcs { - _, err := consulClient.Catalog().Register(svc, nil) - require.NoError(t, err) + // Retry because ACLs may not have been initialized yet. + retry.Run(t, func(r *retry.R) { + _, err := consulClient.Catalog().Register(svc, nil) + require.NoError(r, err) + }) // Create a token for this service if ACLs are enabled. if tt.enableACLs { @@ -4207,6 +4225,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { // Register service and proxy in consul var token *api.ACLToken + var err error for _, svc := range tt.initialConsulSvcs { serviceRegistration := &api.CatalogRegistration{ Node: consulNodeName, @@ -4216,8 +4235,11 @@ func TestReconcileDeleteEndpoint(t *testing.T) { }, Service: svc, } - _, err := consulClient.Catalog().Register(serviceRegistration, nil) - require.NoError(t, err) + // Retry because the ACLs may not have been fully initialized yet. + retry.Run(t, func(r *retry.R) { + _, err = consulClient.Catalog().Register(serviceRegistration, nil) + require.NoError(r, err) + }) // Create a token for it if ACLs are enabled. if tt.enableACLs { From dfb08ed7ca1749d7dcb6e0ac65b53709d029cd11 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Mon, 11 Dec 2023 14:50:53 -0500 Subject: [PATCH 520/592] [NET-6588] Remove abandoned virtual nodes from Consul Catalog (#3307) --- .changelog/3307.txt | 3 ++ .../endpoints/endpoints_controller.go | 43 ++++++++++++++++++- .../endpoints/endpoints_controller_test.go | 5 ++- 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 .changelog/3307.txt diff --git a/.changelog/3307.txt b/.changelog/3307.txt new file mode 100644 index 0000000000..835d3a726f --- /dev/null +++ b/.changelog/3307.txt @@ -0,0 +1,3 @@ +```release-note:bug-fix +control-plane: Remove virtual nodes in the Consul Catalog when they do not have any services listed. +``` diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 7b630e49f5..6c21868670 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -292,7 +292,7 @@ func (r *Controller) registerServicesAndHealthCheck(apiClient *api.Client, pod c // Register the service instance with Consul. r.Log.Info("registering service with Consul", "name", serviceRegistration.Service.Service, - "id", serviceRegistration.ID) + "id", serviceRegistration.Service.ID) _, err = apiClient.Catalog().Register(serviceRegistration, nil) if err != nil { r.Log.Error(err, "failed to register service", "name", serviceRegistration.Service.Service) @@ -308,7 +308,7 @@ func (r *Controller) registerServicesAndHealthCheck(apiClient *api.Client, pod c } // Register the proxy service instance with Consul. - r.Log.Info("registering proxy service with Consul", "name", proxyServiceRegistration.Service.Service) + r.Log.Info("registering proxy service with Consul", "name", proxyServiceRegistration.Service.Service, "id", proxyServiceRegistration.Service.ID) _, err = apiClient.Catalog().Register(proxyServiceRegistration, nil) if err != nil { r.Log.Error(err, "failed to register proxy service", "name", proxyServiceRegistration.Service.Service) @@ -987,6 +987,14 @@ func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvc errs = multierror.Append(errs, err) } } + + if serviceDeregistered { + err = r.deregisterNode(apiClient, nodeSvcs.Node.Node) + if err != nil { + r.Log.Error(err, "failed to deregister node", "svc", svc.Service) + errs = multierror.Append(errs, err) + } + } } } @@ -994,6 +1002,37 @@ func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvc } +// deregisterNode removes a node if it does not have any associated services attached to it. +// When using Consul Enterprise, serviceInstancesForK8SServiceNameAndNamespace will search across all namespaces +// (wildcard) to determine if there any other services associated with the Node. We also only search for nodes that have +// the correct kubernetes metadata (managed-by-endpoints-controller and synthetic-node). +func (r *Controller) deregisterNode(apiClient *api.Client, nodeName string) error { + var ( + serviceList *api.CatalogNodeServiceList + err error + ) + + filter := fmt.Sprintf(`Meta[%q] == %q and Meta[%q] == %q`, + "synthetic-node", "true", metaKeyManagedBy, constants.ManagedByValue) + if r.EnableConsulNamespaces { + serviceList, _, err = apiClient.Catalog().NodeServiceList(nodeName, &api.QueryOptions{Filter: filter, Namespace: namespaces.WildcardNamespace}) + } else { + serviceList, _, err = apiClient.Catalog().NodeServiceList(nodeName, &api.QueryOptions{Filter: filter}) + } + if err != nil { + return fmt.Errorf("failed to get a list of node services: %s", err) + } + + if len(serviceList.Services) == 0 { + r.Log.Info("deregistering node from consul", "node", nodeName, "services", serviceList.Services) + _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{Node: nodeName}, nil) + if err != nil { + r.Log.Error(err, "failed to deregister node", "name", nodeName) + } + } + return nil +} + // deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. // It will only check for ACL tokens that have been created with the auth method this controller // has been configured with and will only delete tokens for the provided podName and podUID. diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 4e03313ff0..bd5393b96a 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -3567,6 +3567,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { } }) consulClient := testClient.APIClient + // Wait so that bootstrap finishes + testClient.TestServer.WaitForActiveCARoot(t) // Holds token accessorID for each service ID. tokensForServices := make(map[string]string) @@ -4221,7 +4223,8 @@ func TestReconcileDeleteEndpoint(t *testing.T) { } }) consulClient := testClient.APIClient - // TODO: stabilize this test by waiting for the ACL bootstrap + // Wait so that bootstrap finishes + testClient.TestServer.WaitForActiveCARoot(t) // Register service and proxy in consul var token *api.ACLToken From 856ade70c85edd235c753d59b83516b2ef842cd0 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Tue, 12 Dec 2023 14:08:34 -0500 Subject: [PATCH 521/592] Add validation that externalServers.hosts is not set to HCP-managed cluster's addresses when global.cloud.enabled (#3315) --- .changelog/3315.txt | 3 +++ .../templates/connect-inject-deployment.yaml | 3 +++ .../test/unit/connect-inject-deployment.bats | 24 +++++++++++++++++++ charts/consul/values.yaml | 8 +++++-- 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 .changelog/3315.txt diff --git a/.changelog/3315.txt b/.changelog/3315.txt new file mode 100644 index 0000000000..b0c1729b1f --- /dev/null +++ b/.changelog/3315.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters +``` \ No newline at end of file diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 53f894035a..2fafae7df1 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -6,6 +6,9 @@ {{ template "consul.validateVaultWebhookCertConfiguration" . }} {{- template "consul.reservedNamesFailer" (list .Values.connectInject.consulNamespaces.consulDestinationNamespace "connectInject.consulNamespaces.consulDestinationNamespace") }} {{- if and .Values.externalServers.enabled (not .Values.externalServers.hosts) }}{{ fail "externalServers.hosts must be set if externalServers.enabled is true" }}{{ end -}} +{{- if and .Values.externalServers.enabled .Values.global.cloud.enabled }} + {{- if and (gt (len .Values.externalServers.hosts) 0) (regexMatch ".+.hashicorp.cloud$" ( first .Values.externalServers.hosts )) }}{{fail "global.cloud.enabled cannot be used in combination with an HCP-managed cluster address in externalServers.hosts. global.cloud.enabled is for linked self-managed clusters."}}{{- end }} +{{- end }} {{- if and .Values.externalServers.skipServerWatch (not .Values.externalServers.enabled) }}{{ fail "externalServers.enabled must be set if externalServers.skipServerWatch is true" }}{{ end -}} {{- $dnsEnabled := (or (and (ne (.Values.dns.enabled | toString) "-") .Values.dns.enabled) (and (eq (.Values.dns.enabled | toString) "-") .Values.connectInject.transparentProxy.defaultEnabled)) -}} {{- $dnsRedirectionEnabled := (or (and (ne (.Values.dns.enableRedirection | toString) "-") .Values.dns.enableRedirection) (and (eq (.Values.dns.enableRedirection | toString) "-") .Values.connectInject.transparentProxy.defaultEnabled)) -}} diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 748b75de5d..67be4474ea 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -2639,6 +2639,30 @@ reservedNameTest() { [ "${actual}" = "true" ] } +@test "connectInject/Deployment: validates that externalServers.hosts is not set with an HCP-managed cluster's address" { + cd `chart_dir` + run helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'global.enabled=false' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=abc.aws.hashicorp.cloud' \ + --set 'global.cloud.enabled=true' \ + --set 'global.cloud.clientId.secretName=client-id-name' \ + --set 'global.cloud.clientId.secretKey=client-id-key' \ + --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ + --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ + --set 'global.cloud.resourceId.secretName=resource-id-name' \ + --set 'global.cloud.resourceId.secretKey=resource-id-key' \ + . > /dev/stderr + + [ "$status" -eq 1 ] + + [[ "$output" =~ "global.cloud.enabled cannot be used in combination with an HCP-managed cluster address in externalServers.hosts. global.cloud.enabled is for linked self-managed clusters." ]] +} + @test "connectInject/Deployment: can provide a TLS server name for the sidecar-injector when global.cloud.enabled is set" { cd `chart_dir` local env=$(helm template \ diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 49d986cd7d..16ae7f8bc4 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -655,8 +655,12 @@ global: # Enables installing an HCP Consul Central self-managed cluster. # Requires Consul v1.14+. cloud: - # If true, the Helm chart will enable the installation of an HCP Consul Central - # self-managed cluster. + # If true, the Helm chart will link a [self-managed cluster to HCP](https://developer.hashicorp.com/hcp/docs/consul/self-managed). + # This can either be used to [configure a new cluster](https://developer.hashicorp.com/hcp/docs/consul/self-managed/new) + # or [link an existing one](https://developer.hashicorp.com/hcp/docs/consul/self-managed/existing). + # + # Note: this setting should not be enabled for [HashiCorp-managed clusters](https://developer.hashicorp.com/hcp/docs/consul/hcp-managed). + # It is strictly for linking self-managed clusters. enabled: false # The resource id of the HCP Consul Central cluster to link to. Eg: From 9b516ecdb9ab3708b39b5cf1feedc0647347a690 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Tue, 12 Dec 2023 16:26:08 -0500 Subject: [PATCH 522/592] [NET-6723] Node Selector Support for Mesh GW (#3329) * Read in node selector object * Ensure node selector is part of deployment * cleaning up implementation after testing * Remove custom unmarshal, fix chart to handle incoming yaml string * Update control-plane/api/mesh/v2beta1/gateway_class_config_types.go Co-authored-by: Thomas Eckert * Update comments and use `toJson` over `toYaml` to avoid worrying about indentation * gofmt * Fix type for test * Fix deployment of gateway class and gatewayclass config * add comment for common constants * linting --------- Co-authored-by: Thomas Eckert --- .../templates/crd-gatewayclassconfigs.yaml | 93 ++----------------- .../gateway-resources-configmap.yaml | 4 +- control-plane/api/common/common.go | 2 + .../v2beta1/gateway_class_config_types.go | 9 +- .../api/mesh/v2beta1/zz_generated.deepcopy.go | 6 +- .../controllersv2/mesh_gateway_controller.go | 25 +++-- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 93 ++----------------- control-plane/gateways/deployment.go | 8 +- control-plane/gateways/deployment_test.go | 5 +- .../subcommand/gateway-resources/command.go | 8 +- .../gateway-resources/command_test.go | 7 ++ 11 files changed, 62 insertions(+), 198 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 33d231f87b..1edc3de8ad 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -254,92 +254,15 @@ spec: type: object type: object nodeSelector: - description: NodeSelector specifies the node selector to use on - the created Deployment - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The TopologySelectorTerm - type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements by - node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector applies - to. - type: string - operator: - description: Represents a key's relationship to - a set of values. Valid operators are In, NotIn, - Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array must - be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator - is Gt or Lt, the values array must have a single - element, which will be interpreted as an integer. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements by - node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector applies - to. - type: string - operator: - description: Represents a key's relationship to - a set of values. Valid operators are In, NotIn, - Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array must - be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator - is Gt or Lt, the values array must have a single - element, which will be interpreted as an integer. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - type: array - required: - - nodeSelectorTerms + additionalProperties: + type: string + description: 'NodeSelector is a feature that constrains the scheduling + of a pod to nodes that match specified labels. By defining NodeSelector + in a pod''s configuration, you can ensure that the pod is only + scheduled to nodes with the corresponding labels, providing + a way to influence the placement of workloads based on node + attributes. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' type: object - x-kubernetes-map-type: atomic priorityClassName: description: PriorityClassName specifies the priority class name to use on the created Deployment diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index cf20a32d26..4d66e52838 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -54,8 +54,8 @@ data: logging: level: {{ default .Values.global.logLevel .Values.meshGateway.logLevel }} resources: {{ toJson .Values.meshGateway.initServiceInitContainer.resources }} - {{- if .Values.meshGateway.nodeSelector }} - nodeSelector: {{ toJson .Values.meshGateway.nodeSelector }} + {{- with .Values.meshGateway.nodeSelector }} + nodeSelector: {{ fromYaml . | toJson }} {{- end }} priorityClassName: {{ toJson .Values.meshGateway.priorityClassName }} replicas: diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index d810c06dcc..53d4c42e96 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -11,6 +11,8 @@ import ( ) const ( + // NOTE: these are only used in consul types, they do not map to k8s kinds. + // V1 config entries. ServiceDefaults string = "servicedefaults" ProxyDefaults string = "proxydefaults" diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index eb9a2bc69c..cded2b18ab 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -52,8 +52,13 @@ type GatewayClassDeploymentConfig struct { Container *GatewayClassContainerConfig `json:"container,omitempty"` // InitContainer contains config specific to the created Deployment's init container InitContainer *GatewayClassInitContainerConfig `json:"initContainer,omitempty"` - // NodeSelector specifies the node selector to use on the created Deployment - NodeSelector *corev1.NodeSelector `json:"nodeSelector,omitempty"` + // NodeSelector is a feature that constrains the scheduling of a pod to nodes that + // match specified labels. + // By defining NodeSelector in a pod's configuration, you can ensure that the pod is + // only scheduled to nodes with the corresponding labels, providing a way to + // influence the placement of workloads based on node attributes. + // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + NodeSelector map[string]string `json:"nodeSelector,omitempty"` // PriorityClassName specifies the priority class name to use on the created Deployment PriorityClassName string `json:"priorityClassName,omitempty"` // Replicas specifies the configuration to control the number of replicas for the created Deployment diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index 4e53b52ccc..7ba0a78919 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -333,8 +333,10 @@ func (in *GatewayClassDeploymentConfig) DeepCopyInto(out *GatewayClassDeployment } if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector - *out = new(v1.NodeSelector) - (*in).DeepCopyInto(*out) + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } if in.Replicas != nil { in, out := &in.Replicas, &out.Replicas diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index 6ce9baecba..62f202757f 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -19,8 +19,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/gateways" ) @@ -93,7 +91,7 @@ func (r *MeshGatewayController) SetupWithManager(mgr ctrl.Manager) error { // 4. Role // 5. RoleBinding func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { - //fetch gatewayclassconfig + // fetch gatewayclassconfig gcc, err := r.getGatewayClassConfigForGateway(ctx, resource) if err != nil { r.Log.Error(err, "unable to get gatewayclassconfig for gateway: %s gatewayclass: %s", resource.Name, resource.Spec.GatewayClassName) @@ -112,14 +110,14 @@ func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Req return fmt.Errorf("unable to create service account: %w", err) } - //Create Role + // Create Role err = r.opIfNewOrOwned(ctx, resource, &rbacv1.Role{}, builder.Role(), upsertOp) if err != nil { return fmt.Errorf("unable to create role: %w", err) } - //Create RoleBinding + // Create RoleBinding err = r.opIfNewOrOwned(ctx, resource, &rbacv1.RoleBinding{}, builder.RoleBinding(), upsertOp) if err != nil { @@ -128,7 +126,7 @@ func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Req // TODO NET-6393 - //Create deployment + // Create deployment mergeDeploymentOp := func(ctx context.Context, existingObject, object client.Object) error { existingDeployment, ok := existingObject.(*appsv1.Deployment) @@ -181,7 +179,6 @@ type ownedObjectOp func(ctx context.Context, existingObject client.Object, newOb // The purpose of opIfNewOrOwned is to ensure that we aren't updating or deleting a // resource that was not created by us. If this scenario is encountered, we error. func (r *MeshGatewayController) opIfNewOrOwned(ctx context.Context, gateway *meshv2beta1.MeshGateway, scanTarget, writeSource client.Object, op ownedObjectOp) error { - // Ensure owner reference is always set on objects that we write if err := ctrl.SetControllerReference(gateway, writeSource, r.Client.Scheme()); err != nil { return err @@ -227,26 +224,25 @@ func (r *MeshGatewayController) getGatewayClassConfigForGateway(ctx context.Cont if err != nil { return nil, err } + gatewayClassConfig, err := r.getConfigForGatewayClass(ctx, gatewayClass) if err != nil { return nil, err } return gatewayClassConfig, nil - } -func (r *MeshGatewayController) getConfigForGatewayClass(ctx context.Context, gatewayClassConfig *meshv2beta1.GatewayClass) (*meshv2beta1.GatewayClassConfig, error) { - if gatewayClassConfig == nil { +func (r *MeshGatewayController) getConfigForGatewayClass(ctx context.Context, gatewayClass *meshv2beta1.GatewayClass) (*meshv2beta1.GatewayClassConfig, error) { + if gatewayClass == nil { // if we don't have a gateway class we can't fetch the corresponding config return nil, nil } config := &meshv2beta1.GatewayClassConfig{} - if ref := gatewayClassConfig.Spec.ParametersRef; ref != nil { - if ref.Group != meshv2beta1.MeshGroup || - ref.Kind != common.GatewayClassConfig { - //TODO @Gateway-Management additionally check for controller name when available + if ref := gatewayClass.Spec.ParametersRef; ref != nil { + if ref.Group != meshv2beta1.MeshGroup || ref.Kind != "GatewayClassConfig" { + // TODO @Gateway-Management additionally check for controller name when available return nil, nil } @@ -259,6 +255,7 @@ func (r *MeshGatewayController) getConfigForGatewayClass(ctx context.Context, ga func (r *MeshGatewayController) getGatewayClassForGateway(ctx context.Context, gateway *meshv2beta1.MeshGateway) (*meshv2beta1.GatewayClass, error) { var gatewayClass meshv2beta1.GatewayClass + if err := r.Client.Get(ctx, types.NamespacedName{Name: string(gateway.Spec.GatewayClassName)}, &gatewayClass); err != nil { return nil, client.IgnoreNotFound(err) } diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index df9ebf5a77..aab856de9e 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -250,92 +250,15 @@ spec: type: object type: object nodeSelector: - description: NodeSelector specifies the node selector to use on - the created Deployment - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The - terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The TopologySelectorTerm - type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements by - node's labels. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector applies - to. - type: string - operator: - description: Represents a key's relationship to - a set of values. Valid operators are In, NotIn, - Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array must - be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator - is Gt or Lt, the values array must have a single - element, which will be interpreted as an integer. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements by - node's fields. - items: - description: A node selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: The label key that the selector applies - to. - type: string - operator: - description: Represents a key's relationship to - a set of values. Valid operators are In, NotIn, - Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array must - be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator - is Gt or Lt, the values array must have a single - element, which will be interpreted as an integer. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - type: array - required: - - nodeSelectorTerms + additionalProperties: + type: string + description: 'NodeSelector is a feature that constrains the scheduling + of a pod to nodes that match specified labels. By defining NodeSelector + in a pod''s configuration, you can ensure that the pod is only + scheduled to nodes with the corresponding labels, providing + a way to influence the placement of workloads based on node + attributes. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' type: object - x-kubernetes-map-type: atomic priorityClassName: description: PriorityClassName specifies the priority class name to use on the created Deployment diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 15935955cd..6111b8084d 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -46,6 +46,12 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { return nil, err } + var nodeSelector map[string]string + + if b.gcc != nil && b.gcc.Spec.Deployment.NodeSelector != nil { + nodeSelector = b.gcc.Spec.Deployment.NodeSelector + } + return &appsv1.DeploymentSpec{ // TODO NET-6721 Replicas: deploymentReplicaCount(nil, nil), @@ -89,7 +95,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { }, }, }, - NodeSelector: nil, + NodeSelector: nodeSelector, Tolerations: nil, ServiceAccountName: b.serviceAccountName(), }, diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index dd679caa8a..836e49a179 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -6,7 +6,6 @@ package gateways import ( "testing" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -14,6 +13,8 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" ) @@ -41,6 +42,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { gcc: &meshv2beta1.GatewayClassConfig{ Spec: meshv2beta1.GatewayClassConfigSpec{ Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, Replicas: &meshv2beta1.GatewayClassReplicasConfig{ Default: pointer.Int32(1), Min: pointer.Int32(1), @@ -250,6 +252,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { TTY: false, }, }, + NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, Affinity: &corev1.Affinity{ NodeAffinity: nil, PodAffinity: nil, diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 0f256479e6..6328331d9a 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -345,11 +345,7 @@ func (c *Command) validateFlags() error { return fmt.Errorf("error decoding node selector: %w", err) } } - if c.flagNodeSelector != "" { - if err := yaml.Unmarshal([]byte(c.flagNodeSelector), &c.nodeSelector); err != nil { - return fmt.Errorf("error decoding node selector: %w", err) - } - } + if c.flagServiceAnnotations != "" { if err := yaml.Unmarshal([]byte(c.flagServiceAnnotations), &c.serviceAnnotations); err != nil { return fmt.Errorf("error decoding service annotations: %w", err) @@ -441,7 +437,7 @@ func (c *Command) createV2GatewayClassAndClassConfigs(ctx context.Context, compo ControllerName: controllerName, ParametersRef: &meshv2beta1.ParametersReference{ Group: v2beta1.MeshGroup, - Kind: "GatewayClass", + Kind: "GatewayClassConfig", Namespace: &cfg.Namespace, Name: cfg.Name, }, diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 8855c83736..bc4a39e9ca 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -372,6 +372,9 @@ var validGWConfiguration = `gatewayClassConfigs: name: consul-mesh-gateway spec: deployment: + nodeSelector: + beta.kubernetes.io/arch: amd64 + beta.kubernetes.io/os: linux container: portModifier: 8000 resources: @@ -453,6 +456,10 @@ func TestRun_loadGatewayConfigs(t *testing.T) { }, Spec: v2beta1.GatewayClassConfigSpec{ Deployment: v2beta1.GatewayClassDeploymentConfig{ + NodeSelector: map[string]string{ + "beta.kubernetes.io/arch": "amd64", + "beta.kubernetes.io/os": "linux", + }, Container: &v2beta1.GatewayClassContainerConfig{ Resources: expectedResources, PortModifier: 8000, From 7a97beb5b96f88715f70e2e38ea8c976495ecfcd Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Wed, 13 Dec 2023 13:01:39 -0500 Subject: [PATCH 523/592] Declare ownership of Deployment, Role and RoleBinding on MeshGateway controller (#3364) * Declare ownership of Deployment, Role and RoleBinding on MeshGateway controller * Add Service ownership declaration --- .../config-entries/controllersv2/mesh_gateway_controller.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index 62f202757f..a953d6d603 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -78,6 +78,10 @@ func (r *MeshGatewayController) UpdateStatus(ctx context.Context, obj client.Obj func (r *MeshGatewayController) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&meshv2beta1.MeshGateway{}). + Owns(&appsv1.Deployment{}). + Owns(&rbacv1.Role{}). + Owns(&rbacv1.RoleBinding{}). + Owns(&corev1.Service{}). Owns(&corev1.ServiceAccount{}). Complete(r) } From 7693e98ce375da4abced171cc35a48c7072578a3 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 13 Dec 2023 13:01:50 -0500 Subject: [PATCH 524/592] Use retry test context within retry blocks (#3289) Replace usage of t with r for handling retries --- acceptance/framework/cli/cli.go | 4 +-- .../framework/connhelper/connect_helper.go | 2 +- acceptance/framework/consul/cli_cluster.go | 5 +--- acceptance/framework/consul/helm_cluster.go | 28 ++++++++--------- .../framework/consul/helm_cluster_test.go | 7 +++-- .../framework/environment/environment.go | 30 ++++++++----------- acceptance/framework/helpers/helpers.go | 10 +++---- acceptance/framework/k8s/deploy.go | 2 +- acceptance/framework/k8s/helpers.go | 6 ++-- acceptance/framework/k8s/kubectl.go | 7 +++-- acceptance/framework/logger/logger.go | 25 +++++++--------- .../framework/portforward/port_forward.go | 2 +- acceptance/framework/vault/vault_cluster.go | 13 ++++---- acceptance/go.mod | 4 ++- acceptance/go.sum | 4 +-- acceptance/tests/cli/cli_install_test.go | 4 +-- .../config_entries_namespaces_test.go | 2 +- .../config-entries/config_entries_test.go | 12 ++++---- .../connect/connect_proxy_lifecycle_test.go | 23 +++++++------- .../tests/connect/local_rate_limit_test.go | 4 +-- .../tests/consul-dns/consul_dns_test.go | 2 +- acceptance/tests/metrics/metrics_test.go | 2 +- .../peering_connect_namespaces_test.go | 2 +- .../tests/peering/peering_connect_test.go | 2 +- .../tests/peering/peering_gateway_test.go | 2 +- acceptance/tests/sameness/sameness_test.go | 16 +++++----- .../snapshot_agent_k8s_secret_test.go | 4 +-- .../snapshot_agent_vault_test.go | 4 +-- acceptance/tests/sync/sync_catalog_test.go | 6 ++-- .../wan-federation/wan_federation_test.go | 4 +-- control-plane/go.mod | 2 ++ control-plane/go.sum | 4 +-- .../create-federation-secret/command_test.go | 2 +- .../subcommand/sync-catalog/command_test.go | 4 +-- 34 files changed, 122 insertions(+), 128 deletions(-) diff --git a/acceptance/framework/cli/cli.go b/acceptance/framework/cli/cli.go index 11a158269f..f9384cd8d5 100644 --- a/acceptance/framework/cli/cli.go +++ b/acceptance/framework/cli/cli.go @@ -7,10 +7,10 @@ import ( "fmt" "os/exec" "strings" - "testing" "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/sdk/testutil" ) const ( @@ -28,7 +28,7 @@ func NewCLI() (*CLI, error) { } // Run runs the CLI with the given args. -func (c *CLI) Run(t *testing.T, options *k8s.KubectlOptions, args ...string) ([]byte, error) { +func (c *CLI) Run(t testutil.TestingTB, options *k8s.KubectlOptions, args ...string) ([]byte, error) { if !c.initialized { return nil, fmt.Errorf("CLI must be initialized before calling Run, use `cli.NewCLI()` to initialize.") } diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 3f978e1d30..bbcaf7aff9 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -171,7 +171,7 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond}, t, func(r *retry.R) { for _, labelSelector := range []string{"app=static-server", "app=static-client"} { - podList, err := c.Ctx.KubernetesClient(t).CoreV1(). + podList, err := c.Ctx.KubernetesClient(r).CoreV1(). Pods(opts.Namespace). List(context.Background(), metav1.ListOptions{ LabelSelector: labelSelector, diff --git a/acceptance/framework/consul/cli_cluster.go b/acceptance/framework/consul/cli_cluster.go index a45bcc665c..f960a4a612 100644 --- a/acceptance/framework/consul/cli_cluster.go +++ b/acceptance/framework/consul/cli_cluster.go @@ -264,10 +264,7 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool, release ...str // Retry creating the port forward since it can fail occasionally. retry.RunWith(&retry.Counter{Wait: 3 * time.Second, Count: 60}, t, func(r *retry.R) { - // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry - // because we're using ForwardPortE (not ForwardPort) so the `t` won't - // get used to fail the test, just for logging. - require.NoError(r, tunnel.ForwardPortE(t)) + require.NoError(r, tunnel.ForwardPortE(r)) }) t.Cleanup(func() { diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 492c7ec513..e13c366335 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -200,7 +200,7 @@ func (h *HelmCluster) Destroy(t *testing.T) { } retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 30}, t, func(r *retry.R) { - err := helm.DeleteE(t, h.helmOptions, h.releaseName, false) + err := helm.DeleteE(r, h.helmOptions, h.releaseName, false) require.NoError(r, err) }) @@ -211,77 +211,77 @@ func (h *HelmCluster) Destroy(t *testing.T) { // graceful termination takes a long time and since this is an uninstall // we don't care that they're stopped gracefully. pods, err := h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(t, err) + require.NoError(r, err) for _, pod := range pods.Items { if strings.Contains(pod.Name, h.releaseName) { var gracePeriod int64 = 0 err := h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod}) if !errors.IsNotFound(err) { - require.NoError(t, err) + require.NoError(r, err) } } } // Delete PVCs. err = h.kubernetesClient.CoreV1().PersistentVolumeClaims(h.helmOptions.KubectlOptions.Namespace).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(t, err) + require.NoError(r, err) // Delete any serviceaccounts that have h.releaseName in their name. sas, err := h.kubernetesClient.CoreV1().ServiceAccounts(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(t, err) + require.NoError(r, err) for _, sa := range sas.Items { if strings.Contains(sa.Name, h.releaseName) { err := h.kubernetesClient.CoreV1().ServiceAccounts(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), sa.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(t, err) + require.NoError(r, err) } } } // Delete any roles that have h.releaseName in their name. roles, err := h.kubernetesClient.RbacV1().Roles(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(t, err) + require.NoError(r, err) for _, role := range roles.Items { if strings.Contains(role.Name, h.releaseName) { err := h.kubernetesClient.RbacV1().Roles(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), role.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(t, err) + require.NoError(r, err) } } } // Delete any rolebindings that have h.releaseName in their name. roleBindings, err := h.kubernetesClient.RbacV1().RoleBindings(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(t, err) + require.NoError(r, err) for _, roleBinding := range roleBindings.Items { if strings.Contains(roleBinding.Name, h.releaseName) { err := h.kubernetesClient.RbacV1().RoleBindings(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), roleBinding.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(t, err) + require.NoError(r, err) } } } // Delete any secrets that have h.releaseName in their name. secrets, err := h.kubernetesClient.CoreV1().Secrets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{}) - require.NoError(t, err) + require.NoError(r, err) for _, secret := range secrets.Items { if strings.Contains(secret.Name, h.releaseName) { err := h.kubernetesClient.CoreV1().Secrets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), secret.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(t, err) + require.NoError(r, err) } } } // Delete any jobs that have h.releaseName in their name. jobs, err := h.kubernetesClient.BatchV1().Jobs(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(t, err) + require.NoError(r, err) for _, job := range jobs.Items { if strings.Contains(job.Name, h.releaseName) { err := h.kubernetesClient.BatchV1().Jobs(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), job.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(t, err) + require.NoError(r, err) } } } diff --git a/acceptance/framework/consul/helm_cluster_test.go b/acceptance/framework/consul/helm_cluster_test.go index 011ca23e0f..3544718c9e 100644 --- a/acceptance/framework/consul/helm_cluster_test.go +++ b/acceptance/framework/consul/helm_cluster_test.go @@ -9,6 +9,7 @@ import ( "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" @@ -78,16 +79,16 @@ func (c *ctx) Name() string { return "" } -func (c *ctx) KubectlOptions(_ *testing.T) *k8s.KubectlOptions { +func (c *ctx) KubectlOptions(_ testutil.TestingTB) *k8s.KubectlOptions { return &k8s.KubectlOptions{} } func (c *ctx) KubectlOptionsForNamespace(ns string) *k8s.KubectlOptions { return &k8s.KubectlOptions{} } -func (c *ctx) KubernetesClient(_ *testing.T) kubernetes.Interface { +func (c *ctx) KubernetesClient(_ testutil.TestingTB) kubernetes.Interface { return fake.NewSimpleClientset() } -func (c *ctx) ControllerRuntimeClient(_ *testing.T) client.Client { +func (c *ctx) ControllerRuntimeClient(_ testutil.TestingTB) client.Client { return runtimefake.NewClientBuilder().Build() } diff --git a/acceptance/framework/environment/environment.go b/acceptance/framework/environment/environment.go index 7014a3c05f..9ceecf3e96 100644 --- a/acceptance/framework/environment/environment.go +++ b/acceptance/framework/environment/environment.go @@ -5,11 +5,10 @@ package environment import ( "fmt" - "testing" - "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -27,18 +26,17 @@ const ( // TestEnvironment represents the infrastructure environment of the test, // such as the kubernetes cluster(s) the test is running against. type TestEnvironment interface { - DefaultContext(t *testing.T) TestContext - Context(t *testing.T, index int) TestContext + DefaultContext(t testutil.TestingTB) TestContext + Context(t testutil.TestingTB, index int) TestContext } // TestContext represents a specific context a test needs, // for example, information about a specific Kubernetes cluster. type TestContext interface { - KubectlOptions(t *testing.T) *k8s.KubectlOptions - // TODO: I don't love this. + KubectlOptions(t testutil.TestingTB) *k8s.KubectlOptions KubectlOptionsForNamespace(ns string) *k8s.KubectlOptions - KubernetesClient(t *testing.T) kubernetes.Interface - ControllerRuntimeClient(t *testing.T) client.Client + KubernetesClient(t testutil.TestingTB) kubernetes.Interface + ControllerRuntimeClient(t testutil.TestingTB) client.Client } type KubernetesEnvironment struct { @@ -66,13 +64,13 @@ func NewKubernetesEnvironmentFromConfig(config *config.TestConfig) *KubernetesEn return kenv } -func (k *KubernetesEnvironment) Context(t *testing.T, index int) TestContext { +func (k *KubernetesEnvironment) Context(t testutil.TestingTB, index int) TestContext { lenContexts := len(k.contexts) require.Greater(t, lenContexts, index, fmt.Sprintf("context list does not contain an index %d, length is %d", index, lenContexts)) return k.contexts[index] } -func (k *KubernetesEnvironment) DefaultContext(t *testing.T) TestContext { +func (k *KubernetesEnvironment) DefaultContext(t testutil.TestingTB) TestContext { lenContexts := len(k.contexts) require.Greater(t, lenContexts, DefaultContextIndex, fmt.Sprintf("context list does not contain an index %d, length is %d", DefaultContextIndex, lenContexts)) return k.contexts[DefaultContextIndex] @@ -92,9 +90,7 @@ type kubernetesContext struct { // KubernetesContextFromOptions returns the Kubernetes context from options. // If context is explicitly set in options, it returns that context. // Otherwise, it returns the current context. -func KubernetesContextFromOptions(t *testing.T, options *k8s.KubectlOptions) string { - t.Helper() - +func KubernetesContextFromOptions(t testutil.TestingTB, options *k8s.KubectlOptions) string { // First, check if context set in options and return that if options.ContextName != "" { return options.ContextName @@ -110,7 +106,7 @@ func KubernetesContextFromOptions(t *testing.T, options *k8s.KubectlOptions) str return rawConfig.CurrentContext } -func (k kubernetesContext) KubectlOptions(t *testing.T) *k8s.KubectlOptions { +func (k kubernetesContext) KubectlOptions(t testutil.TestingTB) *k8s.KubectlOptions { if k.options != nil { return k.options } @@ -149,7 +145,7 @@ func (k kubernetesContext) KubectlOptionsForNamespace(ns string) *k8s.KubectlOpt } // KubernetesClientFromOptions takes KubectlOptions and returns Kubernetes API client. -func KubernetesClientFromOptions(t *testing.T, options *k8s.KubectlOptions) kubernetes.Interface { +func KubernetesClientFromOptions(t testutil.TestingTB, options *k8s.KubectlOptions) kubernetes.Interface { configPath, err := options.GetConfigPath(t) require.NoError(t, err) @@ -162,7 +158,7 @@ func KubernetesClientFromOptions(t *testing.T, options *k8s.KubectlOptions) kube return client } -func (k kubernetesContext) KubernetesClient(t *testing.T) kubernetes.Interface { +func (k kubernetesContext) KubernetesClient(t testutil.TestingTB) kubernetes.Interface { if k.client != nil { return k.client } @@ -172,7 +168,7 @@ func (k kubernetesContext) KubernetesClient(t *testing.T) kubernetes.Interface { return k.client } -func (k kubernetesContext) ControllerRuntimeClient(t *testing.T) client.Client { +func (k kubernetesContext) ControllerRuntimeClient(t testutil.TestingTB) client.Client { if k.runtimeClient != nil { return k.runtimeClient } diff --git a/acceptance/framework/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index 6d2641ecd3..2ef01df26a 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -18,6 +18,7 @@ import ( "github.com/gruntwork-io/terratest/modules/random" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -43,7 +44,7 @@ func CheckForPriorInstallations(t *testing.T, client kubernetes.Interface, optio // NOTE: It's okay to pass in `t` to RunHelmCommandAndGetOutputE despite being in a retry // because we're using RunHelmCommandAndGetOutputE (not RunHelmCommandAndGetOutput) so the `t` won't // get used to fail the test, just for logging. - helmListOutput, err = helm.RunHelmCommandAndGetOutputE(t, options, "list", "--output", "json") + helmListOutput, err = helm.RunHelmCommandAndGetOutputE(r, options, "list", "--output", "json") require.NoError(r, err) }) @@ -84,10 +85,9 @@ func SetupInterruptHandler(cleanup func()) { }() } -// Cleanup will both register a cleanup function with t -// and SetupInterruptHandler to make sure resources get cleaned up -// if an interrupt signal is caught. -func Cleanup(t *testing.T, noCleanupOnFailure bool, noCleanup bool, cleanup func()) { +// Cleanup will both register a cleanup function with t and SetupInterruptHandler to make sure resources +// get cleaned up if an interrupt signal is caught. +func Cleanup(t testutil.TestingTB, noCleanupOnFailure bool, noCleanup bool, cleanup func()) { t.Helper() // Always clean up when an interrupt signal is caught. diff --git a/acceptance/framework/k8s/deploy.go b/acceptance/framework/k8s/deploy.go index e60f4c4730..a877dff54e 100644 --- a/acceptance/framework/k8s/deploy.go +++ b/acceptance/framework/k8s/deploy.go @@ -135,7 +135,7 @@ func CheckStaticServerConnectionMultipleFailureMessages(t *testing.T, options *k args = append(args, curlArgs...) retry.RunWith(retrier, t, func(r *retry.R) { - output, err := RunKubectlAndGetOutputE(t, options, args...) + output, err := RunKubectlAndGetOutputE(r, options, args...) if expectSuccess { require.NoError(r, err) require.Contains(r, output, expectedOutput) diff --git a/acceptance/framework/k8s/helpers.go b/acceptance/framework/k8s/helpers.go index a6b4d1ca7c..58990d73ce 100644 --- a/acceptance/framework/k8s/helpers.go +++ b/acceptance/framework/k8s/helpers.go @@ -114,8 +114,8 @@ func ServiceHost(t *testing.T, cfg *config.TestConfig, ctx environment.TestConte // It can take some time for the load balancers to be ready and have an IP/Hostname. // Wait for 5 minutes before failing. retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { - svc, err := ctx.KubernetesClient(t).CoreV1().Services(ctx.KubectlOptions(t).Namespace).Get(context.Background(), serviceName, metav1.GetOptions{}) - require.NoError(t, err) + svc, err := ctx.KubernetesClient(r).CoreV1().Services(ctx.KubectlOptions(r).Namespace).Get(context.Background(), serviceName, metav1.GetOptions{}) + require.NoError(r, err) require.NotEmpty(r, svc.Status.LoadBalancer.Ingress) // On AWS, load balancers have a hostname for ingress, while on Azure and GCP // load balancers have IPs. @@ -135,7 +135,7 @@ func CopySecret(t *testing.T, sourceContext, destContext environment.TestContext var secret *corev1.Secret var err error retry.Run(t, func(r *retry.R) { - secret, err = sourceContext.KubernetesClient(t).CoreV1().Secrets(sourceContext.KubectlOptions(t).Namespace).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = sourceContext.KubernetesClient(r).CoreV1().Secrets(sourceContext.KubectlOptions(r).Namespace).Get(context.Background(), secretName, metav1.GetOptions{}) secret.ResourceVersion = "" require.NoError(r, err) }) diff --git a/acceptance/framework/k8s/kubectl.go b/acceptance/framework/k8s/kubectl.go index 2e37b9bd0a..e02db4c474 100644 --- a/acceptance/framework/k8s/kubectl.go +++ b/acceptance/framework/k8s/kubectl.go @@ -13,6 +13,7 @@ import ( terratestLogger "github.com/gruntwork-io/terratest/modules/logger" "github.com/gruntwork-io/terratest/modules/shell" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" ) @@ -34,14 +35,14 @@ var kubeAPIConnectErrs = []string{ // RunKubectlAndGetOutputE runs an arbitrary kubectl command provided via args // and returns its output and error. -func RunKubectlAndGetOutputE(t *testing.T, options *k8s.KubectlOptions, args ...string) (string, error) { +func RunKubectlAndGetOutputE(t testutil.TestingTB, options *k8s.KubectlOptions, args ...string) (string, error) { return RunKubectlAndGetOutputWithLoggerE(t, options, terratestLogger.New(logger.TestLogger{}), args...) } // RunKubectlAndGetOutputWithLoggerE is the same as RunKubectlAndGetOutputE but // it also allows you to provide a custom logger. This is useful if the command output // contains sensitive information, for example, when you can pass logger.Discard. -func RunKubectlAndGetOutputWithLoggerE(t *testing.T, options *k8s.KubectlOptions, logger *terratestLogger.Logger, args ...string) (string, error) { +func RunKubectlAndGetOutputWithLoggerE(t testutil.TestingTB, options *k8s.KubectlOptions, logger *terratestLogger.Logger, args ...string) (string, error) { var cmdArgs []string if options.ContextName != "" { cmdArgs = append(cmdArgs, "--context", options.ContextName) @@ -67,7 +68,7 @@ func RunKubectlAndGetOutputWithLoggerE(t *testing.T, options *k8s.KubectlOptions var output string var err error retry.RunWith(counter, t, func(r *retry.R) { - output, err = shell.RunCommandAndGetOutputE(t, command) + output, err = shell.RunCommandAndGetOutputE(r, command) if err != nil { // Want to retry on errors connecting to actual Kube API because // these are intermittent. diff --git a/acceptance/framework/logger/logger.go b/acceptance/framework/logger/logger.go index 26777c4e22..34bc0e8a6c 100644 --- a/acceptance/framework/logger/logger.go +++ b/acceptance/framework/logger/logger.go @@ -5,20 +5,19 @@ package logger import ( "fmt" - "testing" + "github.com/hashicorp/consul/sdk/testutil" "time" - terratestTesting "github.com/gruntwork-io/terratest/modules/testing" + terratesting "github.com/gruntwork-io/terratest/modules/testing" ) -// TestLogger implements terratest's TestLogger interface -// so that we can pass it to terratest objects to have consistent logging -// across all tests. +// TestLogger implements Terratest's TestLogger interface so that we can pass it to Terratest objects to have consistent +// logging across all tests. type TestLogger struct{} // Logf takes a format string and args and calls Logf function. -func (tl TestLogger) Logf(t terratestTesting.TestingT, format string, args ...interface{}) { - tt, ok := t.(*testing.T) +func (tl TestLogger) Logf(t terratesting.TestingT, format string, args ...any) { + tt, ok := t.(testutil.TestingTB) if !ok { t.Error("failed to cast") } @@ -27,20 +26,18 @@ func (tl TestLogger) Logf(t terratestTesting.TestingT, format string, args ...in Logf(tt, format, args...) } -// Logf takes a format string and args and logs -// formatted string with a timestamp. -func Logf(t *testing.T, format string, args ...interface{}) { +// Logf takes a format string and args and logs formatted string with a timestamp. +func Logf(t testutil.TestingTB, format string, args ...any) { t.Helper() log := fmt.Sprintf(format, args...) Log(t, log) } -// Log calls t.Log, adding an RFC3339 timestamp to the beginning of the log line. -func Log(t *testing.T, args ...interface{}) { +// Log calls t.Log or r.Log, adding an RFC3339 timestamp to the beginning of the log line. +func Log(t testutil.TestingTB, args ...any) { t.Helper() - - allArgs := []interface{}{time.Now().Format(time.RFC3339)} + allArgs := []any{time.Now().Format(time.RFC3339)} allArgs = append(allArgs, args...) t.Log(allArgs...) } diff --git a/acceptance/framework/portforward/port_forward.go b/acceptance/framework/portforward/port_forward.go index 3242541cc5..728c7b7891 100644 --- a/acceptance/framework/portforward/port_forward.go +++ b/acceptance/framework/portforward/port_forward.go @@ -31,7 +31,7 @@ func CreateTunnelToResourcePort(t *testing.T, resourceName string, remotePort in // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry // because we're using ForwardPortE (not ForwardPort) so the `t` won't // get used to fail the test, just for logging. - require.NoError(r, tunnel.ForwardPortE(t)) + require.NoError(r, tunnel.ForwardPortE(r)) }) doneChan := make(chan bool) diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index 8b82e41841..b43957c924 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" vapi "github.com/hashicorp/vault/api" "github.com/stretchr/testify/require" @@ -124,7 +125,7 @@ func NewVaultCluster(t *testing.T, ctx environment.TestContext, cfg *config.Test func (v *VaultCluster) VaultClient(*testing.T) *vapi.Client { return v.vaultClient } // SetupVaultClient sets up and returns a Vault Client. -func (v *VaultCluster) SetupVaultClient(t *testing.T) *vapi.Client { +func (v *VaultCluster) SetupVaultClient(t testutil.TestingTB) *vapi.Client { t.Helper() if v.vaultClient != nil { @@ -144,12 +145,8 @@ func (v *VaultCluster) SetupVaultClient(t *testing.T) *vapi.Client { remotePort, v.logger) - // Retry creating the port forward since it can fail occasionally. retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { - // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry - // because we're using ForwardPortE (not ForwardPort) so the `t` won't - // get used to fail the test, just for logging. - require.NoError(r, tunnel.ForwardPortE(t)) + require.NoError(r, tunnel.ForwardPortE(r)) }) t.Cleanup(func() { @@ -201,7 +198,7 @@ func (v *VaultCluster) bootstrap(t *testing.T, vaultNamespace string) { }, Type: corev1.SecretTypeServiceAccountToken, }, metav1.CreateOptions{}) - require.NoError(t, err) + require.NoError(r, err) } }) v.ConfigureAuthMethod(t, v.vaultClient, "kubernetes", "https://kubernetes.default.svc", vaultServerServiceAccountName, namespace) @@ -422,7 +419,7 @@ func (v *VaultCluster) initAndUnseal(t *testing.T) { require.Equal(r, corev1.PodRunning, serverPod.Status.Phase) // Set up the client so that we can make API calls to initialize and unseal. - v.vaultClient = v.SetupVaultClient(t) + v.vaultClient = v.SetupVaultClient(r) // Initialize Vault with 1 secret share. We don't need to // more key shares for this test installation. diff --git a/acceptance/go.mod b/acceptance/go.mod index b34fcf246c..60d1fb1d8e 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -2,7 +2,10 @@ module github.com/hashicorp/consul-k8s/acceptance go 1.20 +replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c + require ( + github.com/google/uuid v1.3.0 github.com/gruntwork-io/terratest v0.31.2 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 @@ -62,7 +65,6 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 219f7a08ec..984eca3676 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -394,8 +394,8 @@ github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= -github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= -github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= +github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c h1:j89V5jn4/XAYDLbhQNqV0WeMSzEOUa3asaFceXrL70M= +github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= 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= diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index c8b66ff451..cfee1560ae 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -70,11 +70,11 @@ func TestInstall(t *testing.T) { retrier := &retry.Timer{Timeout: 160 * time.Second, Wait: 2 * time.Second} retry.RunWith(retrier, t, func(r *retry.R) { for podName := range list { - out, err := cli.Run(t, ctx.KubectlOptions(t), "proxy", "read", podName) + out, err := cli.Run(r, ctx.KubectlOptions(r), "proxy", "read", podName) require.NoError(r, err) output := string(out) - logger.Log(t, output) + r.Log(output) // Both proxies must see their own local agent and app as clusters. require.Regexp(r, "consul-dataplane.*STATIC", output) diff --git a/acceptance/tests/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index a013c99e09..aa74bdc2b5 100644 --- a/acceptance/tests/config-entries/config_entries_namespaces_test.go +++ b/acceptance/tests/config-entries/config_entries_namespaces_test.go @@ -128,7 +128,7 @@ func TestControllerNamespaces(t *testing.T) { // Retry the kubectl apply because we've seen sporadic // "connection refused" errors where the mutating webhook // endpoint fails initially. - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-n", KubeNS, "-k", "../fixtures/cases/crds-ent") + out, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "apply", "-n", KubeNS, "-k", "../fixtures/cases/crds-ent") require.NoError(r, err, out) // NOTE: No need to clean up because the namespace will be deleted. }) diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index 81b0a75ff4..9f2595ed4f 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -85,13 +85,13 @@ func TestController(t *testing.T) { // Retry the kubectl apply because we've seen sporadic // "connection refused" errors where the mutating webhook // endpoint fails initially. - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/crds-oss") + out, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "apply", "-k", "../fixtures/bases/crds-oss") require.NoError(r, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/crds-oss") - }) + }) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/crds-oss") }) // On startup, the controller can take upwards of 1m to perform diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go index 53c48281fd..7a4b3b383f 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -111,7 +111,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { "static-server", "static-server-sidecar-proxy", } { - logger.Logf(t, "checking for %s service in Consul catalog", name) + logger.Logf(r, "checking for %s service in Consul catalog", name) instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) r.Check(err) @@ -158,7 +158,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { // Ensure outbound requests are still successful during grace // period. retry.RunWith(&retry.Timer{Timeout: time.Duration(gracePeriodSeconds) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { - output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) + output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), args...) require.NoError(r, err) require.Condition(r, func() bool { exists := false @@ -175,7 +175,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { } else { // Ensure outbound requests fail because proxy has terminated retry.RunWith(&retry.Timer{Timeout: time.Duration(terminationGracePeriod) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { - output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) + output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), args...) require.Error(r, err) require.Condition(r, func() bool { exists := false @@ -193,7 +193,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { "static-client", "static-client-sidecar-proxy", } { - logger.Logf(t, "checking for %s service in Consul catalog", name) + logger.Logf(r, "checking for %s service in Consul catalog", name) instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) r.Check(err) @@ -212,7 +212,6 @@ func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { cfg := suite.Config() if cfg.EnableTransparentProxy { - // TODO t-eckert: Come back and review this with wise counsel. t.Skip("Skipping test because transparent proxy is enabled") } @@ -249,7 +248,7 @@ func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { "static-server", "static-server-sidecar-proxy", } { - logger.Logf(t, "checking for %s service in Consul catalog", name) + logger.Logf(r, "checking for %s service in Consul catalog", name) instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) r.Check(err) @@ -270,7 +269,7 @@ func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { "job-client", "job-client-sidecar-proxy", } { - logger.Logf(t, "checking for %s service in Consul catalog", name) + logger.Logf(r, "checking for %s service in Consul catalog", name) instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) r.Check(err) @@ -312,7 +311,7 @@ func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { if gracePeriod > 0 { logger.Log(t, "Checking if connection successful within grace period...") retry.RunWith(&retry.Timer{Timeout: time.Duration(gracePeriod) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { - output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) + output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), args...) require.NoError(r, err) require.True(r, !strings.Contains(output, "curl: (7) Failed to connect")) }) @@ -323,15 +322,15 @@ func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { // Test that requests fail once grace period has ended, or there was no grace period set. logger.Log(t, "Checking that requests fail now that proxy is killed...") retry.RunWith(&retry.Timer{Timeout: 2 * time.Minute, Wait: 2 * time.Second}, t, func(r *retry.R) { - output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) + output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), args...) require.Error(r, err) require.True(r, strings.Contains(output, "curl: (7) Failed to connect")) }) // Wait for the job to complete. retry.RunWith(&retry.Timer{Timeout: 4 * time.Minute, Wait: 30 * time.Second}, t, func(r *retry.R) { - logger.Log(t, "Checking if job completed...") - jobs, err := ctx.KubernetesClient(t).BatchV1().Jobs(ns).List( + logger.Log(r, "Checking if job completed...") + jobs, err := ctx.KubernetesClient(r).BatchV1().Jobs(ns).List( context.Background(), metav1.ListOptions{ LabelSelector: "app=job-client", @@ -363,7 +362,7 @@ func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { "job-client", "job-client-sidecar-proxy", } { - logger.Logf(t, "checking for %s service in Consul catalog", name) + logger.Logf(r, "checking for %s service in Consul catalog", name) instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) r.Check(err) diff --git a/acceptance/tests/connect/local_rate_limit_test.go b/acceptance/tests/connect/local_rate_limit_test.go index 715444cf0f..928028e049 100644 --- a/acceptance/tests/connect/local_rate_limit_test.go +++ b/acceptance/tests/connect/local_rate_limit_test.go @@ -102,12 +102,12 @@ func assertRateLimits(t *testing.T, opts *assertRateLimitOptions, addr string) { // Make up to the allowed numbers of calls in a second t0 := time.Now() - output, err := k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, append(args, repeatAddr)...) + output, err := k8s.RunKubectlAndGetOutputE(r, opts.k8sOpts, append(args, repeatAddr)...) require.NoError(r, err) require.Contains(r, output, opts.successOutput) // Exceed the configured rate limit. - output, err = k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, append(args, addr)...) + output, err = k8s.RunKubectlAndGetOutputE(r, opts.k8sOpts, append(args, addr)...) require.True(r, time.Since(t0) < time.Second, "failed to make all requests within one second window") if opts.enforced { require.Error(r, err) diff --git a/acceptance/tests/consul-dns/consul_dns_test.go b/acceptance/tests/consul-dns/consul_dns_test.go index 84741175af..f67ac96bd3 100644 --- a/acceptance/tests/consul-dns/consul_dns_test.go +++ b/acceptance/tests/consul-dns/consul_dns_test.go @@ -74,7 +74,7 @@ func TestConsulDNS(t *testing.T) { }) retry.Run(t, func(r *retry.R) { - logs, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), dnsTestPodArgs...) + logs, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), dnsTestPodArgs...) require.NoError(r, err) // When the `dig` request is successful, a section of it's response looks like the following: diff --git a/acceptance/tests/metrics/metrics_test.go b/acceptance/tests/metrics/metrics_test.go index ca80d38b75..ec2c4c48dc 100644 --- a/acceptance/tests/metrics/metrics_test.go +++ b/acceptance/tests/metrics/metrics_test.go @@ -133,7 +133,7 @@ func TestAppMetrics(t *testing.T) { // Retry because sometimes the merged metrics server takes a couple hundred milliseconds // to start. retry.RunWith(&retry.Counter{Count: 20, Wait: 2 * time.Second}, t, func(r *retry.R) { - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) require.NoError(r, err) // This assertion represents the metrics from the envoy sidecar. require.Contains(r, metricsOutput, `envoy_cluster_assignment_stale{local_cluster="server",consul_source_service="server"`) diff --git a/acceptance/tests/peering/peering_connect_namespaces_test.go b/acceptance/tests/peering/peering_connect_namespaces_test.go index c660bf9d9c..6b3ef75278 100644 --- a/acceptance/tests/peering/peering_connect_namespaces_test.go +++ b/acceptance/tests/peering/peering_connect_namespaces_test.go @@ -222,7 +222,7 @@ func TestPeering_ConnectNamespaces(t *testing.T) { // Ensure the secret is created. retry.RunWith(timer, t, func(r *retry.R) { - acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(t, staticClientPeerClusterContext.KubectlOptions(t), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") + acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(r, staticClientPeerClusterContext.KubectlOptions(r), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") require.NoError(r, err) require.NotEmpty(r, acceptorSecretName) }) diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index 144bb26495..7522ec3f75 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -187,7 +187,7 @@ func TestPeering_Connect(t *testing.T) { // Ensure the secret is created. retry.RunWith(timer, t, func(r *retry.R) { - acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(t, staticClientPeerClusterContext.KubectlOptions(t), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") + acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(r, staticClientPeerClusterContext.KubectlOptions(r), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") require.NoError(r, err) require.NotEmpty(r, acceptorSecretName) }) diff --git a/acceptance/tests/peering/peering_gateway_test.go b/acceptance/tests/peering/peering_gateway_test.go index 5ccb2b3461..631a5f73b9 100644 --- a/acceptance/tests/peering/peering_gateway_test.go +++ b/acceptance/tests/peering/peering_gateway_test.go @@ -167,7 +167,7 @@ func TestPeering_Gateway(t *testing.T) { // Ensure the secret is created. retry.RunWith(timer, t, func(r *retry.R) { - acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(t, staticClientPeerClusterContext.KubectlOptions(t), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") + acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(r, staticClientPeerClusterContext.KubectlOptions(r), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") require.NoError(r, err) require.NotEmpty(r, acceptorSecretName) }) diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index 9688ff9855..3bb457903b 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -646,7 +647,7 @@ func (c *cluster) serviceTargetCheck(t *testing.T, expectedName string, curlAddr retry.RunWith(timer, t, func(r *retry.R) { // Use -s/--silent and -S/--show-error flags w/ curl to reduce noise during retries. // This silences extra output like the request progress bar, but preserves errors. - resp, err = k8s.RunKubectlAndGetOutputE(t, c.clientOpts, "exec", "-i", + resp, err = k8s.RunKubectlAndGetOutputE(r, c.clientOpts, "exec", "-i", staticClientDeployment, "-c", staticClientName, "--", "curl", "-sS", curlAddress) require.NoError(r, err) assert.Contains(r, resp, expectedName) @@ -672,7 +673,7 @@ func (c *cluster) preparedQueryFailoverCheck(t *testing.T, releaseName string, e // that failover occurred, that is left to client `Execute` dnsPQLookup := []string{fmt.Sprintf("%s.query.consul", *c.pqName)} retry.RunWith(timer, t, func(r *retry.R) { - logs := dnsQuery(t, releaseName, dnsPQLookup, c.primaryCluster, failover) + logs := dnsQuery(r, releaseName, dnsPQLookup, c.primaryCluster, failover) assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", *c.primaryCluster.dnsIP)) assert.Contains(r, logs, "ANSWER SECTION:") assert.Contains(r, logs, *failover.staticServerIP) @@ -686,7 +687,7 @@ func (c *cluster) dnsFailoverCheck(t *testing.T, cfg *config.TestConfig, release retry.RunWith(timer, t, func(r *retry.R) { // Use the primary cluster when performing a DNS lookup, this mostly affects cases // where we are verifying DNS for a partition - logs := dnsQuery(t, releaseName, dnsLookup, c.primaryCluster, failover) + logs := dnsQuery(r, releaseName, dnsLookup, c.primaryCluster, failover) assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", *c.primaryCluster.dnsIP)) assert.Contains(r, logs, "ANSWER SECTION:") @@ -697,7 +698,7 @@ func (c *cluster) dnsFailoverCheck(t *testing.T, cfg *config.TestConfig, release // the context can be used to determine that failover occured to the expected kubernetes cluster // hosting Consul assert.Contains(r, logs, "ADDITIONAL SECTION:") - expectedName := failover.context.KubectlOptions(t).ContextName + expectedName := failover.context.KubectlOptions(r).ContextName if cfg.UseKind { expectedName = strings.Replace(expectedName, "kind-", "", -1) } @@ -712,7 +713,7 @@ func (c *cluster) getPeeringAcceptorSecret(t *testing.T, cfg *config.TestConfig, timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} retry.RunWith(timer, t, func(r *retry.R) { var err error - acceptorSecretName, err = k8s.RunKubectlAndGetOutputE(t, c.context.KubectlOptions(t), "get", "peeringacceptor", acceptorName, "-o", "jsonpath={.status.secret.name}") + acceptorSecretName, err = k8s.RunKubectlAndGetOutputE(r, c.context.KubectlOptions(r), "get", "peeringacceptor", acceptorName, "-o", "jsonpath={.status.secret.name}") require.NoError(r, err) require.NotEmpty(r, acceptorSecretName) }) @@ -830,16 +831,17 @@ func setK8sNodeLocality(t *testing.T, context environment.TestContext, c *cluste } // dnsQuery performs a dns query with the provided query string. -func dnsQuery(t *testing.T, releaseName string, dnsQuery []string, dnsServer, failover *cluster) string { +func dnsQuery(t testutil.TestingTB, releaseName string, dnsQuery []string, dnsServer, failover *cluster) string { timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} var logs string + retry.RunWith(timer, t, func(r *retry.R) { args := []string{"exec", "-i", staticClientDeployment, "-c", staticClientName, "--", "dig", fmt.Sprintf("@%s-consul-dns.default", releaseName)} args = append(args, dnsQuery...) var err error - logs, err = k8s.RunKubectlAndGetOutputE(t, dnsServer.clientOpts, args...) + logs, err = k8s.RunKubectlAndGetOutputE(r, dnsServer.clientOpts, args...) require.NoError(r, err) }) logger.Logf(t, "%s: %s", failover.name, logs) diff --git a/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go b/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go index b5613fe76d..e5f1e785af 100644 --- a/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go +++ b/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go @@ -91,9 +91,9 @@ func TestSnapshotAgent_K8sSecret(t *testing.T) { retry.RunWith(timer, t, func(r *retry.R) { // Loop through snapshot agents. Only one will be the leader and have the snapshot files. pod := podList.Items[0] - snapshotFileListOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(t, kubectlOptions, terratestLogger.Discard, "exec", pod.Name, "-c", "consul-snapshot-agent", "--", "ls", "/tmp") + snapshotFileListOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(r, kubectlOptions, terratestLogger.Discard, "exec", pod.Name, "-c", "consul-snapshot-agent", "--", "ls", "/tmp") require.NoError(r, err) - logger.Logf(t, "Snapshot: \n%s", snapshotFileListOutput) + logger.Logf(r, "Snapshot: \n%s", snapshotFileListOutput) require.Contains(r, snapshotFileListOutput, ".snap", "Agent pod does not contain snapshot files") }) }) diff --git a/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go b/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go index 10cceb5952..3c4354547c 100644 --- a/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go +++ b/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go @@ -213,9 +213,9 @@ func TestSnapshotAgent_Vault(t *testing.T) { retry.RunWith(timer, t, func(r *retry.R) { // Loop through snapshot agents. Only one will be the leader and have the snapshot files. pod := podList.Items[0] - snapshotFileListOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(t, kubectlOptions, terratestLogger.Discard, "exec", pod.Name, "-c", "consul-snapshot-agent", "--", "ls", "/tmp") + snapshotFileListOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(r, kubectlOptions, terratestLogger.Discard, "exec", pod.Name, "-c", "consul-snapshot-agent", "--", "ls", "/tmp") require.NoError(r, err) - logger.Logf(t, "Snapshot: \n%s", snapshotFileListOutput) + logger.Logf(r, "Snapshot: \n%s", snapshotFileListOutput) require.Contains(r, snapshotFileListOutput, ".snap", "Agent pod does not contain snapshot files") }) } diff --git a/acceptance/tests/sync/sync_catalog_test.go b/acceptance/tests/sync/sync_catalog_test.go index dc30570422..4bd5f91039 100644 --- a/acceptance/tests/sync/sync_catalog_test.go +++ b/acceptance/tests/sync/sync_catalog_test.go @@ -118,12 +118,12 @@ func TestSyncCatalogWithIngress(t *testing.T) { // Retry the kubectl apply because we've seen sporadic // "connection refused" errors where the mutating webhook // endpoint fails initially. - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/ingress") + out, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "apply", "-k", "../fixtures/bases/ingress") require.NoError(r, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(r, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/ingress") + k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "delete", "-k", "../fixtures/bases/ingress") }) }) diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index 9ca520ab06..62567ea561 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -426,7 +426,7 @@ func serviceFailoverCheck(t *testing.T, options *terratestK8s.KubectlOptions, po // Retry until we get the response we expect, sometimes you get back the previous server until things stabalize logger.Log(t, "Initial failover check") retry.RunWith(timer, t, func(r *retry.R) { - resp, err = k8s.RunKubectlAndGetOutputE(t, options, "exec", "-i", + resp, err = k8s.RunKubectlAndGetOutputE(r, options, "exec", "-i", staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) assert.NoError(r, err) assert.Contains(r, resp, expectedName) @@ -438,7 +438,7 @@ func serviceFailoverCheck(t *testing.T, options *terratestK8s.KubectlOptions, po time.Sleep(500 * time.Millisecond) resp = "" retry.RunWith(timer, t, func(r *retry.R) { - resp, err = k8s.RunKubectlAndGetOutputE(t, options, "exec", "-i", + resp, err = k8s.RunKubectlAndGetOutputE(r, options, "exec", "-i", staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) assert.NoError(r, err) }) diff --git a/control-plane/go.mod b/control-plane/go.mod index e6f854dd07..202fb683b5 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -4,6 +4,8 @@ module github.com/hashicorp/consul-k8s/control-plane // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f +replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c + require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 diff --git a/control-plane/go.sum b/control-plane/go.sum index 2af704623b..70e9ffe5a2 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -267,8 +267,8 @@ github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f h1:wbJDaQukQ6LPIZwAqMSDl/fO7Wd1Yij8vFBam0ABy6A= github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= -github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= -github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c h1:j89V5jn4/XAYDLbhQNqV0WeMSzEOUa3asaFceXrL70M= +github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= 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= diff --git a/control-plane/subcommand/create-federation-secret/command_test.go b/control-plane/subcommand/create-federation-secret/command_test.go index 29fe3b3d61..954882e7f0 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -993,7 +993,7 @@ func TestRun_ConsulClientDelay(t *testing.T) { timer := &retry.Timer{Timeout: 10 * time.Second, Wait: 500 * time.Millisecond} retry.RunWith(timer, t, func(r *retry.R) { var err error - testserver, err = testutil.NewTestServerConfigT(t, func(cfg *testutil.TestServerConfig) { + testserver, err = testutil.NewTestServerConfigT(r, func(cfg *testutil.TestServerConfig) { cfg.CAFile = caFile cfg.CertFile = certFile cfg.KeyFile = keyFile diff --git a/control-plane/subcommand/sync-catalog/command_test.go b/control-plane/subcommand/sync-catalog/command_test.go index fdabb957f7..9b7365e801 100644 --- a/control-plane/subcommand/sync-catalog/command_test.go +++ b/control-plane/subcommand/sync-catalog/command_test.go @@ -557,14 +557,14 @@ func TestRun_ToConsulChangingFlags(t *testing.T) { require.Len(r, instances, 1) require.Equal(r, instances[0].ServiceName, svcName) } - tt.Log("existing services verified") + r.Log("existing services verified") for _, svcName := range c.SecondRunExpDeletedServices { instances, _, err := consulClient.Catalog().Service(svcName, "k8s", nil) require.NoError(r, err) require.Len(r, instances, 0) } - tt.Log("deleted services verified") + r.Log("deleted services verified") }) } }) From 65a4be8755db0744e48ae62df6b66fc4cba00005 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 13 Dec 2023 14:01:36 -0500 Subject: [PATCH 525/592] Update sdk to point to latest main (#3370) --- acceptance/go.mod | 2 +- acceptance/go.sum | 4 ++-- control-plane/go.mod | 2 +- control-plane/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/acceptance/go.mod b/acceptance/go.mod index 60d1fb1d8e..5e9ab5f291 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/acceptance go 1.20 -replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c +replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f require ( github.com/google/uuid v1.3.0 diff --git a/acceptance/go.sum b/acceptance/go.sum index 984eca3676..2ce8040697 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -394,8 +394,8 @@ github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= -github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c h1:j89V5jn4/XAYDLbhQNqV0WeMSzEOUa3asaFceXrL70M= -github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= +github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= 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= diff --git a/control-plane/go.mod b/control-plane/go.mod index 202fb683b5..4116047f7c 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -4,7 +4,7 @@ module github.com/hashicorp/consul-k8s/control-plane // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f -replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c +replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f require ( github.com/cenkalti/backoff v2.2.1+incompatible diff --git a/control-plane/go.sum b/control-plane/go.sum index 70e9ffe5a2..e65b753ba7 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -267,8 +267,8 @@ github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f h1:wbJDaQukQ6LPIZwAqMSDl/fO7Wd1Yij8vFBam0ABy6A= github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= -github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c h1:j89V5jn4/XAYDLbhQNqV0WeMSzEOUa3asaFceXrL70M= -github.com/hashicorp/consul/sdk v0.4.1-0.20231206152437-b95f9700b04c/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= +github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= 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= From cdc9ba1777271c2d4667611b8147f391aaed5234 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:59:40 -0600 Subject: [PATCH 526/592] Net 6742 - consul k8s mgw deployment priority class name support (#3361) * initial change, unit test updated * rename get gatewayconfig function * fixed name change, add newline --- charts/consul/templates/gateway-resources-configmap.yaml | 3 +++ .../config-entries/controllersv2/mesh_gateway_controller.go | 4 ++-- .../controllersv2/mesh_gateway_controller_test.go | 1 + control-plane/gateways/deployment.go | 4 ++++ control-plane/gateways/deployment_test.go | 4 +++- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 4d66e52838..d836538a1b 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -32,6 +32,9 @@ data: release: {{ .Release.Name }} component: mesh-gateway deployment: + {{- if .Values.meshGateway.priorityClassName }} + priorityClassName: {{ .Values.meshGateway.priorityClassName | quote }} + {{- end }} {{- if .Values.meshGateway.affinity }} affinity: {{ toJson (default "{}" .Values.meshGateway.affinity) }} {{- end }} diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index a953d6d603..13106d1c32 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -229,7 +229,7 @@ func (r *MeshGatewayController) getGatewayClassConfigForGateway(ctx context.Cont return nil, err } - gatewayClassConfig, err := r.getConfigForGatewayClass(ctx, gatewayClass) + gatewayClassConfig, err := r.getGatewayClassConfigForGatewayClass(ctx, gatewayClass) if err != nil { return nil, err } @@ -237,7 +237,7 @@ func (r *MeshGatewayController) getGatewayClassConfigForGateway(ctx context.Cont return gatewayClassConfig, nil } -func (r *MeshGatewayController) getConfigForGatewayClass(ctx context.Context, gatewayClass *meshv2beta1.GatewayClass) (*meshv2beta1.GatewayClassConfig, error) { +func (r *MeshGatewayController) getGatewayClassConfigForGatewayClass(ctx context.Context, gatewayClass *meshv2beta1.GatewayClass) (*meshv2beta1.GatewayClassConfig, error) { if gatewayClass == nil { // if we don't have a gateway class we can't fetch the corresponding config return nil, nil diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index cccee422a1..14c8ebcf16 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -345,6 +345,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { require.NoError(t, corev1.AddToScheme(s)) require.NoError(t, appsv1.AddToScheme(s)) require.NoError(t, rbacv1.AddToScheme(s)) + require.NoError(t, v2beta1.AddMeshToScheme(s)) s.AddKnownTypes(v2beta1.MeshGroupVersion, &v2beta1.MeshGateway{}, &v2beta1.GatewayClass{}, &v2beta1.GatewayClassConfig{}) fakeClient := fake.NewClientBuilder().WithScheme(s). WithRuntimeObjects(testCase.k8sObjects...). diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 6111b8084d..96106a369c 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -37,8 +37,11 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { } var containerConfig *meshv2beta1.GatewayClassContainerConfig + var deploymentConfig meshv2beta1.GatewayClassDeploymentConfig + if b.gcc != nil { containerConfig = b.gcc.Spec.Deployment.Container + deploymentConfig = b.gcc.Spec.Deployment } container, err := consulDataplaneContainer(b.config, containerConfig, b.gateway.Name, b.gateway.Namespace) @@ -96,6 +99,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { }, }, NodeSelector: nodeSelector, + PriorityClassName: deploymentConfig.PriorityClassName, Tolerations: nil, ServiceAccountName: b.serviceAccountName(), }, diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 836e49a179..6ad9ab920a 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -48,6 +48,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Min: pointer.Int32(1), Max: pointer.Int32(8), }, + PriorityClassName: "priorityclassname", }, }, }, @@ -252,7 +253,8 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { TTY: false, }, }, - NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, + NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, + PriorityClassName: "priorityclassname", Affinity: &corev1.Affinity{ NodeAffinity: nil, PodAffinity: nil, From 445fba772c3a0c7c109a82153648cb42534ef329 Mon Sep 17 00:00:00 2001 From: John Murret Date: Wed, 13 Dec 2023 14:42:40 -0700 Subject: [PATCH 527/592] Prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers (#3337) * wip: testing with server works when you add segments as extraValues. Todos: * make similar changes to clients * potentially upgrade test? * consider locality having its own volume, rather than 2 volumes with extra in them * move extra-config out of /consul/config so it does not get applied twice * add comments about use of additional config maps * remove temporary inclusion of values.yaml in root that was used for hand off * get rid of temporary config.file * add segments test * test using 3 servers in a single cluster * add changelog * fix linting issues. * add comment to test. remove extra lines from config map. * fix bats tests --------- Co-authored-by: Nitya Dhanushkodi --- .changelog/3337.txt | 3 + acceptance/tests/segments/main_test.go | 18 ++ acceptance/tests/segments/segments_test.go | 187 ++++++++++++++++++ charts/consul/templates/_helpers.tpl | 2 +- .../templates/client-config-configmap.yaml | 3 - charts/consul/templates/client-daemonset.yaml | 9 +- .../client-tmp-extra-config-configmap.yaml | 21 ++ .../templates/server-config-configmap.yaml | 2 - .../consul/templates/server-statefulset.yaml | 10 +- .../server-tmp-extra-config-configmap.yaml | 21 ++ .../test/unit/client-config-configmap.bats | 11 -- charts/consul/test/unit/client-daemonset.bats | 6 +- .../client-tmp-extra-config-configmap.bats | 43 ++++ .../test/unit/server-config-configmap.bats | 10 - .../consul/test/unit/server-statefulset.bats | 6 +- .../server-tmp-extra-config-configmap.bats | 49 +++++ 16 files changed, 363 insertions(+), 38 deletions(-) create mode 100644 .changelog/3337.txt create mode 100644 acceptance/tests/segments/main_test.go create mode 100644 acceptance/tests/segments/segments_test.go create mode 100644 charts/consul/templates/client-tmp-extra-config-configmap.yaml create mode 100644 charts/consul/templates/server-tmp-extra-config-configmap.yaml create mode 100755 charts/consul/test/unit/client-tmp-extra-config-configmap.bats create mode 100755 charts/consul/test/unit/server-tmp-extra-config-configmap.bats diff --git a/.changelog/3337.txt b/.changelog/3337.txt new file mode 100644 index 0000000000..da9e7d5c70 --- /dev/null +++ b/.changelog/3337.txt @@ -0,0 +1,3 @@ +```release-note:bug-fix +mesh: prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers. +``` \ No newline at end of file diff --git a/acceptance/tests/segments/main_test.go b/acceptance/tests/segments/main_test.go new file mode 100644 index 0000000000..4d66c9ae4f --- /dev/null +++ b/acceptance/tests/segments/main_test.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package segments + +import ( + "os" + "testing" + + testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" +) + +var suite testsuite.Suite + +func TestMain(m *testing.M) { + suite = testsuite.NewSuite(m) + os.Exit(suite.Run()) +} diff --git a/acceptance/tests/segments/segments_test.go b/acceptance/tests/segments/segments_test.go new file mode 100644 index 0000000000..4725fb477c --- /dev/null +++ b/acceptance/tests/segments/segments_test.go @@ -0,0 +1,187 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package segments + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" +) + +// TestSegments_MeshWithAgentfulClients is a simple test that verifies that +// the Consul service mesh can be configured to use segments with: +// - one cluster with an alpha segment configured on the servers. +// - clients enabled and joining the alpha segment. +// - static client can communicate with static server. +func TestSegments_MeshWithAgentfulClients(t *testing.T) { + cases := map[string]struct { + secure bool + }{ + "not-secure": {secure: false}, + "secure": {secure: true}, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + cfg := suite.Config() + if !cfg.EnableEnterprise { + t.Skipf("skipping this test because -enable-enterprise is not set") + } + ctx := suite.Environment().DefaultContext(t) + + releaseName := helpers.RandomName() + + helmValues := map[string]string{ + "connectInject.enabled": "true", + + "server.replicas": "3", + "server.extraConfig": `"{\"segments\": [{\"name\":\"alpha1\"\,\"bind\":\"0.0.0.0\"\,\"port\":8303}]}"`, + + "client.enabled": "true", + // need to configure clients to connect to port 8303 that the alpha segment was configured on rather than + // the standard serf LAN port. + "client.join[0]": "${CONSUL_FULLNAME}-server-0.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.join[1]": "${CONSUL_FULLNAME}-server-1.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.join[2]": "${CONSUL_FULLNAME}-server-2.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.extraConfig": `"{\"segment\": \"alpha1\"}"`, + } + + connHelper := connhelper.ConnectHelper{ + ClusterKind: consul.Helm, + Secure: c.secure, + ReleaseName: releaseName, + Ctx: ctx, + UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, + Cfg: cfg, + HelmValues: helmValues, + } + + connHelper.Setup(t) + + connHelper.Install(t) + connHelper.DeployClientAndServer(t) + if c.secure { + connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) + connHelper.CreateIntention(t, connhelper.IntentionOpts{}) + } + + connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionFailureWhenUnhealthy(t) + }) + } +} + +// TestSegments_MeshWithAgentfulClientsMultiCluster is a simple test that verifies that +// the Consul service mesh can be configured to use segments with: +// - one cluster with an alpha segment configured on the servers. +// - clients enabled on another cluster and joining the alpha segment. +// - static client can communicate with static server. +func TestSegments_MeshWithAgentfulClientsMultiCluster(t *testing.T) { + cases := map[string]struct { + secure bool + }{ + "not-secure": {secure: false}, + "secure": {secure: true}, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + cfg := suite.Config() + if !cfg.EnableEnterprise { + t.Skipf("skipping this test because -enable-enterprise is not set") + } + releaseName := helpers.RandomName() + + // deploy server cluster + serverClusterContext := suite.Environment().DefaultContext(t) + serverClusterHelmValues := map[string]string{ + "connectInject.enabled": "true", + + "server.replicas": "3", + "server.extraConfig": `"{\"segments\": [{\"name\":\"alpha1\"\,\"bind\":\"0.0.0.0\"\,\"port\":8303}]}"`, + + "client.enabled": "true", + "client.join[0]": "${CONSUL_FULLNAME}-server-0.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.join[1]": "${CONSUL_FULLNAME}-server-1.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.join[2]": "${CONSUL_FULLNAME}-server-2.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.extraConfig": `"{\"segment\": \"alpha1\"}"`, + } + + serverConnHelper := connhelper.ConnectHelper{ + ClusterKind: consul.Helm, + Secure: c.secure, + ReleaseName: releaseName, + Ctx: serverClusterContext, + UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, + Cfg: cfg, + HelmValues: serverClusterHelmValues, + } + + serverConnHelper.Setup(t) + serverConnHelper.Install(t) + serverConnHelper.DeployServer(t) + + // deploy client cluster + clientClusterContext := suite.Environment().Context(t, 1) + clientClusterHelmValues := map[string]string{ + "connectInject.enabled": "true", + + "server.enabled": "false", + + "client.enabled": "true", + "client.join[0]": "${CONSUL_FULLNAME}-server-0.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.join[1]": "${CONSUL_FULLNAME}-server-1.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.join[2]": "${CONSUL_FULLNAME}-server-2.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", + "client.extraConfig": `"{\"segment\": \"alpha1\"}"`, + } + + clientClusterConnHelper := connhelper.ConnectHelper{ + ClusterKind: consul.Helm, + Secure: c.secure, + ReleaseName: releaseName, + Ctx: clientClusterContext, + UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, + Cfg: cfg, + HelmValues: clientClusterHelmValues, + } + + clientClusterConnHelper.Setup(t) + clientClusterConnHelper.Install(t) + logger.Log(t, "creating static-client deployments in client cluster") + opts := clientClusterConnHelper.KubectlOptsForApp(t) + + if cfg.EnableTransparentProxy { + k8s.DeployKustomize(t, opts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + } else { + k8s.DeployKustomize(t, opts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + } + + // Check that the static-client has been injected and now have 2 containers in client cluster. + for _, labelSelector := range []string{"app=static-client"} { + podList, err := clientClusterContext.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{ + LabelSelector: labelSelector, + }) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Len(t, podList.Items[0].Spec.Containers, 2) + } + + //if c.secure { + // connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) + // connHelper.CreateIntention(t, connhelper.IntentionOpts{}) + //} + // + //connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + //connHelper.TestConnectionFailureWhenUnhealthy(t) + }) + } +} diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index aa62cb6600..98384b3868 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -145,7 +145,7 @@ substitution for HOST_IP/POD_IP/HOSTNAME. Useful for dogstats telemetry. The out is passed to consul as a -config-file param on command line. */}} {{- define "consul.extraconfig" -}} - cp /consul/config/extra-from-values.json /consul/extra-config/extra-from-values.json + cp /consul/tmp/extra-config/extra-from-values.json /consul/extra-config/extra-from-values.json [ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /consul/extra-config/extra-from-values.json [ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /consul/extra-config/extra-from-values.json [ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /consul/extra-config/extra-from-values.json diff --git a/charts/consul/templates/client-config-configmap.yaml b/charts/consul/templates/client-config-configmap.yaml index d91a4d21bf..cab2c7c043 100644 --- a/charts/consul/templates/client-config-configmap.yaml +++ b/charts/consul/templates/client-config-configmap.yaml @@ -25,13 +25,10 @@ data: "log_level": "{{ .Values.client.logLevel | upper }}" {{- end }} } - extra-from-values.json: |- -{{ tpl .Values.client.extraConfig . | trimAll "\"" | indent 4 }} central-config.json: |- { "enable_central_service_config": true } - {{- if .Values.connectInject.enabled }} {{/* We set check_update_interval to 0s so that check output is immediately viewable in the UI. */}} diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index e7dd83ef26..dd0454b10d 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -87,7 +87,7 @@ spec: {{- end }} "consul.hashicorp.com/connect-inject": "false" "consul.hashicorp.com/mesh-inject": "false" - "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/client-config-configmap.yaml") . | sha256sum }} + "consul.hashicorp.com/config-checksum": {{ print (include (print $.Template.BasePath "/client-config-configmap.yaml") .) (include (print $.Template.BasePath "/client-tmp-extra-config-configmap.yaml") .) | sha256sum }} {{- if .Values.client.annotations }} {{- tpl .Values.client.annotations . | nindent 8 }} {{- end }} @@ -142,6 +142,9 @@ spec: - name: consul-data emptyDir: medium: "Memory" + - name: tmp-extra-config + configMap: + name: {{ template "consul.fullname" . }}-client-tmp-extra-config {{- if .Values.global.tls.enabled }} {{- if not .Values.global.secretsBackend.vault.enabled }} - name: consul-ca-cert @@ -390,7 +393,7 @@ spec: {{- range $value := .Values.global.recursors }} -recursor={{ quote $value }} \ {{- end }} - -config-file=/consul/extra-config/extra-from-values.json \ + -config-dir=/consul/extra-config \ -domain={{ .Values.global.domain }} volumeMounts: - name: data @@ -399,6 +402,8 @@ spec: mountPath: /consul/config - name: extra-config mountPath: /consul/extra-config + - name: tmp-extra-config + mountPath: /consul/tmp/extra-config - mountPath: /consul/login name: consul-data readOnly: true diff --git a/charts/consul/templates/client-tmp-extra-config-configmap.yaml b/charts/consul/templates/client-tmp-extra-config-configmap.yaml new file mode 100644 index 0000000000..a379157f57 --- /dev/null +++ b/charts/consul/templates/client-tmp-extra-config-configmap.yaml @@ -0,0 +1,21 @@ +{{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} +# ConfigMap that is used as a temporary landing spot so that the container command +# in the client-daemonset where it needs to be transformed. ConfigMaps create +# read only volumes so it needs to be copied and transformed to the extra-config +# emptyDir volume where all final extra cofngi lives for use in consul. (locality-init +# also writes to extra-config volume.) +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "consul.fullname" . }}-client-tmp-extra-config + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: client +data: + extra-from-values.json: |- +{{ tpl .Values.client.extraConfig . | trimAll "\"" | indent 4 }} +{{- end }} diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 9ebfbd2571..8cd726f445 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -95,8 +95,6 @@ data: {{- end }} {{- end }} {{- end }} - extra-from-values.json: |- -{{ tpl .Values.server.extraConfig . | trimAll "\"" | indent 4 }} {{- if .Values.global.acls.manageSystemACLs }} acl-config.json: |- { diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index c5935f1499..5c76a12389 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -119,7 +119,7 @@ spec: {{- end }} "consul.hashicorp.com/connect-inject": "false" "consul.hashicorp.com/mesh-inject": "false" - "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/server-config-configmap.yaml") . | sha256sum }} + "consul.hashicorp.com/config-checksum": {{ print (include (print $.Template.BasePath "/server-config-configmap.yaml") .) (include (print $.Template.BasePath "/server-tmp-extra-config-configmap.yaml") .) | sha256sum }} {{- if .Values.server.annotations }} {{- tpl .Values.server.annotations . | nindent 8 }} {{- end }} @@ -159,6 +159,9 @@ spec: name: {{ template "consul.fullname" . }}-server-config - name: extra-config emptyDir: {} + - name: tmp-extra-config + configMap: + name: {{ template "consul.fullname" . }}-server-tmp-extra-config {{- if (and .Values.global.tls.enabled (not .Values.global.secretsBackend.vault.enabled)) }} - name: consul-ca-cert secret: @@ -423,8 +426,7 @@ spec: -config-dir=/consul/userconfig/{{ .name }} \ {{- end }} {{- end }} - -config-file=/consul/extra-config/extra-from-values.json \ - -config-file=/consul/extra-config/locality.json \ + -config-dir=/consul/extra-config \ {{- if and .Values.global.cloud.enabled .Values.global.cloud.resourceId.secretName }} -hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }" {{- end }} @@ -438,6 +440,8 @@ spec: mountPath: /consul/config - name: extra-config mountPath: /consul/extra-config + - name: tmp-extra-config + mountPath: /consul/tmp/extra-config {{- if (and .Values.global.tls.enabled (not .Values.global.secretsBackend.vault.enabled)) }} - name: consul-ca-cert mountPath: /consul/tls/ca/ diff --git a/charts/consul/templates/server-tmp-extra-config-configmap.yaml b/charts/consul/templates/server-tmp-extra-config-configmap.yaml new file mode 100644 index 0000000000..a42d6d09f6 --- /dev/null +++ b/charts/consul/templates/server-tmp-extra-config-configmap.yaml @@ -0,0 +1,21 @@ +{{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} +# ConfigMap that is used as a temporary landing spot so that the container command +# in the server-stateful set where it needs to be transformed. ConfigMaps create +# read only volumes so it needs to be copied and transformed to the extra-config +# emptyDir volume where all final extra cofngi lives for use in consul. (locality-init +# also writes to extra-config volume.) +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "consul.fullname" . }}-server-tmp-extra-config + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: server +data: + extra-from-values.json: |- +{{ tpl .Values.server.extraConfig . | trimAll "\"" | indent 4 }} +{{- end }} \ No newline at end of file diff --git a/charts/consul/test/unit/client-config-configmap.bats b/charts/consul/test/unit/client-config-configmap.bats index 1f1443a156..7d0d424499 100755 --- a/charts/consul/test/unit/client-config-configmap.bats +++ b/charts/consul/test/unit/client-config-configmap.bats @@ -31,17 +31,6 @@ load _helpers . } -@test "client/ConfigMap: extraConfig is set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/client-config-configmap.yaml \ - --set 'client.enabled=true' \ - --set 'client.extraConfig="{\"hello\": \"world\"}"' \ - . | tee /dev/stderr | - yq '.data["extra-from-values.json"] | match("world") | length > 1' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - #-------------------------------------------------------------------- # connectInject.centralConfig [DEPRECATED] diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index c9889c2ffd..bb7b6cd472 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -625,7 +625,7 @@ load _helpers --set 'client.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 4fa9ddc3abc4c79eafccb19e5beef80006b7c9736b867d8873554ca03f42a6b3 ] + [ "${actual}" = 678c5c1c2ca0f8cb1464d38636f12714c05df26fab1a101e43ce619fdbc2e7d1 ] } @test "client/DaemonSet: config-checksum annotation changes when extraConfig is provided" { @@ -636,7 +636,7 @@ load _helpers --set 'client.extraConfig="{\"hello\": \"world\"}"' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 42b99932385e7a0580b134fe36a9bda405aab2e375593326677b9838708f0796 ] + [ "${actual}" = 0ef58da6fd14fb57c702a2a0d631c4eecacff152fe3a36836a23283b19d8dbe1 ] } @test "client/DaemonSet: config-checksum annotation changes when connectInject.enabled=true" { @@ -647,7 +647,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 4fa9ddc3abc4c79eafccb19e5beef80006b7c9736b867d8873554ca03f42a6b3 ] + [ "${actual}" = 678c5c1c2ca0f8cb1464d38636f12714c05df26fab1a101e43ce619fdbc2e7d1 ] } #-------------------------------------------------------------------- diff --git a/charts/consul/test/unit/client-tmp-extra-config-configmap.bats b/charts/consul/test/unit/client-tmp-extra-config-configmap.bats new file mode 100755 index 0000000000..435ed54ce9 --- /dev/null +++ b/charts/consul/test/unit/client-tmp-extra-config-configmap.bats @@ -0,0 +1,43 @@ +#!/usr/bin/env bats + +load _helpers + +@test "client/TmpExtraConfigMap: enable with global.enabled false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-tmp-extra-config-configmap.yaml \ + --set 'client.enabled=true' \ + --set 'global.enabled=false' \ + --set 'client.enabled=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/TmpExtraConfigMap: disable with client.enabled false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/client-tmp-extra-config-configmap.yaml \ + --set 'client.enabled=true' \ + --set 'client.enabled=false' \ + . +} + +@test "client/TmpExtraConfigMap: disable with global.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/client-tmp-extra-config-configmap.yaml \ + --set 'global.enabled=false' \ + . +} + +@test "client/TmpExtraConfigMap: extraConfig is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-tmp-extra-config-configmap.yaml \ + --set 'client.enabled=true' \ + --set 'client.extraConfig="{\"hello\": \"world\"}"' \ + . | tee /dev/stderr | + yq '.data["extra-from-values.json"] | match("world") | length > 1' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 53d842fc9c..7ed7f57a9c 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -38,16 +38,6 @@ load _helpers . } -@test "server/ConfigMap: extraConfig is set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.extraConfig="{\"hello\": \"world\"}"' \ - . | tee /dev/stderr | - yq '.data["extra-from-values.json"] | match("world") | length' | tee /dev/stderr) - [ ! -z "${actual}" ] -} - #-------------------------------------------------------------------- # retry-join diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 895bdeb4e8..f4702af827 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -792,7 +792,7 @@ load _helpers -s templates/server-statefulset.yaml \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 3714bf1fbca840dcd10b5aeb40c6ef35e349bd534727abe80b831c77f88da7da ] + [ "${actual}" = 0e599137f8357c786d46e1b694d7d867c541cb34d6056241a037afd0de14866b ] } @test "server/StatefulSet: adds config-checksum annotation when extraConfig is provided" { @@ -802,7 +802,7 @@ load _helpers --set 'server.extraConfig="{\"hello\": \"world\"}"' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 87126fac3c7704fba1d4265201ad0345f4d972d2123df6e6fb416b40c5823d80 ] + [ "${actual}" = 3f54c51be3473d7ae4cb91c24ba03263b7700d9a3dc3196f624ce3c6c8e93b8f ] } @test "server/StatefulSet: adds config-checksum annotation when config is updated" { @@ -812,7 +812,7 @@ load _helpers --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = d98ff135c5dee661058f33e29970675761ecf235676db0d4d24f354908eee425 ] + [ "${actual}" = b44c82c9e4732433f54eeed8a299f11de0bad82a920047c8a3ad039e512ba281 ] } #-------------------------------------------------------------------- diff --git a/charts/consul/test/unit/server-tmp-extra-config-configmap.bats b/charts/consul/test/unit/server-tmp-extra-config-configmap.bats new file mode 100755 index 0000000000..213cc0cbc8 --- /dev/null +++ b/charts/consul/test/unit/server-tmp-extra-config-configmap.bats @@ -0,0 +1,49 @@ +#!/usr/bin/env bats + +load _helpers + +@test "server/TmpConfigMap: enabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-tmp-extra-config-configmap.yaml \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "server/TmpConfigMap: enable with global.enabled false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-tmp-extra-config-configmap.yaml \ + --set 'global.enabled=false' \ + --set 'server.enabled=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "server/TmpConfigMap: disable with server.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/server-tmp-extra-config-configmap.yaml \ + --set 'server.enabled=false' \ + . +} + +@test "server/TmpConfigMap: disable with global.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/server-tmp-extra-config-configmap.yaml \ + --set 'global.enabled=false' \ + . +} + +@test "server/TmpConfigMap: extraConfig is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-tmp-extra-config-configmap.yaml \ + --set 'server.extraConfig="{\"hello\": \"world\"}"' \ + . | tee /dev/stderr | + yq '.data["extra-from-values.json"] | match("world") | length' | tee /dev/stderr) + [ ! -z "${actual}" ] +} From f44115d39e275af37bfb41c3bea448c577a258f8 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 13 Dec 2023 16:46:30 -0500 Subject: [PATCH 528/592] [NET-6756] Tolerations for MeshGW (#3363) * add tolerations support for mesh gateway deployment * Add tests for minimal configuration, patch bug with default resource requirements not being set --- .../gateway-resources-configmap.yaml | 2 +- control-plane/gateways/deployment.go | 23 ++- .../subcommand/gateway-resources/command.go | 6 + .../gateway-resources/command_test.go | 182 ++++++++++++------ 4 files changed, 138 insertions(+), 75 deletions(-) diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index d836538a1b..6f0fcc1712 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -66,7 +66,7 @@ data: min: {{ .Values.meshGateway.replicas }} max: {{ .Values.meshGateway.replicas }} {{- if .Values.meshGateway.tolerations }} - tolerations: {{ toJson .Values.meshGateway.tolerations }} + tolerations: {{ fromYamlArray .Values.meshGateway.tolerations | toJson }} {{- end }} service: {{- if .Values.meshGateway.service.annotations }} diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 96106a369c..441d19d7e2 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -36,12 +36,21 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { return nil, err } - var containerConfig *meshv2beta1.GatewayClassContainerConfig - var deploymentConfig meshv2beta1.GatewayClassDeploymentConfig - + var ( + containerConfig *meshv2beta1.GatewayClassContainerConfig + nodeSelector map[string]string + tolerations []corev1.Toleration + deploymentConfig meshv2beta1.GatewayClassDeploymentConfig + ) if b.gcc != nil { containerConfig = b.gcc.Spec.Deployment.Container deploymentConfig = b.gcc.Spec.Deployment + + if b.gcc.Spec.Deployment.NodeSelector != nil { + nodeSelector = b.gcc.Spec.Deployment.NodeSelector + } + + tolerations = b.gcc.Spec.Deployment.Tolerations } container, err := consulDataplaneContainer(b.config, containerConfig, b.gateway.Name, b.gateway.Namespace) @@ -49,12 +58,6 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { return nil, err } - var nodeSelector map[string]string - - if b.gcc != nil && b.gcc.Spec.Deployment.NodeSelector != nil { - nodeSelector = b.gcc.Spec.Deployment.NodeSelector - } - return &appsv1.DeploymentSpec{ // TODO NET-6721 Replicas: deploymentReplicaCount(nil, nil), @@ -100,7 +103,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { }, NodeSelector: nodeSelector, PriorityClassName: deploymentConfig.PriorityClassName, - Tolerations: nil, + Tolerations: tolerations, ServiceAccountName: b.serviceAccountName(), }, }, diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 6328331d9a..413c87b497 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -407,6 +407,12 @@ func (c *Command) loadGatewayConfigs() error { return err } + // ensure default resources requirements are set + for idx := range c.gatewayConfig.MeshGateways { + if c.gatewayConfig.GatewayClassConfigs[idx].Spec.Deployment.Container == nil { + c.gatewayConfig.GatewayClassConfigs[idx].Spec.Deployment.Container = &v2beta1.GatewayClassContainerConfig{Resources: &defaultResourceRequirements} + } + } if err := file.Close(); err != nil { return err } diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index bc4a39e9ca..0b3ddd16ec 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -234,7 +234,7 @@ func TestRun(t *testing.T) { configFileName := gatewayConfigFilename if tt.meshGWConfigFileExists { - configFileName = createGatewayConfigFile(t, validGWConfiguration, "config.yaml") + configFileName = createGatewayConfigFile(t, validGWConfigurationKitchenSink, "config.yaml") } objs := []client.Object{} @@ -365,7 +365,7 @@ func TestRun_loadResourceConfigFileWhenConfigFileDoesNotExist(t *testing.T) { require.Contains(t, string(ui.OutputWriter.Bytes()), "No resources.json found, using defaults") } -var validGWConfiguration = `gatewayClassConfigs: +var validGWConfigurationKitchenSink = `gatewayClassConfigs: - apiVersion: mesh.consul.hashicorp.com/v2beta1 kind: GatewayClassConfig metadata: @@ -375,6 +375,11 @@ var validGWConfiguration = `gatewayClassConfigs: nodeSelector: beta.kubernetes.io/arch: amd64 beta.kubernetes.io/os: linux + tolerations: + - key: "key1" + operator: "Equal" + value: "value1" + effect: "NoSchedule" container: portModifier: 8000 resources: @@ -394,6 +399,23 @@ meshGateways: gatewayClassName: consul-mesh-gateway ` +var validGWConfigurationMinimal = `gatewayClassConfigs: +- apiVersion: mesh.consul.hashicorp.com/v2beta1 + kind: GatewayClassConfig + metadata: + name: consul-mesh-gateway + spec: + deployment: +meshGateways: +- apiVersion: mesh.consul.hashicorp.com/v2beta1 + kind: MeshGateway + metadata: + name: mesh-gateway + namespace: consul + spec: + gatewayClassName: consul-mesh-gateway +` + var invalidGWConfiguration = ` gatewayClassConfigs: iVersion= mesh.consul.hashicorp.com/v2beta1 @@ -413,81 +435,113 @@ meshGateways: ` func TestRun_loadGatewayConfigs(t *testing.T) { - expectedResources := &corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("200m"), - }, - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("200m"), - }, - } - filename := createGatewayConfigFile(t, validGWConfiguration, "config.yaml") - // setup k8s client - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - flagGatewayConfigLocation: filename, - } - - err := cmd.loadGatewayConfigs() - require.NoError(t, err) - require.NotEmpty(t, cmd.gatewayConfig.GatewayClassConfigs) - require.NotEmpty(t, cmd.gatewayConfig.MeshGateways) - - // we only created one class config - classConfig := cmd.gatewayConfig.GatewayClassConfigs[0].DeepCopy() - - expectedClassConfig := v2beta1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v2beta1.MeshGroupVersion.String(), - Kind: "GatewayClassConfig", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-mesh-gateway", - }, - Spec: v2beta1.GatewayClassConfigSpec{ - Deployment: v2beta1.GatewayClassDeploymentConfig{ + testCases := map[string]struct { + config string + filename string + expectedDeployment v2beta1.GatewayClassDeploymentConfig + }{ + "kitchen sink": { + config: validGWConfigurationKitchenSink, + filename: "kitchenSinkConfig.yaml", + expectedDeployment: v2beta1.GatewayClassDeploymentConfig{ NodeSelector: map[string]string{ "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/os": "linux", }, + Tolerations: []corev1.Toleration{ + { + Key: "key1", + Operator: "Equal", + Value: "value1", + Effect: "NoSchedule", + }, + }, Container: &v2beta1.GatewayClassContainerConfig{ - Resources: expectedResources, + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("200m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("200m"), + }, + }, PortModifier: 8000, }, }, }, - Status: v2beta1.Status{}, + "minimal configuration": { + config: validGWConfigurationMinimal, + filename: "minimalConfig.yaml", + expectedDeployment: v2beta1.GatewayClassDeploymentConfig{ + Container: &v2beta1.GatewayClassContainerConfig{ + Resources: &defaultResourceRequirements, + }, + }, + }, } - require.Equal(t, expectedClassConfig.DeepCopy(), classConfig) - // check mesh gateway, we only created one of these - actualMeshGateway := cmd.gatewayConfig.MeshGateways[0] + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + filename := createGatewayConfigFile(t, tc.config, tc.filename) + // setup k8s client + s := runtime.NewScheme() + require.NoError(t, gwv1beta1.Install(s)) + require.NoError(t, v1alpha1.AddToScheme(s)) + + client := fake.NewClientBuilder().WithScheme(s).Build() - expectedMeshGateway := &v2beta1.MeshGateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "MeshGateway", - APIVersion: v2beta1.MeshGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "mesh-gateway", - Namespace: "consul", - }, - Spec: meshv2beta1.MeshGateway{ - GatewayClassName: "consul-mesh-gateway", - }, - } + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: client, + flagGatewayConfigLocation: filename, + } + + err := cmd.loadGatewayConfigs() + require.NoError(t, err) + require.NotEmpty(t, cmd.gatewayConfig.GatewayClassConfigs) + require.NotEmpty(t, cmd.gatewayConfig.MeshGateways) + + // we only created one class config + classConfig := cmd.gatewayConfig.GatewayClassConfigs[0].DeepCopy() + + expectedClassConfig := v2beta1.GatewayClassConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v2beta1.MeshGroupVersion.String(), + Kind: "GatewayClassConfig", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-mesh-gateway", + }, + Spec: v2beta1.GatewayClassConfigSpec{ + Deployment: tc.expectedDeployment, + }, + Status: v2beta1.Status{}, + } + require.Equal(t, expectedClassConfig.DeepCopy(), classConfig) + + // check mesh gateway, we only created one of these + actualMeshGateway := cmd.gatewayConfig.MeshGateways[0] + + expectedMeshGateway := &v2beta1.MeshGateway{ + TypeMeta: metav1.TypeMeta{ + Kind: "MeshGateway", + APIVersion: v2beta1.MeshGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "mesh-gateway", + Namespace: "consul", + }, + Spec: meshv2beta1.MeshGateway{ + GatewayClassName: "consul-mesh-gateway", + }, + } - require.Equal(t, expectedMeshGateway.DeepCopy(), actualMeshGateway) + require.Equal(t, expectedMeshGateway.DeepCopy(), actualMeshGateway) + }) + } } func TestRun_loadGatewayConfigsWithInvalidFile(t *testing.T) { From 95ee1569b743de7c2bcc82429fdef6116bad3693 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:04:53 -0800 Subject: [PATCH 529/592] Mw/net 6911 parallel cluster creation in acceptance test causes tests to hang on failure (#3366) * add a smaller multi kind cluster make target for dev * fix bug where parallel cluster creation caused tests to hang on failure * add a retry for cluster creation - Sometimes when creating a cluster the previous cluster is not fully deleted - Fixes NET-6909 * remove warn loglevel * disable the NET-5819 skip so that we can monitor if other changes to Consul since the issue was first logged may have fixed the issue --- Makefile | 7 ++++++- acceptance/framework/consul/helm_cluster.go | 7 ++++++- .../tests/partitions/partitions_connect_test.go | 8 ++++---- .../tests/peering/peering_connect_namespaces_test.go | 6 ++---- acceptance/tests/peering/peering_connect_test.go | 4 ++-- acceptance/tests/peering/peering_gateway_test.go | 4 ++-- acceptance/tests/sameness/sameness_test.go | 11 +++++------ .../subcommand/get-consul-client-ca/command_test.go | 2 +- 8 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index ee326b70a2..248dddf995 100644 --- a/Makefile +++ b/Makefile @@ -183,12 +183,17 @@ kind-cni: kind-delete ## Helper target for doing local cni acceptance testing make kind-cni-calico .PHONY: kind -kind: kind-delete ## Helper target for doing local acceptance testing +kind: kind-delete ## Helper target for doing local acceptance testing (works in all cases) kind create cluster --name dc1 --image $(KIND_NODE_IMAGE) kind create cluster --name dc2 --image $(KIND_NODE_IMAGE) kind create cluster --name dc3 --image $(KIND_NODE_IMAGE) kind create cluster --name dc4 --image $(KIND_NODE_IMAGE) +.PHONY: kind-small +kind-small: kind-delete ## Helper target for doing local acceptance testing (when you only need two clusters) + kind create cluster --name dc1 --image $(KIND_NODE_IMAGE) + kind create cluster --name dc2 --image $(KIND_NODE_IMAGE) + .PHONY: kind-load kind-load: ## Helper target for loading local dev images (run with `DEV_IMAGE=...` to load non-k8s images) kind load docker-image --name dc1 $(DEV_IMAGE) diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index e13c366335..f11f7e4df5 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -150,7 +150,12 @@ func (h *HelmCluster) Create(t *testing.T) { if h.ChartPath != "" { chartName = h.ChartPath } - helm.Install(t, h.helmOptions, chartName, h.releaseName) + + // Retry the install in case previous tests have not finished cleaning up. + retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 30}, t, func(r *retry.R) { + err := helm.InstallE(r, h.helmOptions, chartName, h.releaseName) + require.NoError(r, err) + }) k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName)) } diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index 3a3824fade..73112761fc 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -30,10 +30,10 @@ func TestPartitions_Connect(t *testing.T) { env := suite.Environment() cfg := suite.Config() - // Currently there is a bug which causes flakes when CNI is enabled - if cfg.EnableCNI { - t.Skipf("TODO(flaky): NET-5819") - } + // TODO: We are monitoring that NET-5819 is fixed, if this test is still flaking in CNI, re-enable this skip + //if cfg.EnableCNI { + // t.Skipf("TODO(flaky): NET-5819") + //} if !cfg.EnableEnterprise { t.Skipf("skipping this test because -enable-enterprise is not set") diff --git a/acceptance/tests/peering/peering_connect_namespaces_test.go b/acceptance/tests/peering/peering_connect_namespaces_test.go index 6b3ef75278..473e4976be 100644 --- a/acceptance/tests/peering/peering_connect_namespaces_test.go +++ b/acceptance/tests/peering/peering_connect_namespaces_test.go @@ -123,6 +123,7 @@ func TestPeering_ConnectNamespaces(t *testing.T) { var staticServerPeerCluster *consul.HelmCluster wg.Add(1) go func() { + defer wg.Done() staticServerPeerHelmValues := map[string]string{ "global.datacenter": staticServerPeer, } @@ -145,13 +146,12 @@ func TestPeering_ConnectNamespaces(t *testing.T) { // Install the first peer where static-server will be deployed in the static-server kubernetes context. staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) staticServerPeerCluster.Create(t) - - wg.Done() }() var staticClientPeerCluster *consul.HelmCluster wg.Add(1) go func() { + defer wg.Done() staticClientPeerHelmValues := map[string]string{ "global.datacenter": staticClientPeer, } @@ -171,8 +171,6 @@ func TestPeering_ConnectNamespaces(t *testing.T) { // Install the second peer where static-client will be deployed in the static-client kubernetes context. staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) staticClientPeerCluster.Create(t) - - wg.Done() }() // Wait for the clusters to start up diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index 7522ec3f75..6bab0aa909 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -86,6 +86,7 @@ func TestPeering_Connect(t *testing.T) { var staticServerPeerCluster *consul.HelmCluster wg.Add(1) go func() { + defer wg.Done() staticServerPeerHelmValues := map[string]string{ "global.datacenter": staticServerPeer, "terminatingGateways.enabled": "true", @@ -111,12 +112,12 @@ func TestPeering_Connect(t *testing.T) { // Install the first peer where static-server will be deployed in the static-server kubernetes context. staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) staticServerPeerCluster.Create(t) - wg.Done() }() var staticClientPeerCluster *consul.HelmCluster wg.Add(1) go func() { + defer wg.Done() // Create a second cluster to be peered with staticClientPeerHelmValues := map[string]string{ "global.datacenter": staticClientPeer, @@ -137,7 +138,6 @@ func TestPeering_Connect(t *testing.T) { // Install the second peer where static-client will be deployed in the static-client kubernetes context. staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) staticClientPeerCluster.Create(t) - wg.Done() }() // Wait for the clusters to start up diff --git a/acceptance/tests/peering/peering_gateway_test.go b/acceptance/tests/peering/peering_gateway_test.go index 631a5f73b9..421db98c71 100644 --- a/acceptance/tests/peering/peering_gateway_test.go +++ b/acceptance/tests/peering/peering_gateway_test.go @@ -70,6 +70,7 @@ func TestPeering_Gateway(t *testing.T) { var staticServerPeerCluster *consul.HelmCluster wg.Add(1) go func() { + defer wg.Done() staticServerPeerHelmValues := map[string]string{ "global.datacenter": staticServerPeer, } @@ -92,12 +93,12 @@ func TestPeering_Gateway(t *testing.T) { // Install the first peer where static-server will be deployed in the static-server kubernetes context. staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) staticServerPeerCluster.Create(t) - wg.Done() }() var staticClientPeerCluster *consul.HelmCluster wg.Add(1) go func() { + defer wg.Done() staticClientPeerHelmValues := map[string]string{ "global.datacenter": staticClientPeer, } @@ -117,7 +118,6 @@ func TestPeering_Gateway(t *testing.T) { // Install the second peer where static-client will be deployed in the static-client kubernetes context. staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) staticClientPeerCluster.Create(t) - wg.Done() }() // Wait for the clusters to start up diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index 3bb457903b..81636a5685 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -174,8 +174,6 @@ func TestFailover_Connect(t *testing.T) { "global.adminPartitions.enabled": "true", - "global.logLevel": "warn", - "global.acls.manageSystemACLs": strconv.FormatBool(c.ACLsEnabled), "connectInject.enabled": "true", @@ -196,6 +194,7 @@ func TestFailover_Connect(t *testing.T) { // create in same routine as 01-b depends on 01-a being created first wg.Add(1) go func() { + defer wg.Done() // Create the cluster-01-a defaultPartitionHelmValues := map[string]string{ "global.datacenter": cluster01Datacenter, @@ -266,12 +265,12 @@ func TestFailover_Connect(t *testing.T) { testClusters[keyCluster01b].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, testClusters[keyCluster01b].context, cfg, releaseName) testClusters[keyCluster01b].helmCluster.Create(t) - wg.Done() }() // Create cluster-02-a Cluster. wg.Add(1) go func() { + defer wg.Done() PeerOneHelmValues := map[string]string{ "global.datacenter": cluster02Datacenter, } @@ -285,12 +284,12 @@ func TestFailover_Connect(t *testing.T) { testClusters[keyCluster02a].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, testClusters[keyCluster02a].context, cfg, releaseName) testClusters[keyCluster02a].helmCluster.Create(t) - wg.Done() }() // Create cluster-03-a Cluster. wg.Add(1) go func() { + defer wg.Done() PeerTwoHelmValues := map[string]string{ "global.datacenter": cluster03Datacenter, } @@ -304,10 +303,10 @@ func TestFailover_Connect(t *testing.T) { testClusters[keyCluster03a].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, testClusters[keyCluster03a].context, cfg, releaseName) testClusters[keyCluster03a].helmCluster.Create(t) - wg.Done() }() // Wait for the clusters to start up + logger.Log(t, "waiting for clusters to start up . . .") wg.Wait() // Create a ProxyDefaults resource to configure services to use the mesh @@ -870,7 +869,7 @@ func localityForRegion(r string) api.Locality { func deployCustomizeAsync(t *testing.T, opts *terratestk8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, kustomizeDir string, wg *sync.WaitGroup) { wg.Add(1) go func() { + defer wg.Done() k8s.DeployKustomize(t, opts, noCleanupOnFailure, noCleanup, debugDirectory, kustomizeDir) - wg.Done() }() } diff --git a/control-plane/subcommand/get-consul-client-ca/command_test.go b/control-plane/subcommand/get-consul-client-ca/command_test.go index 68bdd918b1..990c7a2d22 100644 --- a/control-plane/subcommand/get-consul-client-ca/command_test.go +++ b/control-plane/subcommand/get-consul-client-ca/command_test.go @@ -157,6 +157,7 @@ func TestRun_ConsulServerAvailableLater(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) go func() { + defer wg.Done() // start the test server after 100ms time.Sleep(100 * time.Millisecond) a, err = testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { @@ -176,7 +177,6 @@ func TestRun_ConsulServerAvailableLater(t *testing.T) { c.KeyFile = keyFile }) require.NoError(t, err) - wg.Done() }() defer func() { if a != nil { From 56060e3819fa01758e791c42459ba4226cf63d75 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 14 Dec 2023 13:29:30 -0500 Subject: [PATCH 530/592] [NET-6721] Replicas support for MeshGW v2 Deployments (#3371) * Add replicas support for mesh gateways * handle nil values for replicas fields --- control-plane/gateways/deployment.go | 41 +++++++++++++------ .../gateway-resources/command_test.go | 10 +++++ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 441d19d7e2..36494ae739 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -9,8 +9,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" ) @@ -41,16 +39,15 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { nodeSelector map[string]string tolerations []corev1.Toleration deploymentConfig meshv2beta1.GatewayClassDeploymentConfig + replicas *meshv2beta1.GatewayClassReplicasConfig ) + if b.gcc != nil { containerConfig = b.gcc.Spec.Deployment.Container deploymentConfig = b.gcc.Spec.Deployment - - if b.gcc.Spec.Deployment.NodeSelector != nil { - nodeSelector = b.gcc.Spec.Deployment.NodeSelector - } - + nodeSelector = b.gcc.Spec.Deployment.NodeSelector tolerations = b.gcc.Spec.Deployment.Tolerations + replicas = b.gcc.Spec.Deployment.Replicas } container, err := consulDataplaneContainer(b.config, containerConfig, b.gateway.Name, b.gateway.Namespace) @@ -60,7 +57,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { return &appsv1.DeploymentSpec{ // TODO NET-6721 - Replicas: deploymentReplicaCount(nil, nil), + Replicas: deploymentReplicaCount(replicas, nil), Selector: &metav1.LabelSelector{ MatchLabels: b.Labels(), }, @@ -156,11 +153,31 @@ func compareDeployments(a, b *appsv1.Deployment) bool { return *b.Spec.Replicas == *a.Spec.Replicas } -func deploymentReplicaCount(deployment *pbmesh.Deployment, currentReplicas *int32) *int32 { - // TODO NET-6721 tamp replica count up and down based on min and max values - instanceValue := globalDefaultInstances +func deploymentReplicaCount(replicas *meshv2beta1.GatewayClassReplicasConfig, currentReplicas *int32) *int32 { + // if we have the replicas config, use it + if replicas != nil && replicas.Default != nil && currentReplicas == nil { + return replicas.Default + } + + // if we have the replicas config and the current replicas, use the min/max to ensure + // the current replicas are within the min/max range + if replicas != nil && currentReplicas != nil { + if replicas.Max != nil && *currentReplicas > *replicas.Max { + return replicas.Max + } + + if replicas.Min != nil && *currentReplicas < *replicas.Min { + return replicas.Min + } + + return currentReplicas + } + + // if we don't have the replicas config, use the current replicas if we have them if currentReplicas != nil { return currentReplicas } - return pointer.Int32(instanceValue) + + // otherwise use the global default + return pointer.Int32(globalDefaultInstances) } diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 0b3ddd16ec..35b9ae2e49 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -372,6 +372,10 @@ var validGWConfigurationKitchenSink = `gatewayClassConfigs: name: consul-mesh-gateway spec: deployment: + replicas: + min: 3 + default: 3 + max: 3 nodeSelector: beta.kubernetes.io/arch: amd64 beta.kubernetes.io/os: linux @@ -435,6 +439,7 @@ meshGateways: ` func TestRun_loadGatewayConfigs(t *testing.T) { + var replicasCount int32 = 3 testCases := map[string]struct { config string filename string @@ -448,6 +453,11 @@ func TestRun_loadGatewayConfigs(t *testing.T) { "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/os": "linux", }, + Replicas: &v2beta1.GatewayClassReplicasConfig{ + Default: &replicasCount, + Min: &replicasCount, + Max: &replicasCount, + }, Tolerations: []corev1.Toleration{ { Key: "key1", From 6c7161130276968f03e7f4d8eaff8261c82a3187 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Thu, 14 Dec 2023 13:42:26 -0500 Subject: [PATCH 531/592] NET-6663 Set gateway-kind in Workload metadata when it represents a xGateway Pod (#3365) Set gateway-kind in Workload metadata when it represents a xGateway Pod By setting the gateway-kind annotation on the Pods for a MeshGateway, we indicate to the Pod controller in consul-k8s that the Pod represents a mesh gateway (or api/terminating in the future). The Pod controller then passes this along as metadata on the Workload that it creates in Consul. The end result is that the sidecar and gateway proxy controllers can determine which Workloads they should generate ProxyStateTemplates for. --- control-plane/connect-inject/constants/constants.go | 4 ++++ .../connect-inject/controllers/pod/pod_controller.go | 8 +++++++- control-plane/gateways/deployment.go | 7 +++++-- control-plane/gateways/deployment_test.go | 7 +++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index ac846b36cb..35fa3beced 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -27,6 +27,10 @@ const ( // ProxyDefaultHealthPort is the default HTTP health check port for the proxy. ProxyDefaultHealthPort = 21000 + // MetaGatewayKind is the meta key name for indicating which kind of gateway a Pod is for, if any. + // The value should be one of "mesh", "api", or "terminating". + MetaGatewayKind = "gateway-kind" + // MetaKeyManagedBy is the meta key name for indicating which Kubernetes controller manages a Consul resource. MetaKeyManagedBy = "managed-by" diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 4cf7580d5c..8e33a42af3 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -664,10 +664,16 @@ func parseLocality(node corev1.Node) *pbcatalog.Locality { func metaFromPod(pod corev1.Pod) map[string]string { // TODO: allow custom workload metadata - return map[string]string{ + meta := map[string]string{ constants.MetaKeyKubeNS: pod.GetNamespace(), constants.MetaKeyManagedBy: constants.ManagedByPodValue, } + + if gatewayKind := pod.Annotations[constants.AnnotationGatewayKind]; gatewayKind != "" { + meta[constants.MetaGatewayKind] = gatewayKind + } + + return meta } // getHealthStatusFromPod checks the Pod for a "Ready" condition that is true. diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 36494ae739..7dd1d1fcbf 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -10,10 +10,12 @@ import ( "k8s.io/utils/pointer" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) const ( - globalDefaultInstances int32 = 1 + globalDefaultInstances int32 = 1 + meshGatewayAnnotationKind = "mesh-gateway" ) func (b *meshGatewayBuilder) Deployment() (*appsv1.Deployment, error) { @@ -65,7 +67,8 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { ObjectMeta: metav1.ObjectMeta{ Labels: b.Labels(), Annotations: map[string]string{ - "consul.hashicorp.com/mesh-inject": "false", + constants.AnnotationMeshInject: "false", + constants.AnnotationGatewayKind: meshGatewayAnnotationKind, }, }, Spec: corev1.PodSpec{ diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 6ad9ab920a..1398995573 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -16,6 +16,7 @@ import ( pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) func Test_meshGatewayBuilder_Deployment(t *testing.T) { @@ -72,7 +73,8 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "mesh.consul.hashicorp.com/managed-by": "consul-k8s", }, Annotations: map[string]string{ - "consul.hashicorp.com/mesh-inject": "false", + constants.AnnotationMeshInject: "false", + constants.AnnotationGatewayKind: meshGatewayAnnotationKind, }, }, Spec: corev1.PodSpec{ @@ -316,7 +318,8 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "mesh.consul.hashicorp.com/managed-by": "consul-k8s", }, Annotations: map[string]string{ - "consul.hashicorp.com/mesh-inject": "false", + constants.AnnotationMeshInject: "false", + constants.AnnotationGatewayKind: meshGatewayAnnotationKind, }, }, Spec: corev1.PodSpec{ From c8e5d9e27e78f021630d708b88a3387eeec90ab5 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 14 Dec 2023 14:15:12 -0500 Subject: [PATCH 532/592] [NET-6787] HostNetwork support for meshgw deployments (#3379) Add configuration for host network for mesh gateway deployments --- .../consul/templates/crd-gatewayclassconfigs.yaml | 4 ++++ .../templates/gateway-resources-configmap.yaml | 3 +++ .../api/mesh/v2beta1/gateway_class_config_types.go | 2 ++ ...sh.consul.hashicorp.com_gatewayclassconfigs.yaml | 4 ++++ control-plane/gateways/deployment.go | 13 ++++--------- .../subcommand/gateway-resources/command_test.go | 2 ++ 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 1edc3de8ad..68122e21bc 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -164,6 +164,10 @@ spec: type: object type: object type: object + hostNetwork: + description: HostNetwork specifies whether the gateway pods should + run on the host network + type: boolean initContainer: description: InitContainer contains config specific to the created Deployment's init container diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 6f0fcc1712..9da3e4eec8 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -60,6 +60,9 @@ data: {{- with .Values.meshGateway.nodeSelector }} nodeSelector: {{ fromYaml . | toJson }} {{- end }} + {{- with .Values.meshGateway.hostNetwork }} + hostNetwork: {{ . }} + {{- end }} priorityClassName: {{ toJson .Values.meshGateway.priorityClassName }} replicas: default: {{ .Values.meshGateway.replicas }} diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index cded2b18ab..fb47c56510 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -67,6 +67,8 @@ type GatewayClassDeploymentConfig struct { SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` // Tolerations specifies the tolerations to use on the created Deployment Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + // HostNetwork specifies whether the gateway pods should run on the host network + HostNetwork bool `json:"hostNetwork,omitempty"` } type GatewayClassReplicasConfig struct { diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index aab856de9e..a36f540772 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -160,6 +160,10 @@ spec: type: object type: object type: object + hostNetwork: + description: HostNetwork specifies whether the gateway pods should + run on the host network + type: boolean initContainer: description: InitContainer contains config specific to the created Deployment's init container diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 7dd1d1fcbf..bcd44a279a 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -38,18 +38,12 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { var ( containerConfig *meshv2beta1.GatewayClassContainerConfig - nodeSelector map[string]string - tolerations []corev1.Toleration deploymentConfig meshv2beta1.GatewayClassDeploymentConfig - replicas *meshv2beta1.GatewayClassReplicasConfig ) if b.gcc != nil { containerConfig = b.gcc.Spec.Deployment.Container deploymentConfig = b.gcc.Spec.Deployment - nodeSelector = b.gcc.Spec.Deployment.NodeSelector - tolerations = b.gcc.Spec.Deployment.Tolerations - replicas = b.gcc.Spec.Deployment.Replicas } container, err := consulDataplaneContainer(b.config, containerConfig, b.gateway.Name, b.gateway.Namespace) @@ -59,7 +53,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { return &appsv1.DeploymentSpec{ // TODO NET-6721 - Replicas: deploymentReplicaCount(replicas, nil), + Replicas: deploymentReplicaCount(deploymentConfig.Replicas, nil), Selector: &metav1.LabelSelector{ MatchLabels: b.Labels(), }, @@ -101,9 +95,10 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { }, }, }, - NodeSelector: nodeSelector, + NodeSelector: deploymentConfig.NodeSelector, PriorityClassName: deploymentConfig.PriorityClassName, - Tolerations: tolerations, + HostNetwork: deploymentConfig.HostNetwork, + Tolerations: deploymentConfig.Tolerations, ServiceAccountName: b.serviceAccountName(), }, }, diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 35b9ae2e49..0064e9545f 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -372,6 +372,7 @@ var validGWConfigurationKitchenSink = `gatewayClassConfigs: name: consul-mesh-gateway spec: deployment: + hostNetwork: true replicas: min: 3 default: 3 @@ -449,6 +450,7 @@ func TestRun_loadGatewayConfigs(t *testing.T) { config: validGWConfigurationKitchenSink, filename: "kitchenSinkConfig.yaml", expectedDeployment: v2beta1.GatewayClassDeploymentConfig{ + HostNetwork: true, NodeSelector: map[string]string{ "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/os": "linux", From 81312c15b9f83f427f067a09bb06e2d95c4f4749 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:23:27 -0600 Subject: [PATCH 533/592] NET-6788 - Mesh Gateway Host Port Support (#3381) * initial pass * update unit test * fix unit test --- charts/consul/templates/crd-gatewayclassconfigs.yaml | 6 ++++++ .../templates/gateway-resources-configmap.yaml | 3 +++ .../api/mesh/v2beta1/gateway_class_config_types.go | 4 +++- ...esh.consul.hashicorp.com_gatewayclassconfigs.yaml | 6 ++++++ control-plane/gateways/deployment.go | 6 ++++-- .../gateways/deployment_dataplane_container.go | 12 ++++-------- control-plane/gateways/deployment_test.go | 7 ++++++- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 68122e21bc..b87ba3e29f 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -107,11 +107,17 @@ spec: type: string type: object type: object + hostPort: + description: HostPort specifies a port to be exposed to the + external host network + format: int32 + type: integer portModifier: description: PortModifier specifies the value to be added to every port value for listeners on this gateway. This is generally used to avoid binding to privileged ports in the container. + format: int32 type: integer resources: description: Resources specifies the resource requirements diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 9da3e4eec8..1e81d2b2ad 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -51,6 +51,9 @@ data: logging: level: {{ default .Values.global.logLevel .Values.meshGateway.logLevel }} portModifier: {{ sub .Values.meshGateway.containerPort .Values.meshGateway.service.port }} + {{- if .Values.meshGateway.hostPort }} + hostPort: {{ .Values.meshGateway.hostPort }} + {{- end }} resources: {{ toJson .Values.meshGateway.resources }} initContainer: consul: diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index fb47c56510..9ac33a17ea 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -98,7 +98,9 @@ type GatewayClassContainerConfig struct { Resources *corev1.ResourceRequirements `json:"resources,omitempty"` // PortModifier specifies the value to be added to every port value for listeners on this gateway. // This is generally used to avoid binding to privileged ports in the container. - PortModifier int `json:"portModifier,omitempty"` + PortModifier int32 `json:"portModifier,omitempty"` + // HostPort specifies a port to be exposed to the external host network + HostPort int32 `json:"hostPort,omitempty"` } type GatewayClassRoleConfig struct { diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index a36f540772..e200b0ad21 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -103,11 +103,17 @@ spec: type: string type: object type: object + hostPort: + description: HostPort specifies a port to be exposed to the + external host network + format: int32 + type: integer portModifier: description: PortModifier specifies the value to be added to every port value for listeners on this gateway. This is generally used to avoid binding to privileged ports in the container. + format: int32 type: integer resources: description: Resources specifies the resource requirements diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index bcd44a279a..a2ed759643 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -37,13 +37,15 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { } var ( - containerConfig *meshv2beta1.GatewayClassContainerConfig + containerConfig meshv2beta1.GatewayClassContainerConfig deploymentConfig meshv2beta1.GatewayClassDeploymentConfig ) if b.gcc != nil { - containerConfig = b.gcc.Spec.Deployment.Container deploymentConfig = b.gcc.Spec.Deployment + if deploymentConfig.Container != nil { + containerConfig = *b.gcc.Spec.Deployment.Container + } } container, err := consulDataplaneContainer(b.config, containerConfig, b.gateway.Name, b.gateway.Namespace) diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go index feb2bc569b..8c99acec8f 100644 --- a/control-plane/gateways/deployment_dataplane_container.go +++ b/control-plane/gateways/deployment_dataplane_container.go @@ -27,17 +27,14 @@ const ( volumeName = "consul-connect-inject-data" ) -func consulDataplaneContainer(config GatewayConfig, containerConfig *v2beta1.GatewayClassContainerConfig, name, namespace string) (corev1.Container, error) { +func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.GatewayClassContainerConfig, name, namespace string) (corev1.Container, error) { // Extract the service account token's volume mount. var ( err error bearerTokenFile string ) - var resources *corev1.ResourceRequirements - if containerConfig != nil { - resources = containerConfig.Resources - } + resources := containerConfig.Resources if config.AuthMethod != "" { bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" @@ -112,11 +109,10 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig *v2beta1.Gat wanPort := corev1.ContainerPort{ Name: "wan", ContainerPort: int32(constants.DefaultWANPort), + HostPort: containerConfig.HostPort, } - if containerConfig != nil { - wanPort.ContainerPort = int32(443 + containerConfig.PortModifier) - } + wanPort.ContainerPort = 443 + containerConfig.PortModifier container.Ports = append(container.Ports, wanPort) diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 1398995573..33a7aca980 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -43,6 +43,10 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { gcc: &meshv2beta1.GatewayClassConfig{ Spec: meshv2beta1.GatewayClassConfigSpec{ Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + Container: &meshv2beta1.GatewayClassContainerConfig{ + HostPort: 8080, + PortModifier: 8000, + }, NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, Replicas: &meshv2beta1.GatewayClassReplicasConfig{ Default: pointer.Int32(1), @@ -184,6 +188,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { { Name: "wan", ContainerPort: 8443, + HostPort: 8080, }, }, Env: []corev1.EnvVar{ @@ -428,7 +433,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, { Name: "wan", - ContainerPort: 8443, + ContainerPort: 443, }, }, Env: []corev1.EnvVar{ From 13e2d217ed3d32a71a7527f120d25a601bacd80b Mon Sep 17 00:00:00 2001 From: Anita Akaeze Date: Sat, 16 Dec 2023 11:23:25 +0100 Subject: [PATCH 534/592] NET-6393: Create/update/delete Service on MeshGateway reconcile (#3328) * NET-6393: Create/update/delete Service on MeshGateway reconcile * add comment to code --- .../v2beta1/gateway_class_config_types.go | 1 + .../controllersv2/mesh_gateway_controller.go | 61 ++++++++- .../mesh_gateway_controller_test.go | 81 +++++++++++ control-plane/gateways/annotations.go | 26 ++++ control-plane/gateways/service.go | 50 +++++++ control-plane/gateways/service_test.go | 126 ++++++++++++++++++ .../subcommand/gateway-resources/command.go | 1 + 7 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 control-plane/gateways/annotations.go create mode 100644 control-plane/gateways/service.go create mode 100644 control-plane/gateways/service_test.go diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index 9ac33a17ea..d59c37c1c9 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -115,6 +115,7 @@ type GatewayClassServiceConfig struct { GatewayClassAnnotationsAndLabels `json:",inline"` // Type specifies the type of Service to use (LoadBalancer, ClusterIP, etc.) + // +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer Type *corev1.ServiceType `json:"type,omitempty"` } diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index 13106d1c32..45143190dd 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -12,6 +12,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/equality" k8serr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -128,7 +129,28 @@ func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Req return fmt.Errorf("unable to create role binding: %w", err) } - // TODO NET-6393 + //Create Service + + mergeServiceOp := func(ctx context.Context, existingObject, object client.Object) error { + existingService, ok := existingObject.(*corev1.Service) + if !ok && existingService != nil { + return fmt.Errorf("unable to infer existing service type") + } + builtService, ok := object.(*corev1.Service) + if !ok { + return fmt.Errorf("unable to infer built service type") + } + + mergedService := mergeService(existingService, builtService) + + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, mergedService, func() error { return nil }) + return err + } + + err = r.opIfNewOrOwned(ctx, resource, &corev1.Service{}, builder.Service(), mergeServiceOp) + if err != nil { + return fmt.Errorf("unable to create service: %w", err) + } // Create deployment @@ -265,3 +287,40 @@ func (r *MeshGatewayController) getGatewayClassForGateway(ctx context.Context, g } return &gatewayClass, nil } + +func areServicesEqual(a, b *corev1.Service) bool { + // If either service "a" or "b" is nil, don't want to try and merge the nil service + if a == nil || b == nil { + return true + } + + if !equality.Semantic.DeepEqual(a.Annotations, b.Annotations) { + return false + } + + if len(b.Spec.Ports) != len(a.Spec.Ports) { + return false + } + + for i, port := range a.Spec.Ports { + otherPort := b.Spec.Ports[i] + if port.Port != otherPort.Port || port.Protocol != otherPort.Protocol { + return false + } + } + return true +} + +// mergeService is used to keep annotations and ports from the `from` Service +// to the `to` service. This prevents an infinite reconciliation loop when +// Kubernetes adds this configuration back in. +func mergeService(from, to *corev1.Service) *corev1.Service { + if areServicesEqual(from, to) { + return to + } + + to.Annotations = from.Annotations + to.Spec.Ports = from.Spec.Ports + + return to +} diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go index 14c8ebcf16..8b5ca629ea 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go @@ -333,6 +333,87 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { expectedResult: ctrl.Result{}, expectedErr: nil, // The Reconcile should be a no-op }, + // Service + { + name: "MeshGateway created with no existing Service", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "consul", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "consul", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + postReconcile: func(t *testing.T, c client.Client) { + // Verify Service was created + key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} + assert.NoError(t, c.Get(context.Background(), key, &corev1.Service{})) + }, + }, + { + name: "MeshGateway created with existing Service not owned by gateway", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + expectedErr: errResourceNotOwned, + }, + { + name: "MeshGateway created with existing Service owned by gateway", + k8sObjects: []runtime.Object{ + &v2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + UID: "abc123", + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "mesh-gateway", + OwnerReferences: []metav1.OwnerReference{ + { + UID: "abc123", + Name: "mesh-gateway", + }, + }, + }, + }, + }, + request: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "mesh-gateway", + }, + }, + expectedResult: ctrl.Result{}, + expectedErr: nil, // The Reconcile should be a no-op + }, } for _, testCase := range testCases { diff --git a/control-plane/gateways/annotations.go b/control-plane/gateways/annotations.go new file mode 100644 index 0000000000..01de8133b6 --- /dev/null +++ b/control-plane/gateways/annotations.go @@ -0,0 +1,26 @@ +package gateways + +func (b *meshGatewayBuilder) Annotations() map[string]string { + + var ( + annotationNamesToCopy = []string{} + annotationNamesToAdd = map[string]string{} + ) + + if b.gcc != nil { + annotationNamesToCopy = b.gcc.Spec.Service.Annotations.InheritFromGateway + annotationNamesToAdd = b.gcc.Spec.Service.Annotations.Set + } + + out := map[string]string{} + + for _, v := range annotationNamesToCopy { + out[v] = b.gateway.Annotations[v] + } + + for k, v := range annotationNamesToAdd { + out[k] = v + } + + return out +} diff --git a/control-plane/gateways/service.go b/control-plane/gateways/service.go new file mode 100644 index 0000000000..43936d8bd9 --- /dev/null +++ b/control-plane/gateways/service.go @@ -0,0 +1,50 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gateways + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +const port = 443 + +func (b *meshGatewayBuilder) Service() *corev1.Service { + var ( + containerConfig *meshv2beta1.GatewayClassContainerConfig + portModifier = 0 + serviceType = corev1.ServiceType("") + ) + + if b.gcc != nil { + containerConfig = b.gcc.Spec.Deployment.Container + portModifier = containerConfig.PortModifier + serviceType = *b.gcc.Spec.Service.Type + } + + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + Labels: b.Labels(), + Annotations: b.Annotations(), + }, + Spec: corev1.ServiceSpec{ + Selector: b.Labels(), + Type: serviceType, + Ports: []corev1.ServicePort{ + { + Name: "wan", + Port: port, + TargetPort: intstr.IntOrString{ + IntVal: int32(port + portModifier), + }, + }, + }, + }, + } +} diff --git a/control-plane/gateways/service_test.go b/control-plane/gateways/service_test.go new file mode 100644 index 0000000000..ccfa4ded6e --- /dev/null +++ b/control-plane/gateways/service_test.go @@ -0,0 +1,126 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gateways + +import ( + "testing" + + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +func Test_meshGatewayBuilder_Service(t *testing.T) { + lbType := corev1.ServiceTypeLoadBalancer + + type fields struct { + gateway *meshv2beta1.MeshGateway + config GatewayConfig + gcc *meshv2beta1.GatewayClassConfig + } + tests := []struct { + name string + fields fields + want *corev1.Service + }{ + { + name: "service resource crd created - happy path", + fields: fields{ + gateway: &meshv2beta1.MeshGateway{ + Spec: pbmesh.MeshGateway{ + GatewayClassName: "test-gateway-class", + }, + }, + config: GatewayConfig{}, + gcc: &meshv2beta1.GatewayClassConfig{ + Spec: meshv2beta1.GatewayClassConfigSpec{ + Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + Container: &meshv2beta1.GatewayClassContainerConfig{ + PortModifier: 8000, + }, + }, + Service: meshv2beta1.GatewayClassServiceConfig{ + Type: &lbType, + }, + }, + }, + }, + want: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + Annotations: map[string]string{}, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + Type: corev1.ServiceTypeLoadBalancer, + Ports: []corev1.ServicePort{ + { + Name: "wan", + Port: int32(443), + TargetPort: intstr.IntOrString{ + IntVal: int32(8443), + }, + }, + }, + }, + Status: corev1.ServiceStatus{}, + }, + }, + { + name: "create service resource crd - gcc is nil", + fields: fields{ + gateway: &meshv2beta1.MeshGateway{ + Spec: pbmesh.MeshGateway{ + GatewayClassName: "test-gateway-class", + }, + }, + config: GatewayConfig{}, + gcc: nil, + }, + want: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + Annotations: map[string]string{}, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + }, + Type: "", + Ports: []corev1.ServicePort{ + { + Name: "wan", + Port: int32(443), + TargetPort: intstr.IntOrString{ + IntVal: int32(443), + }, + }, + }, + }, + Status: corev1.ServiceStatus{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &meshGatewayBuilder{ + gateway: tt.fields.gateway, + config: tt.fields.config, + gcc: tt.fields.gcc, + } + result := b.Service() + assert.Equalf(t, tt.want, result, "Service()") + }) + } +} diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 413c87b497..1b93d6bba6 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -436,6 +436,7 @@ func (c *Command) createV2GatewayClassAndClassConfigs(ctx context.Context, compo return err } + // TODO: NET-6934 remove hardcoded values class := &v2beta1.GatewayClass{ ObjectMeta: metav1.ObjectMeta{Name: cfg.Name, Labels: labels}, TypeMeta: metav1.TypeMeta{Kind: "GatewayClass"}, From f323396b01529083113ab9a0b6d2f61bec548113 Mon Sep 17 00:00:00 2001 From: Anita Akaeze Date: Mon, 18 Dec 2023 13:57:22 +0100 Subject: [PATCH 535/592] fix build issues as a result of portModifier (#3386) --- control-plane/gateways/service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/control-plane/gateways/service.go b/control-plane/gateways/service.go index 43936d8bd9..39205c109c 100644 --- a/control-plane/gateways/service.go +++ b/control-plane/gateways/service.go @@ -11,12 +11,12 @@ import ( meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" ) -const port = 443 +const port = int32(443) func (b *meshGatewayBuilder) Service() *corev1.Service { var ( containerConfig *meshv2beta1.GatewayClassContainerConfig - portModifier = 0 + portModifier = int32(0) serviceType = corev1.ServiceType("") ) @@ -41,7 +41,7 @@ func (b *meshGatewayBuilder) Service() *corev1.Service { Name: "wan", Port: port, TargetPort: intstr.IntOrString{ - IntVal: int32(port + portModifier), + IntVal: port + portModifier, }, }, }, From 70c2cbb723ddeb79db3b7558aa69fc5e33c1f6d1 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:32:23 -0600 Subject: [PATCH 536/592] Net 6779 - Mesh Gateway eployment topology spread constraints (#3376) * update crds, add to deployment object * update configmap * update unit test * fix indentation --- .../templates/crd-gatewayclassconfigs.yaml | 175 ++++++++++++++++++ .../gateway-resources-configmap.yaml | 4 + .../v2beta1/gateway_class_config_types.go | 3 + ...sul.hashicorp.com_gatewayclassconfigs.yaml | 175 ++++++++++++++++++ control-plane/gateways/deployment.go | 11 +- control-plane/gateways/deployment_test.go | 14 ++ 6 files changed, 377 insertions(+), 5 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index b87ba3e29f..c31012afb8 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -518,6 +518,181 @@ spec: type: string type: object type: array + topologySpreadConstraints: + description: 'TopologySpreadConstraints is a feature that controls + how pods are spead across your topology. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/' + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine + the number of pods in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: MatchLabelKeys is a set of pod label keys to + select the pods over which spreading will be calculated. + The keys are used to lookup values from the incoming pod + labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading + will be calculated for the incoming pod. Keys that don't + exist in the incoming pod labels will be ignored. A null + or empty list means only match against labelSelector. + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: 'MaxSkew describes the degree to which pods + may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, + it is the maximum permitted difference between the number + of matching pods in the target topology and the global + minimum. The global minimum is the minimum number of matching + pods in an eligible domain or zero if the number of eligible + domains is less than MinDomains. For example, in a 3-zone + cluster, MaxSkew is set to 1, and pods with the same labelSelector + spread as 2/2/1: In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | | P P | P P | P | - + if MaxSkew is 1, incoming pod can only be scheduled to + zone3 to become 2/2/2; scheduling it onto zone1(zone2) + would make the ActualSkew(3-1) on zone1(zone2) violate + MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled + onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, + it is used to give higher precedence to topologies that + satisfy it. It''s a required field. Default value is 1 + and 0 is not allowed.' + format: int32 + type: integer + minDomains: + description: "MinDomains indicates a minimum number of eligible + domains. When the number of eligible domains with matching + topology keys is less than minDomains, Pod Topology Spread + treats \"global minimum\" as 0, and then the calculation + of Skew is performed. And when the number of eligible + domains with matching topology keys equals or greater + than minDomains, this value has no effect on scheduling. + As a result, when the number of eligible domains is less + than minDomains, scheduler won't schedule more than maxSkew + Pods to those domains. If value is nil, the constraint + behaves as if MinDomains is equal to 1. Valid values are + integers greater than 0. When value is not nil, WhenUnsatisfiable + must be DoNotSchedule. \n For example, in a 3-zone cluster, + MaxSkew is set to 2, MinDomains is set to 5 and pods with + the same labelSelector spread as 2/2/2: | zone1 | zone2 + | zone3 | | P P | P P | P P | The number of domains + is less than 5(MinDomains), so \"global minimum\" is treated + as 0. In this situation, new pod with the same labelSelector + cannot be scheduled, because computed skew will be 3(3 + - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. \n This is a beta field and requires + the MinDomainsInPodTopologySpread feature gate to be enabled + (enabled by default)." + format: int32 + type: integer + nodeAffinityPolicy: + description: "NodeAffinityPolicy indicates how we will treat + Pod's nodeAffinity/nodeSelector when calculating pod topology + spread skew. Options are: - Honor: only nodes matching + nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes + are included in the calculations. \n If this value is + nil, the behavior is equivalent to the Honor policy. This + is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread + feature flag." + type: string + nodeTaintsPolicy: + description: "NodeTaintsPolicy indicates how we will treat + node taints when calculating pod topology spread skew. + Options are: - Honor: nodes without taints, along with + tainted nodes for which the incoming pod has a toleration, + are included. - Ignore: node taints are ignored. All nodes + are included. \n If this value is nil, the behavior is + equivalent to the Ignore policy. This is a beta-level + feature default enabled by the NodeInclusionPolicyInPodTopologySpread + feature flag." + type: string + topologyKey: + description: TopologyKey is the key of node labels. Nodes + that have a label with this key and identical values are + considered to be in the same topology. We consider each + as a "bucket", and try to put balanced number + of pods into each bucket. We define a domain as a particular + instance of a topology. Also, we define an eligible domain + as a domain whose nodes meet the requirements of nodeAffinityPolicy + and nodeTaintsPolicy. e.g. If TopologyKey is "kubernetes.io/hostname", + each Node is a domain of that topology. And, if TopologyKey + is "topology.kubernetes.io/zone", each zone is a domain + of that topology. It's a required field. + type: string + whenUnsatisfiable: + description: 'WhenUnsatisfiable indicates how to deal with + a pod if it doesn''t satisfy the spread constraint. - + DoNotSchedule (default) tells the scheduler not to schedule + it. - ScheduleAnyway tells the scheduler to schedule the + pod in any location, but giving higher precedence to topologies + that would help reduce the skew. A constraint is considered + "Unsatisfiable" for an incoming pod if and only if every + possible node assignment for that pod would violate "MaxSkew" + on some topology. For example, in a 3-zone cluster, MaxSkew + is set to 1, and pods with the same labelSelector spread + as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming + pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) + as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). + In other words, the cluster can still be imbalanced, but + scheduler won''t make it *more* imbalanced. It''s a required + field.' + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array type: object labels: description: Labels are applied to the created resource diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 1e81d2b2ad..69d2c6abbf 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -67,6 +67,10 @@ data: hostNetwork: {{ . }} {{- end }} priorityClassName: {{ toJson .Values.meshGateway.priorityClassName }} + {{- if .Values.meshGateway.topologySpreadConstraints }} + topologySpreadConstraints: + {{ tpl .Values.meshGateway.topologySpreadConstraints . | nindent 16 | trim }} + {{- end }} replicas: default: {{ .Values.meshGateway.replicas }} min: {{ .Values.meshGateway.replicas }} diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index d59c37c1c9..046d9721c3 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -69,6 +69,9 @@ type GatewayClassDeploymentConfig struct { Tolerations []corev1.Toleration `json:"tolerations,omitempty"` // HostNetwork specifies whether the gateway pods should run on the host network HostNetwork bool `json:"hostNetwork,omitempty"` + // TopologySpreadConstraints is a feature that controls how pods are spead across your topology. + // More info: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ + TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` } type GatewayClassReplicasConfig struct { diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index e200b0ad21..a70f21218b 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -514,6 +514,181 @@ spec: type: string type: object type: array + topologySpreadConstraints: + description: 'TopologySpreadConstraints is a feature that controls + how pods are spead across your topology. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/' + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine + the number of pods in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: MatchLabelKeys is a set of pod label keys to + select the pods over which spreading will be calculated. + The keys are used to lookup values from the incoming pod + labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading + will be calculated for the incoming pod. Keys that don't + exist in the incoming pod labels will be ignored. A null + or empty list means only match against labelSelector. + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: 'MaxSkew describes the degree to which pods + may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, + it is the maximum permitted difference between the number + of matching pods in the target topology and the global + minimum. The global minimum is the minimum number of matching + pods in an eligible domain or zero if the number of eligible + domains is less than MinDomains. For example, in a 3-zone + cluster, MaxSkew is set to 1, and pods with the same labelSelector + spread as 2/2/1: In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | | P P | P P | P | - + if MaxSkew is 1, incoming pod can only be scheduled to + zone3 to become 2/2/2; scheduling it onto zone1(zone2) + would make the ActualSkew(3-1) on zone1(zone2) violate + MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled + onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, + it is used to give higher precedence to topologies that + satisfy it. It''s a required field. Default value is 1 + and 0 is not allowed.' + format: int32 + type: integer + minDomains: + description: "MinDomains indicates a minimum number of eligible + domains. When the number of eligible domains with matching + topology keys is less than minDomains, Pod Topology Spread + treats \"global minimum\" as 0, and then the calculation + of Skew is performed. And when the number of eligible + domains with matching topology keys equals or greater + than minDomains, this value has no effect on scheduling. + As a result, when the number of eligible domains is less + than minDomains, scheduler won't schedule more than maxSkew + Pods to those domains. If value is nil, the constraint + behaves as if MinDomains is equal to 1. Valid values are + integers greater than 0. When value is not nil, WhenUnsatisfiable + must be DoNotSchedule. \n For example, in a 3-zone cluster, + MaxSkew is set to 2, MinDomains is set to 5 and pods with + the same labelSelector spread as 2/2/2: | zone1 | zone2 + | zone3 | | P P | P P | P P | The number of domains + is less than 5(MinDomains), so \"global minimum\" is treated + as 0. In this situation, new pod with the same labelSelector + cannot be scheduled, because computed skew will be 3(3 + - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. \n This is a beta field and requires + the MinDomainsInPodTopologySpread feature gate to be enabled + (enabled by default)." + format: int32 + type: integer + nodeAffinityPolicy: + description: "NodeAffinityPolicy indicates how we will treat + Pod's nodeAffinity/nodeSelector when calculating pod topology + spread skew. Options are: - Honor: only nodes matching + nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes + are included in the calculations. \n If this value is + nil, the behavior is equivalent to the Honor policy. This + is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread + feature flag." + type: string + nodeTaintsPolicy: + description: "NodeTaintsPolicy indicates how we will treat + node taints when calculating pod topology spread skew. + Options are: - Honor: nodes without taints, along with + tainted nodes for which the incoming pod has a toleration, + are included. - Ignore: node taints are ignored. All nodes + are included. \n If this value is nil, the behavior is + equivalent to the Ignore policy. This is a beta-level + feature default enabled by the NodeInclusionPolicyInPodTopologySpread + feature flag." + type: string + topologyKey: + description: TopologyKey is the key of node labels. Nodes + that have a label with this key and identical values are + considered to be in the same topology. We consider each + as a "bucket", and try to put balanced number + of pods into each bucket. We define a domain as a particular + instance of a topology. Also, we define an eligible domain + as a domain whose nodes meet the requirements of nodeAffinityPolicy + and nodeTaintsPolicy. e.g. If TopologyKey is "kubernetes.io/hostname", + each Node is a domain of that topology. And, if TopologyKey + is "topology.kubernetes.io/zone", each zone is a domain + of that topology. It's a required field. + type: string + whenUnsatisfiable: + description: 'WhenUnsatisfiable indicates how to deal with + a pod if it doesn''t satisfy the spread constraint. - + DoNotSchedule (default) tells the scheduler not to schedule + it. - ScheduleAnyway tells the scheduler to schedule the + pod in any location, but giving higher precedence to topologies + that would help reduce the skew. A constraint is considered + "Unsatisfiable" for an incoming pod if and only if every + possible node assignment for that pod would violate "MaxSkew" + on some topology. For example, in a 3-zone cluster, MaxSkew + is set to 1, and pods with the same labelSelector spread + as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming + pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) + as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). + In other words, the cluster can still be imbalanced, but + scheduler won''t make it *more* imbalanced. It''s a required + field.' + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array type: object labels: description: Labels are applied to the created resource diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index a2ed759643..d7370e77ec 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -97,11 +97,12 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { }, }, }, - NodeSelector: deploymentConfig.NodeSelector, - PriorityClassName: deploymentConfig.PriorityClassName, - HostNetwork: deploymentConfig.HostNetwork, - Tolerations: deploymentConfig.Tolerations, - ServiceAccountName: b.serviceAccountName(), + NodeSelector: deploymentConfig.NodeSelector, + PriorityClassName: deploymentConfig.PriorityClassName, + TopologySpreadConstraints: deploymentConfig.TopologySpreadConstraints, + HostNetwork: deploymentConfig.HostNetwork, + Tolerations: deploymentConfig.Tolerations, + ServiceAccountName: b.serviceAccountName(), }, }, }, nil diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 33a7aca980..0454ecf190 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -54,6 +54,13 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Max: pointer.Int32(8), }, PriorityClassName: "priorityclassname", + TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ + { + MaxSkew: 1, + TopologyKey: "key", + WhenUnsatisfiable: "DoNotSchedule", + }, + }, }, }, }, @@ -262,6 +269,13 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, PriorityClassName: "priorityclassname", + TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ + { + MaxSkew: 1, + TopologyKey: "key", + WhenUnsatisfiable: "DoNotSchedule", + }, + }, Affinity: &corev1.Affinity{ NodeAffinity: nil, PodAffinity: nil, From f3f56058df0cc6f8b820cdabe435475d10a9eb97 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:04:29 -0600 Subject: [PATCH 537/592] Update test to match PreparedQuery SamenessGroup bugfix. (#3387) Update test to assert the fixed behavior of PR #7773 in Consul Enterprise. --- acceptance/tests/sameness/sameness_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index 81636a5685..e00502463e 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -535,8 +535,8 @@ func TestFailover_Connect(t *testing.T) { }{ {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "ap1", peerName: "", namespace: "ns2"}}, {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "ap1", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, + {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "ap1", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, }, }, { @@ -574,9 +574,9 @@ func TestFailover_Connect(t *testing.T) { // We're resetting the scale, so make sure we have all the new IP addresses saved testClusters.setServerIP(t) - for _, v := range sc.failovers { + for i, v := range sc.failovers { // Verify Failover (If this is the first check, then just verifying we're starting with the right server) - logger.Log(t, "checking service failover") + logger.Log(t, "checking service failover", i) if cfg.EnableTransparentProxy { sc.server.serviceTargetCheck(t, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) @@ -589,10 +589,10 @@ func TestFailover_Connect(t *testing.T) { // e.g kubectl --context kind-dc1 --namespace ns1 exec -i deploy/static-client -c static-client \ // -- dig @test-3lmypr-consul-dns.default static-server.service.ns2.ns.mine.sg.ap1.ap.consul // Verify DNS. - logger.Log(t, "verifying dns") + logger.Log(t, "verifying dns", i) sc.server.dnsFailoverCheck(t, cfg, releaseName, v.failoverServer) - logger.Log(t, "verifying prepared query") + logger.Log(t, "verifying prepared query", i) sc.server.preparedQueryFailoverCheck(t, releaseName, v.expectedPQ, v.failoverServer) // Scale down static-server on the current failover, will fail over to the next. From 2e5c86b184f2d650b08b7c22857e098aa347b587 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Mon, 18 Dec 2023 15:29:42 -0500 Subject: [PATCH 538/592] [NET-6938] Create workloads in Consul for mesh gateway pods (#3382) * Create workload in Consul for gateway pods Gateway pods are not mesh-injected because it doesn't make sense for an Envoy proxy workload to have a sidecar; however, they still need workloads created in Consul for them. * Log when pod controller creates workload in Consul * Disable t-proxy probe overwrite for mesh gateway pods * Update test assertions * Add test case for gateway pod reconciliation --- control-plane/connect-inject/common/common.go | 10 +++ .../controllers/pod/pod_controller.go | 14 +++- .../pod/pod_controller_ent_test.go | 28 +++---- .../controllers/pod/pod_controller_test.go | 79 ++++++++++++------- control-plane/gateways/deployment.go | 7 +- control-plane/gateways/deployment_test.go | 10 ++- 6 files changed, 99 insertions(+), 49 deletions(-) diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 99372f7aec..9bbaeaab58 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -192,6 +192,16 @@ func HasBeenMeshInjected(pod corev1.Pod) bool { return false } +func IsGateway(pod corev1.Pod) bool { + if pod.Annotations == nil { + return false + } + if anno, ok := pod.Annotations[constants.AnnotationGatewayKind]; ok && anno != "" { + return true + } + return false +} + // ConsulNamespaceIsNotFound checks the gRPC error code and message to determine // if a namespace does not exist. If the namespace exists this function returns false, true otherwise. func ConsulNamespaceIsNotFound(err error) bool { diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index 8e33a42af3..f0d319a475 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -150,7 +150,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.Log.Info("retrieved", "name", pod.Name, "ns", pod.Namespace) - if inject.HasBeenMeshInjected(pod) { + if inject.HasBeenMeshInjected(pod) || inject.IsGateway(pod) { // It is possible the pod was scheduled but doesn't have an allocated IP yet, so safely requeue if pod.Status.PodIP == "" { @@ -336,9 +336,11 @@ func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { } data := inject.ToProtoAny(workload) + resourceID := getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()) + r.Log.Info("registering workload with Consul", getLogFieldsForResource(resourceID)...) req := &pbresource.WriteRequest{ Resource: &pbresource.Resource{ - Id: getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), + Id: resourceID, Metadata: metaFromPod(pod), Data: data, }, @@ -762,3 +764,11 @@ func getDestinationsID(name, namespace, partition string) *pbresource.ID { }, } } + +func getLogFieldsForResource(id *pbresource.ID) []any { + return []any{ + "name", id.Name, + "ns", id.Tenancy.Namespace, + "partition", id.Tenancy.Partition, + } +} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index 1c074dc940..a116122a03 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -101,7 +101,7 @@ func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { expectedConsulNamespace: constants.DefaultConsulNS, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "kitchen sink new pod, non-default ns and partition", @@ -127,7 +127,7 @@ func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { expectedConsulNamespace: "bar", expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "new pod with namespace prefix", @@ -194,7 +194,7 @@ func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { expectedConsulNamespace: constants.DefaultConsulNS, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), expectedDestinations: createDestinations(), }, { @@ -273,12 +273,12 @@ func TestReconcileUpdatePodWithMirrorNamespaces(t *testing.T) { existingConsulNamespace: "bar", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), expectedConsulNamespace: "bar", expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, } @@ -311,7 +311,7 @@ func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { existingConsulNamespace: "bar", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), expectedConsulNamespace: "bar", }, @@ -330,7 +330,7 @@ func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { existingConsulNamespace: "bar", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), existingDestinations: createDestinations(), expectedConsulNamespace: "bar", @@ -415,7 +415,7 @@ func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { expectedConsulNamespace: constants.DefaultConsulNS, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "new pod with explicit destinations, ns and partition", @@ -439,7 +439,7 @@ func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { expectedConsulNamespace: constants.DefaultConsulNS, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), expectedDestinations: createDestinations(), }, { @@ -466,7 +466,7 @@ func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { expectedConsulNamespace: "a-penguin-walks-into-a-bar", expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "namespace in Consul does not exist", @@ -543,12 +543,12 @@ func TestReconcileUpdatePodWithDestinationNamespace(t *testing.T) { existingConsulNamespace: "a-penguin-walks-into-a-bar", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), expectedConsulNamespace: "a-penguin-walks-into-a-bar", expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, } @@ -581,7 +581,7 @@ func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { existingConsulNamespace: "a-penguin-walks-into-a-bar", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), expectedConsulNamespace: "a-penguin-walks-into-a-bar", }, @@ -600,7 +600,7 @@ func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { existingConsulNamespace: "a-penguin-walks-into-a-bar", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), existingDestinations: createDestinations(), expectedConsulNamespace: "a-penguin-walks-into-a-bar", diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 413f9ac49a..55b5fd5ba1 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -785,7 +785,7 @@ func TestProxyConfigurationDelete(t *testing.T) { { name: "proxy configuration delete", pod: createPod("foo", "", true, true), - existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + existingProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, } @@ -1308,7 +1308,7 @@ func TestReconcileCreatePod(t *testing.T) { testCases := []testCase{ { - name: "vanilla new pod", + name: "vanilla new mesh-injected pod", podName: "foo", k8sObjects: func() []runtime.Object { pod := createPod("foo", "", true, true) @@ -1322,7 +1322,26 @@ func TestReconcileCreatePod(t *testing.T) { overwriteProbes: true, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + expectedProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + }, + { + name: "vanilla new gateway pod (not mesh-injected)", + podName: "foo", + k8sObjects: func() []runtime.Object { + pod := createPod("foo", "", false, true) + pod.Annotations[constants.AnnotationGatewayKind] = "mesh-gateway" + pod.Annotations[constants.AnnotationMeshInject] = "false" + pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "false" + + return []runtime.Object{pod} + }, + tproxy: true, + telemetry: true, + metrics: true, + overwriteProbes: true, + expectedWorkload: createWorkload(), + expectedHealthStatus: createPassingHealthStatus(), + expectedProxyConfiguration: createProxyConfiguration("foo", false, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "pod in ignored namespace", @@ -1380,7 +1399,7 @@ func TestReconcileCreatePod(t *testing.T) { overwriteProbes: true, expectedWorkload: createWorkload(), expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + expectedProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), expectedDestinations: createDestinations(), }, { @@ -1827,14 +1846,14 @@ func TestReconcileDeletePod(t *testing.T) { podName: "foo", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + existingProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), }, { name: "annotated delete pod", podName: "foo", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), + existingProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), existingDestinations: createDestinations(), }, { @@ -1842,7 +1861,7 @@ func TestReconcileDeletePod(t *testing.T) { podName: "foo", existingWorkload: createWorkload(), existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), + existingProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), aclsEnabled: true, }, } @@ -1989,32 +2008,14 @@ func createCriticalHealthStatus(name string, namespace string) *pbcatalog.Health // createProxyConfiguration creates a proxyConfiguration that matches the pod from createPod, // assuming that metrics, telemetry, and overwrite probes are enabled separately. -func createProxyConfiguration(podName string, mode pbmesh.ProxyMode) *pbmesh.ProxyConfiguration { +func createProxyConfiguration(podName string, overwriteProbes bool, mode pbmesh.ProxyMode) *pbmesh.ProxyConfiguration { mesh := &pbmesh.ProxyConfiguration{ Workloads: &pbcatalog.WorkloadSelector{ Names: []string{podName}, }, DynamicConfig: &pbmesh.DynamicConfig{ - Mode: mode, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, + Mode: mode, + ExposeConfig: nil, }, BootstrapConfig: &pbmesh.BootstrapConfig{ PrometheusBindAddr: "0.0.0.0:1234", @@ -2022,6 +2023,28 @@ func createProxyConfiguration(podName string, mode pbmesh.ProxyMode) *pbmesh.Pro }, } + if overwriteProbes { + mesh.DynamicConfig.ExposeConfig = &pbmesh.ExposeConfig{ + ExposePaths: []*pbmesh.ExposePath{ + { + ListenerPort: 20400, + LocalPathPort: 2001, + Path: "/livez", + }, + { + ListenerPort: 20300, + LocalPathPort: 2000, + Path: "/readyz", + }, + { + ListenerPort: 20500, + LocalPathPort: 2002, + Path: "/startupz", + }, + }, + } + } + if mode == pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT { mesh.DynamicConfig.TransparentProxy = &pbmesh.TransparentProxy{ OutboundListenerPort: 15001, diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index d7370e77ec..d03ed18765 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -63,8 +63,13 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { ObjectMeta: metav1.ObjectMeta{ Labels: b.Labels(), Annotations: map[string]string{ - constants.AnnotationMeshInject: "false", + // Indicate that this pod is a mesh gateway pod so that the Pod controller, + // consul-k8s CLI, etc. can key off of it constants.AnnotationGatewayKind: meshGatewayAnnotationKind, + // It's not logical to add a proxy sidecar since our workload is itself a proxy + constants.AnnotationMeshInject: "false", + // This functionality only applies when proxy sidecars are used + constants.AnnotationTransparentProxyOverwriteProbes: "false", }, }, Spec: corev1.PodSpec{ diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 0454ecf190..f60ee1d3d2 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -84,8 +84,9 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "mesh.consul.hashicorp.com/managed-by": "consul-k8s", }, Annotations: map[string]string{ - constants.AnnotationMeshInject: "false", - constants.AnnotationGatewayKind: meshGatewayAnnotationKind, + constants.AnnotationGatewayKind: meshGatewayAnnotationKind, + constants.AnnotationMeshInject: "false", + constants.AnnotationTransparentProxyOverwriteProbes: "false", }, }, Spec: corev1.PodSpec{ @@ -337,8 +338,9 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "mesh.consul.hashicorp.com/managed-by": "consul-k8s", }, Annotations: map[string]string{ - constants.AnnotationMeshInject: "false", - constants.AnnotationGatewayKind: meshGatewayAnnotationKind, + constants.AnnotationGatewayKind: meshGatewayAnnotationKind, + constants.AnnotationMeshInject: "false", + constants.AnnotationTransparentProxyOverwriteProbes: "false", }, }, Spec: corev1.PodSpec{ From 8e3bb5aec8df14083beb1eb508f48d3f27d41173 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Mon, 18 Dec 2023 17:32:39 -0500 Subject: [PATCH 539/592] [NET-6789] DNSPolicy support for meshgw v2 deployments (#3380) * Set dns policy for mesh v2 deployment * regenerate files after rebase * Update dnspolicy field on gatewayclassconfigs to be of the corev1 type and to use kubebuilder validations * Clean up from dns policy change --- .../templates/crd-gatewayclassconfigs.yaml | 27 ++++++++++++++----- .../gateway-resources-configmap.yaml | 3 +++ .../v2beta1/gateway_class_config_types.go | 17 +++++++----- .../api/mesh/v2beta1/zz_generated.deepcopy.go | 7 +++++ ...sul.hashicorp.com_gatewayclassconfigs.yaml | 27 ++++++++++++++----- control-plane/gateways/annotations.go | 3 +++ control-plane/gateways/deployment.go | 1 + .../gateway-resources/command_test.go | 2 ++ 8 files changed, 66 insertions(+), 21 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index c31012afb8..0dfa0779d9 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -91,7 +91,7 @@ spec: type: object container: description: Container contains config specific to the created - Deployment's container + Deployment's container. properties: consul: description: Consul specifies configuration for the consul-dataplane @@ -170,13 +170,22 @@ spec: type: object type: object type: object + dnsPolicy: + description: DNSPolicy specifies the dns policy to use. These + are set on a per pod basis. + enum: + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string hostNetwork: description: HostNetwork specifies whether the gateway pods should - run on the host network + run on the host network. type: boolean initContainer: description: InitContainer contains config specific to the created - Deployment's init container + Deployment's init container. properties: consul: description: Consul specifies configuration for the consul-k8s-control-plane @@ -275,11 +284,11 @@ spec: type: object priorityClassName: description: PriorityClassName specifies the priority class name - to use on the created Deployment + to use on the created Deployment. type: string replicas: description: Replicas specifies the configuration to control the - number of replicas for the created Deployment + number of replicas for the created Deployment. properties: default: description: Default is the number of replicas assigned to @@ -303,7 +312,7 @@ spec: type: object securityContext: description: SecurityContext specifies the security context for - the created Deployment's Pod + the created Deployment's Pod. properties: fsGroup: description: "A special supplemental group that applies to @@ -478,7 +487,7 @@ spec: type: object tolerations: description: Tolerations specifies the tolerations to use on the - created Deployment + created Deployment. items: description: The pod this Toleration is attached to tolerates any taint that matches the triple using @@ -850,6 +859,10 @@ spec: type: description: Type specifies the type of Service to use (LoadBalancer, ClusterIP, etc.) + enum: + - ClusterIP + - NodePort + - LoadBalancer type: string type: object serviceAccount: diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 69d2c6abbf..a01b52371b 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -66,6 +66,9 @@ data: {{- with .Values.meshGateway.hostNetwork }} hostNetwork: {{ . }} {{- end }} + {{- with .Values.meshGateway.dnsPolicy }} + dnsPolicy: {{ . }} + {{- end }} priorityClassName: {{ toJson .Values.meshGateway.priorityClassName }} {{- if .Values.meshGateway.topologySpreadConstraints }} topologySpreadConstraints: diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index 046d9721c3..1ef470d959 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -48,9 +48,9 @@ type GatewayClassConfigSpec struct { type GatewayClassDeploymentConfig struct { GatewayClassAnnotationsAndLabels `json:",inline"` - // Container contains config specific to the created Deployment's container + // Container contains config specific to the created Deployment's container. Container *GatewayClassContainerConfig `json:"container,omitempty"` - // InitContainer contains config specific to the created Deployment's init container + // InitContainer contains config specific to the created Deployment's init container. InitContainer *GatewayClassInitContainerConfig `json:"initContainer,omitempty"` // NodeSelector is a feature that constrains the scheduling of a pod to nodes that // match specified labels. @@ -59,19 +59,22 @@ type GatewayClassDeploymentConfig struct { // influence the placement of workloads based on node attributes. // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ NodeSelector map[string]string `json:"nodeSelector,omitempty"` - // PriorityClassName specifies the priority class name to use on the created Deployment + // PriorityClassName specifies the priority class name to use on the created Deployment. PriorityClassName string `json:"priorityClassName,omitempty"` - // Replicas specifies the configuration to control the number of replicas for the created Deployment + // Replicas specifies the configuration to control the number of replicas for the created Deployment. Replicas *GatewayClassReplicasConfig `json:"replicas,omitempty"` - // SecurityContext specifies the security context for the created Deployment's Pod + // SecurityContext specifies the security context for the created Deployment's Pod. SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` - // Tolerations specifies the tolerations to use on the created Deployment + // Tolerations specifies the tolerations to use on the created Deployment. Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - // HostNetwork specifies whether the gateway pods should run on the host network + // HostNetwork specifies whether the gateway pods should run on the host network. HostNetwork bool `json:"hostNetwork,omitempty"` // TopologySpreadConstraints is a feature that controls how pods are spead across your topology. // More info: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` + // DNSPolicy specifies the dns policy to use. These are set on a per pod basis. + // +kubebuilder:validation:Enum=Default;ClusterFirst;ClusterFirstWithHostNet;None + DNSPolicy corev1.DNSPolicy `json:"dnsPolicy,omitempty"` } type GatewayClassReplicasConfig struct { diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index 7ba0a78919..e64c2a1145 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -355,6 +355,13 @@ func (in *GatewayClassDeploymentConfig) DeepCopyInto(out *GatewayClassDeployment (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.TopologySpreadConstraints != nil { + in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints + *out = make([]v1.TopologySpreadConstraint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassDeploymentConfig. diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index a70f21218b..9aa253add4 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -87,7 +87,7 @@ spec: type: object container: description: Container contains config specific to the created - Deployment's container + Deployment's container. properties: consul: description: Consul specifies configuration for the consul-dataplane @@ -166,13 +166,22 @@ spec: type: object type: object type: object + dnsPolicy: + description: DNSPolicy specifies the dns policy to use. These + are set on a per pod basis. + enum: + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string hostNetwork: description: HostNetwork specifies whether the gateway pods should - run on the host network + run on the host network. type: boolean initContainer: description: InitContainer contains config specific to the created - Deployment's init container + Deployment's init container. properties: consul: description: Consul specifies configuration for the consul-k8s-control-plane @@ -271,11 +280,11 @@ spec: type: object priorityClassName: description: PriorityClassName specifies the priority class name - to use on the created Deployment + to use on the created Deployment. type: string replicas: description: Replicas specifies the configuration to control the - number of replicas for the created Deployment + number of replicas for the created Deployment. properties: default: description: Default is the number of replicas assigned to @@ -299,7 +308,7 @@ spec: type: object securityContext: description: SecurityContext specifies the security context for - the created Deployment's Pod + the created Deployment's Pod. properties: fsGroup: description: "A special supplemental group that applies to @@ -474,7 +483,7 @@ spec: type: object tolerations: description: Tolerations specifies the tolerations to use on the - created Deployment + created Deployment. items: description: The pod this Toleration is attached to tolerates any taint that matches the triple using @@ -846,6 +855,10 @@ spec: type: description: Type specifies the type of Service to use (LoadBalancer, ClusterIP, etc.) + enum: + - ClusterIP + - NodePort + - LoadBalancer type: string type: object serviceAccount: diff --git a/control-plane/gateways/annotations.go b/control-plane/gateways/annotations.go index 01de8133b6..62dbb2ae5a 100644 --- a/control-plane/gateways/annotations.go +++ b/control-plane/gateways/annotations.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways func (b *meshGatewayBuilder) Annotations() map[string]string { diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index d03ed18765..24969c4dd0 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -108,6 +108,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { HostNetwork: deploymentConfig.HostNetwork, Tolerations: deploymentConfig.Tolerations, ServiceAccountName: b.serviceAccountName(), + DNSPolicy: deploymentConfig.DNSPolicy, }, }, }, nil diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 0064e9545f..44cb1b3ea1 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -373,6 +373,7 @@ var validGWConfigurationKitchenSink = `gatewayClassConfigs: spec: deployment: hostNetwork: true + dnsPolicy: ClusterFirst replicas: min: 3 default: 3 @@ -451,6 +452,7 @@ func TestRun_loadGatewayConfigs(t *testing.T) { filename: "kitchenSinkConfig.yaml", expectedDeployment: v2beta1.GatewayClassDeploymentConfig{ HostNetwork: true, + DNSPolicy: "ClusterFirst", NodeSelector: map[string]string{ "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/os": "linux", From 8716b2fc25ac48b0367e3184f73b6a0415851a5b Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 19 Dec 2023 15:30:48 -0500 Subject: [PATCH 540/592] [NET-6766] Implement computing of annotations + labels for all created resources (#3397) * Implement computing of annotations + labels for all created resources * Add test coverage * Add pkg-level variables for label key and default labels * Improve docstrings * Remove TODO for work that has been completed * Add un-inherited annotation to gateway set for unit test * Add more robust tests for annotations + labels * Exercise unhandled type * gofmt * Improve UX based on code review feedback --- control-plane/gateways/annotations.go | 29 -- control-plane/gateways/deployment.go | 14 +- control-plane/gateways/deployment_test.go | 34 +- control-plane/gateways/labels.go | 10 - control-plane/gateways/metadata.go | 142 +++++++++ control-plane/gateways/metadata_test.go | 292 ++++++++++++++++++ control-plane/gateways/role.go | 14 +- control-plane/gateways/service.go | 6 +- control-plane/gateways/service_test.go | 20 +- control-plane/gateways/serviceaccount.go | 7 +- control-plane/gateways/serviceaccount_test.go | 7 +- 11 files changed, 476 insertions(+), 99 deletions(-) delete mode 100644 control-plane/gateways/annotations.go delete mode 100644 control-plane/gateways/labels.go create mode 100644 control-plane/gateways/metadata.go create mode 100644 control-plane/gateways/metadata_test.go diff --git a/control-plane/gateways/annotations.go b/control-plane/gateways/annotations.go deleted file mode 100644 index 62dbb2ae5a..0000000000 --- a/control-plane/gateways/annotations.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -func (b *meshGatewayBuilder) Annotations() map[string]string { - - var ( - annotationNamesToCopy = []string{} - annotationNamesToAdd = map[string]string{} - ) - - if b.gcc != nil { - annotationNamesToCopy = b.gcc.Spec.Service.Annotations.InheritFromGateway - annotationNamesToAdd = b.gcc.Spec.Service.Annotations.Set - } - - out := map[string]string{} - - for _, v := range annotationNamesToCopy { - out[v] = b.gateway.Annotations[v] - } - - for k, v := range annotationNamesToAdd { - out[k] = v - } - - return out -} diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 24969c4dd0..c525dbc835 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -22,9 +22,10 @@ func (b *meshGatewayBuilder) Deployment() (*appsv1.Deployment, error) { spec, err := b.deploymentSpec() return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.Name, - Namespace: b.gateway.Namespace, - Labels: b.Labels(), + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + Labels: b.labelsForDeployment(), + Annotations: b.annotationsForDeployment(), }, Spec: *spec, }, err @@ -54,14 +55,13 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { } return &appsv1.DeploymentSpec{ - // TODO NET-6721 Replicas: deploymentReplicaCount(deploymentConfig.Replicas, nil), Selector: &metav1.LabelSelector{ - MatchLabels: b.Labels(), + MatchLabels: b.labelsForDeployment(), }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: b.Labels(), + Labels: b.labelsForDeployment(), Annotations: map[string]string{ // Indicate that this pod is a mesh gateway pod so that the Pod controller, // consul-k8s CLI, etc. can key off of it @@ -94,7 +94,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { Weight: 1, PodAffinityTerm: corev1.PodAffinityTerm{ LabelSelector: &metav1.LabelSelector{ - MatchLabels: b.Labels(), + MatchLabels: b.labelsForDeployment(), }, TopologyKey: "kubernetes.io/hostname", }, diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index f60ee1d3d2..4f682a16ec 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -67,22 +67,17 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, want: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + Labels: defaultLabels, + Annotations: map[string]string{}, }, Spec: appsv1.DeploymentSpec{ Replicas: pointer.Int32(1), Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + MatchLabels: defaultLabels, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + Labels: defaultLabels, Annotations: map[string]string{ constants.AnnotationGatewayKind: meshGatewayAnnotationKind, constants.AnnotationMeshInject: "false", @@ -286,9 +281,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Weight: 1, PodAffinityTerm: corev1.PodAffinityTerm{ LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + MatchLabels: defaultLabels, }, TopologyKey: "kubernetes.io/hostname", }, @@ -321,22 +314,17 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, want: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + Labels: defaultLabels, + Annotations: map[string]string{}, }, Spec: appsv1.DeploymentSpec{ Replicas: pointer.Int32(1), Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + MatchLabels: defaultLabels, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + Labels: defaultLabels, Annotations: map[string]string{ constants.AnnotationGatewayKind: meshGatewayAnnotationKind, constants.AnnotationMeshInject: "false", @@ -530,9 +518,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Weight: 1, PodAffinityTerm: corev1.PodAffinityTerm{ LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + MatchLabels: defaultLabels, }, TopologyKey: "kubernetes.io/hostname", }, diff --git a/control-plane/gateways/labels.go b/control-plane/gateways/labels.go deleted file mode 100644 index d816a4c3f0..0000000000 --- a/control-plane/gateways/labels.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -func (b *meshGatewayBuilder) Labels() map[string]string { - return map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - } -} diff --git a/control-plane/gateways/metadata.go b/control-plane/gateways/metadata.go new file mode 100644 index 0000000000..98e685d9fd --- /dev/null +++ b/control-plane/gateways/metadata.go @@ -0,0 +1,142 @@ +package gateways + +import ( + "golang.org/x/exp/slices" + + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +const labelManagedBy = "mesh.consul.hashicorp.com/managed-by" + +var defaultLabels = map[string]string{labelManagedBy: "consul-k8s"} + +func (b *meshGatewayBuilder) annotationsForDeployment() map[string]string { + if b.gcc == nil { + return map[string]string{} + } + return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.Deployment.Annotations, b.gcc.Spec.Annotations) +} + +func (b *meshGatewayBuilder) annotationsForRole() map[string]string { + if b.gcc == nil { + return map[string]string{} + } + return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.Role.Annotations, b.gcc.Spec.Annotations) +} + +func (b *meshGatewayBuilder) annotationsForRoleBinding() map[string]string { + if b.gcc == nil { + return map[string]string{} + } + return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.RoleBinding.Annotations, b.gcc.Spec.Annotations) +} + +func (b *meshGatewayBuilder) annotationsForService() map[string]string { + if b.gcc == nil { + return map[string]string{} + } + return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.Service.Annotations, b.gcc.Spec.Annotations) +} + +func (b *meshGatewayBuilder) annotationsForServiceAccount() map[string]string { + if b.gcc == nil { + return map[string]string{} + } + return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.ServiceAccount.Annotations, b.gcc.Spec.Annotations) +} + +func (b *meshGatewayBuilder) labelsForDeployment() map[string]string { + if b.gcc == nil { + return defaultLabels + } + + labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.Deployment.Labels, b.gcc.Spec.Labels) + for k, v := range defaultLabels { + labels[k] = v + } + return labels +} + +func (b *meshGatewayBuilder) labelsForRole() map[string]string { + if b.gcc == nil { + return defaultLabels + } + + labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.Role.Labels, b.gcc.Spec.Labels) + for k, v := range defaultLabels { + labels[k] = v + } + return labels +} + +func (b *meshGatewayBuilder) labelsForRoleBinding() map[string]string { + if b.gcc == nil { + return defaultLabels + } + + labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.RoleBinding.Labels, b.gcc.Spec.Labels) + for k, v := range defaultLabels { + labels[k] = v + } + return labels +} + +func (b *meshGatewayBuilder) labelsForService() map[string]string { + if b.gcc == nil { + return defaultLabels + } + + labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.Service.Labels, b.gcc.Spec.Labels) + for k, v := range defaultLabels { + labels[k] = v + } + return labels +} + +func (b *meshGatewayBuilder) labelsForServiceAccount() map[string]string { + if b.gcc == nil { + return defaultLabels + } + + labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.ServiceAccount.Labels, b.gcc.Spec.Labels) + for k, v := range defaultLabels { + labels[k] = v + } + return labels +} + +// computeAnnotationsOrLabels compiles a set of annotations or labels +// using the following priority, highest to lowest: +// 1. inherited keys specified on the primary +// 2. added key-values specified on the primary +// 3. inherited keys specified on the secondary +// 4. added key-values specified on the secondary +func computeAnnotationsOrLabels(inheritFrom map[string]string, primary, secondary v2beta1.GatewayClassAnnotationsLabelsConfig) map[string]string { + out := map[string]string{} + + // Add key-values specified on the secondary + for k, v := range secondary.Set { + out[k] = v + } + + // Inherit keys specified on the secondary + for k, v := range inheritFrom { + if slices.Contains(secondary.InheritFromGateway, k) { + out[k] = v + } + } + + // Add key-values specified on the primary + for k, v := range primary.Set { + out[k] = v + } + + // Inherit keys specified on the primary + for k, v := range inheritFrom { + if slices.Contains(primary.InheritFromGateway, k) { + out[k] = v + } + } + + return out +} diff --git a/control-plane/gateways/metadata_test.go b/control-plane/gateways/metadata_test.go new file mode 100644 index 0000000000..d95a4497e5 --- /dev/null +++ b/control-plane/gateways/metadata_test.go @@ -0,0 +1,292 @@ +package gateways + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +func TestMeshGatewayBuilder_Annotations(t *testing.T) { + gateway := &meshv2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "gateway-annotation": "true", // Will be inherited by all resources + "gateway-deployment-annotation": "true", // Will be inherited by Deployment + "gateway-role-annotation": "true", // Will be inherited by Role + "gateway-role-binding-annotation": "true", // Will be inherited by RoleBinding + "gateway-service-annotation": "true", // Will be inherited by Service + "gateway-service-account-annotation": "true", // Will be inherited by ServiceAccount + }, + }, + } + + gatewayClassConfig := &meshv2beta1.GatewayClassConfig{ + Spec: meshv2beta1.GatewayClassConfigSpec{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-annotation"}, + Set: map[string]string{"global-set": "true"}, + }, + }, + Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-deployment-annotation"}, + Set: map[string]string{"deployment-set": "true"}, + }, + }, + }, + Role: meshv2beta1.GatewayClassRoleConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-role-annotation"}, + Set: map[string]string{"role-set": "true"}, + }, + }, + }, + RoleBinding: meshv2beta1.GatewayClassRoleBindingConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-role-binding-annotation"}, + Set: map[string]string{"role-binding-set": "true"}, + }, + }, + }, + Service: meshv2beta1.GatewayClassServiceConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-service-annotation"}, + Set: map[string]string{"service-set": "true"}, + }, + }, + }, + ServiceAccount: meshv2beta1.GatewayClassServiceAccountConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-service-account-annotation"}, + Set: map[string]string{"service-account-set": "true"}, + }, + }, + }, + }, + } + + b := NewMeshGatewayBuilder(gateway, GatewayConfig{}, gatewayClassConfig) + + for _, testCase := range []struct { + Actual map[string]string + Expected map[string]string + }{ + { + Actual: b.annotationsForDeployment(), + Expected: map[string]string{ + "gateway-annotation": "true", + "global-set": "true", + "gateway-deployment-annotation": "true", + "deployment-set": "true", + }, + }, + { + Actual: b.annotationsForRole(), + Expected: map[string]string{ + "gateway-annotation": "true", + "global-set": "true", + "gateway-role-annotation": "true", + "role-set": "true", + }, + }, + { + Actual: b.annotationsForRoleBinding(), + Expected: map[string]string{ + "gateway-annotation": "true", + "global-set": "true", + "gateway-role-binding-annotation": "true", + "role-binding-set": "true", + }, + }, + { + Actual: b.annotationsForService(), + Expected: map[string]string{ + "gateway-annotation": "true", + "global-set": "true", + "gateway-service-annotation": "true", + "service-set": "true", + }, + }, + { + Actual: b.annotationsForServiceAccount(), + Expected: map[string]string{ + "gateway-annotation": "true", + "global-set": "true", + "gateway-service-account-annotation": "true", + "service-account-set": "true", + }, + }, + } { + assert.Equal(t, testCase.Expected, testCase.Actual) + } +} + +func TestNewMeshGatewayBuilder_Labels(t *testing.T) { + gateway := &meshv2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "gateway-label": "true", // Will be inherited by all resources + "gateway-deployment-label": "true", // Will be inherited by Deployment + "gateway-role-label": "true", // Will be inherited by Role + "gateway-role-binding-label": "true", // Will be inherited by RoleBinding + "gateway-service-label": "true", // Will be inherited by Service + "gateway-service-account-label": "true", // Will be inherited by ServiceAccount + }, + }, + } + + gatewayClassConfig := &meshv2beta1.GatewayClassConfig{ + Spec: meshv2beta1.GatewayClassConfigSpec{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-label"}, + Set: map[string]string{"global-set": "true"}, + }, + }, + Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-deployment-label"}, + Set: map[string]string{"deployment-set": "true"}, + }, + }, + }, + Role: meshv2beta1.GatewayClassRoleConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-role-label"}, + Set: map[string]string{"role-set": "true"}, + }, + }, + }, + RoleBinding: meshv2beta1.GatewayClassRoleBindingConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-role-binding-label"}, + Set: map[string]string{"role-binding-set": "true"}, + }, + }, + }, + Service: meshv2beta1.GatewayClassServiceConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-service-label"}, + Set: map[string]string{"service-set": "true"}, + }, + }, + }, + ServiceAccount: meshv2beta1.GatewayClassServiceAccountConfig{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{"gateway-service-account-label"}, + Set: map[string]string{"service-account-set": "true"}, + }, + }, + }, + }, + } + + b := NewMeshGatewayBuilder(gateway, GatewayConfig{}, gatewayClassConfig) + + for _, testCase := range []struct { + Actual map[string]string + Expected map[string]string + }{ + { + Actual: b.labelsForDeployment(), + Expected: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + "gateway-label": "true", + "global-set": "true", + "gateway-deployment-label": "true", + "deployment-set": "true", + }, + }, + { + Actual: b.labelsForRole(), + Expected: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + "gateway-label": "true", + "global-set": "true", + "gateway-role-label": "true", + "role-set": "true", + }, + }, + { + Actual: b.labelsForRoleBinding(), + Expected: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + "gateway-label": "true", + "global-set": "true", + "gateway-role-binding-label": "true", + "role-binding-set": "true", + }, + }, + { + Actual: b.labelsForService(), + Expected: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + "gateway-label": "true", + "global-set": "true", + "gateway-service-label": "true", + "service-set": "true", + }, + }, + { + Actual: b.labelsForServiceAccount(), + Expected: map[string]string{ + "mesh.consul.hashicorp.com/managed-by": "consul-k8s", + "gateway-label": "true", + "global-set": "true", + "gateway-service-account-label": "true", + "service-account-set": "true", + }, + }, + } { + assert.Equal(t, testCase.Expected, testCase.Actual) + } +} + +func Test_computeAnnotationsOrLabels(t *testing.T) { + gatewaySet := map[string]string{ + "service.beta.kubernetes.io/aws-load-balancer-internal": "true", // Will not be inherited + "service.beta.kubernetes.io/aws-load-balancer-name": "my-lb", // Will be inherited + } + + primary := meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{ + "service.beta.kubernetes.io/aws-load-balancer-name", + }, + Set: map[string]string{ + "created-by": "nathancoleman", // Only exists in primary + "owning-team": "consul-gateway-management", // Will override secondary + }, + } + + secondary := meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + InheritFromGateway: []string{}, + Set: map[string]string{ + "created-on": "kubernetes", // Only exists in secondary + "owning-team": "consul", // Will be overridden by primary + }, + } + + actual := computeAnnotationsOrLabels(gatewaySet, primary, secondary) + expected := map[string]string{ + "created-by": "nathancoleman", // Set by primary + "created-on": "kubernetes", // Set by secondary + "owning-team": "consul-gateway-management", // Set by primary, overrode secondary + "service.beta.kubernetes.io/aws-load-balancer-name": "my-lb", // Inherited from gateway + } + + assert.Equal(t, expected, actual) +} diff --git a/control-plane/gateways/role.go b/control-plane/gateways/role.go index 96745b8db6..3264bb60b0 100644 --- a/control-plane/gateways/role.go +++ b/control-plane/gateways/role.go @@ -11,9 +11,10 @@ import ( func (b *meshGatewayBuilder) Role() *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.Name, - Namespace: b.gateway.Namespace, - Labels: b.Labels(), + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + Labels: b.labelsForRole(), + Annotations: b.annotationsForRole(), }, Rules: []rbacv1.PolicyRule{}, } @@ -22,9 +23,10 @@ func (b *meshGatewayBuilder) Role() *rbacv1.Role { func (b *meshGatewayBuilder) RoleBinding() *rbacv1.RoleBinding { return &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.Name, - Namespace: b.gateway.Namespace, - Labels: b.Labels(), + Name: b.gateway.Name, + Namespace: b.gateway.Namespace, + Labels: b.labelsForRoleBinding(), + Annotations: b.annotationsForRoleBinding(), }, Subjects: []rbacv1.Subject{ { diff --git a/control-plane/gateways/service.go b/control-plane/gateways/service.go index 39205c109c..ec3d14aad5 100644 --- a/control-plane/gateways/service.go +++ b/control-plane/gateways/service.go @@ -30,11 +30,11 @@ func (b *meshGatewayBuilder) Service() *corev1.Service { ObjectMeta: metav1.ObjectMeta{ Name: b.gateway.Name, Namespace: b.gateway.Namespace, - Labels: b.Labels(), - Annotations: b.Annotations(), + Labels: b.labelsForService(), + Annotations: b.annotationsForService(), }, Spec: corev1.ServiceSpec{ - Selector: b.Labels(), + Selector: b.labelsForDeployment(), Type: serviceType, Ports: []corev1.ServicePort{ { diff --git a/control-plane/gateways/service_test.go b/control-plane/gateways/service_test.go index ccfa4ded6e..3ec9251b83 100644 --- a/control-plane/gateways/service_test.go +++ b/control-plane/gateways/service_test.go @@ -52,16 +52,12 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { }, want: &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + Labels: defaultLabels, Annotations: map[string]string{}, }, Spec: corev1.ServiceSpec{ - Selector: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, - Type: corev1.ServiceTypeLoadBalancer, + Selector: defaultLabels, + Type: corev1.ServiceTypeLoadBalancer, Ports: []corev1.ServicePort{ { Name: "wan", @@ -88,16 +84,12 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { }, want: &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, + Labels: defaultLabels, Annotations: map[string]string{}, }, Spec: corev1.ServiceSpec{ - Selector: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - }, - Type: "", + Selector: defaultLabels, + Type: "", Ports: []corev1.ServicePort{ { Name: "wan", diff --git a/control-plane/gateways/serviceaccount.go b/control-plane/gateways/serviceaccount.go index 8459179521..1a0b32c275 100644 --- a/control-plane/gateways/serviceaccount.go +++ b/control-plane/gateways/serviceaccount.go @@ -11,9 +11,10 @@ import ( func (b *meshGatewayBuilder) ServiceAccount() *corev1.ServiceAccount { return &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: b.serviceAccountName(), - Namespace: b.gateway.Namespace, - Labels: b.Labels(), + Name: b.serviceAccountName(), + Namespace: b.gateway.Namespace, + Labels: b.labelsForServiceAccount(), + Annotations: b.annotationsForServiceAccount(), }, } } diff --git a/control-plane/gateways/serviceaccount_test.go b/control-plane/gateways/serviceaccount_test.go index 702fd1fbde..ff0fb4e878 100644 --- a/control-plane/gateways/serviceaccount_test.go +++ b/control-plane/gateways/serviceaccount_test.go @@ -23,9 +23,10 @@ func TestNewMeshGatewayBuilder_ServiceAccount(t *testing.T) { expected := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - Labels: b.Labels(), + Namespace: "default", + Name: "mesh-gateway", + Labels: defaultLabels, + Annotations: map[string]string{}, }, } From 3047e16a3667b7254eb0477c7ba3d23600808444 Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Tue, 19 Dec 2023 17:12:46 -0800 Subject: [PATCH 541/592] [Net-5510][Net-5455]: CRD controller should only patch the finalizer in the metadata of a CRD, rather than the whole object (#3362) Finalizer patcher Co-authored-by: Derek Menteer --- .changelog/3362.txt | 3 + .../controllers/configentry_controller.go | 26 +++-- .../controlplanerequestlimit_controller.go | 3 + .../exportedservices_controller.go | 3 + .../controllers/finalizer_patch.go | 78 +++++++++++++++ .../controllers/finalizer_patch_test.go | 97 +++++++++++++++++++ .../controllers/ingressgateway_controller.go | 1 + .../controllers/jwtprovider_controller.go | 3 + .../controllers/mesh_controller.go | 3 + .../controllers/proxydefaults_controller.go | 3 + .../controllers/samenessgroups_controller.go | 3 + .../controllers/servicedefaults_controller.go | 3 + .../serviceintentions_controller.go | 3 + .../controllers/serviceresolver_controller.go | 3 + .../controllers/servicerouter_controller.go | 3 + .../controllers/servicesplitter_controller.go | 3 + .../terminatinggateway_controller.go | 3 + 17 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 .changelog/3362.txt create mode 100644 control-plane/config-entries/controllers/finalizer_patch.go create mode 100644 control-plane/config-entries/controllers/finalizer_patch_test.go diff --git a/.changelog/3362.txt b/.changelog/3362.txt new file mode 100644 index 0000000000..82a7e56f0a --- /dev/null +++ b/.changelog/3362.txt @@ -0,0 +1,3 @@ +```release-note:bug-fix +crd-controllers: When the CRD controller reconciles a config entry CRD, only patch finalizers in the metadata of the CRD, rather than updating the entire entry, which causes changes to the CRD spec (such as adding in unspecified zero values) when using a Kubernetes client such as kubectl with `replace` mode. +``` diff --git a/control-plane/config-entries/controllers/configentry_controller.go b/control-plane/config-entries/controllers/configentry_controller.go index f1a4e316e8..03d30c0aa1 100644 --- a/control-plane/config-entries/controllers/configentry_controller.go +++ b/control-plane/config-entries/controllers/configentry_controller.go @@ -31,6 +31,7 @@ import ( const ( FinalizerName = "finalizers.consul.hashicorp.com" ConsulAgentError = "ConsulAgentError" + ConsulPatchError = "ConsulPatchError" ExternallyManagedConfigError = "ExternallyManagedConfigError" MigrationFailedError = "MigrationFailedError" ) @@ -38,8 +39,13 @@ const ( // Controller is implemented by CRD-specific controllers. It is used by // ConfigEntryController to abstract CRD-specific controllers. type Controller interface { - // Update updates the state of the whole object. - Update(context.Context, client.Object, ...client.UpdateOption) error + // AddFinalizersPatch creates a patch with the original finalizers with new ones appended to the end. + AddFinalizersPatch(obj client.Object, finalizers ...string) *FinalizerPatch + // RemoveFinalizersPatch creates a patch to remove a set of finalizers, while preserving the order. + RemoveFinalizersPatch(obj client.Object, finalizers ...string) *FinalizerPatch + // Patch patches the object. This should only ever be used for updating the metadata of an object, and not object + // spec or status. Updating the spec could have unintended consequences such as defaulting zero values. + Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error // UpdateStatus updates the state of just the object's status. UpdateStatus(context.Context, client.Object, ...client.SubResourceUpdateOption) error // Get retrieves an obj for the given object key from the Kubernetes Cluster. @@ -125,7 +131,13 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont // then let's add the finalizer and update the object. This is equivalent // registering our finalizer. if !containsString(configEntry.GetFinalizers(), FinalizerName) { - configEntry.AddFinalizer(FinalizerName) + addPatch := crdCtrl.AddFinalizersPatch(configEntry, FinalizerName) + err := crdCtrl.Patch(ctx, configEntry, addPatch) + if err != nil { + return r.syncFailed(ctx, logger, crdCtrl, configEntry, ConsulPatchError, + fmt.Errorf("adding finalizer: %w", err)) + } + if err := r.syncUnknown(ctx, crdCtrl, configEntry); err != nil { return ctrl.Result{}, err } @@ -159,8 +171,8 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont } } // remove our finalizer from the list and update it. - configEntry.RemoveFinalizer(FinalizerName) - if err := crdCtrl.Update(ctx, configEntry); err != nil { + removePatch := crdCtrl.RemoveFinalizersPatch(configEntry, FinalizerName) + if err := crdCtrl.Patch(ctx, configEntry, removePatch); err != nil { return ctrl.Result{}, err } logger.Info("finalizer removed") @@ -355,7 +367,9 @@ func (r *ConfigEntryController) syncSuccessful(ctx context.Context, updater Cont func (r *ConfigEntryController) syncUnknown(ctx context.Context, updater Controller, configEntry common.ConfigEntryResource) error { configEntry.SetSyncedCondition(corev1.ConditionUnknown, "", "") - return updater.Update(ctx, configEntry) + timeNow := metav1.NewTime(time.Now()) + configEntry.SetLastSyncedTime(&timeNow) + return updater.UpdateStatus(ctx, configEntry) } func (r *ConfigEntryController) syncUnknownWithError(ctx context.Context, diff --git a/control-plane/config-entries/controllers/controlplanerequestlimit_controller.go b/control-plane/config-entries/controllers/controlplanerequestlimit_controller.go index f5afbdcc48..d5a41cf8ec 100644 --- a/control-plane/config-entries/controllers/controlplanerequestlimit_controller.go +++ b/control-plane/config-entries/controllers/controlplanerequestlimit_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*ControlPlaneRequestLimitController)(nil) + // ControlPlaneRequestLimitController reconciles a ControlPlaneRequestLimit object. type ControlPlaneRequestLimitController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/exportedservices_controller.go b/control-plane/config-entries/controllers/exportedservices_controller.go index 2e82ed0eae..28d0f9c807 100644 --- a/control-plane/config-entries/controllers/exportedservices_controller.go +++ b/control-plane/config-entries/controllers/exportedservices_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*ExportedServicesController)(nil) + // ExportedServicesController reconciles a ExportedServices object. type ExportedServicesController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/finalizer_patch.go b/control-plane/config-entries/controllers/finalizer_patch.go new file mode 100644 index 0000000000..c0974edae0 --- /dev/null +++ b/control-plane/config-entries/controllers/finalizer_patch.go @@ -0,0 +1,78 @@ +package controllers + +import ( + "encoding/json" + + jsonpatch "github.com/evanphx/json-patch" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type FinalizerPatcher struct{} + +type FinalizerPatch struct { + NewFinalizers []string +} + +// Type implements client.Patch. Since this patch is used for a custom CRD, Kubernetes does not allow the more advanced +// StrategicMergePatch. Therefore, this patcher will replace the entire list of finalizers with the new list, rather +// than adding/removing individual entries. +// +// This can result in a small race condition where we could overwrite recently modified finalizers (either modified by a +// user or another controller process). Before the addition of this finalizer patcher implementation, this race +// condition still existed, but applied to the entirety of the CRD because we used to update the entire CRD rather than +// just the finalizer, so this reduces the surface area of the race condition. Generally we should not expect users or +// other controllers to be touching the finalizers of consul-k8s managed CRDs. +func (fp *FinalizerPatch) Type() types.PatchType { + return types.MergePatchType +} + +var _ client.Patch = (*FinalizerPatch)(nil) + +func (f *FinalizerPatcher) AddFinalizersPatch(oldObj client.Object, addFinalizers ...string) *FinalizerPatch { + output := make([]string, 0, len(addFinalizers)) + existing := make(map[string]bool) + for _, f := range oldObj.GetFinalizers() { + existing[f] = true + output = append(output, f) + } + for _, f := range addFinalizers { + if !existing[f] { + output = append(output, f) + } + } + return &FinalizerPatch{ + NewFinalizers: output, + } +} + +func (f *FinalizerPatcher) RemoveFinalizersPatch(oldObj client.Object, removeFinalizers ...string) *FinalizerPatch { + output := make([]string, 0) + remove := make(map[string]bool) + for _, f := range removeFinalizers { + remove[f] = true + } + for _, f := range oldObj.GetFinalizers() { + if !remove[f] { + output = append(output, f) + } + } + return &FinalizerPatch{ + NewFinalizers: output, + } +} + +// Data implements client.Patch. +func (fp *FinalizerPatch) Data(obj client.Object) ([]byte, error) { + newData, err := json.Marshal(map[string]any{ + "metadata": map[string]any{ + "finalizers": fp.NewFinalizers, + }, + }) + if err != nil { + return nil, err + } + + p, err := jsonpatch.CreateMergePatch([]byte(`{}`), newData) + return p, err +} diff --git a/control-plane/config-entries/controllers/finalizer_patch_test.go b/control-plane/config-entries/controllers/finalizer_patch_test.go new file mode 100644 index 0000000000..290d9c6437 --- /dev/null +++ b/control-plane/config-entries/controllers/finalizer_patch_test.go @@ -0,0 +1,97 @@ +package controllers + +import ( + "testing" + + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" +) + +func TestFinalizersPatcher(t *testing.T) { + cases := []struct { + name string + oldObject client.Object + addFinalizers []string + removeFinalizers []string + expectedFinalizerPatch *FinalizerPatch + op string + }{ + { + name: "adds finalizers at the end and keeps the original list in order", + oldObject: &v1alpha1.ServiceResolver{ + ObjectMeta: v1.ObjectMeta{ + Finalizers: []string{ + "a", + "b", + "c", + }, + }, + }, + addFinalizers: []string{"d", "e"}, + expectedFinalizerPatch: &FinalizerPatch{ + NewFinalizers: []string{"a", "b", "c", "d", "e"}, + }, + }, + { + name: "adds finalizers when original list is empty", + oldObject: &v1alpha1.ServiceResolver{ + ObjectMeta: v1.ObjectMeta{ + Finalizers: []string{}, + }, + }, + addFinalizers: []string{"d", "e"}, + expectedFinalizerPatch: &FinalizerPatch{ + NewFinalizers: []string{"d", "e"}, + }, + }, + { + name: "removes finalizers keeping the original list in order", + oldObject: &v1alpha1.ServiceResolver{ + ObjectMeta: v1.ObjectMeta{ + Finalizers: []string{ + "a", + "b", + "c", + "d", + }, + }, + }, + removeFinalizers: []string{"b"}, + expectedFinalizerPatch: &FinalizerPatch{ + NewFinalizers: []string{"a", "c", "d"}, + }, + }, + { + name: "removes all finalizers if specified", + oldObject: &v1alpha1.ServiceResolver{ + ObjectMeta: v1.ObjectMeta{ + Finalizers: []string{ + "a", + "b", + }, + }, + }, + removeFinalizers: []string{"a", "b"}, + expectedFinalizerPatch: &FinalizerPatch{ + NewFinalizers: []string{}, + }, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + f := FinalizerPatcher{} + var patch *FinalizerPatch + + if len(c.addFinalizers) > 0 { + patch = f.AddFinalizersPatch(c.oldObject, c.addFinalizers...) + } else if len(c.removeFinalizers) > 0 { + patch = f.RemoveFinalizersPatch(c.oldObject, c.removeFinalizers...) + } + + require.Equal(t, c.expectedFinalizerPatch, patch) + }) + } +} diff --git a/control-plane/config-entries/controllers/ingressgateway_controller.go b/control-plane/config-entries/controllers/ingressgateway_controller.go index 5bcb39bc2a..563b9aa606 100644 --- a/control-plane/config-entries/controllers/ingressgateway_controller.go +++ b/control-plane/config-entries/controllers/ingressgateway_controller.go @@ -17,6 +17,7 @@ import ( // IngressGatewayController is the controller for IngressGateway resources. type IngressGatewayController struct { + FinalizerPatcher client.Client Log logr.Logger Scheme *runtime.Scheme diff --git a/control-plane/config-entries/controllers/jwtprovider_controller.go b/control-plane/config-entries/controllers/jwtprovider_controller.go index 157f4bc855..39e76d502c 100644 --- a/control-plane/config-entries/controllers/jwtprovider_controller.go +++ b/control-plane/config-entries/controllers/jwtprovider_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*JWTProviderController)(nil) + // JWTProviderController reconciles a JWTProvider object. type JWTProviderController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/mesh_controller.go b/control-plane/config-entries/controllers/mesh_controller.go index ba78f0c144..7cd6997fc6 100644 --- a/control-plane/config-entries/controllers/mesh_controller.go +++ b/control-plane/config-entries/controllers/mesh_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*MeshController)(nil) + // MeshController reconciles a Mesh object. type MeshController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/proxydefaults_controller.go b/control-plane/config-entries/controllers/proxydefaults_controller.go index 882843bf9e..c96c5968d3 100644 --- a/control-plane/config-entries/controllers/proxydefaults_controller.go +++ b/control-plane/config-entries/controllers/proxydefaults_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*ProxyDefaultsController)(nil) + // ProxyDefaultsController reconciles a ProxyDefaults object. type ProxyDefaultsController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/samenessgroups_controller.go b/control-plane/config-entries/controllers/samenessgroups_controller.go index 815b7e8ab8..c34b017761 100644 --- a/control-plane/config-entries/controllers/samenessgroups_controller.go +++ b/control-plane/config-entries/controllers/samenessgroups_controller.go @@ -16,9 +16,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*SamenessGroupController)(nil) + // SamenessGroupController reconciles a SamenessGroups object. type SamenessGroupController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/servicedefaults_controller.go b/control-plane/config-entries/controllers/servicedefaults_controller.go index 0496788bb3..69e001a741 100644 --- a/control-plane/config-entries/controllers/servicedefaults_controller.go +++ b/control-plane/config-entries/controllers/servicedefaults_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*ServiceDefaultsController)(nil) + // ServiceDefaultsController is the controller for ServiceDefaults resources. type ServiceDefaultsController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/serviceintentions_controller.go b/control-plane/config-entries/controllers/serviceintentions_controller.go index 4461a82dd6..e20cf34317 100644 --- a/control-plane/config-entries/controllers/serviceintentions_controller.go +++ b/control-plane/config-entries/controllers/serviceintentions_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*ServiceIntentionsController)(nil) + // ServiceIntentionsController reconciles a ServiceIntentions object. type ServiceIntentionsController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/serviceresolver_controller.go b/control-plane/config-entries/controllers/serviceresolver_controller.go index cfc5c31ca3..5bbc9c9f11 100644 --- a/control-plane/config-entries/controllers/serviceresolver_controller.go +++ b/control-plane/config-entries/controllers/serviceresolver_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*ServiceResolverController)(nil) + // ServiceResolverController is the controller for ServiceResolver resources. type ServiceResolverController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/servicerouter_controller.go b/control-plane/config-entries/controllers/servicerouter_controller.go index a0b3bc0581..6ea87b590a 100644 --- a/control-plane/config-entries/controllers/servicerouter_controller.go +++ b/control-plane/config-entries/controllers/servicerouter_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*ServiceRouterController)(nil) + // ServiceRouterController is the controller for ServiceRouter resources. type ServiceRouterController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/servicesplitter_controller.go b/control-plane/config-entries/controllers/servicesplitter_controller.go index b733fb9cb5..07e2603833 100644 --- a/control-plane/config-entries/controllers/servicesplitter_controller.go +++ b/control-plane/config-entries/controllers/servicesplitter_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*ServiceSplitterController)(nil) + // ServiceSplitterReconciler reconciles a ServiceSplitter object. type ServiceSplitterController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/config-entries/controllers/terminatinggateway_controller.go b/control-plane/config-entries/controllers/terminatinggateway_controller.go index d73d4e043c..c68db70538 100644 --- a/control-plane/config-entries/controllers/terminatinggateway_controller.go +++ b/control-plane/config-entries/controllers/terminatinggateway_controller.go @@ -15,9 +15,12 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) +var _ Controller = (*TerminatingGatewayController)(nil) + // TerminatingGatewayController is the controller for TerminatingGateway resources. type TerminatingGatewayController struct { client.Client + FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController From 2d5694f399cd012472e74c6fe4b32a8d84480177 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 20 Dec 2023 11:04:17 -0500 Subject: [PATCH 542/592] Add Changelog entries from patch releases (#3409) --- CHANGELOG.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ee998095..1f2ee1c20a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,109 @@ +## 1.3.1 (December 19, 2023) + +SECURITY: + +* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3118](https://github.com/hashicorp/consul-k8s/issues/3118)] +* Update `github.com/golang-jwt/jwt/v4` to v4.5.0 to address [PRISMA-2022-0270](https://github.com/golang-jwt/jwt/issues/258). [[GH-3237](https://github.com/hashicorp/consul-k8s/issues/3237)] +* Upgrade to use Go 1.20.12. This resolves CVEs +[CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) +[CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) +[CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead +[CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git [[GH-3312](https://github.com/hashicorp/consul-k8s/issues/3312)] + +FEATURES: + +* control-plane: adds a named port, `prometheus`, to the `consul-dataplane` sidecar for use with [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#podmetricsendpoint). [[GH-3222](https://github.com/hashicorp/consul-k8s/issues/3222)] +* crd: adds the [`retryOn`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon) field to the ServiceRouter CRD. [[GH-3308](https://github.com/hashicorp/consul-k8s/issues/3308)] +* helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. [[GH-3180](https://github.com/hashicorp/consul-k8s/issues/3180)] + +IMPROVEMENTS: + +* cli: Add -o json (-output-format json) to `consul-k8s proxy list` command that returns the result in json format. [[GH-3221](https://github.com/hashicorp/consul-k8s/issues/3221)] +* cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod [[GH-3158](https://github.com/hashicorp/consul-k8s/issues/3158)] +* control-plane: Add new `consul.hashicorp.com/proxy-config-map` annotation that allows for setting values in the opaque config map for proxy service registrations. [[GH-3347](https://github.com/hashicorp/consul-k8s/issues/3347)] +* helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters [[GH-3315](https://github.com/hashicorp/consul-k8s/issues/3315)] + +BUG FIXES: + +* consul-telemetry-collector: add telemetryCollector.cloud.resourceId that works even when not global.cloud.enabled [[GH-3219](https://github.com/hashicorp/consul-k8s/issues/3219)] +* consul-telemetry-collector: fix deployments to non-default namespaces when global.enableConsulNamespaces [[GH-3215](https://github.com/hashicorp/consul-k8s/issues/3215)] +* consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs [[GH-3184](https://github.com/hashicorp/consul-k8s/issues/3184)] +* control-plane: Fixes a bug with the control-plane CLI validation where the consul-dataplane sidecar CPU request is compared against the memory limit instead of the CPU limit. [[GH-3209](https://github.com/hashicorp/consul-k8s/issues/3209)] +* control-plane: Only delete ACL tokens matched Pod UID in Service Registration metadata [[GH-3210](https://github.com/hashicorp/consul-k8s/issues/3210)] +* control-plane: fixes an issue with the server-acl-init job where the job would fail on upgrades due to consul server ip address changes. [[GH-3137](https://github.com/hashicorp/consul-k8s/issues/3137)] +* control-plane: only alert on valid errors, not timeouts in gateway [[GH-3128](https://github.com/hashicorp/consul-k8s/issues/3128)] +* control-plane: remove extraneous error log in v2 pod controller when a pod is scheduled, but not yet allocated an IP. [[GH-3162](https://github.com/hashicorp/consul-k8s/issues/3162)] +* control-plane: remove extraneous error log in v2 pod controller when attempting to delete ACL tokens. [[GH-3172](https://github.com/hashicorp/consul-k8s/issues/3172)] +* control-plane: Remove virtual nodes in the Consul Catalog when they do not have any services listed. [[GH-3307](https://github.com/hashicorp/consul-k8s/issues/3307)] +* mesh: prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers. [[GH-3337](https://github.com/hashicorp/consul-k8s/issues/3337)] + +## 1.2.4 (December 19, 2023) + +SECURITY: + +* Update `github.com/golang-jwt/jwt/v4` to v4.5.0 to address [PRISMA-2022-0270](https://github.com/golang-jwt/jwt/issues/258). [[GH-3237](https://github.com/hashicorp/consul-k8s/issues/3237)] +* Upgrade to use Go 1.20.12. This resolves CVEs +[CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) +[CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) +[CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead +[CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git [[GH-3312](https://github.com/hashicorp/consul-k8s/issues/3312)] + +FEATURES: + +* crd: adds the [`retryOn`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon) field to the ServiceRouter CRD. [[GH-3308](https://github.com/hashicorp/consul-k8s/issues/3308)] +* helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. [[GH-3180](https://github.com/hashicorp/consul-k8s/issues/3180)] + +IMPROVEMENTS: + +* cli: Add -o json (-output-format json) to `consul-k8s proxy list` command that returns the result in json format. [[GH-3221](https://github.com/hashicorp/consul-k8s/issues/3221)] +* cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod [[GH-3158](https://github.com/hashicorp/consul-k8s/issues/3158)] +* control-plane: Add new `consul.hashicorp.com/proxy-config-map` annotation that allows for setting values in the opaque config map for proxy service registrations. [[GH-3347](https://github.com/hashicorp/consul-k8s/issues/3347)] +* helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters [[GH-3315](https://github.com/hashicorp/consul-k8s/issues/3315)] + +BUG FIXES: + +* consul-telemetry-collector: add telemetryCollector.cloud.resourceId that works even when not global.cloud.enabled [[GH-3219](https://github.com/hashicorp/consul-k8s/issues/3219)] +* consul-telemetry-collector: fix deployments to non-default namespaces when global.enableConsulNamespaces [[GH-3215](https://github.com/hashicorp/consul-k8s/issues/3215)] +* consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs [[GH-3184](https://github.com/hashicorp/consul-k8s/issues/3215)] +* control-plane: Only delete ACL tokens matched Pod UID in Service Registration metadata [[GH-3210](https://github.com/hashicorp/consul-k8s/issues/3210)] +* control-plane: fixes an issue with the server-acl-init job where the job would fail on upgrades due to consul server ip address changes. [[GH-3137](https://github.com/hashicorp/consul-k8s/issues/3137)] +* control-plane: normalize the `partition` and `namespace` fields in V1 CRDs when comparing with saved version of the config-entry. [[GH-3284](https://github.com/hashicorp/consul-k8s/issues/3284)] +* control-plane: Remove virtual nodes in the Consul Catalog when they do not have any services listed. [[GH-3307](https://github.com/hashicorp/consul-k8s/issues/3307)] +* mesh: prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers. [[GH-3337](https://github.com/hashicorp/consul-k8s/issues/3337)] + +## 1.1.8 (December 19, 2023) + +SECURITY: + +* Update `github.com/golang-jwt/jwt/v4` to v4.5.0 to address [PRISMA-2022-0270](https://github.com/golang-jwt/jwt/issues/258). [[GH-3237](https://github.com/hashicorp/consul-k8s/issues/3237)] +* Upgrade to use Go 1.20.12. This resolves CVEs +[CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) +[CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) +[CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead +[CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git [[GH-3312](https://github.com/hashicorp/consul-k8s/issues/3312)] + +FEATURES: + +* crd: adds the [`retryOn`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon) field to the ServiceRouter CRD. [[GH-3308](https://github.com/hashicorp/consul-k8s/issues/3308)] +* helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. [[GH-3180](https://github.com/hashicorp/consul-k8s/issues/3180)] + +IMPROVEMENTS: + +* cli: Add -o json (-output-format json) to `consul-k8s proxy list` command that returns the result in json format. [[GH-3221](https://github.com/hashicorp/consul-k8s/issues/3221)] +* cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod [[GH-3158](https://github.com/hashicorp/consul-k8s/issues/3158)] +* control-plane: Add new `consul.hashicorp.com/proxy-config-map` annotation that allows for setting values in the opaque config map for proxy service registrations. [[GH-3347](https://github.com/hashicorp/consul-k8s/issues/3347)] +* helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters [[GH-3315](https://github.com/hashicorp/consul-k8s/issues/3315)] + +BUG FIXES: + +* consul-telemetry-collector: add telemetryCollector.cloud.resourceId that works even when not global.cloud.enabled [[GH-3219](https://github.com/hashicorp/consul-k8s/issues/3219)] +* consul-telemetry-collector: fix deployments to non-default namespaces when global.enableConsulNamespaces [[GH-3215](https://github.com/hashicorp/consul-k8s/issues/3215)] +* consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs [[GH-3184](https://github.com/hashicorp/consul-k8s/issues/3184)] +* control-plane: Only delete ACL tokens matched Pod UID in Service Registration metadata [[GH-3210](https://github.com/hashicorp/consul-k8s/issues/3210)] +* control-plane: fixes an issue with the server-acl-init job where the job would fail on upgrades due to consul server ip address changes. [[GH-3137](https://github.com/hashicorp/consul-k8s/issues/3137)] +* control-plane: Remove virtual nodes in the Consul Catalog when they do not have any services listed. [[GH-3137](https://github.com/hashicorp/consul-k8s/issues/3137)] +* mesh: prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers. [[GH-3337](https://github.com/hashicorp/consul-k8s/issues/3337)] + ## 1.3.0 (November 8, 2023) SECURITY: From 6f293d5bf2bc6607db00a649c6e1a65030285e59 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 20 Dec 2023 11:46:41 -0500 Subject: [PATCH 543/592] [NET-6809] Add chart related labels for mesh gateway deployments (#3396) * use set labels for setting labels on deployments * incorporate changes for setting labels --- control-plane/gateways/deployment_test.go | 44 ++++++++++++++++++++--- control-plane/gateways/service_test.go | 28 +++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 4f682a16ec..328d6b2026 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -42,6 +42,16 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { config: GatewayConfig{}, gcc: &meshv2beta1.GatewayClassConfig{ Spec: meshv2beta1.GatewayClassConfigSpec{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + Set: map[string]string{ + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + }, + }, Deployment: meshv2beta1.GatewayClassDeploymentConfig{ Container: &meshv2beta1.GatewayClassContainerConfig{ HostPort: 8080, @@ -67,17 +77,37 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, want: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Labels: defaultLabels, + Labels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + Annotations: map[string]string{}, }, Spec: appsv1.DeploymentSpec{ Replicas: pointer.Int32(1), Selector: &metav1.LabelSelector{ - MatchLabels: defaultLabels, + MatchLabels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: defaultLabels, + Labels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + Annotations: map[string]string{ constants.AnnotationGatewayKind: meshGatewayAnnotationKind, constants.AnnotationMeshInject: "false", @@ -281,7 +311,13 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Weight: 1, PodAffinityTerm: corev1.PodAffinityTerm{ LabelSelector: &metav1.LabelSelector{ - MatchLabels: defaultLabels, + MatchLabels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, }, TopologyKey: "kubernetes.io/hostname", }, diff --git a/control-plane/gateways/service_test.go b/control-plane/gateways/service_test.go index 3ec9251b83..3796e5ea56 100644 --- a/control-plane/gateways/service_test.go +++ b/control-plane/gateways/service_test.go @@ -39,6 +39,16 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { config: GatewayConfig{}, gcc: &meshv2beta1.GatewayClassConfig{ Spec: meshv2beta1.GatewayClassConfigSpec{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + Set: map[string]string{ + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + }, + }, Deployment: meshv2beta1.GatewayClassDeploymentConfig{ Container: &meshv2beta1.GatewayClassContainerConfig{ PortModifier: 8000, @@ -52,12 +62,24 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { }, want: &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Labels: defaultLabels, + Labels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, Annotations: map[string]string{}, }, Spec: corev1.ServiceSpec{ - Selector: defaultLabels, - Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + Type: corev1.ServiceTypeLoadBalancer, Ports: []corev1.ServicePort{ { Name: "wan", From e0cbc90ba957f3ca832a7abd365f0f1a9f540845 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Thu, 21 Dec 2023 10:37:52 -0500 Subject: [PATCH 544/592] chore: add compatibility note to .0 changelogs (#3416) Also fix a minor bug with prerelease version in non-prerelease changelog entries. --- .../build-support/functions/10-util.sh | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index 9473226b2c..f1117d88d2 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -95,7 +95,7 @@ function have_gpg_key { function parse_version { # Arguments: - # $1 - Path to the top level Consul source + # $1 - Path to the top level Consul K8s source # $2 - boolean value for whether the release version should be parsed from the source # $3 - boolean whether to use GIT_DESCRIBE and GIT_COMMIT environment variables # $4 - boolean whether to omit the version part of the version string. (optional) @@ -190,7 +190,7 @@ function parse_version { function get_version { # Arguments: - # $1 - Path to the top level Consul source + # $1 - Path to the top level Consul K8s source # $2 - Whether the release version should be parsed from source (optional) # $3 - Whether to use GIT_DESCRIBE and GIT_COMMIT environment variables # @@ -344,7 +344,7 @@ function normalize_git_url { function git_remote_url { # Arguments: - # $1 - Path to the top level Consul source + # $1 - Path to the top level Consul K8s source # $2 - Remote name # # Returns: @@ -380,7 +380,7 @@ function git_remote_url { function find_git_remote { # Arguments: - # $1 - Path to the top level Consul source + # $1 - Path to the top level Consul K8s source # # Returns: # 0 - success @@ -482,7 +482,7 @@ function update_git_env { function git_push_ref { # Arguments: - # $1 - Path to the top level Consul source + # $1 - Path to the top level Consul K8s source # $2 - Git ref (optional) # $3 - remote (optional - if not specified we will try to determine it) # @@ -659,7 +659,7 @@ function update_version_helm { function set_version { # Arguments: - # $1 - Path to top level Consul source + # $1 - Path to top level Consul K8s source # $2 - The version of the release # $3 - The release date # $4 - The pre-release version @@ -709,11 +709,13 @@ function set_version { function set_changelog { # Arguments: - # $1 - Path to top level Consul source + # $1 - Path to top level Consul K8s source # $2 - Version # $3 - Release Date # $4 - The last git release tag # $5 - Pre-release version + # $6 - The version of Consul corresponding to the release (only used for .0) + # $7 - The version of Consul Dataplane corresponding to the release (only used for .0) # # # Returns: @@ -733,7 +735,13 @@ function set_changelog { rel_date="$3" fi local last_release_date_git_tag=$4 - local preReleaseVersion="-$5" + + # Only set prerelease suffix if prerelease version is set + local preReleaseVersion="${5:+-${5}}" + + local version_short="${version%\.*}" + local consul_version_short="${6%\.*}" + local consul_dataplane_version_short="${7%\.*}" if test -z "${version}"; then err "ERROR: Must specify a version to put into the changelog" @@ -745,8 +753,18 @@ function set_changelog { exit 1 fi + if [[ "${version}" =~ \.0$ ]]; then + if [ -z "$version_short" ] || [ -z "$consul_version_short" ] || [ -z "$consul_dataplane_version_short" ]; then + echo "Error: Consul K8s, Consul or Consul Dataplane short version could not be detected." + exit 1 + fi + compatibility_note=" + +_Note: Consul K8s ${version_short} is compatible with Consul ${consul_version_short} and Consul Dataplane ${consul_dataplane_version_short}. Refer to our [compatibility matrix](https://developer.hashicorp.com/consul/docs/k8s/compatibility) for more info._" + fi + cat <tmp && mv tmp "${curdir}"/CHANGELOG.MD -## ${version}${preReleaseVersion} (${rel_date}) +## ${version}${preReleaseVersion} (${rel_date})${compatibility_note} $(changelog-build -last-release ${CONSUL_K8S_LAST_RELEASE_GIT_TAG} \ -entries-dir .changelog/ \ -changelog-template .changelog/changelog.tmpl \ @@ -758,13 +776,13 @@ EOT function prepare_release { # Arguments: - # $1 - Path to top level Consul source + # $1 - Path to top level Consul K8s source # $2 - The version of the release # $3 - The release date # $4 - The last release git tag for this branch (eg. v1.1.0) - # $5 - The consul version + # $5 - The consul version # $6 - The consul-dataplane version - # $7 - The pre-release version + # $7 - The pre-release version # # # Returns: @@ -781,12 +799,12 @@ function prepare_release { echo "prepare_release: dir:${curDir} consul-k8s:${version} consul:${consulVersion} consul-dataplane:${consulDataplaneVersion} date:"${releaseDate}" git tag:${lastGitTag}" set_version "${curDir}" "${version}" "${releaseDate}" "${prereleaseVersion}" "hashicorp\/consul-k8s-control-plane:" "${consulVersion}" "hashicorp\/consul" "${consulDataplaneVersion}" "hashicorp\/consul-dataplane" - set_changelog "${curDir}" "${version}" "${releaseDate}" "${lastGitTag}" "${prereleaseVersion}" + set_changelog "${curDir}" "${version}" "${releaseDate}" "${lastGitTag}" "${prereleaseVersion}" "${consulVersion}" "${consulDataplaneVersion}" } function prepare_rc_branch { # Arguments: - # $1 - Path to top level Consul source + # $1 - Path to top level Consul K8s source # $2 - The version of the release # $3 - The release date # $4 - The last release git tag for this branch (eg. v1.1.0) @@ -813,7 +831,7 @@ function prepare_rc_branch { function prepare_dev { # Arguments: - # $1 - Path to top level Consul source + # $1 - Path to top level Consul K8s source # $2 - The version of the release # $3 - The release date # $4 - The last release git tag for this branch (eg. v1.1.0) (Unused) @@ -868,7 +886,7 @@ function git_staging_empty { function commit_dev_mode { # Arguments: - # $1 - Path to top level Consul source + # $1 - Path to top level Consul K8s source # # Returns: # 0 - success From a96872775779a212843a1baeaa8ea36240e5c349 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 21 Dec 2023 10:44:26 -0500 Subject: [PATCH 545/592] [NET-6744] Fix mesh gw creation for v2 (#3408) * Fix mesh gw creation for v2 * Update tests * Use constant for volume name * Update name from PR review * fixing mesh init tests * Remove need for writing proxyid file by passing in proxy id to dataplane container * Pass in proxy-id as env variable --- .../controllersv2/mesh_gateway_controller.go | 2 +- .../connect-inject/constants/constants.go | 5 +- .../deployment_dataplane_container.go | 29 ++++-- .../gateways/deployment_init_container.go | 19 ++-- control-plane/gateways/deployment_test.go | 90 +++++++++++++++---- control-plane/subcommand/mesh-init/command.go | 13 +-- .../subcommand/mesh-init/command_test.go | 6 +- 7 files changed, 121 insertions(+), 43 deletions(-) diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go index 45143190dd..52da7f678f 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/config-entries/controllersv2/mesh_gateway_controller.go @@ -129,7 +129,7 @@ func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Req return fmt.Errorf("unable to create role binding: %w", err) } - //Create Service + // Create Service mergeServiceOp := func(ctx context.Context, existingObject, object client.Object) error { existingService, ok := existingObject.(*corev1.Service) diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 35fa3beced..a29148be05 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -62,7 +62,7 @@ const ( // DefaultGracefulShutdownPath is the default path that consul-dataplane uses for graceful shutdown. DefaultGracefulShutdownPath = "/graceful_shutdown" - //DefaultWANPort is the default port that consul-dataplane uses for WAN. + // DefaultWANPort is the default port that consul-dataplane uses for WAN. DefaultWANPort = 8443 // ConsulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. @@ -72,6 +72,9 @@ const ( ConsulKubernetesCheckName = "Kubernetes Readiness Check" KubernetesSuccessReasonMsg = "Kubernetes health checks passing" + + // ProxyIDVolumePath is the name of the volume that contains the proxy ID. + ProxyIDVolumePath = "/consul/mesh-inject" ) // GetNormalizedConsulNamespace returns the default namespace if the passed namespace diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go index 8c99acec8f..4fd621ec94 100644 --- a/control-plane/gateways/deployment_dataplane_container.go +++ b/control-plane/gateways/deployment_dataplane_container.go @@ -24,7 +24,7 @@ const ( consulDataplaneDNSBindPort = 8600 defaultPrometheusScrapePath = "/metrics" defaultEnvoyProxyConcurrency = "1" - volumeName = "consul-connect-inject-data" + volumeName = "consul-mesh-inject-data" ) func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.GatewayClassContainerConfig, name, namespace string) (corev1.Container, error) { @@ -65,9 +65,21 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate // TODO(nathancoleman): I don't believe consul-dataplane needs to write anymore, investigate. Env: []corev1.EnvVar{ + { + Name: "DP_PROXY_ID", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, { Name: "TMPDIR", - Value: "/consul/connect-inject", + Value: constants.ProxyIDVolumePath, }, { Name: "NODE_NAME", @@ -77,6 +89,14 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate }, }, }, + { + Name: "DP_CREDENTIAL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", + }, + { + Name: "DP_CREDENTIAL_LOGIN_META1", + Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", + }, { Name: "DP_SERVICE_NODE_NAME", Value: "$(NODE_NAME)-virtual", @@ -85,7 +105,7 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate VolumeMounts: []corev1.VolumeMount{ { Name: volumeName, - MountPath: "/consul/connect-inject", + MountPath: constants.ProxyIDVolumePath, }, }, Args: args, @@ -138,12 +158,9 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate } func getDataplaneArgs(namespace string, config GatewayConfig, bearerTokenFile string, name string) ([]string, error) { - proxyIDFileName := "/consul/connect-inject/proxyid" - args := []string{ "-addresses", config.ConsulConfig.Address, "-grpc-port=" + strconv.Itoa(config.ConsulConfig.GRPCPort), - "-proxy-service-id-path=" + proxyIDFileName, "-log-level=" + config.LogLevel, "-log-json=" + strconv.FormatBool(config.LogJSON), "-envoy-concurrency=" + defaultEnvoyProxyConcurrency, diff --git a/control-plane/gateways/deployment_init_container.go b/control-plane/gateways/deployment_init_container.go index 9e66a7ee22..354ffcce52 100644 --- a/control-plane/gateways/deployment_init_container.go +++ b/control-plane/gateways/deployment_init_container.go @@ -11,11 +11,12 @@ import ( corev1 "k8s.io/api/core/v1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( - injectInitContainerName = "consul-connect-inject-init" + injectInitContainerName = "consul-mesh-init" initContainersUserAndGroupID = 5996 ) @@ -44,7 +45,7 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain volMounts := []corev1.VolumeMount{ { Name: volumeName, - MountPath: "/consul/connect-inject", + MountPath: constants.ProxyIDVolumePath, }, } @@ -157,13 +158,11 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain // the init container. // TODO @GatewayManagement parametrize gateway kind. const initContainerCommandTpl = ` -consul-k8s-control-plane connect-init \ - -pod-name=${POD_NAME} \ - -pod-namespace=${POD_NAMESPACE} \ - -gateway-kind="mesh-gateway" \ - -log-json={{ .LogJSON }} \ - {{- if .AuthMethod }} - -service-account-name="{{ .ServiceAccountName }}" \ +consul-k8s-control-plane mesh-init \ + -proxy-name=${POD_NAME} \ + -namespace=${POD_NAMESPACE} \ + {{- with .LogLevel }} + -log-level={{ . }} \ {{- end }} - -service-name="{{ .ServiceName }}" + -log-json={{ .LogJSON }} ` diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 328d6b2026..e1beebc7fb 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -117,7 +117,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ { - Name: "consul-connect-inject-data", + Name: "consul-mesh-inject-data", VolumeSource: corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{ Medium: "Memory", @@ -127,11 +127,11 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, InitContainers: []corev1.Container{ { - Name: "consul-connect-inject-init", + Name: "consul-mesh-init", Command: []string{ "/bin/sh", "-ec", - "consul-k8s-control-plane connect-init \\\n -pod-name=${POD_NAME} \\\n -pod-namespace=${POD_NAMESPACE} \\\n -gateway-kind=\"mesh-gateway\" \\\n -log-json=false \\\n -service-name=\"\"", + "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-json=false", }, Env: []corev1.EnvVar{ { @@ -192,9 +192,9 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Resources: corev1.ResourceRequirements{}, VolumeMounts: []corev1.VolumeMount{ { - Name: "consul-connect-inject-data", + Name: "consul-mesh-inject-data", ReadOnly: false, - MountPath: "/consul/connect-inject", + MountPath: "/consul/mesh-inject", }, }, }, @@ -205,7 +205,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "-addresses", "", "-grpc-port=0", - "-proxy-service-id-path=/consul/connect-inject/proxyid", "-log-level=", "-log-json=false", "-envoy-concurrency=1", @@ -225,9 +224,29 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, }, Env: []corev1.EnvVar{ + { + Name: "DP_PROXY_ID", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.namespace", + }, + }, + }, { Name: "TMPDIR", - Value: "/consul/connect-inject", + Value: "/consul/mesh-inject", }, { Name: "NODE_NAME", @@ -239,6 +258,14 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, }, }, + { + Name: "DP_CREDENTIAL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", + }, + { + Name: "DP_CREDENTIAL_LOGIN_META1", + Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", + }, { Name: "DP_SERVICE_NODE_NAME", Value: "$(NODE_NAME)-virtual", @@ -256,8 +283,8 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, VolumeMounts: []corev1.VolumeMount{ { - Name: "consul-connect-inject-data", - MountPath: "/consul/connect-inject", + Name: "consul-mesh-inject-data", + MountPath: "/consul/mesh-inject", }, }, ReadinessProbe: &corev1.Probe{ @@ -370,7 +397,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ { - Name: "consul-connect-inject-data", + Name: "consul-mesh-inject-data", VolumeSource: corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{ Medium: "Memory", @@ -380,11 +407,11 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, InitContainers: []corev1.Container{ { - Name: "consul-connect-inject-init", + Name: "consul-mesh-init", Command: []string{ "/bin/sh", "-ec", - "consul-k8s-control-plane connect-init \\\n -pod-name=${POD_NAME} \\\n -pod-namespace=${POD_NAMESPACE} \\\n -gateway-kind=\"mesh-gateway\" \\\n -log-json=false \\\n -service-name=\"\"", + "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-json=false", }, Env: []corev1.EnvVar{ { @@ -445,9 +472,9 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Resources: corev1.ResourceRequirements{}, VolumeMounts: []corev1.VolumeMount{ { - Name: "consul-connect-inject-data", + Name: "consul-mesh-inject-data", ReadOnly: false, - MountPath: "/consul/connect-inject", + MountPath: "/consul/mesh-inject", }, }, }, @@ -458,7 +485,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "-addresses", "", "-grpc-port=0", - "-proxy-service-id-path=/consul/connect-inject/proxyid", "-log-level=", "-log-json=false", "-envoy-concurrency=1", @@ -477,9 +503,29 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, }, Env: []corev1.EnvVar{ + { + Name: "DP_PROXY_ID", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.namespace", + }, + }, + }, { Name: "TMPDIR", - Value: "/consul/connect-inject", + Value: "/consul/mesh-inject", }, { Name: "NODE_NAME", @@ -491,6 +537,14 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, }, }, + { + Name: "DP_CREDENTIAL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", + }, + { + Name: "DP_CREDENTIAL_LOGIN_META1", + Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", + }, { Name: "DP_SERVICE_NODE_NAME", Value: "$(NODE_NAME)-virtual", @@ -508,8 +562,8 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, VolumeMounts: []corev1.VolumeMount{ { - Name: "consul-connect-inject-data", - MountPath: "/consul/connect-inject", + Name: "consul-mesh-inject-data", + MountPath: "/consul/mesh-inject", }, }, ReadinessProbe: &corev1.Probe{ diff --git a/control-plane/subcommand/mesh-init/command.go b/control-plane/subcommand/mesh-init/command.go index 2a6b6ccebd..ea03577848 100644 --- a/control-plane/subcommand/mesh-init/command.go +++ b/control-plane/subcommand/mesh-init/command.go @@ -34,6 +34,7 @@ import ( const ( // The number of times to attempt to read this proxy registration (120s). defaultMaxPollingRetries = 120 + defaultProxyIDFile = "/consul/mesh-inject/proxyid" ) type Command struct { @@ -72,6 +73,7 @@ func (c *Command) init() { c.flagSet.StringVar(&c.flagLogLevel, "log-level", "info", "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ "\"debug\", \"info\", \"warn\", and \"error\".") + c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, "Enable or disable JSON output format for logging.") @@ -210,8 +212,8 @@ func (c *Command) Help() string { func (c *Command) getBootstrapParams( client pbdataplane.DataplaneServiceClient, - bootstrapConfig *pbmesh.BootstrapConfig) backoff.Operation { - + bootstrapConfig *pbmesh.BootstrapConfig, +) backoff.Operation { return func() error { req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ ProxyId: c.flagProxyName, @@ -234,7 +236,6 @@ func (c *Command) getBootstrapParams( // https://github.com/hashicorp/consul/blob/fe2d41ddad9ba2b8ff86cbdebbd8f05855b1523c/command/connect/redirecttraffic/redirect_traffic.go#L136. func (c *Command) applyTrafficRedirectionRules(config *pbmesh.BootstrapConfig) error { - err := json.Unmarshal([]byte(c.flagRedirectTrafficConfig), &c.iptablesConfig) if err != nil { return err @@ -274,11 +275,13 @@ func (c *Command) applyTrafficRedirectionRules(config *pbmesh.BootstrapConfig) e return nil } -const synopsis = "Inject mesh init command." -const help = ` +const ( + synopsis = "Inject mesh init command." + help = ` Usage: consul-k8s-control-plane mesh-init [options] Bootstraps mesh-injected pod components. Uses V2 Consul Catalog APIs. Not intended for stand-alone use. ` +) diff --git a/control-plane/subcommand/mesh-init/command_test.go b/control-plane/subcommand/mesh-init/command_test.go index 85b68adc65..87f38bf011 100644 --- a/control-plane/subcommand/mesh-init/command_test.go +++ b/control-plane/subcommand/mesh-init/command_test.go @@ -114,7 +114,8 @@ func TestRun_MeshServices(t *testing.T) { // We build the consul-addr because normally it's defined by the init container setting // CONSUL_HTTP_ADDR when it processes the command template. - flags := []string{"-proxy-name", testPodName, + flags := []string{ + "-proxy-name", testPodName, "-addresses", "127.0.0.1", "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), @@ -183,6 +184,7 @@ func TestRun_RetryServicePolling(t *testing.T) { UI: ui, maxPollingAttempts: 10, } + flags := []string{ "-proxy-name", testPodName, "-addresses", "127.0.0.1", @@ -232,7 +234,6 @@ func TestRun_TrafficRedirection(t *testing.T) { for name, c := range cases { t.Run(name, func(t *testing.T) { - // Start Consul server. var serverCfg *testutil.TestServerConfig testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { @@ -265,6 +266,7 @@ func TestRun_TrafficRedirection(t *testing.T) { } iptablesCfgJSON, err := json.Marshal(iptablesCfg) require.NoError(t, err) + flags := []string{ "-proxy-name", testPodName, "-addresses", "127.0.0.1", From 47c73920bd0b28ee6eb4537cecf7edc9ee4e6399 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 21 Dec 2023 13:05:20 -0500 Subject: [PATCH 546/592] [NET-6724] Add Affinity for Mesh GW Deployments (#3419) Add affinity for meshgw deployment --- .../templates/crd-gatewayclassconfigs.yaml | 869 ++++++++++++++++++ .../gateway-resources-configmap.yaml | 4 + .../v2beta1/gateway_class_config_types.go | 2 + .../api/mesh/v2beta1/zz_generated.deepcopy.go | 5 + ...sul.hashicorp.com_gatewayclassconfigs.yaml | 869 ++++++++++++++++++ control-plane/gateways/deployment.go | 16 +- control-plane/gateways/deployment_test.go | 38 +- control-plane/gateways/metadata.go | 3 + control-plane/gateways/metadata_test.go | 3 + .../gateway-resources/command_test.go | 26 + 10 files changed, 1803 insertions(+), 32 deletions(-) diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 0dfa0779d9..93effd843b 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -69,6 +69,875 @@ spec: description: Deployment contains config specific to the Deployment created from this GatewayClass properties: + affinity: + description: Affinity specifies the affinity to use on the created + Deployment. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object annotations: description: Annotations are applied to the created resource properties: diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index a01b52371b..1fe8b11979 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -74,6 +74,10 @@ data: topologySpreadConstraints: {{ tpl .Values.meshGateway.topologySpreadConstraints . | nindent 16 | trim }} {{- end }} + {{- if .Values.meshGateway.affinity }} + affinity: + {{ tpl .Values.meshGateway.affinity . | nindent 16 | trim }} + {{- end }} replicas: default: {{ .Values.meshGateway.replicas }} min: {{ .Values.meshGateway.replicas }} diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go index 1ef470d959..dc3499b10c 100644 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go @@ -75,6 +75,8 @@ type GatewayClassDeploymentConfig struct { // DNSPolicy specifies the dns policy to use. These are set on a per pod basis. // +kubebuilder:validation:Enum=Default;ClusterFirst;ClusterFirstWithHostNet;None DNSPolicy corev1.DNSPolicy `json:"dnsPolicy,omitempty"` + // Affinity specifies the affinity to use on the created Deployment. + Affinity *corev1.Affinity `json:"affinity,omitempty"` } type GatewayClassReplicasConfig struct { diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index e64c2a1145..b6ef8feef5 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -362,6 +362,11 @@ func (in *GatewayClassDeploymentConfig) DeepCopyInto(out *GatewayClassDeployment (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassDeploymentConfig. diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml index 9aa253add4..e7f560861b 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml @@ -65,6 +65,875 @@ spec: description: Deployment contains config specific to the Deployment created from this GatewayClass properties: + affinity: + description: Affinity specifies the affinity to use on the created + Deployment. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object annotations: description: Annotations are applied to the created resource properties: diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index c525dbc835..683956b170 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -87,21 +87,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { Containers: []corev1.Container{ container, }, - Affinity: &corev1.Affinity{ - PodAntiAffinity: &corev1.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ - { - Weight: 1, - PodAffinityTerm: corev1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: b.labelsForDeployment(), - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - }, + Affinity: deploymentConfig.Affinity, NodeSelector: deploymentConfig.NodeSelector, PriorityClassName: deploymentConfig.PriorityClassName, TopologySpreadConstraints: deploymentConfig.TopologySpreadConstraints, diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index e1beebc7fb..513aac61f6 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -53,6 +53,27 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, }, Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + Affinity: &corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 1, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, + }, Container: &meshv2beta1.GatewayClassContainerConfig{ HostPort: 8080, PortModifier: 8000, @@ -599,23 +620,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { TTY: false, }, }, - Affinity: &corev1.Affinity{ - NodeAffinity: nil, - PodAffinity: nil, - PodAntiAffinity: &corev1.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ - { - Weight: 1, - PodAffinityTerm: corev1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: defaultLabels, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - }, }, }, Strategy: appsv1.DeploymentStrategy{}, diff --git a/control-plane/gateways/metadata.go b/control-plane/gateways/metadata.go index 98e685d9fd..32357c63a7 100644 --- a/control-plane/gateways/metadata.go +++ b/control-plane/gateways/metadata.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import ( diff --git a/control-plane/gateways/metadata_test.go b/control-plane/gateways/metadata_test.go index d95a4497e5..b0c1496884 100644 --- a/control-plane/gateways/metadata_test.go +++ b/control-plane/gateways/metadata_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package gateways import ( diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 44cb1b3ea1..4d88e5fc13 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -381,6 +381,15 @@ var validGWConfigurationKitchenSink = `gatewayClassConfigs: nodeSelector: beta.kubernetes.io/arch: amd64 beta.kubernetes.io/os: linux + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: consul + release: consul-helm + component: mesh-gateway + topologyKey: kubernetes.io/hostname tolerations: - key: "key1" operator: "Equal" @@ -470,6 +479,23 @@ func TestRun_loadGatewayConfigs(t *testing.T) { Effect: "NoSchedule", }, }, + + Affinity: &corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "consul", + "release": "consul-helm", + "component": "mesh-gateway", + }, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, Container: &v2beta1.GatewayClassContainerConfig{ Resources: &corev1.ResourceRequirements{ Requests: corev1.ResourceList{ From 378a83ea795439316e4eb2b40fb1ed49b222b395 Mon Sep 17 00:00:00 2001 From: David Yu Date: Thu, 21 Dec 2023 12:56:26 -0800 Subject: [PATCH 547/592] Dockerfile: update to ubi-minimal:9.3 (#3418) * Update Dockerfile --- .changelog/3418.txt | 3 +++ control-plane/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .changelog/3418.txt diff --git a/.changelog/3418.txt b/.changelog/3418.txt new file mode 100644 index 0000000000..032356f989 --- /dev/null +++ b/.changelog/3418.txt @@ -0,0 +1,3 @@ +```release-note:security +Upgrade OpenShift container images to use `ubi9-minimal:9.3` as the base image. +``` diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index 42d8e08faf..b9a3252271 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -127,7 +127,7 @@ FROM release-default AS release-default-fips # We don't rebuild the software because we want the exact checksums and # binary signatures to match the software and our builds aren't fully # reproducible currently. -FROM registry.access.redhat.com/ubi9-minimal:9.2 as ubi +FROM registry.access.redhat.com/ubi9-minimal:9.3 as ubi ARG PRODUCT_NAME ARG PRODUCT_VERSION From 5ed8514254e026f562d703e2acc7ae7595ad6610 Mon Sep 17 00:00:00 2001 From: Semir Patel Date: Wed, 3 Jan 2024 10:08:16 -0600 Subject: [PATCH 548/592] v2tenancy: support partition creation when adminPartitions enabled (#3369) --- .../kind_acceptance_test_packages.yaml | 2 +- acceptance/framework/config/config.go | 6 + acceptance/framework/consul/helm_cluster.go | 43 ++- acceptance/framework/k8s/helpers.go | 8 +- .../framework/portforward/port_forward.go | 1 + acceptance/go.mod | 19 +- acceptance/go.sum | 27 +- acceptance/tests/partitions/main_test.go | 2 +- acceptance/tests/tenancy_v2/main_test.go | 30 ++ acceptance/tests/tenancy_v2/partition_test.go | 91 ++++++ charts/consul/templates/_helpers.tpl | 6 +- .../consul/templates/partition-init-job.yaml | 3 + .../consul/templates/server-statefulset.yaml | 13 +- charts/consul/test/unit/helpers.bats | 4 +- .../consul/test/unit/partition-init-job.bats | 25 +- charts/consul/values.yaml | 8 + control-plane/cni/go.mod | 18 +- control-plane/cni/go.sum | 47 ++- control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- .../subcommand/partition-init/command.go | 163 +++++++--- .../partition-init/command_ent_test.go | 298 +++++++++++++----- 22 files changed, 616 insertions(+), 204 deletions(-) create mode 100644 acceptance/tests/tenancy_v2/main_test.go create mode 100644 acceptance/tests/tenancy_v2/partition_test.go diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml index eaf1dae73d..144f1c6678 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -8,4 +8,4 @@ - {runner: 4, test-packages: "cli vault metrics"} - {runner: 5, test-packages: "api-gateway ingress-gateway sync example consul-dns"} - {runner: 6, test-packages: "config-entries terminating-gateway basic"} -- {runner: 7, test-packages: "mesh_v2"} +- {runner: 7, test-packages: "mesh_v2 tenancy_v2"} diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index 49a24a6d10..4f9a8648c2 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -245,6 +245,12 @@ func (c *TestConfig) SkipWhenOpenshiftAndCNI(t *testing.T) { } } +func (c *TestConfig) SkipWhenCNI(t *testing.T) { + if c.EnableCNI { + t.Skip("skipping because -enable-cni is set and doesn't apply to this accepatance test") + } +} + // setIfNotEmpty sets key to val in map m if value is not empty. func setIfNotEmpty(m map[string]string, key, val string) { if val != "" { diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index f11f7e4df5..d4542e736b 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -12,16 +12,9 @@ import ( "github.com/gruntwork-io/terratest/modules/helm" terratestLogger "github.com/gruntwork-io/terratest/modules/logger" - "github.com/hashicorp/consul-k8s/acceptance/framework/config" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul-k8s/acceptance/framework/portforward" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" corev1 "k8s.io/api/core/v1" policyv1beta "k8s.io/api/policy/v1beta1" rbacv1 "k8s.io/api/rbac/v1" @@ -32,6 +25,17 @@ import ( "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/acceptance/framework/config" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul-k8s/acceptance/framework/portforward" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil/retry" ) // HelmCluster implements Cluster and uses Helm @@ -364,6 +368,7 @@ func (h *HelmCluster) Upgrade(t *testing.T, helmValues map[string]string) { k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName)) } +// CreatePortForwardTunnel returns the local address:port of a tunnel to the consul server pod in the given release. func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int, release ...string) string { releaseName := h.releaseName if len(release) > 0 { @@ -373,6 +378,26 @@ func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int, rele return portforward.CreateTunnelToResourcePort(t, serverPod, remotePort, h.helmOptions.KubectlOptions, h.logger) } +// ResourceClient returns a resource service grpc client for the given helm release. +func (h *HelmCluster) ResourceClient(t *testing.T, secure bool, release ...string) (client pbresource.ResourceServiceClient) { + if secure { + panic("TODO: add support for secure resource client") + } + releaseName := h.releaseName + if len(release) > 0 { + releaseName = release[0] + } + + // TODO: get grpc port from somewhere + localTunnelAddr := h.CreatePortForwardTunnel(t, 8502, releaseName) + + // Create a grpc connection to the server pod. + grpcConn, err := grpc.Dial(localTunnelAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + resourceClient := pbresource.NewResourceServiceClient(grpcConn) + return resourceClient +} + func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool, release ...string) (client *api.Client, configAddress string) { t.Helper() diff --git a/acceptance/framework/k8s/helpers.go b/acceptance/framework/k8s/helpers.go index 58990d73ce..ca1cba9d84 100644 --- a/acceptance/framework/k8s/helpers.go +++ b/acceptance/framework/k8s/helpers.go @@ -35,18 +35,18 @@ func KubernetesAPIServerHostFromOptions(t *testing.T, options *terratestk8s.Kube } // WaitForAllPodsToBeReady waits until all pods with the provided podLabelSelector -// are in the ready status. It checks every second for 11 minutes. +// are in the ready status. It checks every 2 second for 20 minutes. // If there is at least one container in a pod that isn't ready after that, // it fails the test. func WaitForAllPodsToBeReady(t *testing.T, client kubernetes.Interface, namespace, podLabelSelector string) { t.Helper() - logger.Logf(t, "Waiting for pods with label %q to be ready.", podLabelSelector) - // Wait up to 20m. // On Azure, volume provisioning can sometimes take close to 5 min, // so we need to give a bit more time for pods to become healthy. - counter := &retry.Counter{Count: 20 * 60, Wait: 2 * time.Second} + counter := &retry.Counter{Count: 10 * 60, Wait: 2 * time.Second} + logger.Logf(t, "Waiting %s for pods with label %q to be ready.", time.Duration(counter.Count*int(counter.Wait)), podLabelSelector) + retry.RunWith(counter, t, func(r *retry.R) { pods, err := client.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: podLabelSelector}) require.NoError(r, err) diff --git a/acceptance/framework/portforward/port_forward.go b/acceptance/framework/portforward/port_forward.go index 728c7b7891..ac9795c98c 100644 --- a/acceptance/framework/portforward/port_forward.go +++ b/acceptance/framework/portforward/port_forward.go @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" ) +// CreateTunnelToResourcePort returns a local address:port that is tunneled to the given resource's port. func CreateTunnelToResourcePort(t *testing.T, resourceName string, remotePort int, options *terratestk8s.KubectlOptions, logger terratestLogger.TestLogger) string { localPort := terratestk8s.GetAvailablePort(t) tunnel := terratestk8s.NewTunnelWithLogger( diff --git a/acceptance/go.mod b/acceptance/go.mod index 5e9ab5f291..ea94b99b6f 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -4,12 +4,15 @@ go 1.20 replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3 + require ( github.com/google/uuid v1.3.0 github.com/gruntwork-io/terratest v0.31.2 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 - github.com/hashicorp/consul/sdk v0.14.1 + github.com/hashicorp/consul/proto-public v0.5.1 + github.com/hashicorp/consul/sdk v0.15.0 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hcp-sdk-go v0.50.0 @@ -17,12 +20,13 @@ require ( github.com/hashicorp/vault/api v1.8.3 github.com/stretchr/testify v1.8.3 go.opentelemetry.io/proto/otlp v1.0.0 + google.golang.org/grpc v1.56.3 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 k8s.io/client-go v0.26.3 - k8s.io/utils v0.0.0-20230209194617-a36077c30491 + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/gateway-api v0.7.1 ) @@ -45,7 +49,7 @@ require ( github.com/fatih/color v1.14.1 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // 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 @@ -64,7 +68,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -125,20 +129,19 @@ require ( go.opentelemetry.io/otel/trace v1.11.1 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.14.0 // indirect - golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect - golang.org/x/mod v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/grpc v1.56.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 2ce8040697..0c3c2d4c78 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -211,8 +211,8 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= @@ -354,8 +354,9 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3/go.mod h1:2wIuQute9+hhWqvL3vEI7YB0EKluF4WcPzI1eAliazk= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -394,6 +395,8 @@ github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3 h1:FFRKi+IpoXHwXZDgqG+BNAG1duAuokbzm+5b2pcY1us= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -807,8 +810,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= -golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -831,8 +834,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -904,7 +907,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -1060,8 +1063,8 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 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= @@ -1240,8 +1243,8 @@ k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3 k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= -k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= diff --git a/acceptance/tests/partitions/main_test.go b/acceptance/tests/partitions/main_test.go index 89833ec2cc..8c4bd7e2a5 100644 --- a/acceptance/tests/partitions/main_test.go +++ b/acceptance/tests/partitions/main_test.go @@ -21,7 +21,7 @@ func TestMain(m *testing.M) { os.Exit(suite.Run()) } else { fmt.Println(fmt.Sprintf("Skipping partitions tests because either -enable-multi-cluster is "+ - "not set or the number of clusters did not match the expected count of %d", expectedNumberOfClusters)) + "not set or the number of clusters, %d, did not match the expected count of %d", len(suite.Config().KubeEnvs), expectedNumberOfClusters)) os.Exit(0) } } diff --git a/acceptance/tests/tenancy_v2/main_test.go b/acceptance/tests/tenancy_v2/main_test.go new file mode 100644 index 0000000000..1766d95319 --- /dev/null +++ b/acceptance/tests/tenancy_v2/main_test.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tenancy_v2 + +import ( + "fmt" + "os" + "testing" + + testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" +) + +var suite testsuite.Suite + +func TestMain(m *testing.M) { + suite = testsuite.NewSuite(m) + + expectedNumberOfClusters := 1 + if suite.Config().IsExpectedClusterCount(expectedNumberOfClusters) { + os.Exit(suite.Run()) + } else { + fmt.Printf( + "Skipping tenancy_v2 tests because the number of clusters, %d, did not match the expected count of %d\n", + len(suite.Config().KubeEnvs), + expectedNumberOfClusters, + ) + os.Exit(0) + } +} diff --git a/acceptance/tests/tenancy_v2/partition_test.go b/acceptance/tests/tenancy_v2/partition_test.go new file mode 100644 index 0000000000..8ad031c8fe --- /dev/null +++ b/acceptance/tests/tenancy_v2/partition_test.go @@ -0,0 +1,91 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tenancy_v2 + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul/proto-public/pbresource" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" +) + +// TestTenancy_Partition_Created tests that V2 partitions are created when requested +// by a consul client external to the consul server cluster's k8s cluster. +// +// It sets up an external Consul server in the same cluster but a different Helm installation +// and then treats this server as external. +func TestTenancy_Partition_Created(t *testing.T) { + // Given a single k8s kind cluster + // Where helm "server" release hosts a consul server cluster (server.enabled=true) + // And helm "client" release hosts a consul client cluster (server.enabled=false) + // And both releases have experiments "resource-apis" and "v2tenancy enabled" + // And helm "client" release is configured to point to the helm "server" release as an external server (externalServer.enabled=true) + // And helm "client" release has admin partitions enabled with name "ap1" (global.adminPartitions.name=ap1) + // And helm "server" release is open for business + // When helm "client" release is installed + // Then partition "ap1" is created by the partition-init job in the helm "client" release + + // We're skipping ACLs for now because they're not supported in v2. + cfg := suite.Config() + // Requires connnectInject.enabled which we disable below. + cfg.SkipWhenCNI(t) + ctx := suite.Environment().DefaultContext(t) + + serverHelmValues := map[string]string{ + "server.enabled": "true", + "global.experiments[0]": "resource-apis", + "global.experiments[1]": "v2tenancy", + "global.adminPartitions.enabled": "false", + "global.enableConsulNamespaces": "true", + + // Don't install injector, controller and cni on this k8s cluster so that it's not installed twice. + "connectInject.enabled": "false", + + // The UI is not supported for v2 in 1.17, so for now it must be disabled. + "ui.enabled": "false", + } + + serverReleaseName := helpers.RandomName() + serverCluster := consul.NewHelmCluster(t, serverHelmValues, ctx, cfg, serverReleaseName) + serverCluster.Create(t) + + clientHelmValues := map[string]string{ + "server.enabled": "false", + "global.experiments[0]": "resource-apis", + "global.experiments[1]": "v2tenancy", + "global.adminPartitions.enabled": "true", + "global.adminPartitions.name": "ap1", + "global.enableConsulNamespaces": "true", + "externalServers.enabled": "true", + "externalServers.hosts[0]": fmt.Sprintf("%s-consul-server", serverReleaseName), + + // This needs to be set to true otherwise the pods never materialize + "connectInject.enabled": "true", + + // The UI is not supported for v2 in 1.17, so for now it must be disabled. + "ui.enabled": "false", + } + + clientReleaseName := helpers.RandomName() + clientCluster := consul.NewHelmCluster(t, clientHelmValues, ctx, cfg, clientReleaseName) + clientCluster.SkipCheckForPreviousInstallations = true + + clientCluster.Create(t) + + // verify partition ap1 created by partition init job + serverResourceClient := serverCluster.ResourceClient(t, false) + _, err := serverResourceClient.Read(context.Background(), &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: "ap1", + Type: pbtenancy.PartitionType, + }, + }) + require.NoError(t, err, "expected partition ap1 to be created by partition init job") +} diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 98384b3868..decb40319c 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -488,8 +488,8 @@ Usage: {{ template "consul.validateResourceAPIs" . }} {{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.peering.enabled ) }} {{fail "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported."}} {{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.adminPartitions.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.adminPartitions.enabled is currently unsupported."}} +{{- if (and (mustHas "resource-apis" .Values.global.experiments) (not (mustHas "v2tenancy" .Values.global.experiments)) .Values.global.adminPartitions.enabled ) }} +{{fail "When the value global.experiments.resourceAPIs is set, global.experiments.v2tenancy must also be set to support global.adminPartitions.enabled."}} {{- end }} {{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.federation.enabled ) }} {{fail "When the value global.experiments.resourceAPIs is set, global.federation.enabled is currently unsupported."}} @@ -515,4 +515,4 @@ Usage: {{ template "consul.validateResourceAPIs" . }} {{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.apiGateway.enabled ) }} {{fail "When the value global.experiments.resourceAPIs is set, apiGateway.enabled is currently unsupported."}} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 59a119e0c7..f8a44859f1 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -118,6 +118,9 @@ spec: {{- if .Values.global.cloud.enabled }} -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} \ {{- end }} + {{- if and (mustHas "resource-apis" .Values.global.experiments) (mustHas "v2tenancy" .Values.global.experiments) }} + -enable-v2tenancy=true + {{- end }} resources: requests: memory: "50Mi" diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index 5c76a12389..b056672c58 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -430,8 +430,17 @@ spec: {{- if and .Values.global.cloud.enabled .Values.global.cloud.resourceId.secretName }} -hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }" {{- end }} - {{- if (mustHas "resource-apis" .Values.global.experiments) }} - -hcl="experiments=[\"resource-apis\"]" + + {{- if .Values.global.experiments }} + {{- $commaSeparatedValues := "" }} + {{- range $index, $value := .Values.global.experiments }} + {{- if ne $index 0 }} + {{- $commaSeparatedValues = printf "%s,\\\"%s\\\"" $commaSeparatedValues $value }} + {{- else }} + {{- $commaSeparatedValues = printf "\\\"%s\\\"" $value }} + {{- end }} + {{- end }} + -hcl="experiments=[{{ $commaSeparatedValues }}]" {{- end }} volumeMounts: - name: data-{{ .Release.Namespace | trunc 58 | trimSuffix "-" }} diff --git a/charts/consul/test/unit/helpers.bats b/charts/consul/test/unit/helpers.bats index b1a7c54cb6..20772788f8 100644 --- a/charts/consul/test/unit/helpers.bats +++ b/charts/consul/test/unit/helpers.bats @@ -348,7 +348,7 @@ load _helpers [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported." ]] } -@test "connectInject/Deployment: fails if resource-apis is set and admin partitions are enabled" { +@test "connectInject/Deployment: fails if resource-apis is set, v2tenancy is unset, and admin partitions are enabled" { cd `chart_dir` run helm template \ -s templates/tests/test-runner.yaml \ @@ -359,7 +359,7 @@ load _helpers --set 'global.adminPartitions.enabled=true' \ . [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.adminPartitions.enabled is currently unsupported." ]] + [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.experiments.v2tenancy must also be set to support global.adminPartitions.enabled." ]] } @test "connectInject/Deployment: fails if resource-apis is set and federation is enabled" { diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index 131d13f8a1..745e23adfe 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -58,9 +58,7 @@ load _helpers cd `chart_dir` assert_empty helm template \ -s templates/partition-init-job.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'server.enabled=true' \ + --set 'global.adminPartitions.enabled=false' \ . } @@ -111,6 +109,27 @@ load _helpers [ "${actual}" = "5s" ] } +#-------------------------------------------------------------------- +# v2tenancy experiment + +@test "partitionInit/Job: -enable-v2tenancy=true is set when global.experiments contains [\"resource-apis\", \"v2tenancy\"]" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'server.enabled=false' \ + --set 'global.adminPartitions.name=bar' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'global.experiments[1]=v2tenancy' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-v2tenancy=true"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # global.tls.enabled diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 16ae7f8bc4..2a9cd57651 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -759,6 +759,14 @@ global: # upgrade could risk breaking your Consul cluster. # If this flag is set, Consul components will use the # V2 resources APIs for all operations. + # * `v2tenancy`: + # _**Danger**_! This feature is under active development. It is not + # recommended for production use. Setting this flag during an + # upgrade could risk breaking your Consul cluster. + # If this flag is set, Consul V2 resources (catalog, mesh, auth, etc) + # will use V2 implementations for tenancy (partitions and namesapces) + # instead of bridging to the existing V1 implementations. The + # `resource-apis` feature flag must also be set. # # Example: # diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index 9c354dfe2e..ade70dac2f 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -3,18 +3,18 @@ module github.com/hashicorp/consul-k8s/control-plane/cni require ( github.com/containernetworking/cni v1.1.1 github.com/containernetworking/plugins v1.1.1 - github.com/hashicorp/consul/sdk v0.13.1 - github.com/hashicorp/go-hclog v1.2.2 - github.com/stretchr/testify v1.7.2 + github.com/hashicorp/consul/sdk v0.15.0 + github.com/hashicorp/go-hclog v1.5.0 + github.com/stretchr/testify v1.8.3 k8s.io/api v0.22.2 k8s.io/apimachinery v0.22.2 k8s.io/client-go v0.22.2 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.14.1 // indirect github.com/go-logr/logr v0.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -23,12 +23,12 @@ require ( github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/json-iterator/go v1.1.11 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // 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.17.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect @@ -48,6 +48,6 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) -replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 +//replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 go 1.20 diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index c08ccadbde..64e48dbda2 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -45,8 +45,9 @@ github.com/containernetworking/plugins v1.1.1 h1:+AGfFigZ5TiQH00vhR8qPeSatj53eNG github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -55,10 +56,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -132,13 +132,10 @@ github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9 github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= -github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= -github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= +github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -156,22 +153,22 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -182,7 +179,6 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -201,26 +197,27 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -306,7 +303,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -317,7 +313,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -338,8 +333,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -457,8 +452,8 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= diff --git a/control-plane/go.mod b/control-plane/go.mod index 4116047f7c..81effa4e17 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3 replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f diff --git a/control-plane/go.sum b/control-plane/go.sum index e65b753ba7..da33abce0d 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f h1:wbJDaQukQ6LPIZwAqMSDl/fO7Wd1Yij8vFBam0ABy6A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231201193057-9b4c05bfd85f/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3 h1:FFRKi+IpoXHwXZDgqG+BNAG1duAuokbzm+5b2pcY1us= +github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go index 0aa8cdc724..19bb1bc6f5 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -11,10 +11,16 @@ import ( "sync" "time" - "github.com/hashicorp/consul-server-connection-manager/discovery" - "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/hashicorp/consul-server-connection-manager/discovery" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/proto-public/pbresource" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" @@ -27,9 +33,10 @@ type Command struct { flags *flag.FlagSet consul *flags.ConsulFlags - flagLogLevel string - flagLogJSON bool - flagTimeout time.Duration + flagLogLevel string + flagLogJSON bool + flagTimeout time.Duration + flagV2Tenancy bool // ctx is cancelled when the command timeout is reached. ctx context.Context @@ -52,6 +59,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.BoolVar(&c.flagV2Tenancy, "enable-v2tenancy", false, + "Enable V2 tenancy.") c.consul = &flags.ConsulFlags{} flags.Merge(c.flags, c.consul.Flags()) @@ -70,6 +79,106 @@ func (c *Command) Help() string { return c.help } +func (c *Command) ensureV2Partition(scm consul.ServerConnectionManager) error { + client, err := consul.NewResourceServiceClient(scm) + if err != nil { + c.UI.Error(fmt.Sprintf("unable to create grpc client: %s", err)) + return err + } + + for { + id := &pbresource.ID{ + Name: c.consul.Partition, + Type: pbtenancy.PartitionType, + } + + _, err = client.Read(c.ctx, &pbresource.ReadRequest{Id: id}) + switch { + + // found -> done + case err == nil: + c.log.Info("Admin Partition already exists", "name", c.consul.Partition) + return nil + + // not found -> create + case status.Code(err) == codes.NotFound: + data, err := anypb.New(&pbtenancy.Partition{Description: "Created by Helm installation"}) + if err != nil { + continue + } + _, err = client.Write(c.ctx, &pbresource.WriteRequest{Resource: &pbresource.Resource{ + Id: id, + Data: data, + }}) + if err == nil { + c.log.Info("Successfully created Admin Partition", "name", c.consul.Partition) + return nil + } + + // unexpected error -> retry + default: + c.log.Error("Error reading Partition from Consul", "name", c.consul.Partition, "error", err.Error()) + } + + // Wait on either the retry duration (in which case we continue) or the + // overall command timeout. + c.log.Info("Retrying in " + c.retryDuration.String()) + select { + case <-time.After(c.retryDuration): + continue + case <-c.ctx.Done(): + c.log.Error("Timed out attempting to ensure partition exists", "name", c.consul.Partition) + return err + } + } +} + +func (c *Command) ensureV1Partition(scm consul.ServerConnectionManager) error { + state, err := scm.State() + if err != nil { + c.UI.Error(fmt.Sprintf("unable to get Consul server addresses from watcher: %s", err)) + return err + } + + consulClient, err := consul.NewClientFromConnMgrState(c.consul.ConsulClientConfig(), state) + if err != nil { + c.UI.Error(fmt.Sprintf("unable to create Consul client: %s", err)) + return err + } + + for { + partition, _, err := consulClient.Partitions().Read(c.ctx, c.consul.Partition, nil) + // The API does not return an error if the Partition does not exist. It returns a nil Partition. + if err != nil { + c.log.Error("Error reading Partition from Consul", "name", c.consul.Partition, "error", err.Error()) + } else if partition == nil { + // Retry Admin Partition creation until it succeeds, or we reach the command timeout. + _, _, err = consulClient.Partitions().Create(c.ctx, &api.Partition{ + Name: c.consul.Partition, + Description: "Created by Helm installation", + }, nil) + if err == nil { + c.log.Info("Successfully created Admin Partition", "name", c.consul.Partition) + return nil + } + c.log.Error("Error creating partition", "name", c.consul.Partition, "error", err.Error()) + } else { + c.log.Info("Admin Partition already exists", "name", c.consul.Partition) + return nil + } + // Wait on either the retry duration (in which case we continue) or the + // overall command timeout. + c.log.Info("Retrying in " + c.retryDuration.String()) + select { + case <-time.After(c.retryDuration): + continue + case <-c.ctx.Done(): + c.log.Error("Timed out attempting to create partition", "name", c.consul.Partition) + return fmt.Errorf("") + } + } +} + // Run bootstraps Admin Partitions on Consul servers. // The function will retry its tasks until success, or it exceeds its timeout. func (c *Command) Run(args []string) int { @@ -115,49 +224,15 @@ func (c *Command) Run(args []string) int { go watcher.Run() defer watcher.Stop() - state, err := watcher.State() - if err != nil { - c.UI.Error(fmt.Sprintf("unable to get Consul server addresses from watcher: %s", err)) - return 1 + if c.flagV2Tenancy { + err = c.ensureV2Partition(watcher) + } else { + err = c.ensureV1Partition(watcher) } - - consulClient, err := consul.NewClientFromConnMgrState(c.consul.ConsulClientConfig(), state) if err != nil { - c.UI.Error(fmt.Sprintf("unable to create Consul client: %s", err)) return 1 } - - for { - partition, _, err := consulClient.Partitions().Read(c.ctx, c.consul.Partition, nil) - // The API does not return an error if the Partition does not exist. It returns a nil Partition. - if err != nil { - c.log.Error("Error reading Partition from Consul", "name", c.consul.Partition, "error", err.Error()) - } else if partition == nil { - // Retry Admin Partition creation until it succeeds, or we reach the command timeout. - _, _, err = consulClient.Partitions().Create(c.ctx, &api.Partition{ - Name: c.consul.Partition, - Description: "Created by Helm installation", - }, nil) - if err == nil { - c.log.Info("Successfully created Admin Partition", "name", c.consul.Partition) - return 0 - } - c.log.Error("Error creating partition", "name", c.consul.Partition, "error", err.Error()) - } else { - c.log.Info("Admin Partition already exists", "name", c.consul.Partition) - return 0 - } - // Wait on either the retry duration (in which case we continue) or the - // overall command timeout. - c.log.Info("Retrying in " + c.retryDuration.String()) - select { - case <-time.After(c.retryDuration): - continue - case <-c.ctx.Done(): - c.log.Error("Timed out attempting to create partition", "name", c.consul.Partition) - return 1 - } - } + return 0 } func (c *Command) validateFlags() error { diff --git a/control-plane/subcommand/partition-init/command_ent_test.go b/control-plane/subcommand/partition-init/command_ent_test.go index 182412c8aa..72858243f4 100644 --- a/control-plane/subcommand/partition-init/command_ent_test.go +++ b/control-plane/subcommand/partition-init/command_ent_test.go @@ -7,14 +7,22 @@ package partition_init import ( "context" - "strings" + "strconv" "testing" "time" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/proto-public/pbresource" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" + "github.com/hashicorp/consul/sdk/testutil" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) func TestRun_FlagValidation(t *testing.T) { @@ -34,7 +42,10 @@ func TestRun_FlagValidation(t *testing.T) { }, { flags: []string{ - "-addresses", "foo", "-partition", "bar", "-api-timeout", "0s"}, + "-addresses", "foo", + "-partition", "bar", + "-api-timeout", "0s", + }, expErr: "-api-timeout must be set to a value greater than 0", }, { @@ -61,103 +72,236 @@ func TestRun_FlagValidation(t *testing.T) { func TestRun_PartitionCreate(t *testing.T) { partitionName := "test-partition" - server, err := testutil.NewTestServerConfigT(t, nil) - require.NoError(t, err) - server.WaitForLeader(t) - defer server.Stop() + type testCase struct { + v2tenancy bool + experiments []string + requirePartitionCreated func(testClient *test.TestServerClient) + } - consul, err := api.NewClient(&api.Config{ - Address: server.HTTPAddr, - }) - require.NoError(t, err) + testCases := map[string]testCase{ + "v2tenancy false": { + v2tenancy: false, + experiments: []string{}, + requirePartitionCreated: func(testClient *test.TestServerClient) { + consul, err := api.NewClient(testClient.Cfg.APIClientConfig) + require.NoError(t, err) - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - } - cmd.init() - args := []string{ - "-addresses=" + "127.0.0.1", - "-http-port=" + strings.Split(server.HTTPAddr, ":")[1], - "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], - "-partition", partitionName, + partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) + require.NoError(t, err) + require.NotNil(t, partition) + require.Equal(t, partitionName, partition.Name) + }, + }, + "v2tenancy true": { + v2tenancy: true, + experiments: []string{"resource-apis", "v2tenancy"}, + requirePartitionCreated: func(testClient *test.TestServerClient) { + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + _, err = resourceClient.Read(context.Background(), &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: partitionName, + Type: pbtenancy.PartitionType, + }, + }) + require.NoError(t, err, "expected partition to be created") + }, + }, } - responseCode := cmd.Run(args) + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + var serverCfg *testutil.TestServerConfig + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = tc.experiments + serverCfg = c + }) - require.Equal(t, 0, responseCode) + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + cmd.init() + args := []string{ + "-addresses=" + "127.0.0.1", + "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), + "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), + "-partition", partitionName, + "-timeout", "1m", + "-enable-v2tenancy=" + strconv.FormatBool(tc.v2tenancy), + } - partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) - require.NoError(t, err) - require.NotNil(t, partition) - require.Equal(t, partitionName, partition.Name) + responseCode := cmd.Run(args) + require.Equal(t, 0, responseCode) + tc.requirePartitionCreated(testClient) + }) + } } func TestRun_PartitionExists(t *testing.T) { partitionName := "test-partition" + partitionDesc := "Created before test" - server, err := testutil.NewTestServerConfigT(t, nil) - require.NoError(t, err) - server.WaitForLeader(t) - defer server.Stop() + type testCase struct { + v2tenancy bool + experiments []string + preCreatePartition func(testClient *test.TestServerClient) + requirePartitionNotCreated func(testClient *test.TestServerClient) + } - consul, err := api.NewClient(&api.Config{ - Address: server.HTTPAddr, - }) - require.NoError(t, err) + testCases := map[string]testCase{ + "v2tenancy false": { + v2tenancy: false, + experiments: []string{}, - // Create the Admin Partition before the test runs. - _, _, err = consul.Partitions().Create(context.Background(), &api.Partition{Name: partitionName, Description: "Created before test"}, nil) - require.NoError(t, err) + preCreatePartition: func(testClient *test.TestServerClient) { + consul, err := api.NewClient(testClient.Cfg.APIClientConfig) + require.NoError(t, err) - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - } - cmd.init() - args := []string{ - "-addresses=" + "127.0.0.1", - "-http-port=" + strings.Split(server.HTTPAddr, ":")[1], - "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], - "-partition", partitionName, + _, _, err = consul.Partitions().Create(context.Background(), &api.Partition{ + Name: partitionName, + Description: partitionDesc, + }, nil) + require.NoError(t, err) + }, + requirePartitionNotCreated: func(testClient *test.TestServerClient) { + consul, err := api.NewClient(testClient.Cfg.APIClientConfig) + require.NoError(t, err) + + partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) + require.NoError(t, err) + require.NotNil(t, partition) + require.Equal(t, partitionName, partition.Name) + require.Equal(t, partitionDesc, partition.Description) + }, + }, + "v2tenancy true": { + v2tenancy: true, + experiments: []string{"resource-apis", "v2tenancy"}, + preCreatePartition: func(testClient *test.TestServerClient) { + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + data, err := anypb.New(&pbtenancy.Partition{Description: partitionDesc}) + require.NoError(t, err) + + _, err = resourceClient.Write(context.Background(), &pbresource.WriteRequest{ + Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: partitionName, + Type: pbtenancy.PartitionType, + }, + Data: data, + }, + }) + require.NoError(t, err) + }, + requirePartitionNotCreated: func(testClient *test.TestServerClient) { + resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) + require.NoError(t, err) + + rsp, err := resourceClient.Read(context.Background(), &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: partitionName, + Type: pbtenancy.PartitionType, + }, + }) + require.NoError(t, err) + + partition := &pbtenancy.Partition{} + err = anypb.UnmarshalTo(rsp.Resource.Data, partition, proto.UnmarshalOptions{}) + require.NoError(t, err) + require.Equal(t, partitionDesc, partition.Description) + }, + }, } - responseCode := cmd.Run(args) + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + var serverCfg *testutil.TestServerConfig + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = tc.experiments + serverCfg = c + }) + + // Create the Admin Partition before the test runs. + tc.preCreatePartition(testClient) - require.Equal(t, 0, responseCode) + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + cmd.init() + args := []string{ + "-addresses=" + "127.0.0.1", + "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), + "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), + "-partition", partitionName, + "-enable-v2tenancy=" + strconv.FormatBool(tc.v2tenancy), + } + + responseCode := cmd.Run(args) + require.Equal(t, 0, responseCode) - partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) - require.NoError(t, err) - require.NotNil(t, partition) - require.Equal(t, partitionName, partition.Name) - require.Equal(t, "Created before test", partition.Description) + // Verify that the Admin Partition was not overwritten. + tc.requirePartitionNotCreated(testClient) + }) + } } func TestRun_ExitsAfterTimeout(t *testing.T) { partitionName := "test-partition" - server, err := testutil.NewTestServerConfigT(t, nil) - require.NoError(t, err) + type testCase struct { + v2tenancy bool + experiments []string + } - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, + testCases := map[string]testCase{ + "v2tenancy false": { + v2tenancy: false, + experiments: []string{}, + }, + "v2tenancy true": { + v2tenancy: true, + experiments: []string{"resource-apis", "v2tenancy"}, + }, } - cmd.init() - args := []string{ - "-addresses=" + "127.0.0.1", - "-http-port=" + strings.Split(server.HTTPAddr, ":")[1], - "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], - "-timeout", "500ms", - "-partition", partitionName, + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + var serverCfg *testutil.TestServerConfig + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = tc.experiments + serverCfg = c + }) + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + cmd.init() + + timeout := 500 * time.Millisecond + args := []string{ + "-addresses=" + "127.0.0.1", + "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), + "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), + "-timeout", timeout.String(), + "-partition", partitionName, + "-enable-v2tenancy=" + strconv.FormatBool(tc.v2tenancy), + } + + testClient.TestServer.Stop() + startTime := time.Now() + responseCode := cmd.Run(args) + completeTime := time.Now() + require.Equal(t, 1, responseCode) + + // While the timeout is 500ms, adding a buffer of 500ms ensures we account for + // some buffer time required for the task to run and assignments to occur. + require.WithinDuration(t, completeTime, startTime, timeout+500*time.Millisecond) + }) } - server.Stop() - startTime := time.Now() - responseCode := cmd.Run(args) - completeTime := time.Now() - - require.Equal(t, 1, responseCode) - // While the timeout is 500ms, adding a buffer of 500ms ensures we account for - // some buffer time required for the task to run and assignments to occur. - require.WithinDuration(t, completeTime, startTime, 1*time.Second) } From 3353bd3dd0de3650a6ca5fae622e4879e929d9cd Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Wed, 3 Jan 2024 13:23:02 -0600 Subject: [PATCH 549/592] delete gateway in cleanup-gateway-resouces (#3427) * delete gateway in cleanup-gateway-resouces * fix nitpick * fix nit in other file --- .../gateway-resources-configmap.yaml | 1 - control-plane/gateways/config.go | 10 ++++- .../subcommand/gateway-cleanup/command.go | 39 ++++++++++++++----- .../subcommand/gateway-resources/command.go | 8 +--- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 1fe8b11979..e9006a180b 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -69,7 +69,6 @@ data: {{- with .Values.meshGateway.dnsPolicy }} dnsPolicy: {{ . }} {{- end }} - priorityClassName: {{ toJson .Values.meshGateway.priorityClassName }} {{- if .Values.meshGateway.topologySpreadConstraints }} topologySpreadConstraints: {{ tpl .Values.meshGateway.topologySpreadConstraints . | nindent 16 | trim }} diff --git a/control-plane/gateways/config.go b/control-plane/gateways/config.go index 9a48ec4350..265678ea8c 100644 --- a/control-plane/gateways/config.go +++ b/control-plane/gateways/config.go @@ -3,7 +3,10 @@ package gateways -import "github.com/hashicorp/consul-k8s/control-plane/api/common" +import ( + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) // GatewayConfig is a combination of settings relevant to Gateways. type GatewayConfig struct { @@ -38,3 +41,8 @@ type GatewayConfig struct { // defined on a Gateway. MapPrivilegedServicePorts int } + +type GatewayResources struct { + GatewayClassConfigs []*v2beta1.GatewayClassConfig `json:"gatewayClassConfigs"` + MeshGateways []*v2beta1.MeshGateway `json:"meshGateways"` +} diff --git a/control-plane/subcommand/gateway-cleanup/command.go b/control-plane/subcommand/gateway-cleanup/command.go index 5e01ca0d3c..709f925c66 100644 --- a/control-plane/subcommand/gateway-cleanup/command.go +++ b/control-plane/subcommand/gateway-cleanup/command.go @@ -25,6 +25,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/gateways" "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -50,15 +51,11 @@ type Command struct { once sync.Once help string - gatewayConfig gatewayConfig + gatewayConfig gateways.GatewayResources ctx context.Context } -type gatewayConfig struct { - GatewayClassConfigs []*v2beta1.GatewayClassConfig `yaml:"gatewayClassConfigs"` -} - func (c *Command) init() { c.flags = flag.NewFlagSet("", flag.ContinueOnError) @@ -144,7 +141,14 @@ func (c *Command) Run(args []string) int { c.UI.Error(err.Error()) return 1 } - err = c.deleteV2GatewayClassAndClassConfigs() + err = c.deleteV2GatewayClassAndClassConfigs(c.ctx) + if err != nil { + c.UI.Error(err.Error()) + + return 1 + } + + err = c.deleteV2MeshGateways(c.ctx) if err != nil { c.UI.Error(err.Error()) @@ -277,13 +281,13 @@ func (c *Command) loadGatewayConfigs() error { return nil } -func (c *Command) deleteV2GatewayClassAndClassConfigs() error { +func (c *Command) deleteV2GatewayClassAndClassConfigs(ctx context.Context) error { for _, gcc := range c.gatewayConfig.GatewayClassConfigs { // find the class config and mark it for deletion first so that we // can do an early return if the gateway class isn't found config := &v2beta1.GatewayClassConfig{} - err := c.k8sClient.Get(context.Background(), types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, config) + err := c.k8sClient.Get(ctx, types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, config) if err != nil { if k8serrors.IsNotFound(err) { // no gateway class config, just ignore and continue @@ -298,7 +302,7 @@ func (c *Command) deleteV2GatewayClassAndClassConfigs() error { // find the gateway class gatewayClass := &v2beta1.GatewayClass{} //TODO: NET-6838 To pull the GatewayClassName from the Configmap - err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, gatewayClass) + err = c.k8sClient.Get(ctx, types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, gatewayClass) if err != nil { if k8serrors.IsNotFound(err) { // no gateway class, just ignore and continue @@ -332,3 +336,20 @@ func (c *Command) deleteV2GatewayClassAndClassConfigs() error { return nil } + +func (c *Command) deleteV2MeshGateways(ctx context.Context) error { + for _, meshGw := range c.gatewayConfig.MeshGateways { + _ = c.k8sClient.Delete(ctx, meshGw) + + err := c.k8sClient.Get(ctx, types.NamespacedName{Name: meshGw.Name, Namespace: meshGw.Namespace}, meshGw) + if err != nil { + if k8serrors.IsNotFound(err) { + // no gateway, just ignore and continue + continue + } + return err + } + + } + return nil +} diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index 1b93d6bba6..bf68fbbe47 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -34,6 +34,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/gateways" "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -104,16 +105,11 @@ type Command struct { tolerations []corev1.Toleration serviceAnnotations []string resources corev1.ResourceRequirements - gatewayConfig gatewayConfig + gatewayConfig gateways.GatewayResources ctx context.Context } -type gatewayConfig struct { - GatewayClassConfigs []*v2beta1.GatewayClassConfig `json:"gatewayClassConfigs"` - MeshGateways []*v2beta1.MeshGateway `json:"meshGateways"` -} - func (c *Command) init() { c.flags = flag.NewFlagSet("", flag.ContinueOnError) From 6310d7e5eff909ca609dd9168ae1deaee40930c4 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Wed, 3 Jan 2024 15:50:42 -0500 Subject: [PATCH 550/592] Support WAN Address Annotations (#3420) * Add wanAddress configuration to the configmap * Set the annotations on the mesh gateway CRD * Patch through the annotations from the Mesh Gateway * Fix Job -> ConfigMap * Use JSON to compare annotations * Add annotations to deployment test * Fix checking annotations in helm tests --- .../gateway-resources-configmap.yaml | 18 ++++ .../unit/gateway-resources-configmap.bats | 85 +++++++++++++++++-- charts/consul/values.yaml | 2 +- control-plane/gateways/deployment.go | 7 ++ control-plane/gateways/deployment_test.go | 20 +++++ 5 files changed, 126 insertions(+), 6 deletions(-) diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index e9006a180b..71d48a58bb 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -1,4 +1,9 @@ {{- if .Values.connectInject.enabled }} + +# Validation +# For meshGateway.wanAddress, static must be set if source is "Static" +{{if (and (eq .Values.meshGateway.wanAddress.source "Static") (eq .Values.meshGateway.wanAddress.static ""))}}{{fail ".meshGateway.wanAddress.static must be set to a value if .meshGateway.wanAddress.source is Static"}}{{ end }} + # Configuration of Gateway Resources Job which creates managed Gateway configuration. apiVersion: v1 kind: ConfigMap @@ -101,6 +106,19 @@ data: metadata: name: mesh-gateway namespace: {{ .Release.Namespace }} + annotations: + # TODO are these annotations even necessary? + "consul.hashicorp.com/gateway-wan-address-source": {{ .Values.meshGateway.wanAddress.source | quote }} + "consul.hashicorp.com/gateway-wan-address-static": {{ .Values.meshGateway.wanAddress.static | quote }} + {{- if eq .Values.meshGateway.wanAddress.source "Service" }} + {{- if eq .Values.meshGateway.service.type "NodePort" }} + "consul.hashicorp.com/gateway-wan-port": {{ .Values.meshGateway.service.nodePort | quote }} + {{- else }} + "consul.hashicorp.com/gateway-wan-port": {{ .Values.meshGateway.service.port | quote }} + {{- end }} + {{- else }} + "consul.hashicorp.com/gateway-wan-port": {{ .Values.meshGateway.wanAddress.port | quote }} + {{- end }} spec: gatewayClassName: consul-mesh-gateway {{- end }} diff --git a/charts/consul/test/unit/gateway-resources-configmap.bats b/charts/consul/test/unit/gateway-resources-configmap.bats index df66606194..4b76e55d39 100644 --- a/charts/consul/test/unit/gateway-resources-configmap.bats +++ b/charts/consul/test/unit/gateway-resources-configmap.bats @@ -2,10 +2,12 @@ load _helpers +target=templates/gateway-resources-configmap.yaml + @test "gateway-resources/ConfigMap: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/gateway-resources-configmap.yaml \ + -s $target \ --set 'connectInject.enabled=false' \ . } @@ -13,7 +15,7 @@ load _helpers @test "gateway-resources/ConfigMap: enabled with connectInject.enabled=true" { cd `chart_dir` local actual=$(helm template \ - -s templates/gateway-resources-configmap.yaml \ + -s $target \ --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) @@ -23,7 +25,7 @@ load _helpers @test "gateway-resources/ConfigMap: contains resources configuration as JSON" { cd `chart_dir` local resources=$(helm template \ - -s templates/gateway-resources-configmap.yaml \ + -s $target \ --set 'connectInject.enabled=true' \ --set 'connectInject.apiGateway.managedGatewayClass.resources.requests.memory=200Mi' \ --set 'connectInject.apiGateway.managedGatewayClass.resources.requests.cpu=200m' \ @@ -48,7 +50,7 @@ load _helpers @test "gateway-resources/ConfigMap: does not contain config.yaml resources without .global.experiments equal to resource-apis" { cd `chart_dir` local resources=$(helm template \ - -s templates/gateway-resources-configmap.yaml \ + -s $target \ --set 'connectInject.enabled=true' \ --set 'ui.enabled=false' \ . | tee /dev/stderr | @@ -60,7 +62,7 @@ load _helpers @test "gateway-resources/ConfigMap: contains config.yaml resources with .global.experiments equal to resource-apis" { cd `chart_dir` local resources=$(helm template \ - -s templates/gateway-resources-configmap.yaml \ + -s $target \ --set 'connectInject.enabled=true' \ --set 'meshGateway.enabled=true' \ --set 'global.experiments[0]=resource-apis' \ @@ -70,3 +72,76 @@ load _helpers [ "$resources" != null ] } + + +#-------------------------------------------------------------------- +# Mesh Gateway WAN Address configuration + +@test "gateway-resources/ConfigMap: Mesh Gateway WAN Address default annotations" { + cd `chart_dir` + local annotations=$(helm template \ + -s $target \ + --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.meshGateways[0].metadata.annotations' | tee /dev/stderr) + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') + [ "${actual}" = 'Service' ] + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-port"]') + [ "${actual}" = '443' ] + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-static"]') + [ "${actual}" = '' ] +} + +@test "gateway-resources/ConfigMap: Mesh Gateway WAN Address NodePort annotations" { + cd `chart_dir` + local annotations=$(helm template \ + -s $target \ + --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'meshGateway.wanAddress.source=Service' \ + --set 'meshGateway.service.type=NodePort' \ + --set 'meshGateway.service.nodePort=30000' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.meshGateways[0].metadata.annotations' | tee /dev/stderr) + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') + [ "${actual}" = 'Service' ] + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-port"]') + [ "${actual}" = '30000' ] + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-static"]') + [ "${actual}" = '' ] +} + +@test "gateway-resources/ConfigMap: Mesh Gateway WAN Address static configuration" { + cd `chart_dir` + local annotations=$(helm template \ + -s $target \ + --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'meshGateway.wanAddress.source=Static' \ + --set 'meshGateway.wanAddress.static=127.0.0.1' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.meshGateways[0].metadata.annotations' | tee /dev/stderr) + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') + [ "${actual}" = 'Static' ] + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-port"]') + [ "${actual}" = '443' ] + + local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-static"]') + [ "${actual}" = '127.0.0.1' ] +} + diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 2a9cd57651..ac576c2f42 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2771,7 +2771,7 @@ meshGateway: # are routable from other datacenters. # # - `Static` - Use the address hardcoded in `meshGateway.wanAddress.static`. - source: "Service" + source: Service # Port that gets registered for WAN traffic. # If source is set to "Service" then this setting will have no effect. diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index 683956b170..d2221bf33a 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -70,6 +70,13 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { constants.AnnotationMeshInject: "false", // This functionality only applies when proxy sidecars are used constants.AnnotationTransparentProxyOverwriteProbes: "false", + // This annotation determines which source to use to set the + // WAN address and WAN port for the Mesh Gateway service registration. + constants.AnnotationGatewayWANSource: b.gateway.Annotations[constants.AnnotationGatewayWANSource], + // This annotation determines the WAN port for the Mesh Gateway service registration. + constants.AnnotationGatewayWANPort: b.gateway.Annotations[constants.AnnotationGatewayWANPort], + // This annotation determines the address for the gateway when the source annotation is "Static". + constants.AnnotationGatewayWANAddress: b.gateway.Annotations[constants.AnnotationGatewayWANAddress], }, }, Spec: corev1.PodSpec{ diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 513aac61f6..2abfd91e57 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -35,6 +35,13 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { name: "happy path", fields: fields{ gateway: &meshv2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationGatewayWANSource: "Service", + constants.AnnotationGatewayWANPort: "443", + constants.AnnotationGatewayWANAddress: "", + }, + }, Spec: pbmesh.MeshGateway{ GatewayClassName: "test-gateway-class", }, @@ -133,6 +140,9 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { constants.AnnotationGatewayKind: meshGatewayAnnotationKind, constants.AnnotationMeshInject: "false", constants.AnnotationTransparentProxyOverwriteProbes: "false", + constants.AnnotationGatewayWANSource: "Service", + constants.AnnotationGatewayWANPort: "443", + constants.AnnotationGatewayWANAddress: "", }, }, Spec: corev1.PodSpec{ @@ -389,6 +399,13 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { name: "nil gatewayclassconfig - (notfound)", fields: fields{ gateway: &meshv2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationGatewayWANSource: "Service", + constants.AnnotationGatewayWANPort: "443", + constants.AnnotationGatewayWANAddress: "", + }, + }, Spec: pbmesh.MeshGateway{ GatewayClassName: "test-gateway-class", }, @@ -413,6 +430,9 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { constants.AnnotationGatewayKind: meshGatewayAnnotationKind, constants.AnnotationMeshInject: "false", constants.AnnotationTransparentProxyOverwriteProbes: "false", + constants.AnnotationGatewayWANSource: "Service", + constants.AnnotationGatewayWANPort: "443", + constants.AnnotationGatewayWANAddress: "", }, }, Spec: corev1.PodSpec{ From bae8e15724dc1871df53643e13a858a65e4c90a0 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 4 Jan 2024 12:04:58 -0500 Subject: [PATCH 551/592] [NET-6829] Add tls support for mesh gateways (#3429) * Add tls support for mesh gateways * Added tests * fixing tests that broke from rebase * extract function to build tls args for dataplane container * move tls env vars to constants --- control-plane/api-gateway/gatekeeper/init.go | 7 +- .../connect-inject/constants/constants.go | 9 +- .../connect-inject/webhook/container_init.go | 6 +- .../webhookv2/container_init.go | 6 +- .../deployment_dataplane_container.go | 22 +- .../gateways/deployment_init_container.go | 18 +- control-plane/gateways/deployment_test.go | 399 ++++++++++++++++++ control-plane/subcommand/flags/consul.go | 14 +- control-plane/subcommand/flags/consul_test.go | 11 +- 9 files changed, 463 insertions(+), 29 deletions(-) diff --git a/control-plane/api-gateway/gatekeeper/init.go b/control-plane/api-gateway/gatekeeper/init.go index f3d4ad1f95..2bfa3f8e83 100644 --- a/control-plane/api-gateway/gatekeeper/init.go +++ b/control-plane/api-gateway/gatekeeper/init.go @@ -12,6 +12,7 @@ import ( corev1 "k8s.io/api/core/v1" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" "k8s.io/utils/pointer" ) @@ -119,15 +120,15 @@ func initContainer(config common.HelmConfig, name, namespace string) (corev1.Con if config.TLSEnabled { container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_USE_TLS", + Name: constants.UseTLSEnvVar, Value: "true", }, corev1.EnvVar{ - Name: "CONSUL_CACERT_PEM", + Name: constants.CACertPEMEnvVar, Value: config.ConsulCACert, }, corev1.EnvVar{ - Name: "CONSUL_TLS_SERVER_NAME", + Name: constants.TLSServerNameEnvVar, Value: config.ConsulTLSServerName, }) } diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index a29148be05..8913019a19 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -73,8 +73,13 @@ const ( KubernetesSuccessReasonMsg = "Kubernetes health checks passing" - // ProxyIDVolumePath is the name of the volume that contains the proxy ID. - ProxyIDVolumePath = "/consul/mesh-inject" + // MeshV2VolumePath is the name of the volume that contains the proxy ID. + MeshV2VolumePath = "/consul/mesh-inject" + + UseTLSEnvVar = "CONSUL_USE_TLS" + CACertFileEnvVar = "CONSUL_CACERT_FILE" + CACertPEMEnvVar = "CONSUL_CACERT_PEM" + TLSServerNameEnvVar = "CONSUL_TLS_SERVER_NAME" ) // GetNormalizedConsulNamespace returns the default namespace if the passed namespace diff --git a/control-plane/connect-inject/webhook/container_init.go b/control-plane/connect-inject/webhook/container_init.go index 88962f771e..f3f1cbc695 100644 --- a/control-plane/connect-inject/webhook/container_init.go +++ b/control-plane/connect-inject/webhook/container_init.go @@ -155,15 +155,15 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, if w.TLSEnabled { container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_USE_TLS", + Name: constants.UseTLSEnvVar, Value: "true", }, corev1.EnvVar{ - Name: "CONSUL_CACERT_PEM", + Name: constants.CACertPEMEnvVar, Value: w.ConsulCACert, }, corev1.EnvVar{ - Name: "CONSUL_TLS_SERVER_NAME", + Name: constants.TLSServerNameEnvVar, Value: w.ConsulTLSServerName, }) } diff --git a/control-plane/connect-inject/webhookv2/container_init.go b/control-plane/connect-inject/webhookv2/container_init.go index dcd486660f..6420b9e97d 100644 --- a/control-plane/connect-inject/webhookv2/container_init.go +++ b/control-plane/connect-inject/webhookv2/container_init.go @@ -124,15 +124,15 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) if w.TLSEnabled { container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_USE_TLS", + Name: constants.UseTLSEnvVar, Value: "true", }, corev1.EnvVar{ - Name: "CONSUL_CACERT_PEM", + Name: constants.CACertPEMEnvVar, Value: w.ConsulCACert, }, corev1.EnvVar{ - Name: "CONSUL_TLS_SERVER_NAME", + Name: constants.TLSServerNameEnvVar, Value: w.ConsulTLSServerName, }) } diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go index 4fd621ec94..9e34c24b52 100644 --- a/control-plane/gateways/deployment_dataplane_container.go +++ b/control-plane/gateways/deployment_dataplane_container.go @@ -79,7 +79,7 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate }, { Name: "TMPDIR", - Value: constants.ProxyIDVolumePath, + Value: constants.MeshV2VolumePath, }, { Name: "NODE_NAME", @@ -105,7 +105,7 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate VolumeMounts: []corev1.VolumeMount{ { Name: volumeName, - MountPath: constants.ProxyIDVolumePath, + MountPath: constants.MeshV2VolumePath, }, }, Args: args, @@ -186,7 +186,7 @@ func getDataplaneArgs(namespace string, config GatewayConfig, bearerTokenFile st args = append(args, "-service-partition="+config.ConsulTenancyConfig.ConsulPartition) } - args = append(args, "-tls-disabled") + args = append(args, buildTLSArgs(config)...) // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) @@ -195,3 +195,19 @@ func getDataplaneArgs(namespace string, config GatewayConfig, bearerTokenFile st return args, nil } + +func buildTLSArgs(config GatewayConfig) []string { + if !config.TLSEnabled { + return []string{"-tls-disabled"} + } + tlsArgs := make([]string, 0, 2) + + if config.ConsulTLSServerName != "" { + tlsArgs = append(tlsArgs, fmt.Sprintf("-tls-server-name=%s", config.ConsulTLSServerName)) + } + if config.ConsulCACert != "" { + tlsArgs = append(tlsArgs, fmt.Sprintf("-ca-certs=%s", constants.ConsulCAFile)) + } + + return tlsArgs +} diff --git a/control-plane/gateways/deployment_init_container.go b/control-plane/gateways/deployment_init_container.go index 354ffcce52..35ab25cf15 100644 --- a/control-plane/gateways/deployment_init_container.go +++ b/control-plane/gateways/deployment_init_container.go @@ -45,7 +45,7 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain volMounts := []corev1.VolumeMount{ { Name: volumeName, - MountPath: constants.ProxyIDVolumePath, + MountPath: constants.MeshV2VolumePath, }, } @@ -143,6 +143,22 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain Value: consulNamespace, }) + if config.TLSEnabled { + container.Env = append(container.Env, + corev1.EnvVar{ + Name: constants.UseTLSEnvVar, + Value: "true", + }, + corev1.EnvVar{ + Name: constants.CACertPEMEnvVar, + Value: config.ConsulCACert, + }, + corev1.EnvVar{ + Name: constants.TLSServerNameEnvVar, + Value: config.ConsulTLSServerName, + }) + } + if config.ConsulTenancyConfig.ConsulPartition != "" { container.Env = append(container.Env, corev1.EnvVar{ diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 2abfd91e57..90e4f0c40a 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -19,6 +19,27 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) +const testCert = `-----BEGIN CERTIFICATE----- │ +MIIDQjCCAuigAwIBAgIUZGIigQ4IKLoCh4XrXyi/c89B7ZgwCgYIKoZIzj0EAwIw │ +gZExCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5j │ +aXNjbzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1 │ +MRcwFQYDVQQKEw5IYXNoaUNvcnAgSW5jLjEYMBYGA1UEAxMPQ29uc3VsIEFnZW50 │ +IENBMB4XDTI0MDEwMzE4NTYyOVoXDTMzMTIzMTE4NTcyOVowgZExCzAJBgNVBAYT │ +AlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEaMBgGA1UE │ +CRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcwFQYDVQQKEw5I │ +YXNoaUNvcnAgSW5jLjEYMBYGA1UEAxMPQ29uc3VsIEFnZW50IENBMFkwEwYHKoZI │ +zj0CAQYIKoZIzj0DAQcDQgAEcbkdpZxlDOEuT3ZCcZ8H9j0Jad8ncDYk/Y0IbHPC │ +OKfFcpldEFPRv16WgSTHg38kK9WgEuK291+joBTHry3y06OCARowggEWMA4GA1Ud │ +DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0T │ +AQH/BAUwAwEB/zBoBgNVHQ4EYQRfZGY6MzA6YWE6NzI6ZTQ6ZTI6NzI6Y2Y6NTg6 │ +NDU6Zjk6YjU6NTA6N2I6ZDQ6MDI6MTE6ZjM6YzY6ZjE6NTc6NTE6MTg6NGU6OGU6 │ +ZjE6MmE6ZTE6MzI6NmY6ZTU6YjMwagYDVR0jBGMwYYBfZGY6MzA6YWE6NzI6ZTQ6 │ +ZTI6NzI6Y2Y6NTg6NDU6Zjk6YjU6NTA6N2I6ZDQ6MDI6MTE6ZjM6YzY6ZjE6NTc6 │ +NTE6MTg6NGU6OGU6ZjE6MmE6ZTE6MzI6NmY6ZTU6YjMwCgYIKoZIzj0EAwIDSAAw │ +RQIgXg8YtejEgGNxswtyXsvqzhLpt7k44L7TJMUhfIw0lUECIQCIxKNowmv0/XVz │ +nRnYLmGy79EZ2Y+CZS9nSm9Es6QNwg== │ +-----END CERTIFICATE-----` + func Test_meshGatewayBuilder_Deployment(t *testing.T) { type fields struct { gateway *meshv2beta1.MeshGateway @@ -395,6 +416,384 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, wantErr: false, }, + { + name: "happy path tls enabled", + fields: fields{ + gateway: &meshv2beta1.MeshGateway{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationGatewayWANSource: "Service", + constants.AnnotationGatewayWANPort: "443", + constants.AnnotationGatewayWANAddress: "", + }, + }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "test-gateway-class", + }, + }, + config: GatewayConfig{ + TLSEnabled: true, + ConsulCACert: testCert, + }, + gcc: &meshv2beta1.GatewayClassConfig{ + Spec: meshv2beta1.GatewayClassConfigSpec{ + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + Set: map[string]string{ + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + }, + }, + Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + Affinity: &corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 1, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, + }, + Container: &meshv2beta1.GatewayClassContainerConfig{ + HostPort: 8080, + PortModifier: 8000, + }, + NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, + Replicas: &meshv2beta1.GatewayClassReplicasConfig{ + Default: pointer.Int32(1), + Min: pointer.Int32(1), + Max: pointer.Int32(8), + }, + PriorityClassName: "priorityclassname", + TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ + { + MaxSkew: 1, + TopologyKey: "key", + WhenUnsatisfiable: "DoNotSchedule", + }, + }, + }, + }, + }, + }, + want: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + + Annotations: map[string]string{}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: pointer.Int32(1), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + Annotations: map[string]string{ + constants.AnnotationGatewayKind: meshGatewayAnnotationKind, + constants.AnnotationMeshInject: "false", + constants.AnnotationTransparentProxyOverwriteProbes: "false", + constants.AnnotationGatewayWANSource: "Service", + constants.AnnotationGatewayWANPort: "443", + constants.AnnotationGatewayWANAddress: "", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "consul-mesh-inject-data", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: "consul-mesh-init", + Command: []string{ + "/bin/sh", + "-ec", + "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-json=false", + }, + Env: []corev1.EnvVar{ + { + Name: "POD_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "NODE_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "CONSUL_ADDRESSES", + Value: "", + }, + { + Name: "CONSUL_GRPC_PORT", + Value: "0", + }, + { + Name: "CONSUL_HTTP_PORT", + Value: "0", + }, + { + Name: "CONSUL_API_TIMEOUT", + Value: "0s", + }, + { + Name: "CONSUL_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "CONSUL_NAMESPACE", + Value: "", + }, + { + Name: "CONSUL_USE_TLS", + Value: "true", + }, + { + Name: "CONSUL_CACERT_PEM", + Value: testCert, + }, + { + Name: "CONSUL_TLS_SERVER_NAME", + Value: "", + }, + }, + Resources: corev1.ResourceRequirements{}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "consul-mesh-inject-data", + ReadOnly: false, + MountPath: "/consul/mesh-inject", + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Args: []string{ + "-addresses", + "", + "-grpc-port=0", + "-log-level=", + "-log-json=false", + "-envoy-concurrency=1", + "-ca-certs=/consul/mesh-inject/consul-ca.pem", + "-envoy-ready-bind-port=21000", + "-envoy-admin-bind-port=19000", + }, + Ports: []corev1.ContainerPort{ + { + Name: "proxy-health", + ContainerPort: 21000, + }, + { + Name: "wan", + ContainerPort: 8443, + HostPort: 8080, + }, + }, + Env: []corev1.EnvVar{ + { + Name: "DP_PROXY_ID", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "TMPDIR", + Value: "/consul/mesh-inject", + }, + { + Name: "NODE_NAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "DP_CREDENTIAL_LOGIN_META", + Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", + }, + { + Name: "DP_CREDENTIAL_LOGIN_META1", + Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", + }, + { + Name: "DP_SERVICE_NODE_NAME", + Value: "$(NODE_NAME)-virtual", + }, + { + Name: "DP_ENVOY_READY_BIND_ADDRESS", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "status.podIP", + }, + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "consul-mesh-inject-data", + MountPath: "/consul/mesh-inject", + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/ready", + Port: intstr.IntOrString{ + Type: 0, + IntVal: 21000, + StrVal: "", + }, + }, + }, + InitialDelaySeconds: 1, + }, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "NET_BIND_SERVICE", + }, + Drop: []corev1.Capability{ + "ALL", + }, + }, + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), + ProcMount: nil, + SeccompProfile: nil, + }, + Stdin: false, + StdinOnce: false, + TTY: false, + }, + }, + NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, + PriorityClassName: "priorityclassname", + TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ + { + MaxSkew: 1, + TopologyKey: "key", + WhenUnsatisfiable: "DoNotSchedule", + }, + }, + Affinity: &corev1.Affinity{ + NodeAffinity: nil, + PodAffinity: nil, + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 1, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + labelManagedBy: "consul-k8s", + "app": "consul", + "chart": "consul-helm", + "heritage": "Helm", + "release": "consul", + }, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + }, + }, + }, + }, + Strategy: appsv1.DeploymentStrategy{}, + MinReadySeconds: 0, + RevisionHistoryLimit: nil, + Paused: false, + ProgressDeadlineSeconds: nil, + }, + Status: appsv1.DeploymentStatus{}, + }, + wantErr: false, + }, { name: "nil gatewayclassconfig - (notfound)", fields: fields{ diff --git a/control-plane/subcommand/flags/consul.go b/control-plane/subcommand/flags/consul.go index 9368b95b3d..e155013258 100644 --- a/control-plane/subcommand/flags/consul.go +++ b/control-plane/subcommand/flags/consul.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" @@ -26,11 +27,6 @@ const ( PartitionEnvVar = "CONSUL_PARTITION" DatacenterEnvVar = "CONSUL_DATACENTER" - UseTLSEnvVar = "CONSUL_USE_TLS" - CACertFileEnvVar = "CONSUL_CACERT_FILE" - CACertPEMEnvVar = "CONSUL_CACERT_PEM" - TLSServerNameEnvVar = "CONSUL_TLS_SERVER_NAME" - ACLTokenEnvVar = "CONSUL_ACL_TOKEN" ACLTokenFileEnvVar = "CONSUL_ACL_TOKEN_FILE" @@ -93,7 +89,7 @@ func (f *ConsulFlags) Flags() *flag.FlagSet { // behave as if that env variable is not provided. grpcPort, _ := strconv.Atoi(os.Getenv(GRPCPortEnvVar)) httpPort, _ := strconv.Atoi(os.Getenv(HTTPPortEnvVar)) - useTLS, _ := strconv.ParseBool(os.Getenv(UseTLSEnvVar)) + useTLS, _ := strconv.ParseBool(os.Getenv(constants.UseTLSEnvVar)) skipServerWatch, _ := strconv.ParseBool(os.Getenv(SkipServerWatchEnvVar)) consulLoginMetaFromEnv := os.Getenv(LoginMetaEnvVar) if consulLoginMetaFromEnv != "" { @@ -142,11 +138,11 @@ func (f *ConsulFlags) Flags() *flag.FlagSet { "[Enterprise only] Consul admin partition. Default to \"default\" if Admin Partitions are enabled.") fs.StringVar(&f.Datacenter, "datacenter", os.Getenv(DatacenterEnvVar), "Consul datacenter.") - fs.StringVar(&f.CACertFile, "ca-cert-file", os.Getenv(CACertFileEnvVar), + fs.StringVar(&f.CACertFile, "ca-cert-file", os.Getenv(constants.CACertFileEnvVar), "Path to a CA certificate to use for TLS when communicating with Consul.") - fs.StringVar(&f.CACertPEM, "ca-cert-pem", os.Getenv(CACertPEMEnvVar), + fs.StringVar(&f.CACertPEM, "ca-cert-pem", os.Getenv(constants.CACertPEMEnvVar), "CA certificate PEM to use for TLS when communicating with Consul.") - fs.StringVar(&f.TLSServerName, "tls-server-name", os.Getenv(TLSServerNameEnvVar), + fs.StringVar(&f.TLSServerName, "tls-server-name", os.Getenv(constants.TLSServerNameEnvVar), "The server name to use as the SNI host when connecting via TLS. "+ "This can also be specified via the CONSUL_TLS_SERVER_NAME environment variable.") fs.BoolVar(&f.UseTLS, "use-tls", useTLS, "If true, use TLS for connections to Consul.") diff --git a/control-plane/subcommand/flags/consul_test.go b/control-plane/subcommand/flags/consul_test.go index 7f35dc8575..e51860024c 100644 --- a/control-plane/subcommand/flags/consul_test.go +++ b/control-plane/subcommand/flags/consul_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" @@ -29,10 +30,10 @@ func TestConsulFlags_Flags(t *testing.T) { DatacenterEnvVar: "test-dc", APITimeoutEnvVar: "10s", - UseTLSEnvVar: "true", - CACertFileEnvVar: "path/to/ca.pem", - CACertPEMEnvVar: "test-ca-pem", - TLSServerNameEnvVar: "server.consul", + constants.UseTLSEnvVar: "true", + constants.CACertFileEnvVar: "path/to/ca.pem", + constants.CACertPEMEnvVar: "test-ca-pem", + constants.TLSServerNameEnvVar: "server.consul", ACLTokenEnvVar: "test-token", ACLTokenFileEnvVar: "/path/to/token", @@ -89,7 +90,7 @@ func TestConsulFlags_Flags(t *testing.T) { HTTPPortEnvVar: "not-int-http-port", APITimeoutEnvVar: "10sec", - UseTLSEnvVar: "not-a-bool", + constants.UseTLSEnvVar: "not-a-bool", LoginMetaEnvVar: "key1:value1;key2:value2", }, From 642a1ea73042e8a29fb6d553c506de2ccfbdeeed Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 5 Jan 2024 10:38:22 -0800 Subject: [PATCH 552/592] disable TestConnectInject_LocalRateLimiting for cloud provider tests (#3439) --- acceptance/tests/connect/local_rate_limit_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/acceptance/tests/connect/local_rate_limit_test.go b/acceptance/tests/connect/local_rate_limit_test.go index 928028e049..eb96be4332 100644 --- a/acceptance/tests/connect/local_rate_limit_test.go +++ b/acceptance/tests/connect/local_rate_limit_test.go @@ -25,6 +25,8 @@ func TestConnectInject_LocalRateLimiting(t *testing.T) { if !cfg.EnableEnterprise { t.Skipf("rate limiting is an enterprise only feature. -enable-enterprise must be set to run this test.") + } else if !cfg.UseKind { + t.Skipf("rate limiting tests are time sensitive and can be flaky on cloud providers. Only test on Kind.") } ctx := suite.Environment().DefaultContext(t) From ae184ae08ef38b5a561cca926927a880cbc11dae Mon Sep 17 00:00:00 2001 From: John Maguire Date: Fri, 5 Jan 2024 16:31:14 -0500 Subject: [PATCH 553/592] [NET-6702] HTTPRoute not being deleted from consul bug (#3440) * Fix bug where http route was not deleted from consul when namespaces are enabled * Added changelog --- .changelog/3440.txt | 3 +++ control-plane/api-gateway/cache/consul.go | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changelog/3440.txt diff --git a/.changelog/3440.txt b/.changelog/3440.txt new file mode 100644 index 0000000000..f5a3a4b3ec --- /dev/null +++ b/.changelog/3440.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: fix issue where deleting an http-route in a non-default namespace would not remove the route from Consul. +``` diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index 881167895f..f2d6ec9bf9 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -434,9 +434,12 @@ func (c *Cache) Delete(ctx context.Context, ref api.ResourceReference) error { return err } - options := &api.WriteOptions{} + options := &api.WriteOptions{Namespace: ref.Namespace, Partition: ref.Partition} _, err = client.ConfigEntries().Delete(ref.Kind, ref.Name, options.WithContext(ctx)) + if err != nil { + c.logger.Info("delete error", "err", err) + } return err } From fad501a18b26b4d8f9c0d652971f5a5430a35746 Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Fri, 5 Jan 2024 17:06:28 -0500 Subject: [PATCH 554/592] [NET-7009] security: update x/crypto to 0.17.0 (#3442) security: update x/crypto to 0.17.0 --- .changelog/3442.txt | 3 +++ acceptance/go.mod | 8 ++++---- acceptance/go.sum | 16 ++++++++-------- cli/go.mod | 8 ++++---- cli/go.sum | 16 ++++++++-------- control-plane/cni/go.mod | 6 +++--- control-plane/cni/go.sum | 12 ++++++------ control-plane/go.mod | 10 +++++----- control-plane/go.sum | 16 ++++++++-------- 9 files changed, 49 insertions(+), 46 deletions(-) create mode 100644 .changelog/3442.txt diff --git a/.changelog/3442.txt b/.changelog/3442.txt new file mode 100644 index 0000000000..812573bf97 --- /dev/null +++ b/.changelog/3442.txt @@ -0,0 +1,3 @@ +```release-note:security +Update `golang.org/x/crypto` to v0.17.0 to address [CVE-2023-48795](https://nvd.nist.gov/vuln/detail/CVE-2023-48795). +``` diff --git a/acceptance/go.mod b/acceptance/go.mod index ea94b99b6f..5604fc2ffa 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -128,14 +128,14 @@ require ( go.opentelemetry.io/otel v1.11.1 // indirect go.opentelemetry.io/otel/trace v1.11.1 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 0c3c2d4c78..f60d72788b 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -796,8 +796,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -979,13 +979,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -996,8 +996,8 @@ 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/cli/go.mod b/cli/go.mod index 664757f8cf..c60bfc2ed6 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -17,7 +17,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.8.3 - golang.org/x/text v0.13.0 + golang.org/x/text v0.14.0 helm.sh/helm/v3 v3.9.4 k8s.io/api v0.25.0 k8s.io/apiextensions-apiserver v0.25.0 @@ -167,13 +167,13 @@ require ( go.opentelemetry.io/otel v1.11.1 // indirect go.opentelemetry.io/otel/trace v1.11.1 // indirect go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect diff --git a/cli/go.sum b/cli/go.sum index dc07c43292..8044b5808a 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -884,8 +884,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1086,13 +1086,13 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1102,8 +1102,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index ade70dac2f..59ed760705 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -32,9 +32,9 @@ require ( github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index 64e48dbda2..c364565c25 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -335,21 +335,21 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/control-plane/go.mod b/control-plane/go.mod index 81effa4e17..e31f24d4bf 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,6 +10,7 @@ require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 github.com/deckarep/golang-set v1.7.1 + github.com/evanphx/json-patch v5.6.0+incompatible github.com/fsnotify/fsnotify v1.6.0 github.com/go-logr/logr v1.2.4 github.com/google/go-cmp v0.5.9 @@ -37,7 +38,7 @@ require ( github.com/stretchr/testify v1.8.3 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/text v0.13.0 + golang.org/x/text v0.14.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 google.golang.org/grpc v1.56.3 @@ -80,7 +81,6 @@ require ( github.com/digitalocean/godo v1.7.5 // indirect github.com/dimchansky/utfbom v1.1.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.15.0 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect @@ -154,13 +154,13 @@ require ( go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index da33abce0d..dc9c907849 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -572,8 +572,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -743,13 +743,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -758,8 +758,8 @@ 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.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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From dc2d6390bca632292f0be22e1fc799c51b58b358 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Mon, 8 Jan 2024 12:28:13 -0500 Subject: [PATCH 555/592] Pass gateway init resources from config to the container (#3430) * Make initConfig an associated function to the builder * Pass in init container resources if they are set * Use values from the builder now that initContainer is associated with it * Update control-plane/gateways/deployment_init_container.go Co-authored-by: Nathan Coleman * Use config on builder --------- Co-authored-by: Nathan Coleman --- control-plane/gateways/deployment.go | 14 +++-- .../gateways/deployment_init_container.go | 59 +++++++++++-------- control-plane/gateways/deployment_test.go | 24 +++++++- 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index d2221bf33a..eb0d7e89f5 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -32,16 +32,13 @@ func (b *meshGatewayBuilder) Deployment() (*appsv1.Deployment, error) { } func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { - initContainer, err := initContainer(b.config, b.gateway.Name, b.gateway.Namespace) - if err != nil { - return nil, err - } - var ( - containerConfig meshv2beta1.GatewayClassContainerConfig deploymentConfig meshv2beta1.GatewayClassDeploymentConfig + containerConfig meshv2beta1.GatewayClassContainerConfig ) + // If GatewayClassConfig is not nil, use it to override the defaults for + // the deployment and container configs. if b.gcc != nil { deploymentConfig = b.gcc.Spec.Deployment if deploymentConfig.Container != nil { @@ -49,6 +46,11 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { } } + initContainer, err := b.initContainer() + if err != nil { + return nil, err + } + container, err := consulDataplaneContainer(b.config, containerConfig, b.gateway.Name, b.gateway.Namespace) if err != nil { return nil, err diff --git a/control-plane/gateways/deployment_init_container.go b/control-plane/gateways/deployment_init_container.go index 35ab25cf15..c6fdcbeff6 100644 --- a/control-plane/gateways/deployment_init_container.go +++ b/control-plane/gateways/deployment_init_container.go @@ -11,6 +11,7 @@ import ( corev1 "k8s.io/api/core/v1" + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) @@ -18,6 +19,7 @@ import ( const ( injectInitContainerName = "consul-mesh-init" initContainersUserAndGroupID = 5996 + defaultBearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" ) type initContainerCommandData struct { @@ -30,15 +32,15 @@ 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 initContainer(config GatewayConfig, name, namespace string) (corev1.Container, error) { +func (b *meshGatewayBuilder) initContainer() (corev1.Container, error) { data := initContainerCommandData{ - AuthMethod: config.AuthMethod, - LogLevel: config.LogLevel, - LogJSON: config.LogJSON, - ServiceName: name, - ServiceAccountName: name, + AuthMethod: b.config.AuthMethod, + LogLevel: b.config.LogLevel, + LogJSON: b.config.LogJSON, + ServiceName: b.gateway.Name, + ServiceAccountName: b.serviceAccountName(), } // Create expected volume mounts @@ -50,8 +52,8 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain } var bearerTokenFile string - if config.AuthMethod != "" { - bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" + if b.config.AuthMethod != "" { + bearerTokenFile = defaultBearerTokenFile } // Render the command @@ -62,12 +64,12 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain return corev1.Container{}, err } - consulNamespace := namespaces.ConsulNamespace(namespace, config.ConsulTenancyConfig.EnableConsulNamespaces, config.ConsulTenancyConfig.ConsulDestinationNamespace, config.ConsulTenancyConfig.EnableConsulNamespaces, config.ConsulTenancyConfig.NSMirroringPrefix) + consulNamespace := namespaces.ConsulNamespace(b.gateway.Namespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.ConsulDestinationNamespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.NSMirroringPrefix) initContainerName := injectInitContainerName container := corev1.Container{ Name: initContainerName, - Image: config.ImageConsulK8S, + Image: b.config.ImageConsulK8S, Env: []corev1.EnvVar{ { @@ -92,19 +94,19 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain }, { Name: "CONSUL_ADDRESSES", - Value: config.ConsulConfig.Address, + Value: b.config.ConsulConfig.Address, }, { Name: "CONSUL_GRPC_PORT", - Value: strconv.Itoa(config.ConsulConfig.GRPCPort), + Value: strconv.Itoa(b.config.ConsulConfig.GRPCPort), }, { Name: "CONSUL_HTTP_PORT", - Value: strconv.Itoa(config.ConsulConfig.HTTPPort), + Value: strconv.Itoa(b.config.ConsulConfig.HTTPPort), }, { Name: "CONSUL_API_TIMEOUT", - Value: config.ConsulConfig.APITimeout.String(), + Value: b.config.ConsulConfig.APITimeout.String(), }, { Name: "CONSUL_NODE_NAME", @@ -113,13 +115,14 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain }, VolumeMounts: volMounts, Command: []string{"/bin/sh", "-ec", buf.String()}, + Resources: initContainerResourcesOrDefault(b.gcc), } - if config.AuthMethod != "" { + if b.config.AuthMethod != "" { container.Env = append(container.Env, corev1.EnvVar{ Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: config.AuthMethod, + Value: b.config.AuthMethod, }, corev1.EnvVar{ Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", @@ -130,10 +133,10 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", }) - if config.ConsulTenancyConfig.ConsulPartition != "" { + if b.config.ConsulTenancyConfig.ConsulPartition != "" { container.Env = append(container.Env, corev1.EnvVar{ Name: "CONSUL_LOGIN_PARTITION", - Value: config.ConsulTenancyConfig.ConsulPartition, + Value: b.config.ConsulTenancyConfig.ConsulPartition, }) } } @@ -143,7 +146,7 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain Value: consulNamespace, }) - if config.TLSEnabled { + if b.config.TLSEnabled { container.Env = append(container.Env, corev1.EnvVar{ Name: constants.UseTLSEnvVar, @@ -151,25 +154,33 @@ func initContainer(config GatewayConfig, name, namespace string) (corev1.Contain }, corev1.EnvVar{ Name: constants.CACertPEMEnvVar, - Value: config.ConsulCACert, + Value: b.config.ConsulCACert, }, corev1.EnvVar{ Name: constants.TLSServerNameEnvVar, - Value: config.ConsulTLSServerName, + Value: b.config.ConsulTLSServerName, }) } - if config.ConsulTenancyConfig.ConsulPartition != "" { + if b.config.ConsulTenancyConfig.ConsulPartition != "" { container.Env = append(container.Env, corev1.EnvVar{ Name: "CONSUL_PARTITION", - Value: config.ConsulTenancyConfig.ConsulPartition, + Value: b.config.ConsulTenancyConfig.ConsulPartition, }) } return container, nil } +func initContainerResourcesOrDefault(gcc *meshv2beta1.GatewayClassConfig) corev1.ResourceRequirements { + if gcc != nil && gcc.Spec.Deployment.InitContainer != nil && gcc.Spec.Deployment.InitContainer.Resources != nil { + return *gcc.Spec.Deployment.InitContainer.Resources + } + + return corev1.ResourceRequirements{} +} + // initContainerCommandTpl is the template for the command executed by // the init container. // TODO @GatewayManagement parametrize gateway kind. diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 90e4f0c40a..dbd58a2c0f 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" @@ -120,6 +121,18 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { WhenUnsatisfiable: "DoNotSchedule", }, }, + InitContainer: &meshv2beta1.GatewayClassInitContainerConfig{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("100m"), + "memory": resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("200m"), + "memory": resource.MustParse("228Mi"), + }, + }, + }, }, }, }, @@ -241,7 +254,16 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Value: "", }, }, - Resources: corev1.ResourceRequirements{}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("100m"), + "memory": resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("200m"), + "memory": resource.MustParse("228Mi"), + }, + }, VolumeMounts: []corev1.VolumeMount{ { Name: "consul-mesh-inject-data", From 9f10756de3988828d960ee977b32248bdbbc7b33 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Mon, 8 Jan 2024 14:38:21 -0500 Subject: [PATCH 556/592] Add acceptance test cleanup (#3375) Add acceptance test cleanup --- acceptance/framework/consul/helm_cluster.go | 106 ++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index d4542e736b..638e0e7c51 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -216,6 +216,7 @@ func (h *HelmCluster) Destroy(t *testing.T) { // Retry because sometimes certain resources (like PVC) take time to delete // in cloud providers. retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { + // Force delete any pods that have h.releaseName in their name because sometimes // graceful termination takes a long time and since this is an uninstall // we don't care that they're stopped gracefully. @@ -231,6 +232,66 @@ func (h *HelmCluster) Destroy(t *testing.T) { } } + // Delete any deployments that have h.releaseName in their name. + deployments, err := h.kubernetesClient.AppsV1().Deployments(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, deployment := range deployments.Items { + if strings.Contains(deployment.Name, h.releaseName) { + err := h.kubernetesClient.AppsV1().Deployments(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(r, err) + } + } + } + + // Delete any replicasets that have h.releaseName in their name. + replicasets, err := h.kubernetesClient.AppsV1().ReplicaSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, replicaset := range replicasets.Items { + if strings.Contains(replicaset.Name, h.releaseName) { + err := h.kubernetesClient.AppsV1().ReplicaSets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), replicaset.Name, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(r, err) + } + } + } + + // Delete any statefulsets that have h.releaseName in their name. + statefulsets, err := h.kubernetesClient.AppsV1().StatefulSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, statefulset := range statefulsets.Items { + if strings.Contains(statefulset.Name, h.releaseName) { + err := h.kubernetesClient.AppsV1().StatefulSets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), statefulset.Name, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(r, err) + } + } + } + + // Delete any daemonsets that have h.releaseName in their name. + daemonsets, err := h.kubernetesClient.AppsV1().DaemonSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, daemonset := range daemonsets.Items { + if strings.Contains(daemonset.Name, h.releaseName) { + err := h.kubernetesClient.AppsV1().DaemonSets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), daemonset.Name, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(r, err) + } + } + } + + // Delete any services that have h.releaseName in their name. + services, err := h.kubernetesClient.CoreV1().Services(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, service := range services.Items { + if strings.Contains(service.Name, h.releaseName) { + err := h.kubernetesClient.CoreV1().Services(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(r, err) + } + } + } + // Delete PVCs. err = h.kubernetesClient.CoreV1().PersistentVolumeClaims(h.helmOptions.KubectlOptions.Namespace).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) require.NoError(r, err) @@ -295,6 +356,51 @@ func (h *HelmCluster) Destroy(t *testing.T) { } } + // Verify that all deployments have been deleted. + deployments, err = h.kubernetesClient.AppsV1().Deployments(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, deployment := range deployments.Items { + if strings.Contains(deployment.Name, h.releaseName) { + r.Errorf("Found deployment which should have been deleted: %s", deployment.Name) + } + } + + // Verify that all replicasets have been deleted. + replicasets, err = h.kubernetesClient.AppsV1().ReplicaSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, replicaset := range replicasets.Items { + if strings.Contains(replicaset.Name, h.releaseName) { + r.Errorf("Found replicaset which should have been deleted: %s", replicaset.Name) + } + } + + // Verify that all statefulets have been deleted. + statefulsets, err = h.kubernetesClient.AppsV1().StatefulSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, statefulset := range statefulsets.Items { + if strings.Contains(statefulset.Name, h.releaseName) { + r.Errorf("Found statefulset which should have been deleted: %s", statefulset.Name) + } + } + + // Verify that all daemonsets have been deleted. + daemonsets, err = h.kubernetesClient.AppsV1().DaemonSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, daemonset := range daemonsets.Items { + if strings.Contains(daemonset.Name, h.releaseName) { + r.Errorf("Found daemonset which should have been deleted: %s", daemonset.Name) + } + } + + // Verify that all services have been deleted. + services, err = h.kubernetesClient.CoreV1().Services(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) + require.NoError(r, err) + for _, service := range services.Items { + if strings.Contains(service.Name, h.releaseName) { + r.Errorf("Found service which should have been deleted: %s", service.Name) + } + } + // Verify all Consul Pods are deleted. pods, err = h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) require.NoError(r, err) From 2fc22e32e6614fea0784f04d7e372b74e0dcd81a Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Tue, 9 Jan 2024 14:56:40 -0500 Subject: [PATCH 557/592] [NET-6581] perf: Fetch services once rather than per-node on deregister (#3322) perf: Fetch services once rather than per-node on deregister Rather than fetching all nodes in a cluster then listing services per-node, fetch all service instances directly by name. This should generally reduce the cost of deregistration in endpoints controller reconciles (in terms of network calls) from N calls (N=node count) to 3-6 calls regardless of K8s cluster size. This does trade the cost of node list and per-node instance fetching for a bulk fetch of service instances. However, a bulk fetch was the previous behavior prior to the introduction of Consul node mirroring in `consul-k8s`, and in the majority of real-world use cases, should be cheaper than listing all nodes and fetching services individually per node in the vast majority of cases. This change is motivated by customers with larger K8s clusters (node counts) seeing performance issues with reconciles. --- .changelog/3322.txt | 3 + .../endpoints/endpoints_controller.go | 189 +++++++++++------- .../endpoints_controller_ent_test.go | 30 +-- .../endpoints/endpoints_controller_test.go | 33 ++- control-plane/namespaces/namespaces.go | 9 + 5 files changed, 163 insertions(+), 101 deletions(-) create mode 100644 .changelog/3322.txt diff --git a/.changelog/3322.txt b/.changelog/3322.txt new file mode 100644 index 0000000000..7c7d685276 --- /dev/null +++ b/.changelog/3322.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: reduce Consul Catalog API requests required for endpoints reconcile in large clusters +``` diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 6c21868670..4ac62cd291 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -933,67 +933,65 @@ func getHealthCheckStatusReason(healthCheckStatus, podName, podNamespace string) // has addresses, it will only deregister instances not in the map. func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvcNamespace string, endpointsAddressesMap map[string]bool) error { // Get services matching metadata from Consul - nodesWithSvcs, err := r.serviceInstancesForNodes(apiClient, k8sSvcName, k8sSvcNamespace) + serviceInstances, err := r.serviceInstances(apiClient, k8sSvcName, k8sSvcNamespace) if err != nil { r.Log.Error(err, "failed to get service instances", "name", k8sSvcName) return err } var errs error - for _, nodeSvcs := range nodesWithSvcs { - for _, svc := range nodeSvcs.Services { - // We need to get services matching "k8s-service-name" and "k8s-namespace" metadata. - // If we selectively deregister, only deregister if the address is not in the map. Otherwise, deregister - // every service instance. - var serviceDeregistered bool - if endpointsAddressesMap != nil { - if _, ok := endpointsAddressesMap[svc.Address]; !ok { - // If the service address is not in the Endpoints addresses, deregister it. - r.Log.Info("deregistering service from consul", "svc", svc.ID) - _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{ - Node: nodeSvcs.Node.Node, - ServiceID: svc.ID, - Namespace: svc.Namespace, - }, nil) - if err != nil { - // Do not exit right away as there might be other services that need to be deregistered. - r.Log.Error(err, "failed to deregister service instance", "id", svc.ID) - errs = multierror.Append(errs, err) - } else { - serviceDeregistered = true - } - } - } else { - r.Log.Info("deregistering service from consul", "svc", svc.ID) + for _, svc := range serviceInstances { + // We need to get services matching "k8s-service-name" and "k8s-namespace" metadata. + // If we selectively deregister, only deregister if the address is not in the map. Otherwise, deregister + // every service instance. + var serviceDeregistered bool + if endpointsAddressesMap != nil { + if _, ok := endpointsAddressesMap[svc.ServiceAddress]; !ok { + // If the service address is not in the Endpoints addresses, deregister it. + r.Log.Info("deregistering service from consul", "svc", svc.ServiceID) _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{ - Node: nodeSvcs.Node.Node, - ServiceID: svc.ID, + Node: svc.Node, + ServiceID: svc.ServiceID, Namespace: svc.Namespace, }, nil) if err != nil { // Do not exit right away as there might be other services that need to be deregistered. - r.Log.Error(err, "failed to deregister service instance", "id", svc.ID) + r.Log.Error(err, "failed to deregister service instance", "id", svc.ServiceID) errs = multierror.Append(errs, err) } else { serviceDeregistered = true } } + } else { + r.Log.Info("deregistering service from consul", "svc", svc.ServiceID) + _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{ + Node: svc.Node, + ServiceID: svc.ServiceID, + Namespace: svc.Namespace, + }, nil) + if err != nil { + // Do not exit right away as there might be other services that need to be deregistered. + r.Log.Error(err, "failed to deregister service instance", "id", svc.ServiceID) + errs = multierror.Append(errs, err) + } else { + serviceDeregistered = true + } + } - if r.AuthMethod != "" && serviceDeregistered { - r.Log.Info("reconciling ACL tokens for service", "svc", svc.Service) - err := r.deleteACLTokensForServiceInstance(apiClient, svc, k8sSvcNamespace, svc.Meta[constants.MetaKeyPodName], svc.Meta[constants.MetaKeyPodUID]) - if err != nil { - r.Log.Error(err, "failed to reconcile ACL tokens for service", "svc", svc.Service) - errs = multierror.Append(errs, err) - } + if r.AuthMethod != "" && serviceDeregistered { + r.Log.Info("reconciling ACL tokens for service", "svc", svc.ServiceName) + err := r.deleteACLTokensForServiceInstance(apiClient, svc, k8sSvcNamespace, svc.ServiceMeta[constants.MetaKeyPodName], svc.ServiceMeta[constants.MetaKeyPodUID]) + if err != nil { + r.Log.Error(err, "failed to reconcile ACL tokens for service", "svc", svc.ServiceName) + errs = multierror.Append(errs, err) } + } - if serviceDeregistered { - err = r.deregisterNode(apiClient, nodeSvcs.Node.Node) - if err != nil { - r.Log.Error(err, "failed to deregister node", "svc", svc.Service) - errs = multierror.Append(errs, err) - } + if serviceDeregistered { + err = r.deregisterNode(apiClient, svc.Node) + if err != nil { + r.Log.Error(err, "failed to deregister node", "svc", svc.ServiceName) + errs = multierror.Append(errs, err) } } } @@ -1036,7 +1034,7 @@ func (r *Controller) deregisterNode(apiClient *api.Client, nodeName string) erro // deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. // It will only check for ACL tokens that have been created with the auth method this controller // has been configured with and will only delete tokens for the provided podName and podUID. -func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, svc *api.AgentService, k8sNS, podName, podUID string) error { +func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, svc *api.CatalogService, k8sNS, podName, podUID string) error { // Skip if podName is empty. if podName == "" { return nil @@ -1049,7 +1047,7 @@ func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, sv // matches as well. tokens, _, err := apiClient.ACL().TokenListFiltered( api.ACLTokenFilterOptions{ - ServiceName: svc.Service, + ServiceName: svc.ServiceName, }, &api.QueryOptions{ Namespace: svc.Namespace, @@ -1064,7 +1062,7 @@ func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, sv // * have a single service identity whose service name is the same as 'svc.Service' if token.AuthMethod == r.AuthMethod && len(token.ServiceIdentities) == 1 && - token.ServiceIdentities[0].ServiceName == svc.Service { + token.ServiceIdentities[0].ServiceName == svc.ServiceName { tokenMeta, err := getTokenMetaFromDescription(token.Description) if err != nil { return fmt.Errorf("failed to parse token metadata: %s", err) @@ -1162,49 +1160,100 @@ func getTokenMetaFromDescription(description string) (map[string]string, error) return tokenMeta, nil } -func (r *Controller) serviceInstancesForNodes(apiClient *api.Client, k8sServiceName, k8sServiceNamespace string) ([]*api.CatalogNodeServiceList, error) { - var serviceList []*api.CatalogNodeServiceList +func (r *Controller) serviceInstances(apiClient *api.Client, k8sServiceName, k8sServiceNamespace string) ([]*api.CatalogService, error) { + var ( + instances []*api.CatalogService + errs error + ) - // The nodelist may have changed between this point and when the event was raised - // For example, if a pod is evicted because a node has been deleted, there is no guarantee that that node will show up here - // query consul catalog for a list of nodes supporting this service - // quite a lot of results as synthetic nodes are never deregistered. - var nodes []*api.Node - filter := fmt.Sprintf(`Meta[%q] == %q `, "synthetic-node", "true") - nodes, _, err := apiClient.Catalog().Nodes(&api.QueryOptions{Filter: filter, Namespace: namespaces.WildcardNamespace}) + // Get the names of services that have the provided k8sServiceName and k8sServiceNamespace in their metadata. + // This is necessary so that we can then list the service instances for each Consul service name, which may + // not match the K8s service name. + services, err := r.servicesForK8SServiceNameAndNamespace(apiClient, k8sServiceName, k8sServiceNamespace) if err != nil { + r.Log.Error(err, "failed to get catalog services", "name", k8sServiceName) return nil, err } - var errs error - for _, node := range nodes { - var nodeServices *api.CatalogNodeServiceList - nodeServices, err := r.serviceInstancesForK8SServiceNameAndNamespace(apiClient, k8sServiceName, k8sServiceNamespace, node.Node) + // Query consul catalog for a list of service instances matching the given service names. + filter := fmt.Sprintf(`NodeMeta[%q] == %q and ServiceMeta[%q] == %q and ServiceMeta[%q] == %q and ServiceMeta[%q] == %q`, + metaKeySyntheticNode, "true", + metaKeyKubeServiceName, k8sServiceName, + constants.MetaKeyKubeNS, k8sServiceNamespace, + metaKeyManagedBy, constants.ManagedByValue) + for _, service := range services { + var is []*api.CatalogService + // Always query the default NS. This ensures that we include mesh gateways, which are always registered to the default NS. + // It also ensures that during migrations that enable namespaces, we deregister old service instances in the default NS. + // An alternative approach to this dual query would be a service instance list using the wildcard NS, which would also + // include instances in Consul namespaces that are no longer in use (e.g. configuration change); this capability does + // not currently exist in Consul's catalog API (as of 1.17) and would need to first be added. + // + // This request uses the service index of the services table (does not perform a full table scan), then decorates each + // result with a single node fetched by ID index from the nodes table. + is, _, err = apiClient.Catalog().Service(service, "", &api.QueryOptions{Filter: filter}) if err != nil { errs = multierror.Append(errs, err) } else { - serviceList = append(serviceList, nodeServices) + instances = append(instances, is...) + } + // If namespaces are enabled a non-default NS is targeted, also query by target Consul NS. + if r.EnableConsulNamespaces { + nonDefaultNamespace := namespaces.NonDefaultConsulNamespace(r.consulNamespace(k8sServiceNamespace)) + if nonDefaultNamespace != "" { + is, _, err = apiClient.Catalog().Service(service, "", &api.QueryOptions{Filter: filter, Namespace: nonDefaultNamespace}) + if err != nil { + errs = multierror.Append(errs, err) + } else { + instances = append(instances, is...) + } + } } } - return serviceList, errs + return instances, errs } -// serviceInstancesForK8SServiceNameAndNamespace calls Consul's ServicesWithFilter to get the list -// of services instances that have the provided k8sServiceName and k8sServiceNamespace in their metadata. -func (r *Controller) serviceInstancesForK8SServiceNameAndNamespace(apiClient *api.Client, k8sServiceName, k8sServiceNamespace, nodeName string) (*api.CatalogNodeServiceList, error) { +// servicesForK8SServiceNameAndNamespace calls Consul's Services to get the list +// of services that have the provided k8sServiceName and k8sServiceNamespace in their metadata. +func (r *Controller) servicesForK8SServiceNameAndNamespace(apiClient *api.Client, k8sServiceName, k8sServiceNamespace string) ([]string, error) { var ( - serviceList *api.CatalogNodeServiceList - err error + services map[string][]string + err error ) - filter := fmt.Sprintf(`Meta[%q] == %q and Meta[%q] == %q and Meta[%q] == %q`, + filter := fmt.Sprintf(`ServiceMeta[%q] == %q and ServiceMeta[%q] == %q and ServiceMeta[%q] == %q`, metaKeyKubeServiceName, k8sServiceName, constants.MetaKeyKubeNS, k8sServiceNamespace, metaKeyManagedBy, constants.ManagedByValue) + // Always query the default NS. This ensures that we cover CE->Ent upgrades where services were previously + // in the default NS, as well as mesh gateways, which are always registered to the default NS. + // + // This request performs a NS-bound scan of the services table. If needed in the future, its performance + // could be improved by adding an index on ServiceMeta to Consul's state store. + services, _, err = apiClient.Catalog().Services(&api.QueryOptions{Filter: filter}) + if err != nil { + return nil, err + } + // If namespaces are enabled a non-default NS is targeted, also query by target Consul NS. if r.EnableConsulNamespaces { - serviceList, _, err = apiClient.Catalog().NodeServiceList(nodeName, &api.QueryOptions{Filter: filter, Namespace: namespaces.WildcardNamespace}) - } else { - serviceList, _, err = apiClient.Catalog().NodeServiceList(nodeName, &api.QueryOptions{Filter: filter}) + nonDefaultNamespace := namespaces.NonDefaultConsulNamespace(r.consulNamespace(k8sServiceNamespace)) + if nonDefaultNamespace != "" { + ss, _, err := apiClient.Catalog().Services(&api.QueryOptions{Filter: filter, Namespace: nonDefaultNamespace}) + if err != nil { + return nil, err + } + // Add to existing map to deduplicate. + for s := range ss { + services[s] = nil // We don't use the tags, so just set to nil + } + } + } + + // Return just the service name keys (we don't need the tags) + // https://developer.hashicorp.com/consul/api-docs/catalog#list-services + var serviceNames []string + for s := range services { + serviceNames = append(serviceNames, s) } - return serviceList, err + return serviceNames, err } // processPreparedQueryUpstream processes an upstream in the format: diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 1c55bcd7f3..c389395973 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1954,7 +1954,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "mesh-gateway", Kind: api.ServiceKindMeshGateway, - Service: "mesh-gateway", + Service: consulSvcName, Port: 80, Address: "1.2.3.4", Meta: map[string]string{ @@ -1985,7 +1985,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "mesh-gateway", Kind: api.ServiceKindMeshGateway, - Service: "mesh-gateway", + Service: consulSvcName, Port: 80, Address: "1.2.3.4", Meta: map[string]string{ @@ -2016,7 +2016,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "terminating-gateway", Kind: api.ServiceKindTerminatingGateway, - Service: "terminating-gateway", + Service: consulSvcName, Port: 8443, Address: "1.2.3.4", Meta: map[string]string{ @@ -2037,7 +2037,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "terminating-gateway", Kind: api.ServiceKindTerminatingGateway, - Service: "terminating-gateway", + Service: consulSvcName, Port: 8443, Address: "1.2.3.4", Meta: map[string]string{ @@ -2089,7 +2089,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "ingress-gateway", Kind: api.ServiceKindIngressGateway, - Service: "ingress-gateway", + Service: consulSvcName, Port: 80, Address: "1.2.3.4", Meta: map[string]string{ @@ -2175,15 +2175,16 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ - Client: fakeClient, - Log: logrtest.NewTestLogger(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - ReleaseName: "consul", - ReleaseNamespace: "default", - EnableConsulNamespaces: true, + Client: fakeClient, + Log: logrtest.NewTestLogger(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + ReleaseName: "consul", + ReleaseNamespace: "default", + EnableConsulNamespaces: true, + ConsulDestinationNamespace: ts.ConsulNS, } if tt.enableACLs { ep.AuthMethod = test.AuthMethod @@ -2216,6 +2217,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { } token, _, err = consulClient.ACL().TokenRead(token.AccessorID, queryOpts) + require.Error(t, err) require.Contains(t, err.Error(), "ACL not found", token) } }) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index bd5393b96a..4527ec84b8 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -4575,7 +4575,7 @@ func TestServiceInstancesForK8SServiceNameAndNamespace(t *testing.T) { name string k8sServiceNameMeta string k8sNamespaceMeta string - expected []*api.AgentService + expected []*api.CatalogService }{ { "no k8s service name or namespace meta", @@ -4599,22 +4599,21 @@ func TestServiceInstancesForK8SServiceNameAndNamespace(t *testing.T) { "both k8s service name and namespace set", k8sSvc, k8sNS, - []*api.AgentService{ + []*api.CatalogService{ { - ID: "foo1", - Service: "foo", - Meta: map[string]string{"k8s-service-name": k8sSvc, "k8s-namespace": k8sNS}, + ID: "foo1", + ServiceName: "foo", + ServiceMeta: map[string]string{"k8s-service-name": k8sSvc, "k8s-namespace": k8sNS}, }, { - Kind: api.ServiceKindConnectProxy, - ID: "foo1-proxy", - Service: "foo-sidecar-proxy", - Port: 20000, - Proxy: &api.AgentServiceConnectProxyConfig{ + ID: "foo1-proxy", + ServiceName: "foo-sidecar-proxy", + ServicePort: 20000, + ServiceProxy: &api.AgentServiceConnectProxyConfig{ DestinationServiceName: "foo", DestinationServiceID: "foo1", }, - Meta: map[string]string{"k8s-service-name": k8sSvc, "k8s-namespace": k8sNS}, + ServiceMeta: map[string]string{"k8s-service-name": k8sSvc, "k8s-namespace": k8sNS}, }, }, }, @@ -4683,14 +4682,14 @@ func TestServiceInstancesForK8SServiceNameAndNamespace(t *testing.T) { } ep := Controller{} - svcs, err := ep.serviceInstancesForK8SServiceNameAndNamespace(consulClient, k8sSvc, k8sNS, consulNodeName) + svcs, err := ep.serviceInstances(consulClient, k8sSvc, k8sNS) require.NoError(t, err) - if len(svcs.Services) > 0 { + if len(svcs) > 0 { require.Len(t, svcs, 2) - require.NotNil(t, c.expected[0], svcs.Services[0]) - require.Equal(t, c.expected[0].Service, svcs.Services[0].Service) - require.NotNil(t, c.expected[1], svcs.Services[1]) - require.Equal(t, c.expected[1].Service, svcs.Services[1].Service) + require.NotNil(t, svcs[0], c.expected[0]) + require.Equal(t, c.expected[0].ServiceName, svcs[0].ServiceName) + require.NotNil(t, svcs[1], c.expected[1]) + require.Equal(t, c.expected[1].ServiceName, svcs[1].ServiceName) } }) } diff --git a/control-plane/namespaces/namespaces.go b/control-plane/namespaces/namespaces.go index 217765c59b..8378c3cc89 100644 --- a/control-plane/namespaces/namespaces.go +++ b/control-plane/namespaces/namespaces.go @@ -93,3 +93,12 @@ func ConsulNamespace(kubeNS string, enableConsulNamespaces bool, consulDestNS st return consulDestNS } + +// NonDefaultConsulNamespace returns the given Consul namespace if it is not default or empty. +// Otherwise, it returns the empty string. +func NonDefaultConsulNamespace(consulNS string) string { + if consulNS == "" || consulNS == DefaultNamespace { + return "" + } + return consulNS +} From fd6a653adb114d4e8bf2eebead4e89aa8c769aec Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Wed, 10 Jan 2024 15:33:45 -0500 Subject: [PATCH 558/592] [NET-7106] Add ExportedServices.multicluster.hashicorp.com CRD (#3458) * Add ExportedServices.multicluster.hashicorp.com CRD * Add controller for ExportedServices CRD * Generate CRD manifests, RBAC policies * Fix casing of multicluster group name * Modify hack script to append -v1 suffix for conflicting CRD names * Add ExportedService to connect-inject-clusterrole.yaml * Update go mod replace to reflect dependency merge * Add bats test for ClusterRole addition --- .../templates/connect-inject-clusterrole.yaml | 24 ++ .../templates/crd-exportedservices-v1.yaml | 139 ++++++++ .../templates/crd-exportedservices.yaml | 51 +-- .../templates/crd-gatewayclassconfigs-v1.yaml | 328 +++++++++--------- .../test/unit/connect-inject-clusterrole.bats | 11 + .../v2beta1/exported_services_types.go | 152 ++++++++ .../v2beta1/multicluster_groupversion_info.go | 27 ++ .../api/multicluster/v2beta1/shared_types.go | 14 + .../api/multicluster/v2beta1/status.go | 93 +++++ .../v2beta1/zz_generated.deepcopy.go | 136 ++++++++ .../exported_services_controller.go | 45 +++ ...consul.hashicorp.com_exportedservices.yaml | 103 ++++++ control-plane/config/rbac/role.yaml | 28 +- control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- .../subcommand/inject-connect/command.go | 2 + .../inject-connect/v2controllers.go | 12 +- hack/copy-crds-to-chart/main.go | 17 +- 18 files changed, 973 insertions(+), 215 deletions(-) create mode 100644 charts/consul/templates/crd-exportedservices-v1.yaml create mode 100644 control-plane/api/multicluster/v2beta1/exported_services_types.go create mode 100644 control-plane/api/multicluster/v2beta1/multicluster_groupversion_info.go create mode 100644 control-plane/api/multicluster/v2beta1/shared_types.go create mode 100644 control-plane/api/multicluster/v2beta1/status.go create mode 100644 control-plane/api/multicluster/v2beta1/zz_generated.deepcopy.go create mode 100644 control-plane/config-entries/controllersv2/exported_services_controller.go create mode 100644 control-plane/config/crd/bases/multicluster.consul.hashicorp.com_exportedservices.yaml diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 34c87f88c6..c6845870ba 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -127,6 +127,30 @@ rules: - get - patch - update +- apiGroups: + - multicluster.consul.hashicorp.com + resources: + - exportedservices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - multicluster.consul.hashicorp.com + resources: + - exportedservices/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch {{- end }} - apiGroups: [ "" ] resources: [ "secrets", "serviceaccounts", "endpoints", "services", "namespaces", "nodes" ] diff --git a/charts/consul/templates/crd-exportedservices-v1.yaml b/charts/consul/templates/crd-exportedservices-v1.yaml new file mode 100644 index 0000000000..081a2b0cf0 --- /dev/null +++ b/charts/consul/templates/crd-exportedservices-v1.yaml @@ -0,0 +1,139 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + name: exportedservices.consul.hashicorp.com +spec: + group: consul.hashicorp.com + names: + kind: ExportedServices + listKind: ExportedServicesList + plural: exportedservices + shortNames: + - exported-services + singular: exportedservices + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: ExportedServices is the Schema for the exportedservices API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ExportedServicesSpec defines the desired state of ExportedServices. + properties: + services: + description: Services is a list of services to be exported and the + list of partitions to expose them to. + items: + description: ExportedService manages the exporting of a service + in the local partition to other partitions. + properties: + consumers: + description: Consumers is a list of downstream consumers of + the service to be exported. + items: + description: ServiceConsumer represents a downstream consumer + of the service to be exported. + properties: + partition: + description: Partition is the admin partition to export + the service to. + type: string + peer: + description: Peer is the name of the peer to export the + service to. + type: string + samenessGroup: + description: SamenessGroup is the name of the sameness + group to export the service to. + type: string + type: object + type: array + name: + description: Name is the name of the service to be exported. + type: string + namespace: + description: Namespace is the namespace to export the service + from. + type: string + type: object + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 081a2b0cf0..8803a03152 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -10,15 +10,13 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: exportedservices.consul.hashicorp.com + name: exportedservices.multicluster.consul.hashicorp.com spec: - group: consul.hashicorp.com + group: multicluster.consul.hashicorp.com names: kind: ExportedServices listKind: ExportedServicesList plural: exportedservices - shortNames: - - exported-services singular: exportedservices scope: Namespaced versions: @@ -35,10 +33,10 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v2beta1 schema: openAPIV3Schema: - description: ExportedServices is the Schema for the exportedservices API + description: ExportedServices is the Schema for the Exported Services API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -53,44 +51,15 @@ spec: metadata: type: object spec: - description: ExportedServicesSpec defines the desired state of ExportedServices. properties: - services: - description: Services is a list of services to be exported and the - list of partitions to expose them to. + consumers: items: - description: ExportedService manages the exporting of a service - in the local partition to other partitions. - properties: - consumers: - description: Consumers is a list of downstream consumers of - the service to be exported. - items: - description: ServiceConsumer represents a downstream consumer - of the service to be exported. - properties: - partition: - description: Partition is the admin partition to export - the service to. - type: string - peer: - description: Peer is the name of the peer to export the - service to. - type: string - samenessGroup: - description: SamenessGroup is the name of the sameness - group to export the service to. - type: string - type: object - type: array - name: - description: Name is the name of the service to be exported. - type: string - namespace: - description: Namespace is the namespace to export the service - from. - type: string type: object + x-kubernetes-preserve-unknown-fields: true + type: array + services: + items: + type: string type: array type: object status: diff --git a/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml b/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml index 685dbf3617..130db72a22 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml @@ -20,182 +20,182 @@ spec: singular: gatewayclassconfig scope: Cluster versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: GatewayClassConfig defines the values that may be set on a GatewayClass - for Consul API Gateway. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GatewayClassConfig defines the values that may be set on a GatewayClass + for Consul API Gateway. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClassConfig. - properties: - copyAnnotations: - description: Annotation Information to copy to services or deployments - properties: - service: - description: List of annotations to copy to the gateway service. - items: - type: string - type: array - type: object - deployment: - description: Deployment defines the deployment configuration for the - gateway. - properties: - defaultInstances: - default: 1 - description: Number of gateway instances that should be deployed - by default - format: int32 - maximum: 8 - minimum: 1 - type: integer - maxInstances: - default: 8 - description: Max allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - minInstances: - default: 1 - description: Minimum allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - resources: - description: Resources defines the resource requirements for the - gateway. - properties: - claims: - description: "Claims lists the names of resources, defined + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClassConfig. + properties: + copyAnnotations: + description: Annotation Information to copy to services or deployments + properties: + service: + description: List of annotations to copy to the gateway service. + items: + type: string + type: array + type: object + deployment: + description: Deployment defines the deployment configuration for the + gateway. + properties: + defaultInstances: + default: 1 + description: Number of gateway instances that should be deployed + by default + format: int32 + maximum: 8 + minimum: 1 + type: integer + maxInstances: + default: 8 + description: Max allowed number of gateway instances + format: int32 + maximum: 8 + minimum: 1 + type: integer + minInstances: + default: 1 + description: Minimum allowed number of gateway instances + format: int32 + maximum: 8 + minimum: 1 + type: integer + resources: + description: Resources defines the resource requirements for the + gateway. + properties: + claims: + description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry in - pod.spec.resourceClaims of the Pod where this field - is used. It makes that resource available inside a - container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - type: object - mapPrivilegedContainerPorts: - description: The value to add to privileged ports ( ports < 1024) - for gateway containers - format: int32 - type: integer - nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a selector which must be true for the + type: object + type: object + type: object + mapPrivilegedContainerPorts: + description: The value to add to privileged ports ( ports < 1024) + for gateway containers + format: int32 + type: integer + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - openshiftSCCName: - description: The name of the OpenShift SecurityContextConstraints - resource for this gateway class to use. - type: string - podSecurityPolicy: - description: The name of an existing Kubernetes PodSecurityPolicy - to bind to the managed ServiceAccount if ACLs are managed. - type: string - serviceType: - description: Service Type string describes ingress methods for a service - enum: - - ClusterIP - - NodePort - - LoadBalancer - type: string - tolerations: - description: 'Tolerations allow the scheduler to schedule nodes with + type: object + openshiftSCCName: + description: The name of the OpenShift SecurityContextConstraints + resource for this gateway class to use. + type: string + podSecurityPolicy: + description: The name of an existing Kubernetes PodSecurityPolicy + to bind to the managed ServiceAccount if ACLs are managed. + type: string + serviceType: + description: Service Type string describes ingress methods for a service + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + tolerations: + description: 'Tolerations allow the scheduler to schedule nodes with matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array - type: object - type: object - served: true - storage: true - {{- end }} \ No newline at end of file + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + served: true + storage: true +{{- end }} diff --git a/charts/consul/test/unit/connect-inject-clusterrole.bats b/charts/consul/test/unit/connect-inject-clusterrole.bats index fafe4a4106..cfe64337d9 100644 --- a/charts/consul/test/unit/connect-inject-clusterrole.bats +++ b/charts/consul/test/unit/connect-inject-clusterrole.bats @@ -254,4 +254,15 @@ load _helpers . | tee /dev/stderr | yq '.rules[4].apiGroups | index("mesh.consul.hashicorp.com")' | tee /dev/stderr) [ "${object}" == 0 ] +} + +@test "connectInject/ClusterRole: adds permission to multicluster.consul.hashicorp.com with resource-apis in global.experiments" { + cd `chart_dir` + local object=$(helm template \ + -s templates/connect-inject-clusterrole.yaml \ + --set 'ui.enabled=false' \ + --set 'global.experiments={resource-apis}' \ + . | tee /dev/stderr | + yq '.rules[6].apiGroups | index("multicluster.consul.hashicorp.com")' | tee /dev/stderr) + [ "${object}" == 0 ] } \ No newline at end of file diff --git a/control-plane/api/multicluster/v2beta1/exported_services_types.go b/control-plane/api/multicluster/v2beta1/exported_services_types.go new file mode 100644 index 0000000000..614192b25a --- /dev/null +++ b/control-plane/api/multicluster/v2beta1/exported_services_types.go @@ -0,0 +1,152 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + pbmulticluster "github.com/hashicorp/consul/proto-public/pbmulticluster/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + exportedServicesKubeKind = "exportedservices" +) + +func init() { + MultiClusterSchemeBuilder.Register(&ExportedServices{}, &ExportedServicesList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// ExportedServices is the Schema for the Exported Services API +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:scope="Namespaced" +type ExportedServices struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmulticluster.ExportedServices `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ExportedServicesList contains a list of ExportedServices. +type ExportedServicesList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*ExportedServices `json:"items"` +} + +func (in *ExportedServices) ResourceID(_, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmulticluster.ExportedServicesType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: "", // Namespace is always unset because ExportedServices is partition-scoped + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *ExportedServices) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: multiClusterConfigMeta(), + } +} + +func (in *ExportedServices) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *ExportedServices) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *ExportedServices) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *ExportedServices) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *ExportedServices) KubeKind() string { + return exportedServicesKubeKind +} + +func (in *ExportedServices) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *ExportedServices) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *ExportedServices) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *ExportedServices) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *ExportedServices) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *ExportedServices) Validate(tenancy common.ConsulTenancyConfig) error { + // TODO add validation logic that ensures we only ever write this to the default namespace. + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *ExportedServices) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/multicluster/v2beta1/multicluster_groupversion_info.go b/control-plane/api/multicluster/v2beta1/multicluster_groupversion_info.go new file mode 100644 index 0000000000..9fef3b60f5 --- /dev/null +++ b/control-plane/api/multicluster/v2beta1/multicluster_groupversion_info.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package v2beta1 contains API Schema definitions for the consul.hashicorp.com v2beta1 API group +// +kubebuilder:object:generate=true +// +groupName=multicluster.consul.hashicorp.com +package v2beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + + // MultiClusterGroup is a collection of multi-cluster resources. + MultiClusterGroup = "multicluster.consul.hashicorp.com" + + // MultiClusterGroupVersion is group version used to register these objects. + MultiClusterGroupVersion = schema.GroupVersion{Group: MultiClusterGroup, Version: "v2beta1"} + + // MultiClusterSchemeBuilder is used to add go types to the GroupVersionKind scheme. + MultiClusterSchemeBuilder = &scheme.Builder{GroupVersion: MultiClusterGroupVersion} + + // AddMultiClusterToScheme adds the types in this group-version to the given scheme. + AddMultiClusterToScheme = MultiClusterSchemeBuilder.AddToScheme +) diff --git a/control-plane/api/multicluster/v2beta1/shared_types.go b/control-plane/api/multicluster/v2beta1/shared_types.go new file mode 100644 index 0000000000..01184df943 --- /dev/null +++ b/control-plane/api/multicluster/v2beta1/shared_types.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + "github.com/hashicorp/consul-k8s/control-plane/api/common" +) + +func multiClusterConfigMeta() map[string]string { + return map[string]string{ + common.SourceKey: common.SourceValue, + } +} diff --git a/control-plane/api/multicluster/v2beta1/status.go b/control-plane/api/multicluster/v2beta1/status.go new file mode 100644 index 0000000000..cc75a1cd82 --- /dev/null +++ b/control-plane/api/multicluster/v2beta1/status.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Conditions is the schema for the conditions portion of the payload. +type Conditions []Condition + +// ConditionType is a camel-cased condition type. +type ConditionType string + +const ( + // ConditionSynced specifies that the resource has been synced with Consul. + ConditionSynced ConditionType = "Synced" +) + +// Conditions define a readiness condition for a Consul resource. +// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Condition struct { + // Type of condition. + // +required + Type ConditionType `json:"type" description:"type of status condition"` + + // Status of the condition, one of True, False, Unknown. + // +required + Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` + + // LastTransitionTime is the last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"` + + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` + + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` +} + +// IsTrue is true if the condition is True. +func (c *Condition) IsTrue() bool { + if c == nil { + return false + } + return c.Status == corev1.ConditionTrue +} + +// IsFalse is true if the condition is False. +func (c *Condition) IsFalse() bool { + if c == nil { + return false + } + return c.Status == corev1.ConditionFalse +} + +// IsUnknown is true if the condition is Unknown. +func (c *Condition) IsUnknown() bool { + if c == nil { + return true + } + return c.Status == corev1.ConditionUnknown +} + +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Status struct { + // Conditions indicate the latest available observations of a resource's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // LastSyncedTime is the last time the resource successfully synced with Consul. + // +optional + LastSyncedTime *metav1.Time `json:"lastSyncedTime,omitempty" description:"last time the condition transitioned from one status to another"` +} + +func (s *Status) GetCondition(t ConditionType) *Condition { + for _, cond := range s.Conditions { + if cond.Type == t { + return &cond + } + } + return nil +} diff --git a/control-plane/api/multicluster/v2beta1/zz_generated.deepcopy.go b/control-plane/api/multicluster/v2beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..5d89bf9619 --- /dev/null +++ b/control-plane/api/multicluster/v2beta1/zz_generated.deepcopy.go @@ -0,0 +1,136 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v2beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Conditions) DeepCopyInto(out *Conditions) { + { + in := &in + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. +func (in Conditions) DeepCopy() Conditions { + if in == nil { + return nil + } + out := new(Conditions) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExportedServices) DeepCopyInto(out *ExportedServices) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportedServices. +func (in *ExportedServices) DeepCopy() *ExportedServices { + if in == nil { + return nil + } + out := new(ExportedServices) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExportedServices) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExportedServicesList) DeepCopyInto(out *ExportedServicesList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*ExportedServices, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ExportedServices) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportedServicesList. +func (in *ExportedServicesList) DeepCopy() *ExportedServicesList { + if in == nil { + return nil + } + out := new(ExportedServicesList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExportedServicesList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Status) DeepCopyInto(out *Status) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.LastSyncedTime != nil { + in, out := &in.LastSyncedTime, &out.LastSyncedTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. +func (in *Status) DeepCopy() *Status { + if in == nil { + return nil + } + out := new(Status) + in.DeepCopyInto(out) + return out +} diff --git a/control-plane/config-entries/controllersv2/exported_services_controller.go b/control-plane/config-entries/controllersv2/exported_services_controller.go new file mode 100644 index 0000000000..dfcfa57fb5 --- /dev/null +++ b/control-plane/config-entries/controllersv2/exported_services_controller.go @@ -0,0 +1,45 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package controllersv2 + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + multiclusterv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/multicluster/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/gateways" +) + +// ExportedServicesController reconciles a MeshGateway object. +type ExportedServicesController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController + GatewayConfig gateways.GatewayConfig +} + +// +kubebuilder:rbac:groups=multicluster.consul.hashicorp.com,resources=exportedservices,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=multicluster.consul.hashicorp.com,resources=exportedservices/status,verbs=get;update;patch + +func (r *ExportedServicesController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.Controller.ReconcileEntry(ctx, r, req, &multiclusterv2beta1.ExportedServices{}) +} + +func (r *ExportedServicesController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *ExportedServicesController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *ExportedServicesController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &multiclusterv2beta1.ExportedServices{}, r) +} diff --git a/control-plane/config/crd/bases/multicluster.consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/multicluster.consul.hashicorp.com_exportedservices.yaml new file mode 100644 index 0000000000..82eb9ca92d --- /dev/null +++ b/control-plane/config/crd/bases/multicluster.consul.hashicorp.com_exportedservices.yaml @@ -0,0 +1,103 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: exportedservices.multicluster.consul.hashicorp.com +spec: + group: multicluster.consul.hashicorp.com + names: + kind: ExportedServices + listKind: ExportedServicesList + plural: exportedservices + singular: exportedservices + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: ExportedServices is the Schema for the Exported Services API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + consumers: + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + services: + items: + type: string + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index 652dcb8e1c..c2ad591c4f 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -428,7 +428,7 @@ rules: - apiGroups: - mesh.consul.hashicorp.com resources: - - meshgateway + - meshconfiguration verbs: - create - delete @@ -440,7 +440,7 @@ rules: - apiGroups: - mesh.consul.hashicorp.com resources: - - meshgateway/status + - meshconfiguration/status verbs: - get - patch @@ -448,7 +448,7 @@ rules: - apiGroups: - mesh.consul.hashicorp.com resources: - - meshconfiguration + - meshgateway verbs: - create - delete @@ -460,7 +460,7 @@ rules: - apiGroups: - mesh.consul.hashicorp.com resources: - - meshconfiguration/status + - meshgateway/status verbs: - get - patch @@ -505,3 +505,23 @@ rules: - get - patch - update +- apiGroups: + - multicluster.consul.hashicorp.com + resources: + - exportedservices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - multicluster.consul.hashicorp.com + resources: + - exportedservices/status + verbs: + - get + - patch + - update diff --git a/control-plane/go.mod b/control-plane/go.mod index e31f24d4bf..7ef010563f 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3 +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240109222754-670b140d87b3 replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f diff --git a/control-plane/go.sum b/control-plane/go.sum index dc9c907849..868409a711 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3 h1:FFRKi+IpoXHwXZDgqG+BNAG1duAuokbzm+5b2pcY1us= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240109222754-670b140d87b3 h1:Ncc9rVhMtMscLem2HdBtx4WujSRGk1plJbvJf8tiO6Y= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240109222754-670b140d87b3/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index ab452e5769..361e967f6b 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -32,6 +32,7 @@ import ( authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + multiclusterv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/multicluster/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" @@ -166,6 +167,7 @@ func init() { // V2 resources utilruntime.Must(authv2beta1.AddAuthToScheme(scheme)) utilruntime.Must(meshv2beta1.AddMeshToScheme(scheme)) + utilruntime.Must(multiclusterv2beta1.AddMultiClusterToScheme(scheme)) //+kubebuilder:scaffold:scheme } diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 989fdf43e3..ba7134211e 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -6,12 +6,13 @@ package connectinject import ( "context" - "github.com/hashicorp/consul-k8s/control-plane/gateways" "github.com/hashicorp/consul-server-connection-manager/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + "github.com/hashicorp/consul-k8s/control-plane/gateways" + authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/common" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" @@ -243,6 +244,15 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } + if err := (&controllersv2.ExportedServicesController{ + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.ExportedServices), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.ExportedServices) + return err + } + mgr.GetWebhookServer().CertDir = c.flagCertDir mgr.GetWebhookServer().Register("/mutate", diff --git a/hack/copy-crds-to-chart/main.go b/hack/copy-crds-to-chart/main.go index 435918c53d..7835a6244a 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -18,6 +18,13 @@ var ( "consul.hashicorp.com_peeringacceptors.yaml": {}, "consul.hashicorp.com_peeringdialers.yaml": {}, } + + // includeV1Suffix is used to add a ...-v1.yaml suffix for types that exist in + // v1 and v2 APIs with the same name and would otherwise result in last man wins + includeV1Suffix = map[string]struct{}{ + "consul.hashicorp.com_exportedservices.yaml": {}, + "consul.hashicorp.com_gatewayclassconfigs.yaml": {}, + } ) func main() { @@ -88,14 +95,20 @@ func realMain(helmPath string) error { withLabels := append(splitOnNewlines[0:split], append(labelLines, splitOnNewlines[split:]...)...) contents = strings.Join(withLabels, "\n") + suffix := "" + if _, ok := includeV1Suffix[info.Name()]; ok { + suffix = "-v1" + } + var crdName string if dir == "bases" { // Construct the destination filename. filenameSplit := strings.Split(info.Name(), "_") - crdName = filenameSplit[1] + filenameSplit = strings.Split(filenameSplit[1], ".") + crdName = filenameSplit[0] + suffix + ".yaml" } else if dir == "external" { filenameSplit := strings.Split(info.Name(), ".") - crdName = filenameSplit[0] + "-external.yaml" + crdName = filenameSplit[0] + suffix + "-external.yaml" } destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName)) From dea23b06f9aec586e46ca24427c1cc24d5037d1f Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:07:28 -0600 Subject: [PATCH 559/592] Net 6822 - consul consul k8s add listeners to mesh gateway consume for service (#3434) * add listeners field * add listeners to configmap * update default logic, update tests * clarify comment * import grouping * regen crds with correct kubebuilder tags * revert accidental change * import blocking * fixed an issue with configmap that I found while testing * regen with port min/max --- charts/consul/templates/crd-meshgateways.yaml | 13 ++++++ .../gateway-resources-configmap.yaml | 7 ++- ...esh.consul.hashicorp.com_meshgateways.yaml | 13 ++++++ control-plane/gateways/service.go | 44 ++++++++++++++----- control-plane/gateways/service_test.go | 12 +++++ control-plane/go.mod | 2 +- control-plane/go.sum | 4 +- 7 files changed, 80 insertions(+), 15 deletions(-) diff --git a/charts/consul/templates/crd-meshgateways.yaml b/charts/consul/templates/crd-meshgateways.yaml index 5cd493ea9e..466705d10c 100644 --- a/charts/consul/templates/crd-meshgateways.yaml +++ b/charts/consul/templates/crd-meshgateways.yaml @@ -56,6 +56,19 @@ spec: description: GatewayClassName is the name of the GatewayClass used by the MeshGateway type: string + listeners: + items: + properties: + name: + type: string + port: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + type: object + minItems: 1 + type: array type: object status: properties: diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 71d48a58bb..4c62433903 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -74,9 +74,9 @@ data: {{- with .Values.meshGateway.dnsPolicy }} dnsPolicy: {{ . }} {{- end }} - {{- if .Values.meshGateway.topologySpreadConstraints }} + {{- with .Values.meshGateway.topologySpreadConstraints }} topologySpreadConstraints: - {{ tpl .Values.meshGateway.topologySpreadConstraints . | nindent 16 | trim }} + {{ fromYamlArray . | toJson }} {{- end }} {{- if .Values.meshGateway.affinity }} affinity: @@ -121,5 +121,8 @@ data: {{- end }} spec: gatewayClassName: consul-mesh-gateway + listeners: + - name: "wan" + port: {{ .Values.meshGateway.service.port }} {{- end }} {{- end }} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml index 2191581e0c..a26a0fc158 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml @@ -52,6 +52,19 @@ spec: description: GatewayClassName is the name of the GatewayClass used by the MeshGateway type: string + listeners: + items: + properties: + name: + type: string + port: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + type: object + minItems: 1 + type: array type: object status: properties: diff --git a/control-plane/gateways/service.go b/control-plane/gateways/service.go index ec3d14aad5..007d9f5791 100644 --- a/control-plane/gateways/service.go +++ b/control-plane/gateways/service.go @@ -9,10 +9,9 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) -const port = int32(443) - func (b *meshGatewayBuilder) Service() *corev1.Service { var ( containerConfig *meshv2beta1.GatewayClassContainerConfig @@ -36,15 +35,40 @@ func (b *meshGatewayBuilder) Service() *corev1.Service { Spec: corev1.ServiceSpec{ Selector: b.labelsForDeployment(), Type: serviceType, - Ports: []corev1.ServicePort{ - { - Name: "wan", - Port: port, - TargetPort: intstr.IntOrString{ - IntVal: port + portModifier, - }, + Ports: b.Ports(portModifier), + }, + } +} + +// Ports build a list of ports from the listener objects. In theory there should only ever be a WAN port on +// mesh gateway but building the ports from a list of listeners will allow for easier compatability with other +// gateway patterns in the future. +func (b *meshGatewayBuilder) Ports(portModifier int32) []corev1.ServicePort { + + ports := []corev1.ServicePort{} + + if len(b.gateway.Spec.Listeners) == 0 { + //If empty use the default value. This should always be set, but in case it's not, this check + //will prevent a panic. + return []corev1.ServicePort{ + { + Name: "wan", + Port: constants.DefaultWANPort, + TargetPort: intstr.IntOrString{ + IntVal: constants.DefaultWANPort + portModifier, }, }, - }, + } + } + for _, listener := range b.gateway.Spec.Listeners { + port := int32(listener.Port) + ports = append(ports, corev1.ServicePort{ + Name: listener.Name, + Port: port, + TargetPort: intstr.IntOrString{ + IntVal: port + portModifier, + }, + }) } + return ports } diff --git a/control-plane/gateways/service_test.go b/control-plane/gateways/service_test.go index 3796e5ea56..6841544281 100644 --- a/control-plane/gateways/service_test.go +++ b/control-plane/gateways/service_test.go @@ -34,6 +34,12 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { gateway: &meshv2beta1.MeshGateway{ Spec: pbmesh.MeshGateway{ GatewayClassName: "test-gateway-class", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 443, + }, + }, }, }, config: GatewayConfig{}, @@ -99,6 +105,12 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { gateway: &meshv2beta1.MeshGateway{ Spec: pbmesh.MeshGateway{ GatewayClassName: "test-gateway-class", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 443, + }, + }, }, }, config: GatewayConfig{}, diff --git a/control-plane/go.mod b/control-plane/go.mod index 7ef010563f..91037460c6 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240109222754-670b140d87b3 +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240110191229-7d92a5dfd601 replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f diff --git a/control-plane/go.sum b/control-plane/go.sum index 868409a711..630016914c 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240109222754-670b140d87b3 h1:Ncc9rVhMtMscLem2HdBtx4WujSRGk1plJbvJf8tiO6Y= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240109222754-670b140d87b3/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240110191229-7d92a5dfd601 h1:Y6tR3aGdKsh12oj80PWz2plRsyczJS8irZ++HdRJIB8= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240110191229-7d92a5dfd601/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= From f690db39e94dc65402994321db35b21992dd7467 Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:19:20 -0500 Subject: [PATCH 560/592] [COMPLIANCE] Add Copyright and License Headers (#3388) Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- control-plane/config-entries/controllers/finalizer_patch.go | 3 +++ .../config-entries/controllers/finalizer_patch_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/control-plane/config-entries/controllers/finalizer_patch.go b/control-plane/config-entries/controllers/finalizer_patch.go index c0974edae0..5eec8d5d29 100644 --- a/control-plane/config-entries/controllers/finalizer_patch.go +++ b/control-plane/config-entries/controllers/finalizer_patch.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controllers import ( diff --git a/control-plane/config-entries/controllers/finalizer_patch_test.go b/control-plane/config-entries/controllers/finalizer_patch_test.go index 290d9c6437..5b9e3350d1 100644 --- a/control-plane/config-entries/controllers/finalizer_patch_test.go +++ b/control-plane/config-entries/controllers/finalizer_patch_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package controllers import ( From 4f5a5d3383ef8e9d2c5279119861934f279b136f Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Thu, 11 Jan 2024 12:02:14 -0500 Subject: [PATCH 561/592] Run ctrl-manifests to clean dirty state of main (#3466) --- .../consul/templates/crd-servicerouters.yaml | 4 +- .../consul.hashicorp.com_servicerouters.yaml | 4 +- control-plane/config/webhook/manifests.yaml | 152 +++++++++--------- 3 files changed, 80 insertions(+), 80 deletions(-) diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index c4e06d05bc..882325598d 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -149,9 +149,9 @@ spec: type: object type: object retryOn: - description: RetryOn is a flat list of conditions for Consul + description: 'RetryOn is a flat list of conditions for Consul to retry requests based on the response from an upstream - service. + service. Refer to the valid conditions here: https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon' items: type: string type: array diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index b43f225249..5628070226 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -145,9 +145,9 @@ spec: type: object type: object retryOn: - description: RetryOn is a flat list of conditions for Consul + description: 'RetryOn is a flat list of conditions for Consul to retry requests based on the response from an upstream - service. + service. Refer to the valid conditions here: https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon' items: type: string type: array diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index b5e3bb52fd..a4b3aaadd0 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -14,19 +14,19 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-controlplanerequestlimits + path: /mutate-v2beta1-grpcroute failurePolicy: Fail - name: mutate-controlplanerequestlimits.consul.hashicorp.com + name: mutate-grpcroute.auth.consul.hashicorp.com rules: - apiGroups: - - consul.hashicorp.com + - auth.consul.hashicorp.com apiVersions: - - v1alpha1 + - v2beta1 operations: - CREATE - UPDATE resources: - - controlplanerequestlimits + - grpcroute sideEffects: None - admissionReviewVersions: - v1beta1 @@ -35,19 +35,19 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-exportedservices + path: /mutate-v2beta1-httproute failurePolicy: Fail - name: mutate-exportedservices.consul.hashicorp.com + name: mutate-httproute.auth.consul.hashicorp.com rules: - apiGroups: - - consul.hashicorp.com + - auth.consul.hashicorp.com apiVersions: - - v1alpha1 + - v2beta1 operations: - CREATE - UPDATE resources: - - exportedservices + - httproute sideEffects: None - admissionReviewVersions: - v1beta1 @@ -56,19 +56,19 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-ingressgateway + path: /mutate-v2beta1-proxyconfiguration failurePolicy: Fail - name: mutate-ingressgateway.consul.hashicorp.com + name: mutate-proxyconfiguration.auth.consul.hashicorp.com rules: - apiGroups: - - consul.hashicorp.com + - auth.consul.hashicorp.com apiVersions: - - v1alpha1 + - v2beta1 operations: - CREATE - UPDATE resources: - - ingressgateways + - proxyconfiguration sideEffects: None - admissionReviewVersions: - v1beta1 @@ -77,19 +77,19 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-jwtprovider + path: /mutate-v2beta1-tcproute failurePolicy: Fail - name: mutate-jwtprovider.consul.hashicorp.com + name: mutate-tcproute.auth.consul.hashicorp.com rules: - apiGroups: - - consul.hashicorp.com + - auth.consul.hashicorp.com apiVersions: - - v1alpha1 + - v2beta1 operations: - CREATE - UPDATE resources: - - jwtproviders + - tcproute sideEffects: None - admissionReviewVersions: - v1beta1 @@ -98,9 +98,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-mesh + path: /mutate-v1alpha1-controlplanerequestlimits failurePolicy: Fail - name: mutate-mesh.consul.hashicorp.com + name: mutate-controlplanerequestlimits.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -110,7 +110,7 @@ webhooks: - CREATE - UPDATE resources: - - mesh + - controlplanerequestlimits sideEffects: None - admissionReviewVersions: - v1beta1 @@ -119,9 +119,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-peeringacceptors + path: /mutate-v1alpha1-exportedservices failurePolicy: Fail - name: mutate-peeringacceptors.consul.hashicorp.com + name: mutate-exportedservices.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -131,7 +131,7 @@ webhooks: - CREATE - UPDATE resources: - - peeringacceptors + - exportedservices sideEffects: None - admissionReviewVersions: - v1beta1 @@ -140,9 +140,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-peeringdialers + path: /mutate-v1alpha1-ingressgateway failurePolicy: Fail - name: mutate-peeringdialers.consul.hashicorp.com + name: mutate-ingressgateway.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -152,7 +152,7 @@ webhooks: - CREATE - UPDATE resources: - - peeringdialers + - ingressgateways sideEffects: None - admissionReviewVersions: - v1beta1 @@ -161,9 +161,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-proxydefaults + path: /mutate-v1alpha1-jwtprovider failurePolicy: Fail - name: mutate-proxydefaults.consul.hashicorp.com + name: mutate-jwtprovider.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -173,7 +173,7 @@ webhooks: - CREATE - UPDATE resources: - - proxydefaults + - jwtproviders sideEffects: None - admissionReviewVersions: - v1beta1 @@ -182,9 +182,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-samenessgroups + path: /mutate-v1alpha1-mesh failurePolicy: Fail - name: mutate-samenessgroup.consul.hashicorp.com + name: mutate-mesh.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -194,7 +194,7 @@ webhooks: - CREATE - UPDATE resources: - - samenessgroups + - mesh sideEffects: None - admissionReviewVersions: - v1beta1 @@ -203,9 +203,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-servicedefaults + path: /mutate-v1alpha1-peeringacceptors failurePolicy: Fail - name: mutate-servicedefaults.consul.hashicorp.com + name: mutate-peeringacceptors.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -215,7 +215,7 @@ webhooks: - CREATE - UPDATE resources: - - servicedefaults + - peeringacceptors sideEffects: None - admissionReviewVersions: - v1beta1 @@ -224,9 +224,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-serviceintentions + path: /mutate-v1alpha1-peeringdialers failurePolicy: Fail - name: mutate-serviceintentions.consul.hashicorp.com + name: mutate-peeringdialers.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -236,7 +236,7 @@ webhooks: - CREATE - UPDATE resources: - - serviceintentions + - peeringdialers sideEffects: None - admissionReviewVersions: - v1beta1 @@ -245,9 +245,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-serviceresolver + path: /mutate-v1alpha1-proxydefaults failurePolicy: Fail - name: mutate-serviceresolver.consul.hashicorp.com + name: mutate-proxydefaults.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -257,7 +257,7 @@ webhooks: - CREATE - UPDATE resources: - - serviceresolvers + - proxydefaults sideEffects: None - admissionReviewVersions: - v1beta1 @@ -266,9 +266,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-servicerouter + path: /mutate-v1alpha1-samenessgroups failurePolicy: Fail - name: mutate-servicerouter.consul.hashicorp.com + name: mutate-samenessgroup.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -278,7 +278,7 @@ webhooks: - CREATE - UPDATE resources: - - servicerouters + - samenessgroups sideEffects: None - admissionReviewVersions: - v1beta1 @@ -287,9 +287,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-servicesplitter + path: /mutate-v1alpha1-servicedefaults failurePolicy: Fail - name: mutate-servicesplitter.consul.hashicorp.com + name: mutate-servicedefaults.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -299,7 +299,7 @@ webhooks: - CREATE - UPDATE resources: - - servicesplitters + - servicedefaults sideEffects: None - admissionReviewVersions: - v1beta1 @@ -308,9 +308,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v1alpha1-terminatinggateway + path: /mutate-v1alpha1-serviceintentions failurePolicy: Fail - name: mutate-terminatinggateway.consul.hashicorp.com + name: mutate-serviceintentions.consul.hashicorp.com rules: - apiGroups: - consul.hashicorp.com @@ -320,7 +320,7 @@ webhooks: - CREATE - UPDATE resources: - - terminatinggateways + - serviceintentions sideEffects: None - admissionReviewVersions: - v1beta1 @@ -329,19 +329,19 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v2beta1-trafficpermissions + path: /mutate-v1alpha1-serviceresolver failurePolicy: Fail - name: mutate-trafficpermissions.auth.consul.hashicorp.com + name: mutate-serviceresolver.consul.hashicorp.com rules: - apiGroups: - - auth.consul.hashicorp.com + - consul.hashicorp.com apiVersions: - - v2beta1 + - v1alpha1 operations: - CREATE - UPDATE resources: - - trafficpermissions + - serviceresolvers sideEffects: None - admissionReviewVersions: - v1beta1 @@ -350,19 +350,19 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v2beta1-grpcroute + path: /mutate-v1alpha1-servicerouter failurePolicy: Fail - name: mutate-grpcroute.auth.consul.hashicorp.com + name: mutate-servicerouter.consul.hashicorp.com rules: - apiGroups: - - auth.consul.hashicorp.com + - consul.hashicorp.com apiVersions: - - v2beta1 + - v1alpha1 operations: - CREATE - UPDATE resources: - - grpcroute + - servicerouters sideEffects: None - admissionReviewVersions: - v1beta1 @@ -371,19 +371,19 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v2beta1-httproute + path: /mutate-v1alpha1-servicesplitter failurePolicy: Fail - name: mutate-httproute.auth.consul.hashicorp.com + name: mutate-servicesplitter.consul.hashicorp.com rules: - apiGroups: - - auth.consul.hashicorp.com + - consul.hashicorp.com apiVersions: - - v2beta1 + - v1alpha1 operations: - CREATE - UPDATE resources: - - httproute + - servicesplitters sideEffects: None - admissionReviewVersions: - v1beta1 @@ -392,19 +392,19 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v2beta1-proxyconfiguration + path: /mutate-v1alpha1-terminatinggateway failurePolicy: Fail - name: mutate-proxyconfiguration.auth.consul.hashicorp.com + name: mutate-terminatinggateway.consul.hashicorp.com rules: - apiGroups: - - auth.consul.hashicorp.com + - consul.hashicorp.com apiVersions: - - v2beta1 + - v1alpha1 operations: - CREATE - UPDATE resources: - - proxyconfiguration + - terminatinggateways sideEffects: None - admissionReviewVersions: - v1beta1 @@ -413,9 +413,9 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-v2beta1-tcproute + path: /mutate-v2beta1-trafficpermissions failurePolicy: Fail - name: mutate-tcproute.auth.consul.hashicorp.com + name: mutate-trafficpermissions.auth.consul.hashicorp.com rules: - apiGroups: - auth.consul.hashicorp.com @@ -425,7 +425,7 @@ webhooks: - CREATE - UPDATE resources: - - tcproute + - trafficpermissions sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 From 202ba1f67dc5064f78a835b3a92f6210a275d313 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:21:15 -0600 Subject: [PATCH 562/592] Add proxy startup and liveness probe config. (#3450) This PR adds in two new `consul.hashicorp.com/sidecar-proxy-startup-failure-seconds` and `consul.hashicorp.com/sidecar-proxy-liveness-failure-seconds` annotations that are disabled by default. When set to a value greater than zero, these configurations will enable their corresponding startup / liveness probes for the Envoy proxy. This helps to prevent scenarios where the Envoy proxy would hang and never recover. --- .changelog/3450.txt | 3 + .../templates/connect-inject-deployment.yaml | 3 +- charts/consul/values.yaml | 7 + .../constants/annotations_and_labels.go | 8 + .../webhook/consul_dataplane_sidecar.go | 57 +++++- .../webhook/consul_dataplane_sidecar_test.go | 163 ++++++++++++++---- .../connect-inject/webhook/mesh_webhook.go | 3 + .../subcommand/inject-connect/command.go | 6 + .../inject-connect/v1controllers.go | 80 ++++----- 9 files changed, 252 insertions(+), 78 deletions(-) create mode 100644 .changelog/3450.txt diff --git a/.changelog/3450.txt b/.changelog/3450.txt new file mode 100644 index 0000000000..b51cd304cb --- /dev/null +++ b/.changelog/3450.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: Add new `consul.hashicorp.com/sidecar-proxy-startup-failure-seconds` and `consul.hashicorp.com/sidecar-proxy-liveness-failure-seconds` annotations that allow users to manually configure startup and liveness probes for Envoy sidecar proxies. +``` diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 2fafae7df1..b87c8223b8 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -259,7 +259,8 @@ spec: -default-sidecar-proxy-lifecycle-shutdown-grace-period-seconds={{ .Values.connectInject.sidecarProxy.lifecycle.defaultShutdownGracePeriodSeconds }} \ -default-sidecar-proxy-lifecycle-graceful-port={{ .Values.connectInject.sidecarProxy.lifecycle.defaultGracefulPort }} \ -default-sidecar-proxy-lifecycle-graceful-shutdown-path="{{ .Values.connectInject.sidecarProxy.lifecycle.defaultGracefulShutdownPath }}" \ - + -default-sidecar-proxy-startup-failure-seconds={{ .Values.connectInject.sidecarProxy.defaultStartupFailureSeconds }} \ + -default-sidecar-proxy-liveness-failure-seconds={{ .Values.connectInject.sidecarProxy.defaultLivenessFailureSeconds }} \ {{- if .Values.connectInject.initContainer }} {{- $initResources := .Values.connectInject.initContainer.resources }} {{- if not (kindIs "invalid" $initResources.limits.memory) }} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index ac576c2f42..47b875d3e3 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -2709,6 +2709,13 @@ connectInject: # @type: string defaultGracefulShutdownPath: "/graceful_shutdown" + # Configures how long the k8s startup probe will wait before the proxy is considered to be unhealthy and the container is restarted. + # A value of zero disables the probe. + defaultStartupFailureSeconds: 0 + # Configures how long the k8s liveness probe will wait before the proxy is considered to be unhealthy and the container is restarted. + # A value of zero disables the probe. + defaultLivenessFailureSeconds: 0 + # The resource settings for the Connect injected init container. If null, the resources # won't be set for the initContainer. The defaults are optimized for developer instances of # Kubernetes, however they should be tweaked with the recommended defaults as shown below to speed up service registration times. diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index cb74939359..4cff554e83 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -99,6 +99,14 @@ const ( // Enable this only if the application does not support health checks. AnnotationUseProxyHealthCheck = "consul.hashicorp.com/use-proxy-health-check" + // AnnotationSidecarProxyStartupFailureSeconds configures how long the k8s startup probe will wait for + // success before the proxy is considered to be unhealthy and the container is restarted. + AnnotationSidecarProxyStartupFailureSeconds = "consul.hashicorp.com/sidecar-proxy-startup-failure-seconds" + + // AnnotationSidecarProxyLivenessFailureSeconds configures how long the k8s liveness probe will wait for + // before the proxy is considered to be unhealthy and the container is restarted. + AnnotationSidecarProxyLivenessFailureSeconds = "consul.hashicorp.com/sidecar-proxy-liveness-failure-seconds" + // annotations for sidecar proxy resource limits. AnnotationSidecarProxyCPULimit = "consul.hashicorp.com/sidecar-proxy-cpu-limit" AnnotationSidecarProxyCPURequest = "consul.hashicorp.com/sidecar-proxy-cpu-request" diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index 6ff75c76c4..a567682356 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -51,11 +51,11 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor containerName = fmt.Sprintf("%s-%s", sidecarContainer, mpi.serviceName) } - var probe *corev1.Probe + var readinessProbe *corev1.Probe if useProxyHealthCheck(pod) { // If using the proxy health check for a service, configure an HTTP handler // that queries the '/ready' endpoint of the proxy. - probe = &corev1.Probe{ + readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(constants.ProxyDefaultHealthPort + mpi.serviceIndex), @@ -65,7 +65,7 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor InitialDelaySeconds: 1, } } else { - probe = &corev1.Probe{ + readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ TCPSocket: &corev1.TCPSocketAction{ Port: intstr.FromInt(constants.ProxyDefaultInboundPort + mpi.serviceIndex), @@ -75,6 +75,27 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor } } + // Configure optional probes on the proxy to force restart it in failure scenarios. + var startupProbe, livenessProbe *corev1.Probe + startupSeconds := w.getStartupFailureSeconds(pod) + livenessSeconds := w.getLivenessFailureSeconds(pod) + if startupSeconds > 0 { + startupProbe = &corev1.Probe{ + // Use the same handler as the readiness probe. + ProbeHandler: readinessProbe.ProbeHandler, + PeriodSeconds: 1, + FailureThreshold: startupSeconds, + } + } + if livenessSeconds > 0 { + livenessProbe = &corev1.Probe{ + // Use the same handler as the readiness probe. + ProbeHandler: readinessProbe.ProbeHandler, + PeriodSeconds: 1, + FailureThreshold: livenessSeconds, + } + } + container := corev1.Container{ Name: containerName, Image: w.ImageConsulDataplane, @@ -140,7 +161,9 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor }, }, Args: args, - ReadinessProbe: probe, + ReadinessProbe: readinessProbe, + StartupProbe: startupProbe, + LivenessProbe: livenessProbe, } if w.AuthMethod != "" { @@ -520,6 +543,32 @@ func useProxyHealthCheck(pod corev1.Pod) bool { return false } +// getStartupFailureSeconds returns number of seconds configured by the annotation 'consul.hashicorp.com/sidecar-proxy-startup-failure-seconds' +// and indicates how long we should wait for the sidecar proxy to initialize before considering the pod unhealthy. +func (w *MeshWebhook) getStartupFailureSeconds(pod corev1.Pod) int32 { + seconds := w.DefaultSidecarProxyStartupFailureSeconds + if v, ok := pod.Annotations[constants.AnnotationSidecarProxyStartupFailureSeconds]; ok { + seconds, _ = strconv.Atoi(v) + } + if seconds > 0 { + return int32(seconds) + } + return 0 +} + +// getLivenessFailureSeconds returns number of seconds configured by the annotation 'consul.hashicorp.com/sidecar-proxy-liveness-failure-seconds' +// and indicates how long we should wait for the sidecar proxy to initialize before considering the pod unhealthy. +func (w *MeshWebhook) getLivenessFailureSeconds(pod corev1.Pod) int32 { + seconds := w.DefaultSidecarProxyLivenessFailureSeconds + if v, ok := pod.Annotations[constants.AnnotationSidecarProxyLivenessFailureSeconds]; ok { + seconds, _ = strconv.Atoi(v) + } + if seconds > 0 { + return int32(seconds) + } + return 0 +} + // getMetricsPorts creates container ports for exposing services such as prometheus. // Prometheus in particular needs a named port for use with the operator. // https://github.com/hashicorp/consul-k8s/pull/1440 diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index c105e71b7b..1dd5525e3c 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -419,48 +419,143 @@ func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { } func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck(t *testing.T) { - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - ConsulAddress: "1.1.1.1", - LogLevel: "info", - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationUseProxyHealthCheck: "true", + tests := map[string]struct { + changeHook func(*MeshWebhook) + changePod func(*corev1.Pod) + expectedReadiness *corev1.Probe + expectedStartup *corev1.Probe + expectedLiveness *corev1.Probe + }{ + "readiness-only": { + changeHook: func(h *MeshWebhook) {}, + changePod: func(p *corev1.Pod) {}, + expectedReadiness: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, }, }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", + "default-values": { + changeHook: func(h *MeshWebhook) { + h.DefaultSidecarProxyStartupFailureSeconds = 11 + h.DefaultSidecarProxyLivenessFailureSeconds = 22 + }, + changePod: func(p *corev1.Pod) {}, + expectedReadiness: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + }, + expectedStartup: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + PeriodSeconds: 1, + FailureThreshold: 11, + }, + expectedLiveness: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, }, + PeriodSeconds: 1, + FailureThreshold: 22, }, }, - } - container, err := h.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) - expectedProbe := &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", + "override-default": { + changeHook: func(h *MeshWebhook) { + h.DefaultSidecarProxyStartupFailureSeconds = 11 + h.DefaultSidecarProxyLivenessFailureSeconds = 22 + }, + changePod: func(p *corev1.Pod) { + p.ObjectMeta.Annotations[constants.AnnotationSidecarProxyStartupFailureSeconds] = "111" + p.ObjectMeta.Annotations[constants.AnnotationSidecarProxyLivenessFailureSeconds] = "222" + }, + expectedReadiness: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + InitialDelaySeconds: 1, + }, + expectedStartup: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + PeriodSeconds: 1, + FailureThreshold: 111, + }, + expectedLiveness: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", + }, + }, + PeriodSeconds: 1, + FailureThreshold: 222, }, }, - InitialDelaySeconds: 1, } - require.NoError(t, err) - require.Contains(t, container.Args, "-envoy-ready-bind-port=21000") - require.Equal(t, expectedProbe, container.ReadinessProbe) - require.Contains(t, container.Env, corev1.EnvVar{ - Name: "DP_ENVOY_READY_BIND_ADDRESS", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, - }, - }) - require.Contains(t, container.Ports, corev1.ContainerPort{ - Name: "proxy-health-0", - ContainerPort: 21000, - }) + for tn, tc := range tests { + t.Run(tn, func(t *testing.T) { + hook := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + ConsulAddress: "1.1.1.1", + LogLevel: "info", + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationUseProxyHealthCheck: "true", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + }, + }, + }, + } + tc.changeHook(&hook) + tc.changePod(&pod) + container, err := hook.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) + require.NoError(t, err) + require.Contains(t, container.Args, "-envoy-ready-bind-port=21000") + require.Equal(t, tc.expectedReadiness, container.ReadinessProbe) + require.Equal(t, tc.expectedStartup, container.StartupProbe) + require.Equal(t, tc.expectedLiveness, container.LivenessProbe) + require.Contains(t, container.Env, corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + }) + require.Contains(t, container.Ports, corev1.ContainerPort{ + Name: "proxy-health-0", + ContainerPort: 21000, + }) + }) + } } func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck_Multiport(t *testing.T) { diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 24c26d4f42..7d7233baa6 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -153,6 +153,9 @@ type MeshWebhook struct { DefaultProxyMemoryRequest resource.Quantity DefaultProxyMemoryLimit resource.Quantity + DefaultSidecarProxyStartupFailureSeconds int + DefaultSidecarProxyLivenessFailureSeconds int + // LifecycleConfig contains proxy lifecycle management configuration from the inject-connect command and has methods to determine whether // configuration should come from the default flags or annotations. The meshWebhook uses this to configure container sidecar proxy args. LifecycleConfig lifecycle.Config diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 361e967f6b..f51a97df18 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -89,6 +89,9 @@ type Command struct { flagDefaultSidecarProxyLifecycleGracefulPort string flagDefaultSidecarProxyLifecycleGracefulShutdownPath string + flagDefaultSidecarProxyStartupFailureSeconds int + flagDefaultSidecarProxyLivenessFailureSeconds int + // Metrics settings. flagDefaultEnableMetrics bool flagEnableGatewayMetrics bool @@ -251,6 +254,9 @@ func (c *Command) init() { c.flagSet.StringVar(&c.flagDefaultSidecarProxyLifecycleGracefulPort, "default-sidecar-proxy-lifecycle-graceful-port", strconv.Itoa(constants.DefaultGracefulPort), "Default port for sidecar proxy lifecycle management HTTP endpoints.") c.flagSet.StringVar(&c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, "default-sidecar-proxy-lifecycle-graceful-shutdown-path", "/graceful_shutdown", "Default sidecar proxy lifecycle management graceful shutdown path.") + c.flagSet.IntVar(&c.flagDefaultSidecarProxyStartupFailureSeconds, "default-sidecar-proxy-startup-failure-seconds", 0, "Default number of seconds for the k8s startup probe to fail before the proxy container is restarted. Zero disables the probe.") + c.flagSet.IntVar(&c.flagDefaultSidecarProxyLivenessFailureSeconds, "default-sidecar-proxy-liveness-failure-seconds", 0, "Default number of seconds for the k8s liveness probe to fail before the proxy container is restarted. Zero disables the probe.") + // Metrics setting flags. c.flagSet.BoolVar(&c.flagDefaultEnableMetrics, "default-enable-metrics", false, "Default for enabling connect service metrics.") c.flagSet.BoolVar(&c.flagEnableGatewayMetrics, "enable-gateway-metrics", false, "Allows enabling Consul gateway metrics.") diff --git a/control-plane/subcommand/inject-connect/v1controllers.go b/control-plane/subcommand/inject-connect/v1controllers.go index c407c99ccf..f717b873b0 100644 --- a/control-plane/subcommand/inject-connect/v1controllers.go +++ b/control-plane/subcommand/inject-connect/v1controllers.go @@ -325,45 +325,47 @@ func (c *Command) configureV1Controllers(ctx context.Context, mgr manager.Manage mgr.GetWebhookServer().Register("/mutate", &ctrlRuntimeWebhook.Admission{Handler: &webhook.MeshWebhook{ - Clientset: c.clientset, - ReleaseNamespace: c.flagReleaseNamespace, - ConsulConfig: consulConfig, - ConsulServerConnMgr: watcher, - ImageConsul: c.flagConsulImage, - ImageConsulDataplane: c.flagConsulDataplaneImage, - EnvoyExtraArgs: c.flagEnvoyExtraArgs, - ImageConsulK8S: c.flagConsulK8sImage, - RequireAnnotation: !c.flagDefaultInject, - AuthMethod: c.flagACLAuthMethod, - ConsulCACert: string(c.caCertPem), - TLSEnabled: c.consul.UseTLS, - ConsulAddress: c.consul.Addresses, - SkipServerWatch: c.consul.SkipServerWatch, - ConsulTLSServerName: c.consul.TLSServerName, - DefaultProxyCPURequest: c.sidecarProxyCPURequest, - DefaultProxyCPULimit: c.sidecarProxyCPULimit, - DefaultProxyMemoryRequest: c.sidecarProxyMemoryRequest, - DefaultProxyMemoryLimit: c.sidecarProxyMemoryLimit, - DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, - LifecycleConfig: lifecycleConfig, - MetricsConfig: metricsConfig, - InitContainerResources: c.initContainerResources, - ConsulPartition: c.consul.Partition, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - EnableNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, - K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableCNI: c.flagEnableCNI, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - EnableConsulDNS: c.flagEnableConsulDNS, - EnableOpenShift: c.flagEnableOpenShift, - Log: ctrl.Log.WithName("handler").WithName("connect"), - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, + Clientset: c.clientset, + ReleaseNamespace: c.flagReleaseNamespace, + ConsulConfig: consulConfig, + ConsulServerConnMgr: watcher, + ImageConsul: c.flagConsulImage, + ImageConsulDataplane: c.flagConsulDataplaneImage, + EnvoyExtraArgs: c.flagEnvoyExtraArgs, + ImageConsulK8S: c.flagConsulK8sImage, + RequireAnnotation: !c.flagDefaultInject, + AuthMethod: c.flagACLAuthMethod, + ConsulCACert: string(c.caCertPem), + TLSEnabled: c.consul.UseTLS, + ConsulAddress: c.consul.Addresses, + SkipServerWatch: c.consul.SkipServerWatch, + ConsulTLSServerName: c.consul.TLSServerName, + DefaultProxyCPURequest: c.sidecarProxyCPURequest, + DefaultProxyCPULimit: c.sidecarProxyCPULimit, + DefaultProxyMemoryRequest: c.sidecarProxyMemoryRequest, + DefaultProxyMemoryLimit: c.sidecarProxyMemoryLimit, + DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, + DefaultSidecarProxyStartupFailureSeconds: c.flagDefaultSidecarProxyStartupFailureSeconds, + DefaultSidecarProxyLivenessFailureSeconds: c.flagDefaultSidecarProxyLivenessFailureSeconds, + LifecycleConfig: lifecycleConfig, + MetricsConfig: metricsConfig, + InitContainerResources: c.initContainerResources, + ConsulPartition: c.consul.Partition, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + EnableNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, + K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + EnableCNI: c.flagEnableCNI, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + EnableConsulDNS: c.flagEnableConsulDNS, + EnableOpenShift: c.flagEnableOpenShift, + Log: ctrl.Log.WithName("handler").WithName("connect"), + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, }}) consulMeta := apicommon.ConsulMeta{ From 3cdb2f4d450e525b53fb92f4bbcc8ac02221232b Mon Sep 17 00:00:00 2001 From: Melissa Kam <3768460+mkam@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:25:56 -0600 Subject: [PATCH 563/592] Fix to support non-enterprise HCP observability testing (#3438) Use namespace query only if enterprise is enabled Requests to the community edition of Consul will fail if namespace is set in the query parameters. --- acceptance/tests/cloud/observability_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/acceptance/tests/cloud/observability_test.go b/acceptance/tests/cloud/observability_test.go index 63e2d15bd2..af553b3ecb 100644 --- a/acceptance/tests/cloud/observability_test.go +++ b/acceptance/tests/cloud/observability_test.go @@ -222,7 +222,11 @@ func TestObservabilityCloud(t *testing.T) { // Validate that the consul-telemetry-collector service was deployed to the expected namespace. consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) - instances, _, err := consulClient.Catalog().Service("consul-telemetry-collector", "", &api.QueryOptions{Namespace: ns}) + q := &api.QueryOptions{} + if cfg.EnableEnterprise { + q.Namespace = ns + } + instances, _, err := consulClient.Catalog().Service("consul-telemetry-collector", "", q) require.NoError(t, err) require.Len(t, instances, 1) require.Equal(t, "passing", instances[0].Checks.AggregatedStatus()) From f8f416da0c3ec5a65853a1609f71a794b00b09c4 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:28:46 -0800 Subject: [PATCH 564/592] added comment about cloud test package (#3471) --- acceptance/ci-inputs/aks_acceptance_test_packages.yaml | 1 + acceptance/ci-inputs/eks_acceptance_test_packages.yaml | 1 + acceptance/ci-inputs/gke_acceptance_test_packages.yaml | 1 + acceptance/ci-inputs/kind_acceptance_test_packages.yaml | 1 + 4 files changed, 4 insertions(+) diff --git a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml index c1f1093200..9b36143c6b 100644 --- a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml @@ -1,6 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +# Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} - {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml index c1f1093200..9b36143c6b 100644 --- a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml @@ -1,6 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +# Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} - {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml index c1f1093200..9b36143c6b 100644 --- a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml @@ -1,6 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +# Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} - {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml index 144f1c6678..7e670fd0aa 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -1,6 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +# Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "partitions"} - {runner: 1, test-packages: "peering"} - {runner: 2, test-packages: "sameness"} From 642d7935f4f4af4f0fae62b98df087b482b74c46 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Fri, 12 Jan 2024 11:03:06 -0600 Subject: [PATCH 565/592] Net 7189 Consul k8s add protocol to listeners on the mesh gateway (#3470) * regen crd with protocol added * add to configmap and pull from services * update test --- charts/consul/templates/crd-meshgateways.yaml | 4 ++++ .../templates/gateway-resources-configmap.yaml | 1 + .../mesh.consul.hashicorp.com_meshgateways.yaml | 4 ++++ control-plane/gateways/service.go | 1 + control-plane/gateways/service_test.go | 12 ++++++++---- control-plane/go.mod | 2 +- control-plane/go.sum | 4 ++-- 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/charts/consul/templates/crd-meshgateways.yaml b/charts/consul/templates/crd-meshgateways.yaml index 466705d10c..a7ebc1836d 100644 --- a/charts/consul/templates/crd-meshgateways.yaml +++ b/charts/consul/templates/crd-meshgateways.yaml @@ -66,6 +66,10 @@ spec: maximum: 65535 minimum: 0 type: integer + protocol: + enum: + - TCP + type: string type: object minItems: 1 type: array diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 4c62433903..2cf7ae86d0 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -124,5 +124,6 @@ data: listeners: - name: "wan" port: {{ .Values.meshGateway.service.port }} + protocol: "TCP" {{- end }} {{- end }} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml index a26a0fc158..2e5ea1596f 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml @@ -62,6 +62,10 @@ spec: maximum: 65535 minimum: 0 type: integer + protocol: + enum: + - TCP + type: string type: object minItems: 1 type: array diff --git a/control-plane/gateways/service.go b/control-plane/gateways/service.go index 007d9f5791..a245a8a902 100644 --- a/control-plane/gateways/service.go +++ b/control-plane/gateways/service.go @@ -68,6 +68,7 @@ func (b *meshGatewayBuilder) Ports(portModifier int32) []corev1.ServicePort { TargetPort: intstr.IntOrString{ IntVal: port + portModifier, }, + Protocol: corev1.Protocol(listener.Protocol), }) } return ports diff --git a/control-plane/gateways/service_test.go b/control-plane/gateways/service_test.go index 6841544281..74d95cd614 100644 --- a/control-plane/gateways/service_test.go +++ b/control-plane/gateways/service_test.go @@ -36,8 +36,9 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { GatewayClassName: "test-gateway-class", Listeners: []*pbmesh.MeshGatewayListener{ { - Name: "wan", - Port: 443, + Name: "wan", + Port: 443, + Protocol: "TCP", }, }, }, @@ -93,6 +94,7 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { TargetPort: intstr.IntOrString{ IntVal: int32(8443), }, + Protocol: "TCP", }, }, }, @@ -107,8 +109,9 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { GatewayClassName: "test-gateway-class", Listeners: []*pbmesh.MeshGatewayListener{ { - Name: "wan", - Port: 443, + Name: "wan", + Port: 443, + Protocol: "TCP", }, }, }, @@ -131,6 +134,7 @@ func Test_meshGatewayBuilder_Service(t *testing.T) { TargetPort: intstr.IntOrString{ IntVal: int32(443), }, + Protocol: "TCP", }, }, }, diff --git a/control-plane/go.mod b/control-plane/go.mod index 91037460c6..3359a0c184 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240110191229-7d92a5dfd601 +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240111191232-1e351e286e08 replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f diff --git a/control-plane/go.sum b/control-plane/go.sum index 630016914c..1feda3fa3e 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240110191229-7d92a5dfd601 h1:Y6tR3aGdKsh12oj80PWz2plRsyczJS8irZ++HdRJIB8= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240110191229-7d92a5dfd601/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240111191232-1e351e286e08 h1:oYbCbKfscW83pWuI/BkVajKH93qCJ1tPzZzGAmj5Ivo= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240111191232-1e351e286e08/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= From 4b6abc7543880006a1b90fa7969e5bf8e742c629 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:33:25 -0800 Subject: [PATCH 566/592] Set leave_on_terminate=true for servers and hardcode maxUnavailable=1 (#3000) When leave_on_terminate=false (default), rolling the statefulset is disruptive because the new servers come up with the same node IDs but different IP addresses. They can't join the server cluster until the old server's node ID is marked as failed by serf. During this time, they continually start leader elections because they don't know there's a leader. When they eventually join the cluster, their election term is higher, and so they trigger a leadership swap. The leadership swap happens at the same time as the next node to be rolled is being stopped, and so the cluster can end up without a leader. With leave_on_terminate=true, the stopping server cleanly leaves the cluster, so the new server can join smoothly, even though it has the same node ID as the old server. This increases the speed of the rollout and in my testing eliminates the period without a leader. The downside of this change is that when a server leaves gracefully, it also reduces the number of raft peers. The number of peers is used to calculate the quorum size, so this can unexpectedly change the fault tolerance of the cluster. When running with an odd number of servers, 1 server leaving the cluster does not affect quorum size. E.g. 5 servers => quorum 3, 4 servers => quorum still 3. During a rollout, Kubernetes only stops 1 server at a time, so the quorum won't change. During a voluntary disruption event, e.g. a node being drained, Kubernetes uses the pod disruption budget to determine how many pods in a statefulset can be made unavailable at a time. That's why this change hardcodes this number to 1 now. Also set autopilot min_quorum to min quorum and disable autopilot upgrade migration since that's for blue/green deploys. --- .changelog/2962.txt | 2 +- .changelog/3000.txt | 36 ++++++++ .../aks_acceptance_test_packages.yaml | 2 +- .../eks_acceptance_test_packages.yaml | 2 +- .../gke_acceptance_test_packages.yaml | 2 +- .../kind_acceptance_test_packages.yaml | 2 +- acceptance/tests/example/main_test.go | 1 + acceptance/tests/server/main_test.go | 18 ++++ acceptance/tests/server/server_test.go | 91 +++++++++++++++++++ charts/consul/templates/_helpers.tpl | 23 +++-- .../templates/server-config-configmap.yaml | 7 +- .../templates/server-disruptionbudget.yaml | 2 +- .../test/unit/server-config-configmap.bats | 60 +++++++++++- .../test/unit/server-disruptionbudget.bats | 28 +++++- .../consul/test/unit/server-statefulset.bats | 6 +- charts/consul/values.yaml | 10 +- 16 files changed, 266 insertions(+), 26 deletions(-) create mode 100644 .changelog/3000.txt create mode 100644 acceptance/tests/server/main_test.go create mode 100644 acceptance/tests/server/server_test.go diff --git a/.changelog/2962.txt b/.changelog/2962.txt index f707e4828f..f4550c2781 100644 --- a/.changelog/2962.txt +++ b/.changelog/2962.txt @@ -1,3 +1,3 @@ -```releast-note:feature +```release-note:feature api-gateway: (Consul Enterprise) Add JWT authentication and authorization for API Gateway and HTTPRoutes. ``` diff --git a/.changelog/3000.txt b/.changelog/3000.txt new file mode 100644 index 0000000000..1e6be21f78 --- /dev/null +++ b/.changelog/3000.txt @@ -0,0 +1,36 @@ +```release-note:breaking-change +server: set `leave_on_terminate` to `true` and set the server pod disruption budget `maxUnavailable` to `1`. + +This change makes server rollouts faster and more reliable. However, there is now a potential for reduced reliability if users accidentally +scale the statefulset down. Now servers will leave the raft pool when they are stopped gracefully which reduces the fault +tolerance. For example, with 5 servers, you can tolerate a loss of 2 servers' data as raft guarantees data is replicated to +a majority of nodes (3). However, if you accidentally scale the statefulset down to 3, then the raft quorum will now be 2, and +if you lose 2 servers, you may lose data. Before this change, the quorum would have remained at 3. + +During a regular rollout, the number of servers will be reduced by 1 at a time, which doesn't affect quorum when running +an odd number of servers, e.g. quorum for 5 servers is 3, and quorum for 4 servers is also 3. That's why the pod disruption +budget is being set to 1 now. + +If a server is stopped ungracefully, e.g. due to a node loss, it will not leave the raft pool, and so fault tolerance won't be affected. + +For the vast majority of users, this change will be beneficial, however if you wish to remain with the old settings you +can set: + + server: + extraConfig: | + {"leave_on_terminate": false} + disruptionBudget: + maxUnavailable: + +``` + +```release-note:breaking-change +server: set `autopilot.min_quorum` to the correct quorum value to ensure autopilot doesn't prune servers needed for quorum. Also set `autopilot. disable_upgrade_migration` to `true` as that setting is meant for blue/green deploys, not rolling deploys. + +This setting makes sense for most use-cases, however if you had a specific reason to use the old settings you can use the following config to keep them: + + server: + extraConfig: | + {"autopilot": {"min_quorum": 0, "disable_upgrade_migration": false}} + +``` diff --git a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml index 9b36143c6b..b2874fd373 100644 --- a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml @@ -4,4 +4,4 @@ # Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} -- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault server"} \ No newline at end of file diff --git a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml index 9b36143c6b..b2874fd373 100644 --- a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml @@ -4,4 +4,4 @@ # Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} -- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault server"} \ No newline at end of file diff --git a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml index 9b36143c6b..b2874fd373 100644 --- a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml @@ -4,4 +4,4 @@ # Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} -- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault server"} \ No newline at end of file diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml index 7e670fd0aa..a4e09abd9c 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -6,7 +6,7 @@ - {runner: 1, test-packages: "peering"} - {runner: 2, test-packages: "sameness"} - {runner: 3, test-packages: "connect snapshot-agent wan-federation"} -- {runner: 4, test-packages: "cli vault metrics"} +- {runner: 4, test-packages: "cli vault metrics server"} - {runner: 5, test-packages: "api-gateway ingress-gateway sync example consul-dns"} - {runner: 6, test-packages: "config-entries terminating-gateway basic"} - {runner: 7, test-packages: "mesh_v2 tenancy_v2"} diff --git a/acceptance/tests/example/main_test.go b/acceptance/tests/example/main_test.go index f92fff8a59..e35893a1d3 100644 --- a/acceptance/tests/example/main_test.go +++ b/acceptance/tests/example/main_test.go @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 // Rename package to your test package. +// NOTE: Remember to add your test package to acceptance/ci-inputs so it gets run in CI. package example import ( diff --git a/acceptance/tests/server/main_test.go b/acceptance/tests/server/main_test.go new file mode 100644 index 0000000000..497df9dca2 --- /dev/null +++ b/acceptance/tests/server/main_test.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package server + +import ( + "os" + "testing" + + testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" +) + +var suite testsuite.Suite + +func TestMain(m *testing.M) { + suite = testsuite.NewSuite(m) + os.Exit(suite.Run()) +} diff --git a/acceptance/tests/server/server_test.go b/acceptance/tests/server/server_test.go new file mode 100644 index 0000000000..5511671935 --- /dev/null +++ b/acceptance/tests/server/server_test.go @@ -0,0 +1,91 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package server + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/go-multierror" + "github.com/stretchr/testify/require" +) + +// Test that when servers are restarted, they don't lose leadership. +func TestServerRestart(t *testing.T) { + cfg := suite.Config() + if cfg.EnableCNI || cfg.EnableTransparentProxy { + t.Skipf("skipping because -enable-cni or -enable-transparent-proxy is set and server restart " + + "is already tested without those settings and those settings don't affect this test") + } + + ctx := suite.Environment().DefaultContext(t) + replicas := 3 + releaseName := helpers.RandomName() + helmValues := map[string]string{ + "global.enabled": "false", + "connectInject.enabled": "false", + "server.enabled": "true", + "server.replicas": fmt.Sprintf("%d", replicas), + "server.affinity": "null", // Allow >1 pods per node so we can test in minikube with one node. + } + consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), suite.Config(), releaseName) + consulCluster.Create(t) + + // Start a separate goroutine to check if at any point more than one server is without + // a leader. We expect the server that is restarting to be without a leader because it hasn't + // yet joined the cluster but the other servers should have a leader. + expReadyPods := replicas - 1 + var unmarshallErrs error + timesWithoutLeader := 0 + done := make(chan bool) + defer close(done) + go func() { + for { + select { + case <-done: + return + default: + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "get", fmt.Sprintf("statefulset/%s-consul-server", releaseName), + "-o", "jsonpath={.status}") + if err != nil { + // Not failing the test on this error to reduce flakiness. + logger.Logf(t, "kubectl err: %s: %s", err, out) + break + } + type statefulsetOut struct { + ReadyReplicas *int `json:"readyReplicas,omitempty"` + } + var jsonOut statefulsetOut + if err = json.Unmarshal([]byte(out), &jsonOut); err != nil { + unmarshallErrs = multierror.Append(err) + } else if jsonOut.ReadyReplicas == nil || *jsonOut.ReadyReplicas < expReadyPods { + // note: for some k8s api reason when readyReplicas is 0 it's not included in the json output so + // that's why we're checking if it's nil. + timesWithoutLeader++ + } + time.Sleep(1 * time.Second) + } + } + }() + + // Restart servers + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "rollout", "restart", fmt.Sprintf("statefulset/%s-consul-server", releaseName)) + require.NoError(t, err, out) + + // Wait for restart to finish. + start := time.Now() + out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "rollout", "status", "--timeout", "5m", "--watch", fmt.Sprintf("statefulset/%s-consul-server", releaseName)) + require.NoError(t, err, out, "rollout status command errored, this likely means the rollout didn't complete in time") + + // Check results + require.NoError(t, unmarshallErrs, "there were some json unmarshall errors, this is likely a bug") + logger.Logf(t, "restart took %s, there were %d instances where more than one server had no leader", time.Since(start), timesWithoutLeader) + require.Equal(t, 0, timesWithoutLeader, "there were %d instances where more than one server had no leader", timesWithoutLeader) +} diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index decb40319c..b78987f908 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -166,24 +166,27 @@ Expand the name of the chart. {{- end -}} {{/* -Compute the maximum number of unavailable replicas for the PodDisruptionBudget. -This defaults to (n/2)-1 where n is the number of members of the server cluster. -Special case of replica equaling 3 and allowing a minor disruption of 1 otherwise -use the integer value -Add a special case for replicas=1, where it should default to 0 as well. +Calculate max number of server pods that are allowed to be voluntarily disrupted. +When there's 1 server, this is set to 0 because this pod should not be disrupted. This is an edge +case and I'm not sure it makes a difference when there's only one server but that's what the previous config was and +I don't want to change it for this edge case. +Otherwise we've changed this to always be 1 as part of the move to set leave_on_terminate +to true. With leave_on_terminate set to true, whenever a server pod is stopped, the number of peers in raft +is reduced. If the number of servers is odd and the count is reduced by 1, the quorum size doesn't change, +but if it's reduced by more than 1, the quorum size can change so that's why this is now always hardcoded to 1. */}} -{{- define "consul.pdb.maxUnavailable" -}} +{{- define "consul.server.pdb.maxUnavailable" -}} {{- if eq (int .Values.server.replicas) 1 -}} {{ 0 }} {{- else if .Values.server.disruptionBudget.maxUnavailable -}} {{ .Values.server.disruptionBudget.maxUnavailable -}} {{- else -}} -{{- if eq (int .Values.server.replicas) 3 -}} -{{- 1 -}} -{{- else -}} -{{- sub (div (int .Values.server.replicas) 2) 1 -}} +{{ 1 }} {{- end -}} {{- end -}} + +{{- define "consul.server.autopilotMinQuorum" -}} +{{- add (div (int .Values.server.replicas) 2) 1 -}} {{- end -}} {{- define "consul.pdb.connectInject.maxUnavailable" -}} diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 8cd726f445..4ce79794b2 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -56,7 +56,12 @@ data: "enabled": true }, {{- end }} - "server": true + "server": true, + "leave_on_terminate": true, + "autopilot": { + "min_quorum": {{ template "consul.server.autopilotMinQuorum" . }}, + "disable_upgrade_migration": true + } } {{- $vaultConnectCAEnabled := and .Values.global.secretsBackend.vault.connectCA.address .Values.global.secretsBackend.vault.connectCA.rootPKIPath .Values.global.secretsBackend.vault.connectCA.intermediatePKIPath -}} {{- if and .Values.global.secretsBackend.vault.enabled $vaultConnectCAEnabled }} diff --git a/charts/consul/templates/server-disruptionbudget.yaml b/charts/consul/templates/server-disruptionbudget.yaml index edf9c1c57f..56805edc2a 100644 --- a/charts/consul/templates/server-disruptionbudget.yaml +++ b/charts/consul/templates/server-disruptionbudget.yaml @@ -17,7 +17,7 @@ metadata: release: {{ .Release.Name }} component: server spec: - maxUnavailable: {{ template "consul.pdb.maxUnavailable" . }} + maxUnavailable: {{ template "consul.server.pdb.maxUnavailable" . }} selector: matchLabels: app: {{ template "consul.name" . }} diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 7ed7f57a9c..53f20a91f6 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -1307,4 +1307,62 @@ load _helpers yq -r '.data["server.json"]' | jq -r .log_level | tee /dev/stderr) [ "${configmap}" = "DEBUG" ] -} \ No newline at end of file +} + +#-------------------------------------------------------------------- +# server.autopilot.min_quorum + +@test "server/ConfigMap: autopilot.min_quorum=1 when replicas=1" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.replicas=1' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) + + [ "${actual}" = "1" ] +} + +@test "server/ConfigMap: autopilot.min_quorum=2 when replicas=2" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.replicas=2' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) + + [ "${actual}" = "2" ] +} + +@test "server/ConfigMap: autopilot.min_quorum=2 when replicas=3" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.replicas=3' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) + + [ "${actual}" = "2" ] +} + +@test "server/ConfigMap: autopilot.min_quorum=3 when replicas=4" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.replicas=4' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) + + [ "${actual}" = "3" ] +} + +@test "server/ConfigMap: autopilot.min_quorum=3 when replicas=5" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.replicas=5' \ + . | tee /dev/stderr | + yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) + + [ "${actual}" = "3" ] +} diff --git a/charts/consul/test/unit/server-disruptionbudget.bats b/charts/consul/test/unit/server-disruptionbudget.bats index eb076ac775..5d30d8b628 100755 --- a/charts/consul/test/unit/server-disruptionbudget.bats +++ b/charts/consul/test/unit/server-disruptionbudget.bats @@ -59,6 +59,16 @@ load _helpers [ "${actual}" = "0" ] } +@test "server/DisruptionBudget: correct maxUnavailable with replicas=2" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-disruptionbudget.yaml \ + --set 'server.replicas=2' \ + . | tee /dev/stderr | + yq '.spec.maxUnavailable' | tee /dev/stderr) + [ "${actual}" = "1" ] +} + @test "server/DisruptionBudget: correct maxUnavailable with replicas=3" { cd `chart_dir` local actual=$(helm template \ @@ -97,7 +107,7 @@ load _helpers --set 'server.replicas=6' \ . | tee /dev/stderr | yq '.spec.maxUnavailable' | tee /dev/stderr) - [ "${actual}" = "2" ] + [ "${actual}" = "1" ] } @test "server/DisruptionBudget: correct maxUnavailable with replicas=7" { @@ -107,7 +117,7 @@ load _helpers --set 'server.replicas=7' \ . | tee /dev/stderr | yq '.spec.maxUnavailable' | tee /dev/stderr) - [ "${actual}" = "2" ] + [ "${actual}" = "1" ] } @test "server/DisruptionBudget: correct maxUnavailable with replicas=8" { @@ -117,9 +127,21 @@ load _helpers --set 'server.replicas=8' \ . | tee /dev/stderr | yq '.spec.maxUnavailable' | tee /dev/stderr) - [ "${actual}" = "3" ] + [ "${actual}" = "1" ] +} + +@test "server/DisruptionBudget: correct maxUnavailable when set with value" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-disruptionbudget.yaml \ + --set 'server.replicas=5' \ + --set 'server.disruptionBudget.maxUnavailable=5' \ + . | tee /dev/stderr | + yq '.spec.maxUnavailable' | tee /dev/stderr) + [ "${actual}" = "5" ] } + #-------------------------------------------------------------------- # apiVersion diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index f4702af827..388d4dc986 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -792,7 +792,7 @@ load _helpers -s templates/server-statefulset.yaml \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 0e599137f8357c786d46e1b694d7d867c541cb34d6056241a037afd0de14866b ] + [ "${actual}" = 59777d0692f3bf9143a09e96e58cc95296c0eaeb7565b1c49bc932954fe4423a ] } @test "server/StatefulSet: adds config-checksum annotation when extraConfig is provided" { @@ -802,7 +802,7 @@ load _helpers --set 'server.extraConfig="{\"hello\": \"world\"}"' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 3f54c51be3473d7ae4cb91c24ba03263b7700d9a3dc3196f624ce3c6c8e93b8f ] + [ "${actual}" = 326eeb3fd8f497297671791c0091e7f804727b3aaeff79a47841b65571bd4cf9 ] } @test "server/StatefulSet: adds config-checksum annotation when config is updated" { @@ -812,7 +812,7 @@ load _helpers --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = b44c82c9e4732433f54eeed8a299f11de0bad82a920047c8a3ad039e512ba281 ] + [ "${actual}" = e14ba19e37a6b79b6fd8566597464154abe9cdc4aa7079012fb3c445ac08c526 ] } #-------------------------------------------------------------------- diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 47b875d3e3..4df6ebc569 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -1005,8 +1005,14 @@ server: # the server cluster is enabled. To disable, set to `false`. enabled: true - # The maximum number of unavailable pods. By default, this will be - # automatically computed based on the `server.replicas` value to be `(n/2)-1`. + # The maximum number of unavailable pods. In most cases you should not change this as it is automatically set to + # the correct number when left as null. This setting has been kept to not break backwards compatibility. + # + # By default, this is set to 1 internally in the chart. When server pods are stopped gracefully, they leave the Raft + # consensus pool. When running an odd number of servers, one server leaving the pool does not change the quorum + # size, and so fault tolerance is not affected. However, if more than one server were to leave the pool, the quorum + # size would change. That's why this is set to 1 internally and should not be changed in most cases. + # # If you need to set this to `0`, you will need to add a # --set 'server.disruptionBudget.maxUnavailable=0'` flag to the helm chart installation # command because of a limitation in the Helm templating language. From 5e97ed013664411f299152e042eef2aa56ca6e35 Mon Sep 17 00:00:00 2001 From: Semir Patel Date: Tue, 16 Jan 2024 14:44:56 -0600 Subject: [PATCH 567/592] v2tenancy: v2 namespace controller + tests (#3435) --- Makefile | 10 +- .../templates/connect-inject-deployment.yaml | 3 + .../test/unit/connect-inject-deployment.bats | 30 +- .../consul_resource_controller_test.go | 67 +--- .../connect-inject/common/common_test.go | 5 +- .../endpointsv2/endpoints_controller_test.go | 34 +- .../pod/pod_controller_ent_test.go | 26 +- .../controllers/pod/pod_controller_test.go | 181 +++-------- .../serviceaccount_controller_test.go | 15 +- control-plane/helper/test/test_util.go | 102 +++++- .../subcommand/inject-connect/command.go | 16 + .../subcommand/inject-connect/command_test.go | 9 + .../inject-connect/v2controllers.go | 47 ++- .../subcommand/mesh-init/command_ent_test.go | 7 +- .../subcommand/mesh-init/command_test.go | 17 +- .../partition-init/command_ent_test.go | 16 +- .../server-acl-init/command_ent_test.go | 2 - control-plane/tenancy/namespace/namespace.go | 114 +++++++ .../tenancy/namespace/namespace_controller.go | 95 ++++++ .../namespace_controller_ent_test.go | 35 ++ .../namespace/namespace_controller_test.go | 301 ++++++++++++++++++ 21 files changed, 822 insertions(+), 310 deletions(-) create mode 100644 control-plane/tenancy/namespace/namespace.go create mode 100644 control-plane/tenancy/namespace/namespace_controller.go create mode 100644 control-plane/tenancy/namespace/namespace_controller_ent_test.go create mode 100644 control-plane/tenancy/namespace/namespace_controller_test.go diff --git a/Makefile b/Makefile index 248dddf995..af99dcbf2f 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ bats-tests: ## Run Helm chart bats tests. .PHONY: control-plane-dev control-plane-dev: ## Build consul-k8s-control-plane binary. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a amd64 + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch amd64 .PHONY: dev-docker dev-docker: control-plane-dev-docker ## build dev local dev docker image @@ -41,7 +41,7 @@ dev-docker: control-plane-dev-docker ## build dev local dev docker image .PHONY: control-plane-dev-docker control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) @docker build -t '$(DEV_IMAGE)' \ --target=dev \ --build-arg 'TARGETARCH=$(GOARCH)' \ @@ -53,7 +53,7 @@ control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. .PHONY: control-plane-dev-skaffold # DANGER: this target is experimental and could be modified/removed at any time. control-plane-dev-skaffold: ## Build consul-k8s-control-plane dev Docker image for use with skaffold or local development. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) @docker build -t '$(DEV_IMAGE)' \ --build-arg 'TARGETARCH=$(GOARCH)' \ -f $(CURDIR)/control-plane/Dockerfile.dev $(CURDIR)/control-plane @@ -66,7 +66,7 @@ endif .PHONY: control-plane-dev-docker-multi-arch control-plane-dev-docker-multi-arch: check-remote-dev-image-env ## Build consul-k8s-control-plane dev multi-arch Docker image. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a "arm64 amd64" + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch "arm64 amd64" @docker buildx create --use && docker buildx build -t '$(REMOTE_DEV_IMAGE)' \ --platform linux/amd64,linux/arm64 \ --target=dev \ @@ -78,7 +78,7 @@ control-plane-dev-docker-multi-arch: check-remote-dev-image-env ## Build consul- .PHONY: control-plane-fips-dev-docker control-plane-fips-dev-docker: ## Build consul-k8s-control-plane FIPS dev Docker image. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) --fips + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) --fips @docker build -t '$(DEV_IMAGE)' \ --target=dev \ --build-arg 'TARGETARCH=$(GOARCH)' \ diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index b87c8223b8..4066fe3103 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -157,6 +157,9 @@ spec: {{- if (mustHas "resource-apis" .Values.global.experiments) }} -enable-resource-apis=true \ {{- end }} + {{- if (mustHas "v2tenancy" .Values.global.experiments) }} + -enable-v2tenancy=true \ + {{- end }} {{- range $k, $v := .Values.connectInject.consulNode.meta }} -node-meta={{ $k }}={{ $v }} \ {{- end }} diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 67be4474ea..1e6397e39c 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -2710,4 +2710,32 @@ reservedNameTest() { yq '.spec.template.spec.containers[0].command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) [ "${actual}" = "true" ] -} \ No newline at end of file +} + +#-------------------------------------------------------------------- +# v2tenancy + +@test "connectInject/Deployment: v2tenancy is not set by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-v2tenancy=true"))' | tee /dev/stderr) + + [ "${actual}" = "false" ] +} + +@test "connectInject/Deployment: -enable-v2tenancy=true is set when global.experiments contains [\"resource-apis\", \"v2tenancy\"]" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'global.experiments[1]=v2tenancy' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-v2tenancy=true"))' | tee /dev/stderr) + + [ "${actual}" = "true" ] +} diff --git a/control-plane/config-entries/controllersv2/consul_resource_controller_test.go b/control-plane/config-entries/controllersv2/consul_resource_controller_test.go index 7f5787882f..23ccc9f656 100644 --- a/control-plane/config-entries/controllersv2/consul_resource_controller_test.go +++ b/control-plane/config-entries/controllersv2/consul_resource_controller_test.go @@ -143,13 +143,6 @@ func TestConsulResourceController_CreatesConsulResource(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) namespacedName := types.NamespacedName{ @@ -163,7 +156,7 @@ func TestConsulResourceController_CreatesConsulResource(t *testing.T) { require.False(t, resp.Requeue) req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - res, err := resourceClient.Read(ctx, req) + res, err := testClient.ResourceClient.Read(ctx, req) require.NoError(t, err) require.NotNil(t, res) require.Equal(t, c.resource.GetName(), res.GetResource().GetId().GetName()) @@ -289,20 +282,13 @@ func TestConsulResourceController_UpdatesConsulResource(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // We haven't run reconcile yet, so we must create the resource // in Consul ourselves. { resource := c.resource.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) req := &pbresource.WriteRequest{Resource: resource} - _, err := resourceClient.Write(ctx, req) + _, err := testClient.ResourceClient.Write(ctx, req) require.NoError(t, err) } @@ -329,7 +315,7 @@ func TestConsulResourceController_UpdatesConsulResource(t *testing.T) { // Now check that the object in Consul is as expected. req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - res, err := resourceClient.Read(ctx, req) + res, err := testClient.ResourceClient.Read(ctx, req) require.NoError(t, err) require.NotNil(t, res) require.Equal(t, c.resource.GetName(), res.GetResource().GetId().GetName()) @@ -411,20 +397,13 @@ func TestConsulResourceController_DeletesConsulResource(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // We haven't run reconcile yet, so we must create the config entry // in Consul ourselves. { resource := c.resource.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) req := &pbresource.WriteRequest{Resource: resource} - _, err := resourceClient.Write(ctx, req) + _, err := testClient.ResourceClient.Write(ctx, req) require.NoError(t, err) } @@ -443,7 +422,7 @@ func TestConsulResourceController_DeletesConsulResource(t *testing.T) { // Now check that the object in Consul is as expected. req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - _, err = resourceClient.Read(ctx, req) + _, err = testClient.ResourceClient.Read(ctx, req) require.Error(t, err) require.True(t, isNotFoundErr(err)) } @@ -485,11 +464,6 @@ func TestConsulResourceController_ErrorUpdatesSyncStatus(t *testing.T) { c.Experiments = []string{"resource-apis"} }) - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - // Stop the server before calling reconcile imitating a server that's not running. _ = testClient.TestServer.Stop() @@ -568,13 +542,6 @@ func TestConsulResourceController_SetsSyncedToTrue(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) reconciler := &TrafficPermissionsController{ Client: fakeClient, @@ -590,7 +557,7 @@ func TestConsulResourceController_SetsSyncedToTrue(t *testing.T) { { resource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) req := &pbresource.WriteRequest{Resource: resource} - _, err := resourceClient.Write(ctx, req) + _, err := testClient.ResourceClient.Write(ctx, req) require.NoError(t, err) } @@ -648,13 +615,6 @@ func TestConsulResourceController_DoesNotCreateUnownedResource(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) unmanagedResource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) unmanagedResource.Metadata = make(map[string]string) // Zero out the metadata @@ -663,7 +623,7 @@ func TestConsulResourceController_DoesNotCreateUnownedResource(t *testing.T) { // in Consul ourselves, without the metadata indicating it is owned by the controller. { req := &pbresource.WriteRequest{Resource: unmanagedResource} - _, err := resourceClient.Write(ctx, req) + _, err := testClient.ResourceClient.Write(ctx, req) require.NoError(t, err) } @@ -694,7 +654,7 @@ func TestConsulResourceController_DoesNotCreateUnownedResource(t *testing.T) { // Now check that the object in Consul is as expected. req := &pbresource.ReadRequest{Id: trafficpermissions.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - readResp, err := resourceClient.Read(ctx, req) + readResp, err := testClient.ResourceClient.Read(ctx, req) require.NoError(t, err) require.NotNil(t, readResp.GetResource()) opts := append([]cmp.Option{ @@ -756,13 +716,6 @@ func TestConsulResourceController_doesNotDeleteUnownedConfig(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) reconciler := &TrafficPermissionsController{ Client: fakeClient, @@ -780,7 +733,7 @@ func TestConsulResourceController_doesNotDeleteUnownedConfig(t *testing.T) { // in Consul ourselves, without the metadata indicating it is owned by the controller. { req := &pbresource.WriteRequest{Resource: unmanagedResource} - _, err := resourceClient.Write(ctx, req) + _, err := testClient.ResourceClient.Write(ctx, req) require.NoError(t, err) } @@ -799,7 +752,7 @@ func TestConsulResourceController_doesNotDeleteUnownedConfig(t *testing.T) { // Now check that the object in Consul is as expected. req := &pbresource.ReadRequest{Id: trafficpermissionsWithDeletion.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - readResp, err := resourceClient.Read(ctx, req) + readResp, err := testClient.ResourceClient.Read(ctx, req) require.NoError(t, err) require.NotNil(t, readResp.GetResource()) opts := append([]cmp.Option{ diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index f7fff948a6..cd4371062d 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -23,7 +23,6 @@ import ( "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) @@ -529,8 +528,6 @@ func Test_ConsulNamespaceIsNotFound_ErrorMsg(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) id := &pbresource.ID{ Name: "foo", @@ -566,7 +563,7 @@ func Test_ConsulNamespaceIsNotFound_ErrorMsg(t *testing.T) { Data: data, } - _, err = resourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: resource}) + _, err := testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: resource}) require.Error(t, err) s, ok := status.FromError(err) diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index cfbd7d7cc2..c55ca06cdb 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "testing" - "time" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" @@ -33,7 +32,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api/common" inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -1755,9 +1753,6 @@ func TestEnsureService(t *testing.T) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { // Create the Endpoints controller. @@ -1774,7 +1769,7 @@ func TestEnsureService(t *testing.T) { // Set up test resourceReadWriter rw := struct{ testReadWriter }{} - defaultRw := defaultResourceReadWriter{resourceClient} + defaultRw := defaultResourceReadWriter{testClient.ResourceClient} rw.readFn = defaultRw.Read rw.writeFn = defaultRw.Write if tc.readFn != nil { @@ -1798,7 +1793,7 @@ func TestEnsureService(t *testing.T) { require.NoError(t, err) // Get written resource before additional calls - beforeResource := getAndValidateResource(t, resourceClient, id) + beforeResource := getAndValidateResource(t, testClient.ResourceClient, id) // Call a second time err = ep.ensureService(context.Background(), &rw, tc.afterArgs.k8sUid, id, tc.afterArgs.meta, tc.afterArgs.consulSvc) @@ -1806,26 +1801,26 @@ func TestEnsureService(t *testing.T) { // Check for change on second call to ensureService if tc.expectWrite { - require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), + require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, testClient.ResourceClient, id).GetGeneration(), "wanted different version for before and after resources following modification and reconcile") } else { - require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), + require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, testClient.ResourceClient, id).GetGeneration(), "wanted same version for before and after resources following repeat reconcile") } // Call several additional times for i := 0; i < 5; i++ { // Get written resource before each additional call - beforeResource = getAndValidateResource(t, resourceClient, id) + beforeResource = getAndValidateResource(t, testClient.ResourceClient, id) err := ep.ensureService(context.Background(), &rw, tc.afterArgs.k8sUid, id, tc.afterArgs.meta, tc.afterArgs.consulSvc) require.NoError(t, err) if tc.expectAlwaysWrite { - require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), + require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, testClient.ResourceClient, id).GetGeneration(), "wanted different version for before and after resources following modification and reconcile") } else { - require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), + require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, testClient.ResourceClient, id).GetGeneration(), "wanted same version for before and after resources following repeat reconcile") } } @@ -2196,11 +2191,6 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { c.Experiments = []string{"resource-apis"} }) - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - // Create the Endpoints controller. ep := &Controller{ Client: fakeClient, @@ -2212,8 +2202,6 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { DenyK8sNamespacesSet: mapset.NewSetWith(), }, } - resourceClient, err := consul.NewResourceServiceClient(ep.ConsulServerConnMgr) - require.NoError(t, err) // Default ns and partition if not specified in test. if tc.targetConsulNs == "" { @@ -2226,9 +2214,9 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { // If existing resource specified, create it and ensure it exists. if tc.existingResource != nil { writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} - _, err = resourceClient.Write(context.Background(), writeReq) + _, err := testClient.ResourceClient.Write(context.Background(), writeReq) require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), resourceClient, tc.existingResource.Id) + test.ResourceHasPersisted(t, context.Background(), testClient.ResourceClient, tc.existingResource.Id) } // Run actual reconcile and verify results. @@ -2245,10 +2233,10 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { } require.False(t, resp.Requeue) - expectedServiceMatches(t, resourceClient, tc.svcName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) + expectedServiceMatches(t, testClient.ResourceClient, tc.svcName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) if tc.caseFn != nil { - tc.caseFn(t, &tc, ep, resourceClient) + tc.caseFn(t, &tc, ep, testClient.ResourceClient) } } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go index a116122a03..614526254e 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go @@ -8,7 +8,6 @@ package pod import ( "context" "testing" - "time" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" @@ -27,7 +26,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -672,14 +670,6 @@ func runControllerTest(t *testing.T, tc testCase) { } }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - // Create the partition in Consul. if tc.partition != "" { testClient.Cfg.APIClientConfig.Partition = tc.partition @@ -740,10 +730,10 @@ func runControllerTest(t *testing.T, tc testCase) { } workloadID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) - loadResource(t, context.Background(), resourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, context.Background(), resourceClient, getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingHealthStatus, workloadID) - loadResource(t, context.Background(), resourceClient, getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingProxyConfiguration, nil) - loadResource(t, context.Background(), resourceClient, getDestinationsID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingDestinations, nil) + loadResource(t, context.Background(), testClient.ResourceClient, workloadID, tc.existingWorkload, nil) + loadResource(t, context.Background(), testClient.ResourceClient, getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingHealthStatus, workloadID) + loadResource(t, context.Background(), testClient.ResourceClient, getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingProxyConfiguration, nil) + loadResource(t, context.Background(), testClient.ResourceClient, getDestinationsID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingDestinations, nil) namespacedName := types.NamespacedName{ Namespace: podNamespace, @@ -762,14 +752,14 @@ func runControllerTest(t *testing.T, tc testCase) { require.Equal(t, tc.expRequeue, resp.Requeue) wID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) + expectedWorkloadMatches(t, context.Background(), testClient.ResourceClient, wID, tc.expectedWorkload) hsID := getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) + expectedHealthStatusMatches(t, context.Background(), testClient.ResourceClient, hsID, tc.expectedHealthStatus) pcID := getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) + expectedProxyConfigurationMatches(t, context.Background(), testClient.ResourceClient, pcID, tc.expectedProxyConfiguration) uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) + expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tc.expectedDestinations) } diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 55b5fd5ba1..55ff24ae5b 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -36,7 +36,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -119,13 +118,6 @@ func TestWorkloadWrite(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // Create the pod controller. pc := &Controller{ @@ -137,16 +129,16 @@ func TestWorkloadWrite(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), }, - ResourceClient: resourceClient, + ResourceClient: testClient.ResourceClient, } - err = pc.writeWorkload(context.Background(), *tc.pod) + err := pc.writeWorkload(context.Background(), *tc.pod) require.NoError(t, err) req := &pbresource.ReadRequest{ Id: getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), } - actualRes, err := resourceClient.Read(context.Background(), req) + actualRes, err := testClient.ResourceClient.Read(context.Background(), req) require.NoError(t, err) require.NotNil(t, actualRes) @@ -310,13 +302,6 @@ func TestWorkloadDelete(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // Create the pod controller. pc := &Controller{ @@ -328,7 +313,7 @@ func TestWorkloadDelete(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), }, - ResourceClient: resourceClient, + ResourceClient: testClient.ResourceClient, } workload, err := anypb.New(tc.existingWorkload) @@ -342,9 +327,9 @@ func TestWorkloadDelete(t *testing.T) { }, } - _, err = resourceClient.Write(context.Background(), writeReq) + _, err = testClient.ResourceClient.Write(context.Background(), writeReq) require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), resourceClient, workloadID) + test.ResourceHasPersisted(t, context.Background(), testClient.ResourceClient, workloadID) reconcileReq := types.NamespacedName{ Namespace: metav1.NamespaceDefault, @@ -356,7 +341,7 @@ func TestWorkloadDelete(t *testing.T) { readReq := &pbresource.ReadRequest{ Id: getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), } - _, err = resourceClient.Read(context.Background(), readReq) + _, err = testClient.ResourceClient.Read(context.Background(), readReq) require.Error(t, err) s, ok := status.FromError(err) require.True(t, ok) @@ -399,13 +384,6 @@ func TestHealthStatusWrite(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // Create the pod controller. pc := &Controller{ @@ -417,7 +395,7 @@ func TestHealthStatusWrite(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), }, - ResourceClient: resourceClient, + ResourceClient: testClient.ResourceClient, } // The owner of a resource is validated, so create a dummy workload for the HealthStatus @@ -431,7 +409,7 @@ func TestHealthStatusWrite(t *testing.T) { Data: workloadData, }, } - _, err = resourceClient.Write(context.Background(), writeReq) + _, err = testClient.ResourceClient.Write(context.Background(), writeReq) require.NoError(t, err) // Test writing the pod to a HealthStatus @@ -441,7 +419,7 @@ func TestHealthStatusWrite(t *testing.T) { req := &pbresource.ReadRequest{ Id: getHealthStatusID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), } - actualRes, err := resourceClient.Read(context.Background(), req) + actualRes, err := testClient.ResourceClient.Read(context.Background(), req) require.NoError(t, err) require.NotNil(t, actualRes) @@ -520,13 +498,6 @@ func TestProxyConfigurationWrite(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // Create the pod controller. pc := &Controller{ @@ -541,7 +512,7 @@ func TestProxyConfigurationWrite(t *testing.T) { EnableTransparentProxy: tc.tproxy, TProxyOverwriteProbes: tc.overwriteProbes, EnableTelemetryCollector: tc.telemetry, - ResourceClient: resourceClient, + ResourceClient: testClient.ResourceClient, } if tc.metrics { @@ -552,13 +523,13 @@ func TestProxyConfigurationWrite(t *testing.T) { } // Test writing the pod to a HealthStatus - err = pc.writeProxyConfiguration(context.Background(), *tc.pod) + err := pc.writeProxyConfiguration(context.Background(), *tc.pod) require.NoError(t, err) req := &pbresource.ReadRequest{ Id: getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), } - actualRes, err := resourceClient.Read(context.Background(), req) + actualRes, err := testClient.ResourceClient.Read(context.Background(), req) if tc.expectedProxyConfiguration == nil { require.Error(t, err) @@ -727,13 +698,6 @@ func TestProxyConfigurationDelete(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // Create the pod controller. pc := &Controller{ @@ -745,7 +709,7 @@ func TestProxyConfigurationDelete(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), }, - ResourceClient: resourceClient, + ResourceClient: testClient.ResourceClient, } // Create the existing ProxyConfiguration @@ -760,9 +724,9 @@ func TestProxyConfigurationDelete(t *testing.T) { }, } - _, err = resourceClient.Write(context.Background(), writeReq) + _, err = testClient.ResourceClient.Write(context.Background(), writeReq) require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), resourceClient, pcID) + test.ResourceHasPersisted(t, context.Background(), testClient.ResourceClient, pcID) reconcileReq := types.NamespacedName{ Namespace: metav1.NamespaceDefault, @@ -774,7 +738,7 @@ func TestProxyConfigurationDelete(t *testing.T) { readReq := &pbresource.ReadRequest{ Id: getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), } - _, err = resourceClient.Read(context.Background(), readReq) + _, err = testClient.ResourceClient.Read(context.Background(), readReq) require.Error(t, err) s, ok := status.FromError(err) require.True(t, ok) @@ -1011,13 +975,6 @@ func TestDestinationsWrite(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) pc := &Controller{ Log: logrtest.New(t), @@ -1029,17 +986,17 @@ func TestDestinationsWrite(t *testing.T) { EnableConsulNamespaces: tt.consulNamespacesEnabled, EnableConsulPartitions: tt.consulPartitionsEnabled, }, - ResourceClient: resourceClient, + ResourceClient: testClient.ResourceClient, } - err = pc.writeDestinations(context.Background(), *tt.pod()) + err := pc.writeDestinations(context.Background(), *tt.pod()) if tt.expErr != "" { require.EqualError(t, err, tt.expErr) } else { require.NoError(t, err) uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tt.expected) + expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tt.expected) } }) } @@ -1099,13 +1056,6 @@ func TestDestinationsDelete(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) pc := &Controller{ Log: logrtest.New(t), @@ -1113,17 +1063,17 @@ func TestDestinationsDelete(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), }, - ResourceClient: resourceClient, + ResourceClient: testClient.ResourceClient, } // Load in the upstream for us to delete and check that it's there - loadResource(t, context.Background(), resourceClient, getDestinationsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.existingDestinations, nil) + loadResource(t, context.Background(), testClient.ResourceClient, getDestinationsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.existingDestinations, nil) uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tt.existingDestinations) + expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tt.existingDestinations) // Delete the upstream nn := types.NamespacedName{Name: tt.pod().Name} - err = pc.deleteDestinations(context.Background(), nn) + err := pc.deleteDestinations(context.Background(), nn) // Verify the upstream has been deleted or that an expected error has been returned if tt.expErr != "" { @@ -1131,7 +1081,7 @@ func TestDestinationsDelete(t *testing.T) { } else { require.NoError(t, err) uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, nil) + expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, nil) } }) } @@ -1150,20 +1100,6 @@ func TestDeleteACLTokens(t *testing.T) { c.ACL.Tokens.InitialManagement = masterToken c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - // Wait for the ACL system to be bootstraped - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.ACL().PolicyList(nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Wait for the default partition to be created - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) test.SetupK8sAuthMethodV2(t, testClient.APIClient, serviceName, metav1.NamespaceDefault) token, _, err := testClient.APIClient.ACL().Login(&api.ACLLoginParams{ @@ -1182,7 +1118,7 @@ func TestDeleteACLTokens(t *testing.T) { AllowK8sNamespacesSet: mapset.NewSetWith("*"), DenyK8sNamespacesSet: mapset.NewSetWith(), }, - ResourceClient: resourceClient, + ResourceClient: testClient.ResourceClient, AuthMethod: test.AuthMethod, ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, @@ -1244,13 +1180,6 @@ func TestReconcileCreatePod(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // Create the pod controller. pc := &Controller{ @@ -1294,16 +1223,16 @@ func TestReconcileCreatePod(t *testing.T) { require.Equal(t, tc.requeue, resp.Requeue) wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) + expectedWorkloadMatches(t, context.Background(), testClient.ResourceClient, wID, tc.expectedWorkload) hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) + expectedHealthStatusMatches(t, context.Background(), testClient.ResourceClient, hsID, tc.expectedHealthStatus) pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) + expectedProxyConfigurationMatches(t, context.Background(), testClient.ResourceClient, pcID, tc.expectedProxyConfiguration) uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) + expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tc.expectedDestinations) } testCases := []testCase{ @@ -1474,13 +1403,6 @@ func TestReconcileUpdatePod(t *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // Create the pod controller. pc := &Controller{ @@ -1509,10 +1431,10 @@ func TestReconcileUpdatePod(t *testing.T) { } workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) - loadResource(t, context.Background(), resourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, context.Background(), resourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) - loadResource(t, context.Background(), resourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) - loadResource(t, context.Background(), resourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) + loadResource(t, context.Background(), testClient.ResourceClient, workloadID, tc.existingWorkload, nil) + loadResource(t, context.Background(), testClient.ResourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) + loadResource(t, context.Background(), testClient.ResourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) + loadResource(t, context.Background(), testClient.ResourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) namespacedName := types.NamespacedName{ Namespace: namespace, @@ -1530,16 +1452,16 @@ func TestReconcileUpdatePod(t *testing.T) { require.False(t, resp.Requeue) wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) + expectedWorkloadMatches(t, context.Background(), testClient.ResourceClient, wID, tc.expectedWorkload) hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) + expectedHealthStatusMatches(t, context.Background(), testClient.ResourceClient, hsID, tc.expectedHealthStatus) pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) + expectedProxyConfigurationMatches(t, context.Background(), testClient.ResourceClient, pcID, tc.expectedProxyConfiguration) uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) + expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tc.expectedDestinations) } testCases := []testCase{ @@ -1736,16 +1658,6 @@ func TestReconcileDeletePod(t *testing.T) { } c.Experiments = []string{"resource-apis"} }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - if tc.aclsEnabled { - // Wait for the ACL system to be bootstraped - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.ACL().PolicyList(nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - } ctx := context.Background() if tc.aclsEnabled { @@ -1779,12 +1691,13 @@ func TestReconcileDeletePod(t *testing.T) { } workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) - loadResource(t, ctx, resourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, ctx, resourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) - loadResource(t, ctx, resourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) - loadResource(t, ctx, resourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) + loadResource(t, ctx, testClient.ResourceClient, workloadID, tc.existingWorkload, nil) + loadResource(t, ctx, testClient.ResourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) + loadResource(t, ctx, testClient.ResourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) + loadResource(t, ctx, testClient.ResourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) var token *api.ACLToken + var err error if tc.aclsEnabled { test.SetupK8sAuthMethodV2(t, testClient.APIClient, tc.podName, metav1.NamespaceDefault) //podName is a standin for the service name token, _, err = testClient.APIClient.ACL().Login(&api.ACLLoginParams{ @@ -1822,16 +1735,16 @@ func TestReconcileDeletePod(t *testing.T) { require.False(t, resp.Requeue) wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, ctx, resourceClient, wID, tc.expectedWorkload) + expectedWorkloadMatches(t, ctx, testClient.ResourceClient, wID, tc.expectedWorkload) hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, ctx, resourceClient, hsID, tc.expectedHealthStatus) + expectedHealthStatusMatches(t, ctx, testClient.ResourceClient, hsID, tc.expectedHealthStatus) pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, ctx, resourceClient, pcID, tc.expectedProxyConfiguration) + expectedProxyConfigurationMatches(t, ctx, testClient.ResourceClient, pcID, tc.expectedProxyConfiguration) uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, ctx, resourceClient, uID, tc.expectedDestinations) + expectedDestinationMatches(t, ctx, testClient.ResourceClient, uID, tc.expectedDestinations) if tc.aclsEnabled { _, _, err = testClient.APIClient.ACL().TokenRead(token.AccessorID, nil) diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go index d2ea94c22d..27bb909d2c 100644 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go +++ b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go @@ -6,7 +6,6 @@ package serviceaccount import ( "context" "testing" - "time" "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/proto" @@ -30,7 +29,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api/common" inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -226,13 +224,6 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { DenyK8sNamespacesSet: mapset.NewSetWith(), }, } - resourceClient, err := consul.NewResourceServiceClient(sa.ConsulServerConnMgr) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) // Default ns and partition if not specified in test. if tc.targetConsulNs == "" { @@ -245,9 +236,9 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { // If existing resource specified, create it and ensure it exists. if tc.existingResource != nil { writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} - _, err = resourceClient.Write(context.Background(), writeReq) + _, err := testClient.ResourceClient.Write(context.Background(), writeReq) require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), resourceClient, tc.existingResource.Id) + test.ResourceHasPersisted(t, context.Background(), testClient.ResourceClient, tc.existingResource.Id) } // Run actual reconcile and verify results. @@ -264,7 +255,7 @@ func runReconcileCase(t *testing.T, tc reconcileCase) { } require.False(t, resp.Requeue) - expectedWorkloadIdentityMatches(t, resourceClient, tc.svcAccountName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) + expectedWorkloadIdentityMatches(t, testClient.ResourceClient, tc.svcAccountName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) } func expectedWorkloadIdentityMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index 3a80232a23..df51927e4c 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -20,24 +20,31 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/protobuf/testing/protocmp" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" ) const ( componentAuthMethod = "consul-k8s-component-auth-method" + eventuallyWaitFor = 5 * time.Second + eventuallyTickEvery = 100 * time.Millisecond ) type TestServerClient struct { - TestServer *testutil.TestServer - APIClient *api.Client - Cfg *consul.Config - Watcher consul.ServerConnectionManager + TestServer *testutil.TestServer + APIClient *api.Client + Cfg *consul.Config + Watcher consul.ServerConnectionManager + ResourceClient pbresource.ResourceServiceClient } func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConfigCallback) *TestServerClient { @@ -66,11 +73,24 @@ func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConf client, err := api.NewClient(consulConfig.APIClientConfig) require.NoError(t, err) + requireACLBootstrapped(t, cfg, client) + watcher := MockConnMgrForIPAndPort(t, "127.0.0.1", cfg.Ports.GRPC, true) + + // Create a gRPC resource service client when the resource-apis experiment is enabled. + var resourceClient pbresource.ResourceServiceClient + if slices.Contains(cfg.Experiments, "resource-apis") { + resourceClient, err = consul.NewResourceServiceClient(watcher) + require.NoError(t, err) + } + + requireTenancyBuiltins(t, cfg, client, resourceClient) + return &TestServerClient{ - TestServer: consulServer, - APIClient: client, - Cfg: consulConfig, - Watcher: MockConnMgrForIPAndPort(t, "127.0.0.1", cfg.Ports.GRPC, true), + TestServer: consulServer, + APIClient: client, + Cfg: consulConfig, + Watcher: watcher, + ResourceClient: resourceClient, } } @@ -397,3 +417,69 @@ w7/VeA7lzmj3TQRE/W0U0ZGeoAxn9b6JtT0iMucYvP0hXKTPBWlnzIijamU50r2Y Z23jGuk6rn9DUHC2xPj3wCTmd8SGEJoV31noJV5dVeQ90wusXz3vTG7ficKnvHFS xtr5PSwH1DusYfVaGH2O -----END CERTIFICATE-----` + +func requireTenancyBuiltins(t *testing.T, cfg *testutil.TestServerConfig, client *api.Client, resourceClient pbresource.ResourceServiceClient) { + t.Helper() + + // There is a window of time post-leader election on startup where v2 tenancy builtins + // (default partition and namespace) have not yet been created. + // Wait for them to exist before considering the server "open for business". + // Only check for default namespace existence since it implies the default partition exists. + if slices.Contains(cfg.Experiments, "v2tenancy") { + require.EventuallyWithT(t, func(c *assert.CollectT) { + _, err := resourceClient.Read(context.Background(), &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: constants.DefaultConsulNS, + Type: pbtenancy.NamespaceType, + Tenancy: &pbresource.Tenancy{Partition: constants.DefaultConsulPartition}, + }, + }) + assert.NoError(c, err) + }, + eventuallyWaitFor, + eventuallyTickEvery, + "failed to eventually read v2 builtin default namespace", + ) + } else { + // Do the same for V1 counterparts in ent only to prevent known test flakes. + require.Eventually(t, + func() bool { + self, err := client.Agent().Self() + if err != nil { + return false + } + if self["DebugConfig"]["VersionMetadata"] != "ent" { + return true + } + + // Check for the default partition instead of the default namespace since this is a thing: + // error="Namespaces are currently disabled until all servers in the datacenter supports the feature" + partition, _, err := client.Partitions().Read( + context.Background(), + constants.DefaultConsulPartition, + nil, + ) + return err == nil && partition != nil + }, + eventuallyWaitFor, + eventuallyTickEvery, + "failed to eventually read v1 builtin default partition") + } +} + +func requireACLBootstrapped(t *testing.T, cfg *testutil.TestServerConfig, client *api.Client) { + t.Helper() + + // Prevent test flakes due to "ACL system must be bootstrapped before ..." error + // by requiring successful retrieval of the initial mgmt token. + if cfg.ACL.Enabled && cfg.ACL.Tokens.InitialManagement != "" { + require.EventuallyWithT(t, func(c *assert.CollectT) { + _, _, err := client.ACL().TokenReadSelf(nil) + assert.NoError(c, err) + }, + eventuallyWaitFor, + eventuallyTickEvery, + "failed to eventually read self token as a proxy for the ACL system bootstrap completion", + ) + } +} diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index f51a97df18..4515a13b21 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -58,6 +58,7 @@ type Command struct { flagLogLevel string flagLogJSON bool flagResourceAPIs bool // Use V2 APIs + flagV2Tenancy bool // Use V2 partitions (ent only) and namespaces instead of V1 counterparts flagAllowK8sNamespacesList []string // K8s namespaces to explicitly inject flagDenyK8sNamespacesList []string // K8s namespaces to deny injection (has precedence) @@ -240,6 +241,8 @@ func (c *Command) init() { "Enable or disable JSON output format for logging.") c.flagSet.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, "Enable or disable Consul V2 Resource APIs.") + c.flagSet.BoolVar(&c.flagV2Tenancy, "enable-v2tenancy", false, + "Enable or disable Consul V2 tenancy.") // Proxy sidecar resource setting flags. c.flagSet.StringVar(&c.flagDefaultSidecarProxyCPURequest, "default-sidecar-proxy-cpu-request", "", "Default sidecar proxy CPU request.") @@ -428,6 +431,19 @@ func (c *Command) validateFlags() error { return errors.New("-consul-dataplane-image must be set") } + // In Consul 1.17, multiport beta shipped with v2 catalog + mesh resources backed by v1 tenancy + // and acls (experiments=[resource-apis]). + // + // With Consul 1.18, we built out v2 tenancy with no support for acls, hence need to be explicit + // about which combination of v1 + v2 features are enabled. + // + // To summarize: + // - experiments=[resource-apis] => v2 catalog and mesh + v1 tenancy and acls + // - experiments=[resource-apis, v2tenancy] => v2 catalog and mesh + v2 tenancy + acls disabled + if c.flagV2Tenancy && !c.flagResourceAPIs { + return errors.New("-enable-resource-apis must be set to 'true' if -enable-v2tenancy is set") + } + if c.flagEnablePartitions && c.consul.Partition == "" { return errors.New("-partition must set if -enable-partitions is set to 'true'") } diff --git a/control-plane/subcommand/inject-connect/command_test.go b/control-plane/subcommand/inject-connect/command_test.go index 9c64020376..e7ca3f12cd 100644 --- a/control-plane/subcommand/inject-connect/command_test.go +++ b/control-plane/subcommand/inject-connect/command_test.go @@ -132,6 +132,15 @@ func TestRun_FlagValidation(t *testing.T) { }, expErr: "-default-envoy-proxy-concurrency must be >= 0 if set", }, + { + flags: []string{ + "-consul-k8s-image", "hashicorp/consul-k8s", + "-consul-image", "hashicorp/consul", + "-consul-dataplane-image", "hashicorp/consul-dataplane", + "-enable-v2tenancy", "true", + }, + expErr: "-enable-resource-apis must be set to 'true' if -enable-v2tenancy is set", + }, } for _, c := range cases { diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index ba7134211e..c3cf57419c 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -6,13 +6,10 @@ package connectinject import ( "context" - "github.com/hashicorp/consul-server-connection-manager/discovery" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" - "github.com/hashicorp/consul-k8s/control-plane/gateways" - authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/common" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" @@ -25,7 +22,10 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/namespace" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhookv2" + "github.com/hashicorp/consul-k8s/control-plane/gateways" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + namespacev2 "github.com/hashicorp/consul-k8s/control-plane/tenancy/namespace" + "github.com/hashicorp/consul-server-connection-manager/discovery" ) func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { @@ -110,23 +110,38 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if c.flagEnableNamespaces { - err := (&namespace.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - Log: ctrl.Log.WithName("controller").WithName("namespace"), + if c.flagV2Tenancy { + // V2 tenancy implies non-default namespaces in CE, so we don't observe flagEnableNamespaces + err := (&namespacev2.Controller{ + Client: mgr.GetClient(), + ConsulServerConnMgr: watcher, + K8sNamespaceConfig: k8sNsConfig, + ConsulTenancyConfig: consulTenancyConfig, + Log: ctrl.Log.WithName("controller").WithName("namespacev2"), }).SetupWithManager(mgr) if err != nil { - setupLog.Error(err, "unable to create controller", "controller", namespace.Controller{}) + setupLog.Error(err, "unable to create controller", "controller", "namespacev2") return err } + } else { + if c.flagEnableNamespaces { + err := (&namespace.Controller{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + Log: ctrl.Log.WithName("controller").WithName("namespace"), + }).SetupWithManager(mgr) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", namespace.Controller{}) + return err + } + } } consulResourceController := &controllersv2.ConsulResourceController{ diff --git a/control-plane/subcommand/mesh-init/command_ent_test.go b/control-plane/subcommand/mesh-init/command_ent_test.go index ad3ea8c87d..59c710f6eb 100644 --- a/control-plane/subcommand/mesh-init/command_ent_test.go +++ b/control-plane/subcommand/mesh-init/command_ent_test.go @@ -16,7 +16,6 @@ import ( "github.com/stretchr/testify/require" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) @@ -52,10 +51,8 @@ func TestRun_WithNamespaces(t *testing.T) { c.Experiments = []string{"resource-apis"} serverCfg = c }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - _, err = EnsurePartitionExists(testClient.APIClient, c.consulPartition) + _, err := EnsurePartitionExists(testClient.APIClient, c.consulPartition) require.NoError(t, err) partitionedCfg := testClient.Cfg.APIClientConfig @@ -68,7 +65,7 @@ func TestRun_WithNamespaces(t *testing.T) { require.NoError(t, err) // Register Consul workload. - loadResource(t, resourceClient, getWorkloadID(testPodName, c.consulNamespace, c.consulPartition), getWorkload(), nil) + loadResource(t, testClient.ResourceClient, getWorkloadID(testPodName, c.consulNamespace, c.consulPartition), getWorkload(), nil) ui := cli.NewMockUi() cmd := Command{ diff --git a/control-plane/subcommand/mesh-init/command_test.go b/control-plane/subcommand/mesh-init/command_test.go index 87f38bf011..90756cee70 100644 --- a/control-plane/subcommand/mesh-init/command_test.go +++ b/control-plane/subcommand/mesh-init/command_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/types/known/anypb" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -100,11 +99,9 @@ func TestRun_MeshServices(t *testing.T) { c.Experiments = []string{"resource-apis"} serverCfg = c }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.workload, nil) - loadResource(t, resourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.proxyConfiguration, nil) + loadResource(t, testClient.ResourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.workload, nil) + loadResource(t, testClient.ResourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.proxyConfiguration, nil) ui := cli.NewMockUi() cmd := Command{ @@ -164,8 +161,6 @@ func TestRun_RetryServicePolling(t *testing.T) { c.Experiments = []string{"resource-apis"} serverCfg = c }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) // Start the consul service registration in a go func and delay it so that it runs // after the cmd.Run() starts. @@ -176,7 +171,7 @@ func TestRun_RetryServicePolling(t *testing.T) { // Wait a moment, this ensures that we are already in the retry logic. time.Sleep(time.Second * 2) // Register counting service. - loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) + loadResource(t, testClient.ResourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) }() ui := cli.NewMockUi() @@ -240,16 +235,14 @@ func TestRun_TrafficRedirection(t *testing.T) { c.Experiments = []string{"resource-apis"} serverCfg = c }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) // Add additional proxy configuration either to a config entry or to the service itself. if c.registerProxyConfiguration { - loadResource(t, resourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getProxyConfiguration(), nil) + loadResource(t, testClient.ResourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getProxyConfiguration(), nil) } // Register Consul workload. - loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) + loadResource(t, testClient.ResourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) iptablesProvider := &fakeIptablesProvider{} iptablesCfg := iptables.Config{ diff --git a/control-plane/subcommand/partition-init/command_ent_test.go b/control-plane/subcommand/partition-init/command_ent_test.go index 72858243f4..21972a5a7a 100644 --- a/control-plane/subcommand/partition-init/command_ent_test.go +++ b/control-plane/subcommand/partition-init/command_ent_test.go @@ -21,7 +21,6 @@ import ( pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -96,10 +95,7 @@ func TestRun_PartitionCreate(t *testing.T) { v2tenancy: true, experiments: []string{"resource-apis", "v2tenancy"}, requirePartitionCreated: func(testClient *test.TestServerClient) { - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - _, err = resourceClient.Read(context.Background(), &pbresource.ReadRequest{ + _, err := testClient.ResourceClient.Read(context.Background(), &pbresource.ReadRequest{ Id: &pbresource.ID{ Name: partitionName, Type: pbtenancy.PartitionType, @@ -180,13 +176,10 @@ func TestRun_PartitionExists(t *testing.T) { v2tenancy: true, experiments: []string{"resource-apis", "v2tenancy"}, preCreatePartition: func(testClient *test.TestServerClient) { - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - data, err := anypb.New(&pbtenancy.Partition{Description: partitionDesc}) require.NoError(t, err) - _, err = resourceClient.Write(context.Background(), &pbresource.WriteRequest{ + _, err = testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{ Resource: &pbresource.Resource{ Id: &pbresource.ID{ Name: partitionName, @@ -198,10 +191,7 @@ func TestRun_PartitionExists(t *testing.T) { require.NoError(t, err) }, requirePartitionNotCreated: func(testClient *test.TestServerClient) { - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - rsp, err := resourceClient.Read(context.Background(), &pbresource.ReadRequest{ + rsp, err := testClient.ResourceClient.Read(context.Background(), &pbresource.ReadRequest{ Id: &pbresource.ID{ Name: partitionName, Type: pbtenancy.PartitionType, diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index b33e4beb60..e28af9ef35 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -1449,9 +1449,7 @@ func partitionedSetup(t *testing.T, bootToken string, partitionName string) *tes server.Cfg.APIClientConfig.Token = bootToken serverAPIClient, err := consul.NewClient(server.Cfg.APIClientConfig, 5*time.Second) require.NoError(t, err) - _, _, err = serverAPIClient.Partitions().Create(context.Background(), &api.Partition{Name: partitionName}, &api.WriteOptions{}) require.NoError(t, err) - return server.TestServer } diff --git a/control-plane/tenancy/namespace/namespace.go b/control-plane/tenancy/namespace/namespace.go new file mode 100644 index 0000000000..84604e37b2 --- /dev/null +++ b/control-plane/tenancy/namespace/namespace.go @@ -0,0 +1,114 @@ +package namespace + +import ( + "context" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul/proto-public/pbresource" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" +) + +// DeletionTimestampKey is the key in a resource's metadata that stores the timestamp +// when a resource was marked for deletion. This only applies to resources with finalizers. +const DeletionTimestampKey = "deletionTimestamp" + +// EnsureDeleted ensures a Consul namespace with name ns in partition ap is deleted or is in the +// process of being deleted. If neither, it will mark it for deletion. +func EnsureDeleted(ctx context.Context, client pbresource.ResourceServiceClient, ap, ns string) error { + if ns == common.WildcardNamespace || ns == common.DefaultNamespaceName { + return nil + } + + // Check if the Consul namespace exists. + rsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: &pbresource.ID{ + Name: ns, + Type: pbtenancy.NamespaceType, + Tenancy: &pbresource.Tenancy{Partition: ap}, + }}) + + switch { + case status.Code(err) == codes.NotFound: + // Nothing to do + return nil + case err != nil: + // Unexpected error + return fmt.Errorf("namespace read failed: %w", err) + case isMarkedForDeletion(rsp.Resource): + // Deletion already in progress, nothing to do + return nil + default: + // Namespace found, so non-CAS delete it. + _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: rsp.Resource.Id, Version: ""}) + if err != nil { + return fmt.Errorf("namespace delete failed: %w", err) + } + return nil + } +} + +// EnsureExists ensures a Consul namespace with name ns exists and is not marked +// for deletion. If it doesn't, exist it will create it. If it is marked for deletion, +// returns an error. +// +// Boolean return value indicates if the namespace was created by this call. +func EnsureExists(ctx context.Context, client pbresource.ResourceServiceClient, ap, ns string) (bool, error) { + if ns == common.WildcardNamespace || ns == common.DefaultNamespaceName { + return false, nil + } + + // Check if the Consul namespace exists. + rsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: &pbresource.ID{ + Name: ns, + Type: pbtenancy.NamespaceType, + Tenancy: &pbresource.Tenancy{Partition: ap}, + }}) + + switch { + case err == nil && isMarkedForDeletion(rsp.Resource): + // Found, but delete in progress + return false, fmt.Errorf("consul namespace %q deletion in progress", ns) + case err == nil: + // Found and not marked for deletion, nothing to do + return false, nil + case status.Code(err) != codes.NotFound: + // Unexpected error + return false, fmt.Errorf("consul namespace read failed: %w", err) + } + + // Consul namespace not found, so create it + // TODO: Handle creation of crossNSACLPolicy when V2 ACLs are supported + nsData, err := anypb.New(&pbtenancy.Namespace{Description: "Auto-generated by consul-k8s"}) + if err != nil { + return false, err + } + + _, err = client.Write(ctx, &pbresource.WriteRequest{Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: ns, + Type: pbtenancy.NamespaceType, + Tenancy: &pbresource.Tenancy{Partition: ap}, + }, + Metadata: map[string]string{"external-source": "kubernetes"}, + Data: nsData, + }}) + + if err != nil { + return false, fmt.Errorf("consul namespace creation failed: %w", err) + } + return true, nil +} + +// isMarkedForDeletion returns true if a resource has been marked for deletion, +// false otherwise. +func isMarkedForDeletion(res *pbresource.Resource) bool { + if res.Metadata == nil { + return false + } + _, ok := res.Metadata[DeletionTimestampKey] + return ok +} diff --git a/control-plane/tenancy/namespace/namespace_controller.go b/control-plane/tenancy/namespace/namespace_controller.go new file mode 100644 index 0000000000..e08951b61c --- /dev/null +++ b/control-plane/tenancy/namespace/namespace_controller.go @@ -0,0 +1,95 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package namespace + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + injectcommon "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/consul" +) + +// Namespace syncing between K8s and Consul is vastly simplified when V2 tenancy is enabled. +// Put simply, a K8s namespace maps 1:1 to a Consul namespace of the same name and that is +// the only supported behavior. +// +// The plethora of configuration options available when using V1 tenancy have been removed +// to simplify the user experience and mapping rules. +// +// Hence, the following V1 tenancy namespace helm configuration values are ignored: +// - global.enableConsulNamespaces +// - connectInject.consulNamespaces.consulDestinationNamespace +// - connectInject.consulNamespaces.mirroringK8S +// - connectInject.consulNamespaces.mirroringK8SPrefix. +type Controller struct { + client.Client + // ConsulServerConnMgr is the watcher for the Consul server addresses. + ConsulServerConnMgr consul.ServerConnectionManager + // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. + common.K8sNamespaceConfig + // ConsulTenancyConfig contains the destination partition. + common.ConsulTenancyConfig + Log logr.Logger +} + +// Reconcile reads a Kubernetes Namespace and reconciles the mapped namespace in Consul. +func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var namespace corev1.Namespace + + // Ignore the request if the namespace should not be synced to consul. + if injectcommon.ShouldIgnore(req.Name, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + return ctrl.Result{}, nil + } + + // Create a gRPC resource service client + resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) + if err != nil { + r.Log.Error(err, "failed to create Consul resource service client", "name", req.Name) + return ctrl.Result{}, err + } + + // Target consul tenancy + consulAP := r.ConsulPartition + consulNS := req.Name + + // Re-read the k8s namespace object + err = r.Client.Get(ctx, req.NamespacedName, &namespace) + + // If the namespace object has been deleted (we get an IsNotFound error), + // we need to remove the Namespace from Consul. + if k8serrors.IsNotFound(err) { + if err := EnsureDeleted(ctx, resourceClient, consulAP, consulNS); err != nil { + return ctrl.Result{}, fmt.Errorf("error deleting consul namespace: %w", err) + } + + return ctrl.Result{}, nil + } else if err != nil { + r.Log.Error(err, "failed to get k8s namespace", "name", req.Name) + return ctrl.Result{}, err + } + + // k8s namespace found, so make sure it is mapped correctly and exists in Consul. + r.Log.Info("retrieved", "k8s namespace", namespace.GetName()) + + if _, err := EnsureExists(ctx, resourceClient, consulAP, consulNS); err != nil { + r.Log.Error(err, "error checking or creating consul namespace", "namespace", consulNS) + return ctrl.Result{}, fmt.Errorf("error checking or creating consul namespace: %w", err) + } + return ctrl.Result{}, nil +} + +// SetupWithManager registers this controller with the manager. +func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Namespace{}). + Complete(r) +} diff --git a/control-plane/tenancy/namespace/namespace_controller_ent_test.go b/control-plane/tenancy/namespace/namespace_controller_ent_test.go new file mode 100644 index 0000000000..997164d638 --- /dev/null +++ b/control-plane/tenancy/namespace/namespace_controller_ent_test.go @@ -0,0 +1,35 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package namespace + +import ( + "testing" +) + +func TestReconcileCreateNamespace_ENT(t *testing.T) { + testCases := []createTestCase{ + { + name: "consul namespace is ap1/ns1", + kubeNamespace: "ns1", + partition: "ap1", + expectedConsulNamespace: "ns1", + }, + } + testReconcileCreateNamespace(t, testCases) +} + +func TestReconcileDeleteNamespace_ENT(t *testing.T) { + testCases := []deleteTestCase{ + { + name: "non-default partition", + kubeNamespace: "ns1", + partition: "ap1", + existingConsulNamespace: "ns1", + expectNamespaceDeleted: "ns1", + }, + } + testReconcileDeleteNamespace(t, testCases) +} diff --git a/control-plane/tenancy/namespace/namespace_controller_test.go b/control-plane/tenancy/namespace/namespace_controller_test.go new file mode 100644 index 0000000000..b9d1cd8728 --- /dev/null +++ b/control-plane/tenancy/namespace/namespace_controller_test.go @@ -0,0 +1,301 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package namespace + +import ( + "context" + "testing" + "time" + + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul/proto-public/pbresource" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" + "github.com/hashicorp/consul/sdk/testutil" +) + +func TestReconcileCreateNamespace(t *testing.T) { + testCases := []createTestCase{ + { + name: "consul namespace is default/ns1", + kubeNamespace: "ns1", + partition: constants.DefaultConsulPartition, + expectedConsulNamespace: "ns1", + }, + } + testReconcileCreateNamespace(t, testCases) +} + +type createTestCase struct { + name string + kubeNamespace string + partition string + expectedConsulNamespace string +} + +// testReconcileCreateNamespace ensures that a new k8s namespace is reconciled to a +// Consul namespace. The actual namespace in Consul depends on if the controller +// is configured with a destination namespace or mirroring enabled. +func testReconcileCreateNamespace(t *testing.T, testCases []createTestCase) { + run := func(t *testing.T, tc createTestCase) { + // Create the default kube namespace and kube namespace under test. + kubeNS := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: tc.kubeNamespace}} + kubeDefaultNS := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: metav1.NamespaceDefault}} + kubeObjects := []runtime.Object{ + &kubeNS, + &kubeDefaultNS, + } + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(kubeObjects...).Build() + + // Fire up consul server with v2tenancy enabled + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis", "v2tenancy"} + }) + + // Create partition if needed + testClient.Cfg.APIClientConfig.Partition = tc.partition + if tc.partition != "" && tc.partition != "default" { + _, err := testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: tc.partition, + Type: pbtenancy.PartitionType, + }, + }}) + require.NoError(t, err, "failed to create partition") + } + + // Create the namespace controller injecting config from tc + nc := &Controller{ + Client: fakeClient, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ConsulTenancyConfig: common.ConsulTenancyConfig{ + ConsulPartition: tc.partition, + }, + Log: logrtest.New(t), + } + + // Reconcile the kube namespace under test + resp, err := nc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: tc.kubeNamespace, + }, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Verify consul namespace exists or was created during reconciliation + _, err = testClient.ResourceClient.Read(context.Background(), &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: tc.expectedConsulNamespace, + Type: pbtenancy.NamespaceType, + Tenancy: &pbresource.Tenancy{Partition: tc.partition}, + }, + }) + require.NoError(t, err, "expected partition/namespace %s/%s to exist", tc.partition, tc.expectedConsulNamespace) + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +func TestReconcileDeleteNamespace(t *testing.T) { + testCases := []deleteTestCase{ + { + name: "consul namespace ns1", + kubeNamespace: "ns1", + partition: "default", + existingConsulNamespace: "ns1", + expectNamespaceDeleted: "ns1", + }, + { + name: "consul default namespace does not get deleted", + kubeNamespace: metav1.NamespaceDefault, + partition: "default", + existingConsulNamespace: "", + expectNamespaceExists: "default", + }, + { + name: "namespace is already removed from Consul", + kubeNamespace: "ns1", + partition: "default", + existingConsulNamespace: "", // don't pre-create consul namespace + expectNamespaceDeleted: "ns1", // read as "was never created" + }, + } + testReconcileDeleteNamespace(t, testCases) +} + +type deleteTestCase struct { + name string + kubeNamespace string + partition string + existingConsulNamespace string // If non-empty, this namespace is created in consul pre-reconcile + + // Pick one + expectNamespaceExists string // If non-empty, this namespace should exist in consul post-reconcile + expectNamespaceDeleted string // If non-empty, this namespace should not exist in consul post-reconcile +} + +// Tests deleting a Namespace object, with and without matching Consul namespace. +func testReconcileDeleteNamespace(t *testing.T, testCases []deleteTestCase) { + run := func(t *testing.T, tc deleteTestCase) { + // Don't seed with any kube namespaces since we're testing deletion. + fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() + + // Fire up consul server with v2tenancy enabled + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis", "v2tenancy"} + }) + + // Create partition if needed + testClient.Cfg.APIClientConfig.Partition = tc.partition + if tc.partition != "" && tc.partition != "default" { + _, err := testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Name: tc.partition, + Type: pbtenancy.PartitionType, + }, + }}) + require.NoError(t, err, "failed to create partition") + } + + // Create the consul namespace if needed + if tc.existingConsulNamespace != "" && tc.existingConsulNamespace != "default" { + id := &pbresource.ID{ + Name: tc.existingConsulNamespace, + Type: pbtenancy.NamespaceType, + Tenancy: &pbresource.Tenancy{Partition: tc.partition}, + } + + rsp, err := testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{Id: id}}) + require.NoError(t, err, "failed to create namespace") + + // TODO: Remove after https://hashicorp.atlassian.net/browse/NET-6719 implemented + requireEventuallyAccepted(t, testClient.ResourceClient, rsp.Resource.Id) + } + + // Create the namespace controller. + nc := &Controller{ + Client: fakeClient, + ConsulServerConnMgr: testClient.Watcher, + K8sNamespaceConfig: common.K8sNamespaceConfig{ + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + }, + ConsulTenancyConfig: common.ConsulTenancyConfig{ + ConsulPartition: tc.partition, + }, + Log: logrtest.New(t), + } + + // Reconcile the kube namespace under test - imagine it has just been deleted + resp, err := nc.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: tc.kubeNamespace, + }, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Verify appropriate action was taken on the counterpart consul namespace + if tc.expectNamespaceExists != "" { + // Verify consul namespace was not deleted + _, err = testClient.ResourceClient.Read(context.Background(), &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: tc.expectNamespaceExists, + Type: pbtenancy.NamespaceType, + Tenancy: &pbresource.Tenancy{Partition: tc.partition}, + }, + }) + require.NoError(t, err, "expected partition/namespace %s/%s to exist", tc.partition, tc.expectNamespaceExists) + } else if tc.expectNamespaceDeleted != "" { + // Verify consul namespace was deleted + id := &pbresource.ID{ + Name: tc.expectNamespaceDeleted, + Type: pbtenancy.NamespaceType, + Tenancy: &pbresource.Tenancy{Partition: tc.partition}, + } + requireEventuallyNotFound(t, testClient.ResourceClient, id) + } else { + panic("tc.expectedNamespaceExists or tc.expectedNamespaceDeleted must be set") + } + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} + +// RequireStatusAccepted waits for a recently created resource to have a resource status of accepted so that +// attempts to delete it by the single-shot controller under test's reconcile will not fail with a CAS error. +// +// Remove refs to this after https://hashicorp.atlassian.net/browse/NET-6719 is implemented. +func requireEventuallyAccepted(t *testing.T, resourceClient pbresource.ResourceServiceClient, id *pbresource.ID) { + require.Eventuallyf(t, + func() bool { + rsp, err := resourceClient.Read(context.Background(), &pbresource.ReadRequest{Id: id}) + if err != nil { + return false + } + if rsp.Resource.Status == nil || len(rsp.Resource.Status) == 0 { + return false + } + + for _, status := range rsp.Resource.Status { + for _, condition := range status.Conditions { + // common.ConditionAccepted in consul namespace controller + if condition.Type == "accepted" && condition.State == pbresource.Condition_STATE_TRUE { + return true + } + } + } + return false + }, + time.Second*5, + time.Millisecond*100, + "timed out out waiting for %s to have status accepted", + id, + ) +} + +func requireEventuallyNotFound(t *testing.T, resourceClient pbresource.ResourceServiceClient, id *pbresource.ID) { + // allow both "not found" and "marked for deletion" so we're not waiting around unnecessarily + require.Eventuallyf(t, func() bool { + rsp, err := resourceClient.Read(context.Background(), &pbresource.ReadRequest{Id: id}) + if err == nil { + return isMarkedForDeletion(rsp.Resource) + } + if status.Code(err) == codes.NotFound { + return true + } + return false + }, + time.Second*5, + time.Millisecond*100, + "timed out waiting for %s to not be found", + id, + ) +} From a5fe4104f0f21bec3774398fa2d175b3ebd61def Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:16:58 -0600 Subject: [PATCH 568/592] Net 6289 improve consul api gateway annotations (#3437) * Add api-gateway to allow list for gateway-kind annotation * Update godoc for AnnotationGatewayKind to list correct allowable values * Exclude api-gateway kind when checking if endpoints controller should act on gateway * Add gateway-kind and gateway-consul-service-name annotations to API gateway pods * Add appropriate component label to api-gateway pods * Update consul-k8s CLI to rely on standard component label for api-gateway This makes api-gateway behave the same as ingress, mesh and terminating gateways; however, it won't pick up pods created by the legacy consul-api-gateway controller. * add backwards compatabiilty, additional tests * add changelog * Update .changelog/3437.txt Co-authored-by: Nathan Coleman * Update cli/cmd/proxy/list/command.go Co-authored-by: Nathan Coleman * Update cli/cmd/proxy/list/command_test.go Co-authored-by: Nathan Coleman * Update cli/cmd/proxy/list/command_test.go Co-authored-by: Nathan Coleman * Update cli/cmd/proxy/list/command.go Co-authored-by: Nathan Coleman * cleanup * Update cli/cmd/proxy/list/command.go Co-authored-by: Nathan Coleman --------- Co-authored-by: Nathan Coleman --- .changelog/3437.txt | 3 + cli/cmd/proxy/list/command.go | 61 ++++++++++++++----- cli/cmd/proxy/list/command_test.go | 44 ++++++++++++- control-plane/api-gateway/common/labels.go | 2 + .../api-gateway/gatekeeper/deployment.go | 13 ++-- .../api-gateway/gatekeeper/gatekeeper_test.go | 8 ++- .../constants/annotations_and_labels.go | 3 +- .../endpoints/endpoints_controller.go | 12 ++-- 8 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 .changelog/3437.txt diff --git a/.changelog/3437.txt b/.changelog/3437.txt new file mode 100644 index 0000000000..3e70f0d4c0 --- /dev/null +++ b/.changelog/3437.txt @@ -0,0 +1,3 @@ +```release-note:bug-fix +api-gateways: API Gateway pods now include `gateway-kind` and `gateway-consul-service-name` annotations consistent with other Consul gateway types. +``` \ No newline at end of file diff --git a/cli/cmd/proxy/list/command.go b/cli/cmd/proxy/list/command.go index 7159b448b8..0204832c44 100644 --- a/cli/cmd/proxy/list/command.go +++ b/cli/cmd/proxy/list/command.go @@ -10,15 +10,16 @@ import ( "strings" "sync" - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" "github.com/posener/complete" 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/client-go/kubernetes" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" ) const ( @@ -211,21 +212,51 @@ func (c *ListCommand) fetchPods() ([]v1.Pod, error) { // 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 (ingress-gateway, mesh-gateway, terminating-gateway), chart=consul-helm", + LabelSelector: "component in (api-gateway, ingress-gateway, mesh-gateway, terminating-gateway), chart=consul-helm", }) if err != nil { return nil, err } pods = append(pods, gatewaypods.Items...) - // Fetch all pods in the namespace with a label indicating they are an API gateway. + // 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", }) + + namespacedName := func(pod v1.Pod) string { + return pod.Namespace + pod.Name + } if err != nil { return nil, err } - pods = append(pods, apigatewaypods.Items...) + 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) + } + + } + } + //--- // 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{ @@ -268,22 +299,22 @@ func (c *ListCommand) output(pods []v1.Pod) { // Get the type for ingress, mesh, and terminating gateways. switch pod.Labels["component"] { + case "api-gateway": + proxyType = "API Gateway" case "ingress-gateway": proxyType = "Ingress Gateway" case "mesh-gateway": proxyType = "Mesh Gateway" case "terminating-gateway": proxyType = "Terminating Gateway" - } - - // Determine if the pod is an API Gateway. - if pod.Labels["api-gateway.consul.hashicorp.com/managed"] == "true" { - proxyType = "API Gateway" - } - - // Fallback to "Sidecar" as a default - if proxyType == "" { + default: + // Fallback to "Sidecar" as a default proxyType = "Sidecar" + + // Determine if deprecated API Gateway pod. + if pod.Labels["api-gateway.consul.hashicorp.com/managed"] == "true" { + proxyType = "API Gateway" + } } if c.flagAllNamespaces { diff --git a/cli/cmd/proxy/list/command_test.go b/cli/cmd/proxy/list/command_test.go index 9e7104886d..5493ab88a9 100644 --- a/cli/cmd/proxy/list/command_test.go +++ b/cli/cmd/proxy/list/command_test.go @@ -274,12 +274,33 @@ func TestListCommandOutput(t *testing.T) { }, }, }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "depricated-api-gateway", + Namespace: "consul", + Labels: map[string]string{ + "api-gateway.consul.hashicorp.com/managed": "true", + }, + }, + }, { ObjectMeta: metav1.ObjectMeta{ Name: "api-gateway", Namespace: "consul", + Labels: map[string]string{ + "component": "api-gateway", + "chart": "consul-helm", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "both-labels-api-gateway", + Namespace: "consul", Labels: map[string]string{ "api-gateway.consul.hashicorp.com/managed": "true", + "component": "api-gateway", + "chart": "consul-helm", }, }, }, @@ -321,7 +342,7 @@ func TestListCommandOutput(t *testing.T) { func TestListCommandOutputInJsonFormat(t *testing.T) { // These regular expressions must be present in the output. - expected := ".*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.*api-gateway.*\n.*Namespace.*consul.*\n.*Type.*API Gateway.*\n.*\n.*\n.*Name.*pod1.*\n.*Namespace.*default.*\n.*Type.*Sidecar.*" + 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{ @@ -359,6 +380,27 @@ func TestListCommandOutputInJsonFormat(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "api-gateway", Namespace: "consul", + Labels: map[string]string{ + "component": "api-gateway", + "chart": "consul-helm", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "both-labels-api-gateway", + Namespace: "consul", + Labels: map[string]string{ + "api-gateway.consul.hashicorp.com/managed": "true", + "component": "api-gateway", + "chart": "consul-helm", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "deprecated-api-gateway", + Namespace: "consul", Labels: map[string]string{ "api-gateway.consul.hashicorp.com/managed": "true", }, diff --git a/control-plane/api-gateway/common/labels.go b/control-plane/api-gateway/common/labels.go index cba13a603e..06f7857c30 100644 --- a/control-plane/api-gateway/common/labels.go +++ b/control-plane/api-gateway/common/labels.go @@ -12,6 +12,7 @@ import ( ) const ( + componentLabel = "component" nameLabel = "gateway.consul.hashicorp.com/name" namespaceLabel = "gateway.consul.hashicorp.com/namespace" createdAtLabel = "gateway.consul.hashicorp.com/created" @@ -21,6 +22,7 @@ const ( // LabelsForGateway formats the default labels that appear on objects managed by the controllers. func LabelsForGateway(gateway *gwv1beta1.Gateway) map[string]string { return map[string]string{ + componentLabel: "api-gateway", nameLabel: gateway.Name, namespaceLabel: gateway.Namespace, createdAtLabel: fmt.Sprintf("%d", gateway.CreationTimestamp.Unix()), diff --git a/control-plane/api-gateway/gatekeeper/deployment.go b/control-plane/api-gateway/gatekeeper/deployment.go index f3e9545e57..6bc1402eed 100644 --- a/control-plane/api-gateway/gatekeeper/deployment.go +++ b/control-plane/api-gateway/gatekeeper/deployment.go @@ -6,18 +6,19 @@ package gatekeeper import ( "context" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "k8s.io/apimachinery/pkg/types" - appsv1 "k8s.io/api/apps/v1" 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/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 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" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) const ( @@ -110,7 +111,9 @@ func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayC ObjectMeta: metav1.ObjectMeta{ Labels: common.LabelsForGateway(&gateway), Annotations: map[string]string{ - "consul.hashicorp.com/connect-inject": "false", + constants.AnnotationInject: "false", + constants.AnnotationGatewayConsulServiceName: gateway.Name, + constants.AnnotationGatewayKind: "api-gateway", }, }, Spec: corev1.PodSpec{ diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go index 8d61e6948c..72bf6aeabc 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -25,6 +25,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) var ( @@ -34,6 +35,7 @@ var ( name = "test" namespace = "default" labels = map[string]string{ + "component": "api-gateway", "gateway.consul.hashicorp.com/name": name, "gateway.consul.hashicorp.com/namespace": namespace, createdAtLabelKey: createdAtLabelValue, @@ -1007,6 +1009,8 @@ func validateResourcesExist(t *testing.T, client client.Client, resources resour require.NotNil(t, actual.Spec.Replicas) require.EqualValues(t, *expected.Spec.Replicas, *actual.Spec.Replicas) } + 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 there is a consul-dataplane container dropping ALL capabilities, adding // back the NET_BIND_SERVICE capability, and establishing a read-only root filesystem @@ -1189,7 +1193,9 @@ func configureDeployment(name, namespace string, labels map[string]string, repli ObjectMeta: metav1.ObjectMeta{ Labels: labels, Annotations: map[string]string{ - "consul.hashicorp.com/connect-inject": "false", + constants.AnnotationInject: "false", + constants.AnnotationGatewayConsulServiceName: name, + constants.AnnotationGatewayKind: "api-gateway", }, }, Spec: corev1.PodSpec{ diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 4cff554e83..5a3567fd6f 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -25,7 +25,8 @@ const ( // AnnotationGatewayKind is the key of the annotation that indicates pods // that represent Consul Connect Gateways. This should be set to a - // value that is either "mesh", "ingress" or "terminating". + // value that is either "mesh-gateway", "ingress-gateway", "terminating-gateway", + // or "api-gateway". AnnotationGatewayKind = "consul.hashicorp.com/gateway-kind" // AnnotationGatewayConsulServiceName is the key of the annotation whose value diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index 4ac62cd291..b0b8bea054 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -43,6 +43,7 @@ const ( meshGateway = "mesh-gateway" terminatingGateway = "terminating-gateway" ingressGateway = "ingress-gateway" + apiGateway = "api-gateway" envoyPrometheusBindAddr = "envoy_prometheus_bind_addr" envoyTelemetryCollectorBindSocketDir = "envoy_telemetry_collector_bind_socket_dir" @@ -801,9 +802,11 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints "address": "0.0.0.0", }, } - + case apiGateway: + // Do nothing. This is only here so that API gateway pods have annotations + // consistent with other gateway types but don't return an error below. default: - return nil, fmt.Errorf("%s must be one of %s, %s, or %s", constants.AnnotationGatewayKind, meshGateway, terminatingGateway, ingressGateway) + return nil, fmt.Errorf("%s must be one of %s, %s, %s, or %s", constants.AnnotationGatewayKind, meshGateway, terminatingGateway, ingressGateway, apiGateway) } if r.MetricsConfig.DefaultEnableMetrics && r.MetricsConfig.EnableGatewayMetrics { @@ -1444,10 +1447,11 @@ func hasBeenInjected(pod corev1.Pod) bool { return false } -// isGateway checks the value of the gateway annotation and returns true if the Pod represents a Gateway. +// isGateway checks the value of the gateway annotation and returns true if the Pod +// represents a Gateway kind that should be acted upon by the endpoints controller. func isGateway(pod corev1.Pod) bool { anno, ok := pod.Annotations[constants.AnnotationGatewayKind] - return ok && anno != "" + return ok && anno != "" && anno != apiGateway } // isTelemetryCollector checks whether a pod is part of a deployment for a Consul Telemetry Collector. If so, From ff7603dc4f81ef4b31bc8d25863a325876f481a2 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Wed, 17 Jan 2024 13:33:16 -0500 Subject: [PATCH 569/592] Update the version of client-go to v0.26.12 (#3480) - This change was made across all the submodules to ensure consistency. This version contains a fix to an issue that was causing panics with certain newer versions of Kubernetes. --- acceptance/go.mod | 10 +- acceptance/go.sum | 20 +-- cli/go.mod | 28 ++-- cli/go.sum | 63 +++----- control-plane/cni/go.mod | 38 +++-- control-plane/cni/go.sum | 319 ++++++--------------------------------- control-plane/go.mod | 14 +- control-plane/go.sum | 28 ++-- 8 files changed, 142 insertions(+), 378 deletions(-) diff --git a/acceptance/go.mod b/acceptance/go.mod index 5604fc2ffa..8be66a29da 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -13,6 +13,7 @@ require ( github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 github.com/hashicorp/consul/proto-public v0.5.1 github.com/hashicorp/consul/sdk v0.15.0 + github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hcp-sdk-go v0.50.0 @@ -23,11 +24,11 @@ require ( google.golang.org/grpc v1.56.3 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.26.3 - k8s.io/apimachinery v0.26.3 - k8s.io/client-go v0.26.3 + k8s.io/api v0.26.12 + k8s.io/apimachinery v0.26.12 + k8s.io/client-go v0.26.12 k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 - sigs.k8s.io/controller-runtime v0.14.6 + sigs.k8s.io/controller-runtime v0.14.7 sigs.k8s.io/gateway-api v0.7.1 ) @@ -76,7 +77,6 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-retryablehttp v0.6.6 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index f60d72788b..36713f97e1 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -1209,22 +1209,22 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= -k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= -k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= -k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= +k8s.io/api v0.26.12 h1:jJm3s5ot05SUN3tPGg3b+XWuBE7rO/X0+dnVMhxyd5o= +k8s.io/api v0.26.12/go.mod h1:N+HUXukmtXNOKDngxXrEPbZWggWx01tH/N0nG4nV0oo= +k8s.io/apiextensions-apiserver v0.26.10 h1:wAriTUc6l7gUqJKOxhmXnYo/VNJzk4oh4QLCUR4Uq+k= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= -k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/apimachinery v0.26.12 h1:y+OgufxqLIZtyXIydRhjLBGzrYLF+qwiDdCFXYOjeN4= +k8s.io/apimachinery v0.26.12/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM= -k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= -k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= +k8s.io/client-go v0.26.12 h1:kPpTpIeFNqwo4UyvoqzNp3DNK2mbGcdGv23eS1U8VMo= +k8s.io/client-go v0.26.12/go.mod h1:V7thEnIFroyNZOU30dKLiiVeqQmJz45shJG1mu7nONQ= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= -k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.10 h1:vl3Gfe5aC09mNxfnQtTng7u3rnBVrShOK3MAkqEleb0= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -1253,8 +1253,8 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= -sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8= +sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8= 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= diff --git a/cli/go.mod b/cli/go.mod index c60bfc2ed6..6d7c56b08b 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -19,27 +19,19 @@ require ( github.com/stretchr/testify v1.8.3 golang.org/x/text v0.14.0 helm.sh/helm/v3 v3.9.4 - k8s.io/api v0.25.0 + k8s.io/api v0.26.12 k8s.io/apiextensions-apiserver v0.25.0 - k8s.io/apimachinery v0.25.0 + k8s.io/apimachinery v0.26.12 k8s.io/cli-runtime v0.24.3 - k8s.io/client-go v0.25.0 - k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed + k8s.io/client-go v0.26.12 + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d sigs.k8s.io/yaml v1.3.0 ) require go.opentelemetry.io/proto/otlp v0.19.0 // indirect require ( - cloud.google.com/go/compute v1.19.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.27 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/BurntSushi/toml v1.0.0 // indirect github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -66,7 +58,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/envoyproxy/go-control-plane v0.11.1 // indirect github.com/envoyproxy/go-control-plane/xdsmatcher v0.0.0-20230524161521-aaaacbfbe53e // indirect github.com/envoyproxy/protoc-gen-validate v1.0.1 // indirect @@ -90,7 +82,6 @@ require ( github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect @@ -164,6 +155,7 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.mongodb.org/mongo-driver v1.11.1 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.11.1 // indirect go.opentelemetry.io/otel/trace v1.11.1 // indirect go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect @@ -174,20 +166,20 @@ require ( golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect - golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect + golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // 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.25.0 // indirect k8s.io/component-base v0.25.0 // indirect - k8s.io/klog/v2 v2.70.1 // indirect - k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect k8s.io/kubectl v0.24.2 // indirect oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect diff --git a/cli/go.sum b/cli/go.sum index 8044b5808a..e69191e709 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -24,10 +24,6 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -43,23 +39,12 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= -github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= -github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= -github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= @@ -191,8 +176,8 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7fo github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -335,10 +320,6 @@ github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -666,11 +647,11 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= +github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= 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.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= @@ -782,6 +763,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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/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= @@ -791,6 +773,7 @@ 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.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -839,6 +822,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 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 v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= @@ -881,7 +865,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= @@ -1108,8 +1091,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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= 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= @@ -1295,8 +1278,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -1341,14 +1324,14 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= -k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= -k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= +k8s.io/api v0.26.12 h1:jJm3s5ot05SUN3tPGg3b+XWuBE7rO/X0+dnVMhxyd5o= +k8s.io/api v0.26.12/go.mod h1:N+HUXukmtXNOKDngxXrEPbZWggWx01tH/N0nG4nV0oo= k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= -k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= +k8s.io/apimachinery v0.26.12 h1:y+OgufxqLIZtyXIydRhjLBGzrYLF+qwiDdCFXYOjeN4= +k8s.io/apimachinery v0.26.12/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4= k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo= k8s.io/cli-runtime v0.24.2/go.mod h1:1LIhKL2RblkhfG4v5lZEt7FtgFG5mVb8wqv5lE9m5qY= @@ -1356,8 +1339,8 @@ k8s.io/cli-runtime v0.24.3 h1:O9YvUHrDSCQUPlsqVmaqDrueqjpJ7IO6Yas9B6xGSoo= k8s.io/cli-runtime v0.24.3/go.mod h1:In84wauoMOqa7JDvDSXGbf8lTNlr70fOGpYlYfJtSqA= k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= -k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= -k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= +k8s.io/client-go v0.26.12 h1:kPpTpIeFNqwo4UyvoqzNp3DNK2mbGcdGv23eS1U8VMo= +k8s.io/client-go v0.26.12/go.mod h1:V7thEnIFroyNZOU30dKLiiVeqQmJz45shJG1mu7nONQ= k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= @@ -1369,19 +1352,19 @@ k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAE k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= -k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kubectl v0.24.2 h1:+RfQVhth8akUmIc2Ge8krMl/pt66V7210ka3RE/p0J4= k8s.io/kubectl v0.24.2/go.mod h1:+HIFJc0bA6Tzu5O/YcuUt45APAxnNL8LeMuXwoiGsPg= k8s.io/metrics v0.24.2/go.mod h1:5NWURxZ6Lz5gj8TFU83+vdWIVASx7W8lwPpHYCqopMo= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index 59ed760705..381b6fe171 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -6,46 +6,54 @@ require ( github.com/hashicorp/consul/sdk v0.15.0 github.com/hashicorp/go-hclog v1.5.0 github.com/stretchr/testify v1.8.3 - k8s.io/api v0.22.2 - k8s.io/apimachinery v0.22.2 - k8s.io/client-go v0.22.2 + k8s.io/api v0.26.12 + k8s.io/apimachinery v0.26.12 + k8s.io/client-go v0.26.12 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/fatih/color v1.14.1 // indirect - github.com/go-logr/logr v0.4.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/json-iterator/go v1.1.11 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 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.17.0 // indirect - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // 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/klog/v2 v2.9.0 // indirect - k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect - k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) //replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index c364565c25..9c8e9f9e46 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -1,39 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -49,48 +15,36 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -102,9 +56,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -115,40 +68,19 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -160,6 +92,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -169,222 +103,114 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -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/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.6.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.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-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.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 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= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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= 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= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 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= @@ -392,50 +218,17 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 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= @@ -447,20 +240,17 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -469,34 +259,25 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= -k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= -k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= -k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= -k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +k8s.io/api v0.26.12 h1:jJm3s5ot05SUN3tPGg3b+XWuBE7rO/X0+dnVMhxyd5o= +k8s.io/api v0.26.12/go.mod h1:N+HUXukmtXNOKDngxXrEPbZWggWx01tH/N0nG4nV0oo= +k8s.io/apimachinery v0.26.12 h1:y+OgufxqLIZtyXIydRhjLBGzrYLF+qwiDdCFXYOjeN4= +k8s.io/apimachinery v0.26.12/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= +k8s.io/client-go v0.26.12 h1:kPpTpIeFNqwo4UyvoqzNp3DNK2mbGcdGv23eS1U8VMo= +k8s.io/client-go v0.26.12/go.mod h1:V7thEnIFroyNZOU30dKLiiVeqQmJz45shJG1mu7nONQ= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/control-plane/go.mod b/control-plane/go.mod index 3359a0c184..2e9c55afdb 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -42,14 +42,14 @@ require ( golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 google.golang.org/grpc v1.56.3 - google.golang.org/protobuf v1.30.0 + google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.26.3 - k8s.io/apimachinery v0.26.3 - k8s.io/client-go v0.26.3 + k8s.io/api v0.26.12 + k8s.io/apimachinery v0.26.12 + k8s.io/client-go v0.26.12 k8s.io/klog/v2 v2.100.1 k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 - sigs.k8s.io/controller-runtime v0.14.6 + sigs.k8s.io/controller-runtime v0.14.7 sigs.k8s.io/gateway-api v0.7.1 sigs.k8s.io/yaml v1.3.0 ) @@ -171,8 +171,8 @@ require ( gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.26.3 // indirect - k8s.io/component-base v0.26.3 // indirect + k8s.io/apiextensions-apiserver v0.26.10 // indirect + k8s.io/component-base v0.26.10 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 1feda3fa3e..debfd13a28 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -909,8 +909,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -946,16 +946,16 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= -k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= -k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= -k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= -k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= -k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= -k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= -k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= -k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= +k8s.io/api v0.26.12 h1:jJm3s5ot05SUN3tPGg3b+XWuBE7rO/X0+dnVMhxyd5o= +k8s.io/api v0.26.12/go.mod h1:N+HUXukmtXNOKDngxXrEPbZWggWx01tH/N0nG4nV0oo= +k8s.io/apiextensions-apiserver v0.26.10 h1:wAriTUc6l7gUqJKOxhmXnYo/VNJzk4oh4QLCUR4Uq+k= +k8s.io/apiextensions-apiserver v0.26.10/go.mod h1:N2qhlxkhJLSoC4f0M1/1lNG627b45SYqnOPEVFoQXw4= +k8s.io/apimachinery v0.26.12 h1:y+OgufxqLIZtyXIydRhjLBGzrYLF+qwiDdCFXYOjeN4= +k8s.io/apimachinery v0.26.12/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= +k8s.io/client-go v0.26.12 h1:kPpTpIeFNqwo4UyvoqzNp3DNK2mbGcdGv23eS1U8VMo= +k8s.io/client-go v0.26.12/go.mod h1:V7thEnIFroyNZOU30dKLiiVeqQmJz45shJG1mu7nONQ= +k8s.io/component-base v0.26.10 h1:vl3Gfe5aC09mNxfnQtTng7u3rnBVrShOK3MAkqEleb0= +k8s.io/component-base v0.26.10/go.mod h1:/IDdENUHG5uGxqcofZajovYXE9KSPzJ4yQbkYQt7oN0= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= @@ -965,8 +965,8 @@ k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= -sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8= +sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8= 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= From 9dc87d0cd6e7d663ed56b37dc853438b09367682 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 17 Jan 2024 15:47:59 -0500 Subject: [PATCH 570/592] Reorg v2 controllers (#3464) * rename ReconcileEntry to be ReconcileResource * Move controllers for resources to resources package rather than config-entries package * update package name * Move controllers to own pacakge with pacakges for configentries and resources * Fix import path in ent test --- .../configentries}/configentry_controller.go | 6 ++--- .../configentry_controller_ent_test.go | 4 ++-- .../configentry_controller_test.go | 2 +- .../controlplanerequestlimit_controller.go | 2 +- .../exportedservices_controller.go | 2 +- .../exportedservices_controller_ent_test.go | 20 ++++++++-------- .../configentries}/finalizer_patch.go | 4 ++-- .../configentries}/finalizer_patch_test.go | 2 +- .../ingressgateway_controller.go | 2 +- .../configentries}/jwtprovider_controller.go | 2 +- .../configentries}/mesh_controller.go | 2 +- .../proxydefaults_controller.go | 2 +- .../samenessgroups_controller.go | 2 +- .../servicedefaults_controller.go | 2 +- .../serviceintentions_controller.go | 2 +- .../serviceresolver_controller.go | 2 +- .../servicerouter_controller.go | 2 +- .../servicesplitter_controller.go | 2 +- .../terminatinggateway_controller.go | 2 +- .../resources}/consul_resource_controller.go | 12 +++++----- .../consul_resource_controller_test.go | 4 ++-- .../exported_services_controller.go | 4 ++-- .../gateway_class_config_controller.go | 2 +- .../resources}/gateway_class_controller.go | 2 +- .../resources}/grpc_route_controller.go | 4 ++-- .../resources}/http_route_controller.go | 4 ++-- .../mesh_configuration_controller.go | 4 ++-- .../resources}/mesh_gateway_controller.go | 4 ++-- .../mesh_gateway_controller_test.go | 2 +- .../proxy_configuration_controller.go | 4 ++-- .../resources}/tcp_route_controller.go | 4 ++-- .../traffic_permissions_controller.go | 4 ++-- .../inject-connect/v1controllers.go | 2 +- .../inject-connect/v2controllers.go | 24 +++++++++---------- 34 files changed, 72 insertions(+), 72 deletions(-) rename control-plane/{config-entries/controllers => controllers/configentries}/configentry_controller.go (99%) rename control-plane/{config-entries/controllers => controllers/configentries}/configentry_controller_ent_test.go (99%) rename control-plane/{config-entries/controllers => controllers/configentries}/configentry_controller_test.go (99%) rename control-plane/{config-entries/controllers => controllers/configentries}/controlplanerequestlimit_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/exportedservices_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/exportedservices_controller_ent_test.go (95%) rename control-plane/{config-entries/controllers => controllers/configentries}/finalizer_patch.go (95%) rename control-plane/{config-entries/controllers => controllers/configentries}/finalizer_patch_test.go (99%) rename control-plane/{config-entries/controllers => controllers/configentries}/ingressgateway_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/jwtprovider_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/mesh_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/proxydefaults_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/samenessgroups_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/servicedefaults_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/serviceintentions_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/serviceresolver_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/servicerouter_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/servicesplitter_controller.go (98%) rename control-plane/{config-entries/controllers => controllers/configentries}/terminatinggateway_controller.go (98%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/consul_resource_controller.go (96%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/consul_resource_controller_test.go (99%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/exported_services_controller.go (93%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/gateway_class_config_controller.go (98%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/gateway_class_controller.go (98%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/grpc_route_controller.go (93%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/http_route_controller.go (93%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/mesh_configuration_controller.go (93%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/mesh_gateway_controller.go (99%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/mesh_gateway_controller_test.go (99%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/proxy_configuration_controller.go (92%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/tcp_route_controller.go (93%) rename control-plane/{config-entries/controllersv2 => controllers/resources}/traffic_permissions_controller.go (92%) diff --git a/control-plane/config-entries/controllers/configentry_controller.go b/control-plane/controllers/configentries/configentry_controller.go similarity index 99% rename from control-plane/config-entries/controllers/configentry_controller.go rename to control-plane/controllers/configentries/configentry_controller.go index 03d30c0aa1..9e9459308f 100644 --- a/control-plane/config-entries/controllers/configentry_controller.go +++ b/control-plane/controllers/configentries/configentry_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" @@ -36,8 +36,8 @@ const ( MigrationFailedError = "MigrationFailedError" ) -// Controller is implemented by CRD-specific controllers. It is used by -// ConfigEntryController to abstract CRD-specific controllers. +// Controller is implemented by CRD-specific configentries. It is used by +// ConfigEntryController to abstract CRD-specific configentries. type Controller interface { // AddFinalizersPatch creates a patch with the original finalizers with new ones appended to the end. AddFinalizersPatch(obj client.Object, finalizers ...string) *FinalizerPatch diff --git a/control-plane/config-entries/controllers/configentry_controller_ent_test.go b/control-plane/controllers/configentries/configentry_controller_ent_test.go similarity index 99% rename from control-plane/config-entries/controllers/configentry_controller_ent_test.go rename to control-plane/controllers/configentries/configentry_controller_ent_test.go index a0fb5ce91e..b9eabf3a72 100644 --- a/control-plane/config-entries/controllers/configentry_controller_ent_test.go +++ b/control-plane/controllers/configentries/configentry_controller_ent_test.go @@ -3,7 +3,7 @@ //go:build enterprise -package controllers +package configentries import ( "context" @@ -32,7 +32,7 @@ import ( // NOTE: We're not testing each controller type here because that's mostly done in // the OSS tests and it would result in too many permutations. Instead -// we're only testing with the ServiceDefaults and ProxyDefaults controllers which +// we're only testing with the ServiceDefaults and ProxyDefaults configentries which // will exercise all the namespaces code for config entries that are namespaced and those that // exist in the global namespace. // We also test Enterprise only features like SamenessGroups. diff --git a/control-plane/config-entries/controllers/configentry_controller_test.go b/control-plane/controllers/configentries/configentry_controller_test.go similarity index 99% rename from control-plane/config-entries/controllers/configentry_controller_test.go rename to control-plane/controllers/configentries/configentry_controller_test.go index 57e48dd46c..faa153c323 100644 --- a/control-plane/config-entries/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentries/configentry_controller_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/controlplanerequestlimit_controller.go b/control-plane/controllers/configentries/controlplanerequestlimit_controller.go similarity index 98% rename from control-plane/config-entries/controllers/controlplanerequestlimit_controller.go rename to control-plane/controllers/configentries/controlplanerequestlimit_controller.go index d5a41cf8ec..c636f7fec5 100644 --- a/control-plane/config-entries/controllers/controlplanerequestlimit_controller.go +++ b/control-plane/controllers/configentries/controlplanerequestlimit_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/exportedservices_controller.go b/control-plane/controllers/configentries/exportedservices_controller.go similarity index 98% rename from control-plane/config-entries/controllers/exportedservices_controller.go rename to control-plane/controllers/configentries/exportedservices_controller.go index 28d0f9c807..474431832a 100644 --- a/control-plane/config-entries/controllers/exportedservices_controller.go +++ b/control-plane/controllers/configentries/exportedservices_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/exportedservices_controller_ent_test.go b/control-plane/controllers/configentries/exportedservices_controller_ent_test.go similarity index 95% rename from control-plane/config-entries/controllers/exportedservices_controller_ent_test.go rename to control-plane/controllers/configentries/exportedservices_controller_ent_test.go index 70b774eb53..61ec75288f 100644 --- a/control-plane/config-entries/controllers/exportedservices_controller_ent_test.go +++ b/control-plane/controllers/configentries/exportedservices_controller_ent_test.go @@ -3,7 +3,7 @@ //go:build enterprise -package controllers_test +package configentries_test import ( "context" @@ -23,7 +23,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllers" + "github.com/hashicorp/consul-k8s/control-plane/controllers/configentries" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -104,11 +104,11 @@ func TestExportedServicesController_createsExportedServices(tt *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &controllers.ExportedServicesController{ + controller := &configentries.ExportedServicesController{ Client: fakeClient, Log: logrtest.NewTestLogger(t), Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ + ConfigEntryController: &configentries.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, @@ -196,7 +196,7 @@ func TestExportedServicesController_updatesExportedServices(tt *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "default", Namespace: c.SourceKubeNS, - Finalizers: []string{controllers.FinalizerName}, + Finalizers: []string{configentries.FinalizerName}, }, Spec: v1alpha1.ExportedServicesSpec{ Services: []v1alpha1.ExportedService{ @@ -219,11 +219,11 @@ func TestExportedServicesController_updatesExportedServices(tt *testing.T) { consulClient := testClient.APIClient fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &controllers.ExportedServicesController{ + controller := &configentries.ExportedServicesController{ Client: fakeClient, Log: logrtest.NewTestLogger(t), Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ + ConfigEntryController: &configentries.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, @@ -333,7 +333,7 @@ func TestExportedServicesController_deletesExportedServices(tt *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "default", Namespace: c.SourceKubeNS, - Finalizers: []string{controllers.FinalizerName}, + Finalizers: []string{configentries.FinalizerName}, DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: v1alpha1.ExportedServicesSpec{ @@ -357,11 +357,11 @@ func TestExportedServicesController_deletesExportedServices(tt *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &controllers.ExportedServicesController{ + controller := &configentries.ExportedServicesController{ Client: fakeClient, Log: logrtest.NewTestLogger(t), Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ + ConfigEntryController: &configentries.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, diff --git a/control-plane/config-entries/controllers/finalizer_patch.go b/control-plane/controllers/configentries/finalizer_patch.go similarity index 95% rename from control-plane/config-entries/controllers/finalizer_patch.go rename to control-plane/controllers/configentries/finalizer_patch.go index 5eec8d5d29..a261220c8f 100644 --- a/control-plane/config-entries/controllers/finalizer_patch.go +++ b/control-plane/controllers/configentries/finalizer_patch.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "encoding/json" @@ -25,7 +25,7 @@ type FinalizerPatch struct { // user or another controller process). Before the addition of this finalizer patcher implementation, this race // condition still existed, but applied to the entirety of the CRD because we used to update the entire CRD rather than // just the finalizer, so this reduces the surface area of the race condition. Generally we should not expect users or -// other controllers to be touching the finalizers of consul-k8s managed CRDs. +// other configentries to be touching the finalizers of consul-k8s managed CRDs. func (fp *FinalizerPatch) Type() types.PatchType { return types.MergePatchType } diff --git a/control-plane/config-entries/controllers/finalizer_patch_test.go b/control-plane/controllers/configentries/finalizer_patch_test.go similarity index 99% rename from control-plane/config-entries/controllers/finalizer_patch_test.go rename to control-plane/controllers/configentries/finalizer_patch_test.go index 5b9e3350d1..70dc782d0e 100644 --- a/control-plane/config-entries/controllers/finalizer_patch_test.go +++ b/control-plane/controllers/configentries/finalizer_patch_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "testing" diff --git a/control-plane/config-entries/controllers/ingressgateway_controller.go b/control-plane/controllers/configentries/ingressgateway_controller.go similarity index 98% rename from control-plane/config-entries/controllers/ingressgateway_controller.go rename to control-plane/controllers/configentries/ingressgateway_controller.go index 563b9aa606..d1cced515e 100644 --- a/control-plane/config-entries/controllers/ingressgateway_controller.go +++ b/control-plane/controllers/configentries/ingressgateway_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/jwtprovider_controller.go b/control-plane/controllers/configentries/jwtprovider_controller.go similarity index 98% rename from control-plane/config-entries/controllers/jwtprovider_controller.go rename to control-plane/controllers/configentries/jwtprovider_controller.go index 39e76d502c..6e4aa6c6d1 100644 --- a/control-plane/config-entries/controllers/jwtprovider_controller.go +++ b/control-plane/controllers/configentries/jwtprovider_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/mesh_controller.go b/control-plane/controllers/configentries/mesh_controller.go similarity index 98% rename from control-plane/config-entries/controllers/mesh_controller.go rename to control-plane/controllers/configentries/mesh_controller.go index 7cd6997fc6..7593e287ad 100644 --- a/control-plane/config-entries/controllers/mesh_controller.go +++ b/control-plane/controllers/configentries/mesh_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/proxydefaults_controller.go b/control-plane/controllers/configentries/proxydefaults_controller.go similarity index 98% rename from control-plane/config-entries/controllers/proxydefaults_controller.go rename to control-plane/controllers/configentries/proxydefaults_controller.go index c96c5968d3..0edea71136 100644 --- a/control-plane/config-entries/controllers/proxydefaults_controller.go +++ b/control-plane/controllers/configentries/proxydefaults_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/samenessgroups_controller.go b/control-plane/controllers/configentries/samenessgroups_controller.go similarity index 98% rename from control-plane/config-entries/controllers/samenessgroups_controller.go rename to control-plane/controllers/configentries/samenessgroups_controller.go index c34b017761..9a33744d7a 100644 --- a/control-plane/config-entries/controllers/samenessgroups_controller.go +++ b/control-plane/controllers/configentries/samenessgroups_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/servicedefaults_controller.go b/control-plane/controllers/configentries/servicedefaults_controller.go similarity index 98% rename from control-plane/config-entries/controllers/servicedefaults_controller.go rename to control-plane/controllers/configentries/servicedefaults_controller.go index 69e001a741..00c5235889 100644 --- a/control-plane/config-entries/controllers/servicedefaults_controller.go +++ b/control-plane/controllers/configentries/servicedefaults_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/serviceintentions_controller.go b/control-plane/controllers/configentries/serviceintentions_controller.go similarity index 98% rename from control-plane/config-entries/controllers/serviceintentions_controller.go rename to control-plane/controllers/configentries/serviceintentions_controller.go index e20cf34317..95706fc960 100644 --- a/control-plane/config-entries/controllers/serviceintentions_controller.go +++ b/control-plane/controllers/configentries/serviceintentions_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/serviceresolver_controller.go b/control-plane/controllers/configentries/serviceresolver_controller.go similarity index 98% rename from control-plane/config-entries/controllers/serviceresolver_controller.go rename to control-plane/controllers/configentries/serviceresolver_controller.go index 5bbc9c9f11..7e2352a287 100644 --- a/control-plane/config-entries/controllers/serviceresolver_controller.go +++ b/control-plane/controllers/configentries/serviceresolver_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/servicerouter_controller.go b/control-plane/controllers/configentries/servicerouter_controller.go similarity index 98% rename from control-plane/config-entries/controllers/servicerouter_controller.go rename to control-plane/controllers/configentries/servicerouter_controller.go index 6ea87b590a..7f16addbf2 100644 --- a/control-plane/config-entries/controllers/servicerouter_controller.go +++ b/control-plane/controllers/configentries/servicerouter_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/servicesplitter_controller.go b/control-plane/controllers/configentries/servicesplitter_controller.go similarity index 98% rename from control-plane/config-entries/controllers/servicesplitter_controller.go rename to control-plane/controllers/configentries/servicesplitter_controller.go index 07e2603833..274020a8d8 100644 --- a/control-plane/config-entries/controllers/servicesplitter_controller.go +++ b/control-plane/controllers/configentries/servicesplitter_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllers/terminatinggateway_controller.go b/control-plane/controllers/configentries/terminatinggateway_controller.go similarity index 98% rename from control-plane/config-entries/controllers/terminatinggateway_controller.go rename to control-plane/controllers/configentries/terminatinggateway_controller.go index c68db70538..f8e4a0bc0b 100644 --- a/control-plane/config-entries/controllers/terminatinggateway_controller.go +++ b/control-plane/controllers/configentries/terminatinggateway_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllers +package configentries import ( "context" diff --git a/control-plane/config-entries/controllersv2/consul_resource_controller.go b/control-plane/controllers/resources/consul_resource_controller.go similarity index 96% rename from control-plane/config-entries/controllersv2/consul_resource_controller.go rename to control-plane/controllers/resources/consul_resource_controller.go index 82511649cb..95c5cbcac6 100644 --- a/control-plane/config-entries/controllersv2/consul_resource_controller.go +++ b/control-plane/controllers/resources/consul_resource_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -38,7 +38,7 @@ const ( ExternallyManagedConfigError = "ExternallyManagedConfigError" ) -// ResourceController is implemented by controllers syncing Consul Resources from their CRD counterparts. +// ResourceController is implemented by resources syncing Consul Resources from their CRD counterparts. // It is used by ConsulResourceController to abstract CRD-specific Consul Resources. type ResourceController interface { // Update updates the state of the whole object. @@ -67,13 +67,13 @@ type ConsulResourceController struct { common.ConsulTenancyConfig } -// ReconcileEntry reconciles an update to a resource. CRD-specific controller's +// ReconcileResource reconciles an update to a resource. CRD-specific controller's // call this function because it handles reconciliation of config entries // generically. // CRD-specific controller should pass themselves in as updater since we // need to call back into their own update methods to ensure they update their // internal state. -func (r *ConsulResourceController) ReconcileEntry(ctx context.Context, crdCtrl ResourceController, req ctrl.Request, resource common.ConsulResource) (ctrl.Result, error) { +func (r *ConsulResourceController) ReconcileResource(ctx context.Context, crdCtrl ResourceController, req ctrl.Request, resource common.ConsulResource) (ctrl.Result, error) { logger := crdCtrl.Logger(req.NamespacedName) err := crdCtrl.Get(ctx, req.NamespacedName, resource) if k8serr.IsNotFound(err) { @@ -263,8 +263,8 @@ func (r *ConsulResourceController) syncUnknownWithError(ctx context.Context, updater ResourceController, resource common.ConsulResource, errType string, - err error) (ctrl.Result, error) { - + err error, +) (ctrl.Result, error) { resource.SetSyncedCondition(corev1.ConditionUnknown, errType, err.Error()) if updateErr := updater.UpdateStatus(ctx, resource); updateErr != nil { // Log the original error here because we are returning the updateErr. diff --git a/control-plane/config-entries/controllersv2/consul_resource_controller_test.go b/control-plane/controllers/resources/consul_resource_controller_test.go similarity index 99% rename from control-plane/config-entries/controllersv2/consul_resource_controller_test.go rename to control-plane/controllers/resources/consul_resource_controller_test.go index 23ccc9f656..e8f63514ab 100644 --- a/control-plane/config-entries/controllersv2/consul_resource_controller_test.go +++ b/control-plane/controllers/resources/consul_resource_controller_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -476,7 +476,7 @@ func TestConsulResourceController_ErrorUpdatesSyncStatus(t *testing.T) { }, } - // ReconcileEntry should result in an error. + // ReconcileResource should result in an error. namespacedName := types.NamespacedName{ Namespace: metav1.NamespaceDefault, Name: trafficpermissions.KubernetesName(), diff --git a/control-plane/config-entries/controllersv2/exported_services_controller.go b/control-plane/controllers/resources/exported_services_controller.go similarity index 93% rename from control-plane/config-entries/controllersv2/exported_services_controller.go rename to control-plane/controllers/resources/exported_services_controller.go index dfcfa57fb5..30197f5b85 100644 --- a/control-plane/config-entries/controllersv2/exported_services_controller.go +++ b/control-plane/controllers/resources/exported_services_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -29,7 +29,7 @@ type ExportedServicesController struct { // +kubebuilder:rbac:groups=multicluster.consul.hashicorp.com,resources=exportedservices/status,verbs=get;update;patch func (r *ExportedServicesController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &multiclusterv2beta1.ExportedServices{}) + return r.Controller.ReconcileResource(ctx, r, req, &multiclusterv2beta1.ExportedServices{}) } func (r *ExportedServicesController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/gateway_class_config_controller.go b/control-plane/controllers/resources/gateway_class_config_controller.go similarity index 98% rename from control-plane/config-entries/controllersv2/gateway_class_config_controller.go rename to control-plane/controllers/resources/gateway_class_config_controller.go index 1db5c9b765..56ade8161a 100644 --- a/control-plane/config-entries/controllersv2/gateway_class_config_controller.go +++ b/control-plane/controllers/resources/gateway_class_config_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" diff --git a/control-plane/config-entries/controllersv2/gateway_class_controller.go b/control-plane/controllers/resources/gateway_class_controller.go similarity index 98% rename from control-plane/config-entries/controllersv2/gateway_class_controller.go rename to control-plane/controllers/resources/gateway_class_controller.go index 17299498b4..23f7f0f4db 100644 --- a/control-plane/config-entries/controllersv2/gateway_class_controller.go +++ b/control-plane/controllers/resources/gateway_class_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" diff --git a/control-plane/config-entries/controllersv2/grpc_route_controller.go b/control-plane/controllers/resources/grpc_route_controller.go similarity index 93% rename from control-plane/config-entries/controllersv2/grpc_route_controller.go rename to control-plane/controllers/resources/grpc_route_controller.go index 6555bb201f..fa5401c800 100644 --- a/control-plane/config-entries/controllersv2/grpc_route_controller.go +++ b/control-plane/controllers/resources/grpc_route_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -27,7 +27,7 @@ type GRPCRouteController struct { // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute/status,verbs=get;update;patch func (r *GRPCRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.GRPCRoute{}) + return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.GRPCRoute{}) } func (r *GRPCRouteController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/http_route_controller.go b/control-plane/controllers/resources/http_route_controller.go similarity index 93% rename from control-plane/config-entries/controllersv2/http_route_controller.go rename to control-plane/controllers/resources/http_route_controller.go index cf9686b47e..9275d8f265 100644 --- a/control-plane/config-entries/controllersv2/http_route_controller.go +++ b/control-plane/controllers/resources/http_route_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -27,7 +27,7 @@ type HTTPRouteController struct { // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute/status,verbs=get;update;patch func (r *HTTPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.HTTPRoute{}) + return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.HTTPRoute{}) } func (r *HTTPRouteController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/mesh_configuration_controller.go b/control-plane/controllers/resources/mesh_configuration_controller.go similarity index 93% rename from control-plane/config-entries/controllersv2/mesh_configuration_controller.go rename to control-plane/controllers/resources/mesh_configuration_controller.go index d485ed3e89..d5813294e9 100644 --- a/control-plane/config-entries/controllersv2/mesh_configuration_controller.go +++ b/control-plane/controllers/resources/mesh_configuration_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -27,7 +27,7 @@ type MeshConfigurationController struct { // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshconfiguration/status,verbs=get;update;patch func (r *MeshConfigurationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.MeshConfiguration{}) + return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.MeshConfiguration{}) } func (r *MeshConfigurationController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go b/control-plane/controllers/resources/mesh_gateway_controller.go similarity index 99% rename from control-plane/config-entries/controllersv2/mesh_gateway_controller.go rename to control-plane/controllers/resources/mesh_gateway_controller.go index 52da7f678f..0e194a9701 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller.go +++ b/control-plane/controllers/resources/mesh_gateway_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -65,7 +65,7 @@ func (r *MeshGatewayController) Reconcile(ctx context.Context, req ctrl.Request) } } - return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.MeshGateway{}) + return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.MeshGateway{}) } func (r *MeshGatewayController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go b/control-plane/controllers/resources/mesh_gateway_controller_test.go similarity index 99% rename from control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go rename to control-plane/controllers/resources/mesh_gateway_controller_test.go index 8b5ca629ea..6aef2ec6e2 100644 --- a/control-plane/config-entries/controllersv2/mesh_gateway_controller_test.go +++ b/control-plane/controllers/resources/mesh_gateway_controller_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" diff --git a/control-plane/config-entries/controllersv2/proxy_configuration_controller.go b/control-plane/controllers/resources/proxy_configuration_controller.go similarity index 92% rename from control-plane/config-entries/controllersv2/proxy_configuration_controller.go rename to control-plane/controllers/resources/proxy_configuration_controller.go index af76aebbcb..7f67afe26a 100644 --- a/control-plane/config-entries/controllersv2/proxy_configuration_controller.go +++ b/control-plane/controllers/resources/proxy_configuration_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -27,7 +27,7 @@ type ProxyConfigurationController struct { // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration/status,verbs=get;update;patch func (r *ProxyConfigurationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.ProxyConfiguration{}) + return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.ProxyConfiguration{}) } func (r *ProxyConfigurationController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/tcp_route_controller.go b/control-plane/controllers/resources/tcp_route_controller.go similarity index 93% rename from control-plane/config-entries/controllersv2/tcp_route_controller.go rename to control-plane/controllers/resources/tcp_route_controller.go index a573835a5c..dc69f879b2 100644 --- a/control-plane/config-entries/controllersv2/tcp_route_controller.go +++ b/control-plane/controllers/resources/tcp_route_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -27,7 +27,7 @@ type TCPRouteController struct { // +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute/status,verbs=get;update;patch func (r *TCPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &meshv2beta1.TCPRoute{}) + return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.TCPRoute{}) } func (r *TCPRouteController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go b/control-plane/controllers/resources/traffic_permissions_controller.go similarity index 92% rename from control-plane/config-entries/controllersv2/traffic_permissions_controller.go rename to control-plane/controllers/resources/traffic_permissions_controller.go index 960d165f97..f844473b0c 100644 --- a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go +++ b/control-plane/controllers/resources/traffic_permissions_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package controllersv2 +package resources import ( "context" @@ -27,7 +27,7 @@ type TrafficPermissionsController struct { // +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions/status,verbs=get;update;patch func (r *TrafficPermissionsController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileEntry(ctx, r, req, &consulv2beta1.TrafficPermissions{}) + return r.Controller.ReconcileResource(ctx, r, req, &consulv2beta1.TrafficPermissions{}) } func (r *TrafficPermissionsController) Logger(name types.NamespacedName) logr.Logger { diff --git a/control-plane/subcommand/inject-connect/v1controllers.go b/control-plane/subcommand/inject-connect/v1controllers.go index f717b873b0..541ac00f49 100644 --- a/control-plane/subcommand/inject-connect/v1controllers.go +++ b/control-plane/subcommand/inject-connect/v1controllers.go @@ -17,12 +17,12 @@ import ( gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllers" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" + controllers "github.com/hashicorp/consul-k8s/control-plane/controllers/configentries" webhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/webhook-configuration" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index c3cf57419c..67c707b592 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -13,7 +13,6 @@ import ( authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/common" meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllersv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/serviceaccount" @@ -22,6 +21,7 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/connect-inject/namespace" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhookv2" + resourceControllers "github.com/hashicorp/consul-k8s/control-plane/controllers/resources" "github.com/hashicorp/consul-k8s/control-plane/gateways" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" namespacev2 "github.com/hashicorp/consul-k8s/control-plane/tenancy/namespace" @@ -144,13 +144,13 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage } } - consulResourceController := &controllersv2.ConsulResourceController{ + consulResourceController := &resourceControllers.ConsulResourceController{ ConsulClientConfig: consulConfig, ConsulServerConnMgr: watcher, ConsulTenancyConfig: consulTenancyConfig, } - if err := (&controllersv2.TrafficPermissionsController{ + if err := (&resourceControllers.TrafficPermissionsController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.TrafficPermissions), @@ -160,7 +160,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.GRPCRouteController{ + if err := (&resourceControllers.GRPCRouteController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.GRPCRoute), @@ -170,7 +170,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.HTTPRouteController{ + if err := (&resourceControllers.HTTPRouteController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.HTTPRoute), @@ -180,7 +180,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.TCPRouteController{ + if err := (&resourceControllers.TCPRouteController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.TCPRoute), @@ -190,7 +190,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.ProxyConfigurationController{ + if err := (&resourceControllers.ProxyConfigurationController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.ProxyConfiguration), @@ -200,7 +200,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.MeshConfigurationController{ + if err := (&resourceControllers.MeshConfigurationController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.MeshConfiguration), @@ -210,7 +210,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.MeshGatewayController{ + if err := (&resourceControllers.MeshGatewayController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.MeshGateway), @@ -239,7 +239,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.GatewayClassConfigController{ + if err := (&resourceControllers.GatewayClassConfigController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.GatewayClassConfig), @@ -249,7 +249,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.GatewayClassController{ + if err := (&resourceControllers.GatewayClassController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.GatewayClass), @@ -259,7 +259,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } - if err := (&controllersv2.ExportedServicesController{ + if err := (&resourceControllers.ExportedServicesController{ Controller: consulResourceController, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.ExportedServices), From 245a845b2b3e486dc27f2615862cf6af147f8532 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Thu, 18 Jan 2024 09:35:52 -0500 Subject: [PATCH 571/592] NET-7147 - Upgrade go to 1.21.6 and use single source of .go-version (#3478) Ugrade go to 1.21.6 and use single source of .go-version --- .changelog/3478.txt | 3 +++ .go-version | 2 +- Makefile | 5 +++++ control-plane/Dockerfile | 3 ++- 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 .changelog/3478.txt diff --git a/.changelog/3478.txt b/.changelog/3478.txt new file mode 100644 index 0000000000..81568f397f --- /dev/null +++ b/.changelog/3478.txt @@ -0,0 +1,3 @@ +```release-note:improvement +Upgrade to use Go 1.21.6. +``` diff --git a/.go-version b/.go-version index 3b9e4a0c18..c262b1f0df 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.20.12 +1.21.6 diff --git a/Makefile b/Makefile index af99dcbf2f..2e5d465318 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ VERSION = $(shell ./control-plane/build-support/scripts/version.sh control-plane/version/version.go) +GOLANG_VERSION?=$(shell head -n 1 .go-version) CONSUL_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-version.sh charts/consul/values.yaml) CONSUL_ENTERPRISE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-enterprise-version.sh charts/consul/values.yaml) CONSUL_DATAPLANE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-dataplane-version.sh charts/consul/values.yaml) @@ -44,6 +45,7 @@ control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) @docker build -t '$(DEV_IMAGE)' \ --target=dev \ + --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ --build-arg 'TARGETARCH=$(GOARCH)' \ --build-arg 'GIT_COMMIT=$(GIT_COMMIT)' \ --build-arg 'GIT_DIRTY=$(GIT_DIRTY)' \ @@ -55,6 +57,7 @@ control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. control-plane-dev-skaffold: ## Build consul-k8s-control-plane dev Docker image for use with skaffold or local development. @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) @docker build -t '$(DEV_IMAGE)' \ + --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ --build-arg 'TARGETARCH=$(GOARCH)' \ -f $(CURDIR)/control-plane/Dockerfile.dev $(CURDIR)/control-plane @@ -70,6 +73,7 @@ control-plane-dev-docker-multi-arch: check-remote-dev-image-env ## Build consul- @docker buildx create --use && docker buildx build -t '$(REMOTE_DEV_IMAGE)' \ --platform linux/amd64,linux/arm64 \ --target=dev \ + --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ --build-arg 'GIT_COMMIT=$(GIT_COMMIT)' \ --build-arg 'GIT_DIRTY=$(GIT_DIRTY)' \ --build-arg 'GIT_DESCRIBE=$(GIT_DESCRIBE)' \ @@ -81,6 +85,7 @@ control-plane-fips-dev-docker: ## Build consul-k8s-control-plane FIPS dev Docker @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) --fips @docker build -t '$(DEV_IMAGE)' \ --target=dev \ + --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ --build-arg 'TARGETARCH=$(GOARCH)' \ --build-arg 'GIT_COMMIT=$(GIT_COMMIT)' \ --build-arg 'GIT_DIRTY=$(GIT_DIRTY)' \ diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index b9a3252271..139f4137e1 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -16,7 +16,8 @@ # go-discover builds the discover binary (which we don't currently publish # either). -FROM golang:1.20.12-alpine as go-discover +ARG GOLANG_VERSION +FROM golang:${GOLANG_VERSION} as go-discover RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@214571b6a5309addf3db7775f4ee8cf4d264fd5f # dev copies the binary from a local build From 5d9bd49b000fc8b9afc6d7de391651eb90c52637 Mon Sep 17 00:00:00 2001 From: Thomas Eckert Date: Thu, 18 Jan 2024 13:17:48 -0500 Subject: [PATCH 572/592] Mesh Gateway Deployment Configuration (#3477) * Add BATs for Gateway Log Level Configuration * Pass logLevel into init-container and dataplane-container * Add BATs for extraLabels * Test that extraLabels get set on the deployment * BATs for annotations * Use config for log level over gcc if available * Make consulDataplaneContainer an assoc func to builder * Use logLevelForDataplaneContainer func * Test annotations getting set * Add comments for Builder obj * Rename config.go to gateway_config.go * Add comments to gateway_config * Move commands closer to their configuration * Extract some constants * `%s/expected/debug/g` --- .../unit/gateway-resources-configmap.bats | 111 ++++++++++++++++++ control-plane/gateways/builder.go | 5 + control-plane/gateways/constants.go | 33 ++++++ control-plane/gateways/deployment.go | 2 +- .../deployment_dataplane_container.go | 56 ++++----- .../gateways/deployment_init_container.go | 48 ++++---- control-plane/gateways/deployment_test.go | 90 ++++++++++---- .../gateways/{config.go => gateway_config.go} | 9 +- control-plane/gateways/metadata.go | 24 ++++ control-plane/gateways/metadata_test.go | 47 ++++++++ 10 files changed, 349 insertions(+), 76 deletions(-) create mode 100644 control-plane/gateways/constants.go rename control-plane/gateways/{config.go => gateway_config.go} (73%) diff --git a/charts/consul/test/unit/gateway-resources-configmap.bats b/charts/consul/test/unit/gateway-resources-configmap.bats index 4b76e55d39..e827644792 100644 --- a/charts/consul/test/unit/gateway-resources-configmap.bats +++ b/charts/consul/test/unit/gateway-resources-configmap.bats @@ -73,6 +73,117 @@ target=templates/gateway-resources-configmap.yaml [ "$resources" != null ] } +#-------------------------------------------------------------------- +# Mesh Gateway logLevel configuration + +@test "gateway-resources/ConfigMap: Mesh Gateway logLevel default configuration" { + cd `chart_dir` + local config=$(helm template \ + -s $target \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) + + local actual=$(echo "$config" | yq -r '.container.consul.logging.level') + [ "${actual}" = 'info' ] + + local actual=$(echo "$config" | yq -r '.initContainer.consul.logging.level') + [ "${actual}" = 'info' ] +} + + +@test "gateway-resources/ConfigMap: Mesh Gateway logLevel custom global configuration" { + cd `chart_dir` + local config=$(helm template \ + -s $target \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'global.logLevel=debug' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) + + local actual=$(echo "$config" | yq -r '.container.consul.logging.level') + [ "${actual}" = 'debug' ] + + local actual=$(echo "$config" | yq -r '.initContainer.consul.logging.level') + [ "${actual}" = 'debug' ] +} + +@test "gateway-resources/ConfigMap: Mesh Gateway logLevel custom meshGateway configuration" { + cd `chart_dir` + local config=$(helm template \ + -s $target \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'meshGateway.logLevel=debug' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) + + local actual=$(echo "$config" | yq -r '.container.consul.logging.level') + [ "${actual}" = 'debug' ] + + local actual=$(echo "$config" | yq -r '.initContainer.consul.logging.level') + [ "${actual}" = 'debug' ] +} + +@test "gateway-resources/ConfigMap: Mesh Gateway logLevel custom meshGateway configuration overrides global configuration" { + cd `chart_dir` + local config=$(helm template \ + -s $target \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'global.logLevel=error' \ + --set 'meshGateway.logLevel=debug' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) + + local actual=$(echo "$config" | yq -r '.container.consul.logging.level') + [ "${actual}" = 'debug' ] + + local actual=$(echo "$config" | yq -r '.initContainer.consul.logging.level') + [ "${actual}" = 'debug' ] +} + +#-------------------------------------------------------------------- +# Mesh Gateway Extra Labels configuration + +@test "gateway-resources/ConfigMap: Mesh Gateway gets Extra Labels when set" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'global.extraLabels.foo'='bar' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment.labels.set.foo' | tee /dev/stderr + ) + [ "$actual" = 'bar' ] +} + +#-------------------------------------------------------------------- +# Mesh Gateway annotations configuration + +@test "gateway-resources/ConfigMap: Mesh Gateway gets annotations when set" { + cd `chart_dir` + local actual=$(helm template \ + -s $target \ + --set 'connectInject.enabled=true' \ + --set 'meshGateway.enabled=true' \ + --set 'global.experiments[0]=resource-apis' \ + --set 'ui.enabled=false' \ + --set 'meshGateway.annotations.foo'='bar' \ + . | tee /dev/stderr | + yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment.annotations.set.foo' | tee /dev/stderr + ) + [ "$actual" = 'bar' ] +} #-------------------------------------------------------------------- # Mesh Gateway WAN Address configuration diff --git a/control-plane/gateways/builder.go b/control-plane/gateways/builder.go index 53dcbe03ec..e43e6dd890 100644 --- a/control-plane/gateways/builder.go +++ b/control-plane/gateways/builder.go @@ -7,12 +7,17 @@ import ( meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" ) +// meshGatewayBuilder is a helper struct for building the Kubernetes resources for a mesh gateway. +// This includes Deployment, Role, Service, and ServiceAccount resources. +// Configuration is combined from the MeshGateway, GatewayConfig, and GatewayClassConfig. type meshGatewayBuilder struct { gateway *meshv2beta1.MeshGateway config GatewayConfig gcc *meshv2beta1.GatewayClassConfig } +// NewMeshGatewayBuilder returns a new meshGatewayBuilder for the given MeshGateway, +// GatewayConfig, and GatewayClassConfig. func NewMeshGatewayBuilder(gateway *meshv2beta1.MeshGateway, gatewayConfig GatewayConfig, gatewayClassConfig *meshv2beta1.GatewayClassConfig) *meshGatewayBuilder { return &meshGatewayBuilder{ gateway: gateway, diff --git a/control-plane/gateways/constants.go b/control-plane/gateways/constants.go new file mode 100644 index 0000000000..7ca89ef1f7 --- /dev/null +++ b/control-plane/gateways/constants.go @@ -0,0 +1,33 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gateways + +const ( + // General environment variables. + envPodName = "POD_NAME" + envPodNamespace = "POD_NAMESPACE" + envNodeName = "NODE_NAME" + envTmpDir = "TMPDIR" + + // Dataplane Configuration Environment variables. + envDPProxyId = "DP_PROXY_ID" + envDPCredentialLoginMeta = "DP_CREDENTIAL_LOGIN_META" + envDPServiceNodeName = "DP_SERVICE_NODE_NAME" + + // Init Container Configuration Environment variables. + envConsulAddresses = "CONSUL_ADDRESSES" + envConsulGRPCPort = "CONSUL_GRPC_PORT" + envConsulHTTPPort = "CONSUL_HTTP_PORT" + envConsulAPITimeout = "CONSUL_API_TIMEOUT" + envConsulNodeName = "CONSUL_NODE_NAME" + envConsulLoginAuthMethod = "CONSUL_LOGIN_AUTH_METHOD" + envConsulLoginBearerTokenFile = "CONSUL_LOGIN_BEARER_TOKEN_FILE" + envConsulLoginMeta = "CONSUL_LOGIN_META" + envConsulLoginPartition = "CONSUL_LOGIN_PARTITION" + envConsulNamespace = "CONSUL_NAMESPACE" + envConsulPartition = "CONSUL_PARTITION" + + // defaultBearerTokenFile is the default location where the init container will store the bearer token for the dataplane container to read. + defaultBearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" +) diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go index eb0d7e89f5..bf944503b8 100644 --- a/control-plane/gateways/deployment.go +++ b/control-plane/gateways/deployment.go @@ -51,7 +51,7 @@ func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { return nil, err } - container, err := consulDataplaneContainer(b.config, containerConfig, b.gateway.Name, b.gateway.Namespace) + container, err := b.consulDataplaneContainer(containerConfig) if err != nil { return nil, err } diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go index 9e34c24b52..967c8b6f1e 100644 --- a/control-plane/gateways/deployment_dataplane_container.go +++ b/control-plane/gateways/deployment_dataplane_container.go @@ -27,7 +27,7 @@ const ( volumeName = "consul-mesh-inject-data" ) -func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.GatewayClassContainerConfig, name, namespace string) (corev1.Container, error) { +func (b *meshGatewayBuilder) consulDataplaneContainer(containerConfig v2beta1.GatewayClassContainerConfig) (corev1.Container, error) { // Extract the service account token's volume mount. var ( err error @@ -36,11 +36,11 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate resources := containerConfig.Resources - if config.AuthMethod != "" { + if b.config.AuthMethod != "" { bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" } - args, err := getDataplaneArgs(namespace, config, bearerTokenFile, name) + args, err := b.dataplaneArgs(bearerTokenFile) if err != nil { return corev1.Container{}, err } @@ -56,8 +56,8 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate } container := corev1.Container{ - Name: name, - Image: config.ImageDataplane, + Name: b.gateway.Name, + Image: b.config.ImageDataplane, // We need to set tmp dir to an ephemeral volume that we're mounting so that // consul-dataplane can write files to it. Otherwise, it wouldn't be able to @@ -66,23 +66,23 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate // TODO(nathancoleman): I don't believe consul-dataplane needs to write anymore, investigate. Env: []corev1.EnvVar{ { - Name: "DP_PROXY_ID", + Name: envDPProxyId, ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, }, }, { - Name: "POD_NAMESPACE", + Name: envPodNamespace, ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, }, }, { - Name: "TMPDIR", + Name: envTmpDir, Value: constants.MeshV2VolumePath, }, { - Name: "NODE_NAME", + Name: envNodeName, ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{ FieldPath: "spec.nodeName", @@ -90,15 +90,11 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate }, }, { - Name: "DP_CREDENTIAL_LOGIN_META", + Name: envDPCredentialLoginMeta, Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", }, { - Name: "DP_CREDENTIAL_LOGIN_META1", - Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", - }, - { - Name: "DP_SERVICE_NODE_NAME", + Name: envDPServiceNodeName, Value: "$(NODE_NAME)-virtual", }, }, @@ -157,36 +153,36 @@ func consulDataplaneContainer(config GatewayConfig, containerConfig v2beta1.Gate return container, nil } -func getDataplaneArgs(namespace string, config GatewayConfig, bearerTokenFile string, name string) ([]string, error) { +func (b *meshGatewayBuilder) dataplaneArgs(bearerTokenFile string) ([]string, error) { args := []string{ - "-addresses", config.ConsulConfig.Address, - "-grpc-port=" + strconv.Itoa(config.ConsulConfig.GRPCPort), - "-log-level=" + config.LogLevel, - "-log-json=" + strconv.FormatBool(config.LogJSON), + "-addresses", b.config.ConsulConfig.Address, + "-grpc-port=" + strconv.Itoa(b.config.ConsulConfig.GRPCPort), + "-log-level=" + b.logLevelForDataplaneContainer(), + "-log-json=" + strconv.FormatBool(b.config.LogJSON), "-envoy-concurrency=" + defaultEnvoyProxyConcurrency, } - consulNamespace := namespaces.ConsulNamespace(namespace, config.ConsulTenancyConfig.EnableConsulNamespaces, config.ConsulTenancyConfig.ConsulDestinationNamespace, config.ConsulTenancyConfig.EnableConsulNamespaces, config.ConsulTenancyConfig.NSMirroringPrefix) + consulNamespace := namespaces.ConsulNamespace(b.gateway.Namespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.ConsulDestinationNamespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.NSMirroringPrefix) - if config.AuthMethod != "" { + if b.config.AuthMethod != "" { args = append(args, "-credential-type=login", - "-login-auth-method="+config.AuthMethod, + "-login-auth-method="+b.config.AuthMethod, "-login-bearer-token-path="+bearerTokenFile, - "-login-meta="+fmt.Sprintf("gateway=%s/%s", namespace, name), + "-login-meta="+fmt.Sprintf("gateway=%s/%s", b.gateway.Namespace, b.gateway.Name), ) - if config.ConsulTenancyConfig.ConsulPartition != "" { - args = append(args, "-login-partition="+config.ConsulTenancyConfig.ConsulPartition) + if b.config.ConsulTenancyConfig.ConsulPartition != "" { + args = append(args, "-login-partition="+b.config.ConsulTenancyConfig.ConsulPartition) } } - if config.ConsulTenancyConfig.EnableConsulNamespaces { + if b.config.ConsulTenancyConfig.EnableConsulNamespaces { args = append(args, "-service-namespace="+consulNamespace) } - if config.ConsulTenancyConfig.ConsulPartition != "" { - args = append(args, "-service-partition="+config.ConsulTenancyConfig.ConsulPartition) + if b.config.ConsulTenancyConfig.ConsulPartition != "" { + args = append(args, "-service-partition="+b.config.ConsulTenancyConfig.ConsulPartition) } - args = append(args, buildTLSArgs(config)...) + args = append(args, buildTLSArgs(b.config)...) // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) diff --git a/control-plane/gateways/deployment_init_container.go b/control-plane/gateways/deployment_init_container.go index c6fdcbeff6..14230b98df 100644 --- a/control-plane/gateways/deployment_init_container.go +++ b/control-plane/gateways/deployment_init_container.go @@ -19,7 +19,10 @@ import ( const ( injectInitContainerName = "consul-mesh-init" initContainersUserAndGroupID = 5996 - defaultBearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" +) + +var ( + tpl = template.Must(template.New("root").Parse(strings.TrimSpace(initContainerCommandTpl))) ) type initContainerCommandData struct { @@ -37,11 +40,16 @@ type initContainerCommandData struct { func (b *meshGatewayBuilder) initContainer() (corev1.Container, error) { data := initContainerCommandData{ AuthMethod: b.config.AuthMethod, - LogLevel: b.config.LogLevel, + LogLevel: b.logLevelForInitContainer(), LogJSON: b.config.LogJSON, ServiceName: b.gateway.Name, ServiceAccountName: b.serviceAccountName(), } + // Render the command + var buf bytes.Buffer + if err := tpl.Execute(&buf, &data); err != nil { + return corev1.Container{}, err + } // Create expected volume mounts volMounts := []corev1.VolumeMount{ @@ -56,14 +64,6 @@ func (b *meshGatewayBuilder) initContainer() (corev1.Container, error) { bearerTokenFile = defaultBearerTokenFile } - // Render the command - var buf bytes.Buffer - tpl := template.Must(template.New("root").Parse(strings.TrimSpace(initContainerCommandTpl))) - - if err := tpl.Execute(&buf, &data); err != nil { - return corev1.Container{}, err - } - consulNamespace := namespaces.ConsulNamespace(b.gateway.Namespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.ConsulDestinationNamespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.NSMirroringPrefix) initContainerName := injectInitContainerName @@ -73,19 +73,19 @@ func (b *meshGatewayBuilder) initContainer() (corev1.Container, error) { Env: []corev1.EnvVar{ { - Name: "POD_NAME", + Name: envPodName, ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, }, }, { - Name: "POD_NAMESPACE", + Name: envPodNamespace, ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, }, }, { - Name: "NODE_NAME", + Name: envNodeName, ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{ FieldPath: "spec.nodeName", @@ -93,23 +93,23 @@ func (b *meshGatewayBuilder) initContainer() (corev1.Container, error) { }, }, { - Name: "CONSUL_ADDRESSES", + Name: envConsulAddresses, Value: b.config.ConsulConfig.Address, }, { - Name: "CONSUL_GRPC_PORT", + Name: envConsulGRPCPort, Value: strconv.Itoa(b.config.ConsulConfig.GRPCPort), }, { - Name: "CONSUL_HTTP_PORT", + Name: envConsulHTTPPort, Value: strconv.Itoa(b.config.ConsulConfig.HTTPPort), }, { - Name: "CONSUL_API_TIMEOUT", + Name: envConsulAPITimeout, Value: b.config.ConsulConfig.APITimeout.String(), }, { - Name: "CONSUL_NODE_NAME", + Name: envConsulNodeName, Value: "$(NODE_NAME)-virtual", }, }, @@ -121,28 +121,28 @@ func (b *meshGatewayBuilder) initContainer() (corev1.Container, error) { if b.config.AuthMethod != "" { container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_LOGIN_AUTH_METHOD", + Name: envConsulLoginAuthMethod, Value: b.config.AuthMethod, }, corev1.EnvVar{ - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", + Name: envConsulLoginBearerTokenFile, Value: bearerTokenFile, }, corev1.EnvVar{ - Name: "CONSUL_LOGIN_META", + Name: envConsulLoginMeta, Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", }) if b.config.ConsulTenancyConfig.ConsulPartition != "" { container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_LOGIN_PARTITION", + Name: envConsulLoginPartition, Value: b.config.ConsulTenancyConfig.ConsulPartition, }) } } container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_NAMESPACE", + Name: envConsulNamespace, Value: consulNamespace, }) @@ -165,7 +165,7 @@ func (b *meshGatewayBuilder) initContainer() (corev1.Container, error) { if b.config.ConsulTenancyConfig.ConsulPartition != "" { container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_PARTITION", + Name: envConsulPartition, Value: b.config.ConsulTenancyConfig.ConsulPartition, }) } diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index dbd58a2c0f..37fcb80c59 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -80,6 +80,11 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "release": "consul", }, }, + Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + Set: map[string]string{ + "a": "b", + }, + }, }, Deployment: meshv2beta1.GatewayClassDeploymentConfig{ Affinity: &corev1.Affinity{ @@ -103,9 +108,26 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { }, }, }, + GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ + Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + Set: map[string]string{ + "foo": "bar", + }, + }, + Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ + Set: map[string]string{ + "baz": "qux", + }, + }, + }, Container: &meshv2beta1.GatewayClassContainerConfig{ HostPort: 8080, PortModifier: 8000, + Consul: meshv2beta1.GatewayClassConsulConfig{ + Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ + Level: "debug", + }, + }, }, NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, Replicas: &meshv2beta1.GatewayClassReplicasConfig{ @@ -132,6 +154,11 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "memory": resource.MustParse("228Mi"), }, }, + Consul: meshv2beta1.GatewayClassConsulConfig{ + Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ + Level: "debug", + }, + }, }, }, }, @@ -145,9 +172,12 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "chart": "consul-helm", "heritage": "Helm", "release": "consul", + "foo": "bar", + }, + Annotations: map[string]string{ + "a": "b", + "baz": "qux", }, - - Annotations: map[string]string{}, }, Spec: appsv1.DeploymentSpec{ Replicas: pointer.Int32(1), @@ -158,6 +188,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "chart": "consul-helm", "heritage": "Helm", "release": "consul", + "foo": "bar", }, }, Template: corev1.PodTemplateSpec{ @@ -167,9 +198,9 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "app": "consul", "chart": "consul-helm", "heritage": "Helm", + "foo": "bar", "release": "consul", }, - Annotations: map[string]string{ constants.AnnotationGatewayKind: meshGatewayAnnotationKind, constants.AnnotationMeshInject: "false", @@ -196,7 +227,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Command: []string{ "/bin/sh", "-ec", - "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-json=false", + "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-level=debug \\\n -log-json=false", }, Env: []corev1.EnvVar{ { @@ -279,7 +310,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "-addresses", "", "-grpc-port=0", - "-log-level=", + "-log-level=debug", "-log-json=false", "-envoy-concurrency=1", "-tls-disabled", @@ -336,10 +367,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", }, - { - Name: "DP_CREDENTIAL_LOGIN_META1", - Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", - }, { Name: "DP_SERVICE_NODE_NAME", Value: "$(NODE_NAME)-virtual", @@ -494,6 +521,11 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Container: &meshv2beta1.GatewayClassContainerConfig{ HostPort: 8080, PortModifier: 8000, + Consul: meshv2beta1.GatewayClassConsulConfig{ + Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ + Level: "debug", + }, + }, }, NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, Replicas: &meshv2beta1.GatewayClassReplicasConfig{ @@ -509,6 +541,23 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { WhenUnsatisfiable: "DoNotSchedule", }, }, + InitContainer: &meshv2beta1.GatewayClassInitContainerConfig{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("100m"), + "memory": resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("200m"), + "memory": resource.MustParse("228Mi"), + }, + }, + Consul: meshv2beta1.GatewayClassConsulConfig{ + Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ + Level: "debug", + }, + }, + }, }, }, }, @@ -571,7 +620,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Command: []string{ "/bin/sh", "-ec", - "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-json=false", + "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-level=debug \\\n -log-json=false", }, Env: []corev1.EnvVar{ { @@ -641,7 +690,16 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Value: "", }, }, - Resources: corev1.ResourceRequirements{}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("100m"), + "memory": resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("200m"), + "memory": resource.MustParse("228Mi"), + }, + }, VolumeMounts: []corev1.VolumeMount{ { Name: "consul-mesh-inject-data", @@ -657,7 +715,7 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { "-addresses", "", "-grpc-port=0", - "-log-level=", + "-log-level=debug", "-log-json=false", "-envoy-concurrency=1", "-ca-certs=/consul/mesh-inject/consul-ca.pem", @@ -714,10 +772,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", }, - { - Name: "DP_CREDENTIAL_LOGIN_META1", - Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", - }, { Name: "DP_SERVICE_NODE_NAME", Value: "$(NODE_NAME)-virtual", @@ -1003,10 +1057,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", }, - { - Name: "DP_CREDENTIAL_LOGIN_META1", - Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", - }, { Name: "DP_SERVICE_NODE_NAME", Value: "$(NODE_NAME)-virtual", diff --git a/control-plane/gateways/config.go b/control-plane/gateways/gateway_config.go similarity index 73% rename from control-plane/gateways/config.go rename to control-plane/gateways/gateway_config.go index 265678ea8c..551dc175b2 100644 --- a/control-plane/gateways/config.go +++ b/control-plane/gateways/gateway_config.go @@ -17,6 +17,7 @@ type GatewayConfig struct { // AuthMethod method used to authenticate with Consul Server. AuthMethod string + // ConsulTenancyConfig is the configuration for the Consul Tenancy feature. ConsulTenancyConfig common.ConsulTenancyConfig // LogLevel is the logging level of the deployed Consul Dataplanes. @@ -42,7 +43,13 @@ type GatewayConfig struct { MapPrivilegedServicePorts int } +// GatewayResources is a collection of Kubernetes resources for a Gateway. type GatewayResources struct { + // GatewayClassConfigs is a list of GatewayClassConfig resources which are + // responsible for defining configuration shared across all gateway kinds. GatewayClassConfigs []*v2beta1.GatewayClassConfig `json:"gatewayClassConfigs"` - MeshGateways []*v2beta1.MeshGateway `json:"meshGateways"` + // MeshGateways is a list of MeshGateway resources which are responsible for + // defining the configuration for a specific mesh gateway. + // Deployments of mesh gateways have a one-to-one relationship with MeshGateway resources. + MeshGateways []*v2beta1.MeshGateway `json:"meshGateways"` } diff --git a/control-plane/gateways/metadata.go b/control-plane/gateways/metadata.go index 32357c63a7..e1479ef3f2 100644 --- a/control-plane/gateways/metadata.go +++ b/control-plane/gateways/metadata.go @@ -60,6 +60,30 @@ func (b *meshGatewayBuilder) labelsForDeployment() map[string]string { return labels } +func (b *meshGatewayBuilder) logLevelForDataplaneContainer() string { + if b.config.LogLevel != "" { + return b.config.LogLevel + } + + if b.gcc == nil || b.gcc.Spec.Deployment.Container == nil { + return "" + } + + return b.gcc.Spec.Deployment.Container.Consul.Logging.Level +} + +func (b *meshGatewayBuilder) logLevelForInitContainer() string { + if b.config.LogLevel != "" { + return b.config.LogLevel + } + + if b.gcc == nil || b.gcc.Spec.Deployment.InitContainer == nil { + return "" + } + + return b.gcc.Spec.Deployment.InitContainer.Consul.Logging.Level +} + func (b *meshGatewayBuilder) labelsForRole() map[string]string { if b.gcc == nil { return defaultLabels diff --git a/control-plane/gateways/metadata_test.go b/control-plane/gateways/metadata_test.go index b0c1496884..7505867992 100644 --- a/control-plane/gateways/metadata_test.go +++ b/control-plane/gateways/metadata_test.go @@ -259,6 +259,53 @@ func TestNewMeshGatewayBuilder_Labels(t *testing.T) { } } +// The LogLevel for deployment containers may be set on the Gateway Class Config or the Gateway Config. +// If it is set on both, the Gateway Config takes precedence. +func TestMeshGatewayBuilder_LogLevel(t *testing.T) { + debug := "debug" + info := "info" + + testCases := map[string]struct { + GatewayLogLevel string + GCCLogLevel string + }{ + "Set on Gateway": { + GatewayLogLevel: debug, + GCCLogLevel: "", + }, + "Set on GCC": { + GatewayLogLevel: "", + GCCLogLevel: debug, + }, + "Set on both": { + GatewayLogLevel: debug, + GCCLogLevel: info, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + + gcc := &meshv2beta1.GatewayClassConfig{ + Spec: meshv2beta1.GatewayClassConfigSpec{ + Deployment: meshv2beta1.GatewayClassDeploymentConfig{ + Container: &meshv2beta1.GatewayClassContainerConfig{ + Consul: meshv2beta1.GatewayClassConsulConfig{ + Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ + Level: testCase.GCCLogLevel, + }, + }, + }, + }, + }, + } + b := NewMeshGatewayBuilder(&meshv2beta1.MeshGateway{}, GatewayConfig{LogLevel: testCase.GatewayLogLevel}, gcc) + + assert.Equal(t, debug, b.logLevelForDataplaneContainer()) + }) + } +} + func Test_computeAnnotationsOrLabels(t *testing.T) { gatewaySet := map[string]string{ "service.beta.kubernetes.io/aws-load-balancer-internal": "true", // Will not be inherited From 602ffde5da5c8af4dccb0b65ce581682848469dd Mon Sep 17 00:00:00 2001 From: Anita Akaeze Date: Thu, 18 Jan 2024 14:40:23 -0800 Subject: [PATCH 573/592] NET-7179: Update MeshGateway to use new proto with workload selector (#3465) * NET-7179: Update MeshGateway to use new proto with workload selector * fix import * Update control-plane/api/mesh/v2beta1/mesh_gateway_types.go Co-authored-by: Nathan Coleman * remove worload override * fix imports --------- Co-authored-by: Nathan Coleman --- charts/consul/templates/crd-meshgateways.yaml | 14 ++++++++++++++ .../templates/gateway-resources-configmap.yaml | 3 +++ .../mesh.consul.hashicorp.com_meshgateways.yaml | 14 ++++++++++++++ control-plane/gateways/deployment_test.go | 3 +-- control-plane/go.mod | 2 +- control-plane/go.sum | 4 ++-- .../subcommand/gateway-resources/command_test.go | 3 +-- 7 files changed, 36 insertions(+), 7 deletions(-) diff --git a/charts/consul/templates/crd-meshgateways.yaml b/charts/consul/templates/crd-meshgateways.yaml index a7ebc1836d..6202add695 100644 --- a/charts/consul/templates/crd-meshgateways.yaml +++ b/charts/consul/templates/crd-meshgateways.yaml @@ -73,6 +73,20 @@ spec: type: object minItems: 1 type: array + workloads: + description: Selection of workloads to be configured as mesh gateways + properties: + filter: + type: string + names: + items: + type: string + type: array + prefixes: + items: + type: string + type: array + type: object type: object status: properties: diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml index 2cf7ae86d0..842ba6690d 100644 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ b/charts/consul/templates/gateway-resources-configmap.yaml @@ -125,5 +125,8 @@ data: - name: "wan" port: {{ .Values.meshGateway.service.port }} protocol: "TCP" + workloads: + prefixes: + - "mesh-gateway" {{- end }} {{- end }} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml index 2e5ea1596f..47f2fcfba8 100644 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml @@ -69,6 +69,20 @@ spec: type: object minItems: 1 type: array + workloads: + description: Selection of workloads to be configured as mesh gateways + properties: + filter: + type: string + names: + items: + type: string + type: array + prefixes: + items: + type: string + type: array + type: object type: object status: properties: diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 37fcb80c59..66b834a951 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -14,10 +14,9 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" ) const testCert = `-----BEGIN CERTIFICATE----- │ diff --git a/control-plane/go.mod b/control-plane/go.mod index 2e9c55afdb..ac4e18079b 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240111191232-1e351e286e08 +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240116214818-6a8554317502 replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f diff --git a/control-plane/go.sum b/control-plane/go.sum index debfd13a28..835183b4c2 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -265,8 +265,8 @@ github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+ github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240111191232-1e351e286e08 h1:oYbCbKfscW83pWuI/BkVajKH93qCJ1tPzZzGAmj5Ivo= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240111191232-1e351e286e08/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240116214818-6a8554317502 h1:DeUPf9K/hAXVY6bTheu1mDsszkOzB3Dgz3hLVSUp8GA= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240116214818-6a8554317502/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 4d88e5fc13..5fbba60230 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -17,10 +17,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - meshv2beta1 "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + meshv2beta1 "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" ) func TestRun_flagValidation(t *testing.T) { From 20a23977648faece1f4b8f62341d5eef43cba9a3 Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 19 Jan 2024 17:05:40 -0500 Subject: [PATCH 574/592] CRT build failing for Go (#3490) --- .github/workflows/build.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87064484af..5506c017c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -243,7 +243,7 @@ jobs: build-docker: name: Docker ${{ matrix.goarch }} ${{ matrix.fips }} default release build - needs: [get-product-version, build] + needs: [get-product-version, get-go-version, build] runs-on: ubuntu-latest strategy: matrix: @@ -279,7 +279,7 @@ jobs: echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV - name: Docker Build (Action) - uses: hashicorp/actions-docker-build@v1 + uses: hashicorp/actions-docker-build@76d2fc91532d816ca2660d8f3139e432ac3700fd if: ${{ !matrix.fips }} with: smoke_test: | @@ -302,9 +302,11 @@ jobs: docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-${{ github.sha }} docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }} docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-${{ github.sha }} + extra_build_args: | + GOLANG_VERSION=${{ needs.get-go-version.outputs.go-version }} - name: Docker FIPS Build (Action) - uses: hashicorp/actions-docker-build@v1 + uses: hashicorp/actions-docker-build@76d2fc91532d816ca2660d8f3139e432ac3700fd if: ${{ matrix.fips }} with: smoke_test: | @@ -327,10 +329,12 @@ jobs: docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.full_dev_tag }}-${{ github.sha }} docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }} docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }}-${{ github.sha }} + extra_build_args: | + GOLANG_VERSION=${{ needs.get-go-version.outputs.go-version }} build-docker-ubi: name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI builds - needs: [get-product-version, build] + needs: [get-product-version, get-go-version, build] runs-on: ubuntu-latest strategy: matrix: @@ -366,7 +370,7 @@ jobs: - name: Docker Build (Action) if: ${{ !matrix.fips }} - uses: hashicorp/actions-docker-build@v1 + uses: hashicorp/actions-docker-build@76d2fc91532d816ca2660d8f3139e432ac3700fd with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -389,9 +393,12 @@ jobs: docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi-${{ github.sha }} redhat_tag: quay.io/redhat-isv-containers/611ca2f89a9b407267837100:${{env.version}}-ubi + extra_build_args: | + GOLANG_VERSION=${{ needs.get-go-version.outputs.go-version }} + - name: Docker FIPS Build (Action) if: ${{ matrix.fips }} - uses: hashicorp/actions-docker-build@v1 + uses: hashicorp/actions-docker-build@76d2fc91532d816ca2660d8f3139e432ac3700fd with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -407,3 +414,5 @@ jobs: bin_name: consul-k8s-control-plane workdir: control-plane redhat_tag: quay.io/redhat-isv-containers/6486b1beabfc4e51588c0416:${{env.version}}-ubi # this is different than the non-FIPS one + extra_build_args: | + GOLANG_VERSION=${{ needs.get-go-version.outputs.go-version }} From 54714a40b9f5bcc27fa2b80e32fcc341d29160bf Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Mon, 22 Jan 2024 15:03:18 -0500 Subject: [PATCH 575/592] Use golang -alpine image when building go-discover (#3501) Use golang -alpine image when building go-discover --- control-plane/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index 139f4137e1..dda0ab6a96 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -17,7 +17,7 @@ # go-discover builds the discover binary (which we don't currently publish # either). ARG GOLANG_VERSION -FROM golang:${GOLANG_VERSION} as go-discover +FROM golang:${GOLANG_VERSION}-alpine as go-discover RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@214571b6a5309addf3db7775f4ee8cf4d264fd5f # dev copies the binary from a local build From 83ed1901a67cef08ca22d390524f7ce48e971c6e Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Wed, 24 Jan 2024 08:04:26 -0600 Subject: [PATCH 576/592] Add CaseInsenstive field to ServiceRouter. (#3502) * Bump consul api version. * Add CaseInsensitive field to service router match. * Add changelog. --- .changelog/3502.txt | 3 ++ .../consul/templates/crd-servicerouters.yaml | 4 +++ .../api/v1alpha1/servicerouter_types.go | 15 ++++++---- .../api/v1alpha1/servicerouter_types_test.go | 28 +++++++++++-------- .../consul.hashicorp.com_servicerouters.yaml | 4 +++ control-plane/go.mod | 2 +- control-plane/go.sum | 13 +++++++++ 7 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 .changelog/3502.txt diff --git a/.changelog/3502.txt b/.changelog/3502.txt new file mode 100644 index 0000000000..56b7334081 --- /dev/null +++ b/.changelog/3502.txt @@ -0,0 +1,3 @@ +```release-note:improvement +control-plane: Add `CaseInsensitive` flag to service-routers that allows paths and path prefixes to ignore URL upper and lower casing. +``` diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index 882325598d..c7924081fd 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -185,6 +185,10 @@ spec: http: description: HTTP is a set of http-specific match criteria. properties: + caseInsensitive: + description: CaseInsensitive configures PathExact and + PathPrefix matches to ignore upper/lower casing. + type: boolean header: description: Header is a set of criteria that can match on HTTP request headers. If more than one is configured diff --git a/control-plane/api/v1alpha1/servicerouter_types.go b/control-plane/api/v1alpha1/servicerouter_types.go index 7054d9e9e2..a06977b9e7 100644 --- a/control-plane/api/v1alpha1/servicerouter_types.go +++ b/control-plane/api/v1alpha1/servicerouter_types.go @@ -75,6 +75,8 @@ type ServiceRouteMatch struct { } type ServiceRouteHTTPMatch struct { + // CaseInsensitive configures PathExact and PathPrefix matches to ignore upper/lower casing. + CaseInsensitive bool `json:"caseInsensitive,omitempty"` // PathExact is an exact path to match on the HTTP request path. PathExact string `json:"pathExact,omitempty"` // PathPrefix is a path prefix to match on the HTTP request path. @@ -422,12 +424,13 @@ func (in *ServiceRouteHTTPMatch) toConsul() *capi.ServiceRouteHTTPMatch { query = append(query, q.toConsul()) } return &capi.ServiceRouteHTTPMatch{ - PathExact: in.PathExact, - PathPrefix: in.PathPrefix, - PathRegex: in.PathRegex, - Header: header, - QueryParam: query, - Methods: in.Methods, + CaseInsensitive: in.CaseInsensitive, + PathExact: in.PathExact, + PathPrefix: in.PathPrefix, + PathRegex: in.PathRegex, + Header: header, + QueryParam: query, + Methods: in.Methods, } } diff --git a/control-plane/api/v1alpha1/servicerouter_types_test.go b/control-plane/api/v1alpha1/servicerouter_types_test.go index f7eb766945..acd4437262 100644 --- a/control-plane/api/v1alpha1/servicerouter_types_test.go +++ b/control-plane/api/v1alpha1/servicerouter_types_test.go @@ -53,9 +53,10 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { { Match: &ServiceRouteMatch{ HTTP: &ServiceRouteHTTPMatch{ - PathExact: "pathExact", - PathPrefix: "pathPrefix", - PathRegex: "pathRegex", + CaseInsensitive: true, + PathExact: "pathExact", + PathPrefix: "pathPrefix", + PathRegex: "pathRegex", Header: []ServiceRouteHTTPMatchHeader{ { Name: "name", @@ -131,9 +132,10 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { { Match: &capi.ServiceRouteMatch{ HTTP: &capi.ServiceRouteHTTPMatch{ - PathExact: "pathExact", - PathPrefix: "pathPrefix", - PathRegex: "pathRegex", + CaseInsensitive: true, + PathExact: "pathExact", + PathPrefix: "pathPrefix", + PathRegex: "pathRegex", Header: []capi.ServiceRouteHTTPMatchHeader{ { Name: "name", @@ -259,9 +261,10 @@ func TestServiceRouter_ToConsul(t *testing.T) { { Match: &ServiceRouteMatch{ HTTP: &ServiceRouteHTTPMatch{ - PathExact: "pathExact", - PathPrefix: "pathPrefix", - PathRegex: "pathRegex", + CaseInsensitive: true, + PathExact: "pathExact", + PathPrefix: "pathPrefix", + PathRegex: "pathRegex", Header: []ServiceRouteHTTPMatchHeader{ { Name: "name", @@ -337,9 +340,10 @@ func TestServiceRouter_ToConsul(t *testing.T) { { Match: &capi.ServiceRouteMatch{ HTTP: &capi.ServiceRouteHTTPMatch{ - PathExact: "pathExact", - PathPrefix: "pathPrefix", - PathRegex: "pathRegex", + CaseInsensitive: true, + PathExact: "pathExact", + PathPrefix: "pathPrefix", + PathRegex: "pathRegex", Header: []capi.ServiceRouteHTTPMatchHeader{ { Name: "name", diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 5628070226..41d4bfbd81 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -181,6 +181,10 @@ spec: http: description: HTTP is a set of http-specific match criteria. properties: + caseInsensitive: + description: CaseInsensitive configures PathExact and + PathPrefix matches to ignore upper/lower casing. + type: boolean header: description: Header is a set of criteria that can match on HTTP request headers. If more than one is configured diff --git a/control-plane/go.mod b/control-plane/go.mod index ac4e18079b..46333b3ab1 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -17,7 +17,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed github.com/hashicorp/consul-server-connection-manager v0.1.6 - github.com/hashicorp/consul/api v1.26.1 + github.com/hashicorp/consul/api v1.10.1-0.20240122152324-758ddf84e9c9 github.com/hashicorp/consul/proto-public v0.5.1 github.com/hashicorp/consul/sdk v0.15.0 github.com/hashicorp/go-bexpr v0.1.11 diff --git a/control-plane/go.sum b/control-plane/go.sum index 835183b4c2..13af622438 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -81,6 +81,7 @@ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -127,6 +128,7 @@ github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TR github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -216,6 +218,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -257,12 +260,18 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed h1:eM9tGgSqAZbm4Ndkp35Dg8YROT0dNH3ghTYu5pcUIAc= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= 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.10.1-0.20240122152324-758ddf84e9c9 h1:qaS6rE768dt5hGPl2y4DIABXF4eA23BNSmWFpEr3kWQ= +github.com/hashicorp/consul/api v1.10.1-0.20240122152324-758ddf84e9c9/go.mod h1:gInwZGrnWlE1Vvq6rSD5pUf6qwNa69NTLLknbdwQRUk= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= github.com/hashicorp/consul/proto-public v0.1.2-0.20240116214818-6a8554317502 h1:DeUPf9K/hAXVY6bTheu1mDsszkOzB3Dgz3hLVSUp8GA= @@ -423,6 +432,7 @@ github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQh github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -434,8 +444,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= @@ -455,6 +467,7 @@ github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otz github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 89f30a62b3ff1bbe231a83a6a1b894065182b7fa Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:38:55 -0800 Subject: [PATCH 577/592] Set ReadOnlyRootFilesystem and AllowPrivilegeEscalation to false (#3498) Tighten up privileges for consul-dataplane and connect-init containers when CNI is enabled. --- .../webhook/consul_dataplane_sidecar.go | 9 ++++--- .../webhook/consul_dataplane_sidecar_test.go | 27 ++++++++++--------- .../connect-inject/webhook/container_init.go | 2 ++ .../webhook/container_init_test.go | 2 ++ .../webhookv2/consul_dataplane_sidecar.go | 9 ++++--- .../consul_dataplane_sidecar_test.go | 27 ++++++++++--------- .../webhookv2/container_init.go | 2 ++ .../webhookv2/container_init_test.go | 2 ++ 8 files changed, 48 insertions(+), 32 deletions(-) diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index a567682356..6bd89010ff 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -229,10 +229,11 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor } } container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), + ReadOnlyRootFilesystem: pointer.Bool(true), } } diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index 1dd5525e3c..6d46d6a3d3 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -803,20 +803,22 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { tproxyEnabled: false, openShiftEnabled: false, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), }, }, "tproxy enabled; openshift disabled": { tproxyEnabled: true, openShiftEnabled: false, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), }, }, "tproxy disabled; openshift enabled": { @@ -828,10 +830,11 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { tproxyEnabled: true, openShiftEnabled: true, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), }, }, } diff --git a/control-plane/connect-inject/webhook/container_init.go b/control-plane/connect-inject/webhook/container_init.go index f3f1cbc695..2626b03689 100644 --- a/control-plane/connect-inject/webhook/container_init.go +++ b/control-plane/connect-inject/webhook/container_init.go @@ -263,6 +263,8 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), } } } diff --git a/control-plane/connect-inject/webhook/container_init_test.go b/control-plane/connect-inject/webhook/container_init_test.go index fa2a95dbf9..8feac95b84 100644 --- a/control-plane/connect-inject/webhook/container_init_test.go +++ b/control-plane/connect-inject/webhook/container_init_test.go @@ -302,6 +302,8 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), } } else if c.expTproxyEnabled { expectedSecurityContext = &corev1.SecurityContext{ diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go index 434899d67e..d3ba5095ac 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go @@ -193,10 +193,11 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor } } container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), } } diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go index 994bf4e446..cf9124c673 100644 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go @@ -467,20 +467,22 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { tproxyEnabled: false, openShiftEnabled: false, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), }, }, "tproxy enabled; openshift disabled": { tproxyEnabled: true, openShiftEnabled: false, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), }, }, "tproxy disabled; openshift enabled": { @@ -492,10 +494,11 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { tproxyEnabled: true, openShiftEnabled: true, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), }, }, } diff --git a/control-plane/connect-inject/webhookv2/container_init.go b/control-plane/connect-inject/webhookv2/container_init.go index 6420b9e97d..7afcaefd33 100644 --- a/control-plane/connect-inject/webhookv2/container_init.go +++ b/control-plane/connect-inject/webhookv2/container_init.go @@ -232,6 +232,8 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), } } } diff --git a/control-plane/connect-inject/webhookv2/container_init_test.go b/control-plane/connect-inject/webhookv2/container_init_test.go index 33189f0d0c..b85ecd3ba5 100644 --- a/control-plane/connect-inject/webhookv2/container_init_test.go +++ b/control-plane/connect-inject/webhookv2/container_init_test.go @@ -293,6 +293,8 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), } } else if c.expTproxyEnabled { expectedSecurityContext = &corev1.SecurityContext{ From 26b8c22b05d0515d66b030888b46c9006e92b93b Mon Sep 17 00:00:00 2001 From: John Landa Date: Fri, 26 Jan 2024 12:45:23 -0700 Subject: [PATCH 578/592] Update tests of traffic permission deny action to enterprise only (#3521) Add imports --- .../consul_resource_controller_ent_test.go | 188 ++++++++++++++++++ .../consul_resource_controller_test.go | 10 +- 2 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 control-plane/controllers/resources/consul_resource_controller_ent_test.go diff --git a/control-plane/controllers/resources/consul_resource_controller_ent_test.go b/control-plane/controllers/resources/consul_resource_controller_ent_test.go new file mode 100644 index 0000000000..56cb6c9b49 --- /dev/null +++ b/control-plane/controllers/resources/consul_resource_controller_ent_test.go @@ -0,0 +1,188 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package resources + +import ( + "context" + "testing" + + "github.com/go-logr/logr" + logrtest "github.com/go-logr/logr/testr" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" +) + +// TestConsulResourceController_UpdatesConsulResourceEnt tests is a mirror of the CE test which also tests the +// enterprise traffic permissions deny action. +func TestConsulResourceController_UpdatesConsulResourceEnt(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + resource common.ConsulResource + expected *pbauth.TrafficPermissions + reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler + updateF func(config common.ConsulResource) + unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message + }{ + { + name: "TrafficPermissions", + resource: &v2beta1.TrafficPermissions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-traffic-permission", + Namespace: metav1.NamespaceDefault, + }, + Spec: pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + Namespace: "the space namespace space", + }, + { + IdentityName: "source-identity", + }, + }, + // TODO: enable this when L7 traffic permissions are supported + //DestinationRules: []*pbauth.DestinationRule{ + // { + // PathExact: "/hello", + // Methods: []string{"GET", "POST"}, + // PortNames: []string{"web", "admin"}, + // }, + //}, + }, + }, + }, + }, + expected: &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "destination-identity", + }, + Action: pbauth.Action_ACTION_DENY, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + Namespace: "the space namespace space", + Partition: common.DefaultConsulPartition, + Peer: constants.DefaultConsulPeer, + }, + }, + //DestinationRules: []*pbauth.DestinationRule{ + // { + // PathExact: "/hello", + // Methods: []string{"GET", "POST"}, + // PortNames: []string{"web", "admin"}, + // }, + //}, + }, + }, + }, + reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { + return &TrafficPermissionsController{ + Client: client, + Log: logger, + Controller: &ConsulResourceController{ + ConsulClientConfig: cfg, + ConsulServerConnMgr: watcher, + }, + } + }, + updateF: func(resource common.ConsulResource) { + trafficPermissions := resource.(*v2beta1.TrafficPermissions) + trafficPermissions.Spec.Action = pbauth.Action_ACTION_DENY + trafficPermissions.Spec.Permissions[0].Sources = trafficPermissions.Spec.Permissions[0].Sources[:1] + }, + unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { + data := resource.Data + + trafficPermission := &pbauth.TrafficPermissions{} + require.NoError(t, data.UnmarshalTo(trafficPermission)) + return trafficPermission + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + ctx := context.Background() + + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, c.resource) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.resource).Build() + + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + c.Experiments = []string{"resource-apis"} + }) + + // We haven't run reconcile yet, so we must create the resource + // in Consul ourselves. + { + resource := c.resource.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) + req := &pbresource.WriteRequest{Resource: resource} + _, err := testClient.ResourceClient.Write(ctx, req) + require.NoError(t, err) + } + + // Now run reconcile which should update the entry in Consul. + { + namespacedName := types.NamespacedName{ + Namespace: metav1.NamespaceDefault, + Name: c.resource.KubernetesName(), + } + // First get it, so we have the latest revision number. + err := fakeClient.Get(ctx, namespacedName, c.resource) + require.NoError(t, err) + + // Update the entry in Kube and run reconcile. + c.updateF(c.resource) + err = fakeClient.Update(ctx, c.resource) + require.NoError(t, err) + r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) + resp, err := r.Reconcile(ctx, ctrl.Request{ + NamespacedName: namespacedName, + }) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Now check that the object in Consul is as expected. + req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} + res, err := testClient.ResourceClient.Read(ctx, req) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, c.resource.GetName(), res.GetResource().GetId().GetName()) + + actual := c.unmarshal(t, res.GetResource()) + opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) + diff := cmp.Diff(c.expected, actual, opts...) + require.Equal(t, "", diff, "TrafficPermissions do not match") + } + }) + } +} diff --git a/control-plane/controllers/resources/consul_resource_controller_test.go b/control-plane/controllers/resources/consul_resource_controller_test.go index e8f63514ab..cb8c1cf6bd 100644 --- a/control-plane/controllers/resources/consul_resource_controller_test.go +++ b/control-plane/controllers/resources/consul_resource_controller_test.go @@ -11,9 +11,6 @@ import ( "github.com/go-logr/logr" logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" @@ -25,6 +22,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" @@ -226,7 +227,7 @@ func TestConsulResourceController_UpdatesConsulResource(t *testing.T) { Destination: &pbauth.Destination{ IdentityName: "destination-identity", }, - Action: pbauth.Action_ACTION_DENY, + Action: pbauth.Action_ACTION_ALLOW, Permissions: []*pbauth.Permission{ { Sources: []*pbauth.Source{ @@ -258,7 +259,6 @@ func TestConsulResourceController_UpdatesConsulResource(t *testing.T) { }, updateF: func(resource common.ConsulResource) { trafficPermissions := resource.(*v2beta1.TrafficPermissions) - trafficPermissions.Spec.Action = pbauth.Action_ACTION_DENY trafficPermissions.Spec.Permissions[0].Sources = trafficPermissions.Spec.Permissions[0].Sources[:1] }, unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { From 901caf387b6f80b4411bdae85b4562d311837bf3 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:07:21 -0800 Subject: [PATCH 579/592] Changelog for #3498 (#3515) --- .changelog/3498.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/3498.txt diff --git a/.changelog/3498.txt b/.changelog/3498.txt new file mode 100644 index 0000000000..7aed5a69af --- /dev/null +++ b/.changelog/3498.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cni: When CNI is enabled, set ReadOnlyRootFilesystem=true and AllowPrivilegeEscalation=false for mesh pod init containers and AllowPrivilegeEscalation=false for consul-dataplane containers (ReadOnlyRootFilesystem was already true for consul-dataplane containers). +``` From 649efe025f02e2e0f9e82be4fdb9592e7d61d5c8 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Mon, 29 Jan 2024 12:48:07 -0500 Subject: [PATCH 580/592] [NET-7492, NET-7495] Support -server-watch-disabled, use -proxy-* args instead of -service-* (#3526) * Support -server-watch-disabled, use -proxy-* args instead of -service-* * Remove now-unused constant * Update test assertions * gofmt --- control-plane/gateways/constants.go | 1 - .../gateways/deployment_dataplane_container.go | 11 +++++------ control-plane/gateways/deployment_test.go | 12 ------------ control-plane/gateways/gateway_config.go | 3 +++ .../subcommand/inject-connect/v2controllers.go | 1 + 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/control-plane/gateways/constants.go b/control-plane/gateways/constants.go index 7ca89ef1f7..ac0242bd2d 100644 --- a/control-plane/gateways/constants.go +++ b/control-plane/gateways/constants.go @@ -13,7 +13,6 @@ const ( // Dataplane Configuration Environment variables. envDPProxyId = "DP_PROXY_ID" envDPCredentialLoginMeta = "DP_CREDENTIAL_LOGIN_META" - envDPServiceNodeName = "DP_SERVICE_NODE_NAME" // Init Container Configuration Environment variables. envConsulAddresses = "CONSUL_ADDRESSES" diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go index 967c8b6f1e..9dc7dad141 100644 --- a/control-plane/gateways/deployment_dataplane_container.go +++ b/control-plane/gateways/deployment_dataplane_container.go @@ -93,10 +93,6 @@ func (b *meshGatewayBuilder) consulDataplaneContainer(containerConfig v2beta1.Ga Name: envDPCredentialLoginMeta, Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", }, - { - Name: envDPServiceNodeName, - Value: "$(NODE_NAME)-virtual", - }, }, VolumeMounts: []corev1.VolumeMount{ { @@ -175,11 +171,14 @@ func (b *meshGatewayBuilder) dataplaneArgs(bearerTokenFile string) ([]string, er args = append(args, "-login-partition="+b.config.ConsulTenancyConfig.ConsulPartition) } } + if b.config.SkipServerWatch { + args = append(args, "-server-watch-disabled=true") + } if b.config.ConsulTenancyConfig.EnableConsulNamespaces { - args = append(args, "-service-namespace="+consulNamespace) + args = append(args, "-proxy-namespace="+consulNamespace) } if b.config.ConsulTenancyConfig.ConsulPartition != "" { - args = append(args, "-service-partition="+b.config.ConsulTenancyConfig.ConsulPartition) + args = append(args, "-proxy-partition="+b.config.ConsulTenancyConfig.ConsulPartition) } args = append(args, buildTLSArgs(b.config)...) diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go index 66b834a951..b3f06ad1d2 100644 --- a/control-plane/gateways/deployment_test.go +++ b/control-plane/gateways/deployment_test.go @@ -366,10 +366,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", }, - { - Name: "DP_SERVICE_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "DP_ENVOY_READY_BIND_ADDRESS", Value: "", @@ -771,10 +767,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", }, - { - Name: "DP_SERVICE_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "DP_ENVOY_READY_BIND_ADDRESS", Value: "", @@ -1056,10 +1048,6 @@ func Test_meshGatewayBuilder_Deployment(t *testing.T) { Name: "DP_CREDENTIAL_LOGIN_META", Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", }, - { - Name: "DP_SERVICE_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, { Name: "DP_ENVOY_READY_BIND_ADDRESS", Value: "", diff --git a/control-plane/gateways/gateway_config.go b/control-plane/gateways/gateway_config.go index 551dc175b2..de3202e29e 100644 --- a/control-plane/gateways/gateway_config.go +++ b/control-plane/gateways/gateway_config.go @@ -41,6 +41,9 @@ type GatewayConfig struct { // MapPrivilegedServicePorts is the value which Consul will add to privileged container port values (ports < 1024) // defined on a Gateway. MapPrivilegedServicePorts int + + // TODO(nathancoleman) Add doc + SkipServerWatch bool } // GatewayResources is a collection of Kubernetes resources for a Gateway. diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 67c707b592..79f8223fae 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -233,6 +233,7 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage TLSEnabled: c.consul.UseTLS, ConsulTLSServerName: c.consul.TLSServerName, ConsulCACert: string(c.caCertPem), + SkipServerWatch: c.consul.SkipServerWatch, }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", common.MeshGateway) From 1e67acc165ea2085d0a7e9ed776451b55a175494 Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:04:18 -0600 Subject: [PATCH 581/592] NET-7153 Generate API Gateway CRDs (#3506) * generate crds * fix issue with test on module update * generate crds --- charts/consul/templates/crd-apigateways.yaml | 240 ++++++++++++++++++ .../v2beta1/traffic_permissions_types_test.go | 136 +++++----- .../api/mesh/v2beta1/api_gateway_types.go | 151 +++++++++++ .../api/mesh/v2beta1/zz_generated.deepcopy.go | 63 +++++ ...mesh.consul.hashicorp.com_apigateways.yaml | 235 +++++++++++++++++ control-plane/go.mod | 2 +- control-plane/go.sum | 17 +- 7 files changed, 766 insertions(+), 78 deletions(-) create mode 100644 charts/consul/templates/crd-apigateways.yaml create mode 100644 control-plane/api/mesh/v2beta1/api_gateway_types.go create mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml diff --git a/charts/consul/templates/crd-apigateways.yaml b/charts/consul/templates/crd-apigateways.yaml new file mode 100644 index 0000000000..755fb05b64 --- /dev/null +++ b/charts/consul/templates/crd-apigateways.yaml @@ -0,0 +1,240 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd + name: apigateways.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: APIGateway + listKind: APIGatewayList + plural: apigateways + singular: apigateway + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: APIGateway is the Schema for the API Gateway + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + gatewayClassName: + description: GatewayClassName is the name of the GatewayClass used + by the APIGateway + type: string + listeners: + items: + properties: + hostname: + description: Hostname is the host name that a listener should + be bound to, if unspecified, the listener accepts requests + for all hostnames. + type: string + name: + description: Name is the name of the listener in a given gateway. + This must be unique within a gateway. + type: string + port: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + protocol: + description: Protocol is the protocol that a listener should + use, it must either be "http" or "tcp" + type: string + tls: + description: TLS is the TLS settings for the listener. + properties: + certificates: + description: Certificates is a set of references to certificates + that a gateway listener uses for TLS termination. + items: + description: Reference identifies which resource a condition + relates to, when it is not the core resource itself. + properties: + name: + description: Name is the user-given name of the resource + (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of the + resource the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units + (i.e. partition, namespace) in which the resource + resides. + properties: + namespace: + description: "Namespace further isolates resources + within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list resources + across all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list resources + across all partitions." + type: string + peerName: + description: "PeerName identifies which peer the + resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list resources + across all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. "catalog", + "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when + sweeping or backward-incompatible changes are + made to the group's resource types. + type: string + kind: + description: Kind identifies the specific resource + type within the group. + type: string + type: object + type: object + type: array + tlsParameters: + description: TLSParameters contains optional configuration + for running TLS termination. + properties: + cipherSuites: + items: + enum: + - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_GCM_SHA256 + - TLS_CIPHER_SUITE_AES256_SHA + - TLS_CIPHER_SUITE_ECDHE_ECDSA_CHACHA20_POLY1305 + - TLS_CIPHER_SUITE_ECDHE_RSA_AES128_GCM_SHA256 + - TLS_CIPHER_SUITE_ECDHE_RSA_CHACHA20_POLY1305 + - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_SHA + - TLS_CIPHER_SUITE_ECDHE_RSA_AES128_SHA + - TLS_CIPHER_SUITE_AES128_GCM_SHA256 + - TLS_CIPHER_SUITE_AES128_SHA + - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_GCM_SHA384 + - TLS_CIPHER_SUITE_ECDHE_RSA_AES256_GCM_SHA384 + - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_SHA + - TLS_CIPHER_SUITE_ECDHE_RSA_AES256_SHA + - TLS_CIPHER_SUITE_AES256_GCM_SHA384 + format: int32 + type: string + type: array + maxVersion: + enum: + - TLS_VERSION_AUTO + - TLS_VERSION_1_0 + - TLS_VERSION_1_1 + - TLS_VERSION_1_2 + - TLS_VERSION_1_3 + - TLS_VERSION_INVALID + - TLS_VERSION_UNSPECIFIED + format: int32 + type: string + minVersion: + enum: + - TLS_VERSION_AUTO + - TLS_VERSION_1_0 + - TLS_VERSION_1_1 + - TLS_VERSION_1_2 + - TLS_VERSION_1_3 + - TLS_VERSION_INVALID + - TLS_VERSION_UNSPECIFIED + format: int32 + type: string + type: object + type: object + type: object + minItems: 1 + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go b/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go index 79b5e71b85..f13503f6de 100644 --- a/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go +++ b/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go @@ -186,14 +186,16 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, + Headers: []*pbauth.DestinationRuleHeader{ + { + Name: "x-consul-test", + Present: true, + Exact: "true", + Prefix: "prefix", + Suffix: "suffix", + Regex: "reg.*ex", + Invert: true, + }, }, Methods: []string{"GET", "POST"}, Exclude: []*pbauth.ExcludePermissionRule{ @@ -201,14 +203,16 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, + Headers: []*pbauth.DestinationRuleHeader{ + { + Name: "x-consul-not-test", + Present: true, + Exact: "false", + Prefix: "~prefix", + Suffix: "~suffix", + Regex: "~reg.*ex", + Invert: true, + }, }, Methods: []string{"DELETE"}, PortNames: []string{"log"}, @@ -257,14 +261,16 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, + Headers: []*pbauth.DestinationRuleHeader{ + { + Name: "x-consul-test", + Present: true, + Exact: "true", + Prefix: "prefix", + Suffix: "suffix", + Regex: "reg.*ex", + Invert: true, + }, }, Methods: []string{"GET", "POST"}, Exclude: []*pbauth.ExcludePermissionRule{ @@ -272,14 +278,16 @@ func TestTrafficPermissions_MatchesConsul(t *testing.T) { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, + Headers: []*pbauth.DestinationRuleHeader{ + { + Name: "x-consul-not-test", + Present: true, + Exact: "false", + Prefix: "~prefix", + Suffix: "~suffix", + Regex: "~reg.*ex", + Invert: true, + }, }, Methods: []string{"DELETE"}, PortNames: []string{"log"}, @@ -391,7 +399,7 @@ func TestTrafficPermissions_Resource(t *testing.T) { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ + Headers: []*pbauth.DestinationRuleHeader{{ Name: "x-consul-test", Present: true, Exact: "true", @@ -399,14 +407,14 @@ func TestTrafficPermissions_Resource(t *testing.T) { Suffix: "suffix", Regex: "reg.*ex", Invert: true, - }, + }}, Methods: []string{"GET", "POST"}, Exclude: []*pbauth.ExcludePermissionRule{ { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ + Headers: []*pbauth.DestinationRuleHeader{{ Name: "x-consul-not-test", Present: true, Exact: "false", @@ -414,7 +422,7 @@ func TestTrafficPermissions_Resource(t *testing.T) { Suffix: "~suffix", Regex: "~reg.*ex", Invert: true, - }, + }}, Methods: []string{"DELETE"}, PortNames: []string{"log"}, }, @@ -462,7 +470,7 @@ func TestTrafficPermissions_Resource(t *testing.T) { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ + Headers: []*pbauth.DestinationRuleHeader{{ Name: "x-consul-test", Present: true, Exact: "true", @@ -470,14 +478,14 @@ func TestTrafficPermissions_Resource(t *testing.T) { Suffix: "suffix", Regex: "reg.*ex", Invert: true, - }, + }}, Methods: []string{"GET", "POST"}, Exclude: []*pbauth.ExcludePermissionRule{ { PathExact: "/hello", PathPrefix: "/world", PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ + Headers: []*pbauth.DestinationRuleHeader{{ Name: "x-consul-not-test", Present: true, Exact: "false", @@ -485,7 +493,7 @@ func TestTrafficPermissions_Resource(t *testing.T) { Suffix: "~suffix", Regex: "~reg.*ex", Invert: true, - }, + }}, Methods: []string{"DELETE"}, PortNames: []string{"log"}, }, @@ -642,27 +650,31 @@ func TestTrafficPermissions_Validate(t *testing.T) { DestinationRules: []*pbauth.DestinationRule{ { PathExact: "/hello", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, + Headers: []*pbauth.DestinationRuleHeader{ + { + Name: "x-consul-test", + Present: true, + Exact: "true", + Prefix: "prefix", + Suffix: "suffix", + Regex: "reg.*ex", + Invert: true, + }, }, Methods: []string{"GET", "POST"}, Exclude: []*pbauth.ExcludePermissionRule{ { PathPrefix: "/world", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, + Headers: []*pbauth.DestinationRuleHeader{ + { + Name: "x-consul-not-test", + Present: true, + Exact: "false", + Prefix: "~prefix", + Suffix: "~suffix", + Regex: "~reg.*ex", + Invert: true, + }, }, Methods: []string{"DELETE"}, PortNames: []string{"log"}, @@ -937,9 +949,9 @@ func TestTrafficPermissions_Validate(t *testing.T) { }, }, expectedErrMsgs: []string{ - `spec.permissions[0].destinationRules[0]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[1]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[2]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[0]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[1]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[2]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, }, }, { @@ -979,9 +991,9 @@ func TestTrafficPermissions_Validate(t *testing.T) { }, }, expectedErrMsgs: []string{ - `spec.permissions[0].destinationRules[0].exclude[0]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[0].exclude[1]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[0].exclude[2]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[0].exclude[0]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[0].exclude[1]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, + `spec.permissions[0].destinationRules[0].exclude[2]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, }, }, } diff --git a/control-plane/api/mesh/v2beta1/api_gateway_types.go b/control-plane/api/mesh/v2beta1/api_gateway_types.go new file mode 100644 index 0000000000..70403789f8 --- /dev/null +++ b/control-plane/api/mesh/v2beta1/api_gateway_types.go @@ -0,0 +1,151 @@ +// // Copyright (c) HashiCorp, Inc. +// // SPDX-License-Identifier: MPL-2.0 +package v2beta1 + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/consul-k8s/control-plane/api/common" + inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" +) + +const ( + apiGatewayKubeKind = "gateway" +) + +func init() { + MeshSchemeBuilder.Register(&GatewayClass{}, &GatewayClassList{}) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// APIGateway is the Schema for the API Gateway +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" +// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" +// +kubebuilder:resource:scope=Cluster +type APIGateway struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec pbmesh.APIGateway `json:"spec,omitempty"` + Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// APIGatewayList contains a list of APIGateway. +type APIGatewayList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*APIGateway `json:"items"` +} + +func (in *APIGateway) ResourceID(namespace, partition string) *pbresource.ID { + return &pbresource.ID{ + Name: in.Name, + Type: pbmesh.APIGatewayType, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + + // Because we are explicitly defining NS/partition, this will not default and must be explicit. + // At a future point, this will move out of the Tenancy block. + PeerName: constants.DefaultConsulPeer, + }, + } +} + +func (in *APIGateway) Resource(namespace, partition string) *pbresource.Resource { + return &pbresource.Resource{ + Id: in.ResourceID(namespace, partition), + Data: inject.ToProtoAny(&in.Spec), + Metadata: meshConfigMeta(), + } +} + +func (in *APIGateway) AddFinalizer(f string) { + in.ObjectMeta.Finalizers = append(in.Finalizers(), f) +} + +func (in *APIGateway) RemoveFinalizer(f string) { + var newFinalizers []string + for _, oldF := range in.Finalizers() { + if oldF != f { + newFinalizers = append(newFinalizers, oldF) + } + } + in.ObjectMeta.Finalizers = newFinalizers +} + +func (in *APIGateway) Finalizers() []string { + return in.ObjectMeta.Finalizers +} + +func (in *APIGateway) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { + return cmp.Equal( + in.Resource(namespace, partition), + candidate, + protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), + protocmp.IgnoreFields(&pbresource.ID{}, "uid"), + protocmp.Transform(), + cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), + ) +} + +func (in *APIGateway) KubeKind() string { + return apiGatewayKubeKind +} + +func (in *APIGateway) KubernetesName() string { + return in.ObjectMeta.Name +} + +func (in *APIGateway) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { + in.Status.Conditions = Conditions{ + { + Type: ConditionSynced, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }, + } +} + +func (in *APIGateway) SetLastSyncedTime(time *metav1.Time) { + in.Status.LastSyncedTime = time +} + +func (in *APIGateway) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { + cond := in.Status.GetCondition(ConditionSynced) + if cond == nil { + return corev1.ConditionUnknown, "", "" + } + return cond.Status, cond.Reason, cond.Message +} + +func (in *APIGateway) SyncedConditionStatus() corev1.ConditionStatus { + condition := in.Status.GetCondition(ConditionSynced) + if condition == nil { + return corev1.ConditionUnknown + } + return condition.Status +} + +func (in *APIGateway) Validate(tenancy common.ConsulTenancyConfig) error { + return nil +} + +// DefaultNamespaceFields is required as part of the common.MeshConfig interface. +func (in *APIGateway) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go index b6ef8feef5..44bec5db3c 100644 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go @@ -10,6 +10,69 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIGateway) DeepCopyInto(out *APIGateway) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGateway. +func (in *APIGateway) DeepCopy() *APIGateway { + if in == nil { + return nil + } + out := new(APIGateway) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIGateway) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIGatewayList) DeepCopyInto(out *APIGatewayList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*APIGateway, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(APIGateway) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGatewayList. +func (in *APIGatewayList) DeepCopy() *APIGatewayList { + if in == nil { + return nil + } + out := new(APIGatewayList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIGatewayList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml new file mode 100644 index 0000000000..44713c234f --- /dev/null +++ b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml @@ -0,0 +1,235 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: apigateways.mesh.consul.hashicorp.com +spec: + group: mesh.consul.hashicorp.com + names: + kind: APIGateway + listKind: APIGatewayList + plural: apigateways + singular: apigateway + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The sync status of the resource with Consul + jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - description: The last successful synced time of the resource with Consul + jsonPath: .status.lastSyncedTime + name: Last Synced + type: date + - description: The age of the resource + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v2beta1 + schema: + openAPIV3Schema: + description: APIGateway is the Schema for the API Gateway + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + gatewayClassName: + description: GatewayClassName is the name of the GatewayClass used + by the APIGateway + type: string + listeners: + items: + properties: + hostname: + description: Hostname is the host name that a listener should + be bound to, if unspecified, the listener accepts requests + for all hostnames. + type: string + name: + description: Name is the name of the listener in a given gateway. + This must be unique within a gateway. + type: string + port: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + protocol: + description: Protocol is the protocol that a listener should + use, it must either be "http" or "tcp" + type: string + tls: + description: TLS is the TLS settings for the listener. + properties: + certificates: + description: Certificates is a set of references to certificates + that a gateway listener uses for TLS termination. + items: + description: Reference identifies which resource a condition + relates to, when it is not the core resource itself. + properties: + name: + description: Name is the user-given name of the resource + (e.g. the "billing" service). + type: string + section: + description: Section identifies which part of the + resource the condition relates to. + type: string + tenancy: + description: Tenancy identifies the tenancy units + (i.e. partition, namespace) in which the resource + resides. + properties: + namespace: + description: "Namespace further isolates resources + within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list resources + across all namespaces." + type: string + partition: + description: "Partition is the topmost administrative + boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list resources + across all partitions." + type: string + peerName: + description: "PeerName identifies which peer the + resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering + \n When using the List and WatchList endpoints, + provide the wildcard value \"*\" to list resources + across all peers." + type: string + type: object + type: + description: Type identifies the resource's type. + properties: + group: + description: Group describes the area of functionality + to which this resource type relates (e.g. "catalog", + "authorization"). + type: string + groupVersion: + description: GroupVersion is incremented when + sweeping or backward-incompatible changes are + made to the group's resource types. + type: string + kind: + description: Kind identifies the specific resource + type within the group. + type: string + type: object + type: object + type: array + tlsParameters: + description: TLSParameters contains optional configuration + for running TLS termination. + properties: + cipherSuites: + items: + enum: + - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_GCM_SHA256 + - TLS_CIPHER_SUITE_AES256_SHA + - TLS_CIPHER_SUITE_ECDHE_ECDSA_CHACHA20_POLY1305 + - TLS_CIPHER_SUITE_ECDHE_RSA_AES128_GCM_SHA256 + - TLS_CIPHER_SUITE_ECDHE_RSA_CHACHA20_POLY1305 + - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_SHA + - TLS_CIPHER_SUITE_ECDHE_RSA_AES128_SHA + - TLS_CIPHER_SUITE_AES128_GCM_SHA256 + - TLS_CIPHER_SUITE_AES128_SHA + - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_GCM_SHA384 + - TLS_CIPHER_SUITE_ECDHE_RSA_AES256_GCM_SHA384 + - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_SHA + - TLS_CIPHER_SUITE_ECDHE_RSA_AES256_SHA + - TLS_CIPHER_SUITE_AES256_GCM_SHA384 + format: int32 + type: string + type: array + maxVersion: + enum: + - TLS_VERSION_AUTO + - TLS_VERSION_1_0 + - TLS_VERSION_1_1 + - TLS_VERSION_1_2 + - TLS_VERSION_1_3 + - TLS_VERSION_INVALID + - TLS_VERSION_UNSPECIFIED + format: int32 + type: string + minVersion: + enum: + - TLS_VERSION_AUTO + - TLS_VERSION_1_0 + - TLS_VERSION_1_1 + - TLS_VERSION_1_2 + - TLS_VERSION_1_3 + - TLS_VERSION_INVALID + - TLS_VERSION_UNSPECIFIED + format: int32 + type: string + type: object + type: object + type: object + minItems: 1 + type: array + type: object + status: + properties: + conditions: + description: Conditions indicate the latest available observations + of a resource's current state. + items: + description: 'Conditions define a readiness condition for a Consul + resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + required: + - status + - type + type: object + type: array + lastSyncedTime: + description: LastSyncedTime is the last time the resource successfully + synced with Consul. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/control-plane/go.mod b/control-plane/go.mod index 46333b3ab1..ec68dff435 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/consul-k8s/control-plane // TODO: Remove this when the next version of the submodule is released. // We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240116214818-6a8554317502 +replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240129174413-a2d50af1bdfb replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f diff --git a/control-plane/go.sum b/control-plane/go.sum index 13af622438..cc78c06247 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -81,7 +81,6 @@ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -128,7 +127,6 @@ github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TR github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -218,7 +216,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -260,22 +257,16 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed h1:eM9tGgSqAZbm4Ndkp35Dg8YROT0dNH3ghTYu5pcUIAc= github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= 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.10.1-0.20240122152324-758ddf84e9c9 h1:qaS6rE768dt5hGPl2y4DIABXF4eA23BNSmWFpEr3kWQ= github.com/hashicorp/consul/api v1.10.1-0.20240122152324-758ddf84e9c9/go.mod h1:gInwZGrnWlE1Vvq6rSD5pUf6qwNa69NTLLknbdwQRUk= -github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= -github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240116214818-6a8554317502 h1:DeUPf9K/hAXVY6bTheu1mDsszkOzB3Dgz3hLVSUp8GA= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240116214818-6a8554317502/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240129174413-a2d50af1bdfb h1:4LCdNw3DTe5WRe3fJvY+hkRLNtRlunl/9PJuOlQmPa8= +github.com/hashicorp/consul/proto-public v0.1.2-0.20240129174413-a2d50af1bdfb/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -432,7 +423,6 @@ github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQh github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -444,10 +434,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= @@ -467,7 +455,6 @@ github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otz github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From a054e33b7c27b19cd73ead741f77db9e831e1bc0 Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Wed, 31 Jan 2024 15:49:56 -0800 Subject: [PATCH 582/592] [NET-7534] v2: Make port names in consul-k8s compatible with NET-5586 (#3528) - [This change in consul](https://github.com/hashicorp/consul/pull/20371) involves now interpreting whether xRoute/FailoverPolicy/DestinationPolicy resource service references use either the service port (virtualPort in consul) or service target port (targetPort in consul). To make this decision unambiguously: > This change updates our interpretation of these reference fields/keys (parent, backend, destination), s.t.: > > * A numeric value will be exclusively interpreted to indicate a ServicePort.virtual_port > * A non-numeric value will be exclusively interpreted to indicate a ServicePort.target_port (this supports VMs/Nomad and other cases where network virtual ports are not used, and port names are expected to be in reference to workload ports, not service ports) - If a K8s service targetport is allowed to be the stringified version of a number, it will be ambiguous in consul what to interpret the string "portID" as. - This change makes it such that the string port can never be a number, and will always also have alpha characters by prefixing "cslport-" to the workload port if the workload port name is unspecified. --- control-plane/connect-inject/common/common.go | 14 +++ .../connect-inject/common/common_test.go | 40 +++++++ .../connect-inject/constants/constants.go | 2 + .../endpointsv2/endpoints_controller.go | 9 +- .../endpointsv2/endpoints_controller_test.go | 16 +-- .../controllers/pod/pod_controller.go | 6 +- .../controllers/pod/pod_controller_test.go | 6 +- .../connect-inject/webhookv2/mesh_webhook.go | 11 ++ .../webhookv2/mesh_webhook_test.go | 109 ++++++++++++++++++ 9 files changed, 190 insertions(+), 23 deletions(-) diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 9bbaeaab58..569b4d96e6 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -73,6 +73,20 @@ func PortValue(pod corev1.Pod, value string) (int32, error) { return int32(raw), err } +// WorkloadPortName returns the container port's name if it has one, and if not, constructs a name from the port number +// and adds a constant prefix. The port name must be 1-15 characters and must have at least 1 alpha character. +func WorkloadPortName(port *corev1.ContainerPort) string { + name := port.Name + var isNum bool + if _, err := strconv.Atoi(name); err == nil { + isNum = true + } + if name == "" || isNum { + name = constants.UnnamedWorkloadPortNamePrefix + strconv.Itoa(int(port.ContainerPort)) + } + return name +} + // TransparentProxyEnabled returns true if transparent proxy should be enabled for this pod. // It returns an error when the annotation value cannot be parsed by strconv.ParseBool or if we are unable // to read the pod's namespace label when it exists. diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index cd4371062d..e3d6ac7227 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -167,6 +167,46 @@ func TestCommonDetermineAndValidatePort(t *testing.T) { } } +func TestWorkloadPortName(t *testing.T) { + cases := []struct { + Name string + Port *corev1.ContainerPort + Expected string + }{ + { + Name: "named port", + Port: &corev1.ContainerPort{ + Name: "http", + ContainerPort: 8080, + }, + Expected: "http", + }, + { + Name: "unnamed port", + Port: &corev1.ContainerPort{ + Name: "", + ContainerPort: 8080, + }, + Expected: "cslport-8080", + }, + { + Name: "number port name", + Port: &corev1.ContainerPort{ + Name: "8080", + ContainerPort: 8080, + }, + Expected: "cslport-8080", + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + name := WorkloadPortName(tt.Port) + require.Equal(t, tt.Expected, name) + }) + } +} + func TestPortValue(t *testing.T) { cases := []struct { Name string diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 8913019a19..d4d109ade5 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -80,6 +80,8 @@ const ( CACertFileEnvVar = "CONSUL_CACERT_FILE" CACertPEMEnvVar = "CONSUL_CACERT_PEM" TLSServerNameEnvVar = "CONSUL_TLS_SERVER_NAME" + + UnnamedWorkloadPortNamePrefix = "cslport-" ) // GetNormalizedConsulNamespace returns the default namespace if the passed namespace diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go index 82dd583dfc..6e98f5f714 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go @@ -444,12 +444,7 @@ func getEffectiveTargetPort(targetPort intstr.IntOrString, prefixedPods selector targetPortInt := int32(targetPort.IntValue()) var mostPrevalentContainerPort *corev1.ContainerPort maxCount := 0 - effectiveNameForPort := func(port *corev1.ContainerPort) string { - if port.Name != "" { - return port.Name - } - return targetPort.String() - } + effectiveNameForPort := inject.WorkloadPortName for _, podData := range prefixedPods { containerPort := getTargetContainerPort(targetPortInt, podData.samplePod) @@ -493,7 +488,7 @@ func getEffectiveTargetPort(targetPort intstr.IntOrString, prefixedPods selector // If still no match for the target port, fall back to string-ifying the target port name, which // is what the PodController will do when converting unnamed ContainerPorts to Workload ports. - return targetPort.String() + return constants.UnnamedWorkloadPortNamePrefix + targetPort.String() } // getTargetContainerPort returns the pod ContainerPort matching the given numeric port value, or nil if none is found. diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go index c55ca06cdb..0c0733191b 100644 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go @@ -229,7 +229,7 @@ func TestReconcile_CreateService(t *testing.T) { Name: "other", Port: 10001, Protocol: "TCP", - TargetPort: intstr.FromString("10001"), + TargetPort: intstr.FromString("cslport-10001"), // no app protocol specified }, }, @@ -260,7 +260,7 @@ func TestReconcile_CreateService(t *testing.T) { }, { VirtualPort: 10001, - TargetPort: "10001", + TargetPort: "cslport-10001", Protocol: pbcatalog.Protocol_PROTOCOL_TCP, }, { @@ -554,12 +554,12 @@ func TestReconcile_CreateService(t *testing.T) { }, { VirtualPort: 9090, - TargetPort: "6789", // Matches service target number + TargetPort: "cslport-6789", // Matches service target number Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, }, { VirtualPort: 10010, - TargetPort: "10010", // Matches service target number (unmatched by container ports) + TargetPort: "cslport-10010", // Matches service target number (unmatched by container ports) Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, }, { @@ -713,7 +713,7 @@ func TestReconcile_CreateService(t *testing.T) { }, { VirtualPort: 9090, - TargetPort: "6789", // Matches service target number due to unnamed being most common + TargetPort: "cslport-6789", // Matches service target number due to unnamed being most common Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, }, { @@ -1269,7 +1269,7 @@ func TestReconcile_UpdateService(t *testing.T) { }, { VirtualPort: 10001, - TargetPort: "10001", + TargetPort: "unspec-port", //this might need to be changed to "my_unspecified_port" Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, }, { @@ -1390,7 +1390,7 @@ func TestReconcile_UpdateService(t *testing.T) { Name: "other", Port: 10001, Protocol: "TCP", - TargetPort: intstr.FromString("10001"), + TargetPort: intstr.FromString("cslport-10001"), // no app protocol specified }, }, @@ -1421,7 +1421,7 @@ func TestReconcile_UpdateService(t *testing.T) { }, { VirtualPort: 10001, - TargetPort: "10001", + TargetPort: "cslport-10001", Protocol: pbcatalog.Protocol_PROTOCOL_TCP, }, { diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go index f0d319a475..8bc29302fe 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "regexp" - "strconv" "strings" "github.com/go-logr/logr" @@ -619,10 +618,7 @@ func getWorkloadPorts(pod corev1.Pod) ([]string, map[string]*pbcatalog.WorkloadP for _, container := range pod.Spec.Containers { for _, port := range container.Ports { - name := port.Name - if name == "" { - name = strconv.Itoa(int(port.ContainerPort)) - } + name := inject.WorkloadPortName(&port) // TODO: error check reserved "mesh" keyword and 20000 diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go index 55ff24ae5b..8d08852ed1 100644 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ b/control-plane/connect-inject/controllers/pod/pod_controller_test.go @@ -239,14 +239,14 @@ func TestWorkloadWrite(t *testing.T) { }, expectedWorkload: &pbcatalog.Workload{ Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"80", "8080", "mesh"}}, + {Host: "10.0.0.1", Ports: []string{"cslport-80", "cslport-8080", "mesh"}}, }, Ports: map[string]*pbcatalog.WorkloadPort{ - "80": { + "cslport-80": { Port: 80, Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, }, - "8080": { + "cslport-8080": { Port: 8080, Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, }, diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook.go b/control-plane/connect-inject/webhookv2/mesh_webhook.go index 839b3fd4ab..590608bce7 100644 --- a/control-plane/connect-inject/webhookv2/mesh_webhook.go +++ b/control-plane/connect-inject/webhookv2/mesh_webhook.go @@ -10,6 +10,7 @@ import ( "fmt" "net/http" "strconv" + "strings" mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" @@ -251,6 +252,16 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Info("received pod", "name", req.Name, "ns", req.Namespace) + // Validate that none of the pod ports start with the prefix "cslport-" as that may result in conflicts with ports + // created by the pod controller when creating workloads. + for _, c := range pod.Spec.Containers { + for _, p := range c.Ports { + if strings.HasPrefix(p.Name, constants.UnnamedWorkloadPortNamePrefix) { + return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error creating pod: port names cannot be prefixed with \"cslport-\" as that prefix is reserved")) + } + } + } + // Add our volume that will be shared by the init container and // the sidecar for passing data in the pod. pod.Spec.Volumes = append(pod.Spec.Volumes, w.containerVolume()) diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go index ee68db5c14..8bb6dc7a2f 100644 --- a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go @@ -1164,6 +1164,115 @@ func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { } } +func TestHandlerValidatePorts(t *testing.T) { + cases := []struct { + Name string + Pod *corev1.Pod + Err string + }{ + { + "basic pod, with ports", + &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + Ports: []corev1.ContainerPort{ + { + Name: "http", + ContainerPort: 8080, + }, + }, + }, + { + Name: "web-side", + }, + }, + }, + }, + "", + }, + { + "basic pod, with unnamed ports", + &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + Ports: []corev1.ContainerPort{ + { + ContainerPort: 8080, + }, + }, + }, + { + Name: "web-side", + }, + }, + }, + }, + "", + }, + { + "basic pod, with invalid prefix name", + &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", + Ports: []corev1.ContainerPort{ + { + Name: "cslport-8080", + ContainerPort: 8080, + }, + }, + }, + { + Name: "web-side", + }, + }, + }, + }, + "error creating pod: port names cannot be prefixed with \"cslport-\" as that prefix is reserved", + }, + } + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{ + Group: "", + Version: "v1", + }, &corev1.Pod{}) + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + + w := MeshWebhook{ + Log: logrtest.New(t), + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSet(), + EnableTransparentProxy: true, + TProxyOverwriteProbes: true, + decoder: decoder, + ConsulConfig: &consul.Config{HTTPPort: 8500}, + Clientset: defaultTestClientWithNamespace(), + } + req := admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Namespace: namespaces.DefaultNamespace, + Object: encodeRaw(t, tt.Pod), + }, + } + resp := w.Handle(context.Background(), req) + if tt.Err == "" { + require.True(t, resp.Allowed) + } else { + require.False(t, resp.Allowed) + require.Contains(t, resp.Result.Message, tt.Err) + } + + }) + } +} func TestHandlerDefaultAnnotations(t *testing.T) { cases := []struct { Name string From f694158c62df46d48b01ad938320d04c128ca3a5 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 1 Feb 2024 13:44:20 -0500 Subject: [PATCH 583/592] Fix meshgw tests (#3532) * Fix meshgw tests * change protocol on mesh gw tests to tcp from mesh --- .../resources/mesh_gateway_controller_test.go | 146 +++++++++++++++++- 1 file changed, 144 insertions(+), 2 deletions(-) diff --git a/control-plane/controllers/resources/mesh_gateway_controller_test.go b/control-plane/controllers/resources/mesh_gateway_controller_test.go index 6aef2ec6e2..63f38624f0 100644 --- a/control-plane/controllers/resources/mesh_gateway_controller_test.go +++ b/control-plane/controllers/resources/mesh_gateway_controller_test.go @@ -8,7 +8,6 @@ import ( "testing" logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" @@ -21,6 +20,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) @@ -50,6 +52,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "consul", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, }, request: ctrl.Request{ @@ -73,6 +85,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "default", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ @@ -99,6 +121,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "consul", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, }, request: ctrl.Request{ @@ -122,6 +154,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "default", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ @@ -148,6 +190,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Name: "mesh-gateway", UID: "abc123", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ @@ -180,6 +232,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "consul", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, }, request: ctrl.Request{ @@ -203,6 +265,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "default", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ @@ -229,6 +301,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Name: "mesh-gateway", UID: "abc123", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ @@ -261,6 +343,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "consul", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, }, request: ctrl.Request{ @@ -284,6 +376,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "default", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ @@ -310,6 +412,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Name: "mesh-gateway", UID: "abc123", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ @@ -342,6 +454,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "consul", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, }, request: ctrl.Request{ @@ -365,6 +487,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Namespace: "default", Name: "mesh-gateway", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -391,6 +523,16 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { Name: "mesh-gateway", UID: "abc123", }, + Spec: pbmesh.MeshGateway{ + GatewayClassName: "consul", + Listeners: []*pbmesh.MeshGatewayListener{ + { + Name: "wan", + Port: 8443, + Protocol: "tcp", + }, + }, + }, }, &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -444,7 +586,7 @@ func TestMeshGatewayController_Reconcile(t *testing.T) { res, err := controller.Reconcile(context.Background(), testCase.request) if testCase.expectedErr != nil { - //require.EqualError(t, err, testCase.expectedErr.Error()) + // require.EqualError(t, err, testCase.expectedErr.Error()) require.ErrorIs(t, err, testCase.expectedErr) } else { require.NoError(t, err) From b38169a603564856535d5ee18035f3a62380b008 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:09:57 -0800 Subject: [PATCH 584/592] add nightly for rc branch (#3533) --- .../workflows/weekly-acceptance-1-4-0-rc1.yml | 28 +++++++++++++++++++ .github/workflows/weekly-acceptance-1-4-x.yml | 28 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 .github/workflows/weekly-acceptance-1-4-0-rc1.yml create mode 100644 .github/workflows/weekly-acceptance-1-4-x.yml diff --git a/.github/workflows/weekly-acceptance-1-4-0-rc1.yml b/.github/workflows/weekly-acceptance-1-4-0-rc1.yml new file mode 100644 index 0000000000..e74a44ea70 --- /dev/null +++ b/.github/workflows/weekly-acceptance-1-4-0-rc1.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-4-0-rc1 +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 * * 5' + +# these should be the only settings that you will ever need to change +env: + BRANCH: "release/1.4.0-rc1" + CONTEXT: "weekly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + 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/.github/workflows/weekly-acceptance-1-4-x.yml b/.github/workflows/weekly-acceptance-1-4-x.yml new file mode 100644 index 0000000000..a6bbe05e6b --- /dev/null +++ b/.github/workflows/weekly-acceptance-1-4-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-4-x +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run weekly on Thursday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 4' + +# these should be the only settings that you will ever need to change +env: + BRANCH: "release/1.4.x" + CONTEXT: "weekly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + 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 }}" }' From ac857212261048e1d5ff98c2d816facbf251c359 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 1 Feb 2024 14:15:10 -0500 Subject: [PATCH 585/592] [NET-7243] Stub APIGateway Controller for v2 (#3507) * stub api-gateway-controller * Add setup to v2 controller --- .../templates/connect-inject-clusterrole.yaml | 2 + control-plane/api/common/common.go | 3 +- .../api/mesh/v2beta1/api_gateway_types.go | 2 +- .../resources/api-gateway-controller.go | 44 +++++++++++++++++++ .../inject-connect/v2controllers.go | 10 +++++ 5 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 control-plane/controllers/resources/api-gateway-controller.go diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index c6845870ba..be816ff391 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -102,6 +102,7 @@ rules: - grpcroutes - httproutes - meshgateways + - apigateways - tcproutes - proxyconfigurations verbs: @@ -121,6 +122,7 @@ rules: - grpcroutes/status - httproutes/status - meshgateways/status + - apigateways/status - tcproutes/status - proxyconfigurations/status verbs: diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 53d4c42e96..730fd622ac 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -29,13 +29,14 @@ const ( RouteAuthFilter string = "routeauthfilter" GatewayPolicy string = "gatewaypolicy" - // V2 config entries. + // V2 resources. TrafficPermissions string = "trafficpermissions" GRPCRoute string = "grpcroute" HTTPRoute string = "httproute" TCPRoute string = "tcproute" ProxyConfiguration string = "proxyconfiguration" MeshGateway string = "meshgateway" + APIGateway string = "apigateway" GatewayClass string = "gatewayclass" GatewayClassConfig string = "gatewayclassconfig" MeshConfiguration string = "meshconfiguration" diff --git a/control-plane/api/mesh/v2beta1/api_gateway_types.go b/control-plane/api/mesh/v2beta1/api_gateway_types.go index 70403789f8..1d52c6580e 100644 --- a/control-plane/api/mesh/v2beta1/api_gateway_types.go +++ b/control-plane/api/mesh/v2beta1/api_gateway_types.go @@ -23,7 +23,7 @@ const ( ) func init() { - MeshSchemeBuilder.Register(&GatewayClass{}, &GatewayClassList{}) + MeshSchemeBuilder.Register(&GatewayClass{}, &GatewayClassList{}, &APIGateway{}, &APIGatewayList{}) } // +kubebuilder:object:root=true diff --git a/control-plane/controllers/resources/api-gateway-controller.go b/control-plane/controllers/resources/api-gateway-controller.go new file mode 100644 index 0000000000..08c116499f --- /dev/null +++ b/control-plane/controllers/resources/api-gateway-controller.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resources + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" +) + +// APIGatewayController reconciles a APIGateway object. +type APIGatewayController struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Controller *ConsulResourceController +} + +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute/status,verbs=get;update;patch + +func (r *APIGatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger(req.NamespacedName).Info("Reconciling APIGateway") + return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.APIGateway{}) +} + +func (r *APIGatewayController) Logger(name types.NamespacedName) logr.Logger { + return r.Log.WithValues("request", name) +} + +func (r *APIGatewayController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return r.Status().Update(ctx, obj, opts...) +} + +func (r *APIGatewayController) SetupWithManager(mgr ctrl.Manager) error { + return setupWithManager(mgr, &meshv2beta1.APIGateway{}, r) +} diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go index 79f8223fae..ddf0758df1 100644 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ b/control-plane/subcommand/inject-connect/v2controllers.go @@ -240,6 +240,16 @@ func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manage return err } + if err := (&resourceControllers.APIGatewayController{ + Controller: consulResourceController, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(common.APIGateway), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", common.APIGateway) + return err + } + if err := (&resourceControllers.GatewayClassConfigController{ Controller: consulResourceController, Client: mgr.GetClient(), From 25708a18e4aaaa9a2b2686710c14f57889830fbe Mon Sep 17 00:00:00 2001 From: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:06:35 -0600 Subject: [PATCH 586/592] Net 7376 Status struct on api gateway with required info from kubesig (#3530) * add status structs * update status --- .../api/mesh/v2beta1/api_gateway_types.go | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/control-plane/api/mesh/v2beta1/api_gateway_types.go b/control-plane/api/mesh/v2beta1/api_gateway_types.go index 1d52c6580e..60b94019d3 100644 --- a/control-plane/api/mesh/v2beta1/api_gateway_types.go +++ b/control-plane/api/mesh/v2beta1/api_gateway_types.go @@ -38,8 +38,26 @@ type APIGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec pbmesh.APIGateway `json:"spec,omitempty"` - Status `json:"status,omitempty"` + Spec pbmesh.APIGateway `json:"spec,omitempty"` + APIGatewayStatus `json:"status,omitempty"` +} + +type APIGatewayStatus struct { + Status + Addresses []GatewayAddress + Listeners []ListenerStatus +} + +type ListenerStatus struct { + Status + Name string `json:"name"` + AttachedRoutes int32 `json:"attachedRoutes"` +} + +type GatewayAddress struct { + // +kubebuilder:default=IPAddress + Type string `json:"type"` + Value string `json:"value"` } // +kubebuilder:object:root=true From 1510cefe3080f6ee68bc58982080177bb909d858 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:21:29 -0800 Subject: [PATCH 587/592] updated script to point at RC version correctly (#3541) * updated script to point at RC version correctly --- .../build-support/scripts/consul-enterprise-version.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/control-plane/build-support/scripts/consul-enterprise-version.sh b/control-plane/build-support/scripts/consul-enterprise-version.sh index d0a1a40b53..d910f428ab 100755 --- a/control-plane/build-support/scripts/consul-enterprise-version.sh +++ b/control-plane/build-support/scripts/consul-enterprise-version.sh @@ -4,11 +4,11 @@ FILE=$1 VERSION=$(yq .global.image $FILE) -if [[ !"${VERSION}" == *"hashicorp/consul:"* ]]; then +if [[ "${VERSION}" == *"hashicorp/consul:"* ]]; then + # for matching release image repos with a -ent label VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") -elif [[ !"${VERSION}" == *"-rc"* ]]; then - VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") else + # for matching preview image repos VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") fi From 78ac0005efad7505e66c39355b2e330d7d5a657e Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:44:37 -0800 Subject: [PATCH 588/592] Mw/prepare main for 1.5 dev (#3535) * bump versions to next version * updated script to handle new Consul-k8s images --- charts/consul/Chart.yaml | 10 +++++----- charts/consul/values.yaml | 6 +++--- cli/version/version.go | 2 +- control-plane/build-support/functions/10-util.sh | 5 +++-- control-plane/version/version.go | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index c33e0f4dac..d74a42ba95 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -3,8 +3,8 @@ apiVersion: v2 name: consul -version: 1.4.0-dev -appVersion: 1.18-dev +version: 1.5.0-dev +appVersion: 1.19-dev kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -16,11 +16,11 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.18-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.19-dev - name: consul-k8s-control-plane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.4-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.5-dev - name: consul-dataplane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.4-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.5-dev - name: envoy image: envoyproxy/envoy:v1.25.11 artifacthub.io/license: MPL-2.0 diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 4df6ebc569..ef2d0cd877 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.18-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.19-dev # 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.4-dev + imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.5-dev # The name of the datacenter that the agents should # register as. This can't be changed once the Consul cluster is up and running @@ -639,7 +639,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.4-dev + imageConsulDataplane: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.5-dev # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. diff --git a/cli/version/version.go b/cli/version/version.go index da2c79a1b4..f68d1632a6 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -17,7 +17,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.4.0" + Version = "1.5.0" // 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 diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index f1117d88d2..dbaeffdb60 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -629,7 +629,8 @@ function update_version_helm { full_consul_version="$5-$3" full_consul_dataplane_version="$7-$3" elif test "$3" == "dev"; then - full_version="$2-$3" + full_version="${2%.*}-$3" + full_version_k8s_for_chart_version="$2-$3" # strip off the last minor patch version so that the consul image can be set to something like 1.16-dev. The image # is produced by Consul every night full_consul_version="${5%.*}-$3" @@ -637,7 +638,7 @@ function update_version_helm { fi sed_i ${SED_EXT} -e "s/(imageK8S:.*\/consul-k8s-control-plane:)[^\"]*/imageK8S: $4${full_version}/g" "${vfile}" - sed_i ${SED_EXT} -e "s/(version:[[:space:]]*)[^\"]*/\1${full_version}/g" "${cfile}" + sed_i ${SED_EXT} -e "s/(version:[[:space:]]*)[^\"]*/\1${full_version_k8s_for_chart_version}/g" "${cfile}" sed_i ${SED_EXT} -e "s/(appVersion:[[:space:]]*)[^\"]*/\1${full_consul_version}/g" "${cfile}" sed_i ${SED_EXT} -e "s/(image:.*\/consul-k8s-control-plane:)[^\"]*/image: $4${full_version}/g" "${cfile}" diff --git a/control-plane/version/version.go b/control-plane/version/version.go index da2c79a1b4..f68d1632a6 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -17,7 +17,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.4.0" + Version = "1.5.0" // 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 From a5221d1791538ff5432d8717877f68c5a9c07b39 Mon Sep 17 00:00:00 2001 From: "hashicorp-copywrite[bot]" <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 09:55:34 -0800 Subject: [PATCH 589/592] [COMPLIANCE] Add Copyright and License Headers (#3499) Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> --- control-plane/tenancy/namespace/namespace.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/control-plane/tenancy/namespace/namespace.go b/control-plane/tenancy/namespace/namespace.go index 84604e37b2..55950bba1d 100644 --- a/control-plane/tenancy/namespace/namespace.go +++ b/control-plane/tenancy/namespace/namespace.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package namespace import ( From 47839c2985b93f1f87eb5938dc460682e8e8bf4d Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Mon, 5 Feb 2024 14:08:16 -0500 Subject: [PATCH 590/592] Add NET_BIND_SERVICE to the security context in the deployment of Mesh Gateway --- charts/consul/templates/mesh-gateway-deployment.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index cda371d58a..2b18bde9aa 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -184,6 +184,12 @@ spec: containers: - name: mesh-gateway image: {{ .Values.global.imageConsulDataplane | quote }} + securityContext: + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE {{- if .Values.meshGateway.resources }} resources: {{- if eq (typeOf .Values.meshGateway.resources) "string" }} From 3b7926972976134bae3e4467b7b028b25ec38e28 Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Mon, 5 Feb 2024 19:08:16 +0000 Subject: [PATCH 591/592] backport of commit 47839c2985b93f1f87eb5938dc460682e8e8bf4d --- .changelog/1914.txt | 3 - .changelog/1920.txt | 3 - .changelog/1976.txt | 3 + .changelog/2029.txt | 3 - .changelog/2030.txt | 3 - .changelog/2048.txt | 3 - .changelog/2075.txt | 3 - .changelog/2086.txt | 3 - .changelog/2093.txt | 3 - .changelog/2097.txt | 3 - .changelog/2100.txt | 3 - .changelog/{2102.txt => 2108.txt} | 13 +- .changelog/2124.txt | 3 - .changelog/2134.txt | 3 - .changelog/2140.txt | 4 + .changelog/2143.txt | 4 - .changelog/2152.txt | 3 - .changelog/2159.txt | 3 + .changelog/2165.txt | 3 - .changelog/2166.txt | 3 - .changelog/2170.txt | 2 - .changelog/2183.txt | 3 - .changelog/2184.txt | 3 - .changelog/2195.txt | 3 - .changelog/2205.txt | 3 - .changelog/2209.txt | 3 - .changelog/2213.txt | 3 - .changelog/2225.txt | 3 + .changelog/2265.txt | 2 +- .changelog/2302.txt | 1 - .changelog/2304.txt | 3 - .changelog/2346.txt | 3 - .changelog/2413.txt | 3 - .changelog/2420.txt | 3 - .changelog/2476.txt | 7 - .changelog/2478.txt | 5 - .changelog/2520.txt | 4 - .changelog/2524.txt | 3 - .changelog/2597.txt | 3 - .changelog/{2642.txt => 2650.txt} | 2 +- .changelog/2678.txt | 3 + .changelog/2707.txt | 3 - .changelog/2711.txt | 3 - .changelog/{2710.txt => 2717.txt} | 2 +- .changelog/2723.txt | 3 - .changelog/2735.txt | 3 - .changelog/2743.txt | 3 - .changelog/2748.txt | 3 - .changelog/2784.txt | 3 - .changelog/2796.txt | 3 - .changelog/2844.txt | 3 - .changelog/2869.txt | 3 - .changelog/2880.txt | 3 - .changelog/2881.txt | 3 - .changelog/2904.txt | 3 - .changelog/{2936.txt => 2938.txt} | 2 +- .changelog/2941.txt | 22 - .changelog/2952.txt | 3 - .changelog/2958.txt | 3 - .changelog/2962.txt | 3 - .changelog/3000.txt | 36 - .changelog/3001.txt | 3 - .changelog/3070.txt | 3 - .changelog/{3116.txt => 3121.txt} | 2 +- .changelog/3128.txt | 3 - .changelog/3137.txt | 3 - .changelog/3138.txt | 3 - .changelog/3158.txt | 3 - .changelog/3162.txt | 3 - .changelog/3172.txt | 7 - .changelog/3180.txt | 3 - .changelog/3184.txt | 3 - .changelog/3209.txt | 4 - .changelog/3215.txt | 3 - .changelog/3219.txt | 3 - .changelog/3221.txt | 3 - .changelog/3222.txt | 3 - .changelog/3237.txt | 3 - .changelog/3284.txt | 3 - .changelog/3307.txt | 3 - .changelog/3308.txt | 3 - .changelog/3312.txt | 7 - .changelog/3315.txt | 3 - .changelog/3322.txt | 3 - .changelog/3337.txt | 3 - .changelog/3347.txt | 3 - .changelog/3362.txt | 3 - .changelog/3418.txt | 3 - .changelog/3437.txt | 3 - .changelog/3440.txt | 3 - .changelog/3442.txt | 3 - .changelog/3450.txt | 3 - .changelog/3478.txt | 3 - .changelog/3498.txt | 3 - .changelog/3502.txt | 3 - .copywrite.hcl | 18 - .github/ISSUE_TEMPLATE/config.yml | 3 - .github/pull_request_template.md | 14 +- .github/workflows/backport-checker.yml | 2 - .github/workflows/build.yml | 201 +- .github/workflows/changelog-checker.yml | 4 +- .github/workflows/jira-issues.yaml | 12 +- .github/workflows/jira-pr.yaml | 14 +- .github/workflows/merge.yml | 4 +- .github/workflows/nightly-acceptance.yml | 2 +- .../nightly-api-gateway-conformance.yml | 27 - .github/workflows/nightly-cleanup.yml | 26 - .github/workflows/pr.yml | 4 +- ...1-4-x.yml => weekly-acceptance-0-49-x.yml} | 10 +- ...-1-2-x.yml => weekly-acceptance-1-0-x.yml} | 6 +- .github/workflows/weekly-acceptance-1-1-x.yml | 6 +- .github/workflows/weekly-acceptance-1-3-x.yml | 28 - .../workflows/weekly-acceptance-1-4-0-rc1.yml | 28 - .go-version | 2 +- .golangci.yml | 3 - .release/ci.hcl | 3 - .release/release-metadata.hcl | 3 - .release/security-scan.hcl | 3 - CHANGELOG.md | 622 +--- CONTRIBUTING.md | 325 +- Makefile | 269 +- README.md | 15 +- .../aks_acceptance_test_packages.yaml | 6 +- .../eks_acceptance_test_packages.yaml | 6 +- .../gke_acceptance_test_packages.yaml | 6 +- acceptance/ci-inputs/kind-inputs.yaml | 5 +- .../kind_acceptance_test_packages.yaml | 14 +- acceptance/framework/cli/cli.go | 7 +- acceptance/framework/config/config.go | 34 +- acceptance/framework/config/config_test.go | 3 - .../framework/connhelper/connect_helper.go | 241 +- acceptance/framework/consul/cli_cluster.go | 27 +- acceptance/framework/consul/cluster.go | 5 +- acceptance/framework/consul/helm_cluster.go | 269 +- .../framework/consul/helm_cluster_test.go | 13 +- .../cni-kind/custom-resources.yaml | 3 - .../environment/cni-kind/tigera-operator.yaml | 3 - .../framework/environment/environment.go | 65 +- acceptance/framework/flags/flags.go | 33 +- acceptance/framework/flags/flags_test.go | 3 - acceptance/framework/helpers/helpers.go | 54 +- acceptance/framework/helpers/helpers_test.go | 3 - acceptance/framework/k8s/debug.go | 3 - acceptance/framework/k8s/deploy.go | 56 +- acceptance/framework/k8s/helpers.go | 21 +- acceptance/framework/k8s/kubectl.go | 35 +- acceptance/framework/logger/logger.go | 28 +- .../framework/portforward/port_forward.go | 8 +- acceptance/framework/suite/suite.go | 3 - acceptance/framework/vault/helpers.go | 17 - acceptance/framework/vault/vault_cluster.go | 58 +- acceptance/go.mod | 134 +- acceptance/go.sum | 582 +-- .../api_gateway_external_servers_test.go | 133 - .../api_gateway_gatewayclassconfig_test.go | 213 -- .../api_gateway_kitchen_sink_test.go | 232 -- .../api-gateway/api_gateway_lifecycle_test.go | 444 --- .../api-gateway/api_gateway_tenancy_test.go | 404 --- .../tests/api-gateway/api_gateway_test.go | 715 ---- acceptance/tests/api-gateway/example_test.go | 64 + acceptance/tests/api-gateway/main_test.go | 27 +- acceptance/tests/basic/basic_test.go | 3 - acceptance/tests/basic/main_test.go | 3 - acceptance/tests/cli/cli_install_test.go | 48 +- acceptance/tests/cli/cli_upgrade_test.go | 3 - acceptance/tests/cli/main_test.go | 3 - acceptance/tests/cloud/fakeserver_client.go | 161 - acceptance/tests/cloud/load/main_test.go | 18 - acceptance/tests/cloud/load/remote.go | 70 - .../tests/cloud/load/remote_load_test.go | 336 -- acceptance/tests/cloud/main_test.go | 18 - acceptance/tests/cloud/metrics_validation.go | 117 - acceptance/tests/cloud/observability_test.go | 308 -- acceptance/tests/cloud/remote_dev_test.go | 264 -- .../config_entries_namespaces_test.go | 157 +- .../config-entries/config_entries_test.go | 146 +- acceptance/tests/config-entries/main_test.go | 3 - .../connect/connect_external_servers_test.go | 11 +- .../connect/connect_inject_namespaces_test.go | 19 +- .../tests/connect/connect_inject_test.go | 50 +- .../connect/connect_proxy_lifecycle_test.go | 187 +- .../tests/connect/local_rate_limit_test.go | 146 - acceptance/tests/connect/main_test.go | 3 - .../tests/connect/permissive_mtls_test.go | 98 - .../tests/consul-dns/consul_dns_test.go | 9 +- acceptance/tests/consul-dns/main_test.go | 3 - acceptance/tests/example/example_test.go | 5 +- acceptance/tests/example/main_test.go | 4 - .../bases/api-gateway/apigateway.yaml | 31 - .../bases/api-gateway/certificate.yaml | 11 - .../bases/api-gateway/gatewayclass.yaml | 13 - .../bases/api-gateway/gatewayclassconfig.yaml | 7 - .../fixtures/bases/api-gateway/httproute.yaml | 10 - .../bases/api-gateway/kustomization.yaml | 9 - .../bases/api-gateway/meshservice.yaml | 9 - .../bases/cloud/hcp-mock/deployment.yaml | 29 - .../bases/cloud/hcp-mock/kustomization.yaml | 10 - .../bases/cloud/hcp-mock/service.yaml | 17 - .../bases/cloud/hcp-mock/serviceaccount.yaml | 7 - .../bases/cloud/service-intentions/acl.yaml | 15 - .../service-intentions/kustomization.yaml | 5 - .../crds-oss/controlplanerequestlimit.yaml | 50 - .../bases/crds-oss/exportedservices.yaml | 13 - .../bases/crds-oss/ingressgateway.yaml | 3 - .../fixtures/bases/crds-oss/jwtprovider.yaml | 30 - .../bases/crds-oss/kustomization.yaml | 24 +- .../tests/fixtures/bases/crds-oss/mesh.yaml | 3 - .../bases/crds-oss/proxydefaults.yaml | 14 - .../bases/crds-oss/servicedefaults.yaml | 31 +- .../bases/crds-oss/serviceexports.yaml | 10 + .../bases/crds-oss/serviceintentions.yaml | 3 - .../bases/crds-oss/serviceresolver.yaml | 5 +- .../bases/crds-oss/servicerouter.yaml | 3 - .../bases/crds-oss/servicesplitter.yaml | 3 - .../bases/crds-oss/terminatinggateway.yaml | 3 - .../exportedservices-default.yaml | 3 - .../kustomization.yaml | 3 - .../exportedservices-secondary.yaml | 3 - .../kustomization.yaml | 3 - .../fixtures/bases/intention/intention.yaml | 3 - .../bases/intention/kustomization.yaml | 3 - .../tests/fixtures/bases/job-client/job.yaml | 30 - .../bases/job-client/kustomization.yaml | 7 - .../fixtures/bases/job-client/service.yaml | 13 - .../bases/job-client/serviceaccount.yaml | 8 - .../bases/mesh-gateway/kustomization.yaml | 3 - .../bases/mesh-gateway/proxydefaults.yaml | 3 - .../bases/mesh-peering/kustomization.yaml | 3 - .../bases/mesh-peering/meshpeering.yaml | 3 - .../multiport-app/anyuid-scc-rolebinding.yaml | 3 - .../bases/multiport-app/deployment.yaml | 3 - .../bases/multiport-app/kustomization.yaml | 3 - .../privileged-scc-rolebinding.yaml | 3 - .../bases/multiport-app/psp-rolebinding.yaml | 3 - .../fixtures/bases/multiport-app/secret.yaml | 3 - .../fixtures/bases/multiport-app/service.yaml | 3 - .../bases/multiport-app/serviceaccount.yaml | 3 - .../bases/openshift/network-attachment.yaml | 3 - .../bases/peering/peering-acceptor.yaml | 3 - .../bases/peering/peering-dialer.yaml | 3 - .../fixtures/bases/pingpong/template.tmpl | 123 - .../bases/resolver-redirect/intention.yaml | 24 - .../resolver-redirect/kustomization.yaml | 8 - .../bases/resolver-redirect/resolver.yaml | 10 - .../bases/resolver-redirect/service.yaml | 15 - .../resolver-redirect/serviceaccount.yaml | 7 - .../kustomization.yaml | 5 - .../cluster-01-a-default-ns/sameness.yaml | 14 - .../kustomization.yaml | 5 - .../cluster-01-b-default-ns/sameness.yaml | 14 - .../kustomization.yaml | 5 - .../cluster-02-a-default-ns/sameness.yaml | 14 - .../kustomization.yaml | 5 - .../cluster-03-a-default-ns/sameness.yaml | 14 - .../exportedservices-ap1.yaml | 9 - .../exportedservices-ap1/kustomization.yaml | 5 - .../sameness/override-ns/kustomization.yaml | 5 - .../override-ns/service-defaults.yaml | 9 - .../cluster-01-a-dialer/kustomization.yaml | 6 - .../peering-dialer-cluster-02-a.yaml | 13 - .../peering-dialer-cluster-03-a.yaml | 13 - .../cluster-01-b-dialer/kustomization.yaml | 6 - .../peering-dialer-cluster-02-a.yaml | 13 - .../peering-dialer-cluster-03-a.yaml | 13 - .../cluster-02-a-acceptor/kustomization.yaml | 6 - .../peering-acceptor-cluster-01-a.yaml | 13 - .../peering-acceptor-cluster-01-b.yaml | 13 - .../cluster-02-a-dialer/kustomization.yaml | 5 - .../peering-dialer-cluster-03-a.yaml | 13 - .../cluster-03-a-acceptor/kustomization.yaml | 7 - .../peering-acceptor-cluster-01-a.yaml | 13 - .../peering-acceptor-cluster-01-b.yaml | 13 - .../peering-acceptor-cluster-02-a.yaml | 13 - .../sameness/peering/mesh/kustomization.yaml | 5 - .../bases/sameness/peering/mesh/mesh.yaml | 10 - .../bases/service-resolver/kustomization.yaml | 5 - .../service-resolver/service-resolver.yaml | 7 - .../static-client/anyuid-scc-rolebinding.yaml | 3 - .../bases/static-client/deployment.yaml | 3 - .../bases/static-client/kustomization.yaml | 3 - .../privileged-scc-rolebinding.yaml | 3 - .../bases/static-client/psp-rolebinding.yaml | 3 - .../fixtures/bases/static-client/service.yaml | 3 - .../bases/static-client/serviceaccount.yaml | 3 - .../bases/static-metrics-app/deployment.yaml | 3 - .../static-metrics-app/kustomization.yaml | 3 - .../bases/static-metrics-app/service.yaml | 3 - .../anyuid-scc-rolebinding.yaml | 3 - .../bases/static-server-https/configmap.yaml | 3 - .../bases/static-server-https/deployment.yaml | 3 - .../static-server-https/kustomization.yaml | 3 - .../privileged-scc-rolebinding.yaml | 3 - .../static-server-https/psp-rolebinding.yaml | 3 - .../bases/static-server-https/service.yaml | 3 - .../static-server-https/serviceaccount.yaml | 3 - .../anyuid-scc-rolebinding.yaml | 14 - .../bases/static-server-tcp/deployment.yaml | 49 - .../static-server-tcp/kustomization.yaml | 11 - .../privileged-scc-rolebinding.yaml | 14 - .../static-server-tcp/psp-rolebinding.yaml | 14 - .../bases/static-server-tcp/service.yaml | 15 - .../static-server-tcp/serviceaccount.yaml | 7 - .../static-server-tcp/servicedefaults.yaml | 10 - .../static-server/anyuid-scc-rolebinding.yaml | 3 - .../bases/static-server/deployment.yaml | 7 +- .../bases/static-server/kustomization.yaml | 3 - .../privileged-scc-rolebinding.yaml | 3 - .../bases/static-server/psp-rolebinding.yaml | 3 - .../fixtures/bases/static-server/service.yaml | 3 - .../bases/static-server/serviceaccount.yaml | 3 - .../trafficpermissions/kustomization.yaml | 5 - .../trafficpermissions.yaml | 14 - .../anyuid-scc-rolebinding.yaml | 26 - .../bases/v2-multiport-app/deployment.yaml | 81 - .../bases/v2-multiport-app/kustomization.yaml | 11 - .../privileged-scc-rolebinding.yaml | 14 - .../v2-multiport-app/psp-rolebinding.yaml | 14 - .../bases/v2-multiport-app/secret.yaml | 10 - .../bases/v2-multiport-app/service.yaml | 18 - .../v2-multiport-app/serviceaccount.yaml | 7 - .../api-gateways/certificate/certificate.yaml | 11 - .../certificate/kustomization.yaml | 5 - .../dc1-to-dc2-resolver/kustomization.yaml | 5 - .../dc1-to-dc2-resolver/serviceresolver.yaml | 11 - .../dc2-to-dc1-resolver/kustomization.yaml | 5 - .../dc2-to-dc1-resolver/serviceresolver.yaml | 11 - .../cases/api-gateways/gateway/gateway.yaml | 20 - .../api-gateways/gateway/kustomization.yaml | 5 - .../api-gateways/httproute/kustomization.yaml | 5 - .../cases/api-gateways/httproute/route.yaml | 8 - .../api-gateways/jwt-auth/api-gateway.yaml | 43 - .../jwt-auth/external-ref-other-ns.yaml | 16 - .../extra-gateway-policy.yaml | 25 - .../extraGatewayPolicy/kustomization.yaml | 7 - .../api-gateways/jwt-auth/gateway-policy.yaml | 25 - .../api-gateways/jwt-auth/httproute-auth.yaml | 32 - .../httproute-invalid-external-ref.yaml | 32 - .../httproute-no-auth-on-auth-listener.yaml | 26 - .../api-gateways/jwt-auth/httproute.yaml | 19 - .../jwt-auth/httproute2-auth.yaml | 32 - .../api-gateways/jwt-auth/jwt-provider.yaml | 12 - .../jwt-auth/jwt-route-filter.yaml | 15 - .../api-gateways/jwt-auth/kustomization.yaml | 20 - .../kitchen-sink-ent/api-gateway.yaml | 25 - .../kitchen-sink-ent/external-ref.yaml | 16 - .../kitchen-sink-ent/filters.yaml | 25 - .../kitchen-sink-ent/gateway-policy.yaml | 25 - .../kitchen-sink-ent/gatewayclassconfig.yaml | 12 - .../kitchen-sink-ent/httproute.yaml | 45 - .../kitchen-sink-ent/jwt-provider.yaml | 12 - .../kitchen-sink-ent/jwt-route-filter.yaml | 15 - .../kitchen-sink-ent/kustomization.yaml | 18 - .../kitchen-sink/api-gateway.yaml | 25 - .../kitchen-sink/external-ref.yaml | 16 - .../api-gateways/kitchen-sink/filters.yaml | 25 - .../kitchen-sink/gateway-policy.yaml | 25 - .../kitchen-sink/gatewayclassconfig.yaml | 12 - .../api-gateways/kitchen-sink/httproute.yaml | 40 - .../kitchen-sink/kustomization.yaml | 15 - .../api-gateways/mesh/kustomization.yaml | 5 - .../api-gateways/mesh/proxydefaults.yaml | 12 - .../peer-resolver/kustomization.yaml | 5 - .../peer-resolver/serviceresolver.yaml | 12 - .../api-gateways/resolver/kustomization.yaml | 5 - .../resolver/serviceresolver.yaml | 12 - .../cases/api-gateways/tcproute/route.yaml | 14 - .../kustomization.yaml | 6 +- .../default-partition-default/patch.yaml | 3 - .../default-partition-ns1/kustomization.yaml | 6 +- .../default-partition-ns1/patch.yaml | 3 - .../kustomization.yaml | 6 +- .../secondary-partition-default/patch.yaml | 3 - .../kustomization.yaml | 6 +- .../secondary-partition-ns1/patch.yaml | 3 - .../default-namespace/kustomization.yaml | 6 +- .../crd-peers/default-namespace/patch.yaml | 3 - .../crd-peers/default/kustomization.yaml | 6 +- .../cases/crd-peers/default/patch.yaml | 3 - .../crd-peers/external/kustomization.yaml | 9 - .../cases/crd-peers/external/patch.yaml | 15 - .../non-default-namespace/kustomization.yaml | 6 +- .../non-default-namespace/patch.yaml | 3 - .../cases/crds-ent/exportedservices.yaml | 7 +- .../cases/crds-ent/kustomization.yaml | 10 +- .../kustomization.yaml | 10 - .../patch.yaml | 15 - .../kustomization.yaml | 10 - .../patch.yaml | 15 - .../jobs/job-client-inject/kustomization.yaml | 10 - .../cases/jobs/job-client-inject/patch.yaml | 15 - .../service-defaults-static-server.yaml | 23 - .../mesh-config-permissive-allowed.yaml | 9 - ...ice-defaults-static-server-permissive.yaml | 10 - ...service-defaults-static-server-strict.yaml | 10 - .../kustomization.yaml | 5 - .../cluster-01-a-acceptor/kustomization.yaml | 9 - .../sameness/cluster-01-a-acceptor/patch.yaml | 13 - .../cluster-01-b-acceptor/kustomization.yaml | 9 - .../sameness/cluster-01-b-acceptor/patch.yaml | 13 - .../cluster-02-a-acceptor/kustomization.yaml | 9 - .../sameness/cluster-02-a-acceptor/patch.yaml | 13 - .../cluster-03-a-acceptor/kustomization.yaml | 9 - .../sameness/cluster-03-a-acceptor/patch.yaml | 13 - .../ap1-partition/kustomization.yaml | 9 - .../ap1-partition/patch.yaml | 16 - .../default-partition/kustomization.yaml | 9 - .../default-partition/patch.yaml | 16 - .../ap1-partition-tproxy/kustomization.yaml | 9 - .../ap1-partition-tproxy/patch.yaml | 21 - .../ap1-partition/kustomization.yaml | 9 - .../static-client/ap1-partition/patch.yaml | 22 - .../kustomization.yaml | 9 - .../default-partition-tproxy/patch.yaml | 21 - .../default-partition/kustomization.yaml | 9 - .../default-partition/patch.yaml | 22 - .../dc1-default/kustomization.yaml | 9 - .../static-server/dc1-default/patch.yaml | 23 - .../dc1-partition/kustomization.yaml | 9 - .../static-server/dc1-partition/patch.yaml | 23 - .../static-server/dc2/kustomization.yaml | 9 - .../sameness/static-server/dc2/patch.yaml | 23 - .../static-server/dc3/kustomization.yaml | 9 - .../sameness/static-server/dc3/patch.yaml | 23 - .../kustomization.yaml | 6 +- .../static-client-inject-multiport/patch.yaml | 3 - .../static-client-inject/kustomization.yaml | 6 +- .../cases/static-client-inject/patch.yaml | 3 - .../static-client-multi-dc/kustomization.yaml | 6 +- .../cases/static-client-multi-dc/patch.yaml | 3 - .../kustomization.yaml | 6 +- .../cases/static-client-namespaces/patch.yaml | 3 - .../kustomization.yaml | 9 +- .../kustomization.yaml | 9 +- .../kustomization.yaml | 6 +- .../default-ns-default-partition/patch.yaml | 3 - .../default-ns-partition/kustomization.yaml | 6 +- .../default-ns-partition/patch.yaml | 3 - .../ns-default-partition/kustomization.yaml | 6 +- .../ns-default-partition/patch.yaml | 3 - .../ns-partition/kustomization.yaml | 6 +- .../ns-partition/patch.yaml | 3 - .../default-namespace/kustomization.yaml | 6 +- .../default-namespace/patch.yaml | 3 - .../default/kustomization.yaml | 6 +- .../static-client-peers/default/patch.yaml | 3 - .../non-default-namespace/kustomization.yaml | 6 +- .../non-default-namespace/patch.yaml | 3 - .../static-client-tproxy/kustomization.yaml | 6 +- .../cases/static-client-tproxy/patch.yaml | 3 - .../static-server-inject/kustomization.yaml | 6 +- .../cases/static-server-inject/patch.yaml | 3 - .../kustomization.yaml | 9 +- .../kustomization.yaml | 9 - .../cases/trafficpermissions-deny/patch.yaml | 9 - .../kustomization.yaml | 9 - .../v2-static-client-inject-tproxy/patch.yaml | 13 - .../kustomization.yaml | 9 - .../cases/v2-static-client-inject/patch.yaml | 13 - .../dc1-ns2-static-server/kustomization.yaml | 10 - .../dc1-ns2-static-server/patch.yaml | 41 - .../dc1-static-server/kustomization.yaml | 10 - .../dc1-static-server/patch.yaml | 41 - .../dc2-static-server/kustomization.yaml | 10 - .../dc2-static-server/patch.yaml | 41 - .../service-resolver/kustomization.yaml | 10 - .../service-resolver/patch.yaml | 15 - .../static-client/kustomization.yaml | 10 - .../wan-federation/static-client/patch.yaml | 22 - .../ingress_gateway_namespaces_test.go | 15 +- .../ingress-gateway/ingress_gateway_test.go | 7 +- acceptance/tests/ingress-gateway/main_test.go | 3 - acceptance/tests/mesh_v2/main_test.go | 18 - acceptance/tests/mesh_v2/mesh_inject_test.go | 155 - acceptance/tests/metrics/main_test.go | 3 - acceptance/tests/metrics/metrics_test.go | 19 +- acceptance/tests/partitions/main_test.go | 5 +- .../partitions/partitions_connect_test.go | 68 +- .../partitions/partitions_gateway_test.go | 360 -- .../tests/partitions/partitions_sync_test.go | 13 +- acceptance/tests/peering/main_test.go | 3 - .../peering_connect_namespaces_test.go | 117 +- .../tests/peering/peering_connect_test.go | 198 +- .../tests/peering/peering_gateway_test.go | 306 -- acceptance/tests/sameness/main_test.go | 28 - acceptance/tests/sameness/sameness_test.go | 875 ----- acceptance/tests/segments/main_test.go | 18 - acceptance/tests/segments/segments_test.go | 187 - acceptance/tests/server/main_test.go | 18 - acceptance/tests/server/server_test.go | 91 - acceptance/tests/snapshot-agent/main_test.go | 3 - .../snapshot_agent_k8s_secret_test.go | 7 +- .../snapshot_agent_vault_test.go | 7 +- acceptance/tests/sync/main_test.go | 3 - .../sync/sync_catalog_namespaces_test.go | 7 +- acceptance/tests/sync/sync_catalog_test.go | 13 +- acceptance/tests/tenancy_v2/main_test.go | 30 - acceptance/tests/tenancy_v2/partition_test.go | 91 - .../tests/terminating-gateway/common.go | 53 +- .../tests/terminating-gateway/main_test.go | 3 - .../terminating_gateway_destinations_test.go | 68 +- .../terminating_gateway_namespaces_test.go | 33 +- .../terminating_gateway_test.go | 48 +- acceptance/tests/vault/main_test.go | 3 - .../tests/vault/vault_namespaces_test.go | 10 +- .../tests/vault/vault_partitions_test.go | 3 - acceptance/tests/vault/vault_test.go | 88 +- .../tests/vault/vault_tls_auto_reload_test.go | 11 +- acceptance/tests/vault/vault_wan_fed_test.go | 13 +- acceptance/tests/wan-federation/main_test.go | 3 - .../wan_federation_gateway_test.go | 235 -- .../wan-federation/wan_federation_test.go | 326 +- charts/consul/.helmignore | 1 - charts/consul/Chart.yaml | 17 +- charts/consul/addons/gen.sh | 3 - charts/consul/addons/values/prometheus.yaml | 3 - charts/consul/templates/_helpers.tpl | 183 +- .../api-gateway-controller-deployment.yaml | 1 - .../templates/client-config-configmap.yaml | 3 + charts/consul/templates/client-daemonset.yaml | 10 +- .../client-tmp-extra-config-configmap.yaml | 21 - charts/consul/templates/cni-daemonset.yaml | 1 - .../templates/connect-inject-clusterrole.yaml | 194 +- .../templates/connect-inject-deployment.yaml | 15 +- ...t-inject-mutatingwebhookconfiguration.yaml | 86 - ...inject-validatingwebhookconfiguration.yaml | 31 - charts/consul/templates/crd-apigateways.yaml | 240 -- .../crd-controlplanerequestlimits.yaml | 195 - .../templates/crd-exportedservices-v1.yaml | 139 - .../templates/crd-exportedservices.yaml | 57 +- .../templates/crd-gatewayclassconfigs-v1.yaml | 201 -- .../templates/crd-gatewayclassconfigs.yaml | 1826 ---------- .../crd-gatewayclasses-external.yaml | 328 -- .../consul/templates/crd-gatewayclasses.yaml | 128 - .../consul/templates/crd-gatewaypolicies.yaml | 282 -- .../templates/crd-gateways-external.yaml | 882 ----- .../templates/crd-grpcroutes-external.yaml | 766 ---- charts/consul/templates/crd-grpcroutes.yaml | 617 ---- .../templates/crd-httproutes-external.yaml | 1914 ---------- charts/consul/templates/crd-httproutes.yaml | 673 ---- .../consul/templates/crd-ingressgateways.yaml | 90 +- charts/consul/templates/crd-jwtproviders.yaml | 313 -- .../templates/crd-meshconfigurations.yaml | 100 - charts/consul/templates/crd-meshes.yaml | 17 +- charts/consul/templates/crd-meshgateways.yaml | 134 - charts/consul/templates/crd-meshservices.yaml | 56 - .../templates/crd-peeringacceptors.yaml | 12 +- .../consul/templates/crd-peeringdialers.yaml | 12 +- .../templates/crd-proxyconfigurations.yaml | 405 --- .../consul/templates/crd-proxydefaults.yaml | 99 +- .../crd-referencegrants-external.yaml | 208 -- .../templates/crd-routeauthfilters.yaml | 199 - .../templates/crd-routeretryfilters.yaml | 115 - .../templates/crd-routetimeoutfilters.yaml | 107 - .../consul/templates/crd-samenessgroups.yaml | 129 - .../consul/templates/crd-servicedefaults.yaml | 156 +- .../templates/crd-serviceintentions.yaml | 94 +- .../templates/crd-serviceresolvers.yaml | 50 +- .../consul/templates/crd-servicerouters.yaml | 23 +- .../templates/crd-servicesplitters.yaml | 12 +- .../templates/crd-tcproutes-external.yaml | 281 -- charts/consul/templates/crd-tcproutes.yaml | 278 -- .../templates/crd-terminatinggateways.yaml | 12 +- .../templates/crd-tlsroutes-external.yaml | 291 -- .../templates/crd-trafficpermissions.yaml | 261 -- .../templates/crd-udproutes-external.yaml | 281 -- .../create-federation-secret-job.yaml | 1 - .../templates/enterprise-license-job.yaml | 1 - .../gateway-cleanup-clusterrole.yaml | 44 - .../gateway-cleanup-clusterrolebinding.yaml | 20 - .../consul/templates/gateway-cleanup-job.yaml | 67 - .../gateway-cleanup-podsecuritypolicy.yaml | 32 - .../gateway-cleanup-serviceaccount.yaml | 13 - .../gateway-resources-clusterrole.yaml | 47 - .../gateway-resources-clusterrolebinding.yaml | 20 - .../gateway-resources-configmap.yaml | 132 - .../templates/gateway-resources-job.yaml | 124 - .../gateway-resources-podsecuritypolicy.yaml | 32 - .../gateway-resources-serviceaccount.yaml | 13 - .../gossip-encryption-autogenerate-job.yaml | 1 - .../ingress-gateways-deployment.yaml | 1 - .../templates/mesh-gateway-clusterrole.yaml | 2 - .../mesh-gateway-clusterrolebinding.yaml | 2 - .../templates/mesh-gateway-deployment.yaml | 3 - .../mesh-gateway-podsecuritypolicy.yaml | 2 - .../templates/mesh-gateway-service.yaml | 2 - .../mesh-gateway-serviceaccount.yaml | 2 - .../consul/templates/partition-init-job.yaml | 4 - charts/consul/templates/prometheus.yaml | 2 +- .../server-acl-init-cleanup-job.yaml | 1 - .../consul/templates/server-acl-init-job.yaml | 63 +- .../consul/templates/server-clusterrole.yaml | 16 - .../templates/server-clusterrolebinding.yaml | 18 - .../templates/server-config-configmap.yaml | 17 +- .../templates/server-disruptionbudget.yaml | 2 +- .../consul/templates/server-statefulset.yaml | 73 +- .../server-tmp-extra-config-configmap.yaml | 21 - .../templates/sync-catalog-deployment.yaml | 1 - .../telemetry-collector-configmap.yaml | 18 - .../telemetry-collector-deployment.yaml | 436 --- ...telemetry-collector-podsecuritypolicy.yaml | 42 - .../templates/telemetry-collector-role.yaml | 21 - .../telemetry-collector-rolebinding.yaml | 21 - .../telemetry-collector-service.yaml | 24 - .../telemetry-collector-serviceaccount.yaml | 23 - .../telemetry-collector-v2-deployment.yaml | 415 --- .../terminating-gateways-deployment.yaml | 1 - .../templates/tls-init-cleanup-job.yaml | 1 - charts/consul/templates/tls-init-job.yaml | 1 - .../webhook-cert-manager-clusterrole.yaml | 3 +- ...bhook-cert-manager-clusterrolebinding.yaml | 2 +- .../webhook-cert-manager-configmap.yaml | 2 +- .../webhook-cert-manager-deployment.yaml | 3 +- ...ebhook-cert-manager-podsecuritypolicy.yaml | 2 +- .../webhook-cert-manager-serviceaccount.yaml | 2 +- charts/consul/test/docker/Test.dockerfile | 3 - charts/consul/test/terraform/eks/main.tf | 15 +- charts/consul/test/terraform/eks/outputs.tf | 2 +- charts/consul/test/terraform/eks/variables.tf | 2 +- charts/consul/test/terraform/gke/main.tf | 11 +- charts/consul/test/terraform/gke/outputs.tf | 6 +- charts/consul/test/terraform/gke/variables.tf | 2 +- .../consul/test/terraform/openshift/main.tf | 3 - .../test/terraform/openshift/oc-login.sh | 3 - .../test/terraform/openshift/outputs.tf | 3 - .../test/terraform/openshift/variables.tf | 3 - charts/consul/test/unit/_helpers.bash | 3 - .../test/unit/client-config-configmap.bats | 11 + charts/consul/test/unit/client-daemonset.bats | 21 +- .../client-tmp-extra-config-configmap.bats | 43 - .../test/unit/connect-inject-clusterrole.bats | 62 +- .../test/unit/connect-inject-deployment.bats | 128 +- ...t-inject-mutatingwebhookconfiguration.bats | 4 +- .../unit/crd-controlplanerequestlimits.bats | 26 - .../test/unit/crd-exportedservices.bats | 2 +- .../test/unit/crd-gatewayclassconfigs.bats | 20 - .../unit/crd-gatewayclasses-external.bats | 28 - .../consul/test/unit/crd-gatewaypolicies.bats | 20 - .../test/unit/crd-gateways-external.bats | 28 - .../test/unit/crd-grpcroutes-external.bats | 28 - .../test/unit/crd-httproutes-external.bats | 28 - charts/consul/test/unit/crd-meshservices.bats | 21 - .../test/unit/crd-routeauthfilters.bats | 20 - .../test/unit/crd-tcproutes-external.bats | 47 - .../test/unit/crd-tlsroutes-external.bats | 28 - .../test/unit/crd-udproutes-external.bats | 28 - .../unit/gateway-cleanup-clusterrole.bats | 33 - .../gateway-cleanup-clusterrolebinding.bats | 23 - .../consul/test/unit/gateway-cleanup-job.bats | 39 - .../gateway-cleanup-podsecuritypolicy.bats | 41 - .../unit/gateway-cleanup-serviceaccount.bats | 23 - .../unit/gateway-resources-clusterrole.bats | 33 - .../gateway-resources-clusterrolebinding.bats | 23 - .../unit/gateway-resources-configmap.bats | 258 -- .../test/unit/gateway-resources-job.bats | 158 - .../gateway-resources-podsecuritypolicy.bats | 41 - .../gateway-resources-serviceaccount.bats | 23 - charts/consul/test/unit/helpers.bats | 141 +- .../unit/ingress-gateways-deployment.bats | 20 +- .../test/unit/mesh-gateway-deployment.bats | 17 +- .../consul/test/unit/partition-init-job.bats | 37 +- .../unit/server-acl-init-cleanup-job.bats | 6 +- .../consul/test/unit/server-acl-init-job.bats | 188 +- .../test/unit/server-config-configmap.bats | 160 +- .../test/unit/server-disruptionbudget.bats | 28 +- .../consul/test/unit/server-statefulset.bats | 199 +- .../server-tmp-extra-config-configmap.bats | 49 - .../test/unit/sync-catalog-deployment.bats | 12 +- .../unit/telemetry-collector-configmap.bats | 29 - .../unit/telemetry-collector-deployment.bats | 1373 ------- ...telemetry-collector-podsecuritypolicy.bats | 21 - .../test/unit/telemetry-collector-role.bats | 21 - .../unit/telemetry-collector-rolebinding.bats | 21 - .../unit/telemetry-collector-service.bats | 86 - .../telemetry-collector-serviceaccount.bats | 84 - .../telemetry-collector-v2-deployment.bats | 1406 -------- .../unit/terminating-gateways-deployment.bats | 16 +- .../test/unit/tls-init-cleanup-job.bats | 6 +- charts/consul/test/unit/tls-init-job.bats | 6 +- .../webhook-cert-manager-clusterrole.bats | 5 +- ...bhook-cert-manager-clusterrolebinding.bats | 5 +- .../unit/webhook-cert-manager-configmap.bats | 5 +- .../unit/webhook-cert-manager-deployment.bats | 5 +- ...ebhook-cert-manager-podsecuritypolicy.bats | 5 +- .../webhook-cert-manager-serviceaccount.bats | 5 +- charts/consul/values.yaml | 441 +-- charts/demo/Chart.yaml | 3 - charts/demo/templates/frontend.yaml | 3 - charts/demo/templates/intentions.yaml | 14 - charts/demo/templates/nginx.yaml | 3 - charts/demo/templates/payments.yaml | 3 - charts/demo/templates/postgres.yaml | 3 - charts/demo/templates/products-api.yaml | 3 - charts/demo/templates/public-api.yaml | 3 - charts/demo/values.yaml | 3 - charts/embed_chart.go | 4 - charts/go.mod | 2 +- cli/cmd/config/command.go | 3 - cli/cmd/config/read/command.go | 3 - cli/cmd/config/read/command_test.go | 3 - cli/cmd/install/install.go | 5 +- cli/cmd/install/install_test.go | 21 +- cli/cmd/proxy/command.go | 3 - cli/cmd/proxy/list/command.go | 88 +- cli/cmd/proxy/list/command_test.go | 124 - cli/cmd/proxy/loglevel/command.go | 349 -- cli/cmd/proxy/loglevel/command_test.go | 296 -- cli/cmd/proxy/read/command.go | 45 +- cli/cmd/proxy/read/command_test.go | 117 +- .../http.go => cmd/proxy/read/config.go} | 63 +- .../proxy/read/config_test.go} | 62 +- .../proxy/read/envoy_types.go} | 5 +- cli/cmd/proxy/read/filters.go | 17 +- cli/cmd/proxy/read/filters_test.go | 49 +- cli/cmd/proxy/read/format.go | 20 +- cli/cmd/proxy/read/format_test.go | 21 +- .../proxy/read}/test_clusters.json | 0 .../proxy/read}/test_config_dump.json | 0 cli/cmd/proxy/stats/command.go | 225 -- cli/cmd/proxy/stats/command_test.go | 160 - cli/cmd/status/status.go | 3 - cli/cmd/status/status_test.go | 3 - cli/cmd/troubleshoot/command.go | 29 - cli/cmd/troubleshoot/proxy/proxy.go | 285 -- cli/cmd/troubleshoot/proxy/proxy_test.go | 75 - cli/cmd/troubleshoot/upstreams/upstreams.go | 277 -- .../troubleshoot/upstreams/upstreams_test.go | 130 - cli/cmd/uninstall/uninstall.go | 3 - cli/cmd/uninstall/uninstall_test.go | 3 - cli/cmd/upgrade/upgrade.go | 6 +- cli/cmd/upgrade/upgrade_test.go | 7 +- cli/cmd/version/version.go | 3 - cli/commands.go | 34 +- cli/common/base.go | 3 - cli/common/diff.go | 3 - cli/common/diff_test.go | 3 - cli/common/envoy/logger_params.go | 169 - cli/common/envoy/logger_params_test.go | 175 - .../envoy/testdata/fetch_debug_levels.txt | 59 - cli/common/error.go | 3 - cli/common/flag/doc.go | 3 - cli/common/flag/flag.go | 3 - cli/common/flag/flag_bool.go | 3 - cli/common/flag/flag_enum.go | 3 - cli/common/flag/flag_enum_single.go | 3 - cli/common/flag/flag_float.go | 3 - cli/common/flag/flag_int.go | 3 - cli/common/flag/flag_string.go | 3 - cli/common/flag/flag_string_map.go | 3 - cli/common/flag/flag_string_slice.go | 3 - cli/common/flag/flag_string_slice_test.go | 3 - cli/common/flag/flag_time.go | 3 - cli/common/flag/flag_var.go | 3 - cli/common/flag/set.go | 3 - cli/common/flag/set_test.go | 3 - cli/common/portforward.go | 8 - cli/common/portforward_test.go | 3 - cli/common/terminal/basic.go | 3 - cli/common/terminal/doc.go | 3 - cli/common/terminal/table.go | 36 +- cli/common/terminal/ui.go | 3 - cli/common/usage.go | 3 - cli/common/utils.go | 3 - cli/common/utils_test.go | 3 - cli/config/config.go | 3 - cli/go.mod | 120 +- cli/go.sum | 425 ++- cli/helm/action.go | 3 - cli/helm/chart.go | 7 +- cli/helm/chart_test.go | 9 +- cli/helm/install.go | 3 - cli/helm/install_test.go | 3 - cli/helm/mock.go | 3 - cli/helm/test_fixtures/consul/Chart.yaml | 3 - .../test_fixtures/consul/templates/foo.yaml | 3 - cli/helm/test_fixtures/consul/values.yaml | 3 - cli/helm/upgrade.go | 3 - cli/helm/upgrade_test.go | 3 - cli/helm/values.go | 15 +- cli/main.go | 3 - cli/preset/cloud_preset.go | 129 +- cli/preset/cloud_preset_test.go | 250 +- cli/preset/demo.go | 5 +- cli/preset/preset.go | 3 - cli/preset/preset_test.go | 3 - cli/preset/quickstart.go | 5 +- cli/preset/secure.go | 5 +- cli/release/release.go | 3 - cli/release/release_test.go | 3 - cli/validation/kubernetes.go | 3 - cli/validation/kubernetes_test.go | 3 - cli/version/fips_build.go | 30 - cli/version/non_fips_build.go | 15 - cli/version/version.go | 11 +- control-plane/Dockerfile | 19 +- control-plane/Dockerfile.dev | 11 - control-plane/PROJECT | 74 - .../api-gateway/binding/annotations.go | 37 - .../api-gateway/binding/annotations_test.go | 207 -- control-plane/api-gateway/binding/binder.go | 436 --- .../api-gateway/binding/binder_test.go | 3187 ----------------- .../api-gateway/binding/reference_grant.go | 148 - .../binding/reference_grant_test.go | 454 --- .../api-gateway/binding/registration.go | 92 - .../api-gateway/binding/registration_test.go | 83 - control-plane/api-gateway/binding/result.go | 741 ---- .../api-gateway/binding/result_test.go | 263 -- .../api-gateway/binding/route_binding.go | 528 --- control-plane/api-gateway/binding/setter.go | 132 - .../api-gateway/binding/setter_test.go | 42 - control-plane/api-gateway/binding/snapshot.go | 66 - .../api-gateway/binding/validation.go | 730 ---- .../api-gateway/binding/validation_test.go | 1573 -------- control-plane/api-gateway/cache/consul.go | 539 --- .../api-gateway/cache/consul_test.go | 2060 ----------- control-plane/api-gateway/cache/gateway.go | 148 - control-plane/api-gateway/cache/kubernetes.go | 32 - .../api-gateway/cache/subscription.go | 30 - control-plane/api-gateway/common/constants.go | 15 - control-plane/api-gateway/common/diff.go | 367 -- control-plane/api-gateway/common/diff_test.go | 2155 ----------- .../api-gateway/common/finalizers.go | 60 - .../api-gateway/common/helm_config.go | 67 - control-plane/api-gateway/common/helpers.go | 237 -- .../api-gateway/common/helpers_test.go | 175 - control-plane/api-gateway/common/labels.go | 41 - control-plane/api-gateway/common/reference.go | 184 - control-plane/api-gateway/common/resources.go | 720 ---- .../api-gateway/common/resources_test.go | 57 - control-plane/api-gateway/common/secrets.go | 123 - .../api-gateway/common/secrets_test.go | 108 - .../api-gateway/common/translation.go | 574 --- .../api-gateway/common/translation_test.go | 1900 ---------- .../api-gateway/controllers/finalizer.go | 44 - .../api-gateway/controllers/finalizer_test.go | 84 - .../controllers/gateway_controller.go | 1290 ------- .../gateway_controller_integration_test.go | 1637 --------- .../controllers/gateway_controller_test.go | 642 ---- .../controllers/gatewayclass_controller.go | 271 -- .../gatewayclass_controller_test.go | 276 -- .../gatewayclassconfig_controller.go | 139 - .../gatewayclassconfig_controller_test.go | 123 - .../api-gateway/controllers/index.go | 366 -- .../api-gateway/controllers/index_test.go | 13 - .../api-gateway/gatekeeper/dataplane.go | 178 - .../api-gateway/gatekeeper/deployment.go | 236 -- .../api-gateway/gatekeeper/gatekeeper.go | 103 - .../api-gateway/gatekeeper/gatekeeper_test.go | 1351 ------- control-plane/api-gateway/gatekeeper/init.go | 198 - control-plane/api-gateway/gatekeeper/role.go | 94 - .../api-gateway/gatekeeper/rolebinding.go | 90 - .../api-gateway/gatekeeper/service.go | 154 - .../api-gateway/gatekeeper/serviceaccount.go | 80 - .../auth/v2beta1/auth_groupversion_info.go | 27 - .../api/auth/v2beta1/shared_types.go | 14 - control-plane/api/auth/v2beta1/status.go | 93 - .../auth/v2beta1/traffic_permissions_types.go | 242 -- .../v2beta1/traffic_permissions_types_test.go | 1048 ------ .../v2beta1/trafficpermissions_webhook.go | 65 - .../api/auth/v2beta1/zz_generated.deepcopy.go | 136 - control-plane/api/common/common.go | 89 +- control-plane/api/common/configentry.go | 3 - .../api/common/configentry_webhook.go | 3 - .../api/common/configentry_webhook_test.go | 3 - control-plane/api/common/consul_resource.go | 59 - .../api/common/consul_resource_webhook.go | 87 - .../common/consul_resource_webhook_test.go | 333 -- .../api/mesh/v2beta1/api_gateway_types.go | 169 - .../v2beta1/gateway_class_config_types.go | 169 - .../api/mesh/v2beta1/gateway_class_types.go | 151 - .../api/mesh/v2beta1/grpc_route_types.go | 327 -- .../api/mesh/v2beta1/grpc_route_types_test.go | 1201 ------- .../api/mesh/v2beta1/grpc_route_webhook.go | 65 - .../api/mesh/v2beta1/http_route_types.go | 309 -- .../api/mesh/v2beta1/http_route_types_test.go | 1338 ------- .../api/mesh/v2beta1/http_route_webhook.go | 65 - .../mesh/v2beta1/mesh_configuration_types.go | 150 - .../api/mesh/v2beta1/mesh_gateway_types.go | 152 - .../mesh/v2beta1/mesh_groupversion_info.go | 27 - .../proxy_configuration_route_webhook.go | 65 - .../mesh/v2beta1/proxy_configuration_types.go | 160 - .../v2beta1/proxy_configuration_types_test.go | 551 --- .../api/mesh/v2beta1/shared_types.go | 14 - control-plane/api/mesh/v2beta1/status.go | 93 - .../api/mesh/v2beta1/tcp_route_types.go | 195 - .../api/mesh/v2beta1/tcp_route_types_test.go | 577 --- .../api/mesh/v2beta1/tcp_route_webhook.go | 65 - .../api/mesh/v2beta1/zz_generated.deepcopy.go | 940 ----- .../v2beta1/exported_services_types.go | 152 - .../v2beta1/multicluster_groupversion_info.go | 27 - .../api/multicluster/v2beta1/shared_types.go | 14 - .../api/multicluster/v2beta1/status.go | 93 - .../v2beta1/zz_generated.deepcopy.go | 136 - .../api/v1alpha1/api_gateway_types.go | 144 - .../api/v1alpha1/api_gateway_types_test.go | 49 - .../controlplanerequestlimit_types.go | 271 -- .../controlplanerequestlimit_types_test.go | 569 --- .../controlplanerequestlimit_webhook.go | 83 - .../controlplanerequestlimit_webhook_test.go | 145 - .../api/v1alpha1/exportedservices_types.go | 55 +- .../v1alpha1/exportedservices_types_test.go | 126 +- .../api/v1alpha1/exportedservices_webhook.go | 3 - .../v1alpha1/exportedservices_webhook_test.go | 5 +- .../api/v1alpha1/gatewaypolicy_types.go | 134 - .../api/v1alpha1/gatewaypolicy_webhook.go | 82 - .../v1alpha1/gatewaypolicy_webhook_test.go | 282 -- .../api/v1alpha1/groupversion_info.go | 3 - .../api/v1alpha1/ingressgateway_types.go | 25 +- .../api/v1alpha1/ingressgateway_types_test.go | 44 +- .../api/v1alpha1/ingressgateway_webhook.go | 3 - .../api/v1alpha1/jwtprovider_types.go | 854 ----- .../api/v1alpha1/jwtprovider_types_test.go | 982 ----- .../api/v1alpha1/jwtprovider_webhook.go | 61 - control-plane/api/v1alpha1/mesh_types.go | 17 +- control-plane/api/v1alpha1/mesh_types_test.go | 7 - control-plane/api/v1alpha1/mesh_webhook.go | 3 - .../api/v1alpha1/mesh_webhook_test.go | 3 - .../api/v1alpha1/peeringacceptor_types.go | 3 - .../v1alpha1/peeringacceptor_types_test.go | 3 - .../api/v1alpha1/peeringacceptor_webhook.go | 3 - .../v1alpha1/peeringacceptor_webhook_test.go | 3 - .../api/v1alpha1/peeringdialer_types.go | 3 - .../api/v1alpha1/peeringdialer_types_test.go | 3 - .../api/v1alpha1/peeringdialer_webhook.go | 3 - .../v1alpha1/peeringdialer_webhook_test.go | 3 - .../api/v1alpha1/proxydefaults_types.go | 140 +- .../api/v1alpha1/proxydefaults_types_test.go | 339 +- .../api/v1alpha1/proxydefaults_webhook.go | 3 - .../v1alpha1/proxydefaults_webhook_test.go | 5 +- .../api/v1alpha1/routeauthfilter_types.go | 65 - .../api/v1alpha1/routeretryfilter_types.go | 55 - .../api/v1alpha1/routetimeoutfilter_types.go | 55 - .../api/v1alpha1/samenessgroup_types.go | 272 -- .../api/v1alpha1/samenessgroup_types_test.go | 399 --- .../api/v1alpha1/samenessgroup_webhook.go | 61 - .../api/v1alpha1/servicedefaults_types.go | 260 +- .../v1alpha1/servicedefaults_types_test.go | 517 +-- .../api/v1alpha1/servicedefaults_webhook.go | 3 - .../api/v1alpha1/serviceintentions_types.go | 176 +- .../v1alpha1/serviceintentions_types_test.go | 368 +- .../api/v1alpha1/serviceintentions_webhook.go | 3 - .../serviceintentions_webhook_test.go | 3 - .../api/v1alpha1/serviceresolver_types.go | 260 +- .../v1alpha1/serviceresolver_types_test.go | 674 +--- .../api/v1alpha1/serviceresolver_webhook.go | 3 - .../api/v1alpha1/servicerouter_types.go | 39 +- .../api/v1alpha1/servicerouter_types_test.go | 39 +- .../api/v1alpha1/servicerouter_webhook.go | 3 - .../api/v1alpha1/servicesplitter_types.go | 18 +- .../v1alpha1/servicesplitter_types_test.go | 7 +- .../api/v1alpha1/servicesplitter_webhook.go | 3 - control-plane/api/v1alpha1/shared_types.go | 165 - control-plane/api/v1alpha1/status.go | 3 - .../api/v1alpha1/terminatinggateway_types.go | 3 - .../v1alpha1/terminatinggateway_types_test.go | 3 - .../v1alpha1/terminatinggateway_webhook.go | 3 - .../api/v1alpha1/zz_generated.deepcopy.go | 2279 ++---------- .../build-support/controller/README.md | 5 + .../controller/boilerplate.go.txt | 0 .../build-support/functions/00-vars.sh | 3 - .../build-support/functions/10-util.sh | 84 +- .../build-support/functions/20-build.sh | 17 +- .../build-support/functions/40-publish.sh | 3 - .../build-support/scripts/build-local.sh | 10 - .../scripts/consul-enterprise-version.sh | 11 +- .../build-support/scripts/functions.sh | 3 - .../scripts/terraformfmtcheck.sh | 3 - .../build-support/scripts/version.sh | 3 - control-plane/catalog/to-consul/annotation.go | 3 - control-plane/catalog/to-consul/resource.go | 9 +- .../catalog/to-consul/resource_test.go | 3 - control-plane/catalog/to-consul/service_id.go | 3 - control-plane/catalog/to-consul/syncer.go | 3 - .../catalog/to-consul/syncer_ent_test.go | 3 - .../catalog/to-consul/syncer_test.go | 10 +- control-plane/catalog/to-consul/testing.go | 3 - control-plane/catalog/to-k8s/sink.go | 3 - control-plane/catalog/to-k8s/sink_test.go | 3 - control-plane/catalog/to-k8s/source.go | 3 - control-plane/catalog/to-k8s/source_test.go | 3 - control-plane/catalog/to-k8s/testing.go | 3 - control-plane/cni/config/config.go | 3 - control-plane/cni/go.mod | 64 +- control-plane/cni/go.sum | 378 +- control-plane/cni/main.go | 15 +- control-plane/cni/main_test.go | 12 - control-plane/commands.go | 25 +- ...nsul.hashicorp.com_trafficpermissions.yaml | 256 -- ...shicorp.com_controlplanerequestlimits.yaml | 190 - ...consul.hashicorp.com_exportedservices.yaml | 21 +- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 196 - .../consul.hashicorp.com_gatewaypolicies.yaml | 277 -- .../consul.hashicorp.com_ingressgateways.yaml | 91 +- .../consul.hashicorp.com_jwtproviders.yaml | 308 -- .../bases/consul.hashicorp.com_meshes.yaml | 18 +- .../consul.hashicorp.com_meshservices.yaml | 51 - ...consul.hashicorp.com_peeringacceptors.yaml | 13 +- .../consul.hashicorp.com_peeringdialers.yaml | 13 +- .../consul.hashicorp.com_proxydefaults.yaml | 100 +- ...consul.hashicorp.com_routeauthfilters.yaml | 194 - ...onsul.hashicorp.com_routeretryfilters.yaml | 110 - ...sul.hashicorp.com_routetimeoutfilters.yaml | 102 - .../consul.hashicorp.com_samenessgroups.yaml | 124 - .../consul.hashicorp.com_servicedefaults.yaml | 157 +- ...onsul.hashicorp.com_serviceintentions.yaml | 95 +- ...consul.hashicorp.com_serviceresolvers.yaml | 51 +- .../consul.hashicorp.com_servicerouters.yaml | 24 +- ...consul.hashicorp.com_servicesplitters.yaml | 13 +- ...sul.hashicorp.com_terminatinggateways.yaml | 13 +- ...mesh.consul.hashicorp.com_apigateways.yaml | 235 -- ...sul.hashicorp.com_gatewayclassconfigs.yaml | 1821 ---------- ...h.consul.hashicorp.com_gatewayclasses.yaml | 123 - .../mesh.consul.hashicorp.com_grpcroutes.yaml | 612 ---- .../mesh.consul.hashicorp.com_httproutes.yaml | 668 ---- ...nsul.hashicorp.com_meshconfigurations.yaml | 95 - ...esh.consul.hashicorp.com_meshgateways.yaml | 129 - ...sul.hashicorp.com_proxyconfigurations.yaml | 400 --- .../mesh.consul.hashicorp.com_tcproutes.yaml | 273 -- ...consul.hashicorp.com_exportedservices.yaml | 103 - ...ewayclasses.gateway.networking.k8s.io.yaml | 320 -- .../gateways.gateway.networking.k8s.io.yaml | 874 ----- .../grpcroutes.gateway.networking.k8s.io.yaml | 758 ---- .../httproutes.gateway.networking.k8s.io.yaml | 1906 ---------- .../config/crd/external/kustomization.yaml | 10 - ...rencegrants.gateway.networking.k8s.io.yaml | 200 -- .../tcproutes.gateway.networking.k8s.io.yaml | 273 -- .../tlsroutes.gateway.networking.k8s.io.yaml | 283 -- .../udproutes.gateway.networking.k8s.io.yaml | 273 -- control-plane/config/crd/kustomizeconfig.yaml | 20 - control-plane/config/rbac/role.yaml | 264 +- control-plane/config/webhook/manifests.yaml | 199 +- .../common/annotation_processor.go | 268 -- .../common/annotation_processor_test.go | 1014 ------ control-plane/connect-inject/common/common.go | 143 +- .../connect-inject/common/common_test.go | 361 +- .../constants/annotations_and_labels.go | 73 +- .../connect-inject/constants/constants.go | 90 +- .../constants/constants_test.go | 85 - .../endpoints/consul_client_health_checks.go | 15 +- .../consul_client_health_checks_test.go | 10 +- .../endpoints/endpoints_controller.go | 443 +-- .../endpoints_controller_ent_test.go | 104 +- .../endpoints/endpoints_controller_test.go | 547 +-- .../endpointsv2/endpoints_controller.go | 644 ---- .../endpoints_controller_ent_test.go | 30 - .../endpointsv2/endpoints_controller_test.go | 2362 ------------ .../controllers/endpointsv2/write_cache.go | 130 - .../endpointsv2/write_cache_test.go | 240 -- .../peering/peering_acceptor_controller.go | 3 - .../peering_acceptor_controller_test.go | 3 - .../peering/peering_dialer_controller.go | 3 - .../peering/peering_dialer_controller_test.go | 3 - .../controllers/pod/pod_controller.go | 770 ---- .../pod/pod_controller_ent_test.go | 765 ---- .../controllers/pod/pod_controller_test.go | 2149 ----------- .../serviceaccount_controller.go | 196 - .../serviceaccount_controller_ent_test.go | 24 - .../serviceaccount_controller_test.go | 307 -- .../metrics/metrics_configuration.go | 6 +- .../metrics/metrics_configuration_test.go | 3 - .../namespace/namespace_controller.go | 131 - .../namespace_controller_ent_test.go | 413 --- .../webhook/consul_dataplane_sidecar.go | 119 +- .../webhook/consul_dataplane_sidecar_test.go | 249 +- .../connect-inject/webhook/container_env.go | 3 - .../webhook/container_env_test.go | 3 - .../connect-inject/webhook/container_init.go | 11 +- .../webhook/container_init_test.go | 5 - .../webhook/container_volume.go | 3 - control-plane/connect-inject/webhook/dns.go | 3 - .../connect-inject/webhook/dns_test.go | 3 - .../webhook/health_checks_test.go | 3 - .../connect-inject/webhook/heath_checks.go | 3 - .../connect-inject/webhook/mesh_webhook.go | 70 +- .../webhook/mesh_webhook_ent_test.go | 41 +- .../webhook/mesh_webhook_test.go | 466 +-- .../webhook/redirect_traffic.go | 22 +- .../webhook/redirect_traffic_test.go | 3 - .../webhookv2/consul_dataplane_sidecar.go | 523 --- .../consul_dataplane_sidecar_test.go | 1277 ------- .../connect-inject/webhookv2/container_env.go | 42 - .../webhookv2/container_env_test.go | 78 - .../webhookv2/container_init.go | 287 -- .../webhookv2/container_init_test.go | 808 ----- .../webhookv2/container_volume.go | 23 - control-plane/connect-inject/webhookv2/dns.go | 93 - .../connect-inject/webhookv2/dns_test.go | 105 - .../webhookv2/health_checks_test.go | 56 - .../connect-inject/webhookv2/heath_checks.go | 30 - .../connect-inject/webhookv2/mesh_webhook.go | 555 --- .../webhookv2/mesh_webhook_ent_test.go | 117 - .../webhookv2/mesh_webhook_test.go | 2177 ----------- .../webhookv2/redirect_traffic.go | 137 - .../webhookv2/redirect_traffic_test.go | 481 --- control-plane/consul/consul.go | 12 +- control-plane/consul/consul_test.go | 3 - control-plane/consul/dataplane_client.go | 28 - control-plane/consul/dataplane_client_test.go | 201 -- control-plane/consul/dynamic.go | 68 - control-plane/consul/dynamic_test.go | 76 - control-plane/consul/resource_client.go | 28 - control-plane/consul/resource_client_test.go | 110 - .../configentry_controller.go | 121 +- .../configentry_controller_ent_test.go | 759 ++++ .../configentry_controller_test.go | 434 +-- .../exportedservices_controller.go | 8 +- .../exportedservices_controller_ent_test.go | 38 +- .../ingressgateway_controller.go | 6 +- .../mesh_controller.go | 8 +- .../proxydefaults_controller.go | 8 +- .../servicedefaults_controller.go | 8 +- .../serviceintentions_controller.go | 8 +- .../serviceresolver_controller.go | 8 +- .../servicerouter_controller.go | 8 +- .../servicesplitter_controller.go | 8 +- .../terminatinggateway_controller.go | 8 +- .../configentry_controller_ent_test.go | 1388 ------- .../controlplanerequestlimit_controller.go | 46 - .../configentries/finalizer_patch.go | 81 - .../configentries/finalizer_patch_test.go | 100 - .../configentries/jwtprovider_controller.go | 46 - .../samenessgroups_controller.go | 48 - .../resources/api-gateway-controller.go | 44 - .../resources/consul_resource_controller.go | 327 -- .../consul_resource_controller_ent_test.go | 188 - .../consul_resource_controller_test.go | 770 ---- .../resources/exported_services_controller.go | 45 - .../gateway_class_config_controller.go | 45 - .../resources/gateway_class_controller.go | 45 - .../resources/grpc_route_controller.go | 43 - .../resources/http_route_controller.go | 43 - .../mesh_configuration_controller.go | 43 - .../resources/mesh_gateway_controller.go | 326 -- .../resources/mesh_gateway_controller_test.go | 601 ---- .../proxy_configuration_controller.go | 43 - .../resources/tcp_route_controller.go | 43 - .../traffic_permissions_controller.go | 43 - control-plane/gateways/builder.go | 27 - control-plane/gateways/constants.go | 32 - control-plane/gateways/deployment.go | 185 - .../deployment_dataplane_container.go | 208 -- .../gateways/deployment_init_container.go | 195 - control-plane/gateways/deployment_test.go | 1128 ------ control-plane/gateways/gateway_config.go | 58 - control-plane/gateways/metadata.go | 169 - control-plane/gateways/metadata_test.go | 342 -- control-plane/gateways/role.go | 45 - control-plane/gateways/service.go | 75 - control-plane/gateways/service_test.go | 156 - control-plane/gateways/serviceaccount.go | 24 - control-plane/gateways/serviceaccount_test.go | 34 - control-plane/go.mod | 113 +- control-plane/go.sum | 289 +- .../hack/lint-api-new-client/main.go | 3 - control-plane/helper/cert/notify.go | 3 - control-plane/helper/cert/notify_test.go | 3 - control-plane/helper/cert/source.go | 3 - control-plane/helper/cert/source_gen.go | 3 - control-plane/helper/cert/source_gen_test.go | 3 - control-plane/helper/cert/tls_util.go | 3 - control-plane/helper/coalesce/coalesce.go | 3 - .../helper/coalesce/coalesce_test.go | 3 - control-plane/helper/controller/controller.go | 3 - .../helper/controller/controller_test.go | 3 - control-plane/helper/controller/resource.go | 3 - control-plane/helper/controller/testing.go | 3 - control-plane/helper/go-discover/discover.go | 3 - .../helper/go-discover/discover_test.go | 3 - .../helper/go-discover/mocks/mock_provider.go | 3 - .../mutating_webhook_configuration.go | 51 + .../mutating_webhook_configuration_test.go | 44 + control-plane/helper/parsetags/parsetags.go | 3 - .../helper/parsetags/parsetags_test.go | 3 - control-plane/helper/test/test_util.go | 207 +- .../webhook_configuration.go | 105 - .../webhook_configuration_test.go | 112 - control-plane/main.go | 6 +- control-plane/namespaces/namespaces.go | 35 - control-plane/namespaces/namespaces_test.go | 55 +- control-plane/subcommand/acl-init/command.go | 12 +- .../subcommand/acl-init/command_test.go | 8 +- control-plane/subcommand/auth.go | 3 - control-plane/subcommand/common/common.go | 6 +- .../subcommand/common/common_test.go | 25 +- control-plane/subcommand/common/test_util.go | 3 - .../subcommand/connect-init/command.go | 31 +- .../connect-init/command_ent_test.go | 6 +- .../subcommand/connect-init/command_test.go | 12 +- .../subcommand/consul-logout/command.go | 10 +- .../subcommand/consul-logout/command_test.go | 14 +- .../create-federation-secret/command.go | 12 +- .../create-federation-secret/command_test.go | 28 +- .../delete-completed-job/command.go | 10 +- .../delete-completed-job/command_test.go | 3 - .../subcommand/fetch-server-region/command.go | 159 - .../fetch-server-region/command_test.go | 114 - control-plane/subcommand/flags/consul.go | 17 +- control-plane/subcommand/flags/consul_test.go | 14 +- .../subcommand/flags/flag_map_value.go | 3 - .../subcommand/flags/flag_map_value_test.go | 3 - .../subcommand/flags/flag_slice_value.go | 3 - .../subcommand/flags/flag_slice_value_test.go | 3 - control-plane/subcommand/flags/flags.go | 3 - control-plane/subcommand/flags/http.go | 6 +- control-plane/subcommand/flags/http_test.go | 3 - control-plane/subcommand/flags/k8s.go | 3 - control-plane/subcommand/flags/mapset.go | 3 - control-plane/subcommand/flags/usage.go | 3 - control-plane/subcommand/flags/usage_test.go | 3 - .../subcommand/gateway-cleanup/command.go | 355 -- .../gateway-cleanup/command_test.go | 250 -- .../subcommand/gateway-resources/command.go | 622 ---- .../gateway-resources/command_test.go | 647 ---- .../get-consul-client-ca/command.go | 12 +- .../get-consul-client-ca/command_test.go | 16 +- .../gossip-encryption-autogenerate/command.go | 10 +- .../command_test.go | 3 - .../subcommand/inject-connect/command.go | 562 ++- .../subcommand/inject-connect/command_test.go | 12 - .../inject-connect/v1controllers.go | 491 --- .../inject-connect/v2controllers.go | 372 -- .../subcommand/install-cni/binary.go | 3 - .../subcommand/install-cni/binary_test.go | 3 - .../subcommand/install-cni/cniconfig.go | 3 - .../subcommand/install-cni/cniconfig_test.go | 3 - .../subcommand/install-cni/command.go | 8 +- .../subcommand/install-cni/command_test.go | 6 +- .../subcommand/install-cni/kubeconfig.go | 3 - .../subcommand/install-cni/kubeconfig_test.go | 3 - control-plane/subcommand/mesh-init/command.go | 287 -- .../subcommand/mesh-init/command_ent_test.go | 115 - .../subcommand/mesh-init/command_test.go | 412 --- .../subcommand/partition-init/command.go | 172 +- .../partition-init/command_ent_test.go | 291 +- .../server-acl-init/anonymous_token.go | 20 +- .../server-acl-init/anonymous_token_test.go | 18 +- .../subcommand/server-acl-init/command.go | 293 +- .../server-acl-init/command_ent_test.go | 282 +- .../server-acl-init/command_test.go | 662 ++-- .../server-acl-init/connect_inject.go | 53 +- .../server-acl-init/connect_inject_test.go | 6 +- .../server-acl-init/create_or_update.go | 82 +- .../server-acl-init/create_or_update_test.go | 26 +- .../server-acl-init/k8s_secrets_backend.go | 66 - .../subcommand/server-acl-init/rules.go | 18 +- .../subcommand/server-acl-init/rules_test.go | 55 +- .../server-acl-init/secrets_backend.go | 21 - .../subcommand/server-acl-init/servers.go | 67 +- .../test_fake_secrets_backend.go | 23 - .../server-acl-init/vault_secrets_backend.go | 69 - .../subcommand/sync-catalog/command.go | 16 +- .../sync-catalog/command_ent_test.go | 6 +- .../subcommand/sync-catalog/command_test.go | 10 +- control-plane/subcommand/tls-init/command.go | 12 +- .../subcommand/tls-init/command_test.go | 3 - control-plane/subcommand/version/command.go | 3 - .../webhook-cert-manager/command.go | 46 +- .../webhook-cert-manager/command_test.go | 10 +- .../webhook-cert-manager/mocks/mocks.go | 3 - control-plane/tenancy/namespace/namespace.go | 117 - .../tenancy/namespace/namespace_controller.go | 95 - .../namespace_controller_ent_test.go | 35 - .../namespace/namespace_controller_test.go | 301 -- control-plane/version/fips_build.go | 30 - control-plane/version/non_fips_build.go | 15 - control-plane/version/version.go | 11 +- docs/admin-partitions-with-acls.md | 98 + hack/aws-acceptance-test-cleanup/go.mod | 2 +- hack/aws-acceptance-test-cleanup/main.go | 128 - hack/camel-crds/go.mod | 15 - hack/camel-crds/go.sum | 25 - hack/camel-crds/main.go | 117 - hack/copy-crds-to-chart/go.mod | 2 +- hack/copy-crds-to-chart/main.go | 138 +- hack/helm-reference-gen/doc_node.go | 3 - .../fixtures/full-values.yaml | 3 - hack/helm-reference-gen/go.mod | 2 +- hack/helm-reference-gen/main.go | 10 +- hack/helm-reference-gen/main_test.go | 3 - hack/helm-reference-gen/parse_error.go | 3 - 1279 files changed, 6322 insertions(+), 128661 deletions(-) delete mode 100644 .changelog/1914.txt delete mode 100644 .changelog/1920.txt create mode 100644 .changelog/1976.txt delete mode 100644 .changelog/2029.txt delete mode 100644 .changelog/2030.txt delete mode 100644 .changelog/2048.txt delete mode 100644 .changelog/2075.txt delete mode 100644 .changelog/2086.txt delete mode 100644 .changelog/2093.txt delete mode 100644 .changelog/2097.txt delete mode 100644 .changelog/2100.txt rename .changelog/{2102.txt => 2108.txt} (76%) delete mode 100644 .changelog/2124.txt delete mode 100644 .changelog/2134.txt create mode 100644 .changelog/2140.txt delete mode 100644 .changelog/2143.txt delete mode 100644 .changelog/2152.txt create mode 100644 .changelog/2159.txt delete mode 100644 .changelog/2165.txt delete mode 100644 .changelog/2166.txt delete mode 100644 .changelog/2170.txt delete mode 100644 .changelog/2183.txt delete mode 100644 .changelog/2184.txt delete mode 100644 .changelog/2195.txt delete mode 100644 .changelog/2205.txt delete mode 100644 .changelog/2209.txt delete mode 100644 .changelog/2213.txt create mode 100644 .changelog/2225.txt delete mode 100644 .changelog/2304.txt delete mode 100644 .changelog/2346.txt delete mode 100644 .changelog/2413.txt delete mode 100644 .changelog/2420.txt delete mode 100644 .changelog/2476.txt delete mode 100644 .changelog/2478.txt delete mode 100644 .changelog/2520.txt delete mode 100644 .changelog/2524.txt delete mode 100644 .changelog/2597.txt rename .changelog/{2642.txt => 2650.txt} (70%) create mode 100644 .changelog/2678.txt delete mode 100644 .changelog/2707.txt delete mode 100644 .changelog/2711.txt rename .changelog/{2710.txt => 2717.txt} (81%) delete mode 100644 .changelog/2723.txt delete mode 100644 .changelog/2735.txt delete mode 100644 .changelog/2743.txt delete mode 100644 .changelog/2748.txt delete mode 100644 .changelog/2784.txt delete mode 100644 .changelog/2796.txt delete mode 100644 .changelog/2844.txt delete mode 100644 .changelog/2869.txt delete mode 100644 .changelog/2880.txt delete mode 100644 .changelog/2881.txt delete mode 100644 .changelog/2904.txt rename .changelog/{2936.txt => 2938.txt} (90%) delete mode 100644 .changelog/2941.txt delete mode 100644 .changelog/2952.txt delete mode 100644 .changelog/2958.txt delete mode 100644 .changelog/2962.txt delete mode 100644 .changelog/3000.txt delete mode 100644 .changelog/3001.txt delete mode 100644 .changelog/3070.txt rename .changelog/{3116.txt => 3121.txt} (61%) delete mode 100644 .changelog/3128.txt delete mode 100644 .changelog/3137.txt delete mode 100644 .changelog/3138.txt delete mode 100644 .changelog/3158.txt delete mode 100644 .changelog/3162.txt delete mode 100644 .changelog/3172.txt delete mode 100644 .changelog/3180.txt delete mode 100644 .changelog/3184.txt delete mode 100644 .changelog/3209.txt delete mode 100644 .changelog/3215.txt delete mode 100644 .changelog/3219.txt delete mode 100644 .changelog/3221.txt delete mode 100644 .changelog/3222.txt delete mode 100644 .changelog/3237.txt delete mode 100644 .changelog/3284.txt delete mode 100644 .changelog/3307.txt delete mode 100644 .changelog/3308.txt delete mode 100644 .changelog/3312.txt delete mode 100644 .changelog/3315.txt delete mode 100644 .changelog/3322.txt delete mode 100644 .changelog/3337.txt delete mode 100644 .changelog/3347.txt delete mode 100644 .changelog/3362.txt delete mode 100644 .changelog/3418.txt delete mode 100644 .changelog/3437.txt delete mode 100644 .changelog/3440.txt delete mode 100644 .changelog/3442.txt delete mode 100644 .changelog/3450.txt delete mode 100644 .changelog/3478.txt delete mode 100644 .changelog/3498.txt delete mode 100644 .changelog/3502.txt delete mode 100644 .copywrite.hcl delete mode 100644 .github/workflows/nightly-api-gateway-conformance.yml delete mode 100644 .github/workflows/nightly-cleanup.yml rename .github/workflows/{weekly-acceptance-1-4-x.yml => weekly-acceptance-0-49-x.yml} (77%) rename .github/workflows/{weekly-acceptance-1-2-x.yml => weekly-acceptance-1-0-x.yml} (85%) delete mode 100644 .github/workflows/weekly-acceptance-1-3-x.yml delete mode 100644 .github/workflows/weekly-acceptance-1-4-0-rc1.yml delete mode 100644 acceptance/tests/api-gateway/api_gateway_external_servers_test.go delete mode 100644 acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go delete mode 100644 acceptance/tests/api-gateway/api_gateway_kitchen_sink_test.go delete mode 100644 acceptance/tests/api-gateway/api_gateway_lifecycle_test.go delete mode 100644 acceptance/tests/api-gateway/api_gateway_tenancy_test.go delete mode 100644 acceptance/tests/api-gateway/api_gateway_test.go create mode 100644 acceptance/tests/api-gateway/example_test.go delete mode 100644 acceptance/tests/cloud/fakeserver_client.go delete mode 100644 acceptance/tests/cloud/load/main_test.go delete mode 100644 acceptance/tests/cloud/load/remote.go delete mode 100644 acceptance/tests/cloud/load/remote_load_test.go delete mode 100644 acceptance/tests/cloud/main_test.go delete mode 100644 acceptance/tests/cloud/metrics_validation.go delete mode 100644 acceptance/tests/cloud/observability_test.go delete mode 100644 acceptance/tests/cloud/remote_dev_test.go delete mode 100644 acceptance/tests/connect/local_rate_limit_test.go delete mode 100644 acceptance/tests/connect/permissive_mtls_test.go delete mode 100644 acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml delete mode 100644 acceptance/tests/fixtures/bases/api-gateway/certificate.yaml delete mode 100644 acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml delete mode 100644 acceptance/tests/fixtures/bases/api-gateway/gatewayclassconfig.yaml delete mode 100644 acceptance/tests/fixtures/bases/api-gateway/httproute.yaml delete mode 100644 acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/api-gateway/meshservice.yaml delete mode 100644 acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml delete mode 100644 acceptance/tests/fixtures/bases/cloud/hcp-mock/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/cloud/hcp-mock/service.yaml delete mode 100644 acceptance/tests/fixtures/bases/cloud/hcp-mock/serviceaccount.yaml delete mode 100644 acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml delete mode 100644 acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/crds-oss/controlplanerequestlimit.yaml delete mode 100644 acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml delete mode 100644 acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml create mode 100644 acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml delete mode 100644 acceptance/tests/fixtures/bases/job-client/job.yaml delete mode 100644 acceptance/tests/fixtures/bases/job-client/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/job-client/service.yaml delete mode 100644 acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml delete mode 100644 acceptance/tests/fixtures/bases/pingpong/template.tmpl delete mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/intention.yaml delete mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/resolver.yaml delete mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/service.yaml delete mode 100644 acceptance/tests/fixtures/bases/resolver-redirect/serviceaccount.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/sameness.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/sameness.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/sameness.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/sameness.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/exportedservices-ap1.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-02-a.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-03-a.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-02-a.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-03-a.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-a.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-b.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/peering-dialer-cluster-03-a.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-a.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-b.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-02-a.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/mesh/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml delete mode 100644 acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml delete mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/anyuid-scc-rolebinding.yaml delete mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/deployment.yaml delete mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/privileged-scc-rolebinding.yaml delete mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml delete mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/service.yaml delete mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/serviceaccount.yaml delete mode 100644 acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml delete mode 100644 acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml delete mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml delete mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml delete mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml delete mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml delete mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml delete mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml delete mode 100644 acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/certificate/certificate.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/certificate/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/serviceresolver.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/serviceresolver.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/gateway/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/httproute/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/httproute/route.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/api-gateway.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/external-ref.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gateway-policy.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gatewayclassconfig.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/httproute.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-provider.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-route-filter.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/api-gateway.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/external-ref.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gateway-policy.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gatewayclassconfig.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/httproute.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/mesh/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/mesh/proxydefaults.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/peer-resolver/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/peer-resolver/serviceresolver.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/resolver/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/resolver/serviceresolver.yaml delete mode 100644 acceptance/tests/fixtures/cases/api-gateways/tcproute/route.yaml delete mode 100644 acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml delete mode 100644 acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml delete mode 100644 acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml delete mode 100644 acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml delete mode 100644 acceptance/tests/fixtures/cases/resolver-redirect-virtualip/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-client/default-partition/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc2/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/sameness/static-server/dc3/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml delete mode 100644 acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml delete mode 100644 acceptance/tests/mesh_v2/main_test.go delete mode 100644 acceptance/tests/mesh_v2/mesh_inject_test.go delete mode 100644 acceptance/tests/partitions/partitions_gateway_test.go delete mode 100644 acceptance/tests/peering/peering_gateway_test.go delete mode 100644 acceptance/tests/sameness/main_test.go delete mode 100644 acceptance/tests/sameness/sameness_test.go delete mode 100644 acceptance/tests/segments/main_test.go delete mode 100644 acceptance/tests/segments/segments_test.go delete mode 100644 acceptance/tests/server/main_test.go delete mode 100644 acceptance/tests/server/server_test.go delete mode 100644 acceptance/tests/tenancy_v2/main_test.go delete mode 100644 acceptance/tests/tenancy_v2/partition_test.go delete mode 100644 acceptance/tests/wan-federation/wan_federation_gateway_test.go delete mode 100644 charts/consul/templates/client-tmp-extra-config-configmap.yaml delete mode 100644 charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml delete mode 100644 charts/consul/templates/crd-apigateways.yaml delete mode 100644 charts/consul/templates/crd-controlplanerequestlimits.yaml delete mode 100644 charts/consul/templates/crd-exportedservices-v1.yaml delete mode 100644 charts/consul/templates/crd-gatewayclassconfigs-v1.yaml delete mode 100644 charts/consul/templates/crd-gatewayclassconfigs.yaml delete mode 100644 charts/consul/templates/crd-gatewayclasses-external.yaml delete mode 100644 charts/consul/templates/crd-gatewayclasses.yaml delete mode 100644 charts/consul/templates/crd-gatewaypolicies.yaml delete mode 100644 charts/consul/templates/crd-gateways-external.yaml delete mode 100644 charts/consul/templates/crd-grpcroutes-external.yaml delete mode 100644 charts/consul/templates/crd-grpcroutes.yaml delete mode 100644 charts/consul/templates/crd-httproutes-external.yaml delete mode 100644 charts/consul/templates/crd-httproutes.yaml delete mode 100644 charts/consul/templates/crd-jwtproviders.yaml delete mode 100644 charts/consul/templates/crd-meshconfigurations.yaml delete mode 100644 charts/consul/templates/crd-meshgateways.yaml delete mode 100644 charts/consul/templates/crd-meshservices.yaml delete mode 100644 charts/consul/templates/crd-proxyconfigurations.yaml delete mode 100644 charts/consul/templates/crd-referencegrants-external.yaml delete mode 100644 charts/consul/templates/crd-routeauthfilters.yaml delete mode 100644 charts/consul/templates/crd-routeretryfilters.yaml delete mode 100644 charts/consul/templates/crd-routetimeoutfilters.yaml delete mode 100644 charts/consul/templates/crd-samenessgroups.yaml delete mode 100644 charts/consul/templates/crd-tcproutes-external.yaml delete mode 100644 charts/consul/templates/crd-tcproutes.yaml delete mode 100644 charts/consul/templates/crd-tlsroutes-external.yaml delete mode 100644 charts/consul/templates/crd-trafficpermissions.yaml delete mode 100644 charts/consul/templates/crd-udproutes-external.yaml delete mode 100644 charts/consul/templates/gateway-cleanup-clusterrole.yaml delete mode 100644 charts/consul/templates/gateway-cleanup-clusterrolebinding.yaml delete mode 100644 charts/consul/templates/gateway-cleanup-job.yaml delete mode 100644 charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml delete mode 100644 charts/consul/templates/gateway-cleanup-serviceaccount.yaml delete mode 100644 charts/consul/templates/gateway-resources-clusterrole.yaml delete mode 100644 charts/consul/templates/gateway-resources-clusterrolebinding.yaml delete mode 100644 charts/consul/templates/gateway-resources-configmap.yaml delete mode 100644 charts/consul/templates/gateway-resources-job.yaml delete mode 100644 charts/consul/templates/gateway-resources-podsecuritypolicy.yaml delete mode 100644 charts/consul/templates/gateway-resources-serviceaccount.yaml delete mode 100644 charts/consul/templates/server-clusterrole.yaml delete mode 100644 charts/consul/templates/server-clusterrolebinding.yaml delete mode 100644 charts/consul/templates/server-tmp-extra-config-configmap.yaml delete mode 100644 charts/consul/templates/telemetry-collector-configmap.yaml delete mode 100644 charts/consul/templates/telemetry-collector-deployment.yaml delete mode 100644 charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml delete mode 100644 charts/consul/templates/telemetry-collector-role.yaml delete mode 100644 charts/consul/templates/telemetry-collector-rolebinding.yaml delete mode 100644 charts/consul/templates/telemetry-collector-service.yaml delete mode 100644 charts/consul/templates/telemetry-collector-serviceaccount.yaml delete mode 100644 charts/consul/templates/telemetry-collector-v2-deployment.yaml delete mode 100755 charts/consul/test/unit/client-tmp-extra-config-configmap.bats delete mode 100644 charts/consul/test/unit/crd-controlplanerequestlimits.bats delete mode 100644 charts/consul/test/unit/crd-gatewayclassconfigs.bats delete mode 100644 charts/consul/test/unit/crd-gatewayclasses-external.bats delete mode 100644 charts/consul/test/unit/crd-gatewaypolicies.bats delete mode 100644 charts/consul/test/unit/crd-gateways-external.bats delete mode 100644 charts/consul/test/unit/crd-grpcroutes-external.bats delete mode 100644 charts/consul/test/unit/crd-httproutes-external.bats delete mode 100644 charts/consul/test/unit/crd-meshservices.bats delete mode 100644 charts/consul/test/unit/crd-routeauthfilters.bats delete mode 100644 charts/consul/test/unit/crd-tcproutes-external.bats delete mode 100644 charts/consul/test/unit/crd-tlsroutes-external.bats delete mode 100644 charts/consul/test/unit/crd-udproutes-external.bats delete mode 100644 charts/consul/test/unit/gateway-cleanup-clusterrole.bats delete mode 100644 charts/consul/test/unit/gateway-cleanup-clusterrolebinding.bats delete mode 100644 charts/consul/test/unit/gateway-cleanup-job.bats delete mode 100644 charts/consul/test/unit/gateway-cleanup-podsecuritypolicy.bats delete mode 100644 charts/consul/test/unit/gateway-cleanup-serviceaccount.bats delete mode 100644 charts/consul/test/unit/gateway-resources-clusterrole.bats delete mode 100644 charts/consul/test/unit/gateway-resources-clusterrolebinding.bats delete mode 100644 charts/consul/test/unit/gateway-resources-configmap.bats delete mode 100644 charts/consul/test/unit/gateway-resources-job.bats delete mode 100644 charts/consul/test/unit/gateway-resources-podsecuritypolicy.bats delete mode 100644 charts/consul/test/unit/gateway-resources-serviceaccount.bats delete mode 100755 charts/consul/test/unit/server-tmp-extra-config-configmap.bats delete mode 100644 charts/consul/test/unit/telemetry-collector-configmap.bats delete mode 100755 charts/consul/test/unit/telemetry-collector-deployment.bats delete mode 100644 charts/consul/test/unit/telemetry-collector-podsecuritypolicy.bats delete mode 100644 charts/consul/test/unit/telemetry-collector-role.bats delete mode 100644 charts/consul/test/unit/telemetry-collector-rolebinding.bats delete mode 100755 charts/consul/test/unit/telemetry-collector-service.bats delete mode 100644 charts/consul/test/unit/telemetry-collector-serviceaccount.bats delete mode 100755 charts/consul/test/unit/telemetry-collector-v2-deployment.bats delete mode 100644 cli/cmd/proxy/loglevel/command.go delete mode 100644 cli/cmd/proxy/loglevel/command_test.go rename cli/{common/envoy/http.go => cmd/proxy/read/config.go} (90%) rename cli/{common/envoy/http_test.go => cmd/proxy/read/config_test.go} (92%) rename cli/{common/envoy/types.go => cmd/proxy/read/envoy_types.go} (99%) rename cli/{common/envoy/testdata => cmd/proxy/read}/test_clusters.json (100%) rename cli/{common/envoy/testdata => cmd/proxy/read}/test_config_dump.json (100%) delete mode 100644 cli/cmd/proxy/stats/command.go delete mode 100644 cli/cmd/proxy/stats/command_test.go delete mode 100644 cli/cmd/troubleshoot/command.go delete mode 100644 cli/cmd/troubleshoot/proxy/proxy.go delete mode 100644 cli/cmd/troubleshoot/proxy/proxy_test.go delete mode 100644 cli/cmd/troubleshoot/upstreams/upstreams.go delete mode 100644 cli/cmd/troubleshoot/upstreams/upstreams_test.go delete mode 100644 cli/common/envoy/logger_params.go delete mode 100644 cli/common/envoy/logger_params_test.go delete mode 100644 cli/common/envoy/testdata/fetch_debug_levels.txt delete mode 100644 cli/version/fips_build.go delete mode 100644 cli/version/non_fips_build.go delete mode 100644 control-plane/Dockerfile.dev delete mode 100644 control-plane/api-gateway/binding/annotations.go delete mode 100644 control-plane/api-gateway/binding/annotations_test.go delete mode 100644 control-plane/api-gateway/binding/binder.go delete mode 100644 control-plane/api-gateway/binding/binder_test.go delete mode 100644 control-plane/api-gateway/binding/reference_grant.go delete mode 100644 control-plane/api-gateway/binding/reference_grant_test.go delete mode 100644 control-plane/api-gateway/binding/registration.go delete mode 100644 control-plane/api-gateway/binding/registration_test.go delete mode 100644 control-plane/api-gateway/binding/result.go delete mode 100644 control-plane/api-gateway/binding/result_test.go delete mode 100644 control-plane/api-gateway/binding/route_binding.go delete mode 100644 control-plane/api-gateway/binding/setter.go delete mode 100644 control-plane/api-gateway/binding/setter_test.go delete mode 100644 control-plane/api-gateway/binding/snapshot.go delete mode 100644 control-plane/api-gateway/binding/validation.go delete mode 100644 control-plane/api-gateway/binding/validation_test.go delete mode 100644 control-plane/api-gateway/cache/consul.go delete mode 100644 control-plane/api-gateway/cache/consul_test.go delete mode 100644 control-plane/api-gateway/cache/gateway.go delete mode 100644 control-plane/api-gateway/cache/kubernetes.go delete mode 100644 control-plane/api-gateway/cache/subscription.go delete mode 100644 control-plane/api-gateway/common/constants.go delete mode 100644 control-plane/api-gateway/common/diff.go delete mode 100644 control-plane/api-gateway/common/diff_test.go delete mode 100644 control-plane/api-gateway/common/finalizers.go delete mode 100644 control-plane/api-gateway/common/helm_config.go delete mode 100644 control-plane/api-gateway/common/helpers.go delete mode 100644 control-plane/api-gateway/common/helpers_test.go delete mode 100644 control-plane/api-gateway/common/labels.go delete mode 100644 control-plane/api-gateway/common/reference.go delete mode 100644 control-plane/api-gateway/common/resources.go delete mode 100644 control-plane/api-gateway/common/resources_test.go delete mode 100644 control-plane/api-gateway/common/secrets.go delete mode 100644 control-plane/api-gateway/common/secrets_test.go delete mode 100644 control-plane/api-gateway/common/translation.go delete mode 100644 control-plane/api-gateway/common/translation_test.go delete mode 100644 control-plane/api-gateway/controllers/finalizer.go delete mode 100644 control-plane/api-gateway/controllers/finalizer_test.go delete mode 100644 control-plane/api-gateway/controllers/gateway_controller.go delete mode 100644 control-plane/api-gateway/controllers/gateway_controller_integration_test.go delete mode 100644 control-plane/api-gateway/controllers/gateway_controller_test.go delete mode 100644 control-plane/api-gateway/controllers/gatewayclass_controller.go delete mode 100644 control-plane/api-gateway/controllers/gatewayclass_controller_test.go delete mode 100644 control-plane/api-gateway/controllers/gatewayclassconfig_controller.go delete mode 100644 control-plane/api-gateway/controllers/gatewayclassconfig_controller_test.go delete mode 100644 control-plane/api-gateway/controllers/index.go delete mode 100644 control-plane/api-gateway/controllers/index_test.go delete mode 100644 control-plane/api-gateway/gatekeeper/dataplane.go delete mode 100644 control-plane/api-gateway/gatekeeper/deployment.go delete mode 100644 control-plane/api-gateway/gatekeeper/gatekeeper.go delete mode 100644 control-plane/api-gateway/gatekeeper/gatekeeper_test.go delete mode 100644 control-plane/api-gateway/gatekeeper/init.go delete mode 100644 control-plane/api-gateway/gatekeeper/role.go delete mode 100644 control-plane/api-gateway/gatekeeper/rolebinding.go delete mode 100644 control-plane/api-gateway/gatekeeper/service.go delete mode 100644 control-plane/api-gateway/gatekeeper/serviceaccount.go delete mode 100644 control-plane/api/auth/v2beta1/auth_groupversion_info.go delete mode 100644 control-plane/api/auth/v2beta1/shared_types.go delete mode 100644 control-plane/api/auth/v2beta1/status.go delete mode 100644 control-plane/api/auth/v2beta1/traffic_permissions_types.go delete mode 100644 control-plane/api/auth/v2beta1/traffic_permissions_types_test.go delete mode 100644 control-plane/api/auth/v2beta1/trafficpermissions_webhook.go delete mode 100644 control-plane/api/auth/v2beta1/zz_generated.deepcopy.go delete mode 100644 control-plane/api/common/consul_resource.go delete mode 100644 control-plane/api/common/consul_resource_webhook.go delete mode 100644 control-plane/api/common/consul_resource_webhook_test.go delete mode 100644 control-plane/api/mesh/v2beta1/api_gateway_types.go delete mode 100644 control-plane/api/mesh/v2beta1/gateway_class_config_types.go delete mode 100644 control-plane/api/mesh/v2beta1/gateway_class_types.go delete mode 100644 control-plane/api/mesh/v2beta1/grpc_route_types.go delete mode 100644 control-plane/api/mesh/v2beta1/grpc_route_types_test.go delete mode 100644 control-plane/api/mesh/v2beta1/grpc_route_webhook.go delete mode 100644 control-plane/api/mesh/v2beta1/http_route_types.go delete mode 100644 control-plane/api/mesh/v2beta1/http_route_types_test.go delete mode 100644 control-plane/api/mesh/v2beta1/http_route_webhook.go delete mode 100644 control-plane/api/mesh/v2beta1/mesh_configuration_types.go delete mode 100644 control-plane/api/mesh/v2beta1/mesh_gateway_types.go delete mode 100644 control-plane/api/mesh/v2beta1/mesh_groupversion_info.go delete mode 100644 control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go delete mode 100644 control-plane/api/mesh/v2beta1/proxy_configuration_types.go delete mode 100644 control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go delete mode 100644 control-plane/api/mesh/v2beta1/shared_types.go delete mode 100644 control-plane/api/mesh/v2beta1/status.go delete mode 100644 control-plane/api/mesh/v2beta1/tcp_route_types.go delete mode 100644 control-plane/api/mesh/v2beta1/tcp_route_types_test.go delete mode 100644 control-plane/api/mesh/v2beta1/tcp_route_webhook.go delete mode 100644 control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go delete mode 100644 control-plane/api/multicluster/v2beta1/exported_services_types.go delete mode 100644 control-plane/api/multicluster/v2beta1/multicluster_groupversion_info.go delete mode 100644 control-plane/api/multicluster/v2beta1/shared_types.go delete mode 100644 control-plane/api/multicluster/v2beta1/status.go delete mode 100644 control-plane/api/multicluster/v2beta1/zz_generated.deepcopy.go delete mode 100644 control-plane/api/v1alpha1/api_gateway_types.go delete mode 100644 control-plane/api/v1alpha1/api_gateway_types_test.go delete mode 100644 control-plane/api/v1alpha1/controlplanerequestlimit_types.go delete mode 100644 control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go delete mode 100644 control-plane/api/v1alpha1/controlplanerequestlimit_webhook.go delete mode 100644 control-plane/api/v1alpha1/controlplanerequestlimit_webhook_test.go delete mode 100644 control-plane/api/v1alpha1/gatewaypolicy_types.go delete mode 100644 control-plane/api/v1alpha1/gatewaypolicy_webhook.go delete mode 100644 control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go delete mode 100644 control-plane/api/v1alpha1/jwtprovider_types.go delete mode 100644 control-plane/api/v1alpha1/jwtprovider_types_test.go delete mode 100644 control-plane/api/v1alpha1/jwtprovider_webhook.go delete mode 100644 control-plane/api/v1alpha1/routeauthfilter_types.go delete mode 100644 control-plane/api/v1alpha1/routeretryfilter_types.go delete mode 100644 control-plane/api/v1alpha1/routetimeoutfilter_types.go delete mode 100644 control-plane/api/v1alpha1/samenessgroup_types.go delete mode 100644 control-plane/api/v1alpha1/samenessgroup_types_test.go delete mode 100644 control-plane/api/v1alpha1/samenessgroup_webhook.go create mode 100644 control-plane/build-support/controller/README.md create mode 100644 control-plane/build-support/controller/boilerplate.go.txt delete mode 100644 control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml delete mode 100644 control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshconfigurations.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml delete mode 100644 control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml delete mode 100644 control-plane/config/crd/bases/multicluster.consul.hashicorp.com_exportedservices.yaml delete mode 100644 control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml delete mode 100644 control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml delete mode 100644 control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml delete mode 100644 control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml delete mode 100644 control-plane/config/crd/external/kustomization.yaml delete mode 100644 control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml delete mode 100644 control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml delete mode 100644 control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml delete mode 100644 control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml delete mode 100644 control-plane/config/crd/kustomizeconfig.yaml delete mode 100644 control-plane/connect-inject/common/annotation_processor.go delete mode 100644 control-plane/connect-inject/common/annotation_processor_test.go delete mode 100644 control-plane/connect-inject/constants/constants_test.go delete mode 100644 control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go delete mode 100644 control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go delete mode 100644 control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go delete mode 100644 control-plane/connect-inject/controllers/endpointsv2/write_cache.go delete mode 100644 control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go delete mode 100644 control-plane/connect-inject/controllers/pod/pod_controller.go delete mode 100644 control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go delete mode 100644 control-plane/connect-inject/controllers/pod/pod_controller_test.go delete mode 100644 control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go delete mode 100644 control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go delete mode 100644 control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go delete mode 100644 control-plane/connect-inject/namespace/namespace_controller.go delete mode 100644 control-plane/connect-inject/namespace/namespace_controller_ent_test.go delete mode 100644 control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go delete mode 100644 control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go delete mode 100644 control-plane/connect-inject/webhookv2/container_env.go delete mode 100644 control-plane/connect-inject/webhookv2/container_env_test.go delete mode 100644 control-plane/connect-inject/webhookv2/container_init.go delete mode 100644 control-plane/connect-inject/webhookv2/container_init_test.go delete mode 100644 control-plane/connect-inject/webhookv2/container_volume.go delete mode 100644 control-plane/connect-inject/webhookv2/dns.go delete mode 100644 control-plane/connect-inject/webhookv2/dns_test.go delete mode 100644 control-plane/connect-inject/webhookv2/health_checks_test.go delete mode 100644 control-plane/connect-inject/webhookv2/heath_checks.go delete mode 100644 control-plane/connect-inject/webhookv2/mesh_webhook.go delete mode 100644 control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go delete mode 100644 control-plane/connect-inject/webhookv2/mesh_webhook_test.go delete mode 100644 control-plane/connect-inject/webhookv2/redirect_traffic.go delete mode 100644 control-plane/connect-inject/webhookv2/redirect_traffic_test.go delete mode 100644 control-plane/consul/dataplane_client.go delete mode 100644 control-plane/consul/dataplane_client_test.go delete mode 100644 control-plane/consul/dynamic.go delete mode 100644 control-plane/consul/dynamic_test.go delete mode 100644 control-plane/consul/resource_client.go delete mode 100644 control-plane/consul/resource_client_test.go rename control-plane/{controllers/configentries => controller}/configentry_controller.go (78%) create mode 100644 control-plane/controller/configentry_controller_ent_test.go rename control-plane/{controllers/configentries => controller}/configentry_controller_test.go (83%) rename control-plane/{controllers/configentries => controller}/exportedservices_controller.go (89%) rename control-plane/{controllers/configentries => controller}/exportedservices_controller_ent_test.go (93%) rename control-plane/{controllers/configentries => controller}/ingressgateway_controller.go (92%) rename control-plane/{controllers/configentries => controller}/mesh_controller.go (89%) rename control-plane/{controllers/configentries => controller}/proxydefaults_controller.go (89%) rename control-plane/{controllers/configentries => controller}/servicedefaults_controller.go (89%) rename control-plane/{controllers/configentries => controller}/serviceintentions_controller.go (89%) rename control-plane/{controllers/configentries => controller}/serviceresolver_controller.go (89%) rename control-plane/{controllers/configentries => controller}/servicerouter_controller.go (89%) rename control-plane/{controllers/configentries => controller}/servicesplitter_controller.go (89%) rename control-plane/{controllers/configentries => controller}/terminatinggateway_controller.go (89%) delete mode 100644 control-plane/controllers/configentries/configentry_controller_ent_test.go delete mode 100644 control-plane/controllers/configentries/controlplanerequestlimit_controller.go delete mode 100644 control-plane/controllers/configentries/finalizer_patch.go delete mode 100644 control-plane/controllers/configentries/finalizer_patch_test.go delete mode 100644 control-plane/controllers/configentries/jwtprovider_controller.go delete mode 100644 control-plane/controllers/configentries/samenessgroups_controller.go delete mode 100644 control-plane/controllers/resources/api-gateway-controller.go delete mode 100644 control-plane/controllers/resources/consul_resource_controller.go delete mode 100644 control-plane/controllers/resources/consul_resource_controller_ent_test.go delete mode 100644 control-plane/controllers/resources/consul_resource_controller_test.go delete mode 100644 control-plane/controllers/resources/exported_services_controller.go delete mode 100644 control-plane/controllers/resources/gateway_class_config_controller.go delete mode 100644 control-plane/controllers/resources/gateway_class_controller.go delete mode 100644 control-plane/controllers/resources/grpc_route_controller.go delete mode 100644 control-plane/controllers/resources/http_route_controller.go delete mode 100644 control-plane/controllers/resources/mesh_configuration_controller.go delete mode 100644 control-plane/controllers/resources/mesh_gateway_controller.go delete mode 100644 control-plane/controllers/resources/mesh_gateway_controller_test.go delete mode 100644 control-plane/controllers/resources/proxy_configuration_controller.go delete mode 100644 control-plane/controllers/resources/tcp_route_controller.go delete mode 100644 control-plane/controllers/resources/traffic_permissions_controller.go delete mode 100644 control-plane/gateways/builder.go delete mode 100644 control-plane/gateways/constants.go delete mode 100644 control-plane/gateways/deployment.go delete mode 100644 control-plane/gateways/deployment_dataplane_container.go delete mode 100644 control-plane/gateways/deployment_init_container.go delete mode 100644 control-plane/gateways/deployment_test.go delete mode 100644 control-plane/gateways/gateway_config.go delete mode 100644 control-plane/gateways/metadata.go delete mode 100644 control-plane/gateways/metadata_test.go delete mode 100644 control-plane/gateways/role.go delete mode 100644 control-plane/gateways/service.go delete mode 100644 control-plane/gateways/service_test.go delete mode 100644 control-plane/gateways/serviceaccount.go delete mode 100644 control-plane/gateways/serviceaccount_test.go create mode 100644 control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go create mode 100644 control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go delete mode 100644 control-plane/helper/webhook-configuration/webhook_configuration.go delete mode 100644 control-plane/helper/webhook-configuration/webhook_configuration_test.go delete mode 100644 control-plane/subcommand/fetch-server-region/command.go delete mode 100644 control-plane/subcommand/fetch-server-region/command_test.go delete mode 100644 control-plane/subcommand/gateway-cleanup/command.go delete mode 100644 control-plane/subcommand/gateway-cleanup/command_test.go delete mode 100644 control-plane/subcommand/gateway-resources/command.go delete mode 100644 control-plane/subcommand/gateway-resources/command_test.go delete mode 100644 control-plane/subcommand/inject-connect/v1controllers.go delete mode 100644 control-plane/subcommand/inject-connect/v2controllers.go delete mode 100644 control-plane/subcommand/mesh-init/command.go delete mode 100644 control-plane/subcommand/mesh-init/command_ent_test.go delete mode 100644 control-plane/subcommand/mesh-init/command_test.go delete mode 100644 control-plane/subcommand/server-acl-init/k8s_secrets_backend.go delete mode 100644 control-plane/subcommand/server-acl-init/secrets_backend.go delete mode 100644 control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go delete mode 100644 control-plane/subcommand/server-acl-init/vault_secrets_backend.go delete mode 100644 control-plane/tenancy/namespace/namespace.go delete mode 100644 control-plane/tenancy/namespace/namespace_controller.go delete mode 100644 control-plane/tenancy/namespace/namespace_controller_ent_test.go delete mode 100644 control-plane/tenancy/namespace/namespace_controller_test.go delete mode 100644 control-plane/version/fips_build.go delete mode 100644 control-plane/version/non_fips_build.go create mode 100644 docs/admin-partitions-with-acls.md delete mode 100644 hack/camel-crds/go.mod delete mode 100644 hack/camel-crds/go.sum delete mode 100644 hack/camel-crds/main.go diff --git a/.changelog/1914.txt b/.changelog/1914.txt deleted file mode 100644 index 3179f3b0b3..0000000000 --- a/.changelog/1914.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -control-plane: fix issue where consul-connect-injector acl token was unintentionally being deleted and not recreated when a container was restarted due to a livenessProbe failure. -``` \ No newline at end of file diff --git a/.changelog/1920.txt b/.changelog/1920.txt deleted file mode 100644 index 4b1f151fe4..0000000000 --- a/.changelog/1920.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: When the `global.acls.bootstrapToken` field is set and the content of the secret is empty, the bootstrap ACL token is written to that secret after bootstrapping ACLs. This applies to both the Vault and Consul secrets backends. -``` diff --git a/.changelog/1976.txt b/.changelog/1976.txt new file mode 100644 index 0000000000..65024aa6f9 --- /dev/null +++ b/.changelog/1976.txt @@ -0,0 +1,3 @@ +```release-note:security +upgrade to use Go 1.19.6. This resolves vulnerabilities CVE-2022-41724 in crypto/tls and CVE-2022-41723 in net/http. +``` \ No newline at end of file diff --git a/.changelog/2029.txt b/.changelog/2029.txt deleted file mode 100644 index c864419eba..0000000000 --- a/.changelog/2029.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -api-gateway: fix ACL issue where when adminPartitions and ACLs are enabled, API Gateway Controller is unable to create a new namespace in Consul -``` \ No newline at end of file diff --git a/.changelog/2030.txt b/.changelog/2030.txt deleted file mode 100644 index 46516d9513..0000000000 --- a/.changelog/2030.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: add failover policy field to service resolver and proxy default CRDs -``` \ No newline at end of file diff --git a/.changelog/2048.txt b/.changelog/2048.txt deleted file mode 100644 index 5796ce2397..0000000000 --- a/.changelog/2048.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: add samenessGroup CRD -``` \ No newline at end of file diff --git a/.changelog/2075.txt b/.changelog/2075.txt deleted file mode 100644 index 2f0f0344eb..0000000000 --- a/.changelog/2075.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: add samenessGroup field to exported services CRD -``` \ No newline at end of file diff --git a/.changelog/2086.txt b/.changelog/2086.txt deleted file mode 100644 index d4e43a630d..0000000000 --- a/.changelog/2086.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: add samenessGroup field to service resolver CRD -``` \ No newline at end of file diff --git a/.changelog/2093.txt b/.changelog/2093.txt deleted file mode 100644 index 20c657e566..0000000000 --- a/.changelog/2093.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: set agent localities on Consul servers to the server node's `topology.kubernetes.io/region` label. -``` diff --git a/.changelog/2097.txt b/.changelog/2097.txt deleted file mode 100644 index 60e99a8515..0000000000 --- a/.changelog/2097.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: add samenessGroup field to source intention CRD -``` \ No newline at end of file diff --git a/.changelog/2100.txt b/.changelog/2100.txt deleted file mode 100644 index 4fece0991c..0000000000 --- a/.changelog/2100.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -crd: Add `mutualTLSMode` to the ProxyDefaults and ServiceDefaults CRDs and `allowEnablingPermissiveMutualTLS` to the Mesh CRD to support configuring permissive mutual TLS. -``` diff --git a/.changelog/2102.txt b/.changelog/2108.txt similarity index 76% rename from .changelog/2102.txt rename to .changelog/2108.txt index 7adf361d2d..1c10ef62d9 100644 --- a/.changelog/2102.txt +++ b/.changelog/2108.txt @@ -1,5 +1,5 @@ ```release-note:security -Upgrade to use Go 1.20.4. +Upgrade to use Go 1.19.9. This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), [CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), [CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and @@ -9,13 +9,4 @@ Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41 ), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 ](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h .) -``` - -```release-note:improvement -cli: update minimum go version for project to 1.20. -``` - -```release-note:improvement -control-plane: update minimum go version for project to 1.20. -``` - +``` \ No newline at end of file diff --git a/.changelog/2124.txt b/.changelog/2124.txt deleted file mode 100644 index b65c23db2e..0000000000 --- a/.changelog/2124.txt +++ /dev/null @@ -1,3 +0,0 @@ -``release-note:improvement -control-plane: Transparent proxy enhancements for failover and virtual Services -``` diff --git a/.changelog/2134.txt b/.changelog/2134.txt deleted file mode 100644 index 980e7ba666..0000000000 --- a/.changelog/2134.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Add support for consul-telemetry-collector to forward envoy metrics to an otelhttp compatible receiver or HCP -``` \ No newline at end of file diff --git a/.changelog/2140.txt b/.changelog/2140.txt new file mode 100644 index 0000000000..d6c5efeffb --- /dev/null +++ b/.changelog/2140.txt @@ -0,0 +1,4 @@ +```release-note:improvement +helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.0.2`, `image` value to `hashicorp/consul:1.14.7`, +and `imageEnvoy` to `envoyproxy/envoy:v1.24.7`. +``` diff --git a/.changelog/2143.txt b/.changelog/2143.txt deleted file mode 100644 index 8f58328f3d..0000000000 --- a/.changelog/2143.txt +++ /dev/null @@ -1,4 +0,0 @@ - -```release-note:feature -consul-telemetry-collector: Configure envoy proxy config during registration when consul-telemetry-collector is enabled. -``` diff --git a/.changelog/2152.txt b/.changelog/2152.txt deleted file mode 100644 index 2f0743a9d8..0000000000 --- a/.changelog/2152.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: Add API Gateway for Consul on Kubernetes leveraging Consul native API Gateway configuration. -``` diff --git a/.changelog/2159.txt b/.changelog/2159.txt new file mode 100644 index 0000000000..9b970bf3f4 --- /dev/null +++ b/.changelog/2159.txt @@ -0,0 +1,3 @@ +```release-note:bug +control-plane: fix issue with json tags of service defaults fields EnforcingConsecutive5xx, MaxEjectionPercent and BaseEjectionTime. +``` \ No newline at end of file diff --git a/.changelog/2165.txt b/.changelog/2165.txt deleted file mode 100644 index 15c4bdb1e0..0000000000 --- a/.changelog/2165.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: add FIPS support -``` \ No newline at end of file diff --git a/.changelog/2166.txt b/.changelog/2166.txt deleted file mode 100644 index b2392bd7d5..0000000000 --- a/.changelog/2166.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Add support for configuring Consul server-side rate limiting -``` diff --git a/.changelog/2170.txt b/.changelog/2170.txt deleted file mode 100644 index 6d10ae1097..0000000000 --- a/.changelog/2170.txt +++ /dev/null @@ -1,2 +0,0 @@ -```release-note:feature -Add support for configuring global level server rate limiting. diff --git a/.changelog/2183.txt b/.changelog/2183.txt deleted file mode 100644 index d54983a8f4..0000000000 --- a/.changelog/2183.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:security -Fix Prometheus CVEs by bumping controller-runtime. -``` diff --git a/.changelog/2184.txt b/.changelog/2184.txt deleted file mode 100644 index bdcb6039fd..0000000000 --- a/.changelog/2184.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: support deploying to OpenShift 4.11 -``` diff --git a/.changelog/2195.txt b/.changelog/2195.txt deleted file mode 100644 index e7475f88e0..0000000000 --- a/.changelog/2195.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -consul-telemetry-collector: add acceptance tests for consul telemetry collector component. -``` diff --git a/.changelog/2205.txt b/.changelog/2205.txt deleted file mode 100644 index 6a66970cfc..0000000000 --- a/.changelog/2205.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -cli: update cloud preset to enable telemetry collector -``` \ No newline at end of file diff --git a/.changelog/2209.txt b/.changelog/2209.txt deleted file mode 100644 index 72a59064e4..0000000000 --- a/.changelog/2209.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -helm: Add `JWTProvider` CRD for configuring the `jwt-provider` config entry. -``` diff --git a/.changelog/2213.txt b/.changelog/2213.txt deleted file mode 100644 index c09c2e0397..0000000000 --- a/.changelog/2213.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -helm: Update the ServiceIntentions CRD to support `JWT` fields. -``` diff --git a/.changelog/2225.txt b/.changelog/2225.txt new file mode 100644 index 0000000000..fcbb89a54b --- /dev/null +++ b/.changelog/2225.txt @@ -0,0 +1,3 @@ +```release-note:security +Bump `controller-runtime` to address CVEs in dependencies. +``` diff --git a/.changelog/2265.txt b/.changelog/2265.txt index 1cf6813c94..35643ce272 100644 --- a/.changelog/2265.txt +++ b/.changelog/2265.txt @@ -1,3 +1,3 @@ ```release-note:improvement (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration -``` +``` \ No newline at end of file diff --git a/.changelog/2302.txt b/.changelog/2302.txt index 7bf7e6b0f6..9d36d912d3 100644 --- a/.changelog/2302.txt +++ b/.changelog/2302.txt @@ -9,5 +9,4 @@ Add support to provide the logLevel flag via helm for multiple low level compone 7. `meshGateway.logLevel` 8. `ingressGateways.logLevel` 9. `terminatingGateways.logLevel` -10. `telemetryCollector.logLevel` ``` diff --git a/.changelog/2304.txt b/.changelog/2304.txt deleted file mode 100644 index c977da5acd..0000000000 --- a/.changelog/2304.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: Kubernetes v1.27 is now supported. Minimum tested version of Kubernetes is now v1.24. -``` diff --git a/.changelog/2346.txt b/.changelog/2346.txt deleted file mode 100644 index fb062ee0fb..0000000000 --- a/.changelog/2346.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Set locality on services registered with connect-inject. -``` diff --git a/.changelog/2413.txt b/.changelog/2413.txt deleted file mode 100644 index 89755b23a7..0000000000 --- a/.changelog/2413.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -api-gateway: Fix creation of invalid Kubernetes Service when multiple Gateway listeners have the same port. -``` diff --git a/.changelog/2420.txt b/.changelog/2420.txt deleted file mode 100644 index 86776497c4..0000000000 --- a/.changelog/2420.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -api-gateway: set route condition appropriately when parent ref includes non-existent section name -``` diff --git a/.changelog/2476.txt b/.changelog/2476.txt deleted file mode 100644 index e57889cabe..0000000000 --- a/.changelog/2476.txt +++ /dev/null @@ -1,7 +0,0 @@ -```release-note:improvement -helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.2.0` -``` - -```release-note:improvement -helm: update `image` value to `hashicorp/consul:1.16.0` -``` \ No newline at end of file diff --git a/.changelog/2478.txt b/.changelog/2478.txt deleted file mode 100644 index ccbbb71ec8..0000000000 --- a/.changelog/2478.txt +++ /dev/null @@ -1,5 +0,0 @@ -```release-note:bug -api-gateway: fixes bug where envoy will silently reject RSA keys less than 2048 bits in length when not in FIPS mode, and -will reject keys that are not 2048, 3072, or 4096 bits in length in FIPS mode. We now validate -and reject invalid certs earlier. -``` diff --git a/.changelog/2520.txt b/.changelog/2520.txt deleted file mode 100644 index 96d03dc093..0000000000 --- a/.changelog/2520.txt +++ /dev/null @@ -1,4 +0,0 @@ -```release-note:bug -transparent-proxy: Fix issue where connect-inject lacked sufficient `mesh:write` privileges in some deployments, -which prevented virtual IPs from persisting properly. -``` diff --git a/.changelog/2524.txt b/.changelog/2524.txt deleted file mode 100644 index 5d634e68e1..0000000000 --- a/.changelog/2524.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -(api-gateway) make API gateway controller less verbose -``` \ No newline at end of file diff --git a/.changelog/2597.txt b/.changelog/2597.txt deleted file mode 100644 index 83cc369b6d..0000000000 --- a/.changelog/2597.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -api-gateway: fix helm install when setting copyAnnotations or nodeSelector -``` diff --git a/.changelog/2642.txt b/.changelog/2650.txt similarity index 70% rename from .changelog/2642.txt rename to .changelog/2650.txt index 5278ed705c..229410a2bf 100644 --- a/.changelog/2642.txt +++ b/.changelog/2650.txt @@ -1,4 +1,4 @@ ```release-note:security -Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. +Upgrade to use Go 1.19.11 and `x/net/http` 0.12.0. This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). ``` diff --git a/.changelog/2678.txt b/.changelog/2678.txt new file mode 100644 index 0000000000..97e7707c41 --- /dev/null +++ b/.changelog/2678.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: do not set container securityContexts by default on OpenShift < 4.11 +``` diff --git a/.changelog/2707.txt b/.changelog/2707.txt deleted file mode 100644 index 370aaa7c17..0000000000 --- a/.changelog/2707.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: adds ability to map privileged ports on Gateway listeners to unprivileged ports so that containers do not require additional privileges -``` diff --git a/.changelog/2711.txt b/.changelog/2711.txt deleted file mode 100644 index abb0b7e4fb..0000000000 --- a/.changelog/2711.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: translate and validate TLS configuration options, including min/max version and cipher suites, setting Gateway status appropriately -``` diff --git a/.changelog/2710.txt b/.changelog/2717.txt similarity index 81% rename from .changelog/2710.txt rename to .changelog/2717.txt index 1d37b32dfb..a4557ecaaf 100644 --- a/.changelog/2710.txt +++ b/.changelog/2717.txt @@ -1,5 +1,5 @@ ```release-note:security -Upgrade to use Go 1.20.7 and `x/net` 0.13.0. +Upgrade to use Go 1.19.12 and `x/net` 0.13.0. This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). ``` diff --git a/.changelog/2723.txt b/.changelog/2723.txt deleted file mode 100644 index 0e46cba7a7..0000000000 --- a/.changelog/2723.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -helm: Add ability to configure resource requests and limits for Gateway API deployments. -``` diff --git a/.changelog/2735.txt b/.changelog/2735.txt deleted file mode 100644 index 8b74b5552d..0000000000 --- a/.changelog/2735.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: add RouteRetryFilter and RouteTimeoutFilter CRDs -``` \ No newline at end of file diff --git a/.changelog/2743.txt b/.changelog/2743.txt deleted file mode 100644 index 4e8db233b1..0000000000 --- a/.changelog/2743.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: Changed the container ordering in connect-inject to insert consul-dataplane container first if lifecycle is enabled. Container ordering is unchanged if lifecycle is disabled. -``` diff --git a/.changelog/2748.txt b/.changelog/2748.txt deleted file mode 100644 index 2a8c922d13..0000000000 --- a/.changelog/2748.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -control-plane: Set locality on sidecar proxies in addition to services when registering with connect-inject. -``` diff --git a/.changelog/2784.txt b/.changelog/2784.txt deleted file mode 100644 index 5b11ca3d43..0000000000 --- a/.changelog/2784.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Add the `PrioritizeByLocality` field to the `ServiceResolver` and `ProxyDefaults` CRDs. -``` diff --git a/.changelog/2796.txt b/.changelog/2796.txt deleted file mode 100644 index 84646b502d..0000000000 --- a/.changelog/2796.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -ingress-gateway: Adds missing PassiveHealthCheck to IngressGateways CRD and updates missing fields on ServiceDefaults CRD -``` \ No newline at end of file diff --git a/.changelog/2844.txt b/.changelog/2844.txt deleted file mode 100644 index 89ba684575..0000000000 --- a/.changelog/2844.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -helm: (Consul Enterprise) Adds rate limiting config to serviceDefaults CRD -``` diff --git a/.changelog/2869.txt b/.changelog/2869.txt deleted file mode 100644 index 8771462414..0000000000 --- a/.changelog/2869.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -bug: Remove `global.acls.nodeSelector` and `global.acls.annotations` from Gateway Resources Jobs -``` diff --git a/.changelog/2880.txt b/.changelog/2880.txt deleted file mode 100644 index b06fcf985f..0000000000 --- a/.changelog/2880.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -api-gateway: reduce log output when disconnecting from consul server -``` diff --git a/.changelog/2881.txt b/.changelog/2881.txt deleted file mode 100644 index 5d76975cd3..0000000000 --- a/.changelog/2881.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: Add `JWKSCluster` field to `JWTProvider` CRD. -``` \ No newline at end of file diff --git a/.changelog/2904.txt b/.changelog/2904.txt deleted file mode 100644 index 69755454d9..0000000000 --- a/.changelog/2904.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: Add support for response header modifiers in HTTPRoute filters -``` diff --git a/.changelog/2936.txt b/.changelog/2938.txt similarity index 90% rename from .changelog/2936.txt rename to .changelog/2938.txt index 923f9383f4..49686646f2 100644 --- a/.changelog/2936.txt +++ b/.changelog/2938.txt @@ -1,5 +1,5 @@ ```release-note:security -Upgrade to use Go 1.20.8. This resolves CVEs +Upgrade to use Go 1.19.13. This resolves CVEs [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), diff --git a/.changelog/2941.txt b/.changelog/2941.txt deleted file mode 100644 index 7b92ac64f3..0000000000 --- a/.changelog/2941.txt +++ /dev/null @@ -1,22 +0,0 @@ -```release-note:feature -:tada: This release provides the ability to preview Consul's v2 Catalog and Resource API if enabled. -The new model supports multi-port application deployments with only a single Envoy proxy. -Note that the v1 and v2 catalogs are not cross compatible, and not all Consul features are available within this v2 feature preview. -See the [v2 Catalog and Resource API documentation](https://developer.hashicorp.com/consul/docs/k8s/multiport) for more information. -The v2 Catalog and Resources API should be considered a feature preview within this release and should not be used in production environments. - -### Limitations -* The v1 and v2 catalog APIs cannot run concurrently. -* The Consul UI must be disable. It does not support multi-port services or the v2 catalog API in this release. -* HCP Consul does not support multi-port services or the v2 catalog API in this release. -* The v2 API only supports transparent proxy mode where services that have permissions to connect to each other can use - Kube DNS to connect. - -### Known Issues -* When using the v2 API with transparent proxy, Kubernetes pods cannot use L7 liveness, readiness, or startup probes. - -[[GH-2868]](https://github.com/hashicorp/consul-k8s/pull/2868) -[[GH-2883]](https://github.com/hashicorp/consul-k8s/pull/2883) -[[GH-2930]](https://github.com/hashicorp/consul-k8s/pull/2930) -[[GH-2967]](https://github.com/hashicorp/consul-k8s/pull/2967) -``` diff --git a/.changelog/2952.txt b/.changelog/2952.txt deleted file mode 100644 index d07be130cf..0000000000 --- a/.changelog/2952.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -Add support for running on GKE Autopilot. -``` diff --git a/.changelog/2958.txt b/.changelog/2958.txt deleted file mode 100644 index 49d10d70e7..0000000000 --- a/.changelog/2958.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Add support for new observability service principal in cloud preset -``` diff --git a/.changelog/2962.txt b/.changelog/2962.txt deleted file mode 100644 index f4550c2781..0000000000 --- a/.changelog/2962.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: (Consul Enterprise) Add JWT authentication and authorization for API Gateway and HTTPRoutes. -``` diff --git a/.changelog/3000.txt b/.changelog/3000.txt deleted file mode 100644 index 1e6be21f78..0000000000 --- a/.changelog/3000.txt +++ /dev/null @@ -1,36 +0,0 @@ -```release-note:breaking-change -server: set `leave_on_terminate` to `true` and set the server pod disruption budget `maxUnavailable` to `1`. - -This change makes server rollouts faster and more reliable. However, there is now a potential for reduced reliability if users accidentally -scale the statefulset down. Now servers will leave the raft pool when they are stopped gracefully which reduces the fault -tolerance. For example, with 5 servers, you can tolerate a loss of 2 servers' data as raft guarantees data is replicated to -a majority of nodes (3). However, if you accidentally scale the statefulset down to 3, then the raft quorum will now be 2, and -if you lose 2 servers, you may lose data. Before this change, the quorum would have remained at 3. - -During a regular rollout, the number of servers will be reduced by 1 at a time, which doesn't affect quorum when running -an odd number of servers, e.g. quorum for 5 servers is 3, and quorum for 4 servers is also 3. That's why the pod disruption -budget is being set to 1 now. - -If a server is stopped ungracefully, e.g. due to a node loss, it will not leave the raft pool, and so fault tolerance won't be affected. - -For the vast majority of users, this change will be beneficial, however if you wish to remain with the old settings you -can set: - - server: - extraConfig: | - {"leave_on_terminate": false} - disruptionBudget: - maxUnavailable: - -``` - -```release-note:breaking-change -server: set `autopilot.min_quorum` to the correct quorum value to ensure autopilot doesn't prune servers needed for quorum. Also set `autopilot. disable_upgrade_migration` to `true` as that setting is meant for blue/green deploys, not rolling deploys. - -This setting makes sense for most use-cases, however if you had a specific reason to use the old settings you can use the following config to keep them: - - server: - extraConfig: | - {"autopilot": {"min_quorum": 0, "disable_upgrade_migration": false}} - -``` diff --git a/.changelog/3001.txt b/.changelog/3001.txt deleted file mode 100644 index a2a6ea1a7a..0000000000 --- a/.changelog/3001.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -crd: fix misspelling of preparedQuery field in ControlPlaneRequestLimit CRD -``` diff --git a/.changelog/3070.txt b/.changelog/3070.txt deleted file mode 100644 index acbfdfdf9d..0000000000 --- a/.changelog/3070.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -api-gateway: fix issue where missing `NET_BIND_SERVICE` capability prevented api-gateway `Pod` from starting up when deployed to OpenShift -``` diff --git a/.changelog/3116.txt b/.changelog/3121.txt similarity index 61% rename from .changelog/3116.txt rename to .changelog/3121.txt index 7a38debb7d..0f7743844a 100644 --- a/.changelog/3116.txt +++ b/.changelog/3121.txt @@ -1,3 +1,3 @@ ```release-note:security -Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) +Update Envoy version to 1.24.12 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) ``` diff --git a/.changelog/3128.txt b/.changelog/3128.txt deleted file mode 100644 index 0e7d321518..0000000000 --- a/.changelog/3128.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -control-plane: only alert on valid errors, not timeouts in gateway -``` diff --git a/.changelog/3137.txt b/.changelog/3137.txt deleted file mode 100644 index 8fd8e0876b..0000000000 --- a/.changelog/3137.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -control-plane: fixes an issue with the server-acl-init job where the job would fail on upgrades due to consul server ip address changes. -``` diff --git a/.changelog/3138.txt b/.changelog/3138.txt deleted file mode 100644 index 2eefd6b616..0000000000 --- a/.changelog/3138.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: Kubernetes v1.28 is now supported. Minimum tested version of Kubernetes is now v1.25. -``` diff --git a/.changelog/3158.txt b/.changelog/3158.txt deleted file mode 100644 index 8c89ec9a89..0000000000 --- a/.changelog/3158.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod -``` \ No newline at end of file diff --git a/.changelog/3162.txt b/.changelog/3162.txt deleted file mode 100644 index 588e39e0f9..0000000000 --- a/.changelog/3162.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -control-plane: remove extraneous error log in v2 pod controller when a pod is scheduled, but not yet allocated an IP. -``` \ No newline at end of file diff --git a/.changelog/3172.txt b/.changelog/3172.txt deleted file mode 100644 index 9e255f4278..0000000000 --- a/.changelog/3172.txt +++ /dev/null @@ -1,7 +0,0 @@ -```release-note:bug -control-plane: remove extraneous error log in v2 pod controller when attempting to delete ACL tokens. -``` -``` -release-note:bug -init container: fix a bug that didn't clear ACL tokens for init container when tproxy is enabled. -``` \ No newline at end of file diff --git a/.changelog/3180.txt b/.changelog/3180.txt deleted file mode 100644 index aee3bfb8aa..0000000000 --- a/.changelog/3180.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. -``` diff --git a/.changelog/3184.txt b/.changelog/3184.txt deleted file mode 100644 index 4e1abf0f35..0000000000 --- a/.changelog/3184.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs -``` \ No newline at end of file diff --git a/.changelog/3209.txt b/.changelog/3209.txt deleted file mode 100644 index 35554fc2d9..0000000000 --- a/.changelog/3209.txt +++ /dev/null @@ -1,4 +0,0 @@ -```release-note:bug -control-plane: Fixes a bug with the control-plane CLI validation where the consul-dataplane sidecar CPU request is -compared against the memory limit instead of the CPU limit. -``` \ No newline at end of file diff --git a/.changelog/3215.txt b/.changelog/3215.txt deleted file mode 100644 index c753e1ff79..0000000000 --- a/.changelog/3215.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -consul-telemetry-collector: fix deployments to non-default namespaces when global.enableConsulNamespaces -``` \ No newline at end of file diff --git a/.changelog/3219.txt b/.changelog/3219.txt deleted file mode 100644 index 90292c4b71..0000000000 --- a/.changelog/3219.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -consul-telemetry-collector: add telemetryCollector.cloud.resourceId that works even when not global.cloud.enabled -``` \ No newline at end of file diff --git a/.changelog/3221.txt b/.changelog/3221.txt deleted file mode 100644 index 953bde146b..0000000000 --- a/.changelog/3221.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -cli: Add -o json (-output-format json) to `consul-k8s proxy list` command that returns the result in json format. -``` diff --git a/.changelog/3222.txt b/.changelog/3222.txt deleted file mode 100644 index 9347bd077a..0000000000 --- a/.changelog/3222.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -control-plane: adds a named port, `prometheus`, to the `consul-dataplane` sidecar for use with [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#podmetricsendpoint). -``` diff --git a/.changelog/3237.txt b/.changelog/3237.txt deleted file mode 100644 index 7b9100d816..0000000000 --- a/.changelog/3237.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:security -Update `github.com/golang-jwt/jwt/v4` to v4.5.0 to address [PRISMA-2022-0270](https://github.com/golang-jwt/jwt/issues/258). -``` diff --git a/.changelog/3284.txt b/.changelog/3284.txt deleted file mode 100644 index 07b896f906..0000000000 --- a/.changelog/3284.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug-fix -control-plane: normalize the `partition` and `namespace` fields in V1 CRDs when comparing with saved version of the config-entry. -``` diff --git a/.changelog/3307.txt b/.changelog/3307.txt deleted file mode 100644 index 835d3a726f..0000000000 --- a/.changelog/3307.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug-fix -control-plane: Remove virtual nodes in the Consul Catalog when they do not have any services listed. -``` diff --git a/.changelog/3308.txt b/.changelog/3308.txt deleted file mode 100644 index a7fde80332..0000000000 --- a/.changelog/3308.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -crd: adds the [`retryOn`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon) field to the ServiceRouter CRD. -``` diff --git a/.changelog/3312.txt b/.changelog/3312.txt deleted file mode 100644 index f63948096e..0000000000 --- a/.changelog/3312.txt +++ /dev/null @@ -1,7 +0,0 @@ -```release-note:security -Upgrade to use Go 1.20.12. This resolves CVEs -[CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) -[CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) -[CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead -[CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git -``` \ No newline at end of file diff --git a/.changelog/3315.txt b/.changelog/3315.txt deleted file mode 100644 index b0c1729b1f..0000000000 --- a/.changelog/3315.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters -``` \ No newline at end of file diff --git a/.changelog/3322.txt b/.changelog/3322.txt deleted file mode 100644 index 7c7d685276..0000000000 --- a/.changelog/3322.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: reduce Consul Catalog API requests required for endpoints reconcile in large clusters -``` diff --git a/.changelog/3337.txt b/.changelog/3337.txt deleted file mode 100644 index da9e7d5c70..0000000000 --- a/.changelog/3337.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug-fix -mesh: prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers. -``` \ No newline at end of file diff --git a/.changelog/3347.txt b/.changelog/3347.txt deleted file mode 100644 index 0be1d2be2b..0000000000 --- a/.changelog/3347.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: Add new `consul.hashicorp.com/proxy-config-map` annotation that allows for setting values in the opaque config map for proxy service registrations. -``` diff --git a/.changelog/3362.txt b/.changelog/3362.txt deleted file mode 100644 index 82a7e56f0a..0000000000 --- a/.changelog/3362.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug-fix -crd-controllers: When the CRD controller reconciles a config entry CRD, only patch finalizers in the metadata of the CRD, rather than updating the entire entry, which causes changes to the CRD spec (such as adding in unspecified zero values) when using a Kubernetes client such as kubectl with `replace` mode. -``` diff --git a/.changelog/3418.txt b/.changelog/3418.txt deleted file mode 100644 index 032356f989..0000000000 --- a/.changelog/3418.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:security -Upgrade OpenShift container images to use `ubi9-minimal:9.3` as the base image. -``` diff --git a/.changelog/3437.txt b/.changelog/3437.txt deleted file mode 100644 index 3e70f0d4c0..0000000000 --- a/.changelog/3437.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug-fix -api-gateways: API Gateway pods now include `gateway-kind` and `gateway-consul-service-name` annotations consistent with other Consul gateway types. -``` \ No newline at end of file diff --git a/.changelog/3440.txt b/.changelog/3440.txt deleted file mode 100644 index f5a3a4b3ec..0000000000 --- a/.changelog/3440.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -api-gateway: fix issue where deleting an http-route in a non-default namespace would not remove the route from Consul. -``` diff --git a/.changelog/3442.txt b/.changelog/3442.txt deleted file mode 100644 index 812573bf97..0000000000 --- a/.changelog/3442.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:security -Update `golang.org/x/crypto` to v0.17.0 to address [CVE-2023-48795](https://nvd.nist.gov/vuln/detail/CVE-2023-48795). -``` diff --git a/.changelog/3450.txt b/.changelog/3450.txt deleted file mode 100644 index b51cd304cb..0000000000 --- a/.changelog/3450.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: Add new `consul.hashicorp.com/sidecar-proxy-startup-failure-seconds` and `consul.hashicorp.com/sidecar-proxy-liveness-failure-seconds` annotations that allow users to manually configure startup and liveness probes for Envoy sidecar proxies. -``` diff --git a/.changelog/3478.txt b/.changelog/3478.txt deleted file mode 100644 index 81568f397f..0000000000 --- a/.changelog/3478.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -Upgrade to use Go 1.21.6. -``` diff --git a/.changelog/3498.txt b/.changelog/3498.txt deleted file mode 100644 index 7aed5a69af..0000000000 --- a/.changelog/3498.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -cni: When CNI is enabled, set ReadOnlyRootFilesystem=true and AllowPrivilegeEscalation=false for mesh pod init containers and AllowPrivilegeEscalation=false for consul-dataplane containers (ReadOnlyRootFilesystem was already true for consul-dataplane containers). -``` diff --git a/.changelog/3502.txt b/.changelog/3502.txt deleted file mode 100644 index 56b7334081..0000000000 --- a/.changelog/3502.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: Add `CaseInsensitive` flag to service-routers that allows paths and path prefixes to ignore URL upper and lower casing. -``` diff --git a/.copywrite.hcl b/.copywrite.hcl deleted file mode 100644 index 9263741105..0000000000 --- a/.copywrite.hcl +++ /dev/null @@ -1,18 +0,0 @@ -schema_version = 1 - -project { - license = "MPL-2.0" - copyright_year = 2018 - - # (OPTIONAL) A list of globs that should not have copyright/license headers. - # Supports doublestar glob patterns for more flexibility in defining which - # files or folders should be ignored - header_ignore = [ - - # ignoring charts templates as adding copyright headers breaks all tests - "charts/consul/templates/**", - # we don't own these and the tool that adds copyright headers breaks them - "control-plane/config/crd/external/**", - - ] -} diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index eb998e2cd9..e22e28a48a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - blank_issues_enabled: false contact_links: - name: Consul Community Support diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e863abf7c5..b615e69dd1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,13 +1,15 @@ -### Changes proposed in this PR ### +Changes proposed in this PR: - - -### How I've tested this PR ### +How I've tested this PR: +How I expect reviewers to test this PR: -### How I expect reviewers to test this PR ### - -### Checklist ### +Checklist: - [ ] Tests added -- [ ] [CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) +- [ ] CHANGELOG entry added + > HashiCorp engineers only, community PRs should not add a changelog entry. + > Entries should use present tense (e.g. Add support for...) + diff --git a/.github/workflows/backport-checker.yml b/.github/workflows/backport-checker.yml index a70790c0c0..5bcac5a38e 100644 --- a/.github/workflows/backport-checker.yml +++ b/.github/workflows/backport-checker.yml @@ -1,5 +1,3 @@ -# Copyright (c) HashiCorp, Inc. - # This workflow checks that there is either a 'pr/no-backport' label applied to a PR # or there is a backport/.txt file associated with a PR for a backport label diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5506c017c6..2b4fc425f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: outputs: go-version: ${{ steps.get-go-version.outputs.go-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@v3 - name: Determine Go version id: get-go-version # We use .go-version as our source of truth for current Go @@ -35,7 +35,7 @@ jobs: outputs: product-version: ${{ steps.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@v3 - name: get product version id: get-product-version run: | @@ -49,7 +49,7 @@ jobs: filepath: ${{ steps.generate-metadata-file.outputs.filepath }} steps: - name: "Checkout directory" - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@v3 - name: Generate metadata file id: generate-metadata-file uses: hashicorp/actions-generate-metadata@v1 @@ -57,14 +57,14 @@ jobs: version: ${{ needs.get-product-version.outputs.product-version }} product: ${{ env.PKG_NAME }} repositoryOwner: "hashicorp" - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@v3 with: name: metadata.json path: ${{ steps.generate-metadata-file.outputs.filepath }} build: needs: [get-go-version, get-product-version] - runs-on: ubuntu-20.04 # the GLIBC is too high on 22.04 + runs-on: ubuntu-latest strategy: matrix: include: @@ -79,28 +79,20 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: "+fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: "+fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: "+fips1402" } - - # control-plane + # control-plane - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - # solaris is only built for the control plane + # solaris is only built for the control plane - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "solaris", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: "+fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: "+fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane", "bin_name": "consul-k8s-control-plane.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: "+fips1402" } - - # consul-cni + # consul-cni - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "386", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } @@ -112,41 +104,18 @@ jobs: - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto", fips: "+fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=boringcrypto CC=aarch64-linux-gnu-gcc", fips: "+fips1402", pkg_suffix: "-fips" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane/cni", pkg_name: "consul-cni", "bin_name": "consul-cni.exe", gotags: "fips", env: "CGO_ENABLED=1 GOEXPERIMENT=cngcrypto", fips: "+fips1402" } - fail-fast: true - name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} ${{ matrix.fips }} build + name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} build steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@v3 - name: Setup go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - - name: Replace Go for Windows FIPS with Microsoft Go - if: ${{ matrix.fips == '+fips1402' && matrix.goos == 'windows' }} - run: | - # Uninstall standard Go and use microsoft/go instead - rm -rf /home/runner/actions-runner/_work/_tool/go - curl https://aka.ms/golang/release/latest/go${{ matrix.go }}-1.linux-amd64.tar.gz -Lo go${{ matrix.go }}.linux-amd64.tar.gz - tar -C $HOME -xf go${{ matrix.go }}.linux-amd64.tar.gz - chmod +x $HOME/go/bin - export PATH=$HOME/go/bin:$PATH - if [ $(which go) != "$HOME/go/bin/go" ]; then - echo "Unable to verify microsoft/go toolchain" - exit 1 - fi - - - name: Install cross-compiler for FIPS on arm - if: ${{ matrix.fips == '+fips1402' && matrix.goarch == 'arm64' }} - run: | - sudo apt-get update --allow-releaseinfo-change-suite --allow-releaseinfo-change-version && sudo apt-get install -y gcc-aarch64-linux-gnu - - name: Build env: GOOS: ${{ matrix.goos }} @@ -161,23 +130,23 @@ jobs: export GIT_IMPORT=github.com/hashicorp/consul-k8s/${{ matrix.component }}/version export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X ${GIT_IMPORT}.GitDescribe=${{ needs.get-product-version.outputs.product-version }}" - ${{ matrix.env }} go build -o dist/${{ matrix.bin_name }} -ldflags "${GOLDFLAGS}" -tags=${{ matrix.gotags }} . - zip -r -j out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ + CGO_ENABLED=0 go build -o dist/${{ matrix.bin_name }} -ldflags "${GOLDFLAGS}" . + zip -r -j out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ - name: Upload built binaries - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@v3 with: - name: ${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip - path: ${{ matrix.component}}/out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip + name: ${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip + path: ${{ matrix.component}}/out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip - name: Package rpm and deb files if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} uses: hashicorp/actions-packaging-linux@v1 with: - name: consul-k8s${{ matrix.pkg_suffix }} + name: consul-k8s description: "consul-k8s provides a cli interface to first-class integrations between Consul and Kubernetes." arch: ${{ matrix.goarch }} - version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} + version: ${{ needs.get-product-version.outputs.product-version }} maintainer: "HashiCorp" homepage: "https://github.com/hashicorp/consul-k8s" license: "MPL-2.0" @@ -193,7 +162,7 @@ jobs: - name: Test rpm package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 + uses: addnab/docker-run-action@v3 with: image: registry.access.redhat.com/ubi9/ubi:latest options: -v ${{ github.workspace }}:/work @@ -202,7 +171,7 @@ jobs: cd /work rpm -ivh out/${{ env.RPM_PACKAGE }} CONSUL_K8S_VERSION="$(consul-k8s version | awk '{print $2}')" - VERSION="v${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}" + VERSION="v${{ needs.get-product-version.outputs.product-version }}" if [ "${VERSION}" != "${CONSUL_K8S_VERSION}" ]; then echo "Test FAILED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" exit 1 @@ -210,7 +179,7 @@ jobs: echo "Test PASSED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" - name: Upload rpm package - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@v3 if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} with: name: ${{ env.RPM_PACKAGE }} @@ -218,7 +187,7 @@ jobs: - name: Test debian package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 + uses: addnab/docker-run-action@v3 with: image: ubuntu:latest options: -v ${{ github.workspace }}:/work @@ -227,7 +196,7 @@ jobs: cd /work apt install ./out/${{ env.DEB_PACKAGE }} CONSUL_K8S_VERSION="$(consul-k8s version | awk '{print $2}')" - VERSION="v${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}" + VERSION="v${{ needs.get-product-version.outputs.product-version }}" if [ "${VERSION}" != "${CONSUL_K8S_VERSION}" ]; then echo "Test FAILED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" exit 1 @@ -235,37 +204,31 @@ jobs: echo "Test PASSED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" - name: Upload debian packages - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@v3 if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} with: name: ${{ env.DEB_PACKAGE }} path: out/${{ env.DEB_PACKAGE }} build-docker: - name: Docker ${{ matrix.goarch }} ${{ matrix.fips }} default release build - needs: [get-product-version, get-go-version, build] + name: Docker ${{ matrix.arch }} default release build + needs: [get-product-version, build] runs-on: ubuntu-latest strategy: matrix: - include: - - { goos: "linux", goarch: "arm" } - - { goos: "linux", goarch: "arm64" } - - { goos: "linux", goarch: "386" } - - { goos: "linux", goarch: "amd64" } - - { goos: "linux", goarch: "amd64", fips: "+fips1402" } - - { goos: "linux", goarch: "arm64", fips: "+fips1402" } + arch: ["arm", "arm64", "386", "amd64"] env: repo: ${{ github.event.repository.name }} - version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} + version: ${{ needs.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 with: - name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos}}_${{ matrix.goarch }}.zip - path: control-plane/dist/cni/${{ matrix.goos}}/${{ matrix.goarch }} + name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip + path: control-plane/dist/cni/linux/${{ matrix.arch }} - name: extract consul-cni zip env: - ZIP_LOCATION: control-plane/dist/cni/${{ matrix.goos}}/${{ matrix.goarch }} + ZIP_LOCATION: control-plane/dist/cni/linux/${{ matrix.arch }} run: | cd "${ZIP_LOCATION}" unzip -j *.zip @@ -279,8 +242,7 @@ jobs: echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV - name: Docker Build (Action) - uses: hashicorp/actions-docker-build@76d2fc91532d816ca2660d8f3139e432ac3700fd - if: ${{ !matrix.fips }} + uses: hashicorp/actions-docker-build@v1 with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -291,7 +253,7 @@ jobs: echo "Test PASSED" version: ${{ env.version }} target: release-default - arch: ${{ matrix.goarch }} + arch: ${{ matrix.arch }} pkg_name: consul-k8s-control-plane_${{ env.version }} bin_name: consul-k8s-control-plane workdir: control-plane @@ -302,12 +264,33 @@ jobs: docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-${{ github.sha }} docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }} docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-${{ github.sha }} - extra_build_args: | - GOLANG_VERSION=${{ needs.get-go-version.outputs.go-version }} - - name: Docker FIPS Build (Action) - uses: hashicorp/actions-docker-build@76d2fc91532d816ca2660d8f3139e432ac3700fd - if: ${{ matrix.fips }} + build-docker-ubi-redhat-registry: + name: Docker ${{ matrix.arch }} UBI build for RedHat Registry + needs: [get-product-version, build] + runs-on: ubuntu-latest + strategy: + matrix: + arch: ["amd64"] + env: + repo: ${{ github.event.repository.name }} + version: ${{ needs.get-product-version.outputs.product-version }} + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip + path: control-plane/dist/cni/linux/${{ matrix.arch }} + - name: extract consul-cni zip + env: + ZIP_LOCATION: control-plane/dist/cni/linux/${{ matrix.arch }} + run: | + cd "${ZIP_LOCATION}" + unzip -j *.zip + - name: Copy LICENSE + run: + cp LICENSE ./control-plane + - uses: hashicorp/actions-docker-build@v1 with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -317,49 +300,38 @@ jobs: fi echo "Test PASSED" version: ${{ env.version }} - target: release-default-fips # duplicate target to distinguish FIPS builds in CRT machinery - arch: ${{ matrix.goarch }} + target: ubi + arch: ${{ matrix.arch }} pkg_name: consul-k8s-control-plane_${{ env.version }} bin_name: consul-k8s-control-plane workdir: control-plane - tags: | - docker.io/hashicorp/${{ env.repo }}-control-plane-fips:${{ env.version }} - dev_tags: | - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.full_dev_tag }} - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.full_dev_tag }}-${{ github.sha }} - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }} - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }}-${{ github.sha }} - extra_build_args: | - GOLANG_VERSION=${{ needs.get-go-version.outputs.go-version }} + redhat_tag: quay.io/redhat-isv-containers/611ca2f89a9b407267837100:${{env.version}}-ubi - build-docker-ubi: - name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI builds - needs: [get-product-version, get-go-version, build] + build-docker-ubi-dockerhub: + name: Docker ${{ matrix.arch }} UBI build for DockerHub + needs: [ get-product-version, build ] runs-on: ubuntu-latest strategy: matrix: - include: - - { arch: "amd64" } - - { arch: "amd64", fips: "+fips1402" } + arch: [ "amd64" ] env: repo: ${{ github.event.repository.name }} - version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} + version: ${{ needs.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 with: - name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_linux_${{ matrix.arch }}.zip + name: consul-cni_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip path: control-plane/dist/cni/linux/${{ matrix.arch }} - name: extract consul-cni zip env: ZIP_LOCATION: control-plane/dist/cni/linux/${{ matrix.arch }} run: | - cd "${ZIP_LOCATION}" + cd ${ZIP_LOCATION} unzip -j *.zip - name: Copy LICENSE run: cp LICENSE ./control-plane - # This naming convention will be used ONLY for per-commit dev images - name: Set docker dev tag run: | @@ -369,8 +341,7 @@ jobs: echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV - name: Docker Build (Action) - if: ${{ !matrix.fips }} - uses: hashicorp/actions-docker-build@76d2fc91532d816ca2660d8f3139e432ac3700fd + uses: hashicorp/actions-docker-build@v1 with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -392,27 +363,3 @@ jobs: docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.full_dev_tag }}-ubi-${{ github.sha }} docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.minor_dev_tag }}-ubi-${{ github.sha }} - redhat_tag: quay.io/redhat-isv-containers/611ca2f89a9b407267837100:${{env.version}}-ubi - extra_build_args: | - GOLANG_VERSION=${{ needs.get-go-version.outputs.go-version }} - - - name: Docker FIPS Build (Action) - if: ${{ matrix.fips }} - uses: hashicorp/actions-docker-build@76d2fc91532d816ca2660d8f3139e432ac3700fd - with: - smoke_test: | - TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" - if [ "${TEST_VERSION}" != "v${version}" ]; then - echo "Test FAILED" - exit 1 - fi - echo "Test PASSED" - version: ${{ env.version }} - target: ubi-fips # duplicate target to distinguish FIPS builds in CRT machinery - arch: ${{ matrix.arch }} - pkg_name: consul-k8s-control-plane_${{ env.version }} - bin_name: consul-k8s-control-plane - workdir: control-plane - redhat_tag: quay.io/redhat-isv-containers/6486b1beabfc4e51588c0416:${{env.version}}-ubi # this is different than the non-FIPS one - extra_build_args: | - GOLANG_VERSION=${{ needs.get-go-version.outputs.go-version }} diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 40c9b17c68..d40bdfbd6a 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -1,5 +1,3 @@ -# Copyright (c) HashiCorp, Inc. - # This workflow checks that there is either a 'pr/no-changelog' label applied to a PR # or there is a .changelog/.txt file associated with a PR for a changelog entry @@ -21,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@v2 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 # by default the checkout action doesn't checkout all branches diff --git a/.github/workflows/jira-issues.yaml b/.github/workflows/jira-issues.yaml index bddc69c83f..dc743e9328 100644 --- a/.github/workflows/jira-issues.yaml +++ b/.github/workflows/jira-issues.yaml @@ -15,7 +15,7 @@ jobs: name: Jira Community Issue sync steps: - name: Login - uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 + uses: atlassian/gajira-login@v3.0.0 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -38,7 +38,7 @@ jobs: - name: Create ticket if an issue is filed, or if PR not by a team member is opened if: github.event.action == 'opened' - uses: tomhjp/gh-action-jira-create@3ed1789cad3521292e591a7cfa703215ec1348bf # v0.2.1 + uses: tomhjp/gh-action-jira-create@v0.2.0 with: project: NET issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" @@ -58,28 +58,28 @@ jobs: - name: Search if: github.event.action != 'opened' id: search - uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2 + uses: tomhjp/gh-action-jira-search@v0.2.1 with: # cf[10089] is Issue Link (use JIRA API to retrieve) jql: 'issuetype = "${{ steps.set-ticket-type.outputs.TYPE }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' - name: Sync comment if: github.event.action == 'created' && steps.search.outputs.issue - uses: tomhjp/gh-action-jira-comment@6eb6b9ead70221916b6badd118c24535ed220bd9 # v0.2.0 + uses: tomhjp/gh-action-jira-comment@v0.1.0 with: issue: ${{ steps.search.outputs.issue }} comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}" - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@v2.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@v2.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index 54a532d940..c07a92ee77 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -13,7 +13,7 @@ jobs: name: Jira sync steps: - name: Login - uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 + uses: atlassian/gajira-login@v3.0.0 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -39,7 +39,7 @@ jobs: id: is-team-member run: | TEAM=consul - ROLE="$(gh api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" + ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" if [[ -n ${ROLE} ]]; then echo "Actor ${{ github.actor }} is a ${TEAM} team member" echo "MESSAGE=true" >> $GITHUB_OUTPUT @@ -52,7 +52,7 @@ jobs: - name: Create ticket if an issue is filed, or if PR not by a team member is opened if: ( github.event.action == 'opened' && steps.is-team-member.outputs.MESSAGE == 'false' ) - uses: tomhjp/gh-action-jira-create@3ed1789cad3521292e591a7cfa703215ec1348bf # v0.2.1 + uses: tomhjp/gh-action-jira-create@v0.2.0 with: project: NET issuetype: "${{ steps.set-ticket-type.outputs.TYPE }}" @@ -72,28 +72,28 @@ jobs: - name: Search if: github.event.action != 'opened' id: search - uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2 + uses: tomhjp/gh-action-jira-search@v0.2.1 with: # cf[10089] is Issue Link (use JIRA API to retrieve) jql: 'issuetype = "${{ steps.set-ticket-type.outputs.TYPE }}" and cf[10089] = "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"' - name: Sync comment if: github.event.action == 'created' && steps.search.outputs.issue - uses: tomhjp/gh-action-jira-comment@6eb6b9ead70221916b6badd118c24535ed220bd9 # v0.2.0 + uses: tomhjp/gh-action-jira-comment@v0.1.0 with: issue: ${{ steps.search.outputs.issue }} comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}" - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@v2.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@v2.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index a62906a5f9..2af1e46532 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -20,11 +20,11 @@ jobs: name: test runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@v1.2.2 name: test with: workflow: test.yml repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "actor":"${{ github.actor }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/nightly-acceptance.yml b/.github/workflows/nightly-acceptance.yml index 6db7684bb8..18a8ba958d 100644 --- a/.github/workflows/nightly-acceptance.yml +++ b/.github/workflows/nightly-acceptance.yml @@ -16,7 +16,7 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@v1.2.2 name: cloud with: workflow: cloud.yml diff --git a/.github/workflows/nightly-api-gateway-conformance.yml b/.github/workflows/nightly-api-gateway-conformance.yml deleted file mode 100644 index abeec34659..0000000000 --- a/.github/workflows/nightly-api-gateway-conformance.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Dispatch to the consul-k8s-workflows with a nightly cron -name: nightly-api-gateway-conformance -on: - schedule: - # * is a special character in YAML so you have to quote this string - # Run nightly at 12AM UTC/8PM EST/5PM PST. - - cron: '0 0 * * *' - - -# these should be the only settings that you will ever need to change -env: - BRANCH: ${{ github.ref_name }} - CONTEXT: "nightly" - -jobs: - api-gateway-conformance: - name: api-gateway-conformance - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 - name: conformance - with: - workflow: api-gateway-conformance.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/.github/workflows/nightly-cleanup.yml b/.github/workflows/nightly-cleanup.yml deleted file mode 100644 index 83d6688ac5..0000000000 --- a/.github/workflows/nightly-cleanup.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Dispatch to the consul-k8s-workflows with a nightly cron -name: nightly-cleanup -on: - schedule: - # * is a special character in YAML so you have to quote this string - # Run nightly at 12PM UTC/8AM EST/5AM PST - - cron: '0 12 * * *' - -# these should be the only settings that you will ever need to change -env: - BRANCH: ${{ github.ref_name }} - CONTEXT: "nightly" - -jobs: - cleanup: - name: cleanup - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 - name: cleanup - with: - workflow: cleanup.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/.github/workflows/pr.yml b/.github/workflows/pr.yml index c211718a2f..65f924e6d0 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -14,11 +14,11 @@ jobs: name: test runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@v1.2.2 name: test with: workflow: test.yml repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "actor":"${{ github.actor }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/weekly-acceptance-1-4-x.yml b/.github/workflows/weekly-acceptance-0-49-x.yml similarity index 77% rename from .github/workflows/weekly-acceptance-1-4-x.yml rename to .github/workflows/weekly-acceptance-0-49-x.yml index a6bbe05e6b..691d0b6236 100644 --- a/.github/workflows/weekly-acceptance-1-4-x.yml +++ b/.github/workflows/weekly-acceptance-0-49-x.yml @@ -1,16 +1,16 @@ # 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-4-x +name: weekly-acceptance-0-49-x on: schedule: # * is a special character in YAML so you have to quote this string - # Run weekly on Thursday at 3AM UTC/11PM EST/8PM PST - - cron: '0 3 * * 4' + # Run weekly on Monday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 1' # these should be the only settings that you will ever need to change env: - BRANCH: "release/1.4.x" + BRANCH: "release/0.49.x" CONTEXT: "weekly" jobs: @@ -18,7 +18,7 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@v1.2.2 name: cloud with: workflow: cloud.yml diff --git a/.github/workflows/weekly-acceptance-1-2-x.yml b/.github/workflows/weekly-acceptance-1-0-x.yml similarity index 85% rename from .github/workflows/weekly-acceptance-1-2-x.yml rename to .github/workflows/weekly-acceptance-1-0-x.yml index 3dac6c8755..663f779bb5 100644 --- a/.github/workflows/weekly-acceptance-1-2-x.yml +++ b/.github/workflows/weekly-acceptance-1-0-x.yml @@ -1,7 +1,7 @@ # 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-2-x +name: weekly-acceptance-1-0-x on: schedule: # * is a special character in YAML so you have to quote this string @@ -11,7 +11,7 @@ on: # these should be the only settings that you will ever need to change env: - BRANCH: "release/1.2.x" + BRANCH: "release/1.0.x" CONTEXT: "weekly" jobs: @@ -19,7 +19,7 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@v1.2.2 name: cloud with: workflow: cloud.yml diff --git a/.github/workflows/weekly-acceptance-1-1-x.yml b/.github/workflows/weekly-acceptance-1-1-x.yml index c3c39fef32..2fc21aba5b 100644 --- a/.github/workflows/weekly-acceptance-1-1-x.yml +++ b/.github/workflows/weekly-acceptance-1-1-x.yml @@ -5,8 +5,8 @@ name: weekly-acceptance-1-1-x on: schedule: # * is a special character in YAML so you have to quote this string - # Run weekly on Monday at 3AM UTC/11PM EST/8PM PST - - cron: '0 3 * * 1' + # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 3' # these should be the only settings that you will ever need to change @@ -19,7 +19,7 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@v1.2.2 name: cloud with: workflow: cloud.yml diff --git a/.github/workflows/weekly-acceptance-1-3-x.yml b/.github/workflows/weekly-acceptance-1-3-x.yml deleted file mode 100644 index 9d1a2d65a6..0000000000 --- a/.github/workflows/weekly-acceptance-1-3-x.yml +++ /dev/null @@ -1,28 +0,0 @@ -# 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-3-x -on: - schedule: - # * is a special character in YAML so you have to quote this string - # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST - - cron: '0 3 * * 3' - -# these should be the only settings that you will ever need to change -env: - BRANCH: "release/1.3.x" - CONTEXT: "weekly" - -jobs: - cloud: - name: cloud - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 - 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/.github/workflows/weekly-acceptance-1-4-0-rc1.yml b/.github/workflows/weekly-acceptance-1-4-0-rc1.yml deleted file mode 100644 index e74a44ea70..0000000000 --- a/.github/workflows/weekly-acceptance-1-4-0-rc1.yml +++ /dev/null @@ -1,28 +0,0 @@ -# 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-4-0-rc1 -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 * * 5' - -# these should be the only settings that you will ever need to change -env: - BRANCH: "release/1.4.0-rc1" - CONTEXT: "weekly" - -jobs: - cloud: - name: cloud - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 - 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/.go-version b/.go-version index c262b1f0df..acdfc7930c 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.21.6 +1.20.10 diff --git a/.golangci.yml b/.golangci.yml index dcad005d10..95aa25b0ff 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - linters: # enables all defaults + the below, `golangci-lint linters` to see the list of active linters. enable: diff --git a/.release/ci.hcl b/.release/ci.hcl index 0f550192e7..677b3081e4 100644 --- a/.release/ci.hcl +++ b/.release/ci.hcl @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - schema = "1" project "consul-k8s" { diff --git a/.release/release-metadata.hcl b/.release/release-metadata.hcl index 10741e9c36..c053fdac2f 100644 --- a/.release/release-metadata.hcl +++ b/.release/release-metadata.hcl @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - url_docker_registry_dockerhub = "https://hub.docker.com/r/hashicorp/consul-k8s-control-plane" url_license = "https://github.com/hashicorp/consul-k8s/blob/main/LICENSE" url_project_website = "https://www.consul.io/docs/k8s" diff --git a/.release/security-scan.hcl b/.release/security-scan.hcl index 692fea1578..42576d29b2 100644 --- a/.release/security-scan.hcl +++ b/.release/security-scan.hcl @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - container { dependencies = true alpine_secdb = true diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f2ee1c20a..5c00720f75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,180 +1,3 @@ -## 1.3.1 (December 19, 2023) - -SECURITY: - -* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3118](https://github.com/hashicorp/consul-k8s/issues/3118)] -* Update `github.com/golang-jwt/jwt/v4` to v4.5.0 to address [PRISMA-2022-0270](https://github.com/golang-jwt/jwt/issues/258). [[GH-3237](https://github.com/hashicorp/consul-k8s/issues/3237)] -* Upgrade to use Go 1.20.12. This resolves CVEs -[CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) -[CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) -[CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead -[CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git [[GH-3312](https://github.com/hashicorp/consul-k8s/issues/3312)] - -FEATURES: - -* control-plane: adds a named port, `prometheus`, to the `consul-dataplane` sidecar for use with [Prometheus operator](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#podmetricsendpoint). [[GH-3222](https://github.com/hashicorp/consul-k8s/issues/3222)] -* crd: adds the [`retryOn`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon) field to the ServiceRouter CRD. [[GH-3308](https://github.com/hashicorp/consul-k8s/issues/3308)] -* helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. [[GH-3180](https://github.com/hashicorp/consul-k8s/issues/3180)] - -IMPROVEMENTS: - -* cli: Add -o json (-output-format json) to `consul-k8s proxy list` command that returns the result in json format. [[GH-3221](https://github.com/hashicorp/consul-k8s/issues/3221)] -* cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod [[GH-3158](https://github.com/hashicorp/consul-k8s/issues/3158)] -* control-plane: Add new `consul.hashicorp.com/proxy-config-map` annotation that allows for setting values in the opaque config map for proxy service registrations. [[GH-3347](https://github.com/hashicorp/consul-k8s/issues/3347)] -* helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters [[GH-3315](https://github.com/hashicorp/consul-k8s/issues/3315)] - -BUG FIXES: - -* consul-telemetry-collector: add telemetryCollector.cloud.resourceId that works even when not global.cloud.enabled [[GH-3219](https://github.com/hashicorp/consul-k8s/issues/3219)] -* consul-telemetry-collector: fix deployments to non-default namespaces when global.enableConsulNamespaces [[GH-3215](https://github.com/hashicorp/consul-k8s/issues/3215)] -* consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs [[GH-3184](https://github.com/hashicorp/consul-k8s/issues/3184)] -* control-plane: Fixes a bug with the control-plane CLI validation where the consul-dataplane sidecar CPU request is compared against the memory limit instead of the CPU limit. [[GH-3209](https://github.com/hashicorp/consul-k8s/issues/3209)] -* control-plane: Only delete ACL tokens matched Pod UID in Service Registration metadata [[GH-3210](https://github.com/hashicorp/consul-k8s/issues/3210)] -* control-plane: fixes an issue with the server-acl-init job where the job would fail on upgrades due to consul server ip address changes. [[GH-3137](https://github.com/hashicorp/consul-k8s/issues/3137)] -* control-plane: only alert on valid errors, not timeouts in gateway [[GH-3128](https://github.com/hashicorp/consul-k8s/issues/3128)] -* control-plane: remove extraneous error log in v2 pod controller when a pod is scheduled, but not yet allocated an IP. [[GH-3162](https://github.com/hashicorp/consul-k8s/issues/3162)] -* control-plane: remove extraneous error log in v2 pod controller when attempting to delete ACL tokens. [[GH-3172](https://github.com/hashicorp/consul-k8s/issues/3172)] -* control-plane: Remove virtual nodes in the Consul Catalog when they do not have any services listed. [[GH-3307](https://github.com/hashicorp/consul-k8s/issues/3307)] -* mesh: prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers. [[GH-3337](https://github.com/hashicorp/consul-k8s/issues/3337)] - -## 1.2.4 (December 19, 2023) - -SECURITY: - -* Update `github.com/golang-jwt/jwt/v4` to v4.5.0 to address [PRISMA-2022-0270](https://github.com/golang-jwt/jwt/issues/258). [[GH-3237](https://github.com/hashicorp/consul-k8s/issues/3237)] -* Upgrade to use Go 1.20.12. This resolves CVEs -[CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) -[CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) -[CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead -[CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git [[GH-3312](https://github.com/hashicorp/consul-k8s/issues/3312)] - -FEATURES: - -* crd: adds the [`retryOn`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon) field to the ServiceRouter CRD. [[GH-3308](https://github.com/hashicorp/consul-k8s/issues/3308)] -* helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. [[GH-3180](https://github.com/hashicorp/consul-k8s/issues/3180)] - -IMPROVEMENTS: - -* cli: Add -o json (-output-format json) to `consul-k8s proxy list` command that returns the result in json format. [[GH-3221](https://github.com/hashicorp/consul-k8s/issues/3221)] -* cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod [[GH-3158](https://github.com/hashicorp/consul-k8s/issues/3158)] -* control-plane: Add new `consul.hashicorp.com/proxy-config-map` annotation that allows for setting values in the opaque config map for proxy service registrations. [[GH-3347](https://github.com/hashicorp/consul-k8s/issues/3347)] -* helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters [[GH-3315](https://github.com/hashicorp/consul-k8s/issues/3315)] - -BUG FIXES: - -* consul-telemetry-collector: add telemetryCollector.cloud.resourceId that works even when not global.cloud.enabled [[GH-3219](https://github.com/hashicorp/consul-k8s/issues/3219)] -* consul-telemetry-collector: fix deployments to non-default namespaces when global.enableConsulNamespaces [[GH-3215](https://github.com/hashicorp/consul-k8s/issues/3215)] -* consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs [[GH-3184](https://github.com/hashicorp/consul-k8s/issues/3215)] -* control-plane: Only delete ACL tokens matched Pod UID in Service Registration metadata [[GH-3210](https://github.com/hashicorp/consul-k8s/issues/3210)] -* control-plane: fixes an issue with the server-acl-init job where the job would fail on upgrades due to consul server ip address changes. [[GH-3137](https://github.com/hashicorp/consul-k8s/issues/3137)] -* control-plane: normalize the `partition` and `namespace` fields in V1 CRDs when comparing with saved version of the config-entry. [[GH-3284](https://github.com/hashicorp/consul-k8s/issues/3284)] -* control-plane: Remove virtual nodes in the Consul Catalog when they do not have any services listed. [[GH-3307](https://github.com/hashicorp/consul-k8s/issues/3307)] -* mesh: prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers. [[GH-3337](https://github.com/hashicorp/consul-k8s/issues/3337)] - -## 1.1.8 (December 19, 2023) - -SECURITY: - -* Update `github.com/golang-jwt/jwt/v4` to v4.5.0 to address [PRISMA-2022-0270](https://github.com/golang-jwt/jwt/issues/258). [[GH-3237](https://github.com/hashicorp/consul-k8s/issues/3237)] -* Upgrade to use Go 1.20.12. This resolves CVEs -[CVE-2023-45283](https://nvd.nist.gov/vuln/detail/CVE-2023-45283): (`path/filepath`) recognize \??\ as a Root Local Device path prefix (Windows) -[CVE-2023-45284](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): recognize device names with trailing spaces and superscripts (Windows) -[CVE-2023-39326](https://nvd.nist.gov/vuln/detail/CVE-2023-39326): (`net/http`) limit chunked data overhead -[CVE-2023-45285](https://nvd.nist.gov/vuln/detail/CVE-2023-45285): (`cmd/go`) go get may unexpectedly fallback to insecure git [[GH-3312](https://github.com/hashicorp/consul-k8s/issues/3312)] - -FEATURES: - -* crd: adds the [`retryOn`](https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon) field to the ServiceRouter CRD. [[GH-3308](https://github.com/hashicorp/consul-k8s/issues/3308)] -* helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. [[GH-3180](https://github.com/hashicorp/consul-k8s/issues/3180)] - -IMPROVEMENTS: - -* cli: Add -o json (-output-format json) to `consul-k8s proxy list` command that returns the result in json format. [[GH-3221](https://github.com/hashicorp/consul-k8s/issues/3221)] -* cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod [[GH-3158](https://github.com/hashicorp/consul-k8s/issues/3158)] -* control-plane: Add new `consul.hashicorp.com/proxy-config-map` annotation that allows for setting values in the opaque config map for proxy service registrations. [[GH-3347](https://github.com/hashicorp/consul-k8s/issues/3347)] -* helm: add validation that global.cloud.enabled is not set with externalServers.hosts set to HCP-managed clusters [[GH-3315](https://github.com/hashicorp/consul-k8s/issues/3315)] - -BUG FIXES: - -* consul-telemetry-collector: add telemetryCollector.cloud.resourceId that works even when not global.cloud.enabled [[GH-3219](https://github.com/hashicorp/consul-k8s/issues/3219)] -* consul-telemetry-collector: fix deployments to non-default namespaces when global.enableConsulNamespaces [[GH-3215](https://github.com/hashicorp/consul-k8s/issues/3215)] -* consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs [[GH-3184](https://github.com/hashicorp/consul-k8s/issues/3184)] -* control-plane: Only delete ACL tokens matched Pod UID in Service Registration metadata [[GH-3210](https://github.com/hashicorp/consul-k8s/issues/3210)] -* control-plane: fixes an issue with the server-acl-init job where the job would fail on upgrades due to consul server ip address changes. [[GH-3137](https://github.com/hashicorp/consul-k8s/issues/3137)] -* control-plane: Remove virtual nodes in the Consul Catalog when they do not have any services listed. [[GH-3137](https://github.com/hashicorp/consul-k8s/issues/3137)] -* mesh: prevent extra-config from being loaded twice (and erroring for segment config) on clients and servers. [[GH-3337](https://github.com/hashicorp/consul-k8s/issues/3337)] - -## 1.3.0 (November 8, 2023) - -SECURITY: - -* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3116](https://github.com/hashicorp/consul-k8s/issues/3116)] - -FEATURES: - -* :tada: This release provides the ability to preview Consul's v2 Catalog and Resource API if enabled. -The new model supports multi-port application deployments with only a single Envoy proxy. -Note that the v1 and v2 catalogs are not cross compatible, and not all Consul features are available within this v2 feature preview. -See the [v2 Catalog and Resource API documentation](https://developer.hashicorp.com/consul/docs/k8s/multiport) for more information. -The v2 Catalog and Resources API should be considered a feature preview within this release and should not be used in production environments. - -### Limitations -* The v1 and v2 catalog APIs cannot run concurrently. -* The Consul UI must be disable. It does not support multi-port services or the v2 catalog API in this release. -* HCP Consul does not support multi-port services or the v2 catalog API in this release. - -[[GH-2868]](https://github.com/hashicorp/consul-k8s/pull/2868) -[[GH-2883]](https://github.com/hashicorp/consul-k8s/pull/2883) -[[GH-2930]](https://github.com/hashicorp/consul-k8s/pull/2930) -[[GH-2967]](https://github.com/hashicorp/consul-k8s/pull/2967) [[GH-2941](https://github.com/hashicorp/consul-k8s/issues/2941)] -* Add the `PrioritizeByLocality` field to the `ServiceResolver` and `ProxyDefaults` CRDs. [[GH-2784](https://github.com/hashicorp/consul-k8s/issues/2784)] -* Set locality on services registered with connect-inject. [[GH-2346](https://github.com/hashicorp/consul-k8s/issues/2346)] -* api-gateway: Add support for response header modifiers in HTTPRoute filters [[GH-2904](https://github.com/hashicorp/consul-k8s/issues/2904)] -* api-gateway: add RouteRetryFilter and RouteTimeoutFilter CRDs [[GH-2735](https://github.com/hashicorp/consul-k8s/issues/2735)] -* helm: (Consul Enterprise) Adds rate limiting config to serviceDefaults CRD [[GH-2844](https://github.com/hashicorp/consul-k8s/issues/2844)] -* helm: add persistentVolumeClaimRetentionPolicy variable for managing Statefulsets PVC retain policy when deleting or downsizing the statefulset. [[GH-3180](https://github.com/hashicorp/consul-k8s/issues/3180)] - -IMPROVEMENTS: - -* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2265](https://github.com/hashicorp/consul-k8s/issues/2265)] -* cli: Add consul-k8s proxy stats command line interface that outputs the localhost:19000/stats of envoy in the pod [[GH-3158](https://github.com/hashicorp/consul-k8s/issues/3158)] -* control-plane: Changed the container ordering in connect-inject to insert consul-dataplane container first if lifecycle is enabled. Container ordering is unchanged if lifecycle is disabled. [[GH-2743](https://github.com/hashicorp/consul-k8s/issues/2743)] -* helm: Kubernetes v1.28 is now supported. Minimum tested version of Kubernetes is now v1.25. [[GH-3138](https://github.com/hashicorp/consul-k8s/issues/3138)] - -BUG FIXES: - -* control-plane: Set locality on sidecar proxies in addition to services when registering with connect-inject. [[GH-2748](https://github.com/hashicorp/consul-k8s/issues/2748)] -* control-plane: remove extraneous error log in v2 pod controller when a pod is scheduled, but not yet allocated an IP. [[GH-3162](https://github.com/hashicorp/consul-k8s/issues/3162)] -* control-plane: remove extraneous error log in v2 pod controller when attempting to delete ACL tokens. [[GH-3172](https://github.com/hashicorp/consul-k8s/issues/3172)] - -## 1.2.3 (November 2, 2023) - -SECURITY: - -* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3119](https://github.com/hashicorp/consul-k8s/issues/3119)] -* Upgrade `google.golang.org/grpc` to 1.56.3. -This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3139](https://github.com/hashicorp/consul-k8s/issues/3139)] -* Upgrade to use Go 1.20.10 and `x/net` 0.17.0. -This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) -/ [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3085](https://github.com/hashicorp/consul-k8s/issues/3085)] - -BUG FIXES: - -* api-gateway: fix issue where missing `NET_BIND_SERVICE` capability prevented api-gateway `Pod` from starting up when deployed to OpenShift [[GH-3070](https://github.com/hashicorp/consul-k8s/issues/3070)] -* control-plane: only alert on valid errors, not timeouts in gateway [[GH-3128](https://github.com/hashicorp/consul-k8s/issues/3128)] -* crd: fix misspelling of preparedQuery field in ControlPlaneRequestLimit CRD [[GH-3001](https://github.com/hashicorp/consul-k8s/issues/3001)] - -## 1.1.7 (November 2, 2023) - -SECURITY: - -* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3120](https://github.com/hashicorp/consul-k8s/issues/3120)] -* Upgrade `google.golang.org/grpc` to 1.56.3. -This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3139](https://github.com/hashicorp/consul-k8s/issues/3139)] -* Upgrade to use Go 1.20.10 and `x/net` 0.17.0. -This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) -/ [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3085](https://github.com/hashicorp/consul-k8s/issues/3085)] - ## 1.0.11 (November 2, 2023) SECURITY: @@ -186,76 +9,16 @@ This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CV This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) / [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3085](https://github.com/hashicorp/consul-k8s/issues/3085)] -## 1.2.2 (September 21, 2023) - -SECURITY: - -* Upgrade to use Go 1.20.8. This resolves CVEs - [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), - [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), - [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), - [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and - [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2936](https://github.com/hashicorp/consul-k8s/issues/2936)] - -FEATURES: - -* Add support for new observability service principal in cloud preset [[GH-2958](https://github.com/hashicorp/consul-k8s/issues/2958)] -* helm: Add ability to configure resource requests and limits for Gateway API deployments. [[GH-2723](https://github.com/hashicorp/consul-k8s/issues/2723)] - -IMPROVEMENTS: - -* Add NET_BIND_SERVICE capability to restricted security context used for consul-dataplane [[GH-2787](https://github.com/hashicorp/consul-k8s/issues/2787)] -* Add new value `global.argocd.enabled`. Set this to `true` when using ArgoCD to deploy this chart. [[GH-2785](https://github.com/hashicorp/consul-k8s/issues/2785)] -* Add support for running on GKE Autopilot. [[GH-2952](https://github.com/hashicorp/consul-k8s/issues/2952)] -* api-gateway: reduce log output when disconnecting from consul server [[GH-2880](https://github.com/hashicorp/consul-k8s/issues/2880)] -* control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. [[GH-2910](https://github.com/hashicorp/consul-k8s/issues/2910)] -* control-plane: prevent updation of anonymous-token-policy and anonymous-token if anonymous-token-policy is already attached to the anonymous-token [[GH-2790](https://github.com/hashicorp/consul-k8s/issues/2790)] -* helm: Add `JWKSCluster` field to `JWTProvider` CRD. [[GH-2881](https://github.com/hashicorp/consul-k8s/issues/2881)] -* vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to - secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. - This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] - -BUG FIXES: - -* audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. [[GH-2905](https://github.com/hashicorp/consul-k8s/issues/2905)] -* bug: Remove `global.acls.nodeSelector` and `global.acls.annotations` from Gateway Resources Jobs [[GH-2869](https://github.com/hashicorp/consul-k8s/issues/2869)] -* control-plane: Fix issue where ACL tokens would have an empty pod name that prevented proper token cleanup. [[GH-2808](https://github.com/hashicorp/consul-k8s/issues/2808)] -* control-plane: When using transparent proxy or CNI, reduced required permissions by setting privileged to false. Privileged must be true when using OpenShift without CNI. [[GH-2755](https://github.com/hashicorp/consul-k8s/issues/2755)] -* helm: Update prometheus port and scheme annotations if tls is enabled [[GH-2782](https://github.com/hashicorp/consul-k8s/issues/2782)] -* ingress-gateway: Adds missing PassiveHealthCheck to IngressGateways CRD and updates missing fields on ServiceDefaults CRD [[GH-2796](https://github.com/hashicorp/consul-k8s/issues/2796)] - -## 1.1.6 (September 21, 2023) - -SECURITY: - -* Upgrade to use Go 1.20.8. This resolves CVEs - [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), - [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), - [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), - [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and - [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2936](https://github.com/hashicorp/consul-k8s/issues/2936)] - -IMPROVEMENTS: - -* control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. [[GH-2910](https://github.com/hashicorp/consul-k8s/issues/2910)] -* vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to - secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. - This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] - -BUG FIXES: - -* audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. [[GH-2905](https://github.com/hashicorp/consul-k8s/issues/2905)] - ## 1.0.10 (September 21, 2023) SECURITY: * Upgrade to use Go 1.19.13. This resolves CVEs - [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), - [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), - [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), - [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and - [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2938](https://github.com/hashicorp/consul-k8s/issues/2938)] +[CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), +[CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), +[CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), +[CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and +[CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2938](https://github.com/hashicorp/consul-k8s/issues/2938)] IMPROVEMENTS: @@ -264,8 +27,8 @@ IMPROVEMENTS: * control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. [[GH-2910](https://github.com/hashicorp/consul-k8s/issues/2910)] * control-plane: prevent updation of anonymous-token-policy and anonymous-token if anonymous-token-policy is already attached to the anonymous-token [[GH-2790](https://github.com/hashicorp/consul-k8s/issues/2790)] * vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to - secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. - This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] +secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. +This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] BUG FIXES: @@ -274,106 +37,15 @@ BUG FIXES: * control-plane: When using transparent proxy or CNI, reduced required permissions by setting privileged to false. Privileged must be true when using OpenShift without CNI. [[GH-2755](https://github.com/hashicorp/consul-k8s/issues/2755)] * helm: Update prometheus port and scheme annotations if tls is enabled [[GH-2782](https://github.com/hashicorp/consul-k8s/issues/2782)] -## 1.2.1 (Aug 10, 2023) -BREAKING CHANGES: - -* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] - -SECURITY: - -* Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. - This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2642](https://github.com/hashicorp/consul-k8s/issues/2642)] -* Upgrade to use Go 1.20.7 and `x/net` 0.13.0. - This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) - and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2710](https://github.com/hashicorp/consul-k8s/issues/2710)] - -FEATURES: - -* Add support for configuring graceful shutdown proxy lifecycle management settings. [[GH-2233](https://github.com/hashicorp/consul-k8s/issues/2233)] -* api-gateway: adds ability to map privileged ports on Gateway listeners to unprivileged ports so that containers do not require additional privileges [[GH-2707](https://github.com/hashicorp/consul-k8s/issues/2707)] -* api-gateway: support deploying to OpenShift 4.11 [[GH-2184](https://github.com/hashicorp/consul-k8s/issues/2184)] -* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] -* sync-catalog: add ability to support weighted loadbalancing by service annotation `consul.hashicorp.com/service-weight: ` [[GH-2293](https://github.com/hashicorp/consul-k8s/issues/2293)] - -IMPROVEMENTS: - -* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2370](https://github.com/hashicorp/consul-k8s/issues/2370)] -* (api-gateway) make API gateway controller less verbose [[GH-2524](https://github.com/hashicorp/consul-k8s/issues/2524)] -* Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields -1. `global.acls.logLevel` -2. `global.tls.logLevel` -3. `global.federation.logLevel` -4. `global.gossipEncryption.logLevel` -5. `server.logLevel` -6. `client.logLevel` -7. `meshGateway.logLevel` -8. `ingressGateways.logLevel` -9. `terminatingGateways.logLevel` -10. `telemetryCollector.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] -* control-plane: increase timeout after login for ACL replication to 60 seconds [[GH-2656](https://github.com/hashicorp/consul-k8s/issues/2656)] -* helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. [[GH-2525](https://github.com/hashicorp/consul-k8s/issues/2525)] -* helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled [[GH-2572](https://github.com/hashicorp/consul-k8s/issues/2572)] -* helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.2.0` [[GH-2476](https://github.com/hashicorp/consul-k8s/issues/2476)] -* helm: update `image` value to `hashicorp/consul:1.16.0` [[GH-2476](https://github.com/hashicorp/consul-k8s/issues/2476)] - -BUG FIXES: - -* api-gateway: Fix creation of invalid Kubernetes Service when multiple Gateway listeners have the same port. [[GH-2413](https://github.com/hashicorp/consul-k8s/issues/2413)] -* api-gateway: fix helm install when setting copyAnnotations or nodeSelector [[GH-2597](https://github.com/hashicorp/consul-k8s/issues/2597)] -* api-gateway: fixes bug where envoy will silently reject RSA keys less than 2048 bits in length when not in FIPS mode, and - will reject keys that are not 2048, 3072, or 4096 bits in length in FIPS mode. We now validate - and reject invalid certs earlier. [[GH-2478](https://github.com/hashicorp/consul-k8s/issues/2478)] -* api-gateway: set route condition appropriately when parent ref includes non-existent section name [[GH-2420](https://github.com/hashicorp/consul-k8s/issues/2420)] -* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] -* control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] -* helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] -* helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] -* transparent-proxy: Fix issue where connect-inject lacked sufficient `mesh:write` privileges in some deployments, - which prevented virtual IPs from persisting properly. [[GH-2520](https://github.com/hashicorp/consul-k8s/issues/2520)] - -## 1.1.4 (Aug 10, 2023) - -SECURITY: - -* Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. - This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2642](https://github.com/hashicorp/consul-k8s/issues/2642)] -* Upgrade to use Go 1.20.7 and `x/net` 0.13.0. - This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) - and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2710](https://github.com/hashicorp/consul-k8s/issues/2710)] - -IMPROVEMENTS: - -* Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields -1. `global.acls.logLevel` -2. `global.tls.logLevel` -3. `global.federation.logLevel` -4. `global.gossipEncryption.logLevel` -5. `server.logLevel` -6. `client.logLevel` -7. `meshGateway.logLevel` -8. `ingressGateways.logLevel` -9. `terminatingGateways.logLevel` -10. `telemetryCollector.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] -* control-plane: increase timeout after login for ACL replication to 60 seconds [[GH-2656](https://github.com/hashicorp/consul-k8s/issues/2656)] -* helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. [[GH-2525](https://github.com/hashicorp/consul-k8s/issues/2525)] -* helm: do not set container securityContexts by default on OpenShift < 4.11 [[GH-2678](https://github.com/hashicorp/consul-k8s/issues/2678)] -* helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled [[GH-2572](https://github.com/hashicorp/consul-k8s/issues/2572)] - -BUG FIXES: - -* control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] -* helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] -* helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] - ## 1.0.9 (Aug 10, 2023) SECURITY: * Upgrade to use Go 1.19.11 and `x/net/http` 0.12.0. - This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2650](https://github.com/hashicorp/consul-k8s/issues/2650)] +This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2650](https://github.com/hashicorp/consul-k8s/issues/2650)] * Upgrade to use Go 1.19.12 and `x/net` 0.13.0. - This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) - and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2717](https://github.com/hashicorp/consul-k8s/issues/2717)] +This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) +and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2717](https://github.com/hashicorp/consul-k8s/issues/2717)] IMPROVEMENTS: @@ -398,86 +70,6 @@ BUG FIXES: * helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] * helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] -## 0.49.8 (July 12, 2023) - -IMPROVEMENTS: - -* helm: Add `connectInject.prepareDataplanesUpgrade` setting for help upgrading to dataplanes. This setting is required if upgrading from non-dataplanes to dataplanes when ACLs are enabled. See https://developer.hashicorp.com/consul/docs/k8s/upgrade#upgrading-to-consul-dataplane for more information. [[GH-2514](https://github.com/hashicorp/consul-k8s/issues/2514)] - -## 1.2.0 (June 28, 2023) - -FEATURES: - -* Add support for configuring Consul server-side rate limiting [[GH-2166](https://github.com/hashicorp/consul-k8s/issues/2166)] -* api-gateway: Add API Gateway for Consul on Kubernetes leveraging Consul native API Gateway configuration. [[GH-2152](https://github.com/hashicorp/consul-k8s/issues/2152)] -* crd: Add `mutualTLSMode` to the ProxyDefaults and ServiceDefaults CRDs and `allowEnablingPermissiveMutualTLS` to the Mesh CRD to support configuring permissive mutual TLS. [[GH-2100](https://github.com/hashicorp/consul-k8s/issues/2100)] -* helm: Add `JWTProvider` CRD for configuring the `jwt-provider` config entry. [[GH-2209](https://github.com/hashicorp/consul-k8s/issues/2209)] -* helm: Update the ServiceIntentions CRD to support `JWT` fields. [[GH-2213](https://github.com/hashicorp/consul-k8s/issues/2213)] - -IMPROVEMENTS: - -* cli: update minimum go version for project to 1.20. [[GH-2102](https://github.com/hashicorp/consul-k8s/issues/2102)] -* control-plane: add FIPS support [[GH-2165](https://github.com/hashicorp/consul-k8s/issues/2165)] -* control-plane: server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/issues/1770)] -* control-plane: set agent localities on Consul servers to the server node's `topology.kubernetes.io/region` label. [[GH-2093](https://github.com/hashicorp/consul-k8s/issues/2093)] -* control-plane: update alpine to 3.17 in the Docker image. [[GH-1934](https://github.com/hashicorp/consul-k8s/issues/1934)] -* control-plane: update minimum go version for project to 1.20. [[GH-2102](https://github.com/hashicorp/consul-k8s/issues/2102)] -* helm: Kubernetes v1.27 is now supported. Minimum tested version of Kubernetes is now v1.24. [[GH-2304](https://github.com/hashicorp/consul-k8s/issues/2304)] -* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] -* helm: add failover policy field to service resolver and proxy default CRDs [[GH-2030](https://github.com/hashicorp/consul-k8s/issues/2030)] -* helm: add samenessGroup CRD [[GH-2048](https://github.com/hashicorp/consul-k8s/issues/2048)] -* helm: add samenessGroup field to exported services CRD [[GH-2075](https://github.com/hashicorp/consul-k8s/issues/2075)] -* helm: add samenessGroup field to service resolver CRD [[GH-2086](https://github.com/hashicorp/consul-k8s/issues/2086)] -* helm: add samenessGroup field to source intention CRD [[GH-2097](https://github.com/hashicorp/consul-k8s/issues/2097)] -* helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.1.0`. [[GH-1953](https://github.com/hashicorp/consul-k8s/issues/1953)] - -SECURITY: - -* Update [Go-Discover](https://github.com/hashicorp/go-discover) in the container has been updated to address [CVE-2020-14040](https://github.com/advisories/GHSA-5rcv-m4m3-hfh7) [[GH-2390](https://github.com/hashicorp/consul-k8s/issues/2390)] -* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] -* Fix Prometheus CVEs by bumping controller-runtime. [[GH-2183](https://github.com/hashicorp/consul-k8s/issues/2183)] -* Upgrade to use Go 1.20.4. - This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), - [CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), - [CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and - [CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). - Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41721 - ](https://github.com/advisories/GHSA-fxg5-wq6x-vr4w - ), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 - ](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h - .) [[GH-2102](https://github.com/hashicorp/consul-k8s/issues/2102)] - -BUG FIXES: - -* control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. [[GH-2266](https://github.com/hashicorp/consul-k8s/issues/2266)] -* control-plane: fix issue where consul-connect-injector acl token was unintentionally being deleted and not recreated when a container was restarted due to a livenessProbe failure. [[GH-1914](https://github.com/hashicorp/consul-k8s/issues/1914)] - -## 1.1.3 (June 28, 2023) -BREAKING CHANGES: - -* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] - -SECURITY: - -* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] -* Update [Go-Discover](https://github.com/hashicorp/go-discover) in the container has been updated to address [CVE-2020-14040](https://github.com/advisories/GHSA-5rcv-m4m3-hfh7) [[GH-2390](https://github.com/hashicorp/consul-k8s/issues/2390)] - -FEATURES: - -* Add support for configuring graceful shutdown proxy lifecycle management settings. [[GH-2233](https://github.com/hashicorp/consul-k8s/issues/2233)] -* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] -* sync-catalog: add ability to support weighted loadbalancing by service annotation `consul.hashicorp.com/service-weight: ` [[GH-2293](https://github.com/hashicorp/consul-k8s/issues/2293)] - -IMPROVEMENTS: - -* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2369](https://github.com/hashicorp/consul-k8s/issues/2369)] -* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] - -BUG FIXES: - -* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] -* control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. [[GH-2266](https://github.com/hashicorp/consul-k8s/issues/2266)] - ## 1.0.8 (June 28, 2023) BREAKING CHANGES: @@ -510,71 +102,6 @@ BUG FIXES: * control-plane: fix issue with multiport pods crashlooping due to dataplane port conflicts by ensuring dns redirection is disabled for non-tproxy pods [[GH-2176](https://github.com/hashicorp/consul-k8s/issues/2176)] * crd: fix bug on service intentions CRD causing some updates to be ignored. [[GH-2194](https://github.com/hashicorp/consul-k8s/issues/2194)] - -## 0.49.7 (June 28, 2023) -BREAKING CHANGES: - -* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] - -SECURITY: - -* Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.2`. [[GH-2204](https://github.com/hashicorp/consul-k8s/issues/2204)] -* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] - -FEATURES: - -* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] - -IMPROVEMENTS: - -* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2265](https://github.com/hashicorp/consul-k8s/issues/2265)] -* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] - -BUG FIXES: - -* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] -* crd: fix bug on service intentions CRD causing some updates to be ignored. [[GH-2194](https://github.com/hashicorp/consul-k8s/issues/2194)] - -## 1.1.2 (June 5, 2023) - -SECURITY: - -* Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.2`. [[GH-2204](https://github.com/hashicorp/consul-k8s/issues/2204)] -* Bump `controller-runtime` to address CVEs in dependencies. [[GH-2226](https://github.com/hashicorp/consul-k8s/issues/2226)] -* Upgrade to use Go 1.20.4. -This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), -[CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), -[CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and -[CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). -Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41721 -](https://github.com/advisories/GHSA-fxg5-wq6x-vr4w -), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 -](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h -.) [[GH-2104](https://github.com/hashicorp/consul-k8s/issues/2104)] - -FEATURES: - -* Add support for consul-telemetry-collector to forward envoy metrics to an otelhttp compatible receiver or HCP [[GH-2134](https://github.com/hashicorp/consul-k8s/issues/2134)] -* consul-telemetry-collector: Configure envoy proxy config during registration when consul-telemetry-collector is enabled. [[GH-2143](https://github.com/hashicorp/consul-k8s/issues/2143)] -* sync-catalog: add ability to sync hostname from a Kubernetes Ingress resource to the Consul Catalog during service registration. [[GH-2098](https://github.com/hashicorp/consul-k8s/issues/2098)] - -IMPROVEMENTS: - -* cli: Add `consul-k8s config read` command that returns the helm configuration in yaml format. [[GH-2078](https://github.com/hashicorp/consul-k8s/issues/2078)] -* cli: add consul-telemetry-gateway allow-all intention for -demo [[GH-2262](https://github.com/hashicorp/consul-k8s/issues/2262)] -* cli: update cloud preset to enable telemetry collector [[GH-2205](https://github.com/hashicorp/consul-k8s/issues/2205)] -* consul-telemetry-collector: add acceptance tests for consul telemetry collector component [[GH-2195](https://github.com/hashicorp/consul-k8s/issues/2195)] - -BUG FIXES: - -* crd: fix bug on service intentions CRD causing some updates to be ignored. [[GH-2194](https://github.com/hashicorp/consul-k8s/issues/2083)] -* api-gateway: fix issue where the API Gateway controller is unable to start up successfully when Vault is configured as the secrets backend [[GH-2083](https://github.com/hashicorp/consul-k8s/issues/2083)] -* control-plane: add support for idleTimeout in the Service Router config [[GH-2156](https://github.com/hashicorp/consul-k8s/issues/2156)] -* control-plane: fix issue with json tags of service defaults fields EnforcingConsecutive5xx, MaxEjectionPercent and BaseEjectionTime. [[GH-2160](https://github.com/hashicorp/consul-k8s/issues/2160)] -* control-plane: fix issue with multiport pods crashlooping due to dataplane port conflicts by ensuring dns redirection is disabled for non-tproxy pods [[GH-2176](https://github.com/hashicorp/consul-k8s/issues/2176)] -* helm: add missing `$HOST_IP` environment variable to to mesh gateway deployments. [[GH-1808](https://github.com/hashicorp/consul-k8s/issues/1808)] -* sync-catalog: fix issue where the sync-catalog ACL token were set with an incorrect ENV VAR. [[GH-2068](https://github.com/hashicorp/consul-k8s/issues/2068)] - ## 1.0.7 (May 17, 2023) SECURITY: @@ -606,37 +133,6 @@ BUG FIXES: * helm: add missing `$HOST_IP` environment variable to to mesh gateway deployments. [[GH-1808](https://github.com/hashicorp/consul-k8s/issues/1808)] * sync-catalog: fix issue where the sync-catalog ACL token were set with an incorrect ENV VAR. [[GH-2068](https://github.com/hashicorp/consul-k8s/issues/2068)] -## 0.49.6 (May 17, 2023) - -SECURITY: - -* Upgrade to use Go 1.19.9. -This resolves vulnerabilities [CVE-2023-24537](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`go/scanner`), -[CVE-2023-24538](https://github.com/advisories/GHSA-v4m2-x4rp-hv22)(`html/template`), -[CVE-2023-24534](https://github.com/advisories/GHSA-8v5j-pwr7-w5f8)(`net/textproto`) and -[CVE-2023-24536](https://github.com/advisories/GHSA-9f7g-gqwh-jpf5)(`mime/multipart`). -Also, `golang.org/x/net` has been updated to v0.7.0 to resolve CVEs [CVE-2022-41721 -](https://github.com/advisories/GHSA-fxg5-wq6x-vr4w -), [CVE-2022-27664](https://github.com/advisories/GHSA-69cg-p879-7622) and [CVE-2022-41723 -](https://github.com/advisories/GHSA-vvpx-j8f3-3w6h -.) [[GH-2110](https://github.com/hashicorp/consul-k8s/issues/2110)] - -IMPROVEMENTS: - -* helm: Set default `limits.cpu` resource setting to `null` for `consul-connect-inject-init` container to speed up registration times when onboarding services onto the mesh during the init container lifecycle. [[GH-2008](https://github.com/hashicorp/consul-k8s/issues/2008)] - -## 1.1.1 (March 31, 2023) - -IMPROVEMENTS: - -* helm: Set default `limits.cpu` resource setting to `null` for `consul-connect-inject-init` container to speed up registration times when onboarding services onto the mesh during the init container lifecycle. [[GH-2008](https://github.com/hashicorp/consul-k8s/issues/2008)] -* helm: When the `global.acls.bootstrapToken` field is set and the content of the secret is empty, the bootstrap ACL token is written to that secret after bootstrapping ACLs. This applies to both the Vault and Consul secrets backends. [[GH-1920](https://github.com/hashicorp/consul-k8s/issues/1920)] - -BUG FIXES: - -* api-gateway: fix ACL issue where when adminPartitions and ACLs are enabled, API Gateway Controller is unable to create a new namespace in Consul [[GH-2029](https://github.com/hashicorp/consul-k8s/issues/2029)] -* api-gateway: fix issue where specifying an external server SNI name while using client nodes resulted in a TLS verification error. [[GH-2013](https://github.com/hashicorp/consul-k8s/issues/2013)] - ## 1.0.6 (March 20, 2023) IMPROVEMENTS: @@ -659,24 +155,7 @@ IMPROVEMENTS: * control-plane: update alpine to 3.17 in the Docker image. [[GH-1934](https://github.com/hashicorp/consul-k8s/issues/1934)] * helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.1.0`. [[GH-1953](https://github.com/hashicorp/consul-k8s/issues/1953)] -## 0.49.5 (March 9, 2023) - -SECURITY: - -* upgrade to use Go 1.19.6. This resolves vulnerabilities CVE-2022-41724 in crypto/tls and CVE-2022-41723 in net/http. [[GH-1975](https://github.com/hashicorp/consul-k8s/issues/1975)] - -IMPROVEMENTS: - -* cli: update minimum go version for project to 1.19. [[GH-1975](https://github.com/hashicorp/consul-k8s/issues/1975)] -* control-plane: server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/issues/1770)] -* control-plane: update alpine to 3.17 in the Docker image. [[GH-1934](https://github.com/hashicorp/consul-k8s/issues/1934)] -* control-plane: update minimum go version for project to 1.19. [[GH-1975](https://github.com/hashicorp/consul-k8s/issues/1975)] - -BUG FIXES: - -* control-plane: fix issue where consul-connect-injector acl token was unintentionally being deleted and not recreated when a container was restarted due to a livenessProbe failure. [[GH-1914](https://github.com/hashicorp/consul-k8s/issues/1914)] - -## 1.1.0 (February 27, 2023) +## 1.0.4 (February 7, 2023) BREAKING CHANGES: * Helm: @@ -692,36 +171,10 @@ BREAKING CHANGES: values: ["kube-system","local-path-storage"] ``` [[GH-1869](https://github.com/hashicorp/consul-k8s/pull/1869)] - + IMPROVEMENTS: -* Helm: - * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] - * Kubernetes v1.26 is now supported. Minimum tested version of Kubernetes is now v1.23. [[GH-1852](https://github.com/hashicorp/consul-k8s/pull/1852)] - * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] - * Add the `accessLogs` field to the `ProxyDefaults` CRD. [[GH-1816](https://github.com/hashicorp/consul-k8s/pull/1816)] - * Add the `envoyExtensions` field to the `ProxyDefaults` and `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) - * Add the `balanceInboundConnections` field to the `ServiceDefaults` CRD. [[GH-1823]](https://github.com/hashicorp/consul-k8s/pull/1823) - * Add the `upstreamConfig.overrides[].peer` field to the `ServiceDefaults` CRD. [[GH-1853]](https://github.com/hashicorp/consul-k8s/pull/1853) -* Control-Plane - * Update minimum go version for project to 1.20 [[GH-1908](https://github.com/hashicorp/consul-k8s/pull/1908)] - * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1841](https://github.com/hashicorp/consul-k8s/pull/1841)] - * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] - * Remove extraneous `gnupg` dependency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] - * Server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/pull/1770)] - * Update alpine to 3.17 in the Docker image. [[GH-1934](https://github.com/hashicorp/consul-k8s/pull/1934)] -* CLI: - * Update minimum go version for project to 1.20 [[GH-1908](https://github.com/hashicorp/consul-k8s/pull/1908)] - * Add `consul-k8s proxy log podname` command for displaying and modifying Envoy log levels for a given Pod. [GH-1844](https://github.com/hashicorp/consul-k8s/pull/1844), [GH-1849](https://github.com/hashicorp/consul-k8s/pull/1849), [GH-1864](https://github.com/hashicorp/consul-k8s/pull/1864) - -BUG FIXES: -* Control Plane - * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] - * Add discover binary to control-plane image [[GH-1749](https://github.com/hashicorp/consul-k8s/pull/1749)] -* Helm: - * Don't pass in a CA file to the API Gateway controller when `externalServers.useSystemRoots` is `true`. [[GH-1743](https://github.com/hashicorp/consul-k8s/pull/1743)] - * Use the correct autogenerated cert for the API Gateway Controller when connecting to servers versus clients. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] -* Security: - * Upgrade to use Go 1.20.1 This resolves vulnerabilities [CVE-2022-41724](https://go.dev/issue/58001) in `crypto/tls` and [CVE-2022-41723](https://go.dev/issue/57855) in `net/http`. [[GH-1908](https://github.com/hashicorp/consul-k8s/pull/1908)] + * Control Plane + * Remove extraneous `gnupg` dependency from `consul-k8s-control-plane` since it is no longer needed for validating binary artifacts prior to release. [[GH-1882](https://github.com/hashicorp/consul-k8s/pull/1882)] ## 1.0.3 (January 30, 2023) @@ -737,42 +190,19 @@ BUG FIXES: * Control Plane * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] -## 0.49.3 (January 30, 2023) - -IMPROVEMENTS: -* Helm: - * Add a `global.extraLabels` stanza to allow setting global Kubernetes labels for all components deployed by the `consul-k8s` Helm chart. [[GH-1778](https://github.com/hashicorp/consul-k8s/pull/1778)] -* Control-Plane - * Add support for the annotation `consul.hashicorp.com/use-proxy-health-check`. When this annotation is used by a service, it configures a readiness endpoint on Consul Dataplane and queries it instead of the proxy's inbound port which forwards requests to the application. [[GH-1824](https://github.com/hashicorp/consul-k8s/pull/1824)], [[GH-1843](https://github.com/hashicorp/consul-k8s/pull/1843)] - * Add health check for synced services based on the status of the Kubernetes readiness probe on synced pod. [[GH-1821](https://github.com/hashicorp/consul-k8s/pull/1821)] - -BUG FIXES: -* Control Plane - * Don't incorrectly diff intention config entries when upgrading from Consul pre-1.12 to 1.12+ [[GH-1804](https://github.com/hashicorp/consul-k8s/pull/1804)] - ## 1.0.2 (December 1, 2022) IMPROVEMENTS: * Helm: * CNI: Add `connectInject.cni.namespace` stanza which allows the CNI plugin resources to be deployed in a namespace other than the namespace that Consul is installed. [[GH-1756](https://github.com/hashicorp/consul-k8s/pull/1756)] +* Control Plane: + * Server ACL Init always appends both, the secrets from the serviceAccount's secretRefs and the one created by the Helm chart, to support Openshift secret handling. [[GH-1770](https://github.com/hashicorp/consul-k8s/pull/1770)] BUG FIXES: * Helm: * Use the correct autogenerated cert for the API Gateway Controller when connecting to servers versus clients. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] * Don't mount the CA cert when `externalServers.useSystemRoots` is `true`. [[GH-1753](https://github.com/hashicorp/consul-k8s/pull/1753)] -## 0.49.2 (December 1, 2022) - -IMPROVEMENTS: -* Control Plane - * Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.1`. [[GH-1725](https://github.com/hashicorp/consul-k8s/pull/1725)] -* Helm - * Add fields `localConnectTimeoutMs` and `localRequestTimeoutMs` to the `ServiceDefaults` CRD. [[GH-1647](https://github.com/hashicorp/consul-k8s/pull/1647)] - -BUG FIXES: -* Helm: - * Disable PodSecurityPolicies templating for `gossip-encryption-autogenerate` and `partition-init` when `global.enablePodSecurityPolicies` is `false`. [[GH-1693](https://github.com/hashicorp/consul-k8s/pull/1693)] - ## 1.0.1 (November 21, 2022) BUG FIXES: @@ -875,6 +305,7 @@ IMPROVEMENTS: * API Gateway: Allow controller to read MeshServices for use as a route backend. [[GH-1574](https://github.com/hashicorp/consul-k8s/pull/1574)] * API Gateway: Add support for using dynamic server discovery strings when running without agents. [[GH-1732](https://github.com/hashicorp/consul-k8s/pull/1732)] + BUG FIXES: * CLI * Allow optional environment variables for use in the cloud preset to the CLI for cluster bootstrapping. [[GH-1608](https://github.com/hashicorp/consul-k8s/pull/1608)] @@ -883,24 +314,7 @@ BUG FIXES: * Peering * Add `peering:read` permissions to mesh gateway token to fix peering connections through the mesh gateways. [[GH-1685](https://github.com/hashicorp/consul-k8s/pull/1685)] * Helm: - * Disable PodSecurityPolicies in all templates when `global.enablePodSecurityPolicies` is `false`. [[GH-1693](https://github.com/hashicorp/consul-k8s/pull/1693)] - -## 0.49.1 (November 14, 2022) -BREAKING CHANGES: -* Peering: - * Rename `PeerName` to `Peer` in ExportedServices CRD. [[GH-1596](https://github.com/hashicorp/consul-k8s/pull/1596)] - -FEATURES: -* Ingress Gateway - * Add support for MaxConnections, MaxConcurrentRequests, and MaxPendingRequests to Ingress Gateway CRD. [[GH-1691](https://github.com/hashicorp/consul-k8s/pull/1691)] - -IMPROVEMENTS: -* Helm: - * Add `tolerations` and `nodeSelector` to Server ACL init jobs and `nodeSelector` to Webhook cert manager. [[GH-1581](https://github.com/hashicorp/consul-k8s/pull/1581)] - * API Gateway: Allow controller to read MeshServices for use as a route backend. [[GH-1574](https://github.com/hashicorp/consul-k8s/pull/1574)] - * API Gateway: Add `tolerations` to `apiGateway.managedGatewayClass` and `apiGateway.controller` [[GH-1650](https://github.com/hashicorp/consul-k8s/pull/1650)] - * API Gateway: Create PodSecurityPolicy for controller when `global.enablePodSecurityPolicies=true`. [[GH-1656](https://github.com/hashicorp/consul-k8s/pull/1656)] - * API Gateway: Create PodSecurityPolicy and allow controller to bind it to ServiceAccounts that it creates for Gateway Deployments when `global.enablePodSecurityPolicies=true`. [[GH-1672](https://github.com/hashicorp/consul-k8s/pull/1672)] + * Disable PodSecurityPolicies templating for `gossip-encryption-autogenerate` and `partition-init` when `global.enablePodSecurityPolicies` is `false`. [[GH-1693](https://github.com/hashicorp/consul-k8s/pull/1693)] ## 0.49.0 (September 29, 2022) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b06c27d8a..4fb322799b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ 1. [Running linters locally](#running-linters-locally) 1. [Rebasing contributions against main](#rebasing-contributions-against-main) 1. [Creating a new CRD](#creating-a-new-crd) - 1. [The Structs](#the-structs) + 1. [The Structs](#the-structs) 1. [Spec Methods](#spec-methods) 1. [Spec Tests](#spec-tests) 1. [Controller](#controller) @@ -22,22 +22,21 @@ 1. [Running the tests](#running-the-tests) 1. [Writing Unit tests](#writing-unit-tests) 1. [Writing Acceptance tests](#writing-acceptance-tests) -1. [Using the Acceptance Test Framework to Debug](#using-acceptance-test-framework-to-debug) 1. [Helm Reference Docs](#helm-reference-docs) -1. [Managing External CRD Dependencies](#managing-external-crd-dependencies) 1. [Adding a Changelog Entry](#adding-a-changelog-entry) + ## Contributing 101 ### Building and running `consul-k8s-control-plane` -To build and install the control plane binary `consul-k8s-control-plane` locally, Go version 1.17.0+ is required. +To build and install the control plane binary `consul-k8s-control-plane` locally, Go version 1.17.0+ is required. You will also need to install the Docker engine: - [Docker for Mac](https://docs.docker.com/engine/installation/mac/) - [Docker for Windows](https://docs.docker.com/engine/installation/windows/) - [Docker for Linux](https://docs.docker.com/engine/installation/linux/ubuntulinux/) - + Install [gox](https://github.com/mitchellh/gox) (v1.14+). For Mac and Linux: ```bash brew install gox @@ -102,7 +101,7 @@ controller: enabled: true ``` -Run a `helm install` from the project root directory to target your dev version of the Helm chart. +Run a `helm install` from the project root directory to target your dev version of the Helm chart. ```shell helm install consul --create-namespace -n consul -f ./values.dev.yaml ./charts/consul @@ -125,7 +124,7 @@ consul-k8s version ### Making changes to consul-k8s -The first step to making changes is to fork Consul K8s. Afterwards, the easiest way +The first step to making changes is to fork Consul K8s. Afterwards, the easiest way to work on the fork is to set it as a remote of the Consul K8s project: 1. Rename the existing remote's name: `git remote rename origin upstream`. @@ -164,46 +163,46 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ## Creating a new CRD ### The Structs -1. Run the generate command from the `control-plane` directory: (installation instructions for `operator-sdk` found [here](https://sdk.operatorframework.io/docs/installation/): +1. Run the generate command: ```bash operator-sdk create api --group consul --version v1alpha1 --kind IngressGateway --controller --namespaced=true --make=false --resource=true ``` -1. Re-order the generated ingressgateway_types.go file, so it looks like: +1. Re-order the file so it looks like: ```go func init() { SchemeBuilder.Register(&IngressGateway{}, &IngressGatewayList{}) } - + // +kubebuilder:object:root=true // +kubebuilder:subresource:status - + // IngressGateway is the Schema for the ingressgateways API type IngressGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - + Spec IngressGatewaySpec `json:"spec,omitempty"` Status IngressGatewayStatus `json:"status,omitempty"` } - + // +kubebuilder:object:root=true - + // IngressGatewayList contains a list of IngressGateway type IngressGatewayList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []IngressGateway `json:"items"` } - + // IngressGatewaySpec defines the desired state of IngressGateway type IngressGatewaySpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - + // Foo is an example field of IngressGateway. Edit IngressGateway_types.go to remove/update Foo string `json:"foo,omitempty"` } - + // IngressGatewayStatus defines the observed state of IngressGateway type IngressGatewayStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster @@ -214,7 +213,6 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ```go // ServiceRouter is the Schema for the servicerouters API // +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" - // +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" type ServiceRouter struct { ``` @@ -225,7 +223,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca type IngressGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - + Spec IngressGatewaySpec `json:"spec,omitempty"` - Status IngressGatewayStatus `json:"status,omitempty"` + Status `json:"status,omitempty"` @@ -233,9 +231,9 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ``` 1. Go to the Consul `api` package for the config entry, e.g. https://github.com/hashicorp/consul/blob/main/api/config_entry_gateways.go 1. Copy the top-level fields over into the `Spec` struct except for - `Kind`, `Name`, `Namespace`, `Partition`, `Meta`, `CreateIndex` and `ModifyIndex`. In this + `Kind`, `Name`, `Namespace`, `Meta`, `CreateIndex` and `ModifyIndex`. In this example, the top-level fields remaining are `TLS` and `Listeners`: - + ```go // IngressGatewaySpec defines the desired state of IngressGateway type IngressGatewaySpec struct { @@ -261,8 +259,8 @@ rebase the branch on main, fixing any conflicts along the way before the code ca automatically stub out all the methods by using Code -> Generate -> IngressGateway -> ConfigEntryResource. 1. Use existing implementations of other types to implement the methods. We have to copy their code because we can't use a common struct that implements the methods - because that messes up the CRD code generation. - + because that messes up the CRD code generation. + You should be able to follow the other "normal" types. The non-normal types are `ServiceIntention` and `ProxyDefault` because they have special behaviour around being global or their spec not matching up with Consul's directly. @@ -273,7 +271,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca 1. For `Validate`, we again follow the pattern of implementing the method on each sub-struct. You'll need to read the Consul documentation to understand what validation needs to be done. - + Things to keep in mind: 1. Re-use the `sliceContains` and `notInSliceMessage` helper methods where applicable. 1. If the invalid field is an entire struct, encode as json (look for `asJSON` for an example). @@ -322,6 +320,8 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ### Controller 1. Delete the file `control-plane/controllers/suite_test.go`. We don't write suite tests, just unit tests. +1. Move `control-plane/controllers/ingressgateway_controller.go` to `control-plane/controller` directory. +1. Delete the `control-plane/controllers` directory. 1. Rename `Reconciler` to `Controller`, e.g. `IngressGatewayReconciler` => `IngressGatewayController` 1. Use the existing controller files as a guide and make this file match. 1. Add your controller as a case in the tests in `configentry_controller_test.go`: @@ -333,7 +333,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca 1. `TestConfigEntryControllers_doesNotCreateUnownedConfigEntry` 1. `TestConfigEntryControllers_doesNotDeleteUnownedConfig` 1. Note: we don't add tests to `configentry_controller_ent_test.go` because we decided - it's too much duplication and the controllers are already properly exercised in the oss tests. + it's too much duplication and the controllers are already properly exercised in the oss tests. ### Webhook 1. Copy an existing webhook to `control-plane/api/v1alpha/ingressgateway_webhook.go` @@ -376,7 +376,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ### Generating YAML 1. Run `make ctrl-manifests` to generate the CRD and webhook YAML. -1. Uncomment your CRD in `control-plane/config/crd/kustomization` under `patches:` +1. Uncomment your CRD in `control-plane/config/crd/kustomization` under `patchesStrategicMerge:` 1. Update the sample, e.g. `control-plane/config/samples/consul_v1alpha1_ingressgateway.yaml` to a valid resource that can be used for testing: ```yaml @@ -395,13 +395,13 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ``` ### Updating Helm chart -1. Update `charts/consul/templates/connect-inject-mutatingwebhookconfiguration` with the webhook for this resource +1. Update `charts/consul/templates/controller-mutatingwebhookconfiguration` with the webhook for this resource using the updated `control-plane/config/webhook/manifests.v1beta1.yaml` and replacing `clientConfig.service.name/namespace` with the templated strings shown below to match the other webhooks.: ```yaml - clientConfig: service: - name: {{ template "consul.fullname" . }}-connect-injector + name: {{ template "consul.fullname" . }}-controller-webhook namespace: {{ .Release.Namespace }} path: /mutate-v1alpha1-ingressgateway failurePolicy: Fail @@ -421,31 +421,24 @@ rebase the branch on main, fixing any conflicts along the way before the code ca - ingressgateways sideEffects: None ``` -1. Update `charts/consul/templates/connect-inject-clusterrole.yaml` to allow the controller to +1. Update `charts/consul/templates/controller-clusterrole.yaml` to allow the controller to manage your resource type. ### Testing A New CRD -1. Build a Docker image for consul-k8s via `make control-plane-dev-docker` and push to a docker repository: - ``` - docker tag consul-k8s-control-plane-dev /consul-k8s-control-plane-dev: - docker push /consul-k8s-control-plane-dev: - ``` +1. Build a Docker image for consul-k8s via `make dev-docker` and tagging your image appropriately. Remember to CD into the `control-plane` directory! 1. Install using the updated Helm repository, with a values like: ```yaml global: - imageK8S: lkysow/consul-k8s-control-plane-dev:nov26 + imageK8S: ghcr.io/lkysow/consul-k8s-dev:nov26 name: consul server: replicas: 1 bootstrapExpect: 1 - ui: - enabled: true - connectInject: + controller: enabled: true ``` -1. Create a sample CRD -1. Run `kubectl apply -f ` to apply your sample CRD. -1. Check its synced status (for example CRD called ingressgateway): +1. `kubectl apply` your sample CRD. +1. Check its synced status: ```bash kubectl get ingressgateway NAME SYNCED AGE @@ -507,7 +500,7 @@ a token named `foo`. ``` * Add `if` statement in `Run` to create your token (follow placement of other tokens). You'll need to decide if you need a local token (use `createLocalACL()`) or a global token (use `createGlobalACL()`). - + ```go if c.flagCreateFooToken { err := c.createLocalACL("foo", fooRules, consulDC, isPrimary, consulClient) @@ -588,7 +581,7 @@ The acceptance tests require a Kubernetes cluster with a configured `kubectl`. ```bash brew install python-yq ``` -* [Helm 3](https://helm.sh) (Currently, must use v3.8.0+.) +* [Helm 3](https://helm.sh) (Currently, must use v3.8.0+.) ```bash brew install kubernetes-helm ``` @@ -617,7 +610,7 @@ To run a specific test by name use the `--filter` flag: bats ./charts/consul/test/unit/.bats --filter "my test name" #### Acceptance Tests -##### Pre-requisites +##### Pre-requisites * [gox](https://github.com/mitchellh/gox) (v1.14+) ```bash brew install gox @@ -629,7 +622,7 @@ To run the acceptance tests: cd acceptance/tests go test ./... -p 1 - + The above command will run all tests that can run against a single Kubernetes cluster, using the current context set in your kubeconfig locally. @@ -689,7 +682,7 @@ Changes to the Helm chart should be accompanied by appropriate unit tests. #### Formatting -- Put tests in the test file in the same order as the variables appear in the `values.yaml`. +- Put tests in the test file in the same order as the variables appear in the `values.yaml`. - Start tests for a chart value with a header that says what is being tested, like this: ``` #-------------------------------------------------------------------- @@ -710,8 +703,8 @@ In all of the tests in this repo, the base command being run is [helm template]( In this way, we're able to test that the various conditionals in the templates render as we would expect. Each test defines the files that should be rendered using the `-x` flag, then it might adjust chart values by adding `--set` flags as well. -The output from this `helm template` command is then piped to [yq](https://pypi.org/project/yq/). -`yq` allows us to pull out just the information we're interested in, either by referencing its position in the yaml file directly or giving information about it (like its length). +The output from this `helm template` command is then piped to [yq](https://pypi.org/project/yq/). +`yq` allows us to pull out just the information we're interested in, either by referencing its position in the yaml file directly or giving information about it (like its length). The `-r` flag can be used with `yq` to return a raw string instead of a quoted one which is especially useful when looking for an exact match. The test passes or fails based on the conditional at the end that is in square brackets, which is a comparison of our expected value and the output of `helm template` piped to `yq`. @@ -786,11 +779,11 @@ Here are some examples of common test patterns: cd `chart_dir` assert_empty helm template \ -s templates/sync-catalog-deployment.yaml \ - . + . } ``` Here we are using the `assert_empty` helper command. - + ### Writing Acceptance Tests If you are adding a feature that fits thematically with one of the existing test suites, @@ -831,9 +824,9 @@ you need to handle that in the `TestMain` function. ```go func TestMain(m *testing.M) { - // First, create a new suite so that all flags are parsed. + // First, create a new suite so that all flags are parsed. suite = framework.NewSuite(m) - + // Run the suite only if our example feature test flag is set. if suite.Config().EnableExampleFeature { os.Exit(suite.Run()) @@ -866,16 +859,16 @@ func TestExample(t *testing.T) { helmValues := map[string]string{ "exampleFeature.enabled": "true", } - - // Generate a random name for this test. + + // Generate a random name for this test. releaseName := helpers.RandomName() // Create a new Consul cluster object. consulCluster := framework.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - + // Create the Consul cluster with Helm. consulCluster.Create(t) - + // Make test assertions. } ``` @@ -941,205 +934,13 @@ Here are some things to consider before adding a test: --- -## Using Acceptance Test Framework to Debug -### Acceptance Tests - -The [consul-k8s](https://github.com/hashicorp/consul-k8s) repository has an extensive list of [acceptance](https://github.com/hashicorp/consul-k8s/tree/main/acceptance/tests) -tests that are used by CI to run per-PR and nightly acceptance tests. -It is built on its own framework that uses Helm and the consul-k8s CLI to deploy consul (and other tools) in various -configurations that provide test coverage for most features that exist and provides coverage for more advanced deployments -than are typically covered in guides. -Importantly, it is **automated**, so you are able to rapidly deploy known working -configurations in known working environments. -It can be very helpful for bootstrapping complex environments such as when using Vault as a CA for Consul or for federating test clusters. - -The tests are organized like this : -```shell -demo $ tree -L 1 -d acceptance/tests -acceptance/tests -├── basic -├── cli -├── config-entries -├── connect -├── consul-dns -├── example -├── fixtures -├── ingress-gateway -├── metrics -├── partitions -├── peering -├── snapshot-agent -├── sync -├── terminating-gateway -├── vault -└── wan-federation -``` - -### Basic Running of Tests -Any given test can be run either through GoLand or another IDE, or via command line using `go test -run`. - -To run all of the connect tests from command line: -```shell -$ cd acceptance/tests -$ go test ./connect/... -v -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-enterprise -enable-multi-cluster -debug-directory=/tmp/debug -consul-k8s-image=kyleschochenmaier/consul-k8s-acls -``` - -When running from command line a few things are important: -* Some tests use Enterprise features, in which case you need: - * Set environment variables `CONSUL_ENT_LICENSE` and possibly `VAULT_LICENSE`. - * Use `-enable-enterprise` on command line when running the test. -* Multi-cluster tests require `-enable-multi-cluster -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2` -* Using `.//...` is required as part of the command-line to pick up necessary environmental config. - -### Using the framework to debug in an environment -=> NOTE: It is helpful to tune the docker desktop resource settings so that docker has at least 4GB memory, plenty of cpu cores and 2GB of swap. - -* If using Kind, `-use-kind` should be added, and be sure you cluster is up and running: -```shell -$ kind create cluster --name=dc1 && kind create cluster --name=dc2 -``` -* Pick a test which replicates the environment you are wanting to work with. - Ex: pick a test from `partitions/` or `vault/` or `connect/`. -* If you need the environment to persist, add a `time.Sleep(1*time.Hour)` to the end of the test in the test file. -* Use the following flags if you need to use or test out a specific consul/k8s image: - `-consul-k8s-image=` && `-consul-image=` -* You can set custom helm flags by modifying the test file directly in the respective directory. - -Finally, run the test like shown above: -```shell -$ cd acceptance/tests -$ go test -run Vault_WANFederationViaGateways ./vault/... -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-multi-cluster -debug-directory=/tmp/debug -``` -You can interact with the running kubernetes clusters now using `kubectl [COMMAND] --context=` - -* `kind delete clusters --all` is helpful for cleanup! - -### Example Debugging session using the acceptance test framework to bootstrap and debug a Vault backed federated Consul installation: -This test utilizes the `consul-k8s` acceptance test framework, with a custom consul-k8s branch which: -* Modifies the acceptance test to use custom consul+consul-k8s images and sleeps at the end of the test to allow analysis. -* Modifies the helm chart to pass in `connect_ca.intermediate_cert_ttl` and `connect_ca.leaf_cert_ttl` in the `server-configmap` - -1. First clone the consul-k8s repo and then check out the branch locally: `git checkout origin/consul-vault-provider-wanfed-acceptance`. -2. Start the kind clusters: `kind create cluster --name=dc1 && kind create cluster --name=dc2` -3. run the `TestVault_WANFederationViaGateways` acceptance test in `acceptance/tests/vault/vault_wan_fed_test.go` - I use goland, but this command should get you most of the way: -```shell -$ cd acceptance/tests -$ go test -run Vault_WANFederationViaGateways ./vault/... -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-multi-cluster -debug-directory=/tmp/debug -``` -NOTE: This specific acceptance test is considered FLAKY with Kind, if things don't come up it's best to run against GKE/AKS/etc, in which case you just modify the `kubecontext` command parameters to point to your clusters. It is worth noting that you will need to setup any necessary networking for non-Kind clusters manually. - -NOTE: This test requires a VAULT_LICENSE set as an environment variable in the shell where you run `go test` - -4. Wait 10-20 minutes to allow the first intermediate ca renewal, this test is particularly resource intensive so it can take time for everything to come online on a laptop, use `kubectl get pods` to validate that `static-server` and `static-client` have been deployed and are online. - -You can validate the ICA rotation by doing: -```shell -# Fetch the vault root token: -$ kubectl get secrets -root-token -o json //----> b64 decode the `data.token` field. -$ kubectl exec -it -- sh -$ export VAULT_TOKEN= -$ export VAULT_ADDR=https://-vault:8200 - -# Fetch the consul bootstrap token -$ vault kv get consul/secret/bootstrap - -# Examine the vault issuers, there should be 2 by now if ICA renewal has occured: -# NOTE: for a federated setup the issuers url for dc2 is `vault list dc2/connect_inter/issuers`! -$ vault list dc1/connect_inter/issuers - -Keys ----- -29bdffbd-87ec-cfe0-fd05-b78f99eba243 -344eea3c-f085-943a-c3ff-66721ef408f4 - -# Now login to the consul-server -$ kubectl exec -it -- sh -$ export CONSUL_HTTP_TOKEN= -$ export CONSUL_HTTP_ADDR=https://localhost:8501 -$ export CONSUL_HTTP_SSL_VERIFY=false - -# Read the `connect/ca/roots` endpoint: -# It should change + rotate with the expiration of the ICA (defined by `intermediate_cert_ttl` which is `15m` in the branch for this gist. - -$ curl -k --header "X-Consul-Token: 1428da53-5e88-db1a-6ad5-e50212b011da" https://127.0.0.1:8501/v1/agent/connect/ca/roots | jq - . - % Total % Received % Xferd Average Speed Time Time Time Current - Dload Upload Total Spent Left Speed -100 3113 100 3113 0 0 6222 0 --:--:-- --:--:-- --:--:-- 7705 -{ - "ActiveRootID": "36:be:19:0e:56:d1:c2:1a:d8:54:22:97:88:3c:91:17:1d:d2:d3:e0", - "TrustDomain": "34a76791-b9b2-b93e-b0e4-1989ed11a28e.consul", - "Roots": [ - { - "ID": "36:be:19:0e:56:d1:c2:1a:d8:54:22:97:88:3c:91:17:1d:d2:d3:e0", - "Name": "Vault CA Primary Cert", - "SerialNumber": 15998414315735550000, - "SigningKeyID": "fe:b9:d6:0b:c6:ce:2c:25:4f:d8:59:cb:11:ea:a5:42:5f:8e:41:4b", - "ExternalTrustDomain": "34a76791-b9b2-b93e-b0e4-1989ed11a28e", - "NotBefore": "2022-11-16T20:16:15Z", - "NotAfter": "2032-11-13T20:16:45Z", - "RootCert": "-----BEGIN CERTIFICATE-----\nMIICLDCCAdKgAwIBAgIUKQ9BPHF9mtC7yFPC3gXJDpLxCHIwCgYIKoZIzj0EAwIw\nLzEtMCsGA1UEAxMkcHJpLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3Vs\nMB4XDTIyMTExNjIwMTYxNVoXDTMyMTExMzIwMTY0NVowLzEtMCsGA1UEAxMkcHJp\nLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3VsMFkwEwYHKoZIzj0CAQYI\nKoZIzj0DAQcDQgAETnpGixC1kW8ep2JcGjRR2jbdESvjlEm9nSIWVAcilemUGFwi\nJ0YW0XUmJeEzRyfwLXnOw6voPzXRf1zXKjdTD6OByzCByDAOBgNVHQ8BAf8EBAMC\nAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUtb6EjDxyI+myIjDc+7KbiN8u\n8XowHwYDVR0jBBgwFoAUtb6EjDxyI+myIjDc+7KbiN8u8XowZQYDVR0RBF4wXIIk\ncHJpLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3VshjRzcGlmZmU6Ly8z\nNGE3Njc5MS1iOWIyLWI5M2UtYjBlNC0xOTg5ZWQxMWEyOGUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCIHBezFSQAK5Nolf0rs3ErvlDcA8Z9esldh6gHupuGsNkAiEA\n9qL+P9PJAW4CrbTL0iF2yZUyJC2nwSSa2K0nYG8bXWQ=\n-----END CERTIFICATE-----\n", - "IntermediateCerts": [ - "-----BEGIN CERTIFICATE-----\nMIICLzCCAdSgAwIBAgIUbILCP3ODM4ScNBOm0jw59Fxju0swCgYIKoZIzj0EAwIw\nLzEtMCsGA1UEAxMkcHJpLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3Vs\nMB4XDTIyMTExNjIwMzIxNloXDTIyMTExNjIwNDc0NlowMDEuMCwGA1UEAxMlcHJp\nLTE4MThxNWlnLnZhdWx0LmNhLjM0YTc2NzkxLmNvbnN1bDBZMBMGByqGSM49AgEG\nCCqGSM49AwEHA0IABI30ikgrwTjbPaGgfNYkushvrEUUpxLzxMMEBlE82ilog1RW\nqwuEU29Qsa+N4SrfOf37xNv/Ey8SXPs5l2HmXJWjgcwwgckwDgYDVR0PAQH/BAQD\nAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCZpC/BTdaggL2kj6Dfyk3+a\nNqBvMB8GA1UdIwQYMBaAFLW+hIw8ciPpsiIw3Puym4jfLvF6MGYGA1UdEQRfMF2C\nJXByaS0xODE4cTVpZy52YXVsdC5jYS4zNGE3Njc5MS5jb25zdWyGNHNwaWZmZTov\nLzM0YTc2NzkxLWI5YjItYjkzZS1iMGU0LTE5ODllZDExYTI4ZS5jb25zdWwwCgYI\nKoZIzj0EAwIDSQAwRgIhAJ8RHgR5qkyW2q866vGYJy+7BJ4zUXs3OJ76QLmxxU3K\nAiEA70S7wBEm1ZduTAk1ZfZPJEUGxvAXAcgy7EWeO/6MJ5o=\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\nMIICLTCCAdKgAwIBAgIUU3qwESuhh4PgW3/tnHDn3qnBMrAwCgYIKoZIzj0EAwIw\nLzEtMCsGA1UEAxMkcHJpLTEwOTJudTEudmF1bHQuY2EuMzRhNzY3OTEuY29uc3Vs\nMB4XDTIyMTExNjIwNDAxNloXDTIyMTExNjIwNTU0NlowLzEtMCsGA1UEAxMkcHJp\nLTFkY2hkbGkudmF1bHQuY2EuMzRhNzY3OTEuY29uc3VsMFkwEwYHKoZIzj0CAQYI\nKoZIzj0DAQcDQgAEpj0BWPkcH82su9XGOo9rN5Zr5+Jyp68LiHy+qlIgH3L+OAir\nYgmXmJfuNwI8S2BB8cu0Gk3w5cTF7O0p/qAghaOByzCByDAOBgNVHQ8BAf8EBAMC\nAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU/rnWC8bOLCVP2FnLEeqlQl+O\nQUswHwYDVR0jBBgwFoAUtb6EjDxyI+myIjDc+7KbiN8u8XowZQYDVR0RBF4wXIIk\ncHJpLTFkY2hkbGkudmF1bHQuY2EuMzRhNzY3OTEuY29uc3VshjRzcGlmZmU6Ly8z\nNGE3Njc5MS1iOWIyLWI5M2UtYjBlNC0xOTg5ZWQxMWEyOGUuY29uc3VsMAoGCCqG\nSM49BAMCA0kAMEYCIQCtq4LiZzkiIKUES9MrzUEflg7wcwQf7Km+8RcOGQbz9QIh\nANWHWt1fe8Hl1wQ55qxsV5lSfOpGAox5WHpgnsBC7cwU\n-----END CERTIFICATE-----\n" - ], - "Active": true, - "PrivateKeyType": "ec", - "PrivateKeyBits": 256, - "CreateIndex": 11, - "ModifyIndex": 797 - } - ] -} - -# You can x509 decode the ICA certs to verify they have been updated and have correct expiry: -$ openssl x509 -in cert.crt -text -noout -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 53:7a:b0:11:2b:a1:87:83:e0:5b:7f:ed:9c:70:e7:de:a9:c1:32:b0 - Signature Algorithm: ecdsa-with-SHA256 - Issuer: CN=pri-1092nu1.vault.ca.34a76791.consul - Validity - Not Before: Nov 16 20:40:16 2022 GMT - Not After : Nov 16 20:55:46 2022 GMT - Subject: CN=pri-1dchdli.vault.ca.34a76791.consul - Subject Public Key Info: - Public Key Algorithm: id-ecPublicKey - Public-Key: (256 bit) - pub: - 04:a6:3d:01:58:f9:1c:1f:cd:ac:bb:d5:c6:3a:8f: - 6b:37:96:6b:e7:e2:72:a7:af:0b:88:7c:be:aa:52: - 20:1f:72:fe:38:08:ab:62:09:97:98:97:ee:37:02: - 3c:4b:60:41:f1:cb:b4:1a:4d:f0:e5:c4:c5:ec:ed: - 29:fe:a0:20:85 - ASN1 OID: prime256v1 - NIST CURVE: P-256 - X509v3 extensions: - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - FE:B9:D6:0B:C6:CE:2C:25:4F:D8:59:CB:11:EA:A5:42:5F:8E:41:4B - X509v3 Authority Key Identifier: - keyid:B5:BE:84:8C:3C:72:23:E9:B2:22:30:DC:FB:B2:9B:88:DF:2E:F1:7A - - X509v3 Subject Alternative Name: - DNS:pri-1dchdli.vault.ca.34a76791.consul, URI:spiffe://34a76791-b9b2-b93e-b0e4-1989ed11a28e.consul - -``` - ---- - ## Helm Reference Docs - + The Helm reference docs (https://www.consul.io/docs/k8s/helm) are automatically generated from our `values.yaml` file. ### Generating Helm Reference Docs - + To generate the docs and update the `helm.mdx` file: 1. Fork `hashicorp/consul` (https://github.com/hashicorp/consul) on GitHub. @@ -1147,7 +948,7 @@ To generate the docs and update the `helm.mdx` file: ```shell-session git clone https://github.com//consul.git ``` -1. Change directory into your `consul-k8s` repo: +1. Change directory into your `consul-k8s` repo: ```shell-session cd /path/to/consul-k8s ``` @@ -1209,26 +1010,6 @@ So that the documentation can look like: - `ports` ((#v-ingressgateways-defaults-service-ports)) (`array: [{port: 8080, port: 8443}]`) - Port docs ``` -## Managing External CRD Dependencies - -Some of the features of Consul on Kubernetes make use of CustomResourceDefinitions (CRDs) that we don't directly -manage. One such example is the Gateway API CRDs which we use to configure API Gateways, but are managed by SIG -Networking. - -To pull external CRDs into our Helm chart and make sure they get installed, we generate their configuration using -[Kustomize](https://kustomize.io/) which can pull in Kubernetes config from external sources. We split these -generated CRDs into individual files and store them in the `charts/consul/templates` directory. - -If you need to update the external CRDs we depend on, or add to them, you can do this by editing the -[control-plane/config/crd/external/kustomization.yaml](/control-plane/config/crd/external/kustomization.yaml) file. -Once modified, running - -```bash -make generate-external-crds -``` - -will update the CRDs in the `/templates` directory. - ## Adding a Changelog Entry Any change that a Consul-K8s user might need to know about should have a changelog entry. @@ -1261,7 +1042,7 @@ Some common values are: - `control-plane`: related to control-plane functionality - `helm`: related to the charts module and any files, yaml, go, etc. therein -There may be cases where a `code area` doesn't make sense (i.e. addressing a Go CVE). In these +There may be cases where a `code area` doesn't make sense (i.e. addressing a Go CVE). In these cases it is okay not to provide a `code area`. For more examples, look in the [`.changelog/`](../.changelog) folder for existing changelog entries. diff --git a/Makefile b/Makefile index 2e5d465318..44a0a6a7f2 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ VERSION = $(shell ./control-plane/build-support/scripts/version.sh control-plane/version/version.go) -GOLANG_VERSION?=$(shell head -n 1 .go-version) CONSUL_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-version.sh charts/consul/values.yaml) CONSUL_ENTERPRISE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-enterprise-version.sh charts/consul/values.yaml) CONSUL_DATAPLANE_IMAGE_VERSION = $(shell ./control-plane/build-support/scripts/consul-dataplane-version.sh charts/consul/values.yaml) @@ -7,161 +6,104 @@ KIND_VERSION= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh KIND_NODE_IMAGE= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh acceptance/ci-inputs/kind-inputs.yaml .kindNodeImage) KUBECTL_VERSION= $(shell ./control-plane/build-support/scripts/read-yaml-config.sh acceptance/ci-inputs/kind-inputs.yaml .kubectlVersion) -##@ Helm Targets +# ===========> Helm Targets -.PHONY: gen-helm-docs gen-helm-docs: ## Generate Helm reference docs from values.yaml and update Consul website. Usage: make gen-helm-docs consul=. @cd hack/helm-reference-gen; go run ./... $(consul) -.PHONY: copy-crds-to-chart copy-crds-to-chart: ## Copy generated CRD YAML into charts/consul. Usage: make copy-crds-to-chart @cd hack/copy-crds-to-chart; go run ./... -.PHONY: camel-crds -camel-crds: ## Convert snake_case keys in yaml to camelCase. Usage: make camel-crds - @cd hack/camel-crds; go run ./... - -.PHONY: generate-external-crds -generate-external-crds: ## Generate CRDs for externally defined CRDs and copy them to charts/consul. Usage: make generate-external-crds - @cd ./control-plane/config/crd/external; \ - kustomize build | yq --split-exp '.metadata.name + ".yaml"' --no-doc - -.PHONY: bats-tests bats-tests: ## Run Helm chart bats tests. bats --jobs 4 charts/consul/test/unit -##@ Control Plane Targets -.PHONY: control-plane-dev -control-plane-dev: ## Build consul-k8s-control-plane binary. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch amd64 +# ===========> Control Plane Targets -.PHONY: dev-docker -dev-docker: control-plane-dev-docker ## build dev local dev docker image - docker tag '$(DEV_IMAGE)' 'consul-k8s-control-plane:local' +control-plane-dev: ## Build consul-k8s-control-plane binary. + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a amd64 -.PHONY: control-plane-dev-docker control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) @docker build -t '$(DEV_IMAGE)' \ --target=dev \ - --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ --build-arg 'TARGETARCH=$(GOARCH)' \ --build-arg 'GIT_COMMIT=$(GIT_COMMIT)' \ --build-arg 'GIT_DIRTY=$(GIT_DIRTY)' \ --build-arg 'GIT_DESCRIBE=$(GIT_DESCRIBE)' \ -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane -.PHONY: control-plane-dev-skaffold -# DANGER: this target is experimental and could be modified/removed at any time. -control-plane-dev-skaffold: ## Build consul-k8s-control-plane dev Docker image for use with skaffold or local development. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) - @docker build -t '$(DEV_IMAGE)' \ - --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ - --build-arg 'TARGETARCH=$(GOARCH)' \ - -f $(CURDIR)/control-plane/Dockerfile.dev $(CURDIR)/control-plane - -.PHONY: check-remote-dev-image-env check-remote-dev-image-env: ifndef REMOTE_DEV_IMAGE $(error REMOTE_DEV_IMAGE is undefined: set this image to /:, e.g. hashicorp/consul-k8s-dev:latest) endif -.PHONY: control-plane-dev-docker-multi-arch control-plane-dev-docker-multi-arch: check-remote-dev-image-env ## Build consul-k8s-control-plane dev multi-arch Docker image. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch "arm64 amd64" + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a "arm64 amd64" @docker buildx create --use && docker buildx build -t '$(REMOTE_DEV_IMAGE)' \ --platform linux/amd64,linux/arm64 \ --target=dev \ - --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ --build-arg 'GIT_COMMIT=$(GIT_COMMIT)' \ --build-arg 'GIT_DIRTY=$(GIT_DIRTY)' \ --build-arg 'GIT_DESCRIBE=$(GIT_DESCRIBE)' \ --push \ -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane -.PHONY: control-plane-fips-dev-docker -control-plane-fips-dev-docker: ## Build consul-k8s-control-plane FIPS dev Docker image. - @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh --os linux --arch $(GOARCH) --fips - @docker build -t '$(DEV_IMAGE)' \ - --target=dev \ - --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ - --build-arg 'TARGETARCH=$(GOARCH)' \ - --build-arg 'GIT_COMMIT=$(GIT_COMMIT)' \ - --build-arg 'GIT_DIRTY=$(GIT_DIRTY)' \ - --build-arg 'GIT_DESCRIBE=$(GIT_DESCRIBE)' \ - --push \ - -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane - -.PHONY: control-plane-test control-plane-test: ## Run go test for the control plane. cd control-plane; go test ./... -.PHONY: control-plane-ent-test control-plane-ent-test: ## Run go test with Consul enterprise tests. The consul binary in your PATH must be Consul Enterprise. cd control-plane; go test ./... -tags=enterprise -.PHONY: control-plane-cov control-plane-cov: ## Run go test with code coverage. cd control-plane; go test ./... -coverprofile=coverage.out; go tool cover -html=coverage.out -.PHONY: control-plane-clean control-plane-clean: ## Delete bin and pkg dirs. @rm -rf \ $(CURDIR)/control-plane/bin \ $(CURDIR)/control-plane/pkg -.PHONY: control-plane-lint control-plane-lint: cni-plugin-lint ## Run linter in the control-plane directory. cd control-plane; golangci-lint run -c ../.golangci.yml -.PHONY: cni-plugin-lint cni-plugin-lint: cd control-plane/cni; golangci-lint run -c ../../.golangci.yml -.PHONY: ctrl-generate ctrl-generate: get-controller-gen ## Run CRD code generation. - make ensure-controller-gen-version - cd control-plane; $(CONTROLLER_GEN) object paths="./..." + cd control-plane; $(CONTROLLER_GEN) object:headerFile="build-support/controller/boilerplate.go.txt" paths="./..." -.PHONY: terraform-fmt-check -terraform-fmt-check: ## Perform a terraform fmt check but don't change anything +# Perform a terraform fmt check but don't change anything +terraform-fmt-check: @$(CURDIR)/control-plane/build-support/scripts/terraformfmtcheck.sh $(TERRAFORM_DIR) +.PHONY: terraform-fmt-check -.PHONY: terraform-fmt -terraform-fmt: ## Format all terraform files according to terraform fmt +# Format all terraform files according to terraform fmt +terraform-fmt: @terraform fmt -recursive +.PHONY: terraform-fmt -.PHONY: check-preview-containers -check-preview-containers: ## Check for hashicorppreview containers +# Check for hashicorppreview containers +check-preview-containers: @source $(CURDIR)/control-plane/build-support/scripts/check-hashicorppreview.sh -##@ CLI Targets -.PHONY: cli-dev -cli-dev: ## run cli dev +# ===========> CLI Targets +cli-dev: @echo "==> Installing consul-k8s CLI tool for ${GOOS}/${GOARCH}" @cd cli; go build -o ./bin/consul-k8s; cp ./bin/consul-k8s ${GOPATH}/bin/ -.PHONY: cli-fips-dev -cli-fips-dev: ## run cli fips dev - @echo "==> Installing consul-k8s CLI tool for ${GOOS}/${GOARCH}" - @cd cli; CGO_ENABLED=1 GOEXPERIMENT=boringcrypto go build -o ./bin/consul-k8s -tags "fips"; cp ./bin/consul-k8s ${GOPATH}/bin/ - -.PHONY: cli-lint cli-lint: ## Run linter in the control-plane directory. cd cli; golangci-lint run -c ../.golangci.yml -##@ Acceptance Tests Targets -.PHONY: acceptance-lint +# ===========> Acceptance Tests Targets + acceptance-lint: ## Run linter in the control-plane directory. cd acceptance; golangci-lint run -c ../.golangci.yml -.PHONY: kind-cni-calico -# For CNI acceptance tests, the calico CNI plugin needs to be installed on Kind. Our consul-cni plugin will not work +# For CNI acceptance tests, the calico CNI pluging needs to be installed on Kind. Our consul-cni plugin will not work # without another plugin installed first -kind-cni-calico: ## install cni plugin on kind +kind-cni-calico: kubectl create namespace calico-system ||true kubectl create -f $(CURDIR)/acceptance/framework/environment/cni-kind/tigera-operator.yaml # Sleeps are needed as installs can happen too quickly for Kind to handle it @@ -169,59 +111,42 @@ kind-cni-calico: ## install cni plugin on kind kubectl create -f $(CURDIR)/acceptance/framework/environment/cni-kind/custom-resources.yaml @sleep 20 -.PHONY: kind-delete -kind-delete: +# Helper target for doing local cni acceptance testing +kind-cni: kind delete cluster --name dc1 kind delete cluster --name dc2 - kind delete cluster --name dc3 - kind delete cluster --name dc4 - -.PHONY: kind-cni -kind-cni: kind-delete ## Helper target for doing local cni acceptance testing kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc1 --image $(KIND_NODE_IMAGE) make kind-cni-calico kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc2 --image $(KIND_NODE_IMAGE) make kind-cni-calico - kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc3 --image $(KIND_NODE_IMAGE) - make kind-cni-calico - kind create cluster --config=$(CURDIR)/acceptance/framework/environment/cni-kind/kind.config --name dc4 --image $(KIND_NODE_IMAGE) - make kind-cni-calico -.PHONY: kind -kind: kind-delete ## Helper target for doing local acceptance testing (works in all cases) +# Helper target for doing local acceptance testing +kind: + kind delete cluster --name dc1 + kind delete cluster --name dc2 kind create cluster --name dc1 --image $(KIND_NODE_IMAGE) kind create cluster --name dc2 --image $(KIND_NODE_IMAGE) - kind create cluster --name dc3 --image $(KIND_NODE_IMAGE) - kind create cluster --name dc4 --image $(KIND_NODE_IMAGE) -.PHONY: kind-small -kind-small: kind-delete ## Helper target for doing local acceptance testing (when you only need two clusters) - kind create cluster --name dc1 --image $(KIND_NODE_IMAGE) - kind create cluster --name dc2 --image $(KIND_NODE_IMAGE) -.PHONY: kind-load -kind-load: ## Helper target for loading local dev images (run with `DEV_IMAGE=...` to load non-k8s images) +# Helper target for loading local dev images (run with `DEV_IMAGE=...` to load non-k8s images) +kind-load: kind load docker-image --name dc1 $(DEV_IMAGE) kind load docker-image --name dc2 $(DEV_IMAGE) kind load docker-image --name dc3 $(DEV_IMAGE) kind load docker-image --name dc4 $(DEV_IMAGE) -##@ Shared Targets +# ===========> Shared Targets + +help: ## Show targets and their descriptions. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-38s\033[0m %s\n", $$1, $$2}' -.PHONY: lint lint: cni-plugin-lint ## Run linter in the control-plane, cli, and acceptance directories. for p in control-plane cli acceptance; do cd $$p; golangci-lint run --path-prefix $$p -c ../.golangci.yml; cd ..; done -.PHONY: ctrl-manifests ctrl-manifests: get-controller-gen ## Generate CRD manifests. - make ensure-controller-gen-version cd control-plane; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - make camel-crds make copy-crds-to-chart - make generate-external-crds - make add-copyright-header -.PHONY: get-controller-gen get-controller-gen: ## Download controller-gen program needed for operator SDK. ifeq (, $(shell which controller-gen)) @{ \ @@ -229,7 +154,7 @@ ifeq (, $(shell which controller-gen)) CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ cd $$CONTROLLER_GEN_TMP_DIR ;\ go mod init tmp ;\ - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.12.1 ;\ + go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 ;\ rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ } CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen @@ -237,85 +162,48 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif -.PHONY: ensure-controller-gen-version -ensure-controller-gen-version: ## Ensure controller-gen version is v0.12.1. -ifeq (, $(shell which $(CONTROLLER_GEN))) - @echo "You don't have $(CONTROLLER_GEN), please install it first." -else -ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.12.1)) - @echo "controller-gen version is not v0.12.1, uninstall the binary and install the correct version with 'make get-controller-gen'." - @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" - @exit 1 -else - @echo "Found correct version: $(shell $(CONTROLLER_GEN) --version)" -endif -endif - -.PHONY: add-copyright-header -add-copyright-header: ## Add copyright header to all files in the project -ifeq (, $(shell which copywrite)) - @echo "Installing copywrite" - @go install github.com/hashicorp/copywrite@latest -endif - @copywrite headers --spdx "MPL-2.0" +# ===========> CI Targets -##@ CI Targets - -.PHONY: ci.aws-acceptance-test-cleanup ci.aws-acceptance-test-cleanup: ## Deletes AWS resources left behind after failed acceptance tests. @cd hack/aws-acceptance-test-cleanup; go run ./... -auto-approve -.PHONY: version -version: ## print version +version: @echo $(VERSION) -.PHONY: consul-version -consul-version: ## print consul version +consul-version: @echo $(CONSUL_IMAGE_VERSION) -.PHONY: consul-enterprise-version -consul-enterprise-version: ## print consul ent version +consul-enterprise-version: @echo $(CONSUL_ENTERPRISE_IMAGE_VERSION) -.PHONY: consul-dataplane-version -consul-dataplane-version: ## print consul data-plane version +consul-dataplane-version: @echo $(CONSUL_DATAPLANE_IMAGE_VERSION) -.PHONY: kind-version -kind-version: ## print kind version +kind-version: @echo $(KIND_VERSION) -.PHONY: kind-node-image -kind-node-image: ## print kind node image +kind-node-image: @echo $(KIND_NODE_IMAGE) -.PHONY: kubectl-version -kubectl-version: ## print kubectl version +kubectl-version: @echo $(KUBECTL_VERSION) -.PHONY: kind-test-packages -kind-test-packages: ## kind test packages +kind-test-packages: @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/kind_acceptance_test_packages.yaml" -.PHONY: gke-test-packages -gke-test-packages: ## gke test packages +gke-test-packages: @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/gke_acceptance_test_packages.yaml" -.PHONY: eks-test-packages -eks-test-packages: ## eks test packages +eks-test-packages: @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/eks_acceptance_test_packages.yaml" -.PHONY: aks-test-packages -aks-test-packages: ## aks test packages +aks-test-packages: @./control-plane/build-support/scripts/set_test_package_matrix.sh "acceptance/ci-inputs/aks_acceptance_test_packages.yaml" -##@ Release Targets - -.PHONY: check-env -check-env: ## check env +# ===========> Release Targets +check-env: @printenv | grep "CONSUL_K8S" -.PHONY: prepare-release-script prepare-release-script: ## Sets the versions, updates changelog to prepare this repository to release ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) @@ -331,30 +219,9 @@ ifndef CONSUL_K8S_CONSUL_VERSION endif @source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_release $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION); \ -.PHONY: prepare-release prepare-release: prepare-release-script check-preview-containers -.PHONY: prepare-rc-script -prepare-rc-script: ## Sets the versions, updates changelog to prepare this repository to release -ifndef CONSUL_K8S_RELEASE_VERSION - $(error CONSUL_K8S_RELEASE_VERSION is required) -endif -ifndef CONSUL_K8S_RELEASE_DATE - $(error CONSUL_K8S_RELEASE_DATE is required, use format , (ex. October 4, 2022)) -endif -ifndef CONSUL_K8S_LAST_RELEASE_GIT_TAG - $(error CONSUL_K8S_LAST_RELEASE_GIT_TAG is required) -endif -ifndef CONSUL_K8S_CONSUL_VERSION - $(error CONSUL_K8S_CONSUL_VERSION is required) -endif - @source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_rc_branch $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION); \ - -.PHONY: prepare-rc-branch -prepare-rc-branch: prepare-rc-script - -.PHONY: prepare-main-dev -prepare-main-dev: ## prepare main dev +prepare-dev: ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) endif @@ -372,27 +239,9 @@ ifndef CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION endif source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_NEXT_CONSUL_VERSION) $(CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION) -.PHONY: prepare-release-dev -prepare-release-dev: ## prepare release dev -ifndef CONSUL_K8S_RELEASE_VERSION - $(error CONSUL_K8S_RELEASE_VERSION is required) -endif -ifndef CONSUL_K8S_RELEASE_DATE - $(error CONSUL_K8S_RELEASE_DATE is required, use format , (ex. October 4, 2022)) -endif -ifndef CONSUL_K8S_NEXT_RELEASE_VERSION - $(error CONSUL_K8S_RELEASE_VERSION is required) -endif -ifndef CONSUL_K8S_CONSUL_VERSION - $(error CONSUL_K8S_CONSUL_VERSION is required) -endif -ifndef CONSUL_K8S_CONSUL_DATAPLANE_VERSION - $(error CONSUL_K8S_CONSUL_DATAPLANE_VERSION is required) -endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) - # ===========> Makefile config .DEFAULT_GOAL := help +.PHONY: gen-helm-docs copy-crds-to-chart bats-tests help ci.aws-acceptance-test-cleanup version cli-dev prepare-dev prepare-release SHELL = bash GOOS?=$(shell go env GOOS) GOARCH?=$(shell go env GOARCH) @@ -401,20 +250,4 @@ DOCKER_HUB_USER=$(shell cat $(HOME)/.dockerhub) GIT_COMMIT?=$(shell git rev-parse --short HEAD) GIT_DIRTY?=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true) GIT_DESCRIBE?=$(shell git describe --tags --always) -CRD_OPTIONS ?= "crd:ignoreUnexportedFields=true,allowDangerousTypes=true" - -##@ Help - -# The help target prints out all targets with their descriptions organized -# beneath their categories. The categories are represented by '##@' and the -# target descriptions by '##'. The awk commands is responsible for reading the -# entire set of makefiles included in this invocation, looking for lines of the -# file as xyz: ## something, and then pretty-format the target and help. Then, -# if there's a line with ##@ something, that gets pretty-printed as a category. -# More info on the usage of ANSI control characters for terminal formatting: -# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters -# More info on the awk command: -# http://linuxcommand.org/lc3_adv_awk.php -.PHONY: help -help: ## Display this help - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) +CRD_OPTIONS ?= "crd:allowDangerousTypes=true" diff --git a/README.md b/README.md index 52e909741b..1d3a3733ab 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,11 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). ## Features - * [**Consul Service Mesh**](https://developer.hashicorp.com/consul/docs/connect): + * [**Consul Service Mesh**](https://www.consul.io/docs/k8s/connect): Run Consul Service Mesh on Kubernetes. This feature injects Envoy sidecars and registers your Pods with Consul. - - * [**Consul API Gateway**](https://developer.hashicorp.com/consul/docs/api-gateway): - Run Consul API Gateway on Kubernetes to allow north/south traffic into Consul Service Mesh. - * [**Catalog Sync**](https://developer.hashicorp.com/consul/docs/k8s/service-sync): + * [**Catalog Sync**](https://www.consul.io/docs/k8s/service-sync): Sync Consul services into first-class Kubernetes services and vice versa. This enables Kubernetes to easily access external services and for non-Kubernetes nodes to easily discover and access Kubernetes services. @@ -50,13 +47,13 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). * A [Docker image `hashicorp/consul-k8s-control-plane`](https://hub.docker.com/r/hashicorp/consul-k8s-control-plane) is available. This can be used to manually run `consul-k8s-control-plane` within a scheduled environment. - * Consul K8s CLI, distributed as `consul-k8s`, can be used to install and uninstall Consul Kubernetes. See the [Consul K8s CLI Reference](https://developer.hashicorp.com/consul/docs/k8s/k8s-cli) for more details on usage. + * Consul K8s CLI, distributed as `consul-k8s`, can be used to install and uninstall Consul Kubernetes. See the [Consul K8s CLI Reference](https://www.consul.io/docs/k8s/k8s-cli) for more details on usage. ### Prerequisites The following pre-requisites must be met before installing Consul on Kubernetes. - * **Kubernetes 1.25.x - 1.28.x** - This represents the earliest versions of Kubernetes tested. + * **Kubernetes 1.23.x - 1.26.x** - This represents the earliest versions of Kubernetes tested. It is possible that this chart works with earlier versions, but it is untested. * Helm install @@ -92,7 +89,7 @@ for each subcommand. ### Helm -The Helm chart is ideal for those who prefer to use Helm for automation for either the installation or upgrade of Consul on Kubernetes. The chart supports multiple use cases of Consul on Kubernetes, depending on the values provided. Detailed installation instructions for Consul on Kubernetes are found [here](https://developer.hashicorp.com/consul/docs/k8s/installation/install). +The Helm chart is ideal for those who prefer to use Helm for automation for either the installation or upgrade of Consul on Kubernetes. The chart supports multiple use cases of Consul on Kubernetes, depending on the values provided. Detailed installation instructions for Consul on Kubernetes are found [here](https://www.consul.io/docs/k8s/installation/overview). 1. Add the HashiCorp Helm repository: @@ -115,7 +112,7 @@ The Helm chart is ideal for those who prefer to use Helm for automation for eith Please see the many options supported in the `values.yaml` file. These are also fully documented directly on the -[Consul website](https://developer.hashicorp.com/consul/docs/k8s/helm). +[Consul website](https://www.consul.io/docs/platform/k8s/helm.html). ## Tutorials diff --git a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml index b2874fd373..cef04a3205 100644 --- a/acceptance/ci-inputs/aks_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/aks_acceptance_test_packages.yaml @@ -1,7 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -# Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} -- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault server"} \ No newline at end of file +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml index b2874fd373..cef04a3205 100644 --- a/acceptance/ci-inputs/eks_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/eks_acceptance_test_packages.yaml @@ -1,7 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -# Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} -- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault server"} \ No newline at end of file +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml index b2874fd373..cef04a3205 100644 --- a/acceptance/ci-inputs/gke_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/gke_acceptance_test_packages.yaml @@ -1,7 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -# Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "connect peering snapshot-agent wan-federation"} - {runner: 1, test-packages: "consul-dns example partitions metrics sync"} -- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault server"} \ No newline at end of file +- {runner: 2, test-packages: "basic cli config-entries api-gateway ingress-gateway terminating-gateway vault"} \ No newline at end of file diff --git a/acceptance/ci-inputs/kind-inputs.yaml b/acceptance/ci-inputs/kind-inputs.yaml index 9b047bdac6..70dc2f0974 100644 --- a/acceptance/ci-inputs/kind-inputs.yaml +++ b/acceptance/ci-inputs/kind-inputs.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - kindVersion: v0.19.0 -kindNodeImage: kindest/node:v1.28.0@sha256:dad5a6238c5e41d7cac405fae3b5eda2ad1de6f1190fa8bfc64ff5bb86173213 +kindNodeImage: kindest/node:v1.25.9 kubectlVersion: v1.27.1 diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml index a4e09abd9c..74991abd76 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -1,12 +1,6 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -# Cloud package is not included in test suite as it is triggered from a non consul-k8s repo and requires HCP credentials - {runner: 0, test-packages: "partitions"} - {runner: 1, test-packages: "peering"} -- {runner: 2, test-packages: "sameness"} -- {runner: 3, test-packages: "connect snapshot-agent wan-federation"} -- {runner: 4, test-packages: "cli vault metrics server"} -- {runner: 5, test-packages: "api-gateway ingress-gateway sync example consul-dns"} -- {runner: 6, test-packages: "config-entries terminating-gateway basic"} -- {runner: 7, test-packages: "mesh_v2 tenancy_v2"} +- {runner: 2, test-packages: "connect snapshot-agent wan-federation"} +- {runner: 3, test-packages: "cli vault metrics"} +- {runner: 4, test-packages: "api-gateway ingress-gateway sync example consul-dns"} +- {runner: 5, test-packages: "config-entries terminating-gateway basic"} \ No newline at end of file diff --git a/acceptance/framework/cli/cli.go b/acceptance/framework/cli/cli.go index f9384cd8d5..5297382d94 100644 --- a/acceptance/framework/cli/cli.go +++ b/acceptance/framework/cli/cli.go @@ -1,16 +1,13 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cli import ( "fmt" "os/exec" "strings" + "testing" "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/sdk/testutil" ) const ( @@ -28,7 +25,7 @@ func NewCLI() (*CLI, error) { } // Run runs the CLI with the given args. -func (c *CLI) Run(t testutil.TestingTB, options *k8s.KubectlOptions, args ...string) ([]byte, error) { +func (c *CLI) Run(t *testing.T, options *k8s.KubectlOptions, args ...string) ([]byte, error) { if !c.initialized { return nil, fmt.Errorf("CLI must be initialized before calling Run, use `cli.NewCLI()` to initialize.") } diff --git a/acceptance/framework/config/config.go b/acceptance/framework/config/config.go index 4f9a8648c2..cba72db125 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package config import ( @@ -88,22 +85,14 @@ type TestConfig struct { ConsulVersion *version.Version ConsulDataplaneVersion *version.Version EnvoyImage string - ConsulCollectorImage string - - HCPResourceID string - - VaultHelmChartVersion string - VaultServerVersion string NoCleanupOnFailure bool - NoCleanup bool DebugDirectory string - UseAKS bool - UseEKS bool - UseGKE bool - UseGKEAutopilot bool - UseKind bool + UseAKS bool + UseEKS bool + UseGKE bool + UseKind bool helmChartPath string } @@ -156,15 +145,6 @@ func (t *TestConfig) HelmValuesFromConfig() (map[string]string, error) { } } - // UseGKEAutopilot is a temporary hack that we need in place as GKE Autopilot is already installing - // Gateway CRDs in the clusters. There are still other CRDs we need to install though (see helm cluster install) - if t.UseGKEAutopilot { - setIfNotEmpty(helmValues, "global.server.resources.requests.cpu", "500m") - setIfNotEmpty(helmValues, "global.server.resources.limits.cpu", "500m") - setIfNotEmpty(helmValues, "connectInject.apiGateway.manageExternalCRDs", "false") - setIfNotEmpty(helmValues, "connectInject.apiGateway.manageNonStandardCRDs", "true") - } - setIfNotEmpty(helmValues, "connectInject.transparentProxy.defaultEnabled", strconv.FormatBool(t.EnableTransparentProxy)) setIfNotEmpty(helmValues, "global.image", t.ConsulImage) @@ -245,12 +225,6 @@ func (c *TestConfig) SkipWhenOpenshiftAndCNI(t *testing.T) { } } -func (c *TestConfig) SkipWhenCNI(t *testing.T) { - if c.EnableCNI { - t.Skip("skipping because -enable-cni is set and doesn't apply to this accepatance test") - } -} - // setIfNotEmpty sets key to val in map m if value is not empty. func setIfNotEmpty(m map[string]string, key, val string) { if val != "" { diff --git a/acceptance/framework/config/config_test.go b/acceptance/framework/config/config_test.go index 4d432da3b0..124bfe65f5 100644 --- a/acceptance/framework/config/config_test.go +++ b/acceptance/framework/config/config_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package config import ( diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index bbcaf7aff9..b673ab7e86 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connhelper import ( @@ -11,25 +8,21 @@ import ( "time" terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( StaticClientName = "static-client" StaticServerName = "static-server" - JobName = "job-client" - - retryTimeout = 120 * time.Second ) // ConnectHelper configures a Consul cluster for connect injection tests. @@ -52,7 +45,6 @@ type ConnectHelper struct { // Ctx is used to deploy Consul Ctx environment.TestContext - // UseAppNamespace is used top optionally deploy applications into a separate namespace. // If unset, the namespace associated with Ctx is used. UseAppNamespace bool @@ -66,13 +58,6 @@ type ConnectHelper struct { ConsulClient *api.Client } -// ConnHelperOpts allows for configuring optional parameters to be passed into the -// conn helper methods. This provides added flexibility, although not every value will be used -// by every method. See documentation for more details. -type ConnHelperOpts struct { - ClientType string -} - // Setup creates a new cluster using the New*Cluster function and assigns it // to the consulCluster field. func (c *ConnectHelper) Setup(t *testing.T) { @@ -101,8 +86,6 @@ func (c *ConnectHelper) Upgrade(t *testing.T) { c.consulCluster.Upgrade(t, c.helmValues()) } -// KubectlOptsForApp returns options using the -apps appended namespace if -// UseAppNamespace is enabled. Otherwise, it returns the ctx options. func (c *ConnectHelper) KubectlOptsForApp(t *testing.T) *terratestK8s.KubectlOptions { opts := c.Ctx.KubectlOptions(t) if !c.UseAppNamespace { @@ -123,7 +106,7 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { // deployments because golang will execute them in reverse order // (i.e. the last registered cleanup function will be executed first). t.Cleanup(func() { - retrier := &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond} + retrier := &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond} retry.RunWith(retrier, t, func(r *retry.R) { tokens, _, err := c.ConsulClient.ACL().TokenList(nil) require.NoError(r, err) @@ -147,31 +130,31 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { // TODO: A base fixture is the wrong place for these files k8s.KubectlApply(t, opts, "../fixtures/bases/openshift/") - helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { + helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, func() { k8s.KubectlDelete(t, opts, "../fixtures/bases/openshift/") }) - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-openshift") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-openshift") if c.Cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-openshift-tproxy") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-openshift-tproxy") } else { - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-openshift-inject") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-openshift-inject") } } else { - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if c.Cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } } // Check that both static-server and static-client have been injected and // now have 2 containers. retry.RunWith( - &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond}, t, + &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond}, t, func(r *retry.R) { for _, labelSelector := range []string{"app=static-server", "app=static-client"} { - podList, err := c.Ctx.KubernetesClient(r).CoreV1(). + podList, err := c.Ctx.KubernetesClient(t).CoreV1(). Pods(opts.Namespace). List(context.Background(), metav1.ListOptions{ LabelSelector: labelSelector, @@ -184,94 +167,6 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { }) } -func (c *ConnectHelper) CreateNamespace(t *testing.T, namespace string) { - opts := c.Ctx.KubectlOptions(t) - _, err := k8s.RunKubectlAndGetOutputE(t, opts, "create", "ns", namespace) - if err != nil && strings.Contains(err.Error(), "AlreadyExists") { - return - } - require.NoError(t, err) - helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { - k8s.RunKubectl(t, opts, "delete", "ns", namespace) - }) -} - -// DeployJob deploys a job pod to the Kubernetes -// cluster which will be used to test service mesh connectivity. If the Secure -// flag is true, a pre-check is done to ensure that the ACL tokens for the test -// are deleted. The status of the deployment and injection is checked after the -// deployment is complete to ensure success. -func (c *ConnectHelper) DeployJob(t *testing.T, path string) { - // Check that the ACL token is deleted. - if c.Secure { - // We need to register the cleanup function before we create the - // deployments because golang will execute them in reverse order - // (i.e. the last registered cleanup function will be executed first). - t.Cleanup(func() { - retrier := &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond} - retry.RunWith(retrier, t, func(r *retry.R) { - tokens, _, err := c.ConsulClient.ACL().TokenList(nil) - require.NoError(r, err) - for _, token := range tokens { - require.NotContains(r, token.Description, JobName) - } - }) - }) - } - - logger.Log(t, "creating job-client deployment") - k8s.DeployJob(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, path) - - // Check that job-client has been injected and - // now have 2 containers. - for _, labelSelector := range []string{"app=job-client"} { - podList, err := c.Ctx.KubernetesClient(t).CoreV1().Pods(c.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labelSelector, - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - } -} - -// DeployServer deploys a server pod to the Kubernetes -// cluster which will be used to test service mesh connectivity. If the Secure -// flag is true, a pre-check is done to ensure that the ACL tokens for the test -// are deleted. The status of the deployment and injection is checked after the -// deployment is complete to ensure success. -func (c *ConnectHelper) DeployServer(t *testing.T) { - // Check that the ACL token is deleted. - if c.Secure { - // We need to register the cleanup function before we create the - // deployments because golang will execute them in reverse order - // (i.e. the last registered cleanup function will be executed first). - t.Cleanup(func() { - retrier := &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond} - retry.RunWith(retrier, t, func(r *retry.R) { - tokens, _, err := c.ConsulClient.ACL().TokenList(nil) - require.NoError(r, err) - for _, token := range tokens { - require.NotContains(r, token.Description, StaticServerName) - } - }) - }) - } - - logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - // Check that static-server has been injected and - // now have 2 containers. - for _, labelSelector := range []string{"app=static-server"} { - podList, err := c.Ctx.KubernetesClient(t).CoreV1().Pods(c.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labelSelector, - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - } -} - // SetupAppNamespace creates a namespace where applications are deployed. This // does nothing if UseAppNamespace is not set. The app namespace is relevant // when testing with restricted PSA enforcement enabled. @@ -282,7 +177,14 @@ func (c *ConnectHelper) SetupAppNamespace(t *testing.T) { opts := c.KubectlOptsForApp(t) // If we are deploying apps in another namespace, create the namespace. - c.CreateNamespace(t, opts.Namespace) + _, err := k8s.RunKubectlAndGetOutputE(t, opts, "create", "ns", opts.Namespace) + if err != nil && strings.Contains(err.Error(), "AlreadyExists") { + return + } + require.NoError(t, err) + helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, func() { + k8s.RunKubectl(t, opts, "delete", "ns", opts.Namespace) + }) if c.Cfg.EnableRestrictedPSAEnforcement { // Allow anything to run in the app namespace. @@ -291,103 +193,48 @@ func (c *ConnectHelper) SetupAppNamespace(t *testing.T) { "pod-security.kubernetes.io/enforce-version=v1.24", ) } -} -// CreateResolverRedirect creates a resolver that redirects to a static-server, a corresponding k8s service, -// and intentions. This helper is primarly used to ensure that the virtual-ips are persisted to consul properly. -func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { - logger.Log(t, "creating resolver redirect") - opts := c.KubectlOptsForApp(t) - c.SetupAppNamespace(t) - kustomizeDir := "../fixtures/cases/resolver-redirect-virtualip" - k8s.KubectlApplyK(t, opts, kustomizeDir) - - helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, opts, kustomizeDir) - }) } // TestConnectionFailureWithoutIntention ensures the connection to the static -// server fails when no intentions are configured. When provided with a ClientType option -// the client is overridden, otherwise a default will be used. -func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T, connHelperOpts ConnHelperOpts) { +// server fails when no intentions are configured. +func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T) { logger.Log(t, "checking that the connection is not successful because there's no intention") opts := c.KubectlOptsForApp(t) - //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. - client := StaticClientName - if connHelperOpts.ClientType != "" { - client = connHelperOpts.ClientType - } - if c.Cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionFailing(t, opts, client, "http://static-server") + k8s.CheckStaticServerConnectionFailing(t, opts, StaticClientName, "http://static-server") } else { - k8s.CheckStaticServerConnectionFailing(t, opts, client, "http://localhost:1234") + k8s.CheckStaticServerConnectionFailing(t, opts, StaticClientName, "http://localhost:1234") } } -type IntentionOpts struct { - ConnHelperOpts - SourceNamespace string - DestinationNamespace string -} - // CreateIntention creates an intention for the static-server pod to connect to -// the static-client pod. opts parameter allows for overriding of some fields. If opts is empty -// then all namespaces and clients use defaults. -func (c *ConnectHelper) CreateIntention(t *testing.T, opts IntentionOpts) { +// the static-client pod. +func (c *ConnectHelper) CreateIntention(t *testing.T) { logger.Log(t, "creating intention") - //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. - client := StaticClientName - if opts.ClientType != "" { - client = opts.ClientType - } - - sourceNamespace := c.KubectlOptsForApp(t).Namespace - if opts.SourceNamespace != "" { - sourceNamespace = opts.SourceNamespace - } - - destinationNamespace := c.KubectlOptsForApp(t).Namespace - if opts.DestinationNamespace != "" { - destinationNamespace = opts.DestinationNamespace - } - - retrier := &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond} - retry.RunWith(retrier, t, func(r *retry.R) { - _, _, err := c.ConsulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: StaticServerName, - Namespace: destinationNamespace, - Sources: []*api.SourceIntention{ - { - Namespace: sourceNamespace, - Name: client, - Action: api.IntentionActionAllow, - }, + _, _, err := c.ConsulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: StaticServerName, + Sources: []*api.SourceIntention{ + { + Name: StaticClientName, + Action: api.IntentionActionAllow, }, - }, nil) - require.NoError(r, err) - }) + }, + }, nil) + require.NoError(t, err) } // TestConnectionSuccess ensures the static-server pod can connect to the -// static-client pod once the intention is set. When provided with a ClientType option -// the client is overridden, otherwise a default will be used. -func (c *ConnectHelper) TestConnectionSuccess(t *testing.T, connHelperOpts ConnHelperOpts) { +// static-client pod once the intention is set. +func (c *ConnectHelper) TestConnectionSuccess(t *testing.T) { logger.Log(t, "checking that connection is successful") opts := c.KubectlOptsForApp(t) - //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. - client := StaticClientName - if connHelperOpts.ClientType != "" { - client = connHelperOpts.ClientType - } - if c.Cfg.EnableTransparentProxy { // todo: add an assertion that the traffic is going through the proxy - k8s.CheckStaticServerConnectionSuccessful(t, opts, client, "http://static-server") + k8s.CheckStaticServerConnectionSuccessful(t, opts, StaticClientName, "http://static-server") } else { - k8s.CheckStaticServerConnectionSuccessful(t, opts, client, "http://localhost:1234") + k8s.CheckStaticServerConnectionSuccessful(t, opts, StaticClientName, "http://localhost:1234") } } @@ -401,7 +248,7 @@ func (c *ConnectHelper) TestConnectionFailureWhenUnhealthy(t *testing.T) { opts := c.KubectlOptsForApp(t) logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, // CheckStaticServerConnection will retry until Consul marks the service @@ -426,7 +273,7 @@ func (c *ConnectHelper) TestConnectionFailureWhenUnhealthy(t *testing.T) { } // Return the static-server to a "healthy state". - k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "-c", "static-server", "--", "rm", "/tmp/unhealthy") + k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "--", "rm", "/tmp/unhealthy") } // helmValues uses the Secure and AutoEncrypt fields to set values for the Helm diff --git a/acceptance/framework/consul/cli_cluster.go b/acceptance/framework/consul/cli_cluster.go index f960a4a612..b90e3134bd 100644 --- a/acceptance/framework/consul/cli_cluster.go +++ b/acceptance/framework/consul/cli_cluster.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consul import ( @@ -45,7 +42,6 @@ type CLICluster struct { kubeConfig string kubeContext string noCleanupOnFailure bool - noCleanup bool debugDirectory string logger terratestLogger.TestLogger cli cli.CLI @@ -110,7 +106,6 @@ func NewCLICluster( kubeConfig: cfg.GetPrimaryKubeEnv().KubeConfig, kubeContext: cfg.GetPrimaryKubeEnv().KubeContext, noCleanupOnFailure: cfg.NoCleanupOnFailure, - noCleanup: cfg.NoCleanup, debugDirectory: cfg.DebugDirectory, logger: logger, cli: *cli, @@ -124,7 +119,7 @@ func (c *CLICluster) Create(t *testing.T) { // Make sure we delete the cluster if we receive an interrupt signal and // register cleanup so that we delete the cluster when test finishes. - helpers.Cleanup(t, c.noCleanupOnFailure, c.noCleanup, func() { + helpers.Cleanup(t, c.noCleanupOnFailure, func() { c.Destroy(t) }) @@ -203,14 +198,9 @@ func (c *CLICluster) Destroy(t *testing.T) { require.NoError(t, err) } -func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool, release ...string) (*api.Client, string) { +func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool) (*api.Client, string) { t.Helper() - releaseName := c.releaseName - if len(release) > 0 { - releaseName = release[0] - } - namespace := c.kubectlOptions.Namespace config := api.DefaultConfig() localPort := terratestk8s.GetAvailablePort(t) @@ -230,13 +220,13 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool, release ...str // In secondary servers, we don't create a bootstrap token since ACLs are only bootstrapped in the primary. // Instead, we provide a replication token that serves the role of the bootstrap token. - aclSecretName := fmt.Sprintf("%s-consul-bootstrap-acl-token", releaseName) + aclSecretName := fmt.Sprintf("%s-consul-bootstrap-acl-token", c.releaseName) if c.releaseName == CLIReleaseName { aclSecretName = "consul-bootstrap-acl-token" } aclSecret, err := c.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), aclSecretName, metav1.GetOptions{}) if err != nil && errors.IsNotFound(err) { - federationSecret := fmt.Sprintf("%s-consul-federation", releaseName) + federationSecret := fmt.Sprintf("%s-consul-federation", c.releaseName) if c.releaseName == CLIReleaseName { federationSecret = "consul-federation" } @@ -250,8 +240,8 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool, release ...str } } - serverPod := fmt.Sprintf("%s-consul-server-0", releaseName) - if releaseName == CLIReleaseName { + serverPod := fmt.Sprintf("%s-consul-server-0", c.releaseName) + if c.releaseName == CLIReleaseName { serverPod = "consul-server-0" } tunnel := terratestk8s.NewTunnelWithLogger( @@ -264,7 +254,10 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool, release ...str // Retry creating the port forward since it can fail occasionally. retry.RunWith(&retry.Counter{Wait: 3 * time.Second, Count: 60}, t, func(r *retry.R) { - require.NoError(r, tunnel.ForwardPortE(r)) + // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry + // because we're using ForwardPortE (not ForwardPort) so the `t` won't + // get used to fail the test, just for logging. + require.NoError(r, tunnel.ForwardPortE(t)) }) t.Cleanup(func() { diff --git a/acceptance/framework/consul/cluster.go b/acceptance/framework/consul/cluster.go index 1b9a543245..41dbec86f8 100644 --- a/acceptance/framework/consul/cluster.go +++ b/acceptance/framework/consul/cluster.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consul import ( @@ -12,7 +9,7 @@ import ( // Cluster represents a consul cluster object. type Cluster interface { // SetupConsulClient returns a new Consul client. - SetupConsulClient(t *testing.T, secure bool, release ...string) (*api.Client, string) + SetupConsulClient(t *testing.T, secure bool) (*api.Client, string) // Create creates a new Consul Cluster. Create(t *testing.T) diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 638e0e7c51..cbd5e44fb8 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consul import ( @@ -12,30 +9,21 @@ import ( "github.com/gruntwork-io/terratest/modules/helm" terratestLogger "github.com/gruntwork-io/terratest/modules/logger" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - corev1 "k8s.io/api/core/v1" - policyv1beta "k8s.io/api/policy/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - "k8s.io/client-go/kubernetes" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/portforward" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + policyv1beta "k8s.io/api/policy/v1beta1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" ) // HelmCluster implements Cluster and uses Helm @@ -50,17 +38,11 @@ type HelmCluster struct { // if there are any previous installations of this Helm chart in the cluster. SkipCheckForPreviousInstallations bool - // ChartPath is an option field that allows consumers to change the default - // chart path if so desired - ChartPath string - ctx environment.TestContext helmOptions *helm.Options releaseName string - runtimeClient client.Client kubernetesClient kubernetes.Interface noCleanupOnFailure bool - noCleanup bool debugDirectory string logger terratestLogger.TestLogger } @@ -117,10 +99,8 @@ func NewHelmCluster( ctx: ctx, helmOptions: opts, releaseName: releaseName, - runtimeClient: ctx.ControllerRuntimeClient(t), kubernetesClient: ctx.KubernetesClient(t), noCleanupOnFailure: cfg.NoCleanupOnFailure, - noCleanup: cfg.NoCleanup, debugDirectory: cfg.DebugDirectory, logger: logger, } @@ -131,7 +111,7 @@ func (h *HelmCluster) Create(t *testing.T) { // Make sure we delete the cluster if we receive an interrupt signal and // register cleanup so that we delete the cluster when test finishes. - helpers.Cleanup(t, h.noCleanupOnFailure, h.noCleanup, func() { + helpers.Cleanup(t, h.noCleanupOnFailure, func() { h.Destroy(t) }) @@ -151,15 +131,7 @@ func (h *HelmCluster) Create(t *testing.T) { logger.Logf(t, "Unable to update helm repository, proceeding anyway: %s.", err) } } - if h.ChartPath != "" { - chartName = h.ChartPath - } - - // Retry the install in case previous tests have not finished cleaning up. - retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 30}, t, func(r *retry.R) { - err := helm.InstallE(r, h.helmOptions, chartName, h.releaseName) - require.NoError(r, err) - }) + helm.Install(t, h.helmOptions, chartName, h.releaseName) k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName)) } @@ -175,232 +147,93 @@ func (h *HelmCluster) Destroy(t *testing.T) { "--wait": nil, } - // Clean up any stuck gateway resources, note that we swallow all errors from - // here down since the terratest helm installation may actually already be - // deleted at this point, in which case these operations will fail on non-existent - // CRD cleanups. - requirement, err := labels.NewRequirement("release", selection.Equals, []string{h.releaseName}) - require.NoError(t, err) - - // Forcibly delete all gateway classes and remove their finalizers. - _ = h.runtimeClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}, client.HasLabels{"release=" + h.releaseName}) - - var gatewayClassList gwv1beta1.GatewayClassList - if h.runtimeClient.List(context.Background(), &gatewayClassList, &client.ListOptions{ - LabelSelector: labels.NewSelector().Add(*requirement), - }) == nil { - for _, item := range gatewayClassList.Items { - item.SetFinalizers([]string{}) - _ = h.runtimeClient.Update(context.Background(), &item) - } - } - - // Forcibly delete all gateway class configs and remove their finalizers. - _ = h.runtimeClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}, client.HasLabels{"release=" + h.releaseName}) - - var gatewayClassConfigList v1alpha1.GatewayClassConfigList - if h.runtimeClient.List(context.Background(), &gatewayClassConfigList, &client.ListOptions{ - LabelSelector: labels.NewSelector().Add(*requirement), - }) == nil { - for _, item := range gatewayClassConfigList.Items { - item.SetFinalizers([]string{}) - _ = h.runtimeClient.Update(context.Background(), &item) - } - } - - retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 30}, t, func(r *retry.R) { - err := helm.DeleteE(r, h.helmOptions, h.releaseName, false) + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { + err := helm.DeleteE(t, h.helmOptions, h.releaseName, false) require.NoError(r, err) }) // Retry because sometimes certain resources (like PVC) take time to delete // in cloud providers. - retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { - + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 600}, t, func(r *retry.R) { // Force delete any pods that have h.releaseName in their name because sometimes // graceful termination takes a long time and since this is an uninstall // we don't care that they're stopped gracefully. pods, err := h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) + require.NoError(t, err) for _, pod := range pods.Items { if strings.Contains(pod.Name, h.releaseName) { var gracePeriod int64 = 0 err := h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod}) if !errors.IsNotFound(err) { - require.NoError(r, err) - } - } - } - - // Delete any deployments that have h.releaseName in their name. - deployments, err := h.kubernetesClient.AppsV1().Deployments(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, deployment := range deployments.Items { - if strings.Contains(deployment.Name, h.releaseName) { - err := h.kubernetesClient.AppsV1().Deployments(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{}) - if !errors.IsNotFound(err) { - require.NoError(r, err) - } - } - } - - // Delete any replicasets that have h.releaseName in their name. - replicasets, err := h.kubernetesClient.AppsV1().ReplicaSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, replicaset := range replicasets.Items { - if strings.Contains(replicaset.Name, h.releaseName) { - err := h.kubernetesClient.AppsV1().ReplicaSets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), replicaset.Name, metav1.DeleteOptions{}) - if !errors.IsNotFound(err) { - require.NoError(r, err) - } - } - } - - // Delete any statefulsets that have h.releaseName in their name. - statefulsets, err := h.kubernetesClient.AppsV1().StatefulSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, statefulset := range statefulsets.Items { - if strings.Contains(statefulset.Name, h.releaseName) { - err := h.kubernetesClient.AppsV1().StatefulSets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), statefulset.Name, metav1.DeleteOptions{}) - if !errors.IsNotFound(err) { - require.NoError(r, err) - } - } - } - - // Delete any daemonsets that have h.releaseName in their name. - daemonsets, err := h.kubernetesClient.AppsV1().DaemonSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, daemonset := range daemonsets.Items { - if strings.Contains(daemonset.Name, h.releaseName) { - err := h.kubernetesClient.AppsV1().DaemonSets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), daemonset.Name, metav1.DeleteOptions{}) - if !errors.IsNotFound(err) { - require.NoError(r, err) - } - } - } - - // Delete any services that have h.releaseName in their name. - services, err := h.kubernetesClient.CoreV1().Services(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, service := range services.Items { - if strings.Contains(service.Name, h.releaseName) { - err := h.kubernetesClient.CoreV1().Services(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{}) - if !errors.IsNotFound(err) { - require.NoError(r, err) + require.NoError(t, err) } } } // Delete PVCs. err = h.kubernetesClient.CoreV1().PersistentVolumeClaims(h.helmOptions.KubectlOptions.Namespace).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) + require.NoError(t, err) // Delete any serviceaccounts that have h.releaseName in their name. sas, err := h.kubernetesClient.CoreV1().ServiceAccounts(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) + require.NoError(t, err) for _, sa := range sas.Items { if strings.Contains(sa.Name, h.releaseName) { err := h.kubernetesClient.CoreV1().ServiceAccounts(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), sa.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(r, err) + require.NoError(t, err) } } } // Delete any roles that have h.releaseName in their name. roles, err := h.kubernetesClient.RbacV1().Roles(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) + require.NoError(t, err) for _, role := range roles.Items { if strings.Contains(role.Name, h.releaseName) { err := h.kubernetesClient.RbacV1().Roles(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), role.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(r, err) + require.NoError(t, err) } } } // Delete any rolebindings that have h.releaseName in their name. roleBindings, err := h.kubernetesClient.RbacV1().RoleBindings(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) + require.NoError(t, err) for _, roleBinding := range roleBindings.Items { if strings.Contains(roleBinding.Name, h.releaseName) { err := h.kubernetesClient.RbacV1().RoleBindings(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), roleBinding.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(r, err) + require.NoError(t, err) } } } // Delete any secrets that have h.releaseName in their name. secrets, err := h.kubernetesClient.CoreV1().Secrets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{}) - require.NoError(r, err) + require.NoError(t, err) for _, secret := range secrets.Items { if strings.Contains(secret.Name, h.releaseName) { err := h.kubernetesClient.CoreV1().Secrets(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), secret.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(r, err) + require.NoError(t, err) } } } // Delete any jobs that have h.releaseName in their name. jobs, err := h.kubernetesClient.BatchV1().Jobs(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) + require.NoError(t, err) for _, job := range jobs.Items { if strings.Contains(job.Name, h.releaseName) { err := h.kubernetesClient.BatchV1().Jobs(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), job.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { - require.NoError(r, err) + require.NoError(t, err) } } } - // Verify that all deployments have been deleted. - deployments, err = h.kubernetesClient.AppsV1().Deployments(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, deployment := range deployments.Items { - if strings.Contains(deployment.Name, h.releaseName) { - r.Errorf("Found deployment which should have been deleted: %s", deployment.Name) - } - } - - // Verify that all replicasets have been deleted. - replicasets, err = h.kubernetesClient.AppsV1().ReplicaSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, replicaset := range replicasets.Items { - if strings.Contains(replicaset.Name, h.releaseName) { - r.Errorf("Found replicaset which should have been deleted: %s", replicaset.Name) - } - } - - // Verify that all statefulets have been deleted. - statefulsets, err = h.kubernetesClient.AppsV1().StatefulSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, statefulset := range statefulsets.Items { - if strings.Contains(statefulset.Name, h.releaseName) { - r.Errorf("Found statefulset which should have been deleted: %s", statefulset.Name) - } - } - - // Verify that all daemonsets have been deleted. - daemonsets, err = h.kubernetesClient.AppsV1().DaemonSets(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, daemonset := range daemonsets.Items { - if strings.Contains(daemonset.Name, h.releaseName) { - r.Errorf("Found daemonset which should have been deleted: %s", daemonset.Name) - } - } - - // Verify that all services have been deleted. - services, err = h.kubernetesClient.CoreV1().Services(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) - require.NoError(r, err) - for _, service := range services.Items { - if strings.Contains(service.Name, h.releaseName) { - r.Errorf("Found service which should have been deleted: %s", service.Name) - } - } - // Verify all Consul Pods are deleted. pods, err = h.kubernetesClient.CoreV1().Pods(h.helmOptions.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "release=" + h.releaseName}) require.NoError(r, err) @@ -474,44 +307,14 @@ func (h *HelmCluster) Upgrade(t *testing.T, helmValues map[string]string) { k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName)) } -// CreatePortForwardTunnel returns the local address:port of a tunnel to the consul server pod in the given release. -func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int, release ...string) string { - releaseName := h.releaseName - if len(release) > 0 { - releaseName = release[0] - } - serverPod := fmt.Sprintf("%s-consul-server-0", releaseName) +func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int) string { + serverPod := fmt.Sprintf("%s-consul-server-0", h.releaseName) return portforward.CreateTunnelToResourcePort(t, serverPod, remotePort, h.helmOptions.KubectlOptions, h.logger) } -// ResourceClient returns a resource service grpc client for the given helm release. -func (h *HelmCluster) ResourceClient(t *testing.T, secure bool, release ...string) (client pbresource.ResourceServiceClient) { - if secure { - panic("TODO: add support for secure resource client") - } - releaseName := h.releaseName - if len(release) > 0 { - releaseName = release[0] - } - - // TODO: get grpc port from somewhere - localTunnelAddr := h.CreatePortForwardTunnel(t, 8502, releaseName) - - // Create a grpc connection to the server pod. - grpcConn, err := grpc.Dial(localTunnelAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - require.NoError(t, err) - resourceClient := pbresource.NewResourceServiceClient(grpcConn) - return resourceClient -} - -func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool, release ...string) (client *api.Client, configAddress string) { +func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool) (client *api.Client, configAddress string) { t.Helper() - releaseName := h.releaseName - if len(release) > 0 { - releaseName = release[0] - } - namespace := h.helmOptions.KubectlOptions.Namespace config := api.DefaultConfig() remotePort := 8500 // use non-secure by default @@ -528,15 +331,15 @@ func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool, release ...st if h.ACLToken != "" { config.Token = h.ACLToken } else { - retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 600}, t, func(r *retry.R) { // Get the ACL token. First, attempt to read it from the bootstrap token (this will be true in primary Consul servers). // If the bootstrap token doesn't exist, it means we are running against a secondary cluster // and will try to read the replication token from the federation secret. // In secondary servers, we don't create a bootstrap token since ACLs are only bootstrapped in the primary. // Instead, we provide a replication token that serves the role of the bootstrap token. - aclSecret, err := h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), releaseName+"-consul-bootstrap-acl-token", metav1.GetOptions{}) + aclSecret, err := h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), h.releaseName+"-consul-bootstrap-acl-token", metav1.GetOptions{}) if err != nil && errors.IsNotFound(err) { - federationSecret := fmt.Sprintf("%s-consul-federation", releaseName) + federationSecret := fmt.Sprintf("%s-consul-federation", h.releaseName) aclSecret, err = h.kubernetesClient.CoreV1().Secrets(namespace).Get(context.Background(), federationSecret, metav1.GetOptions{}) require.NoError(r, err) config.Token = string(aclSecret.Data["replicationToken"]) @@ -550,7 +353,7 @@ func (h *HelmCluster) SetupConsulClient(t *testing.T, secure bool, release ...st } } - config.Address = h.CreatePortForwardTunnel(t, remotePort, release...) + config.Address = h.CreatePortForwardTunnel(t, remotePort) consulClient, err := api.NewClient(config) require.NoError(t, err) @@ -657,7 +460,7 @@ func configurePodSecurityPolicies(t *testing.T, client kubernetes.Interface, cfg } } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { _ = client.PolicyV1beta1().PodSecurityPolicies().Delete(context.Background(), pspName, metav1.DeleteOptions{}) _ = client.RbacV1().ClusterRoles().Delete(context.Background(), pspName, metav1.DeleteOptions{}) _ = client.RbacV1().RoleBindings(namespace).Delete(context.Background(), pspName, metav1.DeleteOptions{}) @@ -737,7 +540,7 @@ func configureSCCs(t *testing.T, client kubernetes.Interface, cfg *config.TestCo } } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { _ = client.RbacV1().RoleBindings(namespace).Delete(context.Background(), anyuidRoleBinding, metav1.DeleteOptions{}) _ = client.RbacV1().RoleBindings(namespace).Delete(context.Background(), privilegedRoleBinding, metav1.DeleteOptions{}) }) @@ -761,7 +564,7 @@ func defaultValues() map[string]string { func CreateK8sSecret(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace, secretName, secretKey, secret string) { - retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 15}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { _, err := client.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) if errors.IsNotFound(err) { _, err := client.CoreV1().Secrets(namespace).Create(context.Background(), &corev1.Secret{ @@ -779,7 +582,7 @@ func CreateK8sSecret(t *testing.T, client kubernetes.Interface, cfg *config.Test } }) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { _ = client.CoreV1().Secrets(namespace).Delete(context.Background(), secretName, metav1.DeleteOptions{}) }) } diff --git a/acceptance/framework/consul/helm_cluster_test.go b/acceptance/framework/consul/helm_cluster_test.go index 3544718c9e..1f7b8b7d58 100644 --- a/acceptance/framework/consul/helm_cluster_test.go +++ b/acceptance/framework/consul/helm_cluster_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consul import ( @@ -9,12 +6,9 @@ import ( "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" - "sigs.k8s.io/controller-runtime/pkg/client" - runtimefake "sigs.k8s.io/controller-runtime/pkg/client/fake" ) // Test that if TestConfig has values that need to be provided @@ -79,17 +73,14 @@ func (c *ctx) Name() string { return "" } -func (c *ctx) KubectlOptions(_ testutil.TestingTB) *k8s.KubectlOptions { +func (c *ctx) KubectlOptions(_ *testing.T) *k8s.KubectlOptions { return &k8s.KubectlOptions{} } func (c *ctx) KubectlOptionsForNamespace(ns string) *k8s.KubectlOptions { return &k8s.KubectlOptions{} } -func (c *ctx) KubernetesClient(_ testutil.TestingTB) kubernetes.Interface { +func (c *ctx) KubernetesClient(_ *testing.T) kubernetes.Interface { return fake.NewSimpleClientset() } -func (c *ctx) ControllerRuntimeClient(_ testutil.TestingTB) client.Client { - return runtimefake.NewClientBuilder().Build() -} var _ environment.TestContext = (*ctx)(nil) diff --git a/acceptance/framework/environment/cni-kind/custom-resources.yaml b/acceptance/framework/environment/cni-kind/custom-resources.yaml index 9a87195041..1d2e08da56 100644 --- a/acceptance/framework/environment/cni-kind/custom-resources.yaml +++ b/acceptance/framework/environment/cni-kind/custom-resources.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # This section includes base Calico installation configuration. # For more information, see: https://projectcalico.docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Installation apiVersion: operator.tigera.io/v1 diff --git a/acceptance/framework/environment/cni-kind/tigera-operator.yaml b/acceptance/framework/environment/cni-kind/tigera-operator.yaml index 8114b6f596..f13e65ceb0 100644 --- a/acceptance/framework/environment/cni-kind/tigera-operator.yaml +++ b/acceptance/framework/environment/cni-kind/tigera-operator.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Namespace metadata: diff --git a/acceptance/framework/environment/environment.go b/acceptance/framework/environment/environment.go index 9ceecf3e96..96245cfde3 100644 --- a/acceptance/framework/environment/environment.go +++ b/acceptance/framework/environment/environment.go @@ -1,22 +1,14 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package environment import ( "fmt" + "testing" + "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/config" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) const ( @@ -26,17 +18,17 @@ const ( // TestEnvironment represents the infrastructure environment of the test, // such as the kubernetes cluster(s) the test is running against. type TestEnvironment interface { - DefaultContext(t testutil.TestingTB) TestContext - Context(t testutil.TestingTB, index int) TestContext + DefaultContext(t *testing.T) TestContext + Context(t *testing.T, index int) TestContext } // TestContext represents a specific context a test needs, // for example, information about a specific Kubernetes cluster. type TestContext interface { - KubectlOptions(t testutil.TestingTB) *k8s.KubectlOptions + KubectlOptions(t *testing.T) *k8s.KubectlOptions + // TODO: I don't love this. KubectlOptionsForNamespace(ns string) *k8s.KubectlOptions - KubernetesClient(t testutil.TestingTB) kubernetes.Interface - ControllerRuntimeClient(t testutil.TestingTB) client.Client + KubernetesClient(t *testing.T) kubernetes.Interface } type KubernetesEnvironment struct { @@ -64,13 +56,13 @@ func NewKubernetesEnvironmentFromConfig(config *config.TestConfig) *KubernetesEn return kenv } -func (k *KubernetesEnvironment) Context(t testutil.TestingTB, index int) TestContext { +func (k *KubernetesEnvironment) Context(t *testing.T, index int) TestContext { lenContexts := len(k.contexts) require.Greater(t, lenContexts, index, fmt.Sprintf("context list does not contain an index %d, length is %d", index, lenContexts)) return k.contexts[index] } -func (k *KubernetesEnvironment) DefaultContext(t testutil.TestingTB) TestContext { +func (k *KubernetesEnvironment) DefaultContext(t *testing.T) TestContext { lenContexts := len(k.contexts) require.Greater(t, lenContexts, DefaultContextIndex, fmt.Sprintf("context list does not contain an index %d, length is %d", DefaultContextIndex, lenContexts)) return k.contexts[DefaultContextIndex] @@ -81,16 +73,16 @@ type kubernetesContext struct { kubeContextName string namespace string - client kubernetes.Interface - runtimeClient client.Client - + client kubernetes.Interface options *k8s.KubectlOptions } // KubernetesContextFromOptions returns the Kubernetes context from options. // If context is explicitly set in options, it returns that context. // Otherwise, it returns the current context. -func KubernetesContextFromOptions(t testutil.TestingTB, options *k8s.KubectlOptions) string { +func KubernetesContextFromOptions(t *testing.T, options *k8s.KubectlOptions) string { + t.Helper() + // First, check if context set in options and return that if options.ContextName != "" { return options.ContextName @@ -106,7 +98,7 @@ func KubernetesContextFromOptions(t testutil.TestingTB, options *k8s.KubectlOpti return rawConfig.CurrentContext } -func (k kubernetesContext) KubectlOptions(t testutil.TestingTB) *k8s.KubectlOptions { +func (k kubernetesContext) KubectlOptions(t *testing.T) *k8s.KubectlOptions { if k.options != nil { return k.options } @@ -145,7 +137,7 @@ func (k kubernetesContext) KubectlOptionsForNamespace(ns string) *k8s.KubectlOpt } // KubernetesClientFromOptions takes KubectlOptions and returns Kubernetes API client. -func KubernetesClientFromOptions(t testutil.TestingTB, options *k8s.KubectlOptions) kubernetes.Interface { +func KubernetesClientFromOptions(t *testing.T, options *k8s.KubectlOptions) kubernetes.Interface { configPath, err := options.GetConfigPath(t) require.NoError(t, err) @@ -158,7 +150,7 @@ func KubernetesClientFromOptions(t testutil.TestingTB, options *k8s.KubectlOptio return client } -func (k kubernetesContext) KubernetesClient(t testutil.TestingTB) kubernetes.Interface { +func (k kubernetesContext) KubernetesClient(t *testing.T) kubernetes.Interface { if k.client != nil { return k.client } @@ -168,31 +160,6 @@ func (k kubernetesContext) KubernetesClient(t testutil.TestingTB) kubernetes.Int return k.client } -func (k kubernetesContext) ControllerRuntimeClient(t testutil.TestingTB) client.Client { - if k.runtimeClient != nil { - return k.runtimeClient - } - - options := k.KubectlOptions(t) - configPath, err := options.GetConfigPath(t) - require.NoError(t, err) - config, err := k8s.LoadApiClientConfigE(configPath, options.ContextName) - require.NoError(t, err) - - s := runtime.NewScheme() - require.NoError(t, clientgoscheme.AddToScheme(s)) - require.NoError(t, gwv1alpha2.Install(s)) - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - client, err := client.New(config, client.Options{Scheme: s}) - require.NoError(t, err) - - k.runtimeClient = client - - return k.runtimeClient -} - func NewContext(namespace, pathToKubeConfig, kubeContextName string) *kubernetesContext { return &kubernetesContext{ namespace: namespace, diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index c68983fe8c..1cca528e05 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -39,22 +39,15 @@ type TestFlags struct { flagConsulVersion string flagConsulDataplaneVersion string flagEnvoyImage string - flagConsulCollectorImage string - flagVaultHelmChartVersion string - flagVaultServerVersion string - - flagHCPResourceID string flagNoCleanupOnFailure bool - flagNoCleanup bool flagDebugDirectory string - flagUseAKS bool - flagUseEKS bool - flagUseGKE bool - flagUseGKEAutopilot bool - flagUseKind bool + flagUseAKS bool + flagUseEKS bool + flagUseGKE bool + flagUseKind bool flagDisablePeering bool @@ -88,16 +81,12 @@ func (t *TestFlags) init() { flag.StringVar(&t.flagConsulDataplaneVersion, "consul-dataplane-version", "", "The consul-dataplane version used for all tests.") flag.StringVar(&t.flagHelmChartVersion, "helm-chart-version", config.HelmChartPath, "The helm chart used for all tests.") flag.StringVar(&t.flagEnvoyImage, "envoy-image", "", "The Envoy image to use for all tests.") - flag.StringVar(&t.flagConsulCollectorImage, "consul-collector-image", "", "The consul collector image to use for all tests.") - flag.StringVar(&t.flagVaultServerVersion, "vault-server-version", "", "The vault serverversion used for all tests.") - flag.StringVar(&t.flagVaultHelmChartVersion, "vault-helm-chart-version", "", "The Vault helm chart used for all tests.") flag.Var(&t.flagKubeconfigs, "kubeconfigs", "The list of paths to a kubeconfig files. If this is blank, "+ "the default kubeconfig path (~/.kube/config) will be used.") flag.Var(&t.flagKubecontexts, "kube-contexts", "The list of names of the Kubernetes contexts to use. If this is blank, "+ "the context set as the current context will be used by default.") flag.Var(&t.flagKubeNamespaces, "kube-namespaces", "The list of Kubernetes namespaces to use for tests.") - flag.StringVar(&t.flagHCPResourceID, "hcp-resource-id", "", "The hcp resource id to use for all tests.") flag.BoolVar(&t.flagEnableMultiCluster, "enable-multi-cluster", false, "If true, the tests that require multiple Kubernetes clusters will be run. "+ @@ -134,9 +123,6 @@ func (t *TestFlags) init() { "If true, the tests will not cleanup Kubernetes resources they create when they finish running."+ "Note this flag must be run with -failfast flag, otherwise subsequent tests will fail.") - flag.BoolVar(&t.flagNoCleanup, "no-cleanup", false, - "If true, the tests will not cleanup Kubernetes resources for Vault test") - flag.StringVar(&t.flagDebugDirectory, "debug-directory", "", "The directory where to write debug information about failed test runs, "+ "such as logs and pod definitions. If not provided, a temporary directory will be created by the tests.") @@ -146,9 +132,6 @@ func (t *TestFlags) init() { "If true, the tests will assume they are running against an EKS cluster(s).") flag.BoolVar(&t.flagUseGKE, "use-gke", false, "If true, the tests will assume they are running against a GKE cluster(s).") - flag.BoolVar(&t.flagUseGKEAutopilot, "use-gke-autopilot", false, - "If true, the tests will assume they are running against a GKE Autopilot cluster(s).") - flag.BoolVar(&t.flagUseKind, "use-kind", false, "If true, the tests will assume they are running against a local kind cluster(s).") @@ -202,7 +185,6 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { kubeEnvs := config.NewKubeTestConfigList(t.flagKubeconfigs, t.flagKubecontexts, t.flagKubeNamespaces) c := &config.TestConfig{ - EnableEnterprise: t.flagEnableEnterprise, EnterpriseLicense: t.flagEnterpriseLicense, @@ -227,19 +209,12 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { ConsulVersion: consulVersion, ConsulDataplaneVersion: consulDataplaneVersion, EnvoyImage: t.flagEnvoyImage, - ConsulCollectorImage: t.flagConsulCollectorImage, - VaultHelmChartVersion: t.flagVaultHelmChartVersion, - VaultServerVersion: t.flagVaultServerVersion, - - HCPResourceID: t.flagHCPResourceID, NoCleanupOnFailure: t.flagNoCleanupOnFailure, - NoCleanup: t.flagNoCleanup, DebugDirectory: tempDir, UseAKS: t.flagUseAKS, UseEKS: t.flagUseEKS, UseGKE: t.flagUseGKE, - UseGKEAutopilot: t.flagUseGKEAutopilot, UseKind: t.flagUseKind, } diff --git a/acceptance/framework/flags/flags_test.go b/acceptance/framework/flags/flags_test.go index 1e2bf0a039..05e9ab19c0 100644 --- a/acceptance/framework/flags/flags_test.go +++ b/acceptance/framework/flags/flags_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( diff --git a/acceptance/framework/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index 2ef01df26a..5f99a682ae 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helpers import ( @@ -18,7 +15,6 @@ import ( "github.com/gruntwork-io/terratest/modules/random" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -39,12 +35,12 @@ func CheckForPriorInstallations(t *testing.T, client kubernetes.Interface, optio // Check if there's an existing cluster and fail if there is one. // We may need to retry since this is the first command run once the Kube // cluster is created and sometimes the API server returns errors. - retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 15}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 15}, t, func(r *retry.R) { var err error // NOTE: It's okay to pass in `t` to RunHelmCommandAndGetOutputE despite being in a retry // because we're using RunHelmCommandAndGetOutputE (not RunHelmCommandAndGetOutput) so the `t` won't // get used to fail the test, just for logging. - helmListOutput, err = helm.RunHelmCommandAndGetOutputE(r, options, "list", "--output", "json") + helmListOutput, err = helm.RunHelmCommandAndGetOutputE(t, options, "list", "--output", "json") require.NoError(r, err) }) @@ -59,7 +55,7 @@ func CheckForPriorInstallations(t *testing.T, client kubernetes.Interface, optio // Wait for all pods in the "default" namespace to exit. A previous // release may not be listed by Helm but its pods may still be terminating. - retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 60}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 60}, t, func(r *retry.R) { pods, err := client.CoreV1().Pods(options.KubectlOptions.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector}) require.NoError(r, err) if len(pods.Items) > 0 { @@ -85,9 +81,10 @@ func SetupInterruptHandler(cleanup func()) { }() } -// Cleanup will both register a cleanup function with t and SetupInterruptHandler to make sure resources -// get cleaned up if an interrupt signal is caught. -func Cleanup(t testutil.TestingTB, noCleanupOnFailure bool, noCleanup bool, cleanup func()) { +// Cleanup will both register a cleanup function with t +// and SetupInterruptHandler to make sure resources get cleaned up +// if an interrupt signal is caught. +func Cleanup(t *testing.T, noCleanupOnFailure bool, cleanup func()) { t.Helper() // Always clean up when an interrupt signal is caught. @@ -97,7 +94,7 @@ func Cleanup(t testutil.TestingTB, noCleanupOnFailure bool, noCleanup bool, clea // We need to wrap the cleanup function because t that is passed in to this function // might not have the information on whether the test has failed yet. wrappedCleanupFunc := func() { - if !((noCleanupOnFailure && t.Failed()) || noCleanup) { + if !(noCleanupOnFailure && t.Failed()) { logger.Logf(t, "cleaning up resources for %s", t.Name()) cleanup() } else { @@ -150,38 +147,3 @@ func MergeMaps(a, b map[string]string) { a[k] = v } } - -// RegisterExternalService registers an external service to a virtual node in Consul for testing purposes. -// This function takes a testing.T object, a Consul client, service namespace, service name, address, and port as -// parameters. It registers the service with Consul, and if a namespace is provided, it also creates the namespace -// in Consul. It uses the provided testing.T object to log registration details and verify the registration process. -// If the registration fails, the test calling the function will fail. -func RegisterExternalService(t *testing.T, consulClient *api.Client, namespace, name, address string, port int) { - t.Helper() - - service := &api.AgentService{ - ID: name, - Service: name, - Port: port, - } - - if namespace != "" { - address = fmt.Sprintf("%s.%s", name, namespace) - service.Namespace = namespace - - logger.Logf(t, "creating the %s namespace in Consul", namespace) - _, _, err := consulClient.Namespaces().Create(&api.Namespace{ - Name: namespace, - }, nil) - require.NoError(t, err) - } - - logger.Log(t, "registering the external service %s", name) - _, err := consulClient.Catalog().Register(&api.CatalogRegistration{ - Node: "external", - Address: address, - NodeMeta: map[string]string{"external-node": "true", "external-probe": "true"}, - Service: service, - }, nil) - require.NoError(t, err) -} diff --git a/acceptance/framework/helpers/helpers_test.go b/acceptance/framework/helpers/helpers_test.go index 2a994d7f9f..c1b4a916e2 100644 --- a/acceptance/framework/helpers/helpers_test.go +++ b/acceptance/framework/helpers/helpers_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helpers import ( diff --git a/acceptance/framework/k8s/debug.go b/acceptance/framework/k8s/debug.go index 773769fced..5bf588f959 100644 --- a/acceptance/framework/k8s/debug.go +++ b/acceptance/framework/k8s/debug.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package k8s import ( diff --git a/acceptance/framework/k8s/deploy.go b/acceptance/framework/k8s/deploy.go index a877dff54e..869ebdd804 100644 --- a/acceptance/framework/k8s/deploy.go +++ b/acceptance/framework/k8s/deploy.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package k8s import ( @@ -16,13 +13,12 @@ import ( "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" v1 "k8s.io/api/apps/v1" - batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/util/yaml" ) // Deploy creates a Kubernetes deployment by applying configuration stored at filepath, // sets up a cleanup function and waits for the deployment to become available. -func Deploy(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, filepath string) { +func Deploy(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, debugDirectory string, filepath string) { t.Helper() KubectlApply(t, options, filepath) @@ -34,7 +30,7 @@ func Deploy(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, err = yaml.NewYAMLOrJSONDecoder(file, 1024).Decode(&deployment) require.NoError(t, err) - helpers.Cleanup(t, noCleanupOnFailure, noCleanup, func() { + helpers.Cleanup(t, noCleanupOnFailure, func() { // Note: this delete command won't wait for pods to be fully terminated. // This shouldn't cause any test pollution because the underlying // objects are deployments, and so when other tests create these @@ -48,7 +44,7 @@ func Deploy(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, // DeployKustomize creates a Kubernetes deployment by applying the kustomize directory stored at kustomizeDir, // sets up a cleanup function and waits for the deployment to become available. -func DeployKustomize(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, kustomizeDir string) { +func DeployKustomize(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, debugDirectory string, kustomizeDir string) { t.Helper() KubectlApplyK(t, options, kustomizeDir) @@ -60,7 +56,7 @@ func DeployKustomize(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailu err = yaml.NewYAMLOrJSONDecoder(strings.NewReader(output), 1024).Decode(&deployment) require.NoError(t, err) - helpers.Cleanup(t, noCleanupOnFailure, noCleanup, func() { + helpers.Cleanup(t, noCleanupOnFailure, func() { // Note: this delete command won't wait for pods to be fully terminated. // This shouldn't cause any test pollution because the underlying // objects are deployments, and so when other tests create these @@ -73,32 +69,6 @@ func DeployKustomize(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailu RunKubectl(t, options, "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", deployment.Name)) } -func DeployJob(t *testing.T, options *k8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory, kustomizeDir string) { - t.Helper() - - KubectlApplyK(t, options, kustomizeDir) - - output, err := RunKubectlAndGetOutputE(t, options, "kustomize", kustomizeDir) - require.NoError(t, err) - - job := batchv1.Job{} - err = yaml.NewYAMLOrJSONDecoder(strings.NewReader(output), 1024).Decode(&job) - require.NoError(t, err) - - helpers.Cleanup(t, noCleanupOnFailure, noCleanup, func() { - // Note: this delete command won't wait for pods to be fully terminated. - // This shouldn't cause any test pollution because the underlying - // objects are deployments, and so when other tests create these - // they should have different pod names. - WritePodsDebugInfoIfFailed(t, options, debugDirectory, labelMapToString(job.GetLabels())) - KubectlDeleteK(t, options, kustomizeDir) - }) - logger.Log(t, "job deployed") - - // Because Jobs don't have a "started" condition, we have to check the status of the Pods they create. - RunKubectl(t, options, "wait", "--for=condition=Ready", "--timeout=5m", "pods", "--selector", fmt.Sprintf("job-name=%s", job.Name)) -} - // CheckStaticServerConnection execs into a pod of sourceApp // and runs a curl command with the provided curlArgs. // This function assumes that the connection is made to the static-server and expects the output @@ -120,10 +90,7 @@ func CheckStaticServerConnection(t *testing.T, options *k8s.KubectlOptions, sour // on the existence of any of them. func CheckStaticServerConnectionMultipleFailureMessages(t *testing.T, options *k8s.KubectlOptions, sourceApp string, expectSuccess bool, failureMessages []string, expectedSuccessOutput string, curlArgs ...string) { t.Helper() - resourceType := "deploy/" - if sourceApp == "job-client" { - resourceType = "jobs/" - } + expectedOutput := "hello world" if expectedSuccessOutput != "" { expectedOutput = expectedSuccessOutput @@ -131,11 +98,11 @@ func CheckStaticServerConnectionMultipleFailureMessages(t *testing.T, options *k retrier := &retry.Timer{Timeout: 320 * time.Second, Wait: 2 * time.Second} - args := []string{"exec", resourceType + sourceApp, "-c", sourceApp, "--", "curl", "-vvvsSf"} + args := []string{"exec", "deploy/" + sourceApp, "-c", sourceApp, "--", "curl", "-vvvsSf"} args = append(args, curlArgs...) retry.RunWith(retrier, t, func(r *retry.R) { - output, err := RunKubectlAndGetOutputE(r, options, args...) + output, err := RunKubectlAndGetOutputE(t, options, args...) if expectSuccess { require.NoError(r, err) require.Contains(r, output, expectedOutput) @@ -183,15 +150,6 @@ func CheckStaticServerConnectionFailing(t *testing.T, options *k8s.KubectlOption }, "", curlArgs...) } -// CheckStaticServerHTTPConnectionFailing is just like CheckStaticServerConnectionFailing -// except with HTTP-based intentions. -func CheckStaticServerHTTPConnectionFailing(t *testing.T, options *k8s.KubectlOptions, sourceApp string, curlArgs ...string) { - t.Helper() - CheckStaticServerConnection(t, options, sourceApp, false, []string{ - "curl: (22) The requested URL returned error: 403", - }, "", curlArgs...) -} - // labelMapToString takes a label map[string]string // and returns the string-ified version of, e.g app=foo,env=dev. func labelMapToString(labelMap map[string]string) string { diff --git a/acceptance/framework/k8s/helpers.go b/acceptance/framework/k8s/helpers.go index ca1cba9d84..7c3205928f 100644 --- a/acceptance/framework/k8s/helpers.go +++ b/acceptance/framework/k8s/helpers.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package k8s import ( @@ -35,18 +32,18 @@ func KubernetesAPIServerHostFromOptions(t *testing.T, options *terratestk8s.Kube } // WaitForAllPodsToBeReady waits until all pods with the provided podLabelSelector -// are in the ready status. It checks every 2 second for 20 minutes. +// are in the ready status. It checks every second for 11 minutes. // If there is at least one container in a pod that isn't ready after that, // it fails the test. func WaitForAllPodsToBeReady(t *testing.T, client kubernetes.Interface, namespace, podLabelSelector string) { t.Helper() - // Wait up to 20m. + logger.Logf(t, "Waiting for pods with label %q to be ready.", podLabelSelector) + + // Wait up to 11m. // On Azure, volume provisioning can sometimes take close to 5 min, // so we need to give a bit more time for pods to become healthy. - counter := &retry.Counter{Count: 10 * 60, Wait: 2 * time.Second} - logger.Logf(t, "Waiting %s for pods with label %q to be ready.", time.Duration(counter.Count*int(counter.Wait)), podLabelSelector) - + counter := &retry.Counter{Count: 11 * 60, Wait: 1 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { pods, err := client.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: podLabelSelector}) require.NoError(r, err) @@ -113,9 +110,9 @@ func ServiceHost(t *testing.T, cfg *config.TestConfig, ctx environment.TestConte var host string // It can take some time for the load balancers to be ready and have an IP/Hostname. // Wait for 5 minutes before failing. - retry.RunWith(&retry.Counter{Wait: 2 * time.Second, Count: 600}, t, func(r *retry.R) { - svc, err := ctx.KubernetesClient(r).CoreV1().Services(ctx.KubectlOptions(r).Namespace).Get(context.Background(), serviceName, metav1.GetOptions{}) - require.NoError(r, err) + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 600}, t, func(r *retry.R) { + svc, err := ctx.KubernetesClient(t).CoreV1().Services(ctx.KubectlOptions(t).Namespace).Get(context.Background(), serviceName, metav1.GetOptions{}) + require.NoError(t, err) require.NotEmpty(r, svc.Status.LoadBalancer.Ingress) // On AWS, load balancers have a hostname for ingress, while on Azure and GCP // load balancers have IPs. @@ -135,7 +132,7 @@ func CopySecret(t *testing.T, sourceContext, destContext environment.TestContext var secret *corev1.Secret var err error retry.Run(t, func(r *retry.R) { - secret, err = sourceContext.KubernetesClient(r).CoreV1().Secrets(sourceContext.KubectlOptions(r).Namespace).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = sourceContext.KubernetesClient(t).CoreV1().Secrets(sourceContext.KubectlOptions(t).Namespace).Get(context.Background(), secretName, metav1.GetOptions{}) secret.ResourceVersion = "" require.NoError(r, err) }) diff --git a/acceptance/framework/k8s/kubectl.go b/acceptance/framework/k8s/kubectl.go index e02db4c474..e766395f47 100644 --- a/acceptance/framework/k8s/kubectl.go +++ b/acceptance/framework/k8s/kubectl.go @@ -1,10 +1,6 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package k8s import ( - "fmt" "strings" "testing" "time" @@ -13,15 +9,10 @@ import ( terratestLogger "github.com/gruntwork-io/terratest/modules/logger" "github.com/gruntwork-io/terratest/modules/shell" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" ) -const ( - kubectlTimeout = "--timeout=120s" -) - // kubeAPIConnectErrs are errors that sometimes occur when talking to the // Kubernetes API related to connection issues. var kubeAPIConnectErrs = []string{ @@ -35,14 +26,14 @@ var kubeAPIConnectErrs = []string{ // RunKubectlAndGetOutputE runs an arbitrary kubectl command provided via args // and returns its output and error. -func RunKubectlAndGetOutputE(t testutil.TestingTB, options *k8s.KubectlOptions, args ...string) (string, error) { +func RunKubectlAndGetOutputE(t *testing.T, options *k8s.KubectlOptions, args ...string) (string, error) { return RunKubectlAndGetOutputWithLoggerE(t, options, terratestLogger.New(logger.TestLogger{}), args...) } // RunKubectlAndGetOutputWithLoggerE is the same as RunKubectlAndGetOutputE but // it also allows you to provide a custom logger. This is useful if the command output // contains sensitive information, for example, when you can pass logger.Discard. -func RunKubectlAndGetOutputWithLoggerE(t testutil.TestingTB, options *k8s.KubectlOptions, logger *terratestLogger.Logger, args ...string) (string, error) { +func RunKubectlAndGetOutputWithLoggerE(t *testing.T, options *k8s.KubectlOptions, logger *terratestLogger.Logger, args ...string) (string, error) { var cmdArgs []string if options.ContextName != "" { cmdArgs = append(cmdArgs, "--context", options.ContextName) @@ -62,13 +53,13 @@ func RunKubectlAndGetOutputWithLoggerE(t testutil.TestingTB, options *k8s.Kubect } counter := &retry.Counter{ - Count: 10, + Count: 3, Wait: 1 * time.Second, } var output string var err error retry.RunWith(counter, t, func(r *retry.R) { - output, err = shell.RunCommandAndGetOutputE(r, command) + output, err = shell.RunCommandAndGetOutputE(t, command) if err != nil { // Want to retry on errors connecting to actual Kube API because // these are intermittent. @@ -103,7 +94,7 @@ func KubectlApplyK(t *testing.T, options *k8s.KubectlOptions, kustomizeDir strin // deletes it from the cluster by running 'kubectl delete -f'. // If there's an error deleting the file, fail the test. func KubectlDelete(t *testing.T, options *k8s.KubectlOptions, configPath string) { - _, err := RunKubectlAndGetOutputE(t, options, "delete", kubectlTimeout, "-f", configPath) + _, err := RunKubectlAndGetOutputE(t, options, "delete", "--timeout=60s", "-f", configPath) require.NoError(t, err) } @@ -113,21 +104,7 @@ func KubectlDelete(t *testing.T, options *k8s.KubectlOptions, configPath string) func KubectlDeleteK(t *testing.T, options *k8s.KubectlOptions, kustomizeDir string) { // Ignore not found errors because Kubernetes automatically cleans up the kube secrets that we deployed // referencing the ServiceAccount when it is deleted. - _, err := RunKubectlAndGetOutputE(t, options, "delete", kubectlTimeout, "--ignore-not-found", "-k", kustomizeDir) - require.NoError(t, err) -} - -// KubectlScale takes a deployment and scales it to the provided number of replicas. -func KubectlScale(t *testing.T, options *k8s.KubectlOptions, deployment string, replicas int) { - _, err := RunKubectlAndGetOutputE(t, options, "scale", kubectlTimeout, fmt.Sprintf("--replicas=%d", replicas), deployment) - require.NoError(t, err) -} - -// KubectlLabel takes an object and applies the given label to it. -// Example: `KubectlLabel(t, options, "node", nodeId, corev1.LabelTopologyRegion, "us-east-1")`. -func KubectlLabel(t *testing.T, options *k8s.KubectlOptions, objectType string, objectId string, key string, value string) { - // `kubectl label` doesn't support timeouts - _, err := RunKubectlAndGetOutputE(t, options, "label", objectType, objectId, "--overwrite", fmt.Sprintf("%s=%s", key, value)) + _, err := RunKubectlAndGetOutputE(t, options, "delete", "--timeout=60s", "--ignore-not-found", "-k", kustomizeDir) require.NoError(t, err) } diff --git a/acceptance/framework/logger/logger.go b/acceptance/framework/logger/logger.go index 34bc0e8a6c..c186e77436 100644 --- a/acceptance/framework/logger/logger.go +++ b/acceptance/framework/logger/logger.go @@ -1,23 +1,21 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package logger import ( "fmt" - "github.com/hashicorp/consul/sdk/testutil" + "testing" "time" - terratesting "github.com/gruntwork-io/terratest/modules/testing" + terratestTesting "github.com/gruntwork-io/terratest/modules/testing" ) -// TestLogger implements Terratest's TestLogger interface so that we can pass it to Terratest objects to have consistent -// logging across all tests. +// TestLogger implements terratest's TestLogger interface +// so that we can pass it to terratest objects to have consistent logging +// across all tests. type TestLogger struct{} // Logf takes a format string and args and calls Logf function. -func (tl TestLogger) Logf(t terratesting.TestingT, format string, args ...any) { - tt, ok := t.(testutil.TestingTB) +func (tl TestLogger) Logf(t terratestTesting.TestingT, format string, args ...interface{}) { + tt, ok := t.(*testing.T) if !ok { t.Error("failed to cast") } @@ -26,18 +24,20 @@ func (tl TestLogger) Logf(t terratesting.TestingT, format string, args ...any) { Logf(tt, format, args...) } -// Logf takes a format string and args and logs formatted string with a timestamp. -func Logf(t testutil.TestingTB, format string, args ...any) { +// Logf takes a format string and args and logs +// formatted string with a timestamp. +func Logf(t *testing.T, format string, args ...interface{}) { t.Helper() log := fmt.Sprintf(format, args...) Log(t, log) } -// Log calls t.Log or r.Log, adding an RFC3339 timestamp to the beginning of the log line. -func Log(t testutil.TestingTB, args ...any) { +// Log calls t.Log, adding an RFC3339 timestamp to the beginning of the log line. +func Log(t *testing.T, args ...interface{}) { t.Helper() - allArgs := []any{time.Now().Format(time.RFC3339)} + + allArgs := []interface{}{time.Now().Format(time.RFC3339)} allArgs = append(allArgs, args...) t.Log(allArgs...) } diff --git a/acceptance/framework/portforward/port_forward.go b/acceptance/framework/portforward/port_forward.go index ac9795c98c..30f4aed157 100644 --- a/acceptance/framework/portforward/port_forward.go +++ b/acceptance/framework/portforward/port_forward.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package portforward import ( @@ -16,7 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -// CreateTunnelToResourcePort returns a local address:port that is tunneled to the given resource's port. func CreateTunnelToResourcePort(t *testing.T, resourceName string, remotePort int, options *terratestk8s.KubectlOptions, logger terratestLogger.TestLogger) string { localPort := terratestk8s.GetAvailablePort(t) tunnel := terratestk8s.NewTunnelWithLogger( @@ -28,11 +24,11 @@ func CreateTunnelToResourcePort(t *testing.T, resourceName string, remotePort in logger) // Retry creating the port forward since it can fail occasionally. - retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { + retry.RunWith(&retry.Counter{Wait: 3 * time.Second, Count: 60}, t, func(r *retry.R) { // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry // because we're using ForwardPortE (not ForwardPort) so the `t` won't // get used to fail the test, just for logging. - require.NoError(r, tunnel.ForwardPortE(r)) + require.NoError(r, tunnel.ForwardPortE(t)) }) doneChan := make(chan bool) diff --git a/acceptance/framework/suite/suite.go b/acceptance/framework/suite/suite.go index 5d93d2c23f..67dff4f587 100644 --- a/acceptance/framework/suite/suite.go +++ b/acceptance/framework/suite/suite.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package suite import ( diff --git a/acceptance/framework/vault/helpers.go b/acceptance/framework/vault/helpers.go index d086e10391..4726e246ae 100644 --- a/acceptance/framework/vault/helpers.go +++ b/acceptance/framework/vault/helpers.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package vault import ( @@ -170,20 +167,6 @@ func (config *KV2Secret) SaveSecretAndAddReadPolicy(t *testing.T, vaultClient *v path "%s" { capabilities = ["read"] }`, config.Path) - config.saveSecretAndAddPolicy(t, vaultClient, policy) -} - -// SaveSecretAndAddUpdatePolicy will create an update policy for the PolicyName -// on the KV2Secret and then will save the secret in the KV2 store. -func (config *KV2Secret) SaveSecretAndAddUpdatePolicy(t *testing.T, vaultClient *vapi.Client) { - policy := fmt.Sprintf(` - path "%s" { - capabilities = ["read", "update"] - }`, config.Path) - config.saveSecretAndAddPolicy(t, vaultClient, policy) -} - -func (config *KV2Secret) saveSecretAndAddPolicy(t *testing.T, vaultClient *vapi.Client, policy string) { // Create the Vault Policy for the secret. logger.Log(t, "Creating policy") err := vaultClient.Sys().PutPolicy(config.PolicyName, policy) diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index b43957c924..dd6cbe4a68 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -1,13 +1,8 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package vault import ( "context" "fmt" - "os" - "strings" "testing" "time" @@ -15,13 +10,11 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" terratestLogger "github.com/gruntwork-io/terratest/modules/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/config" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" vapi "github.com/hashicorp/vault/api" "github.com/stretchr/testify/require" @@ -48,7 +41,6 @@ type VaultCluster struct { kubernetesClient kubernetes.Interface noCleanupOnFailure bool - noCleanup bool debugDirectory string logger terratestLogger.TestLogger } @@ -59,45 +51,18 @@ func NewVaultCluster(t *testing.T, ctx environment.TestContext, cfg *config.Test logger := terratestLogger.New(logger.TestLogger{}) kopts := ctx.KubectlOptions(t) - ns := ctx.KubectlOptions(t).Namespace - - entstr := "-ent" values := defaultHelmValues(releaseName) if cfg.EnablePodSecurityPolicies { values["global.psp.enable"] = "true" } - vaultReleaseName := helpers.RandomName() - k8sClient := environment.KubernetesClientFromOptions(t, ctx.KubectlOptions(t)) - vaultLicenseSecretName := fmt.Sprintf("%s-enterprise-license", vaultReleaseName) - vaultLicenseSecretKey := "license" - - vaultEnterpriseLicense := os.Getenv("VAULT_LICENSE") - - if cfg.VaultServerVersion != "" { - - if strings.Contains(cfg.VaultServerVersion, entstr) { - - logger.Logf(t, "Creating secret for Vault license") - consul.CreateK8sSecret(t, k8sClient, cfg, ns, vaultLicenseSecretName, vaultLicenseSecretKey, vaultEnterpriseLicense) - - values["server.image.repository"] = "docker.mirror.hashicorp.services/hashicorp/vault-enterprise" - values["server.enterpriseLicense.secretName"] = vaultLicenseSecretName - values["server.enterpriseLicense.secretKey"] = vaultLicenseSecretKey - } - values["server.image.tag"] = cfg.VaultServerVersion - } - vaultHelmChartVersion := defaultVaultHelmChartVersion - if cfg.VaultHelmChartVersion != "" { - vaultHelmChartVersion = cfg.VaultHelmChartVersion - } helpers.MergeMaps(values, helmValues) vaultHelmOpts := &helm.Options{ SetValues: values, KubectlOptions: kopts, Logger: logger, - Version: vaultHelmChartVersion, + Version: defaultVaultHelmChartVersion, } helm.AddRepo(t, vaultHelmOpts, "hashicorp", "https://helm.releases.hashicorp.com") @@ -114,7 +79,6 @@ func NewVaultCluster(t *testing.T, ctx environment.TestContext, cfg *config.Test kubectlOptions: kopts, kubernetesClient: ctx.KubernetesClient(t), noCleanupOnFailure: cfg.NoCleanupOnFailure, - noCleanup: cfg.NoCleanup, debugDirectory: cfg.DebugDirectory, logger: logger, releaseName: releaseName, @@ -125,7 +89,7 @@ func NewVaultCluster(t *testing.T, ctx environment.TestContext, cfg *config.Test func (v *VaultCluster) VaultClient(*testing.T) *vapi.Client { return v.vaultClient } // SetupVaultClient sets up and returns a Vault Client. -func (v *VaultCluster) SetupVaultClient(t testutil.TestingTB) *vapi.Client { +func (v *VaultCluster) SetupVaultClient(t *testing.T) *vapi.Client { t.Helper() if v.vaultClient != nil { @@ -145,8 +109,12 @@ func (v *VaultCluster) SetupVaultClient(t testutil.TestingTB) *vapi.Client { remotePort, v.logger) - retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { - require.NoError(r, tunnel.ForwardPortE(r)) + // Retry creating the port forward since it can fail occasionally. + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 60}, t, func(r *retry.R) { + // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry + // because we're using ForwardPortE (not ForwardPort) so the `t` won't + // get used to fail the test, just for logging. + require.NoError(r, tunnel.ForwardPortE(t)) }) t.Cleanup(func() { @@ -198,7 +166,7 @@ func (v *VaultCluster) bootstrap(t *testing.T, vaultNamespace string) { }, Type: corev1.SecretTypeServiceAccountToken, }, metav1.CreateOptions{}) - require.NoError(r, err) + require.NoError(t, err) } }) v.ConfigureAuthMethod(t, v.vaultClient, "kubernetes", "https://kubernetes.default.svc", vaultServerServiceAccountName, namespace) @@ -246,7 +214,7 @@ func (v *VaultCluster) Create(t *testing.T, ctx environment.TestContext, vaultNa // Make sure we delete the cluster if we receive an interrupt signal and // register cleanup so that we delete the cluster when test finishes. - helpers.Cleanup(t, v.noCleanupOnFailure, v.noCleanup, func() { + helpers.Cleanup(t, v.noCleanupOnFailure, func() { v.Destroy(t) }) @@ -368,7 +336,7 @@ func (v *VaultCluster) createTLSCerts(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { - if !(v.noCleanupOnFailure || v.noCleanup) { + if !v.noCleanupOnFailure { // We're ignoring error here because secret deletion is best-effort. _ = v.kubernetesClient.CoreV1().Secrets(namespace).Delete(context.Background(), certSecretName(v.releaseName), metav1.DeleteOptions{}) _ = v.kubernetesClient.CoreV1().Secrets(namespace).Delete(context.Background(), CASecretName(v.releaseName), metav1.DeleteOptions{}) @@ -419,7 +387,7 @@ func (v *VaultCluster) initAndUnseal(t *testing.T) { require.Equal(r, corev1.PodRunning, serverPod.Status.Phase) // Set up the client so that we can make API calls to initialize and unseal. - v.vaultClient = v.SetupVaultClient(r) + v.vaultClient = v.SetupVaultClient(t) // Initialize Vault with 1 secret share. We don't need to // more key shares for this test installation. @@ -441,7 +409,7 @@ func (v *VaultCluster) initAndUnseal(t *testing.T) { rootTokenSecret := fmt.Sprintf("%s-vault-root-token", v.releaseName) v.logger.Logf(t, "saving Vault root token to %q Kubernetes secret", rootTokenSecret) - helpers.Cleanup(t, v.noCleanupOnFailure, v.noCleanup, func() { + helpers.Cleanup(t, v.noCleanupOnFailure, func() { _ = v.kubernetesClient.CoreV1().Secrets(namespace).Delete(context.Background(), rootTokenSecret, metav1.DeleteOptions{}) }) _, err := v.kubernetesClient.CoreV1().Secrets(namespace).Create(context.Background(), &corev1.Secret{ diff --git a/acceptance/go.mod b/acceptance/go.mod index 8be66a29da..139cf0c087 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -1,153 +1,105 @@ module github.com/hashicorp/consul-k8s/acceptance -go 1.20 - -replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f - -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3 +go 1.19 require ( - github.com/google/uuid v1.3.0 github.com/gruntwork-io/terratest v0.31.2 - github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 - github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 - github.com/hashicorp/consul/proto-public v0.5.1 - github.com/hashicorp/consul/sdk v0.15.0 - github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3 + github.com/hashicorp/consul/api v1.18.2 + github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/hcp-sdk-go v0.50.0 - github.com/hashicorp/serf v0.10.1 - github.com/hashicorp/vault/api v1.8.3 + github.com/hashicorp/vault/api v1.2.0 github.com/stretchr/testify v1.8.3 - go.opentelemetry.io/proto/otlp v1.0.0 - google.golang.org/grpc v1.56.3 - google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.26.12 - k8s.io/apimachinery v0.26.12 - k8s.io/client-go v0.26.12 - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 - sigs.k8s.io/controller-runtime v0.14.7 - sigs.k8s.io/gateway-api v0.7.1 + k8s.io/api v0.22.2 + k8s.io/apimachinery v0.22.2 + k8s.io/client-go v0.22.2 ) require ( + cloud.google.com/go/compute v1.19.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go v1.44.262 // indirect - github.com/beorn7/perks v1.0.1 // indirect + github.com/aws/aws-sdk-go v1.30.27 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/deckarep/golang-set v1.7.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.14.1 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect - github.com/go-logr/logr v1.2.4 // 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.1 // 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/validate v0.22.1 // indirect - github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect + github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect + github.com/go-logr/logr v0.4.0 // indirect github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/golang/snappy v0.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-plugin v1.4.5 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.0.1 // indirect github.com/hashicorp/go-retryablehttp v0.6.6 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect - github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/vault/sdk v0.7.0 // indirect + github.com/hashicorp/serf v0.10.1 // indirect + github.com/hashicorp/vault/sdk v0.2.1 // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/josharian/intern v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect - github.com/miekg/dns v1.1.50 // indirect - github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.0.0 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/onsi/ginkgo v1.16.4 // indirect + github.com/onsi/gomega v1.15.0 // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pquerna/otp v1.2.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/urfave/cli v1.22.2 // indirect - go.mongodb.org/mongo-driver v1.11.0 // indirect - go.opentelemetry.io/otel v1.11.1 // indirect - go.opentelemetry.io/otel/trace v1.11.1 // indirect - go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect - golang.org/x/mod v0.12.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect - gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/grpc v1.56.3 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/klog/v2 v2.9.0 // indirect + k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect + k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/acceptance/go.sum b/acceptance/go.sum index 36713f97e1..1b3a682ab1 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -10,27 +11,21 @@ cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gc cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -42,12 +37,14 @@ github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8 github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.5/go.mod h1:foo3aIXRQ90zFve3r0QiDsrjGDUwWhKl0ZOQy1CT14k= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/azure/auth v0.5.1/go.mod h1:ea90/jvmnAwDrSooLH4sRIehEPtG/EPUXavDh31MnA4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= @@ -64,6 +61,7 @@ github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQ github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -71,6 +69,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -80,27 +80,25 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I= -github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.30.27 h1:9gPjZWVDSoQrBO2AvqrWObS6KAZByfEJxQoCYo4ZfK0= +github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -110,9 +108,6 @@ github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3 github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -121,7 +116,17 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= @@ -142,8 +147,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= -github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= @@ -152,6 +155,7 @@ github.com/docker/cli v0.0.0-20200109221225-a4f60165b7a3/go.mod h1:JLrzqnKDaYBop github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -167,131 +171,70 @@ github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/El github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= +github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU= github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= +github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -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.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -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= -github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= -github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 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/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -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.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= -github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -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= -github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc= -github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= -github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= -github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= -github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 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/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= -github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -299,26 +242,22 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -326,49 +265,39 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3/go.mod h1:2wIuQute9+hhWqvL3vEI7YB0EKluF4WcPzI1eAliazk= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -376,8 +305,12 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -385,63 +318,65 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/gruntwork-io/gruntwork-cli v0.7.0 h1:YgSAmfCj9c61H+zuvHwKfYUwlMhu5arnQQLM4RH+CYs= github.com/gruntwork-io/gruntwork-cli v0.7.0/go.mod h1:jp6Z7NcLF2avpY8v71fBx6hds9eOFPELSuD/VPv7w00= github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+XbVVtytUHg= github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 h1:4iI0ztWbVPTSDax+m1/XDs4jIRorxY4kSMyuM0fX+Dc= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= -github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= -github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3 h1:FFRKi+IpoXHwXZDgqG+BNAG1duAuokbzm+5b2pcY1us= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231212195019-69e3f93ee8a3/go.mod h1:fCFq3EfW2Iwu5her/hgqVqcJikY8nBtDiKFgfOdBvvw= -github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= -github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3 h1:4wROIZB8Y4cN/wPILChc2zQ/q00z1VyJitdgyLbITdU= +github.com/hashicorp/consul-k8s/control-plane v0.0.0-20221117191905-0b1cc2b631e3/go.mod h1:j9Db/whkzvNC+KP2GftY0HxxleLm9swxXjlu3tYaOAw= +github.com/hashicorp/consul/api v1.18.2 h1:rBzj4IBJMh0n9BWeSwH7B8A0CSCoeafznlGmo+IQZ0E= +github.com/hashicorp/consul/api v1.18.2/go.mod h1:2u+zn9nwubj4ZeCl48w6bEBjZImW1jOz56XXil+QNwQ= +github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= +github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= 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= -github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY= -github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= -github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= +github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/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.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/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= @@ -451,18 +386,18 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcp-sdk-go v0.50.0 h1:vOUpVf4MQF/gtoBukuoYKs/i6KinTSpP5jhKCvsZ2bc= -github.com/hashicorp/hcp-sdk-go v0.50.0/go.mod h1:hZqky4HEzsKwvLOt4QJlZUrjeQmb4UCZUhDP2HyQFfc= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o= -github.com/hashicorp/vault/api v1.8.3/go.mod h1:4g/9lj9lmuJQMtT6CmVMHC5FW1yENaVv+Nv4ZfG8fAg= -github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg= -github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs= +github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= +github.com/hashicorp/vault/api v1.2.0 h1:ysGFc6XRGbv05NsWPzuO5VTv68Lj8jtwATxRLFOpP9s= +github.com/hashicorp/vault/api v1.2.0/go.mod h1:dAjw0T5shMnrfH7Q/Mst+LrcTKvStZBVs1PICEDpUqY= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M= +github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -472,20 +407,12 @@ github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -498,22 +425,17 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -524,11 +446,6 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -539,6 +456,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -549,33 +467,29 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= -github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +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.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= -github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= @@ -588,45 +502,52 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -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.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/oracle/oci-go-sdk v7.1.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -640,46 +561,30 @@ github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prY github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 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.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= -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/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -688,22 +593,22 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= -github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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= @@ -713,78 +618,48 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag 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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -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/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= -go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= -go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE= -go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= -go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= -go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= -go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= -go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= -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.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/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= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -792,12 +667,12 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -810,8 +685,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -832,15 +705,12 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -864,25 +734,14 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -890,24 +749,16 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 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= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -916,32 +767,35 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -950,60 +804,45 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -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/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1014,16 +853,13 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1031,6 +867,7 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1048,29 +885,14 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +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.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= -gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= @@ -1084,19 +906,12 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -1116,27 +931,16 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= @@ -1144,10 +948,7 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1162,26 +963,29 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1194,37 +998,34 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= -k8s.io/api v0.26.12 h1:jJm3s5ot05SUN3tPGg3b+XWuBE7rO/X0+dnVMhxyd5o= -k8s.io/api v0.26.12/go.mod h1:N+HUXukmtXNOKDngxXrEPbZWggWx01tH/N0nG4nV0oo= -k8s.io/apiextensions-apiserver v0.26.10 h1:wAriTUc6l7gUqJKOxhmXnYo/VNJzk4oh4QLCUR4Uq+k= +k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= +k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apimachinery v0.26.12 h1:y+OgufxqLIZtyXIydRhjLBGzrYLF+qwiDdCFXYOjeN4= -k8s.io/apimachinery v0.26.12/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= +k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= +k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM= -k8s.io/client-go v0.26.12 h1:kPpTpIeFNqwo4UyvoqzNp3DNK2mbGcdGv23eS1U8VMo= -k8s.io/client-go v0.26.12/go.mod h1:V7thEnIFroyNZOU30dKLiiVeqQmJz45shJG1mu7nONQ= +k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= +k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= -k8s.io/component-base v0.26.10 h1:vl3Gfe5aC09mNxfnQtTng7u3rnBVrShOK3MAkqEleb0= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -1234,17 +1035,18 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 h1:XmRqFcQlCy/lKRZ39j+RVpokYNroHPqV3mcBRfnhT5o= +k8s.io/utils v0.0.0-20220812165043-ad590609e2e5/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= @@ -1253,18 +1055,12 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8= -sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8= -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/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/acceptance/tests/api-gateway/api_gateway_external_servers_test.go b/acceptance/tests/api-gateway/api_gateway_external_servers_test.go deleted file mode 100644 index aa0934dc65..0000000000 --- a/acceptance/tests/api-gateway/api_gateway_external_servers_test.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigateway - -import ( - "context" - "fmt" - "testing" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/types" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestAPIGateway_ExternalServers tests that connect works when using external servers. -// It sets up an external Consul server in the same cluster but a different Helm installation -// and then treats this server as external. -func TestAPIGateway_ExternalServers(t *testing.T) { - cfg := suite.Config() - ctx := suite.Environment().DefaultContext(t) - - serverHelmValues := map[string]string{ - "global.acls.manageSystemACLs": "true", - "global.tls.enabled": "true", - - // Don't install injector, controller and cni on this cluster so that it's not installed twice. - "connectInject.enabled": "false", - "connectInject.cni.enabled": "false", - } - serverReleaseName := helpers.RandomName() - consulServerCluster := consul.NewHelmCluster(t, serverHelmValues, ctx, cfg, serverReleaseName) - - consulServerCluster.Create(t) - - helmValues := map[string]string{ - "server.enabled": "false", - "global.acls.manageSystemACLs": "true", - "global.tls.enabled": "true", - "connectInject.enabled": "true", - "externalServers.enabled": "true", - "externalServers.hosts[0]": fmt.Sprintf("%s-consul-server", serverReleaseName), - "externalServers.httpsPort": "8501", - "global.tls.caCert.secretName": fmt.Sprintf("%s-consul-ca-cert", serverReleaseName), - "global.tls.caCert.secretKey": "tls.crt", - "global.acls.bootstrapToken.secretName": fmt.Sprintf("%s-consul-bootstrap-acl-token", serverReleaseName), - "global.acls.bootstrapToken.secretKey": "token", - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - consulCluster.SkipCheckForPreviousInstallations = true - - consulCluster.Create(t) - - logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") - - // Override the default proxy config settings for this test - consulClient, _ := consulCluster.SetupConsulClient(t, true, serverReleaseName) - logger.Log(t, "have consul client") - _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ - Kind: api.ProxyDefaults, - Name: api.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http", - }, - }, nil) - require.NoError(t, err) - logger.Log(t, "set consul config entry") - - logger.Log(t, "creating api-gateway resources") - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") - }) - - logger.Log(t, "patching route to target server") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"name":"static-server","port":80}]}]}}`, "--type=merge") - - // Grab a kubernetes client so that we can verify binding - // behavior prior to issuing requests through the gateway. - k8sClient := ctx.ControllerRuntimeClient(t) - - // On startup, the controller can take upwards of 1m to perform - // leader election so we may need to wait a long time for - // the reconcile loop to run (hence a ~1m timeout here). - var gatewayAddress string - retryCheck(t, 60, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) - require.NoError(r, err) - - // check that we have an address to use - require.Len(r, gateway.Status.Addresses, 1) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.Status.Addresses[0].Value - }) - - k8sOptions := ctx.KubectlOptions(t) - targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) - - // check that intentions keep our connection from happening - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetAddress) - - // Now we create the allow intention. - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - - // Test that we can make a call to the api gateway - // via the static-client pod. It should route to the static-server pod. - logger.Log(t, "trying calls to api gateway") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetAddress) -} diff --git a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go deleted file mode 100644 index 89ba07a1e7..0000000000 --- a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigateway - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// GatewayClassConfig tests the creation of a gatewayclassconfig object and makes sure that its configuration -// is properly applied to any child gateway objects, namely that the number of gateway instances match the defined -// minInstances,maxInstances and defaultInstances parameters, and that changing the parent gateway does not affect -// the child gateways. -func TestAPIGateway_GatewayClassConfig(t *testing.T) { - var ( - defaultInstances = pointer.Int32(2) - maxInstances = pointer.Int32(3) - minInstances = pointer.Int32(1) - - namespace = "default" - gatewayClassName = "gateway-class" - ) - - ctx := suite.Environment().DefaultContext(t) - cfg := suite.Config() - helmValues := map[string]string{ - "global.logLevel": "trace", - "connectInject.enabled": "true", - } - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - consulCluster.Create(t) - - // Override the default proxy config settings for this test. - consulClient, _ := consulCluster.SetupConsulClient(t, false) - _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ - Kind: api.ProxyDefaults, - Name: api.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http", - }, - }, nil) - require.NoError(t, err) - - k8sClient := ctx.ControllerRuntimeClient(t) - - // create a GatewayClassConfig with configuration set - gatewayClassConfigName := "gateway-class-config" - gatewayClassConfig := &v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: gatewayClassConfigName, - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: defaultInstances, - MaxInstances: maxInstances, - MinInstances: minInstances, - }, - }, - } - logger.Log(t, "creating gateway class config") - err = k8sClient.Create(context.Background(), gatewayClassConfig) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - logger.Log(t, "deleting all gateway class configs") - k8sClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}) - }) - - gatewayParametersRef := &gwv1beta1.ParametersReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: gwv1beta1.Kind(v1alpha1.GatewayClassConfigKind), - Name: gatewayClassConfigName, - } - - // Create gateway class referencing gateway-class-config. - logger.Log(t, "creating controlled gateway class") - createGatewayClass(t, k8sClient, gatewayClassName, gatewayClassControllerName, gatewayParametersRef) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - logger.Log(t, "deleting all gateway classes") - k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}) - }) - - // Create a certificate to reference in listeners. - certificateInfo := generateCertificate(t, nil, "certificate.consul.local") - certificateName := "certificate" - certificate := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: certificateName, - Namespace: namespace, - Labels: map[string]string{ - "test-certificate": "true", - }, - }, - Type: corev1.SecretTypeTLS, - Data: map[string][]byte{ - corev1.TLSCertKey: certificateInfo.CertPEM, - corev1.TLSPrivateKeyKey: certificateInfo.PrivateKeyPEM, - }, - } - logger.Log(t, "creating certificate") - err = k8sClient.Create(context.Background(), certificate) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8sClient.Delete(context.Background(), certificate) - }) - - // Create gateway referencing gateway class. - gatewayName := "gcctestgateway" + namespace - logger.Log(t, "creating controlled gateway") - gateway := createGateway(t, k8sClient, gatewayName, namespace, gatewayClassName, certificateName) - - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - logger.Log(t, "deleting all gateways") - k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(namespace)) - }) - - // Ensure it exists. - logger.Log(t, "checking that gateway is synchronized to Consul") - checkConsulExists(t, consulClient, api.APIGateway, gatewayName) - - // Scenario: Gateway deployment should match the default instances defined on the gateway class config - logger.Log(t, "checking that gateway instances match defined gateway class config") - checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, defaultInstances, gateway) - - // Scenario: Updating the GatewayClassConfig should not affect gateways that have already been created - logger.Log(t, "updating gatewayclassconfig values") - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: gatewayClassConfigName, Namespace: namespace}, gatewayClassConfig) - require.NoError(t, err) - gatewayClassConfig.Spec.DeploymentSpec.DefaultInstances = pointer.Int32(8) - gatewayClassConfig.Spec.DeploymentSpec.MinInstances = pointer.Int32(5) - err = k8sClient.Update(context.Background(), gatewayClassConfig) - require.NoError(t, err) - checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, defaultInstances, gateway) - - // Scenario: gateways should be able to scale independently and not get overridden by the controller unless it's above the max - scale(t, k8sClient, gateway.Name, gateway.Namespace, pointer.Int32(*maxInstances+1)) - checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, maxInstances, gateway) - scale(t, k8sClient, gateway.Name, gateway.Namespace, pointer.Int32(0)) - checkNumberOfInstances(t, k8sClient, consulClient, gateway.Name, gateway.Namespace, minInstances, gateway) - -} - -func scale(t *testing.T, client client.Client, name, namespace string, scaleTo *int32) { - t.Helper() - - var deployment appsv1.Deployment - err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &deployment) - require.NoError(t, err) - - logger.Log(t, fmt.Sprintf("scaling gateway from %d to %d", *deployment.Spec.Replicas, *scaleTo)) - - deployment.Spec.Replicas = scaleTo - err = client.Update(context.Background(), &deployment) - require.NoError(t, err) - -} - -func checkNumberOfInstances(t *testing.T, k8client client.Client, consulClient *api.Client, name, namespace string, wantNumber *int32, gateway *gwv1beta1.Gateway) { - t.Helper() - - retryCheckWithWait(t, 30, 10*time.Second, func(r *retry.R) { - logger.Log(t, "checking that gateway instances match defined gateway class config") - logger.Log(t, fmt.Sprintf("want: %d", *wantNumber)) - - // Ensure the number of replicas has been set properly. - var deployment appsv1.Deployment - err := k8client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &deployment) - require.NoError(r, err) - logger.Log(t, fmt.Sprintf("deployment replicas: %d", *deployment.Spec.Replicas)) - require.EqualValues(r, *wantNumber, *deployment.Spec.Replicas, "deployment replicas should match the number of instances defined on the gateway class config") - - // Ensure the number of gateway pods matches the replicas generated. - podList := corev1.PodList{} - labels := common.LabelsForGateway(gateway) - err = k8client.List(context.Background(), &podList, client.InNamespace(namespace), client.MatchingLabels(labels)) - require.NoError(r, err) - logger.Log(t, fmt.Sprintf("number of pods: %d", len(podList.Items))) - require.EqualValues(r, *wantNumber, len(podList.Items), "number of pods should match the number of instances defined on the gateway class config") - - // Ensure the number of services matches the replicas generated. - services, _, err := consulClient.Catalog().Service(name, "", nil) - seenServices := map[string]interface{}{} - require.NoError(r, err) - logger.Log(t, fmt.Sprintf("number of services: %d", len(services))) - //we need to double check that we aren't double counting services with the same ID - for _, s := range services { - seenServices[s.ServiceID] = true - logger.Log(t, fmt.Sprintf("service info: id: %s, name: %s, namespace: %s", s.ServiceID, s.ServiceName, s.Namespace)) - } - - logger.Log(t, fmt.Sprintf("number of services: %d", len(services))) - require.EqualValues(r, int(*wantNumber), len(seenServices), "number of services should match the number of instances defined on the gateway class config") - }) -} diff --git a/acceptance/tests/api-gateway/api_gateway_kitchen_sink_test.go b/acceptance/tests/api-gateway/api_gateway_kitchen_sink_test.go deleted file mode 100644 index d701220a8c..0000000000 --- a/acceptance/tests/api-gateway/api_gateway_kitchen_sink_test.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigateway - -import ( - "context" - "encoding/base64" - "fmt" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "testing" - "time" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/types" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// Enabled everything possible, see if anything breaks. -func TestAPIGateway_KitchenSink(t *testing.T) { - ctx := suite.Environment().DefaultContext(t) - cfg := suite.Config() - - runWithEnterpriseOnlyFeatures := cfg.EnableEnterprise - - serverHelmValues := map[string]string{ - "global.acls.manageSystemACLs": "true", - "global.tls.enabled": "true", - - // Don't install injector, controller and cni on this cluster so that it's not installed twice. - "connectInject.enabled": "false", - "connectInject.cni.enabled": "false", - } - serverReleaseName := helpers.RandomName() - consulServerCluster := consul.NewHelmCluster(t, serverHelmValues, ctx, cfg, serverReleaseName) - consulServerCluster.Create(t) - - helmValues := map[string]string{ - "server.enabled": "false", - "connectInject.consulNamespaces.mirroringK8S": "true", - "global.acls.manageSystemACLs": "true", - "global.tls.enabled": "true", - "global.logLevel": "trace", - "externalServers.enabled": "true", - "externalServers.hosts[0]": fmt.Sprintf("%s-consul-server", serverReleaseName), - "externalServers.httpsPort": "8501", - "global.tls.caCert.secretName": fmt.Sprintf("%s-consul-ca-cert", serverReleaseName), - "global.tls.caCert.secretKey": "tls.crt", - "global.acls.bootstrapToken.secretName": fmt.Sprintf("%s-consul-bootstrap-acl-token", serverReleaseName), - "global.acls.bootstrapToken.secretKey": "token", - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - consulCluster.SkipCheckForPreviousInstallations = true - - consulCluster.Create(t) - - // Override the default proxy config settings for this test - consulClient, _ := consulCluster.SetupConsulClient(t, true, serverReleaseName) - logger.Log(t, "have consul client") - _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ - Kind: api.ProxyDefaults, - Name: api.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http", - }, - }, nil) - require.NoError(t, err) - logger.Log(t, "set consul config entry") - - logger.Log(t, "creating other namespace") - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "create", "namespace", "other") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "namespace", "other") - }) - - k8sClient := ctx.ControllerRuntimeClient(t) - - logger.Log(t, "creating api-gateway resources") - fixturePath := "../fixtures/cases/api-gateways/kitchen-sink" - if runWithEnterpriseOnlyFeatures { - fixturePath += "-ent" - } - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", fixturePath) - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", fixturePath) - }) - - // Create certificate secret, we do this separately since - // applying the secret will make an invalid certificate that breaks other tests - logger.Log(t, "creating certificate secret") - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/bases/api-gateway/certificate.yaml") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/bases/api-gateway/certificate.yaml") - }) - - // patch certificate with data - logger.Log(t, "patching certificate secret with generated data") - certificate := generateCertificate(t, nil, "gateway.test.local") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") - - // Create static server and static client - logger.Log(t, "creating static-client pod") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", "static-server")) - - // On startup, the controller can take upwards of 1m to perform - // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 2m timeout here). - var ( - gatewayAddress string - httpRoute gwv1beta1.HTTPRoute - ) - - counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} - retry.RunWith(counter, t, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) - require.NoError(r, err) - - //CHECK TO MAKE SURE EVERYTHING WAS SET UP CORECTLY BEFORE RUNNING TESTS - require.Len(r, gateway.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, gateway.Finalizers[0]) - - // check our statuses - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) - require.Len(r, gateway.Status.Listeners, 2) - - require.EqualValues(r, int32(1), gateway.Status.Listeners[0].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - - // check that we have an address to use - require.Len(r, gateway.Status.Addresses, 2) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.Status.Addresses[0].Value - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route", Namespace: "default"}, &httpRoute) - require.NoError(r, err) - - // check our finalizers - require.Len(r, httpRoute.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRoute.Finalizers[0]) - - // check parent status - require.Len(r, httpRoute.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRoute.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRoute.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - }) - - // GENERAL Asserts- test that assets were created as expected - entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) - require.NoError(t, err) - gateway := entry.(*api.APIGatewayConfigEntry) - - entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) - require.NoError(t, err) - consulHTTPRoute := entry.(*api.HTTPRouteConfigEntry) - - // now check the gateway status conditions - checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) - - // and the route status conditions - checkConsulStatusCondition(t, consulHTTPRoute.Status.Conditions, trueConsulCondition("Bound", "Bound")) - - // finally we check that we can actually route to the service(s) via the gateway - k8sOptions := ctx.KubectlOptions(t) - targetHTTPAddress := fmt.Sprintf("http://%s/v1", gatewayAddress) - - // Now we create the allow intention. - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server-protected", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - - //asserts only valid when running with enterprise - if runWithEnterpriseOnlyFeatures { - //JWT Related Asserts - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /admin should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddress) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /admin should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddress) - } else { - // Test that we can make a call to the api gateway - logger.Log(t, "trying calls to api gateway http") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) - } -} diff --git a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go b/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go deleted file mode 100644 index f6f66ed995..0000000000 --- a/acceptance/tests/api-gateway/api_gateway_lifecycle_test.go +++ /dev/null @@ -1,444 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigateway - -import ( - "context" - "fmt" - "testing" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestAPIGateway_Lifecycle(t *testing.T) { - ctx := suite.Environment().DefaultContext(t) - cfg := suite.Config() - helmValues := map[string]string{ - "global.logLevel": "trace", - "connectInject.enabled": "true", - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - - consulCluster.Create(t) - - k8sClient := ctx.ControllerRuntimeClient(t) - consulClient, _ := consulCluster.SetupConsulClient(t, false) - - defaultNamespace := "default" - - // create a service to target - targetName := "static-server" - logger.Log(t, "creating target server") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - // create a basic GatewayClassConfig - gatewayClassConfigName := "controlled-gateway-class-config" - gatewayClassConfig := &v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: gatewayClassConfigName, - }, - } - logger.Log(t, "creating gateway class config") - err := k8sClient.Create(context.Background(), gatewayClassConfig) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - logger.Log(t, "deleting all gateway class configs") - k8sClient.DeleteAllOf(context.Background(), &v1alpha1.GatewayClassConfig{}) - }) - - gatewayParametersRef := &gwv1beta1.ParametersReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: gwv1beta1.Kind(v1alpha1.GatewayClassConfigKind), - Name: gatewayClassConfigName, - } - - // create three gateway classes, two we control, one we don't - controlledGatewayClassOneName := "controlled-gateway-class-one" - logger.Log(t, "creating controlled gateway class one") - createGatewayClass(t, k8sClient, controlledGatewayClassOneName, gatewayClassControllerName, gatewayParametersRef) - - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - logger.Log(t, "deleting all gateway classes") - k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.GatewayClass{}) - }) - - controlledGatewayClassTwoName := "controlled-gateway-class-two" - logger.Log(t, "creating controlled gateway class two") - createGatewayClass(t, k8sClient, controlledGatewayClassTwoName, gatewayClassControllerName, gatewayParametersRef) - - uncontrolledGatewayClassName := "uncontrolled-gateway-class" - logger.Log(t, "creating uncontrolled gateway class") - createGatewayClass(t, k8sClient, uncontrolledGatewayClassName, "example.com/some-controller", nil) - - // Create a certificate to reference in listeners - certificateInfo := generateCertificate(t, nil, "certificate.consul.local") - certificateName := "certificate" - certificate := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: certificateName, - Namespace: defaultNamespace, - Labels: map[string]string{ - "test-certificate": "true", - }, - }, - Type: corev1.SecretTypeTLS, - Data: map[string][]byte{ - corev1.TLSCertKey: certificateInfo.CertPEM, - corev1.TLSPrivateKeyKey: certificateInfo.PrivateKeyPEM, - }, - } - logger.Log(t, "creating certificate") - err = k8sClient.Create(context.Background(), certificate) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8sClient.Delete(context.Background(), certificate) - }) - - // Create three gateways with a basic HTTPS listener to correspond to the three classes - controlledGatewayOneName := "controlled-gateway-one" - logger.Log(t, "creating controlled gateway one") - controlledGatewayOne := createGateway(t, k8sClient, controlledGatewayOneName, defaultNamespace, controlledGatewayClassOneName, certificateName) - - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - logger.Log(t, "deleting all gateways") - k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(defaultNamespace)) - }) - - controlledGatewayTwoName := "controlled-gateway-two" - logger.Log(t, "creating controlled gateway two") - controlledGatewayTwo := createGateway(t, k8sClient, controlledGatewayTwoName, defaultNamespace, controlledGatewayClassTwoName, certificateName) - - uncontrolledGatewayName := "uncontrolled-gateway" - logger.Log(t, "creating uncontrolled gateway") - _ = createGateway(t, k8sClient, uncontrolledGatewayName, defaultNamespace, uncontrolledGatewayClassName, certificateName) - - // create two http routes associated with the first controlled gateway - routeOneName := "route-one" - logger.Log(t, "creating route one") - routeOne := createRoute(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayOneName, targetName) - - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - logger.Log(t, "deleting all http routes") - k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.HTTPRoute{}, client.InNamespace(defaultNamespace)) - }) - - routeTwoName := "route-two" - logger.Log(t, "creating route two") - routeTwo := createRoute(t, k8sClient, routeTwoName, defaultNamespace, controlledGatewayTwoName, targetName) - - // Scenario: Swapping a route to another controlled gateway should clean up the old parent statuses and references on Consul resources - - // check that the route is bound properly and objects are reflected in Consul - logger.Log(t, "checking that http route one is bound to gateway one") - checkRouteBound(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayOneName) - - logger.Log(t, "checking that http route one is synchronized to Consul") - checkConsulRouteParent(t, consulClient, routeOneName, controlledGatewayOneName) - - // update the route to point to the other controlled gateway - logger.Log(t, "updating route one to be bound to gateway two") - updateKubernetes(t, k8sClient, routeOne, func(r *gwv1beta1.HTTPRoute) { - r.Spec.ParentRefs[0].Name = gwv1beta1.ObjectName(controlledGatewayTwoName) - }) - - // check that the route is bound properly and objects are reflected in Consul - logger.Log(t, "checking that http route one is bound to gateway two") - checkRouteBound(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayTwoName) - - logger.Log(t, "checking that http route one is synchronized to Consul") - checkConsulRouteParent(t, consulClient, routeOneName, controlledGatewayTwoName) - - // Scenario: Binding a route to a controlled gateway and then associating it with another gateway we don’t control should clean up Consul resources, route statuses, and finalizers - // check that the route is bound properly and objects are reflected in Consul - - // check that our second http route is bound properly - logger.Log(t, "checking that http route two is bound to gateway two") - checkRouteBound(t, k8sClient, routeTwoName, defaultNamespace, controlledGatewayTwoName) - - logger.Log(t, "checking that http route two is synchronized to Consul") - checkConsulRouteParent(t, consulClient, routeTwoName, controlledGatewayTwoName) - - // update the route to point to the uncontrolled gateway - logger.Log(t, "updating route two to be bound to an uncontrolled gateway") - updateKubernetes(t, k8sClient, routeTwo, func(r *gwv1beta1.HTTPRoute) { - r.Spec.ParentRefs[0].Name = gwv1beta1.ObjectName(uncontrolledGatewayName) - }) - - // check that the route is unbound and all Consul objects and Kubernetes statuses are cleaned up - logger.Log(t, "checking that http route two is cleaned up because we no longer control it") - checkEmptyRoute(t, k8sClient, routeTwoName, defaultNamespace) - - logger.Log(t, "checking that http route two is deleted from Consul") - checkConsulNotExists(t, consulClient, api.HTTPRoute, routeTwoName) - - // Scenario: Switching a controlled gateway’s protocol that causes a route to unbind should cause the route to drop the parent ref in Consul and result in proper statuses set in Kubernetes - - // swap the gateway's protocol and see the route unbind - logger.Log(t, "marking gateway two as using TCP") - updateKubernetes(t, k8sClient, controlledGatewayTwo, func(g *gwv1beta1.Gateway) { - g.Spec.Listeners[0].Protocol = gwv1beta1.TCPProtocolType - }) - - // check that the route is unbound and all Consul objects and Kubernetes statuses are cleaned up - logger.Log(t, "checking that http route one is not bound to gateway two") - retryCheck(t, 60, func(r *retry.R) { - var route gwv1beta1.HTTPRoute - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: routeOneName, Namespace: defaultNamespace}, &route) - require.NoError(r, err) - - require.Len(r, route.Status.Parents, 1) - require.EqualValues(r, controlledGatewayTwoName, route.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, route.Status.Parents[0].Conditions, falseCondition("Accepted", "NotAllowedByListeners")) - }) - - logger.Log(t, "checking that route one is deleted from Consul") - checkConsulNotExists(t, consulClient, api.HTTPRoute, routeOneName) - - // Scenario: Deleting a gateway should result in routes only referencing it to get cleaned up from Consul and their statuses/finalizers cleared, but routes referencing another controlled gateway should still exist in Consul and only have their statuses cleaned up from referencing the gateway we previously controlled. Any referenced certificates should also get cleaned up. - - // delete gateway two - logger.Log(t, "deleting gateway two in Kubernetes") - err = k8sClient.Delete(context.Background(), controlledGatewayTwo) - require.NoError(t, err) - - // check that the gateway is deleted from Consul - logger.Log(t, "checking that gateway two is deleted from Consul") - checkConsulNotExists(t, consulClient, api.APIGateway, controlledGatewayTwoName) - - // check that the Kubernetes route is cleaned up and the entries deleted from Consul - logger.Log(t, "checking that http route one is cleaned up in Kubernetes") - checkEmptyRoute(t, k8sClient, routeOneName, defaultNamespace) - - // Scenario: Changing a gateway class name on a gateway to something we don’t control should have the same affect as deleting it with the addition of cleaning up our finalizer from the gateway. - - // reset route one to point to our first gateway and check that it's bound properly - logger.Log(t, "remarking route one as bound to gateway one") - updateKubernetes(t, k8sClient, routeOne, func(r *gwv1beta1.HTTPRoute) { - r.Spec.ParentRefs[0].Name = gwv1beta1.ObjectName(controlledGatewayOneName) - }) - - logger.Log(t, "checking that http route one is bound to gateway one") - checkRouteBound(t, k8sClient, routeOneName, defaultNamespace, controlledGatewayOneName) - - logger.Log(t, "checking that http route one is synchronized to Consul") - checkConsulRouteParent(t, consulClient, routeOneName, controlledGatewayOneName) - - // make the gateway uncontrolled by pointing to a non-existent gateway class - logger.Log(t, "marking gateway one as not controlled by our controller") - updateKubernetes(t, k8sClient, controlledGatewayOne, func(g *gwv1beta1.Gateway) { - g.Spec.GatewayClassName = "non-existent" - }) - - // check that the Kubernetes gateway is cleaned up - logger.Log(t, "checking that gateway one is cleaned up in Kubernetes") - retryCheck(t, 60, func(r *retry.R) { - var route gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: controlledGatewayOneName, Namespace: defaultNamespace}, &route) - require.NoError(r, err) - - require.Len(r, route.Finalizers, 0) - }) - - // check that the gateway is deleted from Consul - logger.Log(t, "checking that gateway one is deleted from Consul") - checkConsulNotExists(t, consulClient, api.APIGateway, controlledGatewayOneName) - - // check that the Kubernetes route is cleaned up and the entries deleted from Consul - logger.Log(t, "checking that http route one is cleaned up in Kubernetes") - checkEmptyRoute(t, k8sClient, routeOneName, defaultNamespace) - - logger.Log(t, "checking that http route one is deleted from Consul") - checkConsulNotExists(t, consulClient, api.HTTPRoute, routeOneName) - - // Scenario: Deleting a certificate referenced by a gateway’s listener should make the listener invalid and drop it from Consul. - - // reset the gateway - logger.Log(t, "remarking gateway one as controlled by our controller") - updateKubernetes(t, k8sClient, controlledGatewayOne, func(g *gwv1beta1.Gateway) { - g.Spec.GatewayClassName = gwv1beta1.ObjectName(controlledGatewayClassOneName) - }) - - // make sure it exists - logger.Log(t, "checking that gateway one is synchronized to Consul") - checkConsulExists(t, consulClient, api.APIGateway, controlledGatewayOneName) - - // make sure our certificate exists - logger.Log(t, "checking that the certificate is synchronized to Consul") - checkConsulExists(t, consulClient, api.InlineCertificate, certificateName) - - // delete the certificate in Kubernetes - logger.Log(t, "deleting the certificate in Kubernetes") - err = k8sClient.Delete(context.Background(), certificate) - require.NoError(t, err) - - // make sure the certificate no longer exists in Consul - logger.Log(t, "checking that the certificate is deleted from Consul") - checkConsulNotExists(t, consulClient, api.InlineCertificate, certificateName) -} - -func checkConsulNotExists(t *testing.T, client *api.Client, kind, name string, namespace ...string) { - t.Helper() - - opts := &api.QueryOptions{} - if len(namespace) != 0 { - opts.Namespace = namespace[0] - } - - retryCheck(t, 60, func(r *retry.R) { - _, _, err := client.ConfigEntries().Get(kind, name, opts) - require.Error(r, err) - require.EqualError(r, err, fmt.Sprintf("Unexpected response code: 404 (Config entry not found for %q / %q)", kind, name)) - }) -} - -func checkConsulExists(t *testing.T, client *api.Client, kind, name string) { - t.Helper() - - retryCheck(t, 60, func(r *retry.R) { - _, _, err := client.ConfigEntries().Get(kind, name, nil) - require.NoError(r, err) - }) -} - -func checkConsulRouteParent(t *testing.T, client *api.Client, name, parent string) { - t.Helper() - - retryCheck(t, 60, func(r *retry.R) { - entry, _, err := client.ConfigEntries().Get(api.HTTPRoute, name, nil) - require.NoError(r, err) - route := entry.(*api.HTTPRouteConfigEntry) - - require.Len(r, route.Parents, 1) - require.Equal(r, parent, route.Parents[0].Name) - }) -} - -func checkEmptyRoute(t *testing.T, client client.Client, name, namespace string) { - t.Helper() - - retryCheck(t, 60, func(r *retry.R) { - var route gwv1beta1.HTTPRoute - err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &route) - require.NoError(r, err) - - require.Len(r, route.Status.Parents, 0) - require.Len(r, route.Finalizers, 0) - }) -} - -func checkRouteBound(t *testing.T, client client.Client, name, namespace, parent string) { - t.Helper() - - retryCheck(t, 60, func(r *retry.R) { - var route gwv1beta1.HTTPRoute - err := client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &route) - require.NoError(r, err) - - require.Len(r, route.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, route.Status.Parents[0].ControllerName) - require.EqualValues(r, parent, route.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, route.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, route.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, route.Status.Parents[0].Conditions, trueCondition("Synced", "Synced")) - }) -} - -func updateKubernetes[T client.Object](t *testing.T, k8sClient client.Client, o T, fn func(o T)) { - t.Helper() - - err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(o), o) - require.NoError(t, err) - fn(o) - err = k8sClient.Update(context.Background(), o) - require.NoError(t, err) -} - -func createRoute(t *testing.T, client client.Client, name, namespace, parent, target string) *gwv1beta1.HTTPRoute { - t.Helper() - - route := &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: gwv1beta1.ObjectName(parent)}, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - {BackendRefs: []gwv1beta1.HTTPBackendRef{ - {BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{Name: gwv1beta1.ObjectName(target)}, - }}, - }}, - }, - }, - } - - err := client.Create(context.Background(), route) - require.NoError(t, err) - return route -} - -func createGateway(t *testing.T, client client.Client, name, namespace, gatewayClass, certificate string) *gwv1beta1.Gateway { - t.Helper() - - gateway := &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gwv1beta1.ObjectName(gatewayClass), - Listeners: []gwv1beta1.Listener{{ - Name: gwv1beta1.SectionName("listener"), - Protocol: gwv1beta1.HTTPSProtocolType, - Port: 8443, - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: gwv1beta1.ObjectName(certificate), - }}, - }, - }}, - }, - } - - err := client.Create(context.Background(), gateway) - require.NoError(t, err) - - return gateway -} - -func createGatewayClass(t *testing.T, client client.Client, name, controllerName string, parameters *gwv1beta1.ParametersReference) { - t.Helper() - - gatewayClass := &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: gwv1beta1.GatewayController(controllerName), - ParametersRef: parameters, - }, - } - - err := client.Create(context.Background(), gatewayClass) - require.NoError(t, err) -} diff --git a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go b/acceptance/tests/api-gateway/api_gateway_tenancy_test.go deleted file mode 100644 index f7b0ac6d79..0000000000 --- a/acceptance/tests/api-gateway/api_gateway_tenancy_test.go +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigateway - -import ( - "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/base64" - "encoding/pem" - "fmt" - "math/big" - "path" - "strconv" - "testing" - "time" - - terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/acceptance/framework/config" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" -) - -var ( - gatewayGroup = gwv1beta1.Group(gwv1beta1.GroupVersion.Group) - consulGroup = gwv1beta1.Group(v1alpha1.GroupVersion.Group) - gatewayKind = gwv1beta1.Kind("Gateway") - serviceKind = gwv1beta1.Kind("Service") - secretKind = gwv1beta1.Kind("Secret") - meshServiceKind = gwv1beta1.Kind("MeshService") - httpRouteKind = gwv1beta1.Kind("HTTPRoute") - tcpRouteKind = gwv1beta1.Kind("TCPRoute") -) - -func TestAPIGateway_Tenancy(t *testing.T) { - cases := []struct { - secure bool - namespaceMirroring bool - }{ - { - secure: false, - namespaceMirroring: false, - }, - { - secure: true, - namespaceMirroring: false, - }, - { - secure: false, - namespaceMirroring: true, - }, - { - secure: true, - namespaceMirroring: true, - }, - } - for _, c := range cases { - name := fmt.Sprintf("secure: %t, namespaces: %t", c.secure, c.namespaceMirroring) - t.Run(name, func(t *testing.T) { - cfg := suite.Config() - - if !cfg.EnableEnterprise && c.namespaceMirroring { - t.Skipf("skipping this test because -enable-enterprise is not set") - } - - ctx := suite.Environment().DefaultContext(t) - - helmValues := map[string]string{ - "global.enableConsulNamespaces": strconv.FormatBool(c.namespaceMirroring), - "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), - "global.tls.enabled": strconv.FormatBool(c.secure), - "global.logLevel": "trace", - "connectInject.enabled": "true", - "connectInject.consulNamespaces.mirroringK8S": strconv.FormatBool(c.namespaceMirroring), - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - - consulCluster.Create(t) - - serviceNamespace, serviceK8SOptions := createNamespace(t, ctx, cfg) - certificateNamespace, certificateK8SOptions := createNamespace(t, ctx, cfg) - gatewayNamespace, gatewayK8SOptions := createNamespace(t, ctx, cfg) - routeNamespace, routeK8SOptions := createNamespace(t, ctx, cfg) - - logger.Logf(t, "creating target server in %s namespace", serviceNamespace) - k8s.DeployKustomize(t, serviceK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - logger.Logf(t, "creating certificate resources in %s namespace", certificateNamespace) - applyFixture(t, cfg, certificateK8SOptions, "cases/api-gateways/certificate") - - logger.Logf(t, "creating gateway in %s namespace", gatewayNamespace) - applyFixture(t, cfg, gatewayK8SOptions, "cases/api-gateways/gateway") - - logger.Logf(t, "creating route resources in %s namespace", routeNamespace) - applyFixture(t, cfg, routeK8SOptions, "cases/api-gateways/httproute") - - // patch certificate with data - logger.Log(t, "patching certificate with generated data") - certificate := generateCertificate(t, nil, "gateway.test.local") - k8s.RunKubectl(t, certificateK8SOptions, "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") - - // patch the resources to reference each other - logger.Log(t, "patching gateway to certificate") - k8s.RunKubectl(t, gatewayK8SOptions, "patch", "gateway", "gateway", "-p", fmt.Sprintf(`{"spec":{"listeners":[{"protocol":"HTTPS","port":8082,"name":"https","tls":{"certificateRefs":[{"name":"certificate","namespace":"%s"}]},"allowedRoutes":{"namespaces":{"from":"All"}}}]}}`, certificateNamespace), "--type=merge") - - logger.Log(t, "patching route to target server") - k8s.RunKubectl(t, routeK8SOptions, "patch", "httproute", "route", "-p", fmt.Sprintf(`{"spec":{"rules":[{"backendRefs":[{"name":"static-server","namespace":"%s","port":80}]}]}}`, serviceNamespace), "--type=merge") - - logger.Log(t, "patching route to gateway") - k8s.RunKubectl(t, routeK8SOptions, "patch", "httproute", "route", "-p", fmt.Sprintf(`{"spec":{"parentRefs":[{"name":"gateway","namespace":"%s"}]}}`, gatewayNamespace), "--type=merge") - - // Grab a kubernetes and consul client so that we can verify binding - // behavior prior to issuing requests through the gateway. - k8sClient := ctx.ControllerRuntimeClient(t) - consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) - - retryCheck(t, 120, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: gatewayNamespace}, &gateway) - require.NoError(r, err) - - // check our statuses - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Conditions, falseCondition("Programmed", "Pending")) - // we expect a sync error here since dropping the listener means the gateway is now invalid - checkStatusCondition(r, gateway.Status.Conditions, falseCondition("Synced", "SyncError")) - - require.Len(r, gateway.Status.Listeners, 1) - require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("ResolvedRefs", "RefNotPermitted")) - }) - - // since the sync operation should fail above, check that we don't have the entry in Consul. - checkConsulNotExists(t, consulClient, api.APIGateway, "gateway", namespaceForConsul(c.namespaceMirroring, gatewayNamespace)) - - // route failure - retryCheck(t, 60, func(r *retry.R) { - var httproute gwv1beta1.HTTPRoute - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "route", Namespace: routeNamespace}, &httproute) - require.NoError(r, err) - - require.Len(r, httproute.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httproute.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httproute.Status.Parents[0].ParentRef.Name) - require.NotNil(r, httproute.Status.Parents[0].ParentRef.Namespace) - require.EqualValues(r, gatewayNamespace, *httproute.Status.Parents[0].ParentRef.Namespace) - checkStatusCondition(r, httproute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httproute.Status.Parents[0].Conditions, falseCondition("ResolvedRefs", "RefNotPermitted")) - }) - - // we only sync validly referenced certificates over, so check to make sure it is not created. - checkConsulNotExists(t, consulClient, api.InlineCertificate, "certificate", namespaceForConsul(c.namespaceMirroring, certificateNamespace)) - - // now create reference grants - createReferenceGrant(t, k8sClient, "gateway-certificate", gatewayNamespace, certificateNamespace) - createReferenceGrant(t, k8sClient, "route-service", routeNamespace, serviceNamespace) - - // gateway updated with references allowed - retryCheck(t, 60, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: gatewayNamespace}, &gateway) - require.NoError(r, err) - - // check our statuses - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Programmed", "Programmed")) - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Synced", "Synced")) - require.Len(r, gateway.Status.Listeners, 1) - require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - }) - - // check the Consul gateway is updated, with the listener. - retryCheck(t, 30, func(r *retry.R) { - entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", &api.QueryOptions{ - Namespace: namespaceForConsul(c.namespaceMirroring, gatewayNamespace), - }) - require.NoError(r, err) - gateway := entry.(*api.APIGatewayConfigEntry) - - require.EqualValues(r, "gateway", gateway.Meta["k8s-name"]) - require.EqualValues(r, gatewayNamespace, gateway.Meta["k8s-namespace"]) - require.Len(r, gateway.Listeners, 1) - checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) - checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("ResolvedRefs", "ResolvedRefs")) - }) - - // route updated with gateway and services allowed - retryCheck(t, 30, func(r *retry.R) { - var httproute gwv1beta1.HTTPRoute - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "route", Namespace: routeNamespace}, &httproute) - require.NoError(r, err) - - require.Len(r, httproute.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httproute.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httproute.Status.Parents[0].ParentRef.Name) - require.NotNil(r, httproute.Status.Parents[0].ParentRef.Namespace) - require.EqualValues(r, gatewayNamespace, *httproute.Status.Parents[0].ParentRef.Namespace) - checkStatusCondition(r, httproute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httproute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - }) - - // now check to make sure that the route is updated and valid - retryCheck(t, 30, func(r *retry.R) { - // since we're not bound, check to make sure that the route doesn't target the gateway in Consul. - entry, _, err := consulClient.ConfigEntries().Get(api.HTTPRoute, "route", &api.QueryOptions{ - Namespace: namespaceForConsul(c.namespaceMirroring, routeNamespace), - }) - require.NoError(r, err) - route := entry.(*api.HTTPRouteConfigEntry) - - require.EqualValues(r, "route", route.Meta["k8s-name"]) - require.EqualValues(r, routeNamespace, route.Meta["k8s-namespace"]) - require.Len(r, route.Parents, 1) - }) - - // and check to make sure that the certificate exists - retryCheck(t, 30, func(r *retry.R) { - entry, _, err := consulClient.ConfigEntries().Get(api.InlineCertificate, "certificate", &api.QueryOptions{ - Namespace: namespaceForConsul(c.namespaceMirroring, certificateNamespace), - }) - require.NoError(r, err) - certificate := entry.(*api.InlineCertificateConfigEntry) - - require.EqualValues(r, "certificate", certificate.Meta["k8s-name"]) - require.EqualValues(r, certificateNamespace, certificate.Meta["k8s-namespace"]) - }) - }) - } -} - -func applyFixture(t *testing.T, cfg *config.TestConfig, k8sOptions *terratestk8s.KubectlOptions, fixture string) { - t.Helper() - - out, err := k8s.RunKubectlAndGetOutputE(t, k8sOptions, "apply", "-k", path.Join("../fixtures", fixture)) - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectlAndGetOutputE(t, k8sOptions, "delete", "-k", path.Join("../fixtures", fixture)) - }) -} - -func createNamespace(t *testing.T, ctx environment.TestContext, cfg *config.TestConfig) (string, *terratestk8s.KubectlOptions) { - t.Helper() - - namespace := helpers.RandomName() - - logger.Logf(t, "creating Kubernetes namespace %s", namespace) - k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", namespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", namespace) - }) - - return namespace, &terratestk8s.KubectlOptions{ - ContextName: ctx.KubectlOptions(t).ContextName, - ConfigPath: ctx.KubectlOptions(t).ConfigPath, - Namespace: namespace, - } -} - -type certificateInfo struct { - Cert *x509.Certificate - PrivateKey *rsa.PrivateKey - CertPEM []byte - PrivateKeyPEM []byte -} - -func generateCertificate(t *testing.T, ca *certificateInfo, commonName string) *certificateInfo { - t.Helper() - - bits := 2048 - privateKey, err := rsa.GenerateKey(rand.Reader, bits) - require.NoError(t, err) - - usage := x509.KeyUsageDigitalSignature - if ca == nil { - usage = x509.KeyUsageCertSign - } - - expiration := time.Now().AddDate(10, 0, 0) - cert := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"Testing, INC."}, - Country: []string{"US"}, - Province: []string{""}, - Locality: []string{"San Francisco"}, - StreetAddress: []string{"Fake Street"}, - PostalCode: []string{"11111"}, - CommonName: commonName, - }, - IsCA: ca == nil, - NotBefore: time.Now().Add(-10 * time.Minute), - NotAfter: expiration, - SubjectKeyId: []byte{1, 2, 3, 4, 6}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: usage, - BasicConstraintsValid: true, - } - caCert := cert - if ca != nil { - caCert = ca.Cert - } - caPrivateKey := privateKey - if ca != nil { - caPrivateKey = ca.PrivateKey - } - data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) - require.NoError(t, err) - - certBytes := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: data, - }) - - privateKeyBytes := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) - - return &certificateInfo{ - Cert: cert, - CertPEM: certBytes, - PrivateKey: privateKey, - PrivateKeyPEM: privateKeyBytes, - } -} - -func retryCheck(t *testing.T, count int, fn func(r *retry.R)) { - retryCheckWithWait(t, count, 2*time.Second, fn) -} - -func retryCheckWithWait(t *testing.T, count int, wait time.Duration, fn func(r *retry.R)) { - t.Helper() - - counter := &retry.Counter{Count: count, Wait: wait} - retry.RunWith(counter, t, fn) -} - -func createReferenceGrant(t *testing.T, client client.Client, name, from, to string) { - t.Helper() - - // we just create a reference grant for all combinations in the given namespaces - - require.NoError(t, client.Create(context.Background(), &gwv1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: to, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{{ - Group: gatewayGroup, - Kind: gatewayKind, - Namespace: gwv1beta1.Namespace(from), - }, { - Group: gatewayGroup, - Kind: httpRouteKind, - Namespace: gwv1beta1.Namespace(from), - }, { - Group: gatewayGroup, - Kind: tcpRouteKind, - Namespace: gwv1beta1.Namespace(from), - }}, - To: []gwv1beta1.ReferenceGrantTo{{ - Group: gatewayGroup, - Kind: gatewayKind, - }, { - Kind: serviceKind, - }, { - Group: consulGroup, - Kind: meshServiceKind, - }, { - Kind: secretKind, - }}, - }, - })) -} - -func namespaceForConsul(namespaceMirroringEnabled bool, namespace string) string { - if namespaceMirroringEnabled { - return namespace - } - return "" -} diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go deleted file mode 100644 index bbbaa569d4..0000000000 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ /dev/null @@ -1,715 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigateway - -import ( - "context" - "encoding/base64" - "fmt" - "strconv" - "testing" - "time" - - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -const ( - StaticClientName = "static-client" - gatewayClassControllerName = "consul.hashicorp.com/gateway-controller" - gatewayClassFinalizer = "gateway-exists-finalizer.consul.hashicorp.com" - gatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" -) - -// Test that api gateway basic functionality works in a default installation and a secure installation. -func TestAPIGateway_Basic(t *testing.T) { - cases := []struct { - secure bool - }{ - { - secure: false, - }, - { - secure: true, - }, - } - for _, c := range cases { - name := fmt.Sprintf("secure: %t", c.secure) - t.Run(name, func(t *testing.T) { - ctx := suite.Environment().DefaultContext(t) - cfg := suite.Config() - helmValues := map[string]string{ - "connectInject.enabled": "true", - "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), - "global.tls.enabled": strconv.FormatBool(c.secure), - "global.logLevel": "trace", - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - - consulCluster.Create(t) - - // Override the default proxy config settings for this test - consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) - _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ - Kind: api.ProxyDefaults, - Name: api.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http", - }, - }, nil) - require.NoError(t, err) - - logger.Log(t, "creating api-gateway resources") - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") - }) - - // Create certificate secret, we do this separately since - // applying the secret will make an invalid certificate that breaks other tests - logger.Log(t, "creating certificate secret") - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/bases/api-gateway/certificate.yaml") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/bases/api-gateway/certificate.yaml") - }) - - // patch certificate with data - logger.Log(t, "patching certificate secret with generated data") - certificate := generateCertificate(t, nil, "gateway.test.local") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") - - logger.Log(t, "creating target http server") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - // We use the static-client pod so that we can make calls to the api gateway - // via kubectl exec without needing a route into the cluster from the test machine. - logger.Log(t, "creating static-client pod") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") - - logger.Log(t, "patching route to target http server") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"name":"static-server","port":80}]}]}}`, "--type=merge") - - logger.Log(t, "creating target tcp server") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server-tcp") - - logger.Log(t, "creating tcp-route") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/cases/api-gateways/tcproute/route.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/cases/api-gateways/tcproute/route.yaml") - }) - - // Grab a kubernetes client so that we can verify binding - // behavior prior to issuing requests through the gateway. - k8sClient := ctx.ControllerRuntimeClient(t) - - // On startup, the controller can take upwards of 1m to perform - // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the timeout here). - var gatewayAddress string - counter := &retry.Counter{Count: 120, Wait: 2 * time.Second} - retry.RunWith(counter, t, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) - require.NoError(r, err) - - // check our finalizers - require.Len(r, gateway.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, gateway.Finalizers[0]) - - // check our statuses - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) - require.Len(r, gateway.Status.Listeners, 3) - - require.EqualValues(r, 1, gateway.Status.Listeners[0].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, 1, gateway.Status.Listeners[1].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, 1, gateway.Status.Listeners[2].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[2].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - - // check that we have an address to use - require.Len(r, gateway.Status.Addresses, 1) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.Status.Addresses[0].Value - }) - - // now that we've satisfied those assertions, we know reconciliation is done - // so we can run assertions on the routes and the other objects - - // gateway class checks - var gatewayClass gwv1beta1.GatewayClass - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway-class"}, &gatewayClass) - require.NoError(t, err) - - // check our finalizers - require.Len(t, gatewayClass.Finalizers, 1) - require.EqualValues(t, gatewayClassFinalizer, gatewayClass.Finalizers[0]) - - // http route checks - var httproute gwv1beta1.HTTPRoute - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route", Namespace: "default"}, &httproute) - require.NoError(t, err) - - // check our finalizers - require.Len(t, httproute.Finalizers, 1) - require.EqualValues(t, gatewayFinalizer, httproute.Finalizers[0]) - - // check parent status - require.Len(t, httproute.Status.Parents, 1) - require.EqualValues(t, gatewayClassControllerName, httproute.Status.Parents[0].ControllerName) - require.EqualValues(t, "gateway", httproute.Status.Parents[0].ParentRef.Name) - checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(t, httproute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // tcp route checks - var tcpRoute gwv1alpha2.TCPRoute - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "tcp-route", Namespace: "default"}, &tcpRoute) - require.NoError(t, err) - - // check our finalizers - require.Len(t, tcpRoute.Finalizers, 1) - require.EqualValues(t, gatewayFinalizer, tcpRoute.Finalizers[0]) - - // check parent status - require.Len(t, tcpRoute.Status.Parents, 1) - require.EqualValues(t, gatewayClassControllerName, tcpRoute.Status.Parents[0].ControllerName) - require.EqualValues(t, "gateway", tcpRoute.Status.Parents[0].ParentRef.Name) - checkStatusCondition(t, tcpRoute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(t, tcpRoute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(t, tcpRoute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check that the Consul entries were created - var gateway *api.APIGatewayConfigEntry - var httpRoute *api.HTTPRouteConfigEntry - var route *api.TCPRouteConfigEntry - retry.RunWith(counter, t, func(r *retry.R) { - entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) - require.NoError(r, err) - gateway = entry.(*api.APIGatewayConfigEntry) - - entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) - require.NoError(r, err) - httpRoute = entry.(*api.HTTPRouteConfigEntry) - - entry, _, err = consulClient.ConfigEntries().Get(api.TCPRoute, "tcp-route", nil) - require.NoError(r, err) - route = entry.(*api.TCPRouteConfigEntry) - }) - - // now check the gateway status conditions - checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) - - // and the route status conditions - checkConsulStatusCondition(t, httpRoute.Status.Conditions, trueConsulCondition("Bound", "Bound")) - checkConsulStatusCondition(t, route.Status.Conditions, trueConsulCondition("Bound", "Bound")) - - // finally we check that we can actually route to the service via the gateway - k8sOptions := ctx.KubectlOptions(t) - targetHTTPAddress := fmt.Sprintf("http://%s", gatewayAddress) - targetHTTPSAddress := fmt.Sprintf("https://%s", gatewayAddress) - targetTCPAddress := fmt.Sprintf("http://%s:81", gatewayAddress) - - if c.secure { - // check that intentions keep our connection from happening - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddress) - - k8s.CheckStaticServerConnectionFailing(t, k8sOptions, StaticClientName, targetTCPAddress) - - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-k", targetHTTPSAddress) - - // Now we create the allow intention. - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - - // Now we create the allow intention tcp. - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server-tcp", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - } - - // Test that we can make a call to the api gateway - // via the static-client pod. It should route to the static-server pod. - logger.Log(t, "trying calls to api gateway http") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) - - logger.Log(t, "trying calls to api gateway tcp") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetTCPAddress) - - logger.Log(t, "trying calls to api gateway https") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPSAddress, "-k") - }) - } -} - -const ( - // valid JWT token with role of "doctor". - doctorToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJkb2N0b3IifQ.FfgpzjMf8Evh6K-fJ1cLXklfIXOm-vojVbWlPPbGVFtzxZ9hxMxoyAY_G8i36SfGrpUlp-RJ6ohMvprMrEgyRgbenu7u5kkm5iGHW-zpMus4izXRxPELBcpWOGF105HIssT2NYRstXieNR8EVzvGfLdvR0GW8ttEERgseqGvuAfdb4-aNYsysGwUUHbsZjazA6H1rZmWqHdCLOJ2ZwFsIdckO9CadnkyTILpcPUmLYyUVJdtlLGOySb0GG8c_dPML_IR5jSXCSUZt6S2JBNBNBdqukrlqpA-fIaaWft0dbWVMhv8DqPC8znult8dKvLZ1qXeU0itsqqJUyE16ihJjw" - // valid JWT token with role of "pet". - petToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJwZXQifQ.l94rJayGGTMB426HwEw5ipSjaIHjm-UWDHiBAlB_Slmi814AxAfl_0AdRwSz67UDnkoygKbvPpR5xUB03JCXNshLZuKLegWsBeQg_OJYvZGmFagl5NglBFvH7Jbta4e1eQoAxZI6Xyy1jHbu7jFBjQPVnK8EaRvWoW8Pe8a8rp_5xhub0pomhvRF6Pm5kAS4cMnxvqpVc5Oo5nO7ws_SmoNnbt2Ok14k23Zx5E2EWmGStOfbgFsdbhVbepB2DMzqv1j8jvBbwa_OxCwc_7pEOthOOxRV6L3ZjgbRSB4GumlXAOCBYXD1cRLgrMSrWB1GkefAKu8PV0Ho1px6sI9Evg" -) - -func TestAPIGateway_JWTAuth_Basic(t *testing.T) { - ctx := suite.Environment().DefaultContext(t) - cfg := suite.Config() - - if !cfg.EnableEnterprise { - t.Skipf("skipping this test because -enable-enterprise is not set") - } - - helmValues := map[string]string{ - "connectInject.enabled": "true", - "connectInject.consulNamespaces.mirroringK8S": "true", - "global.acls.manageSystemACLs": "true", - "global.tls.enabled": "true", - "global.logLevel": "trace", - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - - consulCluster.Create(t) - - // this is necesary when running tests with ACLs enabled - runTestsAsSecure := true - // Override the default proxy config settings for this test - consulClient, _ := consulCluster.SetupConsulClient(t, runTestsAsSecure) - _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ - Kind: api.ProxyDefaults, - Name: api.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http", - }, - }, nil) - require.NoError(t, err) - - logger.Log(t, "creating other namespace") - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "create", "namespace", "other") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "namespace", "other") - }) - - logger.Log(t, "creating api-gateway resources") - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth") - }) - - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-n", "other", "-f", "../fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-n", "other", "-f", "../fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml") - }) - - logger.Log(t, "try (and fail) to add a second gateway policy to the gateway") - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy") - require.Error(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy") - }) - - // Create certificate secret, we do this separately since - // applying the secret will make an invalid certificate that breaks other tests - logger.Log(t, "creating certificate secret") - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/bases/api-gateway/certificate.yaml") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/bases/api-gateway/certificate.yaml") - }) - - // patch certificate with data - logger.Log(t, "patching certificate secret with generated data") - certificate := generateCertificate(t, nil, "gateway.test.local") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") - - // We use the static-client pod so that we can make calls to the api gateway - // via kubectl exec without needing a route into the cluster from the test machine. - logger.Log(t, "creating static-client pod") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") - - k8s.RunKubectl(t, ctx.KubectlOptions(t), "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", "static-server")) - // Grab a kubernetes client so that we can verify binding - // behavior prior to issuing requests through the gateway. - k8sClient := ctx.ControllerRuntimeClient(t) - - // On startup, the controller can take upwards of 1m to perform - // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 2m timeout here). - var ( - gatewayAddress string - gatewayClass gwv1beta1.GatewayClass - httpRoute gwv1beta1.HTTPRoute - httpRouteAuth gwv1beta1.HTTPRoute - httpRouteAuth2 gwv1beta1.HTTPRoute - httpRouteNoAuthOnAuthListener gwv1beta1.HTTPRoute - httpRouteInvalid gwv1beta1.HTTPRoute - ) - - counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} - retry.RunWith(counter, t, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) - require.NoError(r, err) - - // check our finalizers - require.Len(r, gateway.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, gateway.Finalizers[0]) - - // check our statuses - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) - require.Len(r, gateway.Status.Listeners, 5) - - require.EqualValues(r, int32(3), gateway.Status.Listeners[0].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, int32(1), gateway.Status.Listeners[1].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - - // check that we have an address to use - require.Len(r, gateway.Status.Addresses, 1) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.Status.Addresses[0].Value - - // gateway class checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway-class"}, &gatewayClass) - require.NoError(r, err) - - // check our finalizers - require.Len(r, gatewayClass.Finalizers, 1) - require.EqualValues(r, gatewayClassFinalizer, gatewayClass.Finalizers[0]) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route", Namespace: "default"}, &httpRoute) - require.NoError(r, err) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth", Namespace: "default"}, &httpRouteAuth) - require.NoError(r, err) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-no-auth-on-auth-listener", Namespace: "default"}, &httpRouteNoAuthOnAuthListener) - require.NoError(r, err) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route2-auth", Namespace: "default"}, &httpRouteAuth2) - require.NoError(r, err) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth-invalid", Namespace: "default"}, &httpRouteInvalid) - require.NoError(r, err) - - // check our finalizers - require.Len(r, httpRoute.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRoute.Finalizers[0]) - - // check parent status - require.Len(r, httpRoute.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRoute.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRoute.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check our finalizers - require.Len(r, httpRouteAuth.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRouteAuth.Finalizers[0]) - - // check parent status - require.Len(r, httpRouteAuth.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRouteAuth.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRouteAuth.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check our finalizers - require.Len(r, httpRouteNoAuthOnAuthListener.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRouteNoAuthOnAuthListener.Finalizers[0]) - - // check parent status - require.Len(r, httpRouteNoAuthOnAuthListener.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRouteNoAuthOnAuthListener.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRouteNoAuthOnAuthListener.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check our finalizers - require.Len(r, httpRouteAuth2.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRouteAuth2.Finalizers[0]) - - // check parent status - require.Len(r, httpRouteAuth2.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRouteAuth2.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRouteAuth2.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check parent status - require.Len(r, httpRouteInvalid.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRouteInvalid.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRouteInvalid.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, falseCondition("Accepted", "FilterNotFound")) - checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - }) - - // check that the Consul entries were created - entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) - require.NoError(t, err) - gateway := entry.(*api.APIGatewayConfigEntry) - - entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) - require.NoError(t, err) - consulHTTPRoute := entry.(*api.HTTPRouteConfigEntry) - - entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route-auth", nil) - require.NoError(t, err) - consulHTTPRouteAuth := entry.(*api.HTTPRouteConfigEntry) - - // now check the gateway status conditions - checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) - - // and the route status conditions - checkConsulStatusCondition(t, consulHTTPRoute.Status.Conditions, trueConsulCondition("Bound", "Bound")) - checkConsulStatusCondition(t, consulHTTPRouteAuth.Status.Conditions, trueConsulCondition("Bound", "Bound")) - - // finally we check that we can actually route to the service(s) via the gateway - k8sOptions := ctx.KubectlOptions(t) - targetHTTPAddress := fmt.Sprintf("http://%s/v1", gatewayAddress) - targetHTTPAddressAdmin := fmt.Sprintf("http://%s:8081/admin", gatewayAddress) - targetHTTPAddressPet := fmt.Sprintf("http://%s:8081/pet", gatewayAddress) - targetHTTPAddressAdmin2 := fmt.Sprintf("http://%s:8081/admin-2", gatewayAddress) - targetHTTPAddressPet2 := fmt.Sprintf("http://%s:8081/pet-2", gatewayAddress) - targetHTTPAddressAdminNoAuthOnRoute := fmt.Sprintf("http://%s:8081/admin-no-auth", gatewayAddress) - targetHTTPAddressPetNotAuthOnRoute := fmt.Sprintf("http://%s:8081/pet-no-auth", gatewayAddress) - - // Now we create the allow intention. - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server-protected", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - - // Test that we can make a call to the api gateway - logger.Log(t, "trying calls to api gateway http") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) - - // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that a "role" of "doctor" - // we can see that: - // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /admin should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /admin should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdmin) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /admin should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdmin) - - // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has a "role" of "pet" - // the route does not define - // we can see that: - // * the "role" verification in the gateway default is used - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /pet should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /pet should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPet) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /pet should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPet) - - // ensure that routes attached to the same gateway don't cause changes in another route - // * the verification on the gateway is the only one used as this route does not define any JWT configuration - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /pet-no-auth should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPetNotAuthOnRoute) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /pet-no-auth should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPetNotAuthOnRoute) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /pet-no-auth should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPetNotAuthOnRoute) - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /admin-no-auth should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdminNoAuthOnRoute) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /admin-no-auth should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdminNoAuthOnRoute) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /admin-no-auth should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdminNoAuthOnRoute) - - // multiple routes can use the same external ref - // we can see that: - // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /admin-2 should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin2) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /admin-2 should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdmin2) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /admin-2 should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdmin2) - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /pet-2 should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet2) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /pet-2 should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPet2) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /pet-2 should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPet2) -} - -func checkStatusCondition(t require.TestingT, conditions []metav1.Condition, toCheck metav1.Condition) { - for _, c := range conditions { - if c.Type == toCheck.Type { - require.EqualValues(t, toCheck.Reason, c.Reason) - require.EqualValues(t, toCheck.Status, c.Status) - return - } - } - - t.Errorf("expected condition not found: %s", toCheck.Type) -} - -func trueCondition(conditionType, reason string) metav1.Condition { - return metav1.Condition{ - Type: conditionType, - Reason: reason, - Status: metav1.ConditionTrue, - } -} - -func falseCondition(conditionType, reason string) metav1.Condition { - return metav1.Condition{ - Type: conditionType, - Reason: reason, - Status: metav1.ConditionFalse, - } -} - -func checkConsulStatusCondition(t require.TestingT, conditions []api.Condition, toCheck api.Condition) { - for _, c := range conditions { - if c.Type == toCheck.Type { - require.EqualValues(t, toCheck.Reason, c.Reason) - require.EqualValues(t, toCheck.Status, c.Status) - return - } - } - - t.Errorf("expected condition not found: %s", toCheck.Type) -} - -func trueConsulCondition(conditionType, reason string) api.Condition { - return api.Condition{ - Type: conditionType, - Reason: reason, - Status: "True", - } -} diff --git a/acceptance/tests/api-gateway/example_test.go b/acceptance/tests/api-gateway/example_test.go new file mode 100644 index 0000000000..b324ac31fe --- /dev/null +++ b/acceptance/tests/api-gateway/example_test.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Rename package to your test package. +package example + +import ( + "context" + "testing" + + "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" + "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestExample(t *testing.T) { + // Get test configuration. + cfg := suite.Config() + + // Get the default context. + ctx := suite.Environment().DefaultContext(t) + + // Create Helm values for the Helm install. + helmValues := map[string]string{ + "exampleFeature.enabled": "true", + } + + // Generate a random name for this test. + releaseName := helpers.RandomName() + + // Create a new Consul cluster object. + consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) + + // Create the Consul cluster with Helm. + consulCluster.Create(t) + + // Make test assertions. + + // To run kubectl commands, you need to get KubectlOptions from the test context. + // There are a number of kubectl commands available in the helpers/kubectl.go file. + // For example, to call 'kubectl apply' from the test write the following: + k8s.KubectlApply(t, ctx.KubectlOptions(t), "path/to/config") + + // Clean up any Kubernetes resources you have created + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + k8s.KubectlDelete(t, ctx.KubectlOptions(t), "path/to/config") + }) + + // Similarly, you can obtain Kubernetes client from your test context. + // You can use it to, for example, read all services in a namespace: + k8sClient := ctx.KubernetesClient(t) + services, err := k8sClient.CoreV1().Services(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + require.NotNil(t, services.Items) + + // To make Consul API calls, you can get the Consul client from the consulCluster object, + // indicating whether the client needs to be secure or not (i.e. whether TLS and ACLs are enabled on the Consul cluster): + consulClient, _ := consulCluster.SetupConsulClient(t, true) + consulServices, _, err := consulClient.Catalog().Services(nil) + require.NoError(t, err) + require.NotNil(t, consulServices) +} diff --git a/acceptance/tests/api-gateway/main_test.go b/acceptance/tests/api-gateway/main_test.go index f408845b3e..f92fff8a59 100644 --- a/acceptance/tests/api-gateway/main_test.go +++ b/acceptance/tests/api-gateway/main_test.go @@ -1,10 +1,10 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package apigateway +// Rename package to your test package. +package example import ( - "os" "testing" testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" @@ -13,6 +13,25 @@ import ( var suite testsuite.Suite func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) + // First, uncomment the line below to create a new suite so that all flags are parsed. + /* + suite = framework.NewSuite(m) + */ + + // If the test suite needs to run only when certain test flags are passed, + // you need to handle that in the TestMain function. + // Uncomment and modify example code below if that is the case. + /* + if suite.Config().EnableExampleFeature { + os.Exit(suite.Run()) + } else { + fmt.Println("Skipping example feature tests because -enable-example-feature is not set") + os.Exit(0) + } + */ + + // If the test suite should run in every case, uncomment the line below. + /* + os.Exit(suite.Run()) + */ } diff --git a/acceptance/tests/basic/basic_test.go b/acceptance/tests/basic/basic_test.go index 4727b53495..91047711a1 100644 --- a/acceptance/tests/basic/basic_test.go +++ b/acceptance/tests/basic/basic_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package basic import ( diff --git a/acceptance/tests/basic/main_test.go b/acceptance/tests/basic/main_test.go index 0e400ef870..fa858ee4ae 100644 --- a/acceptance/tests/basic/main_test.go +++ b/acceptance/tests/basic/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package basic import ( diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index cfee1560ae..1af26d62c7 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -1,10 +1,6 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cli import ( - "context" "fmt" "strings" "testing" @@ -16,7 +12,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ipv4RegEx = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" @@ -52,8 +47,8 @@ func TestInstall(t *testing.T) { connHelper.Install(t) connHelper.DeployClientAndServer(t) if c.secure { - connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) - connHelper.CreateIntention(t, connhelper.IntentionOpts{}) + connHelper.TestConnectionFailureWithoutIntention(t) + connHelper.CreateIntention(t) } // Run proxy list and get the two results. @@ -70,11 +65,11 @@ func TestInstall(t *testing.T) { retrier := &retry.Timer{Timeout: 160 * time.Second, Wait: 2 * time.Second} retry.RunWith(retrier, t, func(r *retry.R) { for podName := range list { - out, err := cli.Run(r, ctx.KubectlOptions(r), "proxy", "read", podName) + out, err := cli.Run(t, ctx.KubectlOptions(t), "proxy", "read", podName) require.NoError(r, err) output := string(out) - r.Log(output) + logger.Log(t, output) // Both proxies must see their own local agent and app as clusters. require.Regexp(r, "consul-dataplane.*STATIC", output) @@ -88,40 +83,7 @@ func TestInstall(t *testing.T) { } }) - // Troubleshoot: Get the client pod so we can portForward to it and get the 'troubleshoot upstreams' output - clientPod, err := connHelper.Ctx.KubernetesClient(t).CoreV1().Pods(connHelper.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: "app=static-client", - }) - require.NoError(t, err) - - clientPodName := clientPod.Items[0].Name - upstreamsOut, err := cli.Run(t, ctx.KubectlOptions(t), "troubleshoot", "upstreams", "-pod", clientPodName) - logger.Log(t, string(upstreamsOut)) - require.NoError(t, err) - - if cfg.EnableTransparentProxy { - // If tproxy is enabled we are looking for the upstream ip which is the ClusterIP of the Kubernetes Service - serverService, err := connHelper.Ctx.KubernetesClient(t).CoreV1().Services(connHelper.Ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - FieldSelector: "metadata.name=static-server", - }) - require.NoError(t, err) - serverIP := serverService.Items[0].Spec.ClusterIP - - proxyOut, err := cli.Run(t, ctx.KubectlOptions(t), "troubleshoot", "proxy", "-pod", clientPodName, "-upstream-ip", serverIP) - require.NoError(t, err) - require.Regexp(t, "Upstream resources are valid", string(proxyOut)) - logger.Log(t, string(proxyOut)) - } else { - // With tproxy disabled and explicit upstreams we need the envoy-id of the server - require.Regexp(t, "static-server", string(upstreamsOut)) - - proxyOut, err := cli.Run(t, ctx.KubectlOptions(t), "troubleshoot", "proxy", "-pod", clientPodName, "-upstream-envoy-id", "static-server") - require.NoError(t, err) - require.Regexp(t, "Upstream resources are valid", string(proxyOut)) - logger.Log(t, string(proxyOut)) - } - - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionSuccess(t) connHelper.TestConnectionFailureWhenUnhealthy(t) }) } diff --git a/acceptance/tests/cli/cli_upgrade_test.go b/acceptance/tests/cli/cli_upgrade_test.go index 8c8970d88a..6fcf82f738 100644 --- a/acceptance/tests/cli/cli_upgrade_test.go +++ b/acceptance/tests/cli/cli_upgrade_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cli import ( diff --git a/acceptance/tests/cli/main_test.go b/acceptance/tests/cli/main_test.go index 2e66d89543..85cef25abe 100644 --- a/acceptance/tests/cli/main_test.go +++ b/acceptance/tests/cli/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cli import ( diff --git a/acceptance/tests/cloud/fakeserver_client.go b/acceptance/tests/cloud/fakeserver_client.go deleted file mode 100644 index 7cfe8e799d..0000000000 --- a/acceptance/tests/cloud/fakeserver_client.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cloud - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "strconv" -) - -const ( - // recordsPathConsul and recordsPathCollector distinguish metrics for consul vs. collector when fetching records. - recordsPathConsul = "v1/metrics/consul" - recordsPathCollector = "v1/metrics/collector" -) - -var ( - errEncodingPayload = errors.New("failed to encode payload") - errCreatingRequest = errors.New("failed to create HTTP request") - errMakingRequest = errors.New("failed to make request") - errReadingBody = errors.New("failed to read body") - errClosingBody = errors.New("failed to close body") - errParsingBody = errors.New("failed to parse body") -) - -// fakeServerClient provides an interface to communicate with the fakesever (a fake HCP Telemetry Gateway) via HTTP. -type fakeServerClient struct { - client *http.Client - tunnel string -} - -// modifyTelemetryConfigBody is a POST body that provides telemetry config changes to the fakeserver. -type modifyTelemetryConfigBody struct { - Filters []string `json:"filters"` - Labels map[string]string `json:"labels"` - Disabled bool `json:"disabled"` -} - -// TokenResponse is used to read a token response from the fakeserver. -type TokenResponse struct { - Token string `json:"token"` -} - -// RecordsResponse is used to read a /records response from the fakeserver. -type RecordsResponse struct { - Records []*RequestRecord `json:"records"` -} - -// RequestRecord holds info about a single request. -type RequestRecord struct { - Method string `json:"method"` - Path string `json:"path"` - Body []byte `json:"body"` - ValidRequest bool `json:"validRequest"` - Timestamp int64 `json:"timestamp"` -} - -// newfakeServerClient returns a fakeServerClient to be used in tests to communicate with the fake Telemetry Gateway. -func newfakeServerClient(tunnel string) *fakeServerClient { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - - return &fakeServerClient{ - client: &http.Client{Transport: tr}, - tunnel: tunnel, - } -} - -// requestToken retrieves a token from the fakeserver's token endpoint. -func (f *fakeServerClient) requestToken() (string, error) { - url := fmt.Sprintf("https://%s/token", f.tunnel) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return "", fmt.Errorf("%w: %w", errCreatingRequest, err) - } - - resp, err := f.handleRequest(req) - if err != nil { - return "", err - } - - tokenResponse := &TokenResponse{} - err = json.Unmarshal(resp, tokenResponse) - if err != nil { - return "", fmt.Errorf("%w : %w", errParsingBody, err) - } - - return tokenResponse.Token, nil -} - -// modifyTelemetryConfig can update the telemetry config returned by the fakeserver. -// via the fakeserver's modify_telemetry_config endpoint. -func (f *fakeServerClient) modifyTelemetryConfig(payload *modifyTelemetryConfigBody) error { - url := fmt.Sprintf("https://%s/modify_telemetry_config", f.tunnel) - payloadBuf := new(bytes.Buffer) - - err := json.NewEncoder(payloadBuf).Encode(payload) - if err != nil { - return fmt.Errorf("%w:%w", errEncodingPayload, err) - } - - req, err := http.NewRequest("POST", url, payloadBuf) - if err != nil { - return fmt.Errorf("%w: %w", errCreatingRequest, err) - } - - _, err = f.handleRequest(req) - - return err -} - -func (f *fakeServerClient) getRecordsForPath(path string, refreshTime int64) ([]*RequestRecord, error) { - url := fmt.Sprintf("https://%s/records/%s", f.tunnel, path) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, fmt.Errorf("%w: %w", errCreatingRequest, err) - } - if refreshTime > 0 { - q := req.URL.Query() - q.Add("since", strconv.FormatInt(refreshTime, 10)) - req.URL.RawQuery = q.Encode() - } - - resp, err := f.handleRequest(req) - if err != nil { - return nil, err - } - - recordsResponse := &RecordsResponse{} - err = json.Unmarshal(resp, recordsResponse) - if err != nil { - return nil, fmt.Errorf("%w : %w", errParsingBody, err) - } - - return recordsResponse.Records, nil -} - -// handleRequest returns the response body if the request is succesful. -func (f *fakeServerClient) handleRequest(req *http.Request) ([]byte, error) { - resp, err := f.client.Do(req) - if err != nil { - return nil, fmt.Errorf("%w : %w", errMakingRequest, err) - } - body, err := io.ReadAll(resp.Body) - cErr := resp.Body.Close() - if cErr != nil { - return nil, fmt.Errorf("%w : %w", errClosingBody, err) - } - if err != nil { - return nil, fmt.Errorf("%w : %w", errReadingBody, err) - } - - return body, nil -} diff --git a/acceptance/tests/cloud/load/main_test.go b/acceptance/tests/cloud/load/main_test.go deleted file mode 100644 index 4629e0d4d0..0000000000 --- a/acceptance/tests/cloud/load/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package load - -import ( - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) -} diff --git a/acceptance/tests/cloud/load/remote.go b/acceptance/tests/cloud/load/remote.go deleted file mode 100644 index 5ce2ffa286..0000000000 --- a/acceptance/tests/cloud/load/remote.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package load - -import ( - "fmt" - "strings" -) - -// GetDomainSuffix gets the suffix. -func GetDomainSuffix(env string) (string, error) { - suffix, ok := map[string]string{ - "dev-remote": "hcp.dev", - "dev": "hcp.dev", - "int": "hcp.to", - "prod": "hashicorp.cloud", - }[strings.ToLower(env)] - - if !ok { - return "", fmt.Errorf("unrecognized env: %s", env) - } - return suffix, nil -} - -// GetAPIAddr returns the address of HCP given the passed environment. -func GetAPIAddr(env string) (string, error) { - suffix, ok := map[string]string{ - "local": "http://127.0.0.1:28081", - "dev-remote": "https://api.hcp.dev", - "dev": "https://api.hcp.dev", - "int": "https://api.hcp.to", - "prod": "https://api.hashicorp.cloud", - }[strings.ToLower(env)] - - if !ok { - return "", fmt.Errorf("unrecognized env: %s", env) - } - return suffix, nil -} - -// GetAuthIDP returns the authidp for the env. -func GetAuthIDP(env string) (string, error) { - suffix, err := GetDomainSuffix(env) - if err != nil { - return "", fmt.Errorf("unrecognized env: %w", err) - } - - return fmt.Sprintf("https://auth.idp.%s", suffix), nil -} - -// GetScadaAddr returns the scadara for the env. -func GetScadaAddr(env string) (string, error) { - suffix, err := GetDomainSuffix(env) - if err != nil { - return "", fmt.Errorf("unrecognized env: %w", err) - } - - return fmt.Sprintf("https://scada.internal.%s:7224", suffix), nil -} - -// GetScadaAddr returns the scadara for the env. -func GetScadaAddrWithoutProtocol(env string) (string, error) { - suffix, err := GetDomainSuffix(env) - if err != nil { - return "", fmt.Errorf("unrecognized env: %w", err) - } - - return fmt.Sprintf("scada.internal.%s:7224", suffix), nil -} diff --git a/acceptance/tests/cloud/load/remote_load_test.go b/acceptance/tests/cloud/load/remote_load_test.go deleted file mode 100644 index 2ca8e1b814..0000000000 --- a/acceptance/tests/cloud/load/remote_load_test.go +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package load - -import ( - "crypto/tls" - "encoding/json" - "os" - "strconv" - "testing" - "text/template" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - - hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" - hcpcfg "github.com/hashicorp/hcp-sdk-go/config" - "github.com/hashicorp/hcp-sdk-go/httpclient" - "github.com/hashicorp/hcp-sdk-go/resource" -) - -type DevTokenResponse struct { - Token string `json:"token"` -} - -type hcp struct { - ResourceID string - ClientID string - ClientSecret string - AuthURL string - APIHostname string - ScadaAddress string -} - -func TestLoadTestCTGW(t *testing.T) { - _, rIDok := os.LookupEnv("HCP_RESOURCE_ID") - _, cIDok := os.LookupEnv("HCP_CLIENT_ID") - _, cSECok := os.LookupEnv("HCP_CLIENT_SECRET") - - if !rIDok || !cIDok || !cSECok { - t.Log("Must set HCP_RESOURCE_ID, HCP_CLIENT_ID and HCP_CLIENT_SECRET") - t.FailNow() - } - - apiHost := os.Getenv("HCP_AUTH_URL") - if apiHost == "" { - apiHost = "https://api.hcp.dev" - } - authURL := os.Getenv("HCP_API_HOST") - if authURL == "" { - authURL = "https://auth.idp.hcp.dev" - } - scadaAddr := os.Getenv("HCP_SCADA_ADDRESS") - if scadaAddr == "" { - scadaAddr = "scada.internal.hcp.dev:7224" - } - - env := os.Getenv("HCP_ENV") - var err error - if env != "" { - apiHost, err = GetAPIAddr(env) - require.NoError(t, err) - authURL, err = GetAuthIDP(env) - require.NoError(t, err) - scadaAddr, err = GetScadaAddrWithoutProtocol(env) - require.NoError(t, err) - } - - ctx := suite.Environment().DefaultContext(t) - - kubectlOptions := ctx.KubectlOptions(t) - ns := kubectlOptions.Namespace - k8sClient := environment.KubernetesClientFromOptions(t, kubectlOptions) - - var ( - resourceSecretName = "resource-sec-name" - resourceSecretKey = "resource-sec-key" - resourceSecretKeyValue = os.Getenv("HCP_RESOURCE_ID") - - clientIDSecretName = "clientid-sec-name" - clientIDSecretKey = "clientid-sec-key" - clientIDSecretKeyValue = os.Getenv("HCP_CLIENT_ID") - - clientSecretName = "client-sec-name" - clientSecretKey = "client-sec-key" - clientSecretKeyValue = os.Getenv("HCP_CLIENT_SECRET") - - apiHostSecretName = "apihost-sec-name" - apiHostSecretKey = "apihost-sec-key" - apiHostSecretKeyValue = apiHost - - authUrlSecretName = "authurl-sec-name" - authUrlSecretKey = "authurl-sec-key" - authUrlSecretKeyValue = authURL - - scadaAddressSecretName = "scadaaddress-sec-name" - scadaAddressSecretKey = "scadaaddress-sec-key" - scadaAddressSecretKeyValue = scadaAddr - - bootstrapTokenSecretName = "bootstrap-token" - bootstrapTokenSecretKey = "token" - ) - - aclToken := os.Getenv("HCP_CONSUL_TOKEN") - - // This should never happen during the load test since we should have already controlled for it. - if aclToken == "" { - hcpCfg := hcp{ - ResourceID: resourceSecretKeyValue, - ClientID: clientIDSecretKeyValue, - ClientSecret: clientSecretKeyValue, - AuthURL: authUrlSecretKeyValue, - APIHostname: apiHostSecretKeyValue, - ScadaAddress: scadaAddressSecretKeyValue, - } - - aclToken = hcpCfg.fetchAgentBootstrapConfig(t) - } - - cfg := suite.Config() - consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, bootstrapTokenSecretName, bootstrapTokenSecretKey, aclToken) - - releaseName := helpers.RandomName() - - helmValues := map[string]string{ - "global.imagePullPolicy": "IfNotPresent", - "global.cloud.enabled": "true", - "global.cloud.resourceId.secretName": resourceSecretName, - "global.cloud.resourceId.secretKey": resourceSecretKey, - - "global.cloud.clientId.secretName": clientIDSecretName, - "global.cloud.clientId.secretKey": clientIDSecretKey, - - "global.cloud.clientSecret.secretName": clientSecretName, - "global.cloud.clientSecret.secretKey": clientSecretKey, - - "global.cloud.apiHost.secretName": apiHostSecretName, - "global.cloud.apiHost.secretKey": apiHostSecretKey, - - "global.cloud.authUrl.secretName": authUrlSecretName, - "global.cloud.authUrl.secretKey": authUrlSecretKey, - - "global.cloud.scadaAddress.secretName": scadaAddressSecretName, - "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, - "connectInject.default": "true", - - "global.acls.manageSystemACLs": "true", - "global.acls.bootstrapToken.secretName": bootstrapTokenSecretName, - "global.acls.bootstrapToken.secretKey": bootstrapTokenSecretKey, - - "global.gossipEncryption.autoGenerate": "false", - "global.tls.enabled": "true", - "global.tls.enableAutoEncrypt": "true", - - "global.metrics.enableTelemetryCollector": "true", - "server.replicas": "3", - "server.affinity": "null", - - "global.acls.resources.requests.memory": "15Mi", - "global.acls.resources.requests.cpu": "5m", - "global.acls.resources.limits.memory": "15Mi", - "global.acls.resources.limits.cpu": "5m", - - "connectInject.initContainer.resources.requests.memory": "15Mi", - "connectInject.initContainer.resources.requests.cpu": "5m", - - "connectInject.initContainer.resources.limits.memory": "15Mi", - "connectInject.initContainer.resources.limits.cpu": "5m", - - "telemetryCollector.enabled": "true", - "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, - "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, - - "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, - "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, - "telemetryCollector.resources.requests.cpu": "100m", - "telemetryCollector.resources.limits.cpu": "100m", - - // Either we set the global.trustedCAs (make sure it's idented exactly) or we - // set TLS to insecure - - "telemetryCollector.extraEnvironmentVars.HCP_API_ADDRESS": apiHostSecretKeyValue, - } - - if cfg.ConsulImage != "" { - helmValues["global.image"] = cfg.ConsulImage - } - if cfg.ConsulCollectorImage != "" { - helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage - } - - consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), cfg, releaseName) - consulCluster.ChartPath = "../../../../charts/consul" - consulCluster.Create(t) - - logger.Log(t, "setting acl permissions for collector and services") - aclDir := "../../fixtures/bases/cloud/service-intentions" - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), aclDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), aclDir) - }) - - logger.Log(t, "creating static-server deployment") - nServices := 2 - nServicesStr := os.Getenv("LOAD_TEST_RUNS") - if nServicesStr != "" { - i, err := strconv.Atoi(nServicesStr) - require.NoError(t, err) - nServices = i - } - - tmpl, err := template.ParseFiles("../../fixtures/bases/pingpong/template.tmpl") - if err != nil { - require.NoError(t, err) - } - - for i := 0; i < nServices; i++ { - data := &tmplData{Iteration: i, Replicas: 1} - tmplIt, err := tmpl.Clone() - require.NoError(t, err) - - // Create a temporary file - tempFile, err := os.CreateTemp("", "temp*.yaml") - if err != nil { - require.NoError(t, err) - } - - err = tmplIt.Execute(tempFile, data) - require.NoError(t, err) - // Close the temporary file to ensure that changes are saved - err = tempFile.Close() - require.NoError(t, err) - - k8s.KubectlApply(t, ctx.KubectlOptions(t), tempFile.Name()) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDelete(t, ctx.KubectlOptions(t), tempFile.Name()) - os.Remove(tempFile.Name()) - }) - } -} - -type tmplData struct { - Iteration int - Replicas int -} - -// fetchAgentBootstrapConfig use the resource-id, client-id, and client-secret -// to call to the agent bootstrap config endpoint and parse the response into a -// CloudBootstrapConfig struct. -func (c *hcp) fetchAgentBootstrapConfig(t *testing.T) string { - cfg, err := c.HCPConfig() - require.NoError(t, err) - logger.Log(t, "Fetching Consul cluster configuration from HCP") - httpClientCfg := httpclient.Config{ - HCPConfig: cfg, - } - clientRuntime, err := httpclient.New(httpClientCfg) - require.NoError(t, err) - - hcpgnmClient := hcpgnm.New(clientRuntime, nil) - clusterResource, err := resource.FromString(c.ResourceID) - require.NoError(t, err) - - params := hcpgnm.NewAgentBootstrapConfigParams(). - WithID(clusterResource.ID). - WithLocationOrganizationID(clusterResource.Organization). - WithLocationProjectID(clusterResource.Project) - - resp, err := hcpgnmClient.AgentBootstrapConfig(params, nil) - require.NoError(t, err) - - bootstrapConfig := resp.GetPayload() - logger.Log(t, "HCP configuration successfully fetched.") - - return c.parseBootstrapConfigResponse(t, bootstrapConfig) -} - -// ConsulConfig represents 'cluster.consul_config' in the response -// fetched from the agent bootstrap config endpoint in HCP. -type ConsulConfig struct { - ACL ACL `json:"acl"` -} - -// ACL represents 'cluster.consul_config.acl' in the response -// fetched from the agent bootstrap config endpoint in HCP. -type ACL struct { - Tokens Tokens `json:"tokens"` -} - -// Tokens represents 'cluster.consul_config.acl.tokens' in the -// response fetched from the agent bootstrap config endpoint in HCP. -type Tokens struct { - Agent string `json:"agent"` - InitialManagement string `json:"initial_management"` -} - -// parseBootstrapConfigResponse unmarshals the boostrap parseBootstrapConfigResponse -// and also sets the HCPConfig values to return CloudBootstrapConfig struct. -func (c *hcp) parseBootstrapConfigResponse(t *testing.T, bootstrapRepsonse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) string { - - var consulConfig ConsulConfig - err := json.Unmarshal([]byte(bootstrapRepsonse.Bootstrap.ConsulConfig), &consulConfig) - require.NoError(t, err) - - return consulConfig.ACL.Tokens.InitialManagement -} - -func (c *hcp) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { - if c.ClientID != "" && c.ClientSecret != "" { - opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret)) - } - if c.AuthURL != "" { - opts = append(opts, hcpcfg.WithAuth(c.AuthURL, &tls.Config{})) - } - if c.APIHostname != "" { - opts = append(opts, hcpcfg.WithAPI(c.APIHostname, &tls.Config{})) - } - if c.ScadaAddress != "" { - opts = append(opts, hcpcfg.WithSCADA(c.ScadaAddress, &tls.Config{})) - } - opts = append(opts, hcpcfg.FromEnv(), hcpcfg.WithoutBrowserLogin()) - return hcpcfg.NewHCPConfig(opts...) -} diff --git a/acceptance/tests/cloud/main_test.go b/acceptance/tests/cloud/main_test.go deleted file mode 100644 index 85d1867933..0000000000 --- a/acceptance/tests/cloud/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cloud - -import ( - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) -} diff --git a/acceptance/tests/cloud/metrics_validation.go b/acceptance/tests/cloud/metrics_validation.go deleted file mode 100644 index 9288f6e615..0000000000 --- a/acceptance/tests/cloud/metrics_validation.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cloud - -import ( - "strings" - - "github.com/hashicorp/serf/testutil/retry" - "github.com/stretchr/testify/require" - otlpcolmetrics "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - otlpcommon "go.opentelemetry.io/proto/otlp/common/v1" - otlpmetrics "go.opentelemetry.io/proto/otlp/metrics/v1" - "google.golang.org/protobuf/proto" -) - -type metricValidations struct { - disabled bool - expectedMetricName string - disallowedMetricName string - expectedLabelKeys []string -} - -// validateMetrics ensure OTLP metrics as recorded by the Collector or Consul as expected. -func validateMetrics(r *retry.R, records []*RequestRecord, validations *metricValidations, since int64) { - // If metrics are disabled, no metrics records should exist, and return early. - if validations.disabled { - require.Empty(r, records) - return - } - - // If metrics are not disabled, records should not be empty. - require.NotEmpty(r, records) - - for _, record := range records { - require.True(r, record.ValidRequest, "expected request to be valid") - - req := &otlpcolmetrics.ExportMetricsServiceRequest{} - err := proto.Unmarshal(record.Body, req) - require.NoError(r, err, "failed to extract metrics from body") - - // Basic validation that metrics are not empty. - require.NotEmpty(r, req.GetResourceMetrics()) - require.NotEmpty(r, req.ResourceMetrics[0].GetScopeMetrics()) - require.NotEmpty(r, req.ResourceMetrics[0].ScopeMetrics[0].GetMetrics()) - - // Verify expected key labels and metric names. - labels := externalLabels(req, since) - for _, key := range validations.expectedLabelKeys { - require.Contains(r, labels, key) - } - validateMetricName(r, req, validations) - } -} - -// validateMetricName ensures an expected metric name has been recorded based on filters and disallowed metrics are not present. -func validateMetricName(t *retry.R, request *otlpcolmetrics.ExportMetricsServiceRequest, validations *metricValidations) { - exists := false - for _, metric := range request.ResourceMetrics[0].ScopeMetrics[0].GetMetrics() { - require.NotContains(t, metric.Name, validations.disallowedMetricName) - - if strings.Contains(metric.Name, validations.expectedMetricName) { - exists = true - } - } - - require.True(t, exists) -} - -// externalLabels converts OTLP labels to a map[string]string format. -func externalLabels(request *otlpcolmetrics.ExportMetricsServiceRequest, since int64) map[string]string { - // For the Consul Telemetry Collector, labels are contained at the higher level scope. - attrs := request.ResourceMetrics[0].GetResource().GetAttributes() - - // For Consul server metrics, labels are contained with individual metrics, and must be extracted. - if len(attrs) < 1 { - attrs = getMetricLabel(request.ResourceMetrics[0].GetScopeMetrics(), since) - } - - labels := make(map[string]string, len(attrs)) - for _, kv := range attrs { - k := strings.ReplaceAll(kv.GetKey(), ".", "_") - labels[k] = kv.GetValue().GetStringValue() - } - - return labels -} - -// getMetricLabel returns labels at each datapoint within a metric. -func getMetricLabel(scopeMetrics []*otlpmetrics.ScopeMetrics, since int64) []*otlpcommon.KeyValue { - // The attributes field can only be accessed on the specific implementation (gauge, sum or hist). - for _, metric := range scopeMetrics[0].Metrics { - switch v := metric.Data.(type) { - case *otlpmetrics.Metric_Gauge: - for _, dp := range v.Gauge.GetDataPoints() { - // When a refresh has occured, filter time since last refresh as older data points may not have latest labels. - if dp.StartTimeUnixNano > uint64(since) { - return dp.Attributes - } - } - case *otlpmetrics.Metric_Histogram: - for _, dp := range v.Histogram.GetDataPoints() { - if dp.StartTimeUnixNano > uint64(since) { - return dp.Attributes - } - } - case *otlpmetrics.Metric_Sum: - for _, dp := range v.Sum.GetDataPoints() { - if dp.StartTimeUnixNano > uint64(since) { - return dp.Attributes - } - } - } - } - - return []*otlpcommon.KeyValue{} -} diff --git a/acceptance/tests/cloud/observability_test.go b/acceptance/tests/cloud/observability_test.go deleted file mode 100644 index af553b3ecb..0000000000 --- a/acceptance/tests/cloud/observability_test.go +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cloud - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/google/uuid" - terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/serf/testutil/retry" - "github.com/stretchr/testify/require" -) - -var ( - resourceSecretName = "resource-sec-name" - resourceSecretKey = "resource-sec-key" - resourceSecretKeyValue = "organization/11eb1a35-aac0-f7c7-8fe1-0242ac110008/project/11eb1a35-ab64-d576-8fe1-0242ac110008/hashicorp.consul.global-network-manager.cluster/TEST" - - clientIDSecretName = "clientid-sec-name" - clientIDSecretKey = "clientid-sec-key" - clientIDSecretKeyValue = "clientid" - - clientSecretName = "client-sec-name" - clientSecretKey = "client-sec-key" - clientSecretKeyValue = "client-secret" - - apiHostSecretName = "apihost-sec-name" - apiHostSecretKey = "apihost-sec-key" - apiHostSecretKeyValue = "fake-server:443" - - authUrlSecretName = "authurl-sec-name" - authUrlSecretKey = "authurl-sec-key" - authUrlSecretKeyValue = "https://fake-server:443" - - scadaAddressSecretName = "scadaaddress-sec-name" - scadaAddressSecretKey = "scadaaddress-sec-key" - scadaAddressSecretKeyValue = "fake-server:443" - - bootstrapTokenSecretName = "bootstrap-token" - bootstrapTokenSecretKey = "token" - bootstrapToken = uuid.NewString() -) - -func TestObservabilityCloud(t *testing.T) { - cfg := suite.Config() - - if cfg.HCPResourceID != "" { - resourceSecretKeyValue = cfg.HCPResourceID - } - - cases := []struct { - name string - validateCloudInteractions bool - enableConsulNamespaces bool - mirroringK8S bool - adminPartitionsEnabled bool - secure bool - }{ - { - name: "default namespace and partition", - validateCloudInteractions: true, - }, - { - name: "default namespace and partition; secure", - secure: true, - }, - { - name: "namespace mirroring; secure", - enableConsulNamespaces: true, - mirroringK8S: true, - secure: true, - }, - { - name: "admin partitions; secure", - enableConsulNamespaces: true, - mirroringK8S: true, - adminPartitionsEnabled: true, - secure: true, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := suite.Environment().DefaultContext(t) - - if c.enableConsulNamespaces && !cfg.EnableEnterprise { - t.Skip("skipping this test because -enable-enterprise is not set") - } - - options := &terratestk8s.KubectlOptions{ - ContextName: ctx.KubectlOptions(t).ContextName, - ConfigPath: ctx.KubectlOptions(t).ConfigPath, - Namespace: ctx.KubectlOptions(t).Namespace, - } - ns := options.Namespace - - k8sClient := environment.KubernetesClientFromOptions(t, options) - - // Create cloud and telemetryCollector secrets. - consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, bootstrapTokenSecretName, bootstrapTokenSecretKey, bootstrapToken) - - k8s.DeployKustomize(t, options, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/cloud/hcp-mock") - podName, err := k8s.RunKubectlAndGetOutputE(t, options, "get", "pod", "-l", "app=fake-server", "-o", `jsonpath="{.items[0].metadata.name}"`) - podName = strings.ReplaceAll(podName, "\"", "") - if err != nil { - logger.Log(t, "error finding pod name") - return - } - logger.Log(t, "fake-server pod name:"+podName) - localPort := terratestk8s.GetAvailablePort(t) - tunnel := terratestk8s.NewTunnelWithLogger( - options, - terratestk8s.ResourceTypePod, - podName, - localPort, - 443, - logger.TestLogger{}) - defer tunnel.Close() - // Retry creating the port forward since it can fail occasionally. - retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { - // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry - // because we're using ForwardPortE (not ForwardPort) so the `t` won't - // get used to fail the test, just for logging. - require.NoError(r, tunnel.ForwardPortE(t)) - }) - - fsClient := newfakeServerClient(tunnel.Endpoint()) - logger.Log(t, "fake-server addr:"+tunnel.Endpoint()) - consulToken, err := fsClient.requestToken() - if err != nil { - logger.Log(t, "error finding consul token") - return - } - - logger.Log(t, "consul test token :"+consulToken) - - releaseName := helpers.RandomName() - - helmValues := map[string]string{ - "global.imagePullPolicy": "IfNotPresent", - - "global.acls.manageSystemACLs": fmt.Sprint(c.secure), - "global.tls.enabled": fmt.Sprint(c.secure), - "global.adminPartitions.enabled": fmt.Sprint(c.adminPartitionsEnabled), - - "global.enableConsulNamespaces": fmt.Sprint(c.enableConsulNamespaces), - "connectInject.enabled": "true", - "connectInject.consulNamespaces.mirroringK8S": fmt.Sprint(c.mirroringK8S), - - // TODO this doesn't appear to work because we just deploy to default using kubectl options from context. - // https://github.com/hashicorp/consul-k8s/blob/74097fe7b3023105ca755b45da9c72c716547f46/acceptance/framework/consul/helm_cluster.go#L107 - // "connectInject.consulNamespaces.consulDestinationNamespace": c.destinationNamespace, - - "global.cloud.enabled": "true", - "global.cloud.resourceId.secretName": resourceSecretName, - "global.cloud.resourceId.secretKey": resourceSecretKey, - - "global.cloud.clientId.secretName": clientIDSecretName, - "global.cloud.clientId.secretKey": clientIDSecretKey, - - "global.cloud.clientSecret.secretName": clientSecretName, - "global.cloud.clientSecret.secretKey": clientSecretKey, - - "global.cloud.apiHost.secretName": apiHostSecretName, - "global.cloud.apiHost.secretKey": apiHostSecretKey, - - "global.cloud.authUrl.secretName": authUrlSecretName, - "global.cloud.authUrl.secretKey": authUrlSecretKey, - - "global.cloud.scadaAddress.secretName": scadaAddressSecretName, - "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, - "connectInject.default": "true", - - "telemetryCollector.enabled": "true", - "telemetryCollector.image": cfg.ConsulCollectorImage, - "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, - "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, - - "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, - "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, - - "telemetryCollector.extraEnvironmentVars.HCP_API_TLS": "insecure", - "telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", - "telemetryCollector.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", - "telemetryCollector.extraEnvironmentVars.OTLP_EXPORTER_TLS": "insecure", - - "server.extraEnvironmentVars.HCP_API_TLS": "insecure", - "server.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", - "server.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", - } - if cfg.ConsulImage != "" { - helmValues["global.image"] = cfg.ConsulImage - } - if c.secure { - helmValues["global.acls.bootstrapToken.secretName"] = bootstrapTokenSecretName - helmValues["global.acls.bootstrapToken.secretKey"] = bootstrapTokenSecretKey - } - - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - consulCluster.ACLToken = bootstrapToken - consulCluster.Create(t) - - logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, options, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") - t.Log("Finished deployment. Validating expected conditions now") - - // Validate that the consul-telemetry-collector service was deployed to the expected namespace. - consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) - q := &api.QueryOptions{} - if cfg.EnableEnterprise { - q.Namespace = ns - } - instances, _, err := consulClient.Catalog().Service("consul-telemetry-collector", "", q) - require.NoError(t, err) - require.Len(t, instances, 1) - require.Equal(t, "passing", instances[0].Checks.AggregatedStatus()) - - for name, tc := range map[string]struct { - refresh *modifyTelemetryConfigBody - refreshTime int64 - recordsPath string - timeout time.Duration - wait time.Duration - validations *metricValidations - }{ - "collectorExportsMetrics": { - recordsPath: recordsPathCollector, - // High timeout as Collector metrics scraped every 1 minute (https://github.com/hashicorp/consul-telemetry-collector/blob/dfdbf51b91d502a18f3b143a94ab4d50cdff10b8/internal/otel/config/helpers/receivers/prometheus_receiver.go#L54) - timeout: 5 * time.Minute, - wait: 1 * time.Second, - validations: &metricValidations{ - expectedLabelKeys: []string{"service_name", "service_instance_id"}, - expectedMetricName: "otelcol_receiver_accepted_metric_points", - disallowedMetricName: "server.memory_heap_size", - }, - }, - "consulPeriodicRefreshUpdateConfig": { - refresh: &modifyTelemetryConfigBody{ - Filters: []string{"consul.state"}, - Labels: map[string]string{"new_label": "testLabel"}, - }, - recordsPath: recordsPathConsul, - // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) - timeout: 3 * time.Minute, - wait: 1 * time.Second, - validations: &metricValidations{ - expectedLabelKeys: []string{"node_id", "node_name", "new_label"}, - expectedMetricName: "consul.state.services", - disallowedMetricName: "consul.fsm", - }, - }, - "consulPeriodicRefreshDisabled": { - refresh: &modifyTelemetryConfigBody{ - Filters: []string{"consul.state"}, - Labels: map[string]string{"new_label": "testLabel"}, - Disabled: true, - }, - recordsPath: recordsPathConsul, - // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) - timeout: 3 * time.Minute, - wait: 1 * time.Second, - validations: &metricValidations{ - disabled: true, - }, - }, - } { - t.Run(name, func(t *testing.T) { - if !c.validateCloudInteractions { - t.Skip("skipping server metric and config validation") - } - - // For a refresh test, we force a telemetry config update before validating metrics using fakeserver's /telemetry_config_modify endpoint. - if tc.refresh != nil { - refreshTime := time.Now() - err := fsClient.modifyTelemetryConfig(tc.refresh) - require.NoError(t, err) - // Add 10 seconds (2 * periodic refresh interval in fakeserver) to allow a periodic refresh from Consul side to take place. - tc.refreshTime = refreshTime.Add(10 * time.Second).UnixNano() - } - - // Validate metrics are correct using fakeserver's /records endpoint to retrieve metric exports that occured from Consul/Collector to fakeserver. - // We use retry as we wait for Consul or the Collector to export metrics. This is the best we can do to avoid flakiness. - retry.RunWith(&retry.Timer{Timeout: tc.timeout, Wait: tc.wait}, t, func(r *retry.R) { - records, err := fsClient.getRecordsForPath(tc.recordsPath, tc.refreshTime) - require.NoError(r, err) - validateMetrics(r, records, tc.validations, tc.refreshTime) - }) - }) - } - }) - } -} diff --git a/acceptance/tests/cloud/remote_dev_test.go b/acceptance/tests/cloud/remote_dev_test.go deleted file mode 100644 index 457dc4f269..0000000000 --- a/acceptance/tests/cloud/remote_dev_test.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cloud - -import ( - "crypto/tls" - "encoding/json" - "os" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - - hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" - hcpcfg "github.com/hashicorp/hcp-sdk-go/config" - "github.com/hashicorp/hcp-sdk-go/httpclient" - "github.com/hashicorp/hcp-sdk-go/resource" -) - -type DevTokenResponse struct { - Token string `json:"token"` -} - -type hcp struct { - ResourceID string - ClientID string - ClientSecret string - AuthURL string - APIHostname string - ScadaAddress string -} - -func TestRemoteDevCloud(t *testing.T) { - _, rIDok := os.LookupEnv("HCP_RESOURCE_ID") - _, cIDok := os.LookupEnv("HCP_CLIENT_ID") - _, cSECok := os.LookupEnv("HCP_CLIENT_SECRET") - - if !rIDok || !cIDok || !cSECok { - t.Log("Must set HCP_RESOURCE_ID, HCP_CLIENT_ID and HCP_CLIENT_SECRET") - t.FailNow() - } - - apiHost := os.Getenv("HCP_AUTH_URL") - if apiHost == "" { - apiHost = "https://api.hcp.dev" - } - authURL := os.Getenv("HCP_API_HOST") - if authURL == "" { - authURL = "https://auth.idp.hcp.dev" - } - scadaAddr := os.Getenv("HCP_SCADA_ADDRESS") - if scadaAddr == "" { - scadaAddr = "scada.internal.hcp.dev:7224" - } - - ctx := suite.Environment().DefaultContext(t) - - kubectlOptions := ctx.KubectlOptions(t) - ns := kubectlOptions.Namespace - k8sClient := environment.KubernetesClientFromOptions(t, kubectlOptions) - - var ( - resourceSecretName = "resource-sec-name" - resourceSecretKey = "resource-sec-key" - resourceSecretKeyValue = os.Getenv("HCP_RESOURCE_ID") - - clientIDSecretName = "clientid-sec-name" - clientIDSecretKey = "clientid-sec-key" - clientIDSecretKeyValue = os.Getenv("HCP_CLIENT_ID") - - clientSecretName = "client-sec-name" - clientSecretKey = "client-sec-key" - clientSecretKeyValue = os.Getenv("HCP_CLIENT_SECRET") - - apiHostSecretName = "apihost-sec-name" - apiHostSecretKey = "apihost-sec-key" - apiHostSecretKeyValue = apiHost - - authUrlSecretName = "authurl-sec-name" - authUrlSecretKey = "authurl-sec-key" - authUrlSecretKeyValue = authURL - - scadaAddressSecretName = "scadaaddress-sec-name" - scadaAddressSecretKey = "scadaaddress-sec-key" - scadaAddressSecretKeyValue = scadaAddr - - bootstrapTokenSecretName = "bootstrap-token" - bootstrapTokenSecretKey = "token" - ) - - hcpCfg := hcp{ - ResourceID: resourceSecretKeyValue, - ClientID: clientIDSecretKeyValue, - ClientSecret: clientSecretKeyValue, - AuthURL: authUrlSecretKeyValue, - APIHostname: apiHostSecretKeyValue, - ScadaAddress: scadaAddressSecretKeyValue, - } - - aclToken := hcpCfg.fetchAgentBootstrapConfig(t) - - cfg := suite.Config() - consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, bootstrapTokenSecretName, bootstrapTokenSecretKey, aclToken) - - releaseName := helpers.RandomName() - - helmValues := map[string]string{ - "global.imagePullPolicy": "IfNotPresent", - "global.cloud.enabled": "true", - "global.cloud.resourceId.secretName": resourceSecretName, - "global.cloud.resourceId.secretKey": resourceSecretKey, - - "global.cloud.clientId.secretName": clientIDSecretName, - "global.cloud.clientId.secretKey": clientIDSecretKey, - - "global.cloud.clientSecret.secretName": clientSecretName, - "global.cloud.clientSecret.secretKey": clientSecretKey, - - "global.cloud.apiHost.secretName": apiHostSecretName, - "global.cloud.apiHost.secretKey": apiHostSecretKey, - - "global.cloud.authUrl.secretName": authUrlSecretName, - "global.cloud.authUrl.secretKey": authUrlSecretKey, - - "global.cloud.scadaAddress.secretName": scadaAddressSecretName, - "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, - "connectInject.default": "true", - - "global.acls.manageSystemACLs": "true", - "global.acls.bootstrapToken.secretName": bootstrapTokenSecretName, - "global.acls.bootstrapToken.secretKey": bootstrapTokenSecretKey, - - "global.gossipEncryption.autoGenerate": "false", - "global.tls.enabled": "true", - "global.tls.enableAutoEncrypt": "true", - - "telemetryCollector.enabled": "true", - "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, - "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, - - "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, - "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, - // Either we set the global.trustedCAs (make sure it's idented exactly) or we - // set TLS to insecure - - "telemetryCollector.extraEnvironmentVars.HCP_API_ADDRESS": apiHostSecretKeyValue, - } - - if cfg.ConsulImage != "" { - helmValues["global.image"] = cfg.ConsulImage - } - if cfg.ConsulCollectorImage != "" { - helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage - } - - consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), cfg, releaseName) - consulCluster.Create(t) - - logger.Log(t, "setting acl permissions for collector and services") - aclDir := "../fixtures/bases/cloud/service-intentions" - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), aclDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), aclDir) - }) - - logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") - time.Sleep(1 * time.Hour) - - // TODO: add in test assertions here - -} - -// fetchAgentBootstrapConfig use the resource-id, client-id, and client-secret -// to call to the agent bootstrap config endpoint and parse the response into a -// CloudBootstrapConfig struct. -func (c *hcp) fetchAgentBootstrapConfig(t *testing.T) string { - cfg, err := c.HCPConfig() - require.NoError(t, err) - logger.Log(t, "Fetching Consul cluster configuration from HCP") - httpClientCfg := httpclient.Config{ - HCPConfig: cfg, - } - clientRuntime, err := httpclient.New(httpClientCfg) - require.NoError(t, err) - - hcpgnmClient := hcpgnm.New(clientRuntime, nil) - clusterResource, err := resource.FromString(c.ResourceID) - require.NoError(t, err) - - params := hcpgnm.NewAgentBootstrapConfigParams(). - WithID(clusterResource.ID). - WithLocationOrganizationID(clusterResource.Organization). - WithLocationProjectID(clusterResource.Project) - - resp, err := hcpgnmClient.AgentBootstrapConfig(params, nil) - require.NoError(t, err) - - bootstrapConfig := resp.GetPayload() - logger.Log(t, "HCP configuration successfully fetched.") - - return c.parseBootstrapConfigResponse(t, bootstrapConfig) -} - -// ConsulConfig represents 'cluster.consul_config' in the response -// fetched from the agent bootstrap config endpoint in HCP. -type ConsulConfig struct { - ACL ACL `json:"acl"` -} - -// ACL represents 'cluster.consul_config.acl' in the response -// fetched from the agent bootstrap config endpoint in HCP. -type ACL struct { - Tokens Tokens `json:"tokens"` -} - -// Tokens represents 'cluster.consul_config.acl.tokens' in the -// response fetched from the agent bootstrap config endpoint in HCP. -type Tokens struct { - Agent string `json:"agent"` - InitialManagement string `json:"initial_management"` -} - -// parseBootstrapConfigResponse unmarshals the boostrap parseBootstrapConfigResponse -// and also sets the HCPConfig values to return CloudBootstrapConfig struct. -func (c *hcp) parseBootstrapConfigResponse(t *testing.T, bootstrapRepsonse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) string { - - var consulConfig ConsulConfig - err := json.Unmarshal([]byte(bootstrapRepsonse.Bootstrap.ConsulConfig), &consulConfig) - require.NoError(t, err) - - return consulConfig.ACL.Tokens.InitialManagement -} - -func (c *hcp) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { - if c.ClientID != "" && c.ClientSecret != "" { - opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret)) - } - if c.AuthURL != "" { - opts = append(opts, hcpcfg.WithAuth(c.AuthURL, &tls.Config{})) - } - if c.APIHostname != "" { - opts = append(opts, hcpcfg.WithAPI(c.APIHostname, &tls.Config{})) - } - if c.ScadaAddress != "" { - opts = append(opts, hcpcfg.WithSCADA(c.ScadaAddress, &tls.Config{})) - } - opts = append(opts, hcpcfg.FromEnv(), hcpcfg.WithoutBrowserLogin()) - return hcpcfg.NewHCPConfig(opts...) -} diff --git a/acceptance/tests/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index aa74bdc2b5..a2580069a8 100644 --- a/acceptance/tests/config-entries/config_entries_namespaces_test.go +++ b/acceptance/tests/config-entries/config_entries_namespaces_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package config_entries import ( @@ -102,7 +99,7 @@ func TestControllerNamespaces(t *testing.T) { if err != nil && !strings.Contains(out, "(AlreadyExists)") { require.NoError(t, err) } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", KubeNS) }) @@ -128,7 +125,7 @@ func TestControllerNamespaces(t *testing.T) { // Retry the kubectl apply because we've seen sporadic // "connection refused" errors where the mutating webhook // endpoint fails initially. - out, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "apply", "-n", KubeNS, "-k", "../fixtures/cases/crds-ent") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-n", KubeNS, "-k", "../fixtures/cases/crds-ent") require.NoError(r, err, out) // NOTE: No need to clean up because the namespace will be deleted. }) @@ -136,7 +133,7 @@ func TestControllerNamespaces(t *testing.T) { // On startup, the controller can take upwards of 1m to perform // leader election so we may need to wait a long time for // the reconcile loop to run (hence the 1m timeout here). - counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} + counter := &retry.Counter{Count: 60, Wait: 1 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults entry, _, err := consulClient.ConfigEntries().Get(api.ServiceDefaults, "defaults", queryOpts) @@ -159,6 +156,13 @@ func TestControllerNamespaces(t *testing.T) { require.True(r, ok, "could not cast to ProxyConfigEntry") require.Equal(r, api.MeshGatewayModeLocal, proxyDefaultEntry.MeshGateway.Mode) + // exported-services + entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) + require.NoError(r, err) + exportedServicesEntry, ok := entry.(*api.ExportedServicesConfigEntry) + require.True(r, ok, "could not cast to ExportedServicesConfigEntry") + require.Equal(r, "frontend", exportedServicesEntry.Services[0].Name) + // mesh entry, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", defaultOpts) require.NoError(r, err) @@ -209,68 +213,6 @@ func TestControllerNamespaces(t *testing.T) { require.Equal(r, "certFile", terminatingGatewayEntry.Services[0].CertFile) require.Equal(r, "keyFile", terminatingGatewayEntry.Services[0].KeyFile) require.Equal(r, "sni", terminatingGatewayEntry.Services[0].SNI) - - // jwt-provider - entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", defaultOpts) - require.NoError(r, err) - jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) - require.True(r, ok, "could not cast to JWTProviderConfigEntry") - require.Equal(r, "jwks.txt", jwtProviderConfigEntry.JSONWebKeySet.Local.Filename) - require.Equal(r, "test-issuer", jwtProviderConfigEntry.Issuer) - require.ElementsMatch(r, []string{"aud1", "aud2"}, jwtProviderConfigEntry.Audiences) - require.Equal(r, "x-jwt-header", jwtProviderConfigEntry.Locations[0].Header.Name) - require.Equal(r, "x-query-param", jwtProviderConfigEntry.Locations[1].QueryParam.Name) - require.Equal(r, "session-id", jwtProviderConfigEntry.Locations[2].Cookie.Name) - require.Equal(r, "x-forwarded-jwt", jwtProviderConfigEntry.Forwarding.HeaderName) - require.True(r, jwtProviderConfigEntry.Forwarding.PadForwardPayloadHeader) - require.Equal(r, 45, jwtProviderConfigEntry.ClockSkewSeconds) - require.Equal(r, 15, jwtProviderConfigEntry.CacheConfig.Size) - - // exported-services - entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) - require.NoError(r, err) - exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) - require.True(r, ok, "could not cast to ExportedServicesConfigEntry") - require.Equal(r, "frontend", exportedServicesConfigEntry.Services[0].Name) - require.Equal(r, "frontend", exportedServicesConfigEntry.Services[0].Namespace) - require.Equal(r, "partitionName", exportedServicesConfigEntry.Services[0].Consumers[0].Partition) - require.Equal(r, "peerName", exportedServicesConfigEntry.Services[0].Consumers[1].Peer) - require.Equal(r, "groupName", exportedServicesConfigEntry.Services[0].Consumers[2].SamenessGroup) - - // control-plane-request-limit - entry, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", defaultOpts) - require.NoError(r, err) - rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) - require.True(r, ok, "could not cast to RateLimitIPConfigEntry") - require.Equal(r, "permissive", rateLimitIPConfigEntry.Mode) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ACL.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ACL.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Catalog.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Catalog.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ConfigEntry.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ConfigEntry.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ConnectCA.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ConnectCA.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Coordinate.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Coordinate.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.DiscoveryChain.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.DiscoveryChain.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Health.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Health.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Intention.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Intention.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) - //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) - //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.WriteRate) }) } @@ -288,6 +230,10 @@ func TestControllerNamespaces(t *testing.T) { patchMeshGatewayMode := "remote" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "proxydefaults", "global", "-p", fmt.Sprintf(`{"spec":{"meshGateway":{"mode": "%s"}}}`, patchMeshGatewayMode), "--type=merge") + logger.Log(t, "patching partition-exports custom resource") + patchServiceName := "backend" + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "exportedservices", "default", "-p", fmt.Sprintf(`{"spec":{"services":[{"name": "%s", "namespace": "front", "consumers":[{"partition": "foo"}]}]}}`, patchServiceName), "--type=merge") + logger.Log(t, "patching mesh custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "mesh", "mesh", "-p", fmt.Sprintf(`{"spec":{"transparentProxy":{"meshDestinationsOnly": %t}}}`, false), "--type=merge") @@ -309,18 +255,7 @@ func TestControllerNamespaces(t *testing.T) { patchSNI := "patch-sni" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "terminatinggateway", "terminating-gateway", "-p", fmt.Sprintf(`{"spec": {"services": [{"name":"name","caFile":"caFile","certFile":"certFile","keyFile":"keyFile","sni":"%s"}]}}`, patchSNI), "--type=merge") - logger.Log(t, "patching jwt-provider custom resource") - patchIssuer := "other-issuer" - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "jwtprovider", "jwt-provider", "-p", fmt.Sprintf(`{"spec": {"issuer": "%s"}}`, patchIssuer), "--type=merge") - - logger.Log(t, "patching exported-services custom resource") - patchPartition := "destination" - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "exportedservices", "default", "-p", fmt.Sprintf(`{"spec": {"services": [{"name": "frontend", "namespace": "frontend", "consumers": [{"partition": "%s"}, {"peer": "peerName"}, {"samenessGroup": "groupName"}]}]}}`, patchPartition), "--type=merge") - - logger.Log(t, "patching control-plane-request-limit custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "controlplanerequestlimit", "controlplanerequestlimit", "-p", `{"spec": {"mode": "disabled"}}`, "--type=merge") - - counter := &retry.Counter{Count: 20, Wait: 2 * time.Second} + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults entry, _, err := consulClient.ConfigEntries().Get(api.ServiceDefaults, "defaults", queryOpts) @@ -343,6 +278,13 @@ func TestControllerNamespaces(t *testing.T) { require.True(r, ok, "could not cast to ProxyConfigEntry") require.Equal(r, api.MeshGatewayModeRemote, proxyDefaultsEntry.MeshGateway.Mode) + // partition-exports + entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) + require.NoError(r, err) + exportedServicesEntry, ok := entry.(*api.ExportedServicesConfigEntry) + require.True(r, ok, "could not cast to ExportedServicesConfigEntry") + require.Equal(r, "backend", exportedServicesEntry.Services[0].Name) + // mesh entry, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", defaultOpts) require.NoError(r, err) @@ -386,27 +328,6 @@ func TestControllerNamespaces(t *testing.T) { terminatingGatewayEntry, ok := entry.(*api.TerminatingGatewayConfigEntry) require.True(r, ok, "could not cast to TerminatingGatewayConfigEntry") require.Equal(r, patchSNI, terminatingGatewayEntry.Services[0].SNI) - - // jwt-Provider - entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", defaultOpts) - require.NoError(r, err) - jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) - require.True(r, ok, "could not cast to JWTProviderConfigEntry") - require.Equal(r, patchIssuer, jwtProviderConfigEntry.Issuer) - - // exported-services - entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) - require.NoError(r, err) - exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) - require.True(r, ok, "could not cast to ExportedServicesConfigEntry") - require.Equal(r, patchPartition, exportedServicesConfigEntry.Services[0].Consumers[0].Partition) - - // control-plane-request-limit - entry, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", defaultOpts) - require.NoError(r, err) - rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) - require.True(r, ok, "could not cast to RateLimitIPConfigEntry") - require.Equal(r, rateLimitIPConfigEntry.Mode, "disabled") }) } @@ -421,6 +342,9 @@ func TestControllerNamespaces(t *testing.T) { logger.Log(t, "deleting proxy-defaults custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "proxydefaults", "global") + logger.Log(t, "deleting partition-exports custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "exportedservices", "default") + logger.Log(t, "deleting mesh custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "mesh", "mesh") @@ -439,16 +363,7 @@ func TestControllerNamespaces(t *testing.T) { logger.Log(t, "deleting terminating-gateway custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "terminatinggateway", "terminating-gateway") - logger.Log(t, "deleting jwt-provider custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "jwtprovider", "jwt-provider") - - logger.Log(t, "deleting exported-services custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "exportedservices", "default") - - logger.Log(t, "deleting control-plane-request-limit custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "controlplanerequestlimit", "controlplanerequestlimit") - - counter := &retry.Counter{Count: 20, Wait: 2 * time.Second} + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults _, _, err := consulClient.ConfigEntries().Get(api.ServiceDefaults, "defaults", queryOpts) @@ -465,6 +380,11 @@ func TestControllerNamespaces(t *testing.T) { require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") + // partition-exports + _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") + // mesh _, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", defaultOpts) require.Error(r, err) @@ -494,21 +414,6 @@ func TestControllerNamespaces(t *testing.T) { _, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "terminating-gateway", queryOpts) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") - - // jwt-provider - _, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", defaultOpts) - require.Error(r, err) - require.Contains(r, err.Error(), "404 (Config entry not found") - - // exported-services - _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", defaultOpts) - require.Error(r, err) - require.Contains(r, err.Error(), "404 (Config entry not found") - - // control-plane-request-limit - _, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", defaultOpts) - require.Error(r, err) - require.Contains(r, err.Error(), "404 (Config entry not found") }) } }) diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index 9f2595ed4f..035edf6a8f 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package config_entries import ( @@ -9,11 +6,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/go-uuid" - "github.com/stretchr/testify/require" - "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" @@ -21,6 +13,10 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/vault" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/go-uuid" + "github.com/stretchr/testify/require" ) const ( @@ -85,19 +81,19 @@ func TestController(t *testing.T) { // Retry the kubectl apply because we've seen sporadic // "connection refused" errors where the mutating webhook // endpoint fails initially. - out, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "apply", "-k", "../fixtures/bases/crds-oss") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/crds-oss") require.NoError(r, err, out) - }) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/crds-oss") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { + // Ignore errors here because if the test ran as expected + // the custom resources will have been deleted. + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/crds-oss") + }) }) // On startup, the controller can take upwards of 1m to perform // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 2m timeout here). - counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} + // the reconcile loop to run (hence the 1m timeout here). + counter := &retry.Counter{Count: 60, Wait: 1 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults entry, _, err := consulClient.ConfigEntries().Get(api.ServiceDefaults, "defaults", nil) @@ -105,7 +101,6 @@ func TestController(t *testing.T) { svcDefaultEntry, ok := entry.(*api.ServiceConfigEntry) require.True(r, ok, "could not cast to ServiceConfigEntry") require.Equal(r, "http", svcDefaultEntry.Protocol) - require.Equal(r, 1234, svcDefaultEntry.RateLimits.InstanceLevel.RequestsPerSecond) // service-resolver entry, _, err = consulClient.ConfigEntries().Get(api.ServiceResolver, "resolver", nil) @@ -180,65 +175,6 @@ func TestController(t *testing.T) { require.Equal(r, "certFile", terminatingGatewayEntry.Services[0].CertFile) require.Equal(r, "keyFile", terminatingGatewayEntry.Services[0].KeyFile) require.Equal(r, "sni", terminatingGatewayEntry.Services[0].SNI) - - // jwt-provider - entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) - require.NoError(r, err) - jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) - require.True(r, ok, "could not cast to JWTProviderConfigEntry") - require.Equal(r, "jwks.txt", jwtProviderConfigEntry.JSONWebKeySet.Local.Filename) - require.Equal(r, "test-issuer", jwtProviderConfigEntry.Issuer) - require.ElementsMatch(r, []string{"aud1", "aud2"}, jwtProviderConfigEntry.Audiences) - require.Equal(r, "x-jwt-header", jwtProviderConfigEntry.Locations[0].Header.Name) - require.Equal(r, "x-query-param", jwtProviderConfigEntry.Locations[1].QueryParam.Name) - require.Equal(r, "session-id", jwtProviderConfigEntry.Locations[2].Cookie.Name) - require.Equal(r, "x-forwarded-jwt", jwtProviderConfigEntry.Forwarding.HeaderName) - require.True(r, jwtProviderConfigEntry.Forwarding.PadForwardPayloadHeader) - require.Equal(r, 45, jwtProviderConfigEntry.ClockSkewSeconds) - require.Equal(r, 15, jwtProviderConfigEntry.CacheConfig.Size) - - // exported-services - entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) - require.NoError(r, err) - exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) - require.True(r, ok, "could not cast to ExportedServicesConfigEntry") - require.Equal(r, "frontend", exportedServicesConfigEntry.Services[0].Name) - require.Equal(r, "peerName", exportedServicesConfigEntry.Services[0].Consumers[0].Peer) - require.Equal(r, "groupName", exportedServicesConfigEntry.Services[0].Consumers[1].SamenessGroup) - - // control-plane-request-limit - entry, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", nil) - require.NoError(r, err) - rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) - require.True(r, ok, "could not cast to RateLimitIPConfigEntry") - require.Equal(r, "permissive", rateLimitIPConfigEntry.Mode) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ACL.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ACL.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Catalog.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Catalog.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ConfigEntry.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ConfigEntry.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ConnectCA.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.ConnectCA.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Coordinate.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Coordinate.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.DiscoveryChain.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.DiscoveryChain.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Health.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Health.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Intention.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Intention.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.WriteRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) - require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.WriteRate) - }) } @@ -277,17 +213,6 @@ func TestController(t *testing.T) { patchSNI := "patch-sni" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "terminatinggateway", "terminating-gateway", "-p", fmt.Sprintf(`{"spec": {"services": [{"name":"name","caFile":"caFile","certFile":"certFile","keyFile":"keyFile","sni":"%s"}]}}`, patchSNI), "--type=merge") - logger.Log(t, "patching JWTProvider custom resource") - patchIssuer := "other-issuer" - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "jwtprovider", "jwt-provider", "-p", fmt.Sprintf(`{"spec": {"issuer": "%s"}}`, patchIssuer), "--type=merge") - - logger.Log(t, "patching ExportedServices custom resource") - patchPeer := "destination" - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "exportedservices", "default", "-p", fmt.Sprintf(`{"spec": {"services": [{"name": "frontend", "consumers": [{"peer": "%s"}, {"samenessGroup": "groupName"}]}]}}`, patchPeer), "--type=merge") - - logger.Log(t, "patching control-plane-request-limit custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "controlplanerequestlimit", "controlplanerequestlimit", "-p", `{"spec": {"mode": "disabled"}}`, "--type=merge") - counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -355,27 +280,6 @@ func TestController(t *testing.T) { terminatingGatewayEntry, ok := entry.(*api.TerminatingGatewayConfigEntry) require.True(r, ok, "could not cast to TerminatingGatewayConfigEntry") require.Equal(r, patchSNI, terminatingGatewayEntry.Services[0].SNI) - - // jwt-provider - entry, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) - require.NoError(r, err) - jwtProviderConfigEntry, ok := entry.(*api.JWTProviderConfigEntry) - require.True(r, ok, "could not cast to JWTProviderConfigEntry") - require.Equal(r, patchIssuer, jwtProviderConfigEntry.Issuer) - - // exported-services - entry, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) - require.NoError(r, err) - exportedServicesConfigEntry, ok := entry.(*api.ExportedServicesConfigEntry) - require.True(r, ok, "could not cast to ExportedServicesConfigEntry") - require.Equal(r, patchPeer, exportedServicesConfigEntry.Services[0].Consumers[0].Peer) - - // control-plane-request-limit - entry, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", nil) - require.NoError(r, err) - rateLimitIPConfigEntry, ok := entry.(*api.RateLimitIPConfigEntry) - require.True(r, ok, "could not cast to RateLimitIPConfigEntry") - require.Equal(r, rateLimitIPConfigEntry.Mode, "disabled") }) } @@ -408,15 +312,6 @@ func TestController(t *testing.T) { logger.Log(t, "deleting terminating-gateway custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "terminatinggateway", "terminating-gateway") - logger.Log(t, "deleting jwt-provider custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "jwtprovider", "jwt-provider") - - logger.Log(t, "deleting exported-services custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "exportedservices", "default") - - logger.Log(t, "deleting control-plane-request-limit custom resource") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "controlplanerequestlimit", "controlplanerequestlimit") - counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -460,22 +355,7 @@ func TestController(t *testing.T) { require.Contains(r, err.Error(), "404 (Config entry not found") // terminating-gateway - _, _, err = consulClient.ConfigEntries().Get(api.TerminatingGateway, "terminating-gateway", nil) - require.Error(r, err) - require.Contains(r, err.Error(), "404 (Config entry not found") - - // jwt-provider - _, _, err = consulClient.ConfigEntries().Get(api.JWTProvider, "jwt-provider", nil) - require.Error(r, err) - require.Contains(r, err.Error(), "404 (Config entry not found") - - // exported-services - _, _, err = consulClient.ConfigEntries().Get(api.ExportedServices, "default", nil) - require.Error(r, err) - require.Contains(r, err.Error(), "404 (Config entry not found") - - // control-plane-request-limit - _, _, err = consulClient.ConfigEntries().Get(api.RateLimitIPConfig, "controlplanerequestlimit", nil) + _, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "terminating-gateway", nil) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") }) diff --git a/acceptance/tests/config-entries/main_test.go b/acceptance/tests/config-entries/main_test.go index 805045dcef..64034f7663 100644 --- a/acceptance/tests/config-entries/main_test.go +++ b/acceptance/tests/config-entries/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package config_entries import ( diff --git a/acceptance/tests/connect/connect_external_servers_test.go b/acceptance/tests/connect/connect_external_servers_test.go index c0a61f160f..56d7b16bc0 100644 --- a/acceptance/tests/connect/connect_external_servers_test.go +++ b/acceptance/tests/connect/connect_external_servers_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connect import ( @@ -75,11 +72,11 @@ func TestConnectInject_ExternalServers(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } // Check that both static-server and static-client have been injected and now have 2 containers. @@ -128,7 +125,7 @@ func TestConnectInject_ExternalServers(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+connhelper.StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+connhelper.StaticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/connect/connect_inject_namespaces_test.go b/acceptance/tests/connect/connect_inject_namespaces_test.go index 03200cc75b..dbfc4725e4 100644 --- a/acceptance/tests/connect/connect_inject_namespaces_test.go +++ b/acceptance/tests/connect/connect_inject_namespaces_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connect import ( @@ -101,12 +98,12 @@ func TestConnectInjectNamespaces(t *testing.T) { logger.Logf(t, "creating namespaces %s and %s", staticServerNamespace, StaticClientNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { // Note: this deletion will take longer in cases when the static-client deployment // hasn't yet fully terminated. k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", StaticClientNamespace) @@ -147,11 +144,11 @@ func TestConnectInjectNamespaces(t *testing.T) { } logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") } // Check that both static-server and static-client have been injected and now have 2 containers. @@ -221,7 +218,7 @@ func TestConnectInjectNamespaces(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, staticServerOpts, "exec", "deploy/"+connhelper.StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, staticServerOpts, "exec", "deploy/"+connhelper.StaticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. @@ -305,7 +302,7 @@ func TestConnectInjectNamespaces_CleanupController(t *testing.T) { logger.Logf(t, "creating namespace %s", StaticClientNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", StaticClientNamespace) }) @@ -315,7 +312,7 @@ func TestConnectInjectNamespaces_CleanupController(t *testing.T) { ConfigPath: ctx.KubectlOptions(t).ConfigPath, Namespace: StaticClientNamespace, } - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") logger.Log(t, "waiting for static-client to be registered with Consul") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index 0badf69cc0..eeafd345b1 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connect import ( @@ -51,45 +48,16 @@ func TestConnectInject(t *testing.T) { connHelper.Install(t) connHelper.DeployClientAndServer(t) if c.secure { - connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) - connHelper.CreateIntention(t, connhelper.IntentionOpts{}) + connHelper.TestConnectionFailureWithoutIntention(t) + connHelper.CreateIntention(t) } - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionSuccess(t) connHelper.TestConnectionFailureWhenUnhealthy(t) }) } } -// TestConnectInject_VirtualIPFailover ensures that KubeDNS entries are saved to the virtual IP address table in Consul. -func TestConnectInject_VirtualIPFailover(t *testing.T) { - cfg := suite.Config() - if !cfg.EnableTransparentProxy { - // This can only be tested in transparent proxy mode. - t.SkipNow() - } - ctx := suite.Environment().DefaultContext(t) - - releaseName := helpers.RandomName() - connHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: true, - ReleaseName: releaseName, - Ctx: ctx, - UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, - Cfg: cfg, - } - - connHelper.Setup(t) - - connHelper.Install(t) - connHelper.CreateResolverRedirect(t) - connHelper.DeployClientAndServer(t) - - opts := connHelper.KubectlOptsForApp(t) - k8s.CheckStaticServerConnectionSuccessful(t, opts, "static-client", "http://resolver-redirect") -} - // Test the endpoints controller cleans up force-killed pods. func TestConnectInject_CleanupKilledPods(t *testing.T) { for _, secure := range []bool{false, true} { @@ -113,7 +81,7 @@ func TestConnectInject_CleanupKilledPods(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating static-client deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") logger.Log(t, "waiting for static-client to be registered with Consul") consulClient, _ := consulCluster.SetupConsulClient(t, secure) @@ -238,8 +206,8 @@ func TestConnectInject_MultiportServices(t *testing.T) { } logger.Log(t, "creating multiport static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/multiport-app") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject-multiport") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/multiport-app") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject-multiport") // Check that static-client has been injected and now has 2 containers. podList, err := ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ @@ -298,7 +266,7 @@ func TestConnectInject_MultiportServices(t *testing.T) { // pod to static-server. // Deploy static-server. - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // For outbound connections from the multi port pod, only intentions from the first service in the multiport // pod need to be created, since all upstream connections are made through the first service's envoy proxy. @@ -328,9 +296,9 @@ func TestConnectInject_MultiportServices(t *testing.T) { // and check inbound connections to the multi port pods' services. // Create the files so that the readiness probes of the multi port pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the multiport unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport", "--", "touch", "/tmp/unhealthy-multiport") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "--", "touch", "/tmp/unhealthy-multiport") logger.Log(t, "testing k8s -> consul health checks sync by making the multiport-admin unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport-admin", "--", "touch", "/tmp/unhealthy-multiport-admin") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "--", "touch", "/tmp/unhealthy-multiport-admin") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go index 7a4b3b383f..ae70a0fdeb 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -11,10 +11,10 @@ import ( "testing" "time" + "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" @@ -33,11 +33,10 @@ const ( // Test the endpoints controller cleans up force-killed pods. func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { + t.Skipf("skiping this test, will be re-added in a future commit") cfg := suite.Config() cfg.SkipWhenOpenshiftAndCNI(t) - t.Skipf("TODO(flaky-1.17): NET-XXXX") - for _, testCfg := range []LifecycleShutdownConfig{ {secure: false, helmValues: map[string]string{ helmDrainListenersKey: "true", @@ -111,7 +110,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { "static-server", "static-server-sidecar-proxy", } { - logger.Logf(r, "checking for %s service in Consul catalog", name) + logger.Logf(t, "checking for %s service in Consul catalog", name) instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) r.Check(err) @@ -122,11 +121,11 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { }) if testCfg.secure { - connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) - connHelper.CreateIntention(t, connhelper.IntentionOpts{}) + connHelper.TestConnectionFailureWithoutIntention(t) + connHelper.CreateIntention(t) } - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionSuccess(t) // Get static-client pod name ns := ctx.KubectlOptions(t).Namespace @@ -158,7 +157,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { // Ensure outbound requests are still successful during grace // period. retry.RunWith(&retry.Timer{Timeout: time.Duration(gracePeriodSeconds) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { - output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), args...) + output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) require.NoError(r, err) require.Condition(r, func() bool { exists := false @@ -175,7 +174,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { } else { // Ensure outbound requests fail because proxy has terminated retry.RunWith(&retry.Timer{Timeout: time.Duration(terminationGracePeriod) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { - output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), args...) + output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) require.Error(r, err) require.Condition(r, func() bool { exists := false @@ -193,7 +192,7 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { "static-client", "static-client-sidecar-proxy", } { - logger.Logf(r, "checking for %s service in Consul catalog", name) + logger.Logf(t, "checking for %s service in Consul catalog", name) instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) r.Check(err) @@ -207,171 +206,3 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { }) } } - -func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { - cfg := suite.Config() - - if cfg.EnableTransparentProxy { - t.Skip("Skipping test because transparent proxy is enabled") - } - - defaultGracePeriod := 5 - - cases := map[string]int{ - "../fixtures/cases/jobs/job-client-inject": defaultGracePeriod, - "../fixtures/cases/jobs/job-client-inject-grace-period-0s": 0, - "../fixtures/cases/jobs/job-client-inject-grace-period-10s": 10, - } - - // Set up the installation and static-server once. - ctx := suite.Environment().DefaultContext(t) - releaseName := helpers.RandomName() - - connHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - ReleaseName: releaseName, - Ctx: ctx, - Cfg: cfg, - HelmValues: map[string]string{ - "connectInject.sidecarProxy.lifecycle.defaultShutdownGracePeriodSeconds": strconv.FormatInt(int64(defaultGracePeriod), 10), - "connectInject.sidecarProxy.lifecycle.defaultEnabled": strconv.FormatBool(true), - }, - } - - connHelper.Setup(t) - connHelper.Install(t) - connHelper.DeployServer(t) - - logger.Log(t, "waiting for static-server to be registered with Consul") - retry.RunWith(&retry.Timer{Timeout: 3 * time.Minute, Wait: 5 * time.Second}, t, func(r *retry.R) { - for _, name := range []string{ - "static-server", - "static-server-sidecar-proxy", - } { - logger.Logf(r, "checking for %s service in Consul catalog", name) - instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) - r.Check(err) - - if len(instances) != 1 { - r.Errorf("expected 1 instance of %s", name) - - } - } - }) - - // Iterate over the Job cases and test connection. - for path, gracePeriod := range cases { - connHelper.DeployJob(t, path) // Default case. - - logger.Log(t, "waiting for job-client to be registered with Consul") - retry.RunWith(&retry.Timer{Timeout: 300 * time.Second, Wait: 5 * time.Second}, t, func(r *retry.R) { - for _, name := range []string{ - "job-client", - "job-client-sidecar-proxy", - } { - logger.Logf(r, "checking for %s service in Consul catalog", name) - instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) - r.Check(err) - - if len(instances) != 1 { - r.Errorf("expected 1 instance of %s", name) - } - } - }) - - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{ClientType: connhelper.JobName}) - - // Get job-client pod name - ns := ctx.KubectlOptions(t).Namespace - pods, err := ctx.KubernetesClient(t).CoreV1().Pods(ns).List( - context.Background(), - metav1.ListOptions{ - LabelSelector: "app=job-client", - }, - ) - require.NoError(t, err) - require.Len(t, pods.Items, 1) - jobName := pods.Items[0].Name - - // Exec into job and send shutdown request to running proxy. - // curl --max-time 2 -s -f -XPOST http://127.0.0.1:20600/graceful_shutdown - sendProxyShutdownArgs := []string{"exec", jobName, "-c", connhelper.JobName, "--", "curl", "--max-time", "2", "-s", "-f", "-XPOST", "http://127.0.0.1:20600/graceful_shutdown"} - _, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), sendProxyShutdownArgs...) - require.NoError(t, err) - - logger.Log(t, "Proxy killed...") - - args := []string{"exec", jobName, "-c", connhelper.JobName, "--", "curl", "-vvvsSf"} - if cfg.EnableTransparentProxy { - args = append(args, "http://static-server") - } else { - args = append(args, "http://localhost:1234") - } - - if gracePeriod > 0 { - logger.Log(t, "Checking if connection successful within grace period...") - retry.RunWith(&retry.Timer{Timeout: time.Duration(gracePeriod) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { - output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), args...) - require.NoError(r, err) - require.True(r, !strings.Contains(output, "curl: (7) Failed to connect")) - }) - //wait for the grace period to end after successful request - time.Sleep(time.Duration(gracePeriod) * time.Second) - } - - // Test that requests fail once grace period has ended, or there was no grace period set. - logger.Log(t, "Checking that requests fail now that proxy is killed...") - retry.RunWith(&retry.Timer{Timeout: 2 * time.Minute, Wait: 2 * time.Second}, t, func(r *retry.R) { - output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), args...) - require.Error(r, err) - require.True(r, strings.Contains(output, "curl: (7) Failed to connect")) - }) - - // Wait for the job to complete. - retry.RunWith(&retry.Timer{Timeout: 4 * time.Minute, Wait: 30 * time.Second}, t, func(r *retry.R) { - logger.Log(r, "Checking if job completed...") - jobs, err := ctx.KubernetesClient(r).BatchV1().Jobs(ns).List( - context.Background(), - metav1.ListOptions{ - LabelSelector: "app=job-client", - }, - ) - require.NoError(r, err) - require.True(r, jobs.Items[0].Status.Succeeded == 1) - }) - - // Delete the job and its associated Pod. - pods, err = ctx.KubernetesClient(t).CoreV1().Pods(ns).List( - context.Background(), - metav1.ListOptions{ - LabelSelector: "app=job-client", - }, - ) - require.NoError(t, err) - podName := pods.Items[0].Name - - err = ctx.KubernetesClient(t).BatchV1().Jobs(ns).Delete(context.Background(), "job-client", metav1.DeleteOptions{}) - require.NoError(t, err) - - err = ctx.KubernetesClient(t).CoreV1().Pods(ns).Delete(context.Background(), podName, metav1.DeleteOptions{}) - require.NoError(t, err) - - logger.Log(t, "ensuring job is deregistered after termination") - retry.RunWith(&retry.Timer{Timeout: 4 * time.Minute, Wait: 30 * time.Second}, t, func(r *retry.R) { - for _, name := range []string{ - "job-client", - "job-client-sidecar-proxy", - } { - logger.Logf(r, "checking for %s service in Consul catalog", name) - instances, _, err := connHelper.ConsulClient.Catalog().Service(name, "", nil) - r.Check(err) - - for _, instance := range instances { - if strings.Contains(instance.ServiceID, jobName) { - r.Errorf("%s is still registered", instance.ServiceID) - } - } - } - }) - } -} diff --git a/acceptance/tests/connect/local_rate_limit_test.go b/acceptance/tests/connect/local_rate_limit_test.go deleted file mode 100644 index eb96be4332..0000000000 --- a/acceptance/tests/connect/local_rate_limit_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package connect - -import ( - "fmt" - "testing" - "time" - - terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" -) - -// TestConnectInject_LocalRateLimiting tests that local rate limiting works as expected between services. -func TestConnectInject_LocalRateLimiting(t *testing.T) { - cfg := suite.Config() - - if !cfg.EnableEnterprise { - t.Skipf("rate limiting is an enterprise only feature. -enable-enterprise must be set to run this test.") - } else if !cfg.UseKind { - t.Skipf("rate limiting tests are time sensitive and can be flaky on cloud providers. Only test on Kind.") - } - - ctx := suite.Environment().DefaultContext(t) - - releaseName := helpers.RandomName() - connHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: false, - ReleaseName: releaseName, - Ctx: ctx, - UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, - Cfg: cfg, - } - - connHelper.Setup(t) - connHelper.Install(t) - connHelper.DeployClientAndServer(t) - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) - - // By default, target the static-server on localhost:1234 - staticServer := "localhost:1234" - if cfg.EnableTransparentProxy { - // When TProxy is enabled, use the service name. - staticServer = connhelper.StaticServerName - } - - // Map the static-server URL and path to the rate limits defined in the service defaults at: - // ../fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml - rateLimitMap := map[string]int{ - "http://" + staticServer: 2, - "http://" + staticServer + "/exact": 3, - "http://" + staticServer + "/prefix-test": 4, - "http://" + staticServer + "/regex": 5, - } - - opts := newRateLimitOptions(t, ctx) - - t.Run("without ratelimiting", func(t *testing.T) { - // Ensure that all requests from static-client to static-server succeed (no rate limiting set). - for addr, rps := range rateLimitMap { - opts.rps = rps - assertRateLimits(t, opts, addr) - } - }) - - // Apply local rate limiting to the static-server - writeCrd(t, connHelper, "../fixtures/cases/local-rate-limiting") - - t.Run("with ratelimiting", func(t *testing.T) { - // Ensure that going over the limit causes the static-server to apply rate limiting and - // reply with 429 Too Many Requests - opts.enforced = true - for addr, reqPerSec := range rateLimitMap { - opts.rps = reqPerSec - assertRateLimits(t, opts, addr) - } - }) -} - -func assertRateLimits(t *testing.T, opts *assertRateLimitOptions, addr string) { - t.Helper() - args := []string{"exec", opts.resourceType + opts.sourceApp, "-c", opts.sourceApp, "--", "curl", opts.curlOpts} - // curl can glob URLs to make requests to a range of addresses. - // We append a number as a query param since it will be ignored by - // the rate limit path matcher. - repeatAddr := fmt.Sprintf("%s?[1-%d]", addr, opts.rps) - - // This check is time sensitive due to the nature of rate limiting. - // Run the entire assertion in a retry block and on each pass: - // 1. Send the exact number of requests that are allowed per the rate limiting configuration - // and check that all the requests succeed. - // 2. Send an extra request that should exceed the configured rate limit and check that this request fails. - // 3. Make sure that all requests happened within the rate limit enforcement window of one second. - retry.RunWith(opts.retryTimer, t, func(r *retry.R) { - // Make up to the allowed numbers of calls in a second - t0 := time.Now() - - output, err := k8s.RunKubectlAndGetOutputE(r, opts.k8sOpts, append(args, repeatAddr)...) - require.NoError(r, err) - require.Contains(r, output, opts.successOutput) - - // Exceed the configured rate limit. - output, err = k8s.RunKubectlAndGetOutputE(r, opts.k8sOpts, append(args, addr)...) - require.True(r, time.Since(t0) < time.Second, "failed to make all requests within one second window") - if opts.enforced { - require.Error(r, err) - require.Contains(r, output, opts.rateLimitOutput, "request was not rate limited") - } else { - require.NoError(r, err) - require.NotContains(r, output, opts.rateLimitOutput, "request was not successful") - } - }) -} - -type assertRateLimitOptions struct { - resourceType string - successOutput string - rateLimitOutput string - k8sOpts *terratestK8s.KubectlOptions - sourceApp string - rps int - enforced bool - retryTimer *retry.Timer - curlOpts string -} - -func newRateLimitOptions(t *testing.T, ctx environment.TestContext) *assertRateLimitOptions { - return &assertRateLimitOptions{ - resourceType: "deploy/", - successOutput: "hello world", - rateLimitOutput: "curl: (22) The requested URL returned error: 429", - k8sOpts: ctx.KubectlOptions(t), - sourceApp: connhelper.StaticClientName, - retryTimer: &retry.Timer{Timeout: 120 * time.Second, Wait: 2 * time.Second}, - curlOpts: "-f", - } -} diff --git a/acceptance/tests/connect/main_test.go b/acceptance/tests/connect/main_test.go index e44c75e519..a2b5925bed 100644 --- a/acceptance/tests/connect/main_test.go +++ b/acceptance/tests/connect/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connect import ( diff --git a/acceptance/tests/connect/permissive_mtls_test.go b/acceptance/tests/connect/permissive_mtls_test.go deleted file mode 100644 index 929d56acfd..0000000000 --- a/acceptance/tests/connect/permissive_mtls_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package connect - -import ( - "context" - "testing" - - "github.com/hashicorp/consul-k8s/acceptance/framework/config" - "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestConnectInject_PermissiveMTLS(t *testing.T) { - cfg := suite.Config() - if !cfg.EnableTransparentProxy { - t.Skipf("skipping this because -enable-transparent-proxy is not set") - } - cfg.SkipWhenOpenshiftAndCNI(t) - - ctx := suite.Environment().DefaultContext(t) - - releaseName := helpers.RandomName() - connHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: true, - ReleaseName: releaseName, - Ctx: ctx, - Cfg: cfg, - } - connHelper.Setup(t) - connHelper.Install(t) - - deployNonMeshClient(t, connHelper) - deployStaticServer(t, cfg, connHelper) - - kubectlOpts := connHelper.Ctx.KubectlOptions(t) - logger.Logf(t, "Check that incoming non-mTLS connection fails in MutualTLSMode = strict") - k8s.CheckStaticServerConnectionFailing(t, kubectlOpts, "static-client", "http://static-server") - - logger.Log(t, "Set allowEnablingPermissiveMutualTLS = true") - writeCrd(t, connHelper, "../fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml") - - logger.Log(t, "Set mutualTLSMode = permissive for static-server") - writeCrd(t, connHelper, "../fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml") - - logger.Log(t, "Check that incoming mTLS connection is successful in MutualTLSMode = permissive") - k8s.CheckStaticServerConnectionSuccessful(t, kubectlOpts, "static-client", "http://static-server") -} - -func deployNonMeshClient(t *testing.T, ch connhelper.ConnectHelper) { - t.Helper() - - logger.Log(t, "Creating static-client deployment with connect-inject=false") - k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), ch.Cfg.NoCleanupOnFailure, ch.Cfg.NoCleanup, ch.Cfg.DebugDirectory, "../fixtures/bases/static-client") - requirePodContainers(t, ch, "app=static-client", 1) -} - -func deployStaticServer(t *testing.T, cfg *config.TestConfig, ch connhelper.ConnectHelper) { - t.Helper() - - logger.Log(t, "Creating static-server deployment with connect-inject=true") - k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - requirePodContainers(t, ch, "app=static-server", 2) -} - -func writeCrd(t *testing.T, ch connhelper.ConnectHelper, path string) { - t.Helper() - - t.Cleanup(func() { - _, _ = k8s.RunKubectlAndGetOutputE(t, ch.Ctx.KubectlOptions(t), "delete", "-f", path) - }) - - _, err := k8s.RunKubectlAndGetOutputE(t, ch.Ctx.KubectlOptions(t), "apply", "-f", path) - require.NoError(t, err) -} - -func requirePodContainers(t *testing.T, ch connhelper.ConnectHelper, selector string, nContainers int) { - t.Helper() - - opts := ch.Ctx.KubectlOptions(t) - client := ch.Ctx.KubernetesClient(t) - retry.Run(t, func(r *retry.R) { - podList, err := client.CoreV1(). - Pods(opts.Namespace). - List(context.Background(), metav1.ListOptions{LabelSelector: selector}) - require.NoError(r, err) - require.Len(r, podList.Items, 1) - require.Len(r, podList.Items[0].Spec.Containers, nContainers) - }) -} diff --git a/acceptance/tests/consul-dns/consul_dns_test.go b/acceptance/tests/consul-dns/consul_dns_test.go index f67ac96bd3..47cfb4af07 100644 --- a/acceptance/tests/consul-dns/consul_dns_test.go +++ b/acceptance/tests/consul-dns/consul_dns_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consuldns import ( @@ -62,10 +59,10 @@ func TestConsulDNS(t *testing.T) { dnsPodName := fmt.Sprintf("%s-dns-pod", releaseName) dnsTestPodArgs := []string{ - "run", "-it", dnsPodName, "--restart", "Never", "--image", "anubhavmishra/tiny-tools", "--", "dig", fmt.Sprintf("@%s-consul-dns", releaseName), "consul.service.consul", + "run", "-i", dnsPodName, "--restart", "Never", "--image", "anubhavmishra/tiny-tools", "--", "dig", fmt.Sprintf("@%s-consul-dns", releaseName), "consul.service.consul", } - helpers.Cleanup(t, suite.Config().NoCleanupOnFailure, suite.Config().NoCleanup, func() { + helpers.Cleanup(t, suite.Config().NoCleanupOnFailure, func() { // Note: this delete command won't wait for pods to be fully terminated. // This shouldn't cause any test pollution because the underlying // objects are deployments, and so when other tests create these @@ -74,7 +71,7 @@ func TestConsulDNS(t *testing.T) { }) retry.Run(t, func(r *retry.R) { - logs, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), dnsTestPodArgs...) + logs, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), dnsTestPodArgs...) require.NoError(r, err) // When the `dig` request is successful, a section of it's response looks like the following: diff --git a/acceptance/tests/consul-dns/main_test.go b/acceptance/tests/consul-dns/main_test.go index 1a17337d0a..848f30ad8f 100644 --- a/acceptance/tests/consul-dns/main_test.go +++ b/acceptance/tests/consul-dns/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consuldns import ( diff --git a/acceptance/tests/example/example_test.go b/acceptance/tests/example/example_test.go index 07b04d6097..9c6457f906 100644 --- a/acceptance/tests/example/example_test.go +++ b/acceptance/tests/example/example_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Rename package to your test package. package example @@ -44,7 +41,7 @@ func TestExample(t *testing.T) { k8s.KubectlApply(t, ctx.KubectlOptions(t), "path/to/config") // Clean up any Kubernetes resources you have created - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDelete(t, ctx.KubectlOptions(t), "path/to/config") }) diff --git a/acceptance/tests/example/main_test.go b/acceptance/tests/example/main_test.go index e35893a1d3..323f421d32 100644 --- a/acceptance/tests/example/main_test.go +++ b/acceptance/tests/example/main_test.go @@ -1,8 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Rename package to your test package. -// NOTE: Remember to add your test package to acceptance/ci-inputs so it gets run in CI. package example import ( diff --git a/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml b/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml deleted file mode 100644 index 2a355e1b2f..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway/apigateway.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: Gateway -metadata: - name: gateway -spec: - gatewayClassName: gateway-class - listeners: - - protocol: HTTP - port: 80 - name: http - allowedRoutes: - namespaces: - from: "All" - - protocol: TCP - port: 81 - name: tcp - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTPS - port: 443 - name: https - tls: - certificateRefs: - - name: "certificate" - allowedRoutes: - namespaces: - from: "All" diff --git a/acceptance/tests/fixtures/bases/api-gateway/certificate.yaml b/acceptance/tests/fixtures/bases/api-gateway/certificate.yaml deleted file mode 100644 index d35dc559e2..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway/certificate.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Secret -metadata: - name: certificate -type: kubernetes.io/tls -data: - tls.crt: "" - tls.key: "" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml b/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml deleted file mode 100644 index 9ff985fd49..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway/gatewayclass.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: GatewayClass -metadata: - name: gateway-class -spec: - controllerName: "consul.hashicorp.com/gateway-controller" - parametersRef: - group: consul.hashicorp.com - kind: GatewayClassConfig - name: gateway-class-config diff --git a/acceptance/tests/fixtures/bases/api-gateway/gatewayclassconfig.yaml b/acceptance/tests/fixtures/bases/api-gateway/gatewayclassconfig.yaml deleted file mode 100644 index b8dfae7aa5..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway/gatewayclassconfig.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayClassConfig -metadata: - name: gateway-class-config \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway/httproute.yaml b/acceptance/tests/fixtures/bases/api-gateway/httproute.yaml deleted file mode 100644 index d59c4e067e..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway/httproute.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route -spec: - parentRefs: - - name: gateway \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml b/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml deleted file mode 100644 index e2125414d9..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - gatewayclassconfig.yaml - - gatewayclass.yaml - - apigateway.yaml - - httproute.yaml - - meshservice.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway/meshservice.yaml b/acceptance/tests/fixtures/bases/api-gateway/meshservice.yaml deleted file mode 100644 index 4c32452bc3..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway/meshservice.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: MeshService -metadata: - name: mesh-service -spec: - name: static-server \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml deleted file mode 100644 index 78547d5118..0000000000 --- a/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: fake-server -spec: - replicas: 1 - selector: - matchLabels: - app: fake-server - template: - metadata: - name: fake-server - labels: - app: fake-server - spec: - containers: - - name: fake-server - # TODO: move this to a hashicorp mirror - image: docker.io/achooo/fakeserver:latest - ports: - - containerPort: 443 - name: https - - containerPort: 8080 - name: http - serviceAccountName: fake-server - terminationGracePeriodSeconds: 0 # so deletion is quick diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/kustomization.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/kustomization.yaml deleted file mode 100644 index dc9c951ab2..0000000000 --- a/acceptance/tests/fixtures/bases/cloud/hcp-mock/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ - - -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - deployment.yaml - - service.yaml - - serviceaccount.yaml - diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/service.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/service.yaml deleted file mode 100644 index 0cc6f1b9ce..0000000000 --- a/acceptance/tests/fixtures/bases/cloud/hcp-mock/service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Service -metadata: - name: fake-server -spec: - selector: - app: fake-server - ports: - - name: https - port: 443 - targetPort: 443 - - name: http - port: 8080 - targetPort: 8080 diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/serviceaccount.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/serviceaccount.yaml deleted file mode 100644 index f52d9640cd..0000000000 --- a/acceptance/tests/fixtures/bases/cloud/hcp-mock/serviceaccount.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: fake-server diff --git a/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml b/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml deleted file mode 100644 index fb3f77f496..0000000000 --- a/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceIntentions -metadata: - name: consul-telemetry-collector -spec: - destination: - name: 'consul-telemetry-collector' - sources: - - name: '*' - action: allow - - - \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml b/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml deleted file mode 100644 index 9c19bf4ca3..0000000000 --- a/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - acl.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/controlplanerequestlimit.yaml b/acceptance/tests/fixtures/bases/crds-oss/controlplanerequestlimit.yaml deleted file mode 100644 index 5e8e32dbb5..0000000000 --- a/acceptance/tests/fixtures/bases/crds-oss/controlplanerequestlimit.yaml +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ControlPlaneRequestLimit -metadata: - name: controlplanerequestlimit -spec: - mode: "permissive" - readRate: 100.0 - writeRate: 100.0 - acl: - readRate: 100.0 - writeRate: 100.0 - catalog: - readRate: 100.0 - writeRate: 100.0 - configEntry: - readRate: 100.0 - writeRate: 100.0 - connectCA: - readRate: 100.0 - writeRate: 100.0 - coordinate: - readRate: 100.0 - writeRate: 100.0 - discoveryChain: - readRate: 100.0 - writeRate: 100.0 - health: - readRate: 100.0 - writeRate: 100.0 - intention: - readRate: 100.0 - writeRate: 100.0 - kv: - readRate: 100.0 - writeRate: 100.0 - tenancy: - readRate: 100.0 - writeRate: 100.0 -# preparedQuery: -# readRate: 100.0 -# writeRate: 100.0 - session: - readRate: 100.0 - writeRate: 100.0 - txn: - readRate: 100.0 - writeRate: 100.0 diff --git a/acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml b/acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml deleted file mode 100644 index 51d69ae709..0000000000 --- a/acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ExportedServices -metadata: - name: default -spec: - services: - - name: frontend - consumers: - - peer: peerName - - samenessGroup: groupName \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/ingressgateway.yaml b/acceptance/tests/fixtures/bases/crds-oss/ingressgateway.yaml index 79b5b194bc..d613dc217c 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/ingressgateway.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/ingressgateway.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: IngressGateway metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml b/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml deleted file mode 100644 index d35e532bf2..0000000000 --- a/acceptance/tests/fixtures/bases/crds-oss/jwtprovider.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: JWTProvider -metadata: - name: jwt-provider -spec: - jsonWebKeySet: - local: - filename: "jwks.txt" - issuer: "test-issuer" - audiences: - - "aud1" - - "aud2" - locations: - - header: - name: "x-jwt-header" - valuePrefix: "bearer" - forward: true - - queryParam: - name: "x-query-param" - - cookie: - name: "session-id" - forwarding: - headerName: "x-forwarded-jwt" - padForwardPayloadHeader: true - clockSkewSeconds: 45 - cacheConfig: - size: 15 \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml index 77afbc9522..040b64f155 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml @@ -1,16 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: -- ingressgateway.yaml -- mesh.yaml -- proxydefaults.yaml -- servicedefaults.yaml -- serviceintentions.yaml -- serviceresolver.yaml -- servicerouter.yaml -- servicesplitter.yaml -- terminatinggateway.yaml -- jwtprovider.yaml -- exportedservices.yaml -- controlplanerequestlimit.yaml \ No newline at end of file + - ingressgateway.yaml + - mesh.yaml + - proxydefaults.yaml + - servicedefaults.yaml + - serviceintentions.yaml + - serviceresolver.yaml + - servicerouter.yaml + - servicesplitter.yaml + - terminatinggateway.yaml diff --git a/acceptance/tests/fixtures/bases/crds-oss/mesh.yaml b/acceptance/tests/fixtures/bases/crds-oss/mesh.yaml index 9af8106b27..85474c1db6 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/mesh.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/mesh.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: Mesh metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml index af4d7c30c2..040da247f8 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/proxydefaults.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ProxyDefaults metadata: @@ -22,14 +19,3 @@ spec: - path: /health listenerPort: 22000 localPathPort: 8080 - envoyExtensions: - - name: builtin/aws/lambda - required: false - arguments: - payloadPassthrough: false - arn: arn:aws:lambda:us-west-2:111111111111:function:lambda-1234 - - name: builtin/aws/lambda - required: false - arguments: - payloadPassthrough: false - arn: arn:aws:lambda:us-east-1:111111111111:function:lambda-1234 diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index d0d1fe73bb..825c0484d8 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: @@ -22,35 +19,9 @@ spec: interval: 1s maxFailures: 10 enforcingConsecutive5xx: 60 - maxEjectionPercent: 100 - baseEjectionTime: 20s - name: "bar" limits: maxConnections: 5 passiveHealthCheck: interval: 10s - maxFailures: 2 - balanceInboundConnections: "exact_balance" - rateLimits: - instanceLevel: - requestsPerSecond: 1234 - requestsMaxBurst: 2345 - routes: - - pathExact: "/exact" - requestsPerSecond: 222 - requestsMaxBurst: 333 - - pathPrefix: "/prefix" - requestsPerSecond: 444 - - pathRegex: "/regex" - requestsPerSecond: 555 - envoyExtensions: - - name: builtin/aws/lambda - required: false - arguments: - payloadPassthrough: false - arn: arn:aws:lambda:us-west-2:111111111111:function:lambda-1234 - - name: builtin/aws/lambda - required: false - arguments: - payloadPassthrough: false - arn: arn:aws:lambda:us-east-1:111111111111:function:lambda-1234 \ No newline at end of file + maxFailures: 2 \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml b/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml new file mode 100644 index 0000000000..8ae095cd8e --- /dev/null +++ b/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml @@ -0,0 +1,10 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceExports +metadata: + name: exports +spec: + services: + - name: frontend + namespace: frontend + consumers: + - partition: other \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/serviceintentions.yaml b/acceptance/tests/fixtures/bases/crds-oss/serviceintentions.yaml index fe3408cbd5..6b86312a78 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/serviceintentions.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/serviceintentions.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml b/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml index fc236966d6..05dee04184 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/serviceresolver.yaml @@ -1,10 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceResolver metadata: name: resolver spec: redirect: - service: bar \ No newline at end of file + service: bar diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicerouter.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicerouter.yaml index 0f04cd4cd4..8526f1202d 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicerouter.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicerouter.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicesplitter.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicesplitter.yaml index 2eb9c3fccc..f0e8d74fe4 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicesplitter.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicesplitter.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/crds-oss/terminatinggateway.yaml b/acceptance/tests/fixtures/bases/crds-oss/terminatinggateway.yaml index 23daa6da20..77333b2011 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/terminatinggateway.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/terminatinggateway.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: TerminatingGateway metadata: diff --git a/acceptance/tests/fixtures/bases/exportedservices-default/exportedservices-default.yaml b/acceptance/tests/fixtures/bases/exportedservices-default/exportedservices-default.yaml index 0e523bdd7e..a260602109 100644 --- a/acceptance/tests/fixtures/bases/exportedservices-default/exportedservices-default.yaml +++ b/acceptance/tests/fixtures/bases/exportedservices-default/exportedservices-default.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/bases/exportedservices-default/kustomization.yaml b/acceptance/tests/fixtures/bases/exportedservices-default/kustomization.yaml index 59527a69fb..e540a4def1 100644 --- a/acceptance/tests/fixtures/bases/exportedservices-default/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/exportedservices-default/kustomization.yaml @@ -1,5 +1,2 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - exportedservices-default.yaml diff --git a/acceptance/tests/fixtures/bases/exportedservices-secondary/exportedservices-secondary.yaml b/acceptance/tests/fixtures/bases/exportedservices-secondary/exportedservices-secondary.yaml index 69151577f9..a514ed50d9 100644 --- a/acceptance/tests/fixtures/bases/exportedservices-secondary/exportedservices-secondary.yaml +++ b/acceptance/tests/fixtures/bases/exportedservices-secondary/exportedservices-secondary.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/bases/exportedservices-secondary/kustomization.yaml b/acceptance/tests/fixtures/bases/exportedservices-secondary/kustomization.yaml index e1781d47c1..10af8e20c5 100644 --- a/acceptance/tests/fixtures/bases/exportedservices-secondary/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/exportedservices-secondary/kustomization.yaml @@ -1,5 +1,2 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - exportedservices-secondary.yaml diff --git a/acceptance/tests/fixtures/bases/intention/intention.yaml b/acceptance/tests/fixtures/bases/intention/intention.yaml index 973866be03..c7bf26dac2 100644 --- a/acceptance/tests/fixtures/bases/intention/intention.yaml +++ b/acceptance/tests/fixtures/bases/intention/intention.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceIntentions metadata: diff --git a/acceptance/tests/fixtures/bases/intention/kustomization.yaml b/acceptance/tests/fixtures/bases/intention/kustomization.yaml index 572e7727a4..8d15c05511 100644 --- a/acceptance/tests/fixtures/bases/intention/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/intention/kustomization.yaml @@ -1,5 +1,2 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - intention.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/job-client/job.yaml b/acceptance/tests/fixtures/bases/job-client/job.yaml deleted file mode 100644 index 8c31caa7b4..0000000000 --- a/acceptance/tests/fixtures/bases/job-client/job.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: batch/v1 -kind: Job -metadata: - name: job-client - namespace: default - labels: - app: job-client -spec: - template: - metadata: - labels: - app: job-client - spec: - containers: - - name: job-client - image: alpine/curl:3.14 - ports: - - containerPort: 80 - command: - - /bin/sh - - -c - - | - echo "Started test job" - sleep 120 - echo "Ended test job" - serviceAccountName: job-client - restartPolicy: Never diff --git a/acceptance/tests/fixtures/bases/job-client/kustomization.yaml b/acceptance/tests/fixtures/bases/job-client/kustomization.yaml deleted file mode 100644 index 390d19c859..0000000000 --- a/acceptance/tests/fixtures/bases/job-client/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - ./job.yaml - - service.yaml - - serviceaccount.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/job-client/service.yaml b/acceptance/tests/fixtures/bases/job-client/service.yaml deleted file mode 100644 index c18e1dfa2e..0000000000 --- a/acceptance/tests/fixtures/bases/job-client/service.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Service -metadata: - name: job-client - namespace: default -spec: - selector: - app: job-client - ports: - - port: 80 \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml b/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml deleted file mode 100644 index 006ea2a836..0000000000 --- a/acceptance/tests/fixtures/bases/job-client/serviceaccount.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: job-client - namespace: default \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/mesh-gateway/kustomization.yaml b/acceptance/tests/fixtures/bases/mesh-gateway/kustomization.yaml index c271e6af8b..6a913f2c44 100644 --- a/acceptance/tests/fixtures/bases/mesh-gateway/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/mesh-gateway/kustomization.yaml @@ -1,5 +1,2 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - proxydefaults.yaml diff --git a/acceptance/tests/fixtures/bases/mesh-gateway/proxydefaults.yaml b/acceptance/tests/fixtures/bases/mesh-gateway/proxydefaults.yaml index 1560f6c640..2d28036fe5 100644 --- a/acceptance/tests/fixtures/bases/mesh-gateway/proxydefaults.yaml +++ b/acceptance/tests/fixtures/bases/mesh-gateway/proxydefaults.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ProxyDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/mesh-peering/kustomization.yaml b/acceptance/tests/fixtures/bases/mesh-peering/kustomization.yaml index e964c5ab05..b48237763e 100644 --- a/acceptance/tests/fixtures/bases/mesh-peering/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/mesh-peering/kustomization.yaml @@ -1,5 +1,2 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - meshpeering.yaml diff --git a/acceptance/tests/fixtures/bases/mesh-peering/meshpeering.yaml b/acceptance/tests/fixtures/bases/mesh-peering/meshpeering.yaml index 2fb6a04bb6..de84382d3e 100644 --- a/acceptance/tests/fixtures/bases/mesh-peering/meshpeering.yaml +++ b/acceptance/tests/fixtures/bases/mesh-peering/meshpeering.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: Mesh metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml index 5c2e0dcfa2..f80bd41d81 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/deployment.yaml b/acceptance/tests/fixtures/bases/multiport-app/deployment.yaml index f345b27c5e..a99d415d80 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/deployment.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml b/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml index fb792d63a7..ead8b3afe5 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - deployment.yaml - service.yaml diff --git a/acceptance/tests/fixtures/bases/multiport-app/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/multiport-app/privileged-scc-rolebinding.yaml index d0910816ed..f909785b36 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/privileged-scc-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/multiport-app/psp-rolebinding.yaml index f321858164..fce63f0076 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/psp-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/secret.yaml b/acceptance/tests/fixtures/bases/multiport-app/secret.yaml index bc8d931f1b..cb3fa957e2 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/secret.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/secret.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Secret metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/service.yaml b/acceptance/tests/fixtures/bases/multiport-app/service.yaml index 8684c75f21..d18da258a3 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/service.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/multiport-app/serviceaccount.yaml b/acceptance/tests/fixtures/bases/multiport-app/serviceaccount.yaml index 87b33476f4..2293c2e173 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml b/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml index c2f36c5e1a..4b3f7948ee 100644 --- a/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml +++ b/acceptance/tests/fixtures/bases/openshift/network-attachment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: diff --git a/acceptance/tests/fixtures/bases/peering/peering-acceptor.yaml b/acceptance/tests/fixtures/bases/peering/peering-acceptor.yaml index 1b3ea3956b..3eff952833 100644 --- a/acceptance/tests/fixtures/bases/peering/peering-acceptor.yaml +++ b/acceptance/tests/fixtures/bases/peering/peering-acceptor.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: PeeringAcceptor metadata: diff --git a/acceptance/tests/fixtures/bases/peering/peering-dialer.yaml b/acceptance/tests/fixtures/bases/peering/peering-dialer.yaml index 9bf9f03819..ec125d7bb6 100644 --- a/acceptance/tests/fixtures/bases/peering/peering-dialer.yaml +++ b/acceptance/tests/fixtures/bases/peering/peering-dialer.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: PeeringDialer metadata: diff --git a/acceptance/tests/fixtures/bases/pingpong/template.tmpl b/acceptance/tests/fixtures/bases/pingpong/template.tmpl deleted file mode 100644 index a8b893025b..0000000000 --- a/acceptance/tests/fixtures/bases/pingpong/template.tmpl +++ /dev/null @@ -1,123 +0,0 @@ -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: pingpong-client-{{.Iteration}} -spec: - protocol: 'http' ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: pingpong-client-{{.Iteration}} ---- -apiVersion: v1 -kind: Service -metadata: - name: pingpong-client-{{.Iteration}} -spec: - selector: - app: pingpong-client-{{.Iteration}} - ports: - - port: 80 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: pingpong-client-{{.Iteration}} - name: pingpong-client-{{.Iteration}} -spec: - replicas: {{.Replicas}} - selector: - matchLabels: - app: pingpong-client-{{.Iteration}} - template: - metadata: - annotations: - consul.hashicorp.com/connect-inject: 'true' - labels: - app: pingpong-client-{{.Iteration}} - spec: - serviceAccountName: pingpong-client-{{.Iteration}} - containers: - - name: pingpong-client-{{.Iteration}} - image: rancher/curlimages-curl:7.73.0 - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 1; curl -s --output /dev/null http://pingpong-server-{{.Iteration}} ; done;'] - resources: - requests: - memory: "10Mi" - cpu: "5m" - limits: - memory: "10Mi" - cpu: "5m" ---- -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: pingpong-server-{{.Iteration}} -spec: - protocol: 'http' ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: pingpong-server-{{.Iteration}} ---- -apiVersion: v1 -kind: Service -metadata: - name: pingpong-server-{{.Iteration}} -spec: - selector: - app: pingpong-server-{{.Iteration}} - ports: - - port: 80 - targetPort: 8080 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: pingpong-server-{{.Iteration}} - name: pingpong-server-{{.Iteration}} -spec: - replicas: {{.Replicas}} - selector: - matchLabels: - app: pingpong-server-{{.Iteration}} - template: - metadata: - annotations: - consul.hashicorp.com/connect-inject: 'true' - labels: - app: pingpong-server-{{.Iteration}} - spec: - serviceAccountName: pingpong-server-{{.Iteration}} - containers: - - name: pingpong-server-{{.Iteration}} - image: hashicorp/http-echo:latest - args: - - -text="hello world" - - -listen=:8080 - ports: - - containerPort: 8080 - resources: - requests: - memory: "10Mi" - cpu: "5m" - limits: - memory: "10Mi" - cpu: "5m" ---- - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceIntentions -metadata: - name: client-to-server-{{.Iteration}} -spec: - destination: - name: pingpong-server-{{.Iteration}} - sources: - - name: pingpong-client-{{.Iteration}} - action: allow diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/intention.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/intention.yaml deleted file mode 100644 index faff0cd251..0000000000 --- a/acceptance/tests/fixtures/bases/resolver-redirect/intention.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceIntentions -metadata: - name: client-to-server -spec: - destination: - name: static-server - sources: - - name: static-client - action: allow ---- -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceIntentions -metadata: - name: client-to-redirect -spec: - destination: - name: resolver-redirect - sources: - - name: static-client - action: allow \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/kustomization.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/kustomization.yaml deleted file mode 100644 index 323957ad53..0000000000 --- a/acceptance/tests/fixtures/bases/resolver-redirect/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - intention.yaml - - service.yaml - - serviceaccount.yaml - - resolver.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/resolver.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/resolver.yaml deleted file mode 100644 index 9adbcc9fb4..0000000000 --- a/acceptance/tests/fixtures/bases/resolver-redirect/resolver.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: resolver-redirect -spec: - redirect: - service: static-server \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/service.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/service.yaml deleted file mode 100644 index e63ae97cca..0000000000 --- a/acceptance/tests/fixtures/bases/resolver-redirect/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Service -metadata: - name: resolver-redirect -spec: - selector: - # Nothing needs to be selected. We only utilize this service so that KubeDNS has a ClusterIP to resolve. - app: idonotexist - ports: - - name: http - port: 80 - targetPort: 8080 diff --git a/acceptance/tests/fixtures/bases/resolver-redirect/serviceaccount.yaml b/acceptance/tests/fixtures/bases/resolver-redirect/serviceaccount.yaml deleted file mode 100644 index c74ecd667b..0000000000 --- a/acceptance/tests/fixtures/bases/resolver-redirect/serviceaccount.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: resolver-redirect diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/kustomization.yaml deleted file mode 100644 index 3f9d23c28a..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - sameness.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/sameness.yaml deleted file mode 100644 index 9c43bb505f..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/cluster-01-a-default-ns/sameness.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: SamenessGroup -metadata: - name: group-01 -spec: - defaultForFailover: true - members: - - partition: default - - partition: ap1 - - peer: cluster-02-a - - peer: cluster-03-a \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/kustomization.yaml deleted file mode 100644 index 3f9d23c28a..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - sameness.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/sameness.yaml deleted file mode 100644 index bf83338243..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/cluster-01-b-default-ns/sameness.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: SamenessGroup -metadata: - name: group-01 -spec: - defaultForFailover: true - members: - - partition: ap1 - - partition: default - - peer: cluster-02-a - - peer: cluster-03-a \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/kustomization.yaml deleted file mode 100644 index 3f9d23c28a..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - sameness.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/sameness.yaml deleted file mode 100644 index 2ed466585b..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/cluster-02-a-default-ns/sameness.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: SamenessGroup -metadata: - name: group-01 -spec: - defaultForFailover: true - members: - - partition: default - - peer: cluster-01-a - - peer: cluster-01-b - - peer: cluster-03-a \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/kustomization.yaml deleted file mode 100644 index 3f9d23c28a..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - sameness.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/sameness.yaml b/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/sameness.yaml deleted file mode 100644 index 83a3c1e71a..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/cluster-03-a-default-ns/sameness.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: SamenessGroup -metadata: - name: group-01 -spec: - defaultForFailover: true - members: - - partition: default - - peer: cluster-01-a - - peer: cluster-01-b - - peer: cluster-02-a \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/exportedservices-ap1.yaml b/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/exportedservices-ap1.yaml deleted file mode 100644 index 3dc494dd43..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/exportedservices-ap1.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ExportedServices -metadata: - name: ap1 -spec: - services: [] diff --git a/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/kustomization.yaml deleted file mode 100644 index 1793fa6db7..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/exportedservices-ap1/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - exportedservices-ap1.yaml diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml deleted file mode 100644 index 0646179949..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - service-defaults.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml deleted file mode 100644 index 87f6a71f32..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: static-server -spec: - protocol: http \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/kustomization.yaml deleted file mode 100644 index cf214eac6c..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - peering-dialer-cluster-02-a.yaml - - peering-dialer-cluster-03-a.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-02-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-02-a.yaml deleted file mode 100644 index d4c51553f3..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-02-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringDialer -metadata: - name: cluster-02-a -spec: - peer: - secret: - name: "cluster-02-a-cluster-01-a-peering-token" - key: "data" - backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-03-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-03-a.yaml deleted file mode 100644 index e6f9f9a6c9..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-a-dialer/peering-dialer-cluster-03-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringDialer -metadata: - name: cluster-03-a -spec: - peer: - secret: - name: "cluster-03-a-cluster-01-a-peering-token" - key: "data" - backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/kustomization.yaml deleted file mode 100644 index cf214eac6c..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - peering-dialer-cluster-02-a.yaml - - peering-dialer-cluster-03-a.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-02-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-02-a.yaml deleted file mode 100644 index 8f0f7064df..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-02-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringDialer -metadata: - name: cluster-02-a -spec: - peer: - secret: - name: "cluster-02-a-cluster-01-b-peering-token" - key: "data" - backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-03-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-03-a.yaml deleted file mode 100644 index 27cdd27ff8..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-01-b-dialer/peering-dialer-cluster-03-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringDialer -metadata: - name: cluster-03-a -spec: - peer: - secret: - name: "cluster-03-a-cluster-01-b-peering-token" - key: "data" - backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/kustomization.yaml deleted file mode 100644 index 4c485ee633..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - peering-acceptor-cluster-01-a.yaml - - peering-acceptor-cluster-01-b.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-a.yaml deleted file mode 100644 index b20b61328f..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: cluster-01-a -spec: - peer: - secret: - name: "cluster-02-a-cluster-01-a-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-b.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-b.yaml deleted file mode 100644 index c2d5c21b37..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-acceptor/peering-acceptor-cluster-01-b.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: cluster-01-b -spec: - peer: - secret: - name: "cluster-02-a-cluster-01-b-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/kustomization.yaml deleted file mode 100644 index c90eab30cc..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - peering-dialer-cluster-03-a.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/peering-dialer-cluster-03-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/peering-dialer-cluster-03-a.yaml deleted file mode 100644 index 80518a04c2..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-02-a-dialer/peering-dialer-cluster-03-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringDialer -metadata: - name: cluster-03-a -spec: - peer: - secret: - name: "cluster-03-a-cluster-02-a-peering-token" - key: "data" - backend: "kubernetes" diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/kustomization.yaml deleted file mode 100644 index 543a846805..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - peering-acceptor-cluster-01-a.yaml - - peering-acceptor-cluster-01-b.yaml - - peering-acceptor-cluster-02-a.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-a.yaml deleted file mode 100644 index 06c87e15a6..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: cluster-01-a -spec: - peer: - secret: - name: "cluster-03-a-cluster-01-a-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-b.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-b.yaml deleted file mode 100644 index 0a835ecef5..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-01-b.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: cluster-01-b -spec: - peer: - secret: - name: "cluster-03-a-cluster-01-b-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-02-a.yaml b/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-02-a.yaml deleted file mode 100644 index e60ea8b083..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/cluster-03-a-acceptor/peering-acceptor-cluster-02-a.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: cluster-02-a -spec: - peer: - secret: - name: "cluster-03-a-cluster-02-a-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/mesh/kustomization.yaml b/acceptance/tests/fixtures/bases/sameness/peering/mesh/kustomization.yaml deleted file mode 100644 index 926e91236d..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/mesh/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - mesh.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml b/acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml deleted file mode 100644 index 2fb6a04bb6..0000000000 --- a/acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: Mesh -metadata: - name: mesh -spec: - peering: - peerThroughMeshGateways: true diff --git a/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml b/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml deleted file mode 100644 index 8e36fe276e..0000000000 --- a/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - service-resolver.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml b/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml deleted file mode 100644 index 2e0459e381..0000000000 --- a/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server diff --git a/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml index b80bc5c562..be44c13e0a 100644 --- a/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/deployment.yaml b/acceptance/tests/fixtures/bases/static-client/deployment.yaml index ff29f1b03a..66bb771f6f 100644 --- a/acceptance/tests/fixtures/bases/static-client/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-client/deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/kustomization.yaml b/acceptance/tests/fixtures/bases/static-client/kustomization.yaml index 9aa0009dc4..b9d8e11f73 100644 --- a/acceptance/tests/fixtures/bases/static-client/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-client/kustomization.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - deployment.yaml - service.yaml diff --git a/acceptance/tests/fixtures/bases/static-client/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-client/privileged-scc-rolebinding.yaml index 36f6652e65..b8e097c3d8 100644 --- a/acceptance/tests/fixtures/bases/static-client/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-client/privileged-scc-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-client/psp-rolebinding.yaml index e516e57f7c..a377c8038d 100644 --- a/acceptance/tests/fixtures/bases/static-client/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-client/psp-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/service.yaml b/acceptance/tests/fixtures/bases/static-client/service.yaml index abd6ee4faf..16fbee0463 100644 --- a/acceptance/tests/fixtures/bases/static-client/service.yaml +++ b/acceptance/tests/fixtures/bases/static-client/service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/static-client/serviceaccount.yaml b/acceptance/tests/fixtures/bases/static-client/serviceaccount.yaml index 23578e5ead..b9bf136862 100644 --- a/acceptance/tests/fixtures/bases/static-client/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/static-client/serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/bases/static-metrics-app/deployment.yaml b/acceptance/tests/fixtures/bases/static-metrics-app/deployment.yaml index 21852487e3..a3020ddb47 100644 --- a/acceptance/tests/fixtures/bases/static-metrics-app/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-metrics-app/deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/static-metrics-app/kustomization.yaml b/acceptance/tests/fixtures/bases/static-metrics-app/kustomization.yaml index ccc066e0a8..6d1374a18e 100644 --- a/acceptance/tests/fixtures/bases/static-metrics-app/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-metrics-app/kustomization.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - deployment.yaml - service.yaml diff --git a/acceptance/tests/fixtures/bases/static-metrics-app/service.yaml b/acceptance/tests/fixtures/bases/static-metrics-app/service.yaml index 2e553f7e83..a37db26875 100644 --- a/acceptance/tests/fixtures/bases/static-metrics-app/service.yaml +++ b/acceptance/tests/fixtures/bases/static-metrics-app/service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml index 2be7cf13db..d224a082da 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/configmap.yaml b/acceptance/tests/fixtures/bases/static-server-https/configmap.yaml index 5a037ecdc6..dcb975978e 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/configmap.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/configmap.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: ConfigMap metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/deployment.yaml b/acceptance/tests/fixtures/bases/static-server-https/deployment.yaml index c95bdb4289..3071162c15 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml b/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml index da166af201..78508b7938 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - deployment.yaml - configmap.yaml diff --git a/acceptance/tests/fixtures/bases/static-server-https/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-https/privileged-scc-rolebinding.yaml index bed477ed26..1b9fe13789 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/privileged-scc-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-https/psp-rolebinding.yaml index 3c6cfad8f1..acccd2fcec 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/psp-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/service.yaml b/acceptance/tests/fixtures/bases/static-server-https/service.yaml index 7f4f930c0a..168c90d3d0 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/service.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-https/serviceaccount.yaml b/acceptance/tests/fixtures/bases/static-server-https/serviceaccount.yaml index ced9002d6b..f4267a573e 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/anyuid-scc-rolebinding.yaml deleted file mode 100644 index eb86dc8bae..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/anyuid-scc-rolebinding.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: static-server-tcp-openshift-anyuid -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:openshift:scc:anyuid -subjects: - - kind: ServiceAccount - name: static-server-tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/deployment.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/deployment.yaml deleted file mode 100644 index 9aa5177e9e..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/deployment.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: static-server-tcp - name: static-server-tcp -spec: - replicas: 1 - selector: - matchLabels: - app: static-server-tcp - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - labels: - app: static-server-tcp - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/kschoche/http-echo:latest - args: - - -text="hello world" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: static-server-tcp diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/kustomization.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/kustomization.yaml deleted file mode 100644 index 2180aa94e1..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/kustomization.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - deployment.yaml - - service.yaml - - serviceaccount.yaml - - servicedefaults.yaml - - psp-rolebinding.yaml - - anyuid-scc-rolebinding.yaml - - privileged-scc-rolebinding.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/privileged-scc-rolebinding.yaml deleted file mode 100644 index ac28006765..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/privileged-scc-rolebinding.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: static-server-tcp-openshift-privileged -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:openshift:scc:privileged -subjects: - - kind: ServiceAccount - name: static-server-tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml deleted file mode 100644 index f4f008dbea..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: static-server-tcp -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: test-psp -subjects: - - kind: ServiceAccount - name: static-server-tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/service.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/service.yaml deleted file mode 100644 index 6ceccf940a..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Service -metadata: - name: static-server-tcp - labels: - app: static-server-tcp -spec: - ports: - - name: http - port: 8080 - selector: - app: static-server-tcp diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/serviceaccount.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/serviceaccount.yaml deleted file mode 100644 index af2247af8e..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/serviceaccount.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: static-server-tcp diff --git a/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml b/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml deleted file mode 100644 index f89765cf6d..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/servicedefaults.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: static-server-tcp - namespace: default -spec: - protocol: tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml index 2be7cf13db..d224a082da 100644 --- a/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/deployment.yaml b/acceptance/tests/fixtures/bases/static-server/deployment.yaml index 9f5776c9ca..1c724b0d37 100644 --- a/acceptance/tests/fixtures/bases/static-server/deployment.yaml +++ b/acceptance/tests/fixtures/bases/static-server/deployment.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: @@ -18,9 +15,7 @@ spec: spec: containers: - name: static-server - # Using alpine vs latest as there is a build issue with M1s. Also other tests in multiport-app reference - # alpine so standardizing this. - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine + image: docker.mirror.hashicorp.services/hashicorp/http-echo:latest args: - -text="hello world" - -listen=:8080 diff --git a/acceptance/tests/fixtures/bases/static-server/kustomization.yaml b/acceptance/tests/fixtures/bases/static-server/kustomization.yaml index 9aa0009dc4..b9d8e11f73 100644 --- a/acceptance/tests/fixtures/bases/static-server/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-server/kustomization.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resources: - deployment.yaml - service.yaml diff --git a/acceptance/tests/fixtures/bases/static-server/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server/privileged-scc-rolebinding.yaml index bed477ed26..1b9fe13789 100644 --- a/acceptance/tests/fixtures/bases/static-server/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server/privileged-scc-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server/psp-rolebinding.yaml index 3c6cfad8f1..acccd2fcec 100644 --- a/acceptance/tests/fixtures/bases/static-server/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server/psp-rolebinding.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/service.yaml b/acceptance/tests/fixtures/bases/static-server/service.yaml index 6d8d54acc2..4776c07239 100644 --- a/acceptance/tests/fixtures/bases/static-server/service.yaml +++ b/acceptance/tests/fixtures/bases/static-server/service.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Service metadata: diff --git a/acceptance/tests/fixtures/bases/static-server/serviceaccount.yaml b/acceptance/tests/fixtures/bases/static-server/serviceaccount.yaml index ced9002d6b..f4267a573e 100644 --- a/acceptance/tests/fixtures/bases/static-server/serviceaccount.yaml +++ b/acceptance/tests/fixtures/bases/static-server/serviceaccount.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: ServiceAccount metadata: diff --git a/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml b/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml deleted file mode 100644 index 249cb948bb..0000000000 --- a/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - trafficpermissions.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml b/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml deleted file mode 100644 index ed5c0436ed..0000000000 --- a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: auth.consul.hashicorp.com/v2beta1 -kind: TrafficPermissions -metadata: - name: client-to-server -spec: - destination: - identityName: multiport - action: ACTION_ALLOW - permissions: - - sources: - - identityName: static-client diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml deleted file mode 100644 index 5c2e0dcfa2..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: multiport-openshift-anyuid -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:openshift:scc:anyuid -subjects: - - kind: ServiceAccount - name: multiport ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: multiport-admin-openshift-anyuid -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:openshift:scc:anyuid -subjects: - - kind: ServiceAccount - name: multiport-admin diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml deleted file mode 100644 index 0fecd3b590..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: multiport -spec: - replicas: 1 - selector: - matchLabels: - app: multiport - template: - metadata: - name: multiport - labels: - app: multiport - annotations: - "consul.hashicorp.com/mesh-inject": "true" - # TODO: remove this when we add tproxy patch support for this fixture - 'consul.hashicorp.com/transparent-proxy': 'true' - 'consul.hashicorp.com/enable-metrics': 'false' - 'consul.hashicorp.com/enable-metrics-merging': 'false' - spec: - containers: - - name: multiport - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine - args: - - -text="hello world" - - -listen=:8080 - ports: - - containerPort: 8080 - name: web - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy-multiport'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - - name: multiport-admin - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine - args: - - -text="hello world from 9090 admin" - - -listen=:9090 - ports: - - containerPort: 9090 - # This name is meant to be used alongside the _numeric_ K8s service target port - # to verify that we can still route traffic to the named port when there's a mismatch. - name: admin - livenessProbe: - httpGet: - port: 9090 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 9090 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy-multiport-admin'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: multiport - terminationGracePeriodSeconds: 0 # so deletion is quick diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml deleted file mode 100644 index fb792d63a7..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - deployment.yaml - - service.yaml - - secret.yaml - - serviceaccount.yaml - - psp-rolebinding.yaml - - anyuid-scc-rolebinding.yaml - - privileged-scc-rolebinding.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml deleted file mode 100644 index f4f734813e..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: multiport-openshift-privileged -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:openshift:scc:privileged -subjects: - - kind: ServiceAccount - name: multiport diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml deleted file mode 100644 index 623a388d20..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: multiport -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: test-psp -subjects: - - kind: ServiceAccount - name: multiport diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml deleted file mode 100644 index a412cac6c5..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Secret -metadata: - name: multiport - annotations: - kubernetes.io/service-account.name: multiport -type: kubernetes.io/service-account-token diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml deleted file mode 100644 index fe47663c3d..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Service -metadata: - name: multiport -spec: - selector: - app: multiport - ports: - - name: web - port: 8080 - targetPort: web - - name: admin - port: 9090 - # Test with a mix of named and numeric target ports. - targetPort: 9090 diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml deleted file mode 100644 index 8af955e059..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: multiport diff --git a/acceptance/tests/fixtures/cases/api-gateways/certificate/certificate.yaml b/acceptance/tests/fixtures/cases/api-gateways/certificate/certificate.yaml deleted file mode 100644 index d35dc559e2..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/certificate/certificate.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Secret -metadata: - name: certificate -type: kubernetes.io/tls -data: - tls.crt: "" - tls.key: "" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/certificate/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/certificate/kustomization.yaml deleted file mode 100644 index 42b7526335..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/certificate/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - certificate.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/kustomization.yaml deleted file mode 100644 index cdbcd688c0..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - serviceresolver.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/serviceresolver.yaml b/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/serviceresolver.yaml deleted file mode 100644 index ca009754b4..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/dc1-to-dc2-resolver/serviceresolver.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server -spec: - redirect: - service: static-server - datacenter: dc2 \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/kustomization.yaml deleted file mode 100644 index cdbcd688c0..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - serviceresolver.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/serviceresolver.yaml b/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/serviceresolver.yaml deleted file mode 100644 index af8cdb72ed..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/dc2-to-dc1-resolver/serviceresolver.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server -spec: - redirect: - service: static-server - datacenter: dc1 \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml deleted file mode 100644 index 7f0428b039..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/gateway/gateway.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: Gateway -metadata: - name: gateway -spec: - gatewayClassName: consul - listeners: - - protocol: HTTPS - port: 8080 - name: https - tls: - certificateRefs: - - name: "certificate" - namespace: "default" - allowedRoutes: - namespaces: - from: "All" diff --git a/acceptance/tests/fixtures/cases/api-gateways/gateway/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/gateway/kustomization.yaml deleted file mode 100644 index 8efac31693..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/gateway/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - gateway.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/httproute/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/httproute/kustomization.yaml deleted file mode 100644 index 7a6713835c..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/httproute/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - route.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/httproute/route.yaml b/acceptance/tests/fixtures/cases/api-gateways/httproute/route.yaml deleted file mode 100644 index 9f7f66b433..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/httproute/route.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: route -spec: {} \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml deleted file mode 100644 index 64e3d3c8d5..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: Gateway -metadata: - name: gateway -spec: - gatewayClassName: gateway-class - listeners: - - protocol: HTTP - port: 8081 - name: http-auth - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTP - port: 8082 - name: http-invalid-attach - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTP - port: 80 - name: http - allowedRoutes: - namespaces: - from: "All" - - protocol: TCP - port: 81 - name: tcp - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTPS - port: 443 - name: https - tls: - certificateRefs: - - name: "certificate" - allowedRoutes: - namespaces: - from: "All" diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml deleted file mode 100644 index 19b0669c1a..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteAuthFilter -metadata: - name: route-jwt-auth-filter-other - namespace: other -spec: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml deleted file mode 100644 index 03960f67be..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -# This is used to show that a gateway cannot have more than one gateway policy attached to it -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayPolicy -metadata: - name: bad-policy -spec: - targetRef: - name: gateway - sectionName: http-auth - group: gateway.networking.k8s.io/v1beta1 - kind: Gateway - override: - jwt: - providers: - - name: "local" - default: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml deleted file mode 100644 index 0886ca4bed..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- extra-gateway-policy.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml deleted file mode 100644 index 5552d7e085..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayPolicy -metadata: - name: my-policy -spec: - targetRef: - name: gateway - sectionName: http-auth - group: gateway.networking.k8s.io/v1beta1 - kind: Gateway - override: - jwt: - providers: - - name: "local" - default: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml deleted file mode 100644 index 93fee5f24a..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route-auth -spec: - parentRefs: - - name: gateway - sectionName: http-auth - rules: - - matches: - - path: - type: PathPrefix - value: "/admin" - backendRefs: - - name: static-server - port: 8080 - filters: - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteAuthFilter - name: route-jwt-auth-filter - - matches: - - path: - type: PathPrefix - value: "/pet" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml deleted file mode 100644 index 55753c29aa..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route-auth-invalid -spec: - parentRefs: - - name: gateway - sectionName: http-invalid-attach - rules: - - matches: - - path: - type: PathPrefix - value: "/admin" - backendRefs: - - name: static-server - port: 8080 - filters: - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteAuthFilter - name: route-jwt-auth-filter-other - - matches: - - path: - type: PathPrefix - value: "/pet" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml deleted file mode 100644 index e4dc1b5a8b..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route-no-auth-on-auth-listener -spec: - parentRefs: - - name: gateway - sectionName: http-auth - rules: - - matches: - - path: - type: PathPrefix - value: "/admin-no-auth" - backendRefs: - - name: static-server - port: 8080 - - matches: - - path: - type: PathPrefix - value: "/pet-no-auth" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml deleted file mode 100644 index b505d36cb1..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route -spec: - parentRefs: - - name: gateway - sectionName: http - rules: - - matches: - - path: - type: PathPrefix - value: "/v1" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml deleted file mode 100644 index 3894e654ff..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route2-auth -spec: - parentRefs: - - name: gateway - sectionName: http-auth - rules: - - matches: - - path: - type: PathPrefix - value: "/admin-2" - backendRefs: - - name: static-server - port: 8080 - filters: - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteAuthFilter - name: route-jwt-auth-filter - - matches: - - path: - type: PathPrefix - value: "/pet-2" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml deleted file mode 100644 index 1e5cbf35d6..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: JWTProvider -metadata: - name: local -spec: - issuer: local - jsonWebKeySet: - local: - jwks: "ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicCI6ICI5TTlWSVhJR0hpR3FlTnhseEJ2V0xFV09oUFh3dXhXZUpod01uM3dGdG9STEtfZmF6VWxjWEc1cUViLTdpMXo3VmlPUWVZRnh6WUZYTS1pbVU3OVFRa1dTVUVSazR2dHZuc2R5UnpUSnVPc3A0ZUhuWFVMSHJPOU51NkJ5bC1VeVprMzFvSnFGeGllM0pHQXlRLUM2OVF2NVFkVjFZV0hfVDkyTzk4d1hYZGMiLAogICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICJxIjogInFIVnZBb3h0ckgxUTVza25veXNMMkhvbC1ubnU3ZlM3Mjg4clRGdE9jeG9Jb29nWXBKVTljemxwcjctSlo2bjc0TUViVHBBMHRkSUR5TEtQQ0xIN3JKTFRrZzBDZVZNQWpmY01zdkRUcWdFOHNBWE42bzd2ZjYya2hwcExYOHVCU3JxSHkyV1JhZXJsbDROU09hcmRGSkQ2MWhHSVF2cEpXRk4xazFTV3pWcyIsCiAgICAgICAgICAgICJkIjogIlp3elJsVklRZkg5ekZ6d1hOZ2hEMHhkZVctalBCbmRkWnJNZ0wwQ2JjeXZZYlg2X1c0ajlhM1dmYWpobmI2bTFILW9CWjRMczVmNXNRVTB2ZFJ2ZG1laFItUG43aWNRcUdURFNKUTYtdWVtNm15UVRWaEo2UmZiM0lINVJ2VDJTOXUzcVFDZWFadWN3aXFoZ1RCbFhnOWFfV0pwVHJYNFhPQ3JCR1ZsTng3Z2JETVJOamNEN0FnRkZ3S2p2TEZVdDRLTkZmdEJqaFF0TDFLQ2VwblNmamtvRm1RUTVlX3RSS2ozX2U1V3pNSkJkekpQejNkR2YxZEk3OF9wYmJFbmFMcWhqNWg0WUx2UU5JUUhVcURYSGx4ZDc1Qlh3aFJReE1nUDRfd1EwTFk2cVRKNGFDa2Q0RDJBTUtqMzJqeVFiVTRKTE9jQjFNMnZBRWFyc2NTU3l0USIsCiAgICAgICAgICAgICJlIjogIkFRQUIiLAogICAgICAgICAgICAidXNlIjogInNpZyIsCiAgICAgICAgICAgICJraWQiOiAiQy1FMW5DandnQkMtUHVHTTJPNDY3RlJEaEt4OEFrVmN0SVNBYm8zcmlldyIsCiAgICAgICAgICAgICJxaSI6ICJ0N2VOQjhQV21xVHdKREZLQlZKZExrZnJJT2drMFJ4MnREODBGNHB5cjhmNzRuNGlVWXFmWG1haVZtbGx2c2FlT3JlNHlIczQ4UE45NVZsZlVvS3Z6ZEJFaDNZTDFINGZTOGlYYXNzNGJiVnVuWHR4U0hMZFFPYUNZYUplSmhBbGMyUWQ4elR0NFFQWk9yRWVWLVJTYU0tN095ekkwUWtSSF9tcmk1YmRrOXMiLAogICAgICAgICAgICAiZHAiOiAiYnBLckQtVXhrRENDal81MFZLU0NFeE1Ec1Zob2VBZm1tNjMxb1o5aDhUTkZ4TUU1YVptbUJ2VzBJUG9wMm1PUF9qTW9FVWxfUG1RYUlBOEgtVEdqTFp2QTMxSlZBeFN3TU5aQzdwaVFPRjYzVnhneTZUTzlmb1hENVdndC1oLUNxU1N6T2V3eFdmUWNTMmpMcTA3NUFxOTYwTnA2SHhjbE8weUdRN1JDSlpjIiwKICAgICAgICAgICAgImFsZyI6ICJQUzI1NiIsCiAgICAgICAgICAgICJkcSI6ICJpdVZveGwwckFKSEM1c2JzbTZpZWQ3c2ZIVXIwS2Rja0hiVFBLb0lPU1BFcU5YaXBlT3BrWkdEdU55NWlDTXNyRnNHaDFrRW9kTkhZdE40ay1USm5KSDliV296SGdXbGloNnN2R1V0Zi1raFMxWC16ckxaMTJudzlyNDRBbjllWG54bjFaVXMxZm5OakltM3dtZ083algyTWxIeVlNVUZVd0RMd09xNEFPUWsiLAogICAgICAgICAgICAibiI6ICJvUmhjeUREdmp3NFZ4SHRRNTZhRDlNSmRTaWhWSk1nTHd1b2FCQVhhc0RjVDNEWVZjcENlVGxDMVBPdzdPNW1Ec2ZSWVFtcGpoendyRDVZWU8yeDE4REl4czdyNTNJdFMxRy1ybnQxQ1diVE9fUzFJT01DR2xxYzh5VWJnLUhSUkRETXQyb2V3TjJoRGtxYlBKVFJNbXpjRkpNMHRpTm1RZVVMcWViZEVYaWVUblJMT1BkMWg2ZmJycVNLS01mSXlIbGZ1WXFQc1VWSEdkMVBESGljZ3NMazFtZDhtYTNIS1hWM0hJdzZrdUV6R0hQb1gxNHo4YWF6RFFZWndUR3ZxVGlPLUdRUlVDZUJueVo4bVhyWnRmSjNqVk83UUhXcEx3MlM1VDVwVTRwcE0xQXppWTFxUDVfY3ZpOTNZT2Zrb09PalRTX3V3RENZWGFxWjB5bTJHYlEiCiAgICAgICAgfQogICAgXQp9Cg==" diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml deleted file mode 100644 index 9ea3ee2acd..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteAuthFilter -metadata: - name: route-jwt-auth-filter -spec: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml deleted file mode 100644 index 648c936746..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- ../../../bases/api-gateway -- ../../static-server-inject -- httproute-auth.yaml -- httproute-invalid-external-ref.yaml -- httproute2-auth.yaml -- httproute-no-auth-on-auth-listener.yaml -- jwt-provider.yaml -- jwt-route-filter.yaml -- gateway-policy.yaml - - -patches: -- path: httproute.yaml -- path: api-gateway.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/api-gateway.yaml deleted file mode 100644 index 3b59ada305..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/api-gateway.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: Gateway -metadata: - name: gateway -spec: - gatewayClassName: gateway-class - listeners: - - protocol: HTTP - port: 80 - name: http - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTPS - port: 443 - name: https - tls: - certificateRefs: - - name: "certificate" - allowedRoutes: - namespaces: - from: "All" diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/external-ref.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/external-ref.yaml deleted file mode 100644 index 57e6dfee7c..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/external-ref.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteAuthFilter -metadata: - name: route-jwt-auth-filter - namespace: default -spec: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml deleted file mode 100644 index 966eab85a6..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/filters.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteRetryFilter -metadata: - name: retrytrafficfilter -spec: - numRetries: 1 - retryOnConnectFailure: false - retryOn: - - reset - - unavailable - retryOnStatusCodes: - - 500 - - 502 - ---- -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteTimeoutFilter -metadata: - name: timeouttrafficfilter -spec: - requestTimeout: "1s" - idleTimeout: "1s" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gateway-policy.yaml deleted file mode 100644 index 5552d7e085..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gateway-policy.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayPolicy -metadata: - name: my-policy -spec: - targetRef: - name: gateway - sectionName: http-auth - group: gateway.networking.k8s.io/v1beta1 - kind: Gateway - override: - jwt: - providers: - - name: "local" - default: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gatewayclassconfig.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gatewayclassconfig.yaml deleted file mode 100644 index 42c9bee986..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/gatewayclassconfig.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayClassConfig -metadata: - name: gateway-class-config -spec: - deployment: - defaultInstances: 2 - maxInstances: 3 - minInstances: 1 \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/httproute.yaml deleted file mode 100644 index 760791cf51..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/httproute.yaml +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route -spec: - parentRefs: - - name: gateway - sectionName: http - rules: - - matches: - - path: - type: PathPrefix - value: "/v1" - backendRefs: - - name: static-server - port: 8080 - filters: - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteAuthFilter - name: route-jwt-auth-filter - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteRetryFilter - name: retrytrafficfilter - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteTimeoutFilter - name: timeouttrafficfilter - - type: RequestHeaderModifier - requestHeaderModifier: - add: - - name: my-header - value: foo - - type: URLRewrite - urlRewrite: - path: - type: "ReplacePrefixMatch" - replacePrefixMatch: "/v1/test" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-provider.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-provider.yaml deleted file mode 100644 index 1e5cbf35d6..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-provider.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: JWTProvider -metadata: - name: local -spec: - issuer: local - jsonWebKeySet: - local: - jwks: "ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicCI6ICI5TTlWSVhJR0hpR3FlTnhseEJ2V0xFV09oUFh3dXhXZUpod01uM3dGdG9STEtfZmF6VWxjWEc1cUViLTdpMXo3VmlPUWVZRnh6WUZYTS1pbVU3OVFRa1dTVUVSazR2dHZuc2R5UnpUSnVPc3A0ZUhuWFVMSHJPOU51NkJ5bC1VeVprMzFvSnFGeGllM0pHQXlRLUM2OVF2NVFkVjFZV0hfVDkyTzk4d1hYZGMiLAogICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICJxIjogInFIVnZBb3h0ckgxUTVza25veXNMMkhvbC1ubnU3ZlM3Mjg4clRGdE9jeG9Jb29nWXBKVTljemxwcjctSlo2bjc0TUViVHBBMHRkSUR5TEtQQ0xIN3JKTFRrZzBDZVZNQWpmY01zdkRUcWdFOHNBWE42bzd2ZjYya2hwcExYOHVCU3JxSHkyV1JhZXJsbDROU09hcmRGSkQ2MWhHSVF2cEpXRk4xazFTV3pWcyIsCiAgICAgICAgICAgICJkIjogIlp3elJsVklRZkg5ekZ6d1hOZ2hEMHhkZVctalBCbmRkWnJNZ0wwQ2JjeXZZYlg2X1c0ajlhM1dmYWpobmI2bTFILW9CWjRMczVmNXNRVTB2ZFJ2ZG1laFItUG43aWNRcUdURFNKUTYtdWVtNm15UVRWaEo2UmZiM0lINVJ2VDJTOXUzcVFDZWFadWN3aXFoZ1RCbFhnOWFfV0pwVHJYNFhPQ3JCR1ZsTng3Z2JETVJOamNEN0FnRkZ3S2p2TEZVdDRLTkZmdEJqaFF0TDFLQ2VwblNmamtvRm1RUTVlX3RSS2ozX2U1V3pNSkJkekpQejNkR2YxZEk3OF9wYmJFbmFMcWhqNWg0WUx2UU5JUUhVcURYSGx4ZDc1Qlh3aFJReE1nUDRfd1EwTFk2cVRKNGFDa2Q0RDJBTUtqMzJqeVFiVTRKTE9jQjFNMnZBRWFyc2NTU3l0USIsCiAgICAgICAgICAgICJlIjogIkFRQUIiLAogICAgICAgICAgICAidXNlIjogInNpZyIsCiAgICAgICAgICAgICJraWQiOiAiQy1FMW5DandnQkMtUHVHTTJPNDY3RlJEaEt4OEFrVmN0SVNBYm8zcmlldyIsCiAgICAgICAgICAgICJxaSI6ICJ0N2VOQjhQV21xVHdKREZLQlZKZExrZnJJT2drMFJ4MnREODBGNHB5cjhmNzRuNGlVWXFmWG1haVZtbGx2c2FlT3JlNHlIczQ4UE45NVZsZlVvS3Z6ZEJFaDNZTDFINGZTOGlYYXNzNGJiVnVuWHR4U0hMZFFPYUNZYUplSmhBbGMyUWQ4elR0NFFQWk9yRWVWLVJTYU0tN095ekkwUWtSSF9tcmk1YmRrOXMiLAogICAgICAgICAgICAiZHAiOiAiYnBLckQtVXhrRENDal81MFZLU0NFeE1Ec1Zob2VBZm1tNjMxb1o5aDhUTkZ4TUU1YVptbUJ2VzBJUG9wMm1PUF9qTW9FVWxfUG1RYUlBOEgtVEdqTFp2QTMxSlZBeFN3TU5aQzdwaVFPRjYzVnhneTZUTzlmb1hENVdndC1oLUNxU1N6T2V3eFdmUWNTMmpMcTA3NUFxOTYwTnA2SHhjbE8weUdRN1JDSlpjIiwKICAgICAgICAgICAgImFsZyI6ICJQUzI1NiIsCiAgICAgICAgICAgICJkcSI6ICJpdVZveGwwckFKSEM1c2JzbTZpZWQ3c2ZIVXIwS2Rja0hiVFBLb0lPU1BFcU5YaXBlT3BrWkdEdU55NWlDTXNyRnNHaDFrRW9kTkhZdE40ay1USm5KSDliV296SGdXbGloNnN2R1V0Zi1raFMxWC16ckxaMTJudzlyNDRBbjllWG54bjFaVXMxZm5OakltM3dtZ083algyTWxIeVlNVUZVd0RMd09xNEFPUWsiLAogICAgICAgICAgICAibiI6ICJvUmhjeUREdmp3NFZ4SHRRNTZhRDlNSmRTaWhWSk1nTHd1b2FCQVhhc0RjVDNEWVZjcENlVGxDMVBPdzdPNW1Ec2ZSWVFtcGpoendyRDVZWU8yeDE4REl4czdyNTNJdFMxRy1ybnQxQ1diVE9fUzFJT01DR2xxYzh5VWJnLUhSUkRETXQyb2V3TjJoRGtxYlBKVFJNbXpjRkpNMHRpTm1RZVVMcWViZEVYaWVUblJMT1BkMWg2ZmJycVNLS01mSXlIbGZ1WXFQc1VWSEdkMVBESGljZ3NMazFtZDhtYTNIS1hWM0hJdzZrdUV6R0hQb1gxNHo4YWF6RFFZWndUR3ZxVGlPLUdRUlVDZUJueVo4bVhyWnRmSjNqVk83UUhXcEx3MlM1VDVwVTRwcE0xQXppWTFxUDVfY3ZpOTNZT2Zrb09PalRTX3V3RENZWGFxWjB5bTJHYlEiCiAgICAgICAgfQogICAgXQp9Cg==" diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-route-filter.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-route-filter.yaml deleted file mode 100644 index 9ea3ee2acd..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/jwt-route-filter.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteAuthFilter -metadata: - name: route-jwt-auth-filter -spec: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/kustomization.yaml deleted file mode 100644 index 194fc16b6c..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink-ent/kustomization.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- ../../../bases/api-gateway -- ../../static-server-inject -- filters.yaml -- jwt-provider.yaml -- jwt-route-filter.yaml -- gateway-policy.yaml - - -patches: -- path: gatewayclassconfig.yaml -- path: httproute.yaml -- path: api-gateway.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/api-gateway.yaml deleted file mode 100644 index 3b59ada305..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/api-gateway.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: Gateway -metadata: - name: gateway -spec: - gatewayClassName: gateway-class - listeners: - - protocol: HTTP - port: 80 - name: http - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTPS - port: 443 - name: https - tls: - certificateRefs: - - name: "certificate" - allowedRoutes: - namespaces: - from: "All" diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/external-ref.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/external-ref.yaml deleted file mode 100644 index 57e6dfee7c..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/external-ref.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteAuthFilter -metadata: - name: route-jwt-auth-filter - namespace: default -spec: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml deleted file mode 100644 index 966eab85a6..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/filters.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteRetryFilter -metadata: - name: retrytrafficfilter -spec: - numRetries: 1 - retryOnConnectFailure: false - retryOn: - - reset - - unavailable - retryOnStatusCodes: - - 500 - - 502 - ---- -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteTimeoutFilter -metadata: - name: timeouttrafficfilter -spec: - requestTimeout: "1s" - idleTimeout: "1s" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gateway-policy.yaml deleted file mode 100644 index 5552d7e085..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gateway-policy.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayPolicy -metadata: - name: my-policy -spec: - targetRef: - name: gateway - sectionName: http-auth - group: gateway.networking.k8s.io/v1beta1 - kind: Gateway - override: - jwt: - providers: - - name: "local" - default: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gatewayclassconfig.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gatewayclassconfig.yaml deleted file mode 100644 index 42c9bee986..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/gatewayclassconfig.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayClassConfig -metadata: - name: gateway-class-config -spec: - deployment: - defaultInstances: 2 - maxInstances: 3 - minInstances: 1 \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/httproute.yaml deleted file mode 100644 index 519b790a4d..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/httproute.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route -spec: - parentRefs: - - name: gateway - sectionName: http - rules: - - matches: - - path: - type: PathPrefix - value: "/v1" - backendRefs: - - name: static-server - port: 8080 - filters: - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteRetryFilter - name: retrytrafficfilter - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteTimeoutFilter - name: timeouttrafficfilter - - type: RequestHeaderModifier - requestHeaderModifier: - add: - - name: my-header - value: foo - - type: URLRewrite - urlRewrite: - path: - type: "ReplacePrefixMatch" - replacePrefixMatch: "/v1/test" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/kustomization.yaml deleted file mode 100644 index 55a32c7260..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/kitchen-sink/kustomization.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- ../../../bases/api-gateway -- ../../static-server-inject -- filters.yaml - - -patches: -- path: gatewayclassconfig.yaml -- path: httproute.yaml -- path: api-gateway.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/mesh/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/mesh/kustomization.yaml deleted file mode 100644 index c271e6af8b..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/mesh/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - proxydefaults.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/mesh/proxydefaults.yaml b/acceptance/tests/fixtures/cases/api-gateways/mesh/proxydefaults.yaml deleted file mode 100644 index ccc0905e32..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/mesh/proxydefaults.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ProxyDefaults -metadata: - name: global -spec: - config: - protocol: http - meshGateway: - mode: local \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/kustomization.yaml deleted file mode 100644 index cdbcd688c0..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - serviceresolver.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/serviceresolver.yaml b/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/serviceresolver.yaml deleted file mode 100644 index 20874fe1f9..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/peer-resolver/serviceresolver.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server -spec: - redirect: - peer: server - namespace: ns1 - service: static-server \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/resolver/kustomization.yaml deleted file mode 100644 index cdbcd688c0..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/resolver/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - serviceresolver.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/resolver/serviceresolver.yaml b/acceptance/tests/fixtures/cases/api-gateways/resolver/serviceresolver.yaml deleted file mode 100644 index 18960a37db..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/resolver/serviceresolver.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server -spec: - redirect: - partition: default - namespace: ns1 - service: static-server \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/api-gateways/tcproute/route.yaml b/acceptance/tests/fixtures/cases/api-gateways/tcproute/route.yaml deleted file mode 100644 index 37602c65af..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/tcproute/route.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: TCPRoute -metadata: - name: tcp-route -spec: - parentRefs: - - name: gateway - rules: - - backendRefs: - - kind: Service - name: static-server-tcp \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml index f3d0bca3ce..efc4746346 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/exportedservices-default apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/exportedservices-default patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/patch.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/patch.yaml index a06855e317..c98ecb6f48 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml index f3d0bca3ce..efc4746346 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/exportedservices-default apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/exportedservices-default patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/patch.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/patch.yaml index 3e86df911d..f826174aec 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml index 77c6bd3fae..08227f89a5 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/exportedservices-secondary apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/exportedservices-secondary patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/patch.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/patch.yaml index 141a080903..d2fc1ab914 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml index 77c6bd3fae..08227f89a5 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/exportedservices-secondary apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/exportedservices-secondary patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/patch.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/patch.yaml index 3b8c2a4068..4165f2d21a 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml index f3d0bca3ce..efc4746346 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/exportedservices-default apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/exportedservices-default patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/patch.yaml index 0031c7f601..eed6bee4cc 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml index f3d0bca3ce..efc4746346 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/exportedservices-default apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/exportedservices-default patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/default/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/default/patch.yaml index e632512ff1..b0dcd89ebb 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml deleted file mode 100644 index f3d0bca3ce..0000000000 --- a/acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../bases/exportedservices-default -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml deleted file mode 100644 index 4512603bcd..0000000000 --- a/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ExportedServices -metadata: - name: default -spec: - services: - - name: static-server - consumers: - - peer: client - - name: static-server-hostname - consumers: - - peer: client diff --git a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml index f3d0bca3ce..efc4746346 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/exportedservices-default apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/exportedservices-default patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/patch.yaml index 300fbcf3d6..4162a0f27b 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/patch.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: diff --git a/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml b/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml index dd39ab3626..4703d23493 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ExportedServices metadata: @@ -10,6 +7,4 @@ spec: - name: frontend namespace: frontend consumers: - - partition: partitionName - - peer: peerName - - samenessGroup: groupName \ No newline at end of file + - partition: other \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml index 3fd5556ebd..cdc3e60cb1 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml @@ -1,9 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/crds-oss -patches: -- path: exportedservices.yaml + - ../../bases/crds-oss + - exportedservices.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml deleted file mode 100644 index 4a3d4f8f3a..0000000000 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/job-client - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml deleted file mode 100644 index 24d58895cf..0000000000 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/patch.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: batch/v1 -kind: Job -metadata: - name: job-client -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - "consul.hashicorp.com/transparent-proxy": "false" - "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" - "consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds": "0" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml deleted file mode 100644 index 4a3d4f8f3a..0000000000 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/job-client - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml deleted file mode 100644 index eb2774bceb..0000000000 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/patch.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: batch/v1 -kind: Job -metadata: - name: job-client -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - "consul.hashicorp.com/transparent-proxy": "false" - "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" - "consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds": "10" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml deleted file mode 100644 index 4a3d4f8f3a..0000000000 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/job-client - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml deleted file mode 100644 index 338dadce18..0000000000 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject/patch.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: batch/v1 -kind: Job -metadata: - name: job-client -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - "consul.hashicorp.com/transparent-proxy": "false" - "consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds": "5" - "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml b/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml deleted file mode 100644 index b38b25696d..0000000000 --- a/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: static-server - namespace: default -spec: - protocol: http - rateLimits: - instanceLevel: - requestsPerSecond: 2 - requestsMaxBurst: 2 - routes: - - pathExact: "/exact" - requestsPerSecond: 3 - requestsMaxBurst: 3 - - pathPrefix: "/prefix" - requestsPerSecond: 4 - - pathRegex: "/regex" - requestsPerSecond: 5 - diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml deleted file mode 100644 index c336a621e7..0000000000 --- a/acceptance/tests/fixtures/cases/permissive-mtls/mesh-config-permissive-allowed.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: Mesh -metadata: - name: mesh -spec: - allowEnablingPermissiveMutualTLS: true diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml deleted file mode 100644 index 4559570544..0000000000 --- a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-permissive.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: static-server - namespace: default -spec: - mutualTLSMode: "permissive" diff --git a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml b/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml deleted file mode 100644 index cf84c73407..0000000000 --- a/acceptance/tests/fixtures/cases/permissive-mtls/service-defaults-static-server-strict.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: static-server - namespace: default -spec: - mutualTLSMode: "strict" diff --git a/acceptance/tests/fixtures/cases/resolver-redirect-virtualip/kustomization.yaml b/acceptance/tests/fixtures/cases/resolver-redirect-virtualip/kustomization.yaml deleted file mode 100644 index 09790e05c6..0000000000 --- a/acceptance/tests/fixtures/cases/resolver-redirect-virtualip/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - ../../bases/resolver-redirect \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml deleted file mode 100644 index 08c7c9b818..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../bases/sameness/peering/acceptor -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/patch.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/patch.yaml deleted file mode 100644 index 2746eeef2e..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: acceptor -spec: - peer: - secret: - name: "cluster-01-a-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml deleted file mode 100644 index 08c7c9b818..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../bases/sameness/peering/acceptor -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/patch.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/patch.yaml deleted file mode 100644 index 9ca48dad0c..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: acceptor -spec: - peer: - secret: - name: "cluster-01-b-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml deleted file mode 100644 index 08c7c9b818..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../bases/sameness/peering/acceptor -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/patch.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/patch.yaml deleted file mode 100644 index 4343992f8f..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: acceptor -spec: - peer: - secret: - name: "cluster-02-a-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml deleted file mode 100644 index 08c7c9b818..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../bases/sameness/peering/acceptor -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/patch.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/patch.yaml deleted file mode 100644 index 1cd49b79d7..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: PeeringAcceptor -metadata: - name: acceptor -spec: - peer: - secret: - name: "cluster-03-a-peering-token" - key: "data" - backend: "kubernetes" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml deleted file mode 100644 index d25bed6eee..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/sameness/exportedservices-ap1 -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml deleted file mode 100644 index 22fa816fed..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/patch.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ExportedServices -metadata: - name: ap1 -spec: - services: - - name: static-server - namespace: ns2 - consumers: - - samenessGroup: group-01 - - name: mesh-gateway - consumers: - - samenessGroup: group-01 diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml deleted file mode 100644 index 7f4ab4ba7c..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/exportedservices-default -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml deleted file mode 100644 index 4dbacf99e1..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/patch.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ExportedServices -metadata: - name: default -spec: - services: - - name: static-server - namespace: ns2 - consumers: - - samenessGroup: group-01 - - name: mesh-gateway - consumers: - - samenessGroup: group-01 diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml deleted file mode 100644 index 096edd19ed..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/static-client -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/patch.yaml deleted file mode 100644 index 68f3c8dd91..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/patch.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - 'consul.hashicorp.com/connect-inject': 'true' - spec: - containers: - - name: static-client - image: anubhavmishra/tiny-tools:latest - # Just spin & wait forever, we'll use `kubectl exec` to demo - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 30; done;'] - # If ACLs are enabled, the serviceAccountName must match the Consul service name. - serviceAccountName: static-client diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml deleted file mode 100644 index 096edd19ed..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/static-client -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/patch.yaml deleted file mode 100644 index c1a14c6070..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/patch.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - 'consul.hashicorp.com/connect-inject': 'true' - 'consul.hashicorp.com/connect-service-upstreams': 'static-server.ns2.ap1:8080' - spec: - containers: - - name: static-client - image: anubhavmishra/tiny-tools:latest - # Just spin & wait forever, we'll use `kubectl exec` to demo - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 30; done;'] - # If ACLs are enabled, the serviceAccountName must match the Consul service name. - serviceAccountName: static-client diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml deleted file mode 100644 index 096edd19ed..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/static-client -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/patch.yaml deleted file mode 100644 index e53ef7b509..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/patch.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - 'consul.hashicorp.com/connect-inject': 'true' - spec: - containers: - - name: static-client - image: anubhavmishra/tiny-tools:latest - # Just spin & wait forever, we'll use `kubectl exec` to demo - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 30; done;'] - # If ACLs are enabled, the serviceAccountName must match the Consul service name. - serviceAccountName: static-client \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml deleted file mode 100644 index 096edd19ed..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/static-client -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/patch.yaml deleted file mode 100644 index 1775e9abb1..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/patch.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - 'consul.hashicorp.com/connect-inject': 'true' - 'consul.hashicorp.com/connect-service-upstreams': 'static-server.ns2.default:8080' - spec: - containers: - - name: static-client - image: anubhavmishra/tiny-tools:latest - # Just spin & wait forever, we'll use `kubectl exec` to demo - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 30; done;'] - # If ACLs are enabled, the serviceAccountName must match the Consul service name. - serviceAccountName: static-client \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml deleted file mode 100644 index e03603d26d..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/static-server -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/patch.yaml deleted file mode 100644 index ca27b7ba42..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/patch.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine - args: - - -text="cluster-01-a" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml deleted file mode 100644 index e03603d26d..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/static-server -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/patch.yaml deleted file mode 100644 index 044115d1d1..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/patch.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine - args: - - -text="cluster-01-b" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml deleted file mode 100644 index e03603d26d..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/static-server -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/patch.yaml deleted file mode 100644 index 07ac3b9aa9..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/patch.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine - args: - - -text="cluster-02-a" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml deleted file mode 100644 index e03603d26d..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../../../bases/static-server -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/patch.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/patch.yaml deleted file mode 100644 index 135e7b14fb..0000000000 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/patch.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine - args: - - -text="cluster-03-a" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml index 564d02a68f..e5cd5f9882 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-inject-multiport/patch.yaml b/acceptance/tests/fixtures/cases/static-client-inject-multiport/patch.yaml index c8b0ae8412..c38ce8e448 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject-multiport/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject-multiport/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml index 564d02a68f..e5cd5f9882 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-inject/patch.yaml b/acceptance/tests/fixtures/cases/static-client-inject/patch.yaml index aea6e8c778..2f8b32ec0a 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml index 564d02a68f..e5cd5f9882 100644 --- a/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-multi-dc/patch.yaml b/acceptance/tests/fixtures/cases/static-client-multi-dc/patch.yaml index 17e635b1d5..7b9b095073 100644 --- a/acceptance/tests/fixtures/cases/static-client-multi-dc/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-multi-dc/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml index 564d02a68f..e5cd5f9882 100644 --- a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-namespaces/patch.yaml b/acceptance/tests/fixtures/cases/static-client-namespaces/patch.yaml index 2ebdbf6d9e..b41a27df3f 100644 --- a/acceptance/tests/fixtures/cases/static-client-namespaces/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-namespaces/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml index 0ae44380dd..5a16e6eaff 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/patch.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/patch.yaml index 4044d78bdf..43507364f8 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml index 0ae44380dd..5a16e6eaff 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/patch.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/patch.yaml index f6a3c50f74..42857c3d2b 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml index 0ae44380dd..5a16e6eaff 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/patch.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/patch.yaml index 720252e833..2fa8ec7e27 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml index 0ae44380dd..5a16e6eaff 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/patch.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/patch.yaml index df04ceccda..f0ceb634bd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml index 0ae44380dd..5a16e6eaff 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/patch.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/patch.yaml index 075cb6ecd4..02ea8993ec 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml index 0ae44380dd..5a16e6eaff 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default/patch.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default/patch.yaml index a6a2f72b37..715485e0f8 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml index 0ae44380dd..5a16e6eaff 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/patch.yaml b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/patch.yaml index 5a7ce34e1b..fd622759a4 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml index 564d02a68f..e5cd5f9882 100644 --- a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../bases/static-client apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../bases/static-client patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/static-client-tproxy/patch.yaml index 8540fc4721..573bf93b01 100644 --- a/acceptance/tests/fixtures/cases/static-client-tproxy/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-client-tproxy/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml index bd2c22ff5f..95b6deaae7 100644 --- a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml @@ -1,9 +1,7 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 +resources: +- ../../bases/static-server apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -resources: -- ../../bases/static-server patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-server-inject/patch.yaml b/acceptance/tests/fixtures/cases/static-server-inject/patch.yaml index 56ef015d8f..f2bf0d6539 100644 --- a/acceptance/tests/fixtures/cases/static-server-inject/patch.yaml +++ b/acceptance/tests/fixtures/cases/static-server-inject/patch.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: apps/v1 kind: Deployment metadata: diff --git a/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml index bd2c22ff5f..bc50c78adf 100644 --- a/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-server -patches: -- path: patch.yaml + - ../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml b/acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml deleted file mode 100644 index 4d00c57dfd..0000000000 --- a/acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../bases/trafficpermissions -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml b/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml deleted file mode 100644 index e1220bcba5..0000000000 --- a/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: auth.consul.hashicorp.com/v2beta1 -kind: TrafficPermissions -metadata: - name: client-to-server -spec: - action: ACTION_DENY diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml deleted file mode 100644 index 564d02a68f..0000000000 --- a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../bases/static-client -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml deleted file mode 100644 index aa96c39398..0000000000 --- a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/mesh-inject": "true" - "consul.hashicorp.com/transparent-proxy": "true" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml deleted file mode 100644 index 564d02a68f..0000000000 --- a/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../bases/static-client -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml deleted file mode 100644 index 41b3f192f8..0000000000 --- a/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/mesh-inject": "true" - "consul.hashicorp.com/mesh-service-destinations": "web.port.multiport.svc:1234,admin.port.multiport.svc:2345" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml deleted file mode 100644 index 8fa56a3448..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/static-server - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml deleted file mode 100644 index c4f181ce7d..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/kschoche/http-echo:latest - args: - - -text="ns2" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml deleted file mode 100644 index 8fa56a3448..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/static-server - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml deleted file mode 100644 index 60c1219e33..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/kschoche/http-echo:latest - args: - - -text="dc1" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml deleted file mode 100644 index 8fa56a3448..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/static-server - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml deleted file mode 100644 index b167f50c9a..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/kschoche/http-echo:latest - args: - - -text="dc2" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml deleted file mode 100644 index 6be0f308c5..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/service-resolver - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml deleted file mode 100644 index e89156f605..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server -spec: - connectTimeout: 15s - failover: - '*': - targets: - - datacenter: "dc2" - - namespace: "ns2" - diff --git a/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml deleted file mode 100644 index 583889f5d8..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/static-client - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml deleted file mode 100644 index f2f8981601..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - 'consul.hashicorp.com/connect-inject': 'true' - "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" - spec: - containers: - - name: static-client - image: anubhavmishra/tiny-tools:latest - # Just spin & wait forever, we'll use `kubectl exec` to demo - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 30; done;'] - # If ACLs are enabled, the serviceAccountName must match the Consul service name. - serviceAccountName: static-client \ No newline at end of file diff --git a/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go b/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go index 9edb1db010..b713620f1e 100644 --- a/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go +++ b/acceptance/tests/ingress-gateway/ingress_gateway_namespaces_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package ingressgateway import ( @@ -69,7 +66,7 @@ func TestIngressGatewaySingleNamespace(t *testing.T) { logger.Logf(t, "creating Kubernetes namespace %s", testNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", testNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", testNamespace) }) @@ -80,12 +77,12 @@ func TestIngressGatewaySingleNamespace(t *testing.T) { } logger.Logf(t, "creating server in %s namespace", testNamespace) - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // We use the static-client pod so that we can make calls to the ingress gateway // via kubectl exec without needing a route into the cluster from the test machine. logger.Logf(t, "creating static-client in %s namespace", testNamespace) - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") // With the cluster up, we can create our ingress-gateway config entry. logger.Log(t, "creating config entry") @@ -188,7 +185,7 @@ func TestIngressGatewayNamespaceMirroring(t *testing.T) { logger.Logf(t, "creating Kubernetes namespace %s", testNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", testNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", testNamespace) }) @@ -199,12 +196,12 @@ func TestIngressGatewayNamespaceMirroring(t *testing.T) { } logger.Logf(t, "creating server in %s namespace", testNamespace) - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // We use the static-client pod so that we can make calls to the ingress gateway // via kubectl exec without needing a route into the cluster from the test machine. logger.Logf(t, "creating static-client in %s namespace", testNamespace) - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) diff --git a/acceptance/tests/ingress-gateway/ingress_gateway_test.go b/acceptance/tests/ingress-gateway/ingress_gateway_test.go index d2b3d7193e..b6535439c1 100644 --- a/acceptance/tests/ingress-gateway/ingress_gateway_test.go +++ b/acceptance/tests/ingress-gateway/ingress_gateway_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package ingressgateway import ( @@ -52,12 +49,12 @@ func TestIngressGateway(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating server") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") // We use the static-client pod so that we can make calls to the ingress gateway // via kubectl exec without needing a route into the cluster from the test machine. logger.Log(t, "creating static-client pod") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") // With the cluster up, we can create our ingress-gateway config entry. logger.Log(t, "creating config entry") diff --git a/acceptance/tests/ingress-gateway/main_test.go b/acceptance/tests/ingress-gateway/main_test.go index 4a74d2f361..bd927f96cb 100644 --- a/acceptance/tests/ingress-gateway/main_test.go +++ b/acceptance/tests/ingress-gateway/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package ingressgateway import ( diff --git a/acceptance/tests/mesh_v2/main_test.go b/acceptance/tests/mesh_v2/main_test.go deleted file mode 100644 index d510056a10..0000000000 --- a/acceptance/tests/mesh_v2/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package mesh_v2 - -import ( - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) -} diff --git a/acceptance/tests/mesh_v2/mesh_inject_test.go b/acceptance/tests/mesh_v2/mesh_inject_test.go deleted file mode 100644 index d54229d84b..0000000000 --- a/acceptance/tests/mesh_v2/mesh_inject_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package mesh_v2 - -import ( - "context" - "fmt" - "strconv" - "testing" - "time" - - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" -) - -const multiport = "multiport" - -// Test that mesh sidecar proxies work for an application with multiple ports. The multiport application is a Pod listening on -// two ports. This tests inbound connections to each port of the multiport app, and outbound connections from the -// multiport app to static-server. -func TestMeshInject_MultiportService(t *testing.T) { - for _, secure := range []bool{false, true} { - name := fmt.Sprintf("secure: %t", secure) - - t.Run(name, func(t *testing.T) { - cfg := suite.Config() - cfg.SkipWhenOpenshiftAndCNI(t) - ctx := suite.Environment().DefaultContext(t) - - helmValues := map[string]string{ - "global.experiments[0]": "resource-apis", - "global.image": "ndhanushkodi/consul-dev:expose2", - // The UI is not supported for v2 in 1.17, so for now it must be disabled. - "ui.enabled": "false", - "connectInject.enabled": "true", - // Enable DNS so we can test that DNS redirection _isn't_ set in the pod. - "dns.enabled": "true", - - "global.tls.enabled": strconv.FormatBool(secure), - "global.acls.manageSystemACLs": strconv.FormatBool(secure), - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - - consulCluster.Create(t) - - consulClient, _ := consulCluster.SetupConsulClient(t, secure) - - // Check that the ACL token is deleted. - if secure { - // We need to register the cleanup function before we create the deployments - // because golang will execute them in reverse order i.e. the last registered - // cleanup function will be executed first. - t.Cleanup(func() { - retrier := &retry.Timer{Timeout: 5 * time.Minute, Wait: 1 * time.Second} - retry.RunWith(retrier, t, func(r *retry.R) { - tokens, _, err := consulClient.ACL().TokenList(nil) - require.NoError(r, err) - for _, token := range tokens { - require.NotContains(r, token.Description, multiport) - require.NotContains(r, token.Description, connhelper.StaticClientName) - } - }) - }) - } - - logger.Log(t, "creating multiport static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/bases/v2-multiport-app") - if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/v2-static-client-inject-tproxy") - } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/v2-static-client-inject") - } - - // Check that static-client has been injected and now has 2 containers. - podList, err := ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: "app=static-client", - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - - // Check that multiport has been injected and now has 3 containers. - podList, err = ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: "app=multiport", - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 3) - - if !secure { - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/cases/trafficpermissions-deny") - } - - // Now test that traffic is denied between the source and the destination. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") - } else { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") - } - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") - }) - - // TODO: add a trafficpermission to a particular port and validate - - // Check connection from static-client to multiport. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://multiport:8080") - } else { - k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://localhost:1234") - } - - // Check connection from static-client to multiport-admin. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://multiport:9090") - } else { - k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://localhost:2345") - } - - // Test that kubernetes readiness status is synced to Consul. This will make the multi port pods unhealthy - // and check inbound connections to the multi port pods' services. - // Create the files so that the readiness probes of the multi port pod fails. - logger.Log(t, "testing k8s -> consul health checks sync by making the multiport unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport", "--", "touch", "/tmp/unhealthy-multiport") - logger.Log(t, "testing k8s -> consul health checks sync by making the multiport-admin unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport-admin", "--", "touch", "/tmp/unhealthy-multiport-admin") - - // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry - // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. - // We are expecting a "connection reset by peer" error because in a case of health checks, - // there will be no healthy proxy host to connect to. That's why we can't assert that we receive an empty reply - // from server, which is the case when a connection is unsuccessful due to intentions in other tests. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") - } else { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") - } - }) - } -} diff --git a/acceptance/tests/metrics/main_test.go b/acceptance/tests/metrics/main_test.go index d3c15b811b..8717c7c4b5 100644 --- a/acceptance/tests/metrics/main_test.go +++ b/acceptance/tests/metrics/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package metrics import ( diff --git a/acceptance/tests/metrics/metrics_test.go b/acceptance/tests/metrics/metrics_test.go index ec2c4c48dc..5d05e45aa4 100644 --- a/acceptance/tests/metrics/metrics_test.go +++ b/acceptance/tests/metrics/metrics_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package metrics import ( @@ -71,15 +68,15 @@ func TestComponentMetrics(t *testing.T) { // This simulates queries that would be made by a prometheus server that runs externally to the consul // components in the cluster. logger.Log(t, "creating static-client") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") // Server Metrics - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:8500/v1/agent/metrics?format=prometheus", fmt.Sprintf("%s-consul-server.%s.svc", releaseName, ns))) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:8500/v1/agent/metrics?format=prometheus", fmt.Sprintf("%s-consul-server.%s.svc", releaseName, ns))) require.NoError(t, err) require.Contains(t, metricsOutput, `consul_acl_ResolveToken{quantile="0.5"}`) // Client Metrics - metricsOutput, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "sh", "-c", "curl --silent --show-error http://$HOST_IP:8500/v1/agent/metrics?format=prometheus") + metricsOutput, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "sh", "-c", "curl --silent --show-error http://$HOST_IP:8500/v1/agent/metrics?format=prometheus") require.NoError(t, err) require.Contains(t, metricsOutput, `consul_acl_ResolveToken{quantile="0.5"}`) @@ -116,13 +113,13 @@ func TestAppMetrics(t *testing.T) { // Deploy service that will emit app and envoy metrics at merged metrics endpoint logger.Log(t, "creating static-metrics-app") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-metrics-app") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-metrics-app") // Create the static-client deployment so we can use it for in-cluster calls to metrics endpoints. // This simulates queries that would be made by a prometheus server that runs externally to the consul // components in the cluster. logger.Log(t, "creating static-client") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-client") // Merged App Metrics podList, err := ctx.KubernetesClient(t).CoreV1().Pods(ns).List(context.Background(), metav1.ListOptions{LabelSelector: "app=static-metrics-app"}) @@ -132,8 +129,8 @@ func TestAppMetrics(t *testing.T) { // Retry because sometimes the merged metrics server takes a couple hundred milliseconds // to start. - retry.RunWith(&retry.Counter{Count: 20, Wait: 2 * time.Second}, t, func(r *retry.R) { - metricsOutput, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) + retry.RunWith(&retry.Counter{Count: 3, Wait: 1 * time.Second}, t, func(r *retry.R) { + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) require.NoError(r, err) // This assertion represents the metrics from the envoy sidecar. require.Contains(r, metricsOutput, `envoy_cluster_assignment_stale{local_cluster="server",consul_source_service="server"`) @@ -147,7 +144,7 @@ func assertGatewayMetricsEnabled(t *testing.T, ctx environment.TestContext, ns, require.NoError(t, err) for _, pod := range pods.Items { podIP := pod.Status.PodIP - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) require.NoError(t, err) require.Contains(t, metricsOutput, metricsAssertion) } diff --git a/acceptance/tests/partitions/main_test.go b/acceptance/tests/partitions/main_test.go index 8c4bd7e2a5..2052a1b74e 100644 --- a/acceptance/tests/partitions/main_test.go +++ b/acceptance/tests/partitions/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package partitions import ( @@ -21,7 +18,7 @@ func TestMain(m *testing.M) { os.Exit(suite.Run()) } else { fmt.Println(fmt.Sprintf("Skipping partitions tests because either -enable-multi-cluster is "+ - "not set or the number of clusters, %d, did not match the expected count of %d", len(suite.Config().KubeEnvs), expectedNumberOfClusters)) + "not set or the number of clusters did not match the expected count of %d", expectedNumberOfClusters)) os.Exit(0) } } diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index 73112761fc..ae2f0688f6 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package partitions import ( @@ -30,11 +27,6 @@ func TestPartitions_Connect(t *testing.T) { env := suite.Environment() cfg := suite.Config() - // TODO: We are monitoring that NET-5819 is fixed, if this test is still flaking in CNI, re-enable this skip - //if cfg.EnableCNI { - // t.Skipf("TODO(flaky): NET-5819") - //} - if !cfg.EnableEnterprise { t.Skipf("skipping this test because -enable-enterprise is not set") } @@ -113,7 +105,6 @@ func TestPartitions_Connect(t *testing.T) { "dns.enableRedirection": strconv.FormatBool(cfg.EnableTransparentProxy), } - // Setup the default partition defaultPartitionHelmValues := make(map[string]string) // On Kind, there are no load balancers but since all clusters @@ -135,7 +126,6 @@ func TestPartitions_Connect(t *testing.T) { serverConsulCluster := consul.NewHelmCluster(t, defaultPartitionHelmValues, defaultPartitionClusterContext, cfg, releaseName) serverConsulCluster.Create(t) - // Copy secrets from the default partition to the secondary partition // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) @@ -153,7 +143,7 @@ func TestPartitions_Connect(t *testing.T) { k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryPartitionClusterContext) - // Create secondary partition cluster. + // Create client cluster. secondaryPartitionHelmValues := map[string]string{ "global.enabled": "false", @@ -211,14 +201,14 @@ func TestPartitions_Connect(t *testing.T) { logger.Logf(t, "creating namespaces %s and %s in servers cluster", staticServerNamespace, StaticClientNamespace) k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) }) logger.Logf(t, "creating namespaces %s and %s in clients cluster", staticServerNamespace, StaticClientNamespace) k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) }) @@ -278,37 +268,37 @@ func TestPartitions_Connect(t *testing.T) { kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) }) k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) }) // This section of the tests runs the in-partition networking tests. t.Run("in-partition", func(t *testing.T) { logger.Log(t, "test in-partition networking") logger.Log(t, "creating static-server and static-client deployments in server cluster") - k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } else { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") } } logger.Log(t, "creating static-server and static-client deployments in client cluster") - k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } else { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") } } // Check that both static-server and static-client have been injected and now have 2 containers in server cluster. @@ -390,7 +380,7 @@ func TestPartitions_Connect(t *testing.T) { require.NoError(t, err) _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: secondaryPartition}) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { _, err := consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: defaultPartition}) require.NoError(t, err) _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: secondaryPartition}) @@ -410,8 +400,8 @@ func TestPartitions_Connect(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") - k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. @@ -431,25 +421,25 @@ func TestPartitions_Connect(t *testing.T) { t.Run("cross-partition", func(t *testing.T) { logger.Log(t, "test cross-partition networking") logger.Log(t, "creating static-server and static-client deployments in server cluster") - k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/default-ns-partition") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/default-ns-partition") } else { - k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/ns-partition") + k8s.DeployKustomize(t, defaultPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/ns-partition") } } logger.Log(t, "creating static-server and static-client deployments in client cluster") - k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/default-ns-default-partition") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/default-ns-default-partition") } else { - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/ns-default-partition") + k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-partitions/ns-default-partition") } } // Check that both static-server and static-client have been injected and now have 2 containers in server cluster. @@ -503,14 +493,14 @@ func TestPartitions_Connect(t *testing.T) { if c.destinationNamespace == defaultNamespace { k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-default") k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/secondary-partition-default") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-default") k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/secondary-partition-default") }) } else { k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/secondary-partition-ns1") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/secondary-partition-ns1") }) @@ -558,7 +548,7 @@ func TestPartitions_Connect(t *testing.T) { intention.Sources[0].Partition = defaultPartition _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: secondaryPartition}) require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { _, err := consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: defaultPartition}) require.NoError(t, err) _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: secondaryPartition}) @@ -583,8 +573,8 @@ func TestPartitions_Connect(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") - k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/partitions/partitions_gateway_test.go b/acceptance/tests/partitions/partitions_gateway_test.go deleted file mode 100644 index a90a790cb6..0000000000 --- a/acceptance/tests/partitions/partitions_gateway_test.go +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package partitions - -import ( - "context" - "fmt" - "strconv" - "testing" - "time" - - terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// Test that Gateway works in a default and ACLsEnabled installations for X-Partition and in-partition networking. -func TestPartitions_Gateway(t *testing.T) { - env := suite.Environment() - cfg := suite.Config() - - if !cfg.EnableEnterprise { - t.Skipf("skipping this test because -enable-enterprise is not set") - } - - const defaultPartition = "default" - const secondaryPartition = "secondary" - - defaultPartitionClusterContext := env.DefaultContext(t) - secondaryPartitionClusterContext := env.Context(t, 1) - - commonHelmValues := map[string]string{ - "global.adminPartitions.enabled": "true", - "global.enableConsulNamespaces": "true", - "global.logLevel": "debug", - - "global.tls.enabled": "true", - "global.tls.httpsOnly": "true", - - "global.acls.manageSystemACLs": "true", - - "connectInject.enabled": "true", - // When mirroringK8S is set, this setting is ignored. - "connectInject.consulNamespaces.consulDestinationNamespace": staticServerNamespace, - "connectInject.consulNamespaces.mirroringK8S": "true", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - - "dns.enabled": "true", - "dns.enableRedirection": strconv.FormatBool(cfg.EnableTransparentProxy), - } - - defaultPartitionHelmValues := make(map[string]string) - - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - defaultPartitionHelmValues["meshGateway.service.type"] = "NodePort" - defaultPartitionHelmValues["meshGateway.service.nodePort"] = "30200" // todo: do we need to set this port? - defaultPartitionHelmValues["server.exposeService.type"] = "NodePort" - defaultPartitionHelmValues["server.exposeService.nodePort.https"] = "30000" - defaultPartitionHelmValues["server.exposeService.nodePort.grpc"] = "30100" - } - - releaseName := helpers.RandomName() - - helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) - - // Install the consul cluster with servers in the default kubernetes context. - serverConsulCluster := consul.NewHelmCluster(t, defaultPartitionHelmValues, defaultPartitionClusterContext, cfg, releaseName) - serverConsulCluster.Create(t) - - // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. - caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) - - logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) - k8s.CopySecret(t, defaultPartitionClusterContext, secondaryPartitionClusterContext, caCertSecretName) - - partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) - logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) - k8s.CopySecret(t, defaultPartitionClusterContext, secondaryPartitionClusterContext, partitionToken) - - partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) - partitionSvcAddress := k8s.ServiceHost(t, cfg, defaultPartitionClusterContext, partitionServiceName) - - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryPartitionClusterContext) - - // Create client cluster. - secondaryPartitionHelmValues := map[string]string{ - "global.enabled": "false", - - "global.adminPartitions.name": secondaryPartition, - - "global.tls.caCert.secretName": caCertSecretName, - "global.tls.caCert.secretKey": "tls.crt", - - "externalServers.enabled": "true", - "externalServers.hosts[0]": partitionSvcAddress, - "externalServers.tlsServerName": "server.dc1.consul", - } - - // Setup partition token and auth method host since ACLs enabled. - secondaryPartitionHelmValues["global.acls.bootstrapToken.secretName"] = partitionToken - secondaryPartitionHelmValues["global.acls.bootstrapToken.secretKey"] = "token" - secondaryPartitionHelmValues["externalServers.k8sAuthMethodHost"] = k8sAuthMethodHost - - if cfg.UseKind { - secondaryPartitionHelmValues["externalServers.httpsPort"] = "30000" - secondaryPartitionHelmValues["externalServers.grpcPort"] = "30100" - secondaryPartitionHelmValues["meshGateway.service.type"] = "NodePort" - secondaryPartitionHelmValues["meshGateway.service.nodePort"] = "30200" - } - - helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) - - // Install the consul cluster without servers in the client cluster kubernetes context. - clientConsulCluster := consul.NewHelmCluster(t, secondaryPartitionHelmValues, secondaryPartitionClusterContext, cfg, releaseName) - clientConsulCluster.Create(t) - - defaultPartitionClusterStaticServerOpts := &terratestk8s.KubectlOptions{ - ContextName: defaultPartitionClusterContext.KubectlOptions(t).ContextName, - ConfigPath: defaultPartitionClusterContext.KubectlOptions(t).ConfigPath, - Namespace: staticServerNamespace, - } - secondaryPartitionClusterStaticServerOpts := &terratestk8s.KubectlOptions{ - ContextName: secondaryPartitionClusterContext.KubectlOptions(t).ContextName, - ConfigPath: secondaryPartitionClusterContext.KubectlOptions(t).ConfigPath, - Namespace: staticServerNamespace, - } - secondaryPartitionClusterStaticClientOpts := &terratestk8s.KubectlOptions{ - ContextName: secondaryPartitionClusterContext.KubectlOptions(t).ContextName, - ConfigPath: secondaryPartitionClusterContext.KubectlOptions(t).ConfigPath, - Namespace: StaticClientNamespace, - } - - logger.Logf(t, "creating namespaces %s and %s in servers cluster", staticServerNamespace, StaticClientNamespace) - k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, defaultPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) - }) - - logger.Logf(t, "creating namespaces %s and %s in clients cluster", staticServerNamespace, StaticClientNamespace) - k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, secondaryPartitionClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace, StaticClientNamespace) - }) - - consulClient, _ := serverConsulCluster.SetupConsulClient(t, true) - - serverQueryServerOpts := &api.QueryOptions{Namespace: staticServerNamespace, Partition: defaultPartition} - clientQueryServerOpts := &api.QueryOptions{Namespace: StaticClientNamespace, Partition: defaultPartition} - - serverQueryClientOpts := &api.QueryOptions{Namespace: staticServerNamespace, Partition: secondaryPartition} - clientQueryClientOpts := &api.QueryOptions{Namespace: StaticClientNamespace, Partition: secondaryPartition} - - // We need to register the cleanup function before we create the deployments - // because golang will execute them in reverse order i.e. the last registered - // cleanup function will be executed first. - t.Cleanup(func() { - retry.Run(t, func(r *retry.R) { - tokens, _, err := consulClient.ACL().TokenList(serverQueryServerOpts) - require.NoError(r, err) - for _, token := range tokens { - require.NotContains(r, token.Description, staticServerName) - } - - tokens, _, err = consulClient.ACL().TokenList(clientQueryServerOpts) - require.NoError(r, err) - for _, token := range tokens { - require.NotContains(r, token.Description, StaticClientName) - } - tokens, _, err = consulClient.ACL().TokenList(serverQueryClientOpts) - require.NoError(r, err) - for _, token := range tokens { - require.NotContains(r, token.Description, staticServerName) - } - - tokens, _, err = consulClient.ACL().TokenList(clientQueryClientOpts) - require.NoError(r, err) - for _, token := range tokens { - require.NotContains(r, token.Description, StaticClientName) - } - }) - }) - - // Create a ProxyDefaults resource to configure services to use the mesh - // gateways. - logger.Log(t, "creating proxy-defaults config") - kustomizeDir := "../fixtures/cases/api-gateways/mesh" - - k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), kustomizeDir) - }) - - k8s.KubectlApplyK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, secondaryPartitionClusterContext.KubectlOptions(t), kustomizeDir) - }) - - // We use the static-client pod so that we can make calls to the api gateway - // via kubectl exec without needing a route into the cluster from the test machine. - // Since we're deploying the gateway in the secondary cluster, we create the static client - // in the secondary as well. - logger.Log(t, "creating static-client pod in secondary partition cluster") - k8s.DeployKustomize(t, secondaryPartitionClusterStaticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") - - logger.Log(t, "creating api-gateway resources") - out, err := k8s.RunKubectlAndGetOutputE(t, secondaryPartitionClusterStaticServerOpts, "apply", "-k", "../fixtures/bases/api-gateway") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, secondaryPartitionClusterStaticServerOpts, "delete", "-k", "../fixtures/bases/api-gateway") - }) - - // Grab a kubernetes client so that we can verify binding - // behavior prior to issuing requests through the gateway. - k8sClient := secondaryPartitionClusterContext.ControllerRuntimeClient(t) - - // On startup, the controller can take upwards of 1m to perform - // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 1m timeout here). - var gatewayAddress string - counter := &retry.Counter{Count: 600, Wait: 2 * time.Second} - retry.RunWith(counter, t, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: staticServerNamespace}, &gateway) - require.NoError(r, err) - - // check that we have an address to use - require.Len(r, gateway.Status.Addresses, 1) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.Status.Addresses[0].Value - }) - - targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) - - // This section of the tests runs the in-partition networking tests. - t.Run("in-partition", func(t *testing.T) { - logger.Log(t, "test in-partition networking") - logger.Log(t, "creating target server in secondary partition cluster") - k8s.DeployKustomize(t, secondaryPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - // Check that static-server injected 2 containers. - for _, labelSelector := range []string{"app=static-server"} { - podList, err := secondaryPartitionClusterContext.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{ - LabelSelector: labelSelector, - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - } - - logger.Log(t, "patching route to target server") - k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") - - logger.Log(t, "checking that the connection is not successful because there's no intention") - k8s.CheckStaticServerHTTPConnectionFailing(t, secondaryPartitionClusterStaticClientOpts, StaticClientName, targetAddress) - - intention := &api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: staticServerName, - Namespace: staticServerNamespace, - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Namespace: staticServerNamespace, - Action: api.IntentionActionAllow, - }, - }, - } - - logger.Log(t, "creating intention") - _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: secondaryPartition}) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: secondaryPartition}) - require.NoError(t, err) - }) - - logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, secondaryPartitionClusterStaticClientOpts, StaticClientName, targetAddress) - }) - - // This section of the tests runs the cross-partition networking tests. - t.Run("cross-partition", func(t *testing.T) { - logger.Log(t, "test cross-partition networking") - - logger.Log(t, "creating target server in default partition cluster") - k8s.DeployKustomize(t, defaultPartitionClusterStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - // Check that static-server injected 2 containers. - for _, labelSelector := range []string{"app=static-server"} { - podList, err := defaultPartitionClusterContext.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{ - LabelSelector: labelSelector, - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - } - - logger.Log(t, "creating exported services") - k8s.KubectlApplyK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, defaultPartitionClusterContext.KubectlOptions(t), "../fixtures/cases/crd-partitions/default-partition-ns1") - }) - - logger.Log(t, "creating local service resolver") - k8s.KubectlApplyK(t, secondaryPartitionClusterStaticServerOpts, "../fixtures/cases/api-gateways/resolver") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, secondaryPartitionClusterStaticServerOpts, "../fixtures/cases/api-gateways/resolver") - }) - - logger.Log(t, "patching route to target server") - k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") - - logger.Log(t, "checking that the connection is not successful because there's no intention") - k8s.CheckStaticServerHTTPConnectionFailing(t, secondaryPartitionClusterStaticClientOpts, StaticClientName, targetAddress) - - intention := &api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: staticServerName, - Namespace: staticServerNamespace, - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Namespace: staticServerNamespace, - Action: api.IntentionActionAllow, - Partition: secondaryPartition, - }, - }, - } - - logger.Log(t, "creating intention") - _, _, err = consulClient.ConfigEntries().Set(intention, &api.WriteOptions{Partition: defaultPartition}) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - _, err = consulClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{Partition: defaultPartition}) - require.NoError(t, err) - }) - - logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, secondaryPartitionClusterStaticClientOpts, StaticClientName, targetAddress) - }) -} diff --git a/acceptance/tests/partitions/partitions_sync_test.go b/acceptance/tests/partitions/partitions_sync_test.go index da95d7f272..8d761d0765 100644 --- a/acceptance/tests/partitions/partitions_sync_test.go +++ b/acceptance/tests/partitions/partitions_sync_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package partitions import ( @@ -195,13 +192,13 @@ func TestPartitions_Sync(t *testing.T) { logger.Logf(t, "creating namespaces %s in servers cluster", staticServerNamespace) k8s.RunKubectl(t, primaryClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, primaryClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) logger.Logf(t, "creating namespaces %s in clients cluster", staticServerNamespace) k8s.RunKubectl(t, secondaryClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, secondaryClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) @@ -241,13 +238,13 @@ func TestPartitions_Sync(t *testing.T) { logger.Log(t, "creating a static-server with a service") // create service in default partition. - k8s.DeployKustomize(t, primaryStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, primaryStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") // create service in secondary partition. - k8s.DeployKustomize(t, secondaryStaticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, secondaryStaticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") logger.Log(t, "checking that the service has been synced to Consul") var services map[string][]string - counter := &retry.Counter{Count: 30, Wait: 30 * time.Second} + counter := &retry.Counter{Count: 20, Wait: 30 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { var err error // list services in default partition catalog. diff --git a/acceptance/tests/peering/main_test.go b/acceptance/tests/peering/main_test.go index 075051861d..df9812171c 100644 --- a/acceptance/tests/peering/main_test.go +++ b/acceptance/tests/peering/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package peering import ( diff --git a/acceptance/tests/peering/peering_connect_namespaces_test.go b/acceptance/tests/peering/peering_connect_namespaces_test.go index 473e4976be..b52d2793d0 100644 --- a/acceptance/tests/peering/peering_connect_namespaces_test.go +++ b/acceptance/tests/peering/peering_connect_namespaces_test.go @@ -1,13 +1,9 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package peering import ( "context" "fmt" "strconv" - "sync" "testing" "time" @@ -117,77 +113,62 @@ func TestPeering_ConnectNamespaces(t *testing.T) { "dns.enableRedirection": strconv.FormatBool(cfg.EnableTransparentProxy), } - var wg sync.WaitGroup - releaseName := helpers.RandomName() - - var staticServerPeerCluster *consul.HelmCluster - wg.Add(1) - go func() { - defer wg.Done() - staticServerPeerHelmValues := map[string]string{ - "global.datacenter": staticServerPeer, - } + staticServerPeerHelmValues := map[string]string{ + "global.datacenter": staticServerPeer, + } - if !cfg.UseKind { - staticServerPeerHelmValues["server.replicas"] = "3" - } + if !cfg.UseKind { + staticServerPeerHelmValues["server.replicas"] = "3" + } - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } - helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) + releaseName := helpers.RandomName() - // Install the first peer where static-server will be deployed in the static-server kubernetes context. - staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) - staticServerPeerCluster.Create(t) - }() + helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) - var staticClientPeerCluster *consul.HelmCluster - wg.Add(1) - go func() { - defer wg.Done() - staticClientPeerHelmValues := map[string]string{ - "global.datacenter": staticClientPeer, - } + // Install the first peer where static-server will be deployed in the static-server kubernetes context. + staticServerPeerCluster := consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) + staticServerPeerCluster.Create(t) - if !cfg.UseKind { - staticClientPeerHelmValues["server.replicas"] = "3" - } + staticClientPeerHelmValues := map[string]string{ + "global.datacenter": staticClientPeer, + } - if cfg.UseKind { - staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } + if !cfg.UseKind { + staticClientPeerHelmValues["server.replicas"] = "3" + } - helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) + if cfg.UseKind { + staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } - // Install the second peer where static-client will be deployed in the static-client kubernetes context. - staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) - staticClientPeerCluster.Create(t) - }() + helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) - // Wait for the clusters to start up - logger.Log(t, "waiting for clusters to start up . . .") - wg.Wait() + // Install the second peer where static-client will be deployed in the static-client kubernetes context. + staticClientPeerCluster := consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) + staticClientPeerCluster.Create(t) // Create Mesh resource to use mesh gateways. logger.Log(t, "creating mesh config") kustomizeMeshDir := "../fixtures/bases/mesh-peering" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) @@ -214,13 +195,13 @@ func TestPeering_ConnectNamespaces(t *testing.T) { // Create the peering acceptor on the client peer. k8s.KubectlApply(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDelete(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") }) // Ensure the secret is created. retry.RunWith(timer, t, func(r *retry.R) { - acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(r, staticClientPeerClusterContext.KubectlOptions(r), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") + acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(t, staticClientPeerClusterContext.KubectlOptions(t), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") require.NoError(r, err) require.NotEmpty(r, acceptorSecretName) }) @@ -230,7 +211,7 @@ func TestPeering_ConnectNamespaces(t *testing.T) { // Create the peering dialer on the server peer. k8s.KubectlApply(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "secret", "api-token") k8s.KubectlDelete(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") }) @@ -248,13 +229,13 @@ func TestPeering_ConnectNamespaces(t *testing.T) { logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) }) @@ -272,26 +253,26 @@ func TestPeering_ConnectNamespaces(t *testing.T) { kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) }) logger.Log(t, "creating static-server in server peer") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client deployments in client peer") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { if c.destinationNamespace == defaultNamespace { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/default-namespace") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/default-namespace") } else { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/non-default-namespace") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/non-default-namespace") } } // Check that both static-server and static-client have been injected and now have 2 containers. @@ -328,12 +309,12 @@ func TestPeering_ConnectNamespaces(t *testing.T) { logger.Log(t, "creating exported services") if c.destinationNamespace == defaultNamespace { k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/default-namespace") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/default-namespace") }) } else { k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") }) } diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index 6bab0aa909..470ada08a1 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -1,13 +1,9 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package peering import ( "context" "fmt" "strconv" - "sync" "testing" "time" @@ -16,7 +12,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - terminatinggateway "github.com/hashicorp/consul-k8s/acceptance/tests/terminating-gateway" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-version" @@ -24,12 +19,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// TestPeering_Connect validates that service mesh works properly across two peered clusters. -// It deploys a static client in one cluster and a static server in another and checks that requests from the client -// can reach the server. -// It also deploys a static server pod that is not connected to the mesh, but added as a -// destination for a terminating gateway. It checks that requests from the client can reach this server through the -// terminating gateway via the mesh gateways. +// Test that Connect works in installations for X-Peers networking. func TestPeering_Connect(t *testing.T) { env := suite.Environment() cfg := suite.Config() @@ -42,7 +32,6 @@ func TestPeering_Connect(t *testing.T) { const staticServerPeer = "server" const staticClientPeer = "client" - cases := []struct { name string ACLsEnabled bool @@ -62,7 +51,6 @@ func TestPeering_Connect(t *testing.T) { staticServerPeerClusterContext := env.DefaultContext(t) staticClientPeerClusterContext := env.Context(t, 1) - // Create Clusters starting with our first cluster commonHelmValues := map[string]string{ "global.peering.enabled": "true", @@ -80,81 +68,62 @@ func TestPeering_Connect(t *testing.T) { "dns.enableRedirection": strconv.FormatBool(cfg.EnableTransparentProxy), } - var wg sync.WaitGroup - releaseName := helpers.RandomName() - - var staticServerPeerCluster *consul.HelmCluster - wg.Add(1) - go func() { - defer wg.Done() - staticServerPeerHelmValues := map[string]string{ - "global.datacenter": staticServerPeer, - "terminatingGateways.enabled": "true", - "terminatingGateways.gateways[0].name": "terminating-gateway", - "terminatingGateways.gateways[0].replicas": "1", - } + staticServerPeerHelmValues := map[string]string{ + "global.datacenter": staticServerPeer, + } - if !cfg.UseKind { - staticServerPeerHelmValues["server.replicas"] = "3" - } + if !cfg.UseKind { + staticServerPeerHelmValues["server.replicas"] = "3" + } - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } - helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) + releaseName := helpers.RandomName() - // Install the first peer where static-server will be deployed in the static-server kubernetes context. - staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) - staticServerPeerCluster.Create(t) - }() + helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) - var staticClientPeerCluster *consul.HelmCluster - wg.Add(1) - go func() { - defer wg.Done() - // Create a second cluster to be peered with - staticClientPeerHelmValues := map[string]string{ - "global.datacenter": staticClientPeer, - } + // Install the first peer where static-server will be deployed in the static-server kubernetes context. + staticServerPeerCluster := consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) + staticServerPeerCluster.Create(t) - if !cfg.UseKind { - staticClientPeerHelmValues["server.replicas"] = "3" - } + staticClientPeerHelmValues := map[string]string{ + "global.datacenter": staticClientPeer, + } - if cfg.UseKind { - staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } + if !cfg.UseKind { + staticClientPeerHelmValues["server.replicas"] = "3" + } - helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) + if cfg.UseKind { + staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" + staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" + staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" + } - // Install the second peer where static-client will be deployed in the static-client kubernetes context. - staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) - staticClientPeerCluster.Create(t) - }() + helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) - // Wait for the clusters to start up - logger.Log(t, "waiting for clusters to start up . . .") - wg.Wait() + // Install the second peer where static-client will be deployed in the static-client kubernetes context. + staticClientPeerCluster := consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) + staticClientPeerCluster.Create(t) // Create Mesh resource to use mesh gateways. logger.Log(t, "creating mesh config") kustomizeMeshDir := "../fixtures/bases/mesh-peering" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) }) @@ -181,13 +150,13 @@ func TestPeering_Connect(t *testing.T) { // Create the peering acceptor on the client peer. k8s.KubectlApply(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDelete(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") }) // Ensure the secret is created. retry.RunWith(timer, t, func(r *retry.R) { - acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(r, staticClientPeerClusterContext.KubectlOptions(r), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") + acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(t, staticClientPeerClusterContext.KubectlOptions(t), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") require.NoError(r, err) require.NotEmpty(r, acceptorSecretName) }) @@ -197,7 +166,7 @@ func TestPeering_Connect(t *testing.T) { // Create the peering dialer on the server peer. k8s.KubectlApply(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "secret", "api-token") k8s.KubectlDelete(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") }) @@ -213,40 +182,41 @@ func TestPeering_Connect(t *testing.T) { Namespace: staticClientNamespace, } - logger.Logf(t, "creating namespace %s in server peer", staticServerNamespace) + logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) - logger.Logf(t, "creating namespace %s in client peer", staticClientNamespace) + logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) }) - // Create a ProxyDefaults resource to configure services to use the mesh gateways. + // Create a ProxyDefaults resource to configure services to use the mesh + // gateways. logger.Log(t, "creating proxy-defaults config") kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) }) k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) }) logger.Log(t, "creating static-server in server peer") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client deployments in client peer") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/default") + k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/default") } // Check that both static-server and static-client have been injected and now have 2 containers. podList, err := staticServerPeerClusterContext.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{ @@ -276,7 +246,7 @@ func TestPeering_Connect(t *testing.T) { logger.Log(t, "creating exported services") k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/default") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/default") }) @@ -314,70 +284,6 @@ func TestPeering_Connect(t *testing.T) { k8s.CheckStaticServerConnectionSuccessful(t, staticClientOpts, staticClientName, "http://localhost:1234") } - // Check that requests can reach the external static-server from the static client cluster. - if cfg.EnableTransparentProxy { - const ( - externalServerK8sNamespace = "external" - externalServerServiceName = "static-server" - externalServerHostnameID = "static-server-hostname" - terminatingGatewayRules = `service_prefix "static-server" {policy = "write"}` - ) - - // Create the namespace for the "external" static server. - externalServerOpts := &terratestk8s.KubectlOptions{ - ContextName: staticServerOpts.ContextName, - ConfigPath: staticServerOpts.ConfigPath, - Namespace: externalServerK8sNamespace, - } - logger.Logf(t, "creating namespace %s in server peer", externalServerK8sNamespace) - k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", externalServerK8sNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", externalServerK8sNamespace) - }) - - // Create the external server in the server Kubernetes cluster, outside the mesh in the "external" namespace - logger.Log(t, "creating static-server deployment in server peer outside of mesh") - k8s.DeployKustomize(t, externalServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") - - // Prevent dialing the server directly through the sidecar. - terminatinggateway.CreateMeshConfigEntry(t, staticServerPeerClient, "") - terminatinggateway.CreateMeshConfigEntry(t, staticClientPeerClient, "") - - // Create the config entry for the terminating gateway - terminatinggateway.CreateTerminatingGatewayConfigEntry(t, staticServerPeerClient, "", "", externalServerHostnameID) - if c.ACLsEnabled { - // Allow the terminating gateway write access to services prefixed with "static-server". - terminatinggateway.UpdateTerminatingGatewayRole(t, staticServerPeerClient, terminatingGatewayRules) - } - - // This is the URL that the static-client will use to dial the external static server in the server peer. - externalServerHostnameURL := fmt.Sprintf("http://%s.virtual.%s.consul", externalServerHostnameID, staticServerPeer) - - // Register the external service. - terminatinggateway.CreateServiceDefaultDestination(t, staticServerPeerClient, "", externalServerHostnameID, "http", 80, fmt.Sprintf("%s.%s", externalServerServiceName, externalServerK8sNamespace)) - // (t-eckert) this shouldn't be required but currently is with HTTP services. It works around a bug. - helpers.RegisterExternalService(t, staticServerPeerClient, "", externalServerHostnameID, fmt.Sprintf("%s.%s", externalServerServiceName, externalServerK8sNamespace), 80) - - // Export the external service to the client peer. - logger.Log(t, "creating exported external services") - k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/external") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/external") - }) - - // If ACLs are enabled, test that deny intentions prevent connections. - if c.ACLsEnabled { - logger.Log(t, "testing intentions prevent connections through the terminating gateway") - k8s.CheckStaticServerHTTPConnectionFailing(t, staticClientOpts, staticClientName, externalServerHostnameURL) - - logger.Log(t, "adding intentions to allow traffic from client ==> server") - terminatinggateway.AddIntention(t, staticServerPeerClient, staticClientPeer, "", staticClientName, "", externalServerHostnameID) - } - - // Test that we can make a call to the terminating gateway. - logger.Log(t, "trying calls to terminating gateway") - k8s.CheckStaticServerConnectionSuccessful(t, staticClientOpts, staticClientName, externalServerHostnameURL) - } }) } } diff --git a/acceptance/tests/peering/peering_gateway_test.go b/acceptance/tests/peering/peering_gateway_test.go deleted file mode 100644 index 421db98c71..0000000000 --- a/acceptance/tests/peering/peering_gateway_test.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package peering - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/go-version" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/types" - - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestPeering_Gateway(t *testing.T) { - env := suite.Environment() - cfg := suite.Config() - - if !cfg.EnableEnterprise { - t.Skipf("skipping this test because -enable-enterprise is not set") - } - - ver, err := version.NewVersion("1.13.0") - require.NoError(t, err) - if cfg.ConsulVersion != nil && cfg.ConsulVersion.LessThan(ver) { - t.Skipf("skipping this test because peering is not supported in version %v", cfg.ConsulVersion.String()) - } - - const staticServerPeer = "server" - const staticClientPeer = "client" - - staticServerPeerClusterContext := env.DefaultContext(t) - staticClientPeerClusterContext := env.Context(t, 1) - - commonHelmValues := map[string]string{ - "global.peering.enabled": "true", - "global.enableConsulNamespaces": "true", - - "global.tls.enabled": "true", - "global.tls.httpsOnly": "true", - - "global.acls.manageSystemACLs": "true", - - "connectInject.enabled": "true", - - // When mirroringK8S is set, this setting is ignored. - "connectInject.consulNamespaces.mirroringK8S": "true", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - - "dns.enabled": "true", - } - - var wg sync.WaitGroup - releaseName := helpers.RandomName() - - var staticServerPeerCluster *consul.HelmCluster - wg.Add(1) - go func() { - defer wg.Done() - staticServerPeerHelmValues := map[string]string{ - "global.datacenter": staticServerPeer, - } - - if !cfg.UseKind { - staticServerPeerHelmValues["server.replicas"] = "3" - } - - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - staticServerPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticServerPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticServerPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } - - helpers.MergeMaps(staticServerPeerHelmValues, commonHelmValues) - - // Install the first peer where static-server will be deployed in the static-server kubernetes context. - staticServerPeerCluster = consul.NewHelmCluster(t, staticServerPeerHelmValues, staticServerPeerClusterContext, cfg, releaseName) - staticServerPeerCluster.Create(t) - }() - - var staticClientPeerCluster *consul.HelmCluster - wg.Add(1) - go func() { - defer wg.Done() - staticClientPeerHelmValues := map[string]string{ - "global.datacenter": staticClientPeer, - } - - if !cfg.UseKind { - staticClientPeerHelmValues["server.replicas"] = "3" - } - - if cfg.UseKind { - staticClientPeerHelmValues["server.exposeGossipAndRPCPorts"] = "true" - staticClientPeerHelmValues["meshGateway.service.type"] = "NodePort" - staticClientPeerHelmValues["meshGateway.service.nodePort"] = "30100" - } - - helpers.MergeMaps(staticClientPeerHelmValues, commonHelmValues) - - // Install the second peer where static-client will be deployed in the static-client kubernetes context. - staticClientPeerCluster = consul.NewHelmCluster(t, staticClientPeerHelmValues, staticClientPeerClusterContext, cfg, releaseName) - staticClientPeerCluster.Create(t) - }() - - // Wait for the clusters to start up - logger.Log(t, "waiting for clusters to start up . . .") - wg.Wait() - - // Create Mesh resource to use mesh gateways. - logger.Log(t, "creating mesh config") - kustomizeMeshDir := "../fixtures/bases/mesh-peering" - - k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - }) - - k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeMeshDir) - }) - - staticServerPeerClient, _ := staticServerPeerCluster.SetupConsulClient(t, true) - staticClientPeerClient, _ := staticClientPeerCluster.SetupConsulClient(t, true) - - // Ensure mesh config entries are created in Consul. - timer := &retry.Timer{Timeout: 1 * time.Minute, Wait: 1 * time.Second} - retry.RunWith(timer, t, func(r *retry.R) { - ceServer, _, err := staticServerPeerClient.ConfigEntries().Get(api.MeshConfig, "mesh", &api.QueryOptions{}) - require.NoError(r, err) - configEntryServer, ok := ceServer.(*api.MeshConfigEntry) - require.True(r, ok) - require.Equal(r, configEntryServer.GetName(), "mesh") - require.NoError(r, err) - - ceClient, _, err := staticClientPeerClient.ConfigEntries().Get(api.MeshConfig, "mesh", &api.QueryOptions{}) - require.NoError(r, err) - configEntryClient, ok := ceClient.(*api.MeshConfigEntry) - require.True(r, ok) - require.Equal(r, configEntryClient.GetName(), "mesh") - require.NoError(r, err) - }) - - // Create the peering acceptor on the client peer. - k8s.KubectlApply(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDelete(t, staticClientPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-acceptor.yaml") - }) - - // Ensure the secret is created. - retry.RunWith(timer, t, func(r *retry.R) { - acceptorSecretName, err := k8s.RunKubectlAndGetOutputE(r, staticClientPeerClusterContext.KubectlOptions(r), "get", "peeringacceptor", "server", "-o", "jsonpath={.status.secret.name}") - require.NoError(r, err) - require.NotEmpty(r, acceptorSecretName) - }) - - // Copy secret from client peer to server peer. - k8s.CopySecret(t, staticClientPeerClusterContext, staticServerPeerClusterContext, "api-token") - - // Create the peering dialer on the server peer. - k8s.KubectlApply(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "secret", "api-token") - k8s.KubectlDelete(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/bases/peering/peering-dialer.yaml") - }) - - staticServerOpts := &terratestk8s.KubectlOptions{ - ContextName: staticServerPeerClusterContext.KubectlOptions(t).ContextName, - ConfigPath: staticServerPeerClusterContext.KubectlOptions(t).ConfigPath, - Namespace: staticServerNamespace, - } - staticClientOpts := &terratestk8s.KubectlOptions{ - ContextName: staticClientPeerClusterContext.KubectlOptions(t).ContextName, - ConfigPath: staticClientPeerClusterContext.KubectlOptions(t).ConfigPath, - Namespace: staticClientNamespace, - } - - logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) - k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) - }) - - logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) - k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) - }) - - // Create a ProxyDefaults resource to configure services to use the mesh - // gateways. - logger.Log(t, "creating proxy-defaults config") - kustomizeDir := "../fixtures/cases/api-gateways/mesh" - - k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), kustomizeDir) - }) - - k8s.KubectlApplyK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, staticClientPeerClusterContext.KubectlOptions(t), kustomizeDir) - }) - - // We use the static-client pod so that we can make calls to the api gateway - // via kubectl exec without needing a route into the cluster from the test machine. - // Since we're deploying the gateway in the secondary cluster, we create the static client - // in the secondary as well. - logger.Log(t, "creating static-client pod in client peer") - k8s.DeployKustomize(t, staticClientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-peers/non-default-namespace") - - logger.Log(t, "creating static-server in server peer") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - logger.Log(t, "creating exported services") - k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/non-default-namespace") - }) - - logger.Log(t, "creating api-gateway resources in client peer") - out, err := k8s.RunKubectlAndGetOutputE(t, staticClientOpts, "apply", "-k", "../fixtures/bases/api-gateway") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, staticClientOpts, "delete", "-k", "../fixtures/bases/api-gateway") - }) - - // Grab a kubernetes client so that we can verify binding - // behavior prior to issuing requests through the gateway. - k8sClient := staticClientPeerClusterContext.ControllerRuntimeClient(t) - - // On startup, the controller can take upwards of 1m to perform - // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 1m timeout here). - var gatewayAddress string - counter := &retry.Counter{Count: 600, Wait: 2 * time.Second} - retry.RunWith(counter, t, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: staticClientNamespace}, &gateway) - require.NoError(r, err) - - // check that we have an address to use - require.Len(r, gateway.Status.Addresses, 1) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.Status.Addresses[0].Value - }) - - targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) - - logger.Log(t, "creating local service resolver") - k8s.KubectlApplyK(t, staticClientOpts, "../fixtures/cases/api-gateways/peer-resolver") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, staticClientOpts, "../fixtures/cases/api-gateways/peer-resolver") - }) - - logger.Log(t, "patching route to target server") - k8s.RunKubectl(t, staticClientOpts, "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") - - logger.Log(t, "checking that the connection is not successful because there's no intention") - k8s.CheckStaticServerHTTPConnectionFailing(t, staticClientOpts, staticClientName, targetAddress) - - intention := &api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: staticServerName, - Namespace: staticServerNamespace, - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Namespace: staticClientNamespace, - Action: api.IntentionActionAllow, - Peer: staticClientPeer, - }, - }, - } - - logger.Log(t, "creating intention") - _, _, err = staticServerPeerClient.ConfigEntries().Set(intention, &api.WriteOptions{}) - require.NoError(t, err) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - _, err = staticServerPeerClient.ConfigEntries().Delete(api.ServiceIntentions, staticServerName, &api.WriteOptions{}) - require.NoError(t, err) - }) - - logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, staticClientOpts, staticClientName, targetAddress) -} diff --git a/acceptance/tests/sameness/main_test.go b/acceptance/tests/sameness/main_test.go deleted file mode 100644 index ded943c6f0..0000000000 --- a/acceptance/tests/sameness/main_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package sameness - -import ( - "fmt" - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - - expectedNumberOfClusters := 4 - - if suite.Config().EnableMultiCluster && suite.Config().IsExpectedClusterCount(expectedNumberOfClusters) && suite.Config().UseKind { - os.Exit(suite.Run()) - } else { - fmt.Println(fmt.Sprintf("Skipping sameness tests because either -enable-multi-cluster is "+ - "not set, the number of clusters did not match the expected count of %d, or --useKind is false. "+ - "Sameness acceptance tests are currently only supported on Kind clusters", expectedNumberOfClusters)) - } -} diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go deleted file mode 100644 index e00502463e..0000000000 --- a/acceptance/tests/sameness/sameness_test.go +++ /dev/null @@ -1,875 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package sameness - -import ( - ctx "context" - "fmt" - "strconv" - "strings" - "sync" - "testing" - "time" - - terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/config" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - cluster01Partition = "ap1" - cluster01Datacenter = "dc1" - cluster02Datacenter = "dc2" - cluster03Datacenter = "dc3" - staticClientNamespace = "ns1" - staticServerNamespace = "ns2" - - keyCluster01a = "cluster-01-a" - keyCluster01b = "cluster-01-b" - keyCluster02a = "cluster-02-a" - keyCluster03a = "cluster-03-a" - - staticServerName = "static-server" - staticClientName = "static-client" - - staticServerDeployment = "deploy/static-server" - staticClientDeployment = "deploy/static-client" - - peerName1a = keyCluster01a - peerName1b = keyCluster01b - peerName2a = keyCluster02a - peerName3a = keyCluster03a - - samenessGroupName = "group-01" - - cluster01Region = "us-east-1" - cluster02Region = "us-west-1" - cluster03Region = "us-east-2" - - retryTimeout = 5 * time.Minute -) - -func TestFailover_Connect(t *testing.T) { - env := suite.Environment() - cfg := suite.Config() - - if !cfg.EnableEnterprise { - t.Skipf("skipping this test because -enable-enterprise is not set") - } - - cases := []struct { - name string - ACLsEnabled bool - }{ - { - "default failover", - false, - }, - { - "secure failover", - true, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - /* - Architecture Overview: - Primary Datacenter (DC1) - Default Partition - Peer -> DC2 (cluster-02-a) - Peer -> DC3 (cluster-03-a) - AP1 Partition - Peer -> DC2 (cluster-02-a) - Peer -> DC3 (cluster-03-a) - Datacenter 2 (DC2) - Default Partition - Peer -> DC1 (cluster-01-a) - Peer -> DC1 (cluster-01-b) - Peer -> DC3 (cluster-03-a) - Datacenter 3 (DC3) - Default Partition - Peer -> DC1 (cluster-01-a) - Peer -> DC1 (cluster-01-b) - Peer -> DC2 (cluster-02-a) - - - Architecture Diagram + failover scenarios from perspective of DC1 Default Partition Static-Server - +-------------------------------------------+ - | | - | DC1 | - | | - | +-----------------------------+ | +-----------------------------------+ - | | | | | DC2 | - | | +------------------+ | | Failover 2 | +------------------+ | - | | | +-------+--------+-----------------+------>| | | - | | | Static-Server | | | | | Static-Server | | - | | | +-------+---+ | | | | | - | | | | | | | | | | | - | | | | | | | | | | | - | | | +-------+---+----+-------------+ | | | | - | | +------------------+ | | | | | +------------------+ | - | | Admin Partitions: Default | | | | | | - | | Name: cluster-01-a | | | | | Admin Partitions: Default | - | | Region: us-east-1 | | | | | Name: cluster-02-a | - | +-----------------------------+ | | | | Region: us-west-1 | - | | | | +-----------------------------------+ - | Failover 1| | Failover 3 | - | +-------------------------------+ | | | +-----------------------------------+ - | | | | | | | DC3 | - | | +------------------+ | | | | | +------------------+ | - | | | | | | | | | | Static-Server | | - | | | Static-Server | | | | | | | | | - | | | | | | | | | | | | - | | | | | | | +---+------>| | | - | | | |<------+--+ | | | | | - | | | | | | | +------------------+ | - | | +------------------+ | | | | - | | Admin Partitions: ap1 | | | Admin Partitions: Default | - | | Name: cluster-01-b | | | Name: cluster-03-a | - | | Region: us-east-1 | | | Region: us-east-2 | - | +-------------------------------+ | | | - | | +-----------------------------------+ - +-------------------------------------------+ - */ - - testClusters := clusters{ - keyCluster01a: {name: peerName1a, context: env.DefaultContext(t), hasServer: true, acceptors: []string{peerName2a, peerName3a}, locality: localityForRegion(cluster01Region)}, - keyCluster01b: {name: peerName1b, context: env.Context(t, 1), partition: cluster01Partition, hasServer: false, acceptors: []string{peerName2a, peerName3a}, locality: localityForRegion(cluster01Region)}, - keyCluster02a: {name: peerName2a, context: env.Context(t, 2), hasServer: true, acceptors: []string{peerName3a}, locality: localityForRegion(cluster02Region)}, - keyCluster03a: {name: peerName3a, context: env.Context(t, 3), hasServer: true, locality: localityForRegion(cluster03Region)}, - } - - // Set primary clusters per cluster - // This is helpful for cases like DNS with partitions where many aspects of the primary cluster must be used - testClusters[keyCluster01a].primaryCluster = testClusters[keyCluster01a] - testClusters[keyCluster01b].primaryCluster = testClusters[keyCluster01a] - testClusters[keyCluster02a].primaryCluster = testClusters[keyCluster02a] - testClusters[keyCluster03a].primaryCluster = testClusters[keyCluster03a] - - // Setup Namespaces. - for _, v := range testClusters { - createNamespaces(t, cfg, v.context) - } - - commonHelmValues := map[string]string{ - "global.peering.enabled": "true", - - "global.tls.enabled": "true", - "global.tls.httpsOnly": strconv.FormatBool(c.ACLsEnabled), - - "global.enableConsulNamespaces": "true", - - "global.adminPartitions.enabled": "true", - - "global.acls.manageSystemACLs": strconv.FormatBool(c.ACLsEnabled), - - "connectInject.enabled": "true", - "connectInject.consulNamespaces.mirroringK8S": "true", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - - "dns.enabled": "true", - "connectInject.sidecarProxy.lifecycle.defaultEnabled": "false", - } - - releaseName := helpers.RandomName() - - var wg sync.WaitGroup - - // Create the cluster-01-a and cluster-01-b - // create in same routine as 01-b depends on 01-a being created first - wg.Add(1) - go func() { - defer wg.Done() - // Create the cluster-01-a - defaultPartitionHelmValues := map[string]string{ - "global.datacenter": cluster01Datacenter, - } - - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - defaultPartitionHelmValues["meshGateway.service.type"] = "NodePort" - defaultPartitionHelmValues["meshGateway.service.nodePort"] = "30200" - defaultPartitionHelmValues["server.exposeService.type"] = "NodePort" - defaultPartitionHelmValues["server.exposeService.nodePort.https"] = "30000" - defaultPartitionHelmValues["server.exposeService.nodePort.grpc"] = "30100" - } - helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) - - testClusters[keyCluster01a].helmCluster = consul.NewHelmCluster(t, defaultPartitionHelmValues, testClusters[keyCluster01a].context, cfg, releaseName) - testClusters[keyCluster01a].helmCluster.Create(t) - - // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. - caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) - - logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) - k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, caCertSecretName) - - // Create Secondary Partition Cluster (cluster-01-b) which will apply the primary (dc1) datacenter. - partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) - if c.ACLsEnabled { - logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) - k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, partitionToken) - } - - partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) - partitionSvcAddress := k8s.ServiceHost(t, cfg, testClusters[keyCluster01a].context, partitionServiceName) - - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, testClusters[keyCluster01b].context) - - secondaryPartitionHelmValues := map[string]string{ - "global.enabled": "false", - "global.datacenter": cluster01Datacenter, - - "global.adminPartitions.name": cluster01Partition, - - "global.tls.caCert.secretName": caCertSecretName, - "global.tls.caCert.secretKey": "tls.crt", - - "externalServers.enabled": "true", - "externalServers.hosts[0]": partitionSvcAddress, - "externalServers.tlsServerName": fmt.Sprintf("server.%s.consul", cluster01Datacenter), - "global.server.enabled": "false", - } - - if c.ACLsEnabled { - // Setup partition token and auth method host if ACLs enabled. - secondaryPartitionHelmValues["global.acls.bootstrapToken.secretName"] = partitionToken - secondaryPartitionHelmValues["global.acls.bootstrapToken.secretKey"] = "token" - secondaryPartitionHelmValues["externalServers.k8sAuthMethodHost"] = k8sAuthMethodHost - } - - if cfg.UseKind { - secondaryPartitionHelmValues["externalServers.httpsPort"] = "30000" - secondaryPartitionHelmValues["externalServers.grpcPort"] = "30100" - secondaryPartitionHelmValues["meshGateway.service.type"] = "NodePort" - secondaryPartitionHelmValues["meshGateway.service.nodePort"] = "30200" - } - helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) - - testClusters[keyCluster01b].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, testClusters[keyCluster01b].context, cfg, releaseName) - testClusters[keyCluster01b].helmCluster.Create(t) - }() - - // Create cluster-02-a Cluster. - wg.Add(1) - go func() { - defer wg.Done() - PeerOneHelmValues := map[string]string{ - "global.datacenter": cluster02Datacenter, - } - - if cfg.UseKind { - PeerOneHelmValues["server.exposeGossipAndRPCPorts"] = "true" - PeerOneHelmValues["meshGateway.service.type"] = "NodePort" - PeerOneHelmValues["meshGateway.service.nodePort"] = "30100" - } - helpers.MergeMaps(PeerOneHelmValues, commonHelmValues) - - testClusters[keyCluster02a].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, testClusters[keyCluster02a].context, cfg, releaseName) - testClusters[keyCluster02a].helmCluster.Create(t) - }() - - // Create cluster-03-a Cluster. - wg.Add(1) - go func() { - defer wg.Done() - PeerTwoHelmValues := map[string]string{ - "global.datacenter": cluster03Datacenter, - } - - if cfg.UseKind { - PeerTwoHelmValues["server.exposeGossipAndRPCPorts"] = "true" - PeerTwoHelmValues["meshGateway.service.type"] = "NodePort" - PeerTwoHelmValues["meshGateway.service.nodePort"] = "30100" - } - helpers.MergeMaps(PeerTwoHelmValues, commonHelmValues) - - testClusters[keyCluster03a].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, testClusters[keyCluster03a].context, cfg, releaseName) - testClusters[keyCluster03a].helmCluster.Create(t) - }() - - // Wait for the clusters to start up - logger.Log(t, "waiting for clusters to start up . . .") - wg.Wait() - - // Create a ProxyDefaults resource to configure services to use the mesh - // gateways and set server and client opts. - for k, v := range testClusters { - logger.Logf(t, "applying resources on %s", v.context.KubectlOptions(t).ContextName) - - // Client will use the client namespace. - testClusters[k].clientOpts = &terratestk8s.KubectlOptions{ - ContextName: v.context.KubectlOptions(t).ContextName, - ConfigPath: v.context.KubectlOptions(t).ConfigPath, - Namespace: staticClientNamespace, - } - - // Server will use the server namespace. - testClusters[k].serverOpts = &terratestk8s.KubectlOptions{ - ContextName: v.context.KubectlOptions(t).ContextName, - ConfigPath: v.context.KubectlOptions(t).ConfigPath, - Namespace: staticServerNamespace, - } - - // Sameness Defaults need to be applied first so that the sameness group exists. - applyResources(t, cfg, "../fixtures/bases/mesh-gateway", v.context.KubectlOptions(t)) - applyResources(t, cfg, "../fixtures/bases/sameness/override-ns", v.serverOpts) - - // Only assign a client if the cluster is running a Consul server. - if v.hasServer { - testClusters[k].client, _ = testClusters[k].helmCluster.SetupConsulClient(t, c.ACLsEnabled) - } - } - - // Assign the client default partition client to the partition - testClusters[keyCluster01b].client = testClusters[keyCluster01a].client - - // Apply Mesh resource to default partition and peers - for _, v := range testClusters { - if v.hasServer { - applyResources(t, cfg, "../fixtures/bases/sameness/peering/mesh", v.context.KubectlOptions(t)) - } - } - - // Apply locality to clusters - for _, v := range testClusters { - setK8sNodeLocality(t, v.context, v) - } - - // Peering/Dialer relationship - /* - cluster-01-a cluster-02-a - Dialer -> 2a 1a -> acceptor - Dialer -> 3a 1b -> acceptor - Dialer -> 3a - - cluster-01-b cluster-03-a - Dialer -> 2a 1a -> acceptor - Dialer -> 3a 1b -> acceptor - 2a -> acceptor - */ - for _, v := range []*cluster{testClusters[keyCluster02a], testClusters[keyCluster03a]} { - logger.Logf(t, "creating acceptor on %s", v.name) - // Create an acceptor token on the cluster - applyResources(t, cfg, fmt.Sprintf("../fixtures/bases/sameness/peering/%s-acceptor", v.name), v.context.KubectlOptions(t)) - - // Copy secrets to the necessary peers to be used for dialing later - for _, vv := range testClusters { - if isAcceptor(v.name, vv.acceptors) { - acceptorSecretName := v.getPeeringAcceptorSecret(t, cfg, vv.name) - logger.Logf(t, "acceptor %s created on %s", acceptorSecretName, v.name) - - logger.Logf(t, "copying acceptor token %s from %s to %s", acceptorSecretName, v.name, vv.name) - copySecret(t, cfg, v.context, vv.context, acceptorSecretName) - } - } - } - - // Create the dialers - for _, v := range []*cluster{testClusters[keyCluster01a], testClusters[keyCluster01b], testClusters[keyCluster02a]} { - applyResources(t, cfg, fmt.Sprintf("../fixtures/bases/sameness/peering/%s-dialer", v.name), v.context.KubectlOptions(t)) - } - - // If ACLs are enabled, we need to create the intentions - if c.ACLsEnabled { - intention := &api.ServiceIntentionsConfigEntry{ - Name: staticServerName, - Kind: api.ServiceIntentions, - Namespace: staticServerNamespace, - Sources: []*api.SourceIntention{ - { - Name: staticClientName, - Namespace: staticClientNamespace, - SamenessGroup: samenessGroupName, - Action: api.IntentionActionAllow, - }, - }, - } - - for _, v := range testClusters { - logger.Logf(t, "creating intentions on server %s", v.name) - _, _, err := v.client.ConfigEntries().Set(intention, &api.WriteOptions{Partition: v.partition}) - require.NoError(t, err) - } - } - - logger.Log(t, "creating exported services") - for _, v := range testClusters { - if v.hasServer { - applyResources(t, cfg, "../fixtures/cases/sameness/exported-services/default-partition", v.context.KubectlOptions(t)) - } else { - applyResources(t, cfg, "../fixtures/cases/sameness/exported-services/ap1-partition", v.context.KubectlOptions(t)) - } - } - - // Create sameness group after exporting the services, this will reduce flakiness in an automated test - for _, v := range testClusters { - applyResources(t, cfg, fmt.Sprintf("../fixtures/bases/sameness/%s-default-ns", v.name), v.context.KubectlOptions(t)) - } - - // Setup DNS. - for _, v := range testClusters { - dnsService, err := v.context.KubernetesClient(t).CoreV1().Services("default").Get(ctx.Background(), fmt.Sprintf("%s-%s", releaseName, "consul-dns"), metav1.GetOptions{}) - require.NoError(t, err) - v.dnsIP = &dnsService.Spec.ClusterIP - logger.Logf(t, "%s dnsIP: %s", v.name, *v.dnsIP) - } - - // Setup Prepared Query. - - for k, v := range testClusters { - definition := &api.PreparedQueryDefinition{ - Name: fmt.Sprintf("my-query-%s", v.fullTextPartition()), - Service: api.ServiceQuery{ - Service: staticServerName, - SamenessGroup: samenessGroupName, - Namespace: staticServerNamespace, - OnlyPassing: false, - Partition: v.fullTextPartition(), - }, - } - - pqID, _, err := v.client.PreparedQuery().Create(definition, &api.WriteOptions{}) - require.NoError(t, err) - logger.Logf(t, "%s PQ ID: %s", v.name, pqID) - testClusters[k].pqID = &pqID - testClusters[k].pqName = &definition.Name - } - - // Create static server/client after the rest of the config is setup for a more stable testing experience - // Create static server deployments. - logger.Log(t, "creating static-server and static-client deployments") - deployCustomizeAsync(t, testClusters[keyCluster01a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc1-default", &wg) - deployCustomizeAsync(t, testClusters[keyCluster01b].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc1-partition", &wg) - deployCustomizeAsync(t, testClusters[keyCluster02a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc2", &wg) - deployCustomizeAsync(t, testClusters[keyCluster03a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc3", &wg) - - // Create static client deployments. - staticClientKustomizeDirDefault := "../fixtures/cases/sameness/static-client/default-partition" - staticClientKustomizeDirAP1 := "../fixtures/cases/sameness/static-client/ap1-partition" - - // If transparent proxy is enabled create clients without explicit upstreams - if cfg.EnableTransparentProxy { - staticClientKustomizeDirDefault = fmt.Sprintf("%s-%s", staticClientKustomizeDirDefault, "tproxy") - staticClientKustomizeDirAP1 = fmt.Sprintf("%s-%s", staticClientKustomizeDirAP1, "tproxy") - } - - deployCustomizeAsync(t, testClusters[keyCluster01a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault, &wg) - deployCustomizeAsync(t, testClusters[keyCluster02a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault, &wg) - deployCustomizeAsync(t, testClusters[keyCluster03a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault, &wg) - deployCustomizeAsync(t, testClusters[keyCluster01b].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirAP1, &wg) - wg.Wait() - - // Verify that both static-server and static-client have been injected and now have 2 containers in each cluster. - // Also get the server IP - testClusters.setServerIP(t) - - // Everything should be up and running now - testClusters.verifyServerUpState(t, cfg.EnableTransparentProxy) - logger.Log(t, "all infrastructure up and running") - - // Verify locality is set on services based on node labels previously applied. - // - // This is currently the only locality testing we do for k8s and ensures that single-partition - // locality-aware routing will function in consul-k8s. In the future, this test will be expanded - // to test multi-cluster locality-based failover with sameness groups. - for _, v := range testClusters { - v.checkLocalities(t) - } - - // Verify all the failover Scenarios - logger.Log(t, "verifying failover scenarios") - - subCases := []struct { - name string - server *cluster - failovers []struct { - failoverServer *cluster - expectedPQ expectedPQ - } - }{ - { - name: "cluster-01-a perspective", // This matches the diagram at the beginning of the test - server: testClusters[keyCluster01a], - failovers: []struct { - failoverServer *cluster - expectedPQ expectedPQ - }{ - {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "ap1", peerName: "", namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, - }, - }, - { - name: "cluster-01-b partition perspective", - server: testClusters[keyCluster01b], - failovers: []struct { - failoverServer *cluster - expectedPQ expectedPQ - }{ - {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "ap1", peerName: "", namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "ap1", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "ap1", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, - }, - }, - { - name: "cluster-02-a perspective", - server: testClusters[keyCluster02a], - failovers: []struct { - failoverServer *cluster - expectedPQ expectedPQ - }{ - {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01a].name, namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01b].name, namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, - }, - }, - { - name: "cluster-03-a perspective", - server: testClusters[keyCluster03a], - failovers: []struct { - failoverServer *cluster - expectedPQ expectedPQ - }{ - {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: "", namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster01a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01a].name, namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01b].name, namespace: "ns2"}}, - {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, - }, - }, - } - for _, sc := range subCases { - t.Run(sc.name, func(t *testing.T) { - // Reset the scale of all servers - testClusters.resetScale(t) - testClusters.verifyServerUpState(t, cfg.EnableTransparentProxy) - // We're resetting the scale, so make sure we have all the new IP addresses saved - testClusters.setServerIP(t) - - for i, v := range sc.failovers { - // Verify Failover (If this is the first check, then just verifying we're starting with the right server) - logger.Log(t, "checking service failover", i) - - if cfg.EnableTransparentProxy { - sc.server.serviceTargetCheck(t, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) - } else { - sc.server.serviceTargetCheck(t, v.failoverServer.name, "localhost:8080") - } - - // 1. The admin partition does not contain a server, so DNS service will not resolve on the admin partition cluster - // 2. A workaround to perform the DNS and PQ queries on the primary datacenter cluster by specifying the admin partition - // e.g kubectl --context kind-dc1 --namespace ns1 exec -i deploy/static-client -c static-client \ - // -- dig @test-3lmypr-consul-dns.default static-server.service.ns2.ns.mine.sg.ap1.ap.consul - // Verify DNS. - logger.Log(t, "verifying dns", i) - sc.server.dnsFailoverCheck(t, cfg, releaseName, v.failoverServer) - - logger.Log(t, "verifying prepared query", i) - sc.server.preparedQueryFailoverCheck(t, releaseName, v.expectedPQ, v.failoverServer) - - // Scale down static-server on the current failover, will fail over to the next. - logger.Logf(t, "scaling server down on %s", v.failoverServer.name) - k8s.KubectlScale(t, v.failoverServer.serverOpts, staticServerDeployment, 0) - } - }) - } - }) - } -} - -type expectedPQ struct { - partition string - peerName string - namespace string -} - -type cluster struct { - name string - partition string - locality api.Locality - context environment.TestContext - helmCluster *consul.HelmCluster - client *api.Client - hasServer bool - serverOpts *terratestk8s.KubectlOptions - clientOpts *terratestk8s.KubectlOptions - staticServerIP *string - pqID *string - pqName *string - dnsIP *string - acceptors []string - primaryCluster *cluster -} - -func (c *cluster) fullTextPartition() string { - if c.partition == "" { - return "default" - } else { - return c.partition - } -} - -// serviceTargetCheck verifies that curling the `static-server` using the `static-client` responds with the expected -// cluster name. Each static-server responds with a unique name so that we can verify failover occured as expected. -func (c *cluster) serviceTargetCheck(t *testing.T, expectedName string, curlAddress string) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - var resp string - var err error - retry.RunWith(timer, t, func(r *retry.R) { - // Use -s/--silent and -S/--show-error flags w/ curl to reduce noise during retries. - // This silences extra output like the request progress bar, but preserves errors. - resp, err = k8s.RunKubectlAndGetOutputE(r, c.clientOpts, "exec", "-i", - staticClientDeployment, "-c", staticClientName, "--", "curl", "-sS", curlAddress) - require.NoError(r, err) - assert.Contains(r, resp, expectedName) - }) - logger.Log(t, resp) -} - -// preparedQueryFailoverCheck verifies that failover occurs when executing the prepared query. It also assures that -// executing the prepared query via DNS also provides expected results. -func (c *cluster) preparedQueryFailoverCheck(t *testing.T, releaseName string, epq expectedPQ, failover *cluster) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - resp, _, err := c.client.PreparedQuery().Execute(*c.pqID, &api.QueryOptions{Namespace: staticServerNamespace, Partition: c.partition}) - require.NoError(t, err) - require.Len(t, resp.Nodes, 1) - - assert.Equal(t, epq.partition, resp.Nodes[0].Service.Partition) - assert.Equal(t, epq.peerName, resp.Nodes[0].Service.PeerName) - assert.Equal(t, epq.namespace, resp.Nodes[0].Service.Namespace) - assert.Equal(t, *failover.staticServerIP, resp.Nodes[0].Service.Address) - - // Verify that dns lookup is successful, there is no guarantee that the ip address is unique, so for PQ this is - // just verifying that we can query using DNS and that the ip address is correct. It does not however prove - // that failover occurred, that is left to client `Execute` - dnsPQLookup := []string{fmt.Sprintf("%s.query.consul", *c.pqName)} - retry.RunWith(timer, t, func(r *retry.R) { - logs := dnsQuery(r, releaseName, dnsPQLookup, c.primaryCluster, failover) - assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", *c.primaryCluster.dnsIP)) - assert.Contains(r, logs, "ANSWER SECTION:") - assert.Contains(r, logs, *failover.staticServerIP) - }) -} - -// DNS failover check verifies that failover occurred when querying the DNS. -func (c *cluster) dnsFailoverCheck(t *testing.T, cfg *config.TestConfig, releaseName string, failover *cluster) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - dnsLookup := []string{fmt.Sprintf("static-server.service.ns2.ns.%s.sg.%s.ap.consul", samenessGroupName, c.fullTextPartition()), "+tcp", "SRV"} - retry.RunWith(timer, t, func(r *retry.R) { - // Use the primary cluster when performing a DNS lookup, this mostly affects cases - // where we are verifying DNS for a partition - logs := dnsQuery(r, releaseName, dnsLookup, c.primaryCluster, failover) - - assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", *c.primaryCluster.dnsIP)) - assert.Contains(r, logs, "ANSWER SECTION:") - assert.Contains(r, logs, *failover.staticServerIP) - - // Additional checks - // When accessing the SRV record for DNS we can get more information. In the case of Kind, - // the context can be used to determine that failover occured to the expected kubernetes cluster - // hosting Consul - assert.Contains(r, logs, "ADDITIONAL SECTION:") - expectedName := failover.context.KubectlOptions(r).ContextName - if cfg.UseKind { - expectedName = strings.Replace(expectedName, "kind-", "", -1) - } - assert.Contains(r, logs, expectedName) - }) -} - -// getPeeringAcceptorSecret assures that the secret is created and retrieves the secret from the provided acceptor. -func (c *cluster) getPeeringAcceptorSecret(t *testing.T, cfg *config.TestConfig, acceptorName string) string { - // Ensure the secrets are created. - var acceptorSecretName string - timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} - retry.RunWith(timer, t, func(r *retry.R) { - var err error - acceptorSecretName, err = k8s.RunKubectlAndGetOutputE(r, c.context.KubectlOptions(r), "get", "peeringacceptor", acceptorName, "-o", "jsonpath={.status.secret.name}") - require.NoError(r, err) - require.NotEmpty(r, acceptorSecretName) - }) - - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, c.context.KubectlOptions(t), "delete", "secret", acceptorSecretName) - }) - - return acceptorSecretName -} - -// checkLocalities checks the given cluster for `static-client` and `static-server` instances matching the locality -// expected for the cluster. -func (c *cluster) checkLocalities(t *testing.T) { - for ns, svcs := range map[string][]string{ - staticClientNamespace: { - staticClientName, - staticClientName + "-sidecar-proxy", - }, - staticServerNamespace: { - staticServerName, - staticServerName + "-sidecar-proxy", - }, - } { - for _, svc := range svcs { - cs := c.getCatalogService(t, svc, ns, c.partition) - assert.NotNil(t, cs.ServiceLocality, "service %s in %s did not have locality set", svc, c.name) - assert.Equal(t, c.locality, *cs.ServiceLocality, "locality for service %s in %s did not match expected", svc, c.name) - } - } -} - -func (c *cluster) getCatalogService(t *testing.T, svc, ns, partition string) *api.CatalogService { - resp, _, err := c.client.Catalog().Service(svc, "", &api.QueryOptions{Namespace: ns, Partition: partition}) - require.NoError(t, err) - assert.NotEmpty(t, resp, "did not find service %s in cluster %s (partition=%s ns=%s)", svc, c.name, partition, ns) - return resp[0] -} - -type clusters map[string]*cluster - -func (c clusters) resetScale(t *testing.T) { - for _, v := range c { - k8s.KubectlScale(t, v.serverOpts, staticServerDeployment, 1) - } -} - -// setServerIP makes sure everything is up and running and then saves the -// static-server IP to the appropriate cluster. IP addresses can change when -// services are scaled up and down. -func (c clusters) setServerIP(t *testing.T) { - for _, labelSelector := range []string{"app=static-server", "app=static-client"} { - for k, v := range c { - podList, err := v.context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(ctx.Background(), - metav1.ListOptions{LabelSelector: labelSelector}) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - if labelSelector == "app=static-server" { - ip := &podList.Items[0].Status.PodIP - require.NotNil(t, ip) - logger.Logf(t, "%s-static-server-ip: %s", v.name, *ip) - c[k].staticServerIP = ip - } - } - } -} - -// verifyServerUpState will verify that the static-servers are all up and running as -// expected by curling them from their local datacenters. -func (c clusters) verifyServerUpState(t *testing.T, isTproxyEnabled bool) { - logger.Logf(t, "verifying that static-servers are up") - for _, v := range c { - // Query using a client and expect its own name, no failover should occur - if isTproxyEnabled { - v.serviceTargetCheck(t, v.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", v.fullTextPartition())) - } else { - v.serviceTargetCheck(t, v.name, "localhost:8080") - } - } -} - -func copySecret(t *testing.T, cfg *config.TestConfig, sourceContext, destContext environment.TestContext, secretName string) { - k8s.CopySecret(t, sourceContext, destContext, secretName) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, destContext.KubectlOptions(t), "delete", "secret", secretName) - }) -} - -func createNamespaces(t *testing.T, cfg *config.TestConfig, context environment.TestContext) { - logger.Logf(t, "creating namespaces in %s", context.KubectlOptions(t).ContextName) - k8s.RunKubectl(t, context.KubectlOptions(t), "create", "ns", staticServerNamespace) - k8s.RunKubectl(t, context.KubectlOptions(t), "create", "ns", staticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, context.KubectlOptions(t), "delete", "ns", staticClientNamespace, staticServerNamespace) - }) -} - -func applyResources(t *testing.T, cfg *config.TestConfig, kustomizeDir string, opts *terratestk8s.KubectlOptions) { - k8s.KubectlApplyK(t, opts, kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, opts, kustomizeDir) - }) -} - -// setK8sNodeLocality labels the k8s node corresponding to the given cluster with standard labels indicating the -// locality of that node. These are propagated by connect-inject to registered Consul services. -func setK8sNodeLocality(t *testing.T, context environment.TestContext, c *cluster) { - nodeList, err := context.KubernetesClient(t).CoreV1().Nodes().List(ctx.Background(), metav1.ListOptions{}) - require.NoError(t, err) - // Get the name of the (only) node from the Kind cluster. - node := nodeList.Items[0].Name - k8s.KubectlLabel(t, context.KubectlOptions(t), "node", node, corev1.LabelTopologyRegion, c.locality.Region) - k8s.KubectlLabel(t, context.KubectlOptions(t), "node", node, corev1.LabelTopologyZone, c.locality.Zone) -} - -// dnsQuery performs a dns query with the provided query string. -func dnsQuery(t testutil.TestingTB, releaseName string, dnsQuery []string, dnsServer, failover *cluster) string { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} - var logs string - - retry.RunWith(timer, t, func(r *retry.R) { - args := []string{"exec", "-i", - staticClientDeployment, "-c", staticClientName, "--", "dig", fmt.Sprintf("@%s-consul-dns.default", - releaseName)} - args = append(args, dnsQuery...) - var err error - logs, err = k8s.RunKubectlAndGetOutputE(r, dnsServer.clientOpts, args...) - require.NoError(r, err) - }) - logger.Logf(t, "%s: %s", failover.name, logs) - return logs -} - -// isAcceptor iterates through the provided acceptor list of cluster names and determines if -// any match the provided name. Returns true if a match is found, false otherwise. -func isAcceptor(name string, acceptorList []string) bool { - for _, v := range acceptorList { - if name == v { - return true - } - } - return false -} - -// localityForRegion returns the full api.Locality to use in tests for a given region string. -func localityForRegion(r string) api.Locality { - return api.Locality{ - Region: r, - Zone: r + "a", - } -} - -func deployCustomizeAsync(t *testing.T, opts *terratestk8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, kustomizeDir string, wg *sync.WaitGroup) { - wg.Add(1) - go func() { - defer wg.Done() - k8s.DeployKustomize(t, opts, noCleanupOnFailure, noCleanup, debugDirectory, kustomizeDir) - }() -} diff --git a/acceptance/tests/segments/main_test.go b/acceptance/tests/segments/main_test.go deleted file mode 100644 index 4d66c9ae4f..0000000000 --- a/acceptance/tests/segments/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package segments - -import ( - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) -} diff --git a/acceptance/tests/segments/segments_test.go b/acceptance/tests/segments/segments_test.go deleted file mode 100644 index 4725fb477c..0000000000 --- a/acceptance/tests/segments/segments_test.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package segments - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" -) - -// TestSegments_MeshWithAgentfulClients is a simple test that verifies that -// the Consul service mesh can be configured to use segments with: -// - one cluster with an alpha segment configured on the servers. -// - clients enabled and joining the alpha segment. -// - static client can communicate with static server. -func TestSegments_MeshWithAgentfulClients(t *testing.T) { - cases := map[string]struct { - secure bool - }{ - "not-secure": {secure: false}, - "secure": {secure: true}, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - cfg := suite.Config() - if !cfg.EnableEnterprise { - t.Skipf("skipping this test because -enable-enterprise is not set") - } - ctx := suite.Environment().DefaultContext(t) - - releaseName := helpers.RandomName() - - helmValues := map[string]string{ - "connectInject.enabled": "true", - - "server.replicas": "3", - "server.extraConfig": `"{\"segments\": [{\"name\":\"alpha1\"\,\"bind\":\"0.0.0.0\"\,\"port\":8303}]}"`, - - "client.enabled": "true", - // need to configure clients to connect to port 8303 that the alpha segment was configured on rather than - // the standard serf LAN port. - "client.join[0]": "${CONSUL_FULLNAME}-server-0.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.join[1]": "${CONSUL_FULLNAME}-server-1.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.join[2]": "${CONSUL_FULLNAME}-server-2.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.extraConfig": `"{\"segment\": \"alpha1\"}"`, - } - - connHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: c.secure, - ReleaseName: releaseName, - Ctx: ctx, - UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, - Cfg: cfg, - HelmValues: helmValues, - } - - connHelper.Setup(t) - - connHelper.Install(t) - connHelper.DeployClientAndServer(t) - if c.secure { - connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) - connHelper.CreateIntention(t, connhelper.IntentionOpts{}) - } - - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) - connHelper.TestConnectionFailureWhenUnhealthy(t) - }) - } -} - -// TestSegments_MeshWithAgentfulClientsMultiCluster is a simple test that verifies that -// the Consul service mesh can be configured to use segments with: -// - one cluster with an alpha segment configured on the servers. -// - clients enabled on another cluster and joining the alpha segment. -// - static client can communicate with static server. -func TestSegments_MeshWithAgentfulClientsMultiCluster(t *testing.T) { - cases := map[string]struct { - secure bool - }{ - "not-secure": {secure: false}, - "secure": {secure: true}, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - cfg := suite.Config() - if !cfg.EnableEnterprise { - t.Skipf("skipping this test because -enable-enterprise is not set") - } - releaseName := helpers.RandomName() - - // deploy server cluster - serverClusterContext := suite.Environment().DefaultContext(t) - serverClusterHelmValues := map[string]string{ - "connectInject.enabled": "true", - - "server.replicas": "3", - "server.extraConfig": `"{\"segments\": [{\"name\":\"alpha1\"\,\"bind\":\"0.0.0.0\"\,\"port\":8303}]}"`, - - "client.enabled": "true", - "client.join[0]": "${CONSUL_FULLNAME}-server-0.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.join[1]": "${CONSUL_FULLNAME}-server-1.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.join[2]": "${CONSUL_FULLNAME}-server-2.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.extraConfig": `"{\"segment\": \"alpha1\"}"`, - } - - serverConnHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: c.secure, - ReleaseName: releaseName, - Ctx: serverClusterContext, - UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, - Cfg: cfg, - HelmValues: serverClusterHelmValues, - } - - serverConnHelper.Setup(t) - serverConnHelper.Install(t) - serverConnHelper.DeployServer(t) - - // deploy client cluster - clientClusterContext := suite.Environment().Context(t, 1) - clientClusterHelmValues := map[string]string{ - "connectInject.enabled": "true", - - "server.enabled": "false", - - "client.enabled": "true", - "client.join[0]": "${CONSUL_FULLNAME}-server-0.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.join[1]": "${CONSUL_FULLNAME}-server-1.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.join[2]": "${CONSUL_FULLNAME}-server-2.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8303", - "client.extraConfig": `"{\"segment\": \"alpha1\"}"`, - } - - clientClusterConnHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: c.secure, - ReleaseName: releaseName, - Ctx: clientClusterContext, - UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, - Cfg: cfg, - HelmValues: clientClusterHelmValues, - } - - clientClusterConnHelper.Setup(t) - clientClusterConnHelper.Install(t) - logger.Log(t, "creating static-client deployments in client cluster") - opts := clientClusterConnHelper.KubectlOptsForApp(t) - - if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, opts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") - } else { - k8s.DeployKustomize(t, opts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") - } - - // Check that the static-client has been injected and now have 2 containers in client cluster. - for _, labelSelector := range []string{"app=static-client"} { - podList, err := clientClusterContext.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{ - LabelSelector: labelSelector, - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - } - - //if c.secure { - // connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) - // connHelper.CreateIntention(t, connhelper.IntentionOpts{}) - //} - // - //connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) - //connHelper.TestConnectionFailureWhenUnhealthy(t) - }) - } -} diff --git a/acceptance/tests/server/main_test.go b/acceptance/tests/server/main_test.go deleted file mode 100644 index 497df9dca2..0000000000 --- a/acceptance/tests/server/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package server - -import ( - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) -} diff --git a/acceptance/tests/server/server_test.go b/acceptance/tests/server/server_test.go deleted file mode 100644 index 5511671935..0000000000 --- a/acceptance/tests/server/server_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package server - -import ( - "encoding/json" - "fmt" - "testing" - "time" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/go-multierror" - "github.com/stretchr/testify/require" -) - -// Test that when servers are restarted, they don't lose leadership. -func TestServerRestart(t *testing.T) { - cfg := suite.Config() - if cfg.EnableCNI || cfg.EnableTransparentProxy { - t.Skipf("skipping because -enable-cni or -enable-transparent-proxy is set and server restart " + - "is already tested without those settings and those settings don't affect this test") - } - - ctx := suite.Environment().DefaultContext(t) - replicas := 3 - releaseName := helpers.RandomName() - helmValues := map[string]string{ - "global.enabled": "false", - "connectInject.enabled": "false", - "server.enabled": "true", - "server.replicas": fmt.Sprintf("%d", replicas), - "server.affinity": "null", // Allow >1 pods per node so we can test in minikube with one node. - } - consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), suite.Config(), releaseName) - consulCluster.Create(t) - - // Start a separate goroutine to check if at any point more than one server is without - // a leader. We expect the server that is restarting to be without a leader because it hasn't - // yet joined the cluster but the other servers should have a leader. - expReadyPods := replicas - 1 - var unmarshallErrs error - timesWithoutLeader := 0 - done := make(chan bool) - defer close(done) - go func() { - for { - select { - case <-done: - return - default: - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "get", fmt.Sprintf("statefulset/%s-consul-server", releaseName), - "-o", "jsonpath={.status}") - if err != nil { - // Not failing the test on this error to reduce flakiness. - logger.Logf(t, "kubectl err: %s: %s", err, out) - break - } - type statefulsetOut struct { - ReadyReplicas *int `json:"readyReplicas,omitempty"` - } - var jsonOut statefulsetOut - if err = json.Unmarshal([]byte(out), &jsonOut); err != nil { - unmarshallErrs = multierror.Append(err) - } else if jsonOut.ReadyReplicas == nil || *jsonOut.ReadyReplicas < expReadyPods { - // note: for some k8s api reason when readyReplicas is 0 it's not included in the json output so - // that's why we're checking if it's nil. - timesWithoutLeader++ - } - time.Sleep(1 * time.Second) - } - } - }() - - // Restart servers - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "rollout", "restart", fmt.Sprintf("statefulset/%s-consul-server", releaseName)) - require.NoError(t, err, out) - - // Wait for restart to finish. - start := time.Now() - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "rollout", "status", "--timeout", "5m", "--watch", fmt.Sprintf("statefulset/%s-consul-server", releaseName)) - require.NoError(t, err, out, "rollout status command errored, this likely means the rollout didn't complete in time") - - // Check results - require.NoError(t, unmarshallErrs, "there were some json unmarshall errors, this is likely a bug") - logger.Logf(t, "restart took %s, there were %d instances where more than one server had no leader", time.Since(start), timesWithoutLeader) - require.Equal(t, 0, timesWithoutLeader, "there were %d instances where more than one server had no leader", timesWithoutLeader) -} diff --git a/acceptance/tests/snapshot-agent/main_test.go b/acceptance/tests/snapshot-agent/main_test.go index 3d70823059..daa389d4c4 100644 --- a/acceptance/tests/snapshot-agent/main_test.go +++ b/acceptance/tests/snapshot-agent/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package snapshotagent import ( diff --git a/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go b/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go index e5f1e785af..a4c1ef7494 100644 --- a/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go +++ b/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package snapshotagent import ( @@ -91,9 +88,9 @@ func TestSnapshotAgent_K8sSecret(t *testing.T) { retry.RunWith(timer, t, func(r *retry.R) { // Loop through snapshot agents. Only one will be the leader and have the snapshot files. pod := podList.Items[0] - snapshotFileListOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(r, kubectlOptions, terratestLogger.Discard, "exec", pod.Name, "-c", "consul-snapshot-agent", "--", "ls", "/tmp") + snapshotFileListOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(t, kubectlOptions, terratestLogger.Discard, "exec", pod.Name, "-c", "consul-snapshot-agent", "--", "ls", "/tmp") require.NoError(r, err) - logger.Logf(r, "Snapshot: \n%s", snapshotFileListOutput) + logger.Logf(t, "Snapshot: \n%s", snapshotFileListOutput) require.Contains(r, snapshotFileListOutput, ".snap", "Agent pod does not contain snapshot files") }) }) diff --git a/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go b/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go index 3c4354547c..ee20ffd9d7 100644 --- a/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go +++ b/acceptance/tests/snapshot-agent/snapshot_agent_vault_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package snapshotagent import ( @@ -213,9 +210,9 @@ func TestSnapshotAgent_Vault(t *testing.T) { retry.RunWith(timer, t, func(r *retry.R) { // Loop through snapshot agents. Only one will be the leader and have the snapshot files. pod := podList.Items[0] - snapshotFileListOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(r, kubectlOptions, terratestLogger.Discard, "exec", pod.Name, "-c", "consul-snapshot-agent", "--", "ls", "/tmp") + snapshotFileListOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(t, kubectlOptions, terratestLogger.Discard, "exec", pod.Name, "-c", "consul-snapshot-agent", "--", "ls", "/tmp") require.NoError(r, err) - logger.Logf(r, "Snapshot: \n%s", snapshotFileListOutput) + logger.Logf(t, "Snapshot: \n%s", snapshotFileListOutput) require.Contains(r, snapshotFileListOutput, ".snap", "Agent pod does not contain snapshot files") }) } diff --git a/acceptance/tests/sync/main_test.go b/acceptance/tests/sync/main_test.go index a26ecc9670..80c394e561 100644 --- a/acceptance/tests/sync/main_test.go +++ b/acceptance/tests/sync/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package sync import ( diff --git a/acceptance/tests/sync/sync_catalog_namespaces_test.go b/acceptance/tests/sync/sync_catalog_namespaces_test.go index 67123d6e4f..79f592033c 100644 --- a/acceptance/tests/sync/sync_catalog_namespaces_test.go +++ b/acceptance/tests/sync/sync_catalog_namespaces_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package sync import ( @@ -97,12 +94,12 @@ func TestSyncCatalogNamespaces(t *testing.T) { logger.Logf(t, "creating namespace %s", staticServerNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", staticServerNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) logger.Log(t, "creating a static-server with a service") - k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, staticServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) diff --git a/acceptance/tests/sync/sync_catalog_test.go b/acceptance/tests/sync/sync_catalog_test.go index 4bd5f91039..3e3880b618 100644 --- a/acceptance/tests/sync/sync_catalog_test.go +++ b/acceptance/tests/sync/sync_catalog_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package sync import ( @@ -50,7 +47,7 @@ func TestSyncCatalog(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating a static-server with a service") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().NoCleanup, suite.Config().DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().DebugDirectory, "../fixtures/bases/static-server") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) @@ -118,19 +115,19 @@ func TestSyncCatalogWithIngress(t *testing.T) { // Retry the kubectl apply because we've seen sporadic // "connection refused" errors where the mutating webhook // endpoint fails initially. - out, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "apply", "-k", "../fixtures/bases/ingress") + out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/bases/ingress") require.NoError(r, err, out) - helpers.Cleanup(r, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { // Ignore errors here because if the test ran as expected // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), "delete", "-k", "../fixtures/bases/ingress") + k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/bases/ingress") }) }) consulCluster.Create(t) logger.Log(t, "creating a static-server with a service") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().NoCleanup, suite.Config().DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), suite.Config().NoCleanupOnFailure, suite.Config().DebugDirectory, "../fixtures/bases/static-server") consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) diff --git a/acceptance/tests/tenancy_v2/main_test.go b/acceptance/tests/tenancy_v2/main_test.go deleted file mode 100644 index 1766d95319..0000000000 --- a/acceptance/tests/tenancy_v2/main_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package tenancy_v2 - -import ( - "fmt" - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - - expectedNumberOfClusters := 1 - if suite.Config().IsExpectedClusterCount(expectedNumberOfClusters) { - os.Exit(suite.Run()) - } else { - fmt.Printf( - "Skipping tenancy_v2 tests because the number of clusters, %d, did not match the expected count of %d\n", - len(suite.Config().KubeEnvs), - expectedNumberOfClusters, - ) - os.Exit(0) - } -} diff --git a/acceptance/tests/tenancy_v2/partition_test.go b/acceptance/tests/tenancy_v2/partition_test.go deleted file mode 100644 index 8ad031c8fe..0000000000 --- a/acceptance/tests/tenancy_v2/partition_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package tenancy_v2 - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" -) - -// TestTenancy_Partition_Created tests that V2 partitions are created when requested -// by a consul client external to the consul server cluster's k8s cluster. -// -// It sets up an external Consul server in the same cluster but a different Helm installation -// and then treats this server as external. -func TestTenancy_Partition_Created(t *testing.T) { - // Given a single k8s kind cluster - // Where helm "server" release hosts a consul server cluster (server.enabled=true) - // And helm "client" release hosts a consul client cluster (server.enabled=false) - // And both releases have experiments "resource-apis" and "v2tenancy enabled" - // And helm "client" release is configured to point to the helm "server" release as an external server (externalServer.enabled=true) - // And helm "client" release has admin partitions enabled with name "ap1" (global.adminPartitions.name=ap1) - // And helm "server" release is open for business - // When helm "client" release is installed - // Then partition "ap1" is created by the partition-init job in the helm "client" release - - // We're skipping ACLs for now because they're not supported in v2. - cfg := suite.Config() - // Requires connnectInject.enabled which we disable below. - cfg.SkipWhenCNI(t) - ctx := suite.Environment().DefaultContext(t) - - serverHelmValues := map[string]string{ - "server.enabled": "true", - "global.experiments[0]": "resource-apis", - "global.experiments[1]": "v2tenancy", - "global.adminPartitions.enabled": "false", - "global.enableConsulNamespaces": "true", - - // Don't install injector, controller and cni on this k8s cluster so that it's not installed twice. - "connectInject.enabled": "false", - - // The UI is not supported for v2 in 1.17, so for now it must be disabled. - "ui.enabled": "false", - } - - serverReleaseName := helpers.RandomName() - serverCluster := consul.NewHelmCluster(t, serverHelmValues, ctx, cfg, serverReleaseName) - serverCluster.Create(t) - - clientHelmValues := map[string]string{ - "server.enabled": "false", - "global.experiments[0]": "resource-apis", - "global.experiments[1]": "v2tenancy", - "global.adminPartitions.enabled": "true", - "global.adminPartitions.name": "ap1", - "global.enableConsulNamespaces": "true", - "externalServers.enabled": "true", - "externalServers.hosts[0]": fmt.Sprintf("%s-consul-server", serverReleaseName), - - // This needs to be set to true otherwise the pods never materialize - "connectInject.enabled": "true", - - // The UI is not supported for v2 in 1.17, so for now it must be disabled. - "ui.enabled": "false", - } - - clientReleaseName := helpers.RandomName() - clientCluster := consul.NewHelmCluster(t, clientHelmValues, ctx, cfg, clientReleaseName) - clientCluster.SkipCheckForPreviousInstallations = true - - clientCluster.Create(t) - - // verify partition ap1 created by partition init job - serverResourceClient := serverCluster.ResourceClient(t, false) - _, err := serverResourceClient.Read(context.Background(), &pbresource.ReadRequest{ - Id: &pbresource.ID{ - Name: "ap1", - Type: pbtenancy.PartitionType, - }, - }) - require.NoError(t, err, "expected partition ap1 to be created by partition init job") -} diff --git a/acceptance/tests/terminating-gateway/common.go b/acceptance/tests/terminating-gateway/common.go index 65dd7545a8..b94dd8a897 100644 --- a/acceptance/tests/terminating-gateway/common.go +++ b/acceptance/tests/terminating-gateway/common.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package terminatinggateway import ( @@ -19,7 +16,7 @@ const ( staticServerLocalAddress = "http://localhost:1234" ) -func AddIntention(t *testing.T, consulClient *api.Client, sourcePeer, sourceNS, sourceService, destinationNS, destinationsService string) { +func addIntention(t *testing.T, consulClient *api.Client, sourceNS, sourceService, destinationNS, destinationsService string) { t.Helper() logger.Log(t, fmt.Sprintf("creating %s => %s intention", sourceService, destinationsService)) @@ -32,14 +29,13 @@ func AddIntention(t *testing.T, consulClient *api.Client, sourcePeer, sourceNS, Name: sourceService, Namespace: sourceNS, Action: api.IntentionActionAllow, - Peer: sourcePeer, }, }, }, nil) require.NoError(t, err) } -func CreateTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, gwNamespace, serviceNamespace string, serviceNames ...string) { +func createTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, gwNamespace, serviceNamespace string, serviceNames ...string) { t.Helper() logger.Log(t, "creating config entry") @@ -70,7 +66,7 @@ func CreateTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, require.True(t, created, "failed to create config entry") } -func UpdateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules string) { +func updateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules string) { t.Helper() logger.Log(t, "creating a write policy for the static-server") @@ -98,46 +94,3 @@ func UpdateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules _, _, err = consulClient.ACL().RoleUpdate(termGwRole, nil) require.NoError(t, err) } - -func CreateServiceDefaultDestination(t *testing.T, consulClient *api.Client, serviceNamespace string, name string, protocol string, port int, addresses ...string) { - t.Helper() - - logger.Log(t, "creating config entry") - - if serviceNamespace != "" { - logger.Logf(t, "creating the %s namespace in Consul", serviceNamespace) - _, _, err := consulClient.Namespaces().Create(&api.Namespace{ - Name: serviceNamespace, - }, nil) - require.NoError(t, err) - } - - configEntry := &api.ServiceConfigEntry{ - Kind: api.ServiceDefaults, - Name: name, - Namespace: serviceNamespace, - Protocol: protocol, - Destination: &api.DestinationConfig{ - Addresses: addresses, - Port: port, - }, - } - - created, _, err := consulClient.ConfigEntries().Set(configEntry, nil) - require.NoError(t, err) - require.True(t, created, "failed to create config entry") -} - -func CreateMeshConfigEntry(t *testing.T, consulClient *api.Client, namespace string) { - t.Helper() - - logger.Log(t, "creating mesh config entry to enable MeshDestinationOnly") - created, _, err := consulClient.ConfigEntries().Set(&api.MeshConfigEntry{ - Namespace: namespace, - TransparentProxy: api.TransparentProxyMeshConfig{ - MeshDestinationsOnly: true, - }, - }, nil) - require.NoError(t, err) - require.True(t, created, "failed to create config entry") -} diff --git a/acceptance/tests/terminating-gateway/main_test.go b/acceptance/tests/terminating-gateway/main_test.go index 0c8127623d..477e125f94 100644 --- a/acceptance/tests/terminating-gateway/main_test.go +++ b/acceptance/tests/terminating-gateway/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package terminatinggateway import ( diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go index 67097b7648..4ff4ae7bd4 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package terminatinggateway import ( @@ -12,6 +9,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" "github.com/hashicorp/go-version" "github.com/stretchr/testify/require" ) @@ -72,25 +70,25 @@ func TestTerminatingGatewayDestinations(t *testing.T) { // Deploy a static-server that will play the role of an external service. logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server-https") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server-https") // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - UpdateTerminatingGatewayRole(t, consulClient, terminatingGatewayRules) + updateTerminatingGatewayRole(t, consulClient, terminatingGatewayRules) } // Since we are using the transparent kube DNS, disable the ability // of the service to dial the server directly through the sidecar - CreateMeshConfigEntry(t, consulClient, "") + createMeshConfigEntry(t, consulClient, "") // Create the config entry for the terminating gateway. - CreateTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerHostnameID, staticServerIPID) + createTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerHostnameID, staticServerIPID) // Deploy the static client logger.Log(t, "deploying static client") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") staticServerIP, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "get", "po", "-l", "app=static-server", `-o=jsonpath={.items[0].status.podIP}`) require.NoError(t, err) @@ -101,8 +99,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { // Create the service default declaring the external service (aka Destination) logger.Log(t, "creating tcp-based service defaults") - CreateServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "", 443, staticServerServiceName) - CreateServiceDefaultDestination(t, consulClient, "", staticServerIPID, "", 80, staticServerIP) + createServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "", 443, staticServerServiceName) + createServiceDefaultDestination(t, consulClient, "", staticServerIPID, "", 80, staticServerIP) // If ACLs are enabled, test that intentions prevent connections. if c.secure { @@ -114,8 +112,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ctx.KubectlOptions(t), staticClientName, "-k", staticServerHostnameURL) logger.Log(t, "adding intentions to allow traffic from client ==> server") - AddIntention(t, consulClient, "", "", staticClientName, "", staticServerHostnameID) - AddIntention(t, consulClient, "", "", staticClientName, "", staticServerIPID) + addIntention(t, consulClient, "", staticClientName, "", staticServerHostnameID) + addIntention(t, consulClient, "", staticClientName, "", staticServerIPID) } // Test that we can make a call to the terminating gateway. @@ -131,8 +129,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { logger.Log(t, "updating service defaults to try other scenarios") // You can't use TLS w/ protocol set to anything L7; Envoy can't snoop the traffic when the client encrypts it - CreateServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "http", 80, staticServerServiceName) - CreateServiceDefaultDestination(t, consulClient, "", staticServerIPID, "http", 80, staticServerIP) + createServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "http", 80, staticServerServiceName) + createServiceDefaultDestination(t, consulClient, "", staticServerIPID, "http", 80, staticServerIP) logger.Log(t, "trying calls to terminating gateway") k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), staticClientName, staticServerIPURL) @@ -140,3 +138,45 @@ func TestTerminatingGatewayDestinations(t *testing.T) { }) } } +func createServiceDefaultDestination(t *testing.T, consulClient *api.Client, serviceNamespace string, name string, protocol string, port int, addresses ...string) { + t.Helper() + + logger.Log(t, "creating config entry") + + if serviceNamespace != "" { + logger.Logf(t, "creating the %s namespace in Consul", serviceNamespace) + _, _, err := consulClient.Namespaces().Create(&api.Namespace{ + Name: serviceNamespace, + }, nil) + require.NoError(t, err) + } + + configEntry := &api.ServiceConfigEntry{ + Kind: api.ServiceDefaults, + Name: name, + Namespace: serviceNamespace, + Protocol: protocol, + Destination: &api.DestinationConfig{ + Addresses: addresses, + Port: port, + }, + } + + created, _, err := consulClient.ConfigEntries().Set(configEntry, nil) + require.NoError(t, err) + require.True(t, created, "failed to create config entry") +} + +func createMeshConfigEntry(t *testing.T, consulClient *api.Client, namespace string) { + t.Helper() + + logger.Log(t, "creating mesh config entry to enable MeshDestinationOnly") + created, _, err := consulClient.ConfigEntries().Set(&api.MeshConfigEntry{ + Namespace: namespace, + TransparentProxy: api.TransparentProxyMeshConfig{ + MeshDestinationsOnly: true, + }, + }, nil) + require.NoError(t, err) + require.True(t, created, "failed to create config entry") +} diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go index ee51a64c0d..8c4435ae75 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package terminatinggateway import ( @@ -62,7 +59,7 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { logger.Logf(t, "creating Kubernetes namespace %s", testNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", testNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", testNamespace) }) @@ -74,24 +71,24 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { // Deploy a static-server that will play the role of an external service. logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") // Register the external service. - helpers.RegisterExternalService(t, consulClient, testNamespace, staticServerName, staticServerName, 80) + registerExternalService(t, consulClient, testNamespace) // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - UpdateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) + updateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) } // Create the config entry for the terminating gateway. - CreateTerminatingGatewayConfigEntry(t, consulClient, testNamespace, testNamespace, staticServerName) + createTerminatingGatewayConfigEntry(t, consulClient, testNamespace, testNamespace, staticServerName) // Deploy the static client. logger.Log(t, "deploying static client") - k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") // If ACLs are enabled, test that intentions prevent connections. if c.secure { @@ -102,7 +99,7 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, nsK8SOptions, staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - AddIntention(t, consulClient, "", testNamespace, staticClientName, testNamespace, staticServerName) + addIntention(t, consulClient, testNamespace, staticClientName, testNamespace, staticServerName) } // Test that we can make a call to the terminating gateway. @@ -159,14 +156,14 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { logger.Logf(t, "creating Kubernetes namespace %s", testNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", testNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", testNamespace) }) StaticClientNamespace := "ns2" logger.Logf(t, "creating Kubernetes namespace %s", StaticClientNamespace) k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "ns", StaticClientNamespace) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", StaticClientNamespace) }) @@ -183,24 +180,24 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { // Deploy a static-server that will play the role of an external service. logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ns1K8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ns1K8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") // Register the external service - helpers.RegisterExternalService(t, consulClient, testNamespace, staticServerName, staticServerName, 80) + registerExternalService(t, consulClient, testNamespace) // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - UpdateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) + updateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) } // Create the config entry for the terminating gateway - CreateTerminatingGatewayConfigEntry(t, consulClient, "", testNamespace, staticServerName) + createTerminatingGatewayConfigEntry(t, consulClient, "", testNamespace, staticServerName) // Deploy the static client logger.Log(t, "deploying static client") - k8s.DeployKustomize(t, ns2K8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") + k8s.DeployKustomize(t, ns2K8SOptions, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-namespaces") // If ACLs are enabled, test that intentions prevent connections. if c.secure { @@ -211,7 +208,7 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ns2K8SOptions, staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - AddIntention(t, consulClient, "", StaticClientNamespace, staticClientName, testNamespace, staticServerName) + addIntention(t, consulClient, StaticClientNamespace, staticClientName, testNamespace, staticServerName) } // Test that we can make a call to the terminating gateway diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_test.go index acd0232227..16809de5e2 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package terminatinggateway import ( @@ -12,6 +9,8 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" ) // Test that terminating gateways work in a default and secure installations. @@ -49,27 +48,27 @@ func TestTerminatingGateway(t *testing.T) { // Deploy a static-server that will play the role of an external service. logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") // Once the cluster is up, register the external service, then create the config entry. consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) // Register the external service - helpers.RegisterExternalService(t, consulClient, "", staticServerName, staticServerName, 80) + registerExternalService(t, consulClient, "") // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - UpdateTerminatingGatewayRole(t, consulClient, staticServerPolicyRules) + updateTerminatingGatewayRole(t, consulClient, staticServerPolicyRules) } // Create the config entry for the terminating gateway. - CreateTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerName) + createTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerName) // Deploy the static client logger.Log(t, "deploying static client") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") // If ACLs are enabled, test that intentions prevent connections. if c.secure { @@ -80,7 +79,7 @@ func TestTerminatingGateway(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ctx.KubectlOptions(t), staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - AddIntention(t, consulClient, "", "", staticClientName, "", staticServerName) + addIntention(t, consulClient, "", staticClientName, "", staticServerName) } // Test that we can make a call to the terminating gateway. @@ -93,3 +92,34 @@ func TestTerminatingGateway(t *testing.T) { const staticServerPolicyRules = `service "static-server" { policy = "write" }` + +func registerExternalService(t *testing.T, consulClient *api.Client, namespace string) { + t.Helper() + + address := staticServerName + service := &api.AgentService{ + ID: staticServerName, + Service: staticServerName, + Port: 80, + } + + if namespace != "" { + address = fmt.Sprintf("%s.%s", staticServerName, namespace) + service.Namespace = namespace + + logger.Logf(t, "creating the %s namespace in Consul", namespace) + _, _, err := consulClient.Namespaces().Create(&api.Namespace{ + Name: namespace, + }, nil) + require.NoError(t, err) + } + + logger.Log(t, "registering the external service") + _, err := consulClient.Catalog().Register(&api.CatalogRegistration{ + Node: "legacy_node", + Address: address, + NodeMeta: map[string]string{"external-node": "true", "external-probe": "true"}, + Service: service, + }, nil) + require.NoError(t, err) +} diff --git a/acceptance/tests/vault/main_test.go b/acceptance/tests/vault/main_test.go index 02a22c2b79..6b38cad022 100644 --- a/acceptance/tests/vault/main_test.go +++ b/acceptance/tests/vault/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package vault import ( diff --git a/acceptance/tests/vault/vault_namespaces_test.go b/acceptance/tests/vault/vault_namespaces_test.go index 939795f199..fbedc7443c 100644 --- a/acceptance/tests/vault/vault_namespaces_test.go +++ b/acceptance/tests/vault/vault_namespaces_test.go @@ -25,8 +25,6 @@ import ( // It then configures Consul to use vault as the backend and checks that it works // with the vault namespace. Namespace is added in this via global.secretsBackend.vault.vaultNamespace. func TestVault_VaultNamespace(t *testing.T) { - t.Skipf("TODO(flaky): NET-5682") - cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) ns := ctx.KubectlOptions(t).Namespace @@ -265,13 +263,13 @@ func TestVault_VaultNamespace(t *testing.T) { // Deploy two services and check that they can talk to each other. logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") }) k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") diff --git a/acceptance/tests/vault/vault_partitions_test.go b/acceptance/tests/vault/vault_partitions_test.go index 63002993a6..609147b676 100644 --- a/acceptance/tests/vault/vault_partitions_test.go +++ b/acceptance/tests/vault/vault_partitions_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package vault import ( diff --git a/acceptance/tests/vault/vault_test.go b/acceptance/tests/vault/vault_test.go index 9dab0a3e71..fdde364b5b 100644 --- a/acceptance/tests/vault/vault_test.go +++ b/acceptance/tests/vault/vault_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package vault import ( @@ -17,7 +14,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/portforward" "github.com/hashicorp/consul-k8s/acceptance/framework/vault" - "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-uuid" "github.com/hashicorp/go-version" "github.com/stretchr/testify/require" @@ -35,29 +31,6 @@ const ( // TestVault installs Vault, bootstraps it with secrets, policies, and Kube Auth Method. // It then configures Consul to use vault as the backend and checks that it works. func TestVault(t *testing.T) { - cases := map[string]struct { - autoBootstrap bool - }{ - "manual ACL bootstrap": {}, - "automatic ACL bootstrap": { - autoBootstrap: true, - }, - } - for name, c := range cases { - c := c - t.Run(name, func(t *testing.T) { - testVault(t, c.autoBootstrap) - }) - } -} - -// testVault is the implementation for TestVault: -// -// - testAutoBootstrap = false. Test when ACL bootstrapping has already occurred. -// The test pre-populates a Vault secret with the bootstrap token. -// - testAutoBootstrap = true. Test that server-acl-init automatically ACL bootstraps -// consul and writes the bootstrap token to Vault. -func testVault(t *testing.T, testAutoBootstrap bool) { cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) kubectlOptions := ctx.KubectlOptions(t) @@ -111,6 +84,20 @@ func testVault(t *testing.T, testAutoBootstrap bool) { } serverPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) + // Configure controller webhook PKI + controllerWebhookPKIConfig := &vault.PKIAndAuthRoleConfiguration{ + BaseURL: "controller", + PolicyName: "controller-ca-policy", + RoleName: "controller-ca-role", + KubernetesNamespace: ns, + DataCenter: "dc1", + ServiceAccountName: fmt.Sprintf("%s-consul-%s", consulReleaseName, "controller"), + AllowedSubdomain: fmt.Sprintf("%s-consul-%s", consulReleaseName, "controller-webhook"), + MaxTTL: fmt.Sprintf("%ds", expirationInSeconds), + AuthMethodPath: KubernetesAuthMethodPath, + } + controllerWebhookPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) + // Configure connect injector webhook PKI connectInjectorWebhookPKIConfig := &vault.PKIAndAuthRoleConfiguration{ BaseURL: "connect", @@ -150,22 +137,16 @@ func testVault(t *testing.T, testAutoBootstrap bool) { licenseSecret.SaveSecretAndAddReadPolicy(t, vaultClient) } + // Bootstrap Token + bootstrapToken, err := uuid.GenerateUUID() + require.NoError(t, err) bootstrapTokenSecret := &vault.KV2Secret{ Path: "consul/data/secret/bootstrap", Key: "token", - Value: "", + Value: bootstrapToken, PolicyName: "bootstrap", } - if testAutoBootstrap { - bootstrapTokenSecret.SaveSecretAndAddUpdatePolicy(t, vaultClient) - } else { - id, err := uuid.GenerateUUID() - require.NoError(t, err) - bootstrapTokenSecret.Value = id - bootstrapTokenSecret.SaveSecretAndAddReadPolicy(t, vaultClient) - } - - bootstrapToken := bootstrapTokenSecret.Value + bootstrapTokenSecret.SaveSecretAndAddReadPolicy(t, vaultClient) // ------------------------- // Additional Auth Roles @@ -231,12 +212,15 @@ func testVault(t *testing.T, testAutoBootstrap bool) { "connectInject.replicas": "1", "global.secretsBackend.vault.connectInject.tlsCert.secretName": connectInjectorWebhookPKIConfig.CertPath, "global.secretsBackend.vault.connectInject.caCert.secretName": connectInjectorWebhookPKIConfig.CAPath, + "global.secretsBackend.vault.controller.tlsCert.secretName": controllerWebhookPKIConfig.CertPath, + "global.secretsBackend.vault.controller.caCert.secretName": controllerWebhookPKIConfig.CAPath, "global.secretsBackend.vault.enabled": "true", "global.secretsBackend.vault.consulServerRole": consulServerRole, "global.secretsBackend.vault.consulClientRole": consulClientRole, "global.secretsBackend.vault.consulCARole": serverPKIConfig.RoleName, "global.secretsBackend.vault.connectInjectRole": connectInjectorWebhookPKIConfig.RoleName, + "global.secretsBackend.vault.controllerRole": controllerWebhookPKIConfig.RoleName, "global.secretsBackend.vault.manageSystemACLsRole": manageSystemACLsRole, "global.secretsBackend.vault.ca.secretName": vaultCASecret, @@ -298,26 +282,6 @@ func testVault(t *testing.T, testAutoBootstrap bool) { logger.Logf(t, "Wait %d seconds for certificates to rotate....", expirationInSeconds) time.Sleep(time.Duration(expirationInSeconds) * time.Second) - if testAutoBootstrap { - logger.Logf(t, "Validating the ACL bootstrap token was stored in Vault.") - timer := &retry.Timer{Timeout: 10 * time.Second, Wait: 1 * time.Second} - retry.RunWith(timer, t, func(r *retry.R) { - secret, err := vaultClient.Logical().Read("consul/data/secret/bootstrap") - require.NoError(r, err) - - data, ok := secret.Data["data"].(map[string]interface{}) - require.True(r, ok) - require.NotNil(r, data) - - tok, ok := data["token"].(string) - require.True(r, ok) - require.NotEmpty(r, tok) - - // Set bootstrapToken for subsequent validations. - bootstrapToken = tok - }) - } - // Validate that the gossip encryption key is set correctly. logger.Log(t, "Validating the gossip key has been set correctly.") consulCluster.ACLToken = bootstrapToken @@ -350,13 +314,13 @@ func testVault(t *testing.T, testAutoBootstrap bool) { // Deploy two services and check that they can talk to each other. logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") }) k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") diff --git a/acceptance/tests/vault/vault_tls_auto_reload_test.go b/acceptance/tests/vault/vault_tls_auto_reload_test.go index d5d4d33c4c..f079ee7492 100644 --- a/acceptance/tests/vault/vault_tls_auto_reload_test.go +++ b/acceptance/tests/vault/vault_tls_auto_reload_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package vault import ( @@ -246,13 +243,13 @@ func TestVault_TLSAutoReload(t *testing.T) { // Deploy two services and check that they can talk to each other. logger.Log(t, "creating static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") }) k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention") diff --git a/acceptance/tests/vault/vault_wan_fed_test.go b/acceptance/tests/vault/vault_wan_fed_test.go index fa63c4d5fb..a65702907f 100644 --- a/acceptance/tests/vault/vault_wan_fed_test.go +++ b/acceptance/tests/vault/vault_wan_fed_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package vault import ( @@ -31,7 +28,6 @@ import ( // in the secondary that will treat the Vault server in the primary as an external server. func TestVault_WANFederationViaGateways(t *testing.T) { cfg := suite.Config() - if cfg.UseKind { t.Skipf("Skipping this test because it's currently flaky on kind") } @@ -492,18 +488,16 @@ func TestVault_WANFederationViaGateways(t *testing.T) { logger.Log(t, "creating proxy-defaults config") kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, primaryCtx.KubectlOptions(t), kustomizeDir) - - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, primaryCtx.KubectlOptions(t), kustomizeDir) }) // Check that we can connect services over the mesh gateways. - logger.Log(t, "creating static-server in dc2") - k8s.DeployKustomize(t, secondaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client in dc1") - k8s.DeployKustomize(t, primaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + k8s.DeployKustomize(t, primaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") logger.Log(t, "creating intention") _, _, err = primaryClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ @@ -520,7 +514,6 @@ func TestVault_WANFederationViaGateways(t *testing.T) { logger.Log(t, "checking that connection is successful") k8s.CheckStaticServerConnectionSuccessful(t, primaryCtx.KubectlOptions(t), StaticClientName, "http://localhost:1234") - } // vaultAddress returns Vault's server URL depending on test configuration. diff --git a/acceptance/tests/wan-federation/main_test.go b/acceptance/tests/wan-federation/main_test.go index 4a47a8a00f..4f6b0a7b54 100644 --- a/acceptance/tests/wan-federation/main_test.go +++ b/acceptance/tests/wan-federation/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package wanfederation import ( diff --git a/acceptance/tests/wan-federation/wan_federation_gateway_test.go b/acceptance/tests/wan-federation/wan_federation_gateway_test.go deleted file mode 100644 index ec466c93ec..0000000000 --- a/acceptance/tests/wan-federation/wan_federation_gateway_test.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package wanfederation - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/serf/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestWANFederation_Gateway(t *testing.T) { - env := suite.Environment() - cfg := suite.Config() - - if cfg.UseKind { - // the only way this test can currently run on kind, at least on a Mac, is via leveraging MetalLB, which - // isn't in CI, so we just skip for now. - t.Skipf("skipping wan federation tests as they currently fail on Kind even though they work on other clouds.") - } - - primaryContext := env.DefaultContext(t) - secondaryContext := env.Context(t, 1) - - primaryHelmValues := map[string]string{ - "global.datacenter": "dc1", - - "global.tls.enabled": "true", - "global.tls.httpsOnly": "true", - - "global.federation.enabled": "true", - "global.federation.createFederationSecret": "true", - - "global.acls.manageSystemACLs": "true", - "global.acls.createReplicationToken": "true", - - "connectInject.enabled": "true", - "connectInject.replicas": "1", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - } - - releaseName := helpers.RandomName() - - // Install the primary consul cluster in the default kubernetes context - primaryConsulCluster := consul.NewHelmCluster(t, primaryHelmValues, primaryContext, cfg, releaseName) - primaryConsulCluster.Create(t) - - var k8sAuthMethodHost string - // When running on kind, the kube API address in kubeconfig will have a localhost address - // which will not work from inside the container. That's why we need to use the endpoints address instead - // which will point the node IP. - if cfg.UseKind { - // The Kubernetes AuthMethod host is read from the endpoints for the Kubernetes service. - kubernetesEndpoint, err := secondaryContext.KubernetesClient(t).CoreV1().Endpoints("default").Get(context.Background(), "kubernetes", metav1.GetOptions{}) - require.NoError(t, err) - k8sAuthMethodHost = fmt.Sprintf("%s:%d", kubernetesEndpoint.Subsets[0].Addresses[0].IP, kubernetesEndpoint.Subsets[0].Ports[0].Port) - } else { - k8sAuthMethodHost = k8s.KubernetesAPIServerHostFromOptions(t, secondaryContext.KubectlOptions(t)) - } - - federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) - - // Create secondary cluster - secondaryHelmValues := map[string]string{ - "global.datacenter": "dc2", - - "global.tls.enabled": "true", - "global.tls.httpsOnly": "false", - "global.acls.manageSystemACLs": "true", - "global.tls.caCert.secretName": federationSecretName, - "global.tls.caCert.secretKey": "caCert", - "global.tls.caKey.secretName": federationSecretName, - "global.tls.caKey.secretKey": "caKey", - - "global.federation.enabled": "true", - - "server.extraVolumes[0].type": "secret", - "server.extraVolumes[0].name": federationSecretName, - "server.extraVolumes[0].load": "true", - "server.extraVolumes[0].items[0].key": "serverConfigJSON", - "server.extraVolumes[0].items[0].path": "config.json", - - "connectInject.enabled": "true", - "connectInject.replicas": "1", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - - "global.acls.replicationToken.secretName": federationSecretName, - "global.acls.replicationToken.secretKey": "replicationToken", - "global.federation.k8sAuthMethodHost": k8sAuthMethodHost, - "global.federation.primaryDatacenter": "dc1", - } - - // Install the secondary consul cluster in the secondary kubernetes context - secondaryConsulCluster := consul.NewHelmCluster(t, secondaryHelmValues, secondaryContext, cfg, releaseName) - secondaryConsulCluster.Create(t) - - primaryClient, _ := primaryConsulCluster.SetupConsulClient(t, true) - secondaryClient, _ := secondaryConsulCluster.SetupConsulClient(t, true) - - // Verify federation between servers - logger.Log(t, "verifying federation was successful") - helpers.VerifyFederation(t, primaryClient, secondaryClient, releaseName, true) - - // Create a ProxyDefaults resource to configure services to use the mesh - // gateways. - logger.Log(t, "creating proxy-defaults config in dc1") - kustomizeDir := "../fixtures/cases/api-gateways/mesh" - k8s.KubectlApplyK(t, primaryContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, primaryContext.KubectlOptions(t), kustomizeDir) - }) - - // these clients are just there so we can exec in and curl on them. - logger.Log(t, "creating static-client in dc1") - k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") - - logger.Log(t, "creating static-client in dc2") - k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") - - t.Run("from primary to secondary", func(t *testing.T) { - logger.Log(t, "creating static-server in dc2") - k8s.DeployKustomize(t, secondaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - logger.Log(t, "creating api-gateway resources in dc1") - out, err := k8s.RunKubectlAndGetOutputE(t, primaryContext.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, primaryContext.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") - }) - - // create a service resolver for doing cross-dc redirects. - k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc1-to-dc2-resolver") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc1-to-dc2-resolver") - }) - - // patching the route to target a MeshService since we don't have the corresponding Kubernetes service in this - // cluster. - k8s.RunKubectl(t, primaryContext.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") - - checkConnectivity(t, primaryContext, primaryClient) - }) - - t.Run("from secondary to primary", func(t *testing.T) { - // Check that we can connect services over the mesh gateways - logger.Log(t, "creating static-server in dc1") - k8s.DeployKustomize(t, primaryContext.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") - - logger.Log(t, "creating api-gateway resources in dc2") - out, err := k8s.RunKubectlAndGetOutputE(t, secondaryContext.KubectlOptions(t), "apply", "-k", "../fixtures/bases/api-gateway") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, secondaryContext.KubectlOptions(t), "delete", "-k", "../fixtures/bases/api-gateway") - }) - - // create a service resolver for doing cross-dc redirects. - k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc2-to-dc1-resolver") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), "../fixtures/cases/api-gateways/dc2-to-dc1-resolver") - }) - - // patching the route to target a MeshService since we don't have the corresponding Kubernetes service in this - // cluster. - k8s.RunKubectl(t, secondaryContext.KubectlOptions(t), "patch", "httproute", "http-route", "-p", `{"spec":{"rules":[{"backendRefs":[{"group":"consul.hashicorp.com","kind":"MeshService","name":"mesh-service","port":80}]}]}}`, "--type=merge") - - checkConnectivity(t, secondaryContext, primaryClient) - }) -} - -func checkConnectivity(t *testing.T, ctx environment.TestContext, client *api.Client) { - k8sClient := ctx.ControllerRuntimeClient(t) - - // On startup, the controller can take upwards of 1m to perform - // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 1m timeout here). - var gatewayAddress string - counter := &retry.Counter{Count: 600, Wait: 2 * time.Second} - retry.RunWith(counter, t, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err := k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) - require.NoError(r, err) - - // check that we have an address to use - require.Len(r, gateway.Status.Addresses, 1) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.Status.Addresses[0].Value - }) - - targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) - - logger.Log(t, "checking that the connection is not successful because there's no intention") - k8s.CheckStaticServerHTTPConnectionFailing(t, ctx.KubectlOptions(t), connhelper.StaticClientName, targetAddress) - - logger.Log(t, "creating intention") - _, _, err := client.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - defer func() { - _, err := client.ConfigEntries().Delete(api.ServiceIntentions, "static-server", &api.WriteOptions{}) - require.NoError(t, err) - }() - - logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, targetAddress) -} diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index 62567ea561..d347695faa 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package wanfederation import ( @@ -8,35 +5,17 @@ import ( "fmt" "strconv" "testing" - "time" - terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - staticClientDeployment = "deploy/static-client" - staticServerDeployment = "deploy/static-server" - - retryTimeout = 5 * time.Minute - - primaryDatacenter = "dc1" - secondaryDatacenter = "dc2" - - localServerPort = "1234" - - primaryNamespace = "ns1" - secondaryNamespace = "ns2" -) +const StaticClientName = "static-client" // Test that Connect and wan federation over mesh gateways work in a default installation // i.e. without ACLs because TLS is required for WAN federation over mesh gateways. @@ -65,7 +44,7 @@ func TestWANFederation(t *testing.T) { secondaryContext := env.Context(t, 1) primaryHelmValues := map[string]string{ - "global.datacenter": primaryDatacenter, + "global.datacenter": "dc1", "global.tls.enabled": "true", "global.tls.httpsOnly": strconv.FormatBool(c.secure), @@ -95,13 +74,31 @@ func TestWANFederation(t *testing.T) { primaryConsulCluster.Create(t) // Get the federation secret from the primary cluster and apply it to secondary cluster - federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) - - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryContext) + federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) + logger.Logf(t, "retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) + federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) + require.NoError(t, err) + federationSecret.ResourceVersion = "" + federationSecret.Namespace = secondaryContext.KubectlOptions(t).Namespace + _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + var k8sAuthMethodHost string + // When running on kind, the kube API address in kubeconfig will have a localhost address + // which will not work from inside the container. That's why we need to use the endpoints address instead + // which will point the node IP. + if cfg.UseKind { + // The Kubernetes AuthMethod host is read from the endpoints for the Kubernetes service. + kubernetesEndpoint, err := secondaryContext.KubernetesClient(t).CoreV1().Endpoints("default").Get(context.Background(), "kubernetes", metav1.GetOptions{}) + require.NoError(t, err) + k8sAuthMethodHost = fmt.Sprintf("%s:%d", kubernetesEndpoint.Subsets[0].Addresses[0].IP, kubernetesEndpoint.Subsets[0].Ports[0].Port) + } else { + k8sAuthMethodHost = k8s.KubernetesAPIServerHostFromOptions(t, secondaryContext.KubectlOptions(t)) + } // Create secondary cluster secondaryHelmValues := map[string]string{ - "global.datacenter": secondaryDatacenter, + "global.datacenter": "dc2", "global.tls.enabled": "true", "global.tls.httpsOnly": "false", @@ -130,7 +127,7 @@ func TestWANFederation(t *testing.T) { secondaryHelmValues["global.acls.replicationToken.secretName"] = federationSecretName secondaryHelmValues["global.acls.replicationToken.secretKey"] = "replicationToken" secondaryHelmValues["global.federation.k8sAuthMethodHost"] = k8sAuthMethodHost - secondaryHelmValues["global.federation.primaryDatacenter"] = primaryDatacenter + secondaryHelmValues["global.federation.primaryDatacenter"] = "dc1" } if cfg.UseKind { @@ -154,7 +151,7 @@ func TestWANFederation(t *testing.T) { logger.Log(t, "creating proxy-defaults config") kustomizeDir := "../fixtures/bases/mesh-gateway" k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), kustomizeDir) }) @@ -184,280 +181,17 @@ func TestWANFederation(t *testing.T) { // Check that we can connect services over the mesh gateways logger.Log(t, "creating static-server in dc2") - k8s.DeployKustomize(t, secondaryHelper.KubectlOptsForApp(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, secondaryHelper.KubectlOptsForApp(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") logger.Log(t, "creating static-client in dc1") - k8s.DeployKustomize(t, primaryHelper.KubectlOptsForApp(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") + k8s.DeployKustomize(t, primaryHelper.KubectlOptsForApp(t), cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") if c.secure { - primaryHelper.CreateIntention(t, connhelper.IntentionOpts{}) + primaryHelper.CreateIntention(t) } logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, primaryHelper.KubectlOptsForApp(t), connhelper.StaticClientName, "http://localhost:1234") - }) - } -} - -// Test failover scenarios with a static-server in dc1 and a static-server -// in dc2. Use the static-client on dc1 to reach static-server on dc1 in the -// nominal scenario, then cause a failure in dc1 static-server to see the static-client failover to -// the static-server in dc2 -/* - dc1-static-client -- nominal -- > dc1-static-server in namespace ns1 - dc1-static-client -- failover --> dc2-static-server in namespace ns1 - dc1-static-client -- failover --> dc1-static-server in namespace ns2 -*/ -func TestWANFederationFailover(t *testing.T) { - cases := []struct { - name string - secure bool - }{ - { - name: "secure", - secure: true, - }, - { - name: "default", - secure: false, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - env := suite.Environment() - cfg := suite.Config() - - if cfg.EnableRestrictedPSAEnforcement { - t.Skip("This test case is not run with enable restricted PSA enforcement enabled") - } - - primaryContext := env.DefaultContext(t) - secondaryContext := env.Context(t, 1) - - primaryHelmValues := map[string]string{ - "global.datacenter": primaryDatacenter, - - "global.tls.enabled": "true", - "global.tls.httpsOnly": strconv.FormatBool(c.secure), - - "global.federation.enabled": "true", - "global.federation.createFederationSecret": "true", - - "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), - "global.acls.createReplicationToken": strconv.FormatBool(c.secure), - - "connectInject.enabled": "true", - "connectInject.replicas": "1", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - - "global.enableConsulNamespaces": "true", - "connectInject.consulNamespaces.mirroringK8S": "true", - } - - if cfg.UseKind { - primaryHelmValues["meshGateway.service.type"] = "NodePort" - primaryHelmValues["meshGateway.service.nodePort"] = "30000" - } - - releaseName := helpers.RandomName() - - // Install the primary consul cluster in the default kubernetes context - primaryConsulCluster := consul.NewHelmCluster(t, primaryHelmValues, primaryContext, cfg, releaseName) - primaryConsulCluster.Create(t) - - // Get the federation secret from the primary cluster and apply it to secondary cluster - federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) - - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryContext) - - // Create secondary cluster - secondaryHelmValues := map[string]string{ - "global.datacenter": secondaryDatacenter, - - "global.tls.enabled": "true", - "global.tls.httpsOnly": "false", - "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), - "global.tls.caCert.secretName": federationSecretName, - "global.tls.caCert.secretKey": "caCert", - "global.tls.caKey.secretName": federationSecretName, - "global.tls.caKey.secretKey": "caKey", - - "global.federation.enabled": "true", - - "server.extraVolumes[0].type": "secret", - "server.extraVolumes[0].name": federationSecretName, - "server.extraVolumes[0].load": "true", - "server.extraVolumes[0].items[0].key": "serverConfigJSON", - "server.extraVolumes[0].items[0].path": "config.json", - - "connectInject.enabled": "true", - "connectInject.replicas": "1", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - - "global.enableConsulNamespaces": "true", - "connectInject.consulNamespaces.mirroringK8S": "true", - } - - if c.secure { - secondaryHelmValues["global.acls.replicationToken.secretName"] = federationSecretName - secondaryHelmValues["global.acls.replicationToken.secretKey"] = "replicationToken" - secondaryHelmValues["global.federation.k8sAuthMethodHost"] = k8sAuthMethodHost - secondaryHelmValues["global.federation.primaryDatacenter"] = primaryDatacenter - } - - if cfg.UseKind { - secondaryHelmValues["meshGateway.service.type"] = "NodePort" - secondaryHelmValues["meshGateway.service.nodePort"] = "30000" - } - - // Install the secondary consul cluster in the secondary kubernetes context - secondaryConsulCluster := consul.NewHelmCluster(t, secondaryHelmValues, secondaryContext, cfg, releaseName) - secondaryConsulCluster.Create(t) - - primaryClient, _ := primaryConsulCluster.SetupConsulClient(t, c.secure) - secondaryClient, _ := secondaryConsulCluster.SetupConsulClient(t, c.secure) - - // Verify federation between servers - logger.Log(t, "Verifying federation was successful") - helpers.VerifyFederation(t, primaryClient, secondaryClient, releaseName, c.secure) - - // Create a ProxyDefaults resource to configure services to use the mesh - // gateways. - logger.Log(t, "Creating proxy-defaults config") - kustomizeDir := "../fixtures/bases/mesh-gateway" - k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), kustomizeDir) - }) - - primaryHelper := connhelper.ConnectHelper{ - Secure: c.secure, - ReleaseName: releaseName, - Ctx: primaryContext, - UseAppNamespace: false, - Cfg: cfg, - ConsulClient: primaryClient, - } - secondaryHelper := connhelper.ConnectHelper{ - Secure: c.secure, - ReleaseName: releaseName, - Ctx: secondaryContext, - UseAppNamespace: false, - Cfg: cfg, - ConsulClient: secondaryClient, - } - - // Create Namespaces - // We create a namespace (ns1) in both the primary and secondary datacenters (dc1, dc2) - // We then create a secondary namespace (ns2) in the primary datacenter (dc1) - primaryNamespaceOpts := primaryHelper.Ctx.KubectlOptionsForNamespace(primaryNamespace) - primaryHelper.CreateNamespace(t, primaryNamespaceOpts.Namespace) - primarySecondaryNamepsaceOpts := primaryHelper.Ctx.KubectlOptionsForNamespace(secondaryNamespace) - primaryHelper.CreateNamespace(t, primarySecondaryNamepsaceOpts.Namespace) - secondaryNamespaceOpts := secondaryHelper.Ctx.KubectlOptionsForNamespace(primaryNamespace) - secondaryHelper.CreateNamespace(t, secondaryNamespaceOpts.Namespace) - - // Create a static-server in dc2 to respond with its own name for checking failover. - logger.Log(t, "Creating static-server in dc2") - k8s.DeployKustomize(t, secondaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc2-static-server") - - // Spin up a server on dc1 which will be the primary upstream for our client - logger.Log(t, "Creating static-server in dc1") - k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc1-static-server") - logger.Log(t, "Creating static-client in dc1") - k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/static-client") - - // Spin up a second server on dc1 in a separate namespace - logger.Logf(t, "Creating server on dc1 in namespace %s", primarySecondaryNamepsaceOpts.Namespace) - k8s.DeployKustomize(t, primarySecondaryNamepsaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc1-ns2-static-server") - - // There is currently an issue that requires the intentions and resolvers to be created after - // the static-server/clients when using namespaces. When created before, Consul gives a "namespace does not exist" - // error - if c.secure { - // Only need to create intentions in the primary datacenter as they will be replicated to the secondary - // ns1 static-client (source) -> ns1 static-server (destination) - primaryHelper.CreateIntention(t, connhelper.IntentionOpts{DestinationNamespace: primaryNamespaceOpts.Namespace, SourceNamespace: primaryNamespaceOpts.Namespace}) - - // ns1 static-client (source) -> ns2 static-server (destination) - primaryHelper.CreateIntention(t, connhelper.IntentionOpts{DestinationNamespace: primarySecondaryNamepsaceOpts.Namespace, SourceNamespace: primaryNamespaceOpts.Namespace}) - } - - // Create a service resolver for failover - logger.Log(t, "Creating service resolver") - k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/service-resolver") - - // Verify that we respond with the static-server in the primary datacenter - logger.Log(t, "Verifying static-server in dc1 responds") - serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, primaryDatacenter) - - // Scale down the primary datacenter static-server and see the failover - logger.Log(t, "Scale down dc1 static-server") - k8s.KubectlScale(t, primaryNamespaceOpts, staticServerDeployment, 0) - - // Verify that we respond with the static-server in the secondary datacenter - logger.Log(t, "Verifying static-server in dc2 responds") - serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, secondaryDatacenter) - - // scale down the primary datacenter static-server and see the failover - logger.Log(t, "Scale down dc2 static-server") - k8s.KubectlScale(t, secondaryNamespaceOpts, staticServerDeployment, 0) - - // Verify that we respond with the static-server in the secondary datacenter - logger.Log(t, "Verifying static-server in secondary namespace (ns2) responds") - serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, secondaryNamespace) + k8s.CheckStaticServerConnectionSuccessful(t, primaryHelper.KubectlOptsForApp(t), StaticClientName, "http://localhost:1234") }) } } - -// serviceFailoverCheck verifies that the server failed over as expected by checking that curling the `static-server` -// using the `static-client` responds with the expected cluster name. Each static-server responds with a unique -// name so that we can verify failover occurred as expected. -func serviceFailoverCheck(t *testing.T, options *terratestK8s.KubectlOptions, port string, expectedName string) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - var resp string - var err error - - // Retry until we get the response we expect, sometimes you get back the previous server until things stabalize - logger.Log(t, "Initial failover check") - retry.RunWith(timer, t, func(r *retry.R) { - resp, err = k8s.RunKubectlAndGetOutputE(r, options, "exec", "-i", - staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) - assert.NoError(r, err) - assert.Contains(r, resp, expectedName) - }) - - // Try again to rule out load-balancing. Errors can still happen so retry - logger.Log(t, "Check failover again to rule out load balancing") - for i := 0; i < 10; i++ { - time.Sleep(500 * time.Millisecond) - resp = "" - retry.RunWith(timer, t, func(r *retry.R) { - resp, err = k8s.RunKubectlAndGetOutputE(r, options, "exec", "-i", - staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) - assert.NoError(r, err) - }) - require.Contains(t, resp, expectedName) - } - - logger.Log(t, resp) -} - -func copyFederationSecret(t *testing.T, releaseName string, primaryContext, secondaryContext environment.TestContext) string { - // Get the federation secret from the primary cluster and apply it to secondary cluster - federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) - logger.Logf(t, "Retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) - federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) - require.NoError(t, err) - federationSecret.ResourceVersion = "" - federationSecret.Namespace = secondaryContext.KubectlOptions(t).Namespace - _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) - require.NoError(t, err) - - return federationSecretName -} diff --git a/charts/consul/.helmignore b/charts/consul/.helmignore index 3fa2f24edf..d1180d2fb7 100644 --- a/charts/consul/.helmignore +++ b/charts/consul/.helmignore @@ -2,4 +2,3 @@ .terraform/ bin/ test/ -crds/kustomization.yaml diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index d74a42ba95..bb04b74e49 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -1,11 +1,8 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v2 name: consul -version: 1.5.0-dev -appVersion: 1.19-dev -kubeVersion: ">=1.22.0-0" +version: 1.0.12-dev +appVersion: 1.14-dev +kubeVersion: ">=1.21.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io icon: https://raw.githubusercontent.com/hashicorp/consul-k8s/main/assets/icon.png @@ -16,13 +13,13 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.19-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.14-dev - name: consul-k8s-control-plane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.5-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.0.12-dev - name: consul-dataplane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.5-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.0-dev - name: envoy - image: envoyproxy/envoy:v1.25.11 + image: envoyproxy/envoy:v1.24.12 artifacthub.io/license: MPL-2.0 artifacthub.io/links: | - name: Documentation diff --git a/charts/consul/addons/gen.sh b/charts/consul/addons/gen.sh index 1d03390bed..967b368c63 100755 --- a/charts/consul/addons/gen.sh +++ b/charts/consul/addons/gen.sh @@ -1,7 +1,4 @@ #!/usr/bin/env bash -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - WD=$(dirname "$0") WD=$(cd "$WD"; pwd) diff --git a/charts/consul/addons/values/prometheus.yaml b/charts/consul/addons/values/prometheus.yaml index 1f90636f2e..9ffe9af5ae 100644 --- a/charts/consul/addons/values/prometheus.yaml +++ b/charts/consul/addons/values/prometheus.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # Disable non-essential components alertmanager: enabled: false diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index b78987f908..bdcf2ecb19 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -15,8 +15,23 @@ as well as the global.name setting. {{- end -}} {{- end -}} + {{- define "consul.restrictedSecurityContext" -}} {{- if not .Values.global.enablePodSecurityPolicies -}} +{{/* +To be compatible with the 'restricted' Pod Security Standards profile, we +should set this securityContext on containers whenever possible. + +In OpenShift < 4.11 the restricted SCC disallows setting most of these fields, +so we do not set any for simplicity (and because that's how it was configured +prior to adding restricted PSA support here). In OpenShift >= 4.11, the new +restricted-v2 SCC allows setting these in the securityContext, and by setting +them we avoid PSA warnings that are enabled by default. + +We use the K8s version as a proxy for the OpenShift version because there is a +1:1 mapping of versions. OpenShift 4.11 corresponds to K8s 1.24.x. +*/}} +{{- if (or (not .Values.global.openshift.enabled) (and (ge .Capabilities.KubeVersion.Major "1") (ge .Capabilities.KubeVersion.Minor "24"))) -}} securityContext: allowPrivilegeEscalation: false capabilities: @@ -27,11 +42,12 @@ securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault +{{- end -}} {{- if not .Values.global.openshift.enabled -}} {{/* We must set runAsUser or else the root user will be used in some cases and containers will fail to start due to runAsNonRoot above (e.g. -tls-init-cleanup). On OpenShift, runAsUser is automatically. We pick user 100 +tls-init-cleanup). On OpenShift, runAsUser is set automatically. We pick user 100 because it is a non-root user id that exists in the consul, consul-dataplane, and consul-k8s-control-plane images. */}} @@ -98,6 +114,22 @@ and consul-k8s-control-plane images. {{ "{{" }}- end -{{ "}}" }} {{- end -}} +{{- define "consul.controllerWebhookTLSCertTemplate" -}} + | + {{ "{{" }}- with secret "{{ .Values.global.secretsBackend.vault.controller.tlsCert.secretName }}" "{{- $name := include "consul.fullname" . -}}{{ printf "common_name=%s-controller-webhook" $name }}" + "alt_names={{ include "consul.controllerWebhookTLSAltNames" . }}" -{{ "}}" }} + {{ "{{" }}- .Data.certificate -{{ "}}" }} + {{ "{{" }}- end -{{ "}}" }} +{{- end -}} + +{{- define "consul.controllerWebhookTLSKeyTemplate" -}} + | + {{ "{{" }}- with secret "{{ .Values.global.secretsBackend.vault.controller.tlsCert.secretName }}" "{{- $name := include "consul.fullname" . -}}{{ printf "common_name=%s-controller-webhook" $name }}" + "alt_names={{ include "consul.controllerWebhookTLSAltNames" . }}" -{{ "}}" }} + {{ "{{" }}- .Data.private_key -{{ "}}" }} + {{ "{{" }}- end -{{ "}}" }} +{{- end -}} + {{- define "consul.serverTLSAltNames" -}} {{- $name := include "consul.fullname" . -}} {{- $ns := .Release.Namespace -}} @@ -118,6 +150,12 @@ and consul-k8s-control-plane images. {{ printf "%s-connect-injector,%s-connect-injector.%s,%s-connect-injector.%s.svc,%s-connect-injector.%s.svc.cluster.local" $name $name $ns $name $ns $name $ns}} {{- end -}} +{{- define "consul.controllerWebhookTLSAltNames" -}} +{{- $name := include "consul.fullname" . -}} +{{- $ns := .Release.Namespace -}} +{{ printf "%s-controller-webhook,%s-controller-webhook.%s,%s-controller-webhook.%s.svc,%s-controller-webhook.%s.svc.cluster.local" $name $name $ns $name $ns $name $ns}} +{{- end -}} + {{- define "consul.vaultReplicationTokenTemplate" -}} | {{ "{{" }}- with secret "{{ .Values.global.acls.replicationToken.secretName }}" -{{ "}}" }} @@ -145,7 +183,7 @@ substitution for HOST_IP/POD_IP/HOSTNAME. Useful for dogstats telemetry. The out is passed to consul as a -config-file param on command line. */}} {{- define "consul.extraconfig" -}} - cp /consul/tmp/extra-config/extra-from-values.json /consul/extra-config/extra-from-values.json + cp /consul/config/extra-from-values.json /consul/extra-config/extra-from-values.json [ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /consul/extra-config/extra-from-values.json [ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /consul/extra-config/extra-from-values.json [ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /consul/extra-config/extra-from-values.json @@ -166,27 +204,24 @@ Expand the name of the chart. {{- end -}} {{/* -Calculate max number of server pods that are allowed to be voluntarily disrupted. -When there's 1 server, this is set to 0 because this pod should not be disrupted. This is an edge -case and I'm not sure it makes a difference when there's only one server but that's what the previous config was and -I don't want to change it for this edge case. -Otherwise we've changed this to always be 1 as part of the move to set leave_on_terminate -to true. With leave_on_terminate set to true, whenever a server pod is stopped, the number of peers in raft -is reduced. If the number of servers is odd and the count is reduced by 1, the quorum size doesn't change, -but if it's reduced by more than 1, the quorum size can change so that's why this is now always hardcoded to 1. +Compute the maximum number of unavailable replicas for the PodDisruptionBudget. +This defaults to (n/2)-1 where n is the number of members of the server cluster. +Special case of replica equaling 3 and allowing a minor disruption of 1 otherwise +use the integer value +Add a special case for replicas=1, where it should default to 0 as well. */}} -{{- define "consul.server.pdb.maxUnavailable" -}} +{{- define "consul.pdb.maxUnavailable" -}} {{- if eq (int .Values.server.replicas) 1 -}} {{ 0 }} {{- else if .Values.server.disruptionBudget.maxUnavailable -}} {{ .Values.server.disruptionBudget.maxUnavailable -}} {{- else -}} -{{ 1 }} +{{- if eq (int .Values.server.replicas) 3 -}} +{{- 1 -}} +{{- else -}} +{{- sub (div (int .Values.server.replicas) 2) 1 -}} {{- end -}} {{- end -}} - -{{- define "consul.server.autopilotMinQuorum" -}} -{{- add (div (int .Values.server.replicas) 2) 1 -}} {{- end -}} {{- define "consul.pdb.connectInject.maxUnavailable" -}} @@ -291,17 +326,20 @@ Fails when at least one but not all of the following have been set: - global.secretsBackend.vault.connectInjectRole - global.secretsBackend.vault.connectInject.tlsCert.secretName - global.secretsBackend.vault.connectInject.caCert.secretName +- global.secretsBackend.vault.controllerRole +- global.secretsBackend.vault.controller.tlsCert.secretName +- global.secretsBackend.vault.controller.caCert.secretName The above values are needed in full to turn off web cert manager and allow -connect inject to manage its own webhook certs. +connect inject and controller to manage its own webhook certs. Usage: {{ template "consul.validateVaultWebhookCertConfiguration" . }} */}} {{- define "consul.validateVaultWebhookCertConfiguration" -}} -{{- if or .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName}} -{{- if or (not .Values.global.secretsBackend.vault.connectInjectRole) (not .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) (not .Values.global.secretsBackend.vault.connectInject.caCert.secretName) }} -{{fail "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName"}} +{{- if or .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName}} +{{- if or (not .Values.global.secretsBackend.vault.connectInjectRole) (not .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) (not .Values.global.secretsBackend.vault.connectInject.caCert.secretName) (not .Values.global.secretsBackend.vault.controllerRole) (not .Values.global.secretsBackend.vault.controller.tlsCert.secretName) (not .Values.global.secretsBackend.vault.controller.caCert.secretName) }} +{{fail "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName."}} {{ end }} {{ end }} {{- end -}} @@ -363,7 +401,7 @@ Consul server environment variables for consul-k8s commands. {{- end }} {{- if and .Values.externalServers.enabled .Values.externalServers.skipServerWatch }} - name: CONSUL_SKIP_SERVER_WATCH - value: "true" + value: "true" {{- end }} {{- end -}} @@ -394,7 +432,7 @@ Usage: {{ template "consul.validateCloudSecretKeys" . }} */}} {{- define "consul.validateCloudSecretKeys" -}} -{{- if and .Values.global.cloud.enabled }} +{{- if and .Values.global.cloud.enabled }} {{- if or (and .Values.global.cloud.resourceId.secretName (not .Values.global.cloud.resourceId.secretKey)) (and .Values.global.cloud.resourceId.secretKey (not .Values.global.cloud.resourceId.secretName)) }} {{fail "When either global.cloud.resourceId.secretName or global.cloud.resourceId.secretKey is defined, both must be set."}} {{- end }} @@ -416,106 +454,3 @@ Usage: {{ template "consul.validateCloudSecretKeys" . }} {{- end }} {{- end -}} - -{{/* -Fails if telemetryCollector.clientId or telemetryCollector.clientSecret exist and one of other secrets is nil or empty. -- telemetryCollector.cloud.clientId.secretName -- telemetryCollector.cloud.clientSecret.secretName -- global.cloud.resourceId.secretName - -Usage: {{ template "consul.validateTelemetryCollectorCloud" . }} - -*/}} -{{- define "consul.validateTelemetryCollectorCloud" -}} -{{- if (and .Values.telemetryCollector.cloud.clientId.secretName (and (not .Values.global.cloud.clientSecret.secretName) (not .Values.telemetryCollector.cloud.clientSecret.secretName))) }} -{{fail "When telemetryCollector.cloud.clientId.secretName is set, telemetryCollector.cloud.clientSecret.secretName must also be set."}} -{{- end }} -{{- if (and .Values.telemetryCollector.cloud.clientSecret.secretName (and (not .Values.global.cloud.clientId.secretName) (not .Values.telemetryCollector.cloud.clientId.secretName))) }} -{{fail "When telemetryCollector.cloud.clientSecret.secretName is set, telemetryCollector.cloud.clientId.secretName must also be set."}} -{{- end }} -{{- end }} - -{{/**/}} - -{{- define "consul.validateTelemetryCollectorCloudSecretKeys" -}} -{{- if or (and .Values.telemetryCollector.cloud.clientId.secretName (not .Values.telemetryCollector.cloud.clientId.secretKey)) (and .Values.telemetryCollector.cloud.clientId.secretKey (not .Values.telemetryCollector.cloud.clientId.secretName)) }} -{{fail "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set."}} -{{- end }} -{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName (not .Values.telemetryCollector.cloud.clientSecret.secretKey)) (and .Values.telemetryCollector.cloud.clientSecret.secretKey (not .Values.telemetryCollector.cloud.clientSecret.secretName)) }} -{{fail "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set."}} -{{- end }} -{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not (or .Values.telemetryCollector.cloud.resourceId.secretName .Values.global.cloud.resourceId.secretName))) }} -{{fail "When telemetryCollector has clientId and clientSecret, telemetryCollector.cloud.resourceId.secretName or global.cloud.resourceId.secretName must be set"}} -{{- end }} -{{- if or (and .Values.telemetryCollector.cloud.clientSecret.secretName .Values.telemetryCollector.cloud.clientSecret.secretKey .Values.telemetryCollector.cloud.clientId.secretName .Values.telemetryCollector.cloud.clientId.secretKey (not (or .Values.telemetryCollector.cloud.resourceId.secretKey .Values.global.cloud.resourceId.secretKey))) }} -{{fail "When telemetryCollector has clientId and clientSecret, telemetryCollector.cloud.resourceId.secretKey or global.cloud.resourceId.secretKey must be set"}} -{{- end }} -{{- end -}} - -{{/* -Fails if telemetryCollector.cloud.resourceId is set but differs from global.cloud.resourceId. This should never happen. Either one or both are set, but they should never differ. -If they differ, that implies we're configuring servers for one HCP Consul cluster but pushing envoy metrics for a different HCP Consul cluster. A user could set the same value -in two secrets (it's questionable whether resourceId should be a secret at all) but we won't know at this point, so we just check secret name+key. - -Usage: {{ template "consul.validateTelemetryCollectorResourceId" . }} - -*/}} -{{- define "consul.validateTelemetryCollectorResourceId" -}} -{{- if and (and .Values.telemetryCollector.cloud.resourceId.secretName .Values.global.cloud.resourceId.secretName) (not (eq .Values.telemetryCollector.cloud.resourceId.secretName .Values.global.cloud.resourceId.secretName)) }} -{{fail "When both global.cloud.resourceId.secretName and telemetryCollector.cloud.resourceId.secretName are set, they should be the same."}} -{{- end }} -{{- if and (and .Values.telemetryCollector.cloud.resourceId.secretKey .Values.global.cloud.resourceId.secretKey) (not (eq .Values.telemetryCollector.cloud.resourceId.secretKey .Values.global.cloud.resourceId.secretKey)) }} -{{fail "When both global.cloud.resourceId.secretKey and telemetryCollector.cloud.resourceId.secretKey are set, they should be the same."}} -{{- end }} -{{- end }} - -{{/**/}} - -{{/* -Fails if global.experiments.resourceAPIs is set along with any of these unsupported features. -- global.peering.enabled -- global.federation.enabled -- global.cloud.enabled -- client.enabled -- ui.enabled -- syncCatalog.enabled -- meshGateway.enabled -- ingressGateways.enabled -- terminatingGateways.enabled -- apiGateway.enabled - -Usage: {{ template "consul.validateResourceAPIs" . }} - -*/}} -{{- define "consul.validateResourceAPIs" -}} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.peering.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) (not (mustHas "v2tenancy" .Values.global.experiments)) .Values.global.adminPartitions.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.experiments.v2tenancy must also be set to support global.adminPartitions.enabled."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.federation.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.federation.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.cloud.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.cloud.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.client.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, client.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.ui.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, ui.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.syncCatalog.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, syncCatalog.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.ingressGateways.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, ingressGateways.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.terminatingGateways.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, terminatingGateways.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.apiGateway.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, apiGateway.enabled is currently unsupported."}} -{{- end }} -{{- end }} diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 1bd1f8500a..11396c8a03 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -30,7 +30,6 @@ spec: metadata: annotations: consul.hashicorp.com/connect-inject: "false" - consul.hashicorp.com/mesh-inject: "false" {{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.tls.enabled) }} "vault.hashicorp.com/agent-init-first": "true" "vault.hashicorp.com/agent-inject": "true" diff --git a/charts/consul/templates/client-config-configmap.yaml b/charts/consul/templates/client-config-configmap.yaml index cab2c7c043..d91a4d21bf 100644 --- a/charts/consul/templates/client-config-configmap.yaml +++ b/charts/consul/templates/client-config-configmap.yaml @@ -25,10 +25,13 @@ data: "log_level": "{{ .Values.client.logLevel | upper }}" {{- end }} } + extra-from-values.json: |- +{{ tpl .Values.client.extraConfig . | trimAll "\"" | indent 4 }} central-config.json: |- { "enable_central_service_config": true } + {{- if .Values.connectInject.enabled }} {{/* We set check_update_interval to 0s so that check output is immediately viewable in the UI. */}} diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index dd0454b10d..61425cfdb8 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -86,8 +86,7 @@ spec: {{- end }} {{- end }} "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" - "consul.hashicorp.com/config-checksum": {{ print (include (print $.Template.BasePath "/client-config-configmap.yaml") .) (include (print $.Template.BasePath "/client-tmp-extra-config-configmap.yaml") .) | sha256sum }} + "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/client-config-configmap.yaml") . | sha256sum }} {{- if .Values.client.annotations }} {{- tpl .Values.client.annotations . | nindent 8 }} {{- end }} @@ -142,9 +141,6 @@ spec: - name: consul-data emptyDir: medium: "Memory" - - name: tmp-extra-config - configMap: - name: {{ template "consul.fullname" . }}-client-tmp-extra-config {{- if .Values.global.tls.enabled }} {{- if not .Values.global.secretsBackend.vault.enabled }} - name: consul-ca-cert @@ -393,7 +389,7 @@ spec: {{- range $value := .Values.global.recursors }} -recursor={{ quote $value }} \ {{- end }} - -config-dir=/consul/extra-config \ + -config-file=/consul/extra-config/extra-from-values.json \ -domain={{ .Values.global.domain }} volumeMounts: - name: data @@ -402,8 +398,6 @@ spec: mountPath: /consul/config - name: extra-config mountPath: /consul/extra-config - - name: tmp-extra-config - mountPath: /consul/tmp/extra-config - mountPath: /consul/login name: consul-data readOnly: true diff --git a/charts/consul/templates/client-tmp-extra-config-configmap.yaml b/charts/consul/templates/client-tmp-extra-config-configmap.yaml deleted file mode 100644 index a379157f57..0000000000 --- a/charts/consul/templates/client-tmp-extra-config-configmap.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if (or (and (ne (.Values.client.enabled | toString) "-") .Values.client.enabled) (and (eq (.Values.client.enabled | toString) "-") .Values.global.enabled)) }} -# ConfigMap that is used as a temporary landing spot so that the container command -# in the client-daemonset where it needs to be transformed. ConfigMaps create -# read only volumes so it needs to be copied and transformed to the extra-config -# emptyDir volume where all final extra cofngi lives for use in consul. (locality-init -# also writes to extra-config volume.) -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "consul.fullname" . }}-client-tmp-extra-config - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: client -data: - extra-from-values.json: |- -{{ tpl .Values.client.extraConfig . | trimAll "\"" | indent 4 }} -{{- end }} diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index 258924f449..ae04d9e657 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -37,7 +37,6 @@ spec: {{- end }} annotations: consul.hashicorp.com/connect-inject: "false" - consul.hashicorp.com/mesh-inject: "false" spec: # consul-cni only runs on linux operating systems nodeSelector: diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index be816ff391..f2e12f0ad9 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -24,20 +24,10 @@ rules: - serviceintentions - ingressgateways - terminatinggateways - - gatewayclassconfigs - - meshservices - - samenessgroups - - controlplanerequestlimits - - routeretryfilters - - routetimeoutfilters - - routeauthfilters - - gatewaypolicies {{- if .Values.global.peering.enabled }} - peeringacceptors - peeringdialers {{- end }} - - jwtproviders - - routeauthfilters verbs: - create - delete @@ -59,119 +49,26 @@ rules: - serviceintentions/status - ingressgateways/status - terminatinggateways/status - - samenessgroups/status - - controlplanerequestlimits/status {{- if .Values.global.peering.enabled }} - peeringacceptors/status - peeringdialers/status {{- end }} - - jwtproviders/status - - routeauthfilters/status - - gatewaypolicies/status verbs: - get - patch - update -{{- if (mustHas "resource-apis" .Values.global.experiments) }} -- apiGroups: - - auth.consul.hashicorp.com - resources: - - trafficpermissions - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - auth.consul.hashicorp.com - resources: - - trafficpermissions/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclassconfigs - - gatewayclasses - - meshconfigurations - - grpcroutes - - httproutes - - meshgateways - - apigateways - - tcproutes - - proxyconfigurations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclassconfigs/status - - gatewayclasses/status - - meshconfigurations/status - - grpcroutes/status - - httproutes/status - - meshgateways/status - - apigateways/status - - tcproutes/status - - proxyconfigurations/status - verbs: - - get - - patch - - update -- apiGroups: - - multicluster.consul.hashicorp.com - resources: - - exportedservices - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - multicluster.consul.hashicorp.com - resources: - - exportedservices/status - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -{{- end }} +{{- if .Values.global.acls.manageSystemACLs }} - apiGroups: [ "" ] - resources: [ "secrets", "serviceaccounts", "endpoints", "services", "namespaces", "nodes" ] + resources: [ "serviceaccounts", "secrets" ] verbs: - - create - get - - list - - watch - - delete - - update -- apiGroups: [ "rbac.authorization.k8s.io" ] - resources: [ "roles", "rolebindings" ] +{{- end }} +- apiGroups: [ "" ] + resources: [ "endpoints", "services", "namespaces", "nodes" ] verbs: - - get - - list - - watch - - delete - - create - - update + - "get" + - "list" + - "watch" - apiGroups: [ "" ] resources: - pods @@ -194,7 +91,6 @@ rules: - admissionregistration.k8s.io resources: - mutatingwebhookconfigurations - - validatingwebhookconfigurations verbs: - get - list @@ -212,78 +108,12 @@ rules: - "update" - "delete" {{- end }} +{{- if .Values.global.enablePodSecurityPolicies }} - apiGroups: [ "policy" ] resources: [ "podsecuritypolicies" ] - verbs: - - use -- apiGroups: - - gateway.networking.k8s.io - resources: - - gatewayclasses - - gateways - - httproutes - - tcproutes - - referencegrants - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - gateway.networking.k8s.io - resources: - - gatewayclasses/finalizers - - gateways/finalizers - - httproutes/finalizers - - tcproutes/finalizers - verbs: - - update -- apiGroups: - - gateway.networking.k8s.io - resources: - - gatewayclasses/status - - gateways/status - - httproutes/status - - tcproutes/status - verbs: - - get - - patch - - update -- apiGroups: - - apps - resources: - - deployments - verbs: - - create - - get - - list - - update - - watch - - delete -- apiGroups: - - core - resources: - - services - verbs: - - watch - - list -- apiGroups: [ "" ] - resources: [ "secrets" ] - verbs: - - "get" - - "list" - - "watch" -{{- if .Values.global.openshift.enabled }} -- apiGroups: - - security.openshift.io - resources: - - securitycontextconstraints resourceNames: - - {{ .Values.connectInject.apiGateway.managedGatewayClass.openshiftSCCName }} + - {{ template "consul.fullname" . }}-connect-injector verbs: - - use - {{- end }} + - use +{{- end }} {{- end }} diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 4066fe3103..b480ddafaa 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -6,15 +6,11 @@ {{ template "consul.validateVaultWebhookCertConfiguration" . }} {{- template "consul.reservedNamesFailer" (list .Values.connectInject.consulNamespaces.consulDestinationNamespace "connectInject.consulNamespaces.consulDestinationNamespace") }} {{- if and .Values.externalServers.enabled (not .Values.externalServers.hosts) }}{{ fail "externalServers.hosts must be set if externalServers.enabled is true" }}{{ end -}} -{{- if and .Values.externalServers.enabled .Values.global.cloud.enabled }} - {{- if and (gt (len .Values.externalServers.hosts) 0) (regexMatch ".+.hashicorp.cloud$" ( first .Values.externalServers.hosts )) }}{{fail "global.cloud.enabled cannot be used in combination with an HCP-managed cluster address in externalServers.hosts. global.cloud.enabled is for linked self-managed clusters."}}{{- end }} -{{- end }} {{- if and .Values.externalServers.skipServerWatch (not .Values.externalServers.enabled) }}{{ fail "externalServers.enabled must be set if externalServers.skipServerWatch is true" }}{{ end -}} {{- $dnsEnabled := (or (and (ne (.Values.dns.enabled | toString) "-") .Values.dns.enabled) (and (eq (.Values.dns.enabled | toString) "-") .Values.connectInject.transparentProxy.defaultEnabled)) -}} {{- $dnsRedirectionEnabled := (or (and (ne (.Values.dns.enableRedirection | toString) "-") .Values.dns.enableRedirection) (and (eq (.Values.dns.enableRedirection | toString) "-") .Values.connectInject.transparentProxy.defaultEnabled)) -}} {{ template "consul.validateRequiredCloudSecretsExist" . }} {{ template "consul.validateCloudSecretKeys" . }} -{{ template "consul.validateResourceAPIs" . }} # The deployment for running the Connect sidecar injector apiVersion: apps/v1 kind: Deployment @@ -53,7 +49,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.connectInject.annotations }} {{- tpl .Values.connectInject.annotations . | nindent 8 }} {{- end }} @@ -154,12 +149,6 @@ spec: -release-namespace="{{ .Release.Namespace }}" \ -resource-prefix={{ template "consul.fullname" . }} \ -listen=:8080 \ - {{- if (mustHas "resource-apis" .Values.global.experiments) }} - -enable-resource-apis=true \ - {{- end }} - {{- if (mustHas "v2tenancy" .Values.global.experiments) }} - -enable-v2tenancy=true \ - {{- end }} {{- range $k, $v := .Values.connectInject.consulNode.meta }} -node-meta={{ $k }}={{ $v }} \ {{- end }} @@ -262,8 +251,7 @@ spec: -default-sidecar-proxy-lifecycle-shutdown-grace-period-seconds={{ .Values.connectInject.sidecarProxy.lifecycle.defaultShutdownGracePeriodSeconds }} \ -default-sidecar-proxy-lifecycle-graceful-port={{ .Values.connectInject.sidecarProxy.lifecycle.defaultGracefulPort }} \ -default-sidecar-proxy-lifecycle-graceful-shutdown-path="{{ .Values.connectInject.sidecarProxy.lifecycle.defaultGracefulShutdownPath }}" \ - -default-sidecar-proxy-startup-failure-seconds={{ .Values.connectInject.sidecarProxy.defaultStartupFailureSeconds }} \ - -default-sidecar-proxy-liveness-failure-seconds={{ .Values.connectInject.sidecarProxy.defaultLivenessFailureSeconds }} \ + {{- if .Values.connectInject.initContainer }} {{- $initResources := .Values.connectInject.initContainer.resources }} {{- if not (kindIs "invalid" $initResources.limits.memory) }} @@ -286,7 +274,6 @@ spec: {{- if and .Values.global.tls.enabled .Values.global.tls.enableAutoEncrypt }} -enable-auto-encrypt \ {{- end }} - -enable-telemetry-collector={{ .Values.global.metrics.enableTelemetryCollector}} \ startupProbe: httpGet: path: /readyz/ready diff --git a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml index e65c386636..afcfd3800f 100644 --- a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml +++ b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml @@ -222,27 +222,6 @@ webhooks: resources: - exportedservices sideEffects: None -- clientConfig: - service: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - path: /mutate-v1alpha1-controlplanerequestlimits - failurePolicy: Fail - admissionReviewVersions: - - "v1beta1" - - "v1" - name: mutate-controlplanerequestlimit.consul.hashicorp.com - rules: - - apiGroups: - - consul.hashicorp.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - controlplanerequestlimits - sideEffects: None - name: {{ template "consul.fullname" . }}-connect-injector.consul.hashicorp.com # The webhook will fail scheduling all pods that are not part of consul if all replicas of the webhook are unhealthy. objectSelector: @@ -312,70 +291,5 @@ webhooks: admissionReviewVersions: - "v1beta1" - "v1" -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - path: /mutate-v1alpha1-samenessgroup - failurePolicy: Fail - name: mutate-samenessgroup.consul.hashicorp.com - rules: - - apiGroups: - - consul.hashicorp.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - samenessgroups - sideEffects: None -{{- if (mustHas "resource-apis" .Values.global.experiments) }} -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - path: /mutate-v2beta1-trafficpermissions - failurePolicy: Fail - name: mutate-trafficpermissions.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - trafficpermissions - sideEffects: None {{- end }} {{- end }} -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - path: /mutate-v1alpha1-jwtprovider - failurePolicy: Fail - name: mutate-jwtprovider.consul.hashicorp.com - rules: - - apiGroups: - - consul.hashicorp.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - jwtproviders - sideEffects: None -{{- end }} diff --git a/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml deleted file mode 100644 index 8d01ace911..0000000000 --- a/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} -# The ValidatingWebhookConfiguration to enable the Connect injector. -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: connect-injector -webhooks: -- name: validate-gatewaypolicy.consul.hashicorp.com - matchPolicy: Equivalent - rules: - - operations: [ "CREATE" , "UPDATE" ] - apiGroups: [ "consul.hashicorp.com" ] - apiVersions: [ "v1alpha1" ] - resources: [ "gatewaypolicies" ] - failurePolicy: Fail - sideEffects: None - admissionReviewVersions: - - v1 - clientConfig: - service: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - path: /validate-v1alpha1-gatewaypolicy -{{- end }} diff --git a/charts/consul/templates/crd-apigateways.yaml b/charts/consul/templates/crd-apigateways.yaml deleted file mode 100644 index 755fb05b64..0000000000 --- a/charts/consul/templates/crd-apigateways.yaml +++ /dev/null @@ -1,240 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: apigateways.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: APIGateway - listKind: APIGatewayList - plural: apigateways - singular: apigateway - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: APIGateway is the Schema for the API Gateway - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - gatewayClassName: - description: GatewayClassName is the name of the GatewayClass used - by the APIGateway - type: string - listeners: - items: - properties: - hostname: - description: Hostname is the host name that a listener should - be bound to, if unspecified, the listener accepts requests - for all hostnames. - type: string - name: - description: Name is the name of the listener in a given gateway. - This must be unique within a gateway. - type: string - port: - format: int32 - maximum: 65535 - minimum: 0 - type: integer - protocol: - description: Protocol is the protocol that a listener should - use, it must either be "http" or "tcp" - type: string - tls: - description: TLS is the TLS settings for the listener. - properties: - certificates: - description: Certificates is a set of references to certificates - that a gateway listener uses for TLS termination. - items: - description: Reference identifies which resource a condition - relates to, when it is not the core resource itself. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the - resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list resources - across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list resources - across all partitions." - type: string - peerName: - description: "PeerName identifies which peer the - resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list resources - across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes are - made to the group's resource types. - type: string - kind: - description: Kind identifies the specific resource - type within the group. - type: string - type: object - type: object - type: array - tlsParameters: - description: TLSParameters contains optional configuration - for running TLS termination. - properties: - cipherSuites: - items: - enum: - - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_GCM_SHA256 - - TLS_CIPHER_SUITE_AES256_SHA - - TLS_CIPHER_SUITE_ECDHE_ECDSA_CHACHA20_POLY1305 - - TLS_CIPHER_SUITE_ECDHE_RSA_AES128_GCM_SHA256 - - TLS_CIPHER_SUITE_ECDHE_RSA_CHACHA20_POLY1305 - - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_SHA - - TLS_CIPHER_SUITE_ECDHE_RSA_AES128_SHA - - TLS_CIPHER_SUITE_AES128_GCM_SHA256 - - TLS_CIPHER_SUITE_AES128_SHA - - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_GCM_SHA384 - - TLS_CIPHER_SUITE_ECDHE_RSA_AES256_GCM_SHA384 - - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_SHA - - TLS_CIPHER_SUITE_ECDHE_RSA_AES256_SHA - - TLS_CIPHER_SUITE_AES256_GCM_SHA384 - format: int32 - type: string - type: array - maxVersion: - enum: - - TLS_VERSION_AUTO - - TLS_VERSION_1_0 - - TLS_VERSION_1_1 - - TLS_VERSION_1_2 - - TLS_VERSION_1_3 - - TLS_VERSION_INVALID - - TLS_VERSION_UNSPECIFIED - format: int32 - type: string - minVersion: - enum: - - TLS_VERSION_AUTO - - TLS_VERSION_1_0 - - TLS_VERSION_1_1 - - TLS_VERSION_1_2 - - TLS_VERSION_1_3 - - TLS_VERSION_INVALID - - TLS_VERSION_UNSPECIFIED - format: int32 - type: string - type: object - type: object - type: object - minItems: 1 - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml deleted file mode 100644 index 1939a8d373..0000000000 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ /dev/null @@ -1,195 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: controlplanerequestlimits.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: ControlPlaneRequestLimit - listKind: ControlPlaneRequestLimitList - plural: controlplanerequestlimits - singular: controlplanerequestlimit - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: ControlPlaneRequestLimit is the Schema for the controlplanerequestlimits - API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ControlPlaneRequestLimitSpec defines the desired state of - ControlPlaneRequestLimit. - properties: - acl: - properties: - readRate: - type: number - writeRate: - type: number - type: object - catalog: - properties: - readRate: - type: number - writeRate: - type: number - type: object - configEntry: - properties: - readRate: - type: number - writeRate: - type: number - type: object - connectCA: - properties: - readRate: - type: number - writeRate: - type: number - type: object - coordinate: - properties: - readRate: - type: number - writeRate: - type: number - type: object - discoveryChain: - properties: - readRate: - type: number - writeRate: - type: number - type: object - health: - properties: - readRate: - type: number - writeRate: - type: number - type: object - intention: - properties: - readRate: - type: number - writeRate: - type: number - type: object - kv: - properties: - readRate: - type: number - writeRate: - type: number - type: object - mode: - type: string - preparedQuery: - properties: - readRate: - type: number - writeRate: - type: number - type: object - readRate: - type: number - session: - properties: - readRate: - type: number - writeRate: - type: number - type: object - tenancy: - properties: - readRate: - type: number - writeRate: - type: number - type: object - txn: - properties: - readRate: - type: number - writeRate: - type: number - type: object - writeRate: - type: number - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-exportedservices-v1.yaml b/charts/consul/templates/crd-exportedservices-v1.yaml deleted file mode 100644 index 081a2b0cf0..0000000000 --- a/charts/consul/templates/crd-exportedservices-v1.yaml +++ /dev/null @@ -1,139 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: exportedservices.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: ExportedServices - listKind: ExportedServicesList - plural: exportedservices - shortNames: - - exported-services - singular: exportedservices - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: ExportedServices is the Schema for the exportedservices API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ExportedServicesSpec defines the desired state of ExportedServices. - properties: - services: - description: Services is a list of services to be exported and the - list of partitions to expose them to. - items: - description: ExportedService manages the exporting of a service - in the local partition to other partitions. - properties: - consumers: - description: Consumers is a list of downstream consumers of - the service to be exported. - items: - description: ServiceConsumer represents a downstream consumer - of the service to be exported. - properties: - partition: - description: Partition is the admin partition to export - the service to. - type: string - peer: - description: Peer is the name of the peer to export the - service to. - type: string - samenessGroup: - description: SamenessGroup is the name of the sameness - group to export the service to. - type: string - type: object - type: array - name: - description: Name is the name of the service to be exported. - type: string - namespace: - description: Namespace is the namespace to export the service - from. - type: string - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 8803a03152..007990372c 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -1,22 +1,26 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: exportedservices.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: exportedservices.multicluster.consul.hashicorp.com spec: - group: multicluster.consul.hashicorp.com + group: consul.hashicorp.com names: kind: ExportedServices listKind: ExportedServicesList plural: exportedservices + shortNames: + - exported-services singular: exportedservices scope: Namespaced versions: @@ -33,10 +37,10 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date - name: v2beta1 + name: v1alpha1 schema: openAPIV3Schema: - description: ExportedServices is the Schema for the Exported Services API + description: ExportedServices is the Schema for the exportedservices API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -51,15 +55,40 @@ spec: metadata: type: object spec: + description: ExportedServicesSpec defines the desired state of ExportedServices. properties: - consumers: - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array services: + description: Services is a list of services to be exported and the + list of partitions to expose them to. items: - type: string + description: ExportedService manages the exporting of a service + in the local partition to other partitions. + properties: + consumers: + description: Consumers is a list of downstream consumers of + the service to be exported. + items: + description: ServiceConsumer represents a downstream consumer + of the service to be exported. + properties: + partition: + description: Partition is the admin partition to export + the service to. + type: string + peer: + description: '[Experimental] Peer is the name of the peer + to export the service to.' + type: string + type: object + type: array + name: + description: Name is the name of the service to be exported. + type: string + namespace: + description: Namespace is the namespace to export the service + from. + type: string + type: object type: array type: object status: @@ -105,4 +134,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml b/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml deleted file mode 100644 index 130db72a22..0000000000 --- a/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml +++ /dev/null @@ -1,201 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: gatewayclassconfigs.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: GatewayClassConfig - listKind: GatewayClassConfigList - plural: gatewayclassconfigs - singular: gatewayclassconfig - scope: Cluster - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: GatewayClassConfig defines the values that may be set on a GatewayClass - for Consul API Gateway. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClassConfig. - properties: - copyAnnotations: - description: Annotation Information to copy to services or deployments - properties: - service: - description: List of annotations to copy to the gateway service. - items: - type: string - type: array - type: object - deployment: - description: Deployment defines the deployment configuration for the - gateway. - properties: - defaultInstances: - default: 1 - description: Number of gateway instances that should be deployed - by default - format: int32 - maximum: 8 - minimum: 1 - type: integer - maxInstances: - default: 8 - description: Max allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - minInstances: - default: 1 - description: Minimum allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - resources: - description: Resources defines the resource requirements for the - gateway. - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only be - set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry in - pod.spec.resourceClaims of the Pod where this field - is used. It makes that resource available inside a - container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - type: object - mapPrivilegedContainerPorts: - description: The value to add to privileged ports ( ports < 1024) - for gateway containers - format: int32 - type: integer - nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a selector which must be true for the - pod to fit on a node. Selector which must match a node''s labels - for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - openshiftSCCName: - description: The name of the OpenShift SecurityContextConstraints - resource for this gateway class to use. - type: string - podSecurityPolicy: - description: The name of an existing Kubernetes PodSecurityPolicy - to bind to the managed ServiceAccount if ACLs are managed. - type: string - serviceType: - description: Service Type string describes ingress methods for a service - enum: - - ClusterIP - - NodePort - - LoadBalancer - type: string - tolerations: - description: 'Tolerations allow the scheduler to schedule nodes with - matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array - type: object - type: object - served: true - storage: true -{{- end }} diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml deleted file mode 100644 index 93effd843b..0000000000 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ /dev/null @@ -1,1826 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: gatewayclassconfigs.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: GatewayClassConfig - listKind: GatewayClassConfigList - plural: gatewayclassconfigs - singular: gatewayclassconfig - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: GatewayClassConfig is the Schema for the Mesh Gateway API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GatewayClassConfigSpec specifies the desired state of the - GatewayClassConfig CRD. - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key included - here will override those in Set if specified on the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included here - will be overridden if present in InheritFromGateway and set - on the Gateway. - type: object - type: object - deployment: - description: Deployment contains config specific to the Deployment - created from this GatewayClass - properties: - affinity: - description: Affinity specifies the affinity to use on the created - Deployment. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for - the pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the affinity expressions specified - by this field, but it may choose a node that violates - one or more of the expressions. The node that is most - preferred is the one with the greatest sum of weights, - i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node matches the corresponding matchExpressions; - the node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches - all objects with implicit weight 0 (i.e. it's a no-op). - A null preferred scheduling term matches no objects - (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated with - the corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the - corresponding nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by - this field are not met at scheduling time, the pod will - not be scheduled onto the node. If the affinity requirements - specified by this field cease to be met at some point - during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from - its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: A null or empty node selector term - matches no objects. The requirements of them are - ANDed. The TopologySelectorTerm type implements - a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - type: array - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. - co-locate this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the affinity expressions specified - by this field, but it may choose a node that violates - one or more of the expressions. The node that is most - preferred is the one with the greatest sum of weights, - i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node has pods which matches the corresponding - podAffinityTerm; the node(s) with the highest sum are - the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by - this field and the ones listed in the namespaces - field. null selector and null or empty namespaces - list means "this pod's namespace". An empty - selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. - The term is applied to the union of the namespaces - listed in this field and the ones selected - by namespaceSelector. null or empty namespaces - list and null namespaceSelector means "this - pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the - pods matching the labelSelector in the specified - namespaces, where co-located is defined as - running on a node whose value of the label - with key topologyKey matches that of any node - on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the - corresponding podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by - this field are not met at scheduling time, the pod will - not be scheduled onto the node. If the affinity requirements - specified by this field cease to be met at some point - during pod execution (e.g. due to a pod label update), - the system may or may not try to eventually evict the - pod from its node. When there are multiple elements, - the lists of nodes corresponding to each podAffinityTerm - are intersected, i.e. all terms must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located - is defined as running on a node whose value of the - label with key matches that of any node - on which a pod of the set of pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules - (e.g. avoid putting this pod in the same node, zone, etc. - as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the anti-affinity expressions - specified by this field, but it may choose a node that - violates one or more of the expressions. The node that - is most preferred is the one with the greatest sum of - weights, i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - anti-affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node has pods which matches the corresponding - podAffinityTerm; the node(s) with the highest sum are - the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by - this field and the ones listed in the namespaces - field. null selector and null or empty namespaces - list means "this pod's namespace". An empty - selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. - The term is applied to the union of the namespaces - listed in this field and the ones selected - by namespaceSelector. null or empty namespaces - list and null namespaceSelector means "this - pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the - pods matching the labelSelector in the specified - namespaces, where co-located is defined as - running on a node whose value of the label - with key topologyKey matches that of any node - on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the - corresponding podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified - by this field are not met at scheduling time, the pod - will not be scheduled onto the node. If the anti-affinity - requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod - label update), the system may or may not try to eventually - evict the pod from its node. When there are multiple - elements, the lists of nodes corresponding to each podAffinityTerm - are intersected, i.e. all terms must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located - is defined as running on a node whose value of the - label with key matches that of any node - on which a pod of the set of pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - type: object - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - container: - description: Container contains config specific to the created - Deployment's container. - properties: - consul: - description: Consul specifies configuration for the consul-dataplane - container - properties: - logging: - description: Logging specifies the logging configuration - for Consul Dataplane - properties: - level: - description: Level sets the logging level for Consul - Dataplane (debug, info, etc.) - type: string - type: object - type: object - hostPort: - description: HostPort specifies a port to be exposed to the - external host network - format: int32 - type: integer - portModifier: - description: PortModifier specifies the value to be added - to every port value for listeners on this gateway. This - is generally used to avoid binding to privileged ports in - the container. - format: int32 - type: integer - resources: - description: Resources specifies the resource requirements - for the created Deployment's container - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. \n This field - is immutable. It can only be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If Requests is omitted for - a container, it defaults to Limits if that is explicitly - specified, otherwise to an implementation-defined value. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - type: object - dnsPolicy: - description: DNSPolicy specifies the dns policy to use. These - are set on a per pod basis. - enum: - - Default - - ClusterFirst - - ClusterFirstWithHostNet - - None - type: string - hostNetwork: - description: HostNetwork specifies whether the gateway pods should - run on the host network. - type: boolean - initContainer: - description: InitContainer contains config specific to the created - Deployment's init container. - properties: - consul: - description: Consul specifies configuration for the consul-k8s-control-plane - init container - properties: - logging: - description: Logging specifies the logging configuration - for Consul Dataplane - properties: - level: - description: Level sets the logging level for Consul - Dataplane (debug, info, etc.) - type: string - type: object - type: object - resources: - description: Resources specifies the resource requirements - for the created Deployment's init container - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. \n This field - is immutable. It can only be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If Requests is omitted for - a container, it defaults to Limits if that is explicitly - specified, otherwise to an implementation-defined value. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a feature that constrains the scheduling - of a pod to nodes that match specified labels. By defining NodeSelector - in a pod''s configuration, you can ensure that the pod is only - scheduled to nodes with the corresponding labels, providing - a way to influence the placement of workloads based on node - attributes. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - priorityClassName: - description: PriorityClassName specifies the priority class name - to use on the created Deployment. - type: string - replicas: - description: Replicas specifies the configuration to control the - number of replicas for the created Deployment. - properties: - default: - description: Default is the number of replicas assigned to - the Deployment when created - format: int32 - type: integer - max: - description: Max is the maximum number of replicas allowed - for a gateway with this class. If the replica count exceeds - this value due to manual or automated scaling, the replica - count will be restored to this value. - format: int32 - type: integer - min: - description: Min is the minimum number of replicas allowed - for a gateway with this class. If the replica count drops - below this value due to manual or automated scaling, the - replica count will be restored to this value. - format: int32 - type: integer - type: object - securityContext: - description: SecurityContext specifies the security context for - the created Deployment's Pod. - properties: - fsGroup: - description: "A special supplemental group that applies to - all containers in a pod. Some volume types allow the Kubelet - to change the ownership of that volume to be owned by the - pod: \n 1. The owning GID will be the FSGroup 2. The setgid - bit is set (new files created in the volume will be owned - by FSGroup) 3. The permission bits are OR'd with rw-rw---- - \n If unset, the Kubelet will not modify the ownership and - permissions of any volume. Note that this field cannot be - set when spec.os.name is windows." - format: int64 - type: integer - fsGroupChangePolicy: - description: 'fsGroupChangePolicy defines behavior of changing - ownership and permission of the volume before being exposed - inside Pod. This field will only apply to volume types which - support fsGroup based ownership(and permissions). It will - have no effect on ephemeral volume types such as: secret, - configmaps and emptydir. Valid values are "OnRootMismatch" - and "Always". If not specified, "Always" is used. Note that - this field cannot be set when spec.os.name is windows.' - type: string - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in SecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. Note that this field - cannot be set when spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - Note that this field cannot be set when spec.os.name is - windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to all containers. - If unspecified, the container runtime will allocate a random - SELinux context for each container. May also be set in - SecurityContext. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence - for that container. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by the containers - in this pod. Note that this field cannot be set when spec.os.name - is windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile must - be preconfigured on the node to work. Must be a descending - path, relative to the kubelet's configured seccomp profile - location. Must only be set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - a - profile defined in a file on the node should be used. - RuntimeDefault - the container runtime default profile - should be used. Unconfined - no profile should be applied." - type: string - required: - - type - type: object - supplementalGroups: - description: A list of groups applied to the first process - run in each container, in addition to the container's primary - GID, the fsGroup (if specified), and group memberships defined - in the container image for the uid of the container process. - If unspecified, no additional groups are added to any container. - Note that group memberships defined in the container image - for the uid of the container process are still effective, - even if they are not included in this list. Note that this - field cannot be set when spec.os.name is windows. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used - for the pod. Pods with unsupported sysctls (by the container - runtime) might fail to launch. Note that this field cannot - be set when spec.os.name is windows. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: object - type: array - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options within a container's - SecurityContext will be used. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components that - enable the WindowsHostProcessContainers feature flag. - Setting this field without the feature flag will result - in errors when validating the Pod. All of a Pod's containers - must have the same effective HostProcess value (it is - not allowed to have a mix of HostProcess containers - and non-HostProcess containers). In addition, if HostProcess - is true then HostNetwork must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - tolerations: - description: Tolerations specifies the tolerations to use on the - created Deployment. - items: - description: The pod this Toleration is attached to tolerates - any taint that matches the triple using - the matching operator . - properties: - effect: - description: Effect indicates the taint effect to match. - Empty means match all taint effects. When specified, allowed - values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match - all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to - the value. Valid operators are Exists and Equal. Defaults - to Equal. Exists is equivalent to wildcard for value, - so that a pod can tolerate all taints of a particular - category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of - time the toleration (which must be of effect NoExecute, - otherwise this field is ignored) tolerates the taint. - By default, it is not set, which means tolerate the taint - forever (do not evict). Zero and negative values will - be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array - topologySpreadConstraints: - description: 'TopologySpreadConstraints is a feature that controls - how pods are spead across your topology. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/' - items: - description: TopologySpreadConstraint specifies how to spread - matching pods among the given topology. - properties: - labelSelector: - description: LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine - the number of pods in their corresponding topology domain. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: MatchLabelKeys is a set of pod label keys to - select the pods over which spreading will be calculated. - The keys are used to lookup values from the incoming pod - labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading - will be calculated for the incoming pod. Keys that don't - exist in the incoming pod labels will be ignored. A null - or empty list means only match against labelSelector. - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: 'MaxSkew describes the degree to which pods - may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, - it is the maximum permitted difference between the number - of matching pods in the target topology and the global - minimum. The global minimum is the minimum number of matching - pods in an eligible domain or zero if the number of eligible - domains is less than MinDomains. For example, in a 3-zone - cluster, MaxSkew is set to 1, and pods with the same labelSelector - spread as 2/2/1: In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to - zone3 to become 2/2/2; scheduling it onto zone1(zone2) - would make the ActualSkew(3-1) on zone1(zone2) violate - MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled - onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, - it is used to give higher precedence to topologies that - satisfy it. It''s a required field. Default value is 1 - and 0 is not allowed.' - format: int32 - type: integer - minDomains: - description: "MinDomains indicates a minimum number of eligible - domains. When the number of eligible domains with matching - topology keys is less than minDomains, Pod Topology Spread - treats \"global minimum\" as 0, and then the calculation - of Skew is performed. And when the number of eligible - domains with matching topology keys equals or greater - than minDomains, this value has no effect on scheduling. - As a result, when the number of eligible domains is less - than minDomains, scheduler won't schedule more than maxSkew - Pods to those domains. If value is nil, the constraint - behaves as if MinDomains is equal to 1. Valid values are - integers greater than 0. When value is not nil, WhenUnsatisfiable - must be DoNotSchedule. \n For example, in a 3-zone cluster, - MaxSkew is set to 2, MinDomains is set to 5 and pods with - the same labelSelector spread as 2/2/2: | zone1 | zone2 - | zone3 | | P P | P P | P P | The number of domains - is less than 5(MinDomains), so \"global minimum\" is treated - as 0. In this situation, new pod with the same labelSelector - cannot be scheduled, because computed skew will be 3(3 - - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. \n This is a beta field and requires - the MinDomainsInPodTopologySpread feature gate to be enabled - (enabled by default)." - format: int32 - type: integer - nodeAffinityPolicy: - description: "NodeAffinityPolicy indicates how we will treat - Pod's nodeAffinity/nodeSelector when calculating pod topology - spread skew. Options are: - Honor: only nodes matching - nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes - are included in the calculations. \n If this value is - nil, the behavior is equivalent to the Honor policy. This - is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread - feature flag." - type: string - nodeTaintsPolicy: - description: "NodeTaintsPolicy indicates how we will treat - node taints when calculating pod topology spread skew. - Options are: - Honor: nodes without taints, along with - tainted nodes for which the incoming pod has a toleration, - are included. - Ignore: node taints are ignored. All nodes - are included. \n If this value is nil, the behavior is - equivalent to the Ignore policy. This is a beta-level - feature default enabled by the NodeInclusionPolicyInPodTopologySpread - feature flag." - type: string - topologyKey: - description: TopologyKey is the key of node labels. Nodes - that have a label with this key and identical values are - considered to be in the same topology. We consider each - as a "bucket", and try to put balanced number - of pods into each bucket. We define a domain as a particular - instance of a topology. Also, we define an eligible domain - as a domain whose nodes meet the requirements of nodeAffinityPolicy - and nodeTaintsPolicy. e.g. If TopologyKey is "kubernetes.io/hostname", - each Node is a domain of that topology. And, if TopologyKey - is "topology.kubernetes.io/zone", each zone is a domain - of that topology. It's a required field. - type: string - whenUnsatisfiable: - description: 'WhenUnsatisfiable indicates how to deal with - a pod if it doesn''t satisfy the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule - it. - ScheduleAnyway tells the scheduler to schedule the - pod in any location, but giving higher precedence to topologies - that would help reduce the skew. A constraint is considered - "Unsatisfiable" for an incoming pod if and only if every - possible node assignment for that pod would violate "MaxSkew" - on some topology. For example, in a 3-zone cluster, MaxSkew - is set to 1, and pods with the same labelSelector spread - as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming - pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) - as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). - In other words, the cluster can still be imbalanced, but - scheduler won''t make it *more* imbalanced. It''s a required - field.' - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key included - here will override those in Set if specified on the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included here - will be overridden if present in InheritFromGateway and set - on the Gateway. - type: object - type: object - role: - description: Role contains config specific to the Role created from - this GatewayClass - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - type: object - roleBinding: - description: RoleBinding contains config specific to the RoleBinding - created from this GatewayClass - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - type: object - service: - description: Service contains config specific to the Service created - from this GatewayClass - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - type: - description: Type specifies the type of Service to use (LoadBalancer, - ClusterIP, etc.) - enum: - - ClusterIP - - NodePort - - LoadBalancer - type: string - type: object - serviceAccount: - description: ServiceAccount contains config specific to the corev1.ServiceAccount - created from this GatewayClass - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-gatewayclasses-external.yaml b/charts/consul/templates/crd-gatewayclasses-external.yaml deleted file mode 100644 index 93435b7fce..0000000000 --- a/charts/consul/templates/crd-gatewayclasses-external.yaml +++ /dev/null @@ -1,328 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: gatewayclasses.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: GatewayClass - listKind: GatewayClassList - plural: gatewayclasses - shortNames: - - gc - singular: gatewayclass - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .spec.controllerName - name: Controller - type: string - - jsonPath: .status.conditions[?(@.type=="Accepted")].status - name: Accepted - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - deprecated: true - deprecationWarning: The v1alpha2 version of GatewayClass has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.controllerName - name: Controller - type: string - - jsonPath: .status.conditions[?(@.type=="Accepted")].status - name: Accepted - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - name: v1beta1 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-gatewayclasses.yaml b/charts/consul/templates/crd-gatewayclasses.yaml deleted file mode 100644 index 7ccdd524ed..0000000000 --- a/charts/consul/templates/crd-gatewayclasses.yaml +++ /dev/null @@ -1,128 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: gatewayclasses.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: GatewayClass - listKind: GatewayClassList - plural: gatewayclasses - singular: gatewayclass - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: GatewayClass is the Schema for the Gateway Class API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayClass - \n This is a Resource type." - properties: - controllerName: - description: ControllerName is the name of the Kubernetes controller - that manages Gateways of this class - type: string - description: - description: Description of GatewayClass - type: string - parametersRef: - description: ParametersRef refers to a resource responsible for configuring - the behavior of the GatewayClass. - properties: - group: - description: The Kubernetes Group that the referred object belongs - to - type: string - kind: - description: The Kubernetes Kind that the referred object is - type: string - name: - description: The name of the referred object - type: string - namespace: - description: The kubernetes namespace that the referred object - is in - type: string - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-gatewaypolicies.yaml b/charts/consul/templates/crd-gatewaypolicies.yaml deleted file mode 100644 index 1cdfa331f5..0000000000 --- a/charts/consul/templates/crd-gatewaypolicies.yaml +++ /dev/null @@ -1,282 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: gatewaypolicies.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: GatewayPolicy - listKind: GatewayPolicyList - plural: gatewaypolicies - singular: gatewaypolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: GatewayPolicy is the Schema for the gatewaypolicies API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GatewayPolicySpec defines the desired state of GatewayPolicy. - properties: - default: - properties: - jwt: - description: GatewayJWTRequirement holds the list of JWT providers - to be verified against. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry - with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the - actual claim information to be verified. - properties: - path: - description: Path is the path to the claim in - the token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the - given path: - If the type at the path is a list - then we verify that this value is contained - in the list. \n - If the type at the path is - a string then we verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - override: - properties: - jwt: - description: GatewayJWTRequirement holds the list of JWT providers - to be verified against. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry - with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the - actual claim information to be verified. - properties: - path: - description: Path is the path to the claim in - the token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the - given path: - If the type at the path is a list - then we verify that this value is contained - in the list. \n - If the type at the path is - a string then we verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - targetRef: - description: TargetRef identifies an API object to apply policy to. - properties: - group: - description: Group is the group of the target resource. - maxLength: 253 - minLength: 1 - type: string - kind: - description: Kind is kind of the target resource. - maxLength: 253 - minLength: 1 - type: string - name: - description: Name is the name of the target resource. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. When - unspecified, the local namespace is inferred. Even when policy - targets a resource in a different namespace, it may only apply - to traffic originating from the same namespace as the policy. - maxLength: 253 - minLength: 1 - type: string - sectionName: - description: SectionName refers to the listener targeted by this - policy. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - group - - kind - - name - type: object - required: - - targetRef - type: object - status: - description: GatewayPolicyStatus defines the observed state of the gateway. - properties: - conditions: - description: "Conditions describe the current conditions of the Policy. - \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-gateways-external.yaml b/charts/consul/templates/crd-gateways-external.yaml deleted file mode 100644 index 41df34942a..0000000000 --- a/charts/consul/templates/crd-gateways-external.yaml +++ /dev/null @@ -1,882 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: gateways.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: Gateway - listKind: GatewayList - plural: gateways - shortNames: - - gtw - singular: gateway - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Programmed")].status - name: Programmed - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: The v1alpha2 version of Gateway has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. - name: v1alpha2 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n The .listener.hostname field is used to route traffic that has already arrived at the Gateway to the correct in-cluster destination. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended" - items: - description: GatewayAddress describes an address that can be bound to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - name: - description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - port: - description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" - items: - description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Secret - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Accepted - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Programmed - description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this listener. - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status corresponds to. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." - items: - description: RouteGroupKind indicates the group and kind of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds - type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Programmed")].status - name: Programmed - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n The .listener.hostname field is used to route traffic that has already arrived at the Gateway to the correct in-cluster destination. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended" - items: - description: GatewayAddress describes an address that can be bound to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - name: - description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - port: - description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" - items: - description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Secret - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Accepted - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Programmed - description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this listener. - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status corresponds to. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." - items: - description: RouteGroupKind indicates the group and kind of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds - type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-grpcroutes-external.yaml b/charts/consul/templates/crd-grpcroutes-external.yaml deleted file mode 100644 index 739ed2c659..0000000000 --- a/charts/consul/templates/crd-grpcroutes-external.yaml +++ /dev/null @@ -1,766 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: grpcroutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: GRPCRoute - listKind: GRPCRouteList - plural: grpcroutes - singular: grpcroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge. \n Support: Extended" - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GRPCRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - method: - type: Exact - description: Rules are a list of GRPC matchers, filters and actions. - items: - description: GRPCRouteRule defines the semantics for matching an gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. - properties: - filters: - description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" - items: - description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " - enum: - - ResponseHeaderModifier - - RequestHeaderModifier - - RequestMirror - - ExtensionRef - type: string - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. Support: Core" - items: - description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " - enum: - - ResponseHeaderModifier - - RequestHeaderModifier - - RequestMirror - - ExtensionRef - type: string - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - method: - type: Exact - description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." - items: - description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" - properties: - headers: - description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. - items: - description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. - properties: - name: - description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: Type specifies how to match against the value of the header. - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of the gRPC Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - default: - type: Exact - description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. - properties: - method: - description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Method must be a valid Protobuf Method (https://protobuf.com/docs/language-spec#methods)." - maxLength: 1024 - pattern: ^[A-Za-z_][A-Za-z_0-9]*$ - type: string - service: - description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Service must be a valid Protobuf Type Name (https://protobuf.com/docs/language-spec#type-references)." - maxLength: 1024 - pattern: ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$ - type: string - type: - default: Exact - description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - RegularExpression - type: string - type: object - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of GRPCRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-grpcroutes.yaml b/charts/consul/templates/crd-grpcroutes.yaml deleted file mode 100644 index 31812fff35..0000000000 --- a/charts/consul/templates/crd-grpcroutes.yaml +++ /dev/null @@ -1,617 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: grpcroutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: GRPCRoute - listKind: GRPCRouteList - plural: grpcroutes - shortNames: - - grpc-route - singular: grpcroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: GRPCRoute is the Schema for the GRPC Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute - \n This is a Resource type." - properties: - hostnames: - description: "Hostnames are the hostnames for which this GRPCRoute - should respond to requests. \n This is only valid for north/south." - items: - type: string - type: array - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of GRPC matchers, filters and actions. - items: - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. Failure behavior here depends on - how many BackendRefs are specified and how many are invalid. - \n If all entries in BackendRefs are invalid, and there are - also no filters specified in this route rule, all traffic - which matches this rule MUST receive a 500 status code. \n - See the GRPCBackendRef definition for the rules about what - makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef - is invalid, 500 status codes MUST be returned for requests - that would have otherwise been routed to an invalid backend. - If multiple backends are specified, and some are invalid, - the proportion of requests that would otherwise have been - routed to an invalid backend MUST receive a 500 status code. - \n For example, if two backends are specified with equal weights, - and one is invalid, 50 percent of traffic must receive a 500. - Implementations may choose how that 50 percent is determined." - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - filters: - description: Filters defined at this level should be executed - if and only if the request is being forwarded to the - backend defined here. - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema - for a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema - for a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - filters: - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema for - a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema for - a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - matches: - items: - properties: - headers: - description: Headers specifies gRPC request header matchers. - Multiple match values are ANDed together, meaning, a - request MUST match all the specified headers to select - the route. - items: - properties: - name: - type: string - type: - description: "HeaderMatchType specifies the semantics - of how HTTP header values should be compared. - Valid HeaderMatchType values, along with their - conformance levels, are: \n Note that values may - be added to this enum, implementations must ensure - that unknown values will not cause a crash. \n - Unknown values here must result in the implementation - setting the Accepted Condition for the Route to - status: False, with a Reason of UnsupportedValue." - enum: - - HEADER_MATCH_TYPE_UNSPECIFIED - - HEADER_MATCH_TYPE_EXACT - - HEADER_MATCH_TYPE_REGEX - - HEADER_MATCH_TYPE_PRESENT - - HEADER_MATCH_TYPE_PREFIX - - HEADER_MATCH_TYPE_SUFFIX - format: int32 - type: string - value: - type: string - type: object - type: array - method: - description: Method specifies a gRPC request service/method - matcher. If this field is not specified, all services - and methods will match. - properties: - method: - description: "Value of the method to match against. - If left empty or omitted, will match all services. - \n At least one of Service and Method MUST be a - non-empty string.}" - type: string - service: - description: "Value of the service to match against. - If left empty or omitted, will match any service. - \n At least one of Service and Method MUST be a - non-empty string." - type: string - type: - description: 'Type specifies how to match against - the service and/or method. Support: Core (Exact - with service and method specified)' - enum: - - GRPC_METHOD_MATCH_TYPE_UNSPECIFIED - - GRPC_METHOD_MATCH_TYPE_EXACT - - GRPC_METHOD_MATCH_TYPE_REGEX - format: int32 - type: string - type: object - type: object - type: array - retries: - properties: - number: - description: Number is the number of times to retry the - request when a retryable result occurs. - properties: - value: - description: The uint32 value. - format: int32 - type: integer - type: object - onConditions: - description: RetryOn allows setting envoy specific conditions - when a request should be automatically retried. - items: - type: string - type: array - onConnectFailure: - description: RetryOnConnectFailure allows for connection - failure errors to trigger a retry. - type: boolean - onStatusCodes: - description: RetryOnStatusCodes is a flat list of http response - status codes that are eligible for retry. This again should - be feasible in any reasonable proxy. - items: - format: int32 - type: integer - type: array - type: object - timeouts: - description: HTTPRouteTimeouts defines timeouts that can be - configured for an HTTPRoute or GRPCRoute. - properties: - idle: - description: Idle specifies the total amount of time permitted - for the request stream to be idle. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - request: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-httproutes-external.yaml b/charts/consul/templates/crd-httproutes-external.yaml deleted file mode 100644 index bba3672d16..0000000000 --- a/charts/consul/templates/crd-httproutes-external.yaml +++ /dev/null @@ -1,1914 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: httproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: HTTPRoute - listKind: HTTPRouteList - plural: httproutes - singular: httproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: The v1alpha2 version of HTTPRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. - name: v1alpha2 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. - properties: - filters: - description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." - items: - description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. - items: - description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH - type: string - path: - default: - type: PathPrefix - value: / - description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. - properties: - type: - default: PathPrefix - description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - PathPrefix - - RegularExpression - type: string - value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 - type: string - type: object - queryParams: - description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" - items: - description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. - properties: - name: - description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." - maxLength: 256 - minLength: 1 - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP query param to be matched. - maxLength: 1024 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. - properties: - filters: - description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." - items: - description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. - items: - description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH - type: string - path: - default: - type: PathPrefix - value: / - description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. - properties: - type: - default: PathPrefix - description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - PathPrefix - - RegularExpression - type: string - value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 - type: string - type: object - queryParams: - description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" - items: - description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. - properties: - name: - description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." - maxLength: 256 - minLength: 1 - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP query param to be matched. - maxLength: 1024 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-httproutes.yaml b/charts/consul/templates/crd-httproutes.yaml deleted file mode 100644 index 3da6e1e637..0000000000 --- a/charts/consul/templates/crd-httproutes.yaml +++ /dev/null @@ -1,673 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: httproutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: HTTPRoute - listKind: HTTPRouteList - plural: httproutes - shortNames: - - http-route - singular: httproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: HTTPRoute is the Schema for the HTTP Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute - \n This is a Resource type." - properties: - hostnames: - description: "Hostnames are the hostnames for which this HTTPRoute - should respond to requests. \n This is only valid for north/south." - items: - type: string - type: array - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of HTTP-based routing rules that this - route should use for constructing a routing table. - items: - description: HTTPRouteRule specifies the routing rules used to determine - what upstream service an HTTP request is routed to. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. \n Failure behavior here depends - on how many BackendRefs are specified and how many are invalid. - \n If all entries in BackendRefs are invalid, and there are - also no filters specified in this route rule, all traffic - which matches this rule MUST receive a 500 status code. \n - See the HTTPBackendRef definition for the rules about what - makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef - is invalid, 500 status codes MUST be returned for requests - that would have otherwise been routed to an invalid backend. - If multiple backends are specified, and some are invalid, - the proportion of requests that would otherwise have been - routed to an invalid backend MUST receive a 500 status code. - \n For example, if two backends are specified with equal weights, - and one is invalid, 50 percent of traffic must receive a 500. - Implementations may choose how that 50 percent is determined." - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - filters: - description: Filters defined at this level should be executed - if and only if the request is being forwarded to the - backend defined here. - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema - for a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema - for a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - filters: - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema for - a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema for - a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - matches: - items: - properties: - headers: - description: Headers specifies HTTP request header matchers. - Multiple match values are ANDed together, meaning, a - request must match all the specified headers to select - the route. - items: - properties: - invert: - description: 'NOTE: not in gamma; service-router - compat' - type: boolean - name: - description: "Name is the name of the HTTP Header - to be matched. Name matching MUST be case insensitive. - (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent header - names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent header name MUST be - ignored. Due to the case-insensitivity of header - names, “foo” and “Foo” are considered equivalent. - \n When a header is repeated in an HTTP request, - it is implementation-specific behavior as to how - this is represented. Generally, proxies should - follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 - regarding processing a repeated header, with special - handling for “Set-Cookie”." - type: string - type: - description: Type specifies how to match against - the value of the header. - enum: - - HEADER_MATCH_TYPE_UNSPECIFIED - - HEADER_MATCH_TYPE_EXACT - - HEADER_MATCH_TYPE_REGEX - - HEADER_MATCH_TYPE_PRESENT - - HEADER_MATCH_TYPE_PREFIX - - HEADER_MATCH_TYPE_SUFFIX - format: int32 - type: string - value: - description: Value is the value of HTTP Header to - be matched. - type: string - type: object - type: array - method: - description: Method specifies HTTP method matcher. When - specified, this route will be matched only if the request - has the specified method. - type: string - path: - description: Path specifies a HTTP request path matcher. - If this field is not specified, a default prefix match - on the “/” path is provided. - properties: - type: - description: Type specifies how to match against the - path Value. - enum: - - PATH_MATCH_TYPE_UNSPECIFIED - - PATH_MATCH_TYPE_EXACT - - PATH_MATCH_TYPE_PREFIX - - PATH_MATCH_TYPE_REGEX - format: int32 - type: string - value: - description: Value of the HTTP path to match against. - type: string - type: object - queryParams: - description: QueryParams specifies HTTP query parameter - matchers. Multiple match values are ANDed together, - meaning, a request must match all the specified query - parameters to select the route. - items: - properties: - name: - description: "Name is the name of the HTTP query - param to be matched. This must be an exact string - match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). - \n If multiple entries specify equivalent query - param names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent query param name MUST - be ignored. \n If a query param is repeated in - an HTTP request, the behavior is purposely left - undefined, since different data planes have different - capabilities. However, it is recommended that - implementations should match against the first - value of the param if the data plane supports - it, as this behavior is expected in other load - balancing contexts outside of the Gateway API. - \n Users SHOULD NOT route traffic based on repeated - query params to guard themselves against potential - differences in the implementations." - type: string - type: - description: Type specifies how to match against - the value of the query parameter. - enum: - - QUERY_PARAM_MATCH_TYPE_UNSPECIFIED - - QUERY_PARAM_MATCH_TYPE_EXACT - - QUERY_PARAM_MATCH_TYPE_REGEX - - QUERY_PARAM_MATCH_TYPE_PRESENT - format: int32 - type: string - value: - description: Value is the value of HTTP query param - to be matched. - type: string - type: object - type: array - type: object - type: array - retries: - properties: - number: - description: Number is the number of times to retry the - request when a retryable result occurs. - properties: - value: - description: The uint32 value. - format: int32 - type: integer - type: object - onConditions: - description: RetryOn allows setting envoy specific conditions - when a request should be automatically retried. - items: - type: string - type: array - onConnectFailure: - description: RetryOnConnectFailure allows for connection - failure errors to trigger a retry. - type: boolean - onStatusCodes: - description: RetryOnStatusCodes is a flat list of http response - status codes that are eligible for retry. This again should - be feasible in any reasonable proxy. - items: - format: int32 - type: integer - type: array - type: object - timeouts: - description: HTTPRouteTimeouts defines timeouts that can be - configured for an HTTPRoute or GRPCRoute. - properties: - idle: - description: Idle specifies the total amount of time permitted - for the request stream to be idle. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - request: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index dcbc543525..a01fafd8dd 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: ingressgateways.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -76,43 +78,6 @@ spec: while waiting for a connection to be established. format: int32 type: integer - passiveHealthCheck: - description: PassiveHealthCheck configuration determines how upstream - proxy instances will be monitored for removal from the load - balancing pool. - properties: - baseEjectionTime: - description: The base time that a host is ejected for. The - real time is equal to the base time multiplied by the number - of times the host has been ejected and is capped by max_ejection_time - (Default 300s). Defaults to 30s. - type: string - enforcingConsecutive5xx: - description: EnforcingConsecutive5xx is the % chance that - a host will be actually ejected when an outlier status is - detected through consecutive 5xx. This setting can be used - to disable ejection or to ramp it up slowly. Ex. Setting - this to 10 will make it a 10% chance that the host will - be ejected. - format: int32 - type: integer - interval: - description: Interval between health check analysis sweeps. - Each sweep may remove hosts or return hosts to the pool. - Ex. setting this to "10s" will set the interval to 10 seconds. - type: string - maxEjectionPercent: - description: The maximum % of an upstream cluster that can - be ejected due to outlier detection. Defaults to 10% but - will eject at least one host regardless of the value. - format: int32 - type: integer - maxFailures: - description: MaxFailures is the count of consecutive failures - that results in a host being removed from the pool. - format: int32 - type: integer - type: object type: object listeners: description: Listeners declares what ports the ingress gateway should @@ -192,47 +157,6 @@ spec: service is located. Partitioning is a Consul Enterprise feature. type: string - passiveHealthCheck: - description: PassiveHealthCheck configuration determines - how upstream proxy instances will be monitored for removal - from the load balancing pool. - properties: - baseEjectionTime: - description: The base time that a host is ejected - for. The real time is equal to the base time multiplied - by the number of times the host has been ejected - and is capped by max_ejection_time (Default 300s). - Defaults to 30s. - type: string - enforcingConsecutive5xx: - description: EnforcingConsecutive5xx is the % chance - that a host will be actually ejected when an outlier - status is detected through consecutive 5xx. This - setting can be used to disable ejection or to ramp - it up slowly. Ex. Setting this to 10 will make it - a 10% chance that the host will be ejected. - format: int32 - type: integer - interval: - description: Interval between health check analysis - sweeps. Each sweep may remove hosts or return hosts - to the pool. Ex. setting this to "10s" will set - the interval to 10 seconds. - type: string - maxEjectionPercent: - description: The maximum % of an upstream cluster - that can be ejected due to outlier detection. Defaults - to 10% but will eject at least one host regardless - of the value. - format: int32 - type: integer - maxFailures: - description: MaxFailures is the count of consecutive - failures that results in a host being removed from - the pool. - format: int32 - type: integer - type: object requestHeaders: description: Allow HTTP header manipulation to be configured. properties: @@ -444,4 +368,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml deleted file mode 100644 index 94c9697b33..0000000000 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ /dev/null @@ -1,313 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: jwtproviders.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: JWTProvider - listKind: JWTProviderList - plural: jwtproviders - singular: jwtprovider - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: JWTProvider is the Schema for the jwtproviders API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: JWTProviderSpec defines the desired state of JWTProvider - properties: - audiences: - description: Audiences is the set of audiences the JWT is allowed - to access. If specified, all JWTs verified with this provider must - address at least one of these to be considered valid. - items: - type: string - type: array - cacheConfig: - description: CacheConfig defines configuration for caching the validation - result for previously seen JWTs. Caching results can speed up verification - when individual tokens are expected to be handled multiple times. - properties: - size: - description: "Size specifies the maximum number of JWT verification - results to cache. \n Defaults to 0, meaning that JWT caching - is disabled." - type: integer - type: object - clockSkewSeconds: - description: "ClockSkewSeconds specifies the maximum allowable time - difference from clock skew when validating the \"exp\" (Expiration) - and \"nbf\" (Not Before) claims. \n Default value is 30 seconds." - type: integer - forwarding: - description: Forwarding defines rules for forwarding verified JWTs - to the backend. - properties: - headerName: - description: "HeaderName is a header name to use when forwarding - a verified JWT to the backend. The verified JWT could have been - extracted from any location (query param, header, or cookie). - \n The header value will be base64-URL-encoded, and will not - be padded unless PadForwardPayloadHeader is true." - type: string - padForwardPayloadHeader: - description: "PadForwardPayloadHeader determines whether padding - should be added to the base64 encoded token forwarded with ForwardPayloadHeader. - \n Default value is false." - type: boolean - type: object - issuer: - description: Issuer is the entity that must have issued the JWT. This - value must match the "iss" claim of the token. - type: string - jsonWebKeySet: - description: JSONWebKeySet defines a JSON Web Key Set, its location - on disk, or the means with which to fetch a key set from a remote - server. - properties: - local: - description: Local specifies a local source for the key set. - properties: - filename: - description: Filename configures a location on disk where - the JWKS can be found. If specified, the file must be present - on the disk of ALL proxies with intentions referencing this - provider. - type: string - jwks: - description: JWKS contains a base64 encoded JWKS. - type: string - type: object - remote: - description: Remote specifies how to fetch a key set from a remote - server. - properties: - cacheDuration: - description: "CacheDuration is the duration after which cached - keys should be expired. \n Default value is 5 minutes." - type: string - fetchAsynchronously: - description: "FetchAsynchronously indicates that the JWKS - should be fetched when a client request arrives. Client - requests will be paused until the JWKS is fetched. If false, - the proxy listener will wait for the JWKS to be fetched - before being activated. \n Default value is false." - type: boolean - jwksCluster: - description: JWKSCluster defines how the specified Remote - JWKS URI is to be fetched. - properties: - connectTimeout: - description: The timeout for new network connections to - hosts in the cluster. If not set, a default value of - 5s will be used. - type: string - discoveryType: - description: "DiscoveryType refers to the service discovery - type to use for resolving the cluster. \n This defaults - to STRICT_DNS. Other options include STATIC, LOGICAL_DNS, - EDS or ORIGINAL_DST." - type: string - tlsCertificates: - description: "TLSCertificates refers to the data containing - certificate authority certificates to use in verifying - a presented peer certificate. If not specified and a - peer certificate is presented it will not be verified. - \n Must be either CaCertificateProviderInstance or TrustedCA." - properties: - caCertificateProviderInstance: - description: CaCertificateProviderInstance Certificate - provider instance for fetching TLS certificates. - properties: - certificateName: - description: "CertificateName is used to specify - certificate instances or types. For example, - \"ROOTCA\" to specify a root-certificate (validation - context) or \"example.com\" to specify a certificate - for a particular domain. \n The default value - is the empty string." - type: string - instanceName: - description: "InstanceName refers to the certificate - provider instance name. \n The default value - is \"default\"." - type: string - type: object - trustedCA: - description: "TrustedCA defines TLS certificate data - containing certificate authority certificates to - use in verifying a presented peer certificate. \n - Exactly one of Filename, EnvironmentVariable, InlineString - or InlineBytes must be specified." - properties: - environmentVariable: - type: string - filename: - type: string - inlineBytes: - format: byte - type: string - inlineString: - type: string - type: object - type: object - type: object - requestTimeoutMs: - description: RequestTimeoutMs is the number of milliseconds - to time out when making a request for the JWKS. - type: integer - retryPolicy: - description: "RetryPolicy defines a retry policy for fetching - JWKS. \n There is no retry by default." - properties: - numRetries: - description: "NumRetries is the number of times to retry - fetching the JWKS. The retry strategy uses jittered - exponential backoff with a base interval of 1s and max - of 10s. \n Default value is 0." - type: integer - retryPolicyBackOff: - description: "Retry's backoff policy. \n Defaults to Envoy's - backoff policy." - properties: - baseInterval: - description: "BaseInterval to be used for the next - back off computation. \n The default value from - envoy is 1s." - type: string - maxInterval: - description: "MaxInternal to be used to specify the - maximum interval between retries. Optional but should - be greater or equal to BaseInterval. \n Defaults - to 10 times BaseInterval." - type: string - type: object - type: object - uri: - description: URI is the URI of the server to query for the - JWKS. - type: string - type: object - type: object - locations: - description: 'Locations where the JWT will be present in requests. - Envoy will check all of these locations to extract a JWT. If no - locations are specified Envoy will default to: 1. Authorization - header with Bearer schema: "Authorization: Bearer " 2. accessToken - query parameter.' - items: - description: "JWTLocation is a location where the JWT could be present - in requests. \n Only one of Header, QueryParam, or Cookie can - be specified." - properties: - cookie: - description: Cookie defines how to extract a JWT from an HTTP - request cookie. - properties: - name: - description: Name is the name of the cookie containing the - token. - type: string - type: object - header: - description: Header defines how to extract a JWT from an HTTP - request header. - properties: - forward: - description: "Forward defines whether the header with the - JWT should be forwarded after the token has been verified. - If false, the header will not be forwarded to the backend. - \n Default value is false." - type: boolean - name: - description: Name is the name of the header containing the - token. - type: string - valuePrefix: - description: 'ValuePrefix is an optional prefix that precedes - the token in the header value. For example, "Bearer " - is a standard value prefix for a header named "Authorization", - but the prefix is not part of the token itself: "Authorization: - Bearer "' - type: string - type: object - queryParam: - description: QueryParam defines how to extract a JWT from an - HTTP request query parameter. - properties: - name: - description: Name is the name of the query param containing - the token. - type: string - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-meshconfigurations.yaml b/charts/consul/templates/crd-meshconfigurations.yaml deleted file mode 100644 index 21114d723f..0000000000 --- a/charts/consul/templates/crd-meshconfigurations.yaml +++ /dev/null @@ -1,100 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: meshconfigurations.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: MeshConfiguration - listKind: MeshConfigurationList - plural: meshconfigurations - singular: meshconfiguration - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: MeshConfiguration is the Schema for the Mesh Configuration - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: MeshConfiguration is responsible for configuring the default - behavior of Mesh Gateways. This is a Resource type. - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index f8ce4fc12e..2e33eb9653 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: meshes.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -53,11 +55,6 @@ spec: spec: description: MeshSpec defines the desired state of Mesh. properties: - allowEnablingPermissiveMutualTLS: - description: AllowEnablingPermissiveMutualTLS must be true in order - to allow setting MutualTLSMode=permissive in either service-defaults - or proxy-defaults. - type: boolean http: description: HTTP defines the HTTP configuration for the service mesh. properties: @@ -204,4 +201,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshgateways.yaml b/charts/consul/templates/crd-meshgateways.yaml deleted file mode 100644 index 6202add695..0000000000 --- a/charts/consul/templates/crd-meshgateways.yaml +++ /dev/null @@ -1,134 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: meshgateways.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: MeshGateway - listKind: MeshGatewayList - plural: meshgateways - singular: meshgateway - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: MeshGateway is the Schema for the Mesh Gateway API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - gatewayClassName: - description: GatewayClassName is the name of the GatewayClass used - by the MeshGateway - type: string - listeners: - items: - properties: - name: - type: string - port: - format: int32 - maximum: 65535 - minimum: 0 - type: integer - protocol: - enum: - - TCP - type: string - type: object - minItems: 1 - type: array - workloads: - description: Selection of workloads to be configured as mesh gateways - properties: - filter: - type: string - names: - items: - type: string - type: array - prefixes: - items: - type: string - type: array - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml deleted file mode 100644 index a5d36fb966..0000000000 --- a/charts/consul/templates/crd-meshservices.yaml +++ /dev/null @@ -1,56 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: meshservices.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: MeshService - listKind: MeshServiceList - plural: meshservices - singular: meshservice - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: MeshService holds a reference to an externally managed Consul - Service Mesh service. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of MeshService. - properties: - name: - description: Name holds the service name for a Consul service. - type: string - peer: - description: Peer optionally specifies the name of the peer exporting - the Consul service. If not specified, the Consul service is assumed - to be in the local datacenter. - type: string - type: object - type: object - served: true - storage: true -{{- end }} diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index 2352ba7ad3..e06e830f04 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -1,16 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: peeringacceptors.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -143,4 +145,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index 09991d2091..e24401e761 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -1,16 +1,18 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: peeringdialers.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -143,4 +145,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-proxyconfigurations.yaml b/charts/consul/templates/crd-proxyconfigurations.yaml deleted file mode 100644 index 3d19d5ea4f..0000000000 --- a/charts/consul/templates/crd-proxyconfigurations.yaml +++ /dev/null @@ -1,405 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: proxyconfigurations.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: ProxyConfiguration - listKind: ProxyConfigurationList - plural: proxyconfigurations - shortNames: - - proxy-configuration - singular: proxyconfiguration - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: ProxyConfiguration is the Schema for the TCP Routes API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: This is a Resource type. - properties: - bootstrapConfig: - description: bootstrap_config is the configuration that requires proxies - to be restarted to be applied. - properties: - dogstatsdUrl: - type: string - overrideJsonTpl: - type: string - prometheusBindAddr: - type: string - readyBindAddr: - type: string - staticClustersJson: - type: string - staticListenersJson: - type: string - statsBindAddr: - type: string - statsConfigJson: - type: string - statsFlushInterval: - type: string - statsSinksJson: - type: string - statsTags: - items: - type: string - type: array - statsdUrl: - type: string - telemetryCollectorBindSocketDir: - type: string - tracingConfigJson: - type: string - type: object - dynamicConfig: - description: dynamic_config is the configuration that could be changed - dynamically (i.e. without needing restart). - properties: - accessLogs: - description: AccessLogs configures the output and format of Envoy - access logs - properties: - disableListenerLogs: - description: DisableListenerLogs turns off just listener logs - for connections rejected by Envoy because they don't have - a matching listener filter. - type: boolean - enabled: - description: Enabled turns off all access logging - type: boolean - jsonFormat: - description: The presence of one format string or the other - implies the access log string encoding. Defining both is - invalid. - type: string - path: - description: Path is the output file to write logs - type: string - textFormat: - type: string - type: - description: 'Type selects the output for logs: "file", "stderr". - "stdout"' - enum: - - LOG_SINK_TYPE_DEFAULT - - LOG_SINK_TYPE_FILE - - LOG_SINK_TYPE_STDERR - - LOG_SINK_TYPE_STDOUT - format: int32 - type: string - type: object - exposeConfig: - properties: - exposePaths: - items: - properties: - listenerPort: - format: int32 - type: integer - localPathPort: - format: int32 - type: integer - path: - type: string - protocol: - enum: - - EXPOSE_PATH_PROTOCOL_HTTP - - EXPOSE_PATH_PROTOCOL_HTTP2 - format: int32 - type: string - type: object - type: array - type: object - inboundConnections: - description: inbound_connections configures inbound connections - to the proxy. - properties: - balanceInboundConnections: - enum: - - BALANCE_CONNECTIONS_DEFAULT - - BALANCE_CONNECTIONS_EXACT - format: int32 - type: string - maxInboundConnections: - format: int32 - type: integer - type: object - listenerTracingJson: - type: string - localClusterJson: - type: string - localConnection: - additionalProperties: - description: Referenced by ProxyConfiguration - properties: - connectTimeout: - description: "A Duration represents a signed, fixed-length - span of time represented as a count of seconds and fractions - of seconds at nanosecond resolution. It is independent - of any calendar and concepts like \"day\" or \"month\". - It is related to Timestamp in that the difference between - two Timestamp values is a Duration and it can be added - or subtracted from a Timestamp. Range is approximately - +-10,000 years. \n # Examples \n Example 1: Compute Duration - from two Timestamps in pseudo code. \n Timestamp start - = ...; Timestamp end = ...; Duration duration = ...; \n - duration.seconds = end.seconds - start.seconds; duration.nanos - = end.nanos - start.nanos; \n if (duration.seconds < 0 - && duration.nanos > 0) { duration.seconds += 1; duration.nanos - -= 1000000000; } else if (duration.seconds > 0 && duration.nanos - < 0) { duration.seconds -= 1; duration.nanos += 1000000000; - } \n Example 2: Compute Timestamp from Timestamp + Duration - in pseudo code. \n Timestamp start = ...; Duration duration - = ...; Timestamp end = ...; \n end.seconds = start.seconds - + duration.seconds; end.nanos = start.nanos + duration.nanos; - \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += - 1000000000; } else if (end.nanos >= 1000000000) { end.seconds - += 1; end.nanos -= 1000000000; } \n Example 3: Compute - Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, - minutes=10) duration = Duration() duration.FromTimedelta(td) - \n # JSON Mapping \n In JSON format, the Duration type - is encoded as a string rather than an object, where the - string ends in the suffix \"s\" (indicating seconds) and - is preceded by the number of seconds, with nanoseconds - expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as - \"3s\", while 3 seconds and 1 nanosecond should be expressed - in JSON format as \"3.000000001s\", and 3 seconds and - 1 microsecond should be expressed in JSON format as \"3.000001s\"." - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - requestTimeout: - description: "A Duration represents a signed, fixed-length - span of time represented as a count of seconds and fractions - of seconds at nanosecond resolution. It is independent - of any calendar and concepts like \"day\" or \"month\". - It is related to Timestamp in that the difference between - two Timestamp values is a Duration and it can be added - or subtracted from a Timestamp. Range is approximately - +-10,000 years. \n # Examples \n Example 1: Compute Duration - from two Timestamps in pseudo code. \n Timestamp start - = ...; Timestamp end = ...; Duration duration = ...; \n - duration.seconds = end.seconds - start.seconds; duration.nanos - = end.nanos - start.nanos; \n if (duration.seconds < 0 - && duration.nanos > 0) { duration.seconds += 1; duration.nanos - -= 1000000000; } else if (duration.seconds > 0 && duration.nanos - < 0) { duration.seconds -= 1; duration.nanos += 1000000000; - } \n Example 2: Compute Timestamp from Timestamp + Duration - in pseudo code. \n Timestamp start = ...; Duration duration - = ...; Timestamp end = ...; \n end.seconds = start.seconds - + duration.seconds; end.nanos = start.nanos + duration.nanos; - \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += - 1000000000; } else if (end.nanos >= 1000000000) { end.seconds - += 1; end.nanos -= 1000000000; } \n Example 3: Compute - Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, - minutes=10) duration = Duration() duration.FromTimedelta(td) - \n # JSON Mapping \n In JSON format, the Duration type - is encoded as a string rather than an object, where the - string ends in the suffix \"s\" (indicating seconds) and - is preceded by the number of seconds, with nanoseconds - expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as - \"3s\", while 3 seconds and 1 nanosecond should be expressed - in JSON format as \"3.000000001s\", and 3 seconds and - 1 microsecond should be expressed in JSON format as \"3.000001s\"." - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - description: local_connection is the configuration that should - be used to connect to the local application provided per-port. - The map keys should correspond to port names on the workload. - type: object - localWorkloadAddress: - description: "deprecated: local_workload_address, local_workload_port, - and local_workload_socket_path are deprecated and are only needed - for migration of existing resources. \n Deprecated: Marked as - deprecated in pbmesh/v2beta1/proxy_configuration.proto." - type: string - localWorkloadPort: - description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' - format: int32 - type: integer - localWorkloadSocketPath: - description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' - type: string - meshGatewayMode: - enum: - - MESH_GATEWAY_MODE_UNSPECIFIED - - MESH_GATEWAY_MODE_NONE - - MESH_GATEWAY_MODE_LOCAL - - MESH_GATEWAY_MODE_REMOTE - format: int32 - type: string - mode: - description: mode indicates the proxy's mode. This will default - to 'transparent'. - enum: - - PROXY_MODE_DEFAULT - - PROXY_MODE_TRANSPARENT - - PROXY_MODE_DIRECT - format: int32 - type: string - mutualTlsMode: - enum: - - MUTUAL_TLS_MODE_DEFAULT - - MUTUAL_TLS_MODE_STRICT - - MUTUAL_TLS_MODE_PERMISSIVE - format: int32 - type: string - publicListenerJson: - type: string - transparentProxy: - properties: - dialedDirectly: - description: dialed_directly indicates whether this proxy - should be dialed using original destination IP in the connection - rather than load balance between all endpoints. - type: boolean - outboundListenerPort: - description: outbound_listener_port is the port for the proxy's - outbound listener. This defaults to 15001. - format: int32 - type: integer - type: object - type: object - opaqueConfig: - description: "deprecated: prevent usage when using v2 APIs directly. - needed for backwards compatibility \n Deprecated: Marked as deprecated - in pbmesh/v2beta1/proxy_configuration.proto." - type: object - x-kubernetes-preserve-unknown-fields: true - workloads: - description: Selection of workloads this proxy configuration should - apply to. These can be prefixes or specific workload names. - properties: - filter: - type: string - names: - items: - type: string - type: array - prefixes: - items: - type: string - type: array - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index ce49c9149a..e66543637f 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: proxydefaults.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -55,60 +57,12 @@ spec: spec: description: ProxyDefaultsSpec defines the desired state of ProxyDefaults. properties: - accessLogs: - description: AccessLogs controls all envoy instances' access logging - configuration. - properties: - disableListenerLogs: - description: DisableListenerLogs turns off just listener logs - for connections rejected by Envoy because they don't have a - matching listener filter. - type: boolean - enabled: - description: Enabled turns on all access logging - type: boolean - jsonFormat: - description: 'JSONFormat is a JSON-formatted string of an Envoy - access log format dictionary. See for more info on formatting: - https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-dictionaries - Defining JSONFormat and TextFormat is invalid.' - type: string - path: - description: Path is the output file to write logs for file-type - logging - type: string - textFormat: - description: 'TextFormat is a representation of Envoy access logs - format. See for more info on formatting: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-strings - Defining JSONFormat and TextFormat is invalid.' - type: string - type: - description: Type selects the output for logs one of "file", "stderr". - "stdout" - type: string - type: object config: description: Config is an arbitrary map of configuration values used by Connect proxies. Any values that your proxy allows can be configured globally here. Supports JSON config values. See https://www.consul.io/docs/connect/proxies/envoy#configuration-formatting type: object x-kubernetes-preserve-unknown-fields: true - envoyExtensions: - description: EnvoyExtensions are a list of extensions to modify Envoy - proxy configuration. - items: - description: EnvoyExtension has configuration for an extension that - patches Envoy resources. - properties: - arguments: - type: object - x-kubernetes-preserve-unknown-fields: true - name: - type: string - required: - type: boolean - type: object - type: array expose: description: Expose controls the default expose path configuration for Envoy. @@ -141,23 +95,6 @@ spec: type: object type: array type: object - failoverPolicy: - description: FailoverPolicy specifies the exact mechanism used for - failover. - properties: - mode: - description: Mode specifies the type of failover that will be - performed. Valid values are "sequential", "" (equivalent to - "sequential") and "order-by-locality". - type: string - regions: - description: Regions is the ordered list of the regions of the - failover targets. Valid values can be "us-west-1", "us-west-2", - and so on. - items: - type: string - type: array - type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. @@ -178,28 +115,6 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string - mutualTLSMode: - description: 'MutualTLSMode controls whether mutual TLS is required - for all incoming connections when transparent proxy is enabled. - This can be set to "permissive" or "strict". "strict" is the default - which requires mutual TLS for incoming connections. In the insecure - "permissive" mode, connections to the sidecar proxy public listener - port require mutual TLS, but connections to the service port do - not require mutual TLS and are proxied to the application unmodified. - Note: Intentions are not enforced for non-mTLS connections. To keep - your services secure, we recommend using "strict" mode whenever - possible and enabling "permissive" mode only when necessary.' - type: string - prioritizeByLocality: - description: PrioritizeByLocality controls whether the locality of - services within the local partition will be used to prioritize connectivity. - properties: - mode: - description: 'Mode specifies the type of prioritization that will - be performed when selecting nodes in the local partition. Valid - values are: "" (default "none"), "none", and "failover".' - type: string - type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the @@ -262,4 +177,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-referencegrants-external.yaml b/charts/consul/templates/crd-referencegrants-external.yaml deleted file mode 100644 index db9cf12027..0000000000 --- a/charts/consul/templates/crd-referencegrants-external.yaml +++ /dev/null @@ -1,208 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: referencegrants.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: ReferenceGrant - listKind: ReferenceGrantList - plural: referencegrants - shortNames: - - refgrant - singular: referencegrant - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed. \n Support: Core" - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of ReferenceGrant. - properties: - from: - description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" - items: - description: ReferenceGrantFrom describes trusted namespaces and kinds. - properties: - group: - description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - namespace: - description: "Namespace is the namespace of the referent. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - namespace - type: object - maxItems: 16 - minItems: 1 - type: array - to: - description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" - items: - description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. - properties: - group: - description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - from - - to - type: object - type: object - served: true - storage: true - subresources: {} - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed. \n Support: Core" - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of ReferenceGrant. - properties: - from: - description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" - items: - description: ReferenceGrantFrom describes trusted namespaces and kinds. - properties: - group: - description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - namespace: - description: "Namespace is the namespace of the referent. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - namespace - type: object - maxItems: 16 - minItems: 1 - type: array - to: - description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" - items: - description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. - properties: - group: - description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - from - - to - type: object - type: object - served: true - storage: false - subresources: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-routeauthfilters.yaml b/charts/consul/templates/crd-routeauthfilters.yaml deleted file mode 100644 index a51bf226cd..0000000000 --- a/charts/consul/templates/crd-routeauthfilters.yaml +++ /dev/null @@ -1,199 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: routeauthfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteAuthFilter - listKind: RouteAuthFilterList - plural: routeauthfilters - singular: routeauthfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteAuthFilter is the Schema for the routeauthfilters API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteAuthFilterSpec defines the desired state of RouteAuthFilter. - properties: - jwt: - description: This re-uses the JWT requirement type from Gateway Policy - Types. - properties: - providers: - description: Providers is a list of providers to consider when - verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry with - this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the actual - claim information to be verified. - properties: - path: - description: Path is the path to the claim in the - token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the given - path: - If the type at the path is a list then we - verify that this value is contained in the list. - \n - If the type at the path is a string then we - verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - status: - description: RouteAuthFilterStatus defines the observed state of the gateway. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: ResolvedRefs - description: "Conditions describe the current conditions of the Filter. - \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-routeretryfilters.yaml b/charts/consul/templates/crd-routeretryfilters.yaml deleted file mode 100644 index 14b6062f60..0000000000 --- a/charts/consul/templates/crd-routeretryfilters.yaml +++ /dev/null @@ -1,115 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: routeretryfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteRetryFilter - listKind: RouteRetryFilterList - plural: routeretryfilters - singular: routeretryfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteRetryFilter is the Schema for the routeretryfilters API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter. - properties: - numRetries: - format: int32 - minimum: 0 - type: integer - retryOn: - items: - type: string - type: array - retryOnConnectFailure: - type: boolean - retryOnStatusCodes: - items: - format: int32 - type: integer - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-routetimeoutfilters.yaml b/charts/consul/templates/crd-routetimeoutfilters.yaml deleted file mode 100644 index 07ebfe9386..0000000000 --- a/charts/consul/templates/crd-routetimeoutfilters.yaml +++ /dev/null @@ -1,107 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: routetimeoutfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteTimeoutFilter - listKind: RouteTimeoutFilterList - plural: routetimeoutfilters - singular: routetimeoutfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteTimeoutFilter is the Schema for the httproutetimeoutfilters - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. - properties: - idleTimeout: - format: duration - type: string - requestTimeout: - format: duration - type: string - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml deleted file mode 100644 index ea0ad7c8a0..0000000000 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ /dev/null @@ -1,129 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: samenessgroups.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: SamenessGroup - listKind: SamenessGroupList - plural: samenessgroups - shortNames: - - sameness-group - singular: samenessgroup - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: SamenessGroup is the Schema for the samenessgroups API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SamenessGroupSpec defines the desired state of SamenessGroup. - properties: - defaultForFailover: - description: DefaultForFailover indicates that upstream requests to - members of the given sameness group will implicitly failover between - members of this sameness group. When DefaultForFailover is true, - the local partition must be a member of the sameness group or IncludeLocal - must be set to true. - type: boolean - includeLocal: - description: IncludeLocal is used to include the local partition as - the first member of the sameness group. The local partition can - only be a member of a single sameness group. - type: boolean - members: - description: Members are the partitions and peers that are part of - the sameness group. If a member of a sameness group does not exist, - it will be ignored. - items: - properties: - partition: - description: The partitions and peers that are part of the sameness - group. A sameness group member cannot define both peer and - partition at the same time. - type: string - peer: - type: string - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index c7e2b5bb2b..3b5503eebe 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicedefaults.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -55,12 +57,6 @@ spec: spec: description: ServiceDefaultsSpec defines the desired state of ServiceDefaults. properties: - balanceInboundConnections: - description: BalanceInboundConnections sets the strategy for allocating - inbound connections to the service across proxy threads. The only - supported value is exact_balance. By default, no connection balancing - is used. Refer to the Envoy Connection Balance config for details. - type: string destination: description: Destination is an address(es)/port combination that represents an endpoint outside the mesh. This is only valid when the mesh is @@ -80,22 +76,6 @@ spec: format: int32 type: integer type: object - envoyExtensions: - description: EnvoyExtensions are a list of extensions to modify Envoy - proxy configuration. - items: - description: EnvoyExtension has configuration for an extension that - patches Envoy resources. - properties: - arguments: - type: object - x-kubernetes-preserve-unknown-fields: true - name: - type: string - required: - type: boolean - type: object - type: array expose: description: Expose controls the default expose path configuration for Envoy. @@ -134,15 +114,15 @@ spec: with an external system. type: string localConnectTimeoutMs: - description: LocalConnectTimeoutMs is the number of milliseconds allowed - to make connections to the local application instance before timing - out. Defaults to 5000. + description: The number of milliseconds allowed to make connections + to the local application instance before timing out. Defaults to + 5000. type: integer localRequestTimeoutMs: - description: LocalRequestTimeoutMs is the timeout for HTTP requests - to the local application instance in milliseconds. Applies to HTTP-based - protocols only. If not specified, inherits the Envoy default for - route timeouts (15s). + description: In milliseconds, the timeout for HTTP requests to the + local application instance. Applies to HTTP-based protocols only. + If not specified, inherits the Envoy default for route timeouts + (15s). type: integer maxInboundConnections: description: MaxInboundConnections is the maximum number of concurrent @@ -169,87 +149,12 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string - mutualTLSMode: - description: 'MutualTLSMode controls whether mutual TLS is required - for all incoming connections when transparent proxy is enabled. - This can be set to "permissive" or "strict". "strict" is the default - which requires mutual TLS for incoming connections. In the insecure - "permissive" mode, connections to the sidecar proxy public listener - port require mutual TLS, but connections to the service port do - not require mutual TLS and are proxied to the application unmodified. - Note: Intentions are not enforced for non-mTLS connections. To keep - your services secure, we recommend using "strict" mode whenever - possible and enabling "permissive" mode only when necessary.' - type: string protocol: description: Protocol sets the protocol of the service. This is used by Connect proxies for things like observability features and to unlock usage of the service-splitter and service-router config entries for a service. type: string - rateLimits: - description: RateLimits is rate limiting configuration that is applied - to inbound traffic for a service. Rate limiting is a Consul enterprise - feature. - properties: - instanceLevel: - description: InstanceLevel represents rate limit configuration - that is applied per service instance. - properties: - requestsMaxBurst: - description: "RequestsMaxBurst is the maximum number of requests - that can be sent in a burst. Should be equal to or greater - than RequestsPerSecond. If unset, defaults to RequestsPerSecond. - \n Internally, this is the maximum size of the token bucket - used for rate limiting." - type: integer - requestsPerSecond: - description: "RequestsPerSecond is the average number of requests - per second that can be made without being throttled. This - field is required if RequestsMaxBurst is set. The allowed - number of requests may exceed RequestsPerSecond up to the - value specified in RequestsMaxBurst. \n Internally, this - is the refill rate of the token bucket used for rate limiting." - type: integer - routes: - description: Routes is a list of rate limits applied to specific - routes. For a given request, the first matching route will - be applied, if any. Overrides any top-level configuration. - items: - properties: - pathExact: - description: Exact path to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - pathPrefix: - description: Prefix to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - pathRegex: - description: Regex to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - requestsMaxBurst: - description: RequestsMaxBurst is the maximum number - of requests that can be sent in a burst. Should be - equal to or greater than RequestsPerSecond. If unset, - defaults to RequestsPerSecond. Internally, this is - the maximum size of the token bucket used for rate - limiting. - type: integer - requestsPerSecond: - description: RequestsPerSecond is the average number - of requests per second that can be made without being - throttled. This field is required if RequestsMaxBurst - is set. The allowed number of requests may exceed - RequestsPerSecond up to the value specified in RequestsMaxBurst. - Internally, this is the refill rate of the token bucket - used for rate limiting. - type: integer - type: object - type: array - type: object - type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the @@ -331,15 +236,15 @@ spec: type: string type: object name: - description: Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Name is only accepted within a service-defaults config entry. type: string namespace: - description: Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Namespace is only accepted within a service-defaults config entry. type: string partition: - description: Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Partition is only accepted within a service-defaults config entry. type: string passiveHealthCheck: @@ -352,22 +257,18 @@ spec: The real time is equal to the base time multiplied by the number of times the host has been ejected and is capped by max_ejection_time (Default 300s). Defaults - to 30s. + to 30000ms or 30s. type: string enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can be used to disable ejection or to ramp it up slowly. - Ex. Setting this to 10 will make it a 10% chance that - the host will be ejected. format: int32 type: integer interval: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts to the pool. - Ex. setting this to "10s" will set the interval to 10 - seconds. type: string maxEjectionPercent: description: The maximum % of an upstream cluster that @@ -382,10 +283,6 @@ spec: format: int32 type: integer type: object - peer: - description: Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides - config entry. - type: string protocol: description: Protocol describes the upstream's service protocol. Valid values are "tcp", "http" and "grpc". Anything else @@ -452,15 +349,15 @@ spec: type: string type: object name: - description: Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Name is only accepted within a service-defaults config entry. type: string namespace: - description: Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Namespace is only accepted within a service-defaults config entry. type: string partition: - description: Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Partition is only accepted within a service-defaults config entry. type: string passiveHealthCheck: @@ -473,22 +370,19 @@ spec: The real time is equal to the base time multiplied by the number of times the host has been ejected and is capped by max_ejection_time (Default 300s). Defaults - to 30s. + to 30000ms or 30s. type: string enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can be used to disable ejection or to ramp it up slowly. - Ex. Setting this to 10 will make it a 10% chance that - the host will be ejected. format: int32 type: integer interval: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts - to the pool. Ex. setting this to "10s" will set the - interval to 10 seconds. + to the pool. type: string maxEjectionPercent: description: The maximum % of an upstream cluster that @@ -504,10 +398,6 @@ spec: format: int32 type: integer type: object - peer: - description: Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides - config entry. - type: string protocol: description: Protocol describes the upstream's service protocol. Valid values are "tcp", "http" and "grpc". Anything else @@ -562,4 +452,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 75299f016e..cdbb5413b0 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: serviceintentions.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -72,43 +74,6 @@ spec: have intentions defined. type: string type: object - jwt: - description: JWT specifies the configuration to validate a JSON Web - Token for all incoming requests. - properties: - providers: - description: Providers is a list of providers to consider when - verifying a JWT. - items: - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry with - this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - properties: - path: - description: Path is the path to the claim in the - token JSON. - items: - type: string - type: array - value: - description: Value is the expected value at the given - path. If the type at the path is a list then we - verify that this value is contained in the list. - If the type at the path is a string then we verify - that this value matches. - type: string - type: object - type: array - type: object - type: array - type: object sources: description: Sources is the list of all intention sources and the authorization granted to those sources. The order of this list does @@ -137,7 +102,8 @@ spec: description: Partition is the Admin Partition for the Name parameter. type: string peer: - description: Peer is the peer name for the Name parameter. + description: '[Experimental] Peer is the peer name for the Name + parameter.' type: string permissions: description: Permissions is the list of all additional L7 attributes @@ -218,50 +184,8 @@ spec: match on the HTTP request path. type: string type: object - jwt: - description: JWT specifies configuration to validate a - JSON Web Token for incoming requests. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - properties: - name: - description: Name is the name of the JWT provider. - There MUST be a corresponding "jwt-provider" - config entry with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional - claims to verify in a JWT's payload. - items: - properties: - path: - description: Path is the path to the claim - in the token JSON. - items: - type: string - type: array - value: - description: Value is the expected value - at the given path. If the type at the - path is a list then we verify that this - value is contained in the list. If the - type at the path is a string then we - verify that this value matches. - type: string - type: object - type: array - type: object - type: array - type: object type: object type: array - samenessGroup: - description: SamenessGroup is the name of the sameness group, - if applicable. - type: string type: object type: array type: object @@ -308,4 +232,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 6d89125216..e058052e97 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: serviceresolvers.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -77,26 +79,6 @@ spec: service from to form the failover group of instances. If empty the current namespace is used. type: string - policy: - description: Policy specifies the exact mechanism used for failover. - properties: - mode: - description: Mode specifies the type of failover that will - be performed. Valid values are "sequential", "" (equivalent - to "sequential") and "order-by-locality". - type: string - regions: - description: Regions is the ordered list of the regions - of the failover targets. Valid values can be "us-west-1", - "us-west-2", and so on. - items: - type: string - type: array - type: object - samenessGroup: - description: SamenessGroup is the name of the sameness group - to try during failover. - type: string service: description: Service is the service to resolve instead of the default as the failover group of instances during failover. @@ -225,16 +207,6 @@ spec: type: integer type: object type: object - prioritizeByLocality: - description: PrioritizeByLocality controls whether the locality of - services within the local partition will be used to prioritize connectivity. - properties: - mode: - description: 'Mode specifies the type of prioritization that will - be performed when selecting nodes in the local partition. Valid - values are: "" (default "none"), "none", and "failover".' - type: string - type: object redirect: description: Redirect when configured, all attempts to resolve the service this resolver defines will be substituted for the supplied @@ -260,10 +232,6 @@ spec: description: Peer is the name of the cluster peer to resolve the service from instead of the current one. type: string - samenessGroup: - description: SamenessGroup is the name of the sameness group to - resolve the service from instead of the current one. - type: string service: description: Service is a service to resolve instead of the current service. @@ -274,10 +242,6 @@ spec: If empty the default subset is used. type: string type: object - requestTimeout: - description: RequestTimeout is the timeout for receiving an HTTP response - from this service before the connection is terminated. - type: string subsets: additionalProperties: properties: @@ -345,4 +309,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index c7924081fd..f28da9e7c1 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicerouters.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -148,13 +150,6 @@ spec: any existing header values of the same name. type: object type: object - retryOn: - description: 'RetryOn is a flat list of conditions for Consul - to retry requests based on the response from an upstream - service. Refer to the valid conditions here: https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon' - items: - type: string - type: array retryOnConnectFailure: description: RetryOnConnectFailure allows for connection failure errors to trigger a retry. @@ -185,10 +180,6 @@ spec: http: description: HTTP is a set of http-specific match criteria. properties: - caseInsensitive: - description: CaseInsensitive configures PathExact and - PathPrefix matches to ignore upper/lower casing. - type: boolean header: description: Header is a set of criteria that can match on HTTP request headers. If more than one is configured @@ -320,4 +311,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index 8d5ed58023..a2af050c3d 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: servicesplitters.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -183,4 +185,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-tcproutes-external.yaml b/charts/consul/templates/crd-tcproutes-external.yaml deleted file mode 100644 index b5bc7be13c..0000000000 --- a/charts/consul/templates/crd-tcproutes-external.yaml +++ /dev/null @@ -1,281 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: tcproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: TCPRoute - listKind: TCPRouteList - plural: tcproutes - singular: tcproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of TCPRoute. - properties: - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - description: TCPRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of TCPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-tcproutes.yaml b/charts/consul/templates/crd-tcproutes.yaml deleted file mode 100644 index ae9d2cd080..0000000000 --- a/charts/consul/templates/crd-tcproutes.yaml +++ /dev/null @@ -1,278 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: tcproutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: TCPRoute - listKind: TCPRouteList - plural: tcproutes - shortNames: - - tcp-route - singular: tcproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: TCPRoute is the Schema for the TCP Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute - \n This is a Resource type." - properties: - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - properties: - backendRefs: - description: BackendRefs defines the backend(s) where matching - requests should be sent. If unspecified or invalid (refers - to a non-existent resource or a Service with no endpoints), - the underlying implementation MUST actively reject connection - attempts to this backend. Connection rejections must respect - weight; if an invalid backend is requested to have 80% of - connections, then 80% of connections must be rejected instead. - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 565aa63381..583c218be8 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -1,16 +1,18 @@ {{- if .Values.connectInject.enabled }} +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: terminatinggateways.consul.hashicorp.com labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com names: @@ -134,4 +136,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-tlsroutes-external.yaml b/charts/consul/templates/crd-tlsroutes-external.yaml deleted file mode 100644 index 1acd1b973a..0000000000 --- a/charts/consul/templates/crd-tlsroutes-external.yaml +++ /dev/null @@ -1,291 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: tlsroutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: TLSRoute - listKind: TLSRouteList - plural: tlsroutes - singular: tlsroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "The TLSRoute resource is similar to TCPRoute, but can be configured to match against TLS-specific metadata. This allows more flexibility in matching streams for a given TLS listener. \n If you need to forward traffic to a single target for a TLS listener, you could choose to use a TCPRoute with a TLS listener." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of TLSRoute. - properties: - hostnames: - description: "Hostnames defines a set of SNI names that should match against the SNI attribute of TLS ClientHello message in TLS handshake. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and TLSRoute, there must be at least one intersecting hostname for the TLSRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n If both the Listener and TLSRoute have specified hostnames, any TLSRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the TLSRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and TLSRoute have specified hostnames, and none match with the criteria above, then the TLSRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of TLS matchers and actions. - items: - description: TLSRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this backend, by rejecting the connection or returning a 500 status code. Request rejections must respect weight; if an invalid backend is requested to have 80% of requests, then 80% of requests must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of TLSRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-trafficpermissions.yaml b/charts/consul/templates/crd-trafficpermissions.yaml deleted file mode 100644 index 27ab6f5e3d..0000000000 --- a/charts/consul/templates/crd-trafficpermissions.yaml +++ /dev/null @@ -1,261 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: trafficpermissions.auth.consul.hashicorp.com -spec: - group: auth.consul.hashicorp.com - names: - kind: TrafficPermissions - listKind: TrafficPermissionsList - plural: trafficpermissions - shortNames: - - traffic-permissions - singular: trafficpermissions - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: TrafficPermissions is the Schema for the traffic-permissions - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - action: - description: "Action can be either allow or deny for the entire object. - It will default to allow. \n If action is allow, we will allow the - connection if one of the rules in Rules matches, in other words, - we will deny all requests except for the ones that match Rules. - If Consul is in default allow mode, then allow actions have no effect - without a deny permission as everything is allowed by default. \n - If action is deny, we will deny the connection if one of the rules - in Rules match, in other words, we will allow all requests except - for the ones that match Rules. If Consul is default deny mode, then - deny permissions have no effect without an allow permission as everything - is denied by default. \n Action unspecified is reserved for compatibility - with the addition of future actions." - enum: - - ACTION_ALLOW - - ACTION_DENY - - ACTION_UNKNOWN - format: int32 - type: string - destination: - description: Destination is a configuration of the destination proxies - where these traffic permissions should apply. - properties: - identityName: - type: string - type: object - permissions: - description: Permissions is a list of permissions to match on. They - are applied using OR semantics. - items: - description: Permissions is a list of permissions to match on. - properties: - destinationRules: - description: DestinationRules is a list of rules to apply for - matching sources in this Permission. These rules are specific - to the request or connection that is going to the destination(s) - selected by the TrafficPermissions resource. - items: - description: DestinationRule contains rules rules to apply - to the incoming connection. - properties: - exclude: - description: Exclude contains a list of rules to exclude - when evaluating rules for the incoming connection. - items: - properties: - header: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - methods: - description: Methods is the list of HTTP methods. - items: - type: string - type: array - pathExact: - type: string - pathPrefix: - type: string - pathRegex: - type: string - portNames: - description: PortNames is a list of workload ports - to apply this rule to. The ports specified here - must be the ports used in the connection. - items: - type: string - type: array - type: object - type: array - header: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - methods: - description: Methods is the list of HTTP methods. If no - methods are specified, this rule will apply to all methods. - items: - type: string - type: array - pathExact: - type: string - pathPrefix: - type: string - pathRegex: - type: string - portNames: - items: - type: string - type: array - type: object - type: array - sources: - description: Sources is a list of sources in this traffic permission. - items: - description: Source represents the source identity. To specify - any of the wildcard sources, the specific fields need to - be omitted. For example, for a wildcard namespace, identity_name - should be omitted. - properties: - exclude: - description: Exclude is a list of sources to exclude from - this source. - items: - description: ExcludeSource is almost the same as source - but it prevents the addition of matching sources. - properties: - identityName: - type: string - namespace: - type: string - partition: - type: string - peer: - type: string - samenessGroup: - type: string - type: object - type: array - identityName: - type: string - namespace: - type: string - partition: - type: string - peer: - type: string - samenessGroup: - type: string - type: object - type: array - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-udproutes-external.yaml b/charts/consul/templates/crd-udproutes-external.yaml deleted file mode 100644 index 0661b24c1a..0000000000 --- a/charts/consul/templates/crd-udproutes-external.yaml +++ /dev/null @@ -1,281 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: udproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: UDPRoute - listKind: UDPRouteList - plural: udproutes - singular: udproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: UDPRoute provides a way to route UDP traffic. When combined with a Gateway listener, it can be used to forward traffic on the port specified by the listener to a set of backends specified by the UDPRoute. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of UDPRoute. - properties: - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of UDP matchers and actions. - items: - description: UDPRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Packet drops must respect weight; if an invalid backend is requested to have 80% of the packets, then 80% of packets must be dropped instead. \n Support: Core for Kubernetes Service Support: Implementation-specific for any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of UDPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/create-federation-secret-job.yaml b/charts/consul/templates/create-federation-secret-job.yaml index f0d5a1c821..678a2af3ba 100644 --- a/charts/consul/templates/create-federation-secret-job.yaml +++ b/charts/consul/templates/create-federation-secret-job.yaml @@ -37,7 +37,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-create-federation-secret diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index 80ae582152..0122690104 100644 --- a/charts/consul/templates/enterprise-license-job.yaml +++ b/charts/consul/templates/enterprise-license-job.yaml @@ -39,7 +39,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-enterprise-license diff --git a/charts/consul/templates/gateway-cleanup-clusterrole.yaml b/charts/consul/templates/gateway-cleanup-clusterrole.yaml deleted file mode 100644 index 5518bfc390..0000000000 --- a/charts/consul/templates/gateway-cleanup-clusterrole.yaml +++ /dev/null @@ -1,44 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ template "consul.fullname" . }}-gateway-cleanup - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-cleanup -rules: - - apiGroups: - - consul.hashicorp.com - resources: - - gatewayclassconfigs - verbs: - - get - - delete - - apiGroups: - - gateway.networking.k8s.io - resources: - - gatewayclasses - verbs: - - get - - delete - - apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclassconfigs - - gatewayclasses - - meshgateways - verbs: - - get - - delete -{{- if .Values.global.enablePodSecurityPolicies }} - - apiGroups: ["policy"] - resources: ["podsecuritypolicies"] - resourceNames: - - {{ template "consul.fullname" . }}-gateway-cleanup - verbs: - - use -{{- end }} -{{- end }} diff --git a/charts/consul/templates/gateway-cleanup-clusterrolebinding.yaml b/charts/consul/templates/gateway-cleanup-clusterrolebinding.yaml deleted file mode 100644 index 9235f32101..0000000000 --- a/charts/consul/templates/gateway-cleanup-clusterrolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ template "consul.fullname" . }}-gateway-cleanup - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-cleanup -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ template "consul.fullname" . }}-gateway-cleanup -subjects: - - kind: ServiceAccount - name: {{ template "consul.fullname" . }}-gateway-cleanup - namespace: {{ .Release.Namespace }} -{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml deleted file mode 100644 index 0d4f84272c..0000000000 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ /dev/null @@ -1,67 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ template "consul.fullname" . }}-gateway-cleanup - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-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" . }}-gateway-cleanup - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: gateway-cleanup - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 8 }} - {{- end }} - annotations: - "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" - spec: - restartPolicy: Never - serviceAccountName: {{ template "consul.fullname" . }}-gateway-cleanup - containers: - - name: gateway-cleanup - image: {{ .Values.global.imageK8S }} - {{- include "consul.restrictedSecurityContext" . | nindent 10 }} - command: - - consul-k8s-control-plane - args: - - gateway-cleanup - - -gateway-class-name=consul - - -gateway-class-config-name=consul-api-gateway - resources: - requests: - memory: "50Mi" - cpu: "50m" - limits: - memory: "50Mi" - cpu: "50m" - volumeMounts: - - name: config - mountPath: /consul/config - readOnly: true - {{- if .Values.global.acls.tolerations }} - tolerations: - {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} - {{- end }} - volumes: - - name: config - configMap: - name: {{ template "consul.fullname" . }}-gateway-resources-config -{{- end }} diff --git a/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml deleted file mode 100644 index ffbad130cc..0000000000 --- a/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{{- if (and .Values.connectInject.enabled .Values.global.enablePodSecurityPolicies)}} -apiVersion: policy/v1beta1 -kind: PodSecurityPolicy -metadata: - name: {{ template "consul.fullname" . }}-gateway-cleanup - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-cleanup -spec: - privileged: false - allowPrivilegeEscalation: false - # This is redundant with non-root + disallow privilege escalation, - # but we can provide it for defense in depth. - requiredDropCapabilities: - - ALL - hostNetwork: false - hostIPC: false - hostPID: false - runAsUser: - rule: 'RunAsAny' - seLinux: - rule: 'RunAsAny' - supplementalGroups: - rule: 'RunAsAny' - fsGroup: - rule: 'RunAsAny' - readOnlyRootFilesystem: false -{{- end }} diff --git a/charts/consul/templates/gateway-cleanup-serviceaccount.yaml b/charts/consul/templates/gateway-cleanup-serviceaccount.yaml deleted file mode 100644 index f50eb72d97..0000000000 --- a/charts/consul/templates/gateway-cleanup-serviceaccount.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ template "consul.fullname" . }}-gateway-cleanup - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-cleanup -{{- end }} diff --git a/charts/consul/templates/gateway-resources-clusterrole.yaml b/charts/consul/templates/gateway-resources-clusterrole.yaml deleted file mode 100644 index ad7082f060..0000000000 --- a/charts/consul/templates/gateway-resources-clusterrole.yaml +++ /dev/null @@ -1,47 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ template "consul.fullname" . }}-gateway-resources - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-resources -rules: - - apiGroups: - - mesh.consul.hashicorp.com - resources: - - meshgateways - verbs: - - get - - update - - create - - apiGroups: - - consul.hashicorp.com - - mesh.consul.hashicorp.com - resources: - - gatewayclassconfigs - verbs: - - get - - update - - create - - apiGroups: - - gateway.networking.k8s.io - - mesh.consul.hashicorp.com - resources: - - gatewayclasses - verbs: - - get - - update - - create -{{- if .Values.global.enablePodSecurityPolicies }} - - apiGroups: ["policy"] - resources: ["podsecuritypolicies"] - resourceNames: - - {{ template "consul.fullname" . }}-gateway-resources - verbs: - - use -{{- end }} -{{- end }} diff --git a/charts/consul/templates/gateway-resources-clusterrolebinding.yaml b/charts/consul/templates/gateway-resources-clusterrolebinding.yaml deleted file mode 100644 index 921df23239..0000000000 --- a/charts/consul/templates/gateway-resources-clusterrolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ template "consul.fullname" . }}-gateway-resources - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-resources -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ template "consul.fullname" . }}-gateway-resources -subjects: - - kind: ServiceAccount - name: {{ template "consul.fullname" . }}-gateway-resources - namespace: {{ .Release.Namespace }} -{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/gateway-resources-configmap.yaml b/charts/consul/templates/gateway-resources-configmap.yaml deleted file mode 100644 index 842ba6690d..0000000000 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ /dev/null @@ -1,132 +0,0 @@ -{{- if .Values.connectInject.enabled }} - -# Validation -# For meshGateway.wanAddress, static must be set if source is "Static" -{{if (and (eq .Values.meshGateway.wanAddress.source "Static") (eq .Values.meshGateway.wanAddress.static ""))}}{{fail ".meshGateway.wanAddress.static must be set to a value if .meshGateway.wanAddress.source is Static"}}{{ end }} - -# Configuration of Gateway Resources Job which creates managed Gateway configuration. -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "consul.fullname" . }}-gateway-resources-config - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-resources -data: - {{- if .Values.connectInject.apiGateway.managedGatewayClass.resources }} - resources.json: | - {{ toJson .Values.connectInject.apiGateway.managedGatewayClass.resources }} - {{- end }} - {{- if and (mustHas "resource-apis" .Values.global.experiments) .Values.meshGateway.enabled }} - config.yaml: | - gatewayClassConfigs: - - apiVersion: mesh.consul.hashicorp.com/v2beta1 - metadata: - name: consul-mesh-gateway - kind: GatewayClassConfig - spec: - labels: - set: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: mesh-gateway - deployment: - {{- if .Values.meshGateway.priorityClassName }} - priorityClassName: {{ .Values.meshGateway.priorityClassName | quote }} - {{- end }} - {{- if .Values.meshGateway.affinity }} - affinity: {{ toJson (default "{}" .Values.meshGateway.affinity) }} - {{- end }} - {{- if .Values.meshGateway.annotations }} - annotations: - set: {{ toJson .Values.meshGateway.annotations }} - {{- end }} - {{- if .Values.global.extraLabels }} - labels: - set: {{ toJson .Values.global.extraLabels }} - {{- end }} - container: - consul: - logging: - level: {{ default .Values.global.logLevel .Values.meshGateway.logLevel }} - portModifier: {{ sub .Values.meshGateway.containerPort .Values.meshGateway.service.port }} - {{- if .Values.meshGateway.hostPort }} - hostPort: {{ .Values.meshGateway.hostPort }} - {{- end }} - resources: {{ toJson .Values.meshGateway.resources }} - initContainer: - consul: - logging: - level: {{ default .Values.global.logLevel .Values.meshGateway.logLevel }} - resources: {{ toJson .Values.meshGateway.initServiceInitContainer.resources }} - {{- with .Values.meshGateway.nodeSelector }} - nodeSelector: {{ fromYaml . | toJson }} - {{- end }} - {{- with .Values.meshGateway.hostNetwork }} - hostNetwork: {{ . }} - {{- end }} - {{- with .Values.meshGateway.dnsPolicy }} - dnsPolicy: {{ . }} - {{- end }} - {{- with .Values.meshGateway.topologySpreadConstraints }} - topologySpreadConstraints: - {{ fromYamlArray . | toJson }} - {{- end }} - {{- if .Values.meshGateway.affinity }} - affinity: - {{ tpl .Values.meshGateway.affinity . | nindent 16 | trim }} - {{- end }} - replicas: - default: {{ .Values.meshGateway.replicas }} - min: {{ .Values.meshGateway.replicas }} - max: {{ .Values.meshGateway.replicas }} - {{- if .Values.meshGateway.tolerations }} - tolerations: {{ fromYamlArray .Values.meshGateway.tolerations | toJson }} - {{- end }} - service: - {{- if .Values.meshGateway.service.annotations }} - annotations: - set: {{ toJson .Values.meshGateway.service.annotations }} - {{- end }} - type: {{ .Values.meshGateway.service.type }} - {{- if .Values.meshGateway.serviceAccount.annotations }} - serviceAccount: - annotations: - set: {{ toJson .Values.meshGateway.serviceAccount.annotations }} - {{- end }} - meshGateways: - - apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: MeshGateway - metadata: - name: mesh-gateway - namespace: {{ .Release.Namespace }} - annotations: - # TODO are these annotations even necessary? - "consul.hashicorp.com/gateway-wan-address-source": {{ .Values.meshGateway.wanAddress.source | quote }} - "consul.hashicorp.com/gateway-wan-address-static": {{ .Values.meshGateway.wanAddress.static | quote }} - {{- if eq .Values.meshGateway.wanAddress.source "Service" }} - {{- if eq .Values.meshGateway.service.type "NodePort" }} - "consul.hashicorp.com/gateway-wan-port": {{ .Values.meshGateway.service.nodePort | quote }} - {{- else }} - "consul.hashicorp.com/gateway-wan-port": {{ .Values.meshGateway.service.port | quote }} - {{- end }} - {{- else }} - "consul.hashicorp.com/gateway-wan-port": {{ .Values.meshGateway.wanAddress.port | quote }} - {{- end }} - spec: - gatewayClassName: consul-mesh-gateway - listeners: - - name: "wan" - port: {{ .Values.meshGateway.service.port }} - protocol: "TCP" - workloads: - prefixes: - - "mesh-gateway" - {{- end }} -{{- end }} diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml deleted file mode 100644 index 5934372ed3..0000000000 --- a/charts/consul/templates/gateway-resources-job.yaml +++ /dev/null @@ -1,124 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ template "consul.fullname" . }}-gateway-resources - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-resources - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 4 }} - {{- end }} - annotations: - "helm.sh/hook": post-install,post-upgrade - "helm.sh/hook-weight": "0" - "helm.sh/hook-delete-policy": hook-succeeded -spec: - template: - metadata: - name: {{ template "consul.fullname" . }}-gateway-resources - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: gateway-resources - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 8 }} - {{- end }} - annotations: - "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" - spec: - restartPolicy: Never - serviceAccountName: {{ template "consul.fullname" . }}-gateway-resources - containers: - - name: gateway-resources - image: {{ .Values.global.imageK8S }} - {{- include "consul.restrictedSecurityContext" . | nindent 10 }} - command: - - consul-k8s-control-plane - args: - - gateway-resources - - -gateway-class-name=consul - - -gateway-class-config-name=consul-api-gateway - - -controller-name=consul.hashicorp.com/gateway-controller - - -app={{template "consul.name" .}} - - -chart={{template "consul.chart" .}} - - -heritage={{ .Release.Service }} - - -release-name={{ .Release.Name }} - - -component=api-gateway - {{- if .Values.apiGateway.enabled }} # Override values from the old stanza. To be removed after ~1.18 (t-eckert 2023-05-19) NET-6263 - {{- if .Values.apiGateway.managedGatewayClass.deployment }} - {{- if .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} - - -deployment-default-instances={{ .Values.apiGateway.managedGatewayClass.deployment.defaultInstances }} - {{- end}} - {{- if .Values.apiGateway.managedGatewayClass.deployment.maxInstances }} - - -deployment-max-instances={{ .Values.apiGateway.managedGatewayClass.deployment.maxInstances }} - {{- end}} - {{- if .Values.apiGateway.managedGatewayClass.deployment.minInstances }} - - -deployment-min-instances={{ .Values.apiGateway.managedGatewayClass.deployment.minInstances }} - {{- end}} - {{- end}} - {{- if .Values.apiGateway.managedGatewayClass.nodeSelector }} - - -node-selector={{ .Values.apiGateway.managedGatewayClass.nodeSelector }} - {{- end }} - {{- if .Values.apiGateway.managedGatewayClass.tolerations }} - - -tolerations={{ .Values.apiGateway.managedGatewayClass.tolerations }} - {{- end }} - {{- if .Values.apiGateway.managedGatewayClass.copyAnnotations.service }} - - -service-annotations={{ .Values.apiGateway.managedGatewayClass.copyAnnotations.service.annotations }} - {{- end }} - - -service-type={{ .Values.apiGateway.managedGatewayClass.serviceType }} - {{- else }} # the new stanza - {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} - - -deployment-default-instances={{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} - {{- end}} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} - - -deployment-max-instances={{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} - {{- end}} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} - - -deployment-min-instances={{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} - {{- end}} - {{- end}} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector }} - - -node-selector - - {{- toYaml .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector | nindent 14 -}} - {{- end }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} - - -tolerations={{ .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} - {{- end }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service }} - - -service-annotations - - {{- toYaml .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations | nindent 14 -}} - {{- end }} - - -service-type={{ .Values.connectInject.apiGateway.managedGatewayClass.serviceType }} - {{- if .Values.global.openshift.enabled }} - - -openshift-scc-name={{ .Values.connectInject.apiGateway.managedGatewayClass.openshiftSCCName }} - {{- end }} - - -map-privileged-container-ports={{ .Values.connectInject.apiGateway.managedGatewayClass.mapPrivilegedContainerPorts }} - {{- end}} - resources: - requests: - memory: "50Mi" - cpu: "50m" - limits: - memory: "50Mi" - cpu: "50m" - volumeMounts: - - name: config - mountPath: /consul/config - readOnly: true - {{- if .Values.global.acls.tolerations }} - tolerations: - {{ tpl .Values.global.acls.tolerations . | indent 8 | trim }} - {{- end }} - volumes: - - name: config - configMap: - name: {{ template "consul.fullname" . }}-gateway-resources-config -{{- end }} diff --git a/charts/consul/templates/gateway-resources-podsecuritypolicy.yaml b/charts/consul/templates/gateway-resources-podsecuritypolicy.yaml deleted file mode 100644 index da5299194c..0000000000 --- a/charts/consul/templates/gateway-resources-podsecuritypolicy.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{{- if (and .Values.global.enablePodSecurityPolicies .Values.connectInject.enabled)}} -apiVersion: policy/v1beta1 -kind: PodSecurityPolicy -metadata: - name: {{ template "consul.fullname" . }}-gateway-resources - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-resources -spec: - privileged: false - allowPrivilegeEscalation: false - # This is redundant with non-root + disallow privilege escalation, - # but we can provide it for defense in depth. - requiredDropCapabilities: - - ALL - hostNetwork: false - hostIPC: false - hostPID: false - runAsUser: - rule: 'RunAsAny' - seLinux: - rule: 'RunAsAny' - supplementalGroups: - rule: 'RunAsAny' - fsGroup: - rule: 'RunAsAny' - readOnlyRootFilesystem: false -{{- end }} diff --git a/charts/consul/templates/gateway-resources-serviceaccount.yaml b/charts/consul/templates/gateway-resources-serviceaccount.yaml deleted file mode 100644 index 4611dc38e1..0000000000 --- a/charts/consul/templates/gateway-resources-serviceaccount.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ template "consul.fullname" . }}-gateway-resources - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: gateway-resources -{{- end }} diff --git a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml index cc5b5397c2..02fb3ea168 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml @@ -35,7 +35,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gossip-encryption-autogenerate diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index 4eef2e96ac..df9f500e3c 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -74,7 +74,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "ingress-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .name }}" {{- if $root.Values.global.enableConsulNamespaces }} diff --git a/charts/consul/templates/mesh-gateway-clusterrole.yaml b/charts/consul/templates/mesh-gateway-clusterrole.yaml index 3053105105..b951418b26 100644 --- a/charts/consul/templates/mesh-gateway-clusterrole.yaml +++ b/charts/consul/templates/mesh-gateway-clusterrole.yaml @@ -1,5 +1,4 @@ {{- if .Values.meshGateway.enabled }} -{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -33,4 +32,3 @@ rules: rules: [] {{- end }} {{- end }} -{{- end }} diff --git a/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml b/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml index 2fb80fc04c..f8150ebb53 100644 --- a/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml +++ b/charts/consul/templates/mesh-gateway-clusterrolebinding.yaml @@ -1,5 +1,4 @@ {{- if .Values.meshGateway.enabled }} -{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -19,4 +18,3 @@ subjects: name: {{ template "consul.fullname" . }}-mesh-gateway namespace: {{ .Release.Namespace }} {{- end }} -{{- end }} diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index cda371d58a..ac050e7199 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -1,5 +1,4 @@ {{- if .Values.meshGateway.enabled }} -{{- if not (mustHas "resource-apis" .Values.global.experiments) }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.acls.manageSystemACLs (ne .Values.meshGateway.consulServiceName "") (ne .Values.meshGateway.consulServiceName "mesh-gateway") }}{{ fail "if global.acls.manageSystemACLs is true, meshGateway.consulServiceName cannot be set" }}{{ end -}} {{- if .Values.meshGateway.globalMode }}{{ fail "meshGateway.globalMode is no longer supported; instead, you must migrate to CRDs (see www.consul.io/docs/k8s/crds/upgrade-to-crds)" }}{{ end -}} @@ -44,7 +43,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "mesh-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .Values.meshGateway.consulServiceName }}" "consul.hashicorp.com/mesh-gateway-container-port": "{{ .Values.meshGateway.containerPort }}" @@ -310,4 +308,3 @@ spec: {{ tpl .Values.meshGateway.nodeSelector . | indent 8 | trim }} {{- end }} {{- end }} -{{- end }} diff --git a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml index 56e4b7924c..04576fe926 100644 --- a/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml +++ b/charts/consul/templates/mesh-gateway-podsecuritypolicy.yaml @@ -1,5 +1,4 @@ {{- if and .Values.global.enablePodSecurityPolicies .Values.meshGateway.enabled }} -{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: @@ -53,4 +52,3 @@ spec: rule: 'RunAsAny' readOnlyRootFilesystem: false {{- end }} -{{- end }} diff --git a/charts/consul/templates/mesh-gateway-service.yaml b/charts/consul/templates/mesh-gateway-service.yaml index 80f82ac897..5fdceca8df 100644 --- a/charts/consul/templates/mesh-gateway-service.yaml +++ b/charts/consul/templates/mesh-gateway-service.yaml @@ -1,5 +1,4 @@ {{- if and .Values.meshGateway.enabled }} -{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: v1 kind: Service metadata: @@ -32,4 +31,3 @@ spec: {{ tpl .Values.meshGateway.service.additionalSpec . | nindent 2 | trim }} {{- end }} {{- end }} -{{- end }} diff --git a/charts/consul/templates/mesh-gateway-serviceaccount.yaml b/charts/consul/templates/mesh-gateway-serviceaccount.yaml index b1a0661eaa..8c2da5ae06 100644 --- a/charts/consul/templates/mesh-gateway-serviceaccount.yaml +++ b/charts/consul/templates/mesh-gateway-serviceaccount.yaml @@ -1,5 +1,4 @@ {{- if .Values.meshGateway.enabled }} -{{- if not (mustHas "resource-apis" .Values.global.experiments) }} apiVersion: v1 kind: ServiceAccount metadata: @@ -22,4 +21,3 @@ imagePullSecrets: {{- end }} {{- end }} {{- end }} -{{- end }} diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index f8a44859f1..6e21289f22 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -36,7 +36,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if (and .Values.global.secretsBackend.vault.enabled (or .Values.global.tls.enabled .Values.global.acls.manageSystemACLs)) }} "vault.hashicorp.com/agent-pre-populate-only": "true" "vault.hashicorp.com/agent-inject": "true" @@ -118,9 +117,6 @@ spec: {{- if .Values.global.cloud.enabled }} -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} \ {{- end }} - {{- if and (mustHas "resource-apis" .Values.global.experiments) (mustHas "v2tenancy" .Values.global.experiments) }} - -enable-v2tenancy=true - {{- end }} resources: requests: memory: "50Mi" diff --git a/charts/consul/templates/prometheus.yaml b/charts/consul/templates/prometheus.yaml index a708708daf..4dcede1745 100644 --- a/charts/consul/templates/prometheus.yaml +++ b/charts/consul/templates/prometheus.yaml @@ -410,8 +410,8 @@ spec: template: metadata: annotations: + consul.hashicorp.com/connect-inject: "false" - consul.hashicorp.com/mesh-inject: "false" labels: component: "server" app: prometheus diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index b47e04188f..39754d6c6f 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -47,7 +47,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.acls.annotations }} {{- tpl .Values.global.acls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index aca1444a9b..87f44d54b9 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -46,7 +46,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.acls.annotations }} {{- tpl .Values.global.acls.annotations . | nindent 8 }} {{- end }} @@ -55,23 +54,14 @@ spec: "argocd.argoproj.io/hook-delete-policy": "HookSucceeded" {{- end }} {{- if .Values.global.secretsBackend.vault.enabled }} - - {{- /* Run the Vault agent as both an init container and sidecar. - The Vault agent sidecar is needed when server-acl-init bootstraps ACLs - and writes the bootstrap token back to Vault. - * agent-pre-populate: true - Run the Vault agent init container. - * agent-pre-populate-only: false - Also, run the Vault agent sidecar. - * agent-cache-enable: true - Enable the Agent cache listener. - * agent-cache-listener-port: 8200 - (optional) Listen on 127.0.0.1:8200. - * agent-enable-quit: true - Enable a "quit" endpoint. server-acl-init - tells the Vault agent to stop (without this the Job will not complete). - */}} - "vault.hashicorp.com/agent-pre-populate": "true" - "vault.hashicorp.com/agent-pre-populate-only": "false" - "vault.hashicorp.com/agent-cache-enable": "true" - "vault.hashicorp.com/agent-cache-listener-port": "8200" - "vault.hashicorp.com/agent-enable-quit": "true" + "vault.hashicorp.com/agent-pre-populate-only": "true" "vault.hashicorp.com/agent-inject": "true" + {{- if .Values.global.acls.bootstrapToken.secretName }} + {{- with .Values.global.acls.bootstrapToken }} + "vault.hashicorp.com/agent-inject-secret-bootstrap-token": "{{ .secretName }}" + "vault.hashicorp.com/agent-inject-template-bootstrap-token": {{ template "consul.vaultSecretTemplate" . }} + {{- end }} + {{- end }} {{- if .Values.global.acls.partitionToken.secretName }} {{- with .Values.global.acls.partitionToken }} "vault.hashicorp.com/agent-inject-secret-partition-token": "{{ .secretName }}" @@ -125,7 +115,14 @@ spec: path: tls.crt {{- end }} {{- end }} - {{- if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} + {{- if (and .Values.global.acls.bootstrapToken.secretName (not .Values.global.secretsBackend.vault.enabled)) }} + - name: bootstrap-token + secret: + secretName: {{ .Values.global.acls.bootstrapToken.secretName }} + items: + - key: {{ .Values.global.acls.bootstrapToken.secretKey }} + path: bootstrap-token + {{- else if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} - name: acl-replication-token secret: secretName: {{ .Values.global.acls.replicationToken.secretName }} @@ -149,16 +146,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name - # Extract the Vault namespace from the Vault agent annotations. - {{- if .Values.global.secretsBackend.vault.enabled }} - {{- if and (.Values.global.secretsBackend.vault.agentAnnotations) (hasKey (default "" .Values.global.secretsBackend.vault.agentAnnotations | fromYaml) "vault.hashicorp.com/namespace") }} - - name: VAULT_NAMESPACE - value: {{ get (tpl .Values.global.secretsBackend.vault.agentAnnotations . | fromYaml) "vault.hashicorp.com/namespace" }} - {{- else if .Values.global.secretsBackend.vault.vaultNamespace }} - - name: VAULT_NAMESPACE - value: {{ .Values.global.secretsBackend.vault.vaultNamespace }} - {{- end }} - {{- end }} {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} {{- if (or .Values.global.tls.enabled .Values.global.acls.replicationToken.secretName .Values.global.acls.bootstrapToken.secretName) }} volumeMounts: @@ -169,7 +156,11 @@ spec: readOnly: true {{- end }} {{- end }} - {{- if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} + {{- if (and .Values.global.acls.bootstrapToken.secretName (not .Values.global.secretsBackend.vault.enabled)) }} + - name: bootstrap-token + mountPath: /consul/acl/tokens + readOnly: true + {{- else if and .Values.global.acls.replicationToken.secretName (not .Values.global.secretsBackend.vault.enabled) }} - name: acl-replication-token mountPath: /consul/acl/tokens readOnly: true @@ -187,19 +178,13 @@ spec: -resource-prefix=${CONSUL_FULLNAME} \ -k8s-namespace={{ .Release.Namespace }} \ -set-server-tokens={{ $serverEnabled }} \ + + {{- if .Values.global.acls.bootstrapToken.secretName }} {{- if .Values.global.secretsBackend.vault.enabled }} - -secrets-backend=vault \ + -bootstrap-token-file=/vault/secrets/bootstrap-token \ {{- else }} - -secrets-backend=kubernetes \ + -bootstrap-token-file=/consul/acl/tokens/bootstrap-token \ {{- end }} - - {{- if (mustHas "resource-apis" .Values.global.experiments) }} - -enable-resource-apis=true \ - {{- end }} - - {{- if .Values.global.acls.bootstrapToken.secretName }} - -bootstrap-token-secret-name={{ .Values.global.acls.bootstrapToken.secretName }} \ - -bootstrap-token-secret-key={{ .Values.global.acls.bootstrapToken.secretKey }} \ {{- end }} {{- if .Values.syncCatalog.enabled }} diff --git a/charts/consul/templates/server-clusterrole.yaml b/charts/consul/templates/server-clusterrole.yaml deleted file mode 100644 index c22f562264..0000000000 --- a/charts/consul/templates/server-clusterrole.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ template "consul.fullname" . }}-server - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: server -rules: -- apiGroups: [""] - resources: ["nodes"] - verbs: - - get diff --git a/charts/consul/templates/server-clusterrolebinding.yaml b/charts/consul/templates/server-clusterrolebinding.yaml deleted file mode 100644 index 854fda870e..0000000000 --- a/charts/consul/templates/server-clusterrolebinding.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ template "consul.fullname" . }}-server - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: server -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ template "consul.fullname" . }}-server -subjects: -- kind: ServiceAccount - name: {{ template "consul.fullname" . }}-server - namespace: {{ .Release.Namespace }} diff --git a/charts/consul/templates/server-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 4ce79794b2..f044429dcb 100644 --- a/charts/consul/templates/server-config-configmap.yaml +++ b/charts/consul/templates/server-config-configmap.yaml @@ -1,5 +1,4 @@ {{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} -{{- if (not (or (eq .Values.server.limits.requestLimits.mode "disabled") (eq .Values.server.limits.requestLimits.mode "permissive") (eq .Values.server.limits.requestLimits.mode "enforce"))) }}{{fail "server.limits.requestLimits.mode must be one of the following values: disabled, permissive, and enforce." }}{{ end -}} {{- if and .Values.server.auditLogs.enabled (not .Values.global.acls.manageSystemACLs) }}{{fail "ACLs must be enabled inorder to configure audit logs"}}{{ end -}} # StatefulSet to run the actual Consul server cluster. apiVersion: v1 @@ -31,13 +30,6 @@ data: "log_level": "{{ .Values.server.logLevel | upper }}", {{- end }} "domain": "{{ .Values.global.domain }}", - "limits": { - "request_limits": { - "mode": "{{ .Values.server.limits.requestLimits.mode }}", - "read_rate": {{ .Values.server.limits.requestLimits.readRate }}, - "write_rate": {{ .Values.server.limits.requestLimits.writeRate }} - } - }, "ports": { {{- if not .Values.global.tls.enabled }} "grpc": 8502, @@ -56,12 +48,7 @@ data: "enabled": true }, {{- end }} - "server": true, - "leave_on_terminate": true, - "autopilot": { - "min_quorum": {{ template "consul.server.autopilotMinQuorum" . }}, - "disable_upgrade_migration": true - } + "server": true } {{- $vaultConnectCAEnabled := and .Values.global.secretsBackend.vault.connectCA.address .Values.global.secretsBackend.vault.connectCA.rootPKIPath .Values.global.secretsBackend.vault.connectCA.intermediatePKIPath -}} {{- if and .Values.global.secretsBackend.vault.enabled $vaultConnectCAEnabled }} @@ -100,6 +87,8 @@ data: {{- end }} {{- end }} {{- end }} + extra-from-values.json: |- +{{ tpl .Values.server.extraConfig . | trimAll "\"" | indent 4 }} {{- if .Values.global.acls.manageSystemACLs }} acl-config.json: |- { diff --git a/charts/consul/templates/server-disruptionbudget.yaml b/charts/consul/templates/server-disruptionbudget.yaml index 56805edc2a..edf9c1c57f 100644 --- a/charts/consul/templates/server-disruptionbudget.yaml +++ b/charts/consul/templates/server-disruptionbudget.yaml @@ -17,7 +17,7 @@ metadata: release: {{ .Release.Name }} component: server spec: - maxUnavailable: {{ template "consul.server.pdb.maxUnavailable" . }} + maxUnavailable: {{ template "consul.pdb.maxUnavailable" . }} selector: matchLabels: app: {{ template "consul.name" . }} diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index b056672c58..035b498abb 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -44,9 +44,6 @@ spec: rollingUpdate: partition: {{ .Values.server.updatePartition }} {{- end }} - {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.server.persistentVolumeClaimRetentionPolicy) }} - persistentVolumeClaimRetentionPolicy: {{ toYaml .Values.server.persistentVolumeClaimRetentionPolicy | nindent 4 }} - {{- end }} selector: matchLabels: app: {{ template "consul.name" . }} @@ -118,8 +115,7 @@ spec: {{- end }} {{- end }} "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" - "consul.hashicorp.com/config-checksum": {{ print (include (print $.Template.BasePath "/server-config-configmap.yaml") .) (include (print $.Template.BasePath "/server-tmp-extra-config-configmap.yaml") .) | sha256sum }} + "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/server-config-configmap.yaml") . | sha256sum }} {{- if .Values.server.annotations }} {{- tpl .Values.server.annotations . | nindent 8 }} {{- end }} @@ -159,9 +155,6 @@ spec: name: {{ template "consul.fullname" . }}-server-config - name: extra-config emptyDir: {} - - name: tmp-extra-config - configMap: - name: {{ template "consul.fullname" . }}-server-tmp-extra-config {{- if (and .Values.global.tls.enabled (not .Values.global.secretsBackend.vault.enabled)) }} - name: consul-ca-cert secret: @@ -214,11 +207,6 @@ spec: medium: "Memory" {{- end }} {{- end }} - {{- if .Values.global.trustedCAs }} - - name: trusted-cas - emptyDir: - medium: "Memory" - {{- end }} {{- range .Values.server.extraVolumes }} - name: userconfig-{{ .name }} {{ .type }}: @@ -238,27 +226,9 @@ spec: {{- if .Values.server.priorityClassName }} priorityClassName: {{ .Values.server.priorityClassName | quote }} {{- end }} - initContainers: - - name: locality-init - image: {{ .Values.global.imageK8S }} - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - command: - - "/bin/sh" - - "-ec" - - | - consul-k8s-control-plane fetch-server-region -node-name "$NODE_NAME" -output-file /consul/extra-config/locality.json - volumeMounts: - - name: extra-config - mountPath: /consul/extra-config - {{- include "consul.restrictedSecurityContext" . | nindent 8 }} containers: - name: consul image: "{{ default .Values.global.image .Values.server.image }}" - imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: ADVERTISE_IP valueFrom: @@ -330,9 +300,9 @@ spec: {{- end }} {{- if .Values.global.cloud.enabled}} # These are mounted as secrets so that the consul server agent can use them. - # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, + # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. - # - HCP_RESOURCE_ID is created for use in the + # - HCP_RESOURCE_ID is created for use in the # `-hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }"` logic in the command below. {{- if .Values.global.cloud.clientId.secretName }} - name: HCP_CLIENT_ID @@ -367,7 +337,7 @@ spec: valueFrom: secretKeyRef: name: {{ .Values.global.cloud.apiHost.secretName }} - key: {{ .Values.global.cloud.apiHost.secretKey }} + key: {{ .Values.global.cloud.apiHost.secretKey }} {{- end}} {{- if .Values.global.cloud.scadaAddress.secretName }} - name: HCP_SCADA_ADDRESS @@ -375,25 +345,13 @@ spec: secretKeyRef: name: {{ .Values.global.cloud.scadaAddress.secretName }} key: {{ .Values.global.cloud.scadaAddress.secretKey }} - {{- end}} - {{- end }} - {{- if .Values.global.trustedCAs }} - - name: SSL_CERT_DIR - value: "/etc/ssl/certs:/trusted-cas" + {{- end}} {{- end }} {{- include "consul.extraEnvironmentVars" .Values.server | nindent 12 }} command: - "/bin/sh" - "-ec" - | - {{- if .Values.global.trustedCAs }} - {{- range $i, $cert := .Values.global.trustedCAs }} - cat < /trusted-cas/custom-ca-{{$i}}.pem - {{- $cert | nindent 14 }} - EOF - {{- end }} - {{- end }} - {{- if and .Values.global.secretsBackend.vault.enabled .Values.global.gossipEncryption.secretName }} GOSSIP_KEY=`cat /vault/secrets/gossip.txt` {{- end }} @@ -426,22 +384,10 @@ spec: -config-dir=/consul/userconfig/{{ .name }} \ {{- end }} {{- end }} - -config-dir=/consul/extra-config \ + -config-file=/consul/extra-config/extra-from-values.json {{- if and .Values.global.cloud.enabled .Values.global.cloud.resourceId.secretName }} -hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }" {{- end }} - - {{- if .Values.global.experiments }} - {{- $commaSeparatedValues := "" }} - {{- range $index, $value := .Values.global.experiments }} - {{- if ne $index 0 }} - {{- $commaSeparatedValues = printf "%s,\\\"%s\\\"" $commaSeparatedValues $value }} - {{- else }} - {{- $commaSeparatedValues = printf "\\\"%s\\\"" $value }} - {{- end }} - {{- end }} - -hcl="experiments=[{{ $commaSeparatedValues }}]" - {{- end }} volumeMounts: - name: data-{{ .Release.Namespace | trunc 58 | trimSuffix "-" }} mountPath: /consul/data @@ -449,8 +395,6 @@ spec: mountPath: /consul/config - name: extra-config mountPath: /consul/extra-config - - name: tmp-extra-config - mountPath: /consul/tmp/extra-config {{- if (and .Values.global.tls.enabled (not .Values.global.secretsBackend.vault.enabled)) }} - name: consul-ca-cert mountPath: /consul/tls/ca/ @@ -474,11 +418,6 @@ spec: mountPath: /consul/vault-ca/ readOnly: true {{- end }} - {{- if .Values.global.trustedCAs }} - - name: trusted-cas - mountPath: /trusted-cas - readOnly: false - {{- end }} ports: {{- if (or (not .Values.global.tls.enabled) (not .Values.global.tls.httpsOnly)) }} - name: http diff --git a/charts/consul/templates/server-tmp-extra-config-configmap.yaml b/charts/consul/templates/server-tmp-extra-config-configmap.yaml deleted file mode 100644 index a42d6d09f6..0000000000 --- a/charts/consul/templates/server-tmp-extra-config-configmap.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) }} -# ConfigMap that is used as a temporary landing spot so that the container command -# in the server-stateful set where it needs to be transformed. ConfigMaps create -# read only volumes so it needs to be copied and transformed to the extra-config -# emptyDir volume where all final extra cofngi lives for use in consul. (locality-init -# also writes to extra-config volume.) -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "consul.fullname" . }}-server-tmp-extra-config - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: server -data: - extra-from-values.json: |- -{{ tpl .Values.server.extraConfig . | trimAll "\"" | indent 4 }} -{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index f81b999e79..f4aeb1cdb8 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -40,7 +40,6 @@ spec: {{- 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 }} diff --git a/charts/consul/templates/telemetry-collector-configmap.yaml b/charts/consul/templates/telemetry-collector-configmap.yaml deleted file mode 100644 index 0bf5b8753c..0000000000 --- a/charts/consul/templates/telemetry-collector-configmap.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if (and .Values.telemetryCollector.enabled .Values.telemetryCollector.customExporterConfig) }} -# Immutable ConfigMap which saves the partition name. Attempting to update this configmap -# with a new Admin Partition name will cause the helm upgrade to fail -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: consul-telemetry-collector -data: - config.json: |- - {{ tpl .Values.telemetryCollector.customExporterConfig . | trimAll "\"" | indent 4 }} -{{- end }} diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml deleted file mode 100644 index d36034b29c..0000000000 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ /dev/null @@ -1,436 +0,0 @@ -{{- if and .Values.telemetryCollector.enabled (not (mustHas "resource-apis" .Values.global.experiments)) }} -{{- if not .Values.telemetryCollector.image}}{{ fail "telemetryCollector.image must be set to enable consul-telemetry-collector" }}{{ end }} -{{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} -{{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} -{{ template "consul.validateCloudSecretKeys" . }} -{{ template "consul.validateTelemetryCollectorCloud" . }} -{{ template "consul.validateTelemetryCollectorCloudSecretKeys" . }} -{{ template "consul.validateTelemetryCollectorResourceId" . }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 4 }} - {{- end }} -spec: - replicas: {{ .Values.telemetryCollector.replicas }} - selector: - matchLabels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "false" - # This annotation tells the endpoints controller that this pod was injected even though it wasn't. The - # endpoints controller would then sync the endpoint into Consul - "consul.hashicorp.com/connect-inject-status": "injected" - # Signals to the endpoints controller that we should force Consul NS creation, since we bypass the mesh webhook. - "consul.hashicorp.com/telemetry-collector": "true" - # We aren't using tproxy and we don't have an original pod. This would be simpler if we made a path similar - # to gateways - "consul.hashicorp.com/connect-service-port": "metricsserver" - "consul.hashicorp.com/transparent-proxy": "false" - "consul.hashicorp.com/transparent-proxy-overwrite-probes": "false" - "consul.hashicorp.com/connect-k8s-version": {{ $.Chart.Version }} - {{- if .Values.telemetryCollector.customExporterConfig }} - # configmap checksum - "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/telemetry-collector-configmap.yaml") . | sha256sum }} - {{- end }} - # vault annotations - {{- 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 }} - - labels: - consul.hashicorp.com/connect-inject-managed-by: consul-k8s-endpoints-controller - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 8 }} - {{- end }} - spec: - # This needs to explicitly be consul-telemetry-collector because we look this up from each service consul-dataplane - # to forward metrics to it. - serviceAccountName: consul-telemetry-collector - initContainers: - # We're manually managing this init container instead of using the connect injector so that we don't run into - # any race conditions on the connect-injector deployment or upgrade - - name: consul-connect-init - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: CONSUL_NODE_NAME - value: $(NODE_NAME)-virtual - {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} - # acl login info - {{- if .Values.global.acls.manageSystemACLs }} - - name: CONSUL_LOGIN_AUTH_METHOD - value: {{ template "consul.fullname" . }}-k8s-auth-method - - name: CONSUL_LOGIN_DATACENTER - value: {{ .Values.global.datacenter }} - - name: CONSUL_LOGIN_META - value: "component=consul-telemetry-collector,pod=$(NAMESPACE)/$(POD_NAME)" - {{- end }} - # service and login namespace - # this is attempting to replicate the behavior of webhooks in calculating namespace - # https://github.com/hashicorp/consul-k8s/blob/b84339050bb2c4b62b60cec96275f74952b0ac9d/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go#L200 - {{- if .Values.global.enableConsulNamespaces }} - {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - - name: CONSUL_NAMESPACE - value: {{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }}{{ .Release.Namespace }} - {{- else }} - - name: CONSUL_NAMESPACE - value: {{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- if .Values.global.acls.manageSystemACLs }} - {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - - name: CONSUL_LOGIN_NAMESPACE - value: default - {{- else }} - - name: CONSUL_LOGIN_NAMESPACE - value: {{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- end }} - {{- end }} - command: - - /bin/sh - - -ec - - |- - consul-k8s-control-plane connect-init \ - -log-json={{ .Values.global.logJSON }} \ - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} \ - -pod-name=${POD_NAME} \ - -pod-namespace=${POD_NAMESPACE} \ - -proxy-id-file="/consul/connect-inject/proxyid" \ - -service-account-name="consul-telemetry-collector" \ - -service-name="" - - image: {{ .Values.global.imageK8S }} - imagePullPolicy: IfNotPresent - {{- if .Values.telemetryCollector.initContainer.resources }} - resources: - {{- toYaml .Values.telemetryCollector.initContainer.resources | nindent 12 }} - {{- else }} - resources: - limits: - cpu: 50m - memory: 150Mi - requests: - cpu: 50m - memory: 25Mi - {{- end }} - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - volumeMounts: - - mountPath: /consul/connect-inject - name: consul-connect-inject-data - {{- 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 }} - containers: - - name: consul-telemetry-collector - image: {{ .Values.telemetryCollector.image }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} - ports: - - containerPort: 9090 - name: metrics - protocol: TCP - - containerPort: 9356 - name: metricsserver - protocol: TCP - env: - # These are mounted as secrets so that the telemetry-collector can use them when cloud is enabled. - # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, - # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. - # - HCP_RESOURCE_ID is created either in the global cloud section or in telemetryCollector.cloud - {{- if .Values.telemetryCollector.cloud.resourceId.secretName }} - - name: HCP_RESOURCE_ID - valueFrom: - secretKeyRef: - name: {{ .Values.telemetryCollector.cloud.resourceId.secretName }} - key: {{ .Values.telemetryCollector.cloud.resourceId.secretKey }} - {{- else if .Values.global.cloud.resourceId.secretName }} - - name: HCP_RESOURCE_ID - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.resourceId.secretName }} - key: {{ .Values.global.cloud.resourceId.secretKey }} - {{- end }} - {{- if .Values.telemetryCollector.cloud.clientId.secretName }} - - name: HCP_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ .Values.telemetryCollector.cloud.clientId.secretName }} - key: {{ .Values.telemetryCollector.cloud.clientId.secretKey }} - {{- else if .Values.global.cloud.clientId.secretName }} - - name: HCP_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.clientId.secretName }} - key: {{ .Values.global.cloud.clientId.secretKey }} - {{- end }} - {{- if .Values.telemetryCollector.cloud.clientSecret.secretName }} - - name: HCP_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ .Values.telemetryCollector.cloud.clientSecret.secretName }} - key: {{ .Values.telemetryCollector.cloud.clientSecret.secretKey }} - {{- else if .Values.global.cloud.clientSecret.secretName }} - - name: HCP_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.clientSecret.secretName }} - key: {{ .Values.global.cloud.clientSecret.secretKey }} - {{- end}} - {{- if .Values.global.cloud.authUrl.secretName }} - - name: HCP_AUTH_URL - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.authUrl.secretName }} - key: {{ .Values.global.cloud.authUrl.secretKey }} - {{- end}} - {{- if .Values.global.cloud.apiHost.secretName }} - - name: HCP_API_HOST - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.apiHost.secretName }} - key: {{ .Values.global.cloud.apiHost.secretKey }} - {{- end}} - {{- if .Values.global.cloud.scadaAddress.secretName }} - - name: HCP_SCADA_ADDRESS - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.scadaAddress.secretName }} - key: {{ .Values.global.cloud.scadaAddress.secretKey }} - {{- end}} - {{- if .Values.global.trustedCAs }} - - name: SSL_CERT_DIR - value: "/etc/ssl/certs:/trusted-cas" - {{- end }} - {{- include "consul.extraEnvironmentVars" .Values.telemetryCollector | nindent 12 }} - command: - - "/bin/sh" - - "-ec" - - | - {{- if .Values.global.trustedCAs }} - {{- range $i, $cert := .Values.global.trustedCAs }} - cat < /trusted-cas/custom-ca-{{$i}}.pem - {{- $cert | nindent 10 }} - EOF - {{- end }} - {{- end }} - - consul-telemetry-collector agent \ - {{- if .Values.telemetryCollector.customExporterConfig }} - -config-file-path /consul/config/config.json \ - {{ end }} - volumeMounts: - {{- if .Values.telemetryCollector.customExporterConfig }} - - name: config - mountPath: /consul/config - {{- end }} - {{- if .Values.global.trustedCAs }} - - name: trusted-cas - mountPath: /trusted-cas - readOnly: false - {{- end }} - resources: - {{- if .Values.telemetryCollector.resources }} - {{- toYaml .Values.telemetryCollector.resources | nindent 12 }} - {{- end }} - # consul-dataplane container - - name: consul-dataplane - image: "{{ .Values.global.imageConsulDataplane }}" - imagePullPolicy: IfNotPresent - command: - - consul-dataplane - args: - # addresses - {{- if .Values.externalServers.enabled }} - - -addresses={{ .Values.externalServers.hosts | first }} - {{- else }} - - -addresses={{ template "consul.fullname" . }}-server.{{ .Release.Namespace }}.svc - {{- end }} - # grpc - {{- if .Values.externalServers.enabled }} - - -grpc-port={{ .Values.externalServers.grpcPort }} - {{- else }} - - -grpc-port=8502 - {{- end }} - - -proxy-service-id-path=/consul/connect-inject/proxyid - # tls - {{- if .Values.global.tls.enabled }} - {{- if (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) }} - {{- if .Values.global.secretsBackend.vault.enabled }} - - -ca-certs=/vault/secrets/serverca.crt - {{- else }} - - -ca-certs=/consul/tls/ca/tls.crt - {{- end }} - {{- end }} - {{- if and .Values.externalServers.enabled .Values.externalServers.tlsServerName }} - - -tls-server-name={{.Values.externalServers.tlsServerName }} - {{- else if .Values.global.cloud.enabled }} - - -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} - {{- end }} - {{- else }} - - -tls-disabled - {{- end }} - # credentials - {{- if .Values.global.acls.manageSystemACLs }} - - -credential-type=login - - -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token - - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method - {{- end }} - # service and login namespace - {{- if .Values.global.enableConsulNamespaces }} - {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - - -service-namespace={{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }}{{ .Release.Namespace }} - {{- else }} - - -service-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- if .Values.global.acls.manageSystemACLs }} - {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - - -login-namespace=default - {{- else }} - - -login-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- end }} - {{- end }} - # service and login partition - {{- if .Values.global.adminPartitions.enabled }} - - -service-partition={{ .Values.global.adminPartitions.name }} - {{- if .Values.global.acls.manageSystemACLs }} - - -login-partition={{ .Values.global.adminPartitions.name }} - {{- end }} - {{- end }} - # telemetry - {{- if .Values.global.metrics.enabled }} - - -telemetry-prom-scrape-path=/metrics - {{- end }} - - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} - - -log-json={{ .Values.global.logJSON }} - - -envoy-concurrency=2 - {{- if and .Values.externalServers.enabled .Values.externalServers.skipServerWatch }} - - -server-watch-disabled=true - {{- end }} - env: - - name: NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: DP_CREDENTIAL_LOGIN_META1 - value: pod=$(NAMESPACE)/$(POD_NAME) - - name: DP_CREDENTIAL_LOGIN_META2 - value: component=consul-telemetry-collector - - name: DP_SERVICE_NODE_NAME - value: $(NODE_NAME)-virtual - - name: TMPDIR - value: /consul/connect-inject - readinessProbe: - failureThreshold: 3 - initialDelaySeconds: 1 - periodSeconds: 10 - successThreshold: 1 - tcpSocket: - port: 20000 - timeoutSeconds: 1 - securityContext: - readOnlyRootFilesystem: true - runAsGroup: 5995 - runAsNonRoot: true - runAsUser: 5995 - # dataplane volume mounts - volumeMounts: - - mountPath: /consul/connect-inject - name: consul-connect-inject-data - {{- 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 }} - - {{- if .Values.telemetryCollector.nodeSelector }} - nodeSelector: - {{ tpl .Values.telemetryCollector.nodeSelector . | indent 8 | trim }} - {{- end }} - {{- if .Values.telemetryCollector.priorityClassName }} - priorityClassName: {{ .Values.telemetryCollector.priorityClassName }} - {{- end }} - volumes: - - emptyDir: - medium: Memory - name: consul-connect-inject-data - {{- if .Values.global.trustedCAs }} - - name: trusted-cas - emptyDir: - medium: "Memory" - {{- end }} - {{- 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 }} - - name: config - configMap: - name: {{ template "consul.fullname" . }}-telemetry-collector -{{- end }} diff --git a/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml b/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml deleted file mode 100644 index f4c05a2f33..0000000000 --- a/charts/consul/templates/telemetry-collector-podsecuritypolicy.yaml +++ /dev/null @@ -1,42 +0,0 @@ -{{- if and .Values.global.enablePodSecurityPolicies .Values.telemetryCollector.enabled }} -apiVersion: policy/v1beta1 -kind: PodSecurityPolicy -metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: telemetry-collector -spec: - privileged: false - # Required to prevent escalations to root. - allowPrivilegeEscalation: false - # This is redundant with non-root + disallow privilege escalation, - # but we can provide it for defense in depth. - requiredDropCapabilities: - - ALL - defaultAddCapabilities: - - NET_BIND_SERVICE - # Allow core volume types. - volumes: - - 'configMap' - - 'emptyDir' - - 'projected' - - 'secret' - - 'downwardAPI' - hostNetwork: false - hostIPC: false - hostPID: false - runAsUser: - rule: 'RunAsAny' - seLinux: - rule: 'RunAsAny' - supplementalGroups: - rule: 'RunAsAny' - fsGroup: - rule: 'RunAsAny' - readOnlyRootFilesystem: false -{{- end }} diff --git a/charts/consul/templates/telemetry-collector-role.yaml b/charts/consul/templates/telemetry-collector-role.yaml deleted file mode 100644 index f89373252d..0000000000 --- a/charts/consul/templates/telemetry-collector-role.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if and .Values.global.enablePodSecurityPolicies .Values.telemetryCollector.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: consul-telemetry-collector -rules: - - apiGroups: ["policy"] - resources: ["podsecuritypolicies"] - resourceNames: - - {{ template "consul.fullname" . }}-telemetry-collector - verbs: - - use -{{- end }} - diff --git a/charts/consul/templates/telemetry-collector-rolebinding.yaml b/charts/consul/templates/telemetry-collector-rolebinding.yaml deleted file mode 100644 index 1f9a896997..0000000000 --- a/charts/consul/templates/telemetry-collector-rolebinding.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if and .Values.global.enablePodSecurityPolicies .Values.telemetryCollector.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: consul-telemetry-collector -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ template "consul.fullname" . }}-telemetry-collector -subjects: - - kind: ServiceAccount - name: {{ template "consul.fullname" . }}-telemetry-collector -{{- end }} - diff --git a/charts/consul/templates/telemetry-collector-service.yaml b/charts/consul/templates/telemetry-collector-service.yaml deleted file mode 100644 index 266c80b4bf..0000000000 --- a/charts/consul/templates/telemetry-collector-service.yaml +++ /dev/null @@ -1,24 +0,0 @@ -{{- if .Values.telemetryCollector.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: consul-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - {{ if .Values.telemetryCollector.service.annotations }} - annotations: - {{ tpl .Values.telemetryCollector.service.annotations . | nindent 4 | trim }} - {{- end }} -spec: - type: ClusterIP - ports: - - port: 9356 - targetPort: 9356 - selector: - app: consul - component: consul-telemetry-collector -{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/telemetry-collector-serviceaccount.yaml b/charts/consul/templates/telemetry-collector-serviceaccount.yaml deleted file mode 100644 index fca58eede9..0000000000 --- a/charts/consul/templates/telemetry-collector-serviceaccount.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.telemetryCollector.enabled }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: consul-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - {{- if .Values.telemetryCollector.serviceAccount.annotations }} - annotations: - {{ tpl .Values.telemetryCollector.serviceAccount.annotations . | nindent 4 | trim }} - {{- end }} -automountServiceAccountToken: true -{{- with .Values.global.imagePullSecrets }} -imagePullSecrets: -{{- range . }} - - name: {{ .name }} -{{- end }} -{{- end }} -{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/telemetry-collector-v2-deployment.yaml b/charts/consul/templates/telemetry-collector-v2-deployment.yaml deleted file mode 100644 index d8c94e7ecf..0000000000 --- a/charts/consul/templates/telemetry-collector-v2-deployment.yaml +++ /dev/null @@ -1,415 +0,0 @@ -{{- if and .Values.telemetryCollector.enabled (mustHas "resource-apis" .Values.global.experiments) }} -{{- if not .Values.telemetryCollector.image}}{{ fail "telemetryCollector.image must be set to enable consul-telemetry-collector" }}{{ end }} -{{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} -{{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} -{{ template "consul.validateCloudSecretKeys" . }} -{{ template "consul.validateTelemetryCollectorCloud" . }} -{{ template "consul.validateTelemetryCollectorCloudSecretKeys" . }} -{{ template "consul.validateTelemetryCollectorResourceId" . }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 4 }} - {{- end }} -spec: - replicas: {{ .Values.telemetryCollector.replicas }} - selector: - matchLabels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - template: - metadata: - annotations: - "consul.hashicorp.com/mesh-inject": "false" - # This annotation tells the pod controller that this pod was injected even though it wasn't. - # This ensures the pod controller will sync a workload for the pod into Consul - "consul.hashicorp.com/mesh-inject-status": "injected" - # We aren't using tproxy and we don't have an original pod. This would be simpler if we made a path similar - # to gateways - "consul.hashicorp.com/transparent-proxy": "false" - "consul.hashicorp.com/transparent-proxy-overwrite-probes": "false" - "consul.hashicorp.com/consul-k8s-version": {{ $.Chart.Version }} - {{- if .Values.telemetryCollector.customExporterConfig }} - # configmap checksum - "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/telemetry-collector-configmap.yaml") . | sha256sum }} - {{- end }} - # vault annotations - {{- 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 }} - - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 8 }} - {{- end }} - spec: - # This needs to explicitly be consul-telemetry-collector because we look this up from each service consul-dataplane - # to forward metrics to it. - serviceAccountName: consul-telemetry-collector - initContainers: - # We're manually managing this init container instead of using the mesh injector so that we don't run into - # any race conditions on the mesh-injector deployment or upgrade - - name: consul-mesh-init - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - # acl login info - {{- if .Values.global.acls.manageSystemACLs }} - - name: CONSUL_LOGIN_AUTH_METHOD - value: {{ template "consul.fullname" . }}-k8s-auth-method - - name: CONSUL_LOGIN_DATACENTER - value: {{ .Values.global.datacenter }} - - name: CONSUL_LOGIN_META - value: "component=consul-telemetry-collector,pod=$(NAMESPACE)/$(POD_NAME)" - {{- end }} - # service and login namespace - # this is attempting to replicate the behavior of webhooks in calculating namespace - # https://github.com/hashicorp/consul-k8s/blob/b84339050bb2c4b62b60cec96275f74952b0ac9d/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go#L200 - {{- if .Values.global.enableConsulNamespaces }} - {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - - name: CONSUL_NAMESPACE - value: {{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }}{{ .Release.Namespace }} - {{- else }} - - name: CONSUL_NAMESPACE - value: {{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- if .Values.global.acls.manageSystemACLs }} - {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - - name: CONSUL_LOGIN_NAMESPACE - value: "default" - {{- else }} - - name: CONSUL_LOGIN_NAMESPACE - value: {{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- end }} - {{- end }} - command: - - /bin/sh - - -ec - - |- - consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} \ - -log-json={{ .Values.global.logJSON }} - - image: {{ .Values.global.imageK8S }} - imagePullPolicy: IfNotPresent - {{- if .Values.telemetryCollector.initContainer.resources }} - resources: - {{- toYaml .Values.telemetryCollector.initContainer.resources | nindent 12 }} - {{- else }} - resources: - limits: - cpu: 50m - memory: 150Mi - requests: - cpu: 50m - memory: 25Mi - {{- end }} - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - volumeMounts: - - mountPath: /consul/mesh-inject - name: consul-mesh-inject-data - {{- 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 }} - containers: - - name: consul-telemetry-collector - image: {{ .Values.telemetryCollector.image }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} - ports: - - containerPort: 9090 - name: metrics - protocol: TCP - - containerPort: 9356 - name: metricsserver - protocol: TCP - env: - # These are mounted as secrets so that the telemetry-collector can use them when cloud is enabled. - # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, - # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. - # - HCP_RESOURCE_ID is created either in the global cloud section or in telemetryCollector.cloud - {{- if .Values.telemetryCollector.cloud.resourceId.secretName }} - - name: HCP_RESOURCE_ID - valueFrom: - secretKeyRef: - name: {{ .Values.telemetryCollector.cloud.resourceId.secretName }} - key: {{ .Values.telemetryCollector.cloud.resourceId.secretKey }} - {{- else if .Values.global.cloud.resourceId.secretName }} - - name: HCP_RESOURCE_ID - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.resourceId.secretName }} - key: {{ .Values.global.cloud.resourceId.secretKey }} - {{- end }} - {{- if .Values.telemetryCollector.cloud.clientId.secretName }} - - name: HCP_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ .Values.telemetryCollector.cloud.clientId.secretName }} - key: {{ .Values.telemetryCollector.cloud.clientId.secretKey }} - {{- else if .Values.global.cloud.clientId.secretName }} - - name: HCP_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.clientId.secretName }} - key: {{ .Values.global.cloud.clientId.secretKey }} - {{- end }} - {{- if .Values.telemetryCollector.cloud.clientSecret.secretName }} - - name: HCP_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ .Values.telemetryCollector.cloud.clientSecret.secretName }} - key: {{ .Values.telemetryCollector.cloud.clientSecret.secretKey }} - {{- else if .Values.global.cloud.clientSecret.secretName }} - - name: HCP_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.clientSecret.secretName }} - key: {{ .Values.global.cloud.clientSecret.secretKey }} - {{- end}} - {{- if .Values.global.cloud.authUrl.secretName }} - - name: HCP_AUTH_URL - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.authUrl.secretName }} - key: {{ .Values.global.cloud.authUrl.secretKey }} - {{- end}} - {{- if .Values.global.cloud.apiHost.secretName }} - - name: HCP_API_HOST - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.apiHost.secretName }} - key: {{ .Values.global.cloud.apiHost.secretKey }} - {{- end}} - {{- if .Values.global.cloud.scadaAddress.secretName }} - - name: HCP_SCADA_ADDRESS - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.scadaAddress.secretName }} - key: {{ .Values.global.cloud.scadaAddress.secretKey }} - {{- end}} - {{- if .Values.global.trustedCAs }} - - name: SSL_CERT_DIR - value: "/etc/ssl/certs:/trusted-cas" - {{- end }} - {{- include "consul.extraEnvironmentVars" .Values.telemetryCollector | nindent 12 }} - command: - - "/bin/sh" - - "-ec" - - | - {{- if .Values.global.trustedCAs }} - {{- range $i, $cert := .Values.global.trustedCAs }} - cat < /trusted-cas/custom-ca-{{$i}}.pem - {{- $cert | nindent 10 }} - EOF - {{- end }} - {{- end }} - - consul-telemetry-collector agent \ - {{- if .Values.telemetryCollector.customExporterConfig }} - -config-file-path /consul/config/config.json \ - {{ end }} - volumeMounts: - {{- if .Values.telemetryCollector.customExporterConfig }} - - name: config - mountPath: /consul/config - {{- end }} - {{- if .Values.global.trustedCAs }} - - name: trusted-cas - mountPath: /trusted-cas - readOnly: false - {{- end }} - resources: - {{- if .Values.telemetryCollector.resources }} - {{- toYaml .Values.telemetryCollector.resources | nindent 12 }} - {{- end }} - # consul-dataplane container - - name: consul-dataplane - image: "{{ .Values.global.imageConsulDataplane }}" - imagePullPolicy: IfNotPresent - command: - - consul-dataplane - args: - # addresses - {{- if .Values.externalServers.enabled }} - - -addresses={{ .Values.externalServers.hosts | first }} - {{- else }} - - -addresses={{ template "consul.fullname" . }}-server.{{ .Release.Namespace }}.svc - {{- end }} - # grpc - {{- if .Values.externalServers.enabled }} - - -grpc-port={{ .Values.externalServers.grpcPort }} - {{- else }} - - -grpc-port=8502 - {{- end }} - # tls - {{- if .Values.global.tls.enabled }} - {{- if (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) }} - {{- if .Values.global.secretsBackend.vault.enabled }} - - -ca-certs=/vault/secrets/serverca.crt - {{- else }} - - -ca-certs=/consul/tls/ca/tls.crt - {{- end }} - {{- end }} - {{- if and .Values.externalServers.enabled .Values.externalServers.tlsServerName }} - - -tls-server-name={{.Values.externalServers.tlsServerName }} - {{- else if .Values.global.cloud.enabled }} - - -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} - {{- end }} - {{- else }} - - -tls-disabled - {{- end }} - # credentials - {{- if .Values.global.acls.manageSystemACLs }} - - -credential-type=login - - -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token - - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method - {{- end }} - # service and login namespace - {{- if .Values.global.enableConsulNamespaces }} - {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - - -service-namespace={{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }}{{ .Release.Namespace }} - {{- else }} - - -service-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- if .Values.global.acls.manageSystemACLs }} - {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} - - -login-namespace=default - {{- else }} - - -login-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- end }} - {{- end }} - # service and login partition - {{- if .Values.global.adminPartitions.enabled }} - - -service-partition={{ .Values.global.adminPartitions.name }} - {{- if .Values.global.acls.manageSystemACLs }} - - -login-partition={{ .Values.global.adminPartitions.name }} - {{- end }} - {{- end }} - # telemetry - {{- if .Values.global.metrics.enabled }} - - -telemetry-prom-scrape-path=/metrics - {{- end }} - - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} - - -log-json={{ .Values.global.logJSON }} - - -envoy-concurrency=2 - {{- if and .Values.externalServers.enabled .Values.externalServers.skipServerWatch }} - - -server-watch-disabled=true - {{- end }} - env: - - name: NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: DP_PROXY_ID - value: $(POD_NAME) - - name: DP_CREDENTIAL_LOGIN_META1 - value: pod=$(NAMESPACE)/$(POD_NAME) - - name: DP_CREDENTIAL_LOGIN_META2 - value: component=consul-telemetry-collector - - name: TMPDIR - value: /consul/mesh-inject - readinessProbe: - failureThreshold: 3 - initialDelaySeconds: 1 - periodSeconds: 10 - successThreshold: 1 - tcpSocket: - port: 20000 - timeoutSeconds: 1 - securityContext: - readOnlyRootFilesystem: true - runAsGroup: 5995 - runAsNonRoot: true - runAsUser: 5995 - # dataplane volume mounts - volumeMounts: - - mountPath: /consul/mesh-inject - name: consul-mesh-inject-data - {{- 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 }} - - {{- if .Values.telemetryCollector.nodeSelector }} - nodeSelector: - {{ tpl .Values.telemetryCollector.nodeSelector . | indent 8 | trim }} - {{- end }} - {{- if .Values.telemetryCollector.priorityClassName }} - priorityClassName: {{ .Values.telemetryCollector.priorityClassName }} - {{- end }} - volumes: - - emptyDir: - medium: Memory - name: consul-mesh-inject-data - {{- if .Values.global.trustedCAs }} - - name: trusted-cas - emptyDir: - medium: "Memory" - {{- end }} - {{- 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 }} - - name: config - configMap: - name: {{ template "consul.fullname" . }}-telemetry-collector -{{- end }} diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index 3a1a23fad3..ea2131b8a2 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -76,7 +76,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "terminating-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .name }}" {{- if $root.Values.global.enableConsulNamespaces }} diff --git a/charts/consul/templates/tls-init-cleanup-job.yaml b/charts/consul/templates/tls-init-cleanup-job.yaml index 9500410a53..2254a38ed2 100644 --- a/charts/consul/templates/tls-init-cleanup-job.yaml +++ b/charts/consul/templates/tls-init-cleanup-job.yaml @@ -35,7 +35,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.tls.annotations }} {{- tpl .Values.global.tls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index 54727e03dd..47651fe14b 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -35,7 +35,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.tls.annotations }} {{- tpl .Values.global.tls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml index 2a5c80d94c..c2a2422d02 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -27,7 +27,6 @@ rules: - admissionregistration.k8s.io resources: - mutatingwebhookconfigurations - - validatingwebhookconfigurations verbs: - get - list diff --git a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml index 472ef4ee1d..ca2bb84bda 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/consul/templates/webhook-cert-manager-configmap.yaml b/charts/consul/templates/webhook-cert-manager-configmap.yaml index 293dd32d9f..914d2b87dd 100644 --- a/charts/consul/templates/webhook-cert-manager-configmap.yaml +++ b/charts/consul/templates/webhook-cert-manager-configmap.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 kind: ConfigMap diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index 29b85d7079..ffa68216f3 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: apps/v1 kind: Deployment @@ -36,7 +36,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/webhook-cert-manager-configmap.yaml") . | sha256sum }} spec: containers: diff --git a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml index 4d685edc39..b67dbda510 100644 --- a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml +++ b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} {{- if (and .Values.global.enablePodSecurityPolicies (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled))) }} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: policy/v1beta1 diff --git a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml index 68c54f3c27..fa4b24ef8b 100644 --- a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml +++ b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml @@ -1,4 +1,4 @@ -{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName) -}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} {{- if (and .Values.connectInject.enabled (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 kind: ServiceAccount diff --git a/charts/consul/test/docker/Test.dockerfile b/charts/consul/test/docker/Test.dockerfile index e6a4caa6e0..85f3a607e3 100644 --- a/charts/consul/test/docker/Test.dockerfile +++ b/charts/consul/test/docker/Test.dockerfile @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # This Dockerfile installs all the dependencies necessary to run the unit and # acceptance tests. This image also contains gcloud so you can run tests # against a GKE cluster easily. diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index 3bc8b40451..c404d0bb72 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -68,7 +68,7 @@ module "eks" { kubeconfig_api_version = "client.authentication.k8s.io/v1beta1" cluster_name = "consul-k8s-${random_id.suffix[count.index].dec}" - cluster_version = "1.26" + cluster_version = "1.25" subnets = module.vpc[count.index].private_subnets enable_irsa = true @@ -124,13 +124,12 @@ resource "aws_iam_role_policy_attachment" "csi" { } resource "aws_eks_addon" "csi-driver" { - count = var.cluster_count - cluster_name = module.eks[count.index].cluster_id - addon_name = "aws-ebs-csi-driver" - addon_version = "v1.15.0-eksbuild.1" - service_account_role_arn = aws_iam_role.csi-driver-role[count.index].arn - resolve_conflicts_on_create = "OVERWRITE" - resolve_conflicts_on_update = "OVERWRITE" + count = var.cluster_count + cluster_name = module.eks[count.index].cluster_id + addon_name = "aws-ebs-csi-driver" + addon_version = "v1.15.0-eksbuild.1" + service_account_role_arn = aws_iam_role.csi-driver-role[count.index].arn + resolve_conflicts = "OVERWRITE" } data "aws_eks_cluster" "cluster" { diff --git a/charts/consul/test/terraform/eks/outputs.tf b/charts/consul/test/terraform/eks/outputs.tf index 1d971bf0b5..986a917f6a 100644 --- a/charts/consul/test/terraform/eks/outputs.tf +++ b/charts/consul/test/terraform/eks/outputs.tf @@ -3,4 +3,4 @@ output "kubeconfigs" { value = [for cl in module.eks : pathexpand(format("~/.kube/%s", cl.cluster_id))] -} +} \ No newline at end of file diff --git a/charts/consul/test/terraform/eks/variables.tf b/charts/consul/test/terraform/eks/variables.tf index 548cddeb33..7536668442 100644 --- a/charts/consul/test/terraform/eks/variables.tf +++ b/charts/consul/test/terraform/eks/variables.tf @@ -27,4 +27,4 @@ variable "tags" { type = map(any) default = {} description = "Tags to attach to the created resources." -} +} \ No newline at end of file diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index 800aca5246..fe5adc5e8d 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { google = { - version = "~> 5.3.0" + version = "~> 4.58.0" } } } @@ -21,7 +21,7 @@ resource "random_id" "suffix" { data "google_container_engine_versions" "main" { location = var.zone - version_prefix = "1.27." + version_prefix = "1.25." } # We assume that the subnets are already created to save time. @@ -37,17 +37,14 @@ resource "google_container_cluster" "cluster" { project = var.project initial_node_count = 3 location = var.zone - # 2023-10-30 - There is a bug with the terraform provider where lastest_master_version is not being returned by the - # api. Hardcode GKE version for now. min_master_version = data.google_container_engine_versions.main.latest_master_version node_version = data.google_container_engine_versions.main.latest_master_version node_config { tags = ["consul-k8s-${random_id.suffix[count.index].dec}"] machine_type = "e2-standard-8" } - subnetwork = data.google_compute_subnetwork.subnet.name - resource_labels = var.labels - deletion_protection = false + subnetwork = data.google_compute_subnetwork.subnet.name + resource_labels = var.labels } resource "google_compute_firewall" "firewall-rules" { diff --git a/charts/consul/test/terraform/gke/outputs.tf b/charts/consul/test/terraform/gke/outputs.tf index b1c1343e9e..17a2aac4fd 100644 --- a/charts/consul/test/terraform/gke/outputs.tf +++ b/charts/consul/test/terraform/gke/outputs.tf @@ -11,8 +11,4 @@ output "cluster_names" { output "kubeconfigs" { value = [for cl in google_container_cluster.cluster : format("$HOME/.kube/%s", cl.name)] -} - -output "versions" { - value = data.google_container_engine_versions.main -} +} \ No newline at end of file diff --git a/charts/consul/test/terraform/gke/variables.tf b/charts/consul/test/terraform/gke/variables.tf index f33952850a..ba580a540d 100644 --- a/charts/consul/test/terraform/gke/variables.tf +++ b/charts/consul/test/terraform/gke/variables.tf @@ -42,4 +42,4 @@ variable "subnet" { type = string default = "default" description = "Subnet to create the cluster in. Currently all clusters use the default subnet and we are running out of IPs" -} +} \ No newline at end of file diff --git a/charts/consul/test/terraform/openshift/main.tf b/charts/consul/test/terraform/openshift/main.tf index 3605c37a7b..88da265c17 100644 --- a/charts/consul/test/terraform/openshift/main.tf +++ b/charts/consul/test/terraform/openshift/main.tf @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - provider "azurerm" { features {} } diff --git a/charts/consul/test/terraform/openshift/oc-login.sh b/charts/consul/test/terraform/openshift/oc-login.sh index f463c03af7..e7db48761d 100755 --- a/charts/consul/test/terraform/openshift/oc-login.sh +++ b/charts/consul/test/terraform/openshift/oc-login.sh @@ -1,7 +1,4 @@ #!/usr/bin/env bash -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - resource_group=$1 cluster_name=$2 diff --git a/charts/consul/test/terraform/openshift/outputs.tf b/charts/consul/test/terraform/openshift/outputs.tf index 7a395e33b7..66fc892934 100644 --- a/charts/consul/test/terraform/openshift/outputs.tf +++ b/charts/consul/test/terraform/openshift/outputs.tf @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - output "kubeconfigs" { value = [for rg in azurerm_resource_group.test : format("$HOME/.kube/%s", rg.name)] } \ No newline at end of file diff --git a/charts/consul/test/terraform/openshift/variables.tf b/charts/consul/test/terraform/openshift/variables.tf index 5aa3cc0180..1df518f8ed 100644 --- a/charts/consul/test/terraform/openshift/variables.tf +++ b/charts/consul/test/terraform/openshift/variables.tf @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - variable "location" { default = "westus2" description = "The Azure Region to create all resources in." diff --git a/charts/consul/test/unit/_helpers.bash b/charts/consul/test/unit/_helpers.bash index d57d2a0fbf..1b9d1aa4d2 100644 --- a/charts/consul/test/unit/_helpers.bash +++ b/charts/consul/test/unit/_helpers.bash @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # chart_dir returns the directory for the chart chart_dir() { echo ${BATS_TEST_DIRNAME}/../.. diff --git a/charts/consul/test/unit/client-config-configmap.bats b/charts/consul/test/unit/client-config-configmap.bats index 7d0d424499..1f1443a156 100755 --- a/charts/consul/test/unit/client-config-configmap.bats +++ b/charts/consul/test/unit/client-config-configmap.bats @@ -31,6 +31,17 @@ load _helpers . } +@test "client/ConfigMap: extraConfig is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-config-configmap.yaml \ + --set 'client.enabled=true' \ + --set 'client.extraConfig="{\"hello\": \"world\"}"' \ + . | tee /dev/stderr | + yq '.data["extra-from-values.json"] | match("world") | length > 1' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # connectInject.centralConfig [DEPRECATED] diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index bb7b6cd472..ff9288a51d 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -530,11 +530,7 @@ load _helpers -s templates/client-daemonset.yaml \ --set 'client.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -625,7 +621,7 @@ load _helpers --set 'client.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 678c5c1c2ca0f8cb1464d38636f12714c05df26fab1a101e43ce619fdbc2e7d1 ] + [ "${actual}" = 4fa9ddc3abc4c79eafccb19e5beef80006b7c9736b867d8873554ca03f42a6b3 ] } @test "client/DaemonSet: config-checksum annotation changes when extraConfig is provided" { @@ -636,7 +632,7 @@ load _helpers --set 'client.extraConfig="{\"hello\": \"world\"}"' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 0ef58da6fd14fb57c702a2a0d631c4eecacff152fe3a36836a23283b19d8dbe1 ] + [ "${actual}" = 42b99932385e7a0580b134fe36a9bda405aab2e375593326677b9838708f0796 ] } @test "client/DaemonSet: config-checksum annotation changes when connectInject.enabled=true" { @@ -647,7 +643,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 678c5c1c2ca0f8cb1464d38636f12714c05df26fab1a101e43ce619fdbc2e7d1 ] + [ "${actual}" = 4fa9ddc3abc4c79eafccb19e5beef80006b7c9736b867d8873554ca03f42a6b3 ] } #-------------------------------------------------------------------- @@ -2728,14 +2724,7 @@ rollingUpdate: --set 'global.secretsBackend.vault.consulClientRole=test' \ --set 'global.secretsBackend.vault.consulServerRole=foo' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role") | - del(."vault.hashicorp.com/agent-init-first")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-init-first")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/client-tmp-extra-config-configmap.bats b/charts/consul/test/unit/client-tmp-extra-config-configmap.bats deleted file mode 100755 index 435ed54ce9..0000000000 --- a/charts/consul/test/unit/client-tmp-extra-config-configmap.bats +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "client/TmpExtraConfigMap: enable with global.enabled false" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/client-tmp-extra-config-configmap.yaml \ - --set 'client.enabled=true' \ - --set 'global.enabled=false' \ - --set 'client.enabled=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "client/TmpExtraConfigMap: disable with client.enabled false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/client-tmp-extra-config-configmap.yaml \ - --set 'client.enabled=true' \ - --set 'client.enabled=false' \ - . -} - -@test "client/TmpExtraConfigMap: disable with global.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/client-tmp-extra-config-configmap.yaml \ - --set 'global.enabled=false' \ - . -} - -@test "client/TmpExtraConfigMap: extraConfig is set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/client-tmp-extra-config-configmap.yaml \ - --set 'client.enabled=true' \ - --set 'client.extraConfig="{\"hello\": \"world\"}"' \ - . | tee /dev/stderr | - yq '.data["extra-from-values.json"] | match("world") | length > 1' | tee /dev/stderr) - [ "${actual}" = "true" ] -} diff --git a/charts/consul/test/unit/connect-inject-clusterrole.bats b/charts/consul/test/unit/connect-inject-clusterrole.bats index cfe64337d9..b1ad10a80c 100644 --- a/charts/consul/test/unit/connect-inject-clusterrole.bats +++ b/charts/consul/test/unit/connect-inject-clusterrole.bats @@ -77,7 +77,7 @@ load _helpers --set 'client.enabled=true' \ --set 'connectInject.enabled=true' \ . | tee /dev/stderr | - yq -r '.rules[4]' | tee /dev/stderr) + yq -r '.rules[3]' | tee /dev/stderr) local actual=$(echo $object | yq -r '.resources[| index("pods")' | tee /dev/stderr) [ "${actual}" != null ] @@ -106,7 +106,7 @@ load _helpers --set 'client.enabled=true' \ --set 'connectInject.enabled=true' \ . | tee /dev/stderr | - yq -r '.rules[5]' | tee /dev/stderr) + yq -r '.rules[4]' | tee /dev/stderr) local actual=$(echo $object | yq -r '.resources[| index("leases")' | tee /dev/stderr) [ "${actual}" != null ] @@ -154,7 +154,7 @@ load _helpers #-------------------------------------------------------------------- # global.enablePodSecurityPolicies -@test "connectInject/ClusterRole: allows podsecuritypolicies access with global.enablePodSecurityPolicies=false" { +@test "connectInject/ClusterRole: no podsecuritypolicies access with global.enablePodSecurityPolicies=false" { cd `chart_dir` local actual=$(helm template \ -s templates/connect-inject-clusterrole.yaml \ @@ -162,7 +162,7 @@ load _helpers --set 'global.enablePodSecurityPolicies=false' \ . | tee /dev/stderr | yq -r '.rules | map(select(.resources[0] == "podsecuritypolicies")) | length' | tee /dev/stderr) - [ "${actual}" = "1" ] + [ "${actual}" = "0" ] } @test "connectInject/ClusterRole: allows podsecuritypolicies access with global.enablePodSecurityPolicies=true" { @@ -193,11 +193,14 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ . | tee /dev/stderr | - yq -r '.rules[6]' | tee /dev/stderr) + yq -r '.rules[5]' | tee /dev/stderr) local actual=$(echo $object | yq -r '.resources[0]' | tee /dev/stderr) [ "${actual}" = "mutatingwebhookconfigurations" ] @@ -217,52 +220,3 @@ load _helpers local actual=$(echo $object | yq -r '.verbs | index("watch")' | tee /dev/stderr) [ "${actual}" != null ] } - -#-------------------------------------------------------------------- -# openshift - -@test "connectInject/ClusterRole: adds permission to securitycontextconstraints for Openshift with global.openshift.enabled=true with default apiGateway Openshift SCC Name" { - cd `chart_dir` - local object=$(helm template \ - -s templates/connect-inject-clusterrole.yaml \ - --set 'global.openshift.enabled=true' \ - . | tee /dev/stderr | - yq '.rules[13].resourceNames | index("restricted-v2")' | tee /dev/stderr) - [ "${object}" == 0 ] -} - -@test "connectInject/ClusterRole: adds permission to securitycontextconstraints for Openshift with global.openshift.enabled=true and sets apiGateway Openshift SCC Name" { - cd `chart_dir` - local object=$(helm template \ - -s templates/connect-inject-clusterrole.yaml \ - --set 'global.openshift.enabled=true' \ - --set 'connectInject.apiGateway.managedGatewayClass.openshiftSCCName=fakescc' \ - . | tee /dev/stderr | - yq '.rules[13].resourceNames | index("fakescc")' | tee /dev/stderr) - [ "${object}" == 0 ] -} - -#-------------------------------------------------------------------- -# resource-apis - -@test "connectInject/ClusterRole: adds permission to mesh.consul.hashicorp.com with resource-apis in global.experiments" { - cd `chart_dir` - local object=$(helm template \ - -s templates/connect-inject-clusterrole.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments={resource-apis}' \ - . | tee /dev/stderr | - yq '.rules[4].apiGroups | index("mesh.consul.hashicorp.com")' | tee /dev/stderr) - [ "${object}" == 0 ] -} - -@test "connectInject/ClusterRole: adds permission to multicluster.consul.hashicorp.com with resource-apis in global.experiments" { - cd `chart_dir` - local object=$(helm template \ - -s templates/connect-inject-clusterrole.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments={resource-apis}' \ - . | tee /dev/stderr | - yq '.rules[6].apiGroups | index("multicluster.consul.hashicorp.com")' | tee /dev/stderr) - [ "${object}" == 0 ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 1e6397e39c..e7bd3d9138 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -211,19 +211,6 @@ load _helpers [ "${actual}" = "true" ] } -@test "connectInject/Deployment: metrics.enableTelemetryCollector can be configured" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.metrics.enableTelemetryCollector=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-enable-telemetry-collector=true"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} #-------------------------------------------------------------------- # consul and consul-dataplane images @@ -1461,10 +1448,7 @@ load _helpers -s templates/connect-inject-deployment.yaml \ --set 'connectInject.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) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -1881,6 +1865,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=test' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].command | any(contains("-enable-webhook-ca-update"))' | tee /dev/stderr) [ "${actual}" = "true" ] @@ -1987,7 +1974,7 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=connectinjectcarole' \ --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . [ "$status" -eq 1 ] - [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName" ]] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] } @test "connectInject/Deployment: fails if vault is enabled and global.secretsBackend.vault.connectInject.tlsCert.secretName is set but global.secretsBackend.vault.connectInjectRole and global.secretsBackend.vault.connectInject.caCert.secretName are not" { @@ -2004,7 +1991,7 @@ load _helpers --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . [ "$status" -eq 1 ] - [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName" ]] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] } @test "connectInject/Deployment: fails if vault is enabled and global.secretsBackend.vault.connectInject.caCert.secretName is set but global.secretsBackend.vault.connectInjectRole and global.secretsBackend.vault.connectInject.tlsCert.secretName are not" { @@ -2021,7 +2008,7 @@ load _helpers --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . [ "$status" -eq 1 ] - [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName" ]] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] } @test "connectInject/Deployment: vault tls annotations are set when tls is enabled" { @@ -2039,6 +2026,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=test' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq -r '.spec.template.metadata' | tee /dev/stderr) @@ -2112,6 +2102,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ @@ -2134,6 +2127,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'server.serverCert.secretName=pki_int/issue/test' \ --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ . | tee /dev/stderr | @@ -2163,6 +2159,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq '.spec.template.spec.volumes[] | select(.name == "certs")' | tee /dev/stderr) [ "${actual}" == "" ] @@ -2182,6 +2181,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "certs")' | tee /dev/stderr) [ "${actual}" == "" ] @@ -2220,12 +2222,7 @@ load _helpers --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) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -2639,30 +2636,6 @@ reservedNameTest() { [ "${actual}" = "true" ] } -@test "connectInject/Deployment: validates that externalServers.hosts is not set with an HCP-managed cluster's address" { - cd `chart_dir` - run helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'global.enabled=false' \ - --set 'connectInject.enabled=true' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=abc.aws.hashicorp.cloud' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . > /dev/stderr - - [ "$status" -eq 1 ] - - [[ "$output" =~ "global.cloud.enabled cannot be used in combination with an HCP-managed cluster address in externalServers.hosts. global.cloud.enabled is for linked self-managed clusters." ]] -} - @test "connectInject/Deployment: can provide a TLS server name for the sidecar-injector when global.cloud.enabled is set" { cd `chart_dir` local env=$(helm template \ @@ -2684,58 +2657,3 @@ reservedNameTest() { jq -r '. | select( .name == "CONSUL_TLS_SERVER_NAME").value' | tee /dev/stderr) [ "${actual}" = "server.dc1.consul" ] } - -#-------------------------------------------------------------------- -# resource-apis - -@test "connectInject/Deployment: resource-apis is not set by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) - - [ "${actual}" = "false" ] -} - -@test "connectInject/Deployment: -enable-resource-apis=true is set when global.experiments contains [\"resource-apis\"] " { - cd `chart_dir` - local actual=$(helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) - - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# v2tenancy - -@test "connectInject/Deployment: v2tenancy is not set by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-enable-v2tenancy=true"))' | tee /dev/stderr) - - [ "${actual}" = "false" ] -} - -@test "connectInject/Deployment: -enable-v2tenancy=true is set when global.experiments contains [\"resource-apis\", \"v2tenancy\"]" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'global.experiments[1]=v2tenancy' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-enable-v2tenancy=true"))' | tee /dev/stderr) - - [ "${actual}" = "true" ] -} diff --git a/charts/consul/test/unit/connect-inject-mutatingwebhookconfiguration.bats b/charts/consul/test/unit/connect-inject-mutatingwebhookconfiguration.bats index bc0876586c..81eda87875 100755 --- a/charts/consul/test/unit/connect-inject-mutatingwebhookconfiguration.bats +++ b/charts/consul/test/unit/connect-inject-mutatingwebhookconfiguration.bats @@ -60,7 +60,7 @@ load _helpers --set 'meshGateway.enabled=true' \ --set 'global.peering.enabled=true' \ . | tee /dev/stderr | - yq '.webhooks[12].name | contains("peeringacceptors.consul.hashicorp.com")' | tee /dev/stderr) + yq '.webhooks[11].name | contains("peeringacceptors.consul.hashicorp.com")' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(helm template \ -s templates/connect-inject-mutatingwebhookconfiguration.yaml \ @@ -69,6 +69,6 @@ load _helpers --set 'meshGateway.enabled=true' \ --set 'global.peering.enabled=true' \ . | tee /dev/stderr | - yq '.webhooks[13].name | contains("peeringdialers.consul.hashicorp.com")' | tee /dev/stderr) + yq '.webhooks[12].name | contains("peeringdialers.consul.hashicorp.com")' | tee /dev/stderr) [ "${actual}" = "true" ] } diff --git a/charts/consul/test/unit/crd-controlplanerequestlimits.bats b/charts/consul/test/unit/crd-controlplanerequestlimits.bats deleted file mode 100644 index ed98fc539f..0000000000 --- a/charts/consul/test/unit/crd-controlplanerequestlimits.bats +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "controlPlaneRequestLimit/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-controlplanerequestlimits.yaml \ - . | tee /dev/stderr | - yq -s 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "controlPlaneRequestLimit/CustomResourceDefinition: enabled with connectInject.enabled=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-controlplanerequestlimits.yaml \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - # The generated CRDs have "---" at the top which results in two objects - # being detected by yq, the first of which is null. We must therefore use - # yq -s so that length operates on both objects at once rather than - # individually, which would output false\ntrue and fail the test. - yq -s 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} diff --git a/charts/consul/test/unit/crd-exportedservices.bats b/charts/consul/test/unit/crd-exportedservices.bats index 235fe6bd24..1b8f4430b5 100644 --- a/charts/consul/test/unit/crd-exportedservices.bats +++ b/charts/consul/test/unit/crd-exportedservices.bats @@ -7,7 +7,7 @@ load _helpers local actual=$(helm template \ -s templates/crd-exportedservices.yaml \ . | tee /dev/stderr | - yq -s 'length > 0' | tee /dev/stderr) + yq 'length > 0' | tee /dev/stderr) [ "${actual}" = "true" ] } diff --git a/charts/consul/test/unit/crd-gatewayclassconfigs.bats b/charts/consul/test/unit/crd-gatewayclassconfigs.bats deleted file mode 100644 index 0228110b6b..0000000000 --- a/charts/consul/test/unit/crd-gatewayclassconfigs.bats +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "gatewayclassconfigs/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-gatewayclassconfigs.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewayclassconfigs/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-gatewayclassconfigs.yaml \ - --set 'connectInject.enabled=false' \ - . -} diff --git a/charts/consul/test/unit/crd-gatewayclasses-external.bats b/charts/consul/test/unit/crd-gatewayclasses-external.bats deleted file mode 100644 index a1a845a249..0000000000 --- a/charts/consul/test/unit/crd-gatewayclasses-external.bats +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "gatewayclasses/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-gatewayclasses-external.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewayclasses/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-gatewayclasses-external.yaml \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gatewayclasses/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-gatewayclasses-external.yaml \ - --set 'connectInject.apiGateway.manageExternalCRDs=false' \ - . -} diff --git a/charts/consul/test/unit/crd-gatewaypolicies.bats b/charts/consul/test/unit/crd-gatewaypolicies.bats deleted file mode 100644 index 2a40a8182e..0000000000 --- a/charts/consul/test/unit/crd-gatewaypolicies.bats +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "gatewaypolicies/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-gatewaypolicies.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewaypolicies/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-gatewaypolicies.yaml \ - --set 'connectInject.enabled=false' \ - . -} diff --git a/charts/consul/test/unit/crd-gateways-external.bats b/charts/consul/test/unit/crd-gateways-external.bats deleted file mode 100644 index 30b6d71630..0000000000 --- a/charts/consul/test/unit/crd-gateways-external.bats +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "gateways/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-gateways-external.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gateways/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-gateways-external.yaml \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gateways/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-gateways-external.yaml \ - --set 'connectInject.apiGateway.manageExternalCRDs=false' \ - . -} diff --git a/charts/consul/test/unit/crd-grpcroutes-external.bats b/charts/consul/test/unit/crd-grpcroutes-external.bats deleted file mode 100644 index 625648e326..0000000000 --- a/charts/consul/test/unit/crd-grpcroutes-external.bats +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "grpcroutes/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-grpcroutes-external.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "grpcroutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-grpcroutes-external.yaml \ - --set 'connectInject.enabled=false' \ - . -} - -@test "grpcroutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-grpcroutes-external.yaml \ - --set 'connectInject.apiGateway.manageExternalCRDs=false' \ - . -} diff --git a/charts/consul/test/unit/crd-httproutes-external.bats b/charts/consul/test/unit/crd-httproutes-external.bats deleted file mode 100644 index e35bebc3e4..0000000000 --- a/charts/consul/test/unit/crd-httproutes-external.bats +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "httproutes/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-httproutes-external.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "httproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-httproutes-external.yaml \ - --set 'connectInject.enabled=false' \ - . -} - -@test "httproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-httproutes-external.yaml \ - --set 'connectInject.apiGateway.manageExternalCRDs=false' \ - . -} diff --git a/charts/consul/test/unit/crd-meshservices.bats b/charts/consul/test/unit/crd-meshservices.bats deleted file mode 100644 index c1ee806ad4..0000000000 --- a/charts/consul/test/unit/crd-meshservices.bats +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "meshservices/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-meshservices.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "meshservices/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-meshservices.yaml \ - --set 'connectInject.enabled=false' \ - . -} - diff --git a/charts/consul/test/unit/crd-routeauthfilters.bats b/charts/consul/test/unit/crd-routeauthfilters.bats deleted file mode 100644 index d4af62dd5c..0000000000 --- a/charts/consul/test/unit/crd-routeauthfilters.bats +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "routeauth-filters/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-routeauthfilters.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "routeauth-filter/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-routeauthfilters.yaml \ - --set 'connectInject.enabled=false' \ - . -} diff --git a/charts/consul/test/unit/crd-tcproutes-external.bats b/charts/consul/test/unit/crd-tcproutes-external.bats deleted file mode 100644 index c91eb15e6b..0000000000 --- a/charts/consul/test/unit/crd-tcproutes-external.bats +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "tcproutes/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-tcproutes-external.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "tcproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-tcproutes-external.yaml \ - --set 'connectInject.enabled=false' \ - . -} - -@test "tcproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-tcproutes-external.yaml \ - --set 'connectInject.apiGateway.manageExternalCRDs=false' \ - . -} - -@test "tcproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false and connectInject.apiGateway.manageNonStandardCRDs=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-tcproutes-external.yaml \ - --set 'connectInject.apiGateway.manageExternalCRDs=false' \ - --set 'connectInject.apiGateway.manageNonStandardCRDs=false' \ - . -} - -@test "tcproutes/CustomResourceDefinition: enabled with connectInject.apiGateway.manageNonStandardCRDs=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-tcproutes-external.yaml \ - --set 'connectInject.apiGateway.manageNonStandardCRDs=true' \ - . | tee /dev/stderr | - yq -s 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} diff --git a/charts/consul/test/unit/crd-tlsroutes-external.bats b/charts/consul/test/unit/crd-tlsroutes-external.bats deleted file mode 100644 index 88b37521f2..0000000000 --- a/charts/consul/test/unit/crd-tlsroutes-external.bats +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "tlsroutes/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-tlsroutes-external.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "tlsroutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-tlsroutes-external.yaml \ - --set 'connectInject.enabled=false' \ - . -} - -@test "tlsroutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-tlsroutes-external.yaml \ - --set 'connectInject.apiGateway.manageExternalCRDs=false' \ - . -} diff --git a/charts/consul/test/unit/crd-udproutes-external.bats b/charts/consul/test/unit/crd-udproutes-external.bats deleted file mode 100644 index 6693e67b2d..0000000000 --- a/charts/consul/test/unit/crd-udproutes-external.bats +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "udproutes/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-udproutes-external.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "udproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-udproutes-external.yaml \ - --set 'connectInject.enabled=false' \ - . -} - -@test "udproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-udproutes-external.yaml \ - --set 'connectInject.apiGateway.manageExternalCRDs=false' \ - . -} diff --git a/charts/consul/test/unit/gateway-cleanup-clusterrole.bats b/charts/consul/test/unit/gateway-cleanup-clusterrole.bats deleted file mode 100644 index c672ac5593..0000000000 --- a/charts/consul/test/unit/gateway-cleanup-clusterrole.bats +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-cleanup-clusterrole.yaml - -@test "gatewaycleanup/ClusterRole: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewaycleanup/ClusterRole: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gatewaycleanup/ClusterRole: can use podsecuritypolicies with global.enablePodSecurityPolicy=true" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set "global.enablePodSecurityPolicies=true" \ - . | tee /dev/stderr | - yq '.rules[] | select((.resources[0] == "podsecuritypolicies") and (.verbs[0] == "use")) | length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - diff --git a/charts/consul/test/unit/gateway-cleanup-clusterrolebinding.bats b/charts/consul/test/unit/gateway-cleanup-clusterrolebinding.bats deleted file mode 100644 index a6e4af5d2c..0000000000 --- a/charts/consul/test/unit/gateway-cleanup-clusterrolebinding.bats +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-cleanup-clusterrolebinding.yaml - -@test "gatewaycleanup/ClusterRoleBinding: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewaycleanup/ClusterRoleBinding: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - diff --git a/charts/consul/test/unit/gateway-cleanup-job.bats b/charts/consul/test/unit/gateway-cleanup-job.bats deleted file mode 100644 index 26c3d08e97..0000000000 --- a/charts/consul/test/unit/gateway-cleanup-job.bats +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-cleanup-job.yaml - -@test "gatewaycleanup/Job: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewaycleanup/Job: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - - -#-------------------------------------------------------------------- -# annotations - -@test "gatewaycleanup/Job: no annotations defined by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) - [ "${actual}" = "{}" ] -} diff --git a/charts/consul/test/unit/gateway-cleanup-podsecuritypolicy.bats b/charts/consul/test/unit/gateway-cleanup-podsecuritypolicy.bats deleted file mode 100644 index 66974da2fd..0000000000 --- a/charts/consul/test/unit/gateway-cleanup-podsecuritypolicy.bats +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-cleanup-podsecuritypolicy.yaml - -@test "gatewaycleanup/PodSecurityPolicy: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gatewaycleanup/PodSecurityPolicy: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gatewaycleanup/PodSecurityPolicy: disabled with global.enablePodSecurityPolicies=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'global.enablePodSecurityPolicies=false' \ - . -} - - -@test "gatewaycleanup/PodSecurityPolicy: enabled with connectInject.enabled=true and global.enablePodSecurityPolicies=true" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'global.enablePodSecurityPolicies=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} diff --git a/charts/consul/test/unit/gateway-cleanup-serviceaccount.bats b/charts/consul/test/unit/gateway-cleanup-serviceaccount.bats deleted file mode 100644 index 50d01b99e9..0000000000 --- a/charts/consul/test/unit/gateway-cleanup-serviceaccount.bats +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-cleanup-serviceaccount.yaml - -@test "gatewaycleanup/ServiceAccount: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewaycleanup/ServiceAccount: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - diff --git a/charts/consul/test/unit/gateway-resources-clusterrole.bats b/charts/consul/test/unit/gateway-resources-clusterrole.bats deleted file mode 100644 index 152209a1b5..0000000000 --- a/charts/consul/test/unit/gateway-resources-clusterrole.bats +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-resources-clusterrole.yaml - -@test "gatewayresources/ClusterRole: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewayresources/ClusterRole: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gatewayresources/ClusterRole: can use podsecuritypolicies with global.enablePodSecurityPolicy=true" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set "global.enablePodSecurityPolicies=true" \ - . | tee /dev/stderr | - yq '.rules[] | select((.resources[0] == "podsecuritypolicies") and (.verbs[0] == "use")) | length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - diff --git a/charts/consul/test/unit/gateway-resources-clusterrolebinding.bats b/charts/consul/test/unit/gateway-resources-clusterrolebinding.bats deleted file mode 100644 index efc1429e20..0000000000 --- a/charts/consul/test/unit/gateway-resources-clusterrolebinding.bats +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-resources-clusterrolebinding.yaml - -@test "gatewayresources/ClusterRoleBinding: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewayresources/ClusterRoleBinding: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - diff --git a/charts/consul/test/unit/gateway-resources-configmap.bats b/charts/consul/test/unit/gateway-resources-configmap.bats deleted file mode 100644 index e827644792..0000000000 --- a/charts/consul/test/unit/gateway-resources-configmap.bats +++ /dev/null @@ -1,258 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-resources-configmap.yaml - -@test "gateway-resources/ConfigMap: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gateway-resources/ConfigMap: enabled with connectInject.enabled=true" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gateway-resources/ConfigMap: contains resources configuration as JSON" { - cd `chart_dir` - local resources=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'connectInject.apiGateway.managedGatewayClass.resources.requests.memory=200Mi' \ - --set 'connectInject.apiGateway.managedGatewayClass.resources.requests.cpu=200m' \ - --set 'connectInject.apiGateway.managedGatewayClass.resources.limits.memory=220Mi' \ - --set 'connectInject.apiGateway.managedGatewayClass.resources.limits.cpu=220m' \ - . | tee /dev/stderr | - yq '.data["resources.json"] | fromjson' | tee /dev/stderr) - - local actual=$(echo $resources | jq -r '.requests.memory') - [ $actual = '200Mi' ] - - local actual=$(echo $resources | jq -r '.requests.cpu') - [ $actual = '200m' ] - - local actual=$(echo $resources | jq -r '.limits.memory') - [ $actual = '220Mi' ] - - local actual=$(echo $resources | jq -r '.limits.cpu') - [ $actual = '220m' ] -} - -@test "gateway-resources/ConfigMap: does not contain config.yaml resources without .global.experiments equal to resource-apis" { - cd `chart_dir` - local resources=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq '.data["config.yaml"]' | tee /dev/stderr) - [ $resources = null ] - -} - -@test "gateway-resources/ConfigMap: contains config.yaml resources with .global.experiments equal to resource-apis" { - cd `chart_dir` - local resources=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq '.data["config.yaml"]' | tee /dev/stderr) - - [ "$resources" != null ] -} - -#-------------------------------------------------------------------- -# Mesh Gateway logLevel configuration - -@test "gateway-resources/ConfigMap: Mesh Gateway logLevel default configuration" { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.container.consul.logging.level') - [ "${actual}" = 'info' ] - - local actual=$(echo "$config" | yq -r '.initContainer.consul.logging.level') - [ "${actual}" = 'info' ] -} - - -@test "gateway-resources/ConfigMap: Mesh Gateway logLevel custom global configuration" { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=debug' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.container.consul.logging.level') - [ "${actual}" = 'debug' ] - - local actual=$(echo "$config" | yq -r '.initContainer.consul.logging.level') - [ "${actual}" = 'debug' ] -} - -@test "gateway-resources/ConfigMap: Mesh Gateway logLevel custom meshGateway configuration" { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'meshGateway.logLevel=debug' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.container.consul.logging.level') - [ "${actual}" = 'debug' ] - - local actual=$(echo "$config" | yq -r '.initContainer.consul.logging.level') - [ "${actual}" = 'debug' ] -} - -@test "gateway-resources/ConfigMap: Mesh Gateway logLevel custom meshGateway configuration overrides global configuration" { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - --set 'meshGateway.logLevel=debug' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.container.consul.logging.level') - [ "${actual}" = 'debug' ] - - local actual=$(echo "$config" | yq -r '.initContainer.consul.logging.level') - [ "${actual}" = 'debug' ] -} - -#-------------------------------------------------------------------- -# Mesh Gateway Extra Labels configuration - -@test "gateway-resources/ConfigMap: Mesh Gateway gets Extra Labels when set" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.extraLabels.foo'='bar' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment.labels.set.foo' | tee /dev/stderr - ) - [ "$actual" = 'bar' ] -} - -#-------------------------------------------------------------------- -# Mesh Gateway annotations configuration - -@test "gateway-resources/ConfigMap: Mesh Gateway gets annotations when set" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'meshGateway.annotations.foo'='bar' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment.annotations.set.foo' | tee /dev/stderr - ) - [ "$actual" = 'bar' ] -} - -#-------------------------------------------------------------------- -# Mesh Gateway WAN Address configuration - -@test "gateway-resources/ConfigMap: Mesh Gateway WAN Address default annotations" { - cd `chart_dir` - local annotations=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.meshGateways[0].metadata.annotations' | tee /dev/stderr) - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') - [ "${actual}" = 'Service' ] - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-port"]') - [ "${actual}" = '443' ] - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-static"]') - [ "${actual}" = '' ] -} - -@test "gateway-resources/ConfigMap: Mesh Gateway WAN Address NodePort annotations" { - cd `chart_dir` - local annotations=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'meshGateway.wanAddress.source=Service' \ - --set 'meshGateway.service.type=NodePort' \ - --set 'meshGateway.service.nodePort=30000' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.meshGateways[0].metadata.annotations' | tee /dev/stderr) - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') - [ "${actual}" = 'Service' ] - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-port"]') - [ "${actual}" = '30000' ] - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-static"]') - [ "${actual}" = '' ] -} - -@test "gateway-resources/ConfigMap: Mesh Gateway WAN Address static configuration" { - cd `chart_dir` - local annotations=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'meshGateway.wanAddress.source=Static' \ - --set 'meshGateway.wanAddress.static=127.0.0.1' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.meshGateways[0].metadata.annotations' | tee /dev/stderr) - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') - [ "${actual}" = 'Static' ] - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-port"]') - [ "${actual}" = '443' ] - - local actual=$(echo "$annotations" | jq -r '.["consul.hashicorp.com/gateway-wan-address-static"]') - [ "${actual}" = '127.0.0.1' ] -} - diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats deleted file mode 100644 index e38397231b..0000000000 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-resources-job.yaml - -@test "gatewayresources/Job: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewayresources/Job: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gatewayresources/Job: imageK8S set properly" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set 'global.imageK8S=foo' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].image == "foo"' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -#-------------------------------------------------------------------- -# fallback configuration -# to be removed in 1.17 (t-eckert 2023-05-23) - -@test "gatewayresources/Job: fallback configuration is used when apiGateway.enabled is true" { - cd `chart_dir` - local spec=$(helm template \ - -s $target \ - --set 'apiGateway.enabled=true' \ - --set 'apiGateway.image=testing' \ - --set 'apiGateway.managedGatewayClass.nodeSelector=foo: bar' \ - --set 'apiGateway.managedGatewayClass.tolerations=- key: bar' \ - --set 'apiGateway.managedGatewayClass.copyAnnotations.service.annotations=- bingo' \ - --set 'apiGateway.managedGatewayClass.serviceType=LoadBalancer' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) - - local actual=$(echo "$spec" | jq '.[9] | ."-node-selector=foo"') - [ "${actual}" = "\"bar\"" ] - - local actual=$(echo "$spec" | jq '.[10] | ."-tolerations=- key"') - [ "${actual}" = "\"bar\"" ] - - local actual=$(echo "$spec" | jq '.[11]') - [ "${actual}" = "\"-service-annotations=- bingo\"" ] -} - -#-------------------------------------------------------------------- -# configuration - -@test "gatewayresources/Job: default configuration" { - cd `chart_dir` - local spec=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) - - local actual=$(echo "$spec" | jq 'any(index("-deployment-default-instances=1"))') - [ "${actual}" = "true" ] - - local actual=$(echo "$spec" | jq 'any(index("-deployment-max-instances=1"))') - [ "${actual}" = "true" ] - - local actual=$(echo "$spec" | jq 'any(index("-deployment-min-instances=1"))') - [ "${actual}" = "true" ] - - local actual=$(echo "$spec" | jq 'any(index("-service-type=LoadBalancer"))') - [ "${actual}" = "true" ] -} - -@test "apiGateway/GatewayClassConfig: custom configuration" { - cd `chart_dir` - local spec=$(helm template \ - -s $target \ - --set 'connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances=2' \ - --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' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) - - local actual=$(echo "$spec" | jq 'any(index("-deployment-default-instances=2"))') - [ "${actual}" = "true" ] - - local actual=$(echo "$spec" | jq 'any(index("-deployment-max-instances=3"))') - [ "${actual}" = "true" ] - - local actual=$(echo "$spec" | jq 'any(index("-deployment-min-instances=1"))') - [ "${actual}" = "true" ] - - 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" | jq '.[17]') - [ "${actual}" = "\"-service-type=Foo\"" ] -} - -@test "apiGateway/GatewayClassConfig: custom configuration openshift enabled" { - cd `chart_dir` - local spec=$(helm template \ - -s $target \ - --set 'global.openshift.enabled=true' \ - --set 'connectInject.apiGateway.managedGatewayClass.openshiftSCCName=hello' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) - - local actual=$(echo "$spec" | jq '.[13]') - [ "${actual}" = "\"-openshift-scc-name=hello\"" ] -} - - -#-------------------------------------------------------------------- -# annotations - -@test "gatewayresources/Job: no annotations defined by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) - [ "${actual}" = "{}" ] -} diff --git a/charts/consul/test/unit/gateway-resources-podsecuritypolicy.bats b/charts/consul/test/unit/gateway-resources-podsecuritypolicy.bats deleted file mode 100644 index 81818c525a..0000000000 --- a/charts/consul/test/unit/gateway-resources-podsecuritypolicy.bats +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-resources-podsecuritypolicy.yaml - -@test "gatewayresources/PodSecurityPolicy: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gatewayresources/PodSecurityPolicy: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - -@test "gatewayresources/PodSecurityPolicy: disabled with global.enablePodSecurityPolicies=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'global.enablePodSecurityPolicies=false' \ - . -} - - -@test "gatewayresources/PodSecurityPolicy: enabled with connectInject.enabled=true and global.enablePodSecurityPolicies=true" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - --set 'connectInject.enabled=true' \ - --set 'global.enablePodSecurityPolicies=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} diff --git a/charts/consul/test/unit/gateway-resources-serviceaccount.bats b/charts/consul/test/unit/gateway-resources-serviceaccount.bats deleted file mode 100644 index 90011e226b..0000000000 --- a/charts/consul/test/unit/gateway-resources-serviceaccount.bats +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-resources-serviceaccount.yaml - -@test "gatewayresources/ServiceAccount: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s $target \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewayresources/ServiceAccount: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s $target \ - --set 'connectInject.enabled=false' \ - . -} - diff --git a/charts/consul/test/unit/helpers.bats b/charts/consul/test/unit/helpers.bats index 20772788f8..4245b519c4 100644 --- a/charts/consul/test/unit/helpers.bats +++ b/charts/consul/test/unit/helpers.bats @@ -115,7 +115,7 @@ load _helpers @test "helper/namespace: used everywhere" { cd `chart_dir` # Grep for files that don't have 'namespace: ' in them - local actual=$(grep -L 'namespace: ' templates/*.yaml | grep -v 'crd' | grep -v 'clusterrole' | grep -v 'gateway-gateway' | tee /dev/stderr ) + local actual=$(grep -L 'namespace: ' templates/*.yaml | grep -v 'crd' | grep -v 'clusterrole' | grep -v 'api-gateway-gateway' | tee /dev/stderr ) [ "${actual}" = '' ] } @@ -327,142 +327,3 @@ load _helpers actual=$(echo $object | jq '.volumeMounts[] | select(.name == "consul-ca-cert")') [ "${actual}" = "" ] } - -#-------------------------------------------------------------------- -# consul.validateResourceAPIs -# These tests use test-runner.yaml to test the -# consul.validateResourceAPIs helper since we need an existing template - -@test "connectInject/Deployment: fails if resource-apis is set and peering is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.tls.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.peering.enabled=true' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set, v2tenancy is unset, and admin partitions are enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'global.adminPartitions.enabled=true' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.experiments.v2tenancy must also be set to support global.adminPartitions.enabled." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and federation is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.tls.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.federation.enabled=true' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.federation.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and cloud is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.resourceId.secretName=hello' \ - --set 'global.cloud.resourceId.secretKey=hello' \ - --set 'global.cloud.clientId.secretName=hello' \ - --set 'global.cloud.clientId.secretKey=hello' \ - --set 'global.cloud.clientSecret.secretName=hello' \ - --set 'global.cloud.clientSecret.secretKey=hello' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.cloud.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and client is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'client.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, client.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and ui is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, ui.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and syncCatalog is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'syncCatalog.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, syncCatalog.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and ingressGateways is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'ingressGateways.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, ingressGateways.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and terminatingGateways is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'terminatingGateways.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, terminatingGateways.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and apiGateway is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'apiGateway.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, apiGateway.enabled is currently unsupported." ]] -} diff --git a/charts/consul/test/unit/ingress-gateways-deployment.bats b/charts/consul/test/unit/ingress-gateways-deployment.bats index 96f6d7ee3c..be617ac538 100644 --- a/charts/consul/test/unit/ingress-gateways-deployment.bats +++ b/charts/consul/test/unit/ingress-gateways-deployment.bats @@ -799,7 +799,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -s -r '.[0].spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "6" ] + [ "${actual}" = "5" ] } @test "ingressGateways/Deployment: extra annotations can be set through defaults" { @@ -814,7 +814,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "8" ] + [ "${actual}" = "7" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -836,7 +836,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "8" ] + [ "${actual}" = "7" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -859,7 +859,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "9" ] + [ "${actual}" = "8" ] local actual=$(echo $object | yq -r '.defaultkey' | tee /dev/stderr) [ "${actual}" = "defaultvalue" ] @@ -1146,17 +1146,7 @@ key2: value2' \ --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") | - del(."consul.hashicorp.com/gateway-wan-address-source") | - del(."consul.hashicorp.com/gateway-wan-port") | - del(."vconsul.hashicorp.com/gateway-wan-address-source") | - del(."consul.hashicorp.com/gateway-consul-service-name") | - del(."consul.hashicorp.com/gateway-kind")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/gateway-wan-port") | del(."vconsul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/gateway-consul-service-name") | del(."consul.hashicorp.com/gateway-kind")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/mesh-gateway-deployment.bats b/charts/consul/test/unit/mesh-gateway-deployment.bats index afde4976a7..8e08463c43 100755 --- a/charts/consul/test/unit/mesh-gateway-deployment.bats +++ b/charts/consul/test/unit/mesh-gateway-deployment.bats @@ -44,7 +44,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "8" ] + [ "${actual}" = "7" ] } @test "meshGateway/Deployment: extra annotations can be set" { @@ -57,7 +57,7 @@ load _helpers key2: value2' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "10" ] + [ "${actual}" = "9" ] } #-------------------------------------------------------------------- @@ -1415,18 +1415,7 @@ key2: value2' \ --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") | - del(."consul.hashicorp.com/gateway-kind") | - del(."consul.hashicorp.com/gateway-wan-address-source") | - del(."consul.hashicorp.com/mesh-gateway-container-port") | - del(."consul.hashicorp.com/gateway-wan-address-static") | - del(."consul.hashicorp.com/gateway-wan-port") | - del(."consul.hashicorp.com/gateway-consul-service-name")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-kind") | del(."consul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/mesh-gateway-container-port") | del(."consul.hashicorp.com/gateway-wan-address-static") | del(."consul.hashicorp.com/gateway-wan-port") | del(."consul.hashicorp.com/gateway-consul-service-name")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index 745e23adfe..12912416f0 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -58,7 +58,9 @@ load _helpers cd `chart_dir` assert_empty helm template \ -s templates/partition-init-job.yaml \ - --set 'global.adminPartitions.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'server.enabled=true' \ . } @@ -109,27 +111,6 @@ load _helpers [ "${actual}" = "5s" ] } -#-------------------------------------------------------------------- -# v2tenancy experiment - -@test "partitionInit/Job: -enable-v2tenancy=true is set when global.experiments contains [\"resource-apis\", \"v2tenancy\"]" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/partition-init-job.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'server.enabled=false' \ - --set 'global.adminPartitions.name=bar' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=foo' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'global.experiments[1]=v2tenancy' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-enable-v2tenancy=true"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - #-------------------------------------------------------------------- # global.tls.enabled @@ -654,15 +635,7 @@ reservedNameTest() { --set 'global.secretsBackend.vault.consulCARole=carole' \ --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ . | 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/agent-pre-populate-only") | - del(."vault.hashicorp.com/role") | - del(."vault.hashicorp.com/agent-inject-secret-serverca.crt") | - del(."vault.hashicorp.com/agent-inject-template-serverca.crt")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/agent-pre-populate-only") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-inject-secret-serverca.crt") | del(."vault.hashicorp.com/agent-inject-template-serverca.crt")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -1044,4 +1017,4 @@ reservedNameTest() { local actualTemplateBaz=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.baz' | tee /dev/stderr) [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] -} \ No newline at end of file +} diff --git a/charts/consul/test/unit/server-acl-init-cleanup-job.bats b/charts/consul/test/unit/server-acl-init-cleanup-job.bats index f6a67a1893..8743ea4a8d 100644 --- a/charts/consul/test/unit/server-acl-init-cleanup-job.bats +++ b/charts/consul/test/unit/server-acl-init-cleanup-job.bats @@ -236,11 +236,7 @@ load _helpers -s templates/server-acl-init-cleanup-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 2205192ad2..852863a2b0 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -650,19 +650,7 @@ load _helpers yq -r '.spec.template' | tee /dev/stderr) # Check annotations - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate"]' | tee /dev/stderr) - [ "${actual}" = "true" ] - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) - [ "${actual}" = "false" ] - - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-enable"]' | tee /dev/stderr) - [ "${actual}" = "true" ] - - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-listener-port"]' | tee /dev/stderr) - [ "${actual}" = "8200" ] - - actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-enable-quit"]' | tee /dev/stderr) [ "${actual}" = "true" ] local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) @@ -671,13 +659,15 @@ load _helpers local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) [ "${actual}" = "aclrole" ] - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-secrets-backend=vault"))') - [ "${actual}" = "true" ] + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-bootstrap-token"]' | tee /dev/stderr) + [ "${actual}" = "foo" ] - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=foo"))') - [ "${actual}" = "true" ] + local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-bootstrap-token"]' | tee /dev/stderr) + local expected=$'{{- with secret \"foo\" -}}\n{{- .Data.data.bar -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=bar"))') + # Check that the bootstrap token flag is set to the path of the Vault secret. + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-file=/vault/secrets/bootstrap-token"))') [ "${actual}" = "true" ] # Check that no (secret) volumes are not attached @@ -782,31 +772,20 @@ load _helpers yq -r '.spec.template' | tee /dev/stderr) # Check annotations - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate"]' | tee /dev/stderr) - [ "${actual}" = "true" ] - - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) - [ "${actual}" = "false" ] - - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-enable"]' | tee /dev/stderr) - [ "${actual}" = "true" ] - - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-cache-listener-port"]' | tee /dev/stderr) - [ "${actual}" = "8200" ] - - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-enable-quit"]' | tee /dev/stderr) + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-pre-populate-only"]' | tee /dev/stderr) [ "${actual}" = "true" ] - - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) [ "${actual}" = "true" ] - - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) [ "${actual}" = "aclrole" ] - - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr) + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr) [ "${actual}" = "foo" ] - - local actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr) + 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 -}}' ] # Check that the consul-ca-cert volume is not attached @@ -992,14 +971,12 @@ load _helpers local expected=$'{{- with secret \"/vault/replication\" -}}\n{{- .Data.data.token -}}\n{{- end -}}' [ "${actual}" = "${expected}" ] - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-secrets-backend=vault"))') - [ "${actual}" = "true" ] - - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=/vault/bootstrap"))') - [ "${actual}" = "true" ] + local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-inject-secret-bootstrap-token"') + [ "${actual}" = "/vault/bootstrap" ] - local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=token"))') - [ "${actual}" = "true" ] + local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-inject-template-bootstrap-token"') + local expected=$'{{- with secret \"/vault/bootstrap\" -}}\n{{- .Data.data.token -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] # Check that replication token Kubernetes secret volumes and volumeMounts are not attached. local actual=$(echo $object | jq -r '.spec.volumes') @@ -1008,9 +985,12 @@ load _helpers local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").volumeMounts') [ "${actual}" = "null" ] - # Replication token file is passed. + # Check that the replication and bootstrap token flags are set to the path of the Vault secret. local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-acl-replication-token-file=/vault/secrets/replication-token"))') [ "${actual}" = "true" ] + + local actual=$(echo $object | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-file=/vault/secrets/bootstrap-token"))') + [ "${actual}" = "true" ] } #-------------------------------------------------------------------- @@ -1063,7 +1043,7 @@ load _helpers #-------------------------------------------------------------------- # Vault agent annotations -@test "serverACLInit/Job: default vault agent annotations" { +@test "serverACLInit/Job: no vault agent annotations defined by default" { cd `chart_dir` local actual=$(helm template \ -s templates/server-acl-init-job.yaml \ @@ -1077,22 +1057,8 @@ load _helpers --set 'global.secretsBackend.vault.consulCARole=carole' \ --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ . | tee /dev/stderr | - yq -r .spec.template.metadata.annotations | tee /dev/stderr) - - local expected=$(echo '{ - "consul.hashicorp.com/connect-inject": "false", - "consul.hashicorp.com/mesh-inject": "false", - "vault.hashicorp.com/agent-inject": "true", - "vault.hashicorp.com/agent-pre-populate": "true", - "vault.hashicorp.com/agent-pre-populate-only": "false", - "vault.hashicorp.com/agent-cache-enable": "true", - "vault.hashicorp.com/agent-cache-listener-port": "8200", - "vault.hashicorp.com/agent-enable-quit": "true", - "vault.hashicorp.com/role": "aclrole" - }' | tee /dev/stderr) - - local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') - [ "$equal" = "true" ] + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/agent-pre-populate-only") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-inject-secret-bootstrap-token") | del(."vault.hashicorp.com/agent-inject-template-bootstrap-token")' | tee /dev/stderr) + [ "${actual}" = "{}" ] } @test "serverACLInit/Job: vault agent annotations can be set" { @@ -1931,23 +1897,55 @@ load _helpers [[ "$output" =~ "both global.acls.bootstrapToken.secretKey and global.acls.bootstrapToken.secretName must be set if one of them is provided" ]] } -@test "serverACLInit/Job: bootstrap token secret is passed when acls.bootstrapToken.secretKey and secretName are set" { +@test "serverACLInit/Job: -bootstrap-token-file is not set by default" { + cd `chart_dir` + local object=$(helm template \ + -s templates/server-acl-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr) + + # Test the flag is not set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) + [ "${actual}" = "false" ] + + # Test the volume doesn't exist + local actual=$(echo "$object" | + yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) + [ "${actual}" = "true" ] + + # Test the volume mount doesn't exist + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "serverACLInit/Job: -bootstrap-token-file is set when acls.bootstrapToken.secretKey and secretName are set" { cd `chart_dir` local object=$(helm template \ -s templates/server-acl-init-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ --set 'global.acls.bootstrapToken.secretName=name' \ --set 'global.acls.bootstrapToken.secretKey=key' \ - . | yq .spec.template | tee /dev/stderr) + . | tee /dev/stderr) + + # Test the -bootstrap-token-file flag is set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file=/consul/acl/tokens/bootstrap-token"))' | tee /dev/stderr) + [ "${actual}" = "true" ] - local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=name"))') + # Test the volume exists + local actual=$(echo "$object" | + yq '.spec.template.spec.volumes | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) [ "${actual}" = "true" ] - local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=key"))') + # Test the volume mount exists + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].volumeMounts | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) [ "${actual}" = "true" ] } -@test "serverACLInit/Job: bootstrap token secret is passed when both acl.bootstrapToken and acls.replicationToken are set" { +@test "serverACLInit/Job: -bootstrap-token-file is preferred when both acls.bootstrapToken and acls.replicationToken are set" { cd `chart_dir` local object=$(helm template \ -s templates/server-acl-init-job.yaml \ @@ -1956,12 +1954,21 @@ load _helpers --set 'global.acls.bootstrapToken.secretKey=key' \ --set 'global.acls.replicationToken.secretName=replication' \ --set 'global.acls.replicationToken.secretKey=token' \ - . | yq .spec.template | tee /dev/stderr) + . | tee /dev/stderr) - local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-name=name"))') + # Test the -bootstrap-token-file flag is set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file=/consul/acl/tokens/bootstrap-token"))' | tee /dev/stderr) [ "${actual}" = "true" ] - local actual=$(echo "$object" | jq -r '.spec.containers[] | select(.name=="server-acl-init-job").command | any(contains("-bootstrap-token-secret-key=key"))') + # Test the volume exists + local actual=$(echo "$object" | + yq '.spec.template.spec.volumes | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) + [ "${actual}" = "true" ] + + # Test the volume mount exists + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].volumeMounts | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) [ "${actual}" = "true" ] } @@ -2357,11 +2364,7 @@ load _helpers -s templates/server-acl-init-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -2411,36 +2414,3 @@ load _helpers yq -r '.spec.template.metadata.annotations["argocd.argoproj.io/hook-delete-policy"]' | tee /dev/stderr) [ "${actual}" = null ] } - -#-------------------------------------------------------------------- -# resource-apis - -@test "serverACLInit/Job: resource-apis is not set by default" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-acl-init-job.yaml \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) - - local actual=$(echo $object | - yq 'any(contains("-enable-resource-apis"))' | tee /dev/stderr) - [ "${actual}" = "false" ] -} - -@test "serverACLInit/Job: -enable-resource-apis=true is set when global.experiments contains [\"resource-apis\"] " { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-acl-init-job.yaml \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) - - local actual=$(echo $object | - yq 'any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 53f20a91f6..a80a093c29 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -38,6 +38,16 @@ load _helpers . } +@test "server/ConfigMap: extraConfig is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/server-config-configmap.yaml \ + --set 'server.extraConfig="{\"hello\": \"world\"}"' \ + . | tee /dev/stderr | + yq '.data["extra-from-values.json"] | match("world") | length' | tee /dev/stderr) + [ ! -z "${actual}" ] +} + #-------------------------------------------------------------------- # retry-join @@ -1033,97 +1043,7 @@ load _helpers [ "${actual}" = "true" ] } -#-------------------------------------------------------------------- -# server.limits.requestLimits - -@test "server/ConfigMap: server.limits.requestLimits.mode is disabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .limits.request_limits.mode | tee /dev/stderr) - - [ "${actual}" = "disabled" ] -} - -@test "server/ConfigMap: server.limits.requestLimits.mode accepts disabled, permissive, and enforce" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.limits.requestLimits.mode=disabled' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .limits.request_limits.mode | tee /dev/stderr) - - [ "${actual}" = "disabled" ] - - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.limits.requestLimits.mode=permissive' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .limits.request_limits.mode | tee /dev/stderr) - - [ "${actual}" = "permissive" ] - - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.limits.requestLimits.mode=enforce' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .limits.request_limits.mode | tee /dev/stderr) - - [ "${actual}" = "enforce" ] -} - -@test "server/ConfigMap: server.limits.requestLimits.mode errors with value other than disabled, permissive, and enforce" { - cd `chart_dir` - run helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.limits.requestLimits.mode=notvalid' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "server.limits.requestLimits.mode must be one of the following values: disabled, permissive, and enforce" ]] -} -@test "server/ConfigMap: server.limits.request_limits.read_rate is -1 by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .limits.request_limits.read_rate | tee /dev/stderr) - - [ "${actual}" = "-1" ] -} - -@test "server/ConfigMap: server.limits.request_limits.read_rate is set properly when specified " { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.limits.requestLimits.readRate=100' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .limits.request_limits.read_rate | tee /dev/stderr) - - [ "${actual}" = "100" ] -} - -@test "server/ConfigMap: server.limits.request_limits.write_rate is -1 by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .limits.request_limits.write_rate | tee /dev/stderr) - - [ "${actual}" = "-1" ] -} - -@test "server/ConfigMap: server.limits.request_limits.write_rate is set properly when specified " { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.limits.requestLimits.writeRate=100' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .limits.request_limits.write_rate | tee /dev/stderr) - - [ "${actual}" = "100" ] -} #-------------------------------------------------------------------- # server.auditLogs @@ -1307,62 +1227,4 @@ load _helpers yq -r '.data["server.json"]' | jq -r .log_level | tee /dev/stderr) [ "${configmap}" = "DEBUG" ] -} - -#-------------------------------------------------------------------- -# server.autopilot.min_quorum - -@test "server/ConfigMap: autopilot.min_quorum=1 when replicas=1" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.replicas=1' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) - - [ "${actual}" = "1" ] -} - -@test "server/ConfigMap: autopilot.min_quorum=2 when replicas=2" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.replicas=2' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) - - [ "${actual}" = "2" ] -} - -@test "server/ConfigMap: autopilot.min_quorum=2 when replicas=3" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.replicas=3' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) - - [ "${actual}" = "2" ] -} - -@test "server/ConfigMap: autopilot.min_quorum=3 when replicas=4" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.replicas=4' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) - - [ "${actual}" = "3" ] -} - -@test "server/ConfigMap: autopilot.min_quorum=3 when replicas=5" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.replicas=5' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .autopilot.min_quorum | tee /dev/stderr) - - [ "${actual}" = "3" ] -} +} \ No newline at end of file diff --git a/charts/consul/test/unit/server-disruptionbudget.bats b/charts/consul/test/unit/server-disruptionbudget.bats index 5d30d8b628..eb076ac775 100755 --- a/charts/consul/test/unit/server-disruptionbudget.bats +++ b/charts/consul/test/unit/server-disruptionbudget.bats @@ -59,16 +59,6 @@ load _helpers [ "${actual}" = "0" ] } -@test "server/DisruptionBudget: correct maxUnavailable with replicas=2" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-disruptionbudget.yaml \ - --set 'server.replicas=2' \ - . | tee /dev/stderr | - yq '.spec.maxUnavailable' | tee /dev/stderr) - [ "${actual}" = "1" ] -} - @test "server/DisruptionBudget: correct maxUnavailable with replicas=3" { cd `chart_dir` local actual=$(helm template \ @@ -107,7 +97,7 @@ load _helpers --set 'server.replicas=6' \ . | tee /dev/stderr | yq '.spec.maxUnavailable' | tee /dev/stderr) - [ "${actual}" = "1" ] + [ "${actual}" = "2" ] } @test "server/DisruptionBudget: correct maxUnavailable with replicas=7" { @@ -117,7 +107,7 @@ load _helpers --set 'server.replicas=7' \ . | tee /dev/stderr | yq '.spec.maxUnavailable' | tee /dev/stderr) - [ "${actual}" = "1" ] + [ "${actual}" = "2" ] } @test "server/DisruptionBudget: correct maxUnavailable with replicas=8" { @@ -127,21 +117,9 @@ load _helpers --set 'server.replicas=8' \ . | tee /dev/stderr | yq '.spec.maxUnavailable' | tee /dev/stderr) - [ "${actual}" = "1" ] -} - -@test "server/DisruptionBudget: correct maxUnavailable when set with value" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-disruptionbudget.yaml \ - --set 'server.replicas=5' \ - --set 'server.disruptionBudget.maxUnavailable=5' \ - . | tee /dev/stderr | - yq '.spec.maxUnavailable' | tee /dev/stderr) - [ "${actual}" = "5" ] + [ "${actual}" = "3" ] } - #-------------------------------------------------------------------- # apiVersion diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index 388d4dc986..2625f2ce64 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -196,73 +196,6 @@ load _helpers [ "${actual}" = "foo" ] } -#-------------------------------------------------------------------- -# persistentVolumeClaimRetentionPolicy - -@test "server/StatefulSet: persistentVolumeClaimRetentionPolicy not set by default when kubernetes < 1.23" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --kube-version "1.22" \ - . | tee /dev/stderr | - yq -r '.spec.persistentVolumeClaimRetentionPolicy' | tee /dev/stderr) - [ "${actual}" = "null" ] -} - -@test "server/StatefulSet: unset persistentVolumeClaimRetentionPolicy.whenDeleted when kubernetes < 1.23" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --kube-version "1.22" \ - --set 'server.persistentVolumeClaimRetentionPolicy.whenDeleted=Delete' \ - . | tee /dev/stderr | - yq -r '.spec.persistentVolumeClaimRetentionPolicy.whenDeleted' | tee /dev/stderr) - [ "${actual}" = "null" ] -} - -@test "server/StatefulSet: unset persistentVolumeClaimRetentionPolicy.whenScaled when kubernetes < 1.23" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --kube-version "1.22" \ - --set 'server.persistentVolumeClaimRetentionPolicy.whenScaled=Delete' \ - . | tee /dev/stderr | - yq -r '.spec.persistentVolumeClaimRetentionPolicy.whenScaled' | tee /dev/stderr) - [ "${actual}" = "null" ] -} - -@test "server/StatefulSet: persistentVolumeClaimRetentionPolicy not set by default when kubernetes >= 1.23" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --kube-version "1.23" \ - . | tee /dev/stderr | - yq -r '.spec.persistentVolumeClaimRetentionPolicy' | tee /dev/stderr) - [ "${actual}" = "null" ] -} - -@test "server/StatefulSet: can set persistentVolumeClaimRetentionPolicy.whenDeleted when kubernetes >= 1.23" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --kube-version "1.23" \ - --set 'server.persistentVolumeClaimRetentionPolicy.whenDeleted=Delete' \ - . | tee /dev/stderr | - yq -r '.spec.persistentVolumeClaimRetentionPolicy.whenDeleted' | tee /dev/stderr) - [ "${actual}" = "Delete" ] -} - -@test "server/StatefulSet: can set persistentVolumeClaimRetentionPolicy.whenScaled when kubernetes >= 1.23" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --kube-version "1.23" \ - --set 'server.persistentVolumeClaimRetentionPolicy.whenScaled=Delete' \ - . | tee /dev/stderr | - yq -r '.spec.persistentVolumeClaimRetentionPolicy.whenScaled' | tee /dev/stderr) - [ "${actual}" = "Delete" ] -} - #-------------------------------------------------------------------- # serverCert @@ -694,11 +627,7 @@ load _helpers local actual=$(helm template \ -s templates/server-statefulset.yaml \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -792,7 +721,7 @@ load _helpers -s templates/server-statefulset.yaml \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 59777d0692f3bf9143a09e96e58cc95296c0eaeb7565b1c49bc932954fe4423a ] + [ "${actual}" = dc165411861bb45d37e20a0a337697336f333407f5fb29fca9252cdba652339c ] } @test "server/StatefulSet: adds config-checksum annotation when extraConfig is provided" { @@ -802,7 +731,7 @@ load _helpers --set 'server.extraConfig="{\"hello\": \"world\"}"' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = 326eeb3fd8f497297671791c0091e7f804727b3aaeff79a47841b65571bd4cf9 ] + [ "${actual}" = d69874f33a862f6728265246a3e38b3f64702e013ecd4afc5dcdc33d34a66954 ] } @test "server/StatefulSet: adds config-checksum annotation when config is updated" { @@ -812,7 +741,7 @@ load _helpers --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations."consul.hashicorp.com/config-checksum"' | tee /dev/stderr) - [ "${actual}" = e14ba19e37a6b79b6fd8566597464154abe9cdc4aa7079012fb3c445ac08c526 ] + [ "${actual}" = 95a3d3b4816a0f183b8a9aac41ff386e659cd2a465f3030f01c5c4a2b9052a6c ] } #-------------------------------------------------------------------- @@ -952,9 +881,11 @@ load _helpers #-------------------------------------------------------------------- # global.openshift.enabled -@test "server/StatefulSet: restricted container securityContexts are set when global.openshift.enabled=true" { +@test "server/StatefulSet: restricted container securityContexts are set when global.openshift.enabled=true on OpenShift >= 4.11" { cd `chart_dir` + # OpenShift 4.11 == Kube 1.24 local manifest=$(helm template \ + --kube-version '1.24' \ -s templates/server-statefulset.yaml \ --set 'global.openshift.enabled=true' \ . | tee /dev/stderr) @@ -975,11 +906,20 @@ load _helpers local actual=$(echo "$manifest" | yq -r '.spec.template.spec.containers | map(select(.name == "consul")) | .[0].securityContext') local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') [ "$equal" == "true" ] +} - # Check locality-init container - local actual=$(echo "$manifest" | yq -r '.spec.template.spec.initContainers | map(select(.name == "locality-init")) | .[0].securityContext') - local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') - [ "$equal" == "true" ] +@test "server/StatefulSet: restricted container securityContexts are not set when global.openshift.enabled=true on OpenShift < 4.11" { + cd `chart_dir` + # OpenShift 4.11 == Kube 1.24 + local manifest=$(helm template \ + --kube-version '1.23' \ + -s templates/server-statefulset.yaml \ + --set 'global.openshift.enabled=true' \ + . | tee /dev/stderr) + + # Check consul container + local actual=$(echo "$manifest" | yq -r '.spec.template.spec.containers | map(select(.name == "consul")) | .[0].securityContext') + [ "$actual" == "null" ] } #-------------------------------------------------------------------- @@ -1008,11 +948,6 @@ load _helpers local actual=$(echo "$manifest" | yq -r '.spec.template.spec.containers | map(select(.name == "consul")) | .[0].securityContext') local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') [ "$equal" == "true" ] - - # Check locality-init container - local actual=$(echo "$manifest" | yq -r '.spec.template.spec.initContainers | map(select(.name == "locality-init")) | .[0].securityContext') - local equal=$(jq -n --argjson a "$actual" --argjson b "$expected" '$a == $b') - [ "$equal" == "true" ] } #-------------------------------------------------------------------- @@ -2050,13 +1985,7 @@ load _helpers --set 'global.secretsBackend.vault.consulClientRole=test' \ --set 'global.secretsBackend.vault.consulServerRole=foo' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -2829,64 +2758,6 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ } -#-------------------------------------------------------------------- -# global.trustedCAs - -@test "server/StatefulSet: trustedCAs: if trustedCAs is set command is modified correctly" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "server/StatefulSet: trustedCAs: if tustedCAs multiple are set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - --set 'global.trustedCAs[1]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0]' | tee /dev/stderr) - - - local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] - local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-1.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -# global.trustedCAs -@test "server/StatefulSet: trustedCAs: if trustedCAs is set /trusted-cas volumeMount is added" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) - local actual=$(echo $object | jq -r '.volumes[] | select(.name == "trusted-cas") | .name' | tee /dev/stderr) - [ "${actual}" = "trusted-cas" ] -} - -@test "server/StatefulSet: trustedCAs: if trustedCAs is set SSL_CERT_DIR env var is set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | yq -r '.spec.template.spec.containers[0].env[] | select(.name == "SSL_CERT_DIR")' | tee /dev/stderr) - - local actual=$(echo $object | jq -r '.name' | tee /dev/stderr) - [ "${actual}" = "SSL_CERT_DIR" ] - local actual=$(echo $object | jq -r '.value' | tee /dev/stderr) - [ "${actual}" = "/etc/ssl/certs:/trusted-cas" ] -} - #-------------------------------------------------------------------- # snapshotAgent license-autoload @@ -3075,31 +2946,3 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ yq -r '.spec.template.spec.containers[1].command[2] | contains("-interval=10h34m5s")' | tee /dev/stderr) [ "${actual}" = "true" ] } - -#-------------------------------------------------------------------- -# global.experiments=["resource-apis"] - -@test "server/StatefulSet: experiments=[\"resource-apis\"] is not set in command when global.experiments is empty" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-statefulset.yaml \ - . | tee /dev/stderr) - - # Test the flag is set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[] | select(.name == "consul") | .command | any(contains("-hcl=\"experiments=[\\\"resource-apis\\\"]\""))' | tee /dev/stderr) - [ "${actual}" = "false" ] -} - -@test "server/StatefulSet: experiments=[\"resource-apis\"] is set in command when global.experiments contains \"resource-apis\"" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr) - - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[] | select(.name == "consul") | .command | any(contains("-hcl=\"experiments=[\\\"resource-apis\\\"]\""))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/server-tmp-extra-config-configmap.bats b/charts/consul/test/unit/server-tmp-extra-config-configmap.bats deleted file mode 100755 index 213cc0cbc8..0000000000 --- a/charts/consul/test/unit/server-tmp-extra-config-configmap.bats +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "server/TmpConfigMap: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-tmp-extra-config-configmap.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "server/TmpConfigMap: enable with global.enabled false" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-tmp-extra-config-configmap.yaml \ - --set 'global.enabled=false' \ - --set 'server.enabled=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "server/TmpConfigMap: disable with server.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/server-tmp-extra-config-configmap.yaml \ - --set 'server.enabled=false' \ - . -} - -@test "server/TmpConfigMap: disable with global.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/server-tmp-extra-config-configmap.yaml \ - --set 'global.enabled=false' \ - . -} - -@test "server/TmpConfigMap: extraConfig is set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-tmp-extra-config-configmap.yaml \ - --set 'server.extraConfig="{\"hello\": \"world\"}"' \ - . | tee /dev/stderr | - yq '.data["extra-from-values.json"] | match("world") | length' | tee /dev/stderr) - [ ! -z "${actual}" ] -} diff --git a/charts/consul/test/unit/sync-catalog-deployment.bats b/charts/consul/test/unit/sync-catalog-deployment.bats index 0c9579df20..d8321eefdf 100755 --- a/charts/consul/test/unit/sync-catalog-deployment.bats +++ b/charts/consul/test/unit/sync-catalog-deployment.bats @@ -984,10 +984,7 @@ load _helpers -s templates/sync-catalog-deployment.yaml \ --set 'syncCatalog.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) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -1236,12 +1233,7 @@ load _helpers --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) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/telemetry-collector-configmap.bats b/charts/consul/test/unit/telemetry-collector-configmap.bats deleted file mode 100644 index c866f0d3e1..0000000000 --- a/charts/consul/test/unit/telemetry-collector-configmap.bats +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/ConfigMap: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-configmap.yaml \ - . -} - -@test "telemetryCollector/ConfigMap: enabled with telemetryCollector enabled and config has content" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-configmap.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.customExporterConfig="{}"' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/ConfigMap: disabled with telemetryCollector enabled and config is empty content" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-configmap.yaml \ - --set 'telemetryCollector.enabled=true' \ - . -} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats deleted file mode 100755 index dd871b30df..0000000000 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ /dev/null @@ -1,1373 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/Deployment: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-deployment.yaml \ - . -} - -@test "telemetryCollector/Deployment: fails if no image is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=null' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "telemetryCollector.image must be set to enable consul-telemetry-collector" ]] -} - -@test "telemetryCollector/Deployment: disable with telemetry-collector.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=false' \ - . -} - -@test "telemetryCollector/Deployment: disable with global.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'global.enabled=false' \ - . -} - -@test "telemetryCollector/Deployment: container image overrides" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].image' | tee /dev/stderr) - [ "${actual}" = "\"bar\"" ] -} - -#-------------------------------------------------------------------- -# nodeSelector - -@test "telemetryCollector/Deployment: nodeSelector is not set by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) - [ "${actual}" = "null" ] -} - -@test "telemetryCollector/Deployment: specified nodeSelector" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.nodeSelector=testing' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) - [ "${actual}" = "testing" ] -} - -#-------------------------------------------------------------------- -# consul.name - -@test "telemetryCollector/Deployment: name is constant regardless of consul name" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'consul.name=foobar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].name' | tee /dev/stderr) - [ "${actual}" = "consul-telemetry-collector" ] -} - -#-------------------------------------------------------------------- -# global.tls.enabled - -@test "telemetryCollector/Deployment: Adds tls-ca-cert volume when global.tls.enabled is true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" != "" ] -} - -@test "telemetryCollector/Deployment: Adds tls-ca-cert volumeMounts when global.tls.enabled is true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" != "" ] -} - -@test "telemetryCollector/Deployment: can overwrite CA secret with the provided one" { - cd `chart_dir` - local ca_cert_volume=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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" ] -} - -#-------------------------------------------------------------------- -# global.tls.enableAutoEncrypt - -@test "telemetryCollector/Deployment: consul-ca-cert volumeMount is added when TLS with auto-encrypt is enabled without clients" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'client.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert") | length > 0' | tee - /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment: consul-ca-cert volume is not added if externalServers.enabled=true and externalServers.useSystemRoots=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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 "telemetryCollector/Deployment: resources has default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].resources' | tee /dev/stderr) - - [ $(echo "${actual}" | yq -r '.requests.memory') = "512Mi" ] - [ $(echo "${actual}" | yq -r '.requests.cpu') = "1000m" ] - [ $(echo "${actual}" | yq -r '.limits.memory') = "512Mi" ] - [ $(echo "${actual}" | yq -r '.limits.cpu') = "1000m" ] -} - -@test "telemetryCollector/Deployment: resources can be overridden" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.resources.foo=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# init container resources - -@test "telemetryCollector/Deployment: init container has default resources" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) - - [ $(echo "${actual}" | yq -r '.requests.memory') = "25Mi" ] - [ $(echo "${actual}" | yq -r '.requests.cpu') = "50m" ] - [ $(echo "${actual}" | yq -r '.limits.memory') = "150Mi" ] - [ $(echo "${actual}" | yq -r '.limits.cpu') = "50m" ] -} - -@test "telemetryCollector/Deployment: init container resources can be set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'telemetryCollector.initContainer.resources.requests.memory=memory' \ - --set 'telemetryCollector.initContainer.resources.requests.cpu=cpu' \ - --set 'telemetryCollector.initContainer.resources.limits.memory=memory2' \ - --set 'telemetryCollector.initContainer.resources.limits.cpu=cpu2' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) - - local actual=$(echo $object | yq -r '.requests.memory' | tee /dev/stderr) - [ "${actual}" = "memory" ] - - local actual=$(echo $object | yq -r '.requests.cpu' | tee /dev/stderr) - [ "${actual}" = "cpu" ] - - local actual=$(echo $object | yq -r '.limits.memory' | tee /dev/stderr) - [ "${actual}" = "memory2" ] - - local actual=$(echo $object | yq -r '.limits.cpu' | tee /dev/stderr) - [ "${actual}" = "cpu2" ] -} - -#-------------------------------------------------------------------- -# priorityClassName - -@test "telemetryCollector/Deployment: no priorityClassName by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) - - [ "${actual}" = "null" ] -} - -@test "telemetryCollector/Deployment: can set a priorityClassName" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.priorityClassName=name' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) - - [ "${actual}" = "name" ] -} - -#-------------------------------------------------------------------- -# replicas - -@test "telemetryCollector/Deployment: replicas defaults to 1" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq '.spec.replicas' | tee /dev/stderr) - - [ "${actual}" = "1" ] -} - -@test "telemetryCollector/Deployment: replicas can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.replicas=3' \ - . | tee /dev/stderr | - yq '.spec.replicas' | tee /dev/stderr) - - [ "${actual}" = "3" ] -} - -#-------------------------------------------------------------------- -# Vault - -@test "telemetryCollector/Deployment: vault CA is not configured by default" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - . | 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 "telemetryCollector/Deployment: vault CA is not configured when secretName is set but secretKey is not" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - --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 "telemetryCollector/Deployment: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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 "telemetryCollector/Deployment: 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 templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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 "telemetryCollector/Deployment: 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 templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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 "telemetryCollector/Deployment: vault CA is not configured when secretKey is set but secretName is not" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - --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 "telemetryCollector/Deployment: vault CA is configured when both secretName and secretKey are set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - --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" ] -} - -@test "telemetryCollector/Deployment: vault tls annotations are set when tls is enabled" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'server.serverCert.secretName=pki_int/issue/test' \ - --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata' | tee /dev/stderr) - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr)" - local expected=$'{{- with secret \"pki_int/cert/ca\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' - [ "${actual}" = "${expected}" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr)" - [ "${actual}" = "pki_int/cert/ca" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" - [ "${actual}" = "test" ] -} - -@test "telemetryCollector/Deployment: vault agent annotations can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# telemetryCollector.cloud - -@test "telemetryCollector/Deployment: success with global.cloud env vars" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-key' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_RESOURCE_ID")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) - [ "${actual}" = "client-resource-id-name" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_RESOURCE_ID")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) - [ "${actual}" = "client-resource-id-key" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_CLIENT_ID")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) - [ "${actual}" = "client-id-name" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_CLIENT_ID")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) - [ "${actual}" = "client-id-key" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_CLIENT_SECRET")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) - [ "${actual}" = "client-secret-name" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_CLIENT_SECRET")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) - [ "${actual}" = "client-secret-key" ] -} - -@test "telemetryCollector/Deployment: success with telemetryCollector.cloud env vars" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'global.cloud.enabled=false' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.resourceId.secretName=client-resource-id-name' \ - --set 'telemetryCollector.cloud.resourceId.secretKey=client-resource-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_RESOURCE_ID")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) - [ "${actual}" = "client-resource-id-name" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_RESOURCE_ID")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) - [ "${actual}" = "client-resource-id-key" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_CLIENT_ID")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) - [ "${actual}" = "client-id-name" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_CLIENT_ID")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) - [ "${actual}" = "client-id-key" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_CLIENT_SECRET")) | .[0].valueFrom.secretKeyRef.name' | tee /dev/stderr) - [ "${actual}" = "client-secret-name" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_CLIENT_SECRET")) | .[0].valueFrom.secretKeyRef.key' | tee /dev/stderr) - [ "${actual}" = "client-secret-key" ] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId is set and global.cloud.resourceId is not set or global.cloud.clientSecret.secretName is not set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientSecret.secretName=client-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.enabled is true and global.cloud.clientSecret.secretName is not set but global.cloud.clientId.secretName and global.cloud.resourceId.secretName is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.enabled is true and global.cloud.resourceId.secretName is not set but global.cloud.clientId.secretName and global.cloud.clientSecret.secretName is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.resourceId.secretName is set but global.cloud.resourceId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When either global.cloud.resourceId.secretName or global.cloud.resourceId.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.authURL.secretName is set but global.cloud.authURL.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.authUrl.secretName=auth-url-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.authURL.secretKey is set but global.cloud.authURL.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.authUrl.secretKey=auth-url-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.apiHost.secretName is set but global.cloud.apiHost.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.apiHost.secretName=auth-url-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.apiHost.secretKey is set but global.cloud.apiHost.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.apiHost.secretKey=auth-url-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.scadaAddress.secretName is set but global.cloud.scadaAddress.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.scadaAddress.secretName=scada-address-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when global.cloud.scadaAddress.secretKey is set but global.cloud.scadaAddress.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.scadaAddress.secretKey=scada-address-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId.secretName is set but telemetryCollector.cloud.clientId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - echo "$output" > /dev/stderr - - [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId.secretKey is set but telemetryCollector.cloud.clientId.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - echo "$output" > /dev/stderr - - [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetryCollector.cloud.clientId.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - echo "$output" > /dev/stderr - - [[ "$output" =~ "When telemetryCollector.cloud.clientSecret.secretName is set, telemetryCollector.cloud.clientId.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId.secretName is set but telemetry.cloud.clientId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetry.cloud.clientSecret.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.clientId and telemetryCollector.cloud.clientSecret is set but global.cloud.resourceId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When telemetryCollector has clientId and clientSecret, telemetryCollector.cloud.resourceId.secretKey or global.cloud.resourceId.secretKey must be set" ]] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.resourceId.secretName differs from global.cloud.resourceId.secretName." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.cloud.resourceId.secretName=resource-id-1' \ - --set 'global.cloud.resourceId.secretKey=key' \ - --set 'telemetryCollector.cloud.resourceId.secretName=resource-id-2' \ - --set 'telemetryCollector.cloud.resourceId.secretKey=key' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When both global.cloud.resourceId.secretName and telemetryCollector.cloud.resourceId.secretName are set, they should be the same." ]] -} - -@test "telemetryCollector/Deployment: fails when telemetryCollector.cloud.resourceId.secretKey differs from global.cloud.resourceId.secretKey." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.cloud.resourceId.secretName=name' \ - --set 'global.cloud.resourceId.secretKey=key-1' \ - --set 'telemetryCollector.cloud.resourceId.secretName=name' \ - --set 'telemetryCollector.cloud.resourceId.secretKey=key-2' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When both global.cloud.resourceId.secretKey and telemetryCollector.cloud.resourceId.secretKey are set, they should be the same." ]] -} - -#-------------------------------------------------------------------- -# global.tls.enabled - -@test "telemetryCollector/Deployment: sets -tls-disabled args when when not using TLS." { - cd `chart_dir` - - local flags=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=false' \ - . | yq -r .spec.template.spec.containers[1].args) - - local actual=$(echo $flags | yq -r '. | any(contains("-tls-disabled"))') - [ "${actual}" = 'true' ] - -} - -@test "telemetryCollector/Deployment: -ca-certs set correctly when using TLS." { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} - -#-------------------------------------------------------------------- -# External Server - -@test "telemetryCollector/Deployment: sets external server args when global.tls.enabled and externalServers.enabled" { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.httpsPort=8501' \ - --set 'externalServers.tlsServerName=foo.tls.server' \ - --set 'externalServers.useSystemRoots=true' \ - --set 'server.enabled=false' \ - --set 'client.enabled=false' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) - [ "${actual}" = 'false' ] - - local actual=$(echo $flags | yq -r '. | any(contains("-tls-server-name=foo.tls.server"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $flags | jq -r '. | any(contains("-addresses=external-consul.host"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} - -#-------------------------------------------------------------------- -# Admin Partitions - -@test "telemetryCollector/Deployment: partition flags are set when using admin partitions" { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.adminPartitions.name=hashi' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo $flags | jq -r '. | any(contains("-login-partition=hashi"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $flags | jq -r '. | any(contains("-service-partition=hashi"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment: consul-ca-cert volume mount is not set when using externalServers and useSystemRoots" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'server.enabled=false' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.useSystemRoots=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" = "" ] -} - -@test "telemetryCollector/Deployment: config volume mount is set when config exists" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.customExporterConfig="foo"' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "config") | .name' | tee /dev/stderr) - [ "${actual}" = "config" ] -} - -@test "telemetryCollector/Deployment: config flag is set when config exists" { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.customExporterConfig="foo"' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command') - - local actual=$(echo $flags | yq -r '. | any(contains("-config-file-path /consul/config/config.json"))') - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment: consul-ca-cert volume mount is not set on acl-init when using externalServers and useSystemRoots" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'server.enabled=false' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.useSystemRoots=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.initContainers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" = "" ] -} -#-------------------------------------------------------------------- -# trustedCAs - -@test "telemetryCollector/Deployment: trustedCAs: if trustedCAs is set command is modified correctly" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment: trustedCAs: if multiple Trusted cas were set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - --set 'global.trustedCAs[1]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0]' | tee /dev/stderr) - - - local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] - local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-1.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment: trustedCAs: if trustedCAs is set /trusted-cas volumeMount is added" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) - local actual=$(echo $object | jq -r '.volumes[] | select(.name == "trusted-cas") | .name' | tee /dev/stderr) - [ "${actual}" = "trusted-cas" ] -} - - -@test "telemetryCollector/Deployment: trustedCAs: if trustedCAs is set SSL_CERT_DIR env var is set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | yq -r '.spec.template.spec.containers[0].env[] | select(.name == "SSL_CERT_DIR")' | tee /dev/stderr) - - local actual=$(echo $object | jq -r '.name' | tee /dev/stderr) - [ "${actual}" = "SSL_CERT_DIR" ] - local actual=$(echo $object | jq -r '.value' | tee /dev/stderr) - [ "${actual}" = "/etc/ssl/certs:/trusted-cas" ] -} - -#-------------------------------------------------------------------- -# extraLabels - -@test "telemetryCollector/Deployment: no extra labels defined by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."consul.hashicorp.com/connect-inject-managed-by")' \ - | tee /dev/stderr) - [ "${actual}" = "{}" ] -} - -@test "telemetryCollector/Deployment: extra global labels can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --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 "telemetryCollector/Deployment: multiple global extra labels can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --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" ] -} - -#-------------------------------------------------------------------- -# extraEnvironmentVariables - -@test "telemetryCollector/Deployment: extra environment variables" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS=insecure' \ - --set 'telemetryCollector.extraEnvironmentVars.foo=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_AUTH_TLS")) | .[0].value' | tee /dev/stderr) - [ "${actual}" = "insecure" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "foo")) | .[0].value' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# logLevel - -@test "telemetryCollector/Deployment: use global.logLevel by default" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=info"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment: override global.logLevel when telemetryCollector.logLevel is set" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.logLevel=warn' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment: use global.logLevel by default for dataplane container" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=info"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment: override global.logLevel when telemetryCollector.logLevel is set for dataplane container" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.logLevel=debug' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# global.experiments=["resource-apis"] - -@test "telemetryCollector/Deployment: disabled when V2 is enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - . -} - -#-------------------------------------------------------------------- -# Namespaces - -@test "telemetryCollector/Deployment: namespace flags when mirroringK8S" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'connectInject.consulNamespaces.mirroringK8S=true' \ - --namespace 'test-namespace' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec' | tee /dev/stderr) - - local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-login-namespace=default"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-service-namespace=test-namespace"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} - -@test "telemetryCollector/Deployment: namespace flags when not mirroringK8S" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'connectInject.consulNamespaces.mirroringK8S=false' \ - --set 'connectInject.consulNamespaces.consulDestinationNamespace=fakenamespace' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers' | tee /dev/stderr) - - local actual=$(echo $object | jq -r '.[1].args | any(contains("-login-namespace=fakenamespace"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $object | jq -r '.[1].args | any(contains("-service-namespace=fakenamespace"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} diff --git a/charts/consul/test/unit/telemetry-collector-podsecuritypolicy.bats b/charts/consul/test/unit/telemetry-collector-podsecuritypolicy.bats deleted file mode 100644 index 90c494eca5..0000000000 --- a/charts/consul/test/unit/telemetry-collector-podsecuritypolicy.bats +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/PodSecurityPolicy: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-podsecuritypolicy.yaml \ - . -} - -@test "telemetryCollector/PodSecurityPolicy: enabled with telemetryCollector and global.enablePodSecurityPolicies=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-podsecuritypolicy.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.enablePodSecurityPolicies=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-role.bats b/charts/consul/test/unit/telemetry-collector-role.bats deleted file mode 100644 index 8fa209ef4b..0000000000 --- a/charts/consul/test/unit/telemetry-collector-role.bats +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/Role: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-role.yaml \ - . -} - -@test "telemetryCollector/Role: enabled with telemetryCollector and global.enablePodSecurityPolicies=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-role.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.enablePodSecurityPolicies=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-rolebinding.bats b/charts/consul/test/unit/telemetry-collector-rolebinding.bats deleted file mode 100644 index 454c2569fb..0000000000 --- a/charts/consul/test/unit/telemetry-collector-rolebinding.bats +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/RoleBinding: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-rolebinding.yaml \ - . -} - -@test "telemetryCollector/RoleBinding: enabled with telemetryCollector and global.enablePodSecurityPolicies=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-rolebinding.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.enablePodSecurityPolicies=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-service.bats b/charts/consul/test/unit/telemetry-collector-service.bats deleted file mode 100755 index c5406b8124..0000000000 --- a/charts/consul/test/unit/telemetry-collector-service.bats +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/Service: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-service.yaml \ - . -} - -@test "telemetryCollector/Service: enabled by default with telemetryCollector, connectInject enabled" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-service.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Service: enabled with telemetryCollector.enabled=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-service.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# consul.name - -@test "telemetryCollector/Service: name is constant regardless of consul name" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-service.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'consul.name=foobar' \ - . | tee /dev/stderr | - yq -r '.metadata.name' | tee /dev/stderr) - [ "${actual}" = "consul-telemetry-collector" ] -} - -#-------------------------------------------------------------------- -# annotations - -@test "telemetryCollector/Service: no annotations by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-service.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq -r '.metadata.annotations' | tee /dev/stderr) - [ "${actual}" = "null" ] -} - -@test "telemetryCollector/Service: can set annotations" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-service.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=true' \ - --set 'telemetryCollector.service.annotations=key: value' \ - . | tee /dev/stderr | - yq -r '.metadata.annotations.key' | tee /dev/stderr) - [ "${actual}" = "value" ] -} - -#-------------------------------------------------------------------- -# port - -@test "telemetryCollector/Service: has default port" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-service.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.ports[0].port' | tee /dev/stderr) - [ "${actual}" = "9356" ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-serviceaccount.bats b/charts/consul/test/unit/telemetry-collector-serviceaccount.bats deleted file mode 100644 index 589632d5b5..0000000000 --- a/charts/consul/test/unit/telemetry-collector-serviceaccount.bats +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/ServiceAccount: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-serviceaccount.yaml \ - . -} - -@test "telemetryCollector/ServiceAccount: enabled with telemetryCollector, connectInject enabled" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-serviceaccount.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# global.imagePullSecrets - -@test "telemetryCollector/ServiceAccount: can set image pull secrets" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-serviceaccount.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=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" ] -} - -#-------------------------------------------------------------------- -# telemetryCollector.serviceAccount.annotations - -@test "telemetryCollector/ServiceAccount: no annotations by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-serviceaccount.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq '.metadata.annotations | length > 0' | tee /dev/stderr) - [ "${actual}" = "false" ] -} - -@test "telemetryCollector/ServiceAccount: annotations when enabled" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-serviceaccount.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'connectInject.enabled=true' \ - --set "telemetryCollector.serviceAccount.annotations=foo: bar" \ - . | tee /dev/stderr | - yq -r '.metadata.annotations.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - - -#-------------------------------------------------------------------- -# consul.name - -@test "telemetryCollector/ServiceAccount: name is constant regardless of consul name" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-serviceaccount.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'consul.name=foobar' \ - . | tee /dev/stderr | - yq -r '.metadata.name' | tee /dev/stderr) - [ "${actual}" = "consul-telemetry-collector" ] -} diff --git a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats deleted file mode 100755 index 5cfdab96cf..0000000000 --- a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats +++ /dev/null @@ -1,1406 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/Deployment(V2): disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - . -} - -@test "telemetryCollector/Deployment(V2): fails if no image is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=null' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "telemetryCollector.image must be set to enable consul-telemetry-collector" ]] -} - -@test "telemetryCollector/Deployment(V2): disable with telemetry-collector.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=false' \ - . -} - -@test "telemetryCollector/Deployment(V2): disable with global.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'global.enabled=false' \ - . -} - -@test "telemetryCollector/Deployment(V2): container image overrides" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].image' | tee /dev/stderr) - [ "${actual}" = "\"bar\"" ] -} - -#-------------------------------------------------------------------- -# nodeSelector - -@test "telemetryCollector/Deployment(V2): nodeSelector is not set by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) - [ "${actual}" = "null" ] -} - -@test "telemetryCollector/Deployment(V2): specified nodeSelector" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.nodeSelector=testing' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) - [ "${actual}" = "testing" ] -} - -#-------------------------------------------------------------------- -# consul.name - -@test "telemetryCollector/Deployment(V2): name is constant regardless of consul name" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'consul.name=foobar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].name' | tee /dev/stderr) - [ "${actual}" = "consul-telemetry-collector" ] -} - -#-------------------------------------------------------------------- -# global.tls.enabled - -@test "telemetryCollector/Deployment(V2): Adds tls-ca-cert volume when global.tls.enabled is true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" != "" ] -} - -@test "telemetryCollector/Deployment(V2): Adds tls-ca-cert volumeMounts when global.tls.enabled is true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" != "" ] -} - -@test "telemetryCollector/Deployment(V2): can overwrite CA secret with the provided one" { - cd `chart_dir` - local ca_cert_volume=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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" ] -} - -#-------------------------------------------------------------------- -# global.tls.enableAutoEncrypt - -@test "telemetryCollector/Deployment(V2): consul-ca-cert volumeMount is added when TLS with auto-encrypt is enabled without clients" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'client.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert") | length > 0' | tee - /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): consul-ca-cert volume is not added if externalServers.enabled=true and externalServers.useSystemRoots=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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 "telemetryCollector/Deployment(V2): resources has default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].resources' | tee /dev/stderr) - - [ $(echo "${actual}" | yq -r '.requests.memory') = "512Mi" ] - [ $(echo "${actual}" | yq -r '.requests.cpu') = "1000m" ] - [ $(echo "${actual}" | yq -r '.limits.memory') = "512Mi" ] - [ $(echo "${actual}" | yq -r '.limits.cpu') = "1000m" ] -} - -@test "telemetryCollector/Deployment(V2): resources can be overridden" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.resources.foo=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# init container resources - -@test "telemetryCollector/Deployment(V2): init container has default resources" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) - - [ $(echo "${actual}" | yq -r '.requests.memory') = "25Mi" ] - [ $(echo "${actual}" | yq -r '.requests.cpu') = "50m" ] - [ $(echo "${actual}" | yq -r '.limits.memory') = "150Mi" ] - [ $(echo "${actual}" | yq -r '.limits.cpu') = "50m" ] -} - -@test "telemetryCollector/Deployment(V2): init container resources can be set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'telemetryCollector.initContainer.resources.requests.memory=memory' \ - --set 'telemetryCollector.initContainer.resources.requests.cpu=cpu' \ - --set 'telemetryCollector.initContainer.resources.limits.memory=memory2' \ - --set 'telemetryCollector.initContainer.resources.limits.cpu=cpu2' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) - - local actual=$(echo $object | yq -r '.requests.memory' | tee /dev/stderr) - [ "${actual}" = "memory" ] - - local actual=$(echo $object | yq -r '.requests.cpu' | tee /dev/stderr) - [ "${actual}" = "cpu" ] - - local actual=$(echo $object | yq -r '.limits.memory' | tee /dev/stderr) - [ "${actual}" = "memory2" ] - - local actual=$(echo $object | yq -r '.limits.cpu' | tee /dev/stderr) - [ "${actual}" = "cpu2" ] -} - -#-------------------------------------------------------------------- -# priorityClassName - -@test "telemetryCollector/Deployment(V2): no priorityClassName by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) - - [ "${actual}" = "null" ] -} - -@test "telemetryCollector/Deployment(V2): can set a priorityClassName" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.priorityClassName=name' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) - - [ "${actual}" = "name" ] -} - -#-------------------------------------------------------------------- -# replicas - -@test "telemetryCollector/Deployment(V2): replicas defaults to 1" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq '.spec.replicas' | tee /dev/stderr) - - [ "${actual}" = "1" ] -} - -@test "telemetryCollector/Deployment(V2): replicas can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.replicas=3' \ - . | tee /dev/stderr | - yq '.spec.replicas' | tee /dev/stderr) - - [ "${actual}" = "3" ] -} - -#-------------------------------------------------------------------- -# Vault - -@test "telemetryCollector/Deployment(V2): vault CA is not configured by default" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - . | 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 "telemetryCollector/Deployment(V2): vault CA is not configured when secretName is set but secretKey is not" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - --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 "telemetryCollector/Deployment(V2): vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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 "telemetryCollector/Deployment(V2): 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 templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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 "telemetryCollector/Deployment(V2): 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 templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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 "telemetryCollector/Deployment(V2): vault CA is not configured when secretKey is set but secretName is not" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - --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 "telemetryCollector/Deployment(V2): vault CA is configured when both secretName and secretKey are set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - --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" ] -} - -@test "telemetryCollector/Deployment(V2): vault tls annotations are set when tls is enabled" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --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.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'server.serverCert.secretName=pki_int/issue/test' \ - --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata' | tee /dev/stderr) - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr)" - local expected=$'{{- with secret \"pki_int/cert/ca\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' - [ "${actual}" = "${expected}" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr)" - [ "${actual}" = "pki_int/cert/ca" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" - [ "${actual}" = "test" ] -} - -@test "telemetryCollector/Deployment(V2): vault agent annotations can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=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=test' \ - --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# telemetryCollector.cloud - -@test "telemetryCollector/Deployment(V2): success with all cloud bits set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-key' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ - . -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId is set and global.cloud.resourceId is not set or global.cloud.clientSecret.secretName is not set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientSecret.secretName=client-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.enabled is true and global.cloud.clientSecret.secretName is not set but global.cloud.clientId.secretName and global.cloud.resourceId.secretName is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.enabled is true and global.cloud.resourceId.secretName is not set but global.cloud.clientId.secretName and global.cloud.clientSecret.secretName is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.resourceId.secretName is set but global.cloud.resourceId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When either global.cloud.resourceId.secretName or global.cloud.resourceId.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.authURL.secretName is set but global.cloud.authURL.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.authUrl.secretName=auth-url-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.authURL.secretKey is set but global.cloud.authURL.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.authUrl.secretKey=auth-url-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.apiHost.secretName is set but global.cloud.apiHost.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.apiHost.secretName=auth-url-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.apiHost.secretKey is set but global.cloud.apiHost.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.apiHost.secretKey=auth-url-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.scadaAddress.secretName is set but global.cloud.scadaAddress.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.scadaAddress.secretName=scada-address-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.scadaAddress.secretKey is set but global.cloud.scadaAddress.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.scadaAddress.secretKey=scada-address-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretName is set but telemetryCollector.cloud.clientId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - echo "$output" > /dev/stderr - - [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretKey is set but telemetryCollector.cloud.clientId.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - echo "$output" > /dev/stderr - - [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetryCollector.cloud.clientId.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - echo "$output" > /dev/stderr - - [[ "$output" =~ "When telemetryCollector.cloud.clientSecret.secretName is set, telemetryCollector.cloud.clientId.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretName is set but telemetry.cloud.clientId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetry.cloud.clientSecret.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId and telemetryCollector.cloud.clientSecret is set but global.cloud.resourceId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - - echo "$output" > /dev/stderr - - [[ "$output" =~ "When telemetryCollector has clientId and clientSecret, telemetryCollector.cloud.resourceId.secretKey or global.cloud.resourceId.secretKey must be set" ]] -} - -#-------------------------------------------------------------------- -# global.tls.enabled - -@test "telemetryCollector/Deployment(V2): sets -tls-disabled args when when not using TLS." { - cd `chart_dir` - - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=false' \ - . | yq -r .spec.template.spec.containers[1].args) - - local actual=$(echo $flags | yq -r '. | any(contains("-tls-disabled"))') - [ "${actual}" = 'true' ] - -} - -@test "telemetryCollector/Deployment(V2): -ca-certs set correctly when using TLS." { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} - -#-------------------------------------------------------------------- -# External Server - -@test "telemetryCollector/Deployment(V2): sets external server args when global.tls.enabled and externalServers.enabled" { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.httpsPort=8501' \ - --set 'externalServers.tlsServerName=foo.tls.server' \ - --set 'externalServers.useSystemRoots=true' \ - --set 'server.enabled=false' \ - --set 'client.enabled=false' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) - [ "${actual}" = 'false' ] - - local actual=$(echo $flags | yq -r '. | any(contains("-tls-server-name=foo.tls.server"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $flags | jq -r '. | any(contains("-addresses=external-consul.host"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} - -#-------------------------------------------------------------------- -# Admin Partitions -# TODO: re-enable this test when V2 supports admin partitions. - -# @test "telemetryCollector/Deployment: partition flags are set when using admin partitions" { -# cd `chart_dir` -# local flags=$(helm template \ -# -s templates/telemetry-collector-deployment.yaml \ -# --set 'ui.enabled=false' \ -# --set 'global.experiments[0]=resource-apis' \ -# --set 'telemetryCollector.enabled=true' \ -# --set 'telemetryCollector.image=bar' \ -# --set 'global.enableConsulNamespaces=true' \ -# --set 'global.adminPartitions.enabled=true' \ -# --set 'global.adminPartitions.name=hashi' \ -# --set 'global.acls.manageSystemACLs=true' \ -# . | tee /dev/stderr | -# yq '.spec.template.spec.containers[1].args' | tee /dev/stderr) -# -# local actual=$(echo $flags | jq -r '. | any(contains("-login-partition=hashi"))' | tee /dev/stderr) -# [ "${actual}" = 'true' ] -# -# local actual=$(echo $flags | jq -r '. | any(contains("-service-partition=hashi"))' | tee /dev/stderr) -# [ "${actual}" = "true" ] -# } - -@test "telemetryCollector/Deployment(V2): consul-ca-cert volume mount is not set when using externalServers and useSystemRoots" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'server.enabled=false' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.useSystemRoots=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" = "" ] -} - -@test "telemetryCollector/Deployment(V2): config volume mount is set when config exists" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.customExporterConfig="foo"' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "config") | .name' | tee /dev/stderr) - [ "${actual}" = "config" ] -} - -@test "telemetryCollector/Deployment(V2): config flag is set when config exists" { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.customExporterConfig="foo"' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command') - - local actual=$(echo $flags | yq -r '. | any(contains("-config-file-path /consul/config/config.json"))') - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): consul-ca-cert volume mount is not set on acl-init when using externalServers and useSystemRoots" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'server.enabled=false' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.useSystemRoots=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.initContainers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" = "" ] -} -#-------------------------------------------------------------------- -# trustedCAs - -@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set command is modified correctly" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): trustedCAs: if multiple Trusted cas were set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - --set 'global.trustedCAs[1]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0]' | tee /dev/stderr) - - - local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] - local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-1.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set /trusted-cas volumeMount is added" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) - local actual=$(echo $object | jq -r '.volumes[] | select(.name == "trusted-cas") | .name' | tee /dev/stderr) - [ "${actual}" = "trusted-cas" ] -} - - -@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set SSL_CERT_DIR env var is set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | yq -r '.spec.template.spec.containers[0].env[] | select(.name == "SSL_CERT_DIR")' | tee /dev/stderr) - - local actual=$(echo $object | jq -r '.name' | tee /dev/stderr) - [ "${actual}" = "SSL_CERT_DIR" ] - local actual=$(echo $object | jq -r '.value' | tee /dev/stderr) - [ "${actual}" = "/etc/ssl/certs:/trusted-cas" ] -} - -#-------------------------------------------------------------------- -# extraLabels - -@test "telemetryCollector/Deployment(V2): no extra labels defined by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."consul.hashicorp.com/connect-inject-managed-by")' \ - | tee /dev/stderr) - [ "${actual}" = "{}" ] -} - -@test "telemetryCollector/Deployment(V2): extra global labels can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --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 "telemetryCollector/Deployment(V2): multiple global extra labels can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --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" ] -} - -#-------------------------------------------------------------------- -# extraEnvironmentVariables - -@test "telemetryCollector/Deployment(V2): extra environment variables" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS=insecure' \ - --set 'telemetryCollector.extraEnvironmentVars.foo=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_AUTH_TLS")) | .[0].value' | tee /dev/stderr) - [ "${actual}" = "insecure" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "foo")) | .[0].value' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# logLevel - -@test "telemetryCollector/Deployment(V2): use global.logLevel by default" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=info"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): override global.logLevel when telemetryCollector.logLevel is set" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.logLevel=warn' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): use global.logLevel by default for dataplane container" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=info"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): override global.logLevel when telemetryCollector.logLevel is set for dataplane container" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.logLevel=debug' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# global.experiments=["resource-apis"] - -@test "telemetryCollector/Deployment(V2): disabled when V2 is disabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . -} - -#-------------------------------------------------------------------- -# Namespaces - -@test "telemetryCollector/Deployment(V2): namespace flags when mirroringK8S" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'connectInject.consulNamespaces.mirroringK8S=true' \ - --namespace 'test-namespace' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec' | tee /dev/stderr) - - local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-login-namespace=default"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $object | jq -r '.containers[1].args | any(contains("-service-namespace=test-namespace"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} - -@test "telemetryCollector/Deployment(V2): namespace flags when not mirroringK8S" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'connectInject.consulNamespaces.mirroringK8S=false' \ - --set 'connectInject.consulNamespaces.consulDestinationNamespace=fakenamespace' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers' | tee /dev/stderr) - - local actual=$(echo $object | jq -r '.[1].args | any(contains("-login-namespace=fakenamespace"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $object | jq -r '.[1].args | any(contains("-service-namespace=fakenamespace"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} diff --git a/charts/consul/test/unit/terminating-gateways-deployment.bats b/charts/consul/test/unit/terminating-gateways-deployment.bats index 0c4ab7dcca..5e10c5b1fd 100644 --- a/charts/consul/test/unit/terminating-gateways-deployment.bats +++ b/charts/consul/test/unit/terminating-gateways-deployment.bats @@ -871,7 +871,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -s -r '.[0].spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "4" ] + [ "${actual}" = "3" ] } @test "terminatingGateways/Deployment: extra annotations can be set through defaults" { @@ -886,7 +886,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "6" ] + [ "${actual}" = "5" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -908,7 +908,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "6" ] + [ "${actual}" = "5" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -931,7 +931,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "7" ] + [ "${actual}" = "6" ] local actual=$(echo $object | yq -r '.defaultkey' | tee /dev/stderr) [ "${actual}" = "defaultvalue" ] @@ -1214,13 +1214,7 @@ key2: value2' \ --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") | - del(."consul.hashicorp.com/gateway-consul-service-name") | - del(."consul.hashicorp.com/gateway-kind")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-consul-service-name") | del(."consul.hashicorp.com/gateway-kind")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/tls-init-cleanup-job.bats b/charts/consul/test/unit/tls-init-cleanup-job.bats index 2e72033396..735d991780 100644 --- a/charts/consul/test/unit/tls-init-cleanup-job.bats +++ b/charts/consul/test/unit/tls-init-cleanup-job.bats @@ -145,11 +145,7 @@ load _helpers -s templates/tls-init-cleanup-job.yaml \ --set 'global.tls.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/tls-init-job.bats b/charts/consul/test/unit/tls-init-job.bats index 1c5b7ae7a1..f71edc43d5 100644 --- a/charts/consul/test/unit/tls-init-job.bats +++ b/charts/consul/test/unit/tls-init-job.bats @@ -262,11 +262,7 @@ load _helpers -s templates/tls-init-job.yaml \ --set 'global.tls.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats b/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats index 4d1a4abdd2..d58ce20928 100644 --- a/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats +++ b/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats @@ -130,7 +130,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/ClusterRole: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { +@test "webhookCertManager/ClusterRole: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-clusterrole.yaml \ @@ -142,6 +142,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats b/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats index 2e507d279d..89a60a0ef3 100644 --- a/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats +++ b/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats @@ -24,7 +24,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/ClusterRoleBinding: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { +@test "webhookCertManager/ClusterRoleBinding: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-clusterrolebinding.yaml \ @@ -36,6 +36,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-configmap.bats b/charts/consul/test/unit/webhook-cert-manager-configmap.bats index 196da220d4..774ada21d1 100644 --- a/charts/consul/test/unit/webhook-cert-manager-configmap.bats +++ b/charts/consul/test/unit/webhook-cert-manager-configmap.bats @@ -24,7 +24,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/Configmap: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { +@test "webhookCertManager/Configmap: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-configmap.yaml \ @@ -36,6 +36,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-deployment.bats b/charts/consul/test/unit/webhook-cert-manager-deployment.bats index 7d1a028d20..c0e54ccd25 100644 --- a/charts/consul/test/unit/webhook-cert-manager-deployment.bats +++ b/charts/consul/test/unit/webhook-cert-manager-deployment.bats @@ -66,7 +66,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/Deployment: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { +@test "webhookCertManager/Deployment: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-deployment.yaml \ @@ -78,6 +78,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats b/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats index d8e16f867c..820be91404 100644 --- a/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats +++ b/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats @@ -35,7 +35,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/PodSecurityPolicy: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { +@test "webhookCertManager/PodSecurityPolicy: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-podsecuritypolicy.yaml \ @@ -48,6 +48,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats b/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats index f420e7319c..766d20fb66 100644 --- a/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats +++ b/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats @@ -45,7 +45,7 @@ load _helpers #-------------------------------------------------------------------- # Vault -@test "webhookCertManager/ServiceAccount: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName" { +@test "webhookCertManager/ServiceAccount: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { cd `chart_dir` assert_empty helm template \ -s templates/webhook-cert-manager-serviceaccount.yaml \ @@ -57,6 +57,9 @@ load _helpers --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ --set 'global.secretsBackend.vault.consulCARole=test2' \ diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index ef2d0cd877..096005599a 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.19-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.14-dev # 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.5-dev + imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.0.12-dev # The name of the datacenter that the agents should # register as. This can't be changed once the Consul cluster is up and running @@ -118,7 +118,7 @@ global: # agent annotation and [Vault Connect CA namespace](https://developer.hashicorp.com/consul/docs/connect/ca/vault#namespace). # To override one of these values individually, see `agentAnnotations` and `connectCA.additionalConfig`. vaultNamespace: "" - + # Enabling the Vault secrets backend will replace Kubernetes secrets with referenced Vault secrets. enabled: false @@ -169,6 +169,12 @@ global: # and check the name of `metadata.name`. adminPartitionsRole: "" + # The Vault role to read Consul controller's webhook's + # CA and issue a certificate and private key. + # A Vault policy must be created which grants issue capabilities to + # `global.secretsBackend.vault.controller.tlsCert.secretName`. + controllerRole: "" + # The Vault role to read Consul connect-injector webhook's CA # and issue a certificate and private key. # A Vault policy must be created which grants issue capabilities to @@ -243,6 +249,25 @@ global: additionalConfig: | {} + controller: + # Configuration to the Vault Secret that Kubernetes will use on + # Kubernetes CRD creation, deletion, and update, to get TLS certificates + # used issued from vault to send webhooks to the controller. + tlsCert: + # The Vault secret path that issues TLS certificates for controller + # webhooks. + # @type: string + secretName: null + + # Configuration to the Vault Secret that Kubernetes will use on + # Kubernetes CRD creation, deletion, and update, to get CA certificates + # used issued from vault to send webhooks to the controller. + caCert: + # The Vault secret path that contains the CA certificate for controller + # webhooks. + # @type: string + secretName: null + connectInject: # Configuration to the Vault Secret that Kubernetes uses on # Kubernetes pod creation, deletion, and update, to get CA certificates @@ -293,7 +318,7 @@ global: # The key within the Kubernetes secret or Vault secret key that holds the gossip # encryption key. secretKey: "" - # Override global log verbosity level for gossip-encryption-autogenerate-job pods. One of "trace", "debug", "info", "warn", or "error". + # Override global log verbosity level for `gossip-encryption-autogenerate-job` pods. One of "trace", "debug", "info", "warn", or "error". # @type: string logLevel: "" @@ -391,7 +416,7 @@ global: secretKey: null # This value defines additional annotations for - # tls init jobs. This should be formatted as a multi-line string. + # tls init jobs. Format this value as a multi-line string. # # ```yaml # annotations: | @@ -421,20 +446,14 @@ global: # @type: string logLevel: "" - # A Kubernetes or Vault secret containing the bootstrap token to use for creating policies and - # tokens for all Consul and consul-k8s-control-plane components. If `secretName` and `secretKey` - # are unset, a default secret name and secret key are used. If the secret is populated, then - # we will skip ACL bootstrapping of the servers and will only initialize ACLs for the Consul - # clients and consul-k8s-control-plane system components. - # If the secret is empty, then we will bootstrap ACLs on the Consul servers, and write the - # bootstrap token to this secret. If ACLs are already bootstrapped on the servers, then the - # secret must contain the bootstrap token. + # A Kubernetes or Vault secret containing the bootstrap token to use for + # creating policies and tokens for all Consul and consul-k8s-control-plane components. + # If set, we will skip ACL bootstrapping of the servers and will only + # initialize ACLs for the Consul clients and consul-k8s-control-plane system components. bootstrapToken: # The name of the Kubernetes or Vault secret that holds the bootstrap token. - # If unset, this defaults to `{{ global.name }}-bootstrap-acl-token`. secretName: null # The key within the Kubernetes or Vault secret that holds the bootstrap token. - # If unset, this defaults to `token`. secretKey: null # If true, an ACL token will be created that can be used in secondary @@ -517,7 +536,7 @@ global: nodeSelector: null # This value defines additional annotations for - # acl init jobs. This should be formatted as a multi-line string. + # acl init jobs. Format this value as a multi-line string. # # ```yaml # annotations: | @@ -601,7 +620,7 @@ global: # @type: string k8sAuthMethodHost: null - # Override global log verbosity level for the create-federation-secret-job pods. One of "trace", "debug", "info", "warn", or "error". + # Override global log verbosity level for the `create-federation-secret-job` pods. One of "trace", "debug", "info", "warn", or "error". # @type: string logLevel: "" @@ -631,15 +650,10 @@ global: # @type: boolean enableGatewayMetrics: true - # Configures the Helm chart’s components to forward envoy metrics for the Consul service mesh to the - # consul-telemetry-collector. This includes gateway metrics and sidecar metrics. - # @type: boolean - enableTelemetryCollector: false - # 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.5-dev + imageConsulDataplane: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.0-dev # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. @@ -652,19 +666,14 @@ global: # the API before cancelling the request. consulAPITimeout: 5s - # Enables installing an HCP Consul Central self-managed cluster. + # Enables installing an HCP Consul self-managed cluster. # Requires Consul v1.14+. cloud: - # If true, the Helm chart will link a [self-managed cluster to HCP](https://developer.hashicorp.com/hcp/docs/consul/self-managed). - # This can either be used to [configure a new cluster](https://developer.hashicorp.com/hcp/docs/consul/self-managed/new) - # or [link an existing one](https://developer.hashicorp.com/hcp/docs/consul/self-managed/existing). - # - # Note: this setting should not be enabled for [HashiCorp-managed clusters](https://developer.hashicorp.com/hcp/docs/consul/hcp-managed). - # It is strictly for linking self-managed clusters. + # If true, the Helm chart will enable the installation of an HCP Consul + # self-managed cluster. enabled: false - # The resource id of the HCP Consul Central cluster to link to. Eg: - # organization/27109cd4-a309-4bf3-9986-e1d071914b18/project/fcef6c24-259d-4510-bb8d-1d812e120e34/hashicorp.consul.global-network-manager.cluster/consul-cluster + # The name of the Kubernetes secret that holds the HCP resource id. # This is required when global.cloud.enabled is true. resourceId: # The name of the Kubernetes secret that holds the resource id. @@ -674,8 +683,7 @@ global: # @type: string secretKey: null - # The client id portion of a [service principal](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals#service-principals) with authorization to link the cluster - # in global.cloud.resourceId to HCP Consul Central. + # The name of the Kubernetes secret that holds the HCP cloud client id. # This is required when global.cloud.enabled is true. clientId: # The name of the Kubernetes secret that holds the client id. @@ -685,8 +693,7 @@ global: # @type: string secretKey: null - # The client secret portion of a [service principal](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals#service-principals) with authorization to link the cluster - # in global.cloud.resourceId to HCP Consul Central. + # The name of the Kubernetes secret that holds the HCP cloud client secret. # This is required when global.cloud.enabled is true. clientSecret: # The name of the Kubernetes secret that holds the client secret. @@ -696,7 +703,8 @@ global: # @type: string secretKey: null - # The hostname of HCP's API. This setting is used for internal testing and validation. + # The name of the Kubernetes secret that holds the HCP cloud client id. + # This is optional when global.cloud.enabled is true. apiHost: # The name of the Kubernetes secret that holds the api hostname. # @type: string @@ -705,7 +713,8 @@ global: # @type: string secretKey: null - # The URL of HCP's auth API. This setting is used for internal testing and validation. + # The name of the Kubernetes secret that holds the HCP cloud authorization url. + # This is optional when global.cloud.enabled is true. authUrl: # The name of the Kubernetes secret that holds the authorization url. # @type: string @@ -714,7 +723,8 @@ global: # @type: string secretKey: null - # The address of HCP's scada service. This setting is used for internal testing and validation. + # The name of the Kubernetes secret that holds the HCP cloud scada address. + # This is optional when global.cloud.enabled is true. scadaAddress: # The name of the Kubernetes secret that holds the scada address. # @type: string @@ -736,46 +746,6 @@ global: # @type: map extraLabels: {} - # Optional PEM-encoded CA certificates that will be added to trusted system CAs. - # - # Example: - # - # ```yaml - # trustedCAs: [ - # | - # -----BEGIN CERTIFICATE----- - # MIIC7jCCApSgAwIBAgIRAIq2zQEVexqxvtxP6J0bXAwwCgYIKoZIzj0EAwIwgbkx - # ... - # ] - # ``` - # @type: array - trustedCAs: [] - - # Consul feature flags that will be enabled across components. - # Supported feature flags: - # * `resource-apis`: - # _**Danger**_! This feature is under active development. It is not - # recommended for production use. Setting this flag during an - # upgrade could risk breaking your Consul cluster. - # If this flag is set, Consul components will use the - # V2 resources APIs for all operations. - # * `v2tenancy`: - # _**Danger**_! This feature is under active development. It is not - # recommended for production use. Setting this flag during an - # upgrade could risk breaking your Consul cluster. - # If this flag is set, Consul V2 resources (catalog, mesh, auth, etc) - # will use V2 implementations for tenancy (partitions and namesapces) - # instead of bridging to the existing V1 implementations. The - # `resource-apis` feature flag must also be set. - # - # Example: - # - # ```yaml - # experiments: [ "resource-apis" ] - # ``` - # @type: array - experiments: [] - # Server, when enabled, configures a server cluster to run. This should # be disabled if you plan on connecting to a Consul cluster external to # the Kube cluster. @@ -893,21 +863,6 @@ server: # @type: string storageClass: null - # The [Persistent Volume Claim (PVC) retention policy](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#persistentvolumeclaim-retention) - # controls if and how PVCs are deleted during the lifecycle of a StatefulSet. - # WhenDeleted specifies what happens to PVCs created from StatefulSet VolumeClaimTemplates when the StatefulSet is deleted, - # and WhenScaled specifies what happens to PVCs created from StatefulSet VolumeClaimTemplates when the StatefulSet is scaled down. - # - # Example: - # - # ```yaml - # persistentVolumeClaimRetentionPolicy: - # whenDeleted: Retain - # whenScaled: Retain - # ``` - # @type: map - persistentVolumeClaimRetentionPolicy: null - # This will enable/disable [service mesh](https://developer.hashicorp.com/consul/docs/connect). Setting this to true # _will not_ automatically secure pod communication, this # setting will only enable usage of the feature. Consul will automatically initialize @@ -1005,14 +960,8 @@ server: # the server cluster is enabled. To disable, set to `false`. enabled: true - # The maximum number of unavailable pods. In most cases you should not change this as it is automatically set to - # the correct number when left as null. This setting has been kept to not break backwards compatibility. - # - # By default, this is set to 1 internally in the chart. When server pods are stopped gracefully, they leave the Raft - # consensus pool. When running an odd number of servers, one server leaving the pool does not change the quorum - # size, and so fault tolerance is not affected. However, if more than one server were to leave the pool, the quorum - # size would change. That's why this is set to 1 internally and should not be changed in most cases. - # + # The maximum number of unavailable pods. By default, this will be + # automatically computed based on the `server.replicas` value to be `(n/2)-1`. # If you need to set this to `0`, you will need to add a # --set 'server.disruptionBudget.maxUnavailable=0'` flag to the helm chart installation # command because of a limitation in the Helm templating language. @@ -1342,43 +1291,6 @@ server: # @type: array sinks: [] - # Settings for potentially limiting timeouts, rate limiting on clients as well - # as servers, and other settings to limit exposure too many requests, requests - # waiting for too long, and other runtime considerations. - limits: - # This object specifies configurations that limit the rate of RPC and gRPC - # requests on the Consul server. Limiting the rate of gRPC and RPC requests - # also limits HTTP requests to the Consul server. - # https://developer.hashicorp.com/consul/docs/agent/config/config-files#request_limits - requestLimits: - # Setting for disabling or enabling rate limiting. If not disabled, it - # enforces the action that will occur when RequestLimitsReadRate - # or RequestLimitsWriteRate is exceeded. The default value of "disabled" will - # prevent any rate limiting from occuring. A value of "enforce" will block - # the request from processings by returning an error. A value of - # "permissive" will not block the request and will allow the request to - # continue processing. - # @type: string - mode: "disabled" - - # Setting that controls how frequently RPC, gRPC, and HTTP - # queries are allowed to happen. In any large enough time interval, rate - # limiter limits the rate to RequestLimitsReadRate tokens per second. - # - # See https://en.wikipedia.org/wiki/Token_bucket for more about token - # buckets. - # @type: integer - readRate: -1 - - # Setting that controls how frequently RPC, gRPC, and HTTP - # writes are allowed to happen. In any large enough time interval, rate - # limiter limits the rate to RequestLimitsWriteRate tokens per second. - # - # See https://en.wikipedia.org/wiki/Token_bucket for more about token - # buckets. - # @type: integer - writeRate: -1 - # Configuration for Consul servers when the servers are running outside of Kubernetes. # When running external servers, configuring these values is recommended # if setting `global.tls.enableAutoEncrypt` to true @@ -2209,101 +2121,6 @@ connectInject: # @type: integer minAvailable: null - # Configuration settings for the Consul API Gateway integration. - apiGateway: - # Enables Consul on Kubernetes to manage the CRDs used for Gateway API. - # Setting this to true will install the CRDs used for the Gateway API when Consul on Kubernetes is installed. - # These CRDs can clash with existing Gateway API CRDs if they are already installed in your cluster. - # If this setting is false, you will need to install the Gateway API CRDs manually. - manageExternalCRDs: true - - # Enables Consul on Kubernets to manage only the non-standard CRDs used for Gateway API. If manageExternalCRDs is true - # then all CRDs will be installed; otherwise, if manageNonStandardCRDs is true then only TCPRoute, GatewayClassConfig and MeshService - # will be installed. - manageNonStandardCRDs: false - - # Configuration settings for the GatewayClass installed by Consul on Kubernetes. - managedGatewayClass: - # This value defines [`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) - # labels for gateway pod assignment, formatted as a multi-line string. - # - # Example: - # - # ```yaml - # nodeSelector: | - # beta.kubernetes.io/arch: amd64 - # ``` - # - # @type: string - nodeSelector: null - - # Toleration settings for gateway pods created with the managed gateway class. - # This should be a multi-line string matching the - # [Tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. - # - # @type: string - tolerations: null - - # This value defines the type of Service created for gateways (e.g. LoadBalancer, ClusterIP) - serviceType: LoadBalancer - - # Configuration settings for annotations to be copied from the Gateway to other child resources. - copyAnnotations: - # This value defines a list of annotations to be copied from the Gateway to the Service created, formatted as a multi-line string. - # - # Example: - # - # ```yaml - # service: - # annotations: | - # - external-dns.alpha.kubernetes.io/hostname - # ``` - # - # @type: string - service: null - - # The resource settings for Pods handling traffic for Gateway API. - # @recurse: false - # @type: map - resources: - requests: - memory: "100Mi" - cpu: "100m" - limits: - memory: "100Mi" - cpu: "100m" - - # This value defines the number of pods to deploy for each Gateway as well as a min and max number of pods for all Gateways - deployment: - defaultInstances: 1 - maxInstances: 1 - minInstances: 1 - - # The name of the OpenShift SecurityContextConstraints resource to use for Gateways. - # Only applicable if `global.openshift.enabled` is true. - # @type: string - openshiftSCCName: "restricted-v2" - - # This value defines the amount we will add to privileged container ports on gateways that use this class. - # This is useful if you don't want to give your containers extra permissions to run privileged ports. - # Example: The gateway listener is defined on port 80, but the underlying value of the port on the container - # will be the 80 + the number defined below. - mapPrivilegedContainerPorts: 0 - - # Configuration for the ServiceAccount created for the api-gateway component - serviceAccount: - # This value defines additional annotations for the client service account. This should be formatted as a multi-line - # string. - # - # ```yaml - # annotations: | - # "sample/annotation1": "foo" - # "sample/annotation2": "bar" - # ``` - # - # @type: string - annotations: null - # Configures consul-cni plugin for Consul Service mesh services cni: # If true, then all traffic redirection setup uses the consul-cni plugin. @@ -2715,13 +2532,6 @@ connectInject: # @type: string defaultGracefulShutdownPath: "/graceful_shutdown" - # Configures how long the k8s startup probe will wait before the proxy is considered to be unhealthy and the container is restarted. - # A value of zero disables the probe. - defaultStartupFailureSeconds: 0 - # Configures how long the k8s liveness probe will wait before the proxy is considered to be unhealthy and the container is restarted. - # A value of zero disables the probe. - defaultLivenessFailureSeconds: 0 - # The resource settings for the Connect injected init container. If null, the resources # won't be set for the initContainer. The defaults are optimized for developer instances of # Kubernetes, however they should be tweaked with the recommended defaults as shown below to speed up service registration times. @@ -2751,7 +2561,7 @@ meshGateway: # Requirements: consul 1.6.0+ if using `global.acls.manageSystemACLs``. enabled: false - # Override global log verbosity level for mesh-gateway-deployment pods. One of "trace", "debug", "info", "warn", or "error". + # Override global log verbosity level for `mesh-gateway-deployment` pods. One of "trace", "debug", "info", "warn", or "error". # @type: string logLevel: "" @@ -2784,7 +2594,7 @@ meshGateway: # are routable from other datacenters. # # - `Static` - Use the address hardcoded in `meshGateway.wanAddress.static`. - source: Service + source: "Service" # Port that gets registered for WAN traffic. # If source is set to "Service" then this setting will have no effect. @@ -2964,10 +2774,11 @@ meshGateway: # for a specific gateway. # Requirements: consul >= 1.8.0 ingressGateways: - # Enable ingress gateway deployment. Requires `connectInject.enabled=true`. + # Enable ingress gateway deployment. Requires `connectInject.enabled=true` + # and `client.enabled=true`. enabled: false - # Override global log verbosity level for ingress-gateways-deployment pods. One of "trace", "debug", "info", "warn", or "error". + # Override global log verbosity level for `ingress-gateways-deployment` pods. One of "trace", "debug", "info", "warn", or "error". # @type: string logLevel: "" @@ -3135,7 +2946,8 @@ ingressGateways: # for a specific gateway. # Requirements: consul >= 1.8.0 terminatingGateways: - # Enable terminating gateway deployment. Requires `connectInject.enabled=true`. + # Enable terminating gateway deployment. Requires `connectInject.enabled=true` + # and `client.enabled=true`. enabled: false # Override global log verbosity level. One of "trace", "debug", "info", "warn", or "error". @@ -3272,7 +3084,6 @@ terminatingGateways: gateways: - name: terminating-gateway -# [DEPRECATED] Use connectInject.apiGateway instead. # Configuration settings for the Consul API Gateway integration apiGateway: # When true the helm chart will install the Consul API Gateway controller @@ -3287,7 +3098,7 @@ apiGateway: # The name (and tag) of the Envoy Docker image used for the # apiGateway. For other Consul compoenents, imageEnvoy has been replaced with Consul Dataplane. # @default: envoyproxy/envoy: - imageEnvoy: "envoyproxy/envoy:v1.25.11" + imageEnvoy: "envoyproxy/envoy:v1.24.12" # Override global log verbosity level for api-gateway-controller pods. One of "debug", "info", "warn", or "error". # @type: string @@ -3474,137 +3285,3 @@ prometheus: # is only useful when running helm template. tests: enabled: true - -telemetryCollector: - # Enables the consul-telemetry-collector deployment - # @type: boolean - enabled: false - - # Override global log verbosity level. One of "trace", "debug", "info", "warn", or "error". - # @type: string - logLevel: "" - - # The name of the Docker image (including any tag) for the containers running - # the consul-telemetry-collector - # @type: string - image: "hashicorp/consul-telemetry-collector:0.0.2" - - # The resource settings for consul-telemetry-collector pods. - # @recurse: false - # @type: map - resources: - requests: - memory: "512Mi" - cpu: "1000m" - limits: - memory: "512Mi" - cpu: "1000m" - - # This value sets the number of consul-telemetry-collector replicas to deploy. - replicas: 1 - - # This value defines additional configuration for the telemetry collector. It should be formatted as a multi-line - # json blob string - # - # ```yaml - # customExporterConfig: | - # {"http_collector_endpoint": "other-otel-collector"} - # ``` - # - # @type: string - customExporterConfig: null - - service: - # This value defines additional annotations for the telemetry-collector's service account. This should be formatted as a multi-line - # string. - # - # ```yaml - # annotations: | - # "sample/annotation1": "foo" - # "sample/annotation2": "bar" - # ``` - # - # @type: string - annotations: null - - serviceAccount: - # This value defines additional annotations for the telemetry-collector's service account. This should be formatted - # as a multi-line string. - # - # ```yaml - # annotations: | - # "sample/annotation1": "foo" - # "sample/annotation2": "bar" - # ``` - # - # @type: string - annotations: null - - cloud: - # The resource id of the HCP Consul Central cluster to push metrics for. Eg: - # `organization/27109cd4-a309-4bf3-9986-e1d071914b18/project/fcef6c24-259d-4510-bb8d-1d812e120e34/hashicorp.consul.global-network-manager.cluster/consul-cluster` - # - # This is used for HCP Consul Central-linked or managed clusters where global.cloud.resourceId is unset. For example, when using externalServers - # with HCP Consul-managed clusters or HCP Consul Central-linked clusters in a different admin partition. - # - # If global.cloud.resourceId is set, this should either be unset (defaulting to global.cloud.resourceId) or be the same as global.cloud.resourceId. - # - # @default: global.cloud.resourceId - resourceId: - # The name of the Kubernetes secret that holds the resource id. - # @type: string - secretName: null - # The key within the Kubernetes secret that holds the resource id. - # @type: string - secretKey: null - - # The client id portion of a [service principal](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals#service-principals) with authorization to push metrics to HCP - # - # This is set in two scenarios: - # - the service principal in global.cloud is unset - # - the HCP UI provides a service principal with more narrowly scoped permissions that the service principal used in global.cloud - # - # @default: global.cloud.clientId - clientId: - # The name of the Kubernetes secret that holds the client id. - # @type: string - secretName: null - # The key within the Kubernetes secret that holds the client id. - # @type: string - secretKey: null - - # The client secret portion of a [service principal](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals#service-principals) with authorization to push metrics to HCP. - # - # This is set in two scenarios: - # - the service principal in global.cloud is unset - # - the HCP UI provides a service principal with more narrowly scoped permissions that the service principal used in global.cloud - # - # @default: global.cloud.clientSecret - clientSecret: - # The name of the Kubernetes secret that holds the client secret. - # @type: string - secretName: null - # The key within the Kubernetes secret that holds the client secret. - # @type: string - secretKey: null - - initContainer: - # The resource settings for consul-telemetry-collector initContainer. - # @recurse: false - # @type: map - resources: {} - - # Optional YAML string to specify a nodeSelector config. - # @type: string - nodeSelector: null - - # Optional priorityClassName. - # @type: string - priorityClassName: "" - - # A list of extra environment variables to set within the deployment. - # These could be used to include proxy settings required for cloud auto-join - # feature, in case kubernetes cluster is behind egress http proxies. Additionally, - # it could be used to configure custom consul parameters. - # @type: map - extraEnvironmentVars: {} diff --git a/charts/demo/Chart.yaml b/charts/demo/Chart.yaml index ffd518082f..82fc51d2df 100644 --- a/charts/demo/Chart.yaml +++ b/charts/demo/Chart.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v2 name: consul-demo description: A Helm chart for Consul demo app diff --git a/charts/demo/templates/frontend.yaml b/charts/demo/templates/frontend.yaml index f64aaa24a8..38d466e87e 100644 --- a/charts/demo/templates/frontend.yaml +++ b/charts/demo/templates/frontend.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Service metadata: diff --git a/charts/demo/templates/intentions.yaml b/charts/demo/templates/intentions.yaml index ef36025b16..e0a0a0a5b1 100644 --- a/charts/demo/templates/intentions.yaml +++ b/charts/demo/templates/intentions.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceIntentions metadata: @@ -58,17 +55,6 @@ spec: --- apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceIntentions -metadata: - name: consul-telemetry-collector -spec: - destination: - name: 'consul-telemetry-collector' - sources: - - name: '*' - action: allow ---- -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceIntentions metadata: name: deny-all spec: diff --git a/charts/demo/templates/nginx.yaml b/charts/demo/templates/nginx.yaml index 3d37535733..ebca16f2a0 100644 --- a/charts/demo/templates/nginx.yaml +++ b/charts/demo/templates/nginx.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: v1 kind: Service diff --git a/charts/demo/templates/payments.yaml b/charts/demo/templates/payments.yaml index af36c62fb4..362a7ec1e1 100644 --- a/charts/demo/templates/payments.yaml +++ b/charts/demo/templates/payments.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: v1 kind: Service diff --git a/charts/demo/templates/postgres.yaml b/charts/demo/templates/postgres.yaml index 033a6724d8..5c7c903b7c 100644 --- a/charts/demo/templates/postgres.yaml +++ b/charts/demo/templates/postgres.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: v1 kind: Service diff --git a/charts/demo/templates/products-api.yaml b/charts/demo/templates/products-api.yaml index 22119f86d5..4e2fc4bea8 100644 --- a/charts/demo/templates/products-api.yaml +++ b/charts/demo/templates/products-api.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: v1 kind: Service diff --git a/charts/demo/templates/public-api.yaml b/charts/demo/templates/public-api.yaml index a397ddad35..14d4369ff8 100644 --- a/charts/demo/templates/public-api.yaml +++ b/charts/demo/templates/public-api.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: v1 kind: Service metadata: diff --git a/charts/demo/values.yaml b/charts/demo/values.yaml index 4509c555cb..2dd99602c7 100644 --- a/charts/demo/values.yaml +++ b/charts/demo/values.yaml @@ -1,4 +1 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # Default values for demo. diff --git a/charts/embed_chart.go b/charts/embed_chart.go index 8e36abba23..29e7e9635e 100644 --- a/charts/embed_chart.go +++ b/charts/embed_chart.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package charts import "embed" @@ -15,7 +12,6 @@ import "embed" // // The embed directive does not include files with underscores unless explicitly listed, which is why _helpers.tpl is // explicitly embedded. - //go:embed consul/Chart.yaml consul/values.yaml consul/templates consul/templates/_helpers.tpl var ConsulHelmChart embed.FS diff --git a/charts/go.mod b/charts/go.mod index f76282d756..cdb23e46b0 100644 --- a/charts/go.mod +++ b/charts/go.mod @@ -1,3 +1,3 @@ module github.com/hashicorp/consul-k8s/charts -go 1.20 +go 1.19 diff --git a/cli/cmd/config/command.go b/cli/cmd/config/command.go index 302b054bd9..5e44677ff6 100644 --- a/cli/cmd/config/command.go +++ b/cli/cmd/config/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package config import ( diff --git a/cli/cmd/config/read/command.go b/cli/cmd/config/read/command.go index 0565294bd0..e2258bd013 100644 --- a/cli/cmd/config/read/command.go +++ b/cli/cmd/config/read/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package read import ( diff --git a/cli/cmd/config/read/command_test.go b/cli/cmd/config/read/command_test.go index 07275f872a..a3716cf3c1 100644 --- a/cli/cmd/config/read/command_test.go +++ b/cli/cmd/config/read/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package read import ( diff --git a/cli/cmd/install/install.go b/cli/cmd/install/install.go index f3d4671c6e..7b5d5bb31c 100644 --- a/cli/cmd/install/install.go +++ b/cli/cmd/install/install.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package install import ( @@ -592,7 +589,7 @@ func (c *Command) validateFlags(args []string) error { return fmt.Errorf("cannot set both -%s and -%s", flagNameConfigFile, flagNamePreset) } if ok := slices.Contains(preset.Presets, c.flagPreset); c.flagPreset != defaultPreset && !ok { - return fmt.Errorf("'%s' is not a valid preset (valid presets: %s)", c.flagPreset, strings.Join(preset.Presets, ", ")) + return fmt.Errorf("'%s' is not a valid preset", c.flagPreset) } if !common.IsValidLabel(c.flagNamespace) { return fmt.Errorf("'%s' is an invalid namespace. Namespaces follow the RFC 1123 label convention and must "+ diff --git a/cli/cmd/install/install_test.go b/cli/cmd/install/install_test.go index c34eac9ac3..04c250b1e4 100644 --- a/cli/cmd/install/install_test.go +++ b/cli/cmd/install/install_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package install import ( @@ -165,45 +162,39 @@ func TestValidateFlags(t *testing.T) { testCases := []struct { description string input []string - expErr string }{ { "Should disallow non-flag arguments.", []string{"foo", "-auto-approve"}, - "should have no non-flag arguments", }, { "Should disallow specifying both values file AND presets.", []string{"-f='f.txt'", "-preset=demo"}, - "cannot set both -config-file and -preset", }, { "Should error on invalid presets.", []string{"-preset=foo"}, - "'foo' is not a valid preset (valid presets: cloud, quickstart, secure)", }, { "Should error on invalid timeout.", []string{"-timeout=invalid-timeout"}, - "unable to parse -timeout: time: invalid duration \"invalid-timeout\"", }, { "Should error on an invalid namespace. If this failed, TestValidLabel() probably did too.", []string{"-namespace=\" nsWithSpace\""}, - "'\" nsWithSpace\"' is an invalid namespace. Namespaces follow the RFC 1123 label convention and must consist of a lower case alphanumeric character or '-' and must start/end with an alphanumeric character", }, { - "Should have errored on a non-existent file.", + "Should have errored on a non-existant file.", []string{"-f=\"does_not_exist.txt\""}, - "file '\"does_not_exist.txt\"' does not exist", }, } for _, testCase := range testCases { c := getInitializedCommand(t, nil) t.Run(testCase.description, func(t *testing.T) { - err := c.validateFlags(testCase.input) - require.EqualError(t, err, testCase.expErr) + if err := c.validateFlags(testCase.input); err == nil { + t.Errorf("Test case should have failed.") + } }) } } @@ -567,7 +558,7 @@ func TestInstall(t *testing.T) { }, messages: []string{ "\n==> Checking if Consul can be installed\n ✓ No existing Consul installations found.\n ✓ No existing Consul persistent volume claims found\n ✓ No existing Consul secrets found.\n", - "\n==> Consul Installation Summary\n Name: consul\n Namespace: consul\n \n Helm value overrides\n --------------------\n connectInject:\n enabled: true\n metrics:\n defaultEnableMerging: true\n defaultEnabled: true\n enableGatewayMetrics: true\n global:\n metrics:\n enableAgentMetrics: true\n enabled: true\n name: consul\n prometheus:\n enabled: true\n server:\n replicas: 1\n ui:\n enabled: true\n service:\n enabled: true\n \n", + "\n==> Consul Installation Summary\n Name: consul\n Namespace: consul\n \n Helm value overrides\n --------------------\n connectInject:\n enabled: true\n metrics:\n defaultEnableMerging: true\n defaultEnabled: true\n enableGatewayMetrics: true\n controller:\n enabled: true\n global:\n metrics:\n enableAgentMetrics: true\n enabled: true\n name: consul\n prometheus:\n enabled: true\n server:\n replicas: 1\n ui:\n enabled: true\n service:\n enabled: true\n \n", "\n==> Installing Consul\n ✓ Downloaded charts.\n ✓ Consul installed in namespace \"consul\".\n", }, helmActionsRunner: &helm.MockActionRunner{}, @@ -583,7 +574,7 @@ func TestInstall(t *testing.T) { }, messages: []string{ "\n==> Checking if Consul can be installed\n ✓ No existing Consul installations found.\n ✓ No existing Consul persistent volume claims found\n ✓ No existing Consul secrets found.\n", - "\n==> Consul Installation Summary\n Name: consul\n Namespace: consul\n \n Helm value overrides\n --------------------\n connectInject:\n enabled: true\n global:\n acls:\n manageSystemACLs: true\n gossipEncryption:\n autoGenerate: true\n name: consul\n tls:\n enableAutoEncrypt: true\n enabled: true\n server:\n replicas: 1\n \n", + "\n==> Consul Installation Summary\n Name: consul\n Namespace: consul\n \n Helm value overrides\n --------------------\n connectInject:\n enabled: true\n controller:\n enabled: true\n global:\n acls:\n manageSystemACLs: true\n gossipEncryption:\n autoGenerate: true\n name: consul\n tls:\n enableAutoEncrypt: true\n enabled: true\n server:\n replicas: 1\n \n", "\n==> Installing Consul\n ✓ Downloaded charts.\n ✓ Consul installed in namespace \"consul\".\n", }, helmActionsRunner: &helm.MockActionRunner{}, diff --git a/cli/cmd/proxy/command.go b/cli/cmd/proxy/command.go index e15469fb37..bc55ee0312 100644 --- a/cli/cmd/proxy/command.go +++ b/cli/cmd/proxy/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package proxy import ( diff --git a/cli/cmd/proxy/list/command.go b/cli/cmd/proxy/list/command.go index 0204832c44..cbecda79a1 100644 --- a/cli/cmd/proxy/list/command.go +++ b/cli/cmd/proxy/list/command.go @@ -1,25 +1,20 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package list import ( - "encoding/json" "errors" "fmt" "strings" "sync" + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" "github.com/posener/complete" 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/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" ) const ( @@ -27,7 +22,6 @@ const ( flagNameAllNamespaces = "all-namespaces" flagNameKubeConfig = "kubeconfig" flagNameKubeContext = "context" - flagOutputFormat = "output-format" ) // ListCommand is the command struct for the proxy list command. @@ -40,7 +34,6 @@ type ListCommand struct { flagNamespace string flagAllNamespaces bool - flagOutputFormat string flagKubeConfig string flagKubeContext string @@ -67,13 +60,6 @@ func (c *ListCommand) init() { Usage: "List pods in all namespaces.", Aliases: []string{"A"}, }) - f.StringVar(&flag.StringVar{ - Name: flagOutputFormat, - Default: "table", - Target: &c.flagOutputFormat, - Usage: "Output format", - Aliases: []string{"o"}, - }) f = c.set.NewSet("Global Options") f.StringVar(&flag.StringVar{ @@ -148,7 +134,6 @@ func (c *ListCommand) AutocompleteFlags() complete.Flags { fmt.Sprintf("-%s", flagNameAllNamespaces): complete.PredictNothing, fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, - fmt.Sprintf("-%s", flagOutputFormat): complete.PredictNothing, } } @@ -212,51 +197,21 @@ func (c *ListCommand) fetchPods() ([]v1.Pod, error) { // 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", + LabelSelector: "component in (ingress-gateway, mesh-gateway, terminating-gateway), chart=consul-helm", }) if err != nil { return nil, err } 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. + // Fetch all pods in the namespace with a label indicating they are an API gateway. apigatewaypods, err := c.kubernetes.CoreV1().Pods(c.namespace()).List(c.Ctx, metav1.ListOptions{ LabelSelector: "api-gateway.consul.hashicorp.com/managed=true", }) - - namespacedName := func(pod v1.Pod) string { - return pod.Namespace + pod.Name - } if err != nil { return nil, err } - 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) - } - - } - } - //--- + pods = append(pods, apigatewaypods.Items...) // 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{ @@ -299,22 +254,22 @@ func (c *ListCommand) output(pods []v1.Pod) { // Get the type for ingress, mesh, and terminating gateways. switch pod.Labels["component"] { - case "api-gateway": - proxyType = "API Gateway" case "ingress-gateway": proxyType = "Ingress Gateway" case "mesh-gateway": proxyType = "Mesh Gateway" case "terminating-gateway": proxyType = "Terminating Gateway" - default: - // Fallback to "Sidecar" as a default - proxyType = "Sidecar" + } - // Determine if deprecated API Gateway pod. - if pod.Labels["api-gateway.consul.hashicorp.com/managed"] == "true" { - proxyType = "API Gateway" - } + // Determine if the pod is an API Gateway. + if pod.Labels["api-gateway.consul.hashicorp.com/managed"] == "true" { + proxyType = "API Gateway" + } + + // Fallback to "Sidecar" as a default + if proxyType == "" { + proxyType = "Sidecar" } if c.flagAllNamespaces { @@ -324,16 +279,5 @@ func (c *ListCommand) output(pods []v1.Pod) { } } - if c.flagOutputFormat == "json" { - tableJson := tbl.ToJson() - jsonSt, err := json.MarshalIndent(tableJson, "", " ") - if err != nil { - c.UI.Output("Error converting table to json: %v", err.Error(), terminal.WithErrorStyle()) - } else { - c.UI.Output(string(jsonSt)) - } - } else { - c.UI.Table(tbl) - } - + c.UI.Table(tbl) } diff --git a/cli/cmd/proxy/list/command_test.go b/cli/cmd/proxy/list/command_test.go index 5493ab88a9..b2c2cb6043 100644 --- a/cli/cmd/proxy/list/command_test.go +++ b/cli/cmd/proxy/list/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package list import ( @@ -274,33 +271,12 @@ func TestListCommandOutput(t *testing.T) { }, }, }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "depricated-api-gateway", - Namespace: "consul", - Labels: map[string]string{ - "api-gateway.consul.hashicorp.com/managed": "true", - }, - }, - }, { ObjectMeta: metav1.ObjectMeta{ Name: "api-gateway", Namespace: "consul", - Labels: map[string]string{ - "component": "api-gateway", - "chart": "consul-helm", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "both-labels-api-gateway", - Namespace: "consul", Labels: map[string]string{ "api-gateway.consul.hashicorp.com/managed": "true", - "component": "api-gateway", - "chart": "consul-helm", }, }, }, @@ -340,106 +316,6 @@ func TestListCommandOutput(t *testing.T) { } } -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{ - Name: "ingress-gateway", - Namespace: "default", - Labels: map[string]string{ - "component": "ingress-gateway", - "chart": "consul-helm", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "mesh-gateway", - Namespace: "consul", - Labels: map[string]string{ - "component": "mesh-gateway", - "chart": "consul-helm", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "terminating-gateway", - Namespace: "consul", - Labels: map[string]string{ - "component": "terminating-gateway", - "chart": "consul-helm", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "api-gateway", - Namespace: "consul", - Labels: map[string]string{ - "component": "api-gateway", - "chart": "consul-helm", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "both-labels-api-gateway", - Namespace: "consul", - Labels: map[string]string{ - "api-gateway.consul.hashicorp.com/managed": "true", - "component": "api-gateway", - "chart": "consul-helm", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "deprecated-api-gateway", - Namespace: "consul", - Labels: map[string]string{ - "api-gateway.consul.hashicorp.com/managed": "true", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dont-fetch", - Namespace: "default", - Labels: map[string]string{}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod1", - Namespace: "default", - Labels: map[string]string{ - "consul.hashicorp.com/connect-inject-status": "injected", - }, - }, - }, - } - client := fake.NewSimpleClientset(&v1.PodList{Items: pods}) - - buf := new(bytes.Buffer) - c := setupCommand(buf) - c.kubernetes = client - - 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) - } -} - func TestNoPodsFound(t *testing.T) { cases := map[string]struct { args []string diff --git a/cli/cmd/proxy/loglevel/command.go b/cli/cmd/proxy/loglevel/command.go deleted file mode 100644 index caf67d61de..0000000000 --- a/cli/cmd/proxy/loglevel/command.go +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package loglevel - -import ( - "context" - "errors" - "fmt" - "strings" - "sync" - - "github.com/posener/complete" - helmCLI "helm.sh/helm/v3/pkg/cli" - "k8s.io/apimachinery/pkg/api/validation" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/envoy" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" -) - -const ( - defaultAdminPort = 19000 - flagNameNamespace = "namespace" - flagNameUpdateLevel = "update-level" - flagNameReset = "reset" - flagNameKubeConfig = "kubeconfig" - flagNameKubeContext = "context" -) - -var ErrIncorrectArgFormat = errors.New("Exactly one positional argument is required: ") - -type LoggerConfig map[string]string - -var levelToColor = map[string]string{ - "trace": terminal.Green, - "debug": terminal.HiWhite, - "info": terminal.Blue, - "warning": terminal.Yellow, - "error": terminal.Red, - "critical": terminal.Magenta, - "off": "", -} - -type LogLevelCommand struct { - *common.BaseCommand - - kubernetes kubernetes.Interface - set *flag.Sets - - // Command Flags - podName string - namespace string - level string - reset bool - kubeConfig string - kubeContext string - - once sync.Once - help string - restConfig *rest.Config - envoyLoggingCaller func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) -} - -func (l *LogLevelCommand) init() { - l.Log.ResetNamed("loglevel") - l.set = flag.NewSets() - f := l.set.NewSet("Command Options") - f.StringVar(&flag.StringVar{ - Name: flagNameNamespace, - Target: &l.namespace, - Usage: "The namespace where the target Pod can be found.", - Aliases: []string{"n"}, - }) - - f.StringVar(&flag.StringVar{ - Name: flagNameUpdateLevel, - Target: &l.level, - Usage: "Update the level for the logger. Can be either `-update-level warning` to change all loggers to warning, or a comma delineated list of loggers with level can be passed like `-update-level grpc:warning,http:info` to only modify specific loggers.", - Aliases: []string{"u"}, - }) - - f.BoolVar(&flag.BoolVar{ - Name: flagNameReset, - Target: &l.reset, - Usage: "Reset the log level for all loggers in a pod to the Envoy default (info).", - Aliases: []string{"r"}, - }) - - f = l.set.NewSet("Global Options") - f.StringVar(&flag.StringVar{ - Name: flagNameKubeConfig, - Aliases: []string{"c"}, - Target: &l.kubeConfig, - Usage: "Set the path to kubeconfig file.", - }) - f.StringVar(&flag.StringVar{ - Name: flagNameKubeContext, - Target: &l.kubeContext, - Usage: "Set the Kubernetes context to use.", - }) - - l.help = l.set.Help() -} - -func (l *LogLevelCommand) Run(args []string) int { - l.once.Do(l.init) - defer common.CloseWithError(l.BaseCommand) - - err := l.parseFlags(args) - if err != nil { - return l.logOutputAndDie(err) - } - err = l.validateFlags() - if err != nil { - return l.logOutputAndDie(err) - } - - // if we're resetting the default log level for envoy is info: https://www.envoyproxy.io/docs/envoy/latest/start/quick-start/run-envoy#debugging-envoy - if l.reset { - l.level = "info" - } - - if l.envoyLoggingCaller == nil { - l.envoyLoggingCaller = envoy.CallLoggingEndpoint - } - - err = l.initKubernetes() - if err != nil { - return l.logOutputAndDie(err) - } - - adminPorts, err := l.fetchAdminPorts() - if err != nil { - return l.logOutputAndDie(err) - } - - err = l.fetchOrSetLogLevels(adminPorts) - if err != nil { - return l.logOutputAndDie(err) - } - - return 0 -} - -func (l *LogLevelCommand) parseFlags(args []string) error { - if len(args) == 0 { - return ErrIncorrectArgFormat - } - - positional := []string{} - // Separate positional args from keyed args - for _, arg := range args { - if strings.HasPrefix(arg, "-") { - break - } - positional = append(positional, arg) - } - keyed := args[len(positional):] - - if len(positional) != 1 { - return ErrIncorrectArgFormat - } - - l.podName = positional[0] - - err := l.set.Parse(keyed) - if err != nil { - return err - } - - return nil -} - -func (l *LogLevelCommand) validateFlags() error { - if l.level != "" && l.reset { - return fmt.Errorf("cannot set log level to %q and reset to 'info' at the same time", l.level) - } - if l.namespace == "" { - return nil - } - - errs := validation.ValidateNamespaceName(l.namespace, false) - if len(errs) > 0 { - return fmt.Errorf("invalid namespace name passed for -namespace/-n: %v", strings.Join(errs, "; ")) - } - - return nil -} - -func (l *LogLevelCommand) initKubernetes() error { - settings := helmCLI.New() - var err error - - if l.kubeConfig != "" { - settings.KubeConfig = l.kubeConfig - } - - if l.kubeContext != "" { - settings.KubeContext = l.kubeContext - } - - if l.restConfig == nil { - l.restConfig, err = settings.RESTClientGetter().ToRESTConfig() - if err != nil { - return fmt.Errorf("error creating Kubernetes REST config %v", err) - } - - } - - if l.kubernetes == nil { - l.kubernetes, err = kubernetes.NewForConfig(l.restConfig) - if err != nil { - return fmt.Errorf("error creating Kubernetes client %v", err) - } - } - if l.namespace == "" { - l.namespace = settings.Namespace() - } - - return nil -} - -// fetchAdminPorts retrieves all admin ports for Envoy Proxies running in a pod given namespace. -func (l *LogLevelCommand) fetchAdminPorts() (map[string]int, error) { - adminPorts := make(map[string]int, 0) - pod, err := l.kubernetes.CoreV1().Pods(l.namespace).Get(l.Ctx, l.podName, metav1.GetOptions{}) - if err != nil { - return adminPorts, err - } - - connectService, isMultiport := pod.Annotations["consul.hashicorp.com/connect-service"] - - if !isMultiport { - // Return the default port configuration. - adminPorts[l.podName] = defaultAdminPort - return adminPorts, nil - } - - for idx, svc := range strings.Split(connectService, ",") { - adminPorts[svc] = defaultAdminPort + idx - } - - return adminPorts, nil -} - -func (l *LogLevelCommand) fetchOrSetLogLevels(adminPorts map[string]int) error { - loggers := make(map[string]LoggerConfig, 0) - - for name, port := range adminPorts { - pf := common.PortForward{ - Namespace: l.namespace, - PodName: l.podName, - RemotePort: port, - KubeClient: l.kubernetes, - RestConfig: l.restConfig, - } - params, err := parseParams(l.level) - if err != nil { - return err - } - logLevels, err := l.envoyLoggingCaller(l.Ctx, &pf, params) - if err != nil { - return err - } - loggers[name] = logLevels - } - - l.outputLevels(loggers) - return nil -} - -func parseParams(params string) (*envoy.LoggerParams, error) { - loggerParams := envoy.NewLoggerParams() - if len(params) == 0 { - return loggerParams, nil - } - - // contains global log level change - if !strings.Contains(params, ":") { - err := loggerParams.SetGlobalLoggerLevel(params) - if err != nil { - return nil, err - } - return loggerParams, nil - } - - // contains changes to at least 1 specific log level - loggerChanges := strings.Split(params, ",") - - for _, logger := range loggerChanges { - levelValues := strings.Split(logger, ":") - err := loggerParams.SetLoggerLevel(levelValues[0], levelValues[1]) - if err != nil { - return nil, err - } - } - return loggerParams, nil -} - -func (l *LogLevelCommand) outputLevels(logLevels map[string]LoggerConfig) { - l.UI.Output(fmt.Sprintf("Envoy log configuration for %s in namespace default:", l.podName)) - for n, levels := range logLevels { - l.UI.Output(fmt.Sprintf("Log Levels for %s", n), terminal.WithHeaderStyle()) - table := terminal.NewTable("Name", "Level") - for name, level := range levels { - table.AddRow([]string{name, level}, []string{"", levelToColor[level]}) - } - l.UI.Table(table) - l.UI.Output("") - } -} - -func (l *LogLevelCommand) Help() string { - l.once.Do(l.init) - return fmt.Sprintf("%s\n\nUsage: consul-k8s proxy log [flags]\n\n%s", l.Synopsis(), l.help) -} - -func (l *LogLevelCommand) Synopsis() string { - return "Inspect and Modify the Envoy Log configuration for a given Pod." -} - -// AutocompleteFlags returns a mapping of supported flags and autocomplete -// options for this command. The map key for the Flags map should be the -// complete flag such as "-foo" or "--foo". -func (l *LogLevelCommand) AutocompleteFlags() complete.Flags { - return complete.Flags{ - fmt.Sprintf("-%s", flagNameNamespace): complete.PredictNothing, - fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), - fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, - } -} - -// AutocompleteArgs returns the argument predictor for this command. -// Since argument completion is not supported, this will return -// complete.PredictNothing. -func (l *LogLevelCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictNothing -} - -func (l *LogLevelCommand) logOutputAndDie(err error) int { - l.UI.Output(err.Error(), terminal.WithErrorStyle()) - l.UI.Output(fmt.Sprintf("\n%s", l.Help())) - return 1 -} diff --git a/cli/cmd/proxy/loglevel/command_test.go b/cli/cmd/proxy/loglevel/command_test.go deleted file mode 100644 index 87e0355c1e..0000000000 --- a/cli/cmd/proxy/loglevel/command_test.go +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package loglevel - -import ( - "bytes" - "context" - "fmt" - "io" - "os" - "regexp" - "testing" - - "github.com/stretchr/testify/require" - 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" - "github.com/hashicorp/consul-k8s/cli/common/envoy" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/go-hclog" -) - -func TestFlagParsingFails(t *testing.T) { - t.Parallel() - testCases := map[string]struct { - args []string - out int - }{ - "No args": { - args: []string{}, - out: 1, - }, - "Multiple podnames passed": { - args: []string{"podname", "podname2"}, - out: 1, - }, - "Nonexistent flag passed, -foo bar": { - args: []string{"podName", "-foo", "bar"}, - out: 1, - }, - "Invalid argument passed, -namespace YOLO": { - args: []string{"podName", "-namespace", "YOLO"}, - out: 1, - }, - } - podName := "now-this-is-pod-racing" - fakePod := v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Namespace: "default", - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - c := setupCommand(bytes.NewBuffer([]byte{})) - c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - c.envoyLoggingCaller = func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) { - return testLogConfig, nil - } - - out := c.Run(tc.args) - require.Equal(t, tc.out, out) - }) - } -} - -func TestFlagParsingSucceeds(t *testing.T) { - t.Parallel() - podName := "now-this-is-pod-racing" - testCases := map[string]struct { - args []string - podNamespace string - out int - }{ - "With single pod name": { - args: []string{podName}, - podNamespace: "default", - out: 0, - }, - "With single pod name and namespace": { - args: []string{podName, "-n", "another"}, - podNamespace: "another", - out: 0, - }, - "With single pod name and blanket level": { - args: []string{podName, "-u", "warning"}, - podNamespace: "default", - out: 0, - }, - "With single pod name and single level": { - args: []string{podName, "-u", "grpc:warning"}, - podNamespace: "default", - out: 0, - }, - "With single pod name and multiple levels": { - args: []string{podName, "-u", "grpc:warning,http:info"}, - podNamespace: "default", - out: 0, - }, - "With single pod name and blanket level full flag": { - args: []string{podName, "-update-level", "warning"}, - podNamespace: "default", - out: 0, - }, - "With single pod name and single level full flag": { - args: []string{podName, "-update-level", "grpc:warning"}, - podNamespace: "default", - out: 0, - }, - "With single pod name and multiple levels full flag": { - args: []string{podName, "-update-level", "grpc:warning,http:info"}, - podNamespace: "default", - out: 0, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - fakePod := v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Namespace: tc.podNamespace, - }, - } - - c := setupCommand(bytes.NewBuffer([]byte{})) - c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - c.envoyLoggingCaller = func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) { - return testLogConfig, nil - } - - out := c.Run(tc.args) - require.Equal(t, tc.out, out) - }) - } -} - -func TestOutputForGettingLogLevels(t *testing.T) { - t.Parallel() - podName := "now-this-is-pod-racing" - expectedHeader := fmt.Sprintf("Envoy log configuration for %s in namespace default:", podName) - fakePod := v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Namespace: "default", - }, - } - - buf := bytes.NewBuffer([]byte{}) - c := setupCommand(buf) - newLogLevel := "warning" - config := make(map[string]string, len(testLogConfig)) - for logger := range testLogConfig { - config[logger] = newLogLevel - } - - c.envoyLoggingCaller = func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) { - return config, nil - } - c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - - args := []string{podName, "-u", newLogLevel} - out := c.Run(args) - require.Equal(t, 0, out) - - actual := buf.String() - - require.Regexp(t, expectedHeader, actual) - require.Regexp(t, "Log Levels for now-this-is-pod-racing", actual) - for logger, level := range config { - require.Regexp(t, regexp.MustCompile(logger+`.*`+level), actual) - } -} - -func TestOutputForSettingLogLevels(t *testing.T) { - t.Parallel() - podName := "now-this-is-pod-racing" - expectedHeader := fmt.Sprintf("Envoy log configuration for %s in namespace default:", podName) - fakePod := v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Namespace: "default", - }, - } - - buf := bytes.NewBuffer([]byte{}) - c := setupCommand(buf) - c.envoyLoggingCaller = func(context.Context, common.PortForwarder, *envoy.LoggerParams) (map[string]string, error) { - return testLogConfig, nil - } - c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - - args := []string{podName, "-u", "warning"} - out := c.Run(args) - require.Equal(t, 0, out) - - actual := buf.String() - - require.Regexp(t, expectedHeader, actual) - require.Regexp(t, "Log Levels for now-this-is-pod-racing", actual) - for logger, level := range testLogConfig { - require.Regexp(t, regexp.MustCompile(logger+`.*`+level), actual) - } -} - -func TestHelp(t *testing.T) { - t.Parallel() - buf := bytes.NewBuffer([]byte{}) - c := setupCommand(buf) - expectedSynposis := "Inspect and Modify the Envoy Log configuration for a given Pod." - expectedUsage := `Usage: consul-k8s proxy log \[flags\]` - actual := c.Help() - require.Regexp(t, expectedSynposis, actual) - require.Regexp(t, expectedUsage, actual) -} - -func setupCommand(buf io.Writer) *LogLevelCommand { - log := hclog.New(&hclog.LoggerOptions{ - Name: "test", - Level: hclog.Debug, - Output: os.Stdout, - }) - - command := &LogLevelCommand{ - BaseCommand: &common.BaseCommand{ - Log: log, - UI: terminal.NewUI(context.Background(), buf), - }, - } - command.init() - return command -} - -var testLogConfig = map[string]string{ - "admin": "debug", - "alternate_protocols_cache": "debug", - "aws": "debug", - "assert": "debug", - "backtrace": "debug", - "cache_filter": "debug", - "client": "debug", - "config": "debug", - "connection": "debug", - "conn_handler": "debug", - "decompression": "debug", - "dns": "debug", - "dubbo": "debug", - "envoy_bug": "debug", - "ext_authz": "debug", - "ext_proc": "debug", - "rocketmq": "debug", - "file": "debug", - "filter": "debug", - "forward_proxy": "debug", - "grpc": "debug", - "happy_eyeballs": "debug", - "hc": "debug", - "health_checker": "debug", - "http": "debug", - "http2": "debug", - "hystrix": "debug", - "init": "debug", - "io": "debug", - "jwt": "debug", - "kafka": "debug", - "key_value_store": "debug", - "lua": "debug", - "main": "debug", - "matcher": "debug", - "misc": "debug", - "mongo": "debug", - "multi_connection": "debug", - "oauth2": "debug", - "quic": "debug", - "quic_stream": "debug", - "pool": "debug", - "rbac": "debug", - "rds": "debug", - "redis": "debug", - "router": "debug", - "runtime": "debug", - "stats": "debug", - "secret": "debug", - "tap": "debug", - "testing": "debug", - "thrift": "debug", - "tracing": "debug", - "upstream": "debug", - "udp": "debug", - "wasm": "debug", - "websocket": "debug", -} diff --git a/cli/cmd/proxy/read/command.go b/cli/cmd/proxy/read/command.go index 26ca33b045..ad2bb96303 100644 --- a/cli/cmd/proxy/read/command.go +++ b/cli/cmd/proxy/read/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package read import ( @@ -10,6 +7,9 @@ import ( "strings" "sync" + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" "github.com/posener/complete" helmCLI "helm.sh/helm/v3/pkg/cli" "k8s.io/apimachinery/pkg/api/validation" @@ -17,11 +17,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/utils/strings/slices" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/envoy" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" ) // defaultAdminPort is the port where the Envoy admin API is exposed. @@ -72,7 +67,7 @@ type ReadCommand struct { flagKubeConfig string flagKubeContext string - fetchConfig func(context.Context, common.PortForwarder) (*envoy.EnvoyConfig, error) + fetchConfig func(context.Context, common.PortForwarder) (*EnvoyConfig, error) restConfig *rest.Config @@ -82,7 +77,7 @@ type ReadCommand struct { func (c *ReadCommand) init() { if c.fetchConfig == nil { - c.fetchConfig = envoy.FetchConfig + c.fetchConfig = FetchConfig } c.set = flag.NewSets() @@ -325,8 +320,8 @@ func (c *ReadCommand) fetchAdminPorts() (map[string]int, error) { return adminPorts, nil } -func (c *ReadCommand) fetchConfigs(adminPorts map[string]int) (map[string]*envoy.EnvoyConfig, error) { - configs := make(map[string]*envoy.EnvoyConfig, 0) +func (c *ReadCommand) fetchConfigs(adminPorts map[string]int) (map[string]*EnvoyConfig, error) { + configs := make(map[string]*EnvoyConfig, 0) for name, adminPort := range adminPorts { pf := common.PortForward{ @@ -348,7 +343,7 @@ func (c *ReadCommand) fetchConfigs(adminPorts map[string]int) (map[string]*envoy return configs, nil } -func (c *ReadCommand) outputConfigs(configs map[string]*envoy.EnvoyConfig) error { +func (c *ReadCommand) outputConfigs(configs map[string]*EnvoyConfig) error { switch c.flagOutput { case Table: return c.outputTables(configs) @@ -401,7 +396,7 @@ func (c *ReadCommand) filterWarnings() []string { return warnings } -func (c *ReadCommand) outputTables(configs map[string]*envoy.EnvoyConfig) error { +func (c *ReadCommand) outputTables(configs map[string]*EnvoyConfig) error { if c.flagFQDN != "" || c.flagAddress != "" || c.flagPort != -1 { c.UI.Output("Filters applied", terminal.WithHeaderStyle()) @@ -436,7 +431,7 @@ func (c *ReadCommand) outputTables(configs map[string]*envoy.EnvoyConfig) error return nil } -func (c *ReadCommand) outputJSON(configs map[string]*envoy.EnvoyConfig) error { +func (c *ReadCommand) outputJSON(configs map[string]*EnvoyConfig) error { cfgs := make(map[string]interface{}) for name, config := range configs { cfg := make(map[string]interface{}) @@ -472,11 +467,11 @@ func (c *ReadCommand) outputJSON(configs map[string]*envoy.EnvoyConfig) error { return nil } -func (c *ReadCommand) outputRaw(configs map[string]*envoy.EnvoyConfig) error { +func (c *ReadCommand) outputRaw(configs map[string]*EnvoyConfig) error { cfgs := make(map[string]interface{}, 0) for name, config := range configs { var cfg interface{} - if err := json.Unmarshal(config.RawCfg, &cfg); err != nil { + if err := json.Unmarshal(config.rawCfg, &cfg); err != nil { return err } @@ -493,7 +488,7 @@ func (c *ReadCommand) outputRaw(configs map[string]*envoy.EnvoyConfig) error { return nil } -func (c *ReadCommand) outputClustersTable(clusters []envoy.Cluster) { +func (c *ReadCommand) outputClustersTable(clusters []Cluster) { if !c.shouldPrintTable(c.flagClusters) { return } @@ -501,16 +496,14 @@ func (c *ReadCommand) outputClustersTable(clusters []envoy.Cluster) { c.UI.Output(fmt.Sprintf("Clusters (%d)", len(clusters)), terminal.WithHeaderStyle()) table := terminal.NewTable("Name", "FQDN", "Endpoints", "Type", "Last Updated") for _, cluster := range clusters { - table.AddRow([]string{ - cluster.Name, cluster.FullyQualifiedDomainName, strings.Join(cluster.Endpoints, ", "), - cluster.Type, cluster.LastUpdated, - }, []string{}) + table.AddRow([]string{cluster.Name, cluster.FullyQualifiedDomainName, strings.Join(cluster.Endpoints, ", "), + cluster.Type, cluster.LastUpdated}, []string{}) } c.UI.Table(table) c.UI.Output("") } -func (c *ReadCommand) outputEndpointsTable(endpoints []envoy.Endpoint) { +func (c *ReadCommand) outputEndpointsTable(endpoints []Endpoint) { if !c.shouldPrintTable(c.flagEndpoints) { return } @@ -519,7 +512,7 @@ func (c *ReadCommand) outputEndpointsTable(endpoints []envoy.Endpoint) { c.UI.Table(formatEndpoints(endpoints)) } -func (c *ReadCommand) outputListenersTable(listeners []envoy.Listener) { +func (c *ReadCommand) outputListenersTable(listeners []Listener) { if !c.shouldPrintTable(c.flagListeners) { return } @@ -528,7 +521,7 @@ func (c *ReadCommand) outputListenersTable(listeners []envoy.Listener) { c.UI.Table(formatListeners(listeners)) } -func (c *ReadCommand) outputRoutesTable(routes []envoy.Route) { +func (c *ReadCommand) outputRoutesTable(routes []Route) { if !c.shouldPrintTable(c.flagRoutes) { return } @@ -537,7 +530,7 @@ func (c *ReadCommand) outputRoutesTable(routes []envoy.Route) { c.UI.Table(formatRoutes(routes)) } -func (c *ReadCommand) outputSecretsTable(secrets []envoy.Secret) { +func (c *ReadCommand) outputSecretsTable(secrets []Secret) { if !c.shouldPrintTable(c.flagSecrets) { return } diff --git a/cli/cmd/proxy/read/command_test.go b/cli/cmd/proxy/read/command_test.go index 992a9a3909..27f19e7370 100644 --- a/cli/cmd/proxy/read/command_test.go +++ b/cli/cmd/proxy/read/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package read import ( @@ -12,18 +9,16 @@ import ( "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" "github.com/stretchr/testify/require" 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" - "github.com/hashicorp/consul-k8s/cli/common/envoy" - cmnFlag "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/go-hclog" ) func TestFlagParsing(t *testing.T) { @@ -70,48 +65,38 @@ func TestReadCommandOutput(t *testing.T) { // These regular expressions must be present in the output. expectedHeader := fmt.Sprintf("Envoy configuration for %s in namespace default:", podName) expected := map[string][]string{ - "-clusters": { - "==> Clusters \\(5\\)", + "-clusters": {"==> Clusters \\(5\\)", "Name.*FQDN.*Endpoints.*Type.*Last Updated", "local_agent.*192\\.168\\.79\\.187:8502.*STATIC.*2022-05-13T04:22:39\\.553Z", "local_app.*127\\.0\\.0\\.1:8080.*STATIC.*2022-05-13T04:22:39\\.655Z", "client.*client\\.default\\.dc1\\.internal\\.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00\\.consul.*EDS", "frontend.*frontend\\.default\\.dc1\\.internal\\.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00\\.consul", - "original-destination.*ORIGINAL_DST", - }, + "original-destination.*ORIGINAL_DST"}, - "-endpoints": { - "==> Endpoints \\(6\\)", + "-endpoints": {"==> Endpoints \\(6\\)", "Address:Port.*Cluster.*Weight.*Status", "192.168.79.187:8502.*local_agent.*1.00.*HEALTHY", "127.0.0.1:8080.*local_app.*1.00.*HEALTHY", "192.168.18.110:20000.*client.*1.00.*HEALTHY", "192.168.52.101:20000.*client.*1.00.*HEALTHY", "192.168.65.131:20000.*client.*1.00.*HEALTHY", - "192.168.63.120:20000.*frontend.*1.00.*HEALTHY", - }, + "192.168.63.120:20000.*frontend.*1.00.*HEALTHY"}, - "-listeners": { - "==> Listeners \\(2\\)", + "-listeners": {"==> Listeners \\(2\\)", "Name.*Address:Port.*Direction.*Filter Chain Match.*Filters.*Last Updated", "public_listener.*192\\.168\\.69\\.179:20000.*INBOUND.*Any.*\\* -> local_app/", "outbound_listener.*127.0.0.1:15001.*OUTBOUND.*10\\.100\\.134\\.173/32, 240\\.0\\.0\\.3/32.*TCP: -> client", "10\\.100\\.31\\.2/32, 240\\.0\\.0\\.5/32.*TCP: -> frontend", - "Any.*TCP: -> original-destination", - }, + "Any.*TCP: -> original-destination"}, - "-routes": { - "==> Routes \\(1\\)", + "-routes": {"==> Routes \\(1\\)", "Name.*Destination Cluster.*Last Updated", - "public_listener.*local_app/", - }, + "public_listener.*local_app/"}, - "-secrets": { - "==> Secrets \\(2\\)", + "-secrets": {"==> Secrets \\(2\\)", "Name.*Type.*Last Updated", "default.*Dynamic Active", - "ROOTCA.*Dynamic Warming", - }, + "ROOTCA.*Dynamic Warming"}, } cases := map[string][]string{ @@ -137,7 +122,7 @@ func TestReadCommandOutput(t *testing.T) { c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) // A fetchConfig function that just returns the test Envoy config. - c.fetchConfig = func(context.Context, common.PortForwarder) (*envoy.EnvoyConfig, error) { + c.fetchConfig = func(context.Context, common.PortForwarder) (*EnvoyConfig, error) { return testEnvoyConfig, nil } @@ -245,7 +230,7 @@ func TestFilterWarnings(t *testing.T) { buf := new(bytes.Buffer) c := setupCommand(buf) c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}}) - c.fetchConfig = func(context.Context, common.PortForwarder) (*envoy.EnvoyConfig, error) { + c.fetchConfig = func(context.Context, common.PortForwarder) (*EnvoyConfig, error) { return testEnvoyConfig, nil } @@ -310,73 +295,3 @@ func TestTaskCreateCommand_AutocompleteArgs(t *testing.T) { c := cmd.AutocompleteArgs() assert.Equal(t, complete.PredictNothing, c) } - -// testEnvoyConfig is what we expect the config at `test_config_dump.json` to be. - -var testEnvoyConfig = &envoy.EnvoyConfig{ - Clusters: []envoy.Cluster{ - {Name: "local_agent", FullyQualifiedDomainName: "local_agent", Endpoints: []string{"192.168.79.187:8502"}, Type: "STATIC", LastUpdated: "2022-05-13T04:22:39.553Z"}, - - {Name: "client", FullyQualifiedDomainName: "client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", Endpoints: []string{"192.168.18.110:20000", "192.168.52.101:20000", "192.168.65.131:20000"}, Type: "EDS", LastUpdated: "2022-08-10T12:30:32.326Z"}, - - {Name: "frontend", FullyQualifiedDomainName: "frontend.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", Endpoints: []string{"192.168.63.120:20000"}, Type: "EDS", LastUpdated: "2022-08-10T12:30:32.233Z"}, - - {Name: "local_app", FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, Type: "STATIC", LastUpdated: "2022-05-13T04:22:39.655Z"}, - - {Name: "original-destination", FullyQualifiedDomainName: "original-destination", Endpoints: []string{}, Type: "ORIGINAL_DST", LastUpdated: "2022-05-13T04:22:39.743Z"}, - }, - - Endpoints: []envoy.Endpoint{ - {Address: "192.168.79.187:8502", Cluster: "local_agent", Weight: 1, Status: "HEALTHY"}, - - {Address: "192.168.18.110:20000", Cluster: "client", Weight: 1, Status: "HEALTHY"}, - - {Address: "192.168.52.101:20000", Cluster: "client", Weight: 1, Status: "HEALTHY"}, - - {Address: "192.168.65.131:20000", Cluster: "client", Weight: 1, Status: "HEALTHY"}, - - {Address: "192.168.63.120:20000", Cluster: "frontend", Weight: 1, Status: "HEALTHY"}, - - {Address: "127.0.0.1:8080", Cluster: "local_app", Weight: 1, Status: "HEALTHY"}, - }, - - Listeners: []envoy.Listener{ - {Name: "public_listener", Address: "192.168.69.179:20000", FilterChain: []envoy.FilterChain{{Filters: []string{"HTTP: * -> local_app/"}, FilterChainMatch: "Any"}}, Direction: "INBOUND", LastUpdated: "2022-08-10T12:30:47.142Z"}, - - {Name: "outbound_listener", Address: "127.0.0.1:15001", FilterChain: []envoy.FilterChain{ - {Filters: []string{"TCP: -> client"}, FilterChainMatch: "10.100.134.173/32, 240.0.0.3/32"}, - - {Filters: []string{"TCP: -> frontend"}, FilterChainMatch: "10.100.31.2/32, 240.0.0.5/32"}, - - {Filters: []string{"TCP: -> original-destination"}, FilterChainMatch: "Any"}, - }, Direction: "OUTBOUND", LastUpdated: "2022-07-18T15:31:03.246Z"}, - }, - - Routes: []envoy.Route{ - { - Name: "public_listener", - - DestinationCluster: "local_app/", - - LastUpdated: "2022-08-10T12:30:47.141Z", - }, - }, - - Secrets: []envoy.Secret{ - { - Name: "default", - - Type: "Dynamic Active", - - LastUpdated: "2022-05-24T17:41:59.078Z", - }, - - { - Name: "ROOTCA", - - Type: "Dynamic Warming", - - LastUpdated: "2022-03-15T05:14:22.868Z", - }, - }, -} diff --git a/cli/common/envoy/http.go b/cli/cmd/proxy/read/config.go similarity index 90% rename from cli/common/envoy/http.go rename to cli/cmd/proxy/read/config.go index 39c58df530..e7e6bcad34 100644 --- a/cli/common/envoy/http.go +++ b/cli/cmd/proxy/read/config.go @@ -1,13 +1,8 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package envoy +package read import ( - "bytes" "context" "encoding/json" - "errors" "fmt" "io" "net" @@ -17,13 +12,11 @@ import ( "github.com/hashicorp/consul-k8s/cli/common" ) -var ErrNoLoggersReturned = errors.New("No loggers were returned from Envoy") - // EnvoyConfig represents the configuration retrieved from a config dump at the // admin endpoint. It wraps the Envoy ConfigDump struct to give us convenient // access to the different sections of the config. type EnvoyConfig struct { - RawCfg []byte + rawCfg []byte Clusters []Cluster Endpoints []Endpoint Listeners []Listener @@ -76,54 +69,6 @@ type Secret struct { LastUpdated string } -// CallLoggingEndpoint requests the logging endpoint from Envoy Admin Interface for a given port -// This is used to both read and update the logging levels (the envoy admin interface uses the same endpoint for both) -// more can be read about that endpoint https://www.envoyproxy.io/docs/envoy/latest/operations/admin#post--logging -func CallLoggingEndpoint(ctx context.Context, portForward common.PortForwarder, params *LoggerParams) (map[string]string, error) { - endpoint, err := portForward.Open(ctx) - if err != nil { - return nil, err - } - - defer portForward.Close() - - // this endpoint does not support returning json, so we've gotta parse the plain text - response, err := http.Post(fmt.Sprintf("http://%s/logging%s", endpoint, params), "text/plain", bytes.NewBuffer([]byte{})) - if err != nil { - return nil, err - } - - body, err := io.ReadAll(response.Body) - if err != nil { - return nil, fmt.Errorf("failed to reach envoy: %v", err) - } - - if response.StatusCode >= 400 { - return nil, fmt.Errorf("call to envoy failed with status code: %d, and message: %s", response.StatusCode, body) - } - - loggers := strings.Split(string(body), "\n") - if len(loggers) == 0 { - return nil, ErrNoLoggersReturned - } - - logLevels := make(map[string]string) - var name string - var level string - - // the first line here is just a header - for _, logger := range loggers[1:] { - if len(logger) == 0 { - continue - } - fmt.Sscanf(logger, "%s %s", &name, &level) - name = strings.TrimRight(name, ":") - logLevels[name] = level - } - - return logLevels, nil -} - // FetchConfig opens a port forward to the Envoy admin API and fetches the // configuration from the config dump endpoint. func FetchConfig(ctx context.Context, portForward common.PortForwarder) (*EnvoyConfig, error) { @@ -172,7 +117,7 @@ func FetchConfig(ctx context.Context, portForward common.PortForwarder) (*EnvoyC // JSON returns the original JSON Envoy config dump data which was used to create // the Config object. func (c *EnvoyConfig) JSON() []byte { - return c.RawCfg + return c.rawCfg } // UnmarshalJSON implements the json.Unmarshaler interface to unmarshal the raw @@ -181,7 +126,7 @@ func (c *EnvoyConfig) JSON() []byte { func (c *EnvoyConfig) UnmarshalJSON(b []byte) error { // Save the original config dump bytes for marshalling. We should treat this // struct as immutable so this should be safe. - c.RawCfg = b + c.rawCfg = b var root root err := json.Unmarshal(b, &root) diff --git a/cli/common/envoy/http_test.go b/cli/cmd/proxy/read/config_test.go similarity index 92% rename from cli/common/envoy/http_test.go rename to cli/cmd/proxy/read/config_test.go index 197bb62e84..6b0e425794 100644 --- a/cli/common/envoy/http_test.go +++ b/cli/cmd/proxy/read/config_test.go @@ -1,42 +1,21 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package envoy +package read import ( "bytes" "context" + "embed" "encoding/json" "fmt" - "k8s.io/apimachinery/pkg/util/rand" "net/http" "net/http/httptest" - "os" "strings" "testing" "github.com/stretchr/testify/require" ) -func TestCallLoggingEndpoint(t *testing.T) { - t.Parallel() - rawLogLevels, err := os.ReadFile("testdata/fetch_debug_levels.txt") - require.NoError(t, err) - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(rawLogLevels) - })) - - defer mockServer.Close() - - mpf := &mockPortForwarder{ - openBehavior: func(ctx context.Context) (string, error) { - return strings.Replace(mockServer.URL, "http://", "", 1), nil - }, - } - logLevels, err := CallLoggingEndpoint(context.Background(), mpf, NewLoggerParams()) - require.NoError(t, err) - require.Equal(t, testLogConfig(), logLevels) -} +//go:embed test_config_dump.json test_clusters.json +var fs embed.FS const ( testConfigDump = "test_config_dump.json" @@ -56,7 +35,7 @@ func TestUnmarshaling(t *testing.T) { } func TestJSON(t *testing.T) { - raw, err := os.ReadFile(fmt.Sprintf("testdata/%s", testConfigDump)) + raw, err := fs.ReadFile(testConfigDump) require.NoError(t, err) expected := bytes.TrimSpace(raw) @@ -70,10 +49,10 @@ func TestJSON(t *testing.T) { } func TestFetchConfig(t *testing.T) { - configDump, err := os.ReadFile(fmt.Sprintf("testdata/%s", testConfigDump)) + configDump, err := fs.ReadFile(testConfigDump) require.NoError(t, err) - clusters, err := os.ReadFile(fmt.Sprintf("testdata/%s", testClusters)) + clusters, err := fs.ReadFile(testClusters) require.NoError(t, err) mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -467,11 +446,18 @@ func TestClusterParsingEndpoints(t *testing.T) { require.Equal(t, expected, actual) } +type mockPortForwarder struct { + openBehavior func(context.Context) (string, error) +} + +func (m *mockPortForwarder) Open(ctx context.Context) (string, error) { return m.openBehavior(ctx) } +func (m *mockPortForwarder) Close() {} + func rawEnvoyConfig(t *testing.T) []byte { - configDump, err := os.ReadFile(fmt.Sprintf("testdata/%s", testConfigDump)) + configDump, err := fs.ReadFile(testConfigDump) require.NoError(t, err) - clusters, err := os.ReadFile(fmt.Sprintf("testdata/%s", testClusters)) + clusters, err := fs.ReadFile(testClusters) require.NoError(t, err) return []byte(fmt.Sprintf("{\n\"config_dump\":%s,\n\"clusters\":%s}", string(configDump), string(clusters))) @@ -522,19 +508,3 @@ var testEnvoyConfig = &EnvoyConfig{ }, }, } - -type mockPortForwarder struct { - openBehavior func(context.Context) (string, error) -} - -func (m *mockPortForwarder) Open(ctx context.Context) (string, error) { return m.openBehavior(ctx) } -func (m *mockPortForwarder) Close() {} -func (m *mockPortForwarder) GetLocalPort() int { return int(rand.Int63nRange(0, 65535)) } - -func testLogConfig() map[string]string { - cfg := make(map[string]string, len(EnvoyLoggers)) - for k := range EnvoyLoggers { - cfg[k] = "debug" - } - return cfg -} diff --git a/cli/common/envoy/types.go b/cli/cmd/proxy/read/envoy_types.go similarity index 99% rename from cli/common/envoy/types.go rename to cli/cmd/proxy/read/envoy_types.go index 8923eee271..cc1ffcf7e2 100644 --- a/cli/common/envoy/types.go +++ b/cli/cmd/proxy/read/envoy_types.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package envoy +package read /* Envoy Types These types are based on the JSON returned from the Envoy Config Dump API on the diff --git a/cli/cmd/proxy/read/filters.go b/cli/cmd/proxy/read/filters.go index 3c02102df8..dc65172f32 100644 --- a/cli/cmd/proxy/read/filters.go +++ b/cli/cmd/proxy/read/filters.go @@ -1,13 +1,8 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package read import ( "strconv" "strings" - - "github.com/hashicorp/consul-k8s/cli/common/envoy" ) // FilterClusters takes a slice of clusters along with parameters for filtering @@ -22,7 +17,7 @@ import ( // // The filters are applied in combination such that a cluster must adhere to // all of the filtering values which are passed in. -func FilterClusters(clusters []envoy.Cluster, fqdn, address string, port int) []envoy.Cluster { +func FilterClusters(clusters []Cluster, fqdn, address string, port int) []Cluster { // No filtering no-op. if fqdn == "" && address == "" && port == -1 { return clusters @@ -30,7 +25,7 @@ func FilterClusters(clusters []envoy.Cluster, fqdn, address string, port int) [] portStr := ":" + strconv.Itoa(port) - filtered := make([]envoy.Cluster, 0) + filtered := make([]Cluster, 0) for _, cluster := range clusters { if !strings.Contains(cluster.FullyQualifiedDomainName, fqdn) { continue @@ -63,14 +58,14 @@ func FilterClusters(clusters []envoy.Cluster, fqdn, address string, port int) [] // // The filters are applied in combination such that an endpoint must adhere to // all of the filtering values which are passed in. -func FilterEndpoints(endpoints []envoy.Endpoint, address string, port int) []envoy.Endpoint { +func FilterEndpoints(endpoints []Endpoint, address string, port int) []Endpoint { if address == "" && port == -1 { return endpoints } portStr := ":" + strconv.Itoa(port) - filtered := make([]envoy.Endpoint, 0) + filtered := make([]Endpoint, 0) for _, endpoint := range endpoints { if strings.Contains(endpoint.Address, address) && (port == -1 || strings.Contains(endpoint.Address, portStr)) { filtered = append(filtered, endpoint) @@ -90,14 +85,14 @@ func FilterEndpoints(endpoints []envoy.Endpoint, address string, port int) []env // // The filters are applied in combination such that an listener must adhere to // all of the filtering values which are passed in. -func FilterListeners(listeners []envoy.Listener, address string, port int) []envoy.Listener { +func FilterListeners(listeners []Listener, address string, port int) []Listener { if address == "" && port == -1 { return listeners } portStr := ":" + strconv.Itoa(port) - filtered := make([]envoy.Listener, 0) + filtered := make([]Listener, 0) for _, listener := range listeners { if strings.Contains(listener.Address, address) && (port == -1 || strings.Contains(listener.Address, portStr)) { filtered = append(filtered, listener) diff --git a/cli/cmd/proxy/read/filters_test.go b/cli/cmd/proxy/read/filters_test.go index 5d998e6a7c..48ff3a97da 100644 --- a/cli/cmd/proxy/read/filters_test.go +++ b/cli/cmd/proxy/read/filters_test.go @@ -1,18 +1,13 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package read import ( "testing" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/cli/common/envoy" ) func TestFilterClusters(t *testing.T) { - given := []envoy.Cluster{ + given := []Cluster{ { FullyQualifiedDomainName: "local_agent", Endpoints: []string{"192.168.79.187:8502"}, @@ -47,13 +42,13 @@ func TestFilterClusters(t *testing.T) { fqdn string address string port int - expected []envoy.Cluster + expected []Cluster }{ "No filter": { fqdn: "", address: "", port: -1, - expected: []envoy.Cluster{ + expected: []Cluster{ { FullyQualifiedDomainName: "local_agent", Endpoints: []string{"192.168.79.187:8502"}, @@ -88,7 +83,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "default", address: "", port: -1, - expected: []envoy.Cluster{ + expected: []Cluster{ { FullyQualifiedDomainName: "client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul", Endpoints: []string{}, @@ -107,7 +102,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "", address: "127.0.", port: -1, - expected: []envoy.Cluster{ + expected: []Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -122,7 +117,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "", address: "", port: 8080, - expected: []envoy.Cluster{ + expected: []Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -137,7 +132,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "local", address: "127.0.0.1", port: -1, - expected: []envoy.Cluster{ + expected: []Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -152,7 +147,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "local", address: "", port: 8080, - expected: []envoy.Cluster{ + expected: []Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -163,7 +158,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "", address: "127.0.0.1", port: 8080, - expected: []envoy.Cluster{ + expected: []Cluster{ { FullyQualifiedDomainName: "local_app", Endpoints: []string{"127.0.0.1:8080"}, @@ -174,7 +169,7 @@ func TestFilterClusters(t *testing.T) { fqdn: "local", address: "192.168.79.187", port: 8502, - expected: []envoy.Cluster{ + expected: []Cluster{ { FullyQualifiedDomainName: "local_agent", Endpoints: []string{"192.168.79.187:8502"}, @@ -192,7 +187,7 @@ func TestFilterClusters(t *testing.T) { } func TestFilterEndpoints(t *testing.T) { - given := []envoy.Endpoint{ + given := []Endpoint{ { Address: "192.168.79.187:8502", }, @@ -213,12 +208,12 @@ func TestFilterEndpoints(t *testing.T) { cases := map[string]struct { address string port int - expected []envoy.Endpoint + expected []Endpoint }{ "No filter": { address: "", port: -1, - expected: []envoy.Endpoint{ + expected: []Endpoint{ { Address: "192.168.79.187:8502", }, @@ -239,7 +234,7 @@ func TestFilterEndpoints(t *testing.T) { "Filter address": { address: "127.0.0.1", port: -1, - expected: []envoy.Endpoint{ + expected: []Endpoint{ { Address: "127.0.0.1:8080", }, @@ -248,7 +243,7 @@ func TestFilterEndpoints(t *testing.T) { "Filter port": { address: "", port: 20000, - expected: []envoy.Endpoint{ + expected: []Endpoint{ { Address: "192.168.31.201:20000", }, @@ -263,7 +258,7 @@ func TestFilterEndpoints(t *testing.T) { "Filter address and port": { address: "235", port: 20000, - expected: []envoy.Endpoint{ + expected: []Endpoint{ { Address: "192.168.47.235:20000", }, @@ -280,7 +275,7 @@ func TestFilterEndpoints(t *testing.T) { } func TestFilterListeners(t *testing.T) { - given := []envoy.Listener{ + given := []Listener{ { Address: "192.168.69.179:20000", }, @@ -292,12 +287,12 @@ func TestFilterListeners(t *testing.T) { cases := map[string]struct { address string port int - expected []envoy.Listener + expected []Listener }{ "No filter": { address: "", port: -1, - expected: []envoy.Listener{ + expected: []Listener{ { Address: "192.168.69.179:20000", }, @@ -309,7 +304,7 @@ func TestFilterListeners(t *testing.T) { "Filter address": { address: "127.0.0.1", port: -1, - expected: []envoy.Listener{ + expected: []Listener{ { Address: "127.0.0.1:15001", }, @@ -318,7 +313,7 @@ func TestFilterListeners(t *testing.T) { "Filter port": { address: "", port: 20000, - expected: []envoy.Listener{ + expected: []Listener{ { Address: "192.168.69.179:20000", }, @@ -327,7 +322,7 @@ func TestFilterListeners(t *testing.T) { "Filter address and port": { address: "192.168.69.179", port: 20000, - expected: []envoy.Listener{ + expected: []Listener{ { Address: "192.168.69.179:20000", }, diff --git a/cli/cmd/proxy/read/format.go b/cli/cmd/proxy/read/format.go index 596a6254f4..97d5ada86a 100644 --- a/cli/cmd/proxy/read/format.go +++ b/cli/cmd/proxy/read/format.go @@ -1,29 +1,23 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package read import ( "fmt" "strings" - "github.com/hashicorp/consul-k8s/cli/common/envoy" "github.com/hashicorp/consul-k8s/cli/common/terminal" ) -func formatClusters(clusters []envoy.Cluster) *terminal.Table { +func formatClusters(clusters []Cluster) *terminal.Table { table := terminal.NewTable("Name", "FQDN", "Endpoints", "Type", "Last Updated") for _, cluster := range clusters { - table.AddRow([]string{ - cluster.Name, cluster.FullyQualifiedDomainName, strings.Join(cluster.Endpoints, ", "), - cluster.Type, cluster.LastUpdated, - }, []string{}) + table.AddRow([]string{cluster.Name, cluster.FullyQualifiedDomainName, strings.Join(cluster.Endpoints, ", "), + cluster.Type, cluster.LastUpdated}, []string{}) } return table } -func formatEndpoints(endpoints []envoy.Endpoint) *terminal.Table { +func formatEndpoints(endpoints []Endpoint) *terminal.Table { table := terminal.NewTable("Address:Port", "Cluster", "Weight", "Status") for _, endpoint := range endpoints { var statusColor string @@ -41,7 +35,7 @@ func formatEndpoints(endpoints []envoy.Endpoint) *terminal.Table { return table } -func formatListeners(listeners []envoy.Listener) *terminal.Table { +func formatListeners(listeners []Listener) *terminal.Table { table := terminal.NewTable("Name", "Address:Port", "Direction", "Filter Chain Match", "Filters", "Last Updated") for _, listener := range listeners { for index, filter := range listener.FilterChain { @@ -63,7 +57,7 @@ func formatListeners(listeners []envoy.Listener) *terminal.Table { return table } -func formatRoutes(routes []envoy.Route) *terminal.Table { +func formatRoutes(routes []Route) *terminal.Table { table := terminal.NewTable("Name", "Destination Cluster", "Last Updated") for _, route := range routes { table.AddRow([]string{route.Name, route.DestinationCluster, route.LastUpdated}, []string{}) @@ -72,7 +66,7 @@ func formatRoutes(routes []envoy.Route) *terminal.Table { return table } -func formatSecrets(secrets []envoy.Secret) *terminal.Table { +func formatSecrets(secrets []Secret) *terminal.Table { table := terminal.NewTable("Name", "Type", "Last Updated") for _, secret := range secrets { table.AddRow([]string{secret.Name, secret.Type, secret.LastUpdated}, []string{}) diff --git a/cli/cmd/proxy/read/format_test.go b/cli/cmd/proxy/read/format_test.go index bac72df862..7d6f975d39 100644 --- a/cli/cmd/proxy/read/format_test.go +++ b/cli/cmd/proxy/read/format_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package read import ( @@ -8,10 +5,8 @@ import ( "context" "testing" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/cli/common/envoy" "github.com/hashicorp/consul-k8s/cli/common/terminal" + "github.com/stretchr/testify/require" ) func TestFormatClusters(t *testing.T) { @@ -26,7 +21,7 @@ func TestFormatClusters(t *testing.T) { "server.*server.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul.*EDS.*2022-06-09T00:39:12\\.754Z", } - given := []envoy.Cluster{ + given := []Cluster{ { Name: "local_agent", FullyQualifiedDomainName: "local_agent", @@ -102,7 +97,7 @@ func TestFormatEndpoints(t *testing.T) { "192.168.65.131:20000.*1.00.*HEALTHY", } - given := []envoy.Endpoint{ + given := []Endpoint{ { Address: "192.168.79.187:8502", Cluster: "local_agent", @@ -179,11 +174,11 @@ func TestFormatListeners(t *testing.T) { "Any.*-> original-destination", } - given := []envoy.Listener{ + given := []Listener{ { Name: "public_listener", Address: "192.168.69.179:20000", - FilterChain: []envoy.FilterChain{ + FilterChain: []FilterChain{ { FilterChainMatch: "Any", Filters: []string{"* -> local_app/"}, @@ -195,7 +190,7 @@ func TestFormatListeners(t *testing.T) { { Name: "outbound_listener", Address: "127.0.0.1:15001", - FilterChain: []envoy.FilterChain{ + FilterChain: []FilterChain{ { FilterChainMatch: "10.100.134.173/32, 240.0.0.3/32", Filters: []string{"-> client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul"}, @@ -250,7 +245,7 @@ func TestFormatRoutes(t *testing.T) { "server.*server\\.default\\.dc1\\.internal\\.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00\\.consul/.*2022-05-24T17:41:59\\.078Z", } - given := []envoy.Route{ + given := []Route{ { Name: "public_listener", DestinationCluster: "local_app/", @@ -287,7 +282,7 @@ func TestFormatSecrets(t *testing.T) { "ROOTCA.*Dynamic Warming.*2022-03-15T05:14:22.868Z", } - given := []envoy.Secret{ + given := []Secret{ { Name: "default", Type: "Dynamic Active", diff --git a/cli/common/envoy/testdata/test_clusters.json b/cli/cmd/proxy/read/test_clusters.json similarity index 100% rename from cli/common/envoy/testdata/test_clusters.json rename to cli/cmd/proxy/read/test_clusters.json diff --git a/cli/common/envoy/testdata/test_config_dump.json b/cli/cmd/proxy/read/test_config_dump.json similarity index 100% rename from cli/common/envoy/testdata/test_config_dump.json rename to cli/cmd/proxy/read/test_config_dump.json diff --git a/cli/cmd/proxy/stats/command.go b/cli/cmd/proxy/stats/command.go deleted file mode 100644 index ec46ec52bc..0000000000 --- a/cli/cmd/proxy/stats/command.go +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package stats - -import ( - "errors" - "fmt" - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/consul-k8s/cli/helm" - helmCLI "helm.sh/helm/v3/pkg/cli" - "io" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "net/http" - "strconv" - "strings" - "sync" -) - -const envoyAdminPort = 19000 - -type StatsCommand struct { - *common.BaseCommand - - helmActionsRunner helm.HelmActionsRunner - - kubernetes kubernetes.Interface - - restConfig *rest.Config - - set *flag.Sets - - flagKubeConfig string - flagKubeContext string - flagNamespace string - flagPod string - - once sync.Once - help string -} - -func (c *StatsCommand) init() { - c.set = flag.NewSets() - if c.helmActionsRunner == nil { - c.helmActionsRunner = &helm.ActionRunner{} - } - - f := c.set.NewSet("Command Options") - f.StringVar(&flag.StringVar{ - Name: "namespace", - Target: &c.flagNamespace, - Usage: "The namespace where the target Pod can be found.", - Aliases: []string{"n"}, - }) - - f = c.set.NewSet("Global Options") - f.StringVar(&flag.StringVar{ - Name: "kubeconfig", - Aliases: []string{"c"}, - Target: &c.flagKubeConfig, - Default: "", - Usage: "Path to kubeconfig file.", - }) - f.StringVar(&flag.StringVar{ - Name: "context", - Target: &c.flagKubeContext, - Default: "", - Usage: "Kubernetes context to use.", - }) - - c.help = c.set.Help() -} - -// validateFlags checks the command line flags and values for errors. -func (c *StatsCommand) validateFlags() error { - if len(c.set.Args()) > 0 { - return errors.New("should have no non-flag arguments") - } - return nil -} - -func (c *StatsCommand) Run(args []string) int { - c.once.Do(c.init) - - if err := c.parseFlags(args); err != nil { - c.UI.Output(err.Error(), terminal.WithErrorStyle()) - c.UI.Output("\n" + c.Help()) - return 1 - } - - if err := c.validateFlags(); err != nil { - c.UI.Output(err.Error()) - return 1 - } - - if c.flagPod == "" { - c.UI.Output("pod name is required") - return 1 - } - - // helmCLI.New() will create a settings object which is used by the Helm Go SDK calls. - settings := helmCLI.New() - if c.flagKubeConfig != "" { - settings.KubeConfig = c.flagKubeConfig - } - if c.flagKubeContext != "" { - settings.KubeContext = c.flagKubeContext - } - - if c.flagNamespace == "" { - c.flagNamespace = settings.Namespace() - } - - if err := c.setupKubeClient(settings); err != nil { - c.UI.Output(err.Error(), terminal.WithErrorStyle()) - return 1 - } - - if c.restConfig == nil { - var err error - if c.restConfig, err = settings.RESTClientGetter().ToRESTConfig(); err != nil { - c.UI.Output("error setting rest config") - return 1 - } - } - - pf := common.PortForward{ - Namespace: c.flagNamespace, - PodName: c.flagPod, - RemotePort: envoyAdminPort, - KubeClient: c.kubernetes, - RestConfig: c.restConfig, - } - - stats, err := c.getEnvoyStats(&pf) - if err != nil { - c.UI.Output("error fetching envoy stats %v", err, terminal.WithErrorStyle()) - return 1 - } - - c.UI.Output(stats) - return 0 - -} - -func (c *StatsCommand) getEnvoyStats(pf common.PortForwarder) (string, error) { - _, err := pf.Open(c.Ctx) - if err != nil { - return "", fmt.Errorf("error port forwarding %s", err) - } - defer pf.Close() - - resp, err := http.Get(fmt.Sprintf("http://localhost:%s/stats", strconv.Itoa(pf.GetLocalPort()))) - if err != nil { - return "", fmt.Errorf("error hitting stats endpoint of envoy %s", err) - } - - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("error reading body of http response %s", err) - } - - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - return string(bodyBytes), nil -} - -// setupKubeClient to use for non Helm SDK calls to the Kubernetes API The Helm SDK will use -// settings.RESTClientGetter for its calls as well, so this will use a consistent method to -// target the right cluster for both Helm SDK and non Helm SDK calls. -func (c *StatsCommand) setupKubeClient(settings *helmCLI.EnvSettings) error { - if c.kubernetes == nil { - restConfig, err := settings.RESTClientGetter().ToRESTConfig() - if err != nil { - c.UI.Output("Error retrieving Kubernetes authentication: %v", err, terminal.WithErrorStyle()) - return err - } - c.kubernetes, err = kubernetes.NewForConfig(restConfig) - if err != nil { - c.UI.Output("Error initializing Kubernetes client: %v", err, terminal.WithErrorStyle()) - return err - } - } - - return nil -} - -func (c *StatsCommand) parseFlags(args []string) error { - // Separate positional arguments from keyed arguments. - var positional []string - for _, arg := range args { - if strings.HasPrefix(arg, "-") { - break - } - positional = append(positional, arg) - } - keyed := args[len(positional):] - - if len(positional) != 1 { - return fmt.Errorf("exactly one positional argument is required: ") - } - c.flagPod = positional[0] - - if err := c.set.Parse(keyed); err != nil { - return err - } - - return nil -} - -// Help returns a description of the command and how it is used. -func (c *StatsCommand) Help() string { - c.once.Do(c.init) - return c.Synopsis() + "\n\nUsage: consul-k8s proxy stats pod-name -n namespace [flags]\n\n" + c.help -} - -// Synopsis returns a one-line command summary. -func (c *StatsCommand) Synopsis() string { - return "Display Envoy stats for a proxy" -} diff --git a/cli/cmd/proxy/stats/command_test.go b/cli/cmd/proxy/stats/command_test.go deleted file mode 100644 index c223570a61..0000000000 --- a/cli/cmd/proxy/stats/command_test.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package stats - -import ( - "bytes" - "context" - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "io" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" - "net/http" - "os" - "strconv" - "testing" -) - -func TestFlagParsing(t *testing.T) { - cases := map[string]struct { - args []string - out int - }{ - "No args, should fail": { - args: []string{}, - out: 1, - }, - "Nonexistent flag passed, -foo bar, should fail": { - args: []string{"-foo", "bar"}, - out: 1, - }, - "Invalid argument passed, -namespace notaname, should fail": { - args: []string{"-namespace", "notaname"}, - out: 1, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - c := setupCommand(new(bytes.Buffer)) - c.kubernetes = fake.NewSimpleClientset() - out := c.Run(tc.args) - require.Equal(t, tc.out, out) - }) - } -} -func setupCommand(buf io.Writer) *StatsCommand { - // 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 := &StatsCommand{ - BaseCommand: &common.BaseCommand{ - Log: log, - UI: terminal.NewUI(context.Background(), buf), - }, - } - command.init() - - return command -} - -type MockPortForwarder struct { -} - -func (mpf *MockPortForwarder) Open(ctx context.Context) (string, error) { - return "localhost:" + strconv.Itoa(envoyAdminPort), nil -} - -func (mpf *MockPortForwarder) Close() { - //noop -} - -func (mpf *MockPortForwarder) GetLocalPort() int { - return envoyAdminPort -} - -func TestEnvoyStats(t *testing.T) { - cases := map[string]struct { - namespace string - pods []v1.Pod - }{ - "Sidecar Pods": { - namespace: "default", - pods: []v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod1", - Namespace: "default", - Labels: map[string]string{ - "consul.hashicorp.com/connect-inject-status": "injected", - }, - }, - }, - }, - }, - "Pods in consul namespaces": { - namespace: "consul", - pods: []v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "api-gateway", - Namespace: "consul", - Labels: map[string]string{ - "api-gateway.consul.hashicorp.com/managed": "true", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod1", - Namespace: "consul", - Labels: map[string]string{ - "consul.hashicorp.com/connect-inject-status": "injected", - }, - }, - }, - }, - }, - } - - srv := startHttpServer(envoyAdminPort) - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - c := setupCommand(new(bytes.Buffer)) - c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: tc.pods}) - c.flagNamespace = tc.namespace - for _, pod := range tc.pods { - c.flagPod = pod.Name - mpf := &MockPortForwarder{} - resp, err := c.getEnvoyStats(mpf) - require.NoError(t, err) - require.Equal(t, resp, "Envoy Stats") - } - }) - } - srv.Shutdown(context.Background()) -} - -func startHttpServer(port int) *http.Server { - srv := &http.Server{Addr: ":" + strconv.Itoa(port)} - - http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "Envoy Stats") - }) - - go func() { - srv.ListenAndServe() - }() - - return srv -} diff --git a/cli/cmd/status/status.go b/cli/cmd/status/status.go index ebe60528f2..c2108cc631 100644 --- a/cli/cmd/status/status.go +++ b/cli/cmd/status/status.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package status import ( diff --git a/cli/cmd/status/status_test.go b/cli/cmd/status/status_test.go index 7984415c43..8666fd8493 100644 --- a/cli/cmd/status/status_test.go +++ b/cli/cmd/status/status_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package status import ( diff --git a/cli/cmd/troubleshoot/command.go b/cli/cmd/troubleshoot/command.go deleted file mode 100644 index 723088a134..0000000000 --- a/cli/cmd/troubleshoot/command.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package troubleshoot - -import ( - "fmt" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/mitchellh/cli" -) - -// TroubleshootCommand provides a synopsis for the troubleshoot subcommands (e.g. proxy, upstreams). -type TroubleshootCommand struct { - *common.BaseCommand -} - -// Run prints out information about the subcommands. -func (c *TroubleshootCommand) Run([]string) int { - return cli.RunResultHelp -} - -func (c *TroubleshootCommand) Help() string { - return fmt.Sprintf("%s\n\nUsage: consul-k8s troubleshoot ", c.Synopsis()) -} - -func (c *TroubleshootCommand) Synopsis() string { - return "Troubleshoot network and security configurations." -} diff --git a/cli/cmd/troubleshoot/proxy/proxy.go b/cli/cmd/troubleshoot/proxy/proxy.go deleted file mode 100644 index cd8362bf28..0000000000 --- a/cli/cmd/troubleshoot/proxy/proxy.go +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package proxy - -import ( - "fmt" - "net" - "strings" - "sync" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - troubleshoot "github.com/hashicorp/consul/troubleshoot/proxy" - "github.com/posener/complete" - helmCLI "helm.sh/helm/v3/pkg/cli" - "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" -) - -const ( - defaultAdminPort int = 19000 - flagNameKubeConfig = "kubeconfig" - flagNameKubeContext = "context" - flagNameNamespace = "namespace" - flagNamePod = "pod" - flagNameUpstreamEnvoyID = "upstream-envoy-id" - flagNameUpstreamIP = "upstream-ip" - DebugColor = "\033[0;36m%s\033[0m" -) - -type ProxyCommand struct { - *common.BaseCommand - - kubernetes kubernetes.Interface - - set *flag.Sets - - flagKubeConfig string - flagKubeContext string - flagNamespace string - - flagPod string - flagUpstreamEnvoyID string - flagUpstreamIP string - - restConfig *rest.Config - - once sync.Once - help string -} - -// init sets up flags and help text for the command. -func (c *ProxyCommand) init() { - c.set = flag.NewSets() - f := c.set.NewSet("Command Options") - - f.StringVar(&flag.StringVar{ - Name: flagNamePod, - Target: &c.flagPod, - Usage: "The pod to port-forward to.", - Aliases: []string{"p"}, - }) - - f.StringVar(&flag.StringVar{ - Name: flagNameUpstreamEnvoyID, - Target: &c.flagUpstreamEnvoyID, - Usage: "The envoy identifier of the upstream service that receives the communication. (explicit upstreams only)", - Aliases: []string{"id"}, - }) - - f.StringVar(&flag.StringVar{ - Name: flagNameUpstreamIP, - Target: &c.flagUpstreamIP, - Usage: "The IP address of the upstream service that receives the communication. (transparent proxy only)", - Aliases: []string{"ip"}, - }) - - f = c.set.NewSet("Global Options") - f.StringVar(&flag.StringVar{ - Name: flagNameKubeConfig, - Aliases: []string{"c"}, - Target: &c.flagKubeConfig, - Default: "", - Usage: "Set the path to kubeconfig file.", - }) - f.StringVar(&flag.StringVar{ - Name: flagNameKubeContext, - Target: &c.flagKubeContext, - Default: "", - Usage: "Set the Kubernetes context to use.", - }) - - f.StringVar(&flag.StringVar{ - Name: flagNameNamespace, - Target: &c.flagNamespace, - Usage: "The namespace the pod is in.", - Aliases: []string{"n"}, - }) - - c.help = c.set.Help() -} - -// Run executes the list command. -func (c *ProxyCommand) Run(args []string) int { - c.once.Do(c.init) - c.Log.ResetNamed("list") - defer common.CloseWithError(c.BaseCommand) - - // Parse the command line flags. - if err := c.set.Parse(args); err != nil { - c.UI.Output("Error parsing arguments: %v", err.Error(), terminal.WithErrorStyle()) - return 1 - } - - // Validate the command line flags. - if err := c.validateFlags(); err != nil { - c.UI.Output("Invalid argument: %v", err.Error(), terminal.WithErrorStyle()) - return 1 - } - - if c.kubernetes == nil { - if err := c.initKubernetes(); err != nil { - c.UI.Output("Error initializing Kubernetes client: %v", err.Error(), terminal.WithErrorStyle()) - return 1 - } - } - - if err := c.Troubleshoot(); err != nil { - c.UI.Output("Error running troubleshoot: %v", err.Error(), terminal.WithErrorStyle()) - return 1 - } - - return 0 -} - -// validateFlags ensures that the flags passed in by the can be used. -func (c *ProxyCommand) validateFlags() error { - - if (c.flagUpstreamEnvoyID == "" && c.flagUpstreamIP == "") || (c.flagUpstreamEnvoyID != "" && c.flagUpstreamIP != "") { - return fmt.Errorf("-upstream-envoy-id OR -upstream-ip is required.\n Please run `consul troubleshoot upstreams` to find the corresponding upstream.") - } - - if c.flagPod == "" { - return fmt.Errorf("-pod flag is required") - } - - if errs := validation.ValidateNamespaceName(c.flagNamespace, false); c.flagNamespace != "" && len(errs) > 0 { - return fmt.Errorf("invalid namespace name passed for -namespace/-n: %v", strings.Join(errs, "; ")) - } - - return nil -} - -// initKubernetes initializes the Kubernetes client. -func (c *ProxyCommand) initKubernetes() (err error) { - settings := helmCLI.New() - - if c.flagKubeConfig != "" { - settings.KubeConfig = c.flagKubeConfig - } - - if c.flagKubeContext != "" { - settings.KubeContext = c.flagKubeContext - } - - if c.restConfig == nil { - if c.restConfig, err = settings.RESTClientGetter().ToRESTConfig(); err != nil { - return fmt.Errorf("error creating Kubernetes REST config %v", err) - } - } - - if c.kubernetes == nil { - if c.kubernetes, err = kubernetes.NewForConfig(c.restConfig); err != nil { - return fmt.Errorf("error creating Kubernetes client %v", err) - } - } - - if c.flagNamespace == "" { - c.flagNamespace = settings.Namespace() - } - - return nil -} - -func (c *ProxyCommand) Troubleshoot() error { - pf := common.PortForward{ - Namespace: c.flagNamespace, - PodName: c.flagPod, - RemotePort: defaultAdminPort, - KubeClient: c.kubernetes, - RestConfig: c.restConfig, - } - - endpoint, err := pf.Open(c.Ctx) - if err != nil { - return err - } - defer pf.Close() - - adminAddr, adminPort, err := net.SplitHostPort(endpoint) - if err != nil { - return err - } - - adminAddrIP, err := net.ResolveIPAddr("ip", adminAddr) - if err != nil { - return err - } - - t, err := troubleshoot.NewTroubleshoot(adminAddrIP, adminPort) - if err != nil { - return err - } - - // err = t.GetEnvoyConfigDump() - // if err != nil { - // return err - // } - - messages, err := t.RunAllTests(c.flagUpstreamEnvoyID, c.flagUpstreamIP) - if err != nil { - return err - } - - c.UI.Output("Validation", terminal.WithHeaderStyle()) - for _, o := range messages { - if o.Success { - c.UI.Output(o.Message, terminal.WithSuccessStyle()) - } else { - c.UI.Output(o.Message, terminal.WithErrorStyle()) - for _, action := range o.PossibleActions { - c.UI.Output(fmt.Sprintf("-> %s", action), terminal.WithInfoStyle()) - } - } - } - - return nil -} - -// AutocompleteFlags returns a mapping of supported flags and autocomplete -// options for this command. The map key for the Flags map should be the -// complete flag such as "-foo" or "--foo". -func (c *ProxyCommand) AutocompleteFlags() complete.Flags { - return complete.Flags{ - fmt.Sprintf("-%s", flagNameNamespace): complete.PredictNothing, - fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), - fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, - } -} - -// AutocompleteArgs returns the argument predictor for this command. -// Since argument completion is not supported, this will return -// complete.PredictNothing. -func (c *ProxyCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictNothing -} - -func (c *ProxyCommand) Synopsis() string { - return synopsis -} - -func (c *ProxyCommand) Help() string { - return help -} - -const ( - synopsis = "Troubleshoots service mesh issues." - help = ` -Usage: consul-k8s troubleshoot proxy [options] - - Connect to a pod with a proxy and troubleshoots service mesh communication issues. - - Requires a pod and upstream service SNI. - - Examples: - $ consul-k8s troubleshoot proxy -pod pod1 -upstream foo - - where 'pod1' is the pod running a consul proxy and 'foo' is the upstream envoy ID which - can be obtained by running: - $ consul-k8s troubleshoot upstreams [options] -` -) diff --git a/cli/cmd/troubleshoot/proxy/proxy_test.go b/cli/cmd/troubleshoot/proxy/proxy_test.go deleted file mode 100644 index e018878bf5..0000000000 --- a/cli/cmd/troubleshoot/proxy/proxy_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package proxy - -import ( - "bytes" - "context" - "io" - "os" - "testing" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "k8s.io/client-go/kubernetes/fake" -) - -func TestFlagParsing(t *testing.T) { - cases := map[string]struct { - args []string - out int - }{ - "No args, should fail": { - args: []string{}, - out: 1, - }, - "Nonexistent flag passed, -foo bar, should fail": { - args: []string{"-foo", "bar"}, - out: 1, - }, - "Invalid argument passed, -namespace notaname, should fail": { - args: []string{"-namespace", "notaname"}, - out: 1, - }, - "Cannot pass both -upstream-envoy-id and -upstream-ip flags, should fail": { - args: []string{"-upstream-envoy-id", "1234", "-upstream-ip", "127.0.0.1"}, - out: 1, - }, - "Cannot pass empty -upstream-envoy-id and -upstream-ip flags, should fail": { - args: []string{"-upstream-envoy-id", "-upstream-ip"}, - out: 1, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - c := setupCommand(new(bytes.Buffer)) - c.kubernetes = fake.NewSimpleClientset() - out := c.Run(tc.args) - require.Equal(t, tc.out, out) - }) - } -} - -func setupCommand(buf io.Writer) *ProxyCommand { - // 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 := &ProxyCommand{ - BaseCommand: &common.BaseCommand{ - Log: log, - UI: terminal.NewUI(context.Background(), buf), - }, - } - command.init() - - return command -} diff --git a/cli/cmd/troubleshoot/upstreams/upstreams.go b/cli/cmd/troubleshoot/upstreams/upstreams.go deleted file mode 100644 index abc3235cd5..0000000000 --- a/cli/cmd/troubleshoot/upstreams/upstreams.go +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package upstreams - -import ( - "fmt" - "net" - "sort" - "strconv" - "strings" - "sync" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - troubleshoot "github.com/hashicorp/consul/troubleshoot/proxy" - "github.com/posener/complete" - helmCLI "helm.sh/helm/v3/pkg/cli" - "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" -) - -const ( - defaultAdminPort int = 19000 - flagNameKubeConfig = "kubeconfig" - flagNameKubeContext = "context" - flagNameNamespace = "namespace" - flagNamePod = "pod" -) - -type UpstreamsCommand struct { - *common.BaseCommand - - kubernetes kubernetes.Interface - - set *flag.Sets - - flagKubeConfig string - flagKubeContext string - flagNamespace string - - flagPod string - - restConfig *rest.Config - - once sync.Once - help string -} - -// init sets up flags and help text for the command. -func (c *UpstreamsCommand) init() { - c.set = flag.NewSets() - f := c.set.NewSet("Command Options") - - f.StringVar(&flag.StringVar{ - Name: flagNamePod, - Target: &c.flagPod, - Usage: "The pod to port-forward to.", - Aliases: []string{"p"}, - }) - - f = c.set.NewSet("Global Options") - f.StringVar(&flag.StringVar{ - Name: flagNameKubeConfig, - Aliases: []string{"c"}, - Target: &c.flagKubeConfig, - Default: "", - Usage: "Set the path to kubeconfig file.", - }) - f.StringVar(&flag.StringVar{ - Name: flagNameKubeContext, - Target: &c.flagKubeContext, - Default: "", - Usage: "Set the Kubernetes context to use.", - }) - - f.StringVar(&flag.StringVar{ - Name: flagNameNamespace, - Target: &c.flagNamespace, - Usage: "The namespace the pod is in.", - Aliases: []string{"n"}, - }) - - c.help = c.set.Help() -} - -// Run executes the list command. -func (c *UpstreamsCommand) Run(args []string) int { - c.once.Do(c.init) - c.Log.ResetNamed("list") - defer common.CloseWithError(c.BaseCommand) - - // Parse the command line flags. - if err := c.set.Parse(args); err != nil { - c.UI.Output("Error parsing arguments: %v", err.Error(), terminal.WithErrorStyle()) - return 1 - } - - // Validate the command line flags. - if err := c.validateFlags(); err != nil { - c.UI.Output("Invalid argument: %v", err.Error(), terminal.WithErrorStyle()) - return 1 - } - - if c.kubernetes == nil { - if err := c.initKubernetes(); err != nil { - c.UI.Output("Error initializing Kubernetes client: %v", err.Error(), terminal.WithErrorStyle()) - return 1 - } - } - - if err := c.Troubleshoot(); err != nil { - c.UI.Output("Error running troubleshoot: %v", err.Error(), terminal.WithErrorStyle()) - return 1 - } - - return 0 -} - -// validateFlags ensures that the flags passed in by the can be used. -func (c *UpstreamsCommand) validateFlags() error { - - if c.flagPod == "" { - return fmt.Errorf("-pod flag is required") - } - - if errs := validation.ValidateNamespaceName(c.flagNamespace, false); c.flagNamespace != "" && len(errs) > 0 { - return fmt.Errorf("invalid namespace name passed for -namespace/-n: %v", strings.Join(errs, "; ")) - } - - return nil -} - -// initKubernetes initializes the Kubernetes client. -func (c *UpstreamsCommand) initKubernetes() (err error) { - settings := helmCLI.New() - - if c.flagKubeConfig != "" { - settings.KubeConfig = c.flagKubeConfig - } - - if c.flagKubeContext != "" { - settings.KubeContext = c.flagKubeContext - } - - if c.restConfig == nil { - if c.restConfig, err = settings.RESTClientGetter().ToRESTConfig(); err != nil { - return fmt.Errorf("error creating Kubernetes REST config %v", err) - } - } - - if c.kubernetes == nil { - if c.kubernetes, err = kubernetes.NewForConfig(c.restConfig); err != nil { - return fmt.Errorf("error creating Kubernetes client %v", err) - } - } - - if c.flagNamespace == "" { - c.flagNamespace = settings.Namespace() - } - - return nil -} - -func (c *UpstreamsCommand) Troubleshoot() error { - pf := common.PortForward{ - Namespace: c.flagNamespace, - PodName: c.flagPod, - RemotePort: defaultAdminPort, - KubeClient: c.kubernetes, - RestConfig: c.restConfig, - } - - endpoint, err := pf.Open(c.Ctx) - if err != nil { - return fmt.Errorf("error opening endpoint: %v", err) - } - defer pf.Close() - - adminAddr, adminPort, err := net.SplitHostPort(endpoint) - if err != nil { - return fmt.Errorf("error splitting hostport: %v", err) - } - - adminAddrIP, err := net.ResolveIPAddr("ip", adminAddr) - if err != nil { - return fmt.Errorf("error resolving ip address: %v", err) - } - - t, err := troubleshoot.NewTroubleshoot(adminAddrIP, adminPort) - if err != nil { - return fmt.Errorf("error creating new troubleshoot: %v", err) - } - - envoyIDs, upstreamIPs, err := t.GetUpstreams() - if err != nil { - return fmt.Errorf("error getting upstreams: %v", err) - } - - c.UI.Output(fmt.Sprintf("Upstreams (explicit upstreams only) (%v)", len(envoyIDs)), terminal.WithHeaderStyle()) - for _, e := range envoyIDs { - c.UI.Output(e) - } - - c.UI.Output(fmt.Sprintf("Upstream IPs (transparent proxy only) (%v)", len(upstreamIPs)), terminal.WithHeaderStyle()) - table := terminal.NewTable("IPs ", "Virtual ", "Cluster Names") - for _, u := range upstreamIPs { - table.AddRow([]string{formatIPs(u.IPs), strconv.FormatBool(u.IsVirtual), formatClusterNames(u.ClusterNames)}, []string{}) - } - c.UI.Table(table) - - c.UI.Output("\nIf you cannot find the upstream address or cluster for a transparent proxy upstream:", terminal.WithInfoStyle()) - c.UI.Output("-> Check intentions: Transparent proxy upstreams are configured based on intentions. Make sure you "+ - "have configured intentions to allow traffic to your upstream.", terminal.WithInfoStyle()) - c.UI.Output("-> To check that the right cluster is being dialed, run a DNS lookup "+ - "for the upstream you are dialing. For example, run `dig backend.svc.consul` to return the IP address for the `backend` service. If the address you get from that is missing "+ - "from the upstream IPs, it means that your proxy may be misconfigured.", terminal.WithInfoStyle()) - - return nil -} - -// AutocompleteFlags returns a mapping of supported flags and autocomplete -// options for this command. The map key for the Flags map should be the -// complete flag such as "-foo" or "--foo". -func (c *UpstreamsCommand) AutocompleteFlags() complete.Flags { - return complete.Flags{ - fmt.Sprintf("-%s", flagNameNamespace): complete.PredictNothing, - fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"), - fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing, - } -} - -// AutocompleteArgs returns the argument predictor for this command. -// Since argument completion is not supported, this will return -// complete.PredictNothing. -func (c *UpstreamsCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictNothing -} - -func (c *UpstreamsCommand) Synopsis() string { - return synopsis -} - -func (c *UpstreamsCommand) Help() string { - return help -} - -func formatIPs(ips []string) string { - return strings.Join(ips, ", ") -} - -func formatClusterNames(names map[string]struct{}) string { - var out []string - for k := range names { - out = append(out, k) - } - sort.Strings(out) - return strings.Join(out, ", ") -} - -const ( - synopsis = "Connect to a pod with a proxy and gather upstream services." - help = ` -Usage: consul-k8s troubleshoot upstreams [options] - - Connect to a pod with a proxy and gather upstream services. - - Requires a pod. - - Examples: - $ consul-k8s troubleshoot upstreams -pod pod1 - - where 'pod1' is the pod running a consul proxy -` -) diff --git a/cli/cmd/troubleshoot/upstreams/upstreams_test.go b/cli/cmd/troubleshoot/upstreams/upstreams_test.go deleted file mode 100644 index 595c480a35..0000000000 --- a/cli/cmd/troubleshoot/upstreams/upstreams_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package upstreams - -import ( - "bytes" - "context" - "io" - "os" - "testing" - - "github.com/hashicorp/consul-k8s/cli/common" - "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "k8s.io/client-go/kubernetes/fake" -) - -func TestFlagParsing(t *testing.T) { - cases := map[string]struct { - args []string - out int - }{ - "No args, should fail": { - args: []string{}, - out: 1, - }, - "Nonexistent flag passed, -foo bar, should fail": { - args: []string{"-foo", "bar"}, - out: 1, - }, - "Invalid argument passed, -namespace notaname, should fail": { - args: []string{"-namespace", "notaname"}, - out: 1, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - c := setupCommand(new(bytes.Buffer)) - c.kubernetes = fake.NewSimpleClientset() - out := c.Run(tc.args) - require.Equal(t, tc.out, out) - }) - } -} - -func TestFormatIPs(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - actual []string - expected string - }{ - { - name: "single IPs", - actual: []string{"1.1.1.1"}, - expected: "1.1.1.1", - }, - - { - name: "several IPs", - actual: []string{"1.1.1.1", "2.2.2.2"}, - expected: "1.1.1.1, 2.2.2.2", - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - got := formatIPs(c.actual) - if c.expected != got { - t.Errorf("expected %v, got %v", c.expected, got) - } - }) - } -} - -func TestFormatClusterNames(t *testing.T) { - cases := []struct { - name string - actual map[string]struct{} - expected string - }{ - { - name: "single cluster", - actual: map[string]struct{}{ - "cluster1": {}, - }, - expected: "cluster1", - }, - { - name: "several clusters", - actual: map[string]struct{}{ - "cluster1": {}, - "cluster2": {}, - "cluster3": {}, - }, - expected: "cluster1, cluster2, cluster3", - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - got := formatClusterNames(c.actual) - if c.expected != got { - t.Errorf("expected %v, got %v", c.expected, got) - } - }) - } -} - -func setupCommand(buf io.Writer) *UpstreamsCommand { - // 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 := &UpstreamsCommand{ - BaseCommand: &common.BaseCommand{ - Log: log, - UI: terminal.NewUI(context.Background(), buf), - }, - } - command.init() - - return command -} diff --git a/cli/cmd/uninstall/uninstall.go b/cli/cmd/uninstall/uninstall.go index caaaa2f0fa..06bf4a19b3 100644 --- a/cli/cmd/uninstall/uninstall.go +++ b/cli/cmd/uninstall/uninstall.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package uninstall import ( diff --git a/cli/cmd/uninstall/uninstall_test.go b/cli/cmd/uninstall/uninstall_test.go index 1d3fffc2c1..2adc0960ab 100644 --- a/cli/cmd/uninstall/uninstall_test.go +++ b/cli/cmd/uninstall/uninstall_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package uninstall import ( diff --git a/cli/cmd/upgrade/upgrade.go b/cli/cmd/upgrade/upgrade.go index a7e79d2239..4c962c47b5 100644 --- a/cli/cmd/upgrade/upgrade.go +++ b/cli/cmd/upgrade/upgrade.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package upgrade import ( @@ -20,6 +17,7 @@ import ( "github.com/hashicorp/consul-k8s/cli/helm" "github.com/hashicorp/consul-k8s/cli/preset" "github.com/posener/complete" + helmCLI "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/getter" @@ -427,7 +425,7 @@ func (c *Command) validateFlags(args []string) error { return fmt.Errorf("cannot set both -%s and -%s", flagNameConfigFile, flagNamePreset) } if ok := slices.Contains(preset.Presets, c.flagPreset); c.flagPreset != defaultPreset && !ok { - return fmt.Errorf("'%s' is not a valid preset (valid presets: %s)", c.flagPreset, strings.Join(preset.Presets, ", ")) + return fmt.Errorf("'%s' is not a valid preset", c.flagPreset) } if _, err := time.ParseDuration(c.flagTimeout); err != nil { return fmt.Errorf("unable to parse -%s: %s", flagNameTimeout, err) diff --git a/cli/cmd/upgrade/upgrade_test.go b/cli/cmd/upgrade/upgrade_test.go index f8421296a7..809cac1034 100644 --- a/cli/cmd/upgrade/upgrade_test.go +++ b/cli/cmd/upgrade/upgrade_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package upgrade import ( @@ -455,7 +452,7 @@ func TestUpgrade(t *testing.T) { messages: []string{ "\n==> Checking if Consul can be upgraded\n ✓ Existing Consul installation found to be upgraded.\n Name: consul\n Namespace: consul\n", "\n==> Checking if Consul demo application can be upgraded\n No existing Consul demo application installation found.\n", - "\n==> Consul Upgrade Summary\n ✓ Downloaded charts.\n \n Difference between user overrides for current and upgraded charts\n -----------------------------------------------------------------\n + connectInject:\n + enabled: true\n + metrics:\n + defaultEnableMerging: true\n + defaultEnabled: true\n + enableGatewayMetrics: true\n + global:\n + metrics:\n + enableAgentMetrics: true\n + enabled: true\n + name: consul\n + prometheus:\n + enabled: true\n + server:\n + replicas: 1\n + ui:\n + enabled: true\n + service:\n + enabled: true\n \n", + "\n==> Consul Upgrade Summary\n ✓ Downloaded charts.\n \n Difference between user overrides for current and upgraded charts\n -----------------------------------------------------------------\n + connectInject:\n + enabled: true\n + metrics:\n + defaultEnableMerging: true\n + defaultEnabled: true\n + enableGatewayMetrics: true\n + controller:\n + enabled: true\n + global:\n + metrics:\n + enableAgentMetrics: true\n + enabled: true\n + name: consul\n + prometheus:\n + enabled: true\n + server:\n + replicas: 1\n + ui:\n + enabled: true\n + service:\n + enabled: true\n \n", "\n==> Upgrading Consul\n ✓ Consul upgraded in namespace \"consul\".\n", }, helmActionsRunner: &helm.MockActionRunner{ @@ -480,7 +477,7 @@ func TestUpgrade(t *testing.T) { messages: []string{ "\n==> Checking if Consul can be upgraded\n ✓ Existing Consul installation found to be upgraded.\n Name: consul\n Namespace: consul\n", "\n==> Checking if Consul demo application can be upgraded\n No existing Consul demo application installation found.\n", - "\n==> Consul Upgrade Summary\n ✓ Downloaded charts.\n \n Difference between user overrides for current and upgraded charts\n -----------------------------------------------------------------\n + connectInject:\n + enabled: true\n + global:\n + acls:\n + manageSystemACLs: true\n + gossipEncryption:\n + autoGenerate: true\n + name: consul\n + tls:\n + enableAutoEncrypt: true\n + enabled: true\n + server:\n + replicas: 1\n \n", + "\n==> Consul Upgrade Summary\n ✓ Downloaded charts.\n \n Difference between user overrides for current and upgraded charts\n -----------------------------------------------------------------\n + connectInject:\n + enabled: true\n + controller:\n + enabled: true\n + global:\n + acls:\n + manageSystemACLs: true\n + gossipEncryption:\n + autoGenerate: true\n + name: consul\n + tls:\n + enableAutoEncrypt: true\n + enabled: true\n + server:\n + replicas: 1\n \n", "\n==> Upgrading Consul\n ✓ Consul upgraded in namespace \"consul\".\n", }, helmActionsRunner: &helm.MockActionRunner{ diff --git a/cli/cmd/version/version.go b/cli/cmd/version/version.go index cab5cbc34d..f31fa711f1 100644 --- a/cli/cmd/version/version.go +++ b/cli/cmd/version/version.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package version import ( diff --git a/cli/commands.go b/cli/commands.go index 513bd6d9d7..fdb581decd 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -1,23 +1,15 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( "context" - "github.com/hashicorp/consul-k8s/cli/cmd/proxy/stats" "github.com/hashicorp/consul-k8s/cli/cmd/config" config_read "github.com/hashicorp/consul-k8s/cli/cmd/config/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/status" - "github.com/hashicorp/consul-k8s/cli/cmd/troubleshoot" - troubleshoot_proxy "github.com/hashicorp/consul-k8s/cli/cmd/troubleshoot/proxy" - "github.com/hashicorp/consul-k8s/cli/cmd/troubleshoot/upstreams" "github.com/hashicorp/consul-k8s/cli/cmd/uninstall" "github.com/hashicorp/consul-k8s/cli/cmd/upgrade" cmdversion "github.com/hashicorp/consul-k8s/cli/cmd/version" @@ -29,6 +21,7 @@ import ( ) func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseCommand, map[string]cli.CommandFactory) { + baseCommand := &common.BaseCommand{ Ctx: ctx, Log: log, @@ -72,21 +65,11 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm BaseCommand: baseCommand, }, nil }, - "proxy log": func() (cli.Command, error) { - return &loglevel.LogLevelCommand{ - BaseCommand: baseCommand, - }, nil - }, "proxy read": func() (cli.Command, error) { return &read.ReadCommand{ BaseCommand: baseCommand, }, nil }, - "proxy stats": func() (cli.Command, error) { - return &stats.StatsCommand{ - BaseCommand: baseCommand, - }, nil - }, "config": func() (cli.Command, error) { return &config.ConfigCommand{ BaseCommand: baseCommand, @@ -97,21 +80,6 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm BaseCommand: baseCommand, }, nil }, - "troubleshoot": func() (cli.Command, error) { - return &troubleshoot.TroubleshootCommand{ - BaseCommand: baseCommand, - }, nil - }, - "troubleshoot proxy": func() (cli.Command, error) { - return &troubleshoot_proxy.ProxyCommand{ - BaseCommand: baseCommand, - }, nil - }, - "troubleshoot upstreams": func() (cli.Command, error) { - return &upstreams.UpstreamsCommand{ - BaseCommand: baseCommand, - }, nil - }, } return baseCommand, commands diff --git a/cli/common/base.go b/cli/common/base.go index 126e52ab07..2237b66f84 100644 --- a/cli/common/base.go +++ b/cli/common/base.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/cli/common/diff.go b/cli/common/diff.go index c11f4f8c78..30d03e2968 100644 --- a/cli/common/diff.go +++ b/cli/common/diff.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/cli/common/diff_test.go b/cli/common/diff_test.go index 83bc4a59ab..31563e0b6b 100644 --- a/cli/common/diff_test.go +++ b/cli/common/diff_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/cli/common/envoy/logger_params.go b/cli/common/envoy/logger_params.go deleted file mode 100644 index ce18e727c3..0000000000 --- a/cli/common/envoy/logger_params.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package envoy - -import ( - "fmt" - "strings" -) - -type logLevel struct { - name string - level string -} - -type LoggerParams struct { - globalLevel string - individualLevels []logLevel -} - -func NewLoggerParams() *LoggerParams { - return &LoggerParams{ - individualLevels: make([]logLevel, 0), - } -} - -func (l *LoggerParams) SetLoggerLevel(name, level string) error { - err := validateLoggerName(name) - if err != nil { - return err - } - err = validateLogLevel(level) - if err != nil { - return err - } - - l.individualLevels = append(l.individualLevels, logLevel{name: name, level: level}) - return nil -} - -func (l *LoggerParams) SetGlobalLoggerLevel(level string) error { - err := validateLogLevel(level) - if err != nil { - return err - } - l.globalLevel = level - return nil -} - -func validateLogLevel(level string) error { - if _, ok := envoyLevels[level]; !ok { - logLevels := []string{} - for levelName := range envoyLevels { - logLevels = append(logLevels, levelName) - } - return fmt.Errorf("Unknown log level %s, available log levels are %q", level, strings.Join(logLevels, ", ")) - } - return nil -} - -func validateLoggerName(name string) error { - if _, ok := EnvoyLoggers[name]; !ok { - loggers := []string{} - for loggerName := range envoyLevels { - loggers = append(loggers, loggerName) - } - return fmt.Errorf("Unknown logger %s, available loggers are %q", name, strings.Join(loggers, ", ")) - - } - return nil -} - -func (l *LoggerParams) String() string { - switch { - // Global log level change is set - case l.globalLevel != "": - return fmt.Sprintf("?level=%s", l.globalLevel) - - // only one specific logger is changed - case len(l.individualLevels) == 1: - params := fmt.Sprintf("?%s=%s", l.individualLevels[0].name, l.individualLevels[0].level) - return params - - // multiple specific loggers are changed - case len(l.individualLevels) > 1: - logParams := make([]string, 0, len(l.individualLevels)) - for _, logger := range l.individualLevels { - logParams = append(logParams, fmt.Sprintf("%s:%s", logger.name, logger.level)) - } - - params := fmt.Sprintf("?paths=%s", strings.Join(logParams, ",")) - return params - default: - - // default path, this is hit if there are no params - return "" - } -} - -// trace debug info warning error critical off. -var envoyLevels = map[string]struct{}{ - "trace": {}, - "debug": {}, - "info": {}, - "warning": {}, - "error": {}, - "critical": {}, - "off": {}, -} - -var EnvoyLoggers = map[string]struct{}{ - "admin": {}, - "alternate_protocols_cache": {}, - "aws": {}, - "assert": {}, - "backtrace": {}, - "cache_filter": {}, - "client": {}, - "config": {}, - "connection": {}, - "conn_handler": {}, - "decompression": {}, - "dns": {}, - "dubbo": {}, - "envoy_bug": {}, - "ext_authz": {}, - "ext_proc": {}, - "rocketmq": {}, - "file": {}, - "filter": {}, - "forward_proxy": {}, - "grpc": {}, - "happy_eyeballs": {}, - "hc": {}, - "health_checker": {}, - "http": {}, - "http2": {}, - "hystrix": {}, - "init": {}, - "io": {}, - "jwt": {}, - "kafka": {}, - "key_value_store": {}, - "lua": {}, - "main": {}, - "matcher": {}, - "misc": {}, - "mongo": {}, - "multi_connection": {}, - "oauth2": {}, - "quic": {}, - "quic_stream": {}, - "pool": {}, - "rbac": {}, - "rds": {}, - "redis": {}, - "router": {}, - "runtime": {}, - "stats": {}, - "secret": {}, - "tap": {}, - "testing": {}, - "thrift": {}, - "tracing": {}, - "upstream": {}, - "udp": {}, - "wasm": {}, - "websocket": {}, -} diff --git a/cli/common/envoy/logger_params_test.go b/cli/common/envoy/logger_params_test.go deleted file mode 100644 index 1d1779e267..0000000000 --- a/cli/common/envoy/logger_params_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package envoy - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSetLoggerLevelSucceeds(t *testing.T) { - t.Parallel() - testCases := map[string]struct { - levelsToSet [][]string - expectedIndividualLevels []logLevel - }{ - "single log level change trace": { - levelsToSet: [][]string{ - {"admin", "trace"}, - }, - expectedIndividualLevels: []logLevel{ - {name: "admin", level: "trace"}, - }, - }, - "single log level change debug": { - levelsToSet: [][]string{ - {"admin", "debug"}, - }, - expectedIndividualLevels: []logLevel{ - {name: "admin", level: "debug"}, - }, - }, - "single log level change info": { - levelsToSet: [][]string{ - {"admin", "info"}, - }, - expectedIndividualLevels: []logLevel{ - {name: "admin", level: "info"}, - }, - }, - "single log level change warning": { - levelsToSet: [][]string{ - {"admin", "warning"}, - }, - expectedIndividualLevels: []logLevel{ - {name: "admin", level: "warning"}, - }, - }, - "single log level change error": { - levelsToSet: [][]string{ - {"admin", "error"}, - }, - expectedIndividualLevels: []logLevel{ - {name: "admin", level: "error"}, - }, - }, - "single log level change critical": { - levelsToSet: [][]string{ - {"admin", "critical"}, - }, - expectedIndividualLevels: []logLevel{ - {name: "admin", level: "critical"}, - }, - }, - "single log level change off": { - levelsToSet: [][]string{ - {"admin", "off"}, - }, - expectedIndividualLevels: []logLevel{ - {name: "admin", level: "off"}, - }, - }, - "multiple log level change": { - levelsToSet: [][]string{ - {"admin", "info"}, - {"grpc", "debug"}, - }, - expectedIndividualLevels: []logLevel{ - {name: "admin", level: "info"}, - {name: "grpc", level: "debug"}, - }, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - loggerParams := NewLoggerParams() - for _, loggerLevel := range tc.levelsToSet { - logger, level := loggerLevel[0], loggerLevel[1] - err := loggerParams.SetLoggerLevel(logger, level) - require.NoError(t, err) - } - require.Equal(t, loggerParams.individualLevels, tc.expectedIndividualLevels) - }) - } -} - -func TestSetLoggerLevelFails(t *testing.T) { - t.Parallel() - testCases := map[string]struct { - loggerName string - loggerLevel string - }{ - "invalid logger name": { - loggerName: "this is not the logger you're looking for", - loggerLevel: "info", - }, - "invalid logger level": { - loggerName: "grpc", - loggerLevel: "this is also incorrect", - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - loggerParams := NewLoggerParams() - err := loggerParams.SetLoggerLevel(tc.loggerName, tc.loggerLevel) - require.Error(t, err) - }) - } -} - -func TestSetGlobalLoggerLevel(t *testing.T) { - t.Parallel() - for level := range envoyLevels { - loggerParams := NewLoggerParams() - err := loggerParams.SetGlobalLoggerLevel(level) - require.NoError(t, err) - } -} - -func TestSetGlobalLoggerLevelFails(t *testing.T) { - t.Parallel() - loggerParams := NewLoggerParams() - err := loggerParams.SetGlobalLoggerLevel("not a valid level") - require.Error(t, err) -} - -func TestString(t *testing.T) { - t.Parallel() - testCases := map[string]struct { - subject *LoggerParams - expectedOutput string - }{ - "when global level is set": { - subject: &LoggerParams{globalLevel: "warn"}, - expectedOutput: "?level=warn", - }, - "when one specific log level is set": { - subject: &LoggerParams{ - individualLevels: []logLevel{ - {name: "grpc", level: "warn"}, - }, - }, - expectedOutput: "?grpc=warn", - }, - "when multiple specific log levels are set": { - subject: &LoggerParams{ - individualLevels: []logLevel{ - {name: "grpc", level: "warn"}, - {name: "http", level: "info"}, - }, - }, - expectedOutput: "?paths=grpc:warn,http:info", - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - actual := tc.subject.String() - require.Equal(t, actual, tc.expectedOutput) - }) - } -} diff --git a/cli/common/envoy/testdata/fetch_debug_levels.txt b/cli/common/envoy/testdata/fetch_debug_levels.txt deleted file mode 100644 index 6b059dc1aa..0000000000 --- a/cli/common/envoy/testdata/fetch_debug_levels.txt +++ /dev/null @@ -1,59 +0,0 @@ -active loggers: - admin: debug - alternate_protocols_cache: debug - aws: debug - assert: debug - backtrace: debug - cache_filter: debug - client: debug - config: debug - connection: debug - conn_handler: debug - decompression: debug - dns: debug - dubbo: debug - envoy_bug: debug - ext_authz: debug - ext_proc: debug - rocketmq: debug - file: debug - filter: debug - forward_proxy: debug - grpc: debug - happy_eyeballs: debug - hc: debug - health_checker: debug - http: debug - http2: debug - hystrix: debug - init: debug - io: debug - jwt: debug - kafka: debug - key_value_store: debug - lua: debug - main: debug - matcher: debug - misc: debug - mongo: debug - multi_connection: debug - oauth2: debug - quic: debug - quic_stream: debug - pool: debug - rbac: debug - rds: debug - redis: debug - router: debug - runtime: debug - stats: debug - secret: debug - tap: debug - testing: debug - thrift: debug - tracing: debug - upstream: debug - udp: debug - wasm: debug - websocket: debug - diff --git a/cli/common/error.go b/cli/common/error.go index bbdd4087ff..3d8e3deb51 100644 --- a/cli/common/error.go +++ b/cli/common/error.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common // DanglingResourceError should be used when a request was made to remove diff --git a/cli/common/flag/doc.go b/cli/common/flag/doc.go index 90d2b46ffa..1e40f0742c 100644 --- a/cli/common/flag/doc.go +++ b/cli/common/flag/doc.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Package flag is a thin layer over the stdlib flag package that provides // some minimal features such as aliasing, autocompletion handling, improved // defaults, etc. It was created for mitchellh/cli but can work as a standalone diff --git a/cli/common/flag/flag.go b/cli/common/flag/flag.go index 471ff86dcf..a0ccf2b239 100644 --- a/cli/common/flag/flag.go +++ b/cli/common/flag/flag.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_bool.go b/cli/common/flag/flag_bool.go index 2800c84495..2862c2fe40 100644 --- a/cli/common/flag/flag_bool.go +++ b/cli/common/flag/flag_bool.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_enum.go b/cli/common/flag/flag_enum.go index a5612ccdb3..ac9ecaedc1 100644 --- a/cli/common/flag/flag_enum.go +++ b/cli/common/flag/flag_enum.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_enum_single.go b/cli/common/flag/flag_enum_single.go index 0165689c76..ddef52796b 100644 --- a/cli/common/flag/flag_enum_single.go +++ b/cli/common/flag/flag_enum_single.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_float.go b/cli/common/flag/flag_float.go index a27be881c8..1f7fcfad67 100644 --- a/cli/common/flag/flag_float.go +++ b/cli/common/flag/flag_float.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_int.go b/cli/common/flag/flag_int.go index fc6b880ac8..41ce1c19ab 100644 --- a/cli/common/flag/flag_int.go +++ b/cli/common/flag/flag_int.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_string.go b/cli/common/flag/flag_string.go index b79b44c6ed..b16a1be16d 100644 --- a/cli/common/flag/flag_string.go +++ b/cli/common/flag/flag_string.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_string_map.go b/cli/common/flag/flag_string_map.go index dfa54da656..5314a66655 100644 --- a/cli/common/flag/flag_string_map.go +++ b/cli/common/flag/flag_string_map.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_string_slice.go b/cli/common/flag/flag_string_slice.go index 93d3483f5b..b567c64936 100644 --- a/cli/common/flag/flag_string_slice.go +++ b/cli/common/flag/flag_string_slice.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_string_slice_test.go b/cli/common/flag/flag_string_slice_test.go index 87a5790db0..5bea43386f 100644 --- a/cli/common/flag/flag_string_slice_test.go +++ b/cli/common/flag/flag_string_slice_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_time.go b/cli/common/flag/flag_time.go index 062c558c1a..45dc297f3d 100644 --- a/cli/common/flag/flag_time.go +++ b/cli/common/flag/flag_time.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/flag_var.go b/cli/common/flag/flag_var.go index 69e2a46675..1b4114f16f 100644 --- a/cli/common/flag/flag_var.go +++ b/cli/common/flag/flag_var.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/set.go b/cli/common/flag/set.go index 8c97313f98..03ee353f80 100644 --- a/cli/common/flag/set.go +++ b/cli/common/flag/set.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/flag/set_test.go b/cli/common/flag/set_test.go index 46e195430c..9bb4ef4a7a 100644 --- a/cli/common/flag/set_test.go +++ b/cli/common/flag/set_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flag import ( diff --git a/cli/common/portforward.go b/cli/common/portforward.go index 3b0e030ac1..4cca979ac6 100644 --- a/cli/common/portforward.go +++ b/cli/common/portforward.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( @@ -47,7 +44,6 @@ type PortForward struct { type PortForwarder interface { Open(context.Context) (string, error) Close() - GetLocalPort() int } // forwarder is an interface which can be used for opening a port forward session. @@ -118,10 +114,6 @@ func (pf *PortForward) Close() { close(pf.stopChan) } -func (pf *PortForward) GetLocalPort() int { - return pf.localPort -} - // allocateLocalPort looks for an open port on localhost and sets it to the // localPort field. func (pf *PortForward) allocateLocalPort() error { diff --git a/cli/common/portforward_test.go b/cli/common/portforward_test.go index e4dad39e5a..9bf7cf8e9d 100644 --- a/cli/common/portforward_test.go +++ b/cli/common/portforward_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/cli/common/terminal/basic.go b/cli/common/terminal/basic.go index 4aba11f9f6..e8866415bc 100644 --- a/cli/common/terminal/basic.go +++ b/cli/common/terminal/basic.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package terminal import ( diff --git a/cli/common/terminal/doc.go b/cli/common/terminal/doc.go index 975bb416ec..2935fa9db5 100644 --- a/cli/common/terminal/doc.go +++ b/cli/common/terminal/doc.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Package terminal is a modified version of // https://github.com/hashicorp/waypoint-plugin-sdk/tree/74d9328929293551499078da388b8d057f3b2341/terminal. // diff --git a/cli/common/terminal/table.go b/cli/common/terminal/table.go index 553b4e85f3..67278931a4 100644 --- a/cli/common/terminal/table.go +++ b/cli/common/terminal/table.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package terminal import ( @@ -8,21 +5,15 @@ import ( ) const ( - Yellow = "yellow" - Green = "green" - Red = "red" - Blue = "blue" - Magenta = "magenta" - HiWhite = "hiwhite" + Yellow = "yellow" + Green = "green" + Red = "red" ) var colorMapping = map[string]int{ - Green: tablewriter.FgGreenColor, - Yellow: tablewriter.FgYellowColor, - Red: tablewriter.FgRedColor, - Blue: tablewriter.FgBlueColor, - Magenta: tablewriter.FgMagentaColor, - HiWhite: tablewriter.FgHiWhiteColor, + Green: tablewriter.FgGreenColor, + Yellow: tablewriter.FgYellowColor, + Red: tablewriter.FgRedColor, } // Passed to UI.Table to provide a nicely formatted table. @@ -59,21 +50,6 @@ func (t *Table) AddRow(cols []string, colors []string) { t.Rows = append(t.Rows, row) } -func (t *Table) ToJson() []map[string]interface{} { - if t == nil { - return make([]map[string]interface{}, 0) - } - jsonRes := make([]map[string]interface{}, 0) - for _, row := range t.Rows { - jsonRow := make(map[string]interface{}) - for i, ent := range row { - jsonRow[t.Headers[i]] = ent.Value - } - jsonRes = append(jsonRes, jsonRow) - } - return jsonRes -} - // Table implements UI. func (u *basicUI) Table(tbl *Table, opts ...Option) { // Build our config and set our options diff --git a/cli/common/terminal/ui.go b/cli/common/terminal/ui.go index b371e95654..dde5d532ba 100644 --- a/cli/common/terminal/ui.go +++ b/cli/common/terminal/ui.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package terminal import ( diff --git a/cli/common/usage.go b/cli/common/usage.go index 6b09950b3e..b4b4cdf780 100644 --- a/cli/common/usage.go +++ b/cli/common/usage.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/cli/common/utils.go b/cli/common/utils.go index 43a94a38ea..b2e9714a9d 100644 --- a/cli/common/utils.go +++ b/cli/common/utils.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/cli/common/utils_test.go b/cli/common/utils_test.go index 9001aba79e..c15397e4a3 100644 --- a/cli/common/utils_test.go +++ b/cli/common/utils_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/cli/config/config.go b/cli/config/config.go index 6bda5b702a..d964bc3b5c 100644 --- a/cli/config/config.go +++ b/cli/config/config.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package config import "sigs.k8s.io/yaml" diff --git a/cli/go.mod b/cli/go.mod index 6d7c56b08b..a2ff931ffd 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,37 +1,42 @@ module github.com/hashicorp/consul-k8s/cli -go 1.20 +go 1.19 require ( github.com/bgentry/speakeasy v0.1.0 github.com/cenkalti/backoff v2.2.1+incompatible - github.com/fatih/color v1.14.1 + github.com/fatih/color v1.13.0 github.com/google/go-cmp v0.5.9 github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 - github.com/hashicorp/consul/troubleshoot v0.3.0-rc1 - github.com/hashicorp/go-hclog v1.5.0 - github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54 + github.com/hashicorp/go-hclog v1.2.2 + github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc github.com/kr/text v0.2.0 - github.com/mattn/go-isatty v0.0.17 + github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/cli v1.1.2 github.com/olekukonko/tablewriter v0.0.5 github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.8.3 - golang.org/x/text v0.14.0 + golang.org/x/text v0.13.0 helm.sh/helm/v3 v3.9.4 - k8s.io/api v0.26.12 + k8s.io/api v0.25.0 k8s.io/apiextensions-apiserver v0.25.0 - k8s.io/apimachinery v0.26.12 + k8s.io/apimachinery v0.25.0 k8s.io/cli-runtime v0.24.3 - k8s.io/client-go v0.26.12 - k8s.io/utils v0.0.0-20221107191617-1a15be271d1d + k8s.io/client-go v0.25.0 + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed sigs.k8s.io/yaml v1.3.0 ) -require go.opentelemetry.io/proto/otlp v0.19.0 // indirect - require ( + cloud.google.com/go/compute v1.19.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/BurntSushi/toml v1.0.0 // indirect github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -40,17 +45,16 @@ require ( github.com/Masterminds/sprig v2.22.0+incompatible // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect - github.com/armon/go-metrics v0.4.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // 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/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect - github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect github.com/containerd/containerd v1.6.6 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.17+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/docker v20.10.17+incompatible // indirect @@ -58,30 +62,28 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/envoyproxy/go-control-plane v0.11.1 // indirect - github.com/envoyproxy/go-control-plane/xdsmatcher v0.0.0-20230524161521-aaaacbfbe53e // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.1 // indirect + github.com/emicklei/go-restful/v3 v3.8.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-errors/errors v1.0.1 // indirect - github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-gorp/gorp/v3 v3.0.2 // indirect github.com/go-logr/logr v1.2.3 // 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/analysis v0.20.0 // indirect + github.com/go-openapi/errors v0.20.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.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/validate v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/loads v0.20.2 // indirect + github.com/go-openapi/runtime v0.19.24 // indirect + github.com/go-openapi/spec v0.20.3 // indirect + github.com/go-openapi/strfmt v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-openapi/validate v0.20.2 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect + github.com/go-stack/stack v1.8.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect @@ -91,18 +93,11 @@ require ( github.com/gorilla/mux v1.8.0 // 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.22.0-rc1 // indirect - github.com/hashicorp/consul/envoyextensions v0.3.0-rc1 // 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-cleanhttp v0.5.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/golang-lru v0.5.4 // indirect - github.com/hashicorp/serf v0.10.1 // indirect github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -110,17 +105,16 @@ require ( github.com/klauspost/compress v1.13.6 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lib/pq v1.10.7 // indirect + github.com/lib/pq v1.10.6 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // 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 github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // 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 @@ -130,14 +124,12 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oklog/ulid v1.3.1 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect 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/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -150,36 +142,30 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect - go.mongodb.org/mongo-driver v1.11.1 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.11.1 // indirect - go.opentelemetry.io/otel/trace v1.11.1 // indirect - go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + go.mongodb.org/mongo-driver v1.4.6 // indirect + go.starlark.net v0.0.0-20200707032745-474f21a9602d // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.30.0 // 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.25.0 // indirect k8s.io/component-base v0.25.0 // indirect - k8s.io/klog/v2 v2.80.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect k8s.io/kubectl v0.24.2 // indirect oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect diff --git a/cli/go.sum b/cli/go.sum index e69191e709..e5bee3daf9 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -24,6 +24,10 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -39,19 +43,29 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= +github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -71,28 +85,34 @@ github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6 github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= -github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -110,8 +130,6 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -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 v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -122,19 +140,11 @@ github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -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/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -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/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= @@ -149,9 +159,8 @@ github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= @@ -168,6 +177,7 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= @@ -176,8 +186,8 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7fo github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -185,14 +195,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= -github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= -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.1 h1:kt9FtLiooDc0vbwTLhdg3dyNX1K9Qwa1EK9LcD4jVUQ= -github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -200,10 +203,8 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -215,14 +216,15 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= -github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= -github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -232,55 +234,106 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -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.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= -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= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= +github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk= +github.com/go-openapi/analysis v0.20.0 h1:UN09o0kNhleunxW7LR+KnltD0YrJ8FF03pSqvAN3Vro= +github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= -github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -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= -github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc= -github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= -github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= -github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= -github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= +github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= +github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= +github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4= +github.com/go-openapi/loads v0.20.2 h1:z5p5Xf5wujMxS1y8aP+vxwW5qYT2zdJBbXKmQUG3lcc= +github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= +github.com/go-openapi/runtime v0.19.24 h1:TqagMVlRAOTwllE/7hNKx6rQ10O6T8ZzeJdMjSTKaD4= +github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= +github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= +github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ= +github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ= +github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= +github.com/go-openapi/strfmt v0.20.0 h1:l2omNtmNbMc39IGptl9BuXBEKcZfS8zjrTsPKTiJiDM= +github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M= +github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -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/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= -github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= +github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= +github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= +github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= +github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts= +github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= @@ -320,9 +373,11 @@ github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -375,7 +430,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -399,6 +453,7 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= @@ -416,63 +471,35 @@ github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16 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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.22.0-rc1 h1:ePmGqndeMgaI38KUbSA/CqTzeEAIogXyWnfNJzglo70= -github.com/hashicorp/consul/api v1.22.0-rc1/go.mod h1:wtduXtbAqSGtBdi3tyA5SSAYGAG51rBejV9SEUBciMY= -github.com/hashicorp/consul/envoyextensions v0.3.0-rc1 h1:weclrwjvLeX+vxPOyo4b4dCDxSpnDl60Z9K16nnCVnI= -github.com/hashicorp/consul/envoyextensions v0.3.0-rc1/go.mod h1:ckxoPHMiWXAe6dhyxmKsX1XqO4KTV64KWIyTu44z8UI= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.14.0-rc1 h1:PuETOfN0uxl28i0Pq6rK7TBCrIl7psMbL0YTSje4KvM= -github.com/hashicorp/consul/troubleshoot v0.3.0-rc1 h1:Z6ZUEKILsf85wA/zXK3XMop6IGtjui4ZZ0bAu+JIAz4= -github.com/hashicorp/consul/troubleshoot v0.3.0-rc1/go.mod h1:2WfcYZ8M4vpLtTv9M5Dp3egqSPZ16l5XsqMpO9QUYxc= 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= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= +github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 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-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54 h1:m05LS8cYY4A81y5hUaNKTn2/F+V9BVvulB2GxopoZFo= -github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54/go.mod h1:xP7wmWAmdMxs/7+ovH3jZn+MCDhHRj50Rn+m7JIY3Ck= +github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc h1:on26TCKYnX7JzZCtwkR/LWHSqMu40PoZ6h/0e6Pq8ug= +github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc/go.mod h1:/9UoDY2FYYA8lFaKBb2HmM/jKYZGANmf65q9QRc/cVw= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= -github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= -github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= @@ -481,10 +508,12 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 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.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -494,7 +523,6 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -508,8 +536,10 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -520,9 +550,10 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -532,18 +563,20 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= @@ -552,36 +585,25 @@ github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2 github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= @@ -590,8 +612,6 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -600,10 +620,11 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -636,8 +657,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -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.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= @@ -647,20 +666,18 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= 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.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -672,18 +689,16 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= -github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 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.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= @@ -697,7 +712,6 @@ github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUo github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 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.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= @@ -705,7 +719,6 @@ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ 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.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= @@ -714,7 +727,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L 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.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY= github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= @@ -723,8 +735,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= @@ -761,40 +773,30 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag 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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -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/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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 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= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -805,15 +807,19 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= -go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= -go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8= -go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.4.6 h1:rh7GdYmDrb8AQSkF8yteAus8qYOgOASWDOv1BWqBXkU= +go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -822,28 +828,20 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 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 v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= -go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= -go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.starlark.net v0.0.0-20230128213706-3f75dec8e403 h1:jPeC7Exc+m8OBJUlWbBLh0O5UZPM7yU5W4adnhhbG4U= -go.starlark.net v0.0.0-20230128213706-3f75dec8e403/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +go.starlark.net v0.0.0-20200707032745-474f21a9602d h1:uFqwFYlX7d5ZSp+IqhXxct0SybXrTzEBDvb2CkEhPBs= +go.starlark.net v0.0.0-20200707032745-474f21a9602d/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -852,23 +850,25 @@ go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -879,8 +879,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -910,12 +908,14 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -925,7 +925,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -938,19 +937,20 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -986,8 +986,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -995,8 +995,8 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1005,12 +1005,11 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1045,13 +1044,11 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1066,16 +1063,12 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1085,16 +1078,18 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -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/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1108,11 +1103,12 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1232,12 +1228,7 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= -google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= -google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e h1:AZX1ra8YbFMSb7+1pI8S9v4rrgRR7jU1FmuFSSjTVcQ= -google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1262,7 +1253,6 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1278,8 +1268,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -1307,7 +1297,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.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= @@ -1324,14 +1313,14 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= -k8s.io/api v0.26.12 h1:jJm3s5ot05SUN3tPGg3b+XWuBE7rO/X0+dnVMhxyd5o= -k8s.io/api v0.26.12/go.mod h1:N+HUXukmtXNOKDngxXrEPbZWggWx01tH/N0nG4nV0oo= +k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= +k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apimachinery v0.26.12 h1:y+OgufxqLIZtyXIydRhjLBGzrYLF+qwiDdCFXYOjeN4= -k8s.io/apimachinery v0.26.12/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= +k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= +k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4= k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo= k8s.io/cli-runtime v0.24.2/go.mod h1:1LIhKL2RblkhfG4v5lZEt7FtgFG5mVb8wqv5lE9m5qY= @@ -1339,8 +1328,8 @@ k8s.io/cli-runtime v0.24.3 h1:O9YvUHrDSCQUPlsqVmaqDrueqjpJ7IO6Yas9B6xGSoo= k8s.io/cli-runtime v0.24.3/go.mod h1:In84wauoMOqa7JDvDSXGbf8lTNlr70fOGpYlYfJtSqA= k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= -k8s.io/client-go v0.26.12 h1:kPpTpIeFNqwo4UyvoqzNp3DNK2mbGcdGv23eS1U8VMo= -k8s.io/client-go v0.26.12/go.mod h1:V7thEnIFroyNZOU30dKLiiVeqQmJz45shJG1mu7nONQ= +k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= +k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= @@ -1352,19 +1341,19 @@ k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAE k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kubectl v0.24.2 h1:+RfQVhth8akUmIc2Ge8krMl/pt66V7210ka3RE/p0J4= k8s.io/kubectl v0.24.2/go.mod h1:+HIFJc0bA6Tzu5O/YcuUt45APAxnNL8LeMuXwoiGsPg= k8s.io/metrics v0.24.2/go.mod h1:5NWURxZ6Lz5gj8TFU83+vdWIVASx7W8lwPpHYCqopMo= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/cli/helm/action.go b/cli/helm/action.go index 4afe1ba9d8..d71014c762 100644 --- a/cli/helm/action.go +++ b/cli/helm/action.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm import ( diff --git a/cli/helm/chart.go b/cli/helm/chart.go index c57d9220bc..f679ca591d 100644 --- a/cli/helm/chart.go +++ b/cli/helm/chart.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm import ( @@ -65,7 +62,7 @@ func readChartFiles(chart embed.FS, chartDirName string) ([]*loader.BufferedFile // filepath.* functions, then Go on Windows will try to use `\` delimiters to access // the embedded filesystem, which will then fail. - // Load Chart.yaml and values.yaml. + // Load Chart.yaml and values.yaml first. for _, f := range []string{chartFileName, valuesFileName} { file, err := readFile(chart, path.Join(chartDirName, f), chartDirName) if err != nil { @@ -74,7 +71,7 @@ func readChartFiles(chart embed.FS, chartDirName string) ([]*loader.BufferedFile chartFiles = append(chartFiles, file) } - // Load everything under templates/. + // Now load everything under templates/. dirs, err := chart.ReadDir(path.Join(chartDirName, templatesDirName)) if err != nil { return nil, err diff --git a/cli/helm/chart_test.go b/cli/helm/chart_test.go index f0e33e092b..9ed48d50a5 100644 --- a/cli/helm/chart_test.go +++ b/cli/helm/chart_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm import ( @@ -38,10 +35,10 @@ func TestLoadChart(t *testing.T) { func TestReadChartFiles(t *testing.T) { directory := "test_fixtures/consul" expectedFiles := map[string]string{ - "Chart.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\n# This is a mock Helm Chart.yaml file used for testing.\napiVersion: v2\nname: Foo\nversion: 0.1.0\ndescription: Mock Helm Chart for testing.", - "values.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\n# This is a mock Helm values.yaml file used for testing.\nkey: value", + "Chart.yaml": "# This is a mock Helm Chart.yaml file used for testing.\napiVersion: v2\nname: Foo\nversion: 0.1.0\ndescription: Mock Helm Chart for testing.", + "values.yaml": "# This is a mock Helm values.yaml file used for testing.\nkey: value", "templates/_helpers.tpl": "helpers", - "templates/foo.yaml": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nfoo: bar\n", + "templates/foo.yaml": "foo: bar\n", } files, err := readChartFiles(testChartFiles, directory) diff --git a/cli/helm/install.go b/cli/helm/install.go index c19dd9dff3..1e0a83b8b8 100644 --- a/cli/helm/install.go +++ b/cli/helm/install.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm import ( diff --git a/cli/helm/install_test.go b/cli/helm/install_test.go index 7b154f6975..6458a06e54 100644 --- a/cli/helm/install_test.go +++ b/cli/helm/install_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm import ( diff --git a/cli/helm/mock.go b/cli/helm/mock.go index ce0793ef01..05d3b6edb4 100644 --- a/cli/helm/mock.go +++ b/cli/helm/mock.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm import ( diff --git a/cli/helm/test_fixtures/consul/Chart.yaml b/cli/helm/test_fixtures/consul/Chart.yaml index 488e2da2a7..72cdfea793 100644 --- a/cli/helm/test_fixtures/consul/Chart.yaml +++ b/cli/helm/test_fixtures/consul/Chart.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # This is a mock Helm Chart.yaml file used for testing. apiVersion: v2 name: Foo diff --git a/cli/helm/test_fixtures/consul/templates/foo.yaml b/cli/helm/test_fixtures/consul/templates/foo.yaml index b17972509f..20e9ff3fea 100644 --- a/cli/helm/test_fixtures/consul/templates/foo.yaml +++ b/cli/helm/test_fixtures/consul/templates/foo.yaml @@ -1,4 +1 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - foo: bar diff --git a/cli/helm/test_fixtures/consul/values.yaml b/cli/helm/test_fixtures/consul/values.yaml index a0b3ab57b1..e8062923bc 100644 --- a/cli/helm/test_fixtures/consul/values.yaml +++ b/cli/helm/test_fixtures/consul/values.yaml @@ -1,5 +1,2 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # This is a mock Helm values.yaml file used for testing. key: value \ No newline at end of file diff --git a/cli/helm/upgrade.go b/cli/helm/upgrade.go index 8c0072c345..e9d4545652 100644 --- a/cli/helm/upgrade.go +++ b/cli/helm/upgrade.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm import ( diff --git a/cli/helm/upgrade_test.go b/cli/helm/upgrade_test.go index 750c903d33..4ae09bb36f 100644 --- a/cli/helm/upgrade_test.go +++ b/cli/helm/upgrade_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm import ( diff --git a/cli/helm/values.go b/cli/helm/values.go index 19e19d8d05..1f55ecfdf3 100644 --- a/cli/helm/values.go +++ b/cli/helm/values.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package helm // HACK this is a temporary hard-coded struct. We should actually generate this from our `values.yaml` file. @@ -576,13 +573,11 @@ type CopyAnnotations struct { } type ManagedGatewayClass struct { - Enabled bool `yaml:"enabled"` - NodeSelector interface{} `yaml:"nodeSelector"` - ServiceType string `yaml:"serviceType"` - UseHostPorts bool `yaml:"useHostPorts"` - CopyAnnotations CopyAnnotations `yaml:"copyAnnotations"` - OpenshiftSCCName string `yaml:"openshiftSCCName"` - MapPrivilegedContainerPorts int `yaml:"mapPrivilegedContainerPorts"` + Enabled bool `yaml:"enabled"` + NodeSelector interface{} `yaml:"nodeSelector"` + ServiceType string `yaml:"serviceType"` + UseHostPorts bool `yaml:"useHostPorts"` + CopyAnnotations CopyAnnotations `yaml:"copyAnnotations"` } type Service struct { diff --git a/cli/main.go b/cli/main.go index 133e2a28a8..0ffd03328c 100644 --- a/cli/main.go +++ b/cli/main.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( diff --git a/cli/preset/cloud_preset.go b/cli/preset/cloud_preset.go index 6122c3f6d1..95219cb378 100644 --- a/cli/preset/cloud_preset.go +++ b/cli/preset/cloud_preset.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package preset import ( @@ -8,7 +5,6 @@ import ( "encoding/json" "fmt" "net/http" - "strings" "github.com/hashicorp/consul-k8s/cli/common" "github.com/hashicorp/consul-k8s/cli/common/terminal" @@ -25,26 +21,24 @@ import ( ) const ( - secretNameHCPClientID = "consul-hcp-client-id" - secretKeyHCPClientID = "client-id" - secretNameHCPClientSecret = "consul-hcp-client-secret" - secretKeyHCPClientSecret = "client-secret" - secretNameHCPObservabilityClientID = "consul-hcp-observability-client-id" - secretNameHCPObservabilityClientSecret = "consul-hcp-observability-client-secret" - secretNameHCPResourceID = "consul-hcp-resource-id" - secretKeyHCPResourceID = "resource-id" - secretNameHCPAPIHostname = "consul-hcp-api-host" - secretKeyHCPAPIHostname = "api-hostname" - secretNameHCPAuthURL = "consul-hcp-auth-url" - secretKeyHCPAuthURL = "auth-url" - secretNameHCPScadaAddress = "consul-hcp-scada-address" - secretKeyHCPScadaAddress = "scada-address" - secretNameGossipKey = "consul-gossip-key" - secretKeyGossipKey = "key" - secretNameBootstrapToken = "consul-bootstrap-token" - secretKeyBootstrapToken = "token" - secretNameServerCA = "consul-server-ca" - secretNameServerCert = "consul-server-cert" + secretNameHCPClientID = "consul-hcp-client-id" + secretKeyHCPClientID = "client-id" + secretNameHCPClientSecret = "consul-hcp-client-secret" + secretKeyHCPClientSecret = "client-secret" + secretNameHCPResourceID = "consul-hcp-resource-id" + secretKeyHCPResourceID = "resource-id" + secretNameHCPAPIHostname = "consul-hcp-api-host" + secretKeyHCPAPIHostname = "api-hostname" + secretNameHCPAuthURL = "consul-hcp-auth-url" + secretKeyHCPAuthURL = "auth-url" + secretNameHCPScadaAddress = "consul-hcp-scada-address" + secretKeyHCPScadaAddress = "scada-address" + secretNameGossipKey = "consul-gossip-key" + secretKeyGossipKey = "key" + secretNameBootstrapToken = "consul-bootstrap-token" + secretKeyBootstrapToken = "token" + secretNameServerCA = "consul-server-ca" + secretNameServerCert = "consul-server-cert" ) // CloudBootstrapConfig represents the response fetched from the agent @@ -59,14 +53,12 @@ type CloudBootstrapConfig struct { // provided by the user in order to make a call to fetch the agent bootstrap // config data from the endpoint in HCP. type HCPConfig struct { - ResourceID string - ClientID string - ClientSecret string - ObservabilityClientID string - ObservabilityClientSecret string - AuthURL string - APIHostname string - ScadaAddress string + ResourceID string + ClientID string + ClientSecret string + AuthURL string + APIHostname string + ScadaAddress string } // ConsulConfig represents 'cluster.consul_config' in the response @@ -147,32 +139,10 @@ func (c *CloudPreset) fetchAgentBootstrapConfig() (*CloudBootstrapConfig, error) return nil, err } - obsParams := hcpgnm.NewGetObservabilitySecretParamsWithContext(c.Context). - WithID(clusterResource.ID). - WithLocationOrganizationID(clusterResource.Organization). - WithLocationProjectID(clusterResource.Project). - WithHTTPClient(c.HTTPClient) - - obsResp, err := hcpgnmClient.GetObservabilitySecret(obsParams, nil) - if err != nil { - return nil, err - } - bootstrapConfig := resp.GetPayload() c.UI.Output("HCP configuration successfully fetched.", terminal.WithSuccessStyle()) - cloudConfig, err := c.parseBootstrapConfigResponse(bootstrapConfig) - if err != nil { - return nil, err - } - - // if we don't have any keys fall back to the cluster credentials. Remove fallback in the future probably - if len(obsResp.GetPayload().Keys) != 0 { - cloudConfig.HCPConfig.ObservabilityClientID = obsResp.GetPayload().Keys[0].ClientID - cloudConfig.HCPConfig.ObservabilityClientSecret = obsResp.GetPayload().Keys[0].ClientSecret - } - - return cloudConfig, nil + return c.parseBootstrapConfigResponse(bootstrapConfig) } // parseBootstrapConfigResponse unmarshals the boostrap parseBootstrapConfigResponse @@ -210,16 +180,6 @@ func (c *CloudPreset) getHelmConfigWithMapSecretNames(cfg *CloudBootstrapConfig) authURLCfg := getOptionalSecretFromHCPConfig(cfg.HCPConfig.AuthURL, "authUrl", secretNameHCPAuthURL, secretKeyHCPAuthURL) scadaAddressCfg := getOptionalSecretFromHCPConfig(cfg.HCPConfig.ScadaAddress, "scadaAddress", secretNameHCPScadaAddress, secretKeyHCPScadaAddress) - var ( - observabilityClientIDSecretName = secretNameHCPObservabilityClientID - observabilityClientSecretSecretName = secretNameHCPObservabilityClientSecret - ) - - if cfg.HCPConfig.ObservabilityClientID == "" && cfg.HCPConfig.ObservabilityClientSecret == "" { - observabilityClientIDSecretName = secretNameHCPClientID - observabilityClientSecretSecretName = secretNameHCPClientSecret - } - // Need to make sure the below has strict spaces and no tabs values := fmt.Sprintf(` global: @@ -238,8 +198,6 @@ global: bootstrapToken: secretName: %s secretKey: %s - metrics: - enableTelemetryCollector: true cloud: enabled: true resourceId: @@ -254,33 +212,22 @@ global: %s %s %s -telemetryCollector: - enabled: true - cloud: - clientId: - secretName: %s - secretKey: %s - clientSecret: - secretName: %s - secretKey: %s server: replicas: %d affinity: null - serverCert: + serverCert: secretName: %s connectInject: enabled: true controller: enabled: true -`, strings.ToLower(cfg.BootstrapResponse.Cluster.ID), secretNameServerCA, corev1.TLSCertKey, +`, cfg.BootstrapResponse.Cluster.ID, secretNameServerCA, corev1.TLSCertKey, secretNameGossipKey, secretKeyGossipKey, secretNameBootstrapToken, secretKeyBootstrapToken, secretNameHCPResourceID, secretKeyHCPResourceID, secretNameHCPClientID, secretKeyHCPClientID, secretNameHCPClientSecret, secretKeyHCPClientSecret, apiHostCfg, authURLCfg, scadaAddressCfg, - observabilityClientIDSecretName, secretKeyHCPClientID, - observabilityClientSecretSecretName, secretKeyHCPClientSecret, cfg.BootstrapResponse.Cluster.BootstrapExpect, secretNameServerCert) valuesMap := config.ConvertToMap(values) return valuesMap @@ -341,28 +288,6 @@ func (c *CloudPreset) saveSecretsFromBootstrapConfig(config *CloudBootstrapConfi secretKeyHCPClientSecret, c.KubernetesNamespace), terminal.WithSuccessStyle()) } - if config.HCPConfig.ObservabilityClientID != "" { - data := map[string][]byte{ - secretKeyHCPClientID: []byte(config.HCPConfig.ObservabilityClientID), - } - if err := c.saveSecret(secretNameHCPObservabilityClientID, data, corev1.SecretTypeOpaque); err != nil { - return err - } - c.UI.Output(fmt.Sprintf("HCP client secret saved in '%s' secret in namespace '%s'.", - "observability-"+secretKeyHCPClientID, c.KubernetesNamespace), terminal.WithSuccessStyle()) - } - - if config.HCPConfig.ObservabilityClientSecret != "" { - data := map[string][]byte{ - secretKeyHCPClientSecret: []byte(config.HCPConfig.ObservabilityClientSecret), - } - if err := c.saveSecret(secretNameHCPObservabilityClientSecret, data, corev1.SecretTypeOpaque); err != nil { - return err - } - c.UI.Output(fmt.Sprintf("HCP client secret saved in '%s' secret in namespace '%s'.", - "observability-"+secretKeyHCPClientSecret, c.KubernetesNamespace), terminal.WithSuccessStyle()) - } - // bootstrap token if config.ConsulConfig.ACL.Tokens.InitialManagement != "" { data := map[string][]byte{ diff --git a/cli/preset/cloud_preset_test.go b/cli/preset/cloud_preset_test.go index 001f5c762e..946e1ca158 100644 --- a/cli/preset/cloud_preset_test.go +++ b/cli/preset/cloud_preset_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package preset import ( @@ -25,32 +22,28 @@ import ( ) const ( - hcpClientID = "RAxJflDbxDXw8kLY6jWmwqMz3kVe7NnL" - hcpClientSecret = "1fNzurLatQPLPwf7jnD4fRtU9f5nH31RKBHayy08uQ6P-6nwI1rFZjMXb4m3cCKH" - observabilityHCPClientId = "fake-client-id" - observabilityHCPClientSecret = "fake-client-secret" - hcpResourceID = "organization/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/project/36019e0d-ed59-4df6-9990-05bb7fc793b6/hashicorp.consul.global-network-manager.cluster/prod-on-prem" - expectedSecretNameHCPClientId = "consul-hcp-client-id" - expectedSecretNameHCPClientSecret = "consul-hcp-client-secret" - expectedSecretNameHCPObservabilityClientId = "consul-hcp-observability-client-id" - expectedSecretNameHCPObservabilityClientSecret = "consul-hcp-observability-client-secret" - expectedSecretNameHCPResourceId = "consul-hcp-resource-id" - expectedSecretNameHCPAuthURL = "consul-hcp-auth-url" - expectedSecretNameHCPApiHostname = "consul-hcp-api-host" - expectedSecretNameHCPScadaAddress = "consul-hcp-scada-address" - expectedSecretNameGossipKey = "consul-gossip-key" - expectedSecretNameBootstrap = "consul-bootstrap-token" - expectedSecretNameServerCA = "consul-server-ca" - expectedSecretNameServerCert = "consul-server-cert" - namespace = "consul" - validResponse = ` + hcpClientID = "RAxJflDbxDXw8kLY6jWmwqMz3kVe7NnL" + hcpClientSecret = "1fNzurLatQPLPwf7jnD4fRtU9f5nH31RKBHayy08uQ6P-6nwI1rFZjMXb4m3cCKH" + hcpResourceID = "organization/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/project/36019e0d-ed59-4df6-9990-05bb7fc793b6/hashicorp.consul.global-network-manager.cluster/prod-on-prem" + expectedSecretNameHCPClientId = "consul-hcp-client-id" + expectedSecretNameHCPClientSecret = "consul-hcp-client-secret" + expectedSecretNameHCPResourceId = "consul-hcp-resource-id" + expectedSecretNameHCPAuthURL = "consul-hcp-auth-url" + expectedSecretNameHCPApiHostname = "consul-hcp-api-host" + expectedSecretNameHCPScadaAddress = "consul-hcp-scada-address" + expectedSecretNameGossipKey = "consul-gossip-key" + expectedSecretNameBootstrap = "consul-bootstrap-token" + expectedSecretNameServerCA = "consul-server-ca" + expectedSecretNameServerCert = "consul-server-cert" + namespace = "consul" + validResponse = ` { - "cluster": + "cluster": { - "id": "Dc1", + "id": "dc1", "bootstrap_expect" : 3 }, - "bootstrap": + "bootstrap": { "gossip_key": "Wa6/XFAnYy0f9iqVH2iiG+yore3CqHSemUy4AIVTa/w=", "server_tls": { @@ -63,27 +56,11 @@ const ( "consul_config": "{\"acl\":{\"default_policy\":\"deny\",\"enable_token_persistence\":true,\"enabled\":true,\"tokens\":{\"agent\":\"74044c72-03c8-42b0-b57f-728bb22ca7fb\",\"initial_management\":\"74044c72-03c8-42b0-b57f-728bb22ca7fb\"}},\"auto_encrypt\":{\"allow_tls\":true},\"bootstrap_expect\":1,\"encrypt\":\"yUPhgtteok1/bHoVIoRnJMfOrKrb1TDDyWJRh9rlUjg=\",\"encrypt_verify_incoming\":true,\"encrypt_verify_outgoing\":true,\"ports\":{\"http\":-1,\"https\":8501},\"retry_join\":[],\"verify_incoming\":true,\"verify_outgoing\":true,\"verify_server_hostname\":true}" } }` - observabilityResponse = ` -{ - "id": "Dc1", - "location": { - "organization_id": "abc123", - "project_id": "123abc" - }, - "keys": [ - { - "created_at":"", - "client_id": "fake-client-id", - "client_secret": "fake-client-secret" - } - ] -} -` ) var validBootstrapReponse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse = &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ Bootstrap: &models.HashicorpCloudGlobalNetworkManager20220215ClusterBootstrap{ - ID: "Dc1", + ID: "dc1", GossipKey: "Wa6/XFAnYy0f9iqVH2iiG+yore3CqHSemUy4AIVTa/w=", BootstrapExpect: 3, ServerTLS: &models.HashicorpCloudGlobalNetworkManager20220215ServerTLS{ @@ -99,14 +76,12 @@ var validBootstrapReponse *models.HashicorpCloudGlobalNetworkManager20220215Agen } var hcpConfig *HCPConfig = &HCPConfig{ - ResourceID: hcpResourceID, - ClientID: hcpClientID, - ClientSecret: hcpClientSecret, - AuthURL: "https://foobar", - APIHostname: "https://foo.bar", - ScadaAddress: "10.10.10.10", - ObservabilityClientID: observabilityHCPClientId, - ObservabilityClientSecret: observabilityHCPClientSecret, + ResourceID: hcpResourceID, + ClientID: hcpClientID, + ClientSecret: hcpClientSecret, + AuthURL: "https://foobar", + APIHostname: "https://foo.bar", + ScadaAddress: "10.10.10.10", } var validBootstrapConfig *CloudBootstrapConfig = &CloudBootstrapConfig{ @@ -130,19 +105,9 @@ func TestGetValueMap(t *testing.T) { // Start the mock HCP server. hcpMockServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/json") - if r != nil && r.Method == "GET" { - switch r.URL.Path { - case "/global-network-manager/2022-02-15/organizations/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/projects/36019e0d-ed59-4df6-9990-05bb7fc793b6/clusters/prod-on-prem/agent/bootstrap_config": - w.Write([]byte(validResponse)) - case "/global-network-manager/2022-02-15/organizations/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/projects/36019e0d-ed59-4df6-9990-05bb7fc793b6/clusters/prod-on-prem/credentials/observability": - w.Write([]byte(observabilityResponse)) - default: - w.Write([]byte(` - { - "access_token": "dummy-token" - } - `)) - } + if r != nil && r.URL.Path == "/global-network-manager/2022-02-15/organizations/ccbdd191-5dc3-4a73-9e05-6ac30ca67992/projects/36019e0d-ed59-4df6-9990-05bb7fc793b6/clusters/prod-on-prem/agent/bootstrap_config" && + r.Method == "GET" { + w.Write([]byte(validResponse)) } else { w.Write([]byte(` { @@ -235,14 +200,6 @@ func TestGetValueMap(t *testing.T) { ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPClientSecret, secretKeyHCPClientSecret, bsConfig.HCPConfig.ClientSecret, corev1.SecretTypeOpaque) - // Check the observability hcp client id secret is as expected. - ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPObservabilityClientID, secretKeyHCPClientID, - bsConfig.HCPConfig.ObservabilityClientID, corev1.SecretTypeOpaque) - - // Check the observability hcp client secret secret is as expected. - ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPObservabilityClientSecret, secretKeyHCPClientSecret, - bsConfig.HCPConfig.ObservabilityClientSecret, corev1.SecretTypeOpaque) - // Check the bootstrap token secret is as expected. ensureSecretKeyValueMatchesExpected(t, k8s, secretNameBootstrapToken, secretKeyBootstrapToken, bsConfig.ConsulConfig.ACL.Tokens.InitialManagement, corev1.SecretTypeOpaque) @@ -523,8 +480,6 @@ global: gossipEncryption: secretKey: key secretName: consul-gossip-key - metrics: - enableTelemetryCollector: true tls: caCert: secretKey: tls.crt @@ -536,15 +491,6 @@ server: replicas: 3 serverCert: secretName: consul-server-cert -telemetryCollector: - cloud: - clientId: - secretKey: client-id - secretName: consul-hcp-observability-client-id - clientSecret: - secretKey: client-secret - secretName: consul-hcp-observability-client-secret - enabled: true ` const expectedWithoutOptional = `connectInject: @@ -572,57 +518,6 @@ global: gossipEncryption: secretKey: key secretName: consul-gossip-key - metrics: - enableTelemetryCollector: true - tls: - caCert: - secretKey: tls.crt - secretName: consul-server-ca - enableAutoEncrypt: true - enabled: true -server: - affinity: null - replicas: 3 - serverCert: - secretName: consul-server-cert -telemetryCollector: - cloud: - clientId: - secretKey: client-id - secretName: consul-hcp-observability-client-id - clientSecret: - secretKey: client-secret - secretName: consul-hcp-observability-client-secret - enabled: true -` - - const expectedWithoutObservability = `connectInject: - enabled: true -controller: - enabled: true -global: - acls: - bootstrapToken: - secretKey: token - secretName: consul-bootstrap-token - manageSystemACLs: true - cloud: - clientId: - secretKey: client-id - secretName: consul-hcp-client-id - clientSecret: - secretKey: client-secret - secretName: consul-hcp-client-secret - enabled: true - resourceId: - secretKey: resource-id - secretName: consul-hcp-resource-id - datacenter: dc1 - gossipEncryption: - secretKey: key - secretName: consul-gossip-key - metrics: - enableTelemetryCollector: true tls: caCert: secretKey: tls.crt @@ -634,45 +529,16 @@ server: replicas: 3 serverCert: secretName: consul-server-cert -telemetryCollector: - cloud: - clientId: - secretKey: client-id - secretName: consul-hcp-client-id - clientSecret: - secretKey: client-secret - secretName: consul-hcp-client-secret - enabled: true ` cloudPreset := &CloudPreset{} - testCases := map[string]struct { + testCases := []struct { + description string config *CloudBootstrapConfig expectedYaml string }{ - "Config_including_optional_parameters_with_mixedcase_DC": { - &CloudBootstrapConfig{ - BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ - Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ - BootstrapExpect: 3, - ID: "Dc1", - }, - }, - HCPConfig: HCPConfig{ - ResourceID: "consul-hcp-resource-id", - ClientID: "consul-hcp-client-id", - ClientSecret: "consul-hcp-client-secret", - AuthURL: "consul-hcp-auth-url", - APIHostname: "consul-hcp-api-host", - ScadaAddress: "consul-hcp-scada-address", - ObservabilityClientID: "consul-hcp-observability-client-id", - ObservabilityClientSecret: "consul-hcp-observability-client-secret", - }, - }, - expectedFull, - }, - "Config_including_optional_parameters": { + {"Config including optional parameters", &CloudBootstrapConfig{ BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ @@ -681,37 +547,17 @@ telemetryCollector: }, }, HCPConfig: HCPConfig{ - ResourceID: "consul-hcp-resource-id", - ClientID: "consul-hcp-client-id", - ClientSecret: "consul-hcp-client-secret", - AuthURL: "consul-hcp-auth-url", - APIHostname: "consul-hcp-api-host", - ScadaAddress: "consul-hcp-scada-address", - ObservabilityClientID: "consul-hcp-observability-client-id", - ObservabilityClientSecret: "consul-hcp-observability-client-secret", + ResourceID: "consul-hcp-resource-id", + ClientID: "consul-hcp-client-id", + ClientSecret: "consul-hcp-client-secret", + AuthURL: "consul-hcp-auth-url", + APIHostname: "consul-hcp-api-host", + ScadaAddress: "consul-hcp-scada-address", }, }, expectedFull, }, - "Config_without_optional_parameters": { - &CloudBootstrapConfig{ - BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ - Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ - BootstrapExpect: 3, - ID: "dc1", - }, - }, - HCPConfig: HCPConfig{ - ResourceID: "consul-hcp-resource-id", - ClientID: "consul-hcp-client-id", - ClientSecret: "consul-hcp-client-secret", - ObservabilityClientID: "consul-hcp-observability-client-id", - ObservabilityClientSecret: "consul-hcp-observability-client-secret", - }, - }, - expectedWithoutOptional, - }, - "Config_without_observability_parameters": { + {"Config without optional parameters", &CloudBootstrapConfig{ BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ @@ -725,11 +571,11 @@ telemetryCollector: ClientSecret: "consul-hcp-client-secret", }, }, - expectedWithoutObservability, + expectedWithoutOptional, }, } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { cloudHelmValues := cloudPreset.getHelmConfigWithMapSecretNames(tc.config) require.NotNil(t, cloudHelmValues) valuesYaml, err := yaml.Marshal(cloudHelmValues) @@ -758,8 +604,6 @@ func savePlaceholderSecret(secretName string, k8sClient kubernetes.Interface) { func deleteSecrets(k8sClient kubernetes.Interface) { k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPClientId, metav1.DeleteOptions{}) k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPClientSecret, metav1.DeleteOptions{}) - k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPObservabilityClientId, metav1.DeleteOptions{}) - k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPObservabilityClientSecret, metav1.DeleteOptions{}) k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPResourceId, metav1.DeleteOptions{}) k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPAuthURL, metav1.DeleteOptions{}) k8sClient.CoreV1().Secrets(namespace).Delete(context.Background(), expectedSecretNameHCPApiHostname, metav1.DeleteOptions{}) @@ -788,14 +632,6 @@ func checkAllSecretsWereSaved(t require.TestingT, k8s kubernetes.Interface, expe ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPClientSecret, secretKeyHCPClientSecret, expectedConfig.HCPConfig.ClientSecret, corev1.SecretTypeOpaque) - // Check the hcp client id secret is as expected. - ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPObservabilityClientID, secretKeyHCPClientID, - expectedConfig.HCPConfig.ObservabilityClientID, corev1.SecretTypeOpaque) - - // Check the hcp client secret secret is as expected. - ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPObservabilityClientSecret, secretKeyHCPClientSecret, - expectedConfig.HCPConfig.ObservabilityClientSecret, corev1.SecretTypeOpaque) - // Check the hcp auth URL secret is as expected. ensureSecretKeyValueMatchesExpected(t, k8s, secretNameHCPAuthURL, secretKeyHCPAuthURL, expectedConfig.HCPConfig.AuthURL, corev1.SecretTypeOpaque) @@ -841,8 +677,6 @@ func checkSecretsWereNotSaved(k8s kubernetes.Interface) bool { ns, _ := k8s.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) hcpClientIdSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPClientID, metav1.GetOptions{}) hcpClientSecretSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPClientSecret, metav1.GetOptions{}) - hcpObservabilityClientIdSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPObservabilityClientID, metav1.GetOptions{}) - hcpObservabilityClientSecretSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPObservabilityClientSecret, metav1.GetOptions{}) hcpResourceIdSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameHCPResourceID, metav1.GetOptions{}) bootstrapSecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameBootstrapToken, metav1.GetOptions{}) gossipKeySecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameGossipKey, metav1.GetOptions{}) @@ -850,7 +684,7 @@ func checkSecretsWereNotSaved(k8s kubernetes.Interface) bool { serverCASecret, _ := k8s.CoreV1().Secrets(namespace).Get(context.Background(), secretNameServerCA, metav1.GetOptions{}) return ns == nil && hcpClientIdSecret == nil && hcpClientSecretSecret == nil && hcpResourceIdSecret == nil && bootstrapSecret == nil && - gossipKeySecret == nil && serverCASecret == nil && serverCertSecret == nil && hcpObservabilityClientIdSecret == nil && hcpObservabilityClientSecretSecret == nil + gossipKeySecret == nil && serverCASecret == nil && serverCertSecret == nil } func getDeepCopyOfValidBootstrapConfig() *CloudBootstrapConfig { diff --git a/cli/preset/demo.go b/cli/preset/demo.go index 5e011904fa..bf6c0bb122 100644 --- a/cli/preset/demo.go +++ b/cli/preset/demo.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package preset import "github.com/hashicorp/consul-k8s/cli/config" @@ -32,6 +29,8 @@ connectInject: enableGatewayMetrics: true server: replicas: 1 +controller: + enabled: true ui: enabled: true service: diff --git a/cli/preset/preset.go b/cli/preset/preset.go index 1b53dd021f..2eb2c94bc4 100644 --- a/cli/preset/preset.go +++ b/cli/preset/preset.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package preset import ( diff --git a/cli/preset/preset_test.go b/cli/preset/preset_test.go index 53b95b56e6..c39c11e80f 100644 --- a/cli/preset/preset_test.go +++ b/cli/preset/preset_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package preset import ( diff --git a/cli/preset/quickstart.go b/cli/preset/quickstart.go index 1e16a1e9c3..52b3f000b1 100644 --- a/cli/preset/quickstart.go +++ b/cli/preset/quickstart.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package preset import "github.com/hashicorp/consul-k8s/cli/config" @@ -32,6 +29,8 @@ connectInject: enableGatewayMetrics: true server: replicas: 1 +controller: + enabled: true ui: enabled: true service: diff --git a/cli/preset/secure.go b/cli/preset/secure.go index 62727273be..ded436804c 100644 --- a/cli/preset/secure.go +++ b/cli/preset/secure.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package preset import "github.com/hashicorp/consul-k8s/cli/config" @@ -32,6 +29,8 @@ server: replicas: 1 connectInject: enabled: true +controller: + enabled: true ` return config.ConvertToMap(values), nil diff --git a/cli/release/release.go b/cli/release/release.go index 9b973f080f..e1590f9057 100644 --- a/cli/release/release.go +++ b/cli/release/release.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package release import ( diff --git a/cli/release/release_test.go b/cli/release/release_test.go index f65c67ac05..a6f1c28994 100644 --- a/cli/release/release_test.go +++ b/cli/release/release_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package release import ( diff --git a/cli/validation/kubernetes.go b/cli/validation/kubernetes.go index 8a7d77ed5b..99dc8af6f7 100644 --- a/cli/validation/kubernetes.go +++ b/cli/validation/kubernetes.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package validation import ( diff --git a/cli/validation/kubernetes_test.go b/cli/validation/kubernetes_test.go index 90a5c6c298..60d7ec243b 100644 --- a/cli/validation/kubernetes_test.go +++ b/cli/validation/kubernetes_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package validation import ( diff --git a/cli/version/fips_build.go b/cli/version/fips_build.go deleted file mode 100644 index 63e0e68883..0000000000 --- a/cli/version/fips_build.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build fips - -package version - -// This validates during compilation that we are being built with a FIPS enabled go toolchain -import ( - _ "crypto/tls/fipsonly" - "runtime" - "strings" -) - -// IsFIPS returns true if consul-k8s is operating in FIPS-140-2 mode. -func IsFIPS() bool { - return true -} - -func GetFIPSInfo() string { - str := "Enabled" - // Try to get the crypto module name - gover := strings.Split(runtime.Version(), "X:") - if len(gover) >= 2 { - gover_last := gover[len(gover)-1] - // Able to find crypto module name; add that to status string. - str = "FIPS 140-2 Enabled, crypto module " + gover_last - } - return str -} diff --git a/cli/version/non_fips_build.go b/cli/version/non_fips_build.go deleted file mode 100644 index ce99575d2c..0000000000 --- a/cli/version/non_fips_build.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build !fips - -package version - -// IsFIPS returns true if consul-k8s is operating in FIPS-140-2 mode. -func IsFIPS() bool { - return false -} - -func GetFIPSInfo() string { - return "" -} diff --git a/cli/version/version.go b/cli/version/version.go index f68d1632a6..a7f5aa5cd0 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package version import ( @@ -17,7 +14,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.5.0" + Version = "1.0.12" // 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 @@ -39,12 +36,8 @@ func GetHumanVersion() string { release = "dev" } - if IsFIPS() { - version += "+fips1402" - } - if release != "" { - if !strings.Contains(version, "-"+release) { + if !strings.HasSuffix(version, "-"+release) { // if we tagged a prerelease version then the release is in the version already version += fmt.Sprintf("-%s", release) } diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile index dda0ab6a96..773db0b0e9 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # This Dockerfile contains multiple targets. # Use 'docker build --target= .' to build one. # @@ -16,8 +13,7 @@ # go-discover builds the discover binary (which we don't currently publish # either). -ARG GOLANG_VERSION -FROM golang:${GOLANG_VERSION}-alpine as go-discover +FROM golang:1.19.9-alpine as go-discover RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@214571b6a5309addf3db7775f4ee8cf4d264fd5f # dev copies the binary from a local build @@ -93,11 +89,7 @@ LABEL name=${BIN_NAME} \ ENV BIN_NAME=${BIN_NAME} ENV VERSION=${PRODUCT_VERSION} -RUN apk add --no-cache ca-certificates libcap openssl su-exec iputils gcompat libc6-compat libstdc++ iptables - -# for FIPS CGO glibc compatibility in alpine -# see https://github.com/golang/go/issues/59305 -RUN ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 +RUN apk add --no-cache ca-certificates libcap openssl su-exec iputils libc6-compat iptables # TARGETOS and TARGETARCH are set automatically when --platform is provided. ARG TARGETOS @@ -114,9 +106,6 @@ COPY dist/cni/${TARGETOS}/${TARGETARCH}/${CNI_BIN_NAME} /bin/ USER 100 CMD /bin/${BIN_NAME} -# Duplicate target for FIPS builds -FROM release-default AS release-default-fips - # ----------------------------------- # Dockerfile target for consul-k8s with UBI as its base image. Used for running on # OpenShift. @@ -128,7 +117,7 @@ FROM release-default AS release-default-fips # We don't rebuild the software because we want the exact checksums and # binary signatures to match the software and our builds aren't fully # reproducible currently. -FROM registry.access.redhat.com/ubi9-minimal:9.3 as ubi +FROM registry.access.redhat.com/ubi9-minimal:9.2 as ubi ARG PRODUCT_NAME ARG PRODUCT_VERSION @@ -179,8 +168,6 @@ COPY dist/cni/${TARGETOS}/${TARGETARCH}/${CNI_BIN_NAME} /bin/ USER 100 CMD /bin/${BIN_NAME} -# Duplicate target for FIPS builds -FROM ubi AS ubi-fips # =================================== # # Set default target to 'dev'. diff --git a/control-plane/Dockerfile.dev b/control-plane/Dockerfile.dev deleted file mode 100644 index 5da7e2a236..0000000000 --- a/control-plane/Dockerfile.dev +++ /dev/null @@ -1,11 +0,0 @@ -# DANGER: this dockerfile is experimental and could be modified/removed at any time. -# A simple image for testing changes to consul-k8s -# -# Meant to be used with the following make target -# DEV_IMAGE= make control-plane-dev-skaffold - -FROM hashicorp/consul-k8s-control-plane as cache -ARG TARGETARCH - -COPY pkg/bin/linux_${TARGETARCH}/consul-k8s-control-plane /bin -COPY cni/pkg/bin/linux_${TARGETARCH}/consul-cni /bin diff --git a/control-plane/PROJECT b/control-plane/PROJECT index 7146070824..c11e857849 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -1,7 +1,3 @@ -# Code generated by tool. DO NOT EDIT. -# This file is used to track the info used to scaffold your project -# and allow the plugins properly work. -# More info: https://book.kubebuilder.io/reference/project-config.html domain: hashicorp.com layout: - go.kubebuilder.io/v2 @@ -81,74 +77,4 @@ resources: kind: PeeringDialer path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: hashicorp.com - group: consul - kind: SamenessGroup - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: hashicorp.com - group: consul - kind: JWTProvider - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: hashicorp.com - group: consul - kind: ControlPlaneRequestLimit - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: hashicorp.com - group: consul - kind: RouteRetryFilter - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: hashicorp.com - group: consul - kind: RouteTimeoutFilter - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - domain: hashicorp.com - group: consul - kind: RouteAuthFilter - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - domain: hashicorp.com - group: consul - kind: GatewayPolicy - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: consul.hashicorp.com - group: auth - kind: TrafficPermissions - path: github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1 - version: v2beta1 version: "3" diff --git a/control-plane/api-gateway/binding/annotations.go b/control-plane/api-gateway/binding/annotations.go deleted file mode 100644 index 2bd4d0db15..0000000000 --- a/control-plane/api-gateway/binding/annotations.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "encoding/json" - - 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" -) - -func serializeGatewayClassConfig(gw *gwv1beta1.Gateway, gwcc *v1alpha1.GatewayClassConfig) (*v1alpha1.GatewayClassConfig, bool) { - if gwcc == nil { - return nil, false - } - - if gw.Annotations == nil { - gw.Annotations = make(map[string]string) - } - - if annotatedConfig, ok := gw.Annotations[common.AnnotationGatewayClassConfig]; ok { - var config v1alpha1.GatewayClassConfig - if err := json.Unmarshal([]byte(annotatedConfig), &config.Spec); err == nil { - // if we can unmarshal the gateway, return it - return &config, false - } - } - - // otherwise if we failed to unmarshal or there was no annotation, marshal it onto - // the gateway - marshaled, _ := json.Marshal(gwcc.Spec) - gw.Annotations[common.AnnotationGatewayClassConfig] = string(marshaled) - return gwcc, true -} diff --git a/control-plane/api-gateway/binding/annotations_test.go b/control-plane/api-gateway/binding/annotations_test.go deleted file mode 100644 index edb44ccfb4..0000000000 --- a/control-plane/api-gateway/binding/annotations_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "encoding/json" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - 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" -) - -func TestSerializeGatewayClassConfig_HappyPath(t *testing.T) { - t.Parallel() - - type args struct { - gw *gwv1beta1.Gateway - gwcc *v1alpha1.GatewayClassConfig - } - tests := []struct { - name string - args args - expectedDidUpdate bool - }{ - { - name: "when gateway has not been annotated yet and annotations are nil", - args: args{ - gw: &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gw", - }, - Spec: gwv1beta1.GatewaySpec{}, - Status: gwv1beta1.GatewayStatus{}, - }, - gwcc: &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "the config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: common.PointerTo(corev1.ServiceType("serviceType")), - NodeSelector: map[string]string{ - "selector": "of node", - }, - Tolerations: []v1.Toleration{ - { - Key: "key", - Operator: "op", - Value: "120", - Effect: "to the moon", - TolerationSeconds: new(int64), - }, - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ - Service: []string{"service"}, - }, - }, - }, - }, - expectedDidUpdate: true, - }, - { - name: "when gateway has not been annotated yet but annotations are empty", - args: args{ - gw: &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gw", - Annotations: make(map[string]string), - }, - Spec: gwv1beta1.GatewaySpec{}, - Status: gwv1beta1.GatewayStatus{}, - }, - gwcc: &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "the config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: common.PointerTo(corev1.ServiceType("serviceType")), - NodeSelector: map[string]string{ - "selector": "of node", - }, - Tolerations: []v1.Toleration{ - { - Key: "key", - Operator: "op", - Value: "120", - Effect: "to the moon", - TolerationSeconds: new(int64), - }, - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ - Service: []string{"service"}, - }, - }, - }, - }, - expectedDidUpdate: true, - }, - { - name: "when gateway has been annotated", - args: args{ - gw: &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gw", - Annotations: map[string]string{ - common.AnnotationGatewayClassConfig: `{"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`, - }, - }, - Spec: gwv1beta1.GatewaySpec{}, - Status: gwv1beta1.GatewayStatus{}, - }, - gwcc: &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "the config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: common.PointerTo(corev1.ServiceType("serviceType")), - NodeSelector: map[string]string{ - "selector": "of node", - }, - Tolerations: []v1.Toleration{ - { - Key: "key", - Operator: "op", - Value: "120", - Effect: "to the moon", - TolerationSeconds: new(int64), - }, - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ - Service: []string{"service"}, - }, - }, - }, - }, - expectedDidUpdate: false, - }, - { - name: "when gateway has been annotated but the serialization was invalid", - args: args{ - gw: &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gw", - Annotations: map[string]string{ - // we remove the opening brace to make unmarshalling fail - common.AnnotationGatewayClassConfig: `"serviceType":"serviceType","nodeSelector":{"selector":"of node"},"tolerations":[{"key":"key","operator":"op","value":"120","effect":"to the moon","tolerationSeconds":0}],"copyAnnotations":{"service":["service"]}}`, - }, - }, - Spec: gwv1beta1.GatewaySpec{}, - Status: gwv1beta1.GatewayStatus{}, - }, - gwcc: &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "the config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: common.PointerTo(corev1.ServiceType("serviceType")), - NodeSelector: map[string]string{ - "selector": "of node", - }, - Tolerations: []v1.Toleration{ - { - Key: "key", - Operator: "op", - Value: "120", - Effect: "to the moon", - TolerationSeconds: new(int64), - }, - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ - Service: []string{"service"}, - }, - }, - }, - }, - expectedDidUpdate: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, actualDidUpdate := serializeGatewayClassConfig(tt.args.gw, tt.args.gwcc) - - if actualDidUpdate != tt.expectedDidUpdate { - t.Errorf("SerializeGatewayClassConfig() = %v, want %v", actualDidUpdate, tt.expectedDidUpdate) - } - - var config v1alpha1.GatewayClassConfig - err := json.Unmarshal([]byte(tt.args.gw.Annotations[common.AnnotationGatewayClassConfig]), &config.Spec) - require.NoError(t, err) - - if diff := cmp.Diff(config.Spec, tt.args.gwcc.Spec); diff != "" { - t.Errorf("Expected gwconfig spec to match serialized version (-want,+got):\n%s", diff) - } - }) - } -} diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go deleted file mode 100644 index c6557985b5..0000000000 --- a/control-plane/api-gateway/binding/binder.go +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - mapset "github.com/deckarep/golang-set" - "github.com/go-logr/logr" - "github.com/hashicorp/consul/api" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - 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" -) - -// BinderConfig configures a binder instance with all of the information -// that it needs to know to generate a snapshot of bound state. -type BinderConfig struct { - // Logger for any internal logs - Logger logr.Logger - // Translator instance initialized with proper name/namespace translation - // configuration from helm. - Translator common.ResourceTranslator - // ControllerName is the name of the controller used in determining which - // gateways we control, also leveraged for setting route statuses. - ControllerName string - - // Namespaces is a map of all namespaces in Kubernetes indexed by their names for looking up labels - // for AllowedRoutes matching purposes. - Namespaces map[string]corev1.Namespace - // GatewayClassConfig is the configuration corresponding to the given - // GatewayClass -- if it is nil we should treat the gateway as deleted - // since the gateway is now pointing to an invalid gateway class - GatewayClassConfig *v1alpha1.GatewayClassConfig - // GatewayClass is the GatewayClass corresponding to the Gateway we want to - // bind routes to. It is passed as a pointer because it could be nil. If no - // GatewayClass corresponds to a Gateway, we ought to clean up any sort of - // state that we may have set on the Gateway, its corresponding Routes or in - // Consul, because we should no longer be managing the Gateway (its association - // to our controller is through a parameter on the GatewayClass). - GatewayClass *gwv1beta1.GatewayClass - // Gateway is the Gateway being reconciled that we want to bind routes to. - Gateway gwv1beta1.Gateway - // HTTPRoutes is a list of HTTPRoute objects that ought to be bound to the Gateway. - HTTPRoutes []gwv1beta1.HTTPRoute - // TCPRoutes is a list of TCPRoute objects that ought to be bound to the Gateway. - TCPRoutes []gwv1alpha2.TCPRoute - // Pods are any pods that are part of the Gateway deployment. - Pods []corev1.Pod - // Service is the deployed service associated with the Gateway deployment. - Service *corev1.Service - // JWTProviders is the list of all JWTProviders in the cluster - JWTProviders []v1alpha1.JWTProvider - - // ConsulGateway is the config entry we've created in Consul. - ConsulGateway *api.APIGatewayConfigEntry - // GatewayServices are the services associated with the Gateway - ConsulGatewayServices []api.CatalogService - - // Resources is a map containing all service targets to verify - // against the routing backends. - Resources *common.ResourceMap - - // Policies is a list containing all GatewayPolicies that are part of the Gateway Deployment - Policies []v1alpha1.GatewayPolicy -} - -// Binder is used for generating a Snapshot of all operations that should occur both -// in Kubernetes and Consul as a result of binding routes to a Gateway. -type Binder struct { - statusSetter *setter - key types.NamespacedName - nonNormalizedConsulKey api.ResourceReference - normalizedConsulKey api.ResourceReference - config BinderConfig -} - -// NewBinder creates a Binder object with the given configuration. -func NewBinder(config BinderConfig) *Binder { - id := client.ObjectKeyFromObject(&config.Gateway) - - return &Binder{ - config: config, - statusSetter: newSetter(config.ControllerName), - key: id, - nonNormalizedConsulKey: config.Translator.NonNormalizedConfigEntryReference(api.APIGateway, id), - normalizedConsulKey: config.Translator.ConfigEntryReference(api.APIGateway, id), - } -} - -// isGatewayDeleted returns whether we should treat the given gateway as a deleted object. -// This is true if the gateway has a deleted timestamp, if its GatewayClass does not match -// our controller name, or if the GatewayClass it references doesn't exist. -func (b *Binder) isGatewayDeleted() bool { - gatewayClassMismatch := b.config.GatewayClass == nil || b.config.ControllerName != string(b.config.GatewayClass.Spec.ControllerName) - isGatewayDeleted := isDeleted(&b.config.Gateway) || gatewayClassMismatch || b.config.GatewayClassConfig == nil - return isGatewayDeleted -} - -// Snapshot generates a snapshot of operations that need to occur in Kubernetes and Consul -// in order for a Gateway to be reconciled. -func (b *Binder) Snapshot() *Snapshot { - // at this point we assume all tcp routes and http routes - // actually reference this gateway - snapshot := NewSnapshot() - - registrationPods := []corev1.Pod{} - // filter out any pod that is being deleted - for _, pod := range b.config.Pods { - if !isDeleted(&pod) { - registrationPods = append(registrationPods, pod) - } - } - - gatewayClassConfig := b.config.GatewayClassConfig - - isGatewayDeleted := b.isGatewayDeleted() - - var gatewayValidation gatewayValidationResult - var listenerValidation listenerValidationResults - var policyValidation gatewayPolicyValidationResults - var authFilterValidation authFilterValidationResults - - authFilters := b.config.Resources.GetExternalAuthFilters() - if !isGatewayDeleted { - var updated bool - - gatewayClassConfig, updated = serializeGatewayClassConfig(&b.config.Gateway, gatewayClassConfig) - - // we don't have a deletion but if we add a finalizer for the gateway, then just add it and return - // otherwise try and resolve as much as possible - if common.EnsureFinalizer(&b.config.Gateway) || updated { - // if we've added the finalizer or serialized the class config, then update - snapshot.Kubernetes.Updates.Add(&b.config.Gateway) - return snapshot - } - - // calculate the status for the gateway - gatewayValidation = validateGateway(b.config.Gateway, registrationPods, b.config.ConsulGateway) - listenerValidation = validateListeners(b.config.Gateway, b.config.Gateway.Spec.Listeners, b.config.Resources, b.config.GatewayClassConfig) - policyValidation = validateGatewayPolicies(b.config.Gateway, b.config.Policies, b.config.Resources) - authFilterValidation = validateAuthFilters(authFilters, b.config.Resources) - } - - // used for tracking how many routes have successfully bound to which listeners - // on a gateway for reporting the number of bound routes in a gateway listener's - // status - boundCounts := make(map[gwv1beta1.SectionName]int) - - // attempt to bind all routes - - for _, r := range b.config.HTTPRoutes { - b.bindRoute(common.PointerTo(r), boundCounts, snapshot) - } - - for _, r := range b.config.TCPRoutes { - b.bindRoute(common.PointerTo(r), boundCounts, snapshot) - } - - // process secrets - gatewaySecrets := secretsForGateway(b.config.Gateway, b.config.Resources) - if !isGatewayDeleted { - // we only do this if the gateway isn't going to be deleted so that the - // resources can get GC'd - for secret := range gatewaySecrets.Iter() { - // ignore the error if the certificate cannot be processed and just don't add it into the final - // sync set - if err := b.config.Resources.TranslateInlineCertificate(secret.(types.NamespacedName)); err != nil { - b.config.Logger.Error(err, "error parsing referenced secret, ignoring") - continue - } - } - } - - // now cleanup any routes or certificates that we haven't already processed - - snapshot.Consul.Deletions = b.config.Resources.ResourcesToGC(b.key) - snapshot.Consul.Updates = b.config.Resources.Mutations() - - // finally, handle the gateway itself - - // we only want to upsert the gateway into Consul or update its status - // if the gateway hasn't been marked for deletion - if !isGatewayDeleted { - snapshot.GatewayClassConfig = gatewayClassConfig - snapshot.UpsertGatewayDeployment = true - - var consulStatus api.ConfigEntryStatus - if b.config.ConsulGateway != nil { - consulStatus = b.config.ConsulGateway.Status - } - entry := b.config.Translator.ToAPIGateway(b.config.Gateway, b.config.Resources, gatewayClassConfig) - snapshot.Consul.Updates = append(snapshot.Consul.Updates, &common.ConsulUpdateOperation{ - Entry: entry, - OnUpdate: b.handleGatewaySyncStatus(snapshot, &b.config.Gateway, consulStatus), - }) - - registrations := registrationsForPods(entry.Namespace, b.config.Gateway, registrationPods) - snapshot.Consul.Registrations = registrations - - // deregister any not explicitly registered service - for _, service := range b.config.ConsulGatewayServices { - found := false - for _, registration := range registrations { - if service.ServiceID == registration.Service.ID { - found = true - break - } - } - if !found { - // we didn't register the service instance, so drop it - snapshot.Consul.Deregistrations = append(snapshot.Consul.Deregistrations, api.CatalogDeregistration{ - Node: service.Node, - ServiceID: service.ServiceID, - Namespace: service.Namespace, - }) - } - } - - // calculate the status for the gateway - var status gwv1beta1.GatewayStatus - for i, listener := range b.config.Gateway.Spec.Listeners { - status.Listeners = append(status.Listeners, gwv1beta1.ListenerStatus{ - Name: listener.Name, - SupportedKinds: supportedKinds(listener), - AttachedRoutes: int32(boundCounts[listener.Name]), - Conditions: listenerValidation.Conditions(b.config.Gateway.Generation, i), - }) - } - status.Conditions = b.config.Gateway.Status.Conditions - - // we do this loop to not accidentally override any additional statuses that - // have been set anywhere outside of validation. - for _, condition := range gatewayValidation.Conditions(b.config.Gateway.Generation, listenerValidation.Invalid()) { - status.Conditions, _ = setCondition(status.Conditions, condition) - } - status.Addresses = addressesForGateway(b.config.Service, registrationPods) - - // only mark the gateway as needing a status update if there's a diff with its old - // status, this keeps the controller from infinitely reconciling - if !common.GatewayStatusesEqual(status, b.config.Gateway.Status) { - b.config.Gateway.Status = status - snapshot.Kubernetes.StatusUpdates.Add(&b.config.Gateway) - } - - for idx, policy := range b.config.Policies { - policy := policy - - var policyStatus v1alpha1.GatewayPolicyStatus - - policyStatus.Conditions = policyValidation.Conditions(policy.Generation, idx) - // only mark the policy as needing a status update if there's a diff with its old status - if !common.GatewayPolicyStatusesEqual(policyStatus, policy.Status) { - b.config.Policies[idx].Status = policyStatus - snapshot.Kubernetes.StatusUpdates.Add(&b.config.Policies[idx]) - } - } - - for idx, authFilter := range authFilters { - if authFilter == nil { - continue - } - authFilter := authFilter - - var filterStatus v1alpha1.RouteAuthFilterStatus - - filterStatus.Conditions = authFilterValidation.Conditions(authFilter.Generation, idx) - - // only mark the filter as needing a status update if there's a diff with its old status - if !common.RouteAuthFilterStatusesEqual(filterStatus, authFilter.Status) { - authFilter.Status = filterStatus - snapshot.Kubernetes.StatusUpdates.Add(authFilter) - } - } - } else { - // if the gateway has been deleted, unset whatever we've set on it - snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, b.nonNormalizedConsulKey) - for _, service := range b.config.ConsulGatewayServices { - // deregister all gateways - snapshot.Consul.Deregistrations = append(snapshot.Consul.Deregistrations, api.CatalogDeregistration{ - Node: service.Node, - ServiceID: service.ServiceID, - Namespace: service.Namespace, - }) - } - - if common.RemoveFinalizer(&b.config.Gateway) { - snapshot.Kubernetes.Updates.Add(&b.config.Gateway) - for _, policy := range b.config.Policies { - policy := policy - policy.Status = v1alpha1.GatewayPolicyStatus{} - snapshot.Kubernetes.StatusUpdates.Add(&policy) - } - } - } - - return snapshot -} - -func secretsForGateway(gateway gwv1beta1.Gateway, resources *common.ResourceMap) mapset.Set { - set := mapset.NewSet() - - for _, listener := range gateway.Spec.Listeners { - if listener.TLS == nil { - continue - } - - for _, cert := range listener.TLS.CertificateRefs { - if resources.GatewayCanReferenceSecret(gateway, cert) { - if common.NilOrEqual(cert.Group, "") && common.NilOrEqual(cert.Kind, common.KindSecret) { - key := common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace) - set.Add(key) - } - } - } - } - - return set -} - -func addressesForGateway(service *corev1.Service, pods []corev1.Pod) []gwv1beta1.GatewayAddress { - if service == nil { - return addressesFromPods(pods) - } - - switch service.Spec.Type { - case corev1.ServiceTypeLoadBalancer: - return addressesFromLoadBalancer(service) - case corev1.ServiceTypeClusterIP: - return addressesFromClusterIP(service) - case corev1.ServiceTypeNodePort: - /* For serviceType: NodePort, there isn't a consistent way to guarantee access to the - * service from outside the k8s cluster. For now, we're putting the IP address of the - * nodes that the gateway pods are running on. - * The practitioner will have to understand that they may need to port forward into the - * cluster (in the case of Kind) or open firewall rules (in the case of GKE) in order to - * access the gateway from outside the cluster. - */ - return addressesFromPodHosts(pods) - } - - return []gwv1beta1.GatewayAddress{} -} - -func addressesFromLoadBalancer(service *corev1.Service) []gwv1beta1.GatewayAddress { - addresses := []gwv1beta1.GatewayAddress{} - - for _, ingress := range service.Status.LoadBalancer.Ingress { - if ingress.IP != "" { - addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: common.PointerTo(gwv1beta1.IPAddressType), - Value: ingress.IP, - }) - } - if ingress.Hostname != "" { - addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: common.PointerTo(gwv1beta1.HostnameAddressType), - Value: ingress.Hostname, - }) - } - } - - return addresses -} - -func addressesFromClusterIP(service *corev1.Service) []gwv1beta1.GatewayAddress { - addresses := []gwv1beta1.GatewayAddress{} - - if service.Spec.ClusterIP != "" { - addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: common.PointerTo(gwv1beta1.IPAddressType), - Value: service.Spec.ClusterIP, - }) - } - - return addresses -} - -func addressesFromPods(pods []corev1.Pod) []gwv1beta1.GatewayAddress { - addresses := []gwv1beta1.GatewayAddress{} - seenIPs := make(map[string]struct{}) - - for _, pod := range pods { - if pod.Status.PodIP != "" { - if _, found := seenIPs[pod.Status.PodIP]; !found { - addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: common.PointerTo(gwv1beta1.IPAddressType), - Value: pod.Status.PodIP, - }) - seenIPs[pod.Status.PodIP] = struct{}{} - } - } - } - - return addresses -} - -func addressesFromPodHosts(pods []corev1.Pod) []gwv1beta1.GatewayAddress { - addresses := []gwv1beta1.GatewayAddress{} - seenIPs := make(map[string]struct{}) - - for _, pod := range pods { - if pod.Status.HostIP != "" { - if _, found := seenIPs[pod.Status.HostIP]; !found { - addresses = append(addresses, gwv1beta1.GatewayAddress{ - Type: common.PointerTo(gwv1beta1.IPAddressType), - Value: pod.Status.HostIP, - }) - seenIPs[pod.Status.HostIP] = struct{}{} - } - } - } - - return addresses -} - -// isDeleted checks if the deletion timestamp is set for an object. -func isDeleted(object client.Object) bool { - return !object.GetDeletionTimestamp().IsZero() -} - -func supportedKinds(listener gwv1beta1.Listener) []gwv1beta1.RouteGroupKind { - if listener.AllowedRoutes != nil && listener.AllowedRoutes.Kinds != nil { - return common.Filter(listener.AllowedRoutes.Kinds, func(kind gwv1beta1.RouteGroupKind) bool { - if _, ok := allSupportedRouteKinds[kind.Kind]; !ok { - return true - } - return !common.NilOrEqual(kind.Group, gwv1beta1.GroupVersion.Group) - }) - } - return supportedKindsForProtocol[listener.Protocol] -} diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go deleted file mode 100644 index b4a274c8fa..0000000000 --- a/control-plane/api-gateway/binding/binder_test.go +++ /dev/null @@ -1,3187 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" - "testing" - "time" - - logrtest "github.com/go-logr/logr/testing" - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -func init() { - timeFunc = func() metav1.Time { - return metav1.Time{} - } -} - -const ( - testGatewayClassName = "gateway-class" - testControllerName = "test-controller" -) - -var ( - testGatewayClassObjectName = gwv1beta1.ObjectName(testGatewayClassName) - deletionTimestamp = common.PointerTo(metav1.Now()) - - testGatewayClass = &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: testGatewayClassName, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: gwv1beta1.GatewayController(testControllerName), - }, - } -) - -type resourceMapResources struct { - grants []gwv1beta1.ReferenceGrant - secrets []corev1.Secret - gateways []gwv1beta1.Gateway - httpRoutes []gwv1beta1.HTTPRoute - tcpRoutes []gwv1alpha2.TCPRoute - meshServices []v1alpha1.MeshService - services []types.NamespacedName - jwtProviders []*v1alpha1.JWTProvider - gatewayPolicies []*v1alpha1.GatewayPolicy - externalAuthFilters []*v1alpha1.RouteAuthFilter - consulInlineCertificates []api.InlineCertificateConfigEntry - consulHTTPRoutes []api.HTTPRouteConfigEntry - consulTCPRoutes []api.TCPRouteConfigEntry -} - -func newTestResourceMap(t *testing.T, resources resourceMapResources) *common.ResourceMap { - resourceMap := common.NewResourceMap(common.ResourceTranslator{}, NewReferenceValidator(resources.grants), logrtest.NewTestLogger(t)) - - for _, s := range resources.services { - resourceMap.AddService(s, s.Name) - } - for _, s := range resources.meshServices { - resourceMap.AddMeshService(s) - } - for _, s := range resources.secrets { - resourceMap.ReferenceCountCertificate(s) - } - for _, g := range resources.gateways { - resourceMap.ReferenceCountGateway(g) - } - for _, r := range resources.httpRoutes { - resourceMap.ReferenceCountHTTPRoute(r) - } - for _, r := range resources.tcpRoutes { - resourceMap.ReferenceCountTCPRoute(r) - } - for _, r := range resources.consulHTTPRoutes { - resourceMap.ReferenceCountConsulHTTPRoute(r) - } - for _, r := range resources.consulTCPRoutes { - resourceMap.ReferenceCountConsulTCPRoute(r) - } - for _, r := range resources.gatewayPolicies { - resourceMap.AddGatewayPolicy(r) - } - for _, r := range resources.jwtProviders { - resourceMap.AddJWTProvider(r) - } - - for _, r := range resources.externalAuthFilters { - resourceMap.AddExternalFilter(r) - } - - return resourceMap -} - -func TestBinder_Lifecycle(t *testing.T) { - t.Parallel() - - certificateOne, secretOne := generateTestCertificate(t, "default", "secret-one") - certificateTwo, secretTwo := generateTestCertificate(t, "default", "secret-two") - - for name, tt := range map[string]struct { - resources resourceMapResources - config BinderConfig - expectedStatusUpdates []client.Object - expectedUpdates []client.Object - expectedConsulDeletions []api.ResourceReference - expectedConsulUpdates []api.ConfigEntry - }{ - "no gateway class and empty routes": { - config: BinderConfig{ - Gateway: gwv1beta1.Gateway{}, - }, - expectedConsulDeletions: []api.ResourceReference{{ - Kind: api.APIGateway, - }}, - }, - "no gateway class and empty routes remove finalizer": { - config: BinderConfig{ - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{common.GatewayFinalizer}, - }, - }, - }, - expectedUpdates: []client.Object{ - addClassConfig(gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{}}}), - }, - expectedConsulDeletions: []api.ResourceReference{ - {Kind: api.APIGateway}, - }, - }, - "deleting gateway empty routes": { - config: BinderConfig{ - ControllerName: testControllerName, - GatewayClass: testGatewayClass, - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassObjectName, - }, - }, - }, - expectedUpdates: []client.Object{ - addClassConfig(gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: deletionTimestamp, Finalizers: []string{}}, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassObjectName, - }, - }), - }, - expectedConsulDeletions: []api.ResourceReference{ - {Kind: api.APIGateway}, - }, - }, - "basic gateway no finalizer": { - config: BinderConfig{ - ControllerName: testControllerName, - GatewayClass: testGatewayClass, - Gateway: gwv1beta1.Gateway{ - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassObjectName, - }, - }, - }, - expectedUpdates: []client.Object{ - addClassConfig(gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{Finalizers: []string{common.GatewayFinalizer}}, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassObjectName, - }, - }), - }, - }, - "basic gateway": { - config: controlledBinder(BinderConfig{ - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Protocol: gwv1beta1.HTTPSProtocolType, - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "secret-one"}, - }, - Mode: common.PointerTo(gwv1beta1.TLSModeTerminate), - }, - }}, - }), - }), - resources: resourceMapResources{ - secrets: []corev1.Secret{ - secretOne, - }, - }, - expectedStatusUpdates: []client.Object{ - addClassConfig(gatewayWithFinalizerStatus( - gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Protocol: gwv1beta1.HTTPSProtocolType, - TLS: &gwv1beta1.GatewayTLSConfig{ - Mode: common.PointerTo(gwv1beta1.TLSModeTerminate), - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "secret-one"}, - }, - }, - }}, - }, - gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }, - }, - Listeners: []gwv1beta1.ListenerStatus{{ - SupportedKinds: supportedKindsForProtocol[gwv1beta1.HTTPSProtocolType], - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "listener accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionTrue, - Reason: "Programmed", - Message: "listener programmed", - }, { - Type: "Conflicted", - Status: metav1.ConditionFalse, - Reason: "NoConflicts", - Message: "listener has no conflicts", - }, { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }}, - }), - ), - }, - expectedConsulUpdates: []api.ConfigEntry{ - certificateOne, - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "default", - }, - Listeners: []api.APIGatewayListener{{ - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{{ - Kind: api.InlineCertificate, - Name: "secret-one", - }}, - }, - }}, - }, - }, - }, - "gateway http route no finalizer": { - config: controlledBinder(BinderConfig{ - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - HTTPRoutes: []gwv1beta1.HTTPRoute{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - }, - }), - expectedUpdates: []client.Object{ - common.PointerTo(testHTTPRoute("route", []string{"gateway"}, nil)), - }, - expectedStatusUpdates: []client.Object{ - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{}, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - })), - }, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "default", - }, - Listeners: []api.APIGatewayListener{}, - }, - }, - }, - "gateway http route deleting": { - config: controlledBinder(BinderConfig{ - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - HTTPRoutes: []gwv1beta1.HTTPRoute{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }}, - }), - resources: resourceMapResources{ - consulHTTPRoutes: []api.HTTPRouteConfigEntry{{ - Kind: api.HTTPRoute, - Name: "route", - Parents: []api.ResourceReference{ - {Kind: api.APIGateway, Name: "gateway"}, - }, - }}, - }, - expectedUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - }, - expectedStatusUpdates: []client.Object{ - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{}, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - })), - }, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "default", - }, - Listeners: []api.APIGatewayListener{}, - }, - }, - expectedConsulDeletions: []api.ResourceReference{ - {Kind: api.HTTPRoute, Name: "route"}, - }, - }, - "gateway tcp route no finalizer": { - config: controlledBinder(BinderConfig{ - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - TCPRoutes: []gwv1alpha2.TCPRoute{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "TCPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - }, - }), - expectedUpdates: []client.Object{ - common.PointerTo(testTCPRoute("route", []string{"gateway"}, nil)), - }, - expectedStatusUpdates: []client.Object{ - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{}, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - })), - }, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "default", - }, - Listeners: []api.APIGatewayListener{}, - }, - }, - }, - "gateway tcp route deleting": { - config: controlledBinder(BinderConfig{ - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - TCPRoutes: []gwv1alpha2.TCPRoute{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }}, - }), - resources: resourceMapResources{ - consulTCPRoutes: []api.TCPRouteConfigEntry{{ - Kind: api.TCPRoute, - Name: "route", - Parents: []api.ResourceReference{ - {Kind: api.APIGateway, Name: "gateway"}, - }, - }}, - }, - expectedUpdates: []client.Object{ - &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "route", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{{ - Name: "gateway", - }}, - }, - }, - }, - }, - expectedStatusUpdates: []client.Object{ - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{}, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - })), - }, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "gateway", - Meta: map[string]string{ - "k8s-name": "gateway", - "k8s-namespace": "default", - }, - Listeners: []api.APIGatewayListener{}, - }, - }, - expectedConsulDeletions: []api.ResourceReference{ - {Kind: api.TCPRoute, Name: "route"}, - }, - }, - "gateway deletion routes and secrets": { - config: controlledBinder(BinderConfig{ - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-deleted", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassName, - Listeners: []gwv1beta1.Listener{{ - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "secret-one"}, - {Name: "secret-two"}, - }, - }, - }}, - }, - }, - HTTPRoutes: []gwv1beta1.HTTPRoute{ - testHTTPRoute("http-route-one", []string{"gateway-deleted"}, nil), - testHTTPRouteStatus("http-route-two", nil, []gwv1alpha2.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway-deleted"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - }, - }}, - {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - }, - }}, - }), - }, - TCPRoutes: []gwv1alpha2.TCPRoute{ - testTCPRoute("tcp-route-one", []string{"gateway-deleted"}, nil), - testTCPRouteStatus("tcp-route-two", nil, []gwv1alpha2.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway-deleted"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - }, - }}, - {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - }, - }}, - }), - }, - }), - resources: resourceMapResources{ - consulHTTPRoutes: []api.HTTPRouteConfigEntry{ - { - Kind: api.HTTPRoute, Name: "http-route-two", Meta: map[string]string{ - "k8s-name": "http-route-two", - "k8s-namespace": "", - }, - Parents: []api.ResourceReference{ - {Kind: api.APIGateway, Name: "gateway-deleted"}, - {Kind: api.APIGateway, Name: "gateway"}, - }, - }, - { - Kind: api.HTTPRoute, Name: "http-route-one", Meta: map[string]string{ - "k8s-name": "http-route-one", - "k8s-namespace": "", - }, - Parents: []api.ResourceReference{ - {Kind: api.APIGateway, Name: "gateway-deleted"}, - }, - }, - }, - consulTCPRoutes: []api.TCPRouteConfigEntry{ - { - Kind: api.TCPRoute, Name: "tcp-route-two", - Meta: map[string]string{ - "k8s-name": "tcp-route-two", - "k8s-namespace": "", - }, - Parents: []api.ResourceReference{ - {Kind: api.APIGateway, Name: "gateway-deleted"}, - {Kind: api.APIGateway, Name: "gateway"}, - }, - }, - { - Kind: api.TCPRoute, Name: "tcp-route-one", - Meta: map[string]string{ - "k8s-name": "tcp-route-one", - "k8s-namespace": "", - }, - Parents: []api.ResourceReference{ - {Kind: api.APIGateway, Name: "gateway-deleted"}, - }, - }, - }, - consulInlineCertificates: []api.InlineCertificateConfigEntry{ - *certificateOne, - *certificateTwo, - }, - secrets: []corev1.Secret{ - secretOne, - secretTwo, - }, - gateways: []gwv1beta1.Gateway{ - gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "secret-one"}, - {Name: "secret-three"}, - }, - }, - }}, - }), - }, - }, - expectedStatusUpdates: []client.Object{ - common.PointerTo(testHTTPRouteStatus("http-route-two", nil, []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - }, - }}, - }, "gateway-deleted")), - common.PointerTo(testTCPRouteStatus("tcp-route-two", nil, []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, ControllerName: testControllerName, Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - }, - }}, - }, "gateway-deleted")), - }, - expectedUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route-one", - // removing a finalizer - Finalizers: []string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway-deleted"}, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{RouteStatus: gwv1beta1.RouteStatus{Parents: []gwv1alpha2.RouteParentStatus{}}}, - }, - &gwv1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "TCPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route-one", - Finalizers: []string{}, - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway-deleted"}, - }, - }, - }, - Status: gwv1alpha2.TCPRouteStatus{RouteStatus: gwv1beta1.RouteStatus{Parents: []gwv1alpha2.RouteParentStatus{}}}, - }, - addClassConfig(gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-deleted", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassName, - Listeners: []gwv1beta1.Listener{{ - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "secret-one"}, - {Name: "secret-two"}, - }, - }, - }}, - }, - }), - }, - expectedConsulUpdates: []api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "http-route-two", - Meta: map[string]string{ - "k8s-name": "http-route-two", - "k8s-namespace": "", - }, - // dropped ref to gateway - Parents: []api.ResourceReference{{ - Kind: api.APIGateway, - Name: "gateway", - }}, - }, - &api.TCPRouteConfigEntry{ - Kind: api.TCPRoute, - Name: "tcp-route-two", - Meta: map[string]string{ - "k8s-name": "tcp-route-two", - "k8s-namespace": "", - }, - // dropped ref to gateway - Parents: []api.ResourceReference{{ - Kind: api.APIGateway, - Name: "gateway", - }}, - }, - }, - expectedConsulDeletions: []api.ResourceReference{ - {Kind: api.HTTPRoute, Name: "http-route-one"}, - {Kind: api.TCPRoute, Name: "tcp-route-one"}, - {Kind: api.InlineCertificate, Name: "secret-two"}, - {Kind: api.APIGateway, Name: "gateway-deleted"}, - }, - }, - "gateway deletion policies": { - config: controlledBinder(BinderConfig{ - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-deleted", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassName, - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName("l1"), - }, - { - Name: gwv1beta1.SectionName("l2"), - }, - }, - }, - }, - Policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "p1", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: "Gateway", - Name: "gateway-deleted", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - Status: v1alpha1.GatewayPolicyStatus{ - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: 5, - Message: "gateway policy accepted", - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: 5, - Message: "resolved references", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "p2", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: "Gateway", - Name: "gateway-deleted", - SectionName: common.PointerTo(gwv1beta1.SectionName("l2")), - }, - }, - Status: v1alpha1.GatewayPolicyStatus{ - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: 5, - Message: "gateway policy accepted", - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: 5, - Message: "resolved references", - }, - }, - }, - }, - }, - }), - resources: resourceMapResources{ - gateways: []gwv1beta1.Gateway{ - gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - { - Name: "l2", - }, - }, - }), - }, - }, - expectedStatusUpdates: []client.Object{ - &v1alpha1.GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p1", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: "Gateway", - Name: "gateway-deleted", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - &v1alpha1.GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p2", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: "Gateway", - Name: "gateway-deleted", - SectionName: common.PointerTo(gwv1beta1.SectionName("l2")), - }, - }, - }, - }, - expectedUpdates: []client.Object{ - addClassConfig(gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-deleted", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassName, - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - { - Name: "l2", - }, - }, - }, - }), - }, - expectedConsulUpdates: []api.ConfigEntry{}, - expectedConsulDeletions: []api.ResourceReference{ - {Kind: api.APIGateway, Name: "gateway-deleted"}, - }, - }, - "gateway http route references missing external ref": { - resources: resourceMapResources{ - gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Name: "l1", - Protocol: "HTTP", - }}, - })}, - httpRoutes: []gwv1beta1.HTTPRoute{}, - jwtProviders: []*v1alpha1.JWTProvider{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - externalAuthFilters: []*v1alpha1.RouteAuthFilter{}, - }, - config: controlledBinder(BinderConfig{ - ConsulGateway: &api.APIGatewayConfigEntry{ - Name: "gateway", - Kind: "api-gateway", - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Protocol: "HTTP", - }, - }, - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - }, - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }), - HTTPRoutes: []gwv1beta1.HTTPRoute{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "RouteAuthFilter", - Name: "route-auth", - }, - }}, - }, - }, - }, - }, - testHTTPRoute("http-route-2", []string{"gateway"}, nil), - }, - }), - expectedStatusUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "RouteAuthFilter", - Name: "route-auth", - }, - }}, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - { - ParentRef: gwv1beta1.ParentReference{ - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Name: "gateway", - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - ControllerName: testControllerName, - Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, - { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "FilterNotFound", - Message: "ref not found", - }, - }, - }, - }, - }, - }, - }, - common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - Listeners: []gwv1beta1.ListenerStatus{ - { - Name: "l1", - SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "listener accepted", - }, - { - Type: "Programmed", - Status: "True", - Reason: "Programmed", - Message: "listener programmed", - }, - { - Type: "Conflicted", - Status: "False", - Reason: "NoConflicts", - Message: "listener has no conflicts", - }, - { - Type: "ResolvedRefs", - Status: "True", - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - }, - })), - }, - expectedUpdates: []client.Object{}, - expectedConsulDeletions: []api.ResourceReference{}, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: "api-gateway", - Name: "gateway", - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, - }, - }, - }, - "gateway http route route auth filter references missing jwt provider": { - resources: resourceMapResources{ - gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Name: "l1", - Protocol: "HTTP", - }}, - })}, - httpRoutes: []gwv1beta1.HTTPRoute{}, - jwtProviders: []*v1alpha1.JWTProvider{}, - externalAuthFilters: []*v1alpha1.RouteAuthFilter{ - { - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteAuthFilterKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "route-auth", - Namespace: "default", - }, - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - config: controlledBinder(BinderConfig{ - ConsulGateway: &api.APIGatewayConfigEntry{ - Name: "gateway", - Kind: "api-gateway", - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Protocol: "HTTP", - }, - }, - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - }, - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }), - HTTPRoutes: []gwv1beta1.HTTPRoute{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: v1alpha1.RouteAuthFilterKind, - Name: "route-auth", - }, - }}, - }, - }, - }, - }, - testHTTPRoute("http-route-2", []string{"gateway"}, nil), - }, - }), - expectedStatusUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "RouteAuthFilter", - Name: "route-auth", - }, - }}, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - { - ParentRef: gwv1beta1.ParentReference{ - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Name: "gateway", - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - ControllerName: testControllerName, - Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, - { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "JWTProviderNotFound", - Message: "filter invalid: default/route-auth", - }, - }, - }, - }, - }, - }, - }, - common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - Listeners: []gwv1beta1.ListenerStatus{ - { - Name: "l1", - SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "listener accepted", - }, - { - Type: "Programmed", - Status: "True", - Reason: "Programmed", - Message: "listener programmed", - }, - { - Type: "Conflicted", - Status: "False", - Reason: "NoConflicts", - Message: "listener has no conflicts", - }, - { - Type: "ResolvedRefs", - Status: "True", - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - }, - })), - &v1alpha1.RouteAuthFilter{ - TypeMeta: metav1.TypeMeta{Kind: "RouteAuthFilter"}, - ObjectMeta: metav1.ObjectMeta{Name: "route-auth", Namespace: "default"}, - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - Status: v1alpha1.RouteAuthFilterStatus{ - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: "False", - Reason: "ReferencesNotValid", - Message: "route filter is not accepted due to errors with references", - }, - { - Type: "ResolvedRefs", - Status: "False", - Reason: "MissingJWTProviderReference", - Message: "route filter references one or more jwt providers that do not exist: missingProviderNames: okta", - }, - }, - }, - }, - }, - expectedUpdates: []client.Object{}, - expectedConsulDeletions: []api.ResourceReference{}, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: "api-gateway", - Name: "gateway", - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, - }, - }, - }, - "gateway http route route references invalid external ref type": { - resources: resourceMapResources{ - gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Name: "l1", - Protocol: "HTTP", - }}, - })}, - }, - config: controlledBinder(BinderConfig{ - ConsulGateway: &api.APIGatewayConfigEntry{ - Name: "gateway", - Kind: "api-gateway", - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Protocol: "HTTP", - }, - }, - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - }, - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }), - HTTPRoutes: []gwv1beta1.HTTPRoute{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "OhNoThisIsInvalid", - Name: "route-auth", - }, - }}, - }, - }, - }, - }, - testHTTPRoute("http-route-2", []string{"gateway"}, nil), - }, - }), - expectedStatusUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "OhNoThisIsInvalid", - Name: "route-auth", - }, - }}, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - { - ParentRef: gwv1beta1.ParentReference{ - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Name: "gateway", - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - ControllerName: testControllerName, - Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, - { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "UnsupportedValue", - Message: "invalid externalref filter kind", - }, - }, - }, - }, - }, - }, - }, - common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - Listeners: []gwv1beta1.ListenerStatus{ - { - Name: "l1", - SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "listener accepted", - }, - { - Type: "Programmed", - Status: "True", - Reason: "Programmed", - Message: "listener programmed", - }, - { - Type: "Conflicted", - Status: "False", - Reason: "NoConflicts", - Message: "listener has no conflicts", - }, - { - Type: "ResolvedRefs", - Status: "True", - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - }, - })), - }, - expectedUpdates: []client.Object{}, - expectedConsulDeletions: []api.ResourceReference{}, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: "api-gateway", - Name: "gateway", - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) - tt.resources.httpRoutes = append(tt.resources.httpRoutes, tt.config.HTTPRoutes...) - tt.resources.tcpRoutes = append(tt.resources.tcpRoutes, tt.config.TCPRoutes...) - - tt.config.Resources = newTestResourceMap(t, tt.resources) - tt.config.ControllerName = testControllerName - tt.config.Logger = logrtest.NewTestLogger(t) - tt.config.GatewayClassConfig = &v1alpha1.GatewayClassConfig{} - serializeGatewayClassConfig(&tt.config.Gateway, tt.config.GatewayClassConfig) - - binder := NewBinder(tt.config) - actual := binder.Snapshot() - - actualConsulUpdates := common.ConvertSliceFunc(actual.Consul.Updates, func(op *common.ConsulUpdateOperation) api.ConfigEntry { - return op.Entry - }) - - require.ElementsMatch(t, tt.expectedConsulUpdates, actualConsulUpdates, "consul updates differ", cmp.Diff(tt.expectedConsulUpdates, actualConsulUpdates)) - require.ElementsMatch(t, tt.expectedConsulDeletions, actual.Consul.Deletions, "consul deletions differ") - require.ElementsMatch(t, tt.expectedStatusUpdates, actual.Kubernetes.StatusUpdates.Operations(), "kubernetes statuses differ", cmp.Diff(tt.expectedStatusUpdates, actual.Kubernetes.StatusUpdates.Operations())) - require.ElementsMatch(t, tt.expectedUpdates, actual.Kubernetes.Updates.Operations(), "kubernetes updates differ", cmp.Diff(tt.expectedUpdates, actual.Kubernetes.Updates.Operations())) - }) - } -} - -func TestBinder_Registrations(t *testing.T) { - t.Parallel() - - setDeleted := func(gateway gwv1beta1.Gateway) gwv1beta1.Gateway { - gateway.DeletionTimestamp = deletionTimestamp - return gateway - } - - for name, tt := range map[string]struct { - config BinderConfig - resources resourceMapResources - expectedRegistrations []string - expectedDeregistrations []api.CatalogDeregistration - }{ - "deleting gateway with consul services": { - config: controlledBinder(BinderConfig{ - Gateway: setDeleted(gatewayWithFinalizer(gwv1beta1.GatewaySpec{})), - ConsulGatewayServices: []api.CatalogService{ - {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, - {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, - {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, - }, - Pods: []corev1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Name: "pod1"}, - Status: corev1.PodStatus{ - Phase: corev1.PodRunning, - Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "pod2"}, - Status: corev1.PodStatus{ - Phase: corev1.PodRunning, - Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "pod3"}, - Status: corev1.PodStatus{ - Phase: corev1.PodRunning, - Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, - }, - }, - }, - }), - expectedDeregistrations: []api.CatalogDeregistration{ - {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, - {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, - {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, - }, - }, - "gateway with consul services and mixed pods": { - config: controlledBinder(BinderConfig{ - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - Pods: []corev1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "namespace1"}, - Status: corev1.PodStatus{ - Phase: corev1.PodRunning, - Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "pod3", Namespace: "namespace1"}, - Status: corev1.PodStatus{ - Phase: corev1.PodFailed, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "pod4", Namespace: "namespace1"}, - Status: corev1.PodStatus{ - Phase: corev1.PodRunning, - Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionTrue}}, - }, - }, - }, - ConsulGatewayServices: []api.CatalogService{ - {Node: "test", ServiceID: "pod1", Namespace: "namespace1"}, - {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, - {Node: "test", ServiceID: "pod3", Namespace: "namespace1"}, - }, - }), - expectedRegistrations: []string{"pod1", "pod3", "pod4"}, - expectedDeregistrations: []api.CatalogDeregistration{ - {Node: "test", ServiceID: "pod2", Namespace: "namespace1"}, - }, - }, - } { - t.Run(name, func(t *testing.T) { - tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) - tt.resources.httpRoutes = append(tt.resources.httpRoutes, tt.config.HTTPRoutes...) - tt.resources.tcpRoutes = append(tt.resources.tcpRoutes, tt.config.TCPRoutes...) - - tt.config.Resources = newTestResourceMap(t, tt.resources) - tt.config.ControllerName = testControllerName - tt.config.Logger = logrtest.NewTestLogger(t) - tt.config.GatewayClassConfig = &v1alpha1.GatewayClassConfig{} - serializeGatewayClassConfig(&tt.config.Gateway, tt.config.GatewayClassConfig) - - binder := NewBinder(tt.config) - actual := binder.Snapshot() - - require.Len(t, actual.Consul.Registrations, len(tt.expectedRegistrations)) - for i := range actual.Consul.Registrations { - registration := actual.Consul.Registrations[i] - expected := tt.expectedRegistrations[i] - - require.EqualValues(t, expected, registration.Service.ID) - require.EqualValues(t, "gateway", registration.Service.Service) - } - - require.EqualValues(t, tt.expectedDeregistrations, actual.Consul.Deregistrations) - }) - } -} - -func TestBinder_BindingRulesKitchenSink(t *testing.T) { - t.Parallel() - - gateway := gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Name: "http-listener-default-same", - Protocol: gwv1beta1.HTTPProtocolType, - }, { - Name: "http-listener-hostname", - Protocol: gwv1beta1.HTTPProtocolType, - Hostname: common.PointerTo[gwv1beta1.Hostname]("host.name"), - }, { - Name: "http-listener-mismatched-kind-allowed", - Protocol: gwv1beta1.HTTPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Kinds: []gwv1beta1.RouteGroupKind{{ - Kind: "Foo", - }}, - }, - }, { - Name: "http-listener-explicit-all-allowed", - Protocol: gwv1beta1.HTTPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromAll), - }, - }, - }, { - Name: "http-listener-explicit-allowed-same", - Protocol: gwv1beta1.HTTPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromSame), - }, - }, - }, { - Name: "http-listener-allowed-selector", - Protocol: gwv1beta1.HTTPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "foo", - }, - }, - }, - }, - }, { - Name: "http-listener-tls", - Protocol: gwv1beta1.HTTPSProtocolType, - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }}, - }, - }, { - Name: "tcp-listener-default-same", - Protocol: gwv1beta1.TCPProtocolType, - }, { - Name: "tcp-listener-mismatched-kind-allowed", - Protocol: gwv1beta1.TCPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Kinds: []gwv1beta1.RouteGroupKind{{ - Kind: "Foo", - }}, - }, - }, { - Name: "tcp-listener-explicit-all-allowed", - Protocol: gwv1beta1.TCPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromAll), - }, - }, - }, { - Name: "tcp-listener-explicit-allowed-same", - Protocol: gwv1beta1.TCPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromSame), - }, - }, - }, { - Name: "tcp-listener-allowed-selector", - Protocol: gwv1beta1.TCPProtocolType, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "foo", - }, - }, - }, - }, - }, { - Name: "tcp-listener-tls", - Protocol: gwv1beta1.TCPProtocolType, - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{{ - Name: "secret-one", - }}, - }, - }}, - }) - - namespaces := map[string]corev1.Namespace{ - "default": { - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - }, - }, - "test": { - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{ - "test": "foo", - }, - }, - }, - } - - _, secretOne := generateTestCertificate(t, "", "secret-one") - - gateway.Namespace = "default" - defaultNamespacePointer := common.PointerTo[gwv1beta1.Namespace]("default") - - for name, tt := range map[string]struct { - httpRoute *gwv1beta1.HTTPRoute - tcpRoute *gwv1alpha2.TCPRoute - referenceGrants []gwv1beta1.ReferenceGrant - expectedStatusUpdates []client.Object - }{ - "untargeted http route same namespace": { - httpRoute: testHTTPRouteBackends("route", "default", nil, []gwv1beta1.ParentReference{ - {Name: "gateway"}, - }), - expectedStatusUpdates: []client.Object{ - testHTTPRouteStatusBackends("route", "default", nil, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "untargeted http route same namespace missing backend": { - httpRoute: testHTTPRouteBackends("route", "default", []gwv1beta1.BackendObjectReference{ - {Name: gwv1beta1.ObjectName("backend")}, - }, []gwv1beta1.ParentReference{ - {Name: "gateway"}, - }), - expectedStatusUpdates: []client.Object{ - testHTTPRouteStatusBackends("route", "default", []gwv1beta1.BackendObjectReference{ - {Name: gwv1beta1.ObjectName("backend")}, - }, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "BackendNotFound", - Message: "default/backend: backend not found", - }, - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "untargeted http route same namespace invalid backend type": { - httpRoute: testHTTPRouteBackends("route", "default", []gwv1beta1.BackendObjectReference{ - { - Name: gwv1beta1.ObjectName("backend"), - Group: common.PointerTo[gwv1beta1.Group]("invalid.foo.com"), - }, - }, []gwv1beta1.ParentReference{ - {Name: "gateway"}, - }), - expectedStatusUpdates: []client.Object{ - testHTTPRouteStatusBackends("route", "default", []gwv1beta1.BackendObjectReference{ - { - Name: gwv1beta1.ObjectName("backend"), - Group: common.PointerTo[gwv1beta1.Group]("invalid.foo.com"), - }, - }, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidKind", - Message: "default/backend [Service.invalid.foo.com]: invalid backend kind", - }, - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "untargeted http route different namespace": { - httpRoute: testHTTPRouteBackends("route", "other", nil, []gwv1beta1.ParentReference{ - { - Name: "gateway", - Namespace: defaultNamespacePointer, - }, - }), - expectedStatusUpdates: []client.Object{ - testHTTPRouteStatusBackends("route", "other", nil, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "untargeted http route different namespace and reference grants": { - httpRoute: testHTTPRouteBackends("route", "other", nil, []gwv1beta1.ParentReference{ - { - Name: "gateway", - Namespace: defaultNamespacePointer, - }, - }), - referenceGrants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("other")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Group: gwv1beta1.GroupName, Kind: "Gateway"}, - }, - }}, - }, - expectedStatusUpdates: []client.Object{ - testHTTPRouteStatusBackends("route", "other", nil, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "targeted http route same namespace": { - httpRoute: testHTTPRouteBackends("route", "default", nil, []gwv1beta1.ParentReference{ - { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - }), - expectedStatusUpdates: []client.Object{ - testHTTPRouteStatusBackends("route", "default", nil, []gwv1beta1.RouteParentStatus{ - { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-allowed-selector: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-explicit-all-allowed: listener does not support route protocol", - }}, - }, - }), - }, - }, - "targeted http route different namespace": { - referenceGrants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("test")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Group: gwv1beta1.GroupName, Kind: "Gateway"}, - }, - }}, - }, - httpRoute: testHTTPRouteBackends("route", "test", nil, []gwv1beta1.ParentReference{ - { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - }), - expectedStatusUpdates: []client.Object{ - testHTTPRouteStatusBackends("route", "test", nil, []gwv1beta1.RouteParentStatus{ - { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-default-same: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-hostname: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-explicit-allowed-same: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-tls: listener does not allow binding routes from the given namespace", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "tcp-listener-explicit-all-allowed: listener does not support route protocol", - }}, - }, - }), - }, - }, - "untargeted tcp route same namespace": { - tcpRoute: testTCPRouteBackends("route", "default", nil, []gwv1beta1.ParentReference{ - {Name: "gateway"}, - }), - expectedStatusUpdates: []client.Object{ - testTCPRouteStatusBackends("route", "default", nil, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "untargeted tcp route same namespace missing backend": { - tcpRoute: testTCPRouteBackends("route", "default", []gwv1beta1.BackendObjectReference{ - {Name: gwv1beta1.ObjectName("backend")}, - }, []gwv1beta1.ParentReference{ - {Name: "gateway"}, - }), - expectedStatusUpdates: []client.Object{ - testTCPRouteStatusBackends("route", "default", []gwv1beta1.BackendObjectReference{ - {Name: gwv1beta1.ObjectName("backend")}, - }, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "BackendNotFound", - Message: "default/backend: backend not found", - }, - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "untargeted tcp route same namespace invalid backend type": { - tcpRoute: testTCPRouteBackends("route", "default", []gwv1beta1.BackendObjectReference{ - { - Name: gwv1beta1.ObjectName("backend"), - Group: common.PointerTo[gwv1beta1.Group]("invalid.foo.com"), - }, - }, []gwv1beta1.ParentReference{ - {Name: "gateway"}, - }), - expectedStatusUpdates: []client.Object{ - testTCPRouteStatusBackends("route", "default", []gwv1beta1.BackendObjectReference{ - { - Name: gwv1beta1.ObjectName("backend"), - Group: common.PointerTo[gwv1beta1.Group]("invalid.foo.com"), - }, - }, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{Name: "gateway"}, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidKind", - Message: "default/backend [Service.invalid.foo.com]: invalid backend kind", - }, - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "untargeted tcp route different namespace": { - tcpRoute: testTCPRouteBackends("route", "other", nil, []gwv1beta1.ParentReference{ - { - Name: "gateway", - Namespace: defaultNamespacePointer, - }, - }), - expectedStatusUpdates: []client.Object{ - testTCPRouteStatusBackends("route", "other", nil, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "untargeted tcp route different namespace and reference grants": { - tcpRoute: testTCPRouteBackends("route", "other", nil, []gwv1beta1.ParentReference{ - { - Name: "gateway", - Namespace: defaultNamespacePointer, - }, - }), - referenceGrants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "TCPRoute", Namespace: gwv1beta1.Namespace("other")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Group: gwv1beta1.GroupName, Kind: "Gateway"}, - }, - }}, - }, - expectedStatusUpdates: []client.Object{ - testTCPRouteStatusBackends("route", "other", nil, []gwv1beta1.RouteParentStatus{ - {ControllerName: testControllerName, ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - }, Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }, - }}, - }), - }, - }, - "targeted tcp route same namespace": { - tcpRoute: testTCPRouteBackends("route", "default", nil, []gwv1beta1.ParentReference{ - { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, { - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - }), - expectedStatusUpdates: []client.Object{ - testTCPRouteStatusBackends("route", "default", nil, []gwv1beta1.RouteParentStatus{ - { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-default-same: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-hostname: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-explicit-all-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-explicit-allowed-same: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-allowed-selector: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-tls: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, - }), - }, - }, - "targeted tcp route different namespace": { - referenceGrants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "TCPRoute", Namespace: gwv1beta1.Namespace("test")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Group: gwv1beta1.GroupName, Kind: "Gateway"}, - }, - }}, - }, - tcpRoute: testTCPRouteBackends("route", "test", nil, []gwv1beta1.ParentReference{ - { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, { - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - }), - expectedStatusUpdates: []client.Object{ - testTCPRouteStatusBackends("route", "test", nil, []gwv1beta1.RouteParentStatus{ - { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-default-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-default-same: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-hostname"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-hostname: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-mismatched-kind-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-mismatched-kind-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-explicit-all-allowed: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-explicit-allowed-same"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-explicit-allowed-same: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-allowed-selector"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-allowed-selector: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("http-listener-tls"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "NotAllowedByListeners", - Message: "http-listener-tls: listener does not support route protocol", - }}, - }, { - ControllerName: testControllerName, - ParentRef: gwv1beta1.ParentReference{ - Name: "gateway", - Namespace: defaultNamespacePointer, - SectionName: common.PointerTo[gwv1beta1.SectionName]("tcp-listener-explicit-all-allowed"), - }, - Conditions: []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - }}, - }, - }), - }, - }, - } { - t.Run(name, func(t *testing.T) { - g := *addClassConfig(gateway) - - resources := resourceMapResources{ - gateways: []gwv1beta1.Gateway{g}, - secrets: []corev1.Secret{ - secretOne, - }, - grants: tt.referenceGrants, - } - - if tt.httpRoute != nil { - resources.httpRoutes = append(resources.httpRoutes, *tt.httpRoute) - } - if tt.tcpRoute != nil { - resources.tcpRoutes = append(resources.tcpRoutes, *tt.tcpRoute) - } - - config := controlledBinder(BinderConfig{ - Gateway: g, - GatewayClassConfig: &v1alpha1.GatewayClassConfig{}, - Namespaces: namespaces, - Resources: newTestResourceMap(t, resources), - HTTPRoutes: resources.httpRoutes, - TCPRoutes: resources.tcpRoutes, - }) - - binder := NewBinder(config) - actual := binder.Snapshot() - - compareUpdates(t, tt.expectedStatusUpdates, actual.Kubernetes.StatusUpdates.Operations()) - }) - } -} - -func compareUpdates(t *testing.T, expected []client.Object, actual []client.Object) { - t.Helper() - - filtered := common.Filter(actual, func(o client.Object) bool { - if _, ok := o.(*gwv1beta1.HTTPRoute); ok { - return false - } - if _, ok := o.(*gwv1alpha2.TCPRoute); ok { - return false - } - return true - }) - - require.ElementsMatch(t, expected, filtered, "statuses don't match", cmp.Diff(expected, filtered)) -} - -func addClassConfig(g gwv1beta1.Gateway) *gwv1beta1.Gateway { - serializeGatewayClassConfig(&g, &v1alpha1.GatewayClassConfig{}) - return &g -} - -func gatewayWithFinalizer(spec gwv1beta1.GatewaySpec) gwv1beta1.Gateway { - spec.GatewayClassName = testGatewayClassObjectName - - typeMeta := metav1.TypeMeta{} - typeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("Gateway")) - - return gwv1beta1.Gateway{ - TypeMeta: typeMeta, - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Namespace: "default", - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: spec, - } -} - -func gatewayWithFinalizerStatus(spec gwv1beta1.GatewaySpec, status gwv1beta1.GatewayStatus) gwv1beta1.Gateway { - g := gatewayWithFinalizer(spec) - g.Status = status - return g -} - -func testHTTPRoute(name string, parents []string, services []string) gwv1beta1.HTTPRoute { - var parentRefs []gwv1beta1.ParentReference - var rules []gwv1beta1.HTTPRouteRule - - for _, parent := range parents { - parentRefs = append(parentRefs, gwv1beta1.ParentReference{Name: gwv1beta1.ObjectName(parent)}) - } - - for _, service := range services { - rules = append(rules, gwv1beta1.HTTPRouteRule{ - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName(service), - }, - }, - }, - }, - }) - } - - httpTypeMeta := metav1.TypeMeta{} - httpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("HTTPRoute")) - - return gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{Name: name, Finalizers: []string{common.GatewayFinalizer}}, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: parentRefs, - }, - Rules: rules, - }, - } -} - -func testHTTPRouteBackends(name, namespace string, services []gwv1beta1.BackendObjectReference, parents []gwv1beta1.ParentReference) *gwv1beta1.HTTPRoute { - var rules []gwv1beta1.HTTPRouteRule - for _, service := range services { - rules = append(rules, gwv1beta1.HTTPRouteRule{ - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: service, - }, - }, - }, - }) - } - - httpTypeMeta := metav1.TypeMeta{} - httpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("HTTPRoute")) - - return &gwv1beta1.HTTPRoute{ - TypeMeta: httpTypeMeta, - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, Finalizers: []string{common.GatewayFinalizer}}, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: parents, - }, - Rules: rules, - }, - } -} - -func testHTTPRouteStatusBackends(name, namespace string, services []gwv1beta1.BackendObjectReference, parentStatuses []gwv1beta1.RouteParentStatus) *gwv1beta1.HTTPRoute { - var parentRefs []gwv1beta1.ParentReference - - for _, parent := range parentStatuses { - parentRefs = append(parentRefs, parent.ParentRef) - } - - route := testHTTPRouteBackends(name, namespace, services, parentRefs) - route.Status.RouteStatus.Parents = parentStatuses - return route -} - -func testHTTPRouteStatus(name string, services []string, parentStatuses []gwv1beta1.RouteParentStatus, extraParents ...string) gwv1beta1.HTTPRoute { - parentRefs := extraParents - - for _, parent := range parentStatuses { - parentRefs = append(parentRefs, string(parent.ParentRef.Name)) - } - - route := testHTTPRoute(name, parentRefs, services) - route.Status.RouteStatus.Parents = parentStatuses - - return route -} - -func testTCPRoute(name string, parents []string, services []string) gwv1alpha2.TCPRoute { - var parentRefs []gwv1beta1.ParentReference - var rules []gwv1alpha2.TCPRouteRule - - for _, parent := range parents { - parentRefs = append(parentRefs, gwv1beta1.ParentReference{Name: gwv1beta1.ObjectName(parent)}) - } - - for _, service := range services { - rules = append(rules, gwv1alpha2.TCPRouteRule{ - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: gwv1beta1.ObjectName(service), - }, - }, - }, - }) - } - - tcpTypeMeta := metav1.TypeMeta{} - tcpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("TCPRoute")) - - return gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{Name: name, Finalizers: []string{common.GatewayFinalizer}}, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: parentRefs, - }, - Rules: rules, - }, - } -} - -func testTCPRouteBackends(name, namespace string, services []gwv1beta1.BackendObjectReference, parents []gwv1beta1.ParentReference) *gwv1alpha2.TCPRoute { - var rules []gwv1alpha2.TCPRouteRule - for _, service := range services { - rules = append(rules, gwv1alpha2.TCPRouteRule{ - BackendRefs: []gwv1beta1.BackendRef{ - {BackendObjectReference: service}, - }, - }) - } - - tcpTypeMeta := metav1.TypeMeta{} - tcpTypeMeta.SetGroupVersionKind(gwv1beta1.SchemeGroupVersion.WithKind("TCPRoute")) - - return &gwv1alpha2.TCPRoute{ - TypeMeta: tcpTypeMeta, - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, Finalizers: []string{common.GatewayFinalizer}}, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: parents, - }, - Rules: rules, - }, - } -} - -func testTCPRouteStatusBackends(name, namespace string, services []gwv1beta1.BackendObjectReference, parentStatuses []gwv1beta1.RouteParentStatus) *gwv1alpha2.TCPRoute { - var parentRefs []gwv1beta1.ParentReference - - for _, parent := range parentStatuses { - parentRefs = append(parentRefs, parent.ParentRef) - } - - route := testTCPRouteBackends(name, namespace, services, parentRefs) - route.Status.RouteStatus.Parents = parentStatuses - return route -} - -func testTCPRouteStatus(name string, services []string, parentStatuses []gwv1beta1.RouteParentStatus, extraParents ...string) gwv1alpha2.TCPRoute { - parentRefs := extraParents - - for _, parent := range parentStatuses { - parentRefs = append(parentRefs, string(parent.ParentRef.Name)) - } - - route := testTCPRoute(name, parentRefs, services) - route.Status.RouteStatus.Parents = parentStatuses - - return route -} - -func controlledBinder(config BinderConfig) BinderConfig { - config.ControllerName = testControllerName - config.GatewayClass = testGatewayClass - return config -} - -func generateTestCertificate(t *testing.T, namespace, name string) (*api.InlineCertificateConfigEntry, corev1.Secret) { - privateKey, err := rsa.GenerateKey(rand.Reader, common.MinKeyLength) - require.NoError(t, err) - - usage := x509.KeyUsageCertSign - expiration := time.Now().AddDate(10, 0, 0) - - cert := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: "consul.test", - }, - IsCA: true, - NotBefore: time.Now().Add(-10 * time.Minute), - NotAfter: expiration, - SubjectKeyId: []byte{1, 2, 3, 4, 6}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: usage, - BasicConstraintsValid: true, - } - caCert := cert - caPrivateKey := privateKey - - data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) - require.NoError(t, err) - - certBytes := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: data, - }) - - privateKeyBytes := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) - - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: certBytes, - corev1.TLSPrivateKeyKey: privateKeyBytes, - }, - } - - certificate, err := (common.ResourceTranslator{}).ToInlineCertificate(secret) - require.NoError(t, err) - - return certificate, secret -} diff --git a/control-plane/api-gateway/binding/reference_grant.go b/control-plane/api-gateway/binding/reference_grant.go deleted file mode 100644 index c2cc421a30..0000000000 --- a/control-plane/api-gateway/binding/reference_grant.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" -) - -type referenceValidator struct { - grants map[string]map[types.NamespacedName]gwv1beta1.ReferenceGrant -} - -func NewReferenceValidator(grants []gwv1beta1.ReferenceGrant) common.ReferenceValidator { - byNamespace := make(map[string]map[types.NamespacedName]gwv1beta1.ReferenceGrant) - for _, grant := range grants { - grantsForNamespace, ok := byNamespace[grant.Namespace] - if !ok { - grantsForNamespace = make(map[types.NamespacedName]gwv1beta1.ReferenceGrant) - } - grantsForNamespace[client.ObjectKeyFromObject(&grant)] = grant - byNamespace[grant.Namespace] = grantsForNamespace - } - return &referenceValidator{ - grants: byNamespace, - } -} - -func (rv *referenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool { - fromNS := gateway.GetNamespace() - fromGK := metav1.GroupKind{ - Group: gateway.GroupVersionKind().Group, - Kind: gateway.GroupVersionKind().Kind, - } - - // Kind should default to Secret if not set - // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#LL59C21-L59C21 - toNS, toGK := createValuesFromRef(secretRef.Namespace, secretRef.Group, secretRef.Kind, "", common.KindSecret) - - return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(secretRef.Name)) -} - -func (rv *referenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { - fromNS := httproute.GetNamespace() - fromGK := metav1.GroupKind{ - Group: httproute.GroupVersionKind().Group, - Kind: httproute.GroupVersionKind().Kind, - } - - // Kind should default to Service if not set - // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#L106 - toNS, toGK := createValuesFromRef(backendRef.Namespace, backendRef.Group, backendRef.Kind, "", common.KindService) - - return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(backendRef.Name)) -} - -func (rv *referenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { - fromNS := tcpRoute.GetNamespace() - fromGK := metav1.GroupKind{ - Group: tcpRoute.GroupVersionKind().Group, - Kind: tcpRoute.GroupVersionKind().Kind, - } - - // Kind should default to Service if not set - // https://github.com/kubernetes-sigs/gateway-api/blob/v0.6.2/apis/v1beta1/object_reference_types.go#L106 - toNS, toGK := createValuesFromRef(backendRef.Namespace, backendRef.Group, backendRef.Kind, common.BetaGroup, common.KindService) - - return rv.referenceAllowed(fromGK, fromNS, toGK, toNS, string(backendRef.Name)) -} - -func createValuesFromRef(ns *gwv1beta1.Namespace, group *gwv1beta1.Group, kind *gwv1beta1.Kind, defaultGroup, defaultKind string) (string, metav1.GroupKind) { - toNS := "" - if ns != nil { - toNS = string(*ns) - } - - gk := metav1.GroupKind{ - Kind: defaultKind, - Group: defaultGroup, - } - if group != nil { - gk.Group = string(*group) - } - if kind != nil { - gk.Kind = string(*kind) - } - - return toNS, gk -} - -// referenceAllowed checks to see if a reference between resources is allowed. -// In particular, references from one namespace to a resource in a different namespace -// require an applicable ReferenceGrant be found in the namespace containing the resource -// being referred to. -// -// For example, a Gateway in namespace "foo" may only reference a Secret in namespace "bar" -// if a ReferenceGrant in namespace "bar" allows references from namespace "foo". -func (rv *referenceValidator) referenceAllowed(fromGK metav1.GroupKind, fromNamespace string, toGK metav1.GroupKind, toNamespace, toName string) bool { - // Reference does not cross namespaces - if toNamespace == "" || toNamespace == fromNamespace { - return true - } - - // Fetch all ReferenceGrants in the referenced namespace - grants, ok := rv.grants[toNamespace] - if !ok { - return false - } - - for _, grant := range grants { - // Check for a From that applies - fromMatch := false - for _, from := range grant.Spec.From { - if fromGK.Group == string(from.Group) && fromGK.Kind == string(from.Kind) && fromNamespace == string(from.Namespace) { - fromMatch = true - break - } - } - - if !fromMatch { - continue - } - - // Check for a To that applies - for _, to := range grant.Spec.To { - if toGK.Group == string(to.Group) && toGK.Kind == string(to.Kind) { - if to.Name == nil || *to.Name == "" { - // No name specified is treated as a wildcard within the namespace - return true - } - - if gwv1beta1.ObjectName(toName) == *to.Name { - // The ReferenceGrant specifically targets this object - return true - } - } - } - } - - // No ReferenceGrant was found which allows this cross-namespace reference - return false -} diff --git a/control-plane/api-gateway/binding/reference_grant_test.go b/control-plane/api-gateway/binding/reference_grant_test.go deleted file mode 100644 index 12f01478fc..0000000000 --- a/control-plane/api-gateway/binding/reference_grant_test.go +++ /dev/null @@ -1,454 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "context" - "testing" - - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - ToNamespace = "toNamespace" - FromNamespace = "fromNamespace" - InvalidNamespace = "invalidNamespace" - Group = "gateway.networking.k8s.io" - V1Beta1 = "/v1beta1" - V1Alpha2 = "/v1alpha2" - HTTPRouteKind = "HTTPRoute" - TCPRouteKind = "TCPRoute" - GatewayKind = "Gateway" - BackendRefKind = "Service" - SecretKind = "Secret" -) - -func TestGatewayCanReferenceSecret(t *testing.T) { - t.Parallel() - - objName := gwv1beta1.ObjectName("mysecret") - - basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: ToNamespace, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - { - Group: Group, - Kind: GatewayKind, - Namespace: FromNamespace, - }, - }, - To: []gwv1beta1.ReferenceGrantTo{ - { - Group: Group, - Kind: SecretKind, - Name: &objName, - }, - }, - }, - } - - secretRefGroup := gwv1beta1.Group(Group) - secretRefKind := gwv1beta1.Kind(SecretKind) - secretRefNamespace := gwv1beta1.Namespace(ToNamespace) - - cases := map[string]struct { - canReference bool - err error - ctx context.Context - gateway gwv1beta1.Gateway - secret gwv1beta1.SecretObjectReference - k8sReferenceGrants []gwv1beta1.ReferenceGrant - }{ - "gateway allowed to secret": { - canReference: true, - err: nil, - ctx: context.TODO(), - gateway: gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: GatewayKind, - APIVersion: Group + V1Beta1, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: FromNamespace, - }, - Spec: gwv1beta1.GatewaySpec{}, - Status: gwv1beta1.GatewayStatus{}, - }, - secret: gwv1beta1.SecretObjectReference{ - Group: &secretRefGroup, - Kind: &secretRefKind, - Namespace: &secretRefNamespace, - Name: objName, - }, - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - basicValidReferenceGrant, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - rv := NewReferenceValidator(tc.k8sReferenceGrants) - canReference := rv.GatewayCanReferenceSecret(tc.gateway, tc.secret) - - require.Equal(t, tc.canReference, canReference) - }) - } -} - -func TestHTTPRouteCanReferenceBackend(t *testing.T) { - t.Parallel() - - objName := gwv1beta1.ObjectName("myBackendRef") - - basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: ToNamespace, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - { - Group: Group, - Kind: HTTPRouteKind, - Namespace: FromNamespace, - }, - }, - To: []gwv1beta1.ReferenceGrantTo{ - { - Group: Group, - Kind: BackendRefKind, - Name: &objName, - }, - }, - }, - } - - backendRefGroup := gwv1beta1.Group(Group) - backendRefKind := gwv1beta1.Kind(BackendRefKind) - backendRefNamespace := gwv1beta1.Namespace(ToNamespace) - - cases := map[string]struct { - canReference bool - err error - ctx context.Context - httpRoute gwv1beta1.HTTPRoute - backendRef gwv1beta1.BackendRef - k8sReferenceGrants []gwv1beta1.ReferenceGrant - }{ - "httproute allowed to gateway": { - canReference: true, - err: nil, - ctx: context.TODO(), - httpRoute: gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: HTTPRouteKind, - APIVersion: Group + V1Beta1, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: FromNamespace, - }, - Spec: gwv1beta1.HTTPRouteSpec{}, - Status: gwv1beta1.HTTPRouteStatus{}, - }, - backendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Group: &backendRefGroup, - Kind: &backendRefKind, - Name: objName, - Namespace: &backendRefNamespace, - Port: nil, - }, - Weight: nil, - }, - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - basicValidReferenceGrant, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - rv := NewReferenceValidator(tc.k8sReferenceGrants) - canReference := rv.HTTPRouteCanReferenceBackend(tc.httpRoute, tc.backendRef) - - require.Equal(t, tc.canReference, canReference) - }) - } -} - -func TestTCPRouteCanReferenceBackend(t *testing.T) { - t.Parallel() - - objName := gwv1beta1.ObjectName("myBackendRef") - - basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: ToNamespace, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - { - Group: Group, - Kind: TCPRouteKind, - Namespace: FromNamespace, - }, - }, - To: []gwv1beta1.ReferenceGrantTo{ - { - Group: Group, - Kind: BackendRefKind, - Name: &objName, - }, - }, - }, - } - - backendRefGroup := gwv1beta1.Group(Group) - backendRefKind := gwv1beta1.Kind(BackendRefKind) - backendRefNamespace := gwv1beta1.Namespace(ToNamespace) - - cases := map[string]struct { - canReference bool - err error - ctx context.Context - tcpRoute gwv1alpha2.TCPRoute - backendRef gwv1beta1.BackendRef - k8sReferenceGrants []gwv1beta1.ReferenceGrant - }{ - "tcpRoute allowed to gateway": { - canReference: true, - err: nil, - ctx: context.TODO(), - tcpRoute: gwv1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: TCPRouteKind, - APIVersion: Group + V1Alpha2, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: FromNamespace, - }, - Spec: gwv1alpha2.TCPRouteSpec{}, - Status: gwv1alpha2.TCPRouteStatus{}, - }, - backendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Group: &backendRefGroup, - Kind: &backendRefKind, - Name: objName, - Namespace: &backendRefNamespace, - Port: nil, - }, - Weight: nil, - }, - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - basicValidReferenceGrant, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - rv := NewReferenceValidator(tc.k8sReferenceGrants) - canReference := rv.TCPRouteCanReferenceBackend(tc.tcpRoute, tc.backendRef) - - require.Equal(t, tc.canReference, canReference) - }) - } -} - -func TestReferenceAllowed(t *testing.T) { - t.Parallel() - - objName := gwv1beta1.ObjectName("myObject") - - basicValidReferenceGrant := gwv1beta1.ReferenceGrant{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: ToNamespace, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - { - Group: Group, - Kind: HTTPRouteKind, - Namespace: FromNamespace, - }, - }, - To: []gwv1beta1.ReferenceGrantTo{ - { - Group: Group, - Kind: GatewayKind, - Name: &objName, - }, - }, - }, - } - - cases := map[string]struct { - refAllowed bool - err error - ctx context.Context - fromGK metav1.GroupKind - fromNamespace string - toGK metav1.GroupKind - toNamespace string - toName string - k8sReferenceGrants []gwv1beta1.ReferenceGrant - }{ - "same namespace": { - refAllowed: true, - err: nil, - ctx: context.TODO(), - fromGK: metav1.GroupKind{ - Group: Group, - Kind: HTTPRouteKind, - }, - fromNamespace: FromNamespace, - toGK: metav1.GroupKind{ - Group: Group, - Kind: GatewayKind, - }, - toNamespace: FromNamespace, - toName: string(objName), - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - { - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: FromNamespace, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - { - Group: Group, - Kind: HTTPRouteKind, - Namespace: FromNamespace, - }, - }, - To: []gwv1beta1.ReferenceGrantTo{ - { - Group: Group, - Kind: GatewayKind, - Name: &objName, - }, - }, - }, - }, - }, - }, - "reference allowed": { - refAllowed: true, - err: nil, - ctx: context.TODO(), - fromGK: metav1.GroupKind{ - Group: Group, - Kind: HTTPRouteKind, - }, - fromNamespace: FromNamespace, - toGK: metav1.GroupKind{ - Group: Group, - Kind: GatewayKind, - }, - toNamespace: ToNamespace, - toName: string(objName), - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - basicValidReferenceGrant, - }, - }, - "reference not allowed": { - refAllowed: false, - err: nil, - ctx: context.TODO(), - fromGK: metav1.GroupKind{ - Group: Group, - Kind: HTTPRouteKind, - }, - fromNamespace: InvalidNamespace, - toGK: metav1.GroupKind{ - Group: Group, - Kind: GatewayKind, - }, - toNamespace: ToNamespace, - toName: string(objName), - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - basicValidReferenceGrant, - }, - }, - "no reference grant defined in namespace": { - refAllowed: false, - err: nil, - ctx: context.TODO(), - fromGK: metav1.GroupKind{ - Group: Group, - Kind: HTTPRouteKind, - }, - fromNamespace: FromNamespace, - toGK: metav1.GroupKind{ - Group: Group, - Kind: GatewayKind, - }, - toNamespace: ToNamespace, - toName: string(objName), - k8sReferenceGrants: nil, - }, - "reference allowed to all objects in namespace": { - refAllowed: true, - err: nil, - ctx: context.TODO(), - fromGK: metav1.GroupKind{ - Group: Group, - Kind: HTTPRouteKind, - }, - fromNamespace: FromNamespace, - toGK: metav1.GroupKind{ - Group: Group, - Kind: GatewayKind, - }, - toNamespace: ToNamespace, - toName: string(objName), - k8sReferenceGrants: []gwv1beta1.ReferenceGrant{ - { - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: ToNamespace, - }, - Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - { - Group: Group, - Kind: HTTPRouteKind, - Namespace: FromNamespace, - }, - }, - To: []gwv1beta1.ReferenceGrantTo{ - { - Group: Group, - Kind: GatewayKind, - Name: nil, - }, - }, - }, - }, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - rv := NewReferenceValidator(tc.k8sReferenceGrants).(*referenceValidator) - refAllowed := rv.referenceAllowed(tc.fromGK, tc.fromNamespace, tc.toGK, tc.toNamespace, tc.toName) - - require.Equal(t, tc.refAllowed, refAllowed) - }) - } -} diff --git a/control-plane/api-gateway/binding/registration.go b/control-plane/api-gateway/binding/registration.go deleted file mode 100644 index ae26ab51f6..0000000000 --- a/control-plane/api-gateway/binding/registration.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "fmt" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul/api" - corev1 "k8s.io/api/core/v1" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -const ( - metaKeySyntheticNode = "synthetic-node" - kubernetesSuccessReasonMsg = "Kubernetes health checks passing" - - // consulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. - consulKubernetesCheckType = "kubernetes-readiness" - - // consulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. - consulKubernetesCheckName = "Kubernetes Readiness Check" -) - -func registrationsForPods(namespace string, gateway gwv1beta1.Gateway, pods []corev1.Pod) []api.CatalogRegistration { - registrations := []api.CatalogRegistration{} - for _, pod := range pods { - registrations = append(registrations, registrationForPod(namespace, gateway, pod)) - } - return registrations -} - -func registrationForPod(namespace string, gateway gwv1beta1.Gateway, pod corev1.Pod) api.CatalogRegistration { - healthStatus := api.HealthCritical - if isPodReady(pod) { - healthStatus = api.HealthPassing - } - - return api.CatalogRegistration{ - Node: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), - Address: pod.Status.HostIP, - NodeMeta: map[string]string{ - metaKeySyntheticNode: "true", - }, - Service: &api.AgentService{ - Kind: api.ServiceKindAPIGateway, - ID: pod.Name, - Service: gateway.Name, - Address: pod.Status.PodIP, - Namespace: namespace, - Meta: map[string]string{ - constants.MetaKeyPodName: pod.Name, - constants.MetaKeyKubeNS: pod.Namespace, - constants.MetaKeyKubeServiceName: gateway.Name, - "external-source": "consul-api-gateway", - }, - }, - Check: &api.AgentCheck{ - CheckID: fmt.Sprintf("%s/%s", pod.Namespace, pod.Name), - Name: consulKubernetesCheckName, - Type: consulKubernetesCheckType, - Status: healthStatus, - ServiceID: pod.Name, - Output: getHealthCheckStatusReason(healthStatus, pod.Name, pod.Namespace), - Namespace: namespace, - }, - SkipNodeUpdate: true, - } -} - -func getHealthCheckStatusReason(healthCheckStatus, podName, podNamespace string) string { - if healthCheckStatus == api.HealthPassing { - return kubernetesSuccessReasonMsg - } - - return fmt.Sprintf("Pod \"%s/%s\" is not ready", podNamespace, podName) -} - -func isPodReady(pod corev1.Pod) bool { - if corev1.PodRunning != pod.Status.Phase { - return false - } - - for _, condition := range pod.Status.Conditions { - if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue { - return true - } - } - return false -} diff --git a/control-plane/api-gateway/binding/registration_test.go b/control-plane/api-gateway/binding/registration_test.go deleted file mode 100644 index 356915f9f7..0000000000 --- a/control-plane/api-gateway/binding/registration_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "testing" - - "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestRegistrationsForPods_Health(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - consulNamespace string - gateway gwv1beta1.Gateway - pods []corev1.Pod - expected []string - }{ - "empty": { - consulNamespace: "", - gateway: gwv1beta1.Gateway{}, - pods: []corev1.Pod{}, - expected: []string{}, - }, - "mix": { - consulNamespace: "", - gateway: gwv1beta1.Gateway{}, - pods: []corev1.Pod{ - // Pods without a running status - {Status: corev1.PodStatus{Phase: corev1.PodFailed}}, - {Status: corev1.PodStatus{Phase: corev1.PodPending}}, - {Status: corev1.PodStatus{Phase: corev1.PodSucceeded}}, - {Status: corev1.PodStatus{Phase: corev1.PodUnknown}}, - // Running statuses that don't show readiness - {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ - {Type: corev1.PodScheduled, Status: corev1.ConditionTrue}, - }}}, - {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ - {Type: corev1.PodInitialized, Status: corev1.ConditionTrue}, - }}}, - {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ - {Type: corev1.DisruptionTarget, Status: corev1.ConditionTrue}, - }}}, - {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ - {Type: corev1.ContainersReady, Status: corev1.ConditionTrue}, - }}}, - // And finally, the successful check - {Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{ - {Type: corev1.PodReady, Status: corev1.ConditionTrue}, - }}}, - }, - expected: []string{ - api.HealthCritical, - api.HealthCritical, - api.HealthCritical, - api.HealthCritical, - api.HealthCritical, - api.HealthCritical, - api.HealthCritical, - api.HealthCritical, - api.HealthPassing, - }, - }, - } { - t.Run(name, func(t *testing.T) { - registrations := registrationsForPods(tt.consulNamespace, tt.gateway, tt.pods) - require.Len(t, registrations, len(tt.expected)) - - for i := range registrations { - registration := registrations[i] - expected := tt.expected[i] - - require.EqualValues(t, "Kubernetes Readiness Check", registration.Check.Name) - require.EqualValues(t, expected, registration.Check.Status) - } - }) - } -} diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go deleted file mode 100644 index 38219a2c79..0000000000 --- a/control-plane/api-gateway/binding/result.go +++ /dev/null @@ -1,741 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "errors" - "fmt" - "sort" - "strings" - - mapset "github.com/deckarep/golang-set" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" -) - -// override function for tests. -var timeFunc = metav1.Now - -// This is used for any error related to a lack of proper reference grant creation. -var errRefNotPermitted = errors.New("reference not permitted due to lack of ReferenceGrant") - -var ( - // Each of the below are specified in the Gateway spec under RouteConditionReason - // to the RouteConditionReason given in the spec. If a reason is overloaded and can - // be used with two different types of things (i.e. something is not found or it's not supported) - // then we distinguish those two usages with errRoute*_Usage. - errRouteNotAllowedByListeners_Namespace = errors.New("listener does not allow binding routes from the given namespace") - errRouteNotAllowedByListeners_Protocol = errors.New("listener does not support route protocol") - errRouteNoMatchingListenerHostname = errors.New("listener cannot bind route with a non-aligned hostname") - errRouteInvalidKind = errors.New("invalid backend kind") - errRouteBackendNotFound = errors.New("backend not found") - errRouteNoMatchingParent = errors.New("no matching parent") - errInvalidExternalRefType = errors.New("invalid externalref filter kind") - errExternalRefNotFound = errors.New("ref not found") - errFilterInvalid = errors.New("filter invalid") -) - -// routeValidationResult holds the result of validating a route globally, in other -// words, for a particular backend reference without consideration to its particular -// gateway. Unfortunately, due to the fact that the spec requires a route status be -// associated with a parent reference, what it means is that anything that is global -// in nature, like this status will need to be duplicated for every parent reference -// on a given route status. -type routeValidationResult struct { - namespace string - backend gwv1beta1.BackendRef - err error -} - -// Type is used for error printing a backend reference type that we don't support on -// a validation error. -func (v routeValidationResult) Type() string { - return (&metav1.GroupKind{ - Group: common.ValueOr(v.backend.Group, ""), - Kind: common.ValueOr(v.backend.Kind, common.KindService), - }).String() -} - -// String is the namespace/name of the reference that has an error. -func (v routeValidationResult) String() string { - return (types.NamespacedName{Namespace: v.namespace, Name: string(v.backend.Name)}).String() -} - -// routeValidationResults contains a list of validation results for the backend references -// on a route. -type routeValidationResults []routeValidationResult - -// Condition returns the ResolvedRefs condition that gets duplicated across every relevant -// parent on a route's status. -func (e routeValidationResults) Condition() metav1.Condition { - // we only use the first error due to the way the spec is structured - // where you can only have a single condition - for _, v := range e { - err := v.err - if err != nil { - switch err { - case errRouteInvalidKind: - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidKind", - Message: fmt.Sprintf("%s [%s]: %s", v.String(), v.Type(), err.Error()), - } - case errRouteBackendNotFound: - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "BackendNotFound", - Message: fmt.Sprintf("%s: %s", v.String(), err.Error()), - } - case errRefNotPermitted: - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "RefNotPermitted", - Message: fmt.Sprintf("%s: %s", v.String(), err.Error()), - } - default: - // this should never happen - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "UnhandledValidationError", - Message: err.Error(), - } - } - } - } - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - } -} - -// bindResult holds the result of attempting to bind a route to a particular gateway listener -// an error value here means that the route did not bind successfully, no error means that -// the route should be considered bound. -type bindResult struct { - section gwv1beta1.SectionName - err error -} - -// bindResults holds the results of attempting to bind a route to a gateway, having a separate -// bindResult for each listener on the gateway. -type bindResults []bindResult - -// Error constructs a human readable error for bindResults, containing any errors that a route -// had in binding to a gateway. Note that this is only used if a route failed to bind to every -// listener it attempted to bind to. -func (b bindResults) Error() string { - messages := []string{} - for _, result := range b { - if result.err != nil { - message := result.err.Error() - if result.section != "" { - message = fmt.Sprintf("%s: %s", result.section, result.err.Error()) - } - messages = append(messages, message) - } - } - - sort.Strings(messages) - return strings.Join(messages, "; ") -} - -// DidBind returns whether a route successfully bound to any listener on a gateway. -func (b bindResults) DidBind() bool { - for _, result := range b { - if result.err == nil { - return true - } - } - return false -} - -// Condition constructs an Accepted condition for a route that will be scoped -// to the particular parent reference it's using to attempt binding. -func (b bindResults) Condition() metav1.Condition { - // if we bound to any listeners, say we're accepted - if b.DidBind() { - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - } - } - - // default to the most generic reason in the spec "NotAllowedByListeners" - reason := "NotAllowedByListeners" - - // if we only have a single binding error, we can get more specific - if len(b) == 1 { - for _, result := range b { - switch { - case errors.Is(result.err, errRouteNoMatchingListenerHostname): - // if we have a hostname mismatch error, then use the more specific reason - reason = "NoMatchingListenerHostname" - case errors.Is(result.err, errRefNotPermitted): - // or if we have a ref not permitted, then use that - reason = "RefNotPermitted" - case errors.Is(result.err, errRouteNoMatchingParent): - // or if the route declares a parent that we can't find - reason = "NoMatchingParent" - case errors.Is(result.err, errExternalRefNotFound): - reason = "FilterNotFound" - case errors.Is(result.err, errFilterInvalid): - reason = "JWTProviderNotFound" - case errors.Is(result.err, errInvalidExternalRefType): - reason = "UnsupportedValue" - } - } - } - - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: reason, - Message: b.Error(), - } -} - -// parentBindResult associates a binding result with the given parent reference. -type parentBindResult struct { - parent gwv1beta1.ParentReference - results bindResults -} - -// parentBindResults contains the list of all results that occurred when this route -// attempted to bind to a gateway using its parent references. -type parentBindResults []parentBindResult - -func (p parentBindResults) boundSections() mapset.Set { - set := mapset.NewSet() - for _, result := range p { - for _, r := range result.results { - if r.err == nil { - set.Add(string(r.section)) - } - } - } - return set -} - -var ( - // Each of the below are specified in the Gateway spec under ListenerConditionReason. - // The general usage is that each error is specified as errListener* where * corresponds - // to the ListenerConditionReason given in the spec. If a reason is overloaded and can - // be used with two different types of things (i.e. something is not found or it's not supported) - // then we distinguish those two usages with errListener*_Usage. - errListenerUnsupportedProtocol = errors.New("listener protocol is unsupported") - errListenerPortUnavailable = errors.New("listener port is unavailable") - errListenerHostnameConflict = errors.New("listener hostname conflicts with another listener") - errListenerProtocolConflict = errors.New("listener protocol conflicts with another listener") - errListenerInvalidCertificateRef_NotFound = errors.New("certificate not found") - errListenerInvalidCertificateRef_NotSupported = errors.New("certificate type is not supported") - errListenerInvalidCertificateRef_InvalidData = errors.New("certificate is invalid or does not contain a supported server name") - errListenerInvalidCertificateRef_NonFIPSRSAKeyLen = errors.New("certificate has an invalid length: RSA Keys must be at least 2048-bit") - errListenerInvalidCertificateRef_FIPSRSAKeyLen = errors.New("certificate has an invalid length: RSA keys must be either 2048-bit, 3072-bit, or 4096-bit in FIPS mode") - errListenerJWTProviderNotFound = errors.New("policy referencing this listener references unknown JWT provider") - errListenerInvalidRouteKinds = errors.New("allowed route kind is invalid") - errListenerProgrammed_Invalid = errors.New("listener cannot be programmed because it is invalid") - - // Below is where any custom generic listener validation errors should go. - // We map anything under here to a custom ListenerConditionReason of Invalid on - // an Accepted status type. - errListenerNoTLSPassthrough = errors.New("TLS passthrough is not supported") - errListenerTLSCipherSuiteNotConfigurable = errors.New("tls_min_version does not allow tls_cipher_suites configuration") - errListenerUnsupportedTLSCipherSuite = errors.New("unsupported cipher suite in tls_cipher_suites") - errListenerUnsupportedTLSMaxVersion = errors.New("unsupported tls_max_version") - errListenerUnsupportedTLSMinVersion = errors.New("unsupported tls_min_version") - - // This custom listener validation error is used to differentiate between an errListenerPortUnavailable because of - // direct port conflicts defined by the user (two listeners on the same port) vs a port conflict because we map - // privileged ports by adding the value passed into the gatewayClassConfig. - // (i.e. one listener on 80 with a privileged port mapping of 2000, and one listener on 2080 would conflict). - errListenerMappedToPrivilegedPortMapping = errors.New("listener conflicts with privileged port mapped by GatewayClassConfig privileged port mapping setting") -) - -// listenerValidationResult contains the result of internally validating a single listener -// as well as the result of validating it in relation to all its peers (via conflictedErr). -// an error set on any of its members corresponds to an error condition on the corresponding -// status type. -type listenerValidationResult struct { - // status type: Accepted - acceptedErr error - // status type: Conflicted - conflictedErr error - // status type: ResolvedRefs - refErrs []error - // status type: ResolvedRefs (but with internal validation) - routeKindErr error -} - -// programmedCondition constructs the condition for the Programmed status type. -// If there are no validation errors for the listener, we mark it as programmed. -// If there are validation errors for the listener, we mark it as invalid. -func (l listenerValidationResult) programmedCondition(generation int64) metav1.Condition { - now := timeFunc() - - switch { - case l.acceptedErr != nil, l.conflictedErr != nil, len(l.refErrs) != 0, l.routeKindErr != nil: - return metav1.Condition{ - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Invalid", - ObservedGeneration: generation, - Message: errListenerProgrammed_Invalid.Error(), - LastTransitionTime: now, - } - default: - return metav1.Condition{ - Type: "Programmed", - Status: metav1.ConditionTrue, - Reason: "Programmed", - ObservedGeneration: generation, - Message: "listener programmed", - LastTransitionTime: now, - } - } -} - -// acceptedCondition constructs the condition for the Accepted status type. -func (l listenerValidationResult) acceptedCondition(generation int64) metav1.Condition { - now := timeFunc() - switch l.acceptedErr { - case errListenerPortUnavailable, errListenerMappedToPrivilegedPortMapping: - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "PortUnavailable", - ObservedGeneration: generation, - Message: l.acceptedErr.Error(), - LastTransitionTime: now, - } - case errListenerUnsupportedProtocol: - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "UnsupportedProtocol", - ObservedGeneration: generation, - Message: l.acceptedErr.Error(), - LastTransitionTime: now, - } - case nil: - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: generation, - Message: "listener accepted", - LastTransitionTime: now, - } - default: - // falback to invalid - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "Invalid", - ObservedGeneration: generation, - Message: l.acceptedErr.Error(), - LastTransitionTime: now, - } - } -} - -// conflictedCondition constructs the condition for the Conflicted status type. -func (l listenerValidationResult) conflictedCondition(generation int64) metav1.Condition { - now := timeFunc() - - switch l.conflictedErr { - case errListenerProtocolConflict: - return metav1.Condition{ - Type: "Conflicted", - Status: metav1.ConditionTrue, - Reason: "ProtocolConflict", - ObservedGeneration: generation, - Message: l.conflictedErr.Error(), - LastTransitionTime: now, - } - case errListenerHostnameConflict: - return metav1.Condition{ - Type: "Conflicted", - Status: metav1.ConditionTrue, - Reason: "HostnameConflict", - ObservedGeneration: generation, - Message: l.conflictedErr.Error(), - LastTransitionTime: now, - } - default: - return metav1.Condition{ - Type: "Conflicted", - Status: metav1.ConditionFalse, - Reason: "NoConflicts", - ObservedGeneration: generation, - Message: "listener has no conflicts", - LastTransitionTime: now, - } - } -} - -// acceptedCondition constructs the condition for the ResolvedRefs status type. -func (l listenerValidationResult) resolvedRefsConditions(generation int64) []metav1.Condition { - now := timeFunc() - - conditions := make([]metav1.Condition, 0) - - if l.routeKindErr != nil { - return []metav1.Condition{{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidRouteKinds", - ObservedGeneration: generation, - Message: l.routeKindErr.Error(), - LastTransitionTime: now, - }} - } - - for _, refErr := range l.refErrs { - switch refErr { - case errListenerInvalidCertificateRef_NotFound, - errListenerInvalidCertificateRef_NotSupported, - errListenerInvalidCertificateRef_InvalidData, - errListenerInvalidCertificateRef_NonFIPSRSAKeyLen, - errListenerInvalidCertificateRef_FIPSRSAKeyLen: - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidCertificateRef", - ObservedGeneration: generation, - Message: refErr.Error(), - LastTransitionTime: now, - }) - case errListenerJWTProviderNotFound: - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidJWTProviderRef", - ObservedGeneration: generation, - Message: refErr.Error(), - LastTransitionTime: now, - }) - case errRefNotPermitted: - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "RefNotPermitted", - ObservedGeneration: generation, - Message: refErr.Error(), - LastTransitionTime: now, - }) - } - } - if len(conditions) == 0 { - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: generation, - Message: "resolved references", - LastTransitionTime: now, - }) - } - return conditions -} - -// Conditions constructs the entire set of conditions for a given gateway listener. -func (l listenerValidationResult) Conditions(generation int64) []metav1.Condition { - conditions := []metav1.Condition{ - l.acceptedCondition(generation), - l.programmedCondition(generation), - l.conflictedCondition(generation), - } - return append(conditions, l.resolvedRefsConditions(generation)...) -} - -// listenerValidationResults holds all of the results for a gateway's listeners -// the index of each result needs to correspond exactly to the index of the listener -// on the gateway spec for which it is describing. -type listenerValidationResults []listenerValidationResult - -// Invalid returns whether or not there is any listener that is not "Accepted" -// this is used in constructing a gateway's status where the Accepted status -// at the top-level can have a GatewayConditionReason of ListenersNotValid. -func (l listenerValidationResults) Invalid() bool { - for _, r := range l { - if r.acceptedErr != nil { - return true - } - } - return false -} - -// Conditions returns the listener conditions at a given index. -func (l listenerValidationResults) Conditions(generation int64, index int) []metav1.Condition { - result := l[index] - return result.Conditions(generation) -} - -var ( - // Each of the below are specified in the Gateway spec under GatewayConditionReason - // the general usage is that each error is specified as errGateway* where * corresponds - // to the GatewayConditionReason given in the spec. - errGatewayUnsupportedAddress = errors.New("gateway does not support specifying addresses") - errGatewayListenersNotValid = errors.New("one or more listeners are invalid") - errGatewayPending_Pods = errors.New("gateway pods are still being scheduled") - errGatewayPending_Consul = errors.New("gateway configuration is not yet synced to Consul") -) - -// gatewayValidationResult contains the result of internally validating a gateway. -// An error set on any of its members corresponds to an error condition on the corresponding -// status type. -type gatewayValidationResult struct { - acceptedErr error - programmedErr error -} - -// programmedCondition returns a condition for the Programmed status type. -func (l gatewayValidationResult) programmedCondition(generation int64) metav1.Condition { - now := timeFunc() - - switch l.programmedErr { - case errGatewayPending_Pods, errGatewayPending_Consul: - return metav1.Condition{ - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - ObservedGeneration: generation, - Message: l.programmedErr.Error(), - LastTransitionTime: now, - } - default: - return metav1.Condition{ - Type: "Programmed", - Status: metav1.ConditionTrue, - Reason: "Programmed", - ObservedGeneration: generation, - Message: "gateway programmed", - LastTransitionTime: now, - } - } -} - -// acceptedCondition returns a condition for the Accepted status type. It takes a boolean argument -// for whether or not any of the gateway's listeners are invalid, if they are, it overrides whatever -// Reason is set as an error on the result and instead uses the ListenersNotValid reason. -func (l gatewayValidationResult) acceptedCondition(generation int64, listenersInvalid bool) metav1.Condition { - now := timeFunc() - - if l.acceptedErr == nil { - if listenersInvalid { - return metav1.Condition{ - Type: "Accepted", - // should one invalid listener cause the entire gateway to become invalid? - Status: metav1.ConditionFalse, - Reason: "ListenersNotValid", - ObservedGeneration: generation, - Message: errGatewayListenersNotValid.Error(), - LastTransitionTime: now, - } - } - - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: generation, - Message: "gateway accepted", - LastTransitionTime: now, - } - } - - if l.acceptedErr == errGatewayUnsupportedAddress { - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "UnsupportedAddress", - ObservedGeneration: generation, - Message: l.acceptedErr.Error(), - LastTransitionTime: now, - } - } - - // fallback to Invalid reason - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "Invalid", - ObservedGeneration: generation, - Message: l.acceptedErr.Error(), - LastTransitionTime: now, - } -} - -// Conditions constructs the gateway conditions given whether its listeners are valid. -func (l gatewayValidationResult) Conditions(generation int64, listenersInvalid bool) []metav1.Condition { - return []metav1.Condition{ - l.acceptedCondition(generation, listenersInvalid), - l.programmedCondition(generation), - } -} - -type gatewayPolicyValidationResult struct { - acceptedErr error - resolvedRefsErrs []error -} - -type gatewayPolicyValidationResults []gatewayPolicyValidationResult - -var ( - errPolicyListenerReferenceDoesNotExist = errors.New("gateway policy references a listener that does not exist") - errPolicyJWTProvidersReferenceDoesNotExist = errors.New("gateway policy references one or more jwt providers that do not exist") - errNotAcceptedDueToInvalidRefs = errors.New("policy is not accepted due to errors with references") -) - -func (g gatewayPolicyValidationResults) Conditions(generation int64, idx int) []metav1.Condition { - result := g[idx] - return result.Conditions(generation) -} - -func (g gatewayPolicyValidationResult) Conditions(generation int64) []metav1.Condition { - return append([]metav1.Condition{g.acceptedCondition(generation)}, g.resolvedRefsConditions(generation)...) -} - -func (g gatewayPolicyValidationResult) acceptedCondition(generation int64) metav1.Condition { - now := timeFunc() - if g.acceptedErr != nil { - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "ReferencesNotValid", - ObservedGeneration: generation, - Message: g.acceptedErr.Error(), - LastTransitionTime: now, - } - } - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: generation, - Message: "gateway policy accepted", - LastTransitionTime: now, - } -} - -func (g gatewayPolicyValidationResult) resolvedRefsConditions(generation int64) []metav1.Condition { - now := timeFunc() - if len(g.resolvedRefsErrs) == 0 { - return []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: generation, - Message: "resolved references", - LastTransitionTime: now, - }, - } - } - - conditions := make([]metav1.Condition, 0, len(g.resolvedRefsErrs)) - for _, err := range g.resolvedRefsErrs { - switch { - case errors.Is(err, errPolicyListenerReferenceDoesNotExist): - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "MissingListenerReference", - ObservedGeneration: generation, - Message: err.Error(), - LastTransitionTime: now, - }) - case errors.Is(err, errPolicyJWTProvidersReferenceDoesNotExist): - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "MissingJWTProviderReference", - ObservedGeneration: generation, - Message: err.Error(), - LastTransitionTime: now, - }) - } - } - return conditions -} - -type authFilterValidationResults []authFilterValidationResult - -type authFilterValidationResult struct { - acceptedErr error - resolvedRefErr error -} - -var ( - errRouteFilterJWTProvidersReferenceDoesNotExist = errors.New("route filter references one or more jwt providers that do not exist") - errRouteFilterNotAcceptedDueToInvalidRefs = errors.New("route filter is not accepted due to errors with references") -) - -func (g authFilterValidationResults) Conditions(generation int64, idx int) []metav1.Condition { - result := g[idx] - return result.Conditions(generation) -} - -func (g authFilterValidationResult) Conditions(generation int64) []metav1.Condition { - return []metav1.Condition{ - g.acceptedCondition(generation), - g.resolvedRefsCondition(generation), - } -} - -func (g authFilterValidationResult) acceptedCondition(generation int64) metav1.Condition { - now := timeFunc() - if g.acceptedErr != nil { - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "ReferencesNotValid", - ObservedGeneration: generation, - Message: g.acceptedErr.Error(), - LastTransitionTime: now, - } - } - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: generation, - Message: "route auth filter accepted", - LastTransitionTime: now, - } -} - -func (g authFilterValidationResult) resolvedRefsCondition(generation int64) metav1.Condition { - now := timeFunc() - if g.resolvedRefErr == nil { - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: generation, - Message: "resolved references", - LastTransitionTime: now, - } - } - - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "MissingJWTProviderReference", - ObservedGeneration: generation, - Message: g.resolvedRefErr.Error(), - LastTransitionTime: now, - } -} diff --git a/control-plane/api-gateway/binding/result_test.go b/control-plane/api-gateway/binding/result_test.go deleted file mode 100644 index 327e1733ae..0000000000 --- a/control-plane/api-gateway/binding/result_test.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestBindResults_Condition(t *testing.T) { - testCases := []struct { - Name string - Results bindResults - Expected metav1.Condition - }{ - { - Name: "route successfully bound", - Results: bindResults{{section: "", err: nil}}, - Expected: metav1.Condition{Type: "Accepted", Status: "True", Reason: "Accepted", Message: "route accepted"}, - }, - { - Name: "multiple bind results", - Results: bindResults{ - {section: "abc", err: errRouteNoMatchingListenerHostname}, - {section: "def", err: errRouteNoMatchingParent}, - }, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NotAllowedByListeners", Message: "abc: listener cannot bind route with a non-aligned hostname; def: no matching parent"}, - }, - { - Name: "no matching listener hostname error", - Results: bindResults{{section: "abc", err: errRouteNoMatchingListenerHostname}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NoMatchingListenerHostname", Message: "abc: listener cannot bind route with a non-aligned hostname"}, - }, - { - Name: "ref not permitted error", - Results: bindResults{{section: "abc", err: errRefNotPermitted}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "RefNotPermitted", Message: "abc: reference not permitted due to lack of ReferenceGrant"}, - }, - { - Name: "no matching parent error", - Results: bindResults{{section: "hello1", err: errRouteNoMatchingParent}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NoMatchingParent", Message: "hello1: no matching parent"}, - }, - { - Name: "bind result without section name", - Results: bindResults{{section: "", err: errRouteNoMatchingParent}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NoMatchingParent", Message: "no matching parent"}, - }, - { - Name: "external filter ref not found", - Results: bindResults{{section: "", err: errExternalRefNotFound}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "FilterNotFound", Message: "ref not found"}, - }, - { - Name: "jwt provider referenced by external filter is not found", - Results: bindResults{{section: "", err: errFilterInvalid}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "JWTProviderNotFound", Message: "filter invalid"}, - }, - { - Name: "route references invalid filter type", - Results: bindResults{{section: "", err: errInvalidExternalRefType}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "UnsupportedValue", Message: "invalid externalref filter kind"}, - }, - { - Name: "unhandled error type", - Results: bindResults{{section: "abc", err: errors.New("you don't know me")}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NotAllowedByListeners", Message: "abc: you don't know me"}, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("%s_%s", t.Name(), tc.Name), func(t *testing.T) { - actual := tc.Results.Condition() - assert.Equalf(t, tc.Expected.Type, actual.Type, "expected condition with type %q but got %q", tc.Expected.Type, actual.Type) - assert.Equalf(t, tc.Expected.Status, actual.Status, "expected condition with status %q but got %q", tc.Expected.Status, actual.Status) - assert.Equalf(t, tc.Expected.Reason, actual.Reason, "expected condition with reason %q but got %q", tc.Expected.Reason, actual.Reason) - assert.Equalf(t, tc.Expected.Message, actual.Message, "expected condition with message %q but got %q", tc.Expected.Message, actual.Message) - }) - } -} - -func TestGatewayPolicyValidationResult_Conditions(t *testing.T) { - t.Parallel() - var generation int64 = 5 - for name, tc := range map[string]struct { - results gatewayPolicyValidationResult - expected []metav1.Condition - }{ - "policy valid": { - results: gatewayPolicyValidationResult{}, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "Accepted", - Message: "gateway policy accepted", - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - "errors with JWT references": { - results: gatewayPolicyValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{errorForMissingJWTProviders(map[string]struct{}{"okta": {}})}, - }, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ReferencesNotValid", - Message: errNotAcceptedDueToInvalidRefs.Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingJWTProviderReference", - Message: errorForMissingJWTProviders(map[string]struct{}{"okta": {}}).Error(), - }, - }, - }, - "errors with listener references": { - results: gatewayPolicyValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{errorForMissingListener("gw", "l1")}, - }, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ReferencesNotValid", - Message: errNotAcceptedDueToInvalidRefs.Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingListenerReference", - Message: errorForMissingListener("gw", "l1").Error(), - }, - }, - }, - "errors with listener and jwt references": { - results: gatewayPolicyValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{ - errorForMissingJWTProviders(map[string]struct{}{"okta": {}}), - errorForMissingListener("gw", "l1"), - }, - }, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ReferencesNotValid", - Message: errNotAcceptedDueToInvalidRefs.Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingJWTProviderReference", - Message: errorForMissingJWTProviders(map[string]struct{}{"okta": {}}).Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingListenerReference", - Message: errorForMissingListener("gw", "l1").Error(), - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - require.EqualValues(t, tc.expected, tc.results.Conditions(generation)) - }) - } -} - -func TestAuthFilterValidationResult_Conditions(t *testing.T) { - t.Parallel() - var generation int64 = 5 - for name, tc := range map[string]struct { - results authFilterValidationResult - expected []metav1.Condition - }{ - "policy valid": { - results: authFilterValidationResult{}, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "Accepted", - Message: "route auth filter accepted", - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - "errors with JWT references": { - results: authFilterValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefErr: fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta"), - }, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ReferencesNotValid", - Message: errNotAcceptedDueToInvalidRefs.Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingJWTProviderReference", - Message: fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta").Error(), - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - require.EqualValues(t, tc.expected, tc.results.Conditions(generation)) - }) - } -} diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go deleted file mode 100644 index a2a1c49754..0000000000 --- a/control-plane/api-gateway/binding/route_binding.go +++ /dev/null @@ -1,528 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "fmt" - "strings" - - mapset "github.com/deckarep/golang-set" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" -) - -// bindRoute contains the main logic for binding a route to a given gateway. -func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.SectionName]int, snapshot *Snapshot) { - // use the non-normalized key since we can't write back enterprise metadata - // on non-enterprise installations - routeConsulKey := r.config.Translator.NonNormalizedConfigEntryReference(entryKind(route), client.ObjectKeyFromObject(route)) - filteredParents := filterParentRefs(r.key, route.GetNamespace(), getRouteParents(route)) - filteredParentStatuses := filterParentRefs(r.key, route.GetNamespace(), - common.ConvertSliceFunc(getRouteParentsStatus(route), func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { - return parentStatus.ParentRef - }), - ) - - // flags to mark that some operation needs to occur - kubernetesNeedsUpdate := false - kubernetesNeedsStatusUpdate := false - - // we do this in a closure at the end to make sure we don't accidentally - // add something multiple times into the list of update/delete operations - // instead we just set a flag indicating that an update is needed and then - // append to the snapshot right before returning - defer func() { - if kubernetesNeedsUpdate { - snapshot.Kubernetes.Updates.Add(route) - } - if kubernetesNeedsStatusUpdate { - snapshot.Kubernetes.StatusUpdates.Add(route) - } - }() - - if isDeleted(route) { - // mark the route as needing to get cleaned up if we detect that it's being deleted - if common.RemoveFinalizer(route) { - kubernetesNeedsUpdate = true - } - return - } - - if r.isGatewayDeleted() { - if canGCOnUnbind(routeConsulKey, r.config.Resources) && common.RemoveFinalizer(route) { - kubernetesNeedsUpdate = true - } else { - // Remove the condition since we no longer know if we should - // control the route and drop any references for the Consul route. - // This only gets run if we can't GC the route at the end of this - // loop. - r.dropConsulRouteParent(snapshot, route, r.nonNormalizedConsulKey, r.config.Resources) - } - - // drop the status conditions - if r.statusSetter.removeRouteReferences(route, filteredParentStatuses) { - kubernetesNeedsStatusUpdate = true - } - if r.statusSetter.removeRouteReferences(route, filteredParents) { - kubernetesNeedsStatusUpdate = true - } - return - } - - if common.EnsureFinalizer(route) { - kubernetesNeedsUpdate = true - return - } - - validation := validateRefs(route, getRouteBackends(route), r.config.Resources) - // the spec is dumb and makes you set a parent for any status, even when the - // status is not with respect to a parent, as is the case of resolved refs - // so we need to set the status on all parents - for _, parent := range filteredParents { - if r.statusSetter.setRouteCondition(route, &parent, validation.Condition()) { - kubernetesNeedsStatusUpdate = true - } - } - // if we're orphaned from this gateway we'll - // always need a status update. - if len(filteredParents) == 0 { - // we already checked that these refs existed, so no need to check - // the return value here. - _ = r.statusSetter.removeRouteReferences(route, filteredParentStatuses) - kubernetesNeedsStatusUpdate = true - } - - namespace := r.config.Namespaces[route.GetNamespace()] - groupKind := route.GetObjectKind().GroupVersionKind().GroupKind() - - var results parentBindResults - - for _, ref := range filteredParents { - var result bindResults - - listeners := listenersFor(&r.config.Gateway, ref.SectionName) - - // If there are no matching listeners, then we failed to find the parent - if len(listeners) == 0 { - var sectionName gwv1beta1.SectionName - if ref.SectionName != nil { - sectionName = *ref.SectionName - } - - result = append(result, bindResult{ - section: sectionName, - err: errRouteNoMatchingParent, - }) - } - - for _, listener := range listeners { - if !routeKindIsAllowedForListener(supportedKindsForProtocol[listener.Protocol], groupKind) { - result = append(result, bindResult{ - section: listener.Name, - err: errRouteNotAllowedByListeners_Protocol, - }) - continue - } - - if !routeKindIsAllowedForListenerExplicit(listener.AllowedRoutes, groupKind) { - result = append(result, bindResult{ - section: listener.Name, - err: errRouteNotAllowedByListeners_Protocol, - }) - continue - } - - if !routeAllowedForListenerNamespaces(r.config.Gateway.Namespace, listener.AllowedRoutes, namespace) { - result = append(result, bindResult{ - section: listener.Name, - err: errRouteNotAllowedByListeners_Namespace, - }) - continue - } - - if !routeAllowedForListenerHostname(listener.Hostname, getRouteHostnames(route)) { - result = append(result, bindResult{ - section: listener.Name, - err: errRouteNoMatchingListenerHostname, - }) - continue - } - - result = append(result, bindResult{ - section: listener.Name, - }) - - boundCount[listener.Name]++ - } - - results = append(results, parentBindResult{ - parent: ref, - results: result, - }) - - httproute, ok := route.(*gwv1beta1.HTTPRoute) - if ok { - if !externalRefsOnRouteAllExist(httproute, r.config.Resources) { - results = append(results, parentBindResult{ - parent: ref, - results: []bindResult{ - { - err: errExternalRefNotFound, - }, - }, - }) - } - - if invalidFilterNames := authFilterReferencesMissingJWTProvider(httproute, r.config.Resources); len(invalidFilterNames) > 0 { - results = append(results, parentBindResult{ - parent: ref, - results: []bindResult{ - { - err: fmt.Errorf("%w: %s", errFilterInvalid, strings.Join(invalidFilterNames, ",")), - }, - }, - }) - } - - if !externalRefsKindAllowedOnRoute(httproute) { - results = append(results, parentBindResult{ - parent: ref, - results: []bindResult{ - { - err: errInvalidExternalRefType, - }, - }, - }) - } - } - } - - updated := false - for _, result := range results { - if r.statusSetter.setRouteCondition(route, &result.parent, result.results.Condition()) { - updated = true - } - } - - if updated { - kubernetesNeedsStatusUpdate = true - } - - r.mutateRouteWithBindingResults(snapshot, route, r.nonNormalizedConsulKey, r.config.Resources, results) -} - -// filterParentRefs returns the subset of parent references on a route that point to the given gateway. -func filterParentRefs(gateway types.NamespacedName, namespace string, refs []gwv1beta1.ParentReference) []gwv1beta1.ParentReference { - references := []gwv1beta1.ParentReference{} - for _, ref := range refs { - if common.NilOrEqual(ref.Group, common.BetaGroup) && - common.NilOrEqual(ref.Kind, common.KindGateway) && - gateway.Namespace == common.ValueOr(ref.Namespace, namespace) && - gateway.Name == string(ref.Name) { - references = append(references, ref) - } - } - - return references -} - -// listenersFor returns the listeners corresponding to the given section name. If the section -// name is actually specified, the returned set will only contain the named listener. If it is -// unspecified, then all gateway listeners will be returned. -func listenersFor(gateway *gwv1beta1.Gateway, name *gwv1beta1.SectionName) []gwv1beta1.Listener { - listeners := []gwv1beta1.Listener{} - for _, listener := range gateway.Spec.Listeners { - if name == nil { - listeners = append(listeners, listener) - continue - } - if listener.Name == *name { - listeners = append(listeners, listener) - } - } - return listeners -} - -func consulParentMatches(namespace string, gatewayKey api.ResourceReference, parent api.ResourceReference) bool { - gatewayKey = common.NormalizeMeta(gatewayKey) - - if parent.Namespace == "" { - parent.Namespace = namespace - } - if parent.Kind == "" { - parent.Kind = api.APIGateway - } - - parent = common.NormalizeMeta(parent) - - return parent.Kind == api.APIGateway && - parent.Name == gatewayKey.Name && - parent.Namespace == gatewayKey.Namespace && - parent.Partition == gatewayKey.Partition -} - -func (r *Binder) dropConsulRouteParent(snapshot *Snapshot, object client.Object, gateway api.ResourceReference, resources *common.ResourceMap) { - switch object.(type) { - case *gwv1beta1.HTTPRoute: - resources.MutateHTTPRoute(client.ObjectKeyFromObject(object), r.handleRouteSyncStatus(snapshot, object), func(entry api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry { - entry.Parents = common.Filter(entry.Parents, func(parent api.ResourceReference) bool { - return consulParentMatches(entry.Namespace, gateway, parent) - }) - return entry - }) - case *gwv1alpha2.TCPRoute: - resources.MutateTCPRoute(client.ObjectKeyFromObject(object), r.handleRouteSyncStatus(snapshot, object), func(entry api.TCPRouteConfigEntry) api.TCPRouteConfigEntry { - entry.Parents = common.Filter(entry.Parents, func(parent api.ResourceReference) bool { - return consulParentMatches(entry.Namespace, gateway, parent) - }) - return entry - }) - } -} - -func (r *Binder) mutateRouteWithBindingResults(snapshot *Snapshot, object client.Object, gatewayConsulKey api.ResourceReference, resources *common.ResourceMap, results parentBindResults) { - if results.boundSections().Cardinality() == 0 { - r.dropConsulRouteParent(snapshot, object, r.nonNormalizedConsulKey, r.config.Resources) - return - } - - key := client.ObjectKeyFromObject(object) - - parents := mapset.NewSet() - // the normalized set keeps us from accidentally adding the same thing - // twice due to the Consul server normalizing our refs. - normalized := make(map[api.ResourceReference]api.ResourceReference) - for section := range results.boundSections().Iter() { - ref := api.ResourceReference{ - Kind: api.APIGateway, - Name: gatewayConsulKey.Name, - SectionName: section.(string), - Namespace: gatewayConsulKey.Namespace, - Partition: gatewayConsulKey.Partition, - } - parents.Add(ref) - normalized[common.NormalizeMeta(ref)] = ref - } - - switch object.(type) { - case *gwv1beta1.HTTPRoute: - resources.TranslateAndMutateHTTPRoute(key, r.handleRouteSyncStatus(snapshot, object), func(old *api.HTTPRouteConfigEntry, new api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry { - if old != nil { - for _, parent := range old.Parents { - // drop any references that already exist - if parents.Contains(parent) { - parents.Remove(parent) - } - if id, ok := normalized[parent]; ok { - parents.Remove(id) - } - } - - // set the old parent states - new.Parents = old.Parents - new.Status = old.Status - } - // and now add what is left - for parent := range parents.Iter() { - new.Parents = append(new.Parents, parent.(api.ResourceReference)) - } - - return new - }) - case *gwv1alpha2.TCPRoute: - resources.TranslateAndMutateTCPRoute(key, r.handleRouteSyncStatus(snapshot, object), func(old *api.TCPRouteConfigEntry, new api.TCPRouteConfigEntry) api.TCPRouteConfigEntry { - if old != nil { - for _, parent := range old.Parents { - // drop any references that already exist - if parents.Contains(parent) { - parents.Remove(parent) - } - } - - // set the old parent states - new.Parents = old.Parents - new.Status = old.Status - } - // and now add what is left - for parent := range parents.Iter() { - new.Parents = append(new.Parents, parent.(api.ResourceReference)) - } - return new - }) - } -} - -func entryKind(object client.Object) string { - switch object.(type) { - case *gwv1beta1.HTTPRoute: - return api.HTTPRoute - case *gwv1alpha2.TCPRoute: - return api.TCPRoute - } - return "" -} - -func canGCOnUnbind(id api.ResourceReference, resources *common.ResourceMap) bool { - switch id.Kind { - case api.HTTPRoute: - return resources.CanGCHTTPRouteOnUnbind(id) - case api.TCPRoute: - return resources.CanGCTCPRouteOnUnbind(id) - } - return true -} - -func getRouteHostnames(object client.Object) []gwv1beta1.Hostname { - switch v := object.(type) { - case *gwv1beta1.HTTPRoute: - return v.Spec.Hostnames - } - return nil -} - -func getRouteParents(object client.Object) []gwv1beta1.ParentReference { - switch v := object.(type) { - case *gwv1beta1.HTTPRoute: - return v.Spec.ParentRefs - case *gwv1alpha2.TCPRoute: - return v.Spec.ParentRefs - } - return nil -} - -func getRouteParentsStatus(object client.Object) []gwv1beta1.RouteParentStatus { - switch v := object.(type) { - case *gwv1beta1.HTTPRoute: - return v.Status.RouteStatus.Parents - case *gwv1alpha2.TCPRoute: - return v.Status.RouteStatus.Parents - } - return nil -} - -func setRouteParentsStatus(object client.Object, parents []gwv1beta1.RouteParentStatus) { - switch v := object.(type) { - case *gwv1beta1.HTTPRoute: - v.Status.RouteStatus.Parents = parents - case *gwv1alpha2.TCPRoute: - v.Status.RouteStatus.Parents = parents - } -} - -func getRouteBackends(object client.Object) []gwv1beta1.BackendRef { - switch v := object.(type) { - case *gwv1beta1.HTTPRoute: - return common.Flatten(common.ConvertSliceFunc(v.Spec.Rules, func(rule gwv1beta1.HTTPRouteRule) []gwv1beta1.BackendRef { - return common.ConvertSliceFunc(rule.BackendRefs, func(rule gwv1beta1.HTTPBackendRef) gwv1beta1.BackendRef { - return rule.BackendRef - }) - })) - case *gwv1alpha2.TCPRoute: - return common.Flatten(common.ConvertSliceFunc(v.Spec.Rules, func(rule gwv1alpha2.TCPRouteRule) []gwv1beta1.BackendRef { - return rule.BackendRefs - })) - } - return nil -} - -func canReferenceBackend(object client.Object, ref gwv1beta1.BackendRef, resources *common.ResourceMap) bool { - switch v := object.(type) { - case *gwv1beta1.HTTPRoute: - return resources.HTTPRouteCanReferenceBackend(*v, ref) - case *gwv1alpha2.TCPRoute: - return resources.TCPRouteCanReferenceBackend(*v, ref) - } - return false -} - -func (r *Binder) handleRouteSyncStatus(snapshot *Snapshot, object client.Object) func(error, api.ConfigEntryStatus) { - return func(err error, status api.ConfigEntryStatus) { - condition := metav1.Condition{ - Type: "Synced", - Status: metav1.ConditionTrue, - ObservedGeneration: object.GetGeneration(), - LastTransitionTime: timeFunc(), - Reason: "Synced", - Message: "route synced to Consul", - } - if err != nil { - condition = metav1.Condition{ - Type: "Synced", - Status: metav1.ConditionFalse, - ObservedGeneration: object.GetGeneration(), - LastTransitionTime: timeFunc(), - Reason: "SyncError", - Message: err.Error(), - } - } - if r.statusSetter.setRouteConditionOnAllRefs(object, condition) { - snapshot.Kubernetes.StatusUpdates.Add(object) - } - if consulCondition := consulCondition(object.GetGeneration(), status); consulCondition != nil { - if r.statusSetter.setRouteConditionOnAllRefs(object, *consulCondition) { - snapshot.Kubernetes.StatusUpdates.Add(object) - } - } - } -} - -func (r *Binder) handleGatewaySyncStatus(snapshot *Snapshot, gateway *gwv1beta1.Gateway, status api.ConfigEntryStatus) func(error) { - return func(err error) { - condition := metav1.Condition{ - Type: "Synced", - Status: metav1.ConditionTrue, - ObservedGeneration: gateway.Generation, - LastTransitionTime: timeFunc(), - Reason: "Synced", - Message: "gateway synced to Consul", - } - if err != nil { - condition = metav1.Condition{ - Type: "Synced", - Status: metav1.ConditionFalse, - ObservedGeneration: gateway.Generation, - LastTransitionTime: timeFunc(), - Reason: "SyncError", - Message: err.Error(), - } - } - - if conditions, updated := setCondition(gateway.Status.Conditions, condition); updated { - gateway.Status.Conditions = conditions - snapshot.Kubernetes.StatusUpdates.Add(gateway) - } - - if consulCondition := consulCondition(gateway.Generation, status); consulCondition != nil { - if conditions, updated := setCondition(gateway.Status.Conditions, *consulCondition); updated { - gateway.Status.Conditions = conditions - snapshot.Kubernetes.StatusUpdates.Add(gateway) - } - } - } -} - -func consulCondition(generation int64, status api.ConfigEntryStatus) *metav1.Condition { - for _, c := range status.Conditions { - // we only care about the top-level status that isn't in reference - // to a resource. - if c.Type == "Accepted" && (c.Resource == nil || c.Resource.Name == "") { - return &metav1.Condition{ - Type: "ConsulAccepted", - Reason: c.Reason, - Status: metav1.ConditionStatus(c.Status), - Message: c.Message, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - } - } - } - return nil -} diff --git a/control-plane/api-gateway/binding/setter.go b/control-plane/api-gateway/binding/setter.go deleted file mode 100644 index 5b3a9096d6..0000000000 --- a/control-plane/api-gateway/binding/setter.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// setter wraps the status setting logic for routes. -type setter struct { - controllerName string -} - -// newSetter constructs a status setter with the given controller name. -func newSetter(controllerName string) *setter { - return &setter{controllerName: controllerName} -} - -// removeRouteReferences removes the given parent reference sections from a routes's status. -func (s *setter) removeRouteReferences(route client.Object, refs []gwv1beta1.ParentReference) bool { - modified := false - for _, parent := range refs { - parents, removed := s.removeParentStatus(getRouteParentsStatus(route), parent) - setRouteParentsStatus(route, parents) - if removed { - modified = true - } - } - return modified -} - -// setRouteCondition sets an route condition on its status with the given parent. -func (s *setter) setRouteCondition(route client.Object, parent *gwv1beta1.ParentReference, condition metav1.Condition) bool { - condition.LastTransitionTime = timeFunc() - condition.ObservedGeneration = route.GetGeneration() - - parents := getRouteParentsStatus(route) - status := s.getParentStatus(parents, parent) - conditions, modified := setCondition(status.Conditions, condition) - if modified { - status.Conditions = conditions - setRouteParentsStatus(route, s.setParentStatus(parents, status)) - } - return modified -} - -// setRouteConditionOnAllRefs sets an route condition and its status on all parents. -func (s *setter) setRouteConditionOnAllRefs(route client.Object, condition metav1.Condition) bool { - condition.LastTransitionTime = timeFunc() - condition.ObservedGeneration = route.GetGeneration() - - parents := getRouteParentsStatus(route) - statuses := common.Filter(getRouteParentsStatus(route), func(status gwv1beta1.RouteParentStatus) bool { - return string(status.ControllerName) != s.controllerName - }) - - updated := false - for _, status := range statuses { - conditions, modified := setCondition(status.Conditions, condition) - if modified { - updated = true - status.Conditions = conditions - setRouteParentsStatus(route, s.setParentStatus(parents, status)) - } - } - return updated -} - -// getParentStatus returns the section of a status referenced by the given parent reference. -func (s *setter) getParentStatus(statuses []gwv1beta1.RouteParentStatus, parent *gwv1beta1.ParentReference) gwv1beta1.RouteParentStatus { - var parentRef gwv1beta1.ParentReference - if parent != nil { - parentRef = *parent - } - - for _, status := range statuses { - if common.ParentsEqual(status.ParentRef, parentRef) && string(status.ControllerName) == s.controllerName { - return status - } - } - return gwv1beta1.RouteParentStatus{ - ParentRef: parentRef, - ControllerName: gwv1beta1.GatewayController(s.controllerName), - } -} - -// removeParentStatus removes the section of a status referenced by the given parent reference. -func (s *setter) removeParentStatus(statuses []gwv1beta1.RouteParentStatus, parent gwv1beta1.ParentReference) ([]gwv1beta1.RouteParentStatus, bool) { - found := false - filtered := []gwv1beta1.RouteParentStatus{} - for _, status := range statuses { - if common.ParentsEqual(status.ParentRef, parent) && string(status.ControllerName) == s.controllerName { - found = true - continue - } - filtered = append(filtered, status) - } - return filtered, found -} - -// setCondition overrides or appends a condition to the list of conditions, returning if a modification -// to the condition set was made or not. Modifications only occur if a field other than the observation -// timestamp is modified. -func setCondition(conditions []metav1.Condition, condition metav1.Condition) ([]metav1.Condition, bool) { - for i, existing := range conditions { - if existing.Type == condition.Type { - // no-op if we have the exact same thing - if condition.Reason == existing.Reason && condition.Message == existing.Message && condition.ObservedGeneration == existing.ObservedGeneration { - return conditions, false - } - - conditions[i] = condition - return conditions, true - } - } - return append(conditions, condition), true -} - -// setParentStatus updates or inserts the set of parent statuses with the newly modified parent. -func (s *setter) setParentStatus(statuses []gwv1beta1.RouteParentStatus, parent gwv1beta1.RouteParentStatus) []gwv1beta1.RouteParentStatus { - for i, status := range statuses { - if common.ParentsEqual(status.ParentRef, parent.ParentRef) && status.ControllerName == parent.ControllerName { - statuses[i] = parent - return statuses - } - } - return append(statuses, parent) -} diff --git a/control-plane/api-gateway/binding/setter_test.go b/control-plane/api-gateway/binding/setter_test.go deleted file mode 100644 index 84d3ecc7d5..0000000000 --- a/control-plane/api-gateway/binding/setter_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "testing" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestSetter(t *testing.T) { - setter := newSetter("test") - parentRef := gwv1beta1.ParentReference{ - Name: "test", - } - parentRefDup := gwv1beta1.ParentReference{ - Name: "test", - } - condition := metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "route accepted", - } - route := &gwv1beta1.HTTPRoute{ - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{parentRef}, - }, - }, - } - require.True(t, setter.setRouteCondition(route, &parentRef, condition)) - require.False(t, setter.setRouteCondition(route, &parentRefDup, condition)) - require.False(t, setter.setRouteCondition(route, &parentRefDup, condition)) - require.False(t, setter.setRouteCondition(route, &parentRefDup, condition)) - - require.Len(t, route.Status.Parents, 1) - require.Len(t, route.Status.Parents[0].Conditions, 1) -} diff --git a/control-plane/api-gateway/binding/snapshot.go b/control-plane/api-gateway/binding/snapshot.go deleted file mode 100644 index 18888a6d46..0000000000 --- a/control-plane/api-gateway/binding/snapshot.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" -) - -// KubernetesSnapshot contains all the operations -// required in Kubernetes to complete reconciliation. -type KubernetesSnapshot struct { - // Updates is the list of objects that need to have - // aspects of their metadata or spec updated in Kubernetes - // (i.e. for finalizers or annotations) - Updates *common.KubernetesUpdates - // StatusUpdates is the list of objects that need - // to have their statuses updated in Kubernetes - StatusUpdates *common.KubernetesUpdates -} - -// ConsulSnapshot contains all the operations required -// in Consul to complete reconciliation. -type ConsulSnapshot struct { - // Updates is the list of ConfigEntry objects that should - // either be updated or created in Consul - Updates []*common.ConsulUpdateOperation - // Deletions is a list of references that ought to be - // deleted in Consul - Deletions []api.ResourceReference - // Registrations is a list of Consul services to make sure - // are registered in Consul - Registrations []api.CatalogRegistration - // Deregistrations is a list of Consul services to make sure - // are no longer registered in Consul - Deregistrations []api.CatalogDeregistration -} - -// Snapshot contains all Kubernetes and Consul operations -// needed to complete reconciliation. -type Snapshot struct { - // Kubernetes holds the snapshot of required Kubernetes operations - Kubernetes *KubernetesSnapshot - // Consul holds the snapshot of required Consul operations - Consul *ConsulSnapshot - // GatewayClassConfig is the configuration to use for determining - // a Gateway deployment, if it is not set, a deployment should be - // deleted instead of updated - GatewayClassConfig *v1alpha1.GatewayClassConfig - - // UpsertGatewayDeployment determines whether the gateway deployment - // objects should be updated, i.e. deployments, roles, services - UpsertGatewayDeployment bool -} - -func NewSnapshot() *Snapshot { - return &Snapshot{ - Kubernetes: &KubernetesSnapshot{ - Updates: common.NewKubernetesUpdates(), - StatusUpdates: common.NewKubernetesUpdates(), - }, - Consul: &ConsulSnapshot{}, - } -} diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go deleted file mode 100644 index ea9208f150..0000000000 --- a/control-plane/api-gateway/binding/validation.go +++ /dev/null @@ -1,730 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "fmt" - "strings" - - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - klabels "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/version" -) - -var ( - // the list of kinds we can support by listener protocol. - supportedKindsForProtocol = map[gwv1beta1.ProtocolType][]gwv1beta1.RouteGroupKind{ - gwv1beta1.HTTPProtocolType: {{ - Group: (*gwv1beta1.Group)(&gwv1beta1.GroupVersion.Group), - Kind: "HTTPRoute", - }}, - gwv1beta1.HTTPSProtocolType: {{ - Group: (*gwv1beta1.Group)(&gwv1beta1.GroupVersion.Group), - Kind: "HTTPRoute", - }}, - gwv1beta1.TCPProtocolType: {{ - Group: (*gwv1alpha2.Group)(&gwv1alpha2.GroupVersion.Group), - Kind: "TCPRoute", - }}, - } - allSupportedRouteKinds = map[gwv1beta1.Kind]struct{}{ - gwv1beta1.Kind("HTTPRoute"): {}, - gwv1beta1.Kind("TCPRoute"): {}, - } - - allSupportedTLSVersions = map[string]struct{}{ - "TLS_AUTO": {}, - "TLSv1_0": {}, - "TLSv1_1": {}, - "TLSv1_2": {}, - "TLSv1_3": {}, - } - - allTLSVersionsWithConfigurableCipherSuites = map[string]struct{}{ - // Remove "" and "TLS_AUTO" if Envoy ever sets TLS 1.3 as default minimum - "": {}, - "TLS_AUTO": {}, - "TLSv1_0": {}, - "TLSv1_1": {}, - "TLSv1_2": {}, - } - - allSupportedTLSCipherSuites = map[string]struct{}{ - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": {}, - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": {}, - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": {}, - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": {}, - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": {}, - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": {}, - - // NOTE: the following cipher suites are currently supported by Envoy - // but have been identified as insecure and are pending removal - // https://github.com/envoyproxy/envoy/issues/5399 - "TLS_RSA_WITH_AES_128_GCM_SHA256": {}, - "TLS_RSA_WITH_AES_128_CBC_SHA": {}, - "TLS_RSA_WITH_AES_256_GCM_SHA384": {}, - "TLS_RSA_WITH_AES_256_CBC_SHA": {}, - // https://github.com/envoyproxy/envoy/issues/5400 - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": {}, - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": {}, - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": {}, - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": {}, - } -) - -// validateRefs validates backend references for a route, determining whether or -// not they were found in the list of known connect-injected services. -func validateRefs(route client.Object, refs []gwv1beta1.BackendRef, resources *common.ResourceMap) routeValidationResults { - namespace := route.GetNamespace() - - var result routeValidationResults - for _, ref := range refs { - backendRef := ref.BackendObjectReference - - nsn := types.NamespacedName{ - Name: string(backendRef.Name), - Namespace: common.ValueOr(backendRef.Namespace, namespace), - } - - isServiceRef := common.NilOrEqual(backendRef.Group, "") && common.NilOrEqual(backendRef.Kind, common.KindService) - isMeshServiceRef := common.DerefEqual(backendRef.Group, v1alpha1.ConsulHashicorpGroup) && common.DerefEqual(backendRef.Kind, v1alpha1.MeshServiceKind) - - if !isServiceRef && !isMeshServiceRef { - result = append(result, routeValidationResult{ - namespace: nsn.Namespace, - backend: ref, - err: errRouteInvalidKind, - }) - continue - } - - if isServiceRef && !resources.HasService(nsn) { - result = append(result, routeValidationResult{ - namespace: nsn.Namespace, - backend: ref, - err: errRouteBackendNotFound, - }) - continue - } - - if isMeshServiceRef && !resources.HasMeshService(nsn) { - result = append(result, routeValidationResult{ - namespace: nsn.Namespace, - backend: ref, - err: errRouteBackendNotFound, - }) - continue - } - - if !canReferenceBackend(route, ref, resources) { - result = append(result, routeValidationResult{ - namespace: nsn.Namespace, - backend: ref, - err: errRefNotPermitted, - }) - continue - } - - result = append(result, routeValidationResult{ - namespace: nsn.Namespace, - backend: ref, - }) - } - return result -} - -// validateGateway validates that a gateway is semantically valid given -// the set of features that we support. -func validateGateway(gateway gwv1beta1.Gateway, pods []corev1.Pod, consulGateway *api.APIGatewayConfigEntry) gatewayValidationResult { - var result gatewayValidationResult - - if len(gateway.Spec.Addresses) > 0 { - result.acceptedErr = errGatewayUnsupportedAddress - } - - if len(pods) == 0 { - result.programmedErr = errGatewayPending_Pods - } else if consulGateway == nil { - result.programmedErr = errGatewayPending_Consul - } - - return result -} - -func validateGatewayPolicies(gateway gwv1beta1.Gateway, policies []v1alpha1.GatewayPolicy, resources *common.ResourceMap) gatewayPolicyValidationResults { - results := make(gatewayPolicyValidationResults, 0, len(policies)) - - for _, policy := range policies { - result := gatewayPolicyValidationResult{ - resolvedRefsErrs: []error{}, - } - - exists := listenerExistsForPolicy(gateway, policy) - if !exists { - result.resolvedRefsErrs = append(result.resolvedRefsErrs, errorForMissingListener(policy.Spec.TargetRef.Name, string(*policy.Spec.TargetRef.SectionName))) - } - - missingJWTProviders := make(map[string]struct{}) - if policy.Spec.Override != nil && policy.Spec.Override.JWT != nil { - for _, policyJWTProvider := range policy.Spec.Override.JWT.Providers { - _, jwtExists := resources.GetJWTProviderForGatewayJWTProvider(policyJWTProvider) - if !jwtExists { - missingJWTProviders[policyJWTProvider.Name] = struct{}{} - } - } - } - - if policy.Spec.Default != nil && policy.Spec.Default.JWT != nil { - for _, policyJWTProvider := range policy.Spec.Default.JWT.Providers { - _, jwtExists := resources.GetJWTProviderForGatewayJWTProvider(policyJWTProvider) - if !jwtExists { - missingJWTProviders[policyJWTProvider.Name] = struct{}{} - } - } - } - - if len(missingJWTProviders) > 0 { - result.resolvedRefsErrs = append(result.resolvedRefsErrs, errorForMissingJWTProviders(missingJWTProviders)) - } - - if len(result.resolvedRefsErrs) > 0 { - result.acceptedErr = errNotAcceptedDueToInvalidRefs - } - results = append(results, result) - - } - return results -} - -func listenerExistsForPolicy(gateway gwv1beta1.Gateway, policy v1alpha1.GatewayPolicy) bool { - return gateway.Name == policy.Spec.TargetRef.Name && - slices.ContainsFunc(gateway.Spec.Listeners, func(l gwv1beta1.Listener) bool { return l.Name == *policy.Spec.TargetRef.SectionName }) -} - -func errorForMissingListener(name, listenerName string) error { - return fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, name, listenerName) -} - -func errorForMissingJWTProviders(names map[string]struct{}) error { - namesList := make([]string, 0, len(names)) - for name := range names { - namesList = append(namesList, name) - } - slices.Sort(namesList) - mergedNames := strings.Join(namesList, ",") - return fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, mergedNames) -} - -// mergedListener associates a listener with its indexed position -// in the gateway spec, it's used to re-associate a status with -// a listener after we merge compatible listeners together and then -// validate their conflicts. -type mergedListener struct { - index int - listener gwv1beta1.Listener -} - -// mergedListeners is a set of a listeners that are considered "merged" -// due to referencing the same listener port. -type mergedListeners []mergedListener - -// validateProtocol validates that the protocols used across all merged -// listeners are compatible. -func (m mergedListeners) validateProtocol() error { - var protocol *gwv1beta1.ProtocolType - for _, l := range m { - if protocol == nil { - protocol = common.PointerTo(l.listener.Protocol) - } - if *protocol != l.listener.Protocol { - return errListenerProtocolConflict - } - } - return nil -} - -// validateHostname validates that the merged listeners don't use the same -// hostnames as per the spec. -func (m mergedListeners) validateHostname(index int, listener gwv1beta1.Listener) error { - for _, l := range m { - if l.index == index { - continue - } - if common.BothNilOrEqual(listener.Hostname, l.listener.Hostname) { - return errListenerHostnameConflict - } - } - return nil -} - -// validateTLS validates that the TLS configuration for a given listener is valid and that -// the certificates that it references exist. -func validateTLS(gateway gwv1beta1.Gateway, tls *gwv1beta1.GatewayTLSConfig, resources *common.ResourceMap) (error, error) { - // If there's no TLS, there's nothing to validate - if tls == nil { - return nil, nil - } - - // Validate the certificate references and then return any error - // alongside any TLS configuration error that we find below. - refsErr := validateCertificateRefs(gateway, tls.CertificateRefs, resources) - - if tls.Mode != nil && *tls.Mode == gwv1beta1.TLSModePassthrough { - return errListenerNoTLSPassthrough, refsErr - } - - if err := validateTLSOptions(tls.Options); err != nil { - return err, refsErr - } - - return nil, refsErr -} - -func validateJWT(gateway gwv1beta1.Gateway, listener gwv1beta1.Listener, resources *common.ResourceMap) error { - policy, _ := resources.GetPolicyForGatewayListener(gateway, listener) - if policy == nil { - return nil - } - - if policy.Spec.Override != nil && policy.Spec.Override.JWT != nil { - for _, provider := range policy.Spec.Override.JWT.Providers { - _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) - if !ok { - return errListenerJWTProviderNotFound - } - } - } - - if policy.Spec.Default != nil && policy.Spec.Default.JWT != nil { - for _, provider := range policy.Spec.Default.JWT.Providers { - _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) - if !ok { - return errListenerJWTProviderNotFound - } - } - } - return nil -} - -func validateCertificateRefs(gateway gwv1beta1.Gateway, refs []gwv1beta1.SecretObjectReference, resources *common.ResourceMap) error { - for _, cert := range refs { - // Verify that the reference has a group and kind that we support - if !common.NilOrEqual(cert.Group, "") || !common.NilOrEqual(cert.Kind, common.KindSecret) { - return errListenerInvalidCertificateRef_NotSupported - } - - // Verify that the reference is within the namespace or, - // if cross-namespace, that it's allowed by a ReferenceGrant - if !resources.GatewayCanReferenceSecret(gateway, cert) { - return errRefNotPermitted - } - - // Verify that the referenced resource actually exists - key := common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace) - secret := resources.Certificate(key) - if secret == nil { - return errListenerInvalidCertificateRef_NotFound - } - - // Verify that the referenced resource contains the data shape that we expect - if err := validateCertificateData(*secret); err != nil { - return err - } - } - - return nil -} - -func validateTLSOptions(options map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue) error { - if options == nil { - return nil - } - - tlsMinVersionValue := string(options[common.TLSMinVersionAnnotationKey]) - if tlsMinVersionValue != "" { - if _, supported := allSupportedTLSVersions[tlsMinVersionValue]; !supported { - return errListenerUnsupportedTLSMinVersion - } - } - - tlsMaxVersionValue := string(options[common.TLSMaxVersionAnnotationKey]) - if tlsMaxVersionValue != "" { - if _, supported := allSupportedTLSVersions[tlsMaxVersionValue]; !supported { - return errListenerUnsupportedTLSMaxVersion - } - } - - tlsCipherSuitesValue := string(options[common.TLSCipherSuitesAnnotationKey]) - if tlsCipherSuitesValue != "" { - // If a minimum TLS version is configured, verify that it supports configuring cipher suites - if tlsMinVersionValue != "" { - if _, supported := allTLSVersionsWithConfigurableCipherSuites[tlsMinVersionValue]; !supported { - return errListenerTLSCipherSuiteNotConfigurable - } - } - - for _, tlsCipherSuiteValue := range strings.Split(tlsCipherSuitesValue, ",") { - tlsCipherSuite := strings.TrimSpace(tlsCipherSuiteValue) - if _, supported := allSupportedTLSCipherSuites[tlsCipherSuite]; !supported { - return errListenerUnsupportedTLSCipherSuite - } - } - } - - return nil -} - -func validateCertificateData(secret corev1.Secret) error { - _, privateKey, err := common.ParseCertificateData(secret) - if err != nil { - return errListenerInvalidCertificateRef_InvalidData - } - - err = common.ValidateKeyLength(privateKey) - if err != nil { - if version.IsFIPS() { - return errListenerInvalidCertificateRef_FIPSRSAKeyLen - } - - return errListenerInvalidCertificateRef_NonFIPSRSAKeyLen - } - - return nil -} - -// validateListeners validates the given listeners both internally and with respect to each -// other for purposes of setting "Conflicted" status conditions. -func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener, resources *common.ResourceMap, gwcc *v1alpha1.GatewayClassConfig) listenerValidationResults { - var results listenerValidationResults - merged := make(map[gwv1beta1.PortNumber]mergedListeners) - for i, listener := range listeners { - merged[listener.Port] = append(merged[listener.Port], mergedListener{ - index: i, - listener: listener, - }) - } - // This list keeps track of port conflicts directly on gateways. i.e., two listeners on the same port as - // defined by the user. - seenListenerPorts := map[int]struct{}{} - // This list keeps track of port conflicts caused by privileged port mappings. - seenContainerPorts := map[int]struct{}{} - portMapping := int32(0) - if gwcc != nil { - portMapping = gwcc.Spec.MapPrivilegedContainerPorts - } - for i, listener := range listeners { - var result listenerValidationResult - - err, refErr := validateTLS(gateway, listener.TLS, resources) - if refErr != nil { - result.refErrs = append(result.refErrs, refErr) - } - - jwtErr := validateJWT(gateway, listener, resources) - if jwtErr != nil { - result.refErrs = append(result.refErrs, jwtErr) - } - - if err != nil { - result.acceptedErr = err - } else if jwtErr != nil { - result.acceptedErr = jwtErr - } else { - _, supported := supportedKindsForProtocol[listener.Protocol] - if !supported { - result.acceptedErr = errListenerUnsupportedProtocol - } else if listener.Port == 20000 { // admin port - result.acceptedErr = errListenerPortUnavailable - } else if _, ok := seenListenerPorts[int(listener.Port)]; ok { - result.acceptedErr = errListenerPortUnavailable - } else if _, ok := seenContainerPorts[common.ToContainerPort(listener.Port, portMapping)]; ok { - result.acceptedErr = errListenerMappedToPrivilegedPortMapping - } - - result.routeKindErr = validateListenerAllowedRouteKinds(listener.AllowedRoutes) - } - - if err := merged[listener.Port].validateProtocol(); err != nil { - result.conflictedErr = err - } else { - result.conflictedErr = merged[listener.Port].validateHostname(i, listener) - } - - results = append(results, result) - - seenListenerPorts[int(listener.Port)] = struct{}{} - seenContainerPorts[common.ToContainerPort(listener.Port, portMapping)] = struct{}{} - } - return results -} - -func validateListenerAllowedRouteKinds(allowedRoutes *gwv1beta1.AllowedRoutes) error { - if allowedRoutes == nil { - return nil - } - for _, kind := range allowedRoutes.Kinds { - if _, ok := allSupportedRouteKinds[kind.Kind]; !ok { - return errListenerInvalidRouteKinds - } - if !common.NilOrEqual(kind.Group, gwv1beta1.GroupVersion.Group) { - return errListenerInvalidRouteKinds - } - } - return nil -} - -// routeAllowedForListenerNamespaces determines whether the route is allowed -// to bind to the Gateway based on the AllowedRoutes namespace selectors. -func routeAllowedForListenerNamespaces(gatewayNamespace string, allowedRoutes *gwv1beta1.AllowedRoutes, namespace corev1.Namespace) bool { - var namespaceSelector *gwv1beta1.RouteNamespaces - if allowedRoutes != nil { - // check gateway namespace - namespaceSelector = allowedRoutes.Namespaces - } - - // set default if namespace selector is nil - from := gwv1beta1.NamespacesFromSame - if namespaceSelector != nil && namespaceSelector.From != nil && *namespaceSelector.From != "" { - from = *namespaceSelector.From - } - - switch from { - case gwv1beta1.NamespacesFromAll: - return true - case gwv1beta1.NamespacesFromSame: - return gatewayNamespace == namespace.Name - case gwv1beta1.NamespacesFromSelector: - namespaceSelector, err := metav1.LabelSelectorAsSelector(namespaceSelector.Selector) - if err != nil { - // log the error here, the label selector is invalid - return false - } - - return namespaceSelector.Matches(toNamespaceSet(namespace.GetName(), namespace.GetLabels())) - default: - return false - } -} - -// routeAllowedForListenerHostname checks that a hostname specified on a route and the hostname specified -// on the gateway listener are compatible. -func routeAllowedForListenerHostname(hostname *gwv1beta1.Hostname, hostnames []gwv1beta1.Hostname) bool { - if hostname == nil || len(hostnames) == 0 { - return true - } - - for _, name := range hostnames { - if hostnamesMatch(name, *hostname) { - return true - } - } - return false -} - -// externalRefsOnRouteAllExist checks to make sure that all external filters referenced by the route exist in the resource map. -func externalRefsOnRouteAllExist(route *gwv1beta1.HTTPRoute, resources *common.ResourceMap) bool { - for _, rule := range route.Spec.Rules { - for _, filter := range rule.Filters { - if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { - continue - } - - if !resources.ExternalFilterExists(*filter.ExtensionRef, route.Namespace) { - return false - } - - } - - for _, backendRef := range rule.BackendRefs { - for _, filter := range backendRef.Filters { - if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { - continue - } - - if !resources.ExternalFilterExists(*filter.ExtensionRef, route.Namespace) { - return false - } - } - } - } - - return true -} - -func checkIfReferencesMissingJWTProvider(filter gwv1beta1.HTTPRouteFilter, resources *common.ResourceMap, namespace string, invalidFilters map[string]struct{}) { - if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { - return - } - externalFilter, ok := resources.GetExternalFilter(*filter.ExtensionRef, namespace) - if !ok { - return - } - authFilter, ok := externalFilter.(*v1alpha1.RouteAuthFilter) - if !ok { - return - } - - for _, provider := range authFilter.Spec.JWT.Providers { - _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) - if !ok { - invalidFilters[fmt.Sprintf("%s/%s", namespace, authFilter.Name)] = struct{}{} - return - } - } -} - -func authFilterReferencesMissingJWTProvider(httproute *gwv1beta1.HTTPRoute, resources *common.ResourceMap) []string { - invalidFilters := make(map[string]struct{}) - for _, rule := range httproute.Spec.Rules { - for _, filter := range rule.Filters { - checkIfReferencesMissingJWTProvider(filter, resources, httproute.Namespace, invalidFilters) - } - - for _, backendRef := range rule.BackendRefs { - for _, filter := range backendRef.Filters { - checkIfReferencesMissingJWTProvider(filter, resources, httproute.Namespace, invalidFilters) - } - } - } - - return maps.Keys(invalidFilters) -} - -// externalRefsKindAllowedOnRoute makes sure that all externalRefs reference a kind supported by gatewaycontroller. -func externalRefsKindAllowedOnRoute(route *gwv1beta1.HTTPRoute) bool { - for _, rule := range route.Spec.Rules { - if !filtersAllAllowedType(rule.Filters) { - return false - } - - // same thing but for backendref - for _, backendRef := range rule.BackendRefs { - if !filtersAllAllowedType(backendRef.Filters) { - return false - } - } - } - return true -} - -func filtersAllAllowedType(filters []gwv1beta1.HTTPRouteFilter) bool { - for _, filter := range filters { - if filter.ExtensionRef == nil { - continue - } - - if !common.FilterIsExternalFilter(filter) { - return false - } - } - return true -} - -// hostnameMatch checks that an individual hostname matches another hostname for -// compatibility. -func hostnamesMatch(a gwv1alpha2.Hostname, b gwv1beta1.Hostname) bool { - if a == "" || a == "*" || b == "" || b == "*" { - // any wildcard always matches - return true - } - - if strings.HasPrefix(string(a), "*.") || strings.HasPrefix(string(b), "*.") { - aLabels, bLabels := strings.Split(string(a), "."), strings.Split(string(b), ".") - if len(aLabels) != len(bLabels) { - return false - } - - for i := 1; i < len(aLabels); i++ { - if !strings.EqualFold(aLabels[i], bLabels[i]) { - return false - } - } - return true - } - - return string(a) == string(b) -} - -// routeKindIsAllowedForListener checks that the given route kind is present in the allowed set. -func routeKindIsAllowedForListener(kinds []gwv1beta1.RouteGroupKind, gk schema.GroupKind) bool { - if kinds == nil { - return true - } - - for _, kind := range kinds { - if string(kind.Kind) == gk.Kind && common.NilOrEqual(kind.Group, gk.Group) { - return true - } - } - - return false -} - -// routeKindIsAllowedForListenerExplicit checks that a route is allowed by the kinds specified explicitly -// on the listener. -func routeKindIsAllowedForListenerExplicit(allowedRoutes *gwv1alpha2.AllowedRoutes, gk schema.GroupKind) bool { - if allowedRoutes == nil { - return true - } - - return routeKindIsAllowedForListener(allowedRoutes.Kinds, gk) -} - -func validateAuthFilters(authFilters []*v1alpha1.RouteAuthFilter, resources *common.ResourceMap) authFilterValidationResults { - results := make(authFilterValidationResults, 0, len(authFilters)) - - for _, filter := range authFilters { - if filter == nil { - continue - } - var result authFilterValidationResult - missingJWTProviders := make([]string, 0) - for _, provider := range filter.Spec.JWT.Providers { - if _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider); !ok { - missingJWTProviders = append(missingJWTProviders, provider.Name) - } - } - - if len(missingJWTProviders) > 0 { - mergedNames := strings.Join(missingJWTProviders, ",") - result.resolvedRefErr = fmt.Errorf("%w: missingProviderNames: %s", errRouteFilterJWTProvidersReferenceDoesNotExist, mergedNames) - } - - if result.resolvedRefErr != nil { - result.acceptedErr = errRouteFilterNotAcceptedDueToInvalidRefs - } - - results = append(results, result) - } - return results -} - -// toNamespaceSet constructs a list of labels used to match a Namespace. -func toNamespaceSet(name string, labels map[string]string) klabels.Labels { - // If namespace label is not set, implicitly insert it to support older Kubernetes versions - if labels[common.NamespaceNameLabel] == name { - // Already set, avoid copies - return klabels.Set(labels) - } - // First we need a copy to not modify the underlying object - ret := make(map[string]string, len(labels)+1) - for k, v := range labels { - ret[k] = v - } - ret[common.NamespaceNameLabel] = name - return klabels.Set(ret) -} diff --git a/control-plane/api-gateway/binding/validation_test.go b/control-plane/api-gateway/binding/validation_test.go deleted file mode 100644 index c1c9e250ed..0000000000 --- a/control-plane/api-gateway/binding/validation_test.go +++ /dev/null @@ -1,1573 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "fmt" - "testing" - - logrtest "github.com/go-logr/logr/testing" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - 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" -) - -func TestValidateRefs(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - route client.Object - services map[types.NamespacedName]corev1.Service - referenceGrants []gwv1beta1.ReferenceGrant - meshServices []v1alpha1.MeshService - expectedErrors []error - }{ - "all pass no namespaces": { - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{{Name: "1"}, {Name: "2"}}, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "test"}: {}, - {Name: "2", Namespace: "test"}: {}, - {Name: "3", Namespace: "test"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{nil, nil}, - }, - "all fails namespaces no reference grants": { - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - {Name: "2", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - }, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "other"}: {}, - {Name: "2", Namespace: "other"}: {}, - {Name: "3", Namespace: "other"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{errRefNotPermitted, errRefNotPermitted}, - }, - "all pass namespaces": { - referenceGrants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("test")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Kind: "Service"}, - }, - }}, - }, - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - {Name: "2", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - }, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "other"}: {}, - {Name: "2", Namespace: "other"}: {}, - {Name: "3", Namespace: "other"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{nil, nil}, - }, - "some pass mixed missing reference grants": { - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - {Name: "2"}, - }, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "other"}: {}, - {Name: "2", Namespace: "test"}: {}, - {Name: "3", Namespace: "other"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{errRefNotPermitted, nil}, - }, - "all pass mixed": { - referenceGrants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("test")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Kind: "Service"}, - }, - }}, - }, - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - {Name: "2"}, - }, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "other"}: {}, - {Name: "2", Namespace: "test"}: {}, - {Name: "3", Namespace: "other"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{nil, nil}, - }, - "all fail mixed": { - referenceGrants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "HTTPRoute", Namespace: gwv1beta1.Namespace("test")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Kind: "Service"}, - }, - }}, - }, - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - {Name: "1"}, - {Name: "2", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - }, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "other"}: {}, - {Name: "2", Namespace: "test"}: {}, - {Name: "3", Namespace: "other"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, - }, - "all fail no namespaces": { - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - {Name: "1"}, - {Name: "2"}, - }, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "other"}: {}, - {Name: "2", Namespace: "other"}: {}, - {Name: "3", Namespace: "other"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, - }, - "all fail namespaces": { - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - {Name: "1", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - {Name: "2", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - }, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "test"}: {}, - {Name: "2", Namespace: "test"}: {}, - {Name: "3", Namespace: "test"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{errRouteBackendNotFound, errRouteBackendNotFound}, - }, - "type failures": { - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - {Name: "1", Group: common.PointerTo[gwv1beta1.Group]("test")}, - {Name: "2"}, - }, nil), - services: map[types.NamespacedName]corev1.Service{ - {Name: "1", Namespace: "test"}: {}, - {Name: "2", Namespace: "test"}: {}, - {Name: "3", Namespace: "test"}: {}, - }, - meshServices: []v1alpha1.MeshService{}, - expectedErrors: []error{errRouteInvalidKind, nil}, - }, - "mesh services": { - route: testHTTPRouteBackends("route", "test", []gwv1beta1.BackendObjectReference{ - { - Name: "1", - Group: common.PointerTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), - Kind: common.PointerTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), - }, - }, nil), - meshServices: []v1alpha1.MeshService{ - {ObjectMeta: metav1.ObjectMeta{Name: "1", Namespace: "test"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "2", Namespace: "test"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "3", Namespace: "test"}}, - }, - expectedErrors: []error{nil}, - }, - } { - t.Run(name, func(t *testing.T) { - refs := getRouteBackends(tt.route) - resources := common.NewResourceMap(common.ResourceTranslator{}, NewReferenceValidator(tt.referenceGrants), logrtest.NewTestLogger(t)) - for _, service := range tt.meshServices { - resources.AddMeshService(service) - } - for id := range tt.services { - resources.AddService(id, id.Name) - } - - actual := validateRefs(tt.route, refs, resources) - require.Equal(t, len(actual), len(tt.expectedErrors)) - for i, err := range tt.expectedErrors { - require.Equal(t, err, actual[i].err) - } - }) - } -} - -func TestValidateGateway(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - object gwv1beta1.Gateway - expected error - }{ - "valid": { - object: gwv1beta1.Gateway{}, - expected: nil, - }, - "invalid": { - object: gwv1beta1.Gateway{Spec: gwv1beta1.GatewaySpec{Addresses: []gwv1beta1.GatewayAddress{ - {Value: "1"}, - }}}, - expected: errGatewayUnsupportedAddress, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, validateGateway(tt.object, nil, nil).acceptedErr) - }) - } -} - -func TestMergedListeners_ValidateProtocol(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - mergedListeners mergedListeners - expected error - }{ - "valid": { - mergedListeners: []mergedListener{ - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - }, - expected: nil, - }, - "invalid": { - mergedListeners: []mergedListener{ - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.TCPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - }, - expected: errListenerProtocolConflict, - }, - "big list": { - mergedListeners: []mergedListener{ - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPSProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - {listener: gwv1beta1.Listener{Protocol: gwv1beta1.HTTPProtocolType}}, - }, - expected: errListenerProtocolConflict, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, tt.mergedListeners.validateProtocol()) - }) - } -} - -func TestMergedListeners_ValidateHostname(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - mergedListeners mergedListeners - expected error - }{ - "valid": { - mergedListeners: []mergedListener{ - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("1")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("2")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("3")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("4")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("5")}}, - {}, - }, - expected: nil, - }, - "invalid nil": { - mergedListeners: []mergedListener{ - {}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("1")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("2")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("3")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("4")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("5")}}, - {}, - }, - expected: errListenerHostnameConflict, - }, - "invalid set": { - mergedListeners: []mergedListener{ - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("1")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("2")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("3")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("4")}}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("5")}}, - {}, - {listener: gwv1beta1.Listener{Hostname: common.PointerTo[gwv1beta1.Hostname]("1")}}, - }, - expected: errListenerHostnameConflict, - }, - } { - t.Run(name, func(t *testing.T) { - for i, l := range tt.mergedListeners { - l.index = i - tt.mergedListeners[i] = l - } - - require.Equal(t, tt.expected, tt.mergedListeners.validateHostname(0, tt.mergedListeners[0].listener)) - }) - } -} - -func TestValidateTLS(t *testing.T) { - t.Parallel() - - _, secret := generateTestCertificate(t, "", "") - - for name, tt := range map[string]struct { - gateway gwv1beta1.Gateway - grants []gwv1beta1.ReferenceGrant - tls *gwv1beta1.GatewayTLSConfig - certificates []corev1.Secret - expectedResolvedRefsErr error - expectedAcceptedErr error - }{ - "no tls": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: nil, - certificates: nil, - expectedResolvedRefsErr: nil, - expectedAcceptedErr: nil, - }, - "not supported certificate": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "foo", Namespace: common.PointerTo[gwv1beta1.Namespace]("other"), Group: common.PointerTo[gwv1beta1.Group]("test")}, - }, - }, - certificates: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "other"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "other"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "other"}}, - }, - expectedResolvedRefsErr: errListenerInvalidCertificateRef_NotSupported, - expectedAcceptedErr: nil, - }, - "not allowed certificate": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "foo", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - }, - }, - certificates: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "other"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "other"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "other"}}, - }, - expectedResolvedRefsErr: errRefNotPermitted, - expectedAcceptedErr: nil, - }, - "not found certificate": { - grants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Kind: "Secret"}, - }, - }}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "zoiks", Namespace: common.PointerTo[gwv1beta1.Namespace]("other")}, - }, - }, - certificates: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "other"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "other"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "other"}}, - }, - expectedResolvedRefsErr: errListenerInvalidCertificateRef_NotFound, - expectedAcceptedErr: nil, - }, - "not found certificate mismatched namespace": { - grants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Kind: "Secret"}, - }, - }}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "foo", Namespace: common.PointerTo[gwv1beta1.Namespace]("foo")}, - }, - }, - certificates: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "other"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "other"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "other"}}, - }, - expectedResolvedRefsErr: errListenerInvalidCertificateRef_NotFound, - expectedAcceptedErr: nil, - }, - "passthrough mode": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - Mode: common.PointerTo(gwv1beta1.TLSModePassthrough), - }, - certificates: nil, - expectedResolvedRefsErr: nil, - expectedAcceptedErr: errListenerNoTLSPassthrough, - }, - "valid targeted namespace": { - grants: []gwv1beta1.ReferenceGrant{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "1", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Kind: "Secret"}, - }, - }}, - {ObjectMeta: metav1.ObjectMeta{Namespace: "2", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Kind: "Secret"}, - }, - }}, - {ObjectMeta: metav1.ObjectMeta{Namespace: "3", Name: "grant"}, Spec: gwv1beta1.ReferenceGrantSpec{ - From: []gwv1beta1.ReferenceGrantFrom{ - {Group: gwv1beta1.GroupName, Kind: "Gateway", Namespace: gwv1beta1.Namespace("default")}, - }, - To: []gwv1beta1.ReferenceGrantTo{ - {Kind: "Secret"}, - }, - }}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "foo", Namespace: common.PointerTo[gwv1beta1.Namespace]("1")}, - {Name: "bar", Namespace: common.PointerTo[gwv1beta1.Namespace]("2")}, - {Name: "baz", Namespace: common.PointerTo[gwv1beta1.Namespace]("3")}, - }, - }, - certificates: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "2"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "3"}}, - }, - expectedResolvedRefsErr: nil, - expectedAcceptedErr: nil, - }, - "valid same namespace": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "foo"}, - {Name: "bar"}, - {Name: "baz"}, - }, - }, - certificates: []corev1.Secret{ - {ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "default"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "default"}}, - }, - expectedResolvedRefsErr: nil, - expectedAcceptedErr: nil, - }, - "valid empty certs": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{}, - certificates: nil, - expectedResolvedRefsErr: nil, - expectedAcceptedErr: nil, - }, - "invalid cipher suite": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ - common.TLSCipherSuitesAnnotationKey: "invalid", - }, - }, - certificates: nil, - expectedAcceptedErr: errListenerUnsupportedTLSCipherSuite, - }, - "cipher suite not configurable": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ - common.TLSMinVersionAnnotationKey: "TLSv1_3", - common.TLSCipherSuitesAnnotationKey: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - }, - }, - certificates: nil, - expectedAcceptedErr: errListenerTLSCipherSuiteNotConfigurable, - }, - "invalid max version": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ - common.TLSMaxVersionAnnotationKey: "invalid", - }, - }, - certificates: nil, - expectedAcceptedErr: errListenerUnsupportedTLSMaxVersion, - }, - "invalid min version": { - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - tls: &gwv1beta1.GatewayTLSConfig{ - Options: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ - common.TLSMinVersionAnnotationKey: "invalid", - }, - }, - certificates: nil, - expectedAcceptedErr: errListenerUnsupportedTLSMinVersion, - }, - } { - t.Run(name, func(t *testing.T) { - resources := common.NewResourceMap(common.ResourceTranslator{}, NewReferenceValidator(tt.grants), logrtest.NewTestLogger(t)) - for _, certificate := range tt.certificates { - // make the data valid - certificate.Data = secret.Data - resources.ReferenceCountCertificate(certificate) - } - - actualAcceptedError, actualResolvedRefsError := validateTLS(tt.gateway, tt.tls, resources) - require.Equal(t, tt.expectedResolvedRefsErr, actualResolvedRefsError) - require.Equal(t, tt.expectedAcceptedErr, actualAcceptedError) - }) - } -} - -func TestValidateListeners(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - listeners []gwv1beta1.Listener - expectedAcceptedErr error - listenerIndexToTest int - mapPrivilegedContainerPorts int32 - gateway gwv1beta1.Gateway - resources resourceMapResources - }{ - "valid protocol HTTP": { - listeners: []gwv1beta1.Listener{ - {Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, - expectedAcceptedErr: nil, - }, - "valid protocol HTTPS": { - listeners: []gwv1beta1.Listener{ - {Protocol: gwv1beta1.HTTPSProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, - expectedAcceptedErr: nil, - }, - "valid protocol TCP": { - listeners: []gwv1beta1.Listener{ - {Protocol: gwv1beta1.TCPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, - expectedAcceptedErr: nil, - }, - "invalid protocol UDP": { - listeners: []gwv1beta1.Listener{ - {Protocol: gwv1beta1.UDPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, - expectedAcceptedErr: errListenerUnsupportedProtocol, - }, - "invalid port": { - listeners: []gwv1beta1.Listener{ - {Protocol: gwv1beta1.TCPProtocolType, Port: 20000}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, - expectedAcceptedErr: errListenerPortUnavailable, - }, - "conflicted port": { - listeners: []gwv1beta1.Listener{ - {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, - {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, - expectedAcceptedErr: errListenerPortUnavailable, - listenerIndexToTest: 1, - }, - "conflicted mapped port": { - listeners: []gwv1beta1.Listener{ - {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, - {Protocol: gwv1beta1.TCPProtocolType, Port: 2080}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - expectedAcceptedErr: errListenerMappedToPrivilegedPortMapping, - resources: resourceMapResources{}, - listenerIndexToTest: 1, - mapPrivilegedContainerPorts: 2000, - }, - "valid JWT provider in override of policy": { - listeners: []gwv1beta1.Listener{ - {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{ - jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - gatewayPolicies: []*v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: common.KindGateway, - Name: "gateway", - Namespace: "default", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{}, - }, - }, - }, - }, - expectedAcceptedErr: nil, - }, - "valid JWT provider in default of policy": { - listeners: []gwv1beta1.Listener{ - {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{ - jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - gatewayPolicies: []*v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: common.KindGateway, - Name: "gateway", - Namespace: "default", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - Override: &v1alpha1.GatewayPolicyConfig{}, - }, - }, - }, - }, - expectedAcceptedErr: nil, - }, - "invalid JWT provider in override of policy": { - listeners: []gwv1beta1.Listener{ - {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{ - jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - gatewayPolicies: []*v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: common.KindGateway, - Name: "gateway", - Namespace: "default", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{}, - }, - }, - }, - }, - expectedAcceptedErr: errListenerJWTProviderNotFound, - }, - "invalid JWT provider in default of policy": { - listeners: []gwv1beta1.Listener{ - {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{ - jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - gatewayPolicies: []*v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: common.KindGateway, - Name: "gateway", - Namespace: "default", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Override: &v1alpha1.GatewayPolicyConfig{}, - }, - }, - }, - }, - expectedAcceptedErr: errListenerJWTProviderNotFound, - }, - } { - t.Run(name, func(t *testing.T) { - gwcc := &v1alpha1.GatewayClassConfig{ - Spec: v1alpha1.GatewayClassConfigSpec{ - MapPrivilegedContainerPorts: tt.mapPrivilegedContainerPorts, - }, - } - - require.Equal(t, tt.expectedAcceptedErr, validateListeners(tt.gateway, tt.listeners, newTestResourceMap(t, tt.resources), gwcc)[tt.listenerIndexToTest].acceptedErr) - }) - } -} - -func TestRouteAllowedForListenerNamespaces(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - allowedRoutes *gwv1beta1.AllowedRoutes - gatewayNamespace string - routeNamespace corev1.Namespace - expected bool - }{ - "default same namespace allowed": { - allowedRoutes: nil, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, - expected: true, - }, - "default same namespace not allowed": { - allowedRoutes: nil, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other"}}, - expected: false, - }, - "explicit same namespace allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: common.PointerTo(gwv1beta1.NamespacesFromSame)}}, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, - expected: true, - }, - "explicit same namespace not allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: common.PointerTo(gwv1beta1.NamespacesFromSame)}}, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other"}}, - expected: false, - }, - "all namespace allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: common.PointerTo(gwv1beta1.NamespacesFromAll)}}, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other"}}, - expected: true, - }, - "invalid namespace from not allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{From: common.PointerTo[gwv1beta1.FromNamespaces]("other")}}, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, - expected: false, - }, - "labeled namespace allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, - }}, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other", Labels: map[string]string{ - "foo": "bar", - }}}, - expected: true, - }, - "labeled namespace not allowed": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, - }}, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other", Labels: map[string]string{ - "foo": "baz", - }}}, - expected: false, - }, - "invalid labeled namespace": { - allowedRoutes: &gwv1beta1.AllowedRoutes{Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{ - {Key: "foo", Operator: "junk", Values: []string{"1"}}, - }}, - }}, - gatewayNamespace: "test", - routeNamespace: corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "other", Labels: map[string]string{ - "foo": "bar", - }}}, - expected: false, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, routeAllowedForListenerNamespaces(tt.gatewayNamespace, tt.allowedRoutes, tt.routeNamespace)) - }) - } -} - -func TestRouteAllowedForListenerHostname(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - hostname *gwv1beta1.Hostname - hostnames []gwv1beta1.Hostname - expected bool - }{ - "empty hostnames": { - hostname: nil, - hostnames: []gwv1beta1.Hostname{"foo", "bar"}, - expected: true, - }, - "empty hostname": { - hostname: common.PointerTo[gwv1beta1.Hostname]("foo"), - hostnames: nil, - expected: true, - }, - "any hostname match": { - hostname: common.PointerTo[gwv1beta1.Hostname]("foo"), - hostnames: []gwv1beta1.Hostname{"foo", "bar"}, - expected: true, - }, - "no match": { - hostname: common.PointerTo[gwv1beta1.Hostname]("foo"), - hostnames: []gwv1beta1.Hostname{"bar"}, - expected: false, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, routeAllowedForListenerHostname(tt.hostname, tt.hostnames)) - }) - } -} - -func TestHostnamesMatch(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - one gwv1beta1.Hostname - two gwv1beta1.Hostname - expected bool - }{ - "wildcard one": { - one: "*", - two: "foo", - expected: true, - }, - "wildcard two": { - one: "foo", - two: "*", - expected: true, - }, - "empty one": { - one: "", - two: "foo", - expected: true, - }, - "empty two": { - one: "foo", - two: "", - expected: true, - }, - "subdomain one": { - one: "*.foo", - two: "sub.foo", - expected: true, - }, - "subdomain two": { - one: "sub.foo", - two: "*.foo", - expected: true, - }, - "exact match": { - one: "foo", - two: "foo", - expected: true, - }, - "no match": { - one: "foo", - two: "bar", - expected: false, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, hostnamesMatch(tt.one, tt.two)) - }) - } -} - -func TestRouteKindIsAllowedForListener(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - kinds []gwv1beta1.RouteGroupKind - gk schema.GroupKind - expected bool - }{ - "empty kinds": { - kinds: nil, - gk: schema.GroupKind{Group: "a", Kind: "b"}, - expected: true, - }, - "group specified": { - kinds: []gwv1beta1.RouteGroupKind{ - {Group: common.PointerTo[gwv1beta1.Group]("a"), Kind: "b"}, - }, - gk: schema.GroupKind{Group: "a", Kind: "b"}, - expected: true, - }, - "group unspecified": { - kinds: []gwv1beta1.RouteGroupKind{ - {Kind: "b"}, - }, - gk: schema.GroupKind{Group: "a", Kind: "b"}, - expected: true, - }, - "kind mismatch": { - kinds: []gwv1beta1.RouteGroupKind{ - {Kind: "b"}, - }, - gk: schema.GroupKind{Group: "a", Kind: "c"}, - expected: false, - }, - "group mismatch": { - kinds: []gwv1beta1.RouteGroupKind{ - {Group: common.PointerTo[gwv1beta1.Group]("a"), Kind: "b"}, - }, - gk: schema.GroupKind{Group: "d", Kind: "b"}, - expected: false, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, routeKindIsAllowedForListener(tt.kinds, tt.gk)) - }) - } -} - -func TestValidateGatewayPolicies(t *testing.T) { - for name, tc := range map[string]struct { - gateway gwv1beta1.Gateway - policies []v1alpha1.GatewayPolicy - resources *common.ResourceMap - expected gatewayPolicyValidationResults - }{ - "happy path, everything exists": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "local", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "local", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "okta", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: nil, - resolvedRefsErrs: []error{}, - }, - }, - }, - "a policy references a gateway that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("does not exist")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "auth0", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, "gw", "does not exist")}, - }, - }, - }, - "a policy references a JWT provider in the override section that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, - }, - }, - }, - "a policy references a JWT provider in the default section that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, - }, - }, - }, - "a policy references the same JWT provider in the both override and default section that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, - }, - }, - }, - "a policy references different JWT providers in the both override and default section that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "local,okta")}, - }, - }, - }, - "everything is wrong: listener does not exist and override and default both reference different missing jwt providers": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("does not exist")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{ - fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, "gw", "does not exist"), - fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "local,okta"), - }, - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - require.EqualValues(t, tc.expected, validateGatewayPolicies(tc.gateway, tc.policies, tc.resources)) - }) - } -} - -func TestValidateAuthFilters(t *testing.T) { - for name, tc := range map[string]struct { - authFilters []*v1alpha1.RouteAuthFilter - resources *common.ResourceMap - expected authFilterValidationResults - }{ - "auth filter valid": { - authFilters: []*v1alpha1.RouteAuthFilter{ - { - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "okta", - }, - }, - }}), - expected: authFilterValidationResults{authFilterValidationResult{}}, - }, - "auth filter references missing JWT Provider": { - authFilters: []*v1alpha1.RouteAuthFilter{ - { - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "auth0", - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "okta", - }, - }, - }}), - expected: authFilterValidationResults{ - authFilterValidationResult{ - acceptedErr: errRouteFilterNotAcceptedDueToInvalidRefs, - resolvedRefErr: fmt.Errorf("%w: missingProviderNames: %s", errRouteFilterJWTProvidersReferenceDoesNotExist, "auth0"), - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tc.expected, validateAuthFilters(tc.authFilters, tc.resources)) - }) - } -} diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go deleted file mode 100644 index f2d6ec9bf9..0000000000 --- a/control-plane/api-gateway/cache/consul.go +++ /dev/null @@ -1,539 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cache - -import ( - "bytes" - "context" - "fmt" - "strings" - "sync" - "text/template" - "time" - - "github.com/go-logr/logr" - "golang.org/x/exp/slices" - "sigs.k8s.io/controller-runtime/pkg/event" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul/api" -) - -func init() { - gatewayTpl = template.Must(template.New("root").Parse(strings.TrimSpace(gatewayRulesTpl))) -} - -type templateArgs struct { - EnableNamespaces bool - APIGatewayName string -} - -var ( - gatewayTpl *template.Template - gatewayRulesTpl = ` -mesh = "read" -{{- if .EnableNamespaces }} - namespace_prefix "" { -{{- end }} - node_prefix "" { - policy = "read" - } - service_prefix "" { - policy = "read" - } - service "{{.APIGatewayName}}" { - policy = "write" - } -{{- if .EnableNamespaces }} - } -{{- end }} -` -) - -const ( - namespaceWildcard = "*" - apiTimeout = 5 * time.Minute -) - -var Kinds = []string{api.APIGateway, api.HTTPRoute, api.TCPRoute, api.InlineCertificate, api.JWTProvider} - -type Config struct { - ConsulClientConfig *consul.Config - ConsulServerConnMgr consul.ServerConnectionManager - NamespacesEnabled bool - Datacenter string - CrossNamespaceACLPolicy string - Logger logr.Logger -} - -// Cache subscribes to and caches Consul objects, it also responsible for mainting subscriptions to -// resources that it caches. -type Cache struct { - config *consul.Config - serverMgr consul.ServerConnectionManager - logger logr.Logger - - cache map[string]*common.ReferenceMap - cacheMutex *sync.Mutex - - subscribers map[string][]*Subscription - subscriberMutex *sync.Mutex - - namespacesEnabled bool - crossNamespaceACLPolicy string - - synced chan struct{} - - kinds []string - - datacenter string -} - -func New(config Config) *Cache { - cache := make(map[string]*common.ReferenceMap, len(Kinds)) - for _, kind := range Kinds { - cache[kind] = common.NewReferenceMap() - } - - config.ConsulClientConfig.APITimeout = apiTimeout - - return &Cache{ - config: config.ConsulClientConfig, - serverMgr: config.ConsulServerConnMgr, - namespacesEnabled: config.NamespacesEnabled, - cache: cache, - cacheMutex: &sync.Mutex{}, - subscribers: make(map[string][]*Subscription), - subscriberMutex: &sync.Mutex{}, - kinds: Kinds, - synced: make(chan struct{}, len(Kinds)), - logger: config.Logger, - crossNamespaceACLPolicy: config.CrossNamespaceACLPolicy, - datacenter: config.Datacenter, - } -} - -// WaitSynced is used to coordinate with the caller when the cache is initially filled. -func (c *Cache) WaitSynced(ctx context.Context) { - for range c.kinds { - select { - case <-c.synced: - case <-ctx.Done(): - return - } - } -} - -// Subscribe handles adding a new subscription for resources of a given kind. -func (c *Cache) Subscribe(ctx context.Context, kind string, translator TranslatorFn) *Subscription { - c.subscriberMutex.Lock() - defer c.subscriberMutex.Unlock() - - // check that kind is registered with cache - if !slices.Contains(c.kinds, kind) { - return &Subscription{} - } - - subscribers, ok := c.subscribers[kind] - if !ok { - subscribers = []*Subscription{} - } - - ctx, cancel := context.WithCancel(ctx) - events := make(chan event.GenericEvent) - sub := &Subscription{ - translator: translator, - ctx: ctx, - cancelCtx: cancel, - events: events, - } - - subscribers = append(subscribers, sub) - - c.subscribers[kind] = subscribers - - return sub -} - -// Run starts the cache watch cycle, on the first call it will fill the cache with existing resources. -func (c *Cache) Run(ctx context.Context) { - wg := &sync.WaitGroup{} - - for i := range c.kinds { - kind := c.kinds[i] - - wg.Add(1) - go func() { - defer wg.Done() - c.subscribeToConsul(ctx, kind) - }() - } - - wg.Wait() -} - -func (c *Cache) subscribeToConsul(ctx context.Context, kind string) { - once := &sync.Once{} - - opts := &api.QueryOptions{} - if c.namespacesEnabled { - opts.Namespace = namespaceWildcard - } - - for { - select { - case <-ctx.Done(): - return - default: - } - - client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) - if err != nil { - c.logger.Error(err, "error initializing consul client") - continue - } - - entries, meta, err := client.ConfigEntries().List(kind, opts.WithContext(ctx)) - if err != nil { - // if we timeout we don't care about the error message because it's expected to happen on long polls - // any other error we want to alert on - if !strings.Contains(strings.ToLower(err.Error()), "timeout") && - !strings.Contains(strings.ToLower(err.Error()), "no such host") && - !strings.Contains(strings.ToLower(err.Error()), "connection refused") { - c.logger.Error(err, fmt.Sprintf("error fetching config entries for kind: %s", kind)) - } - continue - } - - opts.WaitIndex = meta.LastIndex - - c.updateAndNotify(ctx, once, kind, entries) - - select { - case <-ctx.Done(): - return - default: - continue - } - } -} - -func (c *Cache) updateAndNotify(ctx context.Context, once *sync.Once, kind string, entries []api.ConfigEntry) { - c.cacheMutex.Lock() - - cache := common.NewReferenceMap() - - for _, entry := range entries { - meta := entry.GetMeta() - if kind != api.JWTProvider { - if meta[constants.MetaKeyKubeName] == "" || meta[constants.MetaKeyDatacenter] != c.datacenter { - // Don't process things that don't belong to us. The main reason - // for this is so that we don't garbage collect config entries that - // are either user-created or that another controller running in a - // federated datacenter creates. While we still allow for competing controllers - // syncing/overriding each other due to conflicting Kubernetes objects in - // two federated clusters (which is what the rest of the controllers also allow - // for), we don't want to delete a config entry just because we don't have - // its corresponding Kubernetes object if we know it belongs to another datacenter. - continue - } - } - - cache.Set(common.EntryToReference(entry), entry) - } - - diffs := c.cache[kind].Diff(cache) - - c.cache[kind] = cache - - // we run this the first time the cache is filled to notify the waiter - once.Do(func() { - c.logger.Info("sync mark for " + kind) - c.synced <- struct{}{} - }) - - c.cacheMutex.Unlock() - - // now notify all subscribers - c.notifySubscribers(ctx, kind, diffs) -} - -// notifySubscribers notifies each subscriber for a given kind on changes to a config entry of that kind. It also -// handles removing any subscribers that have marked themselves as done. -func (c *Cache) notifySubscribers(ctx context.Context, kind string, entries []api.ConfigEntry) { - c.subscriberMutex.Lock() - defer c.subscriberMutex.Unlock() - - for _, entry := range entries { - // this will hold the new list of current subscribers after we finish notifying - subscribers := make([]*Subscription, 0, len(c.subscribers[kind])) - for _, subscriber := range c.subscribers[kind] { - addSubscriber := false - - for _, namespaceName := range subscriber.translator(entry) { - event := event.GenericEvent{ - Object: newConfigEntryObject(namespaceName), - } - - select { - case <-ctx.Done(): - return - case <-subscriber.ctx.Done(): - // don't add this subscriber to current list because it is done - addSubscriber = false - case subscriber.events <- event: - // keep this one since we can send events to it - addSubscriber = true - } - } - - if addSubscriber { - subscribers = append(subscribers, subscriber) - } - } - c.subscribers[kind] = subscribers - } -} - -// Write handles writing the config entry back to Consul, if the current reference of the -// config entry is stale then it returns an error. -func (c *Cache) Write(ctx context.Context, entry api.ConfigEntry) error { - c.cacheMutex.Lock() - defer c.cacheMutex.Unlock() - - entryMap, ok := c.cache[entry.GetKind()] - if !ok { - return nil - } - - ref := common.EntryToReference(entry) - - old := entryMap.Get(ref) - if old != nil && common.EntriesEqual(old, entry) { - return nil - } - - client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) - if err != nil { - return err - } - - if c.namespacesEnabled { - if _, err := namespaces.EnsureExists(client, entry.GetNamespace(), c.crossNamespaceACLPolicy); err != nil { - return err - } - } - - options := &api.WriteOptions{} - - _, _, err = client.ConfigEntries().Set(entry, options.WithContext(ctx)) - if err != nil { - return err - } - - return nil -} - -func (c *Cache) ensurePolicy(client *api.Client, gatewayName string) (string, error) { - policy := c.gatewayPolicy(gatewayName) - - created, _, err := client.ACL().PolicyCreate(&policy, &api.WriteOptions{}) - - if isPolicyExistsErr(err, policy.Name) { - existing, _, err := client.ACL().PolicyReadByName(policy.Name, &api.QueryOptions{}) - if err != nil { - return "", err - } - return existing.ID, nil - } - if err != nil { - return "", err - } - return created.ID, nil -} - -func (c *Cache) ensureRole(client *api.Client, gatewayName string) (string, error) { - policyID, err := c.ensurePolicy(client, gatewayName) - if err != nil { - return "", err - } - - aclRoleName := fmt.Sprint("managed-gateway-acl-role-", gatewayName) - - aclRole, _, err := client.ACL().RoleReadByName(aclRoleName, &api.QueryOptions{}) - if err != nil { - return "", err - } - if aclRole != nil { - return aclRoleName, nil - } - - role := &api.ACLRole{ - Name: aclRoleName, - Description: "ACL Role for Managed API Gateways", - Policies: []*api.ACLLink{{ID: policyID}}, - } - - _, _, err = client.ACL().RoleCreate(role, &api.WriteOptions{}) - return aclRoleName, err -} - -func (c *Cache) gatewayPolicy(gatewayName string) api.ACLPolicy { - var data bytes.Buffer - if err := gatewayTpl.Execute(&data, templateArgs{ - EnableNamespaces: c.namespacesEnabled, - APIGatewayName: gatewayName, - }); err != nil { - // just panic if we can't compile the simple template - // as it means something else is going severly wrong. - panic(err) - } - - return api.ACLPolicy{ - Name: fmt.Sprint("api-gateway-policy-for-", gatewayName), - Description: "API Gateway token Policy", - Rules: data.String(), - } -} - -// Get returns a config entry from the cache that corresponds to the given resource reference. -func (c *Cache) Get(ref api.ResourceReference) api.ConfigEntry { - c.cacheMutex.Lock() - defer c.cacheMutex.Unlock() - - entryMap, ok := c.cache[ref.Kind] - if !ok { - return nil - } - - return entryMap.Get(ref) -} - -// Delete handles deleting the config entry from consul, if the current reference of the config entry is stale then -// it returns an error. -func (c *Cache) Delete(ctx context.Context, ref api.ResourceReference) error { - c.cacheMutex.Lock() - defer c.cacheMutex.Unlock() - - entryMap, ok := c.cache[ref.Kind] - if !ok { - return nil - } - - if entryMap.Get(ref) == nil { - c.logger.Info("cached object not found, not deleting") - return nil - } - - client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) - if err != nil { - return err - } - - options := &api.WriteOptions{Namespace: ref.Namespace, Partition: ref.Partition} - - _, err = client.ConfigEntries().Delete(ref.Kind, ref.Name, options.WithContext(ctx)) - if err != nil { - c.logger.Info("delete error", "err", err) - } - return err -} - -// List returns a list of config entries from the cache that corresponds to the given kind. -func (c *Cache) List(kind string) []api.ConfigEntry { - c.cacheMutex.Lock() - defer c.cacheMutex.Unlock() - - refMap, ok := c.cache[kind] - if !ok { - return nil - } - - return refMap.Entries() -} - -func (c *Cache) EnsureRoleBinding(authMethod, service, namespace string) error { - client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) - if err != nil { - return err - } - - role, err := c.ensureRole(client, service) - if err != nil { - return ignoreACLsDisabled(err) - } - - bindingRule := &api.ACLBindingRule{ - Description: fmt.Sprintf("Binding Rule for %s/%s", namespace, service), - AuthMethod: authMethod, - Selector: fmt.Sprintf("serviceaccount.name==%q and serviceaccount.namespace==%q", service, namespace), - BindType: api.BindingRuleBindTypeRole, - BindName: role, - } - - existingRules, _, err := client.ACL().BindingRuleList(authMethod, &api.QueryOptions{}) - if err != nil { - return err - } - - for _, existingRule := range existingRules { - if existingRule.BindName == bindingRule.BindName && existingRule.Description == bindingRule.Description { - bindingRule.ID = existingRule.ID - } - } - - if bindingRule.ID == "" { - _, _, err := client.ACL().BindingRuleCreate(bindingRule, &api.WriteOptions{}) - return err - } - _, _, err = client.ACL().BindingRuleUpdate(bindingRule, &api.WriteOptions{}) - return err -} - -// Register registers a service in Consul. -func (c *Cache) Register(ctx context.Context, registration api.CatalogRegistration) error { - client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) - if err != nil { - return err - } - - options := &api.WriteOptions{} - - _, err = client.Catalog().Register(®istration, options.WithContext(ctx)) - return err -} - -// Deregister deregisters a service in Consul. -func (c *Cache) Deregister(ctx context.Context, deregistration api.CatalogDeregistration) error { - client, err := consul.NewClientFromConnMgr(c.config, c.serverMgr) - if err != nil { - return err - } - - options := &api.WriteOptions{} - - _, err = client.Catalog().Deregister(&deregistration, options.WithContext(ctx)) - return err -} - -func ignoreACLsDisabled(err error) error { - if err == nil { - return nil - } - if err.Error() == "Unexpected response code: 401 (ACL support disabled)" { - return nil - } - return err -} - -// isPolicyExistsErr returns true if err is due to trying to call the -// policy create API when the policy already exists. -func isPolicyExistsErr(err error, policyName string) bool { - return err != nil && - strings.Contains(err.Error(), "Unexpected response code: 500") && - strings.Contains(err.Error(), fmt.Sprintf("Invalid Policy: A Policy with Name %q already exists", policyName)) -} diff --git a/control-plane/api-gateway/cache/consul_test.go b/control-plane/api-gateway/cache/consul_test.go deleted file mode 100644 index d206c0a8a8..0000000000 --- a/control-plane/api-gateway/cache/consul_test.go +++ /dev/null @@ -1,2060 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cache - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - - "github.com/go-logr/logr" - logrtest "github.com/go-logr/logr/testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/event" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func Test_resourceCache_diff(t *testing.T) { - t.Parallel() - type args struct { - newCache *common.ReferenceMap - } - tests := []struct { - name string - oldCache *common.ReferenceMap - args args - want []api.ConfigEntry - }{ - { - name: "no difference", - oldCache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - })[api.HTTPRoute], - args: args{ - newCache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - })[api.HTTPRoute], - }, - want: []api.ConfigEntry{}, - }, - { - name: "resource exists in old cache but not new one", - oldCache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route 2", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-2", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - })[api.HTTPRoute], - args: args{ - newCache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - })[api.HTTPRoute], - }, - want: []api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route 2", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-2", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - }, - }, - { - name: "resource exists in new cache but not old one", - oldCache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - })[api.HTTPRoute], - args: args{ - newCache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route 2", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-2", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - })[api.HTTPRoute], - }, - want: []api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route 2", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-2", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - }, - }, - { - name: "same ref new cache has a greater modify index", - oldCache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - ModifyIndex: 1, - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - })[api.HTTPRoute], - args: args{ - newCache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - ModifyIndex: 10, - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - })[api.HTTPRoute], - }, - want: []api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - ModifyIndex: 10, - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - }, - }, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - got := tt.oldCache.Diff(tt.args.newCache) - if diff := cmp.Diff(got, tt.want); diff != "" { - t.Errorf("resourceCache.diff mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestCache_Subscribe(t *testing.T) { - t.Parallel() - type args struct { - ctx context.Context - kind string - translator TranslatorFn - } - tests := []struct { - name string - args args - subscribers map[string][]*Subscription - subscriberChange int - }{ - { - name: "new subscription added when there are no other subscribers of the same kind", - args: args{ - ctx: context.Background(), - kind: api.HTTPRoute, - translator: func(api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{} - }, - }, - subscriberChange: 1, - }, - { - name: "new subscription added when there are existing subscribers of the same kind", - args: args{ - ctx: context.Background(), - kind: api.HTTPRoute, - translator: func(api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{} - }, - }, - subscribers: map[string][]*Subscription{ - api.HTTPRoute: { - { - translator: func(api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{} - }, - ctx: context.Background(), - cancelCtx: func() { - }, - events: make(chan event.GenericEvent), - }, - }, - }, - subscriberChange: 1, - }, - { - name: "subscription for kind that does not exist does not change any subscriber counts", - args: args{ - ctx: context.Background(), - kind: "UnknownKind", - translator: func(api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{} - }, - }, - subscriberChange: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := New(Config{ - ConsulClientConfig: &consul.Config{ - APIClientConfig: &api.Config{}, - HTTPPort: 0, - GRPCPort: 0, - APITimeout: 0, - }, - ConsulServerConnMgr: consul.NewMockServerConnectionManager(t), - NamespacesEnabled: false, - Logger: logr.Logger{}, - }) - - if len(tt.subscribers) > 0 { - c.subscribers = tt.subscribers - } - - kindSubscriberCounts := make(map[string]int) - for kind, subscribers := range c.subscribers { - kindSubscriberCounts[kind] = len(subscribers) - } - - c.Subscribe(tt.args.ctx, tt.args.kind, tt.args.translator) - - for kind, subscribers := range c.subscribers { - expectedSubscriberCount := kindSubscriberCounts[kind] - if kind == tt.args.kind { - expectedSubscriberCount += tt.subscriberChange - } - actualSubscriberCount := len(subscribers) - - if expectedSubscriberCount != actualSubscriberCount { - t.Errorf("Expected there to be %d subscribers, there were %d", expectedSubscriberCount, actualSubscriberCount) - } - } - }) - } -} - -func TestCache_Write(t *testing.T) { - t.Parallel() - testCases := []struct { - name string - responseFn func(w http.ResponseWriter) - expectedErr error - }{ - { - name: "write is successful", - responseFn: func(w http.ResponseWriter) { - w.WriteHeader(200) - fmt.Fprintln(w, `{updated: true}`) - }, - expectedErr: nil, - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/v1/config": - tt.responseFn(w) - case "/v1/catalog/services": - fmt.Fprintln(w, `{}`) - default: - w.WriteHeader(500) - fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) - } - })) - defer consulServer.Close() - - serverURL, err := url.Parse(consulServer.URL) - require.NoError(t, err) - - port, err := strconv.Atoi(serverURL.Port()) - require.NoError(t, err) - - c := New(Config{ - ConsulClientConfig: &consul.Config{ - APIClientConfig: &api.Config{}, - HTTPPort: port, - GRPCPort: port, - APITimeout: 0, - }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), - NamespacesEnabled: false, - Logger: logrtest.NewTestLogger(t), - }) - - entry := &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - } - - err = c.Write(context.Background(), entry) - require.Equal(t, err, tt.expectedErr) - }) - } -} - -func TestCache_Get(t *testing.T) { - t.Parallel() - type args struct { - ref api.ResourceReference - } - tests := []struct { - name string - args args - want api.ConfigEntry - cache map[string]*common.ReferenceMap - }{ - { - name: "entry exists", - args: args{ - ref: api.ResourceReference{ - Kind: api.APIGateway, - Name: "api-gw", - }, - }, - want: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - }, - cache: loadedReferenceMaps([]api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - }, - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-2", - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - }, - }), - }, - { - name: "entry does not exist", - args: args{ - ref: api.ResourceReference{ - Kind: api.APIGateway, - Name: "api-gw-4", - }, - }, - want: nil, - cache: loadedReferenceMaps([]api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - }, - &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw-2", - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - }, - }), - }, - { - name: "kind key does not exist", - args: args{ - ref: api.ResourceReference{ - Kind: api.APIGateway, - Name: "api-gw-4", - }, - }, - want: nil, - cache: loadedReferenceMaps([]api.ConfigEntry{ - &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "route", - Meta: map[string]string{ - constants.MetaKeyKubeName: "name", - }, - }, - }), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := New(Config{ - ConsulClientConfig: &consul.Config{ - APIClientConfig: &api.Config{}, - }, - }) - c.cache = tt.cache - - got := c.Get(tt.args.ref) - - if diff := cmp.Diff(got, tt.want); diff != "" { - t.Errorf("Cache.Get mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func Test_Run(t *testing.T) { - t.Parallel() - // setup httproutes - httpRouteOne, httpRouteTwo := setupHTTPRoutes() - httpRoutes := []*api.HTTPRouteConfigEntry{httpRouteOne, httpRouteTwo} - - // setup gateway - gw := setupGateway() - gateways := []*api.APIGatewayConfigEntry{gw} - - // setup TCPRoutes - tcpRoute := setupTCPRoute() - tcpRoutes := []*api.TCPRouteConfigEntry{tcpRoute} - - // setup inline certs - inlineCert := setupInlineCertificate() - certs := []*api.InlineCertificateConfigEntry{inlineCert} - - // setup jwt providers - jwtProvider := setupJWTProvider() - providers := []*api.JWTProviderConfigEntry{jwtProvider} - - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/v1/config/http-route": - val, err := json.Marshal(httpRoutes) - if err != nil { - w.WriteHeader(500) - fmt.Fprintln(w, err) - return - } - fmt.Fprintln(w, string(val)) - case "/v1/config/api-gateway": - val, err := json.Marshal(gateways) - if err != nil { - w.WriteHeader(500) - fmt.Fprintln(w, err) - return - } - fmt.Fprintln(w, string(val)) - case "/v1/config/tcp-route": - val, err := json.Marshal(tcpRoutes) - if err != nil { - w.WriteHeader(500) - fmt.Fprintln(w, err) - return - } - fmt.Fprintln(w, string(val)) - case "/v1/config/inline-certificate": - val, err := json.Marshal(certs) - if err != nil { - w.WriteHeader(500) - fmt.Fprintln(w, err) - return - } - fmt.Fprintln(w, string(val)) - case "/v1/config/jwt-provider": - val, err := json.Marshal(providers) - if err != nil { - w.WriteHeader(500) - fmt.Fprintln(w, err) - return - } - fmt.Fprintln(w, string(val)) - case "/v1/catalog/services": - fmt.Fprintln(w, `{}`) - case "/v1/peerings": - fmt.Fprintln(w, `[]`) - default: - w.WriteHeader(500) - fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) - } - })) - defer consulServer.Close() - - serverURL, err := url.Parse(consulServer.URL) - require.NoError(t, err) - - port, err := strconv.Atoi(serverURL.Port()) - require.NoError(t, err) - - c := New(Config{ - ConsulClientConfig: &consul.Config{ - APIClientConfig: &api.Config{}, - HTTPPort: port, - GRPCPort: port, - APITimeout: 0, - }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), - NamespacesEnabled: false, - Logger: logrtest.NewTestLogger(t), - }) - prevCache := make(map[string]*common.ReferenceMap) - for kind, cache := range c.cache { - resCache := common.NewReferenceMap() - for _, entry := range cache.Entries() { - resCache.Set(common.EntryToReference(entry), entry) - } - prevCache[kind] = resCache - } - - expectedCache := loadedReferenceMaps([]api.ConfigEntry{ - gw, tcpRoute, httpRouteOne, httpRouteTwo, inlineCert, jwtProvider, - }) - - ctx, cancelFn := context.WithCancel(context.Background()) - - httpRouteOneNsn := types.NamespacedName{ - Name: httpRouteOne.Name, - Namespace: httpRouteOne.Namespace, - } - - httpRouteTwoNsn := types.NamespacedName{ - Name: httpRouteTwo.Name, - Namespace: httpRouteTwo.Namespace, - } - - httpRouteSubscriber := c.Subscribe(ctx, api.HTTPRoute, func(cfe api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{ - {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, - } - }) - - canceledSub := c.Subscribe(ctx, api.HTTPRoute, func(cfe api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{ - {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, - } - }) - - gwNsn := types.NamespacedName{ - Name: gw.Name, - Namespace: gw.Namespace, - } - - gwSubscriber := c.Subscribe(ctx, api.APIGateway, func(cfe api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{ - {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, - } - }) - - tcpRouteNsn := types.NamespacedName{ - Name: tcpRoute.Name, - Namespace: tcpRoute.Namespace, - } - - tcpRouteSubscriber := c.Subscribe(ctx, api.TCPRoute, func(cfe api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{ - {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, - } - }) - - certNsn := types.NamespacedName{ - Name: inlineCert.Name, - Namespace: inlineCert.Namespace, - } - - certSubscriber := c.Subscribe(ctx, api.InlineCertificate, func(cfe api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{ - {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, - } - }) - - jwtProviderNsn := types.NamespacedName{ - Name: jwtProvider.Name, - Namespace: jwtProvider.Namespace, - } - - jwtSubscriber := c.Subscribe(ctx, api.JWTProvider, func(cfe api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{ - {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, - } - }) - // mark this subscription as ended - canceledSub.Cancel() - - go c.Run(ctx) - - // Check subscribers - httpRouteExpectedEvents := []event.GenericEvent{{Object: newConfigEntryObject(httpRouteOneNsn)}, {Object: newConfigEntryObject(httpRouteTwoNsn)}} - gwExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(gwNsn)} - tcpExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(tcpRouteNsn)} - certExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(certNsn)} - jwtProviderExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(jwtProviderNsn)} - - // 2 http routes + 1 gw + 1 tcp route + 1 cert + 1 jwtProvider = 6 - i := 6 - for { - if i == 0 { - break - } - select { - case actualHTTPRouteEvent := <-httpRouteSubscriber.Events(): - require.Contains(t, httpRouteExpectedEvents, actualHTTPRouteEvent) - case actualGWEvent := <-gwSubscriber.Events(): - require.Equal(t, gwExpectedEvent, actualGWEvent) - case actualTCPRouteEvent := <-tcpRouteSubscriber.Events(): - require.Equal(t, tcpExpectedEvent, actualTCPRouteEvent) - case actualCertExpectedEvent := <-certSubscriber.Events(): - require.Equal(t, certExpectedEvent, actualCertExpectedEvent) - case actualJWTExpectedEvent := <-jwtSubscriber.Events(): - require.Equal(t, jwtProviderExpectedEvent, actualJWTExpectedEvent) - } - i -= 1 - } - - // the canceled Subscription should not receive any events - require.Zero(t, len(canceledSub.Events())) - c.WaitSynced(ctx) - - // cancel the context so the Run function exits - cancelFn() - - sorter := func(x, y api.ConfigEntry) bool { - return x.GetName() < y.GetName() - } - // Check cache - // expect the cache to have changed - for _, kind := range Kinds { - if diff := cmp.Diff(prevCache[kind].Entries(), c.cache[kind].Entries(), cmpopts.SortSlices(sorter)); diff == "" { - t.Error("Expect cache to have changed but it did not") - } - - if diff := cmp.Diff(expectedCache[kind].Entries(), c.cache[kind].Entries(), cmpopts.SortSlices(sorter)); diff != "" { - t.Errorf("Cache.cache mismatch (-want +got):\n%s", diff) - } - } -} - -func setupHTTPRoutes() (*api.HTTPRouteConfigEntry, *api.HTTPRouteConfigEntry) { - routeOne := &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-1", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - "metaKey": "metaVal", - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - } - routeTwo := &api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "my route 2", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener-2", - Namespace: "ns", - }, - }, - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - Namespace: "some ns", - }, - }, - }, - }, - Hostnames: []string{"hostname.com"}, - Meta: map[string]string{ - "metakey": "meta val", - constants.MetaKeyKubeName: "name", - }, - } - return routeOne, routeTwo -} - -func setupGateway() *api.APIGatewayConfigEntry { - return &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gw", - Meta: map[string]string{ - "metakey": "meta val", - constants.MetaKeyKubeName: "name", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "listener one", - Hostname: "hostname.com", - Port: 3350, - Protocol: "https", - TLS: api.APIGatewayTLSConfiguration{}, - }, - }, - } -} - -func setupTCPRoute() *api.TCPRouteConfigEntry { - return &api.TCPRouteConfigEntry{ - Kind: api.TCPRoute, - Name: "tcp route", - Parents: []api.ResourceReference{ - { - Kind: api.APIGateway, - Name: "api-gw", - SectionName: "listener two", - }, - }, - Services: []api.TCPService{ - { - Name: "tcp service", - }, - }, - Meta: map[string]string{ - "metakey": "meta val", - constants.MetaKeyKubeName: "name", - }, - Status: api.ConfigEntryStatus{}, - } -} - -func setupInlineCertificate() *api.InlineCertificateConfigEntry { - return &api.InlineCertificateConfigEntry{ - Kind: api.InlineCertificate, - Name: "inline-cert", - Certificate: "cert", - PrivateKey: "super secret", - Meta: map[string]string{ - "metaKey": "meta val", - constants.MetaKeyKubeName: "name", - }, - } -} - -func setupJWTProvider() *api.JWTProviderConfigEntry { - return &api.JWTProviderConfigEntry{ - Kind: api.JWTProvider, - Name: "okta", - } -} - -func TestCache_Delete(t *testing.T) { - t.Parallel() - testCases := []struct { - name string - responseFn func(w http.ResponseWriter) - expectedErr error - }{ - { - name: "delete is successful", - responseFn: func(w http.ResponseWriter) { - w.WriteHeader(200) - fmt.Fprintln(w, `{deleted: true}`) - }, - expectedErr: nil, - }, - } - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - ref := api.ResourceReference{ - Name: "my-route", - Kind: api.HTTPRoute, - } - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case fmt.Sprintf("/v1/config/%s/%s", ref.Kind, ref.Name): - tt.responseFn(w) - default: - w.WriteHeader(500) - fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) - } - })) - defer consulServer.Close() - - serverURL, err := url.Parse(consulServer.URL) - require.NoError(t, err) - - port, err := strconv.Atoi(serverURL.Port()) - require.NoError(t, err) - - c := New(Config{ - ConsulClientConfig: &consul.Config{ - APIClientConfig: &api.Config{}, - HTTPPort: port, - GRPCPort: port, - APITimeout: 0, - }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), - NamespacesEnabled: false, - Logger: logrtest.NewTestLogger(t), - }) - - err = c.Delete(context.Background(), ref) - require.ErrorIs(t, err, tt.expectedErr) - }) - } -} - -func loadedReferenceMaps(entries []api.ConfigEntry) map[string]*common.ReferenceMap { - refs := make(map[string]*common.ReferenceMap) - - for _, entry := range entries { - refMap, ok := refs[entry.GetKind()] - if !ok { - refMap = common.NewReferenceMap() - } - refMap.Set(common.EntryToReference(entry), entry) - refs[entry.GetKind()] = refMap - } - return refs -} diff --git a/control-plane/api-gateway/cache/gateway.go b/control-plane/api-gateway/cache/gateway.go deleted file mode 100644 index c6d2c31099..0000000000 --- a/control-plane/api-gateway/cache/gateway.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cache - -import ( - "context" - "fmt" - "strings" - "sync" - - "github.com/cenkalti/backoff" - "github.com/go-logr/logr" - "github.com/hashicorp/consul/api" - "k8s.io/apimachinery/pkg/types" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/consul" -) - -type GatewayCache struct { - config Config - serverMgr consul.ServerConnectionManager - logger logr.Logger - - data map[api.ResourceReference][]api.CatalogService - dataMutex sync.RWMutex - - subscribedGateways map[api.ResourceReference]context.CancelFunc - mutex sync.RWMutex - - ctx context.Context -} - -func NewGatewayCache(ctx context.Context, config Config) *GatewayCache { - return &GatewayCache{ - config: config, - serverMgr: config.ConsulServerConnMgr, - logger: config.Logger, - data: make(map[api.ResourceReference][]api.CatalogService), - subscribedGateways: make(map[api.ResourceReference]context.CancelFunc), - ctx: ctx, - } -} - -func (r *GatewayCache) ServicesFor(ref api.ResourceReference) []api.CatalogService { - r.dataMutex.RLock() - defer r.dataMutex.RUnlock() - - return r.data[common.NormalizeMeta(ref)] -} - -func (r *GatewayCache) FetchServicesFor(ctx context.Context, ref api.ResourceReference) ([]api.CatalogService, error) { - client, err := consul.NewClientFromConnMgr(r.config.ConsulClientConfig, r.serverMgr) - if err != nil { - return nil, err - } - - opts := &api.QueryOptions{} - if r.config.NamespacesEnabled && ref.Namespace != "" { - opts.Namespace = ref.Namespace - } - - services, _, err := client.Catalog().Service(ref.Name, "", opts.WithContext(ctx)) - if err != nil { - return nil, err - } - return common.DerefAll(services), nil -} - -func (r *GatewayCache) EnsureSubscribed(ref api.ResourceReference, resource types.NamespacedName) { - r.mutex.Lock() - defer r.mutex.Unlock() - - if _, exists := r.subscribedGateways[common.NormalizeMeta(ref)]; exists { - return - } - - ctx, cancel := context.WithCancel(r.ctx) - r.subscribedGateways[common.NormalizeMeta(ref)] = cancel - go r.subscribeToGateway(ctx, ref, resource) -} - -func (r *GatewayCache) RemoveSubscription(ref api.ResourceReference) { - r.mutex.Lock() - defer r.mutex.Unlock() - - if cancel, exists := r.subscribedGateways[common.NormalizeMeta(ref)]; exists { - cancel() - delete(r.subscribedGateways, common.NormalizeMeta(ref)) - } -} - -func (r *GatewayCache) subscribeToGateway(ctx context.Context, ref api.ResourceReference, resource types.NamespacedName) { - opts := &api.QueryOptions{} - if r.config.NamespacesEnabled && ref.Namespace != "" { - opts.Namespace = ref.Namespace - } - - var ( - services []*api.CatalogService - meta *api.QueryMeta - ) - - for { - select { - case <-ctx.Done(): - r.dataMutex.Lock() - delete(r.data, ref) - r.dataMutex.Unlock() - return - default: - } - - retryBackoff := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 10) - - if err := backoff.Retry(func() error { - client, err := consul.NewClientFromConnMgr(r.config.ConsulClientConfig, r.serverMgr) - if err != nil { - return err - } - - services, meta, err = client.Catalog().Service(ref.Name, "", opts.WithContext(ctx)) - if err != nil { - return err - } - - return nil - }, backoff.WithContext(retryBackoff, ctx)); err != nil { - // if we timeout we don't care about the error message because it's expected to happen on long polls - // any other error we want to alert on - if !strings.Contains(strings.ToLower(err.Error()), "timeout") && - !strings.Contains(strings.ToLower(err.Error()), "no such host") && - !strings.Contains(strings.ToLower(err.Error()), "connection refused") { - r.logger.Error(err, fmt.Sprintf("unable to fetch config entry for gateway: %s/%s", ref.Namespace, ref.Name)) - } - continue - } - - opts.WaitIndex = meta.LastIndex - - derefed := common.DerefAll(services) - - r.dataMutex.Lock() - r.data[common.NormalizeMeta(ref)] = derefed - r.dataMutex.Unlock() - } -} diff --git a/control-plane/api-gateway/cache/kubernetes.go b/control-plane/api-gateway/cache/kubernetes.go deleted file mode 100644 index 642a6935fb..0000000000 --- a/control-plane/api-gateway/cache/kubernetes.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cache - -import ( - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// configEntryObject is used for generic k8s events so we maintain the consul name/namespace. -type configEntryObject struct { - client.Object // embed so we fufill the object interface - - Namespace string - Name string -} - -func (c *configEntryObject) GetNamespace() string { - return c.Namespace -} - -func (c *configEntryObject) GetName() string { - return c.Name -} - -func newConfigEntryObject(namespacedName types.NamespacedName) *configEntryObject { - return &configEntryObject{ - Namespace: namespacedName.Namespace, - Name: namespacedName.Name, - } -} diff --git a/control-plane/api-gateway/cache/subscription.go b/control-plane/api-gateway/cache/subscription.go deleted file mode 100644 index 8605c95926..0000000000 --- a/control-plane/api-gateway/cache/subscription.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cache - -import ( - "context" - - "github.com/hashicorp/consul/api" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/event" -) - -type TranslatorFn func(api.ConfigEntry) []types.NamespacedName - -// Subscription represents a watcher for events on a specific kind. -type Subscription struct { - translator TranslatorFn - ctx context.Context - cancelCtx context.CancelFunc - events chan event.GenericEvent -} - -func (s *Subscription) Cancel() { - s.cancelCtx() -} - -func (s *Subscription) Events() chan event.GenericEvent { - return s.events -} diff --git a/control-plane/api-gateway/common/constants.go b/control-plane/api-gateway/common/constants.go deleted file mode 100644 index 04701662b7..0000000000 --- a/control-plane/api-gateway/common/constants.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -const ( - GatewayClassControllerName = "consul.hashicorp.com/gateway-controller" - - AnnotationGatewayClassConfig = "consul.hashicorp.com/gateway-class-config" - - // The following annotation keys are used in the v1beta1.GatewayTLSConfig's Options on a v1beta1.Listener. - TLSCipherSuitesAnnotationKey = "api-gateway.consul.hashicorp.com/tls_cipher_suites" - TLSMaxVersionAnnotationKey = "api-gateway.consul.hashicorp.com/tls_max_version" - TLSMinVersionAnnotationKey = "api-gateway.consul.hashicorp.com/tls_min_version" -) diff --git a/control-plane/api-gateway/common/diff.go b/control-plane/api-gateway/common/diff.go deleted file mode 100644 index 7db86807b7..0000000000 --- a/control-plane/api-gateway/common/diff.go +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "strings" - - "github.com/hashicorp/consul/api" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -func GatewayStatusesEqual(a, b gwv1beta1.GatewayStatus) bool { - return slices.EqualFunc(a.Addresses, b.Addresses, gatewayStatusesAddressesEqual) && - slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) && - slices.EqualFunc(a.Listeners, b.Listeners, gatewayStatusesListenersEqual) -} - -func GatewayPolicyStatusesEqual(a, b v1alpha1.GatewayPolicyStatus) bool { - return slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) -} - -func RouteAuthFilterStatusesEqual(a, b v1alpha1.RouteAuthFilterStatus) bool { - return slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) -} - -func gatewayStatusesAddressesEqual(a, b gwv1beta1.GatewayAddress) bool { - return BothNilOrEqual(a.Type, b.Type) && - a.Value == b.Value -} - -func gatewayStatusesListenersEqual(a, b gwv1beta1.ListenerStatus) bool { - return a.AttachedRoutes == b.AttachedRoutes && - a.Name == b.Name && - slices.EqualFunc(a.SupportedKinds, b.SupportedKinds, routeGroupKindsEqual) && - slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) -} - -func routeGroupKindsEqual(a, b gwv1beta1.RouteGroupKind) bool { - return BothNilOrEqual(a.Group, b.Group) && - a.Kind == b.Kind -} - -// this intentionally ignores the last set time so we don't -// always fail a conditional check per-reconciliation. -func conditionsEqual(a, b metav1.Condition) bool { - return a.Type == b.Type && - a.Status == b.Status && - a.Reason == b.Reason && - a.Message == b.Message && - a.ObservedGeneration == b.ObservedGeneration -} - -func EntriesEqual(a, b api.ConfigEntry) bool { - switch aCast := a.(type) { - case *api.APIGatewayConfigEntry: - if bCast, ok := b.(*api.APIGatewayConfigEntry); ok { - return apiGatewaysEqual(aCast, bCast) - } - case *api.HTTPRouteConfigEntry: - if bCast, ok := b.(*api.HTTPRouteConfigEntry); ok { - return httpRoutesEqual(aCast, bCast) - } - case *api.TCPRouteConfigEntry: - if bCast, ok := b.(*api.TCPRouteConfigEntry); ok { - return tcpRoutesEqual(aCast, bCast) - } - case *api.InlineCertificateConfigEntry: - if bCast, ok := b.(*api.InlineCertificateConfigEntry); ok { - return certificatesEqual(aCast, bCast) - } - } - return false -} - -type entryComparator struct { - namespaceA string - partitionA string - namespaceB string - partitionB string -} - -func apiGatewaysEqual(a, b *api.APIGatewayConfigEntry) bool { - if a == nil || b == nil { - return false - } - - return (entryComparator{ - namespaceA: NormalizeEmptyMetadataString(a.Namespace), - partitionA: NormalizeEmptyMetadataString(a.Partition), - namespaceB: NormalizeEmptyMetadataString(b.Namespace), - partitionB: NormalizeEmptyMetadataString(b.Partition), - }).apiGatewaysEqual(*a, *b) -} - -func (e entryComparator) apiGatewaysEqual(a, b api.APIGatewayConfigEntry) bool { - return a.Kind == b.Kind && - a.Name == b.Name && - e.namespaceA == e.namespaceB && - e.partitionA == e.partitionB && - maps.Equal(a.Meta, b.Meta) && - slices.EqualFunc(a.Listeners, b.Listeners, e.apiGatewayListenersEqual) -} - -func (e entryComparator) apiGatewayListenersEqual(a, b api.APIGatewayListener) bool { - return a.Hostname == b.Hostname && - a.Name == b.Name && - a.Port == b.Port && - // normalize the protocol name - strings.EqualFold(a.Protocol, b.Protocol) && - e.apiGatewayListenerTLSConfigurationsEqual(a.TLS, b.TLS) && - e.apiGatewayPoliciesEqual(a.Override, b.Override) && - e.apiGatewayPoliciesEqual(a.Default, b.Default) -} - -func (e entryComparator) apiGatewayPoliciesEqual(a, b *api.APIGatewayPolicy) bool { - // if both are nil then return true - if a == nil && b == nil { - return true - } - - // if only one is nil then return false - if a == nil || b == nil { - return false - } - - return e.equalJWTProviders(a.JWT, b.JWT) -} - -func (e entryComparator) equalJWTProviders(a, b *api.APIGatewayJWTRequirement) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - return slices.EqualFunc(a.Providers, b.Providers, providersEqual) -} - -func providersEqual(a, b *api.APIGatewayJWTProvider) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - if a.Name != b.Name { - return false - } - - return slices.EqualFunc(a.VerifyClaims, b.VerifyClaims, equalClaims) -} - -func equalClaims(a, b *api.APIGatewayJWTClaimVerification) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - if a.Value != b.Value { - return false - } - - if len(a.Path) != len(b.Path) { - return false - } - - if !slices.Equal(a.Path, b.Path) { - return false - } - - return true -} - -func (e entryComparator) apiGatewayListenerTLSConfigurationsEqual(a, b api.APIGatewayTLSConfiguration) bool { - return a.MaxVersion == b.MaxVersion && - a.MinVersion == b.MinVersion && - slices.Equal(a.CipherSuites, b.CipherSuites) && - slices.EqualFunc(a.Certificates, b.Certificates, e.resourceReferencesEqual) -} - -func (e entryComparator) resourceReferencesEqual(a, b api.ResourceReference) bool { - return a.Kind == b.Kind && - a.Name == b.Name && - a.SectionName == b.SectionName && - orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) && - orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) -} - -func httpRoutesEqual(a, b *api.HTTPRouteConfigEntry) bool { - if a == nil || b == nil { - return false - } - - return (entryComparator{ - namespaceA: NormalizeEmptyMetadataString(a.Namespace), - partitionA: NormalizeEmptyMetadataString(a.Partition), - namespaceB: NormalizeEmptyMetadataString(b.Namespace), - partitionB: NormalizeEmptyMetadataString(b.Partition), - }).httpRoutesEqual(*a, *b) -} - -func (e entryComparator) httpRoutesEqual(a, b api.HTTPRouteConfigEntry) bool { - return a.Kind == b.Kind && - a.Name == b.Name && - e.namespaceA == e.namespaceB && - e.partitionA == e.partitionB && - maps.Equal(a.Meta, b.Meta) && - slices.Equal(a.Hostnames, b.Hostnames) && - slices.EqualFunc(a.Parents, b.Parents, e.resourceReferencesEqual) && - slices.EqualFunc(a.Rules, b.Rules, e.httpRouteRulesEqual) -} - -func (e entryComparator) httpRouteRulesEqual(a, b api.HTTPRouteRule) bool { - return slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && - bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) && - slices.EqualFunc(a.ResponseFilters.Headers, b.ResponseFilters.Headers, e.httpHeaderFiltersEqual) && - slices.EqualFunc(a.Matches, b.Matches, e.httpMatchesEqual) && - slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) && - bothNilOrEqualFunc(a.Filters.RetryFilter, b.Filters.RetryFilter, e.retryFiltersEqual) && - bothNilOrEqualFunc(a.Filters.TimeoutFilter, b.Filters.TimeoutFilter, e.timeoutFiltersEqual) && - bothNilOrEqualFunc(a.Filters.JWT, b.Filters.JWT, e.jwtFiltersEqual) -} - -func (e entryComparator) httpServicesEqual(a, b api.HTTPService) bool { - return a.Name == b.Name && - a.Weight == b.Weight && - orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) && - orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) && - slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && - bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) && - slices.EqualFunc(a.ResponseFilters.Headers, b.ResponseFilters.Headers, e.httpHeaderFiltersEqual) -} - -func (e entryComparator) httpMatchesEqual(a, b api.HTTPMatch) bool { - return a.Method == b.Method && - slices.EqualFunc(a.Headers, b.Headers, e.httpHeaderMatchesEqual) && - slices.EqualFunc(a.Query, b.Query, e.httpQueryMatchesEqual) && - e.httpPathMatchesEqual(a.Path, b.Path) -} - -func (e entryComparator) httpPathMatchesEqual(a, b api.HTTPPathMatch) bool { - return a.Match == b.Match && a.Value == b.Value -} - -func (e entryComparator) httpHeaderMatchesEqual(a, b api.HTTPHeaderMatch) bool { - return a.Match == b.Match && a.Name == b.Name && a.Value == b.Value -} - -func (e entryComparator) httpQueryMatchesEqual(a, b api.HTTPQueryMatch) bool { - return a.Match == b.Match && a.Name == b.Name && a.Value == b.Value -} - -func (e entryComparator) httpHeaderFiltersEqual(a, b api.HTTPHeaderFilter) bool { - return maps.Equal(a.Add, b.Add) && - maps.Equal(a.Set, b.Set) && - slices.Equal(a.Remove, b.Remove) -} - -func (e entryComparator) urlRewritesEqual(a, b api.URLRewrite) bool { - return a.Path == b.Path -} - -func (e entryComparator) retryFiltersEqual(a, b api.RetryFilter) bool { - return a.NumRetries == b.NumRetries && - a.RetryOnConnectFailure == b.RetryOnConnectFailure && - slices.Equal(a.RetryOn, b.RetryOn) && - slices.Equal(a.RetryOnStatusCodes, b.RetryOnStatusCodes) -} - -func (e entryComparator) timeoutFiltersEqual(a, b api.TimeoutFilter) bool { - return a.RequestTimeout == b.RequestTimeout && a.IdleTimeout == b.IdleTimeout -} - -// jwtFiltersEqual compares the contents of the list of providers on the JWT filters for a route, returning true if the -// filters have equal contents. -func (e entryComparator) jwtFiltersEqual(a, b api.JWTFilter) bool { - if len(a.Providers) != len(b.Providers) { - return false - } - - return slices.EqualFunc(a.Providers, b.Providers, providersEqual) -} - -func tcpRoutesEqual(a, b *api.TCPRouteConfigEntry) bool { - if a == nil || b == nil { - return false - } - - return (entryComparator{ - namespaceA: NormalizeEmptyMetadataString(a.Namespace), - partitionA: NormalizeEmptyMetadataString(a.Partition), - namespaceB: NormalizeEmptyMetadataString(b.Namespace), - partitionB: NormalizeEmptyMetadataString(b.Partition), - }).tcpRoutesEqual(*a, *b) -} - -func (e entryComparator) tcpRoutesEqual(a, b api.TCPRouteConfigEntry) bool { - return a.Kind == b.Kind && - a.Name == b.Name && - e.namespaceA == e.namespaceB && - e.partitionA == e.partitionB && - maps.Equal(a.Meta, b.Meta) && - slices.EqualFunc(a.Parents, b.Parents, e.resourceReferencesEqual) && - slices.EqualFunc(a.Services, b.Services, e.tcpRouteServicesEqual) -} - -func (e entryComparator) tcpRouteServicesEqual(a, b api.TCPService) bool { - return a.Name == b.Name && - orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) && - orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) -} - -func certificatesEqual(a, b *api.InlineCertificateConfigEntry) bool { - if a == nil || b == nil { - return false - } - - return (entryComparator{ - namespaceA: NormalizeEmptyMetadataString(a.Namespace), - partitionA: NormalizeEmptyMetadataString(a.Partition), - namespaceB: NormalizeEmptyMetadataString(b.Namespace), - partitionB: NormalizeEmptyMetadataString(b.Partition), - }).certificatesEqual(*a, *b) -} - -func (e entryComparator) certificatesEqual(a, b api.InlineCertificateConfigEntry) bool { - return a.Kind == b.Kind && - a.Name == b.Name && - e.namespaceA == e.namespaceB && - e.partitionA == e.partitionB && - maps.Equal(a.Meta, b.Meta) && - a.Certificate == b.Certificate && - a.PrivateKey == b.PrivateKey -} - -func bothNilOrEqualFunc[T any](one, two *T, fn func(T, T) bool) bool { - if one == nil && two == nil { - return true - } - if one == nil { - return false - } - if two == nil { - return false - } - return fn(*one, *two) -} - -func orDefault[T ~string](v T, fallback string) string { - if v == "" { - return fallback - } - return string(v) -} diff --git a/control-plane/api-gateway/common/diff_test.go b/control-plane/api-gateway/common/diff_test.go deleted file mode 100644 index 04312c8162..0000000000 --- a/control-plane/api-gateway/common/diff_test.go +++ /dev/null @@ -1,2155 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "testing" - - "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" -) - -func TestEntriesEqual(t *testing.T) { - testCases := map[string]struct { - a api.ConfigEntry - b api.ConfigEntry - expectedResult bool - }{ - "gateway equal": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: true, - }, - "gateway name different": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway-2", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway meta different": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey2": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different name": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l2", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different hostname": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host-different.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different port": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 123, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different protocol": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "https", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different TLS max version": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "15", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different TLS min version": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "0", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different TLS cipher suites": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher", "another one"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different TLS certificate references": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert-2", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different override policies jwt provider name": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "auth0", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different override policy jwt claims path": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"roles"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different override policy jwt claims value": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "user", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different default policies jwt provider name": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "auth0", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different default policy jwt claims path": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"roles"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different default policy jwt claims value": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "user", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - } - - for name, tc := range testCases { - name := name - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - actual := EntriesEqual(tc.a, tc.b) - require.Equal(t, tc.expectedResult, actual) - }) - } -} diff --git a/control-plane/api-gateway/common/finalizers.go b/control-plane/api-gateway/common/finalizers.go deleted file mode 100644 index e1fe84bdac..0000000000 --- a/control-plane/api-gateway/common/finalizers.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -const ( - // GatewayFinalizer is the finalizer we add to any gateway object. - GatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" - - // NamespaceNameLabel represents that label added automatically to namespaces in newer Kubernetes clusters. - NamespaceNameLabel = "kubernetes.io/metadata.name" -) - -var ( - // constants extracted for ease of use. - KindGateway = "Gateway" - KindSecret = "Secret" - KindService = "Service" - BetaGroup = gwv1beta1.GroupVersion.Group -) - -// EnsureFinalizer ensures that our finalizer is set on an object -// returning whether or not it modified the object. -func EnsureFinalizer(object client.Object) bool { - if !object.GetDeletionTimestamp().IsZero() { - return false - } - - finalizers := object.GetFinalizers() - for _, f := range finalizers { - if f == GatewayFinalizer { - return false - } - } - - object.SetFinalizers(append(finalizers, GatewayFinalizer)) - return true -} - -// RemoveFinalizer ensures that our finalizer is absent from an object -// returning whether or not it modified the object. -func RemoveFinalizer(object client.Object) bool { - found := false - filtered := []string{} - for _, f := range object.GetFinalizers() { - if f == GatewayFinalizer { - found = true - continue - } - filtered = append(filtered, f) - } - - object.SetFinalizers(filtered) - return found -} diff --git a/control-plane/api-gateway/common/helm_config.go b/control-plane/api-gateway/common/helm_config.go deleted file mode 100644 index 7ce8e0778a..0000000000 --- a/control-plane/api-gateway/common/helm_config.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "strings" - "time" -) - -const componentAuthMethod = "k8s-component-auth-method" - -// HelmConfig is the configuration of gateways that comes in from the user's Helm values. -// This is a combination of the apiGateway stanza and other settings that impact api-gateways. -type HelmConfig struct { - // ImageDataplane is the Consul Dataplane image to use in gateway deployments. - ImageDataplane string - // ImageConsulK8S is the Consul Kubernetes Control Plane image to use in gateway deployments. - ImageConsulK8S string - ConsulDestinationNamespace string - NamespaceMirroringPrefix string - EnableNamespaces bool - EnableNamespaceMirroring bool - AuthMethod string - - // LogLevel is the logging level of the deployed Consul Dataplanes. - LogLevel string - ConsulPartition string - LogJSON bool - TLSEnabled bool - PeeringEnabled bool - ConsulTLSServerName string - ConsulCACert string - ConsulConfig ConsulConfig - - // EnableOpenShift indicates whether we're deploying into an OpenShift environment - // and should create SecurityContextConstraints. - EnableOpenShift bool - - // MapPrivilegedServicePorts is the value which Consul will add to privileged container port values (ports < 1024) - // defined on a Gateway. - MapPrivilegedServicePorts int -} - -type ConsulConfig struct { - Address string - GRPCPort int - HTTPPort int - APITimeout time.Duration -} - -func (h HelmConfig) Normalize() HelmConfig { - if h.AuthMethod != "" { - // strip off any DC naming off the back in case we're - // in a secondary DC, in which case our auth method is - // going to be a globally scoped auth method, and we want - // to target the locally scoped one, which is the auth - // method without the DC-specific suffix. - tokens := strings.Split(h.AuthMethod, componentAuthMethod) - if len(tokens) != 2 { - // skip the normalization if we can't do it. - return h - } - h.AuthMethod = tokens[0] + componentAuthMethod - } - return h -} diff --git a/control-plane/api-gateway/common/helpers.go b/control-plane/api-gateway/common/helpers.go deleted file mode 100644 index 7bc7eb61b6..0000000000 --- a/control-plane/api-gateway/common/helpers.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul/api" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func DerefAll[T any](vs []*T) []T { - e := make([]T, 0, len(vs)) - for _, v := range vs { - e = append(e, *v) - } - return e -} - -func EmptyOrEqual(v, check string) bool { - return v == "" || v == check -} - -func NilOrEqual[T ~string](v *T, check string) bool { - return v == nil || string(*v) == check -} - -func FilterIsExternalFilter(filter gwv1beta1.HTTPRouteFilter) bool { - if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { - return false - } - - if !DerefEqual(&filter.ExtensionRef.Group, v1alpha1.ConsulHashicorpGroup) { - return false - } - - switch filter.ExtensionRef.Kind { - case v1alpha1.RouteRetryFilterKind, v1alpha1.RouteTimeoutFilterKind, v1alpha1.RouteAuthFilterKind: - return true - } - - return false - -} - -func IndexedNamespacedNameWithDefault[T ~string, U ~string, V ~string](t T, u *U, v V) types.NamespacedName { - return types.NamespacedName{ - Namespace: DerefStringOr(u, v), - Name: string(t), - } -} - -func ResourceReferenceWithDefault[T ~string, U ~string, V ~string](kind string, name T, section string, u *U, v V, partition string) api.ResourceReference { - return api.ResourceReference{ - Kind: kind, - Name: string(name), - SectionName: section, - Namespace: DerefStringOr(u, v), - Partition: partition, - } -} - -func DerefStringOr[T ~string, U ~string](v *T, val U) string { - if v == nil { - return string(val) - } - return string(*v) -} - -func DerefLookup[T comparable, U any](v *T, lookup map[T]U) U { - var zero U - if v == nil { - return zero - } - return lookup[*v] -} - -func DerefConvertFunc[T any, U any](v *T, fn func(T) U) U { - var zero U - if v == nil { - return zero - } - return fn(*v) -} - -func DerefEqual[T ~string](v *T, check string) bool { - if v == nil { - return false - } - return string(*v) == check -} - -func DerefIntOr[T ~int | ~int32, U ~int](v *T, val U) int { - if v == nil { - return int(val) - } - return int(*v) -} - -func StringLikeSlice[T ~string](vs []T) []string { - converted := []string{} - for _, v := range vs { - converted = append(converted, string(v)) - } - return converted -} - -func ConvertMapValuesToSlice[T comparable, U any](vs map[T]U) []U { - converted := []U{} - for _, v := range vs { - converted = append(converted, v) - } - return converted -} - -func ConvertSliceFunc[T any, U any](vs []T, fn func(T) U) []U { - converted := []U{} - for _, v := range vs { - converted = append(converted, fn(v)) - } - return converted -} - -func ConvertSliceFuncIf[T any, U any](vs []T, fn func(T) (U, bool)) []U { - converted := []U{} - for _, v := range vs { - if c, ok := fn(v); ok { - converted = append(converted, c) - } - } - return converted -} - -func Flatten[T any](vs [][]T) []T { - flattened := []T{} - for _, v := range vs { - flattened = append(flattened, v...) - } - return flattened -} - -func Filter[T any](vs []T, filterFn func(T) bool) []T { - filtered := []T{} - for _, v := range vs { - if !filterFn(v) { - filtered = append(filtered, v) - } - } - return filtered -} - -func DefaultOrEqual(v, fallback, check string) bool { - if v == "" { - return fallback == check - } - return v == check -} - -// ObjectsToReconcileRequests takes a list of objects and returns a list of -// reconcile Requests. -func ObjectsToReconcileRequests[T metav1.Object](objects []T) []reconcile.Request { - requests := make([]reconcile.Request, 0, len(objects)) - - for _, object := range objects { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: object.GetNamespace(), - Name: object.GetName(), - }, - }) - } - return requests -} - -// ParentRefs takes a list of ParentReference objects and returns a list of NamespacedName objects. -func ParentRefs(group, kind, namespace string, refs []gwv1beta1.ParentReference) []types.NamespacedName { - indexed := make([]types.NamespacedName, 0, len(refs)) - for _, parent := range refs { - if NilOrEqual(parent.Group, group) && NilOrEqual(parent.Kind, kind) { - indexed = append(indexed, IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace)) - } - } - return indexed -} - -// BothNilOrEqual is used to determine if two pointers to comparable -// object are either nil or both point to the same value. -func BothNilOrEqual[T comparable](one, two *T) bool { - if one == nil && two == nil { - return true - } - if one == nil { - return false - } - if two == nil { - return false - } - return *one == *two -} - -// ValueOr checks if a string-like pointer is nil, and if it is, -// returns the given value instead. -func ValueOr[T ~string](v *T, fallback string) string { - if v == nil { - return fallback - } - return string(*v) -} - -// PointerTo is a convenience method for taking a pointer -// of an object without having to declare an intermediate variable. -// It's also useful for making sure we don't accidentally take -// the pointer of a range variable directly. -func PointerTo[T any](v T) *T { - return &v -} - -// ParentsEqual checks for equality between two parent references. -func ParentsEqual(one, two gwv1beta1.ParentReference) bool { - return BothNilOrEqual(one.Group, two.Group) && - BothNilOrEqual(one.Kind, two.Kind) && - BothNilOrEqual(one.SectionName, two.SectionName) && - BothNilOrEqual(one.Port, two.Port) && - one.Name == two.Name -} - -func EntryToReference(entry api.ConfigEntry) api.ResourceReference { - return api.ResourceReference{ - Kind: entry.GetKind(), - Name: entry.GetName(), - Partition: entry.GetPartition(), - Namespace: entry.GetNamespace(), - } -} diff --git a/control-plane/api-gateway/common/helpers_test.go b/control-plane/api-gateway/common/helpers_test.go deleted file mode 100644 index 62070b434c..0000000000 --- a/control-plane/api-gateway/common/helpers_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "testing" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestBothNilOrEqual(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - first *string - second *string - expected bool - }{ - "both nil": { - first: nil, - second: nil, - expected: true, - }, - "second nil": { - first: PointerTo(""), - second: nil, - expected: false, - }, - "first nil": { - first: nil, - second: PointerTo(""), - expected: false, - }, - "both equal": { - first: PointerTo(""), - second: PointerTo(""), - expected: true, - }, - "both not equal": { - first: PointerTo("1"), - second: PointerTo("2"), - expected: false, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, BothNilOrEqual(tt.first, tt.second)) - }) - } -} - -func TestValueOr(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - value *string - or string - expected string - }{ - "nil value": { - value: nil, - or: "test", - expected: "test", - }, - "set value": { - value: PointerTo("value"), - or: "test", - expected: "value", - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, ValueOr(tt.value, tt.or)) - }) - } -} - -func TestNilOrEqual(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - value *string - check string - expected bool - }{ - "nil value": { - value: nil, - check: "test", - expected: true, - }, - "equal values": { - value: PointerTo("test"), - check: "test", - expected: true, - }, - "unequal values": { - value: PointerTo("value"), - check: "test", - expected: false, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, NilOrEqual(tt.value, tt.check)) - }) - } -} - -func TestEnsureFinalizer(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - object client.Object - expected bool - finalizers []string - }{ - "gateway no finalizer": { - object: &gwv1beta1.Gateway{}, - expected: true, - finalizers: []string{GatewayFinalizer}, - }, - "gateway other finalizer": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other"}}}, - expected: true, - finalizers: []string{"other", GatewayFinalizer}, - }, - "gateway already has finalizer": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{GatewayFinalizer}}}, - expected: false, - finalizers: []string{GatewayFinalizer}, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, EnsureFinalizer(tt.object)) - require.Equal(t, tt.finalizers, tt.object.GetFinalizers()) - }) - } -} - -func TestRemoveFinalizer(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - object client.Object - expected bool - finalizers []string - }{ - "gateway no finalizer": { - object: &gwv1beta1.Gateway{}, - expected: false, - finalizers: []string{}, - }, - "gateway other finalizer": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other"}}}, - expected: false, - finalizers: []string{"other"}, - }, - "gateway multiple finalizers": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{GatewayFinalizer, GatewayFinalizer}}}, - expected: true, - finalizers: []string{}, - }, - "gateway mixed finalizers": { - object: &gwv1beta1.Gateway{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"other", GatewayFinalizer}}}, - expected: true, - finalizers: []string{"other"}, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tt.expected, RemoveFinalizer(tt.object)) - require.Equal(t, tt.finalizers, tt.object.GetFinalizers()) - }) - } -} diff --git a/control-plane/api-gateway/common/labels.go b/control-plane/api-gateway/common/labels.go deleted file mode 100644 index 06f7857c30..0000000000 --- a/control-plane/api-gateway/common/labels.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "fmt" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -const ( - componentLabel = "component" - nameLabel = "gateway.consul.hashicorp.com/name" - namespaceLabel = "gateway.consul.hashicorp.com/namespace" - createdAtLabel = "gateway.consul.hashicorp.com/created" - ManagedLabel = "gateway.consul.hashicorp.com/managed" -) - -// LabelsForGateway formats the default labels that appear on objects managed by the controllers. -func LabelsForGateway(gateway *gwv1beta1.Gateway) map[string]string { - return map[string]string{ - componentLabel: "api-gateway", - nameLabel: gateway.Name, - namespaceLabel: gateway.Namespace, - createdAtLabel: fmt.Sprintf("%d", gateway.CreationTimestamp.Unix()), - ManagedLabel: "true", - } -} - -func GatewayFromPod(pod *corev1.Pod) (types.NamespacedName, bool) { - if pod.Labels[ManagedLabel] == "true" { - return types.NamespacedName{ - Name: pod.Labels[nameLabel], - Namespace: pod.Labels[namespaceLabel], - }, true - } - return types.NamespacedName{}, false -} diff --git a/control-plane/api-gateway/common/reference.go b/control-plane/api-gateway/common/reference.go deleted file mode 100644 index 78935c11e1..0000000000 --- a/control-plane/api-gateway/common/reference.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "sync" - - "github.com/hashicorp/consul/api" -) - -// ReferenceMap is contains a map of config entries stored -// by their normalized resource references (with empty string -// for namespaces and partitions stored as "default"). -type ReferenceMap struct { - data map[api.ResourceReference]api.ConfigEntry - ids map[api.ResourceReference]struct{} - mutex sync.RWMutex -} - -// NewReferenceMap constructs a reference map. -func NewReferenceMap() *ReferenceMap { - return &ReferenceMap{ - data: make(map[api.ResourceReference]api.ConfigEntry), - ids: make(map[api.ResourceReference]struct{}), - } -} - -func (r *ReferenceMap) IDs() []api.ResourceReference { - r.mutex.RLock() - defer r.mutex.RUnlock() - - var ids []api.ResourceReference - for id := range r.ids { - ids = append(ids, id) - } - return ids -} - -// Set adds an entry to the reference map. -func (r *ReferenceMap) Set(ref api.ResourceReference, v api.ConfigEntry) { - r.mutex.Lock() - defer r.mutex.Unlock() - - r.ids[ref] = struct{}{} - r.data[NormalizeMeta(ref)] = v -} - -// Get returns an entry from the reference map. -func (r *ReferenceMap) Get(ref api.ResourceReference) api.ConfigEntry { - r.mutex.RLock() - defer r.mutex.RUnlock() - - v, ok := r.data[NormalizeMeta(ref)] - if !ok { - return nil - } - return v -} - -// Entries returns a list of entries stored in the reference map. -func (r *ReferenceMap) Entries() []api.ConfigEntry { - r.mutex.RLock() - defer r.mutex.RUnlock() - - entries := make([]api.ConfigEntry, 0, len(r.data)) - for _, entry := range r.data { - entries = append(entries, entry) - } - return entries -} - -// Delete deletes an entry stored in the reference map. -func (r *ReferenceMap) Delete(ref api.ResourceReference) { - r.mutex.Lock() - defer r.mutex.Unlock() - - delete(r.ids, ref) - delete(r.data, NormalizeMeta(ref)) -} - -// Diff calculates the difference between the stored entries in two reference maps. -func (r *ReferenceMap) Diff(other *ReferenceMap) []api.ConfigEntry { - r.mutex.RLock() - defer r.mutex.RUnlock() - - other.mutex.RLock() - defer other.mutex.RUnlock() - - diffs := make([]api.ConfigEntry, 0) - - for ref, entry := range other.data { - oldRef := r.Get(ref) - // ref from the new cache doesn't exist in the old one - // this means a resource was added - if oldRef == nil { - diffs = append(diffs, entry) - continue - } - - // the entry in the old cache has an older modify index than the ref - // from the new cache - if oldRef.GetModifyIndex() < entry.GetModifyIndex() { - diffs = append(diffs, entry) - } - } - - // get all deleted entries, these are entries present in the old cache - // that are not present in the new - for ref, entry := range r.data { - if other.Get(ref) == nil { - diffs = append(diffs, entry) - } - } - - return diffs -} - -// ReferenceSet is a set of stored references. -type ReferenceSet struct { - data map[api.ResourceReference]struct{} - ids map[api.ResourceReference]struct{} - - mutex sync.RWMutex -} - -// NewReferenceSet constructs a new reference set. -func NewReferenceSet() *ReferenceSet { - return &ReferenceSet{ - data: make(map[api.ResourceReference]struct{}), - ids: make(map[api.ResourceReference]struct{}), - } -} - -// Mark adds a reference to the reference set. -func (r *ReferenceSet) Mark(ref api.ResourceReference) { - r.mutex.Lock() - defer r.mutex.Unlock() - - r.ids[ref] = struct{}{} - r.data[NormalizeMeta(ref)] = struct{}{} -} - -// Contains checks for the inclusion of a reference in the set. -func (r *ReferenceSet) Contains(ref api.ResourceReference) bool { - r.mutex.RLock() - defer r.mutex.RUnlock() - - _, ok := r.data[NormalizeMeta(ref)] - return ok -} - -// Remove drops a reference from the set. -func (r *ReferenceSet) Remove(ref api.ResourceReference) { - r.mutex.Lock() - defer r.mutex.Unlock() - - delete(r.ids, ref) - delete(r.data, NormalizeMeta(ref)) -} - -func (r *ReferenceSet) IDs() []api.ResourceReference { - r.mutex.RLock() - defer r.mutex.RUnlock() - - var ids []api.ResourceReference - for id := range r.ids { - ids = append(ids, id) - } - return ids -} - -func NormalizeMeta(ref api.ResourceReference) api.ResourceReference { - ref.Namespace = NormalizeEmptyMetadataString(ref.Namespace) - ref.Partition = NormalizeEmptyMetadataString(ref.Partition) - return ref -} - -func NormalizeEmptyMetadataString(metaString string) string { - if metaString == "" { - return "default" - } - return metaString -} diff --git a/control-plane/api-gateway/common/resources.go b/control-plane/api-gateway/common/resources.go deleted file mode 100644 index 051c914ae7..0000000000 --- a/control-plane/api-gateway/common/resources.go +++ /dev/null @@ -1,720 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - mapset "github.com/deckarep/golang-set" - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -// ConsulUpdateOperation is an operation representing an -// update in Consul. -type ConsulUpdateOperation struct { - // Entry is the ConfigEntry to write to Consul. - Entry api.ConfigEntry - // OnUpdate is an optional callback to fire after running - // the Consul update operation. If specified, then no more - // error handling occurs after the function is called, otherwise - // normal error handling logic applies. - OnUpdate func(err error) -} - -type gvkNamespacedName struct { - gvk string - nsn types.NamespacedName -} - -// KubernetesUpdates holds all update operations (including status) -// that need to be synced to Kubernetes. So long as you're -// modifying the same pointer object passed in to its Add -// function, this de-duplicates any calls to Add, in order -// for us to Add any previously unseen entires, but ignore -// them if they've already been added. -type KubernetesUpdates struct { - operations map[gvkNamespacedName]client.Object -} - -func NewKubernetesUpdates() *KubernetesUpdates { - return &KubernetesUpdates{ - operations: make(map[gvkNamespacedName]client.Object), - } -} - -func (k *KubernetesUpdates) Add(object client.Object) { - k.operations[gvkNamespacedName{ - gvk: object.GetObjectKind().GroupVersionKind().String(), - nsn: client.ObjectKeyFromObject(object), - }] = object -} - -func (k *KubernetesUpdates) Operations() []client.Object { - return ConvertMapValuesToSlice(k.operations) -} - -type ReferenceValidator interface { - GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool - HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool - TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool -} - -type certificate struct { - secret *corev1.Secret - gateways mapset.Set -} - -type httpRoute struct { - route gwv1beta1.HTTPRoute - gateways mapset.Set -} - -type tcpRoute struct { - route gwv1alpha2.TCPRoute - gateways mapset.Set -} - -type consulHTTPRoute struct { - route api.HTTPRouteConfigEntry - gateways mapset.Set -} - -type consulTCPRoute struct { - route api.TCPRouteConfigEntry - gateways mapset.Set -} - -type resourceSet struct { - httpRoutes mapset.Set - tcpRoutes mapset.Set - certificates mapset.Set - - consulObjects *ReferenceSet -} - -type ResourceMap struct { - translator ResourceTranslator - referenceValidator ReferenceValidator - logger logr.Logger - - services map[types.NamespacedName]api.ResourceReference - meshServices map[types.NamespacedName]api.ResourceReference - certificates mapset.Set - - // this acts a a secondary store of what has not yet - // been processed for the sake of garbage collection. - processedCertificates mapset.Set - certificateGateways map[api.ResourceReference]*certificate - tcpRouteGateways map[api.ResourceReference]*tcpRoute - httpRouteGateways map[api.ResourceReference]*httpRoute - gatewayResources map[api.ResourceReference]*resourceSet - externalFilters map[corev1.ObjectReference]client.Object - gatewayPolicies map[api.ResourceReference]*v1alpha1.GatewayPolicy - - // consul resources for a gateway - consulTCPRoutes map[api.ResourceReference]*consulTCPRoute - consulHTTPRoutes map[api.ResourceReference]*consulHTTPRoute - jwtProviders map[api.ResourceReference]*v1alpha1.JWTProvider - - // mutations - consulMutations []*ConsulUpdateOperation -} - -func NewResourceMap(translator ResourceTranslator, validator ReferenceValidator, logger logr.Logger) *ResourceMap { - return &ResourceMap{ - translator: translator, - referenceValidator: validator, - logger: logger, - processedCertificates: mapset.NewSet(), - services: make(map[types.NamespacedName]api.ResourceReference), - meshServices: make(map[types.NamespacedName]api.ResourceReference), - certificates: mapset.NewSet(), - consulTCPRoutes: make(map[api.ResourceReference]*consulTCPRoute), - consulHTTPRoutes: make(map[api.ResourceReference]*consulHTTPRoute), - certificateGateways: make(map[api.ResourceReference]*certificate), - tcpRouteGateways: make(map[api.ResourceReference]*tcpRoute), - httpRouteGateways: make(map[api.ResourceReference]*httpRoute), - gatewayResources: make(map[api.ResourceReference]*resourceSet), - gatewayPolicies: make(map[api.ResourceReference]*v1alpha1.GatewayPolicy), - jwtProviders: make(map[api.ResourceReference]*v1alpha1.JWTProvider), - } -} - -func (s *ResourceMap) AddService(id types.NamespacedName, name string) { - // this needs to be not-normalized since it gets written straight - // to Consul's configuration, including in non-enterprise builds. - s.services[id] = api.ResourceReference{ - Name: name, - Namespace: s.translator.Namespace(id.Namespace), - Partition: s.translator.ConsulPartition, - } -} - -func (s *ResourceMap) Service(id types.NamespacedName) api.ResourceReference { - return s.services[id] -} - -func (s *ResourceMap) HasService(id types.NamespacedName) bool { - _, ok := s.services[id] - return ok -} - -func (s *ResourceMap) AddMeshService(service v1alpha1.MeshService) { - // this needs to be not-normalized since it gets written straight - // to Consul's configuration, including in non-enterprise builds. - key := client.ObjectKeyFromObject(&service) - s.meshServices[key] = api.ResourceReference{ - Name: service.Spec.Name, - Namespace: s.translator.Namespace(service.Namespace), - Partition: s.translator.ConsulPartition, - } -} - -func (s *ResourceMap) MeshService(id types.NamespacedName) api.ResourceReference { - return s.meshServices[id] -} - -func (s *ResourceMap) HasMeshService(id types.NamespacedName) bool { - _, ok := s.meshServices[id] - return ok -} - -func (s *ResourceMap) Certificate(key types.NamespacedName) *corev1.Secret { - if !s.certificates.Contains(key) { - return nil - } - consulKey := NormalizeMeta(s.toConsulReference(api.InlineCertificate, key)) - if secret, ok := s.certificateGateways[consulKey]; ok { - return secret.secret - } - return nil -} - -func (s *ResourceMap) ReferenceCountCertificate(secret corev1.Secret) { - key := client.ObjectKeyFromObject(&secret) - s.certificates.Add(key) - consulKey := NormalizeMeta(s.toConsulReference(api.InlineCertificate, key)) - if _, ok := s.certificateGateways[consulKey]; !ok { - s.certificateGateways[consulKey] = &certificate{ - secret: &secret, - gateways: mapset.NewSet(), - } - } -} - -func (s *ResourceMap) ReferenceCountGateway(gateway gwv1beta1.Gateway) { - key := client.ObjectKeyFromObject(&gateway) - consulKey := NormalizeMeta(s.toConsulReference(api.APIGateway, key)) - - set := &resourceSet{ - httpRoutes: mapset.NewSet(), - tcpRoutes: mapset.NewSet(), - certificates: mapset.NewSet(), - consulObjects: NewReferenceSet(), - } - - for _, listener := range gateway.Spec.Listeners { - if listener.TLS == nil || (listener.TLS.Mode != nil && *listener.TLS.Mode != gwv1beta1.TLSModeTerminate) { - continue - } - for _, cert := range listener.TLS.CertificateRefs { - if NilOrEqual(cert.Group, "") && NilOrEqual(cert.Kind, "Secret") { - certificateKey := IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace) - - set.certificates.Add(certificateKey) - - consulCertificateKey := s.toConsulReference(api.InlineCertificate, certificateKey) - certificate, ok := s.certificateGateways[NormalizeMeta(consulCertificateKey)] - if ok { - certificate.gateways.Add(key) - set.consulObjects.Mark(consulCertificateKey) - } - } - } - } - - s.gatewayResources[consulKey] = set -} - -func (s *ResourceMap) ResourcesToGC(key types.NamespacedName) []api.ResourceReference { - consulKey := NormalizeMeta(s.toConsulReference(api.APIGateway, key)) - - resources, ok := s.gatewayResources[consulKey] - if !ok { - return nil - } - - var toGC []api.ResourceReference - - for _, id := range resources.consulObjects.IDs() { - // if any of these objects exist in the below maps - // it means we haven't "popped" it to be created - switch id.Kind { - case api.HTTPRoute: - if route, ok := s.consulHTTPRoutes[NormalizeMeta(id)]; ok && route.gateways.Cardinality() <= 1 { - // we only have a single reference, which will be this gateway, so drop - // the route altogether - toGC = append(toGC, id) - } - case api.TCPRoute: - if route, ok := s.consulTCPRoutes[NormalizeMeta(id)]; ok && route.gateways.Cardinality() <= 1 { - // we only have a single reference, which will be this gateway, so drop - // the route altogether - toGC = append(toGC, id) - } - case api.InlineCertificate: - if s.processedCertificates.Contains(id) { - continue - } - if route, ok := s.certificateGateways[NormalizeMeta(id)]; ok && route.gateways.Cardinality() <= 1 { - // we only have a single reference, which will be this gateway, so drop - // the route altogether - toGC = append(toGC, id) - } - } - } - - return toGC -} - -func (s *ResourceMap) ReferenceCountConsulHTTPRoute(route api.HTTPRouteConfigEntry) { - key := s.objectReference(&route) - - set := &consulHTTPRoute{ - route: route, - gateways: mapset.NewSet(), - } - - for gatewayKey := range s.consulGatewaysForRoute(route.Namespace, route.Parents).Iter() { - if gateway, ok := s.gatewayResources[gatewayKey.(api.ResourceReference)]; ok { - gateway.consulObjects.Mark(key) - } - - set.gateways.Add(gatewayKey) - } - - s.consulHTTPRoutes[NormalizeMeta(key)] = set -} - -func (s *ResourceMap) ReferenceCountConsulTCPRoute(route api.TCPRouteConfigEntry) { - key := s.objectReference(&route) - - set := &consulTCPRoute{ - route: route, - gateways: mapset.NewSet(), - } - - for gatewayKey := range s.consulGatewaysForRoute(route.Namespace, route.Parents).Iter() { - if gateway, ok := s.gatewayResources[gatewayKey.(api.ResourceReference)]; ok { - gateway.consulObjects.Mark(key) - } - - set.gateways.Add(gatewayKey) - } - - s.consulTCPRoutes[NormalizeMeta(key)] = set -} - -func (s *ResourceMap) ReferenceCountConsulCertificate(cert api.InlineCertificateConfigEntry) { - key := s.objectReference(&cert) - - var referenced *certificate - if existing, ok := s.certificateGateways[NormalizeMeta(key)]; ok { - referenced = existing - } else { - referenced = &certificate{ - gateways: mapset.NewSet(), - } - } - - s.certificateGateways[NormalizeMeta(key)] = referenced -} - -func (s *ResourceMap) consulGatewaysForRoute(namespace string, refs []api.ResourceReference) mapset.Set { - gateways := mapset.NewSet() - - for _, parent := range refs { - if EmptyOrEqual(parent.Kind, api.APIGateway) { - key := s.sectionlessParentReference(api.APIGateway, namespace, parent) - gateways.Add(key) - } - } - - return gateways -} - -func (s *ResourceMap) ReferenceCountHTTPRoute(route gwv1beta1.HTTPRoute) { - key := client.ObjectKeyFromObject(&route) - consulKey := NormalizeMeta(s.toConsulReference(api.HTTPRoute, key)) - - set := &httpRoute{ - route: route, - gateways: mapset.NewSet(), - } - - for gatewayKey := range s.gatewaysForRoute(route.Namespace, route.Spec.ParentRefs).Iter() { - set.gateways.Add(gatewayKey.(api.ResourceReference)) - - gateway := s.gatewayResources[gatewayKey.(api.ResourceReference)] - gateway.httpRoutes.Add(consulKey) - } - - s.httpRouteGateways[consulKey] = set -} - -func localObjectReferenceToObjectReference(filterRef gwv1beta1.LocalObjectReference, namespace string) corev1.ObjectReference { - return corev1.ObjectReference{ - Kind: string(filterRef.Kind), - Name: string(filterRef.Name), - Namespace: namespace, - } -} - -func objectToObjectReference(object client.Object) corev1.ObjectReference { - return corev1.ObjectReference{ - Kind: object.GetObjectKind().GroupVersionKind().Kind, - Name: object.GetName(), - Namespace: object.GetNamespace(), - } -} - -func (s *ResourceMap) AddExternalFilter(filter client.Object) { - if s.externalFilters == nil { - s.externalFilters = make(map[corev1.ObjectReference]client.Object) - } - - key := objectToObjectReference(filter) - s.externalFilters[key] = filter -} - -func (s *ResourceMap) GetExternalFilter(filterRef gwv1beta1.LocalObjectReference, namespace string) (client.Object, bool) { - key := localObjectReferenceToObjectReference(filterRef, namespace) - filter, ok := s.externalFilters[key] - return filter, ok -} - -func (s *ResourceMap) ExternalFilterExists(filterRef gwv1beta1.LocalObjectReference, namespace string) bool { - _, ok := s.GetExternalFilter(filterRef, namespace) - return ok -} - -func (s *ResourceMap) GetExternalAuthFilters() []*v1alpha1.RouteAuthFilter { - filters := make([]*v1alpha1.RouteAuthFilter, 0, len(s.externalFilters)) - for _, filter := range s.externalFilters { - if authFilter, ok := filter.(*v1alpha1.RouteAuthFilter); ok { - filters = append(filters, authFilter) - } - } - return filters -} - -func (s *ResourceMap) AddGatewayPolicy(gatewayPolicy *v1alpha1.GatewayPolicy) *v1alpha1.GatewayPolicy { - sectionName := "" - if gatewayPolicy.Spec.TargetRef.SectionName != nil { - sectionName = string(*gatewayPolicy.Spec.TargetRef.SectionName) - } - - gwNamespace := gatewayPolicy.Spec.TargetRef.Namespace - if gwNamespace == "" { - gwNamespace = gatewayPolicy.Namespace - } - - key := api.ResourceReference{ - Kind: gatewayPolicy.Spec.TargetRef.Kind, - Name: gatewayPolicy.Spec.TargetRef.Name, - SectionName: sectionName, - Namespace: gwNamespace, - } - - if s.gatewayPolicies == nil { - s.gatewayPolicies = make(map[api.ResourceReference]*v1alpha1.GatewayPolicy) - } - - s.gatewayPolicies[key] = gatewayPolicy - - return s.gatewayPolicies[key] -} - -func (s *ResourceMap) AddJWTProvider(provider *v1alpha1.JWTProvider) { - key := api.ResourceReference{ - Kind: provider.Kind, - Name: provider.Name, - } - s.jwtProviders[key] = provider -} - -func (s *ResourceMap) GetJWTProviderForGatewayJWTProvider(provider *v1alpha1.GatewayJWTProvider) (*v1alpha1.JWTProvider, bool) { - key := api.ResourceReference{ - Name: provider.Name, - Kind: "JWTProvider", - } - - value, exists := s.jwtProviders[key] - return value, exists -} - -func (s *ResourceMap) GetPolicyForGatewayListener(gateway gwv1beta1.Gateway, gatewayListener gwv1beta1.Listener) (*v1alpha1.GatewayPolicy, bool) { - key := api.ResourceReference{ - Name: gateway.Name, - Kind: gateway.Kind, - SectionName: string(gatewayListener.Name), - Namespace: gateway.Namespace, - } - - value, exists := s.gatewayPolicies[key] - - return value, exists -} - -func (s *ResourceMap) ReferenceCountTCPRoute(route gwv1alpha2.TCPRoute) { - key := client.ObjectKeyFromObject(&route) - consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) - - set := &tcpRoute{ - route: route, - gateways: mapset.NewSet(), - } - - for gatewayKey := range s.gatewaysForRoute(route.Namespace, route.Spec.ParentRefs).Iter() { - set.gateways.Add(gatewayKey.(api.ResourceReference)) - - gateway := s.gatewayResources[gatewayKey.(api.ResourceReference)] - gateway.tcpRoutes.Add(consulKey) - } - - s.tcpRouteGateways[consulKey] = set -} - -func (s *ResourceMap) gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference) mapset.Set { - gateways := mapset.NewSet() - - for _, parent := range refs { - if NilOrEqual(parent.Group, gwv1beta1.GroupVersion.Group) && NilOrEqual(parent.Kind, "Gateway") { - key := IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace) - consulKey := NormalizeMeta(s.toConsulReference(api.APIGateway, key)) - - if _, ok := s.gatewayResources[consulKey]; ok { - gateways.Add(consulKey) - } - } - } - - return gateways -} - -func (s *ResourceMap) TranslateAndMutateHTTPRoute(key types.NamespacedName, onUpdate func(error, api.ConfigEntryStatus), mutateFn func(old *api.HTTPRouteConfigEntry, new api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { - consulKey := NormalizeMeta(s.toConsulReference(api.HTTPRoute, key)) - - route, ok := s.httpRouteGateways[consulKey] - if !ok { - return - } - - translated := s.translator.ToHTTPRoute(route.route, s) - - consulRoute, ok := s.consulHTTPRoutes[consulKey] - if ok { - mutated := mutateFn(&consulRoute.route, *translated) - if len(mutated.Parents) != 0 { - // if we don't have any parents set, we keep this around to allow the route - // to be GC'd. - delete(s.consulHTTPRoutes, consulKey) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: func(err error) { - onUpdate(err, mutated.Status) - }, - }) - } - return - } - mutated := mutateFn(nil, *translated) - if len(mutated.Parents) != 0 { - // if we don't have any parents set, we keep this around to allow the route - // to be GC'd. - delete(s.consulHTTPRoutes, consulKey) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: func(err error) { - onUpdate(err, mutated.Status) - }, - }) - } -} - -func (s *ResourceMap) MutateHTTPRoute(key types.NamespacedName, onUpdate func(error, api.ConfigEntryStatus), mutateFn func(api.HTTPRouteConfigEntry) api.HTTPRouteConfigEntry) { - consulKey := NormalizeMeta(s.toConsulReference(api.HTTPRoute, key)) - - consulRoute, ok := s.consulHTTPRoutes[consulKey] - if ok { - mutated := mutateFn(consulRoute.route) - if len(mutated.Parents) != 0 { - // if we don't have any parents set, we keep this around to allow the route - // to be GC'd. - delete(s.consulHTTPRoutes, consulKey) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: func(err error) { - onUpdate(err, mutated.Status) - }, - }) - } - } -} - -func (s *ResourceMap) CanGCHTTPRouteOnUnbind(id api.ResourceReference) bool { - if set := s.httpRouteGateways[NormalizeMeta(id)]; set != nil { - return set.gateways.Cardinality() <= 1 - } - return true -} - -func (s *ResourceMap) TranslateAndMutateTCPRoute(key types.NamespacedName, onUpdate func(error, api.ConfigEntryStatus), mutateFn func(*api.TCPRouteConfigEntry, api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { - consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) - - route, ok := s.tcpRouteGateways[consulKey] - if !ok { - return - } - - translated := s.translator.ToTCPRoute(route.route, s) - - consulRoute, ok := s.consulTCPRoutes[consulKey] - if ok { - mutated := mutateFn(&consulRoute.route, *translated) - if len(mutated.Parents) != 0 { - // if we don't have any parents set, we keep this around to allow the route - // to be GC'd. - delete(s.consulTCPRoutes, consulKey) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: func(err error) { - onUpdate(err, mutated.Status) - }, - }) - } - return - } - mutated := mutateFn(nil, *translated) - if len(mutated.Parents) != 0 { - // if we don't have any parents set, we keep this around to allow the route - // to be GC'd. - delete(s.consulTCPRoutes, consulKey) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: func(err error) { - onUpdate(err, mutated.Status) - }, - }) - } -} - -func (s *ResourceMap) MutateTCPRoute(key types.NamespacedName, onUpdate func(error, api.ConfigEntryStatus), mutateFn func(api.TCPRouteConfigEntry) api.TCPRouteConfigEntry) { - consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) - - consulRoute, ok := s.consulTCPRoutes[consulKey] - if ok { - mutated := mutateFn(consulRoute.route) - if len(mutated.Parents) != 0 { - // if we don't have any parents set, we keep this around to allow the route - // to be GC'd. - delete(s.consulTCPRoutes, consulKey) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: &mutated, - OnUpdate: func(err error) { - onUpdate(err, mutated.Status) - }, - }) - } - } -} - -func (s *ResourceMap) CanGCTCPRouteOnUnbind(id api.ResourceReference) bool { - if set := s.tcpRouteGateways[NormalizeMeta(id)]; set != nil { - return set.gateways.Cardinality() <= 1 - } - return true -} - -func (s *ResourceMap) TranslateInlineCertificate(key types.NamespacedName) error { - consulKey := s.toConsulReference(api.InlineCertificate, key) - - certificate, ok := s.certificateGateways[NormalizeMeta(consulKey)] - if !ok { - return nil - } - - if certificate.secret == nil { - return nil - } - - consulCertificate, err := s.translator.ToInlineCertificate(*certificate.secret) - if err != nil { - return err - } - - // add to the processed set so we don't GC it. - s.processedCertificates.Add(consulKey) - s.consulMutations = append(s.consulMutations, &ConsulUpdateOperation{ - Entry: consulCertificate, - // just swallow the error and log it since we can't propagate status back on a certificate. - OnUpdate: func(error) { - if err != nil { - s.logger.Error(err, "error syncing certificate to Consul") - } - }, - }) - - return nil -} - -func (s *ResourceMap) Mutations() []*ConsulUpdateOperation { - return s.consulMutations -} - -func (s *ResourceMap) objectReference(o api.ConfigEntry) api.ResourceReference { - return api.ResourceReference{ - Kind: o.GetKind(), - Name: o.GetName(), - Namespace: o.GetNamespace(), - Partition: s.translator.ConsulPartition, - } -} - -func (s *ResourceMap) sectionlessParentReference(kind, namespace string, parent api.ResourceReference) api.ResourceReference { - return NormalizeMeta(api.ResourceReference{ - Kind: kind, - Name: parent.Name, - Namespace: orDefault(parent.Namespace, namespace), - Partition: s.translator.ConsulPartition, - }) -} - -func (s *ResourceMap) toConsulReference(kind string, key types.NamespacedName) api.ResourceReference { - return api.ResourceReference{ - Kind: kind, - Name: key.Name, - Namespace: s.translator.Namespace(key.Namespace), - Partition: s.translator.ConsulPartition, - } -} - -func (s *ResourceMap) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, ref gwv1beta1.SecretObjectReference) bool { - return s.referenceValidator.GatewayCanReferenceSecret(gateway, ref) -} - -func (s *ResourceMap) HTTPRouteCanReferenceBackend(route gwv1beta1.HTTPRoute, ref gwv1beta1.BackendRef) bool { - return s.referenceValidator.HTTPRouteCanReferenceBackend(route, ref) -} - -func (s *ResourceMap) TCPRouteCanReferenceBackend(route gwv1alpha2.TCPRoute, ref gwv1beta1.BackendRef) bool { - return s.referenceValidator.TCPRouteCanReferenceBackend(route, ref) -} diff --git a/control-plane/api-gateway/common/resources_test.go b/control-plane/api-gateway/common/resources_test.go deleted file mode 100644 index 7f5619496f..0000000000 --- a/control-plane/api-gateway/common/resources_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -func TestResourceMap_JWTProvider(t *testing.T) { - resourceMap := NewResourceMap(ResourceTranslator{}, mockReferenceValidator{}, logrtest.New(t)) - require.Empty(t, resourceMap.jwtProviders) - provider := &v1alpha1.JWTProvider{ - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-jwt", - }, - Spec: v1alpha1.JWTProviderSpec{}, - } - - key := api.ResourceReference{ - Name: provider.Name, - Kind: "JWTProvider", - } - - resourceMap.AddJWTProvider(provider) - - require.Len(t, resourceMap.jwtProviders, 1) - require.NotNil(t, resourceMap.jwtProviders[key]) - require.Equal(t, resourceMap.jwtProviders[key], provider) -} - -type mockReferenceValidator struct{} - -func (m mockReferenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool { - return true -} - -func (m mockReferenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { - return true -} - -func (m mockReferenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { - return true -} diff --git a/control-plane/api-gateway/common/secrets.go b/control-plane/api-gateway/common/secrets.go deleted file mode 100644 index 1b7d8dec33..0000000000 --- a/control-plane/api-gateway/common/secrets.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "crypto/tls" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - - "github.com/miekg/dns" - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/version" -) - -var ( - errFailedToParsePrivateKeyPem = errors.New("failed to parse private key PEM") - errKeyLengthTooShort = errors.New("RSA key length must be at least 2048-bit") - errKeyLengthTooShortFIPS = errors.New("RSA key length must be at either 2048-bit, 3072-bit, or 4096-bit in FIPS mode") -) - -func ParseCertificateData(secret corev1.Secret) (cert string, privateKey string, err error) { - decodedPrivateKey := secret.Data[corev1.TLSPrivateKeyKey] - decodedCertificate := secret.Data[corev1.TLSCertKey] - - privateKeyBlock, _ := pem.Decode(decodedPrivateKey) - if privateKeyBlock == nil { - return "", "", errFailedToParsePrivateKeyPem - } - - certificateBlock, _ := pem.Decode(decodedCertificate) - if certificateBlock == nil { - return "", "", errors.New("failed to parse certificate PEM") - } - - // make sure we have a valid x509 certificate - certificate, err := x509.ParseCertificate(certificateBlock.Bytes) - if err != nil { - return "", "", err - } - - // validate that the cert was generated with the given private key - _, err = tls.X509KeyPair(decodedCertificate, decodedPrivateKey) - if err != nil { - return "", "", err - } - - // validate that each host referenced in the CN, DNSSans, and IPSans - // are valid hostnames - if err := validateCertificateHosts(certificate); err != nil { - return "", "", err - } - - return string(decodedCertificate), string(decodedPrivateKey), nil -} - -func validateCertificateHosts(certificate *x509.Certificate) error { - hosts := []string{certificate.Subject.CommonName} - - hosts = append(hosts, certificate.DNSNames...) - - for _, ip := range certificate.IPAddresses { - hosts = append(hosts, ip.String()) - } - - for _, host := range hosts { - if _, ok := dns.IsDomainName(host); !ok { - return fmt.Errorf("host %q must be a valid DNS hostname", host) - } - } - - return nil -} - -// Envoy will silently reject any keys that are less than 2048 bytes long -// https://github.com/envoyproxy/envoy/blob/main/source/extensions/transport_sockets/tls/context_impl.cc#L238 -const MinKeyLength = 2048 - -// ValidateKeyLength ensures that the key length for a certificate is of a valid length -// for envoy dependent on if consul is running in FIPS mode or not. -func ValidateKeyLength(privateKey string) error { - privateKeyBlock, _ := pem.Decode([]byte(privateKey)) - - if privateKeyBlock == nil { - return errFailedToParsePrivateKeyPem - } - - if privateKeyBlock.Type != "RSA PRIVATE KEY" { - return nil - } - - key, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes) - if err != nil { - return err - } - - keyBitLen := key.N.BitLen() - - if version.IsFIPS() { - return fipsLenCheck(keyBitLen) - } - - return nonFipsLenCheck(keyBitLen) -} - -func nonFipsLenCheck(keyLen int) error { - // ensure private key is of the correct length - if keyLen < MinKeyLength { - return errKeyLengthTooShort - } - - return nil -} - -func fipsLenCheck(keyLen int) error { - if keyLen != 2048 && keyLen != 3072 && keyLen != 4096 { - return errKeyLengthTooShortFIPS - } - return nil -} diff --git a/control-plane/api-gateway/common/secrets_test.go b/control-plane/api-gateway/common/secrets_test.go deleted file mode 100644 index 223e8aa24e..0000000000 --- a/control-plane/api-gateway/common/secrets_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestValidateKeyLength(t *testing.T) { - tooShortPrivateKey := `-----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCtmK1VjmXJ7vm4CZkkOSjc+kjGNMlyce5rXxwlDRz9LcGGc3Tg -kwUJesyBpDtxLLVHXQIPr5mWYbX/W/ezQ9sntxrATbDek8pBgoOlARebwkD2ivVW -BWfVhlryVihWlXApKiJ2n3i0m+OVtdrceC9Bv2hEMhYVOwzxtb3O0YFkbwIDAQAB -AoGAIxgnipFUEKPIRiVimUkY8ruCdNd9Fi7kNT6wEOl6v9A9PHIg4bm3Hfh+WYMb -JUEVkMzDuuoUEavFQE+WXt5L8oE1lEBmN2++FQsvllN+MRBTRg2sfw4mUWDI6S4r -h8+XNTzTIg2sUd2J3o2qNmQoOheYb+iuYDj76IFoEdwwZ0kCQQDYKKs5HAbnrLj1 -UrOp8TyHdFf0YNw5tGdbNTbffq4rlBD6SW70+Sj624i2UqdnYwRiWzdXv3zN08aI -Vfoh2cGlAkEAzZe5B6BhiX/PcIYutMtuT3K+mysFNlowrutXWoQOpR7gGAkgEt6e -oCDgx1QJRjsp6NFQxKc6l034Hzs17gqJgwJAcu9U873aUg9+HTuHOoKB28haCCAE -mU46cr3d2oKCW7uUN3EaZXmid5iJneBfENMOfrnfuHGiC9NiShXlNWCS3QJAO5Ne -w83+1ahaxUGs4SkeExmuECrcPM7P0rBRxOIFmGWlDHIAgFdQYhiE6l34vghA8b1O -CV5oRRYL84jl7M/S3wJBALDfL5YXcc8P6scLJJ1biqhLYppvGN5CUwbsJsluvHCW -XCTVIbPOaS42A0xUfpoiTcdbNSFRvdCzPR5nsGy8Y7g= ------END RSA PRIVATE KEY-----` - validPrivateKey := `-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAzVKRcYlTHHPjPbCieOFIUT2hCouRYe4N8ZhNrSpZf/BAAn4M -d/LWn/9OrLagbxrRF6cWdWGNEI2COnBRLgNVxyPXneaHaYFqOBRi9GWhuD3sw1jn -7gf4/m/AVO8cu2JYjEX+s9RjSRzpjx+4nhit46bGNUyb9qUeQwoBidAzOSmU8nHY -y3LpuuzkjS3FEyNXHxqgpTJnV4ytx8YGkPnG92GBAlrZnr4Eclv0/Sq6OViTpeuh -z8noNkbugYWHMXGlTZ4lPnELJW2fx/HIpD2ovOO3X8XYBo5KDzs9qyKzDgIOMZLF -i/qLCLHgfosb4TMaXCeVu4fA7Y47jtGOO4mbiwIDAQABAoIBAFhicDibIDtRyaLv -K+l0NPC/4liLPwCUfM0gvmNKJS/VSICqKQzjbK+ANCpWDVb2iMaxRxItdY+IEuS8 -H736cozgaXtP1r+8lXBhmj1RmJ2ajpaC6YgGR5GjonwNWGVzjuGHaf6YcUryVrol -MhBgWE50psMf4M16Q74hCwt7o+k5Lz55xKasgc9dtSnvyCupPBwrOT+d55C1P2Wn -2oebWM4WKtCZIgvlvZrt4xQkGWy9qloxL6V1F67ZbizAyFMZUMmJv+4/whF8tmXi -aydleL64K23ZSK1pM/x0JI+7qo0GpEoA4k+2fdmh5dAOM0TrXhV5Kv01efLIaITT -s7lYjG0CgYEA4qGIM7qO3e9fHgSK/9UdxnpL/1OvfYATBMhEtR46sAxmKQGC8fTM -iTBkmLAKn3zBgDghCbygPIQjex+W+Ra7JkQIcGB6KLR8rr5GkOuF6vkqHV93RQRT -lT/1quqq3fVH6V4ymifKJCDNg0IEPcmo+M8RnXBgpFsCN4b5UyjXNScCgYEA5+4h -LITPJxGytlWzwtsy44U2PvafJYJCktW+LYqhk3xzz4qWX5ubmPz18LrEyybgcy/W -Dm4JCu+TOS2gvf2WbJKR/tKdgRN7dkU/dbgMtRL8QW5ir+5qqRITYOhiSZPIOpbP -5zg+c/ZvmK/t5h35/8l7b0bu/E1FOEF27ADpzP0CgYEArqch2gup0muI+A80N9i7 -q5vQOaL6mVM8VPEp0hLL06Sajnt1uJWZkxhSTkFMzoBMd03KWECflEOZPGep56iW -7fR8NG6Fdh0yAVDt/P0lJWKEDELoHa4p49l4sBFNQOSoWLaZdKe5ZoJJHyCfOCbT -K3wY7SYPtFnWqYhBWM8emv0CgYBdrNqNRp78orNR3c+bNjmZl6ZPTAD/f1swP1Bu -yH12Ol/0RX9y4kC4TANx1Z3Ch9ND8uA8N8lDN3x5Laqs0g29kH2TNLIU/i9xl4qI -G2xWfnKQYutNL7i4zOoyy+lW2m+W6m7Sbu8am0B7pSMrPJRK8a//Q+Em2nbIv/gu -XjgQaQKBgHKZUKkMv597vpAjgTNsKIl5RDFONBq3omnAwlK9EDLVeAxIrvrvMHBW -H/ZMFpSGp1eQgKyu1xkEqGdkYXx7BKtdTHK+Thqif2ZGWczy5rVSAIsBYDo1DGE2 -wbocWxkWNb5o2ZZtis5lTB6nr9EWo0zyaPqIh0pfjqVEES2YDEx6 ------END RSA PRIVATE KEY-----` - nonTraditionalRSAKey := `-----BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCcrB9oNKLtzA3Q -02KDgtsnrxns7vJ5aCkjJCm/h0Ju7a2mel5YHSN5iLlU5oTMJVIMpWlW9E8P76/a -GLGMNfSBRVJdfW71iks/ddp4SjpDe9Bo+aY2snrR2/AP7eQepVNjFbg4YLQqvENh -05k1FuuP1/AgGVNn0kGEwzKxz35shmhRKBCvaRaHLz/fdkDIeIrVLON4FnmAmpOZ -AztZCwAZc6HZfj8Nh9Wlaw6Dg2boIgxTU160pwpX+nUxcJ9M5sUP9DBuNL0Mdrqi -U+R49uqG/5ssSk+xVik3q+WF+XySJ6H21fttWDJS2OTm/Nx/wHlBC73mthbA0emB -rkiBy9SBAgMBAAECggEAOhybz6aKcmKYE0d8yGPejwMjPh9JH+ATNh4hQBHXAdc1 -7ESCPvOb52XfvE5+nkwPeXJXNrIKq1IPq3kyTdvrc5F3Ygb3A6tGiuTXYnvBzasc -m/tRfANKjBGkovvte7J90ghJ2tt/qERJR/1Y2/jC6glB314VcjJqK+jNImfgsDa7 -1r47efKG7B5eUGvhQDTpL5ENXKxIdvCghHrLqj19QGUZ5MbXsEYrso0lxKw2Xk39 -uM8p3WTxIy0LQGyCm+FYlJ7r61tm7tUOGuNT0YiptVavIw1QPgIbRWdS2gnJu3+J -kHS0vu6AW1fJav48TA9hXcIQR70alrJA2VVqsvQouwKBgQDNs96l8BfWD6s/urIw -yzC3/VZPLFJ3BlxvkdP1UDC0S+7pgQ6qdEmJg0z5IfYzDB1PK2X/DS/70JA1LRSS -MRmjQGHCYIp9g8EqmABwfKf4YnN53KPRyR8Yq1pwaq7wKowtW+5GH95qQPINZsNO -J21AENEzq7IoB4gpM3tIaX73YwKBgQDC+yl5JvoV7e6FIpFrwL62aKrWmpidML/G -stdrg9ylCSM9SIVFINMhmFPicW1+DrkQ5HRV7DG//ZcOZNbbNmSu32PVcQI1MJgQ -rkMZ3ukUURnlvQYOEmZY4zHzTJ+jcw6kEH/+b47Bv13PpD7ZqA4/28dpU9wi9gt3 -+GiSnkKDywKBgHqjr63dPEjapK3lQFHJAu3fM7MWaMAf4cJ+/hD202LbFsDOuhC0 -Lhe3WY/7SI7cvSizZicvFJmcmi2qB+a1MWTcgKxj5I26nNMpNrHaEEcNY22XN3Be -6ZRKrSvy3wO/Sj3M3n2eiHtu5yFIUE7rQL5+iEu3JQuqmep+kBT3GMSjAoGAP77B -VlyJ0nWRT3F3vZSsRRJ/F94/GtT/PcTmbL4Vetc78CMvfuQ2YntcoWGX/Ghv1Lf7 -2MN5mF0d75TEMbLcw9dA2l0x7ZXPgVSXl3OrG/tPzi44No2JbHIKuJJKdrN9C+Jh -Fhv+vhUEZIg8DAjHb9U4opTKGZv7L+PEvHqFIHUCgYBTB2TxTgEMNZSsRwrhQRMh -tsz5rS2MoTgzk4BlSsv6xVC4GnBJ2HlNAjYEsBEg50zCCTPlZXcsNjrAxFrwWhLJ -DjN2iMsYFz4WHS94W5UYl6/35ye25KsHuS9vnNeidhFAvYgC1nIkh4mFhLoSeSCG -GODy2KwC2ssLuUHb6WoJ6A== ------END PRIVATE KEY-----` - - testCases := map[string]struct { - key string - expectedError error - }{ - "key is RSA and of the correct length": { - key: validPrivateKey, - expectedError: nil, - }, - "key is RSA and too short": { - key: tooShortPrivateKey, - expectedError: errKeyLengthTooShort, - }, - "key is non-traditional RSA key": { - key: nonTraditionalRSAKey, - expectedError: nil, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - err := ValidateKeyLength(tc.key) - require.ErrorIs(t, err, tc.expectedError) - }) - } -} diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go deleted file mode 100644 index 5161e6b033..0000000000 --- a/control-plane/api-gateway/common/translation.go +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "strings" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -// ResourceTranslator handles translating K8s resources into Consul config entries. -type ResourceTranslator struct { - EnableConsulNamespaces bool - ConsulDestNamespace string - EnableK8sMirroring bool - MirroringPrefix string - ConsulPartition string - Datacenter string -} - -func (t ResourceTranslator) NonNormalizedConfigEntryReference(kind string, id types.NamespacedName) api.ResourceReference { - return api.ResourceReference{ - Kind: kind, - Name: id.Name, - Namespace: t.Namespace(id.Namespace), - Partition: t.ConsulPartition, - } -} - -func (t ResourceTranslator) ConfigEntryReference(kind string, id types.NamespacedName) api.ResourceReference { - return NormalizeMeta(t.NonNormalizedConfigEntryReference(kind, id)) -} - -func (t ResourceTranslator) NormalizedResourceReference(kind, namespace string, ref api.ResourceReference) api.ResourceReference { - return NormalizeMeta(api.ResourceReference{ - Kind: kind, - Name: ref.Name, - SectionName: ref.SectionName, - Namespace: t.Namespace(namespace), - Partition: t.ConsulPartition, - }) -} - -func (t ResourceTranslator) Namespace(namespace string) string { - return namespaces.ConsulNamespace(namespace, t.EnableConsulNamespaces, t.ConsulDestNamespace, t.EnableK8sMirroring, t.MirroringPrefix) -} - -// ToAPIGateway translates a kuberenetes API gateway into a Consul APIGateway Config Entry. -func (t ResourceTranslator) ToAPIGateway(gateway gwv1beta1.Gateway, resources *ResourceMap, gwcc *v1alpha1.GatewayClassConfig) *api.APIGatewayConfigEntry { - namespace := t.Namespace(gateway.Namespace) - - listeners := ConvertSliceFuncIf(gateway.Spec.Listeners, func(listener gwv1beta1.Listener) (api.APIGatewayListener, bool) { - return t.toAPIGatewayListener(gateway, listener, resources, gwcc) - }) - - return &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: gateway.Name, - Namespace: namespace, - Partition: t.ConsulPartition, - Meta: t.addDatacenterToMeta(map[string]string{ - constants.MetaKeyKubeNS: gateway.Namespace, - constants.MetaKeyKubeName: gateway.Name, - }), - Listeners: listeners, - } -} - -var listenerProtocolMap = map[string]string{ - "https": "http", - "http": "http", - "tcp": "tcp", -} - -func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, listener gwv1beta1.Listener, resources *ResourceMap, gwcc *v1alpha1.GatewayClassConfig) (api.APIGatewayListener, bool) { - namespace := gateway.Namespace - - var certificates []api.ResourceReference - var cipherSuites []string - var maxVersion, minVersion string - - if listener.TLS != nil { - cipherSuitesVal := string(listener.TLS.Options[TLSCipherSuitesAnnotationKey]) - if cipherSuitesVal != "" { - cipherSuites = strings.Split(cipherSuitesVal, ",") - } - maxVersion = string(listener.TLS.Options[TLSMaxVersionAnnotationKey]) - minVersion = string(listener.TLS.Options[TLSMinVersionAnnotationKey]) - - for _, ref := range listener.TLS.CertificateRefs { - if !resources.GatewayCanReferenceSecret(gateway, ref) { - return api.APIGatewayListener{}, false - } - - if !NilOrEqual(ref.Group, "") || !NilOrEqual(ref.Kind, "Secret") { - // only translate the valid types we support - continue - } - - ref := IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, namespace) - if resources.Certificate(ref) != nil { - certificates = append(certificates, t.NonNormalizedConfigEntryReference(api.InlineCertificate, ref)) - } - } - } - - // Grab policy if it exists. - gatewayPolicyCrd, _ := resources.GetPolicyForGatewayListener(gateway, listener) - defaultPolicy, overridePolicy := t.translateGatewayPolicy(gatewayPolicyCrd) - - portMapping := int32(0) - if gwcc != nil { - portMapping = gwcc.Spec.MapPrivilegedContainerPorts - } - - return api.APIGatewayListener{ - Name: string(listener.Name), - Hostname: DerefStringOr(listener.Hostname, ""), - Port: ToContainerPort(listener.Port, portMapping), - Protocol: listenerProtocolMap[strings.ToLower(string(listener.Protocol))], - TLS: api.APIGatewayTLSConfiguration{ - Certificates: certificates, - CipherSuites: cipherSuites, - MaxVersion: maxVersion, - MinVersion: minVersion, - }, - Default: defaultPolicy, - Override: overridePolicy, - }, true -} - -func ToContainerPort(portNumber gwv1beta1.PortNumber, mapPrivilegedContainerPorts int32) int { - if portNumber >= 1024 { - // We don't care about privileged port-mapping, this is a non-privileged port - return int(portNumber) - } - - return int(portNumber) + int(mapPrivilegedContainerPorts) -} - -func (t ResourceTranslator) translateRouteRetryFilter(routeRetryFilter *v1alpha1.RouteRetryFilter) *api.RetryFilter { - filter := &api.RetryFilter{ - RetryOn: routeRetryFilter.Spec.RetryOn, - RetryOnStatusCodes: routeRetryFilter.Spec.RetryOnStatusCodes, - } - - if routeRetryFilter.Spec.NumRetries != nil { - filter.NumRetries = *routeRetryFilter.Spec.NumRetries - } - - if routeRetryFilter.Spec.RetryOnConnectFailure != nil { - filter.RetryOnConnectFailure = *routeRetryFilter.Spec.RetryOnConnectFailure - } - - return filter -} - -func (t ResourceTranslator) translateRouteTimeoutFilter(routeTimeoutFilter *v1alpha1.RouteTimeoutFilter) *api.TimeoutFilter { - return &api.TimeoutFilter{ - RequestTimeout: routeTimeoutFilter.Spec.RequestTimeout.Duration, - IdleTimeout: routeTimeoutFilter.Spec.IdleTimeout.Duration, - } -} - -func (t ResourceTranslator) translateRouteJWTFilter(routeJWTFilter *v1alpha1.RouteAuthFilter) *api.JWTFilter { - if routeJWTFilter.Spec.JWT == nil { - return nil - } - - return &api.JWTFilter{ - Providers: ConvertSliceFunc(routeJWTFilter.Spec.JWT.Providers, t.translateJWTProvider), - } -} - -func (t ResourceTranslator) translateGatewayPolicy(policy *v1alpha1.GatewayPolicy) (*api.APIGatewayPolicy, *api.APIGatewayPolicy) { - if policy == nil { - return nil, nil - } - - var defaultPolicy, overridePolicy *api.APIGatewayPolicy - - if policy.Spec.Default != nil { - defaultPolicy = &api.APIGatewayPolicy{ - JWT: t.translateJWTRequirement(policy.Spec.Default.JWT), - } - } - - if policy.Spec.Override != nil { - overridePolicy = &api.APIGatewayPolicy{ - JWT: t.translateJWTRequirement(policy.Spec.Override.JWT), - } - } - return defaultPolicy, overridePolicy -} - -func (t ResourceTranslator) translateJWTRequirement(crdRequirement *v1alpha1.GatewayJWTRequirement) *api.APIGatewayJWTRequirement { - apiRequirement := api.APIGatewayJWTRequirement{} - providers := ConvertSliceFunc(crdRequirement.Providers, t.translateJWTProvider) - apiRequirement.Providers = providers - return &apiRequirement -} - -func (t ResourceTranslator) translateJWTProvider(crdProvider *v1alpha1.GatewayJWTProvider) *api.APIGatewayJWTProvider { - if crdProvider == nil { - return nil - } - - apiProvider := api.APIGatewayJWTProvider{ - Name: crdProvider.Name, - } - claims := ConvertSliceFunc(crdProvider.VerifyClaims, t.translateVerifyClaims) - apiProvider.VerifyClaims = claims - - return &apiProvider -} - -func (t ResourceTranslator) translateVerifyClaims(crdClaims *v1alpha1.GatewayJWTClaimVerification) *api.APIGatewayJWTClaimVerification { - if crdClaims == nil { - return nil - } - verifyClaim := api.APIGatewayJWTClaimVerification{ - Path: crdClaims.Path, - Value: crdClaims.Value, - } - return &verifyClaim -} - -func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *ResourceMap) *api.HTTPRouteConfigEntry { - namespace := t.Namespace(route.Namespace) - - // We don't translate parent refs. - - hostnames := StringLikeSlice(route.Spec.Hostnames) - rules := ConvertSliceFuncIf( - route.Spec.Rules, - func(rule gwv1beta1.HTTPRouteRule) (api.HTTPRouteRule, bool) { - return t.translateHTTPRouteRule(route, rule, resources) - }) - - configEntry := api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: route.Name, - Namespace: namespace, - Partition: t.ConsulPartition, - Meta: t.addDatacenterToMeta(map[string]string{ - constants.MetaKeyKubeNS: route.Namespace, - constants.MetaKeyKubeName: route.Name, - }), - Hostnames: hostnames, - Rules: rules, - } - - return &configEntry -} - -func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, rule gwv1beta1.HTTPRouteRule, resources *ResourceMap) (api.HTTPRouteRule, bool) { - services := ConvertSliceFuncIf( - rule.BackendRefs, - func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) { - return t.translateHTTPBackendRef(route, ref, resources) - }) - - if len(services) == 0 { - return api.HTTPRouteRule{}, false - } - - matches := ConvertSliceFunc(rule.Matches, t.translateHTTPMatch) - filters, responseFilters := t.translateHTTPFilters(rule.Filters, resources, route.Namespace) - - return api.HTTPRouteRule{ - Filters: filters, - Matches: matches, - ResponseFilters: responseFilters, - Services: services, - }, true -} - -func (t ResourceTranslator) translateHTTPBackendRef(route gwv1beta1.HTTPRoute, ref gwv1beta1.HTTPBackendRef, resources *ResourceMap) (api.HTTPService, bool) { - id := types.NamespacedName{ - Name: string(ref.Name), - Namespace: DerefStringOr(ref.Namespace, route.Namespace), - } - - isServiceRef := NilOrEqual(ref.Group, "") && NilOrEqual(ref.Kind, "Service") - - if isServiceRef && resources.HasService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { - filters, responseFilters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) - service := resources.Service(id) - return api.HTTPService{ - Name: service.Name, - Namespace: service.Namespace, - Partition: t.ConsulPartition, - Filters: filters, - ResponseFilters: responseFilters, - Weight: DerefIntOr(ref.Weight, 1), - }, true - } - - isMeshServiceRef := DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) - if isMeshServiceRef && resources.HasMeshService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { - filters, responseFilters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) - service := resources.MeshService(id) - - return api.HTTPService{ - Name: service.Name, - Namespace: service.Namespace, - Partition: t.ConsulPartition, - Filters: filters, - ResponseFilters: responseFilters, - Weight: DerefIntOr(ref.Weight, 1), - }, true - } - - return api.HTTPService{}, false -} - -var headerMatchTypeTranslation = map[gwv1beta1.HeaderMatchType]api.HTTPHeaderMatchType{ - gwv1beta1.HeaderMatchExact: api.HTTPHeaderMatchExact, - gwv1beta1.HeaderMatchRegularExpression: api.HTTPHeaderMatchRegularExpression, -} - -var headerPathMatchTypeTranslation = map[gwv1beta1.PathMatchType]api.HTTPPathMatchType{ - gwv1beta1.PathMatchExact: api.HTTPPathMatchExact, - gwv1beta1.PathMatchPathPrefix: api.HTTPPathMatchPrefix, - gwv1beta1.PathMatchRegularExpression: api.HTTPPathMatchRegularExpression, -} - -var queryMatchTypeTranslation = map[gwv1beta1.QueryParamMatchType]api.HTTPQueryMatchType{ - gwv1beta1.QueryParamMatchExact: api.HTTPQueryMatchExact, - gwv1beta1.QueryParamMatchRegularExpression: api.HTTPQueryMatchRegularExpression, -} - -func (t ResourceTranslator) translateHTTPMatch(match gwv1beta1.HTTPRouteMatch) api.HTTPMatch { - headers := ConvertSliceFunc(match.Headers, t.translateHTTPHeaderMatch) - queries := ConvertSliceFunc(match.QueryParams, t.translateHTTPQueryMatch) - - return api.HTTPMatch{ - Headers: headers, - Query: queries, - Path: DerefConvertFunc(match.Path, t.translateHTTPPathMatch), - Method: api.HTTPMatchMethod(DerefStringOr(match.Method, "")), - } -} - -func (t ResourceTranslator) translateHTTPPathMatch(match gwv1beta1.HTTPPathMatch) api.HTTPPathMatch { - return api.HTTPPathMatch{ - Match: DerefLookup(match.Type, headerPathMatchTypeTranslation), - Value: DerefStringOr(match.Value, ""), - } -} - -func (t ResourceTranslator) translateHTTPHeaderMatch(match gwv1beta1.HTTPHeaderMatch) api.HTTPHeaderMatch { - return api.HTTPHeaderMatch{ - Name: string(match.Name), - Value: match.Value, - Match: DerefLookup(match.Type, headerMatchTypeTranslation), - } -} - -func (t ResourceTranslator) translateHTTPQueryMatch(match gwv1beta1.HTTPQueryParamMatch) api.HTTPQueryMatch { - return api.HTTPQueryMatch{ - Name: string(match.Name), - Value: match.Value, - Match: DerefLookup(match.Type, queryMatchTypeTranslation), - } -} - -func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter, resourceMap *ResourceMap, namespace string) (api.HTTPFilters, api.HTTPResponseFilters) { - var ( - urlRewrite *api.URLRewrite - retryFilter *api.RetryFilter - timeoutFilter *api.TimeoutFilter - requestHeaderFilters = []api.HTTPHeaderFilter{} - responseHeaderFilters = []api.HTTPHeaderFilter{} - jwtFilter *api.JWTFilter - ) - - // Convert Gateway API filters to portions of the Consul request and response filters. - // Multiple filters applying the same or conflicting operations are allowed but may - // result in unexpected behavior. - for _, filter := range filters { - if filter.RequestHeaderModifier != nil { - newFilter := api.HTTPHeaderFilter{} - - newFilter.Remove = append(newFilter.Remove, filter.RequestHeaderModifier.Remove...) - - if len(filter.RequestHeaderModifier.Add) > 0 { - newFilter.Add = map[string]string{} - for _, toAdd := range filter.RequestHeaderModifier.Add { - newFilter.Add[string(toAdd.Name)] = toAdd.Value - } - } - - if len(filter.RequestHeaderModifier.Set) > 0 { - newFilter.Set = map[string]string{} - for _, toSet := range filter.RequestHeaderModifier.Set { - newFilter.Set[string(toSet.Name)] = toSet.Value - } - } - - requestHeaderFilters = append(requestHeaderFilters, newFilter) - } - - if filter.ResponseHeaderModifier != nil { - newFilter := api.HTTPHeaderFilter{} - - newFilter.Remove = append(newFilter.Remove, filter.ResponseHeaderModifier.Remove...) - - if len(filter.ResponseHeaderModifier.Add) > 0 { - newFilter.Add = map[string]string{} - for _, toAdd := range filter.ResponseHeaderModifier.Add { - newFilter.Add[string(toAdd.Name)] = toAdd.Value - } - } - - if len(filter.ResponseHeaderModifier.Set) > 0 { - newFilter.Set = map[string]string{} - for _, toSet := range filter.ResponseHeaderModifier.Set { - newFilter.Set[string(toSet.Name)] = toSet.Value - } - } - - responseHeaderFilters = append(responseHeaderFilters, newFilter) - } - - // we drop any path rewrites that are not prefix matches as we don't support those - if filter.URLRewrite != nil && - filter.URLRewrite.Path != nil && - filter.URLRewrite.Path.Type == gwv1beta1.PrefixMatchHTTPPathModifier { - urlRewrite = &api.URLRewrite{Path: DerefStringOr(filter.URLRewrite.Path.ReplacePrefixMatch, "")} - } - - if filter.ExtensionRef != nil { - // get crd from resources map - crdFilter, exists := resourceMap.GetExternalFilter(*filter.ExtensionRef, namespace) - if !exists { - // this should never be the case because we only translate a route if it's actually valid, and if we're missing filters during the validation step, then we won't get here - continue - } - - switch filter.ExtensionRef.Kind { - case v1alpha1.RouteRetryFilterKind: - retryFilter = t.translateRouteRetryFilter(crdFilter.(*v1alpha1.RouteRetryFilter)) - case v1alpha1.RouteTimeoutFilterKind: - timeoutFilter = t.translateRouteTimeoutFilter(crdFilter.(*v1alpha1.RouteTimeoutFilter)) - case v1alpha1.RouteAuthFilterKind: - jwtFilter = t.translateRouteJWTFilter(crdFilter.(*v1alpha1.RouteAuthFilter)) - } - } - } - - requestFilter := api.HTTPFilters{ - Headers: requestHeaderFilters, - URLRewrite: urlRewrite, - RetryFilter: retryFilter, - TimeoutFilter: timeoutFilter, - JWT: jwtFilter, - } - - responseFilter := api.HTTPResponseFilters{ - Headers: responseHeaderFilters, - } - - return requestFilter, responseFilter -} - -func (t ResourceTranslator) ToTCPRoute(route gwv1alpha2.TCPRoute, resources *ResourceMap) *api.TCPRouteConfigEntry { - namespace := t.Namespace(route.Namespace) - - // we don't translate parent refs - - backendRefs := ConvertSliceFunc(route.Spec.Rules, func(rule gwv1alpha2.TCPRouteRule) []gwv1beta1.BackendRef { return rule.BackendRefs }) - flattenedRefs := Flatten(backendRefs) - services := ConvertSliceFuncIf(flattenedRefs, func(ref gwv1beta1.BackendRef) (api.TCPService, bool) { - return t.translateTCPRouteRule(route, ref, resources) - }) - - return &api.TCPRouteConfigEntry{ - Kind: api.TCPRoute, - Name: route.Name, - Namespace: namespace, - Partition: t.ConsulPartition, - Meta: t.addDatacenterToMeta(map[string]string{ - constants.MetaKeyKubeNS: route.Namespace, - constants.MetaKeyKubeName: route.Name, - }), - Services: services, - } -} - -func (t ResourceTranslator) translateTCPRouteRule(route gwv1alpha2.TCPRoute, ref gwv1beta1.BackendRef, resources *ResourceMap) (api.TCPService, bool) { - // we ignore weight for now - - id := types.NamespacedName{ - Name: string(ref.Name), - Namespace: DerefStringOr(ref.Namespace, route.Namespace), - } - - isServiceRef := NilOrEqual(ref.Group, "") && NilOrEqual(ref.Kind, "Service") - if isServiceRef && resources.HasService(id) && resources.TCPRouteCanReferenceBackend(route, ref) { - service := resources.Service(id) - - return api.TCPService{ - Name: service.Name, - Namespace: service.Namespace, - }, true - } - - isMeshServiceRef := DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) - if isMeshServiceRef && resources.HasMeshService(id) && resources.TCPRouteCanReferenceBackend(route, ref) { - service := resources.MeshService(id) - - return api.TCPService{ - Name: service.Name, - Namespace: service.Namespace, - }, true - } - - return api.TCPService{}, false -} - -func (t ResourceTranslator) ToInlineCertificate(secret corev1.Secret) (*api.InlineCertificateConfigEntry, error) { - certificate, privateKey, err := ParseCertificateData(secret) - if err != nil { - return nil, err - } - - err = ValidateKeyLength(privateKey) - if err != nil { - return nil, err - } - - namespace := t.Namespace(secret.Namespace) - - return &api.InlineCertificateConfigEntry{ - Kind: api.InlineCertificate, - Name: secret.Name, - Namespace: namespace, - Partition: t.ConsulPartition, - Certificate: strings.TrimSpace(certificate), - PrivateKey: strings.TrimSpace(privateKey), - Meta: t.addDatacenterToMeta(map[string]string{ - constants.MetaKeyKubeNS: secret.Namespace, - constants.MetaKeyKubeName: secret.Name, - }), - }, nil -} - -func EntryToNamespacedName(entry api.ConfigEntry) types.NamespacedName { - meta := entry.GetMeta() - - return types.NamespacedName{ - Namespace: meta[constants.MetaKeyKubeNS], - Name: meta[constants.MetaKeyKubeName], - } -} - -func (t ResourceTranslator) addDatacenterToMeta(meta map[string]string) map[string]string { - if t.Datacenter == "" { - return meta - } - meta[constants.MetaKeyDatacenter] = t.Datacenter - return meta -} diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go deleted file mode 100644 index 4331e2b77a..0000000000 --- a/control-plane/api-gateway/common/translation_test.go +++ /dev/null @@ -1,1900 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "math/big" - "strings" - "testing" - "time" - - "k8s.io/utils/pointer" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - logrtest "github.com/go-logr/logr/testing" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -type fakeReferenceValidator struct{} - -func (v fakeReferenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool { - return true -} - -func (v fakeReferenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { - return true -} - -func (v fakeReferenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { - return true -} - -func TestTranslator_Namespace(t *testing.T) { - testCases := []struct { - EnableConsulNamespaces bool - ConsulDestNamespace string - EnableK8sMirroring bool - MirroringPrefix string - Input, ExpectedOutput string - }{ - { - EnableConsulNamespaces: false, - ConsulDestNamespace: "default", - EnableK8sMirroring: false, - MirroringPrefix: "", - Input: "namespace-1", - ExpectedOutput: "", - }, - { - EnableConsulNamespaces: false, - ConsulDestNamespace: "default", - EnableK8sMirroring: true, - MirroringPrefix: "", - Input: "namespace-1", - ExpectedOutput: "", - }, - { - EnableConsulNamespaces: false, - ConsulDestNamespace: "default", - EnableK8sMirroring: true, - MirroringPrefix: "pre-", - Input: "namespace-1", - ExpectedOutput: "", - }, - { - EnableConsulNamespaces: true, - ConsulDestNamespace: "default", - EnableK8sMirroring: false, - MirroringPrefix: "", - Input: "namespace-1", - ExpectedOutput: "default", - }, - { - EnableConsulNamespaces: true, - ConsulDestNamespace: "default", - EnableK8sMirroring: true, - MirroringPrefix: "", - Input: "namespace-1", - ExpectedOutput: "namespace-1", - }, - { - EnableConsulNamespaces: true, - ConsulDestNamespace: "default", - EnableK8sMirroring: true, - MirroringPrefix: "pre-", - Input: "namespace-1", - ExpectedOutput: "pre-namespace-1", - }, - } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("%s_%d", t.Name(), i), func(t *testing.T) { - translator := ResourceTranslator{ - EnableConsulNamespaces: tc.EnableConsulNamespaces, - ConsulDestNamespace: tc.ConsulDestNamespace, - EnableK8sMirroring: tc.EnableK8sMirroring, - MirroringPrefix: tc.MirroringPrefix, - } - assert.Equal(t, tc.ExpectedOutput, translator.Namespace(tc.Input)) - }) - } -} - -func TestTranslator_ToAPIGateway(t *testing.T) { - t.Parallel() - k8sObjectName := "my-k8s-gw" - k8sNamespace := "my-k8s-namespace" - - // gw status - gwLastTransmissionTime := time.Now() - - // listener one configuration - listenerOneName := "listener-one" - listenerOneHostname := "*.consul.io" - listenerOnePort := 3366 - listenerOneProtocol := "http" - - // listener one tls config - listenerOneCertName := "one-cert" - listenerOneCertK8sNamespace := "one-cert-ns" - listenerOneCertConsulNamespace := "one-cert-ns" - listenerOneCert := generateTestCertificate(t, "one-cert-ns", "one-cert") - listenerOneMaxVersion := "TLSv1_2" - listenerOneMinVersion := "TLSv1_3" - listenerOneCipherSuites := []string{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"} - - // listener one status - listenerOneLastTransmissionTime := time.Now() - - // listener two configuration - listenerTwoName := "listener-two" - listenerTwoHostname := "*.consul.io" - listenerTwoPort := 5432 - listenerTwoProtocol := "http" - - // listener one tls config - listenerTwoCertName := "two-cert" - listenerTwoCertK8sNamespace := "two-cert-ns" - listenerTwoCertConsulNamespace := "two-cert-ns" - listenerTwoCert := generateTestCertificate(t, "two-cert-ns", "two-cert") - - // listener two status - listenerTwoLastTransmissionTime := time.Now() - - testCases := map[string]struct { - annotations map[string]string - expectedGWName string - listenerOneK8sCertRefs []gwv1beta1.SecretObjectReference - listenerOneTLSOptions map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue - }{ - "gw name": { - annotations: make(map[string]string), - expectedGWName: k8sObjectName, - listenerOneK8sCertRefs: []gwv1beta1.SecretObjectReference{ - { - Name: gwv1beta1.ObjectName(listenerOneCertName), - Namespace: PointerTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), - }, - }, - listenerOneTLSOptions: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ - TLSMaxVersionAnnotationKey: gwv1beta1.AnnotationValue(listenerOneMaxVersion), - TLSMinVersionAnnotationKey: gwv1beta1.AnnotationValue(listenerOneMinVersion), - TLSCipherSuitesAnnotationKey: gwv1beta1.AnnotationValue(strings.Join(listenerOneCipherSuites, ",")), - }, - }, - "when k8s has certs that are not referenced in consul": { - annotations: make(map[string]string), - expectedGWName: k8sObjectName, - listenerOneK8sCertRefs: []gwv1beta1.SecretObjectReference{ - { - Name: gwv1beta1.ObjectName(listenerOneCertName), - Namespace: PointerTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), - }, - { - Name: gwv1beta1.ObjectName("cert that won't exist in the translated type"), - Namespace: PointerTo(gwv1beta1.Namespace(listenerOneCertK8sNamespace)), - }, - }, - listenerOneTLSOptions: map[gwv1beta1.AnnotationKey]gwv1beta1.AnnotationValue{ - TLSMaxVersionAnnotationKey: gwv1beta1.AnnotationValue(listenerOneMaxVersion), - TLSMinVersionAnnotationKey: gwv1beta1.AnnotationValue(listenerOneMinVersion), - TLSCipherSuitesAnnotationKey: gwv1beta1.AnnotationValue(strings.Join(listenerOneCipherSuites, ",")), - }, - }, - } - - for name, tc := range testCases { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - input := gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "Gateway", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: k8sObjectName, - Namespace: k8sNamespace, - Annotations: tc.annotations, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName(listenerOneName), - Hostname: PointerTo(gwv1beta1.Hostname(listenerOneHostname)), - Port: gwv1beta1.PortNumber(listenerOnePort), - Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: tc.listenerOneK8sCertRefs, - Options: tc.listenerOneTLSOptions, - }, - }, - { - Name: gwv1beta1.SectionName(listenerTwoName), - Hostname: PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), - Port: gwv1beta1.PortNumber(listenerTwoPort), - Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - { - Name: gwv1beta1.ObjectName(listenerTwoCertName), - Namespace: PointerTo(gwv1beta1.Namespace(listenerTwoCertK8sNamespace)), - }, - }, - }, - }, - }, - }, - Status: gwv1beta1.GatewayStatus{ - Conditions: []metav1.Condition{ - { - Type: string(gwv1beta1.GatewayConditionAccepted), - Status: metav1.ConditionTrue, - LastTransitionTime: metav1.Time{Time: gwLastTransmissionTime}, - Reason: string(gwv1beta1.GatewayReasonAccepted), - Message: "I'm accepted", - }, - }, - Listeners: []gwv1beta1.ListenerStatus{ - { - Name: gwv1beta1.SectionName(listenerOneName), - AttachedRoutes: 5, - Conditions: []metav1.Condition{ - { - Type: string(gwv1beta1.GatewayConditionReady), - Status: metav1.ConditionTrue, - LastTransitionTime: metav1.Time{Time: listenerOneLastTransmissionTime}, - Reason: string(gwv1beta1.GatewayConditionReady), - Message: "I'm ready", - }, - }, - }, - - { - Name: gwv1beta1.SectionName(listenerTwoName), - AttachedRoutes: 3, - Conditions: []metav1.Condition{ - { - Type: string(gwv1beta1.GatewayConditionReady), - Status: metav1.ConditionTrue, - LastTransitionTime: metav1.Time{Time: listenerTwoLastTransmissionTime}, - Reason: string(gwv1beta1.GatewayConditionReady), - Message: "I'm also ready", - }, - }, - }, - }, - }, - } - - expectedConfigEntry := &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: tc.expectedGWName, - Meta: map[string]string{ - constants.MetaKeyKubeNS: k8sNamespace, - constants.MetaKeyKubeName: k8sObjectName, - }, - Listeners: []api.APIGatewayListener{ - { - Name: listenerOneName, - Hostname: listenerOneHostname, - Port: listenerOnePort, - Protocol: listenerOneProtocol, - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: listenerOneCertName, - Namespace: listenerOneCertConsulNamespace, - }, - }, - CipherSuites: listenerOneCipherSuites, - MaxVersion: listenerOneMaxVersion, - MinVersion: listenerOneMinVersion, - }, - }, - { - Name: listenerTwoName, - Hostname: listenerTwoHostname, - Port: listenerTwoPort, - Protocol: listenerTwoProtocol, - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: listenerTwoCertName, - Namespace: listenerTwoCertConsulNamespace, - }, - }, - CipherSuites: nil, - MaxVersion: "", - MinVersion: "", - }, - }, - }, - Status: api.ConfigEntryStatus{}, - Namespace: k8sNamespace, - } - translator := ResourceTranslator{ - EnableConsulNamespaces: true, - ConsulDestNamespace: "", - EnableK8sMirroring: true, - MirroringPrefix: "", - } - - resources := NewResourceMap(translator, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) - resources.ReferenceCountCertificate(listenerOneCert) - resources.ReferenceCountCertificate(listenerTwoCert) - - actualConfigEntry := translator.ToAPIGateway(input, resources, &v1alpha1.GatewayClassConfig{}) - - if diff := cmp.Diff(expectedConfigEntry, actualConfigEntry); diff != "" { - t.Errorf("Translator.GatewayToAPIGateway() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestTranslator_ToHTTPRoute(t *testing.T) { - t.Parallel() - type args struct { - k8sHTTPRoute gwv1beta1.HTTPRoute - services []types.NamespacedName - meshServices []v1alpha1.MeshService - externalFilters []client.Object - } - - tests := map[string]struct { - args args - want api.HTTPRouteConfigEntry - }{ - "base test": { - args: args{ - k8sHTTPRoute: gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s-http-route", - Namespace: "k8s-ns", - Annotations: map[string]string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), - Name: gwv1beta1.ObjectName("api-gw"), - Kind: PointerTo(gwv1beta1.Kind("Gateway")), - SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{ - "host-name.example.com", - "consul.io", - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: PointerTo(gwv1beta1.PathMatchPathPrefix), - Value: PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: PointerTo(gwv1beta1.HeaderMatchExact), - Name: "my header match", - Value: "the value", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "term", - }, - }, - Method: PointerTo(gwv1beta1.HTTPMethodGet), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "Magic", - Value: "v2", - }, - { - Name: "Another One", - Value: "dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "add it on", - Value: "the value", - }, - }, - Remove: []string{"time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: PointerTo("v1"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "service one", - Namespace: PointerTo(gwv1beta1.Namespace("other")), - }, - Weight: PointerTo(int32(45)), - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "svc - Magic", - Value: "svc - v2", - }, - { - Name: "svc - Another One", - Value: "svc - dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "svc - add it on", - Value: "svc - the value", - }, - }, - Remove: []string{"svc - time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: PointerTo("path"), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - services: []types.NamespacedName{ - {Name: "service one", Namespace: "other"}, - }, - }, - want: api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "k8s-http-route", - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - ResponseFilters: api.HTTPResponseFilters{Headers: []api.HTTPHeaderFilter{}}, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Namespace: "other", - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - ResponseFilters: api.HTTPResponseFilters{Headers: []api.HTTPHeaderFilter{}}, - Weight: 45, - }, - }, - }, - }, - Hostnames: []string{ - "host-name.example.com", - "consul.io", - }, - Meta: map[string]string{ - constants.MetaKeyKubeNS: "k8s-ns", - constants.MetaKeyKubeName: "k8s-http-route", - }, - Namespace: "k8s-ns", - }, - }, - "dropping path rewrites that are not prefix match": { - args: args{ - k8sHTTPRoute: gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s-http-route", - Namespace: "k8s-ns", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), - Name: gwv1beta1.ObjectName("api-gw"), - SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), - Kind: PointerTo(gwv1beta1.Kind("Gateway")), - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{ - "host-name.example.com", - "consul.io", - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: PointerTo(gwv1beta1.PathMatchPathPrefix), - Value: PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: PointerTo(gwv1beta1.HeaderMatchExact), - Name: "my header match", - Value: "the value", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "term", - }, - }, - Method: PointerTo(gwv1beta1.HTTPMethodGet), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "Magic", - Value: "v2", - }, - { - Name: "Another One", - Value: "dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "add it on", - Value: "the value", - }, - }, - Remove: []string{"time to go"}, - }, - // THIS IS THE CHANGE - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.FullPathHTTPPathModifier, - ReplaceFullPath: PointerTo("v1"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "service one", - Namespace: PointerTo(gwv1beta1.Namespace("some ns")), - }, - Weight: PointerTo(int32(45)), - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "svc - Magic", - Value: "svc - v2", - }, - { - Name: "svc - Another One", - Value: "svc - dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "svc - add it on", - Value: "svc - the value", - }, - }, - Remove: []string{"svc - time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: PointerTo("path"), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - services: []types.NamespacedName{ - {Name: "service one", Namespace: "some ns"}, - }, - }, - want: api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "k8s-http-route", - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Namespace: "some ns", - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Weight: 45, - }, - }, - }, - }, - Hostnames: []string{ - "host-name.example.com", - "consul.io", - }, - Meta: map[string]string{ - constants.MetaKeyKubeNS: "k8s-ns", - constants.MetaKeyKubeName: "k8s-http-route", - }, - Namespace: "k8s-ns", - }, - }, - "parent ref that is not registered with consul is dropped": { - args: args{ - k8sHTTPRoute: gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s-http-route", - Namespace: "k8s-ns", - Annotations: map[string]string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), - Name: gwv1beta1.ObjectName("api-gw"), - Kind: PointerTo(gwv1beta1.Kind("Gateway")), - SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), - }, - - { - Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), - Name: gwv1beta1.ObjectName("consul don't know about me"), - Kind: PointerTo(gwv1beta1.Kind("Gateway")), - SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{ - "host-name.example.com", - "consul.io", - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: PointerTo(gwv1beta1.PathMatchPathPrefix), - Value: PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: PointerTo(gwv1beta1.HeaderMatchExact), - Name: "my header match", - Value: "the value", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "term", - }, - }, - Method: PointerTo(gwv1beta1.HTTPMethodGet), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "Magic", - Value: "v2", - }, - { - Name: "Another One", - Value: "dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "add it on", - Value: "the value", - }, - }, - Remove: []string{"time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: PointerTo("v1"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "service one", - Namespace: PointerTo(gwv1beta1.Namespace("some ns")), - }, - Weight: PointerTo(int32(45)), - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "svc - Magic", - Value: "svc - v2", - }, - { - Name: "svc - Another One", - Value: "svc - dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "svc - add it on", - Value: "svc - the value", - }, - }, - Remove: []string{"svc - time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: PointerTo("path"), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - services: []types.NamespacedName{ - {Name: "service one", Namespace: "some ns"}, - }, - }, - want: api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "k8s-http-route", - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Namespace: "some ns", - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Weight: 45, - }, - }, - }, - }, - Hostnames: []string{ - "host-name.example.com", - "consul.io", - }, - Meta: map[string]string{ - constants.MetaKeyKubeNS: "k8s-ns", - constants.MetaKeyKubeName: "k8s-http-route", - }, - Namespace: "k8s-ns", - }, - }, - "when section name on apigw is not supplied": { - args: args{ - k8sHTTPRoute: gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s-http-route", - Namespace: "k8s-ns", - Annotations: map[string]string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), - Name: gwv1beta1.ObjectName("api-gw"), - Kind: PointerTo(gwv1beta1.Kind("Gateway")), - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{ - "host-name.example.com", - "consul.io", - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: PointerTo(gwv1beta1.PathMatchPathPrefix), - Value: PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: PointerTo(gwv1beta1.HeaderMatchExact), - Name: "my header match", - Value: "the value", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "term", - }, - }, - Method: PointerTo(gwv1beta1.HTTPMethodGet), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "Magic", - Value: "v2", - }, - { - Name: "Another One", - Value: "dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "add it on", - Value: "the value", - }, - }, - Remove: []string{"time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: PointerTo("v1"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - // this ref should get dropped - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "service two", - Namespace: PointerTo(gwv1beta1.Namespace("some ns")), - }, - }, - }, - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "some-service-part-three", - Namespace: PointerTo(gwv1beta1.Namespace("svc-ns")), - Group: PointerTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), - Kind: PointerTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), - }, - }, - }, - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "service one", - Namespace: PointerTo(gwv1beta1.Namespace("some ns")), - }, - Weight: PointerTo(int32(45)), - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "svc - Magic", - Value: "svc - v2", - }, - { - Name: "svc - Another One", - Value: "svc - dj khaled", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "svc - add it on", - Value: "svc - the value", - }, - }, - Remove: []string{"svc - time to go"}, - }, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: PointerTo("path"), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - services: []types.NamespacedName{ - {Name: "service one", Namespace: "some ns"}, - }, - meshServices: []v1alpha1.MeshService{ - {ObjectMeta: metav1.ObjectMeta{Name: "some-service-part-three", Namespace: "svc-ns"}, Spec: v1alpha1.MeshServiceSpec{Name: "some-override"}}, - }, - }, - want: api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "k8s-http-route", - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "add it on": "the value", - }, - Remove: []string{"time to go"}, - Set: map[string]string{ - "Magic": "v2", - "Another One": "dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{Path: "v1"}, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "some-override", - Namespace: "svc-ns", - Weight: 1, - Filters: api.HTTPFilters{Headers: []api.HTTPHeaderFilter{}}, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - }, - { - Name: "service one", - Namespace: "some ns", - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{ - { - Add: map[string]string{ - "svc - add it on": "svc - the value", - }, - Remove: []string{"svc - time to go"}, - Set: map[string]string{ - "svc - Magic": "svc - v2", - "svc - Another One": "svc - dj khaled", - }, - }, - }, - URLRewrite: &api.URLRewrite{ - Path: "path", - }, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Weight: 45, - }, - }, - }, - }, - Hostnames: []string{ - "host-name.example.com", - "consul.io", - }, - Meta: map[string]string{ - constants.MetaKeyKubeNS: "k8s-ns", - constants.MetaKeyKubeName: "k8s-http-route", - }, - Namespace: "k8s-ns", - }, - }, - "test with external filters": { - args: args{ - k8sHTTPRoute: gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s-http-route", - Namespace: "k8s-ns", - Annotations: map[string]string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), - Name: gwv1beta1.ObjectName("api-gw"), - Kind: PointerTo(gwv1beta1.Kind("Gateway")), - SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{ - "host-name.example.com", - "consul.io", - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: PointerTo(gwv1beta1.PathMatchPathPrefix), - Value: PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: PointerTo(gwv1beta1.HeaderMatchExact), - Name: "my header match", - Value: "the value", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "term", - }, - }, - Method: PointerTo(gwv1beta1.HTTPMethodGet), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Name: "test", - Kind: v1alpha1.RouteRetryFilterKind, - Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), - }, - }, - { - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Name: "test-timeout-filter", - Kind: v1alpha1.RouteTimeoutFilterKind, - Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), - }, - }, - { - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Name: "test-jwt-filter", - Kind: v1alpha1.RouteAuthFilterKind, - Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "service one", - Namespace: PointerTo(gwv1beta1.Namespace("other")), - }, - Weight: PointerTo(int32(45)), - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Name: "test", - Kind: v1alpha1.RouteRetryFilterKind, - Group: "consul.hashicorp.com/v1alpha1", - }, - }, - }, - }, - }, - }, - }, - }, - }, - services: []types.NamespacedName{ - {Name: "service one", Namespace: "other"}, - }, - externalFilters: []client.Object{ - &v1alpha1.RouteRetryFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteRetryFilterKind, - APIVersion: "consul.hashicorp.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "k8s-ns", - }, - Spec: v1alpha1.RouteRetryFilterSpec{ - NumRetries: pointer.Uint32(3), - RetryOn: []string{"cancelled"}, - RetryOnStatusCodes: []uint32{500, 502}, - RetryOnConnectFailure: pointer.Bool(false), - }, - }, - - &v1alpha1.RouteRetryFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteRetryFilterKind, - APIVersion: "consul.hashicorp.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "other-namespace-even-though-same-name", - }, - Spec: v1alpha1.RouteRetryFilterSpec{ - NumRetries: pointer.Uint32(3), - RetryOn: []string{"don't"}, - RetryOnStatusCodes: []uint32{404}, - RetryOnConnectFailure: pointer.Bool(true), - }, - }, - - &v1alpha1.RouteTimeoutFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteTimeoutFilterKind, - APIVersion: "consul.hashicorp.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-timeout-filter", - Namespace: "k8s-ns", - }, - Spec: v1alpha1.RouteTimeoutFilterSpec{ - RequestTimeout: metav1.Duration{Duration: 10}, - IdleTimeout: metav1.Duration{Duration: 30}, - }, - }, - - &v1alpha1.RouteAuthFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteAuthFilterKind, - APIVersion: "consul.hashicorp.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwt-filter", - Namespace: "k8s-ns", - }, - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "test-jwt-provider", - VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ - { - Path: []string{"/okta"}, - Value: "okta", - }, - }, - }, - }, - }, - }, - }, - }, - }, - want: api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "k8s-http-route", - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{}, - URLRewrite: nil, - RetryFilter: &api.RetryFilter{ - NumRetries: 3, - RetryOn: []string{"cancelled"}, - RetryOnStatusCodes: []uint32{500, 502}, - RetryOnConnectFailure: false, - }, - TimeoutFilter: &api.TimeoutFilter{ - RequestTimeout: time.Duration(10 * time.Nanosecond), - IdleTimeout: time.Duration(30 * time.Nanosecond), - }, - JWT: &api.JWTFilter{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "test-jwt-provider", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"/okta"}, - Value: "okta", - }, - }, - }, - }, - }, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{}, - RetryFilter: &api.RetryFilter{ - NumRetries: 3, - RetryOn: []string{"cancelled"}, - RetryOnStatusCodes: []uint32{500, 502}, - RetryOnConnectFailure: false, - }, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Namespace: "other", - }, - }, - }, - }, - Hostnames: []string{ - "host-name.example.com", - "consul.io", - }, - Meta: map[string]string{ - constants.MetaKeyKubeNS: "k8s-ns", - constants.MetaKeyKubeName: "k8s-http-route", - }, - Namespace: "k8s-ns", - }, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - tr := ResourceTranslator{ - EnableConsulNamespaces: true, - EnableK8sMirroring: true, - } - - resources := NewResourceMap(tr, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) - for _, service := range tc.args.services { - resources.AddService(service, service.Name) - } - for _, service := range tc.args.meshServices { - resources.AddMeshService(service) - } - - for _, filterToAdd := range tc.args.externalFilters { - resources.AddExternalFilter(filterToAdd) - } - - got := tr.ToHTTPRoute(tc.args.k8sHTTPRoute, resources) - if diff := cmp.Diff(&tc.want, got); diff != "" { - t.Errorf("Translator.ToHTTPRoute() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestTranslator_ToTCPRoute(t *testing.T) { - t.Parallel() - type args struct { - k8sRoute gwv1alpha2.TCPRoute - services []types.NamespacedName - meshServices []v1alpha1.MeshService - } - tests := map[string]struct { - args args - want api.TCPRouteConfigEntry - }{ - "base test": { - args: args{ - k8sRoute: gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route", - Namespace: "k8s-ns", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - Rules: []gwv1alpha2.TCPRouteRule{ - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "some-service", - Namespace: PointerTo(gwv1beta1.Namespace("svc-ns")), - }, - Weight: new(int32), - }, - }, - }, - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "some-service-part-two", - Namespace: PointerTo(gwv1beta1.Namespace("svc-ns")), - }, - Weight: new(int32), - }, - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Group: PointerTo(gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup)), - Kind: PointerTo(gwv1beta1.Kind(v1alpha1.MeshServiceKind)), - Name: "some-service-part-three", - Namespace: PointerTo(gwv1beta1.Namespace("svc-ns")), - }, - Weight: new(int32), - }, - }, - }, - }, - }, - }, - services: []types.NamespacedName{ - {Name: "some-service", Namespace: "svc-ns"}, - {Name: "some-service-part-two", Namespace: "svc-ns"}, - }, - meshServices: []v1alpha1.MeshService{ - {ObjectMeta: metav1.ObjectMeta{Name: "some-service-part-three", Namespace: "svc-ns"}, Spec: v1alpha1.MeshServiceSpec{Name: "some-override"}}, - }, - }, - want: api.TCPRouteConfigEntry{ - Kind: api.TCPRoute, - Name: "tcp-route", - Namespace: "k8s-ns", - Services: []api.TCPService{ - { - Name: "some-service", - Partition: "", - Namespace: "svc-ns", - }, - { - Name: "some-service-part-two", - Partition: "", - Namespace: "svc-ns", - }, - { - Name: "some-override", - Partition: "", - Namespace: "svc-ns", - }, - }, - Meta: map[string]string{ - constants.MetaKeyKubeNS: "k8s-ns", - constants.MetaKeyKubeName: "tcp-route", - }, - }, - }, - } - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - tr := ResourceTranslator{ - EnableConsulNamespaces: true, - EnableK8sMirroring: true, - } - - resources := NewResourceMap(tr, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) - for _, service := range tt.args.services { - resources.AddService(service, service.Name) - } - for _, service := range tt.args.meshServices { - resources.AddMeshService(service) - } - - got := tr.ToTCPRoute(tt.args.k8sRoute, resources) - if diff := cmp.Diff(&tt.want, got); diff != "" { - t.Errorf("Translator.TCPRouteToTCPRoute() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func generateTestCertificate(t *testing.T, namespace, name string) corev1.Secret { - privateKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(t, err) - - usage := x509.KeyUsageCertSign - expiration := time.Now().AddDate(10, 0, 0) - - cert := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: "consul.test", - }, - IsCA: true, - NotBefore: time.Now().Add(-10 * time.Minute), - NotAfter: expiration, - SubjectKeyId: []byte{1, 2, 3, 4, 6}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: usage, - BasicConstraintsValid: true, - } - caCert := cert - caPrivateKey := privateKey - - data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) - require.NoError(t, err) - - certBytes := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: data, - }) - - privateKeyBytes := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) - - return corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: certBytes, - corev1.TLSPrivateKeyKey: privateKeyBytes, - }, - } -} - -func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { - type fields struct { - EnableConsulNamespaces bool - ConsulDestNamespace string - EnableK8sMirroring bool - MirroringPrefix string - ConsulPartition string - Datacenter string - } - type args struct { - filters []gwv1beta1.HTTPRouteFilter - } - tests := []struct { - name string - fields fields - args args - want api.HTTPFilters - wantResponseFilters api.HTTPResponseFilters - }{ - { - name: "no httproutemodifier set", - fields: fields{}, - args: args{ - filters: []gwv1beta1.HTTPRouteFilter{ - { - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{}, - }, - }, - }, - want: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{}, - URLRewrite: nil, - }, - wantResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - }, - } - for _, tt := range tests { - t1.Run(tt.name, func(t1 *testing.T) { - t := ResourceTranslator{ - EnableConsulNamespaces: tt.fields.EnableConsulNamespaces, - ConsulDestNamespace: tt.fields.ConsulDestNamespace, - EnableK8sMirroring: tt.fields.EnableK8sMirroring, - MirroringPrefix: tt.fields.MirroringPrefix, - ConsulPartition: tt.fields.ConsulPartition, - Datacenter: tt.fields.Datacenter, - } - requestHeaders, responseHeaders := t.translateHTTPFilters(tt.args.filters, nil, "") - assert.Equalf(t1, tt.want, requestHeaders, "translateHTTPFilters(%v)", tt.args.filters) - assert.Equalf(t1, tt.wantResponseFilters, responseHeaders, "translateHTTPFilters(%v)", tt.args.filters) - }) - } -} - -func newSectionNamePtr(s string) *gwv1beta1.SectionName { - sectionName := gwv1beta1.SectionName(s) - return §ionName -} - -func TestResourceTranslator_toAPIGatewayListener(t *testing.T) { - type args struct { - gateway gwv1beta1.Gateway - listener gwv1beta1.Listener - gwcc *v1alpha1.GatewayClassConfig - } - tests := []struct { - name string - args args - policies []v1alpha1.GatewayPolicy - want api.APIGatewayListener - want1 bool - }{ - { - name: "listener with jwt auth", - policies: []v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: KindGateway, - Name: "test", - Namespace: "test", - SectionName: newSectionNamePtr("test-listener"), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "override-provider", - VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ - { - Path: []string{"path"}, - Value: "value", - }, - }, - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "default-provider", - VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ - { - Path: []string{"path"}, - Value: "value", - }, - }, - }, - }, - }}, - }, - }, - }, - args: args{ - gateway: gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: KindGateway, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "test", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "test-listener", - Port: 80, - Protocol: "HTTP", - }, - }, - }, - }, - listener: gwv1beta1.Listener{ - Name: "test-listener", - Port: 80, - Protocol: "HTTP", - }, - gwcc: &v1alpha1.GatewayClassConfig{ - Spec: v1alpha1.GatewayClassConfigSpec{}, - }, - }, - want: api.APIGatewayListener{ - Name: "test-listener", - Port: 80, - Protocol: "http", - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "override-provider", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"path"}, - Value: "value", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "default-provider", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"path"}, - Value: "value", - }, - }, - }, - }, - }, - }, - }, - want1: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t1 *testing.T) { - translator := ResourceTranslator{ - EnableConsulNamespaces: true, - ConsulDestNamespace: "", - EnableK8sMirroring: true, - MirroringPrefix: "", - } - - resources := NewResourceMap(translator, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) - for _, p := range tt.policies { - resources.AddGatewayPolicy(&p) - } - got, got1 := translator.toAPIGatewayListener(tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) - assert.Equalf(t, tt.want, got, "toAPIGatewayListener(%v, %v, %v, %v)", tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) - assert.Equalf(t, tt.want1, got1, "toAPIGatewayListener(%v, %v, %v, %v)", tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) - }) - } -} diff --git a/control-plane/api-gateway/controllers/finalizer.go b/control-plane/api-gateway/controllers/finalizer.go deleted file mode 100644 index c12f5f29e7..0000000000 --- a/control-plane/api-gateway/controllers/finalizer.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// EnsureFinalizer ensures that the given object has the given finalizer. -func EnsureFinalizer(ctx context.Context, client client.Client, object client.Object, finalizer string) (didUpdate bool, err error) { - finalizers := object.GetFinalizers() - for _, f := range finalizers { - if f == finalizer { - return false, nil - } - } - object.SetFinalizers(append(finalizers, finalizer)) - if err := client.Update(ctx, object); err != nil { - return false, err - } - - return true, nil -} - -// RemoveFinalizer removes the given finalizer from the given object. -func RemoveFinalizer(ctx context.Context, client client.Client, object client.Object, finalizer string) (didUpdate bool, err error) { - finalizers := object.GetFinalizers() - - for i, f := range finalizers { - if f == finalizer { - finalizers = append(finalizers[:i], finalizers[i+1:]...) - object.SetFinalizers(finalizers) - if err := client.Update(ctx, object); err != nil { - return false, err - } - return true, nil - } - } - - return false, nil -} diff --git a/control-plane/api-gateway/controllers/finalizer_test.go b/control-plane/api-gateway/controllers/finalizer_test.go deleted file mode 100644 index dc265ef6ca..0000000000 --- a/control-plane/api-gateway/controllers/finalizer_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -func TestEnsureFinalizer(t *testing.T) { - t.Parallel() - - finalizer := "test-finalizer" - - cases := map[string]struct { - initialFinalizers []string - finalizerToAdd string - expectedDidUpdate bool - }{ - "should update": {[]string{}, finalizer, true}, - "should not update": {[]string{finalizer}, finalizer, false}, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - // It doesn't matter what the object is, as long as it implements client.Object. - // A Pod was as good as any other object here. - testObj := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-obj", - Finalizers: tc.initialFinalizers, - }, - } - - client := fake.NewClientBuilder().WithObjects(testObj).Build() - - didUpdate, err := EnsureFinalizer(context.Background(), client, testObj, tc.finalizerToAdd) - - require.NoError(t, err) - require.Equal(t, tc.expectedDidUpdate, didUpdate) - }) - } -} - -func TestRemoveFinalizer(t *testing.T) { - t.Parallel() - - finalizer := "test-finalizer" - - cases := map[string]struct { - initialFinalizers []string - finalizerToRemove string - expectedDidUpdate bool - }{ - "should update": {[]string{finalizer}, finalizer, true}, - "should not update": {[]string{}, finalizer, false}, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - // It doesn't matter what the object is, as long as it implements client.Object. - // A Pod was as good as any other object here. - testObj := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-obj", - Finalizers: tc.initialFinalizers, - }, - } - - client := fake.NewClientBuilder().WithObjects(testObj).Build() - - didUpdate, err := RemoveFinalizer(context.Background(), client, testObj, tc.finalizerToRemove) - - require.NoError(t, err) - require.Equal(t, tc.expectedDidUpdate, didUpdate) - }) - } -} diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go deleted file mode 100644 index 8447e69f64..0000000000 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ /dev/null @@ -1,1290 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "fmt" - "reflect" - "strconv" - "strings" - - mapset "github.com/deckarep/golang-set" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - - "github.com/go-logr/logr" - appsv1 "k8s.io/api/apps/v1" - 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/fields" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/binding" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/gatekeeper" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/consul" -) - -// GatewayControllerConfig holds the values necessary for configuring the GatewayController. -type GatewayControllerConfig struct { - HelmConfig common.HelmConfig - ConsulClientConfig *consul.Config - ConsulServerConnMgr consul.ServerConnectionManager - NamespacesEnabled bool - CrossNamespaceACLPolicy string - Partition string - Datacenter string - AllowK8sNamespacesSet mapset.Set - DenyK8sNamespacesSet mapset.Set -} - -// GatewayController reconciles a Gateway object. -// The Gateway is responsible for defining the behavior of API gateways. -type GatewayController struct { - HelmConfig common.HelmConfig - Log logr.Logger - Translator common.ResourceTranslator - - cache *cache.Cache - gatewayCache *cache.GatewayCache - allowK8sNamespacesSet mapset.Set - denyK8sNamespacesSet mapset.Set - client.Client -} - -// Reconcile handles the reconciliation loop for Gateway objects. -func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - consulKey := r.Translator.ConfigEntryReference(api.APIGateway, req.NamespacedName) - nonNormalizedConsulKey := r.Translator.NonNormalizedConfigEntryReference(api.APIGateway, req.NamespacedName) - - var gateway gwv1beta1.Gateway - - log := r.Log.V(1).WithValues("gateway", req.NamespacedName) - log.Info("Reconciling Gateway") - - // get the gateway - if err := r.Client.Get(ctx, req.NamespacedName, &gateway); err != nil { - if !k8serrors.IsNotFound(err) { - log.Error(err, "unable to get Gateway") - } - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - // get the gateway class - gatewayClass, err := r.getGatewayClassForGateway(ctx, gateway) - if err != nil { - log.Error(err, "unable to get GatewayClass") - return ctrl.Result{}, err - } - - // get the gateway class config - gatewayClassConfig, err := r.getConfigForGatewayClass(ctx, gatewayClass) - if err != nil { - log.Error(err, "error fetching the gateway class config") - return ctrl.Result{}, err - } - - // get all namespaces - namespaces, err := r.getNamespaces(ctx) - if err != nil { - log.Error(err, "unable to list Namespaces") - return ctrl.Result{}, err - } - - // get all reference grants - grants, err := r.getReferenceGrants(ctx) - if err != nil { - log.Error(err, "unable to list ReferenceGrants") - return ctrl.Result{}, err - } - - // get related gateway service - service, err := r.getDeployedGatewayService(ctx, req.NamespacedName) - if err != nil { - log.Error(err, "unable to fetch service for Gateway") - } - - // get related gateway pods - pods, err := r.getDeployedGatewayPods(ctx, gateway) - if err != nil { - log.Error(err, "unable to list Pods for Gateway") - return ctrl.Result{}, err - } - - // construct our resource map - referenceValidator := binding.NewReferenceValidator(grants) - resources := common.NewResourceMap(r.Translator, referenceValidator, log) - - if err := r.fetchCertificatesForGateway(ctx, resources, gateway); err != nil { - log.Error(err, "unable to fetch certificates for gateway") - return ctrl.Result{}, err - } - - // fetch our inline certificates from cache, this needs to happen - // here since the certificates need to be reference counted before - // the gateways. - r.fetchConsulInlineCertificates(resources) - - // add our current gateway even if it's not controlled by us so we - // can garbage collect any resources for it. - resources.ReferenceCountGateway(gateway) - - if err := r.fetchControlledGateways(ctx, resources); err != nil { - log.Error(err, "unable to fetch controlled gateways") - return ctrl.Result{}, err - } - - // get all http routes referencing this gateway - httpRoutes, err := r.getRelatedHTTPRoutes(ctx, req.NamespacedName, resources) - if err != nil { - log.Error(err, "unable to list HTTPRoutes") - return ctrl.Result{}, err - } - - // get all tcp routes referencing this gateway - tcpRoutes, err := r.getRelatedTCPRoutes(ctx, req.NamespacedName, resources) - if err != nil { - log.Error(err, "unable to list TCPRoutes") - return ctrl.Result{}, err - } - - if err := r.fetchServicesForRoutes(ctx, resources, tcpRoutes, httpRoutes); err != nil { - log.Error(err, "unable to fetch services for routes") - return ctrl.Result{}, err - } - - // get all gatewaypolicies referencing this gateway - policies, err := r.getRelatedGatewayPolicies(ctx, req.NamespacedName, resources) - if err != nil { - log.Error(err, "unable to list gateway policies") - return ctrl.Result{}, err - } - - _, err = r.getJWTProviders(ctx, resources) - if err != nil { - log.Error(err, "unable to list JWT providers") - return ctrl.Result{}, err - } - - // fetch the rest of the consul objects from cache - consulServices := r.getConsulServices(consulKey) - consulGateway := r.getConsulGateway(consulKey) - r.fetchConsulHTTPRoutes(consulKey, resources) - r.fetchConsulTCPRoutes(consulKey, resources) - - binder := binding.NewBinder(binding.BinderConfig{ - Logger: log, - Translator: r.Translator, - ControllerName: common.GatewayClassControllerName, - Namespaces: namespaces, - GatewayClassConfig: gatewayClassConfig, - GatewayClass: gatewayClass, - Gateway: gateway, - Pods: pods, - Service: service, - HTTPRoutes: httpRoutes, - TCPRoutes: tcpRoutes, - Resources: resources, - ConsulGateway: consulGateway, - ConsulGatewayServices: consulServices, - Policies: policies, - }) - - updates := binder.Snapshot() - - if updates.UpsertGatewayDeployment { - if err := r.cache.EnsureRoleBinding(r.HelmConfig.AuthMethod, gateway.Name, gateway.Namespace); err != nil { - log.Error(err, "error creating role binding") - return ctrl.Result{}, err - } - - err := r.updateGatekeeperResources(ctx, log, &gateway, updates.GatewayClassConfig) - if err != nil { - if k8serrors.IsConflict(err) { - log.Info("error updating object when updating gateway resources, will try to re-reconcile") - - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "unable to update gateway resources") - return ctrl.Result{}, err - } - r.gatewayCache.EnsureSubscribed(nonNormalizedConsulKey, req.NamespacedName) - } else { - err := r.deleteGatekeeperResources(ctx, log, &gateway) - if err != nil { - if k8serrors.IsConflict(err) { - log.Info("error updating object when deleting gateway resources, will try to re-reconcile") - - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "unable to delete gateway resources") - return ctrl.Result{}, err - } - r.gatewayCache.RemoveSubscription(nonNormalizedConsulKey) - // make sure we have deregistered all services even if they haven't - // hit cache yet - if err := r.deregisterAllServices(ctx, nonNormalizedConsulKey); err != nil { - log.Error(err, "error deregistering services") - return ctrl.Result{}, err - } - } - - for _, deletion := range updates.Consul.Deletions { - log.Info("deleting from Consul", "kind", deletion.Kind, "namespace", deletion.Namespace, "name", deletion.Name) - if err := r.cache.Delete(ctx, deletion); err != nil { - log.Error(err, "error deleting config entry") - return ctrl.Result{}, err - } - } - - for _, update := range updates.Consul.Updates { - entry := update.Entry - log.Info("updating in Consul", "kind", entry.GetKind(), "namespace", entry.GetNamespace(), "name", entry.GetName()) - err := r.cache.Write(ctx, entry) - if update.OnUpdate != nil { - // swallow any potential error with our handler if one is provided - update.OnUpdate(err) - continue - } - - if err != nil { - log.Error(err, "error updating config entry") - return ctrl.Result{}, err - } - } - - if updates.UpsertGatewayDeployment { - // We only do some registration/deregistraion if we still have a valid gateway - // otherwise, we've already deregistered everything related to the gateway, so - // no need to do any of the following. - for _, registration := range updates.Consul.Registrations { - log.Info("registering service in Consul", "service", registration.Service.Service, "id", registration.Service.ID) - if err := r.cache.Register(ctx, registration); err != nil { - log.Error(err, "error registering service") - return ctrl.Result{}, err - } - } - - for _, deregistration := range updates.Consul.Deregistrations { - log.Info("deregistering service in Consul", "id", deregistration.ServiceID) - if err := r.cache.Deregister(ctx, deregistration); err != nil { - log.Error(err, "error deregistering service") - return ctrl.Result{}, err - } - } - } - - for _, update := range updates.Kubernetes.Updates.Operations() { - log.Info("update in Kubernetes", "kind", update.GetObjectKind().GroupVersionKind().Kind, "namespace", update.GetNamespace(), "name", update.GetName()) - if err := r.updateAndResetStatus(ctx, update); err != nil { - if k8serrors.IsConflict(err) { - log.Info("error updating object for gateway, will try to re-reconcile") - - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "error updating object") - return ctrl.Result{}, err - } - } - - for _, update := range updates.Kubernetes.StatusUpdates.Operations() { - log.Info("update status in Kubernetes", "kind", update.GetObjectKind().GroupVersionKind().Kind, "namespace", update.GetNamespace(), "name", update.GetName()) - if err := r.Client.Status().Update(ctx, update); err != nil { - if k8serrors.IsConflict(err) { - log.Info("error updating status for gateway, will try to re-reconcile") - - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "error updating status") - return ctrl.Result{}, err - } - } - - return ctrl.Result{}, nil -} - -func (r *GatewayController) deregisterAllServices(ctx context.Context, consulKey api.ResourceReference) error { - services, err := r.gatewayCache.FetchServicesFor(ctx, consulKey) - if err != nil { - return err - } - for _, service := range services { - if err := r.cache.Deregister(ctx, api.CatalogDeregistration{ - Node: service.Node, - ServiceID: service.ServiceID, - Namespace: service.Namespace, - }); err != nil { - return err - } - } - return nil -} - -func (r *GatewayController) updateAndResetStatus(ctx context.Context, o client.Object) error { - // we create a copy so that we can re-update its status if need be - status := reflect.ValueOf(o.DeepCopyObject()).Elem().FieldByName("Status") - if err := r.Client.Update(ctx, o); err != nil { - return err - } - - // reset the status in case it needs to be updated below - reflect.ValueOf(o).Elem().FieldByName("Status").Set(status) - return nil -} - -func configEntriesTo[T api.ConfigEntry](entries []api.ConfigEntry) []T { - es := []T{} - for _, e := range entries { - es = append(es, e.(T)) - } - return es -} - -func (r *GatewayController) deleteGatekeeperResources(ctx context.Context, log logr.Logger, gw *gwv1beta1.Gateway) error { - gk := gatekeeper.New(log, r.Client) - err := gk.Delete(ctx, types.NamespacedName{ - Namespace: gw.Namespace, - Name: gw.Name, - }) - if err != nil { - return err - } - - return nil -} - -func (r *GatewayController) updateGatekeeperResources(ctx context.Context, log logr.Logger, gw *gwv1beta1.Gateway, gwcc *v1alpha1.GatewayClassConfig) error { - gk := gatekeeper.New(log, r.Client) - err := gk.Upsert(ctx, *gw, *gwcc, r.HelmConfig) - if err != nil { - return err - } - - return nil -} - -// SetupWithGatewayControllerManager registers the controller with the given manager. -func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, config GatewayControllerConfig) (*cache.Cache, error) { - cacheConfig := cache.Config{ - ConsulClientConfig: config.ConsulClientConfig, - ConsulServerConnMgr: config.ConsulServerConnMgr, - NamespacesEnabled: config.NamespacesEnabled, - Datacenter: config.Datacenter, - CrossNamespaceACLPolicy: config.CrossNamespaceACLPolicy, - Logger: mgr.GetLogger(), - } - c := cache.New(cacheConfig) - gwc := cache.NewGatewayCache(ctx, cacheConfig) - - predicate, _ := predicate.LabelSelectorPredicate( - *metav1.SetAsLabelSelector(map[string]string{ - common.ManagedLabel: "true", - }), - ) - - r := &GatewayController{ - Client: mgr.GetClient(), - Log: mgr.GetLogger(), - HelmConfig: config.HelmConfig.Normalize(), - Translator: common.ResourceTranslator{ - EnableConsulNamespaces: config.HelmConfig.EnableNamespaces, - ConsulDestNamespace: config.HelmConfig.ConsulDestinationNamespace, - EnableK8sMirroring: config.HelmConfig.EnableNamespaceMirroring, - MirroringPrefix: config.HelmConfig.NamespaceMirroringPrefix, - ConsulPartition: config.HelmConfig.ConsulPartition, - Datacenter: config.Datacenter, - }, - denyK8sNamespacesSet: config.DenyK8sNamespacesSet, - allowK8sNamespacesSet: config.AllowK8sNamespacesSet, - cache: c, - gatewayCache: gwc, - } - - return c, ctrl.NewControllerManagedBy(mgr). - For(&gwv1beta1.Gateway{}). - Owns(&appsv1.Deployment{}). - Owns(&corev1.Service{}). - Owns(&corev1.Pod{}). - Watches( - source.NewKindWithCache(&gwv1beta1.ReferenceGrant{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformReferenceGrant(ctx)), - ). - Watches( - source.NewKindWithCache(&gwv1beta1.GatewayClass{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformGatewayClass(ctx)), - ). - Watches( - source.NewKindWithCache(&gwv1beta1.HTTPRoute{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformHTTPRoute(ctx)), - ). - Watches( - source.NewKindWithCache(&gwv1alpha2.TCPRoute{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformTCPRoute(ctx)), - ). - Watches( - source.NewKindWithCache(&corev1.Secret{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformSecret(ctx)), - ). - Watches( - source.NewKindWithCache(&v1alpha1.MeshService{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformMeshService(ctx)), - ). - Watches( - source.NewKindWithCache(&corev1.Endpoints{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformEndpoints(ctx)), - ). - Watches( - &source.Kind{Type: &corev1.Pod{}}, - handler.EnqueueRequestsFromMapFunc(r.transformPods(ctx)), - builder.WithPredicates(predicate), - ). - Watches( - // Subscribe to changes from Consul for APIGateways - &source.Channel{Source: c.Subscribe(ctx, api.APIGateway, r.transformConsulGateway).Events()}, - &handler.EnqueueRequestForObject{}, - ). - Watches( - // Subscribe to changes from Consul for HTTPRoutes - &source.Channel{Source: c.Subscribe(ctx, api.HTTPRoute, r.transformConsulHTTPRoute(ctx)).Events()}, - &handler.EnqueueRequestForObject{}, - ). - Watches( - // Subscribe to changes from Consul for TCPRoutes - &source.Channel{Source: c.Subscribe(ctx, api.TCPRoute, r.transformConsulTCPRoute(ctx)).Events()}, - &handler.EnqueueRequestForObject{}, - ). - Watches( - // Subscribe to changes from Consul for InlineCertificates - &source.Channel{Source: c.Subscribe(ctx, api.InlineCertificate, r.transformConsulInlineCertificate(ctx)).Events()}, - &handler.EnqueueRequestForObject{}, - ). - Watches( - &source.Channel{Source: c.Subscribe(ctx, api.JWTProvider, r.transformConsulJWTProvider(ctx)).Events()}, - &handler.EnqueueRequestForObject{}, - ). - Watches( - source.NewKindWithCache((&v1alpha1.GatewayPolicy{}), mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformGatewayPolicy(ctx)), - ). - Watches( - source.NewKindWithCache((&v1alpha1.RouteRetryFilter{}), mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformRouteRetryFilter(ctx)), - ). - Watches( - source.NewKindWithCache((&v1alpha1.RouteTimeoutFilter{}), mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformRouteTimeoutFilter(ctx)), - ). - Watches( - // Subscribe to changes in RouteAuthFilter custom resources referenced by HTTPRoutes. - source.NewKindWithCache((&v1alpha1.RouteAuthFilter{}), mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformRouteAuthFilter(ctx)), - ). - Complete(r) -} - -// transformGatewayClass will check the list of GatewayClass objects for a matching -// class, then return a list of reconcile Requests for it. -func (r *GatewayController) transformGatewayClass(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - gatewayClass := o.(*gwv1beta1.GatewayClass) - gatewayList := &gwv1beta1.GatewayList{} - if err := r.Client.List(ctx, gatewayList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Gateway_GatewayClassIndex, gatewayClass.Name), - }); err != nil { - return nil - } - return common.ObjectsToReconcileRequests(pointersOf(gatewayList.Items)) - } -} - -// transformHTTPRoute will check the HTTPRoute object for a matching -// class, then return a list of reconcile Requests for Gateways referring to it. -func (r *GatewayController) transformHTTPRoute(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - route := o.(*gwv1beta1.HTTPRoute) - - refs := refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs)) - statusRefs := refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, common.ConvertSliceFunc(route.Status.Parents, func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { - return parentStatus.ParentRef - }))) - return append(refs, statusRefs...) - } -} - -// transformTCPRoute will check the TCPRoute object for a matching -// class, then return a list of reconcile Requests for Gateways referring to it. -func (r *GatewayController) transformTCPRoute(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - route := o.(*gwv1alpha2.TCPRoute) - - refs := refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs)) - statusRefs := refsToRequests(common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, common.ConvertSliceFunc(route.Status.Parents, func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { - return parentStatus.ParentRef - }))) - return append(refs, statusRefs...) - } -} - -// transformSecret will check the Secret object for a matching -// class, then return a list of reconcile Requests for Gateways referring to it. -func (r *GatewayController) transformSecret(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - secret := o.(*corev1.Secret) - gatewayList := &gwv1beta1.GatewayList{} - if err := r.Client.List(ctx, gatewayList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Secret_GatewayIndex, client.ObjectKeyFromObject(secret).String()), - }); err != nil { - return nil - } - return common.ObjectsToReconcileRequests(pointersOf(gatewayList.Items)) - } -} - -// transformReferenceGrant will check the ReferenceGrant object for a matching -// class, then return a list of reconcile Requests for Gateways referring to it. -func (r *GatewayController) transformReferenceGrant(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - // just re-reconcile all gateways for now ideally this will filter down to gateways - // affected, but technically the blast radius is gateways in the namespace + referencing - // the namespace + the routes that bind to them. - gatewayList := &gwv1beta1.GatewayList{} - if err := r.Client.List(ctx, gatewayList); err != nil { - return nil - } - - return common.ObjectsToReconcileRequests(pointersOf(gatewayList.Items)) - } -} - -// transformMeshService will return a list of gateways that are referenced -// by a TCPRoute or HTTPRoute that references the mesh service. -func (r *GatewayController) transformMeshService(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - service := o.(*v1alpha1.MeshService) - key := client.ObjectKeyFromObject(service).String() - - return r.gatewaysForRoutesReferencing(ctx, TCPRoute_MeshServiceIndex, HTTPRoute_MeshServiceIndex, key) - } -} - -// transformConsulGateway will return a list of gateways that this corresponds to. -func (r *GatewayController) transformConsulGateway(entry api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{common.EntryToNamespacedName(entry)} -} - -// transformConsulHTTPRoute will return a list of gateways that need to be reconciled. -func (r *GatewayController) transformConsulHTTPRoute(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { - return func(entry api.ConfigEntry) []types.NamespacedName { - parents := mapset.NewSet() - for _, parent := range entry.(*api.HTTPRouteConfigEntry).Parents { - parents.Add(api.ResourceReference{ - Kind: parent.Kind, - Name: parent.Name, - Namespace: parent.Namespace, - Partition: parent.Partition, - }) - } - - var gateways []types.NamespacedName - for parent := range parents.Iter() { - if gateway := r.cache.Get(parent.(api.ResourceReference)); gateway != nil { - gateways = append(gateways, common.EntryToNamespacedName(gateway)) - } - } - return gateways - } -} - -// transformGatewayPolicy will return a list of all gateways that need to be reconcilled. -func (r *GatewayController) transformGatewayPolicy(ctx context.Context) func(object client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - gatewayPolicy := o.(*v1alpha1.GatewayPolicy) - gwNamespace := gatewayPolicy.Spec.TargetRef.Namespace - if gwNamespace == "" { - gwNamespace = gatewayPolicy.Namespace - } - gatewayRef := types.NamespacedName{ - Namespace: gwNamespace, - Name: gatewayPolicy.Spec.TargetRef.Name, - } - return []reconcile.Request{ - { - NamespacedName: gatewayRef, - }, - } - } -} - -// transformRouteRetryFilter will return a list of routes that need to be reconciled. -func (r *GatewayController) transformRouteRetryFilter(ctx context.Context) func(object client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteRetryFilterIndex, client.ObjectKeyFromObject(o).String()) - } -} - -// transformTimeoutRetryFilter will return a list of routes that need to be reconciled. -func (r *GatewayController) transformRouteTimeoutFilter(ctx context.Context) func(object client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteTimeoutFilterIndex, client.ObjectKeyFromObject(o).String()) - } -} - -func (r *GatewayController) transformRouteAuthFilter(ctx context.Context) func(object client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteAuthFilterIndex, client.ObjectKeyFromObject(o).String()) - } -} - -func (r *GatewayController) transformConsulTCPRoute(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { - return func(entry api.ConfigEntry) []types.NamespacedName { - parents := mapset.NewSet() - for _, parent := range entry.(*api.TCPRouteConfigEntry).Parents { - parents.Add(api.ResourceReference{ - Kind: parent.Kind, - Name: parent.Name, - Namespace: parent.Namespace, - Partition: parent.Partition, - }) - } - - var gateways []types.NamespacedName - for parent := range parents.Iter() { - if gateway := r.cache.Get(parent.(api.ResourceReference)); gateway != nil { - gateways = append(gateways, common.EntryToNamespacedName(gateway)) - } - } - return gateways - } -} - -func (r *GatewayController) transformConsulInlineCertificate(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { - return func(entry api.ConfigEntry) []types.NamespacedName { - certificateKey := api.ResourceReference{ - Kind: entry.GetKind(), - Name: entry.GetName(), - Namespace: entry.GetNamespace(), - Partition: entry.GetPartition(), - } - - var gateways []types.NamespacedName - for _, entry := range r.cache.List(api.APIGateway) { - gateway := entry.(*api.APIGatewayConfigEntry) - if gatewayReferencesCertificate(certificateKey, gateway) { - gateways = append(gateways, common.EntryToNamespacedName(gateway)) - } - } - - return gateways - } -} - -func (r *GatewayController) transformConsulJWTProvider(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { - return func(entry api.ConfigEntry) []types.NamespacedName { - var gateways []types.NamespacedName - - jwtEntry := entry.(*api.JWTProviderConfigEntry) - r.Log.Info("gatewaycontroller", "gateway items", r.cache.List(api.APIGateway)) - for _, gwEntry := range r.cache.List(api.APIGateway) { - gateway := gwEntry.(*api.APIGatewayConfigEntry) - LISTENER_LOOP: - for _, listener := range gateway.Listeners { - - r.Log.Info("override names", "listener", fmt.Sprintf("%#v", listener)) - if listener.Override != nil && listener.Override.JWT != nil { - for _, provider := range listener.Override.JWT.Providers { - r.Log.Info("override names", "provider", provider.Name, "entry", jwtEntry.Name) - if provider.Name == jwtEntry.Name { - gateways = append(gateways, common.EntryToNamespacedName(gateway)) - continue LISTENER_LOOP - } - } - } - - if listener.Default != nil && listener.Default.JWT != nil { - for _, provider := range listener.Default.JWT.Providers { - if provider.Name == jwtEntry.Name { - gateways = append(gateways, common.EntryToNamespacedName(gateway)) - continue LISTENER_LOOP - } - } - } - } - } - return gateways - } -} - -func gatewayReferencesCertificate(certificateKey api.ResourceReference, gateway *api.APIGatewayConfigEntry) bool { - for _, listener := range gateway.Listeners { - for _, cert := range listener.TLS.Certificates { - if cert == certificateKey { - return true - } - } - } - return false -} - -func (r *GatewayController) transformPods(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - pod := o.(*corev1.Pod) - - if gateway, managed := common.GatewayFromPod(pod); managed { - return []reconcile.Request{ - {NamespacedName: gateway}, - } - } - - return nil - } -} - -// transformEndpoints will return a list of gateways that are referenced -// by a TCPRoute or HTTPRoute that references the service. -func (r *GatewayController) transformEndpoints(ctx context.Context) func(o client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - key := client.ObjectKeyFromObject(o) - endpoints := o.(*corev1.Endpoints) - - if shouldIgnore(key.Namespace, r.denyK8sNamespacesSet, r.allowK8sNamespacesSet) || isLabeledIgnore(endpoints.Labels) { - return nil - } - - return r.gatewaysForRoutesReferencing(ctx, TCPRoute_ServiceIndex, HTTPRoute_ServiceIndex, key.String()) - } -} - -// gatewaysForRoutesReferencing returns a mapping of all gateways that are referenced by routes that -// have a backend associated with the given key and index. -func (r *GatewayController) gatewaysForRoutesReferencing(ctx context.Context, tcpIndex, httpIndex, key string) []reconcile.Request { - requestSet := make(map[types.NamespacedName]struct{}) - - if tcpIndex != "" { - tcpRouteList := &gwv1alpha2.TCPRouteList{} - if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(tcpIndex, key), - }); err != nil { - r.Log.Error(err, "unable to list TCPRoutes") - } - for _, route := range tcpRouteList.Items { - for _, ref := range common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs) { - requestSet[ref] = struct{}{} - } - } - } - - httpRouteList := &gwv1beta1.HTTPRouteList{} - if err := r.Client.List(ctx, httpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(httpIndex, key), - }); err != nil { - r.Log.Error(err, "unable to list HTTPRoutes") - } - for _, route := range httpRouteList.Items { - for _, ref := range common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs) { - requestSet[ref] = struct{}{} - } - } - - requests := []reconcile.Request{} - for request := range requestSet { - requests = append(requests, reconcile.Request{NamespacedName: request}) - } - return requests -} - -// pointersOf returns a list of pointers to the list of objects passed in. -func pointersOf[T any](objects []T) []*T { - pointers := make([]*T, 0, len(objects)) - for _, object := range objects { - pointers = append(pointers, pointerTo(object)) - } - return pointers -} - -// pointerTo returns a pointer to the object type passed in. -func pointerTo[T any](v T) *T { - return &v -} - -// refsToRequests takes a list of NamespacedName objects and returns a list of -// reconcile Requests. -func refsToRequests(objects []types.NamespacedName) []reconcile.Request { - requests := make([]reconcile.Request, 0, len(objects)) - for _, object := range objects { - requests = append(requests, reconcile.Request{ - NamespacedName: object, - }) - } - return requests -} - -// kubernetes helpers - -func (c *GatewayController) getNamespaces(ctx context.Context) (map[string]corev1.Namespace, error) { - var list corev1.NamespaceList - - if err := c.Client.List(ctx, &list); err != nil { - return nil, err - } - namespaces := map[string]corev1.Namespace{} - for _, namespace := range list.Items { - namespaces[namespace.Name] = namespace - } - - return namespaces, nil -} - -func (c *GatewayController) getReferenceGrants(ctx context.Context) ([]gwv1beta1.ReferenceGrant, error) { - var list gwv1beta1.ReferenceGrantList - - if err := c.Client.List(ctx, &list); err != nil { - return nil, err - } - - return list.Items, nil -} - -func (c *GatewayController) getDeployedGatewayService(ctx context.Context, gateway types.NamespacedName) (*corev1.Service, error) { - service := &corev1.Service{} - - // we use the implicit association of a service name/namespace with a corresponding gateway - if err := c.Client.Get(ctx, gateway, service); err != nil { - return nil, client.IgnoreNotFound(err) - } - - return service, nil -} - -func (c *GatewayController) getDeployedGatewayPods(ctx context.Context, gateway gwv1beta1.Gateway) ([]corev1.Pod, error) { - labels := common.LabelsForGateway(&gateway) - - var list corev1.PodList - - if err := c.Client.List(ctx, &list, client.MatchingLabels(labels)); err != nil { - return nil, err - } - - return list.Items, nil -} - -func (c *GatewayController) getRelatedHTTPRoutes(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]gwv1beta1.HTTPRoute, error) { - var list gwv1beta1.HTTPRouteList - - if err := c.Client.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(HTTPRoute_GatewayIndex, gateway.String()), - }); err != nil { - return nil, err - } - - for _, route := range list.Items { - resources.ReferenceCountHTTPRoute(route) - - _, err := c.getExternalFiltersForHTTPRoute(ctx, route, resources) - if err != nil { - c.Log.Error(err, "unable to list HTTPRoute ExternalFilters") - return nil, err - } - } - - return list.Items, nil -} - -func (c *GatewayController) getExternalFiltersForHTTPRoute(ctx context.Context, route gwv1beta1.HTTPRoute, resources *common.ResourceMap) ([]interface{}, error) { - var externalFilters []interface{} - for _, rule := range route.Spec.Rules { - ruleFilters, err := c.filterFiltersForExternalRefs(ctx, route, rule.Filters, resources) - if err != nil { - return nil, err - } - externalFilters = append(externalFilters, ruleFilters...) - - for _, backendRef := range rule.BackendRefs { - backendRefFilter, err := c.filterFiltersForExternalRefs(ctx, route, backendRef.Filters, resources) - if err != nil { - return nil, err - } - - externalFilters = append(externalFilters, backendRefFilter...) - } - } - - return externalFilters, nil -} - -func (c *GatewayController) filterFiltersForExternalRefs(ctx context.Context, route gwv1beta1.HTTPRoute, filters []gwv1beta1.HTTPRouteFilter, resources *common.ResourceMap) ([]interface{}, error) { - var externalFilters []interface{} - - for _, filter := range filters { - var externalFilter client.Object - - // check to see if we need to grab this filter - if filter.ExtensionRef == nil { - continue - } - switch kind := filter.ExtensionRef.Kind; kind { - case v1alpha1.RouteRetryFilterKind: - externalFilter = &v1alpha1.RouteRetryFilter{} - case v1alpha1.RouteTimeoutFilterKind: - externalFilter = &v1alpha1.RouteTimeoutFilter{} - case v1alpha1.RouteAuthFilterKind: - externalFilter = &v1alpha1.RouteAuthFilter{} - default: - continue - } - - // get object from API - err := c.Client.Get(ctx, client.ObjectKey{ - Name: string(filter.ExtensionRef.Name), - Namespace: route.Namespace, - }, externalFilter) - if err != nil { - if k8serrors.IsNotFound(err) { - c.Log.Info(fmt.Sprintf("externalref %s:%s not found: %v", filter.ExtensionRef.Kind, filter.ExtensionRef.Name, err)) - // ignore, the validation call should mark this route as error - continue - } else { - return nil, err - } - } - - // add external ref (or error) to resource map for this route - resources.AddExternalFilter(externalFilter) - externalFilters = append(externalFilters, externalFilter) - } - return externalFilters, nil -} - -func (c *GatewayController) getRelatedGatewayPolicies(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]v1alpha1.GatewayPolicy, error) { - var list v1alpha1.GatewayPolicyList - - if err := c.Client.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gateway.String()), - }); err != nil { - return nil, err - } - - // add all policies to the resourcemap - for _, policy := range list.Items { - resources.AddGatewayPolicy(&policy) - } - - return list.Items, nil -} - -func (c *GatewayController) getJWTProviders(ctx context.Context, resources *common.ResourceMap) ([]v1alpha1.JWTProvider, error) { - var list v1alpha1.JWTProviderList - - if err := c.Client.List(ctx, &list, &client.ListOptions{}); err != nil { - return nil, err - } - - // add all policies to the resourcemap - for _, provider := range list.Items { - resources.AddJWTProvider(&provider) - } - - return list.Items, nil -} - -func (c *GatewayController) getRelatedTCPRoutes(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]gwv1alpha2.TCPRoute, error) { - var list gwv1alpha2.TCPRouteList - - if err := c.Client.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(TCPRoute_GatewayIndex, gateway.String()), - }); err != nil { - return nil, err - } - - for _, route := range list.Items { - resources.ReferenceCountTCPRoute(route) - } - - return list.Items, nil -} - -func (c *GatewayController) getConfigForGatewayClass(ctx context.Context, gatewayClassConfig *gwv1beta1.GatewayClass) (*v1alpha1.GatewayClassConfig, error) { - if gatewayClassConfig == nil { - // if we don't have a gateway class we can't fetch the corresponding config - return nil, nil - } - - config := &v1alpha1.GatewayClassConfig{} - if ref := gatewayClassConfig.Spec.ParametersRef; ref != nil { - if string(ref.Group) != v1alpha1.GroupVersion.Group || - ref.Kind != v1alpha1.GatewayClassConfigKind || - gatewayClassConfig.Spec.ControllerName != common.GatewayClassControllerName { - // we don't have supported params, so return nil - return nil, nil - } - - if err := c.Client.Get(ctx, types.NamespacedName{Name: ref.Name}, config); err != nil { - return nil, client.IgnoreNotFound(err) - } - } - return config, nil -} - -func (c *GatewayController) getGatewayClassForGateway(ctx context.Context, gateway gwv1beta1.Gateway) (*gwv1beta1.GatewayClass, error) { - var gatewayClass gwv1beta1.GatewayClass - if err := c.Client.Get(ctx, types.NamespacedName{Name: string(gateway.Spec.GatewayClassName)}, &gatewayClass); err != nil { - return nil, client.IgnoreNotFound(err) - } - return &gatewayClass, nil -} - -// resource map construction routines - -func (c *GatewayController) fetchControlledGateways(ctx context.Context, resources *common.ResourceMap) error { - set := mapset.NewSet() - - list := gwv1beta1.GatewayClassList{} - if err := c.Client.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(GatewayClass_ControllerNameIndex, common.GatewayClassControllerName), - }); err != nil { - return err - } - for _, gatewayClass := range list.Items { - set.Add(gatewayClass.Name) - } - - gateways := &gwv1beta1.GatewayList{} - if err := c.Client.List(ctx, gateways); err != nil { - return err - } - - for _, gateway := range gateways.Items { - if set.Contains(string(gateway.Spec.GatewayClassName)) { - resources.ReferenceCountGateway(gateway) - } - } - return nil -} - -func (c *GatewayController) fetchCertificatesForGateway(ctx context.Context, resources *common.ResourceMap, gateway gwv1beta1.Gateway) error { - certificates := mapset.NewSet() - - for _, listener := range gateway.Spec.Listeners { - if listener.TLS != nil { - for _, cert := range listener.TLS.CertificateRefs { - if common.NilOrEqual(cert.Group, "") && common.NilOrEqual(cert.Kind, common.KindSecret) { - certificates.Add(common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace)) - } - } - } - } - - for key := range certificates.Iter() { - if err := c.fetchSecret(ctx, resources, key.(types.NamespacedName)); err != nil { - return err - } - } - - return nil -} - -func (c *GatewayController) fetchSecret(ctx context.Context, resources *common.ResourceMap, key types.NamespacedName) error { - var secret corev1.Secret - if err := c.Client.Get(ctx, key, &secret); err != nil { - return client.IgnoreNotFound(err) - } - - resources.ReferenceCountCertificate(secret) - - return nil -} - -func (c *GatewayController) fetchServicesForRoutes(ctx context.Context, resources *common.ResourceMap, tcpRoutes []gwv1alpha2.TCPRoute, httpRoutes []gwv1beta1.HTTPRoute) error { - serviceBackends := mapset.NewSet() - meshServiceBackends := mapset.NewSet() - - for _, route := range httpRoutes { - for _, rule := range route.Spec.Rules { - for _, backend := range rule.BackendRefs { - if common.DerefEqual(backend.Group, v1alpha1.ConsulHashicorpGroup) && - common.DerefEqual(backend.Kind, v1alpha1.MeshServiceKind) { - meshServiceBackends.Add(common.IndexedNamespacedNameWithDefault(backend.Name, backend.Namespace, route.Namespace)) - } else if common.NilOrEqual(backend.Group, "") && common.NilOrEqual(backend.Kind, "Service") { - serviceBackends.Add(common.IndexedNamespacedNameWithDefault(backend.Name, backend.Namespace, route.Namespace)) - } - } - } - } - - for _, route := range tcpRoutes { - for _, rule := range route.Spec.Rules { - for _, backend := range rule.BackendRefs { - if common.DerefEqual(backend.Group, v1alpha1.ConsulHashicorpGroup) && - common.DerefEqual(backend.Kind, v1alpha1.MeshServiceKind) { - meshServiceBackends.Add(common.IndexedNamespacedNameWithDefault(backend.Name, backend.Namespace, route.Namespace)) - } else if common.NilOrEqual(backend.Group, "") && common.NilOrEqual(backend.Kind, "Service") { - serviceBackends.Add(common.IndexedNamespacedNameWithDefault(backend.Name, backend.Namespace, route.Namespace)) - } - } - } - } - - for key := range meshServiceBackends.Iter() { - if err := c.fetchMeshService(ctx, resources, key.(types.NamespacedName)); err != nil { - return err - } - } - - for key := range serviceBackends.Iter() { - if err := c.fetchServicesForEndpoints(ctx, resources, key.(types.NamespacedName)); err != nil { - return err - } - } - return nil -} - -func (c *GatewayController) fetchMeshService(ctx context.Context, resources *common.ResourceMap, key types.NamespacedName) error { - var service v1alpha1.MeshService - if err := c.Client.Get(ctx, key, &service); err != nil { - return client.IgnoreNotFound(err) - } - - resources.AddMeshService(service) - - return nil -} - -func (c *GatewayController) fetchServicesForEndpoints(ctx context.Context, resources *common.ResourceMap, key types.NamespacedName) error { - if shouldIgnore(key.Namespace, c.denyK8sNamespacesSet, c.allowK8sNamespacesSet) { - return nil - } - - var endpoints corev1.Endpoints - if err := c.Client.Get(ctx, key, &endpoints); err != nil { - return client.IgnoreNotFound(err) - } - - if isLabeledIgnore(endpoints.Labels) { - return nil - } - - for _, subset := range endpoints.Subsets { - for _, address := range subset.Addresses { - if address.TargetRef != nil && address.TargetRef.Kind == "Pod" { - objectKey := types.NamespacedName{Name: address.TargetRef.Name, Namespace: address.TargetRef.Namespace} - - var pod corev1.Pod - if err := c.Client.Get(ctx, objectKey, &pod); err != nil { - if k8serrors.IsNotFound(err) { - continue - } - return err - } - - resources.AddService(key, serviceName(pod, endpoints)) - - } - } - } - - return nil -} - -// cache routines - -func (c *GatewayController) getConsulServices(ref api.ResourceReference) []api.CatalogService { - return c.gatewayCache.ServicesFor(ref) -} - -func (c *GatewayController) getConsulGateway(ref api.ResourceReference) *api.APIGatewayConfigEntry { - if entry := c.cache.Get(ref); entry != nil { - return entry.(*api.APIGatewayConfigEntry) - } - return nil -} - -func (c *GatewayController) fetchConsulHTTPRoutes(ref api.ResourceReference, resources *common.ResourceMap) { - for _, route := range configEntriesTo[*api.HTTPRouteConfigEntry](c.cache.List(api.HTTPRoute)) { - if routeReferencesGateway(route.Namespace, ref, route.Parents) { - resources.ReferenceCountConsulHTTPRoute(*route) - } - } -} - -func (c *GatewayController) fetchConsulTCPRoutes(ref api.ResourceReference, resources *common.ResourceMap) { - for _, route := range configEntriesTo[*api.TCPRouteConfigEntry](c.cache.List(api.TCPRoute)) { - if routeReferencesGateway(route.Namespace, ref, route.Parents) { - resources.ReferenceCountConsulTCPRoute(*route) - } - } -} - -func (c *GatewayController) fetchConsulInlineCertificates(resources *common.ResourceMap) { - for _, cert := range configEntriesTo[*api.InlineCertificateConfigEntry](c.cache.List(api.InlineCertificate)) { - resources.ReferenceCountConsulCertificate(*cert) - } -} - -func routeReferencesGateway(namespace string, ref api.ResourceReference, refs []api.ResourceReference) bool { - // we don't need to check partition here since they're all in the same partition - if namespace == "" { - namespace = "default" - } - - for _, parent := range refs { - if common.EmptyOrEqual(parent.Kind, api.APIGateway) { - if common.DefaultOrEqual(parent.Namespace, namespace, ref.Namespace) && - parent.Name == ref.Name { - return true - } - } - } - - return false -} - -func serviceName(pod corev1.Pod, serviceEndpoints corev1.Endpoints) string { - svcName := serviceEndpoints.Name - // If the annotation has a comma, it is a multi port Pod. In that case we always use the name of the endpoint. - if serviceNameFromAnnotation, ok := pod.Annotations[constants.AnnotationService]; ok && serviceNameFromAnnotation != "" && !strings.Contains(serviceNameFromAnnotation, ",") { - svcName = serviceNameFromAnnotation - } - return svcName -} - -func isLabeledIgnore(labels map[string]string) bool { - value, labelExists := labels[constants.LabelServiceIgnore] - shouldIgnore, err := strconv.ParseBool(value) - - return shouldIgnore && labelExists && err == nil -} - -// shouldIgnore ignores namespaces where we don't connect-inject. -func shouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { - // Ignores system namespaces. - if namespace == metav1.NamespaceSystem || namespace == metav1.NamespacePublic || namespace == "local-path-storage" { - return true - } - - // Ignores deny list. - if denySet.Contains(namespace) { - return true - } - - // Ignores if not in allow list or allow list is not *. - if !allowSet.Contains("*") && !allowSet.Contains(namespace) { - return true - } - - return false -} diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go deleted file mode 100644 index ee8d8240f6..0000000000 --- a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go +++ /dev/null @@ -1,1637 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "math/big" - "sync" - "testing" - "time" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, clientgoscheme.AddToScheme(s)) - require.NoError(t, gwv1alpha2.Install(s)) - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - testCases := map[string]struct { - namespace string - certFn func(*testing.T, context.Context, client.WithWatch, string) *corev1.Secret - gwFn func(*testing.T, context.Context, client.WithWatch, string) *gwv1beta1.Gateway - httpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway, *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute - tcpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *v1alpha2.TCPRoute - externalFilterFn func(*testing.T, context.Context, client.WithWatch, string) *v1alpha1.RouteAuthFilter - policyFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway, string) - }{ - "all fields set": { - namespace: "consul", - certFn: createCert, - gwFn: createAllFieldsSetAPIGW, - httpRouteFn: createAllFieldsSetHTTPRoute, - tcpRouteFn: createAllFieldsSetTCPRoute, - externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { - return nil - }, - policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, - }, - "minimal fields set": { - namespace: "", - certFn: createCert, - gwFn: minimalFieldsSetAPIGW, - httpRouteFn: minimalFieldsSetHTTPRoute, - tcpRouteFn: minimalFieldsSetTCPRoute, - externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { - return nil - }, - policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, - }, - "funky casing to test normalization doesnt cause infinite reconciliation": { - namespace: "", - certFn: createCert, - gwFn: createFunkyCasingFieldsAPIGW, - httpRouteFn: createFunkyCasingFieldsHTTPRoute, - tcpRouteFn: createFunkyCasingFieldsTCPRoute, - externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { - return nil - }, - policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, - }, - "http route with JWT auth": { - namespace: "", - certFn: createCert, - gwFn: createAllFieldsSetAPIGW, - httpRouteFn: createJWTAuthHTTPRoute, - tcpRouteFn: createFunkyCasingFieldsTCPRoute, - externalFilterFn: createRouteAuthFilter, - policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, - }, - "policy attached to gateway": { - namespace: "", - certFn: createCert, - gwFn: createAllFieldsSetAPIGW, - httpRouteFn: createAllFieldsSetHTTPRoute, - tcpRouteFn: createFunkyCasingFieldsTCPRoute, - externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { - return nil - }, - policyFn: createGWPolicy, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - k8sClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s)).Build() - consulTestServerClient := test.TestServerWithMockConnMgrWatcher(t, nil) - ctx, cancel := context.WithCancel(context.Background()) - - t.Cleanup(func() { - cancel() - }) - logger := logrtest.New(t) - - cacheCfg := cache.Config{ - ConsulClientConfig: consulTestServerClient.Cfg, - ConsulServerConnMgr: consulTestServerClient.Watcher, - Logger: logger, - } - resourceCache := cache.New(cacheCfg) - - gwCache := cache.NewGatewayCache(ctx, cacheCfg) - - gwCtrl := GatewayController{ - HelmConfig: common.HelmConfig{}, - Log: logger, - Translator: common.ResourceTranslator{}, - cache: resourceCache, - gatewayCache: gwCache, - Client: k8sClient, - allowK8sNamespacesSet: mapset.NewSet(), - denyK8sNamespacesSet: mapset.NewSet(), - } - - go func() { - resourceCache.Run(ctx) - }() - - resourceCache.WaitSynced(ctx) - - gwSub := resourceCache.Subscribe(ctx, api.APIGateway, gwCtrl.transformConsulGateway) - httpRouteSub := resourceCache.Subscribe(ctx, api.HTTPRoute, gwCtrl.transformConsulHTTPRoute(ctx)) - tcpRouteSub := resourceCache.Subscribe(ctx, api.TCPRoute, gwCtrl.transformConsulTCPRoute(ctx)) - inlineCertSub := resourceCache.Subscribe(ctx, api.InlineCertificate, gwCtrl.transformConsulInlineCertificate(ctx)) - - cert := tc.certFn(t, ctx, k8sClient, tc.namespace) - k8sGWObj := tc.gwFn(t, ctx, k8sClient, tc.namespace) - - // reconcile so we add the finalizer - _, err := gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - Name: k8sGWObj.Name, - }, - }) - require.NoError(t, err) - - // reconcile again so that we get the creation with the finalizer - _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - Name: k8sGWObj.Name, - }, - }) - require.NoError(t, err) - - jwtProvider := createJWTProvider(t, ctx, k8sClient) - authFilterObj := tc.externalFilterFn(t, ctx, k8sClient, jwtProvider.Name) - httpRouteObj := tc.httpRouteFn(t, ctx, k8sClient, k8sGWObj, authFilterObj) - tcpRouteObj := tc.tcpRouteFn(t, ctx, k8sClient, k8sGWObj) - tc.policyFn(t, ctx, k8sClient, k8sGWObj, jwtProvider.Name) - - // reconcile again so that we get the route bound to the gateway - _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - Name: k8sGWObj.Name, - }, - }) - require.NoError(t, err) - - // reconcile again so that we get the route bound to the gateway - _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - Name: k8sGWObj.Name, - }, - }) - require.NoError(t, err) - - wg := &sync.WaitGroup{} - // we never get the event from the cert because when it's created there are no gateways that reference it - wg.Add(3) - go func(w *sync.WaitGroup) { - gwDone := false - httpRouteDone := false - tcpRouteDone := false - for { - // get the creation events from the upsert and then continually read from channel so we dont block other subs - select { - case <-ctx.Done(): - return - case <-gwSub.Events(): - if !gwDone { - gwDone = true - w.Done() - } - case <-httpRouteSub.Events(): - if !httpRouteDone { - httpRouteDone = true - w.Done() - } - case <-tcpRouteSub.Events(): - if !tcpRouteDone { - tcpRouteDone = true - w.Done() - } - case <-inlineCertSub.Events(): - } - } - }(wg) - - wg.Wait() - - gwNamespaceName := types.NamespacedName{ - Name: k8sGWObj.Name, - Namespace: k8sGWObj.Namespace, - } - - httpRouteNamespaceName := types.NamespacedName{ - Name: httpRouteObj.Name, - Namespace: httpRouteObj.Namespace, - } - - tcpRouteNamespaceName := types.NamespacedName{ - Name: tcpRouteObj.Name, - Namespace: tcpRouteObj.Namespace, - } - - certNamespaceName := types.NamespacedName{ - Name: cert.Name, - Namespace: cert.Namespace, - } - - gwRef := gwCtrl.Translator.ConfigEntryReference(api.APIGateway, gwNamespaceName) - httpRouteRef := gwCtrl.Translator.ConfigEntryReference(api.HTTPRoute, httpRouteNamespaceName) - tcpRouteRef := gwCtrl.Translator.ConfigEntryReference(api.TCPRoute, tcpRouteNamespaceName) - certRef := gwCtrl.Translator.ConfigEntryReference(api.InlineCertificate, certNamespaceName) - - curGWModifyIndex := resourceCache.Get(gwRef).GetModifyIndex() - curHTTPRouteModifyIndex := resourceCache.Get(httpRouteRef).GetModifyIndex() - curTCPRouteModifyIndex := resourceCache.Get(tcpRouteRef).GetModifyIndex() - curCertModifyIndex := resourceCache.Get(certRef).GetModifyIndex() - - err = k8sClient.Get(ctx, gwNamespaceName, k8sGWObj) - require.NoError(t, err) - curGWResourceVersion := k8sGWObj.ResourceVersion - - err = k8sClient.Get(ctx, httpRouteNamespaceName, httpRouteObj) - require.NoError(t, err) - curHTTPRouteResourceVersion := httpRouteObj.ResourceVersion - - err = k8sClient.Get(ctx, tcpRouteNamespaceName, tcpRouteObj) - require.NoError(t, err) - curTCPRouteResourceVersion := tcpRouteObj.ResourceVersion - - err = k8sClient.Get(ctx, certNamespaceName, cert) - require.NoError(t, err) - curCertResourceVersion := cert.ResourceVersion - - go func() { - // reconcile multiple times with no changes to be sure - for i := 0; i < 5; i++ { - _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: k8sGWObj.Namespace, - }, - }) - require.NoError(t, err) - } - }() - - require.Never(t, func() bool { - err = k8sClient.Get(ctx, gwNamespaceName, k8sGWObj) - require.NoError(t, err) - newGWResourceVersion := k8sGWObj.ResourceVersion - - err = k8sClient.Get(ctx, httpRouteNamespaceName, httpRouteObj) - require.NoError(t, err) - newHTTPRouteResourceVersion := httpRouteObj.ResourceVersion - - err = k8sClient.Get(ctx, tcpRouteNamespaceName, tcpRouteObj) - require.NoError(t, err) - newTCPRouteResourceVersion := tcpRouteObj.ResourceVersion - - err = k8sClient.Get(ctx, certNamespaceName, cert) - require.NoError(t, err) - newCertResourceVersion := cert.ResourceVersion - - return curGWModifyIndex == resourceCache.Get(gwRef).GetModifyIndex() && - curGWResourceVersion == newGWResourceVersion && - curHTTPRouteModifyIndex == resourceCache.Get(httpRouteRef).GetModifyIndex() && - curHTTPRouteResourceVersion == newHTTPRouteResourceVersion && - curTCPRouteModifyIndex == resourceCache.Get(tcpRouteRef).GetModifyIndex() && - curTCPRouteResourceVersion == newTCPRouteResourceVersion && - curCertModifyIndex == resourceCache.Get(certRef).GetModifyIndex() && - curCertResourceVersion == newCertResourceVersion - }, time.Duration(2*time.Second), time.Duration(500*time.Millisecond), fmt.Sprintf("curGWModifyIndex: %d, newIndx: %d", curGWModifyIndex, resourceCache.Get(gwRef).GetModifyIndex()), - ) - }) - } -} - -func createAllFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { - // listener one configuration - listenerOneName := "listener-one" - listenerOneHostname := "*.consul.io" - listenerOnePort := 3366 - listenerOneProtocol := "https" - - // listener two configuration - listenerTwoName := "listener-two" - listenerTwoHostname := "*.consul.io" - listenerTwoPort := 5432 - listenerTwoProtocol := "http" - - // listener three configuration - listenerThreeName := "listener-three" - listenerThreePort := 8081 - listenerThreeProtocol := "tcp" - - // listener four configuration - listenerFourName := "listener-four" - listenerFourHostname := "*.consul.io" - listenerFourPort := 5433 - listenerFourProtocol := "http" - - // Write gw to k8s - gwClassCfg := &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClassConfig", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-class-config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{}, - } - gwClass := &gwv1beta1.GatewayClass{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClass", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewayclass", - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "consul.hashicorp.com/gateway-controller", - ParametersRef: &gwv1beta1.ParametersReference{ - Group: "consul.hashicorp.com", - Kind: "GatewayClassConfig", - Name: "gateway-class-config", - }, - Description: new(string), - }, - } - gw := &gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "Gateway", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: namespace, - Annotations: make(map[string]string), - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName(listenerOneName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), - Port: gwv1beta1.PortNumber(listenerOnePort), - Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - { - Kind: common.PointerTo(gwv1beta1.Kind("Secret")), - Name: gwv1beta1.ObjectName("one-cert"), - Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), - }, - }, - }, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerTwoName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), - Port: gwv1beta1.PortNumber(listenerTwoPort), - Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("Same")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerThreeName), - Port: gwv1beta1.PortNumber(listenerThreePort), - Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerFourName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerFourHostname)), - Port: gwv1beta1.PortNumber(listenerFourPort), - Protocol: gwv1beta1.ProtocolType(listenerFourProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("Selector")), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.NamespaceNameLabel: "consul", - }, - MatchExpressions: []metav1.LabelSelectorRequirement{}, - }, - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, gwClassCfg) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gwClass) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gw) - require.NoError(t, err) - - return gw -} - -func createJWTAuthHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, authFilter *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { - svcDefault := &v1alpha1.ServiceDefaults{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceDefaults", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "high", - Protocol: "TCP", - Port: 8080, - }, - }, - Selector: map[string]string{"app": "Service"}, - }, - } - - serviceAccount := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - } - - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "Service"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{}, - }, - }, - } - - err := k8sClient.Create(ctx, svcDefault) - require.NoError(t, err) - - err = k8sClient.Create(ctx, svc) - require.NoError(t, err) - - err = k8sClient.Create(ctx, serviceAccount) - require.NoError(t, err) - - err = k8sClient.Create(ctx, deployment) - require.NoError(t, err) - - route := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[0].Name, - Port: &gw.Spec.Listeners[0].Port, - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: common.PointerTo(gwv1beta1.PathMatchType("PathPrefix")), - Value: common.PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: common.PointerTo(gwv1beta1.HeaderMatchExact), - Name: "version", - Value: "version", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "q", - }, - }, - Method: common.PointerTo(gwv1beta1.HTTPMethod("GET")), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "foo", - Value: "bax", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "arc", - Value: "reactor", - }, - }, - Remove: []string{"remove"}, - }, - }, - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.FullPathHTTPPathModifier, - ReplaceFullPath: common.PointerTo("/foobar"), - }, - }, - }, - - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: common.PointerTo("/foo"), - }, - }, - }, - { - Type: gwv1beta1.HTTPRouteFilterExtensionRef, - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: v1alpha1.RouteAuthFilterKind, - Name: gwv1beta1.ObjectName(authFilter.Name), - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(8080)), - }, - Weight: common.PointerTo(int32(50)), - }, - }, - }, - }, - }, - }, - } - - err = k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createAllFieldsSetTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { - route := &v1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "TCPRoute", - APIVersion: "gateway.networking.k8s.io/v1alpha2", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[2].Name, - Port: &gw.Spec.Listeners[2].Port, - }, - }, - }, - Rules: []gwv1alpha2.TCPRouteRule{ - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(25000)), - }, - Weight: common.PointerTo(int32(50)), - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createCert(t *testing.T, ctx context.Context, k8sClient client.WithWatch, certNS string) *corev1.Secret { - // listener one tls config - certName := "one-cert" - - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - require.NoError(t, err) - - usage := x509.KeyUsageCertSign - expiration := time.Now().AddDate(10, 0, 0) - - cert := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: "consul.test", - }, - IsCA: true, - NotBefore: time.Now().Add(-10 * time.Minute), - NotAfter: expiration, - SubjectKeyId: []byte{1, 2, 3, 4, 6}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: usage, - BasicConstraintsValid: true, - } - caCert := cert - caPrivateKey := privateKey - - data, err := x509.CreateCertificate(rand.Reader, cert, caCert, &privateKey.PublicKey, caPrivateKey) - require.NoError(t, err) - - certBytes := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: data, - }) - - privateKeyBytes := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: certNS, - Name: certName, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: certBytes, - corev1.TLSPrivateKeyKey: privateKeyBytes, - }, - } - - err = k8sClient.Create(ctx, secret) - require.NoError(t, err) - - return secret -} - -func minimalFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { - // listener one configuration - listenerOneName := "listener-one" - listenerOneHostname := "*.consul.io" - listenerOnePort := 3366 - listenerOneProtocol := "https" - - // listener three configuration - listenerThreeName := "listener-three" - listenerThreePort := 8081 - listenerThreeProtocol := "tcp" - - // Write gw to k8s - gwClassCfg := &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClassConfig", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-class-config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{}, - } - gwClass := &gwv1beta1.GatewayClass{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClass", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewayclass", - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "consul.hashicorp.com/gateway-controller", - ParametersRef: &gwv1beta1.ParametersReference{ - Group: "consul.hashicorp.com", - Kind: "GatewayClassConfig", - Name: "gateway-class-config", - }, - Description: new(string), - }, - } - gw := &gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "Gateway", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Annotations: make(map[string]string), - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName(listenerOneName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), - Port: gwv1beta1.PortNumber(listenerOnePort), - Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - { - Kind: common.PointerTo(gwv1beta1.Kind("Secret")), - Name: gwv1beta1.ObjectName("one-cert"), - Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), - }, - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerThreeName), - Port: gwv1beta1.PortNumber(listenerThreePort), - Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, gwClassCfg) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gwClass) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gw) - require.NoError(t, err) - - return gw -} - -func minimalFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, _ *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { - svcDefault := &v1alpha1.ServiceDefaults{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceDefaults", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "high", - Protocol: "TCP", - Port: 8080, - }, - }, - Selector: map[string]string{"app": "Service"}, - }, - } - - serviceAccount := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - } - - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "Service"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{}, - }, - }, - } - - err := k8sClient.Create(ctx, svcDefault) - require.NoError(t, err) - - err = k8sClient.Create(ctx, svc) - require.NoError(t, err) - - err = k8sClient.Create(ctx, serviceAccount) - require.NoError(t, err) - - err = k8sClient.Create(ctx, deployment) - require.NoError(t, err) - - route := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[0].Name, - Port: &gw.Spec.Listeners[0].Port, - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, - Rules: []gwv1beta1.HTTPRouteRule{ - { - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(8080)), - }, - }, - }, - }, - }, - }, - }, - } - - err = k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func minimalFieldsSetTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { - route := &v1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "TCPRoute", - APIVersion: "gateway.networking.k8s.io/v1alpha2", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[1].Name, - Port: &gw.Spec.Listeners[1].Port, - }, - }, - }, - Rules: []gwv1alpha2.TCPRouteRule{ - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(25000)), - }, - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createFunkyCasingFieldsAPIGW(t *testing.T, ctx context.Context, k8sClient client.WithWatch, namespace string) *gwv1beta1.Gateway { - // listener one configuration - listenerOneName := "listener-one" - listenerOneHostname := "*.consul.io" - listenerOnePort := 3366 - listenerOneProtocol := "hTtPs" - - // listener two configuration - listenerTwoName := "listener-two" - listenerTwoHostname := "*.consul.io" - listenerTwoPort := 5432 - listenerTwoProtocol := "HTTP" - - // listener three configuration - listenerThreeName := "listener-three" - listenerThreePort := 8081 - listenerThreeProtocol := "tCp" - - // listener four configuration - listenerFourName := "listener-four" - listenerFourHostname := "*.consul.io" - listenerFourPort := 5433 - listenerFourProtocol := "hTTp" - - // Write gw to k8s - gwClassCfg := &v1alpha1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClassConfig", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-class-config", - }, - Spec: v1alpha1.GatewayClassConfigSpec{}, - } - gwClass := &gwv1beta1.GatewayClass{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayClass", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewayclass", - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "consul.hashicorp.com/gateway-controller", - ParametersRef: &gwv1beta1.ParametersReference{ - Group: "consul.hashicorp.com", - Kind: "GatewayClassConfig", - Name: "gateway-class-config", - }, - Description: new(string), - }, - } - gw := &gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "Gateway", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: namespace, - Annotations: make(map[string]string), - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: gwv1beta1.ObjectName(gwClass.Name), - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName(listenerOneName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerOneHostname)), - Port: gwv1beta1.PortNumber(listenerOnePort), - Protocol: gwv1beta1.ProtocolType(listenerOneProtocol), - TLS: &gwv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwv1beta1.SecretObjectReference{ - { - Kind: common.PointerTo(gwv1beta1.Kind("Secret")), - Name: gwv1beta1.ObjectName("one-cert"), - Namespace: common.PointerTo(gwv1beta1.Namespace(namespace)), - }, - }, - }, - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerTwoName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerTwoHostname)), - Port: gwv1beta1.PortNumber(listenerTwoPort), - Protocol: gwv1beta1.ProtocolType(listenerTwoProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("Same")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerThreeName), - Port: gwv1beta1.PortNumber(listenerThreePort), - Protocol: gwv1beta1.ProtocolType(listenerThreeProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("All")), - }, - }, - }, - { - Name: gwv1beta1.SectionName(listenerFourName), - Hostname: common.PointerTo(gwv1beta1.Hostname(listenerFourHostname)), - Port: gwv1beta1.PortNumber(listenerFourPort), - Protocol: gwv1beta1.ProtocolType(listenerFourProtocol), - AllowedRoutes: &gwv1beta1.AllowedRoutes{ - Namespaces: &gwv1beta1.RouteNamespaces{ - From: common.PointerTo(gwv1beta1.FromNamespaces("Selector")), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.NamespaceNameLabel: "consul", - }, - MatchExpressions: []metav1.LabelSelectorRequirement{}, - }, - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, gwClassCfg) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gwClass) - require.NoError(t, err) - - err = k8sClient.Create(ctx, gw) - require.NoError(t, err) - - return gw -} - -func createFunkyCasingFieldsHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, _ *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { - svcDefault := &v1alpha1.ServiceDefaults{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceDefaults", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "hTtp", - }, - } - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "high", - Protocol: "TCP", - Port: 8080, - }, - }, - Selector: map[string]string{"app": "Service"}, - }, - } - - serviceAccount := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - } - - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "Service"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{}, - }, - }, - } - - err := k8sClient.Create(ctx, svcDefault) - require.NoError(t, err) - - err = k8sClient.Create(ctx, svc) - require.NoError(t, err) - - err = k8sClient.Create(ctx, serviceAccount) - require.NoError(t, err) - - err = k8sClient.Create(ctx, deployment) - require.NoError(t, err) - - route := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[0].Name, - Port: &gw.Spec.Listeners[0].Port, - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: common.PointerTo(gwv1beta1.PathMatchPathPrefix), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: common.PointerTo(gwv1beta1.HeaderMatchExact), - Name: "version", - Value: "version", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "q", - }, - }, - Method: common.PointerTo(gwv1beta1.HTTPMethod("geT")), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "foo", - Value: "bax", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "arc", - Value: "reactor", - }, - }, - Remove: []string{"remove"}, - }, - }, - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.FullPathHTTPPathModifier, - ReplaceFullPath: common.PointerTo("/foobar"), - }, - }, - }, - - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: common.PointerTo("/foo"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(8080)), - }, - Weight: common.PointerTo(int32(-50)), - }, - }, - }, - }, - }, - }, - } - - err = k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createFunkyCasingFieldsTCPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *v1alpha2.TCPRoute { - route := &v1alpha2.TCPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "TCPRoute", - APIVersion: "gateway.networking.k8s.io/v1alpha2", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-route", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[2].Name, - Port: &gw.Spec.Listeners[2].Port, - }, - }, - }, - Rules: []gwv1alpha2.TCPRouteRule{ - { - BackendRefs: []gwv1beta1.BackendRef{ - { - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(25000)), - }, - Weight: common.PointerTo(int32(-50)), - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, filter *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { - svcDefault := &v1alpha1.ServiceDefaults{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceDefaults", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "high", - Protocol: "TCP", - Port: 8080, - }, - }, - Selector: map[string]string{"app": "Service"}, - }, - } - - serviceAccount := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - } - - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "Service"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{}, - }, - }, - } - - err := k8sClient.Create(ctx, svcDefault) - require.NoError(t, err) - - err = k8sClient.Create(ctx, svc) - require.NoError(t, err) - - err = k8sClient.Create(ctx, serviceAccount) - require.NoError(t, err) - - err = k8sClient.Create(ctx, deployment) - require.NoError(t, err) - - route := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[0].Name, - Port: &gw.Spec.Listeners[0].Port, - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: common.PointerTo(gwv1beta1.PathMatchType("PathPrefix")), - Value: common.PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: common.PointerTo(gwv1beta1.HeaderMatchExact), - Name: "version", - Value: "version", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "q", - }, - }, - Method: common.PointerTo(gwv1beta1.HTTPMethod("GET")), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "foo", - Value: "bax", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "arc", - Value: "reactor", - }, - }, - Remove: []string{"remove"}, - }, - }, - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.FullPathHTTPPathModifier, - ReplaceFullPath: common.PointerTo("/foobar"), - }, - }, - }, - - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: common.PointerTo("/foo"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(8080)), - }, - Weight: common.PointerTo(int32(50)), - }, - }, - }, - }, - }, - }, - } - - err = k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createRouteAuthFilter(t *testing.T, ctx context.Context, k8sClient client.WithWatch, providerName string) *v1alpha1.RouteAuthFilter { - filter := &v1alpha1.RouteAuthFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteAuthFilterKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth-filter", - }, - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: providerName, - }, - }, - }, - }, - } - err := k8sClient.Create(ctx, filter) - require.NoError(t, err) - - return filter -} - -func createJWTProvider(t *testing.T, ctx context.Context, k8sClient client.WithWatch) *v1alpha1.JWTProvider { - provider := &v1alpha1.JWTProvider{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.JWTProviderKubeKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "provider", - }, - Spec: v1alpha1.JWTProviderSpec{ - JSONWebKeySet: &v1alpha1.JSONWebKeySet{}, - Issuer: "local", - }, - } - - err := k8sClient.Create(ctx, provider) - require.NoError(t, err) - - return provider -} - -func createGWPolicy(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, providerName string) { - policy := &v1alpha1.GatewayPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayPolicy", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gw-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gw.GroupVersionKind().Group, - Kind: gw.GroupVersionKind().Kind, - Name: gw.Name, - Namespace: gw.Namespace, - SectionName: &gw.Spec.Listeners[0].Name, - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: providerName, - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: providerName, - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, policy) - require.NoError(t, err) -} diff --git a/control-plane/api-gateway/controllers/gateway_controller_test.go b/control-plane/api-gateway/controllers/gateway_controller_test.go deleted file mode 100644 index 7b21d01ff8..0000000000 --- a/control-plane/api-gateway/controllers/gateway_controller_test.go +++ /dev/null @@ -1,642 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "testing" - - mapset "github.com/deckarep/golang-set" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestTransformEndpoints(t *testing.T) { - t.Parallel() - - httpRoute := &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http", - Namespace: "test", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - Rules: []gwv1beta1.HTTPRouteRule{ - {BackendRefs: []gwv1beta1.HTTPBackendRef{ - {BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-test-namespace"}, - }}, - {BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-other-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, - }}, - {BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-system-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("system"))}, - }}, - {BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-public-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("public"))}, - }}, - {BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "http-local-path-storage-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("local-path-storage"))}, - }}}, - }, - }, - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "http-gateway"}, - {Name: "general-gateway"}, - }, - }, - }, - } - - tcpRoute := &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp", - Namespace: "test", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - Rules: []gwv1alpha2.TCPRouteRule{ - {BackendRefs: []gwv1beta1.BackendRef{ - {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-test-namespace"}}, - {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-other-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}}, - {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-system-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("system"))}}, - {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-public-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("public"))}}, - {BackendObjectReference: gwv1beta1.BackendObjectReference{Name: "tcp-local-path-storage-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("local-path-storage"))}}, - }}, - }, - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "tcp-gateway"}, - {Name: "general-gateway"}, - }, - }, - }, - } - - for name, tt := range map[string]struct { - endpoints *corev1.Endpoints - expected []reconcile.Request - allowedNamespaces []string - denyNamespaces []string - }{ - "ignore system namespace": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-system-namespace", - Namespace: metav1.NamespaceSystem, - }, - }, - allowedNamespaces: []string{"*"}, - }, - "ignore public namespace": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-public-namespace", - Namespace: metav1.NamespacePublic, - }, - }, - allowedNamespaces: []string{"*"}, - }, - "ignore local-path-storage namespace": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-local-path-storage-namespace", - Namespace: "local-path-storage", - }, - }, - allowedNamespaces: []string{"*"}, - }, - "explicit deny namespace": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-test-namespace", - Namespace: "test", - }, - }, - allowedNamespaces: []string{"*"}, - denyNamespaces: []string{"test"}, - }, - "ignore labels": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-test-namespace", - Namespace: "test", - Labels: map[string]string{ - constants.LabelServiceIgnore: "true", - }, - }, - }, - allowedNamespaces: []string{"test"}, - }, - "http same namespace wildcard allow": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-test-namespace", - Namespace: "test", - }, - }, - allowedNamespaces: []string{"*"}, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "http-gateway", Namespace: "test"}}, - {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, - }, - }, - "http same namespace explicit allow": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-test-namespace", - Namespace: "test", - }, - }, - allowedNamespaces: []string{"test"}, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "http-gateway", Namespace: "test"}}, - {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, - }, - }, - "http other namespace wildcard allow": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-other-namespace", - Namespace: "other", - }, - }, - allowedNamespaces: []string{"*"}, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "http-gateway", Namespace: "test"}}, - {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, - }, - }, - "http other namespace explicit allow": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "http-other-namespace", - Namespace: "other", - }, - }, - allowedNamespaces: []string{"other"}, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "http-gateway", Namespace: "test"}}, - {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, - }, - }, - "tcp same namespace wildcard allow": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-test-namespace", - Namespace: "test", - }, - }, - allowedNamespaces: []string{"*"}, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "tcp-gateway", Namespace: "test"}}, - {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, - }, - }, - "tcp same namespace explicit allow": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-test-namespace", - Namespace: "test", - }, - }, - allowedNamespaces: []string{"test"}, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "tcp-gateway", Namespace: "test"}}, - {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, - }, - }, - "tcp other namespace wildcard allow": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-other-namespace", - Namespace: "other", - }, - }, - allowedNamespaces: []string{"*"}, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "tcp-gateway", Namespace: "test"}}, - {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, - }, - }, - "tcp other namespace explicit allow": { - endpoints: &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tcp-other-namespace", - Namespace: "other", - }, - }, - allowedNamespaces: []string{"other"}, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "tcp-gateway", Namespace: "test"}}, - {NamespacedName: types.NamespacedName{Name: "general-gateway", Namespace: "test"}}, - }, - }, - } { - t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, clientgoscheme.AddToScheme(s)) - require.NoError(t, gwv1alpha2.Install(s)) - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - denySet := mapset.NewSet() - for _, v := range tt.denyNamespaces { - denySet.Add(v) - } - allowSet := mapset.NewSet() - for _, v := range tt.allowedNamespaces { - allowSet.Add(v) - } - - fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(httpRoute, tcpRoute)).Build() - - controller := GatewayController{ - Client: fakeClient, - denyK8sNamespacesSet: denySet, - allowK8sNamespacesSet: allowSet, - } - - fn := controller.transformEndpoints(context.Background()) - require.ElementsMatch(t, tt.expected, fn(tt.endpoints)) - }) - } -} - -func TestTransformHTTPRoute(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - route *gwv1beta1.HTTPRoute - expected []reconcile.Request - }{ - "route with parent empty namespace": { - route: &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway"}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "default"}}, - }, - }, - "route with parent with namespace": { - route: &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "other"}}, - }, - }, - "route with non gateway parent with namespace": { - route: &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway", Group: common.PointerTo(gwv1beta1.Group("group"))}, - }, - }, - }, - }, - expected: []reconcile.Request{}, - }, - "route with parent in status and no namespace": { - route: &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "default"}}, - }, - }, - "route with parent in status and namespace": { - route: &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "other"}}, - }, - }, - "route with non gateway parent in status": { - route: &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway", Group: common.PointerTo(gwv1beta1.Group("group"))}}, - }, - }, - }, - }, - expected: []reconcile.Request{}, - }, - "route parent in spec and in status": { - route: &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway-one"}, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway-one", Namespace: "default"}}, - {NamespacedName: types.NamespacedName{Name: "gateway-two", Namespace: "default"}}, - }, - }, - } { - t.Run(name, func(t *testing.T) { - controller := GatewayController{} - - fn := controller.transformHTTPRoute(context.Background()) - require.ElementsMatch(t, tt.expected, fn(tt.route)) - }) - } -} - -func TestTransformTCPRoute(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - route *gwv1alpha2.TCPRoute - expected []reconcile.Request - }{ - "route with parent empty namespace": { - route: &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway"}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "default"}}, - }, - }, - "route with parent with namespace": { - route: &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "other"}}, - }, - }, - "route with non gateway parent with namespace": { - route: &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway", Group: common.PointerTo(gwv1beta1.Group("group"))}, - }, - }, - }, - }, - expected: []reconcile.Request{}, - }, - "route with parent in status and no namespace": { - route: &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway"}}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "default"}}, - }, - }, - "route with parent in status and namespace": { - route: &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "other"}}, - }, - }, - "route with non gateway parent in status": { - route: &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway", Group: common.PointerTo(gwv1beta1.Group("group"))}}, - }, - }, - }, - }, - expected: []reconcile.Request{}, - }, - "route parent in spec and in status": { - route: &gwv1alpha2.TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }, - Spec: gwv1alpha2.TCPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - {Name: "gateway-one"}, - }, - }, - }, - Status: gwv1alpha2.TCPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - {ParentRef: gwv1beta1.ParentReference{Name: "gateway-two"}}, - }, - }, - }, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway-one", Namespace: "default"}}, - {NamespacedName: types.NamespacedName{Name: "gateway-two", Namespace: "default"}}, - }, - }, - } { - t.Run(name, func(t *testing.T) { - controller := GatewayController{} - - fn := controller.transformTCPRoute(context.Background()) - require.ElementsMatch(t, tt.expected, fn(tt.route)) - }) - } -} - -func TestTransformSecret(t *testing.T) { - t.Parallel() - - gateway := &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Namespace: "test", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - {Name: "terminate", TLS: &gwv1beta1.GatewayTLSConfig{ - Mode: common.PointerTo(gwv1beta1.TLSModeTerminate), - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "secret-no-namespace"}, - {Name: "secret-namespace", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, - }, - }}, - {Name: "passthrough", TLS: &gwv1beta1.GatewayTLSConfig{ - Mode: common.PointerTo(gwv1beta1.TLSModePassthrough), - CertificateRefs: []gwv1beta1.SecretObjectReference{ - {Name: "passthrough", Namespace: common.PointerTo(gwv1beta1.Namespace("other"))}, - }, - }}, - }, - }, - } - - for name, tt := range map[string]struct { - secret *corev1.Secret - expected []reconcile.Request - }{ - "explicit namespace from parent": { - secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-namespace", Namespace: "other"}, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "test"}}, - }, - }, - "implicit namespace from parent": { - secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-no-namespace", Namespace: "test"}, - }, - expected: []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: "gateway", Namespace: "test"}}, - }, - }, - "mismatched namespace": { - secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-no-namespace", Namespace: "other"}, - }, - }, - "mismatched names": { - secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test"}, - }, - }, - "passthrough ignored": { - secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "passthrough", Namespace: "other"}, - }, - }, - } { - t.Run(name, func(t *testing.T) { - tt := tt - - t.Parallel() - - s := runtime.NewScheme() - require.NoError(t, clientgoscheme.AddToScheme(s)) - require.NoError(t, gwv1alpha2.Install(s)) - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s)).WithRuntimeObjects(gateway).Build() - - controller := GatewayController{ - Client: fakeClient, - } - - fn := controller.transformSecret(context.Background()) - require.ElementsMatch(t, tt.expected, fn(tt.secret)) - }) - } -} diff --git a/control-plane/api-gateway/controllers/gatewayclass_controller.go b/control-plane/api-gateway/controllers/gatewayclass_controller.go deleted file mode 100644 index 3bde2d6ab1..0000000000 --- a/control-plane/api-gateway/controllers/gatewayclass_controller.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "fmt" - "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -const ( - gatewayClassFinalizer = "gateway-exists-finalizer.consul.hashicorp.com" - - // GatewayClass status fields. - accepted = "Accepted" - invalidParameters = "InvalidParameters" -) - -// GatewayClassController reconciles a GatewayClass object. -// The GatewayClass is responsible for defining the behavior of API gateways -// which reference the given class. -type GatewayClassController struct { - ControllerName string - Log logr.Logger - - client.Client -} - -// Reconcile handles the reconciliation loop for GatewayClass objects. -func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("gatewayClass", req.NamespacedName.Name) - log.V(1).Info("Reconciling GatewayClass") - - gc := &gwv1beta1.GatewayClass{} - - err := r.Client.Get(ctx, req.NamespacedName, gc) - if err != nil { - if k8serrors.IsNotFound(err) { - return ctrl.Result{}, nil - } - log.Error(err, "unable to get GatewayClass") - return ctrl.Result{}, err - } - - if string(gc.Spec.ControllerName) != r.ControllerName { - // This GatewayClass is not for this controller. - _, err := RemoveFinalizer(ctx, r.Client, gc, gatewayClassFinalizer) - if err != nil { - log.Error(err, "unable to remove finalizer") - } - - return ctrl.Result{}, err - } - - if !gc.ObjectMeta.DeletionTimestamp.IsZero() { - // We have a deletion request. Ensure we are not in use. - used, err := r.isGatewayClassInUse(ctx, gc) - if err != nil { - log.Error(err, "unable to check if GatewayClass is in use") - return ctrl.Result{}, err - } - if used { - log.Info("GatewayClass is in use, cannot delete") - return ctrl.Result{}, nil - } - // Remove our finalizer. - if _, err := RemoveFinalizer(ctx, r.Client, gc, gatewayClassFinalizer); err != nil { - if k8serrors.IsConflict(err) { - log.V(1).Info("error removing finalizer for gatewayClass, will try to re-reconcile") - - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "unable to remove finalizer") - return ctrl.Result{}, err - } - return ctrl.Result{}, nil - } - - // We are creating or updating the GatewayClass. - didUpdate, err := EnsureFinalizer(ctx, r.Client, gc, gatewayClassFinalizer) - if err != nil { - if k8serrors.IsConflict(err) { - log.V(1).Info("error adding finalizer for gatewayClass, will try to re-reconcile") - - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "unable to add finalizer") - return ctrl.Result{}, err - } - if didUpdate { - // We updated the GatewayClass, requeue to avoid another update. - return ctrl.Result{}, nil - } - - didUpdate, err = r.validateParametersRef(ctx, gc, log) - if didUpdate { - if err := r.Client.Status().Update(ctx, gc); err != nil { - if k8serrors.IsConflict(err) { - log.V(1).Info("error updating status for gatewayClass, will try to re-reconcile") - - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "unable to update status for GatewayClass") - return ctrl.Result{}, err - } - return ctrl.Result{}, nil - } - if err != nil { - log.Error(err, "unable to validate ParametersRef") - } - - return ctrl.Result{}, err -} - -// SetupWithManager registers the controller with the given manager. -func (r *GatewayClassController) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&gwv1beta1.GatewayClass{}). - // Watch for changes to GatewayClassConfig objects. - Watches(source.NewKindWithCache(&v1alpha1.GatewayClassConfig{}, mgr.GetCache()), r.gatewayClassConfigFieldIndexEventHandler(ctx)). - // Watch for changes to Gateway objects that reference this GatewayClass. - Watches(source.NewKindWithCache(&gwv1beta1.Gateway{}, mgr.GetCache()), r.gatewayFieldIndexEventHandler(ctx)). - Complete(r) -} - -// isGatewayClassInUse returns true if the given GatewayClass is referenced by any Gateway objects. -func (r *GatewayClassController) isGatewayClassInUse(ctx context.Context, gc *gwv1beta1.GatewayClass) (bool, error) { - list := &gwv1beta1.GatewayList{} - if err := r.Client.List(ctx, list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Gateway_GatewayClassIndex, gc.Name), - }); err != nil { - return false, err - } - - return len(list.Items) != 0, nil -} - -// validateParametersRef validates the ParametersRef field of the given GatewayClass -// if it is set, ensuring that the referenced object is a GatewayClassConfig that exists. -func (r *GatewayClassController) validateParametersRef(ctx context.Context, gc *gwv1beta1.GatewayClass, log logr.Logger) (didUpdate bool, err error) { - parametersRef := gc.Spec.ParametersRef - if parametersRef != nil { - if parametersRef.Kind != v1alpha1.GatewayClassConfigKind { - didUpdate = r.setCondition(gc, metav1.Condition{ - Type: accepted, - Status: metav1.ConditionFalse, - Reason: invalidParameters, - Message: fmt.Sprintf("Incorrect type for parametersRef. Expected GatewayClassConfig, got %q.", parametersRef.Kind), - }) - return didUpdate, nil - } - - err = r.Client.Get(ctx, types.NamespacedName{Name: parametersRef.Name}, &v1alpha1.GatewayClassConfig{}) - if k8serrors.IsNotFound(err) { - didUpdate := r.setCondition(gc, metav1.Condition{ - Type: accepted, - Status: metav1.ConditionFalse, - Reason: invalidParameters, - Message: fmt.Sprintf("GatewayClassConfig not found %q.", parametersRef.Name), - }) - return didUpdate, nil - } - if err != nil { - log.Error(err, "unable to fetch GatewayClassConfig") - return false, err - } - } - - didUpdate = r.setCondition(gc, metav1.Condition{ - Type: accepted, - Status: metav1.ConditionTrue, - Reason: accepted, - Message: "GatewayClass Accepted", - }) - - return didUpdate, err -} - -// setCondition sets the given condition on the given GatewayClass. -func (r *GatewayClassController) setCondition(gc *gwv1beta1.GatewayClass, condition metav1.Condition) (didUpdate bool) { - condition.LastTransitionTime = metav1.Now() - condition.ObservedGeneration = gc.GetGeneration() - - // Set the condition if it already exists. - for i, c := range gc.Status.Conditions { - if c.Type == condition.Type { - // The condition already exists and is up to date. - if equalConditions(condition, c) { - return false - } - - gc.Status.Conditions[i] = condition - - return true - } - } - - // Append the condition if it does not exist. - gc.Status.Conditions = append(gc.Status.Conditions, condition) - - return true -} - -// gatewayClassConfigFieldIndexEventHandler returns an EventHandler that will enqueue -// reconcile.Requests for GatewayClass objects that reference the GatewayClassConfig -// object that triggered the event. -func (r *GatewayClassController) gatewayClassConfigFieldIndexEventHandler(ctx context.Context) handler.EventHandler { - return handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { - requests := []reconcile.Request{} - - // Get all GatewayClass objects from the field index of the GatewayClassConfig which triggered the event. - var gcList gwv1beta1.GatewayClassList - err := r.Client.List(ctx, &gcList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(GatewayClass_GatewayClassConfigIndex, o.GetName()), - }) - if err != nil { - r.Log.Error(err, "unable to list gateway classes") - } - - // Create a reconcile request for each GatewayClass. - for _, gc := range gcList.Items { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: gc.Name, - }, - }) - } - - return requests - }) -} - -// gatewayFieldIndexEventHandler returns an EventHandler that will enqueue -// reconcile.Requests for GatewayClass objects from Gateways which reference the GatewayClass -// when those Gateways are updated. -func (r *GatewayClassController) gatewayFieldIndexEventHandler(ctx context.Context) handler.EventHandler { - return handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { - // Get the Gateway object that triggered the event. - g := o.(*gwv1beta1.Gateway) - - // Return a slice with the single reconcile.Request for the GatewayClass - // that the Gateway references. - return []reconcile.Request{ - { - NamespacedName: types.NamespacedName{ - Name: string(g.Spec.GatewayClassName), - }, - }, - } - }) -} - -func equalConditions(a, b metav1.Condition) bool { - return a.Type == b.Type && - a.Status == b.Status && - a.Reason == b.Reason && - a.Message == b.Message && - a.ObservedGeneration == b.ObservedGeneration -} diff --git a/control-plane/api-gateway/controllers/gatewayclass_controller_test.go b/control-plane/api-gateway/controllers/gatewayclass_controller_test.go deleted file mode 100644 index 0eeaf4c1de..0000000000 --- a/control-plane/api-gateway/controllers/gatewayclass_controller_test.go +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "fmt" - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - "sigs.k8s.io/gateway-api/apis/v1beta1" - 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" -) - -func TestGatewayClassReconciler(t *testing.T) { - t.Parallel() - - namespace := "" // GatewayClass is cluster-scoped. - name := "test-gatewayclass" - - req := ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: namespace, - Name: name, - }, - } - - deletionTimestamp := metav1.Now() - - cases := map[string]struct { - gatewayClass *gwv1beta1.GatewayClass - k8sObjects []runtime.Object - expectedResult ctrl.Result - expectedError error - expectedFinalizers []string - expectedIsDeleted bool - expectedConditions []metav1.Condition - }{ - "successful reconcile with no change": { - gatewayClass: &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{gatewayClassFinalizer}, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: common.GatewayClassControllerName, - }, - }, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedFinalizers: []string{gatewayClassFinalizer}, - expectedIsDeleted: false, - expectedConditions: []metav1.Condition{ - { - Type: accepted, - Status: metav1.ConditionTrue, - Reason: accepted, - Message: "GatewayClass Accepted", - }, - }, - }, - "successful reconcile that adds finalizer": { - gatewayClass: &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{}, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: common.GatewayClassControllerName, - }, - }, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedFinalizers: []string{gatewayClassFinalizer}, - expectedConditions: []metav1.Condition{}, - }, - "attempt to reconcile a GatewayClass with a different controller name": { - gatewayClass: &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{}, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "foo", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedConditions: []metav1.Condition{}, - }, - "attempt to reconcile a GatewayClass with a different controller name removing our finalizer": { - gatewayClass: &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{gatewayClassFinalizer}, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: "foo", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedConditions: []metav1.Condition{}, - }, - "attempt to reconcile a GatewayClass with an incorrect parametersRef type": { - gatewayClass: &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{gatewayClassFinalizer}, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: common.GatewayClassControllerName, - ParametersRef: &gwv1beta1.ParametersReference{ - Kind: "some-nonsense", - }, - }, - }, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedFinalizers: []string{gatewayClassFinalizer}, - expectedConditions: []metav1.Condition{ - { - Type: accepted, - Status: metav1.ConditionFalse, - Reason: invalidParameters, - Message: fmt.Sprintf("Incorrect type for parametersRef. Expected GatewayClassConfig, got %q.", "some-nonsense"), - }, - }, - }, - "attempt to reconcile a GatewayClass with a GatewayClassConfig that does not exist": { - gatewayClass: &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{gatewayClassFinalizer}, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: common.GatewayClassControllerName, - ParametersRef: &gwv1beta1.ParametersReference{ - Kind: v1alpha1.GatewayClassConfigKind, - Name: "does-not-exist", - }, - }, - }, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedFinalizers: []string{gatewayClassFinalizer}, - expectedConditions: []metav1.Condition{ - { - Type: accepted, - Status: metav1.ConditionFalse, - Reason: invalidParameters, - Message: fmt.Sprintf("GatewayClassConfig not found %q.", "does-not-exist"), - }, - }, - }, - "attempt to reconcile a non-existent object": { - k8sObjects: []runtime.Object{}, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedConditions: []metav1.Condition{}, - }, - "attempt to remove a GatewayClass that is not in use": { - gatewayClass: &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{ - gatewayClassFinalizer, - }, - DeletionTimestamp: &deletionTimestamp, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: common.GatewayClassControllerName, - }, - }, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedFinalizers: []string{}, - expectedIsDeleted: true, - }, - "attempt to remove a GatewayClass that is in use": { - gatewayClass: &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - Finalizers: []string{ - gatewayClassFinalizer, - }, - DeletionTimestamp: &deletionTimestamp, - }, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: common.GatewayClassControllerName, - }, - }, - k8sObjects: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test-gateway", - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: v1beta1.ObjectName(name), - }, - }, - }, - expectedResult: ctrl.Result{}, - expectedError: nil, - expectedFinalizers: []string{gatewayClassFinalizer}, - }, - // */ - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, clientgoscheme.AddToScheme(s)) - require.NoError(t, gwv1alpha2.Install(s)) - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - objs := tc.k8sObjects - if tc.gatewayClass != nil { - objs = append(objs, tc.gatewayClass) - } - - fakeClient := registerFieldIndexersForTest(fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...)).Build() - - r := &GatewayClassController{ - Client: fakeClient, - ControllerName: common.GatewayClassControllerName, - Log: logrtest.New(t), - } - result, err := r.Reconcile(context.Background(), req) - - require.Equal(t, tc.expectedResult, result) - require.Equal(t, tc.expectedError, err) - - // Check the GatewayClass after reconciliation. - gc := &gwv1beta1.GatewayClass{} - err = r.Client.Get(context.Background(), req.NamespacedName, gc) - - if tc.gatewayClass == nil || tc.expectedIsDeleted { - // There shouldn't be a GatewayClass to check. - require.True(t, apierrors.IsNotFound(err)) - return - } - - require.NoError(t, client.IgnoreNotFound(err)) - require.Equal(t, tc.expectedFinalizers, gc.ObjectMeta.Finalizers) - require.Equal(t, len(tc.expectedConditions), len(gc.Status.Conditions), "expected %+v, got %+v", tc.expectedConditions, gc.Status.Conditions) - for i, expectedCondition := range tc.expectedConditions { - require.True(t, equalConditions(expectedCondition, gc.Status.Conditions[i]), "expected %+v, got %+v", expectedCondition, gc.Status.Conditions[i]) - } - }) - } -} diff --git a/control-plane/api-gateway/controllers/gatewayclassconfig_controller.go b/control-plane/api-gateway/controllers/gatewayclassconfig_controller.go deleted file mode 100644 index 878d6549f9..0000000000 --- a/control-plane/api-gateway/controllers/gatewayclassconfig_controller.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "time" - - "github.com/go-logr/logr" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -const ( - gatewayClassConfigFinalizer = "gateway-class-exists-finalizer.consul.hashicorp.com" -) - -// The GatewayClassConfigController manages the state of GatewayClassConfigs. -type GatewayClassConfigController struct { - client.Client - - Log logr.Logger -} - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile -func (r *GatewayClassConfigController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("gatewayClassConfig", req.NamespacedName.Name) - log.V(1).Info("Reconciling GatewayClassConfig ") - - gcc := &v1alpha1.GatewayClassConfig{} - if err := r.Client.Get(ctx, req.NamespacedName, gcc); err != nil { - if k8serrors.IsNotFound(err) { - return ctrl.Result{}, nil - } - log.Error(err, "failed to get gateway class config") - return ctrl.Result{}, err - } - - if !gcc.ObjectMeta.DeletionTimestamp.IsZero() { - // We have a deletion, ensure we're not in use. - used, err := gatewayClassConfigInUse(ctx, r.Client, gcc) - if err != nil { - log.Error(err, "failed to check if the gateway class config is still in use") - return ctrl.Result{}, err - } - if used { - log.Info("gateway class config still in use") - // Requeue as to not block the reconciliation loop. - return ctrl.Result{RequeueAfter: 10 * time.Second}, nil - } - // gcc is no longer in use. - if _, err := RemoveFinalizer(ctx, r.Client, gcc, gatewayClassConfigFinalizer); err != nil { - if k8serrors.IsConflict(err) { - log.V(1).Info("error removing gateway class config finalizer, will try to re-reconcile") - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "error removing gateway class config finalizer") - return ctrl.Result{}, err - } - return ctrl.Result{}, nil - } - - if _, err := EnsureFinalizer(ctx, r.Client, gcc, gatewayClassConfigFinalizer); err != nil { - if k8serrors.IsConflict(err) { - log.V(1).Info("error adding gateway class config finalizer, will try to re-reconcile") - - return ctrl.Result{Requeue: true}, nil - } - log.Error(err, "error adding gateway class config finalizer") - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -// gatewayClassUsesConfig determines whether a given GatewayClass references a -// given GatewayClassConfig. Since these resources are scoped to the cluster, -// namespace is not considered. -func gatewayClassUsesConfig(gc gwv1beta1.GatewayClass, gcc *v1alpha1.GatewayClassConfig) bool { - parameterRef := gc.Spec.ParametersRef - return parameterRef != nil && - string(parameterRef.Group) == v1alpha1.ConsulHashicorpGroup && - parameterRef.Kind == v1alpha1.GatewayClassConfigKind && - parameterRef.Name == gcc.Name -} - -// GatewayClassConfigInUse determines whether any GatewayClass in the cluster -// references the provided GatewayClassConfig. -func gatewayClassConfigInUse(ctx context.Context, k8sClient client.Client, gcc *v1alpha1.GatewayClassConfig) (bool, error) { - list := &gwv1beta1.GatewayClassList{} - if err := k8sClient.List(ctx, list); err != nil { - return false, err - } - - for _, gc := range list.Items { - if gatewayClassUsesConfig(gc, gcc) { - return true, nil - } - } - - return false, nil -} - -func (r *GatewayClassConfigController) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&v1alpha1.GatewayClassConfig{}). - // Watch for changes to GatewayClass objects associated with this config for purposes of finalizer removal. - Watches(source.NewKindWithCache(&gwv1beta1.GatewayClass{}, mgr.GetCache()), r.transformGatewayClassToGatewayClassConfig(ctx)). - Complete(r) -} - -func (r *GatewayClassConfigController) transformGatewayClassToGatewayClassConfig(ctx context.Context) handler.EventHandler { - return handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { - gc := o.(*gwv1beta1.GatewayClass) - - pr := gc.Spec.ParametersRef - if pr != nil && pr.Kind == v1alpha1.GatewayClassConfigKind { - return []reconcile.Request{{ - NamespacedName: types.NamespacedName{ - Name: pr.Name, - }, - }} - } - - return nil - }) -} diff --git a/control-plane/api-gateway/controllers/gatewayclassconfig_controller_test.go b/control-plane/api-gateway/controllers/gatewayclassconfig_controller_test.go deleted file mode 100644 index 40023d498f..0000000000 --- a/control-plane/api-gateway/controllers/gatewayclassconfig_controller_test.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - "testing" - "time" - - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/stretchr/testify/require" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestGatewayClassConfigReconcile(t *testing.T) { - t.Parallel() - deletionTimestamp := meta.Now() - cases := []struct { - name string - k8sObjects func() []runtime.Object - expErr string - requeue bool - requeueAfter time.Duration - }{ - { - name: "Successfully reconcile without any changes", - k8sObjects: func() []runtime.Object { - gatewayClassConfig := v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-api-gateway", - }, - } - return []runtime.Object{&gatewayClassConfig} - }, - }, - { - name: "GatewayClassConfig Does Not Exist", - k8sObjects: func() []runtime.Object { - return []runtime.Object{} - }, - }, - { - name: "Remove not-in-use GatewayClassConfig", - k8sObjects: func() []runtime.Object { - gatewayClassConfig := v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-api-gateway", - DeletionTimestamp: &deletionTimestamp, - }, - } - return []runtime.Object{&gatewayClassConfig} - }, - }, - { - name: "Try to remove in-use GatewayClassConfig", - k8sObjects: func() []runtime.Object { - gatewayClassConfig := v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-api-gateway", - DeletionTimestamp: &deletionTimestamp, - }, - } - gatewayClass := gwv1beta1.GatewayClass{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-api-gateway-class", - }, - Spec: gwv1beta1.GatewayClassSpec{ - ParametersRef: &gwv1beta1.ParametersReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: v1alpha1.GatewayClassConfigKind, - Name: gatewayClassConfig.ObjectMeta.Name, - Namespace: nil, - }, - }, - Status: gwv1beta1.GatewayClassStatus{}, - } - return []runtime.Object{&gatewayClassConfig, &gatewayClass} - }, - requeueAfter: time.Second * 10, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, clientgoscheme.AddToScheme(s)) - require.NoError(t, gwv1alpha2.Install(s)) - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.k8sObjects()...).Build() - - // Create the gateway class config controller. - gcc := &GatewayClassConfigController{ - Client: fakeClient, - Log: logrtest.New(t), - } - - resp, err := gcc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "", - Name: "consul-api-gateway", - }, - }) - if tt.expErr != "" { - require.EqualError(t, err, tt.expErr) - } else { - require.NoError(t, err) - } - require.Equal(t, tt.requeue, resp.Requeue) - }) - } -} diff --git a/control-plane/api-gateway/controllers/index.go b/control-plane/api-gateway/controllers/index.go deleted file mode 100644 index 46c1f98459..0000000000 --- a/control-plane/api-gateway/controllers/index.go +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import ( - "context" - - "k8s.io/apimachinery/pkg/types" - - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - 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" -) - -const ( - // Naming convention: TARGET_REFERENCE. - GatewayClass_GatewayClassConfigIndex = "__gatewayclass_referencing_gatewayclassconfig" - GatewayClass_ControllerNameIndex = "__gatewayclass_controller_name" - - Gateway_GatewayClassIndex = "__gateway_referencing_gatewayclass" - - HTTPRoute_GatewayIndex = "__httproute_referencing_gateway" - HTTPRoute_ServiceIndex = "__httproute_referencing_service" - HTTPRoute_MeshServiceIndex = "__httproute_referencing_mesh_service" - HTTPRoute_RouteRetryFilterIndex = "__httproute_referencing_retryfilter" - HTTPRoute_RouteTimeoutFilterIndex = "__httproute_referencing_timeoutfilter" - HTTPRoute_RouteAuthFilterIndex = "__httproute_referencing_routeauthfilter" - - TCPRoute_GatewayIndex = "__tcproute_referencing_gateway" - TCPRoute_ServiceIndex = "__tcproute_referencing_service" - TCPRoute_MeshServiceIndex = "__tcproute_referencing_mesh_service" - - MeshService_PeerIndex = "__meshservice_referencing_peer" - Secret_GatewayIndex = "__secret_referencing_gateway" - Gatewaypolicy_GatewayIndex = "__gatewaypolicy_referencing_gateway" -) - -// RegisterFieldIndexes registers all of the field indexes for the API gateway controllers. -// These indexes are similar to indexes used in databases to speed up queries. -// They allow us to quickly find objects based on a field value. -func RegisterFieldIndexes(ctx context.Context, mgr ctrl.Manager) error { - for _, index := range indexes { - if err := mgr.GetFieldIndexer().IndexField(ctx, index.target, index.name, index.indexerFunc); err != nil { - return err - } - } - return nil -} - -type index struct { - name string - target client.Object - indexerFunc client.IndexerFunc -} - -var indexes = []index{ - { - name: GatewayClass_GatewayClassConfigIndex, - target: &gwv1beta1.GatewayClass{}, - indexerFunc: gatewayClassConfigForGatewayClass, - }, - { - name: GatewayClass_ControllerNameIndex, - target: &gwv1beta1.GatewayClass{}, - indexerFunc: gatewayClassControllerName, - }, - { - name: Gateway_GatewayClassIndex, - target: &gwv1beta1.Gateway{}, - indexerFunc: gatewayClassForGateway, - }, - { - name: Secret_GatewayIndex, - target: &gwv1beta1.Gateway{}, - indexerFunc: gatewayForSecret, - }, - { - name: HTTPRoute_GatewayIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: gatewaysForHTTPRoute, - }, - { - name: HTTPRoute_ServiceIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: servicesForHTTPRoute, - }, - { - name: HTTPRoute_MeshServiceIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: meshServicesForHTTPRoute, - }, - { - name: TCPRoute_GatewayIndex, - target: &gwv1alpha2.TCPRoute{}, - indexerFunc: gatewaysForTCPRoute, - }, - { - name: TCPRoute_ServiceIndex, - target: &gwv1alpha2.TCPRoute{}, - indexerFunc: servicesForTCPRoute, - }, - { - name: TCPRoute_MeshServiceIndex, - target: &gwv1alpha2.TCPRoute{}, - indexerFunc: meshServicesForTCPRoute, - }, - { - name: MeshService_PeerIndex, - target: &v1alpha1.MeshService{}, - indexerFunc: peersForMeshService, - }, - { - name: HTTPRoute_RouteRetryFilterIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: filtersForHTTPRoute, - }, - { - name: HTTPRoute_RouteTimeoutFilterIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: filtersForHTTPRoute, - }, - { - name: HTTPRoute_RouteAuthFilterIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: filtersForHTTPRoute, - }, - { - name: Gatewaypolicy_GatewayIndex, - target: &v1alpha1.GatewayPolicy{}, - indexerFunc: gatewayForGatewayPolicy, - }, -} - -// gatewayClassConfigForGatewayClass creates an index of every GatewayClassConfig referenced by a GatewayClass. -func gatewayClassConfigForGatewayClass(o client.Object) []string { - gc := o.(*gwv1beta1.GatewayClass) - - pr := gc.Spec.ParametersRef - if pr != nil && pr.Kind == v1alpha1.GatewayClassConfigKind { - return []string{pr.Name} - } - - return []string{} -} - -func gatewayClassControllerName(o client.Object) []string { - gc := o.(*gwv1beta1.GatewayClass) - - if gc.Spec.ControllerName != "" { - return []string{string(gc.Spec.ControllerName)} - } - - return []string{} -} - -// gatewayClassForGateway creates an index of every GatewayClass referenced by a Gateway. -func gatewayClassForGateway(o client.Object) []string { - g := o.(*gwv1beta1.Gateway) - return []string{string(g.Spec.GatewayClassName)} -} - -func peersForMeshService(o client.Object) []string { - m := o.(*v1alpha1.MeshService) - if m.Spec.Peer != nil { - return []string{string(*m.Spec.Peer)} - } - return nil -} - -func gatewayForSecret(o client.Object) []string { - gateway := o.(*gwv1beta1.Gateway) - var secretReferences []string - for _, listener := range gateway.Spec.Listeners { - if listener.TLS == nil || *listener.TLS.Mode != gwv1beta1.TLSModeTerminate { - continue - } - for _, cert := range listener.TLS.CertificateRefs { - if common.NilOrEqual(cert.Group, "") && common.NilOrEqual(cert.Kind, "Secret") { - // If an explicit Secret namespace is not provided, use the Gateway namespace to lookup the provided Secret Name. - secretReferences = append(secretReferences, common.IndexedNamespacedNameWithDefault(cert.Name, cert.Namespace, gateway.Namespace).String()) - } - } - } - return secretReferences -} - -func gatewaysForHTTPRoute(o client.Object) []string { - route := o.(*gwv1beta1.HTTPRoute) - statusRefs := common.ConvertSliceFunc(route.Status.Parents, func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { - return parentStatus.ParentRef - }) - return gatewaysForRoute(route.Namespace, route.Spec.ParentRefs, statusRefs) -} - -func gatewaysForTCPRoute(o client.Object) []string { - route := o.(*gwv1alpha2.TCPRoute) - statusRefs := common.ConvertSliceFunc(route.Status.Parents, func(parentStatus gwv1beta1.RouteParentStatus) gwv1beta1.ParentReference { - return parentStatus.ParentRef - }) - return gatewaysForRoute(route.Namespace, route.Spec.ParentRefs, statusRefs) -} - -func servicesForHTTPRoute(o client.Object) []string { - route := o.(*gwv1beta1.HTTPRoute) - refs := []string{} - for _, rule := range route.Spec.Rules { - BACKEND_LOOP: - for _, ref := range rule.BackendRefs { - if common.NilOrEqual(ref.Group, "") && common.NilOrEqual(ref.Kind, "Service") { - backendRef := common.IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() - for _, member := range refs { - if member == backendRef { - continue BACKEND_LOOP - } - } - refs = append(refs, backendRef) - } - } - } - return refs -} - -func meshServicesForHTTPRoute(o client.Object) []string { - route := o.(*gwv1beta1.HTTPRoute) - refs := []string{} - for _, rule := range route.Spec.Rules { - BACKEND_LOOP: - for _, ref := range rule.BackendRefs { - if common.DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && common.DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) { - backendRef := common.IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() - for _, member := range refs { - if member == backendRef { - continue BACKEND_LOOP - } - } - refs = append(refs, backendRef) - } - } - } - return refs -} - -func servicesForTCPRoute(o client.Object) []string { - route := o.(*gwv1alpha2.TCPRoute) - refs := []string{} - for _, rule := range route.Spec.Rules { - BACKEND_LOOP: - for _, ref := range rule.BackendRefs { - if common.NilOrEqual(ref.Group, "") && common.NilOrEqual(ref.Kind, common.KindService) { - backendRef := common.IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() - for _, member := range refs { - if member == backendRef { - continue BACKEND_LOOP - } - } - refs = append(refs, backendRef) - } - } - } - return refs -} - -func meshServicesForTCPRoute(o client.Object) []string { - route := o.(*gwv1alpha2.TCPRoute) - refs := []string{} - for _, rule := range route.Spec.Rules { - BACKEND_LOOP: - for _, ref := range rule.BackendRefs { - if common.DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && common.DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) { - backendRef := common.IndexedNamespacedNameWithDefault(ref.Name, ref.Namespace, route.Namespace).String() - for _, member := range refs { - if member == backendRef { - continue BACKEND_LOOP - } - } - refs = append(refs, backendRef) - } - } - } - return refs -} - -func gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference, statusRefs []gwv1beta1.ParentReference) []string { - var references []string - for _, parent := range refs { - if common.NilOrEqual(parent.Group, common.BetaGroup) && common.NilOrEqual(parent.Kind, common.KindGateway) { - // If an explicit Gateway namespace is not provided, use the Route namespace to lookup the provided Gateway Namespace. - references = append(references, common.IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace).String()) - } - } - for _, parent := range statusRefs { - if common.NilOrEqual(parent.Group, common.BetaGroup) && common.NilOrEqual(parent.Kind, common.KindGateway) { - // If an explicit Gateway namespace is not provided, use the Route namespace to lookup the provided Gateway Namespace. - references = append(references, common.IndexedNamespacedNameWithDefault(parent.Name, parent.Namespace, namespace).String()) - } - } - return references -} - -func filtersForHTTPRoute(o client.Object) []string { - route := o.(*gwv1beta1.HTTPRoute) - filters := []string{} - var nilString *string - - for _, rule := range route.Spec.Rules { - FILTERS_LOOP: - for _, filter := range rule.Filters { - if common.FilterIsExternalFilter(filter) { - // TODO this seems like its type agnostic, so this might just work without having to make - // multiple index functions per custom filter type? - - // index external filters - filter := common.IndexedNamespacedNameWithDefault(string(filter.ExtensionRef.Name), nilString, route.Namespace).String() - for _, member := range filters { - if member == filter { - continue FILTERS_LOOP - } - } - filters = append(filters, filter) - } - } - - // same thing but over the backend refs - BACKEND_LOOP: - for _, ref := range rule.BackendRefs { - for _, filter := range ref.Filters { - if common.FilterIsExternalFilter(filter) { - filter := common.IndexedNamespacedNameWithDefault(string(filter.ExtensionRef.Name), nilString, route.Namespace).String() - for _, member := range filters { - if member == filter { - continue BACKEND_LOOP - } - } - filters = append(filters, filter) - } - } - } - } - return filters -} - -func gatewayForGatewayPolicy(o client.Object) []string { - gatewayPolicy := o.(*v1alpha1.GatewayPolicy) - - targetGateway := gatewayPolicy.Spec.TargetRef - if targetGateway.Group == gwv1beta1.GroupVersion.String() && targetGateway.Kind == common.KindGateway { - policyNamespace := gatewayPolicy.Namespace - if policyNamespace == "" { - policyNamespace = "default" - } - targetNS := targetGateway.Namespace - if targetNS == "" { - targetNS = policyNamespace - } - - namespacedName := types.NamespacedName{Name: targetGateway.Name, Namespace: targetNS} - return []string{namespacedName.String()} - } - - return []string{} -} diff --git a/control-plane/api-gateway/controllers/index_test.go b/control-plane/api-gateway/controllers/index_test.go deleted file mode 100644 index 5655a3c3da..0000000000 --- a/control-plane/api-gateway/controllers/index_test.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllers - -import "sigs.k8s.io/controller-runtime/pkg/client/fake" - -func registerFieldIndexersForTest(clientBuilder *fake.ClientBuilder) *fake.ClientBuilder { - for _, index := range indexes { - clientBuilder = clientBuilder.WithIndex(index.target, index.name, index.indexerFunc) - } - return clientBuilder -} diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go deleted file mode 100644 index 090464e9c8..0000000000 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "fmt" - "strconv" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - allCapabilities = "ALL" - netBindCapability = "NET_BIND_SERVICE" - consulDataplaneDNSBindHost = "127.0.0.1" - consulDataplaneDNSBindPort = 8600 - defaultPrometheusScrapePath = "/metrics" - defaultEnvoyProxyConcurrency = 1 - volumeName = "consul-connect-inject-data" -) - -func consulDataplaneContainer(config common.HelmConfig, gcc v1alpha1.GatewayClassConfig, name, namespace string) (corev1.Container, error) { - // Extract the service account token's volume mount. - var ( - err error - bearerTokenFile string - ) - - if config.AuthMethod != "" { - bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" - } - - args, err := getDataplaneArgs(namespace, config, bearerTokenFile, name) - if err != nil { - return corev1.Container{}, err - } - - probe := &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(constants.ProxyDefaultHealthPort), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, - } - - container := corev1.Container{ - Name: name, - Image: config.ImageDataplane, - - // We need to set tmp dir to an ephemeral volume that we're mounting so that - // consul-dataplane can write files to it. Otherwise, it wouldn't be able to - // because we set file system to be read-only. - Env: []corev1.EnvVar{ - { - Name: "TMPDIR", - Value: "/consul/connect-inject", - }, - { - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "DP_SERVICE_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/connect-inject", - }, - }, - Args: args, - ReadinessProbe: probe, - } - - // Configure the Readiness Address for the proxy's health check to be the Pod IP. - container.Env = append(container.Env, corev1.EnvVar{ - Name: "DP_ENVOY_READY_BIND_ADDRESS", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, - }, - }) - // Configure the port on which the readiness probe will query the proxy for its health. - container.Ports = append(container.Ports, corev1.ContainerPort{ - Name: "proxy-health", - ContainerPort: int32(constants.ProxyDefaultHealthPort), - }) - // Configure the resource requests and limits for the proxy if they are set. - if gcc.Spec.DeploymentSpec.Resources != nil { - container.Resources = *gcc.Spec.DeploymentSpec.Resources - } - - // If running in vanilla K8s, run as root to allow binding to privileged ports; - // otherwise, allow the user to be assigned by OpenShift. - container.SecurityContext = &corev1.SecurityContext{ - ReadOnlyRootFilesystem: pointer.Bool(true), - // Drop any Linux capabilities you'd get as root other than NET_BIND_SERVICE. - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{netBindCapability}, - Drop: []corev1.Capability{allCapabilities}, - }, - } - if !config.EnableOpenShift { - container.SecurityContext.RunAsUser = pointer.Int64(0) - } - - return container, nil -} - -func getDataplaneArgs(namespace string, config common.HelmConfig, bearerTokenFile string, name string) ([]string, error) { - proxyIDFileName := "/consul/connect-inject/proxyid" - envoyConcurrency := defaultEnvoyProxyConcurrency - - args := []string{ - "-addresses", config.ConsulConfig.Address, - "-grpc-port=" + strconv.Itoa(config.ConsulConfig.GRPCPort), - "-proxy-service-id-path=" + proxyIDFileName, - "-log-level=" + config.LogLevel, - "-log-json=" + strconv.FormatBool(config.LogJSON), - "-envoy-concurrency=" + strconv.Itoa(envoyConcurrency), - } - - consulNamespace := namespaces.ConsulNamespace(namespace, config.EnableNamespaces, config.ConsulDestinationNamespace, config.EnableNamespaceMirroring, config.NamespaceMirroringPrefix) - - if config.AuthMethod != "" { - args = append(args, - "-credential-type=login", - "-login-auth-method="+config.AuthMethod, - "-login-bearer-token-path="+bearerTokenFile, - "-login-meta="+fmt.Sprintf("gateway=%s/%s", namespace, name), - ) - if config.ConsulPartition != "" { - args = append(args, "-login-partition="+config.ConsulPartition) - } - } - if config.EnableNamespaces { - args = append(args, "-service-namespace="+consulNamespace) - } - if config.ConsulPartition != "" { - args = append(args, "-service-partition="+config.ConsulPartition) - } - if config.TLSEnabled { - if config.ConsulTLSServerName != "" { - args = append(args, "-tls-server-name="+config.ConsulTLSServerName) - } - if config.ConsulCACert != "" { - args = append(args, "-ca-certs="+constants.LegacyConsulCAFile) - } - } else { - args = append(args, "-tls-disabled") - } - - // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. - args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) - - args = append(args, fmt.Sprintf("-envoy-admin-bind-port=%d", 19000)) - - // Set a default scrape path that can be overwritten by the annotation. - prometheusScrapePath := defaultPrometheusScrapePath - args = append(args, "-telemetry-prom-scrape-path="+prometheusScrapePath) - - return args, nil -} diff --git a/control-plane/api-gateway/gatekeeper/deployment.go b/control-plane/api-gateway/gatekeeper/deployment.go deleted file mode 100644 index 6bc1402eed..0000000000 --- a/control-plane/api-gateway/gatekeeper/deployment.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "context" - - appsv1 "k8s.io/api/apps/v1" - 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/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - 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" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - defaultInstances int32 = 1 -) - -func (g *Gatekeeper) upsertDeployment(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { - // Get Deployment if it exists. - existingDeployment := &appsv1.Deployment{} - exists := false - - err := g.Client.Get(ctx, g.namespacedName(gateway), existingDeployment) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } else if k8serrors.IsNotFound(err) { - exists = false - } else { - exists = true - } - - var currentReplicas *int32 - if exists { - currentReplicas = existingDeployment.Spec.Replicas - } - - deployment, err := g.deployment(gateway, gcc, config, currentReplicas) - if err != nil { - return err - } - - if exists { - g.Log.V(1).Info("Existing Gateway Deployment found.") - - // If the user has set the number of replicas, let's respect that. - deployment.Spec.Replicas = existingDeployment.Spec.Replicas - } - - mutated := deployment.DeepCopy() - mutator := newDeploymentMutator(deployment, mutated, gcc, gateway, g.Client.Scheme()) - - result, err := controllerutil.CreateOrUpdate(ctx, g.Client, mutated, mutator) - if err != nil { - return err - } - - switch result { - case controllerutil.OperationResultCreated: - g.Log.V(1).Info("Created Deployment") - case controllerutil.OperationResultUpdated: - g.Log.V(1).Info("Updated Deployment") - case controllerutil.OperationResultNone: - g.Log.V(1).Info("No change to deployment") - } - - return nil -} - -func (g *Gatekeeper) deleteDeployment(ctx context.Context, gwName types.NamespacedName) error { - err := g.Client.Delete(ctx, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}) - if k8serrors.IsNotFound(err) { - return nil - } - - return err -} - -func (g *Gatekeeper) deployment(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig, currentReplicas *int32) (*appsv1.Deployment, error) { - initContainer, err := initContainer(config, gateway.Name, gateway.Namespace) - if err != nil { - return nil, err - } - - container, err := consulDataplaneContainer(config, gcc, gateway.Name, gateway.Namespace) - if err != nil { - return nil, err - } - - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: gateway.Name, - Namespace: gateway.Namespace, - Labels: common.LabelsForGateway(&gateway), - }, - Spec: appsv1.DeploymentSpec{ - Replicas: deploymentReplicas(gcc, currentReplicas), - Selector: &metav1.LabelSelector{ - MatchLabels: common.LabelsForGateway(&gateway), - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: common.LabelsForGateway(&gateway), - Annotations: map[string]string{ - constants.AnnotationInject: "false", - constants.AnnotationGatewayConsulServiceName: gateway.Name, - constants.AnnotationGatewayKind: "api-gateway", - }, - }, - Spec: corev1.PodSpec{ - Volumes: []corev1.Volume{ - { - Name: volumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, - }, - }, - }, - InitContainers: []corev1.Container{ - initContainer, - }, - Containers: []corev1.Container{ - container, - }, - Affinity: &corev1.Affinity{ - PodAntiAffinity: &corev1.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ - { - Weight: 1, - PodAffinityTerm: corev1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: common.LabelsForGateway(&gateway), - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - }, - NodeSelector: gcc.Spec.NodeSelector, - Tolerations: gcc.Spec.Tolerations, - ServiceAccountName: g.serviceAccountName(gateway, config), - }, - }, - }, - }, nil -} - -func mergeDeployments(gcc v1alpha1.GatewayClassConfig, a, b *appsv1.Deployment) *appsv1.Deployment { - if !compareDeployments(a, b) { - b.Spec.Template = a.Spec.Template - b.Spec.Replicas = deploymentReplicas(gcc, a.Spec.Replicas) - } - - return b -} - -func compareDeployments(a, b *appsv1.Deployment) bool { - // since K8s adds a bunch of defaults when we create a deployment, check that - // they don't differ by the things that we may actually change, namely container - // ports - if len(b.Spec.Template.Spec.Containers) != len(a.Spec.Template.Spec.Containers) { - return false - } - for i, container := range a.Spec.Template.Spec.Containers { - otherPorts := b.Spec.Template.Spec.Containers[i].Ports - if len(container.Ports) != len(otherPorts) { - return false - } - for j, port := range container.Ports { - otherPort := otherPorts[j] - if port.ContainerPort != otherPort.ContainerPort { - return false - } - if port.Protocol != otherPort.Protocol { - return false - } - } - } - - if b.Spec.Replicas == nil && a.Spec.Replicas == nil { - return true - } else if b.Spec.Replicas == nil { - return false - } else if a.Spec.Replicas == nil { - return false - } - - return *b.Spec.Replicas == *a.Spec.Replicas -} - -func newDeploymentMutator(deployment, mutated *appsv1.Deployment, gcc v1alpha1.GatewayClassConfig, gateway gwv1beta1.Gateway, scheme *runtime.Scheme) resourceMutator { - return func() error { - mutated = mergeDeployments(gcc, deployment, mutated) - return ctrl.SetControllerReference(&gateway, mutated, scheme) - } -} - -func deploymentReplicas(gcc v1alpha1.GatewayClassConfig, currentReplicas *int32) *int32 { - instanceValue := defaultInstances - - // If currentReplicas is not nil use current value when building deployment... - if currentReplicas != nil { - instanceValue = *currentReplicas - } else if gcc.Spec.DeploymentSpec.DefaultInstances != nil { - // otherwise use the default value on the GatewayClassConfig if set. - instanceValue = *gcc.Spec.DeploymentSpec.DefaultInstances - } - - if gcc.Spec.DeploymentSpec.MaxInstances != nil { - // Check if the deployment replicas are greater than the maximum and lower to the maximum if so. - maxValue := *gcc.Spec.DeploymentSpec.MaxInstances - if instanceValue > maxValue { - instanceValue = maxValue - } - } - - if gcc.Spec.DeploymentSpec.MinInstances != nil { - // Check if the deployment replicas are less than the minimum and raise to the minimum if so. - minValue := *gcc.Spec.DeploymentSpec.MinInstances - if instanceValue < minValue { - instanceValue = minValue - } - - } - return &instanceValue -} diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper.go b/control-plane/api-gateway/gatekeeper/gatekeeper.go deleted file mode 100644 index 6cb7170fc8..0000000000 --- a/control-plane/api-gateway/gatekeeper/gatekeeper.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "context" - "fmt" - - "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// Gatekeeper is used to manage the lifecycle of Gateway deployments and services. -type Gatekeeper struct { - Log logr.Logger - Client client.Client -} - -// New creates a new Gatekeeper from the Config. -func New(log logr.Logger, client client.Client) *Gatekeeper { - return &Gatekeeper{ - Log: log, - Client: client, - } -} - -// Upsert creates or updates the resources for handling routing of network traffic. -// This is done in order based on dependencies between resources. -func (g *Gatekeeper) Upsert(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { - g.Log.V(1).Info(fmt.Sprintf("Upsert Gateway Deployment %s/%s", gateway.Namespace, gateway.Name)) - - if err := g.upsertRole(ctx, gateway, gcc, config); err != nil { - return err - } - - if err := g.upsertServiceAccount(ctx, gateway, config); err != nil { - return err - } - - if err := g.upsertRoleBinding(ctx, gateway, gcc, config); err != nil { - return err - } - - if err := g.upsertService(ctx, gateway, gcc, config); err != nil { - return err - } - - if err := g.upsertDeployment(ctx, gateway, gcc, config); err != nil { - return err - } - - return nil -} - -// Delete removes the resources for handling routing of network traffic. -// This is done in the reverse order of Upsert due to dependencies between resources. -func (g *Gatekeeper) Delete(ctx context.Context, gatewayName types.NamespacedName) error { - g.Log.V(1).Info(fmt.Sprintf("Delete Gateway Deployment %s/%s", gatewayName.Namespace, gatewayName.Name)) - - if err := g.deleteDeployment(ctx, gatewayName); err != nil { - return err - } - - if err := g.deleteService(ctx, gatewayName); err != nil { - return err - } - - if err := g.deleteRoleBinding(ctx, gatewayName); err != nil { - return err - } - - if err := g.deleteServiceAccount(ctx, gatewayName); err != nil { - return err - } - - if err := g.deleteRole(ctx, gatewayName); err != nil { - return err - } - - return nil -} - -// resourceMutator is passed to create or update functions to mutate Kubernetes resources. -type resourceMutator = func() error - -func (g *Gatekeeper) namespacedName(gateway gwv1beta1.Gateway) types.NamespacedName { - return types.NamespacedName{ - Namespace: gateway.Namespace, - Name: gateway.Name, - } -} - -func (g *Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config common.HelmConfig) string { - if config.AuthMethod == "" && !config.EnableOpenShift { - return "" - } - return gateway.Name -} diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go deleted file mode 100644 index 72bf6aeabc..0000000000 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ /dev/null @@ -1,1351 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "context" - "fmt" - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbac "k8s.io/api/rbac/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - 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" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -var ( - createdAtLabelKey = "gateway.consul.hashicorp.com/created" - createdAtLabelValue = "101010" - dataplaneImage = "hashicorp/consul-dataplane" - name = "test" - namespace = "default" - labels = map[string]string{ - "component": "api-gateway", - "gateway.consul.hashicorp.com/name": name, - "gateway.consul.hashicorp.com/namespace": namespace, - createdAtLabelKey: createdAtLabelValue, - "gateway.consul.hashicorp.com/managed": "true", - } - listeners = []gwv1beta1.Listener{ - { - Name: "Listener 1", - Port: 8080, - Protocol: "TCP", - Hostname: common.PointerTo(gwv1beta1.Hostname("example.com")), - }, - { - Name: "Listener 2", - Port: 8081, - Protocol: "TCP", - }, - { - Name: "Listener 3", - Port: 8080, - Protocol: "TCP", - Hostname: common.PointerTo(gwv1beta1.Hostname("example.net")), - }, - } -) - -type testCase struct { - gateway gwv1beta1.Gateway - gatewayClassConfig v1alpha1.GatewayClassConfig - helmConfig common.HelmConfig - - initialResources resources - finalResources resources -} - -type resources struct { - deployments []*appsv1.Deployment - roles []*rbac.Role - roleBindings []*rbac.RoleBinding - services []*corev1.Service - serviceAccounts []*corev1.ServiceAccount -} - -func TestUpsert(t *testing.T) { - t.Parallel() - - cases := map[string]testCase{ - "create a new gateway deployment with only Deployment": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{}, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "create a new gateway with service and map privileged ports correctly": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "Listener 1", - Port: 80, - Protocol: "TCP", - }, - { - Name: "Listener 2", - Port: 8080, - Protocol: "TCP", - }, - }, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - MapPrivilegedContainerPorts: 2000, - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{}, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 80, - TargetPort: intstr.FromInt(2080), - }, - { - Name: "Listener 2", - Protocol: "TCP", - Port: 8080, - TargetPort: intstr.FromInt(8080), - }, - }, "1"), - }, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "create a new gateway deployment with managed Service": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{}, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, - TargetPort: intstr.FromInt(8080), - }, - { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, - TargetPort: intstr.FromInt(8081), - }, - }, "1"), - }, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "create a new gateway deployment with managed Service and ACLs": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - AuthMethod: "method", - ImageDataplane: dataplaneImage, - }, - initialResources: resources{}, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1", false), - }, - roleBindings: []*rbac.RoleBinding{ - configureRoleBinding(name, namespace, labels, "1"), - }, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, - TargetPort: intstr.FromInt(8080), - }, - { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, - TargetPort: intstr.FromInt(8081), - }, - }, "1"), - }, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - }, - "create a new gateway where the GatewayClassConfig has a default number of instances greater than the max on the GatewayClassConfig": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(8)), - MaxInstances: common.PointerTo(int32(5)), - MinInstances: common.PointerTo(int32(2)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{}, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "create a new gateway where the GatewayClassConfig has a default number of instances lesser than the min on the GatewayClassConfig": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(1)), - MaxInstances: common.PointerTo(int32(5)), - MinInstances: common.PointerTo(int32(2)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{}, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 2, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "update a gateway, adding a listener to a service": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - AuthMethod: "method", - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1", false), - }, - roleBindings: []*rbac.RoleBinding{ - configureRoleBinding(name, namespace, labels, "1"), - }, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, - }, - }, "1"), - }, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"), - }, - roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1", false), - }, - roleBindings: []*rbac.RoleBinding{ - configureRoleBinding(name, namespace, labels, "1"), - }, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, - TargetPort: intstr.FromInt(8080), - }, - { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, - TargetPort: intstr.FromInt(8081), - }, - }, "2"), - }, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - }, - "update a gateway, removing a listener from a service": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - listeners[0], - }, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - AuthMethod: "method", - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1", false), - }, - roleBindings: []*rbac.RoleBinding{ - configureRoleBinding(name, namespace, labels, "1"), - }, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, - }, - { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, - }, - }, "1"), - }, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"), - }, - roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1", false), - }, - roleBindings: []*rbac.RoleBinding{ - configureRoleBinding(name, namespace, labels, "1"), - }, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, - TargetPort: intstr.FromInt(8080), - }, - }, "2"), - }, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - }, - "updating a gateway deployment respects the number of replicas a user has set": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(5)), - MaxInstances: common.PointerTo(int32(7)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "update a gateway deployment by scaling it when no min or max number of instances is defined on the GatewayClassConfig": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: nil, - MinInstances: nil, - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 8, nil, nil, "", "1"), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 8, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "update a gateway deployment by scaling it lower than the min number of instances on the GatewayClassConfig": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(5)), - MinInstances: common.PointerTo(int32(2)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 1, nil, nil, "", "1"), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 2, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "update a gateway deployment by scaling it higher than the max number of instances on the GatewayClassConfig": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(5)), - MinInstances: common.PointerTo(int32(2)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 10, nil, nil, "", "1"), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 5, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "create a new gateway with openshift enabled": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - OpenshiftSCCName: "test-api-gateway", - }, - }, - helmConfig: common.HelmConfig{ - EnableOpenShift: true, - ImageDataplane: "hashicorp/consul-dataplane", - }, - initialResources: resources{}, - finalResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1", true), - }, - roleBindings: []*rbac.RoleBinding{ - configureRoleBinding(name, namespace, labels, "1"), - }, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - require.NoError(t, rbac.AddToScheme(s)) - require.NoError(t, corev1.AddToScheme(s)) - require.NoError(t, appsv1.AddToScheme(s)) - - log := logrtest.New(t) - - objs := append(joinResources(tc.initialResources), &tc.gateway, &tc.gatewayClassConfig) - client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() - - gatekeeper := New(log, client) - - err := gatekeeper.Upsert(context.Background(), tc.gateway, tc.gatewayClassConfig, tc.helmConfig) - require.NoError(t, err) - require.NoError(t, validateResourcesExist(t, client, tc.finalResources)) - }) - } -} - -func TestDelete(t *testing.T) { - t.Parallel() - - cases := map[string]testCase{ - "delete a gateway deployment with only Deployment": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{}, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "delete a gateway deployment with a managed Service": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{}, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, - }, - { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, - }, - }, "1"), - }, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{}, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - "delete a gateway deployment with managed Service and ACLs": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: listeners, - }, - }, - gatewayClassConfig: v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-gatewayclassconfig", - }, - Spec: v1alpha1.GatewayClassConfigSpec{ - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: common.PointerTo(int32(3)), - MaxInstances: common.PointerTo(int32(3)), - MinInstances: common.PointerTo(int32(1)), - }, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - AuthMethod: "method", - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), - }, - roles: []*rbac.Role{ - configureRole(name, namespace, labels, "1", false), - }, - roleBindings: []*rbac.RoleBinding{ - configureRoleBinding(name, namespace, labels, "1"), - }, - services: []*corev1.Service{ - configureService(name, namespace, labels, nil, (corev1.ServiceType)("NodePort"), []corev1.ServicePort{ - { - Name: "Listener 1", - Protocol: "TCP", - Port: 8080, - }, - { - Name: "Listener 2", - Protocol: "TCP", - Port: 8081, - }, - }, "1"), - }, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{}, - roles: []*rbac.Role{}, - services: []*corev1.Service{}, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - require.NoError(t, rbac.AddToScheme(s)) - require.NoError(t, corev1.AddToScheme(s)) - require.NoError(t, appsv1.AddToScheme(s)) - - log := logrtest.New(t) - - objs := append(joinResources(tc.initialResources), &tc.gateway, &tc.gatewayClassConfig) - client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() - - gatekeeper := New(log, client) - - err := gatekeeper.Delete(context.Background(), types.NamespacedName{ - Namespace: tc.gateway.Namespace, - Name: tc.gateway.Name, - }) - require.NoError(t, err) - require.NoError(t, validateResourcesExist(t, client, tc.finalResources)) - require.NoError(t, validateResourcesAreDeleted(t, client, tc.initialResources)) - }) - } -} - -func joinResources(resources resources) (objs []client.Object) { - for _, deployment := range resources.deployments { - objs = append(objs, deployment) - } - - for _, role := range resources.roles { - objs = append(objs, role) - } - - for _, roleBinding := range resources.roleBindings { - objs = append(objs, roleBinding) - } - - for _, service := range resources.services { - objs = append(objs, service) - } - - for _, serviceAccount := range resources.serviceAccounts { - objs = append(objs, serviceAccount) - } - - return objs -} - -func validateResourcesExist(t *testing.T, client client.Client, resources resources) error { - t.Helper() - - for _, expected := range resources.deployments { - actual := &appsv1.Deployment{} - err := client.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if err != nil { - return err - } - - // Patch the createdAt label - actual.Labels[createdAtLabelKey] = createdAtLabelValue - actual.Spec.Selector.MatchLabels[createdAtLabelKey] = createdAtLabelValue - actual.Spec.Template.ObjectMeta.Labels[createdAtLabelKey] = createdAtLabelValue - - require.Equal(t, expected.Name, actual.Name) - require.Equal(t, expected.Namespace, actual.Namespace) - require.Equal(t, expected.APIVersion, actual.APIVersion) - require.Equal(t, expected.Labels, actual.Labels) - if expected.Spec.Replicas != nil { - require.NotNil(t, actual.Spec.Replicas) - require.EqualValues(t, *expected.Spec.Replicas, *actual.Spec.Replicas) - } - 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 there is a consul-dataplane container dropping ALL capabilities, adding - // back the NET_BIND_SERVICE capability, and establishing a read-only root filesystem - hasDataplaneContainer := false - for _, container := range actual.Spec.Template.Spec.Containers { - if container.Image == dataplaneImage { - hasDataplaneContainer = true - require.NotNil(t, container.SecurityContext) - require.NotNil(t, container.SecurityContext.Capabilities) - require.NotNil(t, container.SecurityContext.ReadOnlyRootFilesystem) - assert.True(t, *container.SecurityContext.ReadOnlyRootFilesystem) - assert.Equal(t, []corev1.Capability{netBindCapability}, container.SecurityContext.Capabilities.Add) - assert.Equal(t, []corev1.Capability{allCapabilities}, container.SecurityContext.Capabilities.Drop) - } - } - assert.True(t, hasDataplaneContainer) - } - - for _, expected := range resources.roles { - actual := &rbac.Role{} - err := client.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if err != nil { - return err - } - - // Patch the createdAt label - actual.Labels[createdAtLabelKey] = createdAtLabelValue - - require.Equal(t, expected, actual) - } - - for _, expected := range resources.roleBindings { - actual := &rbac.RoleBinding{} - err := client.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if err != nil { - return err - } - - // Patch the createdAt label - actual.Labels[createdAtLabelKey] = createdAtLabelValue - - require.Equal(t, expected, actual) - } - - for _, expected := range resources.services { - actual := &corev1.Service{} - err := client.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if err != nil { - return err - } - - // Patch the createdAt label - actual.Labels[createdAtLabelKey] = createdAtLabelValue - actual.Spec.Selector[createdAtLabelKey] = createdAtLabelValue - - require.Equal(t, expected, actual) - } - - for _, expected := range resources.serviceAccounts { - actual := &corev1.ServiceAccount{} - err := client.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if err != nil { - return err - } - - // Patch the createdAt label - actual.Labels[createdAtLabelKey] = createdAtLabelValue - - require.Equal(t, expected, actual) - } - - return nil -} - -func validateResourcesAreDeleted(t *testing.T, k8sClient client.Client, resources resources) error { - t.Helper() - - for _, expected := range resources.deployments { - actual := &appsv1.Deployment{} - err := k8sClient.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if !k8serrors.IsNotFound(err) { - return fmt.Errorf("expected deployment %s to be deleted", expected.Name) - } - require.Error(t, err) - } - - for _, expected := range resources.roles { - actual := &rbac.Role{} - err := k8sClient.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if !k8serrors.IsNotFound(err) { - return fmt.Errorf("expected role %s to be deleted", expected.Name) - } - require.Error(t, err) - } - - for _, expected := range resources.roleBindings { - actual := &rbac.RoleBinding{} - err := k8sClient.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if !k8serrors.IsNotFound(err) { - return fmt.Errorf("expected rolebinding %s to be deleted", expected.Name) - } - require.Error(t, err) - } - - for _, expected := range resources.services { - actual := &corev1.Service{} - err := k8sClient.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if !k8serrors.IsNotFound(err) { - return fmt.Errorf("expected service %s to be deleted", expected.Name) - } - require.Error(t, err) - } - - for _, expected := range resources.serviceAccounts { - actual := &corev1.ServiceAccount{} - err := k8sClient.Get(context.Background(), types.NamespacedName{ - Name: expected.Name, - Namespace: expected.Namespace, - }, actual) - if !k8serrors.IsNotFound(err) { - return fmt.Errorf("expected service account %s to be deleted", expected.Name) - } - require.Error(t, err) - } - - 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 { - return &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: labels, - ResourceVersion: resourceVersion, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "gateway.networking.k8s.io/v1beta1", - Kind: "Gateway", - Name: name, - Controller: common.PointerTo(true), - BlockOwnerDeletion: common.PointerTo(true), - }, - }, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - Annotations: map[string]string{ - constants.AnnotationInject: "false", - constants.AnnotationGatewayConsulServiceName: name, - constants.AnnotationGatewayKind: "api-gateway", - }, - }, - Spec: corev1.PodSpec{ - Affinity: &corev1.Affinity{ - PodAntiAffinity: &corev1.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ - { - Weight: 1, - PodAffinityTerm: corev1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - }, - NodeSelector: nodeSelector, - Tolerations: tolerations, - ServiceAccountName: serviceAccoutName, - }, - }, - }, - } -} - -func configureRole(name, namespace string, labels map[string]string, resourceVersion string, openshiftEnabled bool) *rbac.Role { - rules := []rbac.PolicyRule{} - - if openshiftEnabled { - rules = []rbac.PolicyRule{ - { - APIGroups: []string{"security.openshift.io"}, - Resources: []string{"securitycontextconstraints"}, - ResourceNames: []string{name + "-api-gateway"}, - Verbs: []string{"use"}, - }, - } - } - return &rbac.Role{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "Role", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: labels, - ResourceVersion: resourceVersion, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "gateway.networking.k8s.io/v1beta1", - Kind: "Gateway", - Name: name, - Controller: common.PointerTo(true), - BlockOwnerDeletion: common.PointerTo(true), - }, - }, - }, - Rules: rules, - } -} - -func configureRoleBinding(name, namespace string, labels map[string]string, resourceVersion string) *rbac.RoleBinding { - return &rbac.RoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "RoleBinding", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: labels, - ResourceVersion: resourceVersion, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "gateway.networking.k8s.io/v1beta1", - Kind: "Gateway", - Name: name, - Controller: common.PointerTo(true), - BlockOwnerDeletion: common.PointerTo(true), - }, - }, - }, - RoleRef: rbac.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: name, - }, - Subjects: []rbac.Subject{ - { - Kind: "ServiceAccount", - Name: name, - Namespace: namespace, - }, - }, - } -} - -func configureService(name, namespace string, labels, annotations map[string]string, serviceType corev1.ServiceType, ports []corev1.ServicePort, resourceVersion string) *corev1.Service { - return &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: labels, - Annotations: annotations, - ResourceVersion: resourceVersion, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "gateway.networking.k8s.io/v1beta1", - Kind: "Gateway", - Name: name, - Controller: common.PointerTo(true), - BlockOwnerDeletion: common.PointerTo(true), - }, - }, - }, - Spec: corev1.ServiceSpec{ - Selector: labels, - Type: serviceType, - Ports: ports, - }, - } -} - -func configureServiceAccount(name, namespace string, labels map[string]string, resourceVersion string) *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: labels, - ResourceVersion: resourceVersion, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "gateway.networking.k8s.io/v1beta1", - Kind: "Gateway", - Name: name, - Controller: common.PointerTo(true), - BlockOwnerDeletion: common.PointerTo(true), - }, - }, - }, - } -} diff --git a/control-plane/api-gateway/gatekeeper/init.go b/control-plane/api-gateway/gatekeeper/init.go deleted file mode 100644 index 2bfa3f8e83..0000000000 --- a/control-plane/api-gateway/gatekeeper/init.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "bytes" - "strconv" - "strings" - "text/template" - - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "k8s.io/utils/pointer" -) - -const ( - injectInitContainerName = "consul-connect-inject-init" - initContainersUserAndGroupID = 5996 -) - -type initContainerCommandData struct { - ServiceName string - ServiceAccountName string - AuthMethod string - - // Log settings for the connect-init command. - LogLevel string - LogJSON bool -} - -// containerInit 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 initContainer(config common.HelmConfig, name, namespace string) (corev1.Container, error) { - data := initContainerCommandData{ - AuthMethod: config.AuthMethod, - LogLevel: config.LogLevel, - LogJSON: config.LogJSON, - ServiceName: name, - ServiceAccountName: name, - } - - // Create expected volume mounts - volMounts := []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/connect-inject", - }, - } - - var bearerTokenFile string - if config.AuthMethod != "" { - bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" - } - - // Render the command - var buf bytes.Buffer - tpl := template.Must(template.New("root").Parse(strings.TrimSpace(initContainerCommandTpl))) - - if err := tpl.Execute(&buf, &data); err != nil { - return corev1.Container{}, err - } - - consulNamespace := namespaces.ConsulNamespace(namespace, config.EnableNamespaces, config.ConsulDestinationNamespace, config.EnableNamespaceMirroring, config.NamespaceMirroringPrefix) - - initContainerName := injectInitContainerName - container := corev1.Container{ - Name: initContainerName, - Image: config.ImageConsulK8S, - - Env: []corev1.EnvVar{ - { - Name: "POD_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, - }, - }, - { - Name: "POD_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, - }, - }, - { - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "CONSUL_ADDRESSES", - Value: config.ConsulConfig.Address, - }, - { - Name: "CONSUL_GRPC_PORT", - Value: strconv.Itoa(config.ConsulConfig.GRPCPort), - }, - { - Name: "CONSUL_HTTP_PORT", - Value: strconv.Itoa(config.ConsulConfig.HTTPPort), - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: config.ConsulConfig.APITimeout.String(), - }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, - }, - VolumeMounts: volMounts, - Command: []string{"/bin/sh", "-ec", buf.String()}, - } - - if config.TLSEnabled { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: constants.UseTLSEnvVar, - Value: "true", - }, - corev1.EnvVar{ - Name: constants.CACertPEMEnvVar, - Value: config.ConsulCACert, - }, - corev1.EnvVar{ - Name: constants.TLSServerNameEnvVar, - Value: config.ConsulTLSServerName, - }) - } - - if config.AuthMethod != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: config.AuthMethod, - }, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: bearerTokenFile, - }, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }) - - if config.ConsulPartition != "" { - container.Env = append(container.Env, corev1.EnvVar{ - Name: "CONSUL_LOGIN_PARTITION", - Value: config.ConsulPartition, - }) - } - } - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_NAMESPACE", - Value: consulNamespace, - }) - - if config.ConsulPartition != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_PARTITION", - Value: config.ConsulPartition, - }) - } - - // Openshift Assigns the security context for us, do not enable if it is enabled. - if !config.EnableOpenShift { - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(initContainersUserAndGroupID), - RunAsGroup: pointer.Int64(initContainersUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(false), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - }, - } - } - - return container, nil -} - -// initContainerCommandTpl is the template for the command executed by -// the init container. -const initContainerCommandTpl = ` -consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ - -gateway-kind="api-gateway" \ - -log-json={{ .LogJSON }} \ - {{- if .AuthMethod }} - -service-account-name="{{ .ServiceAccountName }}" \ - {{- end }} - -service-name="{{ .ServiceName }}" -` diff --git a/control-plane/api-gateway/gatekeeper/role.go b/control-plane/api-gateway/gatekeeper/role.go deleted file mode 100644 index 705e9bffff..0000000000 --- a/control-plane/api-gateway/gatekeeper/role.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "context" - "errors" - - "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" -) - -func (g *Gatekeeper) upsertRole(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { - if config.AuthMethod == "" && !config.EnableOpenShift { - return g.deleteRole(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) - } - - role := &rbac.Role{} - - // If the Role already exists, ensure that we own the Role - err := g.Client.Get(ctx, g.namespacedName(gateway), role) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } else if !k8serrors.IsNotFound(err) { - // Ensure we own the Role. - for _, ref := range role.GetOwnerReferences() { - if ref.UID == gateway.GetUID() && ref.Name == gateway.GetName() { - // We found ourselves! - return nil - } - } - return errors.New("role not owned by controller") - } - - role = g.role(gateway, gcc, config) - if err := ctrl.SetControllerReference(&gateway, role, g.Client.Scheme()); err != nil { - return err - } - if err := g.Client.Create(ctx, role); err != nil { - return err - } - - return nil -} - -func (g *Gatekeeper) deleteRole(ctx context.Context, gwName types.NamespacedName) error { - if err := g.Client.Delete(ctx, &rbac.Role{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}); err != nil { - if k8serrors.IsNotFound(err) { - return nil - } - return err - } - - return nil -} - -func (g *Gatekeeper) role(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) *rbac.Role { - role := &rbac.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: gateway.Name, - Namespace: gateway.Namespace, - Labels: common.LabelsForGateway(&gateway), - }, - Rules: []rbac.PolicyRule{}, - } - - if gcc.Spec.PodSecurityPolicy != "" { - role.Rules = append(role.Rules, rbac.PolicyRule{ - APIGroups: []string{"policy"}, - Resources: []string{"podsecuritypolicies"}, - ResourceNames: []string{gcc.Spec.PodSecurityPolicy}, - Verbs: []string{"use"}, - }) - } - - if config.EnableOpenShift { - role.Rules = append(role.Rules, rbac.PolicyRule{ - APIGroups: []string{"security.openshift.io"}, - Resources: []string{"securitycontextconstraints"}, - ResourceNames: []string{gcc.Spec.OpenshiftSCCName}, - Verbs: []string{"use"}, - }) - } - - return role -} diff --git a/control-plane/api-gateway/gatekeeper/rolebinding.go b/control-plane/api-gateway/gatekeeper/rolebinding.go deleted file mode 100644 index 1a60e752c8..0000000000 --- a/control-plane/api-gateway/gatekeeper/rolebinding.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "context" - "errors" - - "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" -) - -func (g *Gatekeeper) upsertRoleBinding(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { - if config.AuthMethod == "" && !config.EnableOpenShift { - return g.deleteRole(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) - } - - roleBinding := &rbac.RoleBinding{} - - // If the RoleBinding already exists, ensure that we own the RoleBinding - err := g.Client.Get(ctx, g.namespacedName(gateway), roleBinding) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } else if !k8serrors.IsNotFound(err) { - // Ensure we own the Role. - for _, ref := range roleBinding.GetOwnerReferences() { - if ref.UID == gateway.GetUID() && ref.Name == gateway.GetName() { - // We found ourselves! - return nil - } - } - return errors.New("role not owned by controller") - } - - // Create or update the RoleBinding - roleBinding = g.roleBinding(gateway, gcc, config) - if err := ctrl.SetControllerReference(&gateway, roleBinding, g.Client.Scheme()); err != nil { - return err - } - if err := g.Client.Create(ctx, roleBinding); err != nil { - return err - } - - return nil -} - -func (g *Gatekeeper) deleteRoleBinding(ctx context.Context, gwName types.NamespacedName) error { - if err := g.Client.Delete(ctx, &rbac.RoleBinding{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}); err != nil { - if k8serrors.IsNotFound(err) { - return nil - } - return err - } - - return nil -} - -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) - role := g.role(gateway, gcc, config) - - return &rbac.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: gateway.Name, - Namespace: gateway.Namespace, - Labels: common.LabelsForGateway(&gateway), - }, - RoleRef: rbac.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: role.Name, - }, - Subjects: []rbac.Subject{ - { - Kind: "ServiceAccount", - Name: serviceAccount.Name, - Namespace: serviceAccount.Namespace, - }, - }, - } -} diff --git a/control-plane/api-gateway/gatekeeper/service.go b/control-plane/api-gateway/gatekeeper/service.go deleted file mode 100644 index a30a3df89f..0000000000 --- a/control-plane/api-gateway/gatekeeper/service.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "context" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "k8s.io/apimachinery/pkg/types" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -var ( - defaultServiceAnnotations = []string{ - "external-dns.alpha.kubernetes.io/hostname", - } -) - -func (g *Gatekeeper) upsertService(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { - if gcc.Spec.ServiceType == nil { - return g.deleteService(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) - } - - service := g.service(gateway, gcc) - - mutated := service.DeepCopy() - mutator := newServiceMutator(service, mutated, gateway, g.Client.Scheme()) - - result, err := controllerutil.CreateOrUpdate(ctx, g.Client, mutated, mutator) - if err != nil { - return err - } - - switch result { - case controllerutil.OperationResultCreated: - g.Log.V(1).Info("Created Service") - case controllerutil.OperationResultUpdated: - g.Log.V(1).Info("Updated Service") - case controllerutil.OperationResultNone: - g.Log.V(1).Info("No change to service") - } - - return nil -} - -func (g *Gatekeeper) deleteService(ctx context.Context, gwName types.NamespacedName) error { - if err := g.Client.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}); err != nil { - if k8serrors.IsNotFound(err) { - return nil - } - return err - } - - return nil -} - -func (g *Gatekeeper) service(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig) *corev1.Service { - seenPorts := map[gwv1beta1.PortNumber]struct{}{} - ports := []corev1.ServicePort{} - for _, listener := range gateway.Spec.Listeners { - if _, seen := seenPorts[listener.Port]; seen { - // We've already added this listener's port to the Service - continue - } - - ports = append(ports, corev1.ServicePort{ - Name: string(listener.Name), - // only TCP-based services are supported for now - Protocol: corev1.ProtocolTCP, - Port: int32(listener.Port), - TargetPort: intstr.FromInt(common.ToContainerPort(listener.Port, gcc.Spec.MapPrivilegedContainerPorts)), - }) - - seenPorts[listener.Port] = struct{}{} - } - - // Copy annotations from the Gateway, filtered by those allowed by the GatewayClassConfig. - allowedAnnotations := gcc.Spec.CopyAnnotations.Service - if allowedAnnotations == nil { - allowedAnnotations = defaultServiceAnnotations - } - annotations := make(map[string]string) - for _, allowedAnnotation := range allowedAnnotations { - if value, found := gateway.Annotations[allowedAnnotation]; found { - annotations[allowedAnnotation] = value - } - } - - return &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: gateway.Name, - Namespace: gateway.Namespace, - Labels: common.LabelsForGateway(&gateway), - Annotations: annotations, - }, - Spec: corev1.ServiceSpec{ - Selector: common.LabelsForGateway(&gateway), - Type: *gcc.Spec.ServiceType, - Ports: ports, - }, - } -} - -// mergeService is used to keep annotations and ports from the `from` Service -// to the `to` service. This prevents an infinite reconciliation loop when -// Kubernetes adds this configuration back in. -func mergeService(from, to *corev1.Service) *corev1.Service { - if areServicesEqual(from, to) { - return to - } - - to.Annotations = from.Annotations - to.Spec.Ports = from.Spec.Ports - - return to -} - -func areServicesEqual(a, b *corev1.Service) bool { - if !equality.Semantic.DeepEqual(a.Annotations, b.Annotations) { - return false - } - if len(b.Spec.Ports) != len(a.Spec.Ports) { - return false - } - - for i, port := range a.Spec.Ports { - otherPort := b.Spec.Ports[i] - if port.Port != otherPort.Port { - return false - } - if port.Protocol != otherPort.Protocol { - return false - } - } - return true -} - -func newServiceMutator(service, mutated *corev1.Service, gateway gwv1beta1.Gateway, scheme *runtime.Scheme) resourceMutator { - return func() error { - mutated = mergeService(service, mutated) - return ctrl.SetControllerReference(&gateway, mutated, scheme) - } -} diff --git a/control-plane/api-gateway/gatekeeper/serviceaccount.go b/control-plane/api-gateway/gatekeeper/serviceaccount.go deleted file mode 100644 index d1c5c9883a..0000000000 --- a/control-plane/api-gateway/gatekeeper/serviceaccount.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -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" - ctrl "sigs.k8s.io/controller-runtime" -) - -func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1.Gateway, config common.HelmConfig) error { - if config.AuthMethod == "" && !config.EnableOpenShift { - return g.deleteServiceAccount(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) - } - - serviceAccount := &corev1.ServiceAccount{} - exists := false - - // Get ServiceAccount if it exists. - err := g.Client.Get(ctx, g.namespacedName(gateway), serviceAccount) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } else if k8serrors.IsNotFound(err) { - exists = false - } else { - exists = true - } - - if exists { - // Ensure we own the ServiceAccount. - for _, ref := range serviceAccount.GetOwnerReferences() { - if ref.UID == gateway.GetUID() && ref.Name == gateway.GetName() { - // We found ourselves! - return nil - } - } - return errors.New("ServiceAccount not owned by controller") - } - - // Create the ServiceAccount. - serviceAccount = g.serviceAccount(gateway) - 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 -} - -func (g *Gatekeeper) deleteServiceAccount(ctx context.Context, gwName types.NamespacedName) error { - if err := g.Client.Delete(ctx, &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: gwName.Name, Namespace: gwName.Namespace}}); err != nil { - if k8serrors.IsNotFound(err) { - return nil - } - return err - } - - return nil -} - -func (g *Gatekeeper) serviceAccount(gateway gwv1beta1.Gateway) *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: gateway.Name, - Namespace: gateway.Namespace, - Labels: common.LabelsForGateway(&gateway), - }, - } -} diff --git a/control-plane/api/auth/v2beta1/auth_groupversion_info.go b/control-plane/api/auth/v2beta1/auth_groupversion_info.go deleted file mode 100644 index 3329d86855..0000000000 --- a/control-plane/api/auth/v2beta1/auth_groupversion_info.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Package v2beta1 contains API Schema definitions for the consul.hashicorp.com v2beta1 API group -// +kubebuilder:object:generate=true -// +groupName=auth.consul.hashicorp.com -package v2beta1 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var ( - - // AuthGroup is a collection of auth resources. - AuthGroup = "auth.consul.hashicorp.com" - - // AuthGroupVersion is group version used to register these objects. - AuthGroupVersion = schema.GroupVersion{Group: AuthGroup, Version: "v2beta1"} - - // AuthSchemeBuilder is used to add go types to the GroupVersionKind scheme. - AuthSchemeBuilder = &scheme.Builder{GroupVersion: AuthGroupVersion} - - // AddAuthToScheme adds the types in this group-version to the given scheme. - AddAuthToScheme = AuthSchemeBuilder.AddToScheme -) diff --git a/control-plane/api/auth/v2beta1/shared_types.go b/control-plane/api/auth/v2beta1/shared_types.go deleted file mode 100644 index a5225afb71..0000000000 --- a/control-plane/api/auth/v2beta1/shared_types.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -func meshConfigMeta() map[string]string { - return map[string]string{ - common.SourceKey: common.SourceValue, - } -} diff --git a/control-plane/api/auth/v2beta1/status.go b/control-plane/api/auth/v2beta1/status.go deleted file mode 100644 index cc75a1cd82..0000000000 --- a/control-plane/api/auth/v2beta1/status.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Conditions is the schema for the conditions portion of the payload. -type Conditions []Condition - -// ConditionType is a camel-cased condition type. -type ConditionType string - -const ( - // ConditionSynced specifies that the resource has been synced with Consul. - ConditionSynced ConditionType = "Synced" -) - -// Conditions define a readiness condition for a Consul resource. -// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Condition struct { - // Type of condition. - // +required - Type ConditionType `json:"type" description:"type of status condition"` - - // Status of the condition, one of True, False, Unknown. - // +required - Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` - - // LastTransitionTime is the last time the condition transitioned from one status to another. - // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"` - - // The reason for the condition's last transition. - // +optional - Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` - - // A human readable message indicating details about the transition. - // +optional - Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` -} - -// IsTrue is true if the condition is True. -func (c *Condition) IsTrue() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionTrue -} - -// IsFalse is true if the condition is False. -func (c *Condition) IsFalse() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionFalse -} - -// IsUnknown is true if the condition is Unknown. -func (c *Condition) IsUnknown() bool { - if c == nil { - return true - } - return c.Status == corev1.ConditionUnknown -} - -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Status struct { - // Conditions indicate the latest available observations of a resource's current state. - // +optional - // +patchMergeKey=type - // +patchStrategy=merge - Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` - - // LastSyncedTime is the last time the resource successfully synced with Consul. - // +optional - LastSyncedTime *metav1.Time `json:"lastSyncedTime,omitempty" description:"last time the condition transitioned from one status to another"` -} - -func (s *Status) GetCondition(t ConditionType) *Condition { - for _, cond := range s.Conditions { - if cond.Type == t { - return &cond - } - } - return nil -} diff --git a/control-plane/api/auth/v2beta1/traffic_permissions_types.go b/control-plane/api/auth/v2beta1/traffic_permissions_types.go deleted file mode 100644 index e3a0d32f1a..0000000000 --- a/control-plane/api/auth/v2beta1/traffic_permissions_types.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - trafficpermissionsKubeKind = "trafficpermissions" -) - -func init() { - AuthSchemeBuilder.Register(&TrafficPermissions{}, &TrafficPermissionsList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// TrafficPermissions is the Schema for the traffic-permissions API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="traffic-permissions" -type TrafficPermissions struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbauth.TrafficPermissions `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// TrafficPermissionsList contains a list of TrafficPermissions. -type TrafficPermissionsList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*TrafficPermissions `json:"items"` -} - -func (in *TrafficPermissions) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbauth.TrafficPermissionsType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *TrafficPermissions) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *TrafficPermissions) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *TrafficPermissions) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *TrafficPermissions) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *TrafficPermissions) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *TrafficPermissions) KubeKind() string { - return trafficpermissionsKubeKind -} - -func (in *TrafficPermissions) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *TrafficPermissions) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *TrafficPermissions) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *TrafficPermissions) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *TrafficPermissions) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *TrafficPermissions) Validate(tenancy common.ConsulTenancyConfig) error { - var errs field.ErrorList - path := field.NewPath("spec") - var tp pbauth.TrafficPermissions - res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) - if err := res.Data.UnmarshalTo(&tp); err != nil { - return fmt.Errorf("error parsing resource data as type %q: %s", &tp, err) - } - - switch tp.Action { - case pbauth.Action_ACTION_ALLOW: - case pbauth.Action_ACTION_DENY: - case pbauth.Action_ACTION_UNSPECIFIED: - fallthrough - default: - errs = append(errs, field.Invalid(path.Child("action"), tp.Action, "action must be either allow or deny")) - } - - if tp.Destination == nil || (len(tp.Destination.IdentityName) == 0) { - errs = append(errs, field.Invalid(path.Child("destination"), tp.Destination, "cannot be empty")) - } - // Validate permissions - for i, permission := range tp.Permissions { - if err := validatePermission(permission, path.Child("permissions").Index(i)); err != nil { - errs = append(errs, err...) - } - } - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: AuthGroup, Kind: common.TrafficPermissions}, - in.KubernetesName(), errs) - } - return nil -} - -func validatePermission(p *pbauth.Permission, path *field.Path) field.ErrorList { - var errs field.ErrorList - - for s, src := range p.Sources { - if sourceHasIncompatibleTenancies(src) { - errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "permission sources may not specify partitions, peers, and sameness_groups together")) - } - - if src.Namespace == "" && src.IdentityName != "" { - errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "permission sources may not have wildcard namespaces and explicit names")) - } - - // Excludes are only valid for wildcard sources. - if src.IdentityName != "" && len(src.Exclude) > 0 { - errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "must be defined on wildcard sources")) - continue - } - - for e, d := range src.Exclude { - if sourceHasIncompatibleTenancies(d) { - errs = append(errs, field.Invalid(path.Child("sources").Index(s).Child("exclude").Index(e), d, "permissions sources may not specify partitions, peers, and sameness_groups together")) - } - - if d.Namespace == "" && d.IdentityName != "" { - errs = append(errs, field.Invalid(path.Child("sources").Index(s).Child("exclude").Index(e), d, "permission sources may not have wildcard namespaces and explicit names")) - } - } - } - for d, dest := range p.DestinationRules { - if (len(dest.PathExact) > 0 && len(dest.PathPrefix) > 0) || - (len(dest.PathRegex) > 0 && len(dest.PathExact) > 0) || - (len(dest.PathRegex) > 0 && len(dest.PathPrefix) > 0) { - errs = append(errs, field.Invalid(path.Child("destinationRules").Index(d), dest, "prefix values, regex values, and explicit names must not combined")) - } - if len(dest.Exclude) > 0 { - for e, excl := range dest.Exclude { - if (len(excl.PathExact) > 0 && len(excl.PathPrefix) > 0) || - (len(excl.PathRegex) > 0 && len(excl.PathExact) > 0) || - (len(excl.PathRegex) > 0 && len(excl.PathPrefix) > 0) { - errs = append(errs, field.Invalid(path.Child("destinationRules").Index(d).Child("exclude").Index(e), excl, "prefix values, regex values, and explicit names must not combined")) - } - } - } - } - - return errs -} - -func sourceHasIncompatibleTenancies(src pbauth.SourceToSpiffe) bool { - peerSet := src.GetPeer() != common.DefaultPeerName && src.GetPeer() != "" - apSet := src.GetPartition() != common.DefaultPartitionName && src.GetPartition() != "" - sgSet := src.GetSamenessGroup() != "" - - return (apSet && peerSet) || (apSet && sgSet) || (peerSet && sgSet) -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *TrafficPermissions) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go b/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go deleted file mode 100644 index f13503f6de..0000000000 --- a/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go +++ /dev/null @@ -1,1048 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/timestamppb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestTrafficPermissions_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *TrafficPermissions - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbauth.TrafficPermissions - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbauth.TrafficPermissions{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbauth.TrafficPermissions{ - Destination: nil, - Action: pbauth.Action_ACTION_UNSPECIFIED, - Permissions: nil, - }, - Matches: true, - }, - "source namespaces and partitions are compared": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: "the space namespace space", - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: "not space namespace", - }, - }, - }, - }, - }, - Matches: false, - }, - "destination namespaces and partitions are compared": { - OurConsulNamespace: "not-consul-ns", - OurConsulPartition: "not-consul-partition", - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_DENY, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - }, - }, - }, - }, - }, - Matches: false, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - }, - }, - }, - { - IdentityName: "source-identity", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Headers: []*pbauth.DestinationRuleHeader{ - { - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }, - }, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Headers: []*pbauth.DestinationRuleHeader{ - { - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }, - }, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - // These are intentionally in a different order to show that it doesn't matter - { - IdentityName: "source-identity", - }, - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - }, - }, - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Headers: []*pbauth.DestinationRuleHeader{ - { - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }, - }, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Headers: []*pbauth.DestinationRuleHeader{ - { - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }, - }, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbauth.TrafficPermissions{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructTrafficPermissionResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestTrafficPermissions_Resource also includes test to verify ResourceID(). -func TestTrafficPermissions_Resource(t *testing.T) { - cases := map[string]struct { - Ours *TrafficPermissions - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbauth.TrafficPermissions - }{ - "empty fields": { - Ours: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbauth.TrafficPermissions{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbauth.TrafficPermissions{}, - }, - "every field set": { - Ours: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - }, - }, - }, - { - IdentityName: "source-identity", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Headers: []*pbauth.DestinationRuleHeader{{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }}, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Headers: []*pbauth.DestinationRuleHeader{{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }}, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - // These are intentionally in a different order to show that it doesn't matter - { - IdentityName: "source-identity", - }, - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - }, - }, - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Headers: []*pbauth.DestinationRuleHeader{{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }}, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Headers: []*pbauth.DestinationRuleHeader{{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }}, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructTrafficPermissionResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - }) - } -} - -func TestTrafficPermissions_SetSyncedCondition(t *testing.T) { - trafficPermissions := &TrafficPermissions{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestTrafficPermissions_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &TrafficPermissions{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestTrafficPermissions_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &TrafficPermissions{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestTrafficPermissions_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&TrafficPermissions{}).GetCondition(ConditionSynced)) -} - -func TestTrafficPermissions_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&TrafficPermissions{}).SyncedConditionStatus()) -} - -func TestTrafficPermissions_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&TrafficPermissions{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestTrafficPermissions_KubeKind(t *testing.T) { - require.Equal(t, "trafficpermissions", (&TrafficPermissions{}).KubeKind()) -} - -func TestTrafficPermissions_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "foo", - }, - }, - }).KubernetesName()) -} - -func TestTrafficPermissions_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &TrafficPermissions{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestTrafficPermissions_DefaultNamespaceFields(t *testing.T) - -func TestTrafficPermissions_Validate(t *testing.T) { - cases := []struct { - name string - input *TrafficPermissions - expectedErrMsgs []string - }{ - { - name: "kitchen sink OK", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - SamenessGroup: "space-group", - }, - }, - }, - { - IdentityName: "source-identity", - Namespace: "another-namespace", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Headers: []*pbauth.DestinationRuleHeader{ - { - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }, - }, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathPrefix: "/world", - Headers: []*pbauth.DestinationRuleHeader{ - { - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }, - }, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: nil, - }, - { - name: "must have an action", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "dest-service", - }, - }, - }, - expectedErrMsgs: []string{ - `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.action: Invalid value: ACTION_UNSPECIFIED: action must be either allow or deny`, - }, - }, - { - name: "destination is required", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Action: pbauth.Action_ACTION_ALLOW, - }, - }, - expectedErrMsgs: []string{ - `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.destination: Invalid value: "null": cannot be empty`, - }, - }, - { - name: "destination.identityName is required", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Action: pbauth.Action_ACTION_ALLOW, - Destination: &pbauth.Destination{}, - }, - }, - expectedErrMsgs: []string{ - `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.destination: Invalid value: authv2beta1.Destination{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:""}: cannot be empty`, - }, - }, - { - name: "permission.sources: partitions, peers, and sameness_groups", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - }, - { - Namespace: "the space namespace space", - Partition: "space-partition", - SamenessGroup: "space-sameness", - }, - { - Namespace: "the space namespace space", - Peer: "space-peer", - SamenessGroup: "space-sameness", - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].sources[0]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"space-peer", SamenessGroup:"", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, - `spec.permissions[0].sources[1]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"", SamenessGroup:"space-sameness", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, - `spec.permissions[0].sources[2]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"", Peer:"space-peer", SamenessGroup:"space-sameness", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, - }, - }, - { - name: "permission.sources: identity name without namespace", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "false-identity", - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].sources[0]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"false-identity", Namespace:"", Partition:"", Peer:"", SamenessGroup:"", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not have wildcard namespaces and explicit names`, - }, - }, - { - name: "permission.sources: identity name with excludes", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "default-namespace", - IdentityName: "false-identity", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `must be defined on wildcard sources`, - }, - }, - { - name: "permission.sources.exclude: incompatible tenancies", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "default-namespace", - Exclude: []*pbauth.ExcludeSource{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - }, - { - Namespace: "the space namespace space", - Partition: "space-partition", - SamenessGroup: "space-sameness", - }, - { - Namespace: "the space namespace space", - Peer: "space-peer", - SamenessGroup: "space-sameness", - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].sources[0].exclude[0]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"space-peer", SamenessGroup:""}: permissions sources may not specify partitions, peers, and sameness_groups together`, - `spec.permissions[0].sources[0].exclude[1]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"", SamenessGroup:"space-sameness"}: permissions sources may not specify partitions, peers, and sameness_groups together`, - `spec.permissions[0].sources[0].exclude[2]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"", Peer:"space-peer", SamenessGroup:"space-sameness"}: permissions sources may not specify partitions, peers, and sameness_groups together`, - }, - }, - { - name: "permission.sources.exclude: identity name without namespace", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "default-namespace", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "false-identity", - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].sources[0].exclude[0]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"false-identity", Namespace:"", Partition:"", Peer:"", SamenessGroup:""}: permission sources may not have wildcard namespaces and explicit names`, - }, - }, - { - name: "permission.destinationRules: incompatible destination rules", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "foobar", - }, - { - PathExact: "/hello", - PathRegex: "path-regex", - }, - { - PathPrefix: "foobar", - PathRegex: "path-regex", - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].destinationRules[0]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[1]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[2]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, - }, - }, - { - name: "permission.destinationRules.exclude: incompatible destination rules", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - DestinationRules: []*pbauth.DestinationRule{ - { - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "foobar", - }, - { - PathExact: "/hello", - PathRegex: "path-regex", - }, - { - PathPrefix: "foobar", - PathRegex: "path-regex", - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].destinationRules[0].exclude[0]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[0].exclude[1]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[0].exclude[2]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Headers:[]*authv2beta1.DestinationRuleHeader(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.input.Validate(common.ConsulTenancyConfig{}) - if len(tc.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range tc.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func constructTrafficPermissionResource(tp *pbauth.TrafficPermissions, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbauth.TrafficPermissionsType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go b/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go deleted file mode 100644 index 277fa885ef..0000000000 --- a/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type TrafficPermissionsWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.ConsulResourceLister = &TrafficPermissionsWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-trafficpermissions,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=trafficpermissions,versions=v2beta1,name=mutate-trafficpermissions.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *TrafficPermissionsWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource TrafficPermissions - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *TrafficPermissionsWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { - var resourceList TrafficPermissionsList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.ConsulResource - for _, item := range resourceList.Items { - entries = append(entries, common.ConsulResource(item)) - } - return entries, nil -} - -func (v *TrafficPermissionsWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go b/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go deleted file mode 100644 index 3aa46646cb..0000000000 --- a/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,136 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Code generated by controller-gen. DO NOT EDIT. - -package v2beta1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { - if in == nil { - return nil - } - out := new(Condition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Conditions) DeepCopyInto(out *Conditions) { - { - in := &in - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. -func (in Conditions) DeepCopy() Conditions { - if in == nil { - return nil - } - out := new(Conditions) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Status) DeepCopyInto(out *Status) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastSyncedTime != nil { - in, out := &in.LastSyncedTime, &out.LastSyncedTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. -func (in *Status) DeepCopy() *Status { - if in == nil { - return nil - } - out := new(Status) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TrafficPermissions) DeepCopyInto(out *TrafficPermissions) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissions. -func (in *TrafficPermissions) DeepCopy() *TrafficPermissions { - if in == nil { - return nil - } - out := new(TrafficPermissions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TrafficPermissions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TrafficPermissionsList) DeepCopyInto(out *TrafficPermissionsList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*TrafficPermissions, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(TrafficPermissions) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissionsList. -func (in *TrafficPermissionsList) DeepCopy() *TrafficPermissionsList { - if in == nil { - return nil - } - out := new(TrafficPermissionsList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TrafficPermissionsList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 730fd622ac..7c761b6477 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -1,45 +1,16 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Package common holds code that isn't tied to a particular CRD version or type. package common -import ( - "time" - - mapset "github.com/deckarep/golang-set" -) - const ( - // NOTE: these are only used in consul types, they do not map to k8s kinds. - - // V1 config entries. - ServiceDefaults string = "servicedefaults" - ProxyDefaults string = "proxydefaults" - ServiceResolver string = "serviceresolver" - ServiceRouter string = "servicerouter" - ServiceSplitter string = "servicesplitter" - ServiceIntentions string = "serviceintentions" - ExportedServices string = "exportedservices" - IngressGateway string = "ingressgateway" - TerminatingGateway string = "terminatinggateway" - SamenessGroup string = "samenessgroup" - JWTProvider string = "jwtprovider" - ControlPlaneRequestLimit string = "controlplanerequestlimit" - RouteAuthFilter string = "routeauthfilter" - GatewayPolicy string = "gatewaypolicy" - - // V2 resources. - TrafficPermissions string = "trafficpermissions" - GRPCRoute string = "grpcroute" - HTTPRoute string = "httproute" - TCPRoute string = "tcproute" - ProxyConfiguration string = "proxyconfiguration" - MeshGateway string = "meshgateway" - APIGateway string = "apigateway" - GatewayClass string = "gatewayclass" - GatewayClassConfig string = "gatewayclassconfig" - MeshConfiguration string = "meshconfiguration" + ServiceDefaults string = "servicedefaults" + ProxyDefaults string = "proxydefaults" + ServiceResolver string = "serviceresolver" + ServiceRouter string = "servicerouter" + ServiceSplitter string = "servicesplitter" + ServiceIntentions string = "serviceintentions" + ExportedServices string = "exportedservices" + IngressGateway string = "ingressgateway" + TerminatingGateway string = "terminatinggateway" Global string = "global" Mesh string = "mesh" @@ -52,46 +23,4 @@ const ( MigrateEntryKey string = "consul.hashicorp.com/migrate-entry" MigrateEntryTrue string = "true" SourceValue string = "kubernetes" - - DefaultPartitionName = "default" - DefaultNamespaceName = "default" - DefaultPeerName = "local" ) - -// ConsulTenancyConfig manages settings related to Consul namespaces and partitions. -type ConsulTenancyConfig struct { - // EnableConsulPartitions indicates that a user is running Consul Enterprise. - EnableConsulPartitions bool - // ConsulPartition is the Consul Partition to which this controller belongs. - ConsulPartition string - // EnableConsulNamespaces indicates that a user is running Consul Enterprise. - EnableConsulNamespaces bool - // ConsulDestinationNamespace is the name of the Consul namespace to create - // all resources in. If EnableNSMirroring is true this is ignored. - ConsulDestinationNamespace string - // EnableNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any config entry custom resource. Resources will - // be created in the matching Consul namespace. - EnableNSMirroring bool - // NSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - NSMirroringPrefix string -} - -// K8sNamespaceConfig manages allow/deny Kubernetes namespaces. -type K8sNamespaceConfig struct { - // Only endpoints in the AllowK8sNamespacesSet are reconciled. - AllowK8sNamespacesSet mapset.Set - // Endpoints in the DenyK8sNamespacesSet are ignored. - DenyK8sNamespacesSet mapset.Set -} - -// ConsulConfig manages config to tell a pod where consul is located. -type ConsulConfig struct { - Address string - GRPCPort int - HTTPPort int - APITimeout time.Duration -} diff --git a/control-plane/api/common/configentry.go b/control-plane/api/common/configentry.go index 3559d8a6bc..2d83ce05b0 100644 --- a/control-plane/api/common/configentry.go +++ b/control-plane/api/common/configentry.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/control-plane/api/common/configentry_webhook.go b/control-plane/api/common/configentry_webhook.go index 4b8a482d6c..4028a5e3cb 100644 --- a/control-plane/api/common/configentry_webhook.go +++ b/control-plane/api/common/configentry_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/control-plane/api/common/configentry_webhook_test.go b/control-plane/api/common/configentry_webhook_test.go index 2760ff15ff..3a2dc9098b 100644 --- a/control-plane/api/common/configentry_webhook_test.go +++ b/control-plane/api/common/configentry_webhook_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/control-plane/api/common/consul_resource.go b/control-plane/api/common/consul_resource.go deleted file mode 100644 index b957d0fb79..0000000000 --- a/control-plane/api/common/consul_resource.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "github.com/hashicorp/consul/proto-public/pbresource" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type ConsulResource interface { - ResourceID(namespace, partition string) *pbresource.ID - Resource(namespace, partition string) *pbresource.Resource - - // GetObjectKind should be implemented by the generated code. - GetObjectKind() schema.ObjectKind - // DeepCopyObject should be implemented by the generated code. - DeepCopyObject() runtime.Object - - // AddFinalizer adds a finalizer to the list of finalizers. - AddFinalizer(name string) - // RemoveFinalizer removes this finalizer from the list. - RemoveFinalizer(name string) - // Finalizers returns the list of finalizers for this object. - Finalizers() []string - - // MatchesConsul returns true if the resource has the same fields as the Consul - // config entry. - MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool - - // KubeKind returns the Kube config entry kind, i.e. servicedefaults, not - // service-defaults. - KubeKind() string - // KubernetesName returns the name of the Kubernetes resource. - KubernetesName() string - - // SetSyncedCondition updates the synced condition. - SetSyncedCondition(status corev1.ConditionStatus, reason, message string) - // SetLastSyncedTime updates the last synced time. - SetLastSyncedTime(time *metav1.Time) - // SyncedCondition gets the synced condition. - SyncedCondition() (status corev1.ConditionStatus, reason, message string) - // SyncedConditionStatus returns the status of the synced condition. - SyncedConditionStatus() corev1.ConditionStatus - - // Validate returns an error if the resource is invalid. - Validate(tenancy ConsulTenancyConfig) error - - // DefaultNamespaceFields sets Consul namespace fields on the resource - // spec to their default values if namespaces are enabled. - DefaultNamespaceFields(tenancy ConsulTenancyConfig) - - // Object is required so that MeshConfig implements metav1.Object, which is - // the interface supported by controller-runtime reconcile-able resources. - metav1.Object -} diff --git a/control-plane/api/common/consul_resource_webhook.go b/control-plane/api/common/consul_resource_webhook.go deleted file mode 100644 index afda672873..0000000000 --- a/control-plane/api/common/consul_resource_webhook.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/go-logr/logr" - "gomodules.xyz/jsonpatch/v2" - admissionv1 "k8s.io/api/admission/v1" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// ConsulResourceLister is implemented by CRD-specific webhooks. -type ConsulResourceLister interface { - // List returns all resources of this type across all namespaces in a - // Kubernetes cluster. - List(ctx context.Context) ([]ConsulResource, error) -} - -// ValidateConsulResource validates a Consul Resource. It is a generic method that -// can be used by all CRD-specific validators. -// Callers should pass themselves as validator and kind should be the custom -// resource name, e.g. "TrafficPermissions". -func ValidateConsulResource( - ctx context.Context, - req admission.Request, - logger logr.Logger, - resourceLister ConsulResourceLister, - resource ConsulResource, - tenancy ConsulTenancyConfig) admission.Response { - - defaultingPatches, err := ConsulResourceDefaultingPatches(resource, tenancy) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - // On create we need to validate that there isn't already a resource with - // the same name in a different namespace if we're mapping all Kube - // resources to a single Consul namespace. The only case where we're not - // mapping all kube resources to a single Consul namespace is when we - // are running Consul enterprise with namespace mirroring. - singleConsulDestNS := !(tenancy.EnableConsulNamespaces && tenancy.EnableNSMirroring) - if req.Operation == admissionv1.Create && singleConsulDestNS { - logger.Info("validate create", "name", resource.KubernetesName()) - - list, err := resourceLister.List(ctx) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - for _, item := range list { - if item.KubernetesName() == resource.KubernetesName() { - return admission.Errored(http.StatusBadRequest, - fmt.Errorf("%s resource with name %q is already defined – all %s resources must have unique names across namespaces", - resource.KubeKind(), - resource.KubernetesName(), - resource.KubeKind())) - } - } - } - if err := resource.Validate(tenancy); err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - return admission.Patched(fmt.Sprintf("valid %s request", resource.KubeKind()), defaultingPatches...) -} - -// ConsulResourceDefaultingPatches returns the patches needed to set fields to their defaults. -func ConsulResourceDefaultingPatches(resource ConsulResource, tenancy ConsulTenancyConfig) ([]jsonpatch.Operation, error) { - beforeDefaulting, err := json.Marshal(resource) - if err != nil { - return nil, fmt.Errorf("marshalling input: %s", err) - } - resource.DefaultNamespaceFields(tenancy) - afterDefaulting, err := json.Marshal(resource) - if err != nil { - return nil, fmt.Errorf("marshalling after defaulting: %s", err) - } - - defaultingPatches, err := jsonpatch.CreatePatch(beforeDefaulting, afterDefaulting) - if err != nil { - return nil, fmt.Errorf("creating patches: %s", err) - } - return defaultingPatches, nil -} diff --git a/control-plane/api/common/consul_resource_webhook_test.go b/control-plane/api/common/consul_resource_webhook_test.go deleted file mode 100644 index 63bbf9a6e0..0000000000 --- a/control-plane/api/common/consul_resource_webhook_test.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "context" - "encoding/json" - "errors" - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "gomodules.xyz/jsonpatch/v2" - admissionv1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -func TestValidateConsulResource(t *testing.T) { - otherNS := "other" - - cases := map[string]struct { - existingResources []ConsulResource - newResource ConsulResource - enableNamespaces bool - nsMirroring bool - consulDestinationNS string - nsMirroringPrefix string - expAllow bool - expErrMessage string - }{ - "no duplicates, valid": { - existingResources: nil, - newResource: &mockConsulResource{ - MockName: "foo", - MockNamespace: otherNS, - Valid: true, - }, - expAllow: true, - }, - "no duplicates, invalid": { - existingResources: nil, - newResource: &mockConsulResource{ - MockName: "foo", - MockNamespace: otherNS, - Valid: false, - }, - expAllow: false, - expErrMessage: "invalid", - }, - "duplicate name": { - existingResources: []ConsulResource{&mockConsulResource{ - MockName: "foo", - MockNamespace: "default", - }}, - newResource: &mockConsulResource{ - MockName: "foo", - MockNamespace: otherNS, - Valid: true, - }, - expAllow: false, - expErrMessage: "mockkind resource with name \"foo\" is already defined – all mockkind resources must have unique names across namespaces", - }, - "duplicate name, namespaces enabled": { - existingResources: []ConsulResource{&mockConsulResource{ - MockName: "foo", - MockNamespace: "default", - }}, - newResource: &mockConsulResource{ - MockName: "foo", - MockNamespace: otherNS, - Valid: true, - }, - enableNamespaces: true, - expAllow: false, - expErrMessage: "mockkind resource with name \"foo\" is already defined – all mockkind resources must have unique names across namespaces", - }, - "duplicate name, namespaces enabled, mirroring enabled": { - existingResources: []ConsulResource{&mockConsulResource{ - MockName: "foo", - MockNamespace: "default", - }}, - newResource: &mockConsulResource{ - MockName: "foo", - MockNamespace: otherNS, - Valid: true, - }, - enableNamespaces: true, - nsMirroring: true, - expAllow: true, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - ctx := context.Background() - marshalledRequestObject, err := json.Marshal(c.newResource) - require.NoError(t, err) - - lister := &mockConsulResourceLister{ - Resources: c.existingResources, - } - response := ValidateConsulResource(ctx, admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Name: c.newResource.KubernetesName(), - Namespace: otherNS, - Operation: admissionv1.Create, - Object: runtime.RawExtension{ - Raw: marshalledRequestObject, - }, - }, - }, - logrtest.New(t), - lister, - c.newResource, - ConsulTenancyConfig{ - EnableConsulNamespaces: c.enableNamespaces, - ConsulDestinationNamespace: c.consulDestinationNS, - EnableNSMirroring: c.nsMirroring, - NSMirroringPrefix: c.nsMirroringPrefix, - }) - require.Equal(t, c.expAllow, response.Allowed) - if c.expErrMessage != "" { - require.Equal(t, c.expErrMessage, response.AdmissionResponse.Result.Message) - } - }) - } -} - -func TestConsulResourceDefaultingPatches(t *testing.T) { - meshConfig := &mockConsulResource{ - MockName: "test", - Valid: true, - } - - // This test validates that DefaultingPatches invokes DefaultNamespaceFields on the Config Entry. - patches, err := ConsulResourceDefaultingPatches(meshConfig, ConsulTenancyConfig{}) - require.NoError(t, err) - - require.Equal(t, []jsonpatch.Operation{ - { - Operation: "replace", - Path: "/MockNamespace", - Value: "bar", - }, - }, patches) -} - -type mockConsulResourceLister struct { - Resources []ConsulResource -} - -var _ ConsulResourceLister = &mockConsulResourceLister{} - -func (in *mockConsulResourceLister) List(_ context.Context) ([]ConsulResource, error) { - return in.Resources, nil -} - -type mockConsulResource struct { - MockName string - MockNamespace string - Valid bool -} - -var _ ConsulResource = &mockConsulResource{} - -func (in *mockConsulResource) ResourceID(_, _ string) *pbresource.ID { - return nil -} - -func (in *mockConsulResource) Resource(_, _ string) *pbresource.Resource { - return nil -} - -func (in *mockConsulResource) GetNamespace() string { - return in.MockNamespace -} - -func (in *mockConsulResource) SetNamespace(namespace string) { - in.MockNamespace = namespace -} - -func (in *mockConsulResource) GetName() string { - return in.MockName -} - -func (in *mockConsulResource) SetName(name string) { - in.MockName = name -} - -func (in *mockConsulResource) GetGenerateName() string { - return "" -} - -func (in *mockConsulResource) SetGenerateName(_ string) {} - -func (in *mockConsulResource) GetUID() types.UID { - return "" -} - -func (in *mockConsulResource) SetUID(_ types.UID) {} - -func (in *mockConsulResource) GetResourceVersion() string { - return "" -} - -func (in *mockConsulResource) SetResourceVersion(_ string) {} - -func (in *mockConsulResource) GetGeneration() int64 { - return 0 -} - -func (in *mockConsulResource) SetGeneration(_ int64) {} - -func (in *mockConsulResource) GetSelfLink() string { - return "" -} - -func (in *mockConsulResource) SetSelfLink(_ string) {} - -func (in *mockConsulResource) GetCreationTimestamp() metav1.Time { - return metav1.Time{} -} - -func (in *mockConsulResource) SetCreationTimestamp(_ metav1.Time) {} - -func (in *mockConsulResource) GetDeletionTimestamp() *metav1.Time { - return nil -} - -func (in *mockConsulResource) SetDeletionTimestamp(_ *metav1.Time) {} - -func (in *mockConsulResource) GetDeletionGracePeriodSeconds() *int64 { - return nil -} - -func (in *mockConsulResource) SetDeletionGracePeriodSeconds(_ *int64) {} - -func (in *mockConsulResource) GetLabels() map[string]string { - return nil -} - -func (in *mockConsulResource) SetLabels(_ map[string]string) {} - -func (in *mockConsulResource) GetAnnotations() map[string]string { - return nil -} - -func (in *mockConsulResource) SetAnnotations(_ map[string]string) {} - -func (in *mockConsulResource) GetFinalizers() []string { - return nil -} - -func (in *mockConsulResource) SetFinalizers(_ []string) {} - -func (in *mockConsulResource) GetOwnerReferences() []metav1.OwnerReference { - return nil -} - -func (in *mockConsulResource) SetOwnerReferences(_ []metav1.OwnerReference) {} - -func (in *mockConsulResource) GetClusterName() string { - return "" -} - -func (in *mockConsulResource) SetClusterName(_ string) {} - -func (in *mockConsulResource) GetManagedFields() []metav1.ManagedFieldsEntry { - return nil -} - -func (in *mockConsulResource) SetManagedFields(_ []metav1.ManagedFieldsEntry) {} - -func (in *mockConsulResource) KubernetesName() string { - return in.MockName -} - -func (in *mockConsulResource) GetObjectMeta() metav1.ObjectMeta { - return metav1.ObjectMeta{} -} - -func (in *mockConsulResource) GetObjectKind() schema.ObjectKind { - return schema.EmptyObjectKind -} - -func (in *mockConsulResource) DeepCopyObject() runtime.Object { - return in -} - -func (in *mockConsulResource) AddFinalizer(_ string) {} - -func (in *mockConsulResource) RemoveFinalizer(_ string) {} - -func (in *mockConsulResource) Finalizers() []string { - return nil -} - -func (in *mockConsulResource) KubeKind() string { - return "mockkind" -} - -func (in *mockConsulResource) SetSyncedCondition(_ corev1.ConditionStatus, _ string, _ string) {} - -func (in *mockConsulResource) SetLastSyncedTime(_ *metav1.Time) {} - -func (in *mockConsulResource) SyncedCondition() (status corev1.ConditionStatus, reason string, message string) { - return corev1.ConditionTrue, "", "" -} - -func (in *mockConsulResource) SyncedConditionStatus() corev1.ConditionStatus { - return corev1.ConditionTrue -} - -func (in *mockConsulResource) Validate(_ ConsulTenancyConfig) error { - if !in.Valid { - return errors.New("invalid") - } - return nil -} - -func (in *mockConsulResource) DefaultNamespaceFields(_ ConsulTenancyConfig) { - in.MockNamespace = "bar" -} - -func (in *mockConsulResource) MatchesConsul(_ *pbresource.Resource, _, _ string) bool { - return false -} diff --git a/control-plane/api/mesh/v2beta1/api_gateway_types.go b/control-plane/api/mesh/v2beta1/api_gateway_types.go deleted file mode 100644 index 60b94019d3..0000000000 --- a/control-plane/api/mesh/v2beta1/api_gateway_types.go +++ /dev/null @@ -1,169 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - apiGatewayKubeKind = "gateway" -) - -func init() { - MeshSchemeBuilder.Register(&GatewayClass{}, &GatewayClassList{}, &APIGateway{}, &APIGatewayList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// APIGateway is the Schema for the API Gateway -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:scope=Cluster -type APIGateway struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.APIGateway `json:"spec,omitempty"` - APIGatewayStatus `json:"status,omitempty"` -} - -type APIGatewayStatus struct { - Status - Addresses []GatewayAddress - Listeners []ListenerStatus -} - -type ListenerStatus struct { - Status - Name string `json:"name"` - AttachedRoutes int32 `json:"attachedRoutes"` -} - -type GatewayAddress struct { - // +kubebuilder:default=IPAddress - Type string `json:"type"` - Value string `json:"value"` -} - -// +kubebuilder:object:root=true - -// APIGatewayList contains a list of APIGateway. -type APIGatewayList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*APIGateway `json:"items"` -} - -func (in *APIGateway) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.APIGatewayType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *APIGateway) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *APIGateway) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *APIGateway) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *APIGateway) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *APIGateway) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *APIGateway) KubeKind() string { - return apiGatewayKubeKind -} - -func (in *APIGateway) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *APIGateway) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *APIGateway) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *APIGateway) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *APIGateway) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *APIGateway) Validate(tenancy common.ConsulTenancyConfig) error { - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *APIGateway) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go b/control-plane/api/mesh/v2beta1/gateway_class_config_types.go deleted file mode 100644 index dc3499b10c..0000000000 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ /dev/null @@ -1,169 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func init() { - MeshSchemeBuilder.Register(&GatewayClassConfig{}, &GatewayClassConfigList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// GatewayClassConfig is the Schema for the Mesh Gateway API -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:scope=Cluster -type GatewayClassConfig struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec GatewayClassConfigSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen=true - -// GatewayClassConfigSpec specifies the desired state of the GatewayClassConfig CRD. -type GatewayClassConfigSpec struct { - GatewayClassAnnotationsAndLabels `json:",inline"` - - // Deployment contains config specific to the Deployment created from this GatewayClass - Deployment GatewayClassDeploymentConfig `json:"deployment,omitempty"` - // Role contains config specific to the Role created from this GatewayClass - Role GatewayClassRoleConfig `json:"role,omitempty"` - // RoleBinding contains config specific to the RoleBinding created from this GatewayClass - RoleBinding GatewayClassRoleBindingConfig `json:"roleBinding,omitempty"` - // Service contains config specific to the Service created from this GatewayClass - Service GatewayClassServiceConfig `json:"service,omitempty"` - // ServiceAccount contains config specific to the corev1.ServiceAccount created from this GatewayClass - ServiceAccount GatewayClassServiceAccountConfig `json:"serviceAccount,omitempty"` -} - -// GatewayClassDeploymentConfig specifies the desired state of the Deployment created from the GatewayClassConfig. -type GatewayClassDeploymentConfig struct { - GatewayClassAnnotationsAndLabels `json:",inline"` - - // Container contains config specific to the created Deployment's container. - Container *GatewayClassContainerConfig `json:"container,omitempty"` - // InitContainer contains config specific to the created Deployment's init container. - InitContainer *GatewayClassInitContainerConfig `json:"initContainer,omitempty"` - // NodeSelector is a feature that constrains the scheduling of a pod to nodes that - // match specified labels. - // By defining NodeSelector in a pod's configuration, you can ensure that the pod is - // only scheduled to nodes with the corresponding labels, providing a way to - // influence the placement of workloads based on node attributes. - // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - // PriorityClassName specifies the priority class name to use on the created Deployment. - PriorityClassName string `json:"priorityClassName,omitempty"` - // Replicas specifies the configuration to control the number of replicas for the created Deployment. - Replicas *GatewayClassReplicasConfig `json:"replicas,omitempty"` - // SecurityContext specifies the security context for the created Deployment's Pod. - SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` - // Tolerations specifies the tolerations to use on the created Deployment. - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - // HostNetwork specifies whether the gateway pods should run on the host network. - HostNetwork bool `json:"hostNetwork,omitempty"` - // TopologySpreadConstraints is a feature that controls how pods are spead across your topology. - // More info: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ - TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` - // DNSPolicy specifies the dns policy to use. These are set on a per pod basis. - // +kubebuilder:validation:Enum=Default;ClusterFirst;ClusterFirstWithHostNet;None - DNSPolicy corev1.DNSPolicy `json:"dnsPolicy,omitempty"` - // Affinity specifies the affinity to use on the created Deployment. - Affinity *corev1.Affinity `json:"affinity,omitempty"` -} - -type GatewayClassReplicasConfig struct { - // Default is the number of replicas assigned to the Deployment when created - Default *int32 `json:"default,omitempty"` - // Min is the minimum number of replicas allowed for a gateway with this class. - // If the replica count drops below this value due to manual or automated scaling, - // the replica count will be restored to this value. - Min *int32 `json:"min,omitempty"` - // Max is the maximum number of replicas allowed for a gateway with this class. - // If the replica count exceeds this value due to manual or automated scaling, - // the replica count will be restored to this value. - Max *int32 `json:"max,omitempty"` -} - -type GatewayClassInitContainerConfig struct { - // Consul specifies configuration for the consul-k8s-control-plane init container - Consul GatewayClassConsulConfig `json:"consul,omitempty"` - // Resources specifies the resource requirements for the created Deployment's init container - Resources *corev1.ResourceRequirements `json:"resources,omitempty"` -} - -type GatewayClassContainerConfig struct { - // Consul specifies configuration for the consul-dataplane container - Consul GatewayClassConsulConfig `json:"consul,omitempty"` - // Resources specifies the resource requirements for the created Deployment's container - Resources *corev1.ResourceRequirements `json:"resources,omitempty"` - // PortModifier specifies the value to be added to every port value for listeners on this gateway. - // This is generally used to avoid binding to privileged ports in the container. - PortModifier int32 `json:"portModifier,omitempty"` - // HostPort specifies a port to be exposed to the external host network - HostPort int32 `json:"hostPort,omitempty"` -} - -type GatewayClassRoleConfig struct { - GatewayClassAnnotationsAndLabels `json:",inline"` -} - -type GatewayClassRoleBindingConfig struct { - GatewayClassAnnotationsAndLabels `json:",inline"` -} - -type GatewayClassServiceConfig struct { - GatewayClassAnnotationsAndLabels `json:",inline"` - - // Type specifies the type of Service to use (LoadBalancer, ClusterIP, etc.) - // +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer - Type *corev1.ServiceType `json:"type,omitempty"` -} - -type GatewayClassServiceAccountConfig struct { - GatewayClassAnnotationsAndLabels `json:",inline"` -} - -type GatewayClassConsulConfig struct { - // Logging specifies the logging configuration for Consul Dataplane - Logging GatewayClassConsulLoggingConfig `json:"logging,omitempty"` -} - -type GatewayClassConsulLoggingConfig struct { - // Level sets the logging level for Consul Dataplane (debug, info, etc.) - Level string `json:"level,omitempty"` -} - -// GatewayClassAnnotationsAndLabels exists to provide a commonly-embedded wrapper -// for Annotations and Labels on a given resource configuration. -type GatewayClassAnnotationsAndLabels struct { - // Annotations are applied to the created resource - Annotations GatewayClassAnnotationsLabelsConfig `json:"annotations,omitempty"` - // Labels are applied to the created resource - Labels GatewayClassAnnotationsLabelsConfig `json:"labels,omitempty"` -} - -type GatewayClassAnnotationsLabelsConfig struct { - // InheritFromGateway lists the names/keys of annotations or labels to copy from the Gateway resource. - // Any name/key included here will override those in Set if specified on the Gateway. - InheritFromGateway []string `json:"inheritFromGateway,omitempty"` - // Set lists the names/keys and values of annotations or labels to set on the resource. - // Any name/key included here will be overridden if present in InheritFromGateway and set on the Gateway. - Set map[string]string `json:"set,omitempty"` -} - -// +kubebuilder:object:root=true - -// GatewayClassConfigList contains a list of GatewayClassConfig. -type GatewayClassConfigList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*GatewayClassConfig `json:"items"` -} diff --git a/control-plane/api/mesh/v2beta1/gateway_class_types.go b/control-plane/api/mesh/v2beta1/gateway_class_types.go deleted file mode 100644 index fac8535ae5..0000000000 --- a/control-plane/api/mesh/v2beta1/gateway_class_types.go +++ /dev/null @@ -1,151 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - gatewayClassKubeKind = "gatewayclass" -) - -func init() { - MeshSchemeBuilder.Register(&GatewayClass{}, &GatewayClassList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// GatewayClass is the Schema for the Gateway Class API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:scope=Cluster -type GatewayClass struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.GatewayClass `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// GatewayClassList contains a list of GatewayClass. -type GatewayClassList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*GatewayClass `json:"items"` -} - -func (in *GatewayClass) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.GatewayClassType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *GatewayClass) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *GatewayClass) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *GatewayClass) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *GatewayClass) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *GatewayClass) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *GatewayClass) KubeKind() string { - return gatewayClassKubeKind -} - -func (in *GatewayClass) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *GatewayClass) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *GatewayClass) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *GatewayClass) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *GatewayClass) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *GatewayClass) Validate(tenancy common.ConsulTenancyConfig) error { - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *GatewayClass) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/grpc_route_types.go b/control-plane/api/mesh/v2beta1/grpc_route_types.go deleted file mode 100644 index 44a8949e1d..0000000000 --- a/control-plane/api/mesh/v2beta1/grpc_route_types.go +++ /dev/null @@ -1,327 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - grpcRouteKubeKind = "grpcroute" -) - -func init() { - MeshSchemeBuilder.Register(&GRPCRoute{}, &GRPCRouteList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// GRPCRoute is the Schema for the GRPC Route API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="grpc-route" -type GRPCRoute struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.GRPCRoute `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// GRPCRouteList contains a list of GRPCRoute. -type GRPCRouteList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*GRPCRoute `json:"items"` -} - -func (in *GRPCRoute) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.GRPCRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *GRPCRoute) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *GRPCRoute) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *GRPCRoute) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *GRPCRoute) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *GRPCRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *GRPCRoute) KubeKind() string { - return grpcRouteKubeKind -} - -func (in *GRPCRoute) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *GRPCRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *GRPCRoute) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *GRPCRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *GRPCRoute) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *GRPCRoute) Validate(tenancy common.ConsulTenancyConfig) error { - var errs field.ErrorList - var route pbmesh.GRPCRoute - path := field.NewPath("spec") - - res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) - - if err := res.Data.UnmarshalTo(&route); err != nil { - return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) - } - - if len(route.ParentRefs) == 0 { - errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) - } - - if len(route.Hostnames) > 0 { - errs = append(errs, field.Invalid(path.Child("hostnames"), route.Hostnames, "should not populate hostnames")) - } - - for i, rule := range route.Rules { - rulePath := path.Child("rules").Index(i) - for j, match := range rule.Matches { - ruleMatchPath := rulePath.Child("matches").Index(j) - if match.Method != nil { - switch match.Method.Type { - case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: - errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("type"), match.Method.Type, "missing required field")) - case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT: - case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_REGEX: - default: - errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("type"), match.Method.Type, fmt.Sprintf("not a supported enum value: %v", match.Method.Type))) - } - if match.Method.Service == "" && match.Method.Method == "" { - errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("service"), match.Method.Service, "at least one of \"service\" or \"method\" must be set")) - } - } - - for k, header := range match.Headers { - ruleHeaderPath := ruleMatchPath.Child("headers").Index(k) - if err := validateHeaderMatchType(header.Type); err != nil { - errs = append(errs, field.Invalid(ruleHeaderPath.Child("type"), header.Type, err.Error())) - } - - if header.Name == "" { - errs = append(errs, field.Required(ruleHeaderPath.Child("name"), "missing required field")) - } - } - } - - for j, filter := range rule.Filters { - set := 0 - if filter.RequestHeaderModifier != nil { - set++ - } - if filter.ResponseHeaderModifier != nil { - set++ - } - if filter.UrlRewrite != nil { - set++ - if filter.UrlRewrite.PathPrefix == "" { - errs = append(errs, field.Required(rulePath.Child("filters").Index(j).Child("urlRewrite").Child("pathPrefix"), "field should not be empty if enclosing section is set")) - } - } - if set != 1 { - errs = append(errs, field.Invalid(rulePath.Child("filters").Index(j), filter, "exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required")) - } - } - - if len(rule.BackendRefs) == 0 { - errs = append(errs, field.Required(rulePath.Child("backendRefs"), "missing required field")) - } - for j, hbref := range rule.BackendRefs { - ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) - if hbref.BackendRef == nil { - errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) - continue - } - - if hbref.BackendRef.Datacenter != "" { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) - } - - if len(hbref.Filters) > 0 { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("filters"), hbref.Filters, "filters are not supported at this level yet")) - } - } - - if rule.Timeouts != nil { - errs = append(errs, validateHTTPTimeouts(rule.Timeouts, rulePath.Child("timeouts"))...) - } - if rule.Retries != nil { - errs = append(errs, validateHTTPRetries(rule.Retries, rulePath.Child("retries"))...) - } - } - - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: MeshGroup, Kind: common.GRPCRoute}, - in.KubernetesName(), errs) - } - return nil -} - -func validateHeaderMatchType(typ pbmesh.HeaderMatchType) error { - switch typ { - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED: - return fmt.Errorf("missing required field") - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_EXACT: - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_REGEX: - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PRESENT: - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX: - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_SUFFIX: - default: - return fmt.Errorf("not a supported enum value: %v", typ) - } - return nil -} - -func validateHTTPTimeouts(timeouts *pbmesh.HTTPRouteTimeouts, path *field.Path) field.ErrorList { - if timeouts == nil { - return nil - } - - var errs field.ErrorList - - if timeouts.Request != nil { - val := timeouts.Request.AsDuration() - if val < 0 { - errs = append(errs, field.Invalid(path.Child("request"), val, "timeout cannot be negative")) - } - } - if timeouts.Idle != nil { - val := timeouts.Idle.AsDuration() - if val < 0 { - errs = append(errs, field.Invalid(path.Child("idle"), val, "timeout cannot be negative")) - } - } - - return errs -} - -func validateHTTPRetries(retries *pbmesh.HTTPRouteRetries, path *field.Path) field.ErrorList { - if retries == nil { - return nil - } - - var errs field.ErrorList - - for i, condition := range retries.OnConditions { - if !isValidRetryCondition(condition) { - errs = append(errs, field.Invalid(path.Child("onConditions").Index(i), condition, "not a valid retry condition")) - } - } - - return errs -} - -func isValidRetryCondition(retryOn string) bool { - switch retryOn { - case "5xx", - "gateway-error", - "reset", - "connect-failure", - "envoy-ratelimited", - "retriable-4xx", - "refused-stream", - "cancelled", - "deadline-exceeded", - "internal", - "resource-exhausted", - "unavailable": - return true - default: - return false - } -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *GRPCRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/grpc_route_types_test.go b/control-plane/api/mesh/v2beta1/grpc_route_types_test.go deleted file mode 100644 index 69695284d0..0000000000 --- a/control-plane/api/mesh/v2beta1/grpc_route_types_test.go +++ /dev/null @@ -1,1201 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestGRPCRoute_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *GRPCRoute - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbmesh.GRPCRoute - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.GRPCRoute{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbmesh.GRPCRoute{}, - Matches: true, - }, - "hostnames are compared": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.GRPCRoute{ - Hostnames: []string{ - "not-a-hostname", "another-hostname", - }, - }, - Matches: false, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.GRPCRoute{ - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.GRPCRoute{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructGRPCRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestGRPCRoute_Resource also includes test to verify ResourceID(). -func TestGRPCRoute_Resource(t *testing.T) { - cases := map[string]struct { - Ours *GRPCRoute - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbmesh.GRPCRoute - }{ - "empty fields": { - Ours: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbmesh.GRPCRoute{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbmesh.GRPCRoute{}, - }, - "every field set": { - Ours: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbmesh.GRPCRoute{ - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructGRPCRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "GRPCRoute do not match") - }) - } -} - -func TestGRPCRoute_SetSyncedCondition(t *testing.T) { - trafficPermissions := &GRPCRoute{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestGRPCRoute_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &GRPCRoute{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestGRPCRoute_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &GRPCRoute{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestGRPCRoute_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&GRPCRoute{}).GetCondition(ConditionSynced)) -} - -func TestGRPCRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&GRPCRoute{}).SyncedConditionStatus()) -} - -func TestGRPCRoute_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&GRPCRoute{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestGRPCRoute_KubeKind(t *testing.T) { - require.Equal(t, "grpcroute", (&GRPCRoute{}).KubeKind()) -} - -func TestGRPCRoute_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbmesh.GRPCRoute{}, - }).KubernetesName()) -} - -func TestGRPCRoute_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &GRPCRoute{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestGRPCRoute_DefaultNamespaceFields(t *testing.T) - -func TestGRPCRoute_Validate(t *testing.T) { - cases := []struct { - name string - input *GRPCRoute - expectedErrMsgs []string - }{ - { - name: "kitchen sink OK", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "5xx", "resource-exhausted", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: nil, - }, - { - name: "empty parentRefs", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{}, - }, - }, - expectedErrMsgs: []string{ - `spec.parentRefs: Required value: cannot be empty`, - }, - }, - { - name: "populated hostnames", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{"a-hostname"}, - }, - }, - expectedErrMsgs: []string{ - `spec.hostnames: Invalid value: []string{"a-hostname"}: should not populate hostnames`, - }, - }, - { - name: "rules.matches.method", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_UNSPECIFIED, - Service: "test-service", - Method: "GET", - }, - }, { - Method: &pbmesh.GRPCMethodMatch{ - Service: "test-service", - Method: "GET", - }, - }, { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - }, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].matches[0].method.type: Invalid value: GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[1].method.type: Invalid value: GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[2].method.service: Invalid value: "": at least one of "service" or "method" must be set`, - }, - }, - { - name: "rules.matches.headers", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED, - Name: "test-header", - Value: "header-value", - }, - { - Name: "test-header", - Value: "header-value", - }, - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Value: "header-value", - }, - }, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].matches[0].headers[0].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[0].headers[1].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[0].headers[2].name: Required value: missing required field`, - }, - }, - { - name: "rules.filters", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "", - }, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].filters[0].urlRewrite.pathPrefix: Required value: field should not be empty if enclosing section is set`, - `exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required`, - }, - }, - { - name: "missing backendRefs", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - BackendRefs: []*pbmesh.GRPCBackendRef{}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs: Required value: missing required field`, - }, - }, - { - name: "rules.backendRefs", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - Weight: 50, - }, - { - BackendRef: &pbmesh.BackendReference{ - Datacenter: "wrong-datacenter", - Port: "21000", - }, - Weight: 50, - }, - { - BackendRef: &pbmesh.BackendReference{ - Port: "21000", - }, - Filters: []*pbmesh.GRPCRouteFilter{{}}, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs[0].backendRef: Required value: missing required field`, - `spec.rules[0].backendRefs[1].backendRef.datacenter: Invalid value: "wrong-datacenter": datacenter is not yet supported on backend refs`, - `filters are not supported at this level yet`, - }, - }, - { - name: "rules.timeout", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: -9, - Nanos: -10, - }, - Idle: &durationpb.Duration{ - Seconds: -2, - Nanos: -3, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].timeouts.request: Invalid value: -9.00000001s: timeout cannot be negative`, - `spec.rules[0].timeouts.idle: Invalid value: -2.000000003s: timeout cannot be negative`, - }, - }, - { - name: "rules.retries", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Retries: &pbmesh.HTTPRouteRetries{ - OnConditions: []string{"invalid-condition", "another-invalid-condition", "internal"}, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].retries.onConditions[0]: Invalid value: "invalid-condition": not a valid retry condition`, - `spec.rules[0].retries.onConditions[1]: Invalid value: "another-invalid-condition": not a valid retry condition`, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.input.Validate(common.ConsulTenancyConfig{}) - if len(tc.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range tc.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func constructGRPCRouteResource(tp *pbmesh.GRPCRoute, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.GRPCRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/mesh/v2beta1/grpc_route_webhook.go b/control-plane/api/mesh/v2beta1/grpc_route_webhook.go deleted file mode 100644 index b73388b4cf..0000000000 --- a/control-plane/api/mesh/v2beta1/grpc_route_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type GRPCRouteWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.ConsulResourceLister = &GRPCRouteWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-grpcroute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=grpcroute,versions=v2beta1,name=mutate-grpcroute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *GRPCRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource GRPCRoute - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *GRPCRouteWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { - var resourceList GRPCRouteList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.ConsulResource - for _, item := range resourceList.Items { - entries = append(entries, common.ConsulResource(item)) - } - return entries, nil -} - -func (v *GRPCRouteWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/mesh/v2beta1/http_route_types.go b/control-plane/api/mesh/v2beta1/http_route_types.go deleted file mode 100644 index 7ea1b97936..0000000000 --- a/control-plane/api/mesh/v2beta1/http_route_types.go +++ /dev/null @@ -1,309 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - "net/http" - "strings" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - httpRouteKubeKind = "httproute" -) - -func init() { - MeshSchemeBuilder.Register(&HTTPRoute{}, &HTTPRouteList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// HTTPRoute is the Schema for the HTTP Route API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="http-route" -type HTTPRoute struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.HTTPRoute `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// HTTPRouteList contains a list of HTTPRoute. -type HTTPRouteList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*HTTPRoute `json:"items"` -} - -func (in *HTTPRoute) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.HTTPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *HTTPRoute) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *HTTPRoute) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *HTTPRoute) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *HTTPRoute) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *HTTPRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *HTTPRoute) KubeKind() string { - return httpRouteKubeKind -} - -func (in *HTTPRoute) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *HTTPRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *HTTPRoute) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *HTTPRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *HTTPRoute) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *HTTPRoute) Validate(tenancy common.ConsulTenancyConfig) error { - var errs field.ErrorList - var route pbmesh.HTTPRoute - path := field.NewPath("spec") - - res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) - - if err := res.Data.UnmarshalTo(&route); err != nil { - return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) - } - - if len(route.ParentRefs) == 0 { - errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) - } - - if len(route.Hostnames) > 0 { - errs = append(errs, field.Invalid(path.Child("hostnames"), route.Hostnames, "should not populate hostnames")) - } - - for i, rule := range route.Rules { - rulePath := path.Child("rules").Index(i) - for j, match := range rule.Matches { - ruleMatchPath := rulePath.Child("matches").Index(j) - if match.Path != nil { - switch match.Path.Type { - case pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED: - errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("type"), pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED, "missing required field")) - case pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT: - if !strings.HasPrefix(match.Path.Value, "/") { - errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("value"), match.Path.Value, "exact patch value does not start with '/'")) - } - case pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX: - if !strings.HasPrefix(match.Path.Value, "/") { - errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("value"), match.Path.Value, "prefix patch value does not start with '/'")) - } - case pbmesh.PathMatchType_PATH_MATCH_TYPE_REGEX: - if match.Path.Value == "" { - errs = append(errs, field.Required(ruleMatchPath.Child("path").Child("value"), "missing required field")) - } - default: - errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("type"), match.Path, "not a supported enum value")) - } - } - - for k, hdr := range match.Headers { - if err := validateHeaderMatchType(hdr.Type); err != nil { - errs = append(errs, field.Invalid(ruleMatchPath.Child("headers").Index(k).Child("type"), hdr.Type, err.Error())) - } - - if hdr.Name == "" { - errs = append(errs, field.Required(ruleMatchPath.Child("headers").Index(k).Child("name"), "missing required field")) - } - } - - for k, qm := range match.QueryParams { - switch qm.Type { - case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_UNSPECIFIED: - errs = append(errs, field.Invalid(ruleMatchPath.Child("queryParams").Index(k).Child("type"), pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_UNSPECIFIED, "missing required field")) - case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_EXACT: - case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_REGEX: - case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT: - default: - errs = append(errs, field.Invalid(ruleMatchPath.Child("queryParams").Index(k).Child("type"), qm.Type, "not a supported enum value")) - } - - if qm.Name == "" { - errs = append(errs, field.Required(ruleMatchPath.Child("queryParams").Index(k).Child("name"), "missing required field")) - } - } - - if match.Method != "" && !isValidHTTPMethod(match.Method) { - errs = append(errs, field.Invalid(ruleMatchPath.Child("method"), match.Method, "not a valid http method")) - } - } - - var ( - hasReqMod bool - hasUrlRewrite bool - ) - for j, filter := range rule.Filters { - ruleFilterPath := path.Child("filters").Index(j) - set := 0 - if filter.RequestHeaderModifier != nil { - set++ - hasReqMod = true - } - if filter.ResponseHeaderModifier != nil { - set++ - } - if filter.UrlRewrite != nil { - set++ - hasUrlRewrite = true - if filter.UrlRewrite.PathPrefix == "" { - errs = append(errs, field.Invalid(ruleFilterPath.Child("urlRewrite").Child("pathPrefix"), filter.UrlRewrite.PathPrefix, "field should not be empty if enclosing section is set")) - } - } - if set != 1 { - errs = append(errs, field.Invalid(ruleFilterPath, filter, "exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required")) - } - } - - if hasReqMod && hasUrlRewrite { - errs = append(errs, field.Invalid(rulePath.Child("filters"), rule.Filters, "exactly one of request_header_modifier or url_rewrite can be set at a time")) - } - - if len(rule.BackendRefs) == 0 { - errs = append(errs, field.Required(rulePath.Child("backendRefs"), "missing required field")) - } - for j, hbref := range rule.BackendRefs { - ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) - if hbref.BackendRef == nil { - errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) - continue - } - - if hbref.BackendRef.Datacenter != "" { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) - } - - if len(hbref.Filters) > 0 { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("filters"), hbref.Filters, "filters are not supported at this level yet")) - } - } - - if rule.Timeouts != nil { - errs = append(errs, validateHTTPTimeouts(rule.Timeouts, rulePath.Child("timeouts"))...) - } - if rule.Retries != nil { - errs = append(errs, validateHTTPRetries(rule.Retries, rulePath.Child("retries"))...) - } - } - - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: MeshGroup, Kind: common.HTTPRoute}, - in.KubernetesName(), errs) - } - return nil -} - -func isValidHTTPMethod(method string) bool { - switch method { - case http.MethodGet, - http.MethodHead, - http.MethodPost, - http.MethodPut, - http.MethodPatch, - http.MethodDelete, - http.MethodConnect, - http.MethodOptions, - http.MethodTrace: - return true - default: - return false - } -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *HTTPRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/http_route_types_test.go b/control-plane/api/mesh/v2beta1/http_route_types_test.go deleted file mode 100644 index 824d7e35b7..0000000000 --- a/control-plane/api/mesh/v2beta1/http_route_types_test.go +++ /dev/null @@ -1,1338 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestHTTPRoute_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *HTTPRoute - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbmesh.HTTPRoute - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.HTTPRoute{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbmesh.HTTPRoute{}, - Matches: true, - }, - "hostnames are compared": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.HTTPRoute{ - Hostnames: []string{ - "not-a-hostname", "another-hostname", - }, - }, - Matches: false, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "exact-value", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20211", - Datacenter: "another-datacenter", - }, - Weight: 12, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "setting", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "adding", - }, - }, - Remove: []string{"removing"}, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "another-set-header", - Value: "setting", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "another-added-header", - Value: "adding", - }, - }, - Remove: []string{"also-removing"}, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "/prefixing-it", - }, - }, - }, - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "exact-value", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20211", - Datacenter: "another-datacenter", - }, - Weight: 12, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "setting", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "adding", - }, - }, - Remove: []string{"removing"}, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "another-set-header", - Value: "setting", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "another-added-header", - Value: "adding", - }, - }, - Remove: []string{"also-removing"}, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "/prefixing-it", - }, - }, - }, - }, - }, - }, - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.HTTPRoute{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructHTTPRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestHTTPRoute_Resource also includes test to verify ResourceID(). -func TestHTTPRoute_Resource(t *testing.T) { - cases := map[string]struct { - Ours *HTTPRoute - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbmesh.HTTPRoute - }{ - "empty fields": { - Ours: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbmesh.HTTPRoute{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbmesh.HTTPRoute{}, - }, - "every field set": { - Ours: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "exact-value", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "exact-value", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructHTTPRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "HTTPRoute do not match") - }) - } -} - -func TestHTTPRoute_SetSyncedCondition(t *testing.T) { - trafficPermissions := &HTTPRoute{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestHTTPRoute_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &HTTPRoute{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestHTTPRoute_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &HTTPRoute{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestHTTPRoute_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&HTTPRoute{}).GetCondition(ConditionSynced)) -} - -func TestHTTPRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&HTTPRoute{}).SyncedConditionStatus()) -} - -func TestHTTPRoute_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&HTTPRoute{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestHTTPRoute_KubeKind(t *testing.T) { - require.Equal(t, "httproute", (&HTTPRoute{}).KubeKind()) -} - -func TestHTTPRoute_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbmesh.HTTPRoute{}, - }).KubernetesName()) -} - -func TestHTTPRoute_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &HTTPRoute{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestHTTPRoute_DefaultNamespaceFields(t *testing.T) - -func TestHTTPRoute_Validate(t *testing.T) { - cases := []struct { - name string - input *HTTPRoute - expectedErrMsgs []string - }{ - { - name: "kitchen sink OK", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "/exactValue", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "reset", "cancelled", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "backend", - Section: "backend-section", - }, - Port: "20101", - }, - Weight: 15, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: nil, - }, - { - name: "missing parentRefs", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{}, - }, - }, - expectedErrMsgs: []string{ - `spec.parentRefs: Required value: cannot be empty`, - }, - }, - { - name: "hostnames created", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{"a-hostname", "another-hostname"}, - }, - }, - expectedErrMsgs: []string{ - `spec.hostnames: Invalid value: []string{"a-hostname", "another-hostname"}: should not populate hostnames`, - }, - }, - { - name: "rules.matches.path", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED, - }, - }, - { - Path: &pbmesh.HTTPPathMatch{}, - }, - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "does-not-have-/-prefix", - }, - }, - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, - Value: "does-not-have-/-prefix-either", - }, - }, - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_REGEX, - Value: "", - }, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].matches[0].path.type: Invalid value: PATH_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[1].path.type: Invalid value: PATH_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[2].path.value: Invalid value: "does-not-have-/-prefix": exact patch value does not start with '/'`, - `spec.rules[0].matches[3].path.value: Invalid value: "does-not-have-/-prefix-either": prefix patch value does not start with '/'`, - `spec.rules[0].matches[4].path.value: Required value: missing required field`, - }, - }, - { - name: "rules.matches.headers", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED, - Name: "test-header", - Value: "header-value", - }, - { - // Type: "", - Name: "test-header", - Value: "header-value", - }, - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_EXACT, - Name: "", - }, - }, - Method: "GET", - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].matches[0].headers[0].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[0].headers[1].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[0].headers[2].name: Required value: missing required field`, - }, - }, - { - name: "rules.filters", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - }, - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "prefix-1", - }, - }, - { - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "prefix-2", - }, - }, - { - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "", - }, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.filters[0]: Invalid value`, - `spec.filters[1]: Invalid value`, - `spec.filters[2]: Invalid value`, - `spec.filters[3].urlRewrite.pathPrefix: Invalid value: "": field should not be empty if enclosing section is set`, - `exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required`, - }, - }, - { - name: "rule.backendRefs", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - BackendRefs: []*pbmesh.HTTPBackendRef{}, - }, - { - BackendRefs: []*pbmesh.HTTPBackendRef{ - {}, - { - BackendRef: &pbmesh.BackendReference{ - Datacenter: "some-datacenter", - }, - }, - { - BackendRef: &pbmesh.BackendReference{}, - Filters: []*pbmesh.HTTPRouteFilter{ - { - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "/prefixed", - }, - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs: Required value: missing required field`, - `spec.rules[1].backendRefs[0].backendRef: Required value: missing required field`, - `spec.rules[1].backendRefs[1].backendRef.datacenter: Invalid value: "some-datacenter": datacenter is not yet supported on backend refs`, - `spec.rules[1].backendRefs[2].filters: Invalid value`, - `filters are not supported at this level yet`, - }, - }, - { - name: "rules.timeouts", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: -10, - Nanos: -5, - }, - Idle: &durationpb.Duration{ - Seconds: -5, - Nanos: -10, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].timeouts.request: Invalid value: -10.000000005s: timeout cannot be negative`, - `spec.rules[0].timeouts.idle: Invalid value: -5.00000001s: timeout cannot be negative`, - }, - }, - { - name: "rules.timeouts", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Retries: &pbmesh.HTTPRouteRetries{ - OnConditions: []string{ - "invalid-condition", "another-invalid-condition", - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].retries.onConditions[0]: Invalid value: "invalid-condition": not a valid retry condition`, - `spec.rules[0].retries.onConditions[1]: Invalid value: "another-invalid-condition": not a valid retry condition`, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.input.Validate(common.ConsulTenancyConfig{}) - if len(tc.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range tc.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func constructHTTPRouteResource(tp *pbmesh.HTTPRoute, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.HTTPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/mesh/v2beta1/http_route_webhook.go b/control-plane/api/mesh/v2beta1/http_route_webhook.go deleted file mode 100644 index 323db0c74a..0000000000 --- a/control-plane/api/mesh/v2beta1/http_route_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type HTTPRouteWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.ConsulResourceLister = &HTTPRouteWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-httproute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=httproute,versions=v2beta1,name=mutate-httproute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *HTTPRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource HTTPRoute - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *HTTPRouteWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { - var resourceList HTTPRouteList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.ConsulResource - for _, item := range resourceList.Items { - entries = append(entries, common.ConsulResource(item)) - } - return entries, nil -} - -func (v *HTTPRouteWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/mesh/v2beta1/mesh_configuration_types.go b/control-plane/api/mesh/v2beta1/mesh_configuration_types.go deleted file mode 100644 index 4bfa41b2bf..0000000000 --- a/control-plane/api/mesh/v2beta1/mesh_configuration_types.go +++ /dev/null @@ -1,150 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - meshConfigurationKind = "meshconfiguration" -) - -func init() { - MeshSchemeBuilder.Register(&MeshConfiguration{}, &MeshConfigurationList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// MeshConfiguration is the Schema for the Mesh Configuration -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:scope=Cluster -type MeshConfiguration struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.MeshConfiguration `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// MeshConfigurationList contains a list of MeshConfiguration. -type MeshConfigurationList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*MeshConfiguration `json:"items"` -} - -func (in *MeshConfiguration) ResourceID(_, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.MeshConfigurationType, - Tenancy: &pbresource.Tenancy{ - // we don't pass a namespace here because MeshConfiguration is partition-scoped - Partition: partition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *MeshConfiguration) Resource(_, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID("", partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *MeshConfiguration) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *MeshConfiguration) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *MeshConfiguration) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *MeshConfiguration) MatchesConsul(candidate *pbresource.Resource, _, partition string) bool { - return cmp.Equal( - in.Resource("", partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *MeshConfiguration) KubeKind() string { - return meshConfigurationKind -} - -func (in *MeshConfiguration) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *MeshConfiguration) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *MeshConfiguration) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *MeshConfiguration) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *MeshConfiguration) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *MeshConfiguration) Validate(tenancy common.ConsulTenancyConfig) error { - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *MeshConfiguration) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go b/control-plane/api/mesh/v2beta1/mesh_gateway_types.go deleted file mode 100644 index e8bf31cfe3..0000000000 --- a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go +++ /dev/null @@ -1,152 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - meshGatewayKubeKind = "meshgateway" -) - -func init() { - MeshSchemeBuilder.Register(&MeshGateway{}, &MeshGatewayList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// MeshGateway is the Schema for the Mesh Gateway API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:scope="Namespaced" -type MeshGateway struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.MeshGateway `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// MeshGatewayList contains a list of MeshGateway. -type MeshGatewayList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*MeshGateway `json:"items"` -} - -func (in *MeshGateway) ResourceID(_, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.MeshGatewayType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: "", // Namespace is always unset because MeshGateway is partition-scoped - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *MeshGateway) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *MeshGateway) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *MeshGateway) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *MeshGateway) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *MeshGateway) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *MeshGateway) KubeKind() string { - return meshGatewayKubeKind -} - -func (in *MeshGateway) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *MeshGateway) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *MeshGateway) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *MeshGateway) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *MeshGateway) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *MeshGateway) Validate(tenancy common.ConsulTenancyConfig) error { - // TODO add validation logic that ensures we only ever write this to the default namespace. - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *MeshGateway) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go b/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go deleted file mode 100644 index a9fe6a6a83..0000000000 --- a/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Package v2beta1 contains API Schema definitions for the consul.hashicorp.com v2beta1 API group -// +kubebuilder:object:generate=true -// +groupName=mesh.consul.hashicorp.com -package v2beta1 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var ( - - // MeshGroup is a collection of mesh resources. - MeshGroup = "mesh.consul.hashicorp.com" - - // MeshGroupVersion is group version used to register these objects. - MeshGroupVersion = schema.GroupVersion{Group: MeshGroup, Version: "v2beta1"} - - // MeshSchemeBuilder is used to add go types to the GroupVersionKind scheme. - MeshSchemeBuilder = &scheme.Builder{GroupVersion: MeshGroupVersion} - - // AddMeshToScheme adds the types in this group-version to the given scheme. - AddMeshToScheme = MeshSchemeBuilder.AddToScheme -) diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go b/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go deleted file mode 100644 index 8c210703ea..0000000000 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type ProxyConfigurationWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.ConsulResourceLister = &ProxyConfigurationWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-proxyconfiguration,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=proxyconfiguration,versions=v2beta1,name=mutate-proxyconfiguration.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *ProxyConfigurationWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource ProxyConfiguration - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *ProxyConfigurationWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { - var resourceList ProxyConfigurationList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.ConsulResource - for _, item := range resourceList.Items { - entries = append(entries, common.ConsulResource(item)) - } - return entries, nil -} - -func (v *ProxyConfigurationWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_types.go b/control-plane/api/mesh/v2beta1/proxy_configuration_types.go deleted file mode 100644 index 4df6ff95e1..0000000000 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_types.go +++ /dev/null @@ -1,160 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - proxyConfigurationKubeKind = "proxyconfiguration" -) - -func init() { - MeshSchemeBuilder.Register(&ProxyConfiguration{}, &ProxyConfigurationList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// ProxyConfiguration is the Schema for the TCP Routes API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="proxy-configuration" -type ProxyConfiguration struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.ProxyConfiguration `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// ProxyConfigurationList contains a list of ProxyConfiguration. -type ProxyConfigurationList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*ProxyConfiguration `json:"items"` -} - -func (in *ProxyConfiguration) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *ProxyConfiguration) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *ProxyConfiguration) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *ProxyConfiguration) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *ProxyConfiguration) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *ProxyConfiguration) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *ProxyConfiguration) KubeKind() string { - return proxyConfigurationKubeKind -} - -func (in *ProxyConfiguration) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *ProxyConfiguration) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *ProxyConfiguration) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *ProxyConfiguration) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *ProxyConfiguration) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *ProxyConfiguration) Validate(_ common.ConsulTenancyConfig) error { - var errs field.ErrorList - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: MeshGroup, Kind: common.ProxyConfiguration}, - in.KubernetesName(), errs) - } - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *ProxyConfiguration) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go b/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go deleted file mode 100644 index 67ad339697..0000000000 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go +++ /dev/null @@ -1,551 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestProxyConfiguration_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *ProxyConfiguration - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbmesh.ProxyConfiguration - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.ProxyConfiguration{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbmesh.ProxyConfiguration{}, - Matches: true, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"prefix-1", "prefix-2"}, - Names: []string{"workload-name"}, - Filter: "first-filter", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: 2, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 1234, - DialedDirectly: true, - }, - MutualTlsMode: 1, - LocalConnection: map[string]*pbmesh.ConnectionConfig{ - "local": { - ConnectTimeout: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - RequestTimeout: &durationpb.Duration{ - Seconds: 2, - Nanos: 10, - }, - }, - }, - InboundConnections: &pbmesh.InboundConnectionsConfig{ - MaxInboundConnections: 5, - BalanceInboundConnections: 10, - }, - MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 19000, - Path: "/expose-path", - LocalPathPort: 1901, - Protocol: 2, - }, - }, - }, - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: 3, - Path: "/path", - JsonFormat: "jsonFormat", - TextFormat: "text format.", - }, - PublicListenerJson: "publicListenerJson{}", - ListenerTracingJson: "listenerTracingJson{}", - LocalClusterJson: "localClusterJson{}", - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsdUrl: "statsdURL", - DogstatsdUrl: "dogstatsdURL", - StatsTags: []string{"statsTags"}, - PrometheusBindAddr: "firstBindAddr", - StatsBindAddr: "secondBindAddr", - ReadyBindAddr: "thirdBindAddr", - OverrideJsonTpl: "overrideJSON", - StaticClustersJson: "staticClusterJSON", - StaticListenersJson: "staticListenersJSON", - StatsSinksJson: "statsSinksJSON", - StatsConfigJson: "statsConfigJSON", - StatsFlushInterval: "45s", - TracingConfigJson: "tracingConfigJSON", - TelemetryCollectorBindSocketDir: "/bindSocketDir", - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"prefix-1", "prefix-2"}, - Names: []string{"workload-name"}, - Filter: "first-filter", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: 2, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 1234, - DialedDirectly: true, - }, - MutualTlsMode: 1, - LocalConnection: map[string]*pbmesh.ConnectionConfig{ - "local": { - ConnectTimeout: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - RequestTimeout: &durationpb.Duration{ - Seconds: 2, - Nanos: 10, - }, - }, - }, - InboundConnections: &pbmesh.InboundConnectionsConfig{ - MaxInboundConnections: 5, - BalanceInboundConnections: 10, - }, - MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 19000, - Path: "/expose-path", - LocalPathPort: 1901, - Protocol: 2, - }, - }, - }, - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: 3, - Path: "/path", - JsonFormat: "jsonFormat", - TextFormat: "text format.", - }, - PublicListenerJson: "publicListenerJson{}", - ListenerTracingJson: "listenerTracingJson{}", - LocalClusterJson: "localClusterJson{}", - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsdUrl: "statsdURL", - DogstatsdUrl: "dogstatsdURL", - StatsTags: []string{"statsTags"}, - PrometheusBindAddr: "firstBindAddr", - StatsBindAddr: "secondBindAddr", - ReadyBindAddr: "thirdBindAddr", - OverrideJsonTpl: "overrideJSON", - StaticClustersJson: "staticClusterJSON", - StaticListenersJson: "staticListenersJSON", - StatsSinksJson: "statsSinksJSON", - StatsConfigJson: "statsConfigJSON", - StatsFlushInterval: "45s", - TracingConfigJson: "tracingConfigJSON", - TelemetryCollectorBindSocketDir: "/bindSocketDir", - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.ProxyConfiguration{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.TCPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructProxyConfigurationResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestProxyConfiguration_Resource also includes test to verify ResourceID(). -func TestProxyConfiguration_Resource(t *testing.T) { - cases := map[string]struct { - Ours *ProxyConfiguration - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbmesh.ProxyConfiguration - }{ - "empty fields": { - Ours: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbmesh.ProxyConfiguration{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbmesh.ProxyConfiguration{}, - }, - "every field set": { - Ours: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"prefix-1", "prefix-2"}, - Names: []string{"workload-name"}, - Filter: "first-filter", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: 2, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 1234, - DialedDirectly: true, - }, - MutualTlsMode: 1, - LocalConnection: map[string]*pbmesh.ConnectionConfig{ - "local": { - ConnectTimeout: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - RequestTimeout: &durationpb.Duration{ - Seconds: 2, - Nanos: 10, - }, - }, - }, - InboundConnections: &pbmesh.InboundConnectionsConfig{ - MaxInboundConnections: 5, - BalanceInboundConnections: 10, - }, - MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 19000, - Path: "/expose-path", - LocalPathPort: 1901, - Protocol: 2, - }, - }, - }, - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: 3, - Path: "/path", - JsonFormat: "jsonFormat", - TextFormat: "text format.", - }, - PublicListenerJson: "publicListenerJson{}", - ListenerTracingJson: "listenerTracingJson{}", - LocalClusterJson: "localClusterJson{}", - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsdUrl: "statsdURL", - DogstatsdUrl: "dogstatsdURL", - StatsTags: []string{"statsTags"}, - PrometheusBindAddr: "firstBindAddr", - StatsBindAddr: "secondBindAddr", - ReadyBindAddr: "thirdBindAddr", - OverrideJsonTpl: "overrideJSON", - StaticClustersJson: "staticClusterJSON", - StaticListenersJson: "staticListenersJSON", - StatsSinksJson: "statsSinksJSON", - StatsConfigJson: "statsConfigJSON", - StatsFlushInterval: "45s", - TracingConfigJson: "tracingConfigJSON", - TelemetryCollectorBindSocketDir: "/bindSocketDir", - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"prefix-1", "prefix-2"}, - Names: []string{"workload-name"}, - Filter: "first-filter", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: 2, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 1234, - DialedDirectly: true, - }, - MutualTlsMode: 1, - LocalConnection: map[string]*pbmesh.ConnectionConfig{ - "local": { - ConnectTimeout: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - RequestTimeout: &durationpb.Duration{ - Seconds: 2, - Nanos: 10, - }, - }, - }, - InboundConnections: &pbmesh.InboundConnectionsConfig{ - MaxInboundConnections: 5, - BalanceInboundConnections: 10, - }, - MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 19000, - Path: "/expose-path", - LocalPathPort: 1901, - Protocol: 2, - }, - }, - }, - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: 3, - Path: "/path", - JsonFormat: "jsonFormat", - TextFormat: "text format.", - }, - PublicListenerJson: "publicListenerJson{}", - ListenerTracingJson: "listenerTracingJson{}", - LocalClusterJson: "localClusterJson{}", - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsdUrl: "statsdURL", - DogstatsdUrl: "dogstatsdURL", - StatsTags: []string{"statsTags"}, - PrometheusBindAddr: "firstBindAddr", - StatsBindAddr: "secondBindAddr", - ReadyBindAddr: "thirdBindAddr", - OverrideJsonTpl: "overrideJSON", - StaticClustersJson: "staticClusterJSON", - StaticListenersJson: "staticListenersJSON", - StatsSinksJson: "statsSinksJSON", - StatsConfigJson: "statsConfigJSON", - StatsFlushInterval: "45s", - TracingConfigJson: "tracingConfigJSON", - TelemetryCollectorBindSocketDir: "/bindSocketDir", - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructProxyConfigurationResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "ProxyConfiguration do not match") - }) - } -} - -func TestProxyConfiguration_SetSyncedCondition(t *testing.T) { - trafficPermissions := &ProxyConfiguration{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestProxyConfiguration_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &ProxyConfiguration{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestProxyConfiguration_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &ProxyConfiguration{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestProxyConfiguration_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&ProxyConfiguration{}).GetCondition(ConditionSynced)) -} - -func TestProxyConfiguration_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&ProxyConfiguration{}).SyncedConditionStatus()) -} - -func TestProxyConfiguration_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&ProxyConfiguration{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestProxyConfiguration_KubeKind(t *testing.T) { - require.Equal(t, "proxyconfiguration", (&ProxyConfiguration{}).KubeKind()) -} - -func TestProxyConfiguration_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbmesh.ProxyConfiguration{}, - }).KubernetesName()) -} - -func TestProxyConfiguration_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &ProxyConfiguration{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestProxyConfiguration_DefaultNamespaceFields(t *testing.T) - -func constructProxyConfigurationResource(tp *pbmesh.ProxyConfiguration, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/mesh/v2beta1/shared_types.go b/control-plane/api/mesh/v2beta1/shared_types.go deleted file mode 100644 index a5225afb71..0000000000 --- a/control-plane/api/mesh/v2beta1/shared_types.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -func meshConfigMeta() map[string]string { - return map[string]string{ - common.SourceKey: common.SourceValue, - } -} diff --git a/control-plane/api/mesh/v2beta1/status.go b/control-plane/api/mesh/v2beta1/status.go deleted file mode 100644 index cc75a1cd82..0000000000 --- a/control-plane/api/mesh/v2beta1/status.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Conditions is the schema for the conditions portion of the payload. -type Conditions []Condition - -// ConditionType is a camel-cased condition type. -type ConditionType string - -const ( - // ConditionSynced specifies that the resource has been synced with Consul. - ConditionSynced ConditionType = "Synced" -) - -// Conditions define a readiness condition for a Consul resource. -// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Condition struct { - // Type of condition. - // +required - Type ConditionType `json:"type" description:"type of status condition"` - - // Status of the condition, one of True, False, Unknown. - // +required - Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` - - // LastTransitionTime is the last time the condition transitioned from one status to another. - // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"` - - // The reason for the condition's last transition. - // +optional - Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` - - // A human readable message indicating details about the transition. - // +optional - Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` -} - -// IsTrue is true if the condition is True. -func (c *Condition) IsTrue() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionTrue -} - -// IsFalse is true if the condition is False. -func (c *Condition) IsFalse() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionFalse -} - -// IsUnknown is true if the condition is Unknown. -func (c *Condition) IsUnknown() bool { - if c == nil { - return true - } - return c.Status == corev1.ConditionUnknown -} - -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Status struct { - // Conditions indicate the latest available observations of a resource's current state. - // +optional - // +patchMergeKey=type - // +patchStrategy=merge - Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` - - // LastSyncedTime is the last time the resource successfully synced with Consul. - // +optional - LastSyncedTime *metav1.Time `json:"lastSyncedTime,omitempty" description:"last time the condition transitioned from one status to another"` -} - -func (s *Status) GetCondition(t ConditionType) *Condition { - for _, cond := range s.Conditions { - if cond.Type == t { - return &cond - } - } - return nil -} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_types.go b/control-plane/api/mesh/v2beta1/tcp_route_types.go deleted file mode 100644 index 207ef7afb3..0000000000 --- a/control-plane/api/mesh/v2beta1/tcp_route_types.go +++ /dev/null @@ -1,195 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - tcpRouteKubeKind = "tcproute" -) - -func init() { - MeshSchemeBuilder.Register(&TCPRoute{}, &TCPRouteList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// TCPRoute is the Schema for the TCP Route API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="tcp-route" -type TCPRoute struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.TCPRoute `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// TCPRouteList contains a list of TCPRoute. -type TCPRouteList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*TCPRoute `json:"items"` -} - -func (in *TCPRoute) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.TCPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *TCPRoute) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *TCPRoute) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *TCPRoute) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *TCPRoute) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *TCPRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *TCPRoute) KubeKind() string { - return tcpRouteKubeKind -} - -func (in *TCPRoute) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *TCPRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *TCPRoute) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *TCPRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *TCPRoute) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *TCPRoute) Validate(tenancy common.ConsulTenancyConfig) error { - var errs field.ErrorList - var route pbmesh.TCPRoute - path := field.NewPath("spec") - res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) - - if err := res.Data.UnmarshalTo(&route); err != nil { - return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) - } - - if len(route.ParentRefs) == 0 { - errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) - } - - if len(route.Rules) > 1 { - errs = append(errs, field.Invalid(path.Child("rules"), route.Rules, "must only specify a single rule for now")) - } - - for i, rule := range route.Rules { - rulePath := path.Child("rules").Index(i) - - if len(rule.BackendRefs) == 0 { - errs = append(errs, field.Required(rulePath.Child("backendRefs"), "cannot be empty")) - } - for j, hbref := range rule.BackendRefs { - ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) - if hbref.BackendRef == nil { - errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) - continue - } - - if hbref.BackendRef.Datacenter != "" { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) - } - } - } - - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: MeshGroup, Kind: common.TCPRoute}, - in.KubernetesName(), errs) - } - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *TCPRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_types_test.go b/control-plane/api/mesh/v2beta1/tcp_route_types_test.go deleted file mode 100644 index 9bedc58293..0000000000 --- a/control-plane/api/mesh/v2beta1/tcp_route_types_test.go +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/timestamppb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestTCPRoute_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *TCPRoute - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbmesh.TCPRoute - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.TCPRoute{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbmesh.TCPRoute{}, - Matches: true, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - Datacenter: "different-datacenter", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - Datacenter: "different-datacenter", - }, - Weight: 50, - }, - }, - }, - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.TCPRoute{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructTCPRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestTCPRoute_Resource also includes test to verify ResourceID(). -func TestTCPRoute_Resource(t *testing.T) { - cases := map[string]struct { - Ours *TCPRoute - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbmesh.TCPRoute - }{ - "empty fields": { - Ours: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbmesh.TCPRoute{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbmesh.TCPRoute{}, - }, - "every field set": { - Ours: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - Datacenter: "different-datacenter", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - Datacenter: "different-datacenter", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructTCPRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "TCPRoute do not match") - }) - } -} - -func TestTCPRoute_SetSyncedCondition(t *testing.T) { - trafficPermissions := &TCPRoute{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestTCPRoute_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &TCPRoute{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestTCPRoute_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &TCPRoute{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestTCPRoute_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&TCPRoute{}).GetCondition(ConditionSynced)) -} - -func TestTCPRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&TCPRoute{}).SyncedConditionStatus()) -} - -func TestTCPRoute_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&TCPRoute{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestTCPRoute_KubeKind(t *testing.T) { - require.Equal(t, "tcproute", (&TCPRoute{}).KubeKind()) -} - -func TestTCPRoute_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbmesh.TCPRoute{}, - }).KubernetesName()) -} - -func TestTCPRoute_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &TCPRoute{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestTCPRoute_DefaultNamespaceFields(t *testing.T) - -func TestTCPRoute_Validate(t *testing.T) { - cases := []struct { - name string - input *TCPRoute - expectedErrMsgs []string - }{ - { - name: "kitchen sink OK", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: nil, - }, - { - name: "no parentRefs", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{}, - }, - }, - expectedErrMsgs: []string{ - `spec.parentRefs: Required value: cannot be empty`, - }, - }, - { - name: "multiple rules", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{{}}, - Rules: []*pbmesh.TCPRouteRule{ - {BackendRefs: []*pbmesh.TCPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}}, - {BackendRefs: []*pbmesh.TCPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}}, - }, - }, - }, - expectedErrMsgs: []string{ - `must only specify a single rule for now`, - }, - }, - { - name: "rules.backendRefs", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{{}}, - Rules: []*pbmesh.TCPRouteRule{ - {BackendRefs: []*pbmesh.TCPBackendRef{}}, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs: Required value: cannot be empty`, - }, - }, - { - name: "rules.backendRefs.backendRef", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{{}}, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - {}, - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - }, - Datacenter: "backend-datacenter", - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs[0].backendRef: Required value: missing required field`, - `spec.rules[0].backendRefs[1].backendRef.datacenter: Invalid value: "backend-datacenter": datacenter is not yet supported on backend refs`, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.input.Validate(common.ConsulTenancyConfig{}) - if len(tc.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range tc.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func constructTCPRouteResource(tp *pbmesh.TCPRoute, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.TCPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_webhook.go b/control-plane/api/mesh/v2beta1/tcp_route_webhook.go deleted file mode 100644 index 4a7038276e..0000000000 --- a/control-plane/api/mesh/v2beta1/tcp_route_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type TCPRouteWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.ConsulResourceLister = &TCPRouteWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-tcproute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=tcproute,versions=v2beta1,name=mutate-tcproute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *TCPRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource TCPRoute - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateConsulResource(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *TCPRouteWebhook) List(ctx context.Context) ([]common.ConsulResource, error) { - var resourceList TCPRouteList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.ConsulResource - for _, item := range resourceList.Items { - entries = append(entries, common.ConsulResource(item)) - } - return entries, nil -} - -func (v *TCPRouteWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go deleted file mode 100644 index 44bec5db3c..0000000000 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,940 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Code generated by controller-gen. DO NOT EDIT. - -package v2beta1 - -import ( - "k8s.io/api/core/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIGateway) DeepCopyInto(out *APIGateway) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGateway. -func (in *APIGateway) DeepCopy() *APIGateway { - if in == nil { - return nil - } - out := new(APIGateway) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *APIGateway) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIGatewayList) DeepCopyInto(out *APIGatewayList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*APIGateway, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(APIGateway) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGatewayList. -func (in *APIGatewayList) DeepCopy() *APIGatewayList { - if in == nil { - return nil - } - out := new(APIGatewayList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *APIGatewayList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { - if in == nil { - return nil - } - out := new(Condition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Conditions) DeepCopyInto(out *Conditions) { - { - in := &in - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. -func (in Conditions) DeepCopy() Conditions { - if in == nil { - return nil - } - out := new(Conditions) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GRPCRoute) DeepCopyInto(out *GRPCRoute) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCRoute. -func (in *GRPCRoute) DeepCopy() *GRPCRoute { - if in == nil { - return nil - } - out := new(GRPCRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GRPCRoute) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GRPCRouteList) DeepCopyInto(out *GRPCRouteList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*GRPCRoute, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(GRPCRoute) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCRouteList. -func (in *GRPCRouteList) DeepCopy() *GRPCRouteList { - if in == nil { - return nil - } - out := new(GRPCRouteList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GRPCRouteList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClass) DeepCopyInto(out *GatewayClass) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClass. -func (in *GatewayClass) DeepCopy() *GatewayClass { - if in == nil { - return nil - } - out := new(GatewayClass) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayClass) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassAnnotationsAndLabels) DeepCopyInto(out *GatewayClassAnnotationsAndLabels) { - *out = *in - in.Annotations.DeepCopyInto(&out.Annotations) - in.Labels.DeepCopyInto(&out.Labels) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassAnnotationsAndLabels. -func (in *GatewayClassAnnotationsAndLabels) DeepCopy() *GatewayClassAnnotationsAndLabels { - if in == nil { - return nil - } - out := new(GatewayClassAnnotationsAndLabels) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassAnnotationsLabelsConfig) DeepCopyInto(out *GatewayClassAnnotationsLabelsConfig) { - *out = *in - if in.InheritFromGateway != nil { - in, out := &in.InheritFromGateway, &out.InheritFromGateway - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Set != nil { - in, out := &in.Set, &out.Set - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassAnnotationsLabelsConfig. -func (in *GatewayClassAnnotationsLabelsConfig) DeepCopy() *GatewayClassAnnotationsLabelsConfig { - if in == nil { - return nil - } - out := new(GatewayClassAnnotationsLabelsConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassConfig) DeepCopyInto(out *GatewayClassConfig) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfig. -func (in *GatewayClassConfig) DeepCopy() *GatewayClassConfig { - if in == nil { - return nil - } - out := new(GatewayClassConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayClassConfig) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassConfigList) DeepCopyInto(out *GatewayClassConfigList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*GatewayClassConfig, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(GatewayClassConfig) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigList. -func (in *GatewayClassConfigList) DeepCopy() *GatewayClassConfigList { - if in == nil { - return nil - } - out := new(GatewayClassConfigList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayClassConfigList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassConfigSpec) DeepCopyInto(out *GatewayClassConfigSpec) { - *out = *in - in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) - in.Deployment.DeepCopyInto(&out.Deployment) - in.Role.DeepCopyInto(&out.Role) - in.RoleBinding.DeepCopyInto(&out.RoleBinding) - in.Service.DeepCopyInto(&out.Service) - in.ServiceAccount.DeepCopyInto(&out.ServiceAccount) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigSpec. -func (in *GatewayClassConfigSpec) DeepCopy() *GatewayClassConfigSpec { - if in == nil { - return nil - } - out := new(GatewayClassConfigSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassConsulConfig) DeepCopyInto(out *GatewayClassConsulConfig) { - *out = *in - out.Logging = in.Logging -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConsulConfig. -func (in *GatewayClassConsulConfig) DeepCopy() *GatewayClassConsulConfig { - if in == nil { - return nil - } - out := new(GatewayClassConsulConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassConsulLoggingConfig) DeepCopyInto(out *GatewayClassConsulLoggingConfig) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConsulLoggingConfig. -func (in *GatewayClassConsulLoggingConfig) DeepCopy() *GatewayClassConsulLoggingConfig { - if in == nil { - return nil - } - out := new(GatewayClassConsulLoggingConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassContainerConfig) DeepCopyInto(out *GatewayClassContainerConfig) { - *out = *in - out.Consul = in.Consul - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = new(v1.ResourceRequirements) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassContainerConfig. -func (in *GatewayClassContainerConfig) DeepCopy() *GatewayClassContainerConfig { - if in == nil { - return nil - } - out := new(GatewayClassContainerConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassDeploymentConfig) DeepCopyInto(out *GatewayClassDeploymentConfig) { - *out = *in - in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) - if in.Container != nil { - in, out := &in.Container, &out.Container - *out = new(GatewayClassContainerConfig) - (*in).DeepCopyInto(*out) - } - if in.InitContainer != nil { - in, out := &in.InitContainer, &out.InitContainer - *out = new(GatewayClassInitContainerConfig) - (*in).DeepCopyInto(*out) - } - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Replicas != nil { - in, out := &in.Replicas, &out.Replicas - *out = new(GatewayClassReplicasConfig) - (*in).DeepCopyInto(*out) - } - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.PodSecurityContext) - (*in).DeepCopyInto(*out) - } - if in.Tolerations != nil { - in, out := &in.Tolerations, &out.Tolerations - *out = make([]v1.Toleration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.TopologySpreadConstraints != nil { - in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints - *out = make([]v1.TopologySpreadConstraint, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Affinity != nil { - in, out := &in.Affinity, &out.Affinity - *out = new(v1.Affinity) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassDeploymentConfig. -func (in *GatewayClassDeploymentConfig) DeepCopy() *GatewayClassDeploymentConfig { - if in == nil { - return nil - } - out := new(GatewayClassDeploymentConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassInitContainerConfig) DeepCopyInto(out *GatewayClassInitContainerConfig) { - *out = *in - out.Consul = in.Consul - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = new(v1.ResourceRequirements) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassInitContainerConfig. -func (in *GatewayClassInitContainerConfig) DeepCopy() *GatewayClassInitContainerConfig { - if in == nil { - return nil - } - out := new(GatewayClassInitContainerConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassList) DeepCopyInto(out *GatewayClassList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*GatewayClass, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(GatewayClass) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassList. -func (in *GatewayClassList) DeepCopy() *GatewayClassList { - if in == nil { - return nil - } - out := new(GatewayClassList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayClassList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassReplicasConfig) DeepCopyInto(out *GatewayClassReplicasConfig) { - *out = *in - if in.Default != nil { - in, out := &in.Default, &out.Default - *out = new(int32) - **out = **in - } - if in.Min != nil { - in, out := &in.Min, &out.Min - *out = new(int32) - **out = **in - } - if in.Max != nil { - in, out := &in.Max, &out.Max - *out = new(int32) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassReplicasConfig. -func (in *GatewayClassReplicasConfig) DeepCopy() *GatewayClassReplicasConfig { - if in == nil { - return nil - } - out := new(GatewayClassReplicasConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassRoleBindingConfig) DeepCopyInto(out *GatewayClassRoleBindingConfig) { - *out = *in - in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassRoleBindingConfig. -func (in *GatewayClassRoleBindingConfig) DeepCopy() *GatewayClassRoleBindingConfig { - if in == nil { - return nil - } - out := new(GatewayClassRoleBindingConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassRoleConfig) DeepCopyInto(out *GatewayClassRoleConfig) { - *out = *in - in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassRoleConfig. -func (in *GatewayClassRoleConfig) DeepCopy() *GatewayClassRoleConfig { - if in == nil { - return nil - } - out := new(GatewayClassRoleConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassServiceAccountConfig) DeepCopyInto(out *GatewayClassServiceAccountConfig) { - *out = *in - in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassServiceAccountConfig. -func (in *GatewayClassServiceAccountConfig) DeepCopy() *GatewayClassServiceAccountConfig { - if in == nil { - return nil - } - out := new(GatewayClassServiceAccountConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassServiceConfig) DeepCopyInto(out *GatewayClassServiceConfig) { - *out = *in - in.GatewayClassAnnotationsAndLabels.DeepCopyInto(&out.GatewayClassAnnotationsAndLabels) - if in.Type != nil { - in, out := &in.Type, &out.Type - *out = new(v1.ServiceType) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassServiceConfig. -func (in *GatewayClassServiceConfig) DeepCopy() *GatewayClassServiceConfig { - if in == nil { - return nil - } - out := new(GatewayClassServiceConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute. -func (in *HTTPRoute) DeepCopy() *HTTPRoute { - if in == nil { - return nil - } - out := new(HTTPRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HTTPRoute) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HTTPRouteList) DeepCopyInto(out *HTTPRouteList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*HTTPRoute, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(HTTPRoute) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteList. -func (in *HTTPRouteList) DeepCopy() *HTTPRouteList { - if in == nil { - return nil - } - out := new(HTTPRouteList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HTTPRouteList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshConfiguration) DeepCopyInto(out *MeshConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshConfiguration. -func (in *MeshConfiguration) DeepCopy() *MeshConfiguration { - if in == nil { - return nil - } - out := new(MeshConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *MeshConfiguration) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshConfigurationList) DeepCopyInto(out *MeshConfigurationList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*MeshConfiguration, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(MeshConfiguration) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshConfigurationList. -func (in *MeshConfigurationList) DeepCopy() *MeshConfigurationList { - if in == nil { - return nil - } - out := new(MeshConfigurationList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *MeshConfigurationList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshGateway) DeepCopyInto(out *MeshGateway) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshGateway. -func (in *MeshGateway) DeepCopy() *MeshGateway { - if in == nil { - return nil - } - out := new(MeshGateway) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *MeshGateway) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshGatewayList) DeepCopyInto(out *MeshGatewayList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*MeshGateway, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(MeshGateway) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshGatewayList. -func (in *MeshGatewayList) DeepCopy() *MeshGatewayList { - if in == nil { - return nil - } - out := new(MeshGatewayList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *MeshGatewayList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxyConfiguration) DeepCopyInto(out *ProxyConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfiguration. -func (in *ProxyConfiguration) DeepCopy() *ProxyConfiguration { - if in == nil { - return nil - } - out := new(ProxyConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ProxyConfiguration) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxyConfigurationList) DeepCopyInto(out *ProxyConfigurationList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*ProxyConfiguration, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(ProxyConfiguration) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfigurationList. -func (in *ProxyConfigurationList) DeepCopy() *ProxyConfigurationList { - if in == nil { - return nil - } - out := new(ProxyConfigurationList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ProxyConfigurationList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Status) DeepCopyInto(out *Status) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastSyncedTime != nil { - in, out := &in.LastSyncedTime, &out.LastSyncedTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. -func (in *Status) DeepCopy() *Status { - if in == nil { - return nil - } - out := new(Status) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TCPRoute) DeepCopyInto(out *TCPRoute) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRoute. -func (in *TCPRoute) DeepCopy() *TCPRoute { - if in == nil { - return nil - } - out := new(TCPRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TCPRoute) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TCPRouteList) DeepCopyInto(out *TCPRouteList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*TCPRoute, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(TCPRoute) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRouteList. -func (in *TCPRouteList) DeepCopy() *TCPRouteList { - if in == nil { - return nil - } - out := new(TCPRouteList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TCPRouteList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/control-plane/api/multicluster/v2beta1/exported_services_types.go b/control-plane/api/multicluster/v2beta1/exported_services_types.go deleted file mode 100644 index 614192b25a..0000000000 --- a/control-plane/api/multicluster/v2beta1/exported_services_types.go +++ /dev/null @@ -1,152 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmulticluster "github.com/hashicorp/consul/proto-public/pbmulticluster/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - exportedServicesKubeKind = "exportedservices" -) - -func init() { - MultiClusterSchemeBuilder.Register(&ExportedServices{}, &ExportedServicesList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// ExportedServices is the Schema for the Exported Services API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:scope="Namespaced" -type ExportedServices struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmulticluster.ExportedServices `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// ExportedServicesList contains a list of ExportedServices. -type ExportedServicesList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*ExportedServices `json:"items"` -} - -func (in *ExportedServices) ResourceID(_, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmulticluster.ExportedServicesType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: "", // Namespace is always unset because ExportedServices is partition-scoped - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *ExportedServices) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: multiClusterConfigMeta(), - } -} - -func (in *ExportedServices) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *ExportedServices) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *ExportedServices) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *ExportedServices) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *ExportedServices) KubeKind() string { - return exportedServicesKubeKind -} - -func (in *ExportedServices) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *ExportedServices) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *ExportedServices) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *ExportedServices) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *ExportedServices) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *ExportedServices) Validate(tenancy common.ConsulTenancyConfig) error { - // TODO add validation logic that ensures we only ever write this to the default namespace. - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *ExportedServices) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/multicluster/v2beta1/multicluster_groupversion_info.go b/control-plane/api/multicluster/v2beta1/multicluster_groupversion_info.go deleted file mode 100644 index 9fef3b60f5..0000000000 --- a/control-plane/api/multicluster/v2beta1/multicluster_groupversion_info.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Package v2beta1 contains API Schema definitions for the consul.hashicorp.com v2beta1 API group -// +kubebuilder:object:generate=true -// +groupName=multicluster.consul.hashicorp.com -package v2beta1 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var ( - - // MultiClusterGroup is a collection of multi-cluster resources. - MultiClusterGroup = "multicluster.consul.hashicorp.com" - - // MultiClusterGroupVersion is group version used to register these objects. - MultiClusterGroupVersion = schema.GroupVersion{Group: MultiClusterGroup, Version: "v2beta1"} - - // MultiClusterSchemeBuilder is used to add go types to the GroupVersionKind scheme. - MultiClusterSchemeBuilder = &scheme.Builder{GroupVersion: MultiClusterGroupVersion} - - // AddMultiClusterToScheme adds the types in this group-version to the given scheme. - AddMultiClusterToScheme = MultiClusterSchemeBuilder.AddToScheme -) diff --git a/control-plane/api/multicluster/v2beta1/shared_types.go b/control-plane/api/multicluster/v2beta1/shared_types.go deleted file mode 100644 index 01184df943..0000000000 --- a/control-plane/api/multicluster/v2beta1/shared_types.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -func multiClusterConfigMeta() map[string]string { - return map[string]string{ - common.SourceKey: common.SourceValue, - } -} diff --git a/control-plane/api/multicluster/v2beta1/status.go b/control-plane/api/multicluster/v2beta1/status.go deleted file mode 100644 index cc75a1cd82..0000000000 --- a/control-plane/api/multicluster/v2beta1/status.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Conditions is the schema for the conditions portion of the payload. -type Conditions []Condition - -// ConditionType is a camel-cased condition type. -type ConditionType string - -const ( - // ConditionSynced specifies that the resource has been synced with Consul. - ConditionSynced ConditionType = "Synced" -) - -// Conditions define a readiness condition for a Consul resource. -// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Condition struct { - // Type of condition. - // +required - Type ConditionType `json:"type" description:"type of status condition"` - - // Status of the condition, one of True, False, Unknown. - // +required - Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` - - // LastTransitionTime is the last time the condition transitioned from one status to another. - // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"` - - // The reason for the condition's last transition. - // +optional - Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` - - // A human readable message indicating details about the transition. - // +optional - Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` -} - -// IsTrue is true if the condition is True. -func (c *Condition) IsTrue() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionTrue -} - -// IsFalse is true if the condition is False. -func (c *Condition) IsFalse() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionFalse -} - -// IsUnknown is true if the condition is Unknown. -func (c *Condition) IsUnknown() bool { - if c == nil { - return true - } - return c.Status == corev1.ConditionUnknown -} - -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Status struct { - // Conditions indicate the latest available observations of a resource's current state. - // +optional - // +patchMergeKey=type - // +patchStrategy=merge - Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` - - // LastSyncedTime is the last time the resource successfully synced with Consul. - // +optional - LastSyncedTime *metav1.Time `json:"lastSyncedTime,omitempty" description:"last time the condition transitioned from one status to another"` -} - -func (s *Status) GetCondition(t ConditionType) *Condition { - for _, cond := range s.Conditions { - if cond.Type == t { - return &cond - } - } - return nil -} diff --git a/control-plane/api/multicluster/v2beta1/zz_generated.deepcopy.go b/control-plane/api/multicluster/v2beta1/zz_generated.deepcopy.go deleted file mode 100644 index 5d89bf9619..0000000000 --- a/control-plane/api/multicluster/v2beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,136 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Code generated by controller-gen. DO NOT EDIT. - -package v2beta1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { - if in == nil { - return nil - } - out := new(Condition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Conditions) DeepCopyInto(out *Conditions) { - { - in := &in - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. -func (in Conditions) DeepCopy() Conditions { - if in == nil { - return nil - } - out := new(Conditions) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExportedServices) DeepCopyInto(out *ExportedServices) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportedServices. -func (in *ExportedServices) DeepCopy() *ExportedServices { - if in == nil { - return nil - } - out := new(ExportedServices) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ExportedServices) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExportedServicesList) DeepCopyInto(out *ExportedServicesList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*ExportedServices, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(ExportedServices) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportedServicesList. -func (in *ExportedServicesList) DeepCopy() *ExportedServicesList { - if in == nil { - return nil - } - out := new(ExportedServicesList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ExportedServicesList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Status) DeepCopyInto(out *Status) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastSyncedTime != nil { - in, out := &in.LastSyncedTime, &out.LastSyncedTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. -func (in *Status) DeepCopy() *Status { - if in == nil { - return nil - } - out := new(Status) - in.DeepCopyInto(out) - return out -} diff --git a/control-plane/api/v1alpha1/api_gateway_types.go b/control-plane/api/v1alpha1/api_gateway_types.go deleted file mode 100644 index 90f6376d98..0000000000 --- a/control-plane/api/v1alpha1/api_gateway_types.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - GatewayClassConfigKind = "GatewayClassConfig" - MeshServiceKind = "MeshService" -) - -func init() { - SchemeBuilder.Register(&GatewayClassConfig{}, &GatewayClassConfigList{}) - SchemeBuilder.Register(&MeshService{}, &MeshServiceList{}) -} - -// +genclient -// +kubebuilder:object:root=true -// +kubebuilder:resource:scope=Cluster - -// GatewayClassConfig defines the values that may be set on a GatewayClass for Consul API Gateway. -type GatewayClassConfig struct { - // Standard Kubernetes resource metadata. - metav1.TypeMeta `json:",inline"` - - // Standard object's metadata. - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Spec defines the desired state of GatewayClassConfig. - Spec GatewayClassConfigSpec `json:"spec,omitempty"` -} - -// +k8s:deepcopy-gen=true - -// GatewayClassConfigSpec specifies the desired state of the Config CRD. -type GatewayClassConfigSpec struct { - - // +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer - ServiceType *corev1.ServiceType `json:"serviceType,omitempty"` - - // NodeSelector is a selector which must be true for the pod to fit on a node. - // Selector which must match a node's labels for the pod to be scheduled on that node. - // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - - // Tolerations allow the scheduler to schedule nodes with matching taints. - // More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - - // Deployment defines the deployment configuration for the gateway. - DeploymentSpec DeploymentSpec `json:"deployment,omitempty"` - - // Annotation Information to copy to services or deployments - CopyAnnotations CopyAnnotationsSpec `json:"copyAnnotations,omitempty"` - - // The name of an existing Kubernetes PodSecurityPolicy to bind to the managed ServiceAccount if ACLs are managed. - PodSecurityPolicy string `json:"podSecurityPolicy,omitempty"` - - // The name of the OpenShift SecurityContextConstraints resource for this gateway class to use. - OpenshiftSCCName string `json:"openshiftSCCName,omitempty"` - - // The value to add to privileged ports ( ports < 1024) for gateway containers - MapPrivilegedContainerPorts int32 `json:"mapPrivilegedContainerPorts,omitempty"` -} - -// +k8s:deepcopy-gen=true - -type DeploymentSpec struct { - // +kubebuilder:default:=1 - // +kubebuilder:validation:Maximum=8 - // +kubebuilder:validation:Minimum=1 - // Number of gateway instances that should be deployed by default - DefaultInstances *int32 `json:"defaultInstances,omitempty"` - // +kubebuilder:default:=8 - // +kubebuilder:validation:Maximum=8 - // +kubebuilder:validation:Minimum=1 - // Max allowed number of gateway instances - MaxInstances *int32 `json:"maxInstances,omitempty"` - // +kubebuilder:default:=1 - // +kubebuilder:validation:Maximum=8 - // +kubebuilder:validation:Minimum=1 - // Minimum allowed number of gateway instances - MinInstances *int32 `json:"minInstances,omitempty"` - - // Resources defines the resource requirements for the gateway. - Resources *corev1.ResourceRequirements `json:"resources,omitempty"` -} - -//+kubebuilder:object:generate=true - -// CopyAnnotationsSpec defines the annotations that should be copied to the gateway service. -type CopyAnnotationsSpec struct { - // List of annotations to copy to the gateway service. - Service []string `json:"service,omitempty"` -} - -// +kubebuilder:object:root=true - -// GatewayClassConfigList is a list of Config resources. -type GatewayClassConfigList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - // Items is the list of Configs. - Items []GatewayClassConfig `json:"items"` -} - -// +genclient -// +kubebuilder:object:root=true - -// MeshService holds a reference to an externally managed Consul Service Mesh service. -type MeshService struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Spec defines the desired state of MeshService. - Spec MeshServiceSpec `json:"spec,omitempty"` -} - -// +k8s:deepcopy-gen=true - -// MeshServiceSpec specifies the 'spec' of the MeshService CRD. -type MeshServiceSpec struct { - // Name holds the service name for a Consul service. - Name string `json:"name,omitempty"` - // Peer optionally specifies the name of the peer exporting the Consul service. - // If not specified, the Consul service is assumed to be in the local datacenter. - Peer *string `json:"peer,omitempty"` -} - -// +kubebuilder:object:root=true - -// MeshServiceList is a list of MeshService resources. -type MeshServiceList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []MeshService `json:"items"` -} diff --git a/control-plane/api/v1alpha1/api_gateway_types_test.go b/control-plane/api/v1alpha1/api_gateway_types_test.go deleted file mode 100644 index 6e0690b9b2..0000000000 --- a/control-plane/api/v1alpha1/api_gateway_types_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "testing" - - "github.com/stretchr/testify/require" - core "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestGatewayClassConfigDeepCopy(t *testing.T) { - var nilConfig *GatewayClassConfig - require.Nil(t, nilConfig.DeepCopy()) - require.Nil(t, nilConfig.DeepCopyObject()) - lbType := core.ServiceTypeLoadBalancer - spec := GatewayClassConfigSpec{ - ServiceType: &lbType, - NodeSelector: map[string]string{ - "test": "test", - }, - OpenshiftSCCName: "restricted-v2", - } - config := &GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: spec, - } - copy := config.DeepCopy() - copyObject := config.DeepCopyObject() - require.Equal(t, copy, copyObject) - - var nilSpec *GatewayClassConfigSpec - require.Nil(t, nilSpec.DeepCopy()) - specCopy := (&spec).DeepCopy() - require.Equal(t, spec.NodeSelector, specCopy.NodeSelector) - - var nilConfigList *GatewayClassConfigList - require.Nil(t, nilConfigList.DeepCopyObject()) - configList := &GatewayClassConfigList{ - Items: []GatewayClassConfig{*config}, - } - copyConfigList := configList.DeepCopy() - copyConfigListObject := configList.DeepCopyObject() - require.Equal(t, copyConfigList, copyConfigListObject) -} diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_types.go b/control-plane/api/v1alpha1/controlplanerequestlimit_types.go deleted file mode 100644 index 5b260c0abd..0000000000 --- a/control-plane/api/v1alpha1/controlplanerequestlimit_types.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - consul "github.com/hashicorp/consul/api" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -const ( - ControlPlaneRequestLimitKubeKind = "controlplanerequestlimit" -) - -func init() { - SchemeBuilder.Register(&ControlPlaneRequestLimit{}, &ControlPlaneRequestLimitList{}) -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// ControlPlaneRequestLimit is the Schema for the controlplanerequestlimits API. -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type ControlPlaneRequestLimit struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec ControlPlaneRequestLimitSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// ControlPlaneRequestLimitList contains a list of ControlPlaneRequestLimit. -type ControlPlaneRequestLimitList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []ControlPlaneRequestLimit `json:"items"` -} - -type ReadWriteRatesConfig struct { - ReadRate float64 `json:"readRate,omitempty"` - WriteRate float64 `json:"writeRate,omitempty"` -} - -func (c *ReadWriteRatesConfig) toConsul() *consul.ReadWriteRatesConfig { - if c == nil { - return nil - } - return &consul.ReadWriteRatesConfig{ - ReadRate: c.ReadRate, - WriteRate: c.WriteRate, - } -} - -func (c *ReadWriteRatesConfig) validate(path *field.Path) field.ErrorList { - if c == nil { - return nil - } - - var errs field.ErrorList - - if c.ReadRate < 0 { - errs = append(errs, field.Invalid(path.Child("readRate"), c.ReadRate, "readRate must be >= 0")) - } - - if c.WriteRate <= 0 { - errs = append(errs, field.Invalid(path.Child("writeRate"), c.WriteRate, "writeRate must be > 0")) - } - return errs -} - -// ControlPlaneRequestLimitSpec defines the desired state of ControlPlaneRequestLimit. -type ControlPlaneRequestLimitSpec struct { - Mode string `json:"mode,omitempty"` - ReadWriteRatesConfig `json:",inline"` - ACL *ReadWriteRatesConfig `json:"acl,omitempty"` - Catalog *ReadWriteRatesConfig `json:"catalog,omitempty"` - ConfigEntry *ReadWriteRatesConfig `json:"configEntry,omitempty"` - ConnectCA *ReadWriteRatesConfig `json:"connectCA,omitempty"` - Coordinate *ReadWriteRatesConfig `json:"coordinate,omitempty"` - DiscoveryChain *ReadWriteRatesConfig `json:"discoveryChain,omitempty"` - Health *ReadWriteRatesConfig `json:"health,omitempty"` - Intention *ReadWriteRatesConfig `json:"intention,omitempty"` - KV *ReadWriteRatesConfig `json:"kv,omitempty"` - Tenancy *ReadWriteRatesConfig `json:"tenancy,omitempty"` - PreparedQuery *ReadWriteRatesConfig `json:"preparedQuery,omitempty"` - Session *ReadWriteRatesConfig `json:"session,omitempty"` - Txn *ReadWriteRatesConfig `json:"txn,omitempty"` -} - -// GetObjectMeta returns object meta. -func (c *ControlPlaneRequestLimit) GetObjectMeta() metav1.ObjectMeta { - return c.ObjectMeta -} - -// AddFinalizer adds a finalizer to the list of finalizers. -func (c *ControlPlaneRequestLimit) AddFinalizer(name string) { - c.ObjectMeta.Finalizers = append(c.ObjectMeta.Finalizers, name) -} - -// RemoveFinalizer removes this finalizer from the list. -func (c *ControlPlaneRequestLimit) RemoveFinalizer(name string) { - for i, n := range c.ObjectMeta.Finalizers { - if n == name { - c.ObjectMeta.Finalizers = append(c.ObjectMeta.Finalizers[:i], c.ObjectMeta.Finalizers[i+1:]...) - return - } - } -} - -// Finalizers returns the list of finalizers for this object. -func (c *ControlPlaneRequestLimit) Finalizers() []string { - return c.ObjectMeta.Finalizers -} - -// ConsulKind returns the Consul config entry kind, i.e. service-defaults, not -// servicedefaults. -func (c *ControlPlaneRequestLimit) ConsulKind() string { - return consul.RateLimitIPConfig -} - -// ConsulGlobalResource returns if the resource exists in the default -// Consul namespace only. -func (c *ControlPlaneRequestLimit) ConsulGlobalResource() bool { - return true -} - -// ConsulMirroringNS returns the Consul namespace that the config entry should -// be created in if namespaces and mirroring are enabled. -func (c *ControlPlaneRequestLimit) ConsulMirroringNS() string { - return common.DefaultConsulNamespace -} - -// KubeKind returns the Kube config entry kind, i.e. servicedefaults, not -// service-defaults. -func (c *ControlPlaneRequestLimit) KubeKind() string { - return ControlPlaneRequestLimitKubeKind -} - -// ConsulName returns the name of the config entry as saved in Consul. -// This may be different than KubernetesName() in the case of a ServiceIntentions -// config entry. -func (c *ControlPlaneRequestLimit) ConsulName() string { - return c.ObjectMeta.Name -} - -// KubernetesName returns the name of the Kubernetes resource. -func (c *ControlPlaneRequestLimit) KubernetesName() string { - return c.ObjectMeta.Name -} - -// SetSyncedCondition updates the synced condition. -func (c *ControlPlaneRequestLimit) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - c.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -// SetLastSyncedTime updates the last synced time. -func (c *ControlPlaneRequestLimit) SetLastSyncedTime(time *metav1.Time) { - c.Status.LastSyncedTime = time -} - -// SyncedCondition gets the synced condition. -func (c *ControlPlaneRequestLimit) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := c.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -// SyncedConditionStatus returns the status of the synced condition. -func (c *ControlPlaneRequestLimit) SyncedConditionStatus() corev1.ConditionStatus { - condition := c.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -// ToConsul converts the resource to the corresponding Consul API definition. -// Its return type is the generic ConfigEntry but a specific config entry -// type should be constructed e.g. ServiceConfigEntry. -func (c *ControlPlaneRequestLimit) ToConsul(datacenter string) consul.ConfigEntry { - return &consul.RateLimitIPConfigEntry{ - Kind: c.ConsulKind(), - Name: c.ConsulName(), - Mode: c.Spec.Mode, - ReadRate: c.Spec.ReadRate, - WriteRate: c.Spec.WriteRate, - Meta: meta(datacenter), - ACL: c.Spec.ACL.toConsul(), - Catalog: c.Spec.Catalog.toConsul(), - ConfigEntry: c.Spec.ConfigEntry.toConsul(), - ConnectCA: c.Spec.ConnectCA.toConsul(), - Coordinate: c.Spec.Coordinate.toConsul(), - DiscoveryChain: c.Spec.DiscoveryChain.toConsul(), - Health: c.Spec.Health.toConsul(), - Intention: c.Spec.Intention.toConsul(), - KV: c.Spec.KV.toConsul(), - Tenancy: c.Spec.Tenancy.toConsul(), - PreparedQuery: c.Spec.PreparedQuery.toConsul(), - Session: c.Spec.Session.toConsul(), - Txn: c.Spec.Txn.toConsul(), - } -} - -// MatchesConsul returns true if the resource has the same fields as the Consul -// config entry. -func (c *ControlPlaneRequestLimit) MatchesConsul(candidate consul.ConfigEntry) bool { - configEntry, ok := candidate.(*consul.RateLimitIPConfigEntry) - if !ok { - return false - } - // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(c.ToConsul(""), configEntry, cmpopts.IgnoreFields(consul.RateLimitIPConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) -} - -// Validate returns an error if the resource is invalid. -func (c *ControlPlaneRequestLimit) Validate(consulMeta common.ConsulMeta) error { - var errs field.ErrorList - path := field.NewPath("spec") - - if c.Spec.Mode != "permissive" && c.Spec.Mode != "enforcing" && c.Spec.Mode != "disabled" { - errs = append(errs, field.Invalid(path.Child("mode"), c.Spec.Mode, "mode must be one of: permissive, enforcing, disabled")) - } - - errs = append(errs, c.Spec.ReadWriteRatesConfig.validate(path)...) - errs = append(errs, c.Spec.ACL.validate(path.Child("acl"))...) - errs = append(errs, c.Spec.Catalog.validate(path.Child("catalog"))...) - errs = append(errs, c.Spec.ConfigEntry.validate(path.Child("configEntry"))...) - errs = append(errs, c.Spec.ConnectCA.validate(path.Child("connectCA"))...) - errs = append(errs, c.Spec.Coordinate.validate(path.Child("coordinate"))...) - errs = append(errs, c.Spec.DiscoveryChain.validate(path.Child("discoveryChain"))...) - errs = append(errs, c.Spec.Health.validate(path.Child("health"))...) - errs = append(errs, c.Spec.Intention.validate(path.Child("intention"))...) - errs = append(errs, c.Spec.KV.validate(path.Child("kv"))...) - errs = append(errs, c.Spec.Tenancy.validate(path.Child("tenancy"))...) - errs = append(errs, c.Spec.PreparedQuery.validate(path.Child("preparedQuery"))...) - errs = append(errs, c.Spec.Session.validate(path.Child("session"))...) - errs = append(errs, c.Spec.Txn.validate(path.Child("txn"))...) - - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: ConsulHashicorpGroup, Kind: ControlPlaneRequestLimitKubeKind}, - c.KubernetesName(), errs) - } - - return nil -} - -// DefaultNamespaceFields has no behaviour here as control-plane-request-limit have no namespace specific fields. -func (s *ControlPlaneRequestLimit) DefaultNamespaceFields(_ common.ConsulMeta) { -} diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go b/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go deleted file mode 100644 index 12633250ab..0000000000 --- a/control-plane/api/v1alpha1/controlplanerequestlimit_types_test.go +++ /dev/null @@ -1,569 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "testing" - "time" - - consul "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -func TestControlPlaneRequestLimit_ToConsul(t *testing.T) { - cases := map[string]struct { - input *ControlPlaneRequestLimit - expected *consul.RateLimitIPConfigEntry - }{ - "empty fields": { - &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "disabled", - ReadWriteRatesConfig: ReadWriteRatesConfig{ - ReadRate: 0, - WriteRate: 0, - }, - }, - }, - &consul.RateLimitIPConfigEntry{ - Name: "foo", - Kind: consul.RateLimitIPConfig, - Mode: "disabled", - Meta: map[string]string{ - common.DatacenterKey: "datacenter", - common.SourceKey: common.SourceValue, - }, - ReadRate: 0, - WriteRate: 0, - }, - }, - "every field set": { - &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ACL: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - &consul.RateLimitIPConfigEntry{ - Kind: consul.RateLimitIPConfig, - Name: "foo", - Mode: "permissive", - ReadRate: 100.0, - WriteRate: 100.0, - Meta: map[string]string{ - common.DatacenterKey: "datacenter", - common.SourceKey: common.SourceValue, - }, - ACL: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - } - - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - output := testCase.input.ToConsul("datacenter") - require.Equal(t, testCase.expected, output) - }) - } -} - -func TestControlPlaneRequestLimit_MatchesConsul(t *testing.T) { - cases := map[string]struct { - internal *ControlPlaneRequestLimit - consul consul.ConfigEntry - matches bool - }{ - "empty fields matches": { - &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-test-service", - }, - Spec: ControlPlaneRequestLimitSpec{}, - }, - &consul.RateLimitIPConfigEntry{ - Kind: consul.RateLimitIPConfig, - Name: "my-test-service", - Namespace: "namespace", - CreateIndex: 1, - ModifyIndex: 2, - Meta: map[string]string{ - common.SourceKey: common.SourceValue, - common.DatacenterKey: "datacenter", - }, - }, - true, - }, - "all fields populated matches": { - &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-test-service", - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ACL: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - &consul.RateLimitIPConfigEntry{ - Kind: consul.RateLimitIPConfig, - Name: "my-test-service", - Mode: "permissive", - ReadRate: 100.0, - WriteRate: 100.0, - Meta: map[string]string{ - common.DatacenterKey: "datacenter", - common.SourceKey: common.SourceValue, - }, - ACL: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &consul.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - true, - }, - "mismatched types does not match": { - &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-test-service", - }, - Spec: ControlPlaneRequestLimitSpec{}, - }, - &consul.ProxyConfigEntry{ - Kind: consul.RateLimitIPConfig, - Name: "my-test-service", - Namespace: "namespace", - CreateIndex: 1, - ModifyIndex: 2, - }, - false, - }, - } - - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - require.Equal(t, testCase.matches, testCase.internal.MatchesConsul(testCase.consul)) - }) - } -} - -func TestControlPlaneRequestLimit_Validate(t *testing.T) { - invalidReadWriteRatesConfig := &ReadWriteRatesConfig{ - ReadRate: -1, - WriteRate: 0, - } - - validReadWriteRatesConfig := &ReadWriteRatesConfig{ - ReadRate: 100, - WriteRate: 100, - } - - cases := map[string]struct { - input *ControlPlaneRequestLimit - expectedErrMsgs []string - }{ - "invalid": { - input: &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.ControlPlaneRequestLimit, - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "invalid", - ACL: invalidReadWriteRatesConfig, - Catalog: invalidReadWriteRatesConfig, - ConfigEntry: invalidReadWriteRatesConfig, - ConnectCA: invalidReadWriteRatesConfig, - Coordinate: invalidReadWriteRatesConfig, - DiscoveryChain: invalidReadWriteRatesConfig, - Health: invalidReadWriteRatesConfig, - Intention: invalidReadWriteRatesConfig, - KV: invalidReadWriteRatesConfig, - Tenancy: invalidReadWriteRatesConfig, - PreparedQuery: invalidReadWriteRatesConfig, - Session: invalidReadWriteRatesConfig, - Txn: invalidReadWriteRatesConfig, - }, - }, - expectedErrMsgs: []string{ - `spec.mode: Invalid value: "invalid": mode must be one of: permissive, enforcing, disabled`, - `spec.acl.readRate: Invalid value: -1: readRate must be >= 0, spec.acl.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.catalog.readRate: Invalid value: -1: readRate must be >= 0, spec.catalog.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.configEntry.readRate: Invalid value: -1: readRate must be >= 0, spec.configEntry.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.connectCA.readRate: Invalid value: -1: readRate must be >= 0, spec.connectCA.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.coordinate.readRate: Invalid value: -1: readRate must be >= 0, spec.coordinate.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.discoveryChain.readRate: Invalid value: -1: readRate must be >= 0, spec.discoveryChain.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.health.readRate: Invalid value: -1: readRate must be >= 0, spec.health.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.intention.readRate: Invalid value: -1: readRate must be >= 0, spec.intention.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.kv.readRate: Invalid value: -1: readRate must be >= 0, spec.kv.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.tenancy.readRate: Invalid value: -1: readRate must be >= 0, spec.tenancy.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.preparedQuery.readRate: Invalid value: -1: readRate must be >= 0, spec.preparedQuery.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.session.readRate: Invalid value: -1: readRate must be >= 0, spec.session.writeRate: Invalid value: 0: writeRate must be > 0`, - `spec.txn.readRate: Invalid value: -1: readRate must be >= 0, spec.txn.writeRate: Invalid value: 0: writeRate must be > 0`, - }, - }, - "valid": { - input: &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.ControlPlaneRequestLimit, - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: *validReadWriteRatesConfig, - ACL: validReadWriteRatesConfig, - Catalog: validReadWriteRatesConfig, - ConfigEntry: validReadWriteRatesConfig, - ConnectCA: validReadWriteRatesConfig, - Coordinate: validReadWriteRatesConfig, - DiscoveryChain: validReadWriteRatesConfig, - Health: validReadWriteRatesConfig, - Intention: validReadWriteRatesConfig, - KV: validReadWriteRatesConfig, - Tenancy: validReadWriteRatesConfig, - PreparedQuery: validReadWriteRatesConfig, - Session: validReadWriteRatesConfig, - Txn: validReadWriteRatesConfig, - }, - }, - expectedErrMsgs: []string{}, - }, - } - - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - err := testCase.input.Validate(common.ConsulMeta{}) - if len(testCase.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range testCase.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func TestControlPlaneRequestLimit_AddFinalizer(t *testing.T) { - controlPlaneRequestLimit := &ControlPlaneRequestLimit{} - controlPlaneRequestLimit.AddFinalizer("finalizer") - require.Equal(t, []string{"finalizer"}, controlPlaneRequestLimit.ObjectMeta.Finalizers) -} - -func TestControlPlaneRequestLimit_RemoveFinalizer(t *testing.T) { - controlPlaneRequestLimit := &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{"f1", "f2"}, - }, - } - controlPlaneRequestLimit.RemoveFinalizer("f1") - require.Equal(t, []string{"f2"}, controlPlaneRequestLimit.ObjectMeta.Finalizers) -} - -func TestControlPlaneRequestLimit_SetSyncedCondition(t *testing.T) { - controlPlaneRequestLimit := &ControlPlaneRequestLimit{} - controlPlaneRequestLimit.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, controlPlaneRequestLimit.Status.Conditions[0].Status) - require.Equal(t, "reason", controlPlaneRequestLimit.Status.Conditions[0].Reason) - require.Equal(t, "message", controlPlaneRequestLimit.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, controlPlaneRequestLimit.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestControlPlaneRequestLimit_SetLastSyncedTime(t *testing.T) { - controlPlaneRequestLimit := &ControlPlaneRequestLimit{} - syncedTime := metav1.NewTime(time.Now()) - controlPlaneRequestLimit.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, controlPlaneRequestLimit.Status.LastSyncedTime) -} - -func TestControlPlaneRequestLimit_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - controlPlaneRequestLimit := &ControlPlaneRequestLimit{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, controlPlaneRequestLimit.SyncedConditionStatus()) - }) - } -} - -func TestControlPlaneRequestLimit_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&ControlPlaneRequestLimit{}).GetCondition(ConditionSynced)) -} - -func TestControlPlaneRequestLimit_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&ControlPlaneRequestLimit{}).SyncedConditionStatus()) -} - -func TestControlPlaneRequestLimit_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&ControlPlaneRequestLimit{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestControlPlaneRequestLimit_ConsulKind(t *testing.T) { - require.Equal(t, consul.RateLimitIPConfig, (&ControlPlaneRequestLimit{}).ConsulKind()) -} - -func TestControlPlaneRequestLimit_KubeKind(t *testing.T) { - require.Equal(t, "controlplanerequestlimit", (&ControlPlaneRequestLimit{}).KubeKind()) -} - -func TestControlPlaneRequestLimit_ConsulName(t *testing.T) { - require.Equal(t, "foo", (&ControlPlaneRequestLimit{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).ConsulName()) -} - -func TestControlPlaneRequestLimit_KubernetesName(t *testing.T) { - require.Equal(t, "foo", (&ControlPlaneRequestLimit{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).KubernetesName()) -} - -func TestControlPlaneRequestLimit_ConsulNamespace(t *testing.T) { - require.Equal(t, "default", (&ControlPlaneRequestLimit{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}}).ConsulMirroringNS()) -} - -func TestControlPlaneRequestLimit_ConsulGlobalResource(t *testing.T) { - require.True(t, (&ControlPlaneRequestLimit{}).ConsulGlobalResource()) -} - -func TestControlPlaneRequestLimit_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - controlPlaneRequestLimit := &ControlPlaneRequestLimit{ - ObjectMeta: meta, - } - require.Equal(t, meta, controlPlaneRequestLimit.GetObjectMeta()) -} diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_webhook.go b/control-plane/api/v1alpha1/controlplanerequestlimit_webhook.go deleted file mode 100644 index d99d9143f7..0000000000 --- a/control-plane/api/v1alpha1/controlplanerequestlimit_webhook.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "context" - "fmt" - "net/http" - - "github.com/go-logr/logr" - admissionv1 "k8s.io/api/admission/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type ControlPlaneRequestLimitWebhook struct { - client.Client - Logger logr.Logger - decoder *admission.Decoder - ConsulMeta common.ConsulMeta -} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/controller/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is -// it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-controlplanerequestlimits,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=controlplanerequestlimits,versions=v1alpha1,name=mutate-controlplanerequestlimits.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *ControlPlaneRequestLimitWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var limit ControlPlaneRequestLimit - var limitList ControlPlaneRequestLimitList - err := v.decoder.Decode(req, &limit) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - if req.Operation == admissionv1.Create { - v.Logger.Info("validate create", "name", limit.KubernetesName()) - - if limit.KubernetesName() != common.ControlPlaneRequestLimit { - return admission.Errored(http.StatusBadRequest, - fmt.Errorf(`%s resource name must be "%s"`, - limit.KubeKind(), common.ControlPlaneRequestLimit)) - } - - if err := v.Client.List(ctx, &limitList); err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - - if len(limitList.Items) > 0 { - return admission.Errored(http.StatusBadRequest, - fmt.Errorf("%s resource already defined - only one control plane request limit entry is supported", - limit.KubeKind())) - } - } - - return common.ValidateConfigEntry(ctx, req, v.Logger, v, &limit, v.ConsulMeta) -} - -func (v *ControlPlaneRequestLimitWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { - var limitList ControlPlaneRequestLimitList - if err := v.Client.List(ctx, &limitList); err != nil { - return nil, err - } - var entries []common.ConfigEntryResource - for _, item := range limitList.Items { - entries = append(entries, common.ConfigEntryResource(&item)) - } - return entries, nil -} - -func (v *ControlPlaneRequestLimitWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/v1alpha1/controlplanerequestlimit_webhook_test.go b/control-plane/api/v1alpha1/controlplanerequestlimit_webhook_test.go deleted file mode 100644 index c1ab7cc6af..0000000000 --- a/control-plane/api/v1alpha1/controlplanerequestlimit_webhook_test.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "context" - "encoding/json" - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - admissionv1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -func TestValidateControlPlaneRequestLimit(t *testing.T) { - otherNS := "other" - - cases := map[string]struct { - existingResources []runtime.Object - newResource *ControlPlaneRequestLimit - expAllow bool - expErrMessage string - }{ - "no duplicates, valid": { - existingResources: nil, - newResource: &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.ControlPlaneRequestLimit, - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: ReadWriteRatesConfig{ - ReadRate: 100, - WriteRate: 100, - }, - }, - }, - expAllow: true, - }, - "invalid resource name": { - existingResources: nil, - newResource: &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "invalid", - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: ReadWriteRatesConfig{ - ReadRate: 100, - WriteRate: 100, - }, - }, - }, - expAllow: false, - expErrMessage: `controlplanerequestlimit resource name must be "controlplanerequestlimit"`, - }, - "resource already exists": { - existingResources: []runtime.Object{ - &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.ControlPlaneRequestLimit, - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: ReadWriteRatesConfig{ - ReadRate: 100, - WriteRate: 100, - }, - }, - }, - }, - newResource: &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.ControlPlaneRequestLimit, - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: ReadWriteRatesConfig{ - ReadRate: 100, - WriteRate: 100, - }, - }, - }, - expAllow: false, - expErrMessage: `controlplanerequestlimit resource already defined - only one control plane request limit entry is supported`, - }, - "invalid spec": { - existingResources: nil, - newResource: &ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.ControlPlaneRequestLimit, - }, - Spec: ControlPlaneRequestLimitSpec{ - Mode: "invalid", - ReadWriteRatesConfig: ReadWriteRatesConfig{ - ReadRate: 100, - WriteRate: 100, - }, - }, - }, - expAllow: false, - expErrMessage: `controlplanerequestlimit.consul.hashicorp.com "controlplanerequestlimit" is invalid: spec.mode: Invalid value: "invalid": mode must be one of: permissive, enforcing, disabled`, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - ctx := context.Background() - marshalledRequestObject, err := json.Marshal(c.newResource) - require.NoError(t, err) - s := runtime.NewScheme() - s.AddKnownTypes(GroupVersion, &ControlPlaneRequestLimit{}, &ControlPlaneRequestLimitList{}) - client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - validator := &ControlPlaneRequestLimitWebhook{ - Client: client, - Logger: logrtest.New(t), - decoder: decoder, - } - response := validator.Handle(ctx, admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Name: c.newResource.KubernetesName(), - Namespace: otherNS, - Operation: admissionv1.Create, - Object: runtime.RawExtension{ - Raw: marshalledRequestObject, - }, - }, - }) - - require.Equal(t, c.expAllow, response.Allowed) - if c.expErrMessage != "" { - require.Equal(t, c.expErrMessage, response.AdmissionResponse.Result.Message) - } - }) - } -} diff --git a/control-plane/api/v1alpha1/exportedservices_types.go b/control-plane/api/v1alpha1/exportedservices_types.go index bcbb5e07d0..e05f17a177 100644 --- a/control-plane/api/v1alpha1/exportedservices_types.go +++ b/control-plane/api/v1alpha1/exportedservices_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -8,6 +5,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul/api" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" @@ -15,12 +13,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ExportedServicesKubeKind = "exportedservices" -const WildcardSpecifier = "*" func init() { SchemeBuilder.Register(&ExportedServices{}, &ExportedServicesList{}) @@ -73,10 +68,8 @@ type ExportedService struct { type ServiceConsumer struct { // Partition is the admin partition to export the service to. Partition string `json:"partition,omitempty"` - // Peer is the name of the peer to export the service to. + // [Experimental] Peer is the name of the peer to export the service to. Peer string `json:"peer,omitempty"` - // SamenessGroup is the name of the sameness group to export the service to. - SamenessGroup string `json:"samenessGroup,omitempty"` } func (in *ExportedServices) GetObjectMeta() metav1.ObjectMeta { @@ -173,9 +166,8 @@ func (in *ExportedService) toConsul() capi.ExportedService { var consumers []capi.ServiceConsumer for _, consumer := range in.Consumers { consumers = append(consumers, capi.ServiceConsumer{ - Partition: consumer.Partition, - Peer: consumer.Peer, - SamenessGroup: consumer.SamenessGroup, + Partition: consumer.Partition, + Peer: consumer.Peer, }) } return capi.ExportedService{ @@ -190,15 +182,8 @@ func (in *ExportedServices) MatchesConsul(candidate api.ConfigEntry) bool { if !ok { return false } - - specialEquality := cmp.Options{ - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Services.Consumers.Partition" - }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), - } - // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ExportedServicesConfigEntry{}, "Partition", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ExportedServicesConfigEntry{}, "Partition", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) } @@ -242,34 +227,14 @@ func (in *ExportedService) validate(path *field.Path, consulMeta common.ConsulMe } func (in *ServiceConsumer) validate(path *field.Path, consulMeta common.ConsulMeta) *field.Error { - count := 0 - - if in.Partition != "" { - count++ - } - if in.Peer != "" { - count++ - } - if in.SamenessGroup != "" { - count++ - } - if count > 1 { - return field.Invalid(path, *in, "service consumer must define at most one of Peer, Partition, or SamenessGroup") + if in.Partition != "" && in.Peer != "" { + return field.Invalid(path, *in, "both partition and peer cannot be specified.") } - if count == 0 { - return field.Invalid(path, *in, "service consumer must define at least one of Peer, Partition, or SamenessGroup") + if in.Partition == "" && in.Peer == "" { + return field.Invalid(path, *in, "either partition or peer must be specified.") } if !consulMeta.PartitionsEnabled && in.Partition != "" { - return field.Invalid(path.Child("partition"), in.Partition, "Consul Admin Partitions need to be enabled to specify partition.") - } - if in.Partition == WildcardSpecifier { - return field.Invalid(path.Child("partition"), "", "exporting to all partitions (wildcard) is not supported") - } - if in.Peer == WildcardSpecifier { - return field.Invalid(path.Child("peer"), "", "exporting to all peers (wildcard) is not supported") - } - if in.SamenessGroup == WildcardSpecifier { - return field.Invalid(path.Child("samenessgroup"), "", "exporting to all sameness groups (wildcard) is not supported") + return field.Invalid(path.Child("partitions"), in.Partition, "Consul Admin Partitions need to be enabled to specify partition.") } return nil } diff --git a/control-plane/api/v1alpha1/exportedservices_types_test.go b/control-plane/api/v1alpha1/exportedservices_types_test.go index c9f2b66aa8..8826166a76 100644 --- a/control-plane/api/v1alpha1/exportedservices_types_test.go +++ b/control-plane/api/v1alpha1/exportedservices_types_test.go @@ -1,18 +1,14 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) // Test MatchesConsul for cases that should return true. @@ -60,9 +56,6 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg1", - }, }, }, { @@ -78,9 +71,6 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "third-peer", }, - { - SamenessGroup: "sg2", - }, }, }, }, @@ -102,10 +92,6 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg1", - Partition: "default", - }, }, }, { @@ -121,9 +107,6 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "third-peer", }, - { - SamenessGroup: "sg2", - }, }, }, }, @@ -197,9 +180,6 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg2", - }, }, }, { @@ -215,9 +195,6 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "third-peer", }, - { - SamenessGroup: "sg3", - }, }, }, }, @@ -239,9 +216,6 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg2", - }, }, }, { @@ -257,9 +231,6 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "third-peer", }, - { - SamenessGroup: "sg3", - }, }, }, }, @@ -304,9 +275,6 @@ func TestExportedServices_Validate(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg2", - }, }, }, }, @@ -360,10 +328,10 @@ func TestExportedServices_Validate(t *testing.T) { namespaceEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `service consumer must define at most one of Peer, Partition, or SamenessGroup`, + `spec.services[0].consumers[0]: Invalid value: v1alpha1.ServiceConsumer{Partition:"second", Peer:"second-peer"}: both partition and peer cannot be specified.`, }, }, - "none of peer, partition, or sameness group defined": { + "neither partition nor peer name specified": { input: &ExportedServices{ ObjectMeta: metav1.ObjectMeta{ Name: common.DefaultConsulPartition, @@ -383,7 +351,7 @@ func TestExportedServices_Validate(t *testing.T) { namespaceEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `service consumer must define at least one of Peer, Partition, or SamenessGroup`, + `spec.services[0].consumers[0]: Invalid value: v1alpha1.ServiceConsumer{Partition:"", Peer:""}: either partition or peer must be specified.`, }, }, "partition provided when partitions are disabled": { @@ -408,7 +376,7 @@ func TestExportedServices_Validate(t *testing.T) { namespaceEnabled: true, partitionsEnabled: false, expectedErrMsgs: []string{ - `spec.services[0].consumers[0].partition: Invalid value: "test-partition": Consul Admin Partitions need to be enabled to specify partition.`, + `spec.services[0].consumers[0].partitions: Invalid value: "test-partition": Consul Admin Partitions need to be enabled to specify partition.`, }, }, "namespace provided when namespaces are disabled": { @@ -436,81 +404,6 @@ func TestExportedServices_Validate(t *testing.T) { `spec.services[0]: Invalid value: "frontend": Consul Namespaces must be enabled to specify service namespace.`, }, }, - "exporting to all partitions is not supported": { - input: &ExportedServices{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.DefaultConsulPartition, - }, - Spec: ExportedServicesSpec{ - Services: []ExportedService{ - { - Name: "service-frontend", - Namespace: "frontend", - Consumers: []ServiceConsumer{ - { - Partition: "*", - }, - }, - }, - }, - }, - }, - namespaceEnabled: true, - partitionsEnabled: true, - expectedErrMsgs: []string{ - `exporting to all partitions (wildcard) is not supported`, - }, - }, - "exporting to all peers (wildcard) is not supported": { - input: &ExportedServices{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.DefaultConsulPartition, - }, - Spec: ExportedServicesSpec{ - Services: []ExportedService{ - { - Name: "service-frontend", - Namespace: "frontend", - Consumers: []ServiceConsumer{ - { - Peer: "*", - }, - }, - }, - }, - }, - }, - namespaceEnabled: true, - partitionsEnabled: true, - expectedErrMsgs: []string{ - `exporting to all peers (wildcard) is not supported`, - }, - }, - "exporting to all sameness groups (wildcard) is not supported": { - input: &ExportedServices{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.DefaultConsulPartition, - }, - Spec: ExportedServicesSpec{ - Services: []ExportedService{ - { - Name: "service-frontend", - Namespace: "frontend", - Consumers: []ServiceConsumer{ - { - SamenessGroup: "*", - }, - }, - }, - }, - }, - }, - namespaceEnabled: true, - partitionsEnabled: true, - expectedErrMsgs: []string{ - `exporting to all sameness groups (wildcard) is not supported`, - }, - }, "multiple errors": { input: &ExportedServices{ ObjectMeta: metav1.ObjectMeta{ @@ -527,10 +420,6 @@ func TestExportedServices_Validate(t *testing.T) { Peer: "second-peer", }, {}, - { - SamenessGroup: "sg2", - Partition: "partition2", - }, }, }, }, @@ -539,9 +428,8 @@ func TestExportedServices_Validate(t *testing.T) { namespaceEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `spec.services[0].consumers[0]: Invalid value: v1alpha1.ServiceConsumer{Partition:"second", Peer:"second-peer", SamenessGroup:""}: service consumer must define at most one of Peer, Partition, or SamenessGroup`, - `spec.services[0].consumers[1]: Invalid value: v1alpha1.ServiceConsumer{Partition:"", Peer:"", SamenessGroup:""}: service consumer must define at least one of Peer, Partition, or SamenessGroup`, - `spec.services[0].consumers[2]: Invalid value: v1alpha1.ServiceConsumer{Partition:"partition2", Peer:"", SamenessGroup:"sg2"}: service consumer must define at most one of Peer, Partition, or SamenessGroup`, + `spec.services[0].consumers[0]: Invalid value: v1alpha1.ServiceConsumer{Partition:"second", Peer:"second-peer"}: both partition and peer cannot be specified.`, + `spec.services[0].consumers[1]: Invalid value: v1alpha1.ServiceConsumer{Partition:"", Peer:""}: either partition or peer must be specified.`, }, }, } diff --git a/control-plane/api/v1alpha1/exportedservices_webhook.go b/control-plane/api/v1alpha1/exportedservices_webhook.go index 6c870427ac..5a3d2cb2f1 100644 --- a/control-plane/api/v1alpha1/exportedservices_webhook.go +++ b/control-plane/api/v1alpha1/exportedservices_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/exportedservices_webhook_test.go b/control-plane/api/v1alpha1/exportedservices_webhook_test.go index 7cca7ff915..3a66fbdd9c 100644 --- a/control-plane/api/v1alpha1/exportedservices_webhook_test.go +++ b/control-plane/api/v1alpha1/exportedservices_webhook_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -123,7 +120,7 @@ func TestValidateExportedServices(t *testing.T) { Partition: "", }, expAllow: false, - expErrMessage: "exportedservices.consul.hashicorp.com \"default\" is invalid: spec.services[0].consumers[0].partition: Invalid value: \"other\": Consul Admin Partitions need to be enabled to specify partition.", + expErrMessage: "exportedservices.consul.hashicorp.com \"default\" is invalid: spec.services[0].consumers[0].partitions: Invalid value: \"other\": Consul Admin Partitions need to be enabled to specify partition.", }, "no services": { existingResources: []runtime.Object{}, diff --git a/control-plane/api/v1alpha1/gatewaypolicy_types.go b/control-plane/api/v1alpha1/gatewaypolicy_types.go deleted file mode 100644 index 7c5a633e2b..0000000000 --- a/control-plane/api/v1alpha1/gatewaypolicy_types.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func init() { - SchemeBuilder.Register(&GatewayPolicy{}, &GatewayPolicyList{}) -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// GatewayPolicy is the Schema for the gatewaypolicies API. -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type GatewayPolicy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec GatewayPolicySpec `json:"spec,omitempty"` - Status GatewayPolicyStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// GatewayPolicyList contains a list of GatewayPolicy. -type GatewayPolicyList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []GatewayPolicy `json:"items"` -} - -// GatewayPolicySpec defines the desired state of GatewayPolicy. -type GatewayPolicySpec struct { - // TargetRef identifies an API object to apply policy to. - TargetRef PolicyTargetReference `json:"targetRef"` - //+kubebuilder:validation:Optional - Override *GatewayPolicyConfig `json:"override,omitempty"` - //+kubebuilder:validation:Optional - Default *GatewayPolicyConfig `json:"default,omitempty"` -} - -// PolicyTargetReference identifies the target that the policy applies to. -type PolicyTargetReference struct { - // Group is the group of the target resource. - // - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - Group string `json:"group"` - - // Kind is kind of the target resource. - // - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - Kind string `json:"kind"` - - // Name is the name of the target resource. - // - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - Name string `json:"name"` - - // Namespace is the namespace of the referent. When unspecified, the local - // namespace is inferred. Even when policy targets a resource in a different - // namespace, it may only apply to traffic originating from the same - // namespace as the policy. - // - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - // +optional - Namespace string `json:"namespace,omitempty"` - - // SectionName refers to the listener targeted by this policy. - SectionName *gwv1beta1.SectionName `json:"sectionName,omitempty"` -} - -type GatewayPolicyConfig struct { - //+kubebuilder:validation:Optional - JWT *GatewayJWTRequirement `json:"jwt,omitempty"` -} - -// GatewayJWTRequirement holds the list of JWT providers to be verified against. -type GatewayJWTRequirement struct { - // Providers is a list of providers to consider when verifying a JWT. - Providers []*GatewayJWTProvider `json:"providers"` -} - -// GatewayJWTProvider holds the provider and claim verification information. -type GatewayJWTProvider struct { - // Name is the name of the JWT provider. There MUST be a corresponding - // "jwt-provider" config entry with this name. - Name string `json:"name"` - - // VerifyClaims is a list of additional claims to verify in a JWT's payload. - VerifyClaims []*GatewayJWTClaimVerification `json:"verifyClaims,omitempty"` -} - -// GatewayJWTClaimVerification holds the actual claim information to be verified. -type GatewayJWTClaimVerification struct { - // Path is the path to the claim in the token JSON. - Path []string `json:"path"` - - // Value is the expected value at the given path: - // - If the type at the path is a list then we verify - // that this value is contained in the list. - // - // - If the type at the path is a string then we verify - // that this value matches. - Value string `json:"value"` -} - -// GatewayPolicyStatus defines the observed state of the gateway. -type GatewayPolicyStatus struct { - // Conditions describe the current conditions of the Policy. - // - // - // Known condition types are: - // - // * "Accepted" - // * "ResolvedRefs" - // - // +optional - // +listType=map - // +listMapKey=type - // +kubebuilder:validation:MaxItems=8 - Conditions []metav1.Condition `json:"conditions,omitempty"` -} diff --git a/control-plane/api/v1alpha1/gatewaypolicy_webhook.go b/control-plane/api/v1alpha1/gatewaypolicy_webhook.go deleted file mode 100644 index 12bc30416e..0000000000 --- a/control-plane/api/v1alpha1/gatewaypolicy_webhook.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "context" - "fmt" - "net/http" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -const Gatewaypolicy_GatewayIndex = "__gatewaypolicy_referencing_gateway" - -// +kubebuilder:object:generate=false - -type GatewayPolicyWebhook struct { - Logger logr.Logger - - // ConsulMeta contains metadata specific to the Consul installation. - ConsulMeta common.ConsulMeta - - decoder *admission.Decoder - client.Client -} - -// +kubebuilder:webhook:verbs=create;update,path=/validate-v1alpha1-gatewaypolicy,mutating=false,failurePolicy=fail,groups=consul.hashicorp.com,resources=gatewaypolicies,versions=v1alpha1,name=validate-gatewaypolicy.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *GatewayPolicyWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource GatewayPolicy - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - var list GatewayPolicyList - - gwNamespaceName := types.NamespacedName{Name: resource.Spec.TargetRef.Name, Namespace: resource.Namespace} - err = v.Client.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gwNamespaceName.String()), - }) - - if err != nil { - v.Logger.Error(err, "error getting list of policies referencing gateway") - return admission.Errored(http.StatusInternalServerError, err) - } - - for _, policy := range list.Items { - if differentPolicySameTarget(resource, policy) { - return admission.Denied(fmt.Sprintf("policy targets gateway listener %q that is already the target of an existing policy %q", DerefStringOr(resource.Spec.TargetRef.SectionName, ""), policy.Name)) - } - } - - return admission.Allowed("gateway policy is valid") -} - -func differentPolicySameTarget(resource, policy GatewayPolicy) bool { - return resource.Name != policy.Name && - resource.Spec.TargetRef.Name == policy.Spec.TargetRef.Name && - resource.Spec.TargetRef.Group == policy.Spec.TargetRef.Group && - resource.Spec.TargetRef.Kind == policy.Spec.TargetRef.Kind && - resource.Spec.TargetRef.Namespace == policy.Spec.TargetRef.Namespace && - DerefStringOr(resource.Spec.TargetRef.SectionName, "") == DerefStringOr(policy.Spec.TargetRef.SectionName, "") -} - -func (v *GatewayPolicyWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} - -func DerefStringOr[T ~string, U ~string](v *T, val U) string { - if v == nil { - return string(val) - } - return string(*v) -} diff --git a/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go b/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go deleted file mode 100644 index 99b2b55896..0000000000 --- a/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "context" - "encoding/json" - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - admissionv1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestGatewayPolicyWebhook_Handle(t *testing.T) { - tests := map[string]struct { - existingResources []runtime.Object - newResource *GatewayPolicy - expAllow bool - expErrMessage string - }{ - "valid - no other policy targets listener": { - existingResources: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gateway", - Namespace: "default", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - }, - newResource: &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - expAllow: true, - }, - "valid - existing policy targets different gateway": { - existingResources: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gateway", - Namespace: "default", - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: "", - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy-2", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "another-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - }, - newResource: &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewaypolicy", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - expAllow: true, - }, - - "valid - existing policy targets different listener on the same gateway": { - existingResources: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-gateway", - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: "", - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - { - Name: "l2", - }, - }, - }, - }, - &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy-2", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l2")), - }, - }, - }, - }, - newResource: &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - expAllow: true, - }, - "invalid - existing policy targets same listener on same gateway": { - existingResources: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gateway", - Namespace: "default", - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: "", - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - { - Name: "l2", - }, - }, - }, - }, - &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - }, - newResource: &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy-2", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - expAllow: false, - expErrMessage: "policy targets gateway listener \"l1\" that is already the target of an existing policy \"my-policy\"", - }, - } - for name, tt := range tests { - name := name - tt := tt - t.Run(name, func(t *testing.T) { - t.Parallel() - ctx := context.Background() - marshalledRequestObject, err := json.Marshal(tt.newResource) - require.NoError(t, err) - s := runtime.NewScheme() - s.AddKnownTypes(GroupVersion, &GatewayPolicy{}, &GatewayPolicyList{}) - s.AddKnownTypes(gwv1beta1.SchemeGroupVersion, &gwv1beta1.Gateway{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.existingResources...).WithIndex(&GatewayPolicy{}, Gatewaypolicy_GatewayIndex, gatewayForGatewayPolicy).Build() - - var list GatewayPolicyList - - gwNamespaceName := types.NamespacedName{Name: "my-gateway", Namespace: "default"} - fakeClient.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gwNamespaceName.String()), - }) - - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - v := &GatewayPolicyWebhook{ - Logger: logrtest.New(t), - decoder: decoder, - Client: fakeClient, - } - - response := v.Handle(ctx, admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Name: tt.newResource.Name, - Operation: admissionv1.Create, - Object: runtime.RawExtension{ - Raw: marshalledRequestObject, - }, - }, - }) - - require.Equal(t, tt.expAllow, response.Allowed) - if tt.expErrMessage != "" { - require.Equal(t, tt.expErrMessage, string(response.AdmissionResponse.Result.Reason)) - } - }) - } -} - -func ptrTo[T any](v T) *T { - return &v -} - -func gatewayForGatewayPolicy(o client.Object) []string { - gatewayPolicy := o.(*GatewayPolicy) - - targetGateway := gatewayPolicy.Spec.TargetRef - // gateway policy is 1to1 - if targetGateway.Group == "gateway.networking.k8s.io/v1beta1" && targetGateway.Kind == "Gateway" { - policyNamespace := gatewayPolicy.Namespace - if policyNamespace == "" { - policyNamespace = "default" - } - targetNS := targetGateway.Namespace - if targetNS == "" { - targetNS = policyNamespace - } - - return []string{types.NamespacedName{Name: targetGateway.Name, Namespace: targetNS}.String()} - } - - return []string{} -} diff --git a/control-plane/api/v1alpha1/groupversion_info.go b/control-plane/api/v1alpha1/groupversion_info.go index 3657e9b048..cdbe085af4 100644 --- a/control-plane/api/v1alpha1/groupversion_info.go +++ b/control-plane/api/v1alpha1/groupversion_info.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Package v1alpha1 contains API Schema definitions for the consul.hashicorp.com v1alpha1 API group // +kubebuilder:object:generate=true // +groupName=consul.hashicorp.com diff --git a/control-plane/api/v1alpha1/ingressgateway_types.go b/control-plane/api/v1alpha1/ingressgateway_types.go index 23ea9515b3..c94b6e1458 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types.go +++ b/control-plane/api/v1alpha1/ingressgateway_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -9,15 +6,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( @@ -78,9 +74,6 @@ type IngressServiceConfig struct { // will be allowed at a single point in time. Use this to limit HTTP/2 traffic, // since HTTP/2 has many requests per connection. MaxConcurrentRequests *uint32 `json:"maxConcurrentRequests,omitempty"` - // PassiveHealthCheck configuration determines how upstream proxy instances will - // be monitored for removal from the load balancing pool. - PassiveHealthCheck *PassiveHealthCheck `json:"passiveHealthCheck,omitempty"` } type GatewayTLSConfig struct { @@ -270,18 +263,8 @@ func (in *IngressGateway) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } - - specialEquality := cmp.Options{ - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Listeners.Services.Namespace" - }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Listeners.Services.Partition" - }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), - } - // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.IngressGatewayConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.IngressGatewayConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) } func (in *IngressGateway) Validate(consulMeta common.ConsulMeta) error { @@ -378,7 +361,6 @@ func (in IngressService) toConsul() capi.IngressService { MaxConnections: in.MaxConnections, MaxPendingRequests: in.MaxPendingRequests, MaxConcurrentRequests: in.MaxConcurrentRequests, - PassiveHealthCheck: in.PassiveHealthCheck.toConsul(), } } @@ -483,6 +465,5 @@ func (in *IngressServiceConfig) toConsul() *capi.IngressServiceConfig { MaxConnections: in.MaxConnections, MaxPendingRequests: in.MaxPendingRequests, MaxConcurrentRequests: in.MaxConcurrentRequests, - PassiveHealthCheck: in.PassiveHealthCheck.toConsul(), } } diff --git a/control-plane/api/v1alpha1/ingressgateway_types_test.go b/control-plane/api/v1alpha1/ingressgateway_types_test.go index 9250d4b0c6..4942d38e11 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types_test.go +++ b/control-plane/api/v1alpha1/ingressgateway_types_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -8,13 +5,11 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func TestIngressGateway_MatchesConsul(t *testing.T) { @@ -72,17 +67,6 @@ func TestIngressGateway_MatchesConsul(t *testing.T) { MaxConnections: &defaultMaxConnections, MaxPendingRequests: &defaultMaxPendingRequests, MaxConcurrentRequests: &defaultMaxConcurrentRequests, - PassiveHealthCheck: &PassiveHealthCheck{ - Interval: metav1.Duration{ - Duration: 2 * time.Second, - }, - MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), - BaseEjectionTime: &metav1.Duration{ - Duration: 10 * time.Second, - }, - }, }, Listeners: []IngressListener{ { @@ -103,6 +87,7 @@ func TestIngressGateway_MatchesConsul(t *testing.T) { Name: "name1", Hosts: []string{"host1_1", "host1_2"}, Namespace: "ns1", + Partition: "default", IngressServiceConfig: IngressServiceConfig{ MaxConnections: &maxConnections, MaxPendingRequests: &maxPendingRequests, @@ -182,13 +167,6 @@ func TestIngressGateway_MatchesConsul(t *testing.T) { MaxConnections: &defaultMaxConnections, MaxPendingRequests: &defaultMaxPendingRequests, MaxConcurrentRequests: &defaultMaxConcurrentRequests, - PassiveHealthCheck: &capi.PassiveHealthCheck{ - Interval: 2 * time.Second, - MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), - BaseEjectionTime: pointer.Duration(10 * time.Second), - }, }, Listeners: []capi.IngressListener{ { @@ -351,17 +329,6 @@ func TestIngressGateway_ToConsul(t *testing.T) { MaxConnections: &defaultMaxConnections, MaxPendingRequests: &defaultMaxPendingRequests, MaxConcurrentRequests: &defaultMaxConcurrentRequests, - PassiveHealthCheck: &PassiveHealthCheck{ - Interval: metav1.Duration{ - Duration: 2 * time.Second, - }, - MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), - BaseEjectionTime: &metav1.Duration{ - Duration: 10 * time.Second, - }, - }, }, Listeners: []IngressListener{ { @@ -461,13 +428,6 @@ func TestIngressGateway_ToConsul(t *testing.T) { MaxConnections: &defaultMaxConnections, MaxPendingRequests: &defaultMaxPendingRequests, MaxConcurrentRequests: &defaultMaxConcurrentRequests, - PassiveHealthCheck: &capi.PassiveHealthCheck{ - Interval: 2 * time.Second, - MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), - BaseEjectionTime: pointer.Duration(10 * time.Second), - }, }, Listeners: []capi.IngressListener{ { diff --git a/control-plane/api/v1alpha1/ingressgateway_webhook.go b/control-plane/api/v1alpha1/ingressgateway_webhook.go index 04e31a0a3e..7f8ba37558 100644 --- a/control-plane/api/v1alpha1/ingressgateway_webhook.go +++ b/control-plane/api/v1alpha1/ingressgateway_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/jwtprovider_types.go b/control-plane/api/v1alpha1/jwtprovider_types.go deleted file mode 100644 index a38a1df0a7..0000000000 --- a/control-plane/api/v1alpha1/jwtprovider_types.go +++ /dev/null @@ -1,854 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "encoding/base64" - "encoding/json" - "net/url" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul/api" - capi "github.com/hashicorp/consul/api" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -const ( - JWTProviderKubeKind string = "jwtprovider" - DiscoveryTypeStrictDNS ClusterDiscoveryType = "STRICT_DNS" - DiscoveryTypeStatic ClusterDiscoveryType = "STATIC" - DiscoveryTypeLogicalDNS ClusterDiscoveryType = "LOGICAL_DNS" - DiscoveryTypeEDS ClusterDiscoveryType = "EDS" - DiscoveryTypeOriginalDST ClusterDiscoveryType = "ORIGINAL_DST" -) - -func init() { - SchemeBuilder.Register(&JWTProvider{}, &JWTProviderList{}) -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// JWTProvider is the Schema for the jwtproviders API. -type JWTProvider struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec JWTProviderSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// JWTProviderList contains a list of JWTProvider. -type JWTProviderList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []JWTProvider `json:"items"` -} - -// JWTProviderSpec defines the desired state of JWTProvider -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type JWTProviderSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // JSONWebKeySet defines a JSON Web Key Set, its location on disk, or the - // means with which to fetch a key set from a remote server. - JSONWebKeySet *JSONWebKeySet `json:"jsonWebKeySet,omitempty"` - - // Issuer is the entity that must have issued the JWT. - // This value must match the "iss" claim of the token. - Issuer string `json:"issuer,omitempty"` - - // Audiences is the set of audiences the JWT is allowed to access. - // If specified, all JWTs verified with this provider must address - // at least one of these to be considered valid. - Audiences []string `json:"audiences,omitempty"` - - // Locations where the JWT will be present in requests. - // Envoy will check all of these locations to extract a JWT. - // If no locations are specified Envoy will default to: - // 1. Authorization header with Bearer schema: - // "Authorization: Bearer " - // 2. accessToken query parameter. - Locations []*JWTLocation `json:"locations,omitempty"` - - // Forwarding defines rules for forwarding verified JWTs to the backend. - Forwarding *JWTForwardingConfig `json:"forwarding,omitempty"` - - // ClockSkewSeconds specifies the maximum allowable time difference - // from clock skew when validating the "exp" (Expiration) and "nbf" - // (Not Before) claims. - // - // Default value is 30 seconds. - ClockSkewSeconds int `json:"clockSkewSeconds,omitempty"` - - // CacheConfig defines configuration for caching the validation - // result for previously seen JWTs. Caching results can speed up - // verification when individual tokens are expected to be handled - // multiple times. - CacheConfig *JWTCacheConfig `json:"cacheConfig,omitempty"` -} - -type JWTLocations []*JWTLocation - -func (j JWTLocations) toConsul() []*capi.JWTLocation { - if j == nil { - return nil - } - result := make([]*capi.JWTLocation, 0, len(j)) - for _, loc := range j { - result = append(result, loc.toConsul()) - } - return result -} - -func (j JWTLocations) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - for i, loc := range j { - errs = append(errs, loc.validate(path.Index(i))...) - } - return errs -} - -// JWTLocation is a location where the JWT could be present in requests. -// -// Only one of Header, QueryParam, or Cookie can be specified. -type JWTLocation struct { - // Header defines how to extract a JWT from an HTTP request header. - Header *JWTLocationHeader `json:"header,omitempty"` - - // QueryParam defines how to extract a JWT from an HTTP request - // query parameter. - QueryParam *JWTLocationQueryParam `json:"queryParam,omitempty"` - - // Cookie defines how to extract a JWT from an HTTP request cookie. - Cookie *JWTLocationCookie `json:"cookie,omitempty"` -} - -func (j *JWTLocation) toConsul() *capi.JWTLocation { - if j == nil { - return nil - } - return &capi.JWTLocation{ - Header: j.Header.toConsul(), - QueryParam: j.QueryParam.toConsul(), - Cookie: j.Cookie.toConsul(), - } -} - -func (j *JWTLocation) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if j == nil { - return append(errs, field.Invalid(path, j, "location must not be nil")) - } - - if 1 != countTrue( - j.Header != nil, - j.QueryParam != nil, - j.Cookie != nil, - ) { - asJSON, _ := json.Marshal(j) - return append(errs, field.Invalid(path, string(asJSON), "exactly one of 'header', 'queryParam', or 'cookie' is required")) - } - - errs = append(errs, j.Header.validate(path.Child("header"))...) - errs = append(errs, j.QueryParam.validate(path.Child("queryParam"))...) - errs = append(errs, j.Cookie.validate(path.Child("cookie"))...) - return errs -} - -// JWTLocationHeader defines how to extract a JWT from an HTTP -// request header. -type JWTLocationHeader struct { - // Name is the name of the header containing the token. - Name string `json:"name,omitempty"` - - // ValuePrefix is an optional prefix that precedes the token in the - // header value. - // For example, "Bearer " is a standard value prefix for a header named - // "Authorization", but the prefix is not part of the token itself: - // "Authorization: Bearer " - ValuePrefix string `json:"valuePrefix,omitempty"` - - // Forward defines whether the header with the JWT should be - // forwarded after the token has been verified. If false, the - // header will not be forwarded to the backend. - // - // Default value is false. - Forward bool `json:"forward,omitempty"` -} - -func (j *JWTLocationHeader) toConsul() *capi.JWTLocationHeader { - if j == nil { - return nil - } - return &capi.JWTLocationHeader{ - Name: j.Name, - ValuePrefix: j.ValuePrefix, - Forward: j.Forward, - } -} - -func (j *JWTLocationHeader) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if j == nil { - return errs - } - - if j.Name == "" { - errs = append(errs, field.Invalid(path.Child("name"), j.Name, "JWT location header name is required")) - } - return errs -} - -// JWTLocationQueryParam defines how to extract a JWT from an HTTP request query parameter. -type JWTLocationQueryParam struct { - // Name is the name of the query param containing the token. - Name string `json:"name,omitempty"` -} - -func (j *JWTLocationQueryParam) toConsul() *capi.JWTLocationQueryParam { - if j == nil { - return nil - } - return &capi.JWTLocationQueryParam{ - Name: j.Name, - } -} - -func (j *JWTLocationQueryParam) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if j == nil { - return nil - } - if j.Name == "" { - errs = append(errs, field.Invalid(path.Child("name"), j.Name, "JWT location query parameter name is required")) - } - return errs -} - -// JWTLocationCookie defines how to extract a JWT from an HTTP request cookie. -type JWTLocationCookie struct { - // Name is the name of the cookie containing the token. - Name string `json:"name,omitempty"` -} - -func (j *JWTLocationCookie) toConsul() *capi.JWTLocationCookie { - if j == nil { - return nil - } - return &capi.JWTLocationCookie{ - Name: j.Name, - } -} - -func (j *JWTLocationCookie) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if j == nil { - return nil - } - if j.Name == "" { - errs = append(errs, field.Invalid(path.Child("name"), j.Name, "JWT location cookie name is required")) - } - return errs -} - -type JWTForwardingConfig struct { - // HeaderName is a header name to use when forwarding a verified - // JWT to the backend. The verified JWT could have been extracted - // from any location (query param, header, or cookie). - // - // The header value will be base64-URL-encoded, and will not be - // padded unless PadForwardPayloadHeader is true. - HeaderName string `json:"headerName,omitempty"` - - // PadForwardPayloadHeader determines whether padding should be added - // to the base64 encoded token forwarded with ForwardPayloadHeader. - // - // Default value is false. - PadForwardPayloadHeader bool `json:"padForwardPayloadHeader,omitempty"` -} - -func (j *JWTForwardingConfig) toConsul() *capi.JWTForwardingConfig { - if j == nil { - return nil - } - return &capi.JWTForwardingConfig{ - HeaderName: j.HeaderName, - PadForwardPayloadHeader: j.PadForwardPayloadHeader, - } -} - -func (j *JWTForwardingConfig) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if j == nil { - return nil - } - - if j.HeaderName == "" { - errs = append(errs, field.Invalid(path.Child("HeaderName"), j.HeaderName, "JWT forwarding header name is required")) - } - return errs -} - -// JSONWebKeySet defines a key set, its location on disk, or the -// means with which to fetch a key set from a remote server. -// -// Exactly one of Local or Remote must be specified. -type JSONWebKeySet struct { - // Local specifies a local source for the key set. - Local *LocalJWKS `json:"local,omitempty"` - - // Remote specifies how to fetch a key set from a remote server. - Remote *RemoteJWKS `json:"remote,omitempty"` -} - -func (j *JSONWebKeySet) toConsul() *capi.JSONWebKeySet { - if j == nil { - return nil - } - - return &capi.JSONWebKeySet{ - Local: j.Local.toConsul(), - Remote: j.Remote.toConsul(), - } -} - -func (j *JSONWebKeySet) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if j == nil { - return append(errs, field.Invalid(path, j, "jsonWebKeySet is required")) - } - - if countTrue(j.Local != nil, j.Remote != nil) != 1 { - asJSON, _ := json.Marshal(j) - return append(errs, field.Invalid(path, string(asJSON), "exactly one of 'local' or 'remote' is required")) - } - errs = append(errs, j.Local.validate(path.Child("local"))...) - errs = append(errs, j.Remote.validate(path.Child("remote"))...) - return errs -} - -// LocalJWKS specifies a location for a local JWKS. -// -// Only one of String and Filename can be specified. -type LocalJWKS struct { - // JWKS contains a base64 encoded JWKS. - JWKS string `json:"jwks,omitempty"` - - // Filename configures a location on disk where the JWKS can be - // found. If specified, the file must be present on the disk of ALL - // proxies with intentions referencing this provider. - Filename string `json:"filename,omitempty"` -} - -func (l *LocalJWKS) toConsul() *capi.LocalJWKS { - if l == nil { - return nil - } - return &capi.LocalJWKS{ - JWKS: l.JWKS, - Filename: l.Filename, - } -} - -func (l *LocalJWKS) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if l == nil { - return errs - } - - if countTrue(l.JWKS != "", l.Filename != "") != 1 { - asJSON, _ := json.Marshal(l) - return append(errs, field.Invalid(path, string(asJSON), "Exactly one of 'jwks' or 'filename' is required")) - } - if l.JWKS != "" { - if _, err := base64.StdEncoding.DecodeString(l.JWKS); err != nil { - return append(errs, field.Invalid(path.Child("jwks"), l.JWKS, "JWKS must be a valid base64-encoded string")) - } - } - return errs -} - -// RemoteJWKS specifies how to fetch a JWKS from a remote server. -type RemoteJWKS struct { - // URI is the URI of the server to query for the JWKS. - URI string `json:"uri,omitempty"` - - // RequestTimeoutMs is the number of milliseconds to - // time out when making a request for the JWKS. - RequestTimeoutMs int `json:"requestTimeoutMs,omitempty"` - - // CacheDuration is the duration after which cached keys - // should be expired. - // - // Default value is 5 minutes. - CacheDuration metav1.Duration `json:"cacheDuration,omitempty"` - - // FetchAsynchronously indicates that the JWKS should be fetched - // when a client request arrives. Client requests will be paused - // until the JWKS is fetched. - // If false, the proxy listener will wait for the JWKS to be - // fetched before being activated. - // - // Default value is false. - FetchAsynchronously bool `json:"fetchAsynchronously,omitempty"` - - // RetryPolicy defines a retry policy for fetching JWKS. - // - // There is no retry by default. - RetryPolicy *JWKSRetryPolicy `json:"retryPolicy,omitempty"` - - // JWKSCluster defines how the specified Remote JWKS URI is to be fetched. - JWKSCluster *JWKSCluster `json:"jwksCluster,omitempty"` -} - -func (r *RemoteJWKS) toConsul() *capi.RemoteJWKS { - if r == nil { - return nil - } - return &capi.RemoteJWKS{ - URI: r.URI, - RequestTimeoutMs: r.RequestTimeoutMs, - CacheDuration: r.CacheDuration.Duration, - FetchAsynchronously: r.FetchAsynchronously, - RetryPolicy: r.RetryPolicy.toConsul(), - JWKSCluster: r.JWKSCluster.toConsul(), - } -} - -func (r *RemoteJWKS) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if r == nil { - return errs - } - - if r.URI == "" { - errs = append(errs, field.Invalid(path.Child("uri"), r.URI, "remote JWKS URI is required")) - } else if _, err := url.ParseRequestURI(r.URI); err != nil { - errs = append(errs, field.Invalid(path.Child("uri"), r.URI, "remote JWKS URI is invalid")) - } - - errs = append(errs, r.RetryPolicy.validate(path.Child("retryPolicy"))...) - errs = append(errs, r.JWKSCluster.validate(path.Child("jwksCluster"))...) - return errs -} - -// JWKSCluster defines how the specified Remote JWKS URI is to be fetched. -type JWKSCluster struct { - // DiscoveryType refers to the service discovery type to use for resolving the cluster. - // - // This defaults to STRICT_DNS. - // Other options include STATIC, LOGICAL_DNS, EDS or ORIGINAL_DST. - DiscoveryType ClusterDiscoveryType `json:"discoveryType,omitempty"` - - // TLSCertificates refers to the data containing certificate authority certificates to use - // in verifying a presented peer certificate. - // If not specified and a peer certificate is presented it will not be verified. - // - // Must be either CaCertificateProviderInstance or TrustedCA. - TLSCertificates *JWKSTLSCertificate `json:"tlsCertificates,omitempty"` - - // The timeout for new network connections to hosts in the cluster. - // If not set, a default value of 5s will be used. - ConnectTimeout metav1.Duration `json:"connectTimeout,omitempty"` -} - -func (c *JWKSCluster) toConsul() *capi.JWKSCluster { - if c == nil { - return nil - } - return &capi.JWKSCluster{ - DiscoveryType: c.DiscoveryType.toConsul(), - TLSCertificates: c.TLSCertificates.toConsul(), - ConnectTimeout: c.ConnectTimeout.Duration, - } -} - -func (c *JWKSCluster) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if c == nil { - return errs - } - - errs = append(errs, c.DiscoveryType.validate(path.Child("discoveryType"))...) - errs = append(errs, c.TLSCertificates.validate(path.Child("tlsCertificates"))...) - - return errs -} - -type ClusterDiscoveryType string - -func (d ClusterDiscoveryType) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - - switch d { - case DiscoveryTypeStatic, DiscoveryTypeStrictDNS, DiscoveryTypeLogicalDNS, DiscoveryTypeEDS, DiscoveryTypeOriginalDST: - return errs - default: - errs = append(errs, field.Invalid(path, string(d), "unsupported jwks cluster discovery type.")) - } - return errs -} - -func (d ClusterDiscoveryType) toConsul() capi.ClusterDiscoveryType { - return capi.ClusterDiscoveryType(string(d)) -} - -// JWKSTLSCertificate refers to the data containing certificate authority certificates to use -// in verifying a presented peer certificate. -// If not specified and a peer certificate is presented it will not be verified. -// -// Must be either CaCertificateProviderInstance or TrustedCA. -type JWKSTLSCertificate struct { - // CaCertificateProviderInstance Certificate provider instance for fetching TLS certificates. - CaCertificateProviderInstance *JWKSTLSCertProviderInstance `json:"caCertificateProviderInstance,omitempty"` - - // TrustedCA defines TLS certificate data containing certificate authority certificates - // to use in verifying a presented peer certificate. - // - // Exactly one of Filename, EnvironmentVariable, InlineString or InlineBytes must be specified. - TrustedCA *JWKSTLSCertTrustedCA `json:"trustedCA,omitempty"` -} - -func (c *JWKSTLSCertificate) toConsul() *capi.JWKSTLSCertificate { - if c == nil { - return nil - } - - return &capi.JWKSTLSCertificate{ - TrustedCA: c.TrustedCA.toConsul(), - CaCertificateProviderInstance: c.CaCertificateProviderInstance.toConsul(), - } -} - -func (c *JWKSTLSCertificate) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if c == nil { - return errs - } - - hasProviderInstance := c.CaCertificateProviderInstance != nil - hasTrustedCA := c.TrustedCA != nil - - if countTrue(hasTrustedCA, hasProviderInstance) != 1 { - asJSON, _ := json.Marshal(c) - errs = append(errs, field.Invalid(path, string(asJSON), "exactly one of 'trustedCa' or 'caCertificateProviderInstance' is required")) - } - - errs = append(errs, c.TrustedCA.validate(path.Child("trustedCa"))...) - - return errs -} - -// JWKSTLSCertProviderInstance Certificate provider instance for fetching TLS certificates. -type JWKSTLSCertProviderInstance struct { - // InstanceName refers to the certificate provider instance name. - // - // The default value is "default". - InstanceName string `json:"instanceName,omitempty"` - - // CertificateName is used to specify certificate instances or types. For example, "ROOTCA" to specify - // a root-certificate (validation context) or "example.com" to specify a certificate for a - // particular domain. - // - // The default value is the empty string. - CertificateName string `json:"certificateName,omitempty"` -} - -func (c *JWKSTLSCertProviderInstance) toConsul() *capi.JWKSTLSCertProviderInstance { - if c == nil { - return nil - } - - return &capi.JWKSTLSCertProviderInstance{ - InstanceName: c.InstanceName, - CertificateName: c.CertificateName, - } -} - -// JWKSTLSCertTrustedCA defines TLS certificate data containing certificate authority certificates -// to use in verifying a presented peer certificate. -// -// Exactly one of Filename, EnvironmentVariable, InlineString or InlineBytes must be specified. -type JWKSTLSCertTrustedCA struct { - Filename string `json:"filename,omitempty"` - EnvironmentVariable string `json:"environmentVariable,omitempty"` - InlineString string `json:"inlineString,omitempty"` - InlineBytes []byte `json:"inlineBytes,omitempty"` -} - -func (c *JWKSTLSCertTrustedCA) toConsul() *capi.JWKSTLSCertTrustedCA { - if c == nil { - return nil - } - - return &capi.JWKSTLSCertTrustedCA{ - Filename: c.Filename, - EnvironmentVariable: c.EnvironmentVariable, - InlineBytes: c.InlineBytes, - InlineString: c.InlineString, - } -} - -func (c *JWKSTLSCertTrustedCA) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if c == nil { - return errs - } - - hasFilename := c.Filename != "" - hasEnv := c.EnvironmentVariable != "" - hasInlineBytes := len(c.InlineBytes) > 0 - hasInlineString := c.InlineString != "" - - if countTrue(hasFilename, hasEnv, hasInlineString, hasInlineBytes) != 1 { - asJSON, _ := json.Marshal(c) - errs = append(errs, field.Invalid(path, string(asJSON), "exactly one of 'filename', 'environmentVariable', 'inlineString' or 'inlineBytes' is required")) - } - return errs -} - -// JWKSRetryPolicy defines a retry policy for fetching JWKS. -// -// There is no retry by default. -type JWKSRetryPolicy struct { - // NumRetries is the number of times to retry fetching the JWKS. - // The retry strategy uses jittered exponential backoff with - // a base interval of 1s and max of 10s. - // - // Default value is 0. - NumRetries int `json:"numRetries,omitempty"` - - // Retry's backoff policy. - // - // Defaults to Envoy's backoff policy. - RetryPolicyBackOff *RetryPolicyBackOff `json:"retryPolicyBackOff,omitempty"` -} - -func (j *JWKSRetryPolicy) toConsul() *capi.JWKSRetryPolicy { - if j == nil { - return nil - } - return &capi.JWKSRetryPolicy{ - NumRetries: j.NumRetries, - RetryPolicyBackOff: j.RetryPolicyBackOff.toConsul(), - } -} - -func (j *JWKSRetryPolicy) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if j == nil { - return errs - } - - return append(errs, j.RetryPolicyBackOff.validate(path.Child("retryPolicyBackOff"))...) -} - -// RetryPolicyBackOff defines retry's policy backoff. -// -// Defaults to Envoy's backoff policy. -type RetryPolicyBackOff struct { - // BaseInterval to be used for the next back off computation. - // - // The default value from envoy is 1s. - BaseInterval metav1.Duration `json:"baseInterval,omitempty"` - - // MaxInternal to be used to specify the maximum interval between retries. - // Optional but should be greater or equal to BaseInterval. - // - // Defaults to 10 times BaseInterval. - MaxInterval metav1.Duration `json:"maxInterval,omitempty"` -} - -func (r *RetryPolicyBackOff) toConsul() *capi.RetryPolicyBackOff { - if r == nil { - return nil - } - return &capi.RetryPolicyBackOff{ - BaseInterval: r.BaseInterval.Duration, - MaxInterval: r.MaxInterval.Duration, - } -} - -func (r *RetryPolicyBackOff) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if r == nil { - return errs - } - - if (r.MaxInterval.Duration != 0) && (r.BaseInterval.Duration > r.MaxInterval.Duration) { - asJSON, _ := json.Marshal(r) - errs = append(errs, field.Invalid(path, string(asJSON), "maxInterval should be greater or equal to baseInterval")) - } - return errs -} - -type JWTCacheConfig struct { - // Size specifies the maximum number of JWT verification - // results to cache. - // - // Defaults to 0, meaning that JWT caching is disabled. - Size int `json:"size,omitempty"` -} - -func (j *JWTCacheConfig) toConsul() *capi.JWTCacheConfig { - if j == nil { - return nil - } - return &capi.JWTCacheConfig{ - Size: j.Size, - } -} - -func (j *JWTProvider) GetObjectMeta() metav1.ObjectMeta { - return j.ObjectMeta -} - -func (j *JWTProvider) AddFinalizer(name string) { - j.ObjectMeta.Finalizers = append(j.Finalizers(), name) -} - -func (j *JWTProvider) RemoveFinalizer(name string) { - var newFinalizers []string - for _, oldF := range j.Finalizers() { - if oldF != name { - newFinalizers = append(newFinalizers, oldF) - } - } - j.ObjectMeta.Finalizers = newFinalizers -} - -func (j *JWTProvider) Finalizers() []string { - return j.ObjectMeta.Finalizers -} - -func (j *JWTProvider) ConsulKind() string { - return capi.JWTProvider -} - -func (j *JWTProvider) ConsulGlobalResource() bool { - return true -} - -func (j *JWTProvider) ConsulMirroringNS() string { - return common.DefaultConsulNamespace -} - -func (j *JWTProvider) KubeKind() string { - return JWTProviderKubeKind -} - -func (j *JWTProvider) ConsulName() string { - return j.ObjectMeta.Name -} - -func (j *JWTProvider) KubernetesName() string { - return j.ObjectMeta.Name -} - -func (j *JWTProvider) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - j.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (j *JWTProvider) SetLastSyncedTime(time *metav1.Time) { - j.Status.LastSyncedTime = time -} - -// SyncedCondition gets the synced condition. -func (j *JWTProvider) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := j.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -// SyncedConditionStatus returns the status of the synced condition. -func (j *JWTProvider) SyncedConditionStatus() corev1.ConditionStatus { - cond := j.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown - } - return cond.Status -} - -// ToConsul converts the resource to the corresponding Consul API definition. -// Its return type is the generic ConfigEntry but a specific config entry -// type should be constructed e.g. ServiceConfigEntry. -func (j *JWTProvider) ToConsul(datacenter string) api.ConfigEntry { - return &capi.JWTProviderConfigEntry{ - Kind: j.ConsulKind(), - Name: j.ConsulName(), - JSONWebKeySet: j.Spec.JSONWebKeySet.toConsul(), - Issuer: j.Spec.Issuer, - Audiences: j.Spec.Audiences, - Locations: JWTLocations(j.Spec.Locations).toConsul(), - Forwarding: j.Spec.Forwarding.toConsul(), - ClockSkewSeconds: j.Spec.ClockSkewSeconds, - CacheConfig: j.Spec.CacheConfig.toConsul(), - Meta: meta(datacenter), - } -} - -// MatchesConsul returns true if the resource has the same fields as the Consul -// config entry. -func (j *JWTProvider) MatchesConsul(candidate api.ConfigEntry) bool { - configEntry, ok := candidate.(*capi.JWTProviderConfigEntry) - if !ok { - return false - } - // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(j.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.JWTProviderConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) -} - -// Validate returns an error if the resource is invalid. -func (j *JWTProvider) Validate(consulMeta common.ConsulMeta) error { - var errs field.ErrorList - path := field.NewPath("spec") - - errs = append(errs, j.Spec.JSONWebKeySet.validate(path.Child("jsonWebKeySet"))...) - errs = append(errs, JWTLocations(j.Spec.Locations).validate(path.Child("locations"))...) - errs = append(errs, j.Spec.Forwarding.validate(path.Child("forwarding"))...) - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: ConsulHashicorpGroup, Kind: JWTProviderKubeKind}, - j.KubernetesName(), errs) - } - return nil -} - -// DefaultNamespaceFields sets Consul namespace fields on the config entry -// spec to their default values if namespaces are enabled. -func (j *JWTProvider) DefaultNamespaceFields(_ common.ConsulMeta) {} - -func countTrue(vals ...bool) int { - var result int - for _, v := range vals { - if v { - result++ - } - } - return result -} - -var _ common.ConfigEntryResource = (*JWTProvider)(nil) diff --git a/control-plane/api/v1alpha1/jwtprovider_types_test.go b/control-plane/api/v1alpha1/jwtprovider_types_test.go deleted file mode 100644 index 098e34494e..0000000000 --- a/control-plane/api/v1alpha1/jwtprovider_types_test.go +++ /dev/null @@ -1,982 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "testing" - "time" - - capi "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// Test MatchesConsul for cases that should return true. -func TestJWTProvider_MatchesConsul(t *testing.T) { - cases := map[string]struct { - Ours JWTProvider - Theirs capi.ConfigEntry - Matches bool - }{ - "empty fields matches": { - Ours: JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-okta", - }, - Spec: JWTProviderSpec{}, - }, - Theirs: &capi.JWTProviderConfigEntry{ - Kind: capi.JWTProvider, - Name: "test-okta", - Namespace: "default", - CreateIndex: 1, - ModifyIndex: 2, - Meta: map[string]string{ - common.SourceKey: common.SourceValue, - common.DatacenterKey: "datacenter", - }, - }, - Matches: true, - }, - "all fields set matches": { - Ours: JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-okta2", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Local: &LocalJWKS{ - JWKS: "jwks-string", - Filename: "jwks-file", - }, - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - RequestTimeoutMs: 567, - CacheDuration: metav1.Duration{Duration: 890}, - FetchAsynchronously: true, - RetryPolicy: &JWKSRetryPolicy{ - NumRetries: 1, - RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 23}, - MaxInterval: metav1.Duration{Duration: 456}, - }, - }, - JWKSCluster: &JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &JWKSTLSCertificate{ - CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{ - InstanceName: "InstanceName", - CertificateName: "ROOTCA", - }, - TrustedCA: &JWKSTLSCertTrustedCA{ - Filename: "cert.crt", - EnvironmentVariable: "env-variable", - InlineString: "inline-string", - InlineBytes: []byte("inline-bytes"), - }, - }, - ConnectTimeout: metav1.Duration{Duration: 890}, - }, - }, - }, - Issuer: "test-issuer", - Audiences: []string{"aud1", "aud2"}, - Locations: []*JWTLocation{ - { - Header: &JWTLocationHeader{ - Name: "jwt-header", - ValuePrefix: "my-bearer", - Forward: true, - }, - }, - { - QueryParam: &JWTLocationQueryParam{ - Name: "jwt-query-param", - }, - }, - { - Cookie: &JWTLocationCookie{ - Name: "jwt-cookie", - }, - }, - }, - Forwarding: &JWTForwardingConfig{ - HeaderName: "jwt-forward-header", - PadForwardPayloadHeader: true, - }, - ClockSkewSeconds: 357, - CacheConfig: &JWTCacheConfig{ - Size: 468, - }, - }, - }, - Theirs: &capi.JWTProviderConfigEntry{ - Kind: capi.JWTProvider, - Name: "test-okta2", - Namespace: "default", - JSONWebKeySet: &capi.JSONWebKeySet{ - Local: &capi.LocalJWKS{ - JWKS: "jwks-string", - Filename: "jwks-file", - }, - Remote: &capi.RemoteJWKS{ - URI: "https://jwks.example.com", - RequestTimeoutMs: 567, - CacheDuration: 890, - FetchAsynchronously: true, - RetryPolicy: &capi.JWKSRetryPolicy{ - NumRetries: 1, - RetryPolicyBackOff: &capi.RetryPolicyBackOff{ - BaseInterval: 23, - MaxInterval: 456, - }, - }, - JWKSCluster: &capi.JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &capi.JWKSTLSCertificate{ - CaCertificateProviderInstance: &capi.JWKSTLSCertProviderInstance{ - InstanceName: "InstanceName", - CertificateName: "ROOTCA", - }, - TrustedCA: &capi.JWKSTLSCertTrustedCA{ - Filename: "cert.crt", - EnvironmentVariable: "env-variable", - InlineString: "inline-string", - InlineBytes: []byte("inline-bytes"), - }, - }, - ConnectTimeout: 890, - }, - }, - }, - Issuer: "test-issuer", - Audiences: []string{"aud1", "aud2"}, - Locations: []*capi.JWTLocation{ - { - Header: &capi.JWTLocationHeader{ - Name: "jwt-header", - ValuePrefix: "my-bearer", - Forward: true, - }, - }, - { - QueryParam: &capi.JWTLocationQueryParam{ - Name: "jwt-query-param", - }, - }, - { - Cookie: &capi.JWTLocationCookie{ - Name: "jwt-cookie", - }, - }, - }, - Forwarding: &capi.JWTForwardingConfig{ - HeaderName: "jwt-forward-header", - PadForwardPayloadHeader: true, - }, - ClockSkewSeconds: 357, - CacheConfig: &capi.JWTCacheConfig{ - Size: 468, - }, - }, - Matches: true, - }, - "mismatched types does not match": { - Ours: JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-okta3", - }, - Spec: JWTProviderSpec{}, - }, - Theirs: &capi.JWTProviderConfigEntry{}, - Matches: false, - }, - } - for name, c := range cases { - c := c - t.Run(name, func(t *testing.T) { - require.Equal(t, c.Matches, c.Ours.MatchesConsul(c.Theirs)) - }) - } -} - -func TestJWTProvider_ToConsul(t *testing.T) { - cases := map[string]struct { - Ours JWTProvider - Exp *capi.JWTProviderConfigEntry - }{ - "empty fields": { - Ours: JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-okta1", - }, - Spec: JWTProviderSpec{}, - }, - Exp: &capi.JWTProviderConfigEntry{ - Kind: capi.JWTProvider, - Name: "test-okta1", - Meta: map[string]string{ - common.SourceKey: common.SourceValue, - common.DatacenterKey: "datacenter", - }, - }, - }, - "every field set": { - Ours: JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-okta2", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Local: &LocalJWKS{ - JWKS: "jwks-string", - Filename: "jwks-file", - }, - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - RequestTimeoutMs: 567, - CacheDuration: metav1.Duration{Duration: 890}, - FetchAsynchronously: true, - RetryPolicy: &JWKSRetryPolicy{ - NumRetries: 1, - RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 23}, - MaxInterval: metav1.Duration{Duration: 456}, - }, - }, - JWKSCluster: &JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &JWKSTLSCertificate{ - CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{ - InstanceName: "InstanceName", - CertificateName: "ROOTCA", - }, - TrustedCA: &JWKSTLSCertTrustedCA{ - Filename: "cert.crt", - EnvironmentVariable: "env-variable", - InlineString: "inline-string", - InlineBytes: []byte("inline-bytes"), - }, - }, - ConnectTimeout: metav1.Duration{Duration: 890}, - }, - }, - }, - Issuer: "test-issuer", - Audiences: []string{"aud1", "aud2"}, - Locations: []*JWTLocation{ - { - Header: &JWTLocationHeader{ - Name: "jwt-header", - ValuePrefix: "my-bearer", - Forward: true, - }, - }, - { - QueryParam: &JWTLocationQueryParam{ - Name: "jwt-query-param", - }, - }, - { - Cookie: &JWTLocationCookie{ - Name: "jwt-cookie", - }, - }, - }, - Forwarding: &JWTForwardingConfig{ - HeaderName: "jwt-forward-header", - PadForwardPayloadHeader: true, - }, - ClockSkewSeconds: 357, - CacheConfig: &JWTCacheConfig{ - Size: 468, - }, - }, - }, - Exp: &capi.JWTProviderConfigEntry{ - Kind: capi.JWTProvider, - Name: "test-okta2", - JSONWebKeySet: &capi.JSONWebKeySet{ - Local: &capi.LocalJWKS{ - JWKS: "jwks-string", - Filename: "jwks-file", - }, - Remote: &capi.RemoteJWKS{ - URI: "https://jwks.example.com", - RequestTimeoutMs: 567, - CacheDuration: 890, - FetchAsynchronously: true, - RetryPolicy: &capi.JWKSRetryPolicy{ - NumRetries: 1, - RetryPolicyBackOff: &capi.RetryPolicyBackOff{ - BaseInterval: 23, - MaxInterval: 456, - }, - }, - JWKSCluster: &capi.JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &capi.JWKSTLSCertificate{ - CaCertificateProviderInstance: &capi.JWKSTLSCertProviderInstance{ - InstanceName: "InstanceName", - CertificateName: "ROOTCA", - }, - TrustedCA: &capi.JWKSTLSCertTrustedCA{ - Filename: "cert.crt", - EnvironmentVariable: "env-variable", - InlineString: "inline-string", - InlineBytes: []byte("inline-bytes"), - }, - }, - ConnectTimeout: 890, - }, - }, - }, - Issuer: "test-issuer", - Audiences: []string{"aud1", "aud2"}, - Locations: []*capi.JWTLocation{ - { - Header: &capi.JWTLocationHeader{ - Name: "jwt-header", - ValuePrefix: "my-bearer", - Forward: true, - }, - }, - { - QueryParam: &capi.JWTLocationQueryParam{ - Name: "jwt-query-param", - }, - }, - { - Cookie: &capi.JWTLocationCookie{ - Name: "jwt-cookie", - }, - }, - }, - Forwarding: &capi.JWTForwardingConfig{ - HeaderName: "jwt-forward-header", - PadForwardPayloadHeader: true, - }, - ClockSkewSeconds: 357, - CacheConfig: &capi.JWTCacheConfig{ - Size: 468, - }, - Meta: map[string]string{ - common.SourceKey: common.SourceValue, - common.DatacenterKey: "datacenter", - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - act := c.Ours.ToConsul("datacenter") - mesh, ok := act.(*capi.JWTProviderConfigEntry) - require.True(t, ok, "could not cast") - require.Equal(t, c.Exp, mesh) - }) - } -} - -func TestJWTProvider_Validate(t *testing.T) { - cases := map[string]struct { - input *JWTProvider - expectedErrMsgs []string - consulMeta common.ConsulMeta - }{ - "valid - local jwks": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-okta1", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Local: &LocalJWKS{ - Filename: "jwks.txt", - }, - }, - }, - Status: Status{}, - }, - expectedErrMsgs: nil, - }, - - "valid - remote jwks": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwt-provider", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - FetchAsynchronously: true, - }, - }, - Locations: []*JWTLocation{ - { - Header: &JWTLocationHeader{ - Name: "Authorization", - }, - }, - }, - Forwarding: &JWTForwardingConfig{ - HeaderName: "jwt-forward-header", - }, - }, - }, - expectedErrMsgs: nil, - }, - - "valid - remote jwks with all fields with trustedCa": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwt-provider", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - RequestTimeoutMs: 5000, - CacheDuration: metav1.Duration{Duration: 10 * time.Second}, - FetchAsynchronously: true, - RetryPolicy: &JWKSRetryPolicy{ - NumRetries: 3, - RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 5 * time.Second}, - MaxInterval: metav1.Duration{Duration: 20 * time.Second}, - }, - }, - JWKSCluster: &JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &JWKSTLSCertificate{ - TrustedCA: &JWKSTLSCertTrustedCA{ - Filename: "cert.crt", - }, - }, - ConnectTimeout: metav1.Duration{Duration: 890}, - }, - }, - }, - Issuer: "test-issuer", - Audiences: []string{"aud1", "aud2"}, - Locations: []*JWTLocation{ - { - Header: &JWTLocationHeader{ - Name: "Authorization", - ValuePrefix: "Bearer", - Forward: true, - }, - }, - { - QueryParam: &JWTLocationQueryParam{ - Name: "access-token", - }, - }, - { - Cookie: &JWTLocationCookie{ - Name: "session-id", - }, - }, - }, - Forwarding: &JWTForwardingConfig{ - HeaderName: "jwt-forward-header", - PadForwardPayloadHeader: true, - }, - ClockSkewSeconds: 20, - CacheConfig: &JWTCacheConfig{ - Size: 30, - }, - }, - }, - expectedErrMsgs: nil, - }, - - "valid - remote jwks with all fields with CaCertificateProviderInstance": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwt-provider", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - RequestTimeoutMs: 5000, - CacheDuration: metav1.Duration{Duration: 10 * time.Second}, - FetchAsynchronously: true, - RetryPolicy: &JWKSRetryPolicy{ - NumRetries: 3, - RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 5 * time.Second}, - MaxInterval: metav1.Duration{Duration: 20 * time.Second}, - }, - }, - JWKSCluster: &JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &JWKSTLSCertificate{ - CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{ - InstanceName: "InstanceName", - CertificateName: "ROOTCA", - }, - }, - ConnectTimeout: metav1.Duration{Duration: 890}, - }, - }, - }, - Issuer: "test-issuer", - Audiences: []string{"aud1", "aud2"}, - Locations: []*JWTLocation{ - { - Header: &JWTLocationHeader{ - Name: "Authorization", - ValuePrefix: "Bearer", - Forward: true, - }, - }, - { - QueryParam: &JWTLocationQueryParam{ - Name: "access-token", - }, - }, - { - Cookie: &JWTLocationCookie{ - Name: "session-id", - }, - }, - }, - Forwarding: &JWTForwardingConfig{ - HeaderName: "jwt-forward-header", - PadForwardPayloadHeader: true, - }, - ClockSkewSeconds: 20, - CacheConfig: &JWTCacheConfig{ - Size: 30, - }, - }, - }, - expectedErrMsgs: nil, - }, - - "invalid - nil jwks": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-no-jwks", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: nil, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-no-jwks" is invalid: spec.jsonWebKeySet: Invalid value: "null": jsonWebKeySet is required`, - }, - }, - - "invalid - empty jwks": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-no-jwks", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{}, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-no-jwks" is invalid: spec.jsonWebKeySet: Invalid value: "{}": exactly one of 'local' or 'remote' is required`, - }, - }, - - "invalid - local jwks with non-base64 string": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-base64", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Local: &LocalJWKS{ - JWKS: "not base64 encoded", - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-base64" is invalid: spec.jsonWebKeySet.local.jwks: Invalid value: "not base64 encoded": JWKS must be a valid base64-encoded string`, - }, - }, - - "invalid - both local and remote jwks set": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-local-and-remote", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Local: &LocalJWKS{Filename: "jwks.txt"}, - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-local-and-remote" is invalid: spec.jsonWebKeySet: Invalid value: "{\"local\":{\"filename\":\"jwks.txt\"},\"remote\":{\"uri\":\"https://jwks.example.com\",\"cacheDuration\":\"0s\"}}": exactly one of 'local' or 'remote' is required`, - }, - }, - - "invalid - remote jwks missing uri": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-missing-uri", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - FetchAsynchronously: true, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-missing-uri" is invalid: spec.jsonWebKeySet.remote.uri: Invalid value: "": remote JWKS URI is required`, - }, - }, - - "invalid - remote jwks invalid uri": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-invalid-uri", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "invalid-uri", - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.uri: Invalid value: "invalid-uri": remote JWKS URI is invalid`, - }, - }, - - "invalid - remote jwks invalid jwkcluster - all TLSCertificates fields set": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-invalid-uri", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - JWKSCluster: &JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &JWKSTLSCertificate{ - CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{ - InstanceName: "InstanceName", - }, - TrustedCA: &JWKSTLSCertTrustedCA{ - Filename: "cert.crt", - }, - }, - ConnectTimeout: metav1.Duration{Duration: 890}, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.jwksCluster.tlsCertificates: Invalid value:`, - `exactly one of 'trustedCa' or 'caCertificateProviderInstance' is required`, - }, - }, - - "invalid - remote jwks invalid jwkcluster - invalid discovery type": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-invalid-uri", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - JWKSCluster: &JWKSCluster{ - DiscoveryType: "FAKE_DNS", - ConnectTimeout: metav1.Duration{Duration: 890}, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.jwksCluster.discoveryType: Invalid value: "FAKE_DNS": unsupported jwks cluster discovery type.`, - }, - }, - - "invalid - remote jwks invalid jwkcluster - all trustedCa fields set": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-invalid-uri", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - JWKSCluster: &JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &JWKSTLSCertificate{ - TrustedCA: &JWKSTLSCertTrustedCA{ - Filename: "cert.crt", - EnvironmentVariable: "env-variable", - InlineString: "inline-string", - InlineBytes: []byte("inline-bytes"), - }, - }, - ConnectTimeout: metav1.Duration{Duration: 890}, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.jwksCluster.tlsCertificates.trustedCa: Invalid value:`, - `exactly one of 'filename', 'environmentVariable', 'inlineString' or 'inlineBytes' is required`, - }, - }, - - "invalid - remote jwks invalid jwkcluster - set 2 trustedCa fields": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-invalid-uri", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - JWKSCluster: &JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &JWKSTLSCertificate{ - TrustedCA: &JWKSTLSCertTrustedCA{ - Filename: "cert.crt", - EnvironmentVariable: "env-variable", - }, - }, - ConnectTimeout: metav1.Duration{Duration: 890}, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-invalid-uri" is invalid: spec.jsonWebKeySet.remote.jwksCluster.tlsCertificates.trustedCa: Invalid value:`, - `exactly one of 'filename', 'environmentVariable', 'inlineString' or 'inlineBytes' is required`, - }, - }, - - "invalid - JWT location with all fields": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-all-locations", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - }, - }, - Locations: []*JWTLocation{ - { - Header: &JWTLocationHeader{ - Name: "jwt-header", - }, - QueryParam: &JWTLocationQueryParam{ - Name: "jwt-query-param", - }, - Cookie: &JWTLocationCookie{ - Name: "jwt-cookie", - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-all-locations" is invalid: spec.locations[0]: Invalid value: "{\"header\":{\"name\":\"jwt-header\"},\"queryParam\":{\"name\":\"jwt-query-param\"},\"cookie\":{\"name\":\"jwt-cookie\"}}": exactly one of 'header', 'queryParam', or 'cookie' is required`, - }, - }, - - "invalid - JWT location with two fields": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-two-locations", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - }, - }, - Locations: []*JWTLocation{ - { - Header: &JWTLocationHeader{ - Name: "jwt-header", - }, - Cookie: &JWTLocationCookie{ - Name: "jwt-cookie", - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-two-locations" is invalid: spec.locations[0]: Invalid value: "{\"header\":{\"name\":\"jwt-header\"},\"cookie\":{\"name\":\"jwt-cookie\"}}": exactly one of 'header', 'queryParam', or 'cookie' is required`, - }, - }, - - "invalid - remote jwks retry policy maxInterval < baseInterval": { - input: &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwks-retry-intervals", - }, - Spec: JWTProviderSpec{ - JSONWebKeySet: &JSONWebKeySet{ - Remote: &RemoteJWKS{ - URI: "https://jwks.example.com", - RetryPolicy: &JWKSRetryPolicy{ - NumRetries: 0, - RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 100 * time.Second}, - MaxInterval: metav1.Duration{Duration: 10 * time.Second}, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-retry-intervals" is invalid: spec.jsonWebKeySet.remote.retryPolicy.retryPolicyBackOff: Invalid value: "{\"baseInterval\":\"1m40s\",\"maxInterval\":\"10s\"}": maxInterval should be greater or equal to baseInterval`, - }, - }, - } - - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - err := testCase.input.Validate(testCase.consulMeta) - if len(testCase.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range testCase.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } - -} - -func TestJWTProvider_AddFinalizer(t *testing.T) { - jwt := &JWTProvider{} - jwt.AddFinalizer("finalizer") - require.Equal(t, []string{"finalizer"}, jwt.ObjectMeta.Finalizers) -} - -func TestJWTProvider_RemoveFinalizer(t *testing.T) { - jwt := &JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{"f1", "f2"}, - }, - } - jwt.RemoveFinalizer("f1") - require.Equal(t, []string{"f2"}, jwt.ObjectMeta.Finalizers) -} - -func TestJWTProvider_SetSyncedCondition(t *testing.T) { - jwt := &JWTProvider{} - jwt.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, jwt.Status.Conditions[0].Status) - require.Equal(t, "reason", jwt.Status.Conditions[0].Reason) - require.Equal(t, "message", jwt.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, jwt.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestJWTProvider_SetLastSyncedTime(t *testing.T) { - jwt := &JWTProvider{} - syncedTime := metav1.NewTime(time.Now()) - jwt.SetLastSyncedTime(&syncedTime) - require.Equal(t, &syncedTime, jwt.Status.LastSyncedTime) -} - -func TestJWTProvider_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - jwt := &JWTProvider{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, jwt.SyncedConditionStatus()) - }) - } -} - -func TestJWTProvider_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&JWTProvider{}).GetCondition(ConditionSynced)) -} - -func TestJWTProvider_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&JWTProvider{}).SyncedConditionStatus()) -} - -func TestJWTProvider_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&JWTProvider{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestJWTProvider_ConsulKind(t *testing.T) { - require.Equal(t, capi.JWTProvider, (&JWTProvider{}).ConsulKind()) -} - -func TestJWTProvider_KubeKind(t *testing.T) { - require.Equal(t, "jwtprovider", (&JWTProvider{}).KubeKind()) -} - -func TestJWTProvider_ConsulName(t *testing.T) { - require.Equal(t, "foo", (&JWTProvider{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).ConsulName()) -} - -func TestJWTProvider_KubernetesName(t *testing.T) { - require.Equal(t, "foo", (&JWTProvider{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).KubernetesName()) -} - -func TestJWTProvider_ConsulNamespace(t *testing.T) { - require.Equal(t, common.DefaultConsulNamespace, (&JWTProvider{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}}).ConsulMirroringNS()) -} - -func TestJWTProvider_ConsulGlobalResource(t *testing.T) { - require.True(t, (&JWTProvider{}).ConsulGlobalResource()) -} - -func TestJWTProvider_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - jwt := &JWTProvider{ - ObjectMeta: meta, - } - require.Equal(t, meta, jwt.GetObjectMeta()) -} diff --git a/control-plane/api/v1alpha1/jwtprovider_webhook.go b/control-plane/api/v1alpha1/jwtprovider_webhook.go deleted file mode 100644 index c434c83c01..0000000000 --- a/control-plane/api/v1alpha1/jwtprovider_webhook.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// +kubebuilder:object:generate=false - -type JWTProviderWebhook struct { - Logger logr.Logger - - // ConsulMeta contains metadata specific to the Consul installation. - ConsulMeta common.ConsulMeta - - decoder *admission.Decoder - client.Client -} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/controller/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-jwtprovider,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=jwtproviders,versions=v1alpha1,name=mutate-jwtprovider.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *JWTProviderWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource JWTProvider - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateConfigEntry(ctx, req, v.Logger, v, &resource, v.ConsulMeta) -} - -func (v *JWTProviderWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { - var resourceList JWTProviderList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.ConfigEntryResource - for _, item := range resourceList.Items { - entries = append(entries, common.ConfigEntryResource(&item)) - } - return entries, nil -} - -func (v *JWTProviderWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/v1alpha1/mesh_types.go b/control-plane/api/v1alpha1/mesh_types.go index 162132a47a..502e567829 100644 --- a/control-plane/api/v1alpha1/mesh_types.go +++ b/control-plane/api/v1alpha1/mesh_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -51,9 +48,6 @@ type MeshList struct { type MeshSpec struct { // TransparentProxy controls the configuration specific to proxies in "transparent" mode. Added in v1.10.0. TransparentProxy TransparentProxyMeshConfig `json:"transparentProxy,omitempty"` - // AllowEnablingPermissiveMutualTLS must be true in order to allow setting - // MutualTLSMode=permissive in either service-defaults or proxy-defaults. - AllowEnablingPermissiveMutualTLS bool `json:"allowEnablingPermissiveMutualTLS,omitempty"` // TLS defines the TLS configuration for the service mesh. TLS *MeshTLSConfig `json:"tls,omitempty"` // HTTP defines the HTTP configuration for the service mesh. @@ -195,12 +189,11 @@ func (in *Mesh) SetLastSyncedTime(time *metav1.Time) { func (in *Mesh) ToConsul(datacenter string) capi.ConfigEntry { return &capi.MeshConfigEntry{ - TransparentProxy: in.Spec.TransparentProxy.toConsul(), - AllowEnablingPermissiveMutualTLS: in.Spec.AllowEnablingPermissiveMutualTLS, - TLS: in.Spec.TLS.toConsul(), - HTTP: in.Spec.HTTP.toConsul(), - Peering: in.Spec.Peering.toConsul(), - Meta: meta(datacenter), + TransparentProxy: in.Spec.TransparentProxy.toConsul(), + TLS: in.Spec.TLS.toConsul(), + HTTP: in.Spec.HTTP.toConsul(), + Peering: in.Spec.Peering.toConsul(), + Meta: meta(datacenter), } } diff --git a/control-plane/api/v1alpha1/mesh_types_test.go b/control-plane/api/v1alpha1/mesh_types_test.go index f2ea714f60..392c38d354 100644 --- a/control-plane/api/v1alpha1/mesh_types_test.go +++ b/control-plane/api/v1alpha1/mesh_types_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -48,7 +45,6 @@ func TestMesh_MatchesConsul(t *testing.T) { TransparentProxy: TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, - AllowEnablingPermissiveMutualTLS: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -73,7 +69,6 @@ func TestMesh_MatchesConsul(t *testing.T) { TransparentProxy: capi.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, - AllowEnablingPermissiveMutualTLS: true, TLS: &capi.MeshTLSConfig{ Incoming: &capi.MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -150,7 +145,6 @@ func TestMesh_ToConsul(t *testing.T) { TransparentProxy: TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, - AllowEnablingPermissiveMutualTLS: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -175,7 +169,6 @@ func TestMesh_ToConsul(t *testing.T) { TransparentProxy: capi.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, - AllowEnablingPermissiveMutualTLS: true, TLS: &capi.MeshTLSConfig{ Incoming: &capi.MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", diff --git a/control-plane/api/v1alpha1/mesh_webhook.go b/control-plane/api/v1alpha1/mesh_webhook.go index 1c0ea3088e..5c714c4e5f 100644 --- a/control-plane/api/v1alpha1/mesh_webhook.go +++ b/control-plane/api/v1alpha1/mesh_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/mesh_webhook_test.go b/control-plane/api/v1alpha1/mesh_webhook_test.go index 2266a6b77e..83cedb0ba4 100644 --- a/control-plane/api/v1alpha1/mesh_webhook_test.go +++ b/control-plane/api/v1alpha1/mesh_webhook_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringacceptor_types.go b/control-plane/api/v1alpha1/peeringacceptor_types.go index e1ca013475..032870cb80 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_types.go +++ b/control-plane/api/v1alpha1/peeringacceptor_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringacceptor_types_test.go b/control-plane/api/v1alpha1/peeringacceptor_types_test.go index d7c05e7e2c..33d437f46a 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_types_test.go +++ b/control-plane/api/v1alpha1/peeringacceptor_types_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringacceptor_webhook.go b/control-plane/api/v1alpha1/peeringacceptor_webhook.go index 2bb48b3580..60367c1384 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_webhook.go +++ b/control-plane/api/v1alpha1/peeringacceptor_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go index 251ab87c8e..02ddbca588 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringdialer_types.go b/control-plane/api/v1alpha1/peeringdialer_types.go index 89c16bf3ad..4ddde3fe2f 100644 --- a/control-plane/api/v1alpha1/peeringdialer_types.go +++ b/control-plane/api/v1alpha1/peeringdialer_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringdialer_types_test.go b/control-plane/api/v1alpha1/peeringdialer_types_test.go index 69c3569acd..7e358facb8 100644 --- a/control-plane/api/v1alpha1/peeringdialer_types_test.go +++ b/control-plane/api/v1alpha1/peeringdialer_types_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringdialer_webhook.go b/control-plane/api/v1alpha1/peeringdialer_webhook.go index 76c2011e4c..fc0b1c38f6 100644 --- a/control-plane/api/v1alpha1/peeringdialer_webhook.go +++ b/control-plane/api/v1alpha1/peeringdialer_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go index 42372451d1..df69923a92 100644 --- a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index 7b1529b941..215ec708ff 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -67,17 +64,6 @@ type ProxyDefaultsSpec struct { // Note: This cannot be set using the CRD and should be set using annotations on the // services that are part of the mesh. TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"` - // MutualTLSMode controls whether mutual TLS is required for all incoming - // connections when transparent proxy is enabled. This can be set to - // "permissive" or "strict". "strict" is the default which requires mutual - // TLS for incoming connections. In the insecure "permissive" mode, - // connections to the sidecar proxy public listener port require mutual - // TLS, but connections to the service port do not require mutual TLS and - // are proxied to the application unmodified. Note: Intentions are not - // enforced for non-mTLS connections. To keep your services secure, we - // recommend using "strict" mode whenever possible and enabling - // "permissive" mode only when necessary. - MutualTLSMode MutualTLSMode `json:"mutualTLSMode,omitempty"` // Config is an arbitrary map of configuration values used by Connect proxies. // Any values that your proxy allows can be configured globally here. // Supports JSON config values. See https://www.consul.io/docs/connect/proxies/envoy#configuration-formatting @@ -89,15 +75,6 @@ type ProxyDefaultsSpec struct { MeshGateway MeshGateway `json:"meshGateway,omitempty"` // Expose controls the default expose path configuration for Envoy. Expose Expose `json:"expose,omitempty"` - // AccessLogs controls all envoy instances' access logging configuration. - AccessLogs *AccessLogs `json:"accessLogs,omitempty"` - // EnvoyExtensions are a list of extensions to modify Envoy proxy configuration. - EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` - // FailoverPolicy specifies the exact mechanism used for failover. - FailoverPolicy *FailoverPolicy `json:"failoverPolicy,omitempty"` - // PrioritizeByLocality controls whether the locality of services within the - // local partition will be used to prioritize connectivity. - PrioritizeByLocality *PrioritizeByLocality `json:"prioritizeByLocality,omitempty"` } func (in *ProxyDefaults) GetObjectMeta() metav1.ObjectMeta { @@ -182,18 +159,13 @@ func (in *ProxyDefaults) SetLastSyncedTime(time *metav1.Time) { func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry { consulConfig := in.convertConfig() return &capi.ProxyConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - MeshGateway: in.Spec.MeshGateway.toConsul(), - Expose: in.Spec.Expose.toConsul(), - Config: consulConfig, - TransparentProxy: in.Spec.TransparentProxy.toConsul(), - MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), - AccessLogs: in.Spec.AccessLogs.toConsul(), - EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), - FailoverPolicy: in.Spec.FailoverPolicy.toConsul(), - PrioritizeByLocality: in.Spec.PrioritizeByLocality.toConsul(), - Meta: meta(datacenter), + Kind: in.ConsulKind(), + Name: in.ConsulName(), + MeshGateway: in.Spec.MeshGateway.toConsul(), + Expose: in.Spec.Expose.toConsul(), + Config: consulConfig, + TransparentProxy: in.Spec.TransparentProxy.toConsul(), + Meta: meta(datacenter), } } @@ -217,23 +189,13 @@ func (in *ProxyDefaults) Validate(_ common.ConsulMeta) error { if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil { allErrs = append(allErrs, err) } - if err := in.Spec.MutualTLSMode.validate(); err != nil { - allErrs = append(allErrs, field.Invalid(path.Child("mutualTLSMode"), in.Spec.MutualTLSMode, err.Error())) - } if err := in.Spec.Mode.validate(path.Child("mode")); err != nil { allErrs = append(allErrs, err) } if err := in.validateConfig(path.Child("config")); err != nil { allErrs = append(allErrs, err) } - if err := in.Spec.AccessLogs.validate(path.Child("accessLogs")); err != nil { - allErrs = append(allErrs, err) - } allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) - allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) - allErrs = append(allErrs, in.Spec.FailoverPolicy.validate(path.Child("failoverPolicy"))...) - allErrs = append(allErrs, in.Spec.PrioritizeByLocality.validate(path.Child("prioritizeByLocality"))...) - if len(allErrs) > 0 { return apierrors.NewInvalid( schema.GroupKind{Group: ConsulHashicorpGroup, Kind: ProxyDefaultsKubeKind}, @@ -271,93 +233,7 @@ func (in *ProxyDefaults) validateConfig(path *field.Path) *field.Error { } var outConfig map[string]interface{} if err := json.Unmarshal(in.Spec.Config, &outConfig); err != nil { - return field.Invalid(path, string(in.Spec.Config), fmt.Sprintf(`must be valid map value: %s`, err)) + return field.Invalid(path, in.Spec.Config, fmt.Sprintf(`must be valid map value: %s`, err)) } return nil } - -// LogSinkType represents the destination for Envoy access logs. -// One of "file", "stderr", or "stdout". -type LogSinkType string - -const ( - DefaultLogSinkType LogSinkType = "" - FileLogSinkType LogSinkType = "file" - StdErrLogSinkType LogSinkType = "stderr" - StdOutLogSinkType LogSinkType = "stdout" -) - -// AccessLogs describes the access logging configuration for all Envoy proxies in the mesh. -type AccessLogs struct { - // Enabled turns on all access logging - Enabled bool `json:"enabled,omitempty"` - - // DisableListenerLogs turns off just listener logs for connections rejected by Envoy because they don't - // have a matching listener filter. - DisableListenerLogs bool `json:"disableListenerLogs,omitempty"` - - // Type selects the output for logs - // one of "file", "stderr". "stdout" - Type LogSinkType `json:"type,omitempty"` - - // Path is the output file to write logs for file-type logging - Path string `json:"path,omitempty"` - - // JSONFormat is a JSON-formatted string of an Envoy access log format dictionary. - // See for more info on formatting: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-dictionaries - // Defining JSONFormat and TextFormat is invalid. - JSONFormat string `json:"jsonFormat,omitempty"` - - // TextFormat is a representation of Envoy access logs format. - // See for more info on formatting: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-strings - // Defining JSONFormat and TextFormat is invalid. - TextFormat string `json:"textFormat,omitempty"` -} - -func (in *AccessLogs) validate(path *field.Path) *field.Error { - if in == nil { - return nil - } - - switch in.Type { - case DefaultLogSinkType, StdErrLogSinkType, StdOutLogSinkType: - // OK - case FileLogSinkType: - if in.Path == "" { - return field.Invalid(path.Child("path"), in.Path, "path must be specified when using file type access logs") - } - default: - return field.Invalid(path.Child("type"), in.Type, "invalid access log type (must be one of \"stdout\", \"stderr\", \"file\"") - } - - if in.JSONFormat != "" && in.TextFormat != "" { - return field.Invalid(path.Child("textFormat"), in.TextFormat, "cannot specify both access log jsonFormat and textFormat") - } - - if in.Type != FileLogSinkType && in.Path != "" { - return field.Invalid(path.Child("path"), in.Path, "path is only valid for file type access logs") - } - - if in.JSONFormat != "" { - msg := json.RawMessage{} - if err := json.Unmarshal([]byte(in.JSONFormat), &msg); err != nil { - return field.Invalid(path.Child("jsonFormat"), in.JSONFormat, "invalid access log json") - } - } - - return nil -} - -func (in *AccessLogs) toConsul() *capi.AccessLogsConfig { - if in == nil { - return nil - } - return &capi.AccessLogsConfig{ - Enabled: in.Enabled, - DisableListenerLogs: in.DisableListenerLogs, - JSONFormat: in.JSONFormat, - Path: in.Path, - TextFormat: in.TextFormat, - Type: capi.LogSinkType(in.Type), - } -} diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 6a965fce3a..2950a3a36e 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -74,33 +71,6 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: MutualTLSModePermissive, - AccessLogs: &AccessLogs{ - Enabled: true, - DisableListenerLogs: true, - Type: FileLogSinkType, - Path: "/var/log/envoy.logs", - TextFormat: "ITS WORKING %START_TIME%", - }, - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), - Required: true, - }, - }, - FailoverPolicy: &FailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-1"}, - }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "failover", - }, }, }, Theirs: &capi.ProxyConfigEntry{ @@ -133,40 +103,6 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: capi.MutualTLSModePermissive, - AccessLogs: &capi.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: capi.FileLogSinkType, - Path: "/var/log/envoy.logs", - TextFormat: "ITS WORKING %START_TIME%", - }, - EnvoyExtensions: []capi.EnvoyExtension{ - { - Name: "aws_request_signing", - Arguments: map[string]interface{}{ - "AWSServiceName": "s3", - "Region": "us-west-2", - }, - Required: false, - }, - { - Name: "zipkin", - Arguments: map[string]interface{}{ - "ClusterName": "zipkin_cluster", - "Port": "9411", - "CollectorEndpoint": "/api/v2/spans", - }, - Required: true, - }, - }, - FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-1"}, - }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "failover", - }, }, Matches: true, }, @@ -300,33 +236,6 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: MutualTLSModeStrict, - AccessLogs: &AccessLogs{ - Enabled: true, - DisableListenerLogs: true, - Type: FileLogSinkType, - Path: "/var/log/envoy.logs", - TextFormat: "ITS WORKING %START_TIME%", - }, - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), - Required: true, - }, - }, - FailoverPolicy: &FailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-1"}, - }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "none", - }, }, }, Exp: &capi.ProxyConfigEntry{ @@ -360,40 +269,6 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: capi.MutualTLSModeStrict, - AccessLogs: &capi.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: capi.FileLogSinkType, - Path: "/var/log/envoy.logs", - TextFormat: "ITS WORKING %START_TIME%", - }, - EnvoyExtensions: []capi.EnvoyExtension{ - { - Name: "aws_request_signing", - Arguments: map[string]interface{}{ - "AWSServiceName": "s3", - "Region": "us-west-2", - }, - Required: false, - }, - { - Name: "zipkin", - Arguments: map[string]interface{}{ - "ClusterName": "zipkin_cluster", - "Port": "9411", - "CollectorEndpoint": "/api/v2/spans", - }, - Required: true, - }, - }, - FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-1"}, - }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "none", - }, Meta: map[string]string{ common.SourceKey: common.SourceValue, common.DatacenterKey: "datacenter", @@ -418,30 +293,8 @@ func TestProxyDefaults_Validate(t *testing.T) { input *ProxyDefaults expectedErrMsg string }{ - "valid envoyExtension": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), - Required: true, - }, - }, - }, - }, - expectedErrMsg: "", - }, "meshgateway.mode": { - input: &ProxyDefaults{ + &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -451,10 +304,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.meshGateway.mode: Invalid value: "foobar": must be one of "remote", "local", "none", ""`, + `proxydefaults.consul.hashicorp.com "global" is invalid: spec.meshGateway.mode: Invalid value: "foobar": must be one of "remote", "local", "none", ""`, }, "expose.paths[].protocol": { - input: &ProxyDefaults{ + &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -469,10 +322,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].protocol: Invalid value: "invalid-protocol": must be one of "http", "http2"`, + `proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].protocol: Invalid value: "invalid-protocol": must be one of "http", "http2"`, }, "expose.paths[].path": { - input: &ProxyDefaults{ + &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -487,10 +340,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].path: Invalid value: "invalid-path": must begin with a '/'`, + `proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].path: Invalid value: "invalid-path": must begin with a '/'`, }, "transparentProxy.outboundListenerPort": { - input: &ProxyDefaults{ + &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -500,10 +353,10 @@ func TestProxyDefaults_Validate(t *testing.T) { }, }, }, - expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port", + "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port", }, "mode": { - input: &ProxyDefaults{ + &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -511,174 +364,10 @@ func TestProxyDefaults_Validate(t *testing.T) { Mode: proxyModeRef("transparent"), }, }, - expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode", - }, - "mutualTLSMode": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - MutualTLSMode: MutualTLSMode("asdf"), - }, - }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.mutualTLSMode: Invalid value: "asdf": Must be one of "", "strict", or "permissive".`, - }, - "accessLogs.type": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - AccessLogs: &AccessLogs{ - Type: "foo", - }, - }, - }, - expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.type: Invalid value: \"foo\": invalid access log type (must be one of \"stdout\", \"stderr\", \"file\"", - }, - "accessLogs.path missing": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - AccessLogs: &AccessLogs{ - Type: "file", - }, - }, - }, - expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.path: Invalid value: \"\": path must be specified when using file type access logs", - }, - "accessLogs.path for wrong type": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - AccessLogs: &AccessLogs{ - Path: "/var/log/envoy.logs", - }, - }, - }, - expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.path: Invalid value: \"/var/log/envoy.logs\": path is only valid for file type access logs", - }, - "accessLogs.jsonFormat": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - AccessLogs: &AccessLogs{ - JSONFormat: "{ \"start_time\": \"%START_TIME\"", // intentionally missing the closing brace - }, - }, - }, - expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.jsonFormat: Invalid value: \"{ \\\"start_time\\\": \\\"%START_TIME\\\"\": invalid access log json", - }, - "accessLogs.textFormat": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - AccessLogs: &AccessLogs{ - JSONFormat: "{ \"start_time\": \"%START_TIME\" }", - TextFormat: "MY START TIME %START_TIME", - }, - }, - }, - expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.accessLogs.textFormat: Invalid value: \"MY START TIME %START_TIME\": cannot specify both access log jsonFormat and textFormat", - }, - "envoyExtension.arguments single empty": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: nil, - Required: true, - }, - }, - }, - }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.envoyExtensions.envoyExtension[1].arguments: Required value: arguments must be defined`, - }, - "envoyExtension.arguments multi empty": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: nil, - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: nil, - Required: true, - }, - }, - }, - }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: [spec.envoyExtensions.envoyExtension[0].arguments: Required value: arguments must be defined, spec.envoyExtensions.envoyExtension[1].arguments: Required value: arguments must be defined]`, - }, - "envoyExtension.arguments invalid json": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"SOME_INVALID_JSON"}`), - Required: false, - }, - }, - }, - }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.envoyExtensions.envoyExtension[0].arguments: Invalid value: "{\"SOME_INVALID_JSON\"}": must be valid map value: invalid character '}' after object key`, - }, - "failoverPolicy.mode invalid": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - FailoverPolicy: &FailoverPolicy{ - Mode: "wrong-mode", - }, - }, - }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.failoverPolicy.mode: Invalid value: "wrong-mode": must be one of "", "sequential", "order-by-locality"`, - }, - "prioritize by locality invalid": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "wrong-mode", - }, - }, - }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.prioritizeByLocality.mode: Invalid value: "wrong-mode": must be one of "", "none", "failover"`, + "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode", }, "multi-error": { - input: &ProxyDefaults{ + &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ Name: "global", }, @@ -697,14 +386,10 @@ func TestProxyDefaults_Validate(t *testing.T) { TransparentProxy: &TransparentProxy{ OutboundListenerPort: 1000, }, - AccessLogs: &AccessLogs{ - JSONFormat: "{ \"start_time\": \"%START_TIME\" }", - TextFormat: "MY START TIME %START_TIME", - }, Mode: proxyModeRef("transparent"), }, }, - expectedErrMsg: "proxydefaults.consul.hashicorp.com \"global\" is invalid: [spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.accessLogs.textFormat: Invalid value: \"MY START TIME %START_TIME\": cannot specify both access log jsonFormat and textFormat, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]", + "proxydefaults.consul.hashicorp.com \"global\" is invalid: [spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]", }, } for name, testCase := range cases { diff --git a/control-plane/api/v1alpha1/proxydefaults_webhook.go b/control-plane/api/v1alpha1/proxydefaults_webhook.go index ced4853b12..3873516074 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go index ade806b7e3..c1aebe3e11 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -49,7 +46,7 @@ func TestValidateProxyDefault(t *testing.T) { }, expAllow: false, // This error message is because the value "1" is valid JSON but is an invalid map - expErrMessage: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.config: Invalid value: \"1\": must be valid map value: json: cannot unmarshal number into Go value of type map[string]interface {}", + expErrMessage: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.config: Invalid value: json.RawMessage{0x31}: must be valid map value: json: cannot unmarshal number into Go value of type map[string]interface {}", }, "proxy default exists": { existingResources: []runtime.Object{&ProxyDefaults{ diff --git a/control-plane/api/v1alpha1/routeauthfilter_types.go b/control-plane/api/v1alpha1/routeauthfilter_types.go deleted file mode 100644 index 1fb2b02030..0000000000 --- a/control-plane/api/v1alpha1/routeauthfilter_types.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - RouteAuthFilterKind = "RouteAuthFilter" -) - -func init() { - SchemeBuilder.Register(&RouteAuthFilter{}, &RouteAuthFilterList{}) -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// RouteAuthFilter is the Schema for the routeauthfilters API. -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type RouteAuthFilter struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RouteAuthFilterSpec `json:"spec,omitempty"` - Status RouteAuthFilterStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// RouteAuthFilterList contains a list of RouteAuthFilter. -type RouteAuthFilterList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RouteAuthFilter `json:"items"` -} - -// RouteAuthFilterSpec defines the desired state of RouteAuthFilter. -type RouteAuthFilterSpec struct { - // This re-uses the JWT requirement type from Gateway Policy Types. - //+kubebuilder:validation:Optional - JWT *GatewayJWTRequirement `json:"jwt,omitempty"` -} - -// RouteAuthFilterStatus defines the observed state of the gateway. -type RouteAuthFilterStatus struct { - // Conditions describe the current conditions of the Filter. - // - // - // Known condition types are: - // - // * "Accepted" - // * "ResolvedRefs" - // - // +optional - // +listType=map - // +listMapKey=type - // +kubebuilder:validation:MaxItems=8 - // +kubebuilder:default={{type: "Accepted", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"},{type: "ResolvedRefs", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}} - Conditions []metav1.Condition `json:"conditions,omitempty"` -} diff --git a/control-plane/api/v1alpha1/routeretryfilter_types.go b/control-plane/api/v1alpha1/routeretryfilter_types.go deleted file mode 100644 index 79fa85c608..0000000000 --- a/control-plane/api/v1alpha1/routeretryfilter_types.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func init() { - SchemeBuilder.Register(&RouteRetryFilter{}, &RouteRetryFilterList{}) -} - -const RouteRetryFilterKind = "RouteRetryFilter" - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// RouteRetryFilter is the Schema for the routeretryfilters API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type RouteRetryFilter struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RouteRetryFilterSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// RouteRetryFilterList contains a list of RouteRetryFilter. -type RouteRetryFilterList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RouteRetryFilter `json:"items"` -} - -// RouteRetryFilterSpec defines the desired state of RouteRetryFilter. -type RouteRetryFilterSpec struct { - // +kubebuilder:validation:Minimum:=0 - // +kubebuilder:validation:Optional - NumRetries *uint32 `json:"numRetries"` - // +kubebuilder:validation:Optional - RetryOn []string `json:"retryOn"` - // +kubebuilder:validation:Optional - RetryOnStatusCodes []uint32 `json:"retryOnStatusCodes"` - // +kubebuilder:validation:Optional - RetryOnConnectFailure *bool `json:"retryOnConnectFailure"` -} - -func (h *RouteRetryFilter) GetNamespace() string { - return h.Namespace -} diff --git a/control-plane/api/v1alpha1/routetimeoutfilter_types.go b/control-plane/api/v1alpha1/routetimeoutfilter_types.go deleted file mode 100644 index 96c8b79b30..0000000000 --- a/control-plane/api/v1alpha1/routetimeoutfilter_types.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func init() { - SchemeBuilder.Register(&RouteTimeoutFilter{}, &RouteTimeoutFilterList{}) -} - -const RouteTimeoutFilterKind = "RouteTimeoutFilter" - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// RouteTimeoutFilter is the Schema for the httproutetimeoutfilters API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type RouteTimeoutFilter struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RouteTimeoutFilterSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// RouteTimeoutFilterList contains a list of RouteTimeoutFilter. -type RouteTimeoutFilterList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RouteTimeoutFilter `json:"items"` -} - -// RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. -type RouteTimeoutFilterSpec struct { - // +kubebuilder:validation:Optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=duration - RequestTimeout metav1.Duration `json:"requestTimeout"` - - // +kubebuilder:validation:Optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=duration - IdleTimeout metav1.Duration `json:"idleTimeout"` -} - -func (h *RouteTimeoutFilter) GetNamespace() string { - return h.Namespace -} diff --git a/control-plane/api/v1alpha1/samenessgroup_types.go b/control-plane/api/v1alpha1/samenessgroup_types.go deleted file mode 100644 index 2b5dbd372f..0000000000 --- a/control-plane/api/v1alpha1/samenessgroup_types.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "encoding/json" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/consul/api" - capi "github.com/hashicorp/consul/api" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -const ( - SamenessGroupKubeKind string = "samenessgroup" -) - -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - -func init() { - SchemeBuilder.Register(&SamenessGroup{}, &SamenessGroupList{}) -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// SamenessGroup is the Schema for the samenessgroups API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="sameness-group" -type SamenessGroup struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec SamenessGroupSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// SamenessGroupList contains a list of SamenessGroup. -type SamenessGroupList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []SamenessGroup `json:"items"` -} - -// SamenessGroupSpec defines the desired state of SamenessGroup. -type SamenessGroupSpec struct { - // DefaultForFailover indicates that upstream requests to members of the given sameness group will implicitly failover between members of this sameness group. - // When DefaultForFailover is true, the local partition must be a member of the sameness group or IncludeLocal must be set to true. - DefaultForFailover bool `json:"defaultForFailover,omitempty"` - // IncludeLocal is used to include the local partition as the first member of the sameness group. - // The local partition can only be a member of a single sameness group. - IncludeLocal bool `json:"includeLocal,omitempty"` - // Members are the partitions and peers that are part of the sameness group. - // If a member of a sameness group does not exist, it will be ignored. - Members []SamenessGroupMember `json:"members,omitempty"` -} - -type SamenessGroupMember struct { - // The partitions and peers that are part of the sameness group. - // A sameness group member cannot define both peer and partition at the same time. - Partition string `json:"partition,omitempty"` - Peer string `json:"peer,omitempty"` -} - -func (in *SamenessGroup) GetObjectMeta() metav1.ObjectMeta { - return in.ObjectMeta -} - -func (in *SamenessGroup) AddFinalizer(name string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), name) -} - -func (in *SamenessGroup) RemoveFinalizer(name string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != name { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *SamenessGroup) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *SamenessGroup) ConsulKind() string { - return capi.SamenessGroup -} - -func (in *SamenessGroup) ConsulGlobalResource() bool { - return false -} - -func (in *SamenessGroup) ConsulMirroringNS() string { - return common.DefaultConsulNamespace -} - -func (in *SamenessGroup) KubeKind() string { - return SamenessGroupKubeKind -} - -func (in *SamenessGroup) ConsulName() string { - return in.ObjectMeta.Name -} - -func (in *SamenessGroup) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *SamenessGroup) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *SamenessGroup) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *SamenessGroup) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *SamenessGroup) SyncedConditionStatus() corev1.ConditionStatus { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown - } - return cond.Status -} - -func (in *SamenessGroup) ToConsul(datacenter string) api.ConfigEntry { - return &capi.SamenessGroupConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - DefaultForFailover: in.Spec.DefaultForFailover, - IncludeLocal: in.Spec.IncludeLocal, - Members: SamenessGroupMembers(in.Spec.Members).toConsul(), - Meta: meta(datacenter), - } -} - -func (in *SamenessGroup) MatchesConsul(candidate api.ConfigEntry) bool { - configEntry, ok := candidate.(*capi.SamenessGroupConfigEntry) - if !ok { - return false - } - - specialEquality := cmp.Options{ - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Members.Partition" - }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), - } - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.SamenessGroupConfigEntry{}, "Partition", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) -} - -func (in *SamenessGroup) Validate(consulMeta common.ConsulMeta) error { - var allErrs field.ErrorList - path := field.NewPath("spec") - - if in == nil { - return nil - } - if in.Name == "" { - allErrs = append(allErrs, field.Invalid(path.Child("name"), in.Name, "sameness groups must have a name defined")) - } - - partition := consulMeta.Partition - includesLocal := in.Spec.IncludeLocal - - if in.ObjectMeta.Namespace != "default" && in.ObjectMeta.Namespace != "" { - allErrs = append(allErrs, field.Invalid(path.Child("name"), consulMeta.DestinationNamespace, "sameness groups must reside in the default namespace")) - } - - if len(in.Spec.Members) == 0 { - asJSON, _ := json.Marshal(in.Spec.Members) - allErrs = append(allErrs, field.Invalid(path.Child("members"), string(asJSON), "sameness groups must have at least one member")) - } - - seenMembers := make(map[SamenessGroupMember]struct{}) - for i, m := range in.Spec.Members { - if partition == m.Partition { - includesLocal = true - } - if err := m.validate(path.Child("members").Index(i)); err != nil { - allErrs = append(allErrs, err) - } - if _, ok := seenMembers[m]; ok { - asJSON, _ := json.Marshal(m) - allErrs = append(allErrs, field.Invalid(path.Child("members").Index(i), string(asJSON), "sameness group members must be unique")) - } - seenMembers[m] = struct{}{} - - } - - if !includesLocal { - allErrs = append(allErrs, field.Invalid(path.Child("members"), in.Spec.IncludeLocal, "the local partition must be a member of sameness groups")) - } - - if len(allErrs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: ConsulHashicorpGroup, Kind: SamenessGroupKubeKind}, - in.KubernetesName(), allErrs) - } - - return nil -} - -// DefaultNamespaceFields has no behaviour here as sameness-groups have no namespace specific fields. -func (in *SamenessGroup) DefaultNamespaceFields(_ common.ConsulMeta) { -} - -type SamenessGroupMembers []SamenessGroupMember - -func (in SamenessGroupMembers) toConsul() []capi.SamenessGroupMember { - if in == nil { - return nil - } - - outMembers := make([]capi.SamenessGroupMember, 0, len(in)) - for _, e := range in { - consulMember := capi.SamenessGroupMember{ - Peer: e.Peer, - Partition: e.Partition, - } - outMembers = append(outMembers, consulMember) - } - return outMembers -} - -func (in *SamenessGroupMember) validate(path *field.Path) *field.Error { - asJSON, _ := json.Marshal(in) - - if in == nil { - return field.Invalid(path, string(asJSON), "sameness group member is nil") - } - if in.isEmpty() { - return field.Invalid(path, string(asJSON), "sameness group members must specify either partition or peer") - } - // We do not allow referencing peer connections in other partitions. - if in.Peer != "" && in.Partition != "" { - return field.Invalid(path, string(asJSON), "sameness group members cannot specify both partition and peer in the same entry") - } - return nil -} - -func (in *SamenessGroupMember) isEmpty() bool { - return in.Peer == "" && in.Partition == "" -} diff --git a/control-plane/api/v1alpha1/samenessgroup_types_test.go b/control-plane/api/v1alpha1/samenessgroup_types_test.go deleted file mode 100644 index 976f9c8db7..0000000000 --- a/control-plane/api/v1alpha1/samenessgroup_types_test.go +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "testing" - "time" - - capi "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -func TestSamenessGroups_ToConsul(t *testing.T) { - cases := map[string]struct { - input *SamenessGroup - expected *capi.SamenessGroupConfigEntry - }{ - "empty fields": { - &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: SamenessGroupSpec{}, - }, - &capi.SamenessGroupConfigEntry{ - Name: "foo", - Kind: capi.SamenessGroup, - Meta: map[string]string{ - common.SourceKey: common.SourceValue, - common.DatacenterKey: "datacenter", - }, - }, - }, - "every field set": { - &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []SamenessGroupMember{ - { - Peer: "peer2", - }, - { - Partition: "p2", - }, - }, - }, - }, - &capi.SamenessGroupConfigEntry{ - Name: "foo", - Kind: capi.SamenessGroup, - Meta: map[string]string{ - common.SourceKey: common.SourceValue, - common.DatacenterKey: "datacenter", - }, - DefaultForFailover: true, - IncludeLocal: true, - Members: []capi.SamenessGroupMember{ - { - Peer: "peer2", - }, - { - Partition: "p2", - }, - }, - }, - }, - } - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - output := testCase.input.ToConsul("datacenter") - require.Equal(t, testCase.expected, output) - }) - } -} - -func TestSamenessGroups_MatchesConsul(t *testing.T) { - cases := map[string]struct { - internal *SamenessGroup - consul capi.ConfigEntry - matches bool - }{ - "empty fields matches": { - &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-test-sameness-group", - }, - Spec: SamenessGroupSpec{}, - }, - &capi.SamenessGroupConfigEntry{ - Kind: capi.SamenessGroup, - Name: "my-test-sameness-group", - CreateIndex: 1, - ModifyIndex: 2, - Meta: map[string]string{ - common.SourceKey: common.SourceValue, - common.DatacenterKey: "datacenter", - }, - }, - true, - }, - "all fields populated matches": { - &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-test-sameness-group", - }, - Spec: SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []SamenessGroupMember{ - { - Peer: "peer2", - }, - { - Partition: "p2", - }, - { - Peer: "test-peer", - }, - }, - }, - }, - &capi.SamenessGroupConfigEntry{ - Kind: capi.SamenessGroup, - Name: "my-test-sameness-group", - Meta: map[string]string{ - common.SourceKey: common.SourceValue, - common.DatacenterKey: "datacenter", - }, - DefaultForFailover: true, - IncludeLocal: true, - Members: []capi.SamenessGroupMember{ - { - Peer: "peer2", - }, - { - Partition: "p2", - }, - { - Peer: "test-peer", - Partition: "default", - }, - }, - }, - true, - }, - } - - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - require.Equal(t, testCase.matches, testCase.internal.MatchesConsul(testCase.consul)) - }) - } -} - -func TestSamenessGroups_Validate(t *testing.T) { - cases := map[string]struct { - input *SamenessGroup - partitionsEnabled bool - expectedErrMsg string - }{ - "valid": { - input: &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-sameness-group", - }, - Spec: SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []SamenessGroupMember{ - { - Peer: "peer2", - Partition: "", - }, - { - Peer: "", - Partition: "p2", - }, - }, - }, - }, - partitionsEnabled: true, - expectedErrMsg: "", - }, - "invalid - with peer and partition both": { - input: &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-sameness-group", - }, - Spec: SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []SamenessGroupMember{ - { - Peer: "peer2", - Partition: "p2", - }, - }, - }, - }, - partitionsEnabled: true, - expectedErrMsg: "sameness group members cannot specify both partition and peer in the same entry", - }, - "invalid - no name": { - input: &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []SamenessGroupMember{ - { - Peer: "peer2", - }, - { - Partition: "p2", - }, - }, - }, - }, - partitionsEnabled: true, - expectedErrMsg: "sameness groups must have a name defined", - }, - "invalid - empty members": { - input: &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-sameness-group", - }, - Spec: SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []SamenessGroupMember{}, - }, - }, - partitionsEnabled: true, - expectedErrMsg: "sameness groups must have at least one member", - }, - "invalid - not unique members": { - input: &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-sameness-group", - }, - Spec: SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []SamenessGroupMember{ - { - Peer: "peer2", - }, - { - Peer: "peer2", - }, - }, - }, - }, - partitionsEnabled: true, - expectedErrMsg: "sameness group members must be unique", - }, - "invalid - not in default namespace": { - input: &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-sameness-group", - Namespace: "non-default", - }, - Spec: SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []SamenessGroupMember{ - { - Peer: "peer2", - }, - }, - }, - }, - partitionsEnabled: true, - expectedErrMsg: "sameness groups must reside in the default namespace", - }, - } - - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - err := testCase.input.Validate(common.ConsulMeta{}) - if testCase.expectedErrMsg != "" { - require.ErrorContains(t, err, testCase.expectedErrMsg) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestSamenessGroups_GetObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - } - samenessGroups := &SamenessGroup{ - ObjectMeta: meta, - } - require.Equal(t, meta, samenessGroups.GetObjectMeta()) -} - -func TestSamenessGroups_AddFinalizer(t *testing.T) { - samenessGroups := &SamenessGroup{} - samenessGroups.AddFinalizer("finalizer") - require.Equal(t, []string{"finalizer"}, samenessGroups.ObjectMeta.Finalizers) -} - -func TestSamenessGroups_RemoveFinalizer(t *testing.T) { - samenessGroups := &SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{"f1", "f2"}, - }, - } - samenessGroups.RemoveFinalizer("f1") - require.Equal(t, []string{"f2"}, samenessGroups.ObjectMeta.Finalizers) -} - -func TestSamenessGroups_ConsulKind(t *testing.T) { - require.Equal(t, capi.SamenessGroup, (&SamenessGroup{}).ConsulKind()) -} - -func TestSamenessGroups_ConsulGlobalResource(t *testing.T) { - require.False(t, (&SamenessGroup{}).ConsulGlobalResource()) -} - -func TestSamenessGroups_ConsulMirroringNS(t *testing.T) { - -} - -func TestSamenessGroups_KubeKind(t *testing.T) { - require.Equal(t, "samenessgroup", (&SamenessGroup{}).KubeKind()) -} - -func TestSamenessGroups_ConsulName(t *testing.T) { - require.Equal(t, "foo", (&SamenessGroup{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).ConsulName()) -} - -func TestSamenessGroups_KubernetesName(t *testing.T) { - require.Equal(t, "foo", (&SamenessGroup{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).KubernetesName()) -} - -func TestSamenessGroups_SetSyncedCondition(t *testing.T) { - samenessGroups := &SamenessGroup{} - samenessGroups.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, samenessGroups.Status.Conditions[0].Status) - require.Equal(t, "reason", samenessGroups.Status.Conditions[0].Reason) - require.Equal(t, "message", samenessGroups.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, samenessGroups.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestSamenessGroups_SetLastSyncedTime(t *testing.T) { - samenessGroups := &SamenessGroup{} - syncedTime := metav1.NewTime(time.Now()) - samenessGroups.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, samenessGroups.Status.LastSyncedTime) -} - -func TestSamenessGroups_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - samenessGroups := &SamenessGroup{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, samenessGroups.SyncedConditionStatus()) - }) - } -} - -func TestSamenessGroups_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&SamenessGroup{}).SyncedConditionStatus()) -} - -func TestSamenessGroups_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&SamenessGroup{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} diff --git a/control-plane/api/v1alpha1/samenessgroup_webhook.go b/control-plane/api/v1alpha1/samenessgroup_webhook.go deleted file mode 100644 index 6c1da5cba2..0000000000 --- a/control-plane/api/v1alpha1/samenessgroup_webhook.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// +kubebuilder:object:generate=false - -type SamenessGroupWebhook struct { - Logger logr.Logger - - // ConsulMeta contains metadata specific to the Consul installation. - ConsulMeta common.ConsulMeta - - decoder *admission.Decoder - client.Client -} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/controller/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-samenessgroups,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=samenessgroups,versions=v1alpha1,name=mutate-samenessgroup.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *SamenessGroupWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource SamenessGroup - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateConfigEntry(ctx, req, v.Logger, v, &resource, v.ConsulMeta) -} - -func (v *SamenessGroupWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { - var resourceList SamenessGroupList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.ConfigEntryResource - for _, item := range resourceList.Items { - entries = append(entries, common.ConfigEntryResource(&item)) - } - return entries, nil -} - -func (v *SamenessGroupWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index 904154f184..3f1b8f1951 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -11,16 +8,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + capi "github.com/hashicorp/consul/api" "github.com/miekg/dns" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - - capi "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ( @@ -75,17 +70,6 @@ type ServiceDefaultsSpec struct { // Note: This cannot be set using the CRD and should be set using annotations on the // services that are part of the mesh. TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"` - // MutualTLSMode controls whether mutual TLS is required for all incoming - // connections when transparent proxy is enabled. This can be set to - // "permissive" or "strict". "strict" is the default which requires mutual - // TLS for incoming connections. In the insecure "permissive" mode, - // connections to the sidecar proxy public listener port require mutual - // TLS, but connections to the service port do not require mutual TLS and - // are proxied to the application unmodified. Note: Intentions are not - // enforced for non-mTLS connections. To keep your services secure, we - // recommend using "strict" mode whenever possible and enabling - // "permissive" mode only when necessary. - MutualTLSMode MutualTLSMode `json:"mutualTLSMode,omitempty"` // MeshGateway controls the default mesh gateway configuration for this service. MeshGateway MeshGateway `json:"meshGateway,omitempty"` // Expose controls the default expose path configuration for Envoy. @@ -105,22 +89,13 @@ type ServiceDefaultsSpec struct { // MaxInboundConnections is the maximum number of concurrent inbound connections to // each service instance. Defaults to 0 (using consul's default) if not set. MaxInboundConnections int `json:"maxInboundConnections,omitempty"` - // LocalConnectTimeoutMs is the number of milliseconds allowed to make connections to the local application + // The number of milliseconds allowed to make connections to the local application // instance before timing out. Defaults to 5000. LocalConnectTimeoutMs int `json:"localConnectTimeoutMs,omitempty"` - // LocalRequestTimeoutMs is the timeout for HTTP requests to the local application instance in milliseconds. + // In milliseconds, the timeout for HTTP requests to the local application instance. // Applies to HTTP-based protocols only. If not specified, inherits the Envoy default for // route timeouts (15s). LocalRequestTimeoutMs int `json:"localRequestTimeoutMs,omitempty"` - // BalanceInboundConnections sets the strategy for allocating inbound connections to the service across - // proxy threads. The only supported value is exact_balance. By default, no connection balancing is used. - // Refer to the Envoy Connection Balance config for details. - BalanceInboundConnections string `json:"balanceInboundConnections,omitempty"` - // RateLimits is rate limiting configuration that is applied to - // inbound traffic for a service. Rate limiting is a Consul enterprise feature. - RateLimits *RateLimits `json:"rateLimits,omitempty"` - // EnvoyExtensions are a list of extensions to modify Envoy proxy configuration. - EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` } type Upstreams struct { @@ -133,14 +108,12 @@ type Upstreams struct { } type Upstream struct { - // Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. + // Name is only accepted within a service-defaults config entry. Name string `json:"name,omitempty"` - // Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. + // Namespace is only accepted within a service-defaults config entry. Namespace string `json:"namespace,omitempty"` - // Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. + // Partition is only accepted within a service-defaults config entry. Partition string `json:"partition,omitempty"` - // Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides config entry. - Peer string `json:"peer,omitempty"` // EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's // listener. // Note: This escape hatch is NOT compatible with the discovery chain and @@ -191,8 +164,7 @@ type UpstreamLimits struct { // be monitored for removal from the load balancing pool. type PassiveHealthCheck struct { // Interval between health check analysis sweeps. Each sweep may remove - // hosts or return hosts to the pool. Ex. setting this to "10s" will set - // the interval to 10 seconds. + // hosts or return hosts to the pool. Interval metav1.Duration `json:"interval,omitempty"` // MaxFailures is the count of consecutive failures that results in a host // being removed from the pool. @@ -200,14 +172,13 @@ type PassiveHealthCheck struct { // EnforcingConsecutive5xx is the % chance that a host will be actually ejected // when an outlier status is detected through consecutive 5xx. // This setting can be used to disable ejection or to ramp it up slowly. - // Ex. Setting this to 10 will make it a 10% chance that the host will be ejected. EnforcingConsecutive5xx *uint32 `json:"enforcingConsecutive5xx,omitempty"` // The maximum % of an upstream cluster that can be ejected due to outlier detection. // Defaults to 10% but will eject at least one host regardless of the value. MaxEjectionPercent *uint32 `json:"maxEjectionPercent,omitempty"` // The base time that a host is ejected for. The real time is equal to the base time // multiplied by the number of times the host has been ejected and is capped by - // max_ejection_time (Default 300s). Defaults to 30s. + // max_ejection_time (Default 300s). Defaults to 30000ms or 30s. BaseEjectionTime *metav1.Duration `json:"baseEjectionTime,omitempty"` } @@ -220,150 +191,6 @@ type ServiceDefaultsDestination struct { Port uint32 `json:"port,omitempty"` } -// RateLimits is rate limiting configuration that is applied to -// inbound traffic for a service. -// Rate limiting is a Consul Enterprise feature. -type RateLimits struct { - // InstanceLevel represents rate limit configuration - // that is applied per service instance. - InstanceLevel InstanceLevelRateLimits `json:"instanceLevel,omitempty"` -} - -func (rl *RateLimits) toConsul() *capi.RateLimits { - if rl == nil { - return nil - } - routes := make([]capi.InstanceLevelRouteRateLimits, len(rl.InstanceLevel.Routes)) - for i, r := range rl.InstanceLevel.Routes { - routes[i] = capi.InstanceLevelRouteRateLimits{ - PathExact: r.PathExact, - PathPrefix: r.PathPrefix, - PathRegex: r.PathRegex, - RequestsPerSecond: r.RequestsPerSecond, - RequestsMaxBurst: r.RequestsMaxBurst, - } - } - return &capi.RateLimits{ - InstanceLevel: capi.InstanceLevelRateLimits{ - RequestsPerSecond: rl.InstanceLevel.RequestsPerSecond, - RequestsMaxBurst: rl.InstanceLevel.RequestsMaxBurst, - Routes: routes, - }, - } -} - -func (rl *RateLimits) validate(path *field.Path) field.ErrorList { - if rl == nil { - return nil - } - - return rl.InstanceLevel.validate(path.Child("instanceLevel")) -} - -type InstanceLevelRateLimits struct { - // RequestsPerSecond is the average number of requests per second that can be - // made without being throttled. This field is required if RequestsMaxBurst - // is set. The allowed number of requests may exceed RequestsPerSecond up to - // the value specified in RequestsMaxBurst. - // - // Internally, this is the refill rate of the token bucket used for rate limiting. - RequestsPerSecond int `json:"requestsPerSecond,omitempty"` - - // RequestsMaxBurst is the maximum number of requests that can be sent - // in a burst. Should be equal to or greater than RequestsPerSecond. - // If unset, defaults to RequestsPerSecond. - // - // Internally, this is the maximum size of the token bucket used for rate limiting. - RequestsMaxBurst int `json:"requestsMaxBurst,omitempty"` - - // Routes is a list of rate limits applied to specific routes. - // For a given request, the first matching route will be applied, if any. - // Overrides any top-level configuration. - Routes []InstanceLevelRouteRateLimits `json:"routes,omitempty"` -} - -func (irl InstanceLevelRateLimits) validate(path *field.Path) field.ErrorList { - var allErrs field.ErrorList - - // Track if RequestsPerSecond is set in at least one place in the config - isRateLimitSet := irl.RequestsPerSecond > 0 - - // Top-level RequestsPerSecond can be 0 (unset) or a positive number. - if irl.RequestsPerSecond < 0 { - allErrs = append(allErrs, - field.Invalid(path.Child("requestsPerSecond"), - irl.RequestsPerSecond, - "RequestsPerSecond must be positive")) - } - - if irl.RequestsPerSecond == 0 && irl.RequestsMaxBurst > 0 { - allErrs = append(allErrs, - field.Invalid(path.Child("requestsPerSecond"), - irl.RequestsPerSecond, - "RequestsPerSecond must be greater than 0 if RequestsMaxBurst is set")) - } - - if irl.RequestsMaxBurst < 0 { - allErrs = append(allErrs, - field.Invalid(path.Child("requestsMaxBurst"), - irl.RequestsMaxBurst, - "RequestsMaxBurst must be positive")) - } - - for i, route := range irl.Routes { - if exact, prefix, regex := route.PathExact != "", route.PathPrefix != "", route.PathRegex != ""; (!exact && !prefix && !regex) || - (exact && prefix) || (exact && regex) || (prefix && regex) { - allErrs = append(allErrs, field.Required( - path.Child("routes").Index(i), - "Route must define exactly one of PathExact, PathPrefix, or PathRegex")) - } - - isRateLimitSet = isRateLimitSet || route.RequestsPerSecond > 0 - - // Unlike top-level RequestsPerSecond, any route MUST have a RequestsPerSecond defined. - if route.RequestsPerSecond <= 0 { - allErrs = append(allErrs, field.Invalid( - path.Child("routes").Index(i).Child("requestsPerSecond"), - route.RequestsPerSecond, "RequestsPerSecond must be greater than 0")) - } - - if route.RequestsMaxBurst < 0 { - allErrs = append(allErrs, field.Invalid( - path.Child("routes").Index(i).Child("requestsMaxBurst"), - route.RequestsMaxBurst, "RequestsMaxBurst must be positive")) - } - } - - if !isRateLimitSet { - allErrs = append(allErrs, field.Invalid( - path.Child("requestsPerSecond"), - irl.RequestsPerSecond, "At least one of top-level or route-level RequestsPerSecond must be set")) - } - return allErrs -} - -type InstanceLevelRouteRateLimits struct { - // Exact path to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. - PathExact string `json:"pathExact,omitempty"` - // Prefix to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. - PathPrefix string `json:"pathPrefix,omitempty"` - // Regex to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. - PathRegex string `json:"pathRegex,omitempty"` - - // RequestsPerSecond is the average number of requests per - // second that can be made without being throttled. This field is required - // if RequestsMaxBurst is set. The allowed number of requests may exceed - // RequestsPerSecond up to the value specified in RequestsMaxBurst. - // Internally, this is the refill rate of the token bucket used for rate limiting. - RequestsPerSecond int `json:"requestsPerSecond,omitempty"` - - // RequestsMaxBurst is the maximum number of requests that can be sent - // in a burst. Should be equal to or greater than RequestsPerSecond. If unset, - // defaults to RequestsPerSecond. Internally, this is the maximum size of the token - // bucket used for rate limiting. - RequestsMaxBurst int `json:"requestsMaxBurst,omitempty"` -} - func (in *ServiceDefaults) ConsulKind() string { return capi.ServiceDefaults } @@ -441,23 +268,19 @@ func (in *ServiceDefaults) SyncedConditionStatus() corev1.ConditionStatus { // ToConsul converts the entry into it's Consul equivalent struct. func (in *ServiceDefaults) ToConsul(datacenter string) capi.ConfigEntry { return &capi.ServiceConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - Protocol: in.Spec.Protocol, - MeshGateway: in.Spec.MeshGateway.toConsul(), - Expose: in.Spec.Expose.toConsul(), - ExternalSNI: in.Spec.ExternalSNI, - TransparentProxy: in.Spec.TransparentProxy.toConsul(), - MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), - UpstreamConfig: in.Spec.UpstreamConfig.toConsul(), - Destination: in.Spec.Destination.toConsul(), - Meta: meta(datacenter), - MaxInboundConnections: in.Spec.MaxInboundConnections, - LocalConnectTimeoutMs: in.Spec.LocalConnectTimeoutMs, - LocalRequestTimeoutMs: in.Spec.LocalRequestTimeoutMs, - BalanceInboundConnections: in.Spec.BalanceInboundConnections, - RateLimits: in.Spec.RateLimits.toConsul(), - EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), + Kind: in.ConsulKind(), + Name: in.ConsulName(), + Protocol: in.Spec.Protocol, + MeshGateway: in.Spec.MeshGateway.toConsul(), + Expose: in.Spec.Expose.toConsul(), + ExternalSNI: in.Spec.ExternalSNI, + TransparentProxy: in.Spec.TransparentProxy.toConsul(), + UpstreamConfig: in.Spec.UpstreamConfig.toConsul(), + Destination: in.Spec.Destination.toConsul(), + Meta: meta(datacenter), + MaxInboundConnections: in.Spec.MaxInboundConnections, + LocalConnectTimeoutMs: in.Spec.LocalConnectTimeoutMs, + LocalRequestTimeoutMs: in.Spec.LocalRequestTimeoutMs, } } @@ -477,9 +300,6 @@ func (in *ServiceDefaults) Validate(consulMeta common.ConsulMeta) error { if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil { allErrs = append(allErrs, err) } - if err := in.Spec.MutualTLSMode.validate(); err != nil { - allErrs = append(allErrs, field.Invalid(path.Child("mutualTLSMode"), in.Spec.MutualTLSMode, err.Error())) - } if err := in.Spec.Mode.validate(path.Child("mode")); err != nil { allErrs = append(allErrs, err) } @@ -499,14 +319,8 @@ func (in *ServiceDefaults) Validate(consulMeta common.ConsulMeta) error { allErrs = append(allErrs, field.Invalid(path.Child("localRequestTimeoutMs"), in.Spec.LocalRequestTimeoutMs, "LocalRequestTimeoutMs must be > 0")) } - if in.Spec.BalanceInboundConnections != "" && in.Spec.BalanceInboundConnections != "exact_balance" { - allErrs = append(allErrs, field.Invalid(path.Child("balanceInboundConnections"), in.Spec.BalanceInboundConnections, "BalanceInboundConnections must be an empty string or exact_balance")) - } - allErrs = append(allErrs, in.Spec.UpstreamConfig.validate(path.Child("upstreamConfig"), consulMeta.PartitionsEnabled)...) allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) - allErrs = append(allErrs, in.Spec.RateLimits.validate(path.Child("rateLimits"))...) - allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) if len(allErrs) > 0 { return apierrors.NewInvalid( @@ -555,25 +369,10 @@ func (in *Upstream) validate(path *field.Path, kind string, partitionsEnabled bo if in.Name != "" { errs = append(errs, field.Invalid(path.Child("name"), in.Name, "upstream.name for a default upstream must be \"\"")) } - if in.Namespace != "" { - errs = append(errs, field.Invalid(path.Child("namespace"), in.Namespace, "upstream.namespace for a default upstream must be \"\"")) - } - if in.Partition != "" { - errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, "upstream.partition for a default upstream must be \"\"")) - } - if in.Peer != "" { - errs = append(errs, field.Invalid(path.Child("peer"), in.Peer, "upstream.peer for a default upstream must be \"\"")) - } } else if kind == overrideUpstream { if in.Name == "" { errs = append(errs, field.Invalid(path.Child("name"), in.Name, "upstream.name for an override upstream cannot be \"\"")) } - if in.Namespace != "" && in.Peer != "" { - errs = append(errs, field.Invalid(path, in, "both namespace and peer cannot be specified.")) - } - if in.Partition != "" && in.Peer != "" { - errs = append(errs, field.Invalid(path, in, "both partition and peer cannot be specified.")) - } } if !partitionsEnabled && in.Partition != "" { errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, "Consul Enterprise Admin Partitions must be enabled to set upstream.partition")) @@ -592,7 +391,6 @@ func (in *Upstream) toConsul() *capi.UpstreamConfig { Name: in.Name, Namespace: in.Namespace, Partition: in.Partition, - Peer: in.Peer, EnvoyListenerJSON: in.EnvoyListenerJSON, EnvoyClusterJSON: in.EnvoyClusterJSON, Protocol: in.Protocol, @@ -703,19 +501,9 @@ func (in *ServiceDefaults) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } - - specialEquality := cmp.Options{ - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "UpstreamConfig.Overrides.Namespace" - }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "UpstreamConfig.Overrides.Partition" - }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), - cmp.Comparer(transparentProxyConfigComparer), - } - // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), + cmp.Comparer(transparentProxyConfigComparer)) } func (in *ServiceDefaults) ConsulGlobalResource() bool { diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 7cfe606385..78ff6ce692 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -1,20 +1,15 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( - "encoding/json" "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func TestServiceDefaults_ToConsul(t *testing.T) { @@ -70,7 +65,6 @@ func TestServiceDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: MutualTLSModePermissive, UpstreamConfig: &Upstreams{ Defaults: &Upstream{ Name: "upstream-default", @@ -159,36 +153,6 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, }, }, - BalanceInboundConnections: "exact_balance", - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/foo", - RequestsPerSecond: 111, - RequestsMaxBurst: 222, - }, - { - PathPrefix: "/admin", - RequestsPerSecond: 333, - }, - }, - }, - }, - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), - Required: true, - }, - }, Destination: &ServiceDefaultsDestination{ Addresses: []string{"api.google.com"}, Port: 443, @@ -227,7 +191,6 @@ func TestServiceDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: capi.MutualTLSModePermissive, UpstreamConfig: &capi.UpstreamConfiguration{ Defaults: &capi.UpstreamConfig{ Name: "upstream-default", @@ -304,43 +267,6 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, }, }, - BalanceInboundConnections: "exact_balance", - RateLimits: &capi.RateLimits{ - InstanceLevel: capi.InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []capi.InstanceLevelRouteRateLimits{ - { - PathExact: "/foo", - RequestsPerSecond: 111, - RequestsMaxBurst: 222, - }, - { - PathPrefix: "/admin", - RequestsPerSecond: 333, - }, - }, - }, - }, - EnvoyExtensions: []capi.EnvoyExtension{ - { - Name: "aws_request_signing", - Arguments: map[string]interface{}{ - "AWSServiceName": "s3", - "Region": "us-west-2", - }, - Required: false, - }, - { - Name: "zipkin", - Arguments: map[string]interface{}{ - "ClusterName": "zipkin_cluster", - "Port": "9411", - "CollectorEndpoint": "/api/v2/spans", - }, - Required: true, - }, - }, Destination: &capi.DestinationConfig{ Addresses: []string{"api.google.com"}, Port: 443, @@ -506,7 +432,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: MutualTLSModeStrict, UpstreamConfig: &Upstreams{ Defaults: &Upstream{ Name: "upstream-default", @@ -565,6 +490,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, { Name: "upstream-default", + Namespace: "ns", EnvoyListenerJSON: `{"key": "value"}`, EnvoyClusterJSON: `{"key": "value"}`, Protocol: "http2", @@ -591,36 +517,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, }, }, - BalanceInboundConnections: "exact_balance", - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/foo", - RequestsPerSecond: 111, - RequestsMaxBurst: 222, - }, - { - PathPrefix: "/admin", - RequestsPerSecond: 333, - }, - }, - }, - }, - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), - Required: true, - }, - }, Destination: &ServiceDefaultsDestination{ Addresses: []string{"api.google.com"}, Port: 443, @@ -655,7 +551,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: capi.MutualTLSModeStrict, UpstreamConfig: &capi.UpstreamConfiguration{ Defaults: &capi.UpstreamConfig{ Name: "upstream-default", @@ -706,8 +601,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, { Name: "upstream-default", - Namespace: "default", - Partition: "default", + Namespace: "ns", EnvoyListenerJSON: `{"key": "value"}`, EnvoyClusterJSON: `{"key": "value"}`, Protocol: "http2", @@ -730,43 +624,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, }, }, - BalanceInboundConnections: "exact_balance", - RateLimits: &capi.RateLimits{ - InstanceLevel: capi.InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []capi.InstanceLevelRouteRateLimits{ - { - PathExact: "/foo", - RequestsPerSecond: 111, - RequestsMaxBurst: 222, - }, - { - PathPrefix: "/admin", - RequestsPerSecond: 333, - }, - }, - }, - }, - EnvoyExtensions: []capi.EnvoyExtension{ - { - Name: "aws_request_signing", - Arguments: map[string]interface{}{ - "AWSServiceName": "s3", - "Region": "us-west-2", - }, - Required: false, - }, - { - Name: "zipkin", - Arguments: map[string]interface{}{ - "ClusterName": "zipkin_cluster", - "Port": "9411", - "CollectorEndpoint": "/api/v2/spans", - }, - Required: true, - }, - }, Destination: &capi.DestinationConfig{ Addresses: []string{"api.google.com"}, Port: 443, @@ -873,7 +730,6 @@ func TestServiceDefaults_Validate(t *testing.T) { MeshGateway: MeshGateway{ Mode: "remote", }, - MutualTLSMode: MutualTLSModePermissive, Expose: Expose{ Checks: false, Paths: []ExposePath{ @@ -903,39 +759,6 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: "", }, - "valid - balanceInboundConnections": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - BalanceInboundConnections: "exact_balance", - }, - }, - expectedErrMsg: "", - }, - "valid - envoyExtension": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: json.RawMessage(`{"ClusterName": "zipkin_cluster", "Port": "9411", "CollectorEndpoint":"/api/v2/spans"}`), - Required: true, - }, - }, - }, - }, - expectedErrMsg: "", - }, "protocol": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ @@ -1009,17 +832,6 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: "servicedefaults.consul.hashicorp.com \"my-service\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port", }, - "mutualTLSMode": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - MutualTLSMode: MutualTLSMode("asdf"), - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.mutualTLSMode: Invalid value: "asdf": Must be one of "", "strict", or "permissive".`, - }, "mode": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ @@ -1063,21 +875,6 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.defaults.name: Invalid value: "foobar": upstream.name for a default upstream must be ""`, }, - "upstreamConfig.defaults.namespace": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - UpstreamConfig: &Upstreams{ - Defaults: &Upstream{ - Namespace: "foobar", - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.defaults.namespace: Invalid value: "foobar": upstream.namespace for a default upstream must be ""`, - }, "upstreamConfig.defaults.partition": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ @@ -1092,22 +889,7 @@ func TestServiceDefaults_Validate(t *testing.T) { }, }, partitionsEnabled: false, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.upstreamConfig.defaults.partition: Invalid value: "upstream": upstream.partition for a default upstream must be "", spec.upstreamConfig.defaults.partition: Invalid value: "upstream": Consul Enterprise Admin Partitions must be enabled to set upstream.partition]`, - }, - "upstreamConfig.defaults.peer": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - UpstreamConfig: &Upstreams{ - Defaults: &Upstream{ - Peer: "foobar", - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.defaults.peer: Invalid value: "foobar": upstream.peer for a default upstream must be ""`, + expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.defaults.partition: Invalid value: "upstream": Consul Enterprise Admin Partitions must be enabled to set upstream.partition`, }, "upstreamConfig.overrides.meshGateway": { input: &ServiceDefaults{ @@ -1164,44 +946,6 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.overrides[0].partition: Invalid value: "upstream": Consul Enterprise Admin Partitions must be enabled to set upstream.partition`, }, - "upstreamConfig.overrides.partition and namespace": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - UpstreamConfig: &Upstreams{ - Overrides: []*Upstream{ - { - Name: "service", - Namespace: "namespace", - Peer: "peer", - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.upstreamConfig.overrides[0]: Invalid value: v1alpha1.Upstream{Name:"service", Namespace:"namespace", Partition:"", Peer:"peer", EnvoyListenerJSON:"", EnvoyClusterJSON:"", Protocol:"", ConnectTimeoutMs:0, Limits:(*v1alpha1.UpstreamLimits)(nil), PassiveHealthCheck:(*v1alpha1.PassiveHealthCheck)(nil), MeshGateway:v1alpha1.MeshGateway{Mode:""}}: both namespace and peer cannot be specified.`, - }, - "upstreamConfig.overrides.partition and peer": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - UpstreamConfig: &Upstreams{ - Overrides: []*Upstream{ - { - Name: "service", - Partition: "upstream", - Peer: "peer", - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.upstreamConfig.overrides[0]: Invalid value: v1alpha1.Upstream{Name:"service", Namespace:"", Partition:"upstream", Peer:"peer", EnvoyListenerJSON:"", EnvoyClusterJSON:"", Protocol:"", ConnectTimeoutMs:0, Limits:(*v1alpha1.UpstreamLimits)(nil), PassiveHealthCheck:(*v1alpha1.PassiveHealthCheck)(nil), MeshGateway:v1alpha1.MeshGateway{Mode:""}}: both partition and peer cannot be specified., spec.upstreamConfig.overrides[0].partition: Invalid value: "upstream": Consul Enterprise Admin Partitions must be enabled to set upstream.partition]`, - }, "multi-error": { input: &ServiceDefaults{ ObjectMeta: metav1.ObjectMeta{ @@ -1292,257 +1036,6 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.destination.port: Invalid value: 0x0: invalid port number`, }, - "MaxInboundConnections (invalid value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - MaxInboundConnections: -1, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.maxinboundconnections: Invalid value: -1: MaxInboundConnections must be > 0`, - }, - "LocalConnectTimeoutMs (invalid value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - LocalConnectTimeoutMs: -1, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.localConnectTimeoutMs: Invalid value: -1: LocalConnectTimeoutMs must be > 0`, - }, - "LocalRequestTimeoutMs (invalid value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - LocalRequestTimeoutMs: -1, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.localRequestTimeoutMs: Invalid value: -1: LocalRequestTimeoutMs must be > 0`, - }, - "balanceInboundConnections (invalid value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - BalanceInboundConnections: "not_exact_balance", - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.balanceInboundConnections: Invalid value: "not_exact_balance": BalanceInboundConnections must be an empty string or exact_balance`, - }, - "envoyExtension.arguments (single empty)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"AWSServiceName": "s3", "Region": "us-west-2"}`), - Required: false, - }, - EnvoyExtension{ - Name: "zipkin", - Arguments: nil, - Required: true, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.envoyExtensions.envoyExtension[1].arguments: Required value: arguments must be defined`, - }, - "envoyExtension.arguments (multi empty)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: nil, - Required: false, - }, - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: nil, - Required: false, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.envoyExtensions.envoyExtension[0].arguments: Required value: arguments must be defined, spec.envoyExtensions.envoyExtension[1].arguments: Required value: arguments must be defined]`, - }, - "envoyExtension.arguments (invalid json)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - EnvoyExtensions: EnvoyExtensions{ - EnvoyExtension{ - Name: "aws_request_signing", - Arguments: json.RawMessage(`{"SOME_INVALID_JSON"}`), - Required: false, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.envoyExtensions.envoyExtension[0].arguments: Invalid value: "{\"SOME_INVALID_JSON\"}": must be valid map value: invalid character '}' after object key`, - }, - "rateLimits.instanceLevel.requestsPerSecond (negative value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: -1, - RequestsMaxBurst: 0, - Routes: []InstanceLevelRouteRateLimits{ - { - PathPrefix: "/admin", - RequestsPerSecond: 222, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: -1: RequestsPerSecond must be positive`, - }, - "rateLimits.instanceLevel.requestsPerSecond (invalid value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsMaxBurst: 1000, - Routes: []InstanceLevelRouteRateLimits{ - { - PathPrefix: "/admin", - RequestsPerSecond: 222, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0 if RequestsMaxBurst is set`, - }, - "rateLimits.instanceLevel.requestsMaxBurst (negative value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsMaxBurst: -1, - Routes: []InstanceLevelRouteRateLimits{ - { - PathPrefix: "/admin", - RequestsPerSecond: 222, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsMaxBurst: Invalid value: -1: RequestsMaxBurst must be positive`, - }, - "rateLimits.instanceLevel.routes (invalid path)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []InstanceLevelRouteRateLimits{ - { - RequestsPerSecond: 222, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0]: Required value: Route must define exactly one of PathExact, PathPrefix, or PathRegex`, - }, - "rateLimits.instanceLevel.routes.requestsPerSecond (zero value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/", - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0].requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0`, - }, - "rateLimits.instanceLevel.routes.requestsMaxBurst (negative value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/", - RequestsPerSecond: 222, - RequestsMaxBurst: -1, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0].requestsMaxBurst: Invalid value: -1: RequestsMaxBurst must be positive`, - }, - "rateLimits.requestsMaxBurst (top-level and route-level unset)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/", - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.rateLimits.instanceLevel.routes[0].requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0, spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: 0: At least one of top-level or route-level RequestsPerSecond must be set]`, - }, } for name, testCase := range cases { @@ -1647,7 +1140,7 @@ func TestServiceDefaults_ConsulName(t *testing.T) { } func TestServiceDefaults_KubernetesName(t *testing.T) { - require.Equal(t, "foo", (&ServiceDefaults{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).KubernetesName()) + require.Equal(t, "foo", (&ServiceDefaults{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}).ConsulName()) } func TestServiceDefaults_ConsulNamespace(t *testing.T) { diff --git a/control-plane/api/v1alpha1/servicedefaults_webhook.go b/control-plane/api/v1alpha1/servicedefaults_webhook.go index 124aeff5f6..f79e68bcde 100644 --- a/control-plane/api/v1alpha1/servicedefaults_webhook.go +++ b/control-plane/api/v1alpha1/servicedefaults_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index d393f72a2d..1a41caa289 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -1,11 +1,7 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( "encoding/json" - "fmt" "net/http" "strings" @@ -59,8 +55,6 @@ type ServiceIntentionsSpec struct { // The order of this list does not matter, but out of convenience Consul will always store this // reverse sorted by intention precedence, as that is the order that they will be evaluated at enforcement time. Sources SourceIntentions `json:"sources,omitempty"` - // JWT specifies the configuration to validate a JSON Web Token for all incoming requests. - JWT *IntentionJWTRequirement `json:"jwt,omitempty"` } type IntentionDestination struct { @@ -84,12 +78,10 @@ type SourceIntention struct { Name string `json:"name,omitempty"` // Namespace is the namespace for the Name parameter. Namespace string `json:"namespace,omitempty"` - // Peer is the peer name for the Name parameter. + // [Experimental] Peer is the peer name for the Name parameter. Peer string `json:"peer,omitempty"` // Partition is the Admin Partition for the Name parameter. Partition string `json:"partition,omitempty"` - // SamenessGroup is the name of the sameness group, if applicable. - SamenessGroup string `json:"samenessGroup,omitempty"` // Action is required for an L4 intention, and should be set to one of // "allow" or "deny" for the action that should be taken if this intention matches a request. Action IntentionAction `json:"action,omitempty"` @@ -110,8 +102,6 @@ type IntentionPermission struct { Action IntentionAction `json:"action,omitempty"` // HTTP is a set of HTTP-specific authorization criteria. HTTP *IntentionHTTPPermission `json:"http,omitempty"` - // JWT specifies configuration to validate a JSON Web Token for incoming requests. - JWT *IntentionJWTRequirement `json:"jwt,omitempty"` } type IntentionHTTPPermission struct { @@ -146,30 +136,6 @@ type IntentionHTTPHeaderPermission struct { Invert bool `json:"invert,omitempty"` } -type IntentionJWTRequirement struct { - // Providers is a list of providers to consider when verifying a JWT. - Providers []*IntentionJWTProvider `json:"providers,omitempty"` -} - -type IntentionJWTProvider struct { - // Name is the name of the JWT provider. There MUST be a corresponding - // "jwt-provider" config entry with this name. - Name string `json:"name,omitempty"` - - // VerifyClaims is a list of additional claims to verify in a JWT's payload. - VerifyClaims []*IntentionJWTClaimVerification `json:"verifyClaims,omitempty"` -} - -type IntentionJWTClaimVerification struct { - // Path is the path to the claim in the token JSON. - Path []string `json:"path,omitempty"` - - // Value is the expected value at the given path. If the type at the path - // is a list then we verify that this value is contained in the list. If - // the type at the path is a string then we verify that this value matches. - Value string `json:"value,omitempty"` -} - // IntentionAction is the action that the intention represents. This // can be "allow" or "deny" to allowlist or denylist intentions. type IntentionAction string @@ -254,7 +220,6 @@ func (in *ServiceIntentions) ToConsul(datacenter string) api.ConfigEntry { Name: in.Spec.Destination.Name, Namespace: in.Spec.Destination.Namespace, Sources: in.Spec.Sources.toConsul(), - JWT: in.Spec.JWT.toConsul(), Meta: meta(datacenter), } } @@ -321,12 +286,10 @@ func (in *ServiceIntentions) Validate(consulMeta common.ConsulMeta) error { } else { errs = append(errs, source.Permissions.validate(path.Child("sources").Index(i))...) } - errs = append(errs, source.validate(path.Child("sources").Index(i), consulMeta.PartitionsEnabled)...) } errs = append(errs, in.validateNamespaces(consulMeta.NamespacesEnabled)...) - - errs = append(errs, in.Spec.JWT.validate(path.Child("jwt"))...) + errs = append(errs, in.validateSourcePeerAndPartitions(consulMeta.PartitionsEnabled)...) if len(errs) > 0 { return apierrors.NewInvalid( @@ -336,46 +299,6 @@ func (in *ServiceIntentions) Validate(consulMeta common.ConsulMeta) error { return nil } -func (in *SourceIntention) validate(path *field.Path, partitionsEnabled bool) field.ErrorList { - var errs field.ErrorList - - if in.Name == "" { - errs = append(errs, field.Required(path.Child("name"), "name is required.")) - } - - if strings.Contains(in.Partition, WildcardSpecifier) { - errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, "partition cannot use or contain wildcard '*'")) - } - if strings.Contains(in.Peer, WildcardSpecifier) { - errs = append(errs, field.Invalid(path.Child("peer"), in.Peer, "peer cannot use or contain wildcard '*'")) - } - if strings.Contains(in.SamenessGroup, WildcardSpecifier) { - errs = append(errs, field.Invalid(path.Child("samenessgroup"), in.SamenessGroup, "samenessgroup cannot use or contain wildcard '*'")) - } - - if in.Partition != "" && !partitionsEnabled { - errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, `Consul Enterprise Admin Partitions must be enabled to set source.partition`)) - } - - if in.Peer != "" && in.Partition != "" { - errs = append(errs, field.Invalid(path, *in, "cannot set peer and partition at the same time.")) - } - - if in.SamenessGroup != "" && in.Partition != "" { - errs = append(errs, field.Invalid(path, *in, "cannot set samenessgroup and partition at the same time.")) - } - - if in.SamenessGroup != "" && in.Peer != "" { - errs = append(errs, field.Invalid(path, *in, "cannot set samenessgroup and peer at the same time.")) - } - - if len(in.Description) > metaValueMaxLength { - errs = append(errs, field.Invalid(path, "", fmt.Sprintf("description exceeds maximum length %d", metaValueMaxLength))) - } - - return errs -} - // DefaultNamespaceFields sets the namespace field on spec.destination to their default values if namespaces are enabled. func (in *ServiceIntentions) DefaultNamespaceFields(consulMeta common.ConsulMeta) { // If namespaces are enabled we want to set the destination namespace field to it's @@ -404,14 +327,13 @@ func (in *SourceIntention) toConsul() *capi.SourceIntention { return nil } return &capi.SourceIntention{ - Name: in.Name, - Namespace: in.Namespace, - Partition: in.Partition, - Peer: in.Peer, - SamenessGroup: in.SamenessGroup, - Action: in.Action.toConsul(), - Permissions: in.Permissions.toConsul(), - Description: in.Description, + Name: in.Name, + Namespace: in.Namespace, + Partition: in.Partition, + Peer: in.Peer, + Action: in.Action.toConsul(), + Permissions: in.Permissions.toConsul(), + Description: in.Description, } } @@ -425,7 +347,6 @@ func (in IntentionPermissions) toConsul() []*capi.IntentionPermission { consulIntentionPermissions = append(consulIntentionPermissions, &capi.IntentionPermission{ Action: permission.Action.toConsul(), HTTP: permission.HTTP.toConsul(), - JWT: permission.JWT.toConsul(), }) } return consulIntentionPermissions @@ -461,54 +382,15 @@ func (in IntentionHTTPHeaderPermissions) toConsul() []capi.IntentionHTTPHeaderPe return headerPermissions } -func (in *IntentionJWTRequirement) toConsul() *capi.IntentionJWTRequirement { - if in == nil { - return nil - } - var providers []*capi.IntentionJWTProvider - for _, p := range in.Providers { - providers = append(providers, p.toConsul()) - } - return &capi.IntentionJWTRequirement{ - Providers: providers, - } -} - -func (in *IntentionJWTProvider) toConsul() *capi.IntentionJWTProvider { - if in == nil { - return nil - } - var claims []*capi.IntentionJWTClaimVerification - for _, c := range in.VerifyClaims { - claims = append(claims, c.toConsul()) - } - return &capi.IntentionJWTProvider{ - Name: in.Name, - VerifyClaims: claims, - } -} - -func (in *IntentionJWTClaimVerification) toConsul() *capi.IntentionJWTClaimVerification { - if in == nil { - return nil - } - return &capi.IntentionJWTClaimVerification{ - Path: in.Path, - Value: in.Value, - } -} - func (in IntentionPermissions) validate(path *field.Path) field.ErrorList { var errs field.ErrorList for i, permission := range in { - permPath := path.Child("permissions").Index(i) - if err := permission.Action.validate(permPath); err != nil { + if err := permission.Action.validate(path.Child("permissions").Index(i)); err != nil { errs = append(errs, err) } if permission.HTTP != nil { - errs = append(errs, permission.HTTP.validate(permPath)...) + errs = append(errs, permission.HTTP.validate(path.Child("permissions").Index(i))...) } - errs = append(errs, permission.JWT.validate(permPath.Child("jwt"))...) } return errs } @@ -593,6 +475,21 @@ func (in *ServiceIntentions) validateNamespaces(namespacesEnabled bool) field.Er return errs } +func (in *ServiceIntentions) validateSourcePeerAndPartitions(partitionsEnabled bool) field.ErrorList { + var errs field.ErrorList + path := field.NewPath("spec") + for i, source := range in.Spec.Sources { + if source.Partition != "" && !partitionsEnabled { + errs = append(errs, field.Invalid(path.Child("sources").Index(i).Child("partition"), source.Partition, `Consul Enterprise Admin Partitions must be enabled to set source.partition`)) + } + + if source.Peer != "" && source.Partition != "" { + errs = append(errs, field.Invalid(path.Child("sources").Index(i), source, `Both source.peer and source.partition cannot be set.`)) + } + } + return errs +} + func (in IntentionAction) validate(path *field.Path) *field.Error { actions := []string{"allow", "deny"} if !sliceContains(actions, string(in)) { @@ -611,27 +508,6 @@ func numNotEmpty(ss ...string) int { return count } -func (in *IntentionJWTRequirement) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if in == nil { - return errs - } - - for i, p := range in.Providers { - if err := p.validate(path.Child("providers").Index(i)); err != nil { - errs = append(errs, err) - } - } - return errs -} - -func (in *IntentionJWTProvider) validate(path *field.Path) *field.Error { - if in != nil && in.Name == "" { - return field.Invalid(path.Child("name"), in.Name, "JWT provider name is required") - } - return nil -} - // sourceIntentionSortKey returns a string that can be used to sort intention // sources. func sourceIntentionSortKey(ixn *capi.SourceIntention) string { diff --git a/control-plane/api/v1alpha1/serviceintentions_types_test.go b/control-plane/api/v1alpha1/serviceintentions_types_test.go index 8d0a6d907a..01199f74d1 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_types_test.go @@ -1,10 +1,6 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( - "strings" "testing" "time" @@ -165,37 +161,11 @@ func TestServiceIntentions_MatchesConsul(t *testing.T) { "PUT", }, }, - JWT: &IntentionJWTRequirement{ - Providers: []*IntentionJWTProvider{ - { - Name: "okta-nested", - VerifyClaims: []*IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin-nested", - }, - }, - }, - }, - }, }, }, Description: "an L7 config", }, }, - JWT: &IntentionJWTRequirement{ - Providers: []*IntentionJWTProvider{ - { - Name: "okta", - VerifyClaims: []*IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin", - }, - }, - }, - }, - }, }, }, Theirs: &capi.ServiceIntentionsConfigEntry{ @@ -246,37 +216,11 @@ func TestServiceIntentions_MatchesConsul(t *testing.T) { "PUT", }, }, - JWT: &capi.IntentionJWTRequirement{ - Providers: []*capi.IntentionJWTProvider{ - { - Name: "okta-nested", - VerifyClaims: []*capi.IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin-nested", - }, - }, - }, - }, - }, }, }, Description: "an L7 config", }, }, - JWT: &capi.IntentionJWTRequirement{ - Providers: []*capi.IntentionJWTProvider{ - { - Name: "okta", - VerifyClaims: []*capi.IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin", - }, - }, - }, - }, - }, Meta: nil, }, Matches: true, @@ -394,13 +338,6 @@ func TestServiceIntentions_ToConsul(t *testing.T) { Action: "deny", Description: "disallow access from namespace not-test", }, - { - Name: "*", - Namespace: "ns1", - SamenessGroup: "sg2", - Action: "deny", - Description: "disallow access from namespace ns1", - }, { Name: "svc-2", Namespace: "bar", @@ -428,37 +365,11 @@ func TestServiceIntentions_ToConsul(t *testing.T) { "PUT", }, }, - JWT: &IntentionJWTRequirement{ - Providers: []*IntentionJWTProvider{ - { - Name: "okta-nested", - VerifyClaims: []*IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin-nested", - }, - }, - }, - }, - }, }, }, Description: "an L7 config", }, }, - JWT: &IntentionJWTRequirement{ - Providers: []*IntentionJWTProvider{ - { - Name: "okta", - VerifyClaims: []*IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin", - }, - }, - }, - }, - }, }, }, Exp: &capi.ServiceIntentionsConfigEntry{ @@ -480,13 +391,6 @@ func TestServiceIntentions_ToConsul(t *testing.T) { Action: "deny", Description: "disallow access from namespace not-test", }, - { - Name: "*", - Namespace: "ns1", - SamenessGroup: "sg2", - Action: "deny", - Description: "disallow access from namespace ns1", - }, { Name: "svc-2", Namespace: "bar", @@ -514,37 +418,11 @@ func TestServiceIntentions_ToConsul(t *testing.T) { "PUT", }, }, - JWT: &capi.IntentionJWTRequirement{ - Providers: []*capi.IntentionJWTProvider{ - { - Name: "okta-nested", - VerifyClaims: []*capi.IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin-nested", - }, - }, - }, - }, - }, }, }, Description: "an L7 config", }, }, - JWT: &capi.IntentionJWTRequirement{ - Providers: []*capi.IntentionJWTProvider{ - { - Name: "okta", - VerifyClaims: []*capi.IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin", - }, - }, - }, - }, - }, Meta: map[string]string{ common.SourceKey: common.SourceValue, common.DatacenterKey: "datacenter", @@ -793,8 +671,6 @@ func TestServiceIntentions_DefaultNamespaceFields(t *testing.T) { } func TestServiceIntentions_Validate(t *testing.T) { - longDescription := strings.Repeat("x", metaValueMaxLength+1) - cases := map[string]struct { input *ServiceIntentions namespacesEnabled bool @@ -845,37 +721,11 @@ func TestServiceIntentions_Validate(t *testing.T) { "PUT", }, }, - JWT: &IntentionJWTRequirement{ - Providers: []*IntentionJWTProvider{ - { - Name: "okta-nested", - VerifyClaims: []*IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin-nested", - }, - }, - }, - }, - }, }, }, Description: "an L7 config", }, }, - JWT: &IntentionJWTRequirement{ - Providers: []*IntentionJWTProvider{ - { - Name: "okta", - VerifyClaims: []*IntentionJWTClaimVerification{ - { - Path: []string{"perms", "role"}, - Value: "admin", - }, - }, - }, - }, - }, }, }, namespacesEnabled: true, @@ -1343,54 +1193,6 @@ func TestServiceIntentions_Validate(t *testing.T) { `serviceintentions.consul.hashicorp.com "does-not-matter" is invalid: spec.sources[0]: Invalid value: "{\"name\":\"svc-2\",\"namespace\":\"bar\",\"action\":\"deny\",\"permissions\":[{\"action\":\"allow\",\"http\":{\"pathExact\":\"/bar\"}}]}": action and permissions are mutually exclusive and only one of them can be specified`, }, }, - "name not specified": { - input: &ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - }, - Spec: ServiceIntentionsSpec{ - Destination: IntentionDestination{ - Name: "dest-service", - Namespace: "namespace", - }, - Sources: SourceIntentions{ - { - Namespace: "bar", - Action: "deny", - }, - }, - }, - }, - namespacesEnabled: true, - expectedErrMsgs: []string{ - `serviceintentions.consul.hashicorp.com "does-not-matter" is invalid: spec.sources[0].name: Required value: name is required.`, - }, - }, - "description is too long": { - input: &ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - }, - Spec: ServiceIntentionsSpec{ - Destination: IntentionDestination{ - Name: "dest-service", - Namespace: "namespace", - }, - Sources: SourceIntentions{ - { - Name: "foo", - Namespace: "bar", - Action: "deny", - Description: longDescription, - }, - }, - }, - }, - namespacesEnabled: true, - expectedErrMsgs: []string{ - `serviceintentions.consul.hashicorp.com "does-not-matter" is invalid: spec.sources[0]: Invalid value: "": description exceeds maximum length 512`, - }, - }, "namespaces disabled: destination namespace specified": { input: &ServiceIntentions{ ObjectMeta: metav1.ObjectMeta{ @@ -1610,71 +1412,7 @@ func TestServiceIntentions_Validate(t *testing.T) { namespacesEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `cannot set peer and partition at the same time.`, - }, - }, - "single source samenessgroup and partition specified": { - input: &ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - }, - Spec: ServiceIntentionsSpec{ - Destination: IntentionDestination{ - Name: "dest-service", - Namespace: "namespace-a", - }, - Sources: SourceIntentions{ - { - Name: "web", - Action: "allow", - Namespace: "namespace-b", - Partition: "partition-other", - SamenessGroup: "sg2", - }, - { - Name: "db", - Action: "deny", - Namespace: "namespace-c", - }, - }, - }, - }, - namespacesEnabled: true, - partitionsEnabled: true, - expectedErrMsgs: []string{ - `cannot set samenessgroup and partition at the same time.`, - }, - }, - "single source samenessgroup and peer specified": { - input: &ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - }, - Spec: ServiceIntentionsSpec{ - Destination: IntentionDestination{ - Name: "dest-service", - Namespace: "namespace-a", - }, - Sources: SourceIntentions{ - { - Name: "web", - Action: "allow", - Namespace: "namespace-b", - Peer: "p2", - SamenessGroup: "sg2", - }, - { - Name: "db", - Action: "deny", - Namespace: "namespace-c", - }, - }, - }, - }, - namespacesEnabled: true, - partitionsEnabled: true, - expectedErrMsgs: []string{ - `cannot set samenessgroup and peer at the same time.`, + `spec.sources[0]: Invalid value: v1alpha1.SourceIntention{Name:"web", Namespace:"namespace-b", Peer:"peer-other", Partition:"partition-other", Action:"allow", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: Both source.peer and source.partition cannot be set.`, }, }, "multiple source peer and partition specified": { @@ -1708,108 +1446,8 @@ func TestServiceIntentions_Validate(t *testing.T) { namespacesEnabled: true, partitionsEnabled: true, expectedErrMsgs: []string{ - `spec.sources[0]: Invalid value: v1alpha1.SourceIntention{Name:"web", Namespace:"namespace-b", Peer:"peer-other", Partition:"partition-other", SamenessGroup:"", Action:"allow", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: cannot set peer and partition at the same time.`, - `spec.sources[1]: Invalid value: v1alpha1.SourceIntention{Name:"db", Namespace:"namespace-c", Peer:"peer-2", Partition:"partition-2", SamenessGroup:"", Action:"deny", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: cannot set peer and partition at the same time.`, - }, - }, - "multiple errors: wildcard peer and partition and samenessgroup specified": { - input: &ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - }, - Spec: ServiceIntentionsSpec{ - Destination: IntentionDestination{ - Name: "dest-service", - Namespace: "namespace-a", - }, - Sources: SourceIntentions{ - { - Name: "web", - Action: "allow", - Namespace: "namespace-b", - Partition: "*", - }, - { - Name: "db", - Action: "deny", - Namespace: "namespace-c", - Peer: "*", - }, - { - Name: "db2", - Action: "deny", - Namespace: "namespace-d", - SamenessGroup: "*", - }, - }, - }, - }, - namespacesEnabled: true, - partitionsEnabled: true, - expectedErrMsgs: []string{ - `partition cannot use or contain wildcard '*'`, - `peer cannot use or contain wildcard '*'`, - `samenessgroup cannot use or contain wildcard '*'`, - }, - }, - "invalid empty jwt provider name at top-level": { - input: &ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - }, - Spec: ServiceIntentionsSpec{ - Destination: IntentionDestination{ - Name: "dest-service", - }, - Sources: SourceIntentions{ - { - Name: "bar", - Action: "allow", - }, - }, - JWT: &IntentionJWTRequirement{ - Providers: []*IntentionJWTProvider{ - { - Name: "", - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.jwt.providers[0].name: Invalid value: "": JWT provider name is required`, - }, - }, - "invalid empty jwt provider name in permissions": { - input: &ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - }, - Spec: ServiceIntentionsSpec{ - Destination: IntentionDestination{ - Name: "dest-service", - }, - Sources: SourceIntentions{ - { - Name: "bar", - Permissions: IntentionPermissions{ - { - Action: "allow", - JWT: &IntentionJWTRequirement{ - Providers: []*IntentionJWTProvider{ - { - Name: "", - }, - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.sources[0].permissions[0].jwt.providers[0].name: Invalid value: "": JWT provider name is required`, + `spec.sources[0]: Invalid value: v1alpha1.SourceIntention{Name:"web", Namespace:"namespace-b", Peer:"peer-other", Partition:"partition-other", Action:"allow", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: Both source.peer and source.partition cannot be set.`, + `spec.sources[1]: Invalid value: v1alpha1.SourceIntention{Name:"db", Namespace:"namespace-c", Peer:"peer-2", Partition:"partition-2", Action:"deny", Permissions:v1alpha1.IntentionPermissions(nil), Description:""}: Both source.peer and source.partition cannot be set.`, }, }, } diff --git a/control-plane/api/v1alpha1/serviceintentions_webhook.go b/control-plane/api/v1alpha1/serviceintentions_webhook.go index fef2401fb3..ddc6488690 100644 --- a/control-plane/api/v1alpha1/serviceintentions_webhook.go +++ b/control-plane/api/v1alpha1/serviceintentions_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go index 238ff7f33e..2df10ad11d 100644 --- a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 645cc23ac1..4fc637b35f 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -1,24 +1,17 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( "encoding/json" - "regexp" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - - capi "github.com/hashicorp/consul/api" - "github.com/hashicorp/go-bexpr" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ServiceResolverKubeKind string = "serviceresolver" @@ -77,15 +70,9 @@ type ServiceResolverSpec struct { // ConnectTimeout is the timeout for establishing new network connections // to this service. ConnectTimeout metav1.Duration `json:"connectTimeout,omitempty"` - // RequestTimeout is the timeout for receiving an HTTP response from this - // service before the connection is terminated. - RequestTimeout metav1.Duration `json:"requestTimeout,omitempty"` // LoadBalancer determines the load balancing policy and configuration for services // issuing requests to this upstream service. LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` - // PrioritizeByLocality controls whether the locality of services within the - // local partition will be used to prioritize connectivity. - PrioritizeByLocality *PrioritizeByLocality `json:"prioritizeByLocality,omitempty"` } type ServiceResolverRedirect struct { @@ -107,8 +94,6 @@ type ServiceResolverRedirect struct { // Peer is the name of the cluster peer to resolve the service from instead // of the current one. Peer string `json:"peer,omitempty"` - // SamenessGroup is the name of the sameness group to resolve the service from instead of the current one. - SamenessGroup string `json:"samenessGroup,omitempty"` } type ServiceResolverSubsetMap map[string]ServiceResolverSubset @@ -143,10 +128,6 @@ type ServiceResolverFailover struct { Datacenters []string `json:"datacenters,omitempty"` // Targets specifies a fixed list of failover targets to try during failover. Targets []ServiceResolverFailoverTarget `json:"targets,omitempty"` - // Policy specifies the exact mechanism used for failover. - Policy *FailoverPolicy `json:"policy,omitempty"` - // SamenessGroup is the name of the sameness group to try during failover. - SamenessGroup string `json:"samenessGroup,omitempty"` } type ServiceResolverFailoverTarget struct { @@ -307,17 +288,15 @@ func (in *ServiceResolver) SyncedConditionStatus() corev1.ConditionStatus { // ToConsul converts the entry into its Consul equivalent struct. func (in *ServiceResolver) ToConsul(datacenter string) capi.ConfigEntry { return &capi.ServiceResolverConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - DefaultSubset: in.Spec.DefaultSubset, - Subsets: in.Spec.Subsets.toConsul(), - Redirect: in.Spec.Redirect.toConsul(), - Failover: in.Spec.Failover.toConsul(), - ConnectTimeout: in.Spec.ConnectTimeout.Duration, - RequestTimeout: in.Spec.RequestTimeout.Duration, - LoadBalancer: in.Spec.LoadBalancer.toConsul(), - PrioritizeByLocality: in.Spec.PrioritizeByLocality.toConsul(), - Meta: meta(datacenter), + Kind: in.ConsulKind(), + Name: in.ConsulName(), + DefaultSubset: in.Spec.DefaultSubset, + Subsets: in.Spec.Subsets.toConsul(), + Redirect: in.Spec.Redirect.toConsul(), + Failover: in.Spec.Failover.toConsul(), + ConnectTimeout: in.Spec.ConnectTimeout.Duration, + LoadBalancer: in.Spec.LoadBalancer.toConsul(), + Meta: meta(datacenter), } } @@ -326,24 +305,8 @@ func (in *ServiceResolver) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } - - specialEquality := cmp.Options{ - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Redirect.Namespace" - }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Redirect.Partition" - }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Failover.Targets.Namespace" - }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Failover.Targets.Partition" - }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), - } - // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceResolverConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceResolverConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) } func (in *ServiceResolver) ConsulGlobalResource() bool { @@ -354,18 +317,14 @@ func (in *ServiceResolver) Validate(consulMeta common.ConsulMeta) error { var errs field.ErrorList path := field.NewPath("spec") - for subset, f := range in.Spec.Failover { - errs = append(errs, f.validate(path.Child("failover").Key(subset), consulMeta)...) - } - if len(in.Spec.Failover) > 0 && in.Spec.Redirect != nil { - asJSON, _ := json.Marshal(in) - errs = append(errs, field.Invalid(path, string(asJSON), "service resolver redirect and failover cannot both be set")) + for k, v := range in.Spec.Failover { + if err := v.validate(path.Child("failover").Key(k)); err != nil { + errs = append(errs, err) + } } - errs = append(errs, in.Spec.Redirect.validate(path.Child("redirect"), consulMeta)...) - errs = append(errs, in.Spec.PrioritizeByLocality.validate(path.Child("prioritizeByLocality"))...) - errs = append(errs, in.Spec.Subsets.validate(path.Child("subsets"))...) errs = append(errs, in.Spec.LoadBalancer.validate(path.Child("loadBalancer"))...) + errs = append(errs, in.validateEnterprise(consulMeta)...) if len(errs) > 0 { @@ -392,31 +351,6 @@ func (in ServiceResolverSubsetMap) toConsul() map[string]capi.ServiceResolverSub return m } -func (in ServiceResolverSubsetMap) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if len(in) == 0 { - return nil - } - validServiceSubset := regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`) - - for name, subset := range in { - indexPath := path.Key(name) - - if name == "" { - errs = append(errs, field.Invalid(indexPath, name, "subset defined with empty name")) - } - if !validServiceSubset.MatchString(name) { - errs = append(errs, field.Invalid(indexPath, name, "subset name must begin or end with lower case alphanumeric characters, and contain lower case alphanumeric characters or '-' in between")) - } - if subset.Filter != "" { - if _, err := bexpr.CreateEvaluator(subset.Filter, nil); err != nil { - errs = append(errs, field.Invalid(indexPath.Child("filter"), subset.Filter, "filter for subset is not a valid expression")) - } - } - } - return errs -} - func (in ServiceResolverSubset) toConsul() capi.ServiceResolverSubset { return capi.ServiceResolverSubset{ Filter: in.Filter, @@ -435,74 +369,7 @@ func (in *ServiceResolverRedirect) toConsul() *capi.ServiceResolverRedirect { Datacenter: in.Datacenter, Partition: in.Partition, Peer: in.Peer, - SamenessGroup: in.SamenessGroup, - } -} - -func (in *ServiceResolverRedirect) validate(path *field.Path, consulMeta common.ConsulMeta) field.ErrorList { - var errs field.ErrorList - if in == nil { - return nil - } - - asJSON, _ := json.Marshal(in) - if in.isEmpty() { - errs = append(errs, field.Invalid(path, "{}", - "service resolver redirect cannot be empty")) - } - - if consulMeta.Partition != "default" && consulMeta.Partition != "" && in.Datacenter != "" { - errs = append(errs, field.Invalid(path.Child("datacenter"), in.Datacenter, - "cross-datacenter redirect is only supported in the default partition")) - } - if consulMeta.Partition != in.Partition && in.Datacenter != "" { - errs = append(errs, field.Invalid(path.Child("partition"), in.Partition, - "cross-datacenter and cross-partition redirect is not supported")) - } - - switch { - case in.SamenessGroup != "" && in.ServiceSubset != "": - errs = append(errs, field.Invalid(path, string(asJSON), - "samenessGroup cannot be set with serviceSubset")) - case in.SamenessGroup != "" && in.Partition != "": - errs = append(errs, field.Invalid(path, string(asJSON), - "partition cannot be set with samenessGroup")) - case in.SamenessGroup != "" && in.Datacenter != "": - errs = append(errs, field.Invalid(path, string(asJSON), - "samenessGroup cannot be set with datacenter")) - case in.Peer != "" && in.ServiceSubset != "": - errs = append(errs, field.Invalid(path, string(asJSON), - "peer cannot be set with serviceSubset")) - case in.Peer != "" && in.Partition != "": - errs = append(errs, field.Invalid(path, string(asJSON), - "partition cannot be set with peer")) - case in.Peer != "" && in.Datacenter != "": - errs = append(errs, field.Invalid(path, string(asJSON), - "peer cannot be set with datacenter")) - case in.Service == "": - if in.ServiceSubset != "" { - errs = append(errs, field.Invalid(path, string(asJSON), - "serviceSubset defined without service")) - } - if in.Namespace != "" { - errs = append(errs, field.Invalid(path, string(asJSON), - "namespace defined without service")) - } - if in.Partition != "" { - errs = append(errs, field.Invalid(path, string(asJSON), - "partition defined without service")) - } - if in.Peer != "" { - errs = append(errs, field.Invalid(path, string(asJSON), - "peer defined without service")) - } } - - return errs -} - -func (in *ServiceResolverRedirect) isEmpty() bool { - return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && in.Partition == "" && in.Datacenter == "" && in.Peer == "" && in.SamenessGroup == "" } func (in ServiceResolverFailoverMap) toConsul() map[string]capi.ServiceResolverFailover { @@ -511,38 +378,23 @@ func (in ServiceResolverFailoverMap) toConsul() map[string]capi.ServiceResolverF } m := make(map[string]capi.ServiceResolverFailover) for k, v := range in { - if f := v.toConsul(); f != nil { - m[k] = *f - } + m[k] = v.toConsul() } return m } -func (in *ServiceResolverFailover) toConsul() *capi.ServiceResolverFailover { - if in == nil { - return nil - } +func (in ServiceResolverFailover) toConsul() capi.ServiceResolverFailover { var targets []capi.ServiceResolverFailoverTarget for _, target := range in.Targets { targets = append(targets, target.toConsul()) } - var policy *capi.ServiceResolverFailoverPolicy - if in.Policy != nil { - policy = &capi.ServiceResolverFailoverPolicy{ - Mode: in.Policy.Mode, - Regions: in.Policy.Regions, - } - } - - return &capi.ServiceResolverFailover{ + return capi.ServiceResolverFailover{ Service: in.Service, ServiceSubset: in.ServiceSubset, Namespace: in.Namespace, Datacenters: in.Datacenters, Targets: targets, - Policy: policy, - SamenessGroup: in.SamenessGroup, } } @@ -652,79 +504,17 @@ func (in *ServiceResolver) validateEnterprise(consulMeta common.ConsulMeta) fiel } func (in *ServiceResolverFailover) isEmpty() bool { - return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 && in.Policy == nil && in.SamenessGroup == "" + return in.Service == "" && in.ServiceSubset == "" && in.Namespace == "" && len(in.Datacenters) == 0 && len(in.Targets) == 0 } -func (in *ServiceResolverFailover) validate(path *field.Path, consulMeta common.ConsulMeta) field.ErrorList { - var errs field.ErrorList +func (in *ServiceResolverFailover) validate(path *field.Path) *field.Error { if in.isEmpty() { // NOTE: We're passing "{}" here as our value because we know that the // error is we have an empty object. - errs = append(errs, field.Invalid(path, "{}", - "service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once")) - } - - if consulMeta.Partition != "default" && len(in.Datacenters) != 0 { - errs = append(errs, field.Invalid(path.Child("datacenters"), in.Datacenters, - "cross-datacenter failover is only supported in the default partition")) - } - - errs = append(errs, in.Policy.validate(path.Child("policy"))...) - - asJSON, _ := json.Marshal(in) - if in.SamenessGroup != "" { - switch { - case len(in.Datacenters) > 0: - errs = append(errs, field.Invalid(path, string(asJSON), - "samenessGroup cannot be set with datacenters")) - case in.ServiceSubset != "": - errs = append(errs, field.Invalid(path, string(asJSON), - "samenessGroup cannot be set with serviceSubset")) - case len(in.Targets) > 0: - errs = append(errs, field.Invalid(path, string(asJSON), - "samenessGroup cannot be set with targets")) - } - } - - if len(in.Datacenters) != 0 && len(in.Targets) != 0 { - errs = append(errs, field.Invalid(path, string(asJSON), - "targets cannot be set with datacenters")) - } - - if in.ServiceSubset != "" && len(in.Targets) != 0 { - errs = append(errs, field.Invalid(path, string(asJSON), - "targets cannot be set with serviceSubset")) + return field.Invalid(path, "{}", + "service, serviceSubset, namespace, datacenters, and targets cannot all be empty at once") } - - if in.Service != "" && len(in.Targets) != 0 { - errs = append(errs, field.Invalid(path, string(asJSON), - "targets cannot be set with service")) - } - - for i, target := range in.Targets { - asJSON, _ := json.Marshal(target) - switch { - case target.Peer != "" && target.ServiceSubset != "": - errs = append(errs, field.Invalid(path.Child("targets").Index(i), string(asJSON), - "target.peer cannot be set with target.serviceSubset")) - case target.Peer != "" && target.Partition != "": - errs = append(errs, field.Invalid(path.Child("targets").Index(i), string(asJSON), - "target.partition cannot be set with target.peer")) - case target.Peer != "" && target.Datacenter != "": - errs = append(errs, field.Invalid(path.Child("targets").Index(i), string(asJSON), - "target.peer cannot be set with target.datacenter")) - case target.Partition != "" && target.Datacenter != "": - errs = append(errs, field.Invalid(path.Child("targets").Index(i), string(asJSON), - "target.partition cannot be set with target.datacenter")) - } - } - - for i, dc := range in.Datacenters { - if dc == "" { - errs = append(errs, field.Invalid(path.Child("datacenters").Index(i), "", "found empty datacenter")) - } - } - return errs + return nil } func (in *LoadBalancer) validate(path *field.Path) field.ErrorList { diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index 2070bd9df3..fd4fc25a60 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -1,20 +1,14 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( - "strings" "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func TestServiceResolver_MatchesConsul(t *testing.T) { @@ -64,50 +58,30 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Service: "redirect", ServiceSubset: "redirect_subset", Namespace: "redirect_namespace", - Partition: "default", Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "failover", - }, Failover: map[string]ServiceResolverFailover{ "failover1": { Service: "failover1", ServiceSubset: "failover_subset1", Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, - Policy: &FailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-2"}, - }, - SamenessGroup: "sg2", }, "failover2": { Service: "failover2", ServiceSubset: "failover_subset2", Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, - Policy: &FailoverPolicy{ - Mode: "", - Regions: []string{"us-west-1"}, - }, - SamenessGroup: "sg3", }, "failover3": { Targets: []ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, - {Peer: "failover_peer4"}, - }, - Policy: &FailoverPolicy{ - Mode: "order-by-locality", - Regions: []string{"us-east-1"}, }, }, }, ConnectTimeout: metav1.Duration{Duration: 1 * time.Second}, - RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, LoadBalancer: &LoadBalancer{ Policy: "policy", RingHashConfig: &RingHashConfig{ @@ -154,46 +128,27 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "failover", - }, Failover: map[string]capi.ServiceResolverFailover{ "failover1": { Service: "failover1", ServiceSubset: "failover_subset1", Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, - Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-2"}, - }, - SamenessGroup: "sg2", }, "failover2": { Service: "failover2", ServiceSubset: "failover_subset2", Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, - Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "", - Regions: []string{"us-west-1"}, - }, - SamenessGroup: "sg3", }, "failover3": { Targets: []capi.ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, - {Peer: "failover_peer4", Partition: "default", Namespace: "default"}, - }, - Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "order-by-locality", - Regions: []string{"us-east-1"}, }, }, }, ConnectTimeout: 1 * time.Second, - RequestTimeout: 1 * time.Second, LoadBalancer: &capi.LoadBalancer{ Policy: "policy", RingHashConfig: &capi.RingHashConfig{ @@ -289,45 +244,27 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "none", - }, Failover: map[string]ServiceResolverFailover{ "failover1": { Service: "failover1", ServiceSubset: "failover_subset1", Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, - Policy: &FailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-2"}, - }, - SamenessGroup: "sg2", }, "failover2": { Service: "failover2", ServiceSubset: "failover_subset2", Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, - Policy: &FailoverPolicy{ - Mode: "", - Regions: []string{"us-west-1"}, - }, - SamenessGroup: "sg3", }, "failover3": { Targets: []ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, - Policy: &FailoverPolicy{ - Mode: "order-by-locality", - Regions: []string{"us-east-1"}, - }, }, }, ConnectTimeout: metav1.Duration{Duration: 1 * time.Second}, - RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, LoadBalancer: &LoadBalancer{ Policy: "policy", RingHashConfig: &RingHashConfig{ @@ -374,45 +311,27 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "none", - }, Failover: map[string]capi.ServiceResolverFailover{ "failover1": { Service: "failover1", ServiceSubset: "failover_subset1", Namespace: "failover_namespace1", Datacenters: []string{"failover1_dc1", "failover1_dc2"}, - Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-2"}, - }, - SamenessGroup: "sg2", }, "failover2": { Service: "failover2", ServiceSubset: "failover_subset2", Namespace: "failover_namespace2", Datacenters: []string{"failover2_dc1", "failover2_dc2"}, - Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "", - Regions: []string{"us-west-1"}, - }, - SamenessGroup: "sg3", }, "failover3": { Targets: []capi.ServiceResolverFailoverTarget{ {Peer: "failover_peer3"}, {Partition: "failover_partition3", Namespace: "failover_namespace3"}, }, - Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "order-by-locality", - Regions: []string{"us-east-1"}, - }, }, }, ConnectTimeout: 1 * time.Second, - RequestTimeout: 1 * time.Second, LoadBalancer: &capi.LoadBalancer{ Policy: "policy", RingHashConfig: &capi.RingHashConfig{ @@ -573,15 +492,16 @@ func TestServiceResolver_Validate(t *testing.T) { Name: "foo", }, Spec: ServiceResolverSpec{ + Redirect: &ServiceResolverRedirect{ + Service: "bar", + Namespace: "namespace-a", + }, Failover: map[string]ServiceResolverFailover{ - "v1": { + "failA": { Service: "baz", Namespace: "namespace-b", }, }, - Subsets: map[string]ServiceResolverSubset{ - "v1": {Filter: "Service.Meta.version == v1"}, - }, }, }, namespacesEnabled: true, @@ -597,8 +517,10 @@ func TestServiceResolver_Validate(t *testing.T) { Redirect: &ServiceResolverRedirect{ Service: "bar", }, - Subsets: map[string]ServiceResolverSubset{ - "v1": {Filter: "Service.Meta.version == v1"}, + Failover: map[string]ServiceResolverFailover{ + "failA": { + Service: "baz", + }, }, }, }, @@ -612,15 +534,17 @@ func TestServiceResolver_Validate(t *testing.T) { Name: "foo", }, Spec: ServiceResolverSpec{ + Redirect: &ServiceResolverRedirect{ + Service: "bar", + Namespace: "namespace-a", + Partition: "other", + }, Failover: map[string]ServiceResolverFailover{ - "v1": { + "failA": { Service: "baz", Namespace: "namespace-b", }, }, - Subsets: map[string]ServiceResolverSubset{ - "v1": {Filter: "Service.Meta.version == v1"}, - }, }, }, namespacesEnabled: true, @@ -636,6 +560,11 @@ func TestServiceResolver_Validate(t *testing.T) { Redirect: &ServiceResolverRedirect{ Service: "bar", }, + Failover: map[string]ServiceResolverFailover{ + "failA": { + Service: "baz", + }, + }, }, }, namespacesEnabled: false, @@ -649,13 +578,13 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Failover: map[string]ServiceResolverFailover{ - "v1": { + "failA": { Service: "", ServiceSubset: "", Namespace: "", Datacenters: nil, }, - "v2": { + "failB": { Service: "", ServiceSubset: "", Namespace: "", @@ -666,32 +595,10 @@ func TestServiceResolver_Validate(t *testing.T) { }, namespacesEnabled: false, expectedErrMsgs: []string{ - "spec.failover[v1]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", - "spec.failover[v2]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", + "spec.failover[failA]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, and targets cannot all be empty at once", + "spec.failover[failB]: Invalid value: \"{}\": service, serviceSubset, namespace, datacenters, and targets cannot all be empty at once", }, }, - "service resolver redirect and failover cannot both be set": { - input: &ServiceResolver{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: ServiceResolverSpec{ - Redirect: &ServiceResolverRedirect{ - Service: "bar", - Namespace: "namespace-a", - }, - Failover: map[string]ServiceResolverFailover{ - "failA": { - Service: "baz", - Namespace: "namespace-b", - }, - }, - }, - }, - namespacesEnabled: true, - partitionsEnabled: false, - expectedErrMsgs: []string{"service resolver redirect and failover cannot both be set"}, - }, "hashPolicy.field invalid": { input: &ServiceResolver{ ObjectMeta: metav1.ObjectMeta{ @@ -765,19 +672,11 @@ func TestServiceResolver_Validate(t *testing.T) { }, }, }, - Subsets: map[string]ServiceResolverSubset{ - "": { - Filter: "random string", - }, - }, }, }, namespacesEnabled: false, expectedErrMsgs: []string{ - `spec.loadBalancer.hashPolicies[0]: Invalid value: "{\"field\":\"header\",\"sourceIP\":true}": cannot set both field and sourceIP`, - `subset defined with empty name`, - `subset name must begin or end with lower case alphanumeric characters, and contain lower case alphanumeric characters or '-' in between`, - `filter for subset is not a valid expression`, + `serviceresolver.consul.hashicorp.com "foo" is invalid: spec.loadBalancer.hashPolicies[0]: Invalid value: "{\"field\":\"header\",\"sourceIP\":true}": cannot set both field and sourceIP`, }, }, "hashPolicy nothing set is valid": { @@ -828,7 +727,6 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Redirect: &ServiceResolverRedirect{ - Service: "bar", Namespace: "namespace-a", }, }, @@ -845,7 +743,6 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Redirect: &ServiceResolverRedirect{ - Service: "bar", Namespace: "namespace-a", Partition: "other", }, @@ -864,19 +761,14 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Failover: map[string]ServiceResolverFailover{ - "v1": { + "failA": { Namespace: "namespace-a", }, }, - Subsets: map[string]ServiceResolverSubset{ - "v1": { - Filter: "Service.Meta.version == v1", - }, - }, }, }, expectedErrMsgs: []string{ - "serviceresolver.consul.hashicorp.com \"foo\" is invalid: spec.failover[v1].namespace: Invalid value: \"namespace-a\": Consul Enterprise namespaces must be enabled to set failover.namespace", + "serviceresolver.consul.hashicorp.com \"foo\" is invalid: spec.failover[failA].namespace: Invalid value: \"namespace-a\": Consul Enterprise namespaces must be enabled to set failover.namespace", }, namespacesEnabled: false, }, @@ -902,22 +794,6 @@ func TestServiceResolver_Validate(t *testing.T) { "spec.failover[failB].namespace: Invalid value: \"namespace-b\": Consul Enterprise namespaces must be enabled to set failover.namespace", }, }, - "prioritize by locality invalid": { - input: &ServiceResolver{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: ServiceResolverSpec{ - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "bad", - }, - }, - }, - namespacesEnabled: false, - expectedErrMsgs: []string{ - "serviceresolver.consul.hashicorp.com \"foo\" is invalid: spec.prioritizeByLocality.mode: Invalid value: \"bad\": must be one of \"\", \"none\", \"failover\"", - }, - }, } for name, testCase := range cases { t.Run(name, func(t *testing.T) { @@ -933,497 +809,3 @@ func TestServiceResolver_Validate(t *testing.T) { }) } } - -func TestServiceResolverRedirect_ToConsul(t *testing.T) { - cases := map[string]struct { - Ours *ServiceResolverRedirect - Exp *capi.ServiceResolverRedirect - }{ - "nil": { - Ours: nil, - Exp: nil, - }, - "empty fields": { - Ours: &ServiceResolverRedirect{}, - Exp: &capi.ServiceResolverRedirect{}, - }, - "every field set": { - Ours: &ServiceResolverRedirect{ - Service: "foo", - ServiceSubset: "v1", - Namespace: "ns1", - Datacenter: "dc1", - Partition: "default", - Peer: "peer1", - SamenessGroup: "sg1", - }, - Exp: &capi.ServiceResolverRedirect{ - Service: "foo", - ServiceSubset: "v1", - Namespace: "ns1", - Datacenter: "dc1", - Partition: "default", - Peer: "peer1", - SamenessGroup: "sg1", - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.toConsul() - require.Equal(t, c.Exp, actual) - }) - } -} - -func TestServiceResolverRedirect_Validate(t *testing.T) { - cases := map[string]struct { - input *ServiceResolverRedirect - consulMeta common.ConsulMeta - expectedErrMsgs []string - }{ - "empty redirect": { - input: &ServiceResolverRedirect{}, - consulMeta: common.ConsulMeta{}, - expectedErrMsgs: []string{ - "service resolver redirect cannot be empty", - }, - }, - "cross-datacenter redirect is only supported in the default partition": { - input: &ServiceResolverRedirect{ - Datacenter: "dc2", - Partition: "p2", - Service: "foo", - }, - consulMeta: common.ConsulMeta{ - Partition: "p2", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "cross-datacenter redirect is only supported in the default partition", - }, - }, - "cross-datacenter and cross-partition redirect is not supported": { - input: &ServiceResolverRedirect{ - Partition: "p1", - Datacenter: "dc2", - Service: "foo", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "cross-datacenter and cross-partition redirect is not supported", - }, - }, - "samenessGroup cannot be set with serviceSubset": { - input: &ServiceResolverRedirect{ - Service: "foo", - ServiceSubset: "v1", - SamenessGroup: "sg2", - }, - expectedErrMsgs: []string{ - "samenessGroup cannot be set with serviceSubset", - }, - }, - "samenessGroup cannot be set with partition": { - input: &ServiceResolverRedirect{ - Partition: "default", - Service: "foo", - SamenessGroup: "sg2", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "partition cannot be set with samenessGroup", - }, - }, - "samenessGroup cannot be set with datacenter": { - input: &ServiceResolverRedirect{ - Datacenter: "dc2", - Service: "foo", - SamenessGroup: "sg2", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "cross-datacenter and cross-partition redirect is not supported", - "samenessGroup cannot be set with datacenter", - }, - }, - "peer cannot be set with serviceSubset": { - input: &ServiceResolverRedirect{ - Peer: "p2", - Service: "foo", - ServiceSubset: "v1", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "peer cannot be set with serviceSubset", - }, - }, - "partition cannot be set with peer": { - input: &ServiceResolverRedirect{ - Partition: "default", - Peer: "p2", - Service: "foo", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "partition cannot be set with peer", - }, - }, - "peer cannot be set with datacenter": { - input: &ServiceResolverRedirect{ - Peer: "p2", - Service: "foo", - Datacenter: "dc2", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "peer cannot be set with datacenter", - "cross-datacenter and cross-partition redirect is not supported", - }, - }, - "serviceSubset defined without service": { - input: &ServiceResolverRedirect{ - ServiceSubset: "v1", - }, - consulMeta: common.ConsulMeta{ - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "serviceSubset defined without service", - }, - }, - "namespace defined without service": { - input: &ServiceResolverRedirect{ - Namespace: "ns1", - }, - consulMeta: common.ConsulMeta{ - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "namespace defined without service", - }, - }, - "partition defined without service": { - input: &ServiceResolverRedirect{ - Partition: "default", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "partition defined without service", - }, - }, - "peer defined without service": { - input: &ServiceResolverRedirect{ - Peer: "p2", - }, - consulMeta: common.ConsulMeta{ - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "peer defined without service", - }, - }, - } - - path := field.NewPath("spec.redirect") - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - errList := testCase.input.validate(path, testCase.consulMeta) - compareErrorLists(t, testCase.expectedErrMsgs, errList) - }) - } -} - -func compareErrorLists(t *testing.T, expectedErrMsgs []string, errList field.ErrorList) { - if len(expectedErrMsgs) != 0 { - require.Equal(t, len(expectedErrMsgs), len(errList)) - for _, m := range expectedErrMsgs { - found := false - for _, e := range errList { - errMsg := e.ErrorBody() - if strings.Contains(errMsg, m) { - found = true - break - } - } - require.Equal(t, true, found) - } - } else { - require.Equal(t, 0, len(errList)) - } -} - -func TestServiceResolverFailover_ToConsul(t *testing.T) { - cases := map[string]struct { - Ours *ServiceResolverFailover - Exp *capi.ServiceResolverFailover - }{ - "nil": { - Ours: nil, - Exp: nil, - }, - "empty fields": { - Ours: &ServiceResolverFailover{}, - Exp: &capi.ServiceResolverFailover{}, - }, - "every field set": { - Ours: &ServiceResolverFailover{ - Service: "foo", - ServiceSubset: "v1", - Namespace: "ns1", - Datacenters: []string{"dc1"}, - Targets: []ServiceResolverFailoverTarget{ - { - Peer: "p2", - }, - }, - Policy: &FailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-2"}, - }, - SamenessGroup: "sg1", - }, - Exp: &capi.ServiceResolverFailover{ - Service: "foo", - ServiceSubset: "v1", - Namespace: "ns1", - Datacenters: []string{"dc1"}, - Targets: []capi.ServiceResolverFailoverTarget{ - { - Peer: "p2", - }, - }, - Policy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-2"}, - }, - SamenessGroup: "sg1", - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.toConsul() - require.Equal(t, c.Exp, actual) - }) - } -} - -func TestServiceResolverFailover_Validate(t *testing.T) { - cases := map[string]struct { - input *ServiceResolverFailover - consulMeta common.ConsulMeta - expectedErrMsgs []string - }{ - "empty failover": { - input: &ServiceResolverFailover{}, - consulMeta: common.ConsulMeta{}, - expectedErrMsgs: []string{ - "service, serviceSubset, namespace, datacenters, policy, and targets cannot all be empty at once", - }, - }, - "cross-datacenter failover is only supported in the default partition": { - input: &ServiceResolverFailover{ - Datacenters: []string{"dc2"}, - Service: "foo", - }, - consulMeta: common.ConsulMeta{ - Partition: "p2", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "cross-datacenter failover is only supported in the default partition", - }, - }, - "samenessGroup cannot be set with datacenters": { - input: &ServiceResolverFailover{ - Service: "foo", - Datacenters: []string{"dc2"}, - SamenessGroup: "sg2", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "samenessGroup cannot be set with datacenters", - }, - }, - "samenessGroup cannot be set with serviceSubset": { - input: &ServiceResolverFailover{ - ServiceSubset: "v1", - Service: "foo", - SamenessGroup: "sg2", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "samenessGroup cannot be set with serviceSubset", - }, - }, - "samenessGroup cannot be set with targets": { - input: &ServiceResolverFailover{ - Targets: []ServiceResolverFailoverTarget{ - { - Peer: "p2", - }, - }, - SamenessGroup: "sg2", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "samenessGroup cannot be set with targets", - }, - }, - "targets cannot be set with datacenters": { - input: &ServiceResolverFailover{ - Targets: []ServiceResolverFailoverTarget{ - { - Peer: "p2", - }, - }, - Datacenters: []string{"dc1"}, - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "targets cannot be set with datacenters", - }, - }, - "targets cannot be set with serviceSubset or service": { - input: &ServiceResolverFailover{ - Targets: []ServiceResolverFailoverTarget{ - { - Peer: "p2", - }, - }, - ServiceSubset: "v1", - Service: "foo", - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "targets cannot be set with serviceSubset", - "targets cannot be set with service", - }, - }, - "target.peer cannot be set with target.serviceSubset": { - input: &ServiceResolverFailover{ - Targets: []ServiceResolverFailoverTarget{ - { - Peer: "p2", - ServiceSubset: "v1", - }, - }, - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "target.peer cannot be set with target.serviceSubset", - }, - }, - "target.partition cannot be set with target.peer": { - input: &ServiceResolverFailover{ - Targets: []ServiceResolverFailoverTarget{ - { - Peer: "p2", - Partition: "partition2", - }, - }, - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "target.partition cannot be set with target.peer", - }, - }, - "target.peer cannot be set with target.datacenter": { - input: &ServiceResolverFailover{ - Targets: []ServiceResolverFailoverTarget{ - { - Peer: "p2", - Datacenter: "dc2", - }, - }, - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "target.peer cannot be set with target.datacenter", - }, - }, - "target.partition cannot be set with target.datacenter": { - input: &ServiceResolverFailover{ - Targets: []ServiceResolverFailoverTarget{ - { - Partition: "p2", - Datacenter: "dc2", - }, - }, - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "target.partition cannot be set with target.datacenter", - }, - }, - "found empty datacenter": { - input: &ServiceResolverFailover{ - Datacenters: []string{""}, - }, - consulMeta: common.ConsulMeta{ - Partition: "default", - PartitionsEnabled: true, - }, - expectedErrMsgs: []string{ - "found empty datacenter", - }, - }, - } - - path := field.NewPath("spec.redirect") - for name, testCase := range cases { - t.Run(name, func(t *testing.T) { - errList := testCase.input.validate(path, testCase.consulMeta) - compareErrorLists(t, testCase.expectedErrMsgs, errList) - }) - } -} diff --git a/control-plane/api/v1alpha1/serviceresolver_webhook.go b/control-plane/api/v1alpha1/serviceresolver_webhook.go index 88996e2f8d..ca5f9d9482 100644 --- a/control-plane/api/v1alpha1/serviceresolver_webhook.go +++ b/control-plane/api/v1alpha1/serviceresolver_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicerouter_types.go b/control-plane/api/v1alpha1/servicerouter_types.go index a06977b9e7..931d5ccb3a 100644 --- a/control-plane/api/v1alpha1/servicerouter_types.go +++ b/control-plane/api/v1alpha1/servicerouter_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -8,15 +5,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func init() { @@ -75,8 +71,6 @@ type ServiceRouteMatch struct { } type ServiceRouteHTTPMatch struct { - // CaseInsensitive configures PathExact and PathPrefix matches to ignore upper/lower casing. - CaseInsensitive bool `json:"caseInsensitive,omitempty"` // PathExact is an exact path to match on the HTTP request path. PathExact string `json:"pathExact,omitempty"` // PathPrefix is a path prefix to match on the HTTP request path. @@ -153,9 +147,6 @@ type ServiceRouteDestination struct { NumRetries uint32 `json:"numRetries,omitempty"` // RetryOnConnectFailure allows for connection failure errors to trigger a retry. RetryOnConnectFailure bool `json:"retryOnConnectFailure,omitempty"` - // RetryOn is a flat list of conditions for Consul to retry requests based on the response from an upstream service. - // Refer to the valid conditions here: https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon - RetryOn []string `json:"retryOn,omitempty"` // RetryOnStatusCodes is a flat list of http response status codes that are eligible for retry. RetryOnStatusCodes []uint32 `json:"retryOnStatusCodes,omitempty"` // Allow HTTP header manipulation to be configured. @@ -259,18 +250,8 @@ func (in *ServiceRouter) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } - - specialEquality := cmp.Options{ - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Routes.Destination.Namespace" - }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Routes.Destination.Partition" - }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), - } - // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceRouterConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceRouterConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) } func (in *ServiceRouter) Validate(consulMeta common.ConsulMeta) error { @@ -360,7 +341,6 @@ func (in *ServiceRouteDestination) toConsul() *capi.ServiceRouteDestination { RequestTimeout: in.RequestTimeout.Duration, NumRetries: in.NumRetries, RetryOnConnectFailure: in.RetryOnConnectFailure, - RetryOn: in.RetryOn, RetryOnStatusCodes: in.RetryOnStatusCodes, RequestHeaders: in.RequestHeaders.toConsul(), ResponseHeaders: in.ResponseHeaders.toConsul(), @@ -424,13 +404,12 @@ func (in *ServiceRouteHTTPMatch) toConsul() *capi.ServiceRouteHTTPMatch { query = append(query, q.toConsul()) } return &capi.ServiceRouteHTTPMatch{ - CaseInsensitive: in.CaseInsensitive, - PathExact: in.PathExact, - PathPrefix: in.PathPrefix, - PathRegex: in.PathRegex, - Header: header, - QueryParam: query, - Methods: in.Methods, + PathExact: in.PathExact, + PathPrefix: in.PathPrefix, + PathRegex: in.PathRegex, + Header: header, + QueryParam: query, + Methods: in.Methods, } } diff --git a/control-plane/api/v1alpha1/servicerouter_types_test.go b/control-plane/api/v1alpha1/servicerouter_types_test.go index acd4437262..3110922210 100644 --- a/control-plane/api/v1alpha1/servicerouter_types_test.go +++ b/control-plane/api/v1alpha1/servicerouter_types_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -8,12 +5,11 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) // Test MatchesConsul. @@ -53,10 +49,9 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { { Match: &ServiceRouteMatch{ HTTP: &ServiceRouteHTTPMatch{ - CaseInsensitive: true, - PathExact: "pathExact", - PathPrefix: "pathPrefix", - PathRegex: "pathRegex", + PathExact: "pathExact", + PathPrefix: "pathPrefix", + PathRegex: "pathRegex", Header: []ServiceRouteHTTPMatchHeader{ { Name: "name", @@ -88,7 +83,6 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, NumRetries: 1, RetryOnConnectFailure: true, - RetryOn: []string{"gateway-error"}, RetryOnStatusCodes: []uint32{500, 400}, RequestHeaders: &HTTPHeaderModifiers{ Add: map[string]string{ @@ -132,10 +126,9 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { { Match: &capi.ServiceRouteMatch{ HTTP: &capi.ServiceRouteHTTPMatch{ - CaseInsensitive: true, - PathExact: "pathExact", - PathPrefix: "pathPrefix", - PathRegex: "pathRegex", + PathExact: "pathExact", + PathPrefix: "pathPrefix", + PathRegex: "pathRegex", Header: []capi.ServiceRouteHTTPMatchHeader{ { Name: "name", @@ -162,13 +155,11 @@ func TestServiceRouter_MatchesConsul(t *testing.T) { Service: "service", ServiceSubset: "serviceSubset", Namespace: "namespace", - Partition: "default", PrefixRewrite: "prefixRewrite", IdleTimeout: 1 * time.Second, RequestTimeout: 1 * time.Second, NumRetries: 1, RetryOnConnectFailure: true, - RetryOn: []string{"gateway-error"}, RetryOnStatusCodes: []uint32{500, 400}, RequestHeaders: &capi.HTTPHeaderModifiers{ Add: map[string]string{ @@ -261,10 +252,9 @@ func TestServiceRouter_ToConsul(t *testing.T) { { Match: &ServiceRouteMatch{ HTTP: &ServiceRouteHTTPMatch{ - CaseInsensitive: true, - PathExact: "pathExact", - PathPrefix: "pathPrefix", - PathRegex: "pathRegex", + PathExact: "pathExact", + PathPrefix: "pathPrefix", + PathRegex: "pathRegex", Header: []ServiceRouteHTTPMatchHeader{ { Name: "name", @@ -296,7 +286,6 @@ func TestServiceRouter_ToConsul(t *testing.T) { RequestTimeout: metav1.Duration{Duration: 1 * time.Second}, NumRetries: 1, RetryOnConnectFailure: true, - RetryOn: []string{"gateway-error"}, RetryOnStatusCodes: []uint32{500, 400}, RequestHeaders: &HTTPHeaderModifiers{ Add: map[string]string{ @@ -340,10 +329,9 @@ func TestServiceRouter_ToConsul(t *testing.T) { { Match: &capi.ServiceRouteMatch{ HTTP: &capi.ServiceRouteHTTPMatch{ - CaseInsensitive: true, - PathExact: "pathExact", - PathPrefix: "pathPrefix", - PathRegex: "pathRegex", + PathExact: "pathExact", + PathPrefix: "pathPrefix", + PathRegex: "pathRegex", Header: []capi.ServiceRouteHTTPMatchHeader{ { Name: "name", @@ -375,7 +363,6 @@ func TestServiceRouter_ToConsul(t *testing.T) { RequestTimeout: 1 * time.Second, NumRetries: 1, RetryOnConnectFailure: true, - RetryOn: []string{"gateway-error"}, RetryOnStatusCodes: []uint32{500, 400}, RequestHeaders: &capi.HTTPHeaderModifiers{ Add: map[string]string{ diff --git a/control-plane/api/v1alpha1/servicerouter_webhook.go b/control-plane/api/v1alpha1/servicerouter_webhook.go index cdcc3ba439..f6837fcf7b 100644 --- a/control-plane/api/v1alpha1/servicerouter_webhook.go +++ b/control-plane/api/v1alpha1/servicerouter_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/servicesplitter_types.go b/control-plane/api/v1alpha1/servicesplitter_types.go index 99ded7324a..b61b1a320b 100644 --- a/control-plane/api/v1alpha1/servicesplitter_types.go +++ b/control-plane/api/v1alpha1/servicesplitter_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( @@ -9,14 +6,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) func init() { @@ -169,18 +165,8 @@ func (in *ServiceSplitter) MatchesConsul(candidate capi.ConfigEntry) bool { if !ok { return false } - - specialEquality := cmp.Options{ - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Splits.Namespace" - }, cmp.Transformer("NormalizeNamespace", normalizeEmptyToDefault)), - cmp.FilterPath(func(path cmp.Path) bool { - return path.String() == "Splits.Partition" - }, cmp.Transformer("NormalizePartition", normalizeEmptyToDefault)), - } - // No datacenter is passed to ToConsul as we ignore the Meta field when checking for equality. - return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceSplitterConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty(), specialEquality) + return cmp.Equal(in.ToConsul(""), configEntry, cmpopts.IgnoreFields(capi.ServiceSplitterConfigEntry{}, "Partition", "Namespace", "Meta", "ModifyIndex", "CreateIndex"), cmpopts.IgnoreUnexported(), cmpopts.EquateEmpty()) } func (in *ServiceSplitter) Validate(consulMeta common.ConsulMeta) error { diff --git a/control-plane/api/v1alpha1/servicesplitter_types_test.go b/control-plane/api/v1alpha1/servicesplitter_types_test.go index c5ab83becc..48e9eeac54 100644 --- a/control-plane/api/v1alpha1/servicesplitter_types_test.go +++ b/control-plane/api/v1alpha1/servicesplitter_types_test.go @@ -1,18 +1,14 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) // Test MatchesConsul. @@ -97,7 +93,6 @@ func TestServiceSplitter_MatchesConsul(t *testing.T) { Service: "foo", ServiceSubset: "bar", Namespace: "baz", - Partition: "default", RequestHeaders: &capi.HTTPHeaderModifiers{ Add: map[string]string{ "foo": "bar", diff --git a/control-plane/api/v1alpha1/servicesplitter_webhook.go b/control-plane/api/v1alpha1/servicesplitter_webhook.go index 42dbbbf54a..c0020c88b8 100644 --- a/control-plane/api/v1alpha1/servicesplitter_webhook.go +++ b/control-plane/api/v1alpha1/servicesplitter_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 148376a393..9b884cf476 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -1,10 +1,6 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( - "encoding/json" "fmt" "strings" @@ -16,9 +12,6 @@ import ( // This file contains structs that are shared between multiple config entries. -// metaValueMaxLength is the maximum allowed string length of a metadata value. -const metaValueMaxLength = 512 - type MeshGatewayMode string // Expose describes HTTP paths to expose through Envoy outside of Connect. @@ -58,35 +51,6 @@ type TransparentProxy struct { DialedDirectly bool `json:"dialedDirectly,omitempty"` } -type MutualTLSMode string - -const ( - // MutualTLSModeDefault represents no specific mode and should - // be used to indicate that a different layer of the configuration - // chain should take precedence. - MutualTLSModeDefault MutualTLSMode = "" - - // MutualTLSModeStrict requires mTLS for incoming traffic. - MutualTLSModeStrict MutualTLSMode = "strict" - - // MutualTLSModePermissive allows incoming non-mTLS traffic. - MutualTLSModePermissive MutualTLSMode = "permissive" -) - -func (m MutualTLSMode) validate() error { - switch m { - case MutualTLSModeDefault, MutualTLSModeStrict, MutualTLSModePermissive: - return nil - } - return fmt.Errorf("Must be one of %q, %q, or %q.", - MutualTLSModeDefault, MutualTLSModeStrict, MutualTLSModePermissive, - ) -} - -func (m MutualTLSMode) toConsul() capi.MutualTLSMode { - return capi.MutualTLSMode(m) -} - // MeshGateway controls how Mesh Gateways are used for upstream Connect // services. type MeshGateway struct { @@ -115,19 +79,6 @@ type HTTPHeaderModifiers struct { Remove []string `json:"remove,omitempty"` } -// EnvoyExtension has configuration for an extension that patches Envoy resources. -type EnvoyExtension struct { - Name string `json:"name,omitempty"` - Required bool `json:"required,omitempty"` - // +kubebuilder:validation:Type=object - // +kubebuilder:validation:Schemaless - // +kubebuilder:pruning:PreserveUnknownFields - Arguments json.RawMessage `json:"arguments,omitempty"` -} - -// EnvoyExtensions represents a list of the EnvoyExtension configuration. -type EnvoyExtensions []EnvoyExtension - func (in MeshGateway) toConsul() capi.MeshGatewayConfig { mode := capi.MeshGatewayMode(in.Mode) switch mode { @@ -225,122 +176,6 @@ func (in *HTTPHeaderModifiers) toConsul() *capi.HTTPHeaderModifiers { } } -func (in EnvoyExtensions) toConsul() []capi.EnvoyExtension { - if in == nil { - return nil - } - - outConfig := make([]capi.EnvoyExtension, 0) - - for _, e := range in { - consulExtension := capi.EnvoyExtension{ - Name: e.Name, - Required: e.Required, - } - - // We already validate that arguments is present - var args map[string]interface{} - _ = json.Unmarshal(e.Arguments, &args) - consulExtension.Arguments = args - outConfig = append(outConfig, consulExtension) - } - - return outConfig -} - -func (in EnvoyExtensions) validate(path *field.Path) field.ErrorList { - if len(in) == 0 { - return nil - } - - var errs field.ErrorList - for i, e := range in { - if err := e.validate(path.Child("envoyExtension").Index(i)); err != nil { - errs = append(errs, err) - } - } - - return errs -} - -func (in EnvoyExtension) validate(path *field.Path) *field.Error { - // Validate that the arguments are not nil - if in.Arguments == nil { - err := field.Required(path.Child("arguments"), "arguments must be defined") - return err - } - // Validate that the arguments are valid json - var outConfig map[string]interface{} - if err := json.Unmarshal(in.Arguments, &outConfig); err != nil { - return field.Invalid(path.Child("arguments"), string(in.Arguments), fmt.Sprintf(`must be valid map value: %s`, err)) - } - return nil -} - -// FailoverPolicy specifies the exact mechanism used for failover. -type FailoverPolicy struct { - // Mode specifies the type of failover that will be performed. Valid values are - // "sequential", "" (equivalent to "sequential") and "order-by-locality". - Mode string `json:"mode,omitempty"` - // Regions is the ordered list of the regions of the failover targets. - // Valid values can be "us-west-1", "us-west-2", and so on. - Regions []string `json:"regions,omitempty"` -} - -func (in *FailoverPolicy) toConsul() *capi.ServiceResolverFailoverPolicy { - if in == nil { - return nil - } - - return &capi.ServiceResolverFailoverPolicy{ - Mode: in.Mode, - Regions: in.Regions, - } -} - -func (in *FailoverPolicy) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if in == nil { - return nil - } - modes := []string{"", "sequential", "order-by-locality"} - if !sliceContains(modes, in.Mode) { - errs = append(errs, field.Invalid(path.Child("mode"), in.Mode, notInSliceMessage(modes))) - } - return errs -} - -// PrioritizeByLocality controls whether the locality of services within the -// local partition will be used to prioritize connectivity. -type PrioritizeByLocality struct { - // Mode specifies the type of prioritization that will be performed - // when selecting nodes in the local partition. - // Valid values are: "" (default "none"), "none", and "failover". - Mode string `json:"mode,omitempty"` -} - -func (in *PrioritizeByLocality) toConsul() *capi.ServiceResolverPrioritizeByLocality { - if in == nil { - return nil - } - - return &capi.ServiceResolverPrioritizeByLocality{ - Mode: in.Mode, - } -} - -func (in *PrioritizeByLocality) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if in == nil { - return nil - } - modes := []string{"", "none", "failover"} - if !sliceContains(modes, in.Mode) { - errs = append(errs, field.Invalid(path.Child("mode"), in.Mode, notInSliceMessage(modes))) - } - return errs -} - func notInSliceMessage(slice []string) string { return fmt.Sprintf(`must be one of "%s"`, strings.Join(slice, `", "`)) } diff --git a/control-plane/api/v1alpha1/status.go b/control-plane/api/v1alpha1/status.go index 0e11bf930b..d7cd0e0b78 100644 --- a/control-plane/api/v1alpha1/status.go +++ b/control-plane/api/v1alpha1/status.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/terminatinggateway_types.go b/control-plane/api/v1alpha1/terminatinggateway_types.go index cf453160ff..6e708b5d44 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_types.go +++ b/control-plane/api/v1alpha1/terminatinggateway_types.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/terminatinggateway_types_test.go b/control-plane/api/v1alpha1/terminatinggateway_types_test.go index 2daf93c6a4..9d8ba9948d 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_types_test.go +++ b/control-plane/api/v1alpha1/terminatinggateway_types_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/terminatinggateway_webhook.go b/control-plane/api/v1alpha1/terminatinggateway_webhook.go index 481a1a1f15..b0427b87ca 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_webhook.go +++ b/control-plane/api/v1alpha1/terminatinggateway_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package v1alpha1 import ( diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 320f05510f..9131d5aeef 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -7,27 +7,10 @@ package v1alpha1 import ( "encoding/json" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/gateway-api/apis/v1beta1" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AccessLogs) DeepCopyInto(out *AccessLogs) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessLogs. -func (in *AccessLogs) DeepCopy() *AccessLogs { - if in == nil { - return nil - } - out := new(AccessLogs) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -65,146 +48,6 @@ func (in Conditions) DeepCopy() Conditions { return *out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ControlPlaneRequestLimit) DeepCopyInto(out *ControlPlaneRequestLimit) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneRequestLimit. -func (in *ControlPlaneRequestLimit) DeepCopy() *ControlPlaneRequestLimit { - if in == nil { - return nil - } - out := new(ControlPlaneRequestLimit) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ControlPlaneRequestLimit) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ControlPlaneRequestLimitList) DeepCopyInto(out *ControlPlaneRequestLimitList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ControlPlaneRequestLimit, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneRequestLimitList. -func (in *ControlPlaneRequestLimitList) DeepCopy() *ControlPlaneRequestLimitList { - if in == nil { - return nil - } - out := new(ControlPlaneRequestLimitList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ControlPlaneRequestLimitList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ControlPlaneRequestLimitSpec) DeepCopyInto(out *ControlPlaneRequestLimitSpec) { - *out = *in - out.ReadWriteRatesConfig = in.ReadWriteRatesConfig - if in.ACL != nil { - in, out := &in.ACL, &out.ACL - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.Catalog != nil { - in, out := &in.Catalog, &out.Catalog - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.ConfigEntry != nil { - in, out := &in.ConfigEntry, &out.ConfigEntry - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.ConnectCA != nil { - in, out := &in.ConnectCA, &out.ConnectCA - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.Coordinate != nil { - in, out := &in.Coordinate, &out.Coordinate - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.DiscoveryChain != nil { - in, out := &in.DiscoveryChain, &out.DiscoveryChain - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.Health != nil { - in, out := &in.Health, &out.Health - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.Intention != nil { - in, out := &in.Intention, &out.Intention - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.KV != nil { - in, out := &in.KV, &out.KV - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.Tenancy != nil { - in, out := &in.Tenancy, &out.Tenancy - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.PreparedQuery != nil { - in, out := &in.PreparedQuery, &out.PreparedQuery - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.Session != nil { - in, out := &in.Session, &out.Session - *out = new(ReadWriteRatesConfig) - **out = **in - } - if in.Txn != nil { - in, out := &in.Txn, &out.Txn - *out = new(ReadWriteRatesConfig) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneRequestLimitSpec. -func (in *ControlPlaneRequestLimitSpec) DeepCopy() *ControlPlaneRequestLimitSpec { - if in == nil { - return nil - } - out := new(ControlPlaneRequestLimitSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CookieConfig) DeepCopyInto(out *CookieConfig) { *out = *in @@ -221,102 +64,6 @@ func (in *CookieConfig) DeepCopy() *CookieConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CopyAnnotationsSpec) DeepCopyInto(out *CopyAnnotationsSpec) { - *out = *in - if in.Service != nil { - in, out := &in.Service, &out.Service - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CopyAnnotationsSpec. -func (in *CopyAnnotationsSpec) DeepCopy() *CopyAnnotationsSpec { - if in == nil { - return nil - } - out := new(CopyAnnotationsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { - *out = *in - if in.DefaultInstances != nil { - in, out := &in.DefaultInstances, &out.DefaultInstances - *out = new(int32) - **out = **in - } - if in.MaxInstances != nil { - in, out := &in.MaxInstances, &out.MaxInstances - *out = new(int32) - **out = **in - } - if in.MinInstances != nil { - in, out := &in.MinInstances, &out.MinInstances - *out = new(int32) - **out = **in - } - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = new(v1.ResourceRequirements) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec. -func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { - if in == nil { - return nil - } - out := new(DeploymentSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EnvoyExtension) DeepCopyInto(out *EnvoyExtension) { - *out = *in - if in.Arguments != nil { - in, out := &in.Arguments, &out.Arguments - *out = make(json.RawMessage, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyExtension. -func (in *EnvoyExtension) DeepCopy() *EnvoyExtension { - if in == nil { - return nil - } - out := new(EnvoyExtension) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in EnvoyExtensions) DeepCopyInto(out *EnvoyExtensions) { - { - in := &in - *out = make(EnvoyExtensions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyExtensions. -func (in EnvoyExtensions) DeepCopy() EnvoyExtensions { - if in == nil { - return nil - } - out := new(EnvoyExtensions) - in.DeepCopyInto(out) - return *out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExportedService) DeepCopyInto(out *ExportedService) { *out = *in @@ -454,443 +201,130 @@ func (in *ExposePath) DeepCopy() *ExposePath { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FailoverPolicy) DeepCopyInto(out *FailoverPolicy) { +func (in *GatewayServiceTLSConfig) DeepCopyInto(out *GatewayServiceTLSConfig) { *out = *in - if in.Regions != nil { - in, out := &in.Regions, &out.Regions - *out = make([]string, len(*in)) - copy(*out, *in) + if in.SDS != nil { + in, out := &in.SDS, &out.SDS + *out = new(GatewayTLSSDSConfig) + **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailoverPolicy. -func (in *FailoverPolicy) DeepCopy() *FailoverPolicy { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayServiceTLSConfig. +func (in *GatewayServiceTLSConfig) DeepCopy() *GatewayServiceTLSConfig { if in == nil { return nil } - out := new(FailoverPolicy) + out := new(GatewayServiceTLSConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassConfig) DeepCopyInto(out *GatewayClassConfig) { +func (in *GatewayTLSConfig) DeepCopyInto(out *GatewayTLSConfig) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) + if in.SDS != nil { + in, out := &in.SDS, &out.SDS + *out = new(GatewayTLSSDSConfig) + **out = **in + } + if in.CipherSuites != nil { + in, out := &in.CipherSuites, &out.CipherSuites + *out = make([]string, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfig. -func (in *GatewayClassConfig) DeepCopy() *GatewayClassConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayTLSConfig. +func (in *GatewayTLSConfig) DeepCopy() *GatewayTLSConfig { if in == nil { return nil } - out := new(GatewayClassConfig) + out := new(GatewayTLSConfig) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayClassConfig) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassConfigList) DeepCopyInto(out *GatewayClassConfigList) { +func (in *GatewayTLSSDSConfig) DeepCopyInto(out *GatewayTLSSDSConfig) { *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]GatewayClassConfig, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigList. -func (in *GatewayClassConfigList) DeepCopy() *GatewayClassConfigList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayTLSSDSConfig. +func (in *GatewayTLSSDSConfig) DeepCopy() *GatewayTLSSDSConfig { if in == nil { return nil } - out := new(GatewayClassConfigList) + out := new(GatewayTLSSDSConfig) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayClassConfigList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayClassConfigSpec) DeepCopyInto(out *GatewayClassConfigSpec) { +func (in *HTTPHeaderModifiers) DeepCopyInto(out *HTTPHeaderModifiers) { *out = *in - if in.ServiceType != nil { - in, out := &in.ServiceType, &out.ServiceType - *out = new(v1.ServiceType) - **out = **in - } - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector + if in.Add != nil { + in, out := &in.Add, &out.Add *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } - if in.Tolerations != nil { - in, out := &in.Tolerations, &out.Tolerations - *out = make([]v1.Toleration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.Set != nil { + in, out := &in.Set, &out.Set + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } - in.DeploymentSpec.DeepCopyInto(&out.DeploymentSpec) - in.CopyAnnotations.DeepCopyInto(&out.CopyAnnotations) + if in.Remove != nil { + in, out := &in.Remove, &out.Remove + *out = make([]string, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassConfigSpec. -func (in *GatewayClassConfigSpec) DeepCopy() *GatewayClassConfigSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeaderModifiers. +func (in *HTTPHeaderModifiers) DeepCopy() *HTTPHeaderModifiers { if in == nil { return nil } - out := new(GatewayClassConfigSpec) + out := new(HTTPHeaderModifiers) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayJWTClaimVerification) DeepCopyInto(out *GatewayJWTClaimVerification) { +func (in *HashPolicy) DeepCopyInto(out *HashPolicy) { *out = *in - if in.Path != nil { - in, out := &in.Path, &out.Path - *out = make([]string, len(*in)) - copy(*out, *in) + if in.CookieConfig != nil { + in, out := &in.CookieConfig, &out.CookieConfig + *out = new(CookieConfig) + **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTClaimVerification. -func (in *GatewayJWTClaimVerification) DeepCopy() *GatewayJWTClaimVerification { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HashPolicy. +func (in *HashPolicy) DeepCopy() *HashPolicy { if in == nil { return nil } - out := new(GatewayJWTClaimVerification) + out := new(HashPolicy) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayJWTProvider) DeepCopyInto(out *GatewayJWTProvider) { +func (in *IngressGateway) DeepCopyInto(out *IngressGateway) { *out = *in - if in.VerifyClaims != nil { - in, out := &in.VerifyClaims, &out.VerifyClaims - *out = make([]*GatewayJWTClaimVerification, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(GatewayJWTClaimVerification) - (*in).DeepCopyInto(*out) - } - } - } + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTProvider. -func (in *GatewayJWTProvider) DeepCopy() *GatewayJWTProvider { - if in == nil { - return nil - } - out := new(GatewayJWTProvider) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayJWTRequirement) DeepCopyInto(out *GatewayJWTRequirement) { - *out = *in - if in.Providers != nil { - in, out := &in.Providers, &out.Providers - *out = make([]*GatewayJWTProvider, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(GatewayJWTProvider) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTRequirement. -func (in *GatewayJWTRequirement) DeepCopy() *GatewayJWTRequirement { - if in == nil { - return nil - } - out := new(GatewayJWTRequirement) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicy) DeepCopyInto(out *GatewayPolicy) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicy. -func (in *GatewayPolicy) DeepCopy() *GatewayPolicy { - if in == nil { - return nil - } - out := new(GatewayPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayPolicy) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicyConfig) DeepCopyInto(out *GatewayPolicyConfig) { - *out = *in - if in.JWT != nil { - in, out := &in.JWT, &out.JWT - *out = new(GatewayJWTRequirement) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyConfig. -func (in *GatewayPolicyConfig) DeepCopy() *GatewayPolicyConfig { - if in == nil { - return nil - } - out := new(GatewayPolicyConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicyList) DeepCopyInto(out *GatewayPolicyList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]GatewayPolicy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyList. -func (in *GatewayPolicyList) DeepCopy() *GatewayPolicyList { - if in == nil { - return nil - } - out := new(GatewayPolicyList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayPolicyList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicySpec) DeepCopyInto(out *GatewayPolicySpec) { - *out = *in - in.TargetRef.DeepCopyInto(&out.TargetRef) - if in.Override != nil { - in, out := &in.Override, &out.Override - *out = new(GatewayPolicyConfig) - (*in).DeepCopyInto(*out) - } - if in.Default != nil { - in, out := &in.Default, &out.Default - *out = new(GatewayPolicyConfig) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicySpec. -func (in *GatewayPolicySpec) DeepCopy() *GatewayPolicySpec { - if in == nil { - return nil - } - out := new(GatewayPolicySpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicyStatus) DeepCopyInto(out *GatewayPolicyStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyStatus. -func (in *GatewayPolicyStatus) DeepCopy() *GatewayPolicyStatus { - if in == nil { - return nil - } - out := new(GatewayPolicyStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayServiceTLSConfig) DeepCopyInto(out *GatewayServiceTLSConfig) { - *out = *in - if in.SDS != nil { - in, out := &in.SDS, &out.SDS - *out = new(GatewayTLSSDSConfig) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayServiceTLSConfig. -func (in *GatewayServiceTLSConfig) DeepCopy() *GatewayServiceTLSConfig { - if in == nil { - return nil - } - out := new(GatewayServiceTLSConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayTLSConfig) DeepCopyInto(out *GatewayTLSConfig) { - *out = *in - if in.SDS != nil { - in, out := &in.SDS, &out.SDS - *out = new(GatewayTLSSDSConfig) - **out = **in - } - if in.CipherSuites != nil { - in, out := &in.CipherSuites, &out.CipherSuites - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayTLSConfig. -func (in *GatewayTLSConfig) DeepCopy() *GatewayTLSConfig { - if in == nil { - return nil - } - out := new(GatewayTLSConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayTLSSDSConfig) DeepCopyInto(out *GatewayTLSSDSConfig) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayTLSSDSConfig. -func (in *GatewayTLSSDSConfig) DeepCopy() *GatewayTLSSDSConfig { - if in == nil { - return nil - } - out := new(GatewayTLSSDSConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HTTPHeaderModifiers) DeepCopyInto(out *HTTPHeaderModifiers) { - *out = *in - if in.Add != nil { - in, out := &in.Add, &out.Add - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Set != nil { - in, out := &in.Set, &out.Set - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Remove != nil { - in, out := &in.Remove, &out.Remove - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeaderModifiers. -func (in *HTTPHeaderModifiers) DeepCopy() *HTTPHeaderModifiers { - if in == nil { - return nil - } - out := new(HTTPHeaderModifiers) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HashPolicy) DeepCopyInto(out *HashPolicy) { - *out = *in - if in.CookieConfig != nil { - in, out := &in.CookieConfig, &out.CookieConfig - *out = new(CookieConfig) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HashPolicy. -func (in *HashPolicy) DeepCopy() *HashPolicy { - if in == nil { - return nil - } - out := new(HashPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IngressGateway) DeepCopyInto(out *IngressGateway) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressGateway. -func (in *IngressGateway) DeepCopy() *IngressGateway { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressGateway. +func (in *IngressGateway) DeepCopy() *IngressGateway { if in == nil { return nil } @@ -1048,11 +482,6 @@ func (in *IngressServiceConfig) DeepCopyInto(out *IngressServiceConfig) { *out = new(uint32) **out = **in } - if in.PassiveHealthCheck != nil { - in, out := &in.PassiveHealthCheck, &out.PassiveHealthCheck - *out = new(PassiveHealthCheck) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressServiceConfig. @@ -1066,51 +495,16 @@ func (in *IngressServiceConfig) DeepCopy() *IngressServiceConfig { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *InstanceLevelRateLimits) DeepCopyInto(out *InstanceLevelRateLimits) { +func (in *IntentionDestination) DeepCopyInto(out *IntentionDestination) { *out = *in - if in.Routes != nil { - in, out := &in.Routes, &out.Routes - *out = make([]InstanceLevelRouteRateLimits, len(*in)) - copy(*out, *in) - } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceLevelRateLimits. -func (in *InstanceLevelRateLimits) DeepCopy() *InstanceLevelRateLimits { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionDestination. +func (in *IntentionDestination) DeepCopy() *IntentionDestination { if in == nil { return nil } - out := new(InstanceLevelRateLimits) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *InstanceLevelRouteRateLimits) DeepCopyInto(out *InstanceLevelRouteRateLimits) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceLevelRouteRateLimits. -func (in *InstanceLevelRouteRateLimits) DeepCopy() *InstanceLevelRouteRateLimits { - if in == nil { - return nil - } - out := new(InstanceLevelRouteRateLimits) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IntentionDestination) DeepCopyInto(out *IntentionDestination) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionDestination. -func (in *IntentionDestination) DeepCopy() *IntentionDestination { - if in == nil { - return nil - } - out := new(IntentionDestination) + out := new(IntentionDestination) in.DeepCopyInto(out) return out } @@ -1174,78 +568,6 @@ func (in *IntentionHTTPPermission) DeepCopy() *IntentionHTTPPermission { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IntentionJWTClaimVerification) DeepCopyInto(out *IntentionJWTClaimVerification) { - *out = *in - if in.Path != nil { - in, out := &in.Path, &out.Path - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionJWTClaimVerification. -func (in *IntentionJWTClaimVerification) DeepCopy() *IntentionJWTClaimVerification { - if in == nil { - return nil - } - out := new(IntentionJWTClaimVerification) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IntentionJWTProvider) DeepCopyInto(out *IntentionJWTProvider) { - *out = *in - if in.VerifyClaims != nil { - in, out := &in.VerifyClaims, &out.VerifyClaims - *out = make([]*IntentionJWTClaimVerification, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(IntentionJWTClaimVerification) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionJWTProvider. -func (in *IntentionJWTProvider) DeepCopy() *IntentionJWTProvider { - if in == nil { - return nil - } - out := new(IntentionJWTProvider) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IntentionJWTRequirement) DeepCopyInto(out *IntentionJWTRequirement) { - *out = *in - if in.Providers != nil { - in, out := &in.Providers, &out.Providers - *out = make([]*IntentionJWTProvider, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(IntentionJWTProvider) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionJWTRequirement. -func (in *IntentionJWTRequirement) DeepCopy() *IntentionJWTRequirement { - if in == nil { - return nil - } - out := new(IntentionJWTRequirement) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IntentionPermission) DeepCopyInto(out *IntentionPermission) { *out = *in @@ -1254,11 +576,6 @@ func (in *IntentionPermission) DeepCopyInto(out *IntentionPermission) { *out = new(IntentionHTTPPermission) (*in).DeepCopyInto(*out) } - if in.JWT != nil { - in, out := &in.JWT, &out.JWT - *out = new(IntentionJWTRequirement) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionPermission. @@ -1297,1221 +614,285 @@ func (in IntentionPermissions) DeepCopy() IntentionPermissions { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JSONWebKeySet) DeepCopyInto(out *JSONWebKeySet) { +func (in *LeastRequestConfig) DeepCopyInto(out *LeastRequestConfig) { *out = *in - if in.Local != nil { - in, out := &in.Local, &out.Local - *out = new(LocalJWKS) - **out = **in - } - if in.Remote != nil { - in, out := &in.Remote, &out.Remote - *out = new(RemoteJWKS) - (*in).DeepCopyInto(*out) - } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONWebKeySet. -func (in *JSONWebKeySet) DeepCopy() *JSONWebKeySet { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeastRequestConfig. +func (in *LeastRequestConfig) DeepCopy() *LeastRequestConfig { if in == nil { return nil } - out := new(JSONWebKeySet) + out := new(LeastRequestConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWKSCluster) DeepCopyInto(out *JWKSCluster) { +func (in *LinkedService) DeepCopyInto(out *LinkedService) { *out = *in - if in.TLSCertificates != nil { - in, out := &in.TLSCertificates, &out.TLSCertificates - *out = new(JWKSTLSCertificate) - (*in).DeepCopyInto(*out) - } - out.ConnectTimeout = in.ConnectTimeout } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSCluster. -func (in *JWKSCluster) DeepCopy() *JWKSCluster { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkedService. +func (in *LinkedService) DeepCopy() *LinkedService { if in == nil { return nil } - out := new(JWKSCluster) + out := new(LinkedService) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWKSRetryPolicy) DeepCopyInto(out *JWKSRetryPolicy) { +func (in *LoadBalancer) DeepCopyInto(out *LoadBalancer) { *out = *in - if in.RetryPolicyBackOff != nil { - in, out := &in.RetryPolicyBackOff, &out.RetryPolicyBackOff - *out = new(RetryPolicyBackOff) + if in.RingHashConfig != nil { + in, out := &in.RingHashConfig, &out.RingHashConfig + *out = new(RingHashConfig) **out = **in } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSRetryPolicy. -func (in *JWKSRetryPolicy) DeepCopy() *JWKSRetryPolicy { - if in == nil { - return nil - } - out := new(JWKSRetryPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWKSTLSCertProviderInstance) DeepCopyInto(out *JWKSTLSCertProviderInstance) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSTLSCertProviderInstance. -func (in *JWKSTLSCertProviderInstance) DeepCopy() *JWKSTLSCertProviderInstance { - if in == nil { - return nil - } - out := new(JWKSTLSCertProviderInstance) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWKSTLSCertTrustedCA) DeepCopyInto(out *JWKSTLSCertTrustedCA) { - *out = *in - if in.InlineBytes != nil { - in, out := &in.InlineBytes, &out.InlineBytes - *out = make([]byte, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSTLSCertTrustedCA. -func (in *JWKSTLSCertTrustedCA) DeepCopy() *JWKSTLSCertTrustedCA { - if in == nil { - return nil - } - out := new(JWKSTLSCertTrustedCA) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWKSTLSCertificate) DeepCopyInto(out *JWKSTLSCertificate) { - *out = *in - if in.CaCertificateProviderInstance != nil { - in, out := &in.CaCertificateProviderInstance, &out.CaCertificateProviderInstance - *out = new(JWKSTLSCertProviderInstance) + if in.LeastRequestConfig != nil { + in, out := &in.LeastRequestConfig, &out.LeastRequestConfig + *out = new(LeastRequestConfig) **out = **in } - if in.TrustedCA != nil { - in, out := &in.TrustedCA, &out.TrustedCA - *out = new(JWKSTLSCertTrustedCA) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSTLSCertificate. -func (in *JWKSTLSCertificate) DeepCopy() *JWKSTLSCertificate { - if in == nil { - return nil + if in.HashPolicies != nil { + in, out := &in.HashPolicies, &out.HashPolicies + *out = make([]HashPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } - out := new(JWKSTLSCertificate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTCacheConfig) DeepCopyInto(out *JWTCacheConfig) { - *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTCacheConfig. -func (in *JWTCacheConfig) DeepCopy() *JWTCacheConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancer. +func (in *LoadBalancer) DeepCopy() *LoadBalancer { if in == nil { return nil } - out := new(JWTCacheConfig) + out := new(LoadBalancer) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTForwardingConfig) DeepCopyInto(out *JWTForwardingConfig) { +func (in *Mesh) DeepCopyInto(out *Mesh) { *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTForwardingConfig. -func (in *JWTForwardingConfig) DeepCopy() *JWTForwardingConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mesh. +func (in *Mesh) DeepCopy() *Mesh { if in == nil { return nil } - out := new(JWTForwardingConfig) + out := new(Mesh) in.DeepCopyInto(out) return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTLocation) DeepCopyInto(out *JWTLocation) { - *out = *in - if in.Header != nil { - in, out := &in.Header, &out.Header - *out = new(JWTLocationHeader) - **out = **in - } - if in.QueryParam != nil { - in, out := &in.QueryParam, &out.QueryParam - *out = new(JWTLocationQueryParam) - **out = **in - } - if in.Cookie != nil { - in, out := &in.Cookie, &out.Cookie - *out = new(JWTLocationCookie) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocation. -func (in *JWTLocation) DeepCopy() *JWTLocation { - if in == nil { - return nil +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Mesh) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c } - out := new(JWTLocation) - in.DeepCopyInto(out) - return out + return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTLocationCookie) DeepCopyInto(out *JWTLocationCookie) { +func (in *MeshDirectionalTLSConfig) DeepCopyInto(out *MeshDirectionalTLSConfig) { *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocationCookie. -func (in *JWTLocationCookie) DeepCopy() *JWTLocationCookie { - if in == nil { - return nil + if in.CipherSuites != nil { + in, out := &in.CipherSuites, &out.CipherSuites + *out = make([]string, len(*in)) + copy(*out, *in) } - out := new(JWTLocationCookie) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTLocationHeader) DeepCopyInto(out *JWTLocationHeader) { - *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocationHeader. -func (in *JWTLocationHeader) DeepCopy() *JWTLocationHeader { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshDirectionalTLSConfig. +func (in *MeshDirectionalTLSConfig) DeepCopy() *MeshDirectionalTLSConfig { if in == nil { return nil } - out := new(JWTLocationHeader) + out := new(MeshDirectionalTLSConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTLocationQueryParam) DeepCopyInto(out *JWTLocationQueryParam) { +func (in *MeshGateway) DeepCopyInto(out *MeshGateway) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocationQueryParam. -func (in *JWTLocationQueryParam) DeepCopy() *JWTLocationQueryParam { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshGateway. +func (in *MeshGateway) DeepCopy() *MeshGateway { if in == nil { return nil } - out := new(JWTLocationQueryParam) + out := new(MeshGateway) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in JWTLocations) DeepCopyInto(out *JWTLocations) { - { - in := &in - *out = make(JWTLocations, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(JWTLocation) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTLocations. -func (in JWTLocations) DeepCopy() JWTLocations { - if in == nil { - return nil - } - out := new(JWTLocations) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTProvider) DeepCopyInto(out *JWTProvider) { +func (in *MeshHTTPConfig) DeepCopyInto(out *MeshHTTPConfig) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTProvider. -func (in *JWTProvider) DeepCopy() *JWTProvider { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshHTTPConfig. +func (in *MeshHTTPConfig) DeepCopy() *MeshHTTPConfig { if in == nil { return nil } - out := new(JWTProvider) + out := new(MeshHTTPConfig) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *JWTProvider) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTProviderList) DeepCopyInto(out *JWTProviderList) { +func (in *MeshList) DeepCopyInto(out *MeshList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]JWTProvider, len(*in)) + *out = make([]Mesh, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTProviderList. -func (in *JWTProviderList) DeepCopy() *JWTProviderList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshList. +func (in *MeshList) DeepCopy() *MeshList { if in == nil { return nil } - out := new(JWTProviderList) + out := new(MeshList) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *JWTProviderList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JWTProviderSpec) DeepCopyInto(out *JWTProviderSpec) { - *out = *in - if in.JSONWebKeySet != nil { - in, out := &in.JSONWebKeySet, &out.JSONWebKeySet - *out = new(JSONWebKeySet) - (*in).DeepCopyInto(*out) - } - if in.Audiences != nil { - in, out := &in.Audiences, &out.Audiences - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Locations != nil { - in, out := &in.Locations, &out.Locations - *out = make([]*JWTLocation, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(JWTLocation) - (*in).DeepCopyInto(*out) - } - } - } - if in.Forwarding != nil { - in, out := &in.Forwarding, &out.Forwarding - *out = new(JWTForwardingConfig) - **out = **in - } - if in.CacheConfig != nil { - in, out := &in.CacheConfig, &out.CacheConfig - *out = new(JWTCacheConfig) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTProviderSpec. -func (in *JWTProviderSpec) DeepCopy() *JWTProviderSpec { - if in == nil { - return nil - } - out := new(JWTProviderSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LeastRequestConfig) DeepCopyInto(out *LeastRequestConfig) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeastRequestConfig. -func (in *LeastRequestConfig) DeepCopy() *LeastRequestConfig { - if in == nil { - return nil - } - out := new(LeastRequestConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LinkedService) DeepCopyInto(out *LinkedService) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkedService. -func (in *LinkedService) DeepCopy() *LinkedService { - if in == nil { - return nil - } - out := new(LinkedService) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LoadBalancer) DeepCopyInto(out *LoadBalancer) { - *out = *in - if in.RingHashConfig != nil { - in, out := &in.RingHashConfig, &out.RingHashConfig - *out = new(RingHashConfig) - **out = **in - } - if in.LeastRequestConfig != nil { - in, out := &in.LeastRequestConfig, &out.LeastRequestConfig - *out = new(LeastRequestConfig) - **out = **in - } - if in.HashPolicies != nil { - in, out := &in.HashPolicies, &out.HashPolicies - *out = make([]HashPolicy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancer. -func (in *LoadBalancer) DeepCopy() *LoadBalancer { - if in == nil { - return nil - } - out := new(LoadBalancer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LocalJWKS) DeepCopyInto(out *LocalJWKS) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalJWKS. -func (in *LocalJWKS) DeepCopy() *LocalJWKS { - if in == nil { - return nil - } - out := new(LocalJWKS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Mesh) DeepCopyInto(out *Mesh) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mesh. -func (in *Mesh) DeepCopy() *Mesh { - if in == nil { - return nil - } - out := new(Mesh) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Mesh) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshDirectionalTLSConfig) DeepCopyInto(out *MeshDirectionalTLSConfig) { - *out = *in - if in.CipherSuites != nil { - in, out := &in.CipherSuites, &out.CipherSuites - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshDirectionalTLSConfig. -func (in *MeshDirectionalTLSConfig) DeepCopy() *MeshDirectionalTLSConfig { - if in == nil { - return nil - } - out := new(MeshDirectionalTLSConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshGateway) DeepCopyInto(out *MeshGateway) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshGateway. -func (in *MeshGateway) DeepCopy() *MeshGateway { - if in == nil { - return nil - } - out := new(MeshGateway) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshHTTPConfig) DeepCopyInto(out *MeshHTTPConfig) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshHTTPConfig. -func (in *MeshHTTPConfig) DeepCopy() *MeshHTTPConfig { - if in == nil { - return nil - } - out := new(MeshHTTPConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshList) DeepCopyInto(out *MeshList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Mesh, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshList. -func (in *MeshList) DeepCopy() *MeshList { - if in == nil { - return nil - } - out := new(MeshList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *MeshList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshService) DeepCopyInto(out *MeshService) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshService. -func (in *MeshService) DeepCopy() *MeshService { - if in == nil { - return nil - } - out := new(MeshService) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *MeshService) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshServiceList) DeepCopyInto(out *MeshServiceList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]MeshService, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshServiceList. -func (in *MeshServiceList) DeepCopy() *MeshServiceList { - if in == nil { - return nil - } - out := new(MeshServiceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *MeshServiceList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshServiceSpec) DeepCopyInto(out *MeshServiceSpec) { - *out = *in - if in.Peer != nil { - in, out := &in.Peer, &out.Peer - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshServiceSpec. -func (in *MeshServiceSpec) DeepCopy() *MeshServiceSpec { - if in == nil { - return nil - } - out := new(MeshServiceSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshSpec) DeepCopyInto(out *MeshSpec) { - *out = *in - out.TransparentProxy = in.TransparentProxy - if in.TLS != nil { - in, out := &in.TLS, &out.TLS - *out = new(MeshTLSConfig) - (*in).DeepCopyInto(*out) - } - if in.HTTP != nil { - in, out := &in.HTTP, &out.HTTP - *out = new(MeshHTTPConfig) - **out = **in - } - if in.Peering != nil { - in, out := &in.Peering, &out.Peering - *out = new(PeeringMeshConfig) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshSpec. -func (in *MeshSpec) DeepCopy() *MeshSpec { - if in == nil { - return nil - } - out := new(MeshSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MeshTLSConfig) DeepCopyInto(out *MeshTLSConfig) { - *out = *in - if in.Incoming != nil { - in, out := &in.Incoming, &out.Incoming - *out = new(MeshDirectionalTLSConfig) - (*in).DeepCopyInto(*out) - } - if in.Outgoing != nil { - in, out := &in.Outgoing, &out.Outgoing - *out = new(MeshDirectionalTLSConfig) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLSConfig. -func (in *MeshTLSConfig) DeepCopy() *MeshTLSConfig { - if in == nil { - return nil - } - out := new(MeshTLSConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) { - *out = *in - out.Interval = in.Interval - if in.EnforcingConsecutive5xx != nil { - in, out := &in.EnforcingConsecutive5xx, &out.EnforcingConsecutive5xx - *out = new(uint32) - **out = **in - } - if in.MaxEjectionPercent != nil { - in, out := &in.MaxEjectionPercent, &out.MaxEjectionPercent - *out = new(uint32) - **out = **in - } - if in.BaseEjectionTime != nil { - in, out := &in.BaseEjectionTime, &out.BaseEjectionTime - *out = new(metav1.Duration) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PassiveHealthCheck. -func (in *PassiveHealthCheck) DeepCopy() *PassiveHealthCheck { - if in == nil { - return nil - } - out := new(PassiveHealthCheck) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Peer) DeepCopyInto(out *Peer) { - *out = *in - if in.Secret != nil { - in, out := &in.Secret, &out.Secret - *out = new(Secret) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Peer. -func (in *Peer) DeepCopy() *Peer { - if in == nil { - return nil - } - out := new(Peer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringAcceptor) DeepCopyInto(out *PeeringAcceptor) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringAcceptor. -func (in *PeeringAcceptor) DeepCopy() *PeeringAcceptor { - if in == nil { - return nil - } - out := new(PeeringAcceptor) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PeeringAcceptor) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringAcceptorList) DeepCopyInto(out *PeeringAcceptorList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PeeringAcceptor, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringAcceptorList. -func (in *PeeringAcceptorList) DeepCopy() *PeeringAcceptorList { - if in == nil { - return nil - } - out := new(PeeringAcceptorList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PeeringAcceptorList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringAcceptorSpec) DeepCopyInto(out *PeeringAcceptorSpec) { - *out = *in - if in.Peer != nil { - in, out := &in.Peer, &out.Peer - *out = new(Peer) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringAcceptorSpec. -func (in *PeeringAcceptorSpec) DeepCopy() *PeeringAcceptorSpec { - if in == nil { - return nil - } - out := new(PeeringAcceptorSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringAcceptorStatus) DeepCopyInto(out *PeeringAcceptorStatus) { - *out = *in - if in.LatestPeeringVersion != nil { - in, out := &in.LatestPeeringVersion, &out.LatestPeeringVersion - *out = new(uint64) - **out = **in - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - *out = new(SecretRefStatus) - **out = **in - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastSyncedTime != nil { - in, out := &in.LastSyncedTime, &out.LastSyncedTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringAcceptorStatus. -func (in *PeeringAcceptorStatus) DeepCopy() *PeeringAcceptorStatus { - if in == nil { - return nil - } - out := new(PeeringAcceptorStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringDialer) DeepCopyInto(out *PeeringDialer) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringDialer. -func (in *PeeringDialer) DeepCopy() *PeeringDialer { - if in == nil { - return nil - } - out := new(PeeringDialer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PeeringDialer) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringDialerList) DeepCopyInto(out *PeeringDialerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PeeringDialer, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringDialerList. -func (in *PeeringDialerList) DeepCopy() *PeeringDialerList { - if in == nil { - return nil - } - out := new(PeeringDialerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PeeringDialerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringDialerSpec) DeepCopyInto(out *PeeringDialerSpec) { - *out = *in - if in.Peer != nil { - in, out := &in.Peer, &out.Peer - *out = new(Peer) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringDialerSpec. -func (in *PeeringDialerSpec) DeepCopy() *PeeringDialerSpec { - if in == nil { - return nil - } - out := new(PeeringDialerSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringDialerStatus) DeepCopyInto(out *PeeringDialerStatus) { - *out = *in - if in.LatestPeeringVersion != nil { - in, out := &in.LatestPeeringVersion, &out.LatestPeeringVersion - *out = new(uint64) - **out = **in - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - *out = new(SecretRefStatus) - **out = **in - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastSyncedTime != nil { - in, out := &in.LastSyncedTime, &out.LastSyncedTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringDialerStatus. -func (in *PeeringDialerStatus) DeepCopy() *PeeringDialerStatus { - if in == nil { - return nil - } - out := new(PeeringDialerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PeeringMeshConfig) DeepCopyInto(out *PeeringMeshConfig) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringMeshConfig. -func (in *PeeringMeshConfig) DeepCopy() *PeeringMeshConfig { - if in == nil { - return nil - } - out := new(PeeringMeshConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PolicyTargetReference) DeepCopyInto(out *PolicyTargetReference) { - *out = *in - if in.SectionName != nil { - in, out := &in.SectionName, &out.SectionName - *out = new(v1beta1.SectionName) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyTargetReference. -func (in *PolicyTargetReference) DeepCopy() *PolicyTargetReference { - if in == nil { - return nil - } - out := new(PolicyTargetReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PrioritizeByLocality) DeepCopyInto(out *PrioritizeByLocality) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrioritizeByLocality. -func (in *PrioritizeByLocality) DeepCopy() *PrioritizeByLocality { - if in == nil { - return nil - } - out := new(PrioritizeByLocality) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxyDefaults) DeepCopyInto(out *ProxyDefaults) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaults. -func (in *ProxyDefaults) DeepCopy() *ProxyDefaults { - if in == nil { - return nil - } - out := new(ProxyDefaults) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ProxyDefaults) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxyDefaultsList) DeepCopyInto(out *ProxyDefaultsList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ProxyDefaults, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsList. -func (in *ProxyDefaultsList) DeepCopy() *ProxyDefaultsList { - if in == nil { - return nil - } - out := new(ProxyDefaultsList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ProxyDefaultsList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxyDefaultsSpec) DeepCopyInto(out *ProxyDefaultsSpec) { - *out = *in - if in.Mode != nil { - in, out := &in.Mode, &out.Mode - *out = new(ProxyMode) - **out = **in - } - if in.TransparentProxy != nil { - in, out := &in.TransparentProxy, &out.TransparentProxy - *out = new(TransparentProxy) - **out = **in - } - if in.Config != nil { - in, out := &in.Config, &out.Config - *out = make(json.RawMessage, len(*in)) - copy(*out, *in) - } - out.MeshGateway = in.MeshGateway - in.Expose.DeepCopyInto(&out.Expose) - if in.AccessLogs != nil { - in, out := &in.AccessLogs, &out.AccessLogs - *out = new(AccessLogs) - **out = **in - } - if in.EnvoyExtensions != nil { - in, out := &in.EnvoyExtensions, &out.EnvoyExtensions - *out = make(EnvoyExtensions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.FailoverPolicy != nil { - in, out := &in.FailoverPolicy, &out.FailoverPolicy - *out = new(FailoverPolicy) - (*in).DeepCopyInto(*out) - } - if in.PrioritizeByLocality != nil { - in, out := &in.PrioritizeByLocality, &out.PrioritizeByLocality - *out = new(PrioritizeByLocality) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsSpec. -func (in *ProxyDefaultsSpec) DeepCopy() *ProxyDefaultsSpec { - if in == nil { - return nil - } - out := new(ProxyDefaultsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RateLimits) DeepCopyInto(out *RateLimits) { - *out = *in - in.InstanceLevel.DeepCopyInto(&out.InstanceLevel) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimits. -func (in *RateLimits) DeepCopy() *RateLimits { - if in == nil { - return nil +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MeshList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c } - out := new(RateLimits) - in.DeepCopyInto(out) - return out + return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReadWriteRatesConfig) DeepCopyInto(out *ReadWriteRatesConfig) { +func (in *MeshSpec) DeepCopyInto(out *MeshSpec) { *out = *in + out.TransparentProxy = in.TransparentProxy + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(MeshTLSConfig) + (*in).DeepCopyInto(*out) + } + if in.HTTP != nil { + in, out := &in.HTTP, &out.HTTP + *out = new(MeshHTTPConfig) + **out = **in + } + if in.Peering != nil { + in, out := &in.Peering, &out.Peering + *out = new(PeeringMeshConfig) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReadWriteRatesConfig. -func (in *ReadWriteRatesConfig) DeepCopy() *ReadWriteRatesConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshSpec. +func (in *MeshSpec) DeepCopy() *MeshSpec { if in == nil { return nil } - out := new(ReadWriteRatesConfig) + out := new(MeshSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RemoteJWKS) DeepCopyInto(out *RemoteJWKS) { +func (in *MeshTLSConfig) DeepCopyInto(out *MeshTLSConfig) { *out = *in - out.CacheDuration = in.CacheDuration - if in.RetryPolicy != nil { - in, out := &in.RetryPolicy, &out.RetryPolicy - *out = new(JWKSRetryPolicy) + if in.Incoming != nil { + in, out := &in.Incoming, &out.Incoming + *out = new(MeshDirectionalTLSConfig) (*in).DeepCopyInto(*out) } - if in.JWKSCluster != nil { - in, out := &in.JWKSCluster, &out.JWKSCluster - *out = new(JWKSCluster) + if in.Outgoing != nil { + in, out := &in.Outgoing, &out.Outgoing + *out = new(MeshDirectionalTLSConfig) (*in).DeepCopyInto(*out) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteJWKS. -func (in *RemoteJWKS) DeepCopy() *RemoteJWKS { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLSConfig. +func (in *MeshTLSConfig) DeepCopy() *MeshTLSConfig { if in == nil { return nil } - out := new(RemoteJWKS) + out := new(MeshTLSConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RetryPolicyBackOff) DeepCopyInto(out *RetryPolicyBackOff) { +func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) { *out = *in - out.BaseInterval = in.BaseInterval - out.MaxInterval = in.MaxInterval + out.Interval = in.Interval + if in.EnforcingConsecutive5xx != nil { + in, out := &in.EnforcingConsecutive5xx, &out.EnforcingConsecutive5xx + *out = new(uint32) + **out = **in + } + if in.MaxEjectionPercent != nil { + in, out := &in.MaxEjectionPercent, &out.MaxEjectionPercent + *out = new(uint32) + **out = **in + } + if in.BaseEjectionTime != nil { + in, out := &in.BaseEjectionTime, &out.BaseEjectionTime + *out = new(v1.Duration) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryPolicyBackOff. -func (in *RetryPolicyBackOff) DeepCopy() *RetryPolicyBackOff { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PassiveHealthCheck. +func (in *PassiveHealthCheck) DeepCopy() *PassiveHealthCheck { if in == nil { return nil } - out := new(RetryPolicyBackOff) + out := new(PassiveHealthCheck) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RingHashConfig) DeepCopyInto(out *RingHashConfig) { +func (in *Peer) DeepCopyInto(out *Peer) { *out = *in + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + *out = new(Secret) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RingHashConfig. -func (in *RingHashConfig) DeepCopy() *RingHashConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Peer. +func (in *Peer) DeepCopy() *Peer { if in == nil { return nil } - out := new(RingHashConfig) + out := new(Peer) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteAuthFilter) DeepCopyInto(out *RouteAuthFilter) { +func (in *PeeringAcceptor) DeepCopyInto(out *PeeringAcceptor) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -2519,18 +900,18 @@ func (in *RouteAuthFilter) DeepCopyInto(out *RouteAuthFilter) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilter. -func (in *RouteAuthFilter) DeepCopy() *RouteAuthFilter { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringAcceptor. +func (in *PeeringAcceptor) DeepCopy() *PeeringAcceptor { if in == nil { return nil } - out := new(RouteAuthFilter) + out := new(PeeringAcceptor) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteAuthFilter) DeepCopyObject() runtime.Object { +func (in *PeeringAcceptor) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -2538,31 +919,31 @@ func (in *RouteAuthFilter) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteAuthFilterList) DeepCopyInto(out *RouteAuthFilterList) { +func (in *PeeringAcceptorList) DeepCopyInto(out *PeeringAcceptorList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]RouteAuthFilter, len(*in)) + *out = make([]PeeringAcceptor, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterList. -func (in *RouteAuthFilterList) DeepCopy() *RouteAuthFilterList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringAcceptorList. +func (in *PeeringAcceptorList) DeepCopy() *PeeringAcceptorList { if in == nil { return nil } - out := new(RouteAuthFilterList) + out := new(PeeringAcceptorList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteAuthFilterList) DeepCopyObject() runtime.Object { +func (in *PeeringAcceptorList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -2570,49 +951,63 @@ func (in *RouteAuthFilterList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteAuthFilterSpec) DeepCopyInto(out *RouteAuthFilterSpec) { +func (in *PeeringAcceptorSpec) DeepCopyInto(out *PeeringAcceptorSpec) { *out = *in - if in.JWT != nil { - in, out := &in.JWT, &out.JWT - *out = new(GatewayJWTRequirement) + if in.Peer != nil { + in, out := &in.Peer, &out.Peer + *out = new(Peer) (*in).DeepCopyInto(*out) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterSpec. -func (in *RouteAuthFilterSpec) DeepCopy() *RouteAuthFilterSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringAcceptorSpec. +func (in *PeeringAcceptorSpec) DeepCopy() *PeeringAcceptorSpec { if in == nil { return nil } - out := new(RouteAuthFilterSpec) + out := new(PeeringAcceptorSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteAuthFilterStatus) DeepCopyInto(out *RouteAuthFilterStatus) { +func (in *PeeringAcceptorStatus) DeepCopyInto(out *PeeringAcceptorStatus) { *out = *in + if in.LatestPeeringVersion != nil { + in, out := &in.LatestPeeringVersion, &out.LatestPeeringVersion + *out = new(uint64) + **out = **in + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(SecretRefStatus) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) + *out = make(Conditions, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.LastSyncedTime != nil { + in, out := &in.LastSyncedTime, &out.LastSyncedTime + *out = (*in).DeepCopy() + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterStatus. -func (in *RouteAuthFilterStatus) DeepCopy() *RouteAuthFilterStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringAcceptorStatus. +func (in *PeeringAcceptorStatus) DeepCopy() *PeeringAcceptorStatus { if in == nil { return nil } - out := new(RouteAuthFilterStatus) + out := new(PeeringAcceptorStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRetryFilter) DeepCopyInto(out *RouteRetryFilter) { +func (in *PeeringDialer) DeepCopyInto(out *PeeringDialer) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -2620,18 +1015,18 @@ func (in *RouteRetryFilter) DeepCopyInto(out *RouteRetryFilter) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilter. -func (in *RouteRetryFilter) DeepCopy() *RouteRetryFilter { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringDialer. +func (in *PeeringDialer) DeepCopy() *PeeringDialer { if in == nil { return nil } - out := new(RouteRetryFilter) + out := new(PeeringDialer) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteRetryFilter) DeepCopyObject() runtime.Object { +func (in *PeeringDialer) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -2639,31 +1034,31 @@ func (in *RouteRetryFilter) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRetryFilterList) DeepCopyInto(out *RouteRetryFilterList) { +func (in *PeeringDialerList) DeepCopyInto(out *PeeringDialerList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]RouteRetryFilter, len(*in)) + *out = make([]PeeringDialer, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilterList. -func (in *RouteRetryFilterList) DeepCopy() *RouteRetryFilterList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringDialerList. +func (in *PeeringDialerList) DeepCopy() *PeeringDialerList { if in == nil { return nil } - out := new(RouteRetryFilterList) + out := new(PeeringDialerList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteRetryFilterList) DeepCopyObject() runtime.Object { +func (in *PeeringDialerList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -2671,118 +1066,78 @@ func (in *RouteRetryFilterList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRetryFilterSpec) DeepCopyInto(out *RouteRetryFilterSpec) { +func (in *PeeringDialerSpec) DeepCopyInto(out *PeeringDialerSpec) { *out = *in - if in.NumRetries != nil { - in, out := &in.NumRetries, &out.NumRetries - *out = new(uint32) - **out = **in - } - if in.RetryOn != nil { - in, out := &in.RetryOn, &out.RetryOn - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.RetryOnStatusCodes != nil { - in, out := &in.RetryOnStatusCodes, &out.RetryOnStatusCodes - *out = make([]uint32, len(*in)) - copy(*out, *in) - } - if in.RetryOnConnectFailure != nil { - in, out := &in.RetryOnConnectFailure, &out.RetryOnConnectFailure - *out = new(bool) - **out = **in + if in.Peer != nil { + in, out := &in.Peer, &out.Peer + *out = new(Peer) + (*in).DeepCopyInto(*out) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilterSpec. -func (in *RouteRetryFilterSpec) DeepCopy() *RouteRetryFilterSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringDialerSpec. +func (in *PeeringDialerSpec) DeepCopy() *PeeringDialerSpec { if in == nil { return nil } - out := new(RouteRetryFilterSpec) + out := new(PeeringDialerSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteTimeoutFilter) DeepCopyInto(out *RouteTimeoutFilter) { +func (in *PeeringDialerStatus) DeepCopyInto(out *PeeringDialerStatus) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilter. -func (in *RouteTimeoutFilter) DeepCopy() *RouteTimeoutFilter { - if in == nil { - return nil + if in.LatestPeeringVersion != nil { + in, out := &in.LatestPeeringVersion, &out.LatestPeeringVersion + *out = new(uint64) + **out = **in } - out := new(RouteTimeoutFilter) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteTimeoutFilter) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(SecretRefStatus) + **out = **in } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteTimeoutFilterList) DeepCopyInto(out *RouteTimeoutFilterList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]RouteTimeoutFilter, len(*in)) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(Conditions, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.LastSyncedTime != nil { + in, out := &in.LastSyncedTime, &out.LastSyncedTime + *out = (*in).DeepCopy() + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilterList. -func (in *RouteTimeoutFilterList) DeepCopy() *RouteTimeoutFilterList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringDialerStatus. +func (in *PeeringDialerStatus) DeepCopy() *PeeringDialerStatus { if in == nil { return nil } - out := new(RouteTimeoutFilterList) + out := new(PeeringDialerStatus) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteTimeoutFilterList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteTimeoutFilterSpec) DeepCopyInto(out *RouteTimeoutFilterSpec) { +func (in *PeeringMeshConfig) DeepCopyInto(out *PeeringMeshConfig) { *out = *in - out.RequestTimeout = in.RequestTimeout - out.IdleTimeout = in.IdleTimeout } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilterSpec. -func (in *RouteTimeoutFilterSpec) DeepCopy() *RouteTimeoutFilterSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringMeshConfig. +func (in *PeeringMeshConfig) DeepCopy() *PeeringMeshConfig { if in == nil { return nil } - out := new(RouteTimeoutFilterSpec) + out := new(PeeringMeshConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SamenessGroup) DeepCopyInto(out *SamenessGroup) { +func (in *ProxyDefaults) DeepCopyInto(out *ProxyDefaults) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -2790,18 +1145,18 @@ func (in *SamenessGroup) DeepCopyInto(out *SamenessGroup) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroup. -func (in *SamenessGroup) DeepCopy() *SamenessGroup { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaults. +func (in *ProxyDefaults) DeepCopy() *ProxyDefaults { if in == nil { return nil } - out := new(SamenessGroup) + out := new(ProxyDefaults) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *SamenessGroup) DeepCopyObject() runtime.Object { +func (in *ProxyDefaults) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -2809,31 +1164,31 @@ func (in *SamenessGroup) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SamenessGroupList) DeepCopyInto(out *SamenessGroupList) { +func (in *ProxyDefaultsList) DeepCopyInto(out *ProxyDefaultsList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]SamenessGroup, len(*in)) + *out = make([]ProxyDefaults, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupList. -func (in *SamenessGroupList) DeepCopy() *SamenessGroupList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsList. +func (in *ProxyDefaultsList) DeepCopy() *ProxyDefaultsList { if in == nil { return nil } - out := new(SamenessGroupList) + out := new(ProxyDefaultsList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *SamenessGroupList) DeepCopyObject() runtime.Object { +func (in *ProxyDefaultsList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -2841,55 +1196,48 @@ func (in *SamenessGroupList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SamenessGroupMember) DeepCopyInto(out *SamenessGroupMember) { +func (in *ProxyDefaultsSpec) DeepCopyInto(out *ProxyDefaultsSpec) { *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupMember. -func (in *SamenessGroupMember) DeepCopy() *SamenessGroupMember { - if in == nil { - return nil + if in.Mode != nil { + in, out := &in.Mode, &out.Mode + *out = new(ProxyMode) + **out = **in } - out := new(SamenessGroupMember) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in SamenessGroupMembers) DeepCopyInto(out *SamenessGroupMembers) { - { - in := &in - *out = make(SamenessGroupMembers, len(*in)) + if in.TransparentProxy != nil { + in, out := &in.TransparentProxy, &out.TransparentProxy + *out = new(TransparentProxy) + **out = **in + } + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = make(json.RawMessage, len(*in)) copy(*out, *in) } + out.MeshGateway = in.MeshGateway + in.Expose.DeepCopyInto(&out.Expose) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupMembers. -func (in SamenessGroupMembers) DeepCopy() SamenessGroupMembers { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsSpec. +func (in *ProxyDefaultsSpec) DeepCopy() *ProxyDefaultsSpec { if in == nil { return nil } - out := new(SamenessGroupMembers) + out := new(ProxyDefaultsSpec) in.DeepCopyInto(out) - return *out + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SamenessGroupSpec) DeepCopyInto(out *SamenessGroupSpec) { +func (in *RingHashConfig) DeepCopyInto(out *RingHashConfig) { *out = *in - if in.Members != nil { - in, out := &in.Members, &out.Members - *out = make([]SamenessGroupMember, len(*in)) - copy(*out, *in) - } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SamenessGroupSpec. -func (in *SamenessGroupSpec) DeepCopy() *SamenessGroupSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RingHashConfig. +func (in *RingHashConfig) DeepCopy() *RingHashConfig { if in == nil { return nil } - out := new(SamenessGroupSpec) + out := new(RingHashConfig) in.DeepCopyInto(out) return out } @@ -3044,18 +1392,6 @@ func (in *ServiceDefaultsSpec) DeepCopyInto(out *ServiceDefaultsSpec) { *out = new(ServiceDefaultsDestination) (*in).DeepCopyInto(*out) } - if in.RateLimits != nil { - in, out := &in.RateLimits, &out.RateLimits - *out = new(RateLimits) - (*in).DeepCopyInto(*out) - } - if in.EnvoyExtensions != nil { - in, out := &in.EnvoyExtensions, &out.EnvoyExtensions - *out = make(EnvoyExtensions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceDefaultsSpec. @@ -3142,11 +1478,6 @@ func (in *ServiceIntentionsSpec) DeepCopyInto(out *ServiceIntentionsSpec) { } } } - if in.JWT != nil { - in, out := &in.JWT, &out.JWT - *out = new(IntentionJWTRequirement) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceIntentionsSpec. @@ -3199,11 +1530,6 @@ func (in *ServiceResolverFailover) DeepCopyInto(out *ServiceResolverFailover) { *out = make([]ServiceResolverFailoverTarget, len(*in)) copy(*out, *in) } - if in.Policy != nil { - in, out := &in.Policy, &out.Policy - *out = new(FailoverPolicy) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceResolverFailover. @@ -3322,17 +1648,11 @@ func (in *ServiceResolverSpec) DeepCopyInto(out *ServiceResolverSpec) { } } out.ConnectTimeout = in.ConnectTimeout - out.RequestTimeout = in.RequestTimeout if in.LoadBalancer != nil { in, out := &in.LoadBalancer, &out.LoadBalancer *out = new(LoadBalancer) (*in).DeepCopyInto(*out) } - if in.PrioritizeByLocality != nil { - in, out := &in.PrioritizeByLocality, &out.PrioritizeByLocality - *out = new(PrioritizeByLocality) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceResolverSpec. @@ -3411,11 +1731,6 @@ func (in *ServiceRouteDestination) DeepCopyInto(out *ServiceRouteDestination) { *out = *in out.IdleTimeout = in.IdleTimeout out.RequestTimeout = in.RequestTimeout - if in.RetryOn != nil { - in, out := &in.RetryOn, &out.RetryOn - *out = make([]string, len(*in)) - copy(*out, *in) - } if in.RetryOnStatusCodes != nil { in, out := &in.RetryOnStatusCodes, &out.RetryOnStatusCodes *out = make([]uint32, len(*in)) diff --git a/control-plane/build-support/controller/README.md b/control-plane/build-support/controller/README.md new file mode 100644 index 0000000000..0d24937531 --- /dev/null +++ b/control-plane/build-support/controller/README.md @@ -0,0 +1,5 @@ +## Overview + +`boilerplate.go.txt` is a file required by `operator-sdk` when it performs code-generation. + +It's contents provide the headers to the generated files but as we do not require headers for the files we generate, it has been left intentionally blank. diff --git a/control-plane/build-support/controller/boilerplate.go.txt b/control-plane/build-support/controller/boilerplate.go.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/control-plane/build-support/functions/00-vars.sh b/control-plane/build-support/functions/00-vars.sh index 484344703c..1f03013c32 100644 --- a/control-plane/build-support/functions/00-vars.sh +++ b/control-plane/build-support/functions/00-vars.sh @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # GPG Key ID to use for publically released builds HASHICORP_GPG_KEY="348FFC4C" diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index dbaeffdb60..2706c69be8 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - function err { if test "${COLORIZE}" -eq 1; then tput bold @@ -95,7 +92,7 @@ function have_gpg_key { function parse_version { # Arguments: - # $1 - Path to the top level Consul K8s source + # $1 - Path to the top level Consul source # $2 - boolean value for whether the release version should be parsed from the source # $3 - boolean whether to use GIT_DESCRIBE and GIT_COMMIT environment variables # $4 - boolean whether to omit the version part of the version string. (optional) @@ -190,7 +187,7 @@ function parse_version { function get_version { # Arguments: - # $1 - Path to the top level Consul K8s source + # $1 - Path to the top level Consul source # $2 - Whether the release version should be parsed from source (optional) # $3 - Whether to use GIT_DESCRIBE and GIT_COMMIT environment variables # @@ -344,7 +341,7 @@ function normalize_git_url { function git_remote_url { # Arguments: - # $1 - Path to the top level Consul K8s source + # $1 - Path to the top level Consul source # $2 - Remote name # # Returns: @@ -380,7 +377,7 @@ function git_remote_url { function find_git_remote { # Arguments: - # $1 - Path to the top level Consul K8s source + # $1 - Path to the top level Consul source # # Returns: # 0 - success @@ -482,7 +479,7 @@ function update_git_env { function git_push_ref { # Arguments: - # $1 - Path to the top level Consul K8s source + # $1 - Path to the top level Consul source # $2 - Git ref (optional) # $3 - remote (optional - if not specified we will try to determine it) # @@ -619,6 +616,7 @@ function update_version_helm { local vfile="$1/values.yaml" local cfile="$1/Chart.yaml" local version="$2" + local consul_version="$5" local prerelease="$3" local full_version="$2" local full_consul_version="$5" @@ -629,8 +627,7 @@ function update_version_helm { full_consul_version="$5-$3" full_consul_dataplane_version="$7-$3" elif test "$3" == "dev"; then - full_version="${2%.*}-$3" - full_version_k8s_for_chart_version="$2-$3" + full_version="$2-$3" # strip off the last minor patch version so that the consul image can be set to something like 1.16-dev. The image # is produced by Consul every night full_consul_version="${5%.*}-$3" @@ -638,7 +635,7 @@ function update_version_helm { fi sed_i ${SED_EXT} -e "s/(imageK8S:.*\/consul-k8s-control-plane:)[^\"]*/imageK8S: $4${full_version}/g" "${vfile}" - sed_i ${SED_EXT} -e "s/(version:[[:space:]]*)[^\"]*/\1${full_version_k8s_for_chart_version}/g" "${cfile}" + sed_i ${SED_EXT} -e "s/(version:[[:space:]]*)[^\"]*/\1${full_version}/g" "${cfile}" sed_i ${SED_EXT} -e "s/(appVersion:[[:space:]]*)[^\"]*/\1${full_consul_version}/g" "${cfile}" sed_i ${SED_EXT} -e "s/(image:.*\/consul-k8s-control-plane:)[^\"]*/image: $4${full_version}/g" "${cfile}" @@ -660,7 +657,7 @@ function update_version_helm { function set_version { # Arguments: - # $1 - Path to top level Consul K8s source + # $1 - Path to top level Consul source # $2 - The version of the release # $3 - The release date # $4 - The pre-release version @@ -710,13 +707,11 @@ function set_version { function set_changelog { # Arguments: - # $1 - Path to top level Consul K8s source + # $1 - Path to top level Consul source # $2 - Version # $3 - Release Date # $4 - The last git release tag # $5 - Pre-release version - # $6 - The version of Consul corresponding to the release (only used for .0) - # $7 - The version of Consul Dataplane corresponding to the release (only used for .0) # # # Returns: @@ -736,13 +731,7 @@ function set_changelog { rel_date="$3" fi local last_release_date_git_tag=$4 - - # Only set prerelease suffix if prerelease version is set - local preReleaseVersion="${5:+-${5}}" - - local version_short="${version%\.*}" - local consul_version_short="${6%\.*}" - local consul_dataplane_version_short="${7%\.*}" + local preReleaseVersion="-$5" if test -z "${version}"; then err "ERROR: Must specify a version to put into the changelog" @@ -754,18 +743,8 @@ function set_changelog { exit 1 fi - if [[ "${version}" =~ \.0$ ]]; then - if [ -z "$version_short" ] || [ -z "$consul_version_short" ] || [ -z "$consul_dataplane_version_short" ]; then - echo "Error: Consul K8s, Consul or Consul Dataplane short version could not be detected." - exit 1 - fi - compatibility_note=" - -_Note: Consul K8s ${version_short} is compatible with Consul ${consul_version_short} and Consul Dataplane ${consul_dataplane_version_short}. Refer to our [compatibility matrix](https://developer.hashicorp.com/consul/docs/k8s/compatibility) for more info._" - fi - cat <tmp && mv tmp "${curdir}"/CHANGELOG.MD -## ${version}${preReleaseVersion} (${rel_date})${compatibility_note} +## ${version}${preReleaseVersion} (${rel_date}) $(changelog-build -last-release ${CONSUL_K8S_LAST_RELEASE_GIT_TAG} \ -entries-dir .changelog/ \ -changelog-template .changelog/changelog.tmpl \ @@ -777,13 +756,13 @@ EOT function prepare_release { # Arguments: - # $1 - Path to top level Consul K8s source + # $1 - Path to top level Consul source # $2 - The version of the release # $3 - The release date # $4 - The last release git tag for this branch (eg. v1.1.0) - # $5 - The consul version + # $5 - The consul version # $6 - The consul-dataplane version - # $7 - The pre-release version + # $7 - The pre-release version # # # Returns: @@ -800,39 +779,12 @@ function prepare_release { echo "prepare_release: dir:${curDir} consul-k8s:${version} consul:${consulVersion} consul-dataplane:${consulDataplaneVersion} date:"${releaseDate}" git tag:${lastGitTag}" set_version "${curDir}" "${version}" "${releaseDate}" "${prereleaseVersion}" "hashicorp\/consul-k8s-control-plane:" "${consulVersion}" "hashicorp\/consul" "${consulDataplaneVersion}" "hashicorp\/consul-dataplane" - set_changelog "${curDir}" "${version}" "${releaseDate}" "${lastGitTag}" "${prereleaseVersion}" "${consulVersion}" "${consulDataplaneVersion}" -} - -function prepare_rc_branch { - # Arguments: - # $1 - Path to top level Consul K8s source - # $2 - The version of the release - # $3 - The release date - # $4 - The last release git tag for this branch (eg. v1.1.0) - # $5 - The consul version - # $6 - The consul-dataplane version - # $7 - The pre-release version - # - # - # Returns: - # 0 - success - # * - error - - local curDir=$1 - local version=$2 - local releaseDate=$3 - local lastGitTag=$4 - local consulVersion=$5 - local consulDataplaneVersion=$6 - local prereleaseVersion=$7 - - echo "prepare_rc: dir:${curDir} consul-k8s:${version} consul:${consulVersion} consul-dataplane:${consulDataplaneVersion} date:"${releaseDate}" git tag:${lastGitTag}" - set_version "${curDir}" "${version}" "${releaseDate}" "${prereleaseVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" "${consulVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul" "${consulDataplaneVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-dataplane" + set_changelog "${curDir}" "${version}" "${releaseDate}" "${lastGitTag}" "${prereleaseVersion}" } function prepare_dev { # Arguments: - # $1 - Path to top level Consul K8s source + # $1 - Path to top level Consul source # $2 - The version of the release # $3 - The release date # $4 - The last release git tag for this branch (eg. v1.1.0) (Unused) @@ -887,7 +839,7 @@ function git_staging_empty { function commit_dev_mode { # Arguments: - # $1 - Path to top level Consul K8s source + # $1 - Path to top level Consul source # # Returns: # 0 - success diff --git a/control-plane/build-support/functions/20-build.sh b/control-plane/build-support/functions/20-build.sh index dac626b88f..ddde7b6acf 100644 --- a/control-plane/build-support/functions/20-build.sh +++ b/control-plane/build-support/functions/20-build.sh @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - function refresh_docker_images { # Arguments: # $1 - Path to top level Consul source @@ -180,7 +177,7 @@ function build_consul_local { # * - error # # Note: - # The GOLDFLAGS, GOEXPERIMENT, and GOTAGS environment variables will be used if set + # The GOLDFLAGS and GOTAGS environment variables will be used if set # If the CONSUL_DEV environment var is truthy only the local platform/architecture is built. # If the XC_OS or the XC_ARCH environment vars are present then only those platforms/architectures # will be built. Otherwise all supported platform/architectures are built @@ -188,14 +185,6 @@ function build_consul_local { # build with go install. # The GOXPARALLEL environment variable is used if set - if [ "${GOTAGS:-}" == "fips" ]; then - CGO_ENABLED=1 - else - CGO_ENABLED=0 - fi - - echo "GOEXPERIMENT: $GOEXPERIMENT, GOTAGS: $GOTAGS CGO_ENABLED: $CGO_ENABLED" >> ~/debug.txt - if ! test -d "$1" then err "ERROR: '$1' is not a directory. build_consul must be called with the path to the top level source as the first argument'" @@ -250,7 +239,7 @@ function build_consul_local { then status "Using gox for concurrent compilation" - CGO_ENABLED=${CGO_ENABLED} GOEXPERIMENT=${GOEXPERIMENT} gox \ + CGO_ENABLED=0 gox \ -os="${build_os}" \ -arch="${build_arch}" \ -ldflags="${GOLDFLAGS}" \ @@ -298,7 +287,7 @@ function build_consul_local { else OS_BIN_EXTENSION="" fi - CGO_ENABLED=${CGO_ENABLED} GOEXPERIMENT=${GOEXPERIMENT} GOOS=${os} GOARCH=${arch} go build -ldflags "${GOLDFLAGS}" -tags "${GOTAGS}" -o "${outdir}/${bin_name}" + CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go build -ldflags "${GOLDFLAGS}" -tags "${GOTAGS}" -o "${outdir}/${bin_name}" if test $? -ne 0 then err "ERROR: Failed to build Consul for ${osarch}" diff --git a/control-plane/build-support/functions/40-publish.sh b/control-plane/build-support/functions/40-publish.sh index aae9a5f719..975c835bc0 100644 --- a/control-plane/build-support/functions/40-publish.sh +++ b/control-plane/build-support/functions/40-publish.sh @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - function hashicorp_release { # Arguments: # $1 - Path to directory containing all of the release artifacts diff --git a/control-plane/build-support/scripts/build-local.sh b/control-plane/build-support/scripts/build-local.sh index 7325e025b7..95d18e0ba6 100755 --- a/control-plane/build-support/scripts/build-local.sh +++ b/control-plane/build-support/scripts/build-local.sh @@ -1,7 +1,4 @@ #!/bin/bash -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - SCRIPT_NAME="$(basename ${BASH_SOURCE[0]})" pushd $(dirname ${BASH_SOURCE[0]}) > /dev/null SCRIPT_DIR=$(pwd) @@ -35,8 +32,6 @@ Options: -a | --arch ARCH Space separated string of architectures to build. - --fips FIPS Whether to use FIPS cryptography. - -h | --help Print this help text. EOF } @@ -96,11 +91,6 @@ function main { build_arch="$2" shift 2 ;; - --fips ) - GOTAGS="fips" - GOEXPERIMENT="boringcrypto" - shift 1 - ;; * ) err_usage "ERROR: Unknown argument: '$1'" return 1 diff --git a/control-plane/build-support/scripts/consul-enterprise-version.sh b/control-plane/build-support/scripts/consul-enterprise-version.sh index d910f428ab..24adb6a793 100755 --- a/control-plane/build-support/scripts/consul-enterprise-version.sh +++ b/control-plane/build-support/scripts/consul-enterprise-version.sh @@ -4,12 +4,11 @@ FILE=$1 VERSION=$(yq .global.image $FILE) -if [[ "${VERSION}" == *"hashicorp/consul:"* ]]; then - # for matching release image repos with a -ent label +if [[ !"${VERSION}" == *"hashicorppreview/consul:"* ]]; then + VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") +elif [[ !"${VERSION}" == *"hashicorp/consul:"* ]]; then VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") -else - # for matching preview image repos - VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") fi -echo "${VERSION}" \ No newline at end of file + +echo "${VERSION}" diff --git a/control-plane/build-support/scripts/functions.sh b/control-plane/build-support/scripts/functions.sh index 590666eb7d..0301c0d3a1 100644 --- a/control-plane/build-support/scripts/functions.sh +++ b/control-plane/build-support/scripts/functions.sh @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # # NOTE: This file is meant to be sourced from other bash scripts/shells # diff --git a/control-plane/build-support/scripts/terraformfmtcheck.sh b/control-plane/build-support/scripts/terraformfmtcheck.sh index 8608a88e30..0f962a1c1b 100755 --- a/control-plane/build-support/scripts/terraformfmtcheck.sh +++ b/control-plane/build-support/scripts/terraformfmtcheck.sh @@ -1,7 +1,4 @@ #!/usr/bin/env bash -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # Check terraform fmt echo "==> Checking that code complies with terraform fmt requirements..." diff --git a/control-plane/build-support/scripts/version.sh b/control-plane/build-support/scripts/version.sh index fce325e03c..f91b0c3917 100755 --- a/control-plane/build-support/scripts/version.sh +++ b/control-plane/build-support/scripts/version.sh @@ -1,7 +1,4 @@ #!/usr/bin/env bash -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - version_file=$1 version=$(awk '$1 == "Version" && $2 == "=" { gsub(/"/, "", $3); print $3 }' < "${version_file}") diff --git a/control-plane/catalog/to-consul/annotation.go b/control-plane/catalog/to-consul/annotation.go index edca70b60c..5df5ab71f4 100644 --- a/control-plane/catalog/to-consul/annotation.go +++ b/control-plane/catalog/to-consul/annotation.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog const ( diff --git a/control-plane/catalog/to-consul/resource.go b/control-plane/catalog/to-consul/resource.go index 2d29d6c15a..8d6fa9b82b 100644 --- a/control-plane/catalog/to-consul/resource.go +++ b/control-plane/catalog/to-consul/resource.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( @@ -512,7 +509,7 @@ func (t *ServiceResource) generateRegistrations(key string) { r.Service.ID = serviceID(r.Service.Service, ip) r.Service.Address = ip // Adding information about service weight. - // Overrides the existing weight if present. + // Overrides the existing weight if present if weight, ok := svc.Annotations[annotationServiceWeight]; ok && weight != "" { weightI, err := getServiceWeight(weight) if err == nil { @@ -561,7 +558,7 @@ func (t *ServiceResource) generateRegistrations(key string) { r.Service.Address = addr // Adding information about service weight. - // Overrides the existing weight if present. + // Overrides the existing weight if present if weight, ok := svc.Annotations[annotationServiceWeight]; ok && weight != "" { weightI, err := getServiceWeight(weight) if err == nil { @@ -1028,7 +1025,7 @@ func consulHealthCheckID(k8sNS string, serviceID string) string { // Calculates the passing service weight. func getServiceWeight(weight string) (int, error) { - // error validation if the input param is a number. + // error validation if the input param is a number weightI, err := strconv.Atoi(weight) if err != nil { return -1, err diff --git a/control-plane/catalog/to-consul/resource_test.go b/control-plane/catalog/to-consul/resource_test.go index 3b8fb78497..680c052823 100644 --- a/control-plane/catalog/to-consul/resource_test.go +++ b/control-plane/catalog/to-consul/resource_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/catalog/to-consul/service_id.go b/control-plane/catalog/to-consul/service_id.go index 8300871b73..1aa3071497 100644 --- a/control-plane/catalog/to-consul/service_id.go +++ b/control-plane/catalog/to-consul/service_id.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/catalog/to-consul/syncer.go b/control-plane/catalog/to-consul/syncer.go index 9f1df18ba6..19e0aaca6f 100644 --- a/control-plane/catalog/to-consul/syncer.go +++ b/control-plane/catalog/to-consul/syncer.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/catalog/to-consul/syncer_ent_test.go b/control-plane/catalog/to-consul/syncer_ent_test.go index 5dfb158d23..fbe2cbd494 100644 --- a/control-plane/catalog/to-consul/syncer_ent_test.go +++ b/control-plane/catalog/to-consul/syncer_ent_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise package catalog diff --git a/control-plane/catalog/to-consul/syncer_test.go b/control-plane/catalog/to-consul/syncer_test.go index ab2cfee0a2..d8d9b0f402 100644 --- a/control-plane/catalog/to-consul/syncer_test.go +++ b/control-plane/catalog/to-consul/syncer_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( @@ -13,13 +10,12 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const ( @@ -234,7 +230,7 @@ func TestConsulSyncer_stopsGracefully(t *testing.T) { testClient := &test.TestServerClient{ Cfg: &consul.Config{APIClientConfig: &api.Config{}, HTTPPort: port}, - Watcher: test.MockConnMgrForIPAndPort(t, parsedURL.Host, port, false), + Watcher: test.MockConnMgrForIPAndPort(parsedURL.Host, port), } // Start the syncer. diff --git a/control-plane/catalog/to-consul/testing.go b/control-plane/catalog/to-consul/testing.go index 5f19017cbe..e6541c6ba1 100644 --- a/control-plane/catalog/to-consul/testing.go +++ b/control-plane/catalog/to-consul/testing.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/catalog/to-k8s/sink.go b/control-plane/catalog/to-k8s/sink.go index 6e201253df..fa8821989e 100644 --- a/control-plane/catalog/to-k8s/sink.go +++ b/control-plane/catalog/to-k8s/sink.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/catalog/to-k8s/sink_test.go b/control-plane/catalog/to-k8s/sink_test.go index cfba502268..fbce7bbaaf 100644 --- a/control-plane/catalog/to-k8s/sink_test.go +++ b/control-plane/catalog/to-k8s/sink_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/catalog/to-k8s/source.go b/control-plane/catalog/to-k8s/source.go index ab34089d8e..5a384e760a 100644 --- a/control-plane/catalog/to-k8s/source.go +++ b/control-plane/catalog/to-k8s/source.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/catalog/to-k8s/source_test.go b/control-plane/catalog/to-k8s/source_test.go index 66afb4b608..ca00a1e954 100644 --- a/control-plane/catalog/to-k8s/source_test.go +++ b/control-plane/catalog/to-k8s/source_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/catalog/to-k8s/testing.go b/control-plane/catalog/to-k8s/testing.go index 1eb731a17f..d7181bdbea 100644 --- a/control-plane/catalog/to-k8s/testing.go +++ b/control-plane/catalog/to-k8s/testing.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package catalog import ( diff --git a/control-plane/cni/config/config.go b/control-plane/cni/config/config.go index a9dafd0ab7..f22d3ff79b 100644 --- a/control-plane/cni/config/config.go +++ b/control-plane/cni/config/config.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package config const ( diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index 381b6fe171..2553993300 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -3,59 +3,51 @@ module github.com/hashicorp/consul-k8s/control-plane/cni require ( github.com/containernetworking/cni v1.1.1 github.com/containernetworking/plugins v1.1.1 - github.com/hashicorp/consul/sdk v0.15.0 - github.com/hashicorp/go-hclog v1.5.0 - github.com/stretchr/testify v1.8.3 - k8s.io/api v0.26.12 - k8s.io/apimachinery v0.26.12 - k8s.io/client-go v0.26.12 + github.com/hashicorp/consul/sdk v0.13.0 + github.com/hashicorp/go-hclog v1.2.2 + github.com/stretchr/testify v1.7.2 + k8s.io/api v0.22.2 + k8s.io/apimachinery v0.22.2 + k8s.io/client-go v0.22.2 ) require ( - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/fatih/color v1.14.1 // indirect - github.com/go-logr/logr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/go-logr/logr v0.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.6 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.30.0 // 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/klog/v2 v2.80.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/klog/v2 v2.9.0 // indirect + k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect + k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect ) -//replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 +replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 -go 1.20 +go 1.19 diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index 9c8e9f9e46..c08ccadbde 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -1,5 +1,39 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -11,40 +45,52 @@ github.com/containernetworking/plugins v1.1.1 h1:+AGfFigZ5TiQH00vhR8qPeSatj53eNG github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -56,8 +102,9 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -68,149 +115,281 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= -github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= +github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= +github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-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.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 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= @@ -218,17 +397,50 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 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= @@ -240,17 +452,20 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -259,25 +474,34 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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.26.12 h1:jJm3s5ot05SUN3tPGg3b+XWuBE7rO/X0+dnVMhxyd5o= -k8s.io/api v0.26.12/go.mod h1:N+HUXukmtXNOKDngxXrEPbZWggWx01tH/N0nG4nV0oo= -k8s.io/apimachinery v0.26.12 h1:y+OgufxqLIZtyXIydRhjLBGzrYLF+qwiDdCFXYOjeN4= -k8s.io/apimachinery v0.26.12/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= -k8s.io/client-go v0.26.12 h1:kPpTpIeFNqwo4UyvoqzNp3DNK2mbGcdGv23eS1U8VMo= -k8s.io/client-go v0.26.12/go.mod h1:V7thEnIFroyNZOU30dKLiiVeqQmJz45shJG1mu7nONQ= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= +k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= +k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= +k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= +k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/control-plane/cni/main.go b/control-plane/cni/main.go index d642af45a8..7b05b5c6cd 100644 --- a/control-plane/cni/main.go +++ b/control-plane/cni/main.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( @@ -33,10 +30,6 @@ const ( // a pod after an injection is done. keyInjectStatus = "consul.hashicorp.com/connect-inject-status" - // keyMeshInjectStatus is the mesh v2 key of the annotation that is added to - // a pod after an injection is done. - keyMeshInjectStatus = "consul.hashicorp.com/mesh-inject-status" - // keyTransparentProxyStatus is the key of the annotation that is added to // a pod when transparent proxy is done. keyTransparentProxyStatus = "consul.hashicorp.com/transparent-proxy-status" @@ -250,15 +243,11 @@ func main() { } // skipTrafficRedirection looks for annotations on the pod and determines if it should skip traffic redirection. -// The absence of the annotations is the equivalent of "disabled" because it means that the connect-inject mutating +// The absence of the annotations is the equivalent of "disabled" because it means that the connect inject mutating // webhook did not run against the pod. func skipTrafficRedirection(pod corev1.Pod) bool { - // If keyInjectStatus exists, then we are dealing with a mesh v1 pod - // else we have a mesh v2 pod. We need to check for both before we can skip. if anno, ok := pod.Annotations[keyInjectStatus]; !ok || anno == "" { - if anno, ok := pod.Annotations[keyMeshInjectStatus]; !ok || anno == "" { - return true - } + return true } if anno, ok := pod.Annotations[keyTransparentProxyStatus]; !ok || anno == "" { diff --git a/control-plane/cni/main_test.go b/control-plane/cni/main_test.go index dc929f1408..740e15c646 100644 --- a/control-plane/cni/main_test.go +++ b/control-plane/cni/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( @@ -174,15 +171,6 @@ func TestSkipTrafficRedirection(t *testing.T) { }, expectedSkip: false, }, - { - name: "Pod with v2 annotations correctly set", - annotatedPod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[keyMeshInjectStatus] = "foo" - pod.Annotations[keyTransparentProxyStatus] = "bar" - return pod - }, - expectedSkip: false, - }, { name: "Pod without annotations, will timeout waiting", annotatedPod: func(pod *corev1.Pod) *corev1.Pod { diff --git a/control-plane/commands.go b/control-plane/commands.go index 01f5163bc3..ec3b7ca612 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -1,26 +1,17 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( "os" - "github.com/mitchellh/cli" - cmdACLInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/acl-init" cmdConnectInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/connect-init" cmdConsulLogout "github.com/hashicorp/consul-k8s/control-plane/subcommand/consul-logout" cmdCreateFederationSecret "github.com/hashicorp/consul-k8s/control-plane/subcommand/create-federation-secret" cmdDeleteCompletedJob "github.com/hashicorp/consul-k8s/control-plane/subcommand/delete-completed-job" - cmdFetchServerRegion "github.com/hashicorp/consul-k8s/control-plane/subcommand/fetch-server-region" - cmdGatewayCleanup "github.com/hashicorp/consul-k8s/control-plane/subcommand/gateway-cleanup" - cmdGatewayResources "github.com/hashicorp/consul-k8s/control-plane/subcommand/gateway-resources" cmdGetConsulClientCA "github.com/hashicorp/consul-k8s/control-plane/subcommand/get-consul-client-ca" cmdGossipEncryptionAutogenerate "github.com/hashicorp/consul-k8s/control-plane/subcommand/gossip-encryption-autogenerate" cmdInjectConnect "github.com/hashicorp/consul-k8s/control-plane/subcommand/inject-connect" cmdInstallCNI "github.com/hashicorp/consul-k8s/control-plane/subcommand/install-cni" - cmdMeshInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/mesh-init" cmdPartitionInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/partition-init" cmdServerACLInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/server-acl-init" cmdSyncCatalog "github.com/hashicorp/consul-k8s/control-plane/subcommand/sync-catalog" @@ -28,6 +19,7 @@ import ( cmdVersion "github.com/hashicorp/consul-k8s/control-plane/subcommand/version" webhookCertManager "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager" "github.com/hashicorp/consul-k8s/control-plane/version" + "github.com/mitchellh/cli" ) // Commands is the mapping of all available consul-k8s commands. @@ -45,10 +37,6 @@ func init() { return &cmdConnectInit.Command{UI: ui}, nil }, - "mesh-init": func() (cli.Command, error) { - return &cmdMeshInit.Command{UI: ui}, nil - }, - "inject-connect": func() (cli.Command, error) { return &cmdInjectConnect.Command{UI: ui}, nil }, @@ -57,14 +45,6 @@ func init() { return &cmdConsulLogout.Command{UI: ui}, nil }, - "gateway-cleanup": func() (cli.Command, error) { - return &cmdGatewayCleanup.Command{UI: ui}, nil - }, - - "gateway-resources": func() (cli.Command, error) { - return &cmdGatewayResources.Command{UI: ui}, nil - }, - "server-acl-init": func() (cli.Command, error) { return &cmdServerACLInit.Command{UI: ui}, nil }, @@ -107,9 +87,6 @@ func init() { "install-cni": func() (cli.Command, error) { return &cmdInstallCNI.Command{UI: ui}, nil }, - "fetch-server-region": func() (cli.Command, error) { - return &cmdFetchServerRegion.Command{UI: ui}, nil - }, } } diff --git a/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml b/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml deleted file mode 100644 index 3a7699dce4..0000000000 --- a/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml +++ /dev/null @@ -1,256 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: trafficpermissions.auth.consul.hashicorp.com -spec: - group: auth.consul.hashicorp.com - names: - kind: TrafficPermissions - listKind: TrafficPermissionsList - plural: trafficpermissions - shortNames: - - traffic-permissions - singular: trafficpermissions - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: TrafficPermissions is the Schema for the traffic-permissions - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - action: - description: "Action can be either allow or deny for the entire object. - It will default to allow. \n If action is allow, we will allow the - connection if one of the rules in Rules matches, in other words, - we will deny all requests except for the ones that match Rules. - If Consul is in default allow mode, then allow actions have no effect - without a deny permission as everything is allowed by default. \n - If action is deny, we will deny the connection if one of the rules - in Rules match, in other words, we will allow all requests except - for the ones that match Rules. If Consul is default deny mode, then - deny permissions have no effect without an allow permission as everything - is denied by default. \n Action unspecified is reserved for compatibility - with the addition of future actions." - enum: - - ACTION_ALLOW - - ACTION_DENY - - ACTION_UNKNOWN - format: int32 - type: string - destination: - description: Destination is a configuration of the destination proxies - where these traffic permissions should apply. - properties: - identityName: - type: string - type: object - permissions: - description: Permissions is a list of permissions to match on. They - are applied using OR semantics. - items: - description: Permissions is a list of permissions to match on. - properties: - destinationRules: - description: DestinationRules is a list of rules to apply for - matching sources in this Permission. These rules are specific - to the request or connection that is going to the destination(s) - selected by the TrafficPermissions resource. - items: - description: DestinationRule contains rules rules to apply - to the incoming connection. - properties: - exclude: - description: Exclude contains a list of rules to exclude - when evaluating rules for the incoming connection. - items: - properties: - header: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - methods: - description: Methods is the list of HTTP methods. - items: - type: string - type: array - pathExact: - type: string - pathPrefix: - type: string - pathRegex: - type: string - portNames: - description: PortNames is a list of workload ports - to apply this rule to. The ports specified here - must be the ports used in the connection. - items: - type: string - type: array - type: object - type: array - header: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - methods: - description: Methods is the list of HTTP methods. If no - methods are specified, this rule will apply to all methods. - items: - type: string - type: array - pathExact: - type: string - pathPrefix: - type: string - pathRegex: - type: string - portNames: - items: - type: string - type: array - type: object - type: array - sources: - description: Sources is a list of sources in this traffic permission. - items: - description: Source represents the source identity. To specify - any of the wildcard sources, the specific fields need to - be omitted. For example, for a wildcard namespace, identity_name - should be omitted. - properties: - exclude: - description: Exclude is a list of sources to exclude from - this source. - items: - description: ExcludeSource is almost the same as source - but it prevents the addition of matching sources. - properties: - identityName: - type: string - namespace: - type: string - partition: - type: string - peer: - type: string - samenessGroup: - type: string - type: object - type: array - identityName: - type: string - namespace: - type: string - partition: - type: string - peer: - type: string - samenessGroup: - type: string - type: object - type: array - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml deleted file mode 100644 index 49fc1ae135..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: controlplanerequestlimits.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: ControlPlaneRequestLimit - listKind: ControlPlaneRequestLimitList - plural: controlplanerequestlimits - singular: controlplanerequestlimit - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: ControlPlaneRequestLimit is the Schema for the controlplanerequestlimits - API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ControlPlaneRequestLimitSpec defines the desired state of - ControlPlaneRequestLimit. - properties: - acl: - properties: - readRate: - type: number - writeRate: - type: number - type: object - catalog: - properties: - readRate: - type: number - writeRate: - type: number - type: object - configEntry: - properties: - readRate: - type: number - writeRate: - type: number - type: object - connectCA: - properties: - readRate: - type: number - writeRate: - type: number - type: object - coordinate: - properties: - readRate: - type: number - writeRate: - type: number - type: object - discoveryChain: - properties: - readRate: - type: number - writeRate: - type: number - type: object - health: - properties: - readRate: - type: number - writeRate: - type: number - type: object - intention: - properties: - readRate: - type: number - writeRate: - type: number - type: object - kv: - properties: - readRate: - type: number - writeRate: - type: number - type: object - mode: - type: string - preparedQuery: - properties: - readRate: - type: number - writeRate: - type: number - type: object - readRate: - type: number - session: - properties: - readRate: - type: number - writeRate: - type: number - type: object - tenancy: - properties: - readRate: - type: number - writeRate: - type: number - type: object - txn: - properties: - readRate: - type: number - writeRate: - type: number - type: object - writeRate: - type: number - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index 22f816cb18..da1a66fd74 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -70,12 +69,8 @@ spec: the service to. type: string peer: - description: Peer is the name of the peer to export the - service to. - type: string - samenessGroup: - description: SamenessGroup is the name of the sameness - group to export the service to. + description: '[Experimental] Peer is the name of the peer + to export the service to.' type: string type: object type: array @@ -132,3 +127,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml deleted file mode 100644 index ff3158f2a7..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ /dev/null @@ -1,196 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: gatewayclassconfigs.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: GatewayClassConfig - listKind: GatewayClassConfigList - plural: gatewayclassconfigs - singular: gatewayclassconfig - scope: Cluster - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: GatewayClassConfig defines the values that may be set on a GatewayClass - for Consul API Gateway. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClassConfig. - properties: - copyAnnotations: - description: Annotation Information to copy to services or deployments - properties: - service: - description: List of annotations to copy to the gateway service. - items: - type: string - type: array - type: object - deployment: - description: Deployment defines the deployment configuration for the - gateway. - properties: - defaultInstances: - default: 1 - description: Number of gateway instances that should be deployed - by default - format: int32 - maximum: 8 - minimum: 1 - type: integer - maxInstances: - default: 8 - description: Max allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - minInstances: - default: 1 - description: Minimum allowed number of gateway instances - format: int32 - maximum: 8 - minimum: 1 - type: integer - resources: - description: Resources defines the resource requirements for the - gateway. - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only be - set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry in - pod.spec.resourceClaims of the Pod where this field - is used. It makes that resource available inside a - container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - type: object - mapPrivilegedContainerPorts: - description: The value to add to privileged ports ( ports < 1024) - for gateway containers - format: int32 - type: integer - nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a selector which must be true for the - pod to fit on a node. Selector which must match a node''s labels - for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - openshiftSCCName: - description: The name of the OpenShift SecurityContextConstraints - resource for this gateway class to use. - type: string - podSecurityPolicy: - description: The name of an existing Kubernetes PodSecurityPolicy - to bind to the managed ServiceAccount if ACLs are managed. - type: string - serviceType: - description: Service Type string describes ingress methods for a service - enum: - - ClusterIP - - NodePort - - LoadBalancer - type: string - tolerations: - description: 'Tolerations allow the scheduler to schedule nodes with - matching taints. More Info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array - type: object - type: object - served: true - storage: true diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml deleted file mode 100644 index e12db4cf20..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: gatewaypolicies.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: GatewayPolicy - listKind: GatewayPolicyList - plural: gatewaypolicies - singular: gatewaypolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: GatewayPolicy is the Schema for the gatewaypolicies API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GatewayPolicySpec defines the desired state of GatewayPolicy. - properties: - default: - properties: - jwt: - description: GatewayJWTRequirement holds the list of JWT providers - to be verified against. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry - with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the - actual claim information to be verified. - properties: - path: - description: Path is the path to the claim in - the token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the - given path: - If the type at the path is a list - then we verify that this value is contained - in the list. \n - If the type at the path is - a string then we verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - override: - properties: - jwt: - description: GatewayJWTRequirement holds the list of JWT providers - to be verified against. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry - with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the - actual claim information to be verified. - properties: - path: - description: Path is the path to the claim in - the token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the - given path: - If the type at the path is a list - then we verify that this value is contained - in the list. \n - If the type at the path is - a string then we verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - targetRef: - description: TargetRef identifies an API object to apply policy to. - properties: - group: - description: Group is the group of the target resource. - maxLength: 253 - minLength: 1 - type: string - kind: - description: Kind is kind of the target resource. - maxLength: 253 - minLength: 1 - type: string - name: - description: Name is the name of the target resource. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. When - unspecified, the local namespace is inferred. Even when policy - targets a resource in a different namespace, it may only apply - to traffic originating from the same namespace as the policy. - maxLength: 253 - minLength: 1 - type: string - sectionName: - description: SectionName refers to the listener targeted by this - policy. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - group - - kind - - name - type: object - required: - - targetRef - type: object - status: - description: GatewayPolicyStatus defines the observed state of the gateway. - properties: - conditions: - description: "Conditions describe the current conditions of the Policy. - \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index 79450327cb..16ac322090 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -72,43 +71,6 @@ spec: while waiting for a connection to be established. format: int32 type: integer - passiveHealthCheck: - description: PassiveHealthCheck configuration determines how upstream - proxy instances will be monitored for removal from the load - balancing pool. - properties: - baseEjectionTime: - description: The base time that a host is ejected for. The - real time is equal to the base time multiplied by the number - of times the host has been ejected and is capped by max_ejection_time - (Default 300s). Defaults to 30s. - type: string - enforcingConsecutive5xx: - description: EnforcingConsecutive5xx is the % chance that - a host will be actually ejected when an outlier status is - detected through consecutive 5xx. This setting can be used - to disable ejection or to ramp it up slowly. Ex. Setting - this to 10 will make it a 10% chance that the host will - be ejected. - format: int32 - type: integer - interval: - description: Interval between health check analysis sweeps. - Each sweep may remove hosts or return hosts to the pool. - Ex. setting this to "10s" will set the interval to 10 seconds. - type: string - maxEjectionPercent: - description: The maximum % of an upstream cluster that can - be ejected due to outlier detection. Defaults to 10% but - will eject at least one host regardless of the value. - format: int32 - type: integer - maxFailures: - description: MaxFailures is the count of consecutive failures - that results in a host being removed from the pool. - format: int32 - type: integer - type: object type: object listeners: description: Listeners declares what ports the ingress gateway should @@ -188,47 +150,6 @@ spec: service is located. Partitioning is a Consul Enterprise feature. type: string - passiveHealthCheck: - description: PassiveHealthCheck configuration determines - how upstream proxy instances will be monitored for removal - from the load balancing pool. - properties: - baseEjectionTime: - description: The base time that a host is ejected - for. The real time is equal to the base time multiplied - by the number of times the host has been ejected - and is capped by max_ejection_time (Default 300s). - Defaults to 30s. - type: string - enforcingConsecutive5xx: - description: EnforcingConsecutive5xx is the % chance - that a host will be actually ejected when an outlier - status is detected through consecutive 5xx. This - setting can be used to disable ejection or to ramp - it up slowly. Ex. Setting this to 10 will make it - a 10% chance that the host will be ejected. - format: int32 - type: integer - interval: - description: Interval between health check analysis - sweeps. Each sweep may remove hosts or return hosts - to the pool. Ex. setting this to "10s" will set - the interval to 10 seconds. - type: string - maxEjectionPercent: - description: The maximum % of an upstream cluster - that can be ejected due to outlier detection. Defaults - to 10% but will eject at least one host regardless - of the value. - format: int32 - type: integer - maxFailures: - description: MaxFailures is the count of consecutive - failures that results in a host being removed from - the pool. - format: int32 - type: integer - type: object requestHeaders: description: Allow HTTP header manipulation to be configured. properties: @@ -440,3 +361,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml deleted file mode 100644 index df234ae1eb..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ /dev/null @@ -1,308 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: jwtproviders.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: JWTProvider - listKind: JWTProviderList - plural: jwtproviders - singular: jwtprovider - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: JWTProvider is the Schema for the jwtproviders API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: JWTProviderSpec defines the desired state of JWTProvider - properties: - audiences: - description: Audiences is the set of audiences the JWT is allowed - to access. If specified, all JWTs verified with this provider must - address at least one of these to be considered valid. - items: - type: string - type: array - cacheConfig: - description: CacheConfig defines configuration for caching the validation - result for previously seen JWTs. Caching results can speed up verification - when individual tokens are expected to be handled multiple times. - properties: - size: - description: "Size specifies the maximum number of JWT verification - results to cache. \n Defaults to 0, meaning that JWT caching - is disabled." - type: integer - type: object - clockSkewSeconds: - description: "ClockSkewSeconds specifies the maximum allowable time - difference from clock skew when validating the \"exp\" (Expiration) - and \"nbf\" (Not Before) claims. \n Default value is 30 seconds." - type: integer - forwarding: - description: Forwarding defines rules for forwarding verified JWTs - to the backend. - properties: - headerName: - description: "HeaderName is a header name to use when forwarding - a verified JWT to the backend. The verified JWT could have been - extracted from any location (query param, header, or cookie). - \n The header value will be base64-URL-encoded, and will not - be padded unless PadForwardPayloadHeader is true." - type: string - padForwardPayloadHeader: - description: "PadForwardPayloadHeader determines whether padding - should be added to the base64 encoded token forwarded with ForwardPayloadHeader. - \n Default value is false." - type: boolean - type: object - issuer: - description: Issuer is the entity that must have issued the JWT. This - value must match the "iss" claim of the token. - type: string - jsonWebKeySet: - description: JSONWebKeySet defines a JSON Web Key Set, its location - on disk, or the means with which to fetch a key set from a remote - server. - properties: - local: - description: Local specifies a local source for the key set. - properties: - filename: - description: Filename configures a location on disk where - the JWKS can be found. If specified, the file must be present - on the disk of ALL proxies with intentions referencing this - provider. - type: string - jwks: - description: JWKS contains a base64 encoded JWKS. - type: string - type: object - remote: - description: Remote specifies how to fetch a key set from a remote - server. - properties: - cacheDuration: - description: "CacheDuration is the duration after which cached - keys should be expired. \n Default value is 5 minutes." - type: string - fetchAsynchronously: - description: "FetchAsynchronously indicates that the JWKS - should be fetched when a client request arrives. Client - requests will be paused until the JWKS is fetched. If false, - the proxy listener will wait for the JWKS to be fetched - before being activated. \n Default value is false." - type: boolean - jwksCluster: - description: JWKSCluster defines how the specified Remote - JWKS URI is to be fetched. - properties: - connectTimeout: - description: The timeout for new network connections to - hosts in the cluster. If not set, a default value of - 5s will be used. - type: string - discoveryType: - description: "DiscoveryType refers to the service discovery - type to use for resolving the cluster. \n This defaults - to STRICT_DNS. Other options include STATIC, LOGICAL_DNS, - EDS or ORIGINAL_DST." - type: string - tlsCertificates: - description: "TLSCertificates refers to the data containing - certificate authority certificates to use in verifying - a presented peer certificate. If not specified and a - peer certificate is presented it will not be verified. - \n Must be either CaCertificateProviderInstance or TrustedCA." - properties: - caCertificateProviderInstance: - description: CaCertificateProviderInstance Certificate - provider instance for fetching TLS certificates. - properties: - certificateName: - description: "CertificateName is used to specify - certificate instances or types. For example, - \"ROOTCA\" to specify a root-certificate (validation - context) or \"example.com\" to specify a certificate - for a particular domain. \n The default value - is the empty string." - type: string - instanceName: - description: "InstanceName refers to the certificate - provider instance name. \n The default value - is \"default\"." - type: string - type: object - trustedCA: - description: "TrustedCA defines TLS certificate data - containing certificate authority certificates to - use in verifying a presented peer certificate. \n - Exactly one of Filename, EnvironmentVariable, InlineString - or InlineBytes must be specified." - properties: - environmentVariable: - type: string - filename: - type: string - inlineBytes: - format: byte - type: string - inlineString: - type: string - type: object - type: object - type: object - requestTimeoutMs: - description: RequestTimeoutMs is the number of milliseconds - to time out when making a request for the JWKS. - type: integer - retryPolicy: - description: "RetryPolicy defines a retry policy for fetching - JWKS. \n There is no retry by default." - properties: - numRetries: - description: "NumRetries is the number of times to retry - fetching the JWKS. The retry strategy uses jittered - exponential backoff with a base interval of 1s and max - of 10s. \n Default value is 0." - type: integer - retryPolicyBackOff: - description: "Retry's backoff policy. \n Defaults to Envoy's - backoff policy." - properties: - baseInterval: - description: "BaseInterval to be used for the next - back off computation. \n The default value from - envoy is 1s." - type: string - maxInterval: - description: "MaxInternal to be used to specify the - maximum interval between retries. Optional but should - be greater or equal to BaseInterval. \n Defaults - to 10 times BaseInterval." - type: string - type: object - type: object - uri: - description: URI is the URI of the server to query for the - JWKS. - type: string - type: object - type: object - locations: - description: 'Locations where the JWT will be present in requests. - Envoy will check all of these locations to extract a JWT. If no - locations are specified Envoy will default to: 1. Authorization - header with Bearer schema: "Authorization: Bearer " 2. accessToken - query parameter.' - items: - description: "JWTLocation is a location where the JWT could be present - in requests. \n Only one of Header, QueryParam, or Cookie can - be specified." - properties: - cookie: - description: Cookie defines how to extract a JWT from an HTTP - request cookie. - properties: - name: - description: Name is the name of the cookie containing the - token. - type: string - type: object - header: - description: Header defines how to extract a JWT from an HTTP - request header. - properties: - forward: - description: "Forward defines whether the header with the - JWT should be forwarded after the token has been verified. - If false, the header will not be forwarded to the backend. - \n Default value is false." - type: boolean - name: - description: Name is the name of the header containing the - token. - type: string - valuePrefix: - description: 'ValuePrefix is an optional prefix that precedes - the token in the header value. For example, "Bearer " - is a standard value prefix for a header named "Authorization", - but the prefix is not part of the token itself: "Authorization: - Bearer "' - type: string - type: object - queryParam: - description: QueryParam defines how to extract a JWT from an - HTTP request query parameter. - properties: - name: - description: Name is the name of the query param containing - the token. - type: string - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} 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 3c22a4842e..7ad173afbf 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -49,11 +48,6 @@ spec: spec: description: MeshSpec defines the desired state of Mesh. properties: - allowEnablingPermissiveMutualTLS: - description: AllowEnablingPermissiveMutualTLS must be true in order - to allow setting MutualTLSMode=permissive in either service-defaults - or proxy-defaults. - type: boolean http: description: HTTP defines the HTTP configuration for the service mesh. properties: @@ -200,3 +194,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml deleted file mode 100644 index 9eccd85cad..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: meshservices.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: MeshService - listKind: MeshServiceList - plural: meshservices - singular: meshservice - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: MeshService holds a reference to an externally managed Consul - Service Mesh service. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of MeshService. - properties: - name: - description: Name holds the service name for a Consul service. - type: string - peer: - description: Peer optionally specifies the name of the peer exporting - the Consul service. If not specified, the Consul service is assumed - to be in the local datacenter. - type: string - type: object - type: object - served: true - storage: true diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index b568a94962..e782ef472f 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -139,3 +138,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index ebf64adf67..d5103252a5 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -139,3 +138,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 20f2faeb63..6b9628cd74 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -51,60 +50,12 @@ spec: spec: description: ProxyDefaultsSpec defines the desired state of ProxyDefaults. properties: - accessLogs: - description: AccessLogs controls all envoy instances' access logging - configuration. - properties: - disableListenerLogs: - description: DisableListenerLogs turns off just listener logs - for connections rejected by Envoy because they don't have a - matching listener filter. - type: boolean - enabled: - description: Enabled turns on all access logging - type: boolean - jsonFormat: - description: 'JSONFormat is a JSON-formatted string of an Envoy - access log format dictionary. See for more info on formatting: - https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-dictionaries - Defining JSONFormat and TextFormat is invalid.' - type: string - path: - description: Path is the output file to write logs for file-type - logging - type: string - textFormat: - description: 'TextFormat is a representation of Envoy access logs - format. See for more info on formatting: https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-strings - Defining JSONFormat and TextFormat is invalid.' - type: string - type: - description: Type selects the output for logs one of "file", "stderr". - "stdout" - type: string - type: object config: description: Config is an arbitrary map of configuration values used by Connect proxies. Any values that your proxy allows can be configured globally here. Supports JSON config values. See https://www.consul.io/docs/connect/proxies/envoy#configuration-formatting type: object x-kubernetes-preserve-unknown-fields: true - envoyExtensions: - description: EnvoyExtensions are a list of extensions to modify Envoy - proxy configuration. - items: - description: EnvoyExtension has configuration for an extension that - patches Envoy resources. - properties: - arguments: - type: object - x-kubernetes-preserve-unknown-fields: true - name: - type: string - required: - type: boolean - type: object - type: array expose: description: Expose controls the default expose path configuration for Envoy. @@ -137,23 +88,6 @@ spec: type: object type: array type: object - failoverPolicy: - description: FailoverPolicy specifies the exact mechanism used for - failover. - properties: - mode: - description: Mode specifies the type of failover that will be - performed. Valid values are "sequential", "" (equivalent to - "sequential") and "order-by-locality". - type: string - regions: - description: Regions is the ordered list of the regions of the - failover targets. Valid values can be "us-west-1", "us-west-2", - and so on. - items: - type: string - type: array - type: object meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. @@ -174,28 +108,6 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string - mutualTLSMode: - description: 'MutualTLSMode controls whether mutual TLS is required - for all incoming connections when transparent proxy is enabled. - This can be set to "permissive" or "strict". "strict" is the default - which requires mutual TLS for incoming connections. In the insecure - "permissive" mode, connections to the sidecar proxy public listener - port require mutual TLS, but connections to the service port do - not require mutual TLS and are proxied to the application unmodified. - Note: Intentions are not enforced for non-mTLS connections. To keep - your services secure, we recommend using "strict" mode whenever - possible and enabling "permissive" mode only when necessary.' - type: string - prioritizeByLocality: - description: PrioritizeByLocality controls whether the locality of - services within the local partition will be used to prioritize connectivity. - properties: - mode: - description: 'Mode specifies the type of prioritization that will - be performed when selecting nodes in the local partition. Valid - values are: "" (default "none"), "none", and "failover".' - type: string - type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the @@ -258,3 +170,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml deleted file mode 100644 index 5072fdf391..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: routeauthfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteAuthFilter - listKind: RouteAuthFilterList - plural: routeauthfilters - singular: routeauthfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteAuthFilter is the Schema for the routeauthfilters API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteAuthFilterSpec defines the desired state of RouteAuthFilter. - properties: - jwt: - description: This re-uses the JWT requirement type from Gateway Policy - Types. - properties: - providers: - description: Providers is a list of providers to consider when - verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry with - this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the actual - claim information to be verified. - properties: - path: - description: Path is the path to the claim in the - token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the given - path: - If the type at the path is a list then we - verify that this value is contained in the list. - \n - If the type at the path is a string then we - verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - status: - description: RouteAuthFilterStatus defines the observed state of the gateway. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: ResolvedRefs - description: "Conditions describe the current conditions of the Filter. - \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml deleted file mode 100644 index 8fa61cb683..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: routeretryfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteRetryFilter - listKind: RouteRetryFilterList - plural: routeretryfilters - singular: routeretryfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteRetryFilter is the Schema for the routeretryfilters API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter. - properties: - numRetries: - format: int32 - minimum: 0 - type: integer - retryOn: - items: - type: string - type: array - retryOnConnectFailure: - type: boolean - retryOnStatusCodes: - items: - format: int32 - type: integer - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml deleted file mode 100644 index f6cc00f840..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: routetimeoutfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteTimeoutFilter - listKind: RouteTimeoutFilterList - plural: routetimeoutfilters - singular: routetimeoutfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteTimeoutFilter is the Schema for the httproutetimeoutfilters - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. - properties: - idleTimeout: - format: duration - type: string - requestTimeout: - format: duration - type: string - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml deleted file mode 100644 index 4274efffc8..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: samenessgroups.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: SamenessGroup - listKind: SamenessGroupList - plural: samenessgroups - shortNames: - - sameness-group - singular: samenessgroup - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: SamenessGroup is the Schema for the samenessgroups API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SamenessGroupSpec defines the desired state of SamenessGroup. - properties: - defaultForFailover: - description: DefaultForFailover indicates that upstream requests to - members of the given sameness group will implicitly failover between - members of this sameness group. When DefaultForFailover is true, - the local partition must be a member of the sameness group or IncludeLocal - must be set to true. - type: boolean - includeLocal: - description: IncludeLocal is used to include the local partition as - the first member of the sameness group. The local partition can - only be a member of a single sameness group. - type: boolean - members: - description: Members are the partitions and peers that are part of - the sameness group. If a member of a sameness group does not exist, - it will be ignored. - items: - properties: - partition: - description: The partitions and peers that are part of the sameness - group. A sameness group member cannot define both peer and - partition at the same time. - type: string - peer: - type: string - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 7e7bcfaacc..7324f0a296 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -51,12 +50,6 @@ spec: spec: description: ServiceDefaultsSpec defines the desired state of ServiceDefaults. properties: - balanceInboundConnections: - description: BalanceInboundConnections sets the strategy for allocating - inbound connections to the service across proxy threads. The only - supported value is exact_balance. By default, no connection balancing - is used. Refer to the Envoy Connection Balance config for details. - type: string destination: description: Destination is an address(es)/port combination that represents an endpoint outside the mesh. This is only valid when the mesh is @@ -76,22 +69,6 @@ spec: format: int32 type: integer type: object - envoyExtensions: - description: EnvoyExtensions are a list of extensions to modify Envoy - proxy configuration. - items: - description: EnvoyExtension has configuration for an extension that - patches Envoy resources. - properties: - arguments: - type: object - x-kubernetes-preserve-unknown-fields: true - name: - type: string - required: - type: boolean - type: object - type: array expose: description: Expose controls the default expose path configuration for Envoy. @@ -130,15 +107,15 @@ spec: with an external system. type: string localConnectTimeoutMs: - description: LocalConnectTimeoutMs is the number of milliseconds allowed - to make connections to the local application instance before timing - out. Defaults to 5000. + description: The number of milliseconds allowed to make connections + to the local application instance before timing out. Defaults to + 5000. type: integer localRequestTimeoutMs: - description: LocalRequestTimeoutMs is the timeout for HTTP requests - to the local application instance in milliseconds. Applies to HTTP-based - protocols only. If not specified, inherits the Envoy default for - route timeouts (15s). + description: In milliseconds, the timeout for HTTP requests to the + local application instance. Applies to HTTP-based protocols only. + If not specified, inherits the Envoy default for route timeouts + (15s). type: integer maxInboundConnections: description: MaxInboundConnections is the maximum number of concurrent @@ -165,87 +142,12 @@ spec: CRD and should be set using annotations on the services that are part of the mesh.' type: string - mutualTLSMode: - description: 'MutualTLSMode controls whether mutual TLS is required - for all incoming connections when transparent proxy is enabled. - This can be set to "permissive" or "strict". "strict" is the default - which requires mutual TLS for incoming connections. In the insecure - "permissive" mode, connections to the sidecar proxy public listener - port require mutual TLS, but connections to the service port do - not require mutual TLS and are proxied to the application unmodified. - Note: Intentions are not enforced for non-mTLS connections. To keep - your services secure, we recommend using "strict" mode whenever - possible and enabling "permissive" mode only when necessary.' - type: string protocol: description: Protocol sets the protocol of the service. This is used by Connect proxies for things like observability features and to unlock usage of the service-splitter and service-router config entries for a service. type: string - rateLimits: - description: RateLimits is rate limiting configuration that is applied - to inbound traffic for a service. Rate limiting is a Consul enterprise - feature. - properties: - instanceLevel: - description: InstanceLevel represents rate limit configuration - that is applied per service instance. - properties: - requestsMaxBurst: - description: "RequestsMaxBurst is the maximum number of requests - that can be sent in a burst. Should be equal to or greater - than RequestsPerSecond. If unset, defaults to RequestsPerSecond. - \n Internally, this is the maximum size of the token bucket - used for rate limiting." - type: integer - requestsPerSecond: - description: "RequestsPerSecond is the average number of requests - per second that can be made without being throttled. This - field is required if RequestsMaxBurst is set. The allowed - number of requests may exceed RequestsPerSecond up to the - value specified in RequestsMaxBurst. \n Internally, this - is the refill rate of the token bucket used for rate limiting." - type: integer - routes: - description: Routes is a list of rate limits applied to specific - routes. For a given request, the first matching route will - be applied, if any. Overrides any top-level configuration. - items: - properties: - pathExact: - description: Exact path to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - pathPrefix: - description: Prefix to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - pathRegex: - description: Regex to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - requestsMaxBurst: - description: RequestsMaxBurst is the maximum number - of requests that can be sent in a burst. Should be - equal to or greater than RequestsPerSecond. If unset, - defaults to RequestsPerSecond. Internally, this is - the maximum size of the token bucket used for rate - limiting. - type: integer - requestsPerSecond: - description: RequestsPerSecond is the average number - of requests per second that can be made without being - throttled. This field is required if RequestsMaxBurst - is set. The allowed number of requests may exceed - RequestsPerSecond up to the value specified in RequestsMaxBurst. - Internally, this is the refill rate of the token bucket - used for rate limiting. - type: integer - type: object - type: array - type: object - type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the @@ -327,15 +229,15 @@ spec: type: string type: object name: - description: Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Name is only accepted within a service-defaults config entry. type: string namespace: - description: Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Namespace is only accepted within a service-defaults config entry. type: string partition: - description: Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Partition is only accepted within a service-defaults config entry. type: string passiveHealthCheck: @@ -348,22 +250,18 @@ spec: The real time is equal to the base time multiplied by the number of times the host has been ejected and is capped by max_ejection_time (Default 300s). Defaults - to 30s. + to 30000ms or 30s. type: string enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can be used to disable ejection or to ramp it up slowly. - Ex. Setting this to 10 will make it a 10% chance that - the host will be ejected. format: int32 type: integer interval: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts to the pool. - Ex. setting this to "10s" will set the interval to 10 - seconds. type: string maxEjectionPercent: description: The maximum % of an upstream cluster that @@ -378,10 +276,6 @@ spec: format: int32 type: integer type: object - peer: - description: Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides - config entry. - type: string protocol: description: Protocol describes the upstream's service protocol. Valid values are "tcp", "http" and "grpc". Anything else @@ -448,15 +342,15 @@ spec: type: string type: object name: - description: Name is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Name is only accepted within a service-defaults config entry. type: string namespace: - description: Namespace is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Namespace is only accepted within a service-defaults config entry. type: string partition: - description: Partition is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides + description: Partition is only accepted within a service-defaults config entry. type: string passiveHealthCheck: @@ -469,22 +363,19 @@ spec: The real time is equal to the base time multiplied by the number of times the host has been ejected and is capped by max_ejection_time (Default 300s). Defaults - to 30s. + to 30000ms or 30s. type: string enforcingConsecutive5xx: description: EnforcingConsecutive5xx is the % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. This setting can be used to disable ejection or to ramp it up slowly. - Ex. Setting this to 10 will make it a 10% chance that - the host will be ejected. format: int32 type: integer interval: description: Interval between health check analysis sweeps. Each sweep may remove hosts or return hosts - to the pool. Ex. setting this to "10s" will set the - interval to 10 seconds. + to the pool. type: string maxEjectionPercent: description: The maximum % of an upstream cluster that @@ -500,10 +391,6 @@ spec: format: int32 type: integer type: object - peer: - description: Peer is only accepted within service ServiceDefaultsSpec.UpstreamConfig.Overrides - config entry. - type: string protocol: description: Protocol describes the upstream's service protocol. Valid values are "tcp", "http" and "grpc". Anything else @@ -558,3 +445,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] 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 4718ee24e5..a0cc7a6343 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -68,43 +67,6 @@ spec: have intentions defined. type: string type: object - jwt: - description: JWT specifies the configuration to validate a JSON Web - Token for all incoming requests. - properties: - providers: - description: Providers is a list of providers to consider when - verifying a JWT. - items: - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry with - this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - properties: - path: - description: Path is the path to the claim in the - token JSON. - items: - type: string - type: array - value: - description: Value is the expected value at the given - path. If the type at the path is a list then we - verify that this value is contained in the list. - If the type at the path is a string then we verify - that this value matches. - type: string - type: object - type: array - type: object - type: array - type: object sources: description: Sources is the list of all intention sources and the authorization granted to those sources. The order of this list does @@ -133,7 +95,8 @@ spec: description: Partition is the Admin Partition for the Name parameter. type: string peer: - description: Peer is the peer name for the Name parameter. + description: '[Experimental] Peer is the peer name for the Name + parameter.' type: string permissions: description: Permissions is the list of all additional L7 attributes @@ -214,50 +177,8 @@ spec: match on the HTTP request path. type: string type: object - jwt: - description: JWT specifies configuration to validate a - JSON Web Token for incoming requests. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - properties: - name: - description: Name is the name of the JWT provider. - There MUST be a corresponding "jwt-provider" - config entry with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional - claims to verify in a JWT's payload. - items: - properties: - path: - description: Path is the path to the claim - in the token JSON. - items: - type: string - type: array - value: - description: Value is the expected value - at the given path. If the type at the - path is a list then we verify that this - value is contained in the list. If the - type at the path is a string then we - verify that this value matches. - type: string - type: object - type: array - type: object - type: array - type: object type: object type: array - samenessGroup: - description: SamenessGroup is the name of the sameness group, - if applicable. - type: string type: object type: array type: object @@ -304,3 +225,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index a1e3844b9c..a84fc0bd88 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -73,26 +72,6 @@ spec: service from to form the failover group of instances. If empty the current namespace is used. type: string - policy: - description: Policy specifies the exact mechanism used for failover. - properties: - mode: - description: Mode specifies the type of failover that will - be performed. Valid values are "sequential", "" (equivalent - to "sequential") and "order-by-locality". - type: string - regions: - description: Regions is the ordered list of the regions - of the failover targets. Valid values can be "us-west-1", - "us-west-2", and so on. - items: - type: string - type: array - type: object - samenessGroup: - description: SamenessGroup is the name of the sameness group - to try during failover. - type: string service: description: Service is the service to resolve instead of the default as the failover group of instances during failover. @@ -221,16 +200,6 @@ spec: type: integer type: object type: object - prioritizeByLocality: - description: PrioritizeByLocality controls whether the locality of - services within the local partition will be used to prioritize connectivity. - properties: - mode: - description: 'Mode specifies the type of prioritization that will - be performed when selecting nodes in the local partition. Valid - values are: "" (default "none"), "none", and "failover".' - type: string - type: object redirect: description: Redirect when configured, all attempts to resolve the service this resolver defines will be substituted for the supplied @@ -256,10 +225,6 @@ spec: description: Peer is the name of the cluster peer to resolve the service from instead of the current one. type: string - samenessGroup: - description: SamenessGroup is the name of the sameness group to - resolve the service from instead of the current one. - type: string service: description: Service is a service to resolve instead of the current service. @@ -270,10 +235,6 @@ spec: If empty the default subset is used. type: string type: object - requestTimeout: - description: RequestTimeout is the timeout for receiving an HTTP response - from this service before the connection is terminated. - type: string subsets: additionalProperties: properties: @@ -341,3 +302,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 41d4bfbd81..8b55692bd2 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -144,13 +143,6 @@ spec: any existing header values of the same name. type: object type: object - retryOn: - description: 'RetryOn is a flat list of conditions for Consul - to retry requests based on the response from an upstream - service. Refer to the valid conditions here: https://developer.hashicorp.com/consul/docs/connect/config-entries/service-router#routes-destination-retryon' - items: - type: string - type: array retryOnConnectFailure: description: RetryOnConnectFailure allows for connection failure errors to trigger a retry. @@ -181,10 +173,6 @@ spec: http: description: HTTP is a set of http-specific match criteria. properties: - caseInsensitive: - description: CaseInsensitive configures PathExact and - PathPrefix matches to ignore upper/lower casing. - type: boolean header: description: Header is a set of criteria that can match on HTTP request headers. If more than one is configured @@ -316,3 +304,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index 36f9c9f6c9..df8bbbfbdf 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -179,3 +178,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index 7f22c65d09..8e6c449ef8 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -1,11 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -130,3 +129,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml deleted file mode 100644 index 44713c234f..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml +++ /dev/null @@ -1,235 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: apigateways.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: APIGateway - listKind: APIGatewayList - plural: apigateways - singular: apigateway - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: APIGateway is the Schema for the API Gateway - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - gatewayClassName: - description: GatewayClassName is the name of the GatewayClass used - by the APIGateway - type: string - listeners: - items: - properties: - hostname: - description: Hostname is the host name that a listener should - be bound to, if unspecified, the listener accepts requests - for all hostnames. - type: string - name: - description: Name is the name of the listener in a given gateway. - This must be unique within a gateway. - type: string - port: - format: int32 - maximum: 65535 - minimum: 0 - type: integer - protocol: - description: Protocol is the protocol that a listener should - use, it must either be "http" or "tcp" - type: string - tls: - description: TLS is the TLS settings for the listener. - properties: - certificates: - description: Certificates is a set of references to certificates - that a gateway listener uses for TLS termination. - items: - description: Reference identifies which resource a condition - relates to, when it is not the core resource itself. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the - resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list resources - across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list resources - across all partitions." - type: string - peerName: - description: "PeerName identifies which peer the - resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list resources - across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes are - made to the group's resource types. - type: string - kind: - description: Kind identifies the specific resource - type within the group. - type: string - type: object - type: object - type: array - tlsParameters: - description: TLSParameters contains optional configuration - for running TLS termination. - properties: - cipherSuites: - items: - enum: - - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_GCM_SHA256 - - TLS_CIPHER_SUITE_AES256_SHA - - TLS_CIPHER_SUITE_ECDHE_ECDSA_CHACHA20_POLY1305 - - TLS_CIPHER_SUITE_ECDHE_RSA_AES128_GCM_SHA256 - - TLS_CIPHER_SUITE_ECDHE_RSA_CHACHA20_POLY1305 - - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_SHA - - TLS_CIPHER_SUITE_ECDHE_RSA_AES128_SHA - - TLS_CIPHER_SUITE_AES128_GCM_SHA256 - - TLS_CIPHER_SUITE_AES128_SHA - - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_GCM_SHA384 - - TLS_CIPHER_SUITE_ECDHE_RSA_AES256_GCM_SHA384 - - TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_SHA - - TLS_CIPHER_SUITE_ECDHE_RSA_AES256_SHA - - TLS_CIPHER_SUITE_AES256_GCM_SHA384 - format: int32 - type: string - type: array - maxVersion: - enum: - - TLS_VERSION_AUTO - - TLS_VERSION_1_0 - - TLS_VERSION_1_1 - - TLS_VERSION_1_2 - - TLS_VERSION_1_3 - - TLS_VERSION_INVALID - - TLS_VERSION_UNSPECIFIED - format: int32 - type: string - minVersion: - enum: - - TLS_VERSION_AUTO - - TLS_VERSION_1_0 - - TLS_VERSION_1_1 - - TLS_VERSION_1_2 - - TLS_VERSION_1_3 - - TLS_VERSION_INVALID - - TLS_VERSION_UNSPECIFIED - format: int32 - type: string - type: object - type: object - type: object - minItems: 1 - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml deleted file mode 100644 index e7f560861b..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclassconfigs.yaml +++ /dev/null @@ -1,1821 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: gatewayclassconfigs.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: GatewayClassConfig - listKind: GatewayClassConfigList - plural: gatewayclassconfigs - singular: gatewayclassconfig - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: GatewayClassConfig is the Schema for the Mesh Gateway API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GatewayClassConfigSpec specifies the desired state of the - GatewayClassConfig CRD. - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key included - here will override those in Set if specified on the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included here - will be overridden if present in InheritFromGateway and set - on the Gateway. - type: object - type: object - deployment: - description: Deployment contains config specific to the Deployment - created from this GatewayClass - properties: - affinity: - description: Affinity specifies the affinity to use on the created - Deployment. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for - the pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the affinity expressions specified - by this field, but it may choose a node that violates - one or more of the expressions. The node that is most - preferred is the one with the greatest sum of weights, - i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node matches the corresponding matchExpressions; - the node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches - all objects with implicit weight 0 (i.e. it's a no-op). - A null preferred scheduling term matches no objects - (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated with - the corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the - corresponding nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by - this field are not met at scheduling time, the pod will - not be scheduled onto the node. If the affinity requirements - specified by this field cease to be met at some point - during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from - its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: A null or empty node selector term - matches no objects. The requirements of them are - ANDed. The TopologySelectorTerm type implements - a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - type: array - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. - co-locate this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the affinity expressions specified - by this field, but it may choose a node that violates - one or more of the expressions. The node that is most - preferred is the one with the greatest sum of weights, - i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node has pods which matches the corresponding - podAffinityTerm; the node(s) with the highest sum are - the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by - this field and the ones listed in the namespaces - field. null selector and null or empty namespaces - list means "this pod's namespace". An empty - selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. - The term is applied to the union of the namespaces - listed in this field and the ones selected - by namespaceSelector. null or empty namespaces - list and null namespaceSelector means "this - pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the - pods matching the labelSelector in the specified - namespaces, where co-located is defined as - running on a node whose value of the label - with key topologyKey matches that of any node - on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the - corresponding podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by - this field are not met at scheduling time, the pod will - not be scheduled onto the node. If the affinity requirements - specified by this field cease to be met at some point - during pod execution (e.g. due to a pod label update), - the system may or may not try to eventually evict the - pod from its node. When there are multiple elements, - the lists of nodes corresponding to each podAffinityTerm - are intersected, i.e. all terms must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located - is defined as running on a node whose value of the - label with key matches that of any node - on which a pod of the set of pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules - (e.g. avoid putting this pod in the same node, zone, etc. - as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the anti-affinity expressions - specified by this field, but it may choose a node that - violates one or more of the expressions. The node that - is most preferred is the one with the greatest sum of - weights, i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - anti-affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node has pods which matches the corresponding - podAffinityTerm; the node(s) with the highest sum are - the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by - this field and the ones listed in the namespaces - field. null selector and null or empty namespaces - list means "this pod's namespace". An empty - selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. - The term is applied to the union of the namespaces - listed in this field and the ones selected - by namespaceSelector. null or empty namespaces - list and null namespaceSelector means "this - pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the - pods matching the labelSelector in the specified - namespaces, where co-located is defined as - running on a node whose value of the label - with key topologyKey matches that of any node - on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the - corresponding podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified - by this field are not met at scheduling time, the pod - will not be scheduled onto the node. If the anti-affinity - requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod - label update), the system may or may not try to eventually - evict the pod from its node. When there are multiple - elements, the lists of nodes corresponding to each podAffinityTerm - are intersected, i.e. all terms must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located - is defined as running on a node whose value of the - label with key matches that of any node - on which a pod of the set of pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - type: object - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - container: - description: Container contains config specific to the created - Deployment's container. - properties: - consul: - description: Consul specifies configuration for the consul-dataplane - container - properties: - logging: - description: Logging specifies the logging configuration - for Consul Dataplane - properties: - level: - description: Level sets the logging level for Consul - Dataplane (debug, info, etc.) - type: string - type: object - type: object - hostPort: - description: HostPort specifies a port to be exposed to the - external host network - format: int32 - type: integer - portModifier: - description: PortModifier specifies the value to be added - to every port value for listeners on this gateway. This - is generally used to avoid binding to privileged ports in - the container. - format: int32 - type: integer - resources: - description: Resources specifies the resource requirements - for the created Deployment's container - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. \n This field - is immutable. It can only be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If Requests is omitted for - a container, it defaults to Limits if that is explicitly - specified, otherwise to an implementation-defined value. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - type: object - dnsPolicy: - description: DNSPolicy specifies the dns policy to use. These - are set on a per pod basis. - enum: - - Default - - ClusterFirst - - ClusterFirstWithHostNet - - None - type: string - hostNetwork: - description: HostNetwork specifies whether the gateway pods should - run on the host network. - type: boolean - initContainer: - description: InitContainer contains config specific to the created - Deployment's init container. - properties: - consul: - description: Consul specifies configuration for the consul-k8s-control-plane - init container - properties: - logging: - description: Logging specifies the logging configuration - for Consul Dataplane - properties: - level: - description: Level sets the logging level for Consul - Dataplane (debug, info, etc.) - type: string - type: object - type: object - resources: - description: Resources specifies the resource requirements - for the created Deployment's init container - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. \n This field - is immutable. It can only be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If Requests is omitted for - a container, it defaults to Limits if that is explicitly - specified, otherwise to an implementation-defined value. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a feature that constrains the scheduling - of a pod to nodes that match specified labels. By defining NodeSelector - in a pod''s configuration, you can ensure that the pod is only - scheduled to nodes with the corresponding labels, providing - a way to influence the placement of workloads based on node - attributes. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - priorityClassName: - description: PriorityClassName specifies the priority class name - to use on the created Deployment. - type: string - replicas: - description: Replicas specifies the configuration to control the - number of replicas for the created Deployment. - properties: - default: - description: Default is the number of replicas assigned to - the Deployment when created - format: int32 - type: integer - max: - description: Max is the maximum number of replicas allowed - for a gateway with this class. If the replica count exceeds - this value due to manual or automated scaling, the replica - count will be restored to this value. - format: int32 - type: integer - min: - description: Min is the minimum number of replicas allowed - for a gateway with this class. If the replica count drops - below this value due to manual or automated scaling, the - replica count will be restored to this value. - format: int32 - type: integer - type: object - securityContext: - description: SecurityContext specifies the security context for - the created Deployment's Pod. - properties: - fsGroup: - description: "A special supplemental group that applies to - all containers in a pod. Some volume types allow the Kubelet - to change the ownership of that volume to be owned by the - pod: \n 1. The owning GID will be the FSGroup 2. The setgid - bit is set (new files created in the volume will be owned - by FSGroup) 3. The permission bits are OR'd with rw-rw---- - \n If unset, the Kubelet will not modify the ownership and - permissions of any volume. Note that this field cannot be - set when spec.os.name is windows." - format: int64 - type: integer - fsGroupChangePolicy: - description: 'fsGroupChangePolicy defines behavior of changing - ownership and permission of the volume before being exposed - inside Pod. This field will only apply to volume types which - support fsGroup based ownership(and permissions). It will - have no effect on ephemeral volume types such as: secret, - configmaps and emptydir. Valid values are "OnRootMismatch" - and "Always". If not specified, "Always" is used. Note that - this field cannot be set when spec.os.name is windows.' - type: string - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in SecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. Note that this field - cannot be set when spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - Note that this field cannot be set when spec.os.name is - windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to all containers. - If unspecified, the container runtime will allocate a random - SELinux context for each container. May also be set in - SecurityContext. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence - for that container. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by the containers - in this pod. Note that this field cannot be set when spec.os.name - is windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile must - be preconfigured on the node to work. Must be a descending - path, relative to the kubelet's configured seccomp profile - location. Must only be set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - a - profile defined in a file on the node should be used. - RuntimeDefault - the container runtime default profile - should be used. Unconfined - no profile should be applied." - type: string - required: - - type - type: object - supplementalGroups: - description: A list of groups applied to the first process - run in each container, in addition to the container's primary - GID, the fsGroup (if specified), and group memberships defined - in the container image for the uid of the container process. - If unspecified, no additional groups are added to any container. - Note that group memberships defined in the container image - for the uid of the container process are still effective, - even if they are not included in this list. Note that this - field cannot be set when spec.os.name is windows. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used - for the pod. Pods with unsupported sysctls (by the container - runtime) might fail to launch. Note that this field cannot - be set when spec.os.name is windows. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: object - type: array - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options within a container's - SecurityContext will be used. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components that - enable the WindowsHostProcessContainers feature flag. - Setting this field without the feature flag will result - in errors when validating the Pod. All of a Pod's containers - must have the same effective HostProcess value (it is - not allowed to have a mix of HostProcess containers - and non-HostProcess containers). In addition, if HostProcess - is true then HostNetwork must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - tolerations: - description: Tolerations specifies the tolerations to use on the - created Deployment. - items: - description: The pod this Toleration is attached to tolerates - any taint that matches the triple using - the matching operator . - properties: - effect: - description: Effect indicates the taint effect to match. - Empty means match all taint effects. When specified, allowed - values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match - all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to - the value. Valid operators are Exists and Equal. Defaults - to Equal. Exists is equivalent to wildcard for value, - so that a pod can tolerate all taints of a particular - category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of - time the toleration (which must be of effect NoExecute, - otherwise this field is ignored) tolerates the taint. - By default, it is not set, which means tolerate the taint - forever (do not evict). Zero and negative values will - be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array - topologySpreadConstraints: - description: 'TopologySpreadConstraints is a feature that controls - how pods are spead across your topology. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/' - items: - description: TopologySpreadConstraint specifies how to spread - matching pods among the given topology. - properties: - labelSelector: - description: LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine - the number of pods in their corresponding topology domain. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: MatchLabelKeys is a set of pod label keys to - select the pods over which spreading will be calculated. - The keys are used to lookup values from the incoming pod - labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading - will be calculated for the incoming pod. Keys that don't - exist in the incoming pod labels will be ignored. A null - or empty list means only match against labelSelector. - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: 'MaxSkew describes the degree to which pods - may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, - it is the maximum permitted difference between the number - of matching pods in the target topology and the global - minimum. The global minimum is the minimum number of matching - pods in an eligible domain or zero if the number of eligible - domains is less than MinDomains. For example, in a 3-zone - cluster, MaxSkew is set to 1, and pods with the same labelSelector - spread as 2/2/1: In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to - zone3 to become 2/2/2; scheduling it onto zone1(zone2) - would make the ActualSkew(3-1) on zone1(zone2) violate - MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled - onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, - it is used to give higher precedence to topologies that - satisfy it. It''s a required field. Default value is 1 - and 0 is not allowed.' - format: int32 - type: integer - minDomains: - description: "MinDomains indicates a minimum number of eligible - domains. When the number of eligible domains with matching - topology keys is less than minDomains, Pod Topology Spread - treats \"global minimum\" as 0, and then the calculation - of Skew is performed. And when the number of eligible - domains with matching topology keys equals or greater - than minDomains, this value has no effect on scheduling. - As a result, when the number of eligible domains is less - than minDomains, scheduler won't schedule more than maxSkew - Pods to those domains. If value is nil, the constraint - behaves as if MinDomains is equal to 1. Valid values are - integers greater than 0. When value is not nil, WhenUnsatisfiable - must be DoNotSchedule. \n For example, in a 3-zone cluster, - MaxSkew is set to 2, MinDomains is set to 5 and pods with - the same labelSelector spread as 2/2/2: | zone1 | zone2 - | zone3 | | P P | P P | P P | The number of domains - is less than 5(MinDomains), so \"global minimum\" is treated - as 0. In this situation, new pod with the same labelSelector - cannot be scheduled, because computed skew will be 3(3 - - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. \n This is a beta field and requires - the MinDomainsInPodTopologySpread feature gate to be enabled - (enabled by default)." - format: int32 - type: integer - nodeAffinityPolicy: - description: "NodeAffinityPolicy indicates how we will treat - Pod's nodeAffinity/nodeSelector when calculating pod topology - spread skew. Options are: - Honor: only nodes matching - nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes - are included in the calculations. \n If this value is - nil, the behavior is equivalent to the Honor policy. This - is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread - feature flag." - type: string - nodeTaintsPolicy: - description: "NodeTaintsPolicy indicates how we will treat - node taints when calculating pod topology spread skew. - Options are: - Honor: nodes without taints, along with - tainted nodes for which the incoming pod has a toleration, - are included. - Ignore: node taints are ignored. All nodes - are included. \n If this value is nil, the behavior is - equivalent to the Ignore policy. This is a beta-level - feature default enabled by the NodeInclusionPolicyInPodTopologySpread - feature flag." - type: string - topologyKey: - description: TopologyKey is the key of node labels. Nodes - that have a label with this key and identical values are - considered to be in the same topology. We consider each - as a "bucket", and try to put balanced number - of pods into each bucket. We define a domain as a particular - instance of a topology. Also, we define an eligible domain - as a domain whose nodes meet the requirements of nodeAffinityPolicy - and nodeTaintsPolicy. e.g. If TopologyKey is "kubernetes.io/hostname", - each Node is a domain of that topology. And, if TopologyKey - is "topology.kubernetes.io/zone", each zone is a domain - of that topology. It's a required field. - type: string - whenUnsatisfiable: - description: 'WhenUnsatisfiable indicates how to deal with - a pod if it doesn''t satisfy the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule - it. - ScheduleAnyway tells the scheduler to schedule the - pod in any location, but giving higher precedence to topologies - that would help reduce the skew. A constraint is considered - "Unsatisfiable" for an incoming pod if and only if every - possible node assignment for that pod would violate "MaxSkew" - on some topology. For example, in a 3-zone cluster, MaxSkew - is set to 1, and pods with the same labelSelector spread - as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming - pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) - as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). - In other words, the cluster can still be imbalanced, but - scheduler won''t make it *more* imbalanced. It''s a required - field.' - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key included - here will override those in Set if specified on the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included here - will be overridden if present in InheritFromGateway and set - on the Gateway. - type: object - type: object - role: - description: Role contains config specific to the Role created from - this GatewayClass - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - type: object - roleBinding: - description: RoleBinding contains config specific to the RoleBinding - created from this GatewayClass - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - type: object - service: - description: Service contains config specific to the Service created - from this GatewayClass - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - type: - description: Type specifies the type of Service to use (LoadBalancer, - ClusterIP, etc.) - enum: - - ClusterIP - - NodePort - - LoadBalancer - type: string - type: object - serviceAccount: - description: ServiceAccount contains config specific to the corev1.ServiceAccount - created from this GatewayClass - properties: - annotations: - description: Annotations are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - labels: - description: Labels are applied to the created resource - properties: - inheritFromGateway: - description: InheritFromGateway lists the names/keys of annotations - or labels to copy from the Gateway resource. Any name/key - included here will override those in Set if specified on - the Gateway. - items: - type: string - type: array - set: - additionalProperties: - type: string - description: Set lists the names/keys and values of annotations - or labels to set on the resource. Any name/key included - here will be overridden if present in InheritFromGateway - and set on the Gateway. - type: object - type: object - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml deleted file mode 100644 index 1da2d613f4..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: gatewayclasses.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: GatewayClass - listKind: GatewayClassList - plural: gatewayclasses - singular: gatewayclass - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: GatewayClass is the Schema for the Gateway Class API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayClass - \n This is a Resource type." - properties: - controllerName: - description: ControllerName is the name of the Kubernetes controller - that manages Gateways of this class - type: string - description: - description: Description of GatewayClass - type: string - parametersRef: - description: ParametersRef refers to a resource responsible for configuring - the behavior of the GatewayClass. - properties: - group: - description: The Kubernetes Group that the referred object belongs - to - type: string - kind: - description: The Kubernetes Kind that the referred object is - type: string - name: - description: The name of the referred object - type: string - namespace: - description: The kubernetes namespace that the referred object - is in - type: string - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml deleted file mode 100644 index fda3e4255e..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml +++ /dev/null @@ -1,612 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: grpcroutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: GRPCRoute - listKind: GRPCRouteList - plural: grpcroutes - shortNames: - - grpc-route - singular: grpcroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: GRPCRoute is the Schema for the GRPC Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute - \n This is a Resource type." - properties: - hostnames: - description: "Hostnames are the hostnames for which this GRPCRoute - should respond to requests. \n This is only valid for north/south." - items: - type: string - type: array - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of GRPC matchers, filters and actions. - items: - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. Failure behavior here depends on - how many BackendRefs are specified and how many are invalid. - \n If all entries in BackendRefs are invalid, and there are - also no filters specified in this route rule, all traffic - which matches this rule MUST receive a 500 status code. \n - See the GRPCBackendRef definition for the rules about what - makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef - is invalid, 500 status codes MUST be returned for requests - that would have otherwise been routed to an invalid backend. - If multiple backends are specified, and some are invalid, - the proportion of requests that would otherwise have been - routed to an invalid backend MUST receive a 500 status code. - \n For example, if two backends are specified with equal weights, - and one is invalid, 50 percent of traffic must receive a 500. - Implementations may choose how that 50 percent is determined." - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - filters: - description: Filters defined at this level should be executed - if and only if the request is being forwarded to the - backend defined here. - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema - for a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema - for a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - filters: - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema for - a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema for - a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - matches: - items: - properties: - headers: - description: Headers specifies gRPC request header matchers. - Multiple match values are ANDed together, meaning, a - request MUST match all the specified headers to select - the route. - items: - properties: - name: - type: string - type: - description: "HeaderMatchType specifies the semantics - of how HTTP header values should be compared. - Valid HeaderMatchType values, along with their - conformance levels, are: \n Note that values may - be added to this enum, implementations must ensure - that unknown values will not cause a crash. \n - Unknown values here must result in the implementation - setting the Accepted Condition for the Route to - status: False, with a Reason of UnsupportedValue." - enum: - - HEADER_MATCH_TYPE_UNSPECIFIED - - HEADER_MATCH_TYPE_EXACT - - HEADER_MATCH_TYPE_REGEX - - HEADER_MATCH_TYPE_PRESENT - - HEADER_MATCH_TYPE_PREFIX - - HEADER_MATCH_TYPE_SUFFIX - format: int32 - type: string - value: - type: string - type: object - type: array - method: - description: Method specifies a gRPC request service/method - matcher. If this field is not specified, all services - and methods will match. - properties: - method: - description: "Value of the method to match against. - If left empty or omitted, will match all services. - \n At least one of Service and Method MUST be a - non-empty string.}" - type: string - service: - description: "Value of the service to match against. - If left empty or omitted, will match any service. - \n At least one of Service and Method MUST be a - non-empty string." - type: string - type: - description: 'Type specifies how to match against - the service and/or method. Support: Core (Exact - with service and method specified)' - enum: - - GRPC_METHOD_MATCH_TYPE_UNSPECIFIED - - GRPC_METHOD_MATCH_TYPE_EXACT - - GRPC_METHOD_MATCH_TYPE_REGEX - format: int32 - type: string - type: object - type: object - type: array - retries: - properties: - number: - description: Number is the number of times to retry the - request when a retryable result occurs. - properties: - value: - description: The uint32 value. - format: int32 - type: integer - type: object - onConditions: - description: RetryOn allows setting envoy specific conditions - when a request should be automatically retried. - items: - type: string - type: array - onConnectFailure: - description: RetryOnConnectFailure allows for connection - failure errors to trigger a retry. - type: boolean - onStatusCodes: - description: RetryOnStatusCodes is a flat list of http response - status codes that are eligible for retry. This again should - be feasible in any reasonable proxy. - items: - format: int32 - type: integer - type: array - type: object - timeouts: - description: HTTPRouteTimeouts defines timeouts that can be - configured for an HTTPRoute or GRPCRoute. - properties: - idle: - description: Idle specifies the total amount of time permitted - for the request stream to be idle. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - request: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml deleted file mode 100644 index 46bf7162a6..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml +++ /dev/null @@ -1,668 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: httproutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: HTTPRoute - listKind: HTTPRouteList - plural: httproutes - shortNames: - - http-route - singular: httproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: HTTPRoute is the Schema for the HTTP Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute - \n This is a Resource type." - properties: - hostnames: - description: "Hostnames are the hostnames for which this HTTPRoute - should respond to requests. \n This is only valid for north/south." - items: - type: string - type: array - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of HTTP-based routing rules that this - route should use for constructing a routing table. - items: - description: HTTPRouteRule specifies the routing rules used to determine - what upstream service an HTTP request is routed to. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. \n Failure behavior here depends - on how many BackendRefs are specified and how many are invalid. - \n If all entries in BackendRefs are invalid, and there are - also no filters specified in this route rule, all traffic - which matches this rule MUST receive a 500 status code. \n - See the HTTPBackendRef definition for the rules about what - makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef - is invalid, 500 status codes MUST be returned for requests - that would have otherwise been routed to an invalid backend. - If multiple backends are specified, and some are invalid, - the proportion of requests that would otherwise have been - routed to an invalid backend MUST receive a 500 status code. - \n For example, if two backends are specified with equal weights, - and one is invalid, 50 percent of traffic must receive a 500. - Implementations may choose how that 50 percent is determined." - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - filters: - description: Filters defined at this level should be executed - if and only if the request is being forwarded to the - backend defined here. - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema - for a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema - for a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - filters: - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema for - a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema for - a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - matches: - items: - properties: - headers: - description: Headers specifies HTTP request header matchers. - Multiple match values are ANDed together, meaning, a - request must match all the specified headers to select - the route. - items: - properties: - invert: - description: 'NOTE: not in gamma; service-router - compat' - type: boolean - name: - description: "Name is the name of the HTTP Header - to be matched. Name matching MUST be case insensitive. - (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent header - names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent header name MUST be - ignored. Due to the case-insensitivity of header - names, “foo” and “Foo” are considered equivalent. - \n When a header is repeated in an HTTP request, - it is implementation-specific behavior as to how - this is represented. Generally, proxies should - follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 - regarding processing a repeated header, with special - handling for “Set-Cookie”." - type: string - type: - description: Type specifies how to match against - the value of the header. - enum: - - HEADER_MATCH_TYPE_UNSPECIFIED - - HEADER_MATCH_TYPE_EXACT - - HEADER_MATCH_TYPE_REGEX - - HEADER_MATCH_TYPE_PRESENT - - HEADER_MATCH_TYPE_PREFIX - - HEADER_MATCH_TYPE_SUFFIX - format: int32 - type: string - value: - description: Value is the value of HTTP Header to - be matched. - type: string - type: object - type: array - method: - description: Method specifies HTTP method matcher. When - specified, this route will be matched only if the request - has the specified method. - type: string - path: - description: Path specifies a HTTP request path matcher. - If this field is not specified, a default prefix match - on the “/” path is provided. - properties: - type: - description: Type specifies how to match against the - path Value. - enum: - - PATH_MATCH_TYPE_UNSPECIFIED - - PATH_MATCH_TYPE_EXACT - - PATH_MATCH_TYPE_PREFIX - - PATH_MATCH_TYPE_REGEX - format: int32 - type: string - value: - description: Value of the HTTP path to match against. - type: string - type: object - queryParams: - description: QueryParams specifies HTTP query parameter - matchers. Multiple match values are ANDed together, - meaning, a request must match all the specified query - parameters to select the route. - items: - properties: - name: - description: "Name is the name of the HTTP query - param to be matched. This must be an exact string - match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). - \n If multiple entries specify equivalent query - param names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent query param name MUST - be ignored. \n If a query param is repeated in - an HTTP request, the behavior is purposely left - undefined, since different data planes have different - capabilities. However, it is recommended that - implementations should match against the first - value of the param if the data plane supports - it, as this behavior is expected in other load - balancing contexts outside of the Gateway API. - \n Users SHOULD NOT route traffic based on repeated - query params to guard themselves against potential - differences in the implementations." - type: string - type: - description: Type specifies how to match against - the value of the query parameter. - enum: - - QUERY_PARAM_MATCH_TYPE_UNSPECIFIED - - QUERY_PARAM_MATCH_TYPE_EXACT - - QUERY_PARAM_MATCH_TYPE_REGEX - - QUERY_PARAM_MATCH_TYPE_PRESENT - format: int32 - type: string - value: - description: Value is the value of HTTP query param - to be matched. - type: string - type: object - type: array - type: object - type: array - retries: - properties: - number: - description: Number is the number of times to retry the - request when a retryable result occurs. - properties: - value: - description: The uint32 value. - format: int32 - type: integer - type: object - onConditions: - description: RetryOn allows setting envoy specific conditions - when a request should be automatically retried. - items: - type: string - type: array - onConnectFailure: - description: RetryOnConnectFailure allows for connection - failure errors to trigger a retry. - type: boolean - onStatusCodes: - description: RetryOnStatusCodes is a flat list of http response - status codes that are eligible for retry. This again should - be feasible in any reasonable proxy. - items: - format: int32 - type: integer - type: array - type: object - timeouts: - description: HTTPRouteTimeouts defines timeouts that can be - configured for an HTTPRoute or GRPCRoute. - properties: - idle: - description: Idle specifies the total amount of time permitted - for the request stream to be idle. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - request: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshconfigurations.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshconfigurations.yaml deleted file mode 100644 index eb044ecb6c..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshconfigurations.yaml +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: meshconfigurations.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: MeshConfiguration - listKind: MeshConfigurationList - plural: meshconfigurations - singular: meshconfiguration - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: MeshConfiguration is the Schema for the Mesh Configuration - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: MeshConfiguration is responsible for configuring the default - behavior of Mesh Gateways. This is a Resource type. - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml deleted file mode 100644 index 47f2fcfba8..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_meshgateways.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: meshgateways.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: MeshGateway - listKind: MeshGatewayList - plural: meshgateways - singular: meshgateway - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: MeshGateway is the Schema for the Mesh Gateway API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - gatewayClassName: - description: GatewayClassName is the name of the GatewayClass used - by the MeshGateway - type: string - listeners: - items: - properties: - name: - type: string - port: - format: int32 - maximum: 65535 - minimum: 0 - type: integer - protocol: - enum: - - TCP - type: string - type: object - minItems: 1 - type: array - workloads: - description: Selection of workloads to be configured as mesh gateways - properties: - filter: - type: string - names: - items: - type: string - type: array - prefixes: - items: - type: string - type: array - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml deleted file mode 100644 index 4a505adeb9..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml +++ /dev/null @@ -1,400 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: proxyconfigurations.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: ProxyConfiguration - listKind: ProxyConfigurationList - plural: proxyconfigurations - shortNames: - - proxy-configuration - singular: proxyconfiguration - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: ProxyConfiguration is the Schema for the TCP Routes API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: This is a Resource type. - properties: - bootstrapConfig: - description: bootstrap_config is the configuration that requires proxies - to be restarted to be applied. - properties: - dogstatsdUrl: - type: string - overrideJsonTpl: - type: string - prometheusBindAddr: - type: string - readyBindAddr: - type: string - staticClustersJson: - type: string - staticListenersJson: - type: string - statsBindAddr: - type: string - statsConfigJson: - type: string - statsFlushInterval: - type: string - statsSinksJson: - type: string - statsTags: - items: - type: string - type: array - statsdUrl: - type: string - telemetryCollectorBindSocketDir: - type: string - tracingConfigJson: - type: string - type: object - dynamicConfig: - description: dynamic_config is the configuration that could be changed - dynamically (i.e. without needing restart). - properties: - accessLogs: - description: AccessLogs configures the output and format of Envoy - access logs - properties: - disableListenerLogs: - description: DisableListenerLogs turns off just listener logs - for connections rejected by Envoy because they don't have - a matching listener filter. - type: boolean - enabled: - description: Enabled turns off all access logging - type: boolean - jsonFormat: - description: The presence of one format string or the other - implies the access log string encoding. Defining both is - invalid. - type: string - path: - description: Path is the output file to write logs - type: string - textFormat: - type: string - type: - description: 'Type selects the output for logs: "file", "stderr". - "stdout"' - enum: - - LOG_SINK_TYPE_DEFAULT - - LOG_SINK_TYPE_FILE - - LOG_SINK_TYPE_STDERR - - LOG_SINK_TYPE_STDOUT - format: int32 - type: string - type: object - exposeConfig: - properties: - exposePaths: - items: - properties: - listenerPort: - format: int32 - type: integer - localPathPort: - format: int32 - type: integer - path: - type: string - protocol: - enum: - - EXPOSE_PATH_PROTOCOL_HTTP - - EXPOSE_PATH_PROTOCOL_HTTP2 - format: int32 - type: string - type: object - type: array - type: object - inboundConnections: - description: inbound_connections configures inbound connections - to the proxy. - properties: - balanceInboundConnections: - enum: - - BALANCE_CONNECTIONS_DEFAULT - - BALANCE_CONNECTIONS_EXACT - format: int32 - type: string - maxInboundConnections: - format: int32 - type: integer - type: object - listenerTracingJson: - type: string - localClusterJson: - type: string - localConnection: - additionalProperties: - description: Referenced by ProxyConfiguration - properties: - connectTimeout: - description: "A Duration represents a signed, fixed-length - span of time represented as a count of seconds and fractions - of seconds at nanosecond resolution. It is independent - of any calendar and concepts like \"day\" or \"month\". - It is related to Timestamp in that the difference between - two Timestamp values is a Duration and it can be added - or subtracted from a Timestamp. Range is approximately - +-10,000 years. \n # Examples \n Example 1: Compute Duration - from two Timestamps in pseudo code. \n Timestamp start - = ...; Timestamp end = ...; Duration duration = ...; \n - duration.seconds = end.seconds - start.seconds; duration.nanos - = end.nanos - start.nanos; \n if (duration.seconds < 0 - && duration.nanos > 0) { duration.seconds += 1; duration.nanos - -= 1000000000; } else if (duration.seconds > 0 && duration.nanos - < 0) { duration.seconds -= 1; duration.nanos += 1000000000; - } \n Example 2: Compute Timestamp from Timestamp + Duration - in pseudo code. \n Timestamp start = ...; Duration duration - = ...; Timestamp end = ...; \n end.seconds = start.seconds - + duration.seconds; end.nanos = start.nanos + duration.nanos; - \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += - 1000000000; } else if (end.nanos >= 1000000000) { end.seconds - += 1; end.nanos -= 1000000000; } \n Example 3: Compute - Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, - minutes=10) duration = Duration() duration.FromTimedelta(td) - \n # JSON Mapping \n In JSON format, the Duration type - is encoded as a string rather than an object, where the - string ends in the suffix \"s\" (indicating seconds) and - is preceded by the number of seconds, with nanoseconds - expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as - \"3s\", while 3 seconds and 1 nanosecond should be expressed - in JSON format as \"3.000000001s\", and 3 seconds and - 1 microsecond should be expressed in JSON format as \"3.000001s\"." - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - requestTimeout: - description: "A Duration represents a signed, fixed-length - span of time represented as a count of seconds and fractions - of seconds at nanosecond resolution. It is independent - of any calendar and concepts like \"day\" or \"month\". - It is related to Timestamp in that the difference between - two Timestamp values is a Duration and it can be added - or subtracted from a Timestamp. Range is approximately - +-10,000 years. \n # Examples \n Example 1: Compute Duration - from two Timestamps in pseudo code. \n Timestamp start - = ...; Timestamp end = ...; Duration duration = ...; \n - duration.seconds = end.seconds - start.seconds; duration.nanos - = end.nanos - start.nanos; \n if (duration.seconds < 0 - && duration.nanos > 0) { duration.seconds += 1; duration.nanos - -= 1000000000; } else if (duration.seconds > 0 && duration.nanos - < 0) { duration.seconds -= 1; duration.nanos += 1000000000; - } \n Example 2: Compute Timestamp from Timestamp + Duration - in pseudo code. \n Timestamp start = ...; Duration duration - = ...; Timestamp end = ...; \n end.seconds = start.seconds - + duration.seconds; end.nanos = start.nanos + duration.nanos; - \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += - 1000000000; } else if (end.nanos >= 1000000000) { end.seconds - += 1; end.nanos -= 1000000000; } \n Example 3: Compute - Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, - minutes=10) duration = Duration() duration.FromTimedelta(td) - \n # JSON Mapping \n In JSON format, the Duration type - is encoded as a string rather than an object, where the - string ends in the suffix \"s\" (indicating seconds) and - is preceded by the number of seconds, with nanoseconds - expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as - \"3s\", while 3 seconds and 1 nanosecond should be expressed - in JSON format as \"3.000000001s\", and 3 seconds and - 1 microsecond should be expressed in JSON format as \"3.000001s\"." - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - description: local_connection is the configuration that should - be used to connect to the local application provided per-port. - The map keys should correspond to port names on the workload. - type: object - localWorkloadAddress: - description: "deprecated: local_workload_address, local_workload_port, - and local_workload_socket_path are deprecated and are only needed - for migration of existing resources. \n Deprecated: Marked as - deprecated in pbmesh/v2beta1/proxy_configuration.proto." - type: string - localWorkloadPort: - description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' - format: int32 - type: integer - localWorkloadSocketPath: - description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' - type: string - meshGatewayMode: - enum: - - MESH_GATEWAY_MODE_UNSPECIFIED - - MESH_GATEWAY_MODE_NONE - - MESH_GATEWAY_MODE_LOCAL - - MESH_GATEWAY_MODE_REMOTE - format: int32 - type: string - mode: - description: mode indicates the proxy's mode. This will default - to 'transparent'. - enum: - - PROXY_MODE_DEFAULT - - PROXY_MODE_TRANSPARENT - - PROXY_MODE_DIRECT - format: int32 - type: string - mutualTlsMode: - enum: - - MUTUAL_TLS_MODE_DEFAULT - - MUTUAL_TLS_MODE_STRICT - - MUTUAL_TLS_MODE_PERMISSIVE - format: int32 - type: string - publicListenerJson: - type: string - transparentProxy: - properties: - dialedDirectly: - description: dialed_directly indicates whether this proxy - should be dialed using original destination IP in the connection - rather than load balance between all endpoints. - type: boolean - outboundListenerPort: - description: outbound_listener_port is the port for the proxy's - outbound listener. This defaults to 15001. - format: int32 - type: integer - type: object - type: object - opaqueConfig: - description: "deprecated: prevent usage when using v2 APIs directly. - needed for backwards compatibility \n Deprecated: Marked as deprecated - in pbmesh/v2beta1/proxy_configuration.proto." - type: object - x-kubernetes-preserve-unknown-fields: true - workloads: - description: Selection of workloads this proxy configuration should - apply to. These can be prefixes or specific workload names. - properties: - filter: - type: string - names: - items: - type: string - type: array - prefixes: - items: - type: string - type: array - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml deleted file mode 100644 index 21a3a9c5ec..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml +++ /dev/null @@ -1,273 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: tcproutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: TCPRoute - listKind: TCPRouteList - plural: tcproutes - shortNames: - - tcp-route - singular: tcproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: TCPRoute is the Schema for the TCP Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute - \n This is a Resource type." - properties: - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - properties: - backendRefs: - description: BackendRefs defines the backend(s) where matching - requests should be sent. If unspecified or invalid (refers - to a non-existent resource or a Service with no endpoints), - the underlying implementation MUST actively reject connection - attempts to this backend. Connection rejections must respect - weight; if an invalid backend is requested to have 80% of - connections, then 80% of connections must be rejected instead. - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/multicluster.consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/multicluster.consul.hashicorp.com_exportedservices.yaml deleted file mode 100644 index 82eb9ca92d..0000000000 --- a/control-plane/config/crd/bases/multicluster.consul.hashicorp.com_exportedservices.yaml +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: exportedservices.multicluster.consul.hashicorp.com -spec: - group: multicluster.consul.hashicorp.com - names: - kind: ExportedServices - listKind: ExportedServicesList - plural: exportedservices - singular: exportedservices - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: ExportedServices is the Schema for the Exported Services API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - consumers: - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - services: - items: - type: string - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml deleted file mode 100644 index ff0b2fc2f6..0000000000 --- a/control-plane/config/crd/external/gatewayclasses.gateway.networking.k8s.io.yaml +++ /dev/null @@ -1,320 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: gatewayclasses.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: GatewayClass - listKind: GatewayClassList - plural: gatewayclasses - shortNames: - - gc - singular: gatewayclass - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .spec.controllerName - name: Controller - type: string - - jsonPath: .status.conditions[?(@.type=="Accepted")].status - name: Accepted - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - deprecated: true - deprecationWarning: The v1alpha2 version of GatewayClass has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.controllerName - name: Controller - type: string - - jsonPath: .status.conditions[?(@.type=="Accepted")].status - name: Accepted - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - name: v1beta1 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml deleted file mode 100644 index fa64481667..0000000000 --- a/control-plane/config/crd/external/gateways.gateway.networking.k8s.io.yaml +++ /dev/null @@ -1,874 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: gateways.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: Gateway - listKind: GatewayList - plural: gateways - shortNames: - - gtw - singular: gateway - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Programmed")].status - name: Programmed - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: The v1alpha2 version of Gateway has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. - name: v1alpha2 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n The .listener.hostname field is used to route traffic that has already arrived at the Gateway to the correct in-cluster destination. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended" - items: - description: GatewayAddress describes an address that can be bound to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - name: - description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - port: - description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" - items: - description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Secret - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Accepted - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Programmed - description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this listener. - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status corresponds to. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." - items: - description: RouteGroupKind indicates the group and kind of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds - type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Programmed")].status - name: Programmed - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n The .listener.hostname field is used to route traffic that has already arrived at the Gateway to the correct in-cluster destination. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended" - items: - description: GatewayAddress describes an address that can be bound to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. \n An implementation MAY group Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines that the Listeners in the group are \"compatible\". An implementation MAY also group together and collapse compatible Listeners belonging to different Gateways. \n For example, an implementation might consider Listeners to be compatible with each other if all of the following conditions are met: \n 1. Either each Listener within the group specifies the \"HTTP\" Protocol or each Listener within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. \n 2. Each Listener within the group specifies a Hostname that is unique within the group. \n 3. As a special case, one Listener within a group may omit Hostname, in which case this Listener matches when no other Listener matches. \n If the implementation does collapse compatible Listeners, the hostname provided in the incoming client request MUST be matched to a Listener to find the correct set of Routes. The incoming hostname MUST be matched using the Hostname field for each Listener in order of most to least specific. That is, exact matches must be processed before wildcard matches. \n If this field specifies multiple Listeners that have the same Port value but are not compatible, the implementation must raise a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected for this Gateway. Possible values are: * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - name: - description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - port: - description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" - items: - description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Secret - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Accepted - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been bound to the Gateway. These addresses may differ from the addresses in the Spec, e.g. if the Gateway automatically assigns an address from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - maxLength: 253 - minLength: 1 - pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - value: - description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Programmed - description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this listener. - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status corresponds to. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." - items: - description: RouteGroupKind indicates the group and kind of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds - type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml deleted file mode 100644 index 8d190ea7b6..0000000000 --- a/control-plane/config/crd/external/grpcroutes.gateway.networking.k8s.io.yaml +++ /dev/null @@ -1,758 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: grpcroutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: GRPCRoute - listKind: GRPCRouteList - plural: grpcroutes - singular: grpcroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge. \n Support: Extended" - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GRPCRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - method: - type: Exact - description: Rules are a list of GRPC matchers, filters and actions. - items: - description: GRPCRouteRule defines the semantics for matching an gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. - properties: - filters: - description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" - items: - description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " - enum: - - ResponseHeaderModifier - - RequestHeaderModifier - - RequestMirror - - ExtensionRef - type: string - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. Support: Core" - items: - description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " - enum: - - ResponseHeaderModifier - - RequestHeaderModifier - - RequestMirror - - ExtensionRef - type: string - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - method: - type: Exact - description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." - items: - description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" - properties: - headers: - description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. - items: - description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. - properties: - name: - description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: Type specifies how to match against the value of the header. - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of the gRPC Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - default: - type: Exact - description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. - properties: - method: - description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Method must be a valid Protobuf Method (https://protobuf.com/docs/language-spec#methods)." - maxLength: 1024 - pattern: ^[A-Za-z_][A-Za-z_0-9]*$ - type: string - service: - description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Service must be a valid Protobuf Type Name (https://protobuf.com/docs/language-spec#type-references)." - maxLength: 1024 - pattern: ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$ - type: string - type: - default: Exact - description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - RegularExpression - type: string - type: object - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of GRPCRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml deleted file mode 100644 index 90c151a787..0000000000 --- a/control-plane/config/crd/external/httproutes.gateway.networking.k8s.io.yaml +++ /dev/null @@ -1,1906 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: httproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: HTTPRoute - listKind: HTTPRouteList - plural: httproutes - singular: httproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: The v1alpha2 version of HTTPRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. - name: v1alpha2 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. - properties: - filters: - description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." - items: - description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. - items: - description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH - type: string - path: - default: - type: PathPrefix - value: / - description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. - properties: - type: - default: PathPrefix - description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - PathPrefix - - RegularExpression - type: string - value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 - type: string - type: object - queryParams: - description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" - items: - description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. - properties: - name: - description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." - maxLength: 256 - minLength: 1 - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP query param to be matched. - maxLength: 1024 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. - properties: - filters: - description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." - items: - description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. - items: - description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH - type: string - path: - default: - type: PathPrefix - value: / - description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. - properties: - type: - default: PathPrefix - description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - PathPrefix - - RegularExpression - type: string - value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 - type: string - type: object - queryParams: - description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" - items: - description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. - properties: - name: - description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." - maxLength: 256 - minLength: 1 - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP query param to be matched. - maxLength: 1024 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/external/kustomization.yaml b/control-plane/config/crd/external/kustomization.yaml deleted file mode 100644 index a1a0e349ff..0000000000 --- a/control-plane/config/crd/external/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -# This file is Helm ignored. It is only used for the `make generate-external-crds` command. - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v0.6.2 diff --git a/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml deleted file mode 100644 index 5eee4889a4..0000000000 --- a/control-plane/config/crd/external/referencegrants.gateway.networking.k8s.io.yaml +++ /dev/null @@ -1,200 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: referencegrants.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: ReferenceGrant - listKind: ReferenceGrantList - plural: referencegrants - shortNames: - - refgrant - singular: referencegrant - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed. \n Support: Core" - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of ReferenceGrant. - properties: - from: - description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" - items: - description: ReferenceGrantFrom describes trusted namespaces and kinds. - properties: - group: - description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - namespace: - description: "Namespace is the namespace of the referent. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - namespace - type: object - maxItems: 16 - minItems: 1 - type: array - to: - description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" - items: - description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. - properties: - group: - description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - from - - to - type: object - type: object - served: true - storage: true - subresources: {} - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed. \n Support: Core" - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of ReferenceGrant. - properties: - from: - description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" - items: - description: ReferenceGrantFrom describes trusted namespaces and kinds. - properties: - group: - description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - namespace: - description: "Namespace is the namespace of the referent. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - namespace - type: object - maxItems: 16 - minItems: 1 - type: array - to: - description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" - items: - description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. - properties: - group: - description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - from - - to - type: object - type: object - served: true - storage: false - subresources: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml deleted file mode 100644 index a136b28f41..0000000000 --- a/control-plane/config/crd/external/tcproutes.gateway.networking.k8s.io.yaml +++ /dev/null @@ -1,273 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: tcproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: TCPRoute - listKind: TCPRouteList - plural: tcproutes - singular: tcproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of TCPRoute. - properties: - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - description: TCPRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of TCPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml deleted file mode 100644 index cc3cf65d6c..0000000000 --- a/control-plane/config/crd/external/tlsroutes.gateway.networking.k8s.io.yaml +++ /dev/null @@ -1,283 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: tlsroutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: TLSRoute - listKind: TLSRouteList - plural: tlsroutes - singular: tlsroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "The TLSRoute resource is similar to TCPRoute, but can be configured to match against TLS-specific metadata. This allows more flexibility in matching streams for a given TLS listener. \n If you need to forward traffic to a single target for a TLS listener, you could choose to use a TCPRoute with a TLS listener." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of TLSRoute. - properties: - hostnames: - description: "Hostnames defines a set of SNI names that should match against the SNI attribute of TLS ClientHello message in TLS handshake. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and TLSRoute, there must be at least one intersecting hostname for the TLSRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n If both the Listener and TLSRoute have specified hostnames, any TLSRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the TLSRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and TLSRoute have specified hostnames, and none match with the criteria above, then the TLSRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of TLS matchers and actions. - items: - description: TLSRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this backend, by rejecting the connection or returning a 500 status code. Request rejections must respect weight; if an invalid backend is requested to have 80% of requests, then 80% of requests must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of TLSRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml b/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml deleted file mode 100644 index 204f8e4824..0000000000 --- a/control-plane/config/crd/external/udproutes.gateway.networking.k8s.io.yaml +++ /dev/null @@ -1,273 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: udproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: UDPRoute - listKind: UDPRouteList - plural: udproutes - singular: udproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: UDPRoute provides a way to route UDP traffic. When combined with a Gateway listener, it can be used to forward traffic on the port specified by the listener to a set of backends specified by the UDPRoute. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of UDPRoute. - properties: - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of UDP matchers and actions. - items: - description: UDPRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Packet drops must respect weight; if an invalid backend is requested to have 80% of the packets, then 80% of packets must be dropped instead. \n Support: Core for Kubernetes Service Support: Implementation-specific for any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of UDPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/control-plane/config/crd/kustomizeconfig.yaml b/control-plane/config/crd/kustomizeconfig.yaml deleted file mode 100644 index 5d1332c4bf..0000000000 --- a/control-plane/config/crd/kustomizeconfig.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -# This file is for teaching kustomize how to substitute name and namespace reference in CRD -nameReference: -- kind: Service - version: v1 - fieldSpecs: - - kind: CustomResourceDefinition - group: apiextensions.k8s.io - path: spec/conversion/webhookClientConfig/service/name - -namespace: -- kind: CustomResourceDefinition - group: apiextensions.k8s.io - path: spec/conversion/webhookClientConfig/service/namespace - create: false - -varReference: -- path: metadata/annotations diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index c2ad591c4f..8daf050ec0 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -1,10 +1,8 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: + creationTimestamp: null name: manager-role rules: - apiGroups: @@ -25,46 +23,6 @@ rules: - secrets/status verbs: - get -- apiGroups: - - auth.consul.hashicorp.com - resources: - - trafficpermissions - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - auth.consul.hashicorp.com - resources: - - trafficpermissions/status - verbs: - - get - - patch - - update -- apiGroups: - - consul.hashicorp.com - resources: - - controlplanerequestlimits - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - consul.hashicorp.com - resources: - - controlplanerequestlimits/status - verbs: - - get - - patch - - update - apiGroups: - consul.hashicorp.com resources: @@ -105,26 +63,6 @@ rules: - get - patch - update -- apiGroups: - - consul.hashicorp.com - resources: - - jwtproviders - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - consul.hashicorp.com - resources: - - jwtproviders/status - verbs: - - get - - patch - - update - apiGroups: - consul.hashicorp.com resources: @@ -205,26 +143,6 @@ rules: - get - patch - update -- apiGroups: - - consul.hashicorp.com - resources: - - samenessgroups - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - consul.hashicorp.com - resources: - - samenessgroups/status - verbs: - - get - - patch - - update - apiGroups: - consul.hashicorp.com resources: @@ -345,183 +263,3 @@ rules: - get - patch - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclass - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclass/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclassconfig - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - gatewayclassconfig/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - grpcroute - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - grpcroute/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - httproute - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - httproute/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - meshconfiguration - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - meshconfiguration/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - meshgateway - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - meshgateway/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - proxyconfiguration - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - proxyconfiguration/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - tcproute - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - tcproute/status - verbs: - - get - - patch - - update -- apiGroups: - - multicluster.consul.hashicorp.com - resources: - - exportedservices - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - multicluster.consul.hashicorp.com - resources: - - exportedservices/status - verbs: - - get - - patch - - update diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index a4b3aaadd0..c1ffb5e0fe 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -1,117 +1,10 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: + creationTimestamp: null name: mutating-webhook-configuration webhooks: -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-grpcroute - failurePolicy: Fail - name: mutate-grpcroute.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - grpcroute - sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-httproute - failurePolicy: Fail - name: mutate-httproute.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - httproute - sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-proxyconfiguration - failurePolicy: Fail - name: mutate-proxyconfiguration.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - proxyconfiguration - sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-tcproute - failurePolicy: Fail - name: mutate-tcproute.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - tcproute - sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v1alpha1-controlplanerequestlimits - failurePolicy: Fail - name: mutate-controlplanerequestlimits.consul.hashicorp.com - rules: - - apiGroups: - - consul.hashicorp.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - controlplanerequestlimits - sideEffects: None - admissionReviewVersions: - v1beta1 - v1 @@ -154,27 +47,6 @@ webhooks: resources: - ingressgateways sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v1alpha1-jwtprovider - failurePolicy: Fail - name: mutate-jwtprovider.consul.hashicorp.com - rules: - - apiGroups: - - consul.hashicorp.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - jwtproviders - sideEffects: None - admissionReviewVersions: - v1beta1 - v1 @@ -259,27 +131,6 @@ webhooks: resources: - proxydefaults sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v1alpha1-samenessgroups - failurePolicy: Fail - name: mutate-samenessgroup.consul.hashicorp.com - rules: - - apiGroups: - - consul.hashicorp.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - samenessgroups - sideEffects: None - admissionReviewVersions: - v1beta1 - v1 @@ -406,51 +257,3 @@ webhooks: resources: - terminatinggateways sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-trafficpermissions - failurePolicy: Fail - name: mutate-trafficpermissions.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - trafficpermissions - sideEffects: None ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: validating-webhook-configuration -webhooks: -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-v1alpha1-gatewaypolicy - failurePolicy: Fail - name: validate-gatewaypolicy.consul.hashicorp.com - rules: - - apiGroups: - - consul.hashicorp.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - gatewaypolicies - sideEffects: None diff --git a/control-plane/connect-inject/common/annotation_processor.go b/control-plane/connect-inject/common/annotation_processor.go deleted file mode 100644 index 778f630049..0000000000 --- a/control-plane/connect-inject/common/annotation_processor.go +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "fmt" - "strings" - - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - ConsulNodeAddress = "127.0.0.1" -) - -// ProcessPodDestinationsForMeshWebhook reads the list of destinations from the Pod annotation and converts them into a pbmesh.Destinations -// object. -func ProcessPodDestinationsForMeshWebhook(pod corev1.Pod) (*pbmesh.Destinations, error) { - return ProcessPodDestinations(pod, true, true) -} - -// ProcessPodDestinations reads the list of destinations from the Pod annotation and converts them into a pbmesh.Destinations -// object. -func ProcessPodDestinations(pod corev1.Pod, enablePartitions, enableNamespaces bool) (*pbmesh.Destinations, error) { - destinations := &pbmesh.Destinations{} - raw, ok := pod.Annotations[constants.AnnotationMeshDestinations] - if !ok || raw == "" { - return nil, nil - } - - destinations.Workloads = &pbcatalog.WorkloadSelector{ - Names: []string{pod.Name}, - } - - for _, raw := range strings.Split(raw, ",") { - var destination *pbmesh.Destination - - // Determine the type of processing required unlabeled or labeled - // [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter] - // or - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port] - - // Scan the string for the annotation keys. - // Even if the first key is missing, and the order is unexpected, we should let the processing - // provide us with errors - labeledFormat := false - keys := []string{"port", "svc", "ns", "ap", "peer", "dc"} - for _, v := range keys { - if strings.Contains(raw, fmt.Sprintf(".%s.", v)) || strings.Contains(raw, fmt.Sprintf(".%s:", v)) { - labeledFormat = true - break - } - } - - if labeledFormat { - var err error - destination, err = processPodLabeledDestination(pod, raw, enablePartitions, enableNamespaces) - if err != nil { - return nil, err - } - } else { - var err error - destination, err = processPodUnlabeledDestination(pod, raw, enablePartitions, enableNamespaces) - if err != nil { - return nil, err - } - } - - destinations.Destinations = append(destinations.Destinations, destination) - } - - return destinations, nil -} - -// processPodLabeledDestination processes a destination in the format: -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port]. -// peer/ap/dc are mutually exclusive. At minimum service-port-name and service-name are required. -// The ordering matters for labeled as well as unlabeled. The ordering of the labeled parameters should follow -// the order and requirements of the unlabeled parameters. -// TODO: enable dc and peer support when ready, currently return errors if set. -func processPodLabeledDestination(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Destination, error) { - parts := strings.SplitN(rawUpstream, ":", 3) - var port int32 - port, _ = PortValue(pod, strings.TrimSpace(parts[1])) - if port <= 0 { - return nil, fmt.Errorf("port value %d in destination is invalid: %s", port, rawUpstream) - } - - service := parts[0] - pieces := strings.Split(service, ".") - - var portName, datacenter, svcName, namespace, partition, peer string - if enablePartitions || enableNamespaces { - switch len(pieces) { - case 8: - end := strings.TrimSpace(pieces[7]) - switch end { - case "peer": - // TODO: uncomment and remove error when peers supported - //peer = strings.TrimSpace(pieces[6]) - return nil, fmt.Errorf("destination currently does not support peers: %s", rawUpstream) - case "ap": - partition = strings.TrimSpace(pieces[6]) - case "dc": - // TODO: uncomment and remove error when datacenters are supported - //datacenter = strings.TrimSpace(pieces[6]) - return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - fallthrough - case 6: - if strings.TrimSpace(pieces[5]) == "ns" { - namespace = strings.TrimSpace(pieces[4]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - fallthrough - case 4: - if strings.TrimSpace(pieces[3]) == "svc" { - svcName = strings.TrimSpace(pieces[2]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - if strings.TrimSpace(pieces[1]) == "port" { - portName = strings.TrimSpace(pieces[0]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - } else { - switch len(pieces) { - case 6: - end := strings.TrimSpace(pieces[5]) - switch end { - case "peer": - // TODO: uncomment and remove error when peers supported - //peer = strings.TrimSpace(pieces[4]) - return nil, fmt.Errorf("destination currently does not support peers: %s", rawUpstream) - case "dc": - // TODO: uncomment and remove error when datacenter supported - //datacenter = strings.TrimSpace(pieces[4]) - return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - // TODO: uncomment and remove error when datacenter and/or peers supported - //fallthrough - case 4: - if strings.TrimSpace(pieces[3]) == "svc" { - svcName = strings.TrimSpace(pieces[2]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - if strings.TrimSpace(pieces[1]) == "port" { - portName = strings.TrimSpace(pieces[0]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - } - - destination := pbmesh.Destination{ - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(partition), - Namespace: constants.GetNormalizedConsulNamespace(namespace), - PeerName: constants.GetNormalizedConsulPeer(peer), - }, - Name: svcName, - }, - DestinationPort: portName, - Datacenter: datacenter, - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(port), - Ip: ConsulNodeAddress, - }, - }, - } - - return &destination, nil -} - -// processPodUnlabeledDestination processes a destination in the format: -// [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. -// There is no unlabeled field for peering. -// TODO: enable dc and peer support when ready, currently return errors if set. -func processPodUnlabeledDestination(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Destination, error) { - var portName, datacenter, svcName, namespace, partition string - var port int32 - var destination pbmesh.Destination - - parts := strings.SplitN(rawUpstream, ":", 3) - - port, _ = PortValue(pod, strings.TrimSpace(parts[1])) - - // If Consul Namespaces or Admin Partitions are enabled, attempt to parse the - // destination for a namespace. - if enableNamespaces || enablePartitions { - pieces := strings.SplitN(parts[0], ".", 4) - switch len(pieces) { - case 4: - partition = strings.TrimSpace(pieces[3]) - fallthrough - case 3: - namespace = strings.TrimSpace(pieces[2]) - fallthrough - case 2: - svcName = strings.TrimSpace(pieces[1]) - portName = strings.TrimSpace(pieces[0]) - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - } else { - pieces := strings.SplitN(parts[0], ".", 2) - if len(pieces) < 2 { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - svcName = strings.TrimSpace(pieces[1]) - portName = strings.TrimSpace(pieces[0]) - } - - // parse the optional datacenter - if len(parts) > 2 { - // TODO: uncomment and remove error when datacenters supported - //datacenter = strings.TrimSpace(parts[2]) - return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) - } - - if port > 0 { - destination = pbmesh.Destination{ - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(partition), - Namespace: constants.GetNormalizedConsulNamespace(namespace), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: svcName, - }, - DestinationPort: portName, - Datacenter: datacenter, - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(port), - Ip: ConsulNodeAddress, - }, - }, - } - } - return &destination, nil -} diff --git a/control-plane/connect-inject/common/annotation_processor_test.go b/control-plane/connect-inject/common/annotation_processor_test.go deleted file mode 100644 index 223067e6c5..0000000000 --- a/control-plane/connect-inject/common/annotation_processor_test.go +++ /dev/null @@ -1,1014 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -func TestProcessUpstreams(t *testing.T) { - t.Parallel() - - const podName = "pod1" - - cases := []struct { - name string - pod func() *corev1.Pod - expected *pbmesh.Destinations - expErr string - configEntry func() api.ConfigEntry - consulUnavailable bool - consulNamespacesEnabled bool - consulPartitionsEnabled bool - }{ - { - name: "labeled annotated destination with svc only", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc and dc", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.dc1.dc:1234") - return pod1 - }, - expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc and peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.peer:1234") - return pod1 - }, - expErr: "destination currently does not support peers: myPort.port.upstream1.svc.peer1.peer:1234", - // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: "peer1", - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc, ns, and peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234") - return pod1 - }, - expErr: "destination currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", - // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: "peer1", - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc, ns, and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "part1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "labeled annotated destination with svc, ns, and dc", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234") - return pod1 - }, - expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "labeled multiple annotated destinations", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.port.upstream2.svc:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "ap1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream4", - }, - DestinationPort: "myPort4", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(4234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "labeled multiple annotated destinations with dcs and peers", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234, myPort2.port.upstream2.svc:2234, myPort3.port.upstream3.svc.ns1.ns:3234, myPort4.port.upstream4.svc.ns1.ns.peer1.peer:4234") - return pod1 - }, - expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: "peer1", - // }, - // Name: "upstream4", - // }, - // DestinationPort: "myPort4", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(4234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: invalid partition/dc/peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination with svc and peer, needs ns before peer if namespaces enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.peer:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.peer1.peer:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: invalid namespace", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: invalid number of pieces in the address", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: invalid peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.peer1.err:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: invalid number of pieces in the address without namespaces and partitions", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.err:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: both peer and partition provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: both peer and dc provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: both dc and partition provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: wrong ordering for port and svc with namespace partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: wrong ordering for port and svc with namespace partition disabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1.svc.myPort.port:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1.svc.myPort.port:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: incorrect key name namespace partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: incorrect key name namespace partition disabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.portage.upstream1.svc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.portage.upstream1.svc:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled missing port name", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1.svc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1.svc:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled missing port name namespace partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1.svc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1.svc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled and labeled multiple annotated destinations", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.upstream2:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "ap1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream4", - }, - DestinationPort: "myPort4", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(4234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled single destination", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single destination with namespace", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream.foo:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: "foo", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single destination with namespace and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream.foo.bar:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "bar", - Namespace: "foo", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled multiple destinations", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2:2234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled multiple destinations with consul namespaces, partitions and datacenters", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo.baz:3234:dc2") - return pod1 - }, - configEntry: func() api.ConfigEntry { - ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") - pd := ce.(*api.ProxyConfigEntry) - pd.MeshGateway.Mode = "remote" - return pd - }, - expErr: "destination currently does not support datacenters: myPort3.upstream3.foo.baz:3234:dc2", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "bar", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: "baz", - // Namespace: "foo", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "dc2", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled multiple destinations with consul namespaces and datacenters", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo:3234:dc2") - return pod1 - }, - configEntry: func() api.ConfigEntry { - ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") - pd := ce.(*api.ProxyConfigEntry) - pd.MeshGateway.Mode = "remote" - return pd - }, - expErr: "destination currently does not support datacenters: myPort3.upstream3.foo:3234:dc2", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "bar", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "foo", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "dc2", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - }, - { - name: "error unlabeled missing port name with namespace and partition disabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error unlabeled missing port name with namespace and partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - destinations, err := ProcessPodDestinations(*tt.pod(), tt.consulNamespacesEnabled, tt.consulPartitionsEnabled) - if tt.expErr != "" { - require.EqualError(t, err, tt.expErr) - } else { - require.NoError(t, err) - require.Equal(t, tt.expected, destinations) - - if diff := cmp.Diff(tt.expected, destinations, protocmp.Transform()); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - } - }) - } -} - -// createPod creates a multi-port pod as a base for tests. -func createPod(name string, annotation string) *corev1.Pod { - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } - pod.Annotations = map[string]string{ - constants.AnnotationMeshDestinations: annotation, - } - return pod -} diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 569b4d96e6..0ebf829666 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( @@ -8,17 +5,8 @@ import ( "strconv" "strings" - mapset "github.com/deckarep/golang-set" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + corev1 "k8s.io/api/core/v1" ) // DetermineAndValidatePort behaves as follows: @@ -73,20 +61,6 @@ func PortValue(pod corev1.Pod, value string) (int32, error) { return int32(raw), err } -// WorkloadPortName returns the container port's name if it has one, and if not, constructs a name from the port number -// and adds a constant prefix. The port name must be 1-15 characters and must have at least 1 alpha character. -func WorkloadPortName(port *corev1.ContainerPort) string { - name := port.Name - var isNum bool - if _, err := strconv.Atoi(name); err == nil { - isNum = true - } - if name == "" || isNum { - name = constants.UnnamedWorkloadPortNamePrefix + strconv.Itoa(int(port.ContainerPort)) - } - return name -} - // TransparentProxyEnabled returns true if transparent proxy should be enabled for this pod. // It returns an error when the annotation value cannot be parsed by strconv.ParseBool or if we are unable // to read the pod's namespace label when it exists. @@ -113,121 +87,6 @@ func ShouldOverwriteProbes(pod corev1.Pod, globalOverwrite bool) (bool, error) { return globalOverwrite, nil } -// ShouldIgnore ignores namespaces where we don't mesh-inject. -func ShouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { - // Ignores system namespaces. - if namespace == metav1.NamespaceSystem || namespace == metav1.NamespacePublic || namespace == "local-path-storage" { - return true - } - - // Ignores deny list. - if denySet.Contains(namespace) { - return true - } - - // Ignores if not in allow list or allow list is not *. - if !allowSet.Contains("*") && !allowSet.Contains(namespace) { - return true - } - - return false -} - func ConsulNodeNameFromK8sNode(nodeName string) string { return fmt.Sprintf("%s-virtual", nodeName) } - -// ******************** -// V2 Exclusive Common Code -// ******************** - -// ToProtoAny is a convenience function for converting proto.Message values to anypb.Any without error handling. -// This should _only_ be used in cases where a nil or valid proto.Message value is _guaranteed_, else it will panic. -// If the type of m is *anypb.Any, that value will be returned unmodified. -func ToProtoAny(m proto.Message) *anypb.Any { - switch v := m.(type) { - case nil: - return nil - case *anypb.Any: - return v - } - a, err := anypb.New(m) - if err != nil { - panic(fmt.Errorf("unexpected error: failed to convert proto message to anypb.Any: %w", err)) - } - return a -} - -// GetPortProtocol matches the Kubernetes EndpointPort.AppProtocol or ServicePort.AppProtocol (*string) to a supported -// Consul catalog port protocol. If nil or unrecognized, the default of `PROTOCOL_UNSPECIFIED` is returned. -func GetPortProtocol(appProtocol *string) pbcatalog.Protocol { - if appProtocol == nil { - return pbcatalog.Protocol_PROTOCOL_UNSPECIFIED - } - switch *appProtocol { - case "tcp": - return pbcatalog.Protocol_PROTOCOL_TCP - case "http": - return pbcatalog.Protocol_PROTOCOL_HTTP - case "http2": - return pbcatalog.Protocol_PROTOCOL_HTTP2 - case "grpc": - return pbcatalog.Protocol_PROTOCOL_GRPC - } - // If unrecognized or empty string, return default - return pbcatalog.Protocol_PROTOCOL_UNSPECIFIED -} - -// PortValueFromIntOrString returns the integer port value from the port that can be -// a named port, an integer string (e.g. "80"), or an integer. If the port is a named port, -// this function will attempt to find the value from the containers of the pod. -func PortValueFromIntOrString(pod corev1.Pod, port intstr.IntOrString) (uint32, error) { - if port.Type == intstr.Int { - return uint32(port.IntValue()), nil - } - - // Otherwise, find named port or try to parse the string as an int. - portVal, err := PortValue(pod, port.StrVal) - if err != nil { - return 0, err - } - return uint32(portVal), nil -} - -// HasBeenMeshInjected checks the value of the status annotation and returns true if the Pod has been injected. -// Does not apply to V1 pods, which use a different key (`constants.KeyInjectStatus`). -func HasBeenMeshInjected(pod corev1.Pod) bool { - if pod.Annotations == nil { - return false - } - if anno, ok := pod.Annotations[constants.KeyMeshInjectStatus]; ok && anno == constants.Injected { - return true - } - return false -} - -func IsGateway(pod corev1.Pod) bool { - if pod.Annotations == nil { - return false - } - if anno, ok := pod.Annotations[constants.AnnotationGatewayKind]; ok && anno != "" { - return true - } - return false -} - -// ConsulNamespaceIsNotFound checks the gRPC error code and message to determine -// if a namespace does not exist. If the namespace exists this function returns false, true otherwise. -func ConsulNamespaceIsNotFound(err error) bool { - if err == nil { - return false - } - s, ok := status.FromError(err) - if !ok { - return false - } - if codes.InvalidArgument == s.Code() && strings.Contains(s.Message(), "namespace not found") { - return true - } - return false -} diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index e3d6ac7227..155f6b892d 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -1,30 +1,13 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( - "context" - "fmt" "testing" - mapset "github.com/deckarep/golang-set" - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func TestCommonDetermineAndValidatePort(t *testing.T) { @@ -167,46 +150,6 @@ func TestCommonDetermineAndValidatePort(t *testing.T) { } } -func TestWorkloadPortName(t *testing.T) { - cases := []struct { - Name string - Port *corev1.ContainerPort - Expected string - }{ - { - Name: "named port", - Port: &corev1.ContainerPort{ - Name: "http", - ContainerPort: 8080, - }, - Expected: "http", - }, - { - Name: "unnamed port", - Port: &corev1.ContainerPort{ - Name: "", - ContainerPort: 8080, - }, - Expected: "cslport-8080", - }, - { - Name: "number port name", - Port: &corev1.ContainerPort{ - Name: "8080", - ContainerPort: 8080, - }, - Expected: "cslport-8080", - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - name := WorkloadPortName(tt.Port) - require.Equal(t, tt.Expected, name) - }) - } -} - func TestPortValue(t *testing.T) { cases := []struct { Name string @@ -313,303 +256,3 @@ func minimal() *corev1.Pod { }, } } - -func TestShouldIgnore(t *testing.T) { - t.Parallel() - cases := []struct { - name string - namespace string - denySet mapset.Set - allowSet mapset.Set - expected bool - }{ - { - name: "system namespace", - namespace: "kube-system", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "other system namespace", - namespace: "local-path-storage", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "any namespace allowed", - namespace: "foo", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: false, - }, - { - name: "in deny list", - namespace: "foo", - denySet: mapset.NewSetWith("foo"), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "not in allow list", - namespace: "foo", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("bar"), - expected: true, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := ShouldIgnore(tt.namespace, tt.denySet, tt.allowSet) - require.Equal(t, tt.expected, actual) - }) - } -} - -func TestToProtoAny(t *testing.T) { - t.Parallel() - - t.Run("nil gets nil", func(t *testing.T) { - require.Nil(t, ToProtoAny(nil)) - }) - - t.Run("anypb.Any gets same value", func(t *testing.T) { - testMsg := &pbresource.Resource{Id: &pbresource.ID{Name: "foo"}} - testAny, err := anypb.New(testMsg) - require.NoError(t, err) - - require.Equal(t, testAny, ToProtoAny(testAny)) - }) - - t.Run("valid proto is successfully serialized", func(t *testing.T) { - testMsg := &pbresource.Resource{Id: &pbresource.ID{Name: "foo"}} - testAny, err := anypb.New(testMsg) - require.NoError(t, err) - - if diff := cmp.Diff(testAny, ToProtoAny(testMsg), protocmp.Transform()); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - }) -} - -func TestGetPortProtocol(t *testing.T) { - t.Parallel() - toStringPtr := func(s string) *string { - return &s - } - cases := []struct { - name string - input *string - expected pbcatalog.Protocol - }{ - { - name: "nil gets UNSPECIFIED", - input: nil, - expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - { - name: "tcp gets TCP", - input: toStringPtr("tcp"), - expected: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - name: "http gets HTTP", - input: toStringPtr("http"), - expected: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - name: "http2 gets HTTP2", - input: toStringPtr("http2"), - expected: pbcatalog.Protocol_PROTOCOL_HTTP2, - }, - { - name: "grpc gets GRPC", - input: toStringPtr("grpc"), - expected: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - name: "case sensitive", - input: toStringPtr("gRPC"), - expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - { - name: "unknown gets UNSPECIFIED", - input: toStringPtr("foo"), - expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := GetPortProtocol(tt.input) - require.Equal(t, tt.expected, actual) - }) - } -} - -func TestHasBeenMeshInjected(t *testing.T) { - t.Parallel() - cases := []struct { - name string - pod corev1.Pod - expected bool - }{ - { - name: "Pod with injected annotation", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.KeyMeshInjectStatus: constants.Injected, - }, - }, - }, - expected: true, - }, - { - name: "Pod without injected annotation", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{}, - Annotations: map[string]string{ - "consul.hashicorp.com/foo": "bar", - }, - }, - }, - expected: false, - }, - { - name: "Pod with injected annotation but wrong value", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.KeyMeshInjectStatus: "hiya", - }, - }, - }, - expected: false, - }, - { - name: "Pod with nil annotations", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{}, - }, - }, - expected: false, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := HasBeenMeshInjected(tt.pod) - require.Equal(t, tt.expected, actual) - }) - } -} - -func Test_ConsulNamespaceIsNotFound(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - input error - expectMissingNamespace bool - }{ - { - name: "nil error", - expectMissingNamespace: false, - }, - { - name: "random error", - input: fmt.Errorf("namespace resource not found"), - expectMissingNamespace: false, - }, - { - name: "grpc code is not InvalidArgument", - input: status.Error(codes.NotFound, "namespace resource not found"), - expectMissingNamespace: false, - }, - { - name: "grpc code is InvalidArgument, but the message is not for namespaces", - input: status.Error(codes.InvalidArgument, "blurg resource not found"), - expectMissingNamespace: false, - }, - { - name: "namespace is missing", - input: status.Error(codes.InvalidArgument, "namespace not found"), - expectMissingNamespace: true, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := ConsulNamespaceIsNotFound(tt.input) - require.Equal(t, tt.expectMissingNamespace, actual) - }) - } -} - -// Test_ConsulNamespaceIsNotFound_ErrorMsg is an integration test that verifies the error message -// associated with a missing namespace while creating a resource doesn't drift. -func Test_ConsulNamespaceIsNotFound_ErrorMsg(t *testing.T) { - t.Parallel() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - id := &pbresource.ID{ - Name: "foo", - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulPartition, - Namespace: "i-dont-exist-but-its-ok-we-will-meet-again-someday", - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: "banana", - Identity: "foo", - } - - data := ToProtoAny(workload) - - resource := &pbresource.Resource{ - Id: id, - Data: data, - } - - _, err := testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: resource}) - require.Error(t, err) - - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.InvalidArgument, s.Code()) - require.Contains(t, s.Message(), "namespace not found") - - require.True(t, ConsulNamespaceIsNotFound(err)) -} diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 5a3567fd6f..8767de33b3 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package constants const ( @@ -25,8 +22,7 @@ const ( // AnnotationGatewayKind is the key of the annotation that indicates pods // that represent Consul Connect Gateways. This should be set to a - // value that is either "mesh-gateway", "ingress-gateway", "terminating-gateway", - // or "api-gateway". + // value that is either "mesh", "ingress" or "terminating". AnnotationGatewayKind = "consul.hashicorp.com/gateway-kind" // AnnotationGatewayConsulServiceName is the key of the annotation whose value @@ -74,14 +70,9 @@ const ( // connections to. AnnotationPort = "consul.hashicorp.com/connect-service-port" - // AnnotationProxyConfigMap allows for default values to be set in the opaque config map - // during proxy registration. The value for this annotation is expected to be valid json. - // Other annotations / configuration may overwrite the values in the map. - AnnotationProxyConfigMap = "consul.hashicorp.com/proxy-config-map" - // AnnotationUpstreams is a list of upstreams to register with the // proxy in the format of `:,...`. The - // service name should map to a Consul service name and the local port + // service name should map to a Consul service namd and the local port // is the local port in the pod that the listener will bind to. It can // be a named port. AnnotationUpstreams = "consul.hashicorp.com/connect-service-upstreams" @@ -100,14 +91,6 @@ const ( // Enable this only if the application does not support health checks. AnnotationUseProxyHealthCheck = "consul.hashicorp.com/use-proxy-health-check" - // AnnotationSidecarProxyStartupFailureSeconds configures how long the k8s startup probe will wait for - // success before the proxy is considered to be unhealthy and the container is restarted. - AnnotationSidecarProxyStartupFailureSeconds = "consul.hashicorp.com/sidecar-proxy-startup-failure-seconds" - - // AnnotationSidecarProxyLivenessFailureSeconds configures how long the k8s liveness probe will wait for - // before the proxy is considered to be unhealthy and the container is restarted. - AnnotationSidecarProxyLivenessFailureSeconds = "consul.hashicorp.com/sidecar-proxy-liveness-failure-seconds" - // annotations for sidecar proxy resource limits. AnnotationSidecarProxyCPULimit = "consul.hashicorp.com/sidecar-proxy-cpu-limit" AnnotationSidecarProxyCPURequest = "consul.hashicorp.com/sidecar-proxy-cpu-request" @@ -195,12 +178,8 @@ const ( // to explicitly perform the peering operation again. AnnotationPeeringVersion = "consul.hashicorp.com/peering-version" - // LegacyAnnotationConsulK8sVersion is the current version of this binary. - // TODO: remove this annotation in a future release. - LegacyAnnotationConsulK8sVersion = "consul.hashicorp.com/connect-k8s-version" - // AnnotationConsulK8sVersion is the current version of this binary. - AnnotationConsulK8sVersion = "consul.hashicorp.com/consul-k8s-version" + AnnotationConsulK8sVersion = "consul.hashicorp.com/connect-k8s-version" // LabelServiceIgnore is a label that can be added to a service to prevent it from being // registered with Consul. @@ -210,11 +189,6 @@ const ( // by the peering controllers. LabelPeeringToken = "consul.hashicorp.com/peering-token" - // LabelTelemetryCollector is a label signaling the pod is associated with the deployment of a Consul Telemetry - // Collector. If this is set, during connect-inject, the endpoints-controller ensures the deployed Namespace exists in Consul and create it if it does not. - // This is only meant to be used by Deployment/consul-telemetry-collector. - LabelTelemetryCollector = "consul.hashicorp.com/telemetry-collector" - // Injected is used as the annotation value for keyInjectStatus and annotationInjected. Injected = "injected" @@ -222,50 +196,9 @@ const ( Enabled = "enabled" // ManagedByValue is the value for keyManagedBy. - //TODO(zalimeni) rename this to ManagedByLegacyEndpointsValue. ManagedByValue = "consul-k8s-endpoints-controller" ) -// ******************** -// V2 Exclusive Annotations & Labels -// ******************** - -const ( - // AnnotationMeshInject is the key of the annotation that controls whether - // V2 mesh injection is explicitly enabled or disabled for a pod using. - // be set to a truthy or falsy value, as parseable by strconv.ParseBool. - AnnotationMeshInject = "consul.hashicorp.com/mesh-inject" - - // KeyMeshInjectStatus is the key of the annotation that is added to - // a pod after an injection is done. - KeyMeshInjectStatus = "consul.hashicorp.com/mesh-inject-status" - - // ManagedByEndpointsValue is used in Consul metadata to identify the manager - // of resources. The 'v2' suffix is used to differentiate from the legacy - // endpoints controller of the same name. - ManagedByEndpointsValue = "consul-k8s-endpoints-controller-v2" - - // ManagedByPodValue is used in Consul metadata to identify the manager - // of resources. - ManagedByPodValue = "consul-k8s-pod-controller" - - // ManagedByServiceAccountValue is used in Consul metadata to identify the manager - // of resources. - ManagedByServiceAccountValue = "consul-k8s-service-account-controller" - - // AnnotationMeshDestinations is a list of destinations to register with the - // proxy. The service name should map to a Consul service name and the local - // port is the local port in the pod that the listener will bind to. It can - // be a named port. - AnnotationMeshDestinations = "consul.hashicorp.com/mesh-service-destinations" - - // AnnotationMeshInjectMountVolumes is the key of the annotation that controls whether - // the data volume that mesh inject uses to store data including the Consul ACL token - // is mounted to other containers in the pod. It is a comma-separated list of container names - // to mount the volume on. It will be mounted at the path `/consul/mesh-inject`. - AnnotationMeshInjectMountVolumes = "consul.hashicorp.com/mesh-inject-mount-volume" -) - // Annotations used by Prometheus. const ( AnnotationPrometheusScrape = "prometheus.io/scrape" diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index d4d109ade5..75f9eaaad6 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -1,25 +1,8 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package constants const ( - // LegacyConsulCAFile is the location of the Consul CA file inside the injected pod. - // This is used with the V1 API. - LegacyConsulCAFile = "/consul/connect-inject/consul-ca.pem" - // ConsulCAFile is the location of the Consul CA file inside the injected pod. - // This is used with the V2 API. - ConsulCAFile = "/consul/mesh-inject/consul-ca.pem" - - // DefaultConsulNS is the default Consul namespace name. - DefaultConsulNS = "default" - - // DefaultConsulPartition is the default Consul partition name. - DefaultConsulPartition = "default" - - // DefaultConsulPeer is the name used to refer to resources that are in the same cluster. - DefaultConsulPeer = "local" + ConsulCAFile = "/consul/connect-inject/consul-ca.pem" // ProxyDefaultInboundPort is the default inbound port for the proxy. ProxyDefaultInboundPort = 20000 @@ -27,29 +10,9 @@ const ( // ProxyDefaultHealthPort is the default HTTP health check port for the proxy. ProxyDefaultHealthPort = 21000 - // MetaGatewayKind is the meta key name for indicating which kind of gateway a Pod is for, if any. - // The value should be one of "mesh", "api", or "terminating". - MetaGatewayKind = "gateway-kind" - - // MetaKeyManagedBy is the meta key name for indicating which Kubernetes controller manages a Consul resource. - MetaKeyManagedBy = "managed-by" - // MetaKeyKubeNS is the meta key name for Kubernetes namespace used for the Consul services. MetaKeyKubeNS = "k8s-namespace" - // MetaKeyKubeName is the meta key name for Kubernetes object name used for a Consul object. - MetaKeyKubeName = "k8s-name" - - // MetaKeyDatacenter is the datacenter that this object was registered from. - MetaKeyDatacenter = "datacenter" - - // MetaKeyKubeServiceName is the meta key name for Kubernetes service name used for the Consul services. - MetaKeyKubeServiceName = "k8s-service-name" - - // MetaKeyKubeServiceAccountName is the meta key name for Kubernetes service account name used for the Consul - // v2 workload identity. - MetaKeyKubeServiceAccountName = "k8s-service-account-name" - // MetaKeyPodName is the meta key name for Kubernetes pod name used for the Consul services. MetaKeyPodName = "pod-name" @@ -61,55 +24,4 @@ const ( // DefaultGracefulShutdownPath is the default path that consul-dataplane uses for graceful shutdown. DefaultGracefulShutdownPath = "/graceful_shutdown" - - // DefaultWANPort is the default port that consul-dataplane uses for WAN. - DefaultWANPort = 8443 - - // ConsulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. - ConsulKubernetesCheckType = "kubernetes-readiness" - - // ConsulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. - ConsulKubernetesCheckName = "Kubernetes Readiness Check" - - KubernetesSuccessReasonMsg = "Kubernetes health checks passing" - - // MeshV2VolumePath is the name of the volume that contains the proxy ID. - MeshV2VolumePath = "/consul/mesh-inject" - - UseTLSEnvVar = "CONSUL_USE_TLS" - CACertFileEnvVar = "CONSUL_CACERT_FILE" - CACertPEMEnvVar = "CONSUL_CACERT_PEM" - TLSServerNameEnvVar = "CONSUL_TLS_SERVER_NAME" - - UnnamedWorkloadPortNamePrefix = "cslport-" ) - -// GetNormalizedConsulNamespace returns the default namespace if the passed namespace -// is empty, otherwise returns back the passed in namespace. -func GetNormalizedConsulNamespace(ns string) string { - if ns == "" { - ns = DefaultConsulNS - } - - return ns -} - -// GetNormalizedConsulPartition returns the default partition if the passed partition -// is empty, otherwise returns back the passed in partition. -func GetNormalizedConsulPartition(ap string) string { - if ap == "" { - ap = DefaultConsulPartition - } - - return ap -} - -// GetNormalizedConsulPeer returns the default peer if the passed peer -// is empty, otherwise returns back the passed in peer. -func GetNormalizedConsulPeer(peer string) string { - if peer == "" { - peer = DefaultConsulPeer - } - - return peer -} diff --git a/control-plane/connect-inject/constants/constants_test.go b/control-plane/connect-inject/constants/constants_test.go deleted file mode 100644 index 2637c3b7d3..0000000000 --- a/control-plane/connect-inject/constants/constants_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package constants - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGetNormalizedConsulNamespace(t *testing.T) { - tests := []struct { - name string - value string - expect string - }{ - { - name: "expect contant", - value: "", - expect: DefaultConsulNS, - }, - { - name: "expect passed in value", - value: "some-value", - expect: "some-value", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - actual := GetNormalizedConsulNamespace(tc.value) - require.Equal(t, actual, tc.expect) - }) - } -} - -func TestGetNormalizedConsulPartition(t *testing.T) { - tests := []struct { - name string - value string - expect string - }{ - { - name: "expect contant", - value: "", - expect: DefaultConsulPartition, - }, - { - name: "expect passed in value", - value: "some-value", - expect: "some-value", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - actual := GetNormalizedConsulPartition(tc.value) - require.Equal(t, actual, tc.expect) - }) - } -} - -func TestGetNormalizedConsulPeer(t *testing.T) { - tests := []struct { - name string - value string - expect string - }{ - { - name: "expect contant", - value: "", - expect: DefaultConsulPeer, - }, - { - name: "expect passed in value", - value: "some-value", - expect: "some-value", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - actual := GetNormalizedConsulPeer(tc.value) - require.Equal(t, actual, tc.expect) - }) - } -} diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go index 6d78654989..f54fb71d11 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go @@ -1,18 +1,14 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package endpoints import ( "fmt" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-version" corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" ) const minSupportedConsulDataplaneVersion = "v1.0.0-beta1" @@ -20,12 +16,7 @@ const minSupportedConsulDataplaneVersion = "v1.0.0-beta1" // isConsulDataplaneSupported returns true if the consul-k8s version on the pod supports // consul-dataplane architecture of Consul. func isConsulDataplaneSupported(pod corev1.Pod) bool { - anno, ok := pod.Annotations[constants.LegacyAnnotationConsulK8sVersion] - if !ok { - anno, ok = pod.Annotations[constants.AnnotationConsulK8sVersion] - } - - if ok { + if anno, ok := pod.Annotations[constants.AnnotationConsulK8sVersion]; ok { consulK8sVersion, err := version.NewVersion(anno) if err != nil { // Only consul-k8s v1.0.0+ (including pre-release versions) have the version annotation. So it would be diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go index 5aa7448ef3..7f4978c133 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go @@ -1,21 +1,17 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package endpoints import ( "testing" logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) func TestIsConsulDataplaneSupported(t *testing.T) { @@ -47,7 +43,7 @@ func TestIsConsulDataplaneSupported(t *testing.T) { }, } if version != "" { - pod.ObjectMeta.Annotations[constants.LegacyAnnotationConsulK8sVersion] = version + pod.ObjectMeta.Annotations[constants.AnnotationConsulK8sVersion] = version } require.Equal(t, c.expIsConsulDataplaneSupported, isConsulDataplaneSupported(pod)) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index b0b8bea054..0150a212c9 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -1,5 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 package endpoints import ( @@ -13,22 +11,22 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/parsetags" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-multierror" 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/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/parsetags" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( @@ -43,11 +41,10 @@ const ( meshGateway = "mesh-gateway" terminatingGateway = "terminating-gateway" ingressGateway = "ingress-gateway" - apiGateway = "api-gateway" - envoyPrometheusBindAddr = "envoy_prometheus_bind_addr" - envoyTelemetryCollectorBindSocketDir = "envoy_telemetry_collector_bind_socket_dir" - defaultNS = "default" + kubernetesSuccessReasonMsg = "Kubernetes health checks passing" + envoyPrometheusBindAddr = "envoy_prometheus_bind_addr" + defaultNS = "default" // clusterIPTaggedAddressName is the key for the tagged address to store the service's cluster IP and service port // in Consul. Note: This value should not be changed without a corresponding change in Consul. @@ -57,6 +54,12 @@ const ( // This address does not need to be routable as this node is ephemeral, and we're only providing it because // Consul's API currently requires node address to be provided when registering a node. consulNodeAddress = "127.0.0.1" + + // consulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. + consulKubernetesCheckType = "kubernetes-readiness" + + // consulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. + consulKubernetesCheckName = "Kubernetes Readiness Check" ) type Controller struct { @@ -114,10 +117,6 @@ type Controller struct { // to Consul client agents. EnableAutoEncrypt bool - // EnableTelemetryCollector controls whether the proxy service should be registered - // with config to enable telemetry forwarding. - EnableTelemetryCollector bool - MetricsConfig metrics.Config Log logr.Logger @@ -136,7 +135,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu var serviceEndpoints corev1.Endpoints // Ignore the request if the namespace of the endpoint is not allowed. - if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + if shouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { return ctrl.Result{}, nil } @@ -205,13 +204,6 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu continue } - if isTelemetryCollector(pod) { - if err = r.ensureNamespaceExists(apiClient, pod); err != nil { - r.Log.Error(err, "failed to ensure a namespace exists for Consul Telemetry Collector") - errs = multierror.Append(errs, err) - } - } - if hasBeenInjected(pod) { endpointPods.Add(address.TargetRef.Name) if isConsulDataplaneSupported(pod) { @@ -239,7 +231,6 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu continue } } - if isGateway(pod) { endpointPods.Add(address.TargetRef.Name) if err = r.registerGateway(apiClient, pod, serviceEndpoints, healthStatus, endpointAddressMap); err != nil { @@ -293,23 +284,15 @@ func (r *Controller) registerServicesAndHealthCheck(apiClient *api.Client, pod c // Register the service instance with Consul. r.Log.Info("registering service with Consul", "name", serviceRegistration.Service.Service, - "id", serviceRegistration.Service.ID) + "id", serviceRegistration.ID) _, err = apiClient.Catalog().Register(serviceRegistration, nil) if err != nil { r.Log.Error(err, "failed to register service", "name", serviceRegistration.Service.Service) return err } - // Add manual ip to the VIP table - r.Log.Info("adding manual ip to virtual ip table in Consul", "name", serviceRegistration.Service.Service, - "id", serviceRegistration.ID) - err = assignServiceVirtualIP(r.Context, apiClient, serviceRegistration.Service) - if err != nil { - r.Log.Error(err, "failed to add ip to virtual ip table", "name", serviceRegistration.Service.Service) - } - // Register the proxy service instance with Consul. - r.Log.Info("registering proxy service with Consul", "name", proxyServiceRegistration.Service.Service, "id", proxyServiceRegistration.Service.ID) + r.Log.Info("registering proxy service with Consul", "name", proxyServiceRegistration.Service.Service) _, err = apiClient.Catalog().Register(proxyServiceRegistration, nil) if err != nil { r.Log.Error(err, "failed to register proxy service", "name", proxyServiceRegistration.Service.Service) @@ -319,20 +302,6 @@ func (r *Controller) registerServicesAndHealthCheck(apiClient *api.Client, pod c return nil } -func parseLocality(node corev1.Node) *api.Locality { - region := node.Labels[corev1.LabelTopologyRegion] - zone := node.Labels[corev1.LabelTopologyZone] - - if region == "" { - return nil - } - - return &api.Locality{ - Region: region, - Zone: zone, - } -} - // registerGateway creates Consul registrations for the Connect Gateways and registers them with Consul. // It also upserts a Kubernetes health check for the service based on whether the endpoint address is ready. func (r *Controller) registerGateway(apiClient *api.Client, pod corev1.Pod, serviceEndpoints corev1.Endpoints, healthStatus string, endpointAddressMap map[string]bool) error { @@ -399,18 +368,6 @@ func proxyServiceID(pod corev1.Pod, serviceEndpoints corev1.Endpoints) string { return fmt.Sprintf("%s-%s", pod.Name, proxySvcName) } -func annotationProxyConfigMap(pod corev1.Pod) (map[string]any, error) { - parsed := make(map[string]any) - if config, ok := pod.Annotations[constants.AnnotationProxyConfigMap]; ok && config != "" { - err := json.Unmarshal([]byte(config), &parsed) - if err != nil { - // Always return an empty map on error - return make(map[string]any), fmt.Errorf("unable to parse `%v` annotation for pod `%v`: %w", constants.AnnotationProxyConfigMap, pod.Name, err) - } - } - return parsed, nil -} - // createServiceRegistrations creates the service and proxy service instance registrations with the information from the // Pod. func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints corev1.Endpoints, healthStatus string) (*api.CatalogRegistration, *api.CatalogRegistration, error) { @@ -432,11 +389,6 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints } } - var node corev1.Node - // Ignore errors because we don't want failures to block running services. - _ = r.Client.Get(context.Background(), types.NamespacedName{Name: pod.Spec.NodeName, Namespace: pod.Namespace}, &node) - locality := parseLocality(node) - // We only want that annotation to be present when explicitly overriding the consul svc name // Otherwise, the Consul service name should equal the Kubernetes Service name. // The service name in Consul defaults to the Endpoints object name, and is overridden by the pod @@ -465,7 +417,6 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints tags := consulTags(pod) consulNS := r.consulNamespace(pod.Namespace) - service := &api.AgentService{ ID: svcID, Service: svcName, @@ -474,7 +425,6 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Meta: meta, Namespace: consulNS, Tags: tags, - Locality: locality, } serviceRegistration := &api.CatalogRegistration{ Node: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), @@ -485,8 +435,8 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Service: service, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, svcID), - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: healthStatus, ServiceID: svcID, Output: getHealthCheckStatusReason(healthStatus, pod.Name, pod.Namespace), @@ -498,16 +448,10 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints proxySvcName := proxyServiceName(pod, serviceEndpoints) proxySvcID := proxyServiceID(pod, serviceEndpoints) - - // Set the default values from the annotation, if possible. - baseConfig, err := annotationProxyConfigMap(pod) - if err != nil { - r.Log.Error(err, "annotation unable to be applied") - } proxyConfig := &api.AgentServiceConnectProxyConfig{ DestinationServiceName: svcName, DestinationServiceID: svcID, - Config: baseConfig, + Config: make(map[string]interface{}), } // If metrics are enabled, the proxyConfig should set envoy_prometheus_bind_addr to a listener on 0.0.0.0 on @@ -528,10 +472,6 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints proxyConfig.Config[envoyPrometheusBindAddr] = prometheusScrapeListener } - if r.EnableTelemetryCollector && proxyConfig.Config != nil { - proxyConfig.Config[envoyTelemetryCollectorBindSocketDir] = "/consul/connect-inject" - } - if consulServicePort > 0 { proxyConfig.LocalServiceAddress = "127.0.0.1" proxyConfig.LocalServicePort = consulServicePort @@ -557,8 +497,6 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Namespace: consulNS, Proxy: proxyConfig, Tags: tags, - // Sidecar locality (not proxied service locality) is used for locality-aware routing. - Locality: locality, } // A user can enable/disable tproxy for an entire namespace. @@ -686,8 +624,8 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Service: proxyService, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, proxySvcID), - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: healthStatus, ServiceID: proxySvcID, Output: getHealthCheckStatusReason(healthStatus, pod.Name, pod.Namespace), @@ -711,19 +649,10 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints constants.MetaKeyPodUID: string(pod.UID), } - // Set the default values from the annotation, if possible. - baseConfig, err := annotationProxyConfigMap(pod) - if err != nil { - r.Log.Error(err, "annotation unable to be applied") - } - service := &api.AgentService{ ID: pod.Name, Address: pod.Status.PodIP, Meta: meta, - Proxy: &api.AgentServiceConnectProxyConfig{ - Config: baseConfig, - }, } gatewayServiceName, ok := pod.Annotations[constants.AnnotationGatewayConsulServiceName] @@ -796,25 +725,31 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints Port: wanPort, }, } - service.Proxy.Config["envoy_gateway_no_default_bind"] = true - service.Proxy.Config["envoy_gateway_bind_addresses"] = map[string]interface{}{ - "all-interfaces": map[string]interface{}{ - "address": "0.0.0.0", + service.Proxy = &api.AgentServiceConnectProxyConfig{ + Config: map[string]interface{}{ + "envoy_gateway_no_default_bind": true, + "envoy_gateway_bind_addresses": map[string]interface{}{ + "all-interfaces": map[string]interface{}{ + "address": "0.0.0.0", + }, + }, }, } - case apiGateway: - // Do nothing. This is only here so that API gateway pods have annotations - // consistent with other gateway types but don't return an error below. + default: - return nil, fmt.Errorf("%s must be one of %s, %s, %s, or %s", constants.AnnotationGatewayKind, meshGateway, terminatingGateway, ingressGateway, apiGateway) + return nil, fmt.Errorf("%s must be one of %s, %s, or %s", constants.AnnotationGatewayKind, meshGateway, terminatingGateway, ingressGateway) } if r.MetricsConfig.DefaultEnableMetrics && r.MetricsConfig.EnableGatewayMetrics { - service.Proxy.Config["envoy_prometheus_bind_addr"] = fmt.Sprintf("%s:20200", pod.Status.PodIP) - } - - if r.EnableTelemetryCollector && service.Proxy != nil && service.Proxy.Config != nil { - service.Proxy.Config[envoyTelemetryCollectorBindSocketDir] = "/consul/service" + if pod.Annotations[constants.AnnotationGatewayKind] == ingressGateway { + service.Proxy.Config["envoy_prometheus_bind_addr"] = fmt.Sprintf("%s:20200", pod.Status.PodIP) + } else { + service.Proxy = &api.AgentServiceConnectProxyConfig{ + Config: map[string]interface{}{ + "envoy_prometheus_bind_addr": fmt.Sprintf("%s:20200", pod.Status.PodIP), + }, + } + } } serviceRegistration := &api.CatalogRegistration{ @@ -826,8 +761,8 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints Service: service, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, pod.Name), - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: healthStatus, ServiceID: pod.Name, Namespace: consulNS, @@ -919,7 +854,7 @@ func consulHealthCheckID(k8sNS string, serviceID string) string { // as well as pod name and namespace and returns the reason message. func getHealthCheckStatusReason(healthCheckStatus, podName, podNamespace string) string { if healthCheckStatus == api.HealthPassing { - return constants.KubernetesSuccessReasonMsg + return kubernetesSuccessReasonMsg } return fmt.Sprintf("Pod \"%s/%s\" is not ready", podNamespace, podName) @@ -936,65 +871,59 @@ func getHealthCheckStatusReason(healthCheckStatus, podName, podNamespace string) // has addresses, it will only deregister instances not in the map. func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvcNamespace string, endpointsAddressesMap map[string]bool) error { // Get services matching metadata from Consul - serviceInstances, err := r.serviceInstances(apiClient, k8sSvcName, k8sSvcNamespace) + nodesWithSvcs, err := r.serviceInstancesForNodes(apiClient, k8sSvcName, k8sSvcNamespace) if err != nil { r.Log.Error(err, "failed to get service instances", "name", k8sSvcName) return err } var errs error - for _, svc := range serviceInstances { - // We need to get services matching "k8s-service-name" and "k8s-namespace" metadata. - // If we selectively deregister, only deregister if the address is not in the map. Otherwise, deregister - // every service instance. - var serviceDeregistered bool - if endpointsAddressesMap != nil { - if _, ok := endpointsAddressesMap[svc.ServiceAddress]; !ok { - // If the service address is not in the Endpoints addresses, deregister it. - r.Log.Info("deregistering service from consul", "svc", svc.ServiceID) + for _, nodeSvcs := range nodesWithSvcs { + for _, svc := range nodeSvcs.Services { + // We need to get services matching "k8s-service-name" and "k8s-namespace" metadata. + // If we selectively deregister, only deregister if the address is not in the map. Otherwise, deregister + // every service instance. + var serviceDeregistered bool + if endpointsAddressesMap != nil { + if _, ok := endpointsAddressesMap[svc.Address]; !ok { + // If the service address is not in the Endpoints addresses, deregister it. + r.Log.Info("deregistering service from consul", "svc", svc.ID) + _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{ + Node: nodeSvcs.Node.Node, + ServiceID: svc.ID, + Namespace: svc.Namespace, + }, nil) + if err != nil { + // Do not exit right away as there might be other services that need to be deregistered. + r.Log.Error(err, "failed to deregister service instance", "id", svc.ID) + errs = multierror.Append(errs, err) + } else { + serviceDeregistered = true + } + } + } else { + r.Log.Info("deregistering service from consul", "svc", svc.ID) _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{ - Node: svc.Node, - ServiceID: svc.ServiceID, + Node: nodeSvcs.Node.Node, + ServiceID: svc.ID, Namespace: svc.Namespace, }, nil) if err != nil { // Do not exit right away as there might be other services that need to be deregistered. - r.Log.Error(err, "failed to deregister service instance", "id", svc.ServiceID) + r.Log.Error(err, "failed to deregister service instance", "id", svc.ID) errs = multierror.Append(errs, err) } else { serviceDeregistered = true } } - } else { - r.Log.Info("deregistering service from consul", "svc", svc.ServiceID) - _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{ - Node: svc.Node, - ServiceID: svc.ServiceID, - Namespace: svc.Namespace, - }, nil) - if err != nil { - // Do not exit right away as there might be other services that need to be deregistered. - r.Log.Error(err, "failed to deregister service instance", "id", svc.ServiceID) - errs = multierror.Append(errs, err) - } else { - serviceDeregistered = true - } - } - - if r.AuthMethod != "" && serviceDeregistered { - r.Log.Info("reconciling ACL tokens for service", "svc", svc.ServiceName) - err := r.deleteACLTokensForServiceInstance(apiClient, svc, k8sSvcNamespace, svc.ServiceMeta[constants.MetaKeyPodName], svc.ServiceMeta[constants.MetaKeyPodUID]) - if err != nil { - r.Log.Error(err, "failed to reconcile ACL tokens for service", "svc", svc.ServiceName) - errs = multierror.Append(errs, err) - } - } - if serviceDeregistered { - err = r.deregisterNode(apiClient, svc.Node) - if err != nil { - r.Log.Error(err, "failed to deregister node", "svc", svc.ServiceName) - errs = multierror.Append(errs, err) + if r.AuthMethod != "" && serviceDeregistered { + r.Log.Info("reconciling ACL tokens for service", "svc", svc.Service) + err := r.deleteACLTokensForServiceInstance(apiClient, svc, k8sSvcNamespace, svc.Meta[constants.MetaKeyPodName], svc.Meta[constants.MetaKeyPodUID]) + if err != nil { + r.Log.Error(err, "failed to reconcile ACL tokens for service", "svc", svc.Service) + errs = multierror.Append(errs, err) + } } } } @@ -1003,41 +932,10 @@ func (r *Controller) deregisterService(apiClient *api.Client, k8sSvcName, k8sSvc } -// deregisterNode removes a node if it does not have any associated services attached to it. -// When using Consul Enterprise, serviceInstancesForK8SServiceNameAndNamespace will search across all namespaces -// (wildcard) to determine if there any other services associated with the Node. We also only search for nodes that have -// the correct kubernetes metadata (managed-by-endpoints-controller and synthetic-node). -func (r *Controller) deregisterNode(apiClient *api.Client, nodeName string) error { - var ( - serviceList *api.CatalogNodeServiceList - err error - ) - - filter := fmt.Sprintf(`Meta[%q] == %q and Meta[%q] == %q`, - "synthetic-node", "true", metaKeyManagedBy, constants.ManagedByValue) - if r.EnableConsulNamespaces { - serviceList, _, err = apiClient.Catalog().NodeServiceList(nodeName, &api.QueryOptions{Filter: filter, Namespace: namespaces.WildcardNamespace}) - } else { - serviceList, _, err = apiClient.Catalog().NodeServiceList(nodeName, &api.QueryOptions{Filter: filter}) - } - if err != nil { - return fmt.Errorf("failed to get a list of node services: %s", err) - } - - if len(serviceList.Services) == 0 { - r.Log.Info("deregistering node from consul", "node", nodeName, "services", serviceList.Services) - _, err := apiClient.Catalog().Deregister(&api.CatalogDeregistration{Node: nodeName}, nil) - if err != nil { - r.Log.Error(err, "failed to deregister node", "name", nodeName) - } - } - return nil -} - // deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. // It will only check for ACL tokens that have been created with the auth method this controller // has been configured with and will only delete tokens for the provided podName and podUID. -func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, svc *api.CatalogService, k8sNS, podName, podUID string) error { +func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, svc *api.AgentService, k8sNS, podName, podUID string) error { // Skip if podName is empty. if podName == "" { return nil @@ -1050,7 +948,7 @@ func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, sv // matches as well. tokens, _, err := apiClient.ACL().TokenListFiltered( api.ACLTokenFilterOptions{ - ServiceName: svc.ServiceName, + ServiceName: svc.Service, }, &api.QueryOptions{ Namespace: svc.Namespace, @@ -1065,7 +963,7 @@ func (r *Controller) deleteACLTokensForServiceInstance(apiClient *api.Client, sv // * have a single service identity whose service name is the same as 'svc.Service' if token.AuthMethod == r.AuthMethod && len(token.ServiceIdentities) == 1 && - token.ServiceIdentities[0].ServiceName == svc.ServiceName { + token.ServiceIdentities[0].ServiceName == svc.Service { tokenMeta, err := getTokenMetaFromDescription(token.Description) if err != nil { return fmt.Errorf("failed to parse token metadata: %s", err) @@ -1163,100 +1061,49 @@ func getTokenMetaFromDescription(description string) (map[string]string, error) return tokenMeta, nil } -func (r *Controller) serviceInstances(apiClient *api.Client, k8sServiceName, k8sServiceNamespace string) ([]*api.CatalogService, error) { - var ( - instances []*api.CatalogService - errs error - ) +func (r *Controller) serviceInstancesForNodes(apiClient *api.Client, k8sServiceName, k8sServiceNamespace string) ([]*api.CatalogNodeServiceList, error) { + var serviceList []*api.CatalogNodeServiceList - // Get the names of services that have the provided k8sServiceName and k8sServiceNamespace in their metadata. - // This is necessary so that we can then list the service instances for each Consul service name, which may - // not match the K8s service name. - services, err := r.servicesForK8SServiceNameAndNamespace(apiClient, k8sServiceName, k8sServiceNamespace) + // The nodelist may have changed between this point and when the event was raised + // For example, if a pod is evicted because a node has been deleted, there is no guarantee that that node will show up here + // query consul catalog for a list of nodes supporting this service + // quite a lot of results as synthetic nodes are never deregistered. + var nodes []*api.Node + filter := fmt.Sprintf(`Meta[%q] == %q `, "synthetic-node", "true") + nodes, _, err := apiClient.Catalog().Nodes(&api.QueryOptions{Filter: filter, Namespace: namespaces.WildcardNamespace}) if err != nil { - r.Log.Error(err, "failed to get catalog services", "name", k8sServiceName) return nil, err } - // Query consul catalog for a list of service instances matching the given service names. - filter := fmt.Sprintf(`NodeMeta[%q] == %q and ServiceMeta[%q] == %q and ServiceMeta[%q] == %q and ServiceMeta[%q] == %q`, - metaKeySyntheticNode, "true", - metaKeyKubeServiceName, k8sServiceName, - constants.MetaKeyKubeNS, k8sServiceNamespace, - metaKeyManagedBy, constants.ManagedByValue) - for _, service := range services { - var is []*api.CatalogService - // Always query the default NS. This ensures that we include mesh gateways, which are always registered to the default NS. - // It also ensures that during migrations that enable namespaces, we deregister old service instances in the default NS. - // An alternative approach to this dual query would be a service instance list using the wildcard NS, which would also - // include instances in Consul namespaces that are no longer in use (e.g. configuration change); this capability does - // not currently exist in Consul's catalog API (as of 1.17) and would need to first be added. - // - // This request uses the service index of the services table (does not perform a full table scan), then decorates each - // result with a single node fetched by ID index from the nodes table. - is, _, err = apiClient.Catalog().Service(service, "", &api.QueryOptions{Filter: filter}) + var errs error + for _, node := range nodes { + var nodeServices *api.CatalogNodeServiceList + nodeServices, err := r.serviceInstancesForK8SServiceNameAndNamespace(apiClient, k8sServiceName, k8sServiceNamespace, node.Node) if err != nil { errs = multierror.Append(errs, err) } else { - instances = append(instances, is...) - } - // If namespaces are enabled a non-default NS is targeted, also query by target Consul NS. - if r.EnableConsulNamespaces { - nonDefaultNamespace := namespaces.NonDefaultConsulNamespace(r.consulNamespace(k8sServiceNamespace)) - if nonDefaultNamespace != "" { - is, _, err = apiClient.Catalog().Service(service, "", &api.QueryOptions{Filter: filter, Namespace: nonDefaultNamespace}) - if err != nil { - errs = multierror.Append(errs, err) - } else { - instances = append(instances, is...) - } - } + serviceList = append(serviceList, nodeServices) } } - return instances, errs + return serviceList, errs } -// servicesForK8SServiceNameAndNamespace calls Consul's Services to get the list -// of services that have the provided k8sServiceName and k8sServiceNamespace in their metadata. -func (r *Controller) servicesForK8SServiceNameAndNamespace(apiClient *api.Client, k8sServiceName, k8sServiceNamespace string) ([]string, error) { +// serviceInstancesForK8SServiceNameAndNamespace calls Consul's ServicesWithFilter to get the list +// of services instances that have the provided k8sServiceName and k8sServiceNamespace in their metadata. +func (r *Controller) serviceInstancesForK8SServiceNameAndNamespace(apiClient *api.Client, k8sServiceName, k8sServiceNamespace, nodeName string) (*api.CatalogNodeServiceList, error) { var ( - services map[string][]string - err error + serviceList *api.CatalogNodeServiceList + err error ) - filter := fmt.Sprintf(`ServiceMeta[%q] == %q and ServiceMeta[%q] == %q and ServiceMeta[%q] == %q`, + filter := fmt.Sprintf(`Meta[%q] == %q and Meta[%q] == %q and Meta[%q] == %q`, metaKeyKubeServiceName, k8sServiceName, constants.MetaKeyKubeNS, k8sServiceNamespace, metaKeyManagedBy, constants.ManagedByValue) - // Always query the default NS. This ensures that we cover CE->Ent upgrades where services were previously - // in the default NS, as well as mesh gateways, which are always registered to the default NS. - // - // This request performs a NS-bound scan of the services table. If needed in the future, its performance - // could be improved by adding an index on ServiceMeta to Consul's state store. - services, _, err = apiClient.Catalog().Services(&api.QueryOptions{Filter: filter}) - if err != nil { - return nil, err - } - // If namespaces are enabled a non-default NS is targeted, also query by target Consul NS. if r.EnableConsulNamespaces { - nonDefaultNamespace := namespaces.NonDefaultConsulNamespace(r.consulNamespace(k8sServiceNamespace)) - if nonDefaultNamespace != "" { - ss, _, err := apiClient.Catalog().Services(&api.QueryOptions{Filter: filter, Namespace: nonDefaultNamespace}) - if err != nil { - return nil, err - } - // Add to existing map to deduplicate. - for s := range ss { - services[s] = nil // We don't use the tags, so just set to nil - } - } - } - - // Return just the service name keys (we don't need the tags) - // https://developer.hashicorp.com/consul/api-docs/catalog#list-services - var serviceNames []string - for s := range services { - serviceNames = append(serviceNames, s) + serviceList, _, err = apiClient.Catalog().NodeServiceList(nodeName, &api.QueryOptions{Filter: filter, Namespace: namespaces.WildcardNamespace}) + } else { + serviceList, _, err = apiClient.Catalog().NodeServiceList(nodeName, &api.QueryOptions{Filter: filter}) } - return serviceNames, err + return serviceList, err } // processPreparedQueryUpstream processes an upstream in the format: @@ -1281,9 +1128,8 @@ func processPreparedQueryUpstream(pod corev1.Pod, rawUpstream string) api.Upstre // processUnlabeledUpstream processes an upstream in the format: // [service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. -// There is no unlabeled field for peering. func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string) (api.Upstream, error) { - var datacenter, svcName, namespace, partition string + var datacenter, svcName, namespace, partition, peer string var port int32 var upstream api.Upstream @@ -1317,7 +1163,7 @@ func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string upstream = api.Upstream{ DestinationType: api.UpstreamDestTypeService, DestinationPartition: partition, - DestinationPeer: "", + DestinationPeer: peer, DestinationNamespace: namespace, DestinationName: svcName, Datacenter: datacenter, @@ -1407,6 +1253,26 @@ func (r *Controller) processLabeledUpstream(pod corev1.Pod, rawUpstream string) return upstream, nil } +// shouldIgnore ignores namespaces where we don't connect-inject. +func shouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { + // Ignores system namespaces. + if namespace == metav1.NamespaceSystem || namespace == metav1.NamespacePublic || namespace == "local-path-storage" { + return true + } + + // Ignores deny list. + if denySet.Contains(namespace) { + return true + } + + // Ignores if not in allow list or allow list is not *. + if !allowSet.Contains("*") && !allowSet.Contains(namespace) { + return true + } + + return false +} + // consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace // depending on Consul Namespaces being enabled and the value of namespace mirroring. func (r *Controller) consulNamespace(namespace string) string { @@ -1419,26 +1285,6 @@ func (r *Controller) appendNodeMeta(registration *api.CatalogRegistration) { } } -// assignServiceVirtualIPs manually assigns the ClusterIP to the virtual IP table so that transparent proxy routing works. -func assignServiceVirtualIP(ctx context.Context, apiClient *api.Client, svc *api.AgentService) error { - ip := svc.TaggedAddresses[clusterIPTaggedAddressName].Address - if ip == "" { - return nil - } - - _, _, err := apiClient.Internal().AssignServiceVirtualIP(ctx, svc.Service, []string{ip}, &api.WriteOptions{Namespace: svc.Namespace, Partition: svc.Partition}) - if err != nil { - // Maintain backwards compatibility with older versions of Consul that do not support the VIP improvements. Tproxy - // will not work 100% correctly but the mesh will still work - if strings.Contains(err.Error(), "404") { - return fmt.Errorf("failed to add ip for service %s to virtual ip table. Please upgrade Consul to version 1.16 or higher", svc.Service) - } else { - return err - } - } - return nil -} - // hasBeenInjected checks the value of the status annotation and returns true if the Pod has been injected. func hasBeenInjected(pod corev1.Pod) bool { if anno, ok := pod.Annotations[constants.KeyInjectStatus]; ok && anno == constants.Injected { @@ -1447,35 +1293,12 @@ func hasBeenInjected(pod corev1.Pod) bool { return false } -// isGateway checks the value of the gateway annotation and returns true if the Pod -// represents a Gateway kind that should be acted upon by the endpoints controller. +// isGateway checks the value of the gateway annotation and returns true if the Pod represents a Gateway. func isGateway(pod corev1.Pod) bool { anno, ok := pod.Annotations[constants.AnnotationGatewayKind] - return ok && anno != "" && anno != apiGateway -} - -// isTelemetryCollector checks whether a pod is part of a deployment for a Consul Telemetry Collector. If so, -// and this is the first pod deployed to a Namespace, we need to create the Namespace in Consul. Otherwise the -// deployment may fail out during service registration because it is deployed to a Namespace that does not exist. -func isTelemetryCollector(pod corev1.Pod) bool { - anno, ok := pod.Annotations[constants.LabelTelemetryCollector] return ok && anno != "" } -// ensureNamespaceExists creates a Consul namespace for a pod in the event it does not exist. -// At the time of writing, we use this for the Consul Telemetry Collector which may be the first -// pod deployed to a namespace. If it is, it's connect-inject will fail for lack of a namespace. -func (r *Controller) ensureNamespaceExists(apiClient *api.Client, pod corev1.Pod) error { - if r.EnableConsulNamespaces { - consulNS := r.consulNamespace(pod.Namespace) - if _, err := namespaces.EnsureExists(apiClient, consulNS, r.CrossNSACLPolicy); err != nil { - r.Log.Error(err, "failed to ensure Consul namespace exists", "ns", pod.Namespace, "consul ns", consulNS) - return err - } - } - return nil -} - // mapAddresses combines all addresses to a mapping of address to its health status. func mapAddresses(addresses corev1.EndpointSubset) map[corev1.EndpointAddress]string { m := make(map[corev1.EndpointAddress]string) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index c389395973..4f40eb9a26 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise package endpoints @@ -11,9 +8,12 @@ import ( "testing" mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" @@ -23,10 +23,6 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) // TestReconcileCreateEndpoint tests the logic to create service instances in Consul from the addresses in the Endpoints @@ -172,40 +168,40 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { CheckID: fmt.Sprintf("%s/pod1-service-created", testCase.SourceKubeNS), ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod1-service-created-sidecar-proxy", testCase.SourceKubeNS), ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod2-service-created", testCase.SourceKubeNS), ServiceName: "service-created", ServiceID: "pod2-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod2-service-created-sidecar-proxy", testCase.SourceKubeNS), ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, }, @@ -227,7 +223,7 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -446,30 +442,30 @@ func TestReconcileCreateGatewayWithNamespaces(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: "default", }, { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ConsulNS, }, { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ConsulNS, }, }, @@ -488,7 +484,7 @@ func TestReconcileCreateGatewayWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -1550,7 +1546,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { } else { writeOpts.Namespace = ts.ExpConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service.Service, svc.Service.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix, false) + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service.Service, svc.Service.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix) token, _, err := consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, @@ -1586,7 +1582,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -1656,7 +1652,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(serviceID) { - require.Contains(t, err.Error(), "ACL not found") + require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+serviceID) require.NotNil(t, token) @@ -1861,7 +1857,7 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { } else { writeOpts.Namespace = ts.ExpConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix, false) + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix) token, _, err = consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, @@ -1877,7 +1873,7 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, AllowK8sNamespacesSet: mapset.NewSetWith("*"), @@ -1919,7 +1915,7 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.Contains(t, err.Error(), "ACL not found") + require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") } }) } @@ -1954,7 +1950,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "mesh-gateway", Kind: api.ServiceKindMeshGateway, - Service: consulSvcName, + Service: "mesh-gateway", Port: 80, Address: "1.2.3.4", Meta: map[string]string{ @@ -1985,7 +1981,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "mesh-gateway", Kind: api.ServiceKindMeshGateway, - Service: consulSvcName, + Service: "mesh-gateway", Port: 80, Address: "1.2.3.4", Meta: map[string]string{ @@ -2016,7 +2012,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "terminating-gateway", Kind: api.ServiceKindTerminatingGateway, - Service: consulSvcName, + Service: "terminating-gateway", Port: 8443, Address: "1.2.3.4", Meta: map[string]string{ @@ -2037,7 +2033,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "terminating-gateway", Kind: api.ServiceKindTerminatingGateway, - Service: consulSvcName, + Service: "terminating-gateway", Port: 8443, Address: "1.2.3.4", Meta: map[string]string{ @@ -2089,7 +2085,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { { ID: "ingress-gateway", Kind: api.ServiceKindIngressGateway, - Service: consulSvcName, + Service: "ingress-gateway", Port: 80, Address: "1.2.3.4", Meta: map[string]string{ @@ -2159,7 +2155,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { writeOpts.Namespace = ts.ConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], writeOpts.Namespace, false, "", false) + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], writeOpts.Namespace, false, "") token, _, err = consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, @@ -2175,16 +2171,15 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { // Create the endpoints controller. ep := &Controller{ - Client: fakeClient, - Log: logrtest.NewTestLogger(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - ReleaseName: "consul", - ReleaseNamespace: "default", - EnableConsulNamespaces: true, - ConsulDestinationNamespace: ts.ConsulNS, + Client: fakeClient, + Log: logrtest.New(t), + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + ReleaseName: "consul", + ReleaseNamespace: "default", + EnableConsulNamespaces: true, } if tt.enableACLs { ep.AuthMethod = test.AuthMethod @@ -2217,7 +2212,6 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { } token, _, err = consulClient.ACL().TokenRead(token.AccessorID, queryOpts) - require.Error(t, err) require.Contains(t, err.Error(), "ACL not found", token) } }) @@ -2232,7 +2226,7 @@ func createPodWithNamespace(name, namespace, ip string, inject bool, managedByEn Namespace: namespace, Labels: map[string]string{}, Annotations: map[string]string{ - constants.LegacyAnnotationConsulK8sVersion: "1.0.0", + constants.AnnotationConsulK8sVersion: "1.0.0", }, }, Status: corev1.PodStatus{ diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index 4527ec84b8..de936673f7 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package endpoints import ( @@ -13,9 +10,11 @@ import ( logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,10 +24,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const ( @@ -36,6 +31,59 @@ const ( consulNodeName = "test-node-virtual" ) +func TestShouldIgnore(t *testing.T) { + t.Parallel() + cases := []struct { + name string + namespace string + denySet mapset.Set + allowSet mapset.Set + expected bool + }{ + { + name: "system namespace", + namespace: "kube-system", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "other system namespace", + namespace: "local-path-storage", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "any namespace allowed", + namespace: "foo", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: false, + }, + { + name: "in deny list", + namespace: "foo", + denySet: mapset.NewSetWith("foo"), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "not in allow list", + namespace: "foo", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("bar"), + expected: true, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + actual := shouldIgnore(tt.namespace, tt.denySet, tt.allowSet) + require.Equal(t, tt.expected, actual) + }) + } +} + func TestHasBeenInjected(t *testing.T) { t.Parallel() cases := []struct { @@ -792,37 +840,37 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { CheckID: "default/pod1-web", ServiceName: "web", ServiceID: "pod1-web", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-web-sidecar-proxy", ServiceName: "web-sidecar-proxy", ServiceID: "pod1-web-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-web-admin", ServiceName: "web-admin", ServiceID: "pod1-web-admin", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-web-admin-sidecar-proxy", ServiceName: "web-admin-sidecar-proxy", ServiceID: "pod1-web-admin-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -952,7 +1000,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { expectedProxySvcInstances []*api.CatalogService expectedHealthChecks []*api.HealthCheck metricsEnabled bool - telemetryCollectorDisabled bool nodeMeta map[string]string expErr string }{ @@ -987,7 +1034,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, k8sObjects: func() []runtime.Object { pod1 := createServicePod("pod1", "1.2.3.4", true, true) - pod1.Annotations[constants.AnnotationProxyConfigMap] = `{ "xds_fetch_timeout_ms": 9999 }` endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-created", @@ -1036,10 +1082,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, - Config: map[string]any{ - "envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject"), - "xds_fetch_timeout_ms": float64(9999), - }, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, @@ -1054,19 +1096,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1084,7 +1126,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.AnnotationGatewayWANAddress: "2.3.4.5", constants.AnnotationGatewayWANPort: "443", constants.AnnotationMeshGatewayContainerPort: "8443", - constants.AnnotationProxyConfigMap: `{ "xds_fetch_timeout_ms": 9999 }`, constants.AnnotationGatewayKind: meshGateway}) endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ @@ -1126,12 +1167,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { Port: 443, }, }, - ServiceProxy: &api.AgentServiceConnectProxyConfig{ - Config: map[string]any{ - "envoy_telemetry_collector_bind_socket_dir": string("/consul/service"), - "xds_fetch_timeout_ms": float64(9999), - }, - }, + ServiceProxy: &api.AgentServiceConnectProxyConfig{}, NodeMeta: map[string]string{ "synthetic-node": "true", "test-node": "true", @@ -1143,10 +1179,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1184,80 +1220,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { } return []runtime.Object{gateway, endpoint} }, - expectedConsulSvcInstances: []*api.CatalogService{ - { - ServiceID: "mesh-gateway", - ServiceName: "mesh-gateway", - ServiceAddress: "1.2.3.4", - ServicePort: 8443, - ServiceMeta: map[string]string{constants.MetaKeyPodName: "mesh-gateway", metaKeyKubeServiceName: "mesh-gateway", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, - ServiceTags: []string{}, - ServiceTaggedAddresses: map[string]api.ServiceAddress{ - "lan": { - Address: "1.2.3.4", - Port: 8443, - }, - "wan": { - Address: "2.3.4.5", - Port: 443, - }, - }, - ServiceProxy: &api.AgentServiceConnectProxyConfig{ - Config: map[string]interface{}{ - "envoy_prometheus_bind_addr": "1.2.3.4:20200", - "envoy_telemetry_collector_bind_socket_dir": "/consul/service", - }, - }, - }, - }, - expectedHealthChecks: []*api.HealthCheck{ - { - CheckID: "default/mesh-gateway", - ServiceName: "mesh-gateway", - ServiceID: "mesh-gateway", - Name: constants.ConsulKubernetesCheckName, - Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, - }, - }, - metricsEnabled: true, - }, - { - name: "Mesh_Gateway_with_Metrics_enabled_and_telemetry_collector_disabled", - svcName: "mesh-gateway", - consulSvcName: "mesh-gateway", - telemetryCollectorDisabled: true, - k8sObjects: func() []runtime.Object { - gateway := createGatewayPod("mesh-gateway", "1.2.3.4", map[string]string{ - constants.AnnotationGatewayConsulServiceName: "mesh-gateway", - constants.AnnotationGatewayWANSource: "Static", - constants.AnnotationGatewayWANAddress: "2.3.4.5", - constants.AnnotationGatewayWANPort: "443", - constants.AnnotationMeshGatewayContainerPort: "8443", - constants.AnnotationGatewayKind: meshGateway}) - endpoint := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mesh-gateway", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: []corev1.EndpointAddress{ - { - IP: "1.2.3.4", - TargetRef: &corev1.ObjectReference{ - Kind: "Pod", - Name: "mesh-gateway", - Namespace: "default", - }, - }, - }, - }, - }, - } - return []runtime.Object{gateway, endpoint} - }, expectedConsulSvcInstances: []*api.CatalogService{ { ServiceID: "mesh-gateway", @@ -1288,10 +1250,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, metricsEnabled: true, @@ -1304,7 +1266,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { gateway := createGatewayPod("terminating-gateway", "1.2.3.4", map[string]string{ constants.AnnotationGatewayKind: terminatingGateway, constants.AnnotationGatewayConsulServiceName: "terminating-gateway", - constants.AnnotationProxyConfigMap: `{ "xds_fetch_timeout_ms": 9999 }`, }) endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ @@ -1342,13 +1303,8 @@ func TestReconcileCreateEndpoint(t *testing.T) { metaKeySyntheticNode: "true", constants.MetaKeyPodUID: "", }, - ServiceTags: []string{}, - ServiceProxy: &api.AgentServiceConnectProxyConfig{ - Config: map[string]any{ - "envoy_telemetry_collector_bind_socket_dir": string("/consul/service"), - "xds_fetch_timeout_ms": float64(9999), - }, - }, + ServiceTags: []string{}, + ServiceProxy: &api.AgentServiceConnectProxyConfig{}, }, }, expectedHealthChecks: []*api.HealthCheck{ @@ -1356,10 +1312,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1412,8 +1368,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { ServiceTags: []string{}, ServiceProxy: &api.AgentServiceConnectProxyConfig{ Config: map[string]interface{}{ - "envoy_prometheus_bind_addr": "1.2.3.4:20200", - "envoy_telemetry_collector_bind_socket_dir": "/consul/service", + "envoy_prometheus_bind_addr": "1.2.3.4:20200", }, }, }, @@ -1423,10 +1378,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1440,7 +1395,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { constants.AnnotationGatewayKind: ingressGateway, constants.AnnotationGatewayWANSource: "Service", constants.AnnotationGatewayWANPort: "8443", - constants.AnnotationProxyConfigMap: `{ "xds_fetch_timeout_ms": 9999 }`, }) endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ @@ -1515,8 +1469,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { "address": "0.0.0.0", }, }, - "envoy_telemetry_collector_bind_socket_dir": "/consul/service", - "xds_fetch_timeout_ms": float64(9999), }, }, }, @@ -1526,10 +1478,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1618,8 +1570,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { "address": "0.0.0.0", }, }, - "envoy_prometheus_bind_addr": "1.2.3.4:20200", - "envoy_telemetry_collector_bind_socket_dir": "/consul/service", + "envoy_prometheus_bind_addr": "1.2.3.4:20200", }, }, }, @@ -1629,10 +1580,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1704,7 +1655,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, - Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, @@ -1719,7 +1669,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod2-service-created", LocalServiceAddress: "", LocalServicePort: 0, - Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, @@ -1730,37 +1679,37 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-created", ServiceName: "service-created", ServiceID: "pod2-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1845,7 +1794,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, - Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, @@ -1860,7 +1808,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod2-service-created", LocalServiceAddress: "", LocalServicePort: 0, - Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod2", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, @@ -1871,28 +1818,28 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, expErr: "1 error occurred:\n\t* pods \"pod3\" not found\n\n", @@ -1912,17 +1859,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { pod1.Annotations[constants.AnnotationUpstreams] = "upstream1:1234" pod1.Annotations[constants.AnnotationEnableMetrics] = "true" pod1.Annotations[constants.AnnotationPrometheusScrapePort] = "12345" - pod1.Spec.NodeName = "my-node" - node := &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-node", - Namespace: "default", - Labels: map[string]string{ - corev1.LabelTopologyRegion: "us-west-1", - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-created", @@ -1943,7 +1879,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, }, } - return []runtime.Object{pod1, node, endpoint} + return []runtime.Object{pod1, endpoint} }, expectedConsulSvcInstances: []*api.CatalogService{ { @@ -1964,10 +1900,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, ServiceTags: []string{"abc,123", "pod1"}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, - ServiceLocality: &api.Locality{ - Region: "us-west-1", - Zone: "us-west-1a", - }, }, }, expectedProxySvcInstances: []*api.CatalogService{ @@ -1989,14 +1921,9 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, }, Config: map[string]interface{}{ - "envoy_prometheus_bind_addr": "0.0.0.0:12345", - "envoy_telemetry_collector_bind_socket_dir": "/consul/connect-inject", + "envoy_prometheus_bind_addr": "0.0.0.0:12345", }, }, - ServiceLocality: &api.Locality{ - Region: "us-west-1", - Zone: "us-west-1a", - }, ServiceMeta: map[string]string{ "name": "abc", "version": "2", @@ -2016,19 +1943,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-different-consul-svc-name", ServiceName: "different-consul-svc-name", ServiceID: "pod1-different-consul-svc-name", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-different-consul-svc-name-sidecar-proxy", ServiceName: "different-consul-svc-name-sidecar-proxy", ServiceID: "pod1-different-consul-svc-name-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -2096,7 +2023,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { DestinationServiceID: "pod1-service-created", LocalServiceAddress: "", LocalServicePort: 0, - Config: map[string]any{"envoy_telemetry_collector_bind_socket_dir": string("/consul/connect-inject")}, }, ServiceMeta: map[string]string{constants.MetaKeyPodName: "pod1", metaKeyKubeServiceName: "service-created", constants.MetaKeyKubeNS: "default", metaKeyManagedBy: constants.ManagedByValue, metaKeySyntheticNode: "true", constants.MetaKeyPodUID: ""}, ServiceTags: []string{}, @@ -2107,19 +2033,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -2156,9 +2082,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { EnableGatewayMetrics: true, } } - - ep.EnableTelemetryCollector = !tt.telemetryCollectorDisabled - namespacedName := types.NamespacedName{ Namespace: "default", Name: tt.svcName, @@ -2185,7 +2108,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { require.Equal(t, tt.expectedConsulSvcInstances[i].ServicePort, instance.ServicePort) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceMeta, instance.ServiceMeta) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceTags, instance.ServiceTags) - require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceLocality, instance.ServiceLocality) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceTaggedAddresses, instance.ServiceTaggedAddresses) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceProxy, instance.ServiceProxy) if tt.nodeMeta != nil { @@ -2202,7 +2124,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { require.Equal(t, tt.expectedProxySvcInstances[i].ServicePort, instance.ServicePort) require.Equal(t, tt.expectedProxySvcInstances[i].ServiceMeta, instance.ServiceMeta) require.Equal(t, tt.expectedProxySvcInstances[i].ServiceTags, instance.ServiceTags) - require.Equal(t, tt.expectedProxySvcInstances[i].ServiceLocality, instance.ServiceLocality) if tt.nodeMeta != nil { require.Equal(t, tt.expectedProxySvcInstances[i].NodeMeta, instance.NodeMeta) } @@ -2233,36 +2154,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { } } -func TestParseLocality(t *testing.T) { - t.Run("no labels", func(t *testing.T) { - n := corev1.Node{} - require.Nil(t, parseLocality(n)) - }) - - t.Run("zone only", func(t *testing.T) { - n := corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } - require.Nil(t, parseLocality(n)) - }) - - t.Run("everything", func(t *testing.T) { - n := corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - corev1.LabelTopologyRegion: "us-west-1", - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } - require.Equal(t, &api.Locality{Region: "us-west-1", Zone: "us-west-1a"}, parseLocality(n)) - }) -} - // Tests updating an Endpoints object. // - Tests updates via the register codepath: // - When an address in an Endpoint is updated, that the corresponding service instance in Consul is updated. @@ -2332,8 +2223,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthCritical, ServiceID: "pod1-service-updated", ServiceName: "service-updated", @@ -2359,8 +2250,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthCritical, ServiceID: "pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", @@ -2384,19 +2275,19 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -2443,8 +2334,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthPassing, ServiceName: "service-updated", ServiceID: "pod1-service-updated", @@ -2470,8 +2361,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthPassing, ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", @@ -2495,19 +2386,19 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthCritical, Output: "Pod \"default/pod1\" is not ready", - Type: constants.ConsulKubernetesCheckType, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthCritical, Output: "Pod \"default/pod1\" is not ready", - Type: constants.ConsulKubernetesCheckType, + Type: consulKubernetesCheckType, }, }, }, @@ -2785,37 +2676,37 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-updated", ServiceName: "service-updated", ServiceID: "pod2-service-updated", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod2-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -3567,19 +3458,14 @@ func TestReconcileUpdateEndpoint(t *testing.T) { } }) consulClient := testClient.APIClient - // Wait so that bootstrap finishes - testClient.TestServer.WaitForActiveCARoot(t) // Holds token accessorID for each service ID. tokensForServices := make(map[string]string) // Register service and proxy in consul. for _, svc := range tt.initialConsulSvcs { - // Retry because ACLs may not have been initialized yet. - retry.Run(t, func(r *retry.R) { - _, err := consulClient.Catalog().Register(svc, nil) - require.NoError(r, err) - }) + _, err := consulClient.Catalog().Register(svc, nil) + require.NoError(t, err) // Create a token for this service if ACLs are enabled. if tt.enableACLs { @@ -3684,7 +3570,7 @@ func TestReconcileUpdateEndpoint(t *testing.T) { // Read the token from Consul. token, _, err := consulClient.ACL().TokenRead(tokenID, nil) if deregisteredServices.Contains(sID) { - require.Contains(t, err.Error(), "ACL not found") + require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") } else { require.NoError(t, err, "token should exist for service instance: "+sID) require.NotNil(t, token) @@ -3709,7 +3595,7 @@ func TestReconcileUpdateEndpoint_LegacyService(t *testing.T) { k8sObjects: func() []runtime.Object { pod1 := createServicePod("pod1", "1.2.3.4", true, true) pod1.Status.HostIP = "127.0.0.1" - pod1.Annotations[constants.LegacyAnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. + pod1.Annotations[constants.AnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-updated", @@ -3774,7 +3660,7 @@ func TestReconcileUpdateEndpoint_LegacyService(t *testing.T) { k8sObjects: func() []runtime.Object { pod1 := createServicePod("pod1", "1.2.3.4", true, true) pod1.Status.HostIP = "127.0.0.1" - pod1.Annotations[constants.LegacyAnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. + pod1.Annotations[constants.AnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-updated", @@ -4223,12 +4109,10 @@ func TestReconcileDeleteEndpoint(t *testing.T) { } }) consulClient := testClient.APIClient - // Wait so that bootstrap finishes - testClient.TestServer.WaitForActiveCARoot(t) + // TODO: stabilize this test by waiting for the ACL bootstrap // Register service and proxy in consul var token *api.ACLToken - var err error for _, svc := range tt.initialConsulSvcs { serviceRegistration := &api.CatalogRegistration{ Node: consulNodeName, @@ -4238,11 +4122,8 @@ func TestReconcileDeleteEndpoint(t *testing.T) { }, Service: svc, } - // Retry because the ACLs may not have been fully initialized yet. - retry.Run(t, func(r *retry.R) { - _, err = consulClient.Catalog().Register(serviceRegistration, nil) - require.NoError(r, err) - }) + _, err := consulClient.Catalog().Register(serviceRegistration, nil) + require.NoError(t, err) // Create a token for it if ACLs are enabled. if tt.enableACLs { @@ -4302,7 +4183,7 @@ func TestReconcileDeleteEndpoint(t *testing.T) { if tt.enableACLs { _, _, err = consulClient.ACL().TokenRead(token.AccessorID, nil) - require.Contains(t, err.Error(), "ACL not found") + require.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") } }) } @@ -4575,7 +4456,7 @@ func TestServiceInstancesForK8SServiceNameAndNamespace(t *testing.T) { name string k8sServiceNameMeta string k8sNamespaceMeta string - expected []*api.CatalogService + expected []*api.AgentService }{ { "no k8s service name or namespace meta", @@ -4599,21 +4480,22 @@ func TestServiceInstancesForK8SServiceNameAndNamespace(t *testing.T) { "both k8s service name and namespace set", k8sSvc, k8sNS, - []*api.CatalogService{ + []*api.AgentService{ { - ID: "foo1", - ServiceName: "foo", - ServiceMeta: map[string]string{"k8s-service-name": k8sSvc, "k8s-namespace": k8sNS}, + ID: "foo1", + Service: "foo", + Meta: map[string]string{"k8s-service-name": k8sSvc, "k8s-namespace": k8sNS}, }, { - ID: "foo1-proxy", - ServiceName: "foo-sidecar-proxy", - ServicePort: 20000, - ServiceProxy: &api.AgentServiceConnectProxyConfig{ + Kind: api.ServiceKindConnectProxy, + ID: "foo1-proxy", + Service: "foo-sidecar-proxy", + Port: 20000, + Proxy: &api.AgentServiceConnectProxyConfig{ DestinationServiceName: "foo", DestinationServiceID: "foo1", }, - ServiceMeta: map[string]string{"k8s-service-name": k8sSvc, "k8s-namespace": k8sNS}, + Meta: map[string]string{"k8s-service-name": k8sSvc, "k8s-namespace": k8sNS}, }, }, }, @@ -4682,14 +4564,14 @@ func TestServiceInstancesForK8SServiceNameAndNamespace(t *testing.T) { } ep := Controller{} - svcs, err := ep.serviceInstances(consulClient, k8sSvc, k8sNS) + svcs, err := ep.serviceInstancesForK8SServiceNameAndNamespace(consulClient, k8sSvc, k8sNS, consulNodeName) require.NoError(t, err) - if len(svcs) > 0 { + if len(svcs.Services) > 0 { require.Len(t, svcs, 2) - require.NotNil(t, svcs[0], c.expected[0]) - require.Equal(t, c.expected[0].ServiceName, svcs[0].ServiceName) - require.NotNil(t, svcs[1], c.expected[1]) - require.Equal(t, c.expected[1].ServiceName, svcs[1].ServiceName) + require.NotNil(t, c.expected[0], svcs.Services[0]) + require.Equal(t, c.expected[0].Service, svcs.Services[0].Service) + require.NotNil(t, c.expected[1], svcs.Services[1]) + require.Equal(t, c.expected[1].Service, svcs.Services[1].Service) } }) } @@ -6635,7 +6517,7 @@ func createServicePod(name, ip string, inject bool, managedByEndpointsController Namespace: "default", Labels: map[string]string{}, Annotations: map[string]string{ - constants.LegacyAnnotationConsulK8sVersion: "1.0.0", + constants.AnnotationConsulK8sVersion: "1.0.0", }, }, Status: corev1.PodStatus{ @@ -6685,54 +6567,3 @@ func createGatewayPod(name, ip string, annotations map[string]string) *corev1.Po } return pod } - -func TestReconcileAssignServiceVirtualIP(t *testing.T) { - t.Parallel() - ctx := context.Background() - cases := []struct { - name string - service *api.AgentService - expectErr bool - }{ - { - name: "valid service", - service: &api.AgentService{ - ID: "", - Service: "foo", - Port: 80, - Address: "1.2.3.4", - TaggedAddresses: map[string]api.ServiceAddress{ - "virtual": { - Address: "1.2.3.4", - Port: 80, - }, - }, - Meta: map[string]string{constants.MetaKeyKubeNS: "default"}, - }, - expectErr: false, - }, - { - name: "service missing IP should not error", - service: &api.AgentService{ - ID: "", - Service: "bar", - Meta: map[string]string{constants.MetaKeyKubeNS: "default"}, - }, - expectErr: false, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - apiClient := testClient.APIClient - err := assignServiceVirtualIP(ctx, apiClient, c.service) - if err != nil { - require.True(t, c.expectErr) - } else { - require.False(t, c.expectErr) - } - }) - } -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go deleted file mode 100644 index 6e98f5f714..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ /dev/null @@ -1,644 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package endpointsv2 - -import ( - "context" - "crypto/sha256" - "fmt" - "net" - "sort" - "strings" - - "github.com/go-logr/logr" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-multierror" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - kindReplicaSet = "ReplicaSet" -) - -type Controller struct { - client.Client - // ConsulServerConnMgr is the watcher for the Consul server addresses used to create Consul API v2 clients. - ConsulServerConnMgr consul.ServerConnectionManager - // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. - common.K8sNamespaceConfig - // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. - common.ConsulTenancyConfig - - // WriteCache keeps track of records already written to Consul in order to enable debouncing of writes. - // This is useful in particular for this controller which will see potentially many more reconciles due to - // endpoint changes (e.g. pod health) than changes to service data written to Consul. - // It is intentionally simple and best-effort, and does not guarantee against all redundant writes. - // It is not persistent across restarts of the controller process. - WriteCache WriteCache - - Log logr.Logger - - Scheme *runtime.Scheme - context.Context -} - -func (r *Controller) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - if r.WriteCache == nil { - return fmt.Errorf("WriteCache was not configured for Controller") - } - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Endpoints{}). - Complete(r) -} - -// Reconcile reads the state of an Endpoints object for a Kubernetes Service and reconciles Consul services which -// correspond to the Kubernetes Service. These events are driven by changes to the Pods backing the Kube service. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var endpoints corev1.Endpoints - var service corev1.Service - - // Ignore the request if the namespace of the endpoint is not allowed. - if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - // Create Consul resource service client for this reconcile. - resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - state, err := r.ConsulServerConnMgr.State() - if err != nil { - r.Log.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - if state.Token != "" { - ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) - } - - // If the Endpoints object has been deleted (and we get an IsNotFound error), - // we need to deregister that service from Consul. - err = r.Client.Get(ctx, req.NamespacedName, &endpoints) - if k8serrors.IsNotFound(err) { - err = r.deregisterService(ctx, resourceClient, req) - return ctrl.Result{}, err - } else if err != nil { - r.Log.Error(err, "failed to get Endpoints", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - r.Log.Info("retrieved Endpoints", "name", req.Name, "ns", req.Namespace) - - // We expect this to succeed if the Endpoints fetch for the Service succeeded. - err = r.Client.Get(r.Context, types.NamespacedName{Name: endpoints.Name, Namespace: endpoints.Namespace}, &service) - if err != nil { - r.Log.Error(err, "failed to get Service", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - r.Log.Info("retrieved Service", "name", req.Name, "ns", req.Namespace) - - consulSvc, err := r.getConsulService(ctx, &ClientPodFetcher{client: r.Client}, service, endpoints) - if err != nil { - r.Log.Error(err, "failed to build Consul service resource", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - // If we don't have at least one mesh-injected pod selected by the service, don't register. - // Note that we only _delete_ services when they're deleted from K8s, not when endpoints or - // workload selectors are empty. This ensures that failover can occur normally when targeting - // the existing VIP (ClusterIP) assigned to the service. - if consulSvc.Workloads == nil { - return ctrl.Result{}, nil - } - - // Register the service in Consul. - id := getServiceID( - service.Name, // Consul and Kubernetes service name will always match - r.getConsulNamespace(service.Namespace), - r.getConsulPartition()) - meta := getServiceMeta(service) - k8sUid := string(service.UID) - if err = r.ensureService(ctx, &defaultResourceReadWriter{resourceClient}, k8sUid, id, meta, consulSvc); err != nil { - // We could be racing with the namespace controller. - // Requeue (which includes backoff) to try again. - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "service", service.GetName(), "ns", req.Namespace, - "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -func (r *Controller) getConsulService(ctx context.Context, pf PodFetcher, service corev1.Service, endpoints corev1.Endpoints) (*pbcatalog.Service, error) { - prefixedPods, exactNamePods, err := r.getWorkloadDataFromEndpoints(ctx, pf, endpoints) - if err != nil { - return nil, err - } - - // Create Consul Service resource to be registered. - return &pbcatalog.Service{ - Workloads: getWorkloadSelector(prefixedPods, exactNamePods), - Ports: getServicePorts(service, prefixedPods, exactNamePods), - VirtualIps: r.getServiceVIPs(service), - }, nil -} - -type podSetData struct { - podCount int - samplePod *corev1.Pod -} - -// selectorPodData represents data for each set of pods represented by a WorkloadSelector value. -// The data may be for several pods (prefix) or a single pod (exact name). -// This is used for choosing the ideal Consul service TargetPort value when the K8s service target port is numeric. -type selectorPodData map[string]*podSetData - -// getWorkloadDataFromEndpoints accumulates data to supply the Consul service WorkloadSelector and TargetPort from -// Endpoints based on pod names and owners. -func (r *Controller) getWorkloadDataFromEndpoints(ctx context.Context, pf PodFetcher, endpoints corev1.Endpoints) (selectorPodData, selectorPodData, error) { - var errs error - - // Determine the workload selector by fetching as many pods as needed to accumulate prefixes - // and exact pod name matches. - // - // If the K8s service target port is numeric, we also use this information to determine the - // appropriate Consul target port value. - prefixedPods := make(selectorPodData) - exactNamePods := make(selectorPodData) - ignoredPodPrefixes := make(map[string]any) - for address := range allAddresses(endpoints.Subsets) { - if address.TargetRef != nil && address.TargetRef.Kind == "Pod" { - podName := types.NamespacedName{Name: address.TargetRef.Name, Namespace: endpoints.Namespace} - - // Accumulate owner prefixes and exact pod names for Consul workload selector. - // If this pod is already covered by a known owner prefix, skip it. - // If not, fetch the owner. If the owner has a unique prefix, add it to known prefixes. - // If not, add the pod name to exact name matches. - maybePodOwnerPrefix := getOwnerPrefixFromPodName(podName.Name) - - // If prefix is ignored, skip pod. - if _, ok := ignoredPodPrefixes[maybePodOwnerPrefix]; ok { - continue - } - - if existingPodData, ok := prefixedPods[maybePodOwnerPrefix]; !ok { - // Fetch pod info from K8s. - pod, err := pf.GetPod(ctx, podName) - if err != nil { - r.Log.Error(err, "failed to get pod", "name", podName.Name, "ns", endpoints.Namespace) - errs = multierror.Append(errs, err) - continue - } - - // Store data corresponding to the new selector value, which may be an actual set or exact pod. - podData := podSetData{ - podCount: 1, - samplePod: pod, - } - - // Add pod to workload selector values as appropriate. - // Pods can appear more than once in Endpoints subsets, so we use a set for exact names as well. - if prefix := getOwnerPrefixFromPod(pod); prefix != "" { - if inject.HasBeenMeshInjected(*pod) { - // Add to the list of pods represented by this prefix. This list is used by - // `getEffectiveTargetPort` to determine the most-used target container port name if the - // k8s service target port is numeric. - prefixedPods[prefix] = &podData - } else { - // If the pod hasn't been mesh-injected, ignore it, as it won't be available as a workload. - // Remember its prefix to avoid fetching its siblings needlessly. - ignoredPodPrefixes[prefix] = true - } - } else { - if inject.HasBeenMeshInjected(*pod) { - exactNamePods[podName.Name] = &podData - } - // If the pod hasn't been mesh-injected, ignore it, as it won't be available as a workload. - // No need to remember ignored exact pod names since we don't expect to see them twice. - } - } else { - // We've seen this prefix before. - // Keep track of how many times so that we can choose a container port name if needed later. - existingPodData.podCount += 1 - } - } - } - - return prefixedPods, exactNamePods, errs -} - -// allAddresses combines all Endpoints subset addresses to a single set. Service registration by this controller -// operates independent of health, and an address can appear in multiple subsets if it has a mix of ready and not-ready -// ports, so we combine them here to simplify iteration. -func allAddresses(subsets []corev1.EndpointSubset) map[corev1.EndpointAddress]any { - m := make(map[corev1.EndpointAddress]any) - for _, sub := range subsets { - for _, readyAddress := range sub.Addresses { - m[readyAddress] = true - } - for _, notReadyAddress := range sub.NotReadyAddresses { - m[notReadyAddress] = true - } - } - return m -} - -// getOwnerPrefixFromPodName extracts the owner name prefix from a pod name. -func getOwnerPrefixFromPodName(podName string) string { - podNameParts := strings.Split(podName, "-") - return strings.Join(podNameParts[:len(podNameParts)-1], "-") -} - -// getOwnerPrefixFromPod returns the common name prefix of the pod, if the pod is a member of a set with a unique name -// prefix. Currently, this only applies to ReplicaSets. -// -// We have to fetch the owner and check its type because pod names cannot be disambiguated from pod owner names due to -// the `-` delimiter and unique ID parts also being valid name components. -// -// If the pod owner does not have a unique name, the empty string is returned. -func getOwnerPrefixFromPod(pod *corev1.Pod) string { - for _, ref := range pod.OwnerReferences { - if ref.Kind == "ReplicaSet" { - return ref.Name - } - } - return "" -} - -// ensureService upserts a Consul service resource if an identical write has not already been made to Consul since this -// controller was started. If the check for a previous write fails, the resource is written anyway. -func (r *Controller) ensureService(ctx context.Context, rw resourceReadWriter, k8sUid string, id *pbresource.ID, meta map[string]string, consulSvc *pbcatalog.Service) error { - // Use Marshal w/ Deterministic option to ensure write hash generated from Data is consistent. - data := new(anypb.Any) - if err := anypb.MarshalFrom(data, consulSvc, proto.MarshalOptions{Deterministic: true}); err != nil { - return err - } - - // Use the locally-created Resource and ID (without Uid and Version) when writing so that it - // behaves as an upsert rather than CAS. - consulSvcResource := &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meta, - } - - writeHash, err := getWriteHash(consulSvcResource) - if err != nil { - r.Log.Error(err, "failed to get write hash for service; assuming it is out of sync", - getLogFieldsForResource(id)...) - } - key := getWriteCacheKey(types.NamespacedName{Name: id.Name, Namespace: id.Tenancy.Namespace}) - generationFetchFn := func() string { - // Check for whether a matching service already exists in Consul. - // Gracefully fail on error. This allows us to make a best-effort write attempt in - // case of a persistent read error or permissions issue that does not impact writing. - resp, err := rw.Read(ctx, &pbresource.ReadRequest{Id: id}) - if s, ok := status.FromError(err); !ok || (s.Code() != codes.OK && s.Code() != codes.NotFound) { - r.Log.Error(err, "failed to read existing service resource from Consul; assuming it is out of sync", - append(getLogFieldsForResource(id), "code", s.Code(), "message", s.Message())...) - return "" - } - return resp.GetResource().GetGeneration() - } - if r.WriteCache.hasMatch(key, writeHash, generationFetchFn, k8sUid) { - r.Log.V(1).Info("skipping service write due to matching write hash") - return nil - } - - r.Log.Info("writing service to Consul", getLogFieldsForResource(consulSvcResource.Id)...) - resp, err := rw.Write(ctx, &pbresource.WriteRequest{Resource: consulSvcResource}) - if err != nil { - r.Log.Error(err, fmt.Sprintf("failed to write service: %+v", consulSvc), - getLogFieldsForResource(consulSvcResource.Id)...) - return err - } - - generation := resp.GetResource().GetGeneration() - r.Log.Info("caching service write to Consul", "hash", writeHash, "generation", generation, - "k8sUid", k8sUid) - r.WriteCache.update(key, writeHash, generation, k8sUid) - - return nil -} - -// resourceReadWriter wraps pbresource.ResourceServiceClient for testing purposes. -// The default implementation is a passthrough used outside of tests. -type resourceReadWriter interface { - Read(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) - Write(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) -} - -type defaultResourceReadWriter struct { - client pbresource.ResourceServiceClient -} - -func (c *defaultResourceReadWriter) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { - return c.client.Read(ctx, req) -} - -func (c *defaultResourceReadWriter) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { - return c.client.Write(ctx, req) -} - -func getServiceID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - PeerName: constants.DefaultConsulPeer, - }, - } -} - -// getServicePorts converts Kubernetes Service ports data into Consul service ports. -func getServicePorts(service corev1.Service, prefixedPods selectorPodData, exactNamePods selectorPodData) []*pbcatalog.ServicePort { - ports := make([]*pbcatalog.ServicePort, 0, len(service.Spec.Ports)+1) - - for _, p := range service.Spec.Ports { - // Service mesh only supports TCP as the L4 Protocol (not to be confused w/ L7 AppProtocol). - // - // This check is necessary to deduplicate VirtualPort values when multiple declared ServicePort values exist - // for the same port, which is possible in K8s when e.g. multiplexing TCP and UDP traffic over a single port. - // - // If we otherwise see repeat port values in a K8s service, we pass along and allow Consul to fail validation. - if p.Protocol == corev1.ProtocolTCP { - //TODO(NET-5705): Error check reserved "mesh" target port - ports = append(ports, &pbcatalog.ServicePort{ - VirtualPort: uint32(p.Port), - TargetPort: getEffectiveTargetPort(p.TargetPort, prefixedPods, exactNamePods), - Protocol: inject.GetPortProtocol(p.AppProtocol), - }) - } - } - - // Sort for comparison stability during write deduplication. - sort.Slice(ports, func(i, j int) bool { - return ports[i].VirtualPort < ports[j].VirtualPort - }) - - // Append Consul service mesh port in addition to discovered ports. - ports = append(ports, &pbcatalog.ServicePort{ - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }) - - return ports -} - -func getEffectiveTargetPort(targetPort intstr.IntOrString, prefixedPods selectorPodData, exactNamePods selectorPodData) string { - // The Kubernetes service is targeting a port name; use it directly. - // The expected behavior of Kubernetes is that all included Endpoints conform and have a matching named port. - // This is the simplest path and preferred over services targeting by port number. - if targetPort.Type == intstr.String { - return targetPort.String() - } - - // The Kubernetes service is targeting a numeric port. This is more complicated for mapping to Consul: - // - Endpoints will contain _all_ selected pods, not just those with a matching declared port number. - // - Consul Workload ports always have a name, so we must determine the best name to match on. - // - There may be more than one option among the pods with named ports, including no name at all. - // - // Our best-effort approach is to find the most prevalent port name among selected pods that _do_ declare the target - // port explicitly in container ports. We'll assume that for each set of pods, the first pod is "representative" - - // i.e. we expect a ReplicaSet to be homogenous. In the vast majority of cases, this means we'll be looking for the - // largest selected ReplicaSet and using the first pod from it. - // - // The goal is to make this determination without fetching all pods belonging to the service, as that would be a - // very expensive operation to repeat every time endpoints change, and we don't expect the target port to change - // often if ever across pod/deployment lifecycles. - // - //TODO(NET-5706) in GA, we intend to change port selection to allow for Consul TargetPort to be numeric. If we - // retain the port selection model used here beyond GA, we should consider updating it to also consider pod health, - // s.t. when the selected port name changes between deployments of a ReplicaSet, we route traffic to ports - // belonging to the set most able to serve traffic, rather than simply the largest one. - targetPortInt := int32(targetPort.IntValue()) - var mostPrevalentContainerPort *corev1.ContainerPort - maxCount := 0 - effectiveNameForPort := inject.WorkloadPortName - for _, podData := range prefixedPods { - containerPort := getTargetContainerPort(targetPortInt, podData.samplePod) - - // Ignore pods without a declared port matching the service targetPort. - if containerPort == nil { - continue - } - - // If this is the most prevalent container port by pod set size, update result. - if maxCount < podData.podCount { - mostPrevalentContainerPort = containerPort - maxCount = podData.podCount - } - } - - if mostPrevalentContainerPort != nil { - return effectiveNameForPort(mostPrevalentContainerPort) - } - - // If no pod sets have the expected target port, fall back to the most common name among exact-name pods. - // An assumption here is that exact name pods mixed with pod sets will be rare, and sets should be preferred. - if len(exactNamePods) > 0 { - nameCount := make(map[string]int) - for _, podData := range exactNamePods { - if containerPort := getTargetContainerPort(targetPortInt, podData.samplePod); containerPort != nil { - nameCount[effectiveNameForPort(containerPort)] += 1 - } - } - if len(nameCount) > 0 { - maxNameCount := 0 - mostPrevalentContainerPortName := "" - for name, count := range nameCount { - if maxNameCount < count { - mostPrevalentContainerPortName = name - maxNameCount = count - } - } - return mostPrevalentContainerPortName - } - } - - // If still no match for the target port, fall back to string-ifying the target port name, which - // is what the PodController will do when converting unnamed ContainerPorts to Workload ports. - return constants.UnnamedWorkloadPortNamePrefix + targetPort.String() -} - -// getTargetContainerPort returns the pod ContainerPort matching the given numeric port value, or nil if none is found. -func getTargetContainerPort(targetPort int32, pod *corev1.Pod) *corev1.ContainerPort { - for _, c := range pod.Spec.Containers { - if len(c.Ports) == 0 { - continue - } - for _, p := range c.Ports { - if p.ContainerPort == targetPort && p.Protocol == corev1.ProtocolTCP { - return &p - } - } - } - return nil -} - -// getServiceVIPs returns the VIPs to associate with the registered Consul service. This will contain the Kubernetes -// Service ClusterIP if it exists. -// -// Note that we always provide this data regardless of whether TProxy is enabled, deferring to individual proxy configs -// to decide whether it's used. -func (r *Controller) getServiceVIPs(service corev1.Service) []string { - if parsedIP := net.ParseIP(service.Spec.ClusterIP); parsedIP == nil { - r.Log.Info("skipping service registration virtual IP assignment due to invalid or unset ClusterIP", "name", service.Name, "ns", service.Namespace, "ip", service.Spec.ClusterIP) - return nil - } - - // Note: This slice needs to be sorted for stable comparison during write deduplication. - // If additional values are added in the future, the output order should be consistent. - return []string{service.Spec.ClusterIP} -} - -func getServiceMeta(service corev1.Service) map[string]string { - meta := map[string]string{ - constants.MetaKeyKubeNS: service.Namespace, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - } - return meta -} - -// getWorkloadSelector returns the WorkloadSelector for the given pod name prefixes and exact names. -// It returns nil if the provided name sets are empty. -func getWorkloadSelector(prefixedPods selectorPodData, exactNamePods selectorPodData) *pbcatalog.WorkloadSelector { - // If we don't have any values, return nil - if len(prefixedPods) == 0 && len(exactNamePods) == 0 { - return nil - } - - // Create the WorkloadSelector - workloads := &pbcatalog.WorkloadSelector{} - for v := range prefixedPods { - workloads.Prefixes = append(workloads.Prefixes, v) - } - for v := range exactNamePods { - workloads.Names = append(workloads.Names, v) - } - - // Sort for comparison stability during write deduplication - sort.Strings(workloads.Prefixes) - sort.Strings(workloads.Names) - - return workloads -} - -// deregisterService deletes the service resource corresponding to the given name and namespace from Consul. -// This operation is idempotent and can be executed for non-existent services. -func (r *Controller) deregisterService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, req ctrl.Request) error { - // Regardless of whether we get an error on delete, remove the resource from the cache as we intend for it - // to be deleted and the record is no longer valid for preventing writes. - r.WriteCache.remove(getWriteCacheKey(req.NamespacedName)) - _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{ - Id: getServiceID(req.Name, r.getConsulNamespace(req.Namespace), r.getConsulPartition()), - }) - return err -} - -// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace -// depending on Consul Namespaces being enabled and the value of namespace mirroring. -func (r *Controller) getConsulNamespace(kubeNamespace string) string { - ns := namespaces.ConsulNamespace( - kubeNamespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO(NET-5652): remove this if and when the default namespace of resources is no longer required to be set explicitly. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} - -func (r *Controller) getConsulPartition() string { - if !r.EnableConsulPartitions || r.ConsulPartition == "" { - return constants.DefaultConsulPartition - } - return r.ConsulPartition -} - -// getWriteCacheKey gets a key to track syncronization of a K8s service to deduplicate writes to Consul. -// See also WriteCache.hasMatch. -func getWriteCacheKey(serviceName types.NamespacedName) string { - return serviceName.String() -} - -// getWriteHash gets a hash of the given resource to deduplicate writes to Consul. -// -// This hash is not intended to be cryptographically secure, only deterministic and collision-resistent -// for tens of thousands of values. -// -// If an error occurs marshalling the resource for the hash, returns a nil hash value and the error. -// error will be returned. -func getWriteHash(r *pbresource.Resource) ([]byte, error) { - // We Marshal the entire resource (not just its own Data, which is already serialized) - // in order to take advantage of the deterministic marshal offered by proto and include - // fields like Meta, which are not part of the resource Data. - data, err := proto.MarshalOptions{Deterministic: true}.Marshal(r) - if err != nil { - return nil, err - } - h := sha256.Sum256(data) - return h[:], nil -} - -func getLogFieldsForResource(id *pbresource.ID) []any { - return []any{ - "name", id.Name, - "ns", id.Tenancy.Namespace, - "partition", id.Tenancy.Partition, - } -} - -// PodFetcher fetches pods by NamespacedName. This interface primarily exists for testing. -type PodFetcher interface { - GetPod(context.Context, types.NamespacedName) (*corev1.Pod, error) -} - -// ClientPodFetcher wraps a Kubernetes client to implement PodFetcher. This is the only implementation outside of tests. -type ClientPodFetcher struct { - client client.Client -} - -func (c *ClientPodFetcher) GetPod(ctx context.Context, name types.NamespacedName) (*corev1.Pod, error) { - var pod corev1.Pod - err := c.client.Get(ctx, name, &pod) - if err != nil { - return nil, err - } - return &pod, nil -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go deleted file mode 100644 index 636a1ab923..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package endpointsv2 - -import ( - "testing" -) - -// TODO: ConsulDestinationNamespace and EnableNSMirroring +/- prefix - -// TODO(zalimeni) -// Tests new Service registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_CreateService_WithNamespaces(t *testing.T) { - -} - -// TODO(zalimeni) -// Tests updating Service registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_UpdateService_WithNamespaces(t *testing.T) { - -} - -// TODO(zalimeni) -// Tests removing Service registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_DeleteService_WithNamespaces(t *testing.T) { - -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go deleted file mode 100644 index 0c0733191b..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ /dev/null @@ -1,2362 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package endpointsv2 - -import ( - "context" - "fmt" - "testing" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/google/go-cmp/cmp" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/go-uuid" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -const ( - kindDaemonSet = "DaemonSet" -) - -var ( - appProtocolHttp = "http" - appProtocolHttp2 = "http2" - appProtocolGrpc = "grpc" -) - -type reconcileCase struct { - name string - svcName string - k8sObjects func() []runtime.Object - existingResource *pbresource.Resource - expectedResource *pbresource.Resource - targetConsulNs string - targetConsulPartition string - expErr string - caseFn func(*testing.T, *reconcileCase, *Controller, pbresource.ResourceServiceClient) -} - -// TODO(NET-5716): Allow/deny namespaces for reconcile tests -// TODO(NET-5932): Add tests for consistently sorting repeated output fields (getConsulService, getServicePorts) - -func TestReconcile_CreateService(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Empty endpoints do not get registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: []corev1.EndpointAddress{}, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{endpoints, service} - }, - }, - { - name: "Endpoints without injected pods do not get registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - removeMeshInjectStatus(t, pod1) - removeMeshInjectStatus(t, pod2) - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2), - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - }, - { - name: "Basic endpoints", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("cslport-10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10001, - TargetPort: "cslport-10001", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Unhealthy endpoints should be registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - // Split addresses between ready and not-ready - Addresses: addressesForPods(pod1), - NotReadyAddresses: addressesForPods(pod2), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - // Both replicasets (ready and not ready) should be present - Prefixes: []string{ - "service-created-rs-abcde", - "service-created-rs-fghij", - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Pods with only some service ports should be registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - // Two separate endpoint subsets w/ each of 2 ports served by a different replicaset - { - Addresses: addressesForPods(pod1), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - { - Addresses: addressesForPods(pod2), - Ports: []corev1.EndpointPort{ - { - Name: "api", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolGrpc, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - // Both replicasets should be present even though neither serves both ports - Prefixes: []string{ - "service-created-rs-abcde", - "service-created-rs-fghij", - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Numeric service target port: Named container port gets the pod port name", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde", - // Named port with container port value matching service target port - containerWithPort("named-port", 2345), - // Unnamed port with container port value matching service target port - containerWithPort("", 6789)) - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolGrpc, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromInt(2345), // Numeric target port - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromInt(6789), // Numeric target port - AppProtocol: &appProtocolGrpc, - }, - { - Name: "unmatched-port", - Port: 10010, - Protocol: "TCP", - TargetPort: intstr.FromInt(10010), // Numeric target port - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "named-port", // Matches container port name, not service target number - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "cslport-6789", // Matches service target number - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10010, - TargetPort: "cslport-10010", // Matches service target number (unmatched by container ports) - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Numeric service target port: Container port mix gets the name from largest matching pod set", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - // Unnamed port matching service target port. - // Also has second named port, and is not the most prevalent set for that port. - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde", - containerWithPort("", 2345), - containerWithPort("api-port", 6789)) - - // Named port with different name from most prevalent pods. - // Also has second unnamed port, and _is_ the most prevalent set for that port. - pod2a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij", - containerWithPort("another-port-name", 2345), - containerWithPort("", 6789)) - pod2b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij", - containerWithPort("another-port-name", 2345), - containerWithPort("", 6789)) - - // Named port with container port value matching service target port. - // The most common "set" of pods, so should become the port name for service target port. - pod3a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", - containerWithPort("named-port", 2345)) - pod3b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", - containerWithPort("named-port", 2345)) - pod3c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", - containerWithPort("named-port", 2345)) - - // Named port that does not match service target port. - // More common "set" of pods selected by the service, but does not have a target port (value) match. - pod4a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", - containerWithPort("non-matching-named-port", 5432)) - pod4b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", - containerWithPort("non-matching-named-port", 5432)) - pod4c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", - containerWithPort("non-matching-named-port", 5432)) - pod4d := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", - containerWithPort("non-matching-named-port", 5432)) - - // Named port from non-injected pods. - // More common "set" of pods selected by the service, but should be filtered out. - pod5a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", - containerWithPort("ignored-named-port", 2345)) - pod5b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", - containerWithPort("ignored-named-port", 2345)) - pod5c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", - containerWithPort("ignored-named-port", 2345)) - pod5d := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", - containerWithPort("ignored-named-port", 2345)) - for _, p := range []*corev1.Pod{pod5a, pod5b, pod5c, pod5d} { - removeMeshInjectStatus(t, p) - } - - // Named port with container port value matching service target port. - // Single pod from non-ReplicaSet owner. Should not take precedence over set pods. - pod6a := createServicePod(kindDaemonSet, "service-created-ds", "12345", - containerWithPort("another-port-name", 2345)) - - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods( - pod1, - pod2a, pod2b, - pod3a, pod3b, pod3c, - pod4a, pod4b, pod4c, pod4d, - pod5a, pod5b, pod5c, pod5d, - pod6a), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromInt(2345), // Numeric target port - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromInt(6789), // Numeric target port - AppProtocol: &appProtocolGrpc, - }, - }, - }, - } - return []runtime.Object{ - pod1, - pod2a, pod2b, - pod3a, pod3b, pod3c, - pod4a, pod4b, pod4c, pod4d, - pod5a, pod5b, pod5c, pod5d, - pod6a, - endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "named-port", // Matches container port name, not service target number - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "cslport-6789", // Matches service target number due to unnamed being most common - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{ - "service-created-rs-abcde", - "service-created-rs-fghij", - "service-created-rs-klmno", - "service-created-rs-pqrst", - }, - Names: []string{ - "service-created-ds-12345", - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Numeric service target port: Most used container port name from exact name pods used when no pod sets present", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - // Named port with different name from most prevalent pods. - pod1a := createServicePod(kindDaemonSet, "service-created-ds1", "12345", - containerWithPort("another-port-name", 2345)) - - // Named port with container port value matching service target port. - // The most common container port name, so should become the port name for service target port. - pod2a := createServicePod(kindDaemonSet, "service-created-ds2", "12345", - containerWithPort("named-port", 2345)) - pod2b := createServicePod(kindDaemonSet, "service-created-ds2", "23456", - containerWithPort("named-port", 2345)) - - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods( - pod1a, - pod2a, pod2b), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromInt(2345), // Numeric target port - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{ - pod1a, - pod2a, pod2b, - endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "named-port", // Matches container port name, not service target number - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{ - "service-created-ds1-12345", - "service-created-ds2-12345", - "service-created-ds2-23456", - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Only L4 TCP ports get a Consul Service port when L4 protocols are multiplexed", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1), - Ports: []corev1.EndpointPort{ - { - Name: "public-tcp", - Port: 2345, - Protocol: "TCP", - }, - { - Name: "public-udp", - Port: 2345, - Protocol: "UDP", - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - // Two L4 protocols on one exposed port - { - Name: "public-tcp", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-svc-port"), - }, - { - Name: "public-udp", - Port: 8080, - Protocol: "UDP", - TargetPort: intstr.FromString("my-svc-port"), - }, - }, - }, - } - return []runtime.Object{pod1, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-svc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Services without mesh-injected pods should not be registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - removeMeshInjectStatus(t, pod1) - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, endpoints, service} - }, - // No expected resource - }, - { - name: "Services with mix of injected and non-injected pods registered with only injected selectors", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") - pod3 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - pod4 := createServicePod(kindDaemonSet, "service-created-ds", "23456") - removeMeshInjectStatus(t, pod1) - removeMeshInjectStatus(t, pod3) - // Retain status of second pod - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2, pod3, pod4), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, pod3, pod4, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - // Selector only contains values for injected pods - Prefixes: []string{"service-created-rs-fghij"}, - Names: []string{"service-created-ds-23456"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -func TestReconcile_UpdateService(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Pods changed", - svcName: "service-updated", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno") - pod3 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - pod4 := createServicePod(kindDaemonSet, "service-created-ds", "34567") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2, pod3, pod4), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, pod3, pod4, endpoints, service} - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{ - "service-created-rs-abcde", // Retained - "service-created-rs-fghij", // Removed - }, - Names: []string{ - "service-created-ds-12345", // Retained - "service-created-ds-23456", // Removed - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - - Prefixes: []string{ - "service-created-rs-abcde", // Retained - "service-created-rs-klmno", // New - }, - Names: []string{ - "service-created-ds-12345", // Retained - "service-created-ds-34567", // New - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Service ports changed", - svcName: "service-updated", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - { - Name: "my-grpc-port", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("new-http-port"), - AppProtocol: &appProtocolHttp2, - }, - { - Name: "api", - Port: 9091, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-updated", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10001, - TargetPort: "unspec-port", //this might need to be changed to "my_unspecified_port" - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-updated", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "new-http-port", // Updated - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP2, // Updated - }, - { - VirtualPort: 9091, // Updated - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - // Port 10001 removed - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Redundant reconcile does not write to Consul unless resource was modified", - svcName: "service-updated", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - { - Name: "my-grpc-port", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - UID: types.UID(randomUid()), - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("cslport-10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-updated", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10001, - TargetPort: "cslport-10001", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - caseFn: func(t *testing.T, tc *reconcileCase, ep *Controller, resourceClient pbresource.ResourceServiceClient) { - runReconcile := func() { - r, err := ep.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: tc.svcName, - Namespace: tc.targetConsulNs, - }}) - require.False(t, r.Requeue) - require.NoError(t, err) - } - - // Get resource before additional reconcile - beforeResource := getAndValidateResource(t, resourceClient, tc.expectedResource.Id) - - // Run several additional reconciles, expecting no writes to Consul - for i := 0; i < 5; i++ { - runReconcile() - require.Equal(t, beforeResource.GetGeneration(), - getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), - "wanted same version for before and after resources following repeat reconcile") - } - - // Modify resource external to controller - modified := proto.Clone(beforeResource).(*pbresource.Resource) - modified.Metadata = map[string]string{"foo": "bar"} - modified.Version = "" - modified.Generation = "" - _, err := resourceClient.Write(context.Background(), &pbresource.WriteRequest{ - Resource: modified, - }) - require.NoError(t, err) - - // Get resource after additional reconcile, now expecting a new write to occur - runReconcile() - - require.NotEqual(t, beforeResource.GetGeneration(), - getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), - "wanted different version for before and after resources following modification and reconcile") - - // Get resource before additional reconcile - beforeResource = getAndValidateResource(t, resourceClient, tc.expectedResource.Id) - - // Run several additional reconciles, expecting no writes to Consul - for i := 0; i < 5; i++ { - runReconcile() - require.Equal(t, beforeResource.GetGeneration(), - getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), - "wanted same version for before and after resources following repeat reconcile") - } - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -func TestEnsureService(t *testing.T) { - t.Parallel() - - type args struct { - k8sUid string - meta map[string]string - consulSvc *pbcatalog.Service - } - - uuid1 := randomUid() - uuid2 := randomUid() - meta1 := getServiceMeta(corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }}) - meta2 := getServiceMeta(corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }}) - meta2["some-other-key"] = "value" - - id := getServiceID( - "my-svc", - constants.DefaultConsulNS, - constants.DefaultConsulPartition) - - cases := []struct { - name string - beforeArgs args - afterArgs args - readFn func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) - writeFn func(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) - expectWrite bool - expectAlwaysWrite bool - expectErr string - caseFn func(t *testing.T, ep *Controller) - }{ - { - name: "Identical args writes once", - beforeArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - // Identical to before - afterArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - expectWrite: false, - }, - { - name: "Changed service payload updates resource", - beforeArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - afterArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - // Different workload selector - Prefixes: []string{"service-created-rs-fghij"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - expectWrite: true, - }, - { - name: "Changed service meta updates resource", - beforeArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - afterArgs: args{ - k8sUid: uuid1, - meta: meta2, // Updated meta - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - expectWrite: true, - }, - { - name: "Changed k8s UID updates resource", - beforeArgs: args{ - k8sUid: uuid1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - // Identical to before except K8s UID, indicating delete and rewrite of K8s service - afterArgs: args{ - k8sUid: uuid2, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - expectWrite: true, - }, - { - name: "Read not found fails open and writes update", - readFn: func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { - return nil, status.Error(codes.NotFound, "not found") - }, - expectWrite: true, - expectAlwaysWrite: true, - }, - { - name: "Read error fails open and writes update", - readFn: func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { - return nil, status.Error(codes.PermissionDenied, "not allowed") - }, - expectWrite: true, - expectAlwaysWrite: true, - }, - { - name: "Write error does not prevent future writes (cache not updated)", - writeFn: func(ctx context.Context, request *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { - return nil, status.Error(codes.Internal, "oops") - }, - expectErr: "rpc error: code = Internal desc = oops", - caseFn: func(t *testing.T, ep *Controller) { - require.Empty(t, ep.WriteCache.(*writeCache).data) - }, - }, - } - - // Create test Consul server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - // Create the Endpoints controller. - ep := &Controller{ - Client: fake.NewClientBuilder().WithRuntimeObjects().Build(), // No k8s fetches should be needed - WriteCache: NewWriteCache(logrtest.New(t)), - Log: logrtest.New(t), - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - } - - // Set up test resourceReadWriter - rw := struct{ testReadWriter }{} - defaultRw := defaultResourceReadWriter{testClient.ResourceClient} - rw.readFn = defaultRw.Read - rw.writeFn = defaultRw.Write - if tc.readFn != nil { - rw.readFn = tc.readFn - } - if tc.writeFn != nil { - rw.writeFn = tc.writeFn - } - - // Ensure caseFn runs if provided, regardless of whether error is expected - if tc.caseFn != nil { - defer tc.caseFn(t, ep) - } - - // Call first time - err := ep.ensureService(context.Background(), &rw, tc.beforeArgs.k8sUid, id, tc.beforeArgs.meta, tc.beforeArgs.consulSvc) - if tc.expectErr != "" { - require.Contains(t, err.Error(), tc.expectErr) - return - } - require.NoError(t, err) - - // Get written resource before additional calls - beforeResource := getAndValidateResource(t, testClient.ResourceClient, id) - - // Call a second time - err = ep.ensureService(context.Background(), &rw, tc.afterArgs.k8sUid, id, tc.afterArgs.meta, tc.afterArgs.consulSvc) - require.NoError(t, err) - - // Check for change on second call to ensureService - if tc.expectWrite { - require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, testClient.ResourceClient, id).GetGeneration(), - "wanted different version for before and after resources following modification and reconcile") - } else { - require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, testClient.ResourceClient, id).GetGeneration(), - "wanted same version for before and after resources following repeat reconcile") - } - - // Call several additional times - for i := 0; i < 5; i++ { - // Get written resource before each additional call - beforeResource = getAndValidateResource(t, testClient.ResourceClient, id) - - err := ep.ensureService(context.Background(), &rw, tc.afterArgs.k8sUid, id, tc.afterArgs.meta, tc.afterArgs.consulSvc) - require.NoError(t, err) - - if tc.expectAlwaysWrite { - require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, testClient.ResourceClient, id).GetGeneration(), - "wanted different version for before and after resources following modification and reconcile") - } else { - require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, testClient.ResourceClient, id).GetGeneration(), - "wanted same version for before and after resources following repeat reconcile") - } - } - }) - } -} - -type testReadWriter struct { - readFn func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) - writeFn func(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) -} - -func (rw *testReadWriter) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { - return rw.readFn(ctx, req) -} - -func (rw *testReadWriter) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { - return rw.writeFn(ctx, req) -} - -func TestReconcile_DeleteService(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Basic Endpoints not found (service deleted) deregisters service", - svcName: "service-deleted", - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-deleted", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - caseFn: func(t *testing.T, _ *reconcileCase, ep *Controller, _ pbresource.ResourceServiceClient) { - // Ensure cache was also cleared - require.Empty(t, ep.WriteCache.(*writeCache).data) - }, - }, - { - name: "Empty endpoints does not cause deregistration of existing service", - svcName: "service-deleted", - k8sObjects: func() []runtime.Object { - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-deleted", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: []corev1.EndpointAddress{}, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-deleted", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{endpoints, service} - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-deleted", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-deleted", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - type testCase struct { - name string - endpoints corev1.Endpoints - responses map[types.NamespacedName]*corev1.Pod - expected *pbcatalog.WorkloadSelector - mockFn func(*testing.T, *MockPodFetcher) - } - - rsPods := []*corev1.Pod{ - createServicePod(kindReplicaSet, "svc-rs-abcde", "12345"), - createServicePod(kindReplicaSet, "svc-rs-abcde", "23456"), - createServicePod(kindReplicaSet, "svc-rs-abcde", "34567"), - createServicePod(kindReplicaSet, "svc-rs-fghij", "12345"), - createServicePod(kindReplicaSet, "svc-rs-fghij", "23456"), - createServicePod(kindReplicaSet, "svc-rs-fghij", "34567"), - } - otherPods := []*corev1.Pod{ - createServicePod(kindDaemonSet, "svc-ds", "12345"), - createServicePod(kindDaemonSet, "svc-ds", "23456"), - createServicePod(kindDaemonSet, "svc-ds", "34567"), - createServicePod("StatefulSet", "svc-ss", "12345"), - createServicePod("StatefulSet", "svc-ss", "23456"), - createServicePod("StatefulSet", "svc-ss", "34567"), - } - ignoredPods := []*corev1.Pod{ - createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "12345"), - createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "23456"), - createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "34567"), - } - - podsByName := make(map[types.NamespacedName]*corev1.Pod) - for _, p := range rsPods { - podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p - } - for _, p := range otherPods { - podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p - } - for _, p := range ignoredPods { - removeMeshInjectStatus(t, p) - podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p - } - - cases := []testCase{ - { - name: "Pod is fetched once per ReplicaSet", - endpoints: corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "svc", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(rsPods...), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, - Port: 2345, - }, - }, - }, - }, - }, - responses: podsByName, - expected: getWorkloadSelector( - // Selector should consist of prefixes only. - selectorPodData{ - "svc-rs-abcde": &podSetData{}, - "svc-rs-fghij": &podSetData{}, - }, - selectorPodData{}), - mockFn: func(t *testing.T, pf *MockPodFetcher) { - // Assert called once per set. - require.Equal(t, 2, len(pf.calls)) - }, - }, - { - name: "Pod is fetched once per other pod owner type", - endpoints: corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "svc", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(otherPods...), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, - Port: 2345, - }, - }, - }, - }, - }, - responses: podsByName, - expected: getWorkloadSelector( - // Selector should consist of exact name matches only. - selectorPodData{}, - selectorPodData{ - "svc-ds-12345": &podSetData{}, - "svc-ds-23456": &podSetData{}, - "svc-ds-34567": &podSetData{}, - "svc-ss-12345": &podSetData{}, - "svc-ss-23456": &podSetData{}, - "svc-ss-34567": &podSetData{}, - }), - mockFn: func(t *testing.T, pf *MockPodFetcher) { - // Assert called once per pod. - require.Equal(t, len(otherPods), len(pf.calls)) - }, - }, - { - name: "Pod is ignored if not mesh-injected", - endpoints: corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "svc", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(ignoredPods...), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, - Port: 2345, - }, - }, - }, - }, - }, - responses: podsByName, - expected: nil, - mockFn: func(t *testing.T, pf *MockPodFetcher) { - // Assert called once for single set. - require.Equal(t, 1, len(pf.calls)) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - // Create mock pod fetcher. - pf := MockPodFetcher{responses: tc.responses} - - // Create the Endpoints controller. - ep := &Controller{ - WriteCache: NewWriteCache(logrtest.New(t)), - Log: logrtest.New(t), - } - - prefixedPods, exactNamePods, err := ep.getWorkloadDataFromEndpoints(ctx, &pf, tc.endpoints) - require.NoError(t, err) - - ws := getWorkloadSelector(prefixedPods, exactNamePods) - if diff := cmp.Diff(tc.expected, ws, test.CmpProtoIgnoreOrder()...); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - tc.mockFn(t, &pf) - }) - } -} - -type MockPodFetcher struct { - calls []types.NamespacedName - responses map[types.NamespacedName]*corev1.Pod -} - -func (m *MockPodFetcher) GetPod(_ context.Context, name types.NamespacedName) (*corev1.Pod, error) { - m.calls = append(m.calls, name) - if v, ok := m.responses[name]; !ok { - panic(fmt.Errorf("test is missing response for passed pod name: %v", name)) - } else { - return v, nil - } -} - -func runReconcileCase(t *testing.T, tc reconcileCase) { - t.Helper() - - // Create fake k8s client - var k8sObjects []runtime.Object - if tc.k8sObjects != nil { - k8sObjects = tc.k8sObjects() - } - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test Consul server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the Endpoints controller. - ep := &Controller{ - Client: fakeClient, - WriteCache: NewWriteCache(logrtest.New(t)), - Log: logrtest.New(t), - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - } - - // Default ns and partition if not specified in test. - if tc.targetConsulNs == "" { - tc.targetConsulNs = constants.DefaultConsulNS - } - if tc.targetConsulPartition == "" { - tc.targetConsulPartition = constants.DefaultConsulPartition - } - - // If existing resource specified, create it and ensure it exists. - if tc.existingResource != nil { - writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} - _, err := testClient.ResourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), testClient.ResourceClient, tc.existingResource.Id) - } - - // Run actual reconcile and verify results. - resp, err := ep.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: tc.svcName, - Namespace: tc.targetConsulNs, - }, - }) - if tc.expErr != "" { - require.ErrorContains(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - expectedServiceMatches(t, testClient.ResourceClient, tc.svcName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) - - if tc.caseFn != nil { - tc.caseFn(t, &tc, ep, testClient.ResourceClient) - } -} - -func expectedServiceMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { - req := &pbresource.ReadRequest{Id: getServiceID(name, namespace, partition)} - - res, err := client.Read(context.Background(), req) - - if expectedResource == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - require.NotNil(t, res.GetResource().GetData()) - - expectedService := &pbcatalog.Service{} - err = anypb.UnmarshalTo(expectedResource.Data, expectedService, proto.UnmarshalOptions{}) - require.NoError(t, err) - - actualService := &pbcatalog.Service{} - err = res.GetResource().GetData().UnmarshalTo(actualService) - require.NoError(t, err) - - if diff := cmp.Diff(expectedService, actualService, test.CmpProtoIgnoreOrder()...); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } -} - -func createServicePodOwnedBy(ownerKind, ownerName string, containers ...corev1.Container) *corev1.Pod { - return createServicePod(ownerKind, ownerName, randomKubernetesId(), containers...) -} - -func createServicePod(ownerKind, ownerName, podId string, containers ...corev1.Container) *corev1.Pod { - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", ownerName, podId), - Namespace: "default", - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.AnnotationConsulK8sVersion: "1.3.0", - constants.KeyMeshInjectStatus: constants.Injected, - }, - OwnerReferences: []metav1.OwnerReference{ - { - Name: ownerName, - Kind: ownerKind, - }, - }, - }, - Spec: corev1.PodSpec{ - Containers: containers, - }, - } - return pod -} - -func containerWithPort(name string, port int32) corev1.Container { - return corev1.Container{ - Ports: []corev1.ContainerPort{ - { - Name: name, - ContainerPort: port, - Protocol: "TCP", - }, - }, - } -} - -func addressesForPods(pods ...*corev1.Pod) []corev1.EndpointAddress { - var addresses []corev1.EndpointAddress - for i, p := range pods { - addresses = append(addresses, corev1.EndpointAddress{ - IP: fmt.Sprintf("1.2.3.%d", i), - TargetRef: &corev1.ObjectReference{ - Kind: "Pod", - Name: p.Name, - Namespace: p.Namespace, - }, - }) - } - return addresses -} - -func randomKubernetesId() string { - u, err := uuid.GenerateUUID() - if err != nil { - panic(err) - } - return u[:5] -} - -func randomUid() string { - u, err := uuid.GenerateUUID() - if err != nil { - panic(err) - } - return u -} - -func removeMeshInjectStatus(t *testing.T, pod *corev1.Pod) { - delete(pod.Annotations, constants.KeyMeshInjectStatus) - require.False(t, inject.HasBeenMeshInjected(*pod)) -} - -func getAndValidateResource(t *testing.T, resourceClient pbresource.ResourceServiceClient, id *pbresource.ID) *pbresource.Resource { - resp, err := resourceClient.Read(metadata.NewOutgoingContext( - context.Background(), - // Read with strong consistency to avoid race conditions - metadata.New(map[string]string{"x-consul-consistency-mode": "consistent"}), - ), &pbresource.ReadRequest{ - Id: id, - }) - require.NoError(t, err) - r := resp.GetResource() - require.NotNil(t, r) - require.NotEmpty(t, r.GetGeneration()) - return r -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/write_cache.go b/control-plane/connect-inject/controllers/endpointsv2/write_cache.go deleted file mode 100644 index 0baf537ef7..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/write_cache.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package endpointsv2 - -import ( - "bytes" - "fmt" - "github.com/go-logr/logr" - "github.com/hashicorp/go-multierror" - "sync" -) - -// consulWriteRecord is a record of writing a resource to Consul for the sake of deduplicating writes. -// -// It is bounded in size and even a low-resource pod should be able to store 10Ks of them in-memory without worrying -// about eviction. On average, assuming a SHA256 hash, the total size of each record should be approximately 150 bytes. -type consulWriteRecord struct { - // inputHash is a detrministic hash of the payload written to Consul. - // It should be derived from the "source" data rather than the returned payload in order to be unaffected by added - // fields and defaulting behavior defined by Consul. - inputHash []byte - // generation is the generation of the written resource in Consul. This ensures that we write to Consul if a - // redundant reconcile occurs, but the actual Consul resource has been modified since the last write. - generation string - // k8sUid is the UID of the corresponding resource in K8s. This allows us to check for K8s service recreation in - // between successful reconciles even though deletion of a K8s resource does not expose the UID of the deleted - // resource (the reconcile request only contains the namespaced name). - k8sUid string -} - -// WriteCache is a simple, unbounded, thread-safe in-memory cache for tracking writes of Consul resources. -// It can be used to deduplicate identical writes client-side to "debounce" writes during repeat reconciles -// that do not impact data already written to Consul. -type WriteCache interface { - hasMatch(key string, hash []byte, generationFetchFn func() string, k8sUid string) bool - update(key string, hash []byte, generation string, k8sUid string) - remove(key string) -} - -type writeCache struct { - data map[string]consulWriteRecord - dataMutex sync.RWMutex - - log logr.Logger -} - -func NewWriteCache(log logr.Logger) WriteCache { - return &writeCache{ - data: make(map[string]consulWriteRecord), - log: log.WithName("writeCache"), - } -} - -// update upserts a record containing the given hash and generation to the cache at the given key. -func (c *writeCache) update(key string, hash []byte, generation string, k8sUid string) { - c.dataMutex.Lock() - defer c.dataMutex.Unlock() - - var err error - if key == "" { - err = multierror.Append(err, fmt.Errorf("key was empty")) - } - if len(hash) == 0 { - err = multierror.Append(err, fmt.Errorf("hash was empty")) - } - if generation == "" { - err = multierror.Append(err, fmt.Errorf("generation was empty")) - } - if k8sUid == "" { - err = multierror.Append(err, fmt.Errorf("k8sUid was empty")) - } - if err != nil { - c.log.Error(err, "writeCache could not be updated due to empty value(s) - redundant writes may be repeated") - return - } - - c.data[key] = consulWriteRecord{ - inputHash: hash, - generation: generation, - k8sUid: k8sUid, - } -} - -// remove removes a record from the cache at the given key. -func (c *writeCache) remove(key string) { - c.dataMutex.Lock() - defer c.dataMutex.Unlock() - - delete(c.data, key) -} - -// hasMatch returns true iff. there is an existing write record for the given key in the cache, and that record matches -// the provided non-empty hash, generation, and Kubernetes UID. -// -// The generation is fetched rather than provided directly s.t. a call to Consul can be skipped if a record is not found -// or other available fields do not match. -// -// While not strictly necessary assuming the controller is the sole writer of the resource, the generation check ensures -// that the resource is kept in sync even if externally modified. -// -// When checking for a match, ensures the UID of the K8s service also matches s.t. we don't skip updates on recreation -// of a K8s service, as the intent of the user may have been to force a sync, and a future solution that stores write -// fingerprints in K8s annotations would also have this behavior. -func (c *writeCache) hasMatch(key string, hash []byte, generationFetchFn func() string, k8sUid string) bool { - var lastHash []byte - lastGeneration := "" - lastK8sUid := "" - if s, ok := c.get(key); ok { - lastHash = s.inputHash - lastGeneration = s.generation - lastK8sUid = s.k8sUid - } - - if len(lastHash) == 0 || lastGeneration == "" || lastK8sUid == "" { - return false - } - - return bytes.Equal(lastHash, hash) && - lastK8sUid == k8sUid && - lastGeneration == generationFetchFn() // Fetch generation only if other fields match -} - -func (c *writeCache) get(key string) (consulWriteRecord, bool) { - c.dataMutex.RLock() - defer c.dataMutex.RUnlock() - - v, ok := c.data[key] - return v, ok -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go b/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go deleted file mode 100644 index 2b22c5707a..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package endpointsv2 - -import ( - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/go-uuid" - "testing" -) - -func Test_writeCache(t *testing.T) { - testHash := randomBytes() - testGeneration := randomString() - testK8sUid := randomString() - - type args struct { - key string - hash []byte - generationFetchFn func() string - k8sUid string - } - cases := []struct { - name string - args args - setupFn func(args args, cache WriteCache) - want bool - }{ - { - name: "No data returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - want: false, - }, - { - name: "Non-matching key returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update("another-key", args.hash, args.generationFetchFn(), args.k8sUid) - }, - want: false, - }, - { - name: "Non-matching hash returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, randomBytes(), args.generationFetchFn(), args.k8sUid) - }, - want: false, - }, - { - name: "Non-matching generation returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, randomString(), args.k8sUid) - }, - want: false, - }, - { - name: "Non-matching k8sUid returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), randomString()) - }, - want: false, - }, - { - name: "Matching data returns true", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - }, - want: true, - }, - { - name: "Removed data returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update("another-key", randomBytes(), randomString(), randomString()) - cache.remove(args.key) - }, - want: false, - }, - { - name: "Replaced data returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update(args.key, randomBytes(), args.generationFetchFn(), args.k8sUid) - }, - want: false, - }, - { - name: "Invalid hash does not update cache", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update(args.key, []byte{}, args.generationFetchFn(), args.k8sUid) - }, - want: true, - }, - { - name: "Invalid generation does not update cache", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update(args.key, args.hash, "", args.k8sUid) - }, - want: true, - }, - { - name: "Invalid k8sUid does not update cache", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update(args.key, args.hash, args.generationFetchFn(), "") - }, - want: true, - }, - { - name: "Invalid key is ignored", - args: args{ - "", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update("", args.hash, args.generationFetchFn(), args.k8sUid) - }, - want: false, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - c := NewWriteCache(logrtest.New(t)) - if tc.setupFn != nil { - tc.setupFn(tc.args, c) - } - if got := c.hasMatch(tc.args.key, tc.args.hash, tc.args.generationFetchFn, tc.args.k8sUid); got != tc.want { - t.Errorf("hasMatch() = %v, want %v", got, tc.want) - } - }) - } -} - -func randomBytes() []byte { - b, err := uuid.GenerateRandomBytes(32) - if err != nil { - panic(err) - } - return b -} - -func randomString() string { - u, err := uuid.GenerateUUID() - if err != nil { - panic(err) - } - return u -} diff --git a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go index 3a1251aae6..044de55998 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package peering import ( diff --git a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go index 55ab7a22f9..93024391b9 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package peering import ( diff --git a/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go b/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go index 37de792cb6..98646b1654 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package peering import ( diff --git a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go index 4997579e5b..cc82d7930c 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package peering import ( diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go deleted file mode 100644 index 8bc29302fe..0000000000 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ /dev/null @@ -1,770 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package pod - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "regexp" - "strings" - - "github.com/go-logr/logr" - "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-multierror" - "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/proto" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - DefaultTelemetryBindSocketDir = "/consul/mesh-inject" - consulNodeAddress = "127.0.0.1" - tokenMetaPodNameKey = "pod" -) - -// Controller watches Pod events and converts them to V2 Workloads and HealthStatus. -// The translation from Pod to Workload is 1:1 and the HealthStatus object is a representation -// of the Pod's Status field. Controller is also responsible for generating V2 Upstreams resources -// when not in transparent proxy mode. ProxyConfiguration is also optionally created. -type Controller struct { - client.Client - // ConsulClientConfig is the config for the Consul API client. - ConsulClientConfig *consul.Config - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. - common.K8sNamespaceConfig - // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. - common.ConsulTenancyConfig - - // TODO: EnableWANFederation - - // EnableTransparentProxy controls whether transparent proxy should be enabled - // for all proxy service registrations. - EnableTransparentProxy bool - // TProxyOverwriteProbes controls whether the pods controller should expose pod's HTTP probes - // via Envoy proxy. - TProxyOverwriteProbes bool - - // AuthMethod is the name of the Kubernetes Auth Method that - // was used to login with Consul. The pods controller - // will delete any tokens associated with this auth method - // whenever service instances are deregistered. - AuthMethod string - - // EnableTelemetryCollector controls whether the proxy service should be registered - // with config to enable telemetry forwarding. - EnableTelemetryCollector bool - - MetricsConfig metrics.Config - Log logr.Logger - - // ResourceClient is a gRPC client for the resource service. It is public for testing purposes - ResourceClient pbresource.ResourceServiceClient -} - -// TODO: logs, logs, logs - -// Reconcile reads the state of a Kubernetes Pod and reconciles Consul workloads that are 1:1 mapped. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var errs error - var pod corev1.Pod - - // Ignore the request if the namespace of the pod is not allowed. - // Strictly speaking, this is not required because the mesh webhook also knows valid namespaces - // for injection, but it will somewhat reduce the amount of unnecessary deletions for non-injected - // pods - if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - rc, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create resource client", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - r.ResourceClient = rc - - apiClient, err := consul.NewClientFromConnMgr(r.ConsulClientConfig, r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul API client", "name", req.Name) - return ctrl.Result{}, err - } - - if r.ConsulClientConfig.APIClientConfig.Token != "" { - ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", r.ConsulClientConfig.APIClientConfig.Token) - } - - err = r.Client.Get(ctx, req.NamespacedName, &pod) - - // If the pod object has been deleted (and we get an IsNotFound error), - // we need to remove the Workload from Consul. - if k8serrors.IsNotFound(err) { - - // Consul should also clean up the orphaned HealthStatus - if err := r.deleteWorkload(ctx, req.NamespacedName); err != nil { - errs = multierror.Append(errs, err) - } - - // Delete destinations, if any exist - if err := r.deleteDestinations(ctx, req.NamespacedName); err != nil { - errs = multierror.Append(errs, err) - } - - if err := r.deleteProxyConfiguration(ctx, req.NamespacedName); err != nil { - errs = multierror.Append(errs, err) - } - - if r.AuthMethod != "" { - r.Log.Info("deleting ACL tokens for pod", "name", req.Name, "ns", req.Namespace) - err := r.deleteACLTokensForPod(apiClient, req.NamespacedName) - if err != nil { - r.Log.Error(err, "failed to delete ACL tokens for pod", "name", req.Name, "ns", req.Namespace) - errs = multierror.Append(errs, err) - } - } - - return ctrl.Result{}, errs - } else if err != nil { - r.Log.Error(err, "failed to get Pod", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - r.Log.Info("retrieved", "name", pod.Name, "ns", pod.Namespace) - - if inject.HasBeenMeshInjected(pod) || inject.IsGateway(pod) { - - // It is possible the pod was scheduled but doesn't have an allocated IP yet, so safely requeue - if pod.Status.PodIP == "" { - r.Log.Info("pod does not have IP allocated; re-queueing request", "pod", req.Name, "ns", req.Namespace) - return ctrl.Result{Requeue: true}, nil - } - - if err := r.writeProxyConfiguration(ctx, pod); err != nil { - // We could be racing with the namespace controller. - // Requeue (which includes backoff) to try again. - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "pod", req.Name, "ns", req.Namespace, "consul-ns", - r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) - } - - if err := r.writeWorkload(ctx, pod); err != nil { - // Technically this is not needed, but keeping in case this gets refactored in - // a different order - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "pod", req.Name, "ns", req.Namespace, "consul-ns", - r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) - } - - // Create explicit destinations (if any exist) - if err := r.writeDestinations(ctx, pod); err != nil { - // Technically this is not needed, but keeping in case this gets refactored in - // a different order - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "pod", req.Name, "ns", req.Namespace, "consul-ns", - r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) - } - - if err := r.writeHealthStatus(ctx, pod); err != nil { - // Technically this is not needed, but keeping in case this gets refactored in - // a different order - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "pod", req.Name, "ns", req.Namespace, "consul-ns", - r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) - } - } - - return ctrl.Result{}, errs -} - -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Pod{}). - Complete(r) -} - -func (r *Controller) deleteWorkload(ctx context.Context, pod types.NamespacedName) error { - req := &pbresource.DeleteRequest{ - Id: getWorkloadID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), - } - - _, err := r.ResourceClient.Delete(ctx, req) - return err -} - -func (r *Controller) deleteProxyConfiguration(ctx context.Context, pod types.NamespacedName) error { - req := &pbresource.DeleteRequest{ - Id: getProxyConfigurationID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), - } - - _, err := r.ResourceClient.Delete(ctx, req) - return err -} - -// deleteACLTokensForPod finds the ACL tokens that belongs to the pod and delete them from Consul. -// It will only check for ACL tokens that have been created with the auth method this controller -// has been configured with and will only delete tokens for the provided pod Name. -func (r *Controller) deleteACLTokensForPod(apiClient *api.Client, pod types.NamespacedName) error { - // Skip if name is empty. - if pod.Name == "" { - return nil - } - - // Use the V1 logic for getting a compatible namespace - consulNamespace := namespaces.ConsulNamespace( - pod.Namespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, r.EnableNSMirroring, r.NSMirroringPrefix, - ) - - // TODO: create an index for the workloadidentity in Consul, which will also require - // the identity to be attached to the token for templated-policies. - tokens, _, err := apiClient.ACL().TokenListFiltered( - api.ACLTokenFilterOptions{ - AuthMethod: r.AuthMethod, - }, - &api.QueryOptions{ - Namespace: consulNamespace, - }) - if err != nil { - return fmt.Errorf("failed to get a list of tokens from Consul: %s", err) - } - - // We iterate through each token in the auth method, which is terribly inefficient. - // See discussion above about optimizing the token list query. - for _, token := range tokens { - tokenMeta, err := getTokenMetaFromDescription(token.Description) - // It is possible this is from another component, so continue searching - if errors.Is(err, NoMetadataErr) { - continue - } - if err != nil { - return fmt.Errorf("failed to parse token metadata: %s", err) - } - - tokenPodName := strings.TrimPrefix(tokenMeta[tokenMetaPodNameKey], pod.Namespace+"/") - - // If we can't find token's pod, delete it. - if tokenPodName == pod.Name { - r.Log.Info("deleting ACL token", "name", pod.Name, "namespace", pod.Namespace, "ID", token.AccessorID) - if _, err := apiClient.ACL().TokenDelete(token.AccessorID, &api.WriteOptions{Namespace: consulNamespace}); err != nil { - return fmt.Errorf("failed to delete token from Consul: %s", err) - } - } - } - return nil -} - -var NoMetadataErr = fmt.Errorf("failed to extract token metadata from description") - -// getTokenMetaFromDescription parses JSON metadata from token's description. -func getTokenMetaFromDescription(description string) (map[string]string, error) { - re := regexp.MustCompile(`.*({.+})`) - - matches := re.FindStringSubmatch(description) - if len(matches) != 2 { - return nil, NoMetadataErr - } - tokenMetaJSON := matches[1] - - var tokenMeta map[string]string - err := json.Unmarshal([]byte(tokenMetaJSON), &tokenMeta) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal token metadata '%s': %s", tokenMetaJSON, err) - } - - return tokenMeta, nil -} - -func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { - - // TODO: we should add some validation on the required fields here - // e.g. what if token automount is disabled and there is not SA. The API call - // will fail with no indication to the user other than controller logs - ports, workloadPorts := getWorkloadPorts(pod) - - var node corev1.Node - // Ignore errors because we don't want failures to block running services. - _ = r.Client.Get(context.Background(), types.NamespacedName{Name: pod.Spec.NodeName, Namespace: pod.Namespace}, &node) - locality := parseLocality(node) - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: pod.Status.PodIP, Ports: ports}, - }, - Identity: pod.Spec.ServiceAccountName, - Locality: locality, - // Adding a node does not currently work because the node doesn't exist so its health status will always be - // unhealthy, causing any endpoints on that node to also be unhealthy. - // TODO: (v2/nitya) Bring this back when node controller is built. - //NodeName: inject.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), - Ports: workloadPorts, - } - data := inject.ToProtoAny(workload) - - resourceID := getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()) - r.Log.Info("registering workload with Consul", getLogFieldsForResource(resourceID)...) - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: resourceID, - Metadata: metaFromPod(pod), - Data: data, - }, - } - _, err := r.ResourceClient.Write(ctx, req) - return err -} - -func (r *Controller) writeProxyConfiguration(ctx context.Context, pod corev1.Pod) error { - mode, err := r.getTproxyMode(ctx, pod) - if err != nil { - return fmt.Errorf("failed to get transparent proxy mode: %w", err) - } - - exposeConfig, err := r.getExposeConfig(pod) - if err != nil { - return fmt.Errorf("failed to get expose config: %w", err) - } - - bootstrapConfig, err := r.getBootstrapConfig(pod) - if err != nil { - return fmt.Errorf("failed to get bootstrap config: %w", err) - } - - if exposeConfig == nil && - bootstrapConfig == nil && - mode == pbmesh.ProxyMode_PROXY_MODE_DEFAULT { - // It's possible to remove interesting annotations and need to clear any existing config, - // but for now we treat pods as immutable configs owned by other managers. - return nil - } - - pc := &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{pod.GetName()}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: mode, - ExposeConfig: exposeConfig, - }, - BootstrapConfig: bootstrapConfig, - } - data := inject.ToProtoAny(pc) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: getProxyConfigurationID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Metadata: metaFromPod(pod), - Data: data, - }, - } - _, err = r.ResourceClient.Write(ctx, req) - return err -} - -func (r *Controller) getTproxyMode(ctx context.Context, pod corev1.Pod) (pbmesh.ProxyMode, error) { - // A user can enable/disable tproxy for an entire namespace. - var ns corev1.Namespace - err := r.Client.Get(ctx, types.NamespacedName{Name: pod.GetNamespace()}, &ns) - if err != nil { - return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, fmt.Errorf("could not get namespace info for %s: %w", pod.GetNamespace(), err) - } - - tproxyEnabled, err := inject.TransparentProxyEnabled(ns, pod, r.EnableTransparentProxy) - if err != nil { - return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, fmt.Errorf("could not determine if transparent proxy is enabled: %w", err) - } - - if tproxyEnabled { - return pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, nil - } - return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, nil -} - -func (r *Controller) getExposeConfig(pod corev1.Pod) (*pbmesh.ExposeConfig, error) { - // Expose k8s probes as Envoy listeners if needed. - overwriteProbes, err := inject.ShouldOverwriteProbes(pod, r.TProxyOverwriteProbes) - if err != nil { - return nil, fmt.Errorf("could not determine if probes should be overwritten: %w", err) - } - - if !overwriteProbes { - return nil, nil - } - - var originalPod corev1.Pod - err = json.Unmarshal([]byte(pod.Annotations[constants.AnnotationOriginalPod]), &originalPod) - if err != nil { - return nil, fmt.Errorf("failed to get original pod spec: %w", err) - } - - exposeConfig := &pbmesh.ExposeConfig{} - for _, mutatedContainer := range pod.Spec.Containers { - for _, originalContainer := range originalPod.Spec.Containers { - if originalContainer.Name == mutatedContainer.Name { - paths, err := getContainerExposePaths(originalPod, originalContainer, mutatedContainer) - if err != nil { - return nil, fmt.Errorf("error getting container expose path for %s: %w", originalContainer.Name, err) - } - - exposeConfig.ExposePaths = append(exposeConfig.ExposePaths, paths...) - } - } - } - - if len(exposeConfig.ExposePaths) == 0 { - return nil, nil - } - return exposeConfig, nil -} - -func getContainerExposePaths(originalPod corev1.Pod, originalContainer, mutatedContainer corev1.Container) ([]*pbmesh.ExposePath, error) { - var paths []*pbmesh.ExposePath - if mutatedContainer.LivenessProbe != nil && mutatedContainer.LivenessProbe.HTTPGet != nil { - originalLivenessPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.LivenessProbe.HTTPGet.Port) - if err != nil { - return nil, err - } - - newPath := &pbmesh.ExposePath{ - ListenerPort: uint32(mutatedContainer.LivenessProbe.HTTPGet.Port.IntValue()), - LocalPathPort: originalLivenessPort, - Path: mutatedContainer.LivenessProbe.HTTPGet.Path, - } - paths = append(paths, newPath) - } - if mutatedContainer.ReadinessProbe != nil && mutatedContainer.ReadinessProbe.HTTPGet != nil { - originalReadinessPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.ReadinessProbe.HTTPGet.Port) - if err != nil { - return nil, err - } - - newPath := &pbmesh.ExposePath{ - ListenerPort: uint32(mutatedContainer.ReadinessProbe.HTTPGet.Port.IntValue()), - LocalPathPort: originalReadinessPort, - Path: mutatedContainer.ReadinessProbe.HTTPGet.Path, - } - paths = append(paths, newPath) - } - if mutatedContainer.StartupProbe != nil && mutatedContainer.StartupProbe.HTTPGet != nil { - originalStartupPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.StartupProbe.HTTPGet.Port) - if err != nil { - return nil, err - } - - newPath := &pbmesh.ExposePath{ - ListenerPort: uint32(mutatedContainer.StartupProbe.HTTPGet.Port.IntValue()), - LocalPathPort: originalStartupPort, - Path: mutatedContainer.StartupProbe.HTTPGet.Path, - } - paths = append(paths, newPath) - } - return paths, nil -} - -func (r *Controller) getBootstrapConfig(pod corev1.Pod) (*pbmesh.BootstrapConfig, error) { - bootstrap := &pbmesh.BootstrapConfig{} - - // If metrics are enabled, the BootstrapConfig should set envoy_prometheus_bind_addr to a listener on 0.0.0.0 on - // the PrometheusScrapePort. The backend for this listener will be determined by - // the consul-dataplane command line flags generated by the webhook. - // If there is a merged metrics server, the backend would be that server. - // If we are not running the merged metrics server, the backend should just be the Envoy metrics endpoint. - enableMetrics, err := r.MetricsConfig.EnableMetrics(pod) - if err != nil { - return nil, fmt.Errorf("error determining if metrics are enabled: %w", err) - } - if enableMetrics { - prometheusScrapePort, err := r.MetricsConfig.PrometheusScrapePort(pod) - if err != nil { - return nil, err - } - prometheusScrapeListener := fmt.Sprintf("0.0.0.0:%s", prometheusScrapePort) - bootstrap.PrometheusBindAddr = prometheusScrapeListener - } - - if r.EnableTelemetryCollector { - bootstrap.TelemetryCollectorBindSocketDir = DefaultTelemetryBindSocketDir - } - - if proto.Equal(bootstrap, &pbmesh.BootstrapConfig{}) { - return nil, nil - } - return bootstrap, nil -} - -func (r *Controller) writeHealthStatus(ctx context.Context, pod corev1.Pod) error { - status := getHealthStatusFromPod(pod) - - hs := &pbcatalog.HealthStatus{ - Type: constants.ConsulKubernetesCheckType, - Status: status, - Description: constants.ConsulKubernetesCheckName, - Output: getHealthStatusReason(status, pod), - } - data := inject.ToProtoAny(hs) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: getHealthStatusID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Owner: getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Metadata: metaFromPod(pod), - Data: data, - }, - } - _, err := r.ResourceClient.Write(ctx, req) - return err -} - -// TODO: delete ACL token for workload -// deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. -// It will only check for ACL tokens that have been created with the auth method this controller -// has been configured with and will only delete tokens for the provided podName. -// func (r *Controller) deleteACLTokensForWorkload(apiClient *api.Client, svc *api.AgentService, k8sNS, podName string) error { - -// writeDestinations will write explicit destinations if pod annotations exist. -func (r *Controller) writeDestinations(ctx context.Context, pod corev1.Pod) error { - uss, err := inject.ProcessPodDestinations(pod, r.EnableConsulPartitions, r.EnableConsulNamespaces) - if err != nil { - return fmt.Errorf("error processing destination annotations: %s", err.Error()) - } - if uss == nil { - return nil - } - - data := inject.ToProtoAny(uss) - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: getDestinationsID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Metadata: metaFromPod(pod), - Data: data, - }, - } - _, err = r.ResourceClient.Write(ctx, req) - - return err -} - -func (r *Controller) deleteDestinations(ctx context.Context, pod types.NamespacedName) error { - req := &pbresource.DeleteRequest{ - Id: getDestinationsID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), - } - - _, err := r.ResourceClient.Delete(ctx, req) - return err -} - -// consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace -// depending on Consul Namespaces being enabled and the value of namespace mirroring. -func (r *Controller) getConsulNamespace(kubeNamespace string) string { - ns := namespaces.ConsulNamespace( - kubeNamespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO: remove this if and when the default namespace of resources change. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} - -func (r *Controller) getPartition() string { - if !r.EnableConsulPartitions || r.ConsulPartition == "" { - return constants.DefaultConsulPartition - } - return r.ConsulPartition -} - -func getWorkloadPorts(pod corev1.Pod) ([]string, map[string]*pbcatalog.WorkloadPort) { - ports := make([]string, 0) - workloadPorts := map[string]*pbcatalog.WorkloadPort{} - - for _, container := range pod.Spec.Containers { - for _, port := range container.Ports { - name := inject.WorkloadPortName(&port) - - // TODO: error check reserved "mesh" keyword and 20000 - - if port.Protocol != corev1.ProtocolTCP { - // TODO: also throw an error here - continue - } - - ports = append(ports, name) - workloadPorts[name] = &pbcatalog.WorkloadPort{ - Port: uint32(port.ContainerPort), - - // We leave the protocol unspecified so that it can be inherited from the Service appProtocol - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - } - } - } - - ports = append(ports, "mesh") - workloadPorts["mesh"] = &pbcatalog.WorkloadPort{ - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - } - - return ports, workloadPorts -} - -func parseLocality(node corev1.Node) *pbcatalog.Locality { - region := node.Labels[corev1.LabelTopologyRegion] - zone := node.Labels[corev1.LabelTopologyZone] - - if region == "" { - return nil - } - - return &pbcatalog.Locality{ - Region: region, - Zone: zone, - } -} - -func metaFromPod(pod corev1.Pod) map[string]string { - // TODO: allow custom workload metadata - meta := map[string]string{ - constants.MetaKeyKubeNS: pod.GetNamespace(), - constants.MetaKeyManagedBy: constants.ManagedByPodValue, - } - - if gatewayKind := pod.Annotations[constants.AnnotationGatewayKind]; gatewayKind != "" { - meta[constants.MetaGatewayKind] = gatewayKind - } - - return meta -} - -// getHealthStatusFromPod checks the Pod for a "Ready" condition that is true. -// This is true when all the containers are ready, vs. "Running" on the PodPhase, -// which is true if any container is running. -func getHealthStatusFromPod(pod corev1.Pod) pbcatalog.Health { - if pod.Status.Conditions == nil { - return pbcatalog.Health_HEALTH_CRITICAL - } - - for _, condition := range pod.Status.Conditions { - if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue { - return pbcatalog.Health_HEALTH_PASSING - } - } - - return pbcatalog.Health_HEALTH_CRITICAL -} - -// getHealthStatusReason takes Consul's health check status (either passing or critical) -// and the pod to return a descriptive output for the HealthStatus Output. -func getHealthStatusReason(state pbcatalog.Health, pod corev1.Pod) string { - if state == pbcatalog.Health_HEALTH_PASSING { - return constants.KubernetesSuccessReasonMsg - } - - return fmt.Sprintf("Pod \"%s/%s\" is not ready", pod.GetNamespace(), pod.GetName()) -} - -func getWorkloadID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func getHealthStatusID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.HealthStatusType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func getDestinationsID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbmesh.DestinationsType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func getLogFieldsForResource(id *pbresource.ID) []any { - return []any{ - "name", id.Name, - "ns", id.Tenancy.Namespace, - "partition", id.Tenancy.Partition, - } -} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go deleted file mode 100644 index 614526254e..0000000000 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ /dev/null @@ -1,765 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package pod - -import ( - "context" - "testing" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - capi "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -const ( - testPodName = "foo" - testPartition = "my-partition" -) - -type testCase struct { - name string - podName string // This needs to be aligned with the pod created in `k8sObjects` - podNamespace string // Defaults to metav1.NamespaceDefault if empty. - partition string - - k8sObjects func() []runtime.Object // testing node is injected separately - - // Pod Controller Settings - acls bool - tproxy bool - overwriteProbes bool - metrics bool - telemetry bool - - namespaceMirroring bool - namespaceDestination string - namespacePrefix string - - // Initial Consul state. - existingConsulNamespace string // This namespace will be populated before the test is executed. - existingWorkload *pbcatalog.Workload - existingHealthStatus *pbcatalog.HealthStatus - existingProxyConfiguration *pbmesh.ProxyConfiguration - existingDestinations *pbmesh.Destinations - - // Expected Consul state. - expectedConsulNamespace string // This namespace will be used to query Consul for the results - expectedWorkload *pbcatalog.Workload - expectedHealthStatus *pbcatalog.HealthStatus - expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedDestinations *pbmesh.Destinations - - // Reconcile loop outputs - expErr string - expRequeue bool // The response from the reconcile function -} - -// TestReconcileCreatePodWithMirrorNamespaces creates a Pod object in a non-default NS and Partition -// with namespaces set to mirroring -func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "kitchen sink new pod, ns and partition", - podName: testPodName, - partition: constants.DefaultConsulPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, metav1.NamespaceDefault, true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - expectedConsulNamespace: constants.DefaultConsulNS, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "kitchen sink new pod, non-default ns and partition", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - existingConsulNamespace: "bar", - - expectedConsulNamespace: "bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "new pod with namespace prefix", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - namespacePrefix: "foo-", - - existingConsulNamespace: "foo-bar", - - expectedConsulNamespace: "foo-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - }, - { - name: "namespace mirroring overrides destination namespace", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - namespaceDestination: "supernova", - - existingConsulNamespace: "bar", - - expectedConsulNamespace: "bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - }, - { - name: "new pod with explicit destinations, ns and partition", - podName: testPodName, - partition: constants.DefaultConsulPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, metav1.NamespaceDefault, true, true) - addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" - return []runtime.Object{pod} - }, - tproxy: false, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - expectedConsulNamespace: constants.DefaultConsulNS, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedDestinations: createDestinations(), - }, - { - name: "namespace in Consul does not exist", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - - // The equivalent namespace in Consul does not exist, so requeue for backoff. - expRequeue: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileUpdatePodWithMirrorNamespaces updates a Pod object in a non-default NS and Partition -// with namespaces set to mirroring. -func TestReconcileUpdatePodWithMirrorNamespaces(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "update pod health", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, false) // failing - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - namespacePrefix: "foo-", - - existingConsulNamespace: "foo-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - - expectedConsulNamespace: "foo-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus(testPodName, "bar"), - }, - { - name: "duplicated pod event", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - existingConsulNamespace: "bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - - expectedConsulNamespace: "bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileDeletePodWithMirrorNamespaces deletes a Pod object in a non-default NS and Partition -// with namespaces set to mirroring. -func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "delete kitchen sink pod", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - existingConsulNamespace: "bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - - expectedConsulNamespace: "bar", - }, - { - name: "delete pod w/ explicit destinations", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - existingConsulNamespace: "bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingDestinations: createDestinations(), - - expectedConsulNamespace: "bar", - }, - { - name: "delete pod with namespace prefix", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - namespaceMirroring: true, - namespacePrefix: "foo-", - - existingConsulNamespace: "foo-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - - expectedConsulNamespace: "foo-bar", - }, - { - name: "resources are already gone in Consul", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - existingConsulNamespace: "bar", - - expectedConsulNamespace: "bar", - }, - { - name: "namespace is already missing in Consul", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - namespaceMirroring: true, - - expectedConsulNamespace: "bar", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileCreatePodWithDestinationNamespace creates a Pod object in a non-default NS and Partition -// with namespaces set to a destination. -func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "kitchen sink new pod, ns and partition", - podName: testPodName, - partition: constants.DefaultConsulPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, metav1.NamespaceDefault, true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: constants.DefaultConsulNS, - - existingConsulNamespace: constants.DefaultConsulNS, - - expectedConsulNamespace: constants.DefaultConsulNS, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "new pod with explicit destinations, ns and partition", - podName: testPodName, - partition: constants.DefaultConsulPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, metav1.NamespaceDefault, true, true) - addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" - return []runtime.Object{pod} - }, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: constants.DefaultConsulNS, - - existingConsulNamespace: constants.DefaultConsulNS, - - expectedConsulNamespace: constants.DefaultConsulNS, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedDestinations: createDestinations(), - }, - { - name: "kitchen sink new pod, non-default ns and partition", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "namespace in Consul does not exist", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - return []runtime.Object{pod} - }, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - // The equivalent namespace in Consul does not exist, so requeue for backoff. - expRequeue: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileUpdatePodWithDestinationNamespace updates a Pod object in a non-default NS and Partition -// with namespaces set to a destination. -func TestReconcileUpdatePodWithDestinationNamespace(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "update pod health", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, false) // failing - return []runtime.Object{pod} - }, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus(testPodName, "bar"), - }, - { - name: "duplicated pod event", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileDeletePodWithDestinationNamespace deletes a Pod object in a non-default NS and Partition -// with namespaces set to a destination. -func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "delete kitchen sink pod", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - }, - { - name: "delete pod with explicit destinations", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingDestinations: createDestinations(), - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - }, - { - name: "resources are already gone in Consul", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - }, - { - name: "namespace is already missing in Consul", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -func runControllerTest(t *testing.T, tc testCase) { - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - nsBar := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - - k8sObjects := []runtime.Object{ - &ns, - &nsBar, - &node, - } - if tc.k8sObjects != nil { - k8sObjects = append(k8sObjects, tc.k8sObjects()...) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - adminToken := "123e4567-e89b-12d3-a456-426614174000" - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - if tc.acls { - c.ACL.Enabled = tc.acls - c.ACL.Tokens.InitialManagement = adminToken - } - }) - - // Create the partition in Consul. - if tc.partition != "" { - testClient.Cfg.APIClientConfig.Partition = tc.partition - - partition := &capi.Partition{ - Name: tc.partition, - } - _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) - require.NoError(t, err) - } - - // Create the namespace in Consul if specified. - if tc.existingConsulNamespace != "" { - namespace := &capi.Namespace{ - Name: tc.existingConsulNamespace, - Partition: tc.partition, - } - - _, _, err := testClient.APIClient.Namespaces().Create(namespace, nil) - require.NoError(t, err) - } - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ConsulTenancyConfig: common.ConsulTenancyConfig{ - EnableConsulNamespaces: true, - NSMirroringPrefix: tc.namespacePrefix, - EnableNSMirroring: tc.namespaceMirroring, - ConsulDestinationNamespace: tc.namespaceDestination, - EnableConsulPartitions: true, - ConsulPartition: tc.partition, - }, - TProxyOverwriteProbes: tc.overwriteProbes, - EnableTransparentProxy: tc.tproxy, - EnableTelemetryCollector: tc.telemetry, - } - if tc.metrics { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "1234", - } - } - if tc.acls { - pc.AuthMethod = test.AuthMethod - } - - podNamespace := tc.podNamespace - if podNamespace == "" { - podNamespace = metav1.NamespaceDefault - } - - workloadID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) - loadResource(t, context.Background(), testClient.ResourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, context.Background(), testClient.ResourceClient, getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingHealthStatus, workloadID) - loadResource(t, context.Background(), testClient.ResourceClient, getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingProxyConfiguration, nil) - loadResource(t, context.Background(), testClient.ResourceClient, getDestinationsID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingDestinations, nil) - - namespacedName := types.NamespacedName{ - Namespace: podNamespace, - Name: tc.podName, - } - - resp, err := pc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, tc.expRequeue, resp.Requeue) - - wID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedWorkloadMatches(t, context.Background(), testClient.ResourceClient, wID, tc.expectedWorkload) - - hsID := getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedHealthStatusMatches(t, context.Background(), testClient.ResourceClient, hsID, tc.expectedHealthStatus) - - pcID := getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedProxyConfigurationMatches(t, context.Background(), testClient.ResourceClient, pcID, tc.expectedProxyConfiguration) - - uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tc.expectedDestinations) -} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go deleted file mode 100644 index 8d08852ed1..0000000000 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ /dev/null @@ -1,2149 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package pod - -import ( - "context" - "encoding/json" - "fmt" - "testing" - "time" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -const ( - // TODO: (v2/nitya) Bring back consulLocalityNodeName once node controller is implemented and assertions for - // workloads need node names again. - nodeName = "test-node" - localityNodeName = "test-node-w-locality" - consulNodeName = "test-node-virtual" -) - -func TestParseLocality(t *testing.T) { - t.Run("no labels", func(t *testing.T) { - n := corev1.Node{} - require.Nil(t, parseLocality(n)) - }) - - t.Run("zone only", func(t *testing.T) { - n := corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } - require.Nil(t, parseLocality(n)) - }) - - t.Run("everything", func(t *testing.T) { - n := corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - corev1.LabelTopologyRegion: "us-west-1", - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } - require.True(t, proto.Equal(&pbcatalog.Locality{Region: "us-west-1", Zone: "us-west-1a"}, parseLocality(n))) - }) -} - -func TestWorkloadWrite(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - Namespace: metav1.NamespaceDefault, - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - localityNode := corev1.Node{ObjectMeta: metav1.ObjectMeta{ - Name: localityNodeName, - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{ - corev1.LabelTopologyRegion: "us-east1", - corev1.LabelTopologyZone: "us-east1-b", - }, - }} - - type testCase struct { - name string - pod *corev1.Pod - podModifier func(pod *corev1.Pod) - expectedWorkload *pbcatalog.Workload - } - - run := func(t *testing.T, tc testCase) { - if tc.podModifier != nil { - tc.podModifier(tc.pod) - } - - k8sObjects := []runtime.Object{ - &ns, - &node, - &localityNode, - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: testClient.ResourceClient, - } - - err := pc.writeWorkload(context.Background(), *tc.pod) - require.NoError(t, err) - - req := &pbresource.ReadRequest{ - Id: getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - actualRes, err := testClient.ResourceClient.Read(context.Background(), req) - require.NoError(t, err) - require.NotNil(t, actualRes) - - requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) - require.NotNil(t, actualRes.GetResource().GetData()) - - actualWorkload := &pbcatalog.Workload{} - err = actualRes.GetResource().GetData().UnmarshalTo(actualWorkload) - require.NoError(t, err) - - require.True(t, proto.Equal(actualWorkload, tc.expectedWorkload)) - } - - testCases := []testCase{ - { - name: "multi-port single-container", - pod: createPod("foo", "", true, true), - expectedWorkload: createWorkload(), - }, - { - name: "multi-port multi-container", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - container := corev1.Container{ - Name: "logger", - Ports: []corev1.ContainerPort{ - { - Name: "agent", - Protocol: corev1.ProtocolTCP, - ContainerPort: 6666, - }, - }, - } - pod.Spec.Containers = append(pod.Spec.Containers, container) - }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "agent", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "agent": { - Port: 6666, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Identity: "foo", - }, - }, - { - name: "pod with locality", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Spec.NodeName = localityNodeName - }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Locality: &pbcatalog.Locality{ - Region: "us-east1", - Zone: "us-east1-b", - }, - Identity: "foo", - }, - }, - { - name: "pod with unnamed ports", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Spec.Containers[0].Ports[0].Name = "" - pod.Spec.Containers[0].Ports[1].Name = "" - }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"cslport-80", "cslport-8080", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "cslport-80": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "cslport-8080": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Identity: "foo", - }, - }, - { - name: "pod with no ports", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Spec.Containers[0].Ports = nil - }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Identity: "foo", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestWorkloadDelete(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - pod *corev1.Pod - existingWorkload *pbcatalog.Workload - } - - run := func(t *testing.T, tc testCase) { - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: testClient.ResourceClient, - } - - workload, err := anypb.New(tc.existingWorkload) - require.NoError(t, err) - - workloadID := getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) - writeReq := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: workloadID, - Data: workload, - }, - } - - _, err = testClient.ResourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), testClient.ResourceClient, workloadID) - - reconcileReq := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: tc.pod.GetName(), - } - err = pc.deleteWorkload(context.Background(), reconcileReq) - require.NoError(t, err) - - readReq := &pbresource.ReadRequest{ - Id: getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - _, err = testClient.ResourceClient.Read(context.Background(), readReq) - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - } - - testCases := []testCase{ - { - name: "basic pod delete", - pod: createPod("foo", "", true, true), - existingWorkload: createWorkload(), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestHealthStatusWrite(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - pod *corev1.Pod - podModifier func(pod *corev1.Pod) - expectedHealthStatus *pbcatalog.HealthStatus - } - - run := func(t *testing.T, tc testCase) { - if tc.podModifier != nil { - tc.podModifier(tc.pod) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: testClient.ResourceClient, - } - - // The owner of a resource is validated, so create a dummy workload for the HealthStatus - workloadData, err := anypb.New(createWorkload()) - require.NoError(t, err) - - workloadID := getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) - writeReq := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: workloadID, - Data: workloadData, - }, - } - _, err = testClient.ResourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - - // Test writing the pod to a HealthStatus - err = pc.writeHealthStatus(context.Background(), *tc.pod) - require.NoError(t, err) - - req := &pbresource.ReadRequest{ - Id: getHealthStatusID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - actualRes, err := testClient.ResourceClient.Read(context.Background(), req) - require.NoError(t, err) - require.NotNil(t, actualRes) - - requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) - require.NotNil(t, actualRes.GetResource().GetData()) - - actualHealthStatus := &pbcatalog.HealthStatus{} - err = actualRes.GetResource().GetData().UnmarshalTo(actualHealthStatus) - require.NoError(t, err) - - require.True(t, proto.Equal(actualHealthStatus, tc.expectedHealthStatus)) - } - - testCases := []testCase{ - { - name: "ready pod", - pod: createPod("foo", "", true, true), - expectedHealthStatus: createPassingHealthStatus(), - }, - { - name: "not ready pod", - pod: createPod("foo", "", true, false), - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - }, - { - name: "pod with no condition", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Status.Conditions = []corev1.PodCondition{} - }, - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestProxyConfigurationWrite(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - pod *corev1.Pod - podModifier func(pod *corev1.Pod) - expectedProxyConfiguration *pbmesh.ProxyConfiguration - - tproxy bool - overwriteProbes bool - metrics bool - telemetry bool - } - - run := func(t *testing.T, tc testCase) { - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - - nsTproxy := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: "tproxy-party", - Labels: map[string]string{ - constants.KeyTransparentProxy: "true", - }, - }} - - if tc.podModifier != nil { - tc.podModifier(tc.pod) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(&ns, &nsTproxy).Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - EnableTransparentProxy: tc.tproxy, - TProxyOverwriteProbes: tc.overwriteProbes, - EnableTelemetryCollector: tc.telemetry, - ResourceClient: testClient.ResourceClient, - } - - if tc.metrics { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "5678", - } - } - - // Test writing the pod to a HealthStatus - err := pc.writeProxyConfiguration(context.Background(), *tc.pod) - require.NoError(t, err) - - req := &pbresource.ReadRequest{ - Id: getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - actualRes, err := testClient.ResourceClient.Read(context.Background(), req) - - if tc.expectedProxyConfiguration == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, actualRes) - - requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) - require.NotNil(t, actualRes.GetResource().GetData()) - - actualProxyConfiguration := &pbmesh.ProxyConfiguration{} - err = actualRes.GetResource().GetData().UnmarshalTo(actualProxyConfiguration) - require.NoError(t, err) - - diff := cmp.Diff(actualProxyConfiguration, tc.expectedProxyConfiguration, test.CmpProtoIgnoreOrder()...) - require.Equal(t, "", diff) - } - - testCases := []testCase{ - { - name: "no tproxy, no telemetry, no metrics, no probe overwrite", - pod: createPod("foo", "", true, true), - expectedProxyConfiguration: nil, - }, - { - name: "kitchen sink - globally enabled", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - addProbesAndOriginalPodAnnotation(pod) - }, - tproxy: true, - overwriteProbes: true, - metrics: true, - telemetry: true, - expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - PrometheusBindAddr: "0.0.0.0:5678", - TelemetryCollectorBindSocketDir: DefaultTelemetryBindSocketDir, - }, - }, - }, - { - name: "tproxy, metrics, and probe overwrite enabled on pod", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Annotations[constants.KeyTransparentProxy] = "true" - pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "true" - pod.Annotations[constants.AnnotationEnableMetrics] = "true" - pod.Annotations[constants.AnnotationPrometheusScrapePort] = "21234" - - addProbesAndOriginalPodAnnotation(pod) - }, - expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - PrometheusBindAddr: "0.0.0.0:21234", - }, - }, - }, - { - name: "tproxy enabled on namespace", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Namespace = "tproxy-party" - }, - expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func requireEqualID(t *testing.T, res *pbresource.ReadResponse, name string, ns string, partition string) { - require.Equal(t, name, res.GetResource().GetId().GetName()) - require.Equal(t, ns, res.GetResource().GetId().GetTenancy().GetNamespace()) - require.Equal(t, partition, res.GetResource().GetId().GetTenancy().GetPartition()) -} - -func TestProxyConfigurationDelete(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - pod *corev1.Pod - existingProxyConfiguration *pbmesh.ProxyConfiguration - } - - run := func(t *testing.T, tc testCase) { - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: testClient.ResourceClient, - } - - // Create the existing ProxyConfiguration - pcData, err := anypb.New(tc.existingProxyConfiguration) - require.NoError(t, err) - - pcID := getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) - writeReq := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: pcID, - Data: pcData, - }, - } - - _, err = testClient.ResourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), testClient.ResourceClient, pcID) - - reconcileReq := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: tc.pod.GetName(), - } - err = pc.deleteProxyConfiguration(context.Background(), reconcileReq) - require.NoError(t, err) - - readReq := &pbresource.ReadRequest{ - Id: getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - _, err = testClient.ResourceClient.Read(context.Background(), readReq) - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - } - - testCases := []testCase{ - { - name: "proxy configuration delete", - pod: createPod("foo", "", true, true), - existingProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// TestDestinationsWrite does a subsampling of tests covered in TestProcessUpstreams to make sure things are hooked up -// correctly. For the sake of test speed, more exhaustive testing is performed in TestProcessUpstreams. -func TestDestinationsWrite(t *testing.T) { - t.Parallel() - - const podName = "pod1" - - cases := []struct { - name string - pod func() *corev1.Pod - expected *pbmesh.Destinations - expErr string - consulNamespacesEnabled bool - consulPartitionsEnabled bool - }{ - { - name: "labeled annotated destination with svc only", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc:1234" - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc, ns, and peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.peer1.peer:1234" - return pod1 - }, - expErr: "error processing destination annotations: destination currently does not support peers: destination.port.upstream1.svc.ns1.ns.peer1.peer:1234", - // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Destinations: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: "peer1", - // }, - // Name: "upstream1", - // }, - // DestinationPort: "destination", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc, ns, and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.part1.ap:1234" - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "part1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: invalid partition/dc/peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.part1.err:1234" - return pod1 - }, - expErr: "error processing destination annotations: destination structured incorrectly: destination.port.upstream1.svc.ns1.ns.part1.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single destination", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.upstream:1234" - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single destination with namespace and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.upstream.foo.bar:1234" - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "bar", - Namespace: "foo", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - // Create test consulServer client. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - pc := &Controller{ - Log: logrtest.New(t), - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ConsulTenancyConfig: common.ConsulTenancyConfig{ - EnableConsulNamespaces: tt.consulNamespacesEnabled, - EnableConsulPartitions: tt.consulPartitionsEnabled, - }, - ResourceClient: testClient.ResourceClient, - } - - err := pc.writeDestinations(context.Background(), *tt.pod()) - - if tt.expErr != "" { - require.EqualError(t, err, tt.expErr) - } else { - require.NoError(t, err) - uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tt.expected) - } - }) - } -} - -func TestDestinationsDelete(t *testing.T) { - t.Parallel() - - const podName = "pod1" - - cases := []struct { - name string - pod func() *corev1.Pod - existingDestinations *pbmesh.Destinations - expErr string - configEntry func() api.ConfigEntry - consulUnavailable bool - }{ - { - name: "labeled annotated destination with svc only", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc:1234" - return pod1 - }, - existingDestinations: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - pc := &Controller{ - Log: logrtest.New(t), - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: testClient.ResourceClient, - } - - // Load in the upstream for us to delete and check that it's there - loadResource(t, context.Background(), testClient.ResourceClient, getDestinationsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.existingDestinations, nil) - uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tt.existingDestinations) - - // Delete the upstream - nn := types.NamespacedName{Name: tt.pod().Name} - err := pc.deleteDestinations(context.Background(), nn) - - // Verify the upstream has been deleted or that an expected error has been returned - if tt.expErr != "" { - require.EqualError(t, err, tt.expErr) - } else { - require.NoError(t, err) - uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, nil) - } - }) - } -} - -func TestDeleteACLTokens(t *testing.T) { - t.Parallel() - - podName := "foo-123" - serviceName := "foo" - - // Create test consulServer server. - masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.ACL.Enabled = true - c.ACL.Tokens.InitialManagement = masterToken - c.Experiments = []string{"resource-apis"} - }) - - test.SetupK8sAuthMethodV2(t, testClient.APIClient, serviceName, metav1.NamespaceDefault) - token, _, err := testClient.APIClient.ACL().Login(&api.ACLLoginParams{ - AuthMethod: test.AuthMethod, - BearerToken: test.ServiceAccountJWTToken, - Meta: map[string]string{ - "pod": fmt.Sprintf("%s/%s", metav1.NamespaceDefault, podName), - "component": "connect-injector", - }, - }, nil) - require.NoError(t, err) - - pc := &Controller{ - Log: logrtest.New(t), - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: testClient.ResourceClient, - AuthMethod: test.AuthMethod, - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - } - - // Delete the ACL Token - pod := types.NamespacedName{Name: podName, Namespace: metav1.NamespaceDefault} - err = pc.deleteACLTokensForPod(testClient.APIClient, pod) - require.NoError(t, err) - - // Verify the token has been deleted. - _, _, err = testClient.APIClient.ACL().TokenRead(token.AccessorID, nil) - require.Contains(t, err.Error(), "ACL not found") -} - -// TestReconcileCreatePod ensures that a new pod reconciliation fans out to create -// the appropriate Consul resources. Translation details from pod to Consul workload are -// tested at the relevant private functions. Any error states that are also tested here. -func TestReconcileCreatePod(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - - type testCase struct { - name string - podName string // This needs to be aligned with the pod created in `k8sObjects` - namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod - - k8sObjects func() []runtime.Object // testing node is injected separately - expectedWorkload *pbcatalog.Workload - expectedHealthStatus *pbcatalog.HealthStatus - expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedDestinations *pbmesh.Destinations - - tproxy bool - overwriteProbes bool - metrics bool - telemetry bool - - requeue bool - expErr string - } - - run := func(t *testing.T, tc testCase) { - k8sObjects := []runtime.Object{ - &ns, - &node, - } - if tc.k8sObjects != nil { - k8sObjects = append(k8sObjects, tc.k8sObjects()...) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - TProxyOverwriteProbes: tc.overwriteProbes, - EnableTransparentProxy: tc.tproxy, - EnableTelemetryCollector: tc.telemetry, - } - if tc.metrics { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "1234", - } - } - - namespace := tc.namespace - if namespace == "" { - namespace = metav1.NamespaceDefault - } - - namespacedName := types.NamespacedName{ - Namespace: namespace, - Name: tc.podName, - } - - resp, err := pc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.Equal(t, tc.requeue, resp.Requeue) - - wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, context.Background(), testClient.ResourceClient, wID, tc.expectedWorkload) - - hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, context.Background(), testClient.ResourceClient, hsID, tc.expectedHealthStatus) - - pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, context.Background(), testClient.ResourceClient, pcID, tc.expectedProxyConfiguration) - - uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tc.expectedDestinations) - } - - testCases := []testCase{ - { - name: "vanilla new mesh-injected pod", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "vanilla new gateway pod (not mesh-injected)", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", false, true) - pod.Annotations[constants.AnnotationGatewayKind] = "mesh-gateway" - pod.Annotations[constants.AnnotationMeshInject] = "false" - pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "false" - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration("foo", false, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "pod in ignored namespace", - podName: "foo", - namespace: metav1.NamespaceSystem, - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - pod.ObjectMeta.Namespace = metav1.NamespaceSystem - return []runtime.Object{pod} - }, - }, - { - name: "unhealthy new pod", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, false) - return []runtime.Object{pod} - }, - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - }, - { - name: "return error - pod has no original pod annotation", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, false) - return []runtime.Object{pod} - }, - tproxy: true, - overwriteProbes: true, - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - expErr: "1 error occurred:\n\t* failed to get expose config: failed to get original pod spec: unexpected end of JSON input\n\n", - }, - { - name: "pod has not been injected", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", false, true) - return []runtime.Object{pod} - }, - }, - { - name: "pod with annotations", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" - return []runtime.Object{pod} - }, - tproxy: false, - telemetry: true, - metrics: true, - overwriteProbes: true, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedDestinations: createDestinations(), - }, - { - name: "pod w/o IP", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - pod.Status.PodIP = "" - return []runtime.Object{pod} - }, - requeue: true, - }, - // TODO: make sure multi-error accumulates errors - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// TestReconcileUpdatePod test updating a Pod object when there is already matching resources in Consul. -// Updates are unlikely because of the immutable behaviors of pods as members of deployment/statefulset, -// but theoretically it is possible to update annotations and labels in-place. Most likely this will be -// from a change in health status. -func TestReconcileUpdatePod(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - - type testCase struct { - name string - podName string // This needs to be aligned with the pod created in `k8sObjects` - namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod - - k8sObjects func() []runtime.Object // testing node is injected separately - - existingWorkload *pbcatalog.Workload - existingHealthStatus *pbcatalog.HealthStatus - existingProxyConfiguration *pbmesh.ProxyConfiguration - existingDestinations *pbmesh.Destinations - - expectedWorkload *pbcatalog.Workload - expectedHealthStatus *pbcatalog.HealthStatus - expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedDestinations *pbmesh.Destinations - - tproxy bool - overwriteProbes bool - metrics bool - telemetry bool - - expErr string - } - - run := func(t *testing.T, tc testCase) { - k8sObjects := []runtime.Object{ - &ns, - &node, - } - if tc.k8sObjects != nil { - k8sObjects = append(k8sObjects, tc.k8sObjects()...) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - TProxyOverwriteProbes: tc.overwriteProbes, - EnableTransparentProxy: tc.tproxy, - EnableTelemetryCollector: tc.telemetry, - } - if tc.metrics { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "1234", - } - } - - namespace := tc.namespace - if namespace == "" { - namespace = metav1.NamespaceDefault - } - - workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) - loadResource(t, context.Background(), testClient.ResourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, context.Background(), testClient.ResourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) - loadResource(t, context.Background(), testClient.ResourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) - loadResource(t, context.Background(), testClient.ResourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) - - namespacedName := types.NamespacedName{ - Namespace: namespace, - Name: tc.podName, - } - - resp, err := pc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, context.Background(), testClient.ResourceClient, wID, tc.expectedWorkload) - - hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, context.Background(), testClient.ResourceClient, hsID, tc.expectedHealthStatus) - - pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, context.Background(), testClient.ResourceClient, pcID, tc.expectedProxyConfiguration) - - uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), testClient.ResourceClient, uID, tc.expectedDestinations) - } - - testCases := []testCase{ - { - name: "pod update ports", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - return []runtime.Object{pod} - }, - existingHealthStatus: createPassingHealthStatus(), - existingWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: consulNodeName, - Identity: "foo", - }, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - }, - { - name: "pod healthy to unhealthy", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, false) - return []runtime.Object{pod} - }, - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - }, - { - name: "add metrics, tproxy and probe overwrite to pod", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - pod.Annotations[constants.KeyTransparentProxy] = "true" - pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "true" - pod.Annotations[constants.AnnotationEnableMetrics] = "true" - pod.Annotations[constants.AnnotationPrometheusScrapePort] = "21234" - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - PrometheusBindAddr: "0.0.0.0:21234", - }, - }, - }, - { - name: "pod update explicit destination", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" - return []runtime.Object{pod} - }, - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingDestinations: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "ap1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "mySVC3", - }, - DestinationPort: "destination2", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedDestinations: createDestinations(), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// Tests deleting a Pod object, with and without matching Consul resources. -func TestReconcileDeletePod(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - Namespace: metav1.NamespaceDefault, - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - - type testCase struct { - name string - podName string // This needs to be aligned with the pod created in `k8sObjects` - namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod - - k8sObjects func() []runtime.Object // testing node is injected separately - - existingWorkload *pbcatalog.Workload - existingHealthStatus *pbcatalog.HealthStatus - existingProxyConfiguration *pbmesh.ProxyConfiguration - existingDestinations *pbmesh.Destinations - - expectedWorkload *pbcatalog.Workload - expectedHealthStatus *pbcatalog.HealthStatus - expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedDestinations *pbmesh.Destinations - - aclsEnabled bool - - expErr string - } - - run := func(t *testing.T, tc testCase) { - k8sObjects := []runtime.Object{ - &ns, - &node, - } - if tc.k8sObjects != nil { - k8sObjects = append(k8sObjects, tc.k8sObjects()...) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - if tc.aclsEnabled { - c.ACL.Enabled = true - c.ACL.Tokens.InitialManagement = masterToken - } - c.Experiments = []string{"resource-apis"} - }) - - ctx := context.Background() - if tc.aclsEnabled { - ctx = metadata.AppendToOutgoingContext(context.Background(), "x-consul-token", masterToken) - } - - // Wait for the default partition to be created - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(ctx, constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - } - if tc.aclsEnabled { - pc.AuthMethod = test.AuthMethod - } - - namespace := tc.namespace - if namespace == "" { - namespace = metav1.NamespaceDefault - } - - workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) - loadResource(t, ctx, testClient.ResourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, ctx, testClient.ResourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) - loadResource(t, ctx, testClient.ResourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) - loadResource(t, ctx, testClient.ResourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) - - var token *api.ACLToken - var err error - if tc.aclsEnabled { - test.SetupK8sAuthMethodV2(t, testClient.APIClient, tc.podName, metav1.NamespaceDefault) //podName is a standin for the service name - token, _, err = testClient.APIClient.ACL().Login(&api.ACLLoginParams{ - AuthMethod: test.AuthMethod, - BearerToken: test.ServiceAccountJWTToken, - Meta: map[string]string{ - "pod": fmt.Sprintf("%s/%s", metav1.NamespaceDefault, tc.podName), - "component": "connect-injector", - }, - }, nil) - require.NoError(t, err) - - // We create another junk token here just to make sure it doesn't interfere with cleaning up the - // previous "real" token that has metadata. - _, _, err = testClient.APIClient.ACL().Login(&api.ACLLoginParams{ - AuthMethod: test.AuthMethod, - BearerToken: test.ServiceAccountJWTToken, - }, nil) - require.NoError(t, err) - } - - namespacedName := types.NamespacedName{ - Namespace: namespace, - Name: tc.podName, - } - - resp, err := pc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, ctx, testClient.ResourceClient, wID, tc.expectedWorkload) - - hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, ctx, testClient.ResourceClient, hsID, tc.expectedHealthStatus) - - pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, ctx, testClient.ResourceClient, pcID, tc.expectedProxyConfiguration) - - uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, ctx, testClient.ResourceClient, uID, tc.expectedDestinations) - - if tc.aclsEnabled { - _, _, err = testClient.APIClient.ACL().TokenRead(token.AccessorID, nil) - require.Contains(t, err.Error(), "ACL not found") - } - - } - - testCases := []testCase{ - { - name: "vanilla delete pod", - podName: "foo", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "annotated delete pod", - podName: "foo", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingDestinations: createDestinations(), - }, - { - name: "delete pod w/ acls", - podName: "foo", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", true, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - aclsEnabled: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// createPod creates a multi-port pod as a base for tests. If `namespace` is empty, -// the default Kube namespace will be used. -func createPod(name, namespace string, inject, ready bool) *corev1.Pod { - if namespace == "" { - namespace = metav1.NamespaceDefault - } - - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.AnnotationConsulK8sVersion: "1.3.0", - }, - }, - Status: corev1.PodStatus{ - PodIP: "10.0.0.1", - HostIP: consulNodeAddress, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - Name: "public", - Protocol: corev1.ProtocolTCP, - ContainerPort: 80, - }, - { - Name: "admin", - Protocol: corev1.ProtocolTCP, - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/readyz", - Port: intstr.FromInt(2000), - }, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/livez", - Port: intstr.FromInt(2001), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/startupz", - Port: intstr.FromInt(2002), - }, - }, - }, - }, - }, - NodeName: nodeName, - ServiceAccountName: name, - }, - } - if ready { - pod.Status.Conditions = []corev1.PodCondition{ - { - Type: corev1.PodReady, - Status: corev1.ConditionTrue, - }, - } - } else { - pod.Status.Conditions = []corev1.PodCondition{ - { - Type: corev1.PodReady, - Status: corev1.ConditionFalse, - }, - } - } - - if inject { - pod.Labels[constants.KeyMeshInjectStatus] = constants.Injected - pod.Annotations[constants.KeyMeshInjectStatus] = constants.Injected - } - return pod -} - -// createWorkload creates a workload that matches the pod from createPod. -func createWorkload() *pbcatalog.Workload { - return &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Identity: "foo", - } -} - -// createPassingHealthStatus creates a passing HealthStatus that matches the pod from createPod. -func createPassingHealthStatus() *pbcatalog.HealthStatus { - return &pbcatalog.HealthStatus{ - Type: constants.ConsulKubernetesCheckType, - Status: pbcatalog.Health_HEALTH_PASSING, - Output: constants.KubernetesSuccessReasonMsg, - Description: constants.ConsulKubernetesCheckName, - } -} - -// createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. -func createCriticalHealthStatus(name string, namespace string) *pbcatalog.HealthStatus { - return &pbcatalog.HealthStatus{ - Type: constants.ConsulKubernetesCheckType, - Status: pbcatalog.Health_HEALTH_CRITICAL, - Output: fmt.Sprintf("Pod \"%s/%s\" is not ready", namespace, name), - Description: constants.ConsulKubernetesCheckName, - } -} - -// createProxyConfiguration creates a proxyConfiguration that matches the pod from createPod, -// assuming that metrics, telemetry, and overwrite probes are enabled separately. -func createProxyConfiguration(podName string, overwriteProbes bool, mode pbmesh.ProxyMode) *pbmesh.ProxyConfiguration { - mesh := &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: mode, - ExposeConfig: nil, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - PrometheusBindAddr: "0.0.0.0:1234", - TelemetryCollectorBindSocketDir: DefaultTelemetryBindSocketDir, - }, - } - - if overwriteProbes { - mesh.DynamicConfig.ExposeConfig = &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - } - } - - if mode == pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT { - mesh.DynamicConfig.TransparentProxy = &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - } - } - - return mesh -} - -// createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. -func createDestinations() *pbmesh.Destinations { - return &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "mySVC", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(24601), - Ip: consulNodeAddress, - }, - }, - }, - }, - } -} - -func expectedWorkloadMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedWorkload *pbcatalog.Workload) { - req := &pbresource.ReadRequest{Id: id} - - res, err := client.Read(ctx, req) - - if expectedWorkload == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - - requireEqualResourceID(t, id, res.GetResource().GetId()) - - require.NotNil(t, res.GetResource().GetData()) - - actualWorkload := &pbcatalog.Workload{} - err = res.GetResource().GetData().UnmarshalTo(actualWorkload) - require.NoError(t, err) - - diff := cmp.Diff(expectedWorkload, actualWorkload, test.CmpProtoIgnoreOrder()...) - require.Equal(t, "", diff, "Workloads do not match") -} - -func expectedHealthStatusMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedHealthStatus *pbcatalog.HealthStatus) { - req := &pbresource.ReadRequest{Id: id} - - res, err := client.Read(ctx, req) - - if expectedHealthStatus == nil { - // Because HealthStatus is asynchronously garbage-collected, we can retry to make sure it gets cleaned up. - require.Eventually(t, func() bool { - _, err := client.Read(ctx, req) - s, ok := status.FromError(err) - return ok && codes.NotFound == s.Code() - }, 3*time.Second, 500*time.Millisecond) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - - requireEqualResourceID(t, id, res.GetResource().GetId()) - - require.NotNil(t, res.GetResource().GetData()) - - actualHealthStatus := &pbcatalog.HealthStatus{} - err = res.GetResource().GetData().UnmarshalTo(actualHealthStatus) - require.NoError(t, err) - - diff := cmp.Diff(expectedHealthStatus, actualHealthStatus, test.CmpProtoIgnoreOrder()...) - require.Equal(t, "", diff, "HealthStatuses do not match") -} - -func expectedProxyConfigurationMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedProxyConfiguration *pbmesh.ProxyConfiguration) { - req := &pbresource.ReadRequest{Id: id} - - res, err := client.Read(ctx, req) - - if expectedProxyConfiguration == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - - requireEqualResourceID(t, id, res.GetResource().GetId()) - - require.NotNil(t, res.GetResource().GetData()) - - actualProxyConfiguration := &pbmesh.ProxyConfiguration{} - err = res.GetResource().GetData().UnmarshalTo(actualProxyConfiguration) - require.NoError(t, err) - - diff := cmp.Diff(expectedProxyConfiguration, actualProxyConfiguration, test.CmpProtoIgnoreOrder()...) - require.Equal(t, "", diff, "ProxyConfigurations do not match") -} - -func expectedDestinationMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedUpstreams *pbmesh.Destinations) { - req := &pbresource.ReadRequest{Id: id} - res, err := client.Read(ctx, req) - - if expectedUpstreams == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - - requireEqualResourceID(t, id, res.GetResource().GetId()) - - require.NotNil(t, res.GetResource().GetData()) - - actualUpstreams := &pbmesh.Destinations{} - err = res.GetResource().GetData().UnmarshalTo(actualUpstreams) - require.NoError(t, err) - - require.True(t, proto.Equal(actualUpstreams, expectedUpstreams)) -} - -func loadResource(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { - if id == nil || !proto.ProtoReflect().IsValid() { - return - } - - data, err := anypb.New(proto) - require.NoError(t, err) - - resource := &pbresource.Resource{ - Id: id, - Data: data, - Owner: owner, - } - - req := &pbresource.WriteRequest{Resource: resource} - _, err = client.Write(ctx, req) - require.NoError(t, err) - test.ResourceHasPersisted(t, ctx, client, id) -} - -func addProbesAndOriginalPodAnnotation(pod *corev1.Pod) { - podBytes, _ := json.Marshal(pod) - pod.Annotations[constants.AnnotationOriginalPod] = string(podBytes) - - // Fake the probe changes that would be added by the mesh webhook - pod.Spec.Containers[0].ReadinessProbe.HTTPGet.Port = intstr.FromInt(20300) - pod.Spec.Containers[0].LivenessProbe.HTTPGet.Port = intstr.FromInt(20400) - pod.Spec.Containers[0].StartupProbe.HTTPGet.Port = intstr.FromInt(20500) -} - -func requireEqualResourceID(t *testing.T, expected, actual *pbresource.ID) { - opts := []cmp.Option{ - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - } - opts = append(opts, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "resource IDs do not match") -} diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go deleted file mode 100644 index 8027995ac9..0000000000 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package serviceaccount - -import ( - "context" - - "github.com/go-logr/logr" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/grpc/metadata" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - defaultServiceAccountName = "default" -) - -type Controller struct { - client.Client - // ConsulServerConnMgr is the watcher for the Consul server addresses used to create Consul API v2 clients. - ConsulServerConnMgr consul.ServerConnectionManager - // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. - common.K8sNamespaceConfig - // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. - common.ConsulTenancyConfig - - Log logr.Logger - - Scheme *runtime.Scheme - context.Context -} - -func (r *Controller) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.ServiceAccount{}). - Complete(r) -} - -// Reconcile reads the state of a ServiceAccount object for a Kubernetes namespace and reconciles the corresponding -// Consul WorkloadIdentity. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var serviceAccount corev1.ServiceAccount - - // Ignore the request if the namespace of the service account is not allowed. - if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - // Create Consul resource service client for this reconcile. - resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - state, err := r.ConsulServerConnMgr.State() - if err != nil { - r.Log.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - if state.Token != "" { - ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) - } - - // We don't allow the default service account synced to prevent unintended TrafficPermissions - if req.Name == defaultServiceAccountName { - r.Log.Info("Not syncing default Kubernetes service account", "namespace", req.Namespace) - return ctrl.Result{}, nil - } - - // If the ServiceAccount object has been deleted (and we get an IsNotFound error), - // we need to deregister that WorkloadIdentity from Consul. - err = r.Client.Get(ctx, req.NamespacedName, &serviceAccount) - if k8serrors.IsNotFound(err) { - err = r.deregisterWorkloadIdentity(ctx, resourceClient, req.Name, r.getConsulNamespace(req.Namespace), r.getConsulPartition()) - return ctrl.Result{}, err - } else if err != nil { - r.Log.Error(err, "failed to get ServiceAccount", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - r.Log.Info("retrieved ServiceAccount", "name", req.Name, "ns", req.Namespace) - - // Ensure the WorkloadIdentity exists. - workloadIdentityResource := r.getWorkloadIdentityResource( - serviceAccount.Name, // Consul and Kubernetes service account name will always match - r.getConsulNamespace(serviceAccount.Namespace), - r.getConsulPartition(), - map[string]string{ - constants.MetaKeyKubeNS: serviceAccount.Namespace, - constants.MetaKeyKubeServiceAccountName: serviceAccount.Name, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - ) - - r.Log.Info("registering workload identity with Consul", getLogFieldsForResource(workloadIdentityResource.Id)...) - // We currently blindly write these records as changes to service accounts and resulting reconciles should be rare, - // and there's no data to conflict with in the payload. - if _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: workloadIdentityResource}); err != nil { - // We could be racing with the namespace controller. - // Requeue (which includes backoff) to try again. - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "service-account", serviceAccount.Name, "ns", serviceAccount.Namespace, - "consul-ns", workloadIdentityResource.GetId().GetTenancy().GetNamespace(), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - - r.Log.Error(err, "failed to register workload identity", getLogFieldsForResource(workloadIdentityResource.Id)...) - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -// deregisterWorkloadIdentity deletes the WorkloadIdentity resource corresponding to the given name and namespace from -// Consul. This operation is idempotent and can be executed for non-existent service accounts. -func (r *Controller) deregisterWorkloadIdentity(ctx context.Context, resourceClient pbresource.ResourceServiceClient, name, namespace, partition string) error { - _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{ - Id: getWorkloadIdentityID(name, namespace, partition), - }) - return err -} - -// getWorkloadIdentityResource converts the given Consul WorkloadIdentity and metadata to a Consul resource API record. -func (r *Controller) getWorkloadIdentityResource(name, namespace, partition string, meta map[string]string) *pbresource.Resource { - return &pbresource.Resource{ - Id: getWorkloadIdentityID(name, namespace, partition), - // WorkloadIdentity is currently an empty message. - Data: inject.ToProtoAny(&pbauth.WorkloadIdentity{}), - Metadata: meta, - } -} - -func getWorkloadIdentityID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace -// depending on Consul Namespaces being enabled and the value of namespace mirroring. -func (r *Controller) getConsulNamespace(kubeNamespace string) string { - ns := namespaces.ConsulNamespace( - kubeNamespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO: remove this if and when the default namespace of resources is no longer required to be set explicitly. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} - -func (r *Controller) getConsulPartition() string { - if !r.EnableConsulPartitions || r.ConsulPartition == "" { - return constants.DefaultConsulPartition - } - return r.ConsulPartition -} - -func getLogFieldsForResource(id *pbresource.ID) []any { - return []any{ - "name", id.Name, - "ns", id.Tenancy.Namespace, - "partition", id.Tenancy.Partition, - } -} diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go deleted file mode 100644 index d90791d093..0000000000 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package serviceaccount - -import ( - "testing" -) - -// TODO(NET-5719): ConsulDestinationNamespace and EnableNSMirroring +/- prefix - -// TODO(NET-5719) -// Tests new WorkloadIdentity registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_CreateWorkloadIdentity_WithNamespaces(t *testing.T) { - //TODO(NET-5719): Add test case to cover Consul namespace missing and check for backoff -} - -// TODO(NET-5719) -// Tests removing WorkloadIdentity registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_DeleteWorkloadIdentity_WithNamespaces(t *testing.T) { - //TODO(NET-5719): Add test case to cover Consul namespace missing and check for backoff -} diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go deleted file mode 100644 index 27bb909d2c..0000000000 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package serviceaccount - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "google.golang.org/protobuf/proto" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -type reconcileCase struct { - name string - svcAccountName string - k8sObjects func() []runtime.Object - existingResource *pbresource.Resource - expectedResource *pbresource.Resource - targetConsulNs string - targetConsulPartition string - expErr string -} - -// TODO(NET-5719): Allow/deny namespaces for reconcile tests - -// TestReconcile_CreateWorkloadIdentity ensures that a new ServiceAccount is reconciled -// to a Consul WorkloadIdentity. -func TestReconcile_CreateWorkloadIdentity(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Default ServiceAccount not synced", - svcAccountName: "default", - k8sObjects: func() []runtime.Object { - return []runtime.Object{createServiceAccount("default", "default")} - }, - }, - { - name: "Custom ServiceAccount", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - return []runtime.Object{ - createServiceAccount("default", "default"), - createServiceAccount("my-svc-account", "default"), - } - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - }, - { - name: "Already exists", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - return []runtime.Object{ - createServiceAccount("default", "default"), - createServiceAccount("my-svc-account", "default"), - } - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -// Tests deleting a WorkloadIdentity object, with and without matching Consul resources. -func TestReconcile_DeleteWorkloadIdentity(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Basic ServiceAccount not found (deleted)", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - // Only default exists (always exists). - return []runtime.Object{createServiceAccount("default", "default")} - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - }, - { - name: "Other ServiceAccount exists", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - // Default and other ServiceAccount exist - return []runtime.Object{ - createServiceAccount("default", "default"), - createServiceAccount("other-svc-account", "default"), - } - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - }, - { - name: "Already deleted", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - // Only default exists (always exists). - return []runtime.Object{createServiceAccount("default", "default")} - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -func runReconcileCase(t *testing.T, tc reconcileCase) { - t.Helper() - - // Create fake k8s client - var k8sObjects []runtime.Object - if tc.k8sObjects != nil { - k8sObjects = tc.k8sObjects() - } - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test Consul server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the ServiceAccount controller. - sa := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - } - - // Default ns and partition if not specified in test. - if tc.targetConsulNs == "" { - tc.targetConsulNs = constants.DefaultConsulNS - } - if tc.targetConsulPartition == "" { - tc.targetConsulPartition = constants.DefaultConsulPartition - } - - // If existing resource specified, create it and ensure it exists. - if tc.existingResource != nil { - writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} - _, err := testClient.ResourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), testClient.ResourceClient, tc.existingResource.Id) - } - - // Run actual reconcile and verify results. - resp, err := sa.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: tc.svcAccountName, - Namespace: tc.targetConsulNs, - }, - }) - if tc.expErr != "" { - require.ErrorContains(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - expectedWorkloadIdentityMatches(t, testClient.ResourceClient, tc.svcAccountName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) -} - -func expectedWorkloadIdentityMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { - req := &pbresource.ReadRequest{Id: getWorkloadIdentityID(name, namespace, partition)} - - res, err := client.Read(context.Background(), req) - - if expectedResource == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - require.NotNil(t, res.GetResource().GetData()) - - // This equality check isn't technically necessary because WorkloadIdentity is an empty message, - // but this supports the addition of fields in the future. - expectedWorkloadIdentity := &pbauth.WorkloadIdentity{} - err = anypb.UnmarshalTo(expectedResource.Data, expectedWorkloadIdentity, proto.UnmarshalOptions{}) - require.NoError(t, err) - - actualWorkloadIdentity := &pbauth.WorkloadIdentity{} - err = res.GetResource().GetData().UnmarshalTo(actualWorkloadIdentity) - require.NoError(t, err) - - if diff := cmp.Diff(expectedWorkloadIdentity, actualWorkloadIdentity, test.CmpProtoIgnoreOrder()...); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } -} - -// getWorkloadIdentityData returns a WorkloadIdentity resource payload. -// This function takes no arguments because WorkloadIdentity is currently an empty proto message. -func getWorkloadIdentityData() *anypb.Any { - return inject.ToProtoAny(&pbauth.WorkloadIdentity{}) -} - -func createServiceAccount(name, namespace string) *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - // Other fields exist, but we ignore them in this controller. - } -} diff --git a/control-plane/connect-inject/metrics/metrics_configuration.go b/control-plane/connect-inject/metrics/metrics_configuration.go index 2f217f233d..09f01a4c87 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration.go +++ b/control-plane/connect-inject/metrics/metrics_configuration.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package metrics import ( @@ -8,10 +5,9 @@ import ( "fmt" "strconv" - corev1 "k8s.io/api/core/v1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + corev1 "k8s.io/api/core/v1" ) // Config represents configuration common to connect-inject components related to metrics. diff --git a/control-plane/connect-inject/metrics/metrics_configuration_test.go b/control-plane/connect-inject/metrics/metrics_configuration_test.go index 12045e28d1..ea232576b5 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration_test.go +++ b/control-plane/connect-inject/metrics/metrics_configuration_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package metrics import ( diff --git a/control-plane/connect-inject/namespace/namespace_controller.go b/control-plane/connect-inject/namespace/namespace_controller.go deleted file mode 100644 index 86035bc69f..0000000000 --- a/control-plane/connect-inject/namespace/namespace_controller.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package namespace - -import ( - "context" - "fmt" - - mapset "github.com/deckarep/golang-set" - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -type Controller struct { - client.Client - // ConsulClientConfig is the config for the Consul API client. - ConsulClientConfig *consul.Config - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - // AllowK8sNamespacesSet determines kube namespace that are reconciled. - AllowK8sNamespacesSet mapset.Set - // DenyK8sNamespacesSet determines kube namespace that are ignored. - DenyK8sNamespacesSet mapset.Set - - // Partition is not required. It should already be set in the API ClientConfig - - // ConsulDestinationNamespace is the name of the Consul namespace to create - // all config entries in. If EnableNSMirroring is true this is ignored. - ConsulDestinationNamespace string - // EnableNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any config entry custom resource. Config entries will - // be created in the matching Consul namespace. - EnableNSMirroring bool - // NSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - NSMirroringPrefix string - - // CrossNamespaceACLPolicy is the name of the ACL policy to attach to - // any created Consul namespaces to allow cross namespace service discovery. - // Only necessary if ACLs are enabled. - CrossNamespaceACLPolicy string - - Log logr.Logger -} - -// Reconcile reads a Kubernetes Namespace and reconciles the mapped namespace in Consul. -// TODO: Move the creation of a destination namespace to a dedicated, single-flight goroutine. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var namespace corev1.Namespace - - // Ignore the request if the namespace is not allowed. - if common.ShouldIgnore(req.Name, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - apiClient, err := consul.NewClientFromConnMgr(r.ConsulClientConfig, r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul API client", "name", req.Name) - return ctrl.Result{}, err - } - - err = r.Client.Get(ctx, req.NamespacedName, &namespace) - - // If the namespace object has been deleted (and we get an IsNotFound error), - // we need to remove the Namespace from Consul. - if k8serrors.IsNotFound(err) { - - // if we are using a destination namespace, NEVER delete it. - if !r.EnableNSMirroring { - return ctrl.Result{}, nil - } - - if err := namespaces.EnsureDeleted(apiClient, r.getConsulNamespace(req.Name)); err != nil { - r.Log.Error(err, "error deleting namespace", - "namespace", r.getConsulNamespace(req.Name)) - return ctrl.Result{}, fmt.Errorf("error deleting namespace: %w", err) - } - - return ctrl.Result{}, nil - } else if err != nil { - r.Log.Error(err, "failed to get namespace", "name", req.Name) - return ctrl.Result{}, err - } - - r.Log.Info("retrieved", "namespace", namespace.GetName()) - - // TODO: eventually we will want to replace the V1 namespace APIs with the native V2 resource creation for tenancy - if _, err := namespaces.EnsureExists(apiClient, r.getConsulNamespace(namespace.GetName()), r.CrossNamespaceACLPolicy); err != nil { - r.Log.Error(err, "error checking or creating namespace", - "namespace", r.getConsulNamespace(namespace.GetName())) - return ctrl.Result{}, fmt.Errorf("error checking or creating namespace: %w", err) - } - - return ctrl.Result{}, nil -} - -// SetupWithManager registers this controller with the manager. -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Namespace{}). - Complete(r) -} - -// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace -// depending on Consul Namespaces being enabled and the value of namespace mirroring. -func (r *Controller) getConsulNamespace(kubeNamespace string) string { - ns := namespaces.ConsulNamespace( - kubeNamespace, - true, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO: remove this if and when the default namespace of resources change. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} diff --git a/control-plane/connect-inject/namespace/namespace_controller_ent_test.go b/control-plane/connect-inject/namespace/namespace_controller_ent_test.go deleted file mode 100644 index 1b63161976..0000000000 --- a/control-plane/connect-inject/namespace/namespace_controller_ent_test.go +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package namespace - -import ( - "context" - "testing" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - capi "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -const ( - testNamespaceName = "foo" - testCrossACLPolicy = "cross-namespace-policy" -) - -// TestReconcileCreateNamespace ensures that a new namespace is reconciled to a -// Consul namespace. The actual namespace in Consul depends on if the controller -// is configured with a destination namespace or mirroring enabled. -func TestReconcileCreateNamespace(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: testNamespaceName, - }} - nsDefault := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - - type testCase struct { - name string - kubeNamespaceName string // this will default to "foo" - partition string - - consulDestinationNamespace string - enableNSMirroring bool - nsMirrorPrefix string - - expectedConsulNamespaceName string - expectedConsulNamespace *capi.Namespace - - acls bool - expErr string - } - - run := func(t *testing.T, tc testCase) { - k8sObjects := []runtime.Object{ - &ns, - &nsDefault, - } - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - adminToken := "123e4567-e89b-12d3-a456-426614174000" - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - if tc.acls { - c.ACL.Enabled = tc.acls - c.ACL.Tokens.InitialManagement = adminToken - } - }) - - if tc.partition != "" { - testClient.Cfg.APIClientConfig.Partition = tc.partition - - partition := &capi.Partition{ - Name: tc.partition, - } - _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) - require.NoError(t, err) - } - - // Create the namespace controller. - nc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - EnableNSMirroring: tc.enableNSMirroring, - NSMirroringPrefix: tc.nsMirrorPrefix, - ConsulDestinationNamespace: tc.consulDestinationNamespace, - } - if tc.acls { - nc.CrossNamespaceACLPolicy = testCrossACLPolicy - - policy := &capi.ACLPolicy{Name: testCrossACLPolicy} - _, _, err := testClient.APIClient.ACL().PolicyCreate(policy, nil) - require.NoError(t, err) - } - - if tc.kubeNamespaceName == "" { - tc.kubeNamespaceName = testNamespaceName - } - - namespacedName := types.NamespacedName{ - Name: tc.kubeNamespaceName, - } - - resp, err := nc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - expectedNamespaceMatches(t, testClient.APIClient, tc.expectedConsulNamespaceName, tc.partition, tc.expectedConsulNamespace) - } - - testCases := []testCase{ - { - // This also tests that we don't overwrite anything about the default Consul namespace, - // because the original description is maintained. - name: "destination namespace default", - expectedConsulNamespaceName: constants.DefaultConsulNS, - expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), - }, - { - name: "destination namespace, non-default", - consulDestinationNamespace: "bar", - expectedConsulNamespaceName: "bar", - expectedConsulNamespace: getNamespace("bar", "", false), - }, - { - name: "destination namespace, non-default with ACLs enabled", - consulDestinationNamespace: "bar", - acls: true, - expectedConsulNamespaceName: "bar", - expectedConsulNamespace: getNamespace("bar", constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default - }, - { - name: "destination namespace, non-default, non-default partition", - partition: "baz", - consulDestinationNamespace: "bar", - expectedConsulNamespaceName: "bar", - expectedConsulNamespace: getNamespace("bar", "baz", false), - }, - { - name: "mirrored namespaces", - enableNSMirroring: true, - expectedConsulNamespaceName: testNamespaceName, - expectedConsulNamespace: getNamespace(testNamespaceName, "", false), - }, - { - name: "mirrored namespaces, non-default partition", - partition: "baz", - enableNSMirroring: true, - expectedConsulNamespaceName: testNamespaceName, - expectedConsulNamespace: getNamespace(testNamespaceName, "baz", false), - }, - { - name: "mirrored namespaces with acls", - acls: true, - enableNSMirroring: true, - expectedConsulNamespaceName: testNamespaceName, - expectedConsulNamespace: getNamespace(testNamespaceName, constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default - }, - { - name: "mirrored namespaces with prefix", - nsMirrorPrefix: "k8s-", - enableNSMirroring: true, - expectedConsulNamespaceName: "k8s-foo", - expectedConsulNamespace: getNamespace("k8s-foo", "", false), - }, - { - name: "mirrored namespaces with prefix, non-default partition", - nsMirrorPrefix: "k8s-", - partition: "baz", - enableNSMirroring: true, - expectedConsulNamespaceName: "k8s-foo", - expectedConsulNamespace: getNamespace("k8s-foo", "baz", false), - }, - { - name: "mirrored namespaces with prefix and acls", - nsMirrorPrefix: "k8s-", - acls: true, - enableNSMirroring: true, - expectedConsulNamespaceName: "k8s-foo", - expectedConsulNamespace: getNamespace("k8s-foo", constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default - }, - { - name: "mirrored namespaces overrides destination namespace", - enableNSMirroring: true, - consulDestinationNamespace: "baz", - expectedConsulNamespaceName: testNamespaceName, - expectedConsulNamespace: getNamespace(testNamespaceName, "", false), - }, - { - name: "ignore kube-system", - kubeNamespaceName: metav1.NamespaceSystem, - consulDestinationNamespace: "bar", - expectedConsulNamespaceName: "bar", // we make sure that this doesn't get created from the kube-system space by not providing the actual struct - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// Tests deleting a Namespace object, with and without matching Consul resources. -func TestReconcileDeleteNamespace(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - kubeNamespaceName string // this will default to "foo" - partition string - - destinationNamespace string - enableNSMirroring bool - nsMirrorPrefix string - - existingConsulNamespace *capi.Namespace - - expectedConsulNamespace *capi.Namespace - } - - run := func(t *testing.T, tc testCase) { - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - if tc.partition != "" { - testClient.Cfg.APIClientConfig.Partition = tc.partition - - partition := &capi.Partition{ - Name: tc.partition, - } - _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) - require.NoError(t, err) - } - - if tc.existingConsulNamespace != nil { - _, _, err := testClient.APIClient.Namespaces().Create(tc.existingConsulNamespace, nil) - require.NoError(t, err) - } - - // Create the namespace controller. - nc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - EnableNSMirroring: tc.enableNSMirroring, - NSMirroringPrefix: tc.nsMirrorPrefix, - ConsulDestinationNamespace: tc.destinationNamespace, - } - - if tc.kubeNamespaceName == "" { - tc.kubeNamespaceName = testNamespaceName - } - - namespacedName := types.NamespacedName{ - Name: tc.kubeNamespaceName, - } - - resp, err := nc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - if tc.existingConsulNamespace != nil { - expectedNamespaceMatches(t, testClient.APIClient, tc.existingConsulNamespace.Name, tc.partition, tc.expectedConsulNamespace) - } else { - expectedNamespaceMatches(t, testClient.APIClient, testNamespaceName, tc.partition, tc.expectedConsulNamespace) - } - } - - testCases := []testCase{ - { - name: "destination namespace with default is not cleaned up", - existingConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), - expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), - }, - { - name: "destination namespace with non-default is not cleaned up", - destinationNamespace: "bar", - existingConsulNamespace: getNamespace("bar", "", false), - expectedConsulNamespace: getNamespace("bar", "", false), - }, - { - name: "destination namespace with non-default is not cleaned up, non-default partition", - destinationNamespace: "bar", - partition: "baz", - existingConsulNamespace: getNamespace("bar", "baz", false), - expectedConsulNamespace: getNamespace("bar", "baz", false), - }, - { - name: "mirrored namespaces", - enableNSMirroring: true, - existingConsulNamespace: getNamespace(testNamespaceName, "", false), - }, - { - name: "mirrored namespaces but it's the default namespace", - kubeNamespaceName: metav1.NamespaceDefault, - enableNSMirroring: true, - existingConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), - expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), // Don't ever delete the Consul default NS - }, - { - name: "mirrored namespaces, non-default partition", - partition: "baz", - enableNSMirroring: true, - existingConsulNamespace: getNamespace(testNamespaceName, "baz", false), - }, - { - name: "mirrored namespaces with prefix", - nsMirrorPrefix: "k8s-", - enableNSMirroring: true, - existingConsulNamespace: getNamespace("k8s-foo", "", false), - }, - { - name: "mirrored namespaces with prefix, non-default partition", - partition: "baz", - nsMirrorPrefix: "k8s-", - enableNSMirroring: true, - existingConsulNamespace: getNamespace("k8s-foo", "baz", false), - }, - { - name: "mirrored namespaces overrides destination namespace", - enableNSMirroring: true, - destinationNamespace: "baz", - existingConsulNamespace: getNamespace(testNamespaceName, "", false), - }, - { - name: "mirrored namespace, but the namespace is already removed from Consul", - enableNSMirroring: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// getNamespace return a basic Consul V1 namespace for testing setup and comparison -func getNamespace(name string, partition string, acls bool) *capi.Namespace { - ns := &capi.Namespace{ - Name: name, - Partition: partition, - } - - if name != constants.DefaultConsulNS { - ns.Description = "Auto-generated by consul-k8s" - ns.Meta = map[string]string{"external-source": "kubernetes"} - ns.ACLs = &capi.NamespaceACLConfig{} - } else { - ns.Description = "Builtin Default Namespace" - } - - if acls && name != constants.DefaultConsulNS { - // Create the ACLs config for the cross-Consul-namespace - // default policy that needs to be attached - ns.ACLs = &capi.NamespaceACLConfig{ - PolicyDefaults: []capi.ACLLink{ - {Name: testCrossACLPolicy}, - }, - } - } - - return ns -} - -func expectedNamespaceMatches(t *testing.T, client *capi.Client, name string, partition string, expectedNamespace *capi.Namespace) { - namespaceInfo, _, err := client.Namespaces().Read(name, &capi.QueryOptions{Partition: partition}) - - require.NoError(t, err) - - if expectedNamespace == nil { - require.True(t, namespaceInfo == nil || namespaceInfo.DeletedAt != nil) - return - } - - require.NotNil(t, namespaceInfo) - // Zero out the Raft Index, in this case it is irrelevant. - namespaceInfo.CreateIndex = 0 - namespaceInfo.ModifyIndex = 0 - if namespaceInfo.ACLs != nil && len(namespaceInfo.ACLs.PolicyDefaults) > 0 { - namespaceInfo.ACLs.PolicyDefaults[0].ID = "" // Zero out the ID for ACLs enabled to facilitate testing. - } - require.Equal(t, *expectedNamespace, *namespaceInfo) -} diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index 6bd89010ff..aa22af5fb3 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( @@ -10,13 +7,12 @@ import ( "strings" "github.com/google/shlex" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) const ( @@ -51,11 +47,11 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor containerName = fmt.Sprintf("%s-%s", sidecarContainer, mpi.serviceName) } - var readinessProbe *corev1.Probe + var probe *corev1.Probe if useProxyHealthCheck(pod) { // If using the proxy health check for a service, configure an HTTP handler // that queries the '/ready' endpoint of the proxy. - readinessProbe = &corev1.Probe{ + probe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Port: intstr.FromInt(constants.ProxyDefaultHealthPort + mpi.serviceIndex), @@ -65,7 +61,7 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor InitialDelaySeconds: 1, } } else { - readinessProbe = &corev1.Probe{ + probe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ TCPSocket: &corev1.TCPSocketAction{ Port: intstr.FromInt(constants.ProxyDefaultInboundPort + mpi.serviceIndex), @@ -75,27 +71,6 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor } } - // Configure optional probes on the proxy to force restart it in failure scenarios. - var startupProbe, livenessProbe *corev1.Probe - startupSeconds := w.getStartupFailureSeconds(pod) - livenessSeconds := w.getLivenessFailureSeconds(pod) - if startupSeconds > 0 { - startupProbe = &corev1.Probe{ - // Use the same handler as the readiness probe. - ProbeHandler: readinessProbe.ProbeHandler, - PeriodSeconds: 1, - FailureThreshold: startupSeconds, - } - } - if livenessSeconds > 0 { - livenessProbe = &corev1.Probe{ - // Use the same handler as the readiness probe. - ProbeHandler: readinessProbe.ProbeHandler, - PeriodSeconds: 1, - FailureThreshold: livenessSeconds, - } - } - container := corev1.Container{ Name: containerName, Image: w.ImageConsulDataplane, @@ -161,9 +136,7 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor }, }, Args: args, - ReadinessProbe: readinessProbe, - StartupProbe: startupProbe, - LivenessProbe: livenessProbe, + ReadinessProbe: probe, } if w.AuthMethod != "" { @@ -195,15 +168,6 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor container.VolumeMounts = append(container.VolumeMounts, volumeMounts...) } - // Container Ports - metricsPorts, err := w.getMetricsPorts(pod) - if err != nil { - return corev1.Container{}, err - } - if metricsPorts != nil { - container.Ports = append(container.Ports, metricsPorts...) - } - tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) if err != nil { return corev1.Container{}, err @@ -229,11 +193,10 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor } } container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - ReadOnlyRootFilesystem: pointer.Bool(true), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), } } @@ -299,7 +262,7 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, mpi mu args = append(args, "-tls-server-name="+w.ConsulTLSServerName) } if w.ConsulCACert != "" { - args = append(args, "-ca-certs="+constants.LegacyConsulCAFile) + args = append(args, "-ca-certs="+constants.ConsulCAFile) } } else { args = append(args, "-tls-disabled") @@ -543,63 +506,3 @@ func useProxyHealthCheck(pod corev1.Pod) bool { } return false } - -// getStartupFailureSeconds returns number of seconds configured by the annotation 'consul.hashicorp.com/sidecar-proxy-startup-failure-seconds' -// and indicates how long we should wait for the sidecar proxy to initialize before considering the pod unhealthy. -func (w *MeshWebhook) getStartupFailureSeconds(pod corev1.Pod) int32 { - seconds := w.DefaultSidecarProxyStartupFailureSeconds - if v, ok := pod.Annotations[constants.AnnotationSidecarProxyStartupFailureSeconds]; ok { - seconds, _ = strconv.Atoi(v) - } - if seconds > 0 { - return int32(seconds) - } - return 0 -} - -// getLivenessFailureSeconds returns number of seconds configured by the annotation 'consul.hashicorp.com/sidecar-proxy-liveness-failure-seconds' -// and indicates how long we should wait for the sidecar proxy to initialize before considering the pod unhealthy. -func (w *MeshWebhook) getLivenessFailureSeconds(pod corev1.Pod) int32 { - seconds := w.DefaultSidecarProxyLivenessFailureSeconds - if v, ok := pod.Annotations[constants.AnnotationSidecarProxyLivenessFailureSeconds]; ok { - seconds, _ = strconv.Atoi(v) - } - if seconds > 0 { - return int32(seconds) - } - return 0 -} - -// getMetricsPorts creates container ports for exposing services such as prometheus. -// Prometheus in particular needs a named port for use with the operator. -// https://github.com/hashicorp/consul-k8s/pull/1440 -func (w *MeshWebhook) getMetricsPorts(pod corev1.Pod) ([]corev1.ContainerPort, error) { - enableMetrics, err := w.MetricsConfig.EnableMetrics(pod) - if err != nil { - return nil, fmt.Errorf("error determining if metrics are enabled: %w", err) - } - if !enableMetrics { - return nil, nil - } - - prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(pod) - if err != nil { - return nil, fmt.Errorf("error parsing prometheus port from pod: %w", err) - } - if prometheusScrapePort == "" { - return nil, nil - } - - port, err := strconv.Atoi(prometheusScrapePort) - if err != nil { - return nil, fmt.Errorf("error parsing prometheus port from pod: %w", err) - } - - return []corev1.ContainerPort{ - { - Name: "prometheus", - ContainerPort: int32(port), - Protocol: corev1.ProtocolTCP, - }, - }, nil -} diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go index 6d46d6a3d3..2fc44df270 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( @@ -9,17 +6,15 @@ import ( "strings" "testing" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" ) const nodeName = "test-node" @@ -419,143 +414,48 @@ func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { } func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck(t *testing.T) { - tests := map[string]struct { - changeHook func(*MeshWebhook) - changePod func(*corev1.Pod) - expectedReadiness *corev1.Probe - expectedStartup *corev1.Probe - expectedLiveness *corev1.Probe - }{ - "readiness-only": { - changeHook: func(h *MeshWebhook) {}, - changePod: func(p *corev1.Pod) {}, - expectedReadiness: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, + h := MeshWebhook{ + ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, + ConsulAddress: "1.1.1.1", + LogLevel: "info", + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + constants.AnnotationUseProxyHealthCheck: "true", }, }, - "default-values": { - changeHook: func(h *MeshWebhook) { - h.DefaultSidecarProxyStartupFailureSeconds = 11 - h.DefaultSidecarProxyLivenessFailureSeconds = 22 - }, - changePod: func(p *corev1.Pod) {}, - expectedReadiness: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, - }, - expectedStartup: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, - }, - PeriodSeconds: 1, - FailureThreshold: 11, - }, - expectedLiveness: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "web", }, - PeriodSeconds: 1, - FailureThreshold: 22, }, }, - "override-default": { - changeHook: func(h *MeshWebhook) { - h.DefaultSidecarProxyStartupFailureSeconds = 11 - h.DefaultSidecarProxyLivenessFailureSeconds = 22 - }, - changePod: func(p *corev1.Pod) { - p.ObjectMeta.Annotations[constants.AnnotationSidecarProxyStartupFailureSeconds] = "111" - p.ObjectMeta.Annotations[constants.AnnotationSidecarProxyLivenessFailureSeconds] = "222" - }, - expectedReadiness: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, - }, - expectedStartup: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, - }, - PeriodSeconds: 1, - FailureThreshold: 111, - }, - expectedLiveness: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, - }, - PeriodSeconds: 1, - FailureThreshold: 222, + } + container, err := h.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) + expectedProbe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.FromInt(21000), + Path: "/ready", }, }, + InitialDelaySeconds: 1, } - for tn, tc := range tests { - t.Run(tn, func(t *testing.T) { - hook := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - ConsulAddress: "1.1.1.1", - LogLevel: "info", - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationUseProxyHealthCheck: "true", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - tc.changeHook(&hook) - tc.changePod(&pod) - container, err := hook.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) - require.NoError(t, err) - require.Contains(t, container.Args, "-envoy-ready-bind-port=21000") - require.Equal(t, tc.expectedReadiness, container.ReadinessProbe) - require.Equal(t, tc.expectedStartup, container.StartupProbe) - require.Equal(t, tc.expectedLiveness, container.LivenessProbe) - require.Contains(t, container.Env, corev1.EnvVar{ - Name: "DP_ENVOY_READY_BIND_ADDRESS", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, - }, - }) - require.Contains(t, container.Ports, corev1.ContainerPort{ - Name: "proxy-health-0", - ContainerPort: 21000, - }) - }) - } + require.NoError(t, err) + require.Contains(t, container.Args, "-envoy-ready-bind-port=21000") + require.Equal(t, expectedProbe, container.ReadinessProbe) + require.Contains(t, container.Env, corev1.EnvVar{ + Name: "DP_ENVOY_READY_BIND_ADDRESS", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, + }, + }) + require.Contains(t, container.Ports, corev1.ContainerPort{ + Name: "proxy-health-0", + ContainerPort: 21000, + }) } func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck_Multiport(t *testing.T) { @@ -803,22 +703,20 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { tproxyEnabled: false, openShiftEnabled: false, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), }, }, "tproxy enabled; openshift disabled": { tproxyEnabled: true, openShiftEnabled: false, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), }, }, "tproxy disabled; openshift enabled": { @@ -830,11 +728,10 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { tproxyEnabled: true, openShiftEnabled: true, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), + RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsGroup: pointer.Int64(sidecarUserAndGroupID), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), }, }, } @@ -1290,7 +1187,6 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { name string pod corev1.Pod expCmdArgs string - expPorts []corev1.ContainerPort expErr string }{ { @@ -1313,37 +1209,6 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { }, }, expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20100 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics", - expPorts: []corev1.ContainerPort{ - { - Name: "prometheus", - ContainerPort: 20200, - Protocol: corev1.ProtocolTCP, - }, - }, - }, - { - name: "metrics with prometheus port override", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web", - constants.AnnotationEnableMetrics: "true", - constants.AnnotationEnableMetricsMerging: "true", - constants.AnnotationMergedMetricsPort: "20123", - constants.AnnotationPort: "1234", - constants.AnnotationPrometheusScrapePath: "/scrape-path", - constants.AnnotationPrometheusScrapePort: "6789", - }, - }, - }, - expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20123 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics", - expPorts: []corev1.ContainerPort{ - { - Name: "prometheus", - ContainerPort: 6789, - Protocol: corev1.ProtocolTCP, - }, - }, }, { name: "merged metrics with TLS enabled", @@ -1364,13 +1229,6 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { }, }, expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20100 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics -telemetry-prom-ca-certs-file=/certs/ca.crt -telemetry-prom-ca-certs-path=/certs/ca -telemetry-prom-cert-file=/certs/server.crt -telemetry-prom-key-file=/certs/key.pem", - expPorts: []corev1.ContainerPort{ - { - Name: "prometheus", - ContainerPort: 20200, - Protocol: corev1.ProtocolTCP, - }, - }, }, { name: "merge metrics with TLS enabled, missing CA gives an error", @@ -1435,12 +1293,6 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { t.Run(c.name, func(t *testing.T) { h := MeshWebhook{ ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - MetricsConfig: metrics.Config{ - // These are all the default values passed from the CLI - DefaultPrometheusScrapePort: "20200", - DefaultPrometheusScrapePath: "/metrics", - DefaultMergedMetricsPort: "20100", - }, } container, err := h.consulDataplaneSidecar(testNS, c.pod, multiPortInfo{}) if c.expErr != "" { @@ -1449,9 +1301,6 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { } else { require.NoError(t, err) require.Contains(t, strings.Join(container.Args, " "), c.expCmdArgs) - if c.expPorts != nil { - require.ElementsMatch(t, container.Ports, c.expPorts) - } } }) } diff --git a/control-plane/connect-inject/webhook/container_env.go b/control-plane/connect-inject/webhook/container_env.go index 2b2d7f5471..7c65dcd77c 100644 --- a/control-plane/connect-inject/webhook/container_env.go +++ b/control-plane/connect-inject/webhook/container_env.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( diff --git a/control-plane/connect-inject/webhook/container_env_test.go b/control-plane/connect-inject/webhook/container_env_test.go index 50d38832c0..62200e7bbd 100644 --- a/control-plane/connect-inject/webhook/container_env_test.go +++ b/control-plane/connect-inject/webhook/container_env_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( diff --git a/control-plane/connect-inject/webhook/container_init.go b/control-plane/connect-inject/webhook/container_init.go index 2626b03689..db13485489 100644 --- a/control-plane/connect-inject/webhook/container_init.go +++ b/control-plane/connect-inject/webhook/container_init.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( @@ -155,15 +152,15 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, if w.TLSEnabled { container.Env = append(container.Env, corev1.EnvVar{ - Name: constants.UseTLSEnvVar, + Name: "CONSUL_USE_TLS", Value: "true", }, corev1.EnvVar{ - Name: constants.CACertPEMEnvVar, + Name: "CONSUL_CACERT_PEM", Value: w.ConsulCACert, }, corev1.EnvVar{ - Name: constants.TLSServerNameEnvVar, + Name: "CONSUL_TLS_SERVER_NAME", Value: w.ConsulTLSServerName, }) } @@ -263,8 +260,6 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), } } } diff --git a/control-plane/connect-inject/webhook/container_init_test.go b/control-plane/connect-inject/webhook/container_init_test.go index 8feac95b84..5f14f44bc2 100644 --- a/control-plane/connect-inject/webhook/container_init_test.go +++ b/control-plane/connect-inject/webhook/container_init_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( @@ -302,8 +299,6 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), } } else if c.expTproxyEnabled { expectedSecurityContext = &corev1.SecurityContext{ diff --git a/control-plane/connect-inject/webhook/container_volume.go b/control-plane/connect-inject/webhook/container_volume.go index 76ba461c7b..b33567469b 100644 --- a/control-plane/connect-inject/webhook/container_volume.go +++ b/control-plane/connect-inject/webhook/container_volume.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( diff --git a/control-plane/connect-inject/webhook/dns.go b/control-plane/connect-inject/webhook/dns.go index 3f73928ece..ed4e95703b 100644 --- a/control-plane/connect-inject/webhook/dns.go +++ b/control-plane/connect-inject/webhook/dns.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( diff --git a/control-plane/connect-inject/webhook/dns_test.go b/control-plane/connect-inject/webhook/dns_test.go index e8d718557e..d6392c5317 100644 --- a/control-plane/connect-inject/webhook/dns_test.go +++ b/control-plane/connect-inject/webhook/dns_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( diff --git a/control-plane/connect-inject/webhook/health_checks_test.go b/control-plane/connect-inject/webhook/health_checks_test.go index 53e2781509..9279d8f140 100644 --- a/control-plane/connect-inject/webhook/health_checks_test.go +++ b/control-plane/connect-inject/webhook/health_checks_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( diff --git a/control-plane/connect-inject/webhook/heath_checks.go b/control-plane/connect-inject/webhook/heath_checks.go index 8986f9e985..42d6da08e1 100644 --- a/control-plane/connect-inject/webhook/heath_checks.go +++ b/control-plane/connect-inject/webhook/heath_checks.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 7d7233baa6..c8d412184b 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( @@ -15,6 +12,13 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/version" "gomodules.xyz/jsonpatch/v2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -23,14 +27,6 @@ import ( "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" ) const ( @@ -153,9 +149,6 @@ type MeshWebhook struct { DefaultProxyMemoryRequest resource.Quantity DefaultProxyMemoryLimit resource.Quantity - DefaultSidecarProxyStartupFailureSeconds int - DefaultSidecarProxyLivenessFailureSeconds int - // LifecycleConfig contains proxy lifecycle management configuration from the inject-connect command and has methods to determine whether // configuration should come from the default flags or annotations. The meshWebhook uses this to configure container sidecar proxy args. LifecycleConfig lifecycle.Config @@ -298,10 +291,7 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi // port. annotatedSvcNames := w.annotatedServiceNames(pod) multiPort := len(annotatedSvcNames) > 1 - lifecycleEnabled, ok := w.LifecycleConfig.EnableProxyLifecycle(pod) - if ok != nil { - w.Log.Error(err, "unable to get lifecycle enabled status") - } + // For single port pods, add the single init container and envoy sidecar. if !multiPort { // Add the init container that registers the service and sets up the Envoy configuration. @@ -318,14 +308,8 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) } - //Append the Envoy sidecar before the application container only if lifecycle enabled. - - if lifecycleEnabled && ok == nil { - pod.Spec.Containers = append([]corev1.Container{envoySidecar}, pod.Spec.Containers...) - } else { - pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) - } - + // TODO: invert to start the Envoy sidecar before the application container + pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) } else { // For multi port pods, check for unsupported cases, mount all relevant service account tokens, and mount an init // container and envoy sidecar per port. Tproxy, metrics, and metrics merging are not supported for multi port pods. @@ -340,10 +324,6 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "checking unsupported cases for multi port pods") return admission.Errored(http.StatusInternalServerError, err) } - - //List of sidecar containers for each service. Build as a list to preserve correct ordering in relation - //to services. - sidecarContainers := []corev1.Container{} for i, svc := range annotatedSvcNames { w.Log.Info(fmt.Sprintf("service: %s", svc)) if w.AuthMethod != "" { @@ -399,20 +379,9 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) } - // If Lifecycle is enabled, add to the list of sidecar containers to be added - // to pod containers at the end in order to preserve relative ordering. - if lifecycleEnabled { - sidecarContainers = append(sidecarContainers, envoySidecar) - } else { - pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) - - } - - } - - //Add sidecar containers first if lifecycle enabled. - if lifecycleEnabled { - pod.Spec.Containers = append(sidecarContainers, pod.Spec.Containers...) + // TODO: invert to start the Envoy sidecar container before the + // application container + pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) } } @@ -537,24 +506,20 @@ func (w *MeshWebhook) overwriteProbes(ns corev1.Namespace, pod *corev1.Pod) erro } if tproxyEnabled && overwriteProbes { - // We don't use the loop index because this needs to line up w.withiptablesConfigJSON, - // which is performed before the sidecar is injected. - idx := 0 - for _, container := range pod.Spec.Containers { + for i, container := range pod.Spec.Containers { // skip the "envoy-sidecar" container from having it's probes overridden if container.Name == sidecarContainer { continue } if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + idx) + container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + i) } if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + idx) + container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + i) } if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + idx) + container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + i) } - idx++ } } return nil @@ -623,7 +588,6 @@ func (w *MeshWebhook) defaultAnnotations(pod *corev1.Pod, podJson string) error } } pod.Annotations[constants.AnnotationOriginalPod] = podJson - pod.Annotations[constants.LegacyAnnotationConsulK8sVersion] = version.GetHumanVersion() pod.Annotations[constants.AnnotationConsulK8sVersion] = version.GetHumanVersion() return nil diff --git a/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go b/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go index 860694dcef..3f6b668c6b 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise package webhook @@ -9,8 +6,8 @@ import ( "context" "testing" - "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testing" + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" @@ -52,7 +49,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "single destination namespace 'default' from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -74,7 +71,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "single destination namespace 'default' from k8s 'non-default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -96,7 +93,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "single destination namespace 'dest' from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -118,7 +115,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "single destination namespace 'dest' from k8s 'non-default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -140,7 +137,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "mirroring from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -163,7 +160,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "mirroring from k8s 'dest'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -186,7 +183,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "mirroring with prefix from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -210,7 +207,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { { Name: "mirroring with prefix from k8s 'dest'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -298,7 +295,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + single destination namespace 'default' from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -321,7 +318,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + single destination namespace 'default' from k8s 'non-default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -344,7 +341,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + single destination namespace 'dest' from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -367,7 +364,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + single destination namespace 'dest' from k8s 'non-default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -390,7 +387,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + mirroring from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -414,7 +411,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + mirroring from k8s 'dest'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -438,7 +435,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + mirroring with prefix from k8s 'default'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -463,7 +460,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { { Name: "acls + mirroring with prefix from k8s 'dest'", Webhook: MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, @@ -603,7 +600,7 @@ func TestHandler_MutateWithNamespaces_Annotation(t *testing.T) { require.NoError(t, err) webhook := MeshWebhook{ - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), AllowK8sNamespacesSet: mapset.NewSet("*"), DenyK8sNamespacesSet: mapset.NewSet(), EnableNamespaces: true, diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index 2b71c08500..9761551b85 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -1,18 +1,18 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( "context" "encoding/json" - "strconv" "strings" "testing" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/sdk/iptables" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/version" "github.com/stretchr/testify/require" "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" @@ -24,13 +24,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" ) func TestHandlerHandle(t *testing.T) { @@ -142,73 +135,6 @@ func TestHandlerHandle(t *testing.T) { }, }, }, - { - "empty pod basic with lifecycle", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations", - }, - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - }, - }, { "pod with upstreams specified", @@ -246,10 +172,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -343,10 +265,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -402,10 +320,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -480,10 +394,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -544,10 +454,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -633,10 +539,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -774,10 +676,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -841,10 +739,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -904,99 +798,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - { - "multiport pod kube < 1.24 with AuthMethod, serviceaccount has secret ref, lifecycle enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: testClientWithServiceAccountAndSecretRefs(), - AuthMethod: "k8s", - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web,web-admin", - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/spec/containers/2", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1067,10 +868,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1143,10 +940,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1180,224 +973,6 @@ func TestHandlerHandle(t *testing.T) { } } -// This test validates that overwrite probes match the iptables configuration fromiptablesConfigJSON() -// Because they happen at different points in the injection, the port numbers can get out of sync. -func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { - t.Parallel() - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - cases := []struct { - Name string - Webhook MeshWebhook - Req admission.Request - Err string // expected error string, not exact - Patches []jsonpatch.Operation - }{ - { - "tproxy with overwriteProbes is enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - // We're setting an existing annotation so that we can assert on the - // specific annotations that are set as a result of probes being overwritten. - Annotations: map[string]string{"foo": "bar"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe/tcpSocket", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe/initialDelaySeconds", - }, - { - Operation: "remove", - Path: "/spec/containers/0/readinessProbe/httpGet", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "remove", - Path: "/spec/containers/0/startupProbe", - }, - { - Operation: "remove", - Path: "/spec/containers/0/livenessProbe", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), - }, - - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} - ctx := context.Background() - resp := tt.Webhook.Handle(ctx, tt.Req) - if (tt.Err == "") != resp.Allowed { - t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) - } - if tt.Err != "" { - require.Contains(t, resp.Result.Message, tt.Err) - return - } - - var iptablesCfg iptables.Config - var overwritePorts []string - actual := resp.Patches - if len(actual) > 0 { - for i := range actual { - - // We want to grab the iptables configuration from the connect-init container's - // environment. - if actual[i].Path == "/spec/initContainers" { - value := actual[i].Value.([]any) - valueMap := value[0].(map[string]any) - envs := valueMap["env"].([]any) - redirectEnv := envs[8].(map[string]any) - require.Equal(t, redirectEnv["name"].(string), "CONSUL_REDIRECT_TRAFFIC_CONFIG") - iptablesJson := redirectEnv["value"].(string) - - err := json.Unmarshal([]byte(iptablesJson), &iptablesCfg) - require.NoError(t, err) - } - - // We want to accumulate the httpGet Probes from the application container to - // compare them to the iptables rules. This is now the second container in the spec - if strings.Contains(actual[i].Path, "/spec/containers/1") { - valueMap, ok := actual[i].Value.(map[string]any) - require.True(t, ok) - - for k, v := range valueMap { - if strings.Contains(k, "Probe") { - probe := v.(map[string]any) - httpProbe := probe["httpGet"] - httpProbeMap := httpProbe.(map[string]any) - port := httpProbeMap["port"] - portNum := port.(float64) - - overwritePorts = append(overwritePorts, strconv.Itoa(int(portNum))) - } - } - } - - // nil out all the patch values to just compare the keys changing. - actual[i].Value = nil - } - } - // Make sure the iptables excluded ports match the ports on the container - require.ElementsMatch(t, iptablesCfg.ExcludeInboundPorts, overwritePorts) - require.ElementsMatch(t, tt.Patches, actual) - }) - } -} - func TestHandlerDefaultAnnotations(t *testing.T) { cases := []struct { Name string @@ -1409,9 +984,8 @@ func TestHandlerDefaultAnnotations(t *testing.T) { "empty", &corev1.Pod{}, map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1431,9 +1005,8 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1459,10 +1032,9 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - "consul.hashicorp.com/connect-service": "foo", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null,\"annotations\":{\"consul.hashicorp.com/connect-service\":\"foo\"}},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + "consul.hashicorp.com/connect-service": "foo", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null,\"annotations\":{\"consul.hashicorp.com/connect-service\":\"foo\"}},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", @@ -1489,10 +1061,9 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationPort: "http", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationPort: "http", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1517,10 +1088,9 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationPort: "8080", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationPort: "8080", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, diff --git a/control-plane/connect-inject/webhook/redirect_traffic.go b/control-plane/connect-inject/webhook/redirect_traffic.go index f928df4afd..8ae8fce068 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic.go +++ b/control-plane/connect-inject/webhook/redirect_traffic.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( @@ -8,11 +5,10 @@ import ( "fmt" "strconv" - "github.com/hashicorp/consul/sdk/iptables" - corev1 "k8s.io/api/core/v1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul/sdk/iptables" + corev1 "k8s.io/api/core/v1" ) // addRedirectTrafficConfigAnnotation creates an iptables.Config in JSON format based on proxy configuration. @@ -63,24 +59,20 @@ func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (s } if overwriteProbes { - // We don't use the loop index because this needs to line up w.overwriteProbes(), - // which is performed after the sidecar is injected. - idx := 0 - for _, container := range pod.Spec.Containers { - // skip the "consul-dataplane" container from having its probes overridden + for i, container := range pod.Spec.Containers { + // skip the "envoy-sidecar" container from having its probes overridden if container.Name == sidecarContainer { continue } if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+idx)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+i)) } if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+idx)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+i)) } if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+idx)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+i)) } - idx++ } } diff --git a/control-plane/connect-inject/webhook/redirect_traffic_test.go b/control-plane/connect-inject/webhook/redirect_traffic_test.go index bfa8ad8f2a..38aad20578 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic_test.go +++ b/control-plane/connect-inject/webhook/redirect_traffic_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhook import ( diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go deleted file mode 100644 index d3ba5095ac..0000000000 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go +++ /dev/null @@ -1,523 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - - "github.com/google/shlex" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - consulDataplaneDNSBindHost = "127.0.0.1" - consulDataplaneDNSBindPort = 8600 -) - -func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod corev1.Pod) (corev1.Container, error) { - resources, err := w.sidecarResources(pod) - if err != nil { - return corev1.Container{}, err - } - - // Extract the service account token's volume mount. - var bearerTokenFile string - var saTokenVolumeMount corev1.VolumeMount - if w.AuthMethod != "" { - saTokenVolumeMount, bearerTokenFile, err = findServiceAccountVolumeMount(pod) - if err != nil { - return corev1.Container{}, err - } - } - - args, err := w.getContainerSidecarArgs(namespace, bearerTokenFile, pod) - if err != nil { - return corev1.Container{}, err - } - - containerName := sidecarContainer - - var probe *corev1.Probe - if useProxyHealthCheck(pod) { - // If using the proxy health check for a service, configure an HTTP handler - // that queries the '/ready' endpoint of the proxy. - probe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(constants.ProxyDefaultHealthPort), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, - } - } else { - probe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(constants.ProxyDefaultInboundPort), - }, - }, - InitialDelaySeconds: 1, - } - } - - container := corev1.Container{ - Name: containerName, - Image: w.ImageConsulDataplane, - Resources: resources, - // We need to set tmp dir to an ephemeral volume that we're mounting so that - // consul-dataplane can write files to it. Otherwise, it wouldn't be able to - // because we set file system to be read-only. - Env: []corev1.EnvVar{ - { - Name: "TMPDIR", - Value: "/consul/mesh-inject", - }, - { - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, - }, - // The pod name isn't known currently, so we must rely on the environment variable to fill it in rather than using args. - { - Name: "POD_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, - }, - }, - { - Name: "POD_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, - }, - }, - { - Name: "DP_PROXY_ID", - Value: "$(POD_NAME)", - }, - { - Name: "DP_CREDENTIAL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - // This entry exists to support newer versions of consul dataplane, where environment variable entries - // utilize this numbered notation to indicate individual KV pairs in a map. - { - Name: "DP_CREDENTIAL_LOGIN_META1", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/mesh-inject", - }, - }, - Args: args, - } - - container.ReadinessProbe = probe - - if w.AuthMethod != "" { - container.VolumeMounts = append(container.VolumeMounts, saTokenVolumeMount) - } - - if useProxyHealthCheck(pod) { - // Configure the Readiness Address for the proxy's health check to be the Pod IP. - container.Env = append(container.Env, corev1.EnvVar{ - Name: "DP_ENVOY_READY_BIND_ADDRESS", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, - }, - }) - // Configure the port on which the readiness probe will query the proxy for its health. - container.Ports = append(container.Ports, corev1.ContainerPort{ - Name: "proxy-health", - ContainerPort: int32(constants.ProxyDefaultHealthPort), - }) - } - - // Add any extra VolumeMounts. - if userVolMount, ok := pod.Annotations[constants.AnnotationConsulSidecarUserVolumeMount]; ok { - var volumeMounts []corev1.VolumeMount - err := json.Unmarshal([]byte(userVolMount), &volumeMounts) - if err != nil { - return corev1.Container{}, err - } - container.VolumeMounts = append(container.VolumeMounts, volumeMounts...) - } - - // Container Ports - metricsPorts, err := w.getMetricsPorts(pod) - if err != nil { - return corev1.Container{}, err - } - if metricsPorts != nil { - container.Ports = append(container.Ports, metricsPorts...) - } - - tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) - if err != nil { - return corev1.Container{}, err - } - - // If not running in transparent proxy mode and in an OpenShift environment, - // skip setting the security context and let OpenShift set it for us. - // When transparent proxy is enabled, then consul-dataplane needs to run as our specific user - // so that traffic redirection will work. - if tproxyEnabled || !w.EnableOpenShift { - if pod.Spec.SecurityContext != nil { - // User container and consul-dataplane container cannot have the same UID. - if pod.Spec.SecurityContext.RunAsUser != nil && *pod.Spec.SecurityContext.RunAsUser == sidecarUserAndGroupID { - return corev1.Container{}, fmt.Errorf("pod's security context cannot have the same UID as consul-dataplane: %v", sidecarUserAndGroupID) - } - } - // Ensure that none of the user's containers have the same UID as consul-dataplane. At this point in injection the meshWebhook - // has only injected init containers so all containers defined in pod.Spec.Containers are from the user. - for _, c := range pod.Spec.Containers { - // User container and consul-dataplane container cannot have the same UID. - if c.SecurityContext != nil && c.SecurityContext.RunAsUser != nil && *c.SecurityContext.RunAsUser == sidecarUserAndGroupID && c.Image != w.ImageConsulDataplane { - return corev1.Container{}, fmt.Errorf("container %q has runAsUser set to the same UID \"%d\" as consul-dataplane which is not allowed", c.Name, sidecarUserAndGroupID) - } - } - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - } - } - - return container, nil -} - -func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, bearerTokenFile string, pod corev1.Pod) ([]string, error) { - envoyConcurrency := w.DefaultEnvoyProxyConcurrency - - // Check to see if the user has overriden concurrency via an annotation. - if envoyConcurrencyAnnotation, ok := pod.Annotations[constants.AnnotationEnvoyProxyConcurrency]; ok { - val, err := strconv.ParseUint(envoyConcurrencyAnnotation, 10, 64) - if err != nil { - return nil, fmt.Errorf("unable to parse annotation %q: %w", constants.AnnotationEnvoyProxyConcurrency, err) - } - envoyConcurrency = int(val) - } - - args := []string{ - "-addresses", w.ConsulAddress, - "-grpc-port=" + strconv.Itoa(w.ConsulConfig.GRPCPort), - "-log-level=" + w.LogLevel, - "-log-json=" + strconv.FormatBool(w.LogJSON), - "-envoy-concurrency=" + strconv.Itoa(envoyConcurrency), - } - - if w.SkipServerWatch { - args = append(args, "-server-watch-disabled=true") - } - - if w.AuthMethod != "" { - args = append(args, - "-credential-type=login", - "-login-auth-method="+w.AuthMethod, - "-login-bearer-token-path="+bearerTokenFile, - // We don't know the pod name at this time, so we must use environment variables to populate the login-meta instead. - ) - if w.EnableNamespaces { - if w.EnableK8SNSMirroring { - args = append(args, "-login-namespace=default") - } else { - args = append(args, "-login-namespace="+w.consulNamespace(namespace.Name)) - } - } - if w.ConsulPartition != "" { - args = append(args, "-login-partition="+w.ConsulPartition) - } - } - if w.EnableNamespaces { - args = append(args, "-proxy-namespace="+w.consulNamespace(namespace.Name)) - } - if w.ConsulPartition != "" { - args = append(args, "-proxy-partition="+w.ConsulPartition) - } - if w.TLSEnabled { - if w.ConsulTLSServerName != "" { - args = append(args, "-tls-server-name="+w.ConsulTLSServerName) - } - if w.ConsulCACert != "" { - args = append(args, "-ca-certs="+constants.ConsulCAFile) - } - } else { - args = append(args, "-tls-disabled") - } - - // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. - if useProxyHealthCheck(pod) { - args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) - } - - // The consul-dataplane HTTP listener always starts for graceful shutdown. To avoid port conflicts, the - // graceful port always needs to be set - gracefulPort, err := w.LifecycleConfig.GracefulPort(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine proxy lifecycle graceful port: %w", err) - } - - args = append(args, fmt.Sprintf("-graceful-port=%d", gracefulPort)) - - enableProxyLifecycle, err := w.LifecycleConfig.EnableProxyLifecycle(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if proxy lifecycle management is enabled: %w", err) - } - if enableProxyLifecycle { - shutdownDrainListeners, err := w.LifecycleConfig.EnableShutdownDrainListeners(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if proxy lifecycle shutdown listener draining is enabled: %w", err) - } - if shutdownDrainListeners { - args = append(args, "-shutdown-drain-listeners") - } - - shutdownGracePeriodSeconds, err := w.LifecycleConfig.ShutdownGracePeriodSeconds(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine proxy lifecycle shutdown grace period: %w", err) - } - args = append(args, fmt.Sprintf("-shutdown-grace-period-seconds=%d", shutdownGracePeriodSeconds)) - - gracefulShutdownPath := w.LifecycleConfig.GracefulShutdownPath(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine proxy lifecycle graceful shutdown path: %w", err) - } - args = append(args, fmt.Sprintf("-graceful-shutdown-path=%s", gracefulShutdownPath)) - } - - // Set a default scrape path that can be overwritten by the annotation. - prometheusScrapePath := w.MetricsConfig.PrometheusScrapePath(pod) - args = append(args, "-telemetry-prom-scrape-path="+prometheusScrapePath) - - metricsServer, err := w.MetricsConfig.ShouldRunMergedMetricsServer(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if merged metrics is enabled: %w", err) - } - if metricsServer { - mergedMetricsPort, err := w.MetricsConfig.MergedMetricsPort(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if merged metrics port: %w", err) - } - args = append(args, "-telemetry-prom-merge-port="+mergedMetricsPort) - - serviceMetricsPath := w.MetricsConfig.ServiceMetricsPath(pod) - serviceMetricsPort, err := w.MetricsConfig.ServiceMetricsPort(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if service metrics port: %w", err) - } - - if serviceMetricsPath != "" && serviceMetricsPort != "" { - args = append(args, "-telemetry-prom-service-metrics-url="+fmt.Sprintf("http://127.0.0.1:%s%s", serviceMetricsPort, serviceMetricsPath)) - } - - // Pull the TLS config from the relevant annotations. - var prometheusCAFile string - if raw, ok := pod.Annotations[constants.AnnotationPrometheusCAFile]; ok && raw != "" { - prometheusCAFile = raw - } - - var prometheusCAPath string - if raw, ok := pod.Annotations[constants.AnnotationPrometheusCAPath]; ok && raw != "" { - prometheusCAPath = raw - } - - var prometheusCertFile string - if raw, ok := pod.Annotations[constants.AnnotationPrometheusCertFile]; ok && raw != "" { - prometheusCertFile = raw - } - - var prometheusKeyFile string - if raw, ok := pod.Annotations[constants.AnnotationPrometheusKeyFile]; ok && raw != "" { - prometheusKeyFile = raw - } - - // Validate required Prometheus TLS config is present if set. - if prometheusCAFile != "" || prometheusCAPath != "" || prometheusCertFile != "" || prometheusKeyFile != "" { - if prometheusCAFile == "" && prometheusCAPath == "" { - return nil, fmt.Errorf("must set one of %q or %q when providing prometheus TLS config", constants.AnnotationPrometheusCAFile, constants.AnnotationPrometheusCAPath) - } - if prometheusCertFile == "" { - return nil, fmt.Errorf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusCertFile) - } - if prometheusKeyFile == "" { - return nil, fmt.Errorf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusKeyFile) - } - // TLS config has been validated, add them to the consul-dataplane cmd args - args = append(args, "-telemetry-prom-ca-certs-file="+prometheusCAFile, - "-telemetry-prom-ca-certs-path="+prometheusCAPath, - "-telemetry-prom-cert-file="+prometheusCertFile, - "-telemetry-prom-key-file="+prometheusKeyFile) - } - } - - // If Consul DNS is enabled, we want to configure consul-dataplane to be the DNS proxy - // for Consul DNS in the pod. - dnsEnabled, err := consulDNSEnabled(namespace, pod, w.EnableConsulDNS, w.EnableTransparentProxy) - if err != nil { - return nil, err - } - if dnsEnabled { - args = append(args, "-consul-dns-bind-port="+strconv.Itoa(consulDataplaneDNSBindPort)) - } - - var envoyExtraArgs []string - extraArgs, annotationSet := pod.Annotations[constants.AnnotationEnvoyExtraArgs] - - if annotationSet || w.EnvoyExtraArgs != "" { - extraArgsToUse := w.EnvoyExtraArgs - - // Prefer args set by pod annotation over the flag to the consul-k8s binary (h.EnvoyExtraArgs). - if annotationSet { - extraArgsToUse = extraArgs - } - - // Split string into tokens. - // e.g. "--foo bar --boo baz" --> ["--foo", "bar", "--boo", "baz"] - tokens, err := shlex.Split(extraArgsToUse) - if err != nil { - return []string{}, err - } - for _, t := range tokens { - if strings.Contains(t, " ") { - t = strconv.Quote(t) - } - envoyExtraArgs = append(envoyExtraArgs, t) - } - } - if envoyExtraArgs != nil { - args = append(args, "--") - args = append(args, envoyExtraArgs...) - } - return args, nil -} - -func (w *MeshWebhook) sidecarResources(pod corev1.Pod) (corev1.ResourceRequirements, error) { - resources := corev1.ResourceRequirements{ - Limits: corev1.ResourceList{}, - Requests: corev1.ResourceList{}, - } - // zeroQuantity is used for comparison to see if a quantity was explicitly - // set. - var zeroQuantity resource.Quantity - - // NOTE: We only want to set the limit/request if the default or annotation - // was explicitly set. If it's not explicitly set, it will be the zero value - // which would show up in the pod spec as being explicitly set to zero if we - // set that key, e.g. "cpu" to zero. - // We want it to not show up in the pod spec at all if it's not explicitly - // set so that users aren't wondering why it's set to 0 when they didn't specify - // a request/limit. If they have explicitly set it to 0 then it will be set - // to 0 in the pod spec because we're doing a comparison to the zero-valued - // struct. - - // CPU Limit. - if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyCPULimit]; ok { - cpuLimit, err := resource.ParseQuantity(anno) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyCPULimit, anno, err) - } - resources.Limits[corev1.ResourceCPU] = cpuLimit - } else if w.DefaultProxyCPULimit != zeroQuantity { - resources.Limits[corev1.ResourceCPU] = w.DefaultProxyCPULimit - } - - // CPU Request. - if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyCPURequest]; ok { - cpuRequest, err := resource.ParseQuantity(anno) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyCPURequest, anno, err) - } - resources.Requests[corev1.ResourceCPU] = cpuRequest - } else if w.DefaultProxyCPURequest != zeroQuantity { - resources.Requests[corev1.ResourceCPU] = w.DefaultProxyCPURequest - } - - // Memory Limit. - if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyMemoryLimit]; ok { - memoryLimit, err := resource.ParseQuantity(anno) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyMemoryLimit, anno, err) - } - resources.Limits[corev1.ResourceMemory] = memoryLimit - } else if w.DefaultProxyMemoryLimit != zeroQuantity { - resources.Limits[corev1.ResourceMemory] = w.DefaultProxyMemoryLimit - } - - // Memory Request. - if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyMemoryRequest]; ok { - memoryRequest, err := resource.ParseQuantity(anno) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyMemoryRequest, anno, err) - } - resources.Requests[corev1.ResourceMemory] = memoryRequest - } else if w.DefaultProxyMemoryRequest != zeroQuantity { - resources.Requests[corev1.ResourceMemory] = w.DefaultProxyMemoryRequest - } - - return resources, nil -} - -// useProxyHealthCheck returns true if the pod has the annotation 'consul.hashicorp.com/use-proxy-health-check' -// set to truthy values. -func useProxyHealthCheck(pod corev1.Pod) bool { - if v, ok := pod.Annotations[constants.AnnotationUseProxyHealthCheck]; ok { - useProxyHealthCheck, err := strconv.ParseBool(v) - if err != nil { - return false - } - return useProxyHealthCheck - } - return false -} - -// getMetricsPorts creates container ports for exposing services such as prometheus. -// Prometheus in particular needs a named port for use with the operator. -// https://github.com/hashicorp/consul-k8s/pull/1440 -func (w *MeshWebhook) getMetricsPorts(pod corev1.Pod) ([]corev1.ContainerPort, error) { - enableMetrics, err := w.MetricsConfig.EnableMetrics(pod) - if err != nil { - return nil, fmt.Errorf("error determining if metrics are enabled: %w", err) - } - if !enableMetrics { - return nil, nil - } - - prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(pod) - if err != nil { - return nil, fmt.Errorf("error parsing prometheus port from pod: %w", err) - } - if prometheusScrapePort == "" { - return nil, nil - } - - port, err := strconv.Atoi(prometheusScrapePort) - if err != nil { - return nil, fmt.Errorf("error parsing prometheus port from pod: %w", err) - } - - return []corev1.ContainerPort{ - { - Name: "prometheus", - ContainerPort: int32(port), - Protocol: corev1.ProtocolTCP, - }, - }, nil -} diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go deleted file mode 100644 index cf9124c673..0000000000 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go +++ /dev/null @@ -1,1277 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "fmt" - "strconv" - "strings" - "testing" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" -) - -const nodeName = "test-node" - -func TestHandlerConsulDataplaneSidecar(t *testing.T) { - cases := map[string]struct { - webhookSetupFunc func(w *MeshWebhook) - additionalExpCmdArgs string - }{ - "default": { - webhookSetupFunc: nil, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with custom gRPC port": { - webhookSetupFunc: func(w *MeshWebhook) { - w.ConsulConfig.GRPCPort = 8602 - }, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with ACLs": { - webhookSetupFunc: func(w *MeshWebhook) { - w.AuthMethod = "test-auth-method" - }, - additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with ACLs and namespace mirroring": { - webhookSetupFunc: func(w *MeshWebhook) { - w.AuthMethod = "test-auth-method" - w.EnableNamespaces = true - w.EnableK8SNSMirroring = true - }, - additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-namespace=default -proxy-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with ACLs and single destination namespace": { - webhookSetupFunc: func(w *MeshWebhook) { - w.AuthMethod = "test-auth-method" - w.EnableNamespaces = true - w.ConsulDestinationNamespace = "test-ns" - }, - additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-namespace=test-ns -proxy-namespace=test-ns -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with ACLs and partitions": { - webhookSetupFunc: func(w *MeshWebhook) { - w.AuthMethod = "test-auth-method" - w.ConsulPartition = "test-part" - }, - additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-partition=test-part -proxy-partition=test-part -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with TLS and CA cert provided": { - webhookSetupFunc: func(w *MeshWebhook) { - w.TLSEnabled = true - w.ConsulTLSServerName = "server.dc1.consul" - w.ConsulCACert = "consul-ca-cert" - }, - additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -ca-certs=/consul/mesh-inject/consul-ca.pem -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with TLS and no CA cert provided": { - webhookSetupFunc: func(w *MeshWebhook) { - w.TLSEnabled = true - w.ConsulTLSServerName = "server.dc1.consul" - }, - additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with single destination namespace": { - webhookSetupFunc: func(w *MeshWebhook) { - w.EnableNamespaces = true - w.ConsulDestinationNamespace = "consul-namespace" - }, - additionalExpCmdArgs: " -proxy-namespace=consul-namespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with namespace mirroring": { - webhookSetupFunc: func(w *MeshWebhook) { - w.EnableNamespaces = true - w.EnableK8SNSMirroring = true - }, - additionalExpCmdArgs: " -proxy-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with namespace mirroring prefix": { - webhookSetupFunc: func(w *MeshWebhook) { - w.EnableNamespaces = true - w.EnableK8SNSMirroring = true - w.K8SNSMirroringPrefix = "foo-" - }, - additionalExpCmdArgs: " -proxy-namespace=foo-k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with partitions": { - webhookSetupFunc: func(w *MeshWebhook) { - w.ConsulPartition = "partition-1" - }, - additionalExpCmdArgs: " -proxy-partition=partition-1 -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with different log level": { - webhookSetupFunc: func(w *MeshWebhook) { - w.LogLevel = "debug" - }, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with different log level and log json": { - webhookSetupFunc: func(w *MeshWebhook) { - w.LogLevel = "debug" - w.LogJSON = true - }, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "skip server watch enabled": { - webhookSetupFunc: func(w *MeshWebhook) { - w.SkipServerWatch = true - }, - additionalExpCmdArgs: " -server-watch-disabled=true -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "custom prometheus scrape path": { - webhookSetupFunc: func(w *MeshWebhook) { - w.MetricsConfig.DefaultPrometheusScrapePath = "/scrape-path" // Simulate what would be passed as a flag - }, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/scrape-path", - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - w := &MeshWebhook{ - ConsulAddress: "1.1.1.1", - ConsulConfig: &consul.Config{GRPCPort: 8502}, - LogLevel: "info", - LogJSON: false, - } - if c.webhookSetupFunc != nil { - c.webhookSetupFunc(w) - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pod", - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - { - Name: "auth-method-secret", - VolumeMounts: []corev1.VolumeMount{ - { - Name: "service-account-secret", - MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", - }, - }, - }, - }, - ServiceAccountName: "web", - NodeName: nodeName, - }, - } - - container, err := w.consulDataplaneSidecar(testNS, pod) - require.NoError(t, err) - expCmd := "-addresses 1.1.1.1 -grpc-port=" + strconv.Itoa(w.ConsulConfig.GRPCPort) + - " -log-level=" + w.LogLevel + " -log-json=" + strconv.FormatBool(w.LogJSON) + " -envoy-concurrency=0" + c.additionalExpCmdArgs - require.Equal(t, expCmd, strings.Join(container.Args, " ")) - - if w.AuthMethod != "" { - require.Equal(t, container.VolumeMounts, []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/mesh-inject", - }, - { - Name: "service-account-secret", - MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", - }, - }) - } else { - require.Equal(t, container.VolumeMounts, []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/mesh-inject", - }, - }) - } - - expectedProbe := &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(constants.ProxyDefaultInboundPort), - }, - }, - InitialDelaySeconds: 1, - } - require.Equal(t, expectedProbe, container.ReadinessProbe) - require.Nil(t, container.StartupProbe) - require.Len(t, container.Env, 7) - require.Equal(t, container.Env[0].Name, "TMPDIR") - require.Equal(t, container.Env[0].Value, "/consul/mesh-inject") - require.Equal(t, container.Env[2].Name, "POD_NAME") - require.Equal(t, container.Env[3].Name, "POD_NAMESPACE") - require.Equal(t, container.Env[4].Name, "DP_PROXY_ID") - require.Equal(t, container.Env[4].Value, "$(POD_NAME)") - require.Equal(t, container.Env[5].Name, "DP_CREDENTIAL_LOGIN_META") - require.Equal(t, container.Env[5].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") - }) - } -} - -func TestHandlerConsulDataplaneSidecar_Concurrency(t *testing.T) { - cases := map[string]struct { - annotations map[string]string - expFlags string - expErr string - }{ - "default settings, no annotations": { - annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - expFlags: "-envoy-concurrency=0", - }, - "default settings, annotation override": { - annotations: map[string]string{ - constants.AnnotationService: "foo", - constants.AnnotationEnvoyProxyConcurrency: "42", - }, - expFlags: "-envoy-concurrency=42", - }, - "default settings, invalid concurrency annotation negative number": { - annotations: map[string]string{ - constants.AnnotationService: "foo", - constants.AnnotationEnvoyProxyConcurrency: "-42", - }, - expErr: "unable to parse annotation \"consul.hashicorp.com/consul-envoy-proxy-concurrency\": strconv.ParseUint: parsing \"-42\": invalid syntax", - }, - "default settings, not-parseable concurrency annotation": { - annotations: map[string]string{ - constants.AnnotationService: "foo", - constants.AnnotationEnvoyProxyConcurrency: "not-int", - }, - expErr: "unable to parse annotation \"consul.hashicorp.com/consul-envoy-proxy-concurrency\": strconv.ParseUint: parsing \"not-int\": invalid syntax", - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: c.annotations, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := h.consulDataplaneSidecar(testNS, pod) - if c.expErr != "" { - require.EqualError(t, err, c.expErr) - } else { - require.NoError(t, err) - require.Contains(t, strings.Join(container.Args, " "), c.expFlags) - } - }) - } -} - -// Test that we pass the dns proxy flag to dataplane correctly. -func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { - - // We only want the flag passed when DNS and tproxy are both enabled. DNS/tproxy can - // both be enabled/disabled with annotations/labels on the pod and namespace and then globally - // through the helm chart. To test this we use an outer loop with the possible DNS settings and then - // and inner loop with possible tproxy settings. - dnsCases := []struct { - GlobalConsulDNS bool - NamespaceDNS *bool - PodDNS *bool - ExpEnabled bool - }{ - { - GlobalConsulDNS: false, - ExpEnabled: false, - }, - { - GlobalConsulDNS: true, - ExpEnabled: true, - }, - { - GlobalConsulDNS: false, - NamespaceDNS: boolPtr(true), - ExpEnabled: true, - }, - { - GlobalConsulDNS: false, - PodDNS: boolPtr(true), - ExpEnabled: true, - }, - } - tproxyCases := []struct { - GlobalTProxy bool - NamespaceTProxy *bool - PodTProxy *bool - ExpEnabled bool - }{ - { - GlobalTProxy: false, - ExpEnabled: false, - }, - { - GlobalTProxy: true, - ExpEnabled: true, - }, - { - GlobalTProxy: false, - NamespaceTProxy: boolPtr(true), - ExpEnabled: true, - }, - { - GlobalTProxy: false, - PodTProxy: boolPtr(true), - ExpEnabled: true, - }, - } - - // Outer loop is permutations of dns being enabled. Inner loop is permutations of tproxy being enabled. - // Both must be enabled for dns to be enabled. - for i, dnsCase := range dnsCases { - for j, tproxyCase := range tproxyCases { - t.Run(fmt.Sprintf("dns=%d,tproxy=%d", i, j), func(t *testing.T) { - - // Test setup. - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - EnableTransparentProxy: tproxyCase.GlobalTProxy, - EnableConsulDNS: dnsCase.GlobalConsulDNS, - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - if dnsCase.PodDNS != nil { - pod.Annotations[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.PodDNS) - } - if tproxyCase.PodTProxy != nil { - pod.Annotations[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.PodTProxy) - } - - ns := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: k8sNamespace, - Labels: map[string]string{}, - }, - } - if dnsCase.NamespaceDNS != nil { - ns.Labels[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.NamespaceDNS) - } - if tproxyCase.NamespaceTProxy != nil { - ns.Labels[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.NamespaceTProxy) - } - - // Actual test here. - container, err := h.consulDataplaneSidecar(ns, pod) - require.NoError(t, err) - // Flag should only be passed if both tproxy and dns are enabled. - if tproxyCase.ExpEnabled && dnsCase.ExpEnabled { - require.Contains(t, container.Args, "-consul-dns-bind-port=8600") - } else { - require.NotContains(t, container.Args, "-consul-dns-bind-port=8600") - } - }) - } - } -} - -func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck(t *testing.T) { - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - ConsulAddress: "1.1.1.1", - LogLevel: "info", - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationUseProxyHealthCheck: "true", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := h.consulDataplaneSidecar(testNS, pod) - expectedProbe := &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, - } - require.NoError(t, err) - require.Contains(t, container.Args, "-envoy-ready-bind-port=21000") - require.Equal(t, expectedProbe, container.ReadinessProbe) - require.Contains(t, container.Env, corev1.EnvVar{ - Name: "DP_ENVOY_READY_BIND_ADDRESS", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, - }, - }) - require.Contains(t, container.Ports, corev1.ContainerPort{ - Name: "proxy-health", - ContainerPort: 21000, - }) -} - -func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { - cases := map[string]struct { - tproxyEnabled bool - openShiftEnabled bool - expSecurityContext *corev1.SecurityContext - }{ - "tproxy disabled; openshift disabled": { - tproxyEnabled: false, - openShiftEnabled: false, - expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - }, - }, - "tproxy enabled; openshift disabled": { - tproxyEnabled: true, - openShiftEnabled: false, - expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - }, - }, - "tproxy disabled; openshift enabled": { - tproxyEnabled: false, - openShiftEnabled: true, - expSecurityContext: nil, - }, - "tproxy enabled; openshift enabled": { - tproxyEnabled: true, - openShiftEnabled: true, - expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - w := MeshWebhook{ - EnableTransparentProxy: c.tproxyEnabled, - EnableOpenShift: c.openShiftEnabled, - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - ec, err := w.consulDataplaneSidecar(testNS, pod) - require.NoError(t, err) - require.Equal(t, c.expSecurityContext, ec.SecurityContext) - }) - } -} - -// Test that if the user specifies a pod security context with the same uid as `sidecarUserAndGroupID` that we return -// an error to the meshWebhook. -func TestHandlerConsulDataplaneSidecar_FailsWithDuplicatePodSecurityContextUID(t *testing.T) { - require := require.New(t) - w := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - } - pod := corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - SecurityContext: &corev1.PodSecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - }, - }, - } - _, err := w.consulDataplaneSidecar(testNS, pod) - require.EqualError(err, fmt.Sprintf("pod's security context cannot have the same UID as consul-dataplane: %v", sidecarUserAndGroupID)) -} - -// Test that if the user specifies a container with security context with the same uid as `sidecarUserAndGroupID` that we -// return an error to the meshWebhook. If a container using the consul-dataplane image has the same uid, we don't return an error -// because in multiport pod there can be multiple consul-dataplane sidecars. -func TestHandlerConsulDataplaneSidecar_FailsWithDuplicateContainerSecurityContextUID(t *testing.T) { - cases := []struct { - name string - pod corev1.Pod - webhook MeshWebhook - expErr bool - expErrMessage string - }{ - { - name: "fails with non consul-dataplane image", - pod: corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - // Setting RunAsUser: 1 should succeed. - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(1), - }, - }, - { - Name: "app", - // Setting RunAsUser: 5995 should fail. - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - }, - Image: "not-consul-dataplane", - }, - }, - }, - }, - webhook: MeshWebhook{}, - expErr: true, - expErrMessage: fmt.Sprintf("container \"app\" has runAsUser set to the same UID \"%d\" as consul-dataplane which is not allowed", sidecarUserAndGroupID), - }, - { - name: "doesn't fail with envoy image", - pod: corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - // Setting RunAsUser: 1 should succeed. - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(1), - }, - }, - { - Name: "sidecar", - // Setting RunAsUser: 5995 should succeed if the image matches h.ImageConsulDataplane. - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - }, - Image: "envoy", - }, - }, - }, - }, - webhook: MeshWebhook{ - ImageConsulDataplane: "envoy", - }, - expErr: false, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - tc.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} - _, err := tc.webhook.consulDataplaneSidecar(testNS, tc.pod) - if tc.expErr { - require.EqualError(t, err, tc.expErrMessage) - } else { - require.NoError(t, err) - } - }) - } -} - -// Test that we can pass extra args to envoy via the extraEnvoyArgs flag -// or via pod annotations. When arguments are passed in both ways, the -// arguments set via pod annotations are used. -func TestHandlerConsulDataplaneSidecar_EnvoyExtraArgs(t *testing.T) { - cases := []struct { - name string - envoyExtraArgs string - pod *corev1.Pod - expectedExtraArgs string - }{ - { - name: "no extra options provided", - envoyExtraArgs: "", - pod: &corev1.Pod{}, - expectedExtraArgs: "", - }, - { - name: "via flag: extra log-level option", - envoyExtraArgs: "--log-level debug", - pod: &corev1.Pod{}, - expectedExtraArgs: "-- --log-level debug", - }, - { - name: "via flag: multiple arguments with quotes", - envoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - pod: &corev1.Pod{}, - expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - { - name: "via annotation: multiple arguments with quotes", - envoyExtraArgs: "", - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - }, - }, - expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - { - name: "via flag and annotation: should prefer setting via the annotation", - envoyExtraArgs: "this should be overwritten", - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - }, - }, - expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - h := MeshWebhook{ - ImageConsul: "hashicorp/consul:latest", - ImageConsulDataplane: "hashicorp/consul-k8s:latest", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - EnvoyExtraArgs: tc.envoyExtraArgs, - } - - c, err := h.consulDataplaneSidecar(testNS, *tc.pod) - require.NoError(t, err) - require.Contains(t, strings.Join(c.Args, " "), tc.expectedExtraArgs) - }) - } -} - -func TestHandlerConsulDataplaneSidecar_UserVolumeMounts(t *testing.T) { - cases := []struct { - name string - pod corev1.Pod - expectedContainerVolumeMounts []corev1.VolumeMount - expErr string - }{ - { - name: "able to set a sidecar container volume mount via annotation", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - constants.AnnotationConsulSidecarUserVolumeMount: "[{\"name\": \"tls-cert\", \"mountPath\": \"/custom/path\"}, {\"name\": \"tls-ca\", \"mountPath\": \"/custom/path2\"}]", - }, - }, - }, - expectedContainerVolumeMounts: []corev1.VolumeMount{ - { - Name: "consul-mesh-inject-data", - MountPath: "/consul/mesh-inject", - }, - { - Name: "tls-cert", - MountPath: "/custom/path", - }, - { - Name: "tls-ca", - MountPath: "/custom/path2", - }, - }, - }, - { - name: "invalid annotation results in error", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - constants.AnnotationConsulSidecarUserVolumeMount: "[abcdefg]", - }, - }, - }, - expErr: "invalid character 'a' looking ", - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - h := MeshWebhook{ - ImageConsul: "hashicorp/consul:latest", - ImageConsulDataplane: "hashicorp/consul-k8s:latest", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - } - c, err := h.consulDataplaneSidecar(testNS, tc.pod) - if tc.expErr == "" { - require.NoError(t, err) - require.Equal(t, tc.expectedContainerVolumeMounts, c.VolumeMounts) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), tc.expErr) - } - }) - } -} - -func TestHandlerConsulDataplaneSidecar_Resources(t *testing.T) { - mem1 := resource.MustParse("100Mi") - mem2 := resource.MustParse("200Mi") - cpu1 := resource.MustParse("100m") - cpu2 := resource.MustParse("200m") - zero := resource.MustParse("0") - - cases := map[string]struct { - webhook MeshWebhook - annotations map[string]string - expResources corev1.ResourceRequirements - expErr string - }{ - "no defaults, no annotations": { - webhook: MeshWebhook{}, - annotations: nil, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{}, - Requests: corev1.ResourceList{}, - }, - }, - "all defaults, no annotations": { - webhook: MeshWebhook{ - DefaultProxyCPURequest: cpu1, - DefaultProxyCPULimit: cpu2, - DefaultProxyMemoryRequest: mem1, - DefaultProxyMemoryLimit: mem2, - }, - annotations: nil, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: cpu2, - corev1.ResourceMemory: mem2, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: cpu1, - corev1.ResourceMemory: mem1, - }, - }, - }, - "no defaults, all annotations": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPURequest: "100m", - constants.AnnotationSidecarProxyMemoryRequest: "100Mi", - constants.AnnotationSidecarProxyCPULimit: "200m", - constants.AnnotationSidecarProxyMemoryLimit: "200Mi", - }, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: cpu2, - corev1.ResourceMemory: mem2, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: cpu1, - corev1.ResourceMemory: mem1, - }, - }, - }, - "annotations override defaults": { - webhook: MeshWebhook{ - DefaultProxyCPURequest: zero, - DefaultProxyCPULimit: zero, - DefaultProxyMemoryRequest: zero, - DefaultProxyMemoryLimit: zero, - }, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPURequest: "100m", - constants.AnnotationSidecarProxyMemoryRequest: "100Mi", - constants.AnnotationSidecarProxyCPULimit: "200m", - constants.AnnotationSidecarProxyMemoryLimit: "200Mi", - }, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: cpu2, - corev1.ResourceMemory: mem2, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: cpu1, - corev1.ResourceMemory: mem1, - }, - }, - }, - "defaults set to zero, no annotations": { - webhook: MeshWebhook{ - DefaultProxyCPURequest: zero, - DefaultProxyCPULimit: zero, - DefaultProxyMemoryRequest: zero, - DefaultProxyMemoryLimit: zero, - }, - annotations: nil, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: zero, - corev1.ResourceMemory: zero, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: zero, - corev1.ResourceMemory: zero, - }, - }, - }, - "annotations set to 0": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPURequest: "0", - constants.AnnotationSidecarProxyMemoryRequest: "0", - constants.AnnotationSidecarProxyCPULimit: "0", - constants.AnnotationSidecarProxyMemoryLimit: "0", - }, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: zero, - corev1.ResourceMemory: zero, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: zero, - corev1.ResourceMemory: zero, - }, - }, - }, - "invalid cpu request": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPURequest: "invalid", - }, - expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-cpu-request:\"invalid\": quantities must match the regular expression", - }, - "invalid cpu limit": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPULimit: "invalid", - }, - expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-cpu-limit:\"invalid\": quantities must match the regular expression", - }, - "invalid memory request": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyMemoryRequest: "invalid", - }, - expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-memory-request:\"invalid\": quantities must match the regular expression", - }, - "invalid memory limit": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyMemoryLimit: "invalid", - }, - expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-memory-limit:\"invalid\": quantities must match the regular expression", - }, - } - - for name, c := range cases { - t.Run(name, func(tt *testing.T) { - c.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} - require := require.New(tt) - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: c.annotations, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := c.webhook.consulDataplaneSidecar(testNS, pod) - if c.expErr != "" { - require.NotNil(err) - require.Contains(err.Error(), c.expErr) - } else { - require.NoError(err) - require.Equal(c.expResources, container.Resources) - } - }) - } -} - -func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { - cases := []struct { - name string - pod corev1.Pod - expCmdArgs string - expPorts []corev1.ContainerPort - expErr string - }{ - { - name: "default", - pod: corev1.Pod{}, - expCmdArgs: "", - }, - { - name: "turning on merged metrics", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web", - constants.AnnotationEnableMetrics: "true", - constants.AnnotationEnableMetricsMerging: "true", - constants.AnnotationMergedMetricsPort: "20100", - constants.AnnotationPort: "1234", - constants.AnnotationPrometheusScrapePath: "/scrape-path", - }, - }, - }, - expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20100 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics", - expPorts: []corev1.ContainerPort{ - { - Name: "prometheus", - ContainerPort: 20200, - Protocol: corev1.ProtocolTCP, - }, - }, - }, - { - name: "metrics with prometheus port override", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web", - constants.AnnotationEnableMetrics: "true", - constants.AnnotationEnableMetricsMerging: "true", - constants.AnnotationMergedMetricsPort: "20123", - constants.AnnotationPort: "1234", - constants.AnnotationPrometheusScrapePath: "/scrape-path", - constants.AnnotationPrometheusScrapePort: "6789", - }, - }, - }, - expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20123 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics", - expPorts: []corev1.ContainerPort{ - { - Name: "prometheus", - ContainerPort: 6789, - Protocol: corev1.ProtocolTCP, - }, - }, - }, - { - name: "merged metrics with TLS enabled", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web", - constants.AnnotationEnableMetrics: "true", - constants.AnnotationEnableMetricsMerging: "true", - constants.AnnotationMergedMetricsPort: "20100", - constants.AnnotationPort: "1234", - constants.AnnotationPrometheusScrapePath: "/scrape-path", - constants.AnnotationPrometheusCAFile: "/certs/ca.crt", - constants.AnnotationPrometheusCAPath: "/certs/ca", - constants.AnnotationPrometheusCertFile: "/certs/server.crt", - constants.AnnotationPrometheusKeyFile: "/certs/key.pem", - }, - }, - }, - expCmdArgs: "-telemetry-prom-scrape-path=/scrape-path -telemetry-prom-merge-port=20100 -telemetry-prom-service-metrics-url=http://127.0.0.1:1234/metrics -telemetry-prom-ca-certs-file=/certs/ca.crt -telemetry-prom-ca-certs-path=/certs/ca -telemetry-prom-cert-file=/certs/server.crt -telemetry-prom-key-file=/certs/key.pem", - expPorts: []corev1.ContainerPort{ - { - Name: "prometheus", - ContainerPort: 20200, - Protocol: corev1.ProtocolTCP, - }, - }, - }, - { - name: "merge metrics with TLS enabled, missing CA gives an error", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web", - constants.AnnotationEnableMetrics: "true", - constants.AnnotationEnableMetricsMerging: "true", - constants.AnnotationMergedMetricsPort: "20100", - constants.AnnotationPort: "1234", - constants.AnnotationPrometheusScrapePath: "/scrape-path", - constants.AnnotationPrometheusCertFile: "/certs/server.crt", - constants.AnnotationPrometheusKeyFile: "/certs/key.pem", - }, - }, - }, - expCmdArgs: "", - expErr: fmt.Sprintf("must set one of %q or %q when providing prometheus TLS config", constants.AnnotationPrometheusCAFile, constants.AnnotationPrometheusCAPath), - }, - { - name: "merge metrics with TLS enabled, missing cert gives an error", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web", - constants.AnnotationEnableMetrics: "true", - constants.AnnotationEnableMetricsMerging: "true", - constants.AnnotationMergedMetricsPort: "20100", - constants.AnnotationPort: "1234", - constants.AnnotationPrometheusScrapePath: "/scrape-path", - constants.AnnotationPrometheusCAFile: "/certs/ca.crt", - constants.AnnotationPrometheusKeyFile: "/certs/key.pem", - }, - }, - }, - expCmdArgs: "", - expErr: fmt.Sprintf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusCertFile), - }, - { - name: "merge metrics with TLS enabled, missing key file gives an error", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web", - constants.AnnotationEnableMetrics: "true", - constants.AnnotationEnableMetricsMerging: "true", - constants.AnnotationMergedMetricsPort: "20100", - constants.AnnotationPort: "1234", - constants.AnnotationPrometheusScrapePath: "/scrape-path", - constants.AnnotationPrometheusCAPath: "/certs/ca", - constants.AnnotationPrometheusCertFile: "/certs/server.crt", - }, - }, - }, - expCmdArgs: "", - expErr: fmt.Sprintf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusKeyFile), - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - MetricsConfig: metrics.Config{ - // These are all the default values passed from the CLI - DefaultPrometheusScrapePort: "20200", - DefaultPrometheusScrapePath: "/metrics", - DefaultMergedMetricsPort: "20100", - }, - } - container, err := h.consulDataplaneSidecar(testNS, c.pod) - if c.expErr != "" { - require.NotNil(t, err) - require.Contains(t, err.Error(), c.expErr) - } else { - require.NoError(t, err) - require.Contains(t, strings.Join(container.Args, " "), c.expCmdArgs) - if c.expPorts != nil { - require.ElementsMatch(t, container.Ports, c.expPorts) - } - } - }) - } -} - -func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { - gracefulShutdownSeconds := 10 - gracefulPort := "20307" - gracefulShutdownPath := "/exit" - - cases := []struct { - name string - webhook MeshWebhook - annotations map[string]string - expCmdArgs string - expErr string - }{ - { - name: "no defaults, no annotations", - webhook: MeshWebhook{}, - annotations: nil, - expCmdArgs: "", - }, - { - name: "all defaults, no annotations", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: true, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - }, - }, - annotations: nil, - expCmdArgs: "graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", - }, - { - name: "no defaults, all annotations", - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "true", - constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "true", - constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds), - constants.AnnotationSidecarProxyLifecycleGracefulPort: gracefulPort, - constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: gracefulShutdownPath, - }, - expCmdArgs: "-graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", - }, - { - name: "annotations override defaults", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: false, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - }, - }, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "true", - constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", - constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds + 5), - constants.AnnotationSidecarProxyLifecycleGracefulPort: "20317", - constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: "/foo", - }, - expCmdArgs: "-graceful-port=20317 -shutdown-grace-period-seconds=15 -graceful-shutdown-path=/foo", - }, - { - name: "lifecycle disabled, no annotations", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: false, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - }, - }, - annotations: nil, - expCmdArgs: "-graceful-port=20307", - }, - { - name: "lifecycle enabled, defaults omited, no annotations", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: true, - }, - }, - annotations: nil, - expCmdArgs: "", - }, - { - name: "annotations disable lifecycle default", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: true, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - }, - }, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "false", - }, - expCmdArgs: "-graceful-port=20307", - }, - { - name: "annotations skip graceful shutdown", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: false, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - }, - }, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "false", - constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", - constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: "0", - }, - expCmdArgs: "", - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - c.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} - require := require.New(t) - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: c.annotations, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := c.webhook.consulDataplaneSidecar(testNS, pod) - if c.expErr != "" { - require.NotNil(err) - require.Contains(err.Error(), c.expErr) - } else { - require.NoError(err) - require.Contains(strings.Join(container.Args, " "), c.expCmdArgs) - } - }) - } -} - -// boolPtr returns pointer to b. -func boolPtr(b bool) *bool { - return &b -} diff --git a/control-plane/connect-inject/webhookv2/container_env.go b/control-plane/connect-inject/webhookv2/container_env.go deleted file mode 100644 index b612b3c6aa..0000000000 --- a/control-plane/connect-inject/webhookv2/container_env.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "fmt" - "strconv" - "strings" - - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" -) - -func (w *MeshWebhook) containerEnvVars(pod corev1.Pod) ([]corev1.EnvVar, error) { - destinations, err := common.ProcessPodDestinationsForMeshWebhook(pod) - if err != nil { - return nil, fmt.Errorf("error processing the destination for container environment variable creation: %s", err.Error()) - } - if destinations == nil { - return nil, nil - } - - var result []corev1.EnvVar - for _, destination := range destinations.Destinations { - serviceName := strings.TrimSpace(destination.DestinationRef.Name) - serviceName = strings.ToUpper(strings.Replace(serviceName, "-", "_", -1)) - portName := strings.TrimSpace(destination.DestinationPort) - portName = strings.ToUpper(strings.Replace(portName, "-", "_", -1)) - - result = append(result, corev1.EnvVar{ - Name: fmt.Sprintf("%s_%s_CONNECT_SERVICE_HOST", serviceName, portName), - Value: destination.GetIpPort().Ip, - }, corev1.EnvVar{ - Name: fmt.Sprintf("%s_%s_CONNECT_SERVICE_PORT", serviceName, portName), - Value: strconv.Itoa(int(destination.GetIpPort().Port)), - }) - } - - return result, nil -} diff --git a/control-plane/connect-inject/webhookv2/container_env_test.go b/control-plane/connect-inject/webhookv2/container_env_test.go deleted file mode 100644 index 01f5b1f82e..0000000000 --- a/control-plane/connect-inject/webhookv2/container_env_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "testing" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -func TestContainerEnvVars(t *testing.T) { - cases := []struct { - Name string - Upstream string - ExpectError bool - }{ - { - // TODO: This will not error out when dcs are supported - Name: "Upstream with datacenter", - Upstream: "myPort.static-server:7890:dc1", - ExpectError: true, - }, - { - Name: "Upstream without datacenter", - Upstream: "myPort.static-server:7890", - }, - { - // TODO: This will not error out when dcs are supported - Name: "Upstream with labels and datacenter", - Upstream: "myPort.port.static-server.svc.dc1.dc:7890", - ExpectError: true, - }, - { - Name: "Upstream with labels and no datacenter", - Upstream: "myPort.port.static-server.svc:7890", - }, - { - Name: "Error expected, wrong order", - Upstream: "static-server.svc.myPort.port:7890", - ExpectError: true, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - var w MeshWebhook - envVars, err := w.containerEnvVars(corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - constants.AnnotationMeshDestinations: tt.Upstream, - }, - }, - }) - - if !tt.ExpectError { - require.NoError(err) - require.ElementsMatch(envVars, []corev1.EnvVar{ - { - Name: "STATIC_SERVER_MYPORT_CONNECT_SERVICE_HOST", - Value: "127.0.0.1", - }, { - Name: "STATIC_SERVER_MYPORT_CONNECT_SERVICE_PORT", - Value: "7890", - }, - }) - } else { - require.Error(err) - } - }) - } -} diff --git a/control-plane/connect-inject/webhookv2/container_init.go b/control-plane/connect-inject/webhookv2/container_init.go deleted file mode 100644 index 7afcaefd33..0000000000 --- a/control-plane/connect-inject/webhookv2/container_init.go +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "bytes" - "strconv" - "strings" - "text/template" - - corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - injectInitContainerName = "consul-mesh-init" - rootUserAndGroupID = 0 - sidecarUserAndGroupID = 5995 - initContainersUserAndGroupID = 5996 - netAdminCapability = "NET_ADMIN" -) - -type initContainerCommandData struct { - ServiceName string - ServiceAccountName string - AuthMethod string - - // Log settings for the mesh-init command. - LogLevel string - LogJSON bool -} - -// containerInit returns the init container spec for mesh-init that polls for the workload's bootstrap config -// so that it optionally set up iptables for transparent proxy. Otherwise, it ensures the workload exists before -// the pod starts. -func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) (corev1.Container, error) { - // Check if tproxy is enabled on this pod. - tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) - if err != nil { - return corev1.Container{}, err - } - - data := initContainerCommandData{ - AuthMethod: w.AuthMethod, - LogLevel: w.LogLevel, - LogJSON: w.LogJSON, - } - - // Create expected volume mounts - volMounts := []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/mesh-inject", - }, - } - - data.ServiceName = pod.Annotations[constants.AnnotationService] - var bearerTokenFile string - if w.AuthMethod != "" { - data.ServiceAccountName = pod.Spec.ServiceAccountName - // Extract the service account token's volume mount - var saTokenVolumeMount corev1.VolumeMount - saTokenVolumeMount, bearerTokenFile, err = findServiceAccountVolumeMount(pod) - if err != nil { - return corev1.Container{}, err - } - - // Append to volume mounts - volMounts = append(volMounts, saTokenVolumeMount) - } - - // Render the command - var buf bytes.Buffer - tpl := template.Must(template.New("root").Parse(strings.TrimSpace( - initContainerCommandTpl))) - err = tpl.Execute(&buf, &data) - if err != nil { - return corev1.Container{}, err - } - - initContainerName := injectInitContainerName - container := corev1.Container{ - Name: initContainerName, - Image: w.ImageConsulK8S, - Env: []corev1.EnvVar{ - { - Name: "POD_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, - }, - }, - { - Name: "POD_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, - }, - }, - { - Name: "CONSUL_ADDRESSES", - Value: w.ConsulAddress, - }, - { - Name: "CONSUL_GRPC_PORT", - Value: strconv.Itoa(w.ConsulConfig.GRPCPort), - }, - { - Name: "CONSUL_HTTP_PORT", - Value: strconv.Itoa(w.ConsulConfig.HTTPPort), - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: w.ConsulConfig.APITimeout.String(), - }, - }, - Resources: w.InitContainerResources, - VolumeMounts: volMounts, - Command: []string{"/bin/sh", "-ec", buf.String()}, - } - - if w.TLSEnabled { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: constants.UseTLSEnvVar, - Value: "true", - }, - corev1.EnvVar{ - Name: constants.CACertPEMEnvVar, - Value: w.ConsulCACert, - }, - corev1.EnvVar{ - Name: constants.TLSServerNameEnvVar, - Value: w.ConsulTLSServerName, - }) - } - - if w.AuthMethod != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: w.AuthMethod, - }, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: bearerTokenFile, - }, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }) - - if w.EnableNamespaces { - if w.EnableK8SNSMirroring { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_NAMESPACE", - Value: "default", - }) - } else { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_NAMESPACE", - Value: w.consulNamespace(namespace.Name), - }) - } - } - - if w.ConsulPartition != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_PARTITION", - Value: w.ConsulPartition, - }) - } - } - if w.EnableNamespaces { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_NAMESPACE", - Value: w.consulNamespace(namespace.Name), - }) - } - - if w.ConsulPartition != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_PARTITION", - Value: w.ConsulPartition, - }) - } - - // OpenShift without CNI is the only environment where privileged must be true. - privileged := false - if w.EnableOpenShift && !w.EnableCNI { - privileged = true - } - - if tproxyEnabled { - if !w.EnableCNI { - // Set redirect traffic config for the container so that we can apply iptables rules. - redirectTrafficConfig, err := w.iptablesConfigJSON(pod, namespace) - if err != nil { - return corev1.Container{}, err - } - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_REDIRECT_TRAFFIC_CONFIG", - Value: redirectTrafficConfig, - }) - - // Running consul mesh-init redirect-traffic with iptables - // requires both being a root user and having NET_ADMIN capability. - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(rootUserAndGroupID), - RunAsGroup: pointer.Int64(rootUserAndGroupID), - // RunAsNonRoot overrides any setting in the Pod so that we can still run as root here as required. - RunAsNonRoot: pointer.Bool(false), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{netAdminCapability}, - }, - } - } else { - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(initContainersUserAndGroupID), - RunAsGroup: pointer.Int64(initContainersUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - }, - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - } - } - } - - return container, nil -} - -// consulDNSEnabled returns true if Consul DNS should be enabled for this pod. -// It returns an error when the annotation value cannot be parsed by strconv.ParseBool or if we are unable -// to read the pod's namespace label when it exists. -func consulDNSEnabled(namespace corev1.Namespace, pod corev1.Pod, globalDNSEnabled bool, globalTProxyEnabled bool) (bool, error) { - // DNS is only possible when tproxy is also enabled because it relies - // on traffic being redirected. - tproxy, err := common.TransparentProxyEnabled(namespace, pod, globalTProxyEnabled) - if err != nil { - return false, err - } - if !tproxy { - return false, nil - } - - // First check to see if the pod annotation exists to override the namespace or global settings. - if raw, ok := pod.Annotations[constants.KeyConsulDNS]; ok { - return strconv.ParseBool(raw) - } - // Next see if the namespace has been defaulted. - if raw, ok := namespace.Labels[constants.KeyConsulDNS]; ok { - return strconv.ParseBool(raw) - } - // Else fall back to the global default. - return globalDNSEnabled, nil -} - -// splitCommaSeparatedItemsFromAnnotation takes an annotation and a pod -// and returns the comma-separated value of the annotation as a list of strings. -func splitCommaSeparatedItemsFromAnnotation(annotation string, pod corev1.Pod) []string { - var items []string - if raw, ok := pod.Annotations[annotation]; ok { - items = append(items, strings.Split(raw, ",")...) - } - - return items -} - -// initContainerCommandTpl is the template for the command executed by -// the init container. -const initContainerCommandTpl = ` -consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level={{ .LogLevel }} \ - -log-json={{ .LogJSON }} \ -` diff --git a/control-plane/connect-inject/webhookv2/container_init_test.go b/control-plane/connect-inject/webhookv2/container_init_test.go deleted file mode 100644 index b85ecd3ba5..0000000000 --- a/control-plane/connect-inject/webhookv2/container_init_test.go +++ /dev/null @@ -1,808 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const k8sNamespace = "k8snamespace" - -func TestHandlerContainerInit(t *testing.T) { - minimal := func() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pod", - Namespace: "test-namespace", - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - }, - }, - Status: corev1.PodStatus{ - HostIP: "1.1.1.1", - PodIP: "2.2.2.2", - }, - } - } - - cases := []struct { - Name string - Pod func(*corev1.Pod) *corev1.Pod - Webhook MeshWebhook - ExpCmd string // Strings.Contains test - ExpEnv []corev1.EnvVar - }{ - { - "default cmd and env", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - LogLevel: "info", - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "0s", - }, - }, - }, - - { - "with auth method", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - pod.Spec.ServiceAccountName = "a-service-account-name" - pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{ - { - Name: "sa", - MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", - }, - } - return pod - }, - MeshWebhook{ - AuthMethod: "an-auth-method", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - LogLevel: "debug", - LogJSON: true, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=debug \ - -log-json=true \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: "an-auth-method", - }, - { - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", - }, - { - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - w := tt.Webhook - pod := *tt.Pod(minimal()) - container, err := w.containerInit(testNS, pod) - require.NoError(t, err) - actual := strings.Join(container.Command, " ") - require.Contains(t, actual, tt.ExpCmd) - require.EqualValues(t, container.Env[2:], tt.ExpEnv) - }) - } -} - -func TestHandlerContainerInit_transparentProxy(t *testing.T) { - cases := map[string]struct { - globalEnabled bool - cniEnabled bool - annotations map[string]string - expTproxyEnabled bool - namespaceLabel map[string]string - openShiftEnabled bool - }{ - "enabled globally, ns not set, annotation not provided, cni disabled, openshift disabled": { - true, - false, - nil, - true, - nil, - false, - }, - "enabled globally, ns not set, annotation is false, cni disabled, openshift disabled": { - true, - false, - map[string]string{constants.KeyTransparentProxy: "false"}, - false, - nil, - false, - }, - "enabled globally, ns not set, annotation is true, cni disabled, openshift disabled": { - true, - false, - map[string]string{constants.KeyTransparentProxy: "true"}, - true, - nil, - false, - }, - "disabled globally, ns not set, annotation not provided, cni disabled, openshift disabled": { - false, - false, - nil, - false, - nil, - false, - }, - "disabled globally, ns not set, annotation is false, cni disabled, openshift disabled": { - false, - false, - map[string]string{constants.KeyTransparentProxy: "false"}, - false, - nil, - false, - }, - "disabled globally, ns not set, annotation is true, cni disabled, openshift disabled": { - false, - false, - map[string]string{constants.KeyTransparentProxy: "true"}, - true, - nil, - false, - }, - "disabled globally, ns enabled, annotation not set, cni disabled, openshift disabled": { - false, - false, - nil, - true, - map[string]string{constants.KeyTransparentProxy: "true"}, - false, - }, - "enabled globally, ns disabled, annotation not set, cni disabled, openshift disabled": { - true, - false, - nil, - false, - map[string]string{constants.KeyTransparentProxy: "false"}, - false, - }, - "disabled globally, ns enabled, annotation not set, cni enabled, openshift disabled": { - false, - true, - nil, - false, - map[string]string{constants.KeyTransparentProxy: "true"}, - false, - }, - - "enabled globally, ns not set, annotation not set, cni enabled, openshift disabled": { - true, - true, - nil, - false, - nil, - false, - }, - "enabled globally, ns not set, annotation not set, cni enabled, openshift enabled": { - true, - true, - nil, - false, - nil, - true, - }, - "enabled globally, ns not set, annotation not set, cni disabled, openshift enabled": { - true, - false, - nil, - true, - nil, - true, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - w := MeshWebhook{ - EnableTransparentProxy: c.globalEnabled, - EnableCNI: c.cniEnabled, - ConsulConfig: &consul.Config{HTTPPort: 8500}, - EnableOpenShift: c.openShiftEnabled, - } - pod := minimal() - pod.Annotations = c.annotations - - privileged := false - if c.openShiftEnabled && !c.cniEnabled { - privileged = true - } - - var expectedSecurityContext *corev1.SecurityContext - if c.cniEnabled { - expectedSecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(initContainersUserAndGroupID), - RunAsGroup: pointer.Int64(initContainersUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - }, - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - } - } else if c.expTproxyEnabled { - expectedSecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(0), - RunAsGroup: pointer.Int64(0), - RunAsNonRoot: pointer.Bool(false), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{netAdminCapability}, - }, - } - } - ns := testNS - ns.Labels = c.namespaceLabel - container, err := w.containerInit(ns, *pod) - require.NoError(t, err) - - redirectTrafficEnvVarFound := false - for _, ev := range container.Env { - if ev.Name == "CONSUL_REDIRECT_TRAFFIC_CONFIG" { - redirectTrafficEnvVarFound = true - break - } - } - - require.Equal(t, c.expTproxyEnabled, redirectTrafficEnvVarFound) - require.Equal(t, expectedSecurityContext, container.SecurityContext) - }) - } -} - -func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { - minimal := func() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - { - Name: "auth-method-secret", - VolumeMounts: []corev1.VolumeMount{ - { - Name: "service-account-secret", - MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", - }, - }, - }, - }, - ServiceAccountName: "web", - }, - } - } - - cases := []struct { - Name string - Pod func(*corev1.Pod) *corev1.Pod - Webhook MeshWebhook - Cmd string - ExpEnv []corev1.EnvVar - }{ - { - "default namespace, no partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - EnableNamespaces: true, - ConsulDestinationNamespace: "default", - ConsulPartition: "", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "default", - }, - }, - }, - { - "default namespace, default partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - EnableNamespaces: true, - ConsulDestinationNamespace: "default", - ConsulPartition: "default", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "default", - }, - { - Name: "CONSUL_PARTITION", - Value: "default", - }, - }, - }, - { - "non-default namespace, no partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - EnableNamespaces: true, - ConsulDestinationNamespace: "non-default", - ConsulPartition: "", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "non-default", - }, - }, - }, - { - "non-default namespace, non-default partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - EnableNamespaces: true, - ConsulDestinationNamespace: "non-default", - ConsulPartition: "non-default-part", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "non-default", - }, - { - Name: "CONSUL_PARTITION", - Value: "non-default-part", - }, - }, - }, - { - "auth method, non-default namespace, mirroring disabled, default partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "" - return pod - }, - MeshWebhook{ - AuthMethod: "auth-method", - EnableNamespaces: true, - ConsulDestinationNamespace: "non-default", - ConsulPartition: "default", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: "auth-method", - }, - { - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", - }, - { - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - { - Name: "CONSUL_LOGIN_NAMESPACE", - Value: "non-default", - }, - { - Name: "CONSUL_LOGIN_PARTITION", - Value: "default", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "non-default", - }, - { - Name: "CONSUL_PARTITION", - Value: "default", - }, - }, - }, - { - "auth method, non-default namespace, mirroring enabled, non-default partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "" - return pod - }, - MeshWebhook{ - AuthMethod: "auth-method", - EnableNamespaces: true, - ConsulDestinationNamespace: "non-default", // Overridden by mirroring - EnableK8SNSMirroring: true, - ConsulPartition: "non-default", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: "auth-method", - }, - { - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", - }, - { - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - { - Name: "CONSUL_LOGIN_NAMESPACE", - Value: "default", - }, - { - Name: "CONSUL_LOGIN_PARTITION", - Value: "non-default", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "k8snamespace", - }, - { - Name: "CONSUL_PARTITION", - Value: "non-default", - }, - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - h := tt.Webhook - h.LogLevel = "info" - container, err := h.containerInit(testNS, *tt.Pod(minimal())) - require.NoError(t, err) - actual := strings.Join(container.Command, " ") - require.Equal(t, tt.Cmd, actual) - if tt.ExpEnv != nil { - require.Equal(t, tt.ExpEnv, container.Env[2:]) - } - }) - } -} - -// If TLSEnabled is set, -// Consul addresses should use HTTPS -// and CA cert should be set as env variable if provided. -// Additionally, test that the init container is correctly configured -// when http or gRPC ports are different from defaults. -func TestHandlerContainerInit_WithTLSAndCustomPorts(t *testing.T) { - for _, caProvided := range []bool{true, false} { - name := fmt.Sprintf("ca provided: %t", caProvided) - t.Run(name, func(t *testing.T) { - w := MeshWebhook{ - ConsulAddress: "10.0.0.0", - TLSEnabled: true, - ConsulConfig: &consul.Config{HTTPPort: 443, GRPCPort: 8503}, - } - if caProvided { - w.ConsulCACert = "consul-ca-cert" - } - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := w.containerInit(testNS, *pod) - require.NoError(t, err) - require.Equal(t, "CONSUL_ADDRESSES", container.Env[2].Name) - require.Equal(t, w.ConsulAddress, container.Env[2].Value) - require.Equal(t, "CONSUL_GRPC_PORT", container.Env[3].Name) - require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.GRPCPort), container.Env[3].Value) - require.Equal(t, "CONSUL_HTTP_PORT", container.Env[4].Name) - require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.HTTPPort), container.Env[4].Value) - if w.TLSEnabled { - require.Equal(t, "CONSUL_USE_TLS", container.Env[6].Name) - require.Equal(t, "true", container.Env[6].Value) - if caProvided { - require.Equal(t, "CONSUL_CACERT_PEM", container.Env[7].Name) - require.Equal(t, "consul-ca-cert", container.Env[7].Value) - } else { - for _, ev := range container.Env { - if ev.Name == "CONSUL_CACERT_PEM" { - require.Empty(t, ev.Value) - } - } - } - } - - }) - } -} - -func TestHandlerContainerInit_Resources(t *testing.T) { - w := MeshWebhook{ - InitContainerResources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("10m"), - corev1.ResourceMemory: resource.MustParse("10Mi"), - }, - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("20m"), - corev1.ResourceMemory: resource.MustParse("25Mi"), - }, - }, - ConsulConfig: &consul.Config{HTTPPort: 8500, APITimeout: 5 * time.Second}, - } - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := w.containerInit(testNS, *pod) - require.NoError(t, err) - require.Equal(t, corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("20m"), - corev1.ResourceMemory: resource.MustParse("25Mi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("10m"), - corev1.ResourceMemory: resource.MustParse("10Mi"), - }, - }, container.Resources) -} - -var testNS = corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: k8sNamespace, - Labels: map[string]string{}, - }, -} - -func minimal() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespaces.DefaultNamespace, - Name: "minimal", - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - }, - }, - } -} diff --git a/control-plane/connect-inject/webhookv2/container_volume.go b/control-plane/connect-inject/webhookv2/container_volume.go deleted file mode 100644 index a05a6720db..0000000000 --- a/control-plane/connect-inject/webhookv2/container_volume.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - corev1 "k8s.io/api/core/v1" -) - -// volumeName is the name of the volume that is created to store the -// Consul Connect injection data. -const volumeName = "consul-mesh-inject-data" - -// containerVolume returns the volume data to add to the pod. This volume -// is used for shared data between containers. -func (w *MeshWebhook) containerVolume() corev1.Volume { - return corev1.Volume{ - Name: volumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, - }, - } -} diff --git a/control-plane/connect-inject/webhookv2/dns.go b/control-plane/connect-inject/webhookv2/dns.go deleted file mode 100644 index 883c9ed034..0000000000 --- a/control-plane/connect-inject/webhookv2/dns.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "fmt" - "strconv" - - "github.com/miekg/dns" - corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" -) - -const ( - // These defaults are taken from the /etc/resolv.conf man page - // and are used by the dns library. - defaultDNSOptionNdots = 1 - defaultDNSOptionTimeout = 5 - defaultDNSOptionAttempts = 2 - - // defaultEtcResolvConfFile is the default location of the /etc/resolv.conf file. - defaultEtcResolvConfFile = "/etc/resolv.conf" -) - -func (w *MeshWebhook) configureDNS(pod *corev1.Pod, k8sNS string) error { - // First, we need to determine the nameservers configured in this cluster from /etc/resolv.conf. - etcResolvConf := defaultEtcResolvConfFile - if w.etcResolvFile != "" { - etcResolvConf = w.etcResolvFile - } - cfg, err := dns.ClientConfigFromFile(etcResolvConf) - if err != nil { - return err - } - - // Set DNS policy on the pod to None because we want DNS to work according to the config we will provide. - pod.Spec.DNSPolicy = corev1.DNSNone - - // Set the consul-dataplane's DNS server as the first server in the list (i.e. localhost). - // We want to do that so that when consul cannot resolve the record, we will fall back to the nameservers - // configured in our /etc/resolv.conf. It's important to add Consul DNS as the first nameserver because - // if we put kube DNS first, it will return NXDOMAIN response and a DNS client will not fall back to other nameservers. - if pod.Spec.DNSConfig == nil { - nameservers := []string{consulDataplaneDNSBindHost} - nameservers = append(nameservers, cfg.Servers...) - var options []corev1.PodDNSConfigOption - if cfg.Ndots != defaultDNSOptionNdots { - ndots := strconv.Itoa(cfg.Ndots) - options = append(options, corev1.PodDNSConfigOption{ - Name: "ndots", - Value: &ndots, - }) - } - if cfg.Timeout != defaultDNSOptionTimeout { - options = append(options, corev1.PodDNSConfigOption{ - Name: "timeout", - Value: pointer.String(strconv.Itoa(cfg.Timeout)), - }) - } - if cfg.Attempts != defaultDNSOptionAttempts { - options = append(options, corev1.PodDNSConfigOption{ - Name: "attempts", - Value: pointer.String(strconv.Itoa(cfg.Attempts)), - }) - } - - // Replace release namespace in the searches with the pod namespace. - // This is so that the searches we generate will be for the pod's namespace - // instead of the namespace of the connect-injector. E.g. instead of - // consul.svc.cluster.local it should be .svc.cluster.local. - var searches []string - // Kubernetes will add a search domain for .svc.cluster.local so we can always - // expect it to be there. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services. - consulReleaseNSSearchDomain := fmt.Sprintf("%s.svc.cluster.local", w.ReleaseNamespace) - for _, search := range cfg.Search { - if search == consulReleaseNSSearchDomain { - searches = append(searches, fmt.Sprintf("%s.svc.cluster.local", k8sNS)) - } else { - searches = append(searches, search) - } - } - - pod.Spec.DNSConfig = &corev1.PodDNSConfig{ - Nameservers: nameservers, - Searches: searches, - Options: options, - } - } else { - return fmt.Errorf("DNS redirection to Consul is not supported with an already defined DNSConfig on the pod") - } - return nil -} diff --git a/control-plane/connect-inject/webhookv2/dns_test.go b/control-plane/connect-inject/webhookv2/dns_test.go deleted file mode 100644 index e7a380b271..0000000000 --- a/control-plane/connect-inject/webhookv2/dns_test.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" -) - -func TestMeshWebhook_configureDNS(t *testing.T) { - cases := map[string]struct { - etcResolv string - expDNSConfig *corev1.PodDNSConfig - }{ - "empty /etc/resolv.conf file": { - expDNSConfig: &corev1.PodDNSConfig{ - Nameservers: []string{"127.0.0.1"}, - }, - }, - "one nameserver": { - etcResolv: `nameserver 1.1.1.1`, - expDNSConfig: &corev1.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "1.1.1.1"}, - }, - }, - "mutiple nameservers, searches, and options": { - etcResolv: ` -nameserver 1.1.1.1 -nameserver 2.2.2.2 -search foo.bar bar.baz -options ndots:5 timeout:6 attempts:3`, - expDNSConfig: &corev1.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "1.1.1.1", "2.2.2.2"}, - Searches: []string{"foo.bar", "bar.baz"}, - Options: []corev1.PodDNSConfigOption{ - { - Name: "ndots", - Value: pointer.String("5"), - }, - { - Name: "timeout", - Value: pointer.String("6"), - }, - { - Name: "attempts", - Value: pointer.String("3"), - }, - }, - }, - }, - "replaces release specific search domains": { - etcResolv: ` -nameserver 1.1.1.1 -nameserver 2.2.2.2 -search consul.svc.cluster.local svc.cluster.local cluster.local -options ndots:5`, - expDNSConfig: &corev1.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "1.1.1.1", "2.2.2.2"}, - Searches: []string{"default.svc.cluster.local", "svc.cluster.local", "cluster.local"}, - Options: []corev1.PodDNSConfigOption{ - { - Name: "ndots", - Value: pointer.String("5"), - }, - }, - }, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - etcResolvFile, err := os.CreateTemp("", "") - require.NoError(t, err) - t.Cleanup(func() { - _ = os.RemoveAll(etcResolvFile.Name()) - }) - _, err = etcResolvFile.WriteString(c.etcResolv) - require.NoError(t, err) - w := MeshWebhook{ - etcResolvFile: etcResolvFile.Name(), - ReleaseNamespace: "consul", - } - - pod := minimal() - err = w.configureDNS(pod, "default") - require.NoError(t, err) - require.Equal(t, corev1.DNSNone, pod.Spec.DNSPolicy) - require.Equal(t, c.expDNSConfig, pod.Spec.DNSConfig) - }) - } -} - -func TestMeshWebhook_configureDNS_error(t *testing.T) { - w := MeshWebhook{} - - pod := minimal() - pod.Spec.DNSConfig = &corev1.PodDNSConfig{Nameservers: []string{"1.1.1.1"}} - err := w.configureDNS(pod, "default") - require.EqualError(t, err, "DNS redirection to Consul is not supported with an already defined DNSConfig on the pod") -} diff --git a/control-plane/connect-inject/webhookv2/health_checks_test.go b/control-plane/connect-inject/webhookv2/health_checks_test.go deleted file mode 100644 index 82b7cdd99d..0000000000 --- a/control-plane/connect-inject/webhookv2/health_checks_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestReady(t *testing.T) { - - var cases = []struct { - name string - certFileContents *string - keyFileContents *string - expectError bool - }{ - {"Both cert and key files not present.", nil, nil, true}, - {"Cert file not empty and key file missing.", ptrToString("test"), nil, true}, - {"Key file not empty and cert file missing.", nil, ptrToString("test"), true}, - {"Both cert and key files are present and not empty.", ptrToString("test"), ptrToString("test"), false}, - {"Both cert and key files are present but both are empty.", ptrToString(""), ptrToString(""), true}, - {"Both cert and key files are present but key file is empty.", ptrToString("test"), ptrToString(""), true}, - {"Both cert and key files are present but cert file is empty.", ptrToString(""), ptrToString("test"), true}, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "") - require.NoError(t, err) - if tt.certFileContents != nil { - err := os.WriteFile(filepath.Join(tmpDir, "tls.crt"), []byte(*tt.certFileContents), 0666) - require.NoError(t, err) - } - if tt.keyFileContents != nil { - err := os.WriteFile(filepath.Join(tmpDir, "tls.key"), []byte(*tt.keyFileContents), 0666) - require.NoError(t, err) - } - rc := ReadinessCheck{tmpDir} - err = rc.Ready(nil) - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func ptrToString(s string) *string { - return &s -} diff --git a/control-plane/connect-inject/webhookv2/heath_checks.go b/control-plane/connect-inject/webhookv2/heath_checks.go deleted file mode 100644 index 6bd11f6efa..0000000000 --- a/control-plane/connect-inject/webhookv2/heath_checks.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "errors" - "net/http" - "os" - "path/filepath" -) - -type ReadinessCheck struct { - CertDir string -} - -func (r ReadinessCheck) Ready(_ *http.Request) error { - certFile, err := os.ReadFile(filepath.Join(r.CertDir, "tls.crt")) - if err != nil { - return err - } - keyFile, err := os.ReadFile(filepath.Join(r.CertDir, "tls.key")) - if err != nil { - return err - } - if len(certFile) == 0 || len(keyFile) == 0 { - return errors.New("certificate files have not been loaded") - } - return nil -} diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook.go b/control-plane/connect-inject/webhookv2/mesh_webhook.go deleted file mode 100644 index 590608bce7..0000000000 --- a/control-plane/connect-inject/webhookv2/mesh_webhook.go +++ /dev/null @@ -1,555 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "strconv" - "strings" - - mapset "github.com/deckarep/golang-set" - "github.com/go-logr/logr" - "golang.org/x/exp/slices" - "gomodules.xyz/jsonpatch/v2" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" -) - -const ( - sidecarContainer = "consul-dataplane" - - // exposedPathsLivenessPortsRangeStart is the start of the port range that we will use as - // the ListenerPort for the Expose configuration of the proxy registration for a liveness probe. - exposedPathsLivenessPortsRangeStart = 20300 - - // exposedPathsReadinessPortsRangeStart is the start of the port range that we will use as - // the ListenerPort for the Expose configuration of the proxy registration for a readiness probe. - exposedPathsReadinessPortsRangeStart = 20400 - - // exposedPathsStartupPortsRangeStart is the start of the port range that we will use as - // the ListenerPort for the Expose configuration of the proxy registration for a startup probe. - exposedPathsStartupPortsRangeStart = 20500 -) - -// kubeSystemNamespaces is a set of namespaces that are considered -// "system" level namespaces and are always skipped (never injected). -var kubeSystemNamespaces = mapset.NewSetWith(metav1.NamespaceSystem, metav1.NamespacePublic) - -// MeshWebhook is the HTTP meshWebhook for admission webhooks. -type MeshWebhook struct { - Clientset kubernetes.Interface - - // ConsulClientConfig is the config to create a Consul API client. - ConsulConfig *consul.Config - - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - - // ImageConsul is the container image for Consul to use. - // ImageConsulDataplane is the container image for Envoy to use. - // - // Both of these MUST be set. - ImageConsul string - ImageConsulDataplane string - - // ImageConsulK8S is the container image for consul-k8s to use. - // This image is used for the consul-sidecar container. - ImageConsulK8S string - - // Optional: set when you need extra options to be set when running envoy - // See a list of args here: https://www.envoyproxy.io/docs/envoy/latest/operations/cli - EnvoyExtraArgs string - - // RequireAnnotation means that the annotation must be given to inject. - // If this is false, injection is default. - RequireAnnotation bool - - // AuthMethod is the name of the Kubernetes Auth Method to - // use for identity with connectInjection if ACLs are enabled. - AuthMethod string - - // The PEM-encoded CA certificate string - // to use when communicating with Consul clients over HTTPS. - // If not set, will use HTTP. - ConsulCACert string - - // TLSEnabled indicates whether we should use TLS for communicating to Consul. - TLSEnabled bool - - // ConsulAddress is the address of the Consul server. This should be only the - // host (i.e. not including port or protocol). - ConsulAddress string - - // ConsulTLSServerName is the SNI header to use to connect to the Consul servers - // over TLS. - ConsulTLSServerName string - - // ConsulPartition is the name of the Admin Partition that the controller - // is deployed in. It is an enterprise feature requiring Consul Enterprise 1.11+. - // Its value is an empty string if partitions aren't enabled. - ConsulPartition string - - // EnableNamespaces indicates that a user is running Consul Enterprise - // with version 1.7+ which is namespace aware. It enables Consul namespaces, - // with injection into either a single Consul namespace or mirrored from - // k8s namespaces. - EnableNamespaces bool - - // AllowK8sNamespacesSet is a set of k8s namespaces to explicitly allow for - // injection. It supports the special character `*` which indicates that - // all k8s namespaces are eligible unless explicitly denied. This filter - // is applied before checking pod annotations. - AllowK8sNamespacesSet mapset.Set - - // DenyK8sNamespacesSet is a set of k8s namespaces to explicitly deny - // injection and thus service registration with Consul. An empty set - // means that no namespaces are removed from consideration. This filter - // takes precedence over AllowK8sNamespacesSet. - DenyK8sNamespacesSet mapset.Set - - // ConsulDestinationNamespace is the name of the Consul namespace to register all - // injected services into if Consul namespaces are enabled and mirroring - // is disabled. This may be set, but will not be used if mirroring is enabled. - ConsulDestinationNamespace string - - // EnableK8SNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any service being registered into Consul. Services are - // registered into the Consul namespace that mirrors their k8s namespace. - EnableK8SNSMirroring bool - - // K8SNSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - K8SNSMirroringPrefix string - - // CrossNamespaceACLPolicy is the name of the ACL policy to attach to - // any created Consul namespaces to allow cross namespace service discovery. - // Only necessary if ACLs are enabled. - CrossNamespaceACLPolicy string - - // Default resource settings for sidecar proxies. Some of these - // fields may be empty. - DefaultProxyCPURequest resource.Quantity - DefaultProxyCPULimit resource.Quantity - DefaultProxyMemoryRequest resource.Quantity - DefaultProxyMemoryLimit resource.Quantity - - // LifecycleConfig contains proxy lifecycle management configuration from the inject-connect command and has methods to determine whether - // configuration should come from the default flags or annotations. The meshWebhook uses this to configure container sidecar proxy args. - LifecycleConfig lifecycle.Config - - // Default Envoy concurrency flag, this is the number of worker threads to be used by the proxy. - DefaultEnvoyProxyConcurrency int - - // MetricsConfig contains metrics configuration from the inject-connect command and has methods to determine whether - // configuration should come from the default flags or annotations. The meshWebhook uses this to configure prometheus - // annotations and the merged metrics server. - MetricsConfig metrics.Config - - // Resource settings for init container. All of these fields - // will be populated by the defaults provided in the initial flags. - InitContainerResources corev1.ResourceRequirements - - // Resource settings for Consul sidecar. All of these fields - // will be populated by the defaults provided in the initial flags. - DefaultConsulSidecarResources corev1.ResourceRequirements - - // EnableTransparentProxy enables transparent proxy mode. - // This means that the injected init container will apply traffic redirection rules - // so that all traffic will go through the Envoy proxy. - EnableTransparentProxy bool - - // EnableCNI enables the CNI plugin and prevents the connect-inject init container - // from running the consul redirect-traffic command as the CNI plugin handles traffic - // redirection - EnableCNI bool - - // TProxyOverwriteProbes controls whether the webhook should mutate pod's HTTP probes - // to point them to the Envoy proxy. - TProxyOverwriteProbes bool - - // EnableConsulDNS enables traffic redirection so that DNS requests are directed to Consul - // from mesh services. - EnableConsulDNS bool - - // EnableOpenShift indicates that when tproxy is enabled, the security context for the Envoy and init - // containers should not be added because OpenShift sets a random user for those and will not allow - // those containers to be created otherwise. - EnableOpenShift bool - - // SkipServerWatch prevents consul-dataplane from consuming the server update stream. This is useful - // for situations where Consul servers are behind a load balancer. - SkipServerWatch bool - - // ReleaseNamespace is the Kubernetes namespace where this webhook is running. - ReleaseNamespace string - - // Log - Log logr.Logger - // Log settings for consul-dataplane and connect-init containers. - LogLevel string - LogJSON bool - - decoder *admission.Decoder - // etcResolvFile is only used in tests to stub out /etc/resolv.conf file. - etcResolvFile string -} - -// Handle is the admission.Webhook implementation that actually handles the -// webhook request for admission control. This should be registered or -// served via the controller runtime manager. -func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var pod corev1.Pod - - // Decode the pod from the request - if err := w.decoder.Decode(req, &pod); err != nil { - w.Log.Error(err, "could not unmarshal request to pod") - return admission.Errored(http.StatusBadRequest, err) - } - - // Marshall the contents of the pod that was received. This is compared with the - // marshalled contents of the pod after it has been updated to create the jsonpatch. - origPodJson, err := json.Marshal(pod) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - // Setup the default annotation values that are used for the container. - // This MUST be done before shouldInject is called since that function - // uses these annotations. - if err := w.defaultAnnotations(&pod, string(origPodJson)); err != nil { - w.Log.Error(err, "error creating default annotations", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error creating default annotations: %s", err)) - } - - // Check if we should inject, for example we don't inject in the - // system namespaces. - if shouldInject, err := w.shouldInject(pod, req.Namespace); err != nil { - w.Log.Error(err, "error checking if should inject", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking if should inject: %s", err)) - } else if !shouldInject { - return admission.Allowed(fmt.Sprintf("%s %s does not require injection", pod.Kind, pod.Name)) - } - - w.Log.Info("received pod", "name", req.Name, "ns", req.Namespace) - - // Validate that none of the pod ports start with the prefix "cslport-" as that may result in conflicts with ports - // created by the pod controller when creating workloads. - for _, c := range pod.Spec.Containers { - for _, p := range c.Ports { - if strings.HasPrefix(p.Name, constants.UnnamedWorkloadPortNamePrefix) { - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error creating pod: port names cannot be prefixed with \"cslport-\" as that prefix is reserved")) - } - } - } - - // Add our volume that will be shared by the init container and - // the sidecar for passing data in the pod. - pod.Spec.Volumes = append(pod.Spec.Volumes, w.containerVolume()) - - // Optionally mount data volume to other containers - w.injectVolumeMount(pod) - - // Optionally add any volumes that are to be used by the envoy sidecar. - if _, ok := pod.Annotations[constants.AnnotationConsulSidecarUserVolume]; ok { - var userVolumes []corev1.Volume - err := json.Unmarshal([]byte(pod.Annotations[constants.AnnotationConsulSidecarUserVolume]), &userVolumes) - if err != nil { - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error unmarshalling sidecar user volumes: %s", err)) - } - pod.Spec.Volumes = append(pod.Spec.Volumes, userVolumes...) - } - - // Add the upstream services as environment variables for easy - // service discovery. - containerEnvVars, err := w.containerEnvVars(pod) - if err != nil { - w.Log.Error(err, "error creating the port environment variables based on pod annotations", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error creating the port environment variables based on pod annotations: %s", err)) - } - for i := range pod.Spec.InitContainers { - pod.Spec.InitContainers[i].Env = append(pod.Spec.InitContainers[i].Env, containerEnvVars...) - } - - for i := range pod.Spec.Containers { - pod.Spec.Containers[i].Env = append(pod.Spec.Containers[i].Env, containerEnvVars...) - } - - // A user can enable/disable tproxy for an entire namespace via a label. - ns, err := w.Clientset.CoreV1().Namespaces().Get(ctx, req.Namespace, metav1.GetOptions{}) - if err != nil { - w.Log.Error(err, "error fetching namespace metadata for container", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error getting namespace metadata for container: %s", err)) - } - - lifecycleEnabled, ok := w.LifecycleConfig.EnableProxyLifecycle(pod) - if ok != nil { - w.Log.Error(err, "unable to get lifecycle enabled status") - } - // Add the init container that registers the service and sets up the Envoy configuration. - initContainer, err := w.containerInit(*ns, pod) - if err != nil { - w.Log.Error(err, "error configuring injection init container", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection init container: %s", err)) - } - pod.Spec.InitContainers = append(pod.Spec.InitContainers, initContainer) - - // Add the Envoy sidecar. - envoySidecar, err := w.consulDataplaneSidecar(*ns, pod) - if err != nil { - w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) - } - //Append the Envoy sidecar before the application container only if lifecycle enabled. - - if lifecycleEnabled && ok == nil { - pod.Spec.Containers = append([]corev1.Container{envoySidecar}, pod.Spec.Containers...) - } else { - pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) - } - - // pod.Annotations has already been initialized by h.defaultAnnotations() - // and does not need to be checked for being a nil value. - pod.Annotations[constants.KeyMeshInjectStatus] = constants.Injected - - tproxyEnabled, err := common.TransparentProxyEnabled(*ns, pod, w.EnableTransparentProxy) - if err != nil { - w.Log.Error(err, "error determining if transparent proxy is enabled", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error determining if transparent proxy is enabled: %s", err)) - } - - // Add an annotation to the pod sets transparent-proxy-status to enabled or disabled. Used by the CNI plugin - // to determine if it should traffic redirect or not. - if tproxyEnabled { - pod.Annotations[constants.KeyTransparentProxyStatus] = constants.Enabled - } - - // If DNS redirection is enabled, we want to configure dns on the pod. - dnsEnabled, err := consulDNSEnabled(*ns, pod, w.EnableConsulDNS, w.EnableTransparentProxy) - if err != nil { - w.Log.Error(err, "error determining if dns redirection is enabled", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error determining if dns redirection is enabled: %s", err)) - } - if dnsEnabled { - if err = w.configureDNS(&pod, req.Namespace); err != nil { - w.Log.Error(err, "error configuring DNS on the pod", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring DNS on the pod: %s", err)) - } - } - - // Add annotations for metrics. - if err = w.prometheusAnnotations(&pod); err != nil { - w.Log.Error(err, "error configuring prometheus annotations", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring prometheus annotations: %s", err)) - } - - if pod.Labels == nil { - pod.Labels = make(map[string]string) - } - pod.Labels[constants.KeyMeshInjectStatus] = constants.Injected - - // Consul-ENT only: Add the Consul destination namespace as an annotation to the pod. - if w.EnableNamespaces { - pod.Annotations[constants.AnnotationConsulNamespace] = w.consulNamespace(req.Namespace) - } - - // Overwrite readiness/liveness probes if needed. - err = w.overwriteProbes(*ns, &pod) - if err != nil { - w.Log.Error(err, "error overwriting readiness or liveness probes", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error overwriting readiness or liveness probes: %s", err)) - } - - // When CNI and tproxy are enabled, we add an annotation to the pod that contains the iptables config so that the CNI - // plugin can apply redirect traffic rules on the pod. - if w.EnableCNI && tproxyEnabled { - if err = w.addRedirectTrafficConfigAnnotation(&pod, *ns); err != nil { - w.Log.Error(err, "error configuring annotation for CNI traffic redirection", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring annotation for CNI traffic redirection: %s", err)) - } - } - - // Marshall the pod into JSON after it has the desired envs, annotations, labels, - // sidecars and initContainers appended to it. - updatedPodJson, err := json.Marshal(pod) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - // Create a patches based on the Pod that was received by the meshWebhook - // and the desired Pod spec. - patches, err := jsonpatch.CreatePatch(origPodJson, updatedPodJson) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - // Return a Patched response along with the patches we intend on applying to the - // Pod received by the meshWebhook. - return admission.Patched(fmt.Sprintf("valid %s request", pod.Kind), patches...) -} - -// overwriteProbes overwrites readiness/liveness probes of this pod when -// both transparent proxy is enabled and overwrite probes is true for the pod. -func (w *MeshWebhook) overwriteProbes(ns corev1.Namespace, pod *corev1.Pod) error { - tproxyEnabled, err := common.TransparentProxyEnabled(ns, *pod, w.EnableTransparentProxy) - if err != nil { - return err - } - - overwriteProbes, err := common.ShouldOverwriteProbes(*pod, w.TProxyOverwriteProbes) - if err != nil { - return err - } - - if tproxyEnabled && overwriteProbes { - // We don't use the loop index because this needs to line up w.withiptablesConfigJSON, - // which is performed before the sidecar is injected. - idx := 0 - for _, container := range pod.Spec.Containers { - // skip the "envoy-sidecar" container from having it's probes overridden - if container.Name == sidecarContainer { - continue - } - if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + idx) - } - if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + idx) - } - if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + idx) - } - idx++ - } - } - return nil -} - -func (w *MeshWebhook) injectVolumeMount(pod corev1.Pod) { - containersToInject := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationMeshInjectMountVolumes, pod) - - for index, container := range pod.Spec.Containers { - if slices.Contains(containersToInject, container.Name) { - pod.Spec.Containers[index].VolumeMounts = append(pod.Spec.Containers[index].VolumeMounts, corev1.VolumeMount{ - Name: volumeName, - MountPath: "/consul/mesh-inject", - }) - } - } -} - -func (w *MeshWebhook) shouldInject(pod corev1.Pod, namespace string) (bool, error) { - // Don't inject in the Kubernetes system namespaces - if kubeSystemNamespaces.Contains(namespace) { - return false, nil - } - - // Namespace logic - // If in deny list, don't inject - if w.DenyK8sNamespacesSet.Contains(namespace) { - return false, nil - } - - // If not in allow list or allow list is not *, don't inject - if !w.AllowK8sNamespacesSet.Contains("*") && !w.AllowK8sNamespacesSet.Contains(namespace) { - return false, nil - } - - // If we already injected then don't inject again - if pod.Annotations[constants.KeyMeshInjectStatus] != "" || pod.Annotations[constants.KeyInjectStatus] != "" { - return false, nil - } - - // If the explicit true/false is on, then take that value. Note that - // this has to be the last check since it sets a default value after - // all other checks. - if raw, ok := pod.Annotations[constants.AnnotationMeshInject]; ok { - return strconv.ParseBool(raw) - } - - return !w.RequireAnnotation, nil -} - -func (w *MeshWebhook) defaultAnnotations(pod *corev1.Pod, podJson string) error { - if pod.Annotations == nil { - pod.Annotations = make(map[string]string) - } - - pod.Annotations[constants.AnnotationOriginalPod] = podJson - pod.Annotations[constants.AnnotationConsulK8sVersion] = version.GetHumanVersion() - - return nil -} - -// prometheusAnnotations sets the Prometheus scraping configuration -// annotations on the Pod. -func (w *MeshWebhook) prometheusAnnotations(pod *corev1.Pod) error { - enableMetrics, err := w.MetricsConfig.EnableMetrics(*pod) - if err != nil { - return err - } - prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(*pod) - if err != nil { - return err - } - prometheusScrapePath := w.MetricsConfig.PrometheusScrapePath(*pod) - - if enableMetrics { - pod.Annotations[constants.AnnotationPrometheusScrape] = "true" - pod.Annotations[constants.AnnotationPrometheusPort] = prometheusScrapePort - pod.Annotations[constants.AnnotationPrometheusPath] = prometheusScrapePath - } - return nil -} - -// consulNamespace returns the namespace that a service should be -// registered in based on the namespace options. It returns an -// empty string if namespaces aren't enabled. -func (w *MeshWebhook) consulNamespace(ns string) string { - return namespaces.ConsulNamespace(ns, w.EnableNamespaces, w.ConsulDestinationNamespace, w.EnableK8SNSMirroring, w.K8SNSMirroringPrefix) -} - -func findServiceAccountVolumeMount(pod corev1.Pod) (corev1.VolumeMount, string, error) { - // Find the volume mount that is mounted at the known - // service account token location - var volumeMount corev1.VolumeMount - for _, container := range pod.Spec.Containers { - for _, vm := range container.VolumeMounts { - if vm.MountPath == "/var/run/secrets/kubernetes.io/serviceaccount" { - volumeMount = vm - break - } - } - } - - // Return an error if volumeMount is still empty - if (corev1.VolumeMount{}) == volumeMount { - return volumeMount, "", errors.New("unable to find service account token volumeMount") - } - - return volumeMount, "/var/run/secrets/kubernetes.io/serviceaccount/token", nil -} - -func (w *MeshWebhook) InjectDecoder(d *admission.Decoder) error { - w.decoder = d - return nil -} diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go deleted file mode 100644 index 0924a01e6c..0000000000 --- a/control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package webhookv2 - -import ( - "context" - "testing" - - "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testing" - "github.com/stretchr/testify/require" - admissionv1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -// Test that the annotation for the Consul namespace is added. -func TestHandler_MutateWithNamespaces_Annotation(t *testing.T) { - t.Parallel() - sourceKubeNS := "kube-ns" - - cases := map[string]struct { - ConsulDestinationNamespace string - Mirroring bool - MirroringPrefix string - ExpNamespaceAnnotation string - }{ - "dest: default": { - ConsulDestinationNamespace: "default", - ExpNamespaceAnnotation: "default", - }, - "dest: foo": { - ConsulDestinationNamespace: "foo", - ExpNamespaceAnnotation: "foo", - }, - "mirroring": { - Mirroring: true, - ExpNamespaceAnnotation: sourceKubeNS, - }, - "mirroring with prefix": { - Mirroring: true, - MirroringPrefix: "prefix-", - ExpNamespaceAnnotation: "prefix-" + sourceKubeNS, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - require.NoError(t, err) - - webhook := MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: c.ConsulDestinationNamespace, - EnableK8SNSMirroring: c.Mirroring, - K8SNSMirroringPrefix: c.MirroringPrefix, - ConsulConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - decoder: decoder, - Clientset: clientWithNamespace(sourceKubeNS), - } - - pod := corev1.Pod{ - ObjectMeta: v1.ObjectMeta{ - Namespace: sourceKubeNS, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - request := admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &pod), - Namespace: sourceKubeNS, - }, - } - resp := webhook.Handle(context.Background(), request) - require.Equal(t, resp.Allowed, true) - - // Check that the annotation was added as a patch. - var consulNamespaceAnnotationValue string - for _, patch := range resp.Patches { - if patch.Path == "/metadata/annotations" { - for annotationName, annotationValue := range patch.Value.(map[string]interface{}) { - if annotationName == constants.AnnotationConsulNamespace { - consulNamespaceAnnotationValue = annotationValue.(string) - } - } - } - } - require.NotEmpty(t, consulNamespaceAnnotationValue, "no namespace annotation set") - require.Equal(t, c.ExpNamespaceAnnotation, consulNamespaceAnnotationValue) - }) - } -} diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go deleted file mode 100644 index 8bb6dc7a2f..0000000000 --- a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go +++ /dev/null @@ -1,2177 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "context" - "encoding/json" - "strconv" - "strings" - "testing" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/sdk/iptables" - "github.com/stretchr/testify/require" - "gomodules.xyz/jsonpatch/v2" - admissionv1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" -) - -func TestHandlerHandle(t *testing.T) { - t.Parallel() - basicSpec := corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - } - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - cases := []struct { - Name string - Webhook MeshWebhook - Req admission.Request - Err string // expected error string, not exact - Patches []jsonpatch.Operation - }{ - { - "kube-system namespace", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: metav1.NamespaceSystem, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - }, - }, - "", - nil, - }, - - { - "already injected", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.KeyMeshInjectStatus: constants.Injected, - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - nil, - }, - - { - "empty pod basic", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations", - }, - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - }, - }, - { - "empty pod basic with lifecycle", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations", - }, - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - }, - }, - { - "pod with destinations specified", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshDestinations: "myPort1.echo:1234,myPort2.db:1234", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - }, - }, - { - "error pod with incorrect destinations specified", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshDestinations: "db:1234", - }, - }, - Spec: basicSpec, - }), - }, - }, - "error creating the port environment variables based on pod annotations", - []jsonpatch.Operation{}, - }, - { - "empty pod with injection disabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshInject: "false", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - nil, - }, - - { - "empty pod with injection truthy", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshInject: "t", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - - { - "pod with empty volume mount annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshInjectMountVolumes: "", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - { - "pod with volume mount annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshInjectMountVolumes: "web,unknown,web_three_point_oh", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web_two_point_oh", - }, - { - Name: "web_three_point_oh", - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/containers/2/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/3", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - { - "pod with sidecar volume mount annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationConsulSidecarUserVolume: "[{\"name\":\"bbb\",\"csi\":{\"driver\":\"bob\"}}]", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - { - "pod with sidecar invalid volume mount annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationConsulSidecarUserVolume: "[a]", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - }), - }, - }, - "error unmarshalling sidecar user volumes: invalid character 'a' looking for beginning of value", - nil, - }, - { - "pod with service annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - - { - "pod with existing label", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "testLabel": "123", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/annotations", - }, - { - Operation: "add", - Path: "/metadata/labels/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - }, - }, - { - "tproxy with overwriteProbes is enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - // We're setting an existing annotation so that we can assert on the - // specific annotations that are set as a result of probes being overwritten. - Annotations: map[string]string{"foo": "bar"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), - }, - - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "replace", - Path: "/spec/containers/0/livenessProbe/httpGet/port", - }, - { - Operation: "replace", - Path: "/spec/containers/0/readinessProbe/httpGet/port", - }, - }, - }, - { - "dns redirection enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - Annotations: map[string]string{constants.KeyConsulDNS: "true"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), - }, - - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/spec/dnsPolicy", - }, - { - Operation: "add", - Path: "/spec/dnsConfig", - }, - }, - }, - { - "dns redirection only enabled if tproxy enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.KeyConsulDNS: "true", - constants.KeyTransparentProxy: "false", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - // Note: no DNS policy/config additions. - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} - ctx := context.Background() - resp := tt.Webhook.Handle(ctx, tt.Req) - if (tt.Err == "") != resp.Allowed { - t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) - } - if tt.Err != "" { - require.Contains(t, resp.Result.Message, tt.Err) - return - } - - actual := resp.Patches - if len(actual) > 0 { - for i := range actual { - actual[i].Value = nil - } - } - require.ElementsMatch(t, tt.Patches, actual) - }) - } -} - -// This test validates that overwrite probes match the iptables configuration fromiptablesConfigJSON() -// Because they happen at different points in the injection, the port numbers can get out of sync. -func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { - // TODO (v2/nitya): enable when expose paths and L7 are implemented - t.Skip("Tproxy probes are not supported yet") - t.Parallel() - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - cases := []struct { - Name string - Webhook MeshWebhook - Req admission.Request - Err string // expected error string, not exact - Patches []jsonpatch.Operation - }{ - { - "tproxy with overwriteProbes is enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - // We're setting an existing annotation so that we can assert on the - // specific annotations that are set as a result of probes being overwritten. - Annotations: map[string]string{"foo": "bar"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe/tcpSocket", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe/initialDelaySeconds", - }, - { - Operation: "remove", - Path: "/spec/containers/0/readinessProbe/httpGet", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "remove", - Path: "/spec/containers/0/startupProbe", - }, - { - Operation: "remove", - Path: "/spec/containers/0/livenessProbe", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), - }, - - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} - ctx := context.Background() - resp := tt.Webhook.Handle(ctx, tt.Req) - if (tt.Err == "") != resp.Allowed { - t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) - } - if tt.Err != "" { - require.Contains(t, resp.Result.Message, tt.Err) - return - } - - var iptablesCfg iptables.Config - var overwritePorts []string - actual := resp.Patches - if len(actual) > 0 { - for i := range actual { - - // We want to grab the iptables configuration from the connect-init container's - // environment. - if actual[i].Path == "/spec/initContainers" { - value := actual[i].Value.([]any) - valueMap := value[0].(map[string]any) - envs := valueMap["env"].([]any) - redirectEnv := envs[6].(map[string]any) - require.Equal(t, redirectEnv["name"].(string), "CONSUL_REDIRECT_TRAFFIC_CONFIG") - iptablesJson := redirectEnv["value"].(string) - - err := json.Unmarshal([]byte(iptablesJson), &iptablesCfg) - require.NoError(t, err) - } - - // We want to accumulate the httpGet Probes from the application container to - // compare them to the iptables rules. This is now the second container in the spec - if strings.Contains(actual[i].Path, "/spec/containers/1") { - valueMap, ok := actual[i].Value.(map[string]any) - require.True(t, ok) - - for k, v := range valueMap { - if strings.Contains(k, "Probe") { - probe := v.(map[string]any) - httpProbe := probe["httpGet"] - httpProbeMap := httpProbe.(map[string]any) - port := httpProbeMap["port"] - portNum := port.(float64) - - overwritePorts = append(overwritePorts, strconv.Itoa(int(portNum))) - } - } - } - - // nil out all the patch values to just compare the keys changing. - actual[i].Value = nil - } - } - // Make sure the iptables excluded ports match the ports on the container - require.ElementsMatch(t, iptablesCfg.ExcludeInboundPorts, overwritePorts) - require.ElementsMatch(t, tt.Patches, actual) - }) - } -} - -func TestHandlerValidatePorts(t *testing.T) { - cases := []struct { - Name string - Pod *corev1.Pod - Err string - }{ - { - "basic pod, with ports", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - Name: "http", - ContainerPort: 8080, - }, - }, - }, - { - Name: "web-side", - }, - }, - }, - }, - "", - }, - { - "basic pod, with unnamed ports", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - ContainerPort: 8080, - }, - }, - }, - { - Name: "web-side", - }, - }, - }, - }, - "", - }, - { - "basic pod, with invalid prefix name", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - Name: "cslport-8080", - ContainerPort: 8080, - }, - }, - }, - { - Name: "web-side", - }, - }, - }, - }, - "error creating pod: port names cannot be prefixed with \"cslport-\" as that prefix is reserved", - }, - } - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - w := MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - decoder: decoder, - ConsulConfig: &consul.Config{HTTPPort: 8500}, - Clientset: defaultTestClientWithNamespace(), - } - req := admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, tt.Pod), - }, - } - resp := w.Handle(context.Background(), req) - if tt.Err == "" { - require.True(t, resp.Allowed) - } else { - require.False(t, resp.Allowed) - require.Contains(t, resp.Result.Message, tt.Err) - } - - }) - } -} -func TestHandlerDefaultAnnotations(t *testing.T) { - cases := []struct { - Name string - Pod *corev1.Pod - Expected map[string]string - Err string - }{ - { - "empty", - &corev1.Pod{}, - map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), - }, - "", - }, - { - "basic pod, no ports", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - }, - }, - }, - map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), - }, - "", - }, - { - "basic pod, with ports", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - Name: "http", - ContainerPort: 8080, - }, - }, - }, - { - Name: "web-side", - }, - }, - }, - }, - map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), - }, - "", - }, - - { - "basic pod, with unnamed ports", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - ContainerPort: 8080, - }, - }, - }, - { - Name: "web-side", - }, - }, - }, - }, - map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), - }, - "", - }, - } - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - podJson, err := json.Marshal(tt.Pod) - require.NoError(t, err) - - var w MeshWebhook - err = w.defaultAnnotations(tt.Pod, string(podJson)) - if (tt.Err != "") != (err != nil) { - t.Fatalf("actual: %v, expected err: %v", err, tt.Err) - } - if tt.Err != "" { - require.Contains(t, err.Error(), tt.Err) - return - } - - actual := tt.Pod.Annotations - if len(actual) == 0 { - actual = nil - } - require.Equal(t, tt.Expected, actual) - }) - } -} - -func TestHandlerPrometheusAnnotations(t *testing.T) { - cases := []struct { - Name string - Webhook MeshWebhook - Expected map[string]string - }{ - { - Name: "Sets the correct prometheus annotations on the pod if metrics are enabled", - Webhook: MeshWebhook{ - MetricsConfig: metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "20200", - DefaultPrometheusScrapePath: "/metrics", - }, - }, - Expected: map[string]string{ - constants.AnnotationPrometheusScrape: "true", - constants.AnnotationPrometheusPort: "20200", - constants.AnnotationPrometheusPath: "/metrics", - }, - }, - { - Name: "Does not set annotations if metrics are not enabled", - Webhook: MeshWebhook{ - MetricsConfig: metrics.Config{ - DefaultEnableMetrics: false, - DefaultPrometheusScrapePort: "20200", - DefaultPrometheusScrapePath: "/metrics", - }, - }, - Expected: map[string]string{}, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - h := tt.Webhook - pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}} - - err := h.prometheusAnnotations(pod) - require.NoError(err) - - require.Equal(pod.Annotations, tt.Expected) - }) - } -} - -// Test consulNamespace function. -func TestConsulNamespace(t *testing.T) { - cases := []struct { - Name string - EnableNamespaces bool - ConsulDestinationNamespace string - EnableK8SNSMirroring bool - K8SNSMirroringPrefix string - K8sNamespace string - Expected string - }{ - { - "namespaces disabled", - false, - "default", - false, - "", - "namespace", - "", - }, - - { - "namespaces disabled, mirroring enabled", - false, - "default", - true, - "", - "namespace", - "", - }, - - { - "namespaces disabled, mirroring enabled, prefix defined", - false, - "default", - true, - "test-", - "namespace", - "", - }, - - { - "namespaces enabled, mirroring disabled", - true, - "default", - false, - "", - "namespace", - "default", - }, - - { - "namespaces enabled, mirroring disabled, prefix defined", - true, - "default", - false, - "test-", - "namespace", - "default", - }, - - { - "namespaces enabled, mirroring enabled", - true, - "default", - true, - "", - "namespace", - "namespace", - }, - - { - "namespaces enabled, mirroring enabled, prefix defined", - true, - "default", - true, - "test-", - "namespace", - "test-namespace", - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - - w := MeshWebhook{ - EnableNamespaces: tt.EnableNamespaces, - ConsulDestinationNamespace: tt.ConsulDestinationNamespace, - EnableK8SNSMirroring: tt.EnableK8SNSMirroring, - K8SNSMirroringPrefix: tt.K8SNSMirroringPrefix, - } - - ns := w.consulNamespace(tt.K8sNamespace) - - require.Equal(tt.Expected, ns) - }) - } -} - -// Test shouldInject function. -func TestShouldInject(t *testing.T) { - cases := []struct { - Name string - Pod *corev1.Pod - K8sNamespace string - EnableNamespaces bool - AllowK8sNamespacesSet mapset.Set - DenyK8sNamespacesSet mapset.Set - Expected bool - }{ - { - "kube-system not injected", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - // Service annotation is required for injection - constants.AnnotationService: "testing", - }, - }, - }, - "kube-system", - false, - mapset.NewSet(), - mapset.NewSet(), - false, - }, - { - "kube-public not injected", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "kube-public", - false, - mapset.NewSet(), - mapset.NewSet(), - false, - }, - { - "namespaces disabled, empty allow/deny lists", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSet(), - mapset.NewSet(), - false, - }, - { - "namespaces disabled, allow *", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("*"), - mapset.NewSet(), - true, - }, - { - "namespaces disabled, allow default", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("default"), - mapset.NewSet(), - true, - }, - { - "namespaces disabled, allow * and default", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("*", "default"), - mapset.NewSet(), - true, - }, - { - "namespaces disabled, allow only ns1 and ns2", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("ns1", "ns2"), - mapset.NewSet(), - false, - }, - { - "namespaces disabled, deny default ns", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSet(), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces disabled, allow *, deny default ns", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("*"), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces disabled, default ns in both allow and deny lists", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("default"), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces enabled, empty allow/deny lists", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSet(), - mapset.NewSet(), - false, - }, - { - "namespaces enabled, allow *", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("*"), - mapset.NewSet(), - true, - }, - { - "namespaces enabled, allow default", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("default"), - mapset.NewSet(), - true, - }, - { - "namespaces enabled, allow * and default", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("*", "default"), - mapset.NewSet(), - true, - }, - { - "namespaces enabled, allow only ns1 and ns2", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("ns1", "ns2"), - mapset.NewSet(), - false, - }, - { - "namespaces enabled, deny default ns", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSet(), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces enabled, allow *, deny default ns", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("*"), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces enabled, default ns in both allow and deny lists", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("default"), - mapset.NewSetWith("default"), - false, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - - w := MeshWebhook{ - RequireAnnotation: false, - EnableNamespaces: tt.EnableNamespaces, - AllowK8sNamespacesSet: tt.AllowK8sNamespacesSet, - DenyK8sNamespacesSet: tt.DenyK8sNamespacesSet, - } - - injected, err := w.shouldInject(*tt.Pod, tt.K8sNamespace) - - require.Equal(nil, err) - require.Equal(tt.Expected, injected) - }) - } -} - -func TestOverwriteProbes(t *testing.T) { - t.Parallel() - - cases := map[string]struct { - tproxyEnabled bool - overwriteProbes bool - podContainers []corev1.Container - expLivenessPort []int - expReadinessPort []int - expStartupPort []int - additionalAnnotations map[string]string - }{ - "transparent proxy disabled; overwrites probes disabled": { - tproxyEnabled: false, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - }, - expReadinessPort: []int{8080}, - }, - "transparent proxy enabled; overwrite probes disabled": { - tproxyEnabled: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - }, - expReadinessPort: []int{8080}, - }, - "transparent proxy disabled; overwrite probes enabled": { - tproxyEnabled: false, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - }, - expReadinessPort: []int{8080}, - }, - "just the readiness probe": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - }, - expReadinessPort: []int{exposedPathsReadinessPortsRangeStart}, - }, - "just the liveness probe": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - }, - }, - expLivenessPort: []int{exposedPathsLivenessPortsRangeStart}, - }, - "skips envoy sidecar": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: sidecarContainer, - }, - }, - }, - "readiness, liveness and startup probes": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - }, - }, - expLivenessPort: []int{exposedPathsLivenessPortsRangeStart}, - expReadinessPort: []int{exposedPathsReadinessPortsRangeStart}, - expStartupPort: []int{exposedPathsStartupPortsRangeStart}, - }, - "readiness, liveness and startup probes multiple containers": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - { - Name: "test-2", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8083, - }, - { - Name: "http", - ContainerPort: 8082, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8083), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - }, - }, - expLivenessPort: []int{exposedPathsLivenessPortsRangeStart, exposedPathsLivenessPortsRangeStart + 1}, - expReadinessPort: []int{exposedPathsReadinessPortsRangeStart, exposedPathsReadinessPortsRangeStart + 1}, - expStartupPort: []int{exposedPathsStartupPortsRangeStart, exposedPathsStartupPortsRangeStart + 1}, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - Annotations: map[string]string{}, - }, - Spec: corev1.PodSpec{ - Containers: c.podContainers, - }, - } - if c.additionalAnnotations != nil { - pod.ObjectMeta.Annotations = c.additionalAnnotations - } - - w := MeshWebhook{ - EnableTransparentProxy: c.tproxyEnabled, - TProxyOverwriteProbes: c.overwriteProbes, - } - err := w.overwriteProbes(corev1.Namespace{}, pod) - require.NoError(t, err) - for i, container := range pod.Spec.Containers { - if container.ReadinessProbe != nil { - require.Equal(t, c.expReadinessPort[i], container.ReadinessProbe.HTTPGet.Port.IntValue()) - } - if container.LivenessProbe != nil { - require.Equal(t, c.expLivenessPort[i], container.LivenessProbe.HTTPGet.Port.IntValue()) - } - if container.StartupProbe != nil { - require.Equal(t, c.expStartupPort[i], container.StartupProbe.HTTPGet.Port.IntValue()) - } - } - }) - } -} - -// encodeRaw is a helper to encode some data into a RawExtension. -func encodeRaw(t *testing.T, input interface{}) runtime.RawExtension { - data, err := json.Marshal(input) - require.NoError(t, err) - return runtime.RawExtension{Raw: data} -} - -// https://tools.ietf.org/html/rfc6901 -func escapeJSONPointer(s string) string { - s = strings.Replace(s, "~", "~0", -1) - s = strings.Replace(s, "/", "~1", -1) - return s -} - -func defaultTestClientWithNamespace() kubernetes.Interface { - return clientWithNamespace("default") -} - -func clientWithNamespace(name string) kubernetes.Interface { - ns := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } - return fake.NewSimpleClientset(&ns) -} diff --git a/control-plane/connect-inject/webhookv2/redirect_traffic.go b/control-plane/connect-inject/webhookv2/redirect_traffic.go deleted file mode 100644 index 8432372831..0000000000 --- a/control-plane/connect-inject/webhookv2/redirect_traffic.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "encoding/json" - "fmt" - "strconv" - - "github.com/hashicorp/consul/sdk/iptables" - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -// addRedirectTrafficConfigAnnotation creates an iptables.Config in JSON format based on proxy configuration. -// iptables.Config: -// -// ConsulDNSIP: an environment variable named RESOURCE_PREFIX_DNS_SERVICE_HOST where RESOURCE_PREFIX is the consul.fullname in helm. -// ProxyUserID: a constant set in Annotations -// ProxyInboundPort: the service port or bind port -// ProxyOutboundPort: default transparent proxy outbound port or transparent proxy outbound listener port -// ExcludeInboundPorts: prometheus, envoy stats, expose paths, checks and excluded pod annotations -// ExcludeOutboundPorts: pod annotations -// ExcludeOutboundCIDRs: pod annotations -// ExcludeUIDs: pod annotations -func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (string, error) { - cfg := iptables.Config{ - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - } - - // Set the proxy's inbound port. - cfg.ProxyInboundPort = constants.ProxyDefaultInboundPort - - // Set the proxy's outbound port. - cfg.ProxyOutboundPort = iptables.DefaultTProxyOutboundPort - - // If metrics are enabled, get the prometheusScrapePort and exclude it from the inbound ports - enableMetrics, err := w.MetricsConfig.EnableMetrics(pod) - if err != nil { - return "", err - } - if enableMetrics { - prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(pod) - if err != nil { - return "", err - } - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, prometheusScrapePort) - } - - // Exclude any overwritten liveness/readiness/startup ports from redirection. - overwriteProbes, err := common.ShouldOverwriteProbes(pod, w.TProxyOverwriteProbes) - if err != nil { - return "", err - } - - // Exclude the port on which the proxy health check port will be configured if - // using the proxy health check for a service. - if useProxyHealthCheck(pod) { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(constants.ProxyDefaultHealthPort)) - } - - if overwriteProbes { - // We don't use the loop index because this needs to line up w.overwriteProbes(), - // which is performed after the sidecar is injected. - idx := 0 - for _, container := range pod.Spec.Containers { - // skip the "consul-dataplane" container from having its probes overridden - if container.Name == sidecarContainer { - continue - } - if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+idx)) - } - if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+idx)) - } - if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+idx)) - } - idx++ - } - } - - // Inbound ports - excludeInboundPorts := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeInboundPorts, pod) - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, excludeInboundPorts...) - - // Outbound ports - excludeOutboundPorts := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeOutboundPorts, pod) - cfg.ExcludeOutboundPorts = append(cfg.ExcludeOutboundPorts, excludeOutboundPorts...) - - // Outbound CIDRs - excludeOutboundCIDRs := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeOutboundCIDRs, pod) - cfg.ExcludeOutboundCIDRs = append(cfg.ExcludeOutboundCIDRs, excludeOutboundCIDRs...) - - // UIDs - excludeUIDs := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeUIDs, pod) - cfg.ExcludeUIDs = append(cfg.ExcludeUIDs, excludeUIDs...) - - // Add init container user ID to exclude from traffic redirection. - cfg.ExcludeUIDs = append(cfg.ExcludeUIDs, strconv.Itoa(initContainersUserAndGroupID)) - - dnsEnabled, err := consulDNSEnabled(ns, pod, w.EnableConsulDNS, w.EnableTransparentProxy) - if err != nil { - return "", err - } - - if dnsEnabled { - // If Consul DNS is enabled, we find the environment variable that has the value - // of the ClusterIP of the Consul DNS Service. constructDNSServiceHostName returns - // the name of the env variable whose value is the ClusterIP of the Consul DNS Service. - cfg.ConsulDNSIP = consulDataplaneDNSBindHost - cfg.ConsulDNSPort = consulDataplaneDNSBindPort - } - - iptablesConfigJson, err := json.Marshal(&cfg) - if err != nil { - return "", fmt.Errorf("could not marshal iptables config: %w", err) - } - - return string(iptablesConfigJson), nil -} - -// addRedirectTrafficConfigAnnotation add the created iptables JSON config as an annotation on the provided pod. -func (w *MeshWebhook) addRedirectTrafficConfigAnnotation(pod *corev1.Pod, ns corev1.Namespace) error { - iptablesConfig, err := w.iptablesConfigJSON(*pod, ns) - if err != nil { - return err - } - - pod.Annotations[constants.AnnotationRedirectTraffic] = iptablesConfig - - return nil -} diff --git a/control-plane/connect-inject/webhookv2/redirect_traffic_test.go b/control-plane/connect-inject/webhookv2/redirect_traffic_test.go deleted file mode 100644 index 62b25722db..0000000000 --- a/control-plane/connect-inject/webhookv2/redirect_traffic_test.go +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "encoding/json" - "fmt" - "strconv" - "testing" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/sdk/iptables" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" -) - -const ( - defaultPodName = "fakePod" - defaultNamespace = "default" -) - -func TestAddRedirectTrafficConfig(t *testing.T) { - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - cases := []struct { - name string - webhook MeshWebhook - pod *corev1.Pod - namespace corev1.Namespace - dnsEnabled bool - expCfg iptables.Config - expErr error - }{ - { - name: "basic bare minimum pod", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - }, - }, - { - name: "proxy health checks enabled", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationUseProxyHealthCheck: "true", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{"21000"}, - }, - }, - { - name: "metrics enabled", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationEnableMetrics: "true", - constants.AnnotationPrometheusScrapePort: "13373", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{"13373"}, - }, - }, - { - name: "metrics enabled with incorrect annotation", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationEnableMetrics: "invalid", - constants.AnnotationPrometheusScrapePort: "13373", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{"13373"}, - }, - expErr: fmt.Errorf("%s annotation value of %s was invalid: %s", constants.AnnotationEnableMetrics, "invalid", "strconv.ParseBool: parsing \"invalid\": invalid syntax"), - }, - { - name: "overwrite probes, transparent proxy annotation set", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTransparentProxyOverwriteProbes: "true", - constants.KeyTransparentProxy: "true", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(exposedPathsLivenessPortsRangeStart), - }, - }, - }, - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{strconv.Itoa(exposedPathsLivenessPortsRangeStart)}, - }, - }, - { - name: "exclude inbound ports", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeInboundPorts: "1111,11111", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{"1111", "11111"}, - }, - }, - { - name: "exclude outbound ports", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeOutboundPorts: "2222,22222", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeOutboundPorts: []string{"2222", "22222"}, - }, - }, - { - name: "exclude outbound CIDRs", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeOutboundCIDRs: "3.3.3.3,3.3.3.3/24", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{strconv.Itoa(initContainersUserAndGroupID)}, - ExcludeOutboundCIDRs: []string{"3.3.3.3", "3.3.3.3/24"}, - }, - }, - { - name: "exclude UIDs", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeUIDs: "4444,44444", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"4444", "44444", strconv.Itoa(initContainersUserAndGroupID)}, - }, - }, - { - name: "exclude inbound ports, outbound ports, outbound CIDRs, and UIDs", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeInboundPorts: "1111,11111", - constants.AnnotationTProxyExcludeOutboundPorts: "2222,22222", - constants.AnnotationTProxyExcludeOutboundCIDRs: "3.3.3.3,3.3.3.3/24", - constants.AnnotationTProxyExcludeUIDs: "4444,44444", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeInboundPorts: []string{"1111", "11111"}, - ExcludeOutboundPorts: []string{"2222", "22222"}, - ExcludeOutboundCIDRs: []string{"3.3.3.3", "3.3.3.3/24"}, - ExcludeUIDs: []string{"4444", "44444", strconv.Itoa(initContainersUserAndGroupID)}, - }, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - err = c.webhook.addRedirectTrafficConfigAnnotation(c.pod, c.namespace) - - // Only compare annotation and iptables config on successful runs - if c.expErr == nil { - require.NoError(t, err) - anno, ok := c.pod.Annotations[constants.AnnotationRedirectTraffic] - require.Equal(t, ok, true) - - actualConfig := iptables.Config{} - err = json.Unmarshal([]byte(anno), &actualConfig) - require.NoError(t, err) - require.Equal(t, c.expCfg, actualConfig) - } else { - require.EqualError(t, err, c.expErr.Error()) - } - }) - } -} - -func TestRedirectTraffic_consulDNS(t *testing.T) { - cases := map[string]struct { - globalEnabled bool - annotations map[string]string - namespaceLabel map[string]string - expectConsulDNSConfig bool - }{ - "enabled globally, ns not set, annotation not provided": { - globalEnabled: true, - expectConsulDNSConfig: true, - }, - "enabled globally, ns not set, annotation is false": { - globalEnabled: true, - annotations: map[string]string{constants.KeyConsulDNS: "false"}, - expectConsulDNSConfig: false, - }, - "enabled globally, ns not set, annotation is true": { - globalEnabled: true, - annotations: map[string]string{constants.KeyConsulDNS: "true"}, - expectConsulDNSConfig: true, - }, - "disabled globally, ns not set, annotation not provided": { - expectConsulDNSConfig: false, - }, - "disabled globally, ns not set, annotation is false": { - annotations: map[string]string{constants.KeyConsulDNS: "false"}, - expectConsulDNSConfig: false, - }, - "disabled globally, ns not set, annotation is true": { - annotations: map[string]string{constants.KeyConsulDNS: "true"}, - expectConsulDNSConfig: true, - }, - "disabled globally, ns enabled, annotation not set": { - namespaceLabel: map[string]string{constants.KeyConsulDNS: "true"}, - expectConsulDNSConfig: true, - }, - "enabled globally, ns disabled, annotation not set": { - globalEnabled: true, - namespaceLabel: map[string]string{constants.KeyConsulDNS: "false"}, - expectConsulDNSConfig: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - w := MeshWebhook{ - EnableConsulDNS: c.globalEnabled, - EnableTransparentProxy: true, - ConsulConfig: &consul.Config{HTTPPort: 8500}, - } - - pod := minimal() - pod.Annotations = c.annotations - - ns := testNS - ns.Labels = c.namespaceLabel - iptablesConfig, err := w.iptablesConfigJSON(*pod, ns) - require.NoError(t, err) - - actualConfig := iptables.Config{} - err = json.Unmarshal([]byte(iptablesConfig), &actualConfig) - require.NoError(t, err) - if c.expectConsulDNSConfig { - require.Equal(t, "127.0.0.1", actualConfig.ConsulDNSIP) - require.Equal(t, 8600, actualConfig.ConsulDNSPort) - } else { - require.Empty(t, actualConfig.ConsulDNSIP) - } - }) - } -} diff --git a/control-plane/consul/consul.go b/control-plane/consul/consul.go index 8dea334607..bb46308ff8 100644 --- a/control-plane/consul/consul.go +++ b/control-plane/consul/consul.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consul import ( @@ -8,10 +5,9 @@ import ( "net/http" "time" + "github.com/hashicorp/consul-k8s/control-plane/version" "github.com/hashicorp/consul-server-connection-manager/discovery" capi "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/version" ) //go:generate mockery --name ServerConnectionManager --inpkg @@ -21,7 +17,7 @@ type ServerConnectionManager interface { Stop() } -// NewClient returns a V1 Consul API client. It adds a required User-Agent +// NewClient returns a Consul API client. It adds a required User-Agent // header that describes the version of consul-k8s making the call. func NewClient(config *capi.Config, consulAPITimeout time.Duration) (*capi.Client, error) { if consulAPITimeout <= 0 { @@ -70,7 +66,7 @@ type Config struct { } // todo (ishustava): replace all usages of this one. -// NewClientFromConnMgrState creates a new V1 API client with an IP address from the state +// NewClientFromConnMgrState creates a new API client with an IP address from the state // of the consul-server-connection-manager. func NewClientFromConnMgrState(config *Config, state discovery.State) (*capi.Client, error) { ipAddress := state.Address.IP @@ -81,7 +77,7 @@ func NewClientFromConnMgrState(config *Config, state discovery.State) (*capi.Cli return NewClient(config.APIClientConfig, config.APITimeout) } -// NewClientFromConnMgr creates a new V1 API client by first getting the state of the passed watcher. +// NewClientFromConnMgr creates a new API client by first getting the state of the passed watcher. func NewClientFromConnMgr(config *Config, watcher ServerConnectionManager) (*capi.Client, error) { // Create a new consul client. serverState, err := watcher.State() diff --git a/control-plane/consul/consul_test.go b/control-plane/consul/consul_test.go index 04afe08033..02430b0f48 100644 --- a/control-plane/consul/consul_test.go +++ b/control-plane/consul/consul_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consul import ( diff --git a/control-plane/consul/dataplane_client.go b/control-plane/consul/dataplane_client.go deleted file mode 100644 index 628d353252..0000000000 --- a/control-plane/consul/dataplane_client.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "fmt" - - "github.com/hashicorp/consul/proto-public/pbdataplane" -) - -// NewDataplaneServiceClient creates a pbdataplane.DataplaneServiceClient for gathering proxy bootstrap config. -// It is initialized with a consul-server-connection-manager Watcher to continuously find Consul -// server addresses. -func NewDataplaneServiceClient(watcher ServerConnectionManager) (pbdataplane.DataplaneServiceClient, error) { - - // We recycle the GRPC connection from the discovery client because it - // should have all the necessary dial options, including the resolver that - // continuously updates Consul server addresses. Otherwise, a lot of code from consul-server-connection-manager - // would need to be duplicated - state, err := watcher.State() - if err != nil { - return nil, fmt.Errorf("unable to get connection manager state: %w", err) - } - dpClient := pbdataplane.NewDataplaneServiceClient(state.GRPCConn) - - return dpClient, nil -} diff --git a/control-plane/consul/dataplane_client_test.go b/control-plane/consul/dataplane_client_test.go deleted file mode 100644 index 9463839dda..0000000000 --- a/control-plane/consul/dataplane_client_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "context" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-server-connection-manager/discovery" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbdataplane" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/anypb" -) - -func Test_NewDataplaneServiceClient(t *testing.T) { - - var serverConfig *testutil.TestServerConfig - server, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverConfig = c - }) - require.NoError(t, err) - defer server.Stop() - - server.WaitForLeader(t) - server.WaitForActiveCARoot(t) - - t.Logf("server grpc address on %d", serverConfig.Ports.GRPC) - - // Create discovery configuration - discoverConfig := discovery.Config{ - Addresses: "127.0.0.1", - GRPCPort: serverConfig.Ports.GRPC, - } - - opts := hclog.LoggerOptions{Name: "dataplane-service-client"} - logger := hclog.New(&opts) - - watcher, err := discovery.NewWatcher(context.Background(), discoverConfig, logger) - require.NoError(t, err) - require.NotNil(t, watcher) - - defer watcher.Stop() - go watcher.Run() - - // Create a workload and create a proxyConfiguration - createWorkload(t, watcher, "foo") - pc := createProxyConfiguration(t, watcher, "foo") - - client, err := NewDataplaneServiceClient(watcher) - require.NoError(t, err) - require.NotNil(t, client) - require.NotNil(t, watcher) - - req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ - ProxyId: "foo", - Namespace: "default", - Partition: "default", - } - - res, err := client.GetEnvoyBootstrapParams(context.Background(), req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, "foo", res.GetIdentity()) - require.Equal(t, "default", res.GetNamespace()) - require.Equal(t, "default", res.GetPartition()) - - if diff := cmp.Diff(pc.BootstrapConfig, res.GetBootstrapConfig(), protocmp.Transform()); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - - // NOTE: currently it isn't possible to test that the grpc connection responds to changes in the - // discovery server. The discovery response only includes the IP address of the host, so all servers - // for a local test are de-duplicated as a single entry. -} - -func createWorkload(t *testing.T, watcher ServerConnectionManager, name string) { - - client, err := NewResourceServiceClient(watcher) - require.NoError(t, err) - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "mesh": { - Port: 20000, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: "k8s-node-0-virtual", - Identity: name, - } - - id := &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Partition: "default", - Namespace: "default", - PeerName: "local", - }, - } - - proto, err := anypb.New(workload) - require.NoError(t, err) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: id, - Data: proto, - }, - } - - _, err = client.Write(context.Background(), req) - require.NoError(t, err) - - resourceHasPersisted(t, client, id) -} - -func createProxyConfiguration(t *testing.T, watcher ServerConnectionManager, name string) *pbmesh.ProxyConfiguration { - - client, err := NewResourceServiceClient(watcher) - require.NoError(t, err) - - pc := &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsBindAddr: "127.0.0.2:1234", - ReadyBindAddr: "127.0.0.3:5678", - }, - } - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: "default", - Namespace: "default", - PeerName: "local", - }, - } - - proto, err := anypb.New(pc) - require.NoError(t, err) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: id, - Data: proto, - }, - } - - _, err = client.Write(context.Background(), req) - require.NoError(t, err) - - resourceHasPersisted(t, client, id) - return pc -} - -// resourceHasPersisted checks that a recently written resource exists in the Consul -// state store with a valid version. This must be true before a resource is overwritten -// or deleted. -// TODO: refactor so that there isn't an import cycle when using test.ResourceHasPersisted. -func resourceHasPersisted(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID) { - req := &pbresource.ReadRequest{Id: id} - - require.Eventually(t, func() bool { - res, err := client.Read(context.Background(), req) - if err != nil { - return false - } - - if res.GetResource().GetVersion() == "" { - return false - } - - return true - }, 5*time.Second, - time.Second) -} diff --git a/control-plane/consul/dynamic.go b/control-plane/consul/dynamic.go deleted file mode 100644 index 55db841e58..0000000000 --- a/control-plane/consul/dynamic.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "time" - - capi "github.com/hashicorp/consul/api" -) - -type DynamicClient struct { - ConsulClient *capi.Client - Config *Config - watcher ServerConnectionManager -} - -func NewDynamicClientFromConnMgr(config *Config, watcher ServerConnectionManager) (*DynamicClient, error) { - client, err := NewClientFromConnMgr(config, watcher) - if err != nil { - return nil, err - } - return &DynamicClient{ - ConsulClient: client, - Config: config, - watcher: watcher, - }, nil -} - -func (d *DynamicClient) RefreshClient() error { - var err error - var client *capi.Client - // If the watcher is not set then we did not create the client using NewDynamicClientFromConnMgr and are using it in - // testing - // TODO: Use watcher in testing ;) - if d.watcher == nil { - return nil - } - client, err = NewClientFromConnMgr(d.Config, d.watcher) - if err != nil { - return err - } - d.ConsulClient = client - return nil -} - -func NewDynamicClientWithTimeout(config *capi.Config, consulAPITimeout time.Duration) (*DynamicClient, error) { - client, err := NewClient(config, consulAPITimeout) - if err != nil { - return nil, err - } - return &DynamicClient{ - ConsulClient: client, - Config: &Config{ - APIClientConfig: config, - }, - }, nil -} - -func NewDynamicClient(config *capi.Config) (*DynamicClient, error) { - // defaultTimeout is taken from flags.go.. - defaultTimeout := 5 * time.Second - client, err := NewDynamicClientWithTimeout(config, defaultTimeout) - if err != nil { - return nil, err - } - return client, nil -} diff --git a/control-plane/consul/dynamic_test.go b/control-plane/consul/dynamic_test.go deleted file mode 100644 index 8a260cbdf0..0000000000 --- a/control-plane/consul/dynamic_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "fmt" - "net" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - - "github.com/hashicorp/consul-server-connection-manager/discovery" - capi "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" -) - -func TestRefreshDynamicClient(t *testing.T) { - // Create a server - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "\"leader\"") - })) - defer consulServer.Close() - - serverURL, err := url.Parse(consulServer.URL) - require.NoError(t, err) - serverIP := net.ParseIP(serverURL.Hostname()) - - port, err := strconv.Atoi(serverURL.Port()) - require.NoError(t, err) - - connMgr := &MockServerConnectionManager{} - - // Use a bad IP so that the client call fails - badState := discovery.State{ - Address: discovery.Addr{ - TCPAddr: net.TCPAddr{ - IP: net.ParseIP("126.0.0.1"), - Port: port, - }, - }, - } - - goodState := discovery.State{ - Address: discovery.Addr{ - TCPAddr: net.TCPAddr{ - IP: serverIP, - Port: port, - }, - }, - } - - // testify/mock has a weird behaviour when returning function calls. You cannot update On("State") to return - // something different but instead need to load up the returns. Here we are simulating a bad consul server manager - // state and then a good one - connMgr.On("State").Return(badState, nil).Once() - connMgr.On("State").Return(goodState, nil).Once() - - cfg := capi.DefaultConfig() - client, err := NewDynamicClientFromConnMgr(&Config{APIClientConfig: cfg, HTTPPort: port, GRPCPort: port}, connMgr) - require.NoError(t, err) - - // Make a request to the bad ip of the server - _, err = client.ConsulClient.Status().Leader() - require.Error(t, err) - require.Contains(t, err.Error(), "connection refused") - - // Refresh the client and make a call to the server now that consul-server-connection-manager state is good - err = client.RefreshClient() - require.NoError(t, err) - leader, err := client.ConsulClient.Status().Leader() - require.NoError(t, err) - require.Equal(t, "leader", leader) -} diff --git a/control-plane/consul/resource_client.go b/control-plane/consul/resource_client.go deleted file mode 100644 index 82c24af34f..0000000000 --- a/control-plane/consul/resource_client.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "fmt" - - "github.com/hashicorp/consul/proto-public/pbresource" -) - -// NewResourceServiceClient creates a pbresource.ResourceServiceClient for creating V2 Consul resources. -// It is initialized with a consul-server-connection-manager Watcher to continuously find Consul -// server addresses. -func NewResourceServiceClient(watcher ServerConnectionManager) (pbresource.ResourceServiceClient, error) { - - // We recycle the GRPC connection from the discovery client because it - // should have all the necessary dial options, including the resolver that - // continuously updates Consul server addresses. Otherwise, a lot of code from consul-server-connection-manager - // would need to be duplicated - state, err := watcher.State() - if err != nil { - return nil, fmt.Errorf("unable to get connection manager state: %w", err) - } - resourceClient := pbresource.NewResourceServiceClient(state.GRPCConn) - - return resourceClient, nil -} diff --git a/control-plane/consul/resource_client_test.go b/control-plane/consul/resource_client_test.go deleted file mode 100644 index 0dbecc9798..0000000000 --- a/control-plane/consul/resource_client_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "context" - "testing" - - "github.com/hashicorp/consul-server-connection-manager/discovery" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -func Test_NewResourceServiceClient(t *testing.T) { - - var serverConfig *testutil.TestServerConfig - server, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverConfig = c - }) - require.NoError(t, err) - defer server.Stop() - - server.WaitForLeader(t) - server.WaitForActiveCARoot(t) - - t.Logf("server grpc address on %d", serverConfig.Ports.GRPC) - - // Create discovery configuration - discoverConfig := discovery.Config{ - Addresses: "127.0.0.1", - GRPCPort: serverConfig.Ports.GRPC, - } - - opts := hclog.LoggerOptions{Name: "resource-service-client"} - logger := hclog.New(&opts) - - watcher, err := discovery.NewWatcher(context.Background(), discoverConfig, logger) - require.NoError(t, err) - require.NotNil(t, watcher) - - defer watcher.Stop() - go watcher.Run() - - client, err := NewResourceServiceClient(watcher) - require.NoError(t, err) - require.NotNil(t, client) - require.NotNil(t, watcher) - - req := createWriteRequest(t, "foo") - res, err := client.Write(context.Background(), req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, "foo", res.GetResource().GetId().GetName()) - - // NOTE: currently it isn't possible to test that the grpc connection responds to changes in the - // discovery server. The discovery response only includes the IP address of the host, so all servers - // for a local test are de-duplicated as a single entry. -} - -func createWriteRequest(t *testing.T, name string) *pbresource.WriteRequest { - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "mesh": { - Port: 20000, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: "k8s-node-0-virtual", - Identity: name, - } - - proto, err := anypb.New(workload) - require.NoError(t, err) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: proto, - }, - } - return req -} diff --git a/control-plane/controllers/configentries/configentry_controller.go b/control-plane/controller/configentry_controller.go similarity index 78% rename from control-plane/controllers/configentries/configentry_controller.go rename to control-plane/controller/configentry_controller.go index 9e9459308f..7782d5e9a9 100644 --- a/control-plane/controllers/configentries/configentry_controller.go +++ b/control-plane/controller/configentry_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -11,6 +8,9 @@ import ( "time" "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" capi "github.com/hashicorp/consul/api" "golang.org/x/time/rate" corev1 "k8s.io/api/core/v1" @@ -22,30 +22,20 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( FinalizerName = "finalizers.consul.hashicorp.com" ConsulAgentError = "ConsulAgentError" - ConsulPatchError = "ConsulPatchError" ExternallyManagedConfigError = "ExternallyManagedConfigError" MigrationFailedError = "MigrationFailedError" ) -// Controller is implemented by CRD-specific configentries. It is used by -// ConfigEntryController to abstract CRD-specific configentries. +// Controller is implemented by CRD-specific controllers. It is used by +// ConfigEntryController to abstract CRD-specific controllers. type Controller interface { - // AddFinalizersPatch creates a patch with the original finalizers with new ones appended to the end. - AddFinalizersPatch(obj client.Object, finalizers ...string) *FinalizerPatch - // RemoveFinalizersPatch creates a patch to remove a set of finalizers, while preserving the order. - RemoveFinalizersPatch(obj client.Object, finalizers ...string) *FinalizerPatch - // Patch patches the object. This should only ever be used for updating the metadata of an object, and not object - // spec or status. Updating the spec could have unintended consequences such as defaulting zero values. - Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error + // Update updates the state of the whole object. + Update(context.Context, client.Object, ...client.UpdateOption) error // UpdateStatus updates the state of just the object's status. UpdateStatus(context.Context, client.Object, ...client.SubResourceUpdateOption) error // Get retrieves an obj for the given object key from the Kubernetes Cluster. @@ -131,13 +121,7 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont // then let's add the finalizer and update the object. This is equivalent // registering our finalizer. if !containsString(configEntry.GetFinalizers(), FinalizerName) { - addPatch := crdCtrl.AddFinalizersPatch(configEntry, FinalizerName) - err := crdCtrl.Patch(ctx, configEntry, addPatch) - if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, configEntry, ConsulPatchError, - fmt.Errorf("adding finalizer: %w", err)) - } - + configEntry.AddFinalizer(FinalizerName) if err := r.syncUnknown(ctx, crdCtrl, configEntry); err != nil { return ctrl.Result{}, err } @@ -171,8 +155,8 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont } } // remove our finalizer from the list and update it. - removePatch := crdCtrl.RemoveFinalizersPatch(configEntry, FinalizerName) - if err := crdCtrl.Patch(ctx, configEntry, removePatch); err != nil { + configEntry.RemoveFinalizer(FinalizerName) + if err := crdCtrl.Update(ctx, configEntry); err != nil { return ctrl.Result{}, err } logger.Info("finalizer removed") @@ -212,7 +196,6 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont return r.syncFailed(ctx, logger, crdCtrl, configEntry, ConsulAgentError, fmt.Errorf("writing config entry to consul: %w", err)) } - logger.Info("config entry created", "request-time", writeMeta.RequestTime) return r.syncSuccessful(ctx, crdCtrl, configEntry) } @@ -281,15 +264,6 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont return r.syncSuccessful(ctx, crdCtrl, configEntry) } - // For resolvers and splitters, we need to set the ClusterIP of the matching service to Consul so that transparent - // proxy works correctly. Do not fail the reconcile if assigning the virtual IP returns an error. - if needsVirtualIPAssignment(r.DatacenterName, configEntry) { - err = assignServiceVirtualIP(ctx, logger, consulClient, crdCtrl, req.NamespacedName, configEntry, r.DatacenterName) - if err != nil { - logger.Error(err, "failed assigning service virtual ip") - } - } - return ctrl.Result{}, nil } @@ -367,9 +341,7 @@ func (r *ConfigEntryController) syncSuccessful(ctx context.Context, updater Cont func (r *ConfigEntryController) syncUnknown(ctx context.Context, updater Controller, configEntry common.ConfigEntryResource) error { configEntry.SetSyncedCondition(corev1.ConditionUnknown, "", "") - timeNow := metav1.NewTime(time.Now()) - configEntry.SetLastSyncedTime(&timeNow) - return updater.UpdateStatus(ctx, configEntry) + return updater.Update(ctx, configEntry) } func (r *ConfigEntryController) syncUnknownWithError(ctx context.Context, @@ -406,75 +378,6 @@ func (r *ConfigEntryController) nonMatchingMigrationError(kubeEntry common.Confi return fmt.Errorf("migration failed: Kubernetes resource does not match existing Consul config entry: consul=%s, kube=%s", consulJSON, kubeJSON) } -// needsVirtualIPAssignment checks to see if a configEntry type needs to be assigned a virtual IP. -func needsVirtualIPAssignment(datacenterName string, configEntry common.ConfigEntryResource) bool { - switch configEntry.KubeKind() { - case common.ServiceResolver: - return true - case common.ServiceRouter: - return true - case common.ServiceSplitter: - return true - case common.ServiceDefaults: - return true - case common.ServiceIntentions: - entry := configEntry.ToConsul(datacenterName) - intention, ok := entry.(*capi.ServiceIntentionsConfigEntry) - if !ok { - return false - } - // We should not persist virtual ips if the destination is a wildcard - // in any form, since that would target multiple services. - return !strings.Contains(intention.Name, "*") && - !strings.Contains(intention.Namespace, "*") && - !strings.Contains(intention.Partition, "*") - } - return false -} - -// assignServiceVirtualIPs manually sends the ClusterIP for a matching service for ServiceRouter or ServiceSplitter -// CRDs to Consul so that it can be added to the virtual IP table. The assignment is skipped if the matching service -// does not exist or if an older version of Consul is being used. Endpoints Controller, on service registration, also -// manually sends a ClusterIP when a service is created. This increases the chance of a real IP ending up in the -// discovery chain. -func assignServiceVirtualIP(ctx context.Context, logger logr.Logger, consulClient *capi.Client, crdCtrl Controller, namespacedName types.NamespacedName, configEntry common.ConfigEntryResource, datacenter string) error { - service := corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: configEntry.KubernetesName(), - Namespace: namespacedName.Namespace, - }, - } - if err := crdCtrl.Get(ctx, namespacedName, &service); err != nil { - // It is non-fatal if the service does not exist. The ClusterIP will get added when the service is registered in - // the endpoints controller - if k8serr.IsNotFound(err) { - return nil - } - // Something is really wrong with the service - return err - } - - consulType := configEntry.ToConsul(datacenter) - wo := &capi.WriteOptions{ - Namespace: consulType.GetNamespace(), - Partition: consulType.GetPartition(), - } - - logger.Info("adding manual ip to virtual ip table in Consul", "name", service.Name) - _, _, err := consulClient.Internal().AssignServiceVirtualIP(ctx, consulType.GetName(), []string{service.Spec.ClusterIP}, wo) - if err != nil { - // Maintain backwards compatibility with older versions of Consul that do not support the manual VIP improvements. With the older version, the mesh - // will still work. - if isNotFoundErr(err) { - logger.Error(err, "failed to add ip to virtual ip table. Please upgrade Consul to version 1.16 or higher", "name", service.Name) - return nil - } else { - return err - } - } - return nil -} - func isNotFoundErr(err error) bool { return err != nil && strings.Contains(err.Error(), "404") } diff --git a/control-plane/controller/configentry_controller_ent_test.go b/control-plane/controller/configentry_controller_ent_test.go new file mode 100644 index 0000000000..aa59ddaceb --- /dev/null +++ b/control-plane/controller/configentry_controller_ent_test.go @@ -0,0 +1,759 @@ +//go:build enterprise + +package controller_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/go-logr/logr" + logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/controller" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + capi "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// NOTE: We're not testing each controller type here because that's done in +// the OSS tests and it would result in too many permutations. Instead +// we're only testing with the ServiceDefaults and ProxyDefaults controller which will exercise +// all the namespaces code for config entries that are namespaced and those that +// exist in the global namespace. + +func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T) { + tt.Parallel() + + cases := map[string]struct { + Mirror bool + MirrorPrefix string + SourceKubeNS string + DestConsulNS string + ExpConsulNS string + }{ + "SourceKubeNS=default, DestConsulNS=default": { + SourceKubeNS: "default", + DestConsulNS: "default", + ExpConsulNS: "default", + }, + "SourceKubeNS=kube, DestConsulNS=default": { + SourceKubeNS: "kube", + DestConsulNS: "default", + ExpConsulNS: "default", + }, + "SourceKubeNS=default, DestConsulNS=other": { + SourceKubeNS: "default", + DestConsulNS: "other", + ExpConsulNS: "other", + }, + "SourceKubeNS=kube, DestConsulNS=other": { + SourceKubeNS: "kube", + DestConsulNS: "other", + ExpConsulNS: "other", + }, + "SourceKubeNS=default, Mirror=true": { + SourceKubeNS: "default", + Mirror: true, + ExpConsulNS: "default", + }, + "SourceKubeNS=kube, Mirror=true": { + SourceKubeNS: "kube", + Mirror: true, + ExpConsulNS: "kube", + }, + "SourceKubeNS=default, Mirror=true, Prefix=prefix": { + SourceKubeNS: "default", + Mirror: true, + MirrorPrefix: "prefix-", + ExpConsulNS: "prefix-default", + }, + } + + for name, c := range cases { + configEntryKinds := map[string]struct { + ConsulKind string + ConsulNamespace string + KubeResource common.ConfigEntryResource + GetController func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler + AssertValidConfig func(entry capi.ConfigEntry) bool + }{ + "namespaced": { + ConsulKind: capi.ServiceDefaults, + KubeResource: &v1alpha1.ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: c.SourceKubeNS, + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + }, + GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ServiceDefaultsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + AssertValidConfig: func(cfg capi.ConfigEntry) bool { + configEntry, ok := cfg.(*capi.ServiceConfigEntry) + if !ok { + return false + } + return configEntry.Protocol == "http" + }, + ConsulNamespace: c.ExpConsulNS, + }, + "global": { + ConsulKind: capi.ProxyDefaults, + KubeResource: &v1alpha1.ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.Global, + Namespace: c.SourceKubeNS, + }, + Spec: v1alpha1.ProxyDefaultsSpec{ + MeshGateway: v1alpha1.MeshGateway{ + Mode: "remote", + }, + }, + }, + GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ProxyDefaultsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + AssertValidConfig: func(cfg capi.ConfigEntry) bool { + configEntry, ok := cfg.(*capi.ProxyConfigEntry) + if !ok { + return false + } + return configEntry.MeshGateway.Mode == capi.MeshGatewayModeRemote + }, + ConsulNamespace: common.DefaultConsulNamespace, + }, + "intentions": { + ConsulKind: capi.ServiceIntentions, + KubeResource: &v1alpha1.ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: c.SourceKubeNS, + }, + Spec: v1alpha1.ServiceIntentionsSpec{ + Destination: v1alpha1.IntentionDestination{ + Name: "test", + Namespace: c.ExpConsulNS, + }, + Sources: v1alpha1.SourceIntentions{ + &v1alpha1.SourceIntention{ + Name: "baz", + Namespace: "bar", + Action: "allow", + }, + }, + }, + }, + GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ServiceIntentionsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + AssertValidConfig: func(cfg capi.ConfigEntry) bool { + configEntry, ok := cfg.(*capi.ServiceIntentionsConfigEntry) + if !ok { + return false + } + return configEntry.Sources[0].Action == capi.IntentionActionAllow + }, + ConsulNamespace: c.ExpConsulNS, + }, + } + + for kind, in := range configEntryKinds { + tt.Run(fmt.Sprintf("%s : %s", name, kind), func(t *testing.T) { + req := require.New(t) + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, in.KubeResource) + ctx := context.Background() + + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + testClient.TestServer.WaitForServiceIntentions(t) + consulClient := testClient.APIClient + + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(in.KubeResource).Build() + + r := in.GetController( + fakeClient, + logrtest.New(t), + s, + &controller.ConfigEntryController{ + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + EnableConsulNamespaces: true, + EnableNSMirroring: c.Mirror, + NSMirroringPrefix: c.MirrorPrefix, + ConsulDestinationNamespace: c.DestConsulNS, + }, + ) + + resp, err := r.Reconcile(ctx, ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: c.SourceKubeNS, + Name: in.KubeResource.KubernetesName(), + }, + }) + req.NoError(err) + req.False(resp.Requeue) + + cfg, _, err := consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.ConsulName(), &capi.QueryOptions{ + Namespace: in.ConsulNamespace, + }) + req.NoError(err) + + result := in.AssertValidConfig(cfg) + req.True(result) + + // Check that the status is "synced". + err = fakeClient.Get(ctx, types.NamespacedName{ + Namespace: c.SourceKubeNS, + Name: in.KubeResource.KubernetesName(), + }, in.KubeResource) + req.NoError(err) + conditionSynced := in.KubeResource.SyncedConditionStatus() + req.Equal(conditionSynced, corev1.ConditionTrue) + }) + } + } +} + +func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T) { + tt.Parallel() + + cases := map[string]struct { + Mirror bool + MirrorPrefix string + SourceKubeNS string + DestConsulNS string + ExpConsulNS string + }{ + "SourceKubeNS=default, DestConsulNS=default": { + SourceKubeNS: "default", + DestConsulNS: "default", + ExpConsulNS: "default", + }, + "SourceKubeNS=kube, DestConsulNS=default": { + SourceKubeNS: "kube", + DestConsulNS: "default", + ExpConsulNS: "default", + }, + "SourceKubeNS=default, DestConsulNS=other": { + SourceKubeNS: "default", + DestConsulNS: "other", + ExpConsulNS: "other", + }, + "SourceKubeNS=kube, DestConsulNS=other": { + SourceKubeNS: "kube", + DestConsulNS: "other", + ExpConsulNS: "other", + }, + "SourceKubeNS=default, Mirror=true": { + SourceKubeNS: "default", + Mirror: true, + ExpConsulNS: "default", + }, + "SourceKubeNS=kube, Mirror=true": { + SourceKubeNS: "kube", + Mirror: true, + ExpConsulNS: "kube", + }, + "SourceKubeNS=default, Mirror=true, Prefix=prefix": { + SourceKubeNS: "default", + Mirror: true, + MirrorPrefix: "prefix-", + ExpConsulNS: "prefix-default", + }, + } + + for name, c := range cases { + configEntryKinds := map[string]struct { + ConsulKind string + ConsulNamespace string + KubeResource common.ConfigEntryResource + GetControllerFunc func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler + AssertValidConfigFunc func(entry capi.ConfigEntry) bool + WriteConfigEntryFunc func(consulClient *capi.Client, namespace string) error + UpdateResourceFunc func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error + }{ + "namespaced": { + ConsulKind: capi.ServiceDefaults, + KubeResource: &v1alpha1.ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: c.SourceKubeNS, + Finalizers: []string{controller.FinalizerName}, + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + }, + ConsulNamespace: c.ExpConsulNS, + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ServiceDefaultsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { + _, _, err := consulClient.ConfigEntries().Set(&capi.ServiceConfigEntry{ + Kind: capi.ServiceDefaults, + Name: "foo", + Protocol: "http", + }, &capi.WriteOptions{Namespace: namespace}) + return err + }, + UpdateResourceFunc: func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error { + svcDefault := in.(*v1alpha1.ServiceDefaults) + svcDefault.Spec.Protocol = "tcp" + return client.Update(ctx, svcDefault) + }, + AssertValidConfigFunc: func(cfg capi.ConfigEntry) bool { + configEntry, ok := cfg.(*capi.ServiceConfigEntry) + if !ok { + return false + } + return configEntry.Protocol == "tcp" + }, + }, + "global": { + ConsulKind: capi.ProxyDefaults, + KubeResource: &v1alpha1.ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.Global, + Namespace: c.SourceKubeNS, + Finalizers: []string{controller.FinalizerName}, + }, + Spec: v1alpha1.ProxyDefaultsSpec{ + MeshGateway: v1alpha1.MeshGateway{ + Mode: "remote", + }, + }, + }, + ConsulNamespace: common.DefaultConsulNamespace, + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ProxyDefaultsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { + _, _, err := consulClient.ConfigEntries().Set(&capi.ProxyConfigEntry{ + Kind: capi.ProxyDefaults, + Name: common.Global, + MeshGateway: capi.MeshGatewayConfig{ + Mode: capi.MeshGatewayModeRemote, + }, + }, &capi.WriteOptions{Namespace: namespace}) + return err + }, + UpdateResourceFunc: func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error { + proxyDefaults := in.(*v1alpha1.ProxyDefaults) + proxyDefaults.Spec.MeshGateway.Mode = "local" + return client.Update(ctx, proxyDefaults) + }, + AssertValidConfigFunc: func(cfg capi.ConfigEntry) bool { + configEntry, ok := cfg.(*capi.ProxyConfigEntry) + if !ok { + return false + } + return configEntry.MeshGateway.Mode == capi.MeshGatewayModeLocal + }, + }, + "intentions": { + ConsulKind: capi.ServiceIntentions, + KubeResource: &v1alpha1.ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: c.SourceKubeNS, + Finalizers: []string{controller.FinalizerName}, + }, + Spec: v1alpha1.ServiceIntentionsSpec{ + Destination: v1alpha1.IntentionDestination{ + Name: "foo", + Namespace: c.ExpConsulNS, + }, + Sources: v1alpha1.SourceIntentions{ + &v1alpha1.SourceIntention{ + Name: "bar", + Namespace: "baz", + Action: "deny", + }, + }, + }, + }, + ConsulNamespace: c.ExpConsulNS, + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ServiceIntentionsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { + _, _, err := consulClient.ConfigEntries().Set(&capi.ServiceIntentionsConfigEntry{ + Kind: capi.ServiceIntentions, + Name: "foo", + Sources: []*capi.SourceIntention{ + { + Name: "bar", + Namespace: "baz", + Action: capi.IntentionActionDeny, + }, + }, + }, &capi.WriteOptions{Namespace: namespace}) + return err + }, + UpdateResourceFunc: func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error { + svcIntention := in.(*v1alpha1.ServiceIntentions) + svcIntention.Spec.Sources[0].Action = "allow" + return client.Update(ctx, svcIntention) + }, + AssertValidConfigFunc: func(cfg capi.ConfigEntry) bool { + configEntry, ok := cfg.(*capi.ServiceIntentionsConfigEntry) + if !ok { + return false + } + return configEntry.Sources[0].Action == capi.IntentionActionAllow + }, + }, + } + for kind, in := range configEntryKinds { + tt.Run(fmt.Sprintf("%s : %s", name, kind), func(t *testing.T) { + req := require.New(t) + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, in.KubeResource) + ctx := context.Background() + + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + testClient.TestServer.WaitForServiceIntentions(t) + consulClient := testClient.APIClient + + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(in.KubeResource).Build() + + r := in.GetControllerFunc( + fakeClient, + logrtest.New(t), + s, + &controller.ConfigEntryController{ + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + EnableConsulNamespaces: true, + EnableNSMirroring: c.Mirror, + NSMirroringPrefix: c.MirrorPrefix, + ConsulDestinationNamespace: c.DestConsulNS, + }, + ) + + // We haven't run reconcile yet so ensure it's created in Consul. + { + if in.ConsulNamespace != "default" { + _, _, err := consulClient.Namespaces().Create(&capi.Namespace{ + Name: in.ConsulNamespace, + }, nil) + req.NoError(err) + } + + err := in.WriteConfigEntryFunc(consulClient, in.ConsulNamespace) + req.NoError(err) + } + + // Now update it. + { + // First get it so we have the latest revision number. + err := fakeClient.Get(ctx, types.NamespacedName{ + Namespace: c.SourceKubeNS, + Name: in.KubeResource.KubernetesName(), + }, in.KubeResource) + req.NoError(err) + + // Update the resource. + err = in.UpdateResourceFunc(fakeClient, ctx, in.KubeResource) + req.NoError(err) + + resp, err := r.Reconcile(ctx, ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: c.SourceKubeNS, + Name: in.KubeResource.KubernetesName(), + }, + }) + req.NoError(err) + req.False(resp.Requeue) + + cfg, _, err := consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.ConsulName(), &capi.QueryOptions{ + Namespace: in.ConsulNamespace, + }) + req.NoError(err) + req.True(in.AssertValidConfigFunc(cfg)) + } + }) + } + } +} + +func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T) { + tt.Parallel() + + cases := map[string]struct { + Mirror bool + MirrorPrefix string + SourceKubeNS string + DestConsulNS string + ExpConsulNS string + }{ + "SourceKubeNS=default, DestConsulNS=default": { + SourceKubeNS: "default", + DestConsulNS: "default", + ExpConsulNS: "default", + }, + "SourceKubeNS=kube, DestConsulNS=default": { + SourceKubeNS: "kube", + DestConsulNS: "default", + ExpConsulNS: "default", + }, + "SourceKubeNS=default, DestConsulNS=other": { + SourceKubeNS: "default", + DestConsulNS: "other", + ExpConsulNS: "other", + }, + "SourceKubeNS=kube, DestConsulNS=other": { + SourceKubeNS: "kube", + DestConsulNS: "other", + ExpConsulNS: "other", + }, + "SourceKubeNS=default, Mirror=true": { + SourceKubeNS: "default", + Mirror: true, + ExpConsulNS: "default", + }, + "SourceKubeNS=kube, Mirror=true": { + SourceKubeNS: "kube", + Mirror: true, + ExpConsulNS: "kube", + }, + "SourceKubeNS=default, Mirror=true, Prefix=prefix": { + SourceKubeNS: "default", + Mirror: true, + MirrorPrefix: "prefix-", + ExpConsulNS: "prefix-default", + }, + } + + for name, c := range cases { + configEntryKinds := map[string]struct { + ConsulKind string + ConsulNamespace string + KubeResource common.ConfigEntryResource + GetControllerFunc func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler + WriteConfigEntryFunc func(consulClient *capi.Client, namespace string) error + }{ + "namespaced": { + ConsulKind: capi.ServiceDefaults, + // Create it with the deletion timestamp set to mimic that it's already + // been marked for deletion. + KubeResource: &v1alpha1.ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: c.SourceKubeNS, + Finalizers: []string{controller.FinalizerName}, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + }, + ConsulNamespace: c.ExpConsulNS, + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ServiceDefaultsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { + _, _, err := consulClient.ConfigEntries().Set(&capi.ServiceConfigEntry{ + Kind: capi.ServiceDefaults, + Name: "foo", + Protocol: "http", + }, &capi.WriteOptions{Namespace: namespace}) + return err + }, + }, + "global": { + ConsulKind: capi.ProxyDefaults, + // Create it with the deletion timestamp set to mimic that it's already + // been marked for deletion. + KubeResource: &v1alpha1.ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.Global, + Namespace: c.SourceKubeNS, + Finalizers: []string{controller.FinalizerName}, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + }, + Spec: v1alpha1.ProxyDefaultsSpec{ + MeshGateway: v1alpha1.MeshGateway{ + Mode: "remote", + }, + }, + }, + ConsulNamespace: common.DefaultConsulNamespace, + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ProxyDefaultsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { + _, _, err := consulClient.ConfigEntries().Set(&capi.ProxyConfigEntry{ + Kind: capi.ProxyDefaults, + Name: common.Global, + MeshGateway: capi.MeshGatewayConfig{ + Mode: capi.MeshGatewayModeRemote, + }, + }, &capi.WriteOptions{Namespace: namespace}) + return err + }, + }, + "intentions": { + ConsulKind: capi.ServiceIntentions, + // Create it with the deletion timestamp set to mimic that it's already + // been marked for deletion. + KubeResource: &v1alpha1.ServiceIntentions{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: c.SourceKubeNS, + Finalizers: []string{controller.FinalizerName}, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + }, + Spec: v1alpha1.ServiceIntentionsSpec{ + Destination: v1alpha1.IntentionDestination{ + Name: "test", + Namespace: c.ExpConsulNS, + }, + Sources: v1alpha1.SourceIntentions{ + &v1alpha1.SourceIntention{ + Name: "bar", + Namespace: "baz", + Action: "deny", + }, + }, + }, + }, + ConsulNamespace: c.ExpConsulNS, + GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ServiceIntentionsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { + _, _, err := consulClient.ConfigEntries().Set(&capi.ServiceIntentionsConfigEntry{ + Kind: capi.ServiceIntentions, + Name: "test", + Sources: []*capi.SourceIntention{ + { + Name: "bar", + Namespace: "baz", + Action: capi.IntentionActionDeny, + }, + }, + }, &capi.WriteOptions{Namespace: namespace}) + return err + }, + }, + } + for kind, in := range configEntryKinds { + tt.Run(fmt.Sprintf("%s : %s", name, kind), func(t *testing.T) { + req := require.New(t) + + s := runtime.NewScheme() + s.AddKnownTypes(v1alpha1.GroupVersion, in.KubeResource) + + testClient := test.TestServerWithMockConnMgrWatcher(t, nil) + testClient.TestServer.WaitForServiceIntentions(t) + consulClient := testClient.APIClient + + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(in.KubeResource).Build() + + r := in.GetControllerFunc( + fakeClient, + logrtest.New(t), + s, + &controller.ConfigEntryController{ + ConsulClientConfig: testClient.Cfg, + ConsulServerConnMgr: testClient.Watcher, + EnableConsulNamespaces: true, + EnableNSMirroring: c.Mirror, + NSMirroringPrefix: c.MirrorPrefix, + ConsulDestinationNamespace: c.DestConsulNS, + }, + ) + + // We haven't run reconcile yet so ensure it's created in Consul. + { + if in.ConsulNamespace != "default" { + _, _, err := consulClient.Namespaces().Create(&capi.Namespace{ + Name: in.ConsulNamespace, + }, nil) + req.NoError(err) + } + + err := in.WriteConfigEntryFunc(consulClient, in.ConsulNamespace) + req.NoError(err) + } + + // Now run reconcile. It's marked for deletion so this should delete it. + { + resp, err := r.Reconcile(context.Background(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: c.SourceKubeNS, + Name: in.KubeResource.KubernetesName(), + }, + }) + req.NoError(err) + req.False(resp.Requeue) + + _, _, err = consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.ConsulName(), &capi.QueryOptions{ + Namespace: in.ConsulNamespace, + }) + req.EqualError(err, fmt.Sprintf(`Unexpected response code: 404 (Config entry not found for "%s" / "%s")`, in.ConsulKind, in.KubeResource.ConsulName())) + } + }) + } + } +} diff --git a/control-plane/controllers/configentries/configentry_controller_test.go b/control-plane/controller/configentry_controller_test.go similarity index 83% rename from control-plane/controllers/configentries/configentry_controller_test.go rename to control-plane/controller/configentry_controller_test.go index faa153c323..3b8ba9e561 100644 --- a/control-plane/controllers/configentries/configentry_controller_test.go +++ b/control-plane/controller/configentry_controller_test.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -13,21 +10,19 @@ import ( logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const datacenterName = "datacenter" @@ -433,112 +428,6 @@ func TestConfigEntryControllers_createsConfigEntry(t *testing.T) { require.Equal(t, "sni", resource.Services[0].SNI) }, }, - { - kubeKind: "JWTProvider", - consulKind: capi.JWTProvider, - configEntryResource: &v1alpha1.JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwt-provider", - Namespace: kubeNS, - }, - Spec: v1alpha1.JWTProviderSpec{ - JSONWebKeySet: &v1alpha1.JSONWebKeySet{ - Local: &v1alpha1.LocalJWKS{ - Filename: "jwks.txt", - }, - }, - Issuer: "test-issuer", - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &JWTProviderController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - jwt, ok := consulEntry.(*capi.JWTProviderConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, capi.JWTProvider, jwt.Kind) - require.Equal(t, "test-jwt-provider", jwt.Name) - require.Equal(t, - &capi.JSONWebKeySet{ - Local: &capi.LocalJWKS{ - Filename: "jwks.txt", - }, - }, - jwt.JSONWebKeySet, - ) - require.Equal(t, "test-issuer", jwt.Issuer) - }, - }, - { - kubeKind: "JWTProvider", - consulKind: capi.JWTProvider, - configEntryResource: &v1alpha1.JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwt-provider", - Namespace: kubeNS, - }, - Spec: v1alpha1.JWTProviderSpec{ - JSONWebKeySet: &v1alpha1.JSONWebKeySet{ - Remote: &v1alpha1.RemoteJWKS{ - URI: "https://jwks.example.com", - JWKSCluster: &v1alpha1.JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &v1alpha1.JWKSTLSCertificate{ - CaCertificateProviderInstance: &v1alpha1.JWKSTLSCertProviderInstance{ - InstanceName: "InstanceName", - CertificateName: "ROOTCA", - }, - }, - }, - }, - }, - Issuer: "test-issuer", - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &JWTProviderController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - jwt, ok := consulEntry.(*capi.JWTProviderConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, capi.JWTProvider, jwt.Kind) - require.Equal(t, "test-jwt-provider", jwt.Name) - require.Equal(t, - &capi.JSONWebKeySet{ - Remote: &capi.RemoteJWKS{ - URI: "https://jwks.example.com", - JWKSCluster: &capi.JWKSCluster{ - DiscoveryType: "STRICT_DNS", - TLSCertificates: &capi.JWKSTLSCertificate{ - CaCertificateProviderInstance: &capi.JWKSTLSCertProviderInstance{ - InstanceName: "InstanceName", - CertificateName: "ROOTCA", - }, - }, - }, - }, - }, - jwt.JSONWebKeySet, - ) - require.Equal(t, "test-issuer", jwt.Issuer) - }, - }, } for _, c := range cases { @@ -1016,56 +905,6 @@ func TestConfigEntryControllers_updatesConfigEntry(t *testing.T) { require.Equal(t, "new-sni", resource.Services[0].SNI) }, }, - { - kubeKind: "JWTProvider", - consulKind: capi.JWTProvider, - configEntryResource: &v1alpha1.JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwt-provider", - Namespace: kubeNS, - }, - Spec: v1alpha1.JWTProviderSpec{ - JSONWebKeySet: &v1alpha1.JSONWebKeySet{ - Local: &v1alpha1.LocalJWKS{ - Filename: "jwks.txt", - }, - }, - Issuer: "test-issuer", - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &JWTProviderController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - updateF: func(resource common.ConfigEntryResource) { - jwt := resource.(*v1alpha1.JWTProvider) - jwt.Spec.Issuer = "test-updated-issuer" - jwt.Spec.Audiences = []string{"aud1"} - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - jwt, ok := consulEntry.(*capi.JWTProviderConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, capi.JWTProvider, jwt.Kind) - require.Equal(t, "test-jwt-provider", jwt.Name) - require.Equal(t, - &capi.JSONWebKeySet{ - Local: &capi.LocalJWKS{ - Filename: "jwks.txt", - }, - }, - jwt.JSONWebKeySet, - ) - require.Equal(t, "test-updated-issuer", jwt.Issuer) - require.Equal(t, []string{"aud1"}, jwt.Audiences) - }, - }, } for _, c := range cases { @@ -1467,37 +1306,6 @@ func TestConfigEntryControllers_deletesConfigEntry(t *testing.T) { } }, }, - { - kubeKind: "JWTProvider", - consulKind: capi.JWTProvider, - configEntryResourceWithDeletion: &v1alpha1.JWTProvider{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.Global, - Namespace: kubeNS, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{FinalizerName}, - }, - Spec: v1alpha1.JWTProviderSpec{ - JSONWebKeySet: &v1alpha1.JSONWebKeySet{ - Local: &v1alpha1.LocalJWKS{ - Filename: "jwks.txt", - }, - }, - Issuer: "test-issuer", - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &JWTProviderController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - }, } for _, c := range cases { @@ -2080,235 +1888,3 @@ func TestConfigEntryController_Migration(t *testing.T) { }) } } - -func TestConfigEntryControllers_assignServiceVirtualIP(t *testing.T) { - t.Parallel() - kubeNS := "default" - - cases := []struct { - name string - kubeKind string - consulKind string - consulPrereqs []capi.ConfigEntry - configEntryResource common.ConfigEntryResource - service corev1.Service - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) Controller - expectErr bool - }{ - { - name: "ServiceResolver no error and vip should be assigned", - kubeKind: "ServiceResolver", - consulKind: capi.ServiceRouter, - configEntryResource: &v1alpha1.ServiceRouter{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: v1alpha1.ServiceRouterSpec{ - Routes: []v1alpha1.ServiceRoute{ - { - Match: &v1alpha1.ServiceRouteMatch{ - HTTP: &v1alpha1.ServiceRouteHTTPMatch{ - PathPrefix: "/admin", - }, - }, - }, - }, - }, - }, - service: corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []corev1.ServicePort{ - { - Port: 8081, - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) Controller { - return &ServiceRouterController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - expectErr: false, - }, - { - name: "ServiceRouter no error and vip should be assigned", - kubeKind: "ServiceRouter", - consulKind: capi.ServiceRouter, - consulPrereqs: []capi.ConfigEntry{ - &capi.ServiceConfigEntry{ - Kind: capi.ServiceDefaults, - Name: "bar", - Protocol: "http", - }, - }, - configEntryResource: &v1alpha1.ServiceRouter{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: kubeNS, - }, - Spec: v1alpha1.ServiceRouterSpec{ - Routes: []v1alpha1.ServiceRoute{ - { - Match: &v1alpha1.ServiceRouteMatch{ - HTTP: &v1alpha1.ServiceRouteHTTPMatch{ - PathPrefix: "/admin", - }, - }, - }, - }, - }, - }, - service: corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: kubeNS, - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "10.0.0.2", - Ports: []corev1.ServicePort{ - { - Port: 8081, - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) Controller { - return &ServiceRouterController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - expectErr: false, - }, - { - name: "ServiceRouter should fail because service does not have a valid IP address", - kubeKind: "ServiceRouter", - consulKind: capi.ServiceRouter, - consulPrereqs: []capi.ConfigEntry{ - &capi.ServiceConfigEntry{ - Kind: capi.ServiceDefaults, - Name: "bar", - Protocol: "http", - }, - }, - configEntryResource: &v1alpha1.ServiceRouter{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: kubeNS, - }, - Spec: v1alpha1.ServiceRouterSpec{ - Routes: []v1alpha1.ServiceRoute{ - { - Match: &v1alpha1.ServiceRouteMatch{ - HTTP: &v1alpha1.ServiceRouteHTTPMatch{ - PathPrefix: "/admin", - }, - }, - }, - }, - }, - }, - service: corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: kubeNS, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) Controller { - return &ServiceRouterController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - expectErr: true, - }, - { - name: "ServiceSplitter no error because a matching service does not exist", - kubeKind: "ServiceSplitter", - consulKind: capi.ServiceSplitter, - consulPrereqs: []capi.ConfigEntry{ - &capi.ServiceConfigEntry{ - Kind: capi.ServiceDefaults, - Name: "foo", - Protocol: "http", - }, - }, - configEntryResource: &v1alpha1.ServiceSplitter{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: v1alpha1.ServiceSplitterSpec{ - Splits: []v1alpha1.ServiceSplit{ - { - Weight: 100, - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) Controller { - return &ServiceSplitterController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - expectErr: false, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResource) - s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Service{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(&c.service, c.configEntryResource).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - testClient.TestServer.WaitForLeader(t) - consulClient := testClient.APIClient - - ctrl := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) - namespacedName := types.NamespacedName{ - Namespace: kubeNS, - Name: c.configEntryResource.KubernetesName(), - } - - err := assignServiceVirtualIP(ctx, ctrl.Logger(namespacedName), consulClient, ctrl, namespacedName, c.configEntryResource, "dc1") - if err != nil { - require.True(t, c.expectErr) - } else { - require.False(t, c.expectErr) - } - }) - } -} diff --git a/control-plane/controllers/configentries/exportedservices_controller.go b/control-plane/controller/exportedservices_controller.go similarity index 89% rename from control-plane/controllers/configentries/exportedservices_controller.go rename to control-plane/controller/exportedservices_controller.go index 474431832a..9466360207 100644 --- a/control-plane/controllers/configentries/exportedservices_controller.go +++ b/control-plane/controller/exportedservices_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*ExportedServicesController)(nil) - // ExportedServicesController reconciles a ExportedServices object. type ExportedServicesController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/exportedservices_controller_ent_test.go b/control-plane/controller/exportedservices_controller_ent_test.go similarity index 93% rename from control-plane/controllers/configentries/exportedservices_controller_ent_test.go rename to control-plane/controller/exportedservices_controller_ent_test.go index 61ec75288f..29b4e006f1 100644 --- a/control-plane/controllers/configentries/exportedservices_controller_ent_test.go +++ b/control-plane/controller/exportedservices_controller_ent_test.go @@ -1,9 +1,6 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise -package configentries_test +package controller_test import ( "context" @@ -11,7 +8,11 @@ import ( "testing" "time" - logrtest "github.com/go-logr/logr/testing" + logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/controller" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -20,11 +21,6 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/controllers/configentries" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // This tests explicitly tests ExportedServicesController instead of using the existing @@ -104,11 +100,11 @@ func TestExportedServicesController_createsExportedServices(tt *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &configentries.ExportedServicesController{ + controller := &controller.ExportedServicesController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), Scheme: s, - ConfigEntryController: &configentries.ConfigEntryController{ + ConfigEntryController: &controller.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, @@ -196,7 +192,7 @@ func TestExportedServicesController_updatesExportedServices(tt *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "default", Namespace: c.SourceKubeNS, - Finalizers: []string{configentries.FinalizerName}, + Finalizers: []string{controller.FinalizerName}, }, Spec: v1alpha1.ExportedServicesSpec{ Services: []v1alpha1.ExportedService{ @@ -219,11 +215,11 @@ func TestExportedServicesController_updatesExportedServices(tt *testing.T) { consulClient := testClient.APIClient fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &configentries.ExportedServicesController{ + controller := &controller.ExportedServicesController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), Scheme: s, - ConfigEntryController: &configentries.ConfigEntryController{ + ConfigEntryController: &controller.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, @@ -333,7 +329,7 @@ func TestExportedServicesController_deletesExportedServices(tt *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "default", Namespace: c.SourceKubeNS, - Finalizers: []string{configentries.FinalizerName}, + Finalizers: []string{controller.FinalizerName}, DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, Spec: v1alpha1.ExportedServicesSpec{ @@ -357,11 +353,11 @@ func TestExportedServicesController_deletesExportedServices(tt *testing.T) { fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() - controller := &configentries.ExportedServicesController{ + controller := &controller.ExportedServicesController{ Client: fakeClient, - Log: logrtest.NewTestLogger(t), + Log: logrtest.New(t), Scheme: s, - ConfigEntryController: &configentries.ConfigEntryController{ + ConfigEntryController: &controller.ConfigEntryController{ ConsulClientConfig: testClient.Cfg, ConsulServerConnMgr: testClient.Watcher, EnableConsulNamespaces: true, diff --git a/control-plane/controllers/configentries/ingressgateway_controller.go b/control-plane/controller/ingressgateway_controller.go similarity index 92% rename from control-plane/controllers/configentries/ingressgateway_controller.go rename to control-plane/controller/ingressgateway_controller.go index d1cced515e..5a6a07776b 100644 --- a/control-plane/controllers/configentries/ingressgateway_controller.go +++ b/control-plane/controller/ingressgateway_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -17,7 +14,6 @@ import ( // IngressGatewayController is the controller for IngressGateway resources. type IngressGatewayController struct { - FinalizerPatcher client.Client Log logr.Logger Scheme *runtime.Scheme diff --git a/control-plane/controllers/configentries/mesh_controller.go b/control-plane/controller/mesh_controller.go similarity index 89% rename from control-plane/controllers/configentries/mesh_controller.go rename to control-plane/controller/mesh_controller.go index 7593e287ad..94b0df6a5e 100644 --- a/control-plane/controllers/configentries/mesh_controller.go +++ b/control-plane/controller/mesh_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*MeshController)(nil) - // MeshController reconciles a Mesh object. type MeshController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/proxydefaults_controller.go b/control-plane/controller/proxydefaults_controller.go similarity index 89% rename from control-plane/controllers/configentries/proxydefaults_controller.go rename to control-plane/controller/proxydefaults_controller.go index 0edea71136..fe929c1bf1 100644 --- a/control-plane/controllers/configentries/proxydefaults_controller.go +++ b/control-plane/controller/proxydefaults_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*ProxyDefaultsController)(nil) - // ProxyDefaultsController reconciles a ProxyDefaults object. type ProxyDefaultsController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/servicedefaults_controller.go b/control-plane/controller/servicedefaults_controller.go similarity index 89% rename from control-plane/controllers/configentries/servicedefaults_controller.go rename to control-plane/controller/servicedefaults_controller.go index 00c5235889..e58e186234 100644 --- a/control-plane/controllers/configentries/servicedefaults_controller.go +++ b/control-plane/controller/servicedefaults_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*ServiceDefaultsController)(nil) - // ServiceDefaultsController is the controller for ServiceDefaults resources. type ServiceDefaultsController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/serviceintentions_controller.go b/control-plane/controller/serviceintentions_controller.go similarity index 89% rename from control-plane/controllers/configentries/serviceintentions_controller.go rename to control-plane/controller/serviceintentions_controller.go index 95706fc960..e5bfab959e 100644 --- a/control-plane/controllers/configentries/serviceintentions_controller.go +++ b/control-plane/controller/serviceintentions_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*ServiceIntentionsController)(nil) - // ServiceIntentionsController reconciles a ServiceIntentions object. type ServiceIntentionsController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/serviceresolver_controller.go b/control-plane/controller/serviceresolver_controller.go similarity index 89% rename from control-plane/controllers/configentries/serviceresolver_controller.go rename to control-plane/controller/serviceresolver_controller.go index 7e2352a287..3019369dc6 100644 --- a/control-plane/controllers/configentries/serviceresolver_controller.go +++ b/control-plane/controller/serviceresolver_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*ServiceResolverController)(nil) - // ServiceResolverController is the controller for ServiceResolver resources. type ServiceResolverController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/servicerouter_controller.go b/control-plane/controller/servicerouter_controller.go similarity index 89% rename from control-plane/controllers/configentries/servicerouter_controller.go rename to control-plane/controller/servicerouter_controller.go index 7f16addbf2..9c435169f4 100644 --- a/control-plane/controllers/configentries/servicerouter_controller.go +++ b/control-plane/controller/servicerouter_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*ServiceRouterController)(nil) - // ServiceRouterController is the controller for ServiceRouter resources. type ServiceRouterController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/servicesplitter_controller.go b/control-plane/controller/servicesplitter_controller.go similarity index 89% rename from control-plane/controllers/configentries/servicesplitter_controller.go rename to control-plane/controller/servicesplitter_controller.go index 274020a8d8..f977301afe 100644 --- a/control-plane/controllers/configentries/servicesplitter_controller.go +++ b/control-plane/controller/servicesplitter_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*ServiceSplitterController)(nil) - // ServiceSplitterReconciler reconciles a ServiceSplitter object. type ServiceSplitterController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/terminatinggateway_controller.go b/control-plane/controller/terminatinggateway_controller.go similarity index 89% rename from control-plane/controllers/configentries/terminatinggateway_controller.go rename to control-plane/controller/terminatinggateway_controller.go index f8e4a0bc0b..159f8ff8c1 100644 --- a/control-plane/controllers/configentries/terminatinggateway_controller.go +++ b/control-plane/controller/terminatinggateway_controller.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries +package controller import ( "context" @@ -15,12 +12,9 @@ import ( consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) -var _ Controller = (*TerminatingGatewayController)(nil) - // TerminatingGatewayController is the controller for TerminatingGateway resources. type TerminatingGatewayController struct { client.Client - FinalizerPatcher Log logr.Logger Scheme *runtime.Scheme ConfigEntryController *ConfigEntryController diff --git a/control-plane/controllers/configentries/configentry_controller_ent_test.go b/control-plane/controllers/configentries/configentry_controller_ent_test.go deleted file mode 100644 index b9eabf3a72..0000000000 --- a/control-plane/controllers/configentries/configentry_controller_ent_test.go +++ /dev/null @@ -1,1388 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package configentries - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/go-logr/logr" - logrtest "github.com/go-logr/logr/testing" - capi "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -// NOTE: We're not testing each controller type here because that's mostly done in -// the OSS tests and it would result in too many permutations. Instead -// we're only testing with the ServiceDefaults and ProxyDefaults configentries which -// will exercise all the namespaces code for config entries that are namespaced and those that -// exist in the global namespace. -// We also test Enterprise only features like SamenessGroups. - -func TestConfigEntryController_createsEntConfigEntry(t *testing.T) { - t.Parallel() - kubeNS := "default" - - cases := []struct { - kubeKind string - consulKind string - consulPrereqs []capi.ConfigEntry - configEntryResource common.ConfigEntryResource - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - compare func(t *testing.T, consul capi.ConfigEntry) - }{ - { - kubeKind: "SamenessGroup", - consulKind: capi.SamenessGroup, - configEntryResource: &v1alpha1.SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: v1alpha1.SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []v1alpha1.SamenessGroupMember{ - { - Peer: "dc1", - Partition: "", - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &SamenessGroupController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - resource, ok := consulEntry.(*capi.SamenessGroupConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, true, resource.DefaultForFailover) - require.Equal(t, true, resource.IncludeLocal) - require.Equal(t, "dc1", resource.Members[0].Peer) - require.Equal(t, "", resource.Members[0].Partition) - }, - }, - { - kubeKind: "ControlPlaneRequestLimit", - consulKind: capi.RateLimitIPConfig, - configEntryResource: &v1alpha1.ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: v1alpha1.ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ACL: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &ControlPlaneRequestLimitController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - resource, ok := consulEntry.(*capi.RateLimitIPConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, "permissive", resource.Mode) - require.Equal(t, 100.0, resource.ReadRate) - require.Equal(t, 100.0, resource.WriteRate) - require.Equal(t, 100.0, resource.ACL.ReadRate) - require.Equal(t, 100.0, resource.ACL.WriteRate) - require.Equal(t, 100.0, resource.Catalog.ReadRate) - require.Equal(t, 100.0, resource.Catalog.WriteRate) - require.Equal(t, 100.0, resource.ConfigEntry.ReadRate) - require.Equal(t, 100.0, resource.ConfigEntry.WriteRate) - require.Equal(t, 100.0, resource.ConnectCA.ReadRate) - require.Equal(t, 100.0, resource.ConnectCA.WriteRate) - require.Equal(t, 100.0, resource.Coordinate.ReadRate) - require.Equal(t, 100.0, resource.Coordinate.WriteRate) - require.Equal(t, 100.0, resource.DiscoveryChain.ReadRate) - require.Equal(t, 100.0, resource.DiscoveryChain.WriteRate) - require.Equal(t, 100.0, resource.Health.ReadRate) - require.Equal(t, 100.0, resource.Health.WriteRate) - require.Equal(t, 100.0, resource.Intention.ReadRate) - require.Equal(t, 100.0, resource.Intention.WriteRate) - require.Equal(t, 100.0, resource.KV.ReadRate) - require.Equal(t, 100.0, resource.KV.WriteRate) - require.Equal(t, 100.0, resource.Tenancy.ReadRate) - require.Equal(t, 100.0, resource.Tenancy.WriteRate) - require.Equal(t, 100.0, resource.PreparedQuery.ReadRate) - require.Equal(t, 100.0, resource.PreparedQuery.WriteRate) - require.Equal(t, 100.0, resource.Session.ReadRate) - require.Equal(t, 100.0, resource.Session.WriteRate) - require.Equal(t, 100.0, resource.Txn.ReadRate) - require.Equal(t, 100.0, resource.Txn.WriteRate, 100.0) - }, - }, - } - - for _, c := range cases { - t.Run(c.kubeKind, func(t *testing.T) { - req := require.New(t) - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResource) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResource).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - testClient.TestServer.WaitForServiceIntentions(t) - consulClient := testClient.APIClient - - for _, configEntry := range c.consulPrereqs { - written, _, err := consulClient.ConfigEntries().Set(configEntry, nil) - req.NoError(err) - req.True(written) - } - - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) - namespacedName := types.NamespacedName{ - Namespace: kubeNS, - Name: c.configEntryResource.KubernetesName(), - } - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - req.NoError(err) - req.False(resp.Requeue) - - cfg, _, err := consulClient.ConfigEntries().Get(c.consulKind, c.configEntryResource.ConsulName(), nil) - req.NoError(err) - req.Equal(c.configEntryResource.ConsulName(), cfg.GetName()) - c.compare(t, cfg) - - // Check that the status is "synced". - err = fakeClient.Get(ctx, namespacedName, c.configEntryResource) - req.NoError(err) - req.Equal(corev1.ConditionTrue, c.configEntryResource.SyncedConditionStatus()) - - // Check that the finalizer is added. - req.Contains(c.configEntryResource.Finalizers(), FinalizerName) - }) - } -} - -func TestConfigEntryController_updatesEntConfigEntry(t *testing.T) { - t.Parallel() - kubeNS := "default" - - cases := []struct { - kubeKind string - consulKind string - consulPrereqs []capi.ConfigEntry - configEntryResource common.ConfigEntryResource - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - updateF func(common.ConfigEntryResource) - compare func(t *testing.T, consul capi.ConfigEntry) - }{ - { - kubeKind: "SamenessGroup", - consulKind: capi.SamenessGroup, - configEntryResource: &v1alpha1.SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: v1alpha1.SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []v1alpha1.SamenessGroupMember{ - { - Peer: "dc1", - Partition: "", - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &SamenessGroupController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - updateF: func(resource common.ConfigEntryResource) { - sg := resource.(*v1alpha1.SamenessGroup) - sg.Spec.DefaultForFailover = false - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - resource, ok := consulEntry.(*capi.SamenessGroupConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, false, resource.DefaultForFailover) - require.Equal(t, true, resource.IncludeLocal) - require.Equal(t, "dc1", resource.Members[0].Peer) - require.Equal(t, "", resource.Members[0].Partition) - }, - }, - { - kubeKind: "ControlPlaneRequestLimit", - consulKind: capi.RateLimitIPConfig, - configEntryResource: &v1alpha1.ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - }, - Spec: v1alpha1.ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ACL: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &ControlPlaneRequestLimitController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - updateF: func(resource common.ConfigEntryResource) { - ipRateLimit := resource.(*v1alpha1.ControlPlaneRequestLimit) - ipRateLimit.Spec.Mode = "enforcing" - }, - compare: func(t *testing.T, consulEntry capi.ConfigEntry) { - resource, ok := consulEntry.(*capi.RateLimitIPConfigEntry) - require.True(t, ok, "cast error") - require.Equal(t, "enforcing", resource.Mode) - require.Equal(t, 100.0, resource.ReadRate) - require.Equal(t, 100.0, resource.WriteRate) - require.Equal(t, 100.0, resource.ACL.ReadRate) - require.Equal(t, 100.0, resource.ACL.WriteRate) - require.Equal(t, 100.0, resource.Catalog.ReadRate) - require.Equal(t, 100.0, resource.Catalog.WriteRate) - require.Equal(t, 100.0, resource.ConfigEntry.ReadRate) - require.Equal(t, 100.0, resource.ConfigEntry.WriteRate) - require.Equal(t, 100.0, resource.ConnectCA.ReadRate) - require.Equal(t, 100.0, resource.ConnectCA.WriteRate) - require.Equal(t, 100.0, resource.Coordinate.ReadRate) - require.Equal(t, 100.0, resource.Coordinate.WriteRate) - require.Equal(t, 100.0, resource.DiscoveryChain.ReadRate) - require.Equal(t, 100.0, resource.DiscoveryChain.WriteRate) - require.Equal(t, 100.0, resource.Health.ReadRate) - require.Equal(t, 100.0, resource.Health.WriteRate) - require.Equal(t, 100.0, resource.Intention.ReadRate) - require.Equal(t, 100.0, resource.Intention.WriteRate) - require.Equal(t, 100.0, resource.KV.ReadRate) - require.Equal(t, 100.0, resource.KV.WriteRate) - require.Equal(t, 100.0, resource.Tenancy.ReadRate) - require.Equal(t, 100.0, resource.Tenancy.WriteRate) - require.Equal(t, 100.0, resource.PreparedQuery.ReadRate) - require.Equal(t, 100.0, resource.PreparedQuery.WriteRate) - require.Equal(t, 100.0, resource.Session.ReadRate) - require.Equal(t, 100.0, resource.Session.WriteRate) - require.Equal(t, 100.0, resource.Txn.ReadRate) - require.Equal(t, 100.0, resource.Txn.WriteRate) - }, - }, - } - - for _, c := range cases { - t.Run(c.kubeKind, func(t *testing.T) { - req := require.New(t) - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResource) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResource).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - testClient.TestServer.WaitForServiceIntentions(t) - consulClient := testClient.APIClient - - // Create any prereqs. - for _, configEntry := range c.consulPrereqs { - written, _, err := consulClient.ConfigEntries().Set(configEntry, nil) - req.NoError(err) - req.True(written) - } - - // We haven't run reconcile yet so we must create the config entry - // in Consul ourselves. - { - written, _, err := consulClient.ConfigEntries().Set(c.configEntryResource.ToConsul(datacenterName), nil) - req.NoError(err) - req.True(written) - } - - // Now run reconcile which should update the entry in Consul. - { - namespacedName := types.NamespacedName{ - Namespace: kubeNS, - Name: c.configEntryResource.KubernetesName(), - } - // First get it so we have the latest revision number. - err := fakeClient.Get(ctx, namespacedName, c.configEntryResource) - req.NoError(err) - - // Update the entry in Kube and run reconcile. - c.updateF(c.configEntryResource) - err = fakeClient.Update(ctx, c.configEntryResource) - req.NoError(err) - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - req.NoError(err) - req.False(resp.Requeue) - - // Now check that the object in Consul is as expected. - cfg, _, err := consulClient.ConfigEntries().Get(c.consulKind, c.configEntryResource.ConsulName(), nil) - req.NoError(err) - req.Equal(c.configEntryResource.ConsulName(), cfg.GetName()) - c.compare(t, cfg) - } - }) - } -} - -func TestConfigEntryController_deletesEntConfigEntry(t *testing.T) { - t.Parallel() - kubeNS := "default" - - cases := []struct { - kubeKind string - consulKind string - consulPrereq []capi.ConfigEntry - configEntryResourceWithDeletion common.ConfigEntryResource - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - }{ - { - kubeKind: "SamenessGroup", - consulKind: capi.SamenessGroup, - configEntryResourceWithDeletion: &v1alpha1.SamenessGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{FinalizerName}, - }, - Spec: v1alpha1.SamenessGroupSpec{ - DefaultForFailover: true, - IncludeLocal: true, - Members: []v1alpha1.SamenessGroupMember{ - { - Peer: "dc1", - Partition: "", - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &SamenessGroupController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - }, - { - - kubeKind: "ControlPlaneRequestLimit", - consulKind: capi.RateLimitIPConfig, - configEntryResourceWithDeletion: &v1alpha1.ControlPlaneRequestLimit{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: kubeNS, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{FinalizerName}, - }, - Spec: v1alpha1.ControlPlaneRequestLimitSpec{ - Mode: "permissive", - ReadWriteRatesConfig: v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ACL: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Catalog: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConfigEntry: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - ConnectCA: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Coordinate: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - DiscoveryChain: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Health: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Intention: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - KV: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Tenancy: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - PreparedQuery: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Session: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - Txn: &v1alpha1.ReadWriteRatesConfig{ - ReadRate: 100.0, - WriteRate: 100.0, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &ControlPlaneRequestLimitController{ - Client: client, - Log: logger, - ConfigEntryController: &ConfigEntryController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - DatacenterName: datacenterName, - }, - } - }, - }, - } - - for _, c := range cases { - t.Run(c.kubeKind, func(t *testing.T) { - req := require.New(t) - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResourceWithDeletion) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResourceWithDeletion).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - testClient.TestServer.WaitForServiceIntentions(t) - consulClient := testClient.APIClient - - // Create any prereqs. - for _, configEntry := range c.consulPrereq { - written, _, err := consulClient.ConfigEntries().Set(configEntry, nil) - req.NoError(err) - req.True(written) - } - - // We haven't run reconcile yet so we must create the config entry - // in Consul ourselves. - { - written, _, err := consulClient.ConfigEntries().Set(c.configEntryResourceWithDeletion.ToConsul(datacenterName), nil) - req.NoError(err) - req.True(written) - } - - // Now run reconcile. It's marked for deletion so this should delete it. - { - namespacedName := types.NamespacedName{ - Namespace: kubeNS, - Name: c.configEntryResourceWithDeletion.KubernetesName(), - } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.NewTestLogger(t)) - resp, err := r.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - req.NoError(err) - req.False(resp.Requeue) - - _, _, err = consulClient.ConfigEntries().Get(c.consulKind, c.configEntryResourceWithDeletion.ConsulName(), nil) - req.EqualError(err, - fmt.Sprintf("Unexpected response code: 404 (Config entry not found for %q / %q)", - c.consulKind, c.configEntryResourceWithDeletion.ConsulName())) - } - }) - } -} - -func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T) { - tt.Parallel() - - cases := map[string]struct { - Mirror bool - MirrorPrefix string - SourceKubeNS string - DestConsulNS string - ExpConsulNS string - }{ - "SourceKubeNS=default, DestConsulNS=default": { - SourceKubeNS: "default", - DestConsulNS: "default", - ExpConsulNS: "default", - }, - "SourceKubeNS=kube, DestConsulNS=default": { - SourceKubeNS: "kube", - DestConsulNS: "default", - ExpConsulNS: "default", - }, - "SourceKubeNS=default, DestConsulNS=other": { - SourceKubeNS: "default", - DestConsulNS: "other", - ExpConsulNS: "other", - }, - "SourceKubeNS=kube, DestConsulNS=other": { - SourceKubeNS: "kube", - DestConsulNS: "other", - ExpConsulNS: "other", - }, - "SourceKubeNS=default, Mirror=true": { - SourceKubeNS: "default", - Mirror: true, - ExpConsulNS: "default", - }, - "SourceKubeNS=kube, Mirror=true": { - SourceKubeNS: "kube", - Mirror: true, - ExpConsulNS: "kube", - }, - "SourceKubeNS=default, Mirror=true, Prefix=prefix": { - SourceKubeNS: "default", - Mirror: true, - MirrorPrefix: "prefix-", - ExpConsulNS: "prefix-default", - }, - } - - for name, c := range cases { - configEntryKinds := map[string]struct { - ConsulKind string - ConsulNamespace string - KubeResource common.ConfigEntryResource - GetController func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler - AssertValidConfig func(entry capi.ConfigEntry) bool - }{ - "namespaced": { - ConsulKind: capi.ServiceDefaults, - KubeResource: &v1alpha1.ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: c.SourceKubeNS, - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - }, - GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ServiceDefaultsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - AssertValidConfig: func(cfg capi.ConfigEntry) bool { - configEntry, ok := cfg.(*capi.ServiceConfigEntry) - if !ok { - return false - } - return configEntry.Protocol == "http" - }, - ConsulNamespace: c.ExpConsulNS, - }, - "global": { - ConsulKind: capi.ProxyDefaults, - KubeResource: &v1alpha1.ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.Global, - Namespace: c.SourceKubeNS, - }, - Spec: v1alpha1.ProxyDefaultsSpec{ - MeshGateway: v1alpha1.MeshGateway{ - Mode: "remote", - }, - }, - }, - GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ProxyDefaultsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - AssertValidConfig: func(cfg capi.ConfigEntry) bool { - configEntry, ok := cfg.(*capi.ProxyConfigEntry) - if !ok { - return false - } - return configEntry.MeshGateway.Mode == capi.MeshGatewayModeRemote - }, - ConsulNamespace: common.DefaultConsulNamespace, - }, - "intentions": { - ConsulKind: capi.ServiceIntentions, - KubeResource: &v1alpha1.ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: c.SourceKubeNS, - }, - Spec: v1alpha1.ServiceIntentionsSpec{ - Destination: v1alpha1.IntentionDestination{ - Name: "test", - Namespace: c.ExpConsulNS, - }, - Sources: v1alpha1.SourceIntentions{ - &v1alpha1.SourceIntention{ - Name: "baz", - Namespace: "bar", - Action: "allow", - }, - }, - }, - }, - GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ServiceIntentionsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - AssertValidConfig: func(cfg capi.ConfigEntry) bool { - configEntry, ok := cfg.(*capi.ServiceIntentionsConfigEntry) - if !ok { - return false - } - return configEntry.Sources[0].Action == capi.IntentionActionAllow - }, - ConsulNamespace: c.ExpConsulNS, - }, - } - - for kind, in := range configEntryKinds { - tt.Run(fmt.Sprintf("%s : %s", name, kind), func(t *testing.T) { - req := require.New(t) - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, in.KubeResource) - ctx := context.Background() - - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - testClient.TestServer.WaitForServiceIntentions(t) - consulClient := testClient.APIClient - - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(in.KubeResource).Build() - - r := in.GetController( - fakeClient, - logrtest.NewTestLogger(t), - s, - &ConfigEntryController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - EnableConsulNamespaces: true, - EnableNSMirroring: c.Mirror, - NSMirroringPrefix: c.MirrorPrefix, - ConsulDestinationNamespace: c.DestConsulNS, - }, - ) - - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: c.SourceKubeNS, - Name: in.KubeResource.KubernetesName(), - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - cfg, _, err := consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.ConsulName(), &capi.QueryOptions{ - Namespace: in.ConsulNamespace, - }) - req.NoError(err) - - result := in.AssertValidConfig(cfg) - req.True(result) - - // Check that the status is "synced". - err = fakeClient.Get(ctx, types.NamespacedName{ - Namespace: c.SourceKubeNS, - Name: in.KubeResource.KubernetesName(), - }, in.KubeResource) - req.NoError(err) - conditionSynced := in.KubeResource.SyncedConditionStatus() - req.Equal(conditionSynced, corev1.ConditionTrue) - }) - } - } -} - -func TestConfigEntryController_updatesConfigEntry_consulNamespaces(tt *testing.T) { - tt.Parallel() - - cases := map[string]struct { - Mirror bool - MirrorPrefix string - SourceKubeNS string - DestConsulNS string - ExpConsulNS string - }{ - "SourceKubeNS=default, DestConsulNS=default": { - SourceKubeNS: "default", - DestConsulNS: "default", - ExpConsulNS: "default", - }, - "SourceKubeNS=kube, DestConsulNS=default": { - SourceKubeNS: "kube", - DestConsulNS: "default", - ExpConsulNS: "default", - }, - "SourceKubeNS=default, DestConsulNS=other": { - SourceKubeNS: "default", - DestConsulNS: "other", - ExpConsulNS: "other", - }, - "SourceKubeNS=kube, DestConsulNS=other": { - SourceKubeNS: "kube", - DestConsulNS: "other", - ExpConsulNS: "other", - }, - "SourceKubeNS=default, Mirror=true": { - SourceKubeNS: "default", - Mirror: true, - ExpConsulNS: "default", - }, - "SourceKubeNS=kube, Mirror=true": { - SourceKubeNS: "kube", - Mirror: true, - ExpConsulNS: "kube", - }, - "SourceKubeNS=default, Mirror=true, Prefix=prefix": { - SourceKubeNS: "default", - Mirror: true, - MirrorPrefix: "prefix-", - ExpConsulNS: "prefix-default", - }, - } - - for name, c := range cases { - configEntryKinds := map[string]struct { - ConsulKind string - ConsulNamespace string - KubeResource common.ConfigEntryResource - GetControllerFunc func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler - AssertValidConfigFunc func(entry capi.ConfigEntry) bool - WriteConfigEntryFunc func(consulClient *capi.Client, namespace string) error - UpdateResourceFunc func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error - }{ - "namespaced": { - ConsulKind: capi.ServiceDefaults, - KubeResource: &v1alpha1.ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: c.SourceKubeNS, - Finalizers: []string{FinalizerName}, - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - }, - ConsulNamespace: c.ExpConsulNS, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ServiceDefaultsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { - _, _, err := consulClient.ConfigEntries().Set(&capi.ServiceConfigEntry{ - Kind: capi.ServiceDefaults, - Name: "foo", - Protocol: "http", - }, &capi.WriteOptions{Namespace: namespace}) - return err - }, - UpdateResourceFunc: func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error { - svcDefault := in.(*v1alpha1.ServiceDefaults) - svcDefault.Spec.Protocol = "tcp" - return client.Update(ctx, svcDefault) - }, - AssertValidConfigFunc: func(cfg capi.ConfigEntry) bool { - configEntry, ok := cfg.(*capi.ServiceConfigEntry) - if !ok { - return false - } - return configEntry.Protocol == "tcp" - }, - }, - "global": { - ConsulKind: capi.ProxyDefaults, - KubeResource: &v1alpha1.ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.Global, - Namespace: c.SourceKubeNS, - Finalizers: []string{FinalizerName}, - }, - Spec: v1alpha1.ProxyDefaultsSpec{ - MeshGateway: v1alpha1.MeshGateway{ - Mode: "remote", - }, - }, - }, - ConsulNamespace: common.DefaultConsulNamespace, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ProxyDefaultsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { - _, _, err := consulClient.ConfigEntries().Set(&capi.ProxyConfigEntry{ - Kind: capi.ProxyDefaults, - Name: common.Global, - MeshGateway: capi.MeshGatewayConfig{ - Mode: capi.MeshGatewayModeRemote, - }, - }, &capi.WriteOptions{Namespace: namespace}) - return err - }, - UpdateResourceFunc: func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error { - proxyDefaults := in.(*v1alpha1.ProxyDefaults) - proxyDefaults.Spec.MeshGateway.Mode = "local" - return client.Update(ctx, proxyDefaults) - }, - AssertValidConfigFunc: func(cfg capi.ConfigEntry) bool { - configEntry, ok := cfg.(*capi.ProxyConfigEntry) - if !ok { - return false - } - return configEntry.MeshGateway.Mode == capi.MeshGatewayModeLocal - }, - }, - "intentions": { - ConsulKind: capi.ServiceIntentions, - KubeResource: &v1alpha1.ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: c.SourceKubeNS, - Finalizers: []string{FinalizerName}, - }, - Spec: v1alpha1.ServiceIntentionsSpec{ - Destination: v1alpha1.IntentionDestination{ - Name: "foo", - Namespace: c.ExpConsulNS, - }, - Sources: v1alpha1.SourceIntentions{ - &v1alpha1.SourceIntention{ - Name: "bar", - Namespace: "baz", - Action: "deny", - }, - }, - }, - }, - ConsulNamespace: c.ExpConsulNS, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ServiceIntentionsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { - _, _, err := consulClient.ConfigEntries().Set(&capi.ServiceIntentionsConfigEntry{ - Kind: capi.ServiceIntentions, - Name: "foo", - Sources: []*capi.SourceIntention{ - { - Name: "bar", - Namespace: "baz", - Action: capi.IntentionActionDeny, - }, - }, - }, &capi.WriteOptions{Namespace: namespace}) - return err - }, - UpdateResourceFunc: func(client client.Client, ctx context.Context, in common.ConfigEntryResource) error { - svcIntention := in.(*v1alpha1.ServiceIntentions) - svcIntention.Spec.Sources[0].Action = "allow" - return client.Update(ctx, svcIntention) - }, - AssertValidConfigFunc: func(cfg capi.ConfigEntry) bool { - configEntry, ok := cfg.(*capi.ServiceIntentionsConfigEntry) - if !ok { - return false - } - return configEntry.Sources[0].Action == capi.IntentionActionAllow - }, - }, - } - for kind, in := range configEntryKinds { - tt.Run(fmt.Sprintf("%s : %s", name, kind), func(t *testing.T) { - req := require.New(t) - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, in.KubeResource) - ctx := context.Background() - - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - testClient.TestServer.WaitForServiceIntentions(t) - consulClient := testClient.APIClient - - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(in.KubeResource).Build() - - r := in.GetControllerFunc( - fakeClient, - logrtest.NewTestLogger(t), - s, - &ConfigEntryController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - EnableConsulNamespaces: true, - EnableNSMirroring: c.Mirror, - NSMirroringPrefix: c.MirrorPrefix, - ConsulDestinationNamespace: c.DestConsulNS, - }, - ) - - // We haven't run reconcile yet so ensure it's created in Consul. - { - if in.ConsulNamespace != "default" { - _, _, err := consulClient.Namespaces().Create(&capi.Namespace{ - Name: in.ConsulNamespace, - }, nil) - req.NoError(err) - } - - err := in.WriteConfigEntryFunc(consulClient, in.ConsulNamespace) - req.NoError(err) - } - - // Now update it. - { - // First get it so we have the latest revision number. - err := fakeClient.Get(ctx, types.NamespacedName{ - Namespace: c.SourceKubeNS, - Name: in.KubeResource.KubernetesName(), - }, in.KubeResource) - req.NoError(err) - - // Update the resource. - err = in.UpdateResourceFunc(fakeClient, ctx, in.KubeResource) - req.NoError(err) - - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: c.SourceKubeNS, - Name: in.KubeResource.KubernetesName(), - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - cfg, _, err := consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.ConsulName(), &capi.QueryOptions{ - Namespace: in.ConsulNamespace, - }) - req.NoError(err) - req.True(in.AssertValidConfigFunc(cfg)) - } - }) - } - } -} - -func TestConfigEntryController_deletesConfigEntry_consulNamespaces(tt *testing.T) { - tt.Parallel() - - cases := map[string]struct { - Mirror bool - MirrorPrefix string - SourceKubeNS string - DestConsulNS string - ExpConsulNS string - }{ - "SourceKubeNS=default, DestConsulNS=default": { - SourceKubeNS: "default", - DestConsulNS: "default", - ExpConsulNS: "default", - }, - "SourceKubeNS=kube, DestConsulNS=default": { - SourceKubeNS: "kube", - DestConsulNS: "default", - ExpConsulNS: "default", - }, - "SourceKubeNS=default, DestConsulNS=other": { - SourceKubeNS: "default", - DestConsulNS: "other", - ExpConsulNS: "other", - }, - "SourceKubeNS=kube, DestConsulNS=other": { - SourceKubeNS: "kube", - DestConsulNS: "other", - ExpConsulNS: "other", - }, - "SourceKubeNS=default, Mirror=true": { - SourceKubeNS: "default", - Mirror: true, - ExpConsulNS: "default", - }, - "SourceKubeNS=kube, Mirror=true": { - SourceKubeNS: "kube", - Mirror: true, - ExpConsulNS: "kube", - }, - "SourceKubeNS=default, Mirror=true, Prefix=prefix": { - SourceKubeNS: "default", - Mirror: true, - MirrorPrefix: "prefix-", - ExpConsulNS: "prefix-default", - }, - } - - for name, c := range cases { - configEntryKinds := map[string]struct { - ConsulKind string - ConsulNamespace string - KubeResource common.ConfigEntryResource - GetControllerFunc func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler - WriteConfigEntryFunc func(consulClient *capi.Client, namespace string) error - }{ - "namespaced": { - ConsulKind: capi.ServiceDefaults, - // Create it with the deletion timestamp set to mimic that it's already - // been marked for deletion. - KubeResource: &v1alpha1.ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: c.SourceKubeNS, - Finalizers: []string{FinalizerName}, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - }, - ConsulNamespace: c.ExpConsulNS, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ServiceDefaultsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { - _, _, err := consulClient.ConfigEntries().Set(&capi.ServiceConfigEntry{ - Kind: capi.ServiceDefaults, - Name: "foo", - Protocol: "http", - }, &capi.WriteOptions{Namespace: namespace}) - return err - }, - }, - "global": { - ConsulKind: capi.ProxyDefaults, - // Create it with the deletion timestamp set to mimic that it's already - // been marked for deletion. - KubeResource: &v1alpha1.ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.Global, - Namespace: c.SourceKubeNS, - Finalizers: []string{FinalizerName}, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - }, - Spec: v1alpha1.ProxyDefaultsSpec{ - MeshGateway: v1alpha1.MeshGateway{ - Mode: "remote", - }, - }, - }, - ConsulNamespace: common.DefaultConsulNamespace, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ProxyDefaultsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { - _, _, err := consulClient.ConfigEntries().Set(&capi.ProxyConfigEntry{ - Kind: capi.ProxyDefaults, - Name: common.Global, - MeshGateway: capi.MeshGatewayConfig{ - Mode: capi.MeshGatewayModeRemote, - }, - }, &capi.WriteOptions{Namespace: namespace}) - return err - }, - }, - "intentions": { - ConsulKind: capi.ServiceIntentions, - // Create it with the deletion timestamp set to mimic that it's already - // been marked for deletion. - KubeResource: &v1alpha1.ServiceIntentions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: c.SourceKubeNS, - Finalizers: []string{FinalizerName}, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - }, - Spec: v1alpha1.ServiceIntentionsSpec{ - Destination: v1alpha1.IntentionDestination{ - Name: "test", - Namespace: c.ExpConsulNS, - }, - Sources: v1alpha1.SourceIntentions{ - &v1alpha1.SourceIntention{ - Name: "bar", - Namespace: "baz", - Action: "deny", - }, - }, - }, - }, - ConsulNamespace: c.ExpConsulNS, - GetControllerFunc: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *ConfigEntryController) reconcile.Reconciler { - return &ServiceIntentionsController{ - Client: client, - Log: logger, - Scheme: scheme, - ConfigEntryController: cont, - } - }, - WriteConfigEntryFunc: func(consulClient *capi.Client, namespace string) error { - _, _, err := consulClient.ConfigEntries().Set(&capi.ServiceIntentionsConfigEntry{ - Kind: capi.ServiceIntentions, - Name: "test", - Sources: []*capi.SourceIntention{ - { - Name: "bar", - Namespace: "baz", - Action: capi.IntentionActionDeny, - }, - }, - }, &capi.WriteOptions{Namespace: namespace}) - return err - }, - }, - } - for kind, in := range configEntryKinds { - tt.Run(fmt.Sprintf("%s : %s", name, kind), func(t *testing.T) { - req := require.New(t) - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, in.KubeResource) - - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - testClient.TestServer.WaitForServiceIntentions(t) - consulClient := testClient.APIClient - - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(in.KubeResource).Build() - - r := in.GetControllerFunc( - fakeClient, - logrtest.NewTestLogger(t), - s, - &ConfigEntryController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - EnableConsulNamespaces: true, - EnableNSMirroring: c.Mirror, - NSMirroringPrefix: c.MirrorPrefix, - ConsulDestinationNamespace: c.DestConsulNS, - }, - ) - - // We haven't run reconcile yet so ensure it's created in Consul. - { - if in.ConsulNamespace != "default" { - _, _, err := consulClient.Namespaces().Create(&capi.Namespace{ - Name: in.ConsulNamespace, - }, nil) - req.NoError(err) - } - - err := in.WriteConfigEntryFunc(consulClient, in.ConsulNamespace) - req.NoError(err) - } - - // Now run reconcile. It's marked for deletion so this should delete it. - { - resp, err := r.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: c.SourceKubeNS, - Name: in.KubeResource.KubernetesName(), - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - _, _, err = consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.ConsulName(), &capi.QueryOptions{ - Namespace: in.ConsulNamespace, - }) - req.EqualError(err, fmt.Sprintf(`Unexpected response code: 404 (Config entry not found for "%s" / "%s")`, in.ConsulKind, in.KubeResource.ConsulName())) - } - }) - } - } -} diff --git a/control-plane/controllers/configentries/controlplanerequestlimit_controller.go b/control-plane/controllers/configentries/controlplanerequestlimit_controller.go deleted file mode 100644 index c636f7fec5..0000000000 --- a/control-plane/controllers/configentries/controlplanerequestlimit_controller.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -var _ Controller = (*ControlPlaneRequestLimitController)(nil) - -// ControlPlaneRequestLimitController reconciles a ControlPlaneRequestLimit object. -type ControlPlaneRequestLimitController struct { - client.Client - FinalizerPatcher - Log logr.Logger - Scheme *runtime.Scheme - ConfigEntryController *ConfigEntryController -} - -//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=controlplanerequestlimits,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=controlplanerequestlimits/status,verbs=get;update;patch - -func (r *ControlPlaneRequestLimitController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.ConfigEntryController.ReconcileEntry(ctx, r, req, &consulv1alpha1.ControlPlaneRequestLimit{}) -} - -func (r *ControlPlaneRequestLimitController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *ControlPlaneRequestLimitController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *ControlPlaneRequestLimitController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &consulv1alpha1.ControlPlaneRequestLimit{}, r) -} diff --git a/control-plane/controllers/configentries/finalizer_patch.go b/control-plane/controllers/configentries/finalizer_patch.go deleted file mode 100644 index a261220c8f..0000000000 --- a/control-plane/controllers/configentries/finalizer_patch.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries - -import ( - "encoding/json" - - jsonpatch "github.com/evanphx/json-patch" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type FinalizerPatcher struct{} - -type FinalizerPatch struct { - NewFinalizers []string -} - -// Type implements client.Patch. Since this patch is used for a custom CRD, Kubernetes does not allow the more advanced -// StrategicMergePatch. Therefore, this patcher will replace the entire list of finalizers with the new list, rather -// than adding/removing individual entries. -// -// This can result in a small race condition where we could overwrite recently modified finalizers (either modified by a -// user or another controller process). Before the addition of this finalizer patcher implementation, this race -// condition still existed, but applied to the entirety of the CRD because we used to update the entire CRD rather than -// just the finalizer, so this reduces the surface area of the race condition. Generally we should not expect users or -// other configentries to be touching the finalizers of consul-k8s managed CRDs. -func (fp *FinalizerPatch) Type() types.PatchType { - return types.MergePatchType -} - -var _ client.Patch = (*FinalizerPatch)(nil) - -func (f *FinalizerPatcher) AddFinalizersPatch(oldObj client.Object, addFinalizers ...string) *FinalizerPatch { - output := make([]string, 0, len(addFinalizers)) - existing := make(map[string]bool) - for _, f := range oldObj.GetFinalizers() { - existing[f] = true - output = append(output, f) - } - for _, f := range addFinalizers { - if !existing[f] { - output = append(output, f) - } - } - return &FinalizerPatch{ - NewFinalizers: output, - } -} - -func (f *FinalizerPatcher) RemoveFinalizersPatch(oldObj client.Object, removeFinalizers ...string) *FinalizerPatch { - output := make([]string, 0) - remove := make(map[string]bool) - for _, f := range removeFinalizers { - remove[f] = true - } - for _, f := range oldObj.GetFinalizers() { - if !remove[f] { - output = append(output, f) - } - } - return &FinalizerPatch{ - NewFinalizers: output, - } -} - -// Data implements client.Patch. -func (fp *FinalizerPatch) Data(obj client.Object) ([]byte, error) { - newData, err := json.Marshal(map[string]any{ - "metadata": map[string]any{ - "finalizers": fp.NewFinalizers, - }, - }) - if err != nil { - return nil, err - } - - p, err := jsonpatch.CreateMergePatch([]byte(`{}`), newData) - return p, err -} diff --git a/control-plane/controllers/configentries/finalizer_patch_test.go b/control-plane/controllers/configentries/finalizer_patch_test.go deleted file mode 100644 index 70dc782d0e..0000000000 --- a/control-plane/controllers/configentries/finalizer_patch_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries - -import ( - "testing" - - "github.com/stretchr/testify/require" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -func TestFinalizersPatcher(t *testing.T) { - cases := []struct { - name string - oldObject client.Object - addFinalizers []string - removeFinalizers []string - expectedFinalizerPatch *FinalizerPatch - op string - }{ - { - name: "adds finalizers at the end and keeps the original list in order", - oldObject: &v1alpha1.ServiceResolver{ - ObjectMeta: v1.ObjectMeta{ - Finalizers: []string{ - "a", - "b", - "c", - }, - }, - }, - addFinalizers: []string{"d", "e"}, - expectedFinalizerPatch: &FinalizerPatch{ - NewFinalizers: []string{"a", "b", "c", "d", "e"}, - }, - }, - { - name: "adds finalizers when original list is empty", - oldObject: &v1alpha1.ServiceResolver{ - ObjectMeta: v1.ObjectMeta{ - Finalizers: []string{}, - }, - }, - addFinalizers: []string{"d", "e"}, - expectedFinalizerPatch: &FinalizerPatch{ - NewFinalizers: []string{"d", "e"}, - }, - }, - { - name: "removes finalizers keeping the original list in order", - oldObject: &v1alpha1.ServiceResolver{ - ObjectMeta: v1.ObjectMeta{ - Finalizers: []string{ - "a", - "b", - "c", - "d", - }, - }, - }, - removeFinalizers: []string{"b"}, - expectedFinalizerPatch: &FinalizerPatch{ - NewFinalizers: []string{"a", "c", "d"}, - }, - }, - { - name: "removes all finalizers if specified", - oldObject: &v1alpha1.ServiceResolver{ - ObjectMeta: v1.ObjectMeta{ - Finalizers: []string{ - "a", - "b", - }, - }, - }, - removeFinalizers: []string{"a", "b"}, - expectedFinalizerPatch: &FinalizerPatch{ - NewFinalizers: []string{}, - }, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - f := FinalizerPatcher{} - var patch *FinalizerPatch - - if len(c.addFinalizers) > 0 { - patch = f.AddFinalizersPatch(c.oldObject, c.addFinalizers...) - } else if len(c.removeFinalizers) > 0 { - patch = f.RemoveFinalizersPatch(c.oldObject, c.removeFinalizers...) - } - - require.Equal(t, c.expectedFinalizerPatch, patch) - }) - } -} diff --git a/control-plane/controllers/configentries/jwtprovider_controller.go b/control-plane/controllers/configentries/jwtprovider_controller.go deleted file mode 100644 index 6e4aa6c6d1..0000000000 --- a/control-plane/controllers/configentries/jwtprovider_controller.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -var _ Controller = (*JWTProviderController)(nil) - -// JWTProviderController reconciles a JWTProvider object. -type JWTProviderController struct { - client.Client - FinalizerPatcher - Log logr.Logger - Scheme *runtime.Scheme - ConfigEntryController *ConfigEntryController -} - -//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=jwtproviders,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=jwtproviders/status,verbs=get;update;patch - -func (r *JWTProviderController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.ConfigEntryController.ReconcileEntry(ctx, r, req, &consulv1alpha1.JWTProvider{}) -} - -func (r *JWTProviderController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *JWTProviderController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *JWTProviderController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &consulv1alpha1.JWTProvider{}, r) -} diff --git a/control-plane/controllers/configentries/samenessgroups_controller.go b/control-plane/controllers/configentries/samenessgroups_controller.go deleted file mode 100644 index 9a33744d7a..0000000000 --- a/control-plane/controllers/configentries/samenessgroups_controller.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configentries - -import ( - "context" - - "k8s.io/apimachinery/pkg/types" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - consulv1alpha1 "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -var _ Controller = (*SamenessGroupController)(nil) - -// SamenessGroupController reconciles a SamenessGroups object. -type SamenessGroupController struct { - client.Client - FinalizerPatcher - Log logr.Logger - Scheme *runtime.Scheme - ConfigEntryController *ConfigEntryController -} - -//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=samenessgroups,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=consul.hashicorp.com,resources=samenessgroups/status,verbs=get;update;patch - -func (r *SamenessGroupController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.ConfigEntryController.ReconcileEntry(ctx, r, req, &consulv1alpha1.SamenessGroup{}) -} - -func (r *SamenessGroupController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *SamenessGroupController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *SamenessGroupController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &consulv1alpha1.SamenessGroup{}, r) -} diff --git a/control-plane/controllers/resources/api-gateway-controller.go b/control-plane/controllers/resources/api-gateway-controller.go deleted file mode 100644 index 08c116499f..0000000000 --- a/control-plane/controllers/resources/api-gateway-controller.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// APIGatewayController reconciles a APIGateway object. -type APIGatewayController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute/status,verbs=get;update;patch - -func (r *APIGatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - r.Logger(req.NamespacedName).Info("Reconciling APIGateway") - return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.APIGateway{}) -} - -func (r *APIGatewayController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *APIGatewayController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *APIGatewayController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.APIGateway{}, r) -} diff --git a/control-plane/controllers/resources/consul_resource_controller.go b/control-plane/controllers/resources/consul_resource_controller.go deleted file mode 100644 index 95c5cbcac6..0000000000 --- a/control-plane/controllers/resources/consul_resource_controller.go +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - "fmt" - "time" - - "github.com/go-logr/logr" - "github.com/hashicorp/consul/proto-public/pbresource" - "golang.org/x/time/rate" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - corev1 "k8s.io/api/core/v1" - k8serr "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/workqueue" - "k8s.io/utils/strings/slices" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - tenancy "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - FinalizerName = "finalizers.consul.hashicorp.com" - ConsulAgentError = "ConsulAgentError" - ExternallyManagedConfigError = "ExternallyManagedConfigError" -) - -// ResourceController is implemented by resources syncing Consul Resources from their CRD counterparts. -// It is used by ConsulResourceController to abstract CRD-specific Consul Resources. -type ResourceController interface { - // Update updates the state of the whole object. - Update(context.Context, client.Object, ...client.UpdateOption) error - // UpdateStatus updates the state of just the object's status. - UpdateStatus(context.Context, client.Object, ...client.SubResourceUpdateOption) error - // Get retrieves an object for the given object key from the Kubernetes Cluster. - // obj must be a struct pointer so that obj can be updated with the response - // returned by the Server. - Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error - // Logger returns a logger with values added for the specific controller - // and request name. - Logger(types.NamespacedName) logr.Logger -} - -// ConsulResourceController is a generic controller that is used to reconcile -// all Consul Resource types, e.g. TrafficPermissions, ProxyConfiguration, etc., since -// they share the same reconcile behaviour. -type ConsulResourceController struct { - // ConsulClientConfig is the config for the Consul API client. - ConsulClientConfig *consul.Config - - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - - common.ConsulTenancyConfig -} - -// ReconcileResource reconciles an update to a resource. CRD-specific controller's -// call this function because it handles reconciliation of config entries -// generically. -// CRD-specific controller should pass themselves in as updater since we -// need to call back into their own update methods to ensure they update their -// internal state. -func (r *ConsulResourceController) ReconcileResource(ctx context.Context, crdCtrl ResourceController, req ctrl.Request, resource common.ConsulResource) (ctrl.Result, error) { - logger := crdCtrl.Logger(req.NamespacedName) - err := crdCtrl.Get(ctx, req.NamespacedName, resource) - if k8serr.IsNotFound(err) { - return ctrl.Result{}, client.IgnoreNotFound(err) - } else if err != nil { - logger.Error(err, "retrieving resource") - return ctrl.Result{}, err - } - - // Create Consul resource service client for this reconcile. - resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - logger.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - state, err := r.ConsulServerConnMgr.State() - if err != nil { - logger.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - if state.Token != "" { - ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) - } - - if resource.GetDeletionTimestamp().IsZero() { - // The object is not being deleted, so if it does not have our finalizer, - // then let's add the finalizer and update the object. This is equivalent - // registering our finalizer. - if !slices.Contains(resource.GetFinalizers(), FinalizerName) { - resource.AddFinalizer(FinalizerName) - if err := r.syncUnknown(ctx, crdCtrl, resource); err != nil { - return ctrl.Result{}, err - } - } - } - - if !resource.GetDeletionTimestamp().IsZero() { - if slices.Contains(resource.GetFinalizers(), FinalizerName) { - // The object is being deleted - logger.Info("deletion event") - // Check to see if consul has config entry with the same name - res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: resource.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - - // Ignore the error where the resource isn't found in Consul. - // It is indicative of desired state. - if err != nil && !isNotFoundErr(err) { - return ctrl.Result{}, fmt.Errorf("getting resource from Consul: %w", err) - } - - // In the case this resource was created outside of consul, skip the deletion process and continue - if !managedByConsulResourceController(res.GetResource()) { - logger.Info("resource in Consul was created outside of Kubernetes - skipping delete from Consul") - } - - if err == nil && managedByConsulResourceController(res.GetResource()) { - _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{Id: resource.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, resource, ConsulAgentError, - fmt.Errorf("deleting resource from Consul: %w", err)) - } - logger.Info("deletion from Consul successful") - } - // remove our finalizer from the list and update it. - resource.RemoveFinalizer(FinalizerName) - if err := crdCtrl.Update(ctx, resource); err != nil { - return ctrl.Result{}, err - } - logger.Info("finalizer removed") - } - - // Stop reconciliation as the item is being deleted - return ctrl.Result{}, nil - } - - // Check to see if consul has config entry with the same name - res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: resource.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - - // In the case the namespace doesn't exist in Consul yet, assume we are racing with the namespace controller - // and requeue. - if tenancy.ConsulNamespaceIsNotFound(err) { - logger.Info("Consul namespace not found; re-queueing request", - "name", req.Name, "ns", req.Namespace, "consul-ns", - r.consulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - - // If resource with this name does not exist - if isNotFoundErr(err) { - logger.Info("resource not found in Consul") - - // Create the config entry - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: resource.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, resource, ConsulAgentError, - fmt.Errorf("writing resource to Consul: %w", err)) - } - - logger.Info("resource created") - return r.syncSuccessful(ctx, crdCtrl, resource) - } - - // If there is an error when trying to get the resource from the api server, - // fail the reconcile. - if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, resource, ConsulAgentError, err) - } - - // TODO: consider the case where we want to migrate a resource existing into Consul to a CRD with an annotation - if !managedByConsulResourceController(res.Resource) { - return r.syncFailed(ctx, logger, crdCtrl, resource, ExternallyManagedConfigError, - fmt.Errorf("resource already exists in Consul")) - } - - if !resource.MatchesConsul(res.Resource, r.consulNamespace(req.Namespace), r.getConsulPartition()) { - logger.Info("resource does not match Consul") - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: resource.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - if err != nil { - return r.syncUnknownWithError(ctx, logger, crdCtrl, resource, ConsulAgentError, - fmt.Errorf("updating resource in Consul: %w", err)) - } - logger.Info("resource updated") - return r.syncSuccessful(ctx, crdCtrl, resource) - } else if resource.SyncedConditionStatus() != corev1.ConditionTrue { - return r.syncSuccessful(ctx, crdCtrl, resource) - } - - return ctrl.Result{}, nil -} - -// setupWithManager sets up the controller manager for the given resource -// with our default options. -func setupWithManager(mgr ctrl.Manager, resource client.Object, reconciler reconcile.Reconciler) error { - options := controller.Options{ - // Taken from https://github.com/kubernetes/client-go/blob/master/util/workqueue/default_rate_limiters.go#L39 - // and modified from a starting backoff of 5ms and max of 1000s to a - // starting backoff of 200ms and a max of 5s to better fit our most - // common error cases and performance characteristics. - // - // One common error case is that a resource is applied that requires - // a protocol like http or grpc. Often the user will apply a new resource - // to set the protocol in a minute or two. During this time, the - // default backoff could then be set up to 5m or more which means the - // original resource takes a long time to re-sync. - // - // In terms of performance, Consul servers can handle tens of thousands - // of writes per second, so retrying at max every 5s isn't an issue and - // provides a better UX. - RateLimiter: workqueue.NewMaxOfRateLimiter( - workqueue.NewItemExponentialFailureRateLimiter(200*time.Millisecond, 5*time.Second), - // 10 qps, 100 bucket size. This is only for retry speed, and it's only the overall factor (not per item) - &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)}, - ), - } - - return ctrl.NewControllerManagedBy(mgr). - For(resource). - WithOptions(options). - Complete(reconciler) -} - -func (r *ConsulResourceController) syncFailed(ctx context.Context, logger logr.Logger, updater ResourceController, resource common.ConsulResource, errType string, err error) (ctrl.Result, error) { - resource.SetSyncedCondition(corev1.ConditionFalse, errType, err.Error()) - if updateErr := updater.UpdateStatus(ctx, resource); updateErr != nil { - // Log the original error here because we are returning the updateErr. - // Otherwise, the original error would be lost. - logger.Error(err, "sync failed") - return ctrl.Result{}, updateErr - } - return ctrl.Result{}, err -} - -func (r *ConsulResourceController) syncSuccessful(ctx context.Context, updater ResourceController, resource common.ConsulResource) (ctrl.Result, error) { - resource.SetSyncedCondition(corev1.ConditionTrue, "", "") - timeNow := metav1.NewTime(time.Now()) - resource.SetLastSyncedTime(&timeNow) - return ctrl.Result{}, updater.UpdateStatus(ctx, resource) -} - -func (r *ConsulResourceController) syncUnknown(ctx context.Context, updater ResourceController, resource common.ConsulResource) error { - resource.SetSyncedCondition(corev1.ConditionUnknown, "", "") - return updater.Update(ctx, resource) -} - -func (r *ConsulResourceController) syncUnknownWithError(ctx context.Context, - logger logr.Logger, - updater ResourceController, - resource common.ConsulResource, - errType string, - err error, -) (ctrl.Result, error) { - resource.SetSyncedCondition(corev1.ConditionUnknown, errType, err.Error()) - if updateErr := updater.UpdateStatus(ctx, resource); updateErr != nil { - // Log the original error here because we are returning the updateErr. - // Otherwise, the original error would be lost. - logger.Error(err, "sync status unknown") - return ctrl.Result{}, updateErr - } - return ctrl.Result{}, err -} - -// isNotFoundErr checks the grpc response code for "NotFound". -func isNotFoundErr(err error) bool { - if err == nil { - return false - } - s, ok := status.FromError(err) - if !ok { - return false - } - return codes.NotFound == s.Code() -} - -func (r *ConsulResourceController) consulNamespace(namespace string) string { - ns := namespaces.ConsulNamespace( - namespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO: remove this if and when the default namespace of resources is no longer required to be set explicitly. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} - -func (r *ConsulResourceController) getConsulPartition() string { - if !r.EnableConsulPartitions || r.ConsulPartition == "" { - return constants.DefaultConsulPartition - } - return r.ConsulPartition -} - -func managedByConsulResourceController(resource *pbresource.Resource) bool { - if resource == nil { - return false - } - - consulMeta := resource.GetMetadata() - if consulMeta == nil { - return false - } - - if val, ok := consulMeta[common.SourceKey]; ok && val == common.SourceValue { - return true - } - return false -} diff --git a/control-plane/controllers/resources/consul_resource_controller_ent_test.go b/control-plane/controllers/resources/consul_resource_controller_ent_test.go deleted file mode 100644 index 56cb6c9b49..0000000000 --- a/control-plane/controllers/resources/consul_resource_controller_ent_test.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package resources - -import ( - "context" - "testing" - - "github.com/go-logr/logr" - logrtest "github.com/go-logr/logr/testr" - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" -) - -// TestConsulResourceController_UpdatesConsulResourceEnt tests is a mirror of the CE test which also tests the -// enterprise traffic permissions deny action. -func TestConsulResourceController_UpdatesConsulResourceEnt(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - resource common.ConsulResource - expected *pbauth.TrafficPermissions - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - updateF func(config common.ConsulResource) - unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message - }{ - { - name: "TrafficPermissions", - resource: &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-traffic-permission", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - }, - { - IdentityName: "source-identity", - }, - }, - // TODO: enable this when L7 traffic permissions are supported - //DestinationRules: []*pbauth.DestinationRule{ - // { - // PathExact: "/hello", - // Methods: []string{"GET", "POST"}, - // PortNames: []string{"web", "admin"}, - // }, - //}, - }, - }, - }, - }, - expected: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_DENY, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - //DestinationRules: []*pbauth.DestinationRule{ - // { - // PathExact: "/hello", - // Methods: []string{"GET", "POST"}, - // PortNames: []string{"web", "admin"}, - // }, - //}, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &TrafficPermissionsController{ - Client: client, - Log: logger, - Controller: &ConsulResourceController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - }, - } - }, - updateF: func(resource common.ConsulResource) { - trafficPermissions := resource.(*v2beta1.TrafficPermissions) - trafficPermissions.Spec.Action = pbauth.Action_ACTION_DENY - trafficPermissions.Spec.Permissions[0].Sources = trafficPermissions.Spec.Permissions[0].Sources[:1] - }, - unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { - data := resource.Data - - trafficPermission := &pbauth.TrafficPermissions{} - require.NoError(t, data.UnmarshalTo(trafficPermission)) - return trafficPermission - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, c.resource) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.resource).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // We haven't run reconcile yet, so we must create the resource - // in Consul ourselves. - { - resource := c.resource.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - req := &pbresource.WriteRequest{Resource: resource} - _, err := testClient.ResourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile which should update the entry in Consul. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: c.resource.KubernetesName(), - } - // First get it, so we have the latest revision number. - err := fakeClient.Get(ctx, namespacedName, c.resource) - require.NoError(t, err) - - // Update the entry in Kube and run reconcile. - c.updateF(c.resource) - err = fakeClient.Update(ctx, c.resource) - require.NoError(t, err) - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - res, err := testClient.ResourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, c.resource.GetName(), res.GetResource().GetId().GetName()) - - actual := c.unmarshal(t, res.GetResource()) - opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(c.expected, actual, opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - } - }) - } -} diff --git a/control-plane/controllers/resources/consul_resource_controller_test.go b/control-plane/controllers/resources/consul_resource_controller_test.go deleted file mode 100644 index cb8c1cf6bd..0000000000 --- a/control-plane/controllers/resources/consul_resource_controller_test.go +++ /dev/null @@ -1,770 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - "testing" - "time" - - "github.com/go-logr/logr" - logrtest "github.com/go-logr/logr/testr" - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - - "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -type testReconciler interface { - Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) -} - -// TestConsulResourceController_CreatesConsulResource validated resources are created in Consul from kube objects. -func TestConsulResourceController_CreatesConsulResource(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - resource common.ConsulResource - expected *pbauth.TrafficPermissions - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message - }{ - { - name: "TrafficPermissions", - resource: &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-traffic-permission", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - }, - { - IdentityName: "source-identity", - }, - }, - // TODO: enable this when L7 traffic permissions are supported - //DestinationRules: []*pbauth.DestinationRule{ - // { - // PathExact: "/hello", - // Methods: []string{"GET", "POST"}, - // PortNames: []string{"web", "admin"}, - // }, - //}, - }, - }, - }, - }, - expected: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: common.DefaultConsulNamespace, - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - { - Namespace: "the space namespace space", - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - //DestinationRules: []*pbauth.DestinationRule{ - // { - // PathExact: "/hello", - // Methods: []string{"GET", "POST"}, - // PortNames: []string{"web", "admin"}, - // }, - //}, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &TrafficPermissionsController{ - Client: client, - Log: logger, - Controller: &ConsulResourceController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - }, - } - }, - unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { - data := resource.Data - - trafficPermission := &pbauth.TrafficPermissions{} - require.NoError(t, data.UnmarshalTo(trafficPermission)) - return trafficPermission - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v2beta1.AuthGroupVersion, c.resource) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.resource).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: c.resource.KubernetesName(), - } - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - res, err := testClient.ResourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, c.resource.GetName(), res.GetResource().GetId().GetName()) - - actual := c.unmarshal(t, res.GetResource()) - opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(c.expected, actual, opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - - // Check that the status is "synced". - err = fakeClient.Get(ctx, namespacedName, c.resource) - require.NoError(t, err) - require.Equal(t, corev1.ConditionTrue, c.resource.SyncedConditionStatus()) - - // Check that the finalizer is added. - require.Contains(t, c.resource.Finalizers(), FinalizerName) - }) - } -} - -func TestConsulResourceController_UpdatesConsulResource(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - resource common.ConsulResource - expected *pbauth.TrafficPermissions - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - updateF func(config common.ConsulResource) - unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message - }{ - { - name: "TrafficPermissions", - resource: &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-traffic-permission", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - }, - { - IdentityName: "source-identity", - }, - }, - // TODO: enable this when L7 traffic permissions are supported - //DestinationRules: []*pbauth.DestinationRule{ - // { - // PathExact: "/hello", - // Methods: []string{"GET", "POST"}, - // PortNames: []string{"web", "admin"}, - // }, - //}, - }, - }, - }, - }, - expected: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - //DestinationRules: []*pbauth.DestinationRule{ - // { - // PathExact: "/hello", - // Methods: []string{"GET", "POST"}, - // PortNames: []string{"web", "admin"}, - // }, - //}, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &TrafficPermissionsController{ - Client: client, - Log: logger, - Controller: &ConsulResourceController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - }, - } - }, - updateF: func(resource common.ConsulResource) { - trafficPermissions := resource.(*v2beta1.TrafficPermissions) - trafficPermissions.Spec.Permissions[0].Sources = trafficPermissions.Spec.Permissions[0].Sources[:1] - }, - unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { - data := resource.Data - - trafficPermission := &pbauth.TrafficPermissions{} - require.NoError(t, data.UnmarshalTo(trafficPermission)) - return trafficPermission - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, c.resource) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.resource).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // We haven't run reconcile yet, so we must create the resource - // in Consul ourselves. - { - resource := c.resource.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - req := &pbresource.WriteRequest{Resource: resource} - _, err := testClient.ResourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile which should update the entry in Consul. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: c.resource.KubernetesName(), - } - // First get it, so we have the latest revision number. - err := fakeClient.Get(ctx, namespacedName, c.resource) - require.NoError(t, err) - - // Update the entry in Kube and run reconcile. - c.updateF(c.resource) - err = fakeClient.Update(ctx, c.resource) - require.NoError(t, err) - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - res, err := testClient.ResourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, c.resource.GetName(), res.GetResource().GetId().GetName()) - - actual := c.unmarshal(t, res.GetResource()) - opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(c.expected, actual, opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - } - }) - } -} - -func TestConsulResourceController_DeletesConsulResource(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - resource common.ConsulResource - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - }{ - { - name: "TrafficPermissions", - resource: &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-name", - Namespace: metav1.NamespaceDefault, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{FinalizerName}, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - }, - { - IdentityName: "source-identity", - }, - }, - // TODO: enable this when L7 traffic permissions are supported - //DestinationRules: []*pbauth.DestinationRule{ - // { - // PathExact: "/hello", - // Methods: []string{"GET", "POST"}, - // PortNames: []string{"web", "admin"}, - // }, - //}, - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &TrafficPermissionsController{ - Client: client, - Log: logger, - Controller: &ConsulResourceController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - }, - } - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v2beta1.AuthGroupVersion, c.resource) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.resource).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // We haven't run reconcile yet, so we must create the config entry - // in Consul ourselves. - { - resource := c.resource.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - req := &pbresource.WriteRequest{Resource: resource} - _, err := testClient.ResourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile. It's marked for deletion so this should delete it. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: c.resource.KubernetesName(), - } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) - resp, err := r.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: c.resource.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - _, err = testClient.ResourceClient.Read(ctx, req) - require.Error(t, err) - require.True(t, isNotFoundErr(err)) - } - }) - } -} - -func TestConsulResourceController_ErrorUpdatesSyncStatus(t *testing.T) { - t.Parallel() - - ctx := context.Background() - trafficpermissions := &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - }, - }, - }, - }, - }, - } - - s := runtime.NewScheme() - s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Stop the server before calling reconcile imitating a server that's not running. - _ = testClient.TestServer.Stop() - - reconciler := &TrafficPermissionsController{ - Client: fakeClient, - Log: logrtest.New(t), - Controller: &ConsulResourceController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - - // ReconcileResource should result in an error. - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: trafficpermissions.KubernetesName(), - } - resp, err := reconciler.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.Error(t, err) - require.False(t, resp.Requeue) - actualErrMsg := err.Error() - - // Check that the status is "synced=false". - err = fakeClient.Get(ctx, namespacedName, trafficpermissions) - require.NoError(t, err) - status, reason, errMsg := trafficpermissions.SyncedCondition() - require.Equal(t, corev1.ConditionFalse, status) - require.Equal(t, "ConsulAgentError", reason) - require.Contains(t, errMsg, actualErrMsg) -} - -// TestConsulResourceController_SetsSyncedToTrue tests that if the resource hasn't changed in -// Consul but our resource's synced status isn't set to true, then we update its status. -func TestConsulResourceController_SetsSyncedToTrue(t *testing.T) { - t.Parallel() - - ctx := context.Background() - s := runtime.NewScheme() - - trafficpermissions := &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - }, - }, - }, - }, - }, - Status: v2beta1.Status{ - Conditions: v2beta1.Conditions{ - { - Type: v2beta1.ConditionSynced, - Status: corev1.ConditionUnknown, - }, - }, - }, - } - s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) - - // The config entry exists in kube but its status will be nil. - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - reconciler := &TrafficPermissionsController{ - Client: fakeClient, - Log: logrtest.New(t), - Controller: &ConsulResourceController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - - // Create the resource in Consul to mimic that it was created - // successfully (but its status hasn't been updated). - { - resource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - req := &pbresource.WriteRequest{Resource: resource} - _, err := testClient.ResourceClient.Write(ctx, req) - require.NoError(t, err) - } - - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: trafficpermissions.KubernetesName(), - } - resp, err := reconciler.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Check that the status is now "synced". - err = fakeClient.Get(ctx, namespacedName, trafficpermissions) - require.NoError(t, err) - require.Equal(t, corev1.ConditionTrue, trafficpermissions.SyncedConditionStatus()) -} - -// TestConsulResourceController_DoesNotCreateUnownedResource test that if the resource -// exists in Consul but is not managed by the controller, creating/updating the resource fails. -func TestConsulResourceController_DoesNotCreateUnownedResource(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - s := runtime.NewScheme() - trafficpermissions := &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: common.DefaultConsulNamespace, - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - }, - }, - }, - } - s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - unmanagedResource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - unmanagedResource.Metadata = make(map[string]string) // Zero out the metadata - - // We haven't run reconcile yet. We must create the resource - // in Consul ourselves, without the metadata indicating it is owned by the controller. - { - req := &pbresource.WriteRequest{Resource: unmanagedResource} - _, err := testClient.ResourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile which should **not** update the entry in Consul. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: trafficpermissions.KubernetesName(), - } - // First get it, so we have the latest revision number. - err := fakeClient.Get(ctx, namespacedName, trafficpermissions) - require.NoError(t, err) - - // Attempt to create the entry in Kube and run reconcile. - reconciler := TrafficPermissionsController{ - Client: fakeClient, - Log: logrtest.New(t), - Controller: &ConsulResourceController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - resp, err := reconciler.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.EqualError(t, err, "resource already exists in Consul") - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: trafficpermissions.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - readResp, err := testClient.ResourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, readResp.GetResource()) - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid")}, - test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(unmanagedResource, readResp.GetResource(), opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - - // Check that the status is "synced=false". - err = fakeClient.Get(ctx, namespacedName, trafficpermissions) - require.NoError(t, err) - status, reason, errMsg := trafficpermissions.SyncedCondition() - require.Equal(t, corev1.ConditionFalse, status) - require.Equal(t, "ExternallyManagedConfigError", reason) - require.Equal(t, errMsg, "resource already exists in Consul") - } - -} - -// TestConsulResourceController_doesNotDeleteUnownedConfig tests that if the resource -// exists in Consul but is not managed by the controller, deleting the resource does -// not delete the Consul resource. -func TestConsulResourceController_doesNotDeleteUnownedConfig(t *testing.T) { - t.Parallel() - - ctx := context.Background() - s := runtime.NewScheme() - - trafficpermissionsWithDeletion := &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{FinalizerName}, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: common.DefaultConsulNamespace, - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - }, - }, - }, - } - s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissionsWithDeletion) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissionsWithDeletion).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - reconciler := &TrafficPermissionsController{ - Client: fakeClient, - Log: logrtest.New(t), - Controller: &ConsulResourceController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - - unmanagedResource := trafficpermissionsWithDeletion.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - unmanagedResource.Metadata = make(map[string]string) // Zero out the metadata - - // We haven't run reconcile yet. We must create the resource - // in Consul ourselves, without the metadata indicating it is owned by the controller. - { - req := &pbresource.WriteRequest{Resource: unmanagedResource} - _, err := testClient.ResourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile. It's marked for deletion so this should delete the kubernetes resource - // but not the consul config entry. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: trafficpermissionsWithDeletion.KubernetesName(), - } - resp, err := reconciler.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: trafficpermissionsWithDeletion.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - readResp, err := testClient.ResourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, readResp.GetResource()) - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid")}, - test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(unmanagedResource, readResp.GetResource(), opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - - // Check that the resource is deleted from cluster. - tp := &v2beta1.TrafficPermissions{} - _ = fakeClient.Get(ctx, namespacedName, tp) - require.Empty(t, tp.Finalizers()) - } -} diff --git a/control-plane/controllers/resources/exported_services_controller.go b/control-plane/controllers/resources/exported_services_controller.go deleted file mode 100644 index 30197f5b85..0000000000 --- a/control-plane/controllers/resources/exported_services_controller.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - multiclusterv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/multicluster/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/gateways" -) - -// ExportedServicesController reconciles a MeshGateway object. -type ExportedServicesController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController - GatewayConfig gateways.GatewayConfig -} - -// +kubebuilder:rbac:groups=multicluster.consul.hashicorp.com,resources=exportedservices,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=multicluster.consul.hashicorp.com,resources=exportedservices/status,verbs=get;update;patch - -func (r *ExportedServicesController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileResource(ctx, r, req, &multiclusterv2beta1.ExportedServices{}) -} - -func (r *ExportedServicesController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *ExportedServicesController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *ExportedServicesController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &multiclusterv2beta1.ExportedServices{}, r) -} diff --git a/control-plane/controllers/resources/gateway_class_config_controller.go b/control-plane/controllers/resources/gateway_class_config_controller.go deleted file mode 100644 index 56ade8161a..0000000000 --- a/control-plane/controllers/resources/gateway_class_config_controller.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// GatewayClassConfigController reconciles a GatewayClassConfig object. -type GatewayClassConfigController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclassconfig,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclassconfig/status,verbs=get;update;patch - -func (r *GatewayClassConfigController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - // GatewayClassConfig is not synced into Consul because Consul has no use for it. - // Consul is only aware of the resource for the sake of Kubernetes CRD generation. - return ctrl.Result{}, nil -} - -func (r *GatewayClassConfigController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *GatewayClassConfigController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *GatewayClassConfigController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.GatewayClassConfig{}, r) -} diff --git a/control-plane/controllers/resources/gateway_class_controller.go b/control-plane/controllers/resources/gateway_class_controller.go deleted file mode 100644 index 23f7f0f4db..0000000000 --- a/control-plane/controllers/resources/gateway_class_controller.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// GatewayClassController reconciles a MeshGateway object. -type GatewayClassController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclass,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=gatewayclass/status,verbs=get;update;patch - -func (r *GatewayClassController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - // GatewayClass is not synced into Consul because Consul has no use for it. - // Consul is only aware of the resource for the sake of Kubernetes CRD generation. - return ctrl.Result{}, nil -} - -func (r *GatewayClassController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *GatewayClassController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *GatewayClassController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.GatewayClass{}, r) -} diff --git a/control-plane/controllers/resources/grpc_route_controller.go b/control-plane/controllers/resources/grpc_route_controller.go deleted file mode 100644 index fa5401c800..0000000000 --- a/control-plane/controllers/resources/grpc_route_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// GRPCRouteController reconciles a GRPCRoute object. -type GRPCRouteController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute/status,verbs=get;update;patch - -func (r *GRPCRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.GRPCRoute{}) -} - -func (r *GRPCRouteController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *GRPCRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *GRPCRouteController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.GRPCRoute{}, r) -} diff --git a/control-plane/controllers/resources/http_route_controller.go b/control-plane/controllers/resources/http_route_controller.go deleted file mode 100644 index 9275d8f265..0000000000 --- a/control-plane/controllers/resources/http_route_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// HTTPRouteController reconciles a HTTPRoute object. -type HTTPRouteController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute/status,verbs=get;update;patch - -func (r *HTTPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.HTTPRoute{}) -} - -func (r *HTTPRouteController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *HTTPRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *HTTPRouteController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.HTTPRoute{}, r) -} diff --git a/control-plane/controllers/resources/mesh_configuration_controller.go b/control-plane/controllers/resources/mesh_configuration_controller.go deleted file mode 100644 index d5813294e9..0000000000 --- a/control-plane/controllers/resources/mesh_configuration_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// MeshConfigurationController reconciles a MeshConfiguration object. -type MeshConfigurationController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshconfiguration,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshconfiguration/status,verbs=get;update;patch - -func (r *MeshConfigurationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.MeshConfiguration{}) -} - -func (r *MeshConfigurationController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *MeshConfigurationController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *MeshConfigurationController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.MeshConfiguration{}, r) -} diff --git a/control-plane/controllers/resources/mesh_gateway_controller.go b/control-plane/controllers/resources/mesh_gateway_controller.go deleted file mode 100644 index 0e194a9701..0000000000 --- a/control-plane/controllers/resources/mesh_gateway_controller.go +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - "errors" - "fmt" - - "github.com/go-logr/logr" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/equality" - k8serr "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/gateways" -) - -// errResourceNotOwned indicates that a resource the controller would have -// updated or deleted does not have an owner reference pointing to the MeshGateway. -var errResourceNotOwned = errors.New("existing resource not owned by controller") - -// MeshGatewayController reconciles a MeshGateway object. -type MeshGatewayController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController - GatewayConfig gateways.GatewayConfig -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshgateway,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=meshgateway/status,verbs=get;update;patch - -func (r *MeshGatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger(req.NamespacedName) - - // Fetch the resource being reconciled - resource := &meshv2beta1.MeshGateway{} - if err := r.Get(ctx, req.NamespacedName, resource); k8serr.IsNotFound(err) { - return ctrl.Result{}, client.IgnoreNotFound(err) - } else if err != nil { - logger.Error(err, "retrieving resource") - return ctrl.Result{}, err - } - - // Call hooks - if !resource.GetDeletionTimestamp().IsZero() { - logger.Info("deletion event") - - if err := r.onDelete(ctx, req, resource); err != nil { - return ctrl.Result{}, err - } - } else { - if err := r.onCreateUpdate(ctx, req, resource); err != nil { - return ctrl.Result{}, err - } - } - - return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.MeshGateway{}) -} - -func (r *MeshGatewayController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *MeshGatewayController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *MeshGatewayController) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&meshv2beta1.MeshGateway{}). - Owns(&appsv1.Deployment{}). - Owns(&rbacv1.Role{}). - Owns(&rbacv1.RoleBinding{}). - Owns(&corev1.Service{}). - Owns(&corev1.ServiceAccount{}). - Complete(r) -} - -// onCreateUpdate is responsible for creating/updating all K8s resources that -// are required in order to run a meshv2beta1.MeshGateway. These are created/updated -// in dependency order. -// 1. ServiceAccount -// 2. Deployment -// 3. Service -// 4. Role -// 5. RoleBinding -func (r *MeshGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { - // fetch gatewayclassconfig - gcc, err := r.getGatewayClassConfigForGateway(ctx, resource) - if err != nil { - r.Log.Error(err, "unable to get gatewayclassconfig for gateway: %s gatewayclass: %s", resource.Name, resource.Spec.GatewayClassName) - return err - } - - builder := gateways.NewMeshGatewayBuilder(resource, r.GatewayConfig, gcc) - - upsertOp := func(ctx context.Context, _, object client.Object) error { - _, err := controllerutil.CreateOrUpdate(ctx, r.Client, object, func() error { return nil }) - return err - } - - err = r.opIfNewOrOwned(ctx, resource, &corev1.ServiceAccount{}, builder.ServiceAccount(), upsertOp) - if err != nil { - return fmt.Errorf("unable to create service account: %w", err) - } - - // Create Role - - err = r.opIfNewOrOwned(ctx, resource, &rbacv1.Role{}, builder.Role(), upsertOp) - if err != nil { - return fmt.Errorf("unable to create role: %w", err) - } - - // Create RoleBinding - - err = r.opIfNewOrOwned(ctx, resource, &rbacv1.RoleBinding{}, builder.RoleBinding(), upsertOp) - if err != nil { - return fmt.Errorf("unable to create role binding: %w", err) - } - - // Create Service - - mergeServiceOp := func(ctx context.Context, existingObject, object client.Object) error { - existingService, ok := existingObject.(*corev1.Service) - if !ok && existingService != nil { - return fmt.Errorf("unable to infer existing service type") - } - builtService, ok := object.(*corev1.Service) - if !ok { - return fmt.Errorf("unable to infer built service type") - } - - mergedService := mergeService(existingService, builtService) - - _, err := controllerutil.CreateOrUpdate(ctx, r.Client, mergedService, func() error { return nil }) - return err - } - - err = r.opIfNewOrOwned(ctx, resource, &corev1.Service{}, builder.Service(), mergeServiceOp) - if err != nil { - return fmt.Errorf("unable to create service: %w", err) - } - - // Create deployment - - mergeDeploymentOp := func(ctx context.Context, existingObject, object client.Object) error { - existingDeployment, ok := existingObject.(*appsv1.Deployment) - if !ok && existingDeployment != nil { - return fmt.Errorf("unable to infer existing deployment type") - } - builtDeployment, ok := object.(*appsv1.Deployment) - if !ok { - return fmt.Errorf("unable to infer built deployment type") - } - - mergedDeployment := builder.MergeDeployments(gcc, existingDeployment, builtDeployment) - - _, err := controllerutil.CreateOrUpdate(ctx, r.Client, mergedDeployment, func() error { return nil }) - return err - } - - builtDeployment, err := builder.Deployment() - if err != nil { - return fmt.Errorf("unable to build deployment: %w", err) - } - - err = r.opIfNewOrOwned(ctx, resource, &appsv1.Deployment{}, builtDeployment, mergeDeploymentOp) - if err != nil { - return fmt.Errorf("unable to create deployment: %w", err) - } - - return nil -} - -// onDelete is responsible for cleaning up any side effects of onCreateUpdate. -// We only clean up side effects because all resources that we create explicitly -// have an owner reference and will thus be cleaned up by the K8s garbage collector -// once the owning meshv2beta1.MeshGateway is deleted. -func (r *MeshGatewayController) onDelete(ctx context.Context, req ctrl.Request, resource *meshv2beta1.MeshGateway) error { - // TODO NET-6392 NET-6393 - return nil -} - -// ownedObjectOp represents an operation that needs to be applied -// only if the newObject does not yet exist or if the existingObject -// has an owner reference pointing to the MeshGateway being reconciled. -// -// The existing and new object are available in case any merging needs -// to occur, such as unknown annotations and values from the existing object -// that need to be carried forward onto the new object. -type ownedObjectOp func(ctx context.Context, existingObject client.Object, newObject client.Object) error - -// opIfNewOrOwned runs a given ownedObjectOp to create, update, or delete a resource. -// The purpose of opIfNewOrOwned is to ensure that we aren't updating or deleting a -// resource that was not created by us. If this scenario is encountered, we error. -func (r *MeshGatewayController) opIfNewOrOwned(ctx context.Context, gateway *meshv2beta1.MeshGateway, scanTarget, writeSource client.Object, op ownedObjectOp) error { - // Ensure owner reference is always set on objects that we write - if err := ctrl.SetControllerReference(gateway, writeSource, r.Client.Scheme()); err != nil { - return err - } - - key := client.ObjectKey{ - Namespace: writeSource.GetNamespace(), - Name: writeSource.GetName(), - } - - exists := false - if err := r.Get(ctx, key, scanTarget); err != nil { - // We failed to fetch the object in a way that doesn't tell us about its existence - if !k8serr.IsNotFound(err) { - return err - } - } else { - // We successfully fetched the object, so it exists - exists = true - } - - // None exists, so we need only execute the operation - if !exists { - return op(ctx, nil, writeSource) - } - - // Ensure the existing object was put there by us so that we don't overwrite random objects - owned := false - for _, reference := range scanTarget.GetOwnerReferences() { - if reference.UID == gateway.GetUID() && reference.Name == gateway.GetName() { - owned = true - break - } - } - if !owned { - return errResourceNotOwned - } - return op(ctx, scanTarget, writeSource) -} - -func (r *MeshGatewayController) getGatewayClassConfigForGateway(ctx context.Context, gateway *meshv2beta1.MeshGateway) (*meshv2beta1.GatewayClassConfig, error) { - gatewayClass, err := r.getGatewayClassForGateway(ctx, gateway) - if err != nil { - return nil, err - } - - gatewayClassConfig, err := r.getGatewayClassConfigForGatewayClass(ctx, gatewayClass) - if err != nil { - return nil, err - } - - return gatewayClassConfig, nil -} - -func (r *MeshGatewayController) getGatewayClassConfigForGatewayClass(ctx context.Context, gatewayClass *meshv2beta1.GatewayClass) (*meshv2beta1.GatewayClassConfig, error) { - if gatewayClass == nil { - // if we don't have a gateway class we can't fetch the corresponding config - return nil, nil - } - - config := &meshv2beta1.GatewayClassConfig{} - if ref := gatewayClass.Spec.ParametersRef; ref != nil { - if ref.Group != meshv2beta1.MeshGroup || ref.Kind != "GatewayClassConfig" { - // TODO @Gateway-Management additionally check for controller name when available - return nil, nil - } - - if err := r.Client.Get(ctx, types.NamespacedName{Name: ref.Name}, config); err != nil { - return nil, client.IgnoreNotFound(err) - } - } - return config, nil -} - -func (r *MeshGatewayController) getGatewayClassForGateway(ctx context.Context, gateway *meshv2beta1.MeshGateway) (*meshv2beta1.GatewayClass, error) { - var gatewayClass meshv2beta1.GatewayClass - - if err := r.Client.Get(ctx, types.NamespacedName{Name: string(gateway.Spec.GatewayClassName)}, &gatewayClass); err != nil { - return nil, client.IgnoreNotFound(err) - } - return &gatewayClass, nil -} - -func areServicesEqual(a, b *corev1.Service) bool { - // If either service "a" or "b" is nil, don't want to try and merge the nil service - if a == nil || b == nil { - return true - } - - if !equality.Semantic.DeepEqual(a.Annotations, b.Annotations) { - return false - } - - if len(b.Spec.Ports) != len(a.Spec.Ports) { - return false - } - - for i, port := range a.Spec.Ports { - otherPort := b.Spec.Ports[i] - if port.Port != otherPort.Port || port.Protocol != otherPort.Protocol { - return false - } - } - return true -} - -// mergeService is used to keep annotations and ports from the `from` Service -// to the `to` service. This prevents an infinite reconciliation loop when -// Kubernetes adds this configuration back in. -func mergeService(from, to *corev1.Service) *corev1.Service { - if areServicesEqual(from, to) { - return to - } - - to.Annotations = from.Annotations - to.Spec.Ports = from.Spec.Ports - - return to -} diff --git a/control-plane/controllers/resources/mesh_gateway_controller_test.go b/control-plane/controllers/resources/mesh_gateway_controller_test.go deleted file mode 100644 index 63f38624f0..0000000000 --- a/control-plane/controllers/resources/mesh_gateway_controller_test.go +++ /dev/null @@ -1,601 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/sdk/testutil" - - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestMeshGatewayController_Reconcile(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - // k8sObjects is the list of Kubernetes resources that will be present in the cluster at runtime - k8sObjects []runtime.Object - // request is the request that will be provided to MeshGatewayController.Reconcile - request ctrl.Request - // expectedErr is the error we expect MeshGatewayController.Reconcile to return - expectedErr error - // expectedResult is the result we expect MeshGatewayController.Reconcile to return - expectedResult ctrl.Result - // postReconcile runs some set of assertions on the state of k8s after Reconcile is called - postReconcile func(*testing.T, client.Client) - }{ - // ServiceAccount - { - name: "MeshGateway created with no existing ServiceAccount", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "consul", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "consul", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - postReconcile: func(t *testing.T, c client.Client) { - // Verify ServiceAccount was created - key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} - assert.NoError(t, c.Get(context.Background(), key, &corev1.ServiceAccount{})) - }, - }, - { - name: "MeshGateway created with existing ServiceAccount not owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: errResourceNotOwned, - }, - // Role - { - name: "MeshGateway created with no existing Role", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "consul", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "consul", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - postReconcile: func(t *testing.T, c client.Client) { - // Verify Role was created - key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} - assert.NoError(t, c.Get(context.Background(), key, &rbacv1.Role{})) - }, - }, - { - name: "MeshGateway created with existing Role not owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: errResourceNotOwned, - }, - { - name: "MeshGateway created with existing Role owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - UID: "abc123", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - OwnerReferences: []metav1.OwnerReference{ - { - UID: "abc123", - Name: "mesh-gateway", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: nil, // The Reconcile should be a no-op - }, - // RoleBinding - { - name: "MeshGateway created with no existing RoleBinding", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "consul", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "consul", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - postReconcile: func(t *testing.T, c client.Client) { - // Verify RoleBinding was created - key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} - assert.NoError(t, c.Get(context.Background(), key, &rbacv1.RoleBinding{})) - }, - }, - { - name: "MeshGateway created with existing RoleBinding not owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: errResourceNotOwned, - }, - { - name: "MeshGateway created with existing RoleBinding owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - UID: "abc123", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - OwnerReferences: []metav1.OwnerReference{ - { - UID: "abc123", - Name: "mesh-gateway", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: nil, // The Reconcile should be a no-op - }, - // Deployment - { - name: "MeshGateway created with no existing Deployment", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "consul", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "consul", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - postReconcile: func(t *testing.T, c client.Client) { - // Verify Deployment was created - key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} - assert.NoError(t, c.Get(context.Background(), key, &appsv1.Deployment{})) - }, - }, - { - name: "MeshGateway created with existing Deployment not owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: errResourceNotOwned, - }, - { - name: "MeshGateway created with existing Deployment owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - UID: "abc123", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - OwnerReferences: []metav1.OwnerReference{ - { - UID: "abc123", - Name: "mesh-gateway", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: nil, // The Reconcile should be a no-op - }, - // Service - { - name: "MeshGateway created with no existing Service", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "consul", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "consul", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - postReconcile: func(t *testing.T, c client.Client) { - // Verify Service was created - key := client.ObjectKey{Namespace: "consul", Name: "mesh-gateway"} - assert.NoError(t, c.Get(context.Background(), key, &corev1.Service{})) - }, - }, - { - name: "MeshGateway created with existing Service not owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: errResourceNotOwned, - }, - { - name: "MeshGateway created with existing Service owned by gateway", - k8sObjects: []runtime.Object{ - &v2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - UID: "abc123", - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 8443, - Protocol: "tcp", - }, - }, - }, - }, - &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - OwnerReferences: []metav1.OwnerReference{ - { - UID: "abc123", - Name: "mesh-gateway", - }, - }, - }, - }, - }, - request: ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, - expectedResult: ctrl.Result{}, - expectedErr: nil, // The Reconcile should be a no-op - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - consulClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - s := runtime.NewScheme() - require.NoError(t, corev1.AddToScheme(s)) - require.NoError(t, appsv1.AddToScheme(s)) - require.NoError(t, rbacv1.AddToScheme(s)) - require.NoError(t, v2beta1.AddMeshToScheme(s)) - s.AddKnownTypes(v2beta1.MeshGroupVersion, &v2beta1.MeshGateway{}, &v2beta1.GatewayClass{}, &v2beta1.GatewayClassConfig{}) - fakeClient := fake.NewClientBuilder().WithScheme(s). - WithRuntimeObjects(testCase.k8sObjects...). - Build() - - controller := MeshGatewayController{ - Client: fakeClient, - Log: logrtest.New(t), - Scheme: s, - Controller: &ConsulResourceController{ - ConsulClientConfig: consulClient.Cfg, - ConsulServerConnMgr: consulClient.Watcher, - }, - } - - res, err := controller.Reconcile(context.Background(), testCase.request) - if testCase.expectedErr != nil { - // require.EqualError(t, err, testCase.expectedErr.Error()) - require.ErrorIs(t, err, testCase.expectedErr) - } else { - require.NoError(t, err) - } - assert.Equal(t, testCase.expectedResult, res) - - if testCase.postReconcile != nil { - testCase.postReconcile(t, fakeClient) - } - }) - } -} diff --git a/control-plane/controllers/resources/proxy_configuration_controller.go b/control-plane/controllers/resources/proxy_configuration_controller.go deleted file mode 100644 index 7f67afe26a..0000000000 --- a/control-plane/controllers/resources/proxy_configuration_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// ProxyConfigurationController reconciles a ProxyConfiguration object. -type ProxyConfigurationController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration/status,verbs=get;update;patch - -func (r *ProxyConfigurationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.ProxyConfiguration{}) -} - -func (r *ProxyConfigurationController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *ProxyConfigurationController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *ProxyConfigurationController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.ProxyConfiguration{}, r) -} diff --git a/control-plane/controllers/resources/tcp_route_controller.go b/control-plane/controllers/resources/tcp_route_controller.go deleted file mode 100644 index dc69f879b2..0000000000 --- a/control-plane/controllers/resources/tcp_route_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// TCPRouteController reconciles a TCPRoute object. -type TCPRouteController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute/status,verbs=get;update;patch - -func (r *TCPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.TCPRoute{}) -} - -func (r *TCPRouteController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *TCPRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *TCPRouteController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.TCPRoute{}, r) -} diff --git a/control-plane/controllers/resources/traffic_permissions_controller.go b/control-plane/controllers/resources/traffic_permissions_controller.go deleted file mode 100644 index f844473b0c..0000000000 --- a/control-plane/controllers/resources/traffic_permissions_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - consulv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" -) - -// TrafficPermissionsController reconciles a TrafficPermissions object. -type TrafficPermissionsController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController -} - -// +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions/status,verbs=get;update;patch - -func (r *TrafficPermissionsController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.Controller.ReconcileResource(ctx, r, req, &consulv2beta1.TrafficPermissions{}) -} - -func (r *TrafficPermissionsController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *TrafficPermissionsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *TrafficPermissionsController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &consulv2beta1.TrafficPermissions{}, r) -} diff --git a/control-plane/gateways/builder.go b/control-plane/gateways/builder.go deleted file mode 100644 index e43e6dd890..0000000000 --- a/control-plane/gateways/builder.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// meshGatewayBuilder is a helper struct for building the Kubernetes resources for a mesh gateway. -// This includes Deployment, Role, Service, and ServiceAccount resources. -// Configuration is combined from the MeshGateway, GatewayConfig, and GatewayClassConfig. -type meshGatewayBuilder struct { - gateway *meshv2beta1.MeshGateway - config GatewayConfig - gcc *meshv2beta1.GatewayClassConfig -} - -// NewMeshGatewayBuilder returns a new meshGatewayBuilder for the given MeshGateway, -// GatewayConfig, and GatewayClassConfig. -func NewMeshGatewayBuilder(gateway *meshv2beta1.MeshGateway, gatewayConfig GatewayConfig, gatewayClassConfig *meshv2beta1.GatewayClassConfig) *meshGatewayBuilder { - return &meshGatewayBuilder{ - gateway: gateway, - config: gatewayConfig, - gcc: gatewayClassConfig, - } -} diff --git a/control-plane/gateways/constants.go b/control-plane/gateways/constants.go deleted file mode 100644 index ac0242bd2d..0000000000 --- a/control-plane/gateways/constants.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -const ( - // General environment variables. - envPodName = "POD_NAME" - envPodNamespace = "POD_NAMESPACE" - envNodeName = "NODE_NAME" - envTmpDir = "TMPDIR" - - // Dataplane Configuration Environment variables. - envDPProxyId = "DP_PROXY_ID" - envDPCredentialLoginMeta = "DP_CREDENTIAL_LOGIN_META" - - // Init Container Configuration Environment variables. - envConsulAddresses = "CONSUL_ADDRESSES" - envConsulGRPCPort = "CONSUL_GRPC_PORT" - envConsulHTTPPort = "CONSUL_HTTP_PORT" - envConsulAPITimeout = "CONSUL_API_TIMEOUT" - envConsulNodeName = "CONSUL_NODE_NAME" - envConsulLoginAuthMethod = "CONSUL_LOGIN_AUTH_METHOD" - envConsulLoginBearerTokenFile = "CONSUL_LOGIN_BEARER_TOKEN_FILE" - envConsulLoginMeta = "CONSUL_LOGIN_META" - envConsulLoginPartition = "CONSUL_LOGIN_PARTITION" - envConsulNamespace = "CONSUL_NAMESPACE" - envConsulPartition = "CONSUL_PARTITION" - - // defaultBearerTokenFile is the default location where the init container will store the bearer token for the dataplane container to read. - defaultBearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" -) diff --git a/control-plane/gateways/deployment.go b/control-plane/gateways/deployment.go deleted file mode 100644 index bf944503b8..0000000000 --- a/control-plane/gateways/deployment.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - globalDefaultInstances int32 = 1 - meshGatewayAnnotationKind = "mesh-gateway" -) - -func (b *meshGatewayBuilder) Deployment() (*appsv1.Deployment, error) { - spec, err := b.deploymentSpec() - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.Name, - Namespace: b.gateway.Namespace, - Labels: b.labelsForDeployment(), - Annotations: b.annotationsForDeployment(), - }, - Spec: *spec, - }, err -} - -func (b *meshGatewayBuilder) deploymentSpec() (*appsv1.DeploymentSpec, error) { - var ( - deploymentConfig meshv2beta1.GatewayClassDeploymentConfig - containerConfig meshv2beta1.GatewayClassContainerConfig - ) - - // If GatewayClassConfig is not nil, use it to override the defaults for - // the deployment and container configs. - if b.gcc != nil { - deploymentConfig = b.gcc.Spec.Deployment - if deploymentConfig.Container != nil { - containerConfig = *b.gcc.Spec.Deployment.Container - } - } - - initContainer, err := b.initContainer() - if err != nil { - return nil, err - } - - container, err := b.consulDataplaneContainer(containerConfig) - if err != nil { - return nil, err - } - - return &appsv1.DeploymentSpec{ - Replicas: deploymentReplicaCount(deploymentConfig.Replicas, nil), - Selector: &metav1.LabelSelector{ - MatchLabels: b.labelsForDeployment(), - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: b.labelsForDeployment(), - Annotations: map[string]string{ - // Indicate that this pod is a mesh gateway pod so that the Pod controller, - // consul-k8s CLI, etc. can key off of it - constants.AnnotationGatewayKind: meshGatewayAnnotationKind, - // It's not logical to add a proxy sidecar since our workload is itself a proxy - constants.AnnotationMeshInject: "false", - // This functionality only applies when proxy sidecars are used - constants.AnnotationTransparentProxyOverwriteProbes: "false", - // This annotation determines which source to use to set the - // WAN address and WAN port for the Mesh Gateway service registration. - constants.AnnotationGatewayWANSource: b.gateway.Annotations[constants.AnnotationGatewayWANSource], - // This annotation determines the WAN port for the Mesh Gateway service registration. - constants.AnnotationGatewayWANPort: b.gateway.Annotations[constants.AnnotationGatewayWANPort], - // This annotation determines the address for the gateway when the source annotation is "Static". - constants.AnnotationGatewayWANAddress: b.gateway.Annotations[constants.AnnotationGatewayWANAddress], - }, - }, - Spec: corev1.PodSpec{ - Volumes: []corev1.Volume{ - { - Name: volumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, - }, - }, - }, - InitContainers: []corev1.Container{ - initContainer, - }, - Containers: []corev1.Container{ - container, - }, - Affinity: deploymentConfig.Affinity, - NodeSelector: deploymentConfig.NodeSelector, - PriorityClassName: deploymentConfig.PriorityClassName, - TopologySpreadConstraints: deploymentConfig.TopologySpreadConstraints, - HostNetwork: deploymentConfig.HostNetwork, - Tolerations: deploymentConfig.Tolerations, - ServiceAccountName: b.serviceAccountName(), - DNSPolicy: deploymentConfig.DNSPolicy, - }, - }, - }, nil -} - -func (b *meshGatewayBuilder) MergeDeployments(gcc *meshv2beta1.GatewayClassConfig, old, new *appsv1.Deployment) *appsv1.Deployment { - if old == nil { - return new - } - if !compareDeployments(old, new) { - old.Spec.Template = new.Spec.Template - new.Spec.Replicas = deploymentReplicaCount(nil, old.Spec.Replicas) - } - - return new -} - -func compareDeployments(a, b *appsv1.Deployment) bool { - // since K8s adds a bunch of defaults when we create a deployment, check that - // they don't differ by the things that we may actually change, namely container - // ports - if len(b.Spec.Template.Spec.Containers) != len(a.Spec.Template.Spec.Containers) { - return false - } - for i, container := range a.Spec.Template.Spec.Containers { - otherPorts := b.Spec.Template.Spec.Containers[i].Ports - if len(container.Ports) != len(otherPorts) { - return false - } - for j, port := range container.Ports { - otherPort := otherPorts[j] - if port.ContainerPort != otherPort.ContainerPort { - return false - } - if port.Protocol != otherPort.Protocol { - return false - } - } - } - - if b.Spec.Replicas == nil && a.Spec.Replicas == nil { - return true - } else if b.Spec.Replicas == nil { - return false - } else if a.Spec.Replicas == nil { - return false - } - - return *b.Spec.Replicas == *a.Spec.Replicas -} - -func deploymentReplicaCount(replicas *meshv2beta1.GatewayClassReplicasConfig, currentReplicas *int32) *int32 { - // if we have the replicas config, use it - if replicas != nil && replicas.Default != nil && currentReplicas == nil { - return replicas.Default - } - - // if we have the replicas config and the current replicas, use the min/max to ensure - // the current replicas are within the min/max range - if replicas != nil && currentReplicas != nil { - if replicas.Max != nil && *currentReplicas > *replicas.Max { - return replicas.Max - } - - if replicas.Min != nil && *currentReplicas < *replicas.Min { - return replicas.Min - } - - return currentReplicas - } - - // if we don't have the replicas config, use the current replicas if we have them - if currentReplicas != nil { - return currentReplicas - } - - // otherwise use the global default - return pointer.Int32(globalDefaultInstances) -} diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go deleted file mode 100644 index 9dc7dad141..0000000000 --- a/control-plane/gateways/deployment_dataplane_container.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "fmt" - "strconv" - - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - - corev1 "k8s.io/api/core/v1" -) - -const ( - allCapabilities = "ALL" - netBindCapability = "NET_BIND_SERVICE" - consulDataplaneDNSBindHost = "127.0.0.1" - consulDataplaneDNSBindPort = 8600 - defaultPrometheusScrapePath = "/metrics" - defaultEnvoyProxyConcurrency = "1" - volumeName = "consul-mesh-inject-data" -) - -func (b *meshGatewayBuilder) consulDataplaneContainer(containerConfig v2beta1.GatewayClassContainerConfig) (corev1.Container, error) { - // Extract the service account token's volume mount. - var ( - err error - bearerTokenFile string - ) - - resources := containerConfig.Resources - - if b.config.AuthMethod != "" { - bearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" - } - - args, err := b.dataplaneArgs(bearerTokenFile) - if err != nil { - return corev1.Container{}, err - } - - probe := &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(constants.ProxyDefaultHealthPort), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, - } - - container := corev1.Container{ - Name: b.gateway.Name, - Image: b.config.ImageDataplane, - - // We need to set tmp dir to an ephemeral volume that we're mounting so that - // consul-dataplane can write files to it. Otherwise, it wouldn't be able to - // because we set file system to be read-only. - - // TODO(nathancoleman): I don't believe consul-dataplane needs to write anymore, investigate. - Env: []corev1.EnvVar{ - { - Name: envDPProxyId, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, - }, - }, - { - Name: envPodNamespace, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, - }, - }, - { - Name: envTmpDir, - Value: constants.MeshV2VolumePath, - }, - { - Name: envNodeName, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: envDPCredentialLoginMeta, - Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: constants.MeshV2VolumePath, - }, - }, - Args: args, - ReadinessProbe: probe, - } - - // Configure the Readiness Address for the proxy's health check to be the Pod IP. - container.Env = append(container.Env, corev1.EnvVar{ - Name: "DP_ENVOY_READY_BIND_ADDRESS", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, - }, - }) - // Configure the port on which the readiness probe will query the proxy for its health. - container.Ports = append(container.Ports, corev1.ContainerPort{ - Name: "proxy-health", - ContainerPort: int32(constants.ProxyDefaultHealthPort), - }) - - // Configure the wan port. - wanPort := corev1.ContainerPort{ - Name: "wan", - ContainerPort: int32(constants.DefaultWANPort), - HostPort: containerConfig.HostPort, - } - - wanPort.ContainerPort = 443 + containerConfig.PortModifier - - container.Ports = append(container.Ports, wanPort) - - // Configure the resource requests and limits for the proxy if they are set. - if resources != nil { - container.Resources = *resources - } - - container.SecurityContext = &corev1.SecurityContext{ - AllowPrivilegeEscalation: pointer.Bool(false), - // Drop any Linux capabilities you'd get other than NET_BIND_SERVICE. - // FUTURE: We likely require some additional capability in order to support - // MeshGateway's host network option. - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{netBindCapability}, - Drop: []corev1.Capability{allCapabilities}, - }, - ReadOnlyRootFilesystem: pointer.Bool(true), - RunAsNonRoot: pointer.Bool(true), - } - - return container, nil -} - -func (b *meshGatewayBuilder) dataplaneArgs(bearerTokenFile string) ([]string, error) { - args := []string{ - "-addresses", b.config.ConsulConfig.Address, - "-grpc-port=" + strconv.Itoa(b.config.ConsulConfig.GRPCPort), - "-log-level=" + b.logLevelForDataplaneContainer(), - "-log-json=" + strconv.FormatBool(b.config.LogJSON), - "-envoy-concurrency=" + defaultEnvoyProxyConcurrency, - } - - consulNamespace := namespaces.ConsulNamespace(b.gateway.Namespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.ConsulDestinationNamespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.NSMirroringPrefix) - - if b.config.AuthMethod != "" { - args = append(args, - "-credential-type=login", - "-login-auth-method="+b.config.AuthMethod, - "-login-bearer-token-path="+bearerTokenFile, - "-login-meta="+fmt.Sprintf("gateway=%s/%s", b.gateway.Namespace, b.gateway.Name), - ) - if b.config.ConsulTenancyConfig.ConsulPartition != "" { - args = append(args, "-login-partition="+b.config.ConsulTenancyConfig.ConsulPartition) - } - } - if b.config.SkipServerWatch { - args = append(args, "-server-watch-disabled=true") - } - if b.config.ConsulTenancyConfig.EnableConsulNamespaces { - args = append(args, "-proxy-namespace="+consulNamespace) - } - if b.config.ConsulTenancyConfig.ConsulPartition != "" { - args = append(args, "-proxy-partition="+b.config.ConsulTenancyConfig.ConsulPartition) - } - - args = append(args, buildTLSArgs(b.config)...) - - // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. - args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) - - args = append(args, fmt.Sprintf("-envoy-admin-bind-port=%d", 19000)) - - return args, nil -} - -func buildTLSArgs(config GatewayConfig) []string { - if !config.TLSEnabled { - return []string{"-tls-disabled"} - } - tlsArgs := make([]string, 0, 2) - - if config.ConsulTLSServerName != "" { - tlsArgs = append(tlsArgs, fmt.Sprintf("-tls-server-name=%s", config.ConsulTLSServerName)) - } - if config.ConsulCACert != "" { - tlsArgs = append(tlsArgs, fmt.Sprintf("-ca-certs=%s", constants.ConsulCAFile)) - } - - return tlsArgs -} diff --git a/control-plane/gateways/deployment_init_container.go b/control-plane/gateways/deployment_init_container.go deleted file mode 100644 index 14230b98df..0000000000 --- a/control-plane/gateways/deployment_init_container.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "bytes" - "strconv" - "strings" - "text/template" - - corev1 "k8s.io/api/core/v1" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - injectInitContainerName = "consul-mesh-init" - initContainersUserAndGroupID = 5996 -) - -var ( - tpl = template.Must(template.New("root").Parse(strings.TrimSpace(initContainerCommandTpl))) -) - -type initContainerCommandData struct { - ServiceName string - ServiceAccountName string - AuthMethod string - - // Log settings for the connect-init command. - LogLevel string - LogJSON bool -} - -// 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 (b *meshGatewayBuilder) initContainer() (corev1.Container, error) { - data := initContainerCommandData{ - AuthMethod: b.config.AuthMethod, - LogLevel: b.logLevelForInitContainer(), - LogJSON: b.config.LogJSON, - ServiceName: b.gateway.Name, - ServiceAccountName: b.serviceAccountName(), - } - // Render the command - var buf bytes.Buffer - if err := tpl.Execute(&buf, &data); err != nil { - return corev1.Container{}, err - } - - // Create expected volume mounts - volMounts := []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: constants.MeshV2VolumePath, - }, - } - - var bearerTokenFile string - if b.config.AuthMethod != "" { - bearerTokenFile = defaultBearerTokenFile - } - - consulNamespace := namespaces.ConsulNamespace(b.gateway.Namespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.ConsulDestinationNamespace, b.config.ConsulTenancyConfig.EnableConsulNamespaces, b.config.ConsulTenancyConfig.NSMirroringPrefix) - - initContainerName := injectInitContainerName - container := corev1.Container{ - Name: initContainerName, - Image: b.config.ImageConsulK8S, - - Env: []corev1.EnvVar{ - { - Name: envPodName, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, - }, - }, - { - Name: envPodNamespace, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, - }, - }, - { - Name: envNodeName, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: envConsulAddresses, - Value: b.config.ConsulConfig.Address, - }, - { - Name: envConsulGRPCPort, - Value: strconv.Itoa(b.config.ConsulConfig.GRPCPort), - }, - { - Name: envConsulHTTPPort, - Value: strconv.Itoa(b.config.ConsulConfig.HTTPPort), - }, - { - Name: envConsulAPITimeout, - Value: b.config.ConsulConfig.APITimeout.String(), - }, - { - Name: envConsulNodeName, - Value: "$(NODE_NAME)-virtual", - }, - }, - VolumeMounts: volMounts, - Command: []string{"/bin/sh", "-ec", buf.String()}, - Resources: initContainerResourcesOrDefault(b.gcc), - } - - if b.config.AuthMethod != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: envConsulLoginAuthMethod, - Value: b.config.AuthMethod, - }, - corev1.EnvVar{ - Name: envConsulLoginBearerTokenFile, - Value: bearerTokenFile, - }, - corev1.EnvVar{ - Name: envConsulLoginMeta, - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }) - - if b.config.ConsulTenancyConfig.ConsulPartition != "" { - container.Env = append(container.Env, corev1.EnvVar{ - Name: envConsulLoginPartition, - Value: b.config.ConsulTenancyConfig.ConsulPartition, - }) - } - } - container.Env = append(container.Env, - corev1.EnvVar{ - Name: envConsulNamespace, - Value: consulNamespace, - }) - - if b.config.TLSEnabled { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: constants.UseTLSEnvVar, - Value: "true", - }, - corev1.EnvVar{ - Name: constants.CACertPEMEnvVar, - Value: b.config.ConsulCACert, - }, - corev1.EnvVar{ - Name: constants.TLSServerNameEnvVar, - Value: b.config.ConsulTLSServerName, - }) - } - - if b.config.ConsulTenancyConfig.ConsulPartition != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: envConsulPartition, - Value: b.config.ConsulTenancyConfig.ConsulPartition, - }) - } - - return container, nil -} - -func initContainerResourcesOrDefault(gcc *meshv2beta1.GatewayClassConfig) corev1.ResourceRequirements { - if gcc != nil && gcc.Spec.Deployment.InitContainer != nil && gcc.Spec.Deployment.InitContainer.Resources != nil { - return *gcc.Spec.Deployment.InitContainer.Resources - } - - return corev1.ResourceRequirements{} -} - -// initContainerCommandTpl is the template for the command executed by -// the init container. -// TODO @GatewayManagement parametrize gateway kind. -const initContainerCommandTpl = ` -consul-k8s-control-plane mesh-init \ - -proxy-name=${POD_NAME} \ - -namespace=${POD_NAMESPACE} \ - {{- with .LogLevel }} - -log-level={{ . }} \ - {{- end }} - -log-json={{ .LogJSON }} -` diff --git a/control-plane/gateways/deployment_test.go b/control-plane/gateways/deployment_test.go deleted file mode 100644 index b3f06ad1d2..0000000000 --- a/control-plane/gateways/deployment_test.go +++ /dev/null @@ -1,1128 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "testing" - - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" -) - -const testCert = `-----BEGIN CERTIFICATE----- │ -MIIDQjCCAuigAwIBAgIUZGIigQ4IKLoCh4XrXyi/c89B7ZgwCgYIKoZIzj0EAwIw │ -gZExCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5j │ -aXNjbzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1 │ -MRcwFQYDVQQKEw5IYXNoaUNvcnAgSW5jLjEYMBYGA1UEAxMPQ29uc3VsIEFnZW50 │ -IENBMB4XDTI0MDEwMzE4NTYyOVoXDTMzMTIzMTE4NTcyOVowgZExCzAJBgNVBAYT │ -AlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEaMBgGA1UE │ -CRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcwFQYDVQQKEw5I │ -YXNoaUNvcnAgSW5jLjEYMBYGA1UEAxMPQ29uc3VsIEFnZW50IENBMFkwEwYHKoZI │ -zj0CAQYIKoZIzj0DAQcDQgAEcbkdpZxlDOEuT3ZCcZ8H9j0Jad8ncDYk/Y0IbHPC │ -OKfFcpldEFPRv16WgSTHg38kK9WgEuK291+joBTHry3y06OCARowggEWMA4GA1Ud │ -DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0T │ -AQH/BAUwAwEB/zBoBgNVHQ4EYQRfZGY6MzA6YWE6NzI6ZTQ6ZTI6NzI6Y2Y6NTg6 │ -NDU6Zjk6YjU6NTA6N2I6ZDQ6MDI6MTE6ZjM6YzY6ZjE6NTc6NTE6MTg6NGU6OGU6 │ -ZjE6MmE6ZTE6MzI6NmY6ZTU6YjMwagYDVR0jBGMwYYBfZGY6MzA6YWE6NzI6ZTQ6 │ -ZTI6NzI6Y2Y6NTg6NDU6Zjk6YjU6NTA6N2I6ZDQ6MDI6MTE6ZjM6YzY6ZjE6NTc6 │ -NTE6MTg6NGU6OGU6ZjE6MmE6ZTE6MzI6NmY6ZTU6YjMwCgYIKoZIzj0EAwIDSAAw │ -RQIgXg8YtejEgGNxswtyXsvqzhLpt7k44L7TJMUhfIw0lUECIQCIxKNowmv0/XVz │ -nRnYLmGy79EZ2Y+CZS9nSm9Es6QNwg== │ ------END CERTIFICATE-----` - -func Test_meshGatewayBuilder_Deployment(t *testing.T) { - type fields struct { - gateway *meshv2beta1.MeshGateway - config GatewayConfig - gcc *meshv2beta1.GatewayClassConfig - } - tests := []struct { - name string - fields fields - want *appsv1.Deployment - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - gateway: &meshv2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationGatewayWANSource: "Service", - constants.AnnotationGatewayWANPort: "443", - constants.AnnotationGatewayWANAddress: "", - }, - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "test-gateway-class", - }, - }, - config: GatewayConfig{}, - gcc: &meshv2beta1.GatewayClassConfig{ - Spec: meshv2beta1.GatewayClassConfigSpec{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - Set: map[string]string{ - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - }, - Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - Set: map[string]string{ - "a": "b", - }, - }, - }, - Deployment: meshv2beta1.GatewayClassDeploymentConfig{ - Affinity: &corev1.Affinity{ - PodAntiAffinity: &corev1.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ - { - Weight: 1, - PodAffinityTerm: corev1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - }, - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - Set: map[string]string{ - "foo": "bar", - }, - }, - Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - Set: map[string]string{ - "baz": "qux", - }, - }, - }, - Container: &meshv2beta1.GatewayClassContainerConfig{ - HostPort: 8080, - PortModifier: 8000, - Consul: meshv2beta1.GatewayClassConsulConfig{ - Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ - Level: "debug", - }, - }, - }, - NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, - Replicas: &meshv2beta1.GatewayClassReplicasConfig{ - Default: pointer.Int32(1), - Min: pointer.Int32(1), - Max: pointer.Int32(8), - }, - PriorityClassName: "priorityclassname", - TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "key", - WhenUnsatisfiable: "DoNotSchedule", - }, - }, - InitContainer: &meshv2beta1.GatewayClassInitContainerConfig{ - Resources: &corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - "cpu": resource.MustParse("100m"), - "memory": resource.MustParse("128Mi"), - }, - Limits: corev1.ResourceList{ - "cpu": resource.MustParse("200m"), - "memory": resource.MustParse("228Mi"), - }, - }, - Consul: meshv2beta1.GatewayClassConsulConfig{ - Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ - Level: "debug", - }, - }, - }, - }, - }, - }, - }, - want: &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - "foo": "bar", - }, - Annotations: map[string]string{ - "a": "b", - "baz": "qux", - }, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: pointer.Int32(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - "foo": "bar", - }, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "foo": "bar", - "release": "consul", - }, - Annotations: map[string]string{ - constants.AnnotationGatewayKind: meshGatewayAnnotationKind, - constants.AnnotationMeshInject: "false", - constants.AnnotationTransparentProxyOverwriteProbes: "false", - constants.AnnotationGatewayWANSource: "Service", - constants.AnnotationGatewayWANPort: "443", - constants.AnnotationGatewayWANAddress: "", - }, - }, - Spec: corev1.PodSpec{ - Volumes: []corev1.Volume{ - { - Name: "consul-mesh-inject-data", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: "Memory", - }, - }, - }, - }, - InitContainers: []corev1.Container{ - { - Name: "consul-mesh-init", - Command: []string{ - "/bin/sh", - "-ec", - "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-level=debug \\\n -log-json=false", - }, - Env: []corev1.EnvVar{ - { - Name: "POD_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "POD_NAMESPACE", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.namespace", - }, - }, - }, - { - Name: "NODE_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "CONSUL_ADDRESSES", - Value: "", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "0", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "0", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "0s", - }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "", - }, - }, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - "cpu": resource.MustParse("100m"), - "memory": resource.MustParse("128Mi"), - }, - Limits: corev1.ResourceList{ - "cpu": resource.MustParse("200m"), - "memory": resource.MustParse("228Mi"), - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "consul-mesh-inject-data", - ReadOnly: false, - MountPath: "/consul/mesh-inject", - }, - }, - }, - }, - Containers: []corev1.Container{ - { - Args: []string{ - "-addresses", - "", - "-grpc-port=0", - "-log-level=debug", - "-log-json=false", - "-envoy-concurrency=1", - "-tls-disabled", - "-envoy-ready-bind-port=21000", - "-envoy-admin-bind-port=19000", - }, - Ports: []corev1.ContainerPort{ - { - Name: "proxy-health", - ContainerPort: 21000, - }, - { - Name: "wan", - ContainerPort: 8443, - HostPort: 8080, - }, - }, - Env: []corev1.EnvVar{ - { - Name: "DP_PROXY_ID", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "POD_NAMESPACE", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.namespace", - }, - }, - }, - { - Name: "TMPDIR", - Value: "/consul/mesh-inject", - }, - { - Name: "NODE_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "DP_CREDENTIAL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", - }, - { - Name: "DP_ENVOY_READY_BIND_ADDRESS", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "status.podIP", - }, - }, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "consul-mesh-inject-data", - MountPath: "/consul/mesh-inject", - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/ready", - Port: intstr.IntOrString{ - Type: 0, - IntVal: 21000, - StrVal: "", - }, - }, - }, - InitialDelaySeconds: 1, - }, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "NET_BIND_SERVICE", - }, - Drop: []corev1.Capability{ - "ALL", - }, - }, - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - ProcMount: nil, - SeccompProfile: nil, - }, - Stdin: false, - StdinOnce: false, - TTY: false, - }, - }, - NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, - PriorityClassName: "priorityclassname", - TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "key", - WhenUnsatisfiable: "DoNotSchedule", - }, - }, - Affinity: &corev1.Affinity{ - NodeAffinity: nil, - PodAffinity: nil, - PodAntiAffinity: &corev1.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ - { - Weight: 1, - PodAffinityTerm: corev1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - }, - }, - }, - Strategy: appsv1.DeploymentStrategy{}, - MinReadySeconds: 0, - RevisionHistoryLimit: nil, - Paused: false, - ProgressDeadlineSeconds: nil, - }, - Status: appsv1.DeploymentStatus{}, - }, - wantErr: false, - }, - { - name: "happy path tls enabled", - fields: fields{ - gateway: &meshv2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationGatewayWANSource: "Service", - constants.AnnotationGatewayWANPort: "443", - constants.AnnotationGatewayWANAddress: "", - }, - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "test-gateway-class", - }, - }, - config: GatewayConfig{ - TLSEnabled: true, - ConsulCACert: testCert, - }, - gcc: &meshv2beta1.GatewayClassConfig{ - Spec: meshv2beta1.GatewayClassConfigSpec{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - Set: map[string]string{ - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - }, - }, - Deployment: meshv2beta1.GatewayClassDeploymentConfig{ - Affinity: &corev1.Affinity{ - PodAntiAffinity: &corev1.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ - { - Weight: 1, - PodAffinityTerm: corev1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - }, - Container: &meshv2beta1.GatewayClassContainerConfig{ - HostPort: 8080, - PortModifier: 8000, - Consul: meshv2beta1.GatewayClassConsulConfig{ - Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ - Level: "debug", - }, - }, - }, - NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, - Replicas: &meshv2beta1.GatewayClassReplicasConfig{ - Default: pointer.Int32(1), - Min: pointer.Int32(1), - Max: pointer.Int32(8), - }, - PriorityClassName: "priorityclassname", - TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "key", - WhenUnsatisfiable: "DoNotSchedule", - }, - }, - InitContainer: &meshv2beta1.GatewayClassInitContainerConfig{ - Resources: &corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - "cpu": resource.MustParse("100m"), - "memory": resource.MustParse("128Mi"), - }, - Limits: corev1.ResourceList{ - "cpu": resource.MustParse("200m"), - "memory": resource.MustParse("228Mi"), - }, - }, - Consul: meshv2beta1.GatewayClassConsulConfig{ - Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ - Level: "debug", - }, - }, - }, - }, - }, - }, - }, - want: &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - - Annotations: map[string]string{}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: pointer.Int32(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - Annotations: map[string]string{ - constants.AnnotationGatewayKind: meshGatewayAnnotationKind, - constants.AnnotationMeshInject: "false", - constants.AnnotationTransparentProxyOverwriteProbes: "false", - constants.AnnotationGatewayWANSource: "Service", - constants.AnnotationGatewayWANPort: "443", - constants.AnnotationGatewayWANAddress: "", - }, - }, - Spec: corev1.PodSpec{ - Volumes: []corev1.Volume{ - { - Name: "consul-mesh-inject-data", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: "Memory", - }, - }, - }, - }, - InitContainers: []corev1.Container{ - { - Name: "consul-mesh-init", - Command: []string{ - "/bin/sh", - "-ec", - "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-level=debug \\\n -log-json=false", - }, - Env: []corev1.EnvVar{ - { - Name: "POD_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "POD_NAMESPACE", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.namespace", - }, - }, - }, - { - Name: "NODE_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "CONSUL_ADDRESSES", - Value: "", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "0", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "0", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "0s", - }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "", - }, - { - Name: "CONSUL_USE_TLS", - Value: "true", - }, - { - Name: "CONSUL_CACERT_PEM", - Value: testCert, - }, - { - Name: "CONSUL_TLS_SERVER_NAME", - Value: "", - }, - }, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - "cpu": resource.MustParse("100m"), - "memory": resource.MustParse("128Mi"), - }, - Limits: corev1.ResourceList{ - "cpu": resource.MustParse("200m"), - "memory": resource.MustParse("228Mi"), - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "consul-mesh-inject-data", - ReadOnly: false, - MountPath: "/consul/mesh-inject", - }, - }, - }, - }, - Containers: []corev1.Container{ - { - Args: []string{ - "-addresses", - "", - "-grpc-port=0", - "-log-level=debug", - "-log-json=false", - "-envoy-concurrency=1", - "-ca-certs=/consul/mesh-inject/consul-ca.pem", - "-envoy-ready-bind-port=21000", - "-envoy-admin-bind-port=19000", - }, - Ports: []corev1.ContainerPort{ - { - Name: "proxy-health", - ContainerPort: 21000, - }, - { - Name: "wan", - ContainerPort: 8443, - HostPort: 8080, - }, - }, - Env: []corev1.EnvVar{ - { - Name: "DP_PROXY_ID", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "POD_NAMESPACE", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.namespace", - }, - }, - }, - { - Name: "TMPDIR", - Value: "/consul/mesh-inject", - }, - { - Name: "NODE_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "DP_CREDENTIAL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", - }, - { - Name: "DP_ENVOY_READY_BIND_ADDRESS", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "status.podIP", - }, - }, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "consul-mesh-inject-data", - MountPath: "/consul/mesh-inject", - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/ready", - Port: intstr.IntOrString{ - Type: 0, - IntVal: 21000, - StrVal: "", - }, - }, - }, - InitialDelaySeconds: 1, - }, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "NET_BIND_SERVICE", - }, - Drop: []corev1.Capability{ - "ALL", - }, - }, - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - ProcMount: nil, - SeccompProfile: nil, - }, - Stdin: false, - StdinOnce: false, - TTY: false, - }, - }, - NodeSelector: map[string]string{"beta.kubernetes.io/arch": "amd64"}, - PriorityClassName: "priorityclassname", - TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ - { - MaxSkew: 1, - TopologyKey: "key", - WhenUnsatisfiable: "DoNotSchedule", - }, - }, - Affinity: &corev1.Affinity{ - NodeAffinity: nil, - PodAffinity: nil, - PodAntiAffinity: &corev1.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ - { - Weight: 1, - PodAffinityTerm: corev1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - }, - }, - }, - Strategy: appsv1.DeploymentStrategy{}, - MinReadySeconds: 0, - RevisionHistoryLimit: nil, - Paused: false, - ProgressDeadlineSeconds: nil, - }, - Status: appsv1.DeploymentStatus{}, - }, - wantErr: false, - }, - { - name: "nil gatewayclassconfig - (notfound)", - fields: fields{ - gateway: &meshv2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationGatewayWANSource: "Service", - constants.AnnotationGatewayWANPort: "443", - constants.AnnotationGatewayWANAddress: "", - }, - }, - Spec: pbmesh.MeshGateway{ - GatewayClassName: "test-gateway-class", - }, - }, - config: GatewayConfig{}, - gcc: nil, - }, - want: &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Labels: defaultLabels, - Annotations: map[string]string{}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: pointer.Int32(1), - Selector: &metav1.LabelSelector{ - MatchLabels: defaultLabels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: defaultLabels, - Annotations: map[string]string{ - constants.AnnotationGatewayKind: meshGatewayAnnotationKind, - constants.AnnotationMeshInject: "false", - constants.AnnotationTransparentProxyOverwriteProbes: "false", - constants.AnnotationGatewayWANSource: "Service", - constants.AnnotationGatewayWANPort: "443", - constants.AnnotationGatewayWANAddress: "", - }, - }, - Spec: corev1.PodSpec{ - Volumes: []corev1.Volume{ - { - Name: "consul-mesh-inject-data", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: "Memory", - }, - }, - }, - }, - InitContainers: []corev1.Container{ - { - Name: "consul-mesh-init", - Command: []string{ - "/bin/sh", - "-ec", - "consul-k8s-control-plane mesh-init \\\n -proxy-name=${POD_NAME} \\\n -namespace=${POD_NAMESPACE} \\\n -log-json=false", - }, - Env: []corev1.EnvVar{ - { - Name: "POD_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "POD_NAMESPACE", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.namespace", - }, - }, - }, - { - Name: "NODE_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "CONSUL_ADDRESSES", - Value: "", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "0", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "0", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "0s", - }, - { - Name: "CONSUL_NODE_NAME", - Value: "$(NODE_NAME)-virtual", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "", - }, - }, - Resources: corev1.ResourceRequirements{}, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "consul-mesh-inject-data", - ReadOnly: false, - MountPath: "/consul/mesh-inject", - }, - }, - }, - }, - Containers: []corev1.Container{ - { - Args: []string{ - "-addresses", - "", - "-grpc-port=0", - "-log-level=", - "-log-json=false", - "-envoy-concurrency=1", - "-tls-disabled", - "-envoy-ready-bind-port=21000", - "-envoy-admin-bind-port=19000", - }, - Ports: []corev1.ContainerPort{ - { - Name: "proxy-health", - ContainerPort: 21000, - }, - { - Name: "wan", - ContainerPort: 443, - }, - }, - Env: []corev1.EnvVar{ - { - Name: "DP_PROXY_ID", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "POD_NAMESPACE", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "metadata.namespace", - }, - }, - }, - { - Name: "TMPDIR", - Value: "/consul/mesh-inject", - }, - { - Name: "NODE_NAME", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "DP_CREDENTIAL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(DP_PROXY_ID)", - }, - { - Name: "DP_ENVOY_READY_BIND_ADDRESS", - Value: "", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "status.podIP", - }, - }, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "consul-mesh-inject-data", - MountPath: "/consul/mesh-inject", - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/ready", - Port: intstr.IntOrString{ - Type: 0, - IntVal: 21000, - StrVal: "", - }, - }, - }, - InitialDelaySeconds: 1, - }, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "NET_BIND_SERVICE", - }, - Drop: []corev1.Capability{ - "ALL", - }, - }, - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - ProcMount: nil, - SeccompProfile: nil, - }, - Stdin: false, - StdinOnce: false, - TTY: false, - }, - }, - }, - }, - Strategy: appsv1.DeploymentStrategy{}, - MinReadySeconds: 0, - RevisionHistoryLimit: nil, - Paused: false, - ProgressDeadlineSeconds: nil, - }, - Status: appsv1.DeploymentStatus{}, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &meshGatewayBuilder{ - gateway: tt.fields.gateway, - config: tt.fields.config, - gcc: tt.fields.gcc, - } - got, err := b.Deployment() - if !tt.wantErr && (err != nil) { - assert.Errorf(t, err, "Error") - } - assert.Equalf(t, tt.want, got, "Deployment()") - }) - } -} diff --git a/control-plane/gateways/gateway_config.go b/control-plane/gateways/gateway_config.go deleted file mode 100644 index de3202e29e..0000000000 --- a/control-plane/gateways/gateway_config.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// GatewayConfig is a combination of settings relevant to Gateways. -type GatewayConfig struct { - // ImageDataplane is the Consul Dataplane image to use in gateway deployments. - ImageDataplane string - // ImageConsulK8S is the Consul Kubernetes Control Plane image to use in gateway deployments. - ImageConsulK8S string - // AuthMethod method used to authenticate with Consul Server. - AuthMethod string - - // ConsulTenancyConfig is the configuration for the Consul Tenancy feature. - ConsulTenancyConfig common.ConsulTenancyConfig - - // LogLevel is the logging level of the deployed Consul Dataplanes. - LogLevel string - // LogJSON if JSONLogging has been enabled. - LogJSON bool - // TLSEnabled is the value of whether or not TLS has been enabled in Consul. - TLSEnabled bool - // PeeringEnabled toggles whether or not Peering is enabled in Consul. - PeeringEnabled bool - // ConsulTLSServerName the name of the server running TLS. - ConsulTLSServerName string - // ConsulCACert contains the Consul Certificate Authority. - ConsulCACert string - // ConsulConfig configuration for the consul server address. - ConsulConfig common.ConsulConfig - - // EnableOpenShift indicates whether we're deploying into an OpenShift environment - EnableOpenShift bool - - // MapPrivilegedServicePorts is the value which Consul will add to privileged container port values (ports < 1024) - // defined on a Gateway. - MapPrivilegedServicePorts int - - // TODO(nathancoleman) Add doc - SkipServerWatch bool -} - -// GatewayResources is a collection of Kubernetes resources for a Gateway. -type GatewayResources struct { - // GatewayClassConfigs is a list of GatewayClassConfig resources which are - // responsible for defining configuration shared across all gateway kinds. - GatewayClassConfigs []*v2beta1.GatewayClassConfig `json:"gatewayClassConfigs"` - // MeshGateways is a list of MeshGateway resources which are responsible for - // defining the configuration for a specific mesh gateway. - // Deployments of mesh gateways have a one-to-one relationship with MeshGateway resources. - MeshGateways []*v2beta1.MeshGateway `json:"meshGateways"` -} diff --git a/control-plane/gateways/metadata.go b/control-plane/gateways/metadata.go deleted file mode 100644 index e1479ef3f2..0000000000 --- a/control-plane/gateways/metadata.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "golang.org/x/exp/slices" - - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -const labelManagedBy = "mesh.consul.hashicorp.com/managed-by" - -var defaultLabels = map[string]string{labelManagedBy: "consul-k8s"} - -func (b *meshGatewayBuilder) annotationsForDeployment() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.Deployment.Annotations, b.gcc.Spec.Annotations) -} - -func (b *meshGatewayBuilder) annotationsForRole() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.Role.Annotations, b.gcc.Spec.Annotations) -} - -func (b *meshGatewayBuilder) annotationsForRoleBinding() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.RoleBinding.Annotations, b.gcc.Spec.Annotations) -} - -func (b *meshGatewayBuilder) annotationsForService() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.Service.Annotations, b.gcc.Spec.Annotations) -} - -func (b *meshGatewayBuilder) annotationsForServiceAccount() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.Annotations, b.gcc.Spec.ServiceAccount.Annotations, b.gcc.Spec.Annotations) -} - -func (b *meshGatewayBuilder) labelsForDeployment() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.Deployment.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -func (b *meshGatewayBuilder) logLevelForDataplaneContainer() string { - if b.config.LogLevel != "" { - return b.config.LogLevel - } - - if b.gcc == nil || b.gcc.Spec.Deployment.Container == nil { - return "" - } - - return b.gcc.Spec.Deployment.Container.Consul.Logging.Level -} - -func (b *meshGatewayBuilder) logLevelForInitContainer() string { - if b.config.LogLevel != "" { - return b.config.LogLevel - } - - if b.gcc == nil || b.gcc.Spec.Deployment.InitContainer == nil { - return "" - } - - return b.gcc.Spec.Deployment.InitContainer.Consul.Logging.Level -} - -func (b *meshGatewayBuilder) labelsForRole() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.Role.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -func (b *meshGatewayBuilder) labelsForRoleBinding() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.RoleBinding.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -func (b *meshGatewayBuilder) labelsForService() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.Service.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -func (b *meshGatewayBuilder) labelsForServiceAccount() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.Labels, b.gcc.Spec.ServiceAccount.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -// computeAnnotationsOrLabels compiles a set of annotations or labels -// using the following priority, highest to lowest: -// 1. inherited keys specified on the primary -// 2. added key-values specified on the primary -// 3. inherited keys specified on the secondary -// 4. added key-values specified on the secondary -func computeAnnotationsOrLabels(inheritFrom map[string]string, primary, secondary v2beta1.GatewayClassAnnotationsLabelsConfig) map[string]string { - out := map[string]string{} - - // Add key-values specified on the secondary - for k, v := range secondary.Set { - out[k] = v - } - - // Inherit keys specified on the secondary - for k, v := range inheritFrom { - if slices.Contains(secondary.InheritFromGateway, k) { - out[k] = v - } - } - - // Add key-values specified on the primary - for k, v := range primary.Set { - out[k] = v - } - - // Inherit keys specified on the primary - for k, v := range inheritFrom { - if slices.Contains(primary.InheritFromGateway, k) { - out[k] = v - } - } - - return out -} diff --git a/control-plane/gateways/metadata_test.go b/control-plane/gateways/metadata_test.go deleted file mode 100644 index 7505867992..0000000000 --- a/control-plane/gateways/metadata_test.go +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "testing" - - "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -func TestMeshGatewayBuilder_Annotations(t *testing.T) { - gateway := &meshv2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - "gateway-annotation": "true", // Will be inherited by all resources - "gateway-deployment-annotation": "true", // Will be inherited by Deployment - "gateway-role-annotation": "true", // Will be inherited by Role - "gateway-role-binding-annotation": "true", // Will be inherited by RoleBinding - "gateway-service-annotation": "true", // Will be inherited by Service - "gateway-service-account-annotation": "true", // Will be inherited by ServiceAccount - }, - }, - } - - gatewayClassConfig := &meshv2beta1.GatewayClassConfig{ - Spec: meshv2beta1.GatewayClassConfigSpec{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-annotation"}, - Set: map[string]string{"global-set": "true"}, - }, - }, - Deployment: meshv2beta1.GatewayClassDeploymentConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-deployment-annotation"}, - Set: map[string]string{"deployment-set": "true"}, - }, - }, - }, - Role: meshv2beta1.GatewayClassRoleConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-role-annotation"}, - Set: map[string]string{"role-set": "true"}, - }, - }, - }, - RoleBinding: meshv2beta1.GatewayClassRoleBindingConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-role-binding-annotation"}, - Set: map[string]string{"role-binding-set": "true"}, - }, - }, - }, - Service: meshv2beta1.GatewayClassServiceConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-service-annotation"}, - Set: map[string]string{"service-set": "true"}, - }, - }, - }, - ServiceAccount: meshv2beta1.GatewayClassServiceAccountConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Annotations: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-service-account-annotation"}, - Set: map[string]string{"service-account-set": "true"}, - }, - }, - }, - }, - } - - b := NewMeshGatewayBuilder(gateway, GatewayConfig{}, gatewayClassConfig) - - for _, testCase := range []struct { - Actual map[string]string - Expected map[string]string - }{ - { - Actual: b.annotationsForDeployment(), - Expected: map[string]string{ - "gateway-annotation": "true", - "global-set": "true", - "gateway-deployment-annotation": "true", - "deployment-set": "true", - }, - }, - { - Actual: b.annotationsForRole(), - Expected: map[string]string{ - "gateway-annotation": "true", - "global-set": "true", - "gateway-role-annotation": "true", - "role-set": "true", - }, - }, - { - Actual: b.annotationsForRoleBinding(), - Expected: map[string]string{ - "gateway-annotation": "true", - "global-set": "true", - "gateway-role-binding-annotation": "true", - "role-binding-set": "true", - }, - }, - { - Actual: b.annotationsForService(), - Expected: map[string]string{ - "gateway-annotation": "true", - "global-set": "true", - "gateway-service-annotation": "true", - "service-set": "true", - }, - }, - { - Actual: b.annotationsForServiceAccount(), - Expected: map[string]string{ - "gateway-annotation": "true", - "global-set": "true", - "gateway-service-account-annotation": "true", - "service-account-set": "true", - }, - }, - } { - assert.Equal(t, testCase.Expected, testCase.Actual) - } -} - -func TestNewMeshGatewayBuilder_Labels(t *testing.T) { - gateway := &meshv2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "gateway-label": "true", // Will be inherited by all resources - "gateway-deployment-label": "true", // Will be inherited by Deployment - "gateway-role-label": "true", // Will be inherited by Role - "gateway-role-binding-label": "true", // Will be inherited by RoleBinding - "gateway-service-label": "true", // Will be inherited by Service - "gateway-service-account-label": "true", // Will be inherited by ServiceAccount - }, - }, - } - - gatewayClassConfig := &meshv2beta1.GatewayClassConfig{ - Spec: meshv2beta1.GatewayClassConfigSpec{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-label"}, - Set: map[string]string{"global-set": "true"}, - }, - }, - Deployment: meshv2beta1.GatewayClassDeploymentConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-deployment-label"}, - Set: map[string]string{"deployment-set": "true"}, - }, - }, - }, - Role: meshv2beta1.GatewayClassRoleConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-role-label"}, - Set: map[string]string{"role-set": "true"}, - }, - }, - }, - RoleBinding: meshv2beta1.GatewayClassRoleBindingConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-role-binding-label"}, - Set: map[string]string{"role-binding-set": "true"}, - }, - }, - }, - Service: meshv2beta1.GatewayClassServiceConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-service-label"}, - Set: map[string]string{"service-set": "true"}, - }, - }, - }, - ServiceAccount: meshv2beta1.GatewayClassServiceAccountConfig{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{"gateway-service-account-label"}, - Set: map[string]string{"service-account-set": "true"}, - }, - }, - }, - }, - } - - b := NewMeshGatewayBuilder(gateway, GatewayConfig{}, gatewayClassConfig) - - for _, testCase := range []struct { - Actual map[string]string - Expected map[string]string - }{ - { - Actual: b.labelsForDeployment(), - Expected: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - "gateway-label": "true", - "global-set": "true", - "gateway-deployment-label": "true", - "deployment-set": "true", - }, - }, - { - Actual: b.labelsForRole(), - Expected: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - "gateway-label": "true", - "global-set": "true", - "gateway-role-label": "true", - "role-set": "true", - }, - }, - { - Actual: b.labelsForRoleBinding(), - Expected: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - "gateway-label": "true", - "global-set": "true", - "gateway-role-binding-label": "true", - "role-binding-set": "true", - }, - }, - { - Actual: b.labelsForService(), - Expected: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - "gateway-label": "true", - "global-set": "true", - "gateway-service-label": "true", - "service-set": "true", - }, - }, - { - Actual: b.labelsForServiceAccount(), - Expected: map[string]string{ - "mesh.consul.hashicorp.com/managed-by": "consul-k8s", - "gateway-label": "true", - "global-set": "true", - "gateway-service-account-label": "true", - "service-account-set": "true", - }, - }, - } { - assert.Equal(t, testCase.Expected, testCase.Actual) - } -} - -// The LogLevel for deployment containers may be set on the Gateway Class Config or the Gateway Config. -// If it is set on both, the Gateway Config takes precedence. -func TestMeshGatewayBuilder_LogLevel(t *testing.T) { - debug := "debug" - info := "info" - - testCases := map[string]struct { - GatewayLogLevel string - GCCLogLevel string - }{ - "Set on Gateway": { - GatewayLogLevel: debug, - GCCLogLevel: "", - }, - "Set on GCC": { - GatewayLogLevel: "", - GCCLogLevel: debug, - }, - "Set on both": { - GatewayLogLevel: debug, - GCCLogLevel: info, - }, - } - - for name, testCase := range testCases { - t.Run(name, func(t *testing.T) { - - gcc := &meshv2beta1.GatewayClassConfig{ - Spec: meshv2beta1.GatewayClassConfigSpec{ - Deployment: meshv2beta1.GatewayClassDeploymentConfig{ - Container: &meshv2beta1.GatewayClassContainerConfig{ - Consul: meshv2beta1.GatewayClassConsulConfig{ - Logging: meshv2beta1.GatewayClassConsulLoggingConfig{ - Level: testCase.GCCLogLevel, - }, - }, - }, - }, - }, - } - b := NewMeshGatewayBuilder(&meshv2beta1.MeshGateway{}, GatewayConfig{LogLevel: testCase.GatewayLogLevel}, gcc) - - assert.Equal(t, debug, b.logLevelForDataplaneContainer()) - }) - } -} - -func Test_computeAnnotationsOrLabels(t *testing.T) { - gatewaySet := map[string]string{ - "service.beta.kubernetes.io/aws-load-balancer-internal": "true", // Will not be inherited - "service.beta.kubernetes.io/aws-load-balancer-name": "my-lb", // Will be inherited - } - - primary := meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{ - "service.beta.kubernetes.io/aws-load-balancer-name", - }, - Set: map[string]string{ - "created-by": "nathancoleman", // Only exists in primary - "owning-team": "consul-gateway-management", // Will override secondary - }, - } - - secondary := meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - InheritFromGateway: []string{}, - Set: map[string]string{ - "created-on": "kubernetes", // Only exists in secondary - "owning-team": "consul", // Will be overridden by primary - }, - } - - actual := computeAnnotationsOrLabels(gatewaySet, primary, secondary) - expected := map[string]string{ - "created-by": "nathancoleman", // Set by primary - "created-on": "kubernetes", // Set by secondary - "owning-team": "consul-gateway-management", // Set by primary, overrode secondary - "service.beta.kubernetes.io/aws-load-balancer-name": "my-lb", // Inherited from gateway - } - - assert.Equal(t, expected, actual) -} diff --git a/control-plane/gateways/role.go b/control-plane/gateways/role.go deleted file mode 100644 index 3264bb60b0..0000000000 --- a/control-plane/gateways/role.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func (b *meshGatewayBuilder) Role() *rbacv1.Role { - return &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.Name, - Namespace: b.gateway.Namespace, - Labels: b.labelsForRole(), - Annotations: b.annotationsForRole(), - }, - Rules: []rbacv1.PolicyRule{}, - } -} - -func (b *meshGatewayBuilder) RoleBinding() *rbacv1.RoleBinding { - return &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.Name, - Namespace: b.gateway.Namespace, - Labels: b.labelsForRoleBinding(), - Annotations: b.annotationsForRoleBinding(), - }, - Subjects: []rbacv1.Subject{ - { - APIGroup: "", - Kind: rbacv1.ServiceAccountKind, - Name: b.gateway.Name, - Namespace: b.gateway.Namespace, - }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: b.Role().Name, - }, - } -} diff --git a/control-plane/gateways/service.go b/control-plane/gateways/service.go deleted file mode 100644 index a245a8a902..0000000000 --- a/control-plane/gateways/service.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -func (b *meshGatewayBuilder) Service() *corev1.Service { - var ( - containerConfig *meshv2beta1.GatewayClassContainerConfig - portModifier = int32(0) - serviceType = corev1.ServiceType("") - ) - - if b.gcc != nil { - containerConfig = b.gcc.Spec.Deployment.Container - portModifier = containerConfig.PortModifier - serviceType = *b.gcc.Spec.Service.Type - } - - return &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.Name, - Namespace: b.gateway.Namespace, - Labels: b.labelsForService(), - Annotations: b.annotationsForService(), - }, - Spec: corev1.ServiceSpec{ - Selector: b.labelsForDeployment(), - Type: serviceType, - Ports: b.Ports(portModifier), - }, - } -} - -// Ports build a list of ports from the listener objects. In theory there should only ever be a WAN port on -// mesh gateway but building the ports from a list of listeners will allow for easier compatability with other -// gateway patterns in the future. -func (b *meshGatewayBuilder) Ports(portModifier int32) []corev1.ServicePort { - - ports := []corev1.ServicePort{} - - if len(b.gateway.Spec.Listeners) == 0 { - //If empty use the default value. This should always be set, but in case it's not, this check - //will prevent a panic. - return []corev1.ServicePort{ - { - Name: "wan", - Port: constants.DefaultWANPort, - TargetPort: intstr.IntOrString{ - IntVal: constants.DefaultWANPort + portModifier, - }, - }, - } - } - for _, listener := range b.gateway.Spec.Listeners { - port := int32(listener.Port) - ports = append(ports, corev1.ServicePort{ - Name: listener.Name, - Port: port, - TargetPort: intstr.IntOrString{ - IntVal: port + portModifier, - }, - Protocol: corev1.Protocol(listener.Protocol), - }) - } - return ports -} diff --git a/control-plane/gateways/service_test.go b/control-plane/gateways/service_test.go deleted file mode 100644 index 74d95cd614..0000000000 --- a/control-plane/gateways/service_test.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "testing" - - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -func Test_meshGatewayBuilder_Service(t *testing.T) { - lbType := corev1.ServiceTypeLoadBalancer - - type fields struct { - gateway *meshv2beta1.MeshGateway - config GatewayConfig - gcc *meshv2beta1.GatewayClassConfig - } - tests := []struct { - name string - fields fields - want *corev1.Service - }{ - { - name: "service resource crd created - happy path", - fields: fields{ - gateway: &meshv2beta1.MeshGateway{ - Spec: pbmesh.MeshGateway{ - GatewayClassName: "test-gateway-class", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 443, - Protocol: "TCP", - }, - }, - }, - }, - config: GatewayConfig{}, - gcc: &meshv2beta1.GatewayClassConfig{ - Spec: meshv2beta1.GatewayClassConfigSpec{ - GatewayClassAnnotationsAndLabels: meshv2beta1.GatewayClassAnnotationsAndLabels{ - Labels: meshv2beta1.GatewayClassAnnotationsLabelsConfig{ - Set: map[string]string{ - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - }, - }, - Deployment: meshv2beta1.GatewayClassDeploymentConfig{ - Container: &meshv2beta1.GatewayClassContainerConfig{ - PortModifier: 8000, - }, - }, - Service: meshv2beta1.GatewayClassServiceConfig{ - Type: &lbType, - }, - }, - }, - }, - want: &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - Annotations: map[string]string{}, - }, - Spec: corev1.ServiceSpec{ - Selector: map[string]string{ - labelManagedBy: "consul-k8s", - "app": "consul", - "chart": "consul-helm", - "heritage": "Helm", - "release": "consul", - }, - Type: corev1.ServiceTypeLoadBalancer, - Ports: []corev1.ServicePort{ - { - Name: "wan", - Port: int32(443), - TargetPort: intstr.IntOrString{ - IntVal: int32(8443), - }, - Protocol: "TCP", - }, - }, - }, - Status: corev1.ServiceStatus{}, - }, - }, - { - name: "create service resource crd - gcc is nil", - fields: fields{ - gateway: &meshv2beta1.MeshGateway{ - Spec: pbmesh.MeshGateway{ - GatewayClassName: "test-gateway-class", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 443, - Protocol: "TCP", - }, - }, - }, - }, - config: GatewayConfig{}, - gcc: nil, - }, - want: &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Labels: defaultLabels, - Annotations: map[string]string{}, - }, - Spec: corev1.ServiceSpec{ - Selector: defaultLabels, - Type: "", - Ports: []corev1.ServicePort{ - { - Name: "wan", - Port: int32(443), - TargetPort: intstr.IntOrString{ - IntVal: int32(443), - }, - Protocol: "TCP", - }, - }, - }, - Status: corev1.ServiceStatus{}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &meshGatewayBuilder{ - gateway: tt.fields.gateway, - config: tt.fields.config, - gcc: tt.fields.gcc, - } - result := b.Service() - assert.Equalf(t, tt.want, result, "Service()") - }) - } -} diff --git a/control-plane/gateways/serviceaccount.go b/control-plane/gateways/serviceaccount.go deleted file mode 100644 index 1a0b32c275..0000000000 --- a/control-plane/gateways/serviceaccount.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func (b *meshGatewayBuilder) ServiceAccount() *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.serviceAccountName(), - Namespace: b.gateway.Namespace, - Labels: b.labelsForServiceAccount(), - Annotations: b.annotationsForServiceAccount(), - }, - } -} - -func (b *meshGatewayBuilder) serviceAccountName() string { - return b.gateway.Name -} diff --git a/control-plane/gateways/serviceaccount_test.go b/control-plane/gateways/serviceaccount_test.go deleted file mode 100644 index ff0fb4e878..0000000000 --- a/control-plane/gateways/serviceaccount_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "testing" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -func TestNewMeshGatewayBuilder_ServiceAccount(t *testing.T) { - b := NewMeshGatewayBuilder(&meshv2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, GatewayConfig{}, nil) - - expected := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - Labels: defaultLabels, - Annotations: map[string]string{}, - }, - } - - assert.Equal(t, expected, b.ServiceAccount()) -} diff --git a/control-plane/go.mod b/control-plane/go.mod index ec68dff435..fc736afb46 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -1,35 +1,24 @@ module github.com/hashicorp/consul-k8s/control-plane -// TODO: Remove this when the next version of the submodule is released. -// We need to use a replace directive instead of directly pinning because `api` requires version `0.5.1` and will clobber the pin, but not the replace directive. -replace github.com/hashicorp/consul/proto-public => github.com/hashicorp/consul/proto-public v0.1.2-0.20240129174413-a2d50af1bdfb - -replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f - require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 github.com/deckarep/golang-set v1.7.1 - github.com/evanphx/json-patch v5.6.0+incompatible github.com/fsnotify/fsnotify v1.6.0 - github.com/go-logr/logr v1.2.4 + github.com/go-logr/logr v1.2.3 github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed + github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d github.com/hashicorp/consul-server-connection-manager v0.1.6 - github.com/hashicorp/consul/api v1.10.1-0.20240122152324-758ddf84e9c9 - github.com/hashicorp/consul/proto-public v0.5.1 - github.com/hashicorp/consul/sdk v0.15.0 - github.com/hashicorp/go-bexpr v0.1.11 + github.com/hashicorp/consul/api v1.18.2 + github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-netaddrs v0.1.0 github.com/hashicorp/go-rootcerts v1.0.2 - github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/serf v0.10.1 - github.com/hashicorp/vault/api v1.8.3 github.com/kr/text v0.2.0 github.com/miekg/dns v1.1.50 github.com/mitchellh/cli v1.1.0 @@ -37,21 +26,15 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.8.3 go.uber.org/zap v1.24.0 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/text v0.14.0 + golang.org/x/text v0.13.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 - google.golang.org/grpc v1.56.3 - google.golang.org/protobuf v1.31.0 - gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.26.12 - k8s.io/apimachinery v0.26.12 - k8s.io/client-go v0.26.12 - k8s.io/klog/v2 v2.100.1 - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 - sigs.k8s.io/controller-runtime v0.14.7 - sigs.k8s.io/gateway-api v0.7.1 - sigs.k8s.io/yaml v1.3.0 + k8s.io/api v0.26.1 + k8s.io/apimachinery v0.26.1 + k8s.io/client-go v0.26.1 + k8s.io/klog/v2 v2.90.1 + k8s.io/utils v0.0.0-20230209194617-a36077c30491 + sigs.k8s.io/controller-runtime v0.14.6 ) require ( @@ -73,68 +56,54 @@ require ( github.com/aws/aws-sdk-go v1.44.262 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect - github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 // indirect - github.com/digitalocean/godo v1.7.5 // indirect + github.com/digitalocean/godo v1.10.0 // indirect github.com/dimchansky/utfbom v1.1.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.14.1 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-logr/zapr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/gophercloud/gophercloud v0.1.0 // indirect + github.com/hashicorp/consul/proto-public v0.1.0 // 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-plugin v1.4.5 // indirect - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect - github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect - github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/mdns v1.0.4 // indirect - github.com/hashicorp/vault/sdk v0.7.0 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect - github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 // indirect + github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/linode/linodego v0.7.1 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect - github.com/mitchellh/copystructure v1.0.0 // indirect - github.com/mitchellh/go-testing-interface v1.0.0 // indirect - github.com/mitchellh/pointerstructure v1.2.1 // indirect - github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect - github.com/oklog/run v1.0.0 // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect - github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/posener/complete v1.2.3 // indirect @@ -143,39 +112,43 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658 // indirect github.com/vmware/govmomi v0.18.0 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/grpc v1.56.3 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect - gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.26.10 // indirect - k8s.io/component-base v0.26.10 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.26.1 // indirect + k8s.io/component-base v0.26.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) -go 1.20 +replace github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 + +go 1.19 diff --git a/control-plane/go.sum b/control-plane/go.sum index cc78c06247..882776b3a5 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -68,13 +68,16 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= +github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= @@ -93,11 +96,10 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= -github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -109,8 +111,16 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D 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/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containernetworking/cni v1.1.1 h1:ky20T7c0MvKvbMOwS/FrlbNwjEoqJEUUYfsL4b0mc4k= github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -121,12 +131,14 @@ github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14y github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72MbxIxFUzKmcMCdt9Oi8RzpAxzTNQHD7o= github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/digitalocean/godo v1.7.5 h1:JOQbAO6QT1GGjor0doT0mXefX2FgUDPOpYh2RaXA+ko= -github.com/digitalocean/godo v1.7.5/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/digitalocean/godo v1.10.0 h1:uW1/FcvZE/hoixnJcnlmIUvTVNdZCLjRLzmDtRi1xXY= +github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -140,17 +152,16 @@ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -164,23 +175,26 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= -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/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= -github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -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/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -211,8 +225,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -230,11 +242,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -259,58 +271,48 @@ github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6c github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed h1:eM9tGgSqAZbm4Ndkp35Dg8YROT0dNH3ghTYu5pcUIAc= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d h1:RJ1MZ8JKnfgKQ1kR3IBQAMpOpzXrdseZAYN/QR//MFM= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= 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.10.1-0.20240122152324-758ddf84e9c9 h1:qaS6rE768dt5hGPl2y4DIABXF4eA23BNSmWFpEr3kWQ= -github.com/hashicorp/consul/api v1.10.1-0.20240122152324-758ddf84e9c9/go.mod h1:gInwZGrnWlE1Vvq6rSD5pUf6qwNa69NTLLknbdwQRUk= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240129174413-a2d50af1bdfb h1:4LCdNw3DTe5WRe3fJvY+hkRLNtRlunl/9PJuOlQmPa8= -github.com/hashicorp/consul/proto-public v0.1.2-0.20240129174413-a2d50af1bdfb/go.mod h1:ar/M/Gv31GeE7DxektZgAydwsCiOf6sBeJFcjQVTlVs= -github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f h1:GKsa7bfoL7xgvCkzYJMF9eYYNfLWQyk8QuRZZl4nMTo= -github.com/hashicorp/consul/sdk v0.4.1-0.20231213150639-123bc95e1a3f/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/consul/api v1.18.2 h1:rBzj4IBJMh0n9BWeSwH7B8A0CSCoeafznlGmo+IQZ0E= +github.com/hashicorp/consul/api v1.18.2/go.mod h1:2u+zn9nwubj4ZeCl48w6bEBjZImW1jOz56XXil+QNwQ= +github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= +github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= +github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892 h1:jw0NwPmNPr5CxAU04hACdj61JSaJBKZ0FdBo+kwfNp4= +github.com/hashicorp/consul/sdk v0.4.1-0.20221021205723-cc843c4be892/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= 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= -github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY= -github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 h1:WUwSDou+memX/pb6xnjA0PfAqEEJtdWSrK00kl8ySK8= github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530/go.mod h1:RH2Jr1/cCsZ1nRLmAOC65hp/gRehf55SsUIYV2+NAxI= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-netaddrs v0.1.0 h1:TnlYvODD4C/wO+j7cX1z69kV5gOzI87u3OcUinANaW8= github.com/hashicorp/go-netaddrs v0.1.0/go.mod h1:33+a/emi5R5dqRspOuZKO0E+Tuz5WV1F84eRWALkedA= -github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= -github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= -github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -322,7 +324,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= @@ -331,30 +332,28 @@ github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o= -github.com/hashicorp/vault/api v1.8.3/go.mod h1:4g/9lj9lmuJQMtT6CmVMHC5FW1yENaVv+Nv4ZfG8fAg= -github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg= -github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 h1:JHCT6xuyPUrbbgAPE/3dqlvUKzRHMNuTBKKUb6OeR/k= -github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= +github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= +github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -366,6 +365,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -373,16 +373,18 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -392,12 +394,14 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -405,24 +409,14 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= -github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -436,10 +430,12 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= @@ -455,8 +451,7 @@ github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otz github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= -github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -468,6 +463,7 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -481,6 +477,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= @@ -489,6 +487,7 @@ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -496,16 +495,20 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 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/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= +github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -513,8 +516,18 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQvfjNUeRqaY/uT0tFuvuFY0ulgnczuR684Xic= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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= @@ -525,6 +538,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= @@ -532,19 +546,25 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480 h1:Dwnfdrk3KXpYRH9Kwrk9sHpZSOmrE7P9LBoNsYUJKR4= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.480/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480 h1:YEDZmv2ABU8QvwXEVTOQgVEQzDOByhz73vdjL6sERkE= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480/go.mod h1:zaBIuDDs+rC74X8Aog+LSu91GFtHYRYDC196RGTm2jk= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658 h1:q208plt7F8Pj3b1w8D3XDb/vTgHsn/JlEwDCSe+lvyo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.658/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658 h1:wF/3PTojsx9/J8CaeiTy0zXxvwrcuu282R4g7fDlgCQ= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.658/go.mod h1:LLex9maWMIQzOFF/mYz5rEsTUwKKcmAbGRehSNRWqgQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -552,28 +572,33 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -584,8 +609,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -608,8 +631,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -621,6 +644,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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= @@ -677,13 +701,15 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -692,6 +718,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -700,6 +727,7 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -735,6 +763,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -742,14 +771,13 @@ 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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -758,13 +786,14 @@ 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.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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= @@ -812,8 +841,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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= @@ -884,6 +913,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -909,23 +939,22 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -946,31 +975,29 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.12 h1:jJm3s5ot05SUN3tPGg3b+XWuBE7rO/X0+dnVMhxyd5o= -k8s.io/api v0.26.12/go.mod h1:N+HUXukmtXNOKDngxXrEPbZWggWx01tH/N0nG4nV0oo= -k8s.io/apiextensions-apiserver v0.26.10 h1:wAriTUc6l7gUqJKOxhmXnYo/VNJzk4oh4QLCUR4Uq+k= -k8s.io/apiextensions-apiserver v0.26.10/go.mod h1:N2qhlxkhJLSoC4f0M1/1lNG627b45SYqnOPEVFoQXw4= -k8s.io/apimachinery v0.26.12 h1:y+OgufxqLIZtyXIydRhjLBGzrYLF+qwiDdCFXYOjeN4= -k8s.io/apimachinery v0.26.12/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= -k8s.io/client-go v0.26.12 h1:kPpTpIeFNqwo4UyvoqzNp3DNK2mbGcdGv23eS1U8VMo= -k8s.io/client-go v0.26.12/go.mod h1:V7thEnIFroyNZOU30dKLiiVeqQmJz45shJG1mu7nONQ= -k8s.io/component-base v0.26.10 h1:vl3Gfe5aC09mNxfnQtTng7u3rnBVrShOK3MAkqEleb0= -k8s.io/component-base v0.26.10/go.mod h1:/IDdENUHG5uGxqcofZajovYXE9KSPzJ4yQbkYQt7oN0= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= +k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= +k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= +k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= +k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= +k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= +k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= +k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8= -sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8= -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/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= +sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/control-plane/hack/lint-api-new-client/main.go b/control-plane/hack/lint-api-new-client/main.go index 6297f0abb1..c4c6d896b1 100644 --- a/control-plane/hack/lint-api-new-client/main.go +++ b/control-plane/hack/lint-api-new-client/main.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Parses golang code looking for github.com/hashicorp/consul/api.NewClient() // being used in non-test code. If it finds this, it will error. // The purpose of this lint is that we actually want to use our internal diff --git a/control-plane/helper/cert/notify.go b/control-plane/helper/cert/notify.go index 076c923119..201ca34dd5 100644 --- a/control-plane/helper/cert/notify.go +++ b/control-plane/helper/cert/notify.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cert import ( diff --git a/control-plane/helper/cert/notify_test.go b/control-plane/helper/cert/notify_test.go index 7e7cbf6d68..8813816f86 100644 --- a/control-plane/helper/cert/notify_test.go +++ b/control-plane/helper/cert/notify_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cert import ( diff --git a/control-plane/helper/cert/source.go b/control-plane/helper/cert/source.go index 5a85cb271d..dcc4e3640c 100644 --- a/control-plane/helper/cert/source.go +++ b/control-plane/helper/cert/source.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cert import ( diff --git a/control-plane/helper/cert/source_gen.go b/control-plane/helper/cert/source_gen.go index 9d54f2836c..e9c79ed390 100644 --- a/control-plane/helper/cert/source_gen.go +++ b/control-plane/helper/cert/source_gen.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cert import ( diff --git a/control-plane/helper/cert/source_gen_test.go b/control-plane/helper/cert/source_gen_test.go index fd31de654e..b68ffc7e13 100644 --- a/control-plane/helper/cert/source_gen_test.go +++ b/control-plane/helper/cert/source_gen_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cert import ( diff --git a/control-plane/helper/cert/tls_util.go b/control-plane/helper/cert/tls_util.go index d5552d60f3..37e2f4ea97 100644 --- a/control-plane/helper/cert/tls_util.go +++ b/control-plane/helper/cert/tls_util.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package cert import ( diff --git a/control-plane/helper/coalesce/coalesce.go b/control-plane/helper/coalesce/coalesce.go index bfe341d1d6..d3e96e6be8 100644 --- a/control-plane/helper/coalesce/coalesce.go +++ b/control-plane/helper/coalesce/coalesce.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package coalesce import ( diff --git a/control-plane/helper/coalesce/coalesce_test.go b/control-plane/helper/coalesce/coalesce_test.go index d2c37135c7..8489fed8b4 100644 --- a/control-plane/helper/coalesce/coalesce_test.go +++ b/control-plane/helper/coalesce/coalesce_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package coalesce import ( diff --git a/control-plane/helper/controller/controller.go b/control-plane/helper/controller/controller.go index db6de05b07..12b84b567d 100644 --- a/control-plane/helper/controller/controller.go +++ b/control-plane/helper/controller/controller.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Package controller contains a reusable abstraction for efficiently // watching for changes in resources in a Kubernetes cluster. package controller diff --git a/control-plane/helper/controller/controller_test.go b/control-plane/helper/controller/controller_test.go index a7e960a6aa..43fe677280 100644 --- a/control-plane/helper/controller/controller_test.go +++ b/control-plane/helper/controller/controller_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package controller import ( diff --git a/control-plane/helper/controller/resource.go b/control-plane/helper/controller/resource.go index 9634db51ae..959d101488 100644 --- a/control-plane/helper/controller/resource.go +++ b/control-plane/helper/controller/resource.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package controller import ( diff --git a/control-plane/helper/controller/testing.go b/control-plane/helper/controller/testing.go index e4809c70bd..7575276bf5 100644 --- a/control-plane/helper/controller/testing.go +++ b/control-plane/helper/controller/testing.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package controller import ( diff --git a/control-plane/helper/go-discover/discover.go b/control-plane/helper/go-discover/discover.go index 140586634a..845a7b144b 100644 --- a/control-plane/helper/go-discover/discover.go +++ b/control-plane/helper/go-discover/discover.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package godiscover import ( diff --git a/control-plane/helper/go-discover/discover_test.go b/control-plane/helper/go-discover/discover_test.go index 5c25261516..b655dc388e 100644 --- a/control-plane/helper/go-discover/discover_test.go +++ b/control-plane/helper/go-discover/discover_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package godiscover import ( diff --git a/control-plane/helper/go-discover/mocks/mock_provider.go b/control-plane/helper/go-discover/mocks/mock_provider.go index dfdab445da..51afb86a84 100644 --- a/control-plane/helper/go-discover/mocks/mock_provider.go +++ b/control-plane/helper/go-discover/mocks/mock_provider.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package mocks import ( diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go new file mode 100644 index 0000000000..c3c93b5204 --- /dev/null +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go @@ -0,0 +1,51 @@ +package mutatingwebhookconfiguration + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" +) + +// UpdateWithCABundle iterates over every webhook on the specified webhook configuration and updates +// their caBundle with the the specified CA. +func UpdateWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookConfigName string, caCert []byte) error { + if len(caCert) == 0 { + return errors.New("no CA certificate in the bundle") + } + value := base64.StdEncoding.EncodeToString(caCert) + webhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) + + if err != nil { + return err + } + type patch struct { + Op string `json:"op,omitempty"` + Path string `json:"path,omitempty"` + Value string `json:"value,omitempty"` + } + + var patches []patch + for i := range webhookCfg.Webhooks { + patches = append(patches, patch{ + Op: "add", + Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), + Value: value, + }) + } + patchesJson, err := json.Marshal(patches) + if err != nil { + return err + } + + if _, err = clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(ctx, webhookConfigName, types.JSONPatchType, patchesJson, metav1.PatchOptions{}); err != nil { + return err + } + + return nil +} diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go new file mode 100644 index 0000000000..e247c71d14 --- /dev/null +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go @@ -0,0 +1,44 @@ +package mutatingwebhookconfiguration + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestUpdateWithCABundle_emptyCertReturnsError(t *testing.T) { + var bytes []byte + ctx := context.Background() + clientset := fake.NewSimpleClientset() + + err := UpdateWithCABundle(ctx, clientset, "foo", bytes) + require.Error(t, err, "no CA certificate in the bundle") +} + +func TestUpdateWithCABundle_patchesExistingConfiguration(t *testing.T) { + caBundleOne := []byte("ca-bundle-for-mwc") + ctx := context.Background() + clientset := fake.NewSimpleClientset() + + mwc := &admissionv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mwc-one", + }, + Webhooks: []admissionv1.MutatingWebhook{ + { + Name: "webhook-under-test", + }, + }, + } + mwcCreated, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) + require.NoError(t, err) + err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) + require.NoError(t, err) + mwcFetched, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) +} diff --git a/control-plane/helper/parsetags/parsetags.go b/control-plane/helper/parsetags/parsetags.go index caec75bb1d..e9d9625338 100644 --- a/control-plane/helper/parsetags/parsetags.go +++ b/control-plane/helper/parsetags/parsetags.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package parsetags import ( diff --git a/control-plane/helper/parsetags/parsetags_test.go b/control-plane/helper/parsetags/parsetags_test.go index 2a6b9ad47f..f403a2f711 100644 --- a/control-plane/helper/parsetags/parsetags_test.go +++ b/control-plane/helper/parsetags/parsetags_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package parsetags import ( diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index df51927e4c..0ad4601fde 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -1,10 +1,6 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package test import ( - "context" "fmt" "net" "net/http" @@ -14,37 +10,23 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/slices" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/protobuf/testing/protocmp" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" ) const ( componentAuthMethod = "consul-k8s-component-auth-method" - eventuallyWaitFor = 5 * time.Second - eventuallyTickEvery = 100 * time.Millisecond ) type TestServerClient struct { - TestServer *testutil.TestServer - APIClient *api.Client - Cfg *consul.Config - Watcher consul.ServerConnectionManager - ResourceClient pbresource.ResourceServiceClient + TestServer *testutil.TestServer + APIClient *api.Client + Cfg *consul.Config + Watcher consul.ServerConnectionManager } func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConfigCallback) *TestServerClient { @@ -61,7 +43,7 @@ func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConf t.Cleanup(func() { _ = consulServer.Stop() }) - consulServer.WaitForLeader(t) + consulServer.WaitForSerfCheck(t) consulConfig := &consul.Config{ APIClientConfig: &api.Config{Address: consulServer.HTTPAddr}, @@ -73,50 +55,24 @@ func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConf client, err := api.NewClient(consulConfig.APIClientConfig) require.NoError(t, err) - requireACLBootstrapped(t, cfg, client) - watcher := MockConnMgrForIPAndPort(t, "127.0.0.1", cfg.Ports.GRPC, true) - - // Create a gRPC resource service client when the resource-apis experiment is enabled. - var resourceClient pbresource.ResourceServiceClient - if slices.Contains(cfg.Experiments, "resource-apis") { - resourceClient, err = consul.NewResourceServiceClient(watcher) - require.NoError(t, err) - } - - requireTenancyBuiltins(t, cfg, client, resourceClient) - return &TestServerClient{ - TestServer: consulServer, - APIClient: client, - Cfg: consulConfig, - Watcher: watcher, - ResourceClient: resourceClient, + TestServer: consulServer, + APIClient: client, + Cfg: consulConfig, + Watcher: MockConnMgrForIPAndPort("127.0.0.1", cfg.Ports.GRPC), } } -func MockConnMgrForIPAndPort(t *testing.T, ip string, port int, enableGRPCConn bool) *consul.MockServerConnectionManager { +func MockConnMgrForIPAndPort(ip string, port int) *consul.MockServerConnectionManager { parsedIP := net.ParseIP(ip) connMgr := &consul.MockServerConnectionManager{} - mockState := discovery.State{ Address: discovery.Addr{ TCPAddr: net.TCPAddr{ IP: parsedIP, Port: port, }, - }, - } - - // If the connection is enabled, some tests will receive extra HTTP API calls where - // the server is being dialed. - if enableGRPCConn { - conn, err := grpc.DialContext( - context.Background(), - fmt.Sprintf("%s:%d", parsedIP, port), - grpc.WithTransportCredentials(insecure.NewCredentials())) - require.NoError(t, err) - mockState.GRPCConn = conn - } + }} connMgr.On("State").Return(mockState, nil) connMgr.On("Run").Return(nil) connMgr.On("Stop").Return(nil) @@ -239,19 +195,13 @@ func SetupK8sComponentAuthMethod(t *testing.T, consulClient *api.Client, service // SetupK8sAuthMethod create a k8s auth method and a binding rule in Consul for the // given k8s service and namespace. func SetupK8sAuthMethod(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS string) { - SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "", false) -} - -// SetupK8sAuthMethodV2 create a k8s auth method and a binding rule in Consul for the -// given k8s service and namespace. -func SetupK8sAuthMethodV2(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS string) { - SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "", true) + SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "") } // SetupK8sAuthMethodWithNamespaces creates a k8s auth method and binding rule // in Consul for the k8s service name and namespace. It sets up the auth method and the binding // rule so that it works with consul namespaces. -func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS, consulNS string, mirrorNS bool, nsPrefix string, useV2 bool) { +func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS, consulNS string, mirrorNS bool, nsPrefix string) { t.Helper() // Start the mock k8s server. k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -288,30 +238,14 @@ func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, se require.NoError(t, err) // Create the binding rule. - var aclBindingRule api.ACLBindingRule - if useV2 { - aclBindingRule = api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: AuthMethod, - BindType: api.BindingRuleBindTypeTemplatedPolicy, - BindName: api.ACLTemplatedPolicyWorkloadIdentityName, - BindVars: &api.ACLTemplatedPolicyVariables{ - Name: "${serviceaccount.name}", - }, - Selector: "serviceaccount.name!=default", - Namespace: consulNS, - } - } else { - aclBindingRule = api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: AuthMethod, - BindType: api.BindingRuleBindTypeService, - BindName: "${serviceaccount.name}", - Selector: "serviceaccount.name!=default", - Namespace: consulNS, - } + aclBindingRule := api.ACLBindingRule{ + Description: "Kubernetes binding rule", + AuthMethod: AuthMethod, + BindType: api.BindingRuleBindTypeService, + BindName: "${serviceaccount.name}", + Selector: "serviceaccount.name!=default", + Namespace: consulNS, } - if mirrorNS { aclBindingRule.Namespace = "default" } @@ -320,27 +254,6 @@ func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, se require.NoError(t, err) } -// ResourceHasPersisted checks that a recently written resource exists in the Consul -// state store with a valid version. This must be true before a resource is overwritten -// or deleted. -func ResourceHasPersisted(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID) { - req := &pbresource.ReadRequest{Id: id} - - require.Eventually(t, func() bool { - res, err := client.Read(ctx, req) - if err != nil { - return false - } - - if res.GetResource().GetVersion() == "" { - return false - } - - return true - }, 5*time.Second, - time.Second) -} - func TokenReviewsResponse(name, ns string) string { return fmt.Sprintf(`{ "kind": "TokenReview", @@ -386,16 +299,6 @@ func ServiceAccountGetResponse(name, ns string) string { }`, name, ns, ns, name, name) } -// CmpProtoIgnoreOrder returns a slice of cmp.Option useful for comparing proto messages independent of the order of -// their repeated fields. -func CmpProtoIgnoreOrder() []cmp.Option { - return []cmp.Option{ - protocmp.Transform(), - // Stringify any type passed to the sorter so that we can reliably compare most values. - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - } -} - const AuthMethod = "consul-k8s-auth-method" const ServiceAccountJWTToken = `eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImtoYWtpLWFyYWNobmlkLWNvbnN1bC1jb25uZWN0LWluamVjdG9yLWF1dGhtZXRob2Qtc3ZjLWFjY29obmRidiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJraGFraS1hcmFjaG5pZC1jb25zdWwtY29ubmVjdC1pbmplY3Rvci1hdXRobWV0aG9kLXN2Yy1hY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiN2U5NWUxMjktZTQ3My0xMWU5LThmYWEtNDIwMTBhODAwMTIyIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6a2hha2ktYXJhY2huaWQtY29uc3VsLWNvbm5lY3QtaW5qZWN0b3ItYXV0aG1ldGhvZC1zdmMtYWNjb3VudCJ9.Yi63MMtzh5MBWKKd3a7dzCJjTITE15ikFy_Tnpdk_AwdwA9J4AMSGEeHN5vWtCuuFjo_lMJqBBPHkK2AqbnoFUj9m5CopWyqICJQlvEOP4fUQ-Rc0W1P_JjU1rZERHG39b5TMLgKPQguyhaiZEJ6CjVtm9wUTagrgiuqYV2iUqLuF6SYNm6SrKtkPS-lqIO-u7C06wVk5m5uqwIVQNpZSIC_5Ls5aLmyZU3nHvH-V7E3HmBhVyZAB76jgKB0TyVX1IOskt9PDFarNtU3suZyCjvqC-UJA6sYeySe4dBNKsKlSZ6YuxUUmn1Rgv32YMdImnsWg8khf-zJvqgWk7B5EA` const serviceAccountCACert = `-----BEGIN CERTIFICATE----- @@ -417,69 +320,3 @@ w7/VeA7lzmj3TQRE/W0U0ZGeoAxn9b6JtT0iMucYvP0hXKTPBWlnzIijamU50r2Y Z23jGuk6rn9DUHC2xPj3wCTmd8SGEJoV31noJV5dVeQ90wusXz3vTG7ficKnvHFS xtr5PSwH1DusYfVaGH2O -----END CERTIFICATE-----` - -func requireTenancyBuiltins(t *testing.T, cfg *testutil.TestServerConfig, client *api.Client, resourceClient pbresource.ResourceServiceClient) { - t.Helper() - - // There is a window of time post-leader election on startup where v2 tenancy builtins - // (default partition and namespace) have not yet been created. - // Wait for them to exist before considering the server "open for business". - // Only check for default namespace existence since it implies the default partition exists. - if slices.Contains(cfg.Experiments, "v2tenancy") { - require.EventuallyWithT(t, func(c *assert.CollectT) { - _, err := resourceClient.Read(context.Background(), &pbresource.ReadRequest{ - Id: &pbresource.ID{ - Name: constants.DefaultConsulNS, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: constants.DefaultConsulPartition}, - }, - }) - assert.NoError(c, err) - }, - eventuallyWaitFor, - eventuallyTickEvery, - "failed to eventually read v2 builtin default namespace", - ) - } else { - // Do the same for V1 counterparts in ent only to prevent known test flakes. - require.Eventually(t, - func() bool { - self, err := client.Agent().Self() - if err != nil { - return false - } - if self["DebugConfig"]["VersionMetadata"] != "ent" { - return true - } - - // Check for the default partition instead of the default namespace since this is a thing: - // error="Namespaces are currently disabled until all servers in the datacenter supports the feature" - partition, _, err := client.Partitions().Read( - context.Background(), - constants.DefaultConsulPartition, - nil, - ) - return err == nil && partition != nil - }, - eventuallyWaitFor, - eventuallyTickEvery, - "failed to eventually read v1 builtin default partition") - } -} - -func requireACLBootstrapped(t *testing.T, cfg *testutil.TestServerConfig, client *api.Client) { - t.Helper() - - // Prevent test flakes due to "ACL system must be bootstrapped before ..." error - // by requiring successful retrieval of the initial mgmt token. - if cfg.ACL.Enabled && cfg.ACL.Tokens.InitialManagement != "" { - require.EventuallyWithT(t, func(c *assert.CollectT) { - _, _, err := client.ACL().TokenReadSelf(nil) - assert.NoError(c, err) - }, - eventuallyWaitFor, - eventuallyTickEvery, - "failed to eventually read self token as a proxy for the ACL system bootstrap completion", - ) - } -} diff --git a/control-plane/helper/webhook-configuration/webhook_configuration.go b/control-plane/helper/webhook-configuration/webhook_configuration.go deleted file mode 100644 index be8588434d..0000000000 --- a/control-plane/helper/webhook-configuration/webhook_configuration.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookconfiguration - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - - admissionv1 "k8s.io/api/admissionregistration/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" -) - -// UpdateWithCABundle iterates over every webhook on the specified webhook configuration and updates -// their caBundle with the the specified CA. -func UpdateWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookConfigName string, caCert []byte) error { - if len(caCert) == 0 { - return errors.New("no CA certificate in the bundle") - } - - mutatingWebhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if !k8serrors.IsNotFound(err) { - err = updateMutatingWebhooksWithCABundle(ctx, clientset, mutatingWebhookCfg, caCert) - if err != nil { - return err - } - } - - validatingWebhookCfg, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if k8serrors.IsNotFound(err) { - return nil - } - - return updateValidatingWebhooksWithCABundle(ctx, clientset, validatingWebhookCfg, caCert) -} - -func updateMutatingWebhooksWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookCfg *admissionv1.MutatingWebhookConfiguration, caCert []byte) error { - value := base64.StdEncoding.EncodeToString(caCert) - type patch struct { - Op string `json:"op,omitempty"` - Path string `json:"path,omitempty"` - Value string `json:"value,omitempty"` - } - - var patches []patch - for i := range webhookCfg.Webhooks { - patches = append(patches, patch{ - Op: "add", - Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), - Value: value, - }) - } - patchesJSON, err := json.Marshal(patches) - if err != nil { - return err - } - - if _, err = clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(ctx, webhookCfg.Name, types.JSONPatchType, patchesJSON, metav1.PatchOptions{}); err != nil { - return err - } - - return nil -} - -func updateValidatingWebhooksWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookCfg *admissionv1.ValidatingWebhookConfiguration, caCert []byte) error { - value := base64.StdEncoding.EncodeToString(caCert) - type patch struct { - Op string `json:"op,omitempty"` - Path string `json:"path,omitempty"` - Value string `json:"value,omitempty"` - } - - var patches []patch - for i := range webhookCfg.Webhooks { - patches = append(patches, patch{ - Op: "add", - Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), - Value: value, - }) - } - patchesJSON, err := json.Marshal(patches) - if err != nil { - return err - } - - if _, err = clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Patch(ctx, webhookCfg.Name, types.JSONPatchType, patchesJSON, metav1.PatchOptions{}); err != nil { - return err - } - - return nil -} diff --git a/control-plane/helper/webhook-configuration/webhook_configuration_test.go b/control-plane/helper/webhook-configuration/webhook_configuration_test.go deleted file mode 100644 index bb02573804..0000000000 --- a/control-plane/helper/webhook-configuration/webhook_configuration_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookconfiguration - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - admissionv1 "k8s.io/api/admissionregistration/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" -) - -func TestUpdateWithCABundle_emptyCertReturnsError(t *testing.T) { - var bytes []byte - ctx := context.Background() - clientset := fake.NewSimpleClientset() - - err := UpdateWithCABundle(ctx, clientset, "foo", bytes) - require.Error(t, err, "no CA certificate in the bundle") -} - -func TestUpdateWithCABundle_patchesExistingConfiguration(t *testing.T) { - caBundleOne := []byte("ca-bundle-for-mwc") - ctx := context.Background() - clientset := fake.NewSimpleClientset() - - mwc := &admissionv1.MutatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mwc-one", - }, - Webhooks: []admissionv1.MutatingWebhook{ - { - Name: "webhook-under-test", - }, - }, - } - mwcCreated, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) - require.NoError(t, err) - err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) - require.NoError(t, err) - mwcFetched, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) -} - -func TestUpdateWithCABundle_patchesExistingConfigurationForValidating(t *testing.T) { - caBundleOne := []byte("ca-bundle-for-mwc") - ctx := context.Background() - clientset := fake.NewSimpleClientset() - - mwc := &admissionv1.ValidatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mwc-one", - }, - Webhooks: []admissionv1.ValidatingWebhook{ - { - Name: "webhook-under-test", - }, - }, - } - mwcCreated, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) - require.NoError(t, err) - err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) - require.NoError(t, err) - mwcFetched, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) -} - -func TestUpdateWithCABundle_patchesExistingConfigurationWhenMutatingAndValidatingExist(t *testing.T) { - caBundleOne := []byte("ca-bundle-for-mwc") - ctx := context.Background() - clientset := fake.NewSimpleClientset() - - vwc := &admissionv1.ValidatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mwc-one", - }, - Webhooks: []admissionv1.ValidatingWebhook{ - { - Name: "webhook-under-test", - }, - }, - } - - mwc := &admissionv1.MutatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mwc-one", - }, - Webhooks: []admissionv1.MutatingWebhook{ - { - Name: "webhook-under-test", - }, - }, - } - mwcCreated, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) - require.NoError(t, err) - _, err = clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, vwc, metav1.CreateOptions{}) - require.NoError(t, err) - err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) - require.NoError(t, err) - vwcFetched, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, vwc.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, caBundleOne, vwcFetched.Webhooks[0].ClientConfig.CABundle) - - mwcFetched, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) -} diff --git a/control-plane/main.go b/control-plane/main.go index 64ccd5d43a..7ec1340290 100644 --- a/control-plane/main.go +++ b/control-plane/main.go @@ -1,15 +1,11 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( "log" "os" - "github.com/mitchellh/cli" - "github.com/hashicorp/consul-k8s/control-plane/version" + "github.com/mitchellh/cli" ) func main() { diff --git a/control-plane/namespaces/namespaces.go b/control-plane/namespaces/namespaces.go index 8378c3cc89..c17aa1f788 100644 --- a/control-plane/namespaces/namespaces.go +++ b/control-plane/namespaces/namespaces.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Package namespaces handles interaction with Consul namespaces needed across // commands. package namespaces @@ -55,29 +52,6 @@ func EnsureExists(client *capi.Client, ns string, crossNSAClPolicy string) (bool return true, err } -// EnsureDeleted ensures a Consul namespace with name ns is deleted. If it is already not found -// the call to delete will be skipped. -func EnsureDeleted(client *capi.Client, ns string) error { - if ns == WildcardNamespace || ns == DefaultNamespace { - return nil - } - // Check if the Consul namespace exists. - namespaceInfo, _, err := client.Namespaces().Read(ns, nil) - if err != nil { - return fmt.Errorf("could not read namespace %s: %w", ns, err) - } - if namespaceInfo == nil { - return nil - } - - // If not empty, delete it. - _, err = client.Namespaces().Delete(ns, nil) - if err != nil { - return fmt.Errorf("could not delete namespace %s: %w", ns, err) - } - return nil -} - // ConsulNamespace returns the consul namespace that a service should be // registered in based on the namespace options. It returns an // empty string if namespaces aren't enabled. @@ -93,12 +67,3 @@ func ConsulNamespace(kubeNS string, enableConsulNamespaces bool, consulDestNS st return consulDestNS } - -// NonDefaultConsulNamespace returns the given Consul namespace if it is not default or empty. -// Otherwise, it returns the empty string. -func NonDefaultConsulNamespace(consulNS string) string { - if consulNS == "" || consulNS == DefaultNamespace { - return "" - } - return consulNS -} diff --git a/control-plane/namespaces/namespaces_test.go b/control-plane/namespaces/namespaces_test.go index 6e1305d959..7b6a061a65 100644 --- a/control-plane/namespaces/namespaces_test.go +++ b/control-plane/namespaces/namespaces_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise package namespaces @@ -40,7 +37,7 @@ func TestEnsureExists_AlreadyExists(tt *testing.T) { }) req.NoError(err) defer consul.Stop() - consul.WaitForLeader(t) + consul.WaitForSerfCheck(t) consulClient, err := capi.NewClient(&capi.Config{ Address: consul.HTTPAddr, Token: masterToken, @@ -159,56 +156,6 @@ func TestEnsureExists_CreatesNS(tt *testing.T) { } } -// Test that it creates the namespace if it doesn't exist. -func TestEnsureDelete(tt *testing.T) { - name := "foo" - for _, c := range []struct { - NamespaceExists bool - }{ - { - NamespaceExists: true, - }, - { - NamespaceExists: false, - }, - } { - tt.Run(fmt.Sprintf("namespace: %t", c.NamespaceExists), func(t *testing.T) { - consul, err := testutil.NewTestServerConfigT(t, nil) - require.NoError(t, err) - defer consul.Stop() - consul.WaitForLeader(t) - - consulClient, err := capi.NewClient(&capi.Config{ - Address: consul.HTTPAddr, - }) - require.NoError(t, err) - - if c.NamespaceExists { - ns := capi.Namespace{ - Name: name, - } - _, _, err = consulClient.Namespaces().Create(&ns, nil) - require.NoError(t, err) - - check, _, err := consulClient.Namespaces().Read(name, nil) - require.NoError(t, err) - require.NotNil(t, check) - require.Equal(t, name, check.Name) - } - - err = EnsureDeleted(consulClient, name) - require.NoError(t, err) - - // Ensure it was deleted. - cNS, _, err := consulClient.Namespaces().Read(name, nil) - require.NoError(t, err) - if cNS != nil && cNS.DeletedAt == nil { - require.Fail(t, "the namespace was not deleted") - } - }) - } -} - func TestConsulNamespace(t *testing.T) { cases := map[string]struct { kubeNS string diff --git a/control-plane/subcommand/acl-init/command.go b/control-plane/subcommand/acl-init/command.go index 24ae5c7ebf..af85128ea8 100644 --- a/control-plane/subcommand/acl-init/command.go +++ b/control-plane/subcommand/acl-init/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package aclinit import ( @@ -18,6 +15,10 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-netaddrs" @@ -25,11 +26,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( diff --git a/control-plane/subcommand/acl-init/command_test.go b/control-plane/subcommand/acl-init/command_test.go index ca236bb031..c9f5703459 100644 --- a/control-plane/subcommand/acl-init/command_test.go +++ b/control-plane/subcommand/acl-init/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package aclinit import ( @@ -13,15 +10,14 @@ import ( "testing" "text/template" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" 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/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) const ( diff --git a/control-plane/subcommand/auth.go b/control-plane/subcommand/auth.go index 6389e7d6f7..58a9b801e8 100644 --- a/control-plane/subcommand/auth.go +++ b/control-plane/subcommand/auth.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package subcommand import ( diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index 2b1475daa4..00535cb6fe 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Package common holds code needed by multiple commands. package common @@ -13,13 +10,12 @@ import ( "github.com/cenkalti/backoff" "github.com/go-logr/logr" + godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "go.uber.org/zap/zapcore" "sigs.k8s.io/controller-runtime/pkg/log/zap" - - godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" ) const ( diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index 2925638c6e..bce90d6b76 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( @@ -11,14 +8,14 @@ import ( "net/url" "os" "testing" + "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover/mocks" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover/mocks" ) func TestLogger_InvalidLogLevel(t *testing.T) { @@ -166,33 +163,36 @@ func TestConsulLogin_TokenNotReplicated(t *testing.T) { func TestConsulLogin_EmptyBearerTokenFile(t *testing.T) { t.Parallel() + require := require.New(t) bearerTokenFile := WriteTempFile(t, "") params := LoginParams{ BearerTokenFile: bearerTokenFile, } _, err := ConsulLogin(nil, params, hclog.NewNullLogger()) - require.EqualError(t, err, fmt.Sprintf("no bearer token found in %q", bearerTokenFile)) + require.EqualError(err, fmt.Sprintf("no bearer token found in %q", bearerTokenFile)) } func TestConsulLogin_BearerTokenFileDoesNotExist(t *testing.T) { t.Parallel() + require := require.New(t) randFileName := fmt.Sprintf("/foo/%d/%d", rand.Int(), rand.Int()) params := LoginParams{ BearerTokenFile: randFileName, } _, err := ConsulLogin(nil, params, hclog.NewNullLogger()) - require.Error(t, err) - require.Contains(t, err.Error(), "unable to read bearer token file") + require.Error(err) + require.Contains(err.Error(), "unable to read bearer token file") } func TestConsulLogin_TokenFileUnwritable(t *testing.T) { t.Parallel() + require := require.New(t) bearerTokenFile := WriteTempFile(t, "foo") client := startMockServer(t) // This is a common.Logger. log, err := Logger("INFO", false) - require.NoError(t, err) + require.NoError(err) randFileName := fmt.Sprintf("/foo/%d/%d", rand.Int(), rand.Int()) params := LoginParams{ AuthMethod: testAuthMethod, @@ -201,12 +201,13 @@ func TestConsulLogin_TokenFileUnwritable(t *testing.T) { NumRetries: 2, } _, err = ConsulLogin(client, params, log) - require.Error(t, err) - require.Contains(t, err.Error(), "error writing token to file sink") + require.Error(err) + require.Contains(err.Error(), "error writing token to file sink") } func TestWriteFileWithPerms_InvalidOutputFile(t *testing.T) { t.Parallel() + rand.New(rand.NewSource(time.Now().UnixNano())) randFileName := fmt.Sprintf("/tmp/tmp/tmp/%d", rand.Int()) t.Cleanup(func() { os.RemoveAll(randFileName) @@ -217,6 +218,7 @@ func TestWriteFileWithPerms_InvalidOutputFile(t *testing.T) { func TestWriteFileWithPerms_OutputFileExists(t *testing.T) { t.Parallel() + rand.New(rand.NewSource(time.Now().UnixNano())) randFileName := fmt.Sprintf("/tmp/%d", rand.Int()) err := os.WriteFile(randFileName, []byte("foo"), os.FileMode(0444)) require.NoError(t, err) @@ -234,6 +236,7 @@ func TestWriteFileWithPerms_OutputFileExists(t *testing.T) { func TestWriteFileWithPerms(t *testing.T) { t.Parallel() payload := "foo-foo-foo-foo" + rand.New(rand.NewSource(time.Now().UnixNano())) randFileName := fmt.Sprintf("/tmp/%d", rand.Int()) t.Cleanup(func() { os.RemoveAll(randFileName) diff --git a/control-plane/subcommand/common/test_util.go b/control-plane/subcommand/common/test_util.go index 3399b40e2b..13d9017fe4 100644 --- a/control-plane/subcommand/common/test_util.go +++ b/control-plane/subcommand/common/test_util.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package common import ( diff --git a/control-plane/subcommand/connect-init/command.go b/control-plane/subcommand/connect-init/command.go index 2d245797dc..4750e9455c 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connectinit import ( @@ -17,19 +14,17 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/iptables" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" "github.com/mitchellh/mapstructure" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - "github.com/hashicorp/consul-k8s/control-plane/version" ) const ( @@ -163,17 +158,6 @@ func (c *Command) Run(args []string) int { c.logger.Error("Unable to get client connection", "error", err) return 1 } - if version.IsFIPS() { - // make sure we are also using FIPS Consul - var versionInfo map[string]interface{} - _, err := consulClient.Raw().Query("/v1/agent/version", versionInfo, nil) - if err != nil { - c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. Unable to verify FIPS Consul while setting up Consul API client.") - } - if val, ok := versionInfo["FIPS"]; !ok || val == "" { - c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. A non-FIPS version of Consul was detected.") - } - } proxyService := &api.AgentService{} if c.flagGatewayKind != "" { err = backoff.Retry(c.getGatewayRegistration(consulClient), backoff.WithMaxRetries(backoff.NewConstantBackOff(1*time.Second), c.serviceRegistrationPollingAttempts)) @@ -199,14 +183,13 @@ func (c *Command) Run(args []string) int { // todo (agentless): this should eventually be passed to consul-dataplane as a string so we don't need to write it to file. if c.consul.UseTLS && c.consul.CACertPEM != "" { - if err = common.WriteFileWithPerms(constants.LegacyConsulCAFile, c.consul.CACertPEM, 0444); err != nil { + if err = common.WriteFileWithPerms(constants.ConsulCAFile, c.consul.CACertPEM, 0444); err != nil { c.logger.Error("error writing CA cert file", "error", err) return 1 } } if c.flagRedirectTrafficConfig != "" { - c.watcher.Stop() // Explicitly stop the watcher so that ACLs are cleaned up before we apply re-direction. err = c.applyTrafficRedirectionRules(proxyService) if err != nil { c.logger.Error("error applying traffic redirection rules", "err", err) @@ -330,7 +313,7 @@ func (c *Command) getGatewayRegistration(client *api.Client) backoff.Operation { } for _, gateway := range gatewayList.Services { switch gateway.Kind { - case api.ServiceKindAPIGateway, api.ServiceKindMeshGateway, api.ServiceKindIngressGateway, api.ServiceKindTerminatingGateway: + case api.ServiceKindMeshGateway, api.ServiceKindIngressGateway, api.ServiceKindTerminatingGateway: proxyID = gateway.ID } } diff --git a/control-plane/subcommand/connect-init/command_ent_test.go b/control-plane/subcommand/connect-init/command_ent_test.go index 743bd511fd..ecdc34122e 100644 --- a/control-plane/subcommand/connect-init/command_ent_test.go +++ b/control-plane/subcommand/connect-init/command_ent_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise package connectinit @@ -12,12 +9,11 @@ import ( "strconv" "testing" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func TestRun_WithNamespaces(t *testing.T) { diff --git a/control-plane/subcommand/connect-init/command_test.go b/control-plane/subcommand/connect-init/command_test.go index f756ac7359..14bdc5280c 100644 --- a/control-plane/subcommand/connect-init/command_test.go +++ b/control-plane/subcommand/connect-init/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connectinit import ( @@ -14,14 +11,13 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/iptables" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) const nodeName = "test-node" @@ -616,7 +612,7 @@ func TestRun_Gateways_Errors(t *testing.T) { "-pod-name", testPodName, "-pod-namespace", testPodNamespace, "-proxy-id-file", proxyFile, - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", "-consul-node-name", nodeName, } @@ -730,7 +726,7 @@ func TestRun_InvalidProxyFile(t *testing.T) { "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), "-proxy-id-file", randFileName, - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", } code := cmd.Run(flags) require.Equal(t, 1, code) diff --git a/control-plane/subcommand/consul-logout/command.go b/control-plane/subcommand/consul-logout/command.go index b556556d43..b0836b71f0 100644 --- a/control-plane/subcommand/consul-logout/command.go +++ b/control-plane/subcommand/consul-logout/command.go @@ -1,19 +1,15 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consullogout import ( "flag" "sync" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" ) const ( diff --git a/control-plane/subcommand/consul-logout/command_test.go b/control-plane/subcommand/consul-logout/command_test.go index 3b4d6d39cc..22412ea752 100644 --- a/control-plane/subcommand/consul-logout/command_test.go +++ b/control-plane/subcommand/consul-logout/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package consullogout import ( @@ -9,13 +6,12 @@ import ( "os" "testing" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) func TestRun_FlagValidation(t *testing.T) { @@ -55,7 +51,7 @@ func TestRun_InvalidSinkFile(t *testing.T) { } code := cmd.Run([]string{ "-token-file", randFileName, - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, code) } @@ -108,7 +104,7 @@ func Test_UnableToLogoutDueToInvalidToken(t *testing.T) { code := cmd.Run([]string{ "-http-addr", fmt.Sprintf("%s://%s", cfg.Scheme, cfg.Address), "-token-file", tokenFile, - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, code, ui.ErrorWriter.String()) require.Contains(t, "Unexpected response code: 403 (ACL not found)", ui.ErrorWriter.String()) @@ -173,7 +169,7 @@ func Test_RunUsingLogin(t *testing.T) { code := cmd.Run([]string{ "-http-addr", fmt.Sprintf("%s://%s", cfg.Scheme, cfg.Address), "-token-file", tokenFile, - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 0, code, ui.ErrorWriter.String()) diff --git a/control-plane/subcommand/create-federation-secret/command.go b/control-plane/subcommand/create-federation-secret/command.go index 2d6b66cb83..3a96e18134 100644 --- a/control-plane/subcommand/create-federation-secret/command.go +++ b/control-plane/subcommand/create-federation-secret/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package createfederationsecret import ( @@ -15,6 +12,10 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" @@ -22,11 +23,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( diff --git a/control-plane/subcommand/create-federation-secret/command_test.go b/control-plane/subcommand/create-federation-secret/command_test.go index 954882e7f0..86ff8aec18 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package createfederationsecret import ( @@ -14,6 +11,8 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -23,9 +22,6 @@ 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/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) func TestRun_FlagValidation(t *testing.T) { @@ -81,7 +77,7 @@ func TestRun_FlagValidation(t *testing.T) { "-server-ca-key-file=file", "-ca-file", f.Name(), "-mesh-gateway-service-name=name", - "-consul-api-timeout=10s", + "-consul-api-timeout=5s", "-log-level=invalid", }, expErr: "unknown log level: invalid", @@ -118,7 +114,7 @@ func TestRun_CAFileMissing(t *testing.T) { "-server-ca-cert-file", f.Name(), "-server-ca-key-file", f.Name(), "-ca-file=/this/does/not/exist", - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "error reading CA file") @@ -141,7 +137,7 @@ func TestRun_ServerCACertFileMissing(t *testing.T) { "-ca-file", f.Name(), "-server-ca-cert-file=/this/does/not/exist", "-server-ca-key-file", f.Name(), - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "Error reading server CA cert file") @@ -164,7 +160,7 @@ func TestRun_ServerCAKeyFileMissing(t *testing.T) { "-ca-file", f.Name(), "-server-ca-cert-file", f.Name(), "-server-ca-key-file=/this/does/not/exist", - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "Error reading server CA key file") @@ -188,7 +184,7 @@ func TestRun_GossipEncryptionKeyFileMissing(t *testing.T) { "-server-ca-cert-file", f.Name(), "-server-ca-key-file", f.Name(), "-gossip-key-file=/this/does/not/exist", - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "Error reading gossip encryption key file") @@ -212,7 +208,7 @@ func TestRun_GossipEncryptionKeyFileEmpty(t *testing.T) { "-server-ca-cert-file", f.Name(), "-server-ca-key-file", f.Name(), "-gossip-key-file", f.Name(), - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), fmt.Sprintf("gossip key file %q was empty", f.Name())) @@ -250,7 +246,7 @@ func TestRun_ReplicationTokenMissingExpectedKey(t *testing.T) { "-server-ca-cert-file", f.Name(), "-server-ca-key-file", f.Name(), "-export-replication-token", - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) } @@ -845,7 +841,7 @@ func TestRun_ReplicationSecretDelay(t *testing.T) { "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://%s", testserver.HTTPSAddr), "-export-replication-token", - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", } exitCode := cmd.Run(flags) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -993,7 +989,7 @@ func TestRun_ConsulClientDelay(t *testing.T) { timer := &retry.Timer{Timeout: 10 * time.Second, Wait: 500 * time.Millisecond} retry.RunWith(timer, t, func(r *retry.R) { var err error - testserver, err = testutil.NewTestServerConfigT(r, func(cfg *testutil.TestServerConfig) { + testserver, err = testutil.NewTestServerConfigT(t, func(cfg *testutil.TestServerConfig) { cfg.CAFile = caFile cfg.CertFile = certFile cfg.KeyFile = keyFile @@ -1053,7 +1049,7 @@ func TestRun_ConsulClientDelay(t *testing.T) { "-server-ca-cert-file", caFile, "-server-ca-key-file", keyFile, "-http-addr", fmt.Sprintf("https://127.0.0.1:%d", randomPorts[2]), - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", } exitCode := cmd.Run(flags) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) diff --git a/control-plane/subcommand/delete-completed-job/command.go b/control-plane/subcommand/delete-completed-job/command.go index eb3705223f..273e621a2a 100644 --- a/control-plane/subcommand/delete-completed-job/command.go +++ b/control-plane/subcommand/delete-completed-job/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package deletecompletedjob import ( @@ -10,16 +7,15 @@ import ( "sync" "time" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/mitchellh/cli" v1 "k8s.io/api/batch/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) // Command is the command for deleting completed jobs. diff --git a/control-plane/subcommand/delete-completed-job/command_test.go b/control-plane/subcommand/delete-completed-job/command_test.go index abfbb0e788..0da056fc88 100644 --- a/control-plane/subcommand/delete-completed-job/command_test.go +++ b/control-plane/subcommand/delete-completed-job/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package deletecompletedjob import ( diff --git a/control-plane/subcommand/fetch-server-region/command.go b/control-plane/subcommand/fetch-server-region/command.go deleted file mode 100644 index 58297a10da..0000000000 --- a/control-plane/subcommand/fetch-server-region/command.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package fetchserverregion - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "os" - "sync" - - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" -) - -// The consul-logout command issues a Consul logout API request to delete an ACL token. -type Command struct { - UI cli.Ui - - flagLogLevel string - flagLogJSON bool - flagNodeName string - flagOutputFile string - - flagSet *flag.FlagSet - k8s *flags.K8SFlags - - once sync.Once - help string - logger hclog.Logger - - // for testing - clientset kubernetes.Interface -} - -type Locality struct { - Region string `json:"region"` -} - -type Config struct { - Locality Locality `json:"locality"` -} - -func (c *Command) init() { - c.flagSet = flag.NewFlagSet("", flag.ContinueOnError) - c.flagSet.StringVar(&c.flagLogLevel, "log-level", "info", - "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ - "\"debug\", \"info\", \"warn\", and \"error\".") - c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, - "Enable or disable JSON output format for logging.") - c.flagSet.StringVar(&c.flagNodeName, "node-name", "", - "Specifies the node name that will be used.") - c.flagSet.StringVar(&c.flagOutputFile, "output-file", "", - "The file path for writing the locality portion of a Consul agent configuration to.") - - c.k8s = &flags.K8SFlags{} - flags.Merge(c.flagSet, c.k8s.Flags()) - - c.help = flags.Usage(help, c.flagSet) - -} - -func (c *Command) Run(args []string) int { - var err error - c.once.Do(c.init) - - if err := c.flagSet.Parse(args); err != nil { - return 1 - } - - if c.logger == nil { - c.logger, err = common.Logger(c.flagLogLevel, c.flagLogJSON) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - - if c.flagNodeName == "" { - c.UI.Error("-node-name is required") - return 1 - } - - if c.flagOutputFile == "" { - c.UI.Error("-output-file is required") - return 1 - } - - if c.clientset == nil { - config, err := rest.InClusterConfig() - if err != nil { - // This just allows us to test it locally. - kubeconfig := clientcmd.RecommendedHomeFile - config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - - c.clientset, err = kubernetes.NewForConfig(config) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - - config := c.fetchLocalityConfig() - - jsonData, err := json.Marshal(config) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - - err = os.WriteFile(c.flagOutputFile, jsonData, 0644) - if err != nil { - c.UI.Error(fmt.Sprintf("Error writing locality file: %s", err)) - return 1 - } - - return 0 -} - -func (c *Command) fetchLocalityConfig() Config { - var cfg Config - node, err := c.clientset.CoreV1().Nodes().Get(context.Background(), c.flagNodeName, metav1.GetOptions{}) - if err != nil { - return cfg - } - - cfg.Locality.Region = node.Labels[corev1.LabelTopologyRegion] - - return cfg -} - -func (c *Command) Synopsis() string { return synopsis } -func (c *Command) Help() string { - c.once.Do(c.init) - return c.help -} - -const synopsis = "Fetch the cloud region for a Consul server from the Kubernetes node's region label." -const help = ` -Usage: consul-k8s-control-plane fetch-server-region [options] - - Fetch the region for a Consul server. - Not intended for stand-alone use. -` diff --git a/control-plane/subcommand/fetch-server-region/command_test.go b/control-plane/subcommand/fetch-server-region/command_test.go deleted file mode 100644 index a64dc9be95..0000000000 --- a/control-plane/subcommand/fetch-server-region/command_test.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package fetchserverregion - -import ( - "os" - "testing" - - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" -) - -func TestRun_FlagValidation(t *testing.T) { - t.Parallel() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - } - - cases := map[string]struct { - args []string - err string - }{ - "missing node name": { - args: []string{}, - err: "-node-name is required", - }, - "missing output-file": { - args: []string{"-node-name", "n1"}, - err: "-output-file is required", - }, - } - - for n, c := range cases { - c := c - t.Run(n, func(t *testing.T) { - responseCode := cmd.Run(c.args) - require.Equal(t, 1, responseCode, ui.ErrorWriter.String()) - require.Contains(t, ui.ErrorWriter.String(), c.err) - }) - } -} - -func TestRun(t *testing.T) { - t.Parallel() - - cases := map[string]struct { - region string - expected string - missingNode bool - }{ - "no region": { - expected: `{"locality":{"region":""}}`, - }, - "region": { - region: "us-east-1", - expected: `{"locality":{"region":"us-east-1"}}`, - }, - "missing node": { - region: "us-east-1", - missingNode: true, - expected: `{"locality":{"region":""}}`, - }, - } - - for n, c := range cases { - c := c - t.Run(n, func(t *testing.T) { - outputFile, err := os.CreateTemp("", "ca") - require.NoError(t, err) - t.Cleanup(func() { - os.RemoveAll(outputFile.Name()) - }) - - var objs []runtime.Object - if !c.missingNode { - objs = append(objs, &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-node", - Labels: map[string]string{ - corev1.LabelTopologyRegion: c.region, - }, - }, - }) - } - - k8s := fake.NewSimpleClientset(objs...) - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - } - - responseCode := cmd.Run([]string{ - "-node-name", - "my-node", - "-output-file", - outputFile.Name(), - }) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - require.NoError(t, err) - cfg, err := os.ReadFile(outputFile.Name()) - require.NoError(t, err) - require.Equal(t, c.expected, string(cfg)) - }) - } -} diff --git a/control-plane/subcommand/flags/consul.go b/control-plane/subcommand/flags/consul.go index e155013258..1294729a6b 100644 --- a/control-plane/subcommand/flags/consul.go +++ b/control-plane/subcommand/flags/consul.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( @@ -11,7 +8,6 @@ import ( "strings" "time" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" @@ -27,6 +23,11 @@ const ( PartitionEnvVar = "CONSUL_PARTITION" DatacenterEnvVar = "CONSUL_DATACENTER" + UseTLSEnvVar = "CONSUL_USE_TLS" + CACertFileEnvVar = "CONSUL_CACERT_FILE" + CACertPEMEnvVar = "CONSUL_CACERT_PEM" + TLSServerNameEnvVar = "CONSUL_TLS_SERVER_NAME" + ACLTokenEnvVar = "CONSUL_ACL_TOKEN" ACLTokenFileEnvVar = "CONSUL_ACL_TOKEN_FILE" @@ -89,7 +90,7 @@ func (f *ConsulFlags) Flags() *flag.FlagSet { // behave as if that env variable is not provided. grpcPort, _ := strconv.Atoi(os.Getenv(GRPCPortEnvVar)) httpPort, _ := strconv.Atoi(os.Getenv(HTTPPortEnvVar)) - useTLS, _ := strconv.ParseBool(os.Getenv(constants.UseTLSEnvVar)) + useTLS, _ := strconv.ParseBool(os.Getenv(UseTLSEnvVar)) skipServerWatch, _ := strconv.ParseBool(os.Getenv(SkipServerWatchEnvVar)) consulLoginMetaFromEnv := os.Getenv(LoginMetaEnvVar) if consulLoginMetaFromEnv != "" { @@ -138,11 +139,11 @@ func (f *ConsulFlags) Flags() *flag.FlagSet { "[Enterprise only] Consul admin partition. Default to \"default\" if Admin Partitions are enabled.") fs.StringVar(&f.Datacenter, "datacenter", os.Getenv(DatacenterEnvVar), "Consul datacenter.") - fs.StringVar(&f.CACertFile, "ca-cert-file", os.Getenv(constants.CACertFileEnvVar), + fs.StringVar(&f.CACertFile, "ca-cert-file", os.Getenv(CACertFileEnvVar), "Path to a CA certificate to use for TLS when communicating with Consul.") - fs.StringVar(&f.CACertPEM, "ca-cert-pem", os.Getenv(constants.CACertPEMEnvVar), + fs.StringVar(&f.CACertPEM, "ca-cert-pem", os.Getenv(CACertPEMEnvVar), "CA certificate PEM to use for TLS when communicating with Consul.") - fs.StringVar(&f.TLSServerName, "tls-server-name", os.Getenv(constants.TLSServerNameEnvVar), + fs.StringVar(&f.TLSServerName, "tls-server-name", os.Getenv(TLSServerNameEnvVar), "The server name to use as the SNI host when connecting via TLS. "+ "This can also be specified via the CONSUL_TLS_SERVER_NAME environment variable.") fs.BoolVar(&f.UseTLS, "use-tls", useTLS, "If true, use TLS for connections to Consul.") diff --git a/control-plane/subcommand/flags/consul_test.go b/control-plane/subcommand/flags/consul_test.go index e51860024c..b53025daa6 100644 --- a/control-plane/subcommand/flags/consul_test.go +++ b/control-plane/subcommand/flags/consul_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( @@ -9,7 +6,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" @@ -30,10 +26,10 @@ func TestConsulFlags_Flags(t *testing.T) { DatacenterEnvVar: "test-dc", APITimeoutEnvVar: "10s", - constants.UseTLSEnvVar: "true", - constants.CACertFileEnvVar: "path/to/ca.pem", - constants.CACertPEMEnvVar: "test-ca-pem", - constants.TLSServerNameEnvVar: "server.consul", + UseTLSEnvVar: "true", + CACertFileEnvVar: "path/to/ca.pem", + CACertPEMEnvVar: "test-ca-pem", + TLSServerNameEnvVar: "server.consul", ACLTokenEnvVar: "test-token", ACLTokenFileEnvVar: "/path/to/token", @@ -90,7 +86,7 @@ func TestConsulFlags_Flags(t *testing.T) { HTTPPortEnvVar: "not-int-http-port", APITimeoutEnvVar: "10sec", - constants.UseTLSEnvVar: "not-a-bool", + UseTLSEnvVar: "not-a-bool", LoginMetaEnvVar: "key1:value1;key2:value2", }, diff --git a/control-plane/subcommand/flags/flag_map_value.go b/control-plane/subcommand/flags/flag_map_value.go index c647f57bf5..9ddb4dad08 100644 --- a/control-plane/subcommand/flags/flag_map_value.go +++ b/control-plane/subcommand/flags/flag_map_value.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( diff --git a/control-plane/subcommand/flags/flag_map_value_test.go b/control-plane/subcommand/flags/flag_map_value_test.go index ea68b6b312..a3b7659cc4 100644 --- a/control-plane/subcommand/flags/flag_map_value_test.go +++ b/control-plane/subcommand/flags/flag_map_value_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( diff --git a/control-plane/subcommand/flags/flag_slice_value.go b/control-plane/subcommand/flags/flag_slice_value.go index 42c45562b2..11e838e683 100644 --- a/control-plane/subcommand/flags/flag_slice_value.go +++ b/control-plane/subcommand/flags/flag_slice_value.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import "strings" diff --git a/control-plane/subcommand/flags/flag_slice_value_test.go b/control-plane/subcommand/flags/flag_slice_value_test.go index 24cf4df695..e361c12b10 100644 --- a/control-plane/subcommand/flags/flag_slice_value_test.go +++ b/control-plane/subcommand/flags/flag_slice_value_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( diff --git a/control-plane/subcommand/flags/flags.go b/control-plane/subcommand/flags/flags.go index 6739684bf0..e290e876d7 100644 --- a/control-plane/subcommand/flags/flags.go +++ b/control-plane/subcommand/flags/flags.go @@ -1,5 +1,2 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Package flags holds common flags that are shared between our commands. package flags diff --git a/control-plane/subcommand/flags/http.go b/control-plane/subcommand/flags/http.go index 5421b4c672..74db3c26dc 100644 --- a/control-plane/subcommand/flags/http.go +++ b/control-plane/subcommand/flags/http.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( @@ -9,9 +6,8 @@ import ( "strings" "time" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul/api" ) // Taken from https://github.com/hashicorp/consul/blob/b5b9c8d953cd3c79c6b795946839f4cf5012f507/command/flags/http.go diff --git a/control-plane/subcommand/flags/http_test.go b/control-plane/subcommand/flags/http_test.go index 3292661933..a44f79931c 100644 --- a/control-plane/subcommand/flags/http_test.go +++ b/control-plane/subcommand/flags/http_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( diff --git a/control-plane/subcommand/flags/k8s.go b/control-plane/subcommand/flags/k8s.go index 15f1a996b9..31a2284f65 100644 --- a/control-plane/subcommand/flags/k8s.go +++ b/control-plane/subcommand/flags/k8s.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( diff --git a/control-plane/subcommand/flags/mapset.go b/control-plane/subcommand/flags/mapset.go index d97419a827..c58cc9a3a2 100644 --- a/control-plane/subcommand/flags/mapset.go +++ b/control-plane/subcommand/flags/mapset.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import "github.com/deckarep/golang-set" diff --git a/control-plane/subcommand/flags/usage.go b/control-plane/subcommand/flags/usage.go index df63d9c8a6..960ce85926 100644 --- a/control-plane/subcommand/flags/usage.go +++ b/control-plane/subcommand/flags/usage.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( diff --git a/control-plane/subcommand/flags/usage_test.go b/control-plane/subcommand/flags/usage_test.go index 7e27ad77fb..801888e549 100644 --- a/control-plane/subcommand/flags/usage_test.go +++ b/control-plane/subcommand/flags/usage_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package flags import ( diff --git a/control-plane/subcommand/gateway-cleanup/command.go b/control-plane/subcommand/gateway-cleanup/command.go deleted file mode 100644 index 709f925c66..0000000000 --- a/control-plane/subcommand/gateway-cleanup/command.go +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatewaycleanup - -import ( - "context" - "errors" - "flag" - "fmt" - "io" - "os" - "sync" - "time" - - "github.com/cenkalti/backoff" - "github.com/mitchellh/cli" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - k8syaml "sigs.k8s.io/yaml" - - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/gateways" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" -) - -const ( - gatewayConfigFilename = "/consul/config/config.yaml" - resourceConfigFilename = "/consul/config/resources.json" -) - -type Command struct { - UI cli.Ui - - flags *flag.FlagSet - k8s *flags.K8SFlags - - flagGatewayClassName string - flagGatewayClassConfigName string - flagGatewayConfigLocation string - flagResourceConfigFileLocation string - - k8sClient client.Client - - once sync.Once - help string - - gatewayConfig gateways.GatewayResources - - ctx context.Context -} - -func (c *Command) init() { - c.flags = flag.NewFlagSet("", flag.ContinueOnError) - - c.flags.StringVar(&c.flagGatewayClassName, "gateway-class-name", "", - "Name of Kubernetes GatewayClass to delete.") - c.flags.StringVar(&c.flagGatewayClassConfigName, "gateway-class-config-name", "", - "Name of Kubernetes GatewayClassConfig to delete.") - - c.flags.StringVar(&c.flagGatewayConfigLocation, "gateway-config-file-location", gatewayConfigFilename, - "specify a different location for where the gateway config file is") - - c.flags.StringVar(&c.flagResourceConfigFileLocation, "resource-config-file-location", resourceConfigFilename, - "specify a different location for where the gateway resource config file is") - - c.k8s = &flags.K8SFlags{} - flags.Merge(c.flags, c.k8s.Flags()) - c.help = flags.Usage(help, c.flags) -} - -func (c *Command) Run(args []string) int { - var err error - c.once.Do(c.init) - if err = c.flags.Parse(args); err != nil { - return 1 - } - // Validate flags - if err := c.validateFlags(); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if c.ctx == nil { - c.ctx = context.Background() - } - - // Create the Kubernetes clientset - if c.k8sClient == nil { - config, err := subcommand.K8SConfig(c.k8s.KubeConfig()) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving Kubernetes auth: %s", err)) - return 1 - } - - s := runtime.NewScheme() - if err := clientgoscheme.AddToScheme(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add client-go schema: %s", err)) - return 1 - } - if err := gwv1beta1.Install(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add api-gateway schema: %s", err)) - return 1 - } - if err := v1alpha1.AddToScheme(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add consul-k8s schema: %s", err)) - return 1 - } - - if err := v2beta1.AddMeshToScheme(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add consul-k8s schema: %s", err)) - return 1 - } - - c.k8sClient, err = client.New(config, client.Options{Scheme: s}) - if err != nil { - c.UI.Error(fmt.Sprintf("Error initializing Kubernetes client: %s", err)) - return 1 - } - } - - // do the cleanup - - //V1 Cleanup - err = c.deleteV1GatewayClassAndGatewayClasConfig() - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - - //V2 Cleanup - err = c.loadGatewayConfigs() - if err != nil { - - c.UI.Error(err.Error()) - return 1 - } - err = c.deleteV2GatewayClassAndClassConfigs(c.ctx) - if err != nil { - c.UI.Error(err.Error()) - - return 1 - } - - err = c.deleteV2MeshGateways(c.ctx) - if err != nil { - c.UI.Error(err.Error()) - - return 1 - } - - return 0 -} - -func (c *Command) deleteV1GatewayClassAndGatewayClasConfig() error { - // find the class config and mark it for deletion first so that we - // can do an early return if the gateway class isn't found - config := &v1alpha1.GatewayClassConfig{} - err := c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassConfigName}, config) - if err != nil { - - if k8serrors.IsNotFound(err) { - // no gateway class config, just ignore and return - return nil - } - c.UI.Error(err.Error()) - return err - } - - // ignore any returned errors - _ = c.k8sClient.Delete(context.Background(), config) - - // find the gateway class - - gatewayClass := &gwv1beta1.GatewayClass{} - err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassName}, gatewayClass) - if err != nil { - if k8serrors.IsNotFound(err) { - // no gateway class, just ignore and return - return nil - } - c.UI.Error(err.Error()) - return err - } - - // ignore any returned errors - _ = c.k8sClient.Delete(context.Background(), gatewayClass) - - // make sure they're gone - if err := backoff.Retry(func() error { - err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassConfigName}, config) - if err == nil || !k8serrors.IsNotFound(err) { - return errors.New("gateway class config still exists") - } - - err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: c.flagGatewayClassName}, gatewayClass) - if err == nil || !k8serrors.IsNotFound(err) { - return errors.New("gateway class still exists") - } - - return nil - }, exponentialBackoffWithMaxIntervalAndTime()); err != nil { - c.UI.Error(err.Error()) - // if we failed, return 0 anyway after logging the error - // since we don't want to block someone from uninstallation - } - return nil -} - -func (c *Command) validateFlags() error { - if c.flagGatewayClassConfigName == "" { - return errors.New("-gateway-class-config-name must be set") - } - if c.flagGatewayClassName == "" { - return errors.New("-gateway-class-name must be set") - } - - return nil -} - -func (c *Command) Synopsis() string { return synopsis } -func (c *Command) Help() string { - c.once.Do(c.init) - return c.help -} - -const synopsis = "Clean up global gateway resources prior to uninstall." -const help = ` -Usage: consul-k8s-control-plane gateway-cleanup [options] - - Deletes installed gateway class and gateway class config objects - prior to helm uninstallation. This is required due to finalizers - existing on the GatewayClassConfig that will leave around a dangling - object without deleting these prior to their controllers being deleted. - The job is best effort, so if it fails to successfully delete the - objects, it will allow the uninstallation to continue. - -` - -func exponentialBackoffWithMaxIntervalAndTime() *backoff.ExponentialBackOff { - backoff := backoff.NewExponentialBackOff() - backoff.MaxElapsedTime = 10 * time.Second - backoff.MaxInterval = 1 * time.Second - backoff.Reset() - return backoff -} - -// loadGatewayConfigs reads and loads the configs from `/consul/config/config.yaml`, if this file does not exist nothing is done. -func (c *Command) loadGatewayConfigs() error { - file, err := os.Open(c.flagGatewayConfigLocation) - if err != nil { - if os.IsNotExist(err) { - c.UI.Warn(fmt.Sprintf("gateway configuration file not found, skipping gateway configuration, filename: %s", c.flagGatewayConfigLocation)) - return nil - } - c.UI.Error(fmt.Sprintf("Error opening gateway configuration file %s: %s", c.flagGatewayConfigLocation, err)) - return err - } - - config, err := io.ReadAll(file) - if err != nil { - c.UI.Error(fmt.Sprintf("Error reading gateway configuration file %s: %s", c.flagGatewayConfigLocation, err)) - return err - } - - err = k8syaml.Unmarshal(config, &c.gatewayConfig) - if err != nil { - c.UI.Error(fmt.Sprintf("Error decoding gateway config file: %s", err)) - return err - } - - if err := file.Close(); err != nil { - return err - } - return nil -} - -func (c *Command) deleteV2GatewayClassAndClassConfigs(ctx context.Context) error { - for _, gcc := range c.gatewayConfig.GatewayClassConfigs { - - // find the class config and mark it for deletion first so that we - // can do an early return if the gateway class isn't found - config := &v2beta1.GatewayClassConfig{} - err := c.k8sClient.Get(ctx, types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, config) - if err != nil { - if k8serrors.IsNotFound(err) { - // no gateway class config, just ignore and continue - continue - } - return err - } - - // ignore any returned errors - _ = c.k8sClient.Delete(context.Background(), config) - - // find the gateway class - gatewayClass := &v2beta1.GatewayClass{} - //TODO: NET-6838 To pull the GatewayClassName from the Configmap - err = c.k8sClient.Get(ctx, types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, gatewayClass) - if err != nil { - if k8serrors.IsNotFound(err) { - // no gateway class, just ignore and continue - continue - } - return err - } - - // ignore any returned errors - _ = c.k8sClient.Delete(context.Background(), gatewayClass) - - // make sure they're gone - if err := backoff.Retry(func() error { - err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, config) - if err == nil || !k8serrors.IsNotFound(err) { - return errors.New("gateway class config still exists") - } - - err = c.k8sClient.Get(context.Background(), types.NamespacedName{Name: gcc.Name, Namespace: gcc.Namespace}, gatewayClass) - if err == nil || !k8serrors.IsNotFound(err) { - return errors.New("gateway class still exists") - } - - return nil - }, exponentialBackoffWithMaxIntervalAndTime()); err != nil { - c.UI.Error(err.Error()) - // if we failed, return 0 anyway after logging the error - // since we don't want to block someone from uninstallation - } - } - - return nil -} - -func (c *Command) deleteV2MeshGateways(ctx context.Context) error { - for _, meshGw := range c.gatewayConfig.MeshGateways { - _ = c.k8sClient.Delete(ctx, meshGw) - - err := c.k8sClient.Get(ctx, types.NamespacedName{Name: meshGw.Name, Namespace: meshGw.Namespace}, meshGw) - if err != nil { - if k8serrors.IsNotFound(err) { - // no gateway, just ignore and continue - continue - } - return err - } - - } - return nil -} diff --git a/control-plane/subcommand/gateway-cleanup/command_test.go b/control-plane/subcommand/gateway-cleanup/command_test.go deleted file mode 100644 index 69c626db6c..0000000000 --- a/control-plane/subcommand/gateway-cleanup/command_test.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatewaycleanup - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - corev1 "k8s.io/api/core/v1" - "os" - "testing" - - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -func TestRun(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - gatewayClassConfig *v1alpha1.GatewayClassConfig - gatewayClass *gwv1beta1.GatewayClass - }{ - "both exist": { - gatewayClassConfig: &v1alpha1.GatewayClassConfig{}, - gatewayClass: &gwv1beta1.GatewayClass{}, - }, - "gateway class config doesn't exist": { - gatewayClass: &gwv1beta1.GatewayClass{}, - }, - "gateway class doesn't exist": { - gatewayClassConfig: &v1alpha1.GatewayClassConfig{}, - }, - "neither exist": {}, - "finalizers on gatewayclass blocking deletion": { - gatewayClassConfig: &v1alpha1.GatewayClassConfig{}, - gatewayClass: &gwv1beta1.GatewayClass{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"finalizer"}}}, - }, - "finalizers on gatewayclassconfig blocking deletion": { - gatewayClassConfig: &v1alpha1.GatewayClassConfig{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"finalizer"}}}, - gatewayClass: &gwv1beta1.GatewayClass{}, - }, - } { - t.Run(name, func(t *testing.T) { - tt := tt - - t.Parallel() - - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - require.NoError(t, v2beta1.AddMeshToScheme(s)) - - objs := []client.Object{} - if tt.gatewayClass != nil { - tt.gatewayClass.Name = "gateway-class" - objs = append(objs, tt.gatewayClass) - } - if tt.gatewayClassConfig != nil { - tt.gatewayClassConfig.Name = "gateway-class-config" - objs = append(objs, tt.gatewayClassConfig) - } - - client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - flagGatewayClassName: "gateway-class", - flagGatewayClassConfigName: "gateway-class-config", - } - - code := cmd.Run([]string{ - "-gateway-class-config-name", "gateway-class-config", - "-gateway-class-name", "gateway-class", - }) - - require.Equal(t, 0, code) - }) - } -} - -func TestRunV2Resources(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - gatewayClassConfig []*v2beta1.GatewayClassConfig - gatewayClass []*v2beta1.GatewayClass - configMapData string - }{ - - "v2 resources exists": { - gatewayClassConfig: []*v2beta1.GatewayClassConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test-gateway", - }, - }, - }, - gatewayClass: []*v2beta1.GatewayClass{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test-gateway", - }, - }, - }, - configMapData: `gatewayClassConfigs: -- apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: GatewayClassConfig - metadata: - name: test-gateway - spec: - deployment: - container: - resources: - requests: - cpu: 200m - memory: 200Mi - limits: - cpu: 200m - memory: 200Mi -`, - }, - "multiple v2 resources exists": { - gatewayClassConfig: []*v2beta1.GatewayClassConfig{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test-gateway", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test-gateway2", - }, - }, - }, - gatewayClass: []*v2beta1.GatewayClass{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test-gateway", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test-gateway2", - }, - }, - }, - configMapData: `gatewayClassConfigs: -- apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: GatewayClassConfig - metadata: - name: test-gateway - spec: - deployment: - container: - resources: - requests: - cpu: 200m - memory: 200Mi - limits: - cpu: 200m - memory: 200Mi -- apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: GatewayClassConfig - metadata: - name: test-gateway2 - spec: - deployment: - container: - resources: - requests: - cpu: 200m - memory: 200Mi - limits: - cpu: 200m - memory: 200Mi -`, - }, - "v2 emptyconfigmap": { - configMapData: "", - }, - } { - t.Run(name, func(t *testing.T) { - tt := tt - - t.Parallel() - - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v2beta1.AddMeshToScheme(s)) - require.NoError(t, corev1.AddToScheme(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - objs := []client.Object{} - for _, gatewayClass := range tt.gatewayClass { - objs = append(objs, gatewayClass) - } - for _, gatewayClassConfig := range tt.gatewayClassConfig { - objs = append(objs, gatewayClassConfig) - } - - path := createGatewayConfigFile(t, tt.configMapData, "config.yaml") - - client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - flagGatewayClassName: "gateway-class", - flagGatewayClassConfigName: "gateway-class-config", - flagGatewayConfigLocation: path, - } - - code := cmd.Run([]string{ - "-gateway-class-config-name", "gateway-class-config", - "-gateway-class-name", "gateway-class", - "-gateway-config-file-location", path, - }) - - require.Equal(t, 0, code) - }) - } -} - -func createGatewayConfigFile(t *testing.T, fileContent, filename string) string { - t.Helper() - - // create a temp file to store configuration yaml - tmpdir := t.TempDir() - file, err := os.CreateTemp(tmpdir, filename) - if err != nil { - t.Fatal(err) - } - defer file.Close() - - _, err = file.WriteString(fileContent) - if err != nil { - t.Fatal(err) - } - return file.Name() -} diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go deleted file mode 100644 index bf68fbbe47..0000000000 --- a/control-plane/subcommand/gateway-resources/command.go +++ /dev/null @@ -1,622 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatewayresources - -import ( - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "os" - "sync" - "time" - - "github.com/cenkalti/backoff" - "github.com/mitchellh/cli" - "gopkg.in/yaml.v3" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - k8syaml "sigs.k8s.io/yaml" - - meshv2beta1 "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - - authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/gateways" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" -) - -const ( - gatewayConfigFilename = "/consul/config/config.yaml" - resourceConfigFilename = "/consul/config/resources.json" - meshGatewayComponent = "consul-mesh-gateway" -) - -// this dupes the Kubernetes tolerations -// struct with yaml tags for validation. -type toleration struct { - Key string `yaml:"key"` - Operator string `yaml:"operator"` - Value string `yaml:"value"` - Effect string `yaml:"effect"` - TolerationSeconds *int64 `yaml:"tolerationSeconds"` -} - -func tolerationToKubernetes(t toleration) corev1.Toleration { - return corev1.Toleration{ - Key: t.Key, - Operator: corev1.TolerationOperator(t.Operator), - Value: t.Value, - Effect: corev1.TaintEffect(t.Effect), - TolerationSeconds: t.TolerationSeconds, - } -} - -type Command struct { - UI cli.Ui - - flags *flag.FlagSet - k8s *flags.K8SFlags - - flagHeritage string - flagChart string - flagApp string - flagRelease string - flagComponent string - flagControllerName string - flagGatewayClassName string - flagGatewayClassConfigName string - - flagServiceType string - flagDeploymentDefaultInstances int - flagDeploymentMaxInstances int - flagDeploymentMinInstances int - - flagResourceConfigFileLocation string - flagGatewayConfigLocation string - - flagNodeSelector string // this is a yaml multiline string map - flagTolerations string // this is a multiline yaml string matching the tolerations array - flagServiceAnnotations string // this is a multiline yaml string array of annotations to allow - - flagOpenshiftSCCName string - - flagMapPrivilegedContainerPorts int - - k8sClient client.Client - - once sync.Once - help string - - nodeSelector map[string]string - tolerations []corev1.Toleration - serviceAnnotations []string - resources corev1.ResourceRequirements - gatewayConfig gateways.GatewayResources - - ctx context.Context -} - -func (c *Command) init() { - c.flags = flag.NewFlagSet("", flag.ContinueOnError) - - c.flags.StringVar(&c.flagGatewayClassName, "gateway-class-name", "", - "Name of Kubernetes GatewayClass to ensure is created.") - c.flags.StringVar(&c.flagGatewayClassConfigName, "gateway-class-config-name", "", - "Name of Kubernetes GatewayClassConfig to ensure is created.") - c.flags.StringVar(&c.flagHeritage, "heritage", "", - "Helm chart heritage for created objects.") - c.flags.StringVar(&c.flagChart, "chart", "", - "Helm chart name for created objects.") - c.flags.StringVar(&c.flagApp, "app", "", - "Helm chart app for created objects.") - c.flags.StringVar(&c.flagRelease, "release-name", "", - "Helm chart release for created objects.") - c.flags.StringVar(&c.flagComponent, "component", "", - "Helm chart component for created objects.") - c.flags.StringVar(&c.flagControllerName, "controller-name", "", - "The controller name value to use in the GatewayClass.") - c.flags.StringVar(&c.flagServiceType, "service-type", "", - "The service type to use for a gateway deployment.", - ) - c.flags.IntVar(&c.flagDeploymentDefaultInstances, "deployment-default-instances", 0, - "The number of instances to deploy for each gateway by default.", - ) - c.flags.IntVar(&c.flagDeploymentMaxInstances, "deployment-max-instances", 0, - "The maximum number of instances to deploy for each gateway.", - ) - c.flags.IntVar(&c.flagDeploymentMinInstances, "deployment-min-instances", 0, - "The minimum number of instances to deploy for each gateway.", - ) - c.flags.StringVar(&c.flagNodeSelector, "node-selector", "", - "The node selector to use in scheduling a gateway.", - ) - c.flags.StringVar(&c.flagTolerations, "tolerations", "", - "The tolerations to use in a deployed gateway.", - ) - c.flags.StringVar(&c.flagServiceAnnotations, "service-annotations", "", - "The annotations to copy over from a gateway to its service.", - ) - c.flags.StringVar(&c.flagOpenshiftSCCName, "openshift-scc-name", "", - "Name of security context constraint to use for gateways on Openshift.", - ) - c.flags.IntVar(&c.flagMapPrivilegedContainerPorts, "map-privileged-container-ports", 0, - "The value to add to privileged container ports (< 1024) to avoid requiring addition privileges for the "+ - "gateway container.", - ) - - c.flags.StringVar(&c.flagGatewayConfigLocation, "gateway-config-file-location", gatewayConfigFilename, - "specify a different location for where the gateway config file is") - - c.flags.StringVar(&c.flagResourceConfigFileLocation, "resource-config-file-location", resourceConfigFilename, - "specify a different location for where the gateway resource config file is") - - c.k8s = &flags.K8SFlags{} - flags.Merge(c.flags, c.k8s.Flags()) - c.help = flags.Usage(help, c.flags) -} - -func (c *Command) Run(args []string) int { - var err error - c.once.Do(c.init) - if err = c.flags.Parse(args); err != nil { - return 1 - } - // Validate flags - if err := c.validateFlags(); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - // Load apigw resource config from the configmap. - if c.resources, err = c.loadResourceConfig(c.flagResourceConfigFileLocation); err != nil { - c.UI.Error(fmt.Sprintf("Error loading api-gateway resource config: %s", err)) - return 1 - } - - // Load gateway config from the configmap. - if err := c.loadGatewayConfigs(); err != nil { - c.UI.Error(fmt.Sprintf("Error loading gateway config: %s", err)) - return 1 - } - - if c.ctx == nil { - c.ctx = context.Background() - } - - // Create the Kubernetes client - if c.k8sClient == nil { - config, err := subcommand.K8SConfig(c.k8s.KubeConfig()) - if err != nil { - c.UI.Error(fmt.Sprintf("Error retrieving Kubernetes auth: %s", err)) - return 1 - } - - s := runtime.NewScheme() - if err := clientgoscheme.AddToScheme(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add client-go schema: %s", err)) - return 1 - } - if err := gwv1beta1.Install(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add api-gateway schema: %s", err)) - return 1 - } - if err := v1alpha1.AddToScheme(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add consul-k8s schema: %s", err)) - return 1 - } - - if err := authv2beta1.AddAuthToScheme(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add authv2beta schema: %s", err)) - return 1 - } - - if err := v2beta1.AddMeshToScheme(s); err != nil { - c.UI.Error(fmt.Sprintf("Could not add meshv2 schema: %s", err)) - return 1 - } - - c.k8sClient, err = client.New(config, client.Options{Scheme: s}) - if err != nil { - c.UI.Error(fmt.Sprintf("Error initializing Kubernetes client: %s", err)) - return 1 - } - } - - // do the creation - labels := map[string]string{ - "app": c.flagApp, - "chart": c.flagChart, - "heritage": c.flagHeritage, - "release": c.flagRelease, - "component": c.flagComponent, - } - classConfig := &v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{Name: c.flagGatewayClassConfigName, Labels: labels}, - Spec: v1alpha1.GatewayClassConfigSpec{ - ServiceType: serviceTypeIfSet(c.flagServiceType), - NodeSelector: c.nodeSelector, - CopyAnnotations: v1alpha1.CopyAnnotationsSpec{ - Service: c.serviceAnnotations, - }, - Tolerations: c.tolerations, - DeploymentSpec: v1alpha1.DeploymentSpec{ - DefaultInstances: nonZeroOrNil(c.flagDeploymentDefaultInstances), - MaxInstances: nonZeroOrNil(c.flagDeploymentMaxInstances), - MinInstances: nonZeroOrNil(c.flagDeploymentMinInstances), - Resources: &c.resources, - }, - OpenshiftSCCName: c.flagOpenshiftSCCName, - MapPrivilegedContainerPorts: int32(c.flagMapPrivilegedContainerPorts), - }, - } - - class := &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{Name: c.flagGatewayClassName, Labels: labels}, - Spec: gwv1beta1.GatewayClassSpec{ - ControllerName: gwv1beta1.GatewayController(c.flagControllerName), - ParametersRef: &gwv1beta1.ParametersReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: gwv1beta1.Kind(v1alpha1.GatewayClassConfigKind), - Name: c.flagGatewayClassConfigName, - }, - }, - } - - if err := forceV1ClassConfig(context.Background(), c.k8sClient, classConfig); err != nil { - c.UI.Error(err.Error()) - return 1 - } - if err := forceV1Class(context.Background(), c.k8sClient, class); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if len(c.gatewayConfig.GatewayClassConfigs) > 0 { - err = c.createV2GatewayClassAndClassConfigs(context.Background(), meshGatewayComponent, "consul-mesh-gateway-controller") - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - - if len(c.gatewayConfig.MeshGateways) > 0 { - err = c.createV2MeshGateways(context.Background(), meshGatewayComponent) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - - return 0 -} - -func (c *Command) validateFlags() error { - if c.flagGatewayClassConfigName == "" { - return errors.New("-gateway-class-config-name must be set") - } - if c.flagGatewayClassName == "" { - return errors.New("-gateway-class-name must be set") - } - if c.flagHeritage == "" { - return errors.New("-heritage must be set") - } - if c.flagChart == "" { - return errors.New("-chart must be set") - } - if c.flagApp == "" { - return errors.New("-app must be set") - } - if c.flagRelease == "" { - return errors.New("-release-name must be set") - } - if c.flagComponent == "" { - return errors.New("-component must be set") - } - if c.flagControllerName == "" { - return errors.New("-controller-name must be set") - } - if c.flagTolerations != "" { - var tolerations []toleration - if err := yaml.Unmarshal([]byte(c.flagTolerations), &tolerations); err != nil { - return fmt.Errorf("error decoding tolerations: %w", err) - } - c.tolerations = common.ConvertSliceFunc(tolerations, tolerationToKubernetes) - } - if c.flagNodeSelector != "" { - if err := yaml.Unmarshal([]byte(c.flagNodeSelector), &c.nodeSelector); err != nil { - return fmt.Errorf("error decoding node selector: %w", err) - } - } - - if c.flagServiceAnnotations != "" { - if err := yaml.Unmarshal([]byte(c.flagServiceAnnotations), &c.serviceAnnotations); err != nil { - return fmt.Errorf("error decoding service annotations: %w", err) - } - } - - return nil -} - -func (c *Command) loadResourceConfig(filename string) (corev1.ResourceRequirements, error) { - // Load resources.json - file, err := os.Open(filename) - if err != nil { - if !os.IsNotExist(err) { - return corev1.ResourceRequirements{}, err - } - c.UI.Info("No resources.json found, using defaults") - return defaultResourceRequirements, nil - } - - resources, err := io.ReadAll(file) - if err != nil { - c.UI.Error(fmt.Sprintf("Unable to read resources.json, using defaults: %s", err)) - return defaultResourceRequirements, err - } - - reqs := corev1.ResourceRequirements{} - if err := json.Unmarshal(resources, &reqs); err != nil { - return corev1.ResourceRequirements{}, err - } - - if err := file.Close(); err != nil { - return corev1.ResourceRequirements{}, err - } - return reqs, nil -} - -// loadGatewayConfigs reads and loads the configs from `/consul/config/config.yaml`, if this file does not exist nothing is done. -func (c *Command) loadGatewayConfigs() error { - file, err := os.Open(c.flagGatewayConfigLocation) - if err != nil { - if os.IsNotExist(err) { - c.UI.Warn(fmt.Sprintf("gateway configuration file not found, skipping gateway configuration, filename: %s", c.flagGatewayConfigLocation)) - return nil - } - c.UI.Error(fmt.Sprintf("Error opening gateway configuration file %s: %s", c.flagGatewayConfigLocation, err)) - return err - } - - config, err := io.ReadAll(file) - if err != nil { - c.UI.Error(fmt.Sprintf("Error reading gateway configuration file %s: %s", c.flagGatewayConfigLocation, err)) - return err - } - - err = k8syaml.Unmarshal(config, &c.gatewayConfig) - if err != nil { - c.UI.Error(fmt.Sprintf("Error decoding gateway config file: %s", err)) - return err - } - - // ensure default resources requirements are set - for idx := range c.gatewayConfig.MeshGateways { - if c.gatewayConfig.GatewayClassConfigs[idx].Spec.Deployment.Container == nil { - c.gatewayConfig.GatewayClassConfigs[idx].Spec.Deployment.Container = &v2beta1.GatewayClassContainerConfig{Resources: &defaultResourceRequirements} - } - } - if err := file.Close(); err != nil { - return err - } - return nil -} - -// createV2GatewayClassAndClassConfigs utilizes the configuration loaded from the gateway config file to -// create the GatewayClassConfig and GatewayClass for the gateway. -func (c *Command) createV2GatewayClassAndClassConfigs(ctx context.Context, component, controllerName string) error { - labels := map[string]string{ - "app": c.flagApp, - "chart": c.flagChart, - "heritage": c.flagHeritage, - "release": c.flagRelease, - "component": component, - } - - for _, cfg := range c.gatewayConfig.GatewayClassConfigs { - err := forceV2ClassConfig(ctx, c.k8sClient, cfg) - if err != nil { - return err - } - - // TODO: NET-6934 remove hardcoded values - class := &v2beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{Name: cfg.Name, Labels: labels}, - TypeMeta: metav1.TypeMeta{Kind: "GatewayClass"}, - Spec: meshv2beta1.GatewayClass{ - ControllerName: controllerName, - ParametersRef: &meshv2beta1.ParametersReference{ - Group: v2beta1.MeshGroup, - Kind: "GatewayClassConfig", - Namespace: &cfg.Namespace, - Name: cfg.Name, - }, - }, - } - - err = forceV2Class(ctx, c.k8sClient, class) - if err != nil { - return err - } - } - - return nil -} - -func (c *Command) createV2MeshGateways(ctx context.Context, component string) error { - labels := map[string]string{ - "app": c.flagApp, - "chart": c.flagChart, - "heritage": c.flagHeritage, - "release": c.flagRelease, - "component": component, - } - for _, meshGw := range c.gatewayConfig.MeshGateways { - meshGw.Labels = labels - err := forceV2MeshGateway(ctx, c.k8sClient, meshGw) - if err != nil { - return err - } - - } - return nil -} - -func (c *Command) Synopsis() string { return synopsis } -func (c *Command) Help() string { - c.once.Do(c.init) - return c.help -} - -const ( - synopsis = "Create managed gateway resources after installation/upgrade." - help = ` -Usage: consul-k8s-control-plane gateway-resources [options] - - Installs managed gateway class and configuration resources - after a helm installation or upgrade in order to avoid the - dependencies of CRDs being in-place prior to resource creation. - -` -) - -var defaultResourceRequirements = corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("100Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("100Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, -} - -func forceV1ClassConfig(ctx context.Context, k8sClient client.Client, o *v1alpha1.GatewayClassConfig) error { - return backoff.Retry(func() error { - var existing v1alpha1.GatewayClassConfig - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if k8serrors.IsNotFound(err) { - return k8sClient.Create(ctx, o) - } - - existing.Spec = o.Spec - existing.Labels = o.Labels - - return k8sClient.Update(ctx, &existing) - }, exponentialBackoffWithMaxIntervalAndTime()) -} - -func forceV1Class(ctx context.Context, k8sClient client.Client, o *gwv1beta1.GatewayClass) error { - return backoff.Retry(func() error { - var existing gwv1beta1.GatewayClass - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if k8serrors.IsNotFound(err) { - return k8sClient.Create(ctx, o) - } - - existing.Spec = o.Spec - existing.Labels = o.Labels - - return k8sClient.Update(ctx, &existing) - }, exponentialBackoffWithMaxIntervalAndTime()) -} - -func forceV2ClassConfig(ctx context.Context, k8sClient client.Client, o *v2beta1.GatewayClassConfig) error { - return backoff.Retry(func() error { - var existing v2beta1.GatewayClassConfig - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if k8serrors.IsNotFound(err) { - return k8sClient.Create(ctx, o) - } - - existing.Spec = *o.Spec.DeepCopy() - existing.Labels = o.Labels - - return k8sClient.Update(ctx, &existing) - }, exponentialBackoffWithMaxIntervalAndTime()) -} - -func forceV2Class(ctx context.Context, k8sClient client.Client, o *v2beta1.GatewayClass) error { - return backoff.Retry(func() error { - var existing v2beta1.GatewayClass - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if k8serrors.IsNotFound(err) { - return k8sClient.Create(ctx, o) - } - - existing.Spec = *o.Spec.DeepCopy() - existing.Labels = o.Labels - - return k8sClient.Update(ctx, &existing) - }, exponentialBackoffWithMaxIntervalAndTime()) -} - -func forceV2MeshGateway(ctx context.Context, k8sClient client.Client, o *v2beta1.MeshGateway) error { - return backoff.Retry(func() error { - var existing v2beta1.MeshGateway - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(o), &existing) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if k8serrors.IsNotFound(err) { - return k8sClient.Create(ctx, o) - } - - existing.Spec = *o.Spec.DeepCopy() - existing.Labels = o.Labels - - return k8sClient.Update(ctx, &existing) - }, exponentialBackoffWithMaxIntervalAndTime()) -} - -func exponentialBackoffWithMaxIntervalAndTime() *backoff.ExponentialBackOff { - backoff := backoff.NewExponentialBackOff() - backoff.MaxElapsedTime = 10 * time.Second - backoff.MaxInterval = 1 * time.Second - backoff.Reset() - return backoff -} - -func nonZeroOrNil(v int) *int32 { - if v == 0 { - return nil - } - return common.PointerTo(int32(v)) -} - -func serviceTypeIfSet(v string) *corev1.ServiceType { - if v == "" { - return nil - } - return common.PointerTo(corev1.ServiceType(v)) -} diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go deleted file mode 100644 index 5fbba60230..0000000000 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ /dev/null @@ -1,647 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatewayresources - -import ( - "os" - "testing" - - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - meshv2beta1 "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" -) - -func TestRun_flagValidation(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - cmd *Command - expectedErr string - }{ - "required gateway class config name": { - cmd: &Command{}, - expectedErr: "-gateway-class-config-name must be set", - }, - "required gateway class name": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - }, - expectedErr: "-gateway-class-name must be set", - }, - "required heritage": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - }, - expectedErr: "-heritage must be set", - }, - "required chart": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - }, - expectedErr: "-chart must be set", - }, - "required app": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - }, - expectedErr: "-app must be set", - }, - "required release": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - flagApp: "test", - }, - expectedErr: "-release-name must be set", - }, - "required component": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - flagApp: "test", - flagRelease: "test", - }, - expectedErr: "-component must be set", - }, - "required controller name": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - flagApp: "test", - flagRelease: "test", - flagComponent: "test", - }, - expectedErr: "-controller-name must be set", - }, - "required valid tolerations": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - flagApp: "test", - flagRelease: "test", - flagComponent: "test", - flagControllerName: "test", - flagTolerations: "foo", - }, - expectedErr: "error decoding tolerations: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `foo` into []gatewayresources.toleration", - }, - "required valid nodeSelector": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - flagApp: "test", - flagRelease: "test", - flagComponent: "test", - flagControllerName: "test", - flagNodeSelector: "foo", - }, - expectedErr: "error decoding node selector: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `foo` into map[string]string", - }, - "required valid service annotations": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - flagApp: "test", - flagRelease: "test", - flagComponent: "test", - flagControllerName: "test", - flagServiceAnnotations: "foo", - }, - expectedErr: "error decoding service annotations: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `foo` into []string", - }, - "valid without optional flags": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - flagApp: "test", - flagRelease: "test", - flagComponent: "test", - flagControllerName: "test", - }, - }, - "valid with optional flags": { - cmd: &Command{ - flagGatewayClassConfigName: "test", - flagGatewayClassName: "test", - flagHeritage: "test", - flagChart: "test", - flagApp: "test", - flagRelease: "test", - flagComponent: "test", - flagControllerName: "test", - flagNodeSelector: ` -foo: 1 -bar: 2`, - flagTolerations: ` -- value: foo -- value: bar`, - flagServiceAnnotations: ` -- foo -- bar`, - flagOpenshiftSCCName: "restricted-v2", - }, - }, - } { - t.Run(name, func(t *testing.T) { - tt := tt - - t.Parallel() - - err := tt.cmd.validateFlags() - if tt.expectedErr == "" && err != nil { - t.Errorf("unexpected error occured: %v", err) - } - if tt.expectedErr != "" && err == nil { - t.Error("expected error but got none") - } - if tt.expectedErr != "" { - require.EqualError(t, err, tt.expectedErr) - } - }) - } -} - -func TestRun(t *testing.T) { - t.Parallel() - - for name, tt := range map[string]struct { - existingGatewayClass bool - existingGatewayClassConfig bool - meshGWConfigFileExists bool - }{ - "both exist": { - existingGatewayClass: true, - existingGatewayClassConfig: true, - }, - "api gateway class config doesn't exist": { - existingGatewayClass: true, - }, - "api gateway class doesn't exist": { - existingGatewayClassConfig: true, - }, - "neither exist": {}, - "mesh gw config file exists": { - meshGWConfigFileExists: true, - }, - } { - t.Run(name, func(t *testing.T) { - tt := tt - - t.Parallel() - - existingGatewayClassConfig := &v1alpha1.GatewayClassConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test"}, - } - existingGatewayClass := &gwv1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{Name: "test"}, - } - - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - configFileName := gatewayConfigFilename - if tt.meshGWConfigFileExists { - configFileName = createGatewayConfigFile(t, validGWConfigurationKitchenSink, "config.yaml") - } - - objs := []client.Object{} - if tt.existingGatewayClass { - objs = append(objs, existingGatewayClass) - } - if tt.existingGatewayClassConfig { - objs = append(objs, existingGatewayClassConfig) - } - - client := fake.NewClientBuilder().WithScheme(s).WithObjects(objs...).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - flagGatewayConfigLocation: configFileName, - } - - code := cmd.Run([]string{ - "-gateway-class-config-name", "test", - "-gateway-class-name", "test", - "-heritage", "test", - "-chart", "test", - "-app", "test", - "-release-name", "test", - "-component", "test", - "-controller-name", "test", - "-openshift-scc-name", "restricted-v2", - }) - - require.Equal(t, 0, code) - }) - } -} - -var validResourceConfiguration = `{ - "requests": { - "memory": "200Mi", - "cpu": "200m" - }, - "limits": { - "memory": "200Mi", - "cpu": "200m" - } -} -` - -var invalidResourceConfiguration = `{"resources": -{ - "memory": "100Mi" - "cpu": "100m" - }, - "limits": { - "memory": "100Mi" - "cpu": "100m" - }, -} -` - -func TestRun_loadResourceConfig(t *testing.T) { - filename := createGatewayConfigFile(t, validResourceConfiguration, "resource.json") - // setup k8s client - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - } - - expectedResources := corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("200m"), - }, - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("200m"), - }, - } - - resources, err := cmd.loadResourceConfig(filename) - require.NoError(t, err) - require.Equal(t, resources, expectedResources) -} - -func TestRun_loadResourceConfigInvalidConfigFile(t *testing.T) { - filename := createGatewayConfigFile(t, invalidResourceConfiguration, "resource.json") - // setup k8s client - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - } - - _, err := cmd.loadResourceConfig(filename) - require.Error(t, err) -} - -func TestRun_loadResourceConfigFileWhenConfigFileDoesNotExist(t *testing.T) { - filename := "./consul/config/resources.json" - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - } - - resources, err := cmd.loadResourceConfig(filename) - require.NoError(t, err) - require.Equal(t, resources, defaultResourceRequirements) // should be using defaults - require.Contains(t, string(ui.OutputWriter.Bytes()), "No resources.json found, using defaults") -} - -var validGWConfigurationKitchenSink = `gatewayClassConfigs: -- apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: GatewayClassConfig - metadata: - name: consul-mesh-gateway - spec: - deployment: - hostNetwork: true - dnsPolicy: ClusterFirst - replicas: - min: 3 - default: 3 - max: 3 - nodeSelector: - beta.kubernetes.io/arch: amd64 - beta.kubernetes.io/os: linux - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchLabels: - app: consul - release: consul-helm - component: mesh-gateway - topologyKey: kubernetes.io/hostname - tolerations: - - key: "key1" - operator: "Equal" - value: "value1" - effect: "NoSchedule" - container: - portModifier: 8000 - resources: - requests: - cpu: 200m - memory: 200Mi - limits: - cpu: 200m - memory: 200Mi -meshGateways: -- apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: MeshGateway - metadata: - name: mesh-gateway - namespace: consul - spec: - gatewayClassName: consul-mesh-gateway -` - -var validGWConfigurationMinimal = `gatewayClassConfigs: -- apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: GatewayClassConfig - metadata: - name: consul-mesh-gateway - spec: - deployment: -meshGateways: -- apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: MeshGateway - metadata: - name: mesh-gateway - namespace: consul - spec: - gatewayClassName: consul-mesh-gateway -` - -var invalidGWConfiguration = ` -gatewayClassConfigs: -iVersion= mesh.consul.hashicorp.com/v2beta1 - kind: gatewayClassConfig - metadata: - name: consul-mesh-gateway - namespace: namespace - spec: - deployment: - resources: - requests: - cpu: 100m -meshGateways: -- name: mesh-gateway - spec: - gatewayClassName: consul-mesh-gateway -` - -func TestRun_loadGatewayConfigs(t *testing.T) { - var replicasCount int32 = 3 - testCases := map[string]struct { - config string - filename string - expectedDeployment v2beta1.GatewayClassDeploymentConfig - }{ - "kitchen sink": { - config: validGWConfigurationKitchenSink, - filename: "kitchenSinkConfig.yaml", - expectedDeployment: v2beta1.GatewayClassDeploymentConfig{ - HostNetwork: true, - DNSPolicy: "ClusterFirst", - NodeSelector: map[string]string{ - "beta.kubernetes.io/arch": "amd64", - "beta.kubernetes.io/os": "linux", - }, - Replicas: &v2beta1.GatewayClassReplicasConfig{ - Default: &replicasCount, - Min: &replicasCount, - Max: &replicasCount, - }, - Tolerations: []corev1.Toleration{ - { - Key: "key1", - Operator: "Equal", - Value: "value1", - Effect: "NoSchedule", - }, - }, - - Affinity: &corev1.Affinity{ - PodAntiAffinity: &corev1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "consul", - "release": "consul-helm", - "component": "mesh-gateway", - }, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - }, - }, - Container: &v2beta1.GatewayClassContainerConfig{ - Resources: &corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("200m"), - }, - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("200m"), - }, - }, - PortModifier: 8000, - }, - }, - }, - "minimal configuration": { - config: validGWConfigurationMinimal, - filename: "minimalConfig.yaml", - expectedDeployment: v2beta1.GatewayClassDeploymentConfig{ - Container: &v2beta1.GatewayClassContainerConfig{ - Resources: &defaultResourceRequirements, - }, - }, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - filename := createGatewayConfigFile(t, tc.config, tc.filename) - // setup k8s client - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - flagGatewayConfigLocation: filename, - } - - err := cmd.loadGatewayConfigs() - require.NoError(t, err) - require.NotEmpty(t, cmd.gatewayConfig.GatewayClassConfigs) - require.NotEmpty(t, cmd.gatewayConfig.MeshGateways) - - // we only created one class config - classConfig := cmd.gatewayConfig.GatewayClassConfigs[0].DeepCopy() - - expectedClassConfig := v2beta1.GatewayClassConfig{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v2beta1.MeshGroupVersion.String(), - Kind: "GatewayClassConfig", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "consul-mesh-gateway", - }, - Spec: v2beta1.GatewayClassConfigSpec{ - Deployment: tc.expectedDeployment, - }, - Status: v2beta1.Status{}, - } - require.Equal(t, expectedClassConfig.DeepCopy(), classConfig) - - // check mesh gateway, we only created one of these - actualMeshGateway := cmd.gatewayConfig.MeshGateways[0] - - expectedMeshGateway := &v2beta1.MeshGateway{ - TypeMeta: metav1.TypeMeta{ - Kind: "MeshGateway", - APIVersion: v2beta1.MeshGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "mesh-gateway", - Namespace: "consul", - }, - Spec: meshv2beta1.MeshGateway{ - GatewayClassName: "consul-mesh-gateway", - }, - } - - require.Equal(t, expectedMeshGateway.DeepCopy(), actualMeshGateway) - }) - } -} - -func TestRun_loadGatewayConfigsWithInvalidFile(t *testing.T) { - filename := createGatewayConfigFile(t, invalidGWConfiguration, "config.yaml") - // setup k8s client - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - flagGatewayConfigLocation: filename, - } - - err := cmd.loadGatewayConfigs() - require.Error(t, err) - require.Empty(t, cmd.gatewayConfig.GatewayClassConfigs) - require.Empty(t, cmd.gatewayConfig.MeshGateways) -} - -func TestRun_loadGatewayConfigsWhenConfigFileDoesNotExist(t *testing.T) { - filename := "./consul/config/config.yaml" - s := runtime.NewScheme() - require.NoError(t, gwv1beta1.Install(s)) - require.NoError(t, v1alpha1.AddToScheme(s)) - - client := fake.NewClientBuilder().WithScheme(s).Build() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - k8sClient: client, - flagGatewayConfigLocation: filename, - } - - err := cmd.loadGatewayConfigs() - require.NoError(t, err) - require.Empty(t, cmd.gatewayConfig.GatewayClassConfigs) - require.Empty(t, cmd.gatewayConfig.MeshGateways) - require.Contains(t, string(ui.ErrorWriter.Bytes()), "gateway configuration file not found, skipping gateway configuration") -} - -func createGatewayConfigFile(t *testing.T, fileContent, filename string) string { - t.Helper() - - // create a temp file to store configuration yaml - tmpdir := t.TempDir() - file, err := os.CreateTemp(tmpdir, filename) - if err != nil { - t.Fatal(err) - } - defer file.Close() - - _, err = file.WriteString(fileContent) - if err != nil { - t.Fatal(err) - } - - return file.Name() -} diff --git a/control-plane/subcommand/get-consul-client-ca/command.go b/control-plane/subcommand/get-consul-client-ca/command.go index 069b21ad0f..a154d778e9 100644 --- a/control-plane/subcommand/get-consul-client-ca/command.go +++ b/control-plane/subcommand/get-consul-client-ca/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package getconsulclientca import ( @@ -13,15 +10,14 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/go-discover" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - "github.com/hashicorp/consul-k8s/control-plane/consul" godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-discover" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" ) // get-consul-client-ca command talks to the Consul servers diff --git a/control-plane/subcommand/get-consul-client-ca/command_test.go b/control-plane/subcommand/get-consul-client-ca/command_test.go index 990c7a2d22..1446018922 100644 --- a/control-plane/subcommand/get-consul-client-ca/command_test.go +++ b/control-plane/subcommand/get-consul-client-ca/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package getconsulclientca import ( @@ -51,7 +48,7 @@ func TestRun_FlagsValidation(t *testing.T) { flags: []string{ "-output-file=output.pem", "-server-addr=foo.com", - "-consul-api-timeout=10s", + "-consul-api-timeout=5s", "-log-level=invalid-log-level", }, expErr: "unknown log level: invalid-log-level", @@ -106,7 +103,7 @@ func TestRun(t *testing.T) { "-server-port", strings.Split(a.HTTPSAddr, ":")[1], "-ca-file", caFile, "-output-file", outputFile.Name(), - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -138,7 +135,8 @@ func TestRun(t *testing.T) { // Test that if the Consul server is not available at first, // we continue to poll it until it comes up. func TestRun_ConsulServerAvailableLater(t *testing.T) { - t.Parallel() + // Skipping this test because it is flaky on release/1.0.x. It is much better in newer versions of Consul. + t.Skip() outputFile, err := os.CreateTemp("", "ca") require.NoError(t, err) defer os.RemoveAll(outputFile.Name()) @@ -157,7 +155,6 @@ func TestRun_ConsulServerAvailableLater(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) go func() { - defer wg.Done() // start the test server after 100ms time.Sleep(100 * time.Millisecond) a, err = testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { @@ -177,6 +174,7 @@ func TestRun_ConsulServerAvailableLater(t *testing.T) { c.KeyFile = keyFile }) require.NoError(t, err) + wg.Done() }() defer func() { if a != nil { @@ -281,7 +279,7 @@ func TestRun_GetsOnlyActiveRoot(t *testing.T) { "-server-port", strings.Split(a.HTTPSAddr, ":")[1], "-ca-file", caFile, "-output-file", outputFile.Name(), - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 0, exitCode) @@ -349,7 +347,7 @@ func TestRun_WithProvider(t *testing.T) { "-server-port", strings.Split(a.HTTPSAddr, ":")[1], "-output-file", outputFile.Name(), "-ca-file", caFile, - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) diff --git a/control-plane/subcommand/gossip-encryption-autogenerate/command.go b/control-plane/subcommand/gossip-encryption-autogenerate/command.go index e14f24f847..3668a20c35 100644 --- a/control-plane/subcommand/gossip-encryption-autogenerate/command.go +++ b/control-plane/subcommand/gossip-encryption-autogenerate/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package gossipencryptionautogenerate import ( @@ -11,16 +8,15 @@ import ( "fmt" "sync" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { diff --git a/control-plane/subcommand/gossip-encryption-autogenerate/command_test.go b/control-plane/subcommand/gossip-encryption-autogenerate/command_test.go index 55b32bdb06..91d7101232 100644 --- a/control-plane/subcommand/gossip-encryption-autogenerate/command_test.go +++ b/control-plane/subcommand/gossip-encryption-autogenerate/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package gossipencryptionautogenerate import ( diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 4515a13b21..f3559e2679 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connectinject import ( @@ -15,6 +12,18 @@ import ( "sync" "syscall" + apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" + "github.com/hashicorp/consul-k8s/control-plane/controller" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/mitchellh/cli" "go.uber.org/zap/zapcore" @@ -27,21 +36,11 @@ import ( "k8s.io/client-go/rest" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - multiclusterv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/multicluster/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook" ) -const ( - WebhookCAFilename = "ca.crt" -) +const WebhookCAFilename = "ca.crt" type Command struct { UI cli.Ui @@ -57,8 +56,6 @@ type Command struct { flagEnableWebhookCAUpdate bool flagLogLevel string flagLogJSON bool - flagResourceAPIs bool // Use V2 APIs - flagV2Tenancy bool // Use V2 partitions (ent only) and namespaces instead of V1 counterparts flagAllowK8sNamespacesList []string // K8s namespaces to explicitly inject flagDenyK8sNamespacesList []string // K8s namespaces to deny injection (has precedence) @@ -90,9 +87,6 @@ type Command struct { flagDefaultSidecarProxyLifecycleGracefulPort string flagDefaultSidecarProxyLifecycleGracefulShutdownPath string - flagDefaultSidecarProxyStartupFailureSeconds int - flagDefaultSidecarProxyLivenessFailureSeconds int - // Metrics settings. flagDefaultEnableMetrics bool flagEnableGatewayMetrics bool @@ -125,9 +119,6 @@ type Command struct { flagEnableAutoEncrypt bool - // Consul telemetry collector - flagEnableTelemetryCollector bool - // Consul DNS flags. flagEnableConsulDNS bool flagResourcePrefix string @@ -139,18 +130,6 @@ type Command struct { clientset kubernetes.Interface - // sidecarProxy* are resource limits that are parsed and validated from other flags - // these are individual members because there are override annotations - sidecarProxyCPULimit resource.Quantity - sidecarProxyCPURequest resource.Quantity - sidecarProxyMemoryLimit resource.Quantity - sidecarProxyMemoryRequest resource.Quantity - - // static resources requirements for connect-init - initContainerResources corev1.ResourceRequirements - - caCertPem []byte - once sync.Once help string } @@ -162,17 +141,8 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - // We need v1alpha1 here to add the peering api to the scheme utilruntime.Must(v1alpha1.AddToScheme(scheme)) - utilruntime.Must(gwv1beta1.AddToScheme(scheme)) - utilruntime.Must(gwv1alpha2.AddToScheme(scheme)) - - // V2 resources - utilruntime.Must(authv2beta1.AddAuthToScheme(scheme)) - utilruntime.Must(meshv2beta1.AddMeshToScheme(scheme)) - utilruntime.Must(multiclusterv2beta1.AddMultiClusterToScheme(scheme)) - //+kubebuilder:scaffold:scheme } @@ -232,17 +202,11 @@ func (c *Command) init() { "Enables updating the CABundle on the webhook within this controller rather than using the web cert manager.") c.flagSet.BoolVar(&c.flagEnableAutoEncrypt, "enable-auto-encrypt", false, "Indicates whether TLS with auto-encrypt should be used when talking to Consul clients.") - c.flagSet.BoolVar(&c.flagEnableTelemetryCollector, "enable-telemetry-collector", false, - "Indicates whether proxies should be registered with configuration to enable forwarding metrics to consul-telemetry-collector") c.flagSet.StringVar(&c.flagLogLevel, "log-level", zapcore.InfoLevel.String(), fmt.Sprintf("Log verbosity level. Supported values (in order of detail) are "+ "%q, %q, %q, and %q.", zapcore.DebugLevel.String(), zapcore.InfoLevel.String(), zapcore.WarnLevel.String(), zapcore.ErrorLevel.String())) c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, "Enable or disable JSON output format for logging.") - c.flagSet.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, - "Enable or disable Consul V2 Resource APIs.") - c.flagSet.BoolVar(&c.flagV2Tenancy, "enable-v2tenancy", false, - "Enable or disable Consul V2 tenancy.") // Proxy sidecar resource setting flags. c.flagSet.StringVar(&c.flagDefaultSidecarProxyCPURequest, "default-sidecar-proxy-cpu-request", "", "Default sidecar proxy CPU request.") @@ -257,9 +221,6 @@ func (c *Command) init() { c.flagSet.StringVar(&c.flagDefaultSidecarProxyLifecycleGracefulPort, "default-sidecar-proxy-lifecycle-graceful-port", strconv.Itoa(constants.DefaultGracefulPort), "Default port for sidecar proxy lifecycle management HTTP endpoints.") c.flagSet.StringVar(&c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, "default-sidecar-proxy-lifecycle-graceful-shutdown-path", "/graceful_shutdown", "Default sidecar proxy lifecycle management graceful shutdown path.") - c.flagSet.IntVar(&c.flagDefaultSidecarProxyStartupFailureSeconds, "default-sidecar-proxy-startup-failure-seconds", 0, "Default number of seconds for the k8s startup probe to fail before the proxy container is restarted. Zero disables the probe.") - c.flagSet.IntVar(&c.flagDefaultSidecarProxyLivenessFailureSeconds, "default-sidecar-proxy-liveness-failure-seconds", 0, "Default number of seconds for the k8s liveness probe to fail before the proxy container is restarted. Zero disables the probe.") - // Metrics setting flags. c.flagSet.BoolVar(&c.flagDefaultEnableMetrics, "default-enable-metrics", false, "Default for enabling connect service metrics.") c.flagSet.BoolVar(&c.flagEnableGatewayMetrics, "enable-gateway-metrics", false, "Allows enabling Consul gateway metrics.") @@ -298,13 +259,67 @@ func (c *Command) Run(args []string) int { return 1 } - if err := c.parseAndValidateSidecarProxyFlags(); err != nil { + // Proxy resources. + var sidecarProxyCPULimit, sidecarProxyCPURequest, sidecarProxyMemoryLimit, sidecarProxyMemoryRequest resource.Quantity + var err error + if c.flagDefaultSidecarProxyCPURequest != "" { + sidecarProxyCPURequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPURequest) + if err != nil { + c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-cpu-request is invalid: %s", err)) + return 1 + } + } + + if c.flagDefaultSidecarProxyCPULimit != "" { + sidecarProxyCPULimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPULimit) + if err != nil { + c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-cpu-limit is invalid: %s", err)) + return 1 + } + } + if sidecarProxyCPULimit.Value() != 0 && sidecarProxyCPURequest.Cmp(sidecarProxyCPULimit) > 0 { + c.UI.Error(fmt.Sprintf( + "request must be <= limit: -default-sidecar-proxy-cpu-request value of %q is greater than the -default-sidecar-proxy-cpu-limit value of %q", + c.flagDefaultSidecarProxyCPURequest, c.flagDefaultSidecarProxyCPULimit)) + return 1 + } + + if c.flagDefaultSidecarProxyMemoryRequest != "" { + sidecarProxyMemoryRequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryRequest) + if err != nil { + c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-memory-request is invalid: %s", err)) + return 1 + } + } + if c.flagDefaultSidecarProxyMemoryLimit != "" { + sidecarProxyMemoryLimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryLimit) + if err != nil { + c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-memory-limit is invalid: %s", err)) + return 1 + } + } + if sidecarProxyMemoryLimit.Value() != 0 && sidecarProxyMemoryRequest.Cmp(sidecarProxyMemoryLimit) > 0 { + c.UI.Error(fmt.Sprintf( + "request must be <= limit: -default-sidecar-proxy-memory-request value of %q is greater than the -default-sidecar-proxy-memory-limit value of %q", + c.flagDefaultSidecarProxyMemoryRequest, c.flagDefaultSidecarProxyMemoryLimit)) + return 1 + } + + // Validate ports in metrics flags. + err = common.ValidateUnprivilegedPort("-default-merged-metrics-port", c.flagDefaultMergedMetricsPort) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + err = common.ValidateUnprivilegedPort("-default-prometheus-scrape-port", c.flagDefaultPrometheusScrapePort) + if err != nil { c.UI.Error(err.Error()) return 1 } // Validate resource request/limit flags and parse into corev1.ResourceRequirements - if err := c.parseAndValidateResourceFlags(); err != nil { + initResources, err := c.parseAndValidateResourceFlags() + if err != nil { c.UI.Error(err.Error()) return 1 } @@ -323,6 +338,10 @@ func (c *Command) Run(args []string) int { } } + // Convert allow/deny lists to sets. + allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) + denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) + zapLogger, err := common.ZapLogger(c.flagLogLevel, c.flagLogJSON) if err != nil { c.UI.Error(fmt.Sprintf("Error setting up logging: %s", err.Error())) @@ -349,9 +368,13 @@ func (c *Command) Run(args []string) int { return 1 } + // Create Consul API config object. + consulConfig := c.consul.ConsulClientConfig() + + var caCertPem []byte if c.consul.CACertFile != "" { var err error - c.caCertPem, err = os.ReadFile(c.consul.CACertFile) + caCertPem, err = os.ReadFile(c.consul.CACertFile) if err != nil { c.UI.Error(fmt.Sprintf("error reading Consul's CA cert file %q", c.consul.CACertFile)) return 1 @@ -368,14 +391,14 @@ func (c *Command) Run(args []string) int { c.UI.Error(fmt.Sprintf("unable to create config for consul-server-connection-manager: %s", err)) return 1 } - - watcher, err := discovery.NewWatcher(ctx, serverConnMgrCfg, hcLog.Named("consul-server-connection-manager")) + watcher, err := discovery.NewWatcher(ctx, serverConnMgrCfg, hcLog) if err != nil { c.UI.Error(fmt.Sprintf("unable to create Consul server watcher: %s", err)) return 1 } - defer watcher.Stop() + go watcher.Run() + defer watcher.Stop() // This is a blocking command that is run in order to ensure we only start the // connect-inject controllers only after we have access to the Consul server. @@ -400,17 +423,320 @@ func (c *Command) Run(args []string) int { return 1 } - // Right now we exclusively start controllers for V1 or V2. - // In the future we might add a flag to pick and choose from both. - if c.flagResourceAPIs { - err = c.configureV2Controllers(ctx, mgr, watcher) - } else { - err = c.configureV1Controllers(ctx, mgr, watcher) + lifecycleConfig := lifecycle.Config{ + DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, + DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, + DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, + DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, + DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, + } + + metricsConfig := metrics.Config{ + DefaultEnableMetrics: c.flagDefaultEnableMetrics, + EnableGatewayMetrics: c.flagEnableGatewayMetrics, + DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, + DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, + DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, + DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, + } + + if err = (&endpoints.Controller{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + MetricsConfig: metricsConfig, + EnableConsulPartitions: c.flagEnablePartitions, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + EnableWANFederation: c.flagEnableFederation, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + AuthMethod: c.flagACLAuthMethod, + NodeMeta: c.flagNodeMeta, + Log: ctrl.Log.WithName("controller").WithName("endpoints"), + Scheme: mgr.GetScheme(), + ReleaseName: c.flagReleaseName, + ReleaseNamespace: c.flagReleaseNamespace, + EnableAutoEncrypt: c.flagEnableAutoEncrypt, + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", endpoints.Controller{}) + return 1 } - if err != nil { - setupLog.Error(err, fmt.Sprintf("could not configure controllers: %s", err.Error())) + + consulMeta := apicommon.ConsulMeta{ + PartitionsEnabled: c.flagEnablePartitions, + Partition: c.consul.Partition, + NamespacesEnabled: c.flagEnableNamespaces, + DestinationNamespace: c.flagConsulDestinationNamespace, + Mirroring: c.flagEnableK8SNSMirroring, + Prefix: c.flagK8SNSMirroringPrefix, + } + + configEntryReconciler := &controller.ConfigEntryController{ + ConsulClientConfig: c.consul.ConsulClientConfig(), + ConsulServerConnMgr: watcher, + DatacenterName: c.consul.Datacenter, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, + } + if err = (&controller.ServiceDefaultsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceDefaults), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceDefaults) + return 1 + } + if err = (&controller.ServiceResolverController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceResolver), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceResolver) + return 1 + } + if err = (&controller.ProxyDefaultsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ProxyDefaults), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ProxyDefaults) + return 1 + } + if err = (&controller.MeshController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.Mesh), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.Mesh) return 1 } + if err = (&controller.ExportedServicesController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ExportedServices), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ExportedServices) + return 1 + } + if err = (&controller.ServiceRouterController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceRouter), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceRouter) + return 1 + } + if err = (&controller.ServiceSplitterController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceSplitter), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceSplitter) + return 1 + } + if err = (&controller.ServiceIntentionsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceIntentions), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceIntentions) + return 1 + } + if err = (&controller.IngressGatewayController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.IngressGateway), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.IngressGateway) + return 1 + } + if err = (&controller.TerminatingGatewayController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.TerminatingGateway), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.TerminatingGateway) + return 1 + } + + if err = mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { + setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) + return 1 + } + + if c.flagEnablePeering { + if err = (&peering.AcceptorController{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + ExposeServersServiceName: c.flagResourcePrefix + "-expose-servers", + ReleaseNamespace: c.flagReleaseNamespace, + Log: ctrl.Log.WithName("controller").WithName("peering-acceptor"), + Scheme: mgr.GetScheme(), + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "peering-acceptor") + return 1 + } + if err = (&peering.PeeringDialerController{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + Log: ctrl.Log.WithName("controller").WithName("peering-dialer"), + Scheme: mgr.GetScheme(), + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "peering-dialer") + return 1 + } + + mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringacceptors", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringAcceptorWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName("peering-acceptor"), + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringdialers", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringDialerWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName("peering-dialer"), + }}) + } + + mgr.GetWebhookServer().CertDir = c.flagCertDir + + mgr.GetWebhookServer().Register("/mutate", + &ctrlRuntimeWebhook.Admission{Handler: &webhook.MeshWebhook{ + Clientset: c.clientset, + ReleaseNamespace: c.flagReleaseNamespace, + ConsulConfig: consulConfig, + ConsulServerConnMgr: watcher, + ImageConsul: c.flagConsulImage, + ImageConsulDataplane: c.flagConsulDataplaneImage, + EnvoyExtraArgs: c.flagEnvoyExtraArgs, + ImageConsulK8S: c.flagConsulK8sImage, + RequireAnnotation: !c.flagDefaultInject, + AuthMethod: c.flagACLAuthMethod, + ConsulCACert: string(caCertPem), + TLSEnabled: c.consul.UseTLS, + ConsulAddress: c.consul.Addresses, + SkipServerWatch: c.consul.SkipServerWatch, + ConsulTLSServerName: c.consul.TLSServerName, + DefaultProxyCPURequest: sidecarProxyCPURequest, + DefaultProxyCPULimit: sidecarProxyCPULimit, + DefaultProxyMemoryRequest: sidecarProxyMemoryRequest, + DefaultProxyMemoryLimit: sidecarProxyMemoryLimit, + DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, + LifecycleConfig: lifecycleConfig, + MetricsConfig: metricsConfig, + InitContainerResources: initResources, + ConsulPartition: c.consul.Partition, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + EnableNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, + K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + EnableCNI: c.flagEnableCNI, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + EnableConsulDNS: c.flagEnableConsulDNS, + EnableOpenShift: c.flagEnableOpenShift, + Log: ctrl.Log.WithName("handler").WithName("connect"), + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, + }}) + + // Note: The path here should be identical to the one on the kubebuilder + // annotation in each webhook file. + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", + &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceDefaults), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", + &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceResolver), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", + &ctrlwebhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ProxyDefaults), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-mesh", + &ctrlwebhook.Admission{Handler: &v1alpha1.MeshWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.Mesh), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-exportedservices", + &ctrlwebhook.Admission{Handler: &v1alpha1.ExportedServicesWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ExportedServices), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", + &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceRouter), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", + &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceSplitter), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceintentions", + &ctrlwebhook.Admission{Handler: &v1alpha1.ServiceIntentionsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceIntentions), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-ingressgateway", + &ctrlwebhook.Admission{Handler: &v1alpha1.IngressGatewayWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.IngressGateway), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-terminatinggateway", + &ctrlwebhook.Admission{Handler: &v1alpha1.TerminatingGatewayWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), + ConsulMeta: consulMeta, + }}) + + if c.flagEnableWebhookCAUpdate { + err = c.updateWebhookCABundle(ctx) + if err != nil { + setupLog.Error(err, "problem getting CA Cert") + return 1 + } + } if err = mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") @@ -420,6 +746,20 @@ func (c *Command) Run(args []string) int { return 0 } +func (c *Command) updateWebhookCABundle(ctx context.Context) error { + webhookConfigName := fmt.Sprintf("%s-connect-injector", c.flagResourcePrefix) + caPath := fmt.Sprintf("%s/%s", c.flagCertDir, WebhookCAFilename) + caCert, err := os.ReadFile(caPath) + if err != nil { + return err + } + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) + if err != nil { + return err + } + return nil +} + func (c *Command) validateFlags() error { if c.flagConsulK8sImage == "" { return errors.New("-consul-k8s-image must be set") @@ -431,19 +771,6 @@ func (c *Command) validateFlags() error { return errors.New("-consul-dataplane-image must be set") } - // In Consul 1.17, multiport beta shipped with v2 catalog + mesh resources backed by v1 tenancy - // and acls (experiments=[resource-apis]). - // - // With Consul 1.18, we built out v2 tenancy with no support for acls, hence need to be explicit - // about which combination of v1 + v2 features are enabled. - // - // To summarize: - // - experiments=[resource-apis] => v2 catalog and mesh + v1 tenancy and acls - // - experiments=[resource-apis, v2tenancy] => v2 catalog and mesh + v2 tenancy + acls disabled - if c.flagV2Tenancy && !c.flagResourceAPIs { - return errors.New("-enable-resource-apis must be set to 'true' if -enable-v2tenancy is set") - } - if c.flagEnablePartitions && c.consul.Partition == "" { return errors.New("-partition must set if -enable-partitions is set to 'true'") } @@ -456,95 +783,48 @@ func (c *Command) validateFlags() error { return errors.New("-default-envoy-proxy-concurrency must be >= 0 if set") } - // Validate ports in metrics flags. - err := common.ValidateUnprivilegedPort("-default-merged-metrics-port", c.flagDefaultMergedMetricsPort) - if err != nil { - return err - } - err = common.ValidateUnprivilegedPort("-default-prometheus-scrape-port", c.flagDefaultPrometheusScrapePort) - if err != nil { - return err - } - - return nil -} - -func (c *Command) parseAndValidateSidecarProxyFlags() error { - var err error - - if c.flagDefaultSidecarProxyCPURequest != "" { - c.sidecarProxyCPURequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPURequest) - if err != nil { - return fmt.Errorf("-default-sidecar-proxy-cpu-request is invalid: %w", err) - } - } - - if c.flagDefaultSidecarProxyCPULimit != "" { - c.sidecarProxyCPULimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPULimit) - if err != nil { - return fmt.Errorf("-default-sidecar-proxy-cpu-limit is invalid: %w", err) - } - } - if c.sidecarProxyCPULimit.Value() != 0 && c.sidecarProxyCPURequest.Cmp(c.sidecarProxyCPULimit) > 0 { - return fmt.Errorf("request must be <= limit: -default-sidecar-proxy-cpu-request value of %q is greater than the -default-sidecar-proxy-cpu-limit value of %q", - c.flagDefaultSidecarProxyCPURequest, c.flagDefaultSidecarProxyCPULimit) - } - - if c.flagDefaultSidecarProxyMemoryRequest != "" { - c.sidecarProxyMemoryRequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryRequest) - if err != nil { - return fmt.Errorf("-default-sidecar-proxy-memory-request is invalid: %w", err) - } - } - if c.flagDefaultSidecarProxyMemoryLimit != "" { - c.sidecarProxyMemoryLimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryLimit) - if err != nil { - return fmt.Errorf("-default-sidecar-proxy-memory-limit is invalid: %w", err) - } - } - if c.sidecarProxyMemoryLimit.Value() != 0 && c.sidecarProxyMemoryRequest.Cmp(c.sidecarProxyMemoryLimit) > 0 { - return fmt.Errorf("request must be <= limit: -default-sidecar-proxy-memory-request value of %q is greater than the -default-sidecar-proxy-memory-limit value of %q", - c.flagDefaultSidecarProxyMemoryRequest, c.flagDefaultSidecarProxyMemoryLimit) - } - return nil } -func (c *Command) parseAndValidateResourceFlags() error { +func (c *Command) parseAndValidateResourceFlags() (corev1.ResourceRequirements, error) { // Init container var initContainerCPULimit, initContainerCPURequest, initContainerMemoryLimit, initContainerMemoryRequest resource.Quantity // Parse and validate the initContainer resources. initContainerCPURequest, err := resource.ParseQuantity(c.flagInitContainerCPURequest) if err != nil { - return fmt.Errorf("-init-container-cpu-request '%s' is invalid: %s", c.flagInitContainerCPURequest, err) + return corev1.ResourceRequirements{}, + fmt.Errorf("-init-container-cpu-request '%s' is invalid: %s", c.flagInitContainerCPURequest, err) } initContainerCPULimit, err = resource.ParseQuantity(c.flagInitContainerCPULimit) if err != nil { - return fmt.Errorf("-init-container-cpu-limit '%s' is invalid: %s", c.flagInitContainerCPULimit, err) + return corev1.ResourceRequirements{}, + fmt.Errorf("-init-container-cpu-limit '%s' is invalid: %s", c.flagInitContainerCPULimit, err) } if initContainerCPULimit.Value() != 0 && initContainerCPURequest.Cmp(initContainerCPULimit) > 0 { - return fmt.Errorf( + return corev1.ResourceRequirements{}, fmt.Errorf( "request must be <= limit: -init-container-cpu-request value of %q is greater than the -init-container-cpu-limit value of %q", c.flagInitContainerCPURequest, c.flagInitContainerCPULimit) } initContainerMemoryRequest, err = resource.ParseQuantity(c.flagInitContainerMemoryRequest) if err != nil { - return fmt.Errorf("-init-container-memory-request '%s' is invalid: %s", c.flagInitContainerMemoryRequest, err) + return corev1.ResourceRequirements{}, + fmt.Errorf("-init-container-memory-request '%s' is invalid: %s", c.flagInitContainerMemoryRequest, err) } initContainerMemoryLimit, err = resource.ParseQuantity(c.flagInitContainerMemoryLimit) if err != nil { - return fmt.Errorf("-init-container-memory-limit '%s' is invalid: %s", c.flagInitContainerMemoryLimit, err) + return corev1.ResourceRequirements{}, + fmt.Errorf("-init-container-memory-limit '%s' is invalid: %s", c.flagInitContainerMemoryLimit, err) } if initContainerMemoryLimit.Value() != 0 && initContainerMemoryRequest.Cmp(initContainerMemoryLimit) > 0 { - return fmt.Errorf( + return corev1.ResourceRequirements{}, fmt.Errorf( "request must be <= limit: -init-container-memory-request value of %q is greater than the -init-container-memory-limit value of %q", c.flagInitContainerMemoryRequest, c.flagInitContainerMemoryLimit) } // Put into corev1.ResourceRequirements form - c.initContainerResources = corev1.ResourceRequirements{ + initResources := corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: initContainerCPURequest, corev1.ResourceMemory: initContainerMemoryRequest, @@ -555,7 +835,7 @@ func (c *Command) parseAndValidateResourceFlags() error { }, } - return nil + return initResources, nil } func (c *Command) Synopsis() string { return synopsis } diff --git a/control-plane/subcommand/inject-connect/command_test.go b/control-plane/subcommand/inject-connect/command_test.go index e7ca3f12cd..5f067cf7c2 100644 --- a/control-plane/subcommand/inject-connect/command_test.go +++ b/control-plane/subcommand/inject-connect/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package connectinject import ( @@ -132,15 +129,6 @@ func TestRun_FlagValidation(t *testing.T) { }, expErr: "-default-envoy-proxy-concurrency must be >= 0 if set", }, - { - flags: []string{ - "-consul-k8s-image", "hashicorp/consul-k8s", - "-consul-image", "hashicorp/consul", - "-consul-dataplane-image", "hashicorp/consul-dataplane", - "-enable-v2tenancy", "true", - }, - expErr: "-enable-resource-apis must be set to 'true' if -enable-v2tenancy is set", - }, } for _, c := range cases { diff --git a/control-plane/subcommand/inject-connect/v1controllers.go b/control-plane/subcommand/inject-connect/v1controllers.go deleted file mode 100644 index 541ac00f49..0000000000 --- a/control-plane/subcommand/inject-connect/v1controllers.go +++ /dev/null @@ -1,491 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package connectinject - -import ( - "context" - "fmt" - "os" - - "github.com/hashicorp/consul-server-connection-manager/discovery" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" - ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" - - gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" - apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - controllers "github.com/hashicorp/consul-k8s/control-plane/controllers/configentries" - webhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/webhook-configuration" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" -) - -func (c *Command) configureV1Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { - // Create Consul API config object. - consulConfig := c.consul.ConsulClientConfig() - - // Convert allow/deny lists to sets. - allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) - denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) - - lifecycleConfig := lifecycle.Config{ - DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, - DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, - DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, - DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, - DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, - } - - metricsConfig := metrics.Config{ - DefaultEnableMetrics: c.flagDefaultEnableMetrics, - EnableGatewayMetrics: c.flagEnableGatewayMetrics, - DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, - DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, - DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, - DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, - } - - if err := (&endpoints.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - MetricsConfig: metricsConfig, - EnableConsulPartitions: c.flagEnablePartitions, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableWANFederation: c.flagEnableFederation, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - AuthMethod: c.flagACLAuthMethod, - NodeMeta: c.flagNodeMeta, - Log: ctrl.Log.WithName("controller").WithName("endpoints"), - Scheme: mgr.GetScheme(), - ReleaseName: c.flagReleaseName, - ReleaseNamespace: c.flagReleaseNamespace, - EnableAutoEncrypt: c.flagEnableAutoEncrypt, - EnableTelemetryCollector: c.flagEnableTelemetryCollector, - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", endpoints.Controller{}) - return err - } - - // API Gateway Controllers - if err := gatewaycontrollers.RegisterFieldIndexes(ctx, mgr); err != nil { - setupLog.Error(err, "unable to register field indexes") - return err - } - - if err := (&gatewaycontrollers.GatewayClassConfigController{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName("gateways"), - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", gatewaycontrollers.GatewayClassConfigController{}) - return err - } - - if err := (&gatewaycontrollers.GatewayClassController{ - ControllerName: gatewaycommon.GatewayClassControllerName, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("GatewayClass"), - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "GatewayClass") - return err - } - - cache, err := gatewaycontrollers.SetupGatewayControllerWithManager(ctx, mgr, gatewaycontrollers.GatewayControllerConfig{ - HelmConfig: gatewaycommon.HelmConfig{ - ConsulConfig: gatewaycommon.ConsulConfig{ - Address: c.consul.Addresses, - GRPCPort: consulConfig.GRPCPort, - HTTPPort: consulConfig.HTTPPort, - APITimeout: consulConfig.APITimeout, - }, - ImageDataplane: c.flagConsulDataplaneImage, - ImageConsulK8S: c.flagConsulK8sImage, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - NamespaceMirroringPrefix: c.flagK8SNSMirroringPrefix, - EnableNamespaces: c.flagEnableNamespaces, - PeeringEnabled: c.flagEnablePeering, - EnableOpenShift: c.flagEnableOpenShift, - EnableNamespaceMirroring: c.flagEnableK8SNSMirroring, - AuthMethod: c.consul.ConsulLogin.AuthMethod, - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - TLSEnabled: c.consul.UseTLS, - ConsulTLSServerName: c.consul.TLSServerName, - ConsulPartition: c.consul.Partition, - ConsulCACert: string(c.caCertPem), - }, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - NamespacesEnabled: c.flagEnableNamespaces, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - Partition: c.consul.Partition, - Datacenter: c.consul.Datacenter, - }) - if err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Gateway") - return err - } - - go cache.Run(ctx) - - // wait for the cache to fill - setupLog.Info("waiting for Consul cache sync") - cache.WaitSynced(ctx) - setupLog.Info("Consul cache synced") - - configEntryReconciler := &controllers.ConfigEntryController{ - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - DatacenterName: c.consul.Datacenter, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, - } - if err := (&controllers.ServiceDefaultsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceDefaults), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceDefaults) - return err - } - if err := (&controllers.ServiceResolverController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceResolver), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceResolver) - return err - } - if err := (&controllers.ProxyDefaultsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ProxyDefaults), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ProxyDefaults) - return err - } - if err := (&controllers.MeshController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.Mesh), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.Mesh) - return err - } - if err := (&controllers.ExportedServicesController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ExportedServices), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ExportedServices) - return err - } - if err := (&controllers.ServiceRouterController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceRouter), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceRouter) - return err - } - if err := (&controllers.ServiceSplitterController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceSplitter), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceSplitter) - return err - } - if err := (&controllers.ServiceIntentionsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceIntentions), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceIntentions) - return err - } - if err := (&controllers.IngressGatewayController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.IngressGateway), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.IngressGateway) - return err - } - if err := (&controllers.TerminatingGatewayController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.TerminatingGateway), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.TerminatingGateway) - return err - } - if err := (&controllers.SamenessGroupController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.SamenessGroup), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.SamenessGroup) - return err - } - if err := (&controllers.JWTProviderController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.JWTProvider), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.JWTProvider) - return err - } - if err := (&controllers.ControlPlaneRequestLimitController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ControlPlaneRequestLimit), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ControlPlaneRequestLimit) - return err - } - - if err := mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { - setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) - return err - } - - if c.flagEnablePeering { - if err := (&peering.AcceptorController{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - ExposeServersServiceName: c.flagResourcePrefix + "-expose-servers", - ReleaseNamespace: c.flagReleaseNamespace, - Log: ctrl.Log.WithName("controller").WithName("peering-acceptor"), - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "peering-acceptor") - return err - } - if err := (&peering.PeeringDialerController{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - Log: ctrl.Log.WithName("controller").WithName("peering-dialer"), - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "peering-dialer") - return err - } - - mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringacceptors", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringAcceptorWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName("peering-acceptor"), - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringdialers", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringDialerWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName("peering-dialer"), - }}) - } - - mgr.GetWebhookServer().CertDir = c.flagCertDir - - mgr.GetWebhookServer().Register("/mutate", - &ctrlRuntimeWebhook.Admission{Handler: &webhook.MeshWebhook{ - Clientset: c.clientset, - ReleaseNamespace: c.flagReleaseNamespace, - ConsulConfig: consulConfig, - ConsulServerConnMgr: watcher, - ImageConsul: c.flagConsulImage, - ImageConsulDataplane: c.flagConsulDataplaneImage, - EnvoyExtraArgs: c.flagEnvoyExtraArgs, - ImageConsulK8S: c.flagConsulK8sImage, - RequireAnnotation: !c.flagDefaultInject, - AuthMethod: c.flagACLAuthMethod, - ConsulCACert: string(c.caCertPem), - TLSEnabled: c.consul.UseTLS, - ConsulAddress: c.consul.Addresses, - SkipServerWatch: c.consul.SkipServerWatch, - ConsulTLSServerName: c.consul.TLSServerName, - DefaultProxyCPURequest: c.sidecarProxyCPURequest, - DefaultProxyCPULimit: c.sidecarProxyCPULimit, - DefaultProxyMemoryRequest: c.sidecarProxyMemoryRequest, - DefaultProxyMemoryLimit: c.sidecarProxyMemoryLimit, - DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, - DefaultSidecarProxyStartupFailureSeconds: c.flagDefaultSidecarProxyStartupFailureSeconds, - DefaultSidecarProxyLivenessFailureSeconds: c.flagDefaultSidecarProxyLivenessFailureSeconds, - LifecycleConfig: lifecycleConfig, - MetricsConfig: metricsConfig, - InitContainerResources: c.initContainerResources, - ConsulPartition: c.consul.Partition, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - EnableNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, - K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableCNI: c.flagEnableCNI, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - EnableConsulDNS: c.flagEnableConsulDNS, - EnableOpenShift: c.flagEnableOpenShift, - Log: ctrl.Log.WithName("handler").WithName("connect"), - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - }}) - - consulMeta := apicommon.ConsulMeta{ - PartitionsEnabled: c.flagEnablePartitions, - Partition: c.consul.Partition, - NamespacesEnabled: c.flagEnableNamespaces, - DestinationNamespace: c.flagConsulDestinationNamespace, - Mirroring: c.flagEnableK8SNSMirroring, - Prefix: c.flagK8SNSMirroringPrefix, - } - - // Note: The path here should be identical to the one on the kubebuilder - // annotation in each webhook file. - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceDefaults), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceResolver), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ProxyDefaults), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-mesh", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.MeshWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.Mesh), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-exportedservices", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ExportedServicesWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ExportedServices), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceRouter), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceSplitter), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceintentions", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceIntentionsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceIntentions), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-ingressgateway", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.IngressGatewayWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.IngressGateway), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-terminatinggateway", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.TerminatingGatewayWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-samenessgroup", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.SamenessGroupWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.SamenessGroup), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-jwtprovider", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.JWTProviderWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.JWTProvider), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-controlplanerequestlimits", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ControlPlaneRequestLimitWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ControlPlaneRequestLimit), - ConsulMeta: consulMeta, - }}) - - mgr.GetWebhookServer().Register("/validate-v1alpha1-gatewaypolicy", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.GatewayPolicyWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.GatewayPolicy), - ConsulMeta: consulMeta, - }}) - - if c.flagEnableWebhookCAUpdate { - err = c.updateWebhookCABundle(ctx) - if err != nil { - setupLog.Error(err, "problem getting CA Cert") - return err - } - } - - return nil -} - -func (c *Command) updateWebhookCABundle(ctx context.Context) error { - webhookConfigName := fmt.Sprintf("%s-connect-injector", c.flagResourcePrefix) - caPath := fmt.Sprintf("%s/%s", c.flagCertDir, WebhookCAFilename) - caCert, err := os.ReadFile(caPath) - if err != nil { - return err - } - err = webhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) - if err != nil { - return err - } - return nil -} diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go deleted file mode 100644 index ddf0758df1..0000000000 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package connectinject - -import ( - "context" - - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" - ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" - - authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/serviceaccount" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/namespace" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhookv2" - resourceControllers "github.com/hashicorp/consul-k8s/control-plane/controllers/resources" - "github.com/hashicorp/consul-k8s/control-plane/gateways" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - namespacev2 "github.com/hashicorp/consul-k8s/control-plane/tenancy/namespace" - "github.com/hashicorp/consul-server-connection-manager/discovery" -) - -func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { - // Create Consul API config object. - consulConfig := c.consul.ConsulClientConfig() - - // Convert allow/deny lists to sets. - allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) - denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) - k8sNsConfig := common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - } - consulTenancyConfig := common.ConsulTenancyConfig{ - EnableConsulPartitions: c.flagEnablePartitions, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - ConsulPartition: c.consul.Partition, - } - - lifecycleConfig := lifecycle.Config{ - DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, - DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, - DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, - DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, - DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, - } - - metricsConfig := metrics.Config{ - DefaultEnableMetrics: c.flagDefaultEnableMetrics, - EnableGatewayMetrics: c.flagEnableGatewayMetrics, - DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, - DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, - DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, - DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, - } - - if err := (&pod.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - K8sNamespaceConfig: k8sNsConfig, - ConsulTenancyConfig: consulTenancyConfig, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - AuthMethod: c.flagACLAuthMethod, - MetricsConfig: metricsConfig, - EnableTelemetryCollector: c.flagEnableTelemetryCollector, - Log: ctrl.Log.WithName("controller").WithName("pod"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", pod.Controller{}) - return err - } - - endpointsLogger := ctrl.Log.WithName("controller").WithName("endpoints") - if err := (&endpointsv2.Controller{ - Client: mgr.GetClient(), - ConsulServerConnMgr: watcher, - K8sNamespaceConfig: k8sNsConfig, - ConsulTenancyConfig: consulTenancyConfig, - WriteCache: endpointsv2.NewWriteCache(endpointsLogger), - Log: endpointsLogger, - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", endpointsv2.Controller{}) - return err - } - - if err := (&serviceaccount.Controller{ - Client: mgr.GetClient(), - ConsulServerConnMgr: watcher, - K8sNamespaceConfig: k8sNsConfig, - ConsulTenancyConfig: consulTenancyConfig, - Log: ctrl.Log.WithName("controller").WithName("serviceaccount"), - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", serviceaccount.Controller{}) - return err - } - - if c.flagV2Tenancy { - // V2 tenancy implies non-default namespaces in CE, so we don't observe flagEnableNamespaces - err := (&namespacev2.Controller{ - Client: mgr.GetClient(), - ConsulServerConnMgr: watcher, - K8sNamespaceConfig: k8sNsConfig, - ConsulTenancyConfig: consulTenancyConfig, - Log: ctrl.Log.WithName("controller").WithName("namespacev2"), - }).SetupWithManager(mgr) - if err != nil { - setupLog.Error(err, "unable to create controller", "controller", "namespacev2") - return err - } - } else { - if c.flagEnableNamespaces { - err := (&namespace.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - Log: ctrl.Log.WithName("controller").WithName("namespace"), - }).SetupWithManager(mgr) - if err != nil { - setupLog.Error(err, "unable to create controller", "controller", namespace.Controller{}) - return err - } - } - } - - consulResourceController := &resourceControllers.ConsulResourceController{ - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - ConsulTenancyConfig: consulTenancyConfig, - } - - if err := (&resourceControllers.TrafficPermissionsController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.TrafficPermissions), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.TrafficPermissions) - return err - } - - if err := (&resourceControllers.GRPCRouteController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.GRPCRoute), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.GRPCRoute) - return err - } - - if err := (&resourceControllers.HTTPRouteController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.HTTPRoute), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.HTTPRoute) - return err - } - - if err := (&resourceControllers.TCPRouteController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.TCPRoute), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.TCPRoute) - return err - } - - if err := (&resourceControllers.ProxyConfigurationController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.ProxyConfiguration), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.ProxyConfiguration) - return err - } - - if err := (&resourceControllers.MeshConfigurationController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.MeshConfiguration), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.MeshConfiguration) - return err - } - - if err := (&resourceControllers.MeshGatewayController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.MeshGateway), - Scheme: mgr.GetScheme(), - GatewayConfig: gateways.GatewayConfig{ - ConsulConfig: common.ConsulConfig{ - Address: c.consul.Addresses, - GRPCPort: consulConfig.GRPCPort, - HTTPPort: consulConfig.HTTPPort, - APITimeout: consulConfig.APITimeout, - }, - ImageDataplane: c.flagConsulDataplaneImage, - ImageConsulK8S: c.flagConsulK8sImage, - ConsulTenancyConfig: consulTenancyConfig, - PeeringEnabled: c.flagEnablePeering, - EnableOpenShift: c.flagEnableOpenShift, - AuthMethod: c.consul.ConsulLogin.AuthMethod, - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - TLSEnabled: c.consul.UseTLS, - ConsulTLSServerName: c.consul.TLSServerName, - ConsulCACert: string(c.caCertPem), - SkipServerWatch: c.consul.SkipServerWatch, - }, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.MeshGateway) - return err - } - - if err := (&resourceControllers.APIGatewayController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.APIGateway), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.APIGateway) - return err - } - - if err := (&resourceControllers.GatewayClassConfigController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.GatewayClassConfig), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.GatewayClassConfig) - return err - } - - if err := (&resourceControllers.GatewayClassController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.GatewayClass), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.GatewayClass) - return err - } - - if err := (&resourceControllers.ExportedServicesController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.ExportedServices), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.ExportedServices) - return err - } - - mgr.GetWebhookServer().CertDir = c.flagCertDir - - mgr.GetWebhookServer().Register("/mutate", - &ctrlRuntimeWebhook.Admission{Handler: &webhookv2.MeshWebhook{ - Clientset: c.clientset, - ReleaseNamespace: c.flagReleaseNamespace, - ConsulConfig: consulConfig, - ConsulServerConnMgr: watcher, - ImageConsul: c.flagConsulImage, - ImageConsulDataplane: c.flagConsulDataplaneImage, - EnvoyExtraArgs: c.flagEnvoyExtraArgs, - ImageConsulK8S: c.flagConsulK8sImage, - RequireAnnotation: !c.flagDefaultInject, - AuthMethod: c.flagACLAuthMethod, - ConsulCACert: string(c.caCertPem), - TLSEnabled: c.consul.UseTLS, - ConsulAddress: c.consul.Addresses, - SkipServerWatch: c.consul.SkipServerWatch, - ConsulTLSServerName: c.consul.TLSServerName, - DefaultProxyCPURequest: c.sidecarProxyCPURequest, - DefaultProxyCPULimit: c.sidecarProxyCPULimit, - DefaultProxyMemoryRequest: c.sidecarProxyMemoryRequest, - DefaultProxyMemoryLimit: c.sidecarProxyMemoryLimit, - DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, - LifecycleConfig: lifecycleConfig, - MetricsConfig: metricsConfig, - InitContainerResources: c.initContainerResources, - ConsulPartition: c.consul.Partition, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - EnableNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, - K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableCNI: c.flagEnableCNI, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - EnableConsulDNS: c.flagEnableConsulDNS, - EnableOpenShift: c.flagEnableOpenShift, - Log: ctrl.Log.WithName("handler").WithName("consul-mesh"), - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - }}) - - mgr.GetWebhookServer().Register("/mutate-v2beta1-trafficpermissions", - &ctrlRuntimeWebhook.Admission{Handler: &authv2beta1.TrafficPermissionsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.TrafficPermissions), - ConsulTenancyConfig: consulTenancyConfig, - }}) - mgr.GetWebhookServer().Register("/mutate-v2beta1-proxyconfigurations", - &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.ProxyConfigurationWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.ProxyConfiguration), - ConsulTenancyConfig: consulTenancyConfig, - }}) - mgr.GetWebhookServer().Register("/mutate-v2beta1-httproute", - &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.HTTPRouteWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.HTTPRoute), - ConsulTenancyConfig: consulTenancyConfig, - }}) - mgr.GetWebhookServer().Register("/mutate-v2beta1-grpcroute", - &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.GRPCRouteWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.GRPCRoute), - ConsulTenancyConfig: consulTenancyConfig, - }}) - mgr.GetWebhookServer().Register("/mutate-v2beta1-tcproute", - &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.TCPRouteWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.TCPRoute), - ConsulTenancyConfig: consulTenancyConfig, - }}) - - if err := mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { - setupLog.Error(err, "unable to create readiness check") - return err - } - - if c.flagEnableWebhookCAUpdate { - err := c.updateWebhookCABundle(ctx) - if err != nil { - setupLog.Error(err, "problem getting CA Cert") - return err - } - } - - return nil -} diff --git a/control-plane/subcommand/install-cni/binary.go b/control-plane/subcommand/install-cni/binary.go index 5bf25ab607..2429770109 100644 --- a/control-plane/subcommand/install-cni/binary.go +++ b/control-plane/subcommand/install-cni/binary.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package installcni import ( diff --git a/control-plane/subcommand/install-cni/binary_test.go b/control-plane/subcommand/install-cni/binary_test.go index 397751d42c..e65f61c63b 100644 --- a/control-plane/subcommand/install-cni/binary_test.go +++ b/control-plane/subcommand/install-cni/binary_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package installcni import ( diff --git a/control-plane/subcommand/install-cni/cniconfig.go b/control-plane/subcommand/install-cni/cniconfig.go index 448c9efa92..922d7283dd 100644 --- a/control-plane/subcommand/install-cni/cniconfig.go +++ b/control-plane/subcommand/install-cni/cniconfig.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package installcni import ( diff --git a/control-plane/subcommand/install-cni/cniconfig_test.go b/control-plane/subcommand/install-cni/cniconfig_test.go index 06fa848074..b6e2154adb 100644 --- a/control-plane/subcommand/install-cni/cniconfig_test.go +++ b/control-plane/subcommand/install-cni/cniconfig_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package installcni import ( diff --git a/control-plane/subcommand/install-cni/command.go b/control-plane/subcommand/install-cni/command.go index c626ee006b..7c481a0800 100644 --- a/control-plane/subcommand/install-cni/command.go +++ b/control-plane/subcommand/install-cni/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package installcni import ( @@ -16,11 +13,10 @@ import ( "github.com/fsnotify/fsnotify" "github.com/hashicorp/consul-k8s/control-plane/cni/config" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" ) const ( diff --git a/control-plane/subcommand/install-cni/command_test.go b/control-plane/subcommand/install-cni/command_test.go index 4083ba5153..5cb9bea91e 100644 --- a/control-plane/subcommand/install-cni/command_test.go +++ b/control-plane/subcommand/install-cni/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package installcni import ( @@ -12,11 +9,10 @@ import ( "time" "github.com/hashicorp/consul-k8s/control-plane/cni/config" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/serf/testutil/retry" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) func TestRun_FlagDefaults(t *testing.T) { diff --git a/control-plane/subcommand/install-cni/kubeconfig.go b/control-plane/subcommand/install-cni/kubeconfig.go index 467130dea9..ca93759578 100644 --- a/control-plane/subcommand/install-cni/kubeconfig.go +++ b/control-plane/subcommand/install-cni/kubeconfig.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package installcni import ( diff --git a/control-plane/subcommand/install-cni/kubeconfig_test.go b/control-plane/subcommand/install-cni/kubeconfig_test.go index 8197115db3..899ad3f600 100644 --- a/control-plane/subcommand/install-cni/kubeconfig_test.go +++ b/control-plane/subcommand/install-cni/kubeconfig_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package installcni import ( diff --git a/control-plane/subcommand/mesh-init/command.go b/control-plane/subcommand/mesh-init/command.go deleted file mode 100644 index ea03577848..0000000000 --- a/control-plane/subcommand/mesh-init/command.go +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package meshinit - -import ( - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "net" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-server-connection-manager/discovery" - "github.com/hashicorp/consul/proto-public/pbdataplane" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/sdk/iptables" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - "github.com/hashicorp/consul-k8s/control-plane/version" -) - -const ( - // The number of times to attempt to read this proxy registration (120s). - defaultMaxPollingRetries = 120 - defaultProxyIDFile = "/consul/mesh-inject/proxyid" -) - -type Command struct { - UI cli.Ui - - flagProxyName string - - maxPollingAttempts uint64 // Number of times to poll Consul for proxy registrations. - - flagRedirectTrafficConfig string - flagLogLevel string - flagLogJSON bool - - flagSet *flag.FlagSet - consul *flags.ConsulFlags - - once sync.Once - help string - logger hclog.Logger - - watcher *discovery.Watcher - - // Only used in tests. - iptablesProvider iptables.Provider - iptablesConfig iptables.Config -} - -func (c *Command) init() { - c.flagSet = flag.NewFlagSet("", flag.ContinueOnError) - - // V2 Flags - c.flagSet.StringVar(&c.flagProxyName, "proxy-name", os.Getenv("PROXY_NAME"), "The Consul proxy name. This is the K8s Pod name, which is also the name of the Workload in Consul. (Required)") - - // Universal flags - c.flagSet.StringVar(&c.flagRedirectTrafficConfig, "redirect-traffic-config", os.Getenv("CONSUL_REDIRECT_TRAFFIC_CONFIG"), "Config (in JSON format) to configure iptables for this pod.") - c.flagSet.StringVar(&c.flagLogLevel, "log-level", "info", - "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ - "\"debug\", \"info\", \"warn\", and \"error\".") - - c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, - "Enable or disable JSON output format for logging.") - - if c.maxPollingAttempts == 0 { - c.maxPollingAttempts = defaultMaxPollingRetries - } - - c.consul = &flags.ConsulFlags{} - flags.Merge(c.flagSet, c.consul.Flags()) - c.help = flags.Usage(help, c.flagSet) -} - -func (c *Command) Run(args []string) int { - c.once.Do(c.init) - - if err := c.flagSet.Parse(args); err != nil { - return 1 - } - // Validate flags - if err := c.validateFlags(); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if c.consul.Namespace == "" { - c.consul.Namespace = constants.DefaultConsulNS - } - if c.consul.Partition == "" { - c.consul.Partition = constants.DefaultConsulPartition - } - - // Set up logging. - if c.logger == nil { - var err error - c.logger, err = common.Logger(c.flagLogLevel, c.flagLogJSON) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - - // Create Consul API config object. - consulConfig := c.consul.ConsulClientConfig() - - // Create a context to be used by the processes started in this command. - ctx, cancelFunc := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer cancelFunc() - - // Start Consul server Connection manager. - serverConnMgrCfg, err := c.consul.ConsulServerConnMgrConfig() - // Disable server watch because we only need to get server IPs once. - serverConnMgrCfg.ServerWatchDisabled = true - if err != nil { - c.UI.Error(fmt.Sprintf("unable to create config for consul-server-connection-manager: %s", err)) - return 1 - } - if c.watcher == nil { - c.watcher, err = discovery.NewWatcher(ctx, serverConnMgrCfg, c.logger.Named("consul-server-connection-manager")) - if err != nil { - c.UI.Error(fmt.Sprintf("unable to create Consul server watcher: %s", err)) - return 1 - } - go c.watcher.Run() // The actual ACL login happens here - defer c.watcher.Stop() - } - - state, err := c.watcher.State() - if err != nil { - c.logger.Error("Unable to get state from consul-server-connection-manager", "error", err) - return 1 - } - - consulClient, err := consul.NewClientFromConnMgrState(consulConfig, state) - if err != nil { - c.logger.Error("Unable to get client connection", "error", err) - return 1 - } - - if version.IsFIPS() { - // make sure we are also using FIPS Consul - var versionInfo map[string]interface{} - _, err := consulClient.Raw().Query("/v1/agent/version", versionInfo, nil) - if err != nil { - c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. Unable to verify FIPS Consul while setting up Consul API client.") - } - if val, ok := versionInfo["FIPS"]; !ok || val == "" { - c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. A non-FIPS version of Consul was detected.") - } - } - - // todo (agentless): this should eventually be passed to consul-dataplane as a string so we don't need to write it to file. - if c.consul.UseTLS && c.consul.CACertPEM != "" { - if err = common.WriteFileWithPerms(constants.ConsulCAFile, c.consul.CACertPEM, 0444); err != nil { - c.logger.Error("error writing CA cert file", "error", err) - return 1 - } - } - - dc, err := consul.NewDataplaneServiceClient(c.watcher) - if err != nil { - c.logger.Error("failed to create resource client", "error", err) - return 1 - } - - var bootstrapConfig pbmesh.BootstrapConfig - if err := backoff.Retry(c.getBootstrapParams(dc, &bootstrapConfig), backoff.WithMaxRetries(backoff.NewConstantBackOff(1*time.Second), c.maxPollingAttempts)); err != nil { - c.logger.Error("Timed out waiting for bootstrap parameters", "error", err) - return 1 - } - - if c.flagRedirectTrafficConfig != "" { - c.watcher.Stop() // Explicitly stop the watcher so that ACLs are cleaned up before we apply re-direction. - err := c.applyTrafficRedirectionRules(&bootstrapConfig) // BootstrapConfig is always populated non-nil from the RPC - if err != nil { - c.logger.Error("error applying traffic redirection rules", "err", err) - return 1 - } - } - - c.logger.Info("Proxy initialization completed") - return 0 -} - -func (c *Command) validateFlags() error { - if c.flagProxyName == "" { - return errors.New("-proxy-name must be set") - } - return nil -} - -func (c *Command) Synopsis() string { return synopsis } -func (c *Command) Help() string { - c.once.Do(c.init) - return c.help -} - -func (c *Command) getBootstrapParams( - client pbdataplane.DataplaneServiceClient, - bootstrapConfig *pbmesh.BootstrapConfig, -) backoff.Operation { - return func() error { - req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ - ProxyId: c.flagProxyName, - Namespace: c.consul.Namespace, - Partition: c.consul.Partition, - } - res, err := client.GetEnvoyBootstrapParams(context.Background(), req) - if err != nil { - c.logger.Error("Unable to get bootstrap parameters", "error", err) - return err - } - if res.GetBootstrapConfig() != nil { - *bootstrapConfig = *res.GetBootstrapConfig() - } - return nil - } -} - -// This below implementation is loosely based on -// https://github.com/hashicorp/consul/blob/fe2d41ddad9ba2b8ff86cbdebbd8f05855b1523c/command/connect/redirecttraffic/redirect_traffic.go#L136. - -func (c *Command) applyTrafficRedirectionRules(config *pbmesh.BootstrapConfig) error { - err := json.Unmarshal([]byte(c.flagRedirectTrafficConfig), &c.iptablesConfig) - if err != nil { - return err - } - if c.iptablesProvider != nil { - c.iptablesConfig.IptablesProvider = c.iptablesProvider - } - - // TODO: provide dynamic updates to the c.iptablesConfig.ProxyOutboundPort - // We currently don't have a V2 endpoint that can gather the fully synthesized ProxyConfiguration. - // We need this to dynamically set c.iptablesConfig.ProxyOutboundPort with the outbound port configuration from - // pbmesh.DynamicConfiguration.TransparentProxy.OutboundListenerPort. - // We would either need to grab another resource that has this information rendered in it, or add - // pbmesh.DynamicConfiguration to the GetBootstrapParameters rpc. - // Right now this is an edge case because the mesh webhook configured the flagRedirectTrafficConfig with the default - // 15001 port. - - // TODO: provide dyanmic updates to the c.iptablesConfig.ProxyInboundPort - // This is the `mesh` port in the workload resource. - // Right now this will always be the default port (20000) - - if config.StatsBindAddr != "" { - _, port, err := net.SplitHostPort(config.StatsBindAddr) - if err != nil { - return fmt.Errorf("failed parsing host and port from StatsBindAddr: %s", err) - } - - c.iptablesConfig.ExcludeInboundPorts = append(c.iptablesConfig.ExcludeInboundPorts, port) - } - - // Configure any relevant information from the proxy service - err = iptables.Setup(c.iptablesConfig) - if err != nil { - return err - } - c.logger.Info("Successfully applied traffic redirection rules") - return nil -} - -const ( - synopsis = "Inject mesh init command." - help = ` -Usage: consul-k8s-control-plane mesh-init [options] - - Bootstraps mesh-injected pod components. - Uses V2 Consul Catalog APIs. - Not intended for stand-alone use. -` -) diff --git a/control-plane/subcommand/mesh-init/command_ent_test.go b/control-plane/subcommand/mesh-init/command_ent_test.go deleted file mode 100644 index 59c710f6eb..0000000000 --- a/control-plane/subcommand/mesh-init/command_ent_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package meshinit - -import ( - "context" - "strconv" - "testing" - - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -func TestRun_WithNamespaces(t *testing.T) { - t.Parallel() - cases := []struct { - name string - consulNamespace string - consulPartition string - }{ - { - name: "default ns, default partition", - consulNamespace: constants.DefaultConsulNS, - consulPartition: constants.DefaultConsulPartition, - }, - { - name: "non-default ns, default partition", - consulNamespace: "bar", - consulPartition: constants.DefaultConsulPartition, - }, - { - name: "non-default ns, non-default partition", - consulNamespace: "bar", - consulPartition: "baz", - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverCfg = c - }) - - _, err := EnsurePartitionExists(testClient.APIClient, c.consulPartition) - require.NoError(t, err) - - partitionedCfg := testClient.Cfg.APIClientConfig - partitionedCfg.Partition = c.consulPartition - - partitionedClient, err := api.NewClient(partitionedCfg) - require.NoError(t, err) - - _, err = namespaces.EnsureExists(partitionedClient, c.consulNamespace, "") - require.NoError(t, err) - - // Register Consul workload. - loadResource(t, testClient.ResourceClient, getWorkloadID(testPodName, c.consulNamespace, c.consulPartition), getWorkload(), nil) - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - maxPollingAttempts: 5, - } - // We build the consul-addr because normally it's defined by the init container setting - // CONSUL_HTTP_ADDR when it processes the command template. - flags := []string{"-proxy-name", testPodName, - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - "-namespace", c.consulNamespace, - "-partition", c.consulPartition, - } - - // Run the command. - code := cmd.Run(flags) - require.Equal(t, 0, code, ui.ErrorWriter.String()) - }) - } -} - -// EnsurePartitionExists ensures a Consul partition exists. -// Boolean return value indicates if the partition was created by this call. -// This is borrowed from namespaces.EnsureExists -func EnsurePartitionExists(client *api.Client, name string) (bool, error) { - if name == constants.DefaultConsulPartition { - return false, nil - } - // Check if the Consul namespace exists. - partitionInfo, _, err := client.Partitions().Read(context.Background(), name, nil) - if err != nil { - return false, err - } - if partitionInfo != nil { - return false, nil - } - - consulPartition := api.Partition{ - Name: name, - Description: "Auto-generated by consul-k8s", - } - - _, _, err = client.Partitions().Create(context.Background(), &consulPartition, nil) - return true, err -} diff --git a/control-plane/subcommand/mesh-init/command_test.go b/control-plane/subcommand/mesh-init/command_test.go deleted file mode 100644 index 90756cee70..0000000000 --- a/control-plane/subcommand/mesh-init/command_test.go +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package meshinit - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - "strings" - "sync" - "testing" - "time" - - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/iptables" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestRun_FlagValidation(t *testing.T) { - t.Parallel() - cases := []struct { - flags []string - env string - expErr string - }{ - { - flags: []string{}, - expErr: "-proxy-name must be set", - }, - { - flags: []string{ - "-proxy-name", testPodName, - "-log-level", "invalid", - }, - expErr: "unknown log level: invalid", - }, - } - for _, c := range cases { - t.Run(c.expErr, func(t *testing.T) { - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - } - code := cmd.Run(c.flags) - require.Equal(t, 1, code) - require.Contains(t, ui.ErrorWriter.String(), c.expErr) - }) - } -} - -// TestRun_MeshServices tests that the command can log in to Consul (if ACLs are enabled) using a kubernetes -// auth method and, using the obtained token, make call to the dataplane GetBootstrapParams() RPC. -func TestRun_MeshServices(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - workload *pbcatalog.Workload - proxyConfiguration *pbmesh.ProxyConfiguration - aclsEnabled bool - expFail bool - }{ - { - name: "basic workload bootstrap", - workload: getWorkload(), - }, - { - name: "workload and proxyconfiguration bootstrap", - workload: getWorkload(), - proxyConfiguration: getProxyConfiguration(), - }, - { - name: "missing workload", - expFail: true, - }, - // TODO: acls enabled - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - //tokenFile := fmt.Sprintf("/tmp/%d1", rand.Int()) - //t.Cleanup(func() { - // _ = os.RemoveAll(tokenFile) - //}) - - // Create test consulServer server. - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverCfg = c - }) - - loadResource(t, testClient.ResourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.workload, nil) - loadResource(t, testClient.ResourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.proxyConfiguration, nil) - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - maxPollingAttempts: 3, - } - - // We build the consul-addr because normally it's defined by the init container setting - // CONSUL_HTTP_ADDR when it processes the command template. - flags := []string{ - "-proxy-name", testPodName, - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - } - //if tt.aclsEnabled { - // flags = append(flags, "-auth-method-name", test.AuthMethod, - // "-service-account-name", tt.serviceAccountName, - // "-acl-token-sink", tokenFile) //TODO: what happens if this is unspecified? We don't need this file - //} - - // Run the command. - code := cmd.Run(flags) - if tt.expFail { - require.Equal(t, 1, code) - return - } - require.Equal(t, 0, code, ui.ErrorWriter.String()) - - // TODO: Can we remove the tokenFile from this workflow? - // consul-dataplane performs it's own login using the Serviceaccount bearer token - //if tt.aclsEnabled { - // // Validate the ACL token was written. - // tokenData, err := os.ReadFile(tokenFile) - // require.NoError(t, err) - // require.NotEmpty(t, tokenData) - // - // // Check that the token has the metadata with pod name and pod namespace. - // consulClient, err = api.NewClient(&api.Config{Address: server.HTTPAddr, Token: string(tokenData)}) - // require.NoError(t, err) - // token, _, err := consulClient.ACL().TokenReadSelf(nil) - // require.NoError(t, err) - // require.Equal(t, "token created via login: {\"pod\":\"default-ns/counting-pod\"}", token.Description) - //} - }) - } -} - -// TestRun_RetryServicePolling runs the command but does not register the consul service -// for 2 seconds and then asserts the command exits successfully. -func TestRun_RetryServicePolling(t *testing.T) { - t.Parallel() - - // Start Consul server. - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverCfg = c - }) - - // Start the consul service registration in a go func and delay it so that it runs - // after the cmd.Run() starts. - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - // Wait a moment, this ensures that we are already in the retry logic. - time.Sleep(time.Second * 2) - // Register counting service. - loadResource(t, testClient.ResourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) - }() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - maxPollingAttempts: 10, - } - - flags := []string{ - "-proxy-name", testPodName, - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - } - code := cmd.Run(flags) - wg.Wait() - require.Equal(t, 0, code) -} - -func TestRun_TrafficRedirection(t *testing.T) { - cases := map[string]struct { - registerProxyConfiguration bool - expIptablesParamsFunc func(actual iptables.Config) error - }{ - "no proxyConfiguration provided": { - expIptablesParamsFunc: func(actual iptables.Config) error { - if len(actual.ExcludeInboundPorts) != 0 { - return fmt.Errorf("ExcludeInboundPorts in iptables.Config was %v, but should be empty", actual.ExcludeInboundPorts) - } - if actual.ProxyInboundPort != 20000 { - return fmt.Errorf("ProxyInboundPort in iptables.Config was %d, but should be [20000]", actual.ProxyOutboundPort) - } - if actual.ProxyOutboundPort != 15001 { - return fmt.Errorf("ProxyOutboundPort in iptables.Config was %d, but should be [15001]", actual.ProxyOutboundPort) - } - return nil - }, - }, - "stats bind port is provided in proxyConfiguration": { - registerProxyConfiguration: true, - expIptablesParamsFunc: func(actual iptables.Config) error { - if len(actual.ExcludeInboundPorts) != 1 || actual.ExcludeInboundPorts[0] != "9090" { - return fmt.Errorf("ExcludeInboundPorts in iptables.Config was %v, but should be [9090, 1234]", actual.ExcludeInboundPorts) - } - if actual.ProxyInboundPort != 20000 { - return fmt.Errorf("ProxyInboundPort in iptables.Config was %d, but should be [20000]", actual.ProxyOutboundPort) - } - if actual.ProxyOutboundPort != 15001 { - return fmt.Errorf("ProxyOutboundPort in iptables.Config was %d, but should be [15001]", actual.ProxyOutboundPort) - } - return nil - }, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - // Start Consul server. - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverCfg = c - }) - - // Add additional proxy configuration either to a config entry or to the service itself. - if c.registerProxyConfiguration { - loadResource(t, testClient.ResourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getProxyConfiguration(), nil) - } - - // Register Consul workload. - loadResource(t, testClient.ResourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) - - iptablesProvider := &fakeIptablesProvider{} - iptablesCfg := iptables.Config{ - ProxyUserID: "5995", - ProxyInboundPort: 20000, - ProxyOutboundPort: 15001, - } - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - maxPollingAttempts: 3, - iptablesProvider: iptablesProvider, - } - iptablesCfgJSON, err := json.Marshal(iptablesCfg) - require.NoError(t, err) - - flags := []string{ - "-proxy-name", testPodName, - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - "-redirect-traffic-config", string(iptablesCfgJSON), - } - code := cmd.Run(flags) - require.Equal(t, 0, code, ui.ErrorWriter.String()) - require.Truef(t, iptablesProvider.applyCalled, "redirect traffic rules were not applied") - if c.expIptablesParamsFunc != nil { - errMsg := c.expIptablesParamsFunc(cmd.iptablesConfig) - require.NoError(t, errMsg) - } - }) - } -} - -const ( - testPodName = "foo" -) - -type fakeIptablesProvider struct { - applyCalled bool - rules []string -} - -func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { - if id == nil || !proto.ProtoReflect().IsValid() { - return - } - - data, err := anypb.New(proto) - require.NoError(t, err) - - resource := &pbresource.Resource{ - Id: id, - Data: data, - Owner: owner, - } - - req := &pbresource.WriteRequest{Resource: resource} - _, err = client.Write(context.Background(), req) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), client, id) -} - -func getWorkloadID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -// getWorkload creates a proxyConfiguration that matches the pod from createPod, -// assuming that metrics, telemetry, and overwrite probes are enabled separately. -func getWorkload() *pbcatalog.Workload { - return &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: "k8s-node-0", - Identity: testPodName, - } -} - -func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -// getProxyConfiguration creates a proxyConfiguration that matches the pod from createWorkload. -func getProxyConfiguration() *pbmesh.ProxyConfiguration { - return &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{testPodName}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsBindAddr: "0.0.0.0:9090", - PrometheusBindAddr: "0.0.0.0:21234", // This gets added to the iptables exclude directly in the webhook - }, - } -} - -func (f *fakeIptablesProvider) AddRule(_ string, args ...string) { - f.rules = append(f.rules, strings.Join(args, " ")) -} - -func (f *fakeIptablesProvider) ApplyRules() error { - f.applyCalled = true - return nil -} - -func (f *fakeIptablesProvider) Rules() []string { - return f.rules -} diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go index 19bb1bc6f5..7ca70b50a7 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package partition_init import ( @@ -11,20 +8,13 @@ import ( "sync" "time" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul-server-connection-manager/discovery" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul-server-connection-manager/discovery" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" ) type Command struct { @@ -33,10 +23,9 @@ type Command struct { flags *flag.FlagSet consul *flags.ConsulFlags - flagLogLevel string - flagLogJSON bool - flagTimeout time.Duration - flagV2Tenancy bool + flagLogLevel string + flagLogJSON bool + flagTimeout time.Duration // ctx is cancelled when the command timeout is reached. ctx context.Context @@ -59,8 +48,6 @@ 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.BoolVar(&c.flagV2Tenancy, "enable-v2tenancy", false, - "Enable V2 tenancy.") c.consul = &flags.ConsulFlags{} flags.Merge(c.flags, c.consul.Flags()) @@ -79,106 +66,6 @@ func (c *Command) Help() string { return c.help } -func (c *Command) ensureV2Partition(scm consul.ServerConnectionManager) error { - client, err := consul.NewResourceServiceClient(scm) - if err != nil { - c.UI.Error(fmt.Sprintf("unable to create grpc client: %s", err)) - return err - } - - for { - id := &pbresource.ID{ - Name: c.consul.Partition, - Type: pbtenancy.PartitionType, - } - - _, err = client.Read(c.ctx, &pbresource.ReadRequest{Id: id}) - switch { - - // found -> done - case err == nil: - c.log.Info("Admin Partition already exists", "name", c.consul.Partition) - return nil - - // not found -> create - case status.Code(err) == codes.NotFound: - data, err := anypb.New(&pbtenancy.Partition{Description: "Created by Helm installation"}) - if err != nil { - continue - } - _, err = client.Write(c.ctx, &pbresource.WriteRequest{Resource: &pbresource.Resource{ - Id: id, - Data: data, - }}) - if err == nil { - c.log.Info("Successfully created Admin Partition", "name", c.consul.Partition) - return nil - } - - // unexpected error -> retry - default: - c.log.Error("Error reading Partition from Consul", "name", c.consul.Partition, "error", err.Error()) - } - - // Wait on either the retry duration (in which case we continue) or the - // overall command timeout. - c.log.Info("Retrying in " + c.retryDuration.String()) - select { - case <-time.After(c.retryDuration): - continue - case <-c.ctx.Done(): - c.log.Error("Timed out attempting to ensure partition exists", "name", c.consul.Partition) - return err - } - } -} - -func (c *Command) ensureV1Partition(scm consul.ServerConnectionManager) error { - state, err := scm.State() - if err != nil { - c.UI.Error(fmt.Sprintf("unable to get Consul server addresses from watcher: %s", err)) - return err - } - - consulClient, err := consul.NewClientFromConnMgrState(c.consul.ConsulClientConfig(), state) - if err != nil { - c.UI.Error(fmt.Sprintf("unable to create Consul client: %s", err)) - return err - } - - for { - partition, _, err := consulClient.Partitions().Read(c.ctx, c.consul.Partition, nil) - // The API does not return an error if the Partition does not exist. It returns a nil Partition. - if err != nil { - c.log.Error("Error reading Partition from Consul", "name", c.consul.Partition, "error", err.Error()) - } else if partition == nil { - // Retry Admin Partition creation until it succeeds, or we reach the command timeout. - _, _, err = consulClient.Partitions().Create(c.ctx, &api.Partition{ - Name: c.consul.Partition, - Description: "Created by Helm installation", - }, nil) - if err == nil { - c.log.Info("Successfully created Admin Partition", "name", c.consul.Partition) - return nil - } - c.log.Error("Error creating partition", "name", c.consul.Partition, "error", err.Error()) - } else { - c.log.Info("Admin Partition already exists", "name", c.consul.Partition) - return nil - } - // Wait on either the retry duration (in which case we continue) or the - // overall command timeout. - c.log.Info("Retrying in " + c.retryDuration.String()) - select { - case <-time.After(c.retryDuration): - continue - case <-c.ctx.Done(): - c.log.Error("Timed out attempting to create partition", "name", c.consul.Partition) - return fmt.Errorf("") - } - } -} - // Run bootstraps Admin Partitions on Consul servers. // The function will retry its tasks until success, or it exceeds its timeout. func (c *Command) Run(args []string) int { @@ -224,15 +111,49 @@ func (c *Command) Run(args []string) int { go watcher.Run() defer watcher.Stop() - if c.flagV2Tenancy { - err = c.ensureV2Partition(watcher) - } else { - err = c.ensureV1Partition(watcher) + state, err := watcher.State() + if err != nil { + c.UI.Error(fmt.Sprintf("unable to get Consul server addresses from watcher: %s", err)) + return 1 } + + consulClient, err := consul.NewClientFromConnMgrState(c.consul.ConsulClientConfig(), state) if err != nil { + c.UI.Error(fmt.Sprintf("unable to create Consul client: %s", err)) return 1 } - return 0 + + for { + partition, _, err := consulClient.Partitions().Read(c.ctx, c.consul.Partition, nil) + // The API does not return an error if the Partition does not exist. It returns a nil Partition. + if err != nil { + c.log.Error("Error reading Partition from Consul", "name", c.consul.Partition, "error", err.Error()) + } else if partition == nil { + // Retry Admin Partition creation until it succeeds, or we reach the command timeout. + _, _, err = consulClient.Partitions().Create(c.ctx, &api.Partition{ + Name: c.consul.Partition, + Description: "Created by Helm installation", + }, nil) + if err == nil { + c.log.Info("Successfully created Admin Partition", "name", c.consul.Partition) + return 0 + } + c.log.Error("Error creating partition", "name", c.consul.Partition, "error", err.Error()) + } else { + c.log.Info("Admin Partition already exists", "name", c.consul.Partition) + return 0 + } + // Wait on either the retry duration (in which case we continue) or the + // overall command timeout. + c.log.Info("Retrying in " + c.retryDuration.String()) + select { + case <-time.After(c.retryDuration): + continue + case <-c.ctx.Done(): + c.log.Error("Timed out attempting to create partition", "name", c.consul.Partition) + return 1 + } + } } func (c *Command) validateFlags() error { @@ -247,7 +168,6 @@ func (c *Command) validateFlags() error { if c.consul.APITimeout <= 0 { return errors.New("-api-timeout must be set to a value greater than 0") } - return nil } diff --git a/control-plane/subcommand/partition-init/command_ent_test.go b/control-plane/subcommand/partition-init/command_ent_test.go index 21972a5a7a..5bb1868b39 100644 --- a/control-plane/subcommand/partition-init/command_ent_test.go +++ b/control-plane/subcommand/partition-init/command_ent_test.go @@ -1,27 +1,17 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise package partition_init import ( "context" - "strconv" + "strings" "testing" "time" - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" "github.com/hashicorp/consul/sdk/testutil" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" ) func TestRun_FlagValidation(t *testing.T) { @@ -41,10 +31,7 @@ func TestRun_FlagValidation(t *testing.T) { }, { flags: []string{ - "-addresses", "foo", - "-partition", "bar", - "-api-timeout", "0s", - }, + "-addresses", "foo", "-partition", "bar", "-api-timeout", "0s"}, expErr: "-api-timeout must be set to a value greater than 0", }, { @@ -71,227 +58,103 @@ func TestRun_FlagValidation(t *testing.T) { func TestRun_PartitionCreate(t *testing.T) { partitionName := "test-partition" - type testCase struct { - v2tenancy bool - experiments []string - requirePartitionCreated func(testClient *test.TestServerClient) - } + server, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) + server.WaitForLeader(t) + defer server.Stop() - testCases := map[string]testCase{ - "v2tenancy false": { - v2tenancy: false, - experiments: []string{}, - requirePartitionCreated: func(testClient *test.TestServerClient) { - consul, err := api.NewClient(testClient.Cfg.APIClientConfig) - require.NoError(t, err) + consul, err := api.NewClient(&api.Config{ + Address: server.HTTPAddr, + }) + require.NoError(t, err) - partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) - require.NoError(t, err) - require.NotNil(t, partition) - require.Equal(t, partitionName, partition.Name) - }, - }, - "v2tenancy true": { - v2tenancy: true, - experiments: []string{"resource-apis", "v2tenancy"}, - requirePartitionCreated: func(testClient *test.TestServerClient) { - _, err := testClient.ResourceClient.Read(context.Background(), &pbresource.ReadRequest{ - Id: &pbresource.ID{ - Name: partitionName, - Type: pbtenancy.PartitionType, - }, - }) - require.NoError(t, err, "expected partition to be created") - }, - }, + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + cmd.init() + args := []string{ + "-addresses=" + "127.0.0.1", + "-http-port=" + strings.Split(server.HTTPAddr, ":")[1], + "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], + "-partition", partitionName, } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = tc.experiments - serverCfg = c - }) + responseCode := cmd.Run(args) - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - } - cmd.init() - args := []string{ - "-addresses=" + "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - "-partition", partitionName, - "-timeout", "1m", - "-enable-v2tenancy=" + strconv.FormatBool(tc.v2tenancy), - } + require.Equal(t, 0, responseCode) - responseCode := cmd.Run(args) - require.Equal(t, 0, responseCode) - tc.requirePartitionCreated(testClient) - }) - } + partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) + require.NoError(t, err) + require.NotNil(t, partition) + require.Equal(t, partitionName, partition.Name) } func TestRun_PartitionExists(t *testing.T) { partitionName := "test-partition" - partitionDesc := "Created before test" - type testCase struct { - v2tenancy bool - experiments []string - preCreatePartition func(testClient *test.TestServerClient) - requirePartitionNotCreated func(testClient *test.TestServerClient) - } + server, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) + server.WaitForLeader(t) + defer server.Stop() - testCases := map[string]testCase{ - "v2tenancy false": { - v2tenancy: false, - experiments: []string{}, + consul, err := api.NewClient(&api.Config{ + Address: server.HTTPAddr, + }) + require.NoError(t, err) - preCreatePartition: func(testClient *test.TestServerClient) { - consul, err := api.NewClient(testClient.Cfg.APIClientConfig) - require.NoError(t, err) + // Create the Admin Partition before the test runs. + _, _, err = consul.Partitions().Create(context.Background(), &api.Partition{Name: partitionName, Description: "Created before test"}, nil) + require.NoError(t, err) - _, _, err = consul.Partitions().Create(context.Background(), &api.Partition{ - Name: partitionName, - Description: partitionDesc, - }, nil) - require.NoError(t, err) - }, - requirePartitionNotCreated: func(testClient *test.TestServerClient) { - consul, err := api.NewClient(testClient.Cfg.APIClientConfig) - require.NoError(t, err) - - partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) - require.NoError(t, err) - require.NotNil(t, partition) - require.Equal(t, partitionName, partition.Name) - require.Equal(t, partitionDesc, partition.Description) - }, - }, - "v2tenancy true": { - v2tenancy: true, - experiments: []string{"resource-apis", "v2tenancy"}, - preCreatePartition: func(testClient *test.TestServerClient) { - data, err := anypb.New(&pbtenancy.Partition{Description: partitionDesc}) - require.NoError(t, err) - - _, err = testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: partitionName, - Type: pbtenancy.PartitionType, - }, - Data: data, - }, - }) - require.NoError(t, err) - }, - requirePartitionNotCreated: func(testClient *test.TestServerClient) { - rsp, err := testClient.ResourceClient.Read(context.Background(), &pbresource.ReadRequest{ - Id: &pbresource.ID{ - Name: partitionName, - Type: pbtenancy.PartitionType, - }, - }) - require.NoError(t, err) - - partition := &pbtenancy.Partition{} - err = anypb.UnmarshalTo(rsp.Resource.Data, partition, proto.UnmarshalOptions{}) - require.NoError(t, err) - require.Equal(t, partitionDesc, partition.Description) - }, - }, + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + cmd.init() + args := []string{ + "-addresses=" + "127.0.0.1", + "-http-port=" + strings.Split(server.HTTPAddr, ":")[1], + "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], + "-partition", partitionName, } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = tc.experiments - serverCfg = c - }) - - // Create the Admin Partition before the test runs. - tc.preCreatePartition(testClient) + responseCode := cmd.Run(args) - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - } - cmd.init() - args := []string{ - "-addresses=" + "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - "-partition", partitionName, - "-enable-v2tenancy=" + strconv.FormatBool(tc.v2tenancy), - } + require.Equal(t, 0, responseCode) - responseCode := cmd.Run(args) - require.Equal(t, 0, responseCode) - - // Verify that the Admin Partition was not overwritten. - tc.requirePartitionNotCreated(testClient) - }) - } + partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) + require.NoError(t, err) + require.NotNil(t, partition) + require.Equal(t, partitionName, partition.Name) + require.Equal(t, "Created before test", partition.Description) } func TestRun_ExitsAfterTimeout(t *testing.T) { partitionName := "test-partition" - type testCase struct { - v2tenancy bool - experiments []string - } + server, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) - testCases := map[string]testCase{ - "v2tenancy false": { - v2tenancy: false, - experiments: []string{}, - }, - "v2tenancy true": { - v2tenancy: true, - experiments: []string{"resource-apis", "v2tenancy"}, - }, + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = tc.experiments - serverCfg = c - }) - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - } - cmd.init() - - timeout := 500 * time.Millisecond - args := []string{ - "-addresses=" + "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - "-timeout", timeout.String(), - "-partition", partitionName, - "-enable-v2tenancy=" + strconv.FormatBool(tc.v2tenancy), - } - - testClient.TestServer.Stop() - startTime := time.Now() - responseCode := cmd.Run(args) - completeTime := time.Now() - require.Equal(t, 1, responseCode) - - // While the timeout is 500ms, adding a buffer of 500ms ensures we account for - // some buffer time required for the task to run and assignments to occur. - require.WithinDuration(t, completeTime, startTime, timeout+500*time.Millisecond) - }) + cmd.init() + args := []string{ + "-addresses=" + "127.0.0.1", + "-http-port=" + strings.Split(server.HTTPAddr, ":")[1], + "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], + "-timeout", "500ms", + "-partition", partitionName, } + server.Stop() + startTime := time.Now() + responseCode := cmd.Run(args) + completeTime := time.Now() + + require.Equal(t, 1, responseCode) + // While the timeout is 500ms, adding a buffer of 500ms ensures we account for + // some buffer time required for the task to run and assignments to occur. + require.WithinDuration(t, completeTime, startTime, 1*time.Second) } diff --git a/control-plane/subcommand/server-acl-init/anonymous_token.go b/control-plane/subcommand/server-acl-init/anonymous_token.go index b050558968..46d456471e 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token.go @@ -1,10 +1,6 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" ) @@ -15,8 +11,8 @@ const ( // configureAnonymousPolicy sets up policies and tokens so that Consul DNS and // cross-datacenter Consul connect calls will work. -func (c *Command) configureAnonymousPolicy(client *consul.DynamicClient) error { - exists, err := checkIfAnonymousTokenPolicyExists(client) +func (c *Command) configureAnonymousPolicy(consulClient *api.Client) error { + exists, err := checkIfAnonymousTokenPolicyExists(consulClient) if err != nil { c.log.Error("Error checking if anonymous token policy exists", "err", err) return err @@ -41,7 +37,7 @@ func (c *Command) configureAnonymousPolicy(client *consul.DynamicClient) error { err = c.untilSucceeds("creating anonymous token policy - PUT /v1/acl/policy", func() error { - return c.createOrUpdateACLPolicy(anonPolicy, client) + return c.createOrUpdateACLPolicy(anonPolicy, consulClient) }) if err != nil { return err @@ -56,17 +52,13 @@ func (c *Command) configureAnonymousPolicy(client *consul.DynamicClient) error { // Update anonymous token to include this policy return c.untilSucceeds("updating anonymous token with policy", func() error { - err := client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - _, _, err = client.ConsulClient.ACL().TokenUpdate(&aToken, &api.WriteOptions{}) + _, _, err := consulClient.ACL().TokenUpdate(&aToken, &api.WriteOptions{}) return err }) } -func checkIfAnonymousTokenPolicyExists(client *consul.DynamicClient) (bool, error) { - token, _, err := client.ConsulClient.ACL().TokenRead(anonymousTokenAccessorID, nil) +func checkIfAnonymousTokenPolicyExists(consulClient *api.Client) (bool, error) { + token, _, err := consulClient.ACL().TokenRead(anonymousTokenAccessorID, nil) if err != nil { return false, err } diff --git a/control-plane/subcommand/server-acl-init/anonymous_token_test.go b/control-plane/subcommand/server-acl-init/anonymous_token_test.go index 41689a88c7..4a36c676e1 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token_test.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token_test.go @@ -1,13 +1,9 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( "strings" "testing" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" @@ -15,7 +11,7 @@ import ( func Test_configureAnonymousPolicy(t *testing.T) { - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) consulHTTPAddr := testClient.TestServer.HTTPAddr consulGRPCAddr := testClient.TestServer.GRPCAddr @@ -40,16 +36,16 @@ func Test_configureAnonymousPolicy(t *testing.T) { require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) bootToken := getBootToken(t, k8s, resourcePrefix, ns) - client, err := consul.NewDynamicClient(&api.Config{ + consul, err := api.NewClient(&api.Config{ Address: consulHTTPAddr, Token: bootToken, }) require.NoError(t, err) - err = cmd.configureAnonymousPolicy(client) + err = cmd.configureAnonymousPolicy(consul) require.NoError(t, err) - policy, _, err := client.ConsulClient.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) + policy, _, err := consul.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) require.NoError(t, err) testPolicy := api.ACLPolicy{ @@ -58,13 +54,13 @@ func Test_configureAnonymousPolicy(t *testing.T) { Description: "Anonymous token Policy", Rules: `acl = "read"`, } - readOnlyPolicy, _, err := client.ConsulClient.ACL().PolicyUpdate(&testPolicy, &api.WriteOptions{}) + readOnlyPolicy, _, err := consul.ACL().PolicyUpdate(&testPolicy, &api.WriteOptions{}) require.NoError(t, err) - err = cmd.configureAnonymousPolicy(client) + err = cmd.configureAnonymousPolicy(consul) require.NoError(t, err) - actualPolicy, _, err := client.ConsulClient.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) + actualPolicy, _, err := consul.ACL().PolicyReadByName(anonymousTokenPolicyName, nil) require.NoError(t, err) // assert policy is still same. diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index 9ceb7f8a11..698da2a25c 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( @@ -16,22 +13,22 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + k8sflags "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-netaddrs" - vaultApi "github.com/hashicorp/vault/api" "github.com/mitchellh/cli" "github.com/mitchellh/mapstructure" "golang.org/x/text/cases" "golang.org/x/text/language" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - k8sflags "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { @@ -44,8 +41,6 @@ type Command struct { flagResourcePrefix string flagK8sNamespace string - flagResourceAPIs bool // Use V2 APIs - flagAllowDNS bool flagSetServerTokens bool @@ -91,10 +86,8 @@ type Command struct { flagEnableInjectK8SNSMirroring bool // Enables mirroring of k8s namespaces into Consul for Connect inject flagInjectK8SNSMirroringPrefix string // Prefix added to Consul namespaces created when mirroring injected services - // Flags for the secrets backend. - flagSecretsBackend SecretsBackendType - flagBootstrapTokenSecretName string - flagBootstrapTokenSecretKey string + // Flag to support a custom bootstrap token. + flagBootstrapTokenFile string flagLogLevel string flagLogJSON bool @@ -105,9 +98,7 @@ type Command struct { // flagFederation indicates if federation has been enabled in the cluster. flagFederation bool - backend SecretsBackend // for unit testing. - clientset kubernetes.Interface - vaultClient *vaultApi.Client + clientset kubernetes.Interface watcher consul.ServerConnectionManager @@ -115,9 +106,6 @@ type Command struct { ctx context.Context retryDuration time.Duration - // the amount of time to contact the Consul API before timing out - apiTimeoutDuration time.Duration - // log log hclog.Logger @@ -136,9 +124,6 @@ func (c *Command) init() { c.flags.BoolVar(&c.flagSetServerTokens, "set-server-tokens", true, "Toggle for setting agent tokens for the servers.") - c.flags.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, - "Enable or disable Consul V2 Resource APIs. This will affect the binding rule used for Kubernetes auth (Service vs. WorkloadIdentity)") - c.flags.BoolVar(&c.flagAllowDNS, "allow-dns", false, "Toggle for updating the anonymous token to allow DNS queries to work") c.flags.BoolVar(&c.flagClient, "client", true, @@ -209,14 +194,9 @@ func (c *Command) init() { c.flags.BoolVar(&c.flagFederation, "federation", false, "Toggle for when federation has been enabled.") - c.flags.StringVar((*string)(&c.flagSecretsBackend), "secrets-backend", "kubernetes", - `The secrets backend to use. Either "vault" or "kubernetes". Defaults to "kubernetes"`) - c.flags.StringVar(&c.flagBootstrapTokenSecretName, "bootstrap-token-secret-name", "", - "The name of the Vault or Kuberenetes secret for the bootstrap token. This token must have `ac::write` permission "+ - "in order to create policies and tokens. If not provided or if the secret is empty, then this command will "+ - "bootstrap ACLs and write the bootstrap token to this secret.") - c.flags.StringVar(&c.flagBootstrapTokenSecretKey, "bootstrap-token-secret-key", "", - "The key within the Vault or Kuberenetes secret containing the bootstrap token.") + c.flags.StringVar(&c.flagBootstrapTokenFile, "bootstrap-token-file", "", + "Path to file containing ACL token for creating policies and tokens. This token must have 'acl:write' permissions."+ + "When provided, servers will not be bootstrapped and their policies and tokens will not be updated.") c.flags.DurationVar(&c.flagTimeout, "timeout", 10*time.Minute, "How long we'll try to bootstrap ACLs for before timing out, e.g. 1ms, 2s, 3m") @@ -236,12 +216,6 @@ func (c *Command) init() { if c.retryDuration == 0 { c.retryDuration = 1 * time.Second } - - // Most of the API calls are in an infinite loop until the command cancels. This timeout - // allows us to refresh the server IPs so that calls will succeed. - if c.apiTimeoutDuration == 0 { - c.apiTimeoutDuration = 2 * time.Minute - } } func (c *Command) Synopsis() string { return synopsis } @@ -257,7 +231,6 @@ func (c *Command) Help() string { // The function will retry its tasks indefinitely until they are complete. func (c *Command) Run(args []string) int { c.once.Do(c.init) - defer c.quitVaultAgent() if err := c.flags.Parse(args); err != nil { return 1 } @@ -291,6 +264,16 @@ func (c *Command) Run(args []string) int { } } + var providedBootstrapToken string + if c.flagBootstrapTokenFile != "" { + var err error + providedBootstrapToken, err = loadTokenFromFile(c.flagBootstrapTokenFile) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + } + var cancel context.CancelFunc c.ctx, cancel = context.WithTimeout(context.Background(), c.flagTimeout) // The context will only ever be intentionally ended by the timeout. @@ -311,12 +294,20 @@ func (c *Command) Run(args []string) int { } } - if err := c.configureSecretsBackend(); err != nil { - c.log.Error(err.Error()) - return 1 + var ipAddrs []net.IPAddr + if err := backoff.Retry(func() error { + ipAddrs, err = netaddrs.IPAddrs(c.ctx, c.consulFlags.Addresses, c.log) + if err != nil { + c.log.Error("Error resolving IP Address", "err", err) + return err + } + return nil + }, exponentialBackoffWithMaxInterval()); err != nil { + c.UI.Error(err.Error()) } var bootstrapToken string + if c.flagACLReplicationTokenFile != "" && !c.flagCreateACLReplicationToken { // If ACL replication is enabled, we don't need to ACL bootstrap the servers // since they will be performing replication. @@ -325,22 +316,22 @@ func (c *Command) Run(args []string) int { c.log.Info("ACL replication is enabled so skipping Consul server ACL bootstrapping") bootstrapToken = aclReplicationToken } else { - // During upgrades, there is a rare case where a consul-server statefulset may be rotated out while the - // bootstrap tokens are being updated. Catch this case, refresh the server ip addresses and try again. - if err := backoff.Retry(func() error { - ipAddrs, err := c.serverIPAddresses() + // Check if we've already been bootstrapped. + var bootTokenSecretName string + if providedBootstrapToken != "" { + c.log.Info("Using provided bootstrap token") + bootstrapToken = providedBootstrapToken + } else { + bootTokenSecretName = c.withPrefix("bootstrap-acl-token") + bootstrapToken, err = c.getBootstrapToken(bootTokenSecretName) if err != nil { - c.log.Error(err.Error()) - return err + c.log.Error(fmt.Sprintf("Unexpected error looking for preexisting bootstrap Secret: %s", err)) + return 1 } + } - bootstrapToken, err = c.bootstrapServers(ipAddrs, c.backend) - if err != nil { - c.log.Error(err.Error()) - return err - } - return nil - }, exponentialBackoffWithMaxInterval()); err != nil { + bootstrapToken, err = c.bootstrapServers(ipAddrs, bootstrapToken, bootTokenSecretName) + if err != nil { c.log.Error(err.Error()) return 1 } @@ -374,12 +365,12 @@ func (c *Command) Run(args []string) int { return 1 } - dynamicClient, err := consul.NewDynamicClientFromConnMgr(c.consulFlags.ConsulClientConfig(), watcher) + consulClient, err := consul.NewClientFromConnMgrState(c.consulFlags.ConsulClientConfig(), c.state) if err != nil { c.log.Error(fmt.Sprintf("Error creating Consul client for addr %q: %s", c.state.Address, err)) return 1 } - consulDC, primaryDC, err := c.consulDatacenterList(dynamicClient) + consulDC, primaryDC, err := c.consulDatacenterList(consulClient) if err != nil { c.log.Error("Error getting datacenter name", "err", err) return 1 @@ -390,9 +381,9 @@ func (c *Command) Run(args []string) int { if c.consulFlags.Partition == consulDefaultPartition && primary { // Partition token is local because only the Primary datacenter can have Admin Partitions. if c.flagPartitionTokenFile != "" { - err = c.createACLWithSecretID("partitions", partitionRules, consulDC, primary, dynamicClient, partitionToken, true) + err = c.createACLWithSecretID("partitions", partitionRules, consulDC, primary, consulClient, partitionToken, true) } else { - err = c.createLocalACL("partitions", partitionRules, consulDC, primary, dynamicClient) + err = c.createLocalACL("partitions", partitionRules, consulDC, primary, consulClient) } if err != nil { c.log.Error(err.Error()) @@ -419,7 +410,7 @@ func (c *Command) Run(args []string) int { } err = c.untilSucceeds(fmt.Sprintf("creating %s policy", policyTmpl.Name), func() error { - return c.createOrUpdateACLPolicy(policyTmpl, dynamicClient) + return c.createOrUpdateACLPolicy(policyTmpl, consulClient) }) if err != nil { c.log.Error("Error creating or updating the cross namespace policy", "err", err) @@ -436,7 +427,7 @@ func (c *Command) Run(args []string) int { Name: consulDefaultNamespace, ACLs: &aclConfig, } - _, _, err = dynamicClient.ConsulClient.Namespaces().Update(&consulNamespace, &api.WriteOptions{}) + _, _, err = consulClient.Namespaces().Update(&consulNamespace, &api.WriteOptions{}) if err != nil { if strings.Contains(strings.ToLower(err.Error()), "unexpected response code: 404") { // If this returns a 404 it's most likely because they're not running @@ -452,7 +443,7 @@ func (c *Command) Run(args []string) int { // Create the component auth method, this is the auth method that Consul components will use // to issue an `ACL().Login()` against at startup, for local tokens. localComponentAuthMethodName := c.withPrefix("k8s-component-auth-method") - err = c.configureLocalComponentAuthMethod(dynamicClient, localComponentAuthMethodName) + err = c.configureLocalComponentAuthMethod(consulClient, localComponentAuthMethodName) if err != nil { c.log.Error(err.Error()) return 1 @@ -460,7 +451,7 @@ func (c *Command) Run(args []string) int { globalComponentAuthMethodName := fmt.Sprintf("%s-%s", localComponentAuthMethodName, consulDC) if !primary && c.flagAuthMethodHost != "" { - err = c.configureGlobalComponentAuthMethod(dynamicClient, globalComponentAuthMethodName, primaryDC) + err = c.configureGlobalComponentAuthMethod(consulClient, globalComponentAuthMethodName, primaryDC) if err != nil { c.log.Error(err.Error()) return 1 @@ -475,7 +466,7 @@ func (c *Command) Run(args []string) int { } serviceAccountName := c.withPrefix("client") - err = c.createACLPolicyRoleAndBindingRule("client", agentRules, consulDC, primaryDC, false, primary, localComponentAuthMethodName, serviceAccountName, dynamicClient) + err = c.createACLPolicyRoleAndBindingRule("client", agentRules, consulDC, primaryDC, false, primary, localComponentAuthMethodName, serviceAccountName, consulClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -491,7 +482,7 @@ func (c *Command) Run(args []string) int { if c.consulFlags.Partition != "" { anonTokenConfig.APIClientConfig.Partition = consulDefaultPartition } - anonTokenClient, err := consul.NewDynamicClientFromConnMgr(anonTokenConfig, watcher) + anonTokenClient, err := consul.NewClientFromConnMgrState(anonTokenConfig, c.state) if err != nil { c.log.Error(err.Error()) return 1 @@ -522,9 +513,9 @@ func (c *Command) Run(args []string) int { if !primary { componentAuthMethodName = globalComponentAuthMethodName } - err = c.createACLPolicyRoleAndBindingRule("sync-catalog", syncRules, consulDC, primaryDC, globalPolicy, primary, componentAuthMethodName, serviceAccountName, dynamicClient) + err = c.createACLPolicyRoleAndBindingRule("sync-catalog", syncRules, consulDC, primaryDC, globalPolicy, primary, componentAuthMethodName, serviceAccountName, consulClient) } else { - err = c.createACLPolicyRoleAndBindingRule("sync-catalog", syncRules, consulDC, primaryDC, localPolicy, primary, componentAuthMethodName, serviceAccountName, dynamicClient) + err = c.createACLPolicyRoleAndBindingRule("sync-catalog", syncRules, consulDC, primaryDC, localPolicy, primary, componentAuthMethodName, serviceAccountName, consulClient) } if err != nil { c.log.Error(err.Error()) @@ -534,7 +525,7 @@ func (c *Command) Run(args []string) int { if c.flagConnectInject { connectAuthMethodName := c.withPrefix("k8s-auth-method") - err := c.configureConnectInjectAuthMethod(dynamicClient, connectAuthMethodName) + err := c.configureConnectInjectAuthMethod(consulClient, connectAuthMethodName) if err != nil { c.log.Error(err.Error()) return 1 @@ -556,7 +547,7 @@ func (c *Command) Run(args []string) int { if !primary { componentAuthMethodName = globalComponentAuthMethodName } - err = c.createACLPolicyRoleAndBindingRule("connect-inject", injectRules, consulDC, primaryDC, globalPolicy, primary, componentAuthMethodName, serviceAccountName, dynamicClient) + err = c.createACLPolicyRoleAndBindingRule("connect-inject", injectRules, consulDC, primaryDC, globalPolicy, primary, componentAuthMethodName, serviceAccountName, consulClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -566,9 +557,9 @@ func (c *Command) Run(args []string) int { if c.flagCreateEntLicenseToken { var err error if c.consulFlags.Partition != "" { - err = c.createLocalACL("enterprise-license", entPartitionLicenseRules, consulDC, primary, dynamicClient) + err = c.createLocalACL("enterprise-license", entPartitionLicenseRules, consulDC, primary, consulClient) } else { - err = c.createLocalACL("enterprise-license", entLicenseRules, consulDC, primary, dynamicClient) + err = c.createLocalACL("enterprise-license", entLicenseRules, consulDC, primary, consulClient) } if err != nil { c.log.Error(err.Error()) @@ -578,7 +569,7 @@ func (c *Command) Run(args []string) int { if c.flagSnapshotAgent { serviceAccountName := c.withPrefix("server") - if err := c.createACLPolicyRoleAndBindingRule("snapshot-agent", snapshotAgentRules, consulDC, primaryDC, localPolicy, primary, localComponentAuthMethodName, serviceAccountName, dynamicClient); err != nil { + if err := c.createACLPolicyRoleAndBindingRule("snapshot-agent", snapshotAgentRules, consulDC, primaryDC, localPolicy, primary, localComponentAuthMethodName, serviceAccountName, consulClient); err != nil { c.log.Error(err.Error()) return 1 } @@ -599,7 +590,7 @@ func (c *Command) Run(args []string) int { if !primary { authMethodName = globalComponentAuthMethodName } - err = c.createACLPolicyRoleAndBindingRule("api-gateway-controller", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, dynamicClient) + err = c.createACLPolicyRoleAndBindingRule("api-gateway-controller", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, consulClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -620,7 +611,7 @@ func (c *Command) Run(args []string) int { if !primary { authMethodName = globalComponentAuthMethodName } - err = c.createACLPolicyRoleAndBindingRule("mesh-gateway", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, dynamicClient) + err = c.createACLPolicyRoleAndBindingRule("mesh-gateway", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, consulClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -637,7 +628,7 @@ func (c *Command) Run(args []string) int { PrimaryDC: primaryDC, Primary: primary, } - err := c.configureGateway(params, dynamicClient) + err := c.configureGateway(params, consulClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -654,7 +645,7 @@ func (c *Command) Run(args []string) int { PrimaryDC: primaryDC, Primary: primary, } - err := c.configureGateway(params, dynamicClient) + err := c.configureGateway(params, consulClient) if err != nil { c.log.Error(err.Error()) return 1 @@ -670,9 +661,9 @@ func (c *Command) Run(args []string) int { // Policy must be global because it replicates from the primary DC // and so the primary DC needs to be able to accept the token. if aclReplicationToken != "" { - err = c.createACLWithSecretID(common.ACLReplicationTokenName, rules, consulDC, primary, dynamicClient, aclReplicationToken, false) + err = c.createACLWithSecretID(common.ACLReplicationTokenName, rules, consulDC, primary, consulClient, aclReplicationToken, false) } else { - err = c.createGlobalACL(common.ACLReplicationTokenName, rules, consulDC, primary, dynamicClient) + err = c.createGlobalACL(common.ACLReplicationTokenName, rules, consulDC, primary, consulClient) } if err != nil { c.log.Error(err.Error()) @@ -696,7 +687,7 @@ func exponentialBackoffWithMaxInterval() *backoff.ExponentialBackOff { // configureGlobalComponentAuthMethod sets up an AuthMethod in the primary datacenter, // that the Consul components will use to issue global ACL tokens with. -func (c *Command) configureGlobalComponentAuthMethod(client *consul.DynamicClient, authMethodName, primaryDC string) error { +func (c *Command) configureGlobalComponentAuthMethod(consulClient *api.Client, authMethodName, primaryDC string) error { // Create the auth method template. This requires calls to the kubernetes environment. authMethod, err := c.createAuthMethodTmpl(authMethodName, false) if err != nil { @@ -704,33 +695,29 @@ func (c *Command) configureGlobalComponentAuthMethod(client *consul.DynamicClien } authMethod.TokenLocality = "global" writeOptions := &api.WriteOptions{Datacenter: primaryDC} - return c.createAuthMethod(client, &authMethod, writeOptions) + return c.createAuthMethod(consulClient, &authMethod, writeOptions) } // configureLocalComponentAuthMethod sets up an AuthMethod in the same datacenter, // that the Consul components will use to issue local ACL tokens with. -func (c *Command) configureLocalComponentAuthMethod(client *consul.DynamicClient, authMethodName string) error { +func (c *Command) configureLocalComponentAuthMethod(consulClient *api.Client, authMethodName string) error { // Create the auth method template. This requires calls to the kubernetes environment. authMethod, err := c.createAuthMethodTmpl(authMethodName, false) if err != nil { return err } - return c.createAuthMethod(client, &authMethod, &api.WriteOptions{}) + return c.createAuthMethod(consulClient, &authMethod, &api.WriteOptions{}) } // createAuthMethod creates the desired Authmethod. -func (c *Command) createAuthMethod(client *consul.DynamicClient, authMethod *api.ACLAuthMethod, writeOptions *api.WriteOptions) error { +func (c *Command) createAuthMethod(consulClient *api.Client, authMethod *api.ACLAuthMethod, writeOptions *api.WriteOptions) error { return c.untilSucceeds(fmt.Sprintf("creating auth method %s", authMethod.Name), func() error { var err error - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } // `AuthMethodCreate` will also be able to update an existing // AuthMethod based on the name provided. This means that any // configuration changes will correctly update the AuthMethod. - _, _, err = client.ConsulClient.ACL().AuthMethodCreate(authMethod, writeOptions) + _, _, err = consulClient.ACL().AuthMethodCreate(authMethod, writeOptions) return err }) } @@ -755,7 +742,7 @@ type ConfigureGatewayParams struct { Primary bool } -func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, client *consul.DynamicClient) error { +func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, consulClient *api.Client) error { // Each gateway needs to be configured // separately because users may need to attach different policies // to each gateway role depending on what services it represents. @@ -810,7 +797,7 @@ func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, client serviceAccountName := c.withPrefix(name) err = c.createACLPolicyRoleAndBindingRule(name, rules, gatewayParams.ConsulDC, gatewayParams.PrimaryDC, localPolicy, - gatewayParams.Primary, gatewayParams.AuthMethodName, serviceAccountName, client) + gatewayParams.Primary, gatewayParams.AuthMethodName, serviceAccountName, consulClient) if err != nil { c.log.Error(err.Error()) return err @@ -819,6 +806,24 @@ func (c *Command) configureGateway(gatewayParams ConfigureGatewayParams, client return nil } +// getBootstrapToken returns the existing bootstrap token if there is one by +// reading the Kubernetes Secret with name secretName. +// If there is no bootstrap token yet, then it returns an empty string (not an error). +func (c *Command) getBootstrapToken(secretName string) (string, error) { + secret, err := c.clientset.CoreV1().Secrets(c.flagK8sNamespace).Get(c.ctx, secretName, metav1.GetOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return "", nil + } + return "", err + } + token, ok := secret.Data[common.ACLTokenSecretKey] + if !ok { + return "", fmt.Errorf("secret %q does not have data key 'token'", secretName) + } + return string(token), nil +} + func (c *Command) configureKubeClient() error { config, err := subcommand.K8SConfig(c.k8s.KubeConfig()) if err != nil { @@ -831,60 +836,9 @@ func (c *Command) configureKubeClient() error { return nil } -// configureSecretsBackend configures either the Kubernetes or Vault -// secrets backend based on flags. -func (c *Command) configureSecretsBackend() error { - if c.backend != nil { - // support a fake backend in unit tests - return nil - } - secretName := c.flagBootstrapTokenSecretName - if secretName == "" { - secretName = c.withPrefix("bootstrap-acl-token") - } - - secretKey := c.flagBootstrapTokenSecretKey - if secretKey == "" { - secretKey = common.ACLTokenSecretKey - } - - switch c.flagSecretsBackend { - case SecretsBackendTypeKubernetes: - c.backend = &KubernetesSecretsBackend{ - ctx: c.ctx, - clientset: c.clientset, - k8sNamespace: c.flagK8sNamespace, - secretName: secretName, - secretKey: secretKey, - } - return nil - case SecretsBackendTypeVault: - cfg := vaultApi.DefaultConfig() - cfg.Address = "" - cfg.AgentAddress = "http://127.0.0.1:8200" - vaultClient, err := vaultApi.NewClient(cfg) - if err != nil { - return fmt.Errorf("Error initializing Vault client: %w", err) - } - - c.vaultClient = vaultClient // must set this for c.quitVaultAgent. - c.backend = &VaultSecretsBackend{ - vaultClient: c.vaultClient, - secretName: secretName, - secretKey: secretKey, - } - return nil - default: - validValues := []SecretsBackendType{SecretsBackendTypeKubernetes, SecretsBackendTypeVault} - return fmt.Errorf("Invalid value for -secrets-backend: %q. Valid values are %v.", c.flagSecretsBackend, validValues) - } -} - // untilSucceeds runs op until it returns a nil error. -// If c.timeoutDuration is reached it will exit so that the command can be retried with updated server settings // If c.cmdTimeout is cancelled it will exit. func (c *Command) untilSucceeds(opName string, op func() error) error { - timeoutCh := time.After(c.apiTimeoutDuration) for { err := op() if err == nil { @@ -898,8 +852,6 @@ func (c *Command) untilSucceeds(opName string, op func() error) error { select { case <-time.After(c.retryDuration): continue - case <-timeoutCh: - return errors.New("reached api timeout") case <-c.ctx.Done(): return errors.New("reached command timeout") } @@ -915,16 +867,12 @@ func (c *Command) withPrefix(resource string) string { // consulDatacenterList returns the current datacenter name and the primary datacenter using the // /agent/self API endpoint. -func (c *Command) consulDatacenterList(client *consul.DynamicClient) (string, string, error) { +func (c *Command) consulDatacenterList(client *api.Client) (string, string, error) { var agentCfg map[string]map[string]interface{} err := c.untilSucceeds("calling /agent/self to get datacenter", func() error { var opErr error - opErr = client.RefreshClient() - if opErr != nil { - c.log.Error("could not refresh client", opErr) - } - agentCfg, opErr = client.ConsulClient.Agent().Self() + agentCfg, opErr = client.Agent().Self() return opErr }) if err != nil { @@ -1014,10 +962,6 @@ func (c *Command) validateFlags() error { return errors.New("-consul-api-timeout must be set to a value greater than 0") } - //if c.flagVaultNamespace != "" && c.flagSecretsBackend != SecretsBackendTypeVault { - // return fmt.Errorf("-vault-namespace not supported for -secrets-backend=%q", c.flagSecretsBackend) - //} - return nil } @@ -1033,47 +977,6 @@ func loadTokenFromFile(tokenFile string) (string, error) { return strings.TrimSpace(string(tokenBytes)), nil } -func (c *Command) quitVaultAgent() { - if c.vaultClient == nil { - return - } - - // Tell the Vault agent sidecar to quit. Without this, the Job does not - // complete because the Vault agent does not stop. This retries because it - // does not know exactly when the Vault agent sidecar will start. - err := c.untilSucceeds("tell Vault agent to quit", func() error { - // TODO: RawRequest is deprecated, but there is also not a high level - // method for this in the Vault client. - // nolint:staticcheck // SA1004 ignore - _, err := c.vaultClient.RawRequest( - c.vaultClient.NewRequest("POST", "/agent/v1/quit"), - ) - return err - }) - if err != nil { - c.log.Error("Error telling Vault agent to quit", "error", err) - } -} - -// serverIPAddresses attempts to refresh the server IPs using netaddrs methods. These 'raw' IPs are used -// when boostrapping ACLs and before consul-server-connection-manager runs. -func (c *Command) serverIPAddresses() ([]net.IPAddr, error) { - var ipAddrs []net.IPAddr - var err error - if err = backoff.Retry(func() error { - ipAddrs, err = netaddrs.IPAddrs(c.ctx, c.consulFlags.Addresses, c.log) - if err != nil { - c.log.Error("Error resolving IP Address", "err", err) - return err - } - c.log.Info("Refreshing server IP addresses", "addresses", ipAddrs) - return nil - }, exponentialBackoffWithMaxInterval()); err != nil { - return nil, err - } - return ipAddrs, nil -} - const ( consulDefaultNamespace = "default" consulDefaultPartition = "default" diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index e28af9ef35..4b2d8edc85 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise package serveraclinit @@ -15,6 +12,9 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" @@ -23,10 +23,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // Test the auth method and acl binding rule created when namespaces are enabled @@ -34,27 +30,10 @@ import ( func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { t.Parallel() - cases := map[string]struct { - Destination string - ExtraFlags []string - V2BindingRule bool - }{ - "consul default ns": { - Destination: "default", - }, - "consul non-default ns": { - Destination: "destination", - }, - "consul non-default ns w/ resource-apis": { - Destination: "destination", - ExtraFlags: []string{"-enable-resource-apis=true"}, - V2BindingRule: true, - }, - } - - for name, c := range cases { - t.Run(name, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt, false) + consulDestNamespaces := []string{"default", "destination"} + for _, consulDestNamespace := range consulDestNamespaces { + t.Run(consulDestNamespace, func(tt *testing.T) { + k8s, testAgent := completeSetup(tt) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -72,14 +51,10 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { "-connect-inject", "-partition=default", "-enable-namespaces", - "-consul-inject-destination-namespace", c.Destination, + "-consul-inject-destination-namespace", consulDestNamespace, "-acl-binding-rule-selector=serviceaccount.name!=default", } - if len(c.ExtraFlags) > 0 { - args = append(args, c.ExtraFlags...) - } - responseCode := cmd.Run(args) require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) @@ -92,11 +67,11 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { // Ensure there's only one auth method. namespaceQuery := &api.QueryOptions{ - Namespace: c.Destination, + Namespace: consulDestNamespace, } methods, _, err := consul.ACL().AuthMethodList(namespaceQuery) require.NoError(t, err) - if c.Destination == "default" { + if consulDestNamespace == "default" { // If the destination mamespace is default then AuthMethodList // will return the component-auth-method as well. require.Len(t, methods, 2) @@ -118,19 +93,13 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, namespaceQuery) require.NoError(t, err) require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, namespaceQuery) + actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, namespaceQuery) require.NoError(t, err) - require.NotNil(t, aclRule) - if c.V2BindingRule { - require.Equal(t, api.BindingRuleBindTypeTemplatedPolicy, aclRule.BindType) - require.Equal(t, "builtin/workload-identity", aclRule.BindName) - require.Equal(t, "${serviceaccount.name}", aclRule.BindVars.Name) - } else { - require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) - require.Equal(t, "${serviceaccount.name}", aclRule.BindName) - } - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) + require.NotNil(t, actRule) + require.Equal(t, "Kubernetes binding rule", actRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) + require.Equal(t, "${serviceaccount.name}", actRule.BindName) + require.Equal(t, "serviceaccount.name!=default", actRule.Selector) // Check that the default namespace got an attached ACL policy defNamespace, _, err := consul.Namespaces().Read("default", &api.QueryOptions{}) @@ -140,7 +109,7 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { require.Len(t, defNamespace.ACLs.PolicyDefaults, 1) require.Equal(t, "cross-namespace-policy", defNamespace.ACLs.PolicyDefaults[0].Name) - if c.Destination != "default" { + if consulDestNamespace != "default" { // Check that only one namespace was created besides the // already existing `default` namespace namespaces, _, err := consul.Namespaces().List(&api.QueryOptions{}) @@ -148,10 +117,10 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { require.Len(t, namespaces, 2) // Check the created namespace properties - actNamespace, _, err := consul.Namespaces().Read(c.Destination, &api.QueryOptions{}) + actNamespace, _, err := consul.Namespaces().Read(consulDestNamespace, &api.QueryOptions{}) require.NoError(t, err) require.NotNil(t, actNamespace) - require.Equal(t, c.Destination, actNamespace.Name) + require.Equal(t, consulDestNamespace, actNamespace.Name) require.Equal(t, "Auto-generated by consul-k8s", actNamespace.Description) require.NotNil(t, actNamespace.ACLs) require.Len(t, actNamespace.ACLs.PolicyDefaults, 1) @@ -171,7 +140,6 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { cases := map[string]struct { MirroringPrefix string ExtraFlags []string - V2BindingRule bool }{ "no prefix": { MirroringPrefix: "", @@ -187,16 +155,11 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { // effect. ExtraFlags: []string{"-consul-inject-destination-namespace=dest"}, }, - "no prefix w/ resource-apis": { - MirroringPrefix: "", - ExtraFlags: []string{"-enable-resource-apis=true"}, - V2BindingRule: true, - }, } for name, c := range cases { t.Run(name, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt, false) + k8s, testAgent := completeSetup(tt) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -245,19 +208,13 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, nil) require.NoError(t, err) require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, nil) + actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, nil) require.NoError(t, err) - require.NotNil(t, aclRule) - if c.V2BindingRule { - require.Equal(t, api.BindingRuleBindTypeTemplatedPolicy, aclRule.BindType) - require.Equal(t, "builtin/workload-identity", aclRule.BindName) - require.Equal(t, "${serviceaccount.name}", aclRule.BindVars.Name) - } else { - require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) - require.Equal(t, "${serviceaccount.name}", aclRule.BindName) - } - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) + require.NotNil(t, actRule) + require.Equal(t, "Kubernetes binding rule", actRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) + require.Equal(t, "${serviceaccount.name}", actRule.BindName) + require.Equal(t, "serviceaccount.name!=default", actRule.Selector) }) } } @@ -266,6 +223,7 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { // a non-default partition. func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + tokenFile := common.WriteTempFile(t, bootToken) server := partitionedSetup(t, bootToken, "test") k8s := fake.NewSimpleClientset() setUpK8sServiceAccount(t, k8s, ns) @@ -274,7 +232,6 @@ func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmd.init() args := []string{ @@ -283,6 +240,7 @@ func TestRun_AnonymousToken_CreatedFromNonDefaultPartition(t *testing.T) { "-grpc-port=" + strings.Split(server.GRPCAddr, ":")[1], "-resource-prefix=" + resourcePrefix, "-k8s-namespace=" + ns, + "-bootstrap-token-file", tokenFile, "-allow-dns", "-partition=test", "-enable-namespaces", @@ -315,7 +273,7 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { k8sNamespaceFlags := []string{"default", "other"} for _, k8sNamespaceFlag := range k8sNamespaceFlags { t.Run(k8sNamespaceFlag, func(t *testing.T) { - k8s, testAgent := completeSetup(t, false) + k8s, testAgent := completeSetup(t) setUpK8sServiceAccount(t, k8s, k8sNamespaceFlag) ui := cli.NewMockUi() @@ -375,7 +333,6 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { "enterprise-license-token", "igw-policy", "anotherigw-policy", - "builtin/global-read-only", "tgw-policy", "anothertgw-policy", "connect-inject-policy", @@ -392,14 +349,12 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { actualPolicies[p.Name] = policy.Rules } for _, expected := range firstRunExpectedPolicies { - aclRule, ok := actualPolicies[expected] + actRules, ok := actualPolicies[expected] require.True(t, ok, "Did not find policy %s", expected) // We assert that the policy doesn't have any namespace config // in it because later that's what we're using to test that it - // got updated. builtin/global-ready-only always has namespaces and partitions included - if expected != "builtin/global-read-only" { - require.NotContains(t, aclRule, "namespace", "policy", expected) - } + // got updated. + require.NotContains(t, actRules, "namespace") } // Re-run the command with namespace flags. The policies should be updated. @@ -424,7 +379,6 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { "cross-namespace-policy", "igw-policy", "anotherigw-policy", - "builtin/global-read-only", "tgw-policy", "anothertgw-policy", "partitions-token", @@ -441,27 +395,27 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { actualPolicies[p.Name] = policy.Rules } for _, expected := range secondRunExpectedPolicies { - aclRule, ok := actualPolicies[expected] + actRules, ok := actualPolicies[expected] require.True(t, ok, "Did not find policy %s", expected) switch expected { case "connect-inject-policy": // The connect inject token doesn't have namespace config, // but does change to operator:write from an empty string. - require.Contains(t, aclRule, "policy = \"write\"") + require.Contains(t, actRules, "policy = \"write\"") case "snapshot-agent-policy", "enterprise-license-token": // The snapshot agent and enterprise license tokens shouldn't change. - require.NotContains(t, aclRule, "namespace") - require.Contains(t, aclRule, "acl = \"write\"") + require.NotContains(t, actRules, "namespace") + require.Contains(t, actRules, "acl = \"write\"") case "partitions-token": - require.Contains(t, aclRule, "operator = \"write\"") + require.Contains(t, actRules, "operator = \"write\"") case "anonymous-token-policy": // TODO: This needs to be revisted due to recent changes in how we update the anonymous policy (NET-5174) default: // Assert that the policies have the word namespace in them. This // tests that they were updated. The actual contents are tested // in rules_test.go. - require.Contains(t, aclRule, "namespace") + require.Contains(t, actRules, "namespace") } } }) @@ -489,8 +443,6 @@ func TestRun_ConnectInject_Updates(t *testing.T) { AuthMethodExpectedNamespacePrefixConfig string // Expected namespace for the binding rule. BindingRuleExpectedNS string - // UseV2API, tests the bindingrule is compatible with workloadIdentites. - UseV2API bool }{ "no ns => mirroring ns, no prefix": { FirstRunArgs: nil, @@ -620,148 +572,11 @@ func TestRun_ConnectInject_Updates(t *testing.T) { AuthMethodExpectedNamespacePrefixConfig: "", BindingRuleExpectedNS: "default", }, - "(v2) no ns => mirroring ns, no prefix": { - FirstRunArgs: nil, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) no ns => mirroring ns, prefix": { - FirstRunArgs: nil, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "prefix-", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) no ns => single dest ns": { - FirstRunArgs: nil, - SecondRunArgs: []string{ - "-enable-namespaces", - "-consul-inject-destination-namespace=dest", - }, - AuthMethodExpectedNS: "dest", - AuthMethodExpectMapNamespacesConfig: false, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "dest", - UseV2API: true, - }, - "(v2) mirroring ns => single dest ns": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-consul-inject-destination-namespace=dest", - }, - AuthMethodExpectedNS: "dest", - AuthMethodExpectMapNamespacesConfig: false, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "dest", - UseV2API: true, - }, - "(v2) single dest ns => mirroring ns": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-consul-inject-destination-namespace=dest", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "prefix-", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) mirroring ns (no prefix) => mirroring ns (no prefix)": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) mirroring ns => mirroring ns (same prefix)": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "prefix-", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) mirroring ns (no prefix) => mirroring ns (prefix)": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "prefix-", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) mirroring ns (prefix) => mirroring ns (no prefix)": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, } for name, c := range cases { t.Run(name, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt, c.UseV2API) + k8s, testAgent := completeSetup(tt) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -775,10 +590,6 @@ func TestRun_ConnectInject_Updates(t *testing.T) { "-connect-inject", } - if c.UseV2API { - defaultArgs = append(defaultArgs, "-enable-resource-apis=true") - } - // First run. NOTE: we don't assert anything here since we've // tested these results in other tests. What we care about here // is the result after the second run. @@ -830,11 +641,6 @@ func TestRun_ConnectInject_Updates(t *testing.T) { }) require.NoError(t, err) require.Len(t, rules, 1) - if c.UseV2API { - require.Equal(tt, api.BindingRuleBindTypeTemplatedPolicy, rules[0].BindType) - } else { - require.Equal(tt, api.BindingRuleBindTypeService, rules[0].BindType) - } }) } } @@ -874,7 +680,7 @@ func TestRun_TokensWithNamespacesEnabled(t *testing.T) { } for testName, c := range cases { t.Run(testName, func(t *testing.T) { - k8s, testSvr := completeSetup(t, false) + k8s, testSvr := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -1128,7 +934,7 @@ partition "default" { } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testSvr := completeSetup(t, false) + k8s, testSvr := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -1215,7 +1021,7 @@ func TestRun_NamespaceEnabled_ValidateLoginToken_PrimaryDatacenter(t *testing.T) authMethodName := fmt.Sprintf("%s-%s", resourcePrefix, componentAuthMethod) serviceAccountName := fmt.Sprintf("%s-%s", resourcePrefix, c.ComponentName) - k8s, testSvr := completeSetup(t, false) + k8s, testSvr := completeSetup(t) _, jwtToken := setUpK8sServiceAccount(t, k8s, c.Namespace) k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -1375,7 +1181,7 @@ func TestRun_NamespaceEnabled_ValidateLoginToken_SecondaryDatacenter(t *testing. func TestRun_PartitionTokenDefaultPartition_WithProvidedSecretID(t *testing.T) { t.Parallel() - k8s, testSvr := completeSetup(t, false) + k8s, testSvr := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) partitionToken := "123e4567-e89b-12d3-a456-426614174000" @@ -1449,7 +1255,9 @@ func partitionedSetup(t *testing.T, bootToken string, partitionName string) *tes server.Cfg.APIClientConfig.Token = bootToken serverAPIClient, err := consul.NewClient(server.Cfg.APIClientConfig, 5*time.Second) require.NoError(t, err) + _, _, err = serverAPIClient.Partitions().Create(context.Background(), &api.Partition{Name: partitionName}, &api.WriteOptions{}) require.NoError(t, err) + return server.TestServer } diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index af15429508..3111f58820 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( @@ -18,6 +15,9 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -29,11 +29,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) var ns = "default" @@ -65,6 +60,13 @@ func TestRun_FlagValidation(t *testing.T) { "-resource-prefix=prefix"}, ExpErr: "unable to read token from file \"/notexist\": open /notexist: no such file or directory", }, + { + Flags: []string{ + "-bootstrap-token-file=/notexist", + "-addresses=localhost", + "-resource-prefix=prefix"}, + ExpErr: "unable to read token from file \"/notexist\": open /notexist: no such file or directory", + }, { Flags: []string{ "-addresses=localhost", @@ -104,7 +106,7 @@ func TestRun_FlagValidation(t *testing.T) { func TestRun_Defaults(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -179,7 +181,7 @@ func TestRun_TokensPrimaryDC(t *testing.T) { } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -244,7 +246,7 @@ func TestRun_TokensPrimaryDC(t *testing.T) { func TestRun_ReplicationTokenPrimaryDC_WithProvidedSecretID(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) replicationToken := "123e4567-e89b-12d3-a456-426614174000" @@ -405,6 +407,7 @@ func TestRun_TokensWithProvidedBootstrapToken(t *testing.T) { for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + tokenFile := common.WriteTempFile(t, bootToken) k8s, testAgent := completeBootstrappedSetup(t, bootToken) setUpK8sServiceAccount(t, k8s, ns) @@ -414,11 +417,11 @@ func TestRun_TokensWithProvidedBootstrapToken(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmdArgs := append([]string{ "-timeout=1m", "-k8s-namespace", ns, + "-bootstrap-token-file", tokenFile, "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], @@ -506,7 +509,7 @@ func TestRun_AnonymousTokenPolicy(t *testing.T) { flags = append(flags, "-acl-replication-token-file", tmp.Name()) } else { var testClient *test.TestServerClient - k8s, testClient = completeSetup(t, false) + k8s, testClient = completeSetup(t) consulHTTPAddr = testClient.TestServer.HTTPAddr consulGRPCAddr = testClient.TestServer.GRPCAddr } @@ -581,9 +584,8 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { t.Parallel() cases := map[string]struct { - flags []string - expectedHost string - v2BindingRule bool + flags []string + expectedHost string }{ "-connect-inject flag": { flags: []string{"-connect-inject"}, @@ -596,16 +598,11 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { }, expectedHost: "https://my-kube.com", }, - "-enable-resource-apis flag": { - flags: []string{"-connect-inject", "-enable-resource-apis=true"}, - expectedHost: "https://kubernetes.default.svc", - v2BindingRule: true, - }, } for testName, c := range cases { t.Run(testName, func(t *testing.T) { - k8s, testClient := completeSetup(t, c.v2BindingRule) + k8s, testClient := completeSetup(t) caCert, jwtToken := setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -648,15 +645,8 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, &api.QueryOptions{Token: bootToken}) require.NoError(t, err) require.Len(t, rules, 1) - - if c.v2BindingRule { - require.Equal(t, "templated-policy", string(rules[0].BindType)) - require.Equal(t, "builtin/workload-identity", rules[0].BindName) - require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) - } else { - require.Equal(t, "service", string(rules[0].BindType)) - require.Equal(t, "${serviceaccount.name}", rules[0].BindName) - } + require.Equal(t, "service", string(rules[0].BindType)) + require.Equal(t, "${serviceaccount.name}", rules[0].BindName) require.Equal(t, bindingRuleSelector, rules[0].Selector) // Test that if the same command is re-run it doesn't error. @@ -679,7 +669,7 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { func TestRun_ConnectInjectAuthMethodUpdates(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) caCert, jwtToken := setUpK8sServiceAccount(t, k8s, ns) ui := cli.NewMockUi() @@ -760,86 +750,7 @@ func TestRun_ConnectInjectAuthMethodUpdates(t *testing.T) { // Test that ACL binding rules are updated if the rule selector changes. func TestRun_BindingRuleUpdates(t *testing.T) { - k8s, testClient := completeSetup(t, false) - setUpK8sServiceAccount(t, k8s, ns) - - consul, err := api.NewClient(&api.Config{ - Address: testClient.TestServer.HTTPAddr, - }) - require.NoError(t, err) - - ui := cli.NewMockUi() - commonArgs := []string{ - "-resource-prefix=" + resourcePrefix, - "-k8s-namespace=" + ns, - "-addresses", strings.Split(testClient.TestServer.HTTPAddr, ":")[0], - "-http-port", strings.Split(testClient.TestServer.HTTPAddr, ":")[1], - "-grpc-port", strings.Split(testClient.TestServer.GRPCAddr, ":")[1], - "-connect-inject", - } - firstRunArgs := append(commonArgs, - "-acl-binding-rule-selector=serviceaccount.name!=default", - ) - // On the second run, we change the binding rule selector. - secondRunArgs := append(commonArgs, - "-acl-binding-rule-selector=serviceaccount.name!=changed", - ) - - // Run the command first to populate the binding rule. - cmd := Command{ - UI: ui, - clientset: k8s, - } - responseCode := cmd.Run(firstRunArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - - // Validate the binding rule. - { - queryOpts := &api.QueryOptions{Token: getBootToken(t, k8s, resourcePrefix, ns)} - authMethodName := resourcePrefix + "-k8s-auth-method" - rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) - require.NoError(t, err) - require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) - require.NoError(t, err) - require.NotNil(t, aclRule) - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) - require.Equal(t, "${serviceaccount.name}", aclRule.BindName) - require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) - } - - // Re-run the command with namespace flags. The policies should be updated. - // NOTE: We're redefining the command so that the old flag values are - // reset. - cmd = Command{ - UI: ui, - clientset: k8s, - } - responseCode = cmd.Run(secondRunArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - - // Check the binding rule is changed expected. - { - queryOpts := &api.QueryOptions{Token: getBootToken(t, k8s, resourcePrefix, ns)} - authMethodName := resourcePrefix + "-k8s-auth-method" - rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) - require.NoError(t, err) - require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) - require.NoError(t, err) - require.NotNil(t, aclRule) - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) - require.Equal(t, "${serviceaccount.name}", aclRule.BindName) - require.Equal(t, "serviceaccount.name!=changed", aclRule.Selector) - } -} - -// Test that the ACL binding template is updated if the rule selector changes. -// V2 only. -func TestRun_TemplateBindingRuleUpdates(t *testing.T) { - k8s, testClient := completeSetup(t, true) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) consul, err := api.NewClient(&api.Config{ @@ -854,7 +765,6 @@ func TestRun_TemplateBindingRuleUpdates(t *testing.T) { "-addresses", strings.Split(testClient.TestServer.HTTPAddr, ":")[0], "-http-port", strings.Split(testClient.TestServer.HTTPAddr, ":")[1], "-grpc-port", strings.Split(testClient.TestServer.GRPCAddr, ":")[1], - "-enable-resource-apis=true", "-connect-inject", } firstRunArgs := append(commonArgs, @@ -880,14 +790,13 @@ func TestRun_TemplateBindingRuleUpdates(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) require.NoError(t, err) require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) + actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) require.NoError(t, err) - require.NotNil(t, aclRule) - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, "templated-policy", string(rules[0].BindType)) - require.Equal(t, "builtin/workload-identity", rules[0].BindName) - require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) - require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) + require.NotNil(t, actRule) + require.Equal(t, "Kubernetes binding rule", actRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) + require.Equal(t, "${serviceaccount.name}", actRule.BindName) + require.Equal(t, "serviceaccount.name!=default", actRule.Selector) } // Re-run the command with namespace flags. The policies should be updated. @@ -907,21 +816,20 @@ func TestRun_TemplateBindingRuleUpdates(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) require.NoError(t, err) require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) + actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) require.NoError(t, err) - require.NotNil(t, aclRule) - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, "templated-policy", string(rules[0].BindType)) - require.Equal(t, "builtin/workload-identity", rules[0].BindName) - require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) - require.Equal(t, "serviceaccount.name!=changed", aclRule.Selector) + require.NotNil(t, actRule) + require.Equal(t, "Kubernetes binding rule", actRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) + require.Equal(t, "${serviceaccount.name}", actRule.BindName) + require.Equal(t, "serviceaccount.name!=changed", actRule.Selector) } } // Test that the catalog sync policy is updated if the Consul node name changes. func TestRun_SyncPolicyUpdates(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) ui := cli.NewMockUi() @@ -1007,6 +915,7 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { // Create Consul with ACLs already bootstrapped so that we can // then seed it with our manually created policy. bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + tokenFile := common.WriteTempFile(t, bootToken) k8s, testAgent := completeBootstrappedSetup(t, bootToken) setUpK8sServiceAccount(t, k8s, ns) @@ -1016,12 +925,6 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { }) require.NoError(t, err) - // Make sure the ACL system is bootstrapped first - require.Eventually(t, func() bool { - _, _, err := consul.ACL().PolicyList(nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - // Create the policy manually. description := "not the expected description" policy, _, err := consul.ACL().PolicyCreate(&api.ACLPolicy{ @@ -1035,11 +938,11 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } cmdArgs := []string{ "-timeout=1s", "-k8s-namespace", ns, + "-bootstrap-token-file", tokenFile, "-resource-prefix=" + resourcePrefix, "-k8s-namespace=" + ns, "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], @@ -1067,7 +970,7 @@ func TestRun_DelayedServers(t *testing.T) { t.Parallel() k8s := fake.NewSimpleClientset() setUpK8sServiceAccount(t, k8s, ns) - randomPorts := freeport.GetN(t, 8) + randomPorts := freeport.GetN(t, 7) ui := cli.NewMockUi() cmd := Command{ @@ -1108,11 +1011,10 @@ func TestRun_DelayedServers(t *testing.T) { DNS: randomPorts[0], HTTP: randomPorts[1], GRPC: randomPorts[2], - GRPCTLS: randomPorts[3], - HTTPS: randomPorts[4], - SerfLan: randomPorts[5], - SerfWan: randomPorts[6], - Server: randomPorts[7], + HTTPS: randomPorts[3], + SerfLan: randomPorts[4], + SerfWan: randomPorts[5], + Server: randomPorts[6], } }) require.NoError(t, err) @@ -1223,7 +1125,7 @@ func TestRun_NoLeader(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), } done := make(chan bool) @@ -1390,14 +1292,14 @@ func TestConsulDatacenterList(t *testing.T) { })) defer consulServer.Close() - client, err := consul.NewDynamicClient(&api.Config{Address: consulServer.URL}) + consulClient, err := api.NewClient(&api.Config{Address: consulServer.URL}) require.NoError(t, err) command := Command{ log: hclog.New(hclog.DefaultOptions), ctx: context.Background(), } - actDC, actPrimaryDC, err := command.consulDatacenterList(client) + actDC, actPrimaryDC, err := command.consulDatacenterList(consulClient) if c.expErr != "" { require.EqualError(t, err, c.expErr) } else { @@ -1479,7 +1381,7 @@ func TestRun_ClientPolicyAndBindingRuleRetry(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), } responseCode := cmd.Run([]string{ "-timeout=1m", @@ -1551,223 +1453,272 @@ func TestRun_ClientPolicyAndBindingRuleRetry(t *testing.T) { // Test if there is an old bootstrap Secret we still try to create and set // server tokens. func TestRun_AlreadyBootstrapped(t *testing.T) { - k8s := fake.NewSimpleClientset() - - type APICall struct { - Method string - Path string + t.Parallel() + cases := map[string]bool{ + "token saved in k8s secret": true, + "token provided via file": false, } - var consulAPICalls []APICall - // Start the Consul server. - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Record all the API calls made. - consulAPICalls = append(consulAPICalls, APICall{ - Method: r.Method, - Path: r.URL.Path, - }) - switch r.URL.Path { - case "/v1/agent/self": - fmt.Fprintln(w, `{"Config": {"Datacenter": "dc1", "PrimaryDatacenter": "dc1"}}`) - case "/v1/acl/tokens": - fmt.Fprintln(w, `[]`) - case "/v1/acl/token": - fmt.Fprintln(w, `{}`) - case "/v1/acl/policy": - fmt.Fprintln(w, `{}`) - case "/v1/agent/token/acl_agent_token": - fmt.Fprintln(w, `{}`) - case "/v1/acl/auth-method": - fmt.Fprintln(w, `{}`) - case "/v1/acl/role/name/release-name-consul-client-acl-role": - w.WriteHeader(404) - case "/v1/acl/role": - fmt.Fprintln(w, `{}`) - case "/v1/acl/binding-rules": - fmt.Fprintln(w, `[]`) - case "/v1/acl/binding-rule": - fmt.Fprintln(w, `{}`) - default: - w.WriteHeader(500) - fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) - } - })) - defer consulServer.Close() + for name, tokenFromK8sSecret := range cases { + t.Run(name, func(t *testing.T) { + k8s := fake.NewSimpleClientset() - serverURL, err := url.Parse(consulServer.URL) - require.NoError(t, err) - port, err := strconv.Atoi(serverURL.Port()) - require.NoError(t, err) - setUpK8sServiceAccount(t, k8s, ns) + type APICall struct { + Method string + Path string + } + var consulAPICalls []APICall - cmdArgs := []string{ - "-timeout=500ms", - "-resource-prefix=" + resourcePrefix, - "-k8s-namespace=" + ns, - "-addresses=" + serverURL.Hostname(), - "-http-port=" + serverURL.Port(), - } + // Start the Consul server. + consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Record all the API calls made. + consulAPICalls = append(consulAPICalls, APICall{ + Method: r.Method, + Path: r.URL.Path, + }) + switch r.URL.Path { + case "/v1/agent/self": + fmt.Fprintln(w, `{"Config": {"Datacenter": "dc1", "PrimaryDatacenter": "dc1"}}`) + case "/v1/acl/tokens": + fmt.Fprintln(w, `[]`) + case "/v1/acl/token": + fmt.Fprintln(w, `{}`) + case "/v1/acl/policy": + fmt.Fprintln(w, `{}`) + case "/v1/agent/token/acl_agent_token": + fmt.Fprintln(w, `{}`) + case "/v1/acl/auth-method": + fmt.Fprintln(w, `{}`) + case "/v1/acl/role/name/release-name-consul-client-acl-role": + w.WriteHeader(404) + case "/v1/acl/role": + fmt.Fprintln(w, `{}`) + case "/v1/acl/binding-rules": + fmt.Fprintln(w, `[]`) + case "/v1/acl/binding-rule": + fmt.Fprintln(w, `{}`) + default: + w.WriteHeader(500) + fmt.Fprintln(w, "Mock Server not configured for this route: "+r.URL.Path) + } + })) + defer consulServer.Close() - // Create the bootstrap secret. - _, err = k8s.CoreV1().Secrets(ns).Create( - context.Background(), - &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourcePrefix + "-bootstrap-acl-token", - Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, - }, - Data: map[string][]byte{ - "token": []byte("old-token"), - }, - }, - metav1.CreateOptions{}) - require.NoError(t, err) + serverURL, err := url.Parse(consulServer.URL) + require.NoError(t, err) + port, err := strconv.Atoi(serverURL.Port()) + require.NoError(t, err) + setUpK8sServiceAccount(t, k8s, ns) - // Run the command. - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), - } + cmdArgs := []string{ + "-timeout=500ms", + "-resource-prefix=" + resourcePrefix, + "-k8s-namespace=" + ns, + "-addresses=" + serverURL.Hostname(), + "-http-port=" + serverURL.Port(), + } - responseCode := cmd.Run(cmdArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + // Create the bootstrap secret. + if tokenFromK8sSecret { + _, err = k8s.CoreV1().Secrets(ns).Create( + context.Background(), + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourcePrefix + "-bootstrap-acl-token", + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{ + "token": []byte("old-token"), + }, + }, + metav1.CreateOptions{}) + require.NoError(t, err) + } else { + // Write token to a file. + bootTokenFile, err := os.CreateTemp("", "") + require.NoError(t, err) + defer os.RemoveAll(bootTokenFile.Name()) - // Test that the Secret is the same. - secret, err := k8s.CoreV1().Secrets(ns).Get(context.Background(), resourcePrefix+"-bootstrap-acl-token", metav1.GetOptions{}) - require.NoError(t, err) - require.Contains(t, secret.Data, "token") - require.Equal(t, "old-token", string(secret.Data["token"])) + _, err = bootTokenFile.WriteString("old-token") + require.NoError(t, err) - // Test that the expected API calls were made. - require.Equal(t, []APICall{ - // We expect calls for updating the server policy, setting server tokens, - // and updating client policy. - { - "PUT", - "/v1/acl/policy", - }, - { - "GET", - "/v1/acl/tokens", - }, - { - "PUT", - "/v1/acl/token", - }, - { - "PUT", - "/v1/agent/token/agent", - }, - { - "PUT", - "/v1/agent/token/acl_agent_token", - }, - { - "GET", - "/v1/agent/self", - }, - { - "PUT", - "/v1/acl/auth-method", - }, - { - "PUT", - "/v1/acl/policy", - }, - { - "GET", - "/v1/acl/role/name/release-name-consul-client-acl-role", - }, - { - "PUT", - "/v1/acl/role", - }, - { - "GET", - "/v1/acl/binding-rules", - }, - { - "PUT", - "/v1/acl/binding-rule", - }, - }, consulAPICalls) + require.NoError(t, err) + cmdArgs = append(cmdArgs, "-bootstrap-token-file", bootTokenFile.Name()) + } + + // Run the command. + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), + } + + responseCode := cmd.Run(cmdArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + + // Test that the Secret is the same. + if tokenFromK8sSecret { + secret, err := k8s.CoreV1().Secrets(ns).Get(context.Background(), resourcePrefix+"-bootstrap-acl-token", metav1.GetOptions{}) + require.NoError(t, err) + require.Contains(t, secret.Data, "token") + require.Equal(t, "old-token", string(secret.Data["token"])) + } + + // Test that the expected API calls were made. + require.Equal(t, []APICall{ + // We expect calls for updating the server policy, setting server tokens, + // and updating client policy. + { + "PUT", + "/v1/acl/policy", + }, + { + "GET", + "/v1/acl/tokens", + }, + { + "PUT", + "/v1/acl/token", + }, + { + "PUT", + "/v1/agent/token/agent", + }, + { + "PUT", + "/v1/agent/token/acl_agent_token", + }, + { + "GET", + "/v1/agent/self", + }, + { + "PUT", + "/v1/acl/auth-method", + }, + { + "PUT", + "/v1/acl/policy", + }, + { + "GET", + "/v1/acl/role/name/release-name-consul-client-acl-role", + }, + { + "PUT", + "/v1/acl/role", + }, + { + "GET", + "/v1/acl/binding-rules", + }, + { + "PUT", + "/v1/acl/binding-rule", + }, + }, consulAPICalls) + }) + } } // Test if there is an old bootstrap Secret and the server token exists // that we don't try and recreate the token. func TestRun_AlreadyBootstrapped_ServerTokenExists(t *testing.T) { - // First set everything up with ACLs bootstrapped. - bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - k8s, testAgent := completeBootstrappedSetup(t, bootToken) - setUpK8sServiceAccount(t, k8s, ns) - - cmdArgs := []string{ - "-timeout=1m", - "-k8s-namespace", ns, - "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], - "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], - "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], - "-resource-prefix", resourcePrefix, + t.Parallel() + cases := map[string]bool{ + "token saved in k8s secret": true, + "token provided via file": false, } - _, err := k8s.CoreV1().Secrets(ns).Create(context.Background(), &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourcePrefix + "-bootstrap-acl-token", - }, - Data: map[string][]byte{ - "token": []byte(bootToken), - }, - }, metav1.CreateOptions{}) - require.NoError(t, err) + for name, tokenInK8sSecret := range cases { + t.Run(name, func(t *testing.T) { - consulClient, err := api.NewClient(&api.Config{ - Address: testAgent.TestServer.HTTPAddr, - Token: bootToken, - }) - require.NoError(t, err) - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - } + // First set everything up with ACLs bootstrapped. + bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + k8s, testAgent := completeBootstrappedSetup(t, bootToken) + setUpK8sServiceAccount(t, k8s, ns) - cmd.init() - // Create the server policy and token _before_ we run the command. - agentPolicyRules, err := cmd.agentRules() - require.NoError(t, err) - policy, _, err := consulClient.ACL().PolicyCreate(&api.ACLPolicy{ - Name: "agent-token", - Description: "Agent Token Policy", - Rules: agentPolicyRules, - }, nil) - require.NoError(t, err) - _, _, err = consulClient.ACL().TokenCreate(&api.ACLToken{ - Description: fmt.Sprintf("Server Token for %s", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0]), - Policies: []*api.ACLTokenPolicyLink{ - { - Name: policy.Name, - }, - }, - }, nil) - require.NoError(t, err) + cmdArgs := []string{ + "-timeout=1m", + "-k8s-namespace", ns, + "-addresses", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0], + "-http-port", strings.Split(testAgent.TestServer.HTTPAddr, ":")[1], + "-grpc-port", strings.Split(testAgent.TestServer.GRPCAddr, ":")[1], + "-resource-prefix", resourcePrefix, + } - // Run the command. - responseCode := cmd.Run(cmdArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + if tokenInK8sSecret { + _, err := k8s.CoreV1().Secrets(ns).Create(context.Background(), &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourcePrefix + "-bootstrap-acl-token", + }, + Data: map[string][]byte{ + "token": []byte(bootToken), + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + } else { + // Write token to a file. + bootTokenFile, err := os.CreateTemp("", "") + require.NoError(t, err) + defer os.RemoveAll(bootTokenFile.Name()) - // Check that only one server token exists, i.e. it didn't create an - // extra token. - tokens, _, err := consulClient.ACL().TokenList(nil) - require.NoError(t, err) - count := 0 - for _, token := range tokens { - if len(token.Policies) == 1 && token.Policies[0].Name == policy.Name { - count++ - } + _, err = bootTokenFile.WriteString(bootToken) + require.NoError(t, err) + + require.NoError(t, err) + cmdArgs = append(cmdArgs, "-bootstrap-token-file", bootTokenFile.Name()) + } + + consulClient, err := api.NewClient(&api.Config{ + Address: testAgent.TestServer.HTTPAddr, + Token: bootToken, + }) + require.NoError(t, err) + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + } + + cmd.init() + // Create the server policy and token _before_ we run the command. + agentPolicyRules, err := cmd.agentRules() + require.NoError(t, err) + policy, _, err := consulClient.ACL().PolicyCreate(&api.ACLPolicy{ + Name: "agent-token", + Description: "Agent Token Policy", + Rules: agentPolicyRules, + }, nil) + require.NoError(t, err) + _, _, err = consulClient.ACL().TokenCreate(&api.ACLToken{ + Description: fmt.Sprintf("Server Token for %s", strings.Split(testAgent.TestServer.HTTPAddr, ":")[0]), + Policies: []*api.ACLTokenPolicyLink{ + { + Name: policy.Name, + }, + }, + }, nil) + require.NoError(t, err) + + // Run the command. + responseCode := cmd.Run(cmdArgs) + require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) + + // Check that only one server token exists, i.e. it didn't create an + // extra token. + tokens, _, err := consulClient.ACL().TokenList(nil) + require.NoError(t, err) + count := 0 + for _, token := range tokens { + if len(token.Policies) == 1 && token.Policies[0].Name == policy.Name { + count++ + } + } + require.Equal(t, 1, count) + }) } - require.Equal(t, 1, count) } // Test if -set-server-tokens is false (i.e. servers are disabled), we skip bootstrapping of the servers @@ -1777,6 +1728,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { k8s := fake.NewSimpleClientset() bootToken := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + tokenFile := common.WriteTempFile(t, bootToken) type APICall struct { Method string @@ -1813,8 +1765,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), - backend: &FakeSecretsBackend{bootstrapToken: bootToken}, + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), } responseCode := cmd.Run([]string{ "-timeout=500ms", @@ -1822,6 +1773,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { "-k8s-namespace=" + ns, "-addresses=" + serverURL.Hostname(), "-http-port=" + serverURL.Port(), + "-bootstrap-token-file=" + tokenFile, "-set-server-tokens=false", "-client=false", // disable client token, so there are fewer calls }) @@ -1845,7 +1797,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { // Test that we exit after timeout. func TestRun_Timeout(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) _, err := api.NewClient(&api.Config{ @@ -1857,7 +1809,7 @@ func TestRun_Timeout(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, "localhost", 12345, false), + watcher: test.MockConnMgrForIPAndPort("localhost", 12345), } responseCode := cmd.Run([]string{ @@ -1997,7 +1949,7 @@ func TestRun_GatewayErrors(t *testing.T) { for testName, c := range cases { t.Run(testName, func(tt *testing.T) { - k8s, testClient := completeSetup(tt, false) + k8s, testClient := completeSetup(tt) setUpK8sServiceAccount(t, k8s, ns) require := require.New(tt) @@ -2099,7 +2051,7 @@ func TestRun_PoliciesAndBindingRulesForACLLogin_PrimaryDatacenter(t *testing.T) } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -2413,7 +2365,7 @@ func TestRun_ValidateLoginToken_PrimaryDatacenter(t *testing.T) { serviceAccountName = c.ServiceAccountName } - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) _, jwtToken := setUpK8sServiceAccount(t, k8s, ns) k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -2629,7 +2581,7 @@ func TestRun_ValidateLoginToken_SecondaryDatacenter(t *testing.T) { func TestRun_PrimaryDatacenter_ComponentAuthMethod(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -2705,20 +2657,14 @@ func TestRun_SecondaryDatacenter_ComponentAuthMethod(t *testing.T) { } // Set up test consul agent and kubernetes cluster. -func completeSetup(t *testing.T, useResourceAPI bool) (*fake.Clientset, *test.TestServerClient) { +func completeSetup(t *testing.T) (*fake.Clientset, *test.TestServerClient) { k8s := fake.NewSimpleClientset() - testServerClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.ACL.Enabled = true - - if useResourceAPI { - c.Experiments = []string{"resource-apis"} - } }) - testServerClient.TestServer.WaitForActiveCARoot(t) - - return k8s, testServerClient + return k8s, testClient } // Set up test consul agent and kubernetes cluster. diff --git a/control-plane/subcommand/server-acl-init/connect_inject.go b/control-plane/subcommand/server-acl-init/connect_inject.go index 0e373d2ea5..e732dae452 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject.go +++ b/control-plane/subcommand/server-acl-init/connect_inject.go @@ -1,17 +1,12 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( "fmt" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) // We use the default Kubernetes service as the default host @@ -22,7 +17,7 @@ const defaultKubernetesHost = "https://kubernetes.default.svc" // configureConnectInject sets up auth methods so that connect injection will // work. -func (c *Command) configureConnectInjectAuthMethod(client *consul.DynamicClient, authMethodName string) error { +func (c *Command) configureConnectInjectAuthMethod(consulClient *api.Client, authMethodName string) error { // Create the auth method template. This requires calls to the // kubernetes environment. @@ -48,11 +43,7 @@ func (c *Command) configureConnectInjectAuthMethod(client *consul.DynamicClient, err = c.untilSucceeds(fmt.Sprintf("checking or creating namespace %s", c.flagConsulInjectDestinationNamespace), func() error { - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - _, err := namespaces.EnsureExists(client.ConsulClient, c.flagConsulInjectDestinationNamespace, "cross-namespace-policy") + _, err := namespaces.EnsureExists(consulClient, c.flagConsulInjectDestinationNamespace, "cross-namespace-policy") return err }) if err != nil { @@ -64,44 +55,26 @@ func (c *Command) configureConnectInjectAuthMethod(client *consul.DynamicClient, err = c.untilSucceeds(fmt.Sprintf("creating auth method %s", authMethodTmpl.Name), func() error { var err error - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } // `AuthMethodCreate` will also be able to update an existing // AuthMethod based on the name provided. This means that any // configuration changes will correctly update the AuthMethod. - _, _, err = client.ConsulClient.ACL().AuthMethodCreate(&authMethodTmpl, &writeOptions) + _, _, err = consulClient.ACL().AuthMethodCreate(&authMethodTmpl, &writeOptions) return err }) if err != nil { return err } - var abr api.ACLBindingRule - if c.flagResourceAPIs { - c.log.Info("creating consul binding rule for WorkloadIdentityName") - abr = api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: authMethodName, - BindType: api.BindingRuleBindTypeTemplatedPolicy, - BindName: api.ACLTemplatedPolicyWorkloadIdentityName, - BindVars: &api.ACLTemplatedPolicyVariables{ - Name: "${serviceaccount.name}", - }, - Selector: c.flagBindingRuleSelector, - } - } else { - abr = api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: authMethodName, - BindType: api.BindingRuleBindTypeService, - BindName: "${serviceaccount.name}", - Selector: c.flagBindingRuleSelector, - } + c.log.Info("creating inject binding rule") + // Create the binding rule. + abr := api.ACLBindingRule{ + Description: "Kubernetes binding rule", + AuthMethod: authMethodName, + BindType: api.BindingRuleBindTypeService, + BindName: "${serviceaccount.name}", + Selector: c.flagBindingRuleSelector, } - - return c.createConnectBindingRule(client, authMethodName, &abr) + return c.createConnectBindingRule(consulClient, authMethodName, &abr) } // createAuthMethodTmpl sets up the auth method template based on the connect-injector's service account diff --git a/control-plane/subcommand/server-acl-init/connect_inject_test.go b/control-plane/subcommand/server-acl-init/connect_inject_test.go index 2714bfa3f7..e7144146b7 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject_test.go +++ b/control-plane/subcommand/server-acl-init/connect_inject_test.go @@ -1,19 +1,15 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( "context" "testing" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" 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/control-plane/subcommand/common" ) // Test that createAuthMethodTmpl returns an error when diff --git a/control-plane/subcommand/server-acl-init/create_or_update.go b/control-plane/subcommand/server-acl-init/create_or_update.go index 254fba4964..af67842445 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update.go +++ b/control-plane/subcommand/server-acl-init/create_or_update.go @@ -1,24 +1,19 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( "fmt" "strings" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // createACLPolicyRoleAndBindingRule will create the ACL Policy for the component // then create a set of ACLRole and ACLBindingRule which tie the component's serviceaccount // to the authMethod, allowing the serviceaccount to later be allowed to issue a Consul Login. -func (c *Command) createACLPolicyRoleAndBindingRule(componentName, rules, dc, primaryDC string, global, primary bool, authMethodName, serviceAccountName string, client *consul.DynamicClient) error { +func (c *Command) createACLPolicyRoleAndBindingRule(componentName, rules, dc, primaryDC string, global, primary bool, authMethodName, serviceAccountName string, client *api.Client) error { // Create policy with the given rules. policyName := fmt.Sprintf("%s-policy", componentName) if c.flagFederation && !primary { @@ -56,7 +51,7 @@ func (c *Command) createACLPolicyRoleAndBindingRule(componentName, rules, dc, pr } // addRoleAndBindingRule adds an ACLRole and ACLBindingRule which reference the authMethod. -func (c *Command) addRoleAndBindingRule(client *consul.DynamicClient, componentName, serviceAccountName, authMethodName string, policies []*api.ACLRolePolicyLink, global, primary bool, primaryDC, dc string) error { +func (c *Command) addRoleAndBindingRule(client *api.Client, componentName, serviceAccountName, authMethodName string, policies []*api.ACLRolePolicyLink, global, primary bool, primaryDC, dc string) error { // This is the ACLRole which will allow the component which uses the serviceaccount // to be able to do a consul login. aclRoleName := c.withPrefix(fmt.Sprintf("%s-acl-role", componentName)) @@ -93,28 +88,24 @@ func (c *Command) addRoleAndBindingRule(client *consul.DynamicClient, componentN // updateOrCreateACLRole will query to see if existing role is in place and update them // or create them if they do not yet exist. -func (c *Command) updateOrCreateACLRole(client *consul.DynamicClient, role *api.ACLRole) error { +func (c *Command) updateOrCreateACLRole(client *api.Client, role *api.ACLRole) error { err := c.untilSucceeds(fmt.Sprintf("update or create acl role for %s", role.Name), func() error { var err error - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - aclRole, _, err := client.ConsulClient.ACL().RoleReadByName(role.Name, &api.QueryOptions{}) + aclRole, _, err := client.ACL().RoleReadByName(role.Name, &api.QueryOptions{}) if err != nil { c.log.Error("unable to read ACL Roles", err) return err } if aclRole != nil { - _, _, err := client.ConsulClient.ACL().RoleUpdate(aclRole, &api.WriteOptions{}) + _, _, err := client.ACL().RoleUpdate(aclRole, &api.WriteOptions{}) if err != nil { c.log.Error("unable to update role", err) return err } return nil } - _, _, err = client.ConsulClient.ACL().RoleCreate(role, &api.WriteOptions{}) + _, _, err = client.ACL().RoleCreate(role, &api.WriteOptions{}) if err != nil { c.log.Error("unable to create role", err) return err @@ -126,7 +117,7 @@ func (c *Command) updateOrCreateACLRole(client *consul.DynamicClient, role *api. // createConnectBindingRule will query to see if existing binding rules are in place and update them // or create them if they do not yet exist. -func (c *Command) createConnectBindingRule(client *consul.DynamicClient, authMethodName string, abr *api.ACLBindingRule) error { +func (c *Command) createConnectBindingRule(client *api.Client, authMethodName string, abr *api.ACLBindingRule) error { // Binding rule list api call query options. queryOptions := api.QueryOptions{} @@ -141,16 +132,12 @@ func (c *Command) createConnectBindingRule(client *consul.DynamicClient, authMet return c.createOrUpdateBindingRule(client, authMethodName, abr, &queryOptions, nil) } -func (c *Command) createOrUpdateBindingRule(client *consul.DynamicClient, authMethodName string, abr *api.ACLBindingRule, queryOptions *api.QueryOptions, writeOptions *api.WriteOptions) error { +func (c *Command) createOrUpdateBindingRule(client *api.Client, authMethodName string, abr *api.ACLBindingRule, queryOptions *api.QueryOptions, writeOptions *api.WriteOptions) error { var existingRules []*api.ACLBindingRule err := c.untilSucceeds(fmt.Sprintf("listing binding rules for auth method %s", authMethodName), func() error { var err error - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - existingRules, _, err = client.ConsulClient.ACL().BindingRuleList(authMethodName, queryOptions) + existingRules, _, err = client.ACL().BindingRuleList(authMethodName, queryOptions) return err }) if err != nil { @@ -179,21 +166,13 @@ func (c *Command) createOrUpdateBindingRule(client *consul.DynamicClient, authMe c.log.Info("unable to find a matching ACL binding rule to update. creating ACL binding rule.") err = c.untilSucceeds(fmt.Sprintf("creating acl binding rule for %s", authMethodName), func() error { - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - _, _, err := client.ConsulClient.ACL().BindingRuleCreate(abr, writeOptions) + _, _, err := client.ACL().BindingRuleCreate(abr, writeOptions) return err }) } else { err = c.untilSucceeds(fmt.Sprintf("updating acl binding rule for %s", authMethodName), func() error { - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - _, _, err := client.ConsulClient.ACL().BindingRuleUpdate(abr, writeOptions) + _, _, err := client.ACL().BindingRuleUpdate(abr, writeOptions) return err }) } @@ -201,11 +180,7 @@ func (c *Command) createOrUpdateBindingRule(client *consul.DynamicClient, authMe // Otherwise create the binding rule err = c.untilSucceeds(fmt.Sprintf("creating acl binding rule for %s", authMethodName), func() error { - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - _, _, err := client.ConsulClient.ACL().BindingRuleCreate(abr, writeOptions) + _, _, err := client.ACL().BindingRuleCreate(abr, writeOptions) return err }) } @@ -214,19 +189,19 @@ func (c *Command) createOrUpdateBindingRule(client *consul.DynamicClient, authMe // createLocalACL creates a policy and acl token for this dc (datacenter), i.e. // the policy is only valid for this datacenter and the token is a local token. -func (c *Command) createLocalACL(name, rules, dc string, isPrimary bool, consulClient *consul.DynamicClient) error { +func (c *Command) createLocalACL(name, rules, dc string, isPrimary bool, consulClient *api.Client) error { return c.createACL(name, rules, true, dc, isPrimary, consulClient, "") } // createGlobalACL creates a global policy and acl token. The policy is valid // for all datacenters and the token is global. dc must be passed because the // policy name may have the datacenter name appended. -func (c *Command) createGlobalACL(name, rules, dc string, isPrimary bool, consulClient *consul.DynamicClient) error { +func (c *Command) createGlobalACL(name, rules, dc string, isPrimary bool, consulClient *api.Client) error { return c.createACL(name, rules, false, dc, isPrimary, consulClient, "") } // createACLWithSecretID creates a global policy and acl token with provided secret ID. -func (c *Command) createACLWithSecretID(name, rules, dc string, isPrimary bool, consulClient *consul.DynamicClient, secretID string, local bool) error { +func (c *Command) createACLWithSecretID(name, rules, dc string, isPrimary bool, consulClient *api.Client, secretID string, local bool) error { return c.createACL(name, rules, local, dc, isPrimary, consulClient, secretID) } @@ -236,7 +211,7 @@ func (c *Command) createACLWithSecretID(name, rules, dc string, isPrimary bool, // When secretID is provided, we will use that value for the created token and // will skip writing it to a Kubernetes secret (because in this case we assume that // this value already exists in some secrets storage). -func (c *Command) createACL(name, rules string, localToken bool, dc string, isPrimary bool, client *consul.DynamicClient, secretID string) error { +func (c *Command) createACL(name, rules string, localToken bool, dc string, isPrimary bool, consulClient *api.Client, secretID string) error { // Create policy with the given rules. policyName := fmt.Sprintf("%s-token", name) if c.flagFederation && !isPrimary { @@ -256,7 +231,7 @@ func (c *Command) createACL(name, rules string, localToken bool, dc string, isPr } err := c.untilSucceeds(fmt.Sprintf("creating %s policy", policyTmpl.Name), func() error { - return c.createOrUpdateACLPolicy(policyTmpl, client) + return c.createOrUpdateACLPolicy(policyTmpl, consulClient) }) if err != nil { return err @@ -284,7 +259,7 @@ func (c *Command) createACL(name, rules string, localToken bool, dc string, isPr } else { // If secretID is provided, we check if the token with secretID already exists in Consul // and exit if it does. Otherwise, set the secretID to the provided value. - _, _, err = client.ConsulClient.ACL().TokenReadSelf(&api.QueryOptions{Token: secretID}) + _, _, err = consulClient.ACL().TokenReadSelf(&api.QueryOptions{Token: secretID}) if err == nil { c.log.Info("ACL replication token already exists; skipping creation") return nil @@ -296,11 +271,7 @@ func (c *Command) createACL(name, rules string, localToken bool, dc string, isPr var token string err = c.untilSucceeds(fmt.Sprintf("creating token for policy %s", policyTmpl.Name), func() error { - err = client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - createdToken, _, err := client.ConsulClient.ACL().TokenCreate(&tokenTmpl, &api.WriteOptions{}) + createdToken, _, err := consulClient.ACL().TokenCreate(&tokenTmpl, &api.WriteOptions{}) if err == nil { token = createdToken.SecretID } @@ -330,14 +301,9 @@ func (c *Command) createACL(name, rules string, localToken bool, dc string, isPr return nil } -func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, client *consul.DynamicClient) error { - err := client.RefreshClient() - if err != nil { - c.log.Error("could not refresh client", err) - } - +func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, consulClient *api.Client) error { // Attempt to create the ACL policy. - _, _, err = client.ConsulClient.ACL().PolicyCreate(&policy, &api.WriteOptions{}) + _, _, err := consulClient.ACL().PolicyCreate(&policy, &api.WriteOptions{}) // With the introduction of Consul namespaces, if someone upgrades into a // Consul version with namespace support or changes any of their namespace @@ -350,7 +316,7 @@ func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, client *consul.D // The policy ID is required in any PolicyUpdate call, so first we need to // get the existing policy to extract its ID. - existingPolicies, _, err := client.ConsulClient.ACL().PolicyList(&api.QueryOptions{}) + existingPolicies, _, err := consulClient.ACL().PolicyList(&api.QueryOptions{}) if err != nil { return err } @@ -375,7 +341,7 @@ func (c *Command) createOrUpdateACLPolicy(policy api.ACLPolicy, client *consul.D } // Update the policy now that we've found its ID - _, _, err = client.ConsulClient.ACL().PolicyUpdate(&policy, &api.WriteOptions{}) + _, _, err = consulClient.ACL().PolicyUpdate(&policy, &api.WriteOptions{}) return err } return err diff --git a/control-plane/subcommand/server-acl-init/create_or_update_test.go b/control-plane/subcommand/server-acl-init/create_or_update_test.go index 7fd7dc29b3..23ab46347f 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update_test.go +++ b/control-plane/subcommand/server-acl-init/create_or_update_test.go @@ -1,13 +1,8 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( "testing" - "time" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/go-hclog" @@ -42,7 +37,7 @@ func TestCreateOrUpdateACLPolicy_ErrorsIfDescriptionDoesNotMatch(t *testing.T) { svr.WaitForLeader(t) // Get a Consul client. - client, err := consul.NewDynamicClient(&api.Config{ + consul, err := api.NewClient(&api.Config{ Address: svr.HTTPAddr, Token: bootToken, }) @@ -51,7 +46,7 @@ func TestCreateOrUpdateACLPolicy_ErrorsIfDescriptionDoesNotMatch(t *testing.T) { // Create the policy manually. policyDescription := "not the expected description" policyName := "policy-name" - policy, _, err := client.ConsulClient.ACL().PolicyCreate(&api.ACLPolicy{ + policy, _, err := consul.ACL().PolicyCreate(&api.ACLPolicy{ Name: policyName, Description: policyDescription, }, nil) @@ -61,14 +56,14 @@ func TestCreateOrUpdateACLPolicy_ErrorsIfDescriptionDoesNotMatch(t *testing.T) { err = cmd.createOrUpdateACLPolicy(api.ACLPolicy{ Name: policyName, Description: "expected description", - }, client) + }, consul) require.EqualError(err, "policy found with name \"policy-name\" but not with expected description \"expected description\";"+ " if this policy was created manually it must be renamed to something else because this name is reserved by consul-k8s", ) // Check that the policy wasn't modified. - rereadPolicy, _, err := client.ConsulClient.ACL().PolicyRead(policy.ID, nil) + rereadPolicy, _, err := consul.ACL().PolicyRead(policy.ID, nil) require.NoError(err) require.Equal(policyDescription, rereadPolicy.Description) } @@ -94,17 +89,10 @@ func TestCreateOrUpdateACLPolicy(t *testing.T) { svr.WaitForLeader(t) // Get a Consul client. - client, err := consul.NewDynamicClient(&api.Config{ + consul, err := api.NewClient(&api.Config{ Address: svr.HTTPAddr, Token: bootToken, }) - - // Make sure the ACL system is bootstrapped first - require.Eventually(func() bool { - _, _, err := client.ConsulClient.ACL().PolicyList(nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - require.NoError(err) connectInjectRule, err := cmd.injectRules() require.NoError(err) @@ -137,9 +125,9 @@ func TestCreateOrUpdateACLPolicy(t *testing.T) { Name: tt.PolicyName, Description: tt.PolicyDescription, Rules: tt.Rules, - }, client) + }, consul) require.Nil(err) - policy, _, err := client.ConsulClient.ACL().PolicyReadByName(tt.PolicyName, nil) + policy, _, err := consul.ACL().PolicyReadByName(tt.PolicyName, nil) require.Nil(err) require.Equal(tt.Rules, policy.Rules) require.Equal(tt.PolicyName, policy.Name) diff --git a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go deleted file mode 100644 index e4c5d78e38..0000000000 --- a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package serveraclinit - -import ( - "context" - "fmt" - - apiv1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" -) - -const SecretsBackendTypeKubernetes SecretsBackendType = "kubernetes" - -type KubernetesSecretsBackend struct { - ctx context.Context - clientset kubernetes.Interface - k8sNamespace string - secretName string - secretKey string -} - -var _ SecretsBackend = (*KubernetesSecretsBackend)(nil) - -// BootstrapToken returns the existing bootstrap token if there is one by -// reading the Kubernetes Secret. If there is no bootstrap token yet, then -// it returns an empty string (not an error). -func (b *KubernetesSecretsBackend) BootstrapToken() (string, error) { - secret, err := b.clientset.CoreV1().Secrets(b.k8sNamespace).Get(b.ctx, b.secretName, metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - return "", nil - } - return "", err - } - token, ok := secret.Data[b.secretKey] - if !ok { - return "", fmt.Errorf("secret %q does not have data key %q", b.secretName, b.secretKey) - } - return string(token), nil - -} - -// WriteBootstrapToken writes the given bootstrap token to the Kubernetes Secret. -func (b *KubernetesSecretsBackend) WriteBootstrapToken(bootstrapToken string) error { - secret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.secretName, - Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, - }, - Data: map[string][]byte{ - b.secretKey: []byte(bootstrapToken), - }, - } - _, err := b.clientset.CoreV1().Secrets(b.k8sNamespace).Create(b.ctx, secret, metav1.CreateOptions{}) - return err -} - -func (b *KubernetesSecretsBackend) BootstrapTokenSecretName() string { - return b.secretName -} diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index 2732188305..ee6ae41e40 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( @@ -154,10 +151,8 @@ partition "{{ .PartitionName }}" { operator = "write" acl = "write" {{- end }} - {{- if .EnableNamespaces }} namespace_prefix "" { - policy = "write" {{- end }} service_prefix "" { policy = "write" @@ -172,7 +167,7 @@ namespace_prefix "" { {{- if .EnablePartitions }} } {{- end }} -` + ` return c.renderRules(apiGatewayRulesTpl) } @@ -321,16 +316,15 @@ partition "{{ .PartitionName }}" { func (c *Command) injectRules() (string, error) { // The Connect injector needs permissions to create namespaces when namespaces are enabled. // It must also create/update service health checks via the endpoints controller. - // When ACLs are enabled, the endpoints controller (V1) or pod controller (v2) - // needs "acl:write" permissions to delete ACL tokens created via "consul login". - // policy = "write" is required when creating namespaces within a partition. + // When ACLs are enabled, the endpoints controller needs "acl:write" permissions + // to delete ACL tokens created via "consul login". policy = "write" is required when + // creating namespaces within a partition. injectRulesTpl := ` {{- if .EnablePartitions }} partition "{{ .PartitionName }}" { mesh = "write" acl = "write" {{- else }} - mesh = "write" operator = "write" acl = "write" {{- end }} @@ -351,10 +345,6 @@ partition "{{ .PartitionName }}" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } {{- if .EnableNamespaces }} } {{- end }} diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index c1a02a2218..22e63ed0ce 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( @@ -8,9 +5,8 @@ import ( "strings" "testing" - "github.com/stretchr/testify/require" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/stretchr/testify/require" ) func TestAgentRules(t *testing.T) { @@ -147,7 +143,6 @@ func TestAPIGatewayControllerRules(t *testing.T) { cases := []struct { Name string EnableNamespaces bool - Partition string Expected string }{ { @@ -170,26 +165,6 @@ acl = "write" operator = "write" acl = "write" namespace_prefix "" { - policy = "write" - service_prefix "" { - policy = "write" - intentions = "write" - } - node_prefix "" { - policy = "read" - } -}`, - }, - { - Name: "Namespaces are enabled, partitions enabled", - EnableNamespaces: true, - Partition: "Default", - Expected: ` -partition "Default" { - mesh = "write" - acl = "write" -namespace_prefix "" { - policy = "write" service_prefix "" { policy = "write" intentions = "write" @@ -197,7 +172,6 @@ namespace_prefix "" { node_prefix "" { policy = "read" } -} }`, }, } @@ -206,9 +180,7 @@ namespace_prefix "" { t.Run(tt.Name, func(t *testing.T) { cmd := Command{ flagEnableNamespaces: tt.EnableNamespaces, - consulFlags: &flags.ConsulFlags{ - Partition: tt.Partition, - }, + consulFlags: &flags.ConsulFlags{}, } meshGatewayRules, err := cmd.apiGatewayControllerRules() @@ -954,7 +926,6 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: false, Expected: ` - mesh = "write" operator = "write" acl = "write" node_prefix "" { @@ -964,10 +935,6 @@ func TestInjectRules(t *testing.T) { service_prefix "" { policy = "write" intentions = "write" - } - identity_prefix "" { - policy = "write" - intentions = "write" }`, }, { @@ -975,7 +942,6 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: false, Expected: ` - mesh = "write" operator = "write" acl = "write" node_prefix "" { @@ -987,10 +953,6 @@ func TestInjectRules(t *testing.T) { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } }`, }, { @@ -998,7 +960,6 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: true, Expected: ` - mesh = "write" operator = "write" acl = "write" peering = "write" @@ -1011,10 +972,6 @@ func TestInjectRules(t *testing.T) { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } }`, }, { @@ -1036,10 +993,6 @@ partition "part-1" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } } }`, }, @@ -1063,10 +1016,6 @@ partition "part-1" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } } }`, }, diff --git a/control-plane/subcommand/server-acl-init/secrets_backend.go b/control-plane/subcommand/server-acl-init/secrets_backend.go deleted file mode 100644 index e0d74462cf..0000000000 --- a/control-plane/subcommand/server-acl-init/secrets_backend.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package serveraclinit - -type SecretsBackendType string - -type SecretsBackend interface { - // BootstrapToken fetches the bootstrap token from the backend. If the - // token is not found or empty, implementations should return an empty - // string (not an error). - BootstrapToken() (string, error) - - // WriteBootstrapToken writes the given bootstrap token to the backend. - // Implementations of this method do not need to retry the write until - // successful. - WriteBootstrapToken(string) error - - // BootstrapTokenSecretName returns the name of the bootstrap token secret. - BootstrapTokenSecretName() string -} diff --git a/control-plane/subcommand/server-acl-init/servers.go b/control-plane/subcommand/server-acl-init/servers.go index 9665ec8f48..2dc8f8ab67 100644 --- a/control-plane/subcommand/server-acl-init/servers.go +++ b/control-plane/subcommand/server-acl-init/servers.go @@ -1,9 +1,7 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( + "errors" "fmt" "net" "net/http" @@ -11,30 +9,29 @@ import ( "time" "github.com/hashicorp/consul/api" + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // bootstrapServers bootstraps ACLs and ensures each server has an ACL token. -// If a bootstrap is found in the secrets backend, then skip ACL bootstrapping. -// Otherwise, bootstrap ACLs and write the bootstrap token to the secrets backend. -func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, backend SecretsBackend) (string, error) { +// If bootstrapToken is not empty then ACLs are already bootstrapped. +func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, bootstrapToken, bootTokenSecretName string) (string, error) { // Pick the first server address to connect to for bootstrapping and set up connection. firstServerAddr := fmt.Sprintf("%s:%d", serverAddresses[0].IP.String(), c.consulFlags.HTTPPort) - bootstrapToken, err := backend.BootstrapToken() - if err != nil { - return "", fmt.Errorf("Unexpected error fetching bootstrap token secret: %w", err) - } + if bootstrapToken == "" { + c.log.Info("No bootstrap token from previous installation found, continuing on to bootstrapping") - if bootstrapToken != "" { - c.log.Info("Found bootstrap token in secrets backend", "secret", backend.BootstrapTokenSecretName()) - } else { - c.log.Info("No bootstrap token found in secrets backend, continuing to ACL bootstrapping", "secret", backend.BootstrapTokenSecretName()) - bootstrapToken, err = c.bootstrapACLs(firstServerAddr, backend) + var err error + bootstrapToken, err = c.bootstrapACLs(firstServerAddr, bootTokenSecretName) if err != nil { return "", err } + } else { + c.log.Info(fmt.Sprintf("ACLs already bootstrapped - retrieved bootstrap token from Secret %q", bootTokenSecretName)) } // We should only create and set server tokens when servers are running within this cluster. @@ -50,7 +47,7 @@ func (c *Command) bootstrapServers(serverAddresses []net.IPAddr, backend Secrets // bootstrapACLs makes the ACL bootstrap API call and writes the bootstrap token // to a kube secret. -func (c *Command) bootstrapACLs(firstServerAddr string, backend SecretsBackend) (string, error) { +func (c *Command) bootstrapACLs(firstServerAddr, bootTokenSecretName string) (string, error) { config := c.consulFlags.ConsulClientConfig().APIClientConfig config.Address = firstServerAddr // Exempting this particular use of the http client from using global.consulAPITimeout @@ -81,12 +78,9 @@ func (c *Command) bootstrapACLs(firstServerAddr string, backend SecretsBackend) // Check if already bootstrapped. if strings.Contains(err.Error(), "Unexpected response code: 403") { - unrecoverableErr = fmt.Errorf( - "ACLs already bootstrapped but unable to find the bootstrap token in the secrets backend."+ - " We can't proceed without a bootstrap token."+ - " Store a token with `acl:write` permission in the secret %q.", - backend.BootstrapTokenSecretName(), - ) + unrecoverableErr = errors.New("ACLs already bootstrapped but the ACL token was not written to a Kubernetes secret." + + " We can't proceed because the bootstrap token is lost." + + " You must reset ACLs.") return nil } @@ -104,12 +98,21 @@ func (c *Command) bootstrapACLs(firstServerAddr string, backend SecretsBackend) return "", err } - // Write bootstrap token to the secrets backend. - err = c.untilSucceeds(fmt.Sprintf("writing bootstrap Secret %q", backend.BootstrapTokenSecretName()), + // Write bootstrap token to a Kubernetes secret. + err = c.untilSucceeds(fmt.Sprintf("writing bootstrap Secret %q", bootTokenSecretName), func() error { - return backend.WriteBootstrapToken(bootstrapToken) - }, - ) + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: bootTokenSecretName, + Labels: map[string]string{common.CLILabelKey: common.CLILabelValue}, + }, + Data: map[string][]byte{ + common.ACLTokenSecretKey: []byte(bootstrapToken), + }, + } + _, err := c.clientset.CoreV1().Secrets(c.flagK8sNamespace).Create(c.ctx, secret, metav1.CreateOptions{}) + return err + }) return bootstrapToken, err } @@ -120,17 +123,17 @@ func (c *Command) setServerTokens(serverAddresses []net.IPAddr, bootstrapToken s clientConfig := c.consulFlags.ConsulClientConfig().APIClientConfig clientConfig.Address = fmt.Sprintf("%s:%d", serverAddresses[0].IP.String(), c.consulFlags.HTTPPort) clientConfig.Token = bootstrapToken - client, err := consul.NewDynamicClientWithTimeout(clientConfig, + serverClient, err := consul.NewClient(clientConfig, c.consulFlags.APITimeout) if err != nil { return err } - agentPolicy, err := c.setServerPolicy(client) + agentPolicy, err := c.setServerPolicy(serverClient) if err != nil { return err } - existingTokens, _, err := client.ConsulClient.ACL().TokenList(nil) + existingTokens, _, err := serverClient.ACL().TokenList(nil) if err != nil { return err } @@ -198,7 +201,7 @@ func (c *Command) setServerTokens(serverAddresses []net.IPAddr, bootstrapToken s return nil } -func (c *Command) setServerPolicy(client *consul.DynamicClient) (api.ACLPolicy, error) { +func (c *Command) setServerPolicy(consulClient *api.Client) (api.ACLPolicy, error) { agentRules, err := c.agentRules() if err != nil { c.log.Error("Error templating server agent rules", "err", err) @@ -213,7 +216,7 @@ func (c *Command) setServerPolicy(client *consul.DynamicClient) (api.ACLPolicy, } err = c.untilSucceeds("creating agent policy - PUT /v1/acl/policy", func() error { - return c.createOrUpdateACLPolicy(agentPolicy, client) + return c.createOrUpdateACLPolicy(agentPolicy, consulClient) }) if err != nil { return api.ACLPolicy{}, err diff --git a/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go b/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go deleted file mode 100644 index 87826f6c60..0000000000 --- a/control-plane/subcommand/server-acl-init/test_fake_secrets_backend.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package serveraclinit - -type FakeSecretsBackend struct { - bootstrapToken string -} - -func (b *FakeSecretsBackend) BootstrapToken() (string, error) { - return b.bootstrapToken, nil -} - -func (*FakeSecretsBackend) BootstrapTokenSecretName() string { - return "fake-bootstrap-token" -} - -func (b *FakeSecretsBackend) WriteBootstrapToken(token string) error { - b.bootstrapToken = token - return nil -} - -var _ SecretsBackend = (*FakeSecretsBackend)(nil) diff --git a/control-plane/subcommand/server-acl-init/vault_secrets_backend.go b/control-plane/subcommand/server-acl-init/vault_secrets_backend.go deleted file mode 100644 index 5a9097cca3..0000000000 --- a/control-plane/subcommand/server-acl-init/vault_secrets_backend.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package serveraclinit - -import ( - "fmt" - - "github.com/hashicorp/vault/api" -) - -const SecretsBackendTypeVault SecretsBackendType = "vault" - -type VaultSecretsBackend struct { - vaultClient *api.Client - secretName string - secretKey string -} - -var _ SecretsBackend = (*VaultSecretsBackend)(nil) - -// BootstrapToken returns the bootstrap token stored in Vault. -// If not found this returns an empty string (not an error). -func (b *VaultSecretsBackend) BootstrapToken() (string, error) { - secret, err := b.vaultClient.Logical().Read(b.secretName) - if err != nil { - return "", err - } - if secret == nil || secret.Data == nil { - // secret not found or empty. - return "", nil - } - // Grab secret.Data["data"][secretKey]. - dataRaw, found := secret.Data["data"] - if !found { - return "", nil - } - data, ok := dataRaw.(map[string]interface{}) - if !ok { - return "", nil - } - tokRaw, found := data[b.secretKey] - if !found { - return "", nil - } - if tok, ok := tokRaw.(string); ok { - return tok, nil - } - return "", fmt.Errorf("Unexpected data. To resolve this, "+ - "`vault kv put %[1]s=` if Consul is already ACL bootstrapped. "+ - "If not ACL bootstrapped, `vault kv put %[1]s=\"\"`", b.secretKey, b.secretKey) -} - -// BootstrapTokenSecretName returns the name of the bootstrap token secret. -func (b *VaultSecretsBackend) BootstrapTokenSecretName() string { - return b.secretName -} - -// WriteBootstrapToken writes the bootstrap token to Vault. -func (b *VaultSecretsBackend) WriteBootstrapToken(bootstrapToken string) error { - _, err := b.vaultClient.Logical().Write(b.secretName, - map[string]interface{}{ - "data": map[string]interface{}{ - b.secretKey: bootstrapToken, - }, - }, - ) - return err -} diff --git a/control-plane/subcommand/sync-catalog/command.go b/control-plane/subcommand/sync-catalog/command.go index e461121f3d..e146a76667 100644 --- a/control-plane/subcommand/sync-catalog/command.go +++ b/control-plane/subcommand/sync-catalog/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package synccatalog import ( @@ -16,13 +13,6 @@ import ( "time" mapset "github.com/deckarep/golang-set" - "github.com/hashicorp/consul-server-connection-manager/discovery" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - catalogtoconsul "github.com/hashicorp/consul-k8s/control-plane/catalog/to-consul" catalogtok8s "github.com/hashicorp/consul-k8s/control-plane/catalog/to-k8s" "github.com/hashicorp/consul-k8s/control-plane/consul" @@ -30,6 +20,12 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul-server-connection-manager/discovery" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) // Command is the command for syncing the K8S and Consul service diff --git a/control-plane/subcommand/sync-catalog/command_ent_test.go b/control-plane/subcommand/sync-catalog/command_ent_test.go index 8af712dcbe..fac330c557 100644 --- a/control-plane/subcommand/sync-catalog/command_ent_test.go +++ b/control-plane/subcommand/sync-catalog/command_ent_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - //go:build enterprise package synccatalog @@ -13,6 +10,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" @@ -22,8 +20,6 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // Test syncing to a single destination consul namespace. diff --git a/control-plane/subcommand/sync-catalog/command_test.go b/control-plane/subcommand/sync-catalog/command_test.go index 9b7365e801..8228986d00 100644 --- a/control-plane/subcommand/sync-catalog/command_test.go +++ b/control-plane/subcommand/sync-catalog/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package synccatalog import ( @@ -11,6 +8,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" @@ -18,8 +16,6 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // Test flag validation. @@ -557,14 +553,14 @@ func TestRun_ToConsulChangingFlags(t *testing.T) { require.Len(r, instances, 1) require.Equal(r, instances[0].ServiceName, svcName) } - r.Log("existing services verified") + tt.Log("existing services verified") for _, svcName := range c.SecondRunExpDeletedServices { instances, _, err := consulClient.Catalog().Service(svcName, "k8s", nil) require.NoError(r, err) require.Len(r, instances, 0) } - r.Log("deleted services verified") + tt.Log("deleted services verified") }) } }) diff --git a/control-plane/subcommand/tls-init/command.go b/control-plane/subcommand/tls-init/command.go index 467e4cf89a..354a26fdc5 100644 --- a/control-plane/subcommand/tls-init/command.go +++ b/control-plane/subcommand/tls-init/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package tls_init import ( @@ -13,17 +10,16 @@ import ( "sync" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { diff --git a/control-plane/subcommand/tls-init/command_test.go b/control-plane/subcommand/tls-init/command_test.go index 81f5893543..ae3cbd8982 100644 --- a/control-plane/subcommand/tls-init/command_test.go +++ b/control-plane/subcommand/tls-init/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package tls_init import ( diff --git a/control-plane/subcommand/version/command.go b/control-plane/subcommand/version/command.go index 1f6b2aed01..58768a1f92 100644 --- a/control-plane/subcommand/version/command.go +++ b/control-plane/subcommand/version/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package version import ( diff --git a/control-plane/subcommand/webhook-cert-manager/command.go b/control-plane/subcommand/webhook-cert-manager/command.go index ae9d75d29e..214a1d41e2 100644 --- a/control-plane/subcommand/webhook-cert-manager/command.go +++ b/control-plane/subcommand/webhook-cert-manager/command.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhookcertmanager import ( @@ -17,6 +14,11 @@ import ( "syscall" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" "github.com/mitchellh/cli" @@ -24,12 +26,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - webhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/webhook-configuration" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( @@ -267,7 +263,7 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete } iterLog.Info("Updating webhook configuration") - err = webhookconfiguration.UpdateWithCABundle(ctx, c.clientset, bundle.WebhookConfigName, bundle.CACert) + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, bundle.WebhookConfigName, bundle.CACert) if err != nil { iterLog.Error("Error updating webhook configuration") return err @@ -310,7 +306,7 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete } iterLog.Info("Updating webhook configuration with new CA") - err = webhookconfiguration.UpdateWithCABundle(ctx, clientset, bundle.WebhookConfigName, bundle.CACert) + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, clientset, bundle.WebhookConfigName, bundle.CACert) if err != nil { iterLog.Error("Error updating webhook configuration", "err", err) return err @@ -321,21 +317,11 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete // webhookUpdated verifies if every caBundle on the specified webhook configuration matches the desired CA certificate. // It returns true if the CA is up-to date and false if it needs to be updated. func (c *Command) webhookUpdated(ctx context.Context, bundle cert.MetaBundle, clientset kubernetes.Interface) bool { - mutatingWebhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) - if err != nil { - return false - } - for _, webhook := range mutatingWebhookCfg.Webhooks { - if !bytes.Equal(webhook.ClientConfig.CABundle, bundle.CACert) { - return false - } - } - - validatingWebhookCfg, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) + webhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) if err != nil { return false } - for _, webhook := range validatingWebhookCfg.Webhooks { + for _, webhook := range webhookCfg.Webhooks { if !bytes.Equal(webhook.ClientConfig.CABundle, bundle.CACert) { return false } @@ -355,12 +341,8 @@ func (c webhookConfig) validate(ctx context.Context, client kubernetes.Interface if c.Name == "" { err = multierror.Append(err, errors.New(`config.Name cannot be ""`)) } else { - _, mutHookErr := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}) - - _, validatingHookErr := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}) - - if (mutHookErr != nil && k8serrors.IsNotFound(mutHookErr)) && (validatingHookErr != nil && k8serrors.IsNotFound(validatingHookErr)) { - err = multierror.Append(err, fmt.Errorf("ValidatingWebhookConfiguration or MutatingWebhookConfiguration with name \"%s\" must exist in cluster", c.Name)) + if _, err2 := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}); err2 != nil && k8serrors.IsNotFound(err2) { + err = multierror.Append(err, fmt.Errorf("MutatingWebhookConfiguration with name \"%s\" must exist in cluster", c.Name)) } } if c.SecretName == "" { @@ -402,12 +384,10 @@ func (c *Command) sendSignal(sig os.Signal) { c.sigCh <- sig } -const ( - synopsis = "Starts the Consul Kubernetes webhook-cert-manager" - help = ` +const synopsis = "Starts the Consul Kubernetes webhook-cert-manager" +const help = ` Usage: consul-k8s-control-plane webhook-cert-manager [options] Starts the Consul Kubernetes webhook-cert-manager that manages the lifecycle for webhook TLS certificates. ` -) diff --git a/control-plane/subcommand/webhook-cert-manager/command_test.go b/control-plane/subcommand/webhook-cert-manager/command_test.go index dd4b6504c0..7e302d5261 100644 --- a/control-plane/subcommand/webhook-cert-manager/command_test.go +++ b/control-plane/subcommand/webhook-cert-manager/command_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package webhookcertmanager import ( @@ -10,6 +7,8 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager/mocks" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" @@ -20,9 +19,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager/mocks" ) func TestRun_ExitsCleanlyOnSignals(t *testing.T) { @@ -702,7 +698,7 @@ func TestValidate(t *testing.T) { SecretNamespace: "default", }, clientset: fake.NewSimpleClientset(), - expErr: `ValidatingWebhookConfiguration or MutatingWebhookConfiguration with name "webhook-config-name" must exist in cluster`, + expErr: `MutatingWebhookConfiguration with name "webhook-config-name" must exist in cluster`, }, "secretName": { config: webhookConfig{ diff --git a/control-plane/subcommand/webhook-cert-manager/mocks/mocks.go b/control-plane/subcommand/webhook-cert-manager/mocks/mocks.go index 5efa09eb29..d700b192ac 100644 --- a/control-plane/subcommand/webhook-cert-manager/mocks/mocks.go +++ b/control-plane/subcommand/webhook-cert-manager/mocks/mocks.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package mocks import ( diff --git a/control-plane/tenancy/namespace/namespace.go b/control-plane/tenancy/namespace/namespace.go deleted file mode 100644 index 55950bba1d..0000000000 --- a/control-plane/tenancy/namespace/namespace.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package namespace - -import ( - "context" - "fmt" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" -) - -// DeletionTimestampKey is the key in a resource's metadata that stores the timestamp -// when a resource was marked for deletion. This only applies to resources with finalizers. -const DeletionTimestampKey = "deletionTimestamp" - -// EnsureDeleted ensures a Consul namespace with name ns in partition ap is deleted or is in the -// process of being deleted. If neither, it will mark it for deletion. -func EnsureDeleted(ctx context.Context, client pbresource.ResourceServiceClient, ap, ns string) error { - if ns == common.WildcardNamespace || ns == common.DefaultNamespaceName { - return nil - } - - // Check if the Consul namespace exists. - rsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: &pbresource.ID{ - Name: ns, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: ap}, - }}) - - switch { - case status.Code(err) == codes.NotFound: - // Nothing to do - return nil - case err != nil: - // Unexpected error - return fmt.Errorf("namespace read failed: %w", err) - case isMarkedForDeletion(rsp.Resource): - // Deletion already in progress, nothing to do - return nil - default: - // Namespace found, so non-CAS delete it. - _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: rsp.Resource.Id, Version: ""}) - if err != nil { - return fmt.Errorf("namespace delete failed: %w", err) - } - return nil - } -} - -// EnsureExists ensures a Consul namespace with name ns exists and is not marked -// for deletion. If it doesn't, exist it will create it. If it is marked for deletion, -// returns an error. -// -// Boolean return value indicates if the namespace was created by this call. -func EnsureExists(ctx context.Context, client pbresource.ResourceServiceClient, ap, ns string) (bool, error) { - if ns == common.WildcardNamespace || ns == common.DefaultNamespaceName { - return false, nil - } - - // Check if the Consul namespace exists. - rsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: &pbresource.ID{ - Name: ns, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: ap}, - }}) - - switch { - case err == nil && isMarkedForDeletion(rsp.Resource): - // Found, but delete in progress - return false, fmt.Errorf("consul namespace %q deletion in progress", ns) - case err == nil: - // Found and not marked for deletion, nothing to do - return false, nil - case status.Code(err) != codes.NotFound: - // Unexpected error - return false, fmt.Errorf("consul namespace read failed: %w", err) - } - - // Consul namespace not found, so create it - // TODO: Handle creation of crossNSACLPolicy when V2 ACLs are supported - nsData, err := anypb.New(&pbtenancy.Namespace{Description: "Auto-generated by consul-k8s"}) - if err != nil { - return false, err - } - - _, err = client.Write(ctx, &pbresource.WriteRequest{Resource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: ns, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: ap}, - }, - Metadata: map[string]string{"external-source": "kubernetes"}, - Data: nsData, - }}) - - if err != nil { - return false, fmt.Errorf("consul namespace creation failed: %w", err) - } - return true, nil -} - -// isMarkedForDeletion returns true if a resource has been marked for deletion, -// false otherwise. -func isMarkedForDeletion(res *pbresource.Resource) bool { - if res.Metadata == nil { - return false - } - _, ok := res.Metadata[DeletionTimestampKey] - return ok -} diff --git a/control-plane/tenancy/namespace/namespace_controller.go b/control-plane/tenancy/namespace/namespace_controller.go deleted file mode 100644 index e08951b61c..0000000000 --- a/control-plane/tenancy/namespace/namespace_controller.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package namespace - -import ( - "context" - "fmt" - - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - injectcommon "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/consul" -) - -// Namespace syncing between K8s and Consul is vastly simplified when V2 tenancy is enabled. -// Put simply, a K8s namespace maps 1:1 to a Consul namespace of the same name and that is -// the only supported behavior. -// -// The plethora of configuration options available when using V1 tenancy have been removed -// to simplify the user experience and mapping rules. -// -// Hence, the following V1 tenancy namespace helm configuration values are ignored: -// - global.enableConsulNamespaces -// - connectInject.consulNamespaces.consulDestinationNamespace -// - connectInject.consulNamespaces.mirroringK8S -// - connectInject.consulNamespaces.mirroringK8SPrefix. -type Controller struct { - client.Client - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. - common.K8sNamespaceConfig - // ConsulTenancyConfig contains the destination partition. - common.ConsulTenancyConfig - Log logr.Logger -} - -// Reconcile reads a Kubernetes Namespace and reconciles the mapped namespace in Consul. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var namespace corev1.Namespace - - // Ignore the request if the namespace should not be synced to consul. - if injectcommon.ShouldIgnore(req.Name, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - // Create a gRPC resource service client - resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul resource service client", "name", req.Name) - return ctrl.Result{}, err - } - - // Target consul tenancy - consulAP := r.ConsulPartition - consulNS := req.Name - - // Re-read the k8s namespace object - err = r.Client.Get(ctx, req.NamespacedName, &namespace) - - // If the namespace object has been deleted (we get an IsNotFound error), - // we need to remove the Namespace from Consul. - if k8serrors.IsNotFound(err) { - if err := EnsureDeleted(ctx, resourceClient, consulAP, consulNS); err != nil { - return ctrl.Result{}, fmt.Errorf("error deleting consul namespace: %w", err) - } - - return ctrl.Result{}, nil - } else if err != nil { - r.Log.Error(err, "failed to get k8s namespace", "name", req.Name) - return ctrl.Result{}, err - } - - // k8s namespace found, so make sure it is mapped correctly and exists in Consul. - r.Log.Info("retrieved", "k8s namespace", namespace.GetName()) - - if _, err := EnsureExists(ctx, resourceClient, consulAP, consulNS); err != nil { - r.Log.Error(err, "error checking or creating consul namespace", "namespace", consulNS) - return ctrl.Result{}, fmt.Errorf("error checking or creating consul namespace: %w", err) - } - return ctrl.Result{}, nil -} - -// SetupWithManager registers this controller with the manager. -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Namespace{}). - Complete(r) -} diff --git a/control-plane/tenancy/namespace/namespace_controller_ent_test.go b/control-plane/tenancy/namespace/namespace_controller_ent_test.go deleted file mode 100644 index 997164d638..0000000000 --- a/control-plane/tenancy/namespace/namespace_controller_ent_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package namespace - -import ( - "testing" -) - -func TestReconcileCreateNamespace_ENT(t *testing.T) { - testCases := []createTestCase{ - { - name: "consul namespace is ap1/ns1", - kubeNamespace: "ns1", - partition: "ap1", - expectedConsulNamespace: "ns1", - }, - } - testReconcileCreateNamespace(t, testCases) -} - -func TestReconcileDeleteNamespace_ENT(t *testing.T) { - testCases := []deleteTestCase{ - { - name: "non-default partition", - kubeNamespace: "ns1", - partition: "ap1", - existingConsulNamespace: "ns1", - expectNamespaceDeleted: "ns1", - }, - } - testReconcileDeleteNamespace(t, testCases) -} diff --git a/control-plane/tenancy/namespace/namespace_controller_test.go b/control-plane/tenancy/namespace/namespace_controller_test.go deleted file mode 100644 index b9d1cd8728..0000000000 --- a/control-plane/tenancy/namespace/namespace_controller_test.go +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package namespace - -import ( - "context" - "testing" - "time" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" - "github.com/hashicorp/consul/sdk/testutil" -) - -func TestReconcileCreateNamespace(t *testing.T) { - testCases := []createTestCase{ - { - name: "consul namespace is default/ns1", - kubeNamespace: "ns1", - partition: constants.DefaultConsulPartition, - expectedConsulNamespace: "ns1", - }, - } - testReconcileCreateNamespace(t, testCases) -} - -type createTestCase struct { - name string - kubeNamespace string - partition string - expectedConsulNamespace string -} - -// testReconcileCreateNamespace ensures that a new k8s namespace is reconciled to a -// Consul namespace. The actual namespace in Consul depends on if the controller -// is configured with a destination namespace or mirroring enabled. -func testReconcileCreateNamespace(t *testing.T, testCases []createTestCase) { - run := func(t *testing.T, tc createTestCase) { - // Create the default kube namespace and kube namespace under test. - kubeNS := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: tc.kubeNamespace}} - kubeDefaultNS := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: metav1.NamespaceDefault}} - kubeObjects := []runtime.Object{ - &kubeNS, - &kubeDefaultNS, - } - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(kubeObjects...).Build() - - // Fire up consul server with v2tenancy enabled - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis", "v2tenancy"} - }) - - // Create partition if needed - testClient.Cfg.APIClientConfig.Partition = tc.partition - if tc.partition != "" && tc.partition != "default" { - _, err := testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: tc.partition, - Type: pbtenancy.PartitionType, - }, - }}) - require.NoError(t, err, "failed to create partition") - } - - // Create the namespace controller injecting config from tc - nc := &Controller{ - Client: fakeClient, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ConsulTenancyConfig: common.ConsulTenancyConfig{ - ConsulPartition: tc.partition, - }, - Log: logrtest.New(t), - } - - // Reconcile the kube namespace under test - resp, err := nc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: tc.kubeNamespace, - }, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Verify consul namespace exists or was created during reconciliation - _, err = testClient.ResourceClient.Read(context.Background(), &pbresource.ReadRequest{ - Id: &pbresource.ID{ - Name: tc.expectedConsulNamespace, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: tc.partition}, - }, - }) - require.NoError(t, err, "expected partition/namespace %s/%s to exist", tc.partition, tc.expectedConsulNamespace) - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestReconcileDeleteNamespace(t *testing.T) { - testCases := []deleteTestCase{ - { - name: "consul namespace ns1", - kubeNamespace: "ns1", - partition: "default", - existingConsulNamespace: "ns1", - expectNamespaceDeleted: "ns1", - }, - { - name: "consul default namespace does not get deleted", - kubeNamespace: metav1.NamespaceDefault, - partition: "default", - existingConsulNamespace: "", - expectNamespaceExists: "default", - }, - { - name: "namespace is already removed from Consul", - kubeNamespace: "ns1", - partition: "default", - existingConsulNamespace: "", // don't pre-create consul namespace - expectNamespaceDeleted: "ns1", // read as "was never created" - }, - } - testReconcileDeleteNamespace(t, testCases) -} - -type deleteTestCase struct { - name string - kubeNamespace string - partition string - existingConsulNamespace string // If non-empty, this namespace is created in consul pre-reconcile - - // Pick one - expectNamespaceExists string // If non-empty, this namespace should exist in consul post-reconcile - expectNamespaceDeleted string // If non-empty, this namespace should not exist in consul post-reconcile -} - -// Tests deleting a Namespace object, with and without matching Consul namespace. -func testReconcileDeleteNamespace(t *testing.T, testCases []deleteTestCase) { - run := func(t *testing.T, tc deleteTestCase) { - // Don't seed with any kube namespaces since we're testing deletion. - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Fire up consul server with v2tenancy enabled - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis", "v2tenancy"} - }) - - // Create partition if needed - testClient.Cfg.APIClientConfig.Partition = tc.partition - if tc.partition != "" && tc.partition != "default" { - _, err := testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: tc.partition, - Type: pbtenancy.PartitionType, - }, - }}) - require.NoError(t, err, "failed to create partition") - } - - // Create the consul namespace if needed - if tc.existingConsulNamespace != "" && tc.existingConsulNamespace != "default" { - id := &pbresource.ID{ - Name: tc.existingConsulNamespace, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: tc.partition}, - } - - rsp, err := testClient.ResourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{Id: id}}) - require.NoError(t, err, "failed to create namespace") - - // TODO: Remove after https://hashicorp.atlassian.net/browse/NET-6719 implemented - requireEventuallyAccepted(t, testClient.ResourceClient, rsp.Resource.Id) - } - - // Create the namespace controller. - nc := &Controller{ - Client: fakeClient, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ConsulTenancyConfig: common.ConsulTenancyConfig{ - ConsulPartition: tc.partition, - }, - Log: logrtest.New(t), - } - - // Reconcile the kube namespace under test - imagine it has just been deleted - resp, err := nc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: tc.kubeNamespace, - }, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Verify appropriate action was taken on the counterpart consul namespace - if tc.expectNamespaceExists != "" { - // Verify consul namespace was not deleted - _, err = testClient.ResourceClient.Read(context.Background(), &pbresource.ReadRequest{ - Id: &pbresource.ID{ - Name: tc.expectNamespaceExists, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: tc.partition}, - }, - }) - require.NoError(t, err, "expected partition/namespace %s/%s to exist", tc.partition, tc.expectNamespaceExists) - } else if tc.expectNamespaceDeleted != "" { - // Verify consul namespace was deleted - id := &pbresource.ID{ - Name: tc.expectNamespaceDeleted, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: tc.partition}, - } - requireEventuallyNotFound(t, testClient.ResourceClient, id) - } else { - panic("tc.expectedNamespaceExists or tc.expectedNamespaceDeleted must be set") - } - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// RequireStatusAccepted waits for a recently created resource to have a resource status of accepted so that -// attempts to delete it by the single-shot controller under test's reconcile will not fail with a CAS error. -// -// Remove refs to this after https://hashicorp.atlassian.net/browse/NET-6719 is implemented. -func requireEventuallyAccepted(t *testing.T, resourceClient pbresource.ResourceServiceClient, id *pbresource.ID) { - require.Eventuallyf(t, - func() bool { - rsp, err := resourceClient.Read(context.Background(), &pbresource.ReadRequest{Id: id}) - if err != nil { - return false - } - if rsp.Resource.Status == nil || len(rsp.Resource.Status) == 0 { - return false - } - - for _, status := range rsp.Resource.Status { - for _, condition := range status.Conditions { - // common.ConditionAccepted in consul namespace controller - if condition.Type == "accepted" && condition.State == pbresource.Condition_STATE_TRUE { - return true - } - } - } - return false - }, - time.Second*5, - time.Millisecond*100, - "timed out out waiting for %s to have status accepted", - id, - ) -} - -func requireEventuallyNotFound(t *testing.T, resourceClient pbresource.ResourceServiceClient, id *pbresource.ID) { - // allow both "not found" and "marked for deletion" so we're not waiting around unnecessarily - require.Eventuallyf(t, func() bool { - rsp, err := resourceClient.Read(context.Background(), &pbresource.ReadRequest{Id: id}) - if err == nil { - return isMarkedForDeletion(rsp.Resource) - } - if status.Code(err) == codes.NotFound { - return true - } - return false - }, - time.Second*5, - time.Millisecond*100, - "timed out waiting for %s to not be found", - id, - ) -} diff --git a/control-plane/version/fips_build.go b/control-plane/version/fips_build.go deleted file mode 100644 index 63e0e68883..0000000000 --- a/control-plane/version/fips_build.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build fips - -package version - -// This validates during compilation that we are being built with a FIPS enabled go toolchain -import ( - _ "crypto/tls/fipsonly" - "runtime" - "strings" -) - -// IsFIPS returns true if consul-k8s is operating in FIPS-140-2 mode. -func IsFIPS() bool { - return true -} - -func GetFIPSInfo() string { - str := "Enabled" - // Try to get the crypto module name - gover := strings.Split(runtime.Version(), "X:") - if len(gover) >= 2 { - gover_last := gover[len(gover)-1] - // Able to find crypto module name; add that to status string. - str = "FIPS 140-2 Enabled, crypto module " + gover_last - } - return str -} diff --git a/control-plane/version/non_fips_build.go b/control-plane/version/non_fips_build.go deleted file mode 100644 index ce99575d2c..0000000000 --- a/control-plane/version/non_fips_build.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build !fips - -package version - -// IsFIPS returns true if consul-k8s is operating in FIPS-140-2 mode. -func IsFIPS() bool { - return false -} - -func GetFIPSInfo() string { - return "" -} diff --git a/control-plane/version/version.go b/control-plane/version/version.go index f68d1632a6..a7f5aa5cd0 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package version import ( @@ -17,7 +14,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.5.0" + Version = "1.0.12" // 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 @@ -39,12 +36,8 @@ func GetHumanVersion() string { release = "dev" } - if IsFIPS() { - version += "+fips1402" - } - if release != "" { - if !strings.Contains(version, "-"+release) { + if !strings.HasSuffix(version, "-"+release) { // if we tagged a prerelease version then the release is in the version already version += fmt.Sprintf("-%s", release) } diff --git a/docs/admin-partitions-with-acls.md b/docs/admin-partitions-with-acls.md new file mode 100644 index 0000000000..fb282fa38d --- /dev/null +++ b/docs/admin-partitions-with-acls.md @@ -0,0 +1,98 @@ +## Installing Admin Partitions with ACLs enabled + +To enable ACLs on the server cluster use the following config: +```yaml +global: + enableConsulNamespaces: true + tls: + enabled: true + image: hashicorp/consul-enterprise:1.11.1 + adminPartitions: + enabled: true + acls: + manageSystemACLs: true +server: + exposeGossipAndRPCPorts: true + enterpriseLicense: + secretName: license + secretKey: key + replicas: 1 +connectInject: + enabled: true + transparentProxy: + defaultEnabled: false + consulNamespaces: + mirroringK8S: true +controller: + enabled: true +meshGateway: + enabled: true +``` + +Identify the LoadBalancer External IP of the `partition-service` +```bash +kubectl get svc consul-consul-partition-service -o json | jq -r '.status.loadBalancer.ingress[0].ip' +``` + +Migrate the TLS CA credentials from the server cluster to the workload clusters +```bash +kubectl get secret consul-consul-ca-key --context "server-context" -o json | kubectl apply --context "workload-context" -f - +kubectl get secret consul-consul-ca-cert --context "server-context" -o json | kubectl apply --context "workload-context" -f - +``` + +Migrate the Partition token from the server cluster to the workload clusters +```bash +kubectl get secret consul-consul-partitions-acl-token --context "server-context" -o json | kubectl apply --context "workload-context" -f - +``` + +Identify the Kubernetes AuthMethod URL of the workload cluster to use as the `k8sAuthMethodHost`: +```bash +kubectl config view -o "jsonpath={.clusters[?(@.name=='workload-cluster-name')].cluster.server}" +``` + +Configure the workload cluster using the following: + +```yaml +global: + enabled: false + enableConsulNamespaces: true + image: hashicorp/consul-enterprise:1.11.1 + adminPartitions: + enabled: true + name: "partition-name" + tls: + enabled: true + caCert: + secretName: consul-consul-ca-cert + secretKey: tls.crt + caKey: + secretName: consul-consul-ca-key + secretKey: tls.key + acls: + manageSystemACLs: true + bootstrapToken: + secretName: consul-consul-partitions-acl-token + secretKey: token +server: + enterpriseLicense: + secretName: license + secretKey: key +externalServers: + enabled: true + hosts: [ "loadbalancer IP" ] + tlsServerName: server.dc1.consul + k8sAuthMethodHost: "authmethod-host IP" +client: + enabled: true + exposeGossipPorts: true + join: [ "loadbalancer IP" ] +connectInject: + enabled: true + consulNamespaces: + mirroringK8S: true +controller: + enabled: true +meshGateway: + enabled: true +``` +This should create clusters that have Admin Partitions deployed on them with ACLs enabled. diff --git a/hack/aws-acceptance-test-cleanup/go.mod b/hack/aws-acceptance-test-cleanup/go.mod index 27de04bc87..5cb3bd1452 100644 --- a/hack/aws-acceptance-test-cleanup/go.mod +++ b/hack/aws-acceptance-test-cleanup/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul-helm/hack/aws-acceptance-test-cleanup -go 1.20 +go 1.19 require ( github.com/aws/aws-sdk-go v1.38.63 diff --git a/hack/aws-acceptance-test-cleanup/main.go b/hack/aws-acceptance-test-cleanup/main.go index e4094ec47e..b72b88c12f 100644 --- a/hack/aws-acceptance-test-cleanup/main.go +++ b/hack/aws-acceptance-test-cleanup/main.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main // This script deletes AWS resources created for acceptance tests that have @@ -25,7 +22,6 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/elb" - "github.com/aws/aws-sdk-go/service/iam" "github.com/cenkalti/backoff/v4" ) @@ -39,11 +35,6 @@ var ( errNotDestroyed = errors.New("not yet destroyed") ) -type oidc struct { - arn string - buildUrl string -} - func main() { flag.BoolVar(&flagAutoApprove, "auto-approve", false, "Skip interactive approval before destroying.") flag.Parse() @@ -74,106 +65,6 @@ func realMain(ctx context.Context) error { eksClient := eks.New(clientSession, awsCfg) ec2Client := ec2.New(clientSession, awsCfg) elbClient := elb.New(clientSession, awsCfg) - iamClient := iam.New(clientSession, awsCfg) - - // Find OIDC providers to delete. - oidcProvidersOutput, err := iamClient.ListOpenIDConnectProvidersWithContext(ctx, &iam.ListOpenIDConnectProvidersInput{}) - if err != nil { - return err - } else if oidcProvidersOutput == nil { - return fmt.Errorf("nil output for OIDC Providers") - } - - toDeleteOidcArns := []*oidc{} - for _, providerEntry := range oidcProvidersOutput.OpenIDConnectProviderList { - arnString := "" - if providerEntry.Arn != nil { - arnString = *providerEntry.Arn - } - // Check if it's older than 8 hours. - older, err := oidcOlderThanEightHours(ctx, iamClient, providerEntry.Arn) - if err != nil { - return err - } - // Only add to delete list if it's older than 8 hours and has a buildURL tag. - if older { - output, err := iamClient.ListOpenIDConnectProviderTags(&iam.ListOpenIDConnectProviderTagsInput{OpenIDConnectProviderArn: providerEntry.Arn}) - if err != nil { - return err - } - for _, tag := range output.Tags { - if tag.Key != nil && *tag.Key == buildURLTag { - var buildUrl string - if tag.Value != nil { - buildUrl = *tag.Value - } - toDeleteOidcArns = append(toDeleteOidcArns, &oidc{arn: arnString, buildUrl: buildUrl}) - } - } - } else { - fmt.Printf("Skipping OIDC provider: %s because it's not over 8 hours old\n", arnString) - } - } - - oidcProvidersExist := true - if len(toDeleteOidcArns) == 0 { - fmt.Println("Found no OIDC Providers to clean up") - oidcProvidersExist = false - } else { - // Print out the OIDC Provider arns and the build tags. - var oidcPrint string - for _, oidcProvider := range toDeleteOidcArns { - oidcPrint += fmt.Sprintf("- %s (%s)\n", oidcProvider.arn, oidcProvider.buildUrl) - } - - fmt.Printf("Found OIDC Providers:\n%s", oidcPrint) - } - - // Check for approval. - if !flagAutoApprove && oidcProvidersExist { - type input struct { - text string - err error - } - inputCh := make(chan input) - - // Read input in a goroutine so we can also exit if we get a Ctrl-C - // (see select{} below). - go func() { - reader := bufio.NewReader(os.Stdin) - fmt.Println("\nDo you want to delete these OIDC Providers (y/n)?") - inputStr, err := reader.ReadString('\n') - if err != nil { - inputCh <- input{err: err} - return - } - inputCh <- input{text: inputStr} - }() - - select { - case in := <-inputCh: - if in.err != nil { - return in.err - } - inputTrimmed := strings.TrimSpace(in.text) - if inputTrimmed != "y" && inputTrimmed != "yes" { - return errors.New("exiting after negative") - } - case <-ctx.Done(): - return errors.New("context cancelled") - } - } - - // Actually delete the OIDC providers. - for _, oidcArn := range toDeleteOidcArns { - fmt.Printf("Deleting OIDC provider: %s\n", oidcArn.arn) - _, err := iamClient.DeleteOpenIDConnectProviderWithContext(ctx, &iam.DeleteOpenIDConnectProviderInput{ - OpenIDConnectProviderArn: &oidcArn.arn, - }) - if err != nil { - return err - } - } // Find VPCs to delete. Most resources we create belong to a VPC, except // for IAM resources, and so if there are no VPCs, that means all leftover resources have been deleted. @@ -643,25 +534,6 @@ func realMain(ctx context.Context) error { return nil } -// oidcOlderThanEightHours checks if the oidc provider is older than 8 hours. -func oidcOlderThanEightHours(ctx context.Context, iamClient *iam.IAM, oidcArn *string) (bool, error) { - fullOidc, err := iamClient.GetOpenIDConnectProviderWithContext(ctx, &iam.GetOpenIDConnectProviderInput{ - OpenIDConnectProviderArn: oidcArn, - }) - if err != nil { - return false, err - } - if fullOidc != nil { - if fullOidc.CreateDate != nil { - d := time.Since(*fullOidc.CreateDate) - if d.Hours() > 8 { - return true, nil - } - } - } - return false, nil -} - func vpcNameAndBuildURL(vpc *ec2.Vpc) (string, string) { var vpcName string var buildURL string diff --git a/hack/camel-crds/go.mod b/hack/camel-crds/go.mod deleted file mode 100644 index 0262e70483..0000000000 --- a/hack/camel-crds/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module github.com/hashicorp/consul-k8s/hack/copy-crds-to-chart - -go 1.20 - -require ( - github.com/iancoleman/strcase v0.3.0 - sigs.k8s.io/yaml v1.3.0 -) - -require ( - github.com/kr/pretty v0.3.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect -) diff --git a/hack/camel-crds/go.sum b/hack/camel-crds/go.sum deleted file mode 100644 index 21efe55d32..0000000000 --- a/hack/camel-crds/go.sum +++ /dev/null @@ -1,25 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -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= -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= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/camel-crds/main.go b/hack/camel-crds/main.go deleted file mode 100644 index dde2ac3de2..0000000000 --- a/hack/camel-crds/main.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Script to parse a YAML CRD file and change all the -// snake_case keys to camelCase and rewrite the file in-situ -package main - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/iancoleman/strcase" - "sigs.k8s.io/yaml" -) - -func main() { - if len(os.Args) != 1 { - fmt.Println("Usage: go run ./...") - os.Exit(1) - } - - if err := realMain(); err != nil { - fmt.Printf("Error: %s\n", err) - os.Exit(1) - } - os.Exit(0) -} - -func realMain() error { - root := "../../control-plane/config/crd/" - // explicitly ignore the `external` folder since we only want this to apply to CRDs that we have built-in this project. - dirs := []string{"bases"} - - for _, dir := range dirs { - err := filepath.Walk(root+dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() || filepath.Ext(path) != ".yaml" || filepath.Base(path) == "kustomization.yaml" { - return nil - } - printf("processing %s", filepath.Base(path)) - - contentBytes, err := os.ReadFile(path) - if err != nil { - return err - } - - jsonBytes, err := yaml.YAMLToJSON(contentBytes) - if err != nil { - return err - } - fixedJsonBytes := convertKeys(jsonBytes) - contentsCamel, err := yaml.JSONToYAML(fixedJsonBytes) - return os.WriteFile(path, contentsCamel, os.ModePerm) - }) - if err != nil { - return err - } - } - return nil -} - -func convertKeys(j json.RawMessage) json.RawMessage { - m := make(map[string]json.RawMessage) - n := make([]json.RawMessage, 0) - array := false - if err := json.Unmarshal(j, &m); err != nil { - // Not a JSON object - errArray := json.Unmarshal(j, &n) - if errArray != nil { - return j - } else { - array = true - } - } - if !array { - for k, v := range m { - if k == "annotations" { - continue - } - var fixed string - if !strings.Contains(k, "_") { - fixed = k - } else { - fixed = strcase.ToLowerCamel(k) - } - delete(m, k) - m[fixed] = convertKeys(v) - } - - b, err := json.Marshal(m) - if err != nil { - fmt.Printf("something went wrong", err) - return j - } - return b - } else { - for i, message := range n { - fixed := convertKeys(message) - n[i] = fixed - } - b, err := json.Marshal(n) - if err != nil { - fmt.Printf("something went wrong", err) - return j - } - return b - } -} - -func printf(format string, args ...interface{}) { - fmt.Println(fmt.Sprintf(format, args...)) -} diff --git a/hack/copy-crds-to-chart/go.mod b/hack/copy-crds-to-chart/go.mod index c224f8f244..73b1f10306 100644 --- a/hack/copy-crds-to-chart/go.mod +++ b/hack/copy-crds-to-chart/go.mod @@ -1,3 +1,3 @@ module github.com/hashicorp/consul-k8s/hack/copy-crds-to-chart -go 1.20 +go 1.19 diff --git a/hack/copy-crds-to-chart/main.go b/hack/copy-crds-to-chart/main.go index 7835a6244a..7276c468f8 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - // Script to copy generated CRD yaml into chart directory and modify it to match // the expected chart format (e.g. formatted YAML). package main @@ -12,21 +9,6 @@ import ( "strings" ) -var ( - // HACK IT! - requiresPeering = map[string]struct{}{ - "consul.hashicorp.com_peeringacceptors.yaml": {}, - "consul.hashicorp.com_peeringdialers.yaml": {}, - } - - // includeV1Suffix is used to add a ...-v1.yaml suffix for types that exist in - // v1 and v2 APIs with the same name and would otherwise result in last man wins - includeV1Suffix = map[string]struct{}{ - "consul.hashicorp.com_exportedservices.yaml": {}, - "consul.hashicorp.com_gatewayclassconfigs.yaml": {}, - } -) - func main() { if len(os.Args) != 1 { fmt.Println("Usage: go run ./...") @@ -41,94 +23,54 @@ func main() { } func realMain(helmPath string) error { - root := "../../control-plane/config/crd/" - dirs := []string{"bases", "external"} - - for _, dir := range dirs { - err := filepath.Walk(root+dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() || filepath.Ext(path) != ".yaml" || filepath.Base(path) == "kustomization.yaml" { - return nil - } - - printf("processing %s", filepath.Base(path)) - - contentBytes, err := os.ReadFile(path) - if err != nil { - return err - } - contents := string(contentBytes) - - // Strip leading newline. - contents = strings.TrimPrefix(contents, "\n") - - if _, ok := requiresPeering[info.Name()]; ok { - // Add {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} {{- end }} wrapper. - contents = fmt.Sprintf("{{- if and .Values.connectInject.enabled .Values.global.peering.enabled }}\n%s{{- end }}\n", contents) - } else if dir == "external" { - contents = fmt.Sprintf("{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }}\n%s{{- end }}\n", contents) - } else { - // Add {{- if .Values.connectInject.enabled }} {{- end }} wrapper. - contents = fmt.Sprintf("{{- if .Values.connectInject.enabled }}\n%s{{- end }}\n", contents) - } - - // Add labels, this is hacky because we're relying on the line number - // but it means we don't need to regex or yaml parse. - splitOnNewlines := strings.Split(contents, "\n") - labelLines := []string{ - ` labels:`, - ` app: {{ template "consul.name" . }}`, - ` chart: {{ template "consul.chart" . }}`, - ` heritage: {{ .Release.Service }}`, - ` release: {{ .Release.Name }}`, - ` component: crd`, - } - var split int - if dir == "bases" { - split = 6 - } else { - split = 9 - } - withLabels := append(splitOnNewlines[0:split], append(labelLines, splitOnNewlines[split:]...)...) - contents = strings.Join(withLabels, "\n") + return filepath.Walk("../../control-plane/config/crd/bases", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } - suffix := "" - if _, ok := includeV1Suffix[info.Name()]; ok { - suffix = "-v1" - } + if info.IsDir() || filepath.Ext(path) != ".yaml" { + return nil + } - var crdName string - if dir == "bases" { - // Construct the destination filename. - filenameSplit := strings.Split(info.Name(), "_") - filenameSplit = strings.Split(filenameSplit[1], ".") - crdName = filenameSplit[0] + suffix + ".yaml" - } else if dir == "external" { - filenameSplit := strings.Split(info.Name(), ".") - crdName = filenameSplit[0] + suffix + "-external.yaml" - } + printf("processing %s", filepath.Base(path)) - destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName)) - // Write it. - printf("writing to %s", destinationPath) - return os.WriteFile(destinationPath, []byte(contents), 0644) - }) + contentBytes, err := os.ReadFile(path) if err != nil { return err } - } - return nil + contents := string(contentBytes) + + // Strip leading newline. + contents = strings.TrimPrefix(contents, "\n") + + // Add {{- if .Values.connectInject.enabled }} {{- end }} wrapper. + contents = fmt.Sprintf("{{- if .Values.connectInject.enabled }}\n%s{{- end }}\n", contents) + + // Add labels, this is hacky because we're relying on the line number + // but it means we don't need to regex or yaml parse. + splitOnNewlines := strings.Split(contents, "\n") + labelLines := []string{ + ` labels:`, + ` app: {{ template "consul.name" . }}`, + ` chart: {{ template "consul.chart" . }}`, + ` heritage: {{ .Release.Service }}`, + ` release: {{ .Release.Name }}`, + ` component: crd`, + } + withLabels := append(splitOnNewlines[0:9], append(labelLines, splitOnNewlines[9:]...)...) + contents = strings.Join(withLabels, "\n") + + // Construct the destination filename. + filenameSplit := strings.Split(info.Name(), "_") + crdName := filenameSplit[1] + destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName)) + + // Write it. + printf("writing to %s", destinationPath) + return os.WriteFile(destinationPath, []byte(contents), 0644) + }) } func printf(format string, args ...interface{}) { fmt.Println(fmt.Sprintf(format, args...)) } - -func formatCRDName(name string) string { - name = strings.TrimSuffix(name, ".yaml") - segments := strings.Split(name, "_") - return fmt.Sprintf("%s.%s.yaml", segments[1], segments[0]) -} diff --git a/hack/helm-reference-gen/doc_node.go b/hack/helm-reference-gen/doc_node.go index cf28cf9374..a183ead969 100644 --- a/hack/helm-reference-gen/doc_node.go +++ b/hack/helm-reference-gen/doc_node.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( diff --git a/hack/helm-reference-gen/fixtures/full-values.yaml b/hack/helm-reference-gen/fixtures/full-values.yaml index a79fbc6944..b0eec07666 100644 --- a/hack/helm-reference-gen/fixtures/full-values.yaml +++ b/hack/helm-reference-gen/fixtures/full-values.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - # Available parameters and their default values for the Consul chart. # Holds values that affect multiple components of the chart. diff --git a/hack/helm-reference-gen/go.mod b/hack/helm-reference-gen/go.mod index 165222c84e..3d52e1bcfa 100644 --- a/hack/helm-reference-gen/go.mod +++ b/hack/helm-reference-gen/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul-k8s/hack/helm-reference-gen -go 1.20 +go 1.19 require ( github.com/stretchr/testify v1.7.2 diff --git a/hack/helm-reference-gen/main.go b/hack/helm-reference-gen/main.go index 0bc623cff0..5f23143caa 100644 --- a/hack/helm-reference-gen/main.go +++ b/hack/helm-reference-gen/main.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main // This script generates markdown documentation out of the values.yaml file @@ -162,14 +159,11 @@ func GenerateDocs(yamlStr string) (string, error) { return "", err } - docsStr := strings.Join(children, "\n\n") - docsStr = strings.ReplaceAll(docsStr, "[Enterprise Only]", "") - // Remove https://developer.hashicorp.com prefix from links because docs linting requires it. - docsStr = strings.ReplaceAll(docsStr, "https://developer.hashicorp.com/", "/") + enterpriseSubst := strings.ReplaceAll(strings.Join(children, "\n\n"), "[Enterprise Only]", "") // Add table of contents. toc := generateTOC(node) - return toc + "\n\n" + docsStr + "\n", nil + return toc + "\n\n" + enterpriseSubst + "\n", nil } // Parse parses yamlStr into a tree of DocNode's. diff --git a/hack/helm-reference-gen/main_test.go b/hack/helm-reference-gen/main_test.go index fc91032203..03e8648218 100644 --- a/hack/helm-reference-gen/main_test.go +++ b/hack/helm-reference-gen/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( diff --git a/hack/helm-reference-gen/parse_error.go b/hack/helm-reference-gen/parse_error.go index 914737db5f..f474664f1f 100644 --- a/hack/helm-reference-gen/parse_error.go +++ b/hack/helm-reference-gen/parse_error.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import "fmt" From 378632f0c60c204b36d4f0e06f8273482c86f92a Mon Sep 17 00:00:00 2001 From: Melisa Griffin Date: Tue, 6 Feb 2024 13:31:31 +0000 Subject: [PATCH 592/592] backport of commit 2264c14f5d7d03f764d506a106ff987c92bfe0e4 --- .../test/unit/mesh-gateway-deployment.bats | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/charts/consul/test/unit/mesh-gateway-deployment.bats b/charts/consul/test/unit/mesh-gateway-deployment.bats index 8e08463c43..70a5884e74 100755 --- a/charts/consul/test/unit/mesh-gateway-deployment.bats +++ b/charts/consul/test/unit/mesh-gateway-deployment.bats @@ -1772,4 +1772,21 @@ key2: value2' \ local actual=$(echo "$cmd" | yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# security context + +@test "meshGateway/Deployment: security context is set on Consul Dataplane" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/mesh-gateway-deployment.yaml \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].securityContext' | tee /dev/stderr) + + [ $(echo "${actual}" | yq -r '.capabilities.drop[0]') = "ALL" ] + [ $(echo "${actual}" | yq -r '.capabilities.add[0]') = "NET_BIND_SERVICE" ] + } \ No newline at end of file